/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /* * This code is based on the CRAB engine * * Copyright (c) Arvind Raja Yadav * * Licensed under MIT * */ #include "crab/event/gameeventmanager.h" namespace Crab { using namespace pyrodactyl::people; using namespace pyrodactyl::event; using namespace pyrodactyl::level; using namespace pyrodactyl::image; using namespace pyrodactyl::ui; void Manager::init() { _eventMap.clear(); _activeSeq = UINT_MAX; _curSp = nullptr; _player = false; _curEvent = nullptr; _drawGame = true; } //------------------------------------------------------------------------ // Purpose: Load this //------------------------------------------------------------------------ void Manager::load(rapidxml::xml_node *node, ParagraphData &popup) { if (nodeValid(node)) { XMLDoc conf(node->first_attribute("list")->value()); if (conf.ready()) { rapidxml::xml_node *lnode = conf.doc()->first_node("event_list"); for (rapidxml::xml_node *loc = lnode->first_node("loc"); loc != nullptr; loc = loc->next_sibling("loc")) { Common::String locName; loadStr(locName, "name", loc); for (auto n = loc->first_node("file"); n != nullptr; n = n->next_sibling("file")) { uint id; Common::Path path; loadNum(id, "name", n); loadPath(path, "path", n); _eventMap[locName].addSeq(id, path); } } } _activeSeq = UINT_MAX; conf.load(node->first_attribute("layout")->value()); if (conf.ready()) { rapidxml::xml_node *layout = conf.doc()->first_node("layout"); if (nodeValid(layout)) { if (nodeValid("character", layout)) _oh.load(layout->first_node("character")); if (nodeValid("popup", layout)) popup.load(layout->first_node("popup")); if (nodeValid("intro", layout)) _intro.load(layout->first_node("intro")); } } _reply.load(node->first_attribute("conversation")->value()); _per.load(node->first_attribute("char")->value()); } } //------------------------------------------------------------------------ // Purpose: Handle events //------------------------------------------------------------------------ void Manager::handleEvents(Info &info, const Common::String &playerId, Common::Event &event, HUD &hud, Level &level, Common::Array &result) { // If an event is already being performed if (_eventMap.contains(info.curLocID()) && _eventMap[info.curLocID()].eventInProgress(_activeSeq)) { switch (_curEvent->_type) { case EVENT_DIALOG: if (_oh._showJournal) { info._journal.handleEvents(playerId, event); if (hud._back.handleEvents(event) == BUAC_LCLICK || hud._pausekey.handleEvents(event)) _oh._showJournal = false; } else { // If journal button is select from within an event, go to the entry corresponding to that person's name if (_oh.handleCommonEvents(event)) { if (info.personValid(_curEvent->_title)) { Person &p = info.personGet(_curEvent->_title); if (p._altJournalName) info._journal.open(playerId, JE_PEOPLE, p._journalName); else info._journal.open(playerId, JE_PEOPLE, p._name); } } if (_oh.handleDlboxEvents(event)) { _oh.onExit(); _eventMap[info.curLocID()].nextEvent(_activeSeq, info, playerId, result, _endSeq); _oh._showJournal = false; } } break; case EVENT_ANIM: // Skip animation if key pressed or mouse pressed if (event.type == Common::EVENT_LBUTTONUP || event.type == Common::EVENT_RBUTTONUP) _eventMap[info.curLocID()].nextEvent(_activeSeq, info, playerId, result, _endSeq); break; case EVENT_REPLY: if (_oh._showJournal) { info._journal.handleEvents(playerId, event); if (hud._back.handleEvents(event) == BUAC_LCLICK || hud._pausekey.handleEvents(event)) _oh._showJournal = false; } else { // If journal button is select from within an event, go to the entry corresponding to that person's name if (_oh.handleCommonEvents(event)) if (info.personValid(_curEvent->_title)) info._journal.open(playerId, JE_PEOPLE, info.personGet(_curEvent->_title)._name); unsigned int option = static_cast(_reply.hoverIndex()); const auto &replyChoice = g_engine->_eventStore->_con[_curEvent->_special]._reply; if (option < replyChoice.size()) { _intro.onEntry(replyChoice[option]._text); } int choice = _reply.handleEvents(info, g_engine->_eventStore->_con[_curEvent->_special], _curEvent->_title, _oh, event); if (choice >= 0) { _reply.onExit(); _eventMap[info.curLocID()].nextEvent(_activeSeq, info, playerId, result, _endSeq, choice); _oh._showJournal = false; } } break; case EVENT_TEXT: // If journal button is select from within an event, go to the entry corresponding to that person's name if (_oh.handleCommonEvents(event)) if (info.personValid(_curEvent->_title)) info._journal.open(playerId, JE_PEOPLE, info.personGet(_curEvent->_title)._name); if (_textin.handleEvents(event)) _eventMap[info.curLocID()].nextEvent(_activeSeq, info, playerId, result, _endSeq); break; case EVENT_SPLASH: if (_intro._showTraits) { _per.handleEvents(info, _curEvent->_title, event); if (hud._back.handleEvents(event) == BUAC_LCLICK || hud._pausekey.handleEvents(event)) _intro._showTraits = false; } else { if (_intro.handleEvents(event)) { _intro.onExit(); _eventMap[info.curLocID()].nextEvent(_activeSeq, info, playerId, result, _endSeq); } if (_intro._showTraits) _per.Cache(info, level.playerId(), level); } break; default: break; } endSequence(info.curLocID()); } } //------------------------------------------------------------------------ // Purpose: Internal Events //------------------------------------------------------------------------ void Manager::internalEvents(Info &info, Level &level, Common::Array &result) { if (_eventMap.contains(info.curLocID())) { if (_eventMap[info.curLocID()].eventInProgress(_activeSeq)) { switch (_curEvent->_type) { case EVENT_DIALOG: // fallthrough intended case EVENT_REPLY: // fallthrough intended case EVENT_SPLASH: updateDialogBox(info, level); break; case EVENT_ANIM: { using namespace pyrodactyl::anim; DrawType drawVal = DRAW_SAME; if (g_engine->_eventStore->_anim[_curEvent->_special].internalEvents(drawVal)) _eventMap[info.curLocID()].nextEvent(_activeSeq, info, level.playerId(), result, _endSeq); if (drawVal == DRAW_STOP) _drawGame = false; else if (drawVal == DRAW_START) _drawGame = true; } break; case EVENT_SILENT: _eventMap[info.curLocID()].nextEvent(_activeSeq, info, level.playerId(), result, _endSeq); break; default: break; } endSequence(info.curLocID()); } else { _eventMap[info.curLocID()].internalEvents(info); calcActiveSeq(info, level, level.camera()); } } } void Manager::updateDialogBox(Info &info, pyrodactyl::level::Level &level) { _oh.internalEvents(_curEvent->_state, _curSp); } //------------------------------------------------------------------------ // Purpose: Draw //------------------------------------------------------------------------ void Manager::draw(Info &info, HUD &hud, Level &level) { if (_eventMap.contains(info.curLocID()) && _eventMap[info.curLocID()].eventInProgress(_activeSeq)) { switch (_curEvent->_type) { case EVENT_ANIM: g_engine->_eventStore->_anim[_curEvent->_special].draw(); break; case EVENT_DIALOG: g_engine->_imageManager->dimScreen(); if (_oh._showJournal) { info._journal.draw(level.playerId()); hud._back.draw(); } else _oh.draw(info, _curEvent, _curEvent->_title, _player, _curSp); break; case EVENT_REPLY: g_engine->_imageManager->dimScreen(); if (_oh._showJournal) { info._journal.draw(level.playerId()); hud._back.draw(); } else { _oh.draw(info, _curEvent, _curEvent->_title, _player, _curSp); _reply.draw(); } break; case EVENT_TEXT: _oh.draw(info, _curEvent, _curEvent->_title, _player, _curSp); _textin.draw(); break; case EVENT_SPLASH: g_engine->_imageManager->dimScreen(); if (_intro._showTraits) { _per.draw(info, _curEvent->_title); hud._back.draw(); } else _intro.draw(info, _curEvent->_dialog, _curSp, _curEvent->_state); break; default: break; } } } //------------------------------------------------------------------------ // Purpose: Calculate the current sequence in progress //------------------------------------------------------------------------ void Manager::calcActiveSeq(Info &info, pyrodactyl::level::Level &level, const Rect &camera) { if (_eventMap[info.curLocID()].activeSeq(_activeSeq)) { // Set all the pointers to the new values _curEvent = _eventMap[info.curLocID()].curEvent(_activeSeq); _oh.reset(_curEvent->_title); _curSp = level.getSprite(_curEvent->_title); // The player character's dialog is drawn a bit differently compared to others _player = (_curEvent->_title == level.playerId()); switch (_curEvent->_type) { case EVENT_DIALOG: _oh.onEntry(_curEvent->_dialog); break; case EVENT_ANIM: g_engine->_eventStore->_anim[_curEvent->_special].start(); break; case EVENT_REPLY: _reply.onEntry(_curEvent->_dialog); _reply.cache(info, g_engine->_eventStore->_con[_curEvent->_special]); break; case EVENT_SPLASH: _intro.onEntry(_curEvent->_dialog); break; default: break; } } } //------------------------------------------------------------------------ // Purpose: Get/set info //------------------------------------------------------------------------ void Manager::endSequence(const Common::String &curloc) { if (_endSeq.empty()) return; for (const auto &i : _endSeq) if (i._cur) _eventMap[curloc].endSeq(_activeSeq); else if (_eventMap.contains(i._loc)) _eventMap[i._loc].endSeq(stringToNumber(i._val)); _activeSeq = UINT_MAX; _endSeq.clear(); } bool Manager::eventInProgress() { if (_activeSeq == UINT_MAX) return false; return true; } //------------------------------------------------------------------------ // Purpose: Save the state of the object //------------------------------------------------------------------------ void Manager::saveState(rapidxml::xml_document<> &doc, rapidxml::xml_node *root) { for (auto &i : _eventMap) { rapidxml::xml_node *child = doc.allocate_node(rapidxml::node_element, "loc"); child->append_attribute(doc.allocate_attribute("name", i._key.c_str())); i._value.saveState(doc, child); root->append_node(child); } } //------------------------------------------------------------------------ // Purpose: Load the state of the object //------------------------------------------------------------------------ void Manager::loadState(rapidxml::xml_node *node) { for (auto n = node->first_node("loc"); n != nullptr; n = n->next_sibling("loc")) { if (n->first_attribute("name") != nullptr) { Common::String name = n->first_attribute("name")->value(); if (_eventMap.contains(name)) _eventMap[name].loadState(n); } } } //------------------------------------------------------------------------ // Purpose: Function called when window size is changed to adjust UI //------------------------------------------------------------------------ void Manager::setUI() { _oh.setUI(); _reply.setUI(); _textin.setUI(); _per.setUI(); } } // End of namespace Crab