aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
authorVhati2018-12-27 00:49:35 -0500
committerFilippos Karapetis2019-01-01 23:47:40 +0200
commitaa9a1ab9018ccb59734577652dfdac03b2f5df41 (patch)
tree3f05261e98bf28a9efafa2c45a5735d6089ac82d /engines
parent9bbd0474fab672ff843caf40305475f856b30f74 (diff)
downloadscummvm-rg350-aa9a1ab9018ccb59734577652dfdac03b2f5df41.tar.gz
scummvm-rg350-aa9a1ab9018ccb59734577652dfdac03b2f5df41.tar.bz2
scummvm-rg350-aa9a1ab9018ccb59734577652dfdac03b2f5df41.zip
SCI32: Fix QFG4 cave tentacle
Fixes wriggling and retraction when hero travels over the pit, bug #10615 Supersedes commit 259f262
Diffstat (limited to 'engines')
-rw-r--r--engines/sci/engine/script_patches.cpp198
-rw-r--r--engines/sci/engine/workarounds.cpp2
2 files changed, 199 insertions, 1 deletions
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 112e04d4d4..f291bc8bd5 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -159,8 +159,10 @@ static const char *const selectorNameTable[] = {
"plane", // RAMA
"state", // RAMA
"getSubscriberObj", // RAMA
+ "cue", // QFG4
"moveSpeed", // QFG4
"setLooper", // QFG4
+ "setSpeed", // QFG4
"value", // QFG4
#endif
NULL
@@ -245,8 +247,10 @@ enum ScriptPatcherSelectors {
SELECTOR_plane,
SELECTOR_state,
SELECTOR_getSubscriberObj,
+ SELECTOR_cue,
SELECTOR_moveSpeed,
SELECTOR_setLooper,
+ SELECTOR_setSpeed,
SELECTOR_value
#endif
};
@@ -9253,6 +9257,196 @@ static const uint16 qfg4Tarot5PriorityPatch[] = {
PATCH_END
};
+// When crossing the cave's tightrope in room 710, a tentacle emerges, and then
+// its animation freezes - in ScummVM, not the original interpreter. This
+// happens because of an extraneous argument passed to setCycle().
+//
+// (tentacle setCycle: RandCycle tentacle)
+//
+// RandCycle can accept an optional arg, but it expects a number, not an
+// object. ScummVM doesn't catch the faulty arithmetic and behaves abnormally.
+// We remove the bad arg.
+//
+// Applies to at least: English CD, English floppy, German floppy
+// Responsible method: sTentacleDeath::changeState(3) in script 710
+// Fixes bug: #10615
+static const uint16 qfg4TentacleWriggleSignature[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
+ 0x7a, // push2
+ 0x51, SIG_ADDTOOFFSET(+1), // class RandCycle
+ 0x36, // push
+ 0x72, SIG_ADDTOOFFSET(+2), // loffsa tentacle
+ 0x36, // push
+ 0x72, SIG_ADDTOOFFSET(+2), // loffsa tentacle
+ 0x4a, SIG_UINT16(0x0008), // send 08
+ SIG_END
+};
+
+static const uint16 qfg4TentacleWrigglePatch[] = {
+ PATCH_ADDTOOFFSET(+3),
+ 0x78, // push1 (1 setCycle arg)
+ PATCH_ADDTOOFFSET(+3), // ...
+ 0x35, 0x00, // ldi 0 (erase 2 bytes)
+ 0x35, 0x00, // ldi 0 (erase 2 bytes)
+ PATCH_ADDTOOFFSET(+3), // ...
+ 0x4a, PATCH_UINT16(0x0006), // send 06
+ PATCH_END
+};
+
+// When crossing the cave's tightrope in room 710, a tentacle emerges. The
+// tentacle is supposed to reach a state where it waits indefinitely for an
+// external cue() to retract. If the speed slider is too high, a fighter
+// reaches the other side and sends a cue() before the tentacle is ready to
+// receive it. The tentacle never retracts.
+//
+// The fighter script (crossByHand) drains stamina in state 2 as hero moves
+// across. A slower speed would cost extra stamina. We add a delay after that
+// part is over, in state 3, just as hero is about to dismount. When state 4
+// cues, the tentacle script (sTentacleDeath) will be ready (state 3).
+//
+// To create that delay we set the "cycles" property for a countdown and remove
+// all other advancement mechanisms. State 3 had a cue from say() and a
+// self-cue(). The former's "self" arg becomes null. The latter is erased.
+//
+// Crossing from the left (crossByHandLeft) doesn't require fixing.
+//
+// Applies to at least: English CD, English floppy, German floppy
+// Responsible method: crossByHand::changeState(3) in script 710
+// Fixes bug: #10615
+static const uint16 qfg4PitRopeFighterSignature[] = {
+ 0x65, SIG_ADDTOOFFSET(+1), // aTop state
+ SIG_ADDTOOFFSET(+269), // ...
+ 0x31, 0x1e, // bnt 30d (set a flag and say() on 1st crossing, else cue)
+ SIG_ADDTOOFFSET(+8), // ...
+ 0x38, SIG_SELECTOR16(say), // pushi say ("You just barely made it")
+ 0x38, SIG_UINT16(0x0005), // pushi 5d
+ 0x39, 0x0a, // pushi 10d
+ 0x39, 0x06, // pushi 6d
+ 0x39, 0x20, // pushi 32d
+ 0x76, // push0
+ 0x7c, // pushSelf
+ SIG_ADDTOOFFSET(+5), // ...
+ 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
+ SIG_MAGICDWORD,
+ 0x38, SIG_SELECTOR16(cue), // pushi cue
+ 0x76, // push0
+ 0x54, SIG_UINT16(0x0004), // self 04
+ 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
+ SIG_END
+};
+
+static const uint16 qfg4PitRopeFighterPatch[] = {
+ PATCH_ADDTOOFFSET(+271), // ... (2 + 269)
+ 0x31, 0x1b, // bnt 26d (skip the say w/o ending the switch)
+ PATCH_ADDTOOFFSET(+21), // ... (8 + 13)
+ 0x76, // push0 (null caller, so say won't cue crossByHand)
+ PATCH_ADDTOOFFSET(+5), // ...
+ // (no jmp, self-cue becomes cycles)
+ 0x35, 0x20, // ldi 32d
+ 0x65, PATCH_GETORIGINALBYTE(1)+6, // aTop cycles (property offset = @state + 6d)
+ 0x33, 0x04, // jmp 4d [skip waste bytes, end the switch]
+ PATCH_END
+};
+
+// As above, mages at high speed can get across the pit in room 710 before
+// tentacle is ready to receive a cue().
+//
+// As luck would have it, hero's speed is cached and restored. Twice actually.
+//
+// Overview of the mage script (sLevitateOverPit)
+// State 1-3: Cache hero's slider-based speed.
+// Set a temporary speed as hero unfurls the cloth.
+// Restore the original value.
+// Call handsOn(). Wait for an external cue().
+// State 4: Call handsOff().
+// If cued by the Levitate spell (script 21), go to 5-7.
+// A plain cue() from anywhere else, leads to state 8.
+// State 5-7: Move across the pit. Skip to state 9.
+// State 8: An abort message.
+// State 9-10: Cache hero's speed again.
+// Set a temporary speed as hero folds up the cloth.
+// Restore the original value, and normalize hero. Call handsOn().
+//
+// Patch 1: We overwrite some derelict code in state 5, caching the
+// slider-based speed again, in case the player adjusted it before casting
+// Levitate, then setting a fixed speed of our own for the crossing.
+//
+// Patch 2: Patch 1 already cached and clobbered the speed. We remove the
+// original attempt to cache again in state 9.
+//
+// The result is caching/restoration at the beginning, aborting or caching and
+// crossing with our fixed value, and a restoration at the end (whichever value
+// was last cached). The added travel time has no side effect for mages.
+//
+// Mages have no other script to levitate across from left to right. At some
+// point in development, the meaning of "register" changed. The derelict
+// state 5 code thought 0/1 meant move right/left. Whereas state 4 decides 0/1
+// means abort/cross, only ever moving left. The rightward MoveTo never runs.
+//
+// Applies to at least: English CD, English floppy, German floppy
+// Responsible method: sLevitateOverPit::changeState(5) in script 710
+// Fixes bug: #10615
+static const uint16 qfg4PitRopeMageSignature1[] = {
+ 0x30, SIG_UINT16(0x0017), // bnt 23d [if register == 0 (never), move right]
+ SIG_ADDTOOFFSET(+20), // ... (move left)
+ 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
+
+ 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion (move right)
+ 0x38, SIG_UINT16(0x0004), // pushi 4d
+ 0x51, SIG_ADDTOOFFSET(1), // class MoveTo
+ 0x36, // push
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x00da), // pushi 218d
+ 0x39, 0x30, // pushi 48d
+ 0x7c, // pushSelf
+ 0x81, 0x00, // lag global[0] (hero)
+ 0x4a, SIG_UINT16(0x000c), // send 12d
+ 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
+ SIG_END
+};
+
+static const uint16 qfg4PitRopeMagePatch1[] = {
+ 0x34, PATCH_UINT16(0x0000), // ldi 0 (erase the branch)
+ PATCH_ADDTOOFFSET(+20), // ...
+
+ 0x38, SIG_SELECTOR16(cycleSpeed), // pushi cycleSpeed
+ 0x76, // push0
+ 0x81, 0x00, // lag global[0] (hero)
+ 0x4a, SIG_UINT16(0x0004), // send 4d
+ 0xa3, 0x02, // sal local[2] (cache again)
+ //
+ 0x38, SIG_SELECTOR16(setSpeed), // pushi setSpeed
+ 0x78, // push1
+ 0x39, 0x08, // pushi 8d (set our fixed speed)
+ 0x81, 0x00, // lag global[0] (hero)
+ 0x4a, SIG_UINT16(0x0006), // send 6d
+ 0x5c, // selfID (erase 1 byte to keep disasm aligned)
+ PATCH_END
+};
+
+// Responsible method: sLevitateOverPit::changeState(9) in script 710
+static const uint16 qfg4PitRopeMageSignature2[] = {
+ SIG_MAGICDWORD,
+ 0x35, 0x09, // ldi 9d (case 9 label)
+ 0x38, SIG_SELECTOR16(cycleSpeed), // pushi cycleSpeed
+ SIG_ADDTOOFFSET(+6), // ...
+ 0xa3, 0x02, // sal local[2] (original re-cache)
+ SIG_ADDTOOFFSET(+48), // ...
+ 0x38, SIG_SELECTOR16(cycleSpeed), // pushi cycleSpeed
+ 0x78, // push1
+ 0x8b, 0x02, // lsl local[2] (restore cached speed)
+ SIG_END
+};
+
+static const uint16 qfg4PitRopeMagePatch2[] = {
+ PATCH_ADDTOOFFSET(+11), // (don't cache our fixed speed)
+ 0x35, 0x00, // ldi 0 (erase 2 bytes)
+ PATCH_ADDTOOFFSET(+48), // ...
+ 0x38, PATCH_SELECTOR16(setSpeed), // pushi setSpeed (keep cycleSpeed & moveSpeed sync'd)
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry qfg4Signatures[] = {
{ true, 0, "prevent autosave from deleting save games", 1, qg4AutosaveSignature, qg4AutosavePatch },
@@ -9290,6 +9484,10 @@ static const SciScriptPatcherEntry qfg4Signatures[] = {
{ false, 663, "CD: fix crest bookshelf", 1, qfg4CrestBookshelfCDSignature, qfg4CrestBookshelfCDPatch },
{ false, 663, "Floppy: fix crest bookshelf", 1, qfg4CrestBookshelfFloppySignature, qfg4CrestBookshelfFloppyPatch },
{ true, 663, "CD/Floppy: fix crest bookshelf motion", 1, qfg4CrestBookshelfMotionSignature, qfg4CrestBookshelfMotionPatch },
+ { true, 710, "fix tentacle wriggle cycler", 1, qfg4TentacleWriggleSignature, qfg4TentacleWrigglePatch },
+ { true, 710, "fix tentacle retraction for fighter", 1, qfg4PitRopeFighterSignature, qfg4PitRopeFighterPatch },
+ { true, 710, "fix tentacle retraction for mage (1/2)", 1, qfg4PitRopeMageSignature1, qfg4PitRopeMagePatch1 },
+ { true, 710, "fix tentacle retraction for mage (2/2)", 1, qfg4PitRopeMageSignature2, qfg4PitRopeMagePatch2 },
{ true, 800, "fix setScaler calls", 1, qfg4SetScalerSignature, qfg4SetScalerPatch },
{ true, 800, "fix grapnel removing hero's scaler", 1, qfg4RopeScalerSignature, qfg4RopeScalerPatch },
{ true, 803, "fix sliding down slope", 1, qfg4SlidingDownSlopeSignature, qfg4SlidingDownSlopePatch },
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 184f4adb89..0bff1f62f2 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -87,7 +87,7 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = {
{ GID_QFG1VGA, 301, 928, 0, "Blink", "init", NULL, 0, 0, { WORKAROUND_FAKE, 0 } }, // op_div: when entering the inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object
{ GID_QFG2, 200, 200, 0, "astro", "messages", NULL, 0, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer - bug #5152
{ GID_QFG3, 780, 999, 0, "", "export 6", NULL, 0, 0, { WORKAROUND_FAKE, 0 } }, // op_add: trying to talk to yourself at the top of the giant tree - bug #6692
- { GID_QFG4, 710,64941, 0, "RandCycle", "doit", NULL, 0, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves
+// { GID_QFG4, 710,64941, 0, "RandCycle", "doit", NULL, 0, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves
{ GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, 0, { WORKAROUND_FAKE, 1 } }, // op_div: when Lycentia knocks Torin out after he removes her collar
SCI_WORKAROUNDENTRY_TERMINATOR
};