[Scummvm-git-logs] scummvm master -> ddb8a0dd9a498a85bb3893e65875ee0cd65607c3
neuromancer
noreply at scummvm.org
Mon Jun 22 14:39:57 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:
ca26cb0307 SCUMM: RA2: clean-up of comments for SMUSH code
8d6050a6c9 SCUMM: RA1: clean-up of comments for SMUSH code
2d54fc2225 SCUMM: RA: clean-up of remaining comments
b62c478da5 SCUMM: RA2: implement end of level messages
ddb8a0dd9a SCUMM: RA2: added missing ship shadow for L3
Commit: ca26cb030705995d54ae897fc2d6df787dc9b8e3
https://github.com/scummvm/scummvm/commit/ca26cb030705995d54ae897fc2d6df787dc9b8e3
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-22T16:21:09+02:00
Commit Message:
SCUMM: RA2: clean-up of comments for SMUSH code
Changed paths:
engines/scumm/smush/rebel/codec_ra2.cpp
engines/scumm/smush/rebel/smush_multi_font.cpp
engines/scumm/smush/rebel/smush_multi_font.h
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
diff --git a/engines/scumm/smush/rebel/codec_ra2.cpp b/engines/scumm/smush/rebel/codec_ra2.cpp
index cb6100a7e6d..11c4c2dba88 100644
--- a/engines/scumm/smush/rebel/codec_ra2.cpp
+++ b/engines/scumm/smush/rebel/codec_ra2.cpp
@@ -19,8 +19,6 @@
*
*/
-// Rebel Assault 2 SMUSH video codecs
-
#include "scumm/smush/rebel/codec_ra2.h"
#include "common/endian.h"
@@ -77,14 +75,7 @@ const byte *smushSkipRLELines(const byte *src, int &dataSize, int lines) {
return src;
}
-/**
- * Codec 3 RLE decoder that writes ALL colors including color 0 (black).
- * Use this for background images where color 0 should NOT be treated as transparent.
- * The standard smushDecodeRLE() treats color 0 as transparent, which is correct
- * for overlay sprites but wrong for background images.
- *
- * Used by: Rebel Assault 2 Level 2 background loading (IACT opcode 8, par4=5)
- */
+// RLE decoder for opaque backgrounds, including color 0.
void smushDecodeRLEOpaque(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize) {
if (dataSize <= 0)
return;
@@ -105,13 +96,7 @@ void smushDecodeRLEOpaque(byte *dst, const byte *src, int left, int top, int wid
}
}
-/**
- * Codec 21/44: Line Update codec
- * Used for fonts (NUT files) and some embedded HUD frames.
- * Format: Each line has a 2-byte size header, then 2-byte skip, 2-byte count pairs with literal pixels.
- * The count value needs +1 to get the actual number of pixels to copy.
- * Note: Skip regions preserve previous frame content (delta compression).
- */
+// Codec 21/44 line update.
void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize) {
if (dataSize <= 0)
return;
@@ -130,7 +115,6 @@ void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int wi
byte *lineDst = dst;
while (len > 0 && lineEnd - src >= 2) {
- // Read 2-byte LE skip value
int skip = READ_LE_UINT16(src);
src += 2;
if (skip >= len)
@@ -138,7 +122,6 @@ void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int wi
lineDst += skip;
len -= skip;
- // Read 2-byte LE copy count (+1 for actual count)
if (lineEnd - src < 2)
break;
int count = READ_LE_UINT16(src) + 1;
@@ -146,7 +129,6 @@ void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int wi
if (count > len)
count = len;
- // Copy literal pixels
int toCopy = count;
if (toCopy > (int)(lineEnd - src))
toCopy = (int)(lineEnd - src);
@@ -162,12 +144,7 @@ void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int wi
}
}
-/**
- * Codec 23: Skip/Copy with embedded RLE
- * Used for video frames with skip regions.
- * Format: Each line has 2-byte size, then (skip, runSize, RLE_data) triplets.
- * Note: Skip regions preserve previous frame content (delta compression).
- */
+// Codec 23 skip/copy with embedded RLE.
void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize) {
dst += top * pitch + left;
const byte *srcEnd = src + dataSize;
@@ -196,7 +173,6 @@ void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width
if (runEnd > lineEnd)
runEnd = lineEnd;
- // Decode RLE within this run - write ALL colors including 0
while (src < runEnd && x < width) {
byte code = *src++;
int num = (code >> 1) + 1;
@@ -204,12 +180,10 @@ void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width
num = width - x;
if (code & 1) {
- // RLE run - repeat color
byte color = (src < runEnd) ? *src++ : 0;
memset(lineDst + x, color, num);
x += num;
} else {
- // Literal run - copy bytes
int toCopy = num;
if (toCopy > (int)(runEnd - src))
toCopy = (int)(runEnd - src);
@@ -225,9 +199,7 @@ void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width
}
}
-// Codec 23: a skip/run RLE that tints the background in place (no pixel data). Per line,
-// lineDataSize(2) then [skip(1), run(1)] pairs; each run pixel becomes remap[background]
-// (translucency) or background+addColor.
+// Codec 23 skip/run remap that tints the background in place.
void smushDecodeRA2SkipRemap(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize, const byte *remap, byte addColor) {
dst += top * pitch + left;
const byte *srcEnd = src + dataSize;
@@ -270,7 +242,7 @@ bool smushPrepareRA2BlurData(const byte *src, int dataSize, byte *palette, byte
if (src == nullptr || dataSize < 6 || palette == nullptr || lookup == nullptr)
return false;
- // FUN_0042B530 only processes this codec body when byte +4 is 1.
+ // Byte 4 gates whether this codec body is valid.
if (src[4] != 1)
return false;
@@ -307,10 +279,7 @@ bool smushPrepareRA2BlurData(const byte *src, int dataSize, byte *palette, byte
return maskSize > 3;
}
-/**
- * Codec 45: RA2 blur/wipe mask.
- * Original path: FUN_0042B460 -> FUN_0042B530 -> FUN_0042DDF0.
- */
+// Codec 45 blur/wipe mask.
void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup) {
const byte *maskData = nullptr;
int maskSize = 0;
diff --git a/engines/scumm/smush/rebel/smush_multi_font.cpp b/engines/scumm/smush/rebel/smush_multi_font.cpp
index 07af9cb1b43..3383b60befd 100644
--- a/engines/scumm/smush/rebel/smush_multi_font.cpp
+++ b/engines/scumm/smush/rebel/smush_multi_font.cpp
@@ -39,19 +39,15 @@ SmushMultiFont::~SmushMultiFont() {
}
NutRenderer *SmushMultiFont::getFont(int id) {
- // Delegate to SmushPlayer to get the font
- // SmushPlayer::getFont() handles font loading and caching
return _player->getFont(id);
}
NutRenderer *SmushMultiFont::getCurrentFont() const {
- // We need a const version that doesn't trigger loading
- // For const access, use _player's cached fonts directly
return const_cast<SmushMultiFont*>(this)->getFont(_currentFont);
}
Rebel2FontSet SmushMultiFont::getRebel2FontSet() {
- // High-res mode uses dedicated larger-glyph font assets, not scaled low-res ones (matches getGameFont()).
+ // High-res mode uses dedicated larger-glyph font assets.
static const char *ra2FontsLo[] = {
"SYSTM/TALKFONT.NUT",
"SYSTM/SMALFONT.NUT",
@@ -82,7 +78,6 @@ Rebel2FontSet SmushMultiFont::getRebel2FontSet() {
}
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();
@@ -107,7 +102,6 @@ void SmushMultiFont::drawStringWrap(const char *str, byte *buffer, Common::Rect
}
void SmushMultiFont::drawStringWrap(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags) {
- // Reset to default font before drawing
_currentFont = _defaultFont;
if (_vm->_game.id == GID_REBEL2) {
Rebel2FontSet fontSet = getRebel2FontSet();
@@ -122,7 +116,6 @@ int SmushMultiFont::draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y
if (!font)
return 0;
- // Adjust color for CMI compatibility
int16 adjCol = col;
if (_vm->_game.id == GID_CMI)
adjCol = 255;
diff --git a/engines/scumm/smush/rebel/smush_multi_font.h b/engines/scumm/smush/rebel/smush_multi_font.h
index 88e5a72b5d7..adb532f8225 100644
--- a/engines/scumm/smush/rebel/smush_multi_font.h
+++ b/engines/scumm/smush/rebel/smush_multi_font.h
@@ -32,18 +32,7 @@ namespace Scumm {
class SmushPlayer;
-/**
- * SmushMultiFont - Multi-font renderer for SMUSH videos
- *
- * This class implements the GlyphRenderer_v7 interface and supports
- * switching between multiple fonts during text rendering via the ^fXX
- * escape sequence.
- *
- * The original Rebel Assault 2 (and other SCUMM v7 games) used a linked
- * list of font structures that could be traversed when ^f escape codes
- * were encountered. This class provides equivalent functionality by
- * holding an array of NutRenderer pointers and switching between them.
- */
+// Multi-font renderer for SMUSH text with ^fXX font switching.
class SmushMultiFont : public GlyphRenderer_v7 {
public:
static const int MAX_FONTS = 5;
@@ -51,13 +40,11 @@ public:
SmushMultiFont(ScummEngine *vm, SmushPlayer *player, bool useOriginalColors);
~SmushMultiFont() override;
- // String drawing methods
void drawString(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int16 col, TextStyleFlags flags);
void drawString(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags);
void drawStringWrap(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int16 col, TextStyleFlags flags);
void drawStringWrap(const char *str, byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags);
- // GlyphRenderer_v7 interface
int draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, uint16 chr) override;
int drawCharV7(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, TextStyleFlags flags, byte chr) override;
int getCharWidth(uint16 chr) const override;
@@ -66,7 +53,6 @@ public:
int setFont(int id) override;
bool newStyleWrapping() const override { return true; }
- // Set the initial/default font
void setDefaultFont(int id) { _defaultFont = id; _currentFont = id; }
private:
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index f0455eea3dc..a0ccfbce972 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -19,11 +19,6 @@
*
*/
-// SmushPlayerRebel2 â RA2-specific SmushPlayer subclass
-//
-// Overrides the virtual hooks defined in SmushPlayer to provide
-// Rebel Assault 2 specific video, font, text and codec handling.
-
#include "common/config-manager.h"
#include "common/endian.h"
#include "common/rect.h"
@@ -45,7 +40,6 @@
namespace Scumm {
-// FUN_00423880 is initialized at startup with a 400000-byte preload buffer.
constexpr int kRebel2LoadBufferSize = 400000;
constexpr int kRebel2GameplaySurfaceWidth = 0x1a8;
constexpr int kRebel2GameplaySurfaceHeight = 0x104;
@@ -126,10 +120,6 @@ void smushDecodeRA2Uncompressed(byte *dst, const byte *src, int left, int top,
}
}
-// ---------------------------------------------------------------------------
-// SmushPlayerRebel2 â construction / destruction
-// ---------------------------------------------------------------------------
-
SmushPlayerRebel2::SmushPlayerRebel2(ScummEngine_v7 *scumm, IMuseDigital *imuseDigital, Insane *insane)
: SmushPlayer(scumm, imuseDigital, insane) {
initGamePlayerFields();
@@ -140,10 +130,6 @@ SmushPlayerRebel2::~SmushPlayerRebel2() {
destroyGamePlayerFields();
}
-// ---------------------------------------------------------------------------
-// Virtual hook overrides
-// ---------------------------------------------------------------------------
-
void SmushPlayerRebel2::initGamePlayerFields() {
_multiFont = nullptr;
_storedFobjData = nullptr;
@@ -169,7 +155,7 @@ void SmushPlayerRebel2::initGamePlayerFields() {
_loadBuffer = nullptr;
_loadBufferSize = 0;
_loadBufferOffset = 0;
- _loadReadOffset = 8; // Original starts reading at offset 8 (skips header)
+ _loadReadOffset = 8;
_lastLoadChunkIdx = -1;
_loadStreamId = 0;
_ra2FrameSourceSkipX = 0;
@@ -226,11 +212,6 @@ void SmushPlayerRebel2::ra2InitAudioTrackSizes() {
memset(_smushTracks[musicTrack].blockPtr, 127, _smushTracks[musicTrack].blockSize);
}
-/**
- * RA2-specific initialization in SmushPlayer::init().
- * 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;
_ra2UsingGameplaySurface = false;
@@ -246,10 +227,7 @@ void SmushPlayerRebel2::initGameVideoState() {
_width = _vm->_screenWidth;
_height = _vm->_screenHeight;
- // Keep the 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.
+ // Playback flag bit 0x20 controls per-frame clearing.
if (_dst != nullptr) {
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
if ((_curVideoFlags & 0x20) == 0) {
@@ -258,19 +236,13 @@ void SmushPlayerRebel2::initGameVideoState() {
}
}
-/**
- * RA2-specific cleanup in SmushPlayer::release().
- * Preserves the stored FOBJ and _frameBuffer across videos.
- */
void SmushPlayerRebel2::releaseGameVideoState() {
free(_lastFobjData);
_lastFobjData = nullptr;
_lastFobjDataSize = 0;
_hasFrameFobjForGost = false;
_ra2NativeFrameNeedsClear = false;
- // FUN_00423880 allocates the STOR overlay buffer once for the SMUSH subsystem,
- // and FUN_004246d0 replays the last stored FOBJ even after a new SAN starts.
- // Level 12 wave segments depend on this when 12P01_B.SAN starts with FTCH.
+ // Keep the stored FOBJ across videos; Level 12 wave segments start with FTCH.
}
bool SmushPlayerRebel2::ra2IsHighResMode() const {
@@ -342,11 +314,6 @@ bool SmushPlayerRebel2::ra2PromoteCurrentFrameToHiRes(int scrollX, int scrollY)
return true;
}
-/**
- * RA2-specific FTCH handling.
- * For Handler 25, skips FTCH to preserve overlays.
- * For other handlers, re-decodes stored FOBJ with current offsets.
- */
bool SmushPlayerRebel2::handleGameFetch(int32 subSize, Common::SeekableReadStream &b) {
int16 ftchUnknown = b.readSint16LE();
int16 ftchX = b.readSint16LE();
@@ -367,7 +334,7 @@ bool SmushPlayerRebel2::handleGameFetch(int32 subSize, Common::SeekableReadStrea
}
}
- // Re-decode stored FOBJ data with current offsets (matching original FUN_004246d0).
+ // Re-decode stored FOBJ data with current offsets.
if (_storedFobjData != nullptr) {
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleGameFetch FTCH: Re-decoding stored FOBJ codec=%d pos=(%d,%d) size=%dx%d dataSize=%d",
_storedFobjCodec, _storedFobjLeft, _storedFobjTop,
@@ -461,9 +428,6 @@ bool SmushPlayerRebel2::handleGameTextResource(uint32 subType, int32 subSize, Co
return true;
}
-/**
- * RA2-specific text rendering using SmushMultiFont for inline font switching.
- */
bool SmushPlayerRebel2::handleGameTextRendering(const char *str, int fontId, int color,
int pos_x, int pos_y, int left, int top,
int width, int height, TextStyleFlags flg) {
@@ -477,11 +441,7 @@ bool SmushPlayerRebel2::handleGameTextRendering(const char *str, int fontId, int
}
SmushFont *SmushPlayerRebel2::getGameFont(int font) {
- // Original exe uses pointer arithmetic to select hi/lo font pairs.
- // Font 0: TALKFONT (TKHIFONT hi-res)
- // Font 1: SMALFONT (SMHIFONT hi-res)
- // Font 2: TITLFONT (TIHIFONT hi-res)
- // Font 3: POVFONT (POHIFONT hi-res)
+ // Font ids map to low/high-res font assets.
const char *ra2FontsLo[] = {
"SYSTM/TALKFONT.NUT",
"SYSTM/SMALFONT.NUT",
@@ -506,15 +466,7 @@ SmushFont *SmushPlayerRebel2::getGameFont(int font) {
return _sf[font];
}
-// ---------------------------------------------------------------------------
-// RA2 string resource loading â separate from shared StringResource
-// ---------------------------------------------------------------------------
-
-// RA2 TRS format differences from standard SCUMM:
-// - Up to ~658 entries (standard only supports 200)
-// - Entries can be empty (no content between header and next #)
-// - Content lines prefixed with //
-// - Multi-line entries preserve newlines (credits, cast lists)
+// RA2 TRS supports many entries, empty entries, and //-prefixed multiline content.
static const int RA2_MAX_STRINGS = 800;
static const int RA2_ETRS_HEADER_LENGTH = 16;
@@ -660,10 +612,6 @@ bool SmushPlayerRebel2::handleGameSetupStrings() {
return true;
}
-/**
- * Reset XPAL delta palette from the current base palette.
- * Prevents stale delta values from a previous video corrupting the palette.
- */
void SmushPlayerRebel2::adjustGamePalette() {
for (int j = 0; j < 768; ++j) {
_shiftedDeltaPal[j] = _pal[j] << 7;
@@ -672,8 +620,7 @@ void SmushPlayerRebel2::adjustGamePalette() {
}
bool SmushPlayerRebel2::shouldLoadAnimHeaderPalette() const {
- // RA2 copies the AHDR palette in handleGameAnimHeader() so it can match
- // FUN_00424640/FUN_00424540 timing without changing the generic player.
+ // AHDR palette changes are applied after frame processing.
return false;
}
@@ -722,19 +669,15 @@ void SmushPlayerRebel2::ra2HandleDeltaPalette(int32 subSize, Common::SeekableRea
setDirtyColors(0, 255);
}
-// RA2-specific AHDR handling. AHDR metadata can describe the original SMUSH
-// backing bitmap, but active low-res gameplay dimensions are selected later by
-// IACT/FOBJ state; keep the virtual-screen pitch safe until then.
+// AHDR metadata can describe a backing bitmap, but active low-res gameplay
+// dimensions are selected later by IACT/FOBJ state.
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.
+ // AHDR replaces _pal before the first FRME, so delay the dirty range
+ // until frame start.
_palDirtyMin = 256;
_palDirtyMax = -1;
_ra2PendingAnimHeaderPalette = true;
@@ -748,12 +691,8 @@ bool SmushPlayerRebel2::handleGameAnimHeader(byte *headerContent) {
_height = _vm->_screenHeight;
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleGameAnimHeader: RA2 AHDR has 0x0 dims - using screen size %dx%d", _width, _height);
} else if (width != _vm->_screenWidth || height != _vm->_screenHeight) {
- // FUN_00407FCB/FUN_0040C3CC update the active frame descriptor from
- // IACT opcode 6. In the low-res path the descriptor remains
- // 320x200 and perspective is applied through FUN_00424510-equivalent
- // FOBJ offsets. Do not let AHDR alone change pitch while _dst still
- // points at the virtual screen; FOBJ selection will allocate a larger
- // surface when an actual oversized frame object is decoded.
+ // Do not let AHDR alone change pitch while _dst still points at the
+ // virtual screen; FOBJ selection allocates larger surfaces when needed.
_width = _vm->_screenWidth;
_height = _vm->_screenHeight;
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleGameAnimHeader: RA2 AHDR %dx%d - using screen size until FOBJ selects a surface",
@@ -766,20 +705,11 @@ bool SmushPlayerRebel2::handleGameAnimHeader(byte *headerContent) {
return true;
}
-// ---------------------------------------------------------------------------
-// RA2 helper methods used by the base SmushPlayer pipeline.
-// ---------------------------------------------------------------------------
-
void SmushPlayerRebel2::handleGameLoad(int32 subSize, Common::SeekableReadStream &b) {
handleLoad(subSize, b);
}
-/**
- * Handle LOAD chunk for Rebel Assault 2.
- *
- * LOAD chunks stream embedded resource data across multiple frames.
- * FUN_00424450 treats word 0 as the stream id and word 1 as the chunk index.
- */
+// LOAD chunks stream embedded resource data across multiple frames.
void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b) {
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad()");
@@ -837,13 +767,10 @@ void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b)
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: Accumulated %d bytes total", _loadBufferOffset);
}
-/**
- * RA2-specific text rendering using SmushMultiFont for inline font switching.
- */
void SmushPlayerRebel2::ra2HandleTextResource(const char *str, int fontId, int color,
int pos_x, int pos_y, int left, int top,
int width, int height, TextStyleFlags flg) {
- // Promote the native 320x200 frame to the 640x400 screen before drawing, else 2x-scaled text lands off-buffer.
+ // Promote native frames before drawing 2x-scaled high-res text.
const bool hiRes = ra2IsHighResMode() && !isRebel2GameplayActive(_insane);
if (hiRes)
ra2PromoteCurrentFrameToHiRes(0, 0);
@@ -870,10 +797,6 @@ void SmushPlayerRebel2::ra2HandleTextResource(const char *str, int fontId, int c
}
}
-/**
- * RA2-specific buffer selection for non-standard FOBJ dimensions.
- * Returns true when the dimensions are valid and updates _dst, _width, _height as needed.
- */
void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int width, int height) {
_ra2FrameObjectOriginalWidth = width;
_ra2FrameObjectOriginalHeight = height;
@@ -929,9 +852,8 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
return true;
}
- // Rebel2 allocates the low-res gameplay target as 424x260 (FUN_00424730).
- // High-res presentation still decodes video into native 320x200/424x260
- // surfaces, then promotes the selected viewport to the 640x400 screen.
+ // Low-res gameplay uses a 424x260 target, then promotes the selected
+ // viewport to the 640x400 screen in high-res mode.
const int screenSize = _vm->_screenWidth * _vm->_screenHeight;
const int nativeScreenSize = 320 * 200;
const int64 fobjSize64 = (int64)width * height;
@@ -1039,7 +961,6 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
const int bufSize = (int)bufSize64;
const bool needsSpecialBuffer = useGameplaySurface || oversizedNative || bufSize > screenSize;
if (needsSpecialBuffer) {
- // Frame is larger than the native target - need special buffer.
if (_specialBuffer == nullptr || bufSize > _specialBufferSize) {
byte *oldDst = _dst;
const int oldWidth = _width;
@@ -1094,7 +1015,6 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Reset _dst to virtual screen for FOBJ %dx%d at (%d,%d)",
width, height, 0, 0);
} else {
- // Large frame was in this video, use _specialBuffer for compositing
_dst = _specialBuffer;
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using _specialBuffer for small FOBJ %dx%d (compositing with large frame)",
width, height);
@@ -1104,10 +1024,6 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
return true;
}
-/**
- * Decode RA2 full-frame delta FOBJ streams into a tight/padded scratch frame,
- * then place the visible rectangle at the FOBJ coordinates.
- */
bool SmushPlayerRebel2::ra2DecodePlacedDeltaCodec(int codec, const uint8 *src, int left, int top,
int width, int height, int pitch, int dataSize) {
if (!src || !_dst || width <= 0 || height <= 0 || pitch <= 0 || dataSize <= 0)
@@ -1220,21 +1136,15 @@ bool SmushPlayerRebel2::ra2DecodePlacedDeltaCodec(int codec, const uint8 *src, i
return true;
}
-/**
- * Dispatch to RA2-specific codec functions.
- * Returns true if the codec was handled, false for standard codecs.
- */
bool SmushPlayerRebel2::ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
int width, int height, int pitch, int dataSize) {
switch (codec) {
case SMUSH_CODEC_RLE_ALT:
- // RA2 codec 3 dispatches directly to FUN_0042CAA0, the opaque RLE path.
smushDecodeRLEOpaque(_dst, src, left, top, width, height, pitch, dataSize);
return true;
case SMUSH_CODEC_RLE:
if ((_curVideoFlags & 0x100) != 0) {
- // Original RA2 RLE dispatch selects the opaque renderer only when
- // render flag 0x100 is set. Cinematic wipe masks use transparent 0.
+ // Render flag 0x100 selects the opaque renderer.
smushDecodeRLEOpaque(_dst, src, left, top, width, height, pitch, dataSize);
return true;
}
@@ -1266,9 +1176,6 @@ bool SmushPlayerRebel2::ra2DecodeCodec(int codec, const uint8 *src, int left, in
}
}
-/**
- * Save raw FOBJ data when STOR is pending (for later re-decoding by FTCH).
- */
void SmushPlayerRebel2::ra2StoreFobjData(int codec, const byte *data, int32 dataSize,
int left, int top, int width, int height) {
free(_storedFobjData);
@@ -1284,10 +1191,6 @@ void SmushPlayerRebel2::ra2StoreFobjData(int codec, const byte *data, int32 data
_storeFrame = false;
}
-/**
- * RA2 GOST chunk handler.
- * Re-renders the most recent frame FOBJ at the supplied ghost position.
- */
void SmushPlayerRebel2::ra2HandleGost(int32 subSize, Common::SeekableReadStream &b) {
if (subSize < 6) {
warning("SmushPlayerRebel2::ra2HandleGost: chunk too small (%d bytes)", subSize);
@@ -1312,8 +1215,7 @@ void SmushPlayerRebel2::ra2HandleGost(int32 subSize, Common::SeekableReadStream
priorityFlags = 0x6000;
}
- // Match FUN_0042cba0 default behavior (flags bit 0 clear): GOST coordinates
- // are relative to the cached FOBJ header position.
+ // GOST coordinates are relative to the cached FOBJ header position.
int left = _lastFobjLeft + ghostX;
int top = _lastFobjTop + ghostY;
@@ -1322,27 +1224,18 @@ void SmushPlayerRebel2::ra2HandleGost(int32 subSize, Common::SeekableReadStream
_lastFobjLeft, _lastFobjTop, left, top,
_lastFobjWidth, _lastFobjHeight, _lastFobjCodec);
- // Priority bits (0x2000/0x4000/0x6000) are currently not modeled in
- // the SMUSH decoders. Coordinate-correct re-decode restores expected
- // RA2 chapter preview behavior.
+ // Priority bits are not modeled in the SMUSH decoders.
ra2PrepareFrameObjectSurface(left, top, _lastFobjWidth, _lastFobjHeight);
decodeFrameObject(_lastFobjCodec, _lastFobjData, left, top,
_lastFobjWidth, _lastFobjHeight, _lastFobjDataSize);
}
-/**
- * RA2 per-frame audio processing.
- */
void SmushPlayerRebel2::handleGameParseNextFrame() {
// Call processDispatches directly since RA2 has no iMUSE.
// This is the mixer cadence; SAUD opcodes provide per-block source rates.
processDispatches(_smushAudioSampleRate / 12);
}
-// ---------------------------------------------------------------------------
-// Frame decode pipeline overrides
-// ---------------------------------------------------------------------------
-
bool SmushPlayerRebel2::handleGameFrameBufferSelect(int codec, int width, int height) {
if ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)) {
return ra2SelectFrameBuffer(codec, width, height);
@@ -1425,10 +1318,8 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
if (srcSkipY)
*srcSkipY = 0;
} else if (isRebel2FullFrameDeltaCodec(codec)) {
- // Codec 37/47 streams are full-frame delta streams, not row-prefixed
- // RLE data. The original FOBJ dispatcher applies FUN_00424510 offsets
- // to the destination coordinates passed to FUN_0042cba0; it does not
- // advance the compressed source by clipped scanlines.
+ // Delta streams are full-frame data, so keep clipped scanlines in the
+ // destination offset instead of advancing the compressed source.
_ra2FrameSourceSkipY = sourceSkipY;
if (srcSkipY)
*srcSkipY = 0;
@@ -1442,8 +1333,8 @@ bool SmushPlayerRebel2::handleGameCodecDecode(int codec, const uint8 *src, int l
if (isRebel2FullFrameDeltaCodec(codec))
return ra2DecodePlacedDeltaCodec(codec, src, left, top, width, height, pitch, dataSize);
- // Codec 23 translucent surfaces: parm2 0x100 prefixes a fresh 256-byte remap table, >=0x100
- // reuses the last one. (Font glyphs call smushDecodeSkipRLE directly and are unaffected.)
+ // Codec 23 translucent surfaces: parm2 0x100 prefixes a remap table;
+ // larger values reuse the last one.
if (codec == SMUSH_CODEC_SKIP_RLE && parm2 >= 0x100) {
if (parm2 == 0x100 && dataSize >= 256) {
memcpy(_ra2SkipRemapTable, src, 256);
@@ -1456,12 +1347,10 @@ bool SmushPlayerRebel2::handleGameCodecDecode(int codec, const uint8 *src, int l
return true;
}
- // Handle RA2-specific codecs (21, 23, 44, 45); return false for standard
- // codecs (RLE) so the base class decodes them.
return ra2DecodeCodec(codec, src, left, top, width, height, pitch, dataSize);
}
-// Mirrors SmushPlayer::handleFrameObject but forwards the FOBJ parm2 word (base discards it).
+// Forward the FOBJ parm2 word kept by RA2 codec 23.
void SmushPlayerRebel2::handleFrameObject(int32 subSize, Common::SeekableReadStream &b) {
assert(subSize >= 14);
if (_skipNext) {
@@ -1490,7 +1379,6 @@ void SmushPlayerRebel2::handleFrameObject(int32 subSize, Common::SeekableReadStr
}
bool SmushPlayerRebel2::handleGameStoreFrame() {
- // RA2 handles STOR via ra2StoreFobjData in handleGameFrameObjectPost
return true;
}
@@ -1537,8 +1425,8 @@ void SmushPlayerRebel2::handleGameFrameStart() {
}
}
- // FUN_00424d70 clears the target buffer before decoding a frame
- // unless playback flags contain 0x20. Codec 21 frames in levels like
+ // Clear the target before decoding unless playback flags contain 0x20.
+ // Codec 21 frames in levels like
// LEV05/05PLAY.SAN only contain non-zero literals, so stale skipped pixels
// must not survive from the previous frame.
if ((_curVideoFlags & 0x20) == 0 && _dst != nullptr) {
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index b1628a1a302..89ec5c65d29 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -38,7 +38,6 @@ protected:
void initGameVideoState() override;
void releaseGameVideoState() override;
bool shouldPreserveFrameBuffer() const override { return true; }
- // Override to keep the FOBJ parm2 field (codec 23 marker) the base reader discards.
void handleFrameObject(int32 subSize, Common::SeekableReadStream &b) override;
bool handleGameFetch(int32 subSize, Common::SeekableReadStream &b) override;
bool handleGameTextResource(uint32 subType, int32 subSize, Common::SeekableReadStream &b) override;
@@ -88,7 +87,6 @@ private:
void ra2HandleFrameAudioChunk(uint32 subType, int32 subSize, Common::SeekableReadStream &b);
void ra2FeedAudio(uint8 *srcBuf, int groupId, int volume, int pan, int16 flags);
- // LOAD chunk streaming buffer (embedded resource data)
byte *_loadBuffer;
int32 _loadBufferSize;
int32 _loadBufferOffset;
@@ -112,7 +110,7 @@ private:
bool _ra2PendingAnimHeaderPalette;
byte _ra2Codec45Palette[0x300];
byte _ra2Codec45Lookup[0x8000];
- byte _ra2SkipRemapTable[256]; // codec 23 translucency remap table
+ byte _ra2SkipRemapTable[256];
bool _ra2SkipRemapValid;
};
Commit: 8d6050a6c9f52ece2fccf59882d75570e578db30
https://github.com/scummvm/scummvm/commit/8d6050a6c9f52ece2fccf59882d75570e578db30
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-22T16:21:10+02:00
Commit Message:
SCUMM: RA1: clean-up of comments for SMUSH code
Changed paths:
engines/scumm/smush/rebel/codec_ra1.cpp
engines/scumm/smush/rebel/smush_player_ra1.cpp
engines/scumm/smush/rebel/smush_player_ra1.h
diff --git a/engines/scumm/smush/rebel/codec_ra1.cpp b/engines/scumm/smush/rebel/codec_ra1.cpp
index 3620dea06df..a6ba915d1cc 100644
--- a/engines/scumm/smush/rebel/codec_ra1.cpp
+++ b/engines/scumm/smush/rebel/codec_ra1.cpp
@@ -19,8 +19,6 @@
*
*/
-// Rebel Assault 1 SMUSH video codecs
-
#include "scumm/smush/rebel/codec_ra1.h"
#include "common/endian.h"
@@ -29,11 +27,7 @@
namespace Scumm {
-/**
- * RA1 codec 1: RLE with transparency on pixel 0.
- * Same BOMP encoding as smushDecodeRLE but pixel value 0 is not written,
- * allowing the background (restored via FTCH) to show through.
- */
+// RLE with transparency on pixel 0.
void smushDecodeRA1Transparent(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize) {
if (dst == nullptr || src == nullptr || width <= 0 || height <= 0 || pitch <= 0 || dataSize <= 0)
return;
@@ -82,10 +76,7 @@ void smushDecodeRA1Transparent(byte *dst, const byte *src, int left, int top, in
}
}
-/**
- * RA1 codec 21: Skip/copy line codec (FUN_10D41). Clip copy runs without
- * changing source X; stored cockpit patches can legitimately start offscreen.
- */
+// Codec 21 clips copy runs without changing source X.
void smushDecodeRA1SkipCopy(byte *dst, const byte *src, int left, int top, int width, int height,
int pitch, int bufWidth, int bufHeight, int dataSize) {
if (dst == nullptr || src == nullptr || width <= 0 || height <= 0 || pitch <= 0 || dataSize <= 0)
@@ -139,9 +130,7 @@ void smushDecodeRA1SkipCopy(byte *dst, const byte *src, int left, int top, int w
}
}
-/**
- * RA1 codec 23: Additive line-update overlay (FUN_10B40).
- */
+// Codec 23 applies additive palette deltas to existing pixels.
void smushDecodeRA1AdditiveLineUpdate(byte *dst, const byte *src, int left, int top, int width, int height,
int pitch, int bufWidth, int bufHeight, uint8 paletteBase, int dataSize) {
if (dst == nullptr || src == nullptr || width <= 0 || height <= 0 || pitch <= 0 || dataSize <= 0)
@@ -182,9 +171,7 @@ void smushDecodeRA1AdditiveLineUpdate(byte *dst, const byte *src, int left, int
}
}
-/**
- * RA1 codec 2: Scatter/point draw (FUN_110D7).
- */
+// Codec 2 scatter/point draw.
void smushDecodeRA1Scatter(byte *dst, const byte *src, int left, int top, int bufWidth, int bufHeight, int pitch, int dataSize) {
if (dst == nullptr || src == nullptr || pitch <= 0 || dataSize <= 0)
return;
@@ -204,7 +191,7 @@ void smushDecodeRA1Scatter(byte *dst, const byte *src, int left, int top, int bu
}
}
-// RA1 codec 4/5: block-based dithered codec with 4x4 tile lookup tables
+// Codecs 4/5 use 4x4 tile lookup tables.
static uint8 s_ra1C4Tbl[2][256][16];
static uint16 s_ra1C4Param = 0xFFFF;
@@ -318,11 +305,9 @@ void smushDecodeRA1Block(byte *dst, const byte *src, int left, int top, int widt
}
}
- /* post processing of the hiquality implementations of codec4/5,
- * see e.g. ASSAULT.EXE 121e8 - 12242
- */
+ // High-quality block modes blend tile edges after each 4x4 block.
if (x <= 0 || y <= 0 || (x + 4) >= mx || (y + 4) >= my)
- continue; /* skip unreachable edges */
+ continue;
const uint32 dstoff = y * pitch + x;
if (s_ra1C4Param & 0x80) {
for (int k = 0; k < 4; k++)
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.cpp b/engines/scumm/smush/rebel/smush_player_ra1.cpp
index 69c338d5ba6..baa4a5f303e 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra1.cpp
@@ -19,11 +19,6 @@
*
*/
-// Rebel Assault 1 specific SmushPlayer methods.
-//
-// Keep these in a dedicated file so the shared smush_player.cpp stays close
-// to upstream while RA1 behavior is isolated in one place.
-
#include "common/config-manager.h"
#include "common/endian.h"
#include "common/memstream.h"
@@ -60,9 +55,8 @@ static void ra1ApplyCenteredFetchPlacement(InsaneRebel1 *rebel1, int width, int
const int projectedLeft = (int)centerX - (width >> 1);
const int projectedTop = (int)centerY - (height >> 1);
- // RestoreStoredFramePatch routes FTCH through DispatchFobjCodec with flag 0x800.
- // That path applies ProjectPointToScreen() to the center point, then only moves
- // a quarter of the projected delta before decoding the stored FOBJ.
+ // FTCH placement projects the stored object's center, then applies only a
+ // quarter of the delta.
left -= ((projectedLeft - left) >> 2);
top -= ((projectedTop - top) >> 2);
}
@@ -209,9 +203,7 @@ void SmushPlayerRebel1::resetGameVideoState() {
}
void SmushPlayerRebel1::initGameVideoState() {
- // Some RA1 ANMs inherit the current SMUSH palette. SmushPlayer::play()
- // clears the dirty range before init(), so re-push the palette that was
- // restored or inherited before this video starts.
+ // Some RA1 ANMs inherit the active SMUSH palette.
setDirtyColors(0, 255);
}
@@ -274,22 +266,15 @@ bool SmushPlayerRebel1::handleGameFetch(int32 subSize, Common::SeekableReadStrea
const bool projectedCockpitPatch = (gameOp == 0x0B &&
((rebel1->getCurrentLevel() == 4 && rebel1->getLevelGameplayPhase() == 2) ||
rebel1->getCurrentLevel() == 7));
- // Keep the direct viewport placement used by the existing 0x0B
- // compatibility path, except for the Level 5 part 2 and Level 8
- // cockpit patches.
- // DOS FTCH goes through FUN_28D0A, which sets DispatchFobjCodec
- // flag 0x800; these cockpit patches depend on that quarter-projection
- // so the cockpit and flow-script indicators move together.
+ // Most interactive patches follow the viewport directly; selected
+ // cockpit patches need projected center placement to keep indicators aligned.
if (fullWidthStoredPatch || (gameOp == 0x0B && !projectedCockpitPatch) ||
gameOp == 0x19 || gameOp == 0x1A) {
left += _ra1ViewportOffsetX;
top += _ra1ViewportOffsetY;
} else {
ra1ApplyCenteredFetchPlacement(rebel1, _storedFobjWidth, _storedFobjHeight, left, top);
- // RA1 camera emulation currently uses a source-window crop
- // for interactive scenes. FTCH placement from the original executable
- // is computed in fixed presentation space, so convert it back into the
- // cropped buffer space used by the current renderer.
+ // Convert projected presentation-space placement into the cropped buffer.
left += _ra1ViewportOffsetX;
top += _ra1ViewportOffsetY;
}
@@ -345,9 +330,7 @@ void SmushPlayerRebel1::ra1HandleGost(int32 subSize, Common::SeekableReadStream
_frame, ghostType, priorityFlags, ghostX, ghostY,
_lastFobjWidth, _lastFobjHeight, _lastFobjCodec);
- // DOS reuses the most recent FOBJ payload for RA1 GOST and places it at the
- // absolute BE32 coordinates stored in the chunk. Priority bits are identified
- // here but not yet modeled in the generic decode path.
+ // GOST reuses the latest FOBJ payload at absolute BE32 coordinates.
decodeFrameObject(_lastFobjCodec, _lastFobjData, ghostX, ghostY,
_lastFobjWidth, _lastFobjHeight, _lastFobjDataSize);
}
@@ -364,8 +347,7 @@ bool SmushPlayerRebel1::handleGameTextResource(uint32 subType, int32 subSize, Co
b.seek(textStart, SEEK_SET);
}
- // Original FUN_1FDBC draws RA1 TEXT when the text starts with '.' regardless
- // of the DIALOGUE TEXT option. O1OPEN uses that path for the opening lines.
+ // TEXT chunks starting with '.' are forced even when subtitles are disabled.
if (forceText || ConfMan.getBool("subtitles"))
ra1HandleText(subSize, b);
return true;
@@ -396,8 +378,7 @@ void SmushPlayerRebel1::ra1HandleDeltaPalette(int32 subSize, Common::SeekableRea
_shiftedDeltaPal[0] = 0;
int32 remaining = payloadBytes;
if (remaining >= 2) {
- // The original loop starts at palette component 1, leaving component
- // 0 black and ignoring the first delta word in the XPAL payload.
+ // XPAL delta tables start at component 1, leaving palette entry 0 black.
b.skip(2);
remaining -= 2;
}
@@ -415,9 +396,7 @@ void SmushPlayerRebel1::ra1HandleDeltaPalette(int32 subSize, Common::SeekableRea
if (remaining > 0)
b.skip(remaining);
- // Command 2 in the DOS dispatcher first restores the palette state before
- // loading a new delta table. The active palette lives in _pal, so
- // marking it dirty is the corresponding visible-side effect.
+ // Command 2 reloads delta data after restoring the visible palette.
if (command == 2)
setDirtyColors(0, 255);
return;
@@ -533,7 +512,7 @@ void SmushPlayerRebel1::handleGameParseNextFrame() {
}
bool SmushPlayerRebel1::handleGameFrameBufferSelect(int codec, int width, int height) {
- // RA1 sub-fullscreen frames render into _specialBuffer at their (left, top) offset position.
+ // RA1 decodes into the fixed 384x242 buffer.
int bufSize = kRA1DecodeWidth * kRA1DecodeHeight;
if (_specialBuffer == nullptr || bufSize > _specialBufferSize) {
free(_specialBuffer);
@@ -546,7 +525,7 @@ bool SmushPlayerRebel1::handleGameFrameBufferSelect(int codec, int width, int he
bool SmushPlayerRebel1::handleGameDimensionOverride(int codec, int width, int height) {
if (_dst == _specialBuffer) {
- // RA1: sub-fullscreen FOBJs should not override the 384x242 dimensions.
+ // Keep sub-fullscreen FOBJs inside the fixed RA1 buffer.
_width = kRA1DecodeWidth;
_height = kRA1DecodeHeight;
return true;
@@ -557,23 +536,18 @@ bool SmushPlayerRebel1::handleGameDimensionOverride(int codec, int width, int he
bool SmushPlayerRebel1::handleGameAdjustCoords(int codec, int &left, int &top, int &width, int &height, int pitch, int *srcSkipY) {
_ra1FrameSourceSkipY = 0;
- // RA1 additive codec (SKIP_RLE) and scatter (RA1_SCATTER) use absolute
- // positions â they must NOT be clipped/adjusted.
+ // Additive and scatter codecs use absolute positions.
if (codec == SMUSH_CODEC_SKIP_RLE || codec == SMUSH_CODEC_RA1_SCATTER)
return false;
- // RA1 block codecs are column-major tile streams, not row-prefixed RLE
- // streams. Preserve the source data and let smushDecodeRA1Block() consume
- // offscreen tiles while clipping destination pixels.
+ // Block codecs consume column-major tiles while clipping destination pixels.
if (codec == SMUSH_CODEC_RA1_DELTA || codec == SMUSH_CODEC_RA1_BLOCK) {
left += _fobjOffsetX;
top += _fobjOffsetY;
return false;
}
- // RA1 codec 21 is source-X sensitive: generic left clipping would reduce
- // the destination width without skipping the corresponding source columns.
- // Keep only the global FOBJ offset here and let the codec clip each run.
+ // Codec 21 clips each run itself to preserve source X.
if (codec == SMUSH_CODEC_LINE_UPDATE) {
left += _fobjOffsetX;
top += _fobjOffsetY;
@@ -617,10 +591,7 @@ bool SmushPlayerRebel1::handleGameCodecDecode(int codec, const uint8 *src, int l
case SMUSH_CODEC_SKIP_RLE: {
const int bufWidth = pitch;
const int bufHeight = (_dst == _specialBuffer) ? _height : _vm->_screenHeight;
- // Codec 23 uses the high byte of the FOBJ codec word as the palette
- // band for its additive delta. The event-mask path may subtract 0x10
- // from this value before decoding, which Level 8 uses for the walker
- // armor layers.
+ // Codec 23 uses the FOBJ high byte as the additive palette band.
smushDecodeRA1AdditiveLineUpdate(_dst, src, left, top, width, height,
pitch, bufWidth, bufHeight, param, dataSize);
return true;
@@ -633,7 +604,7 @@ bool SmushPlayerRebel1::handleGameCodecDecode(int codec, const uint8 *src, int l
}
bool SmushPlayerRebel1::handleGameStoreFrame() {
- // RA1 handles STOR via handleGameFrameObjectPost
+ // STOR is handled after the RA1 FOBJ fields are parsed.
return true;
}
@@ -644,7 +615,7 @@ void SmushPlayerRebel1::handleGameFrameObjectPre(int codec, int left, int top, i
void SmushPlayerRebel1::handleGameFrameObjectPost(int codec, const byte *data, int32 dataSize, int left, int top, int width, int height) {
rememberLastFobj(codec, data, dataSize, left, top, width, height);
- // RA1 STOR handling remains in handleFrameObject (needs ra1Param/rawLeft/rawTop)
+ // STOR needs the RA1-specific FOBJ fields parsed by handleFrameObject().
}
void SmushPlayerRebel1::handleGameFrameStart() {
@@ -662,10 +633,6 @@ void SmushPlayerRebel1::handleGameProcessAudio(int16 feedSize) {
}
}
-// ---------------------------------------------------------------------------
-// handleFrameObject override â RA1 FOBJ has extra fields in the codec word
-// ---------------------------------------------------------------------------
-
void SmushPlayerRebel1::handleFrameObject(int32 subSize, Common::SeekableReadStream &b) {
assert(subSize >= 14);
if (_skipNext) {
@@ -697,7 +664,7 @@ void SmushPlayerRebel1::handleFrameObject(int32 subSize, Common::SeekableReadStr
handleGameFrameObjectPost(codec, chunk_buffer, chunk_size, left, top, width, height);
- // RA1 STOR: save raw FOBJ with original (pre-clipped) coords and full codec byte
+ // Store the raw FOBJ coordinates and full RA1 codec word.
if (_storeFrame) {
free(_storedFobjData);
_storedFobjData = (byte *)malloc(chunk_size);
@@ -716,7 +683,7 @@ void SmushPlayerRebel1::handleFrameObject(int32 subSize, Common::SeekableReadStr
_storeFrame = false;
}
- // RA1 target check â Insane can reject certain FOBJs
+ // Insane can reject specific target FOBJs.
if (_insane) {
InsaneRebel1 *rebel1 = static_cast<InsaneRebel1 *>(_insane);
if (!rebel1->handleFrameObjectTarget((int16)ra1ObjectId, (int16)rawLeft, (int16)rawTop,
@@ -732,10 +699,6 @@ void SmushPlayerRebel1::handleFrameObject(int32 subSize, Common::SeekableReadStr
free(chunk_buffer);
}
-// ---------------------------------------------------------------------------
-// handleFrame override â RA1 frame parsing with alignment, OBJ chunks, clean frame
-// ---------------------------------------------------------------------------
-
static bool ra1ScanFrameGameChunks(Common::SeekableReadStream &b, int32 frameSize, uint32 &opcodeMask) {
opcodeMask = 0;
const int64 frameStart = b.pos();
@@ -916,7 +879,7 @@ void SmushPlayerRebel1::ra1HandleObjOverlayFrameChunk(int32 objDataSize, Common:
}
bool SmushPlayerRebel1::ra1HandleUnknownFrameChunk(uint32 subType, int32 subSize) {
- // Original FUN_1FDBC: unknown uppercase tag -> silently stop
+ // Unknown uppercase frame tags silently end the frame.
byte tb0 = (subType >> 24) & 0xFF, tb1 = (subType >> 16) & 0xFF;
byte tb2 = (subType >> 8) & 0xFF, tb3 = subType & 0xFF;
if (tb0 > 0x40 && tb0 < 0x5B && tb1 > 0x40 && tb1 < 0x5B &&
@@ -1025,7 +988,6 @@ void SmushPlayerRebel1::handleFrame(int32 frameSize, Common::SeekableReadStream
preserveFrameHistory = interactive && !forceClear && frameHasGameChunk;
}
- // Restore clean frame for delta source
if (preserveFrameHistory &&
_ra1HasCleanFrame && _ra1CleanFrame &&
_dst && _width > 0 && _height > 0) {
@@ -1037,7 +999,6 @@ void SmushPlayerRebel1::handleFrame(int32 frameSize, Common::SeekableReadStream
if (_insanity)
_insane->procPreRendering(_dst);
- // Clear buffer for non-interactive frames to avoid trails
if (_dst && _width > 0 && _height > 0) {
if (!preserveFrameHistory)
memset(_dst, 0, _width * _height);
@@ -1048,7 +1009,6 @@ void SmushPlayerRebel1::handleFrame(int32 frameSize, Common::SeekableReadStream
while (chunks.next(chunk)) {
const int32 subSize = (int32)chunk.size;
- // Guard against consuming next frame marker
if (chunk.tag == MKTAG('F','R','M','E')) {
b.seek(chunk.offset, SEEK_SET);
break;
@@ -1058,16 +1018,13 @@ void SmushPlayerRebel1::handleFrame(int32 frameSize, Common::SeekableReadStream
continue;
chunks.skip(chunk);
- // RA1 uses top-of-loop alignment, not bottom-of-loop padding
}
- // Re-render cockpit overlay
if (_ra1ObjOverlayData != nullptr && _frame > 0) {
Common::MemoryReadStream overlayStream(_ra1ObjOverlayData, _ra1ObjOverlayDataSize);
handleFrameObject(_ra1ObjOverlayDataSize, overlayStream);
}
- // Save clean frame for next delta
if (preserveFrameHistory && _dst && _width > 0 && _height > 0) {
const int frameBytes = _width * _height;
byte *newClean = (byte *)realloc(_ra1CleanFrame, frameBytes);
@@ -1092,10 +1049,6 @@ void SmushPlayerRebel1::handleFrame(int32 frameSize, Common::SeekableReadStream
_frame++;
}
-// ---------------------------------------------------------------------------
-// handleGameUpdateScreen â RA1 viewport-aware screen blit
-// ---------------------------------------------------------------------------
-
void SmushPlayerRebel1::handleGameUpdateScreen(const byte *src, int srcPitch, int width, int height) {
if (_dst == nullptr || _width <= 0 || _height <= 0)
return;
@@ -1144,8 +1097,7 @@ void SmushPlayerRebel1::handleGameUpdateScreen(const byte *src, int srcPitch, in
}
memset(_ra1PresentationBuffer, 0, presentationSize);
- // ResetPlaybackViewport() (0x20A53) initializes the interactive draw window
- // to (4,4,312,192), leaving a black presentation frame around cockpit scenes.
+ // Interactive gameplay draws a 312x192 viewport inside a black 320x200 frame.
const byte *dst = sourceBase + srcY * sourcePitch + srcX;
byte *presentationDst = _ra1PresentationBuffer +
kRA1PresentationBorder * kRA1PresentationScreenWidth + kRA1PresentationBorder;
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.h b/engines/scumm/smush/rebel/smush_player_ra1.h
index e5ea0da7095..c5c29052198 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.h
+++ b/engines/scumm/smush/rebel/smush_player_ra1.h
@@ -74,16 +74,16 @@ private:
Common::SeekableReadStream &b, bool fastForwarding);
void ra1InitAudioTrackSizes();
- // RA1 clean frame buffer for delta source restoration
+ // Clean frame buffer used as the next delta source.
byte *_ra1CleanFrame;
int32 _ra1CleanFrameSize;
bool _ra1HasCleanFrame;
- // RA1 interactive movies present a 312x192 viewport inside a black 320x200 frame.
+ // Interactive movies present a 312x192 viewport inside a black frame.
byte *_ra1PresentationBuffer;
int32 _ra1PresentationBufferSize;
- // RA1 OBJ overlay FOBJ â cockpit drawn once frame 0, re-rendered every frame
+ // OBJ cockpit overlay decoded once and replayed each frame.
byte *_ra1ObjOverlayData;
int32 _ra1ObjOverlayDataSize;
int _ra1ObjOverlayCodec;
@@ -92,14 +92,13 @@ private:
int _ra1ObjOverlayWidth;
int _ra1ObjOverlayHeight;
- // RA1 viewport scroll offset for interactive gameplay
+ // Interactive viewport scroll offset.
int _ra1ViewportOffsetX;
int _ra1ViewportOffsetY;
int _ra1FrameSourceSkipY;
bool _ra1LastFrameObjectVisible;
- // RA1 FADE chunks update the visible 320x200 screen through a sparse
- // copy mask, separate from the decoded frame buffer.
+ // FADE chunks update the visible screen through a sparse copy mask.
byte *_ra1FadeFrame;
int32 _ra1FadeFrameSize;
int _ra1FadeFrameWidth;
Commit: 2d54fc2225ecd1ff73e89023a0c44b218c74f01d
https://github.com/scummvm/scummvm/commit/2d54fc2225ecd1ff73e89023a0c44b218c74f01d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-22T16:21:10+02:00
Commit Message:
SCUMM: RA: clean-up of remaining comments
Changed paths:
engines/scumm/insane/rebel1/audio.cpp
engines/scumm/insane/rebel1/iact.cpp
engines/scumm/insane/rebel1/levels.cpp
engines/scumm/insane/rebel1/menu.cpp
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/rebel.h
engines/scumm/insane/rebel1/render.cpp
engines/scumm/insane/rebel1/runlevels.cpp
engines/scumm/insane/rebel2/audio.cpp
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/levels.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/insane/rebel2/runlevels.cpp
engines/scumm/smush/rebel/codec_ra2.cpp
engines/scumm/smush/rebel/smush_multi_font.cpp
engines/scumm/smush/rebel/smush_player_ra1.cpp
engines/scumm/smush/rebel/smush_player_ra2.cpp
diff --git a/engines/scumm/insane/rebel1/audio.cpp b/engines/scumm/insane/rebel1/audio.cpp
index 83a52d9f50a..cc885a00ae4 100644
--- a/engines/scumm/insane/rebel1/audio.cpp
+++ b/engines/scumm/insane/rebel1/audio.cpp
@@ -44,8 +44,6 @@ const char *const kRA1SfxFiles[8] = {
"SYS/BLAST.SAD"
};
-// Audio
-
void InsaneRebel1::initAudio(int sampleRate) {
_audio.init(_vm, sampleRate);
}
diff --git a/engines/scumm/insane/rebel1/iact.cpp b/engines/scumm/insane/rebel1/iact.cpp
index a7473fff597..d0913e565e6 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -54,7 +54,6 @@ inline int16 stepRebel1Op0BReticleAxis(int axisValue) {
if (axisValue >= 0)
return (int16)(axisValue >> 4);
- // Preserve arithmetic-shift rounding for negative movement.
return (int16)-((-axisValue + 15) >> 4);
}
@@ -623,7 +622,6 @@ bool InsaneRebel1::handleFrameObjectTarget(int16 objectId, int16 left, int16 top
if (objectId >= 0x280 && _frameObjectHitRevealPending) {
// DispatchSmushFrameChunks keeps a one-object latch after a target hit.
// The following high-id FOBJ clears its hidden bit and becomes the
- // replacement/death frame. This is used heavily by Level 9 troopers.
_frameObjectState[byteIndex] &= ~bit;
_frameObjectHitRevealPending = false;
debugC(DEBUG_INSANE, "FOBJ reveal: object=%d frameObjectByte=%d bit=0x%02x",
@@ -699,9 +697,6 @@ void InsaneRebel1::checkDynamicLevelBranch(int32 curFrame) {
if (!_vm->_smushVideoShouldFinish &&
_pendingRouteCutoverFrame >= 0 &&
routeFrame >= (uint32)_pendingRouteCutoverFrame) {
- // Level 7 route switches open the destination ANM as a fresh file.
- // Keep Rebel runtime state, but do not carry SMUSH decoder state
- // from the previous route into the new file.
if (_player && _currentLevel != 6)
_player->setPreserveGameVideoStateOnRelease(true);
_vm->_smushVideoShouldFinish = true;
@@ -723,9 +718,6 @@ void InsaneRebel1::checkDynamicLevelBranch(int32 curFrame) {
if (curFrame < 0)
return;
const uint32 routeFrame = (uint32)curFrame;
- // GAME 0x09 publishes a separate branch-tested cursor position.
- // Keep the drawn ship center and the 0x09 aim cursor split,
- // so compare the effective gameplay cursor here.
const int16 branchX = getGameplayCursorX();
const int route = CLIP<int>(_levelRouteIndex, 0, 5);
for (int nextRoute = 1; nextRoute < 6; ++nextRoute) {
@@ -844,7 +836,6 @@ void InsaneRebel1::updateFlightVariantCursor() {
if (_rollAccum > 0)
xScale = -xScale;
- // Keep the flight sprite center and aim cursor separate.
const int16 shipBaseX = _shipPosX;
const int16 shipBaseY = _shipPosY;
const int32 liftTerm = (int32)_liftSmooth - 0x0F;
@@ -1038,7 +1029,6 @@ void InsaneRebel1::preprocessMouseAxes(int16 &inputX, int16 &inputY, bool *usedJ
void InsaneRebel1::updateShipPhysics() {
_frameCounter++;
- // Reset ship accumulators and camera when a 0x07 stream starts.
if (_gameCounter == 0) {
_posAccumX = 0;
_posAccumY = 0;
@@ -1076,9 +1066,7 @@ void InsaneRebel1::updateShipPhysics() {
else if (_activeInputSource == kInputSourceJoystickDigital)
inputSourceName = "joystick-dpad";
- // Normal mode: accumulate. For absolute mouse input in flight handlers,
- // steer toward a bounded roll target so holding the cursor off center does
- // not continue accelerating the ship until it clamps.
+ // Mouse flight input steers toward a bounded roll target instead of accumulating.
if ((effectiveOpcode == 0x07 || effectiveOpcode == 0x09) &&
_activeInputSource == kInputSourceMouse && !usedJoystick) {
const int32 targetRoll = (int32)inputX * kRA1MouseFlightRollTargetScale;
@@ -1119,11 +1107,9 @@ void InsaneRebel1::updateShipPhysics() {
_posAccumY += deltaY;
_posAccumY = CLIP<int32>(_posAccumY, -0x3200, 0x4600);
- // Ship position = base + offset
_shipPosX = kRA1CenterX + (int16)(_posAccumX >> 8);
_shipPosY = kRA1CenterY + (int16)(_posAccumY >> 8);
- // Clamp to screen bounds.
_shipPosX = CLIP<int16>(_shipPosX, kRA1MinX, kRA1MaxX);
_shipPosY = CLIP<int16>(_shipPosY, kRA1MinY, kRA1MaxY);
@@ -1220,12 +1206,9 @@ void InsaneRebel1::updateShipPhysics() {
_screenShakeEnabled = (_screenFlash > 0);
}
- // Clear per-frame damage flags
_damageFlags = 0;
// After this point, drift goes strongly negative (pushing ship left for the hard path).
- // If ship is right of center, player chose the hard branch â switch to L1PLAY1R.
- // Keep this as a one-shot decision: once threshold is reached, lock path.
if (_pathBranchEnabled && _gameCounter >= kPathBranchCounter) {
if (_shipPosX > kRA1CenterX) {
_rightPathSelected = true;
@@ -1247,7 +1230,6 @@ void InsaneRebel1::updateShipPhysics() {
_corridorLeftX, _corridorTopY, _corridorRightX, _corridorBottomY);
}
-// Update turret sprite direction from the current frame's movement.
void InsaneRebel1::updateTurretShipDirection(int16 offsetY) {
int dir = 0;
if (_flyControlMode == 2) {
@@ -1408,7 +1390,6 @@ void InsaneRebel1::updateTurretPhysics() {
_perspectiveX = CLIP<int16>((int16)(offsetX + 0x20), 0, 0x40);
_perspectiveY = CLIP<int16>((int16)(offsetY + 0x17), 0, 0x2E);
- // Keep projection bending tied to turret roll.
rebuildProjectionTable((int16)((-_rollAccum) >> 9), 0x0D);
if ((_frameCounter & 0x1F) == 0) {
@@ -1464,7 +1445,6 @@ void InsaneRebel1::updateScreenFlashPalette() {
// Ship position = averaged input + center offset.
// Viewport = second history buffer for smooth camera scrolling.
void InsaneRebel1::updateGameOp0BPhysics() {
- // Keep smoothing short for responsiveness.
const int gameOp0BSmoothWindow = 2;
const bool level15Phase1 = (_currentLevel == 14 && _levelGameplayPhase == 1);
@@ -1522,7 +1502,6 @@ void InsaneRebel1::updateGameOp0BPhysics() {
_health++;
}
- // No cooldown â all three damage types can stack each frame
if (!_noDamage && _damageFlags != 0 && _health >= 0 && _deathTimer < 1) {
const int16 oldHealth = _health;
const byte appliedDamageFlags = _damageFlags;
@@ -1534,7 +1513,7 @@ void InsaneRebel1::updateGameOp0BPhysics() {
if (_damageFlags & 0x20)
_health -= _tuning.wham;
if (_health < 0) {
- _deathTimer = 15; // 0x0F â shorter than Level 1's 30
+ _deathTimer = 15;
if (_damageFlags & 0x80)
_deathCauseIndicator = 2;
else
@@ -1559,12 +1538,10 @@ void InsaneRebel1::updateGameOp0BPhysics() {
_gameLatch5D = 0;
_gameLatch5F = 0;
- // Death fade countdown
if (_deathTimer > 1 && _health < 0) {
_deathTimer--;
}
- // Screen flash countdown â screen shake follows flash
if (_screenFlash > 0) {
_screenFlash--;
_screenShakeEnabled = (_screenFlash > 0);
@@ -1773,7 +1750,6 @@ bool InsaneRebel1::isTorpedoModeActive() const {
if ((_gameplayFlags75ff & 0x2) == 0)
return false;
- // Torpedo rendering is valid only during torpedo-run phases.
return (_currentLevel == 3 && _levelGameplayPhase == 2) ||
(_currentLevel == 14 && _levelGameplayPhase == 2);
}
@@ -1791,7 +1767,6 @@ void InsaneRebel1::initOnFootSequence() {
_prevDamageFlags = 0;
_damageCooldown = 0;
- // SetCameraOffset(0,0) â no viewport crop for on-foot levels
_perspectiveX = 0;
_perspectiveY = 0;
resetProjectionTable();
@@ -1870,15 +1845,12 @@ void InsaneRebel1::preprocessOnFootAim(int16 &inputX, int16 &inputY, bool *usedJ
void InsaneRebel1::updateOnFootSequence() {
initOnFootSequence();
- // Track fire button for animation
if (!_playerFired)
_onFootAnimCounter = 0;
else
_onFootAnimCounter++;
- // Walk direction state machine (from HandleGameOp19)
if (_shipDirIndex == 0) {
- // Left edge: snap to center, step character left
_shipDirIndex = 15;
_onFootCharX -= 0x3A;
} else if (_shipDirIndex < 5) {
@@ -1886,7 +1858,6 @@ void InsaneRebel1::updateOnFootSequence() {
} else if (_shipDirIndex < 10) {
_shipDirIndex++;
} else if (_shipDirIndex == 10) {
- // Right edge: snap to center, step character right
_shipDirIndex = 15;
_onFootCharX += 0x3A;
} else if (_onFootAnimCounter < 5 && !_playerSecondaryHeld) {
@@ -1976,7 +1947,6 @@ void InsaneRebel1::procSKIP(int32 subSize, Common::SeekableReadStream &b) {
}
void InsaneRebel1::handleGameOpcode5EReset(uint32 param1) {
- // This is not a pure control-mode assignment.
if (_frameDispatchFlags & 0x40) {
debugC(DEBUG_INSANE, "GAME 0x5E: reset suppressed by dispatch flags=0x%02x",
_frameDispatchFlags);
@@ -2084,11 +2054,9 @@ void InsaneRebel1::handleGameOpcode5FRandomHitLatch(uint32 param1) {
void InsaneRebel1::handleGameOpcode07ShipFlight(int32 subSize, Common::SeekableReadStream &b, uint32 param1) {
_activeGameOpcode = 0x07;
_frameGameOpcodeMask |= (1u << 0x07);
- // Per-frame corridor data: f1=frame counter, f2=max frames, f3=drift bias, f4=unused
- // f3 is the drift/wind parameter combined with tuning table
_gameCounter = param1;
if (subSize >= 20) {
- b.readUint32BE(); // f2 (max frames, unused in physics)
+ b.readUint32BE();
_driftParam = (int16)(int32)b.readUint32BE();
b.readUint32BE();
debugC(DEBUG_INSANE, "GAME 0x07: counter=%d driftParam=%d", _gameCounter, _driftParam);
@@ -2280,7 +2248,6 @@ void InsaneRebel1::handleGameCounterOpcode(uint32 opcode, int32 subSize, Common:
}
}
-// Reads 7x32-bit BE integers from GAME chunk, routes to per-opcode handlers.
void InsaneRebel1::handleGameChunk(int32 subSize, Common::SeekableReadStream &b,
byte *renderBitmap, int width, int height) {
if (subSize < 8)
@@ -2452,11 +2419,10 @@ void InsaneRebel1::checkTargetHit(int16 targetIdx, int16 left, int16 top, int16
_targetCount++;
- // Check proximity: cursor within target + snap + 5 margin
if (curX > left - snap - 5 && curX < right + snap + 5 &&
curY > top - snap - 5 && curY < bottom + snap + 5) {
if (_targetProximity == 0)
- _targetProximity = 1; // Near
+ _targetProximity = 1;
if (slot < kMaxTargetBoxes)
_targetBoxVariant[slot] = CLIP<int16>((int16)(_targetBoxVariant[slot] + 3), 0, 5);
@@ -2465,10 +2431,9 @@ void InsaneRebel1::checkTargetHit(int16 targetIdx, int16 left, int16 top, int16
screenCursorX, screenCursorY, curX, curY, snap, _targetProximity,
_perspectiveX, _perspectiveY);
- // Check tight lock: cursor within target + snap (no extra margin)
if (curX > left - snap && curX < right + snap &&
curY > top - snap && curY < bottom + snap) {
- _targetProximity = 2; // On-target
+ _targetProximity = 2;
if (snap > 0) {
int16 snappedX = (left + right) / 2;
int16 snappedY = (top + bottom) / 2;
@@ -2477,10 +2442,9 @@ void InsaneRebel1::checkTargetHit(int16 targetIdx, int16 left, int16 top, int16
setGameplayCursor(effectiveOpcode, snappedX, snappedY);
}
- // Only one overlapping target may consume the shot each frame.
if (_lastHitTarget == 0) {
for (int i = 0; i < kMaxShotSlots; i++) {
- if (_shotSlots[i].timer == 1) { // Shot in final frame = impact
+ if (_shotSlots[i].timer == 1) {
int gi = _gostSlotIdx;
_gostSlots[gi].targetId = targetIdx + 1;
_gostSlots[gi].frame = 0;
diff --git a/engines/scumm/insane/rebel1/levels.cpp b/engines/scumm/insane/rebel1/levels.cpp
index aeedbc9e76b..77a4fbdb136 100644
--- a/engines/scumm/insane/rebel1/levels.cpp
+++ b/engines/scumm/insane/rebel1/levels.cpp
@@ -67,7 +67,6 @@ bool InsaneRebel1::loadRA1Nut(const char *filename, RA1SpriteBank &bank, bool wa
file->close();
delete file;
- // data[0..3] = AHDR tag, data[4..7] = AHDR size
if (READ_BE_UINT32(data) != MKTAG('A','H','D','R')) {
warning("InsaneRebel1::loadRA1Nut: no AHDR in %s", filename);
free(data);
@@ -85,7 +84,6 @@ bool InsaneRebel1::loadRA1Nut(const char *filename, RA1SpriteBank &bank, bool wa
return false;
}
- // Pass 1: Parse ANIM chunks properly and collect FRME->FOBJ offsets in-order.
uint32 decodedSize = 0;
uint16 foundSprites = 0;
RA1AnimChunkIterator chunks(data, animSize);
@@ -113,11 +111,9 @@ bool InsaneRebel1::loadRA1Nut(const char *filename, RA1SpriteBank &bank, bool wa
bank.decodedSize = decodedSize;
byte *decPtr = bank.decodedData;
- // Pass 2: Decode collected FOBJ entries.
for (uint16 i = 0; i < foundSprites; i++) {
uint32 fobjOffset = fobjOffsets[i];
if (fobjOffset == 0) {
- // Empty FRME (no FOBJ) â leave sprite as blank (zeroed by memset).
continue;
}
@@ -163,7 +159,6 @@ bool InsaneRebel1::loadRA1Nut(const char *filename, RA1SpriteBank &bank, bool wa
}
void InsaneRebel1::loadLevelSprites(int level) {
- // Ship/character direction bank â try BANK1, BANK, then PILOT (Level 9 on-foot)
Common::String bankFile = Common::String::format("LVL%d/L%dBANK1.NUT", level, level);
if (!loadRA1Nut(bankFile.c_str(), _shipBank, false)) {
Common::String legacyBankFile = Common::String::format("LVL%d/L%dBANK.NUT", level, level);
@@ -174,7 +169,6 @@ void InsaneRebel1::loadLevelSprites(int level) {
}
}
- // Secondary ship bank used by some level-specific handlers (e.g. LVL1 mode-2).
Common::String bankFileAlt = Common::String::format("LVL%d/L%dBANK2.NUT", level, level);
if (!loadRA1Nut(bankFileAlt.c_str(), _shipBankAlt, false)) {
debugC(DEBUG_INSANE, "InsaneRebel1::loadLevelSprites: No BANK2 for level %d", level);
@@ -182,7 +176,6 @@ void InsaneRebel1::loadLevelSprites(int level) {
loadRA1Nut("SYS/DISPLAY.NUT", _displayBank);
- // Explosion sprites â try BANG first, then EXPLD
Common::String bangFile = Common::String::format("LVL%d/L%dBANG.NUT", level, level);
if (!loadRA1Nut(bangFile.c_str(), _bangBank, false)) {
Common::String expldFile = Common::String::format("LVL%d/L%dEXPLD.NUT", level, level);
@@ -190,7 +183,6 @@ void InsaneRebel1::loadLevelSprites(int level) {
debugC(DEBUG_INSANE, "InsaneRebel1::loadLevelSprites: No BANG/EXPLD for level %d", level);
}
- // Laser/shot effect sprites
Common::String laserFile = Common::String::format("LVL%d/L%dLASER.NUT", level, level);
loadRA1Nut(laserFile.c_str(), _laserBank);
}
diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index 9636215b453..0ec0c19f47c 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -1132,12 +1132,10 @@ void InsaneRebel1::renderMainMenuItems(byte *dst, int pitch, int width, int heig
const char *const *menuItems = _unlockAllLevels ? kMenuItems : kMenuItemsLocked;
const int mainMenuItemCount = getMainMenuItemCount();
- // Center title
const int titleW = getFontBankStringWidth("MAIN MENU");
const int titleX = getRebel1MenuCenteredX(titleW);
drawMenuTitleText(dst, pitch, width, height, titleX, 30, "MAIN MENU");
- // Draw menu items centered horizontally
for (int i = 0; i < mainMenuItemCount; i++) {
const int textW = getMenuTalkTextWidth(menuItems[i]);
const int textX = getRebel1MenuCenteredX(textW);
@@ -1356,15 +1354,15 @@ void InsaneRebel1::runOptionsMenu() {
_optTextEnabled = !ConfMan.getBool("subtitles");
ConfMan.setBool("subtitles", _optTextEnabled);
break;
- case 5: // Toggle Y-flip controls
+ case 5:
_optControlsYFlip = !_optControlsYFlip;
break;
- case 6: // Toggle held-fire shooting
+ case 6:
_optRapidFire = !_optRapidFire;
break;
- case 7: // Volume â adjusted via left/right in notifyEvent
+ case 7:
break;
- case 8: // Cycle difficulty
+ case 8:
_difficulty = (_difficulty + 1) % 3;
loadTuningForLevel(0);
break;
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index afee7436c2a..d629a5f5171 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -343,14 +343,10 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_levelSelectSel = 0;
_startLevel = 1;
- // Options: read initial state from the mixer.
_optRookieOneFemale = false;
_optMusicEnabled = !_vm->_mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType);
_optSfxEnabled = !_vm->_mixer->isSoundTypeMuted(Audio::Mixer::kSFXSoundType) &&
!_vm->_mixer->isSoundTypeMuted(Audio::Mixer::kSpeechSoundType);
- // Initialize the dialogue-text (subtitles) toggle from the global setting so the
- // game and the in-game DIALOGUE TEXT menu label reflect it. The menu toggle writes the
- // same "subtitles" key, and ra1HandleText() gates rendering on it.
_optTextEnabled = ConfMan.getBool("subtitles");
_optControlsYFlip = false;
_optRapidFire = true;
@@ -379,7 +375,6 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_highScoreEntryIndex = -1;
memset(_textEntryBuffer, 0, sizeof(_textEntryBuffer));
- // Shooting/targeting state
_playerFired = false;
_playerSecondaryHeld = false;
_fireCooldown = 0;
@@ -430,7 +425,6 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
warning("InsaneRebel1::InsaneRebel1(): failed to load title font bank (TITLFONT/TALKFONT)");
}
- // Keep a dedicated TECH font bank for targeting markers/lock indicators.
if (loadRA1Nut("SYS/TECHFONT.NUT", _techFontBank)) {
debugC(DEBUG_INSANE, "InsaneRebel1::InsaneRebel1(): targeting glyph font loaded from SYS/TECHFONT.NUT (%d chars)", _techFontBank.numSprites);
} else if (loadRA1Nut("SYS/TALKFONT.NUT", _techFontBank)) {
@@ -439,13 +433,11 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
warning("InsaneRebel1::InsaneRebel1(): failed to load targeting font bank (TECHFONT/TALKFONT)");
}
- // Audio
initAudio(11025);
memset(_sfxData, 0, sizeof(_sfxData));
memset(_sfxSize, 0, sizeof(_sfxSize));
loadSfx();
- // Null out Insane base class pointers that the default constructor doesn't initialize
_smush_roadrashRip = nullptr;
_smush_roadrsh2Rip = nullptr;
_smush_roadrsh3Rip = nullptr;
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 60c078c3779..2a07c7b119a 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -513,14 +513,12 @@ private:
bool _level14Play2BSpliced;
int32 _level14Play2BSpliceFrame;
- // Main menu and options state.
void runOptionsMenu();
int runPasscodeEntryDialog();
bool runHighScoreNameEntry();
bool handleMenuCommand(RA1MenuCommand command);
bool handleControllerMenuAction(ScummAction action);
bool handleControllerMenuAxis(int16 oldAxisX, int16 oldAxisY);
- // Extra feature: navigate/click the menus with the mouse.
bool handleMenuMouse(const Common::Event &event);
bool handleTextEntryAction(ScummAction action);
bool handleTextEntryKey(const Common::Event &event);
diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index cad22408e14..0c000abb033 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -47,7 +47,6 @@ int ra1GameplayWindowOffsetX(const InsaneRebel1 *rebel1) {
if (!rebel1 || !rebel1->isInteractiveVideoActive())
return 0;
- // Draw gameplay overlays into the cropped buffer's coordinate space.
switch (rebel1->getEffectiveGameOpcode()) {
case 0x07:
case 0x08:
@@ -626,7 +625,6 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
shotOverlayHandled = _gameOp0BOverlayRenderedThisFrame;
drawGameOp0BTargetingAfterFetch = _gameOp0BOverlayRenderedThisFrame;
} else if (onFootMode) {
- // On-foot handler â opcodes 0x19/0x1A (Level 9 Stormtroopers)
if (_currentLevel == 8 && onFootAimMode && !onFootSequenceMode && _killCount > 0) {
_fireCooldown = _playerFired ? 1 : 0;
_vm->_smushVideoShouldFinish = true;
@@ -642,7 +640,6 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
if (onFootAimMode) {
shotOverlayHandled = true;
if (_health >= 0) {
- // Preserve target snap until deferred 0x1A input runs.
const bool preserveTargetSnap = (_targetProximity == 2 && _tuning.snap > 0);
const int16 snappedTargetX = _shipPosX;
const int16 snappedTargetY = _shipPosY;
@@ -656,7 +653,6 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
}
}
- // Draw character sprite at on-foot position (DrawFobjGlyph with flag 0x80).
// GAME 0x1A-only selector clips reuse the targeting handler but do not
// draw the walking character.
if (onFootSequenceMode && _shipBank.numSprites > 0 && _shipDirIndex >= 0 &&
@@ -742,7 +738,6 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
updateFlightVariantCursor();
}
} else if (!shotOverlayHandled) {
- // Keep lock/target accumulators quiescent outside targeting handlers.
_targetProximity = 0;
_prevTargetProx = 0;
_targetCount = 0;
@@ -779,8 +774,6 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
if (_currentLevel == 10)
renderLevelHitsOverlay(renderBitmap, pitch, width, height, 0x16, true);
- // Level 8 (Imperial Walkers) â walker-specific state update + UI overlay.
- // game loop. We call it from procPostRendering when _currentLevel == 7.
if (_currentLevel == 7) {
updateLevel8WalkerState();
const int viewportX = _player ? ra1Player()->_ra1ViewportOffsetX : 0;
@@ -793,9 +786,6 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
_fireCooldown = _playerFired ? 1 : 0;
}
-// Helper that groups the common shot/lock overlay calls used by the
-// box draw; 0x09/0x0A/0x0B include DrawTargetIndicators first. GAME 0x0B
-// pixels over shots/boxes while the lock/fire indicator remains visible.
void InsaneRebel1::renderShotOverlayPipeline(byte *dst, int pitch, int width, int height,
bool drawTargetBoxes, bool drawTargeting) {
if (drawTargetBoxes) {
@@ -898,14 +888,11 @@ void InsaneRebel1::renderTargeting(byte *dst, int pitch, int width, int height)
}
}
- // Save previous proximity for next frame
_prevTargetProx = _targetProximity;
_targetProximity = 0;
_lastHitTarget = 0;
}
-// from inside the L14PLAY2 loop when the current clip reaches maxFrame - 0x0F.
-// call inline and passes the old L14PLAY2 timeline frame to the ANM frame gate.
void InsaneRebel1::handleLevel14Play2BSplice(int32 curFrame, int32 maxFrame) {
if (_currentLevel != 13 || _levelGameplayPhase != 2 || _level14Play2BSpliced ||
_level14Play2BSplicePending || maxFrame < 0x0F)
@@ -918,17 +905,14 @@ void InsaneRebel1::handleLevel14Play2BSplice(int32 curFrame, int32 maxFrame) {
_level14Play2BSplicePending = true;
_level14Play2BSpliceFrame = curFrame;
- // Keep video state live across the L14PLAY2 -> L14PLY2B jump.
if (_player)
_player->setPreserveGameVideoStateOnRelease(true);
- // Clear carried target flags before switching clips.
clearFrameObjectPrimaryBits(1, 0x05);
clearFrameObjectPrimaryBits(2, 0x40);
_vm->_smushVideoShouldFinish = true;
}
-// Maps kill score to tech-font glyph and draws it rising upward from the kill position.
void InsaneRebel1::renderGostScorePopup(byte *dst, int pitch, int width, int height,
int16 centerX, int16 centerY, int16 frame) {
char glyphChar = '\0';
@@ -943,13 +927,11 @@ void InsaneRebel1::renderGostScorePopup(byte *dst, int pitch, int width, int hei
if (glyphChar == '\0')
return;
- // "<<{glyph}" string selects tech font layer via the << markup
char scoreText[4] = { '<', '<', glyphChar, '\0' };
drawFontBankString(dst, pitch, width, height,
centerX - 4, centerY - frame, scoreText);
}
-// Renders explosion sprites from bangBank + per-kill score popup glyphs.
void InsaneRebel1::renderGostSlots(byte *dst, int pitch, int width, int height) {
if ((_gameplayFlags75fe & 0x10) != 0)
return;
@@ -1211,14 +1193,11 @@ void InsaneRebel1::renderLaserShots(byte *dst, int pitch, int width, int height)
const int targetY = CLIP<int>(overlayY + getGameplayCursorY(), 0, height - 1);
if (onFootMode) {
- // HandleGameOp1A_OnFootVariant: single beam from character to crosshair.
- // Gun barrel offset on first frame (timer==5, dirIndex in aiming range).
if (timer == 5 && _shipDirIndex > 10 && _shipDirIndex < 20) {
_shotSlots[i].centerX += kOnFootGunBarrelX[_shipDirIndex];
_shotSlots[i].centerY += kOnFootGunBarrelY[_shipDirIndex];
}
- // Skip visible rendering when flags indicate invisible shots (auto-fire).
if ((_gameplayFlags75fe & 8) != 0)
continue;
@@ -1305,7 +1284,6 @@ void InsaneRebel1::renderLaserShots(byte *dst, int pitch, int width, int height)
continue;
}
- // Fallback for non-turret handlers that still run shot overlays.
int leftStartY = overlayY + 0x96;
int rightStartY = overlayY + 0x96;
uint32 leftFlags = 0x83;
@@ -1577,7 +1555,6 @@ int InsaneRebel1::getFontBankLineAdvance(const char *text) {
}
void InsaneRebel1::renderShip(byte *dst, int pitch, int width, int height) {
- // Hidden during last 20 frames of death sequence (deathTimer 20â0)
if (_health < 0 && _deathTimer <= 20)
return;
@@ -1627,13 +1604,11 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
shipScreenY = overlayY + centerY;
}
- // When dead and deathTimer > 10: random explosion sprites scatter around ship
if (_health < 0 && _deathTimer > 10) {
- int intensity = _deathTimer - 10; // 20â1 as timer goes 30â11
+ int intensity = _deathTimer - 10;
if (intensity > 10)
- intensity = 20 - intensity; // Triangle: 0â10â0
+ intensity = 20 - intensity;
- // di = intensity * 4 + 1 (vertical scatter range)
int rangeY = intensity * 4 + 1;
int rangeX = -20 + intensity * 4;
if (rangeX < 1) rangeX = 1;
@@ -1641,7 +1616,6 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
for (int i = 0; i < intensity; i++) {
int sprIdx = _vm->_rnd.getRandomNumber(_bangBank.numSprites - 1);
- // Random position around ship.
int randX = (int)_vm->_rnd.getRandomNumber(rangeX * 2) - rangeX;
int randY = (int)_vm->_rnd.getRandomNumber(rangeY * 2) - rangeY;
@@ -1655,9 +1629,7 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
return;
}
- // When alive, in cooldown, and bang bank loaded
if (_health >= 0 && _damageCooldown > 0) {
- // Sprite index = 10 - damageCooldown (frames 0â9 as cooldown 10â1)
int sprIdx = _bangBank.numSprites - _damageCooldown;
if (sprIdx < 0 || sprIdx >= _bangBank.numSprites)
return;
@@ -1732,8 +1704,6 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
}
}
- // fill rect at (0x92-health, 8), width=health, height=5, color=0.
- // This is a black "remaining health" fill over the HUD template.
{
int barMaxW = kMaxHealth;
int barH = 5;
@@ -1802,8 +1772,6 @@ const int16 InsaneRebel1::kWalkerAttackWindow1[3] = { 2588, 2323, 877 };
const int16 InsaneRebel1::kWalkerAttackWindow2[3] = { 1709, 1444, -2 };
const int16 InsaneRebel1::kWalkerAttackWindow3[3] = { 262, -2, -2 };
-// updateLevel8WalkerState â Per-frame walker health + attack window logic.
-// Called from procPostRendering when _currentLevel == 7.
void InsaneRebel1::updateLevel8WalkerState() {
if (_walkerHealth >= 11) {
_walkerHealth = (int16)(100 - (_killCount + (_killCount >> 2)));
@@ -1816,9 +1784,6 @@ void InsaneRebel1::updateLevel8WalkerState() {
return;
}
- // contact hazards hit the player. The port synthesizes that damage flag in updateGameOp0BPhysics(),
- // where the 0x0B damage flags are consumed. This is unrelated to _walkerHealth,
- // which is the boss health displayed by the Level 8 overlay.
int route = CLIP(_levelRouteIndex, 0, 2);
uint16 fc = (uint16)_gameCounter;
@@ -1829,7 +1794,6 @@ void InsaneRebel1::updateLevel8WalkerState() {
};
int16 frameNum = (int16)fc;
- // Check if we're inside any attack window (window-100 < frame <= window)
bool inWindow = false;
for (int w = 0; w < 3; w++) {
int16 windowEnd = *windows[w];
@@ -1837,7 +1801,6 @@ void InsaneRebel1::updateLevel8WalkerState() {
if (frameNum > windowEnd - 100 && frameNum <= windowEnd) {
inWindow = true;
- // Reset timer at window start (first frame of window)
if (frameNum == windowEnd - 99)
_walkerTimer = 100;
break;
@@ -1847,7 +1810,6 @@ void InsaneRebel1::updateLevel8WalkerState() {
if (inWindow && _walkerBranchChoice == 0) {
_walkerTimer--;
- // Check if we're in the directional phase (last 50 frames)
bool inDirectionalPhase = false;
for (int w = 0; w < 3; w++) {
int16 windowEnd = *windows[w];
@@ -1859,12 +1821,10 @@ void InsaneRebel1::updateLevel8WalkerState() {
}
if (inDirectionalPhase) {
- // Player can choose direction during last 50 frames
if (_playerFired && _inputAxisDeltaX == 0) {
_walkerBranchChoice = (_shipPosX < 0xA0) ? 1 : 2;
}
} else {
- // Torpedo sound every 8 frames during targeting phase
if ((_gameCounter & 7) == 0)
playSfx(kSfxLockOn, 127, 0);
}
@@ -1880,11 +1840,9 @@ void InsaneRebel1::updateLevel8WalkerState() {
(_shipPosX < 0xA0 && _walkerBranchChoice != 2);
if (goLeft) {
- // Left: branch to route 1 unless at window3 (w==2)
if (w != 2)
newRoute = 1;
} else {
- // Right: branch to route 2 unless at window2 (w==1)
if (w != 1)
newRoute = 2;
}
@@ -1902,16 +1860,11 @@ void InsaneRebel1::updateLevel8WalkerState() {
}
}
-// Draws walker health %, attack timer, directional arrows, and target reticle.
-// 1/4 parallax compensation. We draw into the 384x242 SMUSH buffer, so add the
-// coordinates before the final RA1 crop.
void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int height,
int viewportX, int viewportY) {
if (_currentLevel != 7)
return;
- // Walker health display â "<<WALKER %d%%" at projected cockpit panel point (0x61, 0x8D).
- // Blinks when health < 16: only drawn when (GAME counter & 2) != 0.
if (_walkerHealth > 0 && (_walkerHealth >= 16 || (_gameCounter & 2) != 0)) {
int16 projX = 0x61, projY = 0x8D;
projectGameplayPoint(projX, projY);
@@ -1924,7 +1877,6 @@ void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int heig
viewportX + projX, viewportY + projY, walkerStr);
}
- // Attack window overlay (timer + arrows/reticle)
int route = CLIP(_levelRouteIndex, 0, 2);
const int16 *windows[3] = {
&kWalkerAttackWindow1[route],
@@ -1949,7 +1901,6 @@ void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int heig
if (!inWindow || _walkerBranchChoice != 0)
return;
- // Timer countdown â "<<TIME %d" at projected cockpit panel point (0x62, 0x9C).
{
int16 projX = 0x62, projY = 0x9C;
projectGameplayPoint(projX, projY);
@@ -1963,25 +1914,19 @@ void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int heig
}
if (inDirectionalPhase) {
- // Directional arrows â DrawStringEx(..., flags=0x81) at:
- // left: (0xA6 - projX / 4, 0x92 - projY / 4)
- // right: (0xA8 - projX / 4, 0x93 - projY / 4)
int16 projX = 0, projY = 0;
projectGameplayPoint(projX, projY);
const int16 parallaxX = (int16)(projX >> 2);
const int16 parallaxY = (int16)(projY >> 2);
if (_shipPosX < 0xA0) {
- // Left arrow "<<v"
drawFontBankString(dst, pitch, width, height,
viewportX + 0xA6 - parallaxX, viewportY + 0x92 - parallaxY, "<<v");
} else {
- // Right arrow "<<u"
drawFontBankString(dst, pitch, width, height,
viewportX + 0xA8 - parallaxX, viewportY + 0x93 - parallaxY, "<<u");
}
} else {
- // Target reticle â "<<w" at projected (0xA9, 0x9A), blinks on (frame & 4)
if ((_gameCounter & 4) == 0) {
int16 projX = 0, projY = 0;
projectGameplayPoint(projX, projY);
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index efe0be88e1c..347d092a792 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -142,8 +142,6 @@ static int calculateThresholdBonus(int kills, int perfectThreshold, int perKillT
}
-// Play a passive cinematic (no game callback, skippable).
-// startFrame > 0: fast-forward (decode without display/audio) to that frame.
void InsaneRebel1::playCinematic(const char *filename, int32 startFrame) {
debugC(DEBUG_INSANE, "InsaneRebel1::playCinematic('%s', startFrame=%d)", filename, startFrame);
if (shouldAbortGameFlow())
@@ -152,7 +150,6 @@ void InsaneRebel1::playCinematic(const char *filename, int32 startFrame) {
SmushPlayer *splayer = _vm->_splayer;
_player = splayer;
restoreScreenFlashPalette();
- // Clear passive-cinematic audio before chaining the next ANM.
_audio.reset();
splayer->resetAudioTracks();
applyAudioOptions();
@@ -163,13 +160,9 @@ void InsaneRebel1::playCinematic(const char *filename, int32 startFrame) {
splayer->setFastForwardToFrame(startFrame > 0 ? startFrame : 0);
splayer->play(filename, 15);
- // Level-title text is only meant for the intro cinematic that armed it.
- // Clear it even when the movie ended through ESC, so it cannot leak into
- // the next cutscene or gameplay segment.
_introTextActive = false;
}
-// Queue the END ANM, draw the summary while the frontend pumps, then award points/unlock.
void InsaneRebel1::playChapterCompleteCinematic(const char *filename, int16 unlockedChapter,
int revealOffsetFromEnd, int stopOffsetFromEnd,
const char *bonusLabel1, const char *detailText1, int bonusValue1,
@@ -510,7 +503,6 @@ bool InsaneRebel1::runLevel4() {
_killCount = 0;
_levelGameplayPhase = 0;
- // Phase 1: destroy two shield generators that can be hit repeatedly.
_protectedTargetA = 0x39;
_protectedTargetB = 0x3A;
_shieldGenHitsA = 0;
@@ -545,7 +537,6 @@ bool InsaneRebel1::runLevel4() {
return false;
if (_health >= 0 && shieldGeneratorsDestroyed) {
- // Phase 2: torpedo run.
_activeGameOpcode = 0;
_gameLatch5D = 0;
_gameLatch5F = 0;
@@ -559,7 +550,6 @@ bool InsaneRebel1::runLevel4() {
}
if (_health >= 0 && shieldGeneratorsDestroyed) {
- // L4END1 = torpedo hit, L4END2 = torpedo missed
const bool torpedoHit = (_killCount != 0);
playChapterCompleteCinematic(torpedoHit ? "LVL4/L4END1.ANM" : "LVL4/L4END2.ANM",
4, 0x69, 5, " ", torpedoHit ? "Torpedo Hit" : "Torpedo Missed",
@@ -867,7 +857,6 @@ bool InsaneRebel1::runLevel8() {
}
bool InsaneRebel1::runLevel9() {
- // Preserve the game's deterministic three-bit route selection.
uint8 originalRouteSeed = 0;
auto getOriginalRouteBit = [&originalRouteSeed]() {
originalRouteSeed = (uint8)(originalRouteSeed * 9 + 0x35);
@@ -878,8 +867,6 @@ bool InsaneRebel1::runLevel9() {
const int randPath3 = getOriginalRouteBit();
auto playLevel9PathSelector = [&](const char *filename) {
while (!shouldAbortGameFlow()) {
- // Keep the selector cursor centered instead of inheriting the last
- // walking offset from the previous stormtrooper segment.
_onFootCharX = 0;
_onFootCharY = 0;
_shipPosX = kRA1CenterX;
@@ -1087,7 +1074,6 @@ bool InsaneRebel1::runLevel10() {
return false;
}
-// Turret-style level. Single interactive phase with kill-count retry.
bool InsaneRebel1::runLevel11() {
_currentLevel = 10;
loadLevelSprites(11);
@@ -1115,7 +1101,6 @@ bool InsaneRebel1::runLevel11() {
if (_killCount > 4 || _interactiveVideoCheatSkipped)
break;
- // Not enough kills â retry
playCinematic("LVL11/L11RETRY.ANM");
if (shouldAbortGameFlow())
return false;
@@ -1138,7 +1123,6 @@ bool InsaneRebel1::runLevel11() {
return false;
}
-// Single interactive phase with mid-level retry mechanism.
bool InsaneRebel1::runLevel12() {
_currentLevel = 11;
loadLevelSprites(12);
@@ -1232,8 +1216,6 @@ bool InsaneRebel1::runLevel13() {
return false;
}
-// Two interactive phases: L14PLAY (targeting cannons) + L14PLAY2 (exhaust port approach).
-// â L14END/L14NEW/L14DEATH
bool InsaneRebel1::runLevel14() {
_currentLevel = 13;
loadLevelSprites(14);
@@ -1249,7 +1231,6 @@ bool InsaneRebel1::runLevel14() {
resetLevelAttemptState(1, 1);
_level14SuccessFrames = 0;
- // Phase 1: targeting surface cannons
bool level14Phase1Complete = false;
bool replayingLevel14Phase1 = false;
while (!shouldAbortGameFlow()) {
@@ -1275,7 +1256,6 @@ bool InsaneRebel1::runLevel14() {
bool level14Phase2Complete = false;
if (_health >= 0 && level14Phase1Complete) {
- // Phase 2: exhaust port approach
_activeGameOpcode = 0;
_gameLatch5D = 0;
_gameLatch5F = 0;
@@ -1310,7 +1290,6 @@ bool InsaneRebel1::runLevel14() {
_level14Play2BSplicePending = false;
level14Phase2Video = "LVL14/L14PLY2B.ANM";
- // Preserve gameplay/video state across the continuation clip splice.
playInteractiveVideo(level14Phase2Video, spliceFrame);
if (shouldAbortGameFlow())
return false;
@@ -1346,8 +1325,6 @@ bool InsaneRebel1::runLevel14() {
return false;
}
-// Two interactive phases with mid-level cutscene.
-// â L15PLAY2 (final approach + torpedo) â L15END1/L15NEW/L15DEATH
bool InsaneRebel1::runLevel15() {
_currentLevel = 14;
loadLevelSprites(15);
@@ -1362,19 +1339,16 @@ bool InsaneRebel1::runLevel15() {
loadTuningForLevel(0x13);
resetLevelAttemptState(1, 0);
- // Phase 1: trench run
_levelGameplayPhase = 1;
playInteractiveVideo("LVL15/L15PLAY1.ANM");
if (shouldAbortGameFlow())
return false;
if (_health >= 0) {
- // Torpedo lock cutscene
playCinematic("LVL15/L15INTR2.ANM");
if (shouldAbortGameFlow())
return false;
- // Phase 2: final approach and torpedo shot.
_activeGameOpcode = 0;
_gameLatch5D = 0;
_gameLatch5F = 0;
@@ -1418,7 +1392,6 @@ bool InsaneRebel1::runLevel15() {
return false;
}
-// Main game entry point â called from ScummEngine::go().
void InsaneRebel1::runGame() {
typedef bool (InsaneRebel1::*RunLevelMethod)();
const RunLevelMethod kLevelRunners[] = {
@@ -1498,13 +1471,11 @@ void InsaneRebel1::runGame() {
}
if (!loadedStartupSave) {
- // Play intro sequence (logo + opening)
playIntroSequence();
if (shouldAbortGameFlow())
return;
}
- // Main menu â gameplay loop
while (!_vm->shouldQuit()) {
int menuResult = runMainMenu();
if (_loadRequested) {
@@ -1516,16 +1487,13 @@ void InsaneRebel1::runGame() {
switch (menuResult) {
case 1: {
- // START NEW GAME â sequential play from _startLevel
runLevelsFrom(_startLevel, true);
break;
}
case 2:
- // Game Options
runOptionsMenu();
break;
case 3: {
- // game from the decoded chapter when a valid passcode is entered.
const int passcodeLevel = runPasscodeEntryDialog();
if (passcodeLevel >= 1 && passcodeLevel <= numLevels)
runLevelsFrom(passcodeLevel, true);
@@ -1542,20 +1510,17 @@ void InsaneRebel1::runGame() {
break;
}
case 4: {
- // Level Select: extra start point. Continue through the
int selectedLevel = runLevelSelectMenu();
if (selectedLevel >= 1 && selectedLevel <= numLevels)
runLevelsFrom(selectedLevel, true);
break;
}
case 5:
- // CONTINUE DEMO â attract mode.
showHighScores();
if (!shouldAbortGameFlow())
playCinematic("OPEN/O1OPEN.ANM");
break;
case 6:
- // Exit
return;
default:
break;
@@ -1619,13 +1584,11 @@ void InsaneRebel1::setupInteractiveVideoState(int32 startFrame) {
clearBit(0);
_interactiveVideoActive = true;
if (!preserveRuntimeState) {
- _onFootInitialized = false; // Reset so each segment triggers counter==0 init
+ _onFootInitialized = false;
resetFrameObjectState();
resetGamepadReticleAim();
}
_vm->_smushVideoShouldFinish = false;
- // Preserve the previous video/runtime state, but keep the destination clip
- // fully interactive from its first visible frame.
splayer->setPreserveVideoStateOnNextPlay(preserveVideoState);
splayer->setCurVideoFlags(0x28);
splayer->setFastForwardFromFrame(0);
@@ -1718,7 +1681,6 @@ void InsaneRebel1::playInteractiveVideoFile(const char *filename, int32 videoOff
_interactiveVideoActive = false;
}
-// Play interactive gameplay video (with ship physics + HUD).
void InsaneRebel1::playInteractiveVideo(const char *filename, int32 startFrame) {
debugC(DEBUG_INSANE, "InsaneRebel1::playInteractiveVideo('%s', startFrame=%d)", filename, startFrame);
if (shouldAbortGameFlow()) {
diff --git a/engines/scumm/insane/rebel2/audio.cpp b/engines/scumm/insane/rebel2/audio.cpp
index 6cbc4e3e1f4..663413424b0 100644
--- a/engines/scumm/insane/rebel2/audio.cpp
+++ b/engines/scumm/insane/rebel2/audio.cpp
@@ -34,15 +34,10 @@
namespace Scumm {
-// Audio Handling
-// RA2 doesn't use iMUSE -- audio is handled directly through the mixer.
-
-// initAudio -- Initialize audio system for RA2.
void InsaneRebel2::initAudio(int sampleRate) {
_audio.init(_vm, sampleRate);
}
-// terminateAudio -- Stop all tracks and release audio streams.
void InsaneRebel2::terminateAudio() {
_audio.terminate();
}
@@ -55,23 +50,14 @@ void InsaneRebel2::resetVideoAudio() {
splayer->resetAudioTracks();
}
-// queueAudioData -- Queue raw PCM data for playback on a track.
-// Creates the queuing stream on first use. RA2 audio is 8-bit unsigned mono.
void InsaneRebel2::queueAudioData(int trackIdx, uint8 *data, int32 size, int volume, int pan) {
_audio.queueData(trackIdx, data, size, volume, pan);
}
-// processAudioFrame -- Per-frame audio dispatch (replaces iMUSE path)
-// Iterates SmushPlayer audio tracks, handles FADING->PLAYING transitions,
-// and feeds PCM data through queueAudioData. Called from SmushPlayer when
-// iMUSE is null.
void InsaneRebel2::processAudioFrame(int16 feedSize) {
_audio.processFrame(_player, feedSize);
}
-// Sound Effects (SAD files)
-// Standalone SAUD files from SYSTM/ loaded at init for one-shot SFX.
-
const char *const kRA2SfxFiles[InsaneRebel2::kRA2NumSfx] = {
"SYSTM/BLAST.SAD", // 0 - Player laser fire
"SYSTM/CRASH.SAD", // 1 - Corridor/wall collision
@@ -93,8 +79,6 @@ void InsaneRebel2::loadSfx() {
continue;
}
- // SAUD file structure: SAUD header (8) + STRK sub-chunk + SDAT sub-chunk
- // We scan for the SDAT tag to find the PCM data.
uint32 fileSize = file->size();
if (fileSize < 38) { // Minimum: 8 (SAUD) + 22 (STRK) + 8 (SDAT header)
debugC(DEBUG_INSANE, "InsaneRebel2::loadSfx: %s too small (%d bytes)", kRA2SfxFiles[i], fileSize);
@@ -103,7 +87,6 @@ void InsaneRebel2::loadSfx() {
continue;
}
- // Verify SAUD tag
uint32 tag = file->readUint32BE();
if (tag != MKTAG('S', 'A', 'U', 'D')) {
debugC(DEBUG_INSANE, "InsaneRebel2::loadSfx: %s not a SAUD file (tag=0x%08x)", kRA2SfxFiles[i], tag);
@@ -111,16 +94,14 @@ void InsaneRebel2::loadSfx() {
delete file;
continue;
}
- file->readUint32BE(); // Skip SAUD size
+ file->readUint32BE();
- // Scan for SDAT chunk (skip STRK and any other sub-chunks)
bool foundSdat = false;
while (file->pos() + 8 <= (int64)fileSize) {
uint32 chunkTag = file->readUint32BE();
uint32 chunkSize = file->readUint32BE();
if (chunkTag == MKTAG('S', 'D', 'A', 'T')) {
- // Found PCM data
uint32 pcmSize = MIN(chunkSize, fileSize - (uint32)file->pos());
_sfxData[i] = (byte *)malloc(pcmSize);
if (_sfxData[i]) {
@@ -131,7 +112,6 @@ void InsaneRebel2::loadSfx() {
foundSdat = true;
break;
} else {
- // Skip this sub-chunk
file->seek(chunkSize, SEEK_CUR);
}
}
@@ -145,10 +125,8 @@ void InsaneRebel2::loadSfx() {
}
}
-// freeSfx -- Free all loaded SFX data and auxiliary buffers.
void InsaneRebel2::freeSfx() {
for (int i = 0; i < kRA2NumSfx; i++) {
- // Stop any playing SFX on this slot
_vm->_mixer->stopHandle(_sfxHandles[i]);
free(_sfxData[i]);
_sfxData[i] = nullptr;
@@ -162,7 +140,6 @@ void InsaneRebel2::freeSfx() {
}
}
-// playSfx -- Play a one-shot sound effect (8-bit unsigned mono, 11025 Hz).
void InsaneRebel2::playSfx(int slot, int volume, int pan) {
if (slot < 0 || slot >= kRA2NumSfx || !_sfxData[slot] || _sfxSize[slot] == 0) {
return;
@@ -170,17 +147,14 @@ void InsaneRebel2::playSfx(int slot, int volume, int pan) {
if (_player && !_player->isChanActive(CHN_OTHER))
return;
- // Stop any previous instance of this SFX slot
_vm->_mixer->stopHandle(_sfxHandles[slot]);
- // Make a copy of the PCM data (makeRawStream with DisposeAfterUse::YES will free it)
byte *pcmCopy = (byte *)malloc(_sfxSize[slot]);
if (!pcmCopy) {
return;
}
memcpy(pcmCopy, _sfxData[slot], _sfxSize[slot]);
- // Create a one-shot raw audio stream: 8-bit unsigned mono at 11025 Hz
Audio::SeekableAudioStream *stream = Audio::makeRawStream(
pcmCopy, _sfxSize[slot], 11025, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
@@ -214,7 +188,6 @@ void InsaneRebel2::loadAuxSfx(int buffer, const byte *data, uint32 size) {
debugC(DEBUG_INSANE, "InsaneRebel2::loadAuxSfx: buffer=%d size=%d", buffer, size);
}
-// Handles both raw PCM and SAUD-wrapped data.
void InsaneRebel2::playAuxSfx(int buffer, int volume, int pan) {
if (buffer < 0 || buffer >= kRA2NumAuxSfx || !_auxSfxData[buffer] || _auxSfxSize[buffer] == 0) {
return;
@@ -224,14 +197,11 @@ void InsaneRebel2::playAuxSfx(int buffer, int volume, int pan) {
_vm->_mixer->stopHandle(_auxSfxHandles[buffer]);
- // Check if data has SAUD header; if so, extract PCM from SDAT chunk.
- // Otherwise treat as raw 8-bit unsigned PCM at 11025 Hz.
const byte *pcmStart = _auxSfxData[buffer];
uint32 pcmSize = _auxSfxSize[buffer];
if (pcmSize > 8 && READ_BE_UINT32(pcmStart) == MKTAG('S', 'A', 'U', 'D')) {
- // Parse SAUD container to find SDAT chunk
- uint32 pos = 8; // Skip SAUD tag + size
+ uint32 pos = 8;
while (pos + 8 <= pcmSize) {
uint32 chunkTag = READ_BE_UINT32(pcmStart + pos);
uint32 chunkSize = READ_BE_UINT32(pcmStart + pos + 4);
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 3063a0a8f9a..be6bb0e64ee 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -62,37 +62,19 @@ static bool readLevel2BackgroundChunkHeader(Common::SeekableReadStream &stream,
return true;
}
-// procPreRendering -- Pre-frame setup: background restore and corridor overlays.
-// Restores Level 2 background before FOBJ decoding (Handler 8) and handles
-// Handler 25 corridor overlay positioning.
void InsaneRebel2::procPreRendering(byte *renderBitmap) {
Insane::procPreRendering(renderBitmap);
- // Pan the reticle from held directional controls (on-screen/physical gamepad dpad,
- // keyboard arrows) once per frame. No-op outside gameplay; mouse aiming is unaffected.
updateGameplayAimFromGamepad();
- // Reset opcode 6 init flag at the start of each new video.
- // This ensures the per-wave init (clearBit, link table reset, wave state)
- // fires exactly once per wave video, not every frame.
- // Exception: seamless continuation segments (flag 0x40, e.g. the looping 06PLAY1B attack
- // run) keep the init flag set, otherwise the wave init's clearBit(0) would resurrect
- // shield targets the player already destroyed, resetting the shield on every loop.
+ // Seamless continuation segments keep opcode 6 state across video loops.
if (_player && _player->_frame == 0) {
const bool shieldContinuation = _rebelShieldGateActive && (_player->_curVideoFlags & 0x40) != 0;
if (!shieldContinuation)
_rebelOp6Initialized = false;
}
- // For Level 2 handler 8 gameplay, restore the background BEFORE FOBJ decoding.
- // The tiny FOBJ sprites (7x10, 9x38 pixels) only draw new sprite positions but don't
- // clear old ones. By restoring the full background each frame, we ensure old sprite
- // positions are erased before new ones are drawn.
- // This is called at the start of handleFrame(), before any FOBJ chunks are processed.
- // IMPORTANT: Only restore when the render buffer pitch matches the background pitch (320).
- // Levels like Level 12 (Sewers) use oversized buffers (424x260) where FOBJ/FETCH handles
- // background restoration. Copying the 320-wide background into a 640-wide buffer with
- // hardcoded pitch=320 would corrupt the corridor rendering.
+ // Level 2 sprites rely on a restored 320-wide background before FOBJ decoding.
if (_rebelHandler == 8 && _level2BackgroundLoaded && _level2Background && renderBitmap) {
int bufferPitch = (_player && _player->_width > 0) ? _player->_width : 320;
if (bufferPitch == 320) {
@@ -102,12 +84,7 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
}
}
- // Chapter selection: Set FOBJ offset for O_LEVEL.SAN preview strip.
- // The 80x800 FOBJ strip is at left=320. With offset X=-90, it renders at
- // X=230 (inside the preview box). The Y offset scrolls the strip vertically
- // so only the selected chapter's 50px slice appears at Y=75.
- // STOR captures raw FOBJ data regardless of screen rendering, so the
- // offset on frame 0 doesn't affect the STOR/FTCH mechanism.
+ // Chapter previews are selected by scrolling the O_LEVEL.SAN FOBJ strip.
if (_gameState == kStateChapterSelect && _player) {
if (renderBitmap) {
const int clearWidth = (_player->_width > 0) ? _player->_width : _vm->_screenWidth;
@@ -120,7 +97,6 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
}
}
-// procIACT -- Main IACT chunk dispatcher (overrides Insane::procIACT).
void InsaneRebel2::procIACT(byte *renderBitmap, int32 codecparam, int32 setupsan12,
int32 setupsan13, Common::SeekableReadStream &b, int32 size, int32 flags,
int16 par1, int16 par2, int16 par3, int16 par4) {
@@ -130,13 +106,9 @@ void InsaneRebel2::procIACT(byte *renderBitmap, int32 codecparam, int32 setupsan
if (_keyboardDisable)
return;
- // Handle menu IACT - menu videos have embedded ANIM data in IACT chunks
- // Menu IACTs have par1=8 (code), par2=46 (flags), par4>=1000 (userId)
- // The embedded ANIM contains the full menu frame
if (_gameState == kStateMainMenu && par1 == 8 && par4 >= 1000) {
debugC(DEBUG_INSANE, "IACT: Menu mode - processing embedded ANIM (userId=%d)", par4);
- // Scan for embedded ANIM tag in the IACT data
int64 startPos = b.pos();
int64 totalSize = b.size();
debugC(DEBUG_INSANE, "IACT: stream pos=%d, size=%d, remaining=%d",
@@ -154,7 +126,6 @@ void InsaneRebel2::procIACT(byte *renderBitmap, int32 codecparam, int32 setupsan
scanBuf[8], scanBuf[9], scanBuf[10], scanBuf[11],
scanBuf[12], scanBuf[13], scanBuf[14], scanBuf[15]);
- // Look for ANIM tag (embedded SAN containing menu frame)
for (int i = 0; i + 8 <= bytesRead; ++i) {
if (READ_BE_UINT32(scanBuf + i) == MKTAG('A','N','I','M')) {
int64 animStreamPos = startPos + i;
@@ -166,7 +137,6 @@ void InsaneRebel2::procIACT(byte *renderBitmap, int32 codecparam, int32 setupsan
if (animData) {
b.seek(animStreamPos);
b.read(animData, toCopy);
- // Use userId as the HUD slot (1000 -> slot 0 for menu background)
loadEmbeddedSan(0, animData, toCopy, renderBitmap);
free(animData);
}
@@ -192,34 +162,21 @@ void InsaneRebel2::procIACT(byte *renderBitmap, int32 codecparam, int32 setupsan
void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32 setupsan12,
int32 setupsan13, Common::SeekableReadStream &b, int32 size, int32 flags,
int16 par1, int16 par2, int16 par3, int16 par4) {
- // For IACT opcode 4 (enemy position update), the structure is:
- // centerX = X + (Width / 2)
- // centerY = Y + (Height / 2)
- // Then subtracts scroll offsets:
-
-
if (par1 == 4) {
enemyUpdate(renderBitmap, b, par2, par3, par4);
} else if (par1 == 2) {
- // Delegate handling to dedicated opcode 2 handler
iactRebel2Opcode2(b, par2, par3, par4);
} else if (par1 == 3) {
iactRebel2Opcode3(b, par2, par3, par4);
}
else if (par1 == 5) {
- // Sub-opcode 0x0D (13) = Primary collision zones (obstacles)
- // Sub-opcode 0x0E (14) = Secondary collision zones (boundaries)
- // par2 is the sub-opcode that determines which zone table to use
debugC(DEBUG_INSANE, "IACT Opcode 5: par2=%d par3=%d par4=%d", par2, par3, par4);
if (par2 == 0x0D || par2 == 0x0E) {
- // Register the collision zone from the remaining IACT data
registerCollisionZone(b, par2, par4);
}
} else if (par1 == 7) {
- // IACT header: par1=7, par2=flags, par3=0, par4=sub-opcode
- // Body contains 2 int16 values (body[0], body[1])
int16 body0 = 0, body1 = 0;
if (b.size() - b.pos() >= 4) {
body0 = b.readSint16LE();
@@ -233,7 +190,6 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
debugC(DEBUG_INSANE, "Opcode 7 par4=0: wind=(%d,%d)", body0, body1);
break;
case 1:
- // Set LEFT X boundary and TOP Y boundary
_corridorLeftX = body0;
_corridorTopY = body1;
if (_flyControlMode == 2) {
@@ -245,7 +201,6 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
body0, body1, _corridorLeftX);
break;
case 2:
- // Set RIGHT X boundary and BOTTOM Y boundary
_corridorRightX = body0;
_corridorBottomY = body1;
if (_flyControlMode == 2) {
@@ -270,10 +225,8 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
} else if (par1 == 8) {
iactRebel2Opcode8(renderBitmap, b, size, par2, par3, par4);
} else if (par1 == 9) {
- // Opcode 9: Text/subtitle display
iactRebel2Opcode9(renderBitmap, b, par2, par3, par4);
} else if (par1 == 0 || par1 == 1) {
- // Low Opcodes seen in logs
debugC(DEBUG_INSANE, "IACT: Low Opcode %d (par2=%d par3=%d par4=%d)", par1, par2, par3, par4);
} else {
debugC(DEBUG_INSANE, "IACT: Unknown Opcode %d (par2=%d par3=%d par4=%d)", par1, par2, par3, par4);
@@ -281,26 +234,16 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
}
void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
- // Keep existing linking behavior (par3 == 4) for compatibility.
-
- // Link case: par3 == 4
if (par3 == 4) {
- int16 childId = b.readSint16LE(); // Offset +8
- int16 parentId = b.readSint16LE(); // Offset +10
+ int16 childId = b.readSint16LE();
+ int16 parentId = b.readSint16LE();
- // Validate BOTH parentId AND childId to avoid triggering "set/clear ALL bits" behavior
- // which would disable/enable all enemies at once - not the intended linking behavior.
if (parentId >= 1 && parentId < 512 && childId >= 1 && childId < 512) {
_rebelLinks[parentId][2] = _rebelLinks[parentId][1];
_rebelLinks[parentId][1] = _rebelLinks[parentId][0];
_rebelLinks[parentId][0] = childId;
- // Mirror parent's bit state to child (INVERTED):
- // - Parent alive (bit clear) â setBit(child) â child hidden
- // - Parent dead (bit set) â clearBit(child) â child shown
- // if (bVar3 == 0) setBit(childId); else clearBit(childId);
- // This ensures linked children (explosion/death sprites) are hidden
- // while the parent is alive, and revealed when the parent is destroyed.
+ // Linked children are hidden while the parent is alive.
if (!isBitSet(parentId)) {
setBit(childId);
debugC(DEBUG_INSANE, "Linked ID=%d to Parent=%d (Slot 0) - child DISABLED (parent alive)", childId, parentId);
@@ -312,22 +255,18 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
debugC(DEBUG_INSANE, "Skipping link with invalid IDs childId=%d parentId=%d", childId, parentId);
}
return;
- } else if (par3 == 1) { // Probabilistic / counter cases: par3 == 1
+ } else if (par3 == 1) {
int16 value = par4;
int16 targetId = b.readSint16LE();
- // Validate targetId >= 1 to avoid triggering "set/clear ALL bits" behavior
if (targetId < 1 || targetId >= 0x200)
return;
- // if (par4 == 100) clearBit(body0); // Force enable
- // else { bitMask = 1 << (par4 & 0x1f); if (waveState & bitMask) setBit(body0); }
if ((_rebelHandler == 8 || _rebelHandler == 25) && value != 0) {
if (value == 100) {
clearBit(targetId);
debugC(DEBUG_INSANE, "Opcode2 (H%d): Force ENABLE target=%d (par4=100)", _rebelHandler, targetId);
} else {
- // Check wave state: if enemy type has been killed, disable target
int bitMask = 1 << (value & 0x1f);
if ((_rebelWaveState & bitMask) != 0) {
setBit(targetId);
@@ -337,12 +276,12 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
return;
}
- if (value > 1 && value < 10) { // 1 < value < 10: random disable
+ if (value > 1 && value < 10) {
if (_vm->_rnd.getRandomNumber(value) == 0) {
setBit(targetId);
debugC(DEBUG_INSANE, "IACT Opcode2: Random DISABLE target=%d (value=%d)", targetId, value);
}
- } else if (value > 10 && value < 20) { // 10 < value < 20: enable/disable with special value==11 = force enable
+ } else if (value > 10 && value < 20) {
if (value == 11) {
clearBit(targetId);
debugC(DEBUG_INSANE, "IACT Opcode2: FORCE ENABLE target=%d (value=11)", targetId);
@@ -355,14 +294,12 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
debugC(DEBUG_INSANE, "IACT Opcode2: Random DISABLE target=%d (value=%d)", targetId, value);
}
}
- } else if (value > 99 && value < 110) { // 99 < value < 110: increment value counter if target active
+ } else if (value > 99 && value < 110) {
if (!isBitSet(targetId)) {
int idx = value - 100;
if (idx >= 0 && idx < 10) {
_rebelValueCounters[idx]++;
_rebelLastCounter = _rebelValueCounters[idx];
- // Track that this target feeds value-counter[idx] so destroying it
- // decrements the shield gauge. Only while a shield gate is active.
if (_rebelShieldGateActive && targetId >= 0 && targetId < 512) {
_rebelGaugeSlot[targetId] = (int8)idx;
_rebelGaugeArmed = true;
@@ -372,7 +309,7 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
}
}
- } else if (value > 0x3ff) { // Bitmask case: value > 0x3FF
+ } else if (value > 0x3ff) {
for (int slot = 1; slot <= 9; ++slot) {
if ((value & (1 << (slot - 1))) != 0) {
if (!isBitSet(targetId)) {
@@ -389,22 +326,14 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
}
}
- // Unknown sub-type: log and return
debugC(DEBUG_INSANE, "IACT Opcode2: Unhandled par3=%d par4=%d", par3, par4);
}
}
void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
- // IACT opcode 3 â damage and hit counter processing.
- // processed immediately during IACT dispatch.
- // par3 == 1/2: Direct hit â increment hit counter, apply damage if conditions met
- // - par3==1: par4 must be 1..9 for damage
- // - par3==2: par4 must be > 99, with wave state bit check for par4 >= 101
- // par3==1: increment hit counter ONLY (NO damage), requires par4 != 4
- // par4==100: direct damage (separate check after par3 branches, NO cover check)
if (_rebelHandler == 25) {
- int16 srcIdBody0 = b.readSint16LE(); // body[0] (offset +8)
- int16 srcIdBody1 = b.readSint16LE(); // body[1] (offset +10)
+ int16 srcIdBody0 = b.readSint16LE();
+ int16 srcIdBody1 = b.readSint16LE();
if (par3 == 5) {
debugC(DEBUG_INSANE, "Opcode3: H25 par3=5 srcId=%d isBitSet=%d damageLevel=%d",
@@ -482,8 +411,8 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
}
}
} else if (par3 == 5) {
- b.skip(2); // Skip body[0]
- int16 srcId = b.readSint16LE(); // body[1] (offset +10)
+ b.skip(2);
+ int16 srcId = b.readSint16LE();
debugC(DEBUG_INSANE, "Opcode3: par3=5 srcId=%d isBitSet=%d", srcId, isBitSet(srcId));
@@ -516,7 +445,6 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
void InsaneRebel2::updateOpcode6Handler(int16 par2) {
if (par2 == 7 || par2 == 8 || par2 == 0x19 || par2 == 0x26) {
- // Reset Level 2 background flag when transitioning away from Handler 8
if (_rebelHandler == 8 && par2 != 8) {
_level2BackgroundLoaded = false;
}
@@ -555,13 +483,9 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
debugC(DEBUG_INSANE, "Opcode 6 (Handler 8): Wave init, wave=0x%x", _rebelWaveState);
}
- // Skip position calculation for special modes 4 and 5
if (_shipLevelMode != 4 && _shipLevelMode != 5) {
- // Mode 2 = "Covered" state - contract movement range to 41 (0x29)
- // Other modes = "Shooting" state - expand movement range to 127 (0x7f)
- // Transition happens gradually at +/-10 per frame for smooth animation
+ // Cover and shooting states ease toward different movement ranges.
if (_shipLevelMode == 2) {
- // Covered state - contract movement range
if (_movementRangeLimit > 41) {
_movementRangeLimit -= 10;
}
@@ -569,7 +493,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
_movementRangeLimit = 41;
}
} else {
- // Shooting state - expand movement range
if (_movementRangeLimit < 127) {
_movementRangeLimit += 10;
}
@@ -578,20 +501,14 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
}
}
- // Calculate target position from mouse input
- // Mouse X maps to ship horizontal tilt, Mouse Y to vertical tilt
-
- // Map the effective aim position (-127 to 127 range) to the ship target.
Common::Point aimPos = getGameplayAimPoint();
int16 mouseOffsetX = (int16)((aimPos.x - 160) * 127 / 160);
int16 mouseOffsetY = (int16)((aimPos.y - 100) * 127 / 100);
- // Clamp X offset to movement range limit (covered/shooting state)
if (mouseOffsetX > _movementRangeLimit)
mouseOffsetX = _movementRangeLimit;
if (mouseOffsetX < -_movementRangeLimit)
mouseOffsetX = -_movementRangeLimit;
- // Y offset always uses full range (+/-127)
if (mouseOffsetY > 127)
mouseOffsetY = 127;
if (mouseOffsetY < -127)
@@ -600,8 +517,7 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
_shipTargetX = (int16)(((mouseOffsetX * 5 + 0x27b) * 0x40) / 0xfe);
_shipTargetY = (int16)(((mouseOffsetY * 5 + 0x27b) * 0x10) / 0xfe);
- // Smooth interpolation toward target (max 50 pixels per frame)
- const int16 maxStep = 50; // 0x32 in hex
+ const int16 maxStep = 50;
if (_shipPosX < _shipTargetX) {
int16 newX = _shipPosX + maxStep;
_shipPosX = (newX > _shipTargetX) ? _shipTargetX : newX;
@@ -624,12 +540,9 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
_player->_fobjOffsetY = -_shipPosY;
}
- // Calculate ship direction indices for sprite selection
- // Map mouse position to 5x7 direction grid (like Handler 7)
int16 mouseX = aimPos.x;
int16 mouseY = aimPos.y;
- // Scale mouse if video is larger than 320x200
if (_player && _player->_width > 320) {
mouseX = (mouseX * 320) / _player->_width;
}
@@ -668,7 +581,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
_shipDirectionIndex = _shipDirectionH * 7 + _shipDirectionV;
}
- // Update firing state from mouse button or joystick fire action
if (_shipLevelMode == 4) {
_shipFiring = false;
} else {
@@ -682,9 +594,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
}
void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 par4) {
- // Used for Level 3 and similar space combat levels.
-
- // This determines collision mode and shooting capability:
// Mode 0: Obstacle avoidance - SECONDARY zones, corridor boundaries
// Mode 1: Tunnel flight - PRIMARY zones, per-edge push-back (hMargin=0x28)
// Mode 2: Combat mode - shooting ENABLED, SECONDARY zones
@@ -693,7 +602,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
debugC(DEBUG_INSANE, "Opcode 6 (Handler 7): Control mode set to %d (shooting %s)",
par4, (par4 == 2) ? "ENABLED" : "DISABLED");
- // The body word for status bar is read separately below.
int16 bodyStatusFlag = 0;
if (b.size() - b.pos() >= 2) {
bodyStatusFlag = b.readSint16LE();
@@ -706,13 +614,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
debugC(DEBUG_INSANE, "Opcode 6 (Handler 7): Status bar enabled (body flag=%d)", bodyStatusFlag);
}
- // Velocity-based physics with momentum/inertia:
- // Mouse offset from center -> scaled input [-127,127]
- // -> velocity history averaging -> physics delta (clamped +/-12/frame)
- // -> position clamping -> corridor collision -> perspective offsets
- // offset 2: Y speed offset 4: X speed (levelSpeed)
- // offset 6: wind multiplier
- // level types these fields map to lift/slide/drift of the preceding row.
const int flightParamIndex = CLIP(_rebelLevelType - 1, 0, 16);
const LevelDifficultyParams &flightParams =
kDifficultyTable[CLIP(_difficulty, 0, 5)][flightParamIndex];
@@ -737,8 +638,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
const bool useDirectGamepadFlight = _gamepadAimActive && _selectedLevel == 10;
- // Direct mouse/touch aiming can hold the cursor at an edge indefinitely.
- // Keep this sensitivity concession for mouse-like Handler 7 steering.
// Level 10 is the only Handler 7 stage that needs direct full-range
// gamepad axes; other Handler 7 stages use bounded target steering to avoid
// harsh perspective shifts from a held stick.
@@ -748,9 +647,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
scaledInputY = (int16)((scaledInputY * kRA2Handler7DirectInputNumerator) /
kRA2Handler7DirectInputDenominator);
}
- // Mouse/touch and most Handler 7 gamepad stages use bounded target
- // steering. Level 10 keeps direct center-relative gamepad axes for its
- // speeder flight.
const bool useTargetSteering = !useDirectGamepadFlight;
int16 mouseFlightTargetX = _flyShipScreenX;
if (useTargetSteering) {
@@ -811,8 +707,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
if (positionDeltaX > 11)
positionDeltaX = 12;
- // absolute position, so steer toward a bounded position target instead of
- // letting a held off-center cursor keep pushing the ship until it bounces.
if (useTargetSteering) {
int targetDeltaX = mouseFlightTargetX - _flyShipScreenX;
const int targetSteeringDivisor = 4;
@@ -823,7 +717,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
_flyShipScreenX += positionDeltaX;
- // Y delta.
if (_flyControlMode == 1) {
int yCalc = levelYSpeed * scaledInputY - (windEffectY >> 1);
int yDelta = yCalc >> 10;
@@ -864,7 +757,7 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
if (!_noDamage)
initDamageFlash();
_rebelHitCounter++;
- playSfx(1, 127, 100); // CRASH.SAD, right wall, pan right
+ playSfx(1, 127, 100);
}
}
@@ -879,7 +772,7 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
if (!_noDamage)
initDamageFlash();
_rebelHitCounter++;
- playSfx(1, 127, -100); // CRASH.SAD, left wall, pan left
+ playSfx(1, 127, -100);
}
}
@@ -891,7 +784,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
}
}
- // f(x) = (focal * center * |offset|) / ((center - focal) * |offset| + focal * center)
{
int absOffX = ABS(_flyShipScreenX - 0xd4);
int16 focalX = 0x2b; // Far view default for Level 3
@@ -922,15 +814,12 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
if (_viewShift < -127)
_viewShift = -127;
- // 5x7 grid: vDir(0-4) * 7 + hDir(0-6) = sprite index (0-34).
- // vDir from vertical input: (0xa0 - verticalInput) >> 6.
int16 vDir = (int16)(((int)(0xa0 - _verticalInput) + ((0xa0 - _verticalInput) < 0 ? 63 : 0)) >> 6);
if (vDir < 0)
vDir = 0;
if (vDir > 4)
vDir = 4;
- // hDir from smoothed velocity: (0x95 - smoothedVelocity) / 0x2b.
int16 hDir = (int16)((0x95 - _smoothedVelocity) / 0x2b);
if (hDir < 0)
hDir = 0;
@@ -963,9 +852,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
}
void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
- // Handler 25 (0x19) specific logic (mixed mode - speeder bike).
-
- // The stream position should be at offset 8 after par4 was read.
int16 par5 = 0;
if (b.pos() + 2 <= b.size()) {
int64 savedPos = b.pos();
@@ -994,10 +880,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
}
}
- // Mode 1: Uncovered, shooting position - sprite on left
- // Mode 2: Covered, vertical shift
- // Mode 3: Transition between covered/uncovered - sprite position depends on direction
- // Mode 4: Alternative uncovered position - sprite on right
_grdSpriteMode = par4;
debugC(DEBUG_INSANE, "Handler25 Opcode6: par2=%d par3=%d par4=%d(mode) par5=%d(reset) autopilot=%d damageLevel=%d controlMode=%d",
@@ -1019,10 +901,8 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
}
}
- // Clear control mode after processing (sticky flags consumed).
_rebelControlMode = 0;
} else {
- // Auto play: random autopilot changes.
if (_rebelAutopilot == 0) {
if (_vm->_rnd.getRandomNumber(100) == 0) {
_rebelAutopilot = 1;
@@ -1035,15 +915,12 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
}
}
- // This provides the smooth transition animation between covered/uncovered states.
int prevDamageLevel = _rebelDamageLevel;
if (_rebelAutopilot == 0) {
- // Uncovered: decrement damage level towards 0.
if (_rebelDamageLevel > 0) {
_rebelDamageLevel--;
}
} else {
- // Covered: increment damage level towards 5.
if (_rebelDamageLevel < 5) {
_rebelDamageLevel++;
}
@@ -1055,7 +932,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
if (_grdSpriteMode == 3) {
if (_rebelDamageLevel == 5) {
- // At max damage, check for direction change input.
int16 mouseX = getGameplayAimPoint().x;
if (_player && _player->_width > 320) {
mouseX = (mouseX * 320) / _player->_width;
@@ -1072,7 +948,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
}
if (_grdSpriteMode == 1) {
- // Mode 1: Uncovered, shooting - sprite shifts left as damage increases.
_rebelViewMode1 = 0x0e;
_rebelViewMode2 = 0;
_rebelViewOffsetX = _rebelDamageLevel * -5 + -14;
@@ -1080,7 +955,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
_rebelViewOffsetY = 0;
_rebelViewOffset2Y = 0;
} else if (_grdSpriteMode == 4) {
- // Mode 4: Alternative uncovered - sprite shifts right.
_rebelViewMode1 = 0x22;
_rebelViewMode2 = 0;
_rebelViewOffsetX = _rebelDamageLevel * 10 + -16;
@@ -1088,7 +962,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
_rebelViewOffsetY = 0;
_rebelViewOffset2Y = 0;
} else if (_grdSpriteMode == 2) {
- // Mode 2: Covered - vertical shift.
_rebelViewMode1 = 0;
_rebelViewMode2 = 0x0e;
_rebelViewOffsetY = _rebelDamageLevel * -5 + -14;
@@ -1096,7 +969,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
_rebelViewOffsetX = 0;
_rebelViewOffset2X = 0;
} else if (_grdSpriteMode == 3) {
- // Mode 3: Transition - direction-dependent horizontal shift.
_rebelViewMode1 = 0x0f;
_rebelViewMode2 = 0;
int16 dirMultX = (_rebelFlightDir == 0) ? 3 : -3;
@@ -1106,7 +978,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
_rebelViewOffsetY = 0;
_rebelViewOffset2Y = 0;
} else {
- // Mode 0 or unknown: use Mode 1 defaults as fallback.
_rebelViewMode1 = 0x0e;
_rebelViewMode2 = 0;
_rebelViewOffsetX = _rebelDamageLevel * -5 + -14;
@@ -1120,13 +991,11 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
_grdSpriteMode, _rebelDamageLevel, _rebelFlightDir, _rebelAutopilot,
_rebelViewOffsetX, _rebelViewOffsetY, _rebelViewOffset2X, _rebelViewOffset2Y);
- // All subsequent FOBJs in this frame will be shifted by these offsets.
if (_player) {
_player->_fobjOffsetX = _rebelViewOffsetX;
_player->_fobjOffsetY = _rebelViewOffsetY;
}
- // This wipes previous frame content so codec 23 delta skip regions show clean corridor.
drawHandler25CorridorOverlay(renderBitmap);
}
@@ -1187,9 +1056,7 @@ void InsaneRebel2::handleOpcode6GenericInit(int16 par4) {
}
void InsaneRebel2::updateOpcode6GenericFlightState() {
- // This determines whether the ship flies on autopilot or manual control.
if (!_rebelAutoPlay) {
- // Normal mode: check control mode flags.
if (_rebelAutopilot == 0) {
if ((_rebelControlMode & 2) != 0) {
_rebelAutopilot = 1;
@@ -1200,7 +1067,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
}
}
} else {
- // Auto play: random autopilot changes.
if (_rebelAutopilot == 0) {
if (_vm->_rnd.getRandomNumber(100) == 0) {
_rebelAutopilot = 1;
@@ -1225,8 +1091,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
if (_rebelLevelType == 3) {
if (_rebelDamageLevel == 5) {
- // Check for joystick/key input to change direction.
- // Simplified: use mouse position.
int16 mouseX = getGameplayAimPoint().x;
if (mouseX > 235) {
_rebelFlightDir = 1;
@@ -1241,7 +1105,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
switch (_rebelLevelType) {
case 1:
- // Type 1: Vertical movement.
_rebelViewMode1 = 0x0e;
_rebelViewMode2 = 0;
_rebelViewOffsetX = _rebelDamageLevel * -5 - 0x0e;
@@ -1251,7 +1114,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
break;
case 4:
- // Type 4: Different vertical movement.
_rebelViewMode1 = 0x22;
_rebelViewMode2 = 0;
_rebelViewOffsetX = _rebelDamageLevel * 10 - 0x10;
@@ -1261,7 +1123,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
break;
case 2:
- // Type 2: Horizontal movement.
_rebelViewMode1 = 0;
_rebelViewMode2 = 0x0e;
_rebelViewOffsetY = _rebelDamageLevel * -5 - 0x0e;
@@ -1271,7 +1132,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
break;
case 3:
- // Type 3: Direction-based movement.
_rebelViewMode1 = 0x0f;
_rebelViewMode2 = 0;
{
@@ -1285,7 +1145,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
break;
default:
- // Default: No special offsets.
_rebelViewMode1 = 0;
_rebelViewMode2 = 0;
_rebelViewOffsetX = 0;
@@ -1300,11 +1159,8 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
}
void InsaneRebel2::scanOpcode6EmbeddedAnim(byte *renderBitmap, Common::SeekableReadStream &b, int32 chunkSize, int16 par4) {
- // Detect and load embedded ANIM (SAN) within the remaining IACT payload.
- // Note: chunkSize is the remaining IACT payload size after par1-par4 header.
int64 startPos = b.pos();
- // Use chunkSize (remaining IACT payload) rather than b.size() (entire FRME stream).
int64 remaining = chunkSize;
if (remaining > 0) {
int scanSize = (int)MIN<int64>(remaining, 65536);
@@ -1316,7 +1172,6 @@ void InsaneRebel2::scanOpcode6EmbeddedAnim(byte *renderBitmap, Common::SeekableR
int64 animStreamPos = startPos + i;
uint32 animReportedSize = READ_BE_UINT32(scanBuf + i + 4);
- // Limit to remaining IACT payload (chunkSize - offset into payload).
int32 toCopy = (int)MIN<int64>((int64)animReportedSize + 8, chunkSize - i);
if (toCopy > 0) {
byte *animData = (byte *)malloc(toCopy);
@@ -1338,15 +1193,7 @@ void InsaneRebel2::scanOpcode6EmbeddedAnim(byte *renderBitmap, Common::SeekableR
}
}
-// Per-wave initialization: clears bit table, resets link tables, configures
-// gates the reset with handler-specific IACT fields, not always frame 0.
void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStream &b, int32 chunkSize, int16 par2, int16 par3, int16 par4) {
- // Opcode 6: Level setup / mode switch
- // - first body word == 1 triggers status bar display and state reset
- // For Handler 0x26/0x19 (turret/FPS):
- // - Handler-specific status/reset word
- // - Different view offset calculations
-
debugC(DEBUG_INSANE, "IACT Opcode 6: par2=%d par3=%d par4=%d", par2, par3, par4);
updateOpcode6Handler(par2);
@@ -1375,15 +1222,7 @@ void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStr
scanOpcode6EmbeddedAnim(renderBitmap, b, chunkSize, par4);
}
-// Decodes embedded ANIM data from IACT chunks and dispatches to
-// handler-specific loaders for NUT sprites, HUD overlays, and backgrounds.
-// Handler-specific routing:
-// Handler 7 (FLY): FLY NUT sprites via par4 (1, 2, 3, 11)
-// Handler 8 (POV): POV NUT sprites via par3 (1, 3, 6, 7) or background via par4=5
-// Handler 0x26 (turret): Turret HUD NUT via par3/par4 (1-4)
-// Handler 0x19: Speeder bike GRD/HUD resources via par4
bool InsaneRebel2::loadOpcode8Handler7FlySprites(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
- // Handler 7: FLY NUT Loading (Third-Person Ship)
bool isHandler7FLY = (_rebelHandler == 7 && (par4 == 1 || par4 == 2 || par4 == 3 || par4 == 11));
if (isHandler7FLY && remaining >= 14) {
if (loadHandler7FlySprites(b, remaining, par4)) {
@@ -1410,9 +1249,6 @@ bool InsaneRebel2::loadOpcode8Handler7ShotTable(Common::SeekableReadStream &b, i
}
bool InsaneRebel2::loadOpcode8EdgeTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
- // Edge Blend Table Loading (par4 == 1000)
- // If so, loads a per-level 256x256 color blend table from the IACT chunk data.
- // This table controls the edge glow color of laser beams (e.g. red vs green).
if (par4 == 1000 && remaining >= 10 + 8 + 32896) {
byte *edgeData = (byte *)malloc(8 + 32896);
if (edgeData) {
@@ -1430,11 +1266,8 @@ bool InsaneRebel2::loadOpcode8EdgeTable(Common::SeekableReadStream &b, int64 sta
}
bool InsaneRebel2::loadOpcode8AuxSfx(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
- // Auxiliary Sound Buffer Loading (par4 20-47)
// 0x1f-0x25 (31-37) -> aux buffer 1, 0x28 (40) -> aux buffer 3,
// 0x29-0x2f (41-47) -> aux buffer 2
- // Data layout: offset 14 = uint32 data size, offset 18 = PCM data start.
- // Stream is at offset 8 (after par1-par4), so data size at +6, PCM at +10.
if (par4 < 20 || par4 > 47)
return false;
@@ -1450,7 +1283,7 @@ bool InsaneRebel2::loadOpcode8AuxSfx(Common::SeekableReadStream &b, int64 startP
}
if (auxBuffer >= 0 && remaining >= 10) {
- b.seek(startPos + 6); // Skip to data size field (byte offset 14 from IACT start)
+ b.seek(startPos + 6);
uint32 dataSize = b.readUint32LE();
if (dataSize > 0 && remaining >= (int64)(10 + dataSize)) {
byte *soundData = (byte *)malloc(dataSize);
@@ -1471,8 +1304,6 @@ bool InsaneRebel2::loadOpcode8AuxSfx(Common::SeekableReadStream &b, int64 startP
}
bool InsaneRebel2::loadOpcode8ShotOriginTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
- // Handler 25 (0x19): Shot-Origin Lookup Table (par4 == 8)
- // "%hd %hd %hd %hd ... %hd %hd" (15 X/Y pairs).
if (_rebelHandler == 25 && par4 == 8) {
if (loadHandler25ShotOriginTable(b, startPos, remaining)) {
b.seek(startPos);
@@ -1484,7 +1315,6 @@ bool InsaneRebel2::loadOpcode8ShotOriginTable(Common::SeekableReadStream &b, int
}
void InsaneRebel2::loadOpcode8EmbeddedAnim(byte *renderBitmap, Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par3, int16 par4) {
- // Remaining handlers require finding ANIM tag in the stream.
debugC(DEBUG_INSANE, "Opcode 8: Scanning for ANIM tag (startPos=%lld remaining=%lld)",
(long long)startPos, (long long)remaining);
@@ -1544,8 +1374,6 @@ void InsaneRebel2::loadOpcode8EmbeddedAnim(byte *renderBitmap, Common::SeekableR
bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData, int32 animDataSize, int16 par3, int16 par4) {
bool handled = false;
- // Handler 0x26: Turret HUD Overlays.
- // Some chunks use par3 for the same low/high selector.
if (!handled && _rebelHandler == 0x26) {
int hudSelector = (par4 >= 1 && par4 <= 4) ? par4 : par3;
@@ -1554,7 +1382,6 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
}
}
- // Handler 8: POV Ship Sprites or Background.
// NOTE: par3 is always 0 for Handler 8; par4 contains the actual sprite type.
if (!handled && _rebelHandler == 8) {
if (par4 == 5) {
@@ -1564,7 +1391,6 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
}
}
- // Handler 25 (0x19): Level 2 GRD Ship Sprites and Background.
if (!handled && _rebelHandler == 25) {
if (par4 == 1 || par4 == 2 || par4 == 10) {
handled = loadHandler25GrdSprites(animData, animDataSize, par4);
@@ -1577,8 +1403,6 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
}
}
- // Fallback: Embedded SAN HUD overlays.
- // For other cases, load as embedded SAN frame to HUD overlay slots.
if (!handled) {
const bool highRes = isHiRes();
const bool highResHud = (par3 == 2 || par3 == 4);
@@ -1589,7 +1413,6 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
highResHud ? "high-res" : "low-res", par3, highRes ? "high-res" : "low-res");
handled = true;
} else {
- // Determine userId: Handler 0x19 uses par3, others use par4.
// Heuristic: if par3 is valid GRD range (1-13) and par4 is invalid, prefer par3.
bool usePar3 = (_rebelHandler == 0x19);
if (!usePar3 && par3 >= 1 && par3 <= 13 && (par4 <= 0 || par4 >= 1000)) {
@@ -1597,7 +1420,6 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
}
int userId = usePar3 ? par3 : par4;
- // Skip audio tracks (userId >= 1000).
if (userId > 0 && userId < 1000) {
debugC(DEBUG_INSANE, "Opcode 8: Loading embedded SAN HUD userId=%d (handler=%d par3=%d par4=%d)",
userId, _rebelHandler, par3, par4);
@@ -1639,10 +1461,7 @@ void InsaneRebel2::iactRebel2Opcode8(byte *renderBitmap, Common::SeekableReadStr
loadOpcode8EmbeddedAnim(renderBitmap, b, startPos, remaining, par3, par4);
}
-// loadHandler25ShotOriginTable -- Parse shot origin coordinate pairs from IACT payload.
bool InsaneRebel2::loadHandler25ShotOriginTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining) {
- // IACT layout at this point:
- // - stream is positioned after par1..par4 (8 bytes consumed by caller)
if (remaining < 12)
return false;
@@ -1668,7 +1487,6 @@ bool InsaneRebel2::loadHandler25ShotOriginTable(Common::SeekableReadStream &b, i
b.read((byte *)buf, bytesToRead);
buf[bytesToRead] = '\0';
- // Parse signed 16-bit integers from the ASCII payload.
int16 vals[30];
int count = 0;
const char *p = buf;
@@ -1693,7 +1511,7 @@ bool InsaneRebel2::loadHandler25ShotOriginTable(Common::SeekableReadStream &b, i
while (p < end && Common::isDigit(*p)) {
value = value * 10 + (*p - '0');
if (value > 32768)
- value = 32768; // Keep accumulation bounded before sign/clamp.
+ value = 32768;
++p;
}
@@ -1708,8 +1526,6 @@ bool InsaneRebel2::loadHandler25ShotOriginTable(Common::SeekableReadStream &b, i
return false;
}
- // token1->0x4578b0 (X index 5), token2->0x4578d0 (Y index 5), ...
- // token29->0x4578cc (X index 19), token30->0x4578ec (Y index 19).
for (int i = 0; i < 15; ++i) {
int pair = i * 2;
if (pair + 1 >= count)
@@ -1724,7 +1540,6 @@ bool InsaneRebel2::loadHandler25ShotOriginTable(Common::SeekableReadStream &b, i
return true;
}
-// loadHandler7ShotTable -- Parse handler 7 laser muzzle coordinate pairs from IACT payload.
bool InsaneRebel2::loadHandler7ShotTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
if (remaining < 12)
return false;
@@ -1806,18 +1621,11 @@ bool InsaneRebel2::loadHandler7ShotTable(Common::SeekableReadStream &b, int64 st
return true;
}
-// Opcode 8 Helper Functions
-
bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 remaining, int16 par4) {
- // +0-5 (6 bytes): additional header
- // +6-9 (4 bytes): NUT data size (little-endian)
- // +10+: NUT data
-
if (remaining < 14) {
return false;
}
- // Read additional header and size from fixed offset
byte header[10];
if (b.read(header, 10) != 10) {
return false;
@@ -1827,7 +1635,6 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
header[0], header[1], header[2], header[3], header[4],
header[5], header[6], header[7], header[8], header[9]);
- // Size is at offset 14 from IACT start = bytes 6-9 of our header buffer
uint32 nutSize = READ_LE_UINT32(header + 6);
debugC(DEBUG_INSANE, "loadHandler7FlySprites: par4=%d nutSize=%u remaining=%lld",
par4, nutSize, (long long)remaining);
@@ -1859,7 +1666,6 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
return false;
}
- // Verify ANIM header
if (bytesRead >= 8) {
uint32 animTag = READ_BE_UINT32(nutData);
if (animTag != MKTAG('A','N','I','M')) {
@@ -1869,7 +1675,6 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
}
}
- // Load as a Rebel2 embedded sprite ANIM.
NutRenderer *newNut = makeRebel2SpriteFromData(_vm, nutData, bytesRead);
if (!newNut || newNut->getNumChars() <= 0) {
debugC(DEBUG_INSANE, "loadHandler7FlySprites: NUT load failed for par4=%d", par4);
@@ -1911,8 +1716,6 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
}
bool InsaneRebel2::loadTurretHudOverlay(byte *animData, int32 size, int16 selector) {
- // Resolution-dependent loading:
-
if (!animData || size <= 0) {
return false;
}
@@ -1959,7 +1762,6 @@ bool InsaneRebel2::loadHandler8ShipSprites(byte *animData, int32 size, int16 par
return false;
}
- // Only handle valid POV sprite slots
if (par4 != 1 && par4 != 3 && par4 != 6 && par4 != 7) {
return false;
}
@@ -2005,7 +1807,6 @@ bool InsaneRebel2::loadHandler25GrdSprites(byte *animData, int32 size, int16 par
return false;
}
- // Only handle valid GRD sprite slots
if (par4 != 1 && par4 != 2 && par4 != 10) {
return false;
}
@@ -2045,15 +1846,12 @@ bool InsaneRebel2::loadHandler25GrdSprites(byte *animData, int32 size, int16 par
}
bool InsaneRebel2::loadLevel2Background(byte *animData, int32 size, byte *renderBitmap) {
- // par4=5 contains the background image embedded as ANIM with FOBJ codec 3
-
if (!animData || size < 8) {
return false;
}
debugC(DEBUG_INSANE, "loadLevel2Background: Loading Level 2 background (animSize=%d)", size);
- // Allocate background buffer if needed (320x200 = 64000 bytes)
if (_level2Background == nullptr) {
_level2Background = (byte *)malloc(320 * 200);
if (!_level2Background) {
@@ -2116,7 +1914,6 @@ bool InsaneRebel2::loadLevel2Background(byte *animData, int32 size, byte *render
continue;
}
- // FOBJ header: codec(2), x(2), y(2), w(2), h(2)
int codec = stream.readUint16LE();
int fobjX = stream.readSint16LE();
int fobjY = stream.readSint16LE();
@@ -2128,7 +1925,6 @@ bool InsaneRebel2::loadLevel2Background(byte *animData, int32 size, byte *render
debugC(DEBUG_INSANE, "loadLevel2Background: Found FOBJ: codec=%d pos=(%d,%d) size=%dx%d",
codec, fobjX, fobjY, fobjW, fobjH);
- // and clip them there; Level 11 backgrounds extend past the right edge.
if (codec == 3 && fobjX >= 0 && fobjY >= 0 && fobjW > 0 && fobjH > 0 &&
fobjX < 320 && fobjY < 200 && stream.pos() < subDataEnd) {
int drawW = MIN<int>(fobjW, 320 - fobjX);
@@ -2142,7 +1938,6 @@ bool InsaneRebel2::loadLevel2Background(byte *animData, int32 size, byte *render
_level2BackgroundLoaded = true;
foundBackground = true;
- // copy it to the live screen. Handler 8 still uses it as a restore source.
if (renderBitmap && _rebelHandler != 25) {
int bufferPitch = (_player && _player->_width > 0) ? _player->_width : 320;
if (bufferPitch == 320) {
@@ -2169,48 +1964,21 @@ bool InsaneRebel2::loadLevel2Background(byte *animData, int32 size, byte *render
return foundBackground;
}
-// iactRebel2Opcode9 -- Text/subtitle display via IACT chunk
-// Handles inline text in IACT chunks. Most RA2 subtitles use TRES chunks
-// (handled by SmushPlayer::handleTextResource); this opcode is less common.
-// Supports multi-line wrapping, centered/shadowed text, and clip regions.
void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
- // Opcode 9: Text/Subtitle Display via IACT chunk
- // Note: Most RA2 subtitles use TRES chunks handled by SmushPlayer::handleTextResource()
- // This opcode handles inline text in IACT chunks (less common)
- // IACT Chunk Layout (par1-par4 already read by handleIACT):
- // +0x00 (2): opcode = 9 (par1, already read)
- // +0x02 (2): par2 (already read)
- // +0x04 (2): par3 (already read)
- // +0x06 (2): par4 (already read)
- // +0x08 onwards: Text data structure
- // Text Data Structure:
- // +0x00 (2): X position
- // +0x02 (2): Y position
- // +0x04 (2): flags (bit 0=center, bit 1=right, bit 2=wrap, bit 3=difficulty gated)
- // +0x06 (2): clipX (when flag & 4)
- // +0x08 (2): clipY
- // +0x0A (2): clipW
- // +0x0C (2): clipH
- // +0x10 onwards: NUL-terminated text string
-
int64 startPos = b.pos();
- // Check for "TRES" tag (0x54524553) indicating string resource lookup
uint32 tag = b.readUint32BE();
const char *textStr = nullptr;
char textBuffer[512];
- int16 posX = 160; // Default center position
- int16 posY = 150; // Default bottom-ish position
- int16 textFlags = 1; // Default: center aligned
+ int16 posX = 160;
+ int16 posY = 150;
+ int16 textFlags = 1;
int16 clipX = 16, clipY = 16, clipW = 288, clipH = 168;
if (tag == MKTAG('T','R','E','S')) {
- // String resource lookup via TRES tag
- // The string index follows after the tag
int32 stringIndex = b.readSint32LE();
- // Try to get string from SMUSH player's string resource
if (_player && _player->getString(stringIndex)) {
textStr = _player->getString(stringIndex);
debugC(DEBUG_INSANE, "Opcode 9: TRES string index=%d -> \"%s\"", stringIndex, textStr);
@@ -2219,8 +1987,6 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
return;
}
- // After TRES + index, read positioning data
- // The remaining data contains X, Y, flags etc.
if (b.size() - b.pos() >= 14) {
posX = b.readSint16LE();
posY = b.readSint16LE();
@@ -2231,20 +1997,17 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
clipH = b.readSint16LE();
}
} else {
- // Inline text data - go back and read positioning structure
b.seek(startPos);
- // Read text data structure
- posX = b.readSint16LE(); // +0x00
- posY = b.readSint16LE(); // +0x02
- textFlags = b.readSint16LE(); // +0x04
- clipX = b.readSint16LE(); // +0x06
- clipY = b.readSint16LE(); // +0x08
- clipW = b.readSint16LE(); // +0x0A
- clipH = b.readSint16LE(); // +0x0C
- b.skip(2); // +0x0E padding
-
- // Read inline text string (NUL-terminated)
+ posX = b.readSint16LE();
+ posY = b.readSint16LE();
+ textFlags = b.readSint16LE();
+ clipX = b.readSint16LE();
+ clipY = b.readSint16LE();
+ clipW = b.readSint16LE();
+ clipH = b.readSint16LE();
+ b.skip(2);
+
int textLen = 0;
while (textLen < (int)sizeof(textBuffer) - 1) {
byte ch = b.readByte();
@@ -2263,14 +2026,9 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
return;
}
- // Check difficulty gate (flag bit 3 = 0x08)
- // If set, only show text if difficulty check passes (we skip this check for simplicity)
-
- // Get render buffer dimensions
int width = (_player && _player->_width > 0) ? _player->_width : 320;
int height = (_player && _player->_height > 0) ? _player->_height : 200;
- // Low-res: X clamped to [16, 304], Y clamped to [16, 196]
if (posX < 16)
posX = 16;
if (posX > 304)
@@ -2280,15 +2038,12 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
if (posY > 196)
posY = 196;
- // Use the message font loaded during initialization (DIHIFONT.NUT)
if (!_rebelMsgFont) {
debugC(DEBUG_INSANE, "Opcode 9: No message font loaded (_rebelMsgFont is null)");
return;
}
- // Calculate clipping rectangle
if (!(textFlags & 0x04)) {
- // No clip rect specified, use default full-screen clip
clipX = 0;
clipY = 0;
clipW = width;
@@ -2302,7 +2057,6 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
MIN<int>(clipY + clipH, height)
);
- // Determine text alignment flags
TextStyleFlags styleFlags = kStyleAlignLeft;
if (textFlags & 0x01) {
styleFlags = kStyleAlignCenter;
@@ -2313,14 +2067,9 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
styleFlags = (TextStyleFlags)(styleFlags | kStyleWordWrap);
}
- // Use white color (index 255) for subtitle text
int16 textColor = 255;
- // RA2 fonts (like DIHIFONT.NUT) have only 58 characters starting at ASCII 32 (space).
- // We need to convert ASCII codes to font indices by subtracting 32.
- // Character mapping: font index = ASCII code - 32
- // So 'D' (68) becomes index 36, 'A' (65) becomes index 33, etc.
- // IMPORTANT: Skip format codes (^f00, ^c255, ^l) which TextRenderer parses as raw ASCII.
+ // Convert inline text to font indices while preserving format escapes.
char convertedText[512];
int srcLen = strlen(textStr);
int dstIdx = 0;
@@ -2329,67 +2078,53 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
for (int i = 0; i < srcLen && dstIdx < (int)sizeof(convertedText) - 1; i++) {
byte ch = (byte)textStr[i];
- // Check for format codes (^f, ^c, ^l) - keep them as raw ASCII
if (ch == '^' && i + 1 < srcLen) {
byte next = (byte)textStr[i + 1];
if (next == 'f' && i + 3 < srcLen) {
- // ^fXX - font switch (4 chars total)
- convertedText[dstIdx++] = textStr[i++]; // ^
- convertedText[dstIdx++] = textStr[i++]; // f
- convertedText[dstIdx++] = textStr[i++]; // X
- convertedText[dstIdx++] = textStr[i]; // X
+ convertedText[dstIdx++] = textStr[i++];
+ convertedText[dstIdx++] = textStr[i++];
+ convertedText[dstIdx++] = textStr[i++];
+ convertedText[dstIdx++] = textStr[i];
continue;
} else if (next == 'c' && i + 4 < srcLen) {
- // ^cXXX - color switch (5 chars total)
- convertedText[dstIdx++] = textStr[i++]; // ^
- convertedText[dstIdx++] = textStr[i++]; // c
- convertedText[dstIdx++] = textStr[i++]; // X
- convertedText[dstIdx++] = textStr[i++]; // X
- convertedText[dstIdx++] = textStr[i]; // X
+ convertedText[dstIdx++] = textStr[i++];
+ convertedText[dstIdx++] = textStr[i++];
+ convertedText[dstIdx++] = textStr[i++];
+ convertedText[dstIdx++] = textStr[i++];
+ convertedText[dstIdx++] = textStr[i];
continue;
} else if (next == 'l') {
- // ^l - line break marker (2 chars)
- convertedText[dstIdx++] = textStr[i++]; // ^
- convertedText[dstIdx++] = textStr[i]; // l
+ convertedText[dstIdx++] = textStr[i++];
+ convertedText[dstIdx++] = textStr[i];
continue;
} else if (next == '^') {
- // ^^ - escaped caret (becomes single ^)
- i++; // Skip first ^
- // Fall through to convert second ^ as normal char
+ i++;
ch = '^';
}
}
- // Convert regular characters from ASCII to font index
- // First convert lowercase to uppercase (the font likely only has uppercase)
if (ch >= 'a' && ch <= 'z') {
- ch = ch - 'a' + 'A'; // Convert to uppercase
+ ch = ch - 'a' + 'A';
}
if (ch >= 32 && ch < (byte)(32 + numChars)) {
- convertedText[dstIdx++] = ch - 32; // Convert ASCII to font index
+ convertedText[dstIdx++] = ch - 32;
} else if (ch == '\n' || ch == '\r') {
- convertedText[dstIdx++] = ch; // Keep control characters as-is
+ convertedText[dstIdx++] = ch;
} else {
- convertedText[dstIdx++] = 0; // Replace invalid characters with space (index 0)
+ convertedText[dstIdx++] = 0;
}
}
convertedText[dstIdx] = '\0';
- // Draw the text string (with converted character indices), but only when subtitles are
- // enabled: opcode 9 is a subtitle/message path, so it honors the global
- // "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
drawRebel2StringWrap(fontSet, convertedText, dstIdx, renderBitmap, clipRect, posX, posY, width, textColor, styleFlags);
} else {
- // Single-line text
drawRebel2String(fontSet, convertedText, dstIdx, renderBitmap, clipRect, posX, posY, width, textColor, styleFlags);
}
}
@@ -2399,41 +2134,29 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
}
void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
- // Opcode 4: Enemy position update
- int16 enemyId = b.readSint16LE(); // Offset +8
- int16 x = b.readSint16LE(); // Offset +10 (0x0A)
+ int16 enemyId = b.readSint16LE();
+ int16 x = b.readSint16LE();
- // If enemy is disabled in bit table, skip update
bool disabled = isBitSet(enemyId);
- int16 y = b.readSint16LE(); // Offset +12 (0x0C)
- int16 w = b.readSint16LE(); // Offset +14 (0x0E) - Width
- int16 h = b.readSint16LE(); // Offset +16 (0x10) - Height
+ int16 y = b.readSint16LE();
+ int16 w = b.readSint16LE();
+ int16 h = b.readSint16LE();
- // never a direct hit target â it is destroyed by clearing the gauge group (par4) it depends
- // on. Show it while that group still has elements, hide it once the group is cleared.
+ // Turret surface targets mirror their gauge group state.
if (par3 == 2 && _rebelHandler == 0x26) {
const int surfaceIdx = (par4 >= 100 && par4 < 110) ? (par4 - 100) : -1;
if (surfaceIdx >= 0 && _rebelGaugeCleared[surfaceIdx])
setBit(enemyId);
else
clearBit(enemyId);
- return; // surfaces are not added to the hittable enemy list
+ return;
}
- // If disabled, stop processing this object
if (disabled) {
- // debugC(DEBUG_INSANE, "Skipping Opcode 4 for disabled enemy ID=%d", enemyId);
return;
}
- // halfW = w >> 1
- // halfH = h >> 1
- // centerX = x + halfW
- // centerY = y + halfH
- // But for drawing the bounding box, we want the top-left corner (x, y) and full dimensions.
-
- // Update enemy list for hit detection
debugC(DEBUG_INSANE, "Opcode4: handler=%d enemyId=%d par2=%d par3=%d par4/type=%d pos=(%d,%d) size=(%d,%d)",
_rebelHandler, enemyId, par2, par3, par4, x, y, w, h);
@@ -2442,13 +2165,8 @@ void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b
for (it = _enemies.begin(); it != _enemies.end(); ++it) {
if (it->id == enemyId) {
it->rect = Common::Rect(x, y, x + w, y + h);
- it->type = par4; // Enemy type from IACT offset +6 (userId)
- // The _iactBits[] bit table is the authoritative alive/dead state.
- // We only reach here when isBitSet(enemyId) == false, meaning
- // the game considers this enemy alive. Reset destroyed/active
- // to match â this is critical when clearBit(0) re-enables all
- // enemies at wave start but the _enemies list still has stale
- // destroyed=true from a previous wave.
+ it->type = par4;
+ // IACT bit state is authoritative; clear stale destruction state here.
it->active = true;
it->destroyed = false;
found = true;
@@ -2460,7 +2178,6 @@ void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b
}
}
-// initEnemyStruct -- Create and append a new enemy entry.
void InsaneRebel2::initEnemyStruct(int id, int32 x, int32 y, int32 w, int32 h, bool active, bool destroyed, int32 explosionFrame, int type) {
enemy e;
e.id = id;
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index 63bf52c55b1..cc22446f651 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -45,8 +45,6 @@ static void purgeRebel2GameplayInputEvents(Common::EventManager *eventMan) {
eventMan->purgeKeyboardEvents();
}
-// Level Loading System
-
Common::String InsaneRebel2::getLevelDir(int levelId) {
return Common::String::format("LEV%02d", levelId);
}
@@ -55,21 +53,16 @@ Common::String InsaneRebel2::getLevelPrefix(int levelId) {
return Common::String::format("%02d", levelId);
}
-// Full game loop: intro, main menu, pilot select, chapter select, level
-// progression. Called from ScummEngine::go().
void InsaneRebel2::runGame() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Demo: just play the demo video and return
if (_vm->_game.features & GF_DEMO) {
splayer->play("OPEN/O_DEMO.SAN", 15);
return;
}
- // Case 0: Play intro sequence (Fox logo, LucasArts logo, O_OPEN_A, O_OPEN_B)
playIntroSequence();
- // Cases 1-4: Main menu -> pilot select -> chapter select -> gameplay loop
while (!_vm->shouldQuit()) {
int menuResult = runMainMenu();
@@ -98,7 +91,6 @@ void InsaneRebel2::runGame() {
break;
if (chapterResult == kChapterSelectPlay) {
- // _selectedChapter is 0-based, runLevel expects 1-based
int selectedLevel = _selectedChapter + 1;
debugC(DEBUG_INSANE, "InsaneRebel2: Starting chapter %d (level %d)", _selectedChapter + 1, selectedLevel);
@@ -106,7 +98,6 @@ void InsaneRebel2::runGame() {
playEndingSequence();
}
- // Level progression loop: on success, advance to next level
while (!_vm->shouldQuit() && selectedLevel >= 1 && selectedLevel <= 15) {
int result = runLevel(selectedLevel);
@@ -128,15 +119,10 @@ void InsaneRebel2::runGame() {
if (_vm->shouldQuit())
break;
}
- // If kChapterSelectBack, loop back to main menu
}
}
}
-// - If 'f','o','x' keys all held: play CREDITS/O_OPEN_C.SAN (Fox logo easter egg)
-// - If 'b','o','t' keys all held: play CREDITS/O_OPEN_D.SAN (LucasArts logo)
-// - Else: play OPEN/O_OPEN_A.SAN (main intro)
-// We skip the easter eggs and play both O_OPEN_A + O_OPEN_B unconditionally.
void InsaneRebel2::playIntroSequence() {
debugC(DEBUG_INSANE, "Playing intro sequence");
@@ -145,7 +131,6 @@ void InsaneRebel2::playIntroSequence() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Play main intro (OPEN/O_OPEN_A.SAN)
debugC(DEBUG_INSANE, "Playing main intro (O_OPEN_A.SAN)");
splayer->setCurVideoFlags(0x28);
splayer->play("OPEN/O_OPEN_A.SAN", 15);
@@ -153,8 +138,6 @@ void InsaneRebel2::playIntroSequence() {
if (_vm->shouldQuit())
return;
- // Play additional intro (OPEN/O_OPEN_B.SAN)
- // We play unconditionally (matches "Continue Intro" menu behavior)
debugC(DEBUG_INSANE, "Playing additional intro (O_OPEN_B.SAN)");
splayer->setCurVideoFlags(0x28);
splayer->play("OPEN/O_OPEN_B.SAN", 15);
@@ -169,21 +152,18 @@ void InsaneRebel2::playMissionBriefing() {
splayer->play("OPEN/O_LEVEL.SAN", 15);
}
-// playCinematic -- Play a cinematic/cutscene video.
-// Resets handler to 0 (no HUD) and sets flags to 0x28 (cinematic + buffer preserve).
void InsaneRebel2::playCinematic(const char *filename) {
restoreDamageFlashPalette();
resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
- _rebelStatusBarSprite = 0; // No status bar during cinematics
+ _rebelStatusBarSprite = 0;
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- splayer->setCurVideoFlags(0x28); // Cinematic mode + buffer preserve (0x20 | 0x08)
+ splayer->setCurVideoFlags(0x28);
splayer->play(filename, 15);
}
-// displayLength = currentFrame + 10 - fadeInFrame, capped at 0xBE (190) chars.
void InsaneRebel2::playVideoWithText(const char *filename, int textID, int textX, int textY,
int fadeInFrame, int fadeOutFrame) {
@@ -193,7 +173,6 @@ void InsaneRebel2::playVideoWithText(const char *filename, int textID, int textX
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
- // Set up text overlay state â procPostRendering reads these each frame
_textOverlayActive = true;
_textOverlayID = textID;
_textOverlayX = textX;
@@ -208,7 +187,6 @@ void InsaneRebel2::playVideoWithText(const char *filename, int textID, int textX
_textOverlayActive = false;
}
-// playLevelBegin -- Level beginning cinematic (LEVXX/XXBEG.SAN).
void InsaneRebel2::playLevelBegin(int levelId) {
struct TextOverlayParams {
@@ -219,7 +197,6 @@ void InsaneRebel2::playLevelBegin(int levelId) {
int fadeOutFrame;
};
- // Table of per-level text overlay parameters
// Text IDs are sequential: 0xAA (level 1) through 0xB8 (level 15)
const TextOverlayParams levelTextParams[16] = {
{ -1, 0, 0, 0, 0}, // Level 0 (unused)
@@ -280,7 +257,7 @@ void InsaneRebel2::playLevelRetry(int levelId) {
resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
- _rebelStatusBarSprite = 0; // Reset for retry - will be set by IACT opcode 6 if needed
+ _rebelStatusBarSprite = 0;
Common::String dir = getLevelDir(levelId);
Common::String prefix = getLevelPrefix(levelId);
@@ -299,7 +276,7 @@ void InsaneRebel2::playLevelGameOver(int levelId) {
resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
- _rebelStatusBarSprite = 0; // No status bar during game over cinematic
+ _rebelStatusBarSprite = 0;
Common::String dir = getLevelDir(levelId);
Common::String prefix = getLevelPrefix(levelId);
@@ -312,27 +289,17 @@ void InsaneRebel2::playLevelGameOver(int levelId) {
splayer->play(filename.c_str(), 15);
}
-// 1. Play difficulty-dependent finale video:
-// - Difficulty 2: FINAL/F_FIN_B.SAN
-// - Difficulty 3: FINAL/F_FIN_C.SAN
-// - Default: FINAL/F_FIN_A.SAN
-// 2. Play credits: FINAL/F_CREDIT.SAN
-// 3. Play epilogue: FINAL/F_EPILOG.SAN
-// 4. Return to main menu
void InsaneRebel2::playEndingSequence() {
debugC(DEBUG_INSANE, "Playing ending sequence (difficulty=%d)", _difficulty);
- // Switch to gameplay state to stop menu overlay rendering
_gameState = kStateGameplay;
_menuInputActive = false;
- // Clear the screen to remove any leftover menu pixels
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
memset(vs->getPixels(0, 0), 0, vs->pitch * vs->h);
_vm->markRectAsDirty(kMainVirtScreen, 0, vs->w, 0, vs->h);
- // Difficulty-dependent finale video
if (_difficulty == 2) {
playCinematic("FINAL/F_FIN_B.SAN");
} else if (_difficulty == 3) {
@@ -344,18 +311,14 @@ void InsaneRebel2::playEndingSequence() {
if (_vm->shouldQuit())
return;
- // Credits
playCinematic("FINAL/F_CREDIT.SAN");
if (_vm->shouldQuit())
return;
- // Epilogue
playCinematic("FINAL/F_EPILOG.SAN");
}
-// playCreditsSequence -- Main menu credits (OPEN/O_CREDIT.SAN).
-// This is the credits accessible from the main menu, NOT the ending credits.
void InsaneRebel2::playCreditsSequence() {
debugC(DEBUG_INSANE, "Playing menu credits");
@@ -402,27 +365,22 @@ void InsaneRebel2::warpGameplayMouseNow(int x, int y) {
eventMan->purgeMouseEvents();
}
-// runLevel -- Main level dispatcher, calls per-level handlers.
int InsaneRebel2::runLevel(int levelId) {
debugC(DEBUG_INSANE, "Starting level %d", levelId);
- // Validate level ID
if (levelId < 1 || levelId > 15) {
warning("Rebel2: Invalid level ID %d", levelId);
return kLevelReturnToMenu;
}
- // Switch to gameplay state to stop menu overlay rendering
_gameState = kStateGameplay;
_menuInputActive = false;
- // Clear the screen to remove any leftover menu pixels
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
memset(vs->getPixels(0, 0), 0, vs->pitch * vs->h);
_vm->markRectAsDirty(kMainVirtScreen, 0, vs->w, 0, vs->h);
- // Set the current level
_selectedLevel = levelId;
// Levels 1-6 use types 0-5, but Level 6 also uses type 6 mid-level.
@@ -433,14 +391,10 @@ int InsaneRebel2::runLevel(int levelId) {
};
_rebelLevelType = kLevelTypeMap[levelId];
- // Lock the mouse to the game window during gameplay.
- // the mouse to the game window. Without locking, the cursor can escape the
- // window making the ship uncontrollable.
_gameplaySectionActive = false;
CursorMan.showMouse(false);
g_system->lockMouse(true);
- // Initialize common player state
_playerLives = 3;
_playerShield = 255;
_playerScore = 0;
@@ -451,7 +405,6 @@ int InsaneRebel2::runLevel(int levelId) {
_currentPhase = 1;
_skipSectionRequested = false;
- // Dispatch to per-level handler
int result;
switch (levelId) {
case 1:
@@ -504,7 +457,6 @@ int InsaneRebel2::runLevel(int levelId) {
break;
}
- // Unlock the mouse when returning to menu
restoreIOSGamepadController();
g_system->lockMouse(false);
CursorMan.showMouse(true);
@@ -512,22 +464,17 @@ int InsaneRebel2::runLevel(int levelId) {
return result;
}
-// Helper Functions
-
int InsaneRebel2::getRandomVariant(int max) {
return _vm->_rnd.getRandomNumber(max - 1);
}
-// selectDeathVideoVariant -- Frame-based death video selection
Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int frame) {
switch (levelId) {
case 1:
- // Level 1: Random between A and B
return (getRandomVariant(2) == 0) ? "B" : "A";
case 2:
- // Level 2: Just "DIE" (no variants)
return "";
case 3:
@@ -559,7 +506,6 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
return "A";
case 5:
- // Level 5: Random between A and B
return (getRandomVariant(2) == 0) ? "B" : "A";
case 6:
@@ -610,7 +556,6 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
}
case 7:
- // We use phase as a proxy (phase 2 = reached fork)
return (phase >= 2) ? "B" : "A";
case 8:
@@ -623,7 +568,6 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
return "";
case 11:
- // Phase 1 â DIE_A, Phase 2 â DIE_B, Phase 3 â DIE_C
if (phase <= 1)
return "A";
if (phase == 2)
@@ -667,14 +611,13 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
}
}
-// playLevelDeathVariant -- Death video with variant selection.
void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
restoreDamageFlashPalette();
resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
- _rebelStatusBarSprite = 0; // No status bar during death cinematic
+ _rebelStatusBarSprite = 0;
Common::String dir = getLevelDir(levelId);
Common::String prefix = getLevelPrefix(levelId);
@@ -682,7 +625,6 @@ void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
Common::String filename;
if (variant.empty()) {
- // No variant suffix.
filename = Common::String::format("%s/%sDIE.SAN", dir.c_str(), prefix.c_str());
} else {
filename = Common::String::format("%s/%sDIE_%s.SAN", dir.c_str(), prefix.c_str(), variant.c_str());
@@ -695,24 +637,21 @@ void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
splayer->play(filename.c_str(), 15);
}
-// playLevelRetryVariant -- Phase-specific retry video.
void InsaneRebel2::playLevelRetryVariant(int levelId, int phase) {
restoreDamageFlashPalette();
resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
- _rebelStatusBarSprite = 0; // Reset for retry - will be set by IACT opcode 6 if needed
+ _rebelStatusBarSprite = 0;
Common::String dir = getLevelDir(levelId);
Common::String prefix = getLevelPrefix(levelId);
Common::String filename;
if ((levelId == 3 || levelId == 6) && phase == 2) {
- // Level 3/6 phase 2 has its own retry video: xxRETRYB.SAN
filename = Common::String::format("%s/%sRETRYB.SAN", dir.c_str(), prefix.c_str());
} else {
- // Standard retry video
filename = Common::String::format("%s/%sRETRY.SAN", dir.c_str(), prefix.c_str());
}
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index 9baeddf61b3..555441cdfec 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -40,8 +40,6 @@
namespace Scumm {
-// Menu System Implementation
-
static void setRebel2MixerVolume(ScummEngine_v7 *vm, int volumeLevel) {
const int mixerVolume = CLIP<int>(volumeLevel * 2, 0, (int)Audio::Mixer::kMaxMixerVolume);
vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, mixerVolume);
@@ -95,27 +93,18 @@ void InsaneRebel2::unlockAllChapters() {
}
}
-// contains ONLY audio (no FOBJ frames) resulting in a black background.
-// We always use O_MENU_X.SAN (A-O) which have 320x200 background images.
Common::String InsaneRebel2::getRandomMenuVideo() {
-
- // Select random variant (0-14 maps to A-O), ensuring different from last
int variant;
do {
- variant = _vm->_rnd.getRandomNumber(14); // 0-14
+ variant = _vm->_rnd.getRandomNumber(14);
} while (variant == _lastMenuVariant && _lastMenuVariant >= 0);
_lastMenuVariant = variant;
- // Map 0-14 to A-O (case 0/default = A, 1 = B, etc.)
char letter = 'A' + variant;
debugC(DEBUG_INSANE, "Selected menu variant %c", letter);
return Common::String::format("OPEN/O_MENU_%c.SAN", letter);
}
-// Returns -1 (no action) or a 0-based selected menu item.
-// Events captured by notifyEvent() before ScummEngine consumes them.
-// Keyboard: Up=0x148, Down=0x150, Enter=0x0d.
-// Physical ESC is handled by notifyEvent() and opens the global menu.
int InsaneRebel2::processMenuInput() {
int result = -1;
@@ -126,12 +115,11 @@ int InsaneRebel2::processMenuInput() {
const int itemHitTop = highRes ? 2 : 1;
const int itemHitHeight = highRes ? 18 : 10;
- // Process events from the queue (populated by notifyEvent)
while (!_menuEventQueue.empty()) {
Common::Event event = _menuEventQueue.pop();
switch (event.type) {
case Common::EVENT_KEYDOWN:
- _menuInactivityTimer = 0; // Reset inactivity timer on any input
+ _menuInactivityTimer = 0;
switch (event.kbd.keycode) {
case Common::KEYCODE_UP:
@@ -161,8 +149,7 @@ int InsaneRebel2::processMenuInput() {
break;
case Common::KEYCODE_ESCAPE:
- // Synthetic custom back action - quit/back (last item)
- result = _menuItemCount - 1; // Select quit option
+ result = _menuItemCount - 1;
debugC(DEBUG_INSANE, "Menu: Back action - selecting quit (item %d)", result);
break;
@@ -207,7 +194,6 @@ int InsaneRebel2::processMenuInput() {
case Common::EVENT_QUIT:
case Common::EVENT_RETURN_TO_LAUNCHER:
- // Handle quit request - select quit option
result = _menuItemCount - 1;
break;
@@ -216,7 +202,6 @@ int InsaneRebel2::processMenuInput() {
}
}
- // Decrement repeat delay counter (for smooth keyboard navigation)
if (_menuRepeatDelay > 0) {
_menuRepeatDelay--;
}
@@ -227,15 +212,6 @@ int InsaneRebel2::processMenuInput() {
void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int height,
const char **items, int numItems, int selection,
bool leftAligned) {
- // items[0] = title string, items[1..numItems] = selectable items
- // Title X: center - titleWidth/2 (centerX = 160)
- // Item X: center - textWidth/2
- // Box X: center - bracketWidth/2
- // Title X: 0x28 = 40
- // Item X: 0x17 = 23
- // Box X: 0x14 = 20
- // Both modes:
-
const bool highRes = isHiRes();
const int centerX = highRes ? 0x140 : width / 2;
const int titleY = highRes ?
@@ -260,18 +236,12 @@ void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int h
drawMenuString(renderBitmap, str, x, y, 1);
};
- // Draw title - items[0]
- // Centered: X = center - titleWidth/2
- // Left-aligned: X = 40 (0x28)
{
int titleWidth = getStringWidth(items[0]);
int titleX = leftAligned ? (highRes ? 0x50 : 0x28) : (centerX - titleWidth / 2);
drawString(items[0], titleX, titleY);
}
- // Draw selectable items with selection highlight box
- // Centered: item X = center - textWidth/2, box X = center - bracketWidth/2
- // Left-aligned: item X = 23 (0x17), box X = 20 (0x14)
for (int i = 0; i < numItems; i++) {
int itemY = itemBaseY + i * itemSpacing;
const char *text = items[i + 1];
@@ -284,10 +254,8 @@ void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int h
int bracketWidth = textWidth + (highRes ? 12 : 6);
int bracketHeight = highRes ? 20 : 10;
- // bit0==0: 8-16=248(0xF8), bit0==1: 0-16=240(0xF0)
byte highlightColor = ((_vm->_system->getMillis() / 133) & 1) ? 248 : 240;
- // Box position: Y = itemY - 1 (0x67 vs 0x68)
int leftX = leftAligned ? (highRes ? 0x28 : 0x14) : (centerX - bracketWidth / 2);
int rightX = leftX + bracketWidth;
int topY = highRes ? itemY - 2 : itemY - 1;
@@ -320,8 +288,6 @@ void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int h
}
}
-// parseFormatCode -- Shared ^fNN/^cNNN/^^/^l parser.
-// Returns: fontIdx (>=0) on font change, -2 on color/newline, -1 on no match.
int InsaneRebel2::parseFormatCode(const char *&str, int &outColor) {
if (*str != '^')
return -1;
@@ -346,7 +312,6 @@ int InsaneRebel2::parseFormatCode(const char *&str, int &outColor) {
return -1;
}
-// getMenuStringWidth -- Format-code-aware string width.
int InsaneRebel2::getMenuStringWidth(const char *str) const {
NutRenderer *fonts[3] = { _smush_talkfontNut, _smush_smalfontNut, _smush_titlefontNut };
NutRenderer *defaultFont = fonts[0] ? fonts[0] : _smush_smalfontNut;
@@ -368,7 +333,6 @@ int InsaneRebel2::getMenuStringWidth(const char *str) const {
return w;
}
-// Format-code-aware string rendering at (x, y).
void InsaneRebel2::drawMenuString(byte *renderBitmap, const char *str, int x, int y, int defaultColor) {
NutRenderer *fonts[3] = { _smush_talkfontNut, _smush_smalfontNut, _smush_titlefontNut };
NutRenderer *defaultFont = fonts[0] ? fonts[0] : _smush_smalfontNut;
@@ -405,24 +369,12 @@ void InsaneRebel2::drawMenuStringRight(byte *renderBitmap, const char *str, int
}
void InsaneRebel2::drawMenuOverlay(byte *renderBitmap, int pitch, int width, int height) {
- // Main menu renderer - calls shared drawMenuItems()
- // Menu strings loaded from GAME.TRS (keyboard mode indices 10-17):
- // TRS index 10: "^f02Game Main Menu" -> Title (uses TITLFONT)
- // TRS index 11: "^f01^c005Start Game" -> Item 0 (uses SMALFONT, color 5)
- // TRS index 12: "^f01^c009Options" -> Item 1
- // TRS index 13: "^f01^c009Calibrate Joystick" -> Item 2
- // TRS index 14: "^f01^c009Continue Intro" -> Item 3
- // TRS index 15: "^f01^c009Show Top Pilots" -> Item 4
- // TRS index 16: "^f01^c009Show Credits" -> Item 5
- // TRS index 17: "^f01^c240Return to Launcher" -> Item 6 (color 240)
-
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
if (!splayer) {
debugC(DEBUG_INSANE, "drawMenuOverlay: SmushPlayer not available for TRS strings!");
return;
}
- // Load TRS strings 10-17 (title + 7 selectable items)
const char *menuItems[8];
for (int i = 0; i < 8; i++) {
menuItems[i] = splayer->getString(10 + i);
@@ -435,9 +387,6 @@ void InsaneRebel2::drawMenuOverlay(byte *renderBitmap, int pitch, int width, int
drawMenuItems(renderBitmap, pitch, width, height, menuItems, 7, _menuSelection);
}
-// Pause Overlay
-
-// pauseFillRect -- Helper to fill a rectangle in the frame buffer with bounds checking.
void pauseFillRect(byte *buf, int bufW, int bufH, int x, int y, int w, int h, byte color) {
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
@@ -448,11 +397,6 @@ void pauseFillRect(byte *buf, int bufW, int bufH, int x, int y, int w, int h, by
memset(buf + row * bufW + x, color, w);
}
-// showPauseOverlay -- Dimmed overlay with metallic frame and "PAUSED" text.
-// indices 16-79, relying on a built-in grayscale ramp at those positions.
-// We instead dim the system palette (25% brightness) which achieves the same
-// visual effect regardless of palette layout. The pixel buffer is only modified
-// for the frame decorations and text â the game frame pixels stay unchanged.
void InsaneRebel2::showPauseOverlay() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
if (!splayer)
@@ -475,16 +419,10 @@ void InsaneRebel2::showPauseOverlay() {
byte dimPal[768];
memcpy(dimPal, palette, 768);
for (int i = 0; i < 768; i++)
- dimPal[i] >>= 2; // 25% brightness
+ dimPal[i] >>= 2;
- // Override specific palette entries for UI elements.
- // Frame bars (0x50): medium gray
dimPal[0x50 * 3 + 0] = 80; dimPal[0x50 * 3 + 1] = 80; dimPal[0x50 * 3 + 2] = 80;
- // Rivet outline (0x51): slightly lighter gray
dimPal[0x51 * 3 + 0] = 110; dimPal[0x51 * 3 + 1] = 110; dimPal[0x51 * 3 + 2] = 110;
- // RA2 NUT fonts use hardcoded palette indices 1-4 for rendering:
- // 1=body color (remapped to col param), 2=highlight, 3=anti-alias, 4=outline
- // Index 5 is used by TRS ^c005 escape code as the text body color.
dimPal[1 * 3 + 0] = 255; dimPal[1 * 3 + 1] = 255; dimPal[1 * 3 + 2] = 255;
dimPal[2 * 3 + 0] = 188; dimPal[2 * 3 + 1] = 188; dimPal[2 * 3 + 2] = 188;
dimPal[3 * 3 + 0] = 128; dimPal[3 * 3 + 1] = 128; dimPal[3 * 3 + 2] = 128;
@@ -493,16 +431,12 @@ void InsaneRebel2::showPauseOverlay() {
_vm->_system->getPaletteManager()->setPalette(dimPal, 0, 256);
- // Horizontal border lines: 2px thick at y=23 and y=175
pauseFillRect(frameBuffer, width, height, 0, 0x17, 0x140, 2, 0x50);
pauseFillRect(frameBuffer, width, height, 0, 0xAF, 0x140, 2, 0x50);
- // Thick side bars: 40px wide on left and right
pauseFillRect(frameBuffer, width, height, 0, 0, 0x28, 200, 0x50);
pauseFillRect(frameBuffer, width, height, 0x118, 0, 0x28, 200, 0x50);
- // Rivet decorations along left side bar.
- // Layered rectangles: outer ring (0x51), inner fill (4).
for (int i = 0; i < 6; i++) {
int yOff = i * 0x24; // i * 36
pauseFillRect(frameBuffer, width, height, 0x0C, yOff, 0x19, 0x11, 0x51);
@@ -512,7 +446,6 @@ void InsaneRebel2::showPauseOverlay() {
pauseFillRect(frameBuffer, width, height, 0x0C, yOff + 1, 0x19, 0x0F, 4);
}
- // Right side bar rivets: same pattern at x=282 (0x11A).
for (int i = 0; i < 6; i++) {
int yOff = i * 0x24;
int xBase = 0x11A;
@@ -523,7 +456,6 @@ void InsaneRebel2::showPauseOverlay() {
pauseFillRect(frameBuffer, width, height, xBase, yOff + 1, 0x19, 0x0F, 4);
}
- // fg=4 is the foreground color (used by ^cNNN in the TRS string itself).
const char *pauseText = splayer->getString(0x78);
if (!pauseText || !pauseText[0])
pauseText = "Game Paused";
@@ -535,7 +467,6 @@ void InsaneRebel2::showPauseOverlay() {
}
if (multiFont) {
Common::Rect clipRect(0, 0, screenW, screenH);
- // We use single-buffer mode (y=10).
multiFont->drawString(pauseText, frameBuffer, clipRect, 10, 10, width, 4, kStyleAlignLeft);
}
@@ -543,7 +474,6 @@ void InsaneRebel2::showPauseOverlay() {
_vm->_system->updateScreen();
}
-// Returns kMenuNewGame, kMenuResumeDemo, or kMenuQuit.
int InsaneRebel2::runMainMenu() {
debugC(DEBUG_INSANE, "Entering main menu");
@@ -551,35 +481,22 @@ int InsaneRebel2::runMainMenu() {
resetMenu();
_gameState = kStateMainMenu;
- // Enable menu input capture via EventObserver
_menuInputActive = true;
- // Clear any stale events
while (!_menuEventQueue.empty())
_menuEventQueue.pop();
- // Get the SmushPlayer from ScummEngine_v7
- // Note: _player isn't set until SmushPlayer::initAudio() is called during playback
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Main menu loop
while (!_vm->shouldQuit()) {
- // Reset video finish flag before playing menu
_vm->_smushVideoShouldFinish = false;
- // Select and play a random menu video
Common::String menuVideo = getRandomMenuVideo();
debugC(DEBUG_INSANE, "Playing menu video: %s", menuVideo.c_str());
- // Set video flags for menu (0x20 = intro/menu flag)
- // This tells procPostRendering we're in menu mode
splayer->setCurVideoFlags(0x20);
- // Play the menu video
- // Input is processed in procPostRendering during playback
- // When user confirms selection, _vm->_smushVideoShouldFinish is set
splayer->play(menuVideo.c_str(), 15);
- // Check for quit
if (_vm->shouldQuit()) {
_menuInputActive = false;
return kMenuQuit;
@@ -592,42 +509,32 @@ int InsaneRebel2::runMainMenu() {
return kMenuResumeDemo;
}
- // Only process selection if user explicitly confirmed (ENTER/ESC),
- // not when video ended naturally (EOF sets _smushVideoShouldFinish too)
if (!_menuSelectionConfirmed) {
continue;
}
- // Clear the flags
_vm->_smushVideoShouldFinish = false;
_menuSelectionConfirmed = false;
- // A selection was made - process it
debugC(DEBUG_INSANE, "Menu video ended with selection=%d", _menuSelection);
- // case 0 (TRS 11): Start Game -> pilot selection, returns 2
- // case 3 (TRS 14): Continue Intro -> return to intro/demo loop
- // case 5 (TRS 16): Show Credits -> play O_CREDIT.SAN, returns 1
- // case 6 (TRS 17): Return to Launcher -> quit, returns 0
switch (_menuSelection) {
- case 0: // Start Game -> go to pilot selection
+ case 0:
debugC(DEBUG_INSANE, "Start Game selected - going to pilot selection");
_gameState = kStatePilotSelect;
_menuInputActive = false;
- return kMenuNewGame; // Return 2 (kMenuNewGame)
+ return kMenuNewGame;
case 1:
debugC(DEBUG_INSANE, "Options selected");
showOptionsMenu();
break;
- case 2: // Calibrate Joystick
+ case 2:
debugC(DEBUG_INSANE, "Calibrate Joystick selected - no-op for modern joystick support");
- // Modern controller support uses live keymapper actions; no explicit
- // joystick calibration flow is required here.
break;
- case 3: // Continue Intro -> return to intro/demo loop
+ case 3:
debugC(DEBUG_INSANE, "Continue Intro selected - resuming intro/demo loop");
_menuInputActive = false;
return kMenuResumeDemo;
@@ -637,7 +544,7 @@ int InsaneRebel2::runMainMenu() {
showTopPilots();
break;
- case 5: // Show Credits -> play credits video
+ case 5:
debugC(DEBUG_INSANE, "Show Credits selected - playing O_CREDIT.SAN");
_gameState = kStateCredits;
_menuInputActive = false;
@@ -648,7 +555,7 @@ int InsaneRebel2::runMainMenu() {
resetMenuGamepadAxis();
break;
- case 6: // Return to Launcher -> quit game
+ case 6:
debugC(DEBUG_INSANE, "Return to Launcher selected");
_menuInputActive = false;
return kMenuQuit;
@@ -663,26 +570,20 @@ int InsaneRebel2::runMainMenu() {
return kMenuQuit;
}
-// Chapter Selection Screen
-
-// Returns kChapterSelectPlay, kChapterSelectBack, or kChapterSelectQuit.
int InsaneRebel2::runChapterSelect() {
debugC(DEBUG_INSANE, "Entering chapter selection (FUN_00415CF8)");
- // Enable menu input capture
_menuInputActive = true;
while (!_menuEventQueue.empty())
_menuEventQueue.pop();
resetMenuGamepadAxis();
- // Initialize chapter selection state
- // Finds highest unlocked chapter. With debug unlock all = 15 (FINALE).
_chapterSelection = 15;
while (_chapterSelection > 0 && !_chapterUnlocked[_chapterSelection]) {
_chapterSelection--;
}
- _chapterItemCount = 17; // 16 chapters + RETURN TO PILOTS
+ _chapterItemCount = 17;
_selectedChapter = 0;
_passwordInput = "";
_menuRepeatDelay = 0;
@@ -691,12 +592,10 @@ int InsaneRebel2::runChapterSelect() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Initialize preview offset for initial selection
_previewOffsetX = -90;
_previewOffsetY = _chapterSelection * -50 + 75;
- // Bits 16..1 correspond to chapters 0..15: set if unlocked, clear if locked.
- // These control SKIP chunks in O_LEVEL.SAN for locked/unlocked preview variants.
+ // O_LEVEL.SAN SKIP chunks use bits 16..1 for chapter lock state.
for (int i = 0; i < 16; i++) {
if (_chapterUnlocked[i])
setBit(16 - i);
@@ -704,20 +603,13 @@ int InsaneRebel2::runChapterSelect() {
clearBit(16 - i);
}
- // O_LEVEL.SAN contains chapter preview thumbnails at specific FOBJ positions.
- // The FOBJ offset system scrolls the correct preview into the preview box area.
while (!_vm->shouldQuit()) {
_vm->_smushVideoShouldFinish = false;
debugC(DEBUG_INSANE, "Playing chapter select background: OPEN/O_LEVEL.SAN");
- // Flags: 0x20 (overlay/menu rendering) | 0x08 (preserve buffer, suppress
- // O_LEVEL.SAN AHDR specifies 15fps; flag 0x08 suppresses this override
- // so we use our intended 12fps. The preview screen is cleared each frame
- // by procPreRendering's memset, so buffer preservation is harmless.
splayer->setCurVideoFlags(0x28);
- // Play O_LEVEL.SAN â preview thumbnails are rendered by FOBJ offset
splayer->play("OPEN/O_LEVEL.SAN", 15);
if (_vm->shouldQuit()) {
@@ -726,7 +618,6 @@ int InsaneRebel2::runChapterSelect() {
return kChapterSelectQuit;
}
- // Only process selection if user explicitly confirmed
if (!_menuSelectionConfirmed) {
continue;
}
@@ -737,7 +628,6 @@ int InsaneRebel2::runChapterSelect() {
debugC(DEBUG_INSANE, "Chapter selection made: %d", _chapterSelection);
if (_chapterSelection == 16) {
- // BACK selected (index 16 = 17th item)
debugC(DEBUG_INSANE, "BACK to main menu selected");
setVirtualKeyboardVisible(false);
_menuInputActive = false;
@@ -746,7 +636,6 @@ int InsaneRebel2::runChapterSelect() {
if (_chapterSelection >= 0 && _chapterSelection < 16) {
if (_chapterUnlocked[_chapterSelection]) {
- // Unlocked chapter â play it
_selectedChapter = _chapterSelection;
debugC(DEBUG_INSANE, "Chapter %d selected (unlocked)", _selectedChapter + 1);
setVirtualKeyboardVisible(false);
@@ -770,10 +659,9 @@ int InsaneRebel2::runChapterSelect() {
_passwordInput.clear();
updateMenuVirtualKeyboard();
debugC(DEBUG_INSANE, "Chapter %d unlocked via password", _chapterSelection + 1);
- continue; // Re-render with updated unlock state
+ continue;
}
}
- // Wrong password or no password entered
_passwordInput.clear();
debugC(DEBUG_INSANE, "Password rejected for chapter %d", _chapterSelection + 1);
}
@@ -785,9 +673,6 @@ int InsaneRebel2::runChapterSelect() {
}
int InsaneRebel2::processChapterSelectInput() {
- // Process input for chapter selection screen
- // Returns: -1 = no action, 0+ = item selected
-
int result = -1;
while (!_menuEventQueue.empty()) {
@@ -798,7 +683,6 @@ int InsaneRebel2::processChapterSelectInput() {
case Common::EVENT_KEYDOWN:
switch (event.kbd.keycode) {
case Common::KEYCODE_UP:
- // Move selection up, wrap to bottom
_chapterSelection--;
if (_chapterSelection < 0) {
_chapterSelection = _chapterItemCount - 1;
@@ -810,7 +694,6 @@ int InsaneRebel2::processChapterSelectInput() {
break;
case Common::KEYCODE_DOWN:
- // Move selection down, wrap to top
_chapterSelection++;
if (_chapterSelection >= _chapterItemCount) {
_chapterSelection = 0;
@@ -830,9 +713,8 @@ int InsaneRebel2::processChapterSelectInput() {
break;
case Common::KEYCODE_ESCAPE:
- // Synthetic custom back action (same as selecting BACK)
setVirtualKeyboardVisible(false);
- result = 16; // BACK index
+ result = 16;
debugC(DEBUG_INSANE, "ChapterSelect: Back action - back to menu");
break;
@@ -879,7 +761,6 @@ int InsaneRebel2::processChapterSelectInput() {
case Common::EVENT_MOUSEMOVE:
{
- // Item Y = numItems * -5 + i * 10 + 0x68
const bool highRes = isHiRes();
const int baseY = highRes ? (_chapterItemCount * -5 + 0x5a) * 2 + 0x1c : _chapterItemCount * -5 + 0x68;
const int itemSpacing = highRes ? 20 : 10;
@@ -912,22 +793,15 @@ int InsaneRebel2::processChapterSelectInput() {
}
void InsaneRebel2::drawPreviewBox(byte *renderBitmap, int pitch, int width, int height) {
- // Outer box: X=0xe4 (228), Y=0x49 (73), W=0x54 (84), H=0x36 (54), color=0xF8
- // Inner box: X=0xe5 (229), Y=0x4a (74), W=0x52 (82), H=0x34 (52), color=4
- // low-res anchors and dimensions.
const int scale = isHiRes() ? 2 : 1;
- // Outer border (bright)
int outerX = 228 * scale, outerY = 73 * scale, outerW = 84 * scale, outerH = 54 * scale;
byte outerColor = 0xF8;
- // Draw outer box edges
- // Top edge
for (int px = outerX; px < outerX + outerW && px < width; px++) {
if (outerY >= 0 && outerY < height && px >= 0)
renderBitmap[outerY * pitch + px] = outerColor;
}
- // Bottom edge
int bottomY = outerY + outerH - 1;
if (bottomY < height) {
for (int px = outerX; px < outerX + outerW && px < width; px++) {
@@ -935,12 +809,10 @@ void InsaneRebel2::drawPreviewBox(byte *renderBitmap, int pitch, int width, int
renderBitmap[bottomY * pitch + px] = outerColor;
}
}
- // Left edge
for (int py = outerY; py < outerY + outerH && py < height; py++) {
if (py >= 0 && outerX >= 0 && outerX < width)
renderBitmap[py * pitch + outerX] = outerColor;
}
- // Right edge
int rightX = outerX + outerW - 1;
if (rightX < width) {
for (int py = outerY; py < outerY + outerH && py < height; py++) {
@@ -949,16 +821,13 @@ void InsaneRebel2::drawPreviewBox(byte *renderBitmap, int pitch, int width, int
}
}
- // Inner border (dark)
int innerX = 229 * scale, innerY = 74 * scale, innerW = 82 * scale, innerH = 52 * scale;
byte innerColor = 4;
- // Top edge
for (int px = innerX; px < innerX + innerW && px < width; px++) {
if (innerY >= 0 && innerY < height && px >= 0)
renderBitmap[innerY * pitch + px] = innerColor;
}
- // Bottom edge
bottomY = innerY + innerH - 1;
if (bottomY < height) {
for (int px = innerX; px < innerX + innerW && px < width; px++) {
@@ -966,12 +835,10 @@ void InsaneRebel2::drawPreviewBox(byte *renderBitmap, int pitch, int width, int
renderBitmap[bottomY * pitch + px] = innerColor;
}
}
- // Left edge
for (int py = innerY; py < innerY + innerH && py < height; py++) {
if (py >= 0 && innerX >= 0 && innerX < width)
renderBitmap[py * pitch + innerX] = innerColor;
}
- // Right edge
rightX = innerX + innerW - 1;
if (rightX < width) {
for (int py = innerY; py < innerY + innerH && py < height; py++) {
@@ -991,37 +858,21 @@ Common::String InsaneRebel2::getRankString(int rating) {
return result;
}
-// Index formula: difficulty + (level * 3 - 3) * 2, level is 1-based (1-15), difficulty 0-5
const char *const kPasswordTable[90] = {
- // Level 1: diff 0-5
"JABBA", "EWOKS", "BANTHA", "ANAKIN", "WOOKIEE", "WOOKIEE",
- // Level 2: diff 0-5
"ENDOR", "CHEWIE", "KATANA", "KENOBI", "DROID", "DROID",
- // Level 3: diff 0-5
"LACHTON", "DANKIN", "DENGAR", "FORTUNA", "RODIAN", "RODIAN",
- // Level 4: diff 0-5
"BORSK", "NOGHRI", "PELLAEON", "MODON", "BPFASSH", "BPFASSH",
- // Level 5: diff 0-5
"KROYIES", "CHAMMA", "ITHULL", "OMMIN", "KSHYY", "KSHYY",
- // Level 6: diff 0-5
"AURIL", "BOGGA", "STENNESS", "REKKON", "TORVE", "TORVE",
- // Level 7: diff 0-5
"KAMPL", "INCOM", "MYRKR", "SHAZEEN", "SLUISSI", "SLUISSI",
- // Level 8: diff 0-5
"FERRIER", "KOTHLIS", "CHURBA", "KIIRIUM", "PALANHI", "PALANHI",
- // Level 9: diff 0-5
"GALIA", "KRATH", "ARTOO", "GUNDARK", "DROKKO", "DROKKO",
- // Level 10: diff 0-5
"DENARII", "SIOSK", "SATAL", "DIANOGA", "NATTH", "NATTH",
- // Level 11: diff 0-5
"SADOW", "ADEGAN", "LOBUE", "ATUARRE", "SABACC", "SABACC",
- // Level 12: diff 0-5
"ONDERON", "AMANOA", "DENEBA", "ESSADA", "ANDUR", "ANDUR",
- // Level 13: diff 0-5
"ALEEMA", "AMBRIA", "STURM", "PAPLOO", "ARKANIA", "ARKANIA",
- // Level 14: diff 0-5
"CATHAR", "SYLVAR", "CRADO", "NASHTAH", "DIATH", "DIATH",
- // Level 15: diff 0-5
"DOMINIS", "MIRALUKA", "CARRACK", "PESTAGE", "DREEBO", "DREEBO",
};
@@ -1032,7 +883,6 @@ Common::String InsaneRebel2::getChapterPassword(int level, int difficulty) {
return kPasswordTable[idx];
}
-// Draw score/info line at bottom of chapter select
void InsaneRebel2::drawChapterInfoLine(byte *renderBitmap, int pitch, int width, int height) {
if (_chapterSelection < 0 || _chapterSelection >= 16)
return;
@@ -1044,13 +894,10 @@ void InsaneRebel2::drawChapterInfoLine(byte *renderBitmap, int pitch, int width,
const int scale = isHiRes() ? 2 : 1;
if (_chapterUnlocked[_chapterSelection]) {
- // Unlocked: show score info using TRS 80 at X=25 (0x19), Y=190 (0xbe)
- // TRS 80 = "^f01^c248Pilots: %hd Score: %ld Rank: ^f00%s"
const char *fmtStr = splayer->getString(80);
if (!fmtStr || !fmtStr[0])
return;
- // Get pilot data for this chapter
int32 pilotLives = 0;
int32 pilotScore = 0;
int16 pilotRating = 0;
@@ -1061,7 +908,6 @@ void InsaneRebel2::drawChapterInfoLine(byte *renderBitmap, int pitch, int width,
}
Common::String rankStr = getRankString(pilotRating);
- // sprintf substitution: %hd â lives, %ld â score, %s â rank
Common::String displayStr = Common::String::format(fmtStr,
(short)pilotLives, (long)pilotScore, rankStr.c_str());
@@ -1080,30 +926,17 @@ void InsaneRebel2::drawChapterInfoLine(byte *renderBitmap, int pitch, int width,
}
}
-// Draw chapter selection overlay - called during O_LEVEL.SAN playback
void InsaneRebel2::drawChapterSelectOverlay(byte *renderBitmap, int pitch, int width, int height) {
- // GAME.TRS chapter selection strings:
- // TRS 40 = "^f02Chapters" (title)
- // TRS 41-56 = unlocked chapter names (e.g. "^f01^c244Chapter 1 - The Dreighton Triangle")
- // TRS 57 = "^f01^c240RETURN TO PILOTS"
- // TRS 60 = "^f02Chapters" (title, locked section duplicate)
- // TRS 61-76 = locked chapter names (e.g. "^f01^c244Chapter 1 -")
- // TRS 77 = "^f01^c240RETURN TO PILOTS" (locked section duplicate)
- // Menu array: items[0]=title, items[1..16]=chapters, items[17]=RETURN TO PILOTS
-
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
if (!splayer)
return;
const char *items[18];
- // then overrides unlocked chapters individually.
- // items[0] = title = TRS 60 ("^f02Chapters")
items[0] = splayer->getString(60);
if (!items[0] || !items[0][0])
items[0] = "^f02Chapters";
- // items[1..16] = chapters, using unlocked (TRS 41-56) or locked (TRS 61-76) strings
for (int i = 1; i <= 16; i++) {
bool unlocked = _chapterUnlocked[i - 1];
int trsIdx = unlocked ? (40 + i) : (60 + i);
@@ -1112,24 +945,17 @@ void InsaneRebel2::drawChapterSelectOverlay(byte *renderBitmap, int pitch, int w
items[i] = "";
}
- // items[17] = "RETURN TO PILOTS" = TRS 77
items[17] = splayer->getString(77);
if (!items[17] || !items[17][0])
items[17] = "^f01^c240RETURN TO PILOTS";
- // Render menu using shared renderer with left-aligned mode
drawMenuItems(renderBitmap, pitch, width, height, items, 17, _chapterSelection, true);
- // SKIP chunks in O_LEVEL.SAN use iactBits to show locked/unlocked preview variants.
drawPreviewBox(renderBitmap, pitch, width, height);
- // Draw score/info line at bottom
drawChapterInfoLine(renderBitmap, pitch, width, height);
}
-// Pilot/save selection before chapter selection.
-
-// Returns kLevelSelectPlay, kLevelSelectBack, or kLevelSelectQuit.
int InsaneRebel2::runLevelSelect() {
debugC(DEBUG_INSANE, "Entering pilot selection (FUN_00414A41), %d pilots loaded", _numPilots);
@@ -1140,7 +966,7 @@ int InsaneRebel2::runLevelSelect() {
resetMenuGamepadAxis();
_levelSelection = 0;
- _levelItemCount = _numPilots + 4; // N pilots + NEW/COPY/DELETE/MAIN MENU
+ _levelItemCount = _numPilots + 4;
_selectedLevel = 1;
_menuRepeatDelay = 0;
_gameState = kStatePilotSelect;
@@ -1151,7 +977,6 @@ int InsaneRebel2::runLevelSelect() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Pilot selection uses menu video as background (320x200 mode)
while (!_vm->shouldQuit()) {
_vm->_smushVideoShouldFinish = false;
@@ -1172,14 +997,12 @@ int InsaneRebel2::runLevelSelect() {
_menuSelectionConfirmed = false;
if (_pilotMenuMode == kPilotModeDifficulty) {
- // Store difficulty in the new pilot and save
if (_pilotEditIndex >= 0 && _pilotEditIndex < _numPilots) {
_pilots[_pilotEditIndex].difficulty = _difficultySelection;
_difficulty = _difficultySelection;
savePilots();
_activePilot = _pilotEditIndex;
- // Update chapter unlock state from pilot data
for (int i = 0; i < 16; i++) {
_chapterUnlocked[i] = _debugUnlockAll || (_pilots[_activePilot].damage[i] < 0xFF);
}
@@ -1193,7 +1016,6 @@ int InsaneRebel2::runLevelSelect() {
}
if (_pilotMenuMode == kPilotModeNameInput) {
- // Name was confirmed â now show difficulty submenu
if (_pilotEditIndex >= 0 && _pilotEditIndex < _numPilots) {
Common::strlcpy(_pilots[_pilotEditIndex].name, _pilotNameInput.c_str(),
sizeof(_pilots[_pilotEditIndex].name));
@@ -1235,11 +1057,9 @@ int InsaneRebel2::runLevelSelect() {
debugC(DEBUG_INSANE, "Pilot selection: %d (numPilots=%d)", _levelSelection, _numPilots);
if (_levelSelection < _numPilots) {
- // Existing pilot selected â activate and go to chapter select
_activePilot = _levelSelection;
_difficulty = _pilots[_activePilot].difficulty;
- // Update chapter unlock state from pilot data
for (int i = 0; i < 16; i++) {
_chapterUnlocked[i] = _debugUnlockAll || (_pilots[_activePilot].damage[i] < 0xFF);
}
@@ -1251,7 +1071,6 @@ int InsaneRebel2::runLevelSelect() {
return kLevelSelectPlay;
} else if (_levelSelection == _numPilots) {
- // ADD NEW PILOT â create slot, enter name input mode
int newIdx = createNewPilot();
if (newIdx >= 0) {
_pilotEditIndex = newIdx;
@@ -1285,7 +1104,6 @@ int InsaneRebel2::runLevelSelect() {
continue;
} else if (_levelSelection == _numPilots + 3) {
- // RETURN TO MAIN MENU
setVirtualKeyboardVisible(false);
_menuInputActive = false;
return kLevelSelectBack;
@@ -1298,20 +1116,14 @@ int InsaneRebel2::runLevelSelect() {
}
int InsaneRebel2::processLevelSelectInput() {
- // Process input for pilot selection and difficulty submenu.
- // Handles pilot list, pilot operation submenus, name input, and difficulty.
- // Returns: -1 = no action, 0+ = item selected
-
int result = -1;
- // Name input mode â keyboard goes to _pilotNameInput instead of menu nav
if (_pilotMenuMode == kPilotModeNameInput) {
while (!_menuEventQueue.empty()) {
Common::Event event = _menuEventQueue.pop();
if (event.type == Common::EVENT_KEYDOWN) {
if (event.kbd.keycode == Common::KEYCODE_RETURN ||
event.kbd.keycode == Common::KEYCODE_KP_ENTER) {
- // Confirm name â signal back to runLevelSelect()
if (_pilotNameInput.size() > 0) {
setVirtualKeyboardVisible(false);
_menuSelectionConfirmed = true;
@@ -1319,7 +1131,6 @@ int InsaneRebel2::processLevelSelectInput() {
debugC(DEBUG_INSANE, "PilotName: confirmed '%s'", _pilotNameInput.c_str());
}
} else if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
- // Synthetic custom back action - cancel name entry
if (_pilotEditIndex >= 0 && _pilotEditIndex < _numPilots) {
deletePilot(_pilotEditIndex);
}
@@ -1329,12 +1140,10 @@ int InsaneRebel2::processLevelSelectInput() {
updateMenuVirtualKeyboard();
debugC(DEBUG_INSANE, "PilotName: cancelled");
} else if (event.kbd.keycode == Common::KEYCODE_BACKSPACE) {
- // Backspace â remove last character
if (_pilotNameInput.size() > 0) {
_pilotNameInput.deleteLastChar();
}
} else {
- // Printable ASCII (0x20-0x7E), max 15 chars
char c = (char)event.kbd.ascii;
if (c >= 0x20 && c <= 0x7E &&
(int)_pilotNameInput.size() < kMaxPilotNameLen) {
@@ -1358,7 +1167,6 @@ int InsaneRebel2::processLevelSelectInput() {
return -1;
}
- // Normal menu navigation (pilot select or difficulty submenu)
bool isDifficultyMode = (_gameState == kStateDifficultySelect);
bool isPilotOperationMode =
(_pilotMenuMode == kPilotModeCopySelect || _pilotMenuMode == kPilotModeDeleteSelect);
@@ -1410,7 +1218,7 @@ int InsaneRebel2::processLevelSelectInput() {
_levelSelection = _numPilots + (wasCopyMode ? 1 : 2);
resetMenuGamepadAxis();
} else {
- result = _levelItemCount - 1; // Last item = MAIN MENU
+ result = _levelItemCount - 1;
}
break;
@@ -1470,8 +1278,6 @@ int InsaneRebel2::processLevelSelectInput() {
}
void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int width, int height) {
- // Pilot selection / difficulty submenu renderer
-
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
if (!splayer) {
debugC(DEBUG_INSANE, "drawLevelSelectOverlay: SmushPlayer not available for TRS strings!");
@@ -1479,7 +1285,6 @@ void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int wid
}
if (_gameState == kStateDifficultySelect) {
- // Difficulty submenu - LAB_00414ff6
const char *diffItems[7];
for (int i = 0; i < 7; i++) {
diffItems[i] = splayer->getString(110 + i);
@@ -1515,37 +1320,24 @@ void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int wid
return;
}
- // items[0] = title (TRS 20)
- // items[1..N] = saved pilots (formatted with ^f01^c005<name>^f00)
- // items[N+1] = TRS 21 (ADD NEW PILOT)
- // items[N+2] = TRS 22 (COPY PILOT)
- // items[N+3] = TRS 23 (DELETE PILOT)
- // items[N+4] = TRS 24 (RETURN TO MAIN MENU)
-
- // Build pilot name strings with font/color formatting
Common::String pilotNameStrs[kMaxPilots];
for (int i = 0; i < _numPilots; i++) {
if (_pilotMenuMode == kPilotModeNameInput && i == _pilotEditIndex) {
- // Show name being typed with cursor (underscore)
pilotNameStrs[i] = Common::String::format("^f01^c005%s_^f00", _pilotNameInput.c_str());
} else {
pilotNameStrs[i] = Common::String::format("^f01^c005%s^f00", _pilots[i].name);
}
}
- // Max items: 1 title + 10 pilots + 4 options = 15
const char *pilotItems[15];
int idx = 0;
- // Title: TRS 20
pilotItems[idx++] = splayer->getString(20);
- // Saved pilot slots
for (int i = 0; i < _numPilots; i++) {
pilotItems[idx++] = pilotNameStrs[i].c_str();
}
- // Fixed options: TRS 21-24
for (int i = 0; i < 4; i++) {
pilotItems[idx++] = splayer->getString(21 + i);
}
@@ -1559,19 +1351,11 @@ void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int wid
drawMenuItems(renderBitmap, pitch, width, height, pilotItems, _numPilots + 4, _levelSelection);
}
-// Ranked pilot scores with animated reveal over a menu background video.
-// +0x00 (4): timestamp, +0x04 (40): name, +0x36 (4): score,
-// +0x3a (4): rating, +0x3e (2): difficulty tier (1-3, TRS=value+0x9b),
-// +0x40 (2): highest chapter (1-15).
-// Column X positions (low-res): medals=43, name=88, diff=195, ch=245, score=295.
-
-// Generates 15 placeholder entries: score=(15-i)*1500, rating=(15-i)*2,
-// difficulty=((15-i)*3+14)/15, chapter=((15-i)*15+14)/15.
void InsaneRebel2::initDefaultRankings() {
_numRankings = 0;
memset(_rankings, 0, sizeof(_rankings));
for (int i = 0; i < kMaxRankings; i++) {
- int k = kMaxRankings - i; // 15 down to 1
+ int k = kMaxRankings - i;
RankingEntry &r = _rankings[_numRankings];
Common::strlcpy(r.name, "-----", sizeof(r.name));
r.score = k * 1500;
@@ -1587,7 +1371,6 @@ void InsaneRebel2::insertRanking(const char *name, int32 score, int32 rating,
if (score == 0)
return;
- // Find insertion point (first entry with score < new score)
int insertPos = 0;
while (insertPos < _numRankings && score <= _rankings[insertPos].score) {
insertPos++;
@@ -1595,12 +1378,10 @@ void InsaneRebel2::insertRanking(const char *name, int32 score, int32 rating,
if (insertPos > kMaxRankings - 1)
return;
- // Remove any existing entry with same name
for (int i = 0; i < _numRankings; i++) {
if (strcmp(_rankings[i].name, name) == 0) {
if (score <= _rankings[i].score)
- return; // Existing entry has higher score
- // Remove old entry by shifting
+ return;
for (int j = i; j < _numRankings - 1; j++)
_rankings[j] = _rankings[j + 1];
_numRankings--;
@@ -1610,12 +1391,10 @@ void InsaneRebel2::insertRanking(const char *name, int32 score, int32 rating,
}
}
- // Shift entries down to make room
int lastIdx = MIN(_numRankings, kMaxRankings - 1);
for (int i = lastIdx; i > insertPos; i--)
_rankings[i] = _rankings[i - 1];
- // Insert new entry
RankingEntry &r = _rankings[insertPos];
Common::strlcpy(r.name, name, sizeof(r.name));
r.score = score;
@@ -1676,10 +1455,8 @@ void InsaneRebel2::drawTopPilotsOverlay(byte *renderBitmap, int pitch, int width
const int scale = isHiRes() ? 2 : 1;
- // Title centered at X=152, Y=10 (TITLFONT)
drawMenuStringCentered(renderBitmap, "^f02Top Pilots", 152 * scale, 10 * scale);
- // Column headers at Y=30 (SMALFONT), positioned to match data columns
int headerY = 30 * scale;
int headerColor = 5;
drawMenuStringCentered(renderBitmap, "^f01Rank", 43 * scale, headerY, headerColor);
@@ -1688,7 +1465,6 @@ void InsaneRebel2::drawTopPilotsOverlay(byte *renderBitmap, int pitch, int width
drawMenuStringCentered(renderBitmap, "^f01Chapter", 245 * scale, headerY, headerColor);
drawMenuStringRight(renderBitmap, "^f01Score", 295 * scale, headerY, headerColor);
- // Animated reveal: show up to _topPilotsFrameCount entries
int showCount = MIN(_topPilotsFrameCount, _numRankings);
for (int row = 0; row < showCount; row++) {
@@ -1696,18 +1472,15 @@ void InsaneRebel2::drawTopPilotsOverlay(byte *renderBitmap, int pitch, int width
int rowY = (row * 10 + 42) * scale;
int color = 244; // 0xF4
- // Column 1: Rank medals at X=43, centered (font 0 = TALKFONT)
Common::String rankStr = getRankString(r.rating);
if (!rankStr.empty()) {
Common::String rankFmt = Common::String::format("^f00%s", rankStr.c_str());
drawMenuStringCentered(renderBitmap, rankFmt.c_str(), 43 * scale, rowY, color);
}
- // Column 2: Pilot name at X=88, left-aligned (font 1 = SMALFONT)
Common::String nameFmt = Common::String::format("^f01%s", r.name);
drawMenuString(renderBitmap, nameFmt.c_str(), 88 * scale, rowY, color);
- // Column 3: Difficulty at X=195, centered - TRS (difficulty + 155)
int trsIdx = CLIP((int)r.difficulty, 0, 5) + 155;
const char *diffStr = splayer->getString(trsIdx);
if (diffStr && diffStr[0]) {
@@ -1715,11 +1488,9 @@ void InsaneRebel2::drawTopPilotsOverlay(byte *renderBitmap, int pitch, int width
drawMenuStringCentered(renderBitmap, diffFmt.c_str(), 195 * scale, rowY, color);
}
- // Column 4: Highest chapter at X=245, centered
Common::String chFmt = Common::String::format("^f01%d", (int)r.chapter);
drawMenuStringCentered(renderBitmap, chFmt.c_str(), 245 * scale, rowY, color);
- // Column 5: Total score at X=295, right-aligned
Common::String scoreFmt = Common::String::format("^f01%ld", (long)r.score);
drawMenuStringRight(renderBitmap, scoreFmt.c_str(), 295 * scale, rowY, color);
}
@@ -1727,9 +1498,6 @@ void InsaneRebel2::drawTopPilotsOverlay(byte *renderBitmap, int pitch, int width
_topPilotsFrameCount++;
}
-// TRS IDs: Title=89, Music=90/91, SFX=92/93, Voices=94/95, Text=96/97,
-// Controls=98/99, Rapid Fire=100/101, Volume=103 "%hd%%", Back=107.
-
void InsaneRebel2::showOptionsMenu() {
debugC(DEBUG_INSANE, "Showing Options menu (FUN_00416787)");
@@ -1746,7 +1514,6 @@ void InsaneRebel2::showOptionsMenu() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Loop videos until user exits options (same pattern as runMainMenu)
while (!_vm->shouldQuit() && !_optionsExitRequested) {
_vm->_smushVideoShouldFinish = false;
@@ -1790,7 +1557,6 @@ int InsaneRebel2::processOptionsInput() {
return -1;
case Common::KEYCODE_RIGHT:
- // Volume slider: increase by 4
if (_optionsSelection == 6) {
_optVolumeLevel = MIN(127, _optVolumeLevel + 4);
setRebel2MixerVolume(_vm, _optVolumeLevel);
@@ -1799,33 +1565,32 @@ int InsaneRebel2::processOptionsInput() {
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER:
- // Toggle items 0-5, back item 7
switch (_optionsSelection) {
- case 0: // Music toggle
+ case 0:
_optMusicEnabled = !_optMusicEnabled;
_vm->_mixer->muteSoundType(Audio::Mixer::kMusicSoundType, !_optMusicEnabled);
break;
- case 1: // SFX toggle
+ case 1:
_optSfxEnabled = !_optSfxEnabled;
_vm->_mixer->muteSoundType(Audio::Mixer::kSFXSoundType, !_optSfxEnabled);
break;
- case 2: // Voices toggle
+ case 2:
_optVoicesEnabled = !_optVoicesEnabled;
_vm->_mixer->muteSoundType(Audio::Mixer::kSpeechSoundType, !_optVoicesEnabled);
break;
- case 3: // Text toggle
+ case 3:
_optTextEnabled = !ConfMan.getBool("subtitles");
ConfMan.setBool("subtitles", _optTextEnabled);
break;
- case 4: // Controls toggle
+ case 4:
_optControlsFlipped = !_optControlsFlipped;
break;
- case 5: // Rapid fire toggle
+ case 5:
_optRapidFire = !_optRapidFire;
break;
- case 6: // Volume (handled by left/right)
+ case 6:
break;
- case 7: // Back
+ case 7:
_optionsExitRequested = true;
_vm->_smushVideoShouldFinish = true;
return 7;
@@ -1843,7 +1608,6 @@ int InsaneRebel2::processOptionsInput() {
}
if (event.type == Common::EVENT_LBUTTONDOWN) {
- // Mouse click on items â match drawMenuItems Y positions
int mouseY = event.mouse.y;
const bool highRes = isHiRes();
const int baseY = highRes ? (_optionsItemCount * -5 + 0x5a) * 2 + 0x1c : _optionsItemCount * -5 + 0x68;
@@ -1854,7 +1618,6 @@ int InsaneRebel2::processOptionsInput() {
int itemY = baseY + i * itemSpacing;
if (mouseY >= itemY - itemHitTop && mouseY < itemY - itemHitTop + itemHitHeight) {
_optionsSelection = i;
- // Simulate enter for this item
Common::Event enterEvent;
enterEvent.type = Common::EVENT_KEYDOWN;
enterEvent.kbd.keycode = Common::KEYCODE_RETURN;
@@ -1874,46 +1637,36 @@ void InsaneRebel2::drawOptionsOverlay(byte *renderBitmap, int pitch, int width,
_optTextEnabled = ConfMan.getBool("subtitles");
- // TRS 89: title, 90/91: music, 92/93: sfx, 94/95: voices,
- // 96/97: text, 98/99: controls, 100/101: rapid fire, 103: volume, 107: back
- const char *items[10]; // title + up to 9 selectable
+ const char *items[10];
- // [0] Title
items[0] = splayer->getString(89);
if (!items[0] || !items[0][0])
items[0] = "^f02Game Options";
- // [1] Music On/Off
items[1] = splayer->getString(_optMusicEnabled ? 90 : 91);
if (!items[1] || !items[1][0])
items[1] = _optMusicEnabled ? "^f01^c005Music is On" : "^f01^c005Music is Off";
- // [2] SFX On/Off
items[2] = splayer->getString(_optSfxEnabled ? 92 : 93);
if (!items[2] || !items[2][0])
items[2] = _optSfxEnabled ? "^f01^c005SFX are On" : "^f01^c005SFX are Off";
- // [3] Voices On/Off
items[3] = splayer->getString(_optVoicesEnabled ? 94 : 95);
if (!items[3] || !items[3][0])
items[3] = _optVoicesEnabled ? "^f01^c005Voices are On" : "^f01^c005Voices are Off";
- // [4] Text On/Off
items[4] = splayer->getString(_optTextEnabled ? 96 : 97);
if (!items[4] || !items[4][0])
items[4] = _optTextEnabled ? "^f01^c005Text is On" : "^f01^c005Text is Off";
- // [5] Controls Normal/Flipped
items[5] = splayer->getString(_optControlsFlipped ? 99 : 98);
if (!items[5] || !items[5][0])
items[5] = _optControlsFlipped ? "^f01^c005Controls are Flipped" : "^f01^c005Controls are Normal";
- // [6] Rapid Fire On/Off
items[6] = splayer->getString(_optRapidFire ? 100 : 101);
if (!items[6] || !items[6][0])
items[6] = _optRapidFire ? "^f01^c005Rapid Fire On" : "^f01^c005Rapid Fire Off";
- // [7] Volume Level (slider) â TRS 103 = "^f01^c005Volume Level: %hd%%"
char volumeBuf[64];
const char *volFmt = splayer->getString(103);
if (volFmt && volFmt[0])
@@ -1922,7 +1675,6 @@ void InsaneRebel2::drawOptionsOverlay(byte *renderBitmap, int pitch, int width,
Common::sprintf_s(volumeBuf, "^f01^c005Volume Level: %hd%%", (short)(_optVolumeLevel * 100 / 127));
items[7] = volumeBuf;
- // [8] Back â TRS 107
items[8] = splayer->getString(107);
if (!items[8] || !items[8][0])
items[8] = "^f01^c240Return to Main Menu";
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 4bada20bbd6..324d58793a5 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -148,8 +148,6 @@ bool isRebel2MenuState(InsaneRebel2::GameState state) {
InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_vm = scumm;
- // Initialize parent class pointers to nullptr to avoid crash in ~Insane()
- // because Insane() default constructor leaves them uninitialized.
_smush_roadrashRip = nullptr;
_smush_roadrsh2Rip = nullptr;
_smush_roadrsh3Rip = nullptr;
@@ -164,12 +162,9 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
const bool highRes = isHiRes();
- // Rebel Assault 2: Load cockpit sprites NUT which contains crosshairs,
- // explosions, reticles, and warning cues.
_smush_iconsNut = new NutRenderer(_vm, highRes ? "SYSTM/CPITIMHI.NUT" : "SYSTM/CPITIMAG.NUT");
- _smush_icons2Nut = nullptr; // Not used for Rebel2
+ _smush_icons2Nut = nullptr;
- // Sprite 5 is 136x13 pixels - a wide, thin texture perfect for laser beams
_laserTexture.pixels = nullptr;
_laserTexture.width = 0;
_laserTexture.height = 0;
@@ -177,21 +172,12 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
initLaserTexture(_smush_iconsNut, 5);
}
- // Per-level tables are loaded later via IACT opcode 8 par4=1000
initEdgeTable(nullptr);
- // < 0: Edge highlights disabled (low-detail mode)
- // >= 0: Edge highlights enabled, >= 1: high-detail (secondary NUTs, widescreen)
- // Always use high detail.
_rebelDetailMode = 1;
_smush_cockpitNut = new NutRenderer(_vm, highRes ? "SYSTM/DIHIFONT.NUT" : "SYSTM/DISPFONT.NUT");
- // Load DIHIFONT.NUT for in-video messages/subtitles (Opcode 9)
_rebelMsgFont = makeRebel2Font(_vm, "SYSTM/DIHIFONT.NUT");
- // In low resolution mode, fonts are loaded as a linked list:
- // 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
_smush_talkfontNut = makeRebel2Font(_vm, highRes ? "SYSTM/TKHIFONT.NUT" : "SYSTM/TALKFONT.NUT");
_smush_smalfontNut = makeRebel2Font(_vm, highRes ? "SYSTM/SMHIFONT.NUT" : "SYSTM/SMALFONT.NUT");
_smush_titlefontNut = makeRebel2Font(_vm, highRes ? "SYSTM/TIHIFONT.NUT" : "SYSTM/TITLFONT.NUT");
@@ -203,13 +189,13 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
memset(_rebelEmbeddedCodec45Lookup, 0, sizeof(_rebelEmbeddedCodec45Lookup));
_enemies.clear();
- _rebelHandler = 0; // Not set yet - will be set by IACT opcode 6
- _rebelLevelType = 0; // Level type from Opcode 6 par3, determines HUD sprite variant
- _rebelStatusBarSprite = 0; // 0 = disabled, 5 or 53 = enabled (set by IACT opcode 6 par4==1)
- _introCursorPushed = false; // Cursor state tracking for intro sequences
+ _rebelHandler = 0;
+ _rebelLevelType = 0;
+ _rebelStatusBarSprite = 0;
+ _introCursorPushed = false;
_playerDamage = 0;
- _playerShield = 255; // Full shields by default (255)
+ _playerShield = 255;
_playerLives = 3;
_playerScore = 0;
_noDamage = false;
@@ -242,7 +228,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_rebelWaveState = 0;
_rebelPhaseState = 0;
- // Opcode 6 state variables
_rebelAutopilot = 0;
_rebelDamageLevel = 0;
_rebelFlightDir = 0;
@@ -271,7 +256,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
for (int i = 0; i < 10; ++i)
_rebelGaugeCleared[i] = false;
- _difficulty = 1; // Default to Medium.
+ _difficulty = 1;
_targetLockTimer = 0;
_speed = 12;
@@ -363,7 +348,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_explosions[i].counter = 0;
}
- // Initialize collision zone system (for Level 3 pilot ship obstacle avoidance)
_primaryZoneCount = 0;
_secondaryZoneCount = 0;
for (i = 0; i < kMaxCollisionZones; i++) {
@@ -372,8 +356,8 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
}
_corridorLeftX = 0;
_corridorTopY = 0;
- _corridorRightX = 0x1A8; // 424 â full game buffer width
- _corridorBottomY = 0x104; // 260 â full game buffer height
+ _corridorRightX = 0x1A8;
+ _corridorBottomY = 0x104;
_hitCooldown = 0;
for (i = 0; i < 2; i++) {
@@ -426,31 +410,29 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_rebelEmbeddedHud[i].valid = false;
}
- // Initialize Handler 8 ship system
_shipSprite = nullptr;
_shipSprite2 = nullptr;
_shipOverlay1 = nullptr;
_shipOverlay2 = nullptr;
_level2Background = nullptr;
_level2BackgroundLoaded = false;
- _shipPosX = 0xa0; // Start centered (160 in hex)
- _shipPosY = 0x28; // Start at vertical center (40)
+ _shipPosX = 0xa0;
+ _shipPosY = 0x28;
_shipTargetX = 0xa0;
_shipTargetY = 0x28;
_shipLevelMode = 0;
_movementRangeLimit = 127;
_flyControlMode = 0;
_shipFiring = false;
- _prevMouseButtons = 0; // For edge detection in mouse button handling
- _shipDirectionH = 2; // Start centered horizontally (0-4 range)
- _shipDirectionV = 3; // Start centered vertically (0-6 range)
- _shipDirectionIndex = 2 * 7 + 3; // Center = 17
-
- // Initialize Handler 7 FLY ship system
- _flyShipSprite = nullptr; // FLY001 - 35 direction frames
- _flyLaserSprite = nullptr; // FLY002 - effect sprites (danger/overlay cues)
- _flyTargetSprite = nullptr; // FLY003 - targeting overlay
- _flyHiResSprite = nullptr; // FLY004 - high-res alternative
+ _prevMouseButtons = 0;
+ _shipDirectionH = 2;
+ _shipDirectionV = 3;
+ _shipDirectionIndex = 2 * 7 + 3;
+
+ _flyShipSprite = nullptr;
+ _flyLaserSprite = nullptr;
+ _flyTargetSprite = nullptr;
+ _flyHiResSprite = nullptr;
_flyEffectAnimCounter = 0;
_flyOverlayRepeatCount = 0;
_flyShipScreenX = 0xd4;
@@ -467,7 +449,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_viewShift = 0;
_facingRight = false;
- // Initialize Handler 25 GRD ship system
_grd001Sprite = nullptr;
_grd002Sprite = nullptr;
_grd005Sprite = nullptr;
@@ -476,77 +457,61 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
memset(_grdShotOriginY, 0, sizeof(_grdShotOriginY));
_grdShotOriginTableLoaded = false;
- // Initialize Handler 0x26 turret HUD overlay system
_hudOverlayNut = nullptr;
_hudOverlay2Nut = nullptr;
- // Initialize audio system for RA2 (since we don't use iMUSE).
// Individual SMUSH audio blocks carry their own source rate.
initAudio(11025);
- // Initialize and load sound effects (SYSTM/*.SAD files)
for (i = 0; i < kRA2NumSfx; i++) {
_sfxData[i] = nullptr;
_sfxSize[i] = 0;
}
loadSfx();
- // Initialize auxiliary sound buffers (4 Ã 30000 bytes, loaded from IACT stream)
for (i = 0; i < kRA2NumAuxSfx; i++) {
_auxSfxData[i] = (byte *)calloc(kRA2AuxBufSize, 1);
_auxSfxSize[i] = 0;
}
- // Initialize menu system
- _gameState = kStateMainMenu; // Start at main menu
- _menuSelection = 0; // First item selected
- // Main menu has 7 selectable items (0-6) matching GAME.TRS indices 11-17:
- // 0: Start Game, 1: Options, 2: Calibrate Joystick, 3: Continue Intro,
- // 4: Show Top Pilots, 5: Show Credits, 6: Return to Launcher
+ _gameState = kStateMainMenu;
+ _menuSelection = 0;
_menuItemCount = 7;
_menuInactivityTimer = 0;
_menuInactivityTimedOut = false;
- _lastMenuVariant = -1; // No previous menu video
+ _lastMenuVariant = -1;
_menuRepeatDelay = 0;
_menuSelectionConfirmed = false;
for (i = 0; i < 16; i++) {
- _levelUnlocked[i] = (i == 0); // Only level 1 unlocked initially
+ _levelUnlocked[i] = (i == 0);
}
- // 17 items: 16 chapters + BACK option
- _chapterSelection = 0; // First chapter selected
- _chapterItemCount = 17; // 16 chapters + BACK
- _selectedChapter = 0; // Default selected chapter
- _passwordInput = ""; // No password input
+ _chapterSelection = 0;
+ _chapterItemCount = 17;
+ _selectedChapter = 0;
+ _passwordInput = "";
- // Debug flag to unlock all chapters for testing
- // Set to true to bypass normal unlock progression
_debugUnlockAll = ConfMan.getBool("rebel2_unlock_all");
_noDamage = ConfMan.getBool("rebel2_no_damage");
_rebelYodaMode = ConfMan.getBool("rebel2_yoda_mode");
for (i = 0; i < 16; i++) {
- // If debug unlock is enabled, unlock all chapters
- // Otherwise only chapter 1 (index 0) is unlocked initially
_chapterUnlocked[i] = _debugUnlockAll || (i == 0);
}
- // X offset: -90 (0xffa6), Y offset: selection * -50 + 75
_previewOffsetX = -90;
- _previewOffsetY = 75; // Chapter 0: 0 * -50 + 75 = 75
+ _previewOffsetY = 75;
- // Initialize pilot data system
_numPilots = 0;
_activePilot = 0;
for (i = 0; i < kMaxPilots; i++) {
_pilots[i].init();
}
- loadPilots(); // Load saved pilots from disk
+ loadPilots();
- // Menu structure: [saved pilots] + 4 fixed options (NEW/DUPE/DELETE/MAIN MENU)
- _levelSelection = 0; // First item selected
- _levelItemCount = _numPilots + 4; // N saved pilots + 4 fixed options
- _selectedLevel = 1; // Default selected level
+ _levelSelection = 0;
+ _levelItemCount = _numPilots + 4;
+ _selectedLevel = 1;
_difficultySelection = 2;
_pilotMenuMode = kPilotModeSelect;
_pilotNameInput = "";
@@ -569,13 +534,9 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_optRapidFire = false;
_optVolumeLevel = _vm->_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 2;
- // Initialize menu input capture system
_menuInputActive = false;
_virtualKeyboardActive = false;
- // Analog stick state for gamepad aiming (mirrors RA1's analog model).
- // Ingested from EVENT_CUSTOM_BACKEND_ACTION_AXIS, read with a deadzone and
- // integrated as velocity into the reticle in updateGameplayAimFromGamepad().
_joystickAxisX = 0;
_joystickAxisY = 0;
_gamepadAimActive = false;
@@ -592,12 +553,10 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_handler8HudMessageTimer = 0;
_handler8HudMessageIndex = 0;
- // Initialize level state tracking for multi-phase levels
_currentPhase = 1;
_deathFrame = 0;
_skipSectionRequested = false;
- // Register as EventObserver to capture input events before ScummEngine consumes them
_vm->_system->getEventManager()->getEventDispatcher()->registerObserver(this, 1, false);
}
@@ -606,7 +565,6 @@ InsaneRebel2::~InsaneRebel2() {
restoreIOSGamepadController();
setVirtualKeyboardVisible(false);
- // Unregister EventObserver
_vm->_system->getEventManager()->getEventDispatcher()->unregisterObserver(this);
terminateAudio();
@@ -617,7 +575,6 @@ InsaneRebel2::~InsaneRebel2() {
delete _smush_titlefontNut;
delete _smush_povfontNut;
- // Clean up Handler 8 ship sprites
delete _shipSprite;
delete _shipSprite2;
delete _shipOverlay1;
@@ -625,24 +582,20 @@ InsaneRebel2::~InsaneRebel2() {
free(_level2Background);
_level2Background = nullptr;
- // Clean up Handler 7 FLY ship sprites
delete _flyShipSprite;
delete _flyLaserSprite;
delete _flyTargetSprite;
delete _flyHiResSprite;
- // Clean up Handler 25 GRD ship sprites
delete _grd001Sprite;
delete _grd002Sprite;
delete _grd005Sprite;
- // Clean up Handler 0x26 turret HUD overlays
delete _hudOverlayNut;
delete _hudOverlay2Nut;
freeLaserTexture();
- // Clean up embedded HUD overlays
for (int i = 0; i < 16; i++) {
free(_rebelEmbeddedHud[i].pixels);
_rebelEmbeddedHud[i].pixels = nullptr;
@@ -792,8 +745,6 @@ bool InsaneRebel2::handleMenuRawJoystickAxisEvent(const Common::Event &event) {
return true;
}
-// notifyEvent -- EventObserver callback for global input dispatch.
-// Handles ESC (skip) and SPACE (pause) regardless of menu state.
bool InsaneRebel2::notifyEvent(const Common::Event &event) {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
@@ -807,7 +758,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
if (_rebelYodaMode && event.type == Common::EVENT_KEYDOWN && !event.kbdRepeat && event.kbd.hasFlags(Common::KBD_ALT)) {
switch (event.kbd.keycode) {
case Common::KEYCODE_m:
- // sections and keeps the story/cutscene sequence moving.
_rebelMovieMode = !_rebelMovieMode;
debugC(DEBUG_INSANE, "Movie mode %s", _rebelMovieMode ? "enabled" : "disabled");
if (_rebelMovieMode && splayer && _gameState == kStateGameplay && _rebelHandler != 0)
@@ -815,7 +765,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
return true;
case Common::KEYCODE_p:
- // computer controlled.
_rebelAutoPlay = !_rebelAutoPlay;
debugC(DEBUG_INSANE, "Auto play %s", _rebelAutoPlay ? "enabled" : "disabled");
return true;
@@ -865,26 +814,18 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
_gamepadAimActive ? 1 : 0, _menuInputActive ? 1 : 0);
}
- // Keep the gamepad reticle authoritative against stray pointer events. During
- // gameplay the cursor is locked ~screen-center (levels.cpp lockMouse), so any
- // spurious pointer event â e.g. a gamepad "fire" surfacing as a button-down â
- // carries that centered position, and SCUMM's input layer copies it straight
- // into _mouse on button-down (scumm/input.cpp), yanking the reticle to center on
- // every shot. We observe before ScummEngine (priority 1 > kEventManPriority), so
- // dropping the event here prevents the clobber. Firing is driven by the
- // kScummActionInsaneAttack action, not the pointer, so this does not affect shots.
- // A genuine mouse/touch motion (nonzero relative delta) hands control back.
+ // Suppress locked-cursor artifacts while the gamepad owns the reticle.
if (_gamepadAimActive && _gameState == kStateGameplay && !_menuInputActive) {
switch (event.type) {
case Common::EVENT_MOUSEMOVE:
if (event.relMouse.x != 0 || event.relMouse.y != 0) {
- _gamepadAimActive = false; // real pointer motion takes over
+ _gamepadAimActive = false;
break;
}
- return true; // drop zero-delta artifact (e.g. locked-cursor recentre)
+ return true;
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_RBUTTONDOWN:
- return true; // drop stray button-down that would recenter the reticle
+ return true;
// Let LBUTTONUP/RBUTTONUP fall through so the backend _buttonState latch
// always clears and can never stick when a real mouse later takes over.
default:
@@ -897,10 +838,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
return true;
}
- // Analog stick â reticle velocity (mirrors RA1's analog model). The mapped
- // axis actions carry a signed position; a centered stick reports position 0.
- // We ignore a recentre that would fight the opposite direction so a quick
- // flick doesn't get cancelled by the trailing zero of the other axis half.
if (event.type == Common::EVENT_CUSTOM_BACKEND_ACTION_AXIS) {
if (_menuInputActive && handleMenuGamepadAxisEvent(event))
return true;
@@ -1068,12 +1005,7 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
}
}
- // Do NOT consume Attack/Switch during gameplay: like the dpad actions, these
- // custom-action events must fall through to ScummEngine::parseEvent() so it keeps
- // _actionMap in sync (input.cpp). processMouse() reads getActionState() for the
- // gamepad trigger; consuming here breaks the dispatch loop (events.cpp) before the
- // map is updated, leaving fire dead. The menu/paused branches above already
- // consumed (and returned true for) the cases they handle.
+ // Gameplay Attack/Switch actions must still reach ScummEngine::parseEvent().
}
if (_menuInputActive && isRebel2MenuState(_gameState) &&
@@ -1112,8 +1044,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
}
if (event.type == Common::EVENT_KEYDOWN) {
- // Gamepad buttons mapped to ESC can lose their key-up while the GMM is open,
- // leaving the key repeat source to synthesize another ESC immediately after
if (event.kbd.keycode == Common::KEYCODE_ESCAPE && event.kbdRepeat) {
debugC(DEBUG_INSANE, "Ignoring repeated ESC keydown");
return true;
@@ -1143,26 +1073,19 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
switch (event.kbd.keycode) {
case Common::KEYCODE_ESCAPE:
- // ESC handling depends on game state:
- // - In menus: Open the global menu (handled above)
- // - During gameplay: Pause and open the global menu
- // - During cutscenes/intros: Skip video
if (splayer) {
if (_gameState == kStateGameplay && _rebelHandler != 0) {
- // During active gameplay (handler != 0): pause and open the global menu.
debugC(DEBUG_INSANE, "ESC pressed during gameplay - opening global menu");
openGameplayMainMenu(splayer);
} else {
- // During cutscenes/intros/mission briefings: skip video
debugC(DEBUG_INSANE, "ESC pressed - skipping video");
_vm->_smushVideoShouldFinish = true;
}
- return true; // Consume the event
+ return true;
}
break;
case Common::KEYCODE_SPACE:
- // Unpausing is handled above (any key while paused).
if (splayer && _gameState == kStateGameplay && !splayer->_paused) {
debugC(DEBUG_INSANE, "SPACE pressed - pausing");
splayer->pause();
@@ -1172,7 +1095,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
break;
case Common::KEYCODE_s:
- // Debug shortcut: Shift+S skips the current gameplay section.
if (splayer &&
_gameState == kStateGameplay &&
_rebelHandler != 0 &&
@@ -1180,12 +1102,11 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
_skipSectionRequested = true;
debugC(DEBUG_INSANE, "Shift+S pressed - requesting gameplay section skip");
_vm->_smushVideoShouldFinish = true;
- return true; // Consume the event
+ return true;
}
break;
case Common::KEYCODE_d:
- // Debug shortcut: Shift+D forces the current gameplay section to end in death.
if (splayer &&
_gameState == kStateGameplay &&
_rebelHandler != 0 &&
@@ -1198,7 +1119,7 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
} else {
debugC(DEBUG_INSANE, "Shift+D pressed - no damage mode prevents forced death");
}
- return true; // Consume the event
+ return true;
}
break;
@@ -1207,18 +1128,14 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
}
}
- // Capture menu-related input events when menu input is active.
- // This is called before ScummEngine::parseEvents() consumes events,
- // so we can reliably capture keyboard/mouse input for menu navigation.
if (!_menuInputActive)
- return false; // Not capturing, let normal processing occur
+ return false;
switch (event.type) {
case Common::EVENT_KEYDOWN:
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_QUIT:
case Common::EVENT_RETURN_TO_LAUNCHER:
- // Queue these events for processing in processMenuInput()
_menuEventQueue.push(event);
break;
case Common::EVENT_MOUSEMOVE:
@@ -1230,8 +1147,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
break;
}
- // Return false to allow ScummEngine to also process the event
- // (needed for quit handling, etc.)
return false;
}
@@ -1340,7 +1255,7 @@ const InsaneRebel2::LevelDifficultyParams InsaneRebel2::kDifficultyTable[6][17]
{ 5, 4, -1, -1, 7, -1, 75, 75, -1, 6, 1500, -1, 0, -1, -1, -1, -1}, // Lv15A
{ 5, 5, 255, 40, 7, 10, 75, 75, 150, 6, 1500, 750, 0, -1, -1, -1, -1}, // Lv15B
},
- // Difficulty 5 (Custom2) â same as Custom1 except Lv15B roll/lift/slide/drift are 0
+ // Difficulty 5 (Custom2) matches Custom1 except Lv15B movement rates are 0.
{
{ 7, 0, 35, -1, 5, -1, 75, 75, -1, 6, 1500, 750, 0, 7, 7, 8, -1}, // Lv1
{ 4, 1, -1, -1, 6, -1, 40, 75, -1, 0, 1500, 750, 0, 110, 110, 150, 35}, // Lv2
@@ -1362,19 +1277,16 @@ const InsaneRebel2::LevelDifficultyParams InsaneRebel2::kDifficultyTable[6][17]
},
};
-// getDifficultyParams -- Look up difficulty parameters for current level.
InsaneRebel2::LevelDifficultyParams InsaneRebel2::getDifficultyParams() const {
int diff = CLIP(_difficulty, 0, 5);
int lvIdx = 0;
- // This is NOT the same as handler 0x26's gun "levelType" from opcode 6.
// Index mapping reconstructed from level handlers:
// Lv1->0, Lv2->1, Lv3->2, Lv4->3, Lv5->4,
// Lv6A->5, Lv6B->6, Lv7->7 ... Lv14->14, Lv15A->15, Lv15B->16.
// Our Level 6 phase flow sets _currentPhase to 1/2 accordingly.
// Level 15 phase switch to 16 is currently approximated by _currentPhase >= 2.
if (_selectedLevel <= 0) {
- // Fallback during non-gameplay contexts before level selection is initialized.
lvIdx = CLIP((int)_rebelLevelType, 0, 16);
} else if (_selectedLevel <= 5) {
lvIdx = _selectedLevel - 1;
@@ -1402,34 +1314,27 @@ bool InsaneRebel2::applyPlayerDamage(int damage) {
}
void InsaneRebel2::addScore(int points) {
- // if (difficulty < 4) threshold = (difficulty * 5 + 5) * 1000
- // else threshold = 15000
int threshold;
if (_difficulty < 4) {
- threshold = (_difficulty * 5 + 5) * 1000; // 5000, 10000, 15000, 20000
+ threshold = (_difficulty * 5 + 5) * 1000;
} else {
threshold = 15000;
}
- // Check if we're crossing a threshold (award bonus life)
- // Formula: score / threshold < (score + points) / threshold
if (threshold > 0) {
int oldMilestone = _playerScore / threshold;
int newMilestone = (_playerScore + points) / threshold;
if (oldMilestone < newMilestone) {
- // Award bonus life
_playerLives++;
debugC(DEBUG_INSANE, "BONUS LIFE! Score crossed %d threshold. Lives=%d", threshold, _playerLives);
}
}
- // Add points to score
_playerScore += points;
debugC(DEBUG_INSANE, "Score +%d = %d", points, _playerScore);
}
void InsaneRebel2::renderScoreHUD(byte *renderBitmap, int pitch, int width, int height, int statusBarY) {
- // In low-res mode, score text shares the same NUT as the status bar sprites.
NutRenderer *statusFont = _smush_cockpitNut;
if (!statusFont)
return;
@@ -1437,15 +1342,10 @@ void InsaneRebel2::renderScoreHUD(byte *renderBitmap, int pitch, int width, int
char scoreStr[16];
Common::sprintf_s(scoreStr, "%07d", _playerScore);
- // X = 0x101 low-res, 0x202 high-res
- // Y = 4 low-res, 8 high-res (within status bar)
const int statusScale = isHiRes() ? 2 : 1;
int scoreX = 257 * statusScale + _viewX;
int scoreY = statusBarY + 4 * statusScale + _viewY;
- // Render each digit as a NUT sprite (direct pixel blit with color 0 transparency).
- // uses the NUT font's embedded palette colors (1=white, 3=gray, 4=black outline).
- // Render each character applying xoffs/yoffs from NUT frame headers,
int x = scoreX;
for (int i = 0; scoreStr[i] != '\0'; i++) {
byte ch = (byte)scoreStr[i];
@@ -1458,9 +1358,6 @@ void InsaneRebel2::renderScoreHUD(byte *renderBitmap, int pitch, int width, int
}
}
-// Pilot Data System
-// Save/load pilot profiles using the save file system.
-
const uint32 kPilotSaveMagic = MKTAG('R', 'A', '2', 'P');
const uint16 kPilotSaveVersion = 2;
@@ -1535,7 +1432,6 @@ bool InsaneRebel2::savePilots() {
delete sf;
}
- // Remove leftover files beyond current count
for (int i = _numPilots; i < kMaxPilots; i++) {
Common::String filename = _vm->makeSavegameName(i, false);
_vm->_saveFileMan->removeSavefile(filename);
@@ -1564,24 +1460,20 @@ void InsaneRebel2::deletePilot(int index) {
}
_numPilots--;
- // Clear the now-unused last slot
_pilots[_numPilots].init();
}
void InsaneRebel2::copyPilot(int srcIndex) {
- // Copy pilot from srcIndex to a new slot
if (srcIndex < 0 || srcIndex >= _numPilots || _numPilots >= kMaxPilots)
return;
int newIdx = _numPilots;
_pilots[newIdx] = _pilots[srcIndex];
- // Append " COPY" or truncate name to fit
Common::String name(_pilots[newIdx].name);
if (name.size() + 5 <= kMaxPilotNameLen) {
name += " COPY";
} else if (name.size() > 0) {
- // Truncate and add marker
name = name.substr(0, kMaxPilotNameLen - 2) + " C";
}
Common::strlcpy(_pilots[newIdx].name, name.c_str(), sizeof(_pilots[newIdx].name));
@@ -1600,10 +1492,8 @@ void InsaneRebel2::updatePilotProgress(int levelIndex, int32 score, int32 lives,
pilot.lives[levelIndex] = lives;
pilot.damage[levelIndex] = damage;
- // Unlock next level if this one completed with damage < 0xFF
if (damage < 0xFF && levelIndex + 1 < kNumLevels) {
if (pilot.damage[levelIndex + 1] == 0xFF) {
- // Initialize next level as playable
pilot.score[levelIndex + 1] = 0;
pilot.lives[levelIndex + 1] = 4;
pilot.damage[levelIndex + 1] = 0;
@@ -1613,41 +1503,30 @@ void InsaneRebel2::updatePilotProgress(int levelIndex, int32 score, int32 lives,
savePilots();
}
-// processMouse -- Mouse input with edge detection for buttons.
int32 InsaneRebel2::processMouse() {
int32 buttons = 0;
- // Get button state directly from event manager (SCUMM VARs aren't updated during SMUSH)
- // Bit 0 = left button, Bit 1 = right button, Bit 2 = middle button
uint32 currentButtons = _vm->_system->getEventManager()->getButtonState();
if (_vm->getActionState(kScummActionInsaneAttack))
currentButtons |= 1;
if (_vm->getActionState(kScummActionInsaneSwitch))
currentButtons |= 2;
- // Edge detection for buttons
bool leftPressed = (currentButtons & 1) != 0;
bool leftWasPressed = (_prevMouseButtons & 1) != 0;
bool rightPressed = (currentButtons & 2) != 0;
bool rightWasPressed = (_prevMouseButtons & 2) != 0;
- // Store current state for next frame's edge detection
_prevMouseButtons = currentButtons;
- // Use "sticky" flags - set on button press, cleared by IACT handler after consumption.
- // This ensures button presses aren't missed due to timing.
- // For Handler 25: use edge detection with sticky flags
if (_rebelHandler == 25) {
- // Only SET flags on button press (edge), don't clear them here
- // The IACT handler will clear them after processing
if (rightPressed && !rightWasPressed) {
- _rebelControlMode |= 2; // Right button pressed - sticky
+ _rebelControlMode |= 2;
}
if (leftPressed && !leftWasPressed) {
- _rebelControlMode |= 1; // Left button pressed - sticky
+ _rebelControlMode |= 1;
}
} else {
- // Other handlers: use simple hold state
_rebelControlMode = 0;
if (rightPressed) {
_rebelControlMode |= 2;
@@ -1657,9 +1536,6 @@ int32 InsaneRebel2::processMouse() {
}
}
- // Shot trigger behavior:
- // - Handler 25 keeps edge-triggered clicks due cover-toggle/sticky input semantics.
- // - Other gameplay handlers fire while button is held; slot counters still rate-limit.
bool canShoot = isShootingAllowed();
bool autoFire = _rebelAutoPlay && canShoot && _gameState == kStateGameplay && _rebelHandler != 0;
if (autoFire && _player) {
@@ -1671,8 +1547,6 @@ int32 InsaneRebel2::processMouse() {
bool triggerShot = ((_rebelHandler == 25) ? (leftPressed && !leftWasPressed) : leftPressed) || autoFire;
if (_rebelHandler == 8) {
- // to choose the POV gun sprite. Keep the sprite driven by the event-manager
- // input that processMouse() uses during SMUSH playback.
_shipFiring = triggerShot && canShoot;
}
if (triggerShot && canShoot) {
@@ -1690,10 +1564,8 @@ int32 InsaneRebel2::processMouse() {
debugC(DEBUG_INSANE, "Click: Mouse=(%d,%d) Target=(%d,%d) Enemies=%d",
gameplayAim.x, gameplayAim.y, mousePos.x, mousePos.y, _enemies.size());
- // Spawn visual shot immediately
spawnShot(mousePos.x, mousePos.y);
- // Calculate world position for hit testing
Common::Point worldMousePos = mousePos;
if (_rebelHandler == 8) {
worldMousePos.x += _shipPosX;
@@ -1703,7 +1575,6 @@ int32 InsaneRebel2::processMouse() {
worldMousePos.y += _viewY;
}
- // Check for hit on any active enemy
Common::List<enemy>::iterator it;
for (it = _enemies.begin(); it != _enemies.end(); ++it) {
debugC(DEBUG_INSANE, " Enemy ID=%d active=%d destroyed=%d rect=(%d,%d)-(%d,%d) contains=%d",
@@ -1712,15 +1583,12 @@ int32 InsaneRebel2::processMouse() {
it->rect.contains(worldMousePos));
if (it->active && it->rect.contains(worldMousePos)) {
- // Enemy hit!
it->active = false;
- it->destroyed = true; // Mark as destroyed so IACT won't re-activate
+ it->destroyed = true;
debugC(DEBUG_INSANE, "HIT enemy ID=%d type=%d at (%d,%d) - Rect: (%d,%d)-(%d,%d)",
it->id, it->type, mousePos.x, mousePos.y,
it->rect.left, it->rect.top, it->rect.right, it->rect.bottom);
- // - H8/H7/H26 use object half-width
- // - H25 uses half-width + snapDistance (and type 100 doubles it)
int explosionHalfWidth = it->rect.width() / 2;
if (_rebelHandler == 25) {
LevelDifficultyParams dparams = getDifficultyParams();
@@ -1729,10 +1597,6 @@ int32 InsaneRebel2::processMouse() {
explosionHalfWidth *= 2;
}
- // explosion NUT sprites are suppressed. This is checked
- // during rendering in renderExplosions().
- // rendering suppressed by flags bit 0.
- // Handlers 0x26, 7: All types get visual explosions.
if (_rebelHandler != 8 && _rebelHandler != 25) {
spawnExplosion((it->rect.left + it->rect.right) / 2,
(it->rect.top + it->rect.bottom) / 2,
@@ -1742,14 +1606,11 @@ int32 InsaneRebel2::processMouse() {
(it->rect.top + it->rect.bottom) / 2,
explosionHalfWidth);
} else if (_rebelHandler == 25 && it->type > 3) {
- // Counter is set for timing/sound, but rendering
- // may be suppressed by flags bit 0
spawnExplosion((it->rect.left + it->rect.right) / 2,
(it->rect.top + it->rect.bottom) / 2,
explosionHalfWidth);
}
- // Disable self (prevents sprite from rendering via SKIP chunks)
setBit(it->id);
// Shield hit-point gauge: if this target feeds a gauge counter, destroying it
@@ -1763,19 +1624,16 @@ int32 InsaneRebel2::processMouse() {
_rebelLastCounter = *counter;
if (*counter == 0) {
if (slot < 10)
- _rebelGaugeCleared[slot] = true; // group emptied â its par3==2 surface can drop
- // Level 6 ends on any group emptying; Level 13's reactor is gated in
- // procPostRendering instead, so early obstacle groups don't complete it.
+ _rebelGaugeCleared[slot] = true;
if (!_rebelReactorMode) {
_rebelShieldDestroyed = true;
debugC(DEBUG_INSANE, "Shield destroyed (gauge slot %d depleted by target %d)", slot, it->id);
}
}
}
- _rebelGaugeSlot[it->id] = -1; // consume: avoid double-counting
+ _rebelGaugeSlot[it->id] = -1;
}
- // This tracks which enemy GROUPS have been killed in this wave
if (it->type > 0 && it->type < 32) {
_rebelWaveState |= (1 << it->type);
debugC(DEBUG_INSANE, "Wave state updated: 0x%x (set bit %d)", _rebelWaveState, it->type);
@@ -1783,40 +1641,30 @@ int32 InsaneRebel2::processMouse() {
_rebelKillCounter++;
- // Handle dependencies.
- // - type <= 3: process dependency tables
- // - type > 3 (and 100): skip dependency handling
- // Other handlers always use link-table side effects.
bool handleDependencies = !(_rebelHandler == 25 && it->type > 3);
int id = it->id;
if (handleDependencies && id >= 0 && id < 512) {
- // Slot 2: Enable (Explosion?)
if (_rebelLinks[id][2] != 0) {
clearBit(_rebelLinks[id][2]);
debugC(DEBUG_INSANE, "Enabled dependency Slot 2 (ID=%d) for Parent %d", _rebelLinks[id][2], id);
}
- // Slot 1: Enable (Explosion?)
if (_rebelLinks[id][1] != 0) {
clearBit(_rebelLinks[id][1]);
debugC(DEBUG_INSANE, "Enabled dependency Slot 1 (ID=%d) for Parent %d", _rebelLinks[id][1], id);
}
- // Slot 0: Disable (Shots?)
if (_rebelLinks[id][0] != 0) {
setBit(_rebelLinks[id][0]);
debugC(DEBUG_INSANE, "Disabled dependency Slot 0 (ID=%d) for Parent %d", _rebelLinks[id][0], id);
}
}
- // Play enemy death sound.
{
int cameraX = (_rebelHandler == 8) ? _shipPosX : _viewX;
int enemyCenterX = (it->rect.left + it->rect.right) / 2 - cameraX;
int sfxPan = CLIP((enemyCenterX - 160) * 127 / 160, -127, 127);
if (_rebelHandler == 8 && it->type >= 1 && it->type <= 4) {
- // Handler 8 soldier types 1-4: play from auxiliary buffer 0
playAuxSfx(0, 127, sfxPan);
} else {
- // All other enemies: EXPLODE.SAD
playSfx(2, 127, sfxPan);
}
}
@@ -1828,7 +1676,6 @@ int32 InsaneRebel2::processMouse() {
}
}
- // Only hit one enemy per click
break;
}
}
@@ -1871,9 +1718,6 @@ Common::Point InsaneRebel2::getRebelAutoPlayAimPoint() {
}
Common::Point InsaneRebel2::getGameplayAimPoint() {
- // Pure getter (queried many times per frame): the aim/reticle follows the virtual
- // mouse position. Directional controls pan that position incrementally once per frame
- // via updateGameplayAimFromGamepad(), rather than snapping the reticle to a screen edge.
if (_rebelAutoPlay && _gameState == kStateGameplay && !_menuInputActive)
return getRebelAutoPlayAimPoint();
@@ -1891,8 +1735,6 @@ Common::Point InsaneRebel2::getGameplayAimPoint() {
return Common::Point(x, y);
}
-// Apply the user's configured analog deadzone so a resting stick reports no
-// motion. Mirrors RA1's applyRebel1AnalogDeadzone (iact.cpp).
int16 applyRebel2AnalogDeadzone(int16 axisValue) {
const int deadZone = MAX(0, ConfMan.getInt("joystick_deadzone")) * 1000;
return (ABS((int)axisValue) <= deadZone) ? 0 : axisValue;
@@ -1946,11 +1788,6 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
centerY + axisY * centerY / 127 :
centerY + axisY * (199 - centerY) / 127;
} else {
- // The wider Level 10 mapping makes other Handler 7 stages swing the
- // camera too hard. Keep their gamepad cursor in the older bounded
- // range and move it there gradually. Level 3 mode 1 is wall-collision
- // tunnel flight, so neutral stick axes must feed zero input instead of
- // leaving stale off-center mouse coordinates to keep pushing the ship.
const int kHandler7HorizontalRange = 120;
const int kHandler7VerticalRange = 80;
targetX = axisX ?
@@ -1980,8 +1817,6 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
}
}
} else {
- // Handler 0x26 and the remaining gamepad-driven aim modes use relative
- // reticle panning: releasing the stick leaves the aim point in place.
if (dpadX || dpadY) {
const int kOriginalDigitalStep = 3;
deltaX = dpadX * kOriginalDigitalStep;
@@ -1998,11 +1833,8 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
if (!activeGamepadAim)
return;
- // The gamepad is now driving the reticle: mark it the active aim source so
- // notifyEvent() suppresses stray pointer events that would recenter it on fire.
_gamepadAimActive = true;
- // Integrate velocity into the reticle, clamped to the 320x200 play area.
Common::Point aimPos = getGameplayAimPoint();
const int scale = isHiRes() ? 2 : 1;
_vm->_mouse.x = (int16)(CLIP<int>(aimPos.x + deltaX, 0, 319) * scale);
@@ -2010,7 +1842,6 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
}
bool InsaneRebel2::isBitSet(int n) {
- // This means ID 0 or negative IDs are always treated as "enabled" (not skipped).
if (n < 1) {
return false;
}
@@ -2020,7 +1851,6 @@ bool InsaneRebel2::isBitSet(int n) {
}
void InsaneRebel2::setBit(int n) {
- // This is used to disable all enemies/objects at once
if (n < 1) {
for (int i = 0; i < 0x401; i++)
_iactBits[i] = 1;
@@ -2031,7 +1861,6 @@ void InsaneRebel2::setBit(int n) {
}
void InsaneRebel2::clearBit(int n) {
- // ensuring all enemies are visible when a new level/segment starts.
if (n < 1) {
for (int i = 0; i < 0x401; i++)
_iactBits[i] = 0;
@@ -2041,32 +1870,22 @@ void InsaneRebel2::clearBit(int n) {
_iactBits[n] = 0;
}
-// isShootingAllowed -- Check control mode before spawning shots.
-// Handler 7: only mode 2. Handler 8: not mode 4/5. Handler 25: not damaged.
bool InsaneRebel2::isShootingAllowed() {
- // Handler 7 (Third-Person Ship): Only mode 2 allows shooting
if (_rebelHandler == 7) {
return (_flyControlMode == 2);
}
- // Handler 8 (Third-Person On Foot): Modes 4/5 disable shooting
- // Mode 5: Ship not rendered (cutscene)
if (_rebelHandler == 8) {
return (_shipLevelMode != 4 && _shipLevelMode != 5);
}
- // Handler 25 (0x19): Only allow shooting when fully uncovered
if (_rebelHandler == 25) {
return (_rebelDamageLevel == 0);
}
- // Handler 0x26 (Turret): Always allow shooting when active
return (_rebelHandler != 0);
}
-// Same mechanism as Full Throttle, but RA2 uses it for enemy objects:
-// when setBit(enemy_id) is called on destruction, SKIP chunks containing
-// that ID cause the next FOBJ (enemy sprite) to be skipped.
void InsaneRebel2::procSKIP(int32 subSize, Common::SeekableReadStream &b) {
int16 par1, par2;
@@ -2077,14 +1896,12 @@ void InsaneRebel2::procSKIP(int32 subSize, Common::SeekableReadStream &b) {
par2 = b.readUint16LE();
if (!par2) {
- // Single ID mode: skip next chunk if this object's bit is set (disabled)
bool bit1 = isBitSet(par1);
if (bit1) {
_player->_skipNext = true;
}
debugC(DEBUG_INSANE, "SKIP: single ID=%d bit=%d skip=%d frame=%d", par1, bit1 ? 1 : 0, _player->_skipNext ? 1 : 0, _player->_frame);
} else {
- // Dual ID mode: skip if bits are different (XOR logic)
bool bit1 = isBitSet(par1);
bool bit2 = isBitSet(par2);
if (bit1 != bit2) {
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 41b1749aa5b..ca3a8d0767e 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -420,7 +420,6 @@ public:
int32 setupsan13, Common::SeekableReadStream &b, int32 size, int32 flags,
int16 par1, int16 par2, int16 par3, int16 par4) override;
- // Rendering helpers.
void renderStatusBarBackground(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY);
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index a153af2631b..c3ec65bd310 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -37,12 +37,10 @@
namespace Scumm {
-// External codec functions from codec1.cpp
extern void smushDecodeRLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
extern void smushDecodeUncompressed(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
int getRebel2IndicatorScale(int width, int height) {
- // RA2's 424x260 low-res gameplay buffer is still displayed through a 320x200
return (width >= 640 || height >= 400) ? 2 : 1;
}
@@ -179,18 +177,11 @@ static bool readEmbeddedSanChunkHeader(Common::SeekableReadStream &stream, int64
return true;
}
-// renderEmbeddedFrame -- Blit a decoded embedded frame to the video buffer.
void InsaneRebel2::renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFrame &frame, int userId) {
- // Render the decoded embedded frame to the video buffer
- // Skip immediate draw for handlers that render HUD during post-processing:
- // Exception: Handler 25 (0x19) background overlays (par4/userId=4, 6, 7) should draw immediately.
- // These complete the visual scene and are NOT positioned by mouse/crosshair.
+ // Some handler 25 overlays are static scene layers and draw immediately.
bool skipImmediateDraw = (_rebelHandler == 7 || _rebelHandler == 8 ||
_rebelHandler == 0x26);
- // Handler 25 overlays:
- // - userId 4 (corridor overlay): draw immediately at the current view offset.
- // - userId 6, 7 (static overlays): Draw immediately (they don't move)
if (_rebelHandler == 0x19 && userId == 4) {
drawHandler25CorridorOverlay(renderBitmap);
return;
@@ -266,11 +257,7 @@ void InsaneRebel2::drawHandler25CorridorOverlay(byte *renderBitmap) {
_rebelViewOffsetX, _rebelViewOffsetY, corridorOverlay.width, corridorOverlay.height);
}
-// loadEmbeddedSan -- Decode an embedded SAN (ANIM/FOBJ) from IACT opcode 8 data.
-// Parses ANIM container, extracts FOBJ codec data, decodes using codec 21/23/45,
-// and stores the result in _rebelEmbeddedHud[userId] for later rendering.
void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte *renderBitmap) {
- // Validate userId - Level 3 uses slots 0-11, allow up to 15 for safety
if (userId < 0 || userId > 15 || !animData || size < 8) {
debugC(DEBUG_INSANE, "Invalid embedded SAN: userId=%d, size=%d", userId, size);
return;
@@ -279,7 +266,6 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
Common::MemoryReadStream stream(animData, size);
const int64 streamEnd = stream.size();
- // Read ANIM header
uint32 animTag = stream.readUint32BE();
if (animTag != MKTAG('A','N','I','M')) {
debugC(DEBUG_INSANE, "Embedded SAN missing ANIM tag, got 0x%08X", animTag);
@@ -294,7 +280,6 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
}
debugC(DEBUG_INSANE, "Parsing embedded ANIM: userId=%d, reported size=%u, actual=%lld", userId, animSize, streamEnd - 8);
- // Iterate through chunks to find FRME -> FOBJ
while (!stream.eos() && stream.pos() + 8 <= animEnd) {
uint32 tag;
uint32 chunkSize;
@@ -304,7 +289,6 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
break;
if (tag == MKTAG('F','R','M','E')) {
- // Iterate sub-chunks in FRME
while (stream.pos() + 8 <= chunkDataEnd && !stream.eos()) {
uint32 subTag;
uint32 subSize;
@@ -320,7 +304,6 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
continue;
}
- // Read FOBJ header
int codec = stream.readUint16LE();
int left = stream.readSint16LE();
int top = stream.readSint16LE();
@@ -332,8 +315,6 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
debugC(DEBUG_INSANE, "Embedded HUD frame: userId=%d, %dx%d at (%d,%d), codec=%d",
userId, width, height, left, top, codec);
- // High-resolution HUD frames are used when the RA2 high-res option
- // selects a 640x400 virtual screen. Keep skipping them in low-res mode.
if (!isHiRes() && (width > 400 || height > 250)) {
debugC(DEBUG_INSANE, "SKIPPING high-res embedded frame: userId=%d, %dx%d (exceeds 400x250)",
userId, width, height);
@@ -341,7 +322,6 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
continue;
}
- // Allocate storage for the decoded frame
EmbeddedSanFrame &frame = _rebelEmbeddedHud[userId];
frame.valid = false;
@@ -356,14 +336,11 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
frame.width = width;
frame.height = height;
}
- // Clear buffer before decode (important for delta codecs)
memset(frame.pixels, 0, width * height);
- // Update render position from FOBJ header
frame.renderX = left;
frame.renderY = top;
- // Read the raw FOBJ data
int32 dataSize = (int32)(subDataEnd - stream.pos());
if (dataSize > 0) {
byte *fobjData = (byte *)malloc(dataSize);
@@ -403,30 +380,25 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
frame.valid = false;
}
- // Count non-zero pixels to verify frame has content
if (frame.valid) {
int nonZeroPixels = countEmbeddedFramePixels(frame);
debugC(DEBUG_INSANE, "Frame userId=%d has %d non-zero pixels (%d%%)",
userId, nonZeroPixels, (nonZeroPixels * 100) / (width * height));
}
- // Render the decoded frame to the video buffer
renderEmbeddedFrame(renderBitmap, frame, userId);
free(fobjData);
}
}
- // Done with FOBJ - assume only one relevant frame per embedded SAN
return;
} else {
- // Skip other sub-chunks (AHDR inside FRME?) or padding
stream.seek(nextSubPos);
}
}
stream.seek(nextChunkPos);
} else {
- // Skip non-FRME chunks (AHDR, etc at top level)
stream.seek(nextChunkPos);
}
}
@@ -434,9 +406,6 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
debugC(DEBUG_INSANE, "No FOBJ found in embedded SAN userId=%d", userId);
}
-// Spawn explosion into the shared 5-slot system.
-// spawnExplosion -- Allocate an explosion slot at the given position.
-// free slot (counter==0), set counter=10, scale=objectHalfWidth, position=center.
void InsaneRebel2::spawnExplosion(int x, int y, int objectHalfWidth) {
for (int i = 0; i < 5; i++) {
if (!_explosions[i].active || _explosions[i].counter <= 0) {
@@ -450,29 +419,26 @@ void InsaneRebel2::spawnExplosion(int x, int y, int objectHalfWidth) {
}
}
-// Used both as initial shot counter AND maxFrames for beam rendering.
int16 InsaneRebel2::getShotMaxDuration() {
LevelDifficultyParams params = getDifficultyParams();
- // Clamp to reasonable range to avoid division by zero or extreme beams
int16 duration = params.laserDelay;
if (duration <= 0)
- duration = 4; // Fallback for -1 entries (disabled levels)
+ duration = 4;
return duration;
}
-// spawnShot -- Dispatch to per-handler shot spawn.
void InsaneRebel2::spawnShot(int x, int y) {
switch (_rebelHandler) {
- case 0x26: // Turret
+ case 0x26:
spawnTurretShot(x, y);
break;
- case 8: // Vehicle
+ case 8:
spawnVehicleShot(x, y);
break;
- case 7: // Space combat
+ case 7:
spawnSpaceShot(x, y);
break;
- case 25: // Speeder bike - uses turret shot array with different gun position
+ case 25:
spawnHandler25Shot(x, y);
break;
default:
@@ -483,7 +449,6 @@ void InsaneRebel2::spawnShot(int x, int y) {
void InsaneRebel2::spawnTurretShot(int x, int y) {
for (int i = 0; i < 2; i++) {
if (_turretShots[i].counter == 0) {
- // levelType 5: BLAST.SAD (slot 0), otherwise: TBLAST.SAD (slot 7)
playSfx((_rebelLevelType == 5) ? 0 : 7, 127, 0);
_turretShots[i].counter = getShotMaxDuration();
@@ -508,11 +473,9 @@ void InsaneRebel2::spawnVehicleShot(int x, int y) {
}
}
-// Gun position from GRD002 offset tables:
void InsaneRebel2::spawnHandler25Shot(int x, int y) {
- // Handler 25 can only shoot when uncovered (damage == 0)
if (_rebelDamageLevel != 0) {
- return; // Can't shoot while taking cover
+ return;
}
for (int i = 0; i < 2; i++) {
@@ -523,16 +486,12 @@ void InsaneRebel2::spawnHandler25Shot(int x, int y) {
_turretShots[i].seqNum = _turretShotSeqCounter;
_turretShotSeqCounter++;
- // Target position is where player clicked, in buffer coords
_turretShots[i].targetX = x + _viewX;
_turretShots[i].targetY = y + _viewY;
- // where gunXTable/gunYTable are loaded by opcode 8, par4=8.
if (_grdShotOriginTableLoaded) {
- // Compute current sprite index (same logic as renderHandler25Ship)
int spriteIdx;
if (_rebelDamageLevel == 0) {
- // Uncovered: compute from crosshair position zones
int16 areaLeft = (_corridorLeftX > 0) ? _corridorLeftX : 0;
int16 areaRight = (_corridorRightX > 0) ? _corridorRightX : 320;
int16 areaTop = (_corridorTopY > 0) ? _corridorTopY : 0;
@@ -573,7 +532,6 @@ void InsaneRebel2::spawnHandler25Shot(int x, int y) {
_turretShots[i].gunX = gunXTable + _rebelViewOffset2X - _rebelViewOffsetX + _viewX;
_turretShots[i].gunY = gunYTable + _rebelViewOffset2Y - _rebelViewOffsetY + _viewY;
} else {
- // Fallback when table payload (opcode 8/par4=8) was not loaded.
_turretShots[i].gunX = _rebelViewOffset2X + 160 + _viewX;
_turretShots[i].gunY = _rebelViewOffset2Y + 140 + _viewY;
}
@@ -614,7 +572,6 @@ Common::Point InsaneRebel2::getHandler7ProjectedPoint() {
}
Common::Point InsaneRebel2::getHandler7ShotTargetPoint() {
- // Handler 7 targets the projected ship/crosshair point computed in
Common::Point projected = getHandler7ProjectedPoint();
return Common::Point(projected.x + _smoothedVelocity / 2,
@@ -622,7 +579,6 @@ Common::Point InsaneRebel2::getHandler7ShotTargetPoint() {
}
Common::Point InsaneRebel2::getHandler8ShotTargetPoint() {
- // Handler 8 stores and draws the shot target from the damped ship
return Common::Point(((_shipPosX - 0xa0) >> 3) + 0xa0,
((_shipPosY - 0x28) >> 2) + 0x69);
}
@@ -640,8 +596,6 @@ void InsaneRebel2::spawnSpaceShot(int x, int y) {
_spaceShots[i].targetX = target.x;
_spaceShots[i].targetY = target.y;
- // 8 par4=12/13. Values are centered FLY coordinates, adjusted by
- // the projected ship point before drawing the line.
if (_flyLeftGunTableLoaded) {
_spaceShots[i].leftGunX = projected.x + _flyLeftGunX[tableIndex] - 0xd4;
_spaceShots[i].leftGunY = projected.y + _flyLeftGunY[tableIndex] - 0x82;
@@ -662,7 +616,6 @@ void InsaneRebel2::spawnSpaceShot(int x, int y) {
}
}
-// drawTexturedLine -- Draw a line segment textured from a NUT sprite row.
void InsaneRebel2::drawTexturedLine(byte *dst, int pitch, int width, int height, int x0, int y0, int x1, int y1, NutRenderer *nut, int spriteIdx, int v, bool mask231) {
if (!nut || spriteIdx >= nut->getNumChars())
return;
@@ -682,7 +635,6 @@ void InsaneRebel2::drawTexturedLine(byte *dst, int pitch, int width, int height,
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2;
- // Total length approximation for UV mapping
int totalDist = (abs(dx) > abs(dy)) ? abs(dx) : abs(dy);
if (totalDist == 0)
totalDist = 1;
@@ -691,14 +643,12 @@ void InsaneRebel2::drawTexturedLine(byte *dst, int pitch, int width, int height,
for (;;) {
if (x0 >= 0 && x0 < width && y0 >= 0 && y0 < height) {
- // Map currentDist/totalDist to 0..texW (Run along texture width)
int u = (currentDist * texW) / totalDist;
if (u >= texW)
u = texW - 1;
byte color = srcData[v * texW + u];
- // Check for transparency (0 and optionally 231)
if (color != 0 && (!mask231 || color != 231)) {
dst[y0 * pitch + x0] = color;
}
@@ -717,7 +667,6 @@ void InsaneRebel2::drawTexturedLine(byte *dst, int pitch, int width, int height,
void drawTexturedSegment(byte *dst, int pitch, int width, int height,
int param_3, int param_4, int param_5, int param_6, int param_7, const byte *param_8,
int clipLeft, int clipTop, int clipRight, int clipBottom) {
- // Only color 0 is transparent.
int sVar4 = clipLeft;
int sVar1 = clipTop;
int sVar7 = clipRight;
@@ -1075,7 +1024,6 @@ void InsaneRebel2::initLaserTexture(NutRenderer *nut, int spriteIdx) {
if (!_laserTexture.pixels)
return;
- // so we must honor x/y offsets and transparency, not just memcpy glyph rows.
const byte *srcData = nut->getCharData(spriteIdx);
const int srcWidth = nut->getCharWidth(spriteIdx);
const int srcHeight = nut->getCharHeight(spriteIdx);
@@ -1095,7 +1043,6 @@ void InsaneRebel2::initLaserTexture(NutRenderer *nut, int spriteIdx) {
continue;
byte px = srcRow[sx];
- // Keep 231 pixels from the source texture to avoid dropping beam sub-segments.
if (px != 0) {
dstRow[dx] = px;
}
@@ -1114,14 +1061,10 @@ void InsaneRebel2::freeLaserTexture() {
_laserTexture.height = 0;
}
-// When data is nullptr, fills with the default table:
-// _edgeTable[a*256+b] = min(a,b) (symmetric identity blend)
-// When data is non-null, loads the primary table from data+8 (upper triangle, symmetric).
void InsaneRebel2::initEdgeTable(const byte *data) {
if (data == nullptr) {
for (int a = 0; a < 256; a++) {
for (int b = a; b < 256; b++) {
- // Primary table: table[a][b] = a (i.e. min(a,b) since b >= a)
_edgeTable[a + b * 256] = (byte)a;
_edgeTable[b + a * 256] = (byte)a;
}
@@ -1130,7 +1073,6 @@ void InsaneRebel2::initEdgeTable(const byte *data) {
_edgeTable[0x42 + 0xf0 * 256] = 0x42;
_edgeTable[0x41 * 256 + 0xb0] = 0x41;
} else {
- // Data format: 8-byte header + upper triangle of 256x256 symmetric table
const byte *src = data + 8;
for (int a = 0; a < 256; a++) {
for (int b = a; b < 256; b++) {
@@ -1142,11 +1084,6 @@ void InsaneRebel2::initEdgeTable(const byte *data) {
}
}
-// For each pixel along the line, reads the two adjacent pixels perpendicular to
-// the line direction and uses _edgeTable[above*256+below] as the output color.
-// This creates a glow/blend effect at beam edges.
-// For horizontal-dominant lines (dx > dy), reads pixels above and below.
-// For vertical-dominant lines (dy > dx), reads pixels left and right.
void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int height,
int16 x0, int16 y0, int16 x1, int16 y1,
int16 clipLeftIn, int16 clipTopIn, int16 clipRightIn, int16 clipBottomIn) {
@@ -1209,22 +1146,16 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
int16 dx = x1 - x0;
int16 dy = y1 - y0;
- // The key insight: for each pixel, the blend reads neighbors PERPENDICULAR to the line.
- // - Horizontal lines: blend pixel_above * 256 + pixel_below
- // - Vertical lines: blend pixel_left * 256 + pixel_right (reversed: left=[-1], right=[+1])
if (dx == 0) {
if (dy == 0) {
- // Single pixel: blend from above/below
*pixel = _edgeTable[(uint)pixel[pitch] + (uint)pixel[-pitch] * 256];
} else if (dy < 0) {
- // Vertical line going up: read left/right neighbors
while (dy < 1) {
*pixel = _edgeTable[(uint)pixel[1] + (uint)pixel[-1] * 256];
pixel -= pitch;
dy++;
}
} else {
- // Vertical line going down: read left/right neighbors
while (dy >= 0) {
*pixel = _edgeTable[(uint)pixel[1] + (uint)pixel[-1] * 256];
pixel += pitch;
@@ -1233,14 +1164,12 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
} else if (dy == 0) {
if (dx < 0) {
- // Horizontal line going left: read above/below neighbors
while (dx < 1) {
*pixel = _edgeTable[(uint)pixel[pitch] + (uint)pixel[-pitch] * 256];
pixel--;
dx++;
}
} else {
- // Horizontal line going right: read above/below neighbors
while (dx >= 0) {
*pixel = _edgeTable[(uint)pixel[pitch] + (uint)pixel[-pitch] * 256];
pixel++;
@@ -1250,9 +1179,7 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
} else if (dx < 0 || dy < 0) {
if (dy < 0) {
if (dx < 0) {
- // Both negative: going up-left
if (dx < dy) {
- // X-major (|dx| > |dy|): read above/below
int err = (-dx) >> 1;
int steps = 1 - dx;
while (steps > 0) {
@@ -1266,7 +1193,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
} else {
- // Y-major (|dy| > |dx|): read left/right
int err = (-dy) >> 1;
int steps = 1 - dy;
while (steps > 0) {
@@ -1281,9 +1207,7 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
} else {
- // dx > 0, dy < 0: going right-up
if (-dy < dx) {
- // X-major: read above/below
int err = dx >> 1;
int steps = dx + 1;
while (steps > 0) {
@@ -1297,7 +1221,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
} else {
- // Y-major: read left/right
int err = (-dy) >> 1;
int steps = 1 - dy;
while (steps > 0) {
@@ -1313,9 +1236,7 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
} else {
- // dx < 0, dy > 0: going left-down
if (-dx == dy || -dx < dy) {
- // Y-major: read left/right
int err = dy >> 1;
int steps = dy + 1;
while (steps > 0) {
@@ -1329,7 +1250,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
} else {
- // X-major: read above/below
int err = (-dx) >> 1;
int steps = 1 - dx;
while (steps > 0) {
@@ -1346,7 +1266,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
} else {
if (dy < dx) {
- // X-major: read above/below
int err = dx >> 1;
int steps = dx + 1;
while (steps > 0) {
@@ -1360,7 +1279,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
} else {
- // Y-major: read left/right
int err = dy >> 1;
int steps = dy + 1;
while (steps > 0) {
@@ -1377,15 +1295,10 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
-// Two-layer rendering:
-// Layer 1: Textured scanlines (beam body) via drawTexturedSegment()
-// Layer 2: Edge highlights (glow) via drawEdgeHighlightLine(), gated by _rebelDetailMode >= 0
-// dst, pitch, width, height: destination buffer info
void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
int16 gunX, int16 gunY, int16 targetX, int16 targetY,
int16 animFrame, int16 maxFrames,
int16 widthScale, int16 heightScale, int16 thickness) {
- // Check if laser texture is initialized
if (!_laserTexture.pixels || _laserTexture.width <= 0 || _laserTexture.height <= 0)
return;
@@ -1410,16 +1323,13 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
int16 sVar6 = (int16)(((int)dx * (thickness + 1)) / (int)thickness);
int16 sVar1 = (int16)(((int)dy * (thickness + 1)) / (int)thickness);
- // Start point (closer to gun, adjusted by animation progress)
int16 startX = (sVar6 + gunX) - (int16)(((int)sVar6 * 16) / (sVar7 + 16));
int16 startY = (sVar1 + gunY) - (int16)(((int)sVar1 * 16) / (sVar7 + 16));
- // End point (closer to target)
int16 endX = (sVar6 + gunX) - (int16)(((int)sVar6 * 16) / (widthScale + sVar7 + 16));
int16 endY = (sVar1 + gunY) - (int16)(((int)sVar1 * 16) / (widthScale + sVar7 + 16));
byte *local_28 = texPixels;
- // This preserves texture phase at the viewport edge and avoids visibly "chopped" beams.
int clipLeft = renderHiRes ? 0 : CLIP<int>(_viewX, 0, width - 1);
int clipTop = renderHiRes ? 0 : CLIP<int>(_viewY, 0, height - 1);
int clipRight = renderHiRes ? MIN(width - 1, 639) : CLIP<int>(_viewX + 319, 0, width - 1);
@@ -1439,7 +1349,6 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
int iVar3 = abs(startX - endX); // |dx| of beam
if (iVar2 < iVar3) {
- // Mostly horizontal beam - draw vertical scanlines
iVar2 = abs(startX - endX);
int temp = iVar2 * texH * heightScale;
int16 numLines = (int16)((temp >> 3) / texW) + 2;
@@ -1453,7 +1362,6 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
texW, local_28,
clipLeft, clipTop, clipRight, clipBottom);
- // Advance texture pointer (step through texture rows)
for (local_24 = texH + local_24; local_24 > 0; local_24 -= numLines) {
local_28 += texW;
}
@@ -1470,7 +1378,6 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
edgeClipLeft, edgeClipTop, edgeClipRight, edgeClipBottom);
}
} else {
- // Mostly vertical beam - draw horizontal scanlines
iVar2 = abs(startY - endY);
int16 numLines = (int16)((iVar2 * texH) / texW) + 2;
int16 local_24 = -numLines;
@@ -1488,7 +1395,6 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
texW, local_28,
clipLeft, clipTop, clipRight, clipBottom);
- // Advance texture pointer
for (local_24 = texH + local_24; local_24 > 0; local_24 -= numLines) {
local_28 += texW;
}
@@ -1507,34 +1413,20 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
}
}
-// Collision Zone System
-// Level 3 pilot ship obstacle avoidance. Zones are quadrilaterals
+
+// Level 3 obstacle zones are quadrilaterals from IACT opcode 5.
void InsaneRebel2::registerCollisionZone(Common::SeekableReadStream &b, int16 subOpcode, int16 par4) {
- // SmushPlayer reads the first 8 bytes as header (code/flags/unknown/userId),
- // +0x00: opcode (5) â already consumed by SmushPlayer
- // +0x02: par2 (sub-opcode) â already consumed, passed as parameter
- // +0x04: par3 â already consumed by SmushPlayer
- // +0x06: par4 (userId) â filter value for < 1000 test, passed as parameter
- // +0x0C: body[2] â vertex 1 X
- // +0x0E: body[3] â vertex 1 Y
- // +0x10: body[4] â vertex 2 X
- // +0x12: body[5] â vertex 2 Y
- // +0x14: body[6] â vertex 3 X
- // +0x16: body[7] â vertex 3 Y
- // +0x18: body[8] â vertex 4 X
- // +0x1A: body[9] â vertex 4 Y
-
- int16 field1 = b.readSint16LE(); // body[0] â control field 1
- int16 field2 = b.readSint16LE(); // body[1] â control field 2
- int16 x1 = b.readSint16LE(); // body[2] â vertex 1 X
- int16 y1 = b.readSint16LE(); // body[3] â vertex 1 Y
- int16 x2 = b.readSint16LE(); // body[4] â vertex 2 X
- int16 y2 = b.readSint16LE(); // body[5] â vertex 2 Y
- int16 x3 = b.readSint16LE(); // body[6] â vertex 3 X
- int16 y3 = b.readSint16LE(); // body[7] â vertex 3 Y
- int16 x4 = b.readSint16LE(); // body[8] â vertex 4 X
- int16 y4 = b.readSint16LE(); // body[9] â vertex 4 Y
+ int16 field1 = b.readSint16LE();
+ int16 field2 = b.readSint16LE();
+ int16 x1 = b.readSint16LE();
+ int16 y1 = b.readSint16LE();
+ int16 x2 = b.readSint16LE();
+ int16 y2 = b.readSint16LE();
+ int16 x3 = b.readSint16LE();
+ int16 y3 = b.readSint16LE();
+ int16 x4 = b.readSint16LE();
+ int16 y4 = b.readSint16LE();
CollisionZone zone;
zone.x1 = x1;
@@ -1568,21 +1460,14 @@ void InsaneRebel2::registerCollisionZone(Common::SeekableReadStream &b, int16 su
}
void InsaneRebel2::resetCollisionZones() {
- // This clears the zone tables so they can be rebuilt from the next frame's IACT chunks
_primaryZoneCount = 0;
_secondaryZoneCount = 0;
}
void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
- // Tests aim/ship position against primary collision zone quadrilaterals.
- // Zone vertices are in 424x260 buffer space, centered by subtracting (0xD4=212, 0x82=130).
- // For our implementation:
- // Mouse Y 0..200 â centered Y â [-45..45]
-
if (_primaryZoneCount == 0)
return;
- // Calculate aim position in centered coordinates.
const Common::Point aimPos = getGameplayAimPoint();
const int rawX = aimPos.x - 160;
const int rawY = aimPos.y - 100;
@@ -1609,11 +1494,9 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
if (!zone.active)
continue;
- // Filter: only process zones with filterValue < 1000 (par4 from IACT header)
if (zone.filterValue >= 1000)
continue;
- // Center zone vertices by subtracting buffer center (0xD4=212, 0x82=130)
int cx1 = zone.x1 - 0xD4;
int cy1 = zone.y1 - 0x82;
int cx2 = zone.x2 - 0xD4;
@@ -1623,20 +1506,9 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
int cx4 = zone.x4 - 0xD4;
int cy4 = zone.y4 - 0x82;
- // Frame check: field2 - 1 == field1
if (zone.field2 - 1 == zone.field1) {
- // Tests if aim position is OUTSIDE the safe corridor (= collision with obstacle).
- // Edge 1: interpolate Y along top edge (v1âv2) at aim X position
- // if aimY < interpolated Y â outside top edge â collision
- // Edge 2: interpolate Y along bottom edge (v4âv3) at aim X position
- // if interpolated Y < aimY â outside bottom edge â collision
- // Edge 3: interpolate X along left edge (v1âv4) at aim Y position
- // if aimX < interpolated X â outside left edge â collision
- // Edge 4: interpolate X along right edge (v2âv3) at aim Y position
- // if interpolated X < aimX â outside right edge â collision
bool collision = false;
- // Avoid division by zero for degenerate edges
if (cx2 != cx1) {
int interpY1 = ((aimX - cx1) * (cy2 - cy1)) / (cx2 - cx1) + cy1;
if (aimY < interpY1)
@@ -1659,7 +1531,6 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
}
if (collision) {
- // Collision detected â apply damage from collision damage table
int collisionDamage = (dparams.dodgeDamage >= 0) ? dparams.dodgeDamage : 0;
if (applyPlayerDamage(collisionDamage)) {
@@ -1669,14 +1540,11 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
if (!_noDamage)
initDamageFlash();
} else {
- // Safely passed â award score bonus
if (dparams.dodgePoints > 0) {
addScore(dparams.dodgePoints);
}
}
} else if (warningFrame && zone.field2 - 0x0c < zone.field1) {
- // draws directional arrows from cockpit icon slots 0x2a..0x2d.
- // Novice mode (flags bit 0x10) draws a generic indicator from slot 0x36.
const int iconChars = _smush_iconsNut->getNumChars();
if (showDirectionalWarnings) {
int avgX = (cx1 + cx2 + cx3 + cx4) / 4;
@@ -1705,37 +1573,29 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
}
}
-// Two modes: obstacle collision (secondary zones) and wall/boundary
-// collision (primary zones with per-edge push-back).
-// The helpers in this block are split out of checkHandler7CollisionZones;
bool InsaneRebel2::isHandler7ShipInsideObstacleZone(const InsaneRebel2::CollisionZone &zone, int margin) {
int x1 = zone.x1, y1 = zone.y1;
int x2 = zone.x2, y2 = zone.y2;
int x3 = zone.x3, y3 = zone.y3;
int x4 = zone.x4, y4 = zone.y4;
- // Start assuming inside, clear if outside any edge (with margin).
bool inside = true;
- // Top edge: interpolate Y along v1->v2 at shipX, +15 margin.
if (x2 != x1) {
int interpY = (_flyShipScreenX - x1) * (y2 - y1) / (x2 - x1) + margin + y1;
if (_flyShipScreenY < interpY)
inside = false;
}
- // Bottom edge: interpolate Y along v4->v3 at shipX, -15 margin.
if (inside && x3 != x4) {
int interpY = (_flyShipScreenX - x4) * (y3 - y4) / (x3 - x4) + y4 - margin;
if (interpY < _flyShipScreenY)
inside = false;
}
- // Left edge: interpolate X along v1->v4 at shipY, +15 margin.
if (inside && y4 != y1) {
int interpX = (_flyShipScreenY - y1) * (x4 - x1) / (y4 - y1) + margin + x1;
if (_flyShipScreenX < interpX)
inside = false;
}
- // Right edge: interpolate X along v2->v3 at shipY, -15 margin.
if (inside && y3 != y2) {
int interpX = (_flyShipScreenY - y2) * (x3 - x2) / (y3 - y2) + x2 - margin;
if (interpX < _flyShipScreenX)
@@ -1746,7 +1606,6 @@ bool InsaneRebel2::isHandler7ShipInsideObstacleZone(const InsaneRebel2::Collisio
}
void InsaneRebel2::applyHandler7ObstacleHit(const InsaneRebel2::CollisionZone &zone, int zoneIndex) {
- // Collision with obstacle - apply damage and break.
_hitCooldown = 10;
_spaceShotDirection = zone.filterValue + 2;
@@ -1762,7 +1621,6 @@ void InsaneRebel2::applyHandler7ObstacleHit(const InsaneRebel2::CollisionZone &z
}
void InsaneRebel2::awardHandler7DodgeScore() {
- // Safely avoided obstacle - award score.
LevelDifficultyParams scoreParams = getDifficultyParams();
if (scoreParams.dodgePoints > 0) {
addScore(scoreParams.dodgePoints);
@@ -1770,7 +1628,6 @@ void InsaneRebel2::awardHandler7DodgeScore() {
}
void InsaneRebel2::checkHandler7ObstacleZones(uint16 &warningMask) {
- // Inside the quad = collision with obstacle.
const int margin = 15;
for (int i = 0; i < _secondaryZoneCount; i++) {
@@ -1825,15 +1682,14 @@ void InsaneRebel2::checkHandler7TopBoundary(const InsaneRebel2::CollisionZone &z
if (x2 != x1) {
int16 edgeY = (int16)((_flyShipScreenX - x1) * (y2 - y1) / (x2 - x1) + y1 + vMargin);
if (_flyShipScreenY < edgeY) {
- // Ship above top wall - push down.
const bool damageApplied = applyHandler7WallDamage(wallDamage);
if (damageApplied) {
debugC(DEBUG_INSANE, "Handler7 Mode1/3 TOP WALL ship=(%d,%d) edgeY=%d damage=%d",
_flyShipScreenX, _flyShipScreenY, edgeY, wallDamage);
}
- _spaceShotDirection = 2; // Direction: pushed down
- _flyShipScreenY = edgeY; // Push-back
- playSfx(1, 127, 0); // CRASH.SAD, top wall -> center pan (always)
+ _spaceShotDirection = 2;
+ _flyShipScreenY = edgeY;
+ playSfx(1, 127, 0);
if (!_noDamage)
initDamageFlash();
} else if (_flyShipScreenY < edgeY + 0x28) {
@@ -1850,15 +1706,14 @@ void InsaneRebel2::checkHandler7BottomBoundary(const InsaneRebel2::CollisionZone
int16 edgeY = (int16)((_flyShipScreenX - x4) * (y3 - y4) / (x3 - x4) + y4 - vMargin);
_corridorBottomY = vMargin + edgeY;
if (edgeY < _flyShipScreenY) {
- // Ship below bottom wall - push up.
const bool damageApplied = applyHandler7WallDamage(wallDamage);
if (damageApplied) {
debugC(DEBUG_INSANE, "Handler7 Mode1/3 BOTTOM WALL ship=(%d,%d) edgeY=%d damage=%d",
_flyShipScreenX, _flyShipScreenY, edgeY, wallDamage);
}
- _spaceShotDirection = 3; // Direction: pushed up
- _flyShipScreenY = edgeY; // Push-back
- playSfx(1, 127, 0); // CRASH.SAD, bottom wall -> center pan (always)
+ _spaceShotDirection = 3;
+ _flyShipScreenY = edgeY;
+ playSfx(1, 127, 0);
if (!_noDamage)
initDamageFlash();
} else if (edgeY - 0x28 < _flyShipScreenY) {
@@ -1874,8 +1729,7 @@ void InsaneRebel2::checkHandler7LeftBoundary(const InsaneRebel2::CollisionZone &
if (y4 != y1) {
int16 edgeX = (int16)((_flyShipScreenY - y1) * (x4 - x1) / (y4 - y1) + x1 + hMargin);
if (_flyShipScreenX < edgeX) {
- // Ship left of left wall - push right.
- _flyShipScreenX = edgeX; // Push-back
+ _flyShipScreenX = edgeX;
resetHandler7HorizontalVelocity(127);
@@ -1884,8 +1738,8 @@ void InsaneRebel2::checkHandler7LeftBoundary(const InsaneRebel2::CollisionZone &
debugC(DEBUG_INSANE, "Handler7 Mode1/3 LEFT WALL ship=(%d,%d) edgeX=%d damage=%d",
_flyShipScreenX, _flyShipScreenY, edgeX, wallDamage);
}
- _spaceShotDirection = 0; // Direction: pushed right
- playSfx(1, 127, -100); // CRASH.SAD, left wall -> pan left (always)
+ _spaceShotDirection = 0;
+ playSfx(1, 127, -100);
if (!_noDamage)
initDamageFlash();
}
@@ -1899,8 +1753,7 @@ void InsaneRebel2::checkHandler7RightBoundary(const InsaneRebel2::CollisionZone
if (y3 != y2) {
int16 edgeX = (int16)((_flyShipScreenY - y2) * (x3 - x2) / (y3 - y2) + x2 - hMargin);
if (edgeX < _flyShipScreenX) {
- // Ship right of right wall - push left.
- _flyShipScreenX = edgeX; // Push-back
+ _flyShipScreenX = edgeX;
resetHandler7HorizontalVelocity(-127);
@@ -1909,8 +1762,8 @@ void InsaneRebel2::checkHandler7RightBoundary(const InsaneRebel2::CollisionZone
debugC(DEBUG_INSANE, "Handler7 Mode1/3 RIGHT WALL ship=(%d,%d) edgeX=%d damage=%d",
_flyShipScreenX, _flyShipScreenY, edgeX, wallDamage);
}
- _spaceShotDirection = 1; // Direction: pushed left
- playSfx(1, 127, 100); // CRASH.SAD, right wall -> pan right (always)
+ _spaceShotDirection = 1;
+ playSfx(1, 127, 100);
if (!_noDamage)
initDamageFlash();
}
@@ -1918,7 +1771,6 @@ void InsaneRebel2::checkHandler7RightBoundary(const InsaneRebel2::CollisionZone
}
void InsaneRebel2::checkHandler7BoundaryZones(uint16 &warningMask) {
- // Ship position is clamped to wall boundaries when hitting.
int16 hMargin = (_flyControlMode == 1) ? 0x28 : 0x0f;
const int16 vMargin = 0x0f;
LevelDifficultyParams wallParams = getDifficultyParams();
@@ -1962,12 +1814,6 @@ void InsaneRebel2::renderHandler7WarningCues(byte *renderBitmap, int pitch, int
}
void InsaneRebel2::checkHandler7CollisionZones(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
- // Uses ship position (_flyShipScreenX/_flyShipScreenY) in raw buffer coords.
- // Two modes depending on _flyControlMode:
- // Mode 0/2: Obstacle collision using SECONDARY zones (inside quad = hit)
- // Mode 1/3: Wall/boundary collision using PRIMARY zones (per-edge push-back)
-
- // bit 0=left, bit 1=right, bit 2=top, bit 3=bottom
uint16 warningMask = 0;
if (_flyControlMode == 0 || _flyControlMode == 2) {
@@ -1979,12 +1825,10 @@ void InsaneRebel2::checkHandler7CollisionZones(byte *renderBitmap, int pitch, in
renderHandler7WarningCues(renderBitmap, pitch, width, height, curFrame, warningMask);
}
-// renderNutSprite -- Draw a NUT sprite with transparency.
void InsaneRebel2::renderNutSprite(byte *dst, int pitch, int width, int height, int x, int y, NutRenderer *nut, int spriteIdx) {
renderNutSpriteMirrored(dst, pitch, width, height, x, y, nut, spriteIdx, false);
}
-// renderNutSpriteClipped -- Draw a NUT sprite with explicit clip rectangle.
void renderNutSpriteClipped(byte *dst, int pitch, int dstH,
int clipLeft, int clipTop, int clipRight, int clipBottom,
int x, int y, NutRenderer *nut, int spriteIdx) {
@@ -2114,7 +1958,6 @@ void InsaneRebel2::renderNutSpriteMirrored(byte *dst, int pitch, int width, int
int h = nut->getCharHeight(spriteIdx);
const byte *src = nut->getCharData(spriteIdx);
- // Clipping
int drawX = x;
int drawY = y;
int drawW = w;
@@ -2143,27 +1986,24 @@ void InsaneRebel2::renderNutSpriteMirrored(byte *dst, int pitch, int width, int
if (drawW <= 0 || drawH <= 0)
return;
- // Draw loop - with optional horizontal mirroring
for (int iy = 0; iy < drawH; iy++) {
const byte *s = src + (srcOffsetY + iy) * w;
byte *d = dst + (drawY + iy) * pitch + drawX;
for (int ix = 0; ix < drawW; ix++) {
int srcX;
if (mirror) {
- // When mirrored, read from the opposite side of the sprite
srcX = (w - 1) - (srcOffsetX + ix);
} else {
srcX = srcOffsetX + ix;
}
byte px = s[srcX];
- if (px != 231 && px != 0) { // Check both 0 and 231 (0xE7) for transparency
+ if (px != 231 && px != 0) {
d[ix] = px;
}
}
}
}
-// updatePostRenderScroll -- Set SmushPlayer scroll offsets for the current frame.
void InsaneRebel2::updatePostRenderScroll(int width, int height) {
if (_rebelHandler == 0) {
_viewX = 0;
@@ -2205,9 +2045,6 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
maxScrollY = 0;
if (_rebelHandler == 7) {
- // flight buffer is presented through a perspective-derived 320x170
- // Keep the final crop in that same space so rendered ship/cues and
- // raw collision zones describe the same tunnel position.
const int handler7MaxScrollY = isHiRes() ? MAX<int>(0, height - 170) : maxScrollY;
_viewX = CLIP<int>(0x34 + _perspectiveX, 0, maxScrollX);
_viewY = CLIP<int>(0x2d + _perspectiveY, 0, handler7MaxScrollY);
@@ -2223,9 +2060,7 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
_player->setScrollOffset(_viewX, _viewY);
}
-// updatePostRenderDeath -- End gameplay playback when player damage reaches 255.
void InsaneRebel2::updatePostRenderDeath() {
- // Sync _playerShield from _playerDamage and break out of video on death.
if (_rebelHandler != 0) {
_playerShield = 255 - _playerDamage;
if (_playerShield <= 0) {
@@ -2235,7 +2070,6 @@ void InsaneRebel2::updatePostRenderDeath() {
}
}
-// renderPostRenderMenuCursor -- Draw RA2's software cursor for menu videos.
void InsaneRebel2::renderPostRenderMenuCursor(byte *renderBitmap, int pitch, int width, int height) {
static const byte kRa2MenuCursor[] = {
0, 0, 1, 1, 1, 1, 1,
@@ -2273,51 +2107,38 @@ void InsaneRebel2::renderPostRenderMenuCursor(byte *renderBitmap, int pitch, int
}
}
-// handlePostRenderMenuModes -- Process menu-like videos drawn during post-rendering.
bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int width, int height, bool introPlaying) {
- // Check if we're in menu mode (menu state + intro flag).
bool menuMode = (introPlaying && _gameState == kStateMainMenu);
bool pilotSelectMode = (introPlaying && (_gameState == kStatePilotSelect || _gameState == kStateDifficultySelect));
bool chapterSelectMode = (introPlaying && _gameState == kStateChapterSelect);
- // This is the pilot/save slot selection screen with centered menu.
if (pilotSelectMode) {
int selection = processLevelSelectInput();
- // Draw pilot selection overlay - centered menu like main menu.
drawLevelSelectOverlay(renderBitmap, pitch, width, height);
renderPostRenderMenuCursor(renderBitmap, pitch, width, height);
- // If a selection was confirmed, signal video to stop.
if (selection >= 0) {
debugC(DEBUG_INSANE, "Pilot selection confirmed: %d", selection);
_menuSelectionConfirmed = true;
_vm->_smushVideoShouldFinish = true;
}
- // Skip normal HUD rendering in pilot select mode.
return true;
}
- // This is the actual level/chapter selection screen with preview and password.
if (chapterSelectMode) {
- // O_LEVEL.SAN provides the background with chapter preview thumbnails.
- // The FOBJ offset system (set in procPreRendering) scrolls the correct preview
- // into the preview box area. No black fill needed â video frame shows through.
-
int selection = processChapterSelectInput();
drawChapterSelectOverlay(renderBitmap, pitch, width, height);
renderPostRenderMenuCursor(renderBitmap, pitch, width, height);
- // If a selection was confirmed, signal video to stop.
if (selection >= 0) {
debugC(DEBUG_INSANE, "Chapter selection confirmed: %d", selection);
_menuSelectionConfirmed = true;
_vm->_smushVideoShouldFinish = true;
}
- // Skip normal HUD rendering in chapter select mode.
return true;
}
@@ -2334,15 +2155,10 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
}
if (menuMode) {
- // Process menu input during each frame.
int selection = processMenuInput();
- // Update inactivity timer (only increments when no input is received).
- // Input resets timer in processMenuInput().
_menuInactivityTimer++;
- // Check for inactivity timeout.
- // At 12fps video rate, 300 frames = ~25 seconds of inactivity.
if (_menuInactivityTimer > 300) {
debugC(DEBUG_INSANE, "Menu inactivity timeout - resuming intro/demo loop");
_menuInactivityTimer = 0;
@@ -2351,36 +2167,25 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
_vm->_smushVideoShouldFinish = true;
}
- // Draw menu selection overlay.
drawMenuOverlay(renderBitmap, pitch, width, height);
renderPostRenderMenuCursor(renderBitmap, pitch, width, height);
- // If a selection was confirmed, signal video to stop.
if (selection >= 0) {
debugC(DEBUG_INSANE, "Menu selection confirmed: %d", selection);
_menuSelectionConfirmed = true;
_vm->_smushVideoShouldFinish = true;
}
- // Skip normal HUD rendering in menu mode.
return true;
}
return false;
}
-// handlePostRenderIntro -- Hide gameplay HUD for intro/cinematic videos.
bool InsaneRebel2::handlePostRenderIntro(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
- // During intro/cinematic sequences:
- // - Skip all HUD/status bar/crosshair rendering
- // - Skip mouse input processing (no shooting during intros)
- // - Cinematics/intros don't have opcode 6, so handler stays 0
- // - We use _rebelHandler == 0 as the primary indicator for intro/cinematic mode
if (_rebelHandler == 0) {
- // Hide mouse cursor during intro - no crosshair, no clicking.
CursorMan.showMouse(false);
- // Track state transition for debugging.
if (!_introCursorPushed) {
_introCursorPushed = true;
debugC(DEBUG_INSANE, "Intro/cinematic mode (handler=0, flags=0x%x, state=%d) - HUD disabled, mouse hidden",
@@ -2390,11 +2195,9 @@ bool InsaneRebel2::handlePostRenderIntro(byte *renderBitmap, int pitch, int widt
if (_textOverlayActive)
renderTextOverlay(renderBitmap, pitch, width, height, curFrame);
- // Skip all HUD rendering during intro - subtitles are rendered via opcode 9.
return true;
}
- // Gameplay mode - handler was set by IACT opcode 6.
if (_introCursorPushed) {
_introCursorPushed = false;
debugC(DEBUG_INSANE, "Gameplay mode (handler=%d, flags=0x%x, state=%d) - HUD enabled",
@@ -2404,21 +2207,17 @@ bool InsaneRebel2::handlePostRenderIntro(byte *renderBitmap, int pitch, int widt
return false;
}
-// updateGameplayDamageEffects -- Apply handler-specific damage visuals.
void InsaneRebel2::updateGameplayDamageEffects(byte *renderBitmap, int pitch, int width, int height) {
if (_rebelHandler == 8) {
- // Full damage effect: palette flash + screen shake.
// Suppressed during autopilot (mode 4) and cutscene (mode 5).
if (_shipLevelMode != 4 && _shipLevelMode != 5) {
updateDamageEffect(renderBitmap, pitch, width, height);
}
} else if (_rebelHandler == 0x19 || _rebelHandler == 0x26 || _rebelHandler == 7) {
- // Palette flash only - no screen shake for turret/FPS/ship handlers.
updateDamageFlashPalette();
}
}
-// updateGameplayDamageRecovery -- Apply RA2's damage auto-reduction.
void InsaneRebel2::updateGameplayDamageRecovery(int32 curFrame) {
// timed score tick in the same slot and does not reduce damage.
if ((_rebelHandler != 0x26 && _rebelHandler != 8 && _rebelHandler != 7) ||
@@ -2430,13 +2229,7 @@ void InsaneRebel2::updateGameplayDamageRecovery(int32 curFrame) {
_playerShield = 255 - _playerDamage;
}
-// checkGameplayPostRenderCollisions -- Run handler-specific collision checks.
void InsaneRebel2::checkGameplayPostRenderCollisions(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
- // Per-frame collision checking against registered zones.
- // Zones with filterValue < 1000 tested via point-in-quad against mouse/aim position.
- // Mode 0/2: SECONDARY zones (0x0E) - obstacle collision (inside quad = hit)
- // Mode 1/3: PRIMARY zones (0x0D) - wall/boundary per-edge with push-back
- // Uses ship position in raw buffer coords, hit cooldown, directional damage.
if (_rebelHandler == 0x26) {
checkCollisionZones(renderBitmap, pitch, width, height, curFrame);
} else if (_rebelHandler == 7) {
@@ -2446,8 +2239,6 @@ void InsaneRebel2::checkGameplayPostRenderCollisions(byte *renderBitmap, int pit
void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY, int32 curFrame) {
- // From here on, we're in gameplay mode (_rebelHandler != 0).
- // Process mouse input for shooting.
processMouse();
// Handler 7's high-detail flight view uses a 320x170 gameplay area after
@@ -2474,19 +2265,8 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
}
}
- // NOTE: Level 2 handler 8's background is restored in procPreRendering before
- // SMUSH decodes the frame's FOBJ sprites. Handler 25 draws its corridor overlay
- // from IACT opcode 6 instead. Redrawing either here would overwrite enemies.
-
- // The cockpit frame covers laser beam edges, giving the appearance
- // that beams emerge from behind the cockpit.
-
- // 2. Lines 171-226: Draw turret overlays, targeting reticle, crosshair
- // We draw directly to screen at Y=180.
-
renderStatusBarBackground(renderBitmap, pitch, width, height, videoWidth, videoHeight, gameplayStatusBarY);
- // Ship rendering. Handler 7 is drawn later, after its lasers, matching
debugC(DEBUG_INSANE, "Ship Check: handler=%d shipSprite=%p flyShipSprite=%p shipLevelMode=%d numSprites=%d/%d",
_rebelHandler, (void*)_shipSprite, (void*)_flyShipSprite, _shipLevelMode,
_shipSprite ? _shipSprite->getNumChars() : 0,
@@ -2496,25 +2276,19 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
renderHandler8Ship(renderBitmap, pitch, width, height);
renderFallbackShip(renderBitmap, pitch, width, height);
- // Enemy target indicators (handler-specific; sprite-based in turret mode).
renderEnemyOverlays(renderBitmap, pitch, width, height, videoWidth);
renderExplosions(renderBitmap, pitch, width, height);
- // Laser shot beams - drawn BEFORE cockpit/HUD overlays so cockpit covers beam edges.
renderLaserShots(renderBitmap, pitch, width, height);
renderHandler7Ship(renderBitmap, pitch, width, height);
- // Lines 202-229: GRD001 (wall, opaque, covers enemies behind wall)
- // Lines 230-248: GRD002 (character, transparent, drawn last)
renderHandler25ShipPre(renderBitmap, pitch, width, height);
renderHandler25Ship(renderBitmap, pitch, width, height);
- // These are cockpit frame, crosshair, and reticle - drawn ON TOP of laser beams.
renderTurretHudOverlays(renderBitmap, pitch, width, height, curFrame);
- // STEP 1B: Draw embedded SAN HUD overlays (from IACT chunks).
renderEmbeddedHudOverlays(renderBitmap, pitch, width, height);
renderStatusBarSprites(renderBitmap, pitch, width, height, gameplayStatusBarY, curFrame);
@@ -2539,13 +2313,9 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
frameEndCleanup();
}
-// procPostRendering -- Post-frame rendering: HUD, ships, enemies, effects, status bar.
-// Called after FOBJ decoding. Dispatches to per-handler rendering functions
-// for ship sprites, laser shots, explosions, crosshair, and damage effects.
void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32 setupsan12,
int32 setupsan13, int32 curFrame, int32 maxFrame) {
- // Determine correct pitch for the video buffer (usually 320 for Rebel2)
int width = _player->_width;
int height = _player->_height;
if (width == 0)
@@ -2573,14 +2343,11 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
_vm->_smushVideoShouldFinish = true;
}
- // Use video content coordinates, NOT oversized low-res gameplay-buffer coordinates.
const int hudScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
const int videoWidth = 320 * hudScale;
const int videoHeight = 200 * hudScale;
const int statusBarY = 180 * hudScale; // 0xb4 low-res, 0x168 high-res
- // Hide HUD/status bar during intro videos (marked by SmushPlayer video flag 0x20)
- // The 0x20 flag indicates a non-interactive cutscene/intro sequence OR menu
bool introPlaying = ((_player->_curVideoFlags & 0x20) != 0);
if (isHiRes() && _rebelHandler == 0) {
@@ -2601,9 +2368,6 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
renderGameplayPostFrame(renderBitmap, pitch, width, height, videoWidth, videoHeight, statusBarY, curFrame);
}
-// Damage Visual Effect Functions
-// Palette flash + screen shake when the player takes damage.
-
void InsaneRebel2::resetDamageFlash() {
_damageFlashCounter = 0;
}
@@ -2629,7 +2393,6 @@ void InsaneRebel2::initDamageFlash() {
}
if (_damageFlashCounter == 0) {
- // Save current SMUSH palette before modifying it
memcpy(_damageSavedPalette, _player->_pal, 0x300);
}
_damageFlashCounter = 5;
@@ -2640,12 +2403,7 @@ void InsaneRebel2::triggerDamageEffect() {
_damageShakeCounter = 10;
}
-// Normal hit flash (_damageHighFlashCounter == 0 or odd):
-// Blend formula: output[i] = 0xFF - ((0xFF - saved[i]) * (0x10 - counter)) >> 4
-// High-damage red pulse (_playerDamage >= 0xFF, even counter):
-// R channel only (every 3rd byte) using same formula with _damageHighFlashCounter.
void InsaneRebel2::updateDamageFlashPalette() {
- // High-damage mode: persistent red pulsing when damage is maxed out
if (_playerDamage < 0xFF) {
_damageHighFlashCounter = 0;
} else {
@@ -2654,7 +2412,6 @@ void InsaneRebel2::updateDamageFlashPalette() {
memcpy(_damageRestorePalette, _player->_pal, 0x300);
_damageRestorePaletteValid = true;
}
- // Save palette on first frame of high-damage mode
memcpy(_damageSavedPalette, _player->_pal, 0x300);
}
if (_damageHighFlashCounter < 0x10) {
@@ -2663,11 +2420,9 @@ void InsaneRebel2::updateDamageFlashPalette() {
}
if (_damageHighFlashCounter == 0 || (_damageHighFlashCounter & 1) != 0) {
- // Normal hit flash path: decrement counter, apply on even values.
if (_damageFlashCounter != 0) {
_damageFlashCounter--;
if ((_damageFlashCounter & 1) == 0) {
- // Apply palette inversion on ALL RGB channels
byte modPal[0x300];
int blend = 0x10 - _damageFlashCounter;
for (int i = 0; i < 0x300; i++) {
@@ -2679,8 +2434,6 @@ void InsaneRebel2::updateDamageFlashPalette() {
}
}
} else {
- // High-damage red-only flash (even _damageHighFlashCounter):
- // Modify only R channel (stride 3), G and B stay unchanged.
byte modPal[0x300];
memcpy(modPal, _player->_pal, 0x300);
int blend = 0x10 - _damageHighFlashCounter;
@@ -2691,7 +2444,6 @@ void InsaneRebel2::updateDamageFlashPalette() {
}
}
-// Shifts counter*5 random scanlines per frame, diminishing over 10 frames.
void InsaneRebel2::updateDamageEffect(byte *renderBitmap, int pitch, int width, int height) {
if (_damageShakeCounter != 0) {
_damageShakeCounter--;
@@ -2702,7 +2454,6 @@ void InsaneRebel2::updateDamageEffect(byte *renderBitmap, int pitch, int width,
const int maxY = MIN(height, renderHiRes ? 360 : 180);
for (int n = numLines; n > 0; n--) {
- // Pick a random scanline within the gameplay area, not the status bar.
if (maxY <= 0)
continue;
int scanline = _vm->_rnd.getRandomNumber(maxY - 1);
@@ -2718,30 +2469,23 @@ void InsaneRebel2::updateDamageEffect(byte *renderBitmap, int pitch, int width,
switch (direction) {
case 0:
case 3:
- // Shift left: copy line[offset..] -> line[0..]
memmove(linePtr, linePtr + offset, copyLen);
break;
case 1:
- // Shift right with wrap: save, then copy
memcpy(tempLine, linePtr, MIN(copyLen, (int)sizeof(tempLine)));
memmove(linePtr + offset, tempLine, MIN(copyLen, (int)sizeof(tempLine)));
break;
case 2:
case 4:
- // Shift right: copy line[0..] -> line[offset..]
memmove(linePtr + offset, linePtr, copyLen);
break;
}
}
}
- // Palette flash runs every frame (even without shake)
updateDamageFlashPalette();
}
-// Rendering Helper Functions
-// Extracted from procPostRendering for better readability.
-
void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, int height, int curFrame) {
if (curFrame < _textOverlayFadeIn || curFrame >= _textOverlayFadeOut)
return;
@@ -2754,14 +2498,12 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
if (!text)
return;
- // Progressive reveal: displayLen = currentFrame + 10 - fadeInFrame, capped at 0xBE (190)
int displayLen = curFrame + 10 - _textOverlayFadeIn;
if (displayLen > 0xBE)
displayLen = 0xBE;
if (displayLen < 0)
return;
- // Font system â ^fNN = font switch, ^cNNN = color code
NutRenderer *fonts[3] = { _smush_talkfontNut, _smush_smalfontNut, _smush_titlefontNut };
NutRenderer *defaultFont = fonts[0] ? fonts[0] : _smush_smalfontNut;
if (!defaultFont)
@@ -2806,7 +2548,6 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
const char *lineStr = lines[lineIdx].c_str();
const char *lineEnd = lineStr + lines[lineIdx].size();
- // Measure visible chars up to displayLen
int lineWidth = 0;
int lineVisCount = 0;
NutRenderer *lineFont = defaultFont;
@@ -2827,7 +2568,6 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
}
}
- // Draw line centered at textX
int drawX = _textOverlayX * textScale - lineWidth / 2;
int lineCharsDrawn = 0;
{
@@ -2861,7 +2601,6 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
void InsaneRebel2::renderStatusBarBackground(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY) {
- // This fills width=320, height=20 starting at Y=180 with color index 4
const byte statusBarBgColor = 4;
for (int y = statusBarY; y < videoHeight; y++) {
@@ -2878,16 +2617,9 @@ void InsaneRebel2::renderStatusBarBackground(byte *renderBitmap, int pitch, int
}
void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
- // Draw NUT-based HUD overlays for Handler 0x26 (turret modes)
- // - Position formula (low-res):
- // X = 160 + (mouseOffsetX >> 4) - (width / 2) - spriteOffsetX
- // Y = 182 - (mouseOffsetY >> 4) - height - spriteOffsetY
- // - Animation: spriteIndex = (frameCounter / 2) % 6
-
if (_rebelHandler != 0x26 || !_hudOverlayNut || _hudOverlayNut->getNumChars() <= 0)
return;
- // Calculate mouse offset (clamped to -127..127)
Common::Point aimPos = getGameplayAimPoint();
int mouseOffsetX = (aimPos.x - 160);
int mouseOffsetY = (aimPos.y - 100);
@@ -2900,7 +2632,6 @@ void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int wi
if (mouseOffsetY < -127)
mouseOffsetY = -127;
- // Animation frame cycling: (frameCounter / 2) % 6
int numSprites = _hudOverlayNut->getNumChars();
int animFrameCount = MIN(numSprites, 6);
int animFrame = 0;
@@ -2920,15 +2651,12 @@ void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int wi
int hudX = 160 * hudScale + horizontalTerm - baseSpriteW / 2 - baseSpriteXOff;
int hudY = 182 * hudScale - verticalTerm - baseSpriteH - baseSpriteYOff;
- // Apply view offset for scrolling background
hudX += _viewX;
hudY += _viewY;
- // Draw base cockpit (sprite 0 always drawn first)
renderNutSprite(renderBitmap, pitch, width, height,
hudX + baseSpriteXOff, hudY + baseSpriteYOff, _hudOverlayNut, 0);
- // Draw animation overlay frame if not frame 0
if (animFrame != 0 && animFrame < numSprites) {
renderNutSprite(renderBitmap, pitch, width, height,
hudX + _hudOverlayNut->getCharXOffset(animFrame),
@@ -2948,31 +2676,23 @@ void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int wi
}
}
-// renderEmbeddedHudOverlays -- Draw embedded SAN HUD overlays from IACT chunks.
void InsaneRebel2::renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int width, int height) {
- // Draw embedded SAN HUD overlays (from IACT chunks)
- // For Handler 7 (Level 3): HUD elements are scattered across the screen
- // For turret handlers: slots 1-2 form a two-part cockpit overlay
-
for (int hudSlot = 1; hudSlot < 16; hudSlot++) {
EmbeddedSanFrame &frame = _rebelEmbeddedHud[hudSlot];
if (!isValidEmbeddedFrame(frame))
continue;
- // Handler 25: skip slot 4 (corridor overlay) in post-rendering.
- // loading par4=4; drawing it here would cover enemies.
+ // Handler 25 slot 4 is a corridor overlay that would cover enemies here.
if (_rebelHandler == 25 && hudSlot == 4) {
continue;
}
- // Skip small frames at (0,0) - likely animation patches
if (frame.renderX == 0 && frame.renderY == 0 && frame.width < 50 && frame.height < 60) {
debugC(DEBUG_INSANE, "Skipping small embedded frame at (0,0): slot=%d size=%dx%d",
hudSlot, frame.width, frame.height);
continue;
}
- // For Handler 7: handle direction-based frame selection
if (_rebelHandler == 7) {
int groupMembers[16];
int groupCount = 0;
@@ -3005,19 +2725,16 @@ void InsaneRebel2::renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int
}
}
- // Calculate destination position
int destX = frame.renderX;
int destY = frame.renderY;
const int hudScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
- // Handler 0x26 turret positioning
if (_rebelHandler == 0x26 && hudSlot >= 1 && hudSlot <= 4) {
destX = 160 * hudScale - frame.width / 2 - frame.renderX;
destY = 200 * hudScale - frame.height - frame.renderY;
}
- // Handler 7 large cockpit frame positioning
if (_rebelHandler == 7 && (hudSlot == 1 || hudSlot == 2) && frame.width > 100) {
destX = 160 * hudScale - frame.width / 2 - frame.renderX;
destY = 170 * hudScale - frame.height - frame.renderY;
@@ -3041,12 +2758,6 @@ void InsaneRebel2::renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int
void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int width, int height,
int statusBarY, int32 curFrame) {
- // DISPFONT.NUT sprite layout:
- // Sprite 1: Status bar background
- // Sprites 2-5: Difficulty variants (full status bar with 1-4 stars)
- // Sprite 6: Bar fill element (reused for both damage and lives bars)
- // Sprite 7: Damage alert overlay (flashing when critical)
-
if (!_smush_cockpitNut)
return;
@@ -3067,9 +2778,6 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
_viewX, statusBarY + _viewY, _smush_cockpitNut, difficultySprite);
}
- // Clip rect (low-res): {X=0x3f, Y=9, W=0x40, H=6} = {63, 9, 64, 6}
- // Bar width = shield_value >> 2 (divide by 4, range 0-63)
- // If shield > 0xAA (170): alert blink with sprite 7
if (numSprites > 6) {
int damageBarWidth = (_playerDamage * statusScale) >> 2;
@@ -3103,8 +2811,6 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
}
}
- // When damage > 0xAA (170) and frame counter bit 3 is clear, draw sprite 7
- // at full clip rect width (64 pixels) to show flashing alert
if (numSprites > 7 && _playerDamage > 170 && ((curFrame & 8) == 0)) {
if (src && sw > 0 && sh > 0) {
int alertW = MIN(dmgClipW, sw - dmgClipX);
@@ -3136,8 +2842,6 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
}
}
- // Clip rect (low-res): {X=0xa8, Y=7, W=0x32, H=9} = {168, 7, 50, 9}
- // Bar width = min((lives * 5 - 5) * 2, 50) â only drawn when lives > 1
if (numSprites > 6 && _playerLives > 1) {
int livesBarWidth = (_playerLives * 5 - 5) * 2;
if (livesBarWidth > 50)
@@ -3176,7 +2880,6 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
}
}
-// renderHandler7FlySprite -- Draw a native Handler 7 FLY sprite into the current presentation target.
void InsaneRebel2::renderHandler7FlySprite(byte *renderBitmap, int pitch, int width, int height,
bool renderHiRes, int renderScale, int nativeViewX, int nativeViewY,
int nativeX, int nativeY, NutRenderer *nut, int spriteIndex) {
@@ -3193,9 +2896,6 @@ void InsaneRebel2::renderHandler7FlySprite(byte *renderBitmap, int pitch, int wi
}
void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width, int height) {
- // Handler 7 Ship Rendering (Third-Person Ship - FLY sprites)
- // The ship sprite is drawn at the perspective-transformed position offset from center.
-
if (_rebelHandler != 7 || !_flyShipSprite || _shipLevelMode == 5)
return;
@@ -3214,10 +2914,6 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
Common::Point projected = getHandler7ProjectedPoint();
Common::Point shipDraw = getHandler7ShipDrawPoint();
if (renderHiRes) {
- // Low-res draws into the native source buffer with _viewX/_viewY baked in,
- // then SmushPlayer copies the scrolled viewport. High-res promotion has
- // already consumed those offsets, so reconstruct the same native source
- // position before applying the 2x presentation transform.
projected.x += nativeViewX;
projected.y += nativeViewY;
shipDraw.x += nativeViewX;
@@ -3319,13 +3015,6 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
}
void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width, int height) {
- // Handler 8 Ship Rendering (Third-Person On Foot - POV sprites)
- // Uses _shipSprite (POV001) with position-based offset
- // (short)(shipPosX - 0xa0) >> 3, // small X offset
- // (short)(shipPosY - 0x28) >> 2, // small Y offset
- // parameters. The sprite's built-in offsets encode where it should appear
- // on screen (e.g., center for Level 2/11, bottom for Level 12 FPS gun).
-
if (_rebelHandler != 8 || !_shipSprite || _shipLevelMode == 5)
return;
@@ -3337,8 +3026,6 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
if (spriteIndex >= numSprites)
spriteIndex = 0;
- // The internal offsets position the sprite correctly for each level type
- // (centered for Level 2/11 third-person, bottom for Level 12 FPS).
int16 spriteXOffset = _shipSprite->getCharXOffset(spriteIndex);
int16 spriteYOffset = _shipSprite->getCharYOffset(spriteIndex);
int drawX = displayOffsetX + spriteXOffset;
@@ -3419,11 +3106,6 @@ void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int w
}
}
-// Handler 25: Draw GRD001 (wall/cockpit overlay) in procPostRendering.
-// GRD sprites drawn AFTER FOBJ enemies, before GRD002. Mode-based clipping:
-// Mode 1, damage==0: left half only (pixels 0-159)
-// Mode 4, damage==0: right half only (pixels 160-319)
-// All other cases: full width (320 pixels)
void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int width, int height) {
if (_rebelHandler != 25)
return;
@@ -3431,9 +3113,6 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
if (!_grd001Sprite || _grd001Sprite->getNumChars() <= 0)
return;
- // CRITICAL: Clip height to 180 (0xb4) + viewport Y to avoid drawing over status bar.
- // In high-res presentation the low-res GRD sprite is scaled into the promoted
- // 640x400 frame, so the gameplay clip becomes 360 pixels tall.
const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
const int renderScale = renderHiRes ? 2 : 1;
const int nativeViewX = renderHiRes ? _hiResPresentationViewX : 0;
@@ -3448,7 +3127,7 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
if (_grdSpriteMode == 1) {
shouldDraw = true;
- useHalfWidth = (_rebelDamageLevel == 0); // Half width when uncovered
+ useHalfWidth = (_rebelDamageLevel == 0);
}
else if (_grdSpriteMode == 2 && _rebelDamageLevel != 0) {
shouldDraw = true;
@@ -3468,17 +3147,11 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
int16 spriteXOffset = _grd001Sprite->getCharXOffset(0);
int16 spriteYOffset = _grd001Sprite->getCharYOffset(0);
- // Add viewport offset so sprite follows the visible area.
- // Handler 25 stays viewport-locked in low-res mode, so _viewX/_viewY
- // remain 0 even when the backing buffer is larger than 320x200.
- // Other oversized-buffer modes scroll and need this compensation.
int nativeDrawX = _rebelViewOffset2X + spriteXOffset + nativeBufferViewX;
int nativeDrawY = _rebelViewOffset2Y + spriteYOffset + nativeBufferViewY;
int drawX = renderHiRes ? (nativeDrawX - nativeViewX) * renderScale : nativeDrawX;
int drawY = renderHiRes ? (nativeDrawY - nativeViewY) * renderScale : nativeDrawY;
- // - mode1 uncovered: left half
- // - mode4 uncovered: right half
int clipLeft = 0;
int clipRight = renderHiRes ? 320 : width;
if (useHalfWidth) {
@@ -3521,14 +3194,9 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
}
void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width, int height) {
- // Handler 25 POST-rendering: Draw GRD002 (character sprite) on top of enemies.
- // GRD001 (wall/cockpit) is drawn before this via renderHandler25ShipPre().
- // GRD002 is drawn LAST (after enemies) so the character appears in front.
-
if (_rebelHandler != 25)
return;
- // CRITICAL: Clip height to 180 (0xb4) + viewport Y to avoid drawing over status bar.
const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
const int renderScale = renderHiRes ? 2 : 1;
const int nativeViewX = renderHiRes ? _hiResPresentationViewX : 0;
@@ -3537,29 +3205,18 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
const int nativeBufferViewY = renderHiRes ? nativeViewY : _viewY;
int renderHeight = renderHiRes ? MIN(height, 180 * renderScale) : MIN(height, 180 + _viewY);
- // If damage == 0: index = yZone * 5 + xZone + 5 (aiming-based, 5-14)
- // If damage != 0:
- // If direction == 0: index = 5 - damage (0-5, covered left)
- // If direction != 0: index = 25 - damage (20-25, covered right)
if (_grd002Sprite && _grd002Sprite->getNumChars() > 0) {
int spriteIdx;
int numSprites = _grd002Sprite->getNumChars();
- // Mirror when: direction != 0 AND damage == 0 (fully uncovered, facing right)
bool shouldMirror = (_rebelFlightDir != 0) && (_rebelDamageLevel == 0);
if (_rebelDamageLevel == 0) {
- // Uncovered state: use aiming-based sprite selection (5-14)
- // Calculate zones from crosshair position relative to playable area
- // The playable area bounds are defined by corridor boundaries.
- // xZone = 0-4 (left to right), yZone = 0-1 (top to bottom)
- // Default to center if bounds not set
int16 areaLeft = (_corridorLeftX > 0) ? _corridorLeftX : 0;
int16 areaRight = (_corridorRightX > 0) ? _corridorRightX : 320;
int16 areaTop = (_corridorTopY > 0) ? _corridorTopY : 0;
int16 areaBottom = (_corridorBottomY > 0) ? _corridorBottomY : 180;
- // Get crosshair position (using mouse position scaled to game coords)
Common::Point aimPos = getGameplayAimPoint();
int16 crosshairX = aimPos.x;
int16 crosshairY = aimPos.y;
@@ -3568,17 +3225,14 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
crosshairY = (crosshairY * 200) / _player->_height;
}
- // Calculate zone widths
int areaWidth = areaRight - areaLeft;
int areaHeight = areaBottom - areaTop;
- int zoneWidth = (areaWidth > 0) ? (areaWidth + 3) / 4 : 80; // Divide into ~4 zones
- int zoneHeight = (areaHeight > 0) ? areaHeight / 2 : 90; // Divide into 2 zones
+ int zoneWidth = (areaWidth > 0) ? (areaWidth + 3) / 4 : 80;
+ int zoneHeight = (areaHeight > 0) ? areaHeight / 2 : 90;
- // Calculate xZone (0-4) and yZone (0-1) from crosshair position
int xZone = (zoneWidth > 0) ? ((zoneWidth / 2) + (crosshairX - areaLeft)) / zoneWidth : 2;
int yZone = (zoneHeight > 0) ? ((zoneHeight / 2) + (crosshairY - areaTop)) / zoneHeight : 0;
- // Clamp to valid ranges
if (xZone < 0)
xZone = 0;
if (xZone > 4)
@@ -3594,19 +3248,13 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
spriteIdx = yZone * 5 + xZone + 5;
} else {
- // Transitioning/covered state: use direction-based sprite
- // direction == 0: 5 - damage
- // direction != 0: 25 - damage
if (_rebelFlightDir == 0) {
- // Direction 0: sprites 0-5 (transition left)
spriteIdx = 5 - _rebelDamageLevel;
} else {
- // Direction 1: sprites 20-25 (transition right)
spriteIdx = 25 - _rebelDamageLevel;
}
}
- // Clamp to valid range
if (spriteIdx < 0)
spriteIdx = 0;
if (spriteIdx >= numSprites)
@@ -3615,12 +3263,6 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
int spriteW = _grd002Sprite->getCharWidth(spriteIdx);
int spriteH = _grd002Sprite->getCharHeight(spriteIdx);
- // GRD002 explicitly adds sprite internal offsets from NUT header:
- // Normal case (direction==0 OR damage!=0):
-
- // Mirrored case (direction!=0 AND damage==0):
-
- // Now using actual NUT sprite offsets from NutRenderer!
int16 spriteXOffset = _grd002Sprite->getCharXOffset(spriteIdx);
int16 spriteYOffset = _grd002Sprite->getCharYOffset(spriteIdx);
@@ -3645,15 +3287,10 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
}
}
-// renderFallbackShip -- Fallback ship rendering using embedded HUD frame.
void InsaneRebel2::renderFallbackShip(byte *renderBitmap, int pitch, int width, int height) {
- // Fallback: Use embedded HUD frame as ship sprite (Level 3 style)
- // userId=11 contains the ship sprite strip
-
if ((_rebelHandler != 7 && _rebelHandler != 8) || _shipLevelMode == 5)
return;
- // Skip if we have proper sprites
if (_rebelHandler == 7 && _flyShipSprite)
return;
if (_rebelHandler == 8 && _shipSprite)
@@ -3663,13 +3300,11 @@ void InsaneRebel2::renderFallbackShip(byte *renderBitmap, int pitch, int width,
if (!isValidEmbeddedFrame(shipFrame))
return;
- // Calculate display offset
int16 displayOffsetX = (_shipPosX - 0xa0) >> 3;
int16 displayOffsetY = (_shipPosY - 0x28) >> 2;
int shipScreenX = 0xa0 + displayOffsetX;
int shipScreenY = 0x69 + displayOffsetY;
- // Detect sprite strip layout
int spriteW = shipFrame.width;
int spriteH = shipFrame.height;
int srcX = 0, srcY = 0;
@@ -3700,10 +3335,6 @@ void InsaneRebel2::renderFallbackShip(byte *renderBitmap, int pitch, int width,
}
void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width, int height, int videoWidth) {
- // - Draws cockpit icon sprites 6..10 at enemy centers.
- // - Enabled when level flags bit 2 (0x04) is clear.
- // - Sprite index depends on object half-width bucket.
- // It is not a generic all-handler bracket overlay.
if (_rebelHandler != 0x26 || !_smush_iconsNut)
return;
@@ -3711,8 +3342,6 @@ void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width,
if ((dparams.flags & 4) != 0)
return;
- // FOBJ sprites are rendered with _fobjOffsetX/Y applied. Use the same offsets
- // so indicators stay aligned with decoded enemy sprites.
int fobjOffX = _player ? _player->_fobjOffsetX : 0;
int fobjOffY = _player ? _player->_fobjOffsetY : 0;
const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
@@ -3739,7 +3368,6 @@ void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width,
if (halfW <= 0 || halfH <= 0)
continue;
- // class 0..4 -> sprite 6..10.
int indicatorHalfW = halfW;
if (sizeClamp > 0)
indicatorHalfW = MIN(indicatorHalfW, sizeClamp / 2);
@@ -3776,13 +3404,10 @@ void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width,
}
}
-// renderExplosions -- Dispatch to per-handler explosion renderer.
void InsaneRebel2::renderExplosions(byte *renderBitmap, int pitch, int width, int height) {
- // Check flags bit 0: suppress explosion sprite rendering
LevelDifficultyParams dparams = getDifficultyParams();
bool suppressExplosionSprites = (dparams.flags & 1) != 0;
- // Even when suppressed, still tick down explosion counters
if (suppressExplosionSprites) {
for (int i = 0; i < 5; i++) {
if (_explosions[i].active && _explosions[i].counter > 0) {
@@ -3812,9 +3437,6 @@ void InsaneRebel2::renderExplosions(byte *renderBitmap, int pitch, int width, in
}
}
-// renderExplosionFrame -- Shared explosion sprite path.
-// 320x200 path used here, they share centered NUT drawing; callers keep their
-// coordinate transforms, frame timing, and scale bucket rules explicit.
void InsaneRebel2::renderExplosionFrame(byte *renderBitmap, int pitch, int width, int height,
InsaneRebel2::Explosion &explosion, int screenX, int screenY, ExplosionFrameAdvance advance,
bool resolutionDependentScale) {
@@ -3874,7 +3496,6 @@ void InsaneRebel2::renderTurretExplosions(byte *renderBitmap, int pitch, int wid
if (!_explosions[i].active)
continue;
- // At 320x200 low-res turret view, projection is effectively identity.
int screenX = _explosions[i].x;
int screenY = _explosions[i].y;
renderExplosionFrame(renderBitmap, pitch, width, height, _explosions[i],
@@ -3882,7 +3503,6 @@ void InsaneRebel2::renderTurretExplosions(byte *renderBitmap, int pitch, int wid
}
}
-// Scale thresholds: <11, <21. No secondary NUT.
void InsaneRebel2::renderVehicleExplosions(byte *renderBitmap, int pitch, int width, int height) {
if (!_smush_iconsNut)
return;
@@ -3906,21 +3526,17 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
if (!_explosions[i].active)
continue;
- // At low-res, this is close to identity for the ship view.
int screenX = _explosions[i].x;
int screenY = _explosions[i].y;
renderExplosionFrame(renderBitmap, pitch, width, height, _explosions[i],
screenX, screenY, kExplosionAdvanceAfterDraw, true);
}
- // 0 = left side (hit left boundary), 1 = right side (hit right boundary)
- // 2 = bottom (zone push down), 3 = top (zone push up)
- // Sprite frames: 0x15 - cooldown = 21 - cooldown (frames 12â21 as cooldown 9â0)
if (_hitCooldown != 0) {
_hitCooldown--;
int numChars = _smush_iconsNut->getNumChars();
- int spriteIndex = 0x15 - _hitCooldown; // 21 - remaining cooldown
+ int spriteIndex = 0x15 - _hitCooldown;
const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
const int nativeViewX = renderHiRes ? _hiResPresentationViewX : _viewX;
const int nativeViewY = renderHiRes ? _hiResPresentationViewY : _viewY;
@@ -3932,21 +3548,18 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
shipProjected.y += nativeViewY;
}
- // Per-direction offset from ship center.
- // _shipDirectionIndex (35 entries per direction). We approximate
- // with fixed offsets since we don't have the table data.
int offsetX = 0, offsetY = 0;
switch (_spaceShotDirection) {
- case 0: // Left wall hit â explosion on left side of ship
+ case 0:
offsetX = -35;
break;
- case 1: // Right wall hit â explosion on right side of ship
+ case 1:
offsetX = 35;
break;
- case 2: // Zone push down â explosion on bottom
+ case 2:
offsetY = 20;
break;
- case 3: // Zone push up â explosion on top
+ case 3:
offsetY = -20;
break;
default:
@@ -3992,7 +3605,6 @@ void InsaneRebel2::renderHandler25Explosions(byte *renderBitmap, int pitch, int
}
}
-// renderLaserShots -- Dispatch to per-handler laser renderer.
void InsaneRebel2::renderLaserShots(byte *renderBitmap, int pitch, int width, int height) {
switch (_rebelHandler) {
case 0x26:
@@ -4008,14 +3620,11 @@ void InsaneRebel2::renderLaserShots(byte *renderBitmap, int pitch, int width, in
renderHandler25LaserShots(renderBitmap, pitch, width, height);
break;
default:
- // No laser rendering for other handlers
break;
}
}
void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int width, int height) {
- // Uses pre-initialized _laserTexture from sprite 5 of CPITIMAG.NUT
-
int16 maxDuration = getShotMaxDuration();
const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
const int nativeViewX = renderHiRes ? _hiResPresentationViewX : _viewX;
@@ -4027,7 +3636,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
int16 pan = ((2 - _turretShots[i].counter) * (_turretShots[i].targetX - nativeViewX - 160)) / 2;
pan = CLIP<int16>(pan, -127, 127);
- // TODO: Apply panning to sound channel i+1
int16 targetX = _turretShots[i].targetX;
int16 targetY = _turretShots[i].targetY;
@@ -4035,10 +3643,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
switch (_rebelLevelType) {
case 1:
- // Type 1: 3 guns (triple cannon configuration)
- // Gun 1: (0x136, 0xaa) = (310, 170) - right
- // Gun 2: (0xa0, 0x17c) = (160, 380) - center bottom (off-screen, clipped)
- // Gun 3: (0x0a, 0xaa) = (10, 170) - left
drawLaserBeam(renderBitmap, pitch, width, height,
310 + nativeViewX, 170 + nativeViewY, targetX, targetY,
progress, maxDuration, 12, 8, 12);
@@ -4054,10 +3658,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
case 2:
case 5:
- // Type 2/5: 2 guns (wing cannons)
- // Left: (0x6e, 0xe6) = (110, 230)
- // Right: (0xd2, 0xe6) = (210, 230)
- // Assembly: widthScale=0x19(25), heightScale=8, thickness=0xC(12)
drawLaserBeam(renderBitmap, pitch, width, height,
110 + nativeViewX, 230 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
@@ -4068,10 +3668,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
break;
case 6:
- // Type 6: 2 guns (offscreen - cinematic effect)
- // Gun 1: (-100, 0)
- // Gun 2: (0, 0)
- // Assembly: widthScale=0x19(25), heightScale=8, thickness=0xC(12)
drawLaserBeam(renderBitmap, pitch, width, height,
-100 + nativeViewX, 0 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
@@ -4082,9 +3678,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
break;
default:
- // When seqNum & 1 == 0: Left (10, 50), Right (310, 130)
- // When seqNum & 1 == 1: Left (310, 50), Right (10, 130)
- // Assembly: widthScale=0x19(25), heightScale=8, thickness=0xC(12)
if ((_turretShots[i].seqNum & 1) == 0) {
drawLaserBeam(renderBitmap, pitch, width, height,
10 + nativeViewX, 50 + nativeViewY, targetX, targetY,
@@ -4110,24 +3703,15 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
}
void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int width, int height) {
- // No NUT check needed - uses pre-initialized _laserTexture
-
int16 maxDuration = getShotMaxDuration();
for (int i = 0; i < 2; i++) {
if (_vehicleShots[i].counter <= 0)
continue;
- // pan = ((2 - counter) * (targetX - 160)) / 2, clamped to [-127, 127]
int16 pan = ((2 - _vehicleShots[i].counter) * (_vehicleShots[i].targetX - _viewX - 160)) / 2;
pan = CLIP<int16>(pan, -127, 127);
- // TODO: Apply panning
- // creating a short muzzle flash effect (7 pixels horizontal, 25 pixels vertical).
- // shipScreenY = ((shipPosY - 0x28) >> 2) + 0x69 = ((shipPosY - 40) >> 2) + 105
- // shipScreenX = ((shipPosX - 0xa0) >> 3) + 0xa0 = ((shipPosX - 160) >> 3) + 160
- // gunY = ((shipPosY - 0x28) >> 2) + 0x82 = shipScreenY + 25
- // gunX = ((shipPosX - 0xa0) >> 3) + 0xa7 = shipScreenX + 7
int16 shipScreenX = ((_shipPosX - 160) >> 3) + 160;
int16 shipScreenY = ((_shipPosY - 40) >> 2) + 105;
int16 gunX = shipScreenX + 7;
@@ -4135,8 +3719,6 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
int16 progress = maxDuration - _vehicleShots[i].counter;
- // Draw beam from gun toward ship center (muzzle flash effect)
- // Parameters: gunX, gunY -> shipScreenX, shipScreenY (NOT the stored target!)
drawLaserBeam(renderBitmap, pitch, width, height,
gunX, gunY,
shipScreenX, shipScreenY,
@@ -4144,7 +3726,7 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
_vehicleShots[i].counter--;
- // mask pixels select the POV002/POV003 impact sprite index rendered by
+ // Level 2 mask pixels select the POV impact sprite.
if (_shipLevelMode != 2 && _level2BackgroundLoaded && _level2Background) {
int impactX = ((_shipPosX - 160) >> 3) + _shipPosX + 160;
int impactY = ((_shipPosY - 40) >> 2) + _shipPosY + 105;
@@ -4171,18 +3753,14 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
}
void InsaneRebel2::renderSpaceLaserShots(byte *renderBitmap, int pitch, int width, int height) {
- // No NUT check needed - uses pre-initialized _laserTexture
-
int16 maxDuration = getShotMaxDuration();
for (int i = 0; i < 2; i++) {
if (_spaceShots[i].counter <= 0)
continue;
- // Calculate sound panning
int16 pan = ((_spaceShots[i].targetX - 160) * (2 - _spaceShots[i].counter)) / 2;
pan = CLIP<int16>(pan, -127, 127);
- // TODO: Apply panning
int16 targetX = _spaceShots[i].targetX;
int16 targetY = _spaceShots[i].targetY;
@@ -4192,13 +3770,10 @@ void InsaneRebel2::renderSpaceLaserShots(byte *renderBitmap, int pitch, int widt
int16 rightGunY = _spaceShots[i].rightGunY;
int16 progress = maxDuration - _spaceShots[i].counter;
- // Draw dual beams
- // Left gun beam
drawLaserBeam(renderBitmap, pitch, width, height,
leftGunX, leftGunY, targetX, targetY,
progress, maxDuration, 12, 4, 6);
- // Right gun beam
drawLaserBeam(renderBitmap, pitch, width, height,
rightGunX, rightGunY, targetX, targetY,
progress, maxDuration, 12, 4, 6);
@@ -4208,9 +3783,8 @@ void InsaneRebel2::renderSpaceLaserShots(byte *renderBitmap, int pitch, int widt
}
void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int width, int height) {
- // Only render when player is uncovered (damage == 0)
if (_rebelDamageLevel != 0) {
- return; // Can't shoot while taking cover
+ return;
}
int16 maxDuration = getShotMaxDuration();
@@ -4221,7 +3795,6 @@ void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int
int16 pan = ((2 - _turretShots[i].counter) * (_turretShots[i].targetX - 160)) / 2;
pan = CLIP<int16>(pan, -127, 127);
- // TODO: Apply panning to sound channel i+1
int16 targetX = _turretShots[i].targetX + _rebelViewOffsetX;
int16 targetY = _turretShots[i].targetY + _rebelViewOffsetY;
@@ -4231,7 +3804,6 @@ void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int
int16 progress = maxDuration - _turretShots[i].counter;
- // widthScale=0xC(12), heightScale=4, thickness=6
drawLaserBeam(renderBitmap, pitch, width, height,
gunX, gunY, targetX, targetY,
progress, maxDuration, 12, 4, 6);
@@ -4335,9 +3907,7 @@ void InsaneRebel2::renderHandler8PovOverlay(byte *renderBitmap, int pitch, int w
}
}
-// renderCrosshair -- Draw crosshair/reticle at the current handler's aim point.
void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int height) {
- // Don't draw crosshair when shooting is disabled (flight-only segments)
if (!isShootingAllowed()) {
return;
}
@@ -4346,8 +3916,6 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
return;
}
- // Update target lock state and draw crosshair/reticle
-
Common::Point aimPos;
if (_rebelHandler == 7) {
aimPos = getHandler7ShotTargetPoint();
@@ -4382,7 +3950,6 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
_targetLockTimer--;
}
- // Draw crosshair
if (!_smush_iconsNut)
return;
@@ -4412,7 +3979,6 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
int cw = _smush_iconsNut->getCharWidth(reticleIndex);
int ch = _smush_iconsNut->getCharHeight(reticleIndex);
- // Calculate crosshair position
const int reticleScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
int crosshairX = aimPos.x * reticleScale;
int crosshairY = aimPos.y * reticleScale;
@@ -4421,7 +3987,6 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
crosshairY += _viewY;
}
- // Handler 25 (0x19): Add view offset to crosshair position
if (_rebelHandler == 25) {
crosshairX += _rebelViewOffsetX * reticleScale;
crosshairY += _rebelViewOffsetY * reticleScale;
@@ -4437,8 +4002,6 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
}
void InsaneRebel2::frameEndCleanup() {
- // Reset enemy active flags and collision zones at frame end
-
for (Common::List<enemy>::iterator it = _enemies.begin(); it != _enemies.end(); ++it) {
if (!it->destroyed) {
it->active = false;
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 6f835c396c9..ee3cbf09506 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -85,7 +85,6 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
return result;
}
- // Debug shortcut path: force-end current section when requested via Shift+S.
if (_skipSectionRequested) {
_skipSectionRequested = false;
_rebelPhaseState = mask;
@@ -96,10 +95,6 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
return result;
}
- // Threshold-based early exit needs per-frame callbacks; current playback
- // waits for the full wave and accumulates the resulting kill state.
- // TODO: Implement per-frame early exit callback for threshold-based wave termination.
-
_rebelPhaseState = _rebelWaveState;
debugC(DEBUG_INSANE, "processWaveEnd: waveState=0x%x -> phaseState=0x%x mask=0x%x budget=%d threshold=%d flags=%d",
_rebelWaveState, _rebelPhaseState, mask, budget ? *budget : -1, threshold, flags);
@@ -362,19 +357,8 @@ void InsaneRebel2::resetHandler7FlightState() {
}
}
-// Multiple parts with P1/P2/P3 subdirectories
-// Random animation variants for each part
-
int InsaneRebel2::runLevel2() {
- // Three phases, each with looping enemy waves until all enemy types killed.
- // Phase completion: (_rebelPhaseState & mask) == mask
- // Phase 1 mask: 0x06 (enemy types 1,2)
- // Phase 2 mask: 0x0e (enemy types 1,2,3)
- // Phase 3 mask: 0x0e (enemy types 1,2,3)
- // Each phase gets a budget = tableBase + random(3). processWaveEnd() uses
- // this budget to randomly redistribute kill credits, creating non-deterministic
- // wave progression. Using calibrated defaults until exact table values extracted.
- const int16 kLevel2BudgetBase[3] = { 3, 3, 3 }; // Phase 1, 2, 3
+ const int16 kLevel2BudgetBase[3] = { 3, 3, 3 };
int bonusCount = 0;
int totalKills = 0;
@@ -405,10 +389,8 @@ int InsaneRebel2::runLevel2() {
if (!playLevelSegment("LEV02/P1/02P01_A.SAN", 0x28))
return kLevelQuit;
- // processWaveEnd after A.SAN (threshold=0, no early exit for background loader)
processWaveEnd(0x36, &budget, 0, 0);
- // Phase 1 wave loop: random B/C/D until all type 1,2 enemies killed
while (_playerDamage < 255 && (_rebelPhaseState & 0x06) != 0x06) {
if (_vm->shouldQuit())
return kLevelQuit;
@@ -423,7 +405,6 @@ int InsaneRebel2::runLevel2() {
if (!playLevelSegment(variants[variant], 0x428))
return kLevelQuit;
- // processWaveEnd with threshold=0x14 (20) â enables early exit when enemies killed
processWaveEnd(0x36, &budget, 0x14, 0);
debugC(DEBUG_INSANE, "Phase 1 wave done - state=0x%x (need 0x06) budget=%d", _rebelPhaseState, budget);
}
@@ -440,7 +421,6 @@ int InsaneRebel2::runLevel2() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Reset handler to 0 so procPostRendering skips HUD/sprite drawing during cinematic
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
if (!playLevelSegment("LEV02/02PST1.SAN", 0x28, false))
@@ -454,7 +434,6 @@ int InsaneRebel2::runLevel2() {
budget = kLevel2BudgetBase[1] + _vm->_rnd.getRandomNumber(2);
- // Restore handler for gameplay â will be confirmed by IACT opcode 6
_rebelHandler = 8;
debugC(DEBUG_INSANE, "Level 2 Phase 2 - playing 02P02_A.SAN (background) budget=%d", budget);
@@ -507,7 +486,6 @@ int InsaneRebel2::runLevel2() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Reset handler to 0 so procPostRendering skips HUD/sprite drawing during cinematic
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
if (!playLevelSegment("LEV02/02PST2.SAN", 0x28, false))
@@ -522,14 +500,12 @@ int InsaneRebel2::runLevel2() {
budget = kLevel2BudgetBase[2] + _vm->_rnd.getRandomNumber(2);
- // Restore handler for gameplay â will be confirmed by IACT opcode 6
_rebelHandler = 8;
debugC(DEBUG_INSANE, "Level 2 Phase 3 - playing 02P03_A.SAN (background) budget=%d", budget);
if (!playLevelSegment("LEV02/P3/02P03_A.SAN", 0x28))
return kLevelQuit;
- // Phase 3: processWaveEnd at BOTTOM (like Phase 1), waveSelect carried across iterations
{
WaveEndResult waveEnd = processWaveEnd(0x3e, &budget, 0, 0);
@@ -539,7 +515,6 @@ int InsaneRebel2::runLevel2() {
uint16 waveSelect = waveEnd.creditedBits;
- // If previous wave state bit 0 was clear AND random(8)==0, set bit 0
if (((prevWaveState & 1) == 0) && (_vm->_rnd.getRandomNumber(7) == 0)) {
waveSelect |= 1;
}
@@ -571,7 +546,6 @@ int InsaneRebel2::runLevel2() {
if (!playLevelSegment(filename, 0x428))
return kLevelQuit;
- // processWaveEnd at BOTTOM with threshold=0x14
waveEnd = processWaveEnd(0x3e, &budget, 0x14, 0);
debugC(DEBUG_INSANE, "Phase 3 wave done - state=0x%x (need 0x0e) budget=%d", _rebelPhaseState, budget);
}
@@ -590,7 +564,6 @@ int InsaneRebel2::runLevel2() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Score presentation remains to be implemented.
{
totalMisses += _rebelHitCounter;
int accuracy = calculateAccuracy(totalKills, totalMisses);
@@ -599,18 +572,15 @@ int InsaneRebel2::runLevel2() {
}
playLevelEnd(2);
- _levelUnlocked[2] = true; // Unlock level 3
+ _levelUnlocked[2] = true;
return kLevelNextLevel;
}
return kLevelQuit;
}
-// Two phases with per-phase retry handling
-// Phase 1: 03PLAY1.SAN, Phase 2: 03PLAY2.SAN
-
int InsaneRebel2::runLevel3() {
- int phase1Score = 0; // Score preserved across phase 2 retries
+ int phase1Score = 0;
playLevelBegin(3);
if (_vm->shouldQuit())
@@ -655,7 +625,6 @@ int InsaneRebel2::runLevel3() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Reset handler to 0 so procPostRendering skips HUD/sprite drawing during cinematic
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
if (!playLevelSegment("LEV03/03POST1.SAN", 0x28, false))
@@ -681,7 +650,7 @@ int InsaneRebel2::runLevel3() {
if (_playerShield > 0) {
debugC(DEBUG_INSANE, "Level 3 completed!");
playLevelEnd(3);
- _levelUnlocked[3] = true; // Unlock level 4
+ _levelUnlocked[3] = true;
return kLevelNextLevel;
}
@@ -692,7 +661,6 @@ int InsaneRebel2::runLevel3() {
_playerLives--;
if (_playerLives <= 0) {
- // Use phase 2 specific game over (03OVER.SAN, same file but at different point)
playLevelGameOver(3);
return kLevelGameOver;
}
@@ -705,9 +673,6 @@ int InsaneRebel2::runLevel3() {
return kLevelQuit;
}
-// Level 4 Handler
-// Cutscene + single gameplay phase
-
int InsaneRebel2::runLevel4() {
if (!playLevelSegment("LEV04/04CUT.SAN", 0x28, false))
return kLevelQuit;
@@ -754,9 +719,6 @@ int InsaneRebel2::runLevel4() {
return kLevelQuit;
}
-// Single gameplay phase (05PLAY.SAN)
-// Random A/B death video like Level 1
-
int InsaneRebel2::runLevel5() {
playLevelBegin(5);
if (_vm->shouldQuit())
@@ -800,29 +762,19 @@ int InsaneRebel2::runLevel5() {
return kLevelQuit;
}
-// Two phases with per-phase retry (like Level 3)
-// Phase 1: 06PLAY1.SAN, Phase 2: 06PLAY2.SAN
-
int InsaneRebel2::runLevel6() {
- // Phase 1 (levelId=5): 06PLAY1.SAN, mid-switch to 06PLAY1B.SAN at frame 0x2a8
- // Phase 2 (levelId=6): 06PLAY2.SAN, play until near-end
- // phase 2 retries + death handling. Phase 1 death breaks inner â RETRY at outer bottom.
- // Phase 2 death â RETRYB â re-enters phase 2 within inner loop.
int phase1Score = 0;
playLevelBegin(6);
if (_vm->shouldQuit())
return kLevelQuit;
-
- // Outer retry loop â restarts phase 1 on phase 1 death
while (!_vm->shouldQuit()) {
clearBit(0);
resetExplosions();
_rebelPhaseState = 0xffffffff;
- // destroys the shield (its hit gauge reaches 0) or dies.
_rebelLevelType = 5;
_currentPhase = 1;
@@ -909,9 +861,6 @@ int InsaneRebel2::runLevel6() {
return kLevelQuit;
}
-// "TIE Training" - Canyon flight with fork at frame 1592
-// Single gameplay phase (07PLAY.SAN), optional second segment (07PLAYB.SAN)
-
int InsaneRebel2::runLevel7() {
bool reachedFork = false;
@@ -934,9 +883,6 @@ int InsaneRebel2::runLevel7() {
clearBit(0);
- // TODO: Mid-level fork at frame 1592 requires per-frame callback.
- // The fork video (07PLAYB) should be triggered by IACT callbacks setting
- // a state flag during gameplay.
if (!playLevelSegment("LEV07/07PLAY.SAN", 0x28))
return kLevelQuit;
@@ -970,8 +916,6 @@ int InsaneRebel2::runLevel7() {
return kLevelQuit;
}
-// "Flight to Imdaar" - Y-Wing space battle (single phase)
-
int InsaneRebel2::runLevel8() {
playLevelBegin(8);
if (_vm->shouldQuit())
@@ -1017,8 +961,6 @@ int InsaneRebel2::runLevel8() {
return kLevelQuit;
}
-// "The Mine Field" - Navigate through force fields (single phase)
-
int InsaneRebel2::runLevel9() {
playLevelBegin(9);
if (_vm->shouldQuit())
@@ -1036,7 +978,6 @@ int InsaneRebel2::runLevel9() {
_rebelPhaseState = 0xfffffffe;
- // These are handled implicitly â the IACT callbacks manage scoring.
if (!playLevelSegment("LEV09/09PLAY.SAN", 0x28))
return kLevelQuit;
@@ -1067,9 +1008,6 @@ int InsaneRebel2::runLevel9() {
return kLevelQuit;
}
-// "Speeder Bikes" - Forest speeder chase (single phase)
-// Has cutscene. Single death video (10DIE.SAN, no variants).
-
int InsaneRebel2::runLevel10() {
playCinematic("LEV10/10CUT.SAN");
if (_vm->shouldQuit())
@@ -1118,14 +1056,6 @@ int InsaneRebel2::runLevel10() {
return kLevelQuit;
}
-// "Inside the Terror" - Three phases + bridge puzzle (Handler 8, on-foot)
-// Phase 1: P1/11P01_X (A,B,C,D) - behind barrels, mask 0x0e
-// Phase 2: P2/11P02_X (A,B,C,D) - walls on right, mask 0x0e, flags=3
-// Phase 3 first half: P3/11P03_X (A-F) - bridge puzzle, mask 0x7e
-// Exit when (phaseState & 0x70) == 0x70
-// POST3/POST3B/POST3C bridge cinematics
-// Phase 3 second half: P3/11P03_X (G-L) - after bridge, mask 0x0e
-
int InsaneRebel2::runLevel11() {
int totalKills = 0;
int totalMisses = 0;
@@ -1160,15 +1090,12 @@ int InsaneRebel2::runLevel11() {
{
WaveEndResult waveEnd = processWaveEnd(0x0e, &budget, 0, 0);
- // Phase 1 wave loop: random(2) | (waveSelect & 8) â variants
- // 0âD, 1âC, 8âB, 9âA
while (!waveEnd.shouldStop()) {
if (_vm->shouldQuit())
return kLevelQuit;
uint16 waveSelect = waveEnd.creditedBits;
- // Bonus sound check
if ((_rebelPhaseState & 0x10) != 0 && (prevPhaseState & 0x10) == 0) {
}
prevPhaseState = _rebelPhaseState;
@@ -1218,10 +1145,8 @@ int InsaneRebel2::runLevel11() {
return kLevelQuit;
{
- // Phase 2: flags=3 (maxCredits=2, redistribution ON)
WaveEndResult waveEnd = processWaveEnd(0x0e, &budget, 0, 3);
- // Random(4) for variant selection: A, B, C, D
while (!waveEnd.shouldStop()) {
if (_vm->shouldQuit())
return kLevelQuit;
@@ -1260,7 +1185,6 @@ int InsaneRebel2::runLevel11() {
totalKills += _rebelKillCounter;
totalMisses += _rebelHitCounter;
- // Bridge puzzle â exit when (phaseState & 0x70) == 0x70
_currentPhase = 3;
resetLevelPhaseState(true);
prevPhaseState = 0;
@@ -1276,21 +1200,18 @@ int InsaneRebel2::runLevel11() {
WaveEndResult waveEnd = processWaveEnd(0x7e, &budget, 0, 0);
int variantIdx = 0; // Tracks variant for randomization threshold
- // Loop until (phaseState & 0x70) == 0x70 (bridge targets destroyed)
while (!waveEnd.shouldStop() && (_rebelPhaseState & 0x70) != 0x70) {
if (_vm->shouldQuit())
return kLevelQuit;
- // Bonus sound: (phaseState & 0xe) == 0xe and previous wasn't
if ((_rebelPhaseState & 0x0e) == 0x0e && (prevPhaseState & 0x0e) != 0x0e) {
}
prevPhaseState = _rebelPhaseState;
- // Randomization: wider range for first few waves
if (variantIdx < 3) {
- variantIdx = _vm->_rnd.getRandomNumber(7); // 0-7
+ variantIdx = _vm->_rnd.getRandomNumber(7);
} else {
- variantIdx = _vm->_rnd.getRandomNumber(2); // 0-2
+ variantIdx = _vm->_rnd.getRandomNumber(2);
}
const char *filename;
@@ -1302,7 +1223,7 @@ int InsaneRebel2::runLevel11() {
case 4: filename = "LEV11/P3/11P03_E.SAN"; break;
case 5: filename = "LEV11/P3/11P03_F.SAN"; break;
case 6: filename = "LEV11/P3/11P03_F.SAN"; break; // duplicate F
- default: filename = "LEV11/P3/11P03_E.SAN"; break; // duplicate E
+ default: filename = "LEV11/P3/11P03_E.SAN"; break;
}
debugC(DEBUG_INSANE, "Level 11 Phase 3a wave - %s (state=0x%x variantIdx=%d)", filename, _rebelPhaseState, variantIdx);
@@ -1326,10 +1247,8 @@ int InsaneRebel2::runLevel11() {
{
bool allBasicKilled = (_rebelPhaseState & 0x0e) >= 0x0e;
if (!allBasicKilled) {
- // Normal bridge drop cinematic
playCinematic("LEV11/11POST3.SAN");
} else {
- // All enemy types killed â bridge dropped successfully
playCinematic("LEV11/11POST3B.SAN");
}
}
@@ -1351,7 +1270,6 @@ int InsaneRebel2::runLevel11() {
if (!playLevelSegment("LEV11/P3/11P03_G.SAN", 0x28))
return kLevelQuit;
- // Only enter wave loop if not all basic types killed already
if ((_rebelPhaseState & 0x0e) < 0x0e) {
int variantIdx = 0;
WaveEndResult waveEnd = processWaveEnd(0x0e, &budget, 0, 0);
@@ -1360,11 +1278,10 @@ int InsaneRebel2::runLevel11() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Wider randomization for first few waves
if (variantIdx < 4) {
- variantIdx = _vm->_rnd.getRandomNumber(8); // 0-8
+ variantIdx = _vm->_rnd.getRandomNumber(8);
} else {
- variantIdx = _vm->_rnd.getRandomNumber(2); // 0-2
+ variantIdx = _vm->_rnd.getRandomNumber(2);
}
const char *filename;
@@ -1372,9 +1289,9 @@ int InsaneRebel2::runLevel11() {
case 0: filename = "LEV11/P3/11P03_G.SAN"; break;
case 1: filename = "LEV11/P3/11P03_H.SAN"; break;
case 2: filename = "LEV11/P3/11P03_I.SAN"; break;
- case 3: filename = "LEV11/P3/11P03_G.SAN"; break; // G again
- case 4: filename = "LEV11/P3/11P03_H.SAN"; break; // H again
- case 5: filename = "LEV11/P3/11P03_I.SAN"; break; // I again
+ case 3: filename = "LEV11/P3/11P03_G.SAN"; break;
+ case 4: filename = "LEV11/P3/11P03_H.SAN"; break;
+ case 5: filename = "LEV11/P3/11P03_I.SAN"; break;
case 6: filename = "LEV11/P3/11P03_J.SAN"; break;
case 7: filename = "LEV11/P3/11P03_K.SAN"; break;
default: filename = "LEV11/P3/11P03_L.SAN"; break;
@@ -1408,23 +1325,14 @@ int InsaneRebel2::runLevel11() {
}
playLevelEnd(11);
- _levelUnlocked[11] = true; // Unlock level 12
+ _levelUnlocked[11] = true;
return kLevelNextLevel;
}
return kLevelQuit;
}
-// "Sewers" - Four phases FPS corridor shooting (Handler 25)
-// Each phase: init video (P05/P06/P07/P08) â first wave â wave loop
-// Phase 1: P1/12P01_X (A,B,C,D) mask=6
-// Phase 2: P2/12P02_X (A,B,C,D,E,F) mask=6
-// Phase 3: P3/12P03_X (A,B,C,D,F) mask=6
-// Phase 4: P4/12P04_X (A,B,C,D,E,F) mask=0xe
-// Closing: 12P09.SAN
-
int InsaneRebel2::runLevel12() {
- // Kill credit budget bases per phase
const int16 kLevel12BudgetBase[4] = { 3, 4, 4, 4 };
playCinematic("LEV12/12CUT.SAN");
@@ -1455,7 +1363,6 @@ int InsaneRebel2::runLevel12() {
{
WaveEndResult waveEnd = processWaveEnd(6, &budget, 0x14, 0);
- // Wave loop: random(2) | (waveSelect & 2) â 0:C, 1:D, 2:A, 3:B
while (!waveEnd.shouldStop()) {
if (_vm->shouldQuit())
return kLevelQuit;
@@ -1509,12 +1416,11 @@ int InsaneRebel2::runLevel12() {
uint16 waveSelect = waveEnd.creditedBits;
- // Variant selection: (waveSelect & 2) controls which set
int variantIdx;
if ((waveSelect & 2) == 0) {
- variantIdx = _vm->_rnd.getRandomNumber(2) + 3; // 3, 4, or 5
+ variantIdx = _vm->_rnd.getRandomNumber(2) + 3;
} else {
- variantIdx = _vm->_rnd.getRandomNumber(2); // 0, 1, or 2
+ variantIdx = _vm->_rnd.getRandomNumber(2);
}
const char *filename;
@@ -1531,7 +1437,6 @@ int InsaneRebel2::runLevel12() {
if (!playLevelSegment(filename, 0x428))
return kLevelQuit;
- // Variants E(2) and F(5) reset threshold to 0
int16 threshold = (variantIdx == 2 || variantIdx == 5) ? 0 : 0x14;
waveEnd = processWaveEnd(6, &budget, threshold, 0);
}
@@ -1567,11 +1472,10 @@ int InsaneRebel2::runLevel12() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Wider randomization for first few waves
if (variantIdx < 4) {
- variantIdx = _vm->_rnd.getRandomNumber(5); // 0-5
+ variantIdx = _vm->_rnd.getRandomNumber(5);
} else {
- variantIdx = _vm->_rnd.getRandomNumber(3); // 0-3
+ variantIdx = _vm->_rnd.getRandomNumber(3);
}
const char *filename;
@@ -1665,21 +1569,14 @@ int InsaneRebel2::runLevel12() {
_rebelKillCounter, _rebelHitCounter, accuracy);
}
- // If all three bonuses found, play special ending (12END_Z.SAN)
- // Deferred until bonus tracking is implemented
-
playLevelEnd(12);
- _levelUnlocked[12] = true; // Unlock level 13
+ _levelUnlocked[12] = true;
return kLevelNextLevel;
}
return kLevelQuit;
}
-// "Escaping the Star Destroyer" - Two-phase flight/escape
-// Phase A: 13PLAY_A.SAN (main flight), transitions to Phase B at maxFrame-10
-// Phase B: 13PLAY_B.SAN (reactor loop, flags 0x468) â plays until
-
int InsaneRebel2::runLevel13() {
playLevelBegin(13);
if (_vm->shouldQuit())
@@ -1695,7 +1592,6 @@ int InsaneRebel2::runLevel13() {
clearBit(0);
- // Phase A: full approach flight; the reactor gauge is tracked but A plays to the end.
resetShieldGauge();
_rebelShieldGateActive = true;
_rebelReactorMode = true;
@@ -1746,8 +1642,6 @@ int InsaneRebel2::runLevel13() {
return kLevelQuit;
}
-// "TIE Attack" - Final space battle (single phase)
-
int InsaneRebel2::runLevel14() {
playLevelBegin(14);
if (_vm->shouldQuit())
@@ -1793,9 +1687,6 @@ int InsaneRebel2::runLevel14() {
return kLevelQuit;
}
-// "Imdaar Alpha" - Final mission (single long phase with level ID switch)
-// This represents a transition from the tunnel section to the core section.
-
int InsaneRebel2::runLevel15() {
playCinematic("LEV15/15CUT.SAN");
if (_vm->shouldQuit())
@@ -1817,8 +1708,6 @@ int InsaneRebel2::runLevel15() {
clearBit(0);
- // At frame 0x21e (542): switches to 0x10 (affects difficulty lookup mid-level)
- // The frame-based switch is handled by IACT opcode 6 in the video data.
_rebelLevelType = 0xf;
if (!playLevelSegment("LEV15/15PLAY.SAN", 0x28))
diff --git a/engines/scumm/smush/rebel/codec_ra2.cpp b/engines/scumm/smush/rebel/codec_ra2.cpp
index 11c4c2dba88..0e81285b329 100644
--- a/engines/scumm/smush/rebel/codec_ra2.cpp
+++ b/engines/scumm/smush/rebel/codec_ra2.cpp
@@ -163,7 +163,7 @@ void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width
while (src + 2 <= lineEnd && x < width) {
int skip = READ_LE_UINT16(src);
src += 2;
- x += skip; // Skip preserves previous frame content
+ x += skip;
if (src + 2 > lineEnd || x >= width)
break;
diff --git a/engines/scumm/smush/rebel/smush_multi_font.cpp b/engines/scumm/smush/rebel/smush_multi_font.cpp
index 3383b60befd..fefa1efb864 100644
--- a/engines/scumm/smush/rebel/smush_multi_font.cpp
+++ b/engines/scumm/smush/rebel/smush_multi_font.cpp
@@ -163,8 +163,6 @@ int SmushMultiFont::getFontHeight() const {
}
int SmushMultiFont::setFont(int id) {
- // This is called by TextRenderer_v7 when it encounters ^fXX escape codes
- // Actually switch the current font
int oldFont = _currentFont;
if (id >= 0 && id < MAX_FONTS) {
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.cpp b/engines/scumm/smush/rebel/smush_player_ra1.cpp
index baa4a5f303e..28ccee399fa 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra1.cpp
@@ -525,7 +525,6 @@ bool SmushPlayerRebel1::handleGameFrameBufferSelect(int codec, int width, int he
bool SmushPlayerRebel1::handleGameDimensionOverride(int codec, int width, int height) {
if (_dst == _specialBuffer) {
- // Keep sub-fullscreen FOBJs inside the fixed RA1 buffer.
_width = kRA1DecodeWidth;
_height = kRA1DecodeHeight;
return true;
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index a0ccfbce972..ab186cddce6 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -217,17 +217,12 @@ void SmushPlayerRebel2::initGameVideoState() {
_ra2UsingGameplaySurface = false;
_smushAudioTable[100] = 0;
- // 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.
+ // Some menu videos inherit the previous SMUSH palette.
setDirtyColors(0, 255);
- // SmushPlayer::init() resets _dst to the virtual screen. Use matching
- // screen dimensions until AHDR/FOBJ selects a larger gameplay surface.
_width = _vm->_screenWidth;
_height = _vm->_screenHeight;
- // Playback flag bit 0x20 controls per-frame clearing.
if (_dst != nullptr) {
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
if ((_curVideoFlags & 0x20) == 0) {
@@ -242,7 +237,6 @@ void SmushPlayerRebel2::releaseGameVideoState() {
_lastFobjDataSize = 0;
_hasFrameFobjForGost = false;
_ra2NativeFrameNeedsClear = false;
- // Keep the stored FOBJ across videos; Level 12 wave segments start with FTCH.
}
bool SmushPlayerRebel2::ra2IsHighResMode() const {
@@ -431,17 +425,12 @@ bool SmushPlayerRebel2::handleGameTextResource(uint32 subType, int32 subSize, Co
bool SmushPlayerRebel2::handleGameTextRendering(const char *str, int fontId, int color,
int pos_x, int pos_y, int left, int top,
int width, int height, TextStyleFlags flg) {
- // RA2 dialogue subtitles. Only render them when subtitles are enabled â this honors
- // both the global "subtitles" setting and the in-game TEXT toggle (which writes
- // the same ConfMan key). Still return true so the base handler treats the chunk as
- // handled. Querying ConfMan per chunk also lets the setting take effect mid-video.
if (ConfMan.getBool("subtitles"))
ra2HandleTextResource(str, fontId, color, pos_x, pos_y, left, top, width, height, flg);
return true;
}
SmushFont *SmushPlayerRebel2::getGameFont(int font) {
- // Font ids map to low/high-res font assets.
const char *ra2FontsLo[] = {
"SYSTM/TALKFONT.NUT",
"SYSTM/SMALFONT.NUT",
@@ -466,8 +455,6 @@ SmushFont *SmushPlayerRebel2::getGameFont(int font) {
return _sf[font];
}
-// RA2 TRS supports many entries, empty entries, and //-prefixed multiline content.
-
static const int RA2_MAX_STRINGS = 800;
static const int RA2_ETRS_HEADER_LENGTH = 16;
@@ -529,10 +516,8 @@ public:
}
data_end -= 2;
- // Skip empty entries
if (data_end <= data_start) { def_start = strchr(def_end + 1, '#'); continue; }
- // Strip leading // prefix
if (data_start[0] == '/' && data_start[1] == '/')
data_start += 2;
if (data_end <= data_start) { def_start = strchr(def_end + 1, '#'); continue; }
@@ -541,7 +526,6 @@ public:
memcpy(value, data_start, data_end - data_start);
value[data_end - data_start] = 0;
- // Preserve newlines for multi-line TRES text (credits, cast lists)
char *line_start = value;
char *line_end;
while ((line_end = strchr(line_start, '\n'))) {
@@ -620,7 +604,6 @@ void SmushPlayerRebel2::adjustGamePalette() {
}
bool SmushPlayerRebel2::shouldLoadAnimHeaderPalette() const {
- // AHDR palette changes are applied after frame processing.
return false;
}
@@ -669,8 +652,6 @@ void SmushPlayerRebel2::ra2HandleDeltaPalette(int32 subSize, Common::SeekableRea
setDirtyColors(0, 255);
}
-// AHDR metadata can describe a backing bitmap, but active low-res gameplay
-// dimensions are selected later by IACT/FOBJ state.
bool SmushPlayerRebel2::handleGameAnimHeader(byte *headerContent) {
if (!_skipPalette && (_curVideoFlags & 0x400) == 0) {
memcpy(_pal, headerContent + 6, sizeof(_pal));
@@ -709,7 +690,6 @@ void SmushPlayerRebel2::handleGameLoad(int32 subSize, Common::SeekableReadStream
handleLoad(subSize, b);
}
-// LOAD chunks stream embedded resource data across multiple frames.
void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b) {
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad()");
@@ -1144,7 +1124,6 @@ bool SmushPlayerRebel2::ra2DecodeCodec(int codec, const uint8 *src, int left, in
return true;
case SMUSH_CODEC_RLE:
if ((_curVideoFlags & 0x100) != 0) {
- // Render flag 0x100 selects the opaque renderer.
smushDecodeRLEOpaque(_dst, src, left, top, width, height, pitch, dataSize);
return true;
}
@@ -1215,7 +1194,6 @@ void SmushPlayerRebel2::ra2HandleGost(int32 subSize, Common::SeekableReadStream
priorityFlags = 0x6000;
}
- // GOST coordinates are relative to the cached FOBJ header position.
int left = _lastFobjLeft + ghostX;
int top = _lastFobjTop + ghostY;
@@ -1224,15 +1202,12 @@ void SmushPlayerRebel2::ra2HandleGost(int32 subSize, Common::SeekableReadStream
_lastFobjLeft, _lastFobjTop, left, top,
_lastFobjWidth, _lastFobjHeight, _lastFobjCodec);
- // Priority bits are not modeled in the SMUSH decoders.
ra2PrepareFrameObjectSurface(left, top, _lastFobjWidth, _lastFobjHeight);
decodeFrameObject(_lastFobjCodec, _lastFobjData, left, top,
_lastFobjWidth, _lastFobjHeight, _lastFobjDataSize);
}
void SmushPlayerRebel2::handleGameParseNextFrame() {
- // Call processDispatches directly since RA2 has no iMUSE.
- // This is the mixer cadence; SAUD opcodes provide per-block source rates.
processDispatches(_smushAudioSampleRate / 12);
}
@@ -1252,8 +1227,6 @@ bool SmushPlayerRebel2::handleGameDimensionOverride(int codec, int width, int he
if (_insane != nullptr) {
InsaneRebel2 *rebel2 = static_cast<InsaneRebel2 *>(_insane);
if (rebel2->getHandler() != 0) {
- // RA2 gameplay preserves the dimensions selected by the
- // current frame target instead of the individual FOBJ.
return true;
}
}
@@ -1262,8 +1235,6 @@ bool SmushPlayerRebel2::handleGameDimensionOverride(int codec, int width, int he
return true;
}
- // Handler-zero videos are menus/cinematics. Let the base player restore
- // normal screen dimensions so menu overlays do not inherit gameplay pitch.
return false;
}
@@ -1318,8 +1289,6 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
if (srcSkipY)
*srcSkipY = 0;
} else if (isRebel2FullFrameDeltaCodec(codec)) {
- // Delta streams are full-frame data, so keep clipped scanlines in the
- // destination offset instead of advancing the compressed source.
_ra2FrameSourceSkipY = sourceSkipY;
if (srcSkipY)
*srcSkipY = 0;
@@ -1333,8 +1302,6 @@ bool SmushPlayerRebel2::handleGameCodecDecode(int codec, const uint8 *src, int l
if (isRebel2FullFrameDeltaCodec(codec))
return ra2DecodePlacedDeltaCodec(codec, src, left, top, width, height, pitch, dataSize);
- // Codec 23 translucent surfaces: parm2 0x100 prefixes a remap table;
- // larger values reuse the last one.
if (codec == SMUSH_CODEC_SKIP_RLE && parm2 >= 0x100) {
if (parm2 == 0x100 && dataSize >= 256) {
memcpy(_ra2SkipRemapTable, src, 256);
@@ -1350,7 +1317,6 @@ bool SmushPlayerRebel2::handleGameCodecDecode(int codec, const uint8 *src, int l
return ra2DecodeCodec(codec, src, left, top, width, height, pitch, dataSize);
}
-// Forward the FOBJ parm2 word kept by RA2 codec 23.
void SmushPlayerRebel2::handleFrameObject(int32 subSize, Common::SeekableReadStream &b) {
assert(subSize >= 14);
if (_skipNext) {
@@ -1425,10 +1391,6 @@ void SmushPlayerRebel2::handleGameFrameStart() {
}
}
- // Clear the target before decoding unless playback flags contain 0x20.
- // Codec 21 frames in levels like
- // LEV05/05PLAY.SAN only contain non-zero literals, so stale skipped pixels
- // must not survive from the previous frame.
if ((_curVideoFlags & 0x20) == 0 && _dst != nullptr) {
ra2ClearCurrentTarget();
if (!ra2IsHighResMode() || isRebel2GameplayActive(_insane) || _dst != _vm->_virtscr[kMainVirtScreen].getPixels(0, 0))
Commit: b62c478da5160ab62e2b34a0340422e7751a12bf
https://github.com/scummvm/scummvm/commit/b62c478da5160ab62e2b34a0340422e7751a12bf
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-22T16:21:10+02:00
Commit Message:
SCUMM: RA2: implement end of level messages
Changed paths:
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/insane/rebel2/runlevels.cpp
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index cc22446f651..48c4b69ac29 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -21,6 +21,7 @@
#include "common/events.h"
#include "common/system.h"
+#include "common/util.h"
#include "graphics/cursorman.h"
@@ -36,6 +37,34 @@ static const int kRebel2GameplayAimCenterX = 160;
static const int kRebel2GameplayAimCenterY = 100;
static const uint32 kRebel2GameplayMouseSettleMs = 1000;
+struct Rebel2LevelEndParams {
+ int titleStartBeforeEnd;
+ int titleEndBeforeEnd;
+ int accLow;
+ int accHigh;
+ int errLow;
+ int errHigh;
+};
+
+static const Rebel2LevelEndParams kRebel2LevelEndParams[16] = {
+ { 0, 0, -1, -1, -1, -1 },
+ { 120, 10, 96, 100, -1, -1 },
+ { 120, 10, 80, 94, -1, -1 },
+ { 90, 5, 56, 72, 1, 3 },
+ { 120, 10, 42, 48, 2, 4 },
+ { 120, 10, 96, 100, -1, -1 },
+ { 545,435, 60, 76, 1, 4 },
+ { 115, 10, -1, -1, 1, 3 },
+ { 120, 10, 90, 96, -1, -1 },
+ { 100, 1, 36, 48, 2, 4 },
+ { 100, 10, 28, 36, 1, 3 },
+ { 100, 10, 70, 78, -1, -1 },
+ { 120, 10, 28, 36, -1, -1 },
+ { 150, 10, 86, 90, 3, 5 },
+ { 75, 10, 96, 100, -1, -1 },
+ { 100, 10, 60, 68, 1, 3 }
+};
+
static void purgeRebel2GameplayInputEvents(Common::EventManager *eventMan) {
if (!eventMan)
return;
@@ -102,8 +131,8 @@ void InsaneRebel2::runGame() {
int result = runLevel(selectedLevel);
if (result == kLevelNextLevel) {
- updatePilotProgress(selectedLevel - 1,
- _playerScore, _playerLives, _playerDamage);
+ updatePilotProgress(selectedLevel,
+ _playerScore, _playerLives, 0, _playerRating);
selectedLevel++;
if (selectedLevel > 15) {
playEndingSequence();
@@ -232,13 +261,106 @@ void InsaneRebel2::playLevelBegin(int levelId) {
}
}
+int InsaneRebel2::calculateLevelEndRating(int accuracy, int accLow, int accHigh,
+ int flightErrors, int errLow, int errHigh, bool skillBonus) const {
+ int accuracyGrade = -1;
+ if (accuracy >= 0) {
+ if (accuracy < accHigh) {
+ if (accuracy < (accLow + accHigh) / 2)
+ accuracyGrade = (accuracy < accLow) ? 0 : 1;
+ else
+ accuracyGrade = 2;
+ } else {
+ accuracyGrade = 3;
+ }
+ }
+
+ int errorGrade = -1;
+ if (flightErrors >= 0) {
+ if (errHigh < flightErrors) {
+ if ((errLow + errHigh) / 2 < flightErrors)
+ errorGrade = (flightErrors <= errLow) ? 1 : 0;
+ else
+ errorGrade = 2;
+ } else {
+ errorGrade = 3;
+ }
+ }
+
+ int rating = 0;
+ if (accuracy >= 0 && flightErrors >= 0) {
+ rating = (accuracyGrade + errorGrade + 1) / 2;
+ } else if (accuracy >= 0) {
+ rating = accuracyGrade;
+ } else if (flightErrors >= 0) {
+ rating = errorGrade;
+ }
+
+ if (skillBonus)
+ rating++;
+
+ return MAX(0, rating);
+}
+
+void InsaneRebel2::prepareLevelEndStats(int levelId, int accuracy, int flightErrors, bool skillBonus) {
+ if (levelId < 1 || levelId > 15) {
+ _levelEndStats.active = false;
+ return;
+ }
+
+ const Rebel2LevelEndParams &p = kRebel2LevelEndParams[levelId];
+ const bool hasAccuracy = (accuracy >= 0 && p.accLow >= 0 && p.accHigh >= 0);
+ const bool hasFlightErrors = (flightErrors >= 0 && p.errLow >= 0 && p.errHigh >= 0);
+ const int ratingAward = calculateLevelEndRating(
+ hasAccuracy ? accuracy : -1, p.accLow, p.accHigh,
+ hasFlightErrors ? flightErrors : -1, p.errLow, p.errHigh,
+ skillBonus);
+
+ LevelDifficultyParams difficultyParams = getDifficultyParams();
+ if (levelId == 15)
+ difficultyParams = kDifficultyTable[CLIP(_difficulty, 0, 5)][16];
+
+ const int bonus = (difficultyParams.specialPoints > 0) ?
+ difficultyParams.specialPoints * ratingAward : 0;
+ const int levelPoints = MAX(0, (int)difficultyParams.levelPoints);
+
+ _levelEndStats.active = true;
+ _levelEndStats.levelId = levelId;
+ _levelEndStats.textX = 0xa0;
+ _levelEndStats.textY = (levelId == 7) ? 0xb : 10;
+ _levelEndStats.titleStartBeforeEnd = p.titleStartBeforeEnd;
+ _levelEndStats.titleEndBeforeEnd = p.titleEndBeforeEnd;
+ _levelEndStats.hasAccuracy = hasAccuracy;
+ _levelEndStats.hasFlightErrors = hasFlightErrors;
+ _levelEndStats.skillBonus = skillBonus;
+ _levelEndStats.accuracy = hasAccuracy ? accuracy : -1;
+ _levelEndStats.flightErrors = hasFlightErrors ? flightErrors : -1;
+ _levelEndStats.bonus = bonus;
+ _levelEndStats.oldRating = _playerRating;
+
+ addScore(bonus);
+ addScore(levelPoints);
+ _playerRating += ratingAward;
+
+ _levelEndStats.finalScore = _playerScore;
+ _levelEndStats.newRating = _playerRating;
+
+ if (_activePilot >= 0 && _activePilot < _numPilots)
+ insertRanking(_pilots[_activePilot].name, _playerScore, _playerRating, _difficulty, levelId);
+}
+
void InsaneRebel2::playLevelEnd(int levelId) {
+ playLevelEnd(levelId, -1, -1, false);
+}
+
+void InsaneRebel2::playLevelEnd(int levelId, int accuracy, int flightErrors, bool skillBonus) {
restoreDamageFlashPalette();
resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
- _rebelStatusBarSprite = 0; // No status bar during end cinematic
+ _rebelStatusBarSprite = 0;
+ prepareLevelEndStats(levelId, accuracy, flightErrors, skillBonus);
Common::String dir = getLevelDir(levelId);
Common::String prefix = getLevelPrefix(levelId);
@@ -249,6 +371,8 @@ void InsaneRebel2::playLevelEnd(int levelId) {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
splayer->setCurVideoFlags(0x28);
splayer->play(filename.c_str(), 15);
+
+ _levelEndStats.active = false;
}
void InsaneRebel2::playLevelRetry(int levelId) {
@@ -395,9 +519,16 @@ int InsaneRebel2::runLevel(int levelId) {
CursorMan.showMouse(false);
g_system->lockMouse(true);
- _playerLives = 3;
+ if (_activePilot >= 0 && _activePilot < _numPilots && _pilots[_activePilot].damage[levelId - 1] < 0xFF) {
+ _playerLives = _pilots[_activePilot].lives[levelId - 1];
+ _playerScore = _pilots[_activePilot].score[levelId - 1];
+ _playerRating = _pilots[_activePilot].rating[levelId - 1];
+ } else {
+ _playerLives = 3;
+ _playerScore = 0;
+ _playerRating = 0;
+ }
_playerShield = 255;
- _playerScore = 0;
_playerDamage = 0;
resetDamageFlash();
_damageHighFlashCounter = 0;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 324d58793a5..9ce4d44b269 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -198,6 +198,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_playerShield = 255;
_playerLives = 3;
_playerScore = 0;
+ _playerRating = 0;
_noDamage = false;
_viewX = 0;
_viewY = 0;
@@ -218,6 +219,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_textOverlayY = 0;
_textOverlayFadeIn = 0;
_textOverlayFadeOut = 0;
+ memset(&_levelEndStats, 0, sizeof(_levelEndStats));
_rebelOp6Initialized = false;
_rebelHitCounter = 0;
@@ -1481,7 +1483,7 @@ void InsaneRebel2::copyPilot(int srcIndex) {
_numPilots++;
}
-void InsaneRebel2::updatePilotProgress(int levelIndex, int32 score, int32 lives, int32 damage) {
+void InsaneRebel2::updatePilotProgress(int levelIndex, int32 score, int32 lives, int32 damage, int32 rating) {
if (_activePilot < 0 || _activePilot >= _numPilots)
return;
if (levelIndex < 0 || levelIndex >= kNumLevels)
@@ -1491,14 +1493,7 @@ void InsaneRebel2::updatePilotProgress(int levelIndex, int32 score, int32 lives,
pilot.score[levelIndex] = score;
pilot.lives[levelIndex] = lives;
pilot.damage[levelIndex] = damage;
-
- if (damage < 0xFF && levelIndex + 1 < kNumLevels) {
- if (pilot.damage[levelIndex + 1] == 0xFF) {
- pilot.score[levelIndex + 1] = 0;
- pilot.lives[levelIndex + 1] = 4;
- pilot.damage[levelIndex + 1] = 0;
- }
- }
+ pilot.rating[levelIndex] = rating;
savePilots();
}
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index ca3a8d0767e..236bd37bcde 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -217,7 +217,7 @@ public:
void deletePilot(int index);
void copyPilot(int srcIndex);
- void updatePilotProgress(int levelIndex, int32 score, int32 lives, int32 damage);
+ void updatePilotProgress(int levelIndex, int32 score, int32 lives, int32 damage, int32 rating);
enum LevelSelectResult {
kLevelSelectBack = 0,
@@ -260,6 +260,7 @@ public:
int runLevel(int levelId);
void playLevelBegin(int levelId);
void playLevelEnd(int levelId);
+ void playLevelEnd(int levelId, int accuracy, int flightErrors, bool skillBonus);
void playLevelRetry(int levelId);
void playLevelGameOver(int levelId);
void playEndingSequence();
@@ -330,6 +331,29 @@ public:
int _textOverlayFadeOut;
void renderTextOverlay(byte *renderBitmap, int pitch, int width, int height, int curFrame);
+ void renderLevelEndStatsOverlay(byte *renderBitmap, int pitch, int width, int height, int32 curFrame, int32 maxFrame);
+ void drawLevelEndTextBlock(byte *renderBitmap, const char *text, int centerX, int y);
+ void prepareLevelEndStats(int levelId, int accuracy, int flightErrors, bool skillBonus);
+ int calculateLevelEndRating(int accuracy, int accLow, int accHigh, int flightErrors, int errLow, int errHigh, bool skillBonus) const;
+
+ struct LevelEndStatsOverlay {
+ bool active;
+ int levelId;
+ int textX;
+ int textY;
+ int titleStartBeforeEnd;
+ int titleEndBeforeEnd;
+ bool hasAccuracy;
+ bool hasFlightErrors;
+ bool skillBonus;
+ int accuracy;
+ int flightErrors;
+ int bonus;
+ int finalScore;
+ int oldRating;
+ int newRating;
+ };
+ LevelEndStatsOverlay _levelEndStats;
void playLevelDeathVariant(int levelId, int phase, int frame);
void playLevelRetryVariant(int levelId, int phase);
@@ -427,7 +451,7 @@ public:
void updatePostRenderDeath();
void renderPostRenderMenuCursor(byte *renderBitmap, int pitch, int width, int height);
bool handlePostRenderMenuModes(byte *renderBitmap, int pitch, int width, int height, bool introPlaying);
- bool handlePostRenderIntro(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
+ bool handlePostRenderIntro(byte *renderBitmap, int pitch, int width, int height, int32 curFrame, int32 maxFrame);
void renderGameplayPostFrame(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY, int32 curFrame);
void updateGameplayDamageEffects(byte *renderBitmap, int pitch, int width, int height);
@@ -620,6 +644,7 @@ public:
int16 _playerShield;
int16 _playerLives;
int32 _playerScore;
+ int32 _playerRating;
int _viewX;
int _viewY;
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index c3ec65bd310..324892610cb 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -72,6 +72,57 @@ bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont, int &
return fontId == -2;
}
+static Common::String getRebel2VisibleTextPrefix(const char *text, int visibleChars) {
+ Common::String out;
+ if (!text || visibleChars <= 0)
+ return out;
+
+ int visible = 0;
+ const char *s = text;
+ while (*s && visible < visibleChars) {
+ if (*s == '^' && (s[1] == 'f' || s[1] == 'c')) {
+ out += *s++;
+ out += *s++;
+ while (*s >= '0' && *s <= '9')
+ out += *s++;
+ continue;
+ }
+ if (*s == '^' && s[1] == 'l') {
+ out += *s++;
+ out += *s++;
+ continue;
+ }
+
+ out += *s;
+ if (*s != '\n' && *s != '\r')
+ visible++;
+ s++;
+ }
+
+ return out;
+}
+
+static const char *getRebel2LevelEndFallbackString(int id) {
+ switch (id) {
+ case 190:
+ return "^f02^c244Chapter Complete\n^f01^c244Password: %s";
+ case 191:
+ return "^f01^c001Target Accuracy %hd%%\nBonus %ld\n\nScore %ld\n^f00%s";
+ case 192:
+ return "^f01^c001Flight Errors %hd\nBonus %ld\n\nScore %ld\n^f00%s";
+ case 193:
+ return "^f01^c001Flight Errors %hd\n^f01^c001Target Accuracy %hd%%\nTotal Bonus %ld\n\nScore %ld\n^f00%s";
+ case 194:
+ return "^f01^c001Target Accuracy %hd%%\nSkill Bonus Awarded\nTotal Bonus %ld\n\nScore %ld\n^f00%s";
+ case 195:
+ return "^f01^c001Flight Errors %hd\nSkill Bonus Awarded\nTotal Bonus %ld\n\nScore %ld\n^f00%s";
+ case 196:
+ return "^f01^c001Flight Errors %hd\n^f01^c001Target Accuracy %hd%%\nSkill Bonus Awarded\nTotal Bonus %ld\n\nScore %ld\n^f00%s";
+ default:
+ return "";
+ }
+}
+
const char *getHandler8PovOverlayString(int id) {
switch (id) {
case 200:
@@ -2182,7 +2233,7 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
return false;
}
-bool InsaneRebel2::handlePostRenderIntro(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
+bool InsaneRebel2::handlePostRenderIntro(byte *renderBitmap, int pitch, int width, int height, int32 curFrame, int32 maxFrame) {
if (_rebelHandler == 0) {
CursorMan.showMouse(false);
@@ -2194,6 +2245,8 @@ bool InsaneRebel2::handlePostRenderIntro(byte *renderBitmap, int pitch, int widt
if (_textOverlayActive)
renderTextOverlay(renderBitmap, pitch, width, height, curFrame);
+ if (_levelEndStats.active)
+ renderLevelEndStatsOverlay(renderBitmap, pitch, width, height, curFrame, maxFrame);
return true;
}
@@ -2363,7 +2416,7 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
if (handlePostRenderMenuModes(renderBitmap, pitch, width, height, introPlaying))
return;
- if (handlePostRenderIntro(renderBitmap, pitch, width, height, curFrame))
+ if (handlePostRenderIntro(renderBitmap, pitch, width, height, curFrame, maxFrame))
return;
renderGameplayPostFrame(renderBitmap, pitch, width, height, videoWidth, videoHeight, statusBarY, curFrame);
}
@@ -2486,6 +2539,116 @@ void InsaneRebel2::updateDamageEffect(byte *renderBitmap, int pitch, int width,
updateDamageFlashPalette();
}
+void InsaneRebel2::drawLevelEndTextBlock(byte *renderBitmap, const char *text, int centerX, int y) {
+ if (!renderBitmap || !text)
+ return;
+
+ const int lineStep = isHiRes() ? 20 : 10;
+ Common::String line;
+ const char *s = text;
+ while (true) {
+ if (*s == '\0' || *s == '\n' || *s == '\r') {
+ if (!line.empty())
+ drawMenuStringCentered(renderBitmap, line.c_str(), centerX, y);
+ y += lineStep;
+ line.clear();
+ if (*s == '\0')
+ break;
+ if (*s == '\r' && s[1] == '\n')
+ s++;
+ s++;
+ continue;
+ }
+
+ line += *s++;
+ }
+}
+
+void InsaneRebel2::renderLevelEndStatsOverlay(byte *renderBitmap, int pitch, int width, int height,
+ int32 curFrame, int32 maxFrame) {
+ (void)pitch;
+ (void)width;
+ (void)height;
+
+ if (!_levelEndStats.active)
+ return;
+
+ SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
+ if (!splayer)
+ return;
+
+ int32 titleStart = 0;
+ int32 titleEnd = 0x7fffffff;
+ if (maxFrame > 0) {
+ titleStart = maxFrame - _levelEndStats.titleStartBeforeEnd;
+ titleEnd = maxFrame - _levelEndStats.titleEndBeforeEnd;
+ }
+
+ if (curFrame < titleStart || curFrame >= titleEnd)
+ return;
+
+ const int scale = isHiRes() ? 2 : 1;
+ const int centerX = _levelEndStats.textX * scale;
+ const int titleY = _levelEndStats.textY * scale;
+
+ const char *titleFmt = splayer->getString(190);
+ if (!titleFmt || !titleFmt[0])
+ titleFmt = getRebel2LevelEndFallbackString(190);
+
+ Common::String password = getChapterPassword(_levelEndStats.levelId, _difficulty);
+ Common::String titleText = Common::String::format(titleFmt, password.c_str());
+ int visibleChars = curFrame + 10 - titleStart;
+ if (visibleChars > 0xbe)
+ visibleChars = 0xbe;
+ Common::String visibleTitle = getRebel2VisibleTextPrefix(titleText.c_str(), visibleChars);
+ drawLevelEndTextBlock(renderBitmap, visibleTitle.c_str(), centerX, titleY);
+
+ if (curFrame <= titleEnd - 0x32)
+ return;
+
+ int displayBonus = _levelEndStats.bonus;
+ for (int32 frame = titleEnd - 0x23 + 1; frame <= curFrame && displayBonus > 0; frame++) {
+ const int reduced = displayBonus - 0xaf;
+ const int halved = displayBonus >> 1;
+ displayBonus = (reduced <= halved) ? halved : reduced;
+ }
+
+ const int displayScore = _levelEndStats.finalScore - displayBonus;
+ const int rank = (curFrame >= titleEnd - 0x19) ? _levelEndStats.newRating : _levelEndStats.oldRating;
+ Common::String rankStr = getRankString(rank);
+
+ int stringId;
+ if (_levelEndStats.hasAccuracy && _levelEndStats.hasFlightErrors)
+ stringId = _levelEndStats.skillBonus ? 196 : 193;
+ else if (_levelEndStats.hasAccuracy)
+ stringId = _levelEndStats.skillBonus ? 194 : 191;
+ else if (_levelEndStats.hasFlightErrors)
+ stringId = _levelEndStats.skillBonus ? 195 : 192;
+ else
+ return;
+
+ const char *statsFmt = splayer->getString(stringId);
+ if (!statsFmt || !statsFmt[0])
+ statsFmt = getRebel2LevelEndFallbackString(stringId);
+
+ Common::String statsText;
+ if (_levelEndStats.hasAccuracy && _levelEndStats.hasFlightErrors) {
+ statsText = Common::String::format(statsFmt,
+ (short)_levelEndStats.flightErrors, (short)_levelEndStats.accuracy,
+ (long)displayBonus, (long)displayScore, rankStr.c_str());
+ } else if (_levelEndStats.hasAccuracy) {
+ statsText = Common::String::format(statsFmt,
+ (short)_levelEndStats.accuracy, (long)displayBonus,
+ (long)displayScore, rankStr.c_str());
+ } else {
+ statsText = Common::String::format(statsFmt,
+ (short)_levelEndStats.flightErrors, (long)displayBonus,
+ (long)displayScore, rankStr.c_str());
+ }
+
+ drawLevelEndTextBlock(renderBitmap, statsText.c_str(), centerX, titleY + 0x21 * scale);
+}
+
void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, int height, int curFrame) {
if (curFrame < _textOverlayFadeIn || curFrame >= _textOverlayFadeOut)
return;
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index ee3cbf09506..2fa6af8f705 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -41,13 +41,16 @@ int InsaneRebel2::runLevel1() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
if (!playLevelSegment("LEV01/01P01.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
+ int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 1 completed!");
- playLevelEnd(1);
+ playLevelEnd(1, accuracy, -1, false);
_levelUnlocked[1] = true;
return kLevelNextLevel;
}
@@ -569,9 +572,9 @@ int InsaneRebel2::runLevel2() {
int accuracy = calculateAccuracy(totalKills, totalMisses);
debugC(DEBUG_INSANE, "Level 2 completed! kills=%d misses=%d accuracy=%d%% bonus=%d",
totalKills, totalMisses, accuracy, bonusCount);
+ playLevelEnd(2, accuracy, -1, bonusCount > 2);
}
- playLevelEnd(2);
_levelUnlocked[2] = true;
return kLevelNextLevel;
}
@@ -581,6 +584,7 @@ int InsaneRebel2::runLevel2() {
int InsaneRebel2::runLevel3() {
int phase1Score = 0;
+ int phase1FlightErrors = 0;
playLevelBegin(3);
if (_vm->shouldQuit())
@@ -596,6 +600,8 @@ int InsaneRebel2::runLevel3() {
clearBit(0);
resetHandler7FlightState();
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
debugC(DEBUG_INSANE, "Level 3 Phase 1");
if (!playLevelSegment("LEV03/03PLAY1.SAN", 0x28))
@@ -603,6 +609,7 @@ int InsaneRebel2::runLevel3() {
if (_playerShield > 0) {
phase1Score = _playerScore;
+ phase1FlightErrors = _rebelHitCounter;
break;
}
@@ -642,14 +649,18 @@ int InsaneRebel2::runLevel3() {
clearBit(0);
resetHandler7FlightState();
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
debugC(DEBUG_INSANE, "Level 3 Phase 2");
if (!playLevelSegment("LEV03/03PLAY2.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
+ int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
+ int flightErrors = phase1FlightErrors + _rebelHitCounter;
debugC(DEBUG_INSANE, "Level 3 completed!");
- playLevelEnd(3);
+ playLevelEnd(3, accuracy, flightErrors, false);
_levelUnlocked[3] = true;
return kLevelNextLevel;
}
@@ -688,14 +699,17 @@ int InsaneRebel2::runLevel4() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
debugC(DEBUG_INSANE, "Level 4 gameplay");
if (!playLevelSegment("LEV04/04PLAY.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
+ int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 4 completed!");
- playLevelEnd(4);
+ playLevelEnd(4, accuracy, _rebelHitCounter, false);
_levelUnlocked[4] = true; // Unlock level 5
return kLevelNextLevel;
}
@@ -731,14 +745,17 @@ int InsaneRebel2::runLevel5() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
debugC(DEBUG_INSANE, "Level 5 gameplay");
if (!playLevelSegment("LEV05/05PLAY.SAN", 0x08))
return kLevelQuit;
if (_playerShield > 0) {
+ int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 5 completed!");
- playLevelEnd(5);
+ playLevelEnd(5, accuracy, -1, false);
_levelUnlocked[5] = true; // Unlock level 6
return kLevelNextLevel;
}
@@ -764,6 +781,7 @@ int InsaneRebel2::runLevel5() {
int InsaneRebel2::runLevel6() {
int phase1Score = 0;
+ int phase1FlightErrors = 0;
playLevelBegin(6);
if (_vm->shouldQuit())
@@ -777,6 +795,8 @@ int InsaneRebel2::runLevel6() {
_rebelLevelType = 5;
_currentPhase = 1;
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
debugC(DEBUG_INSANE, "Level 6 Phase 1 (shield attack run)");
resetShieldGauge();
@@ -815,6 +835,7 @@ int InsaneRebel2::runLevel6() {
}
phase1Score = _playerScore;
+ phase1FlightErrors = _rebelHitCounter;
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
@@ -827,14 +848,18 @@ int InsaneRebel2::runLevel6() {
_playerScore = phase1Score;
clearBit(0);
resetExplosions();
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
debugC(DEBUG_INSANE, "Level 6 Phase 2");
if (!playLevelSegment("LEV06/06PLAY2.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
+ int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
+ int flightErrors = phase1FlightErrors + _rebelHitCounter;
debugC(DEBUG_INSANE, "Level 6 completed!");
- playLevelEnd(6);
+ playLevelEnd(6, accuracy, flightErrors, false);
_levelUnlocked[6] = true;
return kLevelNextLevel;
}
@@ -882,13 +907,15 @@ int InsaneRebel2::runLevel7() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
if (!playLevelSegment("LEV07/07PLAY.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
debugC(DEBUG_INSANE, "Level 7 completed!");
- playLevelEnd(7);
+ playLevelEnd(7, -1, _rebelHitCounter, !reachedFork);
_levelUnlocked[7] = true;
return kLevelNextLevel;
}
@@ -930,6 +957,8 @@ int InsaneRebel2::runLevel8() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
if (!playLevelSegment("LEV08/08PLAY.SAN", 0x08))
return kLevelQuit;
@@ -937,7 +966,7 @@ int InsaneRebel2::runLevel8() {
if (_playerShield > 0) {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 8 completed! accuracy=%d%%", accuracy);
- playLevelEnd(8);
+ playLevelEnd(8, accuracy, -1, false);
_levelUnlocked[8] = true;
return kLevelNextLevel;
}
@@ -975,6 +1004,8 @@ int InsaneRebel2::runLevel9() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
_rebelPhaseState = 0xfffffffe;
@@ -984,7 +1015,7 @@ int InsaneRebel2::runLevel9() {
if (_playerShield > 0) {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 9 completed! accuracy=%d%%", accuracy);
- playLevelEnd(9);
+ playLevelEnd(9, accuracy, _rebelHitCounter, false);
_levelUnlocked[9] = true;
return kLevelNextLevel;
}
@@ -1026,6 +1057,8 @@ int InsaneRebel2::runLevel10() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
if (!playLevelSegment("LEV10/10PLAY.SAN", 0x28))
return kLevelQuit;
@@ -1033,7 +1066,7 @@ int InsaneRebel2::runLevel10() {
if (_playerShield > 0) {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 10 completed! accuracy=%d%%", accuracy);
- playLevelEnd(10);
+ playLevelEnd(10, accuracy, _rebelHitCounter, false);
_levelUnlocked[10] = true;
return kLevelNextLevel;
}
@@ -1322,9 +1355,9 @@ int InsaneRebel2::runLevel11() {
int accuracy = calculateAccuracy(totalKills, totalMisses);
debugC(DEBUG_INSANE, "Level 11 completed! kills=%d misses=%d accuracy=%d%%",
totalKills, totalMisses, accuracy);
+ playLevelEnd(11, accuracy, -1, false);
}
- playLevelEnd(11);
_levelUnlocked[11] = true;
return kLevelNextLevel;
}
@@ -1567,9 +1600,9 @@ int InsaneRebel2::runLevel12() {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 12 completed! kills=%d misses=%d accuracy=%d%%",
_rebelKillCounter, _rebelHitCounter, accuracy);
+ playLevelEnd(12, accuracy, -1, false);
}
- playLevelEnd(12);
_levelUnlocked[12] = true;
return kLevelNextLevel;
}
@@ -1591,6 +1624,8 @@ int InsaneRebel2::runLevel13() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
resetShieldGauge();
_rebelShieldGateActive = true;
@@ -1618,7 +1653,7 @@ int InsaneRebel2::runLevel13() {
if (_playerShield > 0 && _rebelShieldDestroyed) {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 13 completed! accuracy=%d%%", accuracy);
- playLevelEnd(13);
+ playLevelEnd(13, accuracy, _rebelHitCounter, false);
_levelUnlocked[13] = true;
return kLevelNextLevel;
}
@@ -1656,6 +1691,8 @@ int InsaneRebel2::runLevel14() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
if (!playLevelSegment("LEV14/14PLAY.SAN", 0x28))
return kLevelQuit;
@@ -1663,7 +1700,7 @@ int InsaneRebel2::runLevel14() {
if (_playerShield > 0) {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 14 completed! accuracy=%d%%", accuracy);
- playLevelEnd(14);
+ playLevelEnd(14, accuracy, -1, false);
_levelUnlocked[14] = true;
return kLevelNextLevel;
}
@@ -1707,6 +1744,8 @@ int InsaneRebel2::runLevel15() {
resetExplosions();
clearBit(0);
+ _rebelKillCounter = 0;
+ _rebelHitCounter = 0;
_rebelLevelType = 0xf;
@@ -1716,7 +1755,9 @@ int InsaneRebel2::runLevel15() {
if (_playerShield > 0) {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 15 completed! accuracy=%d%%", accuracy);
- playLevelEnd(15);
+ _currentPhase = 2;
+ _rebelLevelType = 0x10;
+ playLevelEnd(15, accuracy, _rebelHitCounter, false);
_levelUnlocked[15] = true;
return kLevelNextLevel;
}
Commit: ddb8a0dd9a498a85bb3893e65875ee0cd65607c3
https://github.com/scummvm/scummvm/commit/ddb8a0dd9a498a85bb3893e65875ee0cd65607c3
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-22T16:21:10+02:00
Commit Message:
SCUMM: RA2: added missing ship shadow for L3
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/smush/rebel/codec_ra2.cpp
engines/scumm/smush/rebel/codec_ra2.h
engines/scumm/smush/rebel/font_rebel2.cpp
engines/scumm/smush/rebel/font_rebel2.h
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 be6bb0e64ee..c4fcf762791 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -1223,7 +1223,13 @@ void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStr
}
bool InsaneRebel2::loadOpcode8Handler7FlySprites(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
- bool isHandler7FLY = (_rebelHandler == 7 && (par4 == 1 || par4 == 2 || par4 == 3 || par4 == 11));
+ const bool fly004ForResolution = (!isHiRes() && par4 == 10) || (isHiRes() && par4 == 11);
+ if (_rebelHandler == 7 && (par4 == 10 || par4 == 11) && !fly004ForResolution) {
+ b.seek(startPos);
+ return true;
+ }
+
+ bool isHandler7FLY = (_rebelHandler == 7 && (par4 == 1 || par4 == 2 || par4 == 3 || fly004ForResolution));
if (isHandler7FLY && remaining >= 14) {
if (loadHandler7FlySprites(b, remaining, par4)) {
b.seek(startPos);
@@ -1701,7 +1707,8 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
delete _flyLaserSprite;
_flyLaserSprite = newNut;
break;
- case 11: // FLY004 - High-res alternative
+ case 10:
+ case 11:
delete _flyHiResSprite;
_flyHiResSprite = newNut;
break;
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 236bd37bcde..804f06b2c00 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -452,6 +452,7 @@ public:
void renderPostRenderMenuCursor(byte *renderBitmap, int pitch, int width, int height);
bool handlePostRenderMenuModes(byte *renderBitmap, int pitch, int width, int height, bool introPlaying);
bool handlePostRenderIntro(byte *renderBitmap, int pitch, int width, int height, int32 curFrame, int32 maxFrame);
+ void prepareHandler7Viewport(byte *renderBitmap, int pitch, int width, int height);
void renderGameplayPostFrame(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY, int32 curFrame);
void updateGameplayDamageEffects(byte *renderBitmap, int pitch, int width, int height);
@@ -478,7 +479,6 @@ public:
void renderCrosshair(byte *renderBitmap, int pitch, int width, int height);
void renderHandler8MonitorEffect(byte *renderBitmap, int pitch, int width, int height);
void renderHandler8PovOverlay(byte *renderBitmap, int pitch, int width, int height);
- Common::Point getHandler7ShipDrawPoint();
Common::Point getHandler7ProjectedPointFor(int16 x, int16 y);
Common::Point getHandler7ProjectedPoint();
Common::Point getHandler7ShotTargetPoint();
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 324892610cb..9f4b8aaf6bb 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -595,17 +595,6 @@ void InsaneRebel2::spawnHandler25Shot(int x, int y) {
}
}
-Common::Point InsaneRebel2::getHandler7ShipDrawPoint() {
- Common::Point projected = getHandler7ProjectedPoint();
-
- if (!_flyShipSprite || _flyShipSprite->getNumChars() <= 0)
- return Common::Point(projected.x - 0xd4, projected.y - 0x82);
-
- int spriteIndex = CLIP<int>(_shipDirectionIndex, 0, _flyShipSprite->getNumChars() - 1);
- return Common::Point(projected.x - 0xd4 + _flyShipSprite->getCharXOffset(spriteIndex),
- projected.y - 0x82 + _flyShipSprite->getCharYOffset(spriteIndex));
-}
-
Common::Point InsaneRebel2::getHandler7ProjectedPointFor(int16 x, int16 y) {
int viewTilt = (_viewShift * 5) / 128;
int xSkew = (viewTilt * 9) / 4;
@@ -2290,6 +2279,17 @@ void InsaneRebel2::checkGameplayPostRenderCollisions(byte *renderBitmap, int pit
}
}
+void InsaneRebel2::prepareHandler7Viewport(byte *renderBitmap, int pitch, int width, int height) {
+ if (_rebelHandler != 7 || _rebelDetailMode < 1 || isHiRes() || !renderBitmap || pitch <= 0 || width <= 0 || height <= 0)
+ return;
+
+ copyRA2Handler7PerspectiveViewport(renderBitmap, pitch, width, height,
+ renderBitmap, pitch, MIN(width, pitch), height, _perspectiveX, _perspectiveY, _viewShift);
+ _viewX = 0;
+ _viewY = 0;
+ _player->setScrollOffset(0, 0);
+}
+
void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY, int32 curFrame) {
processMouse();
@@ -2306,7 +2306,17 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
SmushPlayerRebel2 *ra2Player = static_cast<SmushPlayerRebel2 *>(_player);
int nativeViewX = _viewX;
int nativeViewY = _viewY;
- if (ra2Player && ra2Player->ra2PromoteCurrentFrameToHiRes(_viewX, _viewY)) {
+ bool promoted = false;
+ if (ra2Player) {
+ if (_rebelHandler == 7 && _rebelDetailMode >= 1) {
+ promoted = ra2Player->ra2PromoteHandler7PerspectiveToHiRes(_perspectiveX, _perspectiveY, _viewShift);
+ nativeViewX = 0;
+ nativeViewY = 0;
+ } else {
+ promoted = ra2Player->ra2PromoteCurrentFrameToHiRes(_viewX, _viewY);
+ }
+ }
+ if (promoted) {
renderBitmap = _player->_dst;
width = _player->_width;
height = _player->_height;
@@ -2317,6 +2327,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
_viewY = 0;
}
}
+ prepareHandler7Viewport(renderBitmap, pitch, width, height);
renderStatusBarBackground(renderBitmap, pitch, width, height, videoWidth, videoHeight, gameplayStatusBarY);
@@ -3051,13 +3062,50 @@ void InsaneRebel2::renderHandler7FlySprite(byte *renderBitmap, int pitch, int wi
if (renderHiRes) {
const int clipBottom = MIN(height, _gameplayPresentationClipBottom + 1);
+ if (drawRebel2Codec23Sprite(nut, renderBitmap, pitch, width, height,
+ Common::Rect(0, 0, width, clipBottom), dstX, dstY, spriteIndex, renderScale))
+ return;
+
+ if (drawRebel2Codec45Sprite(nut, renderBitmap, pitch, width, height,
+ Common::Rect(0, 0, width, clipBottom), dstX, dstY, spriteIndex, renderScale))
+ return;
+
renderNutSpriteScaledClipped(renderBitmap, pitch, width, height,
0, 0, width, clipBottom, dstX, dstY, nut, spriteIndex, false, renderScale, true);
} else {
- renderNutSprite(renderBitmap, pitch, width, height, dstX, dstY, nut, spriteIndex);
+ Common::Rect clipRect(0, 0, width, height);
+ if (_rebelHandler == 7) {
+ clipRect = Common::Rect(_viewX, _viewY,
+ MIN(width, _viewX + 320),
+ MIN(height, _viewY + 170));
+ }
+ if (drawRebel2Codec23Sprite(nut, renderBitmap, pitch, width, height,
+ clipRect, dstX, dstY, spriteIndex, renderScale))
+ return;
+
+ if (drawRebel2Codec45Sprite(nut, renderBitmap, pitch, width, height,
+ clipRect, dstX, dstY, spriteIndex, renderScale))
+ return;
+
+ if (_rebelHandler == 7) {
+ renderNutSpriteClipped(renderBitmap, pitch, height,
+ clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+ dstX, dstY, nut, spriteIndex);
+ } else {
+ renderNutSprite(renderBitmap, pitch, width, height, dstX, dstY, nut, spriteIndex);
+ }
}
}
+static Common::Point getHandler7SpriteDrawPoint(const Common::Point &base, NutRenderer *nut, int spriteIndex) {
+ Common::Point point = base;
+ if (nut && spriteIndex >= 0 && spriteIndex < nut->getNumChars()) {
+ point.x += nut->getCharXOffset(spriteIndex);
+ point.y += nut->getCharYOffset(spriteIndex);
+ }
+ return point;
+}
+
void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width, int height) {
if (_rebelHandler != 7 || !_flyShipSprite || _shipLevelMode == 5)
return;
@@ -3075,12 +3123,9 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
const int nativeViewY = renderHiRes ? _hiResPresentationViewY : 0;
Common::Point projected = getHandler7ProjectedPoint();
- Common::Point shipDraw = getHandler7ShipDrawPoint();
if (renderHiRes) {
projected.x += nativeViewX;
projected.y += nativeViewY;
- shipDraw.x += nativeViewX;
- shipDraw.y += nativeViewY;
}
int shipCenterX = projected.x;
int shipCenterY = projected.y;
@@ -3146,6 +3191,9 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
}
}
+ Common::Point shipBase(projected.x - 0xd4, projected.y - 0x82);
+ Common::Point shipDraw = getHandler7SpriteDrawPoint(shipBase, _flyShipSprite, spriteIndex);
+
int drawX = renderHiRes ? (shipDraw.x - nativeViewX) * renderScale : shipDraw.x;
int drawY = renderHiRes ? (shipDraw.y - nativeViewY) * renderScale : shipDraw.y;
@@ -3156,19 +3204,21 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
if (_flyLaserSprite && _flyOverlayRepeatCount > 0) {
int overlayIndex = spriteIndex + 0x14;
if (overlayIndex >= 0 && overlayIndex < _flyLaserSprite->getNumChars()) {
+ Common::Point overlayDraw = getHandler7SpriteDrawPoint(shipBase, _flyLaserSprite, overlayIndex);
for (int i = 0; i < _flyOverlayRepeatCount; i++) {
renderHandler7FlySprite(renderBitmap, pitch, width, height,
renderHiRes, renderScale, nativeViewX, nativeViewY,
- shipDraw.x, shipDraw.y, _flyLaserSprite, overlayIndex);
+ overlayDraw.x, overlayDraw.y, _flyLaserSprite, overlayIndex);
}
}
}
if (_flyTargetSprite && _rebelDetailMode >= 0 &&
spriteIndex >= 0 && spriteIndex < _flyTargetSprite->getNumChars()) {
+ Common::Point targetDraw = getHandler7SpriteDrawPoint(shipBase, _flyTargetSprite, spriteIndex);
renderHandler7FlySprite(renderBitmap, pitch, width, height,
renderHiRes, renderScale, nativeViewX, nativeViewY,
- shipDraw.x, shipDraw.y, _flyTargetSprite, spriteIndex);
+ targetDraw.x, targetDraw.y, _flyTargetSprite, spriteIndex);
}
debugC(DEBUG_INSANE, "Handler7Ship: draw=(%d,%d) sprite=%d/%d shipPos=(%d,%d) view=(%d,%d) persp=(%d,%d) smoothVel=%d vertIn=%d fxCtr=%d fxRep=%d",
diff --git a/engines/scumm/smush/rebel/codec_ra2.cpp b/engines/scumm/smush/rebel/codec_ra2.cpp
index 0e81285b329..a8681773fd4 100644
--- a/engines/scumm/smush/rebel/codec_ra2.cpp
+++ b/engines/scumm/smush/rebel/codec_ra2.cpp
@@ -23,6 +23,7 @@
#include "common/endian.h"
#include "common/textconsole.h"
+#include "common/util.h"
namespace Scumm {
@@ -75,6 +76,115 @@ const byte *smushSkipRLELines(const byte *src, int &dataSize, int lines) {
return src;
}
+void copyRA2Handler7PerspectiveViewport(byte *dst, int dstPitch, int dstWidth, int dstHeight,
+ const byte *src, int srcPitch, int srcWidth, int srcHeight,
+ int perspectiveX, int perspectiveY, int viewShift) {
+ if (!dst || !src || dstPitch <= 0 || srcPitch <= 0 || dstWidth <= 0 || dstHeight <= 0 || srcWidth <= 0 || srcHeight <= 0)
+ return;
+
+ const int viewportWidth = MIN(320, MIN(dstWidth, dstPitch));
+ const int viewportHeight = MIN(170, dstHeight);
+ if (viewportWidth <= 0 || viewportHeight <= 0)
+ return;
+
+ int tilt = (viewShift * 5) / 128;
+ int xSkew = (tilt * 9) / 4;
+ int ySkew = tilt * 5;
+ int srcBaseX = 0x34 + perspectiveX + xSkew;
+ int srcBaseY = 0x2d + perspectiveY - ySkew;
+
+ const int tempPitch = viewportWidth;
+ byte *temp = new byte[viewportWidth * viewportHeight];
+ memset(temp, 0, viewportWidth * viewportHeight);
+
+ const int absTilt = ABS(tilt);
+ static const int chunkPatterns[6][7] = {
+ {16, 16, 0, 0, 0, 0, 0},
+ {8, 16, 8, 0, 0, 0, 0},
+ {4, 12, 12, 4, 0, 0, 0},
+ {4, 8, 8, 8, 4, 0, 0},
+ {4, 8, 4, 8, 4, 4, 0},
+ {4, 4, 4, 8, 4, 4, 4}
+ };
+
+ if (absTilt > 6)
+ tilt = 0;
+
+ if (tilt == 0) {
+ for (int dy = 0; dy < viewportHeight; ++dy) {
+ const int srcY = srcBaseY + dy;
+ byte *dstRow = temp + dy * tempPitch;
+ if (srcY < 0 || srcY >= srcHeight)
+ continue;
+
+ int copyX = srcBaseX;
+ int dstX = 0;
+ int copyWidth = viewportWidth;
+ if (copyX < 0) {
+ dstX = -copyX;
+ copyWidth -= dstX;
+ copyX = 0;
+ }
+ if (copyX + copyWidth > srcWidth)
+ copyWidth = srcWidth - copyX;
+ if (copyWidth > 0)
+ memcpy(dstRow + dstX, src + srcY * srcPitch + copyX, copyWidth);
+ }
+ } else {
+ const int yStep = (tilt > 0) ? 1 : -1;
+ const int xStep = (tilt > 0) ? -1 : 1;
+ const int rowShiftStep = ABS(xSkew) * 2;
+ int rowX = srcBaseX;
+ int rowY = srcBaseY;
+ int rowError = 0;
+ const int *pattern = chunkPatterns[absTilt - 1];
+ const int patternCount = absTilt + 1;
+
+ for (int dy = 0; dy < viewportHeight; ++dy) {
+ byte *dstRow = temp + dy * tempPitch;
+ int srcX = rowX;
+ int srcY = rowY;
+ int dstX = 0;
+ while (dstX < viewportWidth) {
+ for (int i = 0; i < patternCount && dstX < viewportWidth; ++i) {
+ const int chunkWidth = MIN(pattern[i], viewportWidth - dstX);
+ if (srcY >= 0 && srcY < srcHeight) {
+ int copyX = srcX;
+ int copyDstX = dstX;
+ int copyWidth = chunkWidth;
+ if (copyX < 0) {
+ copyDstX -= copyX;
+ copyWidth += copyX;
+ copyX = 0;
+ }
+ if (copyX + copyWidth > srcWidth)
+ copyWidth = srcWidth - copyX;
+ if (copyWidth > 0)
+ memcpy(dstRow + copyDstX, src + srcY * srcPitch + copyX, copyWidth);
+ }
+
+ srcX += chunkWidth;
+ dstX += chunkWidth;
+ if (i != patternCount - 1)
+ srcY += yStep;
+ }
+ }
+
+ rowY++;
+ rowError += rowShiftStep;
+ if (rowError > 0) {
+ rowError -= viewportHeight;
+ rowX += xStep;
+ }
+ }
+ }
+
+ for (int y = 0; y < viewportHeight; ++y)
+ memcpy(dst + y * dstPitch, temp + y * tempPitch, viewportWidth);
+
+ delete[] temp;
+}
+
// RLE decoder for opaque backgrounds, including color 0.
void smushDecodeRLEOpaque(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize) {
if (dataSize <= 0)
@@ -279,14 +389,22 @@ bool smushPrepareRA2BlurData(const byte *src, int dataSize, byte *palette, byte
return maskSize > 3;
}
-// Codec 45 blur/wipe mask.
-void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup) {
+static void smushDecodeRA2BlurImpl(byte *dst, const byte *src, int left, int top,
+ int clipLeft, int clipTop, int clipRight, int clipBottom,
+ int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup) {
const byte *maskData = nullptr;
int maskSize = 0;
if (dst == nullptr || dstWidth <= 2 || dstHeight <= 2 || pitch <= 0 ||
!smushPrepareRA2BlurData(src, dataSize, palette, lookup, maskData, maskSize))
return;
+ clipLeft = CLIP<int>(clipLeft + 1, 1, dstWidth - 1);
+ clipTop = CLIP<int>(clipTop + 1, 1, dstHeight - 1);
+ clipRight = CLIP<int>(clipRight - 1, 1, dstWidth - 1);
+ clipBottom = CLIP<int>(clipBottom - 1, 1, dstHeight - 1);
+ if (clipLeft >= clipRight || clipTop >= clipBottom)
+ return;
+
int x = left;
int y = top;
@@ -301,8 +419,8 @@ void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWi
y += dy;
for (int i = 0; i <= count; ++i) {
- if (x > 0 && y > 0 && x < dstWidth - 1) {
- if (y >= dstHeight - 1)
+ if (x >= clipLeft && y >= clipTop && x < clipRight) {
+ if (y >= clipBottom)
return;
byte *pixel = dst + y * pitch + x;
@@ -328,4 +446,16 @@ void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWi
}
}
+void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup) {
+ smushDecodeRA2BlurImpl(dst, src, left, top, 0, 0, dstWidth, dstHeight,
+ dstWidth, dstHeight, pitch, dataSize, palette, lookup);
+}
+
+void smushDecodeRA2BlurClip(byte *dst, const byte *src, int left, int top,
+ int clipLeft, int clipTop, int clipRight, int clipBottom,
+ int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup) {
+ smushDecodeRA2BlurImpl(dst, src, left, top, clipLeft, clipTop, clipRight, clipBottom,
+ dstWidth, dstHeight, pitch, dataSize, palette, lookup);
+}
+
} // End of namespace Scumm
diff --git a/engines/scumm/smush/rebel/codec_ra2.h b/engines/scumm/smush/rebel/codec_ra2.h
index 42911065cb0..99428f2f514 100644
--- a/engines/scumm/smush/rebel/codec_ra2.h
+++ b/engines/scumm/smush/rebel/codec_ra2.h
@@ -31,6 +31,12 @@ void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int wi
void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
void smushDecodeRA2SkipRemap(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize, const byte *remap, byte addColor);
void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup);
+void smushDecodeRA2BlurClip(byte *dst, const byte *src, int left, int top,
+ int clipLeft, int clipTop, int clipRight, int clipBottom,
+ int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup);
+void copyRA2Handler7PerspectiveViewport(byte *dst, int dstPitch, int dstWidth, int dstHeight,
+ const byte *src, int srcPitch, int srcWidth, int srcHeight,
+ int perspectiveX, int perspectiveY, int viewShift);
const byte *smushSkipRLELines(const byte *src, int &dataSize, int lines);
} // End of namespace Scumm
diff --git a/engines/scumm/smush/rebel/font_rebel2.cpp b/engines/scumm/smush/rebel/font_rebel2.cpp
index df5179f7332..97b5f29136f 100644
--- a/engines/scumm/smush/rebel/font_rebel2.cpp
+++ b/engines/scumm/smush/rebel/font_rebel2.cpp
@@ -38,13 +38,14 @@ enum {
};
struct Rebel2NutFrameInfo {
- Rebel2NutFrameInfo() : codec(0), xoffs(0), yoffs(0), width(0), height(0), data(nullptr), dataSize(0) {}
+ Rebel2NutFrameInfo() : codec(0), xoffs(0), yoffs(0), width(0), height(0), effect(0), data(nullptr), dataSize(0) {}
int codec;
int16 xoffs;
int16 yoffs;
uint16 width;
uint16 height;
+ uint16 effect;
const byte *data;
int32 dataSize;
};
@@ -56,23 +57,238 @@ static void decodeRebel2RawSprite(byte *dst, const byte *src, int width, int hei
memcpy(dst, src, copySize);
}
+static void applyRebel2Codec23Effect(byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteWidth, int spriteHeight,
+ const byte *src, int32 srcSize, byte effect, const byte *lookup, int scale) {
+ if (!buffer || !src || srcSize <= 0 || spriteWidth <= 0 || spriteHeight <= 0)
+ return;
+
+ if (scale < 1)
+ scale = 1;
+
+ Common::Rect clipped = clipRect;
+ clipped.clip(Common::Rect(0, 0, MIN(width, pitch), height));
+ if (clipped.isEmpty())
+ return;
+
+ const byte *srcEnd = src + srcSize;
+ for (int row = 0; row < spriteHeight && src + 2 <= srcEnd; ++row) {
+ const int rowSize = READ_LE_UINT16(src);
+ src += 2;
+ if (src + rowSize > srcEnd)
+ break;
+
+ const int scaledTop = y + row * scale;
+ const int scaledBottom = scaledTop + scale;
+ if (scaledBottom > clipped.top && scaledTop < clipped.bottom) {
+ const byte *rowPtr = src;
+ const byte *rowEnd = src + rowSize;
+ int dstX = x;
+
+ while (rowPtr + 2 <= rowEnd) {
+ dstX += *rowPtr++ * scale;
+
+ int run = *rowPtr++;
+ int scaledRun = run * scale;
+ int drawLeft = MAX<int>(dstX, clipped.left);
+ int drawRight = MIN<int>(dstX + scaledRun, clipped.right);
+ if (drawLeft < drawRight) {
+ for (int dstY = MAX<int>(scaledTop, clipped.top); dstY < MIN<int>(scaledBottom, clipped.bottom); ++dstY) {
+ byte *pixel = buffer + dstY * pitch + drawLeft;
+ if (lookup) {
+ for (int column = drawLeft; column < drawRight; ++column, ++pixel)
+ *pixel = lookup[*pixel];
+ } else {
+ for (int column = drawLeft; column < drawRight; ++column, ++pixel)
+ *pixel = (byte)(*pixel + effect);
+ }
+ }
+ }
+
+ dstX += scaledRun;
+ if (dstX >= x + spriteWidth * scale)
+ break;
+ }
+ }
+
+ src += rowSize;
+ }
+}
+
class Rebel2NutRenderer : public NutRenderer {
public:
- Rebel2NutRenderer(ScummEngine *vm, const char *filename) : NutRenderer(vm, nullptr) {
+ Rebel2NutRenderer(ScummEngine *vm, const char *filename) :
+ NutRenderer(vm, nullptr), _spriteData(nullptr), _spriteFrameCount(0) {
+ clearCodec23Table();
+ clearCodec45Tables();
loadRebel2Font(filename);
}
- Rebel2NutRenderer(ScummEngine *vm, const byte *data, int32 dataSize) : NutRenderer(vm, nullptr) {
+ Rebel2NutRenderer(ScummEngine *vm, const byte *data, int32 dataSize) :
+ NutRenderer(vm, nullptr), _spriteData(nullptr), _spriteFrameCount(0) {
+ clearCodec23Table();
+ clearCodec45Tables();
loadRebel2SpriteFromData(data, dataSize);
}
+ ~Rebel2NutRenderer() override {
+ delete[] _spriteData;
+ }
+
+ bool drawCodec45Sprite(byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteIdx, int scale);
+ bool drawCodec23Sprite(byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteIdx, int scale);
+
private:
void codec44(byte *dst, const byte *src, int width, int height, int pitch);
void loadRebel2Font(const char *filename);
void loadRebel2SpriteFromData(const byte *data, int32 dataSize);
void decodeRebel2Frame(byte *dst, const Rebel2NutFrameInfo &frame, byte *codec45Palette, byte *codec45Lookup);
+ void clearSpriteData();
+ void clearCodec23Table();
+ void clearCodec45Tables();
+
+ byte *_spriteData;
+ int _spriteFrameCount;
+ Rebel2NutFrameInfo _spriteFrames[256];
+ byte _codec23Lookup[256];
+ byte _codec45Palette[0x300];
+ byte _codec45Lookup[0x8000];
};
+void Rebel2NutRenderer::clearSpriteData() {
+ delete[] _spriteData;
+ _spriteData = nullptr;
+ _spriteFrameCount = 0;
+ for (int i = 0; i < ARRAYSIZE(_spriteFrames); ++i)
+ _spriteFrames[i] = Rebel2NutFrameInfo();
+}
+
+void Rebel2NutRenderer::clearCodec23Table() {
+ for (int i = 0; i < ARRAYSIZE(_codec23Lookup); ++i)
+ _codec23Lookup[i] = (byte)i;
+}
+
+void Rebel2NutRenderer::clearCodec45Tables() {
+ memset(_codec45Palette, 0, sizeof(_codec45Palette));
+ memset(_codec45Lookup, 0, sizeof(_codec45Lookup));
+}
+
+static int rebel2FloorDiv(int value, int divisor) {
+ if (value >= 0)
+ return value / divisor;
+ return -((-value + divisor - 1) / divisor);
+}
+
+bool Rebel2NutRenderer::drawCodec45Sprite(byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteIdx, int scale) {
+ if (!buffer || spriteIdx < 0 || spriteIdx >= _spriteFrameCount)
+ return false;
+
+ const Rebel2NutFrameInfo &frame = _spriteFrames[spriteIdx];
+ if (frame.codec != 45 || !frame.data || frame.dataSize <= 0)
+ return false;
+
+ if (scale < 1)
+ scale = 1;
+
+ const int dstWidth = MIN(width, pitch);
+ if (dstWidth <= 2 || height <= 2)
+ return true;
+
+ Common::Rect clipped = clipRect;
+ clipped.clip(Common::Rect(0, 0, dstWidth, height));
+ if (clipped.isEmpty())
+ return true;
+
+ if (scale == 1) {
+ smushDecodeRA2BlurClip(buffer, frame.data, x, y,
+ clipped.left, clipped.top, clipped.right, clipped.bottom,
+ dstWidth, height, pitch, frame.dataSize,
+ _codec45Palette, _codec45Lookup);
+ return true;
+ }
+
+ const int nativeWidth = (dstWidth + scale - 1) / scale;
+ const int nativeHeight = (height + scale - 1) / scale;
+ if (nativeWidth <= 2 || nativeHeight <= 2)
+ return true;
+
+ const size_t nativeSize = (size_t)nativeWidth * nativeHeight;
+ byte *nativeBuffer = new byte[nativeSize];
+ for (int ny = 0; ny < nativeHeight; ++ny) {
+ const int srcY = MIN(ny * scale, height - 1);
+ for (int nx = 0; nx < nativeWidth; ++nx) {
+ const int srcX = MIN(nx * scale, dstWidth - 1);
+ nativeBuffer[ny * nativeWidth + nx] = buffer[srcY * pitch + srcX];
+ }
+ }
+
+ const int nativeX = rebel2FloorDiv(x, scale);
+ const int nativeY = rebel2FloorDiv(y, scale);
+ const int nativeClipLeft = rebel2FloorDiv(clipped.left, scale);
+ const int nativeClipTop = rebel2FloorDiv(clipped.top, scale);
+ const int nativeClipRight = (clipped.right + scale - 1) / scale;
+ const int nativeClipBottom = (clipped.bottom + scale - 1) / scale;
+ smushDecodeRA2BlurClip(nativeBuffer, frame.data, nativeX, nativeY,
+ nativeClipLeft, nativeClipTop, nativeClipRight, nativeClipBottom,
+ nativeWidth, nativeHeight, nativeWidth, frame.dataSize, _codec45Palette, _codec45Lookup);
+
+ const int copyLeft = CLIP<int>(nativeX - 1, 0, nativeWidth);
+ const int copyTop = CLIP<int>(nativeY - 1, 0, nativeHeight);
+ const int copyRight = CLIP<int>(nativeX + frame.width + 1, 0, nativeWidth);
+ const int copyBottom = CLIP<int>(nativeY + frame.height + 1, 0, nativeHeight);
+ for (int ny = copyTop; ny < copyBottom; ++ny) {
+ for (int sy = 0; sy < scale; ++sy) {
+ const int dstY = ny * scale + sy;
+ if (dstY < clipped.top || dstY >= clipped.bottom)
+ continue;
+
+ byte *dstRow = buffer + dstY * pitch;
+ const byte *srcRow = nativeBuffer + ny * nativeWidth;
+ for (int nx = copyLeft; nx < copyRight; ++nx) {
+ for (int sx = 0; sx < scale; ++sx) {
+ const int dstX = nx * scale + sx;
+ if (dstX >= clipped.left && dstX < clipped.right)
+ dstRow[dstX] = srcRow[nx];
+ }
+ }
+ }
+ }
+
+ delete[] nativeBuffer;
+ return true;
+}
+
+bool Rebel2NutRenderer::drawCodec23Sprite(byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteIdx, int scale) {
+ if (!buffer || spriteIdx < 0 || spriteIdx >= _spriteFrameCount)
+ return false;
+
+ const Rebel2NutFrameInfo &frame = _spriteFrames[spriteIdx];
+ if (frame.codec != 23 || !frame.data || frame.dataSize <= 0)
+ return false;
+
+ const byte *src = frame.data;
+ int32 srcSize = frame.dataSize;
+ const byte *lookup = nullptr;
+
+ if (frame.effect == 0x100) {
+ if (srcSize < 256)
+ return true;
+ lookup = src;
+ src += 256;
+ srcSize -= 256;
+ } else if (frame.effect > 0xff) {
+ lookup = _codec23Lookup;
+ }
+
+ applyRebel2Codec23Effect(buffer, pitch, width, height, clipRect, x, y,
+ frame.width, frame.height, src, srcSize, (byte)frame.effect, lookup, scale);
+ return true;
+}
+
void Rebel2NutRenderer::codec44(byte *dst, const byte *src, int width, int height, int pitch) {
while (height--) {
byte *dstPtrNext = dst + pitch;
@@ -102,6 +318,10 @@ void Rebel2NutRenderer::codec44(byte *dst, const byte *src, int width, int heigh
}
void Rebel2NutRenderer::loadRebel2Font(const char *filename) {
+ clearSpriteData();
+ clearCodec23Table();
+ clearCodec45Tables();
+
ScummFile *file = _vm->instantiateScummFile();
_vm->openFile(*file, filename);
@@ -230,7 +450,6 @@ void Rebel2NutRenderer::decodeRebel2Frame(byte *dst, const Rebel2NutFrameInfo &f
smushDecodeLineUpdate(dst, frame.data, 0, 0, frame.width, frame.height, frame.width, frame.dataSize);
break;
case 23:
- smushDecodeSkipRLE(dst, frame.data, 0, 0, frame.width, frame.height, frame.width, frame.dataSize);
break;
case 44:
codec44(dst, frame.data, frame.width, frame.height, frame.width);
@@ -246,6 +465,10 @@ void Rebel2NutRenderer::decodeRebel2Frame(byte *dst, const Rebel2NutFrameInfo &f
}
void Rebel2NutRenderer::loadRebel2SpriteFromData(const byte *data, int32 dataSize) {
+ clearSpriteData();
+ clearCodec23Table();
+ clearCodec45Tables();
+
if (!data || dataSize < 16) {
warning("Rebel2NutRenderer::loadRebel2SpriteFromData: data too small (%d bytes)", dataSize);
return;
@@ -274,6 +497,10 @@ void Rebel2NutRenderer::loadRebel2SpriteFromData(const byte *data, int32 dataSiz
declaredChars = ARRAYSIZE(_chars);
}
+ _spriteData = new byte[dataSize];
+ memcpy(_spriteData, data, dataSize);
+ data = _spriteData;
+
Rebel2NutFrameInfo frames[ARRAYSIZE(_chars)];
uint64 decodedLength = 0;
int frameCount = 0;
@@ -306,9 +533,14 @@ void Rebel2NutRenderer::loadRebel2SpriteFromData(const byte *data, int32 dataSiz
frame.yoffs = READ_LE_INT16(data + fobjDataStart + 4);
frame.width = READ_LE_UINT16(data + fobjDataStart + 6);
frame.height = READ_LE_UINT16(data + fobjDataStart + 8);
+ frame.effect = READ_LE_UINT16(data + fobjDataStart + 12);
frame.data = data + fobjDataStart + 14;
frame.dataSize = fobjSize - 14;
+ if (frame.codec == 23 && frame.effect == 0x100 && frame.dataSize >= 256) {
+ memcpy(_codec23Lookup, frame.data, sizeof(_codec23Lookup));
+ }
+
const uint64 pixels = (uint64)frame.width * frame.height;
if (pixels == 0) {
frame.width = 0;
@@ -334,21 +566,21 @@ void Rebel2NutRenderer::loadRebel2SpriteFromData(const byte *data, int32 dataSiz
}
_numChars = frameCount;
+ _spriteFrameCount = frameCount;
if (_numChars <= 0) {
warning("Rebel2NutRenderer::loadRebel2SpriteFromData: no decodable frames");
return;
}
+ for (int i = 0; i < _spriteFrameCount; ++i)
+ _spriteFrames[i] = frames[i];
+
delete[] _decodedData;
_decodedData = decodedLength ? new byte[(uint32)decodedLength] : nullptr;
memset(_chars, 0, sizeof(_chars));
_fontHeight = 0;
byte *decodedPtr = _decodedData;
- byte codec45Palette[0x300];
- byte codec45Lookup[0x8000];
- memset(codec45Palette, 0, sizeof(codec45Palette));
- memset(codec45Lookup, 0, sizeof(codec45Lookup));
for (int i = 0; i < _numChars; i++) {
const Rebel2NutFrameInfo &frame = frames[i];
@@ -367,7 +599,7 @@ void Rebel2NutRenderer::loadRebel2SpriteFromData(const byte *data, int32 dataSiz
_fontHeight = MAX<int>(_fontHeight, frame.height);
memset(_chars[i].src, kDefaultTransparentColor, pixels);
- decodeRebel2Frame(_chars[i].src, frame, codec45Palette, codec45Lookup);
+ decodeRebel2Frame(_chars[i].src, frame, _codec45Palette, _codec45Lookup);
}
debugC(DEBUG_SMUSH, "Rebel2NutRenderer::loadRebel2SpriteFromData() - numChars=%d decodedLength=%u", _numChars, (uint32)decodedLength);
@@ -387,6 +619,18 @@ NutRenderer *makeRebel2SpriteFromData(ScummEngine *vm, const byte *data, int32 d
return renderer;
}
+bool drawRebel2Codec45Sprite(NutRenderer *sprite, byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteIdx, int scale) {
+ Rebel2NutRenderer *renderer = dynamic_cast<Rebel2NutRenderer *>(sprite);
+ return renderer && renderer->drawCodec45Sprite(buffer, pitch, width, height, clipRect, x, y, spriteIdx, scale);
+}
+
+bool drawRebel2Codec23Sprite(NutRenderer *sprite, byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteIdx, int scale) {
+ Rebel2NutRenderer *renderer = dynamic_cast<Rebel2NutRenderer *>(sprite);
+ return renderer && renderer->drawCodec23Sprite(buffer, pitch, width, height, clipRect, x, y, spriteIdx, scale);
+}
+
Rebel2FontSet::Rebel2FontSet() : numFonts(0), defaultFont(0) {
memset(fonts, 0, sizeof(fonts));
}
diff --git a/engines/scumm/smush/rebel/font_rebel2.h b/engines/scumm/smush/rebel/font_rebel2.h
index 1276e1c13a3..002ba6c38bd 100644
--- a/engines/scumm/smush/rebel/font_rebel2.h
+++ b/engines/scumm/smush/rebel/font_rebel2.h
@@ -47,6 +47,10 @@ struct Rebel2FontSet {
NutRenderer *makeRebel2Font(ScummEngine *vm, const char *filename);
NutRenderer *makeRebel2SpriteFromData(ScummEngine *vm, const byte *data, int32 dataSize);
+bool drawRebel2Codec23Sprite(NutRenderer *sprite, byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteIdx, int scale);
+bool drawRebel2Codec45Sprite(NutRenderer *sprite, byte *buffer, int pitch, int width, int height,
+ const Common::Rect &clipRect, int x, int y, int spriteIdx, int scale);
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);
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index ab186cddce6..7d3d41aa0a8 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -308,6 +308,37 @@ bool SmushPlayerRebel2::ra2PromoteCurrentFrameToHiRes(int scrollX, int scrollY)
return true;
}
+bool SmushPlayerRebel2::ra2PromoteHandler7PerspectiveToHiRes(int perspectiveX, int perspectiveY, int viewShift) {
+ if (!ra2IsHighResMode() || !_dst || _width <= 0 || _height <= 0)
+ return false;
+
+ VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
+ byte *screen = vs->getPixels(0, 0);
+ if (_dst == screen && _width == _vm->_screenWidth && _height == _vm->_screenHeight)
+ return false;
+
+ if (!ra2EnsureLowResVideoBuffer())
+ return false;
+
+ const byte *src = _dst;
+ const int srcPitch = _width;
+ const int srcWidth = _width;
+ const int srcHeight = _height;
+
+ memset(_ra2LowResVideoBuffer, 0, _ra2LowResVideoBufferSize);
+ copyRA2Handler7PerspectiveViewport(_ra2LowResVideoBuffer, 320, 320, 200,
+ src, srcPitch, srcWidth, srcHeight, perspectiveX, perspectiveY, viewShift);
+
+ scaleNativeViewportToHiRes(screen, _vm->_screenWidth, _vm->_screenWidth, _vm->_screenHeight,
+ _ra2LowResVideoBuffer, 320, 320, 200, 0, 0);
+
+ _dst = screen;
+ _width = _vm->_screenWidth;
+ _height = _vm->_screenHeight;
+ setScrollOffset(0, 0);
+ return true;
+}
+
bool SmushPlayerRebel2::handleGameFetch(int32 subSize, Common::SeekableReadStream &b) {
int16 ftchUnknown = b.readSint16LE();
int16 ftchX = b.readSint16LE();
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 89ec5c65d29..28036038f3c 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -31,6 +31,7 @@ public:
SmushPlayerRebel2(ScummEngine_v7 *scumm, IMuseDigital *imuseDigital, Insane *insane);
~SmushPlayerRebel2() override;
bool ra2PromoteCurrentFrameToHiRes(int scrollX, int scrollY);
+ bool ra2PromoteHandler7PerspectiveToHiRes(int perspectiveX, int perspectiveY, int viewShift);
protected:
void initGamePlayerFields() override;
More information about the Scummvm-git-logs
mailing list