[Scummvm-git-logs] scummvm master -> c607797791af1977cd0478c00a3b475130d11727
bluegr
noreply at scummvm.org
Thu Jul 16 09:42:38 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:
c7500b5c51 NANCY: NANCY12: Updated description of the EVNT chunk
46f72b8c4c NANCY: NANCY13: Map the new mouse cursors
8fbc04d3e1 NANCY: NANCY13: Implement changes for ConversationSound and ConversationCel
c607797791 NANCY: NANCY13: Use correct local rect for ConversationPopup
Commit: c7500b5c5198cea03a73a41557fc734e28415a3a
https://github.com/scummvm/scummvm/commit/c7500b5c5198cea03a73a41557fc734e28415a3a
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-16T12:42:26+03:00
Commit Message:
NANCY: NANCY12: Updated description of the EVNT chunk
Changed paths:
engines/nancy/enginedata.h
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 57afd709651..c4fb7797577 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -743,9 +743,8 @@ struct UINB : public EngineData {
Common::Rect tabCaptionDestRect; // on-screen target
};
-// Named-event table. Introduced in Nancy 12. Empty in Secret of the Old Clock
-// (the engine registers several built-in event categories at runtime on top of
-// whatever this chunk provides). Each record is a name followed by an id.
+// Event flags table. Introduced in Nancy 12, and replaces the event flag names
+// that were hardcoded in the executable. Each record is a name followed by an id.
struct EVNT : public EngineData {
EVNT(Common::SeekableReadStream *chunkStream);
Commit: 46f72b8c4c46c0a1f765f8543d26661f082989cf
https://github.com/scummvm/scummvm/commit/46f72b8c4c46c0a1f765f8543d26661f082989cf
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-16T12:42:27+03:00
Commit Message:
NANCY: NANCY13: Map the new mouse cursors
In Nancy13, cursors have been split in two parts (system and inventory
cursors) and have been remapped. Sort the Nancy10-12 cursors according
to the layout in the data files, and map the new nancy13 cursors.
Changed paths:
engines/nancy/cursor.cpp
engines/nancy/cursor.h
diff --git a/engines/nancy/cursor.cpp b/engines/nancy/cursor.cpp
index 891d5609e2d..29af3cc1472 100644
--- a/engines/nancy/cursor.cpp
+++ b/engines/nancy/cursor.cpp
@@ -116,10 +116,11 @@ void CursorManager::init(Common::SeekableReadStream *chunkStream) {
_primaryVideoInitialPos.x = chunkStream->readUint16LE();
_primaryVideoInitialPos.y = chunkStream->readUint16LE();
- if (g_nancy->getGameType() >= kGameTypeNancy13) {
+ // Nancy13+ split the cursor sheet into two images: system cursors in
+ // _uiCursorsSurface, held-item cursors in _invCursorsSurface (applyCursor
+ // picks the surface).
+ if (g_nancy->getGameType() >= kGameTypeNancy13)
g_nancy->_resource->loadImage(uiCursorsImageName, _uiCursorsSurface);
- // TODO: Add handling for split UI + inventory cursors in Nancy13+
- }
g_nancy->_resource->loadImage(inventoryCursorsImageName, _invCursorsSurface);
@@ -163,30 +164,83 @@ uint CursorManager::resolveNancy10CursorID(CursorType type, int16 itemID, bool s
case kNormal: return kNewNormal;
case kHotspot: return kNewHotspot;
case kHotspotTalk: return kNewHotspotTalk;
- case kDragHand: return kNewDragHand;
- case kDropHand: return kNewDropHand;
- case kPuzzleArrow: return kNewPuzzleArrow;
case kNormalArrow: return kNewNormalArrow;
case kHotspotArrow: return kNewHotspotArrow;
case kExit: return kNewExit;
case kMove: return kNewExit;
- case kRotateCW: return kNewRotateCW;
- case kRotateCCW: return kNewRotateCCW;
case kMoveLeft: return kNewMoveLeft;
case kMoveRight: return kNewMoveRight;
case kMoveForward: return kNewMoveForward;
case kMoveBackward: return kNewMoveBackward;
case kMoveUp: return kNewMoveUp;
case kMoveDown: return kNewMoveDown;
- case kRotateLeft: return kNewRotateLeft;
+ case kRotateCW: return kNewRotateCW;
+ case kRotateCCW: return kNewRotateCCW;
case kRotateRight: return kNewRotateRight;
- case kInvertedRotateLeft: return kNewInvertedRotateLeft;
+ case kRotateLeft: return kNewRotateLeft;
case kInvertedRotateRight: return kNewInvertedRotateRight;
+ case kInvertedRotateLeft: return kNewInvertedRotateLeft;
+ case kDragHand: return kNewDragHand;
+ case kPuzzleArrow: return kNewPuzzleArrow;
+ case kDropHand: return kNewDropHand;
default:
return kNewNormal;
}
}
+uint CursorManager::resolveNancy13CursorID(CursorType type, int16 itemID, bool setFromScript) {
+ // Held-item cursors: the item block follows the 45 system-cursor pairs;
+ // each item owns an [idle, hotspot] pair now indexing _invCursorsSurface
+ // (chosen by applyCursor()).
+ if (itemID != -1 && (type == kNormal || type == kHotspot)) {
+ _hasItem = true;
+ const uint itemsOffset = (uint)_numCursorTypes * 2;
+ const uint variant = (type == kHotspot) ? 1 : 0;
+ return itemsOffset + (uint)itemID * 2 + variant;
+ }
+
+ if (setFromScript) {
+ // Scripts store a raw cursor type number T, while the chunk lays
+ // each type out as a [idle, hotspot] pair (slots T*2 and T*2+1).
+ // Script cursors are only ever applied while hovering a hotspot,
+ // so we always pick the hotspot variant.
+ return (uint)type * 2 + 1;
+ }
+
+ // Map the engine's logical CursorType to a Nancy13 system type, then pick
+ // the idle slot (type * 2) or hotspot slot (type * 2 + 1).
+ uint sysType = kNancy13Normal;
+ bool hotspot = false;
+
+ switch (type) {
+ case kNormal: sysType = kNancy13Normal; break;
+ case kHotspot: sysType = kNancy13Normal; hotspot = true; break;
+ case kNormalArrow: sysType = kNancy13Arrow; break;
+ case kHotspotArrow: sysType = kNancy13Arrow; hotspot = true; break;
+ case kMove:
+ case kExit: sysType = kNancy13Exit; break;
+ case kMoveForward: sysType = kNancy13MoveForward; break;
+ case kMoveBackward: sysType = kNancy13MoveBackward; break;
+ case kMoveUp: sysType = kNancy13MoveUp; break;
+ case kMoveDown: sysType = kNancy13MoveDown; break;
+ case kMoveLeft: sysType = kNancy13MoveLeft; break;
+ case kMoveRight: sysType = kNancy13MoveRight; break;
+ case kRotateCW:
+ case kRotateRight:
+ case kInvertedRotateRight: sysType = kNancy13RotateCW; break;
+ case kRotateCCW:
+ case kRotateLeft:
+ case kInvertedRotateLeft: sysType = kNancy13RotateCCW; break;
+ case kDragHand:
+ case kDropHand: sysType = kNancy13DropHand; break;
+ case kPuzzleArrow: sysType = kNancy13PuzzleArrow; hotspot = true; break;
+ case kHotspotTalk: sysType = kNancy13Normal; hotspot = true; break; // TODO: talk sprite not yet identified in the sheet
+ default: sysType = kNancy13Normal; break;
+ }
+
+ return sysType * 2 + (hotspot ? 1 : 0);
+}
+
void CursorManager::setCursor(CursorType type, int16 itemID, bool setFromScript) {
if (!_isInitialized)
return;
@@ -200,6 +254,11 @@ void CursorManager::setCursor(CursorType type, int16 itemID, bool setFromScript)
_curItemID = itemID;
_hasItem = false;
+ if (gameType >= kGameTypeNancy13) {
+ _curCursorID = resolveNancy13CursorID(type, itemID, setFromScript);
+ return;
+ }
+
if (gameType >= kGameTypeNancy10) {
_curCursorID = resolveNancy10CursorID(type, itemID, setFromScript);
return;
@@ -327,6 +386,8 @@ void CursorManager::warpCursor(const Common::Point &pos) {
}
void CursorManager::applyCursor() {
+ bool isNancy13 = g_nancy->getGameType() >= kGameTypeNancy13;
+
if (_curCursorID != _lastCursorID) {
Graphics::ManagedSurface *surf;
Common::Rect bounds = _cursors[_curCursorID].bounds;
@@ -335,11 +396,11 @@ void CursorManager::applyCursor() {
if (_hasItem)
surf = &_invCursorsSurface;
else
- surf = g_nancy->getGameType() <= kGameTypeNancy12 ? &g_nancy->_graphics->_object0 : &_uiCursorsSurface;
+ surf = !isNancy13 ? &g_nancy->_graphics->_object0 : &_uiCursorsSurface;
Graphics::ManagedSurface temp(*surf, bounds);
- CursorMan.replaceCursor(temp, hotspot.x, hotspot.y, g_nancy->_graphics->getTransColor());
+ CursorMan.replaceCursor(temp, hotspot.x, hotspot.y, !isNancy13 ? g_nancy->_graphics->getTransColor() : 0);
if (g_nancy->getGameType() == kGameTypeVampire) {
byte palette[3 * 256];
surf->grabPalette(palette, 0, 256);
diff --git a/engines/nancy/cursor.h b/engines/nancy/cursor.h
index 30d22e62ec0..b58c1c7b13c 100644
--- a/engines/nancy/cursor.h
+++ b/engines/nancy/cursor.h
@@ -92,6 +92,25 @@ public:
kNewDragHand = 38, // Type 19 â Hand used while dragging puzzle pieces (e.g. SortPuzzle pickup action sets this)
kNewPuzzleArrow = 45, // Type 22 hotspot â Arrow cursor shown when hovering a clickable puzzle hotspot
kNewDropHand = 64, // Type 32 â Hand shown when a held piece is dropped (briefly set on the drop action)
+
+ // Cursor types in Nancy13 and newer games. Nancy13 rebuilt the CURS
+ // sheet with 45 system cursor types, each stored as an [idle, hotspot]
+ // pair at slots (type * 2, type * 2 + 1). These constants are Nancy13
+ // system TYPE indices (not slot indices); resolveNancy13CursorID turns a
+ // type into the idle or hotspot slot.
+ kNancy13Normal = 0, // Eyeglass (kHotspot is the same type's hotspot slot)
+ kNancy13MoveUp = 6, // Scene-change: look/move up
+ kNancy13MoveDown = 7, // Scene-change: look/move down
+ kNancy13MoveLeft = 8, // Scene-change: move left
+ kNancy13MoveRight = 9, // Scene-change: move right
+ kNancy13MoveBackward = 10, // Scene-change: move back / exit puzzle
+ kNancy13MoveForward = 12, // Scene-change: move forward
+ kNancy13Arrow = 14, // Frame / taskbar arrow
+ kNancy13Exit = 19, // Exit
+ kNancy13DropHand = 24, // Hand shown while carrying a puzzle piece
+ kNancy13RotateCCW = 25, // Dial / turn cursor
+ kNancy13RotateCW = 26, // Dial / turn cursor
+ kNancy13PuzzleArrow = 29 // Generic clickable puzzle hotspot
};
CursorManager();
@@ -121,6 +140,11 @@ private:
// Resolve a CursorType + held-item pair to a Nancy 10+ cursor ID.
uint resolveNancy10CursorID(CursorType type, int16 itemID, bool setFromScript);
+ // Nancy13 rebuilt the cursor sheet (45 system types, new layout) and split
+ // held-item cursors into _invCursorsSurface. System cursors use a dedicated
+ // Nancy13 slot table.
+ uint resolveNancy13CursorID(CursorType type, int16 itemID, bool setFromScript);
+
struct Cursor {
Common::Rect bounds;
Common::Point hotspot;
Commit: 8fbc04d3e10618fe967048c19c01b1ae3e67497e
https://github.com/scummvm/scummvm/commit/8fbc04d3e10618fe967048c19c01b1ae3e67497e
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-16T12:42:28+03:00
Commit Message:
NANCY: NANCY13: Implement changes for ConversationSound and ConversationCel
It's now possible to watch the whole intro of Nancy13
Changed paths:
engines/nancy/action/conversation.cpp
engines/nancy/action/conversation.h
diff --git a/engines/nancy/action/conversation.cpp b/engines/nancy/action/conversation.cpp
index b3adda572e9..38dc1f7f905 100644
--- a/engines/nancy/action/conversation.cpp
+++ b/engines/nancy/action/conversation.cpp
@@ -60,6 +60,11 @@ void ConversationSound::init() {
}
void ConversationSound::readData(Common::SeekableReadStream &stream) {
+ if (g_nancy->getGameType() >= kGameTypeNancy13) {
+ readDataNancy13(stream);
+ return;
+ }
+
Common::Serializer ser(&stream, nullptr);
ser.setVersion(g_nancy->getGameType());
@@ -165,6 +170,51 @@ void ConversationSound::readTerseData(Common::SeekableReadStream &stream) {
}
}
+void ConversationSound::readDataNancy13(Common::SeekableReadStream &stream) {
+ readFilename(stream, _sound.name);
+ _sound.channelID = 12; // hardcoded, as in the terse variants
+ _sound.numLoops = 1;
+
+ readCelDataNancy13(stream);
+
+ _conditionalResponseCharacterID = stream.readByte();
+ _goodbyeResponseCharacterID = stream.readByte();
+
+ _sceneChange.sceneID = stream.readUint16LE();
+ _sceneChange.frameID = stream.readUint16LE();
+ _sceneChange.continueSceneSound = kContinueSceneSound;
+
+ // Caption and response texts are external, keyed by sound name in CONVO.
+ const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
+ assert(convo);
+ _text = convo->texts.getValOrDefault(_sound.name, "");
+
+ uint16 numResponses = stream.readUint16LE();
+ _responses.resize(numResponses);
+ for (uint i = 0; i < numResponses; ++i) {
+ ResponseStruct &response = _responses[i];
+ readFilename(stream, response.soundName);
+ response.sceneChange.sceneID = stream.readUint16LE();
+ response.sceneChange.continueSceneSound = kContinueSceneSound;
+ response.conditionFlags.read(stream);
+ response.text = convo->texts.getValOrDefault(response.soundName, "");
+ }
+
+ uint16 numFlagsStructs = stream.readUint16LE();
+ _flagsStructs.resize(numFlagsStructs);
+ for (uint i = 0; i < numFlagsStructs; ++i) {
+ FlagsStruct &flagsStruct = _flagsStructs[i];
+ flagsStruct.flagToSet.type = stream.readByte();
+ flagsStruct.flagToSet.flag.label = stream.readSint16LE();
+ flagsStruct.flagToSet.flag.flag = stream.readByte();
+ }
+}
+
+// The base conversation has no cels; skip the frame fields.
+void ConversationSound::readCelDataNancy13(Common::SeekableReadStream &stream) {
+ stream.skip(8); // firstFrame + lastFrame (int32)
+}
+
void ConversationSound::readCaptionText(Common::SeekableReadStream &stream) {
char *rawText = new char[1500];
stream.read(rawText, 1500);
@@ -837,6 +887,11 @@ void ConversationCel::updateGraphics() {
}
void ConversationCel::readData(Common::SeekableReadStream &stream) {
+ if (g_nancy->getGameType() >= kGameTypeNancy13) {
+ readDataNancy13(stream);
+ return;
+ }
+
Common::String xsheetName;
readFilename(stream, xsheetName);
@@ -869,8 +924,26 @@ void ConversationCel::readData(Common::SeekableReadStream &stream) {
ConversationSound::readData(stream);
}
+// The sound name (read by the base) is also the XSheet name. first/last frame
+// are int32s with -1 sentinels.
+void ConversationCel::readCelDataNancy13(Common::SeekableReadStream &stream) {
+ readXSheet(stream, _sound.name); // also fills _treeNames from the XSheet header
+
+ _drawingOrder = { 1, 0, 2, 3 };
+ _overrideTreeRects.resize(4, kCelOverrideTreeRectsOff);
+
+ const int32 firstFrame = stream.readSint32LE();
+ const int32 lastFrame = stream.readSint32LE();
+ const uint numFrames = (!_celNames.empty() && !_celNames[0].empty()) ? _celNames[0].size() : 0;
+ _firstFrame = firstFrame < 0 ? 0 : (uint16)firstFrame;
+ _lastFrame = lastFrame < 0 ? (numFrames ? (uint16)(numFrames - 1) : 0) : (uint16)lastFrame;
+}
+
void ConversationCel::readXSheet(Common::SeekableReadStream &stream, const Common::String &xsheetName) {
Common::SeekableReadStream *xsheet = SearchMan.createReadStreamForMember(Common::Path(xsheetName));
+ const bool isNancy13 = g_nancy->getGameType() >= kGameTypeNancy13;
+ const uint kNameSize = 33;
+ const uint kFrameRecordSize = 140;
// Read the xsheet and load all images into the arrays
// Completely unoptimized, the original engine uses a buffer
@@ -883,19 +956,37 @@ void ConversationCel::readXSheet(Common::SeekableReadStream &stream, const Commo
}
xsheet->seek(0x22);
- uint numFrames = xsheet->readUint16LE();
- xsheet->skip(2);
- _frameTime = xsheet->readUint16LE();
- xsheet->skip(2);
+ const uint16 numFrames = xsheet->readUint16LE();
+ uint16 numTrees = 4;
+
+ if (isNancy13) {
+ // Nancy13 moved the cel tree / .cal names into the XSheet header. Each
+ // frame is a fixed-size block of numTrees cel names plus padding.
+
+ const uint kTreeNamesOffset = 38;
+ const uint kFrameDataOffset = 174;
- _celNames.resize(4, Common::Array<Common::Path>(numFrames));
+ numTrees = xsheet->readUint16LE();
+
+ _treeNames.resize(numTrees);
+ for (uint j = 0; j < numTrees; ++j) {
+ readFilename(*xsheet, _treeNames[j]);
+ }
+
+ _frameTime = xsheet->readUint16LE();
+ xsheet->skip(kFrameDataOffset - kTreeNamesOffset - numTrees * kNameSize - 2);
+ } else {
+ xsheet->skip(2);
+ _frameTime = xsheet->readUint16LE();
+ xsheet->skip(2);
+ }
+
+ _celNames.resize(numTrees, Common::Array<Common::Path>(numFrames));
for (uint i = 0; i < numFrames; ++i) {
- for (uint j = 0; j < _celNames.size(); ++j) {
+ for (uint j = 0; j < numTrees; ++j) {
readFilename(*xsheet, _celNames[j][i]);
}
-
- // 4 unknown values
- xsheet->skip(8);
+ xsheet->skip(kFrameRecordSize - numTrees * kNameSize);
}
delete xsheet;
diff --git a/engines/nancy/action/conversation.h b/engines/nancy/action/conversation.h
index 5c6e7d180f9..b1a9f4870f0 100644
--- a/engines/nancy/action/conversation.h
+++ b/engines/nancy/action/conversation.h
@@ -109,6 +109,12 @@ protected:
void readTerseCaptionText(Common::SeekableReadStream &stream);
void readTerseResponseText(Common::SeekableReadStream &stream, ResponseStruct &response);
+ // Nancy13 compact conversation format. readCelDataNancy13 is the only
+ // per-class difference: the base skips the cel-only frame fields, while
+ // ConversationCel reads them plus the XSheet.
+ void readDataNancy13(Common::SeekableReadStream &stream);
+ virtual void readCelDataNancy13(Common::SeekableReadStream &stream);
+
// Functions for handling the built-in dialogue responses found in the executable
void addConditionalDialogue();
void addGoodbye();
@@ -196,6 +202,7 @@ protected:
Cel &loadCel(const Common::Path &name, const Common::String &treeName);
void readXSheet(Common::SeekableReadStream &stream, const Common::String &xsheetName);
+ void readCelDataNancy13(Common::SeekableReadStream &stream) override;
Common::Array<Common::Array<Common::Path>> _celNames;
Common::Array<Common::String> _treeNames;
Commit: c607797791af1977cd0478c00a3b475130d11727
https://github.com/scummvm/scummvm/commit/c607797791af1977cd0478c00a3b475130d11727
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-16T12:42:29+03:00
Commit Message:
NANCY: NANCY13: Use correct local rect for ConversationPopup
Fixes conversations in Nancy13. It's now possible to start a new game,
after watching the intro.
Changed paths:
engines/nancy/ui/conversationpopup.cpp
engines/nancy/ui/conversationpopup.h
diff --git a/engines/nancy/ui/conversationpopup.cpp b/engines/nancy/ui/conversationpopup.cpp
index c3e86c3a73d..4660c8071a0 100644
--- a/engines/nancy/ui/conversationpopup.cpp
+++ b/engines/nancy/ui/conversationpopup.cpp
@@ -130,9 +130,7 @@ void ConversationPopup::drawContent() {
drawAllText(textBounds, 0, _tboxData->conversationFontID, _tboxData->highlightConversationFontID);
- Common::Rect localTextRect = _uicoData->textRect;
- localTextRect.translate(-_uicoData->header.normalDestRect.left,
- -_uicoData->header.normalDestRect.top);
+ Common::Rect localTextRect = getLocalTextRect();
const uint16 inner = getInnerHeight();
const uint16 outer = localTextRect.height();
@@ -153,6 +151,15 @@ uint16 ConversationPopup::getInnerHeight() const {
return _drawnTextHeight + _tboxData->scrollbarDefaultPos.y;
}
+Common::Rect ConversationPopup::getLocalTextRect() const {
+ Common::Rect r = _uicoData->textRect;
+ if (g_nancy->getGameType() < kGameTypeNancy13) {
+ r.translate(-_uicoData->header.normalDestRect.left,
+ -_uicoData->header.normalDestRect.top);
+ }
+ return r;
+}
+
Common::Rect ConversationPopup::toPopupLocal(const Common::Rect &chunkRect, bool useGameFrame) const {
// Chunk-space rects need the viewport offset applied when their own
// destUsesGameFrameOffset flag is set, then the popup's screen
@@ -264,9 +271,7 @@ void ConversationPopup::handleInput(NancyInput &input) {
}
// Response hotspot handling â mirrors Textbox::handleInput().
- Common::Rect localTextRect = _uicoData->textRect;
- localTextRect.translate(-_uicoData->header.normalDestRect.left,
- -_uicoData->header.normalDestRect.top);
+ Common::Rect localTextRect = getLocalTextRect();
const uint16 inner = getInnerHeight();
const uint16 outer = localTextRect.height();
diff --git a/engines/nancy/ui/conversationpopup.h b/engines/nancy/ui/conversationpopup.h
index 128c5b2af80..ba0b77b5a5b 100644
--- a/engines/nancy/ui/conversationpopup.h
+++ b/engines/nancy/ui/conversationpopup.h
@@ -72,6 +72,11 @@ private:
// the current scroll position.
Common::Rect computeThumbRect() const;
+ // The text area in popup-local coordinates. Pre-Nancy13 stores textRect in
+ // chunk/screen space and needs the popup origin subtracted; Nancy13 already
+ // stores it image-local.
+ Common::Rect getLocalTextRect() const;
+
// Convert a chunk-space rect into popup-local coordinates.
Common::Rect toPopupLocal(const Common::Rect &chunkRect, bool useGameFrame) const;
Common::Point popupLocalMouse(const Common::Point &screenMouse) const;
More information about the Scummvm-git-logs
mailing list