/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "lastexpress/game/logic.h" // Data #include "lastexpress/data/animation.h" #include "lastexpress/data/cursor.h" #include "lastexpress/data/snd.h" // Entities #include "lastexpress/entities/chapters.h" // Fight #include "lastexpress/fight/fight.h" // Game #include "lastexpress/game/action.h" #include "lastexpress/game/beetle.h" #include "lastexpress/game/entities.h" #include "lastexpress/game/inventory.h" #include "lastexpress/game/object.h" #include "lastexpress/game/savegame.h" #include "lastexpress/game/savepoint.h" #include "lastexpress/game/scenes.h" #include "lastexpress/game/state.h" #include "lastexpress/menu/menu.h" #include "lastexpress/sound/queue.h" #include "lastexpress/lastexpress.h" #include "lastexpress/resource.h" namespace LastExpress { #define EVENT_TICKS_BEETWEEN_SAVEGAMES 450 #define GAME_TICKS_BEETWEEN_SAVEGAMES 2700 Logic::Logic(LastExpressEngine *engine) : _engine(engine) { _action = new Action(engine); _beetle = new Beetle(engine); _entities = new Entities(engine); _fight = new Fight(engine); _saveload = new SaveLoad(engine); _state = new State(engine); // Flags _flagActionPerformed = false; _ignoreFrameInterval = false; _ticksSinceLastSavegame = EVENT_TICKS_BEETWEEN_SAVEGAMES; } Logic::~Logic() { SAFE_DELETE(_action); SAFE_DELETE(_beetle); SAFE_DELETE(_fight); SAFE_DELETE(_entities); SAFE_DELETE(_saveload); SAFE_DELETE(_state); // Zero-out passed pointers _engine = NULL; } ////////////////////////////////////////////////////////////////////////// // Event Handling ////////////////////////////////////////////////////////////////////////// void Logic::eventMouse(const Common::Event &ev) { bool hotspotHandled = false; // Reset mouse flags getFlags()->mouseLeftClick = false; getFlags()->mouseRightClick = false; // Process event flags if (ev.type == Common::EVENT_LBUTTONDOWN) { if (getFlags()->frameInterval) _ignoreFrameInterval = false; getFlags()->frameInterval = false; } if (getFlags()->flag_0) { if (ev.type == Common::EVENT_LBUTTONDOWN || ev.type == Common::EVENT_RBUTTONDOWN) { getFlags()->flag_0 = false; getFlags()->shouldRedraw = true; updateCursor(true); getFlags()->frameInterval = true; } return; } if (_ignoreFrameInterval && getScenes()->checkCurrentPosition(true) && _engine->getCursor()->getStyle() == kCursorForward) { getFlags()->shouldRedraw = false; getFlags()->flag_0 = true; return; } // Update coordinates getGameState()->setCoordinates(ev.mouse); // Handle inventory getInventory()->handleMouseEvent(ev); // Stop processing is inside the menu if (getMenu()->isShown()) return; // Handle whistle case if (getInventory()->getSelectedItem() == kItemWhistle && !getProgress().isEggOpen && !getEntities()->isPlayerPosition(kCarGreenSleeping, 59) && !getEntities()->isPlayerPosition(kCarGreenSleeping, 76) && !getInventory()->isPortraitHighlighted() && !getInventory()->isOpened() && !getInventory()->isEggHighlighted() && !getInventory()->isMagnifierInUse()) { // Update cursor _engine->getCursor()->setStyle(getInventory()->get(kItemWhistle)->cursor); // Check if clicked if (ev.type == Common::EVENT_LBUTTONUP && !getSoundQueue()->isBuffered("LIB045")) { getSound()->playSoundEvent(kEntityPlayer, 45); if (getEntities()->isPlayerPosition(kCarGreenSleeping, 26) || getEntities()->isPlayerPosition(kCarGreenSleeping, 25) || getEntities()->isPlayerPosition(kCarGreenSleeping, 23)) { getSavePoints()->push(kEntityPlayer, kEntityMertens, kAction226078300); } else if (getEntities()->isPlayerPosition(kCarRedSleeping, 26) || getEntities()->isPlayerPosition(kCarRedSleeping, 25) || getEntities()->isPlayerPosition(kCarRedSleeping, 23)) { getSavePoints()->push(kEntityPlayer, kEntityCoudert, kAction226078300); } if (!getState()->sceneUseBackup) getInventory()->unselectItem(); } redrawCursor(); return; } // Handle match case if (getInventory()->getSelectedItem() == kItemMatch && (getEntities()->isPlayerInCar(kCarGreenSleeping) || getEntities()->isPlayerInCar(kCarRedSleeping)) && getProgress().jacket == kJacketGreen && !getInventory()->isPortraitHighlighted() && !getInventory()->isOpened() && !getInventory()->isEggHighlighted() && !getInventory()->isMagnifierInUse() && (getInventory()->get(kItem2)->location == kObjectLocationNone || getEntityData(kEntityPlayer)->car != kCarRedSleeping || getEntityData(kEntityPlayer)->entityPosition != kPosition_2300)) { // Update cursor _engine->getCursor()->setStyle(getInventory()->get(kItemMatch)->cursor); if (ev.type == Common::EVENT_LBUTTONUP) { getAction()->playAnimation(isNight() ? kEventCathSmokeNight : kEventCathSmokeDay); if (!getState()->sceneUseBackup) getInventory()->unselectItem(); getScenes()->processScene(); } redrawCursor(); return; } // Handle entity item case EntityIndex entityIndex = getEntities()->canInteractWith(ev.mouse); if (entityIndex && !getInventory()->isPortraitHighlighted() && !getInventory()->isOpened() && !getInventory()->isEggHighlighted() && !getInventory()->isMagnifierInUse()) { InventoryItem item = getEntityData(entityIndex)->inventoryItem; if (getInventory()->hasItem((InventoryItem)(item & kItemToggleHigh))) { hotspotHandled = true; _engine->getCursor()->setStyle(getInventory()->get((InventoryItem)(item & kItemToggleHigh))->cursor); if (ev.type == Common::EVENT_LBUTTONUP) getSavePoints()->push(kEntityPlayer, entityIndex, kAction1, (InventoryItem)(item & kItemToggleHigh)); } else if ((InventoryItem)(item & kItemInvalid)) { hotspotHandled = true; _engine->getCursor()->setStyle(kCursorTalk2); if (ev.type == Common::EVENT_LBUTTONUP) getSavePoints()->push(kEntityPlayer, entityIndex, kAction1, kCursorNormal); } } ////////////////////////////////////////////////////////////////////////// // Handle standard actions if (getInventory()->isPortraitHighlighted() || getInventory()->isOpened() || getInventory()->isEggHighlighted()) _engine->getCursor()->setStyle(kCursorNormal); if (hotspotHandled || getInventory()->isPortraitHighlighted() || getInventory()->isOpened() || getInventory()->isEggHighlighted()) return; // Magnifier in use if (getInventory()->isMagnifierInUse()) { _engine->getCursor()->setStyle(kCursorMagnifier); if (getInventory()->isPortraitHighlighted() || getInventory()->isOpened() || getInventory()->isEggHighlighted()) _engine->getCursor()->setStyle(kCursorNormal); return; } // Check hotspots int location = 0; SceneHotspot *hotspot = NULL; Scene *scene = getScenes()->get(getState()->scene); for (Common::Array::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { if (!(*it)->isInside(ev.mouse)) continue; if ((*it)->location < location) continue; if (!getAction()->getCursor(**it)) continue; Scene *hotspotScene = getScenes()->get((*it)->scene); if (!getEntities()->getPosition(hotspotScene->car, hotspotScene->position) || (*it)->cursor == kCursorTurnRight || (*it)->cursor == kCursorTurnLeft) { location = (*it)->location; hotspot = *it; } } // No hotspot found: show the normal cursor if (!hotspot) { _engine->getCursor()->setStyle(kCursorNormal); return; } // Found an hotspot: update the cursor and perform the action if the user clicked the mouse _engine->getCursor()->setStyle(getAction()->getCursor(*hotspot)); if (ev.type != Common::EVENT_LBUTTONUP || _flagActionPerformed) return; _flagActionPerformed = true; SceneIndex processedScene = getAction()->processHotspot(*hotspot); SceneIndex testScene = (processedScene == kSceneInvalid) ? hotspot->scene : processedScene; if (testScene) { getFlags()->shouldRedraw = false; getScenes()->setScene(testScene); if (getFlags()->shouldDrawEggOrHourGlass) getInventory()->drawEgg(); getFlags()->shouldRedraw = true; updateCursor(true); } // Switch to next chapter if necessary if (hotspot->action == SceneHotspot::kActionSwitchChapter && hotspot->param1 == getState()->progress.chapter) switchChapter(); } void Logic::eventTick(const Common::Event &) { uint ticks = 1; ////////////////////////////////////////////////////////////////////////// // Adjust ticks if an action has been performed if (_flagActionPerformed) ticks = 10; _flagActionPerformed = false; ////////////////////////////////////////////////////////////////////////// // Draw the blinking egg if needed if (getGlobalTimer() && !getFlags()->shouldDrawEggOrHourGlass) getInventory()->drawBlinkingEgg(ticks); ////////////////////////////////////////////////////////////////////////// // Adjust time and save game if needed if (getFlags()->isGameRunning) { getState()->timeTicks += ticks; getState()->time = (TimeValue)(getState()->time + (TimeValue)(ticks * getState()->timeDelta)); if (getState()->timeDelta) { // Auto-save if (!_ticksSinceLastSavegame) { _ticksSinceLastSavegame = EVENT_TICKS_BEETWEEN_SAVEGAMES; getSaveLoad()->saveGame(kSavegameTypeAuto, kEntityChapters, kEventNone); } // Save after game ticks interval if ((getState()->timeTicks - getSaveLoad()->getLastSavegameTicks()) > GAME_TICKS_BEETWEEN_SAVEGAMES) getSaveLoad()->saveGame(kSavegameTypeTickInterval, kEntityChapters, kEventNone); } } ////////////////////////////////////////////////////////////////////////// // Load scene and process hotspot if (getFlags()->flag_0 && !getFlags()->mouseLeftClick && !getFlags()->mouseRightClick) { Scene *scene = getScenes()->get(getState()->scene); if (getScenes()->checkCurrentPosition(true) && !getEntities()->getPosition(scene->car, scene->position)) { // Process hotspot SceneHotspot *hotspot = scene->getHotspot(); SceneIndex processedScene = getAction()->processHotspot(*hotspot); SceneIndex testScene = (processedScene == kSceneInvalid) ? hotspot->scene : processedScene; if (testScene) { getScenes()->setScene(testScene); } else { getFlags()->flag_0 = false; getFlags()->shouldRedraw = true; updateCursor(true); } if (getFlags()->isGameRunning) getSavePoints()->callAndProcess(); } else { getFlags()->flag_0 = false; getFlags()->shouldRedraw = true; updateCursor(true); } return; } // Stop processing if the game is paused if (!getFlags()->isGameRunning) return; ////////////////////////////////////////////////////////////////////////// // Update beetle, savepoints, entities and draw frames if (_beetle->isLoaded()) _beetle->update(); getSavePoints()->callAndProcess(); getEntities()->updateCallbacks(); getScenes()->drawFrames(true); ////////////////////////////////////////////////////////////////////////// // Update cursor if we can interact with an entity EntityIndex entity = getEntities()->canInteractWith(getCoords()); if (!entity) { if (_engine->getCursor()->getStyle() >= kCursorTalk2) updateCursor(false); return; } // Show item cursor on entity if (getInventory()->hasItem((InventoryItem)(getEntityData(entity)->inventoryItem & kItemToggleHigh)) && (int)getEntityData(entity)->inventoryItem != (int)kCursorTalk2) { _engine->getCursor()->setStyle(getInventory()->get((InventoryItem)(getEntityData(entity)->inventoryItem & kItemToggleHigh))->cursor); return; } getLogic()->updateCursor(false); _engine->getCursor()->setStyle(kCursorTalk2); } ////////////////////////////////////////////////////////////////////////// // Game over, Chapters & credits ////////////////////////////////////////////////////////////////////////// /** * Resets the game state. */ void Logic::resetState() { getScenes()->setCoordinates(Common::Rect(80, 0, 559, 479)); SAFE_DELETE(_entities); _entities = new Entities(_engine); _state->reset(); } /** * Handle game over * * @param type The savegame type. * @param value The value (event, time, index, ...) * @param sceneIndex Index of the scene to show. * @param showScene true to show a scene, false to return to menu directly */ void Logic::gameOver(SavegameType type, uint32 value, SceneIndex sceneIndex, bool showScene) const { getSoundQueue()->processEntries(); getEntities()->reset(); getFlags()->isGameRunning = false; getSavePoints()->reset(); getFlags()->flag_entities_0 = true; if (showScene) { getSoundQueue()->processEntry(kSoundType11); if (sceneIndex && !getFlags()->mouseRightClick) { getScenes()->loadScene(sceneIndex); while (getSoundQueue()->isBuffered(kEntityTables4)) { if (getFlags()->mouseRightClick) break; getSoundQueue()->updateQueue(); } } } // Show Menu getMenu()->show(false, type, value); } void Logic::switchChapter() const { getSoundQueue()->clearStatus(); switch(getState()->progress.chapter) { default: break; case kChapter1: getInventory()->addItem(kItemParchemin); getInventory()->addItem(kItemMatchBox); RESET_ENTITY_STATE(kEntityChapters, Chapters, setup_chapter2); break; case kChapter2: getInventory()->addItem(kItemScarf); RESET_ENTITY_STATE(kEntityChapters, Chapters, setup_chapter3); break; case kChapter3: getInventory()->get(kItemFirebird)->location = kObjectLocation4; getInventory()->get(kItemFirebird)->inPocket = false; getInventory()->get(kItem11)->location = kObjectLocation1; getInventory()->addItem(kItemWhistle); getInventory()->addItem(kItemKey); RESET_ENTITY_STATE(kEntityChapters, Chapters, setup_chapter4); break; case kChapter4: RESET_ENTITY_STATE(kEntityChapters, Chapters, setup_chapter5); break; case kChapter5: playFinalSequence(); break; } } void Logic::playFinalSequence() const { getSoundQueue()->processEntries(); _action->playAnimation(kEventFinalSequence); showCredits(); getEntities()->reset(); getSavePoints()->reset(); getFlags()->flag_entities_0 = true; getMenu()->show(false, kSavegameTypeIndex, 0); } void Logic::showCredits() const { error("[Logic::showCredits] Not implemented"); } ////////////////////////////////////////////////////////////////////////// // Misc ////////////////////////////////////////////////////////////////////////// void Logic::updateCursor(bool) const { /* the cursor is always updated, even when we don't want to redraw it */ CursorStyle style = kCursorNormal; bool interact = false; if (getInventory()->getSelectedItem() != kItemWhistle || getProgress().isEggOpen || getEntities()->isPlayerPosition(kCarGreenSleeping, 59) || getEntities()->isPlayerPosition(kCarGreenSleeping, 76) || getInventory()->isPortraitHighlighted() || getInventory()->isOpened() || getInventory()->isEggHighlighted() || getInventory()->isMagnifierInUse()) { if (getInventory()->getSelectedItem() != kItemMatch || (!getEntities()->isPlayerInCar(kCarGreenSleeping) && !getEntities()->isPlayerInCar(kCarRedSleeping)) || getProgress().jacket != kJacketGreen || getInventory()->isPortraitHighlighted() || getInventory()->isOpened() || getInventory()->isEggHighlighted() || getInventory()->isMagnifierInUse() || (getInventory()->get(kItem2)->location && getEntityData(kEntityPlayer)->car == kCarRedSleeping && getEntityData(kEntityPlayer)->entityPosition == kPosition_2300)) { EntityIndex entity = getEntities()->canInteractWith(getCoords()); if (entity && !getInventory()->isPortraitHighlighted() && !getInventory()->isOpened() && !getInventory()->isEggHighlighted() && !getInventory()->isMagnifierInUse()) { if (getInventory()->hasItem((InventoryItem)(getEntityData(entity)->inventoryItem & kItemToggleHigh))) { interact = true; style = getInventory()->get((InventoryItem)(getEntityData(entity)->inventoryItem & kItemToggleHigh))->cursor; } else if ((int)getEntityData(entity)->inventoryItem == kItemInvalid) { interact = true; style = kCursorTalk2; } } if (!interact && !getInventory()->isPortraitHighlighted() && !getInventory()->isOpened() && !getInventory()->isEggHighlighted() && !getInventory()->isMagnifierInUse()) { int location = 0; SceneHotspot *hotspot = NULL; Scene *scene = getScenes()->get(getState()->scene); // Check all hotspots for (Common::Array::iterator i = scene->getHotspots()->begin(); i != scene->getHotspots()->end(); ++i) { if ((*i)->isInside(getCoords()) && (*i)->location >= location) { if (getAction()->getCursor(**i)) { Scene *hotspotScene = getScenes()->get((*i)->scene); if (!getEntities()->getPosition(hotspotScene->car, hotspotScene->position) || (*i)->cursor == kCursorTurnRight || (*i)->cursor == kCursorTurnLeft) { hotspot = *i; location = (*i)->location; } } } } style = (hotspot) ? getAction()->getCursor(*hotspot) : kCursorNormal; } } else { style = getInventory()->get(kItemMatch)->cursor; } } else { style = getInventory()->get(kItemWhistle)->cursor; } if (getInventory()->isMagnifierInUse()) style = kCursorMagnifier; if (getInventory()->isPortraitHighlighted() || getInventory()->isOpened() || getInventory()->isEggHighlighted()) style = kCursorNormal; _engine->getCursor()->setStyle(style); } void Logic::redrawCursor() const { if (getInventory()->isMagnifierInUse()) _engine->getCursor()->setStyle(kCursorMagnifier); if (getInventory()->isPortraitHighlighted() || getInventory()->isOpened() || getInventory()->isEggHighlighted()) _engine->getCursor()->setStyle(kCursorNormal); } } // End of namespace LastExpress