[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