aboutsummaryrefslogtreecommitdiff
path: root/engines/lastexpress/game
diff options
context:
space:
mode:
authorEugene Sandulenko2010-10-18 19:17:38 +0000
committerEugene Sandulenko2010-10-18 19:17:38 +0000
commit86d650926f9b991b6398e4ad4b0613ac264dfbaa (patch)
tree5e6791249fa5fce7dd3e1a6406dff4bf720ca085 /engines/lastexpress/game
parentc92d2bc234f2f73a9629b3622cd5e66c57439cda (diff)
downloadscummvm-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')
-rw-r--r--engines/lastexpress/game/action.cpp1968
-rw-r--r--engines/lastexpress/game/action.h135
-rw-r--r--engines/lastexpress/game/beetle.cpp517
-rw-r--r--engines/lastexpress/game/beetle.h118
-rw-r--r--engines/lastexpress/game/entities.cpp2731
-rw-r--r--engines/lastexpress/game/entities.h378
-rw-r--r--engines/lastexpress/game/fight.cpp1587
-rw-r--r--engines/lastexpress/game/fight.h269
-rw-r--r--engines/lastexpress/game/inventory.cpp594
-rw-r--r--engines/lastexpress/game/inventory.h169
-rw-r--r--engines/lastexpress/game/logic.cpp577
-rw-r--r--engines/lastexpress/game/logic.h91
-rw-r--r--engines/lastexpress/game/menu.cpp1564
-rw-r--r--engines/lastexpress/game/menu.h211
-rw-r--r--engines/lastexpress/game/object.cpp108
-rw-r--r--engines/lastexpress/game/object.h83
-rw-r--r--engines/lastexpress/game/savegame.cpp310
-rw-r--r--engines/lastexpress/game/savegame.h173
-rw-r--r--engines/lastexpress/game/savepoint.cpp296
-rw-r--r--engines/lastexpress/game/savepoint.h149
-rw-r--r--engines/lastexpress/game/scenes.cpp1195
-rw-r--r--engines/lastexpress/game/scenes.h120
-rw-r--r--engines/lastexpress/game/sound.cpp1676
-rw-r--r--engines/lastexpress/game/sound.h333
-rw-r--r--engines/lastexpress/game/state.cpp74
-rw-r--r--engines/lastexpress/game/state.h593
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