aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
authorsluicebox2019-06-20 18:42:35 -0700
committerFilippos Karapetis2019-06-21 13:42:34 +0300
commita486438c103c42422ae69a15e288111e643af367 (patch)
treeb2fb2b2040a5bda107ea9b2576f8d96a262a03f1 /engines
parentb9e29cedfc1ad4ee5930245de059fb1701bea793 (diff)
downloadscummvm-rg350-a486438c103c42422ae69a15e288111e643af367.tar.gz
scummvm-rg350-a486438c103c42422ae69a15e288111e643af367.tar.bz2
scummvm-rg350-a486438c103c42422ae69a15e288111e643af367.zip
SCI32: Fix QFG4 Ad Avis end-game bugs
Fixes bugs #10835, #10844, #10989
Diffstat (limited to 'engines')
-rw-r--r--engines/sci/engine/script_patches.cpp262
1 files changed, 262 insertions, 0 deletions
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index b1a3641917..af7003f2d0 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -11424,6 +11424,260 @@ static const uint16 qfg4DomovoiInnPatch[] = {
PATCH_END
};
+// During the final battle with Ad Avis his initial timer is never stopped,
+// reducing the intended time the player has to complete the sequence by more
+// than half, and bringing Ad Avis back to life after he's killed.
+//
+// sTimeItOut state 0 sets a timeout when the battle starts. Its length depends
+// on the detected cpu speed and game version. In the floppy versions this was
+// a minimum of 400 seconds, which is so long that it masked the bug, but in CD
+// it was reduced to 20 seconds. This is supposed to be how long the player has
+// to tell the joke, after which sUltimakeJoke sets a second timeout in which
+// the character-specific actions are to be done, but sTimeItOut finishes first
+// and forces the player to complete both phases during the first shorter one.
+// When Ad Avis is killed his death scripts only stop sUltimakeJoke, as they
+// don't expect sTimeItOut to be running, and so when sTimeItOut times out it
+// kills the player unless the death script has already called avis:dispose.
+//
+// We fix this by patching sTimeItOut state 1 to abort the script if the joke
+// has been told. This is equivalent to the NRS patch that ships with the GOG
+// version, which disposes sTimeItOut when telling the joke, and so this patch
+// is applied to all versions except that one.
+//
+// Applies to: All versions
+// Responsible method: sTimeItOut:changeState(1)
+// Fixes bug: #10844
+static const uint16 qfg4AdAvisTimeoutSignature[] = {
+ 0x30, SIG_UINT16(0x002c), // bnt 002c [ state 1 ]
+ SIG_ADDTOOFFSET(+0x29),
+ SIG_MAGICDWORD,
+ 0x32, SIG_UINT16(0x00ae), // jmp 00ae [ end of method ]
+ 0x3c, // dup
+ 0x35, 0x01, // ldi 01
+ 0x1a, // eq?
+ 0x30, SIG_UINT16(0x0027), // bnt 0027 [ state 2 ]
+ SIG_END
+};
+
+static const uint16 qfg4AdAvisTimeoutPatch[] = {
+ 0x30, PATCH_UINT16(0x0029), // bnt 0029 [ state 1 ]
+ PATCH_ADDTOOFFSET(+0x29),
+ 0x3c, // dup
+ 0x35, 0x01, // ldi 01
+ 0x1a, // eq?
+ 0x31, 0x2b, // bnt 2b [ state 2 ]
+ 0x83, 0x04, // lal 04 [ has joke been told? ]
+ 0x2f, 0x27, // bt 27 [ abort script if joke has been told ]
+ PATCH_END
+};
+
+// During the final battle with Ad Avis if the player casts a non-fatal spell at
+// him then they can't cast any more spells. Instead they receive a message
+// about being too busy or it not being a good place and must wait to die.
+// Another symptom of this bug is that fighters and thieves don't get to see
+// the staff transform if they've previously thrown a weapon.
+//
+// SpellItem:doVerb(4) determines if a spell is allowed. If hero:view is the
+// wrong value then it says "This isn't a good place..." and if the room has
+// a script it says "You're too busy...". avis:getHurt breaks one or both of
+// these conditions by setting the room script to sMessages. If the game speed
+// is set to less than high then avis:getHurt runs while the "project" room
+// script is animating hero. Setting the room script to sMessages interrupts
+// this and hero is left on view 14, breaking the first spell condition. Even
+// if the game speed is set to high and hero's animation completes, sMessages
+// fails to dispose itself, leaving it as the room script when it's complete
+// and breaking the second spell condition.
+//
+// We fix this by reassigning sMessages from the room's script to midBlast, an
+// arbitrary Prop that no scripts depend on. project is no longer interrupted,
+// hero's animation completes at all speeds, and it no longer matters that
+// sMessage fails to dispose itself. Due to script changes, this patch is only
+// applied once to floppy and twice to CD.
+//
+// We also include a version of this for the offsets in the NRS patch, which is
+// important as that ships with the GOG version.
+//
+// Applies to: All versions
+// Responsible method: avis:getHurt
+// Fixes bug: #10835
+static const uint16 qfg4AdAvisSpellsFloppySignature[] = {
+ SIG_MAGICDWORD,
+ 0x72, SIG_UINT16(0x0096), // lofsa sMessages
+ 0x36, // push
+ 0x81, 0x02, // lag 02
+ 0x4a, SIG_UINT16(0x0006), // send 06 [ rm730 setScript: sMessages ]
+ SIG_END
+};
+
+static const uint16 qfg4AdAvisSpellsFloppyPatch[] = {
+ 0x74, PATCH_ADDTOOFFSET(+2), // lofss sMessages
+ 0x72, PATCH_UINT16(0x0668), // lofsa midBlast
+ SIG_END
+};
+
+static const uint16 qfg4AdAvisSpellsCDSignature[] = {
+ SIG_MAGICDWORD,
+ 0x72, SIG_UINT16(0x00a6), // lofsa sMessages
+ 0x36, // push
+ 0x81, 0x02, // lag 02
+ 0x4a, SIG_UINT16(0x0006), // send 06 [ rm730 setScript: sMessages ]
+ SIG_END
+};
+
+static const uint16 qfg4AdAvisSpellsCDPatch[] = {
+ 0x74, PATCH_ADDTOOFFSET(+2), // lofss sMessages
+ 0x72, PATCH_UINT16(0x06b6), // lofsa midBlast
+ SIG_END
+};
+
+static const uint16 qfg4AdAvisSpellsNrsSignature[] = {
+ SIG_MAGICDWORD,
+ 0x72, SIG_UINT16(0x00a8), // lofsa sMessages
+ 0x36, // push
+ 0x81, 0x02, // lag 02
+ 0x4a, SIG_UINT16(0x0006), // send 06 [ rm730 setScript: sMessages ]
+ SIG_END
+};
+
+static const uint16 qfg4AdAvisSpellsNrsPatch[] = {
+ 0x74, PATCH_ADDTOOFFSET(+2), // lofss sMessages
+ 0x72, PATCH_UINT16(0x06b8), // lofsa midBlast
+ SIG_END
+};
+
+// If the magic user defeats Ad Avis with the game speed set to less than high
+// then they aren't allowed to cast the final summon staff spell and complete
+// the game. Instead they receive "You're too busy to cast a spell right now."
+//
+// Spells can't be cast if a room script is set, as described in the above patch
+// notes. When Ad Avis is killed, avis:getHurt sets hero's view and loop before
+// running sAdavisDies. If the game speed isn't set to high then the "project"
+// script that deployed the final projectile spell is still running and waiting
+// on hero's animation to complete. By changing the view and loop, avis:getHurt
+// prevents project from advancing to its next state and completing, leaving it
+// stuck as the room script and blocking the final spell.
+//
+// We can't prevent project from being the room script as that's game-wide
+// behavior, and we can't prevent it from being interrupted since avis:getHurt
+// needs to set hero's final view/loop, but we can still fix the bug by setting
+// sAdavisDies as the room's script instead of hero's. This disposes project if
+// it's still running and guarantees that the room script is cleared since
+// sAdavisDies always disposes of itself.
+//
+// Applies to: All versions
+// Responsible method: avis:getHurt
+// Fixes bug: #10835
+static const uint16 qfg4AdAdvisLastSpellSignature[] = {
+ 0x38, SIG_SELECTOR16(setScript), // pushi setScript
+ 0x78, // push1
+ 0x72, SIG_ADDTOOFFSET(+2), // lofsa sAdavisDies
+ SIG_MAGICDWORD,
+ 0x36, // push
+ 0x81, 0x00, // lag 00
+ 0x4a, SIG_UINT16(0x0006), // send 06 [ hero setScript: sAdavisDies ]
+ SIG_END
+};
+
+static const uint16 qfg4AdAdvisLastSpellPatch[] = {
+ PATCH_ADDTOOFFSET(+8),
+ 0x81, 0x02, // lag 02 [ rm730 ]
+ SIG_END
+};
+
+// When throwing a weapon or casting a spell at Ad Avis in room 730, sMessages
+// tests the projectile type incorrectly and transposes the message responses.
+//
+// Applies to: All versions
+// Responsible method: sMessages:changeState(2)
+// Fixes bug: #10989
+static const uint16 qfg4AdAvisMessageSignature[] = {
+ 0x8b, SIG_MAGICDWORD, 0x01, // lsl 01 [ 0 if weapon thrown, else a spell ]
+ 0x35, 0x00, // ldi 00
+ 0x1a, // eq?
+ SIG_END
+};
+
+static const uint16 qfg4AdAvisMessagePatch[] = {
+ PATCH_ADDTOOFFSET(+4),
+ 0x1c, // ne?
+ PATCH_END
+};
+
+// Throwing a rock or dagger at Ad Avis after telling the joke kills him.
+// avis:getHurt fails to test the projectile type correctly in CD, or at all in
+// floppy, and so all versions mistake this for casting a spell with the staff.
+//
+// We fix this by testing the projectile type and not allowing a thrown weapon
+// to kill Ad Avis. This replaces an unnecessary hero:script test.
+//
+// Applies to: All versions
+// Responsible method: avis:getHurt
+// Fixes bug: #10989
+static const uint16 qfg4AdAvisThrowWeaponSignature[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_SELECTOR16(script), // pushi script
+ 0x76, // push0
+ 0x81, 0x00, // lag 00
+ 0x4a, SIG_UINT16(0x0004), // send 04 [ hero script? ]
+ 0x18, // not
+ 0x30, SIG_ADDTOOFFSET(+2), // bnt [ projectile doesn't kill ad avis ]
+ 0x39, SIG_SELECTOR8(view), // pushi view
+ SIG_END
+};
+
+static const uint16 qfg4AdAvisThrowWeaponPatch[] = {
+ 0x83, 0x01, // lal 01 [ 0 if weapon thrown, else a spell ]
+ 0x33, 0x06, // jmp 06 [ throwing a weapon doesn't kill ad avis ]
+ PATCH_END
+};
+
+// When a fighter or paladin selects the staff in the final battle with Ad Avis
+// after throwing a rock or dagger they enter an infinite animation loop due to
+// not clearing hero:cycler. Multiple bugs in this room prevented getting this
+// far, but we fixed those, so we also fix this by clearing the cycler.
+//
+// Applies to: All versions
+// Responsible method: sDoTheStaff:changeState(3)
+// Fixes bug: #10835
+static const uint16 qfg4FighterSpearSignature[] = {
+ 0x39, SIG_SELECTOR8(view), // pushi view [ start of fighter code, same as paladin ]
+ SIG_ADDTOOFFSET(0x3e),
+ 0x3c, // dup
+ 0x35, SIG_MAGICDWORD, 0x03, // ldi 03
+ 0x1a, // eq? [ is paladin? (last condition so always true) ]
+ 0x30, SIG_UINT16(0x0022), // bnt 0022
+ 0x39, SIG_SELECTOR8(view), // pushi view
+ 0x78, // push1
+ 0x39, 0x0a, // pushi 0a
+ 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
+ 0x7a, // push2
+ 0x76, // push0
+ 0x78, // push1
+ 0x38, SIG_SELECTOR16(setCel), // pushi setCel
+ SIG_ADDTOOFFSET(+13),
+ 0x4a, SIG_UINT16(0x001c), // send 1c [ hero view: 10 setLoop 0 1 setCel: 0 ... ]
+ SIG_END
+};
+
+static const uint16 qfg4FighterSpearPatch[] = {
+ 0x33, 0x3e, // jmp 3e [ use patched paladin code for fighter ]
+ PATCH_ADDTOOFFSET(0x3e),
+ 0x39, PATCH_SELECTOR8(view), // pushi view
+ 0x78, // push1
+ 0x39, 0x0a, // pushi 0a
+ 0x38, PATCH_SELECTOR16(setLoop), // pushi setLoop
+ 0x7a, // push2
+ 0x76, // push0
+ 0x78, // push1
+ 0x38, PATCH_SELECTOR16(setCel), // pushi setCel
+ 0x39, 0x01, // pushi 01
+ 0x39, 0x00, // pushi 00
+ 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
+ PATCH_ADDTOOFFSET(+13),
+ 0x4a, PATCH_UINT16(0x0022), // send 22 [ hero view: 10 setLoop 0 1 setCel: 0 setCycle: 0 ... ]
+ PATCH_END
+};
+
// The script that determines how much money a revenant has is missing the first
// parameter to kRandom, which should be zero as it is with other monsters.
// Instead of awarding the intended 15 to 40 kopeks, it always awards 15 and
@@ -11508,6 +11762,14 @@ static const SciScriptPatcherEntry qfg4Signatures[] = {
{ 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, 730, "fix ad avis timeout", 1, qfg4AdAvisTimeoutSignature, qfg4AdAvisTimeoutPatch },
+ { true, 730, "Floppy: fix casting spells at ad avis", 1, qfg4AdAvisSpellsFloppySignature, qfg4AdAvisSpellsFloppyPatch },
+ { true, 730, "CD: fix casting spells at ad avis", 2, qfg4AdAvisSpellsCDSignature, qfg4AdAvisSpellsCDPatch },
+ { true, 730, "NRS: fix casting spells at ad avis", 2, qfg4AdAvisSpellsNrsSignature, qfg4AdAvisSpellsNrsPatch },
+ { true, 730, "fix casting last spell at ad avis", 1, qfg4AdAdvisLastSpellSignature, qfg4AdAdvisLastSpellPatch },
+ { true, 730, "fix ad avis projectile message", 1, qfg4AdAvisMessageSignature, qfg4AdAvisMessagePatch },
+ { true, 730, "fix throwing weapons at ad avis", 1, qfg4AdAvisThrowWeaponSignature,qfg4AdAvisThrowWeaponPatch },
+ { true, 730, "fix fighter's spear animation", 1, qfg4FighterSpearSignature, qfg4FighterSpearPatch },
{ true, 800, "fix setScaler calls", 1, qfg4SetScalerSignature, qfg4SetScalerPatch },
{ true, 800, "fix grapnel removing hero's scaler", 1, qfg4RopeScalerSignature, qfg4RopeScalerPatch },
{ true, 801, "fix runes puzzle (1/2)", 1, qfg4RunesPuzzleSignature1, qfg4RunesPuzzlePatch1 },