[Scummvm-git-logs] scummvm master -> 8b6fb34dc8d1a35b58436c905c894839c3c00813
bluegr
noreply at scummvm.org
Tue Apr 21 21:19:36 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:
8b6fb34dc8 AGOS: Implement day/night palette fading for PN Amiga/Atari ST
Commit: 8b6fb34dc8d1a35b58436c905c894839c3c00813
https://github.com/scummvm/scummvm/commit/8b6fb34dc8d1a35b58436c905c894839c3c00813
Author: Robert Megone (robert.megone at gmail.com)
Date: 2026-04-22T00:19:31+03:00
Commit Message:
AGOS: Implement day/night palette fading for PN Amiga/Atari ST
Changed paths:
engines/agos/agos.cpp
engines/agos/agos.h
engines/agos/debugger.cpp
engines/agos/debugger.h
engines/agos/event.cpp
engines/agos/script.cpp
engines/agos/vga.cpp
diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index b9f3d9e612e..d5b26a481af 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -515,6 +515,13 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
memset(_currentPalette, 0, sizeof(_currentPalette));
memset(_displayPalette, 0, sizeof(_displayPalette));
+ memset(_pnPaletteBanks, 0, sizeof(_pnPaletteBanks));
+ memset(_pnFadeCurrent, 0, sizeof(_pnFadeCurrent));
+ memset(_pnHavePaletteBank, 0, sizeof(_pnHavePaletteBank));
+ _pnDayNightControllerSelectorMask = 0xFFFF;
+ _pnDayNightControllerLastStage = 0xFF;
+ _pnLastClockMinutes = -1;
+ _pnDayNightControllerTickCounter = 0x00C8;
memset(_videoBuf1, 0, sizeof(_videoBuf1));
memset(_videoWindows, 0, sizeof(_videoWindows));
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index f4ba808cea7..3f06b6a331b 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -220,7 +220,8 @@ enum EventType {
ANIMATE_EVENT = 1 << 2,
SCROLL_EVENT = 1 << 3,
PLAYER_DAMAGE_EVENT = 1 << 4,
- MONSTER_DAMAGE_EVENT = 1 << 5
+ MONSTER_DAMAGE_EVENT = 1 << 5,
+ PN_FADE_EVENT = 1 << 6
};
struct GameSpecificSettings;
@@ -625,6 +626,13 @@ protected:
uint8 _simon2LanguageFlagTimer;
bool _simon2LanguageFlagClearPending;
+ uint16 _pnPaletteBanks[2][16];
+ uint16 _pnFadeCurrent[16];
+ uint16 _pnDayNightControllerSelectorMask;
+ uint8 _pnDayNightControllerLastStage;
+ int16 _pnLastClockMinutes;
+ bool _pnHavePaletteBank[2];
+ uint16 _pnDayNightControllerTickCounter;
byte *_planarBuf;
byte _videoBuf1[32000];
uint16 _videoWindows[128];
@@ -1303,6 +1311,16 @@ protected:
void clearVideoBackGround(uint16 windowNum, uint16 color);
void setPaletteSlot(uint16 srcOffs, uint8 dstOffs);
+ bool isPNDayNightPaletteMode() const;
+ uint8 getPNDesiredPaletteBank() const;
+ void notePNClockValueChange();
+ void resetPNRoomPaletteState();
+ void buildPNPaletteTarget(uint16 selectorMask, uint16 *target) const;
+ uint16 blendPNPaletteColor(uint16 source, uint16 target, uint8 steps) const;
+ uint8 getPNDayNightControllerStage() const;
+ void startPNDayNightController(uint16 selectorMask);
+ void updatePNDayNightController(uint16 selectorMask);
+ void applyPNDayNightPalette(const uint16 *palette);
void checkOnStopTable();
void checkWaitEndTable();
@@ -1316,6 +1334,8 @@ protected:
void addVgaEvent(uint16 num, uint8 type, const byte *codePtr, uint16 curSprite, uint16 curZoneNum);
void deleteVgaEvent(VgaTimerEntry * vte);
void processVgaEvents();
+ void schedulePNFadeEvent();
+ void removePNFadeEvent();
void animateEvent(const byte *codePtr, uint16 curZoneNum, uint16 curSprite);
void scrollEvent();
void drawStuff(const byte *src, uint offs);
@@ -1677,6 +1697,9 @@ protected:
void addChar(uint8 chr);
void clearCursor(WindowBlock *window);
void clearInputLine();
+ bool tryHandleDebugTimeCommand();
+ bool tryParseDebugTimeCommand(const char *typed, uint16 &hour, uint16 &minute) const;
+ void setDebugTime(uint16 hour, uint16 minute);
void handleKeyboard();
void handleMouseMoved() override;
void interact(char *buffer, uint8 size);
diff --git a/engines/agos/debugger.cpp b/engines/agos/debugger.cpp
index 2edc8bb3888..5870a8ce3fe 100644
--- a/engines/agos/debugger.cpp
+++ b/engines/agos/debugger.cpp
@@ -45,6 +45,15 @@ Debugger::Debugger(AGOSEngine *vm)
registerCmd("dumpimage", WRAP_METHOD(Debugger, Cmd_dumpImage));
registerCmd("dumpscript", WRAP_METHOD(Debugger, Cmd_dumpScript));
+ if (_vm->getGameType() == GType_PN) {
+ registerCmd("gettime", WRAP_METHOD(Debugger, Cmd_GetTime));
+
+ if (_vm->getPlatform() == Common::kPlatformAmiga || _vm->getPlatform() == Common::kPlatformAtariST) {
+ registerCmd("startday", WRAP_METHOD(Debugger, Cmd_StartDay));
+ registerCmd("startnight", WRAP_METHOD(Debugger, Cmd_StartNight));
+ }
+ }
+
}
bool Debugger::Cmd_PlayMusic(int argc, const char **argv) {
@@ -265,4 +274,25 @@ bool Debugger::Cmd_dumpScript(int argc, const char **argv) {
return true;
}
+
+bool Debugger::setPNDebugTime(uint16 hour, uint16 minute) {
+ _vm->writeVariable(84, hour);
+ _vm->writeVariable(85, minute);
+ debugPrintf("Set time to %02u:%02u\n", hour, minute);
+ return true;
+}
+
+bool Debugger::Cmd_GetTime(int argc, const char **argv) {
+ debugPrintf("Time is %02u:%02u\n", (uint)_vm->readVariable(84), (uint)_vm->readVariable(85));
+ return true;
+}
+
+bool Debugger::Cmd_StartDay(int argc, const char **argv) {
+ return setPNDebugTime(6, 58);
+}
+
+bool Debugger::Cmd_StartNight(int argc, const char **argv) {
+ return setPNDebugTime(17, 58);
+}
+
} // End of namespace AGOS
diff --git a/engines/agos/debugger.h b/engines/agos/debugger.h
index 783d809bcb8..edbc9d9f994 100644
--- a/engines/agos/debugger.h
+++ b/engines/agos/debugger.h
@@ -46,6 +46,10 @@ private:
bool Cmd_StartSubroutine(int argc, const char **argv);
bool Cmd_dumpImage(int argc, const char **argv);
bool Cmd_dumpScript(int argc, const char **argv);
+ bool Cmd_GetTime(int argc, const char **argv);
+ bool Cmd_StartDay(int argc, const char **argv);
+ bool Cmd_StartNight(int argc, const char **argv);
+ bool setPNDebugTime(uint16 hour, uint16 minute);
};
} // End of namespace AGOS
diff --git a/engines/agos/event.cpp b/engines/agos/event.cpp
index b1b84baf9c0..785c692e2c5 100644
--- a/engines/agos/event.cpp
+++ b/engines/agos/event.cpp
@@ -230,6 +230,27 @@ void AGOSEngine::deleteVgaEvent(VgaTimerEntry * vte) {
_videoLockOut &= ~1;
}
+void AGOSEngine::schedulePNFadeEvent() {
+ if (!isPNDayNightPaletteMode())
+ return;
+
+ for (VgaTimerEntry *vte = _vgaTimerList; vte->delay; ++vte) {
+ if (vte->type == PN_FADE_EVENT)
+ return;
+ }
+
+ addVgaEvent(_vgaBaseDelay, PN_FADE_EVENT, nullptr, 0, 0);
+}
+
+void AGOSEngine::removePNFadeEvent() {
+ for (VgaTimerEntry *vte = _vgaTimerList; vte->delay; ++vte) {
+ if (vte->type == PN_FADE_EVENT) {
+ deleteVgaEvent(vte);
+ return;
+ }
+ }
+}
+
void AGOSEngine::processVgaEvents() {
VgaTimerEntry *vte = _vgaTimerList;
@@ -246,6 +267,8 @@ void AGOSEngine::processVgaEvents() {
case ANIMATE_INT:
vte->delay = (getGameType() == GType_SIMON2) ? 5 : _frameCount;
animateSprites();
+ if (isPNDayNightPaletteMode())
+ schedulePNFadeEvent();
vte++;
break;
case ANIMATE_EVENT:
@@ -268,6 +291,20 @@ void AGOSEngine::processVgaEvents() {
monsterDamageEvent(vte, curZoneNum);
vte = _nextVgaTimerToProcess;
break;
+ case PN_FADE_EVENT:
+ if (isPNDayNightPaletteMode()) {
+ if (_pnDayNightControllerTickCounter > _vgaBaseDelay) {
+ _pnDayNightControllerTickCounter -= _vgaBaseDelay;
+ } else {
+ _pnDayNightControllerTickCounter = 0x00C8;
+ updatePNDayNightController(_pnDayNightControllerSelectorMask);
+ }
+ }
+
+ _nextVgaTimerToProcess = vte + 1;
+ deleteVgaEvent(vte);
+ vte = _nextVgaTimerToProcess;
+ break;
default:
error("processVgaEvents: Unknown event type %d", vte->type);
}
diff --git a/engines/agos/script.cpp b/engines/agos/script.cpp
index 31bdeac3415..9d8fb351528 100644
--- a/engines/agos/script.cpp
+++ b/engines/agos/script.cpp
@@ -970,10 +970,19 @@ void AGOSEngine::writeVariable(uint16 variable, uint16 contents) {
if (variable >= _numVars)
error("writeVariable: Variable %d out of range", variable);
+ const bool pnDayNightMode = isPNDayNightPaletteMode();
+ const uint16 oldValue = _variableArray[variable];
+ const bool pnDayNightVar = pnDayNightMode && variable == 249 && oldValue != contents;
+
if (getGameType() == GType_FF && getBitFlag(83))
_variableArray2[variable] = contents;
else
_variableArray[variable] = contents;
+
+ if (pnDayNightVar) {
+ const uint16 selectorMask = (contents == 2) ? 0xFFFF : 0x0000;
+ startPNDayNightController(selectorMask);
+ }
}
int AGOSEngine::runScript() {
diff --git a/engines/agos/vga.cpp b/engines/agos/vga.cpp
index 7d3f96f538e..cf01a1c5e33 100644
--- a/engines/agos/vga.cpp
+++ b/engines/agos/vga.cpp
@@ -289,6 +289,9 @@ uint AGOSEngine::vcReadVar(uint var) {
void AGOSEngine::vcWriteVar(uint var, int16 value) {
assert(var < _numVars);
_variableArrayPtr[var] = value;
+
+ if (isPNDayNightPaletteMode() && (var == 84 || var == 85))
+ notePNClockValueChange();
}
void AGOSEngine::vcSkipNextInstruction() {
@@ -388,6 +391,181 @@ void AGOSEngine::vcSkipNextInstruction() {
debugCN(kDebugVGAOpcode, "; skipped\n");
}
+bool AGOSEngine::isPNDayNightPaletteMode() const {
+ return getGameType() == GType_PN && !(getFeatures() & GF_EGA) &&
+ (getPlatform() == Common::kPlatformAtariST || getPlatform() == Common::kPlatformAmiga);
+}
+
+uint8 AGOSEngine::getPNDesiredPaletteBank() const {
+ if (!isPNDayNightPaletteMode())
+ return 0;
+
+ if (_variableArray != nullptr && _numVars > 85) {
+ const int16 hour = _variableArray[84];
+ const int16 minute = _variableArray[85];
+ if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60) {
+ if (!(_pnLastClockMinutes == -1 && hour == 0 && minute == 0))
+ return (hour >= 23 || hour < 7) ? 1 : 0;
+ }
+ }
+
+ if (_itemArrayPtr != nullptr && _itemArraySize > 289) {
+ const Item *item196 = _itemArrayPtr[196];
+ const Item *item289 = _itemArrayPtr[289];
+ const bool nightState = (item196 && item196->state == 7) || (item289 && item289->state == 7);
+ const bool dayState = (item196 && item196->state == 27) || (item289 && item289->state == 27);
+
+ if (nightState && !dayState)
+ return 1;
+ if (dayState && !nightState)
+ return 0;
+ }
+
+ return 0;
+}
+
+void AGOSEngine::notePNClockValueChange() {
+ if (!isPNDayNightPaletteMode() || _variableArray == nullptr || _numVars <= 85)
+ return;
+
+ int16 hour = _variableArray[84];
+ int16 minute = _variableArray[85];
+ if (hour < 0 || minute < 0)
+ return;
+
+ if (minute >= 60) {
+ hour += minute / 60;
+ minute %= 60;
+ _variableArray[84] = hour;
+ _variableArray[85] = minute;
+ }
+
+ if (hour >= 24) {
+ hour %= 24;
+ _variableArray[84] = hour;
+ }
+
+ const int16 clockMinutes = hour * 60 + minute;
+ if (_pnLastClockMinutes == -1) {
+ _pnLastClockMinutes = clockMinutes;
+ return;
+ }
+
+ if (clockMinutes != _pnLastClockMinutes)
+ _pnLastClockMinutes = clockMinutes;
+}
+
+void AGOSEngine::resetPNRoomPaletteState() {
+ if (!isPNDayNightPaletteMode())
+ return;
+
+ memset(_pnHavePaletteBank, 0, sizeof(_pnHavePaletteBank));
+ _pnDayNightControllerSelectorMask = 0xFFFF;
+ _pnDayNightControllerLastStage = 0xFF;
+ _pnDayNightControllerTickCounter = 0x00C8;
+ removePNFadeEvent();
+}
+
+void AGOSEngine::buildPNPaletteTarget(uint16 selectorMask, uint16 *target) const {
+ for (int i = 0; i < 16; ++i) {
+ const bool useBank1 = (selectorMask & (0x8000 >> i)) != 0;
+ if (useBank1 && _pnHavePaletteBank[1]) {
+ target[i] = _pnPaletteBanks[1][i];
+ } else if (_pnHavePaletteBank[0]) {
+ target[i] = _pnPaletteBanks[0][i];
+ } else if (_pnHavePaletteBank[1]) {
+ target[i] = _pnPaletteBanks[1][i];
+ } else {
+ target[i] = _pnFadeCurrent[i];
+ }
+ }
+}
+
+uint16 AGOSEngine::blendPNPaletteColor(uint16 source, uint16 target, uint8 steps) const {
+ uint16 current = source;
+
+ for (uint8 step = 0; step < steps; ++step) {
+ const uint16 currentRed = current & 0x0f00;
+ const uint16 targetRed = target & 0x0f00;
+ if (currentRed < targetRed)
+ current += 0x0100;
+ else if (currentRed > targetRed)
+ current -= 0x0100;
+
+ const uint16 currentGreen = current & 0x00f0;
+ const uint16 targetGreen = target & 0x00f0;
+ if (currentGreen < targetGreen)
+ current += 0x0010;
+ else if (currentGreen > targetGreen)
+ current -= 0x0010;
+
+ const uint16 currentBlue = current & 0x000f;
+ const uint16 targetBlue = target & 0x000f;
+ if (currentBlue < targetBlue)
+ current += 0x0001;
+ else if (currentBlue > targetBlue)
+ current -= 0x0001;
+ }
+
+ return current;
+}
+
+uint8 AGOSEngine::getPNDayNightControllerStage() const {
+ if (!isPNDayNightPaletteMode() || _variableArray == nullptr || _numVars <= 212)
+ return 0;
+
+ return (uint8)(_variableArray[212] & 7);
+}
+
+void AGOSEngine::startPNDayNightController(uint16 selectorMask) {
+ if (!isPNDayNightPaletteMode())
+ return;
+
+ _pnDayNightControllerSelectorMask = selectorMask;
+ _pnDayNightControllerLastStage = 0xFF;
+ _pnDayNightControllerTickCounter = 0x00C8;
+ updatePNDayNightController(selectorMask);
+ schedulePNFadeEvent();
+}
+
+void AGOSEngine::updatePNDayNightController(uint16 selectorMask) {
+ if (!isPNDayNightPaletteMode())
+ return;
+ if (!_pnHavePaletteBank[0] || !_pnHavePaletteBank[1])
+ return;
+
+ const uint8 stage = getPNDayNightControllerStage();
+ if (stage == _pnDayNightControllerLastStage)
+ return;
+
+ uint16 fadeTarget[16];
+ buildPNPaletteTarget(selectorMask, fadeTarget);
+ for (int i = 0; i < 16; ++i)
+ _pnFadeCurrent[i] = blendPNPaletteColor(_pnPaletteBanks[0][i], fadeTarget[i], stage);
+
+ _pnDayNightControllerLastStage = stage;
+ applyPNDayNightPalette(_pnFadeCurrent);
+}
+
+void AGOSEngine::applyPNDayNightPalette(const uint16 *palette) {
+ for (int i = 0; i < 16; ++i) {
+ const uint16 color = palette[i];
+ _displayPalette[i * 3 + 0] = ((color & 0x0f00) >> 8) * 32;
+ _displayPalette[i * 3 + 1] = ((color & 0x00f0) >> 4) * 32;
+ _displayPalette[i * 3 + 2] = ((color & 0x000f) >> 0) * 32;
+ }
+
+ memcpy(_currentPalette, _displayPalette, sizeof(_displayPalette));
+
+ const bool canPushImmediately = isPNDayNightPaletteMode() && _pnHavePaletteBank[0] && _pnHavePaletteBank[1] && _system->getPaletteManager();
+ if (canPushImmediately) {
+ _system->getPaletteManager()->setPalette(_displayPalette, 0, 16);
+ _paletteFlag = 0;
+ } else {
+ _paletteFlag = 1;
+ }
+}
+
// VGA Script commands
void AGOSEngine::vc1_fadeOut() {
/* dummy opcode */
@@ -443,8 +621,39 @@ void AGOSEngine::vc3_loadSprite() {
}
void AGOSEngine::vc4_fadeIn() {
- /* dummy opcode */
- _vcPtr += 6;
+ if (!isPNDayNightPaletteMode()) {
+ _vcPtr += 6;
+ return;
+ }
+
+ const uint16 selectorMask = vcReadNextWord();
+ vcReadNextWord();
+ const int16 fadeMode = (int16)vcReadNextWord();
+
+ const bool hasAlternateBank = _pnHavePaletteBank[1];
+ const bool isDayPalette = selectorMask == 0;
+
+ if (!hasAlternateBank) {
+ if (_pnHavePaletteBank[0]) {
+ memcpy(_pnFadeCurrent, _pnPaletteBanks[0], sizeof(_pnFadeCurrent));
+ }
+ return;
+ }
+
+ const bool shouldApply = (getPNDesiredPaletteBank() == 0) ? isDayPalette : !isDayPalette;
+
+ if (fadeMode == -1) {
+ startPNDayNightController(selectorMask);
+ return;
+ }
+
+ if (!shouldApply)
+ return;
+
+ uint16 fadeTarget[16];
+ buildPNPaletteTarget(selectorMask, fadeTarget);
+ memcpy(_pnFadeCurrent, fadeTarget, sizeof(_pnFadeCurrent));
+ applyPNDayNightPalette(_pnFadeCurrent);
}
void AGOSEngine::vc5_ifEqual() {
@@ -944,6 +1153,8 @@ void AGOSEngine::vc22_setPalette() {
byte *offs, *palptr, *src;
uint16 b, num;
const uint16 origB = b = vcReadNextWord();
+ uint16 pnPalette[16];
+ const bool pnDayNightPaletteMode = isPNDayNightPaletteMode();
// PC EGA version of Personal Nightmare uses standard EGA palette
if (getGameType() == GType_PN && (getFeatures() & GF_EGA))
@@ -954,10 +1165,14 @@ void AGOSEngine::vc22_setPalette() {
palptr = _displayPalette;
_bottomPalette = 1;
+ uint8 pnBank = 0;
if (getGameType() == GType_PN) {
+ if (origB > 128)
+ pnBank = 1;
if (b > 128) {
b -= 128;
- palptr = _displayPalette + 3 * 16;
+ if (!pnDayNightPaletteMode)
+ palptr = _displayPalette + 3 * 16;
}
} else if (getGameType() == GType_ELVIRA1) {
if (b >= 1000) {
@@ -1000,22 +1215,42 @@ void AGOSEngine::vc22_setPalette() {
offs = _curVgaFile1 + READ_BE_UINT16(_curVgaFile1 + 6);
src = offs + b * 32;
+ uint pnPaletteIndex = 0;
do {
uint16 color = READ_BE_UINT16(src);
- if (getGameType() == GType_ELVIRA2 && getPlatform() == Common::kPlatformAtariST) {
- palptr[0] = atariSTColorNibbleToComponent((color & 0xf00) >> 8);
- palptr[1] = atariSTColorNibbleToComponent((color & 0x0f0) >> 4);
- palptr[2] = atariSTColorNibbleToComponent((color & 0x00f) >> 0);
- } else {
- palptr[0] = ((color & 0xf00) >> 8) * 32;
- palptr[1] = ((color & 0x0f0) >> 4) * 32;
- palptr[2] = ((color & 0x00f) >> 0) * 32;
+ if (pnDayNightPaletteMode)
+ pnPalette[pnPaletteIndex] = color;
+
+ if (!pnDayNightPaletteMode || pnBank == 0) {
+ if (getGameType() == GType_ELVIRA2 && getPlatform() == Common::kPlatformAtariST) {
+ palptr[0] = atariSTColorNibbleToComponent((color & 0xf00) >> 8);
+ palptr[1] = atariSTColorNibbleToComponent((color & 0x0f0) >> 4);
+ palptr[2] = atariSTColorNibbleToComponent((color & 0x00f) >> 0);
+ } else {
+ palptr[0] = ((color & 0xf00) >> 8) * 32;
+ palptr[1] = ((color & 0x0f0) >> 4) * 32;
+ palptr[2] = ((color & 0x00f) >> 0) * 32;
+ }
+ palptr += 3;
}
- palptr += 3;
src += 2;
+ pnPaletteIndex++;
} while (--num);
+ if (pnDayNightPaletteMode) {
+ while (pnPaletteIndex < 16)
+ pnPalette[pnPaletteIndex++] = 0;
+
+ memcpy(_pnPaletteBanks[pnBank], pnPalette, sizeof(pnPalette));
+ _pnHavePaletteBank[pnBank] = true;
+
+ if (pnBank == 0) {
+ memcpy(_pnFadeCurrent, _pnPaletteBanks[0], sizeof(_pnFadeCurrent));
+ }
+
+ }
+
if (getGameType() == GType_PN && origB == 9 &&
(getPlatform() == Common::kPlatformAtariST || getPlatform() == Common::kPlatformAmiga)) {
// Workaround for Atari ST and Amiga 'time passes' screen palette.
More information about the Scummvm-git-logs
mailing list