diff options
author | Eugene Sandulenko | 2010-10-18 19:17:38 +0000 |
---|---|---|
committer | Eugene Sandulenko | 2010-10-18 19:17:38 +0000 |
commit | 86d650926f9b991b6398e4ad4b0613ac264dfbaa (patch) | |
tree | 5e6791249fa5fce7dd3e1a6406dff4bf720ca085 /engines/lastexpress/game | |
parent | c92d2bc234f2f73a9629b3622cd5e66c57439cda (diff) | |
download | scummvm-rg350-86d650926f9b991b6398e4ad4b0613ac264dfbaa.tar.gz scummvm-rg350-86d650926f9b991b6398e4ad4b0613ac264dfbaa.tar.bz2 scummvm-rg350-86d650926f9b991b6398e4ad4b0613ac264dfbaa.zip |
LASTEXPRESS: Merge in the engine.
svn-id: r53579
Diffstat (limited to 'engines/lastexpress/game')
26 files changed, 16019 insertions, 0 deletions
diff --git a/engines/lastexpress/game/action.cpp b/engines/lastexpress/game/action.cpp new file mode 100644 index 0000000000..b2c7fdef1f --- /dev/null +++ b/engines/lastexpress/game/action.cpp @@ -0,0 +1,1968 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/action.h" + +#include "lastexpress/data/animation.h" +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/snd.h" +#include "lastexpress/data/scene.h" + +#include "lastexpress/entities/abbot.h" +#include "lastexpress/entities/anna.h" +#include "lastexpress/entities/entity.h" + +#include "lastexpress/game/beetle.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +static const int _animationListSize = 273; + +// List of animations +static const struct { + const char *filename; + uint16 time; +} _animationList[_animationListSize] = { + {"", 0}, + {"1002", 255}, + {"1002D", 255}, + {"1003", 0}, + {"1005", 195}, + {"1006", 750}, // 5 + {"1006A", 750}, + {"1008", 765}, + {"1008N", 765}, + {"1008A", 750}, + {"1008AN", 750}, // 10 + {"1009", 0}, + {"1011", 1005}, + {"1011A", 780}, + {"1012", 300}, + {"1013", 285}, + {"1017", 870}, // 15 + {"1017A", 0}, // Not in the data files? + {"1019", 120}, + {"1019D", 120}, + {"1020", 120}, // 20 + {"1022", 525}, + {"1022A", 180}, + {"1022AD", 210}, + {"1022B", 210}, + {"1022C", 210}, // 25 + {"1023", 135}, + {"1025", 945}, + {"1028", 300}, + {"1030", 390}, + {"1031", 375}, // 30 + {"1032", 1050}, + {"1033", 945}, + {"1034", 495}, + {"1035", 1230}, + {"1037", 1425}, // 35 + {"1038", 195}, + {"1038A", 405}, + {"1039", 600}, + {"1040", 945}, + {"1041", 510}, // 40 + {"1042", 540}, + {"1043", 855}, + {"1044", 645}, + {"1046", 0}, + {"1047", 0}, // 45 + {"1047A", 0}, + {"1059", 1005}, + {"1060", 255}, + {"1063", 0}, + {"1101", 255}, // 50 + {"1102", 1320}, + {"1103", 210}, + {"1104", 120}, + {"1105", 1350}, + {"1106", 315}, // 55 + {"1106A", 315}, + {"1106D", 315}, + {"1107", 1}, + {"1107A", 660}, + {"1108", 300}, // 60 + {"1109", 1305}, + {"1110", 300}, + {"1112", 0}, + {"1115", 0}, + {"1115A", 0}, // 65 + {"1115B", 0}, + {"1115C", 0}, + {"1115D", 0}, + {"1115E", 0}, + {"1115F", 0}, // 70 + {"1115G", 0}, + {"1115H", 0}, + {"1116", 0}, + {"1117", 0}, + {"1118", 105}, // 75 + {"1202", 510}, + {"1202A", 510}, + {"1203", 720}, + {"1204", 120}, + {"1205", 465}, // 80 + {"1206", 690}, + {"1206A", 450}, + {"1208", 465}, + {"1210", 1020}, + {"1211", 600}, // 85 + {"1212", 435}, + {"1213", 525}, + {"1213A", 150}, + {"1215", 390}, + {"1216", 0}, // 90 + {"1219", 240}, + {"1222", 1095}, + {"1223", 0}, + {"1224", 720}, + {"1225", 1005}, // 95 + {"1227", 840}, + {"1227A", 840}, + {"1303", 450}, + {"1303N", 450}, + {"1304", 450}, // 100 + {"1304N", 450}, + {"1305", 630}, + {"1309", 0}, + {"1311", 1710}, + {"1312", 240}, // 105 + {"1312D", 240}, + {"1313", 930}, + {"1315", 1035}, + {"1315A", 1035}, + {"1401", 540}, // 110 + {"1402", 150}, + {"1402B", 150}, + {"1403", 90}, + {"1404", 885}, + {"1404A", 0}, // 115 + {"1405", 135}, + {"1406", 1665}, + {"1501", 285}, + {"1501A", 285}, + {"1502", 165}, // 120 + {"1502A", 165}, + {"1502D", 165}, + {"1503", 0}, + {"1504", 0}, + {"1505", 0}, // 125 + {"1505A", 0}, + {"1506", 300}, + {"1506A", 180}, + {"1508", 0}, + {"1509", 450}, // 130 + {"1509S", 450}, + {"1509A", 450}, + {"1509AS", 450}, + {"1509N", 450}, + {"1509SN", 450}, // 135 + {"1509AN", 450}, + {"1509BN", 450}, + {"1511", 150}, + {"1511A", 150}, + {"1511B", 90}, // 140 + {"1511BA", 90}, + {"1511C", 135}, + {"1511D", 105}, + {"1930", 0}, + {"1511E", 150}, // 145 + {"1512", 165}, + {"1513", 180}, + {"1517", 0}, + {"1517A", 165}, + {"1518", 165}, // 150 + {"1518A", 165}, + {"1518B", 165}, + {"1591", 450}, + {"1592", 450}, + {"1593", 450}, // 155 + {"1594", 450}, + {"1595", 450}, + {"1596", 450}, + {"1601", 0}, + {"1603", 0}, // 160 + {"1606B", 315}, + {"1607A", 0}, + {"1610", 0}, + {"1611", 0}, + {"1612", 0}, // 165 + {"1615", 0}, + {"1619", 0}, + {"1620", 120}, + {"1621", 105}, + {"1622", 105}, // 170 + {"1629", 450}, + {"1630", 450}, + {"1631", 525}, + {"1632", 0}, + {"1633", 615}, // 175 + {"1634", 180}, + {"1702", 180}, + {"1702DD", 180}, + {"1702NU", 180}, + {"1702ND", 180}, // 180 + {"1704", 300}, + {"1704D", 300}, + {"1705", 195}, + {"1705D", 195}, + {"1706", 195}, // 185 + {"1706DD", 195}, + {"1706ND", 195}, + {"1706NU", 195}, + {"1901", 135}, + {"1902", 1410}, // 190 + {"1903", 0}, + {"1904", 1920}, + {"1908", 600}, + {"1908A", 195}, + {"1908B", 105}, // 195 + {"1908C", 165}, + {"1908CD", 0}, + {"1909A", 150}, + {"1909B", 150}, + {"1909C", 150}, // 200 + {"1910A", 180}, + {"1910B", 180}, + {"1910C", 180}, + {"1911A", 90}, + {"1911B", 90}, // 205 + {"1911C", 90}, + {"1912", 0}, + {"1913", 0}, + {"1917", 0}, + {"1918", 390}, // 210 + {"1919", 360}, + {"1919A", 105}, + {"1920", 75}, + {"1922", 75}, + {"1923", 150}, // 215 + {"8001", 120}, + {"8001A", 120}, + {"8002", 120}, + {"8002A", 120}, + {"8002B", 120}, // 220 + {"8003", 105}, + {"8003A", 105}, + {"8004", 105}, + {"8004A", 105}, + {"8005", 270}, // 225 + {"8005B", 270}, + {"8010", 270}, + {"8013", 120}, + {"8013A", 120}, + {"8014", 165}, // 230 + {"8014A", 165}, + {"8014R", 165}, + {"8014AR", 165}, + {"8015", 150}, + {"8015A", 150}, // 235 + {"8015R", 150}, + {"8015AR", 150}, + {"8017", 120}, + {"8017A", 120}, + {"8017R", 120}, // 240 + {"8017AR", 120}, + {"8017N", 90}, + {"8023", 135}, + {"8023A", 135}, + {"8023M", 135}, // 245 + {"8024", 150}, + {"8024A", 180}, + {"8024M", 180}, + {"8025", 150}, + {"8025A", 150}, // 250 + {"8025M", 150}, + {"8027", 75}, + {"8028", 75}, + {"8029", 120}, + {"8029A", 120}, // 255 + {"8031", 375}, + {"8032", 0}, + {"8032A", 0}, + {"8033", 105}, + {"8035", 195}, // 260 + {"8035A", 120}, + {"8035B", 180}, + {"8035C", 135}, + {"8036", 105}, + {"8037", 195}, // 265 + {"8037A", 195}, + {"8040", 240}, + {"8040A", 240}, + {"8041", 195}, + {"8041A", 195}, // 270 + {"8042", 600}, + {"8042A", 600} +}; + +Action::Action(LastExpressEngine *engine) : _engine(engine) { + ADD_ACTION(dummy); + ADD_ACTION(inventory); + ADD_ACTION(savePoint); + ADD_ACTION(playSound); + ADD_ACTION(playMusic); + ADD_ACTION(knock); + ADD_ACTION(compartment); + ADD_ACTION(playSounds); + ADD_ACTION(playAnimation); + ADD_ACTION(openCloseObject); + ADD_ACTION(updateObjetLocation2); + ADD_ACTION(setItemLocation); + ADD_ACTION(knockNoSound); + ADD_ACTION(pickItem); + ADD_ACTION(dropItem); + ADD_ACTION(dummy); + ADD_ACTION(enterCompartment); + ADD_ACTION(dummy); + ADD_ACTION(getOutsideTrain); + ADD_ACTION(slip); + ADD_ACTION(getInsideTrain); + ADD_ACTION(climbUpTrain); + ADD_ACTION(climbDownTrain); + ADD_ACTION(jumpUpDownTrain); + ADD_ACTION(unbound); + ADD_ACTION(25); + ADD_ACTION(26); + ADD_ACTION(27); + ADD_ACTION(concertSitCough); + ADD_ACTION(29); + ADD_ACTION(catchBeetle); + ADD_ACTION(exitCompartment); + ADD_ACTION(32); + ADD_ACTION(useWhistle); + ADD_ACTION(openMatchBox); + ADD_ACTION(openBed); + ADD_ACTION(dummy); + ADD_ACTION(dialog); + ADD_ACTION(eggBox); + ADD_ACTION(39); + ADD_ACTION(bed); + ADD_ACTION(playMusicChapter); + ADD_ACTION(playMusicChapterSetupTrain); + ADD_ACTION(switchChapter); + ADD_ACTION(44); +} + +Action::~Action() { + for (int i = 0; i < (int)_actions.size(); i++) + delete _actions[i]; + + // Zero-out passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Processing hotspot +////////////////////////////////////////////////////////////////////////// +SceneIndex Action::processHotspot(const SceneHotspot &hotspot) { + if (!hotspot.action || hotspot.action >= (int)_actions.size()) + return kSceneInvalid; + + return (*_actions[hotspot.action])(hotspot); +} + +////////////////////////////////////////////////////////////////////////// +// Actions +////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// Action 0 +IMPLEMENT_ACTION(dummy) + error("Action::action_dummy: Function should never be called (hotspot action: %d)!", hotspot.action); +} + +////////////////////////////////////////////////////////////////////////// +// Action 1 +IMPLEMENT_ACTION(inventory) + if (!getState()->sceneUseBackup) + return kSceneInvalid; + + SceneIndex index = kSceneNone; + if (getState()->sceneBackup2) { + index = getState()->sceneBackup2; + getState()->sceneBackup2 = kSceneNone; + } else { + getState()->sceneUseBackup = false; + index = getState()->sceneBackup; + + Scene *backup = getScenes()->get(getState()->sceneBackup); + if (getEntities()->getPosition(backup->car, backup->position)) + index = getScenes()->processIndex(getState()->sceneBackup); + } + + getScenes()->loadScene(index); + + if (!getInventory()->getSelectedItem()) + return kSceneInvalid; + + if (!getInventory()->getSelectedEntry()->isSelectable || (!getState()->sceneBackup2 && getInventory()->getFirstExaminableItem())) + getInventory()->selectItem(getInventory()->getFirstExaminableItem()); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 2 +IMPLEMENT_ACTION(savePoint) + getSavePoints()->push(kEntityPlayer, (EntityIndex)hotspot.param1, (ActionIndex)hotspot.param2); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 3 +IMPLEMENT_ACTION(playSound) + + // Check that the file is not already buffered + if (hotspot.param2 || !getSound()->isBuffered(Common::String::printf("LIB%03d", hotspot.param1), true)) + getSound()->playSoundEvent(kEntityPlayer, hotspot.param1, hotspot.param2); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 4 +IMPLEMENT_ACTION(playMusic) + // Check that the file is not already buffered + Common::String filename = Common::String::printf("MUS%03d", hotspot.param1); + + if (!getSound()->isBuffered(filename) && (hotspot.param1 != 50 || getProgress().chapter == kChapter5)) + getSound()->playSound(kEntityPlayer, filename, SoundManager::kFlagDefault, hotspot.param2); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 5 +IMPLEMENT_ACTION(knock) + ObjectIndex object = (ObjectIndex)hotspot.param1; + if (object >= kObjectMax) + return kSceneInvalid; + + if (getObjects()->get(object).entity) { + getSavePoints()->push(kEntityPlayer, getObjects()->get(object).entity, kActionKnock, object); + } else { + if (!getSound()->isBuffered("LIB012", true)) + getSound()->playSoundEvent(kEntityPlayer, 12); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 6 +IMPLEMENT_ACTION(compartment) + ObjectIndex compartment = (ObjectIndex)hotspot.param1; + + if (compartment >= kObjectMax) + return kSceneInvalid; + + if (getObjects()->get(compartment).entity) { + getSavePoints()->push(kEntityPlayer, getObjects()->get(compartment).entity, kActionOpenDoor, compartment); + + // Stop processing further + return kSceneNone; + } + + if (handleOtherCompartment(compartment, true, true)) { + // Stop processing further + return kSceneNone; + } + + ObjectLocation location = getObjects()->get(compartment).location; + if (location == kObjectLocation1 || location == kObjectLocation3 || getEntities()->checkFields2(compartment)) { + + if (location != kObjectLocation1 || getEntities()->checkFields2(compartment) + || (getInventory()->getSelectedItem() != kItemKey + && (compartment != kObjectCompartment1 + || !getInventory()->hasItem(kItemKey) + || (getInventory()->getSelectedItem() != kItemFirebird && getInventory()->getSelectedItem() != kItemBriefcase)))) { + if (!getSound()->isBuffered("LIB13")) + getSound()->playSoundEvent(kEntityPlayer, 13); + + // Stop processing further + return kSceneNone; + } + + getSound()->playSoundEvent(kEntityPlayer, 32); + + if ((compartment >= kObjectCompartment1 && compartment <= kObjectCompartment3) || (compartment >= kObjectCompartmentA && compartment <= kObjectCompartmentF)) + getObjects()->update(compartment, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + getSound()->playSoundEvent(kEntityPlayer, 15, 22); + getInventory()->unselectItem(); + + return kSceneInvalid; + } + + if (hotspot.action != SceneHotspot::kActionEnterCompartment || getInventory()->getSelectedItem() != kItemKey) { + if (compartment == kObjectCageMax) { + getSound()->playSoundEvent(kEntityPlayer, 26); + } else { + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->playSoundEvent(kEntityPlayer, 15, 22); + } + return kSceneInvalid; + } + + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getSound()->playSoundEvent(kEntityPlayer, 16); + getInventory()->unselectItem(); + + // Stop processing further + return kSceneNone; +} + +////////////////////////////////////////////////////////////////////////// +// Action 7 +IMPLEMENT_ACTION(playSounds) + getSound()->playSoundEvent(kEntityPlayer, hotspot.param1); + getSound()->playSoundEvent(kEntityPlayer, hotspot.param3, hotspot.param2); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 8 +IMPLEMENT_ACTION(playAnimation) + if (getEvent(hotspot.param1)) + return kSceneInvalid; + + playAnimation((EventIndex)hotspot.param1); + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 9 +IMPLEMENT_ACTION(openCloseObject) + ObjectIndex object = (ObjectIndex)hotspot.param1; + ObjectLocation location = (ObjectLocation)hotspot.param2; + + if (object >= kObjectMax) + return kSceneInvalid; + + getObjects()->update(object, getObjects()->get(object).entity, location, kCursorKeepValue, kCursorKeepValue); + + bool isNotWindow = ((object <= kObjectCompartment8 || object >= kObjectHandleBathroom) && (object <= kObjectCompartmentH || object >= kObject48)); + + switch (location) { + default: + break; + + case kObjectLocation1: + if (isNotWindow) + getSound()->playSoundEvent(kEntityPlayer, 24); + else + getSound()->playSoundEvent(kEntityPlayer, 21); + break; + + case kObjectLocation2: + if (isNotWindow) + getSound()->playSoundEvent(kEntityPlayer, 36); + else + getSound()->playSoundEvent(kEntityPlayer, 20); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 10 +IMPLEMENT_ACTION(updateObjetLocation2) + ObjectIndex object = (ObjectIndex)hotspot.param1; + ObjectLocation location = (ObjectLocation)hotspot.param2; + + if (object >= kObjectMax) + return kSceneInvalid; + + getObjects()->updateLocation2(object, location); + + if (object != kObject112 || getSound()->isBuffered("LIB096")) { + if (object == 1) + getSound()->playSoundEvent(kEntityPlayer, 73); + } else { + getSound()->playSoundEvent(kEntityPlayer, 96); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 11 +IMPLEMENT_ACTION(setItemLocation) + InventoryItem item = (InventoryItem)hotspot.param1; + if (item >= kPortraitOriginal) + return kSceneInvalid; + + Inventory::InventoryEntry* entry = getInventory()->get(item); + if (!entry->isPresent) + return kSceneInvalid; + + entry->location = (ObjectLocation)hotspot.param2; + + if (item == kItemCorpse) { + ObjectLocation corpseLocation = getInventory()->get(kItemCorpse)->location; + + if (corpseLocation == kObjectLocation3 || corpseLocation == kObjectLocation4) + getProgress().eventCorpseMovedFromFloor = true; + else + getProgress().eventCorpseMovedFromFloor = false; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 12 +IMPLEMENT_ACTION(knockNoSound) + ObjectIndex object = (ObjectIndex)hotspot.param1; + if (object >= kObjectMax) + return kSceneInvalid; + + if (getObjects()->get(object).entity) + getSavePoints()->push(kEntityPlayer, getObjects()->get(object).entity, kActionKnock, object); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 13 +IMPLEMENT_ACTION(pickItem) + InventoryItem item = (InventoryItem)hotspot.param1; + ObjectLocation location = (ObjectLocation)hotspot.param2; + bool process = (hotspot.scene == 0); + SceneIndex sceneIndex = kSceneInvalid; + + if (item >= kPortraitOriginal) + return kSceneInvalid; + + Inventory::InventoryEntry* entry = getInventory()->get(item); + if (!entry->location) + return kSceneInvalid; + + // Special case for corpse + if (item == kItemCorpse) { + pickCorpse(location, process); + return kSceneInvalid; + } + + // Add and process items + getInventory()->addItem(item); + + switch (item) { + default: + break; + + case kItemGreenJacket: + pickGreenJacket(process); + break; + + case kItemScarf: + pickScarf(process); + + // stop processing + return kSceneInvalid; + + case kItemParchemin: + if (location != kObjectLocation2) + break; + + getInventory()->addItem(kItemParchemin); + getInventory()->get(kItem11)->location = kObjectLocation1; + getSound()->playSoundEvent(kEntityPlayer, 9); + break; + + case kItemBomb: + RESET_ENTITY_STATE(kEntityAbbot, Abbot, setup_pickBomb); + break; + + case kItemBriefcase: + getSound()->playSoundEvent(kEntityPlayer, 83); + break; + } + + // Load item scene + if (getInventory()->get(item)->scene) { + if (!getState()->sceneUseBackup) { + getState()->sceneUseBackup = true; + getState()->sceneBackup = (hotspot.scene ? hotspot.scene : getState()->scene); + } + + getScenes()->loadScene(getInventory()->get(item)->scene); + + // do not process further + sceneIndex = kSceneNone; + } + + // Select item + if (getInventory()->get(item)->isSelectable) { + getInventory()->selectItem(item); + _engine->getCursor()->setStyle(getInventory()->get(item)->cursor); + } + + return sceneIndex; +} + +////////////////////////////////////////////////////////////////////////// +// Action 14 +IMPLEMENT_ACTION(dropItem) + InventoryItem item = (InventoryItem)hotspot.param1; + ObjectLocation location = (ObjectLocation)hotspot.param2; + bool process = (hotspot.scene == kSceneNone); + + if (item >= kPortraitOriginal) + return kSceneInvalid; + + if (!getInventory()->hasItem(item)) + return kSceneInvalid; + + if (location < kObjectLocation1) + return kSceneInvalid; + + // Handle actions + if (item == kItemBriefcase) { + getSound()->playSoundEvent(kEntityPlayer, 82); + + if (location == kObjectLocation2) { + if (!getProgress().field_58) { + getSaveLoad()->saveGame(kSavegameTypeTime, kEntityPlayer, kTimeNone); + getProgress().field_58 = 1; + } + + if (getInventory()->get(kItemParchemin)->location == kObjectLocation2) { + getInventory()->addItem(kItemParchemin); + getInventory()->get(kItem11)->location = kObjectLocation1; + getSound()->playSoundEvent(kEntityPlayer, 9); + } + } + } + + // Update item location + getInventory()->removeItem(item, location); + + if (item == kItemCorpse) + dropCorpse(process); + + // Unselect item + getInventory()->unselectItem(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 15: Dummy action + +////////////////////////////////////////////////////////////////////////// +// Action 16 +IMPLEMENT_ACTION(enterCompartment) + if (getObjects()->get(kObjectCompartment1).location == kObjectLocation1 || getObjects()->get(kObjectCompartment1).location == kObjectLocation3 || getInventory()->getSelectedItem() == kItemKey) + return action_compartment(hotspot); + + if (getProgress().eventCorpseFound) { + if (hotspot.action != SceneHotspot::kActionEnterCompartment || getInventory()->get(kItemBriefcase)->location != kObjectLocation2) + return action_compartment(hotspot); + + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->playSoundEvent(kEntityPlayer, 15, 22); + + if (getProgress().field_78 && !getSound()->isBuffered("MUS003")) { + getSound()->playSound(kEntityPlayer, "MUS003", SoundManager::kFlagDefault); + getProgress().field_78 = 0; + } + + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 77); + + return kSceneNone; + } + + getSaveLoad()->saveGame(kSavegameTypeTime, kEntityPlayer, kTimeNone); + getSound()->playSound(kEntityPlayer, "LIB014"); + playAnimation(kEventCathFindCorpse); + getSound()->playSound(kEntityPlayer, "LIB015"); + getProgress().eventCorpseFound = true; + + return kSceneCompartmentCorpse; +} + +////////////////////////////////////////////////////////////////////////// +// Action 17: Dummy action + +////////////////////////////////////////////////////////////////////////// +// Action 18 +IMPLEMENT_ACTION(getOutsideTrain) + ObjectIndex object = (ObjectIndex)hotspot.param1; + + if ((getEvent(kEventCathLookOutsideWindowDay) || getEvent(kEventCathLookOutsideWindowNight) || getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) + && getProgress().isTrainRunning + && (object != kObjectOutsideAnnaCompartment || (!getEntities()->isInsideCompartment(kEntityRebecca, kCarRedSleeping, kPosition_4840) && getObjects()->get(kObjectOutsideBetweenCompartments).location == kObjectLocation2)) + && getInventory()->getSelectedItem() != kItemFirebird + && getInventory()->getSelectedItem() != kItemBriefcase) { + + switch (object) { + default: + return kSceneInvalid; + + case kObjectOutsideTylerCompartment: + getEvent(kEventCathLookOutsideWindowDay) = 1; + playAnimation(isNight() ? kEventCathGoOutsideTylerCompartmentNight : kEventCathGoOutsideTylerCompartmentDay); + getProgress().field_C8 = 1; + break; + + case kObjectOutsideBetweenCompartments: + getEvent(kEventCathLookOutsideWindowDay) = 1; + playAnimation(isNight() ? kEventCathGoOutsideNight : kEventCathGoOutsideDay); + getProgress().field_C8 = 1; + break; + + case kObjectOutsideAnnaCompartment: + getEvent(kEventCathLookOutsideWindowDay) = 1; + playAnimation(isNight() ? kEventCathGetInsideNight : kEventCathGetInsideDay); + if (!hotspot.scene) + getScenes()->processScene(); + break; + } + } else { + if (object == kObjectOutsideTylerCompartment || object == kObjectOutsideBetweenCompartments || object == kObjectOutsideAnnaCompartment) { + playAnimation(isNight() ? kEventCathLookOutsideWindowNight : kEventCathLookOutsideWindowDay); + getScenes()->processScene(); + return kSceneNone; + } + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 19 +IMPLEMENT_ACTION(slip) + switch((ObjectIndex)hotspot.param1) { + default: + return kSceneInvalid; + + case kObjectOutsideTylerCompartment: + playAnimation(isNight() ? kEventCathSlipTylerCompartmentNight : kEventCathSlipTylerCompartmentDay); + break; + + case kObjectOutsideBetweenCompartments: + playAnimation(isNight() ? kEventCathSlipNight : kEventCathSlipDay); + break; + } + + getProgress().field_C8 = 0; + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 20 +IMPLEMENT_ACTION(getInsideTrain) + switch ((ObjectIndex)hotspot.param1) { + default: + return kSceneInvalid; + + case kObjectOutsideTylerCompartment: + playAnimation(isNight() ? kEventCathGetInsideTylerCompartmentNight : kEventCathGetInsideTylerCompartmentDay); + break; + + case kObjectOutsideBetweenCompartments: + playAnimation(isNight() ? kEventCathGetInsideNight : kEventCathGetInsideDay); + break; + + case kObjectOutsideAnnaCompartment: + playAnimation(kEventCathGettingInsideAnnaCompartment); + break; + } + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 21 +IMPLEMENT_ACTION(climbUpTrain) + byte action = hotspot.param1; + + if (action != 1 && action != 2) + return kSceneInvalid; + + switch (getProgress().chapter) { + default: + break; + + case kChapter2: + case kChapter3: + if (action == 2) + playAnimation(kEventCathClimbUpTrainGreenJacket); + + playAnimation(kEventCathTopTrainGreenJacket); + break; + + case kChapter5: + if (action == 2) + playAnimation(getProgress().isNightTime ? kEventCathClimbUpTrainNoJacketNight : kEventCathClimbUpTrainNoJacketDay); + + playAnimation(getProgress().isNightTime ? kEventCathTopTrainNoJacketNight : kEventCathTopTrainNoJacketDay); + break; + } + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 22 +IMPLEMENT_ACTION(climbDownTrain) + EventIndex evt = kEventNone; + switch (getProgress().chapter) { + default: + return kSceneInvalid; + + case kChapter2: + case kChapter3: + evt = kEventCathClimbDownTrainGreenJacket; + break; + + case kChapter5: + evt = (getProgress().isNightTime ? kEventCathClimbDownTrainNoJacketNight : kEventCathClimbDownTrainNoJacketDay); + break; + } + + playAnimation(evt); + if (evt == kEventCathClimbDownTrainNoJacketDay) + getSound()->playSoundEvent(kEntityPlayer, 37); + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 23 +IMPLEMENT_ACTION(jumpUpDownTrain) + switch (hotspot.param1) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kActionBreakCeiling); + break; + + case 2: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kActionJumpDownCeiling); + break; + + case 3: + if (getInventory()->getSelectedItem() == kItemBriefcase) { + getInventory()->removeItem(kItemBriefcase, kObjectLocation3); + getSound()->playSoundEvent(kEntityPlayer, 82); + getInventory()->unselectItem(); + } + + // Show animation with or without briefcase + playAnimation((getInventory()->get(kItemBriefcase)->location - 3) ? kEventCathJumpUpCeilingBriefcase : kEventCathJumpUpCeiling); + + if (!hotspot.scene) + getScenes()->processScene(); + + break; + + case 4: + if (getProgress().chapter == kChapter1) + getSavePoints()->push(kEntityPlayer, kEntityKronos, kAction202621266); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 24 +IMPLEMENT_ACTION(unbound) + byte action = hotspot.param1; + + switch (action) { + default: + break; + + case 1: + playAnimation(kEventCathStruggleWithBonds); + if (hotspot.scene) + getScenes()->processScene(); + break; + + case 2: + playAnimation(kEventCathBurnRope); + if (hotspot.scene) + getScenes()->processScene(); + break; + + case 3: + if (getEvent(kEventCathBurnRope)) { + playAnimation(kEventCathRemoveBonds); + getProgress().field_84 = 0; + getScenes()->loadSceneFromPosition(kCarBaggageRear, 89); + return kSceneNone; + } + break; + + case 4: + if (!getEvent(kEventCathStruggleWithBonds2)) { + playAnimation(kEventCathStruggleWithBonds2); + getSound()->playSoundEvent(kEntityPlayer, 101); + getInventory()->setLocationAndProcess(kItemMatch, kObjectLocation2); + if (!hotspot.scene) + getScenes()->processScene(); + } + break; + + case 5: + getSavePoints()->push(kEntityPlayer, kEntityIvo, kAction192637492); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 25 +IMPLEMENT_ACTION(25) + switch(hotspot.param1) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntityAnna, kAction272177921); + break; + + case 2: + if (!getSound()->isBuffered("MUS021")) + getSound()->playSound(kEntityPlayer, "MUS021", SoundManager::kFlagDefault); + break; + + case 3: + getSound()->playSoundEvent(kEntityPlayer, 43); + if (!getInventory()->hasItem(kItemKey) && !getEvent(kEventAnnaBaggageArgument)) { + RESET_ENTITY_STATE(kEntityAnna, Anna, setup_baggage); + return kSceneNone; + } + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 26 +IMPLEMENT_ACTION(26) + switch(hotspot.param1) { + default: + return kSceneInvalid; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction158610240); + break; + + case 2: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction225367984); + getInventory()->unselectItem(); + return kSceneNone; + + case 3: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction191001984); + return kSceneNone; + + case 4: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction201959744); + return kSceneNone; + + case 5: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction169300225); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 27 +IMPLEMENT_ACTION(27) + if (!getSound()->isBuffered("LIB031", true)) + getSound()->playSoundEvent(kEntityPlayer, 31); + + switch (getEntityData(kEntityPlayer)->car) { + default: + break; + + case kCarGreenSleeping: + getSavePoints()->push(kEntityPlayer, kEntityMertens, kAction225358684, hotspot.param1); + break; + + case kCarRedSleeping: + getSavePoints()->push(kEntityPlayer, kEntityCoudert, kAction225358684, hotspot.param1); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 28 +IMPLEMENT_ACTION(concertSitCough) + switch(hotspot.param1) { + default: + return kSceneInvalid; + + case 1: + playAnimation(kEventConcertSit); + break; + + case 2: + playAnimation(kEventConcertCough); + break; + } + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 29 +IMPLEMENT_ACTION(29) + getProgress().field_C = 1; + getSound()->playSoundEvent(kEntityPlayer, hotspot.param1, hotspot.param2); + + Common::String filename = Common::String::printf("MUS%03d", hotspot.param3); + if (!getSound()->isBuffered(filename)) + getSound()->playSound(kEntityPlayer, filename, SoundManager::kFlagDefault); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 30 +IMPLEMENT_ACTION(catchBeetle) + if (!getBeetle()->isLoaded()) + return kSceneInvalid; + + if (getBeetle()->catchBeetle()) { + getBeetle()->unload(); + getInventory()->get(kItemBeetle)->location = kObjectLocation1; + getSavePoints()->push(kEntityPlayer, kEntityChapters, kActionCatchBeetle); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 31 +IMPLEMENT_ACTION(exitCompartment) + if (!getProgress().field_30 && getProgress().jacket != kJacketOriginal) { + getSaveLoad()->saveGame(kSavegameTypeTime, kEntityPlayer, kTimeNone); + getProgress().field_30 = 1; + } + + getObjects()->updateLocation2(kObjectCompartment1, (ObjectLocation)hotspot.param2); + + // fall to case enterCompartment action + return action_enterCompartment(hotspot); +} + +////////////////////////////////////////////////////////////////////////// +// Action 32 +IMPLEMENT_ACTION(32) + switch(hotspot.param1) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntitySalko, kAction167992577); + break; + + case 2: + getSavePoints()->push(kEntityPlayer, kEntityVesna, kAction202884544); + break; + + case 3: + if (getProgress().chapter == kChapter5) { + getSavePoints()->push(kEntityPlayer, kEntityAbbot, kAction168646401); + getSavePoints()->push(kEntityPlayer, kEntityMilos, kAction168646401); + } else { + getSavePoints()->push(kEntityPlayer, kEntityTrain, kAction203339360); + } + // Stop processing further scenes + return kSceneNone; + + case 4: + getSavePoints()->push(kEntityPlayer, kEntityMilos, kAction169773228); + break; + + case 5: + getSavePoints()->push(kEntityPlayer, kEntityVesna, kAction167992577); + break; + + case 6: + getSavePoints()->push(kEntityPlayer, kEntityAugust, kAction203078272); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 33 +IMPLEMENT_ACTION(useWhistle) + EventIndex evt = kEventNone; + SceneIndex sceneIndex = kSceneInvalid; + + switch (hotspot.param1) { + default: + break; + + case 1: + if (getEvent(kEventKronosBringFirebird)) { + getSavePoints()->push(kEntityPlayer, kEntityAnna, kAction205294778); + break; + } + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_8200)) { + evt = kEventCathOpenEgg; + + Scene *scene = getScenes()->get(hotspot.scene); + if (scene->getHotspot()) + sceneIndex = scene->getHotspot()->scene; + + } else { + evt = kEventCathOpenEggNoBackground; + } + getProgress().isEggOpen = true; + break; + + case 2: + if (getEvent(kEventKronosBringFirebird)) { + getSavePoints()->push(kEntityPlayer, kEntityAnna, kAction224309120); + break; + } + + evt = (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_8200)) ? kEventCathCloseEgg : kEventCathCloseEggNoBackground; + getProgress().isEggOpen = false; + break; + + case 3: + if (getEvent(kEventKronosBringFirebird)) { + getSavePoints()->push(kEntityPlayer, kEntityAnna, kActionUseWhistle); + break; + } + + evt = (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_8200)) ? kEventCathUseWhistleOpenEgg : kEventCathUseWhistleOpenEggNoBackground; + break; + + } + + if (evt != kEventNone) { + playAnimation(evt); + if (sceneIndex == kSceneNone || !hotspot.scene) + getScenes()->processScene(); + } + + return sceneIndex; +} + +////////////////////////////////////////////////////////////////////////// +// Action 34 +IMPLEMENT_ACTION(openMatchBox) + // If the match is already in the inventory, do nothing + if (!getInventory()->get(kItemMatch)->location + || getInventory()->get(kItemMatch)->isPresent) + return kSceneInvalid; + + getInventory()->addItem(kItemMatch); + getSound()->playSoundEvent(kEntityPlayer, 102); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 35 +IMPLEMENT_ACTION(openBed) + getSound()->playSoundEvent(kEntityPlayer, 59); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 36: Dummy action + +////////////////////////////////////////////////////////////////////////// +// Action 37 +IMPLEMENT_ACTION(dialog) + getSound()->playDialog(kEntityTables4, (EntityIndex)hotspot.param1, SoundManager::kFlagDefault, 0); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 38 +IMPLEMENT_ACTION(eggBox) + getSound()->playSoundEvent(kEntityPlayer, 43); + if (getProgress().field_7C && !getSound()->isBuffered("MUS003")) { + getSound()->playSound(kEntityPlayer, "MUS003", SoundManager::kFlagDefault); + getProgress().field_7C = 0; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 39 +IMPLEMENT_ACTION(39) + getSound()->playSoundEvent(kEntityPlayer, 24); + if (getProgress().field_80 && !getSound()->isBuffered("MUS003")) { + getSound()->playSound(kEntityPlayer, "MUS003", SoundManager::kFlagDefault); + getProgress().field_80 = 0; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 40 +IMPLEMENT_ACTION(bed) + getSound()->playSoundEvent(kEntityPlayer, 85); + // falls to case knockNoSound + return action_knockNoSound(hotspot); +} + +////////////////////////////////////////////////////////////////////////// +// Action 41 +IMPLEMENT_ACTION(playMusicChapter) + byte id = 0; + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + id = hotspot.param1; + break; + + case kChapter2: + case kChapter3: + id = hotspot.param2; + break; + + case kChapter4: + case kChapter5: + id = hotspot.param3; + break; + } + + if (id) { + Common::String filename = Common::String::printf("MUS%03d", id); + + if (!getSound()->isBuffered(filename)) + getSound()->playSound(kEntityPlayer, filename, SoundManager::kFlagDefault); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 42 +IMPLEMENT_ACTION(playMusicChapterSetupTrain) + int id = 0; + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + id = 1; + break; + + case kChapter2: + case kChapter3: + id = 2; + break; + + case kChapter4: + case kChapter5: + id = 4; + break; + } + + Common::String filename = Common::String::printf("MUS%03d", hotspot.param1); + + if (!getSound()->isBuffered(filename) && hotspot.param3 & id) { + getSound()->playSound(kEntityPlayer, filename, SoundManager::kFlagDefault); + + getSavePoints()->call(kEntityPlayer, kEntityTrain, kAction203863200, filename.c_str()); + getSavePoints()->push(kEntityPlayer, kEntityTrain, kAction222746496, hotspot.param2); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// // Action 43 +IMPLEMENT_ACTION(switchChapter) + // Nothing to do here as an hotspot action + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 44 +IMPLEMENT_ACTION(44) + switch (hotspot.param1) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntityRebecca, kAction205034665); + break; + + case 2: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction225358684); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Helper functions +////////////////////////////////////////////////////////////////////////// +void Action::pickGreenJacket(bool process) const { + getProgress().jacket = kJacketGreen; + getInventory()->addItem(kItemMatchBox); + + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + playAnimation(kEventPickGreenJacket); + + getInventory()->setPortrait(kPortraitGreen); + + if (process) + getScenes()->processScene(); +} + +void Action::pickScarf(bool process) const { + playAnimation(getProgress().jacket == kJacketGreen ? kEventPickScarfGreen : kEventPickScarfOriginal); + + if (process) + getScenes()->processScene(); +} + +void Action::pickCorpse(ObjectLocation bedPosition, bool process) const { + + if (getProgress().jacket == kJacketOriginal) + getProgress().jacket = kJacketBlood; + + switch(getInventory()->get(kItemCorpse)->location) { + // No way to pick the corpse + default: + break; + + // Floor + case kObjectLocation1: + if (bedPosition != 4) { + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpsePickFloorGreen : kEventCorpsePickFloorOriginal); + break; + } + + if (getProgress().jacket) + playAnimation(kEventCorpsePickFloorOpenedBedOriginal); + + getInventory()->get(kItemCorpse)->location = kObjectLocation5; + break; + + // Bed + case kObjectLocation2: + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpsePickFloorGreen : kEventCorpsePickBedOriginal); + break; + } + + if (process) + getScenes()->processScene(); + + // Add corpse to inventory + if (bedPosition != 4) { // bed position + getInventory()->addItem(kItemCorpse); + getInventory()->selectItem(kItemCorpse); + _engine->getCursor()->setStyle(kCursorCorpse); + } +} + +void Action::dropCorpse(bool process) const { + switch(getInventory()->get(kItemCorpse)->location) { + default: + break; + + case kObjectLocation1: // Floor + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpseDropFloorGreen : kEventCorpseDropFloorOriginal); + break; + + case kObjectLocation2: // Bed + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpseDropBedGreen : kEventCorpseDropBedOriginal); + break; + + case kObjectLocation4: // Window + // Say goodbye to an old friend + getInventory()->get(kItemCorpse)->location = kObjectLocationNone; + getProgress().eventCorpseThrown = true; + + if (getState()->time <= kTime1138500) { + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpseDropWindowGreen : kEventCorpseDropWindowOriginal); + + getProgress().field_24 = true; + } else { + playAnimation(kEventCorpseDropBridge); + } + + getProgress().eventCorpseMovedFromFloor = true; + break; + } + + if (process) + getScenes()->processScene(); +} + +bool Action::handleOtherCompartment(ObjectIndex object, bool doPlaySound, bool doLoadScene) const { +#define ENTITY_PARAMS(entity, index, id) \ + ((EntityData::EntityParametersIIII*)getEntities()->get(entity)->getParamData()->getParameters(8, index))->param##id + + // Only handle compartments + if (getEntityData(kEntityPlayer)->location != kLocationOutsideCompartment + || ((object < kObjectCompartment2 || object > kObjectCompartment8) && (object < kObjectCompartmentA || object > kObjectCompartmentH))) + return false; + + ////////////////////////////////////////////////////////////////////////// + // Gendarmes + if (getEntityData(kEntityPlayer)->car == getEntityData(kEntityGendarmes)->car + && getEntityData(kEntityGendarmes)->location == kLocationOutsideCompartment + && !getEntities()->compare(kEntityPlayer, kEntityGendarmes)) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + + return true; + } + + ////////////////////////////////////////////////////////////////////////// + // Mertens + if (getEntityData(kEntityPlayer)->car == kCarGreenSleeping + && getEntityData(kEntityMertens)->car == kCarGreenSleeping + && !getEntityData(kEntityMertens)->location + && !ENTITY_PARAMS(kEntityMertens, 0, 1)) { + + if (!getEntities()->compare(kEntityPlayer, kEntityMertens)) { + + if (getEntityData(kEntityMertens)->entityPosition < kPosition_2740 + && getEntityData(kEntityMertens)->entityPosition > kPosition_850 + && (getEntityData(kEntityCoudert)->car != kCarGreenSleeping || getEntityData(kEntityCoudert)->entityPosition > kPosition_2740) + && (getEntityData(kEntityVerges)->car != kCarGreenSleeping || getEntityData(kEntityVerges)->entityPosition > kPosition_2740)) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityMertens)) + getSound()->playWarningCompartment(kEntityMertens, object); + + getSavePoints()->push(kEntityPlayer, kEntityMertens, kAction305159806); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + + return true; + } + + if (getEntityData(kEntityMertens)->direction == kDirectionUp + && getEntityData(kEntityMertens)->entityPosition < getEntityData(kEntityPlayer)->entityPosition) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityMertens)) + getSound()->playSound(kEntityMertens, (rnd(2)) ? "JAC1000" : "JAC1000A"); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + } + + if (getEntityData(kEntityMertens)->direction == kDirectionDown + && getEntityData(kEntityMertens)->entityPosition > getEntityData(kEntityPlayer)->entityPosition) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityMertens)) + getSound()->playSound(kEntityMertens, (rnd(2)) ? "JAC1000" : "JAC1000A"); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object, true); + } + } + } + + ////////////////////////////////////////////////////////////////////////// + // Coudert + if (getEntityData(kEntityPlayer)->car != kCarRedSleeping + || !getEntityData(kEntityCoudert)->car + || getEntityData(kEntityCoudert)->location != kLocationOutsideCompartment + || ENTITY_PARAMS(kEntityCoudert, 0, 1)) + return false; + + if (!getEntities()->compare(kEntityPlayer, kEntityCoudert)) { + + if (getEntityData(kEntityCoudert)->entityPosition < kPosition_2740 + && getEntityData(kEntityCoudert)->entityPosition > kPosition_850 + && (getEntityData(kEntityMertens)->car != kCarRedSleeping || getEntityData(kEntityMertens)->entityPosition > kPosition_2740) + && (getEntityData(kEntityVerges)->car != kCarRedSleeping || getEntityData(kEntityVerges)->entityPosition > kPosition_2740) + && (getEntityData(kEntityMmeBoutarel)->car != kCarRedSleeping || getEntityData(kEntityMmeBoutarel)->entityPosition > kPosition_2740)) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playWarningCompartment(kEntityCoudert, object); + + getSavePoints()->push(kEntityPlayer, kEntityCoudert, kAction305159806); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + + return true; + } + + // Direction = Up + if (getEntityData(kEntityCoudert)->direction == kDirectionUp + && getEntityData(kEntityCoudert)->entityPosition < getEntityData(kEntityPlayer)->entityPosition) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playSound(kEntityCoudert, (rnd(2)) ? "JAC1000" : "JAC1000A"); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + + return true; + } + + // Direction = down + if (getEntityData(kEntityCoudert)->direction == kDirectionDown + && getEntityData(kEntityCoudert)->entityPosition > getEntityData(kEntityPlayer)->entityPosition) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playSound(kEntityCoudert, (rnd(2)) ? "JAC1000" : "JAC1000A"); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object, true); + } + } + + return false; +} + +void Action::playCompartmentSoundEvents(ObjectIndex object) const { + if (getObjects()->get(object).location == kObjectLocation1 || getObjects()->get(object).location == kObjectLocation3 || getEntities()->checkFields2(object)) { + getSound()->playSoundEvent(kEntityPlayer, 13); + } else { + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->playSoundEvent(kEntityPlayer, 15, 3); + } +} + +////////////////////////////////////////////////////////////////////////// +// Cursors +////////////////////////////////////////////////////////////////////////// +CursorStyle Action::getCursor(const SceneHotspot &hotspot) const { + // Simple cursor style + if (hotspot.cursor != kCursorProcess) + return (CursorStyle)hotspot.cursor; + + ObjectIndex object = (ObjectIndex)hotspot.param1; + + switch (hotspot.action) { + default: + return kCursorNormal; + + case SceneHotspot::kActionInventory: + if (!getState()->sceneBackup2 && (getEvent(kEventKronosBringFirebird) || getProgress().isEggOpen)) + return kCursorNormal; + else + return kCursorBackward; + + case SceneHotspot::kActionKnockOnDoor: + warning("================================= DOOR %03d =================================", object); + if (object >= kObjectMax) + return kCursorNormal; + else + return (CursorStyle)getObjects()->get(object).cursor; + + case SceneHotspot::kAction12: + warning("================================= OBJECT %03d =================================", object); + if (object >= kObjectMax) + return kCursorNormal; + + if (getObjects()->get(object).entity) + return (CursorStyle)getObjects()->get(object).cursor; + else + return kCursorNormal; + + case SceneHotspot::kActionPickItem: + warning("================================= ITEM %03d =================================", object); + if (object >= kObjectCompartmentA) + return kCursorNormal; + + if ((!getInventory()->getSelectedItem() || getInventory()->getSelectedEntry()->manualSelect) + && (object != kObject21 || getProgress().eventCorpseMovedFromFloor == 1)) + return kCursorHand; + else + return kCursorNormal; + + case SceneHotspot::kActionDropItem: + warning("================================= ITEM %03d =================================", object); + if (object >= kObjectCompartmentA) + return kCursorNormal; + + if (getInventory()->getSelectedItem() != (InventoryItem)object) + return kCursorNormal; + + if (object == kObject20 && hotspot.param2 == 4 && !getProgress().isTrainRunning) + return kCursorNormal; + + if (object == kObjectHandleInsideBathroom && hotspot.param2 == 1 && getProgress().field_5C) + return kCursorNormal; + + return (CursorStyle)getInventory()->getSelectedEntry()->cursor; + + case SceneHotspot::kAction15: + if (object >= kObjectMax) + return kCursorNormal; + + if (getProgress().isEqual(object, hotspot.param2)) + return (CursorStyle)hotspot.param3; + + return kCursorNormal; + + case SceneHotspot::kActionEnterCompartment: + if ((getInventory()->getSelectedItem() != kItemKey || getObjects()->get(kObjectCompartment1).location) + && (getObjects()->get(kObjectCompartment1).location != 1 || !getInventory()->hasItem(kItemKey) + || (getInventory()->getSelectedItem() != kItemFirebird && getInventory()->getSelectedItem() != kItemBriefcase))) + goto LABEL_KEY; + + return (CursorStyle)getInventory()->get(kItemKey)->cursor; // TODO is that always the same as kCursorKey ? + + case SceneHotspot::kActionGetOutsideTrain: + if (getProgress().jacket != kJacketGreen) + return kCursorNormal; + + if ((getEvent(kEventCathLookOutsideWindowDay) || getEvent(kEventCathLookOutsideWindowDay) || getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) + && getProgress().isTrainRunning + && (object != kObjectOutsideAnnaCompartment || (getEntities()->isInsideCompartment(kEntityRebecca, kCarRedSleeping, kPosition_4840) && getObjects()->get(kObjectOutsideBetweenCompartments).location == 2)) + && getInventory()->getSelectedItem() != kItemBriefcase && getInventory()->getSelectedItem() != kItemFirebird) + return kCursorForward; + + return (getObjects()->get(kObjectCompartment1).location2 < kObjectLocation2) ? kCursorNormal : kCursorMagnifier; + + case SceneHotspot::kActionSlip: + return (getProgress().field_C8 < 1) ? kCursorNormal : kCursorLeft; + + case SceneHotspot::kActionClimbUpTrain: + if (getProgress().isTrainRunning + && (getProgress().chapter == kChapter2 || getProgress().chapter == kChapter3 || getProgress().chapter == kChapter5) + && getInventory()->getSelectedItem() != kItemFirebird + && getInventory()->getSelectedItem() != kItemBriefcase) + return kCursorUp; + + return kCursorNormal; + + case SceneHotspot::kActionJumpUpDownTrain: + if (object != kObjectCompartment1) + return kCursorNormal; + + return (getObjects()->get(kObjectCeiling).location < kObjectLocation1) ? kCursorHand : kCursorNormal; + + case SceneHotspot::kActionUnbound: + if (hotspot.param2 != 2) + return kCursorNormal; + + if (getEvent(kEventCathBurnRope) || !getEvent(kEventCathStruggleWithBonds2)) + return kCursorNormal; + + return kCursorHand; + + case SceneHotspot::kActionCatchBeetle: + if (!getBeetle()->isLoaded()) + return kCursorNormal; + + if (!getBeetle()->isCatchable()) + return kCursorNormal; + + if (getInventory()->getSelectedItem() == kItemMatchBox && getInventory()->hasItem(kItemMatch)) + return (CursorStyle)getInventory()->get(kItemMatchBox)->cursor; + + return kCursorHandPointer; + + case SceneHotspot::KActionUseWhistle: + if (object != kObjectCompartment3) + return kCursorNormal; + + if (getInventory()->getSelectedItem() == kItemWhistle) + return kCursorWhistle; + else + return kCursorNormal; + + case SceneHotspot::kActionOpenBed: + if (getProgress().chapter < kChapter2) + return kCursorHand; + + return kCursorNormal; + + case SceneHotspot::kActionDialog: + if (getSound()->getDialogName((EntityIndex)object)) + return kCursorHandPointer; + + return kCursorNormal; + + case SceneHotspot::kActionBed: + if (getProgress().field_18 == 2 && !getProgress().field_E4 + && (getState()->time > kTimeBedTime + || (getProgress().eventMetAugust && getProgress().field_CC + && (!getProgress().field_24 || getProgress().field_3C)))) + return kCursorSleep; + + return kCursorNormal; + +LABEL_KEY: + // Handle compartment action + case SceneHotspot::kActionCompartment: + case SceneHotspot::kActionExitCompartment: + warning("================================= DOOR %03d =================================", object); + if (object >= kObjectMax) + return kCursorNormal; + + if (getInventory()->getSelectedItem() != kItemKey + || getObjects()->get(object).entity + || getObjects()->get(object).location != 1 + || !getObjects()->get(object).cursor2 + || getEntities()->isInsideCompartments(kEntityPlayer) + || getEntities()->checkFields2(object)) + return (CursorStyle)getObjects()->get(object).cursor2; + else + return (CursorStyle)getInventory()->get(kItemKey)->cursor; + } +} + +////////////////////////////////////////////////////////////////////////// +// Animation +////////////////////////////////////////////////////////////////////////// + +// Play an animation and add delta time to global game time +void Action::playAnimation(EventIndex index, bool debugMode) const { + if (index >= _animationListSize) + error("Action::playAnimation: invalid event index (value=%i, max=%i)", index, _animationListSize); + + // In debug mode, just show the animation + if (debugMode) { + Animation animation; + if (animation.load(getArchive(Common::String(_animationList[index].filename) + ".nis"))) + animation.play(); + return; + } + + getFlags()->flag_3 = true; + + // Hide cursor + _engine->getCursor()->show(false); + + // Show inventory & hourglass + getInventory()->show(); + getInventory()->showHourGlass(); + + if (!getFlags()->mouseRightClick) { + + if (getGlobalTimer()) { + if (getSound()->isBuffered("TIMER")) { + getSound()->processEntry("TIMER"); + setGlobalTimer(105); + } + } + + bool processSound = false; + if (index >= kEventCorpseDropFloorOriginal + || index == kEventCathWakingUp + || index == kEventConcertCough + || index == kEventConcertSit + || index == kEventConcertLeaveWithBriefcase) + processSound = true; + + Animation animation; + if (animation.load(getArchive(Common::String(_animationList[index].filename) + ".nis") , processSound ? Animation::kFlagDefault : Animation::kFlagProcess)) + animation.play(); + + if (getSound()->isBuffered("TIMER")) + getSound()->removeFromQueue("TIMER"); + } + + // Show cursor + _engine->getCursor()->show(true); + + getEvent(index) = 1; + + // Adjust game time + getState()->timeTicks += _animationList[index].time; + getState()->time += _animationList[index].time * getState()->timeDelta; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/action.h b/engines/lastexpress/game/action.h new file mode 100644 index 0000000000..d8f81abfde --- /dev/null +++ b/engines/lastexpress/game/action.h @@ -0,0 +1,135 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ACTION_H +#define LASTEXPRESS_ACTION_H + +#include "lastexpress/shared.h" + +#include "common/array.h" +#include "common/func.h" +#include "common/system.h" + +namespace LastExpress { + +#define DECLARE_ACTION(name) \ + SceneIndex action_##name(const SceneHotspot &hotspot) const + +#define ADD_ACTION(name) \ + _actions.push_back(new Functor1MemConst<const SceneHotspot &, SceneIndex, Action>(this, &Action::action_##name)); + +#define IMPLEMENT_ACTION(name) \ + SceneIndex Action::action_##name(const SceneHotspot &hotspot) const { \ + debugC(6, kLastExpressDebugLogic, "Hotspot action: " #name "%s", hotspot.toString().c_str()); + +class LastExpressEngine; +class SceneHotspot; + +class Action { +public: + Action(LastExpressEngine *engine); + ~Action(); + + // Hotspot action + SceneIndex processHotspot(const SceneHotspot &hotspot); + + // Cursor + CursorStyle getCursor(const SceneHotspot &hotspot) const; + + // Animation + void playAnimation(EventIndex index, bool debugMode = false) const; + + // Compartment action + bool handleOtherCompartment(ObjectIndex object, bool doPlaySound, bool doLoadScene) const; + +private: + typedef Common::Functor1<const SceneHotspot &, SceneIndex> ActionFunctor; + + LastExpressEngine* _engine; + Common::Array<ActionFunctor *> _actions; + + // Each action is of the form action_<name>(SceneHotspot *hotspot) + // - a pointer to each action is added to the _actions array + // - processHotspot simply calls the proper function given by the hotspot->action value + // + // Note: even though there are 44 actions, only 41 are used in processHotspot + + DECLARE_ACTION(inventory); + DECLARE_ACTION(savePoint); + DECLARE_ACTION(playSound); + DECLARE_ACTION(playMusic); + DECLARE_ACTION(knock); + DECLARE_ACTION(compartment); + DECLARE_ACTION(playSounds); + DECLARE_ACTION(playAnimation); + DECLARE_ACTION(openCloseObject); + DECLARE_ACTION(updateObjetLocation2); + DECLARE_ACTION(setItemLocation); + DECLARE_ACTION(knockNoSound); + DECLARE_ACTION(pickItem); + DECLARE_ACTION(dropItem); + DECLARE_ACTION(enterCompartment); + DECLARE_ACTION(getOutsideTrain); + DECLARE_ACTION(slip); + DECLARE_ACTION(getInsideTrain); + DECLARE_ACTION(climbUpTrain); + DECLARE_ACTION(climbDownTrain); + DECLARE_ACTION(jumpUpDownTrain); + DECLARE_ACTION(unbound); + DECLARE_ACTION(25); + DECLARE_ACTION(26); + DECLARE_ACTION(27); + DECLARE_ACTION(concertSitCough); + DECLARE_ACTION(29); + DECLARE_ACTION(catchBeetle); + DECLARE_ACTION(exitCompartment); + DECLARE_ACTION(32); + DECLARE_ACTION(useWhistle); + DECLARE_ACTION(openMatchBox); + DECLARE_ACTION(openBed); + DECLARE_ACTION(dialog); + DECLARE_ACTION(eggBox); + DECLARE_ACTION(39); + DECLARE_ACTION(bed); + DECLARE_ACTION(playMusicChapter); + DECLARE_ACTION(playMusicChapterSetupTrain); + DECLARE_ACTION(switchChapter); + DECLARE_ACTION(44); + + // Special dummy function + DECLARE_ACTION(dummy); + + // Helpers + void pickGreenJacket(bool process) const; + void pickScarf(bool process) const; + void pickCorpse(ObjectLocation bedPosition, bool process) const; + void dropCorpse(bool process) const; + + void playCompartmentSoundEvents(ObjectIndex object) const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ACTION_H diff --git a/engines/lastexpress/game/beetle.cpp b/engines/lastexpress/game/beetle.cpp new file mode 100644 index 0000000000..665edb79a5 --- /dev/null +++ b/engines/lastexpress/game/beetle.cpp @@ -0,0 +1,517 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/beetle.h" + +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +Beetle::Beetle(LastExpressEngine *engine) : _engine(engine), _data(NULL) {} + +Beetle::~Beetle() { + SAFE_DELETE(_data); + + // Free passed pointers + _engine = NULL; +} + +void Beetle::load() { + // Only load in chapter 2 & 3 + if (getProgress().chapter != kChapter2 && getProgress().chapter != kChapter3) + return; + + // Already loaded + if (_data) + return; + + // Do not load if beetle is in the wrong location + if (getInventory()->get(kItemBeetle)->location != kObjectLocation3) + return; + + /////////////////////// + // Load Beetle data + _data = new BeetleData(); + + // Load sequences + _data->sequences.push_back(loadSequence("BW000.seq")); // 0 + _data->sequences.push_back(loadSequence("BT000045.seq")); + _data->sequences.push_back(loadSequence("BT045000.seq")); + _data->sequences.push_back(loadSequence("BW045.seq")); + _data->sequences.push_back(loadSequence("BT045090.seq")); + _data->sequences.push_back(loadSequence("BT090045.seq")); // 5 + _data->sequences.push_back(loadSequence("BW090.seq")); + _data->sequences.push_back(loadSequence("BT090135.seq")); + _data->sequences.push_back(loadSequence("BT135090.seq")); + _data->sequences.push_back(loadSequence("BW135.seq")); + _data->sequences.push_back(loadSequence("BT135180.seq")); // 10 + _data->sequences.push_back(loadSequence("BT180135.seq")); + _data->sequences.push_back(loadSequence("BW180.seq")); + _data->sequences.push_back(loadSequence("BT180225.seq")); + _data->sequences.push_back(loadSequence("BT225180.seq")); + _data->sequences.push_back(loadSequence("BW225.seq")); // 15 + _data->sequences.push_back(loadSequence("BT225270.seq")); + _data->sequences.push_back(loadSequence("BT270225.seq")); + _data->sequences.push_back(loadSequence("BW270.seq")); + _data->sequences.push_back(loadSequence("BT270315.seq")); + _data->sequences.push_back(loadSequence("BT315270.seq")); // 20 + _data->sequences.push_back(loadSequence("BW315.seq")); + _data->sequences.push_back(loadSequence("BT315000.seq")); + _data->sequences.push_back(loadSequence("BT000315.seq")); + _data->sequences.push_back(loadSequence("BA135.seq")); + _data->sequences.push_back(loadSequence("BL045.seq")); // 25 + _data->sequences.push_back(loadSequence("BL000.seq")); + _data->sequences.push_back(loadSequence("BL315.seq")); + _data->sequences.push_back(loadSequence("BL180.seq")); + + // Init fields + _data->field_74 = 0; + + // Check that all sequences are loaded properly + _data->isLoaded = true; + for (int i = 0; i < (int)_data->sequences.size(); i++) { + if (!_data->sequences[i]->isLoaded()) { + _data->isLoaded = false; + break; + } + } + + _data->field_D9 = 10; + _data->coordOffset = 5; + _data->coordY = 178; + _data->currentSequence = 0; + _data->offset = 0; + _data->frame = NULL; + _data->field_D5 = 0; + _data->indexes[0] = 29; + _data->field_DD = 0; +} + +void Beetle::unload() { + // Remove sequences from display list + if (_data) + getScenes()->removeFromQueue(_data->frame); + + // Delete all loaded sequences + SAFE_DELETE(_data); +} + +bool Beetle::isLoaded() const { + if (!_data) + return false; + + return _data->isLoaded; +} + +bool Beetle::catchBeetle() { + if (!_data) + error("Beetle::catchBeetle: sequences have not been loaded!"); + + if (getInventory()->getSelectedItem() == kItemMatchBox + && getInventory()->hasItem(kItemMatch) + && ABS((int16)(getCoords().x - _data->coordX)) < 10 + && ABS((int16)(getCoords().y - _data->coordY)) < 10) { + return true; + } + + _data->field_D5 = 0; + move(); + + return false; +} + +bool Beetle::isCatchable() const { + if (!_data) + error("Beetle::isCatchable: sequences have not been loaded!"); + + return (_data->indexes[_data->offset] >= 30); +} + +void Beetle::update() { + if (!_data) + error("Beetle::update: sequences have not been loaded!"); + + if (!_data->isLoaded) + return; + + move(); + + if (_data->field_D5) + _data->field_D5--; + + if (_data->currentSequence && _data->indexes[_data->offset] != 29) { + drawUpdate(); + return; + } + + if (getInventory()->get(kItemBeetle)->location == kObjectLocation3) { + if ((!_data->field_DD && rnd(10) < 1) + || (_data->field_DD && rnd(30) < 1) + || rnd(100) < 1) { + + _data->field_DD++; + if (_data->field_DD > 3) + _data->field_DD = 0; + + updateData(24); + + _data->coordX = (int16)(rnd(250) + 190); + _data->coordOffset = (int16)(rnd(5) + 5); + + if (_data->field_D9 > 1) + _data->field_D9--; + + drawUpdate(); + } + } +} + +void Beetle::drawUpdate() { + if (!_data) + error("Beetle::drawUpdate: sequences have not been loaded!"); + + if (_data->frame != NULL) { + getScenes()->setCoordinates(_data->frame); + getScenes()->removeFromQueue(_data->frame); + } + + // Update current frame + switch (_data->indexes[_data->offset]) { + default: + _data->currentFrame += 10; + break; + + case 3: + case 6: + case 9: + case 12: + case 15: + case 18: + case 21: + case 24: + case 25: + case 26: + case 27: + case 28: + _data->currentFrame++; + break; + } + + // Update current sequence + if (_data->currentSequence->count() <= _data->currentFrame) { + switch (_data->indexes[_data->offset]) { + default: + _data->offset++; + _data->currentSequence = _data->sequences[_data->indexes[_data->offset]]; + break; + + case 3: + case 6: + case 9: + case 12: + case 15: + case 18: + case 21: + break; + } + + _data->currentFrame = 0; + if (_data->indexes[_data->offset] == 29) { + SAFE_DELETE(_data->frame); + _data->currentSequence = NULL; // pointer to existing sequence + return; + } + } + + // Update coordinates + switch (_data->indexes[_data->offset]) { + default: + break; + + case 0: + _data->coordY -= _data->coordOffset; + break; + + case 3: + _data->coordX += _data->coordOffset; + _data->coordY -= _data->coordOffset; + break; + + case 6: + _data->coordX += _data->coordOffset; + break; + + case 9: + _data->coordX += _data->coordOffset; + _data->coordY += _data->coordOffset; + break; + + case 12: + _data->coordY += _data->coordOffset; + break; + + case 15: + _data->coordX -= _data->coordOffset; + _data->coordY += _data->coordOffset; + break; + + case 18: + _data->coordX -= _data->coordOffset; + break; + + case 21: + _data->coordX -= _data->coordOffset; + _data->coordY -= _data->coordOffset; + break; + } + + // Update beetle data + int rnd = rnd(100); + if (_data->coordX < 165 || _data->coordX > 465) { + uint index = 0; + + if (rnd >= 30) { + if (rnd >= 70) + index = (_data->coordX < 165) ? 9 : 15; + else + index = (_data->coordX < 165) ? 6 : 18; + } else { + index = (_data->coordX < 165) ? 3 : 21; + } + + updateData(index); + } + + if (_data->coordY < 178) { + switch (_data->indexes[_data->offset]) { + default: + updateData(26); + break; + + case 3: + updateData(25); + break; + + case 21: + updateData(27); + break; + } + } + + if (_data->coordY > 354) { + switch (_data->indexes[_data->offset]) { + default: + break; + + case 9: + case 12: + case 15: + updateData(28); + break; + } + } + +#define INVERT_Y() \ + switch (_data->indexes[_data->offset]) { \ + default: \ + break; \ + case 24: \ + case 25: \ + case 26: \ + case 27: \ + case 28: \ + _data->coordY = -_data->coordY; \ + break; \ + } + + // Invert direction + INVERT_Y(); + + SequenceFrame *frame = new SequenceFrame(_data->currentSequence, (uint16)_data->currentFrame); + updateFrame(frame); + + INVERT_Y(); + + getScenes()->addToQueue(frame); + + SAFE_DELETE(_data->frame); + _data->frame = frame; +} + +void Beetle::move() { + if (!_data) + error("Beetle::move: sequences have not been loaded!"); + + if (_data->indexes[_data->offset] >= 24 && _data->indexes[_data->offset] <= 29) + return; + + if (_data->field_D5) + return; + + if (ABS((int)(getCoords().x - _data->coordX)) > 35) + return; + + if (ABS((int)(getCoords().y - _data->coordY)) > 35) + return; + + int32 deltaX = getCoords().x - _data->coordX; + int32 deltaY = -getCoords().y - _data->coordY; + uint32 index = 0; + + // FIXME: check code path + if (deltaX >= 0) { + if (deltaY > 0) { + if (100 * deltaY - 241 * deltaX <= 0) { + if (100 * deltaY - 41 * deltaX <= 0) + index = 18; + else + index = 15; + } else { + index = 12; + } + + goto update_data; + } + } + + if (deltaX < 0) { + + if (deltaY > 0) { + if (100 * deltaY + 241 * deltaX <= 0) { + if (100 * deltaY + 41 * deltaX <= 0) + index = 6; + else + index = 9; + } else { + index = 12; + } + + goto update_data; + } + + if (deltaY <= 0) { + if (100 * deltaY - 41 * deltaX <= 0) { + if (100 * deltaY - 241 * deltaX <= 0) + index = 0; + else + index = 3; + } else { + index = 6; + } + + goto update_data; + } + } + +update_data: + updateData(index); + + if (_data->coordOffset >= 15) { + _data->field_D5 = 0; + return; + } + + _data->coordOffset = _data->coordOffset + (int16)(4 * rnd(100)/100 + _data->field_D9); + _data->field_D5 = 0; +} + +// Update the beetle sequence to show the correct frames in the correct place +void Beetle::updateFrame(SequenceFrame *frame) const { + if (!_data) + error("Beetle::updateSequence: sequences have not been loaded!"); + + if (!frame) + return; + + // Update coordinates + if (_data->coordX > 0) + frame->getInfo()->xPos1 = (uint16)_data->coordX; + + if (_data->coordY > 0) + frame->getInfo()->yPos1 = (uint16)_data->coordY; +} + +void Beetle::updateData(uint32 index) { + if (!_data) + error("Beetle::updateData: sequences have not been loaded!"); + + if (!_data->isLoaded) + return; + + if (index == 25 || index == 26 || index == 27 || index == 28) { + _data->indexes[0] = index; + _data->indexes[1] = 29; + _data->offset = 0; + + _data->currentSequence = _data->sequences[index]; + _data->currentFrame = 0; + _data->index = index; + } else { + if (!_data->sequences[index]) + return; + + if (_data->index == index) + return; + + _data->offset = 0; + + // Special case for sequence 24 + if (index == 24) { + _data->indexes[0] = index; + _data->coordY = 178; + _data->index = _data->indexes[1]; + _data->indexes[1] = (_data->coordX >= 265) ? 15 : 9; + _data->currentFrame = 0; + _data->currentSequence = _data->sequences[index]; + } else { + if (index <= _data->index) { + for (uint32 i = _data->index - 1; i > index; ++_data->offset) { + _data->indexes[_data->offset] = i; + i -= 3; + } + } else { + for (uint32 i = _data->index + 1; i < index; ++_data->offset) { + _data->indexes[_data->offset] = i; + i += 3; + } + } + + _data->index = index; + _data->indexes[_data->offset] = index; + _data->currentFrame = 0; + _data->offset = 0; + _data->currentSequence = _data->sequences[_data->indexes[0]]; + } + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/beetle.h b/engines/lastexpress/game/beetle.h new file mode 100644 index 0000000000..ece5866b85 --- /dev/null +++ b/engines/lastexpress/game/beetle.h @@ -0,0 +1,118 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_BEETLE_H +#define LASTEXPRESS_BEETLE_H + +#include "lastexpress/data/sequence.h" + +#include "common/array.h" +#include "common/system.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Beetle { +public: + + Beetle(LastExpressEngine *engine); + ~Beetle(); + + void update(); + + void load(); + void unload(); + + bool isLoaded() const; + + bool catchBeetle(); + bool isCatchable() const; + +private: + struct BeetleData { + Common::Array<Sequence *> sequences; + + uint32 field_74; + Sequence *currentSequence; + uint32 currentFrame; + uint32 index; + int16 coordOffset; + int16 field_86; + + int16 coordX; + int16 coordY; + + uint32 indexes[16]; + + uint32 offset; + SequenceFrame* frame; + bool isLoaded; + uint32 field_D5; + uint32 field_D9; + uint32 field_DD; + + BeetleData() { + field_74 = 0; + currentSequence = NULL; + currentFrame = 0; + index = 0; + coordOffset = 0; + + field_86 = 0; + + coordX = 0; + coordY = 0; + + memset(indexes, 0, sizeof(indexes)); + offset = 0; + + frame = NULL; + isLoaded = false; + field_D5 = 0; + field_D9 = 0; + field_DD = 0; + } + + ~BeetleData() { + for (int i = 0; i < (int)sequences.size(); i++) + if (sequences[i]) + delete sequences[i]; + } + }; + + LastExpressEngine* _engine; + + BeetleData *_data; + + void move(); + void updateFrame(SequenceFrame *frame) const; + void updateData(uint32 index); + void drawUpdate(); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_BEETLE_H diff --git a/engines/lastexpress/game/entities.cpp b/engines/lastexpress/game/entities.cpp new file mode 100644 index 0000000000..ef21fee0ae --- /dev/null +++ b/engines/lastexpress/game/entities.cpp @@ -0,0 +1,2731 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/entities.h" + +// Data +#include "lastexpress/data/scene.h" +#include "lastexpress/data/sequence.h" + +// Entities +#include "lastexpress/entities/entity.h" + +#include "lastexpress/entities/abbot.h" +#include "lastexpress/entities/alexei.h" +#include "lastexpress/entities/alouan.h" +#include "lastexpress/entities/anna.h" +#include "lastexpress/entities/august.h" +#include "lastexpress/entities/boutarel.h" +#include "lastexpress/entities/chapters.h" +#include "lastexpress/entities/cooks.h" +#include "lastexpress/entities/coudert.h" +#include "lastexpress/entities/entity39.h" +#include "lastexpress/entities/francois.h" +#include "lastexpress/entities/gendarmes.h" +#include "lastexpress/entities/hadija.h" +#include "lastexpress/entities/ivo.h" +#include "lastexpress/entities/kahina.h" +#include "lastexpress/entities/kronos.h" +#include "lastexpress/entities/mahmud.h" +#include "lastexpress/entities/max.h" +#include "lastexpress/entities/mertens.h" +#include "lastexpress/entities/milos.h" +#include "lastexpress/entities/mmeboutarel.h" +#include "lastexpress/entities/pascale.h" +#include "lastexpress/entities/rebecca.h" +#include "lastexpress/entities/salko.h" +#include "lastexpress/entities/servers0.h" +#include "lastexpress/entities/servers1.h" +#include "lastexpress/entities/sophie.h" +#include "lastexpress/entities/tables.h" +#include "lastexpress/entities/tatiana.h" +#include "lastexpress/entities/train.h" +#include "lastexpress/entities/vassili.h" +#include "lastexpress/entities/verges.h" +#include "lastexpress/entities/vesna.h" +#include "lastexpress/entities/yasmin.h" + +// Game +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +#define STORE_VALUE(data) ((uint)1 << (uint)data) + +static const EntityPosition objectsPosition[8] = {kPosition_8200, kPosition_7500, + kPosition_6470, kPosition_5790, + kPosition_4840, kPosition_4070, + kPosition_3050, kPosition_2740}; + +static const EntityPosition entityPositions[41] = { + kPositionNone, kPosition_851, kPosition_1430, kPosition_2110, kPositionNone, + kPosition_2410, kPosition_2980, kPosition_3450, kPosition_3760, kPosition_4100, + kPosition_4680, kPosition_5140, kPosition_5440, kPosition_5810, kPosition_6410, + kPosition_6850, kPosition_7160, kPosition_7510, kPosition_8514, kPositionNone, + kPositionNone, kPositionNone, kPosition_2086, kPosition_2690, kPositionNone, + kPosition_3110, kPosition_3390, kPosition_3890, kPosition_4460, kPosition_4770, + kPosition_5090, kPosition_5610, kPosition_6160, kPosition_6460, kPosition_6800, + kPosition_7320, kPosition_7870, kPosition_8160, kPosition_8500, kPosition_9020, + kPosition_9269}; + +#define ADD_ENTITY(class) \ + _entities.push_back(new class(engine)); + +#define COMPUTE_SEQUENCE_NAME(sequenceTo, sequenceFrom) { \ + sequenceTo = sequenceFrom; \ + for (int seqIdx = 0; seqIdx < 7; seqIdx++) \ + sequenceTo.deleteLastChar(); \ + if (isInsideTrainCar(entityIndex, kCarGreenSleeping) || isInsideTrainCar(entityIndex, kCarGreenSleeping)) { \ + if (data->car < getData(kEntityPlayer)->car || (data->car == getData(kEntityPlayer)->car && data->entityPosition < getData(kEntityPlayer)->entityPosition)) \ + sequenceTo += "R.SEQ"; \ + else \ + sequenceTo += "F.SEQ"; \ + } else { \ + sequenceTo += ".SEQ"; \ + } \ +} + +#define TRY_LOAD_SEQUENCE(sequence, name, name1, name2) { \ + if (data->car == getData(kEntityPlayer)->car) \ + sequence = loadSequence1(name1, field30); \ + if (sequence) { \ + name = name1; \ + } else { \ + if (name2 != "") \ + sequence = loadSequence1(name2, field30); \ + name = (sequence ? name2 : ""); \ + } \ +} + +////////////////////////////////////////////////////////////////////////// +// Entities +////////////////////////////////////////////////////////////////////////// +Entities::Entities(LastExpressEngine *engine) : _engine(engine) { + _header = new EntityData(); + + _entities.push_back(NULL); // Header + ADD_ENTITY(Anna); + ADD_ENTITY(August); + ADD_ENTITY(Mertens); + ADD_ENTITY(Coudert); + ADD_ENTITY(Pascale); + ADD_ENTITY(Servers0); + ADD_ENTITY(Servers1); + ADD_ENTITY(Cooks); + ADD_ENTITY(Verges); + ADD_ENTITY(Tatiana); + ADD_ENTITY(Vassili); + ADD_ENTITY(Alexei); + ADD_ENTITY(Abbot); + ADD_ENTITY(Milos); + ADD_ENTITY(Vesna); + ADD_ENTITY(Ivo); + ADD_ENTITY(Salko); + ADD_ENTITY(Kronos); + ADD_ENTITY(Kahina); + ADD_ENTITY(Francois); + ADD_ENTITY(MmeBoutarel); + ADD_ENTITY(Boutarel); + ADD_ENTITY(Rebecca); + ADD_ENTITY(Sophie); + ADD_ENTITY(Mahmud); + ADD_ENTITY(Yasmin); + ADD_ENTITY(Hadija); + ADD_ENTITY(Alouan); + ADD_ENTITY(Gendarmes); + ADD_ENTITY(Max); + ADD_ENTITY(Chapters); + ADD_ENTITY(Train); + + // Special case for tables + _entities.push_back(new Tables(engine, kEntityTables0)); + _entities.push_back(new Tables(engine, kEntityTables1)); + _entities.push_back(new Tables(engine, kEntityTables2)); + _entities.push_back(new Tables(engine, kEntityTables3)); + _entities.push_back(new Tables(engine, kEntityTables4)); + _entities.push_back(new Tables(engine, kEntityTables5)); + + ADD_ENTITY(Entity39); + + // Init compartments & positions + memset(&_compartments, 0, sizeof(_compartments)); + memset(&_compartments1, 0, sizeof(_compartments1)); + memset(&_positions, 0, sizeof(_positions)); +} + +Entities::~Entities() { + delete _header; + + for (int i = 0; i < (int)_entities.size(); i++) + delete _entities[i]; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Accessors +////////////////////////////////////////////////////////////////////////// +Entity *Entities::get(EntityIndex entity) { + assert((uint)entity < _entities.size()); + + if (entity == kEntityPlayer) + error("Cannot get entity for index == 0!"); + + return _entities[entity]; +} + +EntityData::EntityCallData *Entities::getData(EntityIndex entity) const { + assert((uint)entity < _entities.size()); + + if (entity == kEntityPlayer) + return _header->getCallData(); + + return _entities[entity]->getData(); +} + +int Entities::getPosition(CarIndex car, Position position) { + int index = 100 * car + position; + + if (car < 0 || car > 10) + error("Entities::getPosition: trying to access an invalid car (was: %d, valid:0-9)", car); + + if (position < 0 || position > 100) + error("Entities::getPosition: trying to access an invalid position (was: %d, valid:0-100)", position); + + return _positions[index]; +} + +int Entities::getCompartments(int index) { + if (index >= _compartmentsCount) + error("Entities::getCompartments: trying to access an invalid compartment (was: %d, valid:0-15)", index); + + return _compartments[index]; +} + +int Entities::getCompartments1(int index) { + if (index >= _compartmentsCount) + error("Entities::getCompartments: trying to access an invalid compartment (was: %d, valid:0-15)", index); + + return _compartments1[index]; +} + +////////////////////////////////////////////////////////////////////////// +// Savegame +////////////////////////////////////////////////////////////////////////// +void Entities::saveLoadWithSerializer(Common::Serializer &ser) { + _header->saveLoadWithSerializer(ser); + for (uint i = 1; i < _entities.size(); i++) + _entities[i]->saveLoadWithSerializer(ser); +} + +////////////////////////////////////////////////////////////////////////// +// Setup +////////////////////////////////////////////////////////////////////////// +void Entities::setup(bool isFirstChapter, EntityIndex entityIndex) { + setupChapter(isFirstChapter ? kChapter1 : kChapterAll); + + bool flag_4 = false; + + if (!isFirstChapter) { + getFlags()->flag_4 = false; + + if (entityIndex) { + getSavePoints()->call(kEntityPlayer, entityIndex, kActionNone); + flag_4 = getFlags()->flag_4; + } + } + + getFlags()->flag_4 = flag_4; + if (!getFlags()->flag_4) + getScenes()->loadScene(getState()->scene); +} + +void Entities::setupChapter(ChapterIndex chapter) { + if (chapter) { + // Reset current call, inventory item & draw sequences + for (uint i = 1; i < _entities.size(); i++) { + getData((EntityIndex)i)->currentCall = 0; + getData((EntityIndex)i)->inventoryItem = kItemNone; + + clearSequences((EntityIndex)i); + } + + // Init compartments & positions + memset(&_compartments, 0, sizeof(_compartments)); + memset(&_compartments1, 0, sizeof(_compartments1)); + memset(&_positions, 0, sizeof(_positions)); + + getSound()->resetQueue(SoundManager::kSoundType13); + } + + // we skip the header when doing entity setup + for (uint i = 1; i < _entities.size(); i++) { + // Special case of chapters (prevents infinite loop as we will be called from Chapters functions when changing chapters) + if (i == kEntityChapters && chapter >= 2) + continue; + + _entities[i]->setup(chapter); + } +} + +void Entities::reset() { + // Reset header + delete _header; + _header = new EntityData(); + + for (uint i = 1; i < _entities.size(); i++) + resetSequences((EntityIndex)i); + + getScenes()->resetDoorsAndClock(); +} + +////////////////////////////////////////////////////////////////////////// +// State & Sequences +////////////////////////////////////////////////////////////////////////// + +EntityIndex Entities::canInteractWith(const Common::Point &point) const { + if (!getFlags()->isGameRunning) + return kEntityPlayer; + + EntityIndex index = kEntityPlayer; + int location = 10000; + + // Check if there is an entity we can interact with + for (uint i = 0; i < _entities.size(); i++) { + + // Skip entities with no current frame + if (!getData((EntityIndex)i)->frame) + continue; + + FrameInfo *info = getData((EntityIndex)i)->frame->getInfo(); + + // Check the hotspot + if (info->hotspot.contains(point)) { + + // If closer to us, update with its values + if (location > info->location) { + location = info->location; + index = (EntityIndex)i; + } + } + } + + // Check if we found an entity + if (!index) + return kEntityPlayer; + + // Check that there is an item to interact with + if (!getData(index)->inventoryItem) + return kEntityPlayer; + + return index; +} + +void Entities::resetState(EntityIndex entityIndex) { + getData(entityIndex)->currentCall = 0; + getData(entityIndex)->inventoryItem = kItemNone; + + if (getSound()->isBuffered(entityIndex)) + getSound()->removeFromQueue(entityIndex); + + clearSequences(entityIndex); + + if (entityIndex == kEntity39) + entityIndex = kEntityPlayer; + + if (entityIndex > kEntityChapters) + return; + + // reset compartments and positions for this entity + for (int i = 0; i < _positionsCount; i++) + _positions[i] &= ~STORE_VALUE(entityIndex); + + for (int i = 0; i < _compartmentsCount; i++) { + _compartments[i] &= ~STORE_VALUE(entityIndex); + _compartments1[i] &= ~STORE_VALUE(entityIndex); + } + + getLogic()->updateCursor(); +} + + +void Entities::updateFields() const { + if (!getFlags()->isGameRunning) + return; + + for (int i = 0; i < (int)_entities.size(); i++) { + + if (!getSavePoints()->getCallback((EntityIndex)i)) + continue; + + EntityData::EntityCallData *data = getData((EntityIndex)i); + int positionDelta = data->field_4A3 * 10; + switch (data->direction) { + default: + break; + + case kDirectionUp: + if (data->entityPosition >= 10000 - positionDelta) + data->entityPosition = (EntityPosition)(data->entityPosition + positionDelta); + break; + + case kDirectionDown: + if (data->entityPosition > positionDelta) + data->entityPosition = (EntityPosition)(data->entityPosition - positionDelta); + break; + + case kDirectionLeft: + data->currentFrame++; + break; + + case kDirectionRight: + data->field_4A1 += 9; + break; + + case kDirectionSwitch: + if (data->directionSwitch == kDirectionRight) + data->field_4A1 += 9; + break; + + } + } +} + +void Entities::updateFrame(EntityIndex entityIndex) const { + Sequence *sequence = NULL; + int16 *currentFrame = NULL; + bool found = false; + + if (getData(entityIndex)->direction == kDirectionSwitch) { + sequence = getData(entityIndex)->sequence2; + currentFrame = &getData(entityIndex)->currentFrame2; + } else { + sequence = getData(entityIndex)->sequence; + currentFrame = &getData(entityIndex)->currentFrame; + } + + if (!sequence) + return; + + // Save current values + int16 oldFrame = *currentFrame; + int16 field_4A1 = getData(entityIndex)->field_4A1; + + do { + // Check we do not get past the end + if (*currentFrame >= (int)sequence->count() - 1) + break; + + // Get the proper frame + FrameInfo *info = sequence->getFrameInfo((uint16)*currentFrame); + + if (info->field_33 & 8) { + found = true; + } else { + if (info->soundAction == 35) + found = true; + + getData(entityIndex)->field_4A1 += info->field_30; + + // Progress to the next frame + ++*currentFrame; + } + } while (!found); + + // Restore old values + if (!found) { + *currentFrame = oldFrame; + getData(entityIndex)->field_4A1 = field_4A1; + } +} + +void Entities::updateSequences() const { + if (!getFlags()->isGameRunning) + return; + + // Update the train clock & doors + getScenes()->updateDoorsAndClock(); + + ////////////////////////////////////////////////////////////////////////// + // First pass: Drawing + ////////////////////////////////////////////////////////////////////////// + for (uint i = 1; i < _entities.size(); i++) { + EntityIndex entityIndex = (EntityIndex)i; + + if (!getSavePoints()->getCallback(entityIndex)) + continue; + + EntityData::EntityCallData *data = getData(entityIndex); + + if (data->frame) { + getScenes()->removeFromQueue(data->frame); + SAFE_DELETE(data->frame); + } + + if (data->frame1) { + getScenes()->removeFromQueue(data->frame1); + SAFE_DELETE(data->frame1); + } + + if (data->direction == kDirectionSwitch) { + + // Clear sequence 2 + if (data->sequence) + SAFE_DELETE(data->sequence); + + // Replace by sequence 3 if available + if (data->sequence2) { + data->sequence = data->sequence2; + data->sequenceName = data->sequenceName2; + + data->sequence2 = NULL; + data->sequenceName2 = ""; + } + + data->direction = data->directionSwitch; + data->currentFrame = -1; + data->field_49B = 0; + } + + // Draw sequences + drawSequences(entityIndex, data->direction, false); + } + + ////////////////////////////////////////////////////////////////////////// + // Second pass: Load sequences for next pass + ////////////////////////////////////////////////////////////////////////// + for (uint i = 1; i < _entities.size(); i++) { + EntityIndex entityIndex = (EntityIndex)i; + + if (!getSavePoints()->getCallback(entityIndex)) + continue; + + EntityData::EntityCallData *data = getData(entityIndex); + byte field30 = (data->direction == kDirectionLeft ? entityIndex + 35 : 15); + + if (data->sequenceName != "" && !data->sequence) { + data->sequence = loadSequence1(data->sequenceName, field30); + + // If sequence 2 was loaded correctly, remove the copied name + // otherwise, compute new name + if (data->sequence) { + data->sequenceNameCopy = ""; + } else { + Common::String sequenceName; + + // Left and down directions + if (data->direction == kDirectionLeft || data->direction == kDirectionRight) { + COMPUTE_SEQUENCE_NAME(sequenceName, data->sequenceName); + + // Try loading the sequence + data->sequence = loadSequence1(sequenceName, field30); + } + + // Update sequence names + data->sequenceNameCopy = (data->sequence ? "" : data->sequenceName); + data->sequenceName = (data->sequence ? sequenceName : ""); + } + } + + // Update sequence 3 + if (data->sequenceName2 != "" && !data->sequence2) { + + if (data->car == getData(kEntityPlayer)->car) + data->sequence2 = loadSequence1(data->sequenceName2, field30); + + if (!data->sequence2) { + Common::String sequenceName; + + // Left and down directions + if (data->directionSwitch == kDirectionLeft || data->directionSwitch == kDirectionRight) { + COMPUTE_SEQUENCE_NAME(sequenceName, data->sequenceName2); + + // Try loading the sequence + data->sequence2 = loadSequence1(sequenceName, field30); + } + + // Update sequence names + data->sequenceName2 = (data->sequence2 ? sequenceName : ""); + } + } + } +} + +void Entities::resetSequences(EntityIndex entityIndex) const { + + // Reset direction + if (getData(entityIndex)->direction == kDirectionSwitch) { + getData(entityIndex)->direction = getData(entityIndex)->directionSwitch; + getData(entityIndex)->field_49B = 0; + getData(entityIndex)->currentFrame = -1; + } + + // FIXME: in the original engine, the sequence pointers might just be copies, + // make sure we free the associated memory at some point + getData(entityIndex)->frame = NULL; + getData(entityIndex)->frame1 = NULL; + + SAFE_DELETE(getData(entityIndex)->sequence); + SAFE_DELETE(getData(entityIndex)->sequence2); + SAFE_DELETE(getData(entityIndex)->sequence3); + + getData(entityIndex)->field_4A9 = false; + getData(entityIndex)->field_4AA = false; + + strcpy((char*)&getData(entityIndex)->sequenceNameCopy, ""); + strcpy((char*)&getData(entityIndex)->sequenceName, ""); + strcpy((char*)&getData(entityIndex)->sequenceName2, ""); + + // Original engine resets flag to decompress data on the fly (we don't need to do that) +} + +////////////////////////////////////////////////////////////////////////// +// Callbacks +////////////////////////////////////////////////////////////////////////// +void Entities::updateCallbacks() { + if (!getFlags()->isGameRunning) + return; + + getFlags()->flag_entities_0 = false; + + if (getFlags()->flag_entities_1) { + executeCallbacks(); + getFlags()->flag_entities_0 = true; + } else { + getFlags()->flag_entities_1 = true; + executeCallbacks(); + getFlags()->flag_entities_1 = false; + } +} + +void Entities::executeCallbacks() { + for (uint i = 1; i < _entities.size(); i++) { + if (getFlags()->flag_entities_0) + break; + + if (getSavePoints()->getCallback((EntityIndex)i)) + processEntity((EntityIndex)i); + } + + if (getFlags()->flag_entities_0) + return; + + bool processed = true; + do { + processed = true; + for (int i = 1; i < (int)_entities.size(); i++) { + if (getFlags()->flag_entities_0) + break; + + if (getSavePoints()->getCallback((EntityIndex)i)) { + if (getData((EntityIndex)i)->doProcessEntity) { + processed = false; + processEntity((EntityIndex)i); + } + } + } + } while (!processed); +} + +////////////////////////////////////////////////////////////////////////// +// Processing +////////////////////////////////////////////////////////////////////////// +#define INCREMENT_DIRECTION_COUNTER() { \ + data->doProcessEntity = false; \ + if (data->direction == kDirectionRight || (data->direction == kDirectionSwitch && data->directionSwitch == kDirectionRight)) \ + ++data->field_4A1; \ + } + +void Entities::processEntity(EntityIndex entityIndex) { + EntityData::EntityCallData *data = getData(entityIndex); + bool keepPreviousFrame = false; + + data->doProcessEntity = false; + + if (getData(kEntityPlayer)->car != data->car && data->direction != kDirectionRight && data->direction != kDirectionSwitch) { + + if (data->position) { + updatePositionExit(entityIndex, data->car2, data->position); + data->car2 = kCarNone; + data->position = 0; + } + + getScenes()->removeAndRedraw(&data->frame, false); + getScenes()->removeAndRedraw(&data->frame1, false); + + INCREMENT_DIRECTION_COUNTER(); + return; + } + + if (data->frame1) { + getScenes()->removeAndRedraw(&data->frame1, false); + + if (data->frame && data->frame->getInfo()->subType != kFrameType3) { + data->frame->getInfo()->subType = kFrameTypeNone; + getScenes()->setFlagDrawSequences(); + } + } + + SAFE_DELETE(data->sequence3); + + if (!data->frame || !data->direction) { + if (!data->sequence) +label_nosequence: + drawSequences(entityIndex, data->direction, true); + + data->doProcessEntity = false; + computeCurrentFrame(entityIndex); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + + if (data->sequence && data->currentFrame != -1 && data->currentFrame <= (int16)(data->sequence->count() - 1)) { + processFrame(entityIndex, false, true); + + if (!getFlags()->flag_entities_0 && !data->doProcessEntity) { + INCREMENT_DIRECTION_COUNTER(); + return; + } + } else { + if (data->direction == kDirectionRight && data->field_4A1 > 100) { + getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (data->position) { + updatePositionExit(entityIndex, data->car2, data->position); + data->car2 = kCarNone; + data->position = 0; + } + + INCREMENT_DIRECTION_COUNTER(); + } + return; + } + + if (!data->sequence) + goto label_nosequence; + + if (data->frame->getInfo()->field_30 > data->field_49B + 1 || (data->direction == kDirectionLeft && data->sequence->count() == 1)) { + ++data->field_49B; + INCREMENT_DIRECTION_COUNTER(); + return; + } + + if (data->frame->getInfo()->field_30 > data->field_49B && !data->frame->getInfo()->keepPreviousFrame) { + ++data->field_49B; + INCREMENT_DIRECTION_COUNTER(); + return; + } + + if (data->frame->getInfo()->keepPreviousFrame == 1) + keepPreviousFrame = true; + + // Increment current frame + ++data->currentFrame; + + if (data->currentFrame > (int16)(data->sequence->count() - 1) || (data->field_4A9 && checkSequenceFromPosition(entityIndex))) { + + if (data->direction == kDirectionLeft) { + data->currentFrame = 0; + } else { + keepPreviousFrame = true; + drawNextSequence(entityIndex); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + + if (!data->sequence2) { + updateEntityPosition(entityIndex); + data->doProcessEntity = false; + return; + } + + copySequenceData(entityIndex); + } + + } + + processFrame(entityIndex, keepPreviousFrame, false); + + if (!getFlags()->flag_entities_0 && !data->doProcessEntity) + INCREMENT_DIRECTION_COUNTER(); +} + +void Entities::computeCurrentFrame(EntityIndex entityIndex) const { + EntityData::EntityCallData *data = getData(entityIndex); + int16 originalCurrentFrame = data->currentFrame; + + if (!data->sequence) { + data->currentFrame = -1; + return; + } + + switch (data->direction) { + default: + break; + + case kDirectionNone: + case kDirectionSwitch: + data->currentFrame = -1; + break; + + case kDirectionUp: + case kDirectionDown: { + Scene *scene = getScenes()->get(getState()->scene); + + if (scene->position > 40) + break; + + switch (scene->position) { + default: + case 4: + case 19: + case 20: + case 21: + case 24: + break; + + case 1: + case 18: + case 22: + case 40: + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); + break; + + case 2: + case 3: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + if (data->field_4A9) { + if (getData(kEntityPlayer)->entityPosition >= data->entityPosition) { + data->currentFrame = -1; + } else { + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, getEntityPositionFromCurrentPosition(), true); + + if (data->currentFrame != -1 && originalCurrentFrame == data->currentFrame) + if (data->currentFrame < (int)(data->sequence->count() - 2)) + data->currentFrame += 2; + } + } else { + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); + } + break; + + case 23: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + case 38: + case 39: + if (data->field_4A9) { + if (getData(kEntityPlayer)->entityPosition <= data->entityPosition) { + data->currentFrame = -1; + } else { + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, getEntityPositionFromCurrentPosition(), true); + + if (data->currentFrame != -1 && originalCurrentFrame == data->currentFrame) + if (data->currentFrame < (int)(data->sequence->count() - 2)) + data->currentFrame += 2; + } + } else { + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); + } + break; + } + + } + break; + + + case kDirectionLeft: + if (data->currentFrame == -1 || data->currentFrame >= (int32)data->sequence->count()) { + data->currentFrame = 0; + data->field_49B = 0; + } + break; + + case kDirectionRight: + bool found = false; + bool flag = false; + uint16 frameIndex = 0; + byte field30 = 0; + + int16 currentFrameCopy = (!data->currentFrame && !data->field_4A1) ? -1 : data->currentFrame; + + // Process frames + do { + if (frameIndex >= data->sequence->count()) + break; + + FrameInfo *info = data->sequence->getFrameInfo(frameIndex); + + if (field30 + info->field_30 >= data->field_4A1) { + found = true; + break; + } + + if (field30 > data->field_4A1 - 10) { + if (info->soundAction) + getSound()->playSoundEvent(entityIndex, info->soundAction, (field30 <= data->field_4A1 - info->field_31) ? 0 : (byte)(field30 + info->field_31 - data->field_4A1)); + } + + field30 += info->field_30; + + if (info->field_33 & 4) + flag = true; + + if (info->field_33 & 2) { + flag = false; + + getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (info->field_33 & 16) { + getSavePoints()->push(kEntityPlayer, entityIndex, kAction4); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + frameIndex++; + + } while (!found); + + if (found) { + + if (flag) { + bool found2 = false; + + do { + if (frameIndex >= data->sequence->count()) + break; + + FrameInfo *info = data->sequence->getFrameInfo(frameIndex); + if (info->field_33 & 2) { + found2 = true; + + getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + + } else { + data->field_4A1 += info->field_30; + + byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; + if (soundAction) + getSound()->playSoundEvent(entityIndex, soundAction); + + ++frameIndex; + } + + } while (!found2); + + if (found2) { + data->currentFrame = frameIndex; + data->field_49B = 0; + + byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; + byte field31 = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_31; + if (soundAction && data->currentFrame != currentFrameCopy) + getSound()->playSoundEvent(entityIndex, soundAction, field31); + + } else { + data->currentFrame = (int16)(data->sequence->count() - 1); + data->field_49B = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_30; + } + + } else { + + data->currentFrame = frameIndex; + data->field_49B = data->field_4A1 - field30; + + byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; + byte field31 = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_31; + if (soundAction && data->currentFrame != currentFrameCopy) + getSound()->playSoundEvent(entityIndex, soundAction, field31 <= data->field_49B ? 0 : (byte)(field31 - data->field_49B)); + } + } else { + data->currentFrame = (int16)(data->sequence->count() - 1); + data->field_49B = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_30; + + getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); + getSavePoints()->process(); + } + break; + } +} + +int16 Entities::getCurrentFrame(EntityIndex entity, Sequence *sequence, EntityPosition position, bool doProcessing) const { + EntityData::EntityCallData *data = getData(entity); + + EntityPosition firstFramePosition = sequence->getFrameInfo(0)->entityPosition; + EntityPosition lastFramePosition = sequence->getFrameInfo(sequence->count() - 1)->entityPosition; + + bool isGoingForward = (firstFramePosition < lastFramePosition); + + if (!doProcessing) { + if (!isGoingForward) { + if (data->field_4A3 + firstFramePosition < data->entityPosition || lastFramePosition - data->field_4A3 > data->entityPosition) + return -1; + } else { + if (firstFramePosition - data->field_4A3 > data->entityPosition || lastFramePosition + data->field_4A3 < data->entityPosition) + return -1; + } + } + + if (sequence->count() == 0) + return 0; + + // Search for the correct frame + // TODO: looks slightly like some sort of binary search + uint16 frame = 0; + uint16 numFrames = sequence->count() - 1; + + for (;;) { + uint16 currentFrame = (frame + numFrames) / 2; + + if (position + sequence->getFrameInfo(currentFrame)->entityPosition <= data->entityPosition) { + if (!isGoingForward) + numFrames = (frame + numFrames) / 2; + else + frame = (frame + numFrames) / 2; + } else { + if (isGoingForward) + numFrames = (frame + numFrames) / 2; + else + frame = (frame + numFrames) / 2; + } + + if (numFrames - frame == 1) { + uint16 lastFramePos = ABS(position - (sequence->getFrameInfo(numFrames)->entityPosition + data->entityPosition)); + uint16 framePosition = ABS(position - (sequence->getFrameInfo(frame)->entityPosition + data->entityPosition)); + + return (framePosition > lastFramePos) ? numFrames : frame; + } + + if (numFrames <= frame) + return currentFrame; + } +} + +void Entities::processFrame(EntityIndex entityIndex, bool keepPreviousFrame, bool dontPlaySound) { + EntityData::EntityCallData *data = getData(entityIndex); + + // Set frame to be drawn again + if (data->frame && keepPreviousFrame) { + if (data->frame->getInfo()->subType != kFrameType3) + data->frame->getInfo()->subType = kFrameType2; + + getScenes()->setFlagDrawSequences(); + } + + // Remove old frame from queue + if (data->frame && !keepPreviousFrame) + getScenes()->removeFromQueue(data->frame); + + // Stop if nothing else to draw + if (data->currentFrame < 0) + return; + + if (data->currentFrame > (int)data->sequence->count()) + return; + + // Get new frame info + FrameInfo *info = data->sequence->getFrameInfo((uint16)data->currentFrame); + + if (data->frame && data->frame->getInfo()->subType != kFrameType3) + if (!info->field_2E || keepPreviousFrame) + getScenes()->setCoordinates(data->frame); + + // Update position + if (info->entityPosition) { + data->entityPosition = info->entityPosition; + if (data->field_4A9) + data->entityPosition = (EntityPosition)(data->entityPosition + getEntityPositionFromCurrentPosition()); + } + + info->location = entityIndex + ABS(getData(entityIndex)->entityPosition - getData(kEntityPlayer)->entityPosition); + + if (info->subType != kFrameType3) { + info->subType = kFrameType1; + + if (!keepPreviousFrame) + info->subType = kFrameTypeNone; + } + + if (info->field_33 & 1) + getSavePoints()->push(kEntityPlayer, entityIndex, kActionExcuseMeCath); + + if (info->field_33 & 2) { + getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (info->field_33 & 16) { + getSavePoints()->push(kEntityPlayer, entityIndex, kAction4); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (data->position) { + updatePositionExit(entityIndex, data->car2, data->position); + data->car2 = kCarNone; + data->position = 0; + } + + if (info->position) { + data->car2 = data->car; + data->position = info->position; + updatePositionEnter(entityIndex, data->car2, data->position); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (info->soundAction && !dontPlaySound) + getSound()->playSoundEvent(entityIndex, info->soundAction, info->field_31); + + // Add the new frame to the queue + SequenceFrame *frame = new SequenceFrame(data->sequence, (uint16)data->currentFrame); + getScenes()->addToQueue(frame); + + // Keep previous frame if needed and store the new frame + if (keepPreviousFrame) + data->frame1 = data->frame; + + data->frame = frame; + + if (!dontPlaySound) + data->field_49B = keepPreviousFrame ? 0 : 1; +} + +void Entities::drawNextSequence(EntityIndex entityIndex) const { + EntityData::EntityCallData *data = getData(entityIndex); + + if (data->direction == kDirectionRight) { + getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (!isDirectionUpOrDown(entityIndex)) + return; + + if (data->sequence2) + return; + + if (!getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingAtDoors)) + return; + + if (getData(kEntityPlayer)->car != data->car) + return; + + if (!data->field_4A9 || isWalkingOppositeToPlayer(entityIndex)) { + if (!data->field_4A9 && isWalkingOppositeToPlayer(entityIndex)) { + data->entityPosition = kPosition_2088; + + if (data->direction != kDirectionUp) + data->entityPosition = kPosition_8512; + + drawSequences(entityIndex, data->direction, true); + } + } else { + data->entityPosition = kPosition_8514; + + if (data->direction != kDirectionUp) + data->entityPosition = kPosition_2086; + + drawSequences(entityIndex, data->direction, true); + } +} + +void Entities::updateEntityPosition(EntityIndex entityIndex) const { + EntityData::EntityCallData *data = getData(entityIndex); + + getScenes()->removeAndRedraw(&data->frame, false); + + SAFE_DELETE(data->frame1); + data->field_49B = 0; + + if (isDirectionUpOrDown(entityIndex) + && (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) || getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) + && data->car == getData(kEntityPlayer)->car) { + + if (isWalkingOppositeToPlayer(entityIndex)) { + data->entityPosition = getData(kEntityPlayer)->entityPosition; + } else if (data->field_4A9) { + data->entityPosition = (data->direction == kDirectionUp) ? kPosition_8514 : kPosition_2086; + } else { + if (isPlayerPosition(kCarGreenSleeping, 1) || isPlayerPosition(kCarGreenSleeping, 40) + || isPlayerPosition(kCarRedSleeping, 1) || isPlayerPosition(kCarRedSleeping, 40)) { + data->entityPosition = (data->direction == kDirectionUp) ? kPosition_2588 : kPosition_8012; + } else { + data->entityPosition = (data->direction == kDirectionUp) ? kPosition_9271 : kPosition_849; + } + } + } + + SAFE_DELETE(data->sequence); + data->sequenceName = ""; + data->field_4A9 = false; + + if (data->directionSwitch) + data->direction = data->directionSwitch; +} + +void Entities::copySequenceData(EntityIndex entityIndex) const { + EntityData::EntityCallData *data = getData(entityIndex); + + if (data->sequence) + data->sequence3 = data->sequence; + + data->sequence = data->sequence2; + data->sequenceName = data->sequenceName2; + data->field_4A9 = data->field_4AA; + + if (data->directionSwitch) + data->direction = data->directionSwitch; + + // Clear sequence 3 + data->sequence2 = NULL; + data->sequenceName2 = ""; + data->field_4AA = false; + data->directionSwitch = kDirectionNone; + + if (data->field_4A9) { + computeCurrentFrame(entityIndex); + + if (data->currentFrame == -1) + data->currentFrame = 0; + } else { + data->currentFrame = data->currentFrame2; + data->currentFrame2 = 0; + + if (data->currentFrame == -1) + data->currentFrame = 0; + } +} + +////////////////////////////////////////////////////////////////////////// +// Drawing +////////////////////////////////////////////////////////////////////////// +void Entities::drawSequenceLeft(EntityIndex index, const char* sequence) const { + drawSequence(index, sequence, kDirectionLeft); +} + +void Entities::drawSequenceRight(EntityIndex index, const char* sequence) const { + drawSequence(index, sequence, kDirectionRight); +} + +void Entities::clearSequences(EntityIndex entityIndex) const { + debugC(8, kLastExpressDebugLogic, "Clear sequences for entity %s", ENTITY_NAME(entityIndex)); + + EntityData::EntityCallData *data = getData(entityIndex); + + getScenes()->removeAndRedraw(&data->frame, false); + getScenes()->removeAndRedraw(&data->frame1, false); + + if (data->sequence2) { + SAFE_DELETE(data->sequence2); + data->sequenceName2 = ""; + data->field_4AA = false; + data->directionSwitch = kDirectionNone; + } + + if (data->sequence) { + SAFE_DELETE(data->sequence); + data->sequenceName = ""; + data->field_4A9 = false; + data->currentFrame = -1; + } + + data->sequenceNamePrefix = ""; + data->direction = kDirectionNone; + data->doProcessEntity = true; +} + +void Entities::drawSequence(EntityIndex index, const char* sequence, EntityDirection direction) const { + debugC(8, kLastExpressDebugLogic, "Drawing sequence %s for entity %s with direction %s", sequence, ENTITY_NAME(index), DIRECTION_NAME(direction)); + + // Copy sequence name + getData(index)->sequenceNamePrefix = sequence; + getData(index)->sequenceNamePrefix.toUppercase(); + getData(index)->sequenceNamePrefix += "-"; + + // Reset fields + getData(index)->field_49B = 0; + getData(index)->currentFrame = 0; + getData(index)->field_4A1 = 0; + + drawSequences(index, direction, true); +} + +void Entities::drawSequences(EntityIndex entityIndex, EntityDirection direction, bool loadSequence) const { + EntityData::EntityCallData *data = getData(entityIndex); + + // Compute value for loading sequence depending on direction + byte field30 = (direction == kDirectionLeft ? entityIndex + 35 : 15); + + data->doProcessEntity = true; + bool field4A9 = data->field_4A9; + + // First case: different car and not going right: cleanup and return + if (data->car != getData(kEntityPlayer)->car && direction != kDirectionRight) { + clearEntitySequenceData(data, direction); + return; + } + + data->directionSwitch = kDirectionNone; + + // Process sequence names + Common::String sequenceName; + Common::String sequenceName1; + Common::String sequenceName2; + Common::String sequenceName3; + + getSequenceName(entityIndex, direction, sequenceName1, sequenceName2); + + // No sequence 1: cleanup and return + if (sequenceName1 == "") { + clearEntitySequenceData(data, direction); + return; + } + + if (sequenceName1 == data->sequenceNameCopy) { + data->direction = direction; + return; + } + + if (direction == kDirectionLeft || direction == kDirectionRight) { + COMPUTE_SEQUENCE_NAME(sequenceName, sequenceName1); + + if (sequenceName3 != "") + COMPUTE_SEQUENCE_NAME(sequenceName3, sequenceName2); + } + + if (!data->frame) { + data->direction = direction; + + if (sequenceName1 == data->sequenceName) { + if (sequenceName2 == "") + return; + + loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); + return; + } + + SAFE_DELETE(data->sequence); + + if (sequenceName1 != data->sequenceName2) { + + if (loadSequence) { + + if (data->car == getData(kEntityPlayer)->car) + data->sequence = loadSequence1(sequenceName1, field30); + + if (data->sequence) { + data->sequenceName = sequenceName1; + data->sequenceNameCopy = ""; + } else { + if (sequenceName != "") + data->sequence = loadSequence1(sequenceName, field30); + + data->sequenceName = (data->sequence ? sequenceName : ""); + data->sequenceNameCopy = (data->sequence ? "" : sequenceName1); + } + } else { + data->sequenceName = sequenceName1; + } + + if (sequenceName2 != "") { + loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); + return; + } + + if (!data->sequence2) { + if (sequenceName2 == "") + return; + + loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); + return; + } + + SAFE_DELETE(data->sequence2); + } else { + data->sequence = data->sequence2; + data->sequenceName = data->sequenceName2; + data->sequence2 = NULL; + } + + data->sequenceName2 = ""; + + if (sequenceName2 == "") + return; + + loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); + return; + } + + if (data->sequenceName != sequenceName1) { + + if (data->sequenceName2 != sequenceName1) { + SAFE_DELETE(data->sequence2); + TRY_LOAD_SEQUENCE(data->sequence2, data->sequenceName2, sequenceName1, sequenceName); + } + + data->field_4AA = data->field_4A9; + if ((direction != kDirectionUp && direction != kDirectionDown) || data->field_4AA || !data->sequence2) { + data->currentFrame2 = 0; + } else { + data->currentFrame2 = getCurrentFrame(entityIndex, data->sequence2, kPositionNone, false); + + if (data->currentFrame2 == -1) { + clearSequences(entityIndex); + return; + } + } + + data->field_4A9 = field4A9; + data->field_49B = data->frame->getInfo()->field_30; + data->currentFrame = (int16)(data->sequence->count() - 1); + data->direction = kDirectionSwitch; + data->directionSwitch = direction; + } else { + SAFE_DELETE(data->sequence2); + + data->sequence2 = loadSequence1(data->sequence->getName(), data->sequence->getField30()); + + data->sequenceName2 = data->sequenceName; + data->field_4AA = data->field_4A9; + data->field_49B = data->frame->getInfo()->field_30; + data->currentFrame = (int16)(data->sequence->count() - 1); + data->direction = kDirectionSwitch; + data->directionSwitch = direction; + + if ((direction != kDirectionUp && direction != kDirectionDown) || data->field_4AA || !data->sequence2) { + data->currentFrame2 = 0; + } else { + data->currentFrame2 = getCurrentFrame(entityIndex, data->sequence2, kPositionNone, false); + + if (data->currentFrame2 == -1) + clearSequences(entityIndex); + } + } +} + +void Entities::loadSequence2(EntityIndex entityIndex, Common::String sequenceName, Common::String sequenceName2, byte field30, bool reloadSequence) const { + EntityData::EntityCallData *data = getData(entityIndex); + + if (data->sequenceName2 == sequenceName) + return; + + if (data->sequence2) + SAFE_DELETE(data->sequence2); + + if (reloadSequence) { + TRY_LOAD_SEQUENCE(data->sequence2, data->sequenceName2, sequenceName, sequenceName2); + } else { + data->sequenceName2 = sequenceName; + } +} + +void Entities::getSequenceName(EntityIndex index, EntityDirection direction, Common::String &sequence1, Common::String &sequence2) const { + EntityData::EntityCallData *data = getData(index); + Position position = getScenes()->get(getState()->scene)->position; + + // reset fields + data->field_4A9 = false; + data->field_4AA = false; + + switch (direction) { + default: + break; + + case kDirectionUp: + switch (position) { + default: + break; + + case 1: + if (data->entityPosition < kPosition_2587) + sequence1 = Common::String::printf("%02d%01d-01u.seq", index, data->clothes); + break; + + case 2: + case 3: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + if (data->entityPosition >= kPosition_9270) + break; + + if (data->entityPosition >= kPosition_8513) { + sequence1 = Common::String::printf("%02d%01d-%02deu.seq", index, data->clothes, position); + } else { + sequence1 = Common::String::printf("%02d%01d-03u.seq", index, data->clothes); + sequence2 = Common::String::printf("%02d%01d-%02deu.seq", index, data->clothes, position); + data->field_4A9 = true; + } + break; + + case 18: + if (data->entityPosition < kPosition_9270) + sequence1 = Common::String::printf("%02d%01d-18u.seq", index, data->clothes); + break; + + case 22: + if (getData(kEntityPlayer)->entityPosition > data->entityPosition) + sequence1 = Common::String::printf("%02d%01d-22u.seq", index, data->clothes); + break; + + case 23: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + case 38: + case 39: + if (getData(kEntityPlayer)->entityPosition <= data->entityPosition) + break; + + if (data->entityPosition >= kPosition_2087) { + sequence1 = Common::String::printf("%02d%01d-38u.seq", index, data->clothes); + data->field_4A9 = true; + } else { + sequence1 = Common::String::printf("%02d%01d-%02deu.seq", index, data->clothes, position); + sequence2 = Common::String::printf("%02d%01d-38u.seq", index, data->clothes); + data->field_4AA = true; + } + break; + + case 40: + if (getData(kEntityPlayer)->entityPosition > data->entityPosition) + sequence1 = Common::String::printf("%02d%01d-40u.seq", index, data->clothes); + break; + } + break; + + case kDirectionDown: + switch (position) { + default: + break; + + case 1: + if (getData(kEntityPlayer)->entityPosition < data->entityPosition) + sequence1 = Common::String::printf("%02d%01d-01d.seq", index, data->clothes); + break; + + case 2: + case 3: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + if (getData(kEntityPlayer)->entityPosition >= data->entityPosition) + break; + + if (data->entityPosition <= kPosition_8513) { + sequence1 = Common::String::printf("%02d%01d-03d.seq", index, data->clothes); + data->field_4A9 = true; + } else { + sequence1 = Common::String::printf("%02d%01d-%02ded.seq", index, data->clothes, position); + sequence2 = Common::String::printf("%02d%01d-03d.seq", index, data->clothes); + data->field_4AA = true; + } + break; + + case 18: + if (getData(kEntityPlayer)->entityPosition < data->entityPosition) + sequence1 = Common::String::printf("%02d%01d-18d.seq", index, data->clothes); + break; + + case 22: + if (data->entityPosition > kPosition_850) + sequence1 = Common::String::printf("%02d%01d-22d.seq", index, data->clothes); + break; + + case 23: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + case 38: + case 39: + if (data->entityPosition <= kPosition_850) + break; + + if (data->entityPosition <= kPosition_2087) { + sequence1 = Common::String::printf("%02d%01d-%02ded.seq", index, data->clothes, position); + } else { + sequence1 = Common::String::printf("%02d%01d-38d.seq", index, data->clothes); + sequence2 = Common::String::printf("%02d%01d-%02ded.seq", index, data->clothes, position); + data->field_4A9 = true; + } + break; + + case 40: + if (getData(kEntityPlayer)->entityPosition > kPosition_8013) + sequence1 = Common::String::printf("%02d%01d-40d.seq", index, data->clothes); + break; + } + break; + + // First part of sequence is already set + case kDirectionLeft: + case kDirectionRight: + sequence1 = Common::String::printf("%s%02d.seq", data->sequenceNamePrefix.c_str(), position); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +/// Compartments +////////////////////////////////////////////////////////////////////////// +void Entities::enterCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1) { + if (entity > kEntityChapters) + return; + + switch (compartment) { + default: + // Return here so we do not update the compartments + return; + + case kObjectCompartment1: + updatePositionsEnter(entity, kCarGreenSleeping, 41, 51, 17, 38); + break; + + case kObjectCompartment2: + updatePositionsEnter(entity, kCarGreenSleeping, 42, 52, 15, 36); + break; + + case kObjectCompartment3: + updatePositionsEnter(entity, kCarGreenSleeping, 43, 53, 13, 34); + break; + + case kObjectCompartment4: + updatePositionsEnter(entity, kCarGreenSleeping, 44, 54, 11, 32); + break; + + case kObjectCompartment5: + updatePositionsEnter(entity, kCarGreenSleeping, 45, 55, 9, 30); + break; + + case kObjectCompartment6: + updatePositionsEnter(entity, kCarGreenSleeping, 46, 56, 7, 28); + break; + + case kObjectCompartment7: + updatePositionsEnter(entity, kCarGreenSleeping, 47, 57, 5, 26); + break; + + case kObjectCompartment8: + updatePositionsEnter(entity, kCarGreenSleeping, 48, 58, 3, 25); + break; + + case kObjectCompartmentA: + updatePositionsEnter(entity, kCarRedSleeping, 41, 51, 17, 38); + break; + + case kObjectCompartmentB: + updatePositionsEnter(entity, kCarRedSleeping, 42, 52, 15, 36); + break; + + case kObjectCompartmentC: + updatePositionsEnter(entity, kCarRedSleeping, 43, 53, 13, 34); + break; + + case kObjectCompartmentD: + updatePositionsEnter(entity, kCarRedSleeping, 44, 54, 11, 32); + break; + + case kObjectCompartmentE: + updatePositionsEnter(entity, kCarRedSleeping, 45, 55, 9, 30); + break; + + case kObjectCompartmentF: + updatePositionsEnter(entity, kCarRedSleeping, 46, 56, 7, 28); + break; + + case kObjectCompartmentG: + updatePositionsEnter(entity, kCarRedSleeping, 47, 57, 5, 26); + break; + + case kObjectCompartmentH: + updatePositionsEnter(entity, kCarRedSleeping, 48, 58, 3, 25); + break; + } + + // Update compartments + int index = (compartment < 32 ? compartment - 1 : compartment - 24); + if (index >= 16) + error("Entities::exitCompartment: invalid compartment index!"); + + if (useCompartment1) + _compartments1[index] |= STORE_VALUE(entity); + else + _compartments[index] |= STORE_VALUE(entity); +} + +void Entities::exitCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1) { + if (entity > kEntityChapters) + return; + + // TODO factorize in one line + switch (compartment) { + default: + // Return here so we do not update the compartments + return; + + case kObjectCompartment1: + updatePositionsExit(entity, kCarGreenSleeping, 41, 51); + break; + + case kObjectCompartment2: + updatePositionsExit(entity, kCarGreenSleeping, 42, 52); + break; + + case kObjectCompartment3: + updatePositionsExit(entity, kCarGreenSleeping, 43, 53); + break; + + case kObjectCompartment4: + updatePositionsExit(entity, kCarGreenSleeping, 44, 54); + break; + + case kObjectCompartment5: + updatePositionsExit(entity, kCarGreenSleeping, 45, 55); + break; + + case kObjectCompartment6: + updatePositionsExit(entity, kCarGreenSleeping, 46, 56); + break; + + case kObjectCompartment7: + updatePositionsExit(entity, kCarGreenSleeping, 47, 57); + break; + + case kObjectCompartment8: + updatePositionsExit(entity, kCarGreenSleeping, 48, 58); + break; + + case kObjectCompartmentA: + updatePositionsExit(entity, kCarRedSleeping, 41, 51); + break; + + case kObjectCompartmentB: + updatePositionsExit(entity, kCarRedSleeping, 42, 52); + break; + + case kObjectCompartmentC: + updatePositionsExit(entity, kCarRedSleeping, 43, 53); + break; + + case kObjectCompartmentD: + updatePositionsExit(entity, kCarRedSleeping, 44, 54); + break; + + case kObjectCompartmentE: + updatePositionsExit(entity, kCarRedSleeping, 45, 55); + break; + + case kObjectCompartmentF: + updatePositionsExit(entity, kCarRedSleeping, 46, 56); + break; + + case kObjectCompartmentG: + updatePositionsExit(entity, kCarRedSleeping, 47, 57); + break; + + case kObjectCompartmentH: + updatePositionsExit(entity, kCarRedSleeping, 48, 58); + break; + } + + // Update compartments + int index = (compartment < 32 ? compartment - 1 : compartment - 24); + if (index >= 16) + error("Entities::exitCompartment: invalid compartment index!"); + + if (useCompartment1) + _compartments1[index] &= ~STORE_VALUE(entity); + else + _compartments[index] &= ~STORE_VALUE(entity); +} + +void Entities::updatePositionEnter(EntityIndex entity, CarIndex car, Position position) { + if (entity == kEntity39) + entity = kEntityPlayer; + + if (entity > kEntityChapters) + return; + + _positions[100 * car + position] |= STORE_VALUE(entity); + + if (isPlayerPosition(car, position) || (car == kCarRestaurant && position == 57 && isPlayerPosition(kCarRestaurant, 50))) { + getSound()->excuseMe(entity); + getScenes()->loadScene(getScenes()->processIndex(getState()->scene)); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + } else { + getLogic()->updateCursor(); + } +} + +void Entities::updatePositionExit(EntityIndex entity, CarIndex car, Position position) { + if (entity == kEntity39) + entity = kEntityPlayer; + + if (entity > kEntityChapters) + return; + + _positions[100 * car + position] &= ~STORE_VALUE(entity); + + getLogic()->updateCursor(); +} + +void Entities::updatePositionsEnter(EntityIndex entity, CarIndex car, Position position1, Position position2, Position position3, Position position4) { + if (entity == kEntity39) + entity = kEntityPlayer; + + if (entity > kEntityChapters) + return; + + _positions[100 * car + position1] |= STORE_VALUE(entity); + _positions[100 * car + position2] |= STORE_VALUE(entity); + + // FIXME: also checking two DWORD values that do not seem to updated anywhere... + if (isPlayerPosition(car, position1) || isPlayerPosition(car, position2) || isPlayerPosition(car, position3) || isPlayerPosition(car, position4)) { + getSound()->excuseMe(entity); + getScenes()->loadScene(getScenes()->processIndex(getState()->scene)); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + } else { + getLogic()->updateCursor(); + } +} + +void Entities::updatePositionsExit(EntityIndex entity, CarIndex car, Position position1, Position position2) { + if (entity == kEntity39) + entity = kEntityPlayer; + + if (entity > kEntityChapters) + return; + + _positions[100 * car + position1] &= ~STORE_VALUE(entity); + _positions[100 * car + position2] &= ~STORE_VALUE(entity); + + getLogic()->updateCursor(); +} + +void Entities::loadSceneFromEntityPosition(CarIndex car, EntityPosition entityPosition, bool alternate) const { + + // Determine position + Position position = (alternate ? 1 : 40); + do { + if (entityPosition > entityPositions[position]) { + if (alternate) + break; + + // For default value, we ignore position 24 + if (position != 24) + break; + } + + alternate ? ++position : --position; + + } while (alternate ? position <= 18 : position >= 22); + + // For position outside bounds, use minimal value + if ((alternate && position > 18) || (alternate && position < 22)) { + getScenes()->loadSceneFromPosition(car, alternate ? 18 : 22); + return; + } + + // Load scene from position + switch (position) { + default: + getScenes()->loadSceneFromPosition(car, (Position)(position + (alternate ? - 1 : 1))); + break; + + // Alternate + case 1: + if (alternate) getScenes()->loadSceneFromPosition(car, 1); + break; + + case 5: + if (alternate) getScenes()->loadSceneFromPosition(car, 3); + break; + + // Default + case 23: + if (!alternate) getScenes()->loadSceneFromPosition(car, 25); + break; + + case 40: + if (!alternate) getScenes()->loadSceneFromPosition(car, 40); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Checks +////////////////////////////////////////////////////////////////////////// +bool Entities::hasValidFrame(EntityIndex entity) const { + return (getData(entity)->frame && (getData(entity)->frame->getInfo()->subType != kFrameType3)); +} + +bool Entities::compare(EntityIndex entity1, EntityIndex entity2) { + EntityData::EntityCallData *data1 = getData(entity1); + EntityData::EntityCallData *data2 = getData(entity2); + + if (data2->car != data1->car + || data1->car < kCarGreenSleeping + || data1->car > kCarRedSleeping) + return false; + + EntityPosition position1 = (data1->entityPosition >= data2->entityPosition) ? data1->entityPosition : data2->entityPosition; + EntityPosition position2 = (data1->entityPosition >= data2->entityPosition) ? data2->entityPosition : data1->entityPosition; + + // Compute position + int index1 = 7; + do { + if (objectsPosition[index1] >= position2) + break; + + --index1; + } while (index1 > -1); + + int index2 = 0; + do { + if (objectsPosition[index2] <= position2) + break; + + ++index2; + } while (index2 < 8); + + if (index1 > -1 && index2 < 8 && index2 <= index1) { + while (index2 <= index1) { + if (getCompartments(index2 + (data1->car == kCarGreenSleeping ? 0 : 8))) + return true; + + if (getCompartments1(index2 + (data1->car == kCarGreenSleeping ? 0 : 8))) + return true; + + ++index2; + } + } + + for (EntityIndex entity = kEntityAnna; entity <= kEntity39; entity = (EntityIndex)(entity + 1)) { + + if (entity1 == entity || entity2 == entity) + continue; + + if (!isDirectionUpOrDown(entity)) + continue; + + if (data1->car == getEntityData(entity)->car + && getEntityData(entity)->entityPosition > position2 + && getEntityData(entity)->entityPosition < position1) + return true; + } + + return false; +} + +bool Entities::updateEntity(EntityIndex entity, CarIndex car, EntityPosition position) { + EntityData::EntityCallData *data = getData(entity); + EntityDirection direction = kDirectionNone; + int delta = 0; + bool flag1 = false; + bool flag2 = false; + bool flag3 = false; + + if (position == kPosition_2000 + && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) + && !isPlayerPosition(kCarGreenSleeping, 1) + && !isPlayerPosition(kCarRedSleeping, 2)) + position = kPosition_1500; + + if (data->direction != kDirectionUp && data->direction != kDirectionDown) + data->field_497 = 0; + + if (data->field_497) { + data->field_497--; + + if (data->field_497 == 128) + data->field_497 = 0; + + if ((data->field_497 & 127) != 8) { + data->field_49B = 0; + return false; + } + + flag1 = true; + + if (data->field_497 & 128) + flag2 = true; + } + + if (data->car != car) + goto label_process_entity; + + // Calculate delta + delta = ABS(data->entityPosition - position); + if (delta < 100 || (position > kPosition_850 && position < kPosition_9270 && delta < 300)) + flag3 = true; + + if (!flag3) { + if ((getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) && data->direction == kDirectionUp) + || (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) && data->direction == kDirectionDown)) { + if (!checkPosition(position) && isDistanceBetweenEntities(entity, kEntityPlayer, 250)) + flag3 = true; + } + + if (!flag3) + goto label_process_entity; + } + + if (getEntities()->hasValidFrame(entity) + && getEntities()->isWalkingOppositeToPlayer(entity) + && !getEntities()->checkPosition(position)) { + flag3 = false; + position = (EntityPosition)(getData(kEntityPlayer)->entityPosition + 250 * (data->direction == kDirectionUp ? 1 : -1)); + } + + if (!flag3) { +label_process_entity: + + // Calculate direction + if (data->car < car) + direction = kDirectionUp; + else if (data->car > car) + direction = kDirectionDown; + else // same car + direction = (data->entityPosition < position) ? kDirectionUp : kDirectionDown; + + if (data->direction == direction) { + if (!flag1) { + + if (checkDistanceFromPosition(entity, kPosition_1500, 750) && entity != kEntityFrancois) { + + if (data->entity != kEntityPlayer) { + if (data->direction != kDirectionUp || (position <= kPosition_2000 && data->car == car)) { + if (data->direction == kDirectionDown && (position < kPosition_1500 || data->car != car)) { + if (data->entityPosition > kPosition_1500 && (data->car == kCarGreenSleeping || data->car == kCarRedSleeping)) { + data->entity = (data->car == kCarGreenSleeping) ? kEntityMertens : kEntityCoudert; + getSavePoints()->push(entity, data->entity, kAction11); + } + } + } else { + if (data->entityPosition < kPosition_1500 && (data->car == kCarGreenSleeping || data->car == kCarRedSleeping)) { + data->entity = (data->car == kCarGreenSleeping) ? kEntityMertens : kEntityCoudert; + getSavePoints()->push(entity, data->entity, kAction11, 1); + } + } + } + + } else if (data->entity) { + getSavePoints()->push(entity, data->entity, kAction16); + data->entity = kEntityPlayer; + } + + if (hasValidFrame(entity)) { + + if (!data->field_4A9) + return false; + + int compartmentIndex = 0; + if (data->car == kCarGreenSleeping) + compartmentIndex = 0; + else if (data->car == kCarRedSleeping) + compartmentIndex = 8; + + for (int i = 0; i < 8; i++) { + if (getCompartments(compartmentIndex) || getCompartments1(compartmentIndex)) { + if (checkDistanceFromPosition(entity, objectsPosition[i], 750)) { + if (checkPosition(objectsPosition[i])) { + + if ((data->direction == kDirectionUp && data->entityPosition < objectsPosition[i] && (data->car != car || position > objectsPosition[i])) + || (data->direction == kDirectionDown && data->entityPosition > objectsPosition[i] && (data->car != car || position < objectsPosition[i]))) { + + getSound()->excuseMe(entity, (EntityIndex)(State::getPowerOfTwo((uint32)(getCompartments(compartmentIndex) ? getCompartments(compartmentIndex) : getCompartments1(compartmentIndex))))); + + data->field_497 = 144; + + break; + } + } + } + } + + compartmentIndex++; + } + + for (EntityIndex entityIndex = kEntityAnna; entityIndex <= kEntity39; entityIndex = (EntityIndex)(entityIndex + 1)) { + if (getSavePoints()->getCallback(entityIndex) + && hasValidFrame(entityIndex) + && entityIndex != entity + && isDistanceBetweenEntities(entity, entityIndex, 750) + && isDirectionUpOrDown(entityIndex) + && (entity != kEntityRebecca || entityIndex != kEntitySophie) + && (entity != kEntitySophie || entityIndex != kEntityRebecca) + && (entity != kEntityIvo || entityIndex != kEntitySalko) + && (entity != kEntitySalko || entityIndex != kEntityIvo) + && (entity != kEntityMilos || entityIndex != kEntityVesna) + && (entity != kEntityVesna || entityIndex != kEntityMilos)) { + + EntityData::EntityCallData *data2 = getData(entityIndex); + + if (data->direction != data2->direction) { + + if ((data->direction != kDirectionUp || data2->entityPosition <= data->entityPosition) + && (data->direction != kDirectionDown || data2->entityPosition >= data->entityPosition)) + continue; + + data->field_49B = 0; + data2->field_49B = 0; + + data->field_497 = 16; + data2->field_497 = 16; + + getSound()->excuseMe(entity, entityIndex); + getSound()->excuseMe(entityIndex, entity); + + if (entityIndex > entity) + ++data2->field_497; + + break; + } + + if (ABS(data2->entityPosition - getData(kEntityPlayer)->entityPosition) < ABS(data->entityPosition - getData(kEntityPlayer)->entityPosition)) { + + if (!isWalkingOppositeToPlayer(entity)) { + + if (direction == kDirectionUp) { + if (data->entityPosition < kPosition_9500) + data->entityPosition = (EntityPosition)(data->entityPosition + 500); + } else { + if (data->entityPosition > kPosition_500) + data->entityPosition = (EntityPosition)(data->entityPosition - 500); + } + + drawSequences(entity, direction, true); + + return false; + } + data->field_49B = 0; + + break; + } + } + } + + return false; + } + + if (data->direction == kDirectionUp) { + if (data->entityPosition + data->field_4A3 < 10000) + data->entityPosition = (EntityPosition)(data->entityPosition + data->field_4A3); + } else { + if (data->entityPosition > data->field_4A3) + data->entityPosition = (EntityPosition)(data->entityPosition - data->field_4A3); + } + + if (data->entityPosition <= kPosition_9270 || data->direction != kDirectionUp) { + if (data->entityPosition < kPosition_850 && data->direction == kDirectionDown) { + if (changeCar(data, entity, car, position, false, kPosition_9269, kCarKronos)) + return true; + } + } else { + if (changeCar(data, entity, car, position, true, kPosition_851, kCarGreenSleeping)) + return true; + } + + if (getData(kEntityPlayer)->car == data->car && data->location == kLocationOutsideCompartment) { + if (data->direction == kDirectionUp) { + + if (getData(kEntityPlayer)->entityPosition > data->entityPosition + && getData(kEntityPlayer)->entityPosition - data->entityPosition >= 500 + && data->field_4A3 + 500 > getData(kEntityPlayer)->entityPosition - data->entityPosition) { + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) || getScenes()->checkCurrentPosition(false)) { + getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMe); + + if (getScenes()->checkCurrentPosition(false)) + getScenes()->loadSceneFromObject((ObjectIndex)getScenes()->get(getState()->scene)->param1, true); + + } else if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) { + getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMeCath); + } + } + } else { + if (getData(kEntityPlayer)->entityPosition < data->entityPosition + && data->entityPosition - getData(kEntityPlayer)->entityPosition >= 500 + && data->field_4A3 + 500 > data->entityPosition - getData(kEntityPlayer)->entityPosition) { + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)) { + getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMeCath); + } else if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) || getScenes()->checkCurrentPosition(false)){ + getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMe); + + if (getScenes()->checkCurrentPosition(false)) + getScenes()->loadSceneFromObject((ObjectIndex)getScenes()->get(getState()->scene)->param1); + } + } + } + return false; + } + } + } else if (!flag1) { + drawSequences(entity, direction, true); + return false; + } + + ////////////////////////////////////////////////////////////////////////// + // Adjust positions + + // Direction Up + if (direction == kDirectionUp) { + if (data->entityPosition < (flag2 ? kPosition_8800 : kPosition_9250)) + data->entityPosition = (EntityPosition)(data->entityPosition + (flag2 ? kPosition_1200 : kPosition_750)); + + if (data->car == car && data->entityPosition >= position) { + data->entityPosition = position; + data->direction = kDirectionNone; + data->entity = kEntityPlayer; + return true; + } + + drawSequences(entity, direction, true); + return false; + } + + // Direction Down + if (data->entityPosition > (flag2 ? kPosition_1200 : kPosition_750)) + data->entityPosition = (EntityPosition)(data->entityPosition - (flag2 ? kPosition_1200 : kPosition_750)); + + if (data->car == car && data->entityPosition <= position) { + data->entityPosition = position; + data->direction = kDirectionNone; + data->entity = kEntityPlayer; + return true; + } + + drawSequences(entity, direction, true); + return false; + } + + data->entityPosition = position; + if (data->direction == kDirectionUp || data->direction == kDirectionDown) + data->direction = kDirectionNone; + data->entity = kEntityPlayer; + + return true; +} + +bool Entities::changeCar(EntityData::EntityCallData * data, EntityIndex entity, CarIndex car, EntityPosition position, bool increment, EntityPosition newPosition, CarIndex newCar) const { + if (getData(kEntityPlayer)->car == data->car) { + getSound()->playSoundEvent(entity, 36); + getSound()->playSoundEvent(entity, 37, 30); + } + + data->car = (CarIndex)(increment ? data->car + 1 : data->car - 1); + data->entityPosition = newPosition; + + if (data->car == newCar) { + if (isInGreenCarEntrance(kEntityPlayer)) { + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->excuseMe(entity, kEntityPlayer, SoundManager::kFlagDefault); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 1); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + getSound()->playSoundEvent(kEntityPlayer, 15); + } + } + + if ((increment ? data->car > car : data->car < car) || (data->car == car && (increment ? data->entityPosition >= position : data->entityPosition <= position))) { + data->car = car; + data->entityPosition = position; + data->direction = kDirectionNone; + data->entity = kEntityPlayer; + + return true; + } + + if (data->car == newCar) { + if (isInKronosCarEntrance(kEntityPlayer)) { + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->excuseMe(entity, kEntityPlayer, SoundManager::kFlagDefault); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 62); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + getSound()->playSoundEvent(kEntityPlayer, 15); + } + } + + if (data->car == getData(kEntityPlayer)->car) { + getSound()->playSoundEvent(entity, 36); + getSound()->playSoundEvent(entity, 37, 30); + } + + return false; +} + +////////////////////////////////////////////////////////////////////////// +// CHECKS +////////////////////////////////////////////////////////////////////////// +bool Entities::isInsideCompartment(EntityIndex entity, CarIndex car, EntityPosition position) const { + return (getData(entity)->entityPosition == position + && getData(entity)->location == kLocationInsideCompartment + && getData(entity)->car == car); +} + +bool Entities::checkFields2(ObjectIndex object) const { + + EntityPosition position = kPositionNone; + CarIndex car = kCarNone; + + switch (object) { + default: + return false; + + case kObjectCompartment1: + case kObjectCompartment2: + case kObjectCompartment3: + case kObjectCompartment4: + case kObjectCompartment5: + case kObjectCompartment6: + case kObjectCompartment7: + case kObjectCompartment8: + position = objectsPosition[object - 1]; + car = kCarGreenSleeping; + if (isInsideCompartment(kEntityPlayer, car, position)) + return false; + break; + + case kObjectHandleBathroom: + case kObjectHandleInsideBathroom: + case kObjectKitchen: + case kObject20: + case kObject21: + case kObject22: + position = objectsPosition[object-17]; + car = kCarGreenSleeping; + break; + + case kObjectCompartmentA: + case kObjectCompartmentB: + case kObjectCompartmentC: + case kObjectCompartmentD: + case kObjectCompartmentE: + case kObjectCompartmentF: + case kObjectCompartmentG: + case kObjectCompartmentH: + position = objectsPosition[object-32]; + car = kCarRedSleeping; + if (isInsideCompartment(kEntityPlayer, car, position)) + return false; + break; + + case kObject48: + case kObject49: + case kObject50: + case kObject51: + case kObject52: + case kObject53: + position = objectsPosition[object-48]; + car = kCarRedSleeping; + break; + + } + + uint index = 1; + while (!isInsideCompartment((EntityIndex)index, car, position) || index == kEntityVassili) { + index++; + if (index >= 40) + return false; + } + + return true; +} + +bool Entities::isInsideCompartments(EntityIndex entity) const { + return (getData(entity)->car == kCarGreenSleeping + || getData(entity)->car == kCarRedSleeping) + && getData(entity)->location == kLocationInsideCompartment; +} + +bool Entities::isPlayerPosition(CarIndex car, Position position) const { + return getData(kEntityPlayer)->car == car && getScenes()->get(getState()->scene)->position == position; +} + +bool Entities::isInsideTrainCar(EntityIndex entity, CarIndex car) const { + return getData(entity)->car == car && getData(entity)->location <= kLocationInsideCompartment; +} + +bool Entities::isInGreenCarEntrance(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarGreenSleeping) && getData(entity)->entityPosition < kPosition_850; +} + +bool Entities::isPlayerInCar(CarIndex car) const { + return isInsideTrainCar(kEntityPlayer, car) && getData(kEntityPlayer)->location && !isInGreenCarEntrance(kEntityPlayer); +} + +bool Entities::isDirectionUpOrDown(EntityIndex entity) const { + return getData(entity)->direction == kDirectionUp || getData(entity)->direction == kDirectionDown; +} + +bool Entities::isDistanceBetweenEntities(EntityIndex entity1, EntityIndex entity2, uint distance) const { + return getData(entity1)->car == getData(entity2)->car + && (uint)ABS(getData(entity1)->entityPosition - getData(entity2)->entityPosition) <= distance + && (getData(entity1)->location != kLocationOutsideTrain || getData(entity2)->location != kLocationOutsideTrain); +} + +bool Entities::checkFields10(EntityIndex entity) const { + return getData(entity)->location <= kLocationOutsideTrain; +} + +bool Entities::isSomebodyInsideRestaurantOrSalon() const { + for (uint i = 1; i < _entities.size(); i++) { + EntityIndex index = (EntityIndex)i; + + if (getData(index)->location == kLocationOutsideCompartment && (isInSalon(index) || isInRestaurant(index))) + return false; + } + + return true; +} + +bool Entities::isInSalon(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarRestaurant) + && getData(entity)->entityPosition >= kPosition_1540 + && getData(entity)->entityPosition <= kPosition_3650; +} + +bool Entities::isInRestaurant(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarRestaurant) + && getData(entity)->entityPosition >= kPosition_3650 + && getData(entity)->entityPosition <= kPosition_5800; +} + +bool Entities::isInKronosSalon(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarKronos) + && getData(entity)->entityPosition >= kPosition_5500 + && getData(entity)->entityPosition <= kPosition_7500; +} + +bool Entities::isOutsideAlexeiWindow() const { + return (getData(kEntityPlayer)->entityPosition == kPosition_7500 || getData(kEntityPlayer)->entityPosition == kPosition_8200) + && getData(kEntityPlayer)->location == kLocationOutsideTrain + && getData(kEntityPlayer)->car == kCarGreenSleeping; +} + +bool Entities::isOutsideAnnaWindow() const { + return (getData(kEntityPlayer)->entityPosition == kPosition_4070 || getData(kEntityPlayer)->entityPosition == kPosition_4840) + && getData(kEntityPlayer)->location == kLocationOutsideTrain + && getData(kEntityPlayer)->car == kCarRedSleeping; +} + +bool Entities::isInKitchen(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarRestaurant) && getData(entity)->entityPosition > kPosition_5800; +} + +bool Entities::isNobodyInCompartment(CarIndex car, EntityPosition position) const { + for (uint i = 1; i < _entities.size(); i++) { + if (isInsideCompartment((EntityIndex)i, car, position)) + return false; + } + return true; +} + +bool Entities::checkFields19(EntityIndex entity, CarIndex car, EntityPosition position) const { + + if (getData(entity)->car != car || getData(entity)->location != kLocationInsideCompartment) + return false; + + EntityPosition entityPosition = getData(entity)->entityPosition; + + // Test values + if (position == kPosition_4455) { + if (entityPosition == kPosition_4070 || entityPosition == kPosition_4455 || entityPosition == kPosition_4840) + return true; + + return false; + } + + if (position == kPosition_6130) { + if (entityPosition == kPosition_5790 || entityPosition == kPosition_6130 || entityPosition == kPosition_6470) + return true; + + return false; + } + + if (position != kPosition_7850 + || (entityPosition != kPosition_7500 && entityPosition != kPosition_7850 && entityPosition != kPosition_8200)) + return false; + + return true; +} + +bool Entities::isInBaggageCarEntrance(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarBaggage) + && getData(entity)->entityPosition >= kPosition_4500 + && getData(entity)->entityPosition <= kPosition_5500; +} + +bool Entities::isInBaggageCar(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarBaggage) && getData(entity)->entityPosition < kPosition_4500; +} + +bool Entities::isInKronosSanctum(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarKronos) + && getData(entity)->entityPosition >= kPosition_3500 + && getData(entity)->entityPosition <= kPosition_5500; +} + +bool Entities::isInKronosCarEntrance(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarKronos) && getData(entity)->entityPosition > kPosition_7900; +} + +bool Entities::checkDistanceFromPosition(EntityIndex entity, EntityPosition position, int distance) const { + return distance >= ABS(getData(entity)->entityPosition - position); +} + +bool Entities::isWalkingOppositeToPlayer(EntityIndex entity) const { + if (getData(entity)->direction == kDirectionUp && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) + return true; + + return (getData(entity)->direction == kDirectionDown && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)); +} + +bool Entities::isFemale(EntityIndex entity) { + return (entity == kEntityAnna + || entity == kEntityTatiana + || entity == kEntityVesna + || entity == kEntityKahina + || entity == kEntityMmeBoutarel + || entity == kEntityRebecca + || entity == kEntitySophie + || entity == kEntityYasmin + || entity == kEntityHadija + || entity == kEntityAlouan); +} + +bool Entities::isMarried(EntityIndex entity) { + return (entity != kEntityTatiana + && entity != kEntityRebecca + && entity != kEntitySophie); +} + +bool Entities::checkPosition(EntityPosition position) const { + Position position1 = 0; + Position position2 = 0; + + switch (position) { + default: + return true; + + case kPosition_1500: + position1 = 1; + position2 = 23; + break; + + case kPosition_2740: + position1 = 3; + position2 = 25; + break; + + case kPosition_3050: + position1 = 5; + position2 = 26; + break; + + case kPosition_4070: + position1 = 7; + position2 = 28; + break; + + case kPosition_4840: + position1 = 9; + position2 = 30; + break; + + case kPosition_5790: + position1 = 11; + position2 = 32; + break; + + case kPosition_6470: + position1 = 13; + position2 = 34; + break; + + case kPosition_7500: + position1 = 15; + position2 = 36; + break; + + case kPosition_8200: + position1 = 17; + position2 = 38; + break; + } + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) && entityPositions[position1] >= getEntityData(kEntityPlayer)->entityPosition) + return true; + else + return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) && entityPositions[position2] <= getEntityData(kEntityPlayer)->entityPosition); +} + +bool Entities::checkSequenceFromPosition(EntityIndex entity) const { + FrameInfo *info = getEntityData(entity)->sequence->getFrameInfo((uint16)getEntityData(entity)->currentFrame); + + if (getEntityData(entity)->direction == kDirectionUp) + return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) + && info->entityPosition + getEntityPositionFromCurrentPosition() > kPosition_8513); + + if (getEntityData(entity)->direction == kDirectionDown) + return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) + && info->entityPosition + getEntityPositionFromCurrentPosition() < kPosition_2087); + + return false; +} + +EntityPosition Entities::getEntityPositionFromCurrentPosition() const { + // Get the scene position first + Position position = getScenes()->get(getState()->scene)->position; + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)) + return (EntityPosition)(entityPositions[position] - kPosition_1430); + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) + return (EntityPosition)(entityPositions[position] - kPosition_9020); + + return kPositionNone; +} + +void Entities::clearEntitySequenceData(EntityData::EntityCallData *data, EntityDirection direction) const { + getScenes()->removeAndRedraw(&data->frame, false); + getScenes()->removeAndRedraw(&data->frame1, false); + + SAFE_DELETE(data->sequence); + SAFE_DELETE(data->sequence2); + + data->sequenceName = ""; + data->sequenceName2 = ""; + + data->field_4A9 = false; + data->field_4AA = false; + data->directionSwitch = kDirectionNone; + + data->currentFrame = -1; + data->currentFrame2 = 0; + + data->direction = direction; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/entities.h b/engines/lastexpress/game/entities.h new file mode 100644 index 0000000000..40d7025b85 --- /dev/null +++ b/engines/lastexpress/game/entities.h @@ -0,0 +1,378 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ENTITIES_H +#define LASTEXPRESS_ENTITIES_H + +/* + Entities + -------- + + The entities structure contains 40 Entity_t structures for each entity + +*/ + +#include "lastexpress/entities/entity.h" + +#include "lastexpress/shared.h" + +#include "common/rect.h" +#include "common/serializer.h" + +namespace LastExpress { + +class LastExpressEngine; +class Sequence; + +class Entities : Common::Serializable { +public: + Entities(LastExpressEngine *engine); + ~Entities(); + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + + void setup(bool isFirstChapter, EntityIndex entity); + void setupChapter(ChapterIndex chapter); + void reset(); + + // Update & drawing + + /** + * Reset an entity state + * + * @param entity entity index + * @note remember to call the function pointer (we do not pass it our implementation) + */ + void resetState(EntityIndex entity); + void updateFields() const; + void updateSequences() const; + void updateCallbacks(); + + EntityIndex canInteractWith(const Common::Point &point) const; + bool compare(EntityIndex entity1, EntityIndex entity2); + + /** + * Update an entity current sequence frame (and related fields) + * + * @param entity entity index + */ + void updateFrame(EntityIndex entity) const; + void updatePositionEnter(EntityIndex entity, CarIndex car, Position position); + void updatePositionExit(EntityIndex entity, CarIndex car, Position position); + void enterCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1 = false); + void exitCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1 = false); + + // Sequences + void drawSequenceLeft(EntityIndex index, const char* sequence) const; + void drawSequenceRight(EntityIndex index, const char* sequence) const; + void clearSequences(EntityIndex index) const; + + bool updateEntity(EntityIndex entity, CarIndex car, EntityPosition position); + bool hasValidFrame(EntityIndex entity) const; + + // Accessors + Entity *get(EntityIndex entity); + EntityData::EntityCallData *getData(EntityIndex entity) const; + int getPosition(CarIndex car, Position position); + int getCompartments(int index); + int getCompartments1(int index); + + // Scene + void loadSceneFromEntityPosition(CarIndex car, EntityPosition position, bool alternate = false) const; + + ////////////////////////////////////////////////////////////////////////// + // Checks + ////////////////////////////////////////////////////////////////////////// + + /** + * Query if 'entity' is inside a compartment + * + * @param entity The entity. + * @param car The car. + * @param position The position. + * + * @return true if inside the compartment, false if not. + */ + bool isInsideCompartment(EntityIndex entity, CarIndex car, EntityPosition position) const; + + bool checkFields2(ObjectIndex object) const; + + /** + * Query if 'entity' is in compartment cars. + * + * @param entity The entity. + * + * @return true if in compartment cars, false if not. + */ + bool isInsideCompartments(EntityIndex entity) const; + + /** + * Query if the player is in the specified position + * + * @param car The car. + * @param position The position. + * @return true if player is in that position, false if not. + */ + bool isPlayerPosition(CarIndex car, Position position) const; + + /** + * Query if 'entity' is inside a train car + * + * @param entity The entity. + * @param car The car. + * + * @return true if inside a train car, false if not. + */ + bool isInsideTrainCar(EntityIndex entity, CarIndex car) const; + + /** + * Query if 'entity' is in green car entrance. + * + * @param entity The entity. + * + * @return true if in the green car entrance, false if not. + */ + bool isInGreenCarEntrance(EntityIndex entity) const; + + /** + * Query if the player is in a specific car + * + * @param car The car. + * + * @return true if player is in the car, false if not. + */ + bool isPlayerInCar(CarIndex car) const; + + /** + * Query if 'entity' is going in the up or down direction. + * + * @param entity The entity. + * + * @return true if direction is up or down, false if not. + */ + bool isDirectionUpOrDown(EntityIndex entity) const; + + /** + * Query if the distance between the two entities is less 'distance' + * + * @param entity1 The first entity. + * @param entity2 The second entity. + * @param distance The distance. + * + * @return true if the distance between entities is less than 'distance', false if not. + */ + bool isDistanceBetweenEntities(EntityIndex entity1, EntityIndex entity2, uint distance) const; + + bool checkFields10(EntityIndex entity) const; + + /** + * Query if there is somebody in the restaurant or salon. + * + * @return true if somebody is in the restaurant or salon, false if not. + */ + bool isSomebodyInsideRestaurantOrSalon() const; + + /** + * Query if 'entity' is in the salon. + * + * @param entity The entity. + * + * @return true if in the salon, false if not. + */ + bool isInSalon(EntityIndex entity) const; + + /** + * Query if 'entity' is in the restaurant. + * + * @param entity The entity. + * + * @return true if in the restaurant, false if not. + */ + bool isInRestaurant(EntityIndex entity) const; + + /** + * Query if 'entity' is in Kronos salon. + * + * @param entity The entity. + * + * @return true if in Kronos salon, false if not. + */ + bool isInKronosSalon(EntityIndex entity) const; + + /** + * Query if the player is outside Alexei window. + * + * @return true if outside alexei window, false if not. + */ + bool isOutsideAlexeiWindow() const; + + /** + * Query if the player is outside Anna window. + * + * @return true if outside anna window, false if not. + */ + bool isOutsideAnnaWindow() const; + + /** + * Query if 'entity' is in the kitchen. + * + * @param entity The entity. + * + * @return true if in the kitchen, false if not. + */ + bool isInKitchen(EntityIndex entity) const; + + /** + * Query if nobody is in a compartment at that position. + * + * @param car The car. + * @param position The position. + * + * @return true if nobody is in a compartment, false if not. + */ + bool isNobodyInCompartment(CarIndex car, EntityPosition position) const; + + bool checkFields19(EntityIndex entity, CarIndex car, EntityPosition position) const; + + /** + * Query if 'entity' is in the baggage car entrance. + * + * @param entity The entity. + * + * @return true if in the baggage car entrance, false if not. + */ + bool isInBaggageCarEntrance(EntityIndex entity) const; + + /** + * Query if 'entity' is in the baggage car. + * + * @param entity The entity. + * + * @return true if in the baggage car, false if not. + */ + bool isInBaggageCar(EntityIndex entity) const; + + /** + * Query if 'entity' is in Kronos sanctum. + * + * @param entity The entity. + * + * @return true if in Kronos sanctum, false if not. + */ + bool isInKronosSanctum(EntityIndex entity) const; + + /** + * Query if 'entity' is in Kronos car entrance. + * + * @param entity The entity. + * + * @return true if in Kronos car entrance, false if not. + */ + bool isInKronosCarEntrance(EntityIndex entity) const; + + /** + * Check distance from position. + * + * @param entity The entity. + * @param position The position. + * @param distance The distance. + * + * @return true if distance is bigger, false otherwise. + */ + bool checkDistanceFromPosition(EntityIndex entity, EntityPosition position, int distance) const; + + /** + * Query if 'entity' is walking opposite to player. + * + * @param entity The entity. + * + * @return true if walking opposite to player, false if not. + */ + bool isWalkingOppositeToPlayer(EntityIndex entity) const; + + /** + * Query if 'entity' is female. + * + * @param entity The entity. + * + * @return true if female, false if not. + */ + static bool isFemale(EntityIndex entity); + + /** + * Query if 'entity' is married. + * + * @param entity The entity. + * + * @return true if married, false if not. + */ + static bool isMarried(EntityIndex entity); + +private: + static const int _compartmentsCount = 16; + static const int _positionsCount = 100 * 10; // 100 positions per train car + + LastExpressEngine *_engine; + EntityData *_header; + Common::Array<Entity *> _entities; + + // Compartments & positions + uint _compartments[_compartmentsCount]; + uint _compartments1[_compartmentsCount]; + uint _positions[_positionsCount]; + + void executeCallbacks(); + void processEntity(EntityIndex entity); + + void drawSequence(EntityIndex entity, const char* sequence, EntityDirection direction) const; + void drawSequences(EntityIndex entity, EntityDirection direction, bool loadSequence) const; + void loadSequence2(EntityIndex entity, Common::String sequenceName, Common::String sequenceName2, byte field30, bool loadSequence) const; + + void clearEntitySequenceData(EntityData::EntityCallData * data, EntityDirection direction) const; + void computeCurrentFrame(EntityIndex entity) const; + int16 getCurrentFrame(EntityIndex entity, Sequence *sequence, EntityPosition position, bool doProcessing) const; + void processFrame(EntityIndex entity, bool keepPreviousFrame, bool dontPlaySound); + void drawNextSequence(EntityIndex entity) const; + void updateEntityPosition(EntityIndex entity) const; + void copySequenceData(EntityIndex entity) const; + + bool changeCar(EntityData::EntityCallData * data, EntityIndex entity, CarIndex car, EntityPosition position, bool increment, EntityPosition newPosition, CarIndex newCar) const; + + void getSequenceName(EntityIndex entity, EntityDirection direction, Common::String &sequence1, Common::String &sequence2) const; + + void updatePositionsEnter(EntityIndex entity, CarIndex car, Position position1, Position position2, Position position3, Position position4); + void updatePositionsExit(EntityIndex entity, CarIndex car, Position position1, Position position2); + + void resetSequences(EntityIndex entity) const; + + bool checkPosition(EntityPosition position) const; + bool checkSequenceFromPosition(EntityIndex entity) const; + EntityPosition getEntityPositionFromCurrentPosition() const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ENTITIES_H diff --git a/engines/lastexpress/game/fight.cpp b/engines/lastexpress/game/fight.cpp new file mode 100644 index 0000000000..8fa711df1c --- /dev/null +++ b/engines/lastexpress/game/fight.cpp @@ -0,0 +1,1587 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/fight.h" + +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/scene.h" +#include "lastexpress/data/sequence.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +#include "common/func.h" + +namespace LastExpress { + +#define CALL_FUNCTION0(fighter, name) \ + (*fighter->name)(fighter) + +#define CALL_FUNCTION1(fighter, name, a) \ + (*fighter->name)(fighter, a) + +#define REGISTER_PLAYER_FUNCTIONS(name) \ + if (!_data) \ + error("Fight::load##namePlayer - invalid data!"); \ + _data->player->handleAction = new Common::Functor2Mem<Fighter *, FightAction, void, Fight>(this, &Fight::handleAction##name); \ + _data->player->update = new Common::Functor1Mem<Fighter *, void, Fight>(this, &Fight::update##name); \ + _data->player->canInteract = new Common::Functor2Mem<Fighter const *, FightAction, bool, Fight>(this, &Fight::canInteract##name); + +#define REGISTER_OPPONENT_FUNCTIONS(name) \ + if (!_data) \ + error("Fight::load##nameOpponent - invalid data!"); \ + _data->opponent->handleAction = new Common::Functor2Mem<Fighter *, FightAction, void, Fight>(this, &Fight::handleOpponentAction##name); \ + _data->opponent->update = new Common::Functor1Mem<Fighter *, void, Fight>(this, &Fight::updateOpponent##name); \ + _data->opponent->canInteract = new Common::Functor2Mem<Fighter const *, FightAction, bool, Fight>(this, &Fight::canInteract); + +#define CHECK_SEQUENCE2(fighter, value) \ + (fighter->frame->getInfo()->field_33 & value) + +Fight::Fight(LastExpressEngine *engine) : _engine(engine), _data(NULL), _endType(kFightEndLost), _state(0), _handleTimer(false) {} + +Fight::~Fight() { + clearData(); + _data = NULL; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Events +////////////////////////////////////////////////////////////////////////// + +void Fight::eventMouse(const Common::Event &ev) { + if (!_data || _data->index) + return; + + // TODO move all the egg handling to inventory functions + + getFlags()->mouseLeftClick = false; + getFlags()->shouldRedraw = false; + getFlags()->mouseRightClick = false; + + if (ev.mouse.x < 608 || ev.mouse.y < 448 || ev.mouse.x >= 640 || ev.mouse.x >= 480) { + + // Handle right button click + if (ev.type == Common::EVENT_RBUTTONUP) { + getSound()->removeFromQueue(kEntityTables0); + setStopped(); + + getGlobalTimer() ? _state = 0 : ++_state; + + getFlags()->mouseRightClick = true; + } + + if (_handleTimer) { + // Timer expired => show with full brightness + if (!getGlobalTimer()) + getInventory()->drawEgg(); + + _handleTimer = false; + } + + // Check hotspots + Scene *scene = getScenes()->get(getState()->scene); + SceneHotspot *hotspot = NULL; + + if (!scene->checkHotSpot(ev.mouse, &hotspot)) { + _engine->getCursor()->setStyle(kCursorNormal); + } else { + _engine->getCursor()->setStyle((CursorStyle)hotspot->cursor); + + // Call player function + if (CALL_FUNCTION1(_data->player, canInteract, (FightAction)hotspot->action)) { + if (ev.type == Common::EVENT_LBUTTONUP) + CALL_FUNCTION1(_data->player, handleAction, (FightAction)hotspot->action); + } else { + _engine->getCursor()->setStyle(kCursorNormal); + } + } + } else { + // Handle clicks on menu icon + + if (!_handleTimer) { + // Timer expired => show with full brightness + if (!getGlobalTimer()) + getInventory()->drawEgg(); + + _handleTimer = true; + } + + // Stop fight if clicked + if (ev.type == Common::EVENT_LBUTTONUP) { + _handleTimer = false; + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndExit); + } + + // Reset timer on right click + if (ev.type == Common::EVENT_RBUTTONUP) { + if (getGlobalTimer()) { + if (getSound()->isBuffered("TIMER")) + getSound()->removeFromQueue("TIMER"); + + setGlobalTimer(900); + } + } + } + + getFlags()->shouldRedraw = true; +} + +void Fight::eventTick(const Common::Event &ev) { + handleTick(ev, true); +} + +void Fight::handleTick(const Common::Event &ev, bool isProcessing) { + // TODO move all the egg handling to inventory functions + + // Blink egg + if (getGlobalTimer()) { + warning("Fight::handleMouseMove - egg blinking not implemented!"); + } + + if (!_data || _data->index) + return; + + SceneHotspot *hotspot = NULL; + if (!getScenes()->get(getState()->scene)->checkHotSpot(ev.mouse, &hotspot) || !CALL_FUNCTION1(_data->player, canInteract, (FightAction)hotspot->action)) { + _engine->getCursor()->setStyle(kCursorNormal); + } else { + _engine->getCursor()->setStyle((CursorStyle)hotspot->cursor); + } + + CALL_FUNCTION0(_data->player, update); + CALL_FUNCTION0(_data->opponent, update); + + // Draw sequences + if (!_data->isRunning) + return; + + if (isProcessing) + getScenes()->drawFrames(true); + + if (_data->index) { + // Set next sequence name index + _data->index--; + _data->sequences[_data->index] = loadSequence(_data->names[_data->index]); + } +} + +////////////////////////////////////////////////////////////////////////// +// Setup +////////////////////////////////////////////////////////////////////////// + +Fight::FightEndType Fight::setup(FightType type) { + if (_data) + error("Fight::setup - calling fight setup again while a fight is already in progress!"); + + ////////////////////////////////////////////////////////////////////////// + // Prepare UI & state + if (_state >= 5 && (type == kFightSalko || type == kFightVesna)) { + _state = 0; + return kFightEndWin; + } + + getInventory()->showHourGlass(); + // TODO events function + getFlags()->flag_0 = false; + getFlags()->mouseRightClick = false; + getEntities()->reset(); + + // Compute scene to use + SceneIndex sceneIndex; + switch(type) { + default: + sceneIndex = kSceneFightDefault; + break; + + case kFightMilos: + sceneIndex = (getObjects()->get(kObjectCompartment1).location2 < kObjectLocation3) ? kSceneFightMilos : kSceneFightMilosBedOpened; + break; + + case kFightAnna: + sceneIndex = kSceneFightAnna; + break; + + case kFightIvo: + sceneIndex = kSceneFightIvo; + break; + + case kFightSalko: + sceneIndex = kSceneFightSalko; + break; + + case kFightVesna: + sceneIndex = kSceneFightVesna; + break; + } + + if (getFlags()->shouldRedraw) { + getFlags()->shouldRedraw = false; + askForRedraw(); + //redrawScreen(); + } + + // Load the scene object + Scene *scene = getScenes()->get(sceneIndex); + + // Update game entities and state + getEntityData(kEntityPlayer)->entityPosition = scene->entityPosition; + getEntityData(kEntityPlayer)->location = scene->location; + + getState()->scene = sceneIndex; + + getFlags()->flag_3 = true; + + // Draw the scene + _engine->getGraphicsManager()->draw(scene, GraphicsManager::kBackgroundC); + // FIXME move to start of fight? + askForRedraw(); + redrawScreen(); + + ////////////////////////////////////////////////////////////////////////// + // Setup the fight + _data = new FightData; + loadData(type); + + // Show opponents & egg button + Common::Event emptyEvent; + handleTick(emptyEvent, false); + getInventory()->drawEgg(); + + // Start fight + _endType = kFightEndLost; + while (_data->isRunning) { + if (_engine->handleEvents()) + continue; + + getSound()->updateQueue(); + } + + // Cleanup after fight is over + clearData(); + + return _endType; +} + +////////////////////////////////////////////////////////////////////////// +// Status +////////////////////////////////////////////////////////////////////////// + +void Fight::setStopped() { + if (_data) + _data->isRunning = false; +} + +void Fight::bailout(FightEndType type) { + _state = 0; + _endType = type; + setStopped(); +} + +////////////////////////////////////////////////////////////////////////// +// Cleanup +////////////////////////////////////////////////////////////////////////// + +void Fight::clearData() { + if (!_data) + return; + + // Clear data + clearSequences(_data->player); + clearSequences(_data->opponent); + + delete _data->player; + delete _data->opponent; + + delete _data; + _data = NULL; + + _engine->restoreEventHandlers(); +} + +void Fight::clearSequences(Fighter *combatant) const { + if (!combatant) + return; + + // The original game resets the function pointers to default values, just before deleting the struct + getScenes()->removeAndRedraw(&combatant->frame, false); + + // Free sequences + for (int i = 0; i < (int)combatant->sequences.size(); i++) + delete combatant->sequences[i]; +} + +////////////////////////////////////////////////////////////////////////// +// Drawing +////////////////////////////////////////////////////////////////////////// + +void Fight::setSequenceAndDraw(Fighter *combatant, uint32 sequenceIndex, FightSequenceType type) const { + if (combatant->sequences.size() < sequenceIndex) + return; + + switch (type) { + default: + break; + + case kFightSequenceType0: + if (combatant->sequenceIndex) + return; + + combatant->sequence = combatant->sequences[sequenceIndex]; + combatant->sequenceIndex = sequenceIndex; + draw(combatant); + break; + + case kFightSequenceType1: + combatant->sequence = combatant->sequences[sequenceIndex]; + combatant->sequenceIndex = sequenceIndex; + combatant->sequenceIndex2 = 0; + draw(combatant); + break; + + case kFightSequenceType2: + combatant->sequenceIndex2 = sequenceIndex; + break; + } +} + +void Fight::draw(Fighter *combatant) const { + getScenes()->removeAndRedraw(&combatant->frame, false); + + combatant->frameIndex = 0; + combatant->field_24 = 0; +} + +////////////////////////////////////////////////////////////////////////// +// Loading +////////////////////////////////////////////////////////////////////////// + +void Fight::loadData(FightType type) { + if (!_data) + error("Fight::loadData - invalid data!"); + + switch (type) { + default: + break; + + case kFightMilos: + loadMilosPlayer(); + loadMilosOpponent(); + break; + + case kFightAnna: + loadAnnaPlayer(); + loadAnnaOpponent(); + break; + + case kFightIvo: + loadIvoPlayer(); + loadIvoOpponent(); + break; + + case kFightSalko: + loadSalkoPlayer(); + loadSalkoOpponent(); + break; + + case kFightVesna: + loadVesnaPlayer(); + loadVesnaOpponent(); + break; + } + + if (!_data->player || !_data->opponent) + error("Fight::loadData - error loading fight data (type=%d)", type); + + ////////////////////////////////////////////////////////////////////////// + // Start running the fight + _data->isRunning = true; + + if (_state < 5) { + setSequenceAndDraw(_data->player, 0, kFightSequenceType0); + setSequenceAndDraw(_data->opponent, 0, kFightSequenceType0); + goto end_load; + } + + switch(type) { + default: + break; + + case kFightMilos: + _data->opponent->countdown = 1; + setSequenceAndDraw(_data->player, 4, kFightSequenceType0); + setSequenceAndDraw(_data->opponent, 0, kFightSequenceType0); + break; + + case kFightIvo: + _data->opponent->countdown = 1; + setSequenceAndDraw(_data->player, 3, kFightSequenceType0); + setSequenceAndDraw(_data->opponent, 6, kFightSequenceType0); + break; + + case kFightVesna: + _data->opponent->countdown = 1; + setSequenceAndDraw(_data->player, 0, kFightSequenceType0); + setSequenceAndDraw(_data->player, 3, kFightSequenceType2); + setSequenceAndDraw(_data->opponent, 5, kFightSequenceType0); + break; + } + +end_load: + // Setup event handlers + _engine->backupEventHandlers(); + SET_EVENT_HANDLERS(Fight, this); +} + +////////////////////////////////////////////////////////////////////////// +// Shared +////////////////////////////////////////////////////////////////////////// +void Fight::processFighter(Fighter *fighter) { + if (!_data) + error("Fight::processFighter - invalid data!"); + + if (!fighter->sequence) { + if (fighter->frame) { + getScenes()->removeFromQueue(fighter->frame); + getScenes()->setCoordinates(fighter->frame); + } + SAFE_DELETE(fighter->frame); + return; + } + + if (fighter->sequence->count() <= fighter->frameIndex) { + switch(fighter->action) { + default: + break; + + case kFightAction101: + setSequenceAndDraw(fighter, fighter->sequenceIndex2, kFightSequenceType1); + fighter->sequenceIndex2 = 0; + break; + + case kFightActionResetFrame: + fighter->frameIndex = 0; + break; + + case kFightAction103: + setSequenceAndDraw(fighter, 0, kFightSequenceType1); + CALL_FUNCTION1(fighter, handleAction, kFightAction101); + setSequenceAndDraw(fighter->opponent, 0, kFightSequenceType1); + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction101); + CALL_FUNCTION0(fighter->opponent, update); + break; + + case kFightActionWin: + bailout(kFightEndWin); + break; + + case kFightActionLost: + bailout(kFightEndLost); + break; + } + } + + if (_data->isRunning) { + + // Get the current sequence frame + SequenceFrame *frame = new SequenceFrame(fighter->sequence, (uint16)fighter->frameIndex); + frame->getInfo()->location = 1; + + if (fighter->frame == frame) { + delete frame; + return; + } + + getSound()->playFightSound(frame->getInfo()->soundAction, frame->getInfo()->field_31); + + // Add current frame to queue and advance + getScenes()->addToQueue(frame); + fighter->frameIndex++; + + if (fighter->frame) { + getScenes()->removeFromQueue(fighter->frame); + + if (!frame->getInfo()->field_2E) + getScenes()->setCoordinates(fighter->frame); + } + + // Replace by new frame + delete fighter->frame; + fighter->frame = frame; + } +} + +void Fight::handleAction(Fighter *fighter, FightAction action) { + switch (action) { + default: + return; + + case kFightAction101: + break; + + case kFightActionResetFrame: + fighter->countdown--; + break; + + case kFightAction103: + CALL_FUNCTION1(fighter->opponent, handleAction, kFightActionResetFrame); + break; + + case kFightActionWin: + _endType = kFightEndWin; + CALL_FUNCTION1(fighter->opponent, handleAction, kFightActionResetFrame); + break; + + case kFightActionLost: + _endType = kFightEndLost; + CALL_FUNCTION1(fighter->opponent, handleAction, kFightActionResetFrame); + break; + } + + // Update action + fighter->action = action; +} + +bool Fight::canInteract(Fighter const *fighter, FightAction /*= (FightAction)0*/ ) { + return (fighter->action == kFightAction101 && !fighter->sequenceIndex); +} + +void Fight::update(Fighter *fighter) { + + processFighter(fighter); + + if (fighter->frame) + fighter->frame->getInfo()->location = (fighter->action == kFightActionResetFrame ? 2 : 0); +} + +void Fight::updateOpponent(Fighter *fighter) { + + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + processFighter(opponent); + + if (opponent->field_38 && !opponent->sequenceIndex) + opponent->field_38--; + + if (fighter->frame) + fighter->frame->getInfo()->location = 1; +} + +////////////////////////////////////////////////////////////////////////// +// Milos +////////////////////////////////////////////////////////////////////////// + +void Fight::loadMilosPlayer() { + REGISTER_PLAYER_FUNCTIONS(Milos) + + _data->player->sequences.push_back(loadSequence("2001cr.seq")); + _data->player->sequences.push_back(loadSequence("2001cdl.seq")); + _data->player->sequences.push_back(loadSequence("2001cdr.seq")); + _data->player->sequences.push_back(loadSequence("2001cdm.seq")); + _data->player->sequences.push_back(loadSequence("2001csgr.seq")); + _data->player->sequences.push_back(loadSequence("2001csgl.seq")); + _data->player->sequences.push_back(loadSequence("2001dbk.seq")); +} + +void Fight::loadMilosOpponent() { + REGISTER_OPPONENT_FUNCTIONS(Milos) + + _data->opponent->sequences.push_back(loadSequence("2001or.seq")); + _data->opponent->sequences.push_back(loadSequence("2001oal.seq")); + _data->opponent->sequences.push_back(loadSequence("2001oam.seq")); + _data->opponent->sequences.push_back(loadSequence("2001okl.seq")); + _data->opponent->sequences.push_back(loadSequence("2001okm.seq")); + _data->opponent->sequences.push_back(loadSequence("2001dbk.seq")); + _data->opponent->sequences.push_back(loadSequence("2001wbk.seq")); + + getSound()->playSound(kEntityTables0, "MUS027", SoundManager::kFlagDefault); + + _data->opponent->field_38 = 35; +} + +void Fight::handleActionMilos(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + if (fighter->sequenceIndex != 1 || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 6, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 3, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction2: + if ((fighter->sequenceIndex != 2 && fighter->sequenceIndex != 3) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 6, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 4, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction128: + if (fighter->sequenceIndex != 1 || CHECK_SEQUENCE2(fighter, 4) || fighter->opponent->sequenceIndex != 1) { + switch (fighter->opponent->sequenceIndex) { + default: + setSequenceAndDraw(fighter, rnd(3) + 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(fighter, 1, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(fighter, 3, kFightSequenceType0); + break; + } + } else { + setSequenceAndDraw(fighter, 4, kFightSequenceType1); + CALL_FUNCTION0(fighter, update); + } + break; + } +} + +void Fight::updateMilos(Fighter *fighter) { + if (fighter->frame && CHECK_SEQUENCE2(fighter, 2)) { + + // Draw sequences + if (fighter->opponent->countdown <= 0) { + setSequenceAndDraw(fighter, 5, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 6, kFightSequenceType1); + + getSound()->removeFromQueue(kEntityTables0); + getSound()->playSound(kEntityTrain, "MUS029", SoundManager::kFlagDefault); + + CALL_FUNCTION1(fighter, handleAction, kFightActionWin); + } + + if (fighter->sequenceIndex == 4) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction4); + _endType = kFightEndLost; + } + } + + update(fighter); +} + +bool Fight::canInteractMilos(Fighter const *fighter, FightAction action) { + if (!_data) + error("Fight::canInteractMilos - invalid data!"); + + if (action != kFightAction128 + || _data->player->sequenceIndex != 1 + || !fighter->frame + || CHECK_SEQUENCE2(fighter, 4) + || fighter->opponent->sequenceIndex != 1) { + return canInteract(fighter); + } + + _engine->getCursor()->setStyle(kCursorHand); + + return true; +} + +void Fight::handleOpponentActionMilos(Fighter *fighter, FightAction action) { + if (action == kFightAction4) { + setSequenceAndDraw(fighter, 5, kFightSequenceType1); + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + } else { + if (action != kFightAction131) + handleAction(fighter, action); + } +} + +void Fight::updateOpponentMilos(Fighter *fighter) { + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + if (opponent->opponent->field_34 >= 2) { + switch (rnd(5)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType1); + break; + + case 3: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + } + } else { + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + } + + // Update field_38 + if (opponent->opponent->field_34 < 5) + opponent->field_38 = 6 * (5 - opponent->opponent->field_34); + else + opponent->field_38 = 0; + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + + if (opponent->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + CALL_FUNCTION1(opponent, handleAction, kFightActionLost); + } + } + + updateOpponent(opponent); +} + +////////////////////////////////////////////////////////////////////////// +// Anna +////////////////////////////////////////////////////////////////////////// + +void Fight::loadAnnaPlayer() { + if (!_data) + error("Fight::loadAnnaPlayer - invalid data!"); + + // Special case: we are using some shared functions directly + _data->player->handleAction = new Common::Functor2Mem<Fighter *, FightAction, void, Fight>(this, &Fight::handleActionAnna); + _data->player->update = new Common::Functor1Mem<Fighter *, void, Fight>(this, &Fight::update); + _data->player->canInteract = new Common::Functor2Mem<Fighter const *, FightAction, bool, Fight>(this, &Fight::canInteract); + + _data->player->sequences.push_back(loadSequence("2002cr.seq")); + _data->player->sequences.push_back(loadSequence("2002cdl.seq")); + _data->player->sequences.push_back(loadSequence("2002cdr.seq")); + _data->player->sequences.push_back(loadSequence("2002cdm.seq")); + _data->player->sequences.push_back(loadSequence("2002lbk.seq")); +} + +void Fight::loadAnnaOpponent() { + if (!_data) + error("Fight::loadAnnaOpponent - invalid data!"); + + // Special case: we are using some shared functions directly + _data->opponent->handleAction = new Common::Functor2Mem<Fighter *, FightAction, void, Fight>(this, &Fight::handleAction); + _data->opponent->update = new Common::Functor1Mem<Fighter *, void, Fight>(this, &Fight::updateOpponentAnna); + _data->opponent->canInteract = new Common::Functor2Mem<Fighter const *, FightAction, bool, Fight>(this, &Fight::canInteract); + + _data->opponent->sequences.push_back(loadSequence("2002or.seq")); + _data->opponent->sequences.push_back(loadSequence("2002oal.seq")); + _data->opponent->sequences.push_back(loadSequence("2002oam.seq")); + _data->opponent->sequences.push_back(loadSequence("2002oar.seq")); + _data->opponent->sequences.push_back(loadSequence("2002okr.seq")); + _data->opponent->sequences.push_back(loadSequence("2002okml.seq")); + _data->opponent->sequences.push_back(loadSequence("2002okm.seq")); + + getSound()->playSound(kEntityTables0, "MUS030", SoundManager::kFlagDefault); + + _data->opponent->field_38 = 30; +} + +void Fight::handleActionAnna(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + if ((fighter->sequenceIndex != 1 && fighter->sequenceIndex != 3) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 4, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 4, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction2: + if ((fighter->sequenceIndex != 2 && fighter->sequenceIndex != 3) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 4, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 5, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction3: + if ((fighter->sequenceIndex != 2 && fighter->sequenceIndex != 1) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 4, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 6, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction128: + switch (fighter->opponent->sequenceIndex) { + default: + setSequenceAndDraw(fighter, 3, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(fighter, 1, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(fighter, 3, kFightSequenceType0); + break; + + case 3: + setSequenceAndDraw(fighter, 2, kFightSequenceType0); + break; + } + break; + } + + if (fighter->field_34 > 4) { + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndWin); + } +} + +void Fight::updateOpponentAnna(Fighter *fighter) { + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + if (opponent->opponent->field_34 >= 2) { + switch (rnd(6)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(opponent, 3, kFightSequenceType0); + break; + + case 3: + setSequenceAndDraw(opponent, 3, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 5: + setSequenceAndDraw(opponent, 3, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + } + } + + // Update field_38 + opponent->field_38 = (int32)rnd(15); + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2 || opponent->sequenceIndex == 3) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + + if (opponent->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + CALL_FUNCTION1(opponent, handleAction, kFightActionLost); + } + } + + updateOpponent(opponent); +} + +////////////////////////////////////////////////////////////////////////// +// Ivo +////////////////////////////////////////////////////////////////////////// + +void Fight::loadIvoPlayer() { + REGISTER_PLAYER_FUNCTIONS(Ivo) + + _data->player->sequences.push_back(loadSequence("2003cr.seq")); + _data->player->sequences.push_back(loadSequence("2003car.seq")); + _data->player->sequences.push_back(loadSequence("2003cal.seq")); + _data->player->sequences.push_back(loadSequence("2003cdr.seq")); + _data->player->sequences.push_back(loadSequence("2003cdm.seq")); + _data->player->sequences.push_back(loadSequence("2003chr.seq")); + _data->player->sequences.push_back(loadSequence("2003chl.seq")); + _data->player->sequences.push_back(loadSequence("2003ckr.seq")); + _data->player->sequences.push_back(loadSequence("2003lbk.seq")); + _data->player->sequences.push_back(loadSequence("2003fbk.seq")); + + _data->player->countdown = 5; +} + +void Fight::loadIvoOpponent() { + REGISTER_OPPONENT_FUNCTIONS(Ivo) + + _data->opponent->sequences.push_back(loadSequence("2003or.seq")); + _data->opponent->sequences.push_back(loadSequence("2003oal.seq")); + _data->opponent->sequences.push_back(loadSequence("2003oar.seq")); + _data->opponent->sequences.push_back(loadSequence("2003odm.seq")); + _data->opponent->sequences.push_back(loadSequence("2003okl.seq")); + _data->opponent->sequences.push_back(loadSequence("2003okj.seq")); + _data->opponent->sequences.push_back(loadSequence("blank.seq")); + _data->opponent->sequences.push_back(loadSequence("csdr.seq")); + _data->opponent->sequences.push_back(loadSequence("2003l.seq")); + + getSound()->playSound(kEntityTables0, "MUS032", SoundManager::kFlagDefault); + + _data->opponent->countdown = 5; + _data->opponent->field_38 = 15; +} + +void Fight::handleActionIvo(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + if (fighter->sequenceIndex != 1 || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 7, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 4, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } + break; + + case kFightAction2: + if ((fighter->sequenceIndex != 2 && fighter->sequenceIndex != 3) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 7, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 5, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } + break; + + case kFightAction128: + switch (fighter->opponent->sequenceIndex) { + default: + case 1: + setSequenceAndDraw(fighter, 1, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(fighter, 2, kFightSequenceType0); + break; + } + break; + + case kFightAction129: + setSequenceAndDraw(fighter, (fighter->opponent->countdown > 1) ? 4 : 3, fighter->sequenceIndex ? kFightSequenceType2 : kFightSequenceType0); + break; + + case kFightAction130: + setSequenceAndDraw(fighter, 3, fighter->sequenceIndex ? kFightSequenceType2 : kFightSequenceType0); + break; + } +} + +void Fight::updateIvo(Fighter *fighter) { + + if ((fighter->sequenceIndex == 3 || fighter->sequenceIndex == 4) && !fighter->frameIndex) + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction131); + + if (fighter->frame && CHECK_SEQUENCE2(fighter, 2)) { + + // Draw sequences + if (fighter->opponent->countdown <= 0) { + setSequenceAndDraw(fighter, 9, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 8, kFightSequenceType1); + getSound()->removeFromQueue(kEntityTables0); + + CALL_FUNCTION1(fighter, handleAction, kFightActionWin); + return; + } + + if (fighter->sequenceIndex == 3 || fighter->sequenceIndex == 4) + CALL_FUNCTION1(fighter->opponent, handleAction, (FightAction)fighter->sequenceIndex); + } + + update(fighter); +} + +bool Fight::canInteractIvo(Fighter const *fighter, FightAction action) { + if (action == kFightAction129 || action == kFightAction130) + return (fighter->sequenceIndex >= 8); + + return canInteract(fighter); +} + +void Fight::handleOpponentActionIvo(Fighter *fighter, FightAction action) { + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + switch (action) { + default: + handleAction(fighter, action); + break; + + case kFightAction3: + if ((opponent->sequenceIndex != 1 && opponent->sequenceIndex != 3) || CHECK_SEQUENCE2(opponent, 4)) { + setSequenceAndDraw(opponent, 6, kFightSequenceType1); + setSequenceAndDraw(opponent->opponent, 6, kFightSequenceType1); + CALL_FUNCTION1(opponent->opponent, handleAction, kFightAction103); + } + break; + + case kFightAction4: + if ((opponent->sequenceIndex != 2 && opponent->sequenceIndex != 3) || CHECK_SEQUENCE2(opponent, 4)) { + setSequenceAndDraw(opponent, 6, kFightSequenceType1); + setSequenceAndDraw(opponent->opponent, 5, kFightSequenceType1); + CALL_FUNCTION1(opponent->opponent, handleAction, kFightAction103); + } + break; + + case kFightAction131: + if (opponent->sequenceIndex) + break; + + if (rnd(100) <= (unsigned int)(opponent->countdown > 2 ? 60 : 75)) { + setSequenceAndDraw(opponent, 3 , kFightSequenceType1); + if (opponent->opponent->sequenceIndex == 4) + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + } + break; + } +} + +void Fight::updateOpponentIvo(Fighter *fighter) { + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + if (opponent->opponent->field_34 >= 2) { + switch (rnd(5)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 3: + setSequenceAndDraw(opponent, 0, kFightSequenceType2); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 0, kFightSequenceType1); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + } + } + + // Update field_38 + opponent->field_38 = 3 * opponent->countdown + (int32)rnd(10); + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + + if (opponent->opponent->countdown <= 0) { + setSequenceAndDraw(opponent, 7, kFightSequenceType1); + setSequenceAndDraw(opponent->opponent, 8, kFightSequenceType1); + getSound()->removeFromQueue(kEntityTables0); + + CALL_FUNCTION1(opponent->opponent, handleAction, kFightActionWin); + + return; + } + + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + } + + updateOpponent(opponent); +} + +////////////////////////////////////////////////////////////////////////// +// Salko +////////////////////////////////////////////////////////////////////////// + +void Fight::loadSalkoPlayer() { + REGISTER_PLAYER_FUNCTIONS(Salko) + + _data->player->sequences.push_back(loadSequence("2004cr.seq")); + _data->player->sequences.push_back(loadSequence("2004cdr.seq")); + _data->player->sequences.push_back(loadSequence("2004chj.seq")); + _data->player->sequences.push_back(loadSequence("2004bk.seq")); + + _data->player->countdown = 2; +} + +void Fight::loadSalkoOpponent() { + REGISTER_OPPONENT_FUNCTIONS(Salko) + + _data->opponent->sequences.push_back(loadSequence("2004or.seq")); + _data->opponent->sequences.push_back(loadSequence("2004oam.seq")); + _data->opponent->sequences.push_back(loadSequence("2004oar.seq")); + _data->opponent->sequences.push_back(loadSequence("2004okr.seq")); + _data->opponent->sequences.push_back(loadSequence("2004ohm.seq")); + _data->opponent->sequences.push_back(loadSequence("blank.seq")); + + getSound()->playSound(kEntityTables0, "MUS035", SoundManager::kFlagDefault); + + _data->opponent->countdown = 3; + _data->opponent->field_38 = 30; +} + +void Fight::handleActionSalko(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + case kFightAction2: + if (fighter->sequenceIndex != 1 && CHECK_SEQUENCE2(fighter, 4)) { + fighter->field_34 = 0; + + setSequenceAndDraw(fighter, 3, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, (action == kFightAction1 ? 3 : 4), kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + + if (action == kFightAction2) + fighter->countdown= 0; + + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction5: + if (fighter->sequenceIndex != 3) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } + break; + + case kFightAction128: + setSequenceAndDraw(fighter, 1, kFightSequenceType0); + fighter->field_34 = 0; + break; + + case kFightAction131: + setSequenceAndDraw(fighter, 2, (fighter->sequenceIndex ? kFightSequenceType2 : kFightSequenceType0)); + break; + } +} + +void Fight::updateSalko(Fighter *fighter) { + update(fighter); + + // The original doesn't check for currentSequence2 != NULL (might not happen when everything is working properly, but crashes with our current implementation) + if (fighter->frame && CHECK_SEQUENCE2(fighter, 2)) { + + if (fighter->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndWin); + + return; + } + + if (fighter->sequenceIndex == 2) + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction2); + } +} + +bool Fight::canInteractSalko(Fighter const *fighter, FightAction action) { + if (action == kFightAction131) { + if (fighter->sequenceIndex == 1) { + if (fighter->opponent->countdown <= 0) + _engine->getCursor()->setStyle(kCursorHand); + + return true; + } + + return false; + } + + return canInteract(fighter); +} + +void Fight::handleOpponentActionSalko(Fighter *fighter, FightAction action) { + if (action == kFightAction2) { + setSequenceAndDraw(fighter, 5, kFightSequenceType1); + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + } else { + handleAction(fighter, action); + } +} + +void Fight::updateOpponentSalko(Fighter *fighter) { + // This is an opponent struct + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + switch (rnd(5)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 3: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + } + + // Update field_38 + opponent->field_38 = 4 * opponent->countdown; + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + if (opponent->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndLost); + + // Stop processing + return; + } + + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + } + + updateOpponent(opponent); +} + +////////////////////////////////////////////////////////////////////////// +// Vesna +////////////////////////////////////////////////////////////////////////// + +void Fight::loadVesnaPlayer() { + REGISTER_PLAYER_FUNCTIONS(Vesna) + + _data->player->sequences.push_back(loadSequence("2005cr.seq")); + _data->player->sequences.push_back(loadSequence("2005cdr.seq")); + _data->player->sequences.push_back(loadSequence("2005cbr.seq")); + _data->player->sequences.push_back(loadSequence("2005bk.seq")); + _data->player->sequences.push_back(loadSequence("2005cdm1.seq")); + _data->player->sequences.push_back(loadSequence("2005chl.seq")); +} + +void Fight::loadVesnaOpponent() { + REGISTER_OPPONENT_FUNCTIONS(Vesna) + + _data->opponent->sequences.push_back(loadSequence("2005or.seq")); + _data->opponent->sequences.push_back(loadSequence("2005oam.seq")); + _data->opponent->sequences.push_back(loadSequence("2005oar.seq")); + _data->opponent->sequences.push_back(loadSequence("2005okml.seq")); + _data->opponent->sequences.push_back(loadSequence("2005okr.seq")); + _data->opponent->sequences.push_back(loadSequence("2005odm1.seq")); + _data->opponent->sequences.push_back(loadSequence("2005csbm.seq")); + _data->opponent->sequences.push_back(loadSequence("2005oam4.seq")); + + getSound()->playSound(kEntityTables0, "MUS038", SoundManager::kFlagDefault); + + _data->opponent->countdown = 4; + _data->opponent->field_38 = 30; +} + +void Fight::handleActionVesna(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + if (fighter->sequenceIndex != 1) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction2: + if (fighter->sequenceIndex != 2) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction5: + if (fighter->sequenceIndex != 3) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } + break; + + case kFightAction128: + if (fighter->sequenceIndex == 1 && fighter->opponent->sequenceIndex == 1 && CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 5, kFightSequenceType1); + } else { + setSequenceAndDraw(fighter, (fighter->opponent->sequenceIndex == 5) ? 3 : 1, kFightSequenceType0); + } + break; + + case kFightAction132: + setSequenceAndDraw(fighter, 2, kFightSequenceType0); + break; + } + + if (fighter->field_34 > 10) { + setSequenceAndDraw(fighter->opponent, 5, kFightSequenceType2); + fighter->opponent->countdown = 1; + fighter->field_34 = 0; + } +} + +void Fight::updateVesna(Fighter *fighter) { + if (fighter->frame && CHECK_SEQUENCE2(fighter, 2)) { + + if (fighter->sequenceIndex == 3) + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction3); + + if (fighter->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndWin); + return; + } + + if (fighter->sequenceIndex == 5) + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction5); + } + + update(fighter); +} + +bool Fight::canInteractVesna(Fighter const *fighter, FightAction action) { + if (action != kFightAction128) + return canInteract(fighter); + + if (fighter->sequenceIndex != 1) { + + if (fighter->opponent->sequenceIndex == 5) { + _engine->getCursor()->setStyle(kCursorDown); + return true; + } + + return canInteract(fighter); + } + + if (fighter->opponent->sequenceIndex == 1 && CHECK_SEQUENCE2(fighter, 4)) { + _engine->getCursor()->setStyle(kCursorPunchLeft); + return true; + } + + return false; +} + +void Fight::handleOpponentActionVesna(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + break; + + case kFightAction3: + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + break; + + case kFightAction5: + setSequenceAndDraw(fighter, 7, kFightSequenceType1); + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + if (fighter->countdown <= 1) + fighter->countdown = 1; + break; + + case kFightAction131: + break; + } +} + +void Fight::updateOpponentVesna(Fighter *fighter) { + // This is an opponent struct + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + if (opponent->opponent->field_34 == 1) { + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + } else { + switch (rnd(6)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + + case 2: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 3: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 5: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + } + } + + // Update field_38 + opponent->field_38 = 4 * opponent->countdown; + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2 || opponent->sequenceIndex == 5) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + + if (opponent->opponent->countdown <= 0) { + + switch (opponent->sequenceIndex) { + default: + break; + + case 1: + setSequenceAndDraw(opponent, 3, kFightSequenceType1); + break; + + case 2: + setSequenceAndDraw(opponent, 4, kFightSequenceType1); + break; + + case 5: + setSequenceAndDraw(opponent, 6, kFightSequenceType1); + break; + } + + setSequenceAndDraw(opponent->opponent, 4, kFightSequenceType1); + + CALL_FUNCTION1(opponent, handleAction, kFightActionLost); + CALL_FUNCTION0(opponent->opponent, update); + CALL_FUNCTION0(opponent, update); + + getSound()->removeFromQueue(kEntityTables0); + + // Stop processing + return; + } + } + + updateOpponent(opponent); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/fight.h b/engines/lastexpress/game/fight.h new file mode 100644 index 0000000000..32d734d57c --- /dev/null +++ b/engines/lastexpress/game/fight.h @@ -0,0 +1,269 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_FIGHT_H +#define LASTEXPRESS_FIGHT_H + +/* + Fight structure + --------------- + uint32 {4} - player struct + uint32 {4} - opponent struct + uint32 {4} - hasLost flag + + byte {1} - isRunning + + Fight participant structure + --------------------------- + uint32 {4} - function pointer + uint32 {4} - pointer to fight structure + uint32 {4} - pointer to opponent (fight participant structure) + uint32 {4} - array of sequences + uint32 {4} - number of sequences + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint16 {2} - ?? + uint16 {2} - ?? - only for opponent structure + uint32 {4} - ?? - only for opponent structure + +*/ + +#include "lastexpress/shared.h" + +#include "lastexpress/eventhandler.h" + +#include "common/array.h" + +namespace LastExpress { + +class LastExpressEngine; +class Sequence; +class SequenceFrame; + +////////////////////////////////////////////////////////////////////////// +// TODO : objectify! +class Fight : public EventHandler { +public: + enum FightEndType { + kFightEndWin = 0, + kFightEndLost = 1, + kFightEndExit = 2 + }; + + Fight(LastExpressEngine *engine); + ~Fight(); + + FightEndType setup(FightType type); + + void eventMouse(const Common::Event &ev); + void eventTick(const Common::Event &ev); + + void setStopped(); + void resetState() { _state = 0; } + +private: + enum FightSequenceType { + kFightSequenceType0 = 0, + kFightSequenceType1 = 1, + kFightSequenceType2 = 2 + }; + + enum FightAction { + kFightAction1 = 1, + kFightAction2 = 2, + kFightAction3 = 3, + kFightAction4 = 4, + kFightAction5 = 5, + kFightAction101 = 101, + kFightActionResetFrame = 102, + kFightAction103 = 103, + kFightActionWin = 104, + kFightActionLost = 105, + kFightAction128 = 128, + kFightAction129 = 129, + kFightAction130 = 130, + kFightAction131 = 131, + kFightAction132 = 132 + }; + + struct Fighter { + Common::Functor2<Fighter *, FightAction, void> *handleAction; + Common::Functor1<Fighter *, void> *update; + Common::Functor2<Fighter const *, FightAction, bool> *canInteract; + Fighter* opponent; + Common::Array<Sequence *> sequences; + uint32 sequenceIndex; + Sequence *sequence; + SequenceFrame *frame; + uint32 frameIndex; + uint32 field_24; + FightAction action; + uint32 sequenceIndex2; + int32 countdown; // countdown before loosing ? + uint32 field_34; + + Fighter() { + handleAction = NULL; + update = NULL; + canInteract = NULL; + + opponent = NULL; + + sequenceIndex = 0; + sequence = NULL; + frame = NULL; + frameIndex = 0; + + field_24 = 0; + + action = kFightAction101; + sequenceIndex2 = 0; + + countdown = 1; + + field_34 = 0; + } + }; + + // Opponent struct + struct Opponent : Fighter { + int32 field_38; + + Opponent() : Fighter() { + field_38 = 0; + } + }; + + struct FightData { + Fighter *player; + Opponent *opponent; + int32 index; + + Sequence *sequences[20]; + Common::String names[20]; + + bool isRunning; + + FightData() { + player = new Fighter(); + opponent = new Opponent(); + + // Set opponents + player->opponent = opponent; + opponent->opponent = player; + + index = 0; + + isRunning = false; + } + }; + + LastExpressEngine* _engine; + FightData *_data; + FightEndType _endType; + int _state; + + bool _handleTimer; + + // Events + void handleTick(const Common::Event &ev, bool unknown); + + // State + void bailout(FightEndType type); + + + // Drawing + void setSequenceAndDraw(Fighter *fighter, uint32 sequenceIndex, FightSequenceType type) const; + void draw(Fighter *fighter) const; + + // Cleanup + void clearData(); + void clearSequences(Fighter *fighter) const; + + ////////////////////////////////////////////////////////////////////////// + // Loading + void loadData(FightType type); + + // Shared + void processFighter(Fighter *fighter); + + // Default functions + void handleAction(Fighter *fighter, FightAction action); + void update(Fighter *fighter); + bool canInteract(Fighter const *fighter, FightAction = (FightAction)0); + void updateOpponent(Fighter *fighter); + + // Milos + void loadMilosPlayer(); + void loadMilosOpponent(); + void handleActionMilos(Fighter *fighter, FightAction action); + void updateMilos(Fighter *fighter); + bool canInteractMilos(Fighter const *fighter, FightAction action); + void handleOpponentActionMilos(Fighter *fighter, FightAction action); + void updateOpponentMilos(Fighter *fighter); + + // Anna + void loadAnnaPlayer(); + void loadAnnaOpponent(); + void handleActionAnna(Fighter *fighter, FightAction action); + void updateOpponentAnna(Fighter *fighter); + + // Ivo + void loadIvoPlayer(); + void loadIvoOpponent(); + void handleActionIvo(Fighter *fighter, FightAction action); + void updateIvo(Fighter *fighter); + bool canInteractIvo(Fighter const *fighter, FightAction action); + void handleOpponentActionIvo(Fighter *fighter, FightAction action); + void updateOpponentIvo(Fighter *fighter); + + // Salko + void loadSalkoPlayer(); + void loadSalkoOpponent(); + void handleActionSalko(Fighter *fighter, FightAction action); + void updateSalko(Fighter *fighter); + bool canInteractSalko(Fighter const *fighter, FightAction action); + void handleOpponentActionSalko(Fighter *fighter, FightAction action); + void updateOpponentSalko(Fighter *fighter); + + // Vesna + void loadVesnaPlayer(); + void loadVesnaOpponent(); + void handleActionVesna(Fighter *fighter, FightAction action); + void updateVesna(Fighter *fighter); + bool canInteractVesna(Fighter const *fighter, FightAction action); + void handleOpponentActionVesna(Fighter *fighter, FightAction action); + void updateOpponentVesna(Fighter *fighter); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_FIGHT_H diff --git a/engines/lastexpress/game/inventory.cpp b/engines/lastexpress/game/inventory.cpp new file mode 100644 index 0000000000..37020604f6 --- /dev/null +++ b/engines/lastexpress/game/inventory.cpp @@ -0,0 +1,594 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/inventory.h" + +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/scene.h" +#include "lastexpress/data/snd.h" + +#include "lastexpress/game/logic.h" +#include "lastexpress/game/menu.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + + +#define drawItem(x, y, index, brightness) { Icon icon((CursorStyle)(index)); icon.setPosition(x, y); icon.setBrightness(brightness); _engine->getGraphicsManager()->draw(&icon, GraphicsManager::kBackgroundInventory); } + +namespace LastExpress { + +Inventory::Inventory(LastExpressEngine *engine) : _engine(engine), _selectedItem(kItemNone), _highlightedItem(kItemNone), _opened(false), _visible(false), + _showingHourGlass(false), _blinkingEgg(false), _blinkingTime(0), _blinkingInterval(_defaultBlinkingInterval), _blinkingBrightness(100), + _flagUseMagnifier(false), _flag1(false), _flag2(false), _flagEggHightlighted(false), _itemScene(NULL) { + + _inventoryRect = Common::Rect(0, 0, 32, 32); + _menuRect = Common::Rect(608, 448, 640, 480); + _selectedRect = Common::Rect(44, 0, 76, 32); + + init(); +} + +Inventory::~Inventory() { + _itemScene = NULL; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Inventory handling +////////////////////////////////////////////////////////////////////////// + +// Initialize inventory contents +void Inventory::init() { + // ID + _entries[kItemMatchBox].cursor = kCursorMatchBox; + _entries[kItemTelegram].cursor = kCursorTelegram; + _entries[kItemPassengerList].cursor = kCursorPassengerList; + _entries[kItemArticle].cursor = kCursorArticle; + _entries[kItemScarf].cursor = kCursorScarf; + _entries[kItemPaper].cursor = kCursorPaper; + _entries[kItemParchemin].cursor = kCursorParchemin; + _entries[kItemMatch].cursor = kCursorMatch; + _entries[kItemWhistle].cursor = kCursorWhistle; + _entries[kItemKey].cursor = kCursorKey; + _entries[kItemBomb].cursor = kCursorBomb; + _entries[kItemFirebird].cursor = kCursorFirebird; + _entries[kItemBriefcase].cursor = kCursorBriefcase; + _entries[kItemCorpse].cursor = kCursorCorpse; + + // Selectable + _entries[kItemMatchBox].isSelectable = true; + _entries[kItemMatch].isSelectable = true; + _entries[kItemTelegram].isSelectable = true; + _entries[kItemWhistle].isSelectable = true; + _entries[kItemKey].isSelectable = true; + _entries[kItemFirebird].isSelectable = true; + _entries[kItemBriefcase].isSelectable = true; + _entries[kItemCorpse].isSelectable = true; + _entries[kItemPassengerList].isSelectable = true; + + // Auto selection + _entries[kItem2].manualSelect = false; + _entries[kItem3].manualSelect = false; + _entries[kItem5].manualSelect = false; + _entries[kItem7].manualSelect = false; + _entries[kItem9].manualSelect = false; + _entries[kItem11].manualSelect = false; + _entries[kItemBeetle].manualSelect = false; + _entries[kItem17].manualSelect = false; + _entries[kItemFirebird].manualSelect = false; + _entries[kItemBriefcase].manualSelect = false; + _entries[kItemCorpse].manualSelect = false; + _entries[kItemGreenJacket].manualSelect = false; + _entries[kItem22].manualSelect = false; + + // Scene + _entries[kItemMatchBox].scene = kSceneMatchbox; + _entries[kItemTelegram].scene = kSceneTelegram; + _entries[kItemPassengerList].scene = kScenePassengerList; + _entries[kItemScarf].scene = kSceneScarf; + _entries[kItemParchemin].scene = kSceneParchemin; + _entries[kItemArticle].scene = kSceneArticle; + _entries[kItemPaper].scene = kScenePaper; + _entries[kItemFirebird].scene = kSceneFirebird; + _entries[kItemBriefcase].scene = kSceneBriefcase; + + // Has item + _entries[kItemTelegram].isPresent = true; + _entries[kItemArticle].isPresent = true; + + _selectedItem = kItemNone; +} + +// FIXME we need to draw cursors with full background opacity so that whatever is in the background is erased +// this saved us clearing some part of the background when switching between states + +// TODO if we draw inventory objects on screen, we need to load a new scene. +// Signal that the inventory has taken over the screen and stop processing mouse events after we have been called +bool Inventory::handleMouseEvent(const Common::Event &ev) { + + // Do not show inventory when on the menu screen + if (getMenu()->isShown() || !_visible) + return false; + + // Flag to know whether to restore the current cursor or not + bool insideInventory = false; + + // Egg (menu) + if (_menuRect.contains(ev.mouse)) { + insideInventory = true; + _engine->getCursor()->setStyle(kCursorNormal); + + // If clicked, show the menu + if (ev.type == Common::EVENT_LBUTTONUP) { + getSound()->playSound(kEntityPlayer, "LIB039"); + getMenu()->show(false, kSavegameTypeIndex, 0); + + // TODO can we return directly or do we need to make sure the state will be "valid" when we come back from the menu + return true; + } else { + // Highlight if needed + if (_highlightedItem != getMenu()->getGameId() + 39) { + _highlightedItem = (InventoryItem)(getMenu()->getGameId() + 39); + drawItem(608, 448, _highlightedItem, 100) + + askForRedraw(); + } + } + } else { + // remove highlight if needed + if (_highlightedItem == getMenu()->getGameId() + 39) { + drawItem(608, 448, _highlightedItem, 50) + _highlightedItem = kItemNone; + askForRedraw(); + } + } + + // Portrait (inventory) + if (_inventoryRect.contains(ev.mouse)) { + insideInventory = true; + _engine->getCursor()->setStyle(kCursorNormal); + + // If clicked, show pressed state and display inventory + if (ev.type == Common::EVENT_LBUTTONUP) { + open(); + } else { + // Highlight if needed + if (_highlightedItem != (InventoryItem)getProgress().portrait && !_opened) { + _highlightedItem = (InventoryItem)getProgress().portrait; + drawItem(0, 0, getProgress().portrait, 100) + + askForRedraw(); + } + } + } else { + // remove highlight if needed + if (_highlightedItem == (InventoryItem)getProgress().portrait && !_opened) { + drawItem(0, 0, getProgress().portrait, 50) + _highlightedItem = kItemNone; + askForRedraw(); + } + } + + // If the inventory is open, check all items rect to see if we need to highlight one / handle click + if (_opened) { + + // Always show normal cursor when the inventory is opened + insideInventory = true; + _engine->getCursor()->setStyle(kCursorNormal); + + bool selected = false; + + // Iterate over items + int16 y = 44; + for (int i = 1; i < 32; i++) { + if (!hasItem((InventoryItem)i)) + continue; + + if (Common::Rect(0, y, 32, 32 + y).contains(ev.mouse)) { + + // If released with an item highlighted, show this item + if (ev.type == Common::EVENT_LBUTTONUP) { + if (_entries[i].isSelectable) { + selected = true; + _selectedItem = (InventoryItem)i; + drawItem(44, 0, get(_selectedItem)->cursor, 100) + } + + examine((InventoryItem)i); + break; + } else { + if (_highlightedItem != i) { + drawItem(0, y, _entries[i].cursor, 100) + _highlightedItem = (InventoryItem)i; + askForRedraw(); + } + } + } else { + // Remove highlight if necessary + if (_highlightedItem == i) { + drawItem(0, y, _entries[i].cursor, 50) + _highlightedItem = kItemNone; + askForRedraw(); + } + } + + y += 40; + } + + // Right button is released: we need to close the inventory + if (ev.type == Common::EVENT_LBUTTONUP) { + + // Not on a selectable item: unselect the current item + if (!selected) + unselectItem(); + + close(); + } + } + + // Selected item + if (_selectedItem != kItemNone && _selectedRect.contains(ev.mouse)) { + insideInventory = true; + + // Show magnifier icon + _engine->getCursor()->setStyle(kCursorMagnifier); + + if (ev.type == Common::EVENT_LBUTTONUP) { + examine((InventoryItem)_selectedItem); + } + } + + // If the egg is blinking, refresh + if (_blinkingEgg) + drawEgg(); + + // Restore cursor + //if (!insideInventory) + // _engine->getCursor()->setStyle(getLogic()->getCursorStyle()); + + return insideInventory; +} + +////////////////////////////////////////////////////////////////////////// +// UI +////////////////////////////////////////////////////////////////////////// +void Inventory::show() { + clearBg(GraphicsManager::kBackgroundInventory); + askForRedraw(); + + // Show portrait (first draw, cannot be highlighted) + drawItem(0, 0, getProgress().portrait, 50) + + // Show selected item + if (_selectedItem != kItemNone) + drawItem(44, 0, _selectedItem, 100) + + drawEgg(); +} + +void Inventory::setPortrait(InventoryItem item) const { + getProgress().portrait = item; + drawItem(0, 0, getProgress().portrait, 50); +} + +void Inventory::blinkEgg(bool enabled) { + _blinkingEgg = enabled; + + // Reset state + _showingHourGlass = false; + + // Show egg at full brightness for first step if blinking + if (_blinkingEgg) + drawItem(608, 448, getMenu()->getGameId() + 39, _blinkingBrightness) + else { + // Reset values + _blinkingBrightness = 100; + _blinkingInterval = _defaultBlinkingInterval; + drawItem(608, 448, getMenu()->getGameId() + 39, 50) // normal egg state + } + + askForRedraw(); +} + +void Inventory::showHourGlass() const{ + if (!getFlags()->flag_5) { + drawItem(608, 448, kCursorHourGlass, 100); + } + + askForRedraw(); + + getFlags()->shouldDrawEggOrHourGlass = true; +} + +////////////////////////////////////////////////////////////////////////// +// Items +////////////////////////////////////////////////////////////////////////// +Inventory::InventoryEntry *Inventory::get(InventoryItem item) { + if (item >= kPortraitOriginal) + error("Inventory::getEntry: Invalid inventory item!"); + + return &_entries[item]; +} + +void Inventory::addItem(InventoryItem item) { + if (item >= kPortraitOriginal) + return; + + get(item)->isPresent = true; + get(item)->location = kObjectLocationNone; + + // Auto-select item if necessary + if (get(item)->cursor && !get(item)->manualSelect) { + _selectedItem = (InventoryItem)get(item)->cursor; + drawItem(44, 0, _selectedItem, 100) + askForRedraw(); + } +} + +void Inventory::removeItem(InventoryItem item, ObjectLocation newLocation) { + if (item >= kPortraitOriginal) + return; + + get(item)->isPresent = false; + get(item)->location = newLocation; + + if (get(item)->cursor == (CursorStyle)_selectedItem) { + _selectedItem = kItemNone; + _engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(44, 0, 44 + 32, 32)); + askForRedraw(); + } +} + +bool Inventory::hasItem(InventoryItem item) { + if (get(item)->isPresent && item < kPortraitOriginal) + return true; + + return false; +} + +void Inventory::selectItem(InventoryItem item) { + _selectedItem = item; + + drawItem(44, 0, get(_selectedItem)->cursor, 100) + askForRedraw(); +} + +void Inventory::unselectItem() { + _selectedItem = kItemNone; + + _engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(44, 0, 44 + 32, 32)); + askForRedraw(); +} + +void Inventory::setLocationAndProcess(InventoryItem item, ObjectLocation location) { + if (item >= kPortraitOriginal) + return; + + if (get(item)->location == location) + return; + + get(item)->location = location; + + if (isItemSceneParameter(item) && !getFlags()->flag_0) + getScenes()->processScene(); +} + +////////////////////////////////////////////////////////////////////////// +// Serializable +////////////////////////////////////////////////////////////////////////// +void Inventory::saveLoadWithSerializer(Common::Serializer &) { + error("Inventory::saveLoadWithSerializer: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// toString +////////////////////////////////////////////////////////////////////////// +Common::String Inventory::toString() { + Common::String ret = ""; + + for (int i = 0; i < kPortraitOriginal; i++) + ret += Common::String::printf("%d : %s\n", i, _entries[i].toString().c_str()); + + return ret; +} + +////////////////////////////////////////////////////////////////////////// +// Private methods +////////////////////////////////////////////////////////////////////////// +InventoryItem Inventory::getFirstExaminableItem() const { + + int index = 0; + InventoryEntry entry = _entries[index]; + while (!entry.isPresent || !entry.cursor || entry.manualSelect) { + index++; + entry = _entries[index]; + + if (index >= kPortraitOriginal) + return kItemNone; + } + + return (InventoryItem)index; +} + +bool Inventory::isItemSceneParameter(InventoryItem item) const { + Scene *scene = getScenes()->get(getState()->scene); + + switch(scene->type) { + default: + return false; + + case Scene::kTypeItem: + if (scene->param1 == item) + return true; + break; + + case Scene::kTypeItem2: + if (scene->param1 == item || scene->param2 == item) + return true; + break; + + case Scene::kTypeObjectItem: + if (scene->param2 == item) + return true; + break; + + case Scene::kTypeItem3: + if (scene->param1 == item || scene->param2 == item || scene->param3 == item) + return true; + break; + + case Scene::kTypeCompartmentsItem: + if (scene->param2 == item) + return true; + break; + } + + return false; +} + +// Examine an inventory item +void Inventory::examine(InventoryItem item) { + SceneIndex index = get(item)->scene; + if (!index) + return; + + /*if (!getState()->sceneUseBackup || + (getState()->sceneBackup2 && getFirstExaminableItem() == _selectedItem)) + flag = 1;*/ + + if (!getState()->sceneUseBackup) { + getState()->sceneBackup = getState()->scene; + getState()->sceneUseBackup = true; + + getScenes()->loadScene(index); + } else { + + if (!getState()->sceneBackup2) + return; + + if (getFirstExaminableItem() == _selectedItem) { + index = getState()->sceneBackup2; + getState()->sceneBackup2 = kSceneNone; + getScenes()->loadScene(index); + } + } +} + +// FIXME: see different callers and adjust +// - draw with different brightness if mousing over +void Inventory::drawEgg() const { + if (!getFlags()->flag_5) + drawItem(608, 448, getMenu()->getGameId() + 39, 50) + + getFlags()->shouldDrawEggOrHourGlass = false; +} + +// Blinking egg: we need to blink the egg for delta time, with the blinking getting faster until it's always lit. +void Inventory::drawBlinkingEgg() { + + warning("Inventory::drawEgg - blinking not implemented!"); + + //// TODO show egg (with or without mouseover) + + //// Play timer sound + //if (getGlobalTimer() < 90) { + // if (getGlobalTimer() + ticks >= 90) + // getSound()->playSoundWithSubtitles("TIMER.SND", 50331664, kEntityPlayer); + + // if (getSound()->isBuffered("TIMER")) + // setGlobalTimer(0); + //} + + //// Restore egg to standard brightness + //if (!getGlobalTimer()) { + // + //} + + + //drawItem(608, 448, getMenu()->getGameId() + 39, _blinkingBrightness) + + //// TODO if delta time > _blinkingInterval, update egg & ask for redraw then adjust blinking time and remaining time + // + + //// Reset values and stop blinking + //if (_blinkingTime == 0) + // blinkEgg(false); + + askForRedraw(); +} + +// Close inventory: clear items and reset icon +void Inventory::open() { + _opened = true; + + // Show selected state + drawItem(0, 0, getProgress().portrait + 1, 100) + + int16 y = 44; + + // Iterate over items + for (uint i = 1; i < 32; i++) { + if (_entries[i].isPresent) { + drawItem(0, y, _entries[i].cursor, 50) + y += 40; + } + } + + askForRedraw(); +} + +// Close inventory: clear items and reset icon +void Inventory::close() { + _opened = false; + + // Fallback to unselected state + drawItem(0, 0, getProgress().portrait, 100) + + // Erase rectangle for all inventory items + int count = 0; + for (uint i = 1; i < 32; i++) { + if (_entries[i].isPresent) { + count++; + } + } + + _engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(0, 44, 32, (int16)(44 + 44 * count))); + + askForRedraw(); +} + +Common::Rect Inventory::getItemRect(int16 index) const{ + return Common::Rect(0, (int16)((32 + 12) * (index + 1)), 32, (int16)((32 + 12) * (index + 2))); // space between items = 12px +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/inventory.h b/engines/lastexpress/game/inventory.h new file mode 100644 index 0000000000..6babe3e60e --- /dev/null +++ b/engines/lastexpress/game/inventory.h @@ -0,0 +1,169 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_INVENTORY_H +#define LASTEXPRESS_INVENTORY_H + +/* + Inventory entry (32 entries) + ---------------------------- + + byte {1} - Item ID (set to 0 for "undefined" items) + byte {1} - Scene ID + byte {1} - ?? + byte {1} - Selectable (1 if item is selectable, 0 otherwise) + byte {1} - Is item in inventory (set to 1 for telegram and article) + byte {1} - Auto selection (1 for no auto selection, 0 otherwise) + byte {1} - Location + +*/ + +#include "lastexpress/shared.h" + +#include "lastexpress/eventhandler.h" + +#include "common/events.h" +#include "common/serializer.h" + +namespace LastExpress { + +class LastExpressEngine; +class Scene; + +class Inventory : Common::Serializable, public EventHandler { +public: + + // Entry + struct InventoryEntry { + CursorStyle cursor; + SceneIndex scene; + byte field_2; + bool isSelectable; + bool isPresent; + bool manualSelect; + ObjectLocation location; + + InventoryEntry() { + cursor = kCursorNormal; + scene = kSceneNone; + field_2 = 0; + isSelectable = false; + isPresent = false; + manualSelect = true; + location = kObjectLocationNone; + } + + Common::String toString() { + return Common::String::printf("{ %d - %d - %d - %d - %d - %d - %d }", cursor, scene, field_2, isSelectable, isPresent, manualSelect, location); + } + }; + + Inventory(LastExpressEngine *engine); + ~Inventory(); + + // Inventory contents + void addItem(InventoryItem item); + void removeItem(InventoryItem item, ObjectLocation newLocation = kObjectLocationNone); + bool hasItem(InventoryItem item); + void selectItem(InventoryItem item); + void unselectItem(); + InventoryItem getSelectedItem() { return _selectedItem; } + + InventoryEntry *get(InventoryItem item); + InventoryEntry *getSelectedEntry() { return get(_selectedItem); } + + InventoryItem getFirstExaminableItem() const; + void setLocationAndProcess(InventoryItem item, ObjectLocation location); + + // UI Control + void show(); + void blinkEgg(bool enabled); + void showHourGlass() const; + void setPortrait(InventoryItem item) const; + void drawEgg() const; + void drawBlinkingEgg(); + + // Handle inventory UI events. + bool handleMouseEvent(const Common::Event &ev); + + // State + bool isMagnifierInUse() { return _flagUseMagnifier; } + bool isFlag1() { return _flag1; } + bool isFlag2() { return _flag2; } + bool isEggHighlighted() { return _flagEggHightlighted; } + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString(); + +private: + static const uint32 _defaultBlinkingInterval = 250; ///< Default blinking interval in ms + + LastExpressEngine *_engine; + + InventoryEntry _entries[32]; + InventoryItem _selectedItem; + InventoryItem _highlightedItem; + bool _opened; + bool _visible; + + bool _showingHourGlass; + bool _blinkingEgg; + uint32 _blinkingTime; + uint32 _blinkingInterval; + uint32 _blinkingBrightness; + + // Flags + bool _flagUseMagnifier; + bool _flag1; + bool _flag2; + bool _flagEggHightlighted; + + Scene *_itemScene; + + // Important rects + Common::Rect _inventoryRect; + Common::Rect _menuRect; + Common::Rect _selectedRect; + + void init(); + + void open(); + void close(); + void examine(InventoryItem item); + Common::Rect getItemRect(int16 index) const; + + bool isItemSceneParameter(InventoryItem item) const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_INVENTORY_H diff --git a/engines/lastexpress/game/logic.cpp b/engines/lastexpress/game/logic.cpp new file mode 100644 index 0000000000..4aedd04a0c --- /dev/null +++ b/engines/lastexpress/game/logic.cpp @@ -0,0 +1,577 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#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" + +// Game +#include "lastexpress/game/action.h" +#include "lastexpress/game/beetle.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/fight.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/menu.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.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() { + delete _action; + delete _beetle; + delete _fight; + delete _entities; + delete _saveload; + delete _state; + + // Zero-out passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Event Handling +////////////////////////////////////////////////////////////////////////// +#define REDRAW_CURSOR() { \ + if (getInventory()->isMagnifierInUse()) \ + _engine->getCursor()->setStyle(kCursorMagnifier); \ + if (getInventory()->isFlag1() \ + || getInventory()->isFlag2() \ + || getInventory()->isEggHighlighted()) \ + _engine->getCursor()->setStyle(kCursorNormal); \ + return; \ +} + +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_LBUTTONUP) { + + if (getFlags()->frameInterval) + _ignoreFrameInterval = false; + + getFlags()->frameInterval = false; + } + + if (getFlags()->flag_0) { + if (ev.type == Common::EVENT_LBUTTONUP || ev.type == Common::EVENT_RBUTTONUP) { + 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()->isFlag1() + && !getInventory()->isFlag2() + && !getInventory()->isEggHighlighted() + && !getInventory()->isMagnifierInUse()) { + + // Update cursor + _engine->getCursor()->setStyle(getInventory()->get(kItemWhistle)->cursor); + + // Check if clicked + if (ev.type == Common::EVENT_LBUTTONUP && !getSound()->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(); + } + + REDRAW_CURSOR() + } + + // Handle match case + if (getInventory()->getSelectedItem() == kItemMatch + && (getEntities()->isPlayerInCar(kCarGreenSleeping) || getEntities()->isPlayerInCar(kCarRedSleeping)) + && getProgress().jacket == kJacketGreen + && !getInventory()->isFlag1() + && !getInventory()->isFlag2() + && !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(); + } + + REDRAW_CURSOR() + } + + // Handle entity item case + EntityIndex entityIndex = getEntities()->canInteractWith(ev.mouse); + if (entityIndex + && !getInventory()->isFlag1() + && !getInventory()->isFlag2() + && !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 (hotspotHandled || getInventory()->isFlag1() || getInventory()->isFlag2() || getInventory()->isEggHighlighted()) + return; + + // Magnifier in use + if (getInventory()->isMagnifierInUse()) { + _engine->getCursor()->setStyle(kCursorMagnifier); + + if (getInventory()->isFlag1() + || getInventory()->isFlag2() + || getInventory()->isEggHighlighted()) + _engine->getCursor()->setStyle(kCursorNormal); + + return; + } + + // Check hotspots + int location = 0; + SceneHotspot *hotspot = NULL; + Scene *scene = getScenes()->get(getState()->scene); + + for (Common::Array<SceneHotspot *>::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(); + + ////////////////////////////////////////////////////////////////////////// + // Adjust time and save game if needed + if (getFlags()->isGameRunning) { + getState()->timeTicks += ticks; + getState()->time += 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 +////////////////////////////////////////////////////////////////////////// + +// Handle game over +void Logic::gameOver(SavegameType type, uint32 value, SceneIndex sceneIndex, bool showScene) const { + + getSound()->processEntries(); + getEntities()->reset(); + getFlags()->isGameRunning = false; + getSavePoints()->reset(); + getFlags()->flag_entities_0 = true; + + if (showScene) { + + getSound()->processEntry(SoundManager::kSoundType11); + + if (sceneIndex && !getFlags()->mouseRightClick) { + getScenes()->loadScene(sceneIndex); + + while (getSound()->isBuffered(kEntityTables4)) { + if (getFlags()->mouseRightClick) + break; + + getSound()->updateQueue(); + } + } + } + + // Show Menu + getMenu()->show(false, type, value); +} + +void Logic::switchChapter() const { + getSound()->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)->isPresent = 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 { + getSound()->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()->isFlag1() + || getInventory()->isFlag2() + || getInventory()->isEggHighlighted() + || getInventory()->isMagnifierInUse()) { + + if (getInventory()->getSelectedItem() != kItemMatch + || (!getEntities()->isPlayerInCar(kCarGreenSleeping) && !getEntities()->isPlayerInCar(kCarRedSleeping)) + || getProgress().jacket != kJacketGreen + || getInventory()->isFlag1() + || getInventory()->isFlag2() + || 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()->isFlag1() + && !getInventory()->isFlag2() + && !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()->isFlag1() + && !getInventory()->isFlag2() + && !getInventory()->isEggHighlighted() + && !getInventory()->isMagnifierInUse()) { + int location = 0; + SceneHotspot *hotspot = NULL; + Scene *scene = getScenes()->get(getState()->scene); + + // Check all hotspots + for (Common::Array<SceneHotspot *>::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()->isFlag1() || getInventory()->isFlag2() || getInventory()->isEggHighlighted()) + style = kCursorNormal; + + _engine->getCursor()->setStyle(style); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/logic.h b/engines/lastexpress/game/logic.h new file mode 100644 index 0000000000..7e01655b68 --- /dev/null +++ b/engines/lastexpress/game/logic.h @@ -0,0 +1,91 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_LOGIC_H +#define LASTEXPRESS_LOGIC_H + +#include "lastexpress/shared.h" + +#include "lastexpress/game/entities.h" + +#include "lastexpress/eventhandler.h" + +#include "common/events.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Action; +class Beetle; +class Debugger; +class Entities; +class Fight; +class SaveLoad; +class State; + +class Logic : public EventHandler { +public: + Logic(LastExpressEngine *engine); + ~Logic(); + + void eventMouse(const Common::Event &ev); + void eventTick(const Common::Event &ev); + + void gameOver(SavegameType type, uint32 value, SceneIndex sceneIndex, bool showScene) const; + void playFinalSequence() const; + void updateCursor(bool redraw = true) const; + + Action *getGameAction() { return _action; } + Beetle *getGameBeetle() { return _beetle; } + Entities *getGameEntities() { return _entities; } + Fight *getGameFight() { return _fight; } + SaveLoad *getGameSaveLoad() { return _saveload; } + State *getGameState() { return _state; } + +private: + LastExpressEngine *_engine; + + Action *_action; ///< Actions + Beetle *_beetle; ///< Beetle catching + Entities *_entities; ///< Entities + Fight *_fight; ///< Fight handling + SaveLoad *_saveload; ///< Save & loading + State *_state; ///< Game state + + void switchChapter() const; + void showCredits() const; + + // Flags & Members + bool _flagActionPerformed; + bool _ignoreFrameInterval; + int _ticksSinceLastSavegame; + + friend class Debugger; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_LOGIC_H diff --git a/engines/lastexpress/game/menu.cpp b/engines/lastexpress/game/menu.cpp new file mode 100644 index 0000000000..19678a3255 --- /dev/null +++ b/engines/lastexpress/game/menu.cpp @@ -0,0 +1,1564 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/menu.h" + +// Data +#include "lastexpress/data/animation.h" +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/snd.h" +#include "lastexpress/data/scene.h" + +#include "lastexpress/game/fight.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +#define getNextGameId() (GameId)((_gameId + 1) % 6) + +namespace LastExpress { + +// Bottom-left buttons (quit.seq) +enum StartMenuButtons { + kButtonVolumeDownPushed, + kButtonVolumeDown, + kButtonVolume, + kButtonVolumeUp, + kButtonVolumeUpPushed, + kButtonBrightnessDownPushed, // 5 + kButtonBrightnessDown, + kButtonBrightness, + kButtonBrightnessUp, + kButtonBrightnessUpPushed, + kButtonQuit, // 10 + kButtonQuitPushed +}; + +// Egg buttons (buttns.seq) +enum StartMenuEggButtons { + kButtonShield, + kButtonRewind, + kButtonRewindPushed, + kButtonForward, + kButtonForwardPushed, + kButtonCredits, // 5 + kButtonCreditsPushed, + kButtonContinue +}; + +// Tooltips sequence (helpnewr.seq) +enum StartMenuTooltips { + kTooltipInsertCd1, + kTooltipInsertCd2, + kTooltipInsertCd3, + kTooltipContinueGame, + kTooltipReplayGame, + kTooltipContinueRewoundGame, // 5 + kTooltipViewGameEnding, + kTooltipStartAnotherGame, + kTooltipVolumeUp, + kTooltipVolumeDown, + kTooltipBrightnessUp, // 10 + kTooltipBrightnessDown, + kTooltipQuit, + kTooltipRewindParis, + kTooltipForwardStrasbourg, + kTooltipRewindStrasbourg, // 15 + kTooltipRewindMunich, + kTooltipForwardMunich, + kTooltipForwardVienna, + kTooltipRewindVienna, + kTooltipRewindBudapest, // 20 + kTooltipForwardBudapest, + kTooltipForwardBelgrade, + kTooltipRewindBelgrade, + kTooltipForwardConstantinople, + kTooltipSwitchBlueGame, // 25 + kTooltipSwitchRedGame, + kTooltipSwitchGoldGame, + kTooltipSwitchGreenGame, + kTooltipSwitchTealGame, + kTooltipSwitchPurpleGame, // 30 + kTooltipPlayNewGame, + kTooltipCredits, + kTooltipFastForward, + kTooltipRewind +}; + +////////////////////////////////////////////////////////////////////////// +// DATA +////////////////////////////////////////////////////////////////////////// + +// Information about the cities on the train line +static const struct { + uint8 frame; + TimeValue time; +} _trainCities[31] = { + {0, kTimeCityParis}, + {9, kTimeCityEpernay}, + {11, kTimeCityChalons}, + {16, kTimeCityBarLeDuc}, + {21, kTimeCityNancy}, + {25, kTimeCityLuneville}, + {35, kTimeCityAvricourt}, + {37, kTimeCityDeutschAvricourt}, + {40, kTimeCityStrasbourg}, + {53, kTimeCityBadenOos}, + {56, kTimeCityKarlsruhe}, + {60, kTimeCityStuttgart}, + {63, kTimeCityGeislingen}, + {66, kTimeCityUlm}, + {68, kTimeCityAugsburg}, + {73, kTimeCityMunich}, + {84, kTimeCitySalzbourg}, + {89, kTimeCityAttnangPuchheim}, + {97, kTimeCityWels}, + {100, kTimeCityLinz}, + {104, kTimeCityAmstetten}, + {111, kTimeCityVienna}, + {120, kTimeCityPoszony}, + {124, kTimeCityGalanta}, + {132, kTimeCityBudapest}, + {148, kTimeCityBelgrade}, + /* Line 1 ends at 150 - line 2 begins at 0 */ + {157, kTimeCityNish}, + {165, kTimeCityTzaribrod}, + {174, kTimeCitySofia}, + {198, kTimeCityAdrianople}, + {210, kTimeCityConstantinople}}; + +static const struct { + TimeValue time; + uint index; + StartMenuTooltips rewind; + StartMenuTooltips forward; +} _cityButtonsInfo[7] = { + {kTimeCityParis, 64, kTooltipRewindParis, kTooltipRewindParis}, + {kTimeCityStrasbourg, 128, kTooltipRewindStrasbourg, kTooltipForwardStrasbourg}, + {kTimeCityMunich, 129, kTooltipRewindMunich, kTooltipForwardMunich}, + {kTimeCityVienna, 130, kTooltipRewindVienna, kTooltipForwardVienna}, + {kTimeCityBudapest, 131, kTooltipRewindBudapest, kTooltipForwardBudapest}, + {kTimeCityBelgrade, 132, kTooltipRewindBelgrade, kTooltipForwardBelgrade}, + {kTimeCityConstantinople, 192, kTooltipForwardConstantinople, kTooltipForwardConstantinople} +}; + +////////////////////////////////////////////////////////////////////////// +// Clock +////////////////////////////////////////////////////////////////////////// +class Clock { +public: + Clock(LastExpressEngine *engine); + ~Clock(); + + void draw(uint32 time); + void clear(); + +private: + LastExpressEngine *_engine; + + // Frames + SequenceFrame *_frameMinutes; + SequenceFrame *_frameHour; + SequenceFrame *_frameSun; + SequenceFrame *_frameDate; +}; + +Clock::Clock(LastExpressEngine *engine) : _engine(engine), _frameMinutes(NULL), _frameHour(NULL), _frameSun(NULL), _frameDate(NULL) { + _frameMinutes = new SequenceFrame(loadSequence("eggmin.seq"), 0, true); + _frameHour = new SequenceFrame(loadSequence("egghour.seq"), 0, true); + _frameSun = new SequenceFrame(loadSequence("sun.seq"), 0, true); + _frameDate = new SequenceFrame(loadSequence("datenew.seq"), 0, true); +} + +Clock::~Clock() { + delete _frameMinutes; + delete _frameHour; + delete _frameSun; + delete _frameDate; + + // Zero passed pointers + _engine = NULL; +} + +void Clock::clear() { + getScenes()->removeAndRedraw(&_frameMinutes, false); + getScenes()->removeAndRedraw(&_frameHour, false); + getScenes()->removeAndRedraw(&_frameSun, false); + getScenes()->removeAndRedraw(&_frameDate, false); +} + +void Clock::draw(uint32 time) { + assert(time >= kTimeCityParis && time <= kTimeCityConstantinople); + + // Check that sequences have been loaded + if (!_frameMinutes || !_frameHour || !_frameSun || !_frameDate) + error("Clock::process: clock sequences have not been loaded correctly!"); + + // Clear existing frames + clear(); + + // Game starts at: 1037700 = 7:13 p.m. on July 24, 1914 + // Game ends at: 4941000 = 7:30 p.m. on July 26, 1914 + // Game lasts for: 3903300 = 2 days + 17 mins = 2897 mins + + // 15 = 1 second + // 15 * 60 = 900 = 1 minute + // 900 * 60 = 54000 = 1 hour + // 54000 * 24 = 1296000 = 1 day + + // Calculate each sequence index from the current time + uint8 hour = (uint8)((time % 1296000) / 54000); + uint8 minute = (uint8)((time % 54000) / 900); + uint32 index_date = 18 * time / 1296000; + if (hour == 23) + index_date += 18 * minute / 60; + + // Set sequences frames + _frameMinutes->setFrame(minute); + _frameHour->setFrame((5 * hour + minute / 12) % 60); + _frameSun->setFrame((5 * hour + minute / 12) % 120); + _frameDate->setFrame((uint16)index_date); + + // Adjust z-order and queue + _frameMinutes->getInfo()->location = 1; + _frameHour->getInfo()->location = 1; + _frameSun->getInfo()->location = 1; + _frameDate->getInfo()->location = 1; + + getScenes()->addToQueue(_frameMinutes); + getScenes()->addToQueue(_frameHour); + getScenes()->addToQueue(_frameSun); + getScenes()->addToQueue(_frameDate); +} + +////////////////////////////////////////////////////////////////////////// +// TrainLine +////////////////////////////////////////////////////////////////////////// +class TrainLine { +public: + TrainLine(LastExpressEngine *engine); + ~TrainLine(); + + void draw(uint32 time); + void clear(); + +private: + LastExpressEngine *_engine; + + // Frames + SequenceFrame *_frameLine1; + SequenceFrame *_frameLine2; +}; + +TrainLine::TrainLine(LastExpressEngine *engine) : _engine(engine), _frameLine1(NULL), _frameLine2(NULL) { + _frameLine1 = new SequenceFrame(loadSequence("line1.seq"), 0, true); + _frameLine2 = new SequenceFrame(loadSequence("line2.seq"), 0, true); +} + +TrainLine::~TrainLine() { + delete _frameLine1; + delete _frameLine2; + + // Zero passed pointers + _engine = NULL; +} + +void TrainLine::clear() { + getScenes()->removeAndRedraw(&_frameLine1, false); + getScenes()->removeAndRedraw(&_frameLine2, false); +} + +// Draw the train line at the time +// line1: 150 frames (=> Belgrade) +// line2: 61 frames (=> Constantinople) +void TrainLine::draw(uint32 time) { + assert(time >= kTimeCityParis && time <= kTimeCityConstantinople); + + // Check that sequences have been loaded + if (!_frameLine1 || !_frameLine2) + error("TrainLine::process: Line sequences have not been loaded correctly!"); + + // Clear existing frames + clear(); + + // Get the index of the last city the train has visited + uint index = 0; + for (uint i = 0; i < ARRAYSIZE(_trainCities); i++) + if ((uint32)_trainCities[i].time <= time) + index = i; + + uint16 frame; + if (time > (uint32)_trainCities[index].time) { + // Interpolate linearly to use a frame between the cities + uint8 diffFrames = _trainCities[index + 1].frame - _trainCities[index].frame; + uint diffTimeCities = (uint)(_trainCities[index + 1].time - _trainCities[index].time); + uint traveledTime = (time - (uint)_trainCities[index].time); + frame = (uint16)(_trainCities[index].frame + (traveledTime * diffFrames) / diffTimeCities); + } else { + // Exactly on the city + frame = _trainCities[index].frame; + } + + // Set frame, z-order and queue + if (frame < 150) { + _frameLine1->setFrame(frame); + + _frameLine1->getInfo()->location = 1; + getScenes()->addToQueue(_frameLine1); + } else { + // We passed Belgrade + _frameLine1->setFrame(149); + _frameLine2->setFrame(frame - 150); + + _frameLine1->getInfo()->location = 1; + _frameLine2->getInfo()->location = 1; + + getScenes()->addToQueue(_frameLine1); + getScenes()->addToQueue(_frameLine2); + } +} + + +////////////////////////////////////////////////////////////////////////// +// Menu +////////////////////////////////////////////////////////////////////////// +Menu::Menu(LastExpressEngine *engine) : _engine(engine), + _seqTooltips(NULL), _seqEggButtons(NULL), _seqButtons(NULL), _seqAcorn(NULL), _seqCity1(NULL), _seqCity2(NULL), _seqCity3(NULL), _seqCredits(NULL), + _gameId(kGameBlue), _hasShownStartScreen(false), _hasShownIntro(false), + _isShowingCredits(false), _isGameStarted(false), _isShowingMenu(false), + _creditsSequenceIndex(0), _checkHotspotsTicks(15), _mouseFlags(Common::EVENT_INVALID), _lastHotspot(NULL), + _currentIndex(0), _currentTime(0), _lowerTime(0), _index(0), _index2(0), _time(0), _delta(0), _handleTimeDelta(false) { + + _clock = new Clock(_engine); + _trainLine = new TrainLine(_engine); +} + +Menu::~Menu() { + delete _clock; + delete _trainLine; + + SAFE_DELETE(_seqTooltips); + SAFE_DELETE(_seqEggButtons); + SAFE_DELETE(_seqButtons); + SAFE_DELETE(_seqAcorn); + SAFE_DELETE(_seqCity1); + SAFE_DELETE(_seqCity2); + SAFE_DELETE(_seqCity3); + SAFE_DELETE(_seqCredits); + + _lastHotspot = NULL; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Setup +void Menu::setup() { + + // Clear drawing queue + getScenes()->removeAndRedraw(&_frames[kOverlayAcorn], false); + SAFE_DELETE(_seqAcorn); + + // Load Menu scene + // + 1 = normal menu with open egg / clock + // + 2 = shield menu, when no savegame exists (no game has been started) + _isGameStarted = _lowerTime >= kTimeStartGame; + getScenes()->loadScene((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2)); + getFlags()->shouldRedraw = true; + getLogic()->updateCursor(); + + ////////////////////////////////////////////////////////////////////////// + // Load Acorn sequence + _seqAcorn = loadSequence(getAcornSequenceName(_isGameStarted ? getNextGameId() : kGameBlue)); + + ////////////////////////////////////////////////////////////////////////// + // Check if we loaded sequences before + if (_seqTooltips && _seqTooltips->count() > 0) + return; + + // Load all static data + _seqTooltips = loadSequence("helpnewr.seq"); + _seqEggButtons = loadSequence("buttns.seq"); + _seqButtons = loadSequence("quit.seq"); + _seqCity1 = loadSequence("jlinetl.seq"); + _seqCity2 = loadSequence("jlinecen.seq"); + _seqCity3 = loadSequence("jlinebr.seq"); + _seqCredits = loadSequence("credits.seq"); + + _frames[kOverlayTooltip] = new SequenceFrame(_seqTooltips); + _frames[kOverlayEggButtons] = new SequenceFrame(_seqEggButtons); + _frames[kOverlayButtons] = new SequenceFrame(_seqButtons); + _frames[kOverlayAcorn] = new SequenceFrame(_seqAcorn); + _frames[kOverlayCity1] = new SequenceFrame(_seqCity1); + _frames[kOverlayCity2] = new SequenceFrame(_seqCity2); + _frames[kOverlayCity3] = new SequenceFrame(_seqCity3); + _frames[kOverlayCredits] = new SequenceFrame(_seqCredits); +} + +////////////////////////////////////////////////////////////////////////// +// Handle events +void Menu::eventMouse(const Common::Event &ev) { + if (!getFlags()->shouldRedraw) + return; + + bool redraw = true; + getFlags()->shouldRedraw = false; + + // Update coordinates + setCoords(ev.mouse); + //_mouseFlags = (Common::EventType)(ev.type & Common::EVENT_LBUTTONUP); + + if (_isShowingCredits) { + if (ev.type == Common::EVENT_RBUTTONUP) { + showFrame(kOverlayCredits, -1, true); + _isShowingCredits = false; + } + + if (ev.type == Common::EVENT_LBUTTONUP) { + // Last frame of the credits + if (_seqCredits && _creditsSequenceIndex == _seqCredits->count() - 1) { + showFrame(kOverlayCredits, -1, true); + _isShowingCredits = false; + } else { + ++_creditsSequenceIndex; + showFrame(kOverlayCredits, _creditsSequenceIndex, true); + } + } + } else { + // Check for hotspots + SceneHotspot *hotspot = NULL; + getScenes()->get(getState()->scene)->checkHotSpot(ev.mouse, &hotspot); + + if (_lastHotspot != hotspot || ev.type == Common::EVENT_LBUTTONUP) { + _lastHotspot = hotspot; + + if (ev.type == Common::EVENT_MOUSEMOVE) { /* todo check event type */ + if (!_handleTimeDelta && hasTimeDelta()) + setTime(); + } + + if (hotspot) { + redraw = handleEvent((StartMenuAction)hotspot->action, ev.type); + getFlags()->mouseRightClick = false; + getFlags()->mouseLeftClick = false; + } else { + hideOverlays(); + } + } + } + + if (redraw) { + getFlags()->shouldRedraw = true; + askForRedraw(); + } +} + +void Menu::eventTick(const Common::Event&) { + if (hasTimeDelta()) + adjustTime(); + else if (_handleTimeDelta) + _handleTimeDelta = false; + + // Check hotspots + if (!--_checkHotspotsTicks) { + checkHotspots(); + _checkHotspotsTicks = 15; + } +} + +////////////////////////////////////////////////////////////////////////// +// Show the intro and load the main menu scene +void Menu::show(bool doSavegame, SavegameType type, uint32 value) { + + if (_isShowingMenu) + return; + + _isShowingMenu = true; + getEntities()->reset(); + + // If no blue savegame exists, this might be the first time we start the game, so we show the full intro + if (!getFlags()->mouseRightClick) { + if (!SaveLoad::isSavegameValid(kGameBlue) && _engine->getResourceManager()->loadArchive(kArchiveCd1)) { + + if (!_hasShownIntro) { + // Show Broderbrund logo + Animation animation; + if (animation.load(getArchive("1930.nis"))) + animation.play(); + + getFlags()->mouseRightClick = false; + + // Play intro music + getSound()->playSoundWithSubtitles("MUS001.SND", SoundManager::kFlagMusic, kEntityPlayer); + + // Show The Smoking Car logo + if (animation.load(getArchive("1931.nis"))) + animation.play(); + + _hasShownIntro = true; + } + } else { + // Only show the quick intro + if (!_hasShownStartScreen) { + getSound()->playSoundWithSubtitles("MUS018.SND", SoundManager::kFlagMusic, kEntityPlayer); + getScenes()->loadScene(kSceneStartScreen); + + // Original game waits 60 frames and loops Sound::unknownFunction1 unless the right button is pressed + uint32 nextFrameCount = getFrameCount() + 60; + while (getFrameCount() < nextFrameCount) { + _engine->pollEvents(); + + if (getFlags()->mouseRightClick) + break; + + getSound()->updateQueue(); + } + } + } + } + + _hasShownStartScreen = true; + + // Init Menu + init(doSavegame, type, value); + + // Setup sound + getSound()->unknownFunction4(); + getSound()->resetQueue(SoundManager::kSoundType11, SoundManager::kSoundType13); + if (getSound()->isBuffered("TIMER")) + getSound()->removeFromQueue("TIMER"); + + // Init flags & misc + _isShowingCredits = false; + _handleTimeDelta = hasTimeDelta(); + getInventory()->unselectItem(); + + // Set Cursor type + _engine->getCursor()->setStyle(kCursorNormal); + _engine->getCursor()->show(true); + + setup(); + checkHotspots(); + + // Set event handlers + SET_EVENT_HANDLERS(Menu, this); +} + +bool Menu::handleEvent(StartMenuAction action, Common::EventType type) { + bool clicked = (type == Common::EVENT_LBUTTONUP); + + switch(action) { + default: + hideOverlays(); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuCredits: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + if (clicked) { + showFrame(kOverlayEggButtons, kButtonCreditsPushed, true); + showFrame(kOverlayTooltip, -1, true); + + getSound()->playSound(kEntityPlayer, "LIB046"); + + hideOverlays(); + + _isShowingCredits = true; + _creditsSequenceIndex = 0; + + showFrame(kOverlayCredits, 0, true); + } else { + // TODO check flags ? + + showFrame(kOverlayEggButtons, kButtonCredits, true); + showFrame(kOverlayTooltip, kTooltipCredits, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuQuitGame: + showFrame(kOverlayTooltip, kTooltipQuit, true); + + if (clicked) { + showFrame(kOverlayButtons, kButtonQuitPushed, true); + + getSound()->clearStatus(); + getSound()->updateQueue(); + getSound()->playSound(kEntityPlayer, "LIB046"); + + // FIXME uncomment when sound queue is properly implemented + /*while (getSound()->isBuffered("LIB046")) + getSound()->updateQueue();*/ + + getFlags()->shouldRedraw = false; + + Engine::quitGame(); + + return false; + } else { + showFrame(kOverlayButtons, kButtonQuit, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuCase4: + if (clicked) + _index = 0; + // fall down to kMenuContinue + + ////////////////////////////////////////////////////////////////////////// + case kMenuContinue: { + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Determine the proper CD archive + ArchiveIndex cd = kArchiveCd1; + if (getProgress().chapter > kChapter1) + cd = (getProgress().chapter > kChapter3) ? kArchiveCd3 : kArchiveCd2; + + // Show tooltips & buttons to start a game, continue a game or load the proper cd + if (ResourceManager::isArchivePresent(cd)) { + if (_isGameStarted) { + showFrame(kOverlayEggButtons, kButtonContinue, true); + + if (_index2 == _index) { + showFrame(kOverlayTooltip, isGameFinished() ? kTooltipViewGameEnding : kTooltipContinueGame, true); + } else { + showFrame(kOverlayTooltip, kTooltipContinueRewoundGame, true); + } + + } else { + showFrame(kOverlayEggButtons, kButtonShield, true); + showFrame(kOverlayTooltip, kTooltipPlayNewGame, true); + } + } else { + showFrame(kOverlayEggButtons, -1, true); + showFrame(kOverlayTooltip, cd - 1, true); + } + + if (!clicked) + break; + + // Try loading the archive file + if (!_engine->getResourceManager()->loadArchive(cd)) + break; + + // Load the train data file and setup game + getScenes()->loadSceneDataFile(cd); + showFrame(kOverlayTooltip, -1, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + + // Setup new game + getSavePoints()->reset(); + setLogicEventHandlers(); + + getSound()->processEntry(SoundManager::kSoundType11); + + if (!getFlags()->mouseRightClick) { + getScenes()->loadScene((SceneIndex)(5 * _gameId + 3)); + + if (!getFlags()->mouseRightClick) { + getScenes()->loadScene((SceneIndex)(5 * _gameId + 4)); + + if (!getFlags()->mouseRightClick) { + getScenes()->loadScene((SceneIndex)(5 * _gameId + 5)); + + if (!getFlags()->mouseRightClick) { + getSound()->processEntry(SoundManager::kSoundType11); + + // Show intro + Animation animation; + if (animation.load(getArchive("1601.nis"))) + animation.play(); + + getEvent(kEventIntro) = 1; + } + } + } + } + + if (!getEvent(kEventIntro)) { + getEvent(kEventIntro) = 1; + + getSound()->processEntry(SoundManager::kSoundType11); + } + + // Setup game + getFlags()->isGameRunning = true; + startGame(); + + if (!_isShowingMenu) + getInventory()->show(); + + return false; + } + + ////////////////////////////////////////////////////////////////////////// + case kMenuSwitchSaveGame: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + if (clicked) { + showFrame(kOverlayAcorn, 1, true); + showFrame(kOverlayTooltip, -1, true); + getSound()->playSound(kEntityPlayer, "LIB047"); + + // Setup new menu screen + switchGame(); + setup(); + + // Set fight state to 0 + getFight()->resetState(); + + return true; + } + + // TODO Check for flag + + showFrame(kOverlayAcorn, 0, true); + + if (_isGameStarted) { + showFrame(kOverlayTooltip, kTooltipSwitchBlueGame, true); + break; + } + + if (_gameId == kGameGold) { + showFrame(kOverlayTooltip, kTooltipSwitchBlueGame, true); + break; + } + + if (!SaveLoad::isSavegameValid(getNextGameId())) { + showFrame(kOverlayTooltip, kTooltipStartAnotherGame, true); + break; + } + + // Stupid tooltips ids are not in order, so we can't just increment them... + switch(_gameId) { + default: + break; + + case kGameBlue: + showFrame(kOverlayTooltip, kTooltipSwitchRedGame, true); + break; + + case kGameRed: + showFrame(kOverlayTooltip, kTooltipSwitchGreenGame, true); + break; + + case kGameGreen: + showFrame(kOverlayTooltip, kTooltipSwitchPurpleGame, true); + break; + + case kGamePurple: + showFrame(kOverlayTooltip, kTooltipSwitchTealGame, true); + break; + + case kGameTeal: + showFrame(kOverlayTooltip, kTooltipSwitchGoldGame, true); + break; + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuRewindGame: + if (!_index || _currentTime < _time) { + hideOverlays(); + break; + } + + if (clicked) { + if (hasTimeDelta()) + _handleTimeDelta = false; + + showFrame(kOverlayEggButtons, kButtonRewindPushed, true); + showFrame(kOverlayTooltip, -1, true); + + getSound()->playSound(kEntityPlayer, "LIB046"); + + rewindTime(); + + _handleTimeDelta = false; + } else { + showFrame(kOverlayEggButtons, kButtonRewind, true); + showFrame(kOverlayTooltip, kTooltipRewind, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuForwardGame: + if (_index2 <= _index || _currentTime > _time) { + hideOverlays(); + break; + } + + if (clicked) { + if (hasTimeDelta()) + _handleTimeDelta = false; + + showFrame(kOverlayEggButtons, kButtonForwardPushed, true); + showFrame(kOverlayTooltip, -1, true); + + getSound()->playSound(kEntityPlayer, "LIB046"); + + forwardTime(); + + _handleTimeDelta = false; + } else { + showFrame(kOverlayEggButtons, kButtonForward, true); + showFrame(kOverlayTooltip, kTooltipFastForward, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuParis: + moveToCity(kParis, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuStrasBourg: + moveToCity(kStrasbourg, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuMunich: + moveToCity(kMunich, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuVienna: + moveToCity(kVienna, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuBudapest: + moveToCity(kBudapest, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuBelgrade: + moveToCity(kBelgrade, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuConstantinople: + moveToCity(kConstantinople, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuDecreaseVolume: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Cannot decrease volume further + if (getVolume() == 0) { + showFrame(kOverlayButtons, kButtonVolume, true); + showFrame(kOverlayTooltip, -1, true); + break; + } + + showFrame(kOverlayTooltip, kTooltipVolumeDown, true); + + // Show highlight on button & adjust volume if needed + if (clicked) { + showFrame(kOverlayButtons, kButtonVolumeDownPushed, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + setVolume(getVolume() - 1); + + getSaveLoad()->saveVolumeBrightness(); + + uint32 nextFrameCount = getFrameCount() + 15; + while (nextFrameCount > getFrameCount()) { + _engine->pollEvents(); + + getSound()->updateQueue(); + } + } else { + showFrame(kOverlayButtons, kButtonVolumeDown, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuIncreaseVolume: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Cannot increase volume further + if (getVolume() >= 7) { + showFrame(kOverlayButtons, kButtonVolume, true); + showFrame(kOverlayTooltip, -1, true); + break; + } + + showFrame(kOverlayTooltip, kTooltipVolumeUp, true); + + // Show highlight on button & adjust volume if needed + if (clicked) { + showFrame(kOverlayButtons, kButtonVolumeUpPushed, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + setVolume(getVolume() + 1); + + getSaveLoad()->saveVolumeBrightness(); + + uint32 nextFrameCount = getFrameCount() + 15; + while (nextFrameCount > getFrameCount()) { + _engine->pollEvents(); + + getSound()->updateQueue(); + } + } else { + showFrame(kOverlayButtons, kButtonVolumeUp, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuDecreaseBrightness: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Cannot increase brightness further + if (getBrightness() == 0) { + showFrame(kOverlayButtons, kButtonBrightness, true); + showFrame(kOverlayTooltip, -1, true); + break; + } + + showFrame(kOverlayTooltip, kTooltipBrightnessDown, true); + + // Show highlight on button & adjust brightness if needed + if (clicked) { + showFrame(kOverlayButtons, kButtonBrightnessDownPushed, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + setBrightness(getBrightness() - 1); + + getSaveLoad()->saveVolumeBrightness(); + + // Reshow the background and frames (they will pick up the new brightness through the GraphicsManager) + _engine->getGraphicsManager()->draw(getScenes()->get((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2)), GraphicsManager::kBackgroundC, true); + showFrame(kOverlayTooltip, kTooltipBrightnessDown, false); + showFrame(kOverlayButtons, kButtonBrightnessDownPushed, false); + } else { + showFrame(kOverlayButtons, kButtonBrightnessDown, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuIncreaseBrightness: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Cannot increase brightness further + if (getBrightness() >= 6) { + showFrame(kOverlayButtons, kButtonBrightness, true); + showFrame(kOverlayTooltip, -1, true); + break; + } + + showFrame(kOverlayTooltip, kTooltipBrightnessUp, true); + + // Show highlight on button & adjust brightness if needed + if (clicked) { + showFrame(kOverlayButtons, kButtonBrightnessUpPushed, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + setBrightness(getBrightness() + 1); + + getSaveLoad()->saveVolumeBrightness(); + + // Reshow the background and frames (they will pick up the new brightness through the GraphicsManager) + _engine->getGraphicsManager()->draw(getScenes()->get((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2)), GraphicsManager::kBackgroundC, true); + showFrame(kOverlayTooltip, kTooltipBrightnessUp, false); + showFrame(kOverlayButtons, kButtonBrightnessUpPushed, false); + } else { + showFrame(kOverlayButtons, kButtonBrightnessUp, true); + } + break; + } + + return true; +} + +void Menu::setLogicEventHandlers() { + SET_EVENT_HANDLERS(Logic, getLogic()); + clear(); + _isShowingMenu = false; +} + +////////////////////////////////////////////////////////////////////////// +// Game-related +////////////////////////////////////////////////////////////////////////// +void Menu::init(bool doSavegame, SavegameType type, uint32 value) { + + bool useSameIndex = true; + + if (getGlobalTimer()) { + value = 0; + + // Check if the CD file is present + ArchiveIndex index = kArchiveCd1; + switch (getProgress().chapter) { + default: + case kChapter1: + break; + + case kChapter2: + case kChapter3: + index = kArchiveCd2; + break; + + case kChapter4: + case kChapter5: + index = kArchiveCd3; + break; + } + + if (ResourceManager::isArchivePresent(index)) { + setGlobalTimer(0); + useSameIndex = false; + + // TODO remove existing savegame and reset index & savegame name + warning("Menu::initGame: not implemented!"); + } + + doSavegame = false; + } else { + // TODO rename saves? + } + + // Create a new savegame if needed + if (!SaveLoad::isSavegamePresent(_gameId)) + SaveLoad::writeMainHeader(_gameId); + + if (doSavegame) + getSaveLoad()->saveGame(kSavegameTypeEvent2, kEntityPlayer, kEventNone); + + if (!getGlobalTimer()) { + // TODO: remove existing savegame temp file + } + + // Init savegame and get the header data + getSaveLoad()->initSavegame(_gameId, true); + SaveLoad::SavegameMainHeader header; + if (!SaveLoad::loadMainHeader(_gameId, &header)) + error("Menu::init: Corrupted savegame - Recovery path not implemented!"); + + // Init Menu values + _index2 = header.index; + _lowerTime = getSaveLoad()->getEntry(_index2)->time; + + if (useSameIndex) + _index = _index2; + + //if (!getGlobalTimer()) + // _index3 = 0; + + if (!getProgress().chapter) + getProgress().chapter = kChapter1; + + getState()->time = getSaveLoad()->getEntry(_index)->time; + getProgress().chapter = getSaveLoad()->getEntry(_index)->chapter; + + if (_lowerTime >= kTimeStartGame) { + _currentTime = getState()->time; + _time = getState()->time; + _clock->draw(_time); + _trainLine->draw(_time); + + initTime(type, value); + } +} + +void Menu::startGame() { + // TODO: we need to reset the current scene + getState()->scene = kSceneDefault; + + getEntities()->setup(true, kEntityPlayer); + warning("Menu::startGame: not implemented!"); +} + +// Switch to the next savegame +void Menu::switchGame() { + + // Switch back to blue game is the current game is not started + _gameId = SaveLoad::isSavegameValid(_gameId) ? getNextGameId() : kGameBlue; + + // Initialize savegame if needed + if (!SaveLoad::isSavegamePresent(_gameId)) + SaveLoad::writeMainHeader(_gameId); + + getState()->time = 0; + + // Clear menu elements + _clock->clear(); + _trainLine->clear(); + + // Clear loaded savegame data + getSaveLoad()->clearEntries(); + + init(false, kSavegameTypeIndex, 0); +} + +bool Menu::isGameFinished() const { + SaveLoad::SavegameEntryHeader *data = getSaveLoad()->getEntry(_index); + + if (_index2 != _index) + return false; + + if (data->type != SaveLoad::kHeaderType2) + return false; + + return (data->event == kEventAnnaKilled + || data->event == kEventKronosHostageAnnaNoFirebird + || data->event == kEventKahinaPunchBaggageCarEntrance + || data->event == kEventKahinaPunchBlue + || data->event == kEventKahinaPunchYellow + || data->event == kEventKahinaPunchSalon + || data->event == kEventKahinaPunchKitchen + || data->event == kEventKahinaPunchBaggageCar + || data->event == kEventKahinaPunchCar + || data->event == kEventKahinaPunchSuite4 + || data->event == kEventKahinaPunchRestaurant + || data->event == kEventKahinaPunch + || data->event == kEventKronosGiveFirebird + || data->event == kEventAugustFindCorpse + || data->event == kEventMertensBloodJacket + || data->event == kEventMertensCorpseFloor + || data->event == kEventMertensCorpseBed + || data->event == kEventCoudertBloodJacket + || data->event == kEventGendarmesArrestation + || data->event == kEventAbbotDrinkGiveDetonator + || data->event == kEventMilosCorpseFloor + || data->event == kEventLocomotiveAnnaStopsTrain + || data->event == kEventTrainStopped + || data->event == kEventCathVesnaRestaurantKilled + || data->event == kEventCathVesnaTrainTopKilled + || data->event == kEventLocomotiveConductorsDiscovered + || data->event == kEventViennaAugustUnloadGuns + || data->event == kEventViennaKronosFirebird + || data->event == kEventVergesAnnaDead + || data->event == kEventTrainExplosionBridge + || data->event == kEventKronosBringNothing); +} + +////////////////////////////////////////////////////////////////////////// +// Overlays & elements +////////////////////////////////////////////////////////////////////////// +void Menu::checkHotspots() { + if (!_isShowingMenu) + return; + + if (!getFlags()->shouldRedraw) + return; + + if (_isShowingCredits) + return; + + SceneHotspot *hotspot = NULL; + getScenes()->get(getState()->scene)->checkHotSpot(getCoords(), &hotspot); + + if (hotspot) + handleEvent((StartMenuAction)hotspot->action, _mouseFlags); + else + hideOverlays(); +} + +void Menu::hideOverlays() { + _lastHotspot = NULL; + + // Hide all menu overlays + for (MenuFrames::iterator it = _frames.begin(); it != _frames.end(); it++) + showFrame(it->_key, -1, false); + + getScenes()->drawFrames(true); +} + +void Menu::showFrame(StartMenuOverlay overlayType, int index, bool redraw) { + if (index == -1) { + getScenes()->removeFromQueue(_frames[overlayType]); + } else { + // Check that the overlay is valid + if (!_frames[overlayType]) + return; + + // Remove the frame and add a new one with the proper index + getScenes()->removeFromQueue(_frames[overlayType]); + _frames[overlayType]->setFrame((uint16)index); + getScenes()->addToQueue(_frames[overlayType]); + } + + if (redraw) + getScenes()->drawFrames(true); +} + +// Remove all frames from the queue +void Menu::clear() { + for (MenuFrames::iterator it = _frames.begin(); it != _frames.end(); it++) + getScenes()->removeAndRedraw(&it->_value, false); + + clearBg(GraphicsManager::kBackgroundOverlay); +} + +// Get the sequence name to use for the acorn highlight, depending of the currently loaded savegame +Common::String Menu::getAcornSequenceName(GameId id) const { + Common::String name = ""; + switch (id) { + default: + case kGameBlue: + name = "aconblu3.seq"; + break; + + case kGameRed: + name = "aconred.seq"; + break; + + case kGameGreen: + name = "acongren.seq"; + break; + + case kGamePurple: + name = "aconpurp.seq"; + break; + + case kGameTeal: + name = "aconteal.seq"; + break; + + case kGameGold: + name = "acongold.seq"; + break; + } + + return name; +} + +////////////////////////////////////////////////////////////////////////// +// Time +////////////////////////////////////////////////////////////////////////// +void Menu::initTime(SavegameType type, uint32 value) { + if (!value) + return; + + // The savegame entry index + uint32 entryIndex = 0; + + switch (type) { + default: + break; + + case kSavegameTypeIndex: + entryIndex = (_index <= value) ? 1 : _index - value; + break; + + case kSavegameTypeTime: + if (value < kTimeStartGame) + break; + + entryIndex = _index; + if (!entryIndex) + break; + + // Iterate through existing entries + do { + if (getSaveLoad()->getEntry(entryIndex)->time <= value) + break; + + entryIndex--; + } while (entryIndex); + break; + + case kSavegameTypeEvent: + entryIndex = _index; + if (!entryIndex) + break; + + do { + if (getSaveLoad()->getEntry(entryIndex)->event == (EventIndex)value) + break; + + entryIndex--; + } while (entryIndex); + break; + + case kSavegameTypeEvent2: + // TODO rewrite in a more legible way + if (_index > 1) { + uint32 index = _index; + do { + if (getSaveLoad()->getEntry(index)->event == (EventIndex)value) + break; + + index--; + } while (index > 1); + + entryIndex = index - 1; + } else { + entryIndex = _index - 1; + } + break; + } + + if (entryIndex) { + _currentIndex = entryIndex; + updateTime(getSaveLoad()->getEntry(entryIndex)->time); + } +} + +void Menu::updateTime(uint32 time) { + if (_currentTime == _time) + _delta = 0; + + _currentTime = time; + + if (_time != time) { + if (getSound()->isBuffered(kEntityChapters)) + getSound()->removeFromQueue(kEntityChapters); + + getSound()->playSoundWithSubtitles((_currentTime >= _time) ? "LIB042" : "LIB041", SoundManager::kFlagMenuClock, kEntityChapters); + adjustIndex(_currentTime, _time, false); + } +} + +void Menu::adjustIndex(uint32 time1, uint32 time2, bool searchEntry) { + uint32 index = 0; + int32 timeDelta = -1; + + if (time1 != time2) { + + index = _index; + + if (time2 >= time1) { + if (searchEntry) { + uint32 currentIndex = _index; + + if ((int32)_index >= 0) { + do { + // Calculate new delta + int32 newDelta = time1 - getSaveLoad()->getEntry(currentIndex)->time; + + if (newDelta >= 0 && timeDelta >= newDelta) { + timeDelta = newDelta; + index = currentIndex; + } + + --currentIndex; + } while ((int32)currentIndex >= 0); + } + } else { + index = _index - 1; + } + } else { + if (searchEntry) { + uint32 currentIndex = _index; + + if (_index2 >= _index) { + do { + // Calculate new delta + int32 newDelta = getSaveLoad()->getEntry(currentIndex)->time - time1; + + if (newDelta >= 0 && timeDelta > newDelta) { + timeDelta = newDelta; + index = currentIndex; + } + + ++currentIndex; + } while (currentIndex >= _index2); + } + } else { + index = _index + 1; + } + } + + _index = index; + checkHotspots(); + } + + if (_index == _currentIndex) { + if (getProgress().chapter != getSaveLoad()->getEntry(index)->chapter) + getProgress().chapter = getSaveLoad()->getEntry(_index)->chapter; + } +} + +void Menu::goToTime(uint32 time) { + + uint32 entryIndex = 0; + uint32 deltaTime = (uint32)ABS((int32)(getSaveLoad()->getEntry(0)->time - time)); + uint32 index = 0; + + do { + uint32 deltaTime2 = (uint32)ABS((int32)(getSaveLoad()->getEntry(index)->time - time)); + if (deltaTime2 < deltaTime) { + deltaTime = deltaTime2; + entryIndex = index; + } + + ++index; + } while (_index2 >= index); + + _currentIndex = entryIndex; + updateTime(getSaveLoad()->getEntry(entryIndex)->time); +} + +void Menu::setTime() { + _currentIndex = _index; + _currentTime = getSaveLoad()->getEntry(_currentIndex)->time; + + if (_time == _currentTime) + adjustTime(); +} + +void Menu::forwardTime() { + if (_index2 <= _index) + return; + + _currentIndex = _index2; + updateTime(getSaveLoad()->getEntry(_currentIndex)->time); +} + +void Menu::rewindTime() { + if (!_index) + return; + + _currentIndex = 0; + updateTime(getSaveLoad()->getEntry(_currentIndex)->time); +} + +void Menu::adjustTime() { + uint32 originalTime = _time; + + // Adjust time delta + uint32 timeDelta = (_delta >= 90) ? 9 : (9 * _delta + 89) / 90; + + if (_currentTime < _time) { + _time -= 900 * timeDelta; + + if (_time >= _currentTime) + _time = _currentTime; + } else { + _time += 900 * timeDelta; + + if (_time < _currentTime) + _time = _currentTime; + } + + if (_currentTime == _time && getSound()->isBuffered(kEntityChapters)) + getSound()->removeFromQueue(kEntityChapters); + + _clock->draw(_time); + _trainLine->draw(_time); + getScenes()->drawFrames(true); + + adjustIndex(_time, originalTime, true); + + ++_delta; +} + +void Menu::moveToCity(CityButton city, bool clicked) { + uint32 time = (uint32)_cityButtonsInfo[city].time; + + // TODO Check if we have access (there seems to be more checks on some internal times) - probably : current_time (menu only) / game time / some other? + if (_lowerTime < time || _time == time || _currentTime == time) { + hideOverlays(); + return; + } + + // Show city overlay + showFrame((StartMenuOverlay)((_cityButtonsInfo[city].index >> 6) + 3), _cityButtonsInfo[city].index & 63, true); + + if (clicked) { + showFrame(kOverlayTooltip, -1, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + goToTime(time); + + _handleTimeDelta = true; + + return; + } + + // Special case of first and last cities + if (city == kParis || city == kConstantinople) { + showFrame(kOverlayTooltip, (city == kParis) ? kTooltipRewindParis : kTooltipForwardConstantinople, true); + return; + } + + showFrame(kOverlayTooltip, (_time <= time) ? _cityButtonsInfo[city].forward : _cityButtonsInfo[city].rewind, true); +} + +////////////////////////////////////////////////////////////////////////// +// Sound / Brightness +////////////////////////////////////////////////////////////////////////// + +// Get current volume (converted internal ScummVM value) +uint32 Menu::getVolume() const { + return getState()->volume; +} + +// Set the volume (converts to ScummVM values) +void Menu::setVolume(uint32 volume) const { + getState()->volume = volume; + + // Clamp volume + uint32 value = volume * Audio::Mixer::kMaxMixerVolume / 7; + + if (value > Audio::Mixer::kMaxMixerVolume) + value = Audio::Mixer::kMaxMixerVolume; + + _engine->_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, (int32)value); +} + +uint32 Menu::getBrightness() const { + return getState()->brightness; +} + +void Menu::setBrightness(uint32 brightness) const { + getState()->brightness = brightness; + + // TODO reload cursor & font with adjusted brightness +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/menu.h b/engines/lastexpress/game/menu.h new file mode 100644 index 0000000000..54d1eb65ec --- /dev/null +++ b/engines/lastexpress/game/menu.h @@ -0,0 +1,211 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_MENU_H +#define LASTEXPRESS_MENU_H + +#include "lastexpress/data/sequence.h" + +#include "lastexpress/eventhandler.h" + +#include "lastexpress/shared.h" + +#include "common/hashmap.h" + +namespace LastExpress { + +class LastExpressEngine; +class Scene; +class SceneHotspot; + +class Clock; +class TrainLine; + +class Menu : public EventHandler { +public: + Menu(LastExpressEngine *engine); + ~Menu(); + + void show(bool doSavegame, SavegameType type, uint32 value); + + // Event handling + void eventMouse(const Common::Event &ev); + void eventTick(const Common::Event &ev); + + bool isShown() const { return _isShowingMenu; } + + GameId getGameId() const { return _gameId; } + +private: + // Start menu events + enum StartMenuAction { + kMenuContinue = 1, + kMenuCredits = 2, + kMenuQuitGame = 3, + kMenuCase4 = 4, + kMenuSwitchSaveGame = 6, + kMenuRewindGame = 7, + kMenuForwardGame = 8, + kMenuParis = 10, + kMenuStrasBourg = 11, + kMenuMunich = 12, + kMenuVienna = 13, + kMenuBudapest = 14, + kMenuBelgrade = 15, + kMenuConstantinople = 16, + kMenuDecreaseVolume = 17, + kMenuIncreaseVolume = 18, + kMenuDecreaseBrightness = 19, + kMenuIncreaseBrightness = 20 + }; + + // City buttons + enum CityButton { + kParis = 0, + kStrasbourg = 1, + kMunich = 2, + kVienna = 3, + kBudapest = 4, + kBelgrade = 5, + kConstantinople = 6 + }; + + // Start menu overlay elements + enum StartMenuOverlay { + kOverlayTooltip, // 0 + kOverlayEggButtons, + kOverlayButtons, + kOverlayAcorn, + kOverlayCity1, + kOverlayCity2, // 5 + kOverlayCity3, + kOverlayCredits + }; + + LastExpressEngine *_engine; + + // Sequences + Sequence* _seqTooltips; + Sequence* _seqEggButtons; + Sequence* _seqButtons; + Sequence* _seqAcorn; + Sequence* _seqCity1; + Sequence* _seqCity2; + Sequence* _seqCity3; + Sequence* _seqCredits; + + GameId _gameId; + + // Indicator to know if we need to show the start animation when showMenu is called + bool _hasShownStartScreen; + bool _hasShownIntro; + + bool _isShowingCredits; + bool _isGameStarted; + bool _isShowingMenu; + + + uint16 _creditsSequenceIndex; + + ////////////////////////////////////////////////////////////////////////// + // Event handling + uint32 _checkHotspotsTicks; + Common::EventType _mouseFlags; + SceneHotspot *_lastHotspot; + + void init(bool doSavegame, SavegameType type, uint32 value); + void setup(); + bool handleEvent(StartMenuAction action, Common::EventType type); + void checkHotspots(); + void setLogicEventHandlers(); + + ////////////////////////////////////////////////////////////////////////// + // Game-related + void startGame(); + void switchGame(); + bool isGameFinished() const; + + ////////////////////////////////////////////////////////////////////////// + // Overlays & elements + Clock *_clock; + TrainLine *_trainLine; + + struct MenuOverlays_EqualTo { + bool operator()(const StartMenuOverlay& x, const StartMenuOverlay& y) const { return x == y; } + }; + + struct MenuOverlays_Hash { + uint operator()(const StartMenuOverlay& x) const { return x; } + }; + + typedef Common::HashMap<StartMenuOverlay, SequenceFrame *, MenuOverlays_Hash, MenuOverlays_EqualTo> MenuFrames; + + MenuFrames _frames; + + void hideOverlays(); + void showFrame(StartMenuOverlay overlay, int index, bool redraw); + + void clear(); + + // TODO: remove? + void moveToCity(CityButton city, bool clicked); + + ////////////////////////////////////////////////////////////////////////// + // Misc + Common::String getAcornSequenceName(GameId id) const; + + ////////////////////////////////////////////////////////////////////////// + // Time + uint32 _currentIndex; // current savegame entry + uint32 _currentTime; // current game time + uint32 _lowerTime; // lower time value + + uint32 _index; + uint32 _index2; + uint32 _time; + uint32 _delta; + bool _handleTimeDelta; + + void initTime(SavegameType type, uint32 time); + void updateTime(uint32 time); + void adjustTime(); + void adjustIndex(uint32 time1, uint32 time2, bool searchEntry); + void goToTime(uint32 time); + void setTime(); + void forwardTime(); + void rewindTime(); + bool hasTimeDelta() { return (_currentTime - _time) >= 1; } + + ////////////////////////////////////////////////////////////////////////// + // Sound/Brightness related + uint32 getVolume() const; + void setVolume(uint32 volume) const; + uint32 getBrightness() const; + void setBrightness(uint32 brightness) const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_MENU_H diff --git a/engines/lastexpress/game/object.cpp b/engines/lastexpress/game/object.cpp new file mode 100644 index 0000000000..0b336b941f --- /dev/null +++ b/engines/lastexpress/game/object.cpp @@ -0,0 +1,108 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/object.h" + +#include "lastexpress/game/logic.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Common::String Objects::Object::toString() { + return Common::String::printf("{ %s - %d - %d - %d - %d }", ENTITY_NAME(entity), location, cursor, cursor2, location2); +} + +Objects::Objects(LastExpressEngine *engine) : _engine(engine) {} + +const Objects::Object Objects::get(ObjectIndex index) const { + if (index >= kObjectMax) + error("Objects::get - internal error: invalid object index (%d)", index); + + return _objects[index]; +} + +void Objects::update(ObjectIndex index, EntityIndex entity, ObjectLocation location, CursorStyle cursor, CursorStyle cursor2) { + if (index >= kObjectMax) + return; + + Object *object = &_objects[index]; + + // Store original location + ObjectLocation original_location = object->location; + + // Update entity + object->entity = entity; + object->location = location; + + if (cursor != kCursorKeepValue || cursor2 != kCursorKeepValue) { + if (cursor != kCursorKeepValue) + object->cursor = cursor; + if (cursor2 != kCursorKeepValue) + object->cursor2 = cursor2; + + getLogic()->updateCursor(); + } + + getFlags()->flag_3 = true; + + // Compartments + if (original_location != location && (original_location == kObjectLocation2 || location == kObjectLocation2)) + if ((index >= kObjectCompartment1 && index <= kObjectCompartment8) + || (index >= kObjectCompartmentA && index <= kObjectCompartmentF)) { + getScenes()->updateDoorsAndClock(); + } +} + +void Objects::updateLocation2(ObjectIndex index, ObjectLocation location2) { + if (index >= kObjectMax) + return; + + _objects[index].location2 = location2; +} + +////////////////////////////////////////////////////////////////////////// +// Serializable +////////////////////////////////////////////////////////////////////////// +void Objects::saveLoadWithSerializer(Common::Serializer &) { + error("Objects::saveLoadWithSerializer: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// toString +////////////////////////////////////////////////////////////////////////// +Common::String Objects::toString() { + Common::String ret = ""; + + for (int i = 0; i < kObjectMax; i++) + ret += Common::String::printf("%d : %s\n", i, _objects[i].toString().c_str()); + + return ret; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/object.h b/engines/lastexpress/game/object.h new file mode 100644 index 0000000000..3417e9bfcf --- /dev/null +++ b/engines/lastexpress/game/object.h @@ -0,0 +1,83 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_OBJECT_H +#define LASTEXPRESS_OBJECT_H + +#include "lastexpress/shared.h" + +#include "common/serializer.h" +#include "common/system.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Objects : Common::Serializable { +public: + + struct Object { // All fields should be saved as bytes + EntityIndex entity; + ObjectLocation location; + CursorStyle cursor; + CursorStyle cursor2; + ObjectLocation location2; + + Object() { + entity = kEntityPlayer; + location = kObjectLocationNone; + cursor = kCursorHandKnock; + cursor2 = kCursorHandKnock; + location2 = kObjectLocationNone; + } + + Common::String toString(); + }; + + Objects(LastExpressEngine *engine); + + const Object get(ObjectIndex index) const; + void update(ObjectIndex index, EntityIndex entity, ObjectLocation location, CursorStyle cursor, CursorStyle cursor2); + void updateLocation2(ObjectIndex index, ObjectLocation location2); + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString(); + +private: + LastExpressEngine* _engine; + + Object _objects[kObjectMax]; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_OBJECT_H diff --git a/engines/lastexpress/game/savegame.cpp b/engines/lastexpress/game/savegame.cpp new file mode 100644 index 0000000000..1bd3d8239b --- /dev/null +++ b/engines/lastexpress/game/savegame.cpp @@ -0,0 +1,310 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/debug.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +#include "common/file.h" +#include "common/system.h" + +namespace LastExpress { + +// Savegame signatures +#define SAVEGAME_SIGNATURE 0x12001200 +#define SAVEGAME_HEADER 0xE660E660 + +// Names of savegames +static const struct { + const char *saveFile; +} gameInfo[6] = { + {"blue.egg"}, + {"red.egg"}, + {"green.egg"}, + {"purple.egg"}, + {"teal.egg"}, + {"gold.egg"} +}; + +////////////////////////////////////////////////////////////////////////// +// Constructors +////////////////////////////////////////////////////////////////////////// + +SaveLoad::SaveLoad(LastExpressEngine *engine) : _engine(engine) { + _gameTicksLastSavegame = 0; +} + +SaveLoad::~SaveLoad() { + //Zero passed pointers + _engine = NULL; + + clearEntries(); +} + +////////////////////////////////////////////////////////////////////////// +// Save & Load +////////////////////////////////////////////////////////////////////////// + +// Load game +bool SaveLoad::loadGame(GameId id) { + + if (!SaveLoad::isSavegamePresent(id)) + return false; + + //Common::InSaveFile *save = SaveLoad::openForLoading(id); + // Validate header + + + + + error("SaveLoad::loadgame: not implemented!"); + + return false; +} + +// Save game +void SaveLoad::saveGame(SavegameType type, EntityIndex entity, uint32 value) { + + // Save ticks + _gameTicksLastSavegame = getState()->timeTicks; + + warning("SaveLoad::savegame: not implemented!"); +} + +void SaveLoad::saveVolumeBrightness() { + warning("SaveLoad::saveVolumeBrightness: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Static Members +////////////////////////////////////////////////////////////////////////// + +// Check if a specific savegame exists +bool SaveLoad::isSavegamePresent(GameId id) { + if (g_system->getSavefileManager()->listSavefiles(getSavegameName(id)).size() == 0) + return false; + + return true; +} + +// Check if the game has been started in the specific savegame +bool SaveLoad::isSavegameValid(GameId id) { + if (!isSavegamePresent(id)) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::isSavegameValid - Savegame does not exist: %s", getSavegameName(id).c_str()); + return false; + } + + SavegameMainHeader header; + if (!loadMainHeader(id, &header)) + return false; + + return validateMainHeader(header); +} + + +////////////////////////////////////////////////////////////////////////// +// Headers +////////////////////////////////////////////////////////////////////////// +bool SaveLoad::loadMainHeader(GameId id, SavegameMainHeader* header) { + // Read first 32 bytes of savegame + Common::InSaveFile *save = openForLoading(id); + if (!save) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Cannot open savegame for reading: %s", getSavegameName(id).c_str()); + return false; + } + + // Check there is enough data + if (save->size() < 32) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Savegame seems to be corrupted (not enough data: %i bytes): %s", save->size(), getSavegameName(id).c_str()); + delete save; + return false; + } + + header->signature = save->readUint32LE(); + header->index = save->readUint32LE(); + header->time = save->readUint32LE(); + header->field_C = save->readUint32LE(); + header->field_10 = save->readUint32LE(); + header->brightness = save->readSint32LE(); + header->volume = save->readSint32LE(); + header->field_1C = save->readUint32LE(); + + delete save; + + // Valide the header + if (!validateMainHeader(*header)) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Cannot validate main header for savegame %s.", getSavegameName(id).c_str()); + return false; + } + + return true; +} + +void SaveLoad::loadEntryHeader(Common::InSaveFile *save, SavegameEntryHeader *header) { + header->signature = save->readUint32LE(); + header->type = (HeaderType)save->readUint32LE(); + header->time = save->readUint32LE(); + header->field_C = save->readUint32LE(); + header->chapter = (ChapterIndex)save->readUint32LE(); + header->event = (EventIndex)save->readUint32LE(); + header->field_18 = save->readUint32LE(); + header->field_1C = save->readUint32LE(); +} + +bool SaveLoad::validateMainHeader(const SavegameMainHeader &header) { + if (header.signature != SAVEGAME_SIGNATURE) + return false; + + /* Check not needed as it can never be < 0 + if (header.chapter < 0) + return false;*/ + + if (header.time < 32) + return false; + + if (header.field_C < 32) + return false; + + if (header.field_10 != 1 && header.field_10) + return false; + + if (header.brightness < 0 || header.brightness > 6) + return false; + + if (header.volume < 0 || header.volume > 7) + return false; + + if (header.field_1C != 9) + return false; + + return true; +} + +bool SaveLoad::validateEntryHeader(const SavegameEntryHeader &header) { + if (header.signature != SAVEGAME_HEADER) + return false; + + if (header.type < kHeaderType1 || header.type > kHeaderType5) + return false; + + if (header.time < kTimeStartGame || header.time > kTimeCityConstantinople) + return false; + + if (header.field_C <= 0 || header.field_C >= 15) + return false; + + /* No check for < 0, as it cannot happen normaly */ + if (header.chapter == 0) + return false; + + return true; +} + +SaveLoad::SavegameEntryHeader *SaveLoad::getEntry(uint32 index) { + if (index >= _gameHeaders.size()) + error("SaveLoad::getEntry: invalid index (was:%d, max:%d)", index, _gameHeaders.size() - 1); + + return _gameHeaders[index]; +} + +void SaveLoad::clearEntries() { + for (uint i = 0; i < _gameHeaders.size(); i++) + SAFE_DELETE(_gameHeaders[i]); + + _gameHeaders.clear(); +} + +////////////////////////////////////////////////////////////////////////// +// Init +////////////////////////////////////////////////////////////////////////// +void SaveLoad::writeMainHeader(GameId id) { + Common::OutSaveFile *save = openForSaving(id); + if (!save) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::initSavegame - Cannot open savegame for writing: %s", getSavegameName(id).c_str()); + return; + } + + // Write default values to savegame + save->writeUint32LE(SAVEGAME_SIGNATURE); + save->writeUint32LE(0); + save->writeUint32LE(32); + save->writeUint32LE(32); + save->writeUint32LE(0); + save->writeUint32LE(3); + save->writeUint32LE(7); + save->writeUint32LE(9); + + delete save; +} + +void SaveLoad::initSavegame(GameId id, bool resetHeaders) { + //Common::OutSaveFile *save = openForSaving(id); + //if (!save) { + // debugC(2, kLastExpressDebugSavegame, "SaveLoad::initSavegame - Cannot open savegame for writing: %s", getSavegameName(id).c_str()); + // return; + //} + + if (resetHeaders) { + clearEntries(); + + SavegameEntryHeader *header = new SavegameEntryHeader(); + header->time = kTimeCityParis; + header->chapter = kChapter1; + + _gameHeaders.push_back(header); + } + + // Open the savegame and read all game headers + + warning("SaveLoad::initSavegame: not implemented!"); + + //delete save; +} + +////////////////////////////////////////////////////////////////////////// +// Private methods +////////////////////////////////////////////////////////////////////////// + +// Get the file name from the savegame ID +Common::String SaveLoad::getSavegameName(GameId id) { + if (id >= 6) + error("SaveLoad::getSavegameName - attempting to use an invalid game id. Valid values: 0 - 5, was %d", id); + + return gameInfo[id].saveFile; +} + +Common::InSaveFile *SaveLoad::openForLoading(GameId id) { + return g_system->getSavefileManager()->openForLoading(getSavegameName(id)); +} + +Common::OutSaveFile *SaveLoad::openForSaving(GameId id) { + return g_system->getSavefileManager()->openForSaving(getSavegameName(id)); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/savegame.h b/engines/lastexpress/game/savegame.h new file mode 100644 index 0000000000..739e6a1798 --- /dev/null +++ b/engines/lastexpress/game/savegame.h @@ -0,0 +1,173 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SAVELOAD_H +#define LASTEXPRESS_SAVELOAD_H + +/* + Savegame format + --------------- + + header: 32 bytes + uint32 {4} - signature: 0x12001200 + uint32 {4} - chapter - needs to be [0; 5] + uint32 {4} - time - needs to be >= 32 [1061100; timeMax] + uint32 {4} - ?? needs to be >= 32 + uint32 {4} - ?? needs to be = 1 + uint32 {4} - Brightness (needs to be [0-6]) + uint32 {4} - Volume (needs to be [0-7]) + uint32 {4} - ?? needs to be = 9 + + Game data Format + ----------------- + + uint32 {4} - entity + uint32 {4} - current time + uint32 {4} - time delta (how much a tick is in "real" time) + uint32 {4} - time ticks + uint32 {4} - scene Index max: 2500 + byte {1} - use backup scene + uint32 {4} - backup Scene Index 1 max: 2500 + uint32 {4} - backup Scene Index 2 max: 2500 + uint32 {4} - selected inventory item max: 32 + uint32 {4*100*10} - positions (by car) + uint32 {4*16} - compartments + uint32 {4*16} - compartments ?? + uint32 {4*128} - game progress + byte {512} - game events + byte {7*32} - inventory + byte {5*128} - objects + byte {1262*40} - entities (characters and train entities) + + uint32 {4} - sound queue state + uint32 {4} - ?? + uint32 {4} - number of sound entries + byte {count*68} - sound entries + + byte {16*128} - save point data + uint32 {4} - number of save points (max: 128) + byte {count*16} - save points + + ... more unknown stuff + +*/ + +#include "lastexpress/shared.h" + +#include "common/savefile.h" + +namespace LastExpress { + +class LastExpressEngine; + +class SaveLoad { +public: + enum HeaderType { + kHeaderTypeNone = 0, + kHeaderType1 = 1, + kHeaderType2 = 2, + kHeaderType3 = 3, + kHeaderType4 = 4, + kHeaderType5 = 5 + }; + + struct SavegameMainHeader { + uint32 signature; + uint32 index; + uint32 time; + uint32 field_C; + uint32 field_10; + int32 brightness; + int32 volume; + uint32 field_1C; + }; + + struct SavegameEntryHeader { + uint32 signature; + HeaderType type; + uint32 time; + int field_C; + ChapterIndex chapter; + EventIndex event; + int field_18; + int field_1C; + + SavegameEntryHeader() { + signature = 0; + type = kHeaderTypeNone; + time = 0; + field_C = 0; + chapter = kChapterAll; + event = kEventNone; + field_18 = 0; + field_1C = 0; + } + }; + + SaveLoad(LastExpressEngine *engine); + ~SaveLoad(); + + // Save & Load + bool loadGame(GameId id); + void saveGame(SavegameType type, EntityIndex entity, uint32 value); + + void saveVolumeBrightness(); + + // Init + void initSavegame(GameId id, bool resetHeaders); + static void writeMainHeader(GameId id); + + // Getting information + static bool isSavegamePresent(GameId id); + static bool isSavegameValid(GameId id); + + // Opening save files + static Common::InSaveFile *openForLoading(GameId id); + static Common::OutSaveFile *openForSaving(GameId id); + + // Headers + static bool loadMainHeader(GameId id, SavegameMainHeader* header); + SavegameEntryHeader *getEntry(uint32 index); + void clearEntries(); + + uint32 getLastSavegameTicks() const { return _gameTicksLastSavegame; } + +private: + LastExpressEngine *_engine; + + uint32 _gameTicksLastSavegame; + Common::Array<SavegameEntryHeader *> _gameHeaders; + + static Common::String getSavegameName(GameId id); + + static void loadEntryHeader(Common::InSaveFile *save, SavegameEntryHeader* header); + + static bool validateMainHeader(const SavegameMainHeader &header); + static bool validateEntryHeader(const SavegameEntryHeader &header); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SAVELOAD_H diff --git a/engines/lastexpress/game/savepoint.cpp b/engines/lastexpress/game/savepoint.cpp new file mode 100644 index 0000000000..e7bae494ed --- /dev/null +++ b/engines/lastexpress/game/savepoint.cpp @@ -0,0 +1,296 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/savepoint.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + + +namespace LastExpress { + +SavePoints::SavePoints(LastExpressEngine *engine) : _engine(engine) { + for (int i = 0; i < 40; i++) + _callbacks[i] = NULL; +} + +SavePoints::~SavePoints() { + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Savepoints +////////////////////////////////////////////////////////////////////////// +void SavePoints::push(EntityIndex entity2, EntityIndex entity1, ActionIndex action, uint32 param) { + if (_savepoints.size() >= _savePointsMaxSize) + return; + + SavePoint point; + point.entity1 = entity1; + point.action = action; + point.entity2 = entity2; + point.param.intValue = param; + + _savepoints.push_back(point); +} + +void SavePoints::push(EntityIndex entity2, EntityIndex entity1, ActionIndex action, const char* param) { + if (_savepoints.size() >= _savePointsMaxSize) + return; + + SavePoint point; + point.entity1 = entity1; + point.action = action; + point.entity2 = entity2; + strcpy((char *)&point.param.charValue, param); + + _savepoints.push_back(point); +} + +SavePoint SavePoints::pop() { + SavePoint point = _savepoints.front(); + _savepoints.pop_front(); + return point; +} + + +void SavePoints::pushAll(EntityIndex entity, ActionIndex action, uint32 param) { + for (uint32 index = 1; index < 40; index++) { + if ((EntityIndex)index != entity) + push(entity, (EntityIndex)index, action, param); + } +} + +// Process all savepoints +void SavePoints::process() { + while (_savepoints.size() > 0 && getFlags()->isGameRunning) { + SavePoint savepoint = pop(); + + // If this is a data savepoint, update the entity + // otherwise, execute the callback + if (!updateEntityFromData(savepoint)) { + + // Call requested callback + Entity::Callback *callback = getCallback(savepoint.entity1); + if (callback && callback->isValid()) { + debugC(8, kLastExpressDebugLogic, "Savepoint: entity1=%s, action=%s, entity2=%s", ENTITY_NAME(savepoint.entity1), ACTION_NAME(savepoint.action), ENTITY_NAME(savepoint.entity2)); + (*callback)(savepoint); + } + } + } +} + +void SavePoints::reset() { + _savepoints.clear(); +} + +////////////////////////////////////////////////////////////////////////// +// Data +////////////////////////////////////////////////////////////////////////// +void SavePoints::addData(EntityIndex entity, ActionIndex action, uint32 param) { + if (_data.size() >= _savePointsMaxSize) + return; + + SavePointData data; + data.entity1 = entity; + data.action = action; + data.param = param; + + _data.push_back(data); +} + +////////////////////////////////////////////////////////////////////////// +// Callbacks +////////////////////////////////////////////////////////////////////////// +void SavePoints::setCallback(EntityIndex index, Entity::Callback* callback) { + if (index >= 40) + error("SavePoints::setCallback - attempting to use an invalid entity index. Valid values 0-39, was %d", index); + + if (!callback || !callback->isValid()) + error("SavePoints::setCallback - attempting to set an invalid callback for entity %s", ENTITY_NAME(index)); + + _callbacks[index] = callback; +} + +Entity::Callback *SavePoints::getCallback(EntityIndex index) const { + if (index >= 40) + error("SavePoints::getCallback - attempting to use an invalid entity index. Valid values 0-39, was %d", index); + + return _callbacks[index]; +} + +void SavePoints::call(EntityIndex entity2, EntityIndex entity1, ActionIndex action, uint32 param) const { + SavePoint point; + point.entity1 = entity1; + point.action = action; + point.entity2 = entity2; + point.param.intValue = param; + + Entity::Callback *callback = getCallback(entity1); + if (callback != NULL && callback->isValid()) { + debugC(8, kLastExpressDebugLogic, "Savepoint: entity1=%s, action=%s, entity2=%s, param=%d", ENTITY_NAME(entity1), ACTION_NAME(action), ENTITY_NAME(entity2), param); + (*callback)(point); + } +} + +void SavePoints::call(EntityIndex entity2, EntityIndex entity1, ActionIndex action, const char *param) const { + SavePoint point; + point.entity1 = entity1; + point.action = action; + point.entity2 = entity2; + strcpy((char *)&point.param.charValue, param); + + Entity::Callback *callback = getCallback(entity1); + if (callback != NULL && callback->isValid()) { + debugC(8, kLastExpressDebugLogic, "Savepoint: entity1=%s, action=%s, entity2=%s, param=%s", ENTITY_NAME(entity1), ACTION_NAME(action), ENTITY_NAME(entity2), param); + (*callback)(point); + } +} + +void SavePoints::callAndProcess() { + SavePoint savepoint; // empty parameters + + // We ignore the kEntityPlayer callback in the list + EntityIndex index = kEntityAnna; + + // Call all callbacks with empty parameters + bool isRunning = getFlags()->isGameRunning; + while (isRunning) { + + Entity::Callback *callback = getCallback(index); + if (callback != NULL && callback->isValid()) { + (*callback)(savepoint); + isRunning = getFlags()->isGameRunning; + } + + index = (EntityIndex)(index + 1); + + // Process all savepoints when done + if (index >= 40) { + if (isRunning) + process(); + + return; + } + } +} + +////////////////////////////////////////////////////////////////////////// +// Misc +////////////////////////////////////////////////////////////////////////// +bool SavePoints::updateEntityFromData(const SavePoint &savepoint) { + for (int i = 0; i < (int)_data.size(); i++) { + + // Not a data savepoint! + if (!_data[i].entity1) + return false; + + // Found our data! + if (_data[i].entity1 == savepoint.entity1 && _data[i].action == savepoint.action) { + debugC(8, kLastExpressDebugLogic, "Update entity from data: entity1=%s, action=%s, param=%d", ENTITY_NAME(_data[i].entity1), ACTION_NAME(_data[i].action), _data[i].param); + + // the SavePoint param value is the index of the entity call parameter to update + getEntities()->get(_data[i].entity1)->getParamData()->updateParameters(_data[i].param); + + return true; + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////////// +// Serializable +////////////////////////////////////////////////////////////////////////// +void SavePoints::saveLoadWithSerializer(Common::Serializer &s) { + + // Serialize savepoint data + uint32 dataSize = (s.isLoading() ? _savePointsMaxSize : _data.size()); + for (uint i = 0; i < dataSize; i++) { + if (s.isLoading()) { + SavePointData data; + _data.push_back(data); + } + + s.syncAsUint32LE(_data[i].entity1); + s.syncAsUint32LE(_data[i].action); + s.syncAsUint32LE(_data[i].entity2); + s.syncAsUint32LE(_data[i].param); + } + + // Skip uninitialized data if any + s.skip((_savePointsMaxSize - dataSize) * 16); + + // Number of savepoints + uint32 count = _savepoints.size(); + s.syncAsUint32LE(count); + + // Savepoints + if (s.isLoading()) { + for (uint i= 0; i < count; i++) { + SavePoint point; + s.syncAsUint32LE(point.entity1); + s.syncAsUint32LE(point.action); + s.syncAsUint32LE(point.entity2); + s.syncAsUint32LE(point.param.intValue); + + _savepoints.push_back(point); + + if (_savepoints.size() >= _savePointsMaxSize) + break; + } + } else { + for (Common::List<SavePoint>::iterator it = _savepoints.begin(); it != _savepoints.end(); ++it) { + s.syncAsUint32LE((*it).entity1); + s.syncAsUint32LE((*it).action); + s.syncAsUint32LE((*it).entity2); + s.syncAsUint32LE((*it).param.intValue); + } + } +} + +////////////////////////////////////////////////////////////////////////// +// toString +////////////////////////////////////////////////////////////////////////// +Common::String SavePoints::toString() { + Common::String ret = ""; + + ret += "Savepoint Data\n"; + for (uint i = 0; i < _data.size(); i++) + ret += _data[i].toString() + "\n"; + + ret += "\nSavepoints\n"; + for (Common::List<SavePoint>::iterator it = _savepoints.begin(); it != _savepoints.end(); ++it) + ret += (*it).toString() + "\n"; + + return ret; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/savepoint.h b/engines/lastexpress/game/savepoint.h new file mode 100644 index 0000000000..da1c9b8ed1 --- /dev/null +++ b/engines/lastexpress/game/savepoint.h @@ -0,0 +1,149 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SAVEPOINT_H +#define LASTEXPRESS_SAVEPOINT_H + +#include "lastexpress/entities/entity.h" + +#include "lastexpress/helpers.h" + +#include "common/array.h" +#include "common/list.h" +#include "common/serializer.h" + +/* + Savepoint format + ---------------- + + Save point: max: 127 - FIFO list (ie. goes back and overwrites first save point when full) + uint32 {4} - Entity 1 + uint32 {4} - Action + uint32 {4} - Entity 2 + uint32 {4} - Parameter + + Save point Data + uint32 {4} - Entity 1 + uint32 {4} - Action + uint32 {4} - Entity 2 + uint32 {4} - function pointer to ?? + +*/ + +namespace LastExpress { + +class LastExpressEngine; + +struct SavePoint { + EntityIndex entity1; + ActionIndex action; + EntityIndex entity2; + union { + uint32 intValue; + char charValue[5]; + } param; + + SavePoint() { + entity1 = kEntityPlayer; + action = kActionNone; + entity2 = kEntityPlayer; + param.intValue = 0; + } + + Common::String toString() { + return Common::String::printf("{ %s - %d - %s - %s }", ENTITY_NAME(entity1), action, ENTITY_NAME(entity2), param.charValue); + } +}; + +class SavePoints : Common::Serializable { +private: + typedef Common::Functor1<const SavePoint&, void> Callback; + +public: + + struct SavePointData { + EntityIndex entity1; + ActionIndex action; + EntityIndex entity2; + uint32 param; + + SavePointData() { + entity1 = kEntityPlayer; + action = kActionNone; + entity2 = kEntityPlayer; + param = 0; + } + + Common::String toString() { + return Common::String::printf(" { %s - %d - %s - %d }", ENTITY_NAME(entity1), action, ENTITY_NAME(entity2), param); + } + }; + + SavePoints(LastExpressEngine *engine); + ~SavePoints(); + + // Savepoints + void push(EntityIndex entity2, EntityIndex entity1, ActionIndex action, uint32 param = 0); + void push(EntityIndex entity2, EntityIndex entity1, ActionIndex action, const char* param); + void pushAll(EntityIndex entity, ActionIndex action, uint32 param = 0); + void process(); + void reset(); + + // Data + void addData(EntityIndex entity, ActionIndex action, uint32 param); + + // Callbacks + void setCallback(EntityIndex index, Entity::Callback* callback); + Callback *getCallback(EntityIndex entity) const; + void call(EntityIndex entity2, EntityIndex entity1, ActionIndex action, uint32 param = 0) const; + void call(EntityIndex entity2, EntityIndex entity1, ActionIndex action, const char *param) const; + void callAndProcess(); + + // Serializable + void saveLoadWithSerializer(Common::Serializer &s); + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString(); + +private: + static const uint32 _savePointsMaxSize = 128; + + LastExpressEngine *_engine; + + Common::List<SavePoint> _savepoints; ///< could be a queue, but we need to be able to iterate on the items + Common::Array<SavePointData> _data; + Callback* _callbacks[40]; + + SavePoint pop(); + bool updateEntityFromData(const SavePoint &point); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SAVEPOINT_H diff --git a/engines/lastexpress/game/scenes.cpp b/engines/lastexpress/game/scenes.cpp new file mode 100644 index 0000000000..2187d331b5 --- /dev/null +++ b/engines/lastexpress/game/scenes.cpp @@ -0,0 +1,1195 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/scenes.h" + +#include "lastexpress/data/scene.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/beetle.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +SceneManager::SceneManager(LastExpressEngine *engine) : _engine(engine), + _flagNoEntity(false), _flagDrawEntities(false), _flagDrawSequences(false), _flagCoordinates(false), + _coords(0, 0, 480, 640), _clockHours(NULL), _clockMinutes(NULL) { + _sceneLoader = new SceneLoader(); +} + +SceneManager::~SceneManager() { + delete _sceneLoader; + + // Clear frames + for (Common::List<SequenceFrame *>::iterator door = _doors.begin(); door != _doors.end(); ++door) + SAFE_DELETE(*door); + + _doors.clear(); + + SAFE_DELETE(_clockHours); + SAFE_DELETE(_clockMinutes); + + // Zero-out passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Scene cache +////////////////////////////////////////////////////////////////////////// +void SceneManager::loadSceneDataFile(ArchiveIndex archive) { + // Demo only has CD2TRAIN.DAT file + if (_engine->isDemo()) + archive = kArchiveCd2; + + switch(archive) { + case kArchiveCd1: + case kArchiveCd2: + case kArchiveCd3: + if (!_sceneLoader->load(getArchive(Common::String::printf("CD%iTRAIN.DAT", archive)))) + error("SceneManager::loadSceneDataFile: cannot load data file CD%iTRAIN.DAT", archive); + break; + + default: + case kArchiveAll: + error("SceneManager::loadSceneDataFile: Invalid archive index (must be [1-3], was %d", archive); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Scene loading +////////////////////////////////////////////////////////////////////////// +void SceneManager::loadScene(SceneIndex index) { + getFlags()->flag_0 = false; + getFlags()->flag_4 = true; + + if (getState()->sceneUseBackup) { + Scene *scene = getScenes()->get(index); + + if (scene->param3 != 255) { + getState()->sceneUseBackup = false; + getState()->sceneBackup2 = kSceneNone; + } + } + + // Save shouldRedraw state and redraw if necessary + bool shouldRedraw = getFlags()->shouldRedraw; + if (shouldRedraw) { + shouldRedraw = false; + // TODO check whether we need to do that here + askForRedraw(); + //redrawScreen(); + } + + // Set the scene + setScene(index); + + // TODO Events method call (might be a low level graphic that we don't need) + + if (getFlags()->isGameRunning && getFlags()->shouldDrawEggOrHourGlass) + getInventory()->drawEgg(); + + getFlags()->shouldRedraw = shouldRedraw; + + getLogic()->updateCursor(); +} + +void SceneManager::loadSceneFromObject(ObjectIndex object, bool alternate) { + switch (object) { + default: + break; + + case kObjectCompartment1: + case kObjectCompartment2: + case kObjectCompartment3: + case kObjectCompartment4: + case kObjectCompartment5: + case kObjectCompartment6: + case kObjectCompartment7: + if (alternate) + loadSceneFromPosition(kCarGreenSleeping, (Position)(17 - (object - 1) * 2)); + else + loadSceneFromPosition(kCarGreenSleeping, (Position)(38 - (object - 1) * 2)); + break; + + case kObjectCompartmentA: + case kObjectCompartmentB: + case kObjectCompartmentC: + case kObjectCompartmentD: + case kObjectCompartmentE: + case kObjectCompartmentF: + case kObjectCompartmentG: + if (alternate) + loadSceneFromPosition(kCarGreenSleeping, (Position)(17 - (object - 32) * 2)); + else + loadSceneFromPosition(kCarRedSleeping, (Position)(38 - (object - 32) * 2)); + break; + + case kObjectCompartment8: + case kObjectCompartmentH: + loadSceneFromPosition(object == kObjectCompartment8 ? kCarGreenSleeping : kCarRedSleeping, alternate ? 3 : 25); + break; + } +} + +void SceneManager::loadSceneFromItem(InventoryItem item) { + if (item >= kPortraitOriginal) + return; + + // Get the scene index from the item + SceneIndex index = getInventory()->get(item)->scene; + if (!index) + return; + + if (!getState()->sceneUseBackup) { + getState()->sceneUseBackup = true; + getState()->sceneBackup = getState()->scene; + } + + loadScene(index); +} + +void SceneManager::loadSceneFromPosition(CarIndex car, Position position, int param3) { + loadScene(getSceneIndexFromPosition(car, position, param3)); +} + +void SceneManager::loadSceneFromItemPosition(InventoryItem item) { + if (item >= kPortraitOriginal) + return; + + // Check item location + Inventory::InventoryEntry *entry = getInventory()->get(item); + if (!entry->location) + return; + + // Reset location + entry->location = kObjectLocationNone; + + if (item != kItem3 && item != kItem5 && item != kItem7) + return; + + // Set field value + CarIndex car = kCarRestaurant; + if (item == kItem5) car = kCarRedSleeping; + if (item == kItem7) car = kCarGreenSleeping; + + if (!getEntities()->isInsideTrainCar(kEntityPlayer, car)) + return; + + if (getFlags()->flag_0) + return; + + // Get current scene position + Scene *scene = getScenes()->get(getState()->scene); + Position position = scene->position; + + if (getState()->sceneUseBackup) { + Scene *sceneBackup = getScenes()->get(getState()->sceneBackup); + position = sceneBackup->position; + } + + // Checks are different for each item + if ((item == kItem3 && position == 56) + || (item == kItem5 && (position >= 23 && position <= 32)) + || (item == kItem7 && (position == 1 || (position >= 22 && position <= 33)))) { + if (getState()->sceneUseBackup) + getState()->sceneBackup = getSceneIndexFromPosition(car, position); + else + loadSceneFromPosition(car, position); + } +} + +////////////////////////////////////////////////////////////////////////// +// Scene drawing & processing +////////////////////////////////////////////////////////////////////////// +void SceneManager::setScene(SceneIndex index) { + _flagNoEntity = false; + + if (_flagDrawEntities) { + // TODO Setup screen size (0, 80)x(480x480) (is it necessary for our animations?) + drawScene(index); + _flagNoEntity = true; + } else { + _flagDrawEntities = true; + drawScene(index); + _flagDrawEntities = false; + } +} + +void SceneManager::drawScene(SceneIndex index) { + + ////////////////////////////////////////////////////////////////////////// + // Preprocess + preProcessScene(&index); + + ////////////////////////////////////////////////////////////////////////// + // Draw background + debugC(9, kLastExpressDebugScenes, "== Drawing scene: %d ==", index); + + // Update scene + _engine->getGraphicsManager()->draw(get(index), GraphicsManager::kBackgroundC, true); + getState()->scene = index; + + ////////////////////////////////////////////////////////////////////////// + // Update entities + Scene *scene = (getState()->sceneUseBackup ? get(getState()->sceneBackup) : get(index)); + + getEntityData(kEntityPlayer)->entityPosition = scene->entityPosition; + getEntityData(kEntityPlayer)->car = scene->car; + + getFlags()->flag_3 = true; + + if (getFlags()->isGameRunning) { + getSavePoints()->pushAll(kEntityPlayer, kActionDrawScene); + getSavePoints()->process(); + + if (_flagNoEntity) + return; + + getEntities()->updateFields(); + getEntities()->updateSequences(); + getEntities()->updateCallbacks(); + } + + ////////////////////////////////////////////////////////////////////////// + // Show the scene + askForRedraw(); + redrawScreen(); + + //////////////////////////////////////////////////////////// + // Post process scene + postProcessScene(); +} + +void SceneManager::processScene() { + if (!getState()->sceneUseBackup) { + loadScene(getState()->scene); + return; + } + + getState()->sceneUseBackup = false; + + // Select item if needed + InventoryItem item = getInventory()->getFirstExaminableItem(); + if (item && getInventory()->getSelectedItem() == item) + getInventory()->selectItem(item); + + Scene *backup = getScenes()->get(getState()->sceneBackup); + + if (getEntities()->getPosition(backup->car, backup->position)) + loadScene(processIndex(getState()->sceneBackup)); + else + loadScene(getState()->sceneBackup); +} + +LastExpress::SceneIndex SceneManager::processIndex(SceneIndex index) { + Scene *scene = get(index); + CarIndex car = scene->car; + + switch (car) { + default: + break; + + case kCarRedSleeping: + if (checkPosition(index, kCheckPositionLookingAtDoors)) { + Position position = (Position)(scene->position + (checkPosition(kSceneNone, kCheckPositionLookingUp) ? -1 : 1)); + + if (position == 4) + position = 3; + + if (position == 24) + position = 25; + + if (getEntities()->getPosition(car, position)) + return index; + else + return getSceneIndexFromPosition(car, position); + } else { + switch (scene->position) { + default: + break; + + case 41: + case 51: + if (!getEntities()->getPosition(car, 39)) + return getSceneIndexFromPosition(car, 39); + // Fallback to next case + + case 42: + case 52: + if (!getEntities()->getPosition(car, 14)) + return getSceneIndexFromPosition(car, 14); + // Fallback to next case + + case 43: + case 53: + if (!getEntities()->getPosition(car, 35)) + return getSceneIndexFromPosition(car, 35); + // Fallback to next case + + case 44: + case 54: + if (!getEntities()->getPosition(car, 10)) + return getSceneIndexFromPosition(car, 10); + // Fallback to next case + + case 45: + case 55: + if (!getEntities()->getPosition(car, 32)) + return getSceneIndexFromPosition(car, 32); + // Fallback to next case + + case 46: + case 56: + if (!getEntities()->getPosition(car, 7)) + return getSceneIndexFromPosition(car, 7); + // Fallback to next case + + case 47: + case 57: + if (!getEntities()->getPosition(car, 27)) + return getSceneIndexFromPosition(car, 27); + // Fallback to next case + + case 48: + case 58: + if (!getEntities()->getPosition(car, 2)) + return getSceneIndexFromPosition(car, 2); + break; + } + } + break; + + case kCarRestaurant: + switch (scene->position) { + default: + break; + + case 52: + case 53: + case 54: + if (!getEntities()->getPosition(car, 51)) + return getSceneIndexFromPosition(car, 51); + // Fallback to next case + + case 50: + case 56: + case 57: + case 58: + if (!getEntities()->getPosition(car, 55)) + return getSceneIndexFromPosition(car, 55); + // Fallback to next case + + case 59: + if (!getEntities()->getPosition(car, 60)) + return getSceneIndexFromPosition(car, 60); + // Fallback to next case + + case 60: + if (!getEntities()->getPosition(car, 59)) + return getSceneIndexFromPosition(car, 59); + // Fallback to next case + + case 62: + case 63: + case 64: + if (!getEntities()->getPosition(car, 61)) + return getSceneIndexFromPosition(car, 61); + // Fallback to next case + + case 66: + case 67: + case 68: + if (!getEntities()->getPosition(car, 65)) + return getSceneIndexFromPosition(car, 65); + // Fallback to next case + + case 69: + case 71: + if (!getEntities()->getPosition(car, 70)) + return getSceneIndexFromPosition(car, 70); + break; + } + break; + } + + return index; +} + +////////////////////////////////////////////////////////////////////////// +// Checks +////////////////////////////////////////////////////////////////////////// +bool SceneManager::checkPosition(SceneIndex index, CheckPositionType type) const { + Scene *scene = getScenes()->get((index ? index : getState()->scene)); + + CarIndex car = (CarIndex)scene->car; + Position position = scene->position; + + bool isInSleepingCar = (car == kCarGreenSleeping || car == kCarRedSleeping); + + switch (type) { + default: + error("SceneManager::checkPosition: Invalid position type: %d", type); + + case kCheckPositionLookingUp: + return isInSleepingCar && (position >= 1 && position <= 19); + + case kCheckPositionLookingDown: + return isInSleepingCar && (position >= 21 && position <= 40); + + case kCheckPositionLookingAtDoors: + return isInSleepingCar && ((position >= 2 && position <= 17) || (position >= 23 && position <= 39)); + + case kCheckPositionLookingAtClock: + return car == kCarRestaurant && position == 81; + } +} + +bool SceneManager::checkCurrentPosition(bool doCheckOtherCars) const { + Scene *scene = getScenes()->get(getState()->scene); + + Position position = scene->position; + CarIndex car = (CarIndex)scene->car; + + if (!doCheckOtherCars) + return (car == kCarGreenSleeping || car == kCarRedSleeping) + && ((position >= 41 && position <= 48) || (position >= 51 && position <= 58)); + + if (position == 99) + return true; + + switch (car){ + default: + break; + + case kCarGreenSleeping: + case kCarRedSleeping: + case kCarLocomotive: + if ((position >= 1 && position <= 18) || (position >= 22 && position <= 40)) + return true; + break; + + case kCarRestaurant: + if (position >= 73 && position <= 80) + return true; + + if (position == 10 || position == 11) + return true; + + break; + + case kCarBaggage: + switch (position) { + default: + break; + + case 10: + case 11: + case 80: + case 81: + case 82: + case 83: + case 84: + case 90: + case 91: + return true; + } + break; + + case kCarCoalTender: + if (position == 2 || position == 10 || position == 11) + return true; + break; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////// +// Train +////////////////////////////////////////////////////////////////////////// +void SceneManager::updateDoorsAndClock() { + // Clear all sequences from the list + for (Common::List<SequenceFrame *>::iterator door = _doors.begin(); door != _doors.end(); ++door) { + removeFromQueue(*door); + setCoordinates(*door); + SAFE_DELETE(*door); + } + + // Cleanup doors sequences + _doors.clear(); + + if (_clockHours) { + removeFromQueue(_clockHours); + setCoordinates(_clockHours); + SAFE_DELETE(_clockHours); + } + + if (_clockMinutes) { + removeFromQueue(_clockMinutes); + setCoordinates(_clockMinutes); + SAFE_DELETE(_clockMinutes); + } + + // Queue doors sequences for display + if (checkPosition(kSceneNone, kCheckPositionLookingAtDoors)) { + + ObjectIndex firstIndex = kObjectNone; + + // Init objectIndex (or exit if not in one of the two compartment cars + if (getEntityData(kEntityPlayer)->car == kCarGreenSleeping) + firstIndex = kObjectCompartment1; + else if (getEntityData(kEntityPlayer)->car == kCarRedSleeping) + firstIndex = kObjectCompartmentA; + else + return; + + // Iterate over each door + for (ObjectIndex index = firstIndex; index < (ObjectIndex)(firstIndex + 8); index = (ObjectIndex)(index + 1)) { + + // Doors is not open, nothing to do + if (getObjects()->get(index).location != kObjectLocation2) + continue; + + // Load door sequence + Scene *scene = getScenes()->get(getState()->scene); + Common::String name = Common::String::printf("633X%c-%02d.seq", (index - firstIndex) + 65, scene->position); + Sequence *sequence = loadSequence1(name, 255); + + // If the sequence doesn't exists, skip + if (!sequence || !sequence->isLoaded()) + continue; + + // Adjust frame data and store in frame list + SequenceFrame *frame = new SequenceFrame(sequence, 0, true); + frame->getInfo()->location = (checkPosition(kSceneNone, kCheckPositionLookingUp) ? (firstIndex - index) - 1 : (index - firstIndex) - 8); + + _doors.push_back(frame); + + // Add frame to list + addToQueue(frame); + } + } + + // Queue clock sequences for display + if (checkPosition(kSceneNone, kCheckPositionLookingAtClock)) { + // Only used in scene 349 to show the hands on the clock + + Sequence *sequenceHours = loadSequence1("SCLKH-81.seq", 255); + Sequence *sequenceMinutes = loadSequence1("SCLKM-81.seq", 255); + + // Compute hours and minutes indexes + uint16 hoursIndex = getState()->time % 1296000 % 54000 / 900; + + uint hours = (getState()->time % 1296000) / 54000; + if (hours >= 12) + hours -= 12; + + uint16 minutesIndex = (uint16)(5 * hours + hoursIndex / 12); + + // Adjust z-order and store sequences to list + _clockHours = new SequenceFrame(sequenceHours, hoursIndex, true); + _clockHours->getInfo()->location = 65534; + + _clockMinutes = new SequenceFrame(sequenceMinutes, minutesIndex, true); + _clockMinutes->getInfo()->location = 65535; + + addToQueue(_clockHours); + addToQueue(_clockMinutes); + } +} + +void SceneManager::resetDoorsAndClock() { + for (Common::List<SequenceFrame *>::iterator door = _doors.begin(); door != _doors.end(); ++door) + SAFE_DELETE(*door); + + _doors.clear(); + + SAFE_DELETE(_clockHours); + SAFE_DELETE(_clockMinutes); + + // Remove the beetle sequences too if needed + getBeetle()->unload(); +} + +////////////////////////////////////////////////////////////////////////// +// Sequence list +////////////////////////////////////////////////////////////////////////// +void SceneManager::drawFrames(bool refreshScreen) { + if (!_flagDrawSequences) + return; + + // TODO handle flag coordinates + + clearBg(GraphicsManager::kBackgroundOverlay); + + for (Common::List<SequenceFrame *>::iterator i = _queue.begin(); i != _queue.end(); ++i) + _engine->getGraphicsManager()->draw(*i, GraphicsManager::kBackgroundOverlay); + + if (refreshScreen) { + askForRedraw(); + //redrawScreen(); + + _flagDrawSequences = false; + } +} + +void SceneManager::addToQueue(SequenceFrame * const frame) { + if (!frame) + return; + + // First check that the frame is not already in the queue + for (Common::List<SequenceFrame *>::iterator i = _queue.begin(); i != _queue.end(); ++i) { + if (frame->equal(*i)) + return; + } + + debugC(8, kLastExpressDebugGraphics, "Adding frame: %s / %d", frame->getName().c_str(), frame->getFrame()); + + // Set flag + _flagDrawSequences = true; + + // Queue empty: just insert the frame + if (_queue.empty()) { + _queue.push_back(frame); + return; + } + + // Frame is closer: insert in first place + if (frame->getInfo()->location > _queue.front()->getInfo()->location) { + _queue.push_front(frame); + return; + } + + // Insert the frame in the queue based on location + for (Common::List<SequenceFrame *>::iterator i = _queue.begin(); i != _queue.end(); ++i) { + if (frame->getInfo()->location > (*i)->getInfo()->location) { + _queue.insert(i, frame); + return; + } + } + + // We are the last frame in location order, insert at the back of the queue + _queue.push_back(frame); +} + +void SceneManager::removeFromQueue(SequenceFrame *frame) { + if (!frame) + return; + + debugC(8, kLastExpressDebugGraphics, "Removing frame: %s / %d", frame->getName().c_str(), frame->getFrame()); + + // Check that the frame is in the queue and remove it + for (Common::List<SequenceFrame *>::iterator i = _queue.begin(); i != _queue.end(); ++i) { + if (frame->equal(*i)) { + _queue.erase(i); + _flagDrawSequences = true; + break; + } + } +} + +void SceneManager::removeAndRedraw(SequenceFrame **frame, bool doRedraw) { + if (!frame) + return; + + removeFromQueue(*frame); + + if (doRedraw) + drawFrames(true); + + SAFE_DELETE(*frame); +} + +void SceneManager::resetQueue() { + _flagDrawSequences = true; + + // The original engine only deletes decompressed data, not the "sequences" since they are just pointers to a memory pool + _queue.clear(); +} + +void SceneManager::setCoordinates(SequenceFrame *frame) { + + if (!frame || frame->getInfo()->subType == 3) + return; + + _flagCoordinates = true; + + if (_coords.right > (int)frame->getInfo()->xPos1) + _coords.right = (int16)frame->getInfo()->xPos1; + + if (_coords.bottom > (int)frame->getInfo()->yPos1) + _coords.bottom = (int16)frame->getInfo()->yPos1; + + if (_coords.left < (int)frame->getInfo()->xPos2) + _coords.left = (int16)frame->getInfo()->xPos2; + + if (_coords.top < (int)frame->getInfo()->yPos2) + _coords.top = (int16)frame->getInfo()->yPos2; +} + +void SceneManager::resetCoordinates() { + _coords.top = 0; + _coords.left = 0; + _coords.bottom = 480; + _coords.right = 640; + + _flagCoordinates = false; +} + +////////////////////////////////////////////////////////////////////////// +// Helpers +////////////////////////////////////////////////////////////////////////// +SceneIndex SceneManager::getSceneIndexFromPosition(CarIndex car, Position position, int param3) { + // Probably can't happen (can we be called during cd-swap?) + if (_sceneLoader->count() <= 1) + return getState()->scene; + + SceneIndex index = kSceneMenu; + + Scene *firstScene = getScenes()->get(index); + + while (firstScene->car != car + || firstScene->position != position + || ((param3 != -1 || firstScene->param3) && firstScene->param3 != param3 && firstScene->type != Scene::kTypeItem3)) { + + // Increment index and look at the next scene + index = (SceneIndex)(index + 1); + + if (index >= _sceneLoader->count()) + return getState()->scene; + + // Load the next scene + firstScene = getScenes()->get(index); + } + + // Process index if necessary + Scene *scene = getScenes()->get(index); + if (getEntities()->getPosition(scene->car, scene->position)) + return processIndex(index); + + return index; +} + +////////////////////////////////////////////////////////////////////////// +// Scene processing +////////////////////////////////////////////////////////////////////////// + +// Process hotspots +// - if it returns kSceneInvalid, the hotspot scene has not been modified +// - if it returns kSceneNone, it has been modified +// +// Note: we use the original hotspot scene to pre-process again +#define PROCESS_HOTSPOT_SCENE(hotspot, index) { \ + SceneIndex processedScene = getAction()->processHotspot(*hotspot); \ + SceneIndex testScene = (processedScene == kSceneInvalid) ? (hotspot)->scene : processedScene; \ + if (testScene) { \ + *index = (hotspot)->scene; \ + preProcessScene(index); \ + } \ +} + +void SceneManager::preProcessScene(SceneIndex *index) { + + // Check index validity + if (*index == 0 || *index > 2500) + *index = kSceneMenu; + + Scene *scene = getScenes()->get(*index); + + switch (scene->type) { + case Scene::kTypeObject: { + ObjectIndex object = (ObjectIndex)scene->param1; + + if (object >= kObjectMax) + break; + + if (getObjects()->get(object).location == kObjectLocationNone) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (getObjects()->get(object).location != (*it)->location) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeItem: { + InventoryItem item = (InventoryItem)scene->param1; + + if (item >= kPortraitOriginal) + break; + + if (getInventory()->get(item)->location == kObjectLocationNone) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (getInventory()->get(item)->location != (*it)->location) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeItem2: { + InventoryItem item1 = (InventoryItem)scene->param1; + InventoryItem item2 = (InventoryItem)scene->param2; + + if (item1 >= kPortraitOriginal || item2 >= kPortraitOriginal) + break; + + int location = kObjectLocationNone; + + if (getInventory()->get(item1)->location != kObjectLocationNone) + location = kObjectLocation1; + + if (getInventory()->get(item2)->location != kObjectLocationNone) + location |= kObjectLocation2; + + if (!location) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (location != (*it)->location) + continue; + + if (getInventory()->get(item1)->location != (*it)->param1) + continue; + + if (getInventory()->get(item2)->location != (*it)->param2) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeObjectItem: { + ObjectIndex object = (ObjectIndex)scene->param1; + InventoryItem item = (InventoryItem)scene->param2; + + if (object >= kObjectMax) + break; + + if (item >= kPortraitOriginal) + break; + + int location = kObjectLocationNone; + + if (getObjects()->get(object).location == kObjectLocation2) + location = kObjectLocation1; + + if (getInventory()->get(item)->location != kObjectLocationNone) + location |= kObjectLocation2; + + if (!location) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (location != (*it)->location) + continue; + + if (getObjects()->get(object).location != (*it)->param1) + continue; + + if (getInventory()->get(item)->location != (*it)->param2) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeItem3: { + InventoryItem item1 = (InventoryItem)scene->param1; + InventoryItem item2 = (InventoryItem)scene->param2; + InventoryItem item3 = (InventoryItem)scene->param3; + + if (item1 >= kPortraitOriginal || item2 >= kPortraitOriginal || item3 >= kPortraitOriginal) + break; + + int location = kObjectLocationNone; + + if (getInventory()->get(item1)->location != kObjectLocationNone) + location = kObjectLocation1; + + if (getInventory()->get(item2)->location != kObjectLocationNone) + location |= kObjectLocation2; + + if (getInventory()->get(item3)->location != kObjectLocationNone) + location |= kObjectLocation4; + + if (!location) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (location != (*it)->location) + continue; + + if (getInventory()->get(item1)->location != (*it)->param1) + continue; + + if (getInventory()->get(item2)->location != (*it)->param2) + continue; + + if (getInventory()->get(item3)->location != (*it)->param3) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeObjectLocation2: { + ObjectIndex object = (ObjectIndex)scene->param1; + + if (object >= kObjectMax) + break; + + bool found = false; + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (getObjects()->get(object).location2 != (*it)->location) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + found = true; + break; + } + + // If we haven't found a proper hotspot, use the first hotspot from the current scene + if (!found) { + Scene *sceneHotspot = getScenes()->get(*index); + SceneHotspot *hotspot = sceneHotspot->getHotspot(); + + PROCESS_HOTSPOT_SCENE(hotspot, index); + } + break; + } + + case Scene::kTypeCompartments: + case Scene::kTypeCompartmentsItem: + if (scene->param1 >= 16) + break; + + if (getEntities()->getCompartments(scene->param1) || getEntities()->getCompartments1(scene->param1)) { + + Scene *currentScene = getScenes()->get(getState()->scene); + + if ((checkPosition(getState()->scene, kCheckPositionLookingUp) && checkPosition(*index, kCheckPositionLookingUp) && currentScene->entityPosition < scene->entityPosition) + || (checkPosition(getState()->scene, kCheckPositionLookingDown) && checkPosition(*index, kCheckPositionLookingDown) && currentScene->entityPosition > scene->entityPosition)) { + + if (State::getPowerOfTwo((uint32)getEntities()->getCompartments(scene->param1)) != 30 + && State::getPowerOfTwo((uint32)getEntities()->getCompartments1(scene->param1)) != 30 ) + getSound()->playSound(kEntityPlayer, "CAT1126A"); + + *index = scene->getHotspot()->scene; + } else { + *index = scene->getHotspot(1)->scene; + } + + preProcessScene(index); + } else { + // Stop processing here for kTypeCompartments + if (scene->type == Scene::kTypeCompartments) + break; + + InventoryItem item = (InventoryItem)scene->param2; + if (item >= kPortraitOriginal) + break; + + if (getInventory()->get(item)->location == kObjectLocationNone) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (getInventory()->get(item)->location != (*it)->location) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + } + break; + + default: + break; + } + + // Sound processing + Scene *newScene = getScenes()->get(*index); + if (getSound()->isBuffered(kEntityTables4)) { + if (newScene->type != Scene::kTypeReadText || newScene->param1) + getSound()->processEntry(kEntityTables4); + } + + // Cleanup beetle sequences + if (getBeetle()->isLoaded()) { + if (newScene->type != Scene::kTypeLoadBeetleSequences) + getBeetle()->unload(); + } +} + +void SceneManager::postProcessScene() { + + Scene *scene = getScenes()->get(getState()->scene); + + switch (scene->type) { + case Scene::kTypeList: { + + // Adjust time + getState()->time += (scene->param1 + 10) * getState()->timeDelta; + getState()->timeTicks += (scene->param1 + 10); + + // Wait for a number of frames unless right mouse is clicked + uint32 nextFrameCount = getFrameCount() + 4 * scene->param1; + if (!getFlags()->mouseRightClick) { + while (nextFrameCount > getFrameCount()) { + _engine->pollEvents(); + + if (getFlags()->mouseRightClick) + break; + + getSound()->updateQueue(); + getSound()->updateSubtitles(); + } + } + + // Process hotspots and load scenes in the list + SceneHotspot *hotspot = scene->getHotspot(); + SceneIndex processedScene = getAction()->processHotspot(*hotspot); + SceneIndex testScene = (processedScene == kSceneInvalid) ? hotspot->scene : processedScene; + + if (getFlags()->mouseRightClick) { + + while (getScenes()->get(testScene)->type == Scene::kTypeList) { + hotspot = getScenes()->get(testScene)->getHotspot(); + processedScene = getAction()->processHotspot(*hotspot); + testScene = (processedScene == kSceneInvalid) ? hotspot->scene : processedScene; + } + } + + // If several entities are there, choose one to sound "Excuse me" + EntityPosition entityPosition = getEntityData(kEntityPlayer)->entityPosition; + if (getEntityData(kEntityPlayer)->car == kCar9 && (entityPosition == kPosition_4 || entityPosition == kPosition_3)) { + EntityIndex entities[39]; + + // Init entities + entities[0] = kEntityPlayer; + + uint progress = 0; + + for (uint i = 1; i < 40 /* number of entities */; i++) { + CarIndex car = getEntityData((EntityIndex)i)->car; + EntityPosition position = getEntityData((EntityIndex)i)->entityPosition; + + if (entityPosition == kPosition_4) { + if ((car == kCarRedSleeping && position > kPosition_9270) || (car == kCarRestaurant && position < kPosition_1540)) + entities[progress++] = (EntityIndex)i; + } else { + if ((car == kCarGreenSleeping && position > kPosition_9270) || (car == kCarRedSleeping && position < kPosition_850)) + entities[progress++] = (EntityIndex)i; + } + } + + if (progress) + getSound()->excuseMe((progress == 1) ? entities[0] : entities[rnd(progress)], kEntityPlayer, SoundManager::kFlagDefault); + } + + if (hotspot->scene) + setScene(hotspot->scene); + break; + } + + case Scene::kTypeSavePointChapter: + if (getProgress().field_18 == 2) + getSavePoints()->push(kEntityPlayer, kEntityChapters, kActionEndChapter); + break; + + case Scene::kTypeLoadBeetleSequences: + if ((getProgress().chapter == kChapter2 || getProgress().chapter == kChapter3) + && getInventory()->get(kItemBeetle)->location == kObjectLocation3) { + if (!getBeetle()->isLoaded()) + getBeetle()->load(); + } + break; + + case Scene::kTypeGameOver: + if (getState()->time >= kTimeCityGalanta || getProgress().field_18 == 4) + break; + + getSound()->processEntry(SoundManager::kSoundType7); + getSound()->playSound(kEntityTrain, "LIB050", SoundManager::kFlagDefault); + + switch (getProgress().chapter) { + default: + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneGameOverPolice2, true); + break; + + case kChapter1: + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneGameOverAlarm, true); + break; + + case kChapter4: + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneGameOverAlarm2, true); + break; + } + break; + + case Scene::kTypeReadText: + getSound()->readText(scene->param1); + break; + + case Scene::kType133: + if (getFlags()->flag_0) { + getFlags()->flag_0 = false; + getFlags()->shouldRedraw = true; + getLogic()->updateCursor(); + } + break; + + default: + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/scenes.h b/engines/lastexpress/game/scenes.h new file mode 100644 index 0000000000..b70526839f --- /dev/null +++ b/engines/lastexpress/game/scenes.h @@ -0,0 +1,120 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SCENEMANAGER_H +#define LASTEXPRESS_SCENEMANAGER_H + +#include "lastexpress/data/scene.h" + +#include "common/hashmap.h" +#include "common/list.h" + +namespace LastExpress { + +class LastExpressEngine; +class SceneLoader; +class Sequence; +class SequenceFrame; + +class SceneManager { +public: + enum CheckPositionType { + kCheckPositionLookingUp, + kCheckPositionLookingDown, + kCheckPositionLookingAtDoors, + kCheckPositionLookingAtClock + }; + + SceneManager(LastExpressEngine *engine); + ~SceneManager(); + + // Scene cache + void loadSceneDataFile(ArchiveIndex archive); + Scene *get(SceneIndex sceneIndex) { return _sceneLoader->get(sceneIndex); } + + // Scene loading + void setScene(SceneIndex sceneIndex); + void loadScene(SceneIndex sceneIndex); + void loadSceneFromObject(ObjectIndex object, bool alternate = false); + void loadSceneFromItem(InventoryItem item); + void loadSceneFromItemPosition(InventoryItem item); + void loadSceneFromPosition(CarIndex car, Position position, int param3 = -1); + + // Scene drawing & processing + void drawScene(SceneIndex sceneIndex); + void processScene(); + SceneIndex processIndex(SceneIndex sceneIndex); + + // Checks + bool checkPosition(SceneIndex sceneIndex, CheckPositionType type) const; + bool checkCurrentPosition(bool doCheckOtherCars) const; + + // Train + void updateDoorsAndClock(); + void resetDoorsAndClock(); + + // Sequence queue + void drawFrames(bool refreshScreen); + void addToQueue(SequenceFrame * const frame); + void removeFromQueue(SequenceFrame *frame); + void removeAndRedraw(SequenceFrame **frame, bool doRedraw); + void resetQueue(); + void setCoordinates(SequenceFrame *frame); + + // Helpers + SceneIndex getSceneIndexFromPosition(CarIndex car, Position position, int param3 = -1); + + void setFlagDrawSequences() { _flagDrawSequences = true; } + +private: + LastExpressEngine *_engine; + SceneLoader *_sceneLoader; ///< Scene loader + + // Flags + bool _flagNoEntity; + bool _flagDrawEntities; + bool _flagDrawSequences; + bool _flagCoordinates; + + Common::Rect _coords; + + // Train sequences + Common::List<SequenceFrame *> _doors; + SequenceFrame *_clockHours; + SequenceFrame *_clockMinutes; + + // Sequence queue + Common::List<SequenceFrame *> _queue; + + // Scene processing + void preProcessScene(SceneIndex *index); + void postProcessScene(); + + void resetCoordinates(); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SCENEMANAGER_H diff --git a/engines/lastexpress/game/sound.cpp b/engines/lastexpress/game/sound.cpp new file mode 100644 index 0000000000..f8b45bb385 --- /dev/null +++ b/engines/lastexpress/game/sound.cpp @@ -0,0 +1,1676 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/sound.h" + +#include "lastexpress/data/snd.h" +#include "lastexpress/data/subtitle.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +// Letters & messages +const char *messages[24] = { + "", + "TXT1001", // 1 + "TXT1001A", // 2 + "TXT1011", // 3 + "TXT1012", // 4 + "TXT1013", // 5 + "TXT1014", // 6 + "TXT1020", // 7 + "TXT1030", // 8 + "END1009B", // 50 + "END1046", // 51 + "END1047", // 52 + "END1112", // 53 + "END1112A", // 54 + "END1503", // 55 + "END1505A", // 56 + "END1505B", // 57 + "END1610", // 58 + "END1612A", // 59 + "END1612C", // 61 + "END1612D", // 62 + "ENDALRM1", // 63 + "ENDALRM2", // 64 + "ENDALRM3" // 65 +}; + +const char *cities[17] = { + "EPERNAY", + "CHALONS", + "BARLEDUC", + "NANCY", + "LUNEVILL", + "AVRICOUR", + "DEUTSCHA", + "STRASBOU", + "BADENOOS", + "SALZBURG", + "ATTNANG", + "WELS", + "LINZ", + "VIENNA", + "POZSONY", + "GALANTA", + "POLICE" +}; + +const char *locomotiveSounds[5] = { + "ZFX1005", + "ZFX1006", + "ZFX1007", + "ZFX1007A", + "ZFX1007B" +}; + +static const SoundManager::FlagType soundFlags[32] = { + SoundManager::kFlagDefault, SoundManager::kFlag15, SoundManager::kFlag14, SoundManager::kFlag13, SoundManager::kFlag12, + SoundManager::kFlag11, SoundManager::kFlag11, SoundManager::kFlag10, SoundManager::kFlag10, SoundManager::kFlag9, SoundManager::kFlag9, SoundManager::kFlag8, SoundManager::kFlag8, + SoundManager::kFlag7, SoundManager::kFlag7, SoundManager::kFlag7, SoundManager::kFlag6, SoundManager::kFlag6, SoundManager::kFlag6, + SoundManager::kFlag5, SoundManager::kFlag5, SoundManager::kFlag5, SoundManager::kFlag5, SoundManager::kFlag4, SoundManager::kFlag4, SoundManager::kFlag4, SoundManager::kFlag4, + SoundManager::kFlag3, SoundManager::kFlag3, SoundManager::kFlag3, SoundManager::kFlag3, SoundManager::kFlag3 +}; + +SoundManager::SoundManager(LastExpressEngine *engine) : _engine(engine), _state(0), _currentType(kSoundType16), _flag(0) { + _soundStream = new StreamedSound(); + + // Initialize unknown data + _data0 = 0; + _data1 = 0; + _data2 = 0; + + memset(&_buffer, 0, sizeof(_buffer)); + memset(&_lastWarning, 0, sizeof(_lastWarning)); +} + +SoundManager::~SoundManager() { + delete _soundStream; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Timer +////////////////////////////////////////////////////////////////////////// +void SoundManager::handleTimer() { + + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + SoundEntry *entry = (*i); + if (entry->stream == NULL) { + i = _cache.reverse_erase(i); + continue; + } else if (!entry->isStreamed) { + entry->isStreamed = true; + _soundStream->load(entry->stream); + } + } + + // TODO: stream any sound in the queue after filtering +} + +////////////////////////////////////////////////////////////////////////// +// Sound queue management +////////////////////////////////////////////////////////////////////////// +void SoundManager::updateQueue() { + //warning("Sound::unknownFunction1: not implemented!"); +} + +void SoundManager::resetQueue(SoundType type1, SoundType type2) { + if (!type2) + type2 = type1; + + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->type != type1 && (*i)->type != type2) + resetEntry(*i); + } +} + +void SoundManager::removeFromQueue(EntityIndex entity) { + SoundEntry *entry = getEntry(entity); + + if (entry) + resetEntry(entry); +} + +void SoundManager::removeFromQueue(Common::String filename) { + SoundEntry *entry = getEntry(filename); + + if (entry) + resetEntry(entry); +} + +void SoundManager::clearQueue() { + _flag |= 4; + + // Wait a while for a flag to be set + for (int i = 0; i < 3000000; i++) + if (_flag & 8) + break; + + _flag |= 8; + + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + SoundEntry *entry = (*i); + + removeEntry(entry); + + // Delete entry + SAFE_DELETE(entry); + i = _cache.reverse_erase(i); + } + + updateSubtitles(); +} + +bool SoundManager::isBuffered(EntityIndex entity) { + return (getEntry(entity) != NULL); +} + +bool SoundManager::isBuffered(Common::String filename, bool testForEntity) { + SoundEntry *entry = getEntry(filename); + + if (testForEntity) + return entry != NULL && !entry->entity; + + return (entry != NULL); +} + +////////////////////////////////////////////////////////////////////////// +// Entry +////////////////////////////////////////////////////////////////////////// +void SoundManager::setupEntry(SoundEntry *entry, Common::String name, FlagType flag, int a4) { + if (!entry) + error("SoundManager::setupEntry: Invalid entry!"); + + entry->field_4C = a4; + setEntryType(entry, flag); + setEntryStatus(entry, flag); + + // Add entry to cache + _cache.push_back(entry); + + setupCache(entry); + loadSoundData(entry, name); +} + +void SoundManager::setEntryType(SoundEntry *entry, FlagType flag) { + switch (flag & kFlagType7) { + default: + case kFlagNone: + entry->type = _currentType; + _currentType = (SoundType)(_currentType + 1); + break; + + case kFlagType1_2: { + SoundEntry *previous2 = getEntry(kSoundType2); + if (previous2) + updateEntry(previous2, 0); + + SoundEntry *previous = getEntry(kSoundType1); + if (previous) { + previous->type = kSoundType2; + updateEntry(previous, 0); + } + + entry->type = kSoundType1; + } + break; + + case kFlagType3: { + SoundEntry *previous = getEntry(kSoundType3); + if (previous) { + previous->type = kSoundType4; + updateEntry(previous, 0); + } + + entry->type = kSoundType11; + } + break; + + case kFlagType7: { + SoundEntry *previous = getEntry(kSoundType7); + if (previous) + previous->type = kSoundType8; + + entry->type = kSoundType7; + } + break; + + case kFlagType9: { + SoundEntry *previous = getEntry(kSoundType9); + if (previous) + previous->type = kSoundType10; + + entry->type = kSoundType9; + } + break; + + case kFlagType11: { + SoundEntry *previous = getEntry(kSoundType11); + if (previous) + previous->type = kSoundType14; + + entry->type = kSoundType11; + } + break; + + case kFlagType13: { + SoundEntry *previous = getEntry(kSoundType13); + if (previous) + previous->type = kSoundType14; + + entry->type = kSoundType13; + } + break; + } +} + +void SoundManager::setEntryStatus(SoundEntry *entry, FlagType flag) const { + SoundStatus status = (SoundStatus)flag; + if (!((status & 0xFF) & kSoundStatusClear1)) + status = (SoundStatus)(status | kSoundStatusClear2); + + if (((status & 0xFF00) >> 8) & kSoundStatusClear0) + entry->status.status = (uint32)status; + else + entry->status.status = (status | kSoundStatusClear4); +} + +bool SoundManager::setupCache(SoundEntry *entry) { + warning("Sound::updateCache: not implemented!"); + return true; +} + +void SoundManager::clearStatus() { + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) + (*i)->status.status |= kSoundStatusClear3; +} + +void SoundManager::loadSoundData(SoundEntry *entry, Common::String name) { + entry->name2 = name; + + // Load sound data + entry->stream = getArchive(name); + + if (!entry->stream) + entry->stream = getArchive("DEFAULT.SND"); + + if (entry->stream) { + warning("Sound::loadSoundData: not implemented!"); + } else { + entry->status.status = kSoundStatusRemoved; + } +} + +void SoundManager::resetEntry(SoundEntry * entry) const { + entry->status.status |= kSoundStatusRemoved; + entry->entity = kEntityPlayer; + + if (entry->stream) { + if (!entry->isStreamed) + SAFE_DELETE(entry->stream); + + entry->stream = NULL; + } +} + + +void SoundManager::removeEntry(SoundEntry *entry) { + entry->status.status |= kSoundStatusRemoved; + + // Loop until ready + while (!(entry->status.status1 & 4) && !(_flag & 8) && (_flag & 1)); + + // The original game remove the entry from the cache here, + // but since we are called from within an iterator loop + // we will remove the entry there + // removeFromCache(entry); + + if (entry->subtitle) { + drawSubtitles(entry->subtitle); + SAFE_DELETE(entry->subtitle); + } + + if (entry->entity) { + if (entry->entity == kEntitySteam) + playLoopingSound(); + else if (entry->entity != kEntityTrain) + getSavePoints()->push(kEntityPlayer, entry->entity, kActionEndSound); + } +} + +void SoundManager::updateEntry(SoundEntry *entry, uint value) const { + if (!(entry->status.status3 & 64)) { + + int value2 = value; + + entry->status.status |= kSoundStatus_100000; + + if (value) { + if (_flag & 32) { + entry->field_40 = value; + value2 = value * 2 + 1; + } + + entry->field_3C = value2; + } else { + entry->field_3C = 0; + entry->status.status |= kSoundStatus_40000000; + } + } +} + +void SoundManager::updateEntryState(SoundEntry *entry) const { + if (_flag & 32) { + if (entry->type != kSoundType9 && entry->type != kSoundType7 && entry->type != kSoundType5) { + uint32 status = entry->status.status & kSoundStatusClear1; + + entry->status.status &= kSoundStatusClearAll; + + entry->field_40 = status; + entry->status.status |= status * 2 + 1; + } + } + + entry->status.status |= kSoundStatus_20; +} + +void SoundManager::processEntry(EntityIndex entity) { + SoundEntry *entry = getEntry(entity); + + if (entry) { + updateEntry(entry, 0); + entry->entity = kEntityPlayer; + } +} + +void SoundManager::processEntry(SoundType type) { + SoundEntry *entry = getEntry(type); + + if (entry) + updateEntry(entry, 0); +} + +void SoundManager::setupEntry(SoundType type, EntityIndex index) { + SoundEntry *entry = getEntry(type); + + if (entry) + entry->entity = index; +} + +void SoundManager::processEntry(Common::String filename) { + SoundEntry *entry = getEntry(filename); + + if (entry) { + updateEntry(entry, 0); + entry->entity = kEntityPlayer; + } +} + +void SoundManager::processEntries() { + _state = 0; + + processEntry(kSoundType1); + processEntry(kSoundType2); +} + +////////////////////////////////////////////////////////////////////////// +// Misc +////////////////////////////////////////////////////////////////////////// + +void SoundManager::unknownFunction4() { + warning("Sound::unknownFunction4: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Entry search +////////////////////////////////////////////////////////////////////////// +SoundManager::SoundEntry *SoundManager::getEntry(EntityIndex index) { + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->entity == index) + return *i; + } + + return NULL; +} + +SoundManager::SoundEntry *SoundManager::getEntry(Common::String name) { + if (!name.contains('.')) + name += ".SND"; + + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->name2 == name) + return *i; + } + + return NULL; +} + +SoundManager::SoundEntry *SoundManager::getEntry(SoundType type) { + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->type == type) + return *i; + } + + return NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Savegame +////////////////////////////////////////////////////////////////////////// +void SoundManager::saveLoadWithSerializer(Common::Serializer &ser) { + error("Sound::saveLoadWithSerializer: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Game-related functions +////////////////////////////////////////////////////////////////////////// +void SoundManager::playSound(EntityIndex entity, Common::String filename, FlagType flag, byte a4) { + if (isBuffered(entity) && entity) + removeFromQueue(entity); + + FlagType currentFlag = (flag == -1) ? getSoundFlag(entity) : (FlagType)(flag | 0x80000); + + // Add .SND at the end of the filename if needed + if (!filename.contains('.')) + filename += ".SND"; + + if (!playSoundWithSubtitles(filename, currentFlag, entity, a4)) + if (entity) + getSavePoints()->push(kEntityPlayer, entity, kActionEndSound); +} + +SoundManager::SoundType SoundManager::playSoundWithSubtitles(Common::String filename, FlagType flag, EntityIndex entity, byte a4) { + SoundEntry* entry = new SoundEntry(); + setupEntry(entry, filename, flag, 30); + entry->entity = entity; + + if (a4) { + entry->field_48 = _data2 + 2 * a4; + entry->status.status |= kSoundStatus_8000; + } else { + // Get subtitles name + while (filename.size() > 4) + filename.deleteLastChar(); + + showSubtitles(entry, filename); + updateEntryState(entry); + } + + return entry->type; +} + +void SoundManager::playSoundEvent(EntityIndex entity, byte action, byte a3) { + char filename[12]; + int values[5]; + + if (getEntityData(entity)->car != getEntityData(kEntityPlayer)->car) + return; + + if (getEntities()->isInSalon(entity) != getEntities()->isInSalon(kEntityPlayer)) + return; + + int _action = (int)action; + FlagType flag = getSoundFlag(entity); + + switch (action) { + case 36: { + int _param3 = (flag <= 9) ? flag + 7 : 16; + + if (_param3 > 7) { + _data0 = (uint)_param3; + _data1 = _data2 + 2 * a3; + } + break; + } + + case 37: + _data0 = 7; + _data1 = _data2 + 2 * a3; + break; + + case 150: + case 156: + case 162: + case 168: + case 188: + case 198: + _action += 1 + (int)rnd(5); + break; + + case 174: + case 184: + case 194: + _action += 1 + (int)rnd(3); + break; + + case 180: + _action += 1 + (int)rnd(4); + break; + + case 246: + values[0] = 0; + values[1] = 104; + values[2] = 105; + values[3] = 106; + values[4] = 116; + _action = values[rnd(5)]; + break; + + case 247: + values[0] = 11; + values[1] = 123; + values[2] = 124; + _action = values[rnd(3)]; + break; + + case 248: + values[0] = 0; + values[1] = 103; + values[2] = 108; + values[3] = 109; + _action = values[rnd(4)]; + break; + + case 249: + values[0] = 0; + values[1] = 56; + values[2] = 112; + values[3] = 113; + _action = values[rnd(4)]; + break; + + case 250: + values[0] = 0; + values[1] = 107; + values[2] = 115; + values[3] = 117; + _action = values[rnd(4)]; + break; + + case 251: + values[0] = 0; + values[1] = 11; + values[2] = 56; + values[3] = 113; + _action = values[rnd(4)]; + break; + + case 252: + values[0] = 0; + values[1] = 6; + values[2] = 109; + values[3] = 121; + _action = values[rnd(4)]; + break; + + case 254: + values[0] = 0; + values[1] = 104; + values[2] = 120; + values[3] = 121; + _action = values[rnd(4)]; + break; + + case 255: + values[0] = 0; + values[1] = 106; + values[2] = 115; + _action = values[rnd(3)]; + break; + + default: + break; + } + + if (_action) { + sprintf((char *)&filename, "LIB%03d.SND", _action); + + if (flag) + playSoundWithSubtitles((char*)&filename, flag, kEntityPlayer, a3); + } +} + +void SoundManager::playSteam(CityIndex index) { + if (index >= ARRAYSIZE(cities)) + error("SoundManager::playSteam: invalid city index (was %d, max %d)", index, ARRAYSIZE(cities)); + + _state |= kSoundState2; + + if (!getEntry(kSoundType1)) + playSoundWithSubtitles("STEAM.SND", kFlagSteam, kEntitySteam); + + // Get the new sound entry and show subtitles + SoundEntry *entry = getEntry(kSoundType1); + if (entry) + showSubtitles(entry, cities[index]); +} + +void SoundManager::playFightSound(byte action, byte a4) { + int _action = (int)action; + char filename[12]; + int values[5]; + + switch (action) { + default: + break; + + case 174: + case 184: + case 194: + values[0] = action + 1; + values[1] = action + 2; + values[2] = action + 3; + _action = values[rnd(3)]; + break; + + case 180: + values[0] = action + 1; + values[1] = action + 2; + values[2] = action + 3; + values[3] = action + 4; + _action = values[rnd(4)]; + break; + + case 150: + case 156: + case 162: + case 168: + case 188: + case 198: + values[0] = action + 1; + values[1] = action + 2; + values[2] = action + 3; + values[3] = action + 4; + values[4] = action + 5; + _action = values[rnd(5)]; + break; + } + + if (_action) { + sprintf((char *)&filename, "LIB%03d.SND", _action); + playSound(kEntityTrain, (char*)&filename, kFlagDefault, a4); + } +} + +void SoundManager::playDialog(EntityIndex entity, EntityIndex entityDialog, FlagType flag, byte a4) { + if (isBuffered(getDialogName(entityDialog))) + removeFromQueue(getDialogName(entityDialog)); + + playSound(entity, getDialogName(entityDialog), flag, a4); +} + +void SoundManager::playLocomotiveSound() { + playSound(kEntityPlayer, locomotiveSounds[rnd(5)], (FlagType)(rnd(15) + 2)); +} + +const char *SoundManager::getDialogName(EntityIndex entity) const { + switch (entity) { + case kEntityAnna: + if (getEvent(kEventAnnaDialogGoToJerusalem)) + return "XANN12"; + + if (getEvent(kEventLocomotiveRestartTrain)) + return "XANN11"; + + if (getEvent(kEventAnnaBaggageTies) || getEvent(kEventAnnaBaggageTies2) || getEvent(kEventAnnaBaggageTies3) || getEvent(kEventAnnaBaggageTies4)) + return "XANN10"; + + if (getEvent(kEventAnnaTired) || getEvent(kEventAnnaTiredKiss)) + return "XANN9"; + + if (getEvent(kEventAnnaBaggageArgument)) + return "XANN8"; + + if (getEvent(kEventKronosVisit)) + return "XANN7"; + + if (getEvent(kEventAbbotIntroduction)) + return "XANN6A"; + + if (getEvent(kEventVassiliSeizure)) + return "XANN6"; + + if (getEvent(kEventAugustPresentAnna) || getEvent(kEventAugustPresentAnnaFirstIntroduction)) + return "XANN5"; + + if (getProgress().field_60) + return "XANN4"; + + if (getEvent(kEventAnnaGiveScarf) || getEvent(kEventAnnaGiveScarfDiner) || getEvent(kEventAnnaGiveScarfSalon) + || getEvent(kEventAnnaGiveScarfMonogram) || getEvent(kEventAnnaGiveScarfDinerMonogram) || getEvent(kEventAnnaGiveScarfSalonMonogram)) + return "XANN3"; + + if (getEvent(kEventDinerMindJoin)) + return "XANN2"; + + if (getEvent(kEventGotALight) || getEvent(kEventGotALightD)) + return "XANN1"; + + break; + + case kEntityAugust: + if (getEvent(kEventAugustTalkCigar)) + return "XAUG6"; + + if (getEvent(kEventAugustBringBriefcase)) + return "XAUG5"; + + // Getting closer to Vienna... + if (getState()->time > kTime2200500 && !getEvent(kEventAugustMerchandise)) + return "XAUG4A"; + + if (getEvent(kEventAugustMerchandise)) + return "XAUG4"; + + if (getEvent(kEventDinerAugust) || getEvent(kEventDinerAugustAlexeiBackground) || getEvent(kEventMeetAugustTylerCompartment) + || getEvent(kEventMeetAugustTylerCompartmentBed) || getEvent(kEventMeetAugustHisCompartment) || getEvent(kEventMeetAugustHisCompartmentBed)) + return "XAUG3"; + + if (getEvent(kEventAugustPresentAnnaFirstIntroduction)) + return "XAUG2"; + + if (getProgress().eventMertensAugustWaiting) + return "XAUG1"; + + break; + + case kEntityTatiana: + if (getEvent(kEventTatianaTylerCompartment)) + return "XTAT6"; + + if (getEvent(kEventTatianaCompartmentStealEgg)) + return "XTAT5"; + + if (getEvent(kEventTatianaGivePoem)) + return "XTAT3"; + + if (getProgress().field_64) + return "XTAT1"; + + break; + + case kEntityVassili: + if (getEvent(kEventCathFreePassengers)) + return "XVAS4"; + + if (getEvent(kEventVassiliCompartmentStealEgg)) + return "XVAS3"; + + if (getEvent(kEventAbbotIntroduction)) + return "XVAS2"; + + if (getEvent(kEventVassiliSeizure)) + return "XVAS1A"; + + if (getProgress().field_64) + return "XVAS1"; + + break; + + case kEntityAlexei: + if (getProgress().field_88) + return "XALX6"; + + if (getProgress().field_8C) + return "XALX5"; + + if (getProgress().field_90) + return "XALX4A"; + + if (getProgress().field_68) + return "XALX4"; + + if (getEvent(kEventAlexeiSalonPoem)) + return "XALX3"; + + if (getEvent(kEventAlexeiSalonVassili)) + return "XALX2"; + + if (getEvent(kEventAlexeiDiner) || getEvent(kEventAlexeiDinerOriginalJacket)) + return "XALX1"; + + break; + + case kEntityAbbot: + if (getEvent(kEventAbbotDrinkDefuse)) + return "XABB4"; + + if (getEvent(kEventAbbotInvitationDrink) || getEvent(kEventDefuseBomb)) + return "XABB3"; + + if (getEvent(kEventAbbotWrongCompartment) || getEvent(kEventAbbotWrongCompartmentBed)) + return "XABB2"; + + if (getEvent(kEventAbbotIntroduction)) + return "XABB1"; + + break; + + case kEntityMilos: + if (getEvent(kEventLocomotiveMilos) || getEvent(kEventLocomotiveMilosNight)) + return "XMIL5"; + + if (getEvent(kEventMilosCompartmentVisitTyler) && (getProgress().chapter == kChapter3 || getProgress().chapter == kChapter4)) + return "XMIL4"; + + if (getEvent(kEventMilosCorridorThanks) || getProgress().chapter == kChapter5) + return "XMIL3"; + + if (getEvent(kEventMilosCompartmentVisitAugust)) + return "XMIL2"; + + if (getEvent(kEventMilosTylerCompartmentDefeat)) + return "XMIL1"; + + break; + + case kEntityVesna: + if (getProgress().field_94) + return "XVES2"; + + if (getProgress().field_98) + return "XVES1"; + + break; + + case kEntityKronos: + if (getEvent(kEventKronosReturnBriefcase)) + return "XKRO6"; + + if (getEvent(kEventKronosBringEggCeiling) || getEvent(kEventKronosBringEgg)) + return "XKRO5"; + + if (getEvent(kEventKronosConversation) || getEvent(kEventKronosConversationFirebird)) { + ObjectLocation location = getInventory()->get(kItemFirebird)->location; + if (location != kObjectLocation6 && location != kObjectLocation5 && location != kObjectLocation2 && location != kObjectLocation1) + return "XKRO4A"; + } + + if (getEvent(kEventKronosConversationFirebird)) + return "XKRO4"; + + if (getEvent(kEventKronosConversation)) { + if (!getEvent(kEventMilosCompartmentVisitAugust)) + return "XKRO3"; + else + return "XKRO2"; + } + + if (getProgress().eventMertensKronosInvitation) + return "XKRO1"; + + break; + + case kEntityFrancois: + if (getProgress().field_9C) + return "XFRA3"; + + if (getProgress().field_A0 + || getEvent(kEventFrancoisWhistle) || getEvent(kEventFrancoisWhistleD) + || getEvent(kEventFrancoisWhistleNight) || getEvent(kEventFrancoisWhistleNightD)) + return "XFRA2"; + + if (getState()->time > kTimeParisEpernay) // Between Paris and Epernay + return "XFRA1"; + + break; + + case kEntityMmeBoutarel: + if (getProgress().field_A4) + return "XMME4"; + + if (getProgress().field_A8) + return "XMME3"; + + if (getProgress().field_A0) + return "XMME2"; + + if (getProgress().field_AC) + return "XMME1"; + + break; + + case kEntityBoutarel: + if (getProgress().eventMetBoutarel) + return "XMRB1"; + + break; + + case kEntityRebecca: + if (getProgress().field_B4) + return "XREB1A"; + + if (getProgress().field_B8) + return "XREB1"; + + break; + + case kEntitySophie: + if (getProgress().field_B0) + return "XSOP2"; + + if (getProgress().field_BC) + return "XSOP1B"; + + if (getProgress().field_B4) + return "XSOP1A"; + + if (getProgress().field_B8) + return "XSOP1"; + + break; + + case kEntityMahmud: + if (getProgress().field_C4) + return "XMAH1"; + + break; + + case kEntityYasmin: + if (getProgress().eventMetYasmin) + return "XHAR2"; + + break; + + case kEntityHadija: + if (getProgress().eventMetHadija) + return "XHAR1"; + + break; + + case kEntityAlouan: + if (getProgress().field_DC) + return "XHAR3"; + + break; + + case kEntityGendarmes: + if (getProgress().field_E0) + return "XHAR4"; + + break; + + case kEntityChapters: + if (getEvent(kEventCathDream) || getEvent(kEventCathWakingUp)) + return "XTYL3"; + + return "XTYL1"; + + default: + break; + } + + return NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Letters & Messages +////////////////////////////////////////////////////////////////////////// +void SoundManager::readText(int id){ + if (!isBuffered(kEntityTables4)) + return; + + if (id < 0 || (id > 8 && id < 50) || id > 64) + error("Sound::readText - attempting to use invalid id. Valid values [1;8] - [50;64], was %d", id); + + // Get proper message file (names are stored in sequence in the array but id is [1;8] - [50;64]) + const char* text = messages[id <= 8 ? id : id - 41]; + + // Check if file is in cache for id [1;8] + if (id <= 8) + if (isBuffered(text)) + removeFromQueue(text); + + playSound(kEntityTables4, text, kFlagDefault); +} + +////////////////////////////////////////////////////////////////////////// +// Sound bites +////////////////////////////////////////////////////////////////////////// +void SoundManager::playWarningCompartment(EntityIndex entity, ObjectIndex compartment) { + +#define PLAY_WARNING(index, sound1, sound2, sound3, sound4, sound5, sound6) { \ + if (_lastWarning[index] + 450 >= getState()->timeTicks) { \ + if (rnd(2)) \ + playSound(kEntityMertens, sound1, kFlagDefault); \ + else \ + playSound(kEntityMertens, rnd(2) ? sound2 : sound3, kFlagDefault); \ + } else { \ + if (rnd(2)) \ + playSound(kEntityMertens, sound4, kFlagDefault); \ + else \ + playSound(kEntityMertens, rnd(2) ? sound5 : sound6, kFlagDefault); \ + } \ + _lastWarning[index] = getState()->timeTicks; \ +} + + if (entity != kEntityMertens && entity != kEntityCoudert) + return; + + ////////////////////////////////////////////////////////////////////////// + // Mertens + if (entity == kEntityMertens) { + + switch (compartment) { + default: + break; + + case kObjectCompartment2: + PLAY_WARNING(0, "Con1502A", "Con1500B", "Con1500C", "Con1502", "Con1500", "Con1500A"); + break; + + case kObjectCompartment3: + PLAY_WARNING(1, "Con1501A", "Con1500B", "Con1500C", "Con1501", "Con1500", "Con1500A"); + break; + + case kObjectCompartment4: + PLAY_WARNING(2, "Con1503", "Con1500B", "Con1500C", "Con1503", "Con1500", "Con1500A"); + break; + + case kObjectCompartment5: + case kObjectCompartment6: + case kObjectCompartment7: + case kObjectCompartment8: + ++_lastWarning[3]; + + switch (_lastWarning[3]) { + default: + break; + + case 1: + getSound()->playSound(kEntityMertens, "Con1503C", kFlagDefault); + break; + + case 2: + getSound()->playSound(kEntityMertens, rnd(2) ? "Con1503E" : "Con1503A", kFlagDefault); + break; + + case 3: + getSound()->playSound(kEntityMertens, rnd(2) ? "Con1503B" : "Con1503D", kFlagDefault); + _lastWarning[3] = 0; + break; + } + } + + return; + } + + ////////////////////////////////////////////////////////////////////////// + // Coudert + switch (compartment) { + default: + break; + + case kObjectCompartmentA: + if (_lastWarning[4] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1508" : "Jac1508A", kFlagDefault); + break; + + case kObjectCompartmentB: + if (_lastWarning[5] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getProgress().field_40 || (getState()->time > kTimeCityLinz && getState()->time < kTime2133000)) + getSound()->playSound(kEntityCoudert, "Jac1507A", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, "Jac1507", kFlagDefault); + break; + + case kObjectCompartmentC: + if (_lastWarning[6] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getProgress().chapter < kChapter3) + getSound()->playSound(kEntityCoudert, "Jac1506", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1506A" : "Jac1506B", kFlagDefault); + break; + + case kObjectCompartmentD: + if (_lastWarning[7] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + getSound()->playSound(kEntityCoudert, "Jac1505", kFlagDefault); + break; + + case kObjectCompartmentE: + if (_lastWarning[8] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getProgress().field_40 || (getState()->time > kTime2115000 && getState()->time < kTime2133000)) { + getSound()->playSound(kEntityCoudert, "Jac1504B", kFlagDefault); + break; + } + + if (getEntities()->isInsideCompartment(kEntityRebecca, kCarRedSleeping, kPosition_4840)) + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1504" : "Jac1504A", kFlagDefault); + break; + + case kObjectCompartmentF: + if (_lastWarning[9] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getProgress().field_40 || (getState()->time > kTime2083500 && getState()->time < kTime2133000)) { + getSound()->playSound(kEntityCoudert, "Jac1503B", kFlagDefault); + break; + } + + if (rnd(2) || getEntities()->isInsideCompartment(kEntityAnna, kCarRedSleeping, kPosition_4070)) + getSound()->playSound(kEntityCoudert, "Jac1503", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, "Jac1503A", kFlagDefault); + break; + + case kObjectCompartmentG: + if (_lastWarning[10] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (rnd(2) || getEntities()->isInsideCompartment(kEntityMilos, kCarRedSleeping, kPosition_3050)) + getSound()->playSound(kEntityCoudert, "Jac1502", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, "Jac1502A", kFlagDefault); + break; + + case kObjectCompartmentH: + if (_lastWarning[11] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getEntities()->isInsideCompartment(kEntityIvo, kCarRedSleeping, kPosition_2740)) + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, "Jac1501", kFlagDefault); + break; + } + + // Update ticks (Compartments A - H are indexes 4 - 11) + _lastWarning[compartment - 28] = getState()->timeTicks; +} + +void SoundManager::excuseMe(EntityIndex entity, EntityIndex entity2, FlagType flag) { + if (isBuffered(entity) && entity != kEntityPlayer && entity != kEntityChapters && entity != kEntityTrain) + return; + + if (entity2 == kEntityFrancois || entity2 == kEntityMax) + return; + + if (entity == kEntityFrancois && getEntityData(kEntityFrancois)->field_4A3 != 30) + return; + + if (flag == kFlagNone) + flag = getSoundFlag(entity); + + switch (entity) { + default: + break; + + case kEntityAnna: + playSound(kEntityPlayer, "ANN1107A", flag); + break; + + case kEntityAugust: + switch(rnd(4)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "AUG1100A", flag); + break; + + case 1: + playSound(kEntityPlayer, "AUG1100B", flag); + break; + + case 2: + playSound(kEntityPlayer, "AUG1100C", flag); + break; + + case 3: + playSound(kEntityPlayer, "AUG1100D", flag); + break; + } + break; + + case kEntityMertens: + if (Entities::isFemale(entity2)) { + playSound(kEntityPlayer, (rnd(2) ? "CON1111" : "CON1111A"), flag); + } else { + if (entity2 || getProgress().jacket != kJacketGreen || !rnd(2)) { + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "CON1110A", flag); + break; + + case 1: + playSound(kEntityPlayer, "CON1110C", flag); + break; + + case 2: + playSound(kEntityPlayer, "CON1110", flag); + break; + } + } else { + if (isNight()) { + playSound(kEntityPlayer, (getProgress().field_18 == 2 ? "CON1110F" : "CON1110E")); + } else { + playSound(kEntityPlayer, "CON1110D"); + } + } + } + break; + + case kEntityCoudert: + if (Entities::isFemale(entity2)) { + playSound(kEntityPlayer, "JAC1111D", flag); + } else { + if (entity2 || getProgress().jacket != kJacketGreen || !rnd(2)) { + switch(rnd(4)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "JAC1111", flag); + break; + + case 1: + playSound(kEntityPlayer, "JAC1111A", flag); + break; + + case 2: + playSound(kEntityPlayer, "JAC1111B", flag); + break; + + case 3: + playSound(kEntityPlayer, "JAC1111C", flag); + break; + } + } else { + playSound(kEntityPlayer, "JAC1113B", flag); + } + } + break; + + case kEntityPascale: + playSound(kEntityPlayer, (rnd(2) ? "HDE1002" : "HED1002A"), flag); + break; + + case kEntityServers0: + case kEntityServers1: + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, (entity == kEntityServers0) ? "WAT1002" : "WAT1003", flag); + break; + + case 1: + playSound(kEntityPlayer, (entity == kEntityServers0) ? "WAT1002A" : "WAT1003A", flag); + break; + + case 2: + playSound(kEntityPlayer, (entity == kEntityServers0) ? "WAT1002B" : "WAT1003B", flag); + break; + } + break; + + case kEntityVerges: + if (Entities::isFemale(entity2)) { + playSound(kEntityPlayer, (rnd(2) ? "TRA1113A" : "TRA1113B")); + } else { + playSound(kEntityPlayer, "TRA1112", flag); + } + break; + + case kEntityTatiana: + playSound(kEntityPlayer, (rnd(2) ? "TAT1102A" : "TAT1102B"), flag); + break; + + case kEntityAlexei: + playSound(kEntityPlayer, (rnd(2) ? "ALX1099C" : "ALX1099D"), flag); + break; + + case kEntityAbbot: + if (Entities::isFemale(entity2)) { + playSound(kEntityPlayer, "ABB3002C", flag); + } else { + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "ABB3002", flag); + break; + + case 1: + playSound(kEntityPlayer, "ABB3002A", flag); + break; + + case 2: + playSound(kEntityPlayer, "ABB3002B", flag); + break; + } + } + break; + + case kEntityVesna: + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "VES1109A", flag); + break; + + case 1: + playSound(kEntityPlayer, "VES1109B", flag); + break; + + case 2: + playSound(kEntityPlayer, "VES1109C", flag); + break; + } + break; + + case kEntityKahina: + playSound(kEntityPlayer, (rnd(2) ? "KAH1001" : "KAH1001A"), flag); + break; + + case kEntityFrancois: + case kEntityMmeBoutarel: + switch(rnd(4)) { + default: + break; + + case 0: + playSound(kEntityPlayer, (entity == kEntityFrancois) ? "FRA1001" : "MME1103A", flag); + break; + + case 1: + playSound(kEntityPlayer, (entity == kEntityFrancois) ? "FRA1001A" : "MME1103B", flag); + break; + + case 2: + playSound(kEntityPlayer, (entity == kEntityFrancois) ? "FRA1001B" : "MME1103C", flag); + break; + + case 3: + playSound(kEntityPlayer, (entity == kEntityFrancois) ? "FRA1001C" : "MME1103D", flag); + break; + } + break; + + case kEntityBoutarel: + playSound(kEntityPlayer, "MRB1104", flag); + if (flag > 2) + getProgress().eventMetBoutarel = true; + break; + + case kEntityRebecca: + playSound(kEntityPlayer, (rnd(2) ? "REB1106" : "REB110A"), flag); + break; + + case kEntitySophie: { + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "SOP1105", flag); + break; + + case 1: + playSound(kEntityPlayer, Entities::isFemale(entity2) ? "SOP1105C" : "SOP1105A", flag); + break; + + case 2: + playSound(kEntityPlayer, Entities::isFemale(entity2) ? "SOP1105D" : "SOP1105B", flag); + break; + } + break; + } + + case kEntityMahmud: + playSound(kEntityPlayer, "MAH1101", flag); + break; + + case kEntityYasmin: + playSound(kEntityPlayer, "HAR1002", flag); + if (flag > 2) + getProgress().eventMetYasmin = true; + break; + + case kEntityHadija: + playSound(kEntityPlayer, (rnd(2) ? "HAR1001" : "HAR1001A"), flag); + if (flag > 2) + getProgress().eventMetHadija = true; + break; + + case kEntityAlouan: + playSound(kEntityPlayer, "HAR1004", flag); + break; + } +} + +void SoundManager::excuseMeCath() { + switch(rnd(3)) { + default: + playSound(kEntityPlayer, "CAT1126B"); + break; + + case 1: + playSound(kEntityPlayer, "CAT1126C"); + break; + + case 2: + playSound(kEntityPlayer, "CAT1126D"); + break; + } +} + +const char *SoundManager::justCheckingCath() const { + switch(rnd(4)) { + default: + break; + + case 0: + return "CAT5001"; + + case 1: + return "CAT5001A"; + + case 2: + return "CAT5001B"; + + case 3: + return "CAT5001C"; + } + + return "CAT5001"; +} + +const char *SoundManager::wrongDoorCath() const { + switch(rnd(5)) { + default: + break; + + case 0: + return "CAT1125"; + + case 1: + return "CAT1125A"; + + case 2: + return "CAT1125B"; + + case 3: + return "CAT1125C"; + + case 4: + return "CAT1125D"; + } + + return "CAT1125"; +} + +const char *SoundManager::justAMinuteCath() const { + switch(rnd(3)) { + default: + break; + + case 0: + return "CAT1520"; + + case 1: + return "CAT1521"; + + case 2: + return "CAT1125"; // ?? is this a bug in the original? + } + + return "CAT1520"; +} + +////////////////////////////////////////////////////////////////////////// +// Sound flags +////////////////////////////////////////////////////////////////////////// +SoundManager::FlagType SoundManager::getSoundFlag(EntityIndex entity) const { + if (entity == kEntityPlayer) + return kFlagDefault; + + if (getEntityData(entity)->car != getEntityData(kEntityPlayer)->car) + return kFlagNone; + + // Compute sound value + FlagType ret = kFlag2; + + // Get default value if valid + int index = ABS(getEntityData(entity)->entityPosition - getEntityData(kEntityPlayer)->entityPosition) / 230; + if (index < 32) + ret = soundFlags[index]; + + if (getEntityData(entity)->location == kLocationOutsideTrain) { + if (getEntityData(entity)->car != kCarKronos + && !getEntities()->isOutsideAlexeiWindow() + && !getEntities()->isOutsideAnnaWindow()) + return kFlagNone; + + return (FlagType)(ret / 6); + } + + switch (getEntityData(entity)->car) { + default: + break; + + case kCarKronos: + if (getEntities()->isInKronosSalon(entity) != getEntities()->isInKronosSalon(kEntityPlayer)) + ret = (FlagType)(ret * 2); + break; + + case kCarGreenSleeping: + case kCarRedSleeping: + if (getEntities()->isInGreenCarEntrance(kEntityPlayer) && !getEntities()->isInKronosSalon(entity)) + ret = (FlagType)(ret * 2); + + if (getEntityData(kEntityPlayer)->location + && (getEntityData(entity)->entityPosition != kPosition_1 || !getEntities()->isDistanceBetweenEntities(kEntityPlayer, entity, 400))) + ret = (FlagType)(ret * 2); + break; + + case kCarRestaurant: + if (getEntities()->isInSalon(entity) == getEntities()->isInSalon(kEntityPlayer) + && (getEntities()->isInRestaurant(entity) != getEntities()->isInRestaurant(kEntityPlayer))) + ret = (FlagType)(ret * 2); + else + ret = (FlagType)(ret * 4); + break; + } + + return ret; +} + +////////////////////////////////////////////////////////////////////////// +// Subtitles +////////////////////////////////////////////////////////////////////////// +void SoundManager::updateSubtitles() { + //warning("SoundManager::updateSubtitles: not implemented!"); +} + +void SoundManager::showSubtitles(SoundEntry *entry, Common::String filename) { + warning("SoundManager::showSubtitles: not implemented!"); +} + +void SoundManager::drawSubtitles(SubtitleManager *subtitle) { + warning("SoundManager::drawSubtitles: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Misc +////////////////////////////////////////////////////////////////////////// +void SoundManager::playLoopingSound() { + warning("SoundManager::playLoopingSound: not implemented!"); +} + +void SoundManager::stopAllSound() const { + _soundStream->stop(); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/sound.h b/engines/lastexpress/game/sound.h new file mode 100644 index 0000000000..3cb17db80c --- /dev/null +++ b/engines/lastexpress/game/sound.h @@ -0,0 +1,333 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SOUND_H +#define LASTEXPRESS_SOUND_H + +/* + + Sound entry: 68 bytes (this is what appears in the savegames) + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - entity + uint32 {4} - ?? + uint32 {4} - ?? + char {16} - name 1 + char {16} - name 2 + + Sound queue entry: 120 bytes + uint16 {2} - status + byte {1} - ?? + byte {1} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - file data pointer + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - archive structure pointer + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - entity + uint32 {4} - ?? + uint32 {4} - ?? + char {16} - name 1 + char {16} - name 2 + uint32 {4} - pointer to next entry in the queue + uint32 {4} - subtitle data pointer + +*/ + +#include "lastexpress/shared.h" + +#include "common/list.h" +#include "common/system.h" +#include "common/serializer.h" + +namespace LastExpress { + +class LastExpressEngine; +class StreamedSound; +class SubtitleManager; + +class SoundManager : Common::Serializable { +public: + enum SoundType { + kSoundTypeNone = 0, + kSoundType1, + kSoundType2, + kSoundType3, + kSoundType4, + kSoundType5, + kSoundType6, + kSoundType7, + kSoundType8, + kSoundType9, + kSoundType10, + kSoundType11, + kSoundType12, + kSoundType13, + kSoundType14, + kSoundType15, + kSoundType16 + }; + + enum FlagType { + kFlagInvalid = -1, + kFlagNone = 0x0, + kFlag2 = 0x2, + kFlag3 = 0x3, + kFlag4 = 0x4, + kFlag5 = 0x5, + kFlag6 = 0x6, + kFlag7 = 0x7, + kFlag8 = 0x8, + kFlag9 = 0x9, + kFlag10 = 0xA, + kFlag11 = 0xB, + kFlag12 = 0xC, + kFlag13 = 0xD, + kFlag14 = 0xE, + kFlag15 = 0xF, + kFlagDefault = 0x10, + + kFlagType1_2 = 0x1000000, + kFlagSteam = 0x1001007, + kFlagType13 = 0x3000000, + kFlagMenuClock = 0x3080010, + kFlagType7 = 0x4000000, + kFlagType11 = 0x5000000, + kFlagMusic = 0x5000010, + kFlagType3 = 0x6000000, + kFlagLoop = 0x6001008, + kFlagType9 = 0x7000000 + }; + + SoundManager(LastExpressEngine *engine); + ~SoundManager(); + + // Timer + void handleTimer(); + + // State + void resetState() { _state |= kSoundType1; } + + // Sound queue + void updateQueue(); + void resetQueue(SoundType type1, SoundType type2 = kSoundTypeNone); + void clearQueue(); + + // Subtitles + void updateSubtitles(); + + // Entry + bool isBuffered(Common::String filename, bool testForEntity = false); + bool isBuffered(EntityIndex entity); + void setupEntry(SoundType type, EntityIndex index); + void processEntry(EntityIndex entity); + void processEntry(SoundType type); + void processEntry(Common::String filename); + void processEntries(); + void removeFromQueue(Common::String filename); + void removeFromQueue(EntityIndex entity); + + // Misc + void unknownFunction4(); + void clearStatus(); + + // Sound playing + void playSound(EntityIndex entity, Common::String filename, FlagType flag = kFlagInvalid, byte a4 = 0); + SoundType playSoundWithSubtitles(Common::String filename, FlagType flag, EntityIndex entity, byte a4 = 0); + void playSoundEvent(EntityIndex entity, byte action, byte a3 = 0); + void playDialog(EntityIndex entity, EntityIndex entityDialog, FlagType flag, byte a4); + void playSteam(CityIndex index); + void playFightSound(byte action, byte a4); + void playLocomotiveSound(); + void playWarningCompartment(EntityIndex entity, ObjectIndex compartment); + + // Dialog & Letters + void readText(int id); + const char *getDialogName(EntityIndex entity) const; + + // Sound bites + void excuseMe(EntityIndex entity, EntityIndex entity2 = kEntityPlayer, FlagType flag = kFlagNone); + void excuseMeCath(); + const char *justCheckingCath() const; + const char *wrongDoorCath() const; + const char *justAMinuteCath() const; + + // FLags + SoundManager::FlagType getSoundFlag(EntityIndex index) const; + + // Debug + void stopAllSound() const; + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + +private: + typedef int32* SoundBuffer; + + enum SoundStatus { + kSoundStatus_20 = 0x20, + kSoundStatusRemoved = 0x200, + + kSoundStatus_8000 = 0x8000, + kSoundStatus_100000 = 0x100000, + kSoundStatus_40000000 = 0x40000000, + + kSoundStatusClear0 = 0x10, + kSoundStatusClear1 = 0x1F, + kSoundStatusClear2 = 0x80, + kSoundStatusClear3 = 0x200, + kSoundStatusClear4 = 0x800, + kSoundStatusClearAll = 0xFFFFFFE0 + }; + + enum SoundState { + kSoundState0 = 0, + kSoundState1 = 1, + kSoundState2 = 2 + }; + + union SoundStatusUnion { + uint32 status; + byte status1; + byte status2; + byte status3; + byte status4; + + SoundStatusUnion() { + status = 0; + } + }; + + struct SoundEntry { + SoundStatusUnion status; + SoundType type; // int + //int field_8; + //int field_C; + //int field_10; + //int fileData; + //int field_18; + //int field_1C; + //int field_20; + //int field_24; + //int field_28; + Common::SeekableReadStream *stream; // int + //int field_30; + //int field_34; + //int field_38; + int field_3C; + int field_40; + EntityIndex entity; + int field_48; + int field_4C; + Common::String name1; //char[16]; + Common::String name2; //char[16]; + //int next; // offset to the next structure in the list (not used) + SubtitleManager *subtitle; + + bool isStreamed; // TEMPORARY + + SoundEntry() { + type = kSoundTypeNone; + stream = NULL; + field_3C = 0; + field_40 = 0; + entity = kEntityPlayer; + field_48 = 0; + field_4C = 0; + + subtitle = NULL; + + isStreamed = false; + }; + }; + + // Engine + LastExpressEngine* _engine; + + // State flag + int _state; + SoundType _currentType; + + // Sound stream + StreamedSound *_soundStream; + + // Unknown data + uint32 _data0; + uint32 _data1; + uint32 _data2; + uint32 _flag; + + // Filters + + int32 _buffer[2940]; ///< Static sound buffer + + // Compartment warnings by Mertens or Coudert + uint32 _lastWarning[12]; + + // Looping sound + void playLoopingSound(); + + // Sound cache + Common::List<SoundEntry *> _cache; + + SoundEntry *getEntry(EntityIndex index); + SoundEntry *getEntry(Common::String name); + SoundEntry *getEntry(SoundType type); + + void setupEntry(SoundEntry *entry, Common::String name, FlagType flag, int a4); + void setEntryType(SoundEntry *entry, FlagType flag); + void setEntryStatus(SoundEntry *entry, FlagType flag) const; + bool setupCache(SoundEntry *entry); + void loadSoundData(SoundEntry *entry, Common::String name); + + void updateEntry(SoundEntry *entry, uint value) const; + void updateEntryState(SoundEntry *entry) const ; + void resetEntry(SoundEntry *entry) const; + void removeEntry(SoundEntry *entry); + + // Subtitles + void showSubtitles(SoundEntry *entry, Common::String filename); + void drawSubtitles(SubtitleManager *subtitle); + + // Sound filter + void applyFilter(SoundEntry *entry, SoundBuffer buffer); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SOUND_H diff --git a/engines/lastexpress/game/state.cpp b/engines/lastexpress/game/state.cpp new file mode 100644 index 0000000000..8675f6e312 --- /dev/null +++ b/engines/lastexpress/game/state.cpp @@ -0,0 +1,74 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/state.h" + +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" + +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +State::State(LastExpressEngine *engine) : _engine(engine), _timer(0) { + _inventory = new Inventory(engine); + _objects = new Objects(engine); + _savepoints = new SavePoints(engine); + _state = new GameState(); + _flags = new Flags(); +} + +State::~State() { + delete _inventory; + delete _objects; + delete _savepoints; + delete _state; + delete _flags; + + // Zero passed pointers + _engine = NULL; +} + +bool State::isNightTime() const { + return (_state->progress.chapter == kChapter1 + || _state->progress.chapter == kChapter4 + || (_state->progress.chapter == kChapter5 && !_state->progress.isNightTime)); +} + +uint32 State::getPowerOfTwo(uint32 x) { + if (!x || (x & 1)) + return 0; + + uint32 num = 0; + do { + x >>= 1; + num++; + } while ((x & 1) == 0); + + return num; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/state.h b/engines/lastexpress/game/state.h new file mode 100644 index 0000000000..a8ddfc7051 --- /dev/null +++ b/engines/lastexpress/game/state.h @@ -0,0 +1,593 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_STATE_H +#define LASTEXPRESS_STATE_H + +#include "lastexpress/shared.h" + +#include "common/system.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Inventory; +class Objects; +class SavePoints; + +class State { +public: + struct GameProgress { + uint32 field_0; + JacketType jacket; + bool eventCorpseMovedFromFloor; + uint32 field_C; + bool eventCorpseFound; + uint32 field_14; ///< EntityIndex (used in Gendarmes) + uint32 field_18; + uint32 portrait; + bool eventCorpseThrown; + uint32 field_24; + uint32 field_28; + ChapterIndex chapter; + uint32 field_30; + bool eventMetAugust; + bool isNightTime; + uint32 field_3C; + uint32 field_40; + uint32 field_44; + uint32 field_48; + uint32 field_4C; + bool isTrainRunning; + uint32 field_54; + uint32 field_58; + uint32 field_5C; + uint32 field_60; + uint32 field_64; + uint32 field_68; + bool eventMertensAugustWaiting; + bool eventMertensKronosInvitation; + bool isEggOpen; + uint32 field_78; // time? + uint32 field_7C; + uint32 field_80; + uint32 field_84; + uint32 field_88; + uint32 field_8C; + uint32 field_90; + uint32 field_94; + uint32 field_98; + uint32 field_9C; + uint32 field_A0; + uint32 field_A4; + uint32 field_A8; + uint32 field_AC; + uint32 field_B0; + uint32 field_B4; + uint32 field_B8; + uint32 field_BC; + uint32 field_C0; + uint32 field_C4; + uint32 field_C8; + uint32 field_CC; + bool eventMetBoutarel; + bool eventMetHadija; + bool eventMetYasmin; + uint32 field_DC; + uint32 field_E0; + uint32 field_E4; + uint32 field_E8; + uint32 field_EC; + uint32 field_F0; + uint32 field_F4; + uint32 field_F8; + uint32 field_FC; + uint32 field_100; + uint32 field_104; + uint32 field_108; + uint32 field_10C; + uint32 field_110; + uint32 field_114; + uint32 field_118; + uint32 field_11C; + uint32 field_120; + uint32 field_124; + uint32 field_128; + uint32 field_12C; + uint32 field_130; + uint32 field_134; + uint32 field_138; + uint32 field_13C; + uint32 field_140; + uint32 field_144; + uint32 field_148; + uint32 field_14C; + uint32 field_150; + uint32 field_154; + uint32 field_158; + uint32 field_15C; + uint32 field_160; + uint32 field_164; + uint32 field_168; + uint32 field_16C; + uint32 field_170; + uint32 field_174; + uint32 field_178; + uint32 field_17C; + uint32 field_180; + uint32 field_184; + uint32 field_188; + uint32 field_18C; + uint32 field_190; + uint32 field_194; + uint32 field_198; + uint32 field_19C; + uint32 field_1A0; + uint32 field_1A4; + uint32 field_1A8; + uint32 field_1AC; + uint32 field_1B0; + uint32 field_1B4; + uint32 field_1B8; + uint32 field_1BC; + uint32 field_1C0; + uint32 field_1C4; + uint32 field_1C8; + uint32 field_1CC; + uint32 field_1D0; + uint32 field_1D4; + uint32 field_1D8; + uint32 field_1DC; + uint32 field_1E0; + uint32 field_1E4; + uint32 field_1E8; + uint32 field_1EC; + uint32 field_1F0; + uint32 field_1F4; + uint32 field_1F8; + uint32 field_1FC; + + GameProgress() { + field_0 = 0; + jacket = kJacketOriginal; + eventCorpseMovedFromFloor = false; + field_C = 0; + eventCorpseFound = false; + field_14 = 0; // 5 + field_18 = 0; + portrait = _defaultPortrait; + eventCorpseThrown = false; + field_24 = 0; + field_28 = 0; // 10 + chapter = kChapter1; + field_30 = 0; + eventMetAugust = false; + isNightTime = false; + field_3C = 0; // 15 + field_40 = 0; + field_44 = 0; + field_48 = 0; + field_4C = 0; + isTrainRunning = false; // 20 + field_54 = 0; + field_58 = 0; + field_5C = 0; + field_60 = 0; + field_64 = 0; // 25 + field_68 = 0; + eventMertensAugustWaiting = false; + eventMertensKronosInvitation = false; + isEggOpen = false; + field_78 = 0; // 30 + field_7C = 0; + field_80 = 0; + field_84 = 0; + field_88 = 0; + field_8C = 0; // 35 + field_90 = 0; + field_94 = 0; + field_98 = 0; + field_9C = 0; + field_A0 = 0; // 40 + field_A4 = 0; + field_A8 = 0; + field_AC = 0; + field_B0 = 0; + field_B4 = 0; // 45 + field_B8 = 0; + field_BC = 0; + field_C0 = 0; + field_C4 = 0; + field_C8 = 0; // 50 + field_CC = 0; + eventMetBoutarel = false; + eventMetHadija = false; + eventMetYasmin = false; + field_DC = 0; // 55 + field_E0 = 0; + field_E4 = 0; + field_E8 = 0; + field_EC = 0; + field_F0 = 0; // 60 + field_F4 = 0; + field_F8 = 0; + field_FC = 0; + field_100 = 0; + field_104 = 0; // 65 + field_108 = 0; + field_10C = 0; + field_110 = 0; + field_114 = 0; + field_118 = 0; // 70 + field_11C = 0; + field_120 = 0; + field_124 = 0; + field_128 = 0; + field_12C = 0; // 75 + field_130 = 0; + field_134 = 0; + field_138 = 0; + field_13C = 0; + field_140 = 0; // 80 + field_144 = 0; + field_148 = 0; + field_14C = 0; + field_150 = 0; + field_154 = 0; // 85 + field_158 = 0; + field_15C = 0; + field_160 = 0; + field_164 = 0; + field_168 = 0; // 90 + field_16C = 0; + field_170 = 0; + field_174 = 0; + field_178 = 0; + field_17C = 0; // 95 + field_180 = 0; + field_184 = 0; + field_188 = 0; + field_18C = 0; + field_190 = 0; // 100 + field_194 = 0; + field_198 = 0; + field_19C = 0; + field_1A0 = 0; + field_1A4 = 0; // 105 + field_1A8 = 0; + field_1AC = 0; + field_1B0 = 0; + field_1B4 = 0; + field_1B8 = 0; // 110 + field_1BC = 0; + field_1C0 = 0; + field_1C4 = 0; + field_1C8 = 0; + field_1CC = 0; // 115 + field_1D0 = 0; + field_1D4 = 0; + field_1D8 = 0; + field_1DC = 0; + field_1E0 = 0; // 120 + field_1E4 = 0; + field_1E8 = 0; + field_1EC = 0; + field_1F0 = 0; + field_1F4 = 0; // 125 + field_1F8 = 0; + field_1FC = 0; + } + + /** + * Query if if a progress value is equal to the specified value. + * + * Note: This is necessary because we store different types in the progress structure + * and need to test a value based on an index in Action::getCursor() + * + * @param index Zero-based index of the progress structure entry + * @param val The value. + * + * @return true if equal, false if not. + */ + bool isEqual(uint index, uint val) { + #define EXPOSE_VALUE(idx, name) case idx: return ((uint)name == val); + + switch (index) { + default: + error("GameProgress::isEqual: invalid index value (was: %d, max:127)", index); + break; + + EXPOSE_VALUE(0, field_0); + EXPOSE_VALUE(1, jacket); + EXPOSE_VALUE(2, eventCorpseMovedFromFloor); + EXPOSE_VALUE(3, field_C); + EXPOSE_VALUE(4, eventCorpseFound); + EXPOSE_VALUE(5, field_14); + EXPOSE_VALUE(6, field_18); + EXPOSE_VALUE(7, portrait); + EXPOSE_VALUE(8, eventCorpseThrown); + EXPOSE_VALUE(9, field_24); + EXPOSE_VALUE(10, field_28); + EXPOSE_VALUE(11, chapter); + EXPOSE_VALUE(12, field_30); + EXPOSE_VALUE(13, eventMetAugust); + EXPOSE_VALUE(14, isNightTime); + EXPOSE_VALUE(15, field_3C); + EXPOSE_VALUE(16, field_40); + EXPOSE_VALUE(17, field_44); + EXPOSE_VALUE(18, field_48); + EXPOSE_VALUE(19, field_4C); + EXPOSE_VALUE(20, isTrainRunning); + EXPOSE_VALUE(21, field_54); + EXPOSE_VALUE(22, field_58); + EXPOSE_VALUE(23, field_5C); + EXPOSE_VALUE(24, field_60); + EXPOSE_VALUE(25, field_64); + EXPOSE_VALUE(26, field_68); + EXPOSE_VALUE(27, eventMertensAugustWaiting); + EXPOSE_VALUE(28, eventMertensKronosInvitation); + EXPOSE_VALUE(29, isEggOpen); + EXPOSE_VALUE(30, field_78); + EXPOSE_VALUE(31, field_7C); + EXPOSE_VALUE(32, field_80); + EXPOSE_VALUE(33, field_84); + EXPOSE_VALUE(34, field_88); + EXPOSE_VALUE(35, field_8C); + EXPOSE_VALUE(36, field_90); + EXPOSE_VALUE(37, field_94); + EXPOSE_VALUE(38, field_98); + EXPOSE_VALUE(39, field_9C); + EXPOSE_VALUE(40, field_A0); + EXPOSE_VALUE(41, field_A4); + EXPOSE_VALUE(42, field_A8); + EXPOSE_VALUE(43, field_AC); + EXPOSE_VALUE(44, field_B0); + EXPOSE_VALUE(45, field_B4); + EXPOSE_VALUE(46, field_B8); + EXPOSE_VALUE(47, field_BC); + EXPOSE_VALUE(48, field_C0); + EXPOSE_VALUE(49, field_C4); + EXPOSE_VALUE(50, field_C8); + EXPOSE_VALUE(51, field_CC); + EXPOSE_VALUE(52, eventMetBoutarel); + EXPOSE_VALUE(53, eventMetHadija); + EXPOSE_VALUE(54, eventMetYasmin); + EXPOSE_VALUE(55, field_DC); + EXPOSE_VALUE(56, field_E0); + EXPOSE_VALUE(57, field_E4); + EXPOSE_VALUE(58, field_E8); + EXPOSE_VALUE(59, field_EC); + EXPOSE_VALUE(60, field_F0); + EXPOSE_VALUE(61, field_F4); + EXPOSE_VALUE(62, field_F8); + EXPOSE_VALUE(63, field_FC); + EXPOSE_VALUE(64, field_100); + EXPOSE_VALUE(65, field_104); + EXPOSE_VALUE(66, field_108); + EXPOSE_VALUE(67, field_10C); + EXPOSE_VALUE(68, field_110); + EXPOSE_VALUE(69, field_114); + EXPOSE_VALUE(70, field_118); + EXPOSE_VALUE(71, field_11C); + EXPOSE_VALUE(72, field_120); + EXPOSE_VALUE(73, field_124); + EXPOSE_VALUE(74, field_128); + EXPOSE_VALUE(75, field_12C); + EXPOSE_VALUE(76, field_130); + EXPOSE_VALUE(77, field_134); + EXPOSE_VALUE(78, field_138); + EXPOSE_VALUE(79, field_13C); + EXPOSE_VALUE(80, field_140); + EXPOSE_VALUE(81, field_144); + EXPOSE_VALUE(82, field_148); + EXPOSE_VALUE(83, field_14C); + EXPOSE_VALUE(84, field_150); + EXPOSE_VALUE(85, field_154); + EXPOSE_VALUE(86, field_158); + EXPOSE_VALUE(87, field_15C); + EXPOSE_VALUE(88, field_160); + EXPOSE_VALUE(89, field_164); + EXPOSE_VALUE(90, field_168); + EXPOSE_VALUE(91, field_16C); + EXPOSE_VALUE(92, field_170); + EXPOSE_VALUE(93, field_174); + EXPOSE_VALUE(94, field_178); + EXPOSE_VALUE(95, field_17C); + EXPOSE_VALUE(96, field_180); + EXPOSE_VALUE(97, field_184); + EXPOSE_VALUE(98, field_188); + EXPOSE_VALUE(99, field_18C); + EXPOSE_VALUE(100, field_190); + EXPOSE_VALUE(101, field_194); + EXPOSE_VALUE(102, field_198); + EXPOSE_VALUE(103, field_19C); + EXPOSE_VALUE(104, field_1A0); + EXPOSE_VALUE(105, field_1A4); + EXPOSE_VALUE(106, field_1A8); + EXPOSE_VALUE(107, field_1AC); + EXPOSE_VALUE(108, field_1B0); + EXPOSE_VALUE(109, field_1B4); + EXPOSE_VALUE(110, field_1B8); + EXPOSE_VALUE(111, field_1BC); + EXPOSE_VALUE(112, field_1C0); + EXPOSE_VALUE(113, field_1C4); + EXPOSE_VALUE(114, field_1C8); + EXPOSE_VALUE(115, field_1CC); + EXPOSE_VALUE(116, field_1D0); + EXPOSE_VALUE(117, field_1D4); + EXPOSE_VALUE(118, field_1D8); + EXPOSE_VALUE(119, field_1DC); + EXPOSE_VALUE(120, field_1E0); + EXPOSE_VALUE(121, field_1E4); + EXPOSE_VALUE(122, field_1E8); + EXPOSE_VALUE(123, field_1EC); + EXPOSE_VALUE(124, field_1F0); + EXPOSE_VALUE(125, field_1F4); + EXPOSE_VALUE(126, field_1F8); + EXPOSE_VALUE(127, field_1FC); + } + } + }; + + struct GameState { + // Header + uint32 brightness; + uint32 volume; + + // Game data + uint32 field_0; + uint32 time; + uint32 timeDelta; + uint32 timeTicks; + bool sceneUseBackup; // byte + SceneIndex scene; // uint32 + SceneIndex sceneBackup; // uint32 + SceneIndex sceneBackup2; // uin32 + + GameProgress progress; + byte events[512]; + + GameState() { + brightness = _defaultBrigthness; + volume = _defaultVolume; + + //Game data + time = _defaultTime; + timeDelta = _defaultTimeDelta; + timeTicks = 0; + sceneUseBackup = false; + scene = kSceneDefault; + sceneBackup = kSceneNone; + sceneBackup2 = kSceneNone; + + // Clear game events + memset(events, 0, 512*sizeof(byte)); + } + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString() { + Common::String ret = ""; + + ret += Common::String::printf("Time: %d - Time delta: %d - Ticks: %d\n", time, timeDelta, timeTicks); + ret += Common::String::printf("Brightness: %d - Volume: %d - UseBackup: %d\n", brightness, volume, sceneUseBackup); + ret += Common::String::printf("Scene: %d - Scene backup: %d - Scene backup 2: %d\n", scene, sceneBackup, sceneBackup2); + + return ret; + } + }; + + struct Flags { + bool flag_0; + bool flag_3; + bool flag_4; + bool flag_5; + + bool frameInterval; + + bool isGameRunning; + + // Mouse flags + bool mouseLeftClick; + bool mouseRightClick; + + bool flag_entities_0; + bool flag_entities_1; + + bool shouldRedraw; + bool shouldDrawEggOrHourGlass; + + Flags() { + flag_0 = false; + flag_3 = false; + flag_4 = false; + flag_5 = false; + + frameInterval = false; + + isGameRunning = false; + + mouseRightClick = false; + mouseLeftClick = false; + + flag_entities_0 = false; + flag_entities_1 = false; + + shouldRedraw = false; + shouldDrawEggOrHourGlass = false; + } + }; + + State(LastExpressEngine *engine); + ~State(); + + // Accessors + Inventory *getGameInventory() { return _inventory; } + Objects *getGameObjects() { return _objects; } + SavePoints *getGameSavePoints() { return _savepoints; } + GameState *getGameState() { return _state; } + Flags *getGameFlags() { return _flags; } + + // Time checks + bool isNightTime() const; + + // Timer + int getTimer() { return _timer; } + void setTimer(int val) { _timer = val; } + + // Coordinates + void setCoordinates(Common::Point coords) { _coords = coords; } + const Common::Point getCoordinates() { return _coords; } + + // Helpers + static uint32 getPowerOfTwo(uint32 x); + +private: + static const uint32 _defaultBrigthness = 0x3; + static const uint32 _defaultVolume = 0x7; + static const uint32 _defaultTime = 1037700; + static const uint32 _defaultTimeDelta = 3; + static const uint32 _defaultPortrait = 32; + + LastExpressEngine *_engine; + + // Timer + int _timer; + + Flags *_flags; ///< Flags + Inventory *_inventory; ///< Inventory + Objects *_objects; ///< Objects + SavePoints *_savepoints; ///< SavePoints + GameState *_state; ///< State + Common::Point _coords; ///< Current coordinates +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_STATE_H |