[Scummvm-git-logs] scummvm master -> 9c143cbe8d45bf93c7e8d659925031d601dddbc3
bluegr
noreply at scummvm.org
Sun Jul 26 16:29:35 UTC 2026
This automated email contains information about 5 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
c33e2d8f3b NANCY: NANCY3: Properly handle hotspots for animated overlays
a0f6e463bb NANCY: Overlay dimensions are calculated from their source rect
d759a711e6 NANCY: NANCY12: Implement new timer design
3aacdcadaa NANCY: NANCY12: Add new transparency mode
9c143cbe8d NANCY: NANCY10: Bottom-align checkboxes in the task list
Commit: c33e2d8f3b7674f554285286f80b3614605921cf
https://github.com/scummvm/scummvm/commit/c33e2d8f3b7674f554285286f80b3614605921cf
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-26T19:29:21+03:00
Commit Message:
NANCY: NANCY3: Properly handle hotspots for animated overlays
This mirrors the handling does for static overlays, as _enableHotspot
is only used in Nancy2.
Fixes giving the toy mouse to Uri the cat in Nancy12
Changed paths:
engines/nancy/action/overlay.cpp
engines/nancy/action/overlay.h
diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index 55a6da14b21..7923a076415 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -103,7 +103,7 @@ void Overlay::readData(Common::SeekableReadStream &stream) {
ser.skip(2); // VIDEO_STOP_RENDERING or VIDEO_CONTINUE_RENDERING
ser.syncAsUint16LE(_transparency);
ser.syncAsUint16LE(_hasSceneChange);
- ser.syncAsUint16LE(_enableHotspot, kGameTypeNancy2, kGameTypeNancy2);
+ ser.syncAsUint16LE(_enableHotspotNancy2, kGameTypeNancy2, kGameTypeNancy2);
ser.syncAsUint16LE(_z, kGameTypeNancy2);
ser.syncAsUint16LE(_overlayType, kGameTypeNancy2);
ser.syncAsUint16LE(numSrcRects, kGameTypeNancy2);
@@ -122,12 +122,6 @@ void Overlay::readData(Common::SeekableReadStream &stream) {
ser.syncAsUint16LE(_z, kGameTypeNancy1, kGameTypeNancy1);
- if (ser.getVersion() > kGameTypeNancy2) {
- if (_overlayType == kPlayOverlayStatic) {
- _enableHotspot = (_hasSceneChange == kPlayOverlaySceneChange) ? kPlayOverlayWithHotspot : kPlayOverlayNoHotspot;
- }
- }
-
if (_isInterruptible) {
ser.syncAsSint16LE(_interruptCondition.label);
ser.syncAsUint16LE(_interruptCondition.flag);
@@ -201,9 +195,16 @@ void Overlay::execute() {
moveTo(_blitDescriptions[i].dest);
setVisible(true);
- if (_enableHotspot == kPlayOverlayWithHotspot) {
- _hotspot = _screenPosition;
- _hasHotspot = true;
+ if (g_nancy->getGameType() <= kGameTypeNancy2) {
+ if (_enableHotspotNancy2 == kPlayOverlayWithHotspot) {
+ _hotspot = _screenPosition;
+ _hasHotspot = true;
+ }
+ } else {
+ if (_blitDescriptions[i].hasHotspot == kPlayOverlayWithHotspot) {
+ _hotspot = _screenPosition;
+ _hasHotspot = true;
+ }
}
break;
@@ -340,7 +341,7 @@ void Overlay::execute() {
if (g_nancy->getGameType() <= kGameTypeNancy2) {
// In nancy2, the presence of a hotspot relies on whether the Overlay has a scene change
- if (_enableHotspot == kPlayOverlayWithHotspot) {
+ if (_enableHotspotNancy2 == kPlayOverlayWithHotspot) {
_hotspot = _screenPosition;
_hasHotspot = true;
}
diff --git a/engines/nancy/action/overlay.h b/engines/nancy/action/overlay.h
index 888f666ab1d..25535db74cf 100644
--- a/engines/nancy/action/overlay.h
+++ b/engines/nancy/action/overlay.h
@@ -57,7 +57,7 @@ public:
uint16 _transparency = kPlayOverlayPlain;
uint16 _hasSceneChange = kPlayOverlaySceneChange;
- uint16 _enableHotspot = kPlayOverlayNoHotspot;
+ uint16 _enableHotspotNancy2 = kPlayOverlayNoHotspot;
uint16 _overlayType = kPlayOverlayAnimated;
uint16 _playDirection = kPlayOverlayForward;
uint16 _loop = kPlayOverlayOnce;
@@ -85,6 +85,7 @@ public:
bool canHaveHotspot() const override { return true; }
bool isViewportRelative() const override { return true; }
+ Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
protected:
Common::String getRecordTypeName() const override;
Commit: a0f6e463bb2dde5a92903eaf01c2d4d4049533ed
https://github.com/scummvm/scummvm/commit/a0f6e463bb2dde5a92903eaf01c2d4d4049533ed
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-26T19:29:23+03:00
Commit Message:
NANCY: Overlay dimensions are calculated from their source rect
Now, the axe and logs in Nancy10 scene 2951 are rendered correctly
Fix #16977
Changed paths:
engines/nancy/action/overlay.cpp
diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index 7923a076415..1b165274393 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -403,9 +403,15 @@ void OverlayStaticTerse::readData(Common::SeekableReadStream &stream) {
readRect(stream, dest);
readRect(stream, src);
- _srcRects.push_back(src);
+ // The source rect only supplies the top-left offset into the image; the overlay
+ // is blitted 1:1, so the source region takes the destination's dimensions. Using
+ // the source rect's own (smaller) size here would scale the image to fit the destination.
+ Common::Rect srcRect(dest.width(), dest.height());
+ srcRect.moveTo(src.left, src.top);
+
+ _srcRects.push_back(srcRect);
_blitDescriptions.resize(1);
- _blitDescriptions[0].src = Common::Rect(src.width(), src.height());
+ _blitDescriptions[0].src = Common::Rect(dest.width(), dest.height());
_blitDescriptions[0].dest = dest;
_overlayType = kPlayOverlayStatic;
Commit: d759a711e6b002bfb0f4a465ca40f5d7a67ff7ec
https://github.com/scummvm/scummvm/commit/d759a711e6b002bfb0f4a465ca40f5d7a67ff7ec
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-26T19:29:24+03:00
Commit Message:
NANCY: NANCY12: Implement new timer design
Fixes giving the mouse to Uri the cat
Changed paths:
engines/nancy/action/miscrecords.cpp
engines/nancy/action/miscrecords.h
engines/nancy/nancy.h
engines/nancy/puzzledata.cpp
engines/nancy/puzzledata.h
engines/nancy/state/scene.cpp
engines/nancy/state/scene.h
diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index 4aac737c170..614f32f7bbb 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -34,6 +34,7 @@
#include "common/events.h"
#include "common/config-manager.h"
+#include "common/random.h"
#include "nancy/ui/taskbar.h"
namespace Nancy {
@@ -439,19 +440,175 @@ static TimerData::Timer *getSoftwareTimer(int16 index) {
return timerData ? &timerData->timers[index] : nullptr;
}
+// Nancy 12+: adds a trigger to a running timer, unless an identical one already
+// exists (so re-running the record on scene re-entry doesn't stack duplicates)
+// or the timer's kNumTriggers slots are full.
+static void addTimerTrigger(TimerData::Timer &timer, TimerData::Trigger::Type type,
+ uint32 durationMs, const SoundDescription &sound, const Common::Array<FlagDescription> &flags) {
+ for (uint i = 0; i < timer.triggers.size(); ++i) {
+ const TimerData::Trigger &t = timer.triggers[i];
+ if (t.type != type || t.durationMs != durationMs ||
+ t.sound.name != sound.name || t.sound.channelID != sound.channelID ||
+ t.sound.volume != sound.volume) {
+ continue;
+ }
+
+ bool flagsMatch = true;
+ for (uint j = 0; j < ARRAYSIZE(t.flags); ++j) {
+ FlagDescription incoming = j < flags.size() ? flags[j] : FlagDescription();
+ if (t.flags[j].label != incoming.label || t.flags[j].flag != incoming.flag) {
+ flagsMatch = false;
+ break;
+ }
+ }
+
+ if (flagsMatch) {
+ return;
+ }
+ }
+
+ if (timer.triggers.size() >= TimerData::kNumTriggers) {
+ return;
+ }
+
+ TimerData::Trigger trigger;
+ trigger.type = type;
+ trigger.durationMs = durationMs;
+ trigger.hasFired = false;
+ trigger.sound = sound;
+ for (uint i = 0; i < ARRAYSIZE(trigger.flags); ++i) {
+ trigger.flags[i] = i < flags.size() ? flags[i] : FlagDescription();
+ }
+
+ timer.triggers.push_back(trigger);
+}
+
+// Reads exactly size bytes from the stream, returning the text up to the first
+// null byte. Used for the fixed-size string fields inside a TimerControl chunk.
+static Common::String readFixedSizeString(Common::SeekableReadStream &stream, uint size) {
+ Common::String result;
+ bool ended = false;
+ for (uint i = 0; i < size; ++i) {
+ byte b = stream.readByte();
+ if (b == 0) {
+ ended = true;
+ }
+ if (!ended) {
+ result += (char)b;
+ }
+ }
+
+ return result;
+}
+
void ResetAndStartTimer::readData(Common::SeekableReadStream &stream) {
- _timerIndex = stream.readByte();
+ if (g_nancy->getGameType() < kGameTypeNancy12) {
+ _timerIndex = stream.readByte();
+ return;
+ }
+
+ _timerIndex = stream.readSint16LE(); // 0x00
+ _command = stream.readSint16LE(); // 0x02
+
+ switch (_command) {
+ case kAddTime:
+ case kSubtractTime:
+ case kSetTime:
+ _hours = stream.readSint16LE(); // 0x04
+ _minutes = stream.readSint16LE(); // 0x06
+ _seconds = stream.readSint16LE(); // 0x08
+ stream.skip(2); // 0x0a, unused
+ break;
+ case kConfigOneShot:
+ case kConfigRepeating: {
+ _hours = stream.readSint16LE(); // 0x04
+ _minutes = stream.readSint16LE(); // 0x06
+ _seconds = stream.readSint16LE(); // 0x08
+ stream.skip(2); // 0x0a, unused
+ _sound.volume = stream.readUint16LE(); // 0x0c
+ _sound.channelID = stream.readUint16LE(); // 0x0e
+ _sound.numLoops = 1;
+
+ // Three candidate expiry-sound names (0x10, 33 bytes each); one is
+ // picked at random, "NO SOUND" marks an empty slot
+ Common::Array<Common::String> names;
+ for (uint i = 0; i < 3; ++i) {
+ Common::String name = readFixedSizeString(stream, 0x21);
+ if (!name.empty() && !name.equalsIgnoreCase("NO SOUND")) {
+ names.push_back(name);
+ }
+ }
+
+ if (!names.empty()) {
+ _sound.name = names[g_nancy->_randomSource->getRandomNumber(names.size() - 1)];
+ }
+
+ // Event flags fired on expiry (count at 0x73)
+ uint16 numFlags = stream.readUint16LE();
+ _flags.resize(numFlags);
+ for (uint i = 0; i < numFlags; ++i) {
+ _flags[i].label = stream.readSint16LE();
+ _flags[i].flag = (byte)stream.readSint16LE();
+ }
+ break;
+ }
+ default:
+ // kStart, kClear and kPause carry no further data
+ break;
+ }
}
void ResetAndStartTimer::execute() {
- NancySceneState.resetAndStartTimer();
+ if (g_nancy->getGameType() < kGameTypeNancy12) {
+ NancySceneState.resetAndStartTimer();
+
+ // Nancy 11 also resets and starts one of the software-timer slots
+ if (g_nancy->getGameType() >= kGameTypeNancy11) {
+ TimerData::Timer *timer = getSoftwareTimer(_timerIndex);
+ if (timer) {
+ timer->reset();
+ timer->state = TimerData::Timer::kRunning;
+ }
+ }
- // Nancy 11 also resets and starts one of the software-timer slots
- if (g_nancy->getGameType() >= kGameTypeNancy11) {
- TimerData::Timer *timer = getSoftwareTimer(_timerIndex);
- if (timer) {
- timer->reset();
+ _isDone = true;
+ return;
+ }
+
+ // Nancy 12+ issues a command to one of the software-timer slots. Every
+ // command other than kStart only takes effect while the slot is running.
+ TimerData::Timer *timer = getSoftwareTimer(_timerIndex);
+ if (timer) {
+ if (_command == kStart) {
timer->state = TimerData::Timer::kRunning;
+ } else if (timer->state == TimerData::Timer::kRunning) {
+ const uint32 durationMs = ((uint32)_hours * 3600 + (uint32)_minutes * 60 + (uint32)_seconds) * 1000;
+
+ switch (_command) {
+ case kClear:
+ timer->reset();
+ break;
+ case kPause:
+ timer->state = TimerData::Timer::kPaused;
+ break;
+ case kAddTime:
+ timer->currentTimeMs += durationMs;
+ break;
+ case kSubtractTime:
+ timer->currentTimeMs = timer->currentTimeMs > durationMs ? timer->currentTimeMs - durationMs : 0;
+ break;
+ case kSetTime:
+ timer->currentTimeMs = durationMs;
+ break;
+ case kConfigOneShot:
+ case kConfigRepeating:
+ addTimerTrigger(*timer,
+ (_command == kConfigRepeating) ? TimerData::Trigger::kRepeating : TimerData::Trigger::kOneShot,
+ durationMs, _sound, _flags);
+ break;
+ default:
+ break;
+ }
}
}
@@ -475,24 +632,6 @@ void StopTimer::execute() {
_isDone = true;
}
-// Reads exactly size bytes from the stream, returning the text up to the first
-// null byte. Used for the fixed-size string fields inside a TimerControl chunk.
-static Common::String readFixedSizeString(Common::SeekableReadStream &stream, uint size) {
- Common::String result;
- bool ended = false;
- for (uint i = 0; i < size; ++i) {
- byte b = stream.readByte();
- if (b == 0) {
- ended = true;
- }
- if (!ended) {
- result += (char)b;
- }
- }
-
- return result;
-}
-
void TimerControl::readData(Common::SeekableReadStream &stream) {
const int64 startPos = stream.pos();
diff --git a/engines/nancy/action/miscrecords.h b/engines/nancy/action/miscrecords.h
index 9dfeac38e59..e383914f898 100644
--- a/engines/nancy/action/miscrecords.h
+++ b/engines/nancy/action/miscrecords.h
@@ -285,14 +285,33 @@ protected:
};
// Starts the timer. Used in combination with Dependency types that check for
-// how much time has passed since the timer was started. From Nancy 11 onwards
-// the record also carries a software-timer slot index (see TimerControl).
+// how much time has passed since the timer was started. Nancy 11 also carries a
+// software-timer slot index (see TimerControl). From Nancy 12 the record became
+// a general "Control a Timer" command: a slot index plus a command whose value
+// selects a variable-size payload.
class ResetAndStartTimer : public ActionRecord {
public:
+ enum Command {
+ kStart = 0, // Begin counting up from the current time
+ kClear = 1, // Reset the slot back to idle
+ kConfigOneShot = 2, // Set target/payload; fire once, then reset
+ kConfigRepeating = 3, // Set target/payload; fire once, then keep counting
+ kPause = 4, // Suspend counting
+ kAddTime = 5, // Add the duration to the elapsed time
+ kSubtractTime = 6, // Subtract the duration from the elapsed time
+ kSetTime = 7 // Set the elapsed time to the duration
+ };
+
void readData(Common::SeekableReadStream &stream) override;
void execute() override;
- byte _timerIndex = 0; // Nancy 11+ software-timer slot
+ int16 _timerIndex = 0; // Software-timer slot (Nancy 11+)
+ int16 _command = kStart; // Nancy 12+
+ int16 _hours = 0;
+ int16 _minutes = 0;
+ int16 _seconds = 0;
+ SoundDescription _sound; // Played on expiry when configured
+ Common::Array<FlagDescription> _flags; // Fired on expiry when configured
protected:
Common::String getRecordTypeName() const override { return "ResetAndStartTimer"; }
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index ad776b60a81..753c64f1572 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -54,7 +54,7 @@ class Serializer;
*/
namespace Nancy {
-static const int kSavegameVersion = 5;
+static const int kSavegameVersion = 6;
struct NancyGameDescription;
diff --git a/engines/nancy/puzzledata.cpp b/engines/nancy/puzzledata.cpp
index 797b2cea943..2d51a7d6679 100644
--- a/engines/nancy/puzzledata.cpp
+++ b/engines/nancy/puzzledata.cpp
@@ -396,6 +396,33 @@ void TimerData::synchronize(Common::Serializer &ser) {
ser.syncAsSint16LE(t.flags[j].label);
ser.syncAsByte(t.flags[j].flag);
}
+
+ // Nancy 12+ triggers, added in savegame version 6
+ if (ser.getVersion() >= 6) {
+ uint16 numTriggers = (uint16)t.triggers.size();
+ ser.syncAsUint16LE(numTriggers);
+ if (ser.isLoading()) {
+ t.triggers.resize(numTriggers);
+ }
+
+ for (uint j = 0; j < numTriggers; ++j) {
+ TimerData::Trigger &trig = t.triggers[j];
+ ser.syncAsSint32LE(trig.type);
+ ser.syncAsUint32LE(trig.durationMs);
+ ser.syncAsByte(trig.hasFired);
+
+ ser.syncString(trig.sound.name);
+ ser.syncAsUint16LE(trig.sound.channelID);
+ ser.syncAsUint16LE(trig.sound.playCommands);
+ ser.syncAsUint16LE(trig.sound.numLoops);
+ ser.syncAsUint16LE(trig.sound.volume);
+
+ for (uint k = 0; k < ARRAYSIZE(trig.flags); ++k) {
+ ser.syncAsSint16LE(trig.flags[k].label);
+ ser.syncAsByte(trig.flags[k].flag);
+ }
+ }
+ }
}
}
diff --git a/engines/nancy/puzzledata.h b/engines/nancy/puzzledata.h
index bc4534db4c3..cbd13954866 100644
--- a/engines/nancy/puzzledata.h
+++ b/engines/nancy/puzzledata.h
@@ -267,11 +267,26 @@ struct CellPhonePictureData : public PuzzleData {
};
// Nancy 11+ AR 69 (TimerControl). 10 software timers, each counting up from
-// zero. A "configured" timer (state 5/6) fires a set of event flags, plays an
-// optional sound and shows an optional caption once its target duration
-// elapses. Started/stopped via ResetAndStartTimer (104) and StopTimer (105),
-// which in Nancy 11 carry a timer-slot index.
+// zero. In Nancy 11 a "configured" timer (state 5/6) fires a set of event flags,
+// plays an optional sound and shows an optional caption once its target duration
+// elapses. From Nancy 12 a running timer instead carries up to kNumTriggers
+// independent triggers (see ResetAndStartTimer), each firing its own flags and
+// sound. Started/stopped via ResetAndStartTimer (104) and StopTimer (105), which
+// in Nancy 11 carry a timer-slot index.
struct TimerData : public PuzzleData {
+ // Nancy 12+ per-timer trigger: fires its flags and sound once its target
+ // duration is reached. A one-shot trigger clears the whole timer when it
+ // fires; a repeating one leaves the timer counting.
+ struct Trigger {
+ enum Type { kOneShot = 1, kRepeating = 2 };
+
+ int32 type = kOneShot;
+ uint32 durationMs = 0;
+ bool hasFired = false;
+ SoundDescription sound;
+ FlagDescription flags[10];
+ };
+
struct Timer {
enum State { kIdle = 0, kRunning = 1, kPaused = 2, kOneShot = 5, kRepeating = 6 };
@@ -283,11 +298,13 @@ struct TimerData : public PuzzleData {
Common::String autotextKey;
Common::String caption;
FlagDescription flags[10];
+ Common::Array<Trigger> triggers; // Nancy 12+
void reset() { *this = Timer(); }
};
static const uint kNumTimers = 10;
+ static const uint kNumTriggers = 20;
TimerData() {}
virtual ~TimerData() {}
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index f2bcc4ce91c..7e6bc40a142 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -1391,6 +1391,7 @@ void Scene::tickSoftwareTimers(uint32 deltaMs) {
timer.currentTimeMs += deltaMs;
+ // Nancy 11 single-config timers fire directly from the timer state
if ((timer.state == TimerData::Timer::kOneShot || timer.state == TimerData::Timer::kRepeating) &&
timer.durationMs > 0 && !timer.hasFired && timer.currentTimeMs >= timer.durationMs) {
fireSoftwareTimer(timer);
@@ -1403,6 +1404,27 @@ void Scene::tickSoftwareTimers(uint32 deltaMs) {
timer.state = TimerData::Timer::kRunning;
}
}
+
+ // Nancy 12+ running timers fire from their triggers. A one-shot trigger
+ // clears the whole timer when it fires; a repeating one leaves it running.
+ if (timer.state == TimerData::Timer::kRunning) {
+ bool clearTimer = false;
+ for (uint j = 0; j < timer.triggers.size(); ++j) {
+ TimerData::Trigger &trigger = timer.triggers[j];
+ if (!trigger.hasFired && trigger.durationMs > 0 && timer.currentTimeMs >= trigger.durationMs) {
+ trigger.hasFired = true;
+ fireTimerTrigger(trigger);
+
+ if (trigger.type == TimerData::Trigger::kOneShot) {
+ clearTimer = true;
+ }
+ }
+ }
+
+ if (clearTimer) {
+ timer.reset();
+ }
+ }
}
}
@@ -1454,6 +1476,30 @@ void Scene::fireSoftwareTimer(TimerData::Timer &timer) {
}
}
+void Scene::fireTimerTrigger(TimerData::Trigger &trigger) {
+ // Set the trigger's event flags
+ for (uint i = 0; i < ARRAYSIZE(trigger.flags); ++i) {
+ if (trigger.flags[i].label != kFlagNoLabel) {
+ setEventFlag(trigger.flags[i]);
+ }
+ }
+
+ // Play the trigger's sound
+ if (trigger.sound.name != "NO SOUND") {
+ g_nancy->_sound->loadSound(trigger.sound);
+ g_nancy->_sound->playSound(trigger.sound);
+ }
+
+ // Nancy 12+ triggers carry no inline caption; the subtitle is looked up from
+ // the played sound's name
+ if (ConfMan.getBool("subtitles", ConfMan.getActiveDomainName()) && trigger.sound.name != "NO SOUND") {
+ const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
+ if (autotext && autotext->texts.contains(trigger.sound.name)) {
+ _textbox.addTextLine(autotext->texts[trigger.sound.name]);
+ }
+ }
+}
+
Common::Rect Scene::activePopupConfinement() const {
// Pick the first visible Nancy 10+ popup; if more than one is open
// (shouldn't normally happen) the priority order matches the input
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 9b82d09a945..1c9a7129cba 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -273,6 +273,7 @@ private:
// puzzle data) and fires any whose configured duration has just elapsed.
void tickSoftwareTimers(uint32 deltaMs);
void fireSoftwareTimer(TimerData::Timer &timer);
+ void fireTimerTrigger(TimerData::Trigger &trigger);
// Rect of the open Nancy 10+ taskbar popup, or empty if none.
Common::Rect activePopupConfinement() const;
Commit: 3aacdcadaa9ac33478f8344422589fdb090247f7
https://github.com/scummvm/scummvm/commit/3aacdcadaa9ac33478f8344422589fdb090247f7
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-26T19:29:25+03:00
Commit Message:
NANCY: NANCY12: Add new transparency mode
Transparency mode 3 has identical handling as mode 2. First observed
in Nancy12, SewingMachinePuzzle (the needle overlay)
Changed paths:
engines/nancy/action/autotext.cpp
engines/nancy/action/overlay.cpp
engines/nancy/action/puzzle/peepholepuzzle.cpp
engines/nancy/commontypes.h
diff --git a/engines/nancy/action/autotext.cpp b/engines/nancy/action/autotext.cpp
index e1761e4d404..9707156d702 100644
--- a/engines/nancy/action/autotext.cpp
+++ b/engines/nancy/action/autotext.cpp
@@ -187,7 +187,7 @@ void Autotext::execute() {
}
_fullSurface.create(surf, surf.getBounds());
- if(_transparency == kPlayOverlayTransparent) {
+ if (_transparency >= kPlayOverlayTransparent) {
_fullSurface.setTransparentColor(g_nancy->_graphics->getTransColor());
}
@@ -224,7 +224,7 @@ void Autotext::execute() {
_viewportRender = new AutotextRender(_placementMode);
_viewportRender->_drawSurface.create(surf, src);
_viewportRender->moveTo(_viewportDest);
- _viewportRender->setTransparent(_transparency == kPlayOverlayTransparent);
+ _viewportRender->setTransparent(_transparency >= kPlayOverlayTransparent);
_viewportRender->setVisible(true);
_viewportRender->init();
_viewportRender->registerGraphics();
diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index 1b165274393..76787952e36 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -254,7 +254,7 @@ void Overlay::execute() {
}
_drawSurface.create(_fullSurface, srcRect);
- setTransparent(_transparency == kPlayOverlayTransparent);
+ setTransparent(_transparency >= kPlayOverlayTransparent);
_currentFrame = nextFrame;
_needsRedraw = true;
@@ -330,7 +330,7 @@ void Overlay::execute() {
if (blitsForThisFrame.size() == 1) {
_drawSurface.create(_fullSurface, srcRect);
- setTransparent(_transparency == kPlayOverlayTransparent);
+ setTransparent(_transparency >= kPlayOverlayTransparent);
} else {
Common::Rect d = _blitDescriptions[blitsForThisFrame[i]].dest;
d.translate(-destRect.left, -destRect.top);
diff --git a/engines/nancy/action/puzzle/peepholepuzzle.cpp b/engines/nancy/action/puzzle/peepholepuzzle.cpp
index 0d3460d2385..67273a35a35 100644
--- a/engines/nancy/action/puzzle/peepholepuzzle.cpp
+++ b/engines/nancy/action/puzzle/peepholepuzzle.cpp
@@ -55,7 +55,7 @@ void PeepholePuzzle::init() {
_currentSrc = _startSrc;
- setTransparent(_transparency == kPlayOverlayTransparent);
+ setTransparent(_transparency >= kPlayOverlayTransparent);
_drawSurface.clear(_drawSurface.getTransparentColor());
setVisible(true);
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 83cfe74af9c..c409cfd203c 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -104,9 +104,12 @@ static const byte kLargeVideoFormat = 2;
static const byte kVideoPlaytypeAVF = 0;
static const byte kVideoPlaytypeBink = 1;
-// Overlay
+// Overlay transparency mode. 1 = opaque, anything >= kPlayOverlayTransparent is
+// drawn transparent (the original engine is 16bpp color-key, with no alpha/blend
+// path, so every transparent mode - Nancy12 also uses 3 - is a plain color key).
static const byte kPlayOverlayPlain = 1;
static const byte kPlayOverlayTransparent = 2;
+static const byte kPlayOverlayTransparent2 = 3;
static const byte kPlayOverlaySceneChange = 1;
static const byte kPlayOverlayNoSceneChange = 2;
Commit: 9c143cbe8d45bf93c7e8d659925031d601dddbc3
https://github.com/scummvm/scummvm/commit/9c143cbe8d45bf93c7e8d659925031d601dddbc3
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-26T19:29:26+03:00
Commit Message:
NANCY: NANCY10: Bottom-align checkboxes in the task list
Fix #16965
Changed paths:
engines/nancy/misc/hypertext.cpp
diff --git a/engines/nancy/misc/hypertext.cpp b/engines/nancy/misc/hypertext.cpp
index f24d0d858ca..f3cd1c78488 100644
--- a/engines/nancy/misc/hypertext.cpp
+++ b/engines/nancy/misc/hypertext.cpp
@@ -360,10 +360,14 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint leftOffse
Common::Rect markSrc = mark->_markSrcs[change.index];
Common::Rect markDest = markSrc;
if (useLineAlignedMark) {
- // Nancy 10+: align the mark with the current
- // line top, matching the original engine.
+ // Nancy 10+: bottom-anchor the mark to the current line,
+ // matching the original engine. The mark's baseline stays
+ // fixed while sprites of different heights (e.g. an unchecked
+ // box vs. a checked one) grow upward, so toggling a checkbox
+ // does not shift it vertically.
+ const int lineTop = textBounds.top + _numDrawnLines * lineStep(font) + _imageVerticalOffset;
markDest.moveTo(textBounds.left + horizontalOffset + (newLineStart ? 0 : leftOffsetNonNewline) + 1,
- textBounds.top + _numDrawnLines * lineStep(font) + _imageVerticalOffset);
+ lineTop + font->getFontHeight() - markSrc.height());
} else {
markDest.moveTo(textBounds.left + horizontalOffset + (newLineStart ? 0 : leftOffsetNonNewline) + 1,
lineNumber == 0 ?
More information about the Scummvm-git-logs
mailing list