[Scummvm-git-logs] scummvm master -> 82ba094179be728991416bde5ab21475e8483d76
bluegr
noreply at scummvm.org
Fri Mar 13 07:11:00 UTC 2026
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
82ba094179 NANCY: Implement the Nancy 8 Squid Toss (angletoss) puzzle
Commit: 82ba094179be728991416bde5ab21475e8483d76
https://github.com/scummvm/scummvm/commit/82ba094179be728991416bde5ab21475e8483d76
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-03-13T09:10:04+02:00
Commit Message:
NANCY: Implement the Nancy 8 Squid Toss (angletoss) puzzle
Changed paths:
engines/nancy/action/puzzle/angletosspuzzle.cpp
engines/nancy/action/puzzle/angletosspuzzle.h
diff --git a/engines/nancy/action/puzzle/angletosspuzzle.cpp b/engines/nancy/action/puzzle/angletosspuzzle.cpp
index a71e75d5f4b..c22d17188d2 100644
--- a/engines/nancy/action/puzzle/angletosspuzzle.cpp
+++ b/engines/nancy/action/puzzle/angletosspuzzle.cpp
@@ -40,59 +40,213 @@ void AngleTossPuzzle::init() {
setVisible(true);
moveTo(screenBounds);
- // TODO
+ g_nancy->_resource->loadImage(_imageName, _image);
+ _image.setTransparentColor(_drawSurface.getTransparentColor());
+
+ // Draw the initial angle and power indicators.
+ // The throw button sprite is NOT drawn here â the static background already shows the
+ // idle button state. The sprite (data+0x4d) is the launched/pressed overlay, drawn
+ // only when LAUNCH is clicked (mirroring the DAT_0059bfbd conditional in FUN_0044b1fa).
+ _drawSurface.blitFrom(_image, _angleSprites[_curAngle], _angleDisplay);
+ _drawSurface.blitFrom(_image, _powerSprites[_curPower], _powerDisplay);
+}
+
+void AngleTossPuzzle::readData(Common::SeekableReadStream &stream) {
+ readFilename(stream, _imageName);
+
+ // data+0x21..0x2c: 6 Ã uint16.
+ // _initialPower/_initialAngle: starting player position (copied to object+0x24/0x26 in original).
+ // _numPowers/_numAngles: UI control bounds.
+ // _targetPower/_targetAngle: the correct answer for this round (compared in FUN_0044a6be).
+ _initialPower = stream.readUint16LE();
+ _initialAngle = stream.readUint16LE();
+ _numPowers = stream.readUint16LE();
+ _numAngles = stream.readUint16LE();
+ _targetPower = stream.readUint16LE();
+ _targetAngle = stream.readUint16LE();
+
+ // 22 rects â see header for full mapping.
+ readRect(stream, _throwHotspot); // Rect 0 â data+0x2d
+ readRect(stream, _throwDisplay); // Rect 1 â data+0x3d
+ readRect(stream, _throwSprite); // Rect 2 â data+0x4d
+ readRect(stream, _aimLeftHotspot); // Rect 3 â data+0x5d
+ readRect(stream, _aimRightHotspot); // Rect 4 â data+0x6d
+ readRect(stream, _angleDisplay); // Rect 5 â data+0x7d
+ readRectArray(stream, _angleSprites, 5); // Rects 6-10 â data+0x8d
+ readRect(stream, _powerDisplay); // Rect 11 â data+0xdd
+ readRectArray(stream, _powerHotspots, 5); // Rects 12-16 â data+0xed
+ readRectArray(stream, _powerSprites, 5); // Rects 17-21 â data+0x13d
+
+ _powerSound.readNormal(stream); // data+0x18d
+ _squeakSound.readNormal(stream); // data+0x1be
+ _chainSound.readNormal(stream); // data+0x1ef
+
+ _throwSquidScene.readData(stream); // data+0x220, ends at data+0x238
+
+ // HACK: We've skipped some of the flag data at this point,
+ // so go back to read them correctly
+ _throwSquidScene._flag.label = kEvNoEvent;
+ _throwSquidScene._flag.flag = 0;
+ stream.seek(-3, SEEK_CUR);
+
+ _powerTooStrongFlag = stream.readSint16LE();
+ _powerTooWeakFlag = stream.readSint16LE();
+ _angleTooLeftFlag = stream.readSint16LE();
+ _angleTooRightFlag = stream.readSint16LE();
+ _winFlag = stream.readSint16LE();
+
+ _exitScene.readData(stream); // data+0x240
+ readRect(stream, _exitHotspot); // data+0x259
}
void AngleTossPuzzle::execute() {
- if (_state == kBegin) {
+ switch (_state) {
+ case kBegin:
+ // Restore the player selection to the starting position for this round.
+ _curPower = _initialPower;
+ _curAngle = _initialAngle;
+
init();
registerGraphics();
+
+ g_nancy->_sound->loadSound(_powerSound);
+ g_nancy->_sound->loadSound(_squeakSound);
+ g_nancy->_sound->loadSound(_chainSound);
+
+ // FUN_0044a526: clear all result/hint flags before the player starts.
+ NancySceneState.setEventFlag(_powerTooStrongFlag, g_nancy->_false);
+ NancySceneState.setEventFlag(_powerTooWeakFlag, g_nancy->_false);
+ NancySceneState.setEventFlag(_angleTooLeftFlag, g_nancy->_false);
+ NancySceneState.setEventFlag(_angleTooRightFlag, g_nancy->_false);
+ NancySceneState.setEventFlag(_winFlag, g_nancy->_false);
+
_state = kRun;
+ break;
+
+ case kRun:
+ // Wait for the chain sound to finish, then evaluate the throw (FUN_0044a6be)
+ // and always transition to _throwSquidScene so the animation plays.
+ if (_isThrown && !g_nancy->_sound->isSoundPlaying(_chainSound)) {
+ _isThrown = false;
+
+ // FUN_0044a6be: set exactly one result flag based on how accurate the throw was.
+ // The animation scene reads these flags to decide what to show.
+ if (_curPower == _targetPower && _curAngle == _targetAngle) {
+ // Exact match of power and angle â player wins round!
+ NancySceneState.setEventFlag(_winFlag, g_nancy->_true);
+ } else if (_curPower > _targetPower) {
+ // Power too strong
+ NancySceneState.setEventFlag(_powerTooStrongFlag, g_nancy->_true);
+ } else if (_curPower < _targetPower) {
+ // Power too weak
+ NancySceneState.setEventFlag(_powerTooWeakFlag, g_nancy->_true);
+ } else if (_curAngle > _targetAngle) {
+ // Angle too far right
+ NancySceneState.setEventFlag(_angleTooRightFlag, g_nancy->_true);
+ } else if (_curAngle < _targetAngle) {
+ // Angle too far left
+ NancySceneState.setEventFlag(_angleTooLeftFlag, g_nancy->_true);
+ }
+
+ _state = kActionTrigger;
+ }
+ break;
+
+ case kActionTrigger:
+ g_nancy->_sound->stopSound(_powerSound);
+ g_nancy->_sound->stopSound(_squeakSound);
+ g_nancy->_sound->stopSound(_chainSound);
+
+ if (_exitPressed) {
+ _exitScene.execute();
+ } else {
+ _throwSquidScene.execute();
+ }
+
+ finishExecution();
+ break;
}
-
- // TODO
- // Stub - return to the winning screen
- warning("STUB - Nancy 8 Squid Toss game");
- SceneChangeDescription scene;
- scene.sceneID = 4465;
- NancySceneState.resetStateToInit();
- NancySceneState.changeScene(scene);
}
-void AngleTossPuzzle::readData(Common::SeekableReadStream &stream) {
- Common::Path tmp;
- readFilename(stream, tmp);
- stream.skip(12); // TODO
-
- for (uint i = 0; i < 22; ++i) {
- Common::Rect r;
- readRect(stream, r);
-
- /*
- Common::String desc = Common::String::format("AngleTossPuzzle rect %d", i);
- debug("%s %d, %d, %d, %d", desc.c_str(), r.left, r.top, r.right, r.bottom);
-
- Graphics::Surface *s = g_system->lockScreen();
- s->fillRect(r, 255);
- g_system->unlockScreen();
- g_system->updateScreen();
- g_system->delayMillis(1000);
- */
+void AngleTossPuzzle::handleInput(NancyInput &input) {
+ if (_state != kRun || _isThrown) {
+ return;
}
- _powerSound.readNormal(stream);
- _squeakSound.readNormal(stream);
- _chainSound.readNormal(stream);
+ // All rects are in viewport-local coordinates.
+ Common::Point localMousePos = input.mousePos;
+ Common::Rect vpPos = NancySceneState.getViewport().getScreenPosition();
+ localMousePos -= Common::Point(vpPos.left, vpPos.top);
- _throwSquidScene.readData(stream);
- stream.skip(7); // TODO
- _exitScene.readData(stream);
+ // Exit button
+ if (_exitHotspot.contains(localMousePos)) {
+ g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
- stream.skip(16); // TODO
-}
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _exitPressed = true;
+ _state = kActionTrigger;
+ }
+ return;
+ }
-void AngleTossPuzzle::handleInput(NancyInput &input) {
- // TODO
+ // Aim left arrow â always show hotspot cursor; only act when not already at min
+ if (_aimLeftHotspot.contains(localMousePos)) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+
+ if (_curAngle > 0 && (input.input & NancyInput::kLeftMouseButtonUp)) {
+ --_curAngle;
+ _drawSurface.fillRect(_angleDisplay, _drawSurface.getTransparentColor());
+ _drawSurface.blitFrom(_image, _angleSprites[_curAngle], _angleDisplay);
+ g_nancy->_sound->playSound(_squeakSound);
+ _needsRedraw = true;
+ }
+ return;
+ }
+
+ // Aim right arrow â always show hotspot cursor; only act when not already at max
+ if (_aimRightHotspot.contains(localMousePos)) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+
+ if (_curAngle + 1 < _numAngles && (input.input & NancyInput::kLeftMouseButtonUp)) {
+ ++_curAngle;
+ _drawSurface.fillRect(_angleDisplay, _drawSurface.getTransparentColor());
+ _drawSurface.blitFrom(_image, _angleSprites[_curAngle], _angleDisplay);
+ g_nancy->_sound->playSound(_squeakSound);
+ _needsRedraw = true;
+ }
+ return;
+ }
+
+ // Power-level buttons â direct selection (Whale = 0, Dolphin = 1, Trout = 2, Shrimp = 3, Fish Fry = 4)
+ for (uint i = 0; i < _powerHotspots.size(); ++i) {
+ if (_powerHotspots[i].contains(localMousePos)) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+
+ if ((input.input & NancyInput::kLeftMouseButtonUp) && _curPower != i) {
+ _curPower = i;
+ _drawSurface.fillRect(_powerDisplay, _drawSurface.getTransparentColor());
+ _drawSurface.blitFrom(_image, _powerSprites[_curPower], _powerDisplay);
+ g_nancy->_sound->playSound(_powerSound);
+ _needsRedraw = true;
+ }
+ return;
+ }
+ }
+
+ // LAUNCH button â hotspot is rect 0 (_throwHotspot), sprite is drawn at rect 1 (_throwDisplay)
+ if (_throwHotspot.contains(localMousePos)) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ g_nancy->_sound->playSound(_chainSound);
+ _isThrown = true;
+
+ // Show the launched/pressed overlay sprite (DAT_0059bfbd != 0 path in FUN_0044b1fa).
+ _drawSurface.blitFrom(_image, _throwSprite, _throwDisplay);
+ _needsRedraw = true;
+ }
+ return;
+ }
}
} // End of namespace Action
diff --git a/engines/nancy/action/puzzle/angletosspuzzle.h b/engines/nancy/action/puzzle/angletosspuzzle.h
index b669da5a697..43b550f8bdc 100644
--- a/engines/nancy/action/puzzle/angletosspuzzle.h
+++ b/engines/nancy/action/puzzle/angletosspuzzle.h
@@ -27,7 +27,21 @@
namespace Nancy {
namespace Action {
-// Squid Toss mini-game in Nancy 8
+// Squid toss mini-game in Nancy 8.
+//
+// UI layout:
+// Left panel - 5 direct-select power buttons (Whale / Dolphin / Trout / Shrimp / Fish Fry).
+// Right panel - Ship's wheel with two arrow buttons (aim left / aim right), 5 angle positions.
+// Top-right - "LAUNCH" button that fires the fish.
+// Top-left - "MENU" button (handled as exit hotspot).
+//
+// Each AR instance represents one round of the puzzle. The player adjusts angle and power
+// then clicks LAUNCH. If the selection matches the AR's _targetPower/_targetAngle, the win
+// flag is set and the game transitions to _throwSquidScene to play the throw animation.
+// Wrong throws transition to _throwSquidScene, showing the appropriate failure animation,
+// based on which of the four fail flags is set (too strong/weak, too left/right). After the
+// separate AR instances (each with their own target) implement the 3-round mechanic.
+// FUN_0044a526 and FUN_0044a6be handle flag clearing and result evaluation in the original.
class AngleTossPuzzle : public RenderActionRecord {
public:
@@ -44,12 +58,72 @@ protected:
Common::String getRecordTypeName() const override { return "AngleTossPuzzle"; }
bool isViewportRelative() const override { return true; }
- SoundDescription _powerSound;
- SoundDescription _squeakSound;
- SoundDescription _chainSound;
+ Common::Path _imageName;
+
+ // data+0x21..0x2c: 6 Ã uint16.
+ // _initialPower/_initialAngle: starting player selection (copied to object+0x24/0x26 in original).
+ // _numPowers/_numAngles: UI control bounds (always 5 in practice).
+ // _targetPower/_targetAngle: the correct answer for this AR instance (compared in FUN_0044a6be).
+ uint16 _initialPower = 0;
+ uint16 _initialAngle = 0;
+ uint16 _numPowers = 0;
+ uint16 _numAngles = 0;
+ uint16 _targetPower = 0;
+ uint16 _targetAngle = 0;
+
+ // The 22 rects read from the data file, in stream order.
+ // Rect-to-data-offset mapping confirmed from the render callback (FUN_0044b1fa).
+ //
+ // Sprite rects (_fooSprite) are source areas within the loaded image.
+ // Display rects (_fooDisplay) are destination areas on the viewport overlay.
+ // Hotspot rects (_fooHotspot) are clickable areas in viewport-local coordinates.
+ //
+ // Rect 0 â data+0x2d _throwHotspot (clickable area for the throw button)
+ // Rect 1 â data+0x3d _throwDisplay (where throw button sprite is drawn)
+ // Rect 2 â data+0x4d _throwSprite (throw button source in image)
+ // Rect 3 â data+0x5d _aimDecHotspot (aim-left arrow)
+ // Rect 4 â data+0x6d _aimIncHotspot (aim-right arrow)
+ // Rect 5 â data+0x7d _angleDisplay (single screen position for angle indicator)
+ // Rects 6-10 â data+0x8d..0xcd _angleSprites[5] (5 angle images in sprite sheet)
+ // Rect 11 â data+0xdd _powerDisplay (single screen position for power indicator)
+ // Rects 12-16 â data+0xed..0x12d _powerHotspots[5] (power-select click areas)
+ // Rects 17-21 â data+0x13d..0x17d _powerSprites[5] (5 power images in sprite sheet)
+
+ Common::Rect _throwHotspot; // Rect 0 â clickable area for LAUNCH
+ Common::Rect _throwDisplay; // Rect 1 â sprite dest on screen
+ Common::Rect _throwSprite; // Rect 2 â source in image
+ Common::Rect _aimLeftHotspot; // Rect 3
+ Common::Rect _aimRightHotspot; // Rect 4
+ Common::Rect _angleDisplay; // Rect 5 â single dest on screen
+ Common::Array<Common::Rect> _angleSprites; // Rects 6-10 â 5 source frames in image
+ Common::Rect _powerDisplay; // Rect 11 â single dest on screen
+ Common::Array<Common::Rect> _powerHotspots; // Rects 12-16 â click areas
+ Common::Array<Common::Rect> _powerSprites; // Rects 17-21 â 5 source frames in image
+
+ SoundDescription _powerSound; // Played when changing power level
+ SoundDescription _squeakSound; // Played when changing aim angle
+ SoundDescription _chainSound; // Played when LAUNCH is pressed
+
+ SceneChangeWithFlag _throwSquidScene; // Triggered on throw (FUN_0044a6be result)
+
+ int16 _powerTooStrongFlag = -1; // 0x236
+ int16 _powerTooWeakFlag = -1; // 0x238
+
+ int16 _angleTooLeftFlag = -1; // 0x23a
+ int16 _angleTooRightFlag = -1; // 0x23c
+
+ int16 _winFlag = -1; // 0x23e
- SceneChangeWithFlag _throwSquidScene;
SceneChangeWithFlag _exitScene;
+ Common::Rect _exitHotspot;
+
+ Graphics::ManagedSurface _image;
+
+ uint16 _curPower = 0;
+ uint16 _curAngle = 0;
+
+ bool _isThrown = false; // True while the chain sound plays after LAUNCH is pressed
+ bool _exitPressed = false;
};
} // End of namespace Action
More information about the Scummvm-git-logs
mailing list