Opened 10 months ago

Closed 4 months ago

#10844 closed defect (fixed)

QFG4: Ad Avis' death is interrupted, he returns and kills hero

Reported by: Vhati Owned by: sluicebox
Priority: normal Component: Engine: SCI
Keywords: SCI32 original Cc:
Game: Quest for Glory 4

Description

ScummVM 2.1.0git4278-gd31e37683c (Dec 13 2018 04:20:25)
Windows 7 64bit
QFG4 CD (English)

In the crystal room, hero is supposed to kill Ad Avis once and for all - he gets sucked away by Avoozl. Sometimes that doesn't finish, and he springs back to life.

This is probably what the NRS patch referred to as "Ad Avis respawning after being killed".

File - 5kb MD5 - Full MD5
RESOURCE.000 - 263dce4aa34c49d3ad29bec889007b1c - 1364ba69e3c0abb68cc0170650a56692
RESOURCE.AUD - c39521bffb1d8b19a57394866184a0ca - 71098b9e97e20c8941c0e4812d5f906f
RESOURCE.MAP - aba367f2102e81782d961b14fbe3d630 - 801a04cc6aa5d437681a2dd0b6545248
RESOURCE.SFX - 3cf95e09dab8b11d675e0537e18b499a - 7c858d7253f86dab4cc6066013c5ecec

Change History (10)

comment:1 by Vhati, 10 months ago

Occurs in the floppy edition under ScummVM, too.


QFG4 Floppy 1.1a + note patch (English)

File - 5kb MD5 - Full MD5
RESOURCE.000 - f64fd6aa3977939a86ff30783dd677e1 - ff42260a665995a85aeb277ad80aac8a
RESOURCE.MAP - d10a4cc177d2091d744e2ad8c049b0ae - 3695b1b0a1d15f3d324ea9f0cc325245
RESOURCE.SFX - 3cf95e09dab8b11d675e0537e18b499a - 7c858d7253f86dab4cc6066013c5ecec

comment:2 by Vhati, 10 months ago

Quick and dirty way to reproduce...

  • Create a new character.
  • Teleport to the endgame crystal room.
    • room 730
  • Esc through the dialogue.
  • A good time to save is when hero is climbing up to stand.
  • Click MOUTH on hero, "Tell Ultimate Joke".
  • Shorten the timeout.
    • CD
      • send sTimeItOut ticks 130
    • Floppy
      • vv g 88 (print global88's decimal value)
      • send sTimeItOut lastTicks {global88's value}
      • send sTimeItOut ticks 110
      • send sTimeItOut seconds 0
  • Trigger Ad Avis' dying script.
    • send avis getHurt
  • Dismiss the debugger and watch the scripts conflict.
Version 0, edited 10 months ago by Vhati (next)

comment:3 by Vhati, 10 months ago

When hero kills Ad Avis, a local2 is set to 1. His sprite isn't disposed until a few states later.


script 730 - sAdavisDies::changeState()

(0 (= seconds 1))
(1
	(g1_Glory handsOff:)
	(= loc2_avisDied 1)
	(heroTeller dispose:)
	(avis setScript: 0)
	(avis view: 748 setLoop: 0 1 setCel: 0 posn: 91 141)
	(self cue:)
)
# ...
(6
	(avis dispose:)
	(g103_longSong number: 107 loop: 1 play: self)
)



Meanwhile, a background script waits an amount of time based on the skill slider (values: 1,2,3).
After the countdown expires, it makes Ad Avis launch a fireball and kill hero instantly.


CD, script 730 - sTimeItOut::changeState()

(0
	(switch g439_mySkillSlider
		(1 (= ticks 2400))
		(2 (= ticks 1800))
		(3 (= ticks 1200))
	)
)
(1
	(avis
		view: 677
		setLoop: 0 1
		setCel: 0
		setCycle: CT 5 1 self
	)
)
# ...

If a tick is 1/60th of a second, that's between 40 and 20 seconds.
At state 1, it mistakenly assumes if there's still a sprite, Ad Avis must be alive.

If the sprite had already been disposed at that point, the cycle would never complete, and the background script would stall harmlessly.


Floppy, script 730 - sTimeItOut::changeState()

(0
	(switch g439_mySkillSlider
		(1 (= seconds 600))
		(2 (= seconds 500))
		(3 (= seconds 400))
	)
)
(1
	(avis
		view: 677
		setLoop: 0 1
		setCel: 0
		setCycle: CT 5 1 self
	)
)
# ...

The floppy edition waited in seconds instead of ticks.
Up to ten minutes!?

comment:4 by Vhati, 10 months ago

There's another lethal timer scheduled when hero tells the Ultimate Joke.


CD, script 730 - sUltimateJoke::changeState()

(3
	(avis setLoop: 1 1 setCycle: Fwd)
	(switch g439_mySkillSlider
		(1 (= seconds 55))
		(2 (= seconds 45))
		(3 (= seconds 30))
	)
)
(4
	(avis
		view: 677
		setLoop: 0 1
		setCel: 0
		setCycle: CT 5 1 self
	)
)

The other timer will always end the game before this cues.
Would've been an identical fireball death anyway.


Floppy, script 730 - sUltimateJoke::changeState()

(3
	(avis setLoop: 1 1 setCycle: Fwd)
	(switch global439
		(1 (= seconds 30))
		(2 (= seconds 15))
		(3 (= seconds 10))
	)
)
(4
	(avis
		view: 677
		setLoop: 0 1
		setCel: 0
		setCycle: CT 5 1 self
	)
)

A second short timer kind of makes sense when the alternative is a ridiculous 10 minutes. The latter gives players time for dialogue. Then they tell the joke to distract Ad Avis, and there's urgency to follow through with the attack.


After the CD edition shortened sTimeItOut, this joke timer became redundant.
It'd be better to unconditionally dispose() it in state 3 and forgo the fireball stuff.

Last edited 10 months ago by Vhati (previous) (diff)

comment:5 by Vhati, 10 months ago

sTimeItOut is scheduled at the end of the cutscene battle between Ad Avis and Katrina.


CD, script 731 - enterScr::changeState()

(53
	(g103_longSong number: 105 loop: -1 play:)
	(switch g439_mySkillSlider
		(1 (PalVary pvINIT 0 600))
		(2 (PalVary pvINIT 0 500))
		(3 (PalVary pvINIT 0 400))
	)
	# (crystal setScript: sTimeItOut)
	((ScriptID 730 9) setScript: (ScriptID 730 10))
	(g1_Glory handsOn:)
	(g69_mainIconBar disable: 0)
	(self dispose:)
)

Interesting. Both editions have 600/500/400 there, like in the floppy's timer.
Except these are in tick units.


API: SCI Companion - PalVary (kernel)

PalVary(pvINIT resourceNumber ticks [stepStop direction])

Begins a transition to a new palette. This only needs to be called once.

  • resourceNumber (number) – The palette resource number.
  • ticks (number) – The number of ticks between each step in the transition (60 ticks is one second).
  • stepStop (number) – How far along the transition to go, ranging from 0-64, where 64 is the complete transition. The default is 64.
  • direction (number) – 1 for forward, -1 for reverse.



Maybe that palette shift was supposed to provide a visual indication for the timer? A complete transition would be 64 steps, times the given value. The floppy timer using seconds would be 60 times as long.

Maybe the CD forgot to adjust those values when the timing changed?
Again, this shift looks like it ought to take over 10 minutes to complete.

Last edited 10 months ago by Vhati (previous) (diff)

comment:6 by sluicebox, 10 months ago

Thanks for the write up, I'm in the process of testing a patch for this and the other bugs in the room #10835

comment:7 by Vhati, 10 months ago

@sluicebox:

I'm in the process of testing a patch for this

Me too. Besides fixing this bug both editions, mine also backports the CD timings to the floppy in a single patch (reducing to a formula, using ticks, with the property offset determined by state's offset).

I was waiting until I was confident my ProjObj solution for #10835 would not require adjusting the durations here.


EDIT: Meant to say backporting CD's timings to floppy, in ticks.

Last edited 10 months ago by Vhati (previous) (diff)

comment:9 by Filippos Karapetis <bluegr@…>, 4 months ago

In a486438:

SCI32: Fix QFG4 Ad Avis end-game bugs

Fixes bugs #10835, #10844, #10989

comment:10 by bluegr, 4 months ago

Resolution: fixed
Status: newclosed
Note: See TracTickets for help on using tickets.