Opened 7 years ago

Closed 6 years ago

#9806 closed defect (fixed)

SCI: SQ4 Windows: Stuck when picking up matches

Reported by: dafioram Owned by: sluicebox
Priority: normal Component: Engine: SCI
Version: Keywords: original has-pull-request
Cc: Game: Space Quest 4

Description

ScummVM: 1.10.0git-3309-g63a87ac
OS: win7-64
Game: SQ4 CD English (Space Quest Collection) v1.0

In the SQ1 portion of SQ4 when you are returning to the bar after kicking over the motorcycles if you enter the bar without dodging the motorcycle and then you try to pickup the matches the game will change the cursor to "Wait" and will never change back.

If you reload the save and dodge then enter the bar and pickup the matches it works fine.

To reproduce enter the bar with the hand icon before the motorcycle gets to you (got to click quickly!).

Attachments (2)

BarStuck.png (33.8 KB ) - added by dafioram 7 years ago.
Picture of being Stuck
sq4-cd-win-2.023 (21.4 KB ) - added by dafioram 7 years ago.
Save File before entering bar

Download all attachments as: .zip

Change History (11)

by dafioram, 7 years ago

Attachment: BarStuck.png added

Picture of being Stuck

by dafioram, 7 years ago

Attachment: sq4-cd-win-2.023 added

Save File before entering bar

comment:1 by m-kiewitz, 7 years ago

How did you get there without dodging a bike?

comment:2 by dafioram, 7 years ago

So when you get to the planet you enter the bar talk to the bikers then leave. Then you go out a kick over there motorcycles.

You then go hide and the bikers bike around. You then come out of hiding go one screen to the right and then you are infront of one of the bikers and the bar, if you click with the hand on the bar then you will go right in, alternatively, you can wait till he is right on you and walk away to dodge him then enter the bar. I did the former and so I evaded him before he got to me.

You can also go north, then east, then south, and west to get to the bar and during that period you will dodge 2 motorcyclers and you can then pick up the matches no problem. This alternate 3rd route doesn't have a biker in front of the bar, but it does have you dodge 2 others.

comment:3 by dafioram, 7 years ago

Once roger has knocked over the bikes, gone to his ship, and if you decided to go directly to the bar one of 3 things can happen:

  1. You enter the bar using the walk or grab icon. In this case you don't get points for dodging the bike, but you are able to pick up the matches, so no bug. The bike appearing or not is a random event.
  1. You use the walk or grab icon to head to the bar and the biker appears and the game pauses you with the biker headed straight for you. If you walk left or right you will roll and get points and points sound for dodging and if you rolled far enough away then you will also not die. You can then enter the bar using the walk or hand icon and no bug.
  1. The bug case. Similar setup to 2. You use the walk or grab icon to head to the bar entrance and the biker appears and the game pauses you with the biker headed straight for you. If you click on the bar entrance with the grab icon you will walk straight into the bar and not do a roll. You will also not get any points for avoiding this. In this case if you pick up the matches the game will show the waiting message and not release it. The game is now stuck.

When the biker is headed at roger if he dodges left or right using the walk icon then the theDodgeR::handleEvent of script 706 will be called. However, if the hand icon is used on the bar door instead then the theDodgeR::handleEvent function will not be called. It seems the hand entering the bar bypasses the other checks.

If I enter the bar without running into any bikers (case 1) versus entering the bar after seeing a biker, but using the hand icon (case 3) then the global variables are different. This difference is shown here: https://gist.github.com/dafioram/d7b0e0e977934836b35f1d6ac63db49f/revisions

I ran the bug state again and looked at the globals again and all the ones that were different are now different from those cases except globals 196, 197, 198, i.e., Those have the same values in the bug state as I gave in the gist (for the bad = bugged state). Setting these 3 global values in the bug state to be the same as the non-bug state values before trying to grab the matches does not prevent the bug. In fact these globals get changed back to those values.

The biker approaching outside the bar happens in script 610 and the matches bug happens in the bar in script 615.

In the bug state if you click on the matches with the hand icon getMatches::changeState of script 615 will be called 3 times while in the bug free case that function will be called 4 times. After the third call the waiting message will be displayed and after the fourth call it will go away freeing up roger (in the no bug case).

comment:4 by Vhati, 6 years ago

In the bugged case (dodge-less bar entry via HAND), ego only faces east while in the bar, no matter which direction he walks. Ego's 'heading' property varies. The sprite does not update to match.


script 615 - getMatches::changeState()

(0
	(proc0_2)
	# Test plot flag 75 (set when kicking the bikes).
	(if (proc0_6 75)
		(g0_ego setMotion: PolyPath 89 181 self)
	else
		(g0_ego setMotion: PolyPath 102 174 self)
	)
)
(1
	# Test flag 75 (bikes kicked).
	(if (proc0_6 75)
		(g0_ego setHeading: 180 self)
	else
		(= local1 1)
		(g89_Sq4GlobalNarrator say: 2)
		(g2_myCurrentRoom setScript: lookMonoGuys)
		(self dispose:)
	)
)
(2
	(proc0_11 76 5)
	(g0_ego get: 13)
	(theMatches dispose:)
	(g89_Sq4GlobalNarrator say: 3)
	(proc0_1 (g0_ego loop?) 0)
	(proc0_3)
	(self dispose:)
)

State 0 runs the without incident.
State 1 calls Actor::setHeading(180). As soon as that method returns, heading instantly becomes 180.
State 2 is never reached.

Normally, time would pass and ego's looper (stopGroop) would somehow get its doit() called (Grooper in script 977), which would sync up the sprite with ego's heading, then cue getMatches to advance. That isn't happening.


I did find a workaround...

  • Restore the attached saved game.
  • Click HAND on the bar for buggy entry.
  • WALK out of the bar. Walking will be fixed.
    • Bug: You may get run over by an invisible biker if you wander around that area.
  • Re-enter the bar with either WALK or HAND.
  • Click HAND on the matches. It won't stall.
Last edited 6 years ago by Vhati (previous) (diff)

comment:5 by Vhati, 6 years ago

Simple solution would be to remove HAND handling in script 610's door::doVerb(4).

I have a patch that turns the case bnt into an unconditional jmp to skip. Since the 'door' is just a hole in the building, players are more likely to use WALK anyway.

Doesn't explain how this bug works though.

comment:6 by Vhati, 6 years ago

HAND invokes door::doverb(4), which schedules enterBar.

WALK is handled by rm610::doit() inRect() tests, one of which schedules enterBar. The debugger can cause the bug by directly repositioning ego onto the bar's entrance as the biker approaches, interrupting theDodgeR without a WALK event, just as HAND would. Ego only faces east. Getting the matches will stall.

  • send ego posn 192 135



Ah! Grooper::doit() is aborting its duties because when View::setLoop() is given a numeric value, it sets a noTurn bit (0x800) on ego's 'signal' property.


When the biker is approaching, theDodgeR is idling after state 1, awaiting a WALK event to advance, via theDodgeR::handleEvent().

theDodgeR::changeState(1)

(1
	(proc0_3)
	(g0_ego view: 635 setLoop: 0 setCel: 0)
)

There's setLoop(0).


script 998 - Actor::setLoop()

(method (setLoop param1 &tmp temp0)
	(if
		(= temp0
			(cond 
				((== argc 0) (super setLoop:) 0)
				((not (IsObject param1)) (super setLoop: param1 &rest) 0)
				((& (param1 -info-?) $8000) (param1 new:))
				(else param1)
			)
		)
		(if looper (looper dispose:))
		((= looper temp0) init: self &rest)
	)
)

Numbers are handled by the superclass...


script 998 - View::setLoop()

(method (setLoop param1)
	(cond 
		((== argc 0) (= signal (| signal noTurn)))
		((== param1 -1) (= signal (& signal $f7ff)))
		(else (= loop param1) (= signal (| signal noTurn)))
	)
	(self forceUpd:)
)

Where ego gets the noTurn (0x800) bit set.


Once inside the bar, ego's 'signal' is normally 24642; 26690 when bugged. That bit is the difference. The following fixes the sprite and makes the matches gettable.

  • send ego 24642



If theDodgeR were allowed to advance...

script 706 - theDodgeR::changeState(3)

(3
	(proc0_11 77 5)
	(proc0_1 (g0_ego loop?) 0)
	(g0_ego
		posn: (+ (g0_ego x?) 24) (+ (g0_ego y?) 5)
		heading: 90 self
	)
)

proc0_11() scores points.
proc0_1() normalizes ego... and sets a given loop and view.


script 0 - proc0_1()

(procedure (proc0_1 param1 param2 param3 &tmp temp0)
	(= temp0 0)
	(if (> argc 0)
		(g0_ego loop: param1)
		(if (> argc 1)
			(g0_ego view: param2)
			(if (> argc 2) (= temp0 param3))
		)
	)
	(if (not temp0) (= temp0 4))
	(g0_ego
		normal: 1
		moveHead: 1
		setLoop: -1
		setLoop: stopGroop
		setPri: -1
		setMotion: 0
		setCycle: StopWalk temp0
		setStep: 3 2
		illegalBits: 0
		ignoreActors: 0
		setSpeed: global199
	)
)

setLoop(-1) resets ego's 'signal' bits.
Ego can turn. The matches are gettable.

The next room does some bit twiddling of its own, but doesn't completely reset the 'signal' property, so when bugged, the noTurn bit persists.

Last edited 6 years ago by Vhati (previous) (diff)

comment:7 by sluicebox, 6 years ago

Keywords: original has-pull-request added
Owner: set to sluicebox

Script patches for CD version: https://github.com/scummvm/scummvm/pull/1553/

Great sleuthing everybody! I count three bugs, and in the process found two unrelated ones.

All three let you to break out of the dodge sequence and leave the room before it's complete, resulting in a broken state. The matches are one symptom, you'll also die if you leave the bar and go north. The fix is to prevent breaking out of the dodge sequence before it completes with "Boy was that close!" or death.

BUGS:

  1. Clicking Do on the bar door doesn't check to see if you're in the dodge sequence. It should be testing ulence:egoBusy before running enterBar, so I added that. That's what happens when you click Do on the timepod.
  1. Dodging quickly before the biker reaches his first checkpoint causes the player to prematurely regain control, allowing walking off to another screen or in to the bar. Each biker script in each room has an unnecessary handsOn call that causes this. Removing these fixes that. Fortunately it's always the same byte sequence so one patch can be applied to them all. Affects all rooms.
  1. Clicking a verb (Do, Look, etc) on something that ego wasn't facing prior to crouching will change ego's heading and view, breaking ego out of crouch mode and allowing walking away without dodging. The first verb event doesn't even reach its target. For example, with the submitted save game, clicking Smell on the force field generator makes ego stand up with no message. Setting ego:looper to 0 when crouching prevents this, and looper is automatically restored after dodging. Affects all rooms.

These script bugs exist in the floppy versions but in those the bikers move so fast that there's no time to exploit them, which explains how they went unnoticed during the game's initial round of QA. They later slowed down the bikers in the CD version and that exposed these bugs.

It should now be impossible to walk away from the dodge sequence in any room.

Regarding the attached save game: the first two bugs will be fixed immediately, but the third won't until ego crouches a second time since ego is already crouched with a looper set. Clicking Hand on the door will still make ego stand up. Next crouch will be escape-proof.

comment:8 by Filippos Karapetis <bluegr@…>, 6 years ago

In 5559702:

SCI: Fix SQ4CD bike dodging, bug #9806

Fixes several game-breaking script bugs in the bike dodging scenes
that were exposed in the CD version by slowing down the bikes.

comment:9 by bluegr, 6 years ago

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