Permalink
Browse files

LASTEXPRESS: multiple fixes in NPC logic

Checked the logic against the original game
(to be precise, DOS English version from GOG, although I think
AI logic has no significant differences with other versions).
Fixed a *lot* of errors with varying visibility for the user.

Also, save+exit+load sometimes resulted in memory corruption like
((EntityParametersSSII*)(new EntityParametersIIII))->param8 = 0;
load operation did not restore the correct type of NPC logic context,
the default one was used (which also has the smallest sizeof).
Should be fixed now. Save+load is still unusable because it locks
everybody waiting for kActionEndSound (the sound state is not restored),
but, at least, it should not corrupt the memory. Hopefully.
  • Loading branch information...
grechnik committed Aug 25, 2018
1 parent 01f3e6c commit 28d83ee63fa303926dee1864688081e141dc33ec
Showing with 1,312 additions and 1,032 deletions.
  1. +1 −4 engines/lastexpress/debug.cpp
  2. +29 −22 engines/lastexpress/entities/abbot.cpp
  3. +31 −33 engines/lastexpress/entities/alexei.cpp
  4. +3 −3 engines/lastexpress/entities/alexei.h
  5. +6 −6 engines/lastexpress/entities/alouan.cpp
  6. +65 −56 engines/lastexpress/entities/anna.cpp
  7. +72 −65 engines/lastexpress/entities/august.cpp
  8. +18 −18 engines/lastexpress/entities/boutarel.cpp
  9. +42 −24 engines/lastexpress/entities/chapters.cpp
  10. +17 −33 engines/lastexpress/entities/cooks.cpp
  11. +44 −42 engines/lastexpress/entities/coudert.cpp
  12. +75 −60 engines/lastexpress/entities/entity.cpp
  13. +95 −49 engines/lastexpress/entities/entity.h
  14. +46 −24 engines/lastexpress/entities/francois.cpp
  15. +27 −18 engines/lastexpress/entities/gendarmes.cpp
  16. +10 −14 engines/lastexpress/entities/hadija.cpp
  17. +12 −11 engines/lastexpress/entities/ivo.cpp
  18. +17 −17 engines/lastexpress/entities/kahina.cpp
  19. +48 −15 engines/lastexpress/entities/kronos.cpp
  20. +13 −13 engines/lastexpress/entities/mahmud.cpp
  21. +24 −20 engines/lastexpress/entities/max.cpp
  22. +111 −82 engines/lastexpress/entities/mertens.cpp
  23. +1 −1 engines/lastexpress/entities/mertens.h
  24. +34 −27 engines/lastexpress/entities/milos.cpp
  25. +10 −7 engines/lastexpress/entities/mmeboutarel.cpp
  26. +12 −12 engines/lastexpress/entities/pascale.cpp
  27. +56 −22 engines/lastexpress/entities/rebecca.cpp
  28. +10 −7 engines/lastexpress/entities/salko.cpp
  29. +4 −3 engines/lastexpress/entities/sophie.cpp
  30. +29 −27 engines/lastexpress/entities/tatiana.cpp
  31. +12 −9 engines/lastexpress/entities/train.cpp
  32. +7 −11 engines/lastexpress/entities/vassili.cpp
  33. +33 −34 engines/lastexpress/entities/verges.cpp
  34. +19 −21 engines/lastexpress/entities/vesna.cpp
  35. +15 −13 engines/lastexpress/entities/waiter1.cpp
  36. +13 −11 engines/lastexpress/entities/waiter2.cpp
  37. +38 −10 engines/lastexpress/entities/yasmin.cpp
  38. +5 −5 engines/lastexpress/game/entities.cpp
  39. +1 −1 engines/lastexpress/game/savegame.cpp
  40. +4 −2 engines/lastexpress/game/savepoint.cpp
  41. +2 −1 engines/lastexpress/game/savepoint.h
  42. +144 −135 engines/lastexpress/game/state.h
  43. +10 −5 engines/lastexpress/shared.h
  44. +7 −7 engines/lastexpress/sound/entry.cpp
  45. +7 −4 engines/lastexpress/sound/entry.h
  46. +1 −1 engines/lastexpress/sound/queue.cpp
  47. +32 −27 engines/lastexpress/sound/sound.cpp
@@ -487,10 +487,7 @@ bool Debugger::cmdPlaySeq(int argc, const char **argv) {

// Handle right-click to interrupt sequence
Common::Event ev;
if (!_engine->getEventManager()->pollEvent(ev))
break;

if (ev.type == Common::EVENT_RBUTTONUP)
if (_engine->getEventManager()->pollEvent(ev) && ev.type == Common::EVENT_RBUTTONUP)
break;

_engine->_system->delayMillis(175);
@@ -41,18 +41,18 @@ namespace LastExpress {

Abbot::Abbot(LastExpressEngine *engine) : Entity(engine, kEntityAbbot) {
ADD_CALLBACK_FUNCTION(Abbot, reset);
ADD_CALLBACK_FUNCTION(Abbot, draw);
ADD_CALLBACK_FUNCTION(Abbot, enterExitCompartment);
ADD_CALLBACK_FUNCTION(Abbot, enterExitCompartment2);
ADD_CALLBACK_FUNCTION_S(Abbot, draw);
ADD_CALLBACK_FUNCTION_SI(Abbot, enterExitCompartment);
ADD_CALLBACK_FUNCTION_SI(Abbot, enterExitCompartment2);
ADD_CALLBACK_FUNCTION(Abbot, callbackActionOnDirection);
ADD_CALLBACK_FUNCTION(Abbot, draw2);
ADD_CALLBACK_FUNCTION(Abbot, updateFromTime);
ADD_CALLBACK_FUNCTION(Abbot, updateFromTicks);
ADD_CALLBACK_FUNCTION(Abbot, playSound);
ADD_CALLBACK_FUNCTION(Abbot, savegame);
ADD_CALLBACK_FUNCTION(Abbot, updateEntity);
ADD_CALLBACK_FUNCTION(Abbot, callSavepoint);
ADD_CALLBACK_FUNCTION(Abbot, updatePosition);
ADD_CALLBACK_FUNCTION_SSI(Abbot, draw2);
ADD_CALLBACK_FUNCTION_I(Abbot, updateFromTime);
ADD_CALLBACK_FUNCTION_I(Abbot, updateFromTicks);
ADD_CALLBACK_FUNCTION_S(Abbot, playSound);
ADD_CALLBACK_FUNCTION_II(Abbot, savegame);
ADD_CALLBACK_FUNCTION_II(Abbot, updateEntity);
ADD_CALLBACK_FUNCTION_SIIS(Abbot, callSavepoint);
ADD_CALLBACK_FUNCTION_SII(Abbot, updatePosition);
ADD_CALLBACK_FUNCTION(Abbot, callbackActionRestaurantOrSalon);
ADD_CALLBACK_FUNCTION(Abbot, chapter1);
ADD_CALLBACK_FUNCTION(Abbot, chapter2);
@@ -79,7 +79,7 @@ Abbot::Abbot(LastExpressEngine *engine) : Entity(engine, kEntityAbbot) {
ADD_CALLBACK_FUNCTION(Abbot, goCompartment4);
ADD_CALLBACK_FUNCTION(Abbot, inCompartment4);
ADD_CALLBACK_FUNCTION(Abbot, chapter4);
ADD_CALLBACK_FUNCTION(Abbot, doWalkSearchingForCath);
ADD_CALLBACK_FUNCTION_II(Abbot, doWalkSearchingForCath);
ADD_CALLBACK_FUNCTION(Abbot, chapter4Handler);
ADD_CALLBACK_FUNCTION(Abbot, leaveDinner);
ADD_CALLBACK_FUNCTION(Abbot, inCompartment);
@@ -616,7 +616,7 @@ IMPLEMENT_FUNCTION(26, Abbot, inSalon1)
break;

case kActionNone:
if (!Entity::updateParameter(params->param2, getState()->time, 4500))
if (!params->param1 || !Entity::updateParameterCheck(params->param2, getState()->time, 4500))
break;

if (getEntities()->isSomebodyInsideRestaurantOrSalon())
@@ -691,15 +691,15 @@ IMPLEMENT_FUNCTION(28, Abbot, openCompartment2)
break;

case kActionNone:
Entity::timeCheckCallback(kTime2052000, params->param1, 1, WRAP_SETUP_FUNCTION(Abbot, setup_goWander));
Entity::timeCheckCallback(kTime2052000, params->param1, 2, WRAP_SETUP_FUNCTION(Abbot, setup_goWander));
break;

case kActionDefault:
getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122358304);
getEntities()->drawSequenceLeft(kEntityAbbot, "508A");

setCallback(1);
setup_playSound("abb3013");
setup_playSound("Abb3013");
break;

case kActionCallback:
@@ -749,6 +749,9 @@ IMPLEMENT_FUNCTION(29, Abbot, goWander)
break;

case 4:
// compare with callback 2.
// This is taken from the original game as is,
// but do we really want real-time 30s in case 2 but simulated-time 15s (aka real-time 5s) here?
setCallback(5);
setup_updateFromTime(225);
break;
@@ -962,7 +965,7 @@ IMPLEMENT_FUNCTION(32, Abbot, goCompartment3)
case 1:
getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue);

setCallback(1);
setCallback(2);
setup_enterExitCompartment("617Ac", kObjectCompartmentC);
break;

@@ -1355,7 +1358,7 @@ IMPLEMENT_FUNCTION(42, Abbot, leaveDinner)

case kActionDefault:
getData()->location = kLocationOutsideCompartment;
getEntities()->updatePositionExit(kEntityAbbot, kCarRestaurant, 67);
getEntities()->updatePositionEnter(kEntityAbbot, kCarRestaurant, 67);

setCallback(1);
setup_callSavepoint("029F", kEntityTables4, kActionDrawTablesWithChairs, "029G");
@@ -1406,16 +1409,16 @@ IMPLEMENT_FUNCTION(43, Abbot, inCompartment)
break;

case kActionNone:
if (params->param1 && params->param4 != kTimeInvalid && params->param2 < getState()->time) {
if (getState()->time < kTime2452500) {
if (params->param1 && params->param4 != kTimeInvalid) {
if (getState()->time > kTime2452500) {
params->param4 = kTimeInvalid;

setCallback(1);
setup_playSound("Abb4002");
break;
} else {
if (!getEntities()->isDistanceBetweenEntities(kEntityAbbot, kEntityPlayer, 1000) || getSoundQueue()->isBuffered(kEntityBoutarel) || !params->param4)
params->param4 = (uint)getState()->time + 450;
params->param4 = (uint)getState()->time;

if (params->param4 < getState()->time) {
params->param4 = kTimeInvalid;
@@ -1655,7 +1658,8 @@ IMPLEMENT_FUNCTION(48, Abbot, afterBomb)
getData()->inventoryItem = kItemNone;

setCallback(4);
setup_updatePosition("126C", kCarRedSleeping, 52);
setup_updatePosition("126C", kCarRestaurant, 52);
break;
}

Entity::timeCheckCallbackInventory(kTime2533500, params->param2, 5, WRAP_SETUP_FUNCTION(Abbot, setup_callbackActionRestaurantOrSalon));
@@ -1771,6 +1775,9 @@ IMPLEMENT_FUNCTION(49, Abbot, catchCath)
getSavePoints()->push(kEntityAbbot, kEntityTatiana, kAction238790488);
getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand);
getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand);

setCallback(1);
setup_savegame(kSavegameTypeEvent, kEventAbbotWrongCompartment);
break;

case kActionDefault:
@@ -1791,7 +1798,7 @@ IMPLEMENT_FUNCTION(49, Abbot, catchCath)
break;

case 1:
getAction()->playAnimation(getObjects()->get(kObjectCompartment2).model < kObjectModel2 ? kEventAbbotWrongCompartmentBed : kEventAbbotWrongCompartment);
getAction()->playAnimation(getObjects()->get(kObjectCompartment2).model == kObjectModel1 ? kEventAbbotWrongCompartmentBed : kEventAbbotWrongCompartment);
getEntities()->updateEntity(kEntityAbbot, kCarRedSleeping, kPosition_6470);
getSound()->playSound(kEntityPlayer, "LIB015");
getScenes()->loadSceneFromObject(kObjectCompartment2, true);
@@ -37,21 +37,21 @@ namespace LastExpress {

Alexei::Alexei(LastExpressEngine *engine) : Entity(engine, kEntityAlexei) {
ADD_CALLBACK_FUNCTION(Alexei, reset);
ADD_CALLBACK_FUNCTION(Alexei, playSound);
ADD_CALLBACK_FUNCTION(Alexei, updateFromTicks);
ADD_CALLBACK_FUNCTION(Alexei, draw);
ADD_CALLBACK_FUNCTION(Alexei, updatePosition);
ADD_CALLBACK_FUNCTION(Alexei, enterExitCompartment);
ADD_CALLBACK_FUNCTION_S(Alexei, playSound);
ADD_CALLBACK_FUNCTION_I(Alexei, updateFromTime);
ADD_CALLBACK_FUNCTION_S(Alexei, draw);
ADD_CALLBACK_FUNCTION_SII(Alexei, updatePosition);
ADD_CALLBACK_FUNCTION_SI(Alexei, enterExitCompartment);
ADD_CALLBACK_FUNCTION(Alexei, callbackActionOnDirection);
ADD_CALLBACK_FUNCTION(Alexei, callSavepoint);
ADD_CALLBACK_FUNCTION(Alexei, savegame);
ADD_CALLBACK_FUNCTION(Alexei, updateEntity);
ADD_CALLBACK_FUNCTION(Alexei, draw2);
ADD_CALLBACK_FUNCTION_SIIS(Alexei, callSavepoint);
ADD_CALLBACK_FUNCTION_II(Alexei, savegame);
ADD_CALLBACK_FUNCTION_II(Alexei, updateEntity);
ADD_CALLBACK_FUNCTION_SSI(Alexei, draw2);
ADD_CALLBACK_FUNCTION(Alexei, callbackActionRestaurantOrSalon);
ADD_CALLBACK_FUNCTION(Alexei, enterComparment);
ADD_CALLBACK_FUNCTION(Alexei, exitCompartment);
ADD_CALLBACK_FUNCTION(Alexei, pacingAtWindow);
ADD_CALLBACK_FUNCTION(Alexei, compartmentLogic);
ADD_CALLBACK_FUNCTION_IS(Alexei, compartmentLogic);
ADD_CALLBACK_FUNCTION(Alexei, chapter1);
ADD_CALLBACK_FUNCTION(Alexei, atDinner);
ADD_CALLBACK_FUNCTION(Alexei, returnCompartment);
@@ -97,8 +97,8 @@ IMPLEMENT_FUNCTION_S(2, Alexei, playSound)
IMPLEMENT_FUNCTION_END

//////////////////////////////////////////////////////////////////////////
IMPLEMENT_FUNCTION_I(3, Alexei, updateFromTicks, uint32)
Entity::updateFromTicks(savepoint);
IMPLEMENT_FUNCTION_I(3, Alexei, updateFromTime, uint32)
Entity::updateFromTime(savepoint);
IMPLEMENT_FUNCTION_END

//////////////////////////////////////////////////////////////////////////
@@ -394,7 +394,7 @@ IMPLEMENT_FUNCTION_IS(16, Alexei, compartmentLogic, TimeValue)

case 7:
setCallback(8);
setup_updateFromTicks(300);
setup_updateFromTime(300);
break;

case 8:
@@ -675,7 +675,7 @@ IMPLEMENT_FUNCTION(20, Alexei, goSalon)

case 4:
getData()->location = kLocationInsideCompartment;
setup_function26();
setup_sitting();
break;
}
break;
@@ -689,7 +689,7 @@ IMPLEMENT_FUNCTION(21, Alexei, sitting)
break;

case kActionNone:
if (Entity::updateParameterCheck(params->param2, getState()->time, params->param1)) {
if (Entity::updateParameterCheck(params->param2, getState()->time, params->param1) && getEntities()->isSomebodyInsideRestaurantOrSalon()) {
getData()->location = kLocationOutsideCompartment;
getData()->inventoryItem = kItemNone;

@@ -738,6 +738,7 @@ IMPLEMENT_FUNCTION(21, Alexei, sitting)
case 3:
getEntities()->drawSequenceLeft(kEntityAlexei, "103B");
getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 52);
getData()->location = kLocationInsideCompartment;
break;
}
break;
@@ -751,7 +752,7 @@ IMPLEMENT_FUNCTION(22, Alexei, standingAtWindow)
break;

case kActionNone:
if (Entity::updateParameter(params->param2, getState()->time, params->param2)) {
if (Entity::updateParameterCheck(params->param2, getState()->time, params->param1)) {
if (getEntities()->isSomebodyInsideRestaurantOrSalon()) {
getData()->location = kLocationOutsideCompartment;
getData()->inventoryItem = kItemNone;
@@ -768,7 +769,7 @@ IMPLEMENT_FUNCTION(22, Alexei, standingAtWindow)
if (getState()->time > kTime1138500) {
params->param3 = kTimeInvalid;
} else {
if (!getEntities()->isInSalon(kEntityPlayer) || getEntities()->isInSalon(kEntityPlayer) || !params->param3)
if (!getEntities()->isInSalon(kEntityPlayer) && !getEntities()->isInRestaurant(kEntityPlayer) || !params->param3)
params->param3 = (uint)getState()->time;

if (params->param3 >= getState()->time)
@@ -788,7 +789,7 @@ IMPLEMENT_FUNCTION(22, Alexei, standingAtWindow)
break;

case kActionDefault:
params->param1 = 255 * (4 * rnd(4) + 8);
params->param1 = 225 * (4 * rnd(4) + 8);
getEntities()->drawSequenceLeft(kEntityAlexei, "103E");
if (!getEvent(kEventAlexeiSalonPoem))
getData()->inventoryItem = kItemParchemin;
@@ -821,7 +822,7 @@ IMPLEMENT_FUNCTION(22, Alexei, standingAtWindow)
getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 52);
getData()->location = kLocationInsideCompartment;

setup_standingAtWindow();
setup_sitting();
break;
}
break;
@@ -835,7 +836,7 @@ IMPLEMENT_FUNCTION(23, Alexei, waitingForTatiana)
break;

case kActionNone:
getData()->inventoryItem = (!getEntities()->isInRestaurant(kEntityAlexei) || getEvent(kEventAlexeiSalonPoem)) ? kItemNone : kItemParchemin;
getData()->inventoryItem = (!getEntities()->isInRestaurant(kEntityTatiana) || getEvent(kEventAlexeiSalonPoem)) ? kItemNone : kItemParchemin;
break;

case kAction1:
@@ -913,7 +914,7 @@ IMPLEMENT_FUNCTION(24, Alexei, upset)

case 1:
getAction()->playAnimation(kEventAlexeiSalonCath);
getData()->car = kCarRestaurant;
getData()->car = kCarRedSleeping;
getData()->entityPosition = kPosition_9460;
getEntities()->clearSequences(kEntityAlexei);
getScenes()->loadSceneFromPosition(kCarRestaurant, 55);
@@ -1125,7 +1126,7 @@ IMPLEMENT_FUNCTION(30, Alexei, atBreakfast)
break;

case 2:
getSound()->playSound(kEntityAlexei, "TAt2116A");
getSound()->playSound(kEntityAlexei, "TAT2116A");
getEntities()->updatePositionEnter(kEntityAlexei, kCarRestaurant, 63);

setCallback(3);
@@ -1333,7 +1334,7 @@ IMPLEMENT_FUNCTION(35, Alexei, pacing3)

case kActionNone:
if (getEntities()->isInSalon(kEntityPlayer)) {
if (Entity::updateParameter(params->param2, getState()->time, 2700)) {
if (Entity::updateParameterCheck(params->param2, getState()->time, 2700)) {
setCallback(1);
setup_callbackActionRestaurantOrSalon();
break;
@@ -1342,7 +1343,7 @@ IMPLEMENT_FUNCTION(35, Alexei, pacing3)
params->param2 = 0;
}

if (Entity::updateParameter(params->param3, getState()->time, params->param1)) {
if (Entity::updateParameterCheck(params->param3, getState()->time, params->param1)) {
if (getEntities()->isSomebodyInsideRestaurantOrSalon()) {
setCallback(3);
setup_pacingAtWindow();
@@ -1535,10 +1536,10 @@ IMPLEMENT_FUNCTION(39, Alexei, meetTatiana)
break;
}

params->param4 = kTimeInvalid;
params->param5 = kTimeInvalid;

getEntities()->updatePositionEnter(kEntityAlexei, kCarGreenSleeping, 70);
getEntities()->updatePositionEnter(kEntityAlexei, kCarGreenSleeping, 71);
getEntities()->updatePositionExit(kEntityAlexei, kCarGreenSleeping, 70);
getEntities()->updatePositionExit(kEntityAlexei, kCarGreenSleeping, 71);

if (getEntities()->isInGreenCarEntrance(kEntityPlayer)) {
getSound()->excuseMe(kEntityAlexei);
@@ -1725,10 +1726,7 @@ IMPLEMENT_FUNCTION(43, Alexei, pacing)
break;

case kActionNone:
if (getState()->time < kTime1806300 && params->param2 < getState()->time) {
if (!params->param2)
params->param2 = (uint)getState()->time + params->param1;

if (getState()->time < kTime1806300 && Entity::updateParameterCheck(params->param2, getState()->time, params->param1)) {
if (getEntities()->isSomebodyInsideRestaurantOrSalon()) {
setCallback(1);
setup_pacingAtWindow();
@@ -1782,7 +1780,7 @@ IMPLEMENT_FUNCTION(44, Alexei, goToPlatform)
break;

case kActionNone:
if (getState()->time > kTime2457000 && !params->param1) {
if (getState()->time > kTime2475000 && !params->param1) {
params->param1 = 1;

getEntities()->updatePositionExit(kEntityAlexei, kCarGreenSleeping, 70);
@@ -1986,7 +1984,7 @@ IMPLEMENT_FUNCTION(47, Alexei, function47)

getData()->entityPosition = kPositionNone;
getData()->location = kLocationOutsideCompartment;
getData()->car = kCarNone;
getData()->car = kCarLocomotive;

getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand);
getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand);
@@ -47,11 +47,11 @@ class Alexei : public Entity {
DECLARE_FUNCTION_1(playSound, const char *filename)

/**
* Updates parameter 2 using ticks value
* Updates parameter 2 using time value
*
* @param ticks The number of ticks to add
* @param time The time to add
*/
DECLARE_FUNCTION_1(updateFromTicks, uint32 ticks)
DECLARE_FUNCTION_1(updateFromTime, uint32 time)

/**
* Draws the entity
Oops, something went wrong.

0 comments on commit 28d83ee

Please sign in to comment.