[Scummvm-git-logs] scummvm master -> 50e2e4bfbbb20b0ceab62b4c184afdd8eb2adce8

bluegr noreply at scummvm.org
Fri Aug 7 23:18:06 UTC 2026


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
22d485345f NANCY: NANCY12: Check against the effect value for special golf holes
ef2bbea1fe NANCY: NANCY12: Cleanup TwoDialPuzzle
6412809e18 NANCY: NANCY12: Add movement scale to MinigolfPuzzle
50e2e4bfbb NANCY: NANCY12: Fixes for the 3 different chase scene states


Commit: 22d485345fe3206fe844da2802d6cdd91b8ca1b9
    https://github.com/scummvm/scummvm/commit/22d485345fe3206fe844da2802d6cdd91b8ca1b9
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-08T02:17:53+03:00

Commit Message:
NANCY: NANCY12: Check against the effect value for special golf holes

Fixes the hole next to the golden golf ball

Changed paths:
    engines/nancy/action/puzzle/minigolfpuzzle.cpp
    engines/nancy/action/puzzle/minigolfpuzzle.h


diff --git a/engines/nancy/action/puzzle/minigolfpuzzle.cpp b/engines/nancy/action/puzzle/minigolfpuzzle.cpp
index 8eac66e4088..85d2b0ca5e3 100644
--- a/engines/nancy/action/puzzle/minigolfpuzzle.cpp
+++ b/engines/nancy/action/puzzle/minigolfpuzzle.cpp
@@ -115,25 +115,15 @@ void MinigolfPuzzle::init() {
 		_maskCenterY = _boundaryMask.h / 2.0;
 	}
 
-	// The cups are the zones that play the sink sounds (GOL_Sink*), in mask/course
-	// space. A hole can have several (each with its own target scene/flag in its
-	// special effect + tail). A separate zone over each cup carries Nancy's reaction
-	// voice line. The scene/flag/fade are resolved from the cup the ball drops in.
+	// The cups are the zones carrying a target scene (their special effect id), in
+	// mask/course space. A hole can have several, each with its own scene/flag/sound
+	// (e.g. hole 6a's special "shot sky high" cup plays Ball_Short, not GOL_Sink). A
+	// separate zone over each cup carries Nancy's reaction voice line.
 	_inSlope.resize(_zones.size(), false);
 	for (uint i = 0; i < _zones.size(); ++i) {
 		const ActionZone &z = _zones[i];
-		bool isSink = false;
-		for (const Common::String &n : z._sound.names) {
-			if (n.contains("Sink") || n.contains("sink")) {
-				isSink = true;
-				break;
-			}
-		}
-		if (isSink) {
+		if (z.specialEffectId >= 1000 && !z.rect.isEmpty()) {
 			_sinkZones.push_back(i);
-			if (_sinkSound.names.empty()) {
-				_sinkSound = z._sound;
-			}
 		} else if (_reactionSound.names.empty() && !z._sound.names.empty()) {
 			_reactionSound = z._sound;
 		}
@@ -620,7 +610,7 @@ void MinigolfPuzzle::updateBall() {
 			_mgState = kSunk;
 			_solved = true;
 			_sunkTime = now;
-			playSoundBlock(_sinkSound);
+			playSoundBlock(cup._sound);
 
 			_winScene.sceneID = cup.specialEffectId;
 			if (cup.type == kZoneSceneChange && cup.tailId != -1) {
diff --git a/engines/nancy/action/puzzle/minigolfpuzzle.h b/engines/nancy/action/puzzle/minigolfpuzzle.h
index 8a5da664f51..85815382d96 100644
--- a/engines/nancy/action/puzzle/minigolfpuzzle.h
+++ b/engines/nancy/action/puzzle/minigolfpuzzle.h
@@ -101,7 +101,6 @@ protected:
 	Common::Array<bool> _inSlope;		// per-zone: ball currently inside a slope zone (for enter/leave kicks)
 	Common::Array<uint> _sinkZones;		// derived: indices of the cup (sink) zones
 	int _sunkZone = -1;					// zone index of the cup the ball dropped into
-	RandomSoundBlock _sinkSound;		// derived: the hole zone's sound
 	RandomSoundBlock _reactionSound;	// derived: Nancy's voice line when the ball rolls over the cup
 
 	// Runtime state


Commit: ef2bbea1fe6a76839eeb293ce99059cba689d81c
    https://github.com/scummvm/scummvm/commit/ef2bbea1fe6a76839eeb293ce99059cba689d81c
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-08T02:17:54+03:00

Commit Message:
NANCY: NANCY12: Cleanup TwoDialPuzzle

Changed paths:
    engines/nancy/action/puzzle/twodialpuzzle.cpp
    engines/nancy/action/puzzle/twodialpuzzle.h


diff --git a/engines/nancy/action/puzzle/twodialpuzzle.cpp b/engines/nancy/action/puzzle/twodialpuzzle.cpp
index b1746447623..00e7096826c 100644
--- a/engines/nancy/action/puzzle/twodialpuzzle.cpp
+++ b/engines/nancy/action/puzzle/twodialpuzzle.cpp
@@ -135,17 +135,8 @@ void TwoDialPuzzle::execute() {
 	case kRun:
 		if (g_nancy->getGameType() >= kGameTypeNancy12) {
 			runNancy12();
-			break;
-		}
-
-		if (g_nancy->_sound->isSoundPlaying(_rotateSounds[0]) || g_nancy->_sound->isSoundPlaying(_rotateSounds[1])) {
-			return;
-		}
-
-		if ((uint)_currentPositions[0] == _correctPositions[0] && (uint)_currentPositions[1] == _correctPositions[1]) {
-			_state = kActionTrigger;
-			_isSolved = true;
-			_solveSoundDelayTime = g_nancy->getTotalPlayTime() + (_solveSoundDelay * 1000);
+		} else {
+			runPreNancy12();
 		}
 
 		break;
@@ -183,6 +174,18 @@ void TwoDialPuzzle::execute() {
 	}
 }
 
+void TwoDialPuzzle::runPreNancy12() {
+	if (g_nancy->_sound->isSoundPlaying(_rotateSounds[0]) || g_nancy->_sound->isSoundPlaying(_rotateSounds[1])) {
+		return;
+	}
+
+	if ((uint)_currentPositions[0] == _correctPositions[0] && (uint)_currentPositions[1] == _correctPositions[1]) {
+		_state = kActionTrigger;
+		_isSolved = true;
+		_solveSoundDelayTime = g_nancy->getTotalPlayTime() + (_solveSoundDelay * 1000);
+	}
+}
+
 void TwoDialPuzzle::runNancy12() {
 	switch (_solveState) {
 	case kCheckSolutions: {
@@ -209,11 +212,11 @@ void TwoDialPuzzle::runNancy12() {
 			// The dials only just landed on this solution; it counts once they
 			// have rested on it for solveSoundDelay milliseconds
 			_lastMatchedSolution = matched;
-			_solveSoundDelayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay;
+			_dwellEndTime = g_nancy->getTotalPlayTime() + _solveSoundDelay;
 			break;
 		}
 
-		if (g_nancy->getTotalPlayTime() > _solveSoundDelayTime) {
+		if (g_nancy->getTotalPlayTime() > _dwellEndTime) {
 			_solveScene._sceneChange.sceneID = _solutions[matched].sceneID;
 			_isSolved = true;
 			_solveState = kPlaySolveSound;
diff --git a/engines/nancy/action/puzzle/twodialpuzzle.h b/engines/nancy/action/puzzle/twodialpuzzle.h
index 083cc78f95a..ffa6b162d16 100644
--- a/engines/nancy/action/puzzle/twodialpuzzle.h
+++ b/engines/nancy/action/puzzle/twodialpuzzle.h
@@ -45,6 +45,7 @@ public:
 protected:
 	Common::String getRecordTypeName() const override { return "TwoDialPuzzle"; }
 
+	void runPreNancy12();
 	void runNancy12();
 
 	Common::Path _imageName;
@@ -75,6 +76,8 @@ protected:
 	enum SolveState { kCheckSolutions, kPlaySolveSound, kWaitForSounds };
 	SolveState _solveState = kCheckSolutions;
 	int16 _lastMatchedSolution = -1;
+	// The time by which the dials must still rest on _lastMatchedSolution
+	uint32 _dwellEndTime = 0;
 
 	SoundDescription _rotateSounds[2];
 
@@ -90,6 +93,7 @@ protected:
 	int16 _currentPositions[2] = { 0, 0 };
 
 	bool _isSolved = false;
+	// The time at which the solve sound starts, once the puzzle has been solved
 	uint32 _solveSoundDelayTime = 0;
 };
 


Commit: 6412809e18afbcab13a2226894293cc5f838a84f
    https://github.com/scummvm/scummvm/commit/6412809e18afbcab13a2226894293cc5f838a84f
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-08T02:17:55+03:00

Commit Message:
NANCY: NANCY12: Add movement scale to MinigolfPuzzle

Fixes the ball movement velocity

Changed paths:
    engines/nancy/action/puzzle/minigolfpuzzle.cpp


diff --git a/engines/nancy/action/puzzle/minigolfpuzzle.cpp b/engines/nancy/action/puzzle/minigolfpuzzle.cpp
index 85d2b0ca5e3..085f2d5e480 100644
--- a/engines/nancy/action/puzzle/minigolfpuzzle.cpp
+++ b/engines/nancy/action/puzzle/minigolfpuzzle.cpp
@@ -42,17 +42,19 @@ namespace Action {
 //    input is gated to the viewport so that hotspot still works, but it isn't
 //    driven from here.
 
-// Struck speed = clamp(dragLen, maxSpeed) * kPowerScale * maxSpeed, in mask px per
-// fixed step; the ball then decelerates linearly by _decel each step, so travel
-// distance scales with drag^2. The physics advance in fixed 30Hz steps to stay
-// frame-rate independent. At this kPowerScale a full-course shot is a small,
-// precise ~24px drag (the three preview balls guide the aim).
+// The struck "speed" = clamp(dragLen, maxSpeed) * kPowerScale * maxSpeed; the ball
+// then decelerates linearly by _decel each step. But the actual per-step movement
+// is that speed times kMoveScale (the original scales the displacement by
+// _DAT_005350a0/_DAT_005352d0 = 1/50), so travel distance = kMoveScale * speed^2 /
+// (2*_decel) - quadratic in drag, but 50x gentler than the raw speed. A full-course
+// shot is thus a comfortable ~170px drag. The physics advance in fixed 30Hz steps.
 static const double kPowerScale = 0.005;
+static const double kMoveScale = 1.0 / 50.0;	// displacement = speed * kMoveScale per step
 static const double kFixedStep = 1.0 / 30.0;
 static const double kRestSpeed = 0.5;		// stop the ball below this per-step speed
-static const double kSinkSpeed = 50.0;		// ball sinks only if it reaches the cup at or below this speed; faster rolls over
+static const double kSinkSpeed = 130.0;		// ball sinks only if it reaches the cup at or below this speed; faster rolls over
 static const double kRestitution = 0.8;		// wall-bounce energy retained
-static const double kDefaultAimDrag = 24.0;	// default aim-cursor distance from the ball (mask px)
+static const double kDefaultAimDrag = 80.0;	// default aim-cursor distance from the ball (mask px)
 
 // Isometric projection (mask space -> screen): rotate 45 degrees, foreshorten Y by
 // half. cos45 == sin45; the Y component is additionally scaled by kIsoYScale.
@@ -394,9 +396,10 @@ void MinigolfPuzzle::writeStrokeCount() {
 }
 
 bool MinigolfPuzzle::stepBall(double &x, double &y, double &vx, double &vy, bool playSounds) {
-	// One fixed physics step: the velocity is already in mask px per step.
-	double dispX = vx;
-	double dispY = vy;
+	// One fixed physics step. The actual displacement is the ball's speed scaled by
+	// kMoveScale (vx/vy carry the raw speed, which the friction below decays).
+	double dispX = vx * kMoveScale;
+	double dispY = vy * kMoveScale;
 
 	// Walk the displacement in ~1px sub-steps so a fast ball can't tunnel through a
 	// thin wall, reflecting off each axis independently so it can slide along an


Commit: 50e2e4bfbbb20b0ceab62b4c184afdd8eb2adce8
    https://github.com/scummvm/scummvm/commit/50e2e4bfbbb20b0ceab62b4c184afdd8eb2adce8
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-08T02:17:56+03:00

Commit Message:
NANCY: NANCY12: Fixes for the 3 different chase scene states

Now, the DrivingPuzzle chase scene is working correctly in all three
states

Changed paths:
    engines/nancy/action/puzzle/drivingpuzzle.cpp
    engines/nancy/action/puzzle/drivingpuzzle.h


diff --git a/engines/nancy/action/puzzle/drivingpuzzle.cpp b/engines/nancy/action/puzzle/drivingpuzzle.cpp
index 44b20a0b4a2..ee20833dc15 100644
--- a/engines/nancy/action/puzzle/drivingpuzzle.cpp
+++ b/engines/nancy/action/puzzle/drivingpuzzle.cpp
@@ -147,12 +147,15 @@ void DrivingPuzzle::classifyZones(const Common::Array<ActionZone> &zones) {
 			_destinations.push_back(dest);
 			break;
 		}
-		case kZoneEventFlag: {	// checkpoint: sets an event flag once driven over
+		case kZoneEventFlag: {	// checkpoint: sets an event flag when driven into while armed
 			Checkpoint cp;
 			cp.rect = z.rect;
 			cp.flagId = z.tailId;
 			cp.flagValue = z.tailFlag;
-			cp.carInside = cp.rect.contains(spawn);
+			cp.condFlag = z.val49;
+			cp.condValue = z.val4b;
+			bool cond = cp.condFlag == -1 || NancySceneState.getEventFlag(cp.condFlag, cp.condValue);
+			cp.wasActive = cond && cp.rect.contains(spawn);
 			_checkpoints.push_back(cp);
 			break;
 		}
@@ -499,12 +502,11 @@ void DrivingPuzzle::updateChaser() {
 	const double slowSlope = 5.0;
 	_speedCap = dist >= slowRadius ? (double)_forwardSpeed : (double)_forwardSpeed - (slowRadius - dist) * slowSlope;
 
-	// While Nancy is still pursuing Jane (states 0 and 1, before Jane is caught and the
-	// crash sequence on the second path begins), letting the chaser leave the visible map
-	// means the player has lost her. The original only tests this in state 0 because its
-	// state stays there through the whole pursuit; here state advances to 1 as soon as its
-	// gate flag is clear, so the check has to cover both.
-	if (_chaseState < 2) {
+	// Losing Jane off the edge of the map is only a loss during the main pursuit, when the
+	// player must stay right behind her. Once a checkpoint clears the pursuit gate, the chase
+	// enters the shortcut phase: Nancy deliberately lets Jane go and races her to the state
+	// line by another road, so the chaser leaving the view is expected.
+	if (_chaseState == kPursuit) {
 		Common::Point cam = cameraOffset();
 		Common::Rect viewport(0, 0, _drawSurface.w, _drawSurface.h);
 		Common::Rect chaserRect(1, 1);
@@ -522,23 +524,23 @@ void DrivingPuzzle::updateChaser() {
 	// Chase state machine (mirrors the original): flags gating it are set by the chase's
 	// own checkpoints (zones2 type 0x0b) and by the scene scripts.
 	switch (_chaseState) {
-	case 0:
+	case kPursuit:
 		if (_chaseParams[kChaseGate01Flag] != -1 &&
 				NancySceneState.getEventFlag(_chaseParams[kChaseGate01Flag], g_nancy->_false)) {
-			_chaseState = 1;
+			_chaseState = kShortcut;
 		}
 		break;
-	case 1:
+	case kShortcut:
 		// Switch onto the second path once Jane is caught.
 		if (_chaseParams[kChaseGate12Flag] != -1 &&
 				NancySceneState.getEventFlag(_chaseParams[kChaseGate12Flag], g_nancy->_true)) {
 			_chaserOnPathB = true;
 			_chaserWaypoint = 0;
 			_chaseStarted = false;
-			_chaseState = 2;
+			_chaseState = kCaught;
 		}
 		break;
-	case 2:
+	case kCaught:
 		// The chaser has completed its route (Jane crashes) - the win.
 		if (_chaserWaypoint + 1 >= path.size()) {
 			armExitScene(_chaseParams[kChasePathEndScene], -1, 0);
@@ -662,15 +664,17 @@ void DrivingPuzzle::updatePhysics(int throttle, double cursorDist) {
 		hole.carInside = nowInside;
 	}
 
-	// Driving over a checkpoint (on entry) sets its event flag once.
+	// Driving into a checkpoint sets its event flag, but only while the checkpoint's own
+	// condition holds (so the chase phases fire in order). Edge-triggered on entering the
+	// armed state.
 	for (uint i = 0; i < _checkpoints.size(); ++i) {
 		Checkpoint &cp = _checkpoints[i];
-		bool nowInside = cp.rect.contains(next);
-		if (nowInside && !cp.carInside && !cp.triggered && cp.flagId != -1) {
-			cp.triggered = true;
+		bool cond = cp.condFlag == -1 || NancySceneState.getEventFlag(cp.condFlag, cp.condValue);
+		bool active = cond && cp.rect.contains(next);
+		if (active && !cp.wasActive && cp.flagId != -1) {
 			NancySceneState.setEventFlag(cp.flagId, cp.flagValue ? g_nancy->_true : g_nancy->_false);
 		}
-		cp.carInside = nowInside;
+		cp.wasActive = active;
 	}
 
 	// A drive-in destination (the chase finish line) fires on entry; a parking
@@ -698,6 +702,12 @@ void DrivingPuzzle::execute() {
 	case kBegin:
 		init();
 		registerGraphics();
+		if (_variant == kChase && _chaseParams[kChaseGate01Flag] != -1) {
+			// Arm the main pursuit: with the gate flag set the chase begins in state 0 (Nancy
+			// must keep Jane in sight). A checkpoint later clears it to start the shortcut. The
+			// original relies on the scene to set this; do it here so the phase is never skipped.
+			NancySceneState.setEventFlag(_chaseParams[kChaseGate01Flag], g_nancy->_true);
+		}
 		classifyZones(_zones);
 		if (_variant == kChase) {
 			classifyZones(_zones2);
diff --git a/engines/nancy/action/puzzle/drivingpuzzle.h b/engines/nancy/action/puzzle/drivingpuzzle.h
index f944cbc9dac..91b4f33c317 100644
--- a/engines/nancy/action/puzzle/drivingpuzzle.h
+++ b/engines/nancy/action/puzzle/drivingpuzzle.h
@@ -106,20 +106,31 @@ protected:
 	// Index into _chaseParams (167). The five values are the chase's outcome scenes and
 	// the event flags that gate its state machine.
 	enum ChaseParam {
-		kChaseGate01Flag = 0,		// state 0 -> 1 once this flag is clear
+		kChaseGate01Flag = 0,		// clears to advance from kPursuit to kShortcut
 		kChaseOffViewScene = 1,		// scene entered when the chaser leaves the viewport
 		kChaseOffViewFlag = 2,		// flag set (to 1) on the off-viewport outcome
-		kChaseGate12Flag = 3,		// state 1 -> 2 (switch to the second path) once this flag is set
+		kChaseGate12Flag = 3,		// sets to advance from kShortcut to kCaught (second path)
 		kChasePathEndScene = 4		// scene entered when the chaser finishes its route
 	};
 
-	// A checkpoint (type 0x0b): driving over it sets an event flag once.
+	// The chase runs through three phases, driven by checkpoint flags:
+	enum ChaseState {
+		kPursuit = 0,	// Nancy must keep Jane in sight; losing her off-screen is a loss
+		kShortcut = 1,	// Nancy lets Jane go and races her to the state line by another road
+		kCaught = 2		// Jane is caught and plays her crash sequence on the second path
+	};
+
+	// A checkpoint (type 0x0b): driving into it sets an event flag, but only while its own
+	// base condition holds. The chase sequences its phases this way - one checkpoint clears
+	// the pursuit gate (starting the shortcut), a later one (gated on that) sets the caught
+	// flag - so the condition must be honored, not just the rect.
 	struct Checkpoint {
 		Common::Rect rect;
-		int16 flagId = -1;
-		byte flagValue = 0;
-		bool triggered = false;
-		bool carInside = false;		// the car was inside this zone last frame
+		int16 flagId = -1;			// tail: the flag driven over sets
+		byte flagValue = 0;			// tail: the value it sets
+		int16 condFlag = -1;		// base zone val49: flag gating whether it can fire
+		byte condValue = 0;			// base zone val4b: the value that arms it
+		bool wasActive = false;		// (condition held AND car inside) last frame
 	};
 
 	// A mud puddle (type 0x03): slows the car (adds to its velocity decay) while inside.
@@ -299,7 +310,7 @@ protected:
 
 	// Chase (167) state machine: 0 = following the first path, 1 = waiting to switch,
 	// 2 = following the second path.
-	int _chaseState = 0;
+	ChaseState _chaseState = kPursuit;
 	bool _chaserOnPathB = false;
 
 	// Fuel + tire hazards. Fuel is the gas-gauge UI resource (index _frictionIndex),




More information about the Scummvm-git-logs mailing list