[Scummvm-git-logs] scummvm master -> 9f31d256415fb6a667dc4bad037da4c59384ef5d
bluegr
noreply at scummvm.org
Sun Jun 7 05:55:36 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
c1653febe9 NANCY: More work on the gridmappuzzle in Nancy10
c6df72b129 NANCY: Fix incoming call sequence in Nancy10+
9f31d25641 NANCY: Use the per-frame blit array when checking for overlay hotspots
Commit: c1653febe9650952aa4738bae345151b3b77b2c5
https://github.com/scummvm/scummvm/commit/c1653febe9650952aa4738bae345151b3b77b2c5
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-07T08:55:22+03:00
Commit Message:
NANCY: More work on the gridmappuzzle in Nancy10
- Glyph sprites are now drawn correctly
- Glyphs now appear correctly when discovering them
- Glyphs now render correctly when placing them on the map
- Glyphs can only be dropped at specific locations on the map grid
- Letters are rendered correctly when dropping glyphs on the map
Changed paths:
engines/nancy/action/puzzle/gridmappuzzle.cpp
engines/nancy/action/puzzle/gridmappuzzle.h
diff --git a/engines/nancy/action/puzzle/gridmappuzzle.cpp b/engines/nancy/action/puzzle/gridmappuzzle.cpp
index 0fee0e4d43a..21d1ccbc0f4 100644
--- a/engines/nancy/action/puzzle/gridmappuzzle.cpp
+++ b/engines/nancy/action/puzzle/gridmappuzzle.cpp
@@ -78,9 +78,9 @@ void GridMapPuzzle::readData(Common::SeekableReadStream &stream) {
stream.skip(1);
for (int i = 0; i < kMaxItems; ++i)
- _leftHalfIdx[i] = stream.readSint16LE();
+ _letterByMapRow[i] = stream.readSint16LE();
for (int i = 0; i < kMaxItems; ++i)
- _rightHalfIdx[i] = stream.readSint16LE();
+ _letterByMapCol[i] = stream.readSint16LE();
for (int i = 0; i < kMaxItems; ++i)
_resultSlot[i] = stream.readSint16LE();
@@ -131,6 +131,7 @@ void GridMapPuzzle::readData(Common::SeekableReadStream &stream) {
void GridMapPuzzle::initState() {
GridMapPuzzleData *gmd = (GridMapPuzzleData *)NancySceneState.getPuzzleData(GridMapPuzzleData::getTag());
+
if (_retainState && gmd && gmd->itemState.size() >= (uint)_numItems * 6) {
const Common::Array<int16> &state = gmd->itemState;
for (int i = 0; i < (int)_numItems; ++i) {
@@ -141,17 +142,18 @@ void GridMapPuzzle::initState() {
_items[i].itemsRow = state[i * 6 + 4];
_items[i].itemsCol = state[i * 6 + 5];
}
- return;
+ } else {
+ for (int i = 0; i < kMaxItems; ++i)
+ _items[i] = ItemSlot();
}
- for (int i = 0; i < kMaxItems; ++i)
- _items[i] = ItemSlot();
-
- // Items with their autoplace flag set start placed in the items grid
- // (acting as the source/inventory). Other items stay hidden until released
- // by gameplay events outside this puzzle.
+ // Always re-check autoplace flags: items released after the last save
+ // (e.g. discovered by examining a wall between visits) should appear in
+ // the items grid even when restoring a previous state.
const int itemsCols = MAX<int>(1, (int)_itemsCols);
for (int i = 0; i < (int)_numItems; ++i) {
+ if (_items[i].inMap || _items[i].inItems)
+ continue;
if (_autoPlaceFlag[i] != -1 && NancySceneState.getEventFlag(_autoPlaceFlag[i], g_nancy->_true)) {
_items[i].inItems = true;
_items[i].itemsRow = (int16)(i / itemsCols);
@@ -189,6 +191,9 @@ void GridMapPuzzle::init() {
g_nancy->_resource->loadImage(_boardImageName, _boardImage);
_boardImage.setTransparentColor(_drawSurface.getTransparentColor());
+ g_nancy->_resource->loadImage(_cursorImageName, _cursorImage);
+ _cursorImage.setTransparentColor(_drawSurface.getTransparentColor());
+
initState();
_heldItem = -1;
@@ -256,14 +261,16 @@ void GridMapPuzzle::execute() {
}
Common::Rect GridMapPuzzle::mapCellRect(int row, int col) const {
- int x = (int)_mapOriginX + col * ((int)_mapSpacingX + _mapCellW);
- int y = (int)_mapOriginY + row * ((int)_mapSpacingY + _mapCellH);
+ // Stride uses the raw src-rect dimensions (right - left, before readRect's
+ // inclusiveâexclusive +1), to match the original's cell layout.
+ int x = (int)_mapOriginX + col * ((int)_mapSpacingX + _mapCellW - 1);
+ int y = (int)_mapOriginY + row * ((int)_mapSpacingY + _mapCellH - 1);
return Common::Rect(x, y, x + _mapCellW, y + _mapCellH);
}
Common::Rect GridMapPuzzle::itemsCellRect(int row, int col) const {
- int x = (int)_itemsOriginX + col * ((int)_itemsSpacingX + _itemsCellW);
- int y = (int)_itemsOriginY + row * ((int)_itemsSpacingY + _itemsCellH);
+ int x = (int)_itemsOriginX + col * ((int)_itemsSpacingX + _itemsCellW - 1);
+ int y = (int)_itemsOriginY + row * ((int)_itemsSpacingY + _itemsCellH - 1);
return Common::Rect(x, y, x + _itemsCellW, y + _itemsCellH);
}
@@ -309,20 +316,24 @@ int GridMapPuzzle::findItemInItems(int row, int col) const {
return -1;
}
-Common::Rect GridMapPuzzle::resultsCellRect(int row, int col) const {
- int x = (int)_resultsOriginX + col * ((int)_resultsSpacingX + _resultsCellW);
- int y = (int)_resultsOriginY + row * ((int)_resultsSpacingY + _resultsCellH);
- return Common::Rect(x, y, x + _resultsCellW, y + _resultsCellH);
-}
-
-bool GridMapPuzzle::isCorrectMapPlacement(int item, int row, int col) const {
+bool GridMapPuzzle::isValidMapSlot(int row, int col) const {
+ // A map cell is a real placement slot iff at least one solution places
+ // some item there. Empty map cells outside any solution reject drops.
for (int s = 0; s < (int)_numSolutions; ++s) {
- if (_solutionRows[s][item] == (int16)row && _solutionCols[s][item] == (int16)col)
- return true;
+ for (int i = 0; i < (int)_numItems; ++i) {
+ if (_solutionRows[s][i] == (int16)row && _solutionCols[s][i] == (int16)col)
+ return true;
+ }
}
return false;
}
+Common::Rect GridMapPuzzle::resultsCellRect(int row, int col) const {
+ int x = (int)_resultsOriginX + col * ((int)_resultsSpacingX + _resultsCellW - 1);
+ int y = (int)_resultsOriginY + row * ((int)_resultsSpacingY + _resultsCellH - 1);
+ return Common::Rect(x, y, x + _resultsCellW, y + _resultsCellH);
+}
+
void GridMapPuzzle::handleInput(NancyInput &input) {
if (_state != kRun || _subState != kPlaying)
return;
@@ -332,6 +343,7 @@ void GridMapPuzzle::handleInput(NancyInput &input) {
if (_heldItem != -1 && _heldDrawPos != mouseVP) {
_heldDrawPos = mouseVP;
+ _skipHeldDraw = false;
redraw();
}
@@ -359,6 +371,16 @@ void GridMapPuzzle::handleInput(NancyInput &input) {
if (!(input.input & NancyInput::kLeftMouseButtonUp))
return;
+ // Reset the held-draw suppression flag on every click; the swap branch
+ // below sets it back to true when appropriate.
+ _skipHeldDraw = false;
+
+ // Sync the held draw position to the click point. Mouse-move tracking
+ // only runs while something is held, so without this a pickup right
+ // after a plain drop would render the new held glyph at the previous
+ // drop's coordinates for one frame.
+ _heldDrawPos = mouseVP;
+
int existingItem = hitMap ? findItemInMap(row, col) : findItemInItems(iRow, iCol);
if (_heldItem == -1) {
@@ -374,23 +396,27 @@ void GridMapPuzzle::handleInput(NancyInput &input) {
g_nancy->_sound->playSound(_pickupSound);
}
} else {
- // Drops onto the map grid only succeed when the cell is the correct
- // placement for the held item per any solution; otherwise the cell
- // rejects the drop and the item stays held.
- if (hitMap && !isCorrectMapPlacement(_heldItem, row, col))
+ // Drop. Map cells only accept the held item if they are real
+ // placement slots (i.e. used by some solution); items cells always
+ // accept. An occupied cell sends its current occupant back to the
+ // cursor so the player can swap.
+ if (hitMap && !isValidMapSlot(row, col))
return;
if (existingItem != -1) {
- // Items-grid cells allow swap: the existing item becomes the new
- // held one. Map cells are gated by the correctness check above, so
- // they can't be occupied by anything except the held item itself.
- if (hitMap)
- return;
- _items[existingItem].inItems = false;
- _items[_heldItem].inItems = true;
- _items[_heldItem].itemsRow = (int16)iRow;
- _items[_heldItem].itemsCol = (int16)iCol;
+ if (hitMap) {
+ _items[existingItem].inMap = false;
+ _items[_heldItem].inMap = true;
+ _items[_heldItem].mapRow = (int16)row;
+ _items[_heldItem].mapCol = (int16)col;
+ } else {
+ _items[existingItem].inItems = false;
+ _items[_heldItem].inItems = true;
+ _items[_heldItem].itemsRow = (int16)iRow;
+ _items[_heldItem].itemsCol = (int16)iCol;
+ }
_heldItem = existingItem;
+ _skipHeldDraw = true;
if (_pickupSound.name != "NO SOUND") {
g_nancy->_sound->loadSound(_pickupSound);
g_nancy->_sound->playSound(_pickupSound);
@@ -446,6 +472,8 @@ void GridMapPuzzle::redraw() {
for (int i = 0; i < (int)_numItems; ++i) {
const ItemSlot &slot = _items[i];
+ // Small map-glyph atlas is on the board image; large items-grid
+ // sprites and the drag cursor are on the cursor image.
if (slot.inMap && slot.mapRow >= 0 && slot.mapCol >= 0) {
const Common::Rect &src = _mapItemSrcRects[i];
if (!src.isEmpty()) {
@@ -457,14 +485,16 @@ void GridMapPuzzle::redraw() {
const Common::Rect &src = _itemsItemSrcRects[i];
if (!src.isEmpty()) {
Common::Rect dst = itemsCellRect(slot.itemsRow, slot.itemsCol);
- _drawSurface.blitFrom(_boardImage, src, Common::Point(dst.left, dst.top));
+ _drawSurface.blitFrom(_cursorImage, src, Common::Point(dst.left, dst.top));
}
}
}
// Each item placed in the map contributes two letter halves to the results
- // bar (its assigned slot indexes a pair of adjacent cells). When all items
- // are correctly placed the letters spell out the solution sentence.
+ // bar at the item's fixed slot. The letters themselves are looked up from
+ // the placement coordinates: column picks the left half, row the right.
+ // When the right items end up at the right cells the strip spells out
+ // the solution sentence.
const int resultsCols = MAX<int>(1, (int)_resultsCols);
for (int i = 0; i < (int)_numItems; ++i) {
if (!_items[i].inMap)
@@ -475,8 +505,10 @@ void GridMapPuzzle::redraw() {
const int base = slot * 2;
const int row = base / resultsCols;
const int col = base % resultsCols;
- const int li = _leftHalfIdx[i];
- const int ri = _rightHalfIdx[i];
+ const int mapRow = (int)_items[i].mapRow;
+ const int mapCol = (int)_items[i].mapCol;
+ const int li = (mapCol >= 0 && mapCol < kMaxItems) ? _letterByMapCol[mapCol] : -1;
+ const int ri = (mapRow >= 0 && mapRow < kMaxItems) ? _letterByMapRow[mapRow] : -1;
if (li >= 0 && li < kMaxResultRects && !_resultSrcRects[li].isEmpty()) {
Common::Rect dst = resultsCellRect(row, col);
_drawSurface.blitFrom(_boardImage, _resultSrcRects[li], Common::Point(dst.left, dst.top));
@@ -487,14 +519,14 @@ void GridMapPuzzle::redraw() {
}
}
- if (_heldItem >= 0 && _heldItem < (int)_numItems) {
+ if (_heldItem >= 0 && _heldItem < (int)_numItems && !_skipHeldDraw) {
const Common::Rect &src = _itemsItemSrcRects[_heldItem].isEmpty()
? _mapItemSrcRects[_heldItem]
: _itemsItemSrcRects[_heldItem];
if (!src.isEmpty()) {
int x = _heldDrawPos.x - src.width() / 2;
int y = _heldDrawPos.y - src.height() / 2;
- _drawSurface.blitFrom(_boardImage, src, Common::Point(x, y));
+ _drawSurface.blitFrom(_cursorImage, src, Common::Point(x, y));
}
}
diff --git a/engines/nancy/action/puzzle/gridmappuzzle.h b/engines/nancy/action/puzzle/gridmappuzzle.h
index 4630e56513b..0a0b6c53ba1 100644
--- a/engines/nancy/action/puzzle/gridmappuzzle.h
+++ b/engines/nancy/action/puzzle/gridmappuzzle.h
@@ -93,15 +93,18 @@ protected:
Common::Rect _itemsItemSrcRects[kMaxItems];
Common::Rect _resultSrcRects[kMaxResultRects];
- int16 _leftHalfIdx[kMaxItems];
- int16 _rightHalfIdx[kMaxItems];
- int16 _resultSlot[kMaxItems];
+ // Result-letter lookups are indexed by the placed item's MAP coordinates,
+ // not by its item index. _letterByMapCol[mapCol] is rendered as the left
+ // half of the item's letter pair; _letterByMapRow[mapRow] as the right.
+ int16 _letterByMapRow[kMaxItems] = {};
+ int16 _letterByMapCol[kMaxItems] = {};
+ int16 _resultSlot[kMaxItems] = {};
- int16 _autoPlaceFlag[kMaxItems];
+ int16 _autoPlaceFlag[kMaxItems] = {};
uint16 _numSolutions = 0;
- int16 _solutionRows[kMaxSolutions][kMaxItems];
- int16 _solutionCols[kMaxSolutions][kMaxItems];
+ int16 _solutionRows[kMaxSolutions][kMaxItems] = {};
+ int16 _solutionCols[kMaxSolutions][kMaxItems] = {};
SoundDescription _pickupSound;
SoundDescription _placeSound;
@@ -129,9 +132,14 @@ protected:
ItemSlot _items[kMaxItems];
int _heldItem = -1;
Common::Point _heldDrawPos;
+ // Set on a swap (drop onto an occupied cell) so the freshly-placed
+ // glyph isn't covered by the picked-up one drawn at the cursor.
+ // Cleared on the next mouse move.
+ bool _skipHeldDraw = false;
bool _isSolved = false;
Graphics::ManagedSurface _boardImage;
+ Graphics::ManagedSurface _cursorImage; // item sprite atlas (right-side panel)
void initState();
void persistState();
@@ -143,7 +151,7 @@ protected:
bool hitTestItems(const Common::Point &p, int &outRow, int &outCol) const;
int findItemInMap(int row, int col) const;
int findItemInItems(int row, int col) const;
- bool isCorrectMapPlacement(int item, int row, int col) const;
+ bool isValidMapSlot(int row, int col) const;
void checkSolved();
};
Commit: c6df72b129d8daab1c09d348ce98597e36c37792
https://github.com/scummvm/scummvm/commit/c6df72b129d8daab1c09d348ce98597e36c37792
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-07T08:55:24+03:00
Commit Message:
NANCY: Fix incoming call sequence in Nancy10+
Changed paths:
engines/nancy/action/miscrecords.cpp
engines/nancy/ui/cellphonepopup.cpp
engines/nancy/ui/cellphonepopup.h
diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index f3054b3afbe..7ac1e104dcd 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -221,7 +221,6 @@ void ControlUIItems::execute() {
break;
case kUITypeCellphone: {
UI::CellPhonePopup &phone = NancySceneState.getCellPhonePopup();
- phone.open();
if (_startScene != (int16)kNoScene) {
SceneChangeDescription scene;
@@ -230,11 +229,10 @@ void ControlUIItems::execute() {
scene.verticalOffset = 0;
// The destination scene's sound carries the conversation audio.
scene.continueSceneSound = kLoadSceneSound;
-
- // Save the pre-call scene on the popup so AR 128 returns
- // there without touching the global push slot.
- phone.setReturnScene(NancySceneState.getSceneInfo());
- NancySceneState.changeScene(scene);
+ // Phone rings, picks up, and changeScenes into `scene`.
+ phone.startIncomingCall(scene);
+ } else {
+ phone.open();
}
break;
}
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index d9aab409861..dcec5b05f61 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -265,6 +265,21 @@ void CellPhonePopup::open() {
}
}
+void CellPhonePopup::startIncomingCall(const SceneChangeDescription &scene) {
+ // open() resets state, so save the pending scene afterwards. Joining
+ // kPlaceCall hands off to the existing ring / pickup / connect chain;
+ // kLookupContact skips the contact lookup when _hasPendingCallScene
+ // is set, and kConnected uses the stored scene for changeScene.
+ if (!_isVisible) {
+ open();
+ }
+ _pendingCallScene = scene;
+ _hasPendingCallScene = true;
+ _resolvedContact = -1;
+ resetDialPad();
+ enterScreenState(kPlaceCall);
+}
+
void CellPhonePopup::close() {
if (!_isVisible) {
return;
@@ -274,6 +289,9 @@ void CellPhonePopup::close() {
g_nancy->_sound->stopSound(_callSound);
}
+ // Closing the phone while ringing declines the call.
+ _hasPendingCallScene = false;
+
setVisible(false);
if (!_uiclData->header.sounds[1].name.empty()) {
@@ -314,14 +332,19 @@ void CellPhonePopup::updateGraphics() {
break;
case kLookupContact: {
- // Directory-mode calls pre-resolve the contact, so only fall back
- // to dial-buffer lookup when the contact isn't already known.
- if (_resolvedContact == -1) {
- _resolvedContact = findContactByDialBuffer();
+ // Incoming calls already know the destination scene, so the
+ // contact lookup is skipped. Directory-mode outgoing calls
+ // pre-resolve the contact, leaving only dial-buffer lookup.
+ if (!_hasPendingCallScene) {
+ if (_resolvedContact == -1) {
+ _resolvedContact = findContactByDialBuffer();
+ }
+ if (_resolvedContact == -1) {
+ enterScreenState(kInvalidNumber);
+ break;
+ }
}
- if (_resolvedContact == -1) {
- enterScreenState(kInvalidNumber);
- } else if (playSoundIfPresent(_uiclData->pickupSound)) {
+ if (playSoundIfPresent(_uiclData->pickupSound)) {
enterScreenState(kWaitPickup);
} else {
enterScreenState(kConnected);
@@ -340,7 +363,15 @@ void CellPhonePopup::updateGraphics() {
// Trigger the scene change once, then sit in kConnected so the
// connecting sprite stays on screen for the duration of the
// conversation. AR 128 closes the popup when the call ends.
- if (_resolvedContact >= 0 &&
+ // Incoming calls carry their destination in _pendingCallScene;
+ // outgoing calls resolve it from the active contact.
+ if (_hasPendingCallScene) {
+ SceneChangeDescription scene = _pendingCallScene;
+ _hasPendingCallScene = false;
+ setReturnScene(NancySceneState.getSceneInfo());
+ NancySceneState.changeScene(scene);
+ resetDialPad();
+ } else if (_resolvedContact >= 0 &&
_resolvedContact < (int)_contacts.size()) {
triggerContactCallSceneChange((uint)_resolvedContact);
_resolvedContact = -1;
diff --git a/engines/nancy/ui/cellphonepopup.h b/engines/nancy/ui/cellphonepopup.h
index 0536c135a7f..9a2759b502b 100644
--- a/engines/nancy/ui/cellphonepopup.h
+++ b/engines/nancy/ui/cellphonepopup.h
@@ -71,6 +71,12 @@ public:
void setReturnScene(const SceneChangeDescription &scene);
bool consumeReturnScene(SceneChangeDescription &out);
+ // Start an incoming-call sequence: opens the popup, stores the
+ // destination scene, and joins the kPlaceCall state chain so it
+ // rings, picks up, shows the connecting sprite, and changeScenes
+ // into `scene` (AR 128 returns via the setReturnScene slot).
+ void startIncomingCall(const SceneChangeDescription &scene);
+
private:
enum ScreenState : int {
kWelcome = 0,
@@ -219,6 +225,11 @@ private:
bool _noSignal = false;
bool _batteryLow = false;
+ // Incoming-call destination (set by startIncomingCall, consumed by the
+ // kConnected handler once the player has answered).
+ SceneChangeDescription _pendingCallScene;
+ bool _hasPendingCallScene = false;
+
SceneChangeDescription _returnScene;
bool _hasReturnScene = false;
};
Commit: 9f31d256415fb6a667dc4bad037da4c59384ef5d
https://github.com/scummvm/scummvm/commit/9f31d256415fb6a667dc4bad037da4c59384ef5d
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-07T08:55:25+03:00
Commit Message:
NANCY: Use the per-frame blit array when checking for overlay hotspots
This check is now done like the ones before it, to ensure uniformity
Changed paths:
engines/nancy/action/overlay.cpp
diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index f4da96d9787..dcf30656ceb 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -347,7 +347,7 @@ void Overlay::execute() {
} else {
// nancy3 added a per-frame flag for hotspots. This allows the overlay to be clickable
// even without a scene change (useful for setting flags).
- if (_blitDescriptions[i].hasHotspot == kPlayOverlayWithHotspot) {
+ if (_blitDescriptions[blitsForThisFrame[i]].hasHotspot == kPlayOverlayWithHotspot) {
_hotspot = _screenPosition;
_hasHotspot = true;
}
More information about the Scummvm-git-logs
mailing list