[Scummvm-git-logs] scummvm master -> ddfbc182ef404711cc508543e10a687038825dab
neuromancer
noreply at scummvm.org
Wed Jun 3 12:33:49 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:
a3cd1a9917 SCUMM: RA2: avoid incorrect palettes in transition frames
bfe931de30 SCUMM: RA2: correctly center input when a new gameplay section starts
ddfbc182ef SCUMM: RA2: refactor nutcracker/font code to isolate it from the rest of the scumm games
Commit: a3cd1a99175cef34d0830c125f79cf40405cd047
https://github.com/scummvm/scummvm/commit/a3cd1a99175cef34d0830c125f79cf40405cd047
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T14:31:53+02:00
Commit Message:
SCUMM: RA2: avoid incorrect palettes in transition frames
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index af30f749a81..cb34ed8fffb 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -1236,43 +1236,9 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
_player->_fobjOffsetY = _rebelViewOffsetY;
}
- // Draw corridor overlay OPAQUELY (FUN_00428A10 in original, line 216).
+ // Draw corridor overlay opaquely (FUN_00428a10 in original, line 216).
// This wipes previous frame content so codec 23 delta skip regions show clean corridor.
- if (renderBitmap) {
- EmbeddedSanFrame &corridorOverlay = _rebelEmbeddedHud[4];
- if (corridorOverlay.valid && corridorOverlay.pixels) {
- int pitch = (_player && _player->_width > 0) ? _player->_width : 320;
- int bufHeight = (_player && _player->_height > 0) ? _player->_height : 200;
-
- int srcOffsetX = 0;
- int srcOffsetY = 0;
- int destX = _rebelViewOffsetX;
- int destY = _rebelViewOffsetY;
- int drawWidth = corridorOverlay.width;
- int drawHeight = corridorOverlay.height;
-
- if (destX < 0) { srcOffsetX = -destX; drawWidth -= srcOffsetX; destX = 0; }
- if (destY < 0) { srcOffsetY = -destY; drawHeight -= srcOffsetY; destY = 0; }
- if (destX + drawWidth > pitch)
- drawWidth = pitch - destX;
- if (destY + drawHeight > bufHeight)
- drawHeight = bufHeight - destY;
- if (drawWidth > corridorOverlay.width - srcOffsetX)
- drawWidth = corridorOverlay.width - srcOffsetX;
- if (drawHeight > corridorOverlay.height - srcOffsetY)
- drawHeight = corridorOverlay.height - srcOffsetY;
-
- if (drawWidth > 0 && drawHeight > 0) {
- for (int y = 0; y < drawHeight; y++) {
- memcpy(renderBitmap + (destY + y) * pitch + destX,
- corridorOverlay.pixels + (srcOffsetY + y) * corridorOverlay.width + srcOffsetX,
- drawWidth);
- }
- }
- debug("Rebel2 Opcode 6: Corridor overlay drawn at (%d,%d) size(%d,%d)",
- _rebelViewOffsetX, _rebelViewOffsetY, corridorOverlay.width, corridorOverlay.height);
- }
- }
+ drawHandler25CorridorOverlay(renderBitmap);
}
// ScummVM refactor helper for opcode 6 Handler 0x26, not a separate retail function.
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index c4ed62eb146..c51f4dfa214 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -765,6 +765,7 @@ public:
// Render a decoded embedded frame to the video buffer
// Handles transparency (color 0 and 231) and boundary checks
void renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFrame &frame, int userId);
+ void drawHandler25CorridorOverlay(byte *renderBitmap);
int16 _rebelLinks[512][3]; // Dependency links: Slot 0 (Disable on death), Slot 1/2 (Enable on death)
void clearBit(int n);
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index e5a373e253f..890a19c9166 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -132,13 +132,18 @@ void InsaneRebel2::renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFram
_rebelHandler == 0x26 || _rebelHandler == 0x19);
// Handler 25 overlays:
- // - userId 4 (corridor overlay): Draw during procPostRendering at view offset, NOT immediately
+ // - userId 4 (corridor overlay): draw immediately at the current view offset.
+ // FUN_0041cadb case 6/par4=4 decodes DAT_00482268, then calls
+ // FUN_00428a10(param_1, 0, DAT_0045790c, DAT_0045790e, DAT_00482268).
// - userId 6, 7 (static overlays): Draw immediately (they don't move)
+ if (_rebelHandler == 0x19 && userId == 4) {
+ drawHandler25CorridorOverlay(renderBitmap);
+ return;
+ }
if (_rebelHandler == 0x19 && (userId == 6 || userId == 7)) {
skipImmediateDraw = false;
debug("Rebel2: Handler 25 static overlay userId=%d - forcing immediate draw", userId);
}
- // userId 4 should NOT draw immediately - it will be drawn at view offset each frame
if (!frame.valid || !renderBitmap || skipImmediateDraw) {
if (skipImmediateDraw && frame.valid) {
@@ -156,6 +161,56 @@ void InsaneRebel2::renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFram
debug("Rebel2: Rendered embedded HUD %d at (%d,%d)", userId, frame.renderX, frame.renderY);
}
+void InsaneRebel2::drawHandler25CorridorOverlay(byte *renderBitmap) {
+ if (!renderBitmap)
+ return;
+
+ EmbeddedSanFrame &corridorOverlay = _rebelEmbeddedHud[4];
+ if (!isValidEmbeddedFrame(corridorOverlay))
+ return;
+
+ int pitch = (_player && _player->_width > 0) ? _player->_width : 320;
+ int bufHeight = (_player && _player->_height > 0) ? _player->_height : 200;
+
+ int srcOffsetX = 0;
+ int srcOffsetY = 0;
+ int destX = _rebelViewOffsetX;
+ int destY = _rebelViewOffsetY;
+ int drawWidth = corridorOverlay.width;
+ int drawHeight = corridorOverlay.height;
+
+ if (destX < 0) {
+ srcOffsetX = -destX;
+ drawWidth -= srcOffsetX;
+ destX = 0;
+ }
+ if (destY < 0) {
+ srcOffsetY = -destY;
+ drawHeight -= srcOffsetY;
+ destY = 0;
+ }
+ if (destX + drawWidth > pitch)
+ drawWidth = pitch - destX;
+ if (destY + drawHeight > bufHeight)
+ drawHeight = bufHeight - destY;
+ if (drawWidth > corridorOverlay.width - srcOffsetX)
+ drawWidth = corridorOverlay.width - srcOffsetX;
+ if (drawHeight > corridorOverlay.height - srcOffsetY)
+ drawHeight = corridorOverlay.height - srcOffsetY;
+
+ if (drawWidth <= 0 || drawHeight <= 0)
+ return;
+
+ for (int y = 0; y < drawHeight; y++) {
+ memcpy(renderBitmap + (destY + y) * pitch + destX,
+ corridorOverlay.pixels + (srcOffsetY + y) * corridorOverlay.width + srcOffsetX,
+ drawWidth);
+ }
+
+ debug("Rebel2 Handler25: Corridor overlay drawn at (%d,%d) size(%d,%d)",
+ _rebelViewOffsetX, _rebelViewOffsetY, corridorOverlay.width, corridorOverlay.height);
+}
+
//
// loadEmbeddedSan -- Decode an embedded SAN (ANIM/FOBJ) from IACT opcode 8 data.
//
@@ -2866,10 +2921,9 @@ void InsaneRebel2::renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int
if (!isValidEmbeddedFrame(frame))
continue;
- // Handler 25: Skip slot 4 (corridor overlay) in post-rendering.
- // The corridor is a full background image (no color 0 transparent center).
- // Drawing it here would cover enemies. It's already drawn in procPreRendering
- // with transparency to preserve frame persistence for codec 23 delta.
+ // Handler 25: skip slot 4 (corridor overlay) in post-rendering.
+ // FUN_0041cadb draws it opaquely from opcode 6 and immediately after
+ // loading par4=4; drawing it here would cover enemies.
if (_rebelHandler == 25 && hudSlot == 4) {
continue;
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index e24b8f56d3c..58d1c3fdf18 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -89,6 +89,7 @@ void SmushPlayerRebel2::initGamePlayerFields() {
_ra2FrameSourceSkipY = 0;
_ra2FrameObjectSurfaceWidth = 0;
_ra2FrameObjectSurfaceHeight = 0;
+ _ra2PendingAnimHeaderPalette = false;
_scrollX = 0;
_scrollY = 0;
}
@@ -106,10 +107,12 @@ void SmushPlayerRebel2::destroyGamePlayerFields() {
/**
* RA2-specific initialization in SmushPlayer::init().
- * Re-pushes the SMUSH palette (videos without NPAL inherit from previous),
- * and handles background preservation between cinematic and gameplay videos.
+ * Re-pushes the SMUSH palette for videos that inherit it from the previous video,
+ * and initializes the screen target state before AHDR/FOBJ selects dimensions.
*/
void SmushPlayerRebel2::initGameVideoState() {
+ _ra2PendingAnimHeaderPalette = false;
+
// Re-push the SMUSH palette to the system. Videos like O_LEVEL.SAN
// have no NPAL chunk and inherit the palette from the previous video.
// Since play() resets _palDirtyMin/Max, the palette would never be pushed otherwise.
@@ -120,16 +123,15 @@ void SmushPlayerRebel2::initGameVideoState() {
_width = _vm->_screenWidth;
_height = _vm->_screenHeight;
- // Handle background preservation between videos:
- // - Cinematic videos (flags 0x20) clear the buffer for a fresh start
- // - Gameplay videos (flags 0x08) preserve the existing screen content
+ // Keep ScummVM's virtual screen consistent with FUN_00424d70: bit 0x20
+ // controls per-frame clearing. Videos that clear every frame can also start
+ // from a cleared target; videos with bit 0x20 set preserve until their first
+ // decoded frame overwrites or composes over it.
if (_dst != nullptr) {
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
- if ((_curVideoFlags & 0x08) == 0) {
- // Cinematic mode (flags 0x20) - clear buffer for fresh video
+ if ((_curVideoFlags & 0x20) == 0) {
memset(_dst, 0, vs->w * vs->h);
}
- // Gameplay mode: no-op, the existing screen content is preserved.
}
}
@@ -397,8 +399,9 @@ void SmushPlayerRebel2::adjustGamePalette() {
}
bool SmushPlayerRebel2::shouldLoadAnimHeaderPalette() const {
- // Original RA2 AHDR handler skips the embedded palette when video flag 0x400 is set.
- return (_curVideoFlags & 0x400) == 0;
+ // RA2 copies the AHDR palette in handleGameAnimHeader() so it can match
+ // FUN_00424640/FUN_00424540 timing without changing the generic player.
+ return false;
}
void SmushPlayerRebel2::ra2HandleDeltaPalette(int32 subSize, Common::SeekableReadStream &b) {
@@ -450,6 +453,20 @@ void SmushPlayerRebel2::ra2HandleDeltaPalette(int32 subSize, Common::SeekableRea
// backing bitmap, but active low-res gameplay dimensions are selected later by
// IACT/FOBJ state; keep the virtual-screen pitch safe until then.
bool SmushPlayerRebel2::handleGameAnimHeader(byte *headerContent) {
+ if (!_skipPalette && (_curVideoFlags & 0x400) == 0) {
+ memcpy(_pal, headerContent + 6, sizeof(_pal));
+ adjustGamePalette();
+
+ // initGameVideoState() marks the previous palette dirty for videos that
+ // inherit it. AHDR replaces _pal before the first FRME, so clear that
+ // inherited dirty range and delay the new AHDR palette until frame start.
+ // Original RA2 marks it dirty from FUN_00424640, then applies it from
+ // FUN_00424540 after FUN_00424d70 has processed a frame.
+ _palDirtyMin = 256;
+ _palDirtyMax = -1;
+ _ra2PendingAnimHeaderPalette = true;
+ }
+
int width = READ_LE_UINT16(&headerContent[4]);
int height = READ_LE_UINT16(&headerContent[6]);
@@ -838,6 +855,11 @@ void SmushPlayerRebel2::handleGameFrameObjectPost(int codec, const byte *data, i
void SmushPlayerRebel2::handleGameFrameStart() {
_hasFrameFobjForGost = false;
+ if (_ra2PendingAnimHeaderPalette) {
+ setDirtyColors(0, 255);
+ _ra2PendingAnimHeaderPalette = false;
+ }
+
// FUN_00424d70 clears the target buffer before decoding a frame
// unless playback flags contain 0x20. Codec 21 frames in levels like
// LEV05/05PLAY.SAN only contain non-zero literals, so stale skipped pixels
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index c10a544cb76..415401af885 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -85,6 +85,7 @@ private:
int _ra2FrameSourceSkipY;
int _ra2FrameObjectSurfaceWidth;
int _ra2FrameObjectSurfaceHeight;
+ bool _ra2PendingAnimHeaderPalette;
};
} // End of namespace Scumm
Commit: bfe931de30a8d0c3d36a9c9c490a1828a400812d
https://github.com/scummvm/scummvm/commit/bfe931de30a8d0c3d36a9c9c490a1828a400812d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T14:31:53+02:00
Commit Message:
SCUMM: RA2: correctly center input when a new gameplay section starts
Changed paths:
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/runlevels.cpp
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index 63d49134bba..8b010dca334 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "common/events.h"
#include "common/system.h"
#include "graphics/cursorman.h"
@@ -34,6 +35,15 @@ namespace Scumm {
static const int kRebel2GameplayAimCenterX = 160;
static const int kRebel2GameplayAimCenterY = 100;
+static void purgeRebel2GameplayInputEvents(Common::EventManager *eventMan) {
+ if (!eventMan)
+ return;
+
+ eventMan->getEventDispatcher()->clearEvents();
+ eventMan->purgeMouseEvents();
+ eventMan->purgeKeyboardEvents();
+}
+
// ---------------------------------------------------------------------------
// Level Loading System
// ---------------------------------------------------------------------------
@@ -174,6 +184,7 @@ void InsaneRebel2::playMissionBriefing() {
// Resets handler to 0 (no HUD) and sets flags to 0x28 (cinematic + buffer preserve).
// All wrapper functions (FUN_00417168/4171c5/417ab2/417327) add | 8 before calling FUN_0041f4d0.
void InsaneRebel2::playCinematic(const char *filename) {
+ _gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // No status bar during cinematics
@@ -188,6 +199,7 @@ void InsaneRebel2::playCinematic(const char *filename) {
void InsaneRebel2::playVideoWithText(const char *filename, int textID, int textX, int textY,
int fadeInFrame, int fadeOutFrame) {
+ _gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
@@ -258,6 +270,7 @@ void InsaneRebel2::playLevelBegin(int levelId) {
// playLevelEnd -- Level completion video (FUN_00417327).
void InsaneRebel2::playLevelEnd(int levelId) {
+ _gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // No status bar during end cinematic
@@ -276,6 +289,7 @@ void InsaneRebel2::playLevelEnd(int levelId) {
// playLevelRetry -- Retry prompt video (LEVXX/XXRETRY.SAN, FUN_00417168).
void InsaneRebel2::playLevelRetry(int levelId) {
+ _gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // Reset for retry - will be set by IACT opcode 6 if needed
@@ -294,6 +308,7 @@ void InsaneRebel2::playLevelRetry(int levelId) {
// playLevelGameOver -- Game over video (FUN_00417ab2).
void InsaneRebel2::playLevelGameOver(int levelId) {
+ _gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // No status bar during game over cinematic
@@ -367,6 +382,9 @@ void InsaneRebel2::playCreditsSequence() {
}
void InsaneRebel2::centerGameplayAim() {
+ Common::EventManager *eventMan = _vm->_system->getEventManager();
+ purgeRebel2GameplayInputEvents(eventMan);
+
_vm->_mouse.x = kRebel2GameplayAimCenterX;
_vm->_mouse.y = kRebel2GameplayAimCenterY;
@@ -375,6 +393,7 @@ void InsaneRebel2::centerGameplayAim() {
_gamepadAimActive = false;
smush_warpMouse(kRebel2GameplayAimCenterX, kRebel2GameplayAimCenterY, -1);
+ purgeRebel2GameplayInputEvents(eventMan);
}
// runLevel -- Main level dispatcher, calls per-level handlers.
@@ -414,7 +433,7 @@ int InsaneRebel2::runLevel(int levelId) {
// The original hides the cursor (ShowCursor(0)) and relies on Windows confining
// the mouse to the game window. Without locking, the cursor can escape the
// ScummVM window making the ship uncontrollable.
- centerGameplayAim();
+ _gameplaySectionActive = false;
CursorMan.showMouse(false);
g_system->lockMouse(true);
@@ -677,6 +696,7 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
// playLevelDeathVariant -- Death video with variant selection.
void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
+ _gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // No status bar during death cinematic
@@ -703,6 +723,7 @@ void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
// playLevelRetryVariant -- Phase-specific retry video.
void InsaneRebel2::playLevelRetryVariant(int levelId, int phase) {
+ _gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // Reset for retry - will be set by IACT opcode 6 if needed
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index d8dfa9bc0b0..1e5f241014c 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -519,6 +519,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_joystickAxisX = 0;
_joystickAxisY = 0;
_gamepadAimActive = false;
+ _gameplaySectionActive = false;
_lastGameplayMenuCloseTime = 0;
_lastMenuGamepadNavigationTime = 0;
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index c51f4dfa214..d833df9de23 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -468,6 +468,8 @@ public:
// Reset gameplay aim to the original centered mouse/joystick baseline.
void centerGameplayAim();
+ // Tracks consecutive recorded gameplay SANs so wave-loop videos do not recenter aim.
+ bool _gameplaySectionActive;
// Level state tracking for multi-phase levels
int _currentPhase; // Current gameplay phase (1, 2, 3 for Level 2; 1, 2 for Level 3/6)
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 77f5c4cbdb0..63dc995ce41 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -191,11 +191,15 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
bool InsaneRebel2::playLevelSegment(const char *filename, uint16 flags, bool recordFrame) {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Retail reset helpers clear the centered input axes before gameplay starts.
- // Do the same for playable segments, but keep seamless continuation videos
- // (0x40, e.g. 13PLAY_B) from snapping the aim point mid-sequence.
- if (recordFrame && (flags & 0x08) != 0 && (flags & 0x40) == 0)
- centerGameplayAim();
+ const bool isRecordedGameplay = recordFrame && (flags & 0x08) != 0;
+ if (isRecordedGameplay) {
+ // Center only at the section boundary; looped wave videos are continuations.
+ if (!_gameplaySectionActive && (flags & 0x40) == 0)
+ centerGameplayAim();
+ _gameplaySectionActive = true;
+ } else {
+ _gameplaySectionActive = false;
+ }
splayer->setCurVideoFlags(flags);
splayer->play(filename, 15);
Commit: ddfbc182ef404711cc508543e10a687038825dab
https://github.com/scummvm/scummvm/commit/ddfbc182ef404711cc508543e10a687038825dab
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T14:31:53+02:00
Commit Message:
SCUMM: RA2: refactor nutcracker/font code to isolate it from the rest of the scumm games
Changed paths:
A engines/scumm/smush/rebel/font_rebel2.cpp
A engines/scumm/smush/rebel/font_rebel2.h
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/menu.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/module.mk
engines/scumm/nut_renderer.cpp
engines/scumm/smush/rebel/smush_multi_font.cpp
engines/scumm/smush/rebel/smush_multi_font.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index cb34ed8fffb..c7d1bf5ca9f 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -27,8 +27,8 @@
#include "scumm/scumm_v7.h"
#include "scumm/smush/smush_player.h"
-#include "scumm/smush/smush_font.h"
#include "scumm/smush/rebel/codec_ra2.h"
+#include "scumm/smush/rebel/font_rebel2.h"
#include "scumm/insane/rebel2/rebel.h"
@@ -2588,12 +2588,16 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
// "subtitles" setting and the in-game TEXT toggle (same ConfMan key). The chunk is
// still fully parsed above so stream consumption is unaffected.
if (ConfMan.getBool("subtitles")) {
+ Rebel2FontSet fontSet;
+ fontSet.numFonts = 1;
+ fontSet.fonts[0] = _rebelMsgFont;
+
if (textFlags & 0x04) {
// Word-wrapped text
- _rebelMsgFont->drawStringWrap(convertedText, renderBitmap, clipRect, posX, posY, textColor, styleFlags);
+ drawRebel2StringWrap(fontSet, convertedText, dstIdx, renderBitmap, clipRect, posX, posY, width, textColor, styleFlags);
} else {
// Single-line text
- _rebelMsgFont->drawString(convertedText, renderBitmap, clipRect, posX, posY, textColor, styleFlags);
+ drawRebel2String(fontSet, convertedText, dstIdx, renderBitmap, clipRect, posX, posY, width, textColor, styleFlags);
}
}
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index 83a5f178ebb..b238e163ce7 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -33,7 +33,7 @@
#include "scumm/scumm_v7.h"
#include "scumm/smush/smush_player.h"
-#include "scumm/smush/smush_font.h"
+#include "scumm/smush/rebel/font_rebel2.h"
#include "scumm/smush/rebel/smush_multi_font.h"
#include "scumm/insane/rebel2/rebel.h"
@@ -416,8 +416,7 @@ void InsaneRebel2::drawMenuString(byte *renderBitmap, const char *str, int x, in
if (!curFont || c >= curFont->getNumChars()) continue;
int charW = curFont->getCharWidth(c);
if (x >= 0 && y >= 0 && charW > 0)
- curFont->drawCharV7(renderBitmap, clipRect, x, y, pitch, curColor,
- kStyleAlignLeft, c, false, false);
+ drawRebel2Char(curFont, renderBitmap, clipRect, x, y, pitch, curColor, c);
x += charW;
}
}
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 1e5f241014c..eef134ae7aa 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -41,7 +41,7 @@
#include "scumm/imuse_digi/dimuse_engine.h"
#include "scumm/smush/smush_player.h"
-#include "scumm/smush/smush_font.h"
+#include "scumm/smush/rebel/font_rebel2.h"
#include "scumm/insane/rebel2/rebel.h"
@@ -140,7 +140,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_smush_cockpitNut = new NutRenderer(_vm, "SYSTM/DISPFONT.NUT");
// Load DIHIFONT.NUT for in-video messages/subtitles (Opcode 9)
- _rebelMsgFont = new SmushFont(_vm, "SYSTM/DIHIFONT.NUT", true);
+ _rebelMsgFont = makeRebel2Font(_vm, "SYSTM/DIHIFONT.NUT");
// Load menu system fonts (from info.md - FUN_403BD0 lines 302-348)
// In low resolution mode, fonts are loaded as a linked list:
@@ -148,9 +148,9 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// Font 1 (^f01): SMALFONT.NUT - Small font for format code switching
// Font 2 (^f02): TITLFONT.NUT - Title font
// Font 3 (^f03): POVFONT.NUT - POV font (not loaded here)
- _smush_talkfontNut = new NutRenderer(_vm, "SYSTM/TALKFONT.NUT");
- _smush_smalfontNut = new NutRenderer(_vm, "SYSTM/SMALFONT.NUT");
- _smush_titlefontNut = new NutRenderer(_vm, "SYSTM/TITLFONT.NUT");
+ _smush_talkfontNut = makeRebel2Font(_vm, "SYSTM/TALKFONT.NUT");
+ _smush_smalfontNut = makeRebel2Font(_vm, "SYSTM/SMALFONT.NUT");
+ _smush_titlefontNut = makeRebel2Font(_vm, "SYSTM/TITLFONT.NUT");
_pauseOverlayActive = false;
memset(_savedPausePalette, 0, sizeof(_savedPausePalette));
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index d833df9de23..45ed0628a66 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -483,7 +483,7 @@ public:
NutRenderer *_smush_cockpitNut;
// Font used for opcode 9 text/subtitle rendering (DIHIFONT / TALKFONT)
- SmushFont *_rebelMsgFont;
+ NutRenderer *_rebelMsgFont;
// Menu system fonts (from info.md - FUN_403BD0 font loading)
// Low resolution mode font list (stored in DAT_00485058 linked list):
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 890a19c9166..5724492124b 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -28,8 +28,8 @@
#include "scumm/scumm_v7.h"
#include "scumm/smush/smush_player.h"
-#include "scumm/smush/smush_font.h"
#include "scumm/smush/rebel/codec_ra2.h"
+#include "scumm/smush/rebel/font_rebel2.h"
#include "scumm/insane/rebel2/rebel.h"
@@ -2806,8 +2806,7 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
}
int charW = curFont->getCharWidth(c);
if (drawX >= 0 && drawY >= 0 && charW > 0) {
- curFont->drawCharV7(renderBitmap, clipRect, drawX, drawY,
- pitch, curColor, kStyleAlignLeft, c, false, false);
+ drawRebel2Char(curFont, renderBitmap, clipRect, drawX, drawY, pitch, curColor, c);
}
drawX += charW;
lineCharsDrawn++;
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index e7fe13f0263..4f42d417fdc 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -156,6 +156,7 @@ MODULE_OBJS += \
smush/smush_player.o \
smush/rebel/codec_ra1.o \
smush/rebel/codec_ra2.o \
+ smush/rebel/font_rebel2.o \
smush/rebel/smush_multi_font.o \
smush/rebel/smush_player_rebel.o \
smush/rebel/smush_player_ra1.o \
diff --git a/engines/scumm/nut_renderer.cpp b/engines/scumm/nut_renderer.cpp
index 5fa5819a613..85cfef2cffd 100644
--- a/engines/scumm/nut_renderer.cpp
+++ b/engines/scumm/nut_renderer.cpp
@@ -196,12 +196,7 @@ void NutRenderer::loadFont(const char *filename) {
// If characters have transparency, then bytes just get skipped and
// so there may appear some garbage. That's why we have to fill it
// with a default color first.
- //
- // For codec 44: standard SCUMM v7/v8 fonts use value 2 as the
- // transparent color. But RA2 codec 44 fonts use value 2 as an actual
- // glyph color (medium body shade), so we must use 0 instead to avoid
- // making those pixels invisible during rendering.
- if (codec == 44 && _vm->_game.id != GID_REBEL2) {
+ if (codec == 44) {
memset(_chars[l].src, kSmush44TransparentColor, _chars[l].width * _chars[l].height);
_chars[l].transparency = kSmush44TransparentColor;
} else {
@@ -315,9 +310,7 @@ void NutRenderer::loadFontFromData(const byte *data, int32 dataSize) {
decodedPtr += (_chars[l].width * _chars[l].height);
- // Same transparency logic as loadFont: RA2 codec 44 fonts use
- // value 2 as a glyph color, not as transparency.
- if (codec == 44 && _vm->_game.id != GID_REBEL2) {
+ if (codec == 44) {
memset(_chars[l].src, kSmush44TransparentColor, _chars[l].width * _chars[l].height);
_chars[l].transparency = kSmush44TransparentColor;
} else {
@@ -438,26 +431,7 @@ int NutRenderer::drawCharV7(byte *buffer, Common::Rect &clipRect, int x, int y,
int clipWdth = (_chars[chr].width - width);
char color = (col != -1) ? col : 1;
- if (_vm->_game.id == GID_REBEL2) {
- // RA2 codec 44 font rendering, matching the original behavior:
- // - Pixel value 1 â remapped to the caller's text color
- // - Other non-transparent values â used as-is (palette indices)
- // The font's pixel layout: value 4 = black outline (38% of pixels),
- // value 1 = body (11%, remapped to color), value 3 = gray AA (2%).
- // The SMUSH video palette reserves indices 0-4 for text rendering:
- // 0=(0,0,0), 1=(255,255,255), 2=(188,188,188), 3=(128,128,128), 4=(0,0,0).
- for (int j = minY; j < height; j++) {
- for (int i = minX; i < width; i++) {
- int8 value = *src++;
- if (value == 1)
- dst[i] = color;
- else if (value != _chars[chr].transparency)
- dst[i] = value;
- }
- src += clipWdth;
- dst += pitch;
- }
- } else if (_vm->_game.version == 7) {
+ if (_vm->_game.version == 7) {
if (hardcodedColors) {
for (int j = minY; j < height; j++) {
for (int i = minX; i < width; i++) {
diff --git a/engines/scumm/smush/rebel/font_rebel2.cpp b/engines/scumm/smush/rebel/font_rebel2.cpp
new file mode 100644
index 00000000000..d83fbef6585
--- /dev/null
+++ b/engines/scumm/smush/rebel/font_rebel2.cpp
@@ -0,0 +1,504 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "scumm/smush/rebel/font_rebel2.h"
+
+#include "common/endian.h"
+#include "common/util.h"
+
+#include "scumm/file.h"
+#include "scumm/nut_renderer.h"
+#include "scumm/scumm.h"
+
+namespace Scumm {
+
+enum {
+ kRebel2MaxStrings = 80
+};
+
+class Rebel2NutRenderer : public NutRenderer {
+public:
+ Rebel2NutRenderer(ScummEngine *vm, const char *filename) : NutRenderer(vm, nullptr) {
+ loadRebel2Font(filename);
+ }
+
+private:
+ void codec44(byte *dst, const byte *src, int width, int height, int pitch);
+ void loadRebel2Font(const char *filename);
+};
+
+void Rebel2NutRenderer::codec44(byte *dst, const byte *src, int width, int height, int pitch) {
+ while (height--) {
+ byte *dstPtrNext = dst + pitch;
+ const byte *srcPtrNext = src + 2 + READ_LE_UINT16(src);
+ src += 2;
+ int len = width;
+ do {
+ int offs = READ_LE_UINT16(src); src += 2;
+ dst += offs;
+ len -= offs;
+ if (len <= 0)
+ break;
+
+ int w = READ_LE_UINT16(src) + 1; src += 2;
+ len -= w;
+ if (len < 0)
+ w += len;
+
+ while (w--) {
+ byte value = *src++;
+ *dst++ = (value == 0xff) ? 0 : value;
+ }
+ } while (len > 0);
+ dst = dstPtrNext;
+ src = srcPtrNext;
+ }
+}
+
+void Rebel2NutRenderer::loadRebel2Font(const char *filename) {
+ ScummFile *file = _vm->instantiateScummFile();
+
+ _vm->openFile(*file, filename);
+ if (!file->isOpen())
+ error("Rebel2NutRenderer::loadRebel2Font() Can't open font file: %s", filename);
+
+ uint32 tag = file->readUint32BE();
+ if (tag != MKTAG('A','N','I','M'))
+ error("Rebel2NutRenderer::loadRebel2Font() there is no ANIM chunk in font header");
+
+ uint32 length = file->readUint32BE();
+ byte *dataSrc = new byte[length];
+ file->read(dataSrc, length);
+ file->close();
+ delete file;
+
+ if (READ_BE_UINT32(dataSrc) != MKTAG('A','H','D','R'))
+ error("Rebel2NutRenderer::loadRebel2Font() there is no AHDR chunk in font header");
+
+ _numChars = READ_LE_UINT16(dataSrc + 10);
+ if (_numChars > ARRAYSIZE(_chars)) {
+ warning("Rebel2NutRenderer::loadRebel2Font(%s) numChars (%d) exceeds max, clamping", filename, _numChars);
+ _numChars = ARRAYSIZE(_chars);
+ }
+
+ uint32 offset = 0;
+ uint32 decodedLength = 0;
+ int l;
+
+ for (l = 0; l < _numChars; l++) {
+ if (offset + 8 > length) {
+ warning("Rebel2NutRenderer::loadRebel2Font(%s) truncated before char %d (offset %x), clamping", filename, l, offset);
+ break;
+ }
+ uint32 chunkSize = READ_BE_UINT32(dataSrc + offset + 4);
+ uint64 nextOffset = (uint64)offset + chunkSize + 16 + (chunkSize & 1);
+ if (nextOffset + 18 > length) {
+ warning("Rebel2NutRenderer::loadRebel2Font(%s) font chunk exceeds file at char %d (offset %x), clamping", filename, l, offset);
+ break;
+ }
+ offset = (uint32)nextOffset;
+ int width = READ_LE_UINT16(dataSrc + offset + 14);
+ _fontHeight = READ_LE_UINT16(dataSrc + offset + 16);
+ decodedLength += width * _fontHeight;
+ }
+
+ if (l < _numChars)
+ _numChars = l;
+
+ if (_numChars <= 0 || decodedLength == 0)
+ error("Rebel2NutRenderer::loadRebel2Font(%s) no decodable characters", filename);
+
+ debug(1, "Rebel2NutRenderer::loadRebel2Font('%s') - decodedLength = %d", filename, decodedLength);
+
+ _decodedData = new byte[decodedLength];
+ byte *decodedPtr = _decodedData;
+
+ offset = 0;
+ for (l = 0; l < _numChars; l++) {
+ if (offset + 8 > length) {
+ warning("Rebel2NutRenderer::loadRebel2Font(%s) invalid font chunk header %d (offset %x), stopping decode", filename, l, offset);
+ break;
+ }
+ uint32 chunkSize = READ_BE_UINT32(dataSrc + offset + 4);
+ uint64 nextOffset = (uint64)offset + chunkSize + 8 + (chunkSize & 1);
+ if (nextOffset + 8 > length) {
+ warning("Rebel2NutRenderer::loadRebel2Font(%s) FRME chunk exceeds file %d (offset %x), stopping decode", filename, l, offset);
+ break;
+ }
+ offset = (uint32)nextOffset;
+ if (READ_BE_UINT32(dataSrc + offset) != MKTAG('F','R','M','E')) {
+ warning("Rebel2NutRenderer::loadRebel2Font(%s) no FRME chunk %d (offset %x), stopping decode", filename, l, offset);
+ break;
+ }
+ offset += 8;
+ if (offset + 22 > length) {
+ warning("Rebel2NutRenderer::loadRebel2Font(%s) FOBJ chunk exceeds file %d (offset %x), stopping decode", filename, l, offset);
+ break;
+ }
+ if (READ_BE_UINT32(dataSrc + offset) != MKTAG('F','O','B','J')) {
+ warning("Rebel2NutRenderer::loadRebel2Font(%s) no FOBJ chunk in FRME chunk %d (offset %x), stopping decode", filename, l, offset);
+ break;
+ }
+
+ int codec = READ_LE_UINT16(dataSrc + offset + 8);
+ _chars[l].xoffs = READ_LE_INT16(dataSrc + offset + 10);
+ _chars[l].yoffs = READ_LE_INT16(dataSrc + offset + 12);
+ _chars[l].width = READ_LE_UINT16(dataSrc + offset + 14);
+ _chars[l].height = READ_LE_UINT16(dataSrc + offset + 16);
+ _chars[l].src = decodedPtr;
+
+ decodedPtr += (_chars[l].width * _chars[l].height);
+
+ memset(_chars[l].src, kDefaultTransparentColor, _chars[l].width * _chars[l].height);
+ _chars[l].transparency = kDefaultTransparentColor;
+
+ const uint8 *fobjptr = dataSrc + offset + 22;
+ switch (codec) {
+ case 1:
+ codec1(_chars[l].src, fobjptr, _chars[l].width, _chars[l].height, _chars[l].width);
+ break;
+ case 21:
+ codec21(_chars[l].src, fobjptr, _chars[l].width, _chars[l].height, _chars[l].width);
+ break;
+ case 44:
+ codec44(_chars[l].src, fobjptr, _chars[l].width, _chars[l].height, _chars[l].width);
+ break;
+ default:
+ error("Rebel2NutRenderer::loadRebel2Font: unknown codec: %d", codec);
+ }
+ }
+
+ delete[] dataSrc;
+}
+
+NutRenderer *makeRebel2Font(ScummEngine *vm, const char *filename) {
+ return new Rebel2NutRenderer(vm, filename);
+}
+
+Rebel2FontSet::Rebel2FontSet() : numFonts(0), defaultFont(0) {
+ memset(fonts, 0, sizeof(fonts));
+}
+
+NutRenderer *Rebel2FontSet::getFont(int id) const {
+ if (numFonts <= 0)
+ return nullptr;
+
+ if (id < 0 || id >= numFonts || fonts[id] == nullptr)
+ id = defaultFont;
+
+ if (id < 0 || id >= numFonts)
+ return nullptr;
+
+ return fonts[id] ? fonts[id] : fonts[0];
+}
+
+static bool parseRebel2FormatCode(const char *str, uint len, uint &pos, int &fontId, int16 &color) {
+ if (pos + 1 >= len || str[pos] != '^')
+ return false;
+
+ const char code = str[pos + 1];
+ if (code == '^') {
+ pos++;
+ return false;
+ }
+
+ if (code == 'f') {
+ pos += 2;
+ int value = 0;
+ while (pos < len && str[pos] >= '0' && str[pos] <= '9') {
+ value = value * 10 + str[pos] - '0';
+ pos++;
+ }
+ fontId = value;
+ return true;
+ }
+
+ if (code == 'c') {
+ pos += 2;
+ int value = 0;
+ while (pos < len && str[pos] >= '0' && str[pos] <= '9') {
+ value = value * 10 + str[pos] - '0';
+ pos++;
+ }
+ color = value;
+ return true;
+ }
+
+ if (code == 'l') {
+ pos += 2;
+ return true;
+ }
+
+ return false;
+}
+
+int drawRebel2Char(NutRenderer *font, byte *buffer, Common::Rect &clipRect, int x, int y,
+ int pitch, int16 col, byte chr) {
+ if (!font || chr >= font->getNumChars())
+ return 0;
+
+ const int charWidth = font->getCharWidth(chr);
+ const int charHeight = font->getCharHeight(chr);
+ int width = MIN(charWidth, clipRect.right - x);
+ int height = MIN(charHeight, clipRect.bottom - y);
+ const int minX = x < clipRect.left ? clipRect.left - x : 0;
+ const int minY = y < clipRect.top ? clipRect.top - y : 0;
+
+ if (width <= 0 || height <= 0)
+ return 0;
+
+ width -= minX;
+ height -= minY;
+ if (width <= 0 || height <= 0)
+ return 0;
+
+ const byte *src = font->getCharData(chr) + minY * charWidth + minX;
+ byte *dst = buffer + pitch * (y + minY) + x + minX;
+ const int color = (col != -1) ? col : 1;
+
+ for (int row = 0; row < height; row++) {
+ for (int column = 0; column < width; column++) {
+ const byte value = src[column];
+ if (value != 0 && value != 0xff)
+ dst[column] = (byte)(value + color - 1);
+ }
+ src += charWidth;
+ dst += pitch;
+ }
+
+ return MIN(charWidth, clipRect.right - x);
+}
+
+int getRebel2StringWidth(const Rebel2FontSet &fontSet, const char *str, uint len) {
+ int width = 0;
+ int fontId = fontSet.defaultFont;
+ int16 color = 0;
+
+ for (uint pos = 0; pos < len;) {
+ if (parseRebel2FormatCode(str, len, pos, fontId, color))
+ continue;
+
+ const byte chr = (byte)str[pos++];
+ if (chr == '\n' || chr == '\r')
+ continue;
+
+ NutRenderer *font = fontSet.getFont(fontId);
+ if (font && chr < font->getNumChars())
+ width += font->getCharWidth(chr);
+ }
+
+ return width;
+}
+
+int getRebel2StringHeight(const Rebel2FontSet &fontSet, const char *str, uint len) {
+ int height = 0;
+ int lineHeight = 0;
+ int fontId = fontSet.defaultFont;
+ int16 color = 0;
+
+ for (uint pos = 0; pos < len;) {
+ if (parseRebel2FormatCode(str, len, pos, fontId, color))
+ continue;
+
+ const byte chr = (byte)str[pos++];
+ if (chr == '\n') {
+ NutRenderer *font = fontSet.getFont(fontId);
+ height += lineHeight ? lineHeight : (font ? font->getFontHeight() : 0);
+ lineHeight = 0;
+ } else if (chr != '\r') {
+ NutRenderer *font = fontSet.getFont(fontId);
+ if (font && chr < font->getNumChars())
+ lineHeight = MAX<int>(lineHeight, font->getCharHeight(chr));
+ }
+ }
+
+ NutRenderer *font = fontSet.getFont(fontId);
+ return height + (lineHeight ? lineHeight : (font ? font->getFontHeight() : 0));
+}
+
+static void drawRebel2Substring(const Rebel2FontSet &fontSet, const char *str, uint len,
+ byte *buffer, Common::Rect &clipRect, int x, int y, int pitch,
+ int &fontId, int16 &color) {
+ for (uint pos = 0; pos < len;) {
+ if (parseRebel2FormatCode(str, len, pos, fontId, color))
+ continue;
+
+ const byte chr = (byte)str[pos++];
+ if (chr == '\n' || chr == '\r')
+ continue;
+
+ NutRenderer *font = fontSet.getFont(fontId);
+ x += drawRebel2Char(font, buffer, clipRect, x, y, pitch, color, chr);
+ }
+}
+
+void drawRebel2String(const Rebel2FontSet &fontSet, const char *str, uint len, byte *buffer,
+ Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags) {
+ if (!str || !buffer)
+ return;
+
+ int lineStart = 0;
+ int maxWidth = 0;
+ int yStart = y;
+ int fontId = fontSet.defaultFont;
+ int16 color = col;
+
+ for (uint pos = 0; pos <= len; pos++) {
+ if (pos < len && str[pos] != '\n')
+ continue;
+
+ const int lineLen = pos - lineStart;
+ const int lineWidth = getRebel2StringWidth(fontSet, str + lineStart, lineLen);
+ const int lineHeight = getRebel2StringHeight(fontSet, str + lineStart, lineLen);
+ maxWidth = MAX(maxWidth, lineWidth);
+
+ int drawX = x;
+ if (flags & kStyleAlignCenter)
+ drawX = x - lineWidth / 2;
+ else if (flags & kStyleAlignRight)
+ drawX = x - lineWidth;
+
+ drawRebel2Substring(fontSet, str + lineStart, lineLen, buffer, clipRect, drawX, y, pitch, fontId, color);
+ y += lineHeight;
+ lineStart = pos + 1;
+ }
+
+ clipRect.left = MAX<int>(0, (flags & kStyleAlignCenter) ? x - maxWidth / 2 : ((flags & kStyleAlignRight) ? x - maxWidth : x));
+ clipRect.right = MIN<int>(clipRect.right, clipRect.left + maxWidth);
+ clipRect.top = yStart;
+ clipRect.bottom = y;
+}
+
+void drawRebel2StringWrap(const Rebel2FontSet &fontSet, const char *str, uint len, byte *buffer,
+ Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags) {
+ if (!str || !buffer)
+ return;
+
+ int16 substrByteLength[kRebel2MaxStrings];
+ int16 substrWidths[kRebel2MaxStrings];
+ int16 substrStart[kRebel2MaxStrings];
+ memset(substrByteLength, 0, sizeof(substrByteLength));
+ memset(substrWidths, 0, sizeof(substrWidths));
+ memset(substrStart, 0, sizeof(substrStart));
+
+ int16 numSubstrings = 0;
+ int curWidth = 0;
+ int curPos = -1;
+ int maxWidth = 0;
+ int height = 0;
+ int lastSubstrHeight = 0;
+
+ while (curPos < (int)len) {
+ int textStart = curPos + 1;
+ while (textStart < (int)len && str[textStart] == ' ')
+ textStart++;
+
+ const int separatorWidth = curPos > 0 ? getRebel2StringWidth(fontSet, str + curPos, textStart - curPos) : 0;
+
+ int nextSeparatorPos = textStart;
+ while (nextSeparatorPos < (int)len && str[nextSeparatorPos] != ' ' && str[nextSeparatorPos] != '\n')
+ nextSeparatorPos++;
+
+ const int wordWidth = getRebel2StringWidth(fontSet, str + textStart, nextSeparatorPos - textStart);
+ int newWidth = curWidth + separatorWidth + wordWidth;
+
+ if (curWidth && newWidth > clipRect.width()) {
+ if (numSubstrings < kRebel2MaxStrings) {
+ substrWidths[numSubstrings] = curWidth;
+ substrByteLength[numSubstrings] = curPos - substrStart[numSubstrings];
+ numSubstrings++;
+ }
+ newWidth = wordWidth;
+ substrStart[numSubstrings] = textStart;
+ }
+ curWidth = newWidth;
+
+ curPos = nextSeparatorPos;
+ if (curPos >= (int)len || str[curPos] == '\n') {
+ if (numSubstrings < kRebel2MaxStrings) {
+ substrWidths[numSubstrings] = curWidth;
+ substrByteLength[numSubstrings] = curPos - substrStart[numSubstrings];
+ numSubstrings++;
+ if (numSubstrings < kRebel2MaxStrings)
+ substrStart[numSubstrings] = curPos + 1;
+ }
+ curWidth = 0;
+ }
+ }
+
+ if (curWidth && numSubstrings < kRebel2MaxStrings) {
+ substrWidths[numSubstrings] = curWidth;
+ substrByteLength[numSubstrings] = curPos - substrStart[numSubstrings];
+ numSubstrings++;
+ }
+
+ for (int i = 0; i < numSubstrings; i++) {
+ maxWidth = MAX<int>(maxWidth, substrWidths[i]);
+ lastSubstrHeight = substrByteLength[i] > 0 ? getRebel2StringHeight(fontSet, str + substrStart[i], substrByteLength[i]) : 0;
+ height += lastSubstrHeight;
+ }
+
+ const int clipHeight = height + lastSubstrHeight / 2;
+ if (y > clipRect.bottom - clipHeight)
+ y = clipRect.bottom - clipHeight;
+ if (y < clipRect.top)
+ y = clipRect.top;
+
+ if (flags & kStyleAlignCenter) {
+ if (x + (maxWidth >> 1) > clipRect.right)
+ x = clipRect.right - (maxWidth >> 1);
+ if (x - (maxWidth >> 1) < clipRect.left)
+ x = clipRect.left + (maxWidth >> 1);
+ } else if (flags & kStyleAlignRight) {
+ if (x > clipRect.right)
+ x = clipRect.right;
+ if (x < clipRect.left + maxWidth)
+ x = clipRect.left + maxWidth;
+ } else {
+ if (x > clipRect.right - maxWidth)
+ x = clipRect.right - maxWidth;
+ if (x < clipRect.left)
+ x = clipRect.left;
+ }
+
+ const int yStart = y;
+ int fontId = fontSet.defaultFont;
+ int16 color = col;
+
+ for (int i = 0; i < numSubstrings; i++) {
+ int drawX = x;
+ if (flags & kStyleAlignCenter)
+ drawX = x - substrWidths[i] / 2;
+ else if (flags & kStyleAlignRight)
+ drawX = x - substrWidths[i];
+
+ const int lineLen = substrByteLength[i] > 0 ? substrByteLength[i] : 0;
+ drawRebel2Substring(fontSet, str + substrStart[i], lineLen, buffer, clipRect, drawX, y, pitch, fontId, color);
+ y += getRebel2StringHeight(fontSet, str + substrStart[i], lineLen);
+ }
+
+ clipRect.left = MAX<int>(0, (flags & kStyleAlignCenter) ? x - maxWidth / 2 : ((flags & kStyleAlignRight) ? x - maxWidth : x));
+ clipRect.right = MIN<int>(clipRect.right, clipRect.left + maxWidth);
+ clipRect.top = yStart;
+ clipRect.bottom = y;
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/smush/rebel/font_rebel2.h b/engines/scumm/smush/rebel/font_rebel2.h
new file mode 100644
index 00000000000..2a5ae2f7ae7
--- /dev/null
+++ b/engines/scumm/smush/rebel/font_rebel2.h
@@ -0,0 +1,60 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SCUMM_SMUSH_REBEL_FONT_REBEL2_H
+#define SCUMM_SMUSH_REBEL_FONT_REBEL2_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+
+#include "scumm/charset_v7.h"
+
+namespace Scumm {
+
+class ScummEngine;
+class NutRenderer;
+
+struct Rebel2FontSet {
+ enum {
+ kMaxFonts = 5
+ };
+
+ NutRenderer *fonts[kMaxFonts];
+ int numFonts;
+ int defaultFont;
+
+ Rebel2FontSet();
+ NutRenderer *getFont(int id) const;
+};
+
+NutRenderer *makeRebel2Font(ScummEngine *vm, const char *filename);
+int drawRebel2Char(NutRenderer *font, byte *buffer, Common::Rect &clipRect, int x, int y,
+ int pitch, int16 col, byte chr);
+int getRebel2StringWidth(const Rebel2FontSet &fontSet, const char *str, uint len);
+int getRebel2StringHeight(const Rebel2FontSet &fontSet, const char *str, uint len);
+void drawRebel2String(const Rebel2FontSet &fontSet, const char *str, uint len, byte *buffer,
+ Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags);
+void drawRebel2StringWrap(const Rebel2FontSet &fontSet, const char *str, uint len, byte *buffer,
+ Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags);
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/smush/rebel/smush_multi_font.cpp b/engines/scumm/smush/rebel/smush_multi_font.cpp
index 9c4205b7a65..47193e58a6e 100644
--- a/engines/scumm/smush/rebel/smush_multi_font.cpp
+++ b/engines/scumm/smush/rebel/smush_multi_font.cpp
@@ -28,10 +28,13 @@ namespace Scumm {
SmushMultiFont::SmushMultiFont(ScummEngine *vm, SmushPlayer *player, bool useOriginalColors)
: _vm(vm), _player(player), _currentFont(0), _defaultFont(0), _hardcodedFontColors(useOriginalColors) {
+ memset(_rebel2Fonts, 0, sizeof(_rebel2Fonts));
_textRenderer = new TextRenderer_v7(vm, this);
}
SmushMultiFont::~SmushMultiFont() {
+ for (int i = 0; i < ARRAYSIZE(_rebel2Fonts); i++)
+ delete _rebel2Fonts[i];
delete _textRenderer;
}
@@ -47,20 +50,54 @@ NutRenderer *SmushMultiFont::getCurrentFont() const {
return const_cast<SmushMultiFont*>(this)->getFont(_currentFont);
}
+Rebel2FontSet SmushMultiFont::getRebel2FontSet() {
+ static const char *ra2Fonts[] = {
+ "SYSTM/TALKFONT.NUT",
+ "SYSTM/SMALFONT.NUT",
+ "SYSTM/TITLFONT.NUT",
+ "SYSTM/POVFONT.NUT"
+ };
+
+ Rebel2FontSet fontSet;
+ fontSet.numFonts = ARRAYSIZE(ra2Fonts);
+ fontSet.defaultFont = CLIP<int>(_defaultFont, 0, fontSet.numFonts - 1);
+ for (int i = 0; i < fontSet.numFonts; i++) {
+ if (!_rebel2Fonts[i])
+ _rebel2Fonts[i] = makeRebel2Font(_vm, ra2Fonts[i]);
+ fontSet.fonts[i] = _rebel2Fonts[i];
+ }
+ return fontSet;
+}
+
void SmushMultiFont::drawString(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int16 col, TextStyleFlags flags) {
// Reset to default font before drawing
_currentFont = _defaultFont;
+ if (_vm->_game.id == GID_REBEL2) {
+ Rebel2FontSet fontSet = getRebel2FontSet();
+ drawRebel2String(fontSet, str, strlen(str), buffer, clipRect, x, y, _vm->_screenWidth, col, flags);
+ return;
+ }
_textRenderer->drawString(str, buffer, clipRect, x, y, _vm->_screenWidth, col, flags);
}
void SmushMultiFont::drawString(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags) {
_currentFont = _defaultFont;
+ if (_vm->_game.id == GID_REBEL2) {
+ Rebel2FontSet fontSet = getRebel2FontSet();
+ drawRebel2String(fontSet, str, strlen(str), buffer, clipRect, x, y, pitch, col, flags);
+ return;
+ }
_textRenderer->drawString(str, buffer, clipRect, x, y, pitch, col, flags);
}
void SmushMultiFont::drawStringWrap(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int16 col, TextStyleFlags flags) {
// Reset to default font before drawing
_currentFont = _defaultFont;
+ if (_vm->_game.id == GID_REBEL2) {
+ Rebel2FontSet fontSet = getRebel2FontSet();
+ drawRebel2StringWrap(fontSet, str, strlen(str), buffer, clipRect, x, y, _vm->_screenWidth, col, flags);
+ return;
+ }
_textRenderer->drawStringWrap(str, buffer, clipRect, x, y, _vm->_screenWidth, col, flags);
}
@@ -80,6 +117,11 @@ int SmushMultiFont::draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y
}
int SmushMultiFont::drawCharV7(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags, byte chr) {
+ if (_vm->_game.id == GID_REBEL2) {
+ Rebel2FontSet fontSet = getRebel2FontSet();
+ return drawRebel2Char(fontSet.getFont(_currentFont), buffer, clipRect, x, y, pitch, col, chr);
+ }
+
NutRenderer *font = getCurrentFont();
if (!font)
return 0;
diff --git a/engines/scumm/smush/rebel/smush_multi_font.h b/engines/scumm/smush/rebel/smush_multi_font.h
index 6c57acbe57f..1f7fba58487 100644
--- a/engines/scumm/smush/rebel/smush_multi_font.h
+++ b/engines/scumm/smush/rebel/smush_multi_font.h
@@ -25,6 +25,7 @@
#include "common/scummsys.h"
#include "scumm/nut_renderer.h"
#include "scumm/scumm.h"
+#include "scumm/smush/rebel/font_rebel2.h"
#include "scumm/string_v7.h"
namespace Scumm {
@@ -70,10 +71,12 @@ public:
private:
NutRenderer *getFont(int id);
NutRenderer *getCurrentFont() const;
+ Rebel2FontSet getRebel2FontSet();
ScummEngine *_vm;
SmushPlayer *_player;
TextRenderer_v7 *_textRenderer;
+ NutRenderer *_rebel2Fonts[Rebel2FontSet::kMaxFonts];
int _currentFont;
int _defaultFont;
More information about the Scummvm-git-logs
mailing list