[Scummvm-git-logs] scummvm master -> afe64b518cdd6e9178ec8ae668e24f722cce390f
neuromancer
noreply at scummvm.org
Sun Jun 21 16:24:44 UTC 2026
This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
3d77964014 SCUMM: RA2: better gamepad navigation in menus, similar to RA1
1ef1e0e2e7 SCUMM: RA2: simplified gamepad controls, no more return to center
b8caec28c0 SCUMM: RA2: gamepad fixes for handler 7
2bd3cf279f SCUMM: RA2: perspective/damage fixes for handler 7
8c086c04d6 SCUMM: RA2: use B to go back/skip cutscenes
5fc31be42d SCUMM: RA2: clean-up of comments for INSANE code
afe64b518c SCUMM: RA1: clean-up of comments for INSANE code
Commit: 3d77964014a1ce401b7f96eb7aadd5c45cc5187e
https://github.com/scummvm/scummvm/commit/3d77964014a1ce401b7f96eb7aadd5c45cc5187e
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-21T18:24:30+02:00
Commit Message:
SCUMM: RA2: better gamepad navigation in menus, similar to RA1
Changed paths:
engines/scumm/insane/rebel2/menu.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index 0ada7d95441..08ece9305ec 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -57,6 +57,7 @@ void InsaneRebel2::resetMenu() {
_menuInactivityTimer = 0;
_menuInactivityTimedOut = false;
_menuRepeatDelay = 0;
+ resetMenuGamepadAxis();
_menuSelectionConfirmed = false;
setVirtualKeyboardVisible(false);
}
@@ -706,6 +707,7 @@ int InsaneRebel2::runMainMenu() {
splayer->play("OPEN/O_CREDIT.SAN", 15);
_gameState = kStateMainMenu;
_menuInputActive = true;
+ resetMenuGamepadAxis();
// Returns 1 in original -> stays at stage 1 (main menu)
break;
@@ -738,6 +740,7 @@ int InsaneRebel2::runChapterSelect() {
_menuInputActive = true;
while (!_menuEventQueue.empty())
_menuEventQueue.pop();
+ resetMenuGamepadAxis();
// Initialize chapter selection state
// Original (lines 51-54): local_10 = 0xf; while (local_10 > 0 && locked) local_10--;
@@ -1234,6 +1237,7 @@ int InsaneRebel2::runLevelSelect() {
_menuInputActive = true;
while (!_menuEventQueue.empty())
_menuEventQueue.pop();
+ resetMenuGamepadAxis();
_levelSelection = 0;
_levelItemCount = _numPilots + 4; // N pilots + NEW/COPY/DELETE/MAIN MENU
@@ -1284,6 +1288,7 @@ int InsaneRebel2::runLevelSelect() {
_pilotMenuMode = kPilotModeSelect;
_levelItemCount = _numPilots + 4;
_gameState = kStatePilotSelect;
+ resetMenuGamepadAxis();
_menuInputActive = false;
return kLevelSelectPlay;
}
@@ -1299,6 +1304,7 @@ int InsaneRebel2::runLevelSelect() {
_pilotMenuMode = kPilotModeDifficulty;
_gameState = kStateDifficultySelect;
_difficultySelection = 2;
+ resetMenuGamepadAxis();
continue;
}
@@ -1325,6 +1331,7 @@ int InsaneRebel2::runLevelSelect() {
_pilotMenuMode = kPilotModeSelect;
_levelItemCount = _numPilots + 4;
_gameState = kStatePilotSelect;
+ resetMenuGamepadAxis();
continue;
}
@@ -1355,6 +1362,7 @@ int InsaneRebel2::runLevelSelect() {
_pilotNameInput = "";
_pilotMenuMode = kPilotModeNameInput;
_levelItemCount = _numPilots + 4;
+ resetMenuGamepadAxis();
updateMenuVirtualKeyboard();
debugC(DEBUG_INSANE, "NEW PILOT - entering name for slot %d", newIdx);
}
@@ -1366,6 +1374,7 @@ int InsaneRebel2::runLevelSelect() {
_pilotMenuMode = kPilotModeCopySelect;
_levelSelection = 0;
_levelItemCount = _numPilots;
+ resetMenuGamepadAxis();
debugC(DEBUG_INSANE, "COPY PILOT - selecting source");
}
continue;
@@ -1376,6 +1385,7 @@ int InsaneRebel2::runLevelSelect() {
_pilotMenuMode = kPilotModeDeleteSelect;
_levelSelection = 0;
_levelItemCount = _numPilots;
+ resetMenuGamepadAxis();
debugC(DEBUG_INSANE, "DELETE PILOT - selecting target");
}
continue;
@@ -1422,6 +1432,7 @@ int InsaneRebel2::processLevelSelectInput() {
}
_pilotMenuMode = kPilotModeSelect;
_levelItemCount = _numPilots + 4;
+ resetMenuGamepadAxis();
updateMenuVirtualKeyboard();
debugC(DEBUG_INSANE, "PilotName: cancelled");
} else if (event.kbd.keycode == Common::KEYCODE_BACKSPACE) {
@@ -1447,6 +1458,7 @@ int InsaneRebel2::processLevelSelectInput() {
}
_pilotMenuMode = kPilotModeSelect;
_levelItemCount = _numPilots + 4;
+ resetMenuGamepadAxis();
updateMenuVirtualKeyboard();
}
}
@@ -1497,11 +1509,13 @@ int InsaneRebel2::processLevelSelectInput() {
case Common::KEYCODE_ESCAPE:
if (isDifficultyMode) {
_gameState = kStatePilotSelect;
+ resetMenuGamepadAxis();
} else if (isPilotOperationMode) {
bool wasCopyMode = (_pilotMenuMode == kPilotModeCopySelect);
_pilotMenuMode = kPilotModeSelect;
_levelItemCount = _numPilots + 4;
_levelSelection = _numPilots + (wasCopyMode ? 1 : 2);
+ resetMenuGamepadAxis();
} else {
result = _levelItemCount - 1; // Last item = MAIN MENU
}
@@ -1542,10 +1556,12 @@ int InsaneRebel2::processLevelSelectInput() {
case Common::EVENT_RETURN_TO_LAUNCHER:
if (isDifficultyMode) {
_gameState = kStatePilotSelect;
+ resetMenuGamepadAxis();
} else if (isPilotOperationMode) {
_pilotMenuMode = kPilotModeSelect;
_levelItemCount = _numPilots + 4;
_levelSelection = _levelItemCount - 1;
+ resetMenuGamepadAxis();
result = _levelSelection;
} else {
result = _levelItemCount - 1;
@@ -1743,6 +1759,7 @@ void InsaneRebel2::showTopPilots() {
_menuInputActive = true;
while (!_menuEventQueue.empty())
_menuEventQueue.pop();
+ resetMenuGamepadAxis();
// param_1 = -1 from main menu: maxFrames = 120 (0x78)
_topPilotsMaxFrames = 120;
@@ -1760,6 +1777,7 @@ void InsaneRebel2::showTopPilots() {
_gameState = kStateMainMenu;
_menuInputActive = true;
+ resetMenuGamepadAxis();
debugC(DEBUG_INSANE, "Top Pilots screen finished");
}
@@ -1850,6 +1868,7 @@ void InsaneRebel2::showOptionsMenu() {
_menuInputActive = true;
while (!_menuEventQueue.empty())
_menuEventQueue.pop();
+ resetMenuGamepadAxis();
_optionsSelection = 0;
_optionsItemCount = 8;
@@ -1870,6 +1889,7 @@ void InsaneRebel2::showOptionsMenu() {
_gameState = kStateMainMenu;
_menuInputActive = true;
+ resetMenuGamepadAxis();
debugC(DEBUG_INSANE, "Options menu finished");
}
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 02f9442f926..5c307511965 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -51,9 +51,11 @@
namespace Scumm {
-const int kRA2MenuAxisThreshold = Common::JOYAXIS_MAX / 5;
+const int kRA2MenuAxisThreshold = Common::JOYAXIS_MAX / 2;
const uint32 kRA2MenuGamepadNavigationDebounceMs = 250;
const uint32 kRA2MenuGamepadMouseSuppressMs = 250;
+const int kRA2MenuKeyLikeFirstAxis = 8;
+const int kRA2MenuKeyLikeAxisTolerance = Common::JOYAXIS_MAX / 14;
const int kRA2Handler7MouseSettleJumpThreshold = 40;
const int kRA2Handler7MouseSettleRelativeThreshold = 40;
const int kRA2Handler7MouseSettleEdgeMargin = 16;
@@ -65,35 +67,70 @@ bool rebel2UsesRelativeGamepadAim(int selectedLevel) {
return selectedLevel == 1 || selectedLevel == 5 || selectedLevel == 14;
}
-bool isRebel2RawMenuAxis(int axis) {
- return axis == Common::JOYSTICK_AXIS_LEFT_STICK_X ||
- axis == Common::JOYSTICK_AXIS_LEFT_STICK_Y ||
- axis == Common::JOYSTICK_AXIS_RIGHT_STICK_X ||
- axis == Common::JOYSTICK_AXIS_RIGHT_STICK_Y ||
- axis == Common::JOYSTICK_AXIS_HAT_X ||
- axis == Common::JOYSTICK_AXIS_HAT_Y ||
- axis >= 8;
+int16 normalizeRebel2AxisMagnitude(int16 position) {
+ return position == Common::JOYAXIS_MIN ? Common::JOYAXIS_MAX : ABS((int)position);
}
-Common::KeyCode getRebel2RawMenuAxisKey(int axis, int16 position) {
- const bool horizontalAxis = axis == Common::JOYSTICK_AXIS_LEFT_STICK_X ||
- axis == Common::JOYSTICK_AXIS_RIGHT_STICK_X ||
- axis == Common::JOYSTICK_AXIS_HAT_X ||
- (axis >= 8 && (axis % 2) == 0);
- const bool verticalAxis = axis == Common::JOYSTICK_AXIS_LEFT_STICK_Y ||
- axis == Common::JOYSTICK_AXIS_RIGHT_STICK_Y ||
- axis == Common::JOYSTICK_AXIS_HAT_Y ||
- (axis >= 8 && (axis % 2) != 0);
-
- if (!horizontalAxis && !verticalAxis)
- return Common::KEYCODE_INVALID;
-
- if (position >= kRA2MenuAxisThreshold)
- return horizontalAxis ? Common::KEYCODE_RIGHT : Common::KEYCODE_DOWN;
- if (position <= -kRA2MenuAxisThreshold)
- return horizontalAxis ? Common::KEYCODE_LEFT : Common::KEYCODE_UP;
-
- return Common::KEYCODE_INVALID;
+int16 combineRebel2MenuAxis(int16 mappedAxis, int16 rawAxis) {
+ return ABS((int)rawAxis) > ABS((int)mappedAxis) ? rawAxis : mappedAxis;
+}
+
+int getRebel2MenuAxisDirection(int16 axisValue) {
+ if (axisValue >= kRA2MenuAxisThreshold)
+ return 1;
+ if (axisValue <= -kRA2MenuAxisThreshold)
+ return -1;
+ return 0;
+}
+
+bool decodeRebel2MenuKeyLikeAxis(int axis, int16 position, int16 &axisX, int16 &axisY) {
+ if (axis < kRA2MenuKeyLikeFirstAxis)
+ return false;
+
+ axisX = 0;
+ axisY = 0;
+
+ // Some backends expose a key-like POV as a single extra axis whose neutral
+ // value is beyond the normal range and arrives clamped to JOYAXIS_MAX.
+ if (position == 0 || position >= Common::JOYAXIS_MAX - kRA2MenuKeyLikeAxisTolerance)
+ return true;
+
+ static const int16 povValues[] = {
+ -Common::JOYAXIS_MAX,
+ -(Common::JOYAXIS_MAX * 5) / 7,
+ -(Common::JOYAXIS_MAX * 3) / 7,
+ -Common::JOYAXIS_MAX / 7,
+ Common::JOYAXIS_MAX / 7,
+ (Common::JOYAXIS_MAX * 3) / 7,
+ (Common::JOYAXIS_MAX * 5) / 7
+ };
+ static const int16 povDirections[][2] = {
+ { 0, -Common::JOYAXIS_MAX },
+ { Common::JOYAXIS_MAX, -Common::JOYAXIS_MAX },
+ { Common::JOYAXIS_MAX, 0 },
+ { Common::JOYAXIS_MAX, Common::JOYAXIS_MAX },
+ { 0, Common::JOYAXIS_MAX },
+ { -Common::JOYAXIS_MAX, Common::JOYAXIS_MAX },
+ { -Common::JOYAXIS_MAX, 0 }
+ };
+
+ int bestIndex = -1;
+ int bestDistance = Common::JOYAXIS_MAX;
+ for (uint i = 0; i < ARRAYSIZE(povValues); i++) {
+ const int distance = ABS((int)position - povValues[i]);
+ if (distance < bestDistance) {
+ bestDistance = distance;
+ bestIndex = i;
+ }
+ }
+
+ if (bestIndex >= 0 && bestDistance <= kRA2MenuKeyLikeAxisTolerance) {
+ axisX = povDirections[bestIndex][0];
+ axisY = povDirections[bestIndex][1];
+ return true;
+ }
+
+ return false;
}
bool isRebel2MenuDirectionKey(Common::KeyCode keycode) {
@@ -574,6 +611,11 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_gameplayMouseSettleUntil = 0;
_lastGameplayMenuCloseTime = 0;
_lastMenuGamepadNavigationTime = 0;
+ _menuGamepadAxisX = 0;
+ _menuGamepadAxisY = 0;
+ _menuGamepadRawAxis = -1;
+ _menuGamepadRawAxisX = 0;
+ _menuGamepadRawAxisY = 0;
_handler8HudGlyph = '#';
_handler8HudMessageTimer = 0;
_handler8HudMessageIndex = 0;
@@ -680,6 +722,105 @@ void InsaneRebel2::restoreIOSGamepadController() {
_iosGamepadControllerState.restore();
}
+void InsaneRebel2::resetMenuGamepadAxis() {
+ _menuGamepadAxisX = 0;
+ _menuGamepadAxisY = 0;
+ _menuGamepadRawAxis = -1;
+ _menuGamepadRawAxisX = 0;
+ _menuGamepadRawAxisY = 0;
+}
+
+void InsaneRebel2::queueMenuGamepadAxisKey(Common::KeyCode keycode) {
+ Common::Event syntheticEvent = Common::Event();
+ syntheticEvent.type = Common::EVENT_KEYDOWN;
+ syntheticEvent.kbd.keycode = keycode;
+ _menuEventQueue.push(syntheticEvent);
+
+ _lastMenuGamepadNavigationTime = _vm->_system->getMillis();
+}
+
+void InsaneRebel2::updateMenuGamepadAxisKey(int16 oldAxisX, int16 oldAxisY) {
+ const int16 axisX = combineRebel2MenuAxis(_menuGamepadAxisX, _menuGamepadRawAxisX);
+ const int16 axisY = combineRebel2MenuAxis(_menuGamepadAxisY, _menuGamepadRawAxisY);
+ const int oldX = getRebel2MenuAxisDirection(oldAxisX);
+ const int oldY = getRebel2MenuAxisDirection(oldAxisY);
+ const int newX = getRebel2MenuAxisDirection(axisX);
+ const int newY = getRebel2MenuAxisDirection(axisY);
+
+ if (newY != oldY && newY != 0)
+ queueMenuGamepadAxisKey(newY > 0 ? Common::KEYCODE_DOWN : Common::KEYCODE_UP);
+ else if (newX != oldX && newX != 0)
+ queueMenuGamepadAxisKey(newX > 0 ? Common::KEYCODE_RIGHT : Common::KEYCODE_LEFT);
+}
+
+bool InsaneRebel2::handleMenuGamepadAxisEvent(const Common::Event &event) {
+ if (event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_AXIS)
+ return false;
+
+ const int16 oldAxisX = combineRebel2MenuAxis(_menuGamepadAxisX, _menuGamepadRawAxisX);
+ const int16 oldAxisY = combineRebel2MenuAxis(_menuGamepadAxisY, _menuGamepadRawAxisY);
+ const int16 axisPosition = normalizeRebel2AxisMagnitude(event.joystick.position);
+ const bool pressed = axisPosition >= kRA2MenuAxisThreshold;
+
+ switch (event.customType) {
+ case kScummBackendActionRebel2AxisUp:
+ if (pressed)
+ _menuGamepadAxisY = -axisPosition;
+ else
+ _menuGamepadAxisY = 0;
+ break;
+
+ case kScummBackendActionRebel2AxisDown:
+ if (pressed)
+ _menuGamepadAxisY = axisPosition;
+ else
+ _menuGamepadAxisY = 0;
+ break;
+
+ case kScummBackendActionRebel2AxisLeft:
+ if (pressed)
+ _menuGamepadAxisX = -axisPosition;
+ else
+ _menuGamepadAxisX = 0;
+ break;
+
+ case kScummBackendActionRebel2AxisRight:
+ if (pressed)
+ _menuGamepadAxisX = axisPosition;
+ else
+ _menuGamepadAxisX = 0;
+ break;
+
+ default:
+ return false;
+ }
+
+ updateMenuGamepadAxisKey(oldAxisX, oldAxisY);
+ return true;
+}
+
+bool InsaneRebel2::handleMenuRawJoystickAxisEvent(const Common::Event &event) {
+ if (event.type != Common::EVENT_JOYAXIS_MOTION)
+ return false;
+
+ int16 axisX = 0;
+ int16 axisY = 0;
+ if (!decodeRebel2MenuKeyLikeAxis(event.joystick.axis, event.joystick.position, axisX, axisY))
+ return false;
+
+ const bool active = axisX != 0 || axisY != 0;
+ if (!active && _menuGamepadRawAxis != event.joystick.axis && _menuGamepadRawAxis != -1)
+ return false;
+
+ const int16 oldAxisX = combineRebel2MenuAxis(_menuGamepadAxisX, _menuGamepadRawAxisX);
+ const int16 oldAxisY = combineRebel2MenuAxis(_menuGamepadAxisY, _menuGamepadRawAxisY);
+ _menuGamepadRawAxis = active ? event.joystick.axis : -1;
+ _menuGamepadRawAxisX = axisX;
+ _menuGamepadRawAxisY = axisY;
+ updateMenuGamepadAxisKey(oldAxisX, oldAxisY);
+ return true;
+}
+
// notifyEvent -- EventObserver callback for global input dispatch.
// Handles ESC (skip) and SPACE (pause) regardless of menu state.
// Pause behavior matches original FUN_405A21: SPACE pauses, ANY key unpauses.
@@ -788,26 +929,8 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
}
if (_menuInputActive && event.type == Common::EVENT_JOYAXIS_MOTION) {
- const int axis = event.joystick.axis;
-
- if (!isRebel2RawMenuAxis(axis))
- return false;
-
- const Common::KeyCode keycode = getRebel2RawMenuAxisKey(axis, event.joystick.position);
- if (keycode == Common::KEYCODE_INVALID)
- return true;
-
- const uint32 now = _vm->_system->getMillis();
- if (_lastMenuGamepadNavigationTime != 0 &&
- now - _lastMenuGamepadNavigationTime < kRA2MenuGamepadNavigationDebounceMs)
+ if (handleMenuRawJoystickAxisEvent(event))
return true;
-
- _lastMenuGamepadNavigationTime = now;
- Common::Event syntheticEvent = Common::Event();
- syntheticEvent.type = Common::EVENT_KEYDOWN;
- syntheticEvent.kbd.keycode = keycode;
- _menuEventQueue.push(syntheticEvent);
- return true;
}
// Analog stick â reticle velocity (mirrors RA1's analog model). The mapped
@@ -815,53 +938,8 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
// 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) {
- if (event.joystick.position == 0) {
- switch (event.customType) {
- case kScummBackendActionRebel2AxisUp:
- case kScummBackendActionRebel2AxisDown:
- return true;
- case kScummBackendActionRebel2AxisLeft:
- case kScummBackendActionRebel2AxisRight:
- return true;
- default:
- break;
- }
- return true;
- }
-
- Common::KeyCode keycode = Common::KEYCODE_INVALID;
- switch (event.customType) {
- case kScummBackendActionRebel2AxisUp:
- keycode = Common::KEYCODE_UP;
- break;
- case kScummBackendActionRebel2AxisDown:
- keycode = Common::KEYCODE_DOWN;
- break;
- case kScummBackendActionRebel2AxisLeft:
- keycode = Common::KEYCODE_LEFT;
- break;
- case kScummBackendActionRebel2AxisRight:
- keycode = Common::KEYCODE_RIGHT;
- break;
- default:
- break;
- }
-
- if (keycode != Common::KEYCODE_INVALID) {
- const uint32 now = _vm->_system->getMillis();
- if (_lastMenuGamepadNavigationTime != 0 &&
- now - _lastMenuGamepadNavigationTime < kRA2MenuGamepadNavigationDebounceMs)
- return true;
-
- _lastMenuGamepadNavigationTime = now;
- Common::Event syntheticEvent = Common::Event();
- syntheticEvent.type = Common::EVENT_KEYDOWN;
- syntheticEvent.kbd.keycode = keycode;
- _menuEventQueue.push(syntheticEvent);
- return true;
- }
- }
+ if (_menuInputActive && handleMenuGamepadAxisEvent(event))
+ return true;
const int16 axisPosition = (event.joystick.position == Common::JOYAXIS_MIN)
? Common::JOYAXIS_MAX : event.joystick.position;
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 008bb55b591..7d3e61d038a 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -511,6 +511,11 @@ public:
int32 processMouse() override;
Common::Point getGameplayAimPoint();
Common::Point getRebelAutoPlayAimPoint();
+ void resetMenuGamepadAxis();
+ bool handleMenuGamepadAxisEvent(const Common::Event &event);
+ bool handleMenuRawJoystickAxisEvent(const Common::Event &event);
+ void updateMenuGamepadAxisKey(int16 oldAxisX, int16 oldAxisY);
+ void queueMenuGamepadAxisKey(Common::KeyCode keycode);
// Per-frame: pan the gameplay reticle incrementally from the held directional controls
// (on-screen/physical gamepad dpad, keyboard arrows) instead of snapping it to a screen
// edge. Call once per frame; getGameplayAimPoint() stays a pure getter.
@@ -531,6 +536,12 @@ public:
uint32 _gameplayMouseSettleUntil;
uint32 _lastGameplayMenuCloseTime;
uint32 _lastMenuGamepadNavigationTime;
+ // Menu-only axis state. Gameplay aiming continues to use _joystickAxisX/Y.
+ int16 _menuGamepadAxisX;
+ int16 _menuGamepadAxisY;
+ int _menuGamepadRawAxis;
+ int16 _menuGamepadRawAxisX;
+ int16 _menuGamepadRawAxisY;
void openGameplayMainMenu(SmushPlayer *splayer);
void openMenuMainMenu(SmushPlayer *splayer);
bool isBitSet(int n) override;
Commit: 1ef1e0e2e7e1a47cd123b44e22eba76b24aa2757
https://github.com/scummvm/scummvm/commit/1ef1e0e2e7e1a47cd123b44e22eba76b24aa2757
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-21T18:24:30+02:00
Commit Message:
SCUMM: RA2: simplified gamepad controls, no more return to center
Changed paths:
engines/scumm/insane/rebel2/rebel.cpp
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 5c307511965..981ec7a9993 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -63,10 +63,6 @@ const int kRA2GameplayMouseMaxX = 319;
const int kRA2GameplayMouseMaxY = 199;
const uint32 kRA2Handler7MouseSettleExtendMs = 1000;
-bool rebel2UsesRelativeGamepadAim(int selectedLevel) {
- return selectedLevel == 1 || selectedLevel == 5 || selectedLevel == 14;
-}
-
int16 normalizeRebel2AxisMagnitude(int16 position) {
return position == Common::JOYAXIS_MIN ? Common::JOYAXIS_MAX : ABS((int)position);
}
@@ -905,15 +901,11 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
// 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) normally hands control
- // back; handler 0x26 keeps ownership until its gamepad reticle returns to center,
- // except for relative gamepad aiming levels where the reticle deliberately holds.
+ // A genuine mouse/touch motion (nonzero relative delta) hands control back.
if (_gamepadAimActive && _gameState == kStateGameplay && !_menuInputActive) {
switch (event.type) {
case Common::EVENT_MOUSEMOVE:
if (event.relMouse.x != 0 || event.relMouse.y != 0) {
- if (_rebelHandler == 0x26 && !rebel2UsesRelativeGamepadAim(_selectedLevel))
- return true;
_gamepadAimActive = false; // real pointer motion takes over
break;
}
@@ -2006,65 +1998,7 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
int deltaY = 0;
bool activeGamepadAim = false;
- if (_rebelHandler == 0x26 && rebel2UsesRelativeGamepadAim(_selectedLevel)) {
- // Levels 1, 5, and 14 play best with the older mouse-like gamepad behavior from
- // ec305dee371/0025c4e1086: pan the reticle directly and leave it where
- // the player releases the stick. Later handler 0x26 levels keep the
- // original-style centered mapping for obstacle avoidance.
- if (dpadX || dpadY) {
- const int kLevel1DigitalStep = 3;
- deltaX = dpadX * kLevel1DigitalStep;
- deltaY = dpadY * kLevel1DigitalStep;
- activeGamepadAim = true;
- } else if (velX || velY) {
- const int kLevel1AnalogMaxStep = 8;
- deltaX = velX * kLevel1AnalogMaxStep / 127;
- deltaY = velY * kLevel1AnalogMaxStep / 127;
- activeGamepadAim = true;
- }
- } else if (_rebelHandler == 0x26) {
- // Original RA2 maps joystick axes into a small centered handler 0x26 reticle box.
- // Gamepad aiming deliberately exposes the full 320x200 mouse aim range
- // too, but preserves the original joystick feel: curved response for precise
- // center aiming, and automatic return to screen center when the stick is released.
- int axisX = 0;
- int axisY = 0;
- if (dpadX || dpadY) {
- axisX = dpadX * 127;
- axisY = dpadY * 127;
- } else {
- axisX = velX;
- axisY = velY;
- }
-
- if (axisX || axisY || _gamepadAimActive) {
- const Common::Point aimPos = getGameplayAimPoint();
- const int centerX = 160;
- const int centerY = 100;
- const int absAxisX = ABS(axisX);
- const int absAxisY = ABS(axisY);
- const int curvedX = (absAxisX * absAxisX + 126) / 127;
- const int curvedY = (absAxisY * absAxisY + 126) / 127;
- const int targetX = axisX < 0 ?
- centerX - curvedX * centerX / 127 :
- centerX + curvedX * (319 - centerX) / 127;
- const int targetY = axisY < 0 ?
- centerY - curvedY * centerY / 127 :
- centerY + curvedY * (199 - centerY) / 127;
- const int maxStep = (axisX || axisY) ? 14 : 10;
- const int distX = targetX - aimPos.x;
- const int distY = targetY - aimPos.y;
-
- if (distX || distY) {
- deltaX = CLIP<int>(distX, -maxStep, maxStep);
- deltaY = CLIP<int>(distY, -maxStep, maxStep);
- activeGamepadAim = true;
- } else {
- _gamepadAimActive = (axisX || axisY);
- return;
- }
- }
- } else if (_rebelHandler == 7) {
+ if (_rebelHandler == 7) {
int axisX = 0;
int axisY = 0;
if (dpadX || dpadY) {
@@ -2097,16 +2031,20 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
return;
}
}
- } else if (dpadX || dpadY) {
- const int kOriginalDigitalStep = 3;
- deltaX = dpadX * kOriginalDigitalStep;
- deltaY = dpadY * kOriginalDigitalStep;
- activeGamepadAim = true;
- } else if (velX || velY) {
- const int kAnalogMaxStep = 8;
- deltaX = velX * kAnalogMaxStep / 127;
- deltaY = velY * kAnalogMaxStep / 127;
- activeGamepadAim = true;
+ } 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;
+ deltaY = dpadY * kOriginalDigitalStep;
+ activeGamepadAim = true;
+ } else if (velX || velY) {
+ const int kAnalogMaxStep = 8;
+ deltaX = velX * kAnalogMaxStep / 127;
+ deltaY = velY * kAnalogMaxStep / 127;
+ activeGamepadAim = true;
+ }
}
if (!activeGamepadAim)
Commit: b8caec28c07ece990e69bb1a5b1394ace96541ff
https://github.com/scummvm/scummvm/commit/b8caec28c07ece990e69bb1a5b1394ace96541ff
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-21T18:24:30+02:00
Commit Message:
SCUMM: RA2: gamepad fixes for handler 7
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/rebel.cpp
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index c4d52b41477..726f62fe56a 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -869,21 +869,23 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
int16 scaledInputX = (int16)((inputX * 127) / 160);
int16 scaledInputY = _optControlsFlipped ? (int16)-inputY : inputY; // local_14
- // Direct mouse/touch/gamepad aiming can hold the cursor at an edge
- // indefinitely. Keep this sensitivity concession local to Handler 7
- // third-person ship steering. A held analog stick is already bounded by its
- // physical range, so keep its full logical range for more responsive L10 flight.
- if (!_gamepadAimActive) {
+ 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.
+ if (!useDirectGamepadFlight) {
scaledInputX = (int16)((scaledInputX * kRA2Handler7DirectInputNumerator) /
kRA2Handler7DirectInputDenominator);
scaledInputY = (int16)((scaledInputY * kRA2Handler7DirectInputNumerator) /
kRA2Handler7DirectInputDenominator);
}
- // Mouse/touch can hold an absolute cursor at an edge indefinitely, so they use
- // bounded target steering. Gamepad input for Handler 7 now feeds the original
- // center-relative flight axes and lets the assembly-derived velocity history,
- // lift/slide tables, wind, and clamps produce the ship movement.
- const bool useTargetSteering = !_gamepadAimActive;
+ // 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) {
mouseFlightTargetX = (int16)(0xd4 + (scaledInputX * kRA2Handler7MouseTargetRangeX) / 127);
@@ -954,7 +956,7 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
// letting a held off-center cursor keep pushing the ship until it bounces.
if (useTargetSteering) {
int targetDeltaX = mouseFlightTargetX - _flyShipScreenX;
- const int targetSteeringDivisor = _gamepadAimActive ? 2 : 4;
+ const int targetSteeringDivisor = 4;
positionDeltaX = (int16)CLIP<int>(targetDeltaX / targetSteeringDivisor, -12, 12);
if (positionDeltaX == 0 && targetDeltaX != 0)
positionDeltaX = (targetDeltaX < 0) ? -1 : 1;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 981ec7a9993..e929510663e 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -2009,22 +2009,50 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
axisY = velY;
}
+ const bool useDirectHandler7Gamepad = _selectedLevel == 10;
+ if (!useDirectHandler7Gamepad && !axisX && !axisY)
+ return;
+
if (axisX || axisY || _gamepadAimActive) {
const Common::Point aimPos = getGameplayAimPoint();
const int centerX = 160;
const int centerY = 100;
- const int targetX = (axisX < 0) ?
- centerX + axisX * centerX / 127 :
- centerX + axisX * (319 - centerX) / 127;
- const int targetY = (axisY < 0) ?
- centerY + axisY * centerY / 127 :
- centerY + axisY * (199 - centerY) / 127;
+ int targetX;
+ int targetY;
+ if (useDirectHandler7Gamepad) {
+ targetX = (axisX < 0) ?
+ centerX + axisX * centerX / 127 :
+ centerX + axisX * (319 - centerX) / 127;
+ targetY = (axisY < 0) ?
+ 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.
+ const int kHandler7HorizontalRange = 120;
+ targetX = axisX ?
+ CLIP<int>(centerX + axisX * kHandler7HorizontalRange / 127, 0, 319) :
+ aimPos.x;
+ targetY = axisY ?
+ ((axisY < 0) ?
+ centerY + axisY * centerY / 127 :
+ centerY + axisY * (199 - centerY) / 127) :
+ aimPos.y;
+ }
const int distX = targetX - aimPos.x;
const int distY = targetY - aimPos.y;
if (distX || distY) {
- deltaX = distX;
- deltaY = distY;
+ if (useDirectHandler7Gamepad) {
+ deltaX = distX;
+ deltaY = distY;
+ } else {
+ const int kHandler7HorizontalMaxStep = 24;
+ const int kHandler7VerticalMaxStep = 32;
+ deltaX = CLIP<int>(distX, -kHandler7HorizontalMaxStep, kHandler7HorizontalMaxStep);
+ deltaY = CLIP<int>(distY, -kHandler7VerticalMaxStep, kHandler7VerticalMaxStep);
+ }
activeGamepadAim = true;
} else {
_gamepadAimActive = true;
Commit: 2bd3cf279f320b9dc382af85b6af52bd256b3cb7
https://github.com/scummvm/scummvm/commit/2bd3cf279f320b9dc382af85b6af52bd256b3cb7
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-21T18:24:31+02:00
Commit Message:
SCUMM: RA2: perspective/damage fixes for handler 7
Changed paths:
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index e929510663e..25ef2db64b2 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -2010,7 +2010,8 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
}
const bool useDirectHandler7Gamepad = _selectedLevel == 10;
- if (!useDirectHandler7Gamepad && !axisX && !axisY)
+ const bool useCenteredHandler7Gamepad = (_selectedLevel == 3 && _flyControlMode == 1);
+ if (!useDirectHandler7Gamepad && !useCenteredHandler7Gamepad && !axisX && !axisY)
return;
if (axisX || axisY || _gamepadAimActive) {
@@ -2029,22 +2030,23 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
} 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.
+ // 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 ?
CLIP<int>(centerX + axisX * kHandler7HorizontalRange / 127, 0, 319) :
- aimPos.x;
+ (useCenteredHandler7Gamepad ? centerX : aimPos.x);
targetY = axisY ?
- ((axisY < 0) ?
- centerY + axisY * centerY / 127 :
- centerY + axisY * (199 - centerY) / 127) :
- aimPos.y;
+ CLIP<int>(centerY + axisY * kHandler7VerticalRange / 127, 0, 199) :
+ (useCenteredHandler7Gamepad ? centerY : aimPos.y);
}
const int distX = targetX - aimPos.x;
const int distY = targetY - aimPos.y;
if (distX || distY) {
- if (useDirectHandler7Gamepad) {
+ if (useDirectHandler7Gamepad || useCenteredHandler7Gamepad) {
deltaX = distX;
deltaY = distY;
} else {
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 7d3e61d038a..a9f2f80ffb9 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -642,6 +642,7 @@ public:
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();
Common::Point getHandler8ShotTargetPoint();
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index b378b56093a..e7538874c1a 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -630,18 +630,22 @@ Common::Point InsaneRebel2::getHandler7ShipDrawPoint() {
projected.y - 0x82 + _flyShipSprite->getCharYOffset(spriteIndex));
}
-Common::Point InsaneRebel2::getHandler7ProjectedPoint() {
+Common::Point InsaneRebel2::getHandler7ProjectedPointFor(int16 x, int16 y) {
int viewTilt = (_viewShift * 5) / 128;
int xSkew = (viewTilt * 9) / 4;
int ySkew = viewTilt * 5;
- int relX = _flyShipScreenX - (_perspectiveX + 0xd4);
- int relY = _flyShipScreenY - (_perspectiveY + 0x82);
+ int relX = x - (_perspectiveX + 0xd4);
+ int relY = y - (_perspectiveY + 0x82);
int projectedX = (xSkew * relY) / 0x55 + relX + 0xa0 + _viewX;
int projectedY = relY + 0x55 - (ySkew * relX) / 0xa0 + _viewY;
return Common::Point(projectedX, projectedY);
}
+Common::Point InsaneRebel2::getHandler7ProjectedPoint() {
+ return getHandler7ProjectedPointFor(_flyShipScreenX, _flyShipScreenY);
+}
+
Common::Point InsaneRebel2::getHandler7ShotTargetPoint() {
// Handler 7 targets the projected ship/crosshair point computed in
// FUN_0040d836; it does not use the generic mouse position for combat shots.
@@ -2370,6 +2374,19 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
if (maxScrollY < 0)
maxScrollY = 0;
+ if (_rebelHandler == 7) {
+ // High-detail Handler 7 follows FUN_0041C5C0: the oversized 424x260
+ // flight buffer is presented through a perspective-derived 320x170
+ // gameplay window at source offset (0x34,0x2d) + DAT_00443712/14.
+ // 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);
+ _player->setScrollOffset(_viewX, _viewY);
+ return;
+ }
+
// Simple linear mapping: Center of screen corresponds to center of buffer.
Common::Point aimPos = getGameplayAimPoint();
_viewX = (aimPos.x * maxScrollX) / viewportWidth;
@@ -2640,9 +2657,11 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
// Original: FUN_00403240 only runs handlers when DAT_0047a814 == 0.
processMouse();
- _gameplayPresentationClipBottom = statusBarY - 1;
- if (isHiRes() && _rebelHandler == 7 && curFrame != 0)
- _gameplayPresentationClipBottom = MIN(_gameplayPresentationClipBottom, 170 * 2 - 1);
+ // Handler 7's high-detail flight view uses a 320x170 gameplay area after
+ // the first frame; the remaining RA2 gameplay handlers keep the 180px split.
+ const int statusScale = (statusBarY >= 360) ? 2 : 1;
+ const int gameplayStatusBarY = (_rebelHandler == 7 && curFrame != 0) ? 170 * statusScale : statusBarY;
+ _gameplayPresentationClipBottom = gameplayStatusBarY - 1;
_hiResPresentationViewX = 0;
_hiResPresentationViewY = 0;
@@ -2690,7 +2709,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
// We draw directly to screen at Y=180.
// STEP 0: Fill status bar background (FUN_004288c0).
- renderStatusBarBackground(renderBitmap, pitch, width, height, videoWidth, videoHeight, statusBarY);
+ renderStatusBarBackground(renderBitmap, pitch, width, height, videoWidth, videoHeight, gameplayStatusBarY);
// Ship rendering. Handler 7 is drawn later, after its lasers, matching
// FUN_0040d836's order so the ship covers the muzzle end of the beams.
@@ -2731,7 +2750,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
renderEmbeddedHudOverlays(renderBitmap, pitch, width, height);
// STEP 2: Draw DISPFONT.NUT status bar sprites (FUN_0041c012).
- renderStatusBarSprites(renderBitmap, pitch, width, height, statusBarY, curFrame);
+ renderStatusBarSprites(renderBitmap, pitch, width, height, gameplayStatusBarY, curFrame);
updateGameplayDamageEffects(renderBitmap, pitch, width, height);
checkGameplayPostRenderCollisions(renderBitmap, pitch, width, height, curFrame);
@@ -2747,7 +2766,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
renderHandler8PovOverlay(renderBitmap, pitch, width, height);
// HUD score/lives rendering (FUN_0041c012).
- renderScoreHUD(renderBitmap, pitch, width, height, statusBarY);
+ renderScoreHUD(renderBitmap, pitch, width, height, gameplayStatusBarY);
// Reset FOBJ position offsets (FUN_00424510(0,0) in original FUN_0041DB5E line 271).
if (_player) {
@@ -3524,9 +3543,13 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
}
} else {
int16 bottomDist = _corridorBottomY - _flyShipScreenY;
- int bottomX = shipCenterX;
- int bottomY = (_corridorBottomY - 0x82) + _perspectiveY + 100 +
- (renderHiRes ? nativeViewY : _viewY);
+ Common::Point bottomProjected = getHandler7ProjectedPointFor(_flyShipScreenX, _corridorBottomY);
+ if (renderHiRes) {
+ bottomProjected.x += nativeViewX;
+ bottomProjected.y += nativeViewY;
+ }
+ int bottomX = bottomProjected.x;
+ int bottomY = bottomProjected.y;
if (bottomDist < 0x19) {
_flyEffectAnimCounter++;
@@ -3580,8 +3603,9 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
shipDraw.x, shipDraw.y, _flyTargetSprite, spriteIndex);
}
- debugC(DEBUG_INSANE, "Handler7Ship: draw=(%d,%d) sprite=%d/%d shipPos=(%d,%d) persp=(%d,%d) smoothVel=%d vertIn=%d fxCtr=%d fxRep=%d",
+ 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",
drawX, drawY, spriteIndex, numSprites, _flyShipScreenX, _flyShipScreenY,
+ renderHiRes ? nativeViewX : _viewX, renderHiRes ? nativeViewY : _viewY,
_perspectiveX, _perspectiveY, _smoothedVelocity, _verticalInput, _flyEffectAnimCounter, _flyOverlayRepeatCount);
}
@@ -4269,9 +4293,11 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
const int nativeViewY = renderHiRes ? _hiResPresentationViewY : _viewY;
if (spriteIndex >= 0 && spriteIndex < numChars) {
- // Compute ship screen position (simplified FUN_0041c720 transform)
- int shipDrawX = (_flyShipScreenX - 0xd4) + _perspectiveX + 160 + nativeViewX;
- int shipDrawY = (_flyShipScreenY - 0x82) + _perspectiveY + 100 + nativeViewY;
+ Common::Point shipProjected = getHandler7ProjectedPoint();
+ if (renderHiRes) {
+ shipProjected.x += nativeViewX;
+ shipProjected.y += nativeViewY;
+ }
// Per-direction offset from ship center.
// Original uses lookup tables (DAT_004438da etc.) indexed by
@@ -4295,8 +4321,8 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
break;
}
- int drawX = shipDrawX + offsetX;
- int drawY = shipDrawY + offsetY;
+ int drawX = shipProjected.x + offsetX;
+ int drawY = shipProjected.y + offsetY;
if (renderHiRes) {
drawX = (drawX - nativeViewX) * 2;
drawY = (drawY - nativeViewY) * 2;
Commit: 8c086c04d6ae46ded60a36b0ea04dcce16497373
https://github.com/scummvm/scummvm/commit/8c086c04d6ae46ded60a36b0ea04dcce16497373
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-21T18:24:31+02:00
Commit Message:
SCUMM: RA2: use B to go back/skip cutscenes
Changed paths:
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/metaengine.cpp
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 25ef2db64b2..81a1364d665 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -1054,9 +1054,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
case kScummActionInsaneAttack:
keycode = Common::KEYCODE_RETURN;
break;
- case kScummActionInsaneSwitch:
- keycode = Common::KEYCODE_ESCAPE;
- break;
case kScummActionInsaneBack:
keycode = Common::KEYCODE_ESCAPE;
break;
@@ -1071,8 +1068,7 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
case kStateTopPilots:
if (event.customType == kScummActionInsaneAttack)
keycode = Common::KEYCODE_RETURN;
- else if (event.customType == kScummActionInsaneSwitch ||
- event.customType == kScummActionInsaneBack ||
+ else if (event.customType == kScummActionInsaneBack ||
event.customType == kScummActionInsaneSkip)
keycode = Common::KEYCODE_ESCAPE;
break;
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index 06c6bd74354..3ed790a2d3c 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -1295,7 +1295,7 @@ Common::KeymapArray ScummMetaEngine::initKeymaps(const char *target) const {
act->addDefaultInputMapping("JOY_A");
rebel2Keymap->addAction(act);
- act = new Action("RA2COVER", _("Cover / back"));
+ act = new Action("RA2COVER", _("Cover"));
act->setCustomEngineActionEvent(kScummActionInsaneSwitch);
act->addDefaultInputMapping("JOY_X");
act->addDefaultInputMapping("JOY_Y");
Commit: 5fc31be42d3657b3b35b81fa88ff25f137e6087f
https://github.com/scummvm/scummvm/commit/5fc31be42d3657b3b35b81fa88ff25f137e6087f
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-21T18:24:31+02:00
Commit Message:
SCUMM: RA2: clean-up of comments for INSANE code
Changed paths:
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
diff --git a/engines/scumm/insane/rebel2/audio.cpp b/engines/scumm/insane/rebel2/audio.cpp
index bf3b26e9122..6cbc4e3e1f4 100644
--- a/engines/scumm/insane/rebel2/audio.cpp
+++ b/engines/scumm/insane/rebel2/audio.cpp
@@ -34,9 +34,7 @@
namespace Scumm {
-// ---------------------------------------------------------------------------
// Audio Handling
-// ---------------------------------------------------------------------------
// RA2 doesn't use iMUSE -- audio is handled directly through the mixer.
// initAudio -- Initialize audio system for RA2.
@@ -63,22 +61,16 @@ void InsaneRebel2::queueAudioData(int trackIdx, uint8 *data, int32 size, int vol
_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.
-// Original: FUN_0042a3b0 loads into DAT_00456888[0..7].
const char *const kRA2SfxFiles[InsaneRebel2::kRA2NumSfx] = {
"SYSTM/BLAST.SAD", // 0 - Player laser fire
@@ -91,7 +83,6 @@ const char *const kRA2SfxFiles[InsaneRebel2::kRA2NumSfx] = {
"SYSTM/TBLAST.SAD" // 7 - TIE blast
};
-// loadSfx -- Load all SAD files from SYSTM/ directory (FUN_0042a3b0).
void InsaneRebel2::loadSfx() {
for (int i = 0; i < kRA2NumSfx; i++) {
ScummFile *file = _vm->instantiateScummFile();
@@ -207,7 +198,6 @@ void InsaneRebel2::playSfx(int slot, int volume, int pan) {
debugC(DEBUG_INSANE, "InsaneRebel2::playSfx: slot=%d vol=%d pan=%d size=%d", slot, mixVolume, clampedPan, _sfxSize[slot]);
}
-// loadAuxSfx -- Load sound data into auxiliary buffer (FUN_004118df).
void InsaneRebel2::loadAuxSfx(int buffer, const byte *data, uint32 size) {
if (buffer < 0 || buffer >= kRA2NumAuxSfx || !data || size == 0) {
return;
@@ -224,7 +214,6 @@ void InsaneRebel2::loadAuxSfx(int buffer, const byte *data, uint32 size) {
debugC(DEBUG_INSANE, "InsaneRebel2::loadAuxSfx: buffer=%d size=%d", buffer, size);
}
-// playAuxSfx -- Play from auxiliary buffer (FUN_00411931).
// 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) {
@@ -235,7 +224,6 @@ void InsaneRebel2::playAuxSfx(int buffer, int volume, int pan) {
_vm->_mixer->stopHandle(_auxSfxHandles[buffer]);
- // The auxiliary buffer data goes through FUN_00425fc0 (format dispatch) in the original.
// 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];
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 726f62fe56a..3063a0a8f9a 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -62,12 +62,9 @@ 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);
@@ -78,7 +75,6 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
// 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.
@@ -92,9 +88,7 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
// 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
@@ -108,18 +102,6 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
}
}
- // For Handler 25 (Level 2 speeder bike), draw the corridor overlay BEFORE FOBJ decoding.
- // The corridor overlay (par3=4 -> _rebelEmbeddedHud[4]) is DAT_00482268, a 350x230 buffer.
- // From FUN_0041cadb line 216: FUN_00428a10(param_1,0,DAT_0045790c,DAT_0045790e,DAT_00482268)
- // It's drawn at (DAT_0045790c, DAT_0045790e) which are _rebelViewOffsetX/Y.
- //
- // For Mode 1: DAT_0045790c = damageLevel * -5 - 14, range -39 (covered) to -14 (uncovered)
- //
- // From FUN_00428a10: When position is negative, we skip source pixels and draw at 0.
- // Handler 25: Corridor overlay and FOBJ position offsets are set during
- // IACT opcode 6 processing (iactRebel2Opcode6), matching the original
- // FUN_41CADB architecture. No corridor drawing needed here.
-
// 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
@@ -207,34 +189,14 @@ void InsaneRebel2::procIACT(byte *renderBitmap, int32 codecparam, int32 setupsan
iactRebel2Scene1(renderBitmap, codecparam, setupsan12, setupsan13, b, size, flags, par1, par2, par3, par4);
}
-// iactRebel2Scene1 -- Scene 1 IACT dispatcher (FUN_4028C5 / FUN_4033CF).
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) {
- // par1 is the Opcode (word at offset +0)
- // par2 is word at offset +2
- // par3 is word at offset +4
- // par4 is word at offset +6
- //
- // Based on disassembly of FUN_4028C5 and FUN_4033CF:
- //
// For IACT opcode 4 (enemy position update), the structure is:
- // Offset +0x06: Type/SubType (par3)
- // Offset +0x08: Enemy ID
- // Offset +0x0A: X position
- // Offset +0x0C: Y position
- // Offset +0x0E: Width
- // Offset +0x10: Height
- //
- // The original game calculates bounding box center:
// centerX = X + (Width / 2)
// centerY = Y + (Height / 2)
// Then subtracts scroll offsets:
- // screenX = centerX - DAT_0043e006 (scrollX)
- // screenY = centerY - DAT_0043e008 (scrollY)
- // screenX = centerX - DAT_0043e006 (scrollX)
- // screenY = centerY - DAT_0043e008 (scrollY)
if (par1 == 4) {
enemyUpdate(renderBitmap, b, par2, par3, par4);
@@ -245,7 +207,6 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
iactRebel2Opcode3(b, par2, par3, par4);
}
else if (par1 == 5) {
- // Opcode 5: Collision Zone Registration (FUN_004033cf case 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
@@ -253,22 +214,12 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
if (par2 == 0x0D || par2 == 0x0E) {
// Register the collision zone from the remaining IACT data
- // par4 (userId from IACT header) is the filter value used by FUN_4092D9
- // for the < 1000 test (offset +6 in the original stored pointer)
registerCollisionZone(b, par2, par4);
}
} else if (par1 == 7) {
- // Opcode 7: Handler 7 corridor/velocity control (FUN_40C3CC case 5)
// IACT header: par1=7, par2=flags, par3=0, par4=sub-opcode
// Body contains 2 int16 values (body[0], body[1])
- //
- // par4 sub-opcodes (from FUN_40C3CC case 5 switch on param_5[3]):
- // 0: Set velocity params (DAT_00443b12, DAT_00443b14)
- // 1: Set left X + top Y corridor boundaries (DAT_00443b0a, DAT_00443b0c)
- // 2: Set right X + bottom Y corridor boundaries (DAT_00443b0e, DAT_00443b10)
- // 5: Set flag (DAT_00443b52)
-
int16 body0 = 0, body1 = 0;
if (b.size() - b.pos() >= 4) {
body0 = b.readSint16LE();
@@ -277,8 +228,6 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
switch (par4) {
case 0:
- // Velocity/wind data â affects ship drift in FUN_40C3CC physics
- // DAT_00443b12 = horizontal wind, DAT_00443b14 = vertical wind
_windParamX = body0;
_windParamY = body1;
debugC(DEBUG_INSANE, "Opcode 7 par4=0: wind=(%d,%d)", body0, body1);
@@ -287,7 +236,6 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
// Set LEFT X boundary and TOP Y boundary
_corridorLeftX = body0;
_corridorTopY = body1;
- // Mode-dependent margin adjustment (FUN_40C3CC lines 341-351)
if (_flyControlMode == 2) {
_corridorLeftX += 15;
} else if (_flyControlMode == 0) {
@@ -300,7 +248,6 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
// Set RIGHT X boundary and BOTTOM Y boundary
_corridorRightX = body0;
_corridorBottomY = body1;
- // Mode-dependent margin adjustment (FUN_40C3CC lines 356-365)
if (_flyControlMode == 2) {
_corridorRightX -= 15;
} else if (_flyControlMode == 0) {
@@ -310,7 +257,6 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
body0, body1, _corridorRightX);
break;
case 5:
- // DAT_00443b52: repeats FLY002 ship overlay in FUN_40D836.
_flyOverlayRepeatCount = body0;
debugC(DEBUG_INSANE, "Opcode 7 par4=5: flyOverlayRepeat=%d", _flyOverlayRepeatCount);
break;
@@ -320,10 +266,8 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
}
} else if (par1 == 6) {
- // Opcode 6: Level setup / mode switch (FUN_41CADB case 4)
iactRebel2Opcode6(renderBitmap, b, size, par2, par3, par4);
} else if (par1 == 8) {
- // Opcode 8: HUD resource loading (FUN_41CADB case 6)
iactRebel2Opcode8(renderBitmap, b, size, par2, par3, par4);
} else if (par1 == 9) {
// Opcode 9: Text/subtitle display
@@ -336,9 +280,7 @@ void InsaneRebel2::iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32
}
}
-// iactRebel2Opcode2 -- Link table and state setup (FUN_00407fcb).
void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
- // Handle IACT opcode 2 subcases based on par3 (type). Mirrors FUN_00407fcb behavior where relevant.
// Keep existing linking behavior (par3 == 4) for compatibility.
// Link case: par3 == 4
@@ -347,10 +289,8 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
int16 parentId = b.readSint16LE(); // Offset +10
// Validate BOTH parentId AND childId to avoid triggering "set/clear ALL bits" behavior
- // when childId <= 0. The original game's setBit(0)/clearBit(0) affects ALL bits,
// which would disable/enable all enemies at once - not the intended linking behavior.
if (parentId >= 1 && parentId < 512 && childId >= 1 && childId < 512) {
- // Shift links (original: 4 link slots at DAT_0045797c/817c/897c/917c)
_rebelLinks[parentId][2] = _rebelLinks[parentId][1];
_rebelLinks[parentId][1] = _rebelLinks[parentId][0];
_rebelLinks[parentId][0] = childId;
@@ -358,8 +298,6 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
// Mirror parent's bit state to child (INVERTED):
// - Parent alive (bit clear) â setBit(child) â child hidden
// - Parent dead (bit set) â clearBit(child) â child shown
- // From FUN_0041CADB case 0, par3==4:
- // bVar3 = FUN_00423970(parentId);
// 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.
@@ -375,21 +313,17 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
}
return;
} else if (par3 == 1) { // Probabilistic / counter cases: par3 == 1
- int16 value = par4; // sVar6
- int16 targetId = b.readSint16LE(); // Offset +8 (sVar7)
+ int16 value = par4;
+ int16 targetId = b.readSint16LE();
// Validate targetId >= 1 to avoid triggering "set/clear ALL bits" behavior
- // The original game's setBit(0)/clearBit(0) affects ALL bits, not intended here
if (targetId < 1 || targetId >= 0x200)
return;
- // Handler 8/25: FUN_401234 case 0 / FUN_0041CADB case 0 par3==1
- // From original FUN_0041CADB:
// 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) {
- // par4==100: Force enable the target (original: FUN_00423a00)
clearBit(targetId);
debugC(DEBUG_INSANE, "Opcode2 (H%d): Force ENABLE target=%d (par4=100)", _rebelHandler, targetId);
} else {
@@ -460,41 +394,19 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
}
}
-// iactRebel2Opcode3 -- Damage and hit counter processing (FUN_4092D9 / FUN_40E35E / FUN_401234).
void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
// IACT opcode 3 â damage and hit counter processing.
- // Based on FUN_4092D9 (Handler 0x26), FUN_40E35E (Handler 7), FUN_401234 (Handler 8).
- //
- // The common dispatcher (FUN_4033CF) stores opcode 3 entries in the projectile impact
- // list (DAT_0043f9e0). For handlers 0x26/7 these are processed per-frame by the
- // per-handler collision function (FUN_4092D9/FUN_40E35E). For handlers 8/25 they're
// processed immediately during IACT dispatch.
- //
- // FUN_403ba9() loop in FUN_4092D9 (lines 209-239):
// par3 == 1/2: Direct hit â increment hit counter, apply damage if conditions met
- // - body[0] (offset +8): srcId for isBitSet check
- // - par4 != 0: damage from DAT_0047e0f4 (direct hit damage table)
// - par3==1: par4 must be 1..9 for damage
// - par3==2: par4 must be > 99, with wave state bit check for par4 >= 101
- //
- // par3 == 5: Probabilistic damage â probability check from DAT_0047e0fc
- // - body[1] (offset +10): srcId for isBitSet check (different from par3=1/2!)
- // - Damage from DAT_0047e0f8 (probabilistic damage table)
- //
- // Stream position on entry: at offset +8 (body[0], first word after 8-byte header)
-
- // Handler 25 has a different opcode 3 structure (FUN_41CADB case 1):
- // par3==5: probabilistic damage WITH cover check (DAT_0045790a < 2)
// par3==1: increment hit counter ONLY (NO damage), requires par4 != 4
// par4==100: direct damage (separate check after par3 branches, NO cover check)
- // Other handlers (0x26/7/8) use FUN_4092D9/FUN_40E35E/FUN_401234 with different logic.
if (_rebelHandler == 25) {
- // Handler 25 opcode 3 â FUN_41CADB case 1
int16 srcIdBody0 = b.readSint16LE(); // body[0] (offset +8)
int16 srcIdBody1 = b.readSint16LE(); // body[1] (offset +10)
if (par3 == 5) {
- // Probabilistic damage with cover check (lines 81-92)
debugC(DEBUG_INSANE, "Opcode3: H25 par3=5 srcId=%d isBitSet=%d damageLevel=%d",
srcIdBody1, isBitSet(srcIdBody1), _rebelDamageLevel);
@@ -518,7 +430,6 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
_rebelDamageLevel, isBitSet(srcIdBody1));
}
} else if (par3 == 1 && !isBitSet(srcIdBody0) && par4 != 4) {
- // Hit counter only â NO damage (lines 94-98)
_rebelHitCounter++;
debugC(DEBUG_INSANE, "H25 hit counter++ -> %d (par3=1 par4=%d, no damage)",
_rebelHitCounter, par4);
@@ -526,7 +437,6 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
debugC(DEBUG_INSANE, "Opcode3: H25 par3=%d par4=%d (no action)", par3, par4);
}
- // Direct damage: par4==100, separate from par3 branches (lines 99-111)
if (par4 == 100 && !isBitSet(srcIdBody0)) {
LevelDifficultyParams dparams = getDifficultyParams();
int directHitDamage = (dparams.missDamage >= 0) ? dparams.missDamage : 0;
@@ -538,7 +448,6 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
initDamageFlash();
}
} else if (par3 == 1 || par3 == 2) {
- // Non-Handler-25 direct hit path â FUN_4092D9 lines 209-227
int16 srcId = b.readSint16LE(); // body[0] (offset +8): source enemy ID
debugC(DEBUG_INSANE, "Opcode3: par3=%d par4=%d srcId=%d isBitSet=%d",
@@ -573,7 +482,6 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
}
}
} else if (par3 == 5) {
- // Non-Handler-25 probabilistic damage â FUN_4092D9 lines 228-239
b.skip(2); // Skip body[0]
int16 srcId = b.readSint16LE(); // body[1] (offset +10)
@@ -606,9 +514,7 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
}
}
-// Helper split out of FUN_004033CF case 6; not a separate original function.
void InsaneRebel2::updateOpcode6Handler(int16 par2) {
- // Update handler type if par2 is a known handler value (from FUN_4033CF case 6).
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) {
@@ -619,13 +525,9 @@ void InsaneRebel2::updateOpcode6Handler(int16 par2) {
}
}
-// Helper split out of FUN_00401234 case 4; not a separate original function.
void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 par4) {
- // Handler 8 specific logic (third-person on foot) - FUN_00401234 case 4.
- // DAT_0043e000 = local_14[3], which maps to the IACT header's par4/userId.
_shipLevelMode = par4;
- // local_14[4] is the first body word after the 8-byte IACT header.
int16 bodyStatusFlag = 0;
if (b.pos() + 2 <= b.size()) {
int64 savedPos = b.pos();
@@ -633,7 +535,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
b.seek(savedPos);
}
- // If local_14[4] == 1, enable status bar and re-render laser texture (FUN_0040bb87)
if (bodyStatusFlag == 1) {
_rebelStatusBarSprite = 5;
if (_smush_iconsNut && _smush_iconsNut->getNumChars() > 5) {
@@ -641,7 +542,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
}
}
- // Reset state when shipLevelMode != 0 && local_14[4] == 1 (FUN_00401234 lines 97-103)
// Guard with _rebelOp6Initialized: runs once per wave video, not per frame.
if (_shipLevelMode != 0 && bodyStatusFlag == 1 && !_rebelOp6Initialized) {
clearBit(0);
@@ -657,8 +557,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
// Skip position calculation for special modes 4 and 5
if (_shipLevelMode != 4 && _shipLevelMode != 5) {
- // ----- Movement Range Transition (Covered vs Shooting) -----
- // Based on FUN_00401234 lines 85-120:
// 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
@@ -682,9 +580,6 @@ 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
- // Based on FUN_00401234 lines 151-166:
- // local_18 = ((DAT_0047a7e0 * 5 + 0x27b) * 0x40) / 0xfe
- // local_1c = ((DAT_0047a7e2 * 5 + 0x27b) * 0x10) / 0xfe
// Map the effective aim position (-127 to 127 range) to the ship target.
Common::Point aimPos = getGameplayAimPoint();
@@ -692,7 +587,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
int16 mouseOffsetY = (int16)((aimPos.y - 100) * 127 / 100);
// Clamp X offset to movement range limit (covered/shooting state)
- // Based on FUN_00401234 lines 119-136
if (mouseOffsetX > _movementRangeLimit)
mouseOffsetX = _movementRangeLimit;
if (mouseOffsetX < -_movementRangeLimit)
@@ -703,13 +597,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
if (mouseOffsetY < -127)
mouseOffsetY = -127;
- // Calculate target positions using the original formula
- // Original FUN_00401234 lines 151-166:
- // local_18 = ((mouseX * 5 + 0x27b) * 0x40) / 0xfe -> X target
- // local_1c = ((mouseY * 5 + 0x27b) * 0x10) / 0xfe -> Y target
- // _DAT_0043e004 = -local_1c (stored negated for cursor display)
- // The interpolation (lines 181-193) uses local_1c (positive), NOT _DAT_0043e004.
- // So the interpolation target must be the positive formula result.
_shipTargetX = (int16)(((mouseOffsetX * 5 + 0x27b) * 0x40) / 0xfe);
_shipTargetY = (int16)(((mouseOffsetY * 5 + 0x27b) * 0x10) / 0xfe);
@@ -731,10 +618,7 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
_shipPosY = (newY < _shipTargetY) ? _shipTargetY : newY;
}
- // FUN_00401234 calls FUN_00424510(-DAT_0043e006, -DAT_0043e008)
- // after updating the handler-8 camera. This shifts subsequent FOBJ
- // decoding into screen coordinates; FUN_00401CCF then draws HUD and
- // weapon sprites without a separate final-buffer scroll.
+ // Shift subsequent FOBJ weapon sprites after moving the handler-8 camera.
if (_player) {
_player->_fobjOffsetX = -_shipPosX;
_player->_fobjOffsetY = -_shipPosY;
@@ -785,7 +669,6 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
}
// Update firing state from mouse button or joystick fire action
- // Mode 4 (autopilot) disables shooting - FUN_00401CCF line 82-84
if (_shipLevelMode == 4) {
_shipFiring = false;
} else {
@@ -798,12 +681,9 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
_shipDirectionH, _shipDirectionV, _shipDirectionIndex);
}
-// Helper split out of FUN_0040C3CC case 4; not a separate original function.
void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 par4) {
- // Handler 7 specific logic (third-person ship) - FUN_0040d836 / FUN_0040c3cc
// Used for Level 3 and similar space combat levels.
- // Set control mode: DAT_004437c0 = param_5[3] = par4 in FUN_40C3CC case 4.
// 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)
@@ -813,10 +693,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");
- // Status bar: param_5[4] == 1 in original (first body word, 5th IACT word)
- // In our parsing, par3 maps to param_5[2] and the body follows par4.
- // FUN_40C3CC: if (param_5[4] == 1) FUN_0040bb87(DAT_0047a828,5);
- // par3 is param_5[2], which the original doesn't use here.
// The body word for status bar is read separately below.
int16 bodyStatusFlag = 0;
if (b.size() - b.pos() >= 2) {
@@ -830,31 +706,23 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
debugC(DEBUG_INSANE, "Opcode 6 (Handler 7): Status bar enabled (body flag=%d)", bodyStatusFlag);
}
- // Ship position update - FUN_40C3CC case 4, lines 49-327.
// 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
- //
- // Level data table (DAT_0047e0e8 + difficulty*0x242 + levelType*0x22):
// offset 2: Y speed offset 4: X speed (levelSpeed)
// offset 6: wind multiplier
- // Our extracted difficulty table starts at DAT_0047e0f0, so for Handler 7
// 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];
- // Step 1: Raw mouse input as offset from screen center.
- // DAT_0047a7e0 = mouseX - 160, DAT_0047a7e2 = mouseY - 100.
- // Handler 7 applies DAT_0047a7fe to its local vertical input after clamping.
const Common::Point aimPos = getGameplayAimPoint();
const int16 mouseX = aimPos.x;
const int16 mouseY = aimPos.y;
- int16 inputX = (int16)(mouseX - 160); // DAT_0047a7e0
- int16 inputY = (int16)(mouseY - 100); // DAT_0047a7e2
+ int16 inputX = (int16)(mouseX - 160);
+ int16 inputY = (int16)(mouseY - 100);
- // Clamp: mouse mode uses [-160, 160] for X, [-127, 127] for Y (lines 55-70).
if (inputX > 160)
inputX = 160;
if (inputX < -160)
@@ -864,10 +732,8 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
if (inputY < -127)
inputY = -127;
- // Step 2: Scale to [-127, 127] (lines 82-84).
- // Mouse mode: scaledInputX = (DAT_0047a7e0 * 0x7f) / 0xa0.
int16 scaledInputX = (int16)((inputX * 127) / 160);
- int16 scaledInputY = _optControlsFlipped ? (int16)-inputY : inputY; // local_14
+ int16 scaledInputY = _optControlsFlipped ? (int16)-inputY : inputY;
const bool useDirectGamepadFlight = _gamepadAimActive && _selectedLevel == 10;
@@ -892,7 +758,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
mouseFlightTargetX = CLIP<int16>(mouseFlightTargetX, 0x14, 0x194);
}
- // Step 3: Velocity history + smoothed average (lines 141-157).
for (int i = 24; i > 0; i--) {
_velocityHistory[i] = _velocityHistory[i - 1];
}
@@ -903,9 +768,8 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
for (int i = 0; i < smoothWindow; i++) {
velSum += _velocityHistory[i];
}
- _smoothedVelocity = (int16)(velSum / smoothWindow); // DAT_0044370c
+ _smoothedVelocity = (int16)(velSum / smoothWindow);
- // Step 4: Wind history (lines 158-173).
const int16 windMult = flightParams.driftRate;
int windSumX = 0, windSumY = 0;
for (int i = 14; i > 0; i--) {
@@ -922,14 +786,12 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
_windHistoryY[0] = _windParamY;
int16 windEffectY = (int16)((windMult * (windSumY + _windParamY)) / 15);
- // Step 5: Position delta (lines 174-242).
const int16 levelSpeed = flightParams.slideRate;
const int16 levelYSpeed = flightParams.liftRate;
int16 absSmoothVel = ABS(_smoothedVelocity);
int16 positionDeltaX;
if (_flyControlMode == 1) {
- // Mode 1: Full cross-axis coupling (lines 174-186).
// Banking: vertical input deflects horizontal movement.
if (scaledInputX < 1) {
positionDeltaX = (int16)((levelSpeed * _smoothedVelocity - absSmoothVel * scaledInputY - windEffectX) >> 9);
@@ -937,7 +799,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
positionDeltaX = (int16)((levelSpeed * _smoothedVelocity + absSmoothVel * scaledInputY - windEffectX) >> 9);
}
} else {
- // Mode 0/2/3: Reduced cross-axis coupling (lines 218-230).
if (scaledInputX < 1) {
positionDeltaX = (int16)((levelSpeed * _smoothedVelocity - (absSmoothVel * scaledInputY >> 2) - windEffectX) >> 9);
} else {
@@ -945,13 +806,11 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
}
}
- // Clamp X delta to +/-12 per frame (lines 187-192 / 231-236).
if (positionDeltaX < -11)
positionDeltaX = -12;
if (positionDeltaX > 11)
positionDeltaX = 12;
- // FUN_0040C3CC integrates relative flight axes. The real mouse is an
// 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) {
@@ -962,12 +821,10 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
positionDeltaX = (targetDeltaX < 0) ? -1 : 1;
}
- // Apply X delta (line 193 / 237).
_flyShipScreenX += positionDeltaX;
// Y delta.
if (_flyControlMode == 1) {
- // Mode 1: clamped to +/-12 with wind (lines 194-216).
int yCalc = levelYSpeed * scaledInputY - (windEffectY >> 1);
int yDelta = yCalc >> 10;
if (yDelta < -12)
@@ -976,17 +833,13 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
yDelta = 12;
_flyShipScreenY -= (int16)yDelta;
} else {
- // Mode 0/2/3: unclamped (lines 238-241).
_flyShipScreenY -= (int16)((levelYSpeed * scaledInputY) >> 10);
}
- // Store vertical input for direction sprite (line 243).
- _verticalInput = scaledInputY; // DAT_0044370e
+ _verticalInput = scaledInputY;
- // Ship facing direction (line 244).
_facingRight = (0xd4 < _smoothedVelocity + _flyShipScreenX);
- // Step 6: Position clamping (lines 245-256).
if (_flyShipScreenX > 0x194)
_flyShipScreenX = 0x194; // 404
if (_flyShipScreenY > 0xF0)
@@ -996,13 +849,10 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
if (_flyShipScreenY < 0x14)
_flyShipScreenY = 0x14; // 20
- // Step 7: Corridor collision - mode 0/2 only (lines 257-292).
if (_flyControlMode == 0 || _flyControlMode == 2) {
LevelDifficultyParams wallParams = getDifficultyParams();
int corridorWallDmg = (wallParams.dodgeDamage >= 0) ? wallParams.dodgeDamage : 0;
- // Right boundary (lines 258-270).
- // Original: position is ALWAYS clamped; damage/bounce only when cooldown < 5.
if (_corridorRightX < _flyShipScreenX) {
_flyShipScreenX = _corridorRightX;
if (_hitCooldown < 5) {
@@ -1018,7 +868,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
}
}
- // Left boundary (lines 271-283).
if (_flyShipScreenX < _corridorLeftX) {
_flyShipScreenX = _corridorLeftX;
if (_hitCooldown < 5) {
@@ -1034,7 +883,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
}
}
- // Y boundary clamping - no damage (lines 285-292).
if (_corridorBottomY < _flyShipScreenY) {
_flyShipScreenY = _corridorBottomY;
}
@@ -1043,10 +891,7 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
}
}
- // Step 8: Perspective offsets (lines 293-316).
// f(x) = (focal * center * |offset|) / ((center - focal) * |offset| + focal * center)
- // Close view (DAT_0047a7fc < 1): focalX=0x34, focalY=0x2d.
- // Far view (DAT_0047a7fc >= 1): focalX=0x2b, focalY=0x19.
{
int absOffX = ABS(_flyShipScreenX - 0xd4);
int16 focalX = 0x2b; // Far view default for Level 3
@@ -1071,14 +916,12 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
_perspectiveY = -_perspectiveY;
}
- // View shift = clamped smoothed velocity (FUN_0040d836 lines 68-74).
_viewShift = _smoothedVelocity;
if (_viewShift > 127)
_viewShift = 127;
if (_viewShift < -127)
_viewShift = -127;
- // Step 9: Direction sprite (FUN_0040d836 lines 88-106).
// 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);
@@ -1094,7 +937,6 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
if (hDir > 6)
hDir = 6;
- // Hysteresis at center (lines 90-97, 98-105).
if (hDir == 3 && ABS(_smoothedVelocity) > 10) {
hDir = (_smoothedVelocity < 1) ? 4 : 2;
}
@@ -1120,14 +962,10 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
useTargetSteering ? 1 : 0, _gamepadAimActive ? 1 : 0);
}
-// Helper split out of FUN_0041CADB case 4; not a separate original function.
void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
// Handler 25 (0x19) specific logic (mixed mode - speeder bike).
- // Based on FUN_0041cadb case 4 (opcode 6) lines 113-229.
- // Read the reset flag from IACT data at offset 8-9 (local_14[4] in decompiled code).
// The stream position should be at offset 8 after par4 was read.
- // From FUN_0041cadb line 114: if (local_14[4] == 1) { ... reset ... }
int16 par5 = 0;
if (b.pos() + 2 <= b.size()) {
int64 savedPos = b.pos();
@@ -1135,8 +973,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
b.seek(savedPos); // Don't consume the stream
}
- // If par5 == 1, enable status bar and reset state (lines 114-121).
- // Note: This is local_14[4] in the decompiled code, NOT local_14[3] (par4).
if (par5 == 1) {
_rebelStatusBarSprite = 5;
if (_smush_iconsNut && _smush_iconsNut->getNumChars() > 5) {
@@ -1158,20 +994,15 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
}
}
- // Set sprite mode (DAT_00457900 = local_14[3]) - controls which GRD sprite to render.
- // From FUN_0041cadb line 122: DAT_00457900 = local_14[3].
- // In this IACT parser: local_14[3] = offset 6-7 = par4.
// 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; // local_14[3] maps to par4 (offset 6-7)
+ _grdSpriteMode = par4;
debugC(DEBUG_INSANE, "Handler25 Opcode6: par2=%d par3=%d par4=%d(mode) par5=%d(reset) autopilot=%d damageLevel=%d controlMode=%d",
par2, par3, par4, par5, _rebelAutopilot, _rebelDamageLevel, _rebelControlMode);
- // Autopilot logic (lines 123-146).
- // From original FUN_0041cadb - NO damageLevel check, toggle happens immediately.
// The damage level counter provides the smooth visual transition.
if (!_rebelAutoPlay) {
if (_rebelAutopilot == 0) {
@@ -1204,7 +1035,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
}
}
- // Update damage level counter (lines 147-154).
// This provides the smooth transition animation between covered/uncovered states.
int prevDamageLevel = _rebelDamageLevel;
if (_rebelAutopilot == 0) {
@@ -1223,7 +1053,6 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
prevDamageLevel, _rebelDamageLevel, _rebelAutopilot);
}
- // Flight direction logic for mode 3 (lines 155-177).
if (_grdSpriteMode == 3) {
if (_rebelDamageLevel == 5) {
// At max damage, check for direction change input.
@@ -1242,45 +1071,38 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
_rebelFlightDir = 0;
}
- // Calculate sprite and view offset positions based on mode (lines 182-213).
- // DAT_0045790c = view offset X (for corridor overlay)
- // DAT_0045790e = view offset Y (for corridor overlay)
- // DAT_00457910 = sprite position X (relative to center)
- // DAT_00457912 = sprite position Y (relative to center)
if (_grdSpriteMode == 1) {
// Mode 1: Uncovered, shooting - sprite shifts left as damage increases.
_rebelViewMode1 = 0x0e;
_rebelViewMode2 = 0;
- _rebelViewOffsetX = _rebelDamageLevel * -5 + -14; // DAT_0045790c
- _rebelViewOffset2X = _rebelDamageLevel * -22; // DAT_00457910
- _rebelViewOffsetY = 0; // DAT_0045790e
- _rebelViewOffset2Y = 0; // DAT_00457912
+ _rebelViewOffsetX = _rebelDamageLevel * -5 + -14;
+ _rebelViewOffset2X = _rebelDamageLevel * -22;
+ _rebelViewOffsetY = 0;
+ _rebelViewOffset2Y = 0;
} else if (_grdSpriteMode == 4) {
// Mode 4: Alternative uncovered - sprite shifts right.
_rebelViewMode1 = 0x22;
_rebelViewMode2 = 0;
- _rebelViewOffsetX = _rebelDamageLevel * 10 + -16; // DAT_0045790c
- _rebelViewOffset2X = _rebelDamageLevel * 17 + -85; // DAT_00457910 (0x11 = 17, -0x55 = -85)
+ _rebelViewOffsetX = _rebelDamageLevel * 10 + -16;
+ _rebelViewOffset2X = _rebelDamageLevel * 17 + -85;
_rebelViewOffsetY = 0;
_rebelViewOffset2Y = 0;
} else if (_grdSpriteMode == 2) {
// Mode 2: Covered - vertical shift.
_rebelViewMode1 = 0;
_rebelViewMode2 = 0x0e;
- _rebelViewOffsetY = _rebelDamageLevel * -5 + -14; // DAT_0045790e
- _rebelViewOffset2Y = (5 - _rebelDamageLevel) * 15 + -60; // DAT_00457912 (0xf = 15, -0x3c = -60)
+ _rebelViewOffsetY = _rebelDamageLevel * -5 + -14;
+ _rebelViewOffset2Y = (5 - _rebelDamageLevel) * 15 + -60;
_rebelViewOffsetX = 0;
_rebelViewOffset2X = 0;
} else if (_grdSpriteMode == 3) {
// Mode 3: Transition - direction-dependent horizontal shift.
_rebelViewMode1 = 0x0f;
_rebelViewMode2 = 0;
- // (-(DAT_00457902 == 0) & 6) - 3 = if dir==0: 6-3=3, else 0-3=-3
int16 dirMultX = (_rebelFlightDir == 0) ? 3 : -3;
- // (-(DAT_00457902 == 0) & 0x28) - 0x14 = if dir==0: 40-20=20, else 0-20=-20
int16 dirMultX2 = (_rebelFlightDir == 0) ? 20 : -20;
- _rebelViewOffsetX = dirMultX * (5 - _rebelDamageLevel) + -15; // DAT_0045790c
- _rebelViewOffset2X = dirMultX2 * (5 - _rebelDamageLevel); // DAT_00457910
+ _rebelViewOffsetX = dirMultX * (5 - _rebelDamageLevel) + -15;
+ _rebelViewOffset2X = dirMultX2 * (5 - _rebelDamageLevel);
_rebelViewOffsetY = 0;
_rebelViewOffset2Y = 0;
} else {
@@ -1298,25 +1120,19 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
_grdSpriteMode, _rebelDamageLevel, _rebelFlightDir, _rebelAutopilot,
_rebelViewOffsetX, _rebelViewOffsetY, _rebelViewOffset2X, _rebelViewOffset2Y);
- // Set FOBJ position offsets (FUN_00424510 in original, line 214).
// All subsequent FOBJs in this frame will be shifted by these offsets.
if (_player) {
_player->_fobjOffsetX = _rebelViewOffsetX;
_player->_fobjOffsetY = _rebelViewOffsetY;
}
- // Draw corridor overlay opaquely (FUN_00428a10 in original, line 216).
// This wipes previous frame content so codec 23 delta skip regions show clean corridor.
drawHandler25CorridorOverlay(renderBitmap);
}
-// Helper split out of FUN_00407FCB case 4; not a separate original function.
void InsaneRebel2::handleOpcode6Turret(Common::SeekableReadStream &b, int16 par4) {
- // Handler 0x26: FUN_407FCB line 77-79 - set level type from par4, read par5 for init trigger.
- // param_5[3] = par4 = levelType, param_5[4] = par5 = init flag.
_rebelLevelType = par4;
- // Read par5 from IACT body (param_5[4]).
int16 par5 = 0;
if (b.pos() + 2 <= b.size()) {
int64 savedPos = b.pos();
@@ -1325,7 +1141,6 @@ void InsaneRebel2::handleOpcode6Turret(Common::SeekableReadStream &b, int16 par4
}
if (par5 == 1) {
- // Re-render laser texture for this level (FUN_0040bb87).
// levelType 5 uses sprite 53, all others use sprite 5.
_rebelStatusBarSprite = (_rebelLevelType == 5) ? 53 : 5;
if (_smush_iconsNut && _smush_iconsNut->getNumChars() > _rebelStatusBarSprite) {
@@ -1348,7 +1163,6 @@ void InsaneRebel2::handleOpcode6Turret(Common::SeekableReadStream &b, int16 par4
}
}
-// Helper split out of generic opcode 6 initialization; not a separate original function.
void InsaneRebel2::handleOpcode6GenericInit(int16 par4) {
// Other handlers: par4 == 1 triggers init (NOT level type).
if (_rebelHandler != 0x26 && par4 == 1) {
@@ -1372,9 +1186,7 @@ void InsaneRebel2::handleOpcode6GenericInit(int16 par4) {
}
}
-// Helper split out of generic opcode 6 flight state; not a separate original function.
void InsaneRebel2::updateOpcode6GenericFlightState() {
- // Step 3: Autopilot/control mode logic (lines 123-146)
// This determines whether the ship flies on autopilot or manual control.
if (!_rebelAutoPlay) {
// Normal mode: check control mode flags.
@@ -1401,7 +1213,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
}
}
- // Step 4: Update damage level counter (lines 147-154).
if (_rebelAutopilot == 0) {
if (_rebelDamageLevel > 0) {
_rebelDamageLevel--;
@@ -1412,7 +1223,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
}
}
- // Handle level type 3 special direction logic (lines 155-181).
if (_rebelLevelType == 3) {
if (_rebelDamageLevel == 5) {
// Check for joystick/key input to change direction.
@@ -1429,7 +1239,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
_rebelFlightDir = 0;
}
- // Step 5: Calculate view offsets based on level type (lines 182-213).
switch (_rebelLevelType) {
case 1:
// Type 1: Vertical movement.
@@ -1466,8 +1275,8 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
_rebelViewMode1 = 0x0f;
_rebelViewMode2 = 0;
{
- int dirFactor = (_rebelFlightDir == 0) ? 3 : -3; // (-(ushort)(DAT_00457902 == 0) & 6) - 3
- int dirFactor2 = (_rebelFlightDir == 0) ? 0x14 : -0x14; // (-(ushort)(DAT_00457902 == 0) & 0x28) - 0x14
+ int dirFactor = (_rebelFlightDir == 0) ? 3 : -3;
+ int dirFactor2 = (_rebelFlightDir == 0) ? 0x14 : -0x14;
_rebelViewOffsetX = dirFactor * (5 - _rebelDamageLevel) - 0x0f;
_rebelViewOffset2X = dirFactor2 * (5 - _rebelDamageLevel);
}
@@ -1490,7 +1299,6 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
_rebelLevelType, _rebelAutopilot, _rebelDamageLevel, _rebelViewOffsetX, _rebelViewOffsetY);
}
-// Helper split out of opcode 6 embedded ANIM scanning; not a separate original function.
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.
@@ -1530,22 +1338,11 @@ void InsaneRebel2::scanOpcode6EmbeddedAnim(byte *renderBitmap, Common::SeekableR
}
}
-//
-// iactRebel2Opcode6 -- Level setup / mode switch (FUN_41CADB case 4)
-//
// Per-wave initialization: clears bit table, resets link tables, configures
-// handler mode (ship/turret/corridor), and loads collision zones. The original
// 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
- // Based on FUN_41CADB case 4 (switch on *local_14 - 2 == 4, meaning opcode 6)
- //
- // For Handler 8 (third-person on foot) - FUN_00401234 case 4:
- // - par4 sets ship level mode (DAT_0043e000)
// - first body word == 1 triggers status bar display and state reset
- // - Updates ship position based on mouse input
- //
// For Handler 0x26/0x19 (turret/FPS):
// - Handler-specific status/reset word
// - Different view offset calculations
@@ -1578,22 +1375,15 @@ void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStr
scanOpcode6EmbeddedAnim(renderBitmap, b, chunkSize, par4);
}
-//
-// iactRebel2Opcode8 -- HUD/Ship resource loading (FUN_0040c3cc / FUN_00401234 / FUN_00407fcb)
-//
// 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
-//
-// Helper split out of FUN_0040C3CC case 6; not a separate original function.
bool InsaneRebel2::loadOpcode8Handler7FlySprites(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
// Handler 7: FLY NUT Loading (Third-Person Ship)
- // FUN_0040c3cc case 6: par4 determines FLY sprite slot.
bool isHandler7FLY = (_rebelHandler == 7 && (par4 == 1 || par4 == 2 || par4 == 3 || par4 == 11));
if (isHandler7FLY && remaining >= 14) {
if (loadHandler7FlySprites(b, remaining, par4)) {
@@ -1606,11 +1396,7 @@ bool InsaneRebel2::loadOpcode8Handler7FlySprites(Common::SeekableReadStream &b,
return false;
}
-// Helper split out of FUN_0040C3CC case 6 shot-table loading; not a separate original function.
bool InsaneRebel2::loadOpcode8Handler7ShotTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
- // FUN_0040c3cc case 6:
- // par4=12 -> FUN_0040fcfa(text, DAT_004437c2, DAT_00443808)
- // par4=13 -> FUN_0040fcfa(text, DAT_0044384e, DAT_00443894)
if (_rebelHandler != 7 || (par4 != 12 && par4 != 13))
return false;
@@ -1623,14 +1409,10 @@ bool InsaneRebel2::loadOpcode8Handler7ShotTable(Common::SeekableReadStream &b, i
return false;
}
-// Helper split out of FUN_00405663 edge-table loading; not a separate original function.
bool InsaneRebel2::loadOpcode8EdgeTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
// Edge Blend Table Loading (par4 == 1000)
- // FUN_405663: After all handler-specific opcode 8 processing, checks if 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).
- // Data starts at byte offset 18 in the IACT chunk (in_stack_00000014 + 9 shorts).
- // The stream is positioned after par1..par4 (8 bytes), so FUN_00405663's +18 is startPos + 10.
if (par4 == 1000 && remaining >= 10 + 8 + 32896) {
byte *edgeData = (byte *)malloc(8 + 32896);
if (edgeData) {
@@ -1647,11 +1429,8 @@ bool InsaneRebel2::loadOpcode8EdgeTable(Common::SeekableReadStream &b, int64 sta
return false;
}
-// Helper split out of opcode 8 aux SFX loading; not a separate original function.
bool InsaneRebel2::loadOpcode8AuxSfx(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
// Auxiliary Sound Buffer Loading (par4 20-47)
- // FUN_401234 case 6 (handler 8): par4 0x14-0x1b (20-27) -> aux buffer 0
- // FUN_41CADB case 6 (handler 25): par4 0x15-0x1b (21-27) -> aux buffer 0,
// 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.
@@ -1691,12 +1470,9 @@ bool InsaneRebel2::loadOpcode8AuxSfx(Common::SeekableReadStream &b, int64 startP
return true;
}
-// Helper split out of FUN_0041CADB case 6 shot-origin loading; not a separate original function.
bool InsaneRebel2::loadOpcode8ShotOriginTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
// Handler 25 (0x19): Shot-Origin Lookup Table (par4 == 8)
- // FUN_0041CADB case 6 pushes 30 short pointers into sscanf with format at 0x482360:
// "%hd %hd %hd %hd ... %hd %hd" (15 X/Y pairs).
- // Parsed values are written into DAT_004578a6 / DAT_004578c6 at indices 5..19.
if (_rebelHandler == 25 && par4 == 8) {
if (loadHandler25ShotOriginTable(b, startPos, remaining)) {
b.seek(startPos);
@@ -1707,7 +1483,6 @@ bool InsaneRebel2::loadOpcode8ShotOriginTable(Common::SeekableReadStream &b, int
return false;
}
-// Helper split out of opcode 8 embedded ANIM scanning; not a separate original function.
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)",
@@ -1766,12 +1541,10 @@ void InsaneRebel2::loadOpcode8EmbeddedAnim(byte *renderBitmap, Common::SeekableR
b.seek(startPos);
}
-// Helper split out of opcode 8 embedded ANIM routing; not a separate original function.
bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData, int32 animDataSize, int16 par3, int16 par4) {
bool handled = false;
// Handler 0x26: Turret HUD Overlays.
- // FUN_00407fcb case 8: handler 0x26 uses par4 1-4 for HUD NUT loading.
// Some chunks use par3 for the same low/high selector.
if (!handled && _rebelHandler == 0x26) {
int hudSelector = (par4 >= 1 && par4 <= 4) ? par4 : par3;
@@ -1782,7 +1555,6 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
}
// Handler 8: POV Ship Sprites or Background.
- // FUN_00401234 case 6: par4 selects POV NUT type (1,3,6,7) or background (5).
// NOTE: par3 is always 0 for Handler 8; par4 contains the actual sprite type.
if (!handled && _rebelHandler == 8) {
if (par4 == 5) {
@@ -1793,14 +1565,6 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
}
// Handler 25 (0x19): Level 2 GRD Ship Sprites and Background.
- // FUN_0041cadb case 6 (opcode 8): Uses PAR4 for switch selection.
- // par4=1: GRD001 - Primary ship sprite -> DAT_00482240 / _grd001Sprite
- // par4=2: GRD002 - Secondary ship sprite -> DAT_00482238 / _grd002Sprite
- // par4=4: 350x230 corridor overlay -> DAT_00482268, draws immediately
- // par4=5: 320x200 background -> DAT_0048226c
- // par4=6: Overlay -> DAT_00482250, draws immediately
- // par4=7: Overlay -> DAT_00482248, draws immediately
- // par4=10: GRD005 - Mode 3 overlay -> DAT_00482258 / _grd005Sprite
if (!handled && _rebelHandler == 25) {
if (par4 == 1 || par4 == 2 || par4 == 10) {
handled = loadHandler25GrdSprites(animData, animDataSize, par4);
@@ -1879,8 +1643,6 @@ void InsaneRebel2::iactRebel2Opcode8(byte *renderBitmap, Common::SeekableReadStr
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)
- // - FUN_0041CADB reads from offset +18 relative to IACT start -> startPos + 10
- // - payload size for this opcode family is at offset +14 -> startPos + 6
if (remaining < 12)
return false;
@@ -1946,7 +1708,6 @@ bool InsaneRebel2::loadHandler25ShotOriginTable(Common::SeekableReadStream &b, i
return false;
}
- // FUN_0041CADB mapping:
// 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) {
@@ -1965,9 +1726,6 @@ bool InsaneRebel2::loadHandler25ShotOriginTable(Common::SeekableReadStream &b, i
// loadHandler7ShotTable -- Parse handler 7 laser muzzle coordinate pairs from IACT payload.
bool InsaneRebel2::loadHandler7ShotTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
- // FUN_0040FCFA parses 35 "%hd %hd" pairs from offset +18 in the IACT
- // chunk into two parallel 35-entry tables. These tables are BSS globals in
- // the EXE, so their values only exist in the SAN/IACT stream.
if (remaining < 12)
return false;
@@ -2048,25 +1806,12 @@ bool InsaneRebel2::loadHandler7ShotTable(Common::SeekableReadStream &b, int64 st
return true;
}
-// ---------------------------------------------------------------------------
// Opcode 8 Helper Functions
-// ---------------------------------------------------------------------------
-// Extracted from the original monolithic iactRebel2Opcode8 to match
-// the original FUN_* function structure.
-// loadHandler7FlySprites -- Handler 7 FLY NUT loading (FUN_0040c3cc case 6).
bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 remaining, int16 par4) {
- // Handler 7 FLY NUT loading - FUN_0040c3cc case 6 (opcode 8)
- // IACT structure after par1-par4 (we're at offset +8):
// +0-5 (6 bytes): additional header
// +6-9 (4 bytes): NUT data size (little-endian)
// +10+: NUT data
- //
- // par4 values (param_5[3] - 1 in assembly):
- // 1 -> case 0: FLY001 - Ship direction sprites (DAT_0047fee8)
- // 2 -> case 1: FLY003 - Targeting overlay (DAT_0047fef8)
- // 3 -> case 2: FLY002 - Laser fire sprites (DAT_0047fef0)
- // 11 -> case 10: FLY004 - High-res alternative (DAT_0047ff00)
if (remaining < 14) {
return false;
@@ -2136,7 +1881,6 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
debugC(DEBUG_INSANE, "loadHandler7FlySprites: Loaded FLY NUT par4=%d with %d sprites",
par4, newNut->getNumChars());
- // Assign to appropriate slot based on par4 (matches FUN_0040c3cc case 6 switch)
bool assigned = true;
switch (par4) {
case 1: // FLY001 - Ship direction sprites (35 frames)
@@ -2166,14 +1910,8 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
return assigned;
}
-// loadTurretHudOverlay -- Handler 0x26 turret HUD loading (FUN_00407fcb case 8).
bool InsaneRebel2::loadTurretHudOverlay(byte *animData, int32 size, int16 selector) {
- // Handler 0x26 turret HUD overlay loading - FUN_00407fcb case 8
// Resolution-dependent loading:
- // selector == 1: Low-res primary HUD (DAT_0047fe78 / _hudOverlayNut)
- // selector == 2: High-res primary HUD (DAT_0047fe78 / _hudOverlayNut)
- // selector == 3: Low-res secondary HUD (DAT_0047fe80 / _hudOverlay2Nut)
- // selector == 4: High-res secondary HUD (DAT_0047fe80 / _hudOverlay2Nut)
if (!animData || size <= 0) {
return false;
@@ -2215,14 +1953,7 @@ bool InsaneRebel2::loadTurretHudOverlay(byte *animData, int32 size, int16 select
return true;
}
-// loadHandler8ShipSprites -- Handler 8 POV NUT loading (FUN_00401234 case 6).
bool InsaneRebel2::loadHandler8ShipSprites(byte *animData, int32 size, int16 par4) {
- // Handler 8 ship POV NUT loading - FUN_00401234 case 6 (opcode 8)
- // par4 values (from IACT data offset +6, NOT par3 which is always 0):
- // 1: POV001 - Primary ship sprite (DAT_0047e010 / _shipSprite)
- // 3: POV004 - Secondary ship sprite (DAT_0047e028 / _shipSprite2)
- // 6: POV002 - Shot impact overlay (DAT_0047e020 / _shipOverlay1)
- // 7: POV003 - Shot impact overlay (DAT_0047e018 / _shipOverlay2)
if (!animData || size <= 0) {
return false;
@@ -2268,13 +1999,7 @@ bool InsaneRebel2::loadHandler8ShipSprites(byte *animData, int32 size, int16 par
return true;
}
-// loadHandler25GrdSprites -- Handler 25 GRD NUT loading (FUN_0041cadb case 6).
bool InsaneRebel2::loadHandler25GrdSprites(byte *animData, int32 size, int16 par4) {
- // Handler 25 GRD ship NUT loading - FUN_0041cadb case 6 (opcode 8)
- // par4 values (from IACT data offset +6):
- // 1: GRD001 - Primary ship sprite (DAT_00482240 / _grd001Sprite)
- // 2: GRD002 - Secondary ship sprite (DAT_00482238 / _grd002Sprite)
- // 10: GRD005 - Mode 3 overlay sprite (DAT_00482258 / _grd005Sprite)
if (!animData || size <= 0) {
return false;
@@ -2319,11 +2044,8 @@ bool InsaneRebel2::loadHandler25GrdSprites(byte *animData, int32 size, int16 par
return true;
}
-// loadLevel2Background -- Decode Level 2 background from embedded ANIM (FUN_00401234 case 5).
bool InsaneRebel2::loadLevel2Background(byte *animData, int32 size, byte *renderBitmap) {
- // Level 2 background loading from embedded ANIM - FUN_00401234 case 5
// par4=5 contains the background image embedded as ANIM with FOBJ codec 3
- // Creates 320x200 buffer (DAT_0047e030 / _level2Background)
if (!animData || size < 8) {
return false;
@@ -2406,8 +2128,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);
- // Decode codec 3 (RLE) into the original 320x200 background buffer.
- // FUN_0041CADB/FUN_00401234 draw these resources into a fixed buffer
// 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) {
@@ -2422,7 +2142,6 @@ bool InsaneRebel2::loadLevel2Background(byte *animData, int32 size, byte *render
_level2BackgroundLoaded = true;
foundBackground = true;
- // Handler 25 uses this buffer as a lookup mask; FUN_0041CADB does not
// 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;
@@ -2450,25 +2169,20 @@ 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
@@ -2551,13 +2265,11 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
// Check difficulty gate (flag bit 3 = 0x08)
// If set, only show text if difficulty check passes (we skip this check for simplicity)
- // FUN_00425D30(0) is called.
// Get render buffer dimensions
int width = (_player && _player->_width > 0) ? _player->_width : 320;
int height = (_player && _player->_height > 0) ? _player->_height : 200;
- // Apply coordinate clamping (from FUN_004033cf disassembly)
// Low-res: X clamped to [16, 304], Y clamped to [16, 196]
if (posX < 16)
posX = 16;
@@ -2602,7 +2314,6 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
}
// Use white color (index 255) for subtitle text
- // The original uses colors from the palette, commonly white or yellow for subtitles
int16 textColor = 255;
// RA2 fonts (like DIHIFONT.NUT) have only 58 characters starting at ASCII 32 (space).
@@ -2687,10 +2398,8 @@ void InsaneRebel2::iactRebel2Opcode9(byte *renderBitmap, Common::SeekableReadStr
posX, posY, textFlags, clipX, clipY, clipW, clipH);
}
-// enemyUpdate -- Opcode 4: update enemy position and state (FUN_004028C5 / FUN_0041E7C2).
void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4) {
// Opcode 4: Enemy position update
- // Read 5 shorts from the stream (offset +8 through +16)
int16 enemyId = b.readSint16LE(); // Offset +8
int16 x = b.readSint16LE(); // Offset +10 (0x0A)
@@ -2701,7 +2410,6 @@ void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b
int16 w = b.readSint16LE(); // Offset +14 (0x0E) - Width
int16 h = b.readSint16LE(); // Offset +16 (0x10) - Height
- // par3==2 (turret handler) marks a translucent surface / force field (FUN_40A2E0 local_8==2):
// 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.
if (par3 == 2 && _rebelHandler == 0x26) {
@@ -2719,7 +2427,6 @@ void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b
return;
}
- // The disassembly shows half-width/half-height are used for centering:
// halfW = w >> 1
// halfH = h >> 1
// centerX = x + halfW
@@ -2727,10 +2434,6 @@ void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b
// But for drawing the bounding box, we want the top-left corner (x, y) and full dimensions.
// Update enemy list for hit detection
- // Enemy type comes from par4 (IACT offset +6), NOT par3 (offset +4).
- // In the original (FUN_004028C5/FUN_0041E7C2): sVar5/sVar2 = *(short *)(*local + 6)
- // This maps to par4 (userId field). Used for DAT_0047ab98 wave state bitmask:
- // DAT_0047ab98 |= 1 << (type & 0x1f)
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);
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index 41be2808224..63bf52c55b1 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -45,11 +45,7 @@ static void purgeRebel2GameplayInputEvents(Common::EventManager *eventMan) {
eventMan->purgeKeyboardEvents();
}
-// ---------------------------------------------------------------------------
// Level Loading System
-// ---------------------------------------------------------------------------
-// Emulates the level handler functions from FUN_00417E53 through FUN_0041BBE8.
-// Based on disassembly analysis of the Rebel Assault 2 executable.
Common::String InsaneRebel2::getLevelDir(int levelId) {
return Common::String::format("LEV%02d", levelId);
@@ -59,12 +55,8 @@ Common::String InsaneRebel2::getLevelPrefix(int levelId) {
return Common::String::format("%02d", levelId);
}
-//
-// runGame -- Main game entry point (FUN_004142BD)
-//
// 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;
@@ -110,7 +102,6 @@ void InsaneRebel2::runGame() {
int selectedLevel = _selectedChapter + 1;
debugC(DEBUG_INSANE, "InsaneRebel2: Starting chapter %d (level %d)", _selectedChapter + 1, selectedLevel);
- // Ending selected directly from chapter select (FUN_0041bbe8, case 0xf)
if (selectedLevel == 16) {
playEndingSequence();
}
@@ -142,17 +133,10 @@ void InsaneRebel2::runGame() {
}
}
-//
-// playIntroSequence -- Intro sequence (FUN_004142BD case 0)
-//
-// Original flow:
// - 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)
-// - If DAT_0047ab45 || DAT_0047ab47: play OPEN/O_OPEN_B.SAN (additional 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");
@@ -162,7 +146,6 @@ void InsaneRebel2::playIntroSequence() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
// Play main intro (OPEN/O_OPEN_A.SAN)
- // Original: FUN_0041f4d0("OPEN/O_OPEN_A.SAN", 0x28, 0xffff, 0xffff, 0)
debugC(DEBUG_INSANE, "Playing main intro (O_OPEN_A.SAN)");
splayer->setCurVideoFlags(0x28);
splayer->play("OPEN/O_OPEN_A.SAN", 15);
@@ -171,14 +154,12 @@ void InsaneRebel2::playIntroSequence() {
return;
// Play additional intro (OPEN/O_OPEN_B.SAN)
- // Original: conditional on DAT_0047ab45 || DAT_0047ab47
// 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);
}
-// playMissionBriefing -- Mission briefing screen (FUN_00415CF8).
void InsaneRebel2::playMissionBriefing() {
debugC(DEBUG_INSANE, "Playing mission briefing");
@@ -190,7 +171,6 @@ void InsaneRebel2::playMissionBriefing() {
// playCinematic -- Play a cinematic/cutscene video.
// Resets handler to 0 (no HUD) and sets flags to 0x28 (cinematic + buffer preserve).
-// All wrapper functions (FUN_00417168/4171c5/417ab2/417327) add | 8 before calling FUN_0041f4d0.
void InsaneRebel2::playCinematic(const char *filename) {
restoreDamageFlashPalette();
resetVideoAudio();
@@ -203,9 +183,7 @@ void InsaneRebel2::playCinematic(const char *filename) {
splayer->play(filename, 15);
}
-// playVideoWithText -- Video with progressive text overlay (FUN_004171c5).
// displayLength = currentFrame + 10 - fadeInFrame, capped at 0xBE (190) chars.
-// Text rendered at (textX, textY) via FUN_004341a0.
void InsaneRebel2::playVideoWithText(const char *filename, int textID, int textX, int textY,
int fadeInFrame, int fadeOutFrame) {
@@ -231,7 +209,6 @@ void InsaneRebel2::playVideoWithText(const char *filename, int textID, int textX
}
// playLevelBegin -- Level beginning cinematic (LEVXX/XXBEG.SAN).
-// Uses per-level text overlay parameters from GAME.TRS via FUN_004171c5.
void InsaneRebel2::playLevelBegin(int levelId) {
struct TextOverlayParams {
@@ -243,25 +220,24 @@ void InsaneRebel2::playLevelBegin(int levelId) {
};
// Table of per-level text overlay parameters
- // All levels use FUN_004171c5 (verified against decompiled level handlers)
// Text IDs are sequential: 0xAA (level 1) through 0xB8 (level 15)
const TextOverlayParams levelTextParams[16] = {
{ -1, 0, 0, 0, 0}, // Level 0 (unused)
- {0xAA, 0xA0, 10, 5, 0x4B}, // Level 1: FUN_00417E53
- {0xAB, 0xA0, 10, 2, 0x46}, // Level 2: FUN_00418063
- {0xAC, 0xA0, 10, 2, 0x46}, // Level 3: FUN_0041885F
- {0xAD, 0xA0, 10, 2, 100}, // Level 4: FUN_00418CC4
- {0xAE, 0xA0, 10, 5, 0x3C}, // Level 5: FUN_00418EC6
- {0xAF, 0xA0, 10, 5, 0x4B}, // Level 6: FUN_004190D6
- {0xB0, 0xA0, 10, 5, 0x4B}, // Level 7: FUN_0041974C
- {0xB1, 0xA0, 10, 5, 0x4B}, // Level 8: FUN_00419976
- {0xB2, 0xA0, 10, 200, 0x10E}, // Level 9: FUN_00419B86
- {0xB3, 0xA0, 10, 2, 0x46}, // Level 10: FUN_00419E0A
- {0xB4, 0xA0, 10, 2, 0x46}, // Level 11: FUN_0041A00C
- {0xB5, 0xA0, 10, 5, 0x4B}, // Level 12: FUN_0041A3EB
- {0xB6, 0xA0, 10, 2, 0x46}, // Level 13: FUN_0041A806
- {0xB7, 0xA0, 10, 2, 0x46}, // Level 14: FUN_0041ABB2
- {0xB8, 0xA0, 10, 2, 0x46}, // Level 15: FUN_0041AEE8
+ {0xAA, 0xA0, 10, 5, 0x4B},
+ {0xAB, 0xA0, 10, 2, 0x46},
+ {0xAC, 0xA0, 10, 2, 0x46},
+ {0xAD, 0xA0, 10, 2, 100},
+ {0xAE, 0xA0, 10, 5, 0x3C},
+ {0xAF, 0xA0, 10, 5, 0x4B},
+ {0xB0, 0xA0, 10, 5, 0x4B},
+ {0xB1, 0xA0, 10, 5, 0x4B},
+ {0xB2, 0xA0, 10, 200, 0x10E},
+ {0xB3, 0xA0, 10, 2, 0x46},
+ {0xB4, 0xA0, 10, 2, 0x46},
+ {0xB5, 0xA0, 10, 5, 0x4B},
+ {0xB6, 0xA0, 10, 2, 0x46},
+ {0xB7, 0xA0, 10, 2, 0x46},
+ {0xB8, 0xA0, 10, 2, 0x46},
};
Common::String dir = getLevelDir(levelId);
@@ -279,7 +255,6 @@ void InsaneRebel2::playLevelBegin(int levelId) {
}
}
-// playLevelEnd -- Level completion video (FUN_00417327).
void InsaneRebel2::playLevelEnd(int levelId) {
restoreDamageFlashPalette();
@@ -295,12 +270,10 @@ void InsaneRebel2::playLevelEnd(int levelId) {
debugC(DEBUG_INSANE, "Playing level %d end: %s", levelId, filename.c_str());
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Original: FUN_00417327 adds | 8, so flags = 0x20 | 0x08 = 0x28
splayer->setCurVideoFlags(0x28);
splayer->play(filename.c_str(), 15);
}
-// playLevelRetry -- Retry prompt video (LEVXX/XXRETRY.SAN, FUN_00417168).
void InsaneRebel2::playLevelRetry(int levelId) {
restoreDamageFlashPalette();
@@ -316,12 +289,10 @@ void InsaneRebel2::playLevelRetry(int levelId) {
debugC(DEBUG_INSANE, "Playing level %d retry: %s", levelId, filename.c_str());
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Original: FUN_00417168 adds | 8, so flags = 0x20 | 0x08 = 0x28
splayer->setCurVideoFlags(0x28);
splayer->play(filename.c_str(), 15);
}
-// playLevelGameOver -- Game over video (FUN_00417ab2).
void InsaneRebel2::playLevelGameOver(int levelId) {
restoreDamageFlashPalette();
@@ -337,14 +308,10 @@ void InsaneRebel2::playLevelGameOver(int levelId) {
debugC(DEBUG_INSANE, "Playing level %d game over: %s", levelId, filename.c_str());
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Original: FUN_00417ab2 adds | 8, so flags = 0x20 | 0x08 = 0x28
splayer->setCurVideoFlags(0x28);
splayer->play(filename.c_str(), 15);
}
-// playEndingSequence -- Finale + credits + epilogue (FUN_0041bbe8).
-//
-// Original flow:
// 1. Play difficulty-dependent finale video:
// - Difficulty 2: FINAL/F_FIN_B.SAN
// - Difficulty 3: FINAL/F_FIN_C.SAN
@@ -352,7 +319,6 @@ void InsaneRebel2::playLevelGameOver(int levelId) {
// 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);
@@ -459,8 +425,6 @@ int InsaneRebel2::runLevel(int levelId) {
// Set the current level
_selectedLevel = levelId;
- // Set the level type for difficulty table lookup (DAT_0047a7f8).
- // Each original level function sets this before gameplay starts.
// Levels 1-6 use types 0-5, but Level 6 also uses type 6 mid-level.
// Levels 7-15 use types 7-15 (gap at type 6 which is Level 6 phase 2).
// Level 15 also switches to type 16 mid-level at frame 0x21e.
@@ -470,7 +434,6 @@ int InsaneRebel2::runLevel(int levelId) {
_rebelLevelType = kLevelTypeMap[levelId];
// Lock the mouse to the game window during gameplay.
- // The original hides the cursor (ShowCursor(0)) and relies on Windows confining
// the mouse to the game window. Without locking, the cursor can escape the
// window making the ship uncontrollable.
_gameplaySectionActive = false;
@@ -549,28 +512,18 @@ int InsaneRebel2::runLevel(int levelId) {
return result;
}
-// ---------------------------------------------------------------------------
// Helper Functions
-// ---------------------------------------------------------------------------
-// Emulates FUN_004233a0.
int InsaneRebel2::getRandomVariant(int max) {
return _vm->_rnd.getRandomNumber(max - 1);
}
-//
// selectDeathVideoVariant -- Frame-based death video selection
-//
-// Returns variant suffix ("A", "B", "C", etc.) based on level, phase,
-// and the frame where the player died. Emulates the per-level frame
-// threshold tables in the original level handlers.
-//
Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int frame) {
switch (levelId) {
case 1:
// Level 1: Random between A and B
- // Original: random!=0 â A (offset 0), random==0 â B (offset 0x14)
return (getRandomVariant(2) == 0) ? "B" : "A";
case 2:
@@ -578,9 +531,7 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
return "";
case 3:
- // Level 3: Based on death frame and phase
if (phase == 1) {
- // Phase 1 death video selection (from FUN_0041885F lines 80-96)
if (frame < 0x10c)
return "B"; // < 268
if (frame < 0x1a9)
@@ -593,7 +544,6 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
return "B";
return "A";
} else {
- // Phase 2 death video selection (from FUN_0041885F lines 53-67)
if (frame < 0x2f1)
return "A"; // < 753
if (frame < 0x347)
@@ -606,18 +556,14 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
}
case 4:
- // Level 4: Single variant "A" (original plays 04DIE_A.SAN)
return "A";
case 5:
// Level 5: Random between A and B
- // Original: random!=0 â A (offset 0), random==0 â B (offset 0x14)
return (getRandomVariant(2) == 0) ? "B" : "A";
case 6:
- // Level 6 (FUN_004190D6): Phase-based with detailed frame selection
if (phase == 1) {
- // DAT_0047a7f8 == 5 (phase 1)
if (frame < 0x4e)
return "D";
if (frame < 0xe0)
@@ -632,7 +578,6 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
return "C";
return "D";
} else {
- // DAT_0047a7f8 == 6 (phase 2)
if (frame < 0xcc)
return "E";
if (frame < 0xfe)
@@ -665,27 +610,19 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
}
case 7:
- // Level 7 (FUN_0041974C): Based on DAT_0047ab8c (fork state)
- // DAT_0047ab8c != 0 â DIE_B; == 0 â DIE_A
// We use phase as a proxy (phase 2 = reached fork)
return (phase >= 2) ? "B" : "A";
case 8:
- // Level 8 (FUN_00419976): Random A or B
- // Original: random!=0 â A (offset 0), random==0 â B (offset 0x14)
return (getRandomVariant(2) == 0) ? "B" : "A";
case 9:
- // Level 9 (FUN_00419B86): Based on DAT_0047ab94 (death cause)
- // 0âA, 1âC, elseâB. DAT_0047ab94 is not tracked yet.
return "A";
case 10:
- // Level 10 (FUN_00419E0A): Single death video (no variant suffix)
return "";
case 11:
- // Level 11 (FUN_0041A00C): Phase-based death videos
// Phase 1 â DIE_A, Phase 2 â DIE_B, Phase 3 â DIE_C
if (phase <= 1)
return "A";
@@ -694,11 +631,9 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
return "C";
case 12:
- // Level 12 (FUN_0041AA14): Single death video (no variants)
return "";
case 13:
- // Level 13 (FUN_0041B3E1): Frame-based
if (frame < 0x1c2)
return "A";
if (frame < 0x302)
@@ -710,11 +645,9 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
return "D";
case 14:
- // Level 14 (FUN_0041B6E8): Single death video (no variant suffix)
return "";
case 15:
- // Level 15 (FUN_0041B8D7): Frame-based with many thresholds
if (frame < 0x21e)
return "A";
if (frame < 0x2f9)
@@ -758,7 +691,6 @@ void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
debugC(DEBUG_INSANE, "Playing death video: %s (phase=%d, frame=%d)", filename.c_str(), phase, frame);
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Original: FUN_00417168 adds | 8, so flags = 0x20 | 0x08 = 0x28
splayer->setCurVideoFlags(0x28);
splayer->play(filename.c_str(), 15);
}
@@ -787,7 +719,6 @@ void InsaneRebel2::playLevelRetryVariant(int levelId, int phase) {
debugC(DEBUG_INSANE, "Playing retry video: %s (phase=%d)", filename.c_str(), phase);
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- // Original: FUN_00417168 adds | 8, so flags = 0x20 | 0x08 = 0x28
splayer->setCurVideoFlags(0x28);
splayer->play(filename.c_str(), 15);
}
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index 08ece9305ec..9baeddf61b3 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -40,10 +40,7 @@
namespace Scumm {
-// ---------------------------------------------------------------------------
// Menu System Implementation
-// ---------------------------------------------------------------------------
-// Emulates original menu system from FUN_004147B2 and FUN_0041FDC8.
static void setRebel2MixerVolume(ScummEngine_v7 *vm, int volumeLevel) {
const int mixerVolume = CLIP<int>(volumeLevel * 2, 0, (int)Audio::Mixer::kMaxMixerVolume);
@@ -90,7 +87,6 @@ void InsaneRebel2::updateMenuVirtualKeyboard() {
setVirtualKeyboardVisible(isMenuTextInputActive());
}
-// unlockAllChapters -- Debug mode unlock (FUN_00415CF8 lines 60-71, DAT_0047ab34=='d').
void InsaneRebel2::unlockAllChapters() {
debugC(DEBUG_INSANE, "Unlocking all chapters for testing");
for (int i = 0; i < 16; i++) {
@@ -99,8 +95,6 @@ void InsaneRebel2::unlockAllChapters() {
}
}
-// getRandomMenuVideo -- Select random menu video variant (FUN_0041FDC8).
-// Original plays O_MENU.SAN when no progress flags are set, but that file
// 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() {
@@ -118,15 +112,10 @@ Common::String InsaneRebel2::getRandomMenuVideo() {
return Common::String::format("OPEN/O_MENU_%c.SAN", letter);
}
-//
-// processMenuInput -- Menu input handling (FUN_0041f5ae)
-//
// 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.
-// Mouse mode (DAT_0047a806 == 1): Y position maps to selection.
-//
int InsaneRebel2::processMenuInput() {
int result = -1;
@@ -146,18 +135,15 @@ int InsaneRebel2::processMenuInput() {
switch (event.kbd.keycode) {
case Common::KEYCODE_UP:
- // Navigate up (wrap around) - emulates key code 0x148
_menuSelection--;
if (_menuSelection < 0) {
_menuSelection = _menuItemCount - 1;
}
- // Reset repeat delay counter (DAT_00459ce0)
_menuRepeatDelay = 3;
debugC(DEBUG_INSANE, "Menu: Selection changed to %d (UP)", _menuSelection);
break;
case Common::KEYCODE_DOWN:
- // Navigate down (wrap around) - emulates key code 0x150
_menuSelection++;
if (_menuSelection >= _menuItemCount) {
_menuSelection = 0;
@@ -168,7 +154,6 @@ int InsaneRebel2::processMenuInput() {
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER:
- // Confirm selection - emulates key code 0x0d
if (_menuSelection >= 0 && _menuSelection < _menuItemCount) {
result = _menuSelection;
debugC(DEBUG_INSANE, "Menu: Item %d selected (ENTER)", _menuSelection);
@@ -239,33 +224,17 @@ int InsaneRebel2::processMenuInput() {
return result;
}
-//
-// drawMenuItems -- Shared menu item renderer (FUN_0041f5ae)
-//
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
- // numItems = number of selectable items (FUN_0041f5ae param_3)
- // selection = currently highlighted item (0-based, maps to DAT_00459988)
- // leftAligned = false: param_4==0 (centered), true: param_4==1 (left-aligned)
- //
- // Coordinate formulas from FUN_0041f5ae (low-res, DAT_0047a808 < 2):
- // Centered (param_4=0):
// Title X: center - titleWidth/2 (centerX = 160)
- // Title Y: param_3 * -5 + 0x51
// Item X: center - textWidth/2
// Box X: center - bracketWidth/2
- // Left-aligned (param_4=1):
// Title X: 0x28 = 40
- // Title Y: param_3 * -5 + 0x56
// Item X: 0x17 = 23
// Box X: 0x14 = 20
// Both modes:
- // Item base Y: param_3 * -5 + 0x68
- // Item Y: param_3 * -5 + i * 10 + 0x68
- // Box Y: param_3 * -5 + i * 10 + 0x67 (1px above text)
const bool highRes = isHiRes();
const int centerX = highRes ? 0x140 : width / 2;
@@ -291,22 +260,18 @@ 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];
@@ -315,14 +280,10 @@ void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int h
int textX = leftAligned ? (highRes ? 0x2e : 0x17) : (centerX - textWidth / 2);
drawString(text, textX, itemY);
- // Selection highlight box - FUN_004292d0
if (i == selection) {
- // Width: textWidth + ((DAT_0047a808 < 2) - 1 & 6) + 6 = textWidth + 6
int bracketWidth = textWidth + (highRes ? 12 : 6);
- // Height: ((DAT_0047a808 < 2) - 1 & 10) + 10 = 10
int bracketHeight = highRes ? 20 : 10;
- // Flash color: (-((DAT_0047a7e4 & 1) == 0) & 8U) - 0x10
// bit0==0: 8-16=248(0xF8), bit0==1: 0-16=240(0xF0)
byte highlightColor = ((_vm->_system->getMillis() / 133) & 1) ? 248 : 240;
@@ -343,7 +304,6 @@ void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int h
if (bottomY >= screenH)
bottomY = screenH - 1;
- // FUN_004292d0 - Draw rectangle border (4 lines)
for (int x = leftX; x <= rightX && x < screenW; x++) {
if (topY >= 0 && topY < screenH)
renderBitmap[topY * actualPitch + x] = highlightColor;
@@ -446,9 +406,6 @@ 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()
- // Emulates FUN_004147b2 -> FUN_0041f5ae with param_3=7, param_4=0
- // -------------------------------------------------------------------
- //
// 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)
@@ -475,13 +432,10 @@ void InsaneRebel2::drawMenuOverlay(byte *renderBitmap, int pitch, int width, int
}
}
- // FUN_004147b2 line 25: param_3 = (DAT_0047a806 == 0) + 6 = 7 (keyboard mode)
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) {
@@ -495,14 +449,10 @@ void pauseFillRect(byte *buf, int bufW, int bufH, int x, int y, int w, int h, by
}
// showPauseOverlay -- Dimmed overlay with metallic frame and "PAUSED" text.
-// Reproduces FUN_405A21 from the original executable.
-//
-// The original dims pixels using a green-channel formula that produces palette
// 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.
-// The original palette is saved in _savedPausePalette and restored on unpause.
void InsaneRebel2::showPauseOverlay() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
if (!splayer)
@@ -519,7 +469,6 @@ void InsaneRebel2::showPauseOverlay() {
int screenW = MIN(width, (int)_vm->_screenWidth);
int screenH = MIN(height, (int)_vm->_screenHeight);
- // Step 1: Save current palette and create a dimmed version.
memcpy(_savedPausePalette, palette, 768);
_pauseOverlayActive = true;
@@ -544,7 +493,6 @@ void InsaneRebel2::showPauseOverlay() {
_vm->_system->getPaletteManager()->setPalette(dimPal, 0, 256);
- // Step 2: Draw the metallic frame (FUN_405A21 lines 261-283).
// 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);
@@ -575,8 +523,6 @@ void InsaneRebel2::showPauseOverlay() {
pauseFillRect(frameBuffer, width, height, xBase, yOff + 1, 0x19, 0x0F, 4);
}
- // Step 3: Draw "Game Paused" text using TRS string 0x78 (120).
- // Original: FUN_00434cb0(-1, buf, NULL, x=10, y=10, align=1, fg=4, bg=0x10, text)
// fg=4 is the foreground color (used by ^cNNN in the TRS string itself).
const char *pauseText = splayer->getString(0x78);
if (!pauseText || !pauseText[0])
@@ -589,17 +535,14 @@ void InsaneRebel2::showPauseOverlay() {
}
if (multiFont) {
Common::Rect clipRect(0, 0, screenW, screenH);
- // Original uses x=10, y=10 (single-buffer mode) or y=20 (double-buffer mode).
// We use single-buffer mode (y=10).
multiFont->drawString(pauseText, frameBuffer, clipRect, 10, 10, width, 4, kStyleAlignLeft);
}
- // Step 4: Push to screen.
_vm->_system->copyRectToScreen(frameBuffer, width, 0, 0, screenW, screenH);
_vm->_system->updateScreen();
}
-// runMainMenu -- Main menu loop (FUN_004147B2).
// Returns kMenuNewGame, kMenuResumeDemo, or kMenuQuit.
int InsaneRebel2::runMainMenu() {
@@ -662,13 +605,8 @@ int InsaneRebel2::runMainMenu() {
// A selection was made - process it
debugC(DEBUG_INSANE, "Menu video ended with selection=%d", _menuSelection);
- // Process the menu result based on current selection
- // Menu items matching GAME.TRS indices 11-17 (FUN_004147B2):
// case 0 (TRS 11): Start Game -> pilot selection, returns 2
- // case 1 (TRS 12): Options -> FUN_00416787 options screen
- // case 2 (TRS 13): Calibrate Joystick -> FUN_00425820
// case 3 (TRS 14): Continue Intro -> return to intro/demo loop
- // case 4 (TRS 15): Show Top Pilots -> FUN_00420116(-1)
// case 5 (TRS 16): Show Credits -> play O_CREDIT.SAN, returns 1
// case 6 (TRS 17): Return to Launcher -> quit, returns 0
switch (_menuSelection) {
@@ -678,7 +616,7 @@ int InsaneRebel2::runMainMenu() {
_menuInputActive = false;
return kMenuNewGame; // Return 2 (kMenuNewGame)
- case 1: // Options -> show options menu (FUN_00416787)
+ case 1:
debugC(DEBUG_INSANE, "Options selected");
showOptionsMenu();
break;
@@ -694,7 +632,7 @@ int InsaneRebel2::runMainMenu() {
_menuInputActive = false;
return kMenuResumeDemo;
- case 4: // Show Top Pilots -> high score display (FUN_00420116(-1))
+ case 4:
debugC(DEBUG_INSANE, "Show Top Pilots selected");
showTopPilots();
break;
@@ -708,7 +646,6 @@ int InsaneRebel2::runMainMenu() {
_gameState = kStateMainMenu;
_menuInputActive = true;
resetMenuGamepadAxis();
- // Returns 1 in original -> stays at stage 1 (main menu)
break;
case 6: // Return to Launcher -> quit game
@@ -726,11 +663,8 @@ int InsaneRebel2::runMainMenu() {
return kMenuQuit;
}
-// ---------------------------------------------------------------------------
// Chapter Selection Screen
-// ---------------------------------------------------------------------------
-// runChapterSelect -- Chapter selection with preview (FUN_00415CF8).
// Returns kChapterSelectPlay, kChapterSelectBack, or kChapterSelectQuit.
int InsaneRebel2::runChapterSelect() {
@@ -743,7 +677,6 @@ int InsaneRebel2::runChapterSelect() {
resetMenuGamepadAxis();
// Initialize chapter selection state
- // Original (lines 51-54): local_10 = 0xf; while (local_10 > 0 && locked) local_10--;
// Finds highest unlocked chapter. With debug unlock all = 15 (FINALE).
_chapterSelection = 15;
while (_chapterSelection > 0 && !_chapterUnlocked[_chapterSelection]) {
@@ -762,7 +695,6 @@ int InsaneRebel2::runChapterSelect() {
_previewOffsetX = -90;
_previewOffsetY = _chapterSelection * -50 + 75;
- // Set iactBits for chapter unlock state (FUN_00415CF8 lines 79-86)
// 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.
for (int i = 0; i < 16; i++) {
@@ -772,8 +704,6 @@ int InsaneRebel2::runChapterSelect() {
clearBit(16 - i);
}
- // Chapter selection background - FUN_00415CF8 line 57:
- // FUN_0041f4d0(s_OPEN_O_LEVEL_SAN, 8, 0xffff, 0xffff, 0)
// 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()) {
@@ -782,7 +712,6 @@ int InsaneRebel2::runChapterSelect() {
debugC(DEBUG_INSANE, "Playing chapter select background: OPEN/O_LEVEL.SAN");
// Flags: 0x20 (overlay/menu rendering) | 0x08 (preserve buffer, suppress
- // AHDR speed override). Matches original FUN_0041f4d0 parameter 8.
// 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.
@@ -807,7 +736,6 @@ int InsaneRebel2::runChapterSelect() {
debugC(DEBUG_INSANE, "Chapter selection made: %d", _chapterSelection);
- // Process chapter selection (lines 134-236 of FUN_00415CF8)
if (_chapterSelection == 16) {
// BACK selected (index 16 = 17th item)
debugC(DEBUG_INSANE, "BACK to main menu selected");
@@ -826,13 +754,11 @@ int InsaneRebel2::runChapterSelect() {
return kChapterSelectPlay;
}
- // Locked chapter â validate password (FUN_00415CF8 lines 239-257)
if (_activePilot >= 0 && _activePilot < _numPilots &&
_pilots[_activePilot].difficulty < 6 && _chapterSelection > 0) {
Common::String expected = getChapterPassword(
_chapterSelection, _pilots[_activePilot].difficulty);
if (!expected.empty() && _passwordInput.equalsIgnoreCase(expected)) {
- // Password accepted â unlock chapter (FUN_00415CF8 lines 253-257)
PilotData &pilot = _pilots[_activePilot];
pilot.score[_chapterSelection] = 0;
pilot.damage[_chapterSelection] = 0;
@@ -840,7 +766,6 @@ int InsaneRebel2::runChapterSelect() {
pilot.rating[_chapterSelection] = 0;
savePilots();
_chapterUnlocked[_chapterSelection] = true;
- // Update iactBit for video preview (original jumps to LAB_00415d88)
setBit(16 - _chapterSelection);
_passwordInput.clear();
updateMenuVirtualKeyboard();
@@ -861,7 +786,6 @@ int InsaneRebel2::runChapterSelect() {
int InsaneRebel2::processChapterSelectInput() {
// Process input for chapter selection screen
- // Emulates input handling in FUN_00415CF8 (lines 95-133)
// Returns: -1 = no action, 0+ = item selected
int result = -1;
@@ -880,7 +804,6 @@ int InsaneRebel2::processChapterSelectInput() {
_chapterSelection = _chapterItemCount - 1;
}
_passwordInput.clear();
- // Update preview offset (FUN_00425170: Y = selected * -50 + 75)
_previewOffsetY = _chapterSelection * -50 + 75;
updateMenuVirtualKeyboard();
debugC(DEBUG_INSANE, "ChapterSelect: Selection changed to %d (UP) offsetY=%d", _chapterSelection, _previewOffsetY);
@@ -893,7 +816,6 @@ int InsaneRebel2::processChapterSelectInput() {
_chapterSelection = 0;
}
_passwordInput.clear();
- // Update preview offset (FUN_00425170: Y = selected * -50 + 75)
_previewOffsetY = _chapterSelection * -50 + 75;
updateMenuVirtualKeyboard();
debugC(DEBUG_INSANE, "ChapterSelect: Selection changed to %d (DOWN) offsetY=%d", _chapterSelection, _previewOffsetY);
@@ -915,7 +837,6 @@ int InsaneRebel2::processChapterSelectInput() {
break;
case Common::KEYCODE_BACKSPACE:
- // Backspace for password input (line 107-112 of FUN_00415CF8)
if (!_passwordInput.empty()) {
_passwordInput.deleteLastChar();
debugC(DEBUG_INSANE, "ChapterSelect: Password backspace, now: %s", _passwordInput.c_str());
@@ -923,7 +844,6 @@ int InsaneRebel2::processChapterSelectInput() {
break;
default:
- // Printable character for password input (lines 114-121 of FUN_00415CF8)
if (event.kbd.ascii >= 0x20 && event.kbd.ascii <= 0x7E) {
if (_passwordInput.size() < 8) {
_passwordInput += (char)event.kbd.ascii;
@@ -959,7 +879,6 @@ int InsaneRebel2::processChapterSelectInput() {
case Common::EVENT_MOUSEMOVE:
{
- // Mouse hover changes highlight (original FUN_0041f5ae mouse mode).
// Item Y = numItems * -5 + i * 10 + 0x68
const bool highRes = isHiRes();
const int baseY = highRes ? (_chapterItemCount * -5 + 0x5a) * 2 + 0x1c : _chapterItemCount * -5 + 0x68;
@@ -992,12 +911,9 @@ int InsaneRebel2::processChapterSelectInput() {
return result;
}
-// Draw preview box border - emulates FUN_004292D0 calls at lines 128-133 of FUN_00415CF8
void InsaneRebel2::drawPreviewBox(byte *renderBitmap, int pitch, int width, int height) {
- // Low-res (320x200) coordinates from FUN_00415CF8:
// 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
- // High-res uses the original DAT_0047a808 >= 2 formulas, doubling the
// low-res anchors and dimensions.
const int scale = isHiRes() ? 2 : 1;
@@ -1065,7 +981,6 @@ void InsaneRebel2::drawPreviewBox(byte *renderBitmap, int pitch, int width, int
}
}
-// Rating to medal string (FUN_0042001f): TALKFONT glyphs 3=big, 2=medium, 1=small
Common::String InsaneRebel2::getRankString(int rating) {
if (rating > 50)
rating = 50;
@@ -1076,8 +991,6 @@ Common::String InsaneRebel2::getRankString(int rating) {
return result;
}
-// Password table lookup - emulates FUN_0041BCE0
-// 90 entries: 15 levels à 6 difficulty slots, extracted from RA2WIN95.EXE at 0x481AF0
// 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
@@ -1168,10 +1081,7 @@ void InsaneRebel2::drawChapterInfoLine(byte *renderBitmap, int pitch, int width,
}
// Draw chapter selection overlay - called during O_LEVEL.SAN playback
-// FUN_00415CF8 - Chapter selection screen with preview thumbnail
void InsaneRebel2::drawChapterSelectOverlay(byte *renderBitmap, int pitch, int width, int height) {
- // Emulates FUN_00415CF8 rendering via shared drawMenuItems(leftAligned=true)
- //
// GAME.TRS chapter selection strings:
// TRS 40 = "^f02Chapters" (title)
// TRS 41-56 = unlocked chapter names (e.g. "^f01^c244Chapter 1 - The Dreighton Triangle")
@@ -1179,18 +1089,14 @@ void InsaneRebel2::drawChapterSelectOverlay(byte *renderBitmap, int pitch, int w
// 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
- // FUN_0041f5ae(0, &DAT_004577a8, 0x11, 1): param_3=17, param_4=1 (left-aligned)
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
if (!splayer)
return;
- // Build items array matching original DAT_004577a8 layout
const char *items[18];
- // Original (FUN_00415CF8 lines 55-77): starts with ALL locked strings,
// then overrides unlocked chapters individually.
// items[0] = title = TRS 60 ("^f02Chapters")
items[0] = splayer->getString(60);
@@ -1214,8 +1120,6 @@ void InsaneRebel2::drawChapterSelectOverlay(byte *renderBitmap, int pitch, int w
// Render menu using shared renderer with left-aligned mode
drawMenuItems(renderBitmap, pitch, width, height, items, 17, _chapterSelection, true);
- // Draw preview box border on the right side (FUN_004292d0 calls at lines 128-133)
- // The preview thumbnail is rendered by O_LEVEL.SAN via FOBJ offset + STOR/FTCH.
// SKIP chunks in O_LEVEL.SAN use iactBits to show locked/unlocked preview variants.
drawPreviewBox(renderBitmap, pitch, width, height);
@@ -1223,12 +1127,8 @@ void InsaneRebel2::drawChapterSelectOverlay(byte *renderBitmap, int pitch, int w
drawChapterInfoLine(renderBitmap, pitch, width, height);
}
-// ---------------------------------------------------------------------------
-// Pilot Selection Menu (FUN_00414A41)
-// ---------------------------------------------------------------------------
// Pilot/save selection before chapter selection.
-// runLevelSelect -- Pilot selection menu (FUN_00414A41).
// Returns kLevelSelectPlay, kLevelSelectBack, or kLevelSelectQuit.
int InsaneRebel2::runLevelSelect() {
@@ -1271,7 +1171,6 @@ int InsaneRebel2::runLevelSelect() {
_vm->_smushVideoShouldFinish = false;
_menuSelectionConfirmed = false;
- // --- Difficulty submenu completed ---
if (_pilotMenuMode == kPilotModeDifficulty) {
// Store difficulty in the new pilot and save
if (_pilotEditIndex >= 0 && _pilotEditIndex < _numPilots) {
@@ -1293,7 +1192,6 @@ int InsaneRebel2::runLevelSelect() {
return kLevelSelectPlay;
}
- // --- Name input completed ---
if (_pilotMenuMode == kPilotModeNameInput) {
// Name was confirmed â now show difficulty submenu
if (_pilotEditIndex >= 0 && _pilotEditIndex < _numPilots) {
@@ -1308,7 +1206,6 @@ int InsaneRebel2::runLevelSelect() {
continue;
}
- // --- Pilot operation submenu completed ---
if (_pilotMenuMode == kPilotModeCopySelect || _pilotMenuMode == kPilotModeDeleteSelect) {
int pilotIndex = _levelSelection;
if (pilotIndex >= 0 && pilotIndex < _numPilots) {
@@ -1335,7 +1232,6 @@ int InsaneRebel2::runLevelSelect() {
continue;
}
- // --- Normal pilot menu selection ---
debugC(DEBUG_INSANE, "Pilot selection: %d (numPilots=%d)", _levelSelection, _numPilots);
if (_levelSelection < _numPilots) {
@@ -1369,7 +1265,6 @@ int InsaneRebel2::runLevelSelect() {
continue;
} else if (_levelSelection == _numPilots + 1) {
- // COPY PILOT - original opens a second menu to select the source pilot.
if (_numPilots > 0 && _numPilots < kMaxPilots) {
_pilotMenuMode = kPilotModeCopySelect;
_levelSelection = 0;
@@ -1380,7 +1275,6 @@ int InsaneRebel2::runLevelSelect() {
continue;
} else if (_levelSelection == _numPilots + 2) {
- // DELETE PILOT - original opens a second menu to select the target pilot.
if (_numPilots > 0) {
_pilotMenuMode = kPilotModeDeleteSelect;
_levelSelection = 0;
@@ -1411,7 +1305,6 @@ int InsaneRebel2::processLevelSelectInput() {
int result = -1;
// Name input mode â keyboard goes to _pilotNameInput instead of menu nav
- // Original: FUN_00414A41 lines 87-116
if (_pilotMenuMode == kPilotModeNameInput) {
while (!_menuEventQueue.empty()) {
Common::Event event = _menuEventQueue.pop();
@@ -1577,10 +1470,7 @@ int InsaneRebel2::processLevelSelectInput() {
}
void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int width, int height) {
- // -------------------------------------------------------------------
// Pilot selection / difficulty submenu renderer
- // Emulates FUN_00414A41 â FUN_0041f5ae
- // -------------------------------------------------------------------
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
if (!splayer) {
@@ -1589,12 +1479,7 @@ void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int wid
}
if (_gameState == kStateDifficultySelect) {
- // -------------------------------------------------------------------
// Difficulty submenu - LAB_00414ff6
- // FUN_0041f5ae(0, &DAT_00457458, 6, 0)
- // DAT_00457458 = 7 entries loaded from TRS 110-116 (FUN_00414073 lines 47-50)
- // param_3 = 6 â items[0]=title(TRS 110), items[1..6]=selectable(TRS 111-116)
- // -------------------------------------------------------------------
const char *diffItems[7];
for (int i = 0; i < 7; i++) {
diffItems[i] = splayer->getString(110 + i);
@@ -1630,9 +1515,6 @@ void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int wid
return;
}
- // -------------------------------------------------------------------
- // Pilot menu - FUN_0041f5ae(0, &DAT_00457768, N+4, 0)
- // -------------------------------------------------------------------
// items[0] = title (TRS 20)
// items[1..N] = saved pilots (formatted with ^f01^c005<name>^f00)
// items[N+1] = TRS 21 (ADD NEW PILOT)
@@ -1641,7 +1523,6 @@ void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int wid
// items[N+4] = TRS 24 (RETURN TO MAIN MENU)
// Build pilot name strings with font/color formatting
- // Original uses "^f01^c005<name>^f00" for pilot entries
Common::String pilotNameStrs[kMaxPilots];
for (int i = 0; i < _numPilots; i++) {
if (_pilotMenuMode == kPilotModeNameInput && i == _pilotEditIndex) {
@@ -1678,18 +1559,12 @@ void InsaneRebel2::drawLevelSelectOverlay(byte *renderBitmap, int pitch, int wid
drawMenuItems(renderBitmap, pitch, width, height, pilotItems, _numPilots + 4, _levelSelection);
}
-// ---------------------------------------------------------------------------
-// Top Pilots Screen (FUN_00420116)
-// ---------------------------------------------------------------------------
// Ranked pilot scores with animated reveal over a menu background video.
-// 0x4a (74) byte records (max 15, from FUN_00410271):
// +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.
-// Row Y: sVar1 * 10 + 42.
-// initDefaultRankings -- Fill ranking table with defaults (FUN_0040FF00).
// 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() {
@@ -1707,7 +1582,6 @@ void InsaneRebel2::initDefaultRankings() {
}
}
-// insertRanking -- Insert into sorted ranking table (FUN_00410271).
void InsaneRebel2::insertRanking(const char *name, int32 score, int32 rating,
int16 difficulty, int16 chapter) {
if (score == 0)
@@ -1761,7 +1635,6 @@ void InsaneRebel2::showTopPilots() {
_menuEventQueue.pop();
resetMenuGamepadAxis();
- // param_1 = -1 from main menu: maxFrames = 120 (0x78)
_topPilotsMaxFrames = 120;
_topPilotsFrameCount = 0;
@@ -1854,14 +1727,9 @@ void InsaneRebel2::drawTopPilotsOverlay(byte *renderBitmap, int pitch, int width
_topPilotsFrameCount++;
}
-// ---------------------------------------------------------------------------
-// Options Menu (FUN_004167A6)
-// ---------------------------------------------------------------------------
-// Toggle labels and slider items. Settings at DAT_00482e20[0..3].
// 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.
-// showOptionsMenu -- Options menu loop (FUN_00416787).
void InsaneRebel2::showOptionsMenu() {
debugC(DEBUG_INSANE, "Showing Options menu (FUN_00416787)");
@@ -1915,7 +1783,6 @@ int InsaneRebel2::processOptionsInput() {
return -1;
case Common::KEYCODE_LEFT:
- // Volume slider: decrease by 4 (original step size)
if (_optionsSelection == 6) {
_optVolumeLevel = MAX(0, _optVolumeLevel - 4);
setRebel2MixerVolume(_vm, _optVolumeLevel);
@@ -2007,7 +1874,6 @@ void InsaneRebel2::drawOptionsOverlay(byte *renderBitmap, int pitch, int width,
_optTextEnabled = ConfMan.getBool("subtitles");
- // Build items array from TRS strings based on current toggle states
// 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
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 81a1364d665..4bada20bbd6 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -169,8 +169,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_smush_iconsNut = new NutRenderer(_vm, highRes ? "SYSTM/CPITIMHI.NUT" : "SYSTM/CPITIMAG.NUT");
_smush_icons2Nut = nullptr; // Not used for Rebel2
- // Initialize laser texture buffer (DAT_0047fee4) from sprite 5 of CPITIMAG/CPITIMHI.NUT
- // This is done by FUN_0040BAB0/FUN_0040BB87 in the original with sprite index 5
// Sprite 5 is 136x13 pixels - a wide, thin texture perfect for laser beams
_laserTexture.pixels = nullptr;
_laserTexture.width = 0;
@@ -179,11 +177,8 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
initLaserTexture(_smush_iconsNut, 5);
}
- // Initialize edge blend table with default identity table (FUN_404BCE -> FUN_410510(NULL))
// Per-level tables are loaded later via IACT opcode 8 par4=1000
initEdgeTable(nullptr);
- // DAT_0047a7fc: Controls edge highlight rendering and widescreen features.
- // Set from param_10 of FUN_403BD0 (main game init). Values:
// < 0: Edge highlights disabled (low-detail mode)
// >= 0: Edge highlights enabled, >= 1: high-detail (secondary NUTs, widescreen)
// Always use high detail.
@@ -193,9 +188,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// Load DIHIFONT.NUT for in-video messages/subtitles (Opcode 9)
_rebelMsgFont = makeRebel2Font(_vm, "SYSTM/DIHIFONT.NUT");
- // Load menu system fonts (from info.md - FUN_403BD0 lines 302-348)
// In low resolution mode, fonts are loaded as a linked list:
- // Font 0 (^f00): TALKFONT.NUT - Default menu font (DAT_00485058)
// 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
@@ -226,7 +219,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_hiResPresentationViewY = 0;
_gameplayPresentationClipBottom = 179;
- // Damage visual effect counters (FUN_420515/420562/420754/42073B)
_damageFlashCounter = 0;
_damageHighFlashCounter = 0;
_damageShakeCounter = 0;
@@ -234,7 +226,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
memset(_damageRestorePalette, 0, sizeof(_damageRestorePalette));
_damageRestorePaletteValid = false;
- // Text overlay state (FUN_004171c5 chapter title rendering)
_textOverlayActive = false;
_textOverlayID = 0;
_textOverlayX = 0;
@@ -242,7 +233,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_textOverlayFadeIn = 0;
_textOverlayFadeOut = 0;
- // Original globals mapped: hit counter, cooldown, movie/auto-play flags.
_rebelOp6Initialized = false;
_rebelHitCounter = 0;
_rebelKillCounter = 0;
@@ -265,7 +255,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_rebelViewMode1 = 0;
_rebelViewMode2 = 0;
- // Initialize mirrored original counters.
for (int i = 0; i < 10; ++i) {
_rebelValueCounters[i] = 0;
_rebelMaskCounters[i] = 0;
@@ -283,7 +272,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_rebelGaugeCleared[i] = false;
_difficulty = 1; // Default to Medium.
- _targetLockTimer = 0; // DAT_00443676 equivalent
+ _targetLockTimer = 0;
_speed = 12;
_insaneIsRunning = false;
@@ -381,15 +370,12 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_primaryZones[i].active = false;
_secondaryZones[i].active = false;
}
- // Corridor boundaries in game coordinate space (FUN_40C040 lines 21-24)
- // DAT_00443b0a=0, DAT_00443b0c=0, DAT_00443b0e=0x1a8(424), DAT_00443b10=0x104(260)
_corridorLeftX = 0;
_corridorTopY = 0;
_corridorRightX = 0x1A8; // 424 â full game buffer width
_corridorBottomY = 0x104; // 260 â full game buffer height
_hitCooldown = 0;
- // Initialize Handler 0x26 Turret shot system (FUN_40AD63)
for (i = 0; i < 2; i++) {
_turretShots[i].counter = 0;
_turretShots[i].targetX = 0;
@@ -400,7 +386,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
}
_turretShotSeqCounter = 0;
- // Initialize Handler 8 Vehicle shot system (FUN_402ED0)
for (i = 0; i < 2; i++) {
_vehicleShots[i].counter = 0;
_vehicleShots[i].targetX = 0;
@@ -414,7 +399,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
}
_vehicleShotImpactIndex = 0;
- // Initialize Handler 7 Space shot system (FUN_40FADF)
for (i = 0; i < 2; i++) {
_spaceShots[i].counter = 0;
_spaceShots[i].targetX = 0;
@@ -454,8 +438,8 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_shipTargetX = 0xa0;
_shipTargetY = 0x28;
_shipLevelMode = 0;
- _movementRangeLimit = 127; // DAT_0047e034 - Start at full range (shooting state)
- _flyControlMode = 0; // DAT_004437c0 - Start in flight-only mode (no shooting)
+ _movementRangeLimit = 127;
+ _flyControlMode = 0;
_shipFiring = false;
_prevMouseButtons = 0; // For edge detection in mouse button handling
_shipDirectionH = 2; // Start centered horizontally (0-4 range)
@@ -467,34 +451,34 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_flyLaserSprite = nullptr; // FLY002 - effect sprites (danger/overlay cues)
_flyTargetSprite = nullptr; // FLY003 - targeting overlay
_flyHiResSprite = nullptr; // FLY004 - high-res alternative
- _flyEffectAnimCounter = 0; // DAT_0047ff1c
- _flyOverlayRepeatCount = 0; // DAT_00443b52
- _flyShipScreenX = 0xd4; // Start at center (212) - matches DAT_00443708 default
- _flyShipScreenY = 0x82; // Start at center (130) - matches DAT_0044370a default
- _smoothedVelocity = 0; // DAT_0044370c
- _verticalInput = 0; // DAT_0044370e
- memset(_velocityHistory, 0, sizeof(_velocityHistory)); // DAT_00443716
- memset(_windHistoryX, 0, sizeof(_windHistoryX)); // DAT_00443b16
- memset(_windHistoryY, 0, sizeof(_windHistoryY)); // DAT_00443b34
- _windParamX = 0; // DAT_00443b12
- _windParamY = 0; // DAT_00443b14
- _perspectiveX = 0; // DAT_00443712
- _perspectiveY = 0; // DAT_00443714
- _viewShift = 0; // DAT_00443710
- _facingRight = false; // DAT_0047ab8c
+ _flyEffectAnimCounter = 0;
+ _flyOverlayRepeatCount = 0;
+ _flyShipScreenX = 0xd4;
+ _flyShipScreenY = 0x82;
+ _smoothedVelocity = 0;
+ _verticalInput = 0;
+ memset(_velocityHistory, 0, sizeof(_velocityHistory));
+ memset(_windHistoryX, 0, sizeof(_windHistoryX));
+ memset(_windHistoryY, 0, sizeof(_windHistoryY));
+ _windParamX = 0;
+ _windParamY = 0;
+ _perspectiveX = 0;
+ _perspectiveY = 0;
+ _viewShift = 0;
+ _facingRight = false;
// Initialize Handler 25 GRD ship system
- _grd001Sprite = nullptr; // DAT_00482240 - GRD001 primary ship
- _grd002Sprite = nullptr; // DAT_00482238 - GRD002 secondary ship
- _grd005Sprite = nullptr; // DAT_00482258 - GRD005 mode 3 overlay
- _grdSpriteMode = 0; // DAT_00457900 - sprite mode (1,2,3,4)
+ _grd001Sprite = nullptr;
+ _grd002Sprite = nullptr;
+ _grd005Sprite = nullptr;
+ _grdSpriteMode = 0;
memset(_grdShotOriginX, 0, sizeof(_grdShotOriginX));
memset(_grdShotOriginY, 0, sizeof(_grdShotOriginY));
_grdShotOriginTableLoaded = false;
// Initialize Handler 0x26 turret HUD overlay system
- _hudOverlayNut = nullptr; // DAT_0047fe78 - Primary HUD overlay (GRD files, animated)
- _hudOverlay2Nut = nullptr; // DAT_0047fe80 - Secondary HUD overlay
+ _hudOverlayNut = nullptr;
+ _hudOverlay2Nut = nullptr;
// Initialize audio system for RA2 (since we don't use iMUSE).
// Individual SMUSH audio blocks carry their own source rate.
@@ -519,7 +503,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// 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
- // FUN_0041f5ae uses the selectable item count for Y position calculation.
_menuItemCount = 7;
_menuInactivityTimer = 0;
_menuInactivityTimedOut = false;
@@ -530,7 +513,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_levelUnlocked[i] = (i == 0); // Only level 1 unlocked initially
}
- // Initialize chapter selection system (FUN_00415CF8)
// 17 items: 16 chapters + BACK option
_chapterSelection = 0; // First chapter selected
_chapterItemCount = 17; // 16 chapters + BACK
@@ -538,7 +520,6 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_passwordInput = ""; // No password input
// Debug flag to unlock all chapters for testing
- // Based on original debug mode (DAT_0047ab34 == 'd') from FUN_00415CF8
// Set to true to bypass normal unlock progression
_debugUnlockAll = ConfMan.getBool("rebel2_unlock_all");
_noDamage = ConfMan.getBool("rebel2_no_damage");
@@ -547,11 +528,9 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
for (i = 0; i < 16; i++) {
// If debug unlock is enabled, unlock all chapters
// Otherwise only chapter 1 (index 0) is unlocked initially
- // Original: pilotData->scores[chapter] < 0xFF means unlocked
_chapterUnlocked[i] = _debugUnlockAll || (i == 0);
}
- // Initialize preview offset for chapter selection (FUN_00425170)
// X offset: -90 (0xffa6), Y offset: selection * -50 + 75
_previewOffsetX = -90;
_previewOffsetY = 75; // Chapter 0: 0 * -50 + 75 = 75
@@ -564,22 +543,19 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
}
loadPilots(); // Load saved pilots from disk
- // Initialize pilot selection system (FUN_00414A41)
// 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
- _difficultySelection = 2; // Default to 3rd difficulty (matching original init param_3=2)
+ _difficultySelection = 2;
_pilotMenuMode = kPilotModeSelect;
_pilotNameInput = "";
_pilotEditIndex = -1;
- // Initialize top pilots display state and ranking table (FUN_0040FF00)
_topPilotsFrameCount = 0;
_topPilotsMaxFrames = 120;
initDefaultRankings();
- // Initialize options menu state (FUN_004167A6 defaults)
_optionsSelection = 0;
_optionsItemCount = 8;
_optMusicEnabled = !_vm->_mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType);
@@ -664,7 +640,6 @@ InsaneRebel2::~InsaneRebel2() {
delete _hudOverlayNut;
delete _hudOverlay2Nut;
- // Clean up laser texture buffer (DAT_0047fee4)
freeLaserTexture();
// Clean up embedded HUD overlays
@@ -819,7 +794,6 @@ bool InsaneRebel2::handleMenuRawJoystickAxisEvent(const Common::Event &event) {
// notifyEvent -- EventObserver callback for global input dispatch.
// Handles ESC (skip) and SPACE (pause) regardless of menu state.
-// Pause behavior matches original FUN_405A21: SPACE pauses, ANY key unpauses.
bool InsaneRebel2::notifyEvent(const Common::Event &event) {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
@@ -833,7 +807,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:
- // DAT_0047ab60: Yoda-mode Movie Mode skips playable
// sections and keeps the story/cutscene sequence moving.
_rebelMovieMode = !_rebelMovieMode;
debugC(DEBUG_INSANE, "Movie mode %s", _rebelMovieMode ? "enabled" : "disabled");
@@ -842,7 +815,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
return true;
case Common::KEYCODE_p:
- // DAT_0047ab64: Yoda-mode Auto Play makes gameplay
// computer controlled.
_rebelAutoPlay = !_rebelAutoPlay;
debugC(DEBUG_INSANE, "Auto play %s", _rebelAutoPlay ? "enabled" : "disabled");
@@ -1142,7 +1114,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
- // returning to gameplay. Treat repeats as part of the original press.
if (event.kbd.keycode == Common::KEYCODE_ESCAPE && event.kbdRepeat) {
debugC(DEBUG_INSANE, "Ignoring repeated ESC keydown");
return true;
@@ -1156,11 +1127,8 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
return true;
}
- // When paused during gameplay, ANY key unpauses (FUN_405A21 line 360-365).
- // ESC additionally opens the global menu (original: quit key exits level).
if (splayer && splayer->_paused && _gameState == kStateGameplay) {
debugC(DEBUG_INSANE, "Key pressed while paused - unpausing");
- // Restore the original palette saved by showPauseOverlay
if (_pauseOverlayActive) {
_vm->_system->getPaletteManager()->setPalette(_savedPausePalette, 0, 256);
_pauseOverlayActive = false;
@@ -1194,7 +1162,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
break;
case Common::KEYCODE_SPACE:
- // SPACE pauses during gameplay (FUN_405A21).
// Unpausing is handled above (any key while paused).
if (splayer && _gameState == kStateGameplay && !splayer->_paused) {
debugC(DEBUG_INSANE, "SPACE pressed - pausing");
@@ -1268,8 +1235,6 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
return false;
}
-// Per-level difficulty parameters extracted from RA2WIN95.EXE at VA 0x47e0f0
-// Original: 2D table indexed by DAT_0047a7fa (difficulty) * 0x242 + DAT_0047a7f8 (levelType) * 0x22
// 17 fields per entry (34 bytes), 17 entries per difficulty, 6 difficulties
// Field names from official Difficulty Editor: {laserDelay, snapDistance, missDamage, dodgeDamage,
// shotDamage, specialDamage, shotAccuracy, hitPoints, dodgePoints, timePoints,
@@ -1356,7 +1321,6 @@ const InsaneRebel2::LevelDifficultyParams InsaneRebel2::kDifficultyTable[6][17]
{ 5, 4, -1, -1, 10, -1, 79, 100, -1, 8, 2000, -1, 4, -1, -1, -1, -1}, // Lv15A
{ 5, 4, 255, 45, 8, 5, 80, 100, 200, 8, 2000, 1000, 4, -1, -1, -1, -1}, // Lv15B
},
- // Difficulty 4 (Custom1) â identical to difficulty 2 (Standard) in original data
{
{ 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
@@ -1403,9 +1367,7 @@ InsaneRebel2::LevelDifficultyParams InsaneRebel2::getDifficultyParams() const {
int diff = CLIP(_difficulty, 0, 5);
int lvIdx = 0;
- // DAT_0047a7f8 is the per-segment difficulty table index.
// 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.
@@ -1439,10 +1401,7 @@ bool InsaneRebel2::applyPlayerDamage(int damage) {
return true;
}
-// addScore -- Score system with bonus life awards (FUN_0041bf8d).
void InsaneRebel2::addScore(int points) {
- // Calculate bonus life threshold based on difficulty (DAT_0047a7fa)
- // From FUN_0041bf8d:
// if (difficulty < 4) threshold = (difficulty * 5 + 5) * 1000
// else threshold = 15000
int threshold;
@@ -1460,7 +1419,6 @@ void InsaneRebel2::addScore(int points) {
if (oldMilestone < newMilestone) {
// Award bonus life
_playerLives++;
- // TODO: Play bonus life sound (FUN_0041189e(5, 0, 0x7f, 0, 0))
debugC(DEBUG_INSANE, "BONUS LIFE! Score crossed %d threshold. Lives=%d", threshold, _playerLives);
}
}
@@ -1470,9 +1428,6 @@ void InsaneRebel2::addScore(int points) {
debugC(DEBUG_INSANE, "Score +%d = %d", points, _playerScore);
}
-// Render score text to HUD (part of FUN_0041c012)
-// FUN_0041c012 lines 133-137: calls FUN_00434cb0 with format "%07ld"
-// using DAT_00482200 (DISPFONT in low-res mode).
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;
@@ -1482,7 +1437,6 @@ void InsaneRebel2::renderScoreHUD(byte *renderBitmap, int pitch, int width, int
char scoreStr[16];
Common::sprintf_s(scoreStr, "%07d", _playerScore);
- // Score position from FUN_0041c012 assembly:
// X = 0x101 low-res, 0x202 high-res
// Y = 4 low-res, 8 high-res (within status bar)
const int statusScale = isHiRes() ? 2 : 1;
@@ -1490,12 +1444,8 @@ void InsaneRebel2::renderScoreHUD(byte *renderBitmap, int pitch, int width, int
int scoreY = statusBarY + 4 * statusScale + _viewY;
// Render each digit as a NUT sprite (direct pixel blit with color 0 transparency).
- // This matches the original's FUN_00434cb0 â FUN_004341a0 text rendering which
// 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,
- // matching FUN_0042cba0 lines 13-14:
- // param_3 = *(short *)(param_6 + 2) + param_3; // X += xoffs
- // param_4 = *(short *)(param_6 + 4) + param_4; // Y += yoffs
int x = scoreX;
for (int i = 0; scoreStr[i] != '\0'; i++) {
byte ch = (byte)scoreStr[i];
@@ -1508,16 +1458,12 @@ void InsaneRebel2::renderScoreHUD(byte *renderBitmap, int pitch, int width, int
}
}
-// ---------------------------------------------------------------------------
// Pilot Data System
-// ---------------------------------------------------------------------------
// Save/load pilot profiles using the save file system.
-// Original: FUN_00411980 (load) / FUN_00411A5D (save).
const uint32 kPilotSaveMagic = MKTAG('R', 'A', '2', 'P');
const uint16 kPilotSaveVersion = 2;
-// loadPilots -- Load all pilot profiles from save files (FUN_00411980).
bool InsaneRebel2::loadPilots() {
_numPilots = 0;
@@ -1558,7 +1504,6 @@ bool InsaneRebel2::loadPilots() {
return _numPilots > 0;
}
-// savePilots -- Save all pilot profiles to save files (FUN_00411A5D).
bool InsaneRebel2::savePilots() {
bool ok = true;
@@ -1601,7 +1546,6 @@ bool InsaneRebel2::savePilots() {
}
int InsaneRebel2::createNewPilot() {
- // FUN_00411B9A: Create new pilot slot
if (_numPilots >= kMaxPilots)
return -1;
@@ -1612,7 +1556,6 @@ int InsaneRebel2::createNewPilot() {
}
void InsaneRebel2::deletePilot(int index) {
- // FUN_00411D29: Delete pilot and shift remaining down
if (index < 0 || index >= _numPilots)
return;
@@ -1691,10 +1634,8 @@ int32 InsaneRebel2::processMouse() {
// Store current state for next frame's edge detection
_prevMouseButtons = currentButtons;
- // Update _rebelControlMode (DAT_0047a7e4) for Handler 25 covered/uncovered toggle:
// 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
@@ -1730,7 +1671,6 @@ int32 InsaneRebel2::processMouse() {
bool triggerShot = ((_rebelHandler == 25) ? (leftPressed && !leftWasPressed) : leftPressed) || autoFire;
if (_rebelHandler == 8) {
- // FUN_00401CCF uses the same per-frame fire bit both to spawn shots and
// to choose the POV gun sprite. Keep the sprite driven by the event-manager
// input that processMouse() uses during SMUSH playback.
_shipFiring = triggerShot && canShoot;
@@ -1779,7 +1719,6 @@ int32 InsaneRebel2::processMouse() {
it->id, it->type, mousePos.x, mousePos.y,
it->rect.left, it->rect.top, it->rect.right, it->rect.bottom);
- // Explosion scale is handler-specific in the original:
// - H8/H7/H26 use object half-width
// - H25 uses half-width + snapDistance (and type 100 doubles it)
int explosionHalfWidth = it->rect.width() / 2;
@@ -1790,15 +1729,8 @@ int32 InsaneRebel2::processMouse() {
explosionHalfWidth *= 2;
}
- // Spawn visual explosion based on handler, enemy type, and flags.
- //
- // Rendering functions (FUN_409FBC, FUN_402696, FUN_40F1C5,
- // FUN_41F29A) check DAT_0047e108 flags & 1 - when set,
// explosion NUT sprites are suppressed. This is checked
// during rendering in renderExplosions().
- //
- // Handler 8 (FUN_4028C5): Only type 0 spawns explosion.
- // Handler 25 (FUN_41E7C2): Types > 3 set counter but
// rendering suppressed by flags bit 0.
// Handlers 0x26, 7: All types get visual explosions.
if (_rebelHandler != 8 && _rebelHandler != 25) {
@@ -1843,19 +1775,15 @@ int32 InsaneRebel2::processMouse() {
_rebelGaugeSlot[it->id] = -1; // consume: avoid double-counting
}
- // Set enemy type bit in wave state (FUN_004028c5 line 74)
- // DAT_0047ab98 |= 1 << (enemyType & 0x1f)
// 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);
}
- // Increment kill counter (DAT_0047ab88)
_rebelKillCounter++;
// Handle dependencies.
- // Handler 25 (FUN_41E7C2) has two kill paths:
// - type <= 3: process dependency tables
// - type > 3 (and 100): skip dependency handling
// Other handlers always use link-table side effects.
@@ -1880,24 +1808,19 @@ int32 InsaneRebel2::processMouse() {
}
// Play enemy death sound.
- // Pan based on enemy center X position: (screenX - 160) mapped to [-127,127]
{
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
- // Original: FUN_00411931(0, slot+3, 0x7f, pan, 0)
playAuxSfx(0, 127, sfxPan);
} else {
// All other enemies: EXPLODE.SAD
- // Original: FUN_0041189e(2, slot+3, 0x7f, pan, 0)
playSfx(2, 127, sfxPan);
}
}
- // Award score for destroying enemy (FUN_0041bf8d called from FUN_40A2E0)
- // Score value comes from DAT_0047e0fe indexed by difficulty*level
{
LevelDifficultyParams dparams = getDifficultyParams();
if (dparams.hitPoints > 0) {
@@ -1963,7 +1886,6 @@ Common::Point InsaneRebel2::getGameplayAimPoint() {
x = CLIP<int>(x, 0, 319);
y = CLIP<int>(y, 0, 199);
if (_optControlsFlipped) {
- // Original DAT_0047a7fe reverses only the up/down gameplay axis.
y = CLIP<int>(200 - y, 0, 199);
}
return Common::Point(x, y);
@@ -2088,7 +2010,6 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
}
bool InsaneRebel2::isBitSet(int n) {
- // FUN_00423970: When param_1 < 1 (0 or negative), the bounds check fails and returns false.
// This means ID 0 or negative IDs are always treated as "enabled" (not skipped).
if (n < 1) {
return false;
@@ -2099,7 +2020,6 @@ bool InsaneRebel2::isBitSet(int n) {
}
void InsaneRebel2::setBit(int n) {
- // FUN_004239b0: When n < 1 (i.e., n == 0 or negative), set ALL bits to 1 (disable all objects)
// This is used to disable all enemies/objects at once
if (n < 1) {
for (int i = 0; i < 0x401; i++)
@@ -2111,8 +2031,6 @@ void InsaneRebel2::setBit(int n) {
}
void InsaneRebel2::clearBit(int n) {
- // FUN_00423a00: When n < 1 (i.e., n == 0 or negative), clear ALL bits to 0 (enable all objects)
- // This is called by FUN_00423880 at the start of video playback to reset the bit table,
// ensuring all enemies are visible when a new level/segment starts.
if (n < 1) {
for (int i = 0; i < 0x401; i++)
@@ -2127,21 +2045,17 @@ void InsaneRebel2::clearBit(int n) {
// 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
- // FUN_0040d836 line 141: if (DAT_004437c0 == 2) { /* spawn shots, draw crosshair */ }
if (_rebelHandler == 7) {
return (_flyControlMode == 2);
}
// Handler 8 (Third-Person On Foot): Modes 4/5 disable shooting
- // FUN_00401CCF line 82-84: if (DAT_0043e000 == 4) { param_5 = 0; }
// Mode 5: Ship not rendered (cutscene)
if (_rebelHandler == 8) {
return (_shipLevelMode != 4 && _shipLevelMode != 5);
}
// Handler 25 (0x19): Only allow shooting when fully uncovered
- // FUN_41DB5E lines 170-171: if (((param_5 & 1) != 0) && (DAT_0045790a == 0))
- // _rebelDamageLevel = DAT_0045790a (cover transition counter, 0 = uncovered, 5 = covered)
if (_rebelHandler == 25) {
return (_rebelDamageLevel == 0);
}
@@ -2150,7 +2064,6 @@ bool InsaneRebel2::isShootingAllowed() {
return (_rebelHandler != 0);
}
-// procSKIP -- Conditional FOBJ/PSAD skip via bit table (FUN_00423A50).
// 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.
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index a9f2f80ffb9..41b1749aa5b 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -52,54 +52,44 @@ public:
InsaneRebel2(ScummEngine_v7 *scumm);
~InsaneRebel2();
- // EventObserver interface - captures events before ScummEngine consumes them
bool notifyEvent(const Common::Event &event) override;
- // Menu input event queue - events are captured by notifyEvent() and processed by processMenuInput()
Common::Queue<Common::Event> _menuEventQueue;
- bool _menuInputActive; // True when we're capturing menu input events
+ bool _menuInputActive;
bool _virtualKeyboardActive;
- // ---------------------------------------------------------------------------
- // Menu System
- // ---------------------------------------------------------------------------
- // Main game states (emulates original state machine from FUN_004142BD)
+ // Menu state.
enum GameState {
- kStateIntro = 0, // Stage 0: Intro/Credits sequence
- kStateMainMenu = 1, // Stage 1: Main menu (FUN_004147B2)
- kStatePilotSelect = 2, // Stage 2: Pilot selection (FUN_00414A41)
- kStateChapterSelect = 3,// Stage 3: Chapter selection (FUN_00415CF8)
- kStateGameplay = 4, // Stage 4: Gameplay (FUN_00416787)
- kStateCredits = 5, // Credits sequence
- kStateQuit = 6, // Exit game
- kStateTopPilots = 8, // Top Pilots score display (FUN_00420116)
- kStateDifficultySelect = 7, // Difficulty submenu within pilot select
- kStateOptions = 9 // Options menu (FUN_004167A6)
+ kStateIntro = 0,
+ kStateMainMenu = 1,
+ kStatePilotSelect = 2,
+ kStateChapterSelect = 3,
+ kStateGameplay = 4,
+ kStateCredits = 5,
+ kStateQuit = 6,
+ kStateTopPilots = 8,
+ kStateDifficultySelect = 7,
+ kStateOptions = 9
};
- // Menu selection results (return values from FUN_004147B2)
enum MenuResult {
- kMenuQuit = -1, // case 6: Return to Launcher
- kMenuResumeDemo = 0, // case 3 / inactivity: Continue Intro
- kMenuCredits = 1, // case 5: Show credits
- kMenuNewGame = 2 // case 0: Start Game
+ kMenuQuit = -1,
+ kMenuResumeDemo = 0,
+ kMenuCredits = 1,
+ kMenuNewGame = 2
};
- GameState _gameState; // Current game state
- int _menuSelection; // Current menu item (0-6), mirrors DAT_00459988
- int _menuItemCount; // Number of menu items (7 for main menu)
- int _menuInactivityTimer; // Timeout counter (300 frames = ~10 sec)
- bool _menuInactivityTimedOut; // Main menu should return to the intro/demo loop
- int _lastMenuVariant; // Last random menu video shown (DAT_00482400)
- int _menuRepeatDelay; // Delay for key repeat (DAT_00459ce0)
- bool _menuSelectionConfirmed; // True only when user explicitly confirmed a selection
- bool _levelUnlocked[16]; // Which levels are available (progress flags)
-
- // Run the main menu loop - returns when game should start or quit
- // This is the main entry point called from ScummEngine::go()
- int runMainMenu();
+ GameState _gameState;
+ int _menuSelection;
+ int _menuItemCount;
+ int _menuInactivityTimer;
+ bool _menuInactivityTimedOut;
+ int _lastMenuVariant;
+ int _menuRepeatDelay;
+ bool _menuSelectionConfirmed;
+ bool _levelUnlocked[16];
- // Process menu input (keyboard/mouse) - returns selected item or -1
+ int runMainMenu();
int processMenuInput();
// Format-code-aware string rendering (^fNN=font, ^cNNN=color)
@@ -112,115 +102,67 @@ public:
void drawMenuStringCentered(byte *renderBitmap, const char *str, int cx, int y, int defaultColor = 1);
void drawMenuStringRight(byte *renderBitmap, const char *str, int rx, int y, int defaultColor = 1);
- // Shared menu item renderer - emulates FUN_0041F5AE
void drawMenuItems(byte *renderBitmap, int pitch, int width, int height,
const char **items, int numItems, int selection,
bool leftAligned = false);
-
- // Draw menu overlay (selection highlight) on current frame
void drawMenuOverlay(byte *renderBitmap, int pitch, int width, int height);
-
- // Get random menu video filename (emulates FUN_0041FDC8)
Common::String getRandomMenuVideo();
-
- // Reset menu state for fresh start
void resetMenu();
bool isMenuTextInputActive() const;
void setVirtualKeyboardVisible(bool visible);
void updateMenuVirtualKeyboard();
- // ---------------------------------------------------------------------------
- // Chapter Selection Screen (FUN_00415CF8)
- // ---------------------------------------------------------------------------
- // Actual level/chapter selection screen with preview thumbnail.
- // Distinct from pilot selection (FUN_00414A41).
-
+ // Chapter selection.
enum ChapterSelectResult {
- kChapterSelectBack = 2, // Return to main menu (ESC or BACK selected)
- kChapterSelectPlay = 5, // Play selected chapter
- kChapterSelectQuit = 0 // Quit game
+ kChapterSelectBack = 2,
+ kChapterSelectPlay = 5,
+ kChapterSelectQuit = 0
};
- int _chapterSelection; // Current chapter selection (0-15, or 16 for BACK)
- int _chapterItemCount; // Number of chapter items (17: 16 chapters + BACK)
- int _selectedChapter; // Final selected chapter ID (0-15)
- Common::String _passwordInput; // Current password input string (max 8 chars)
- bool _chapterUnlocked[16]; // Which chapters are unlocked
- bool _debugUnlockAll; // Debug flag to unlock all chapters for testing
- bool _noDamage; // Game option: disable player damage
+ int _chapterSelection;
+ int _chapterItemCount;
+ int _selectedChapter;
+ Common::String _passwordInput;
+ bool _chapterUnlocked[16];
+ bool _debugUnlockAll;
+ bool _noDamage;
- // Unlock all chapters for testing (debug mode)
- // Call this to enable access to all chapters without passwords
void unlockAllChapters();
-
- // Run chapter selection screen - emulates FUN_00415CF8
int runChapterSelect();
-
- // Draw chapter selection overlay - called during O_LEVEL.SAN playback
void drawChapterSelectOverlay(byte *renderBitmap, int pitch, int width, int height);
-
- // Process chapter select input - returns -1 (no action) or action code
int processChapterSelectInput();
-
- // Draw the preview thumbnail box - emulates FUN_004292D0 calls in FUN_00415CF8
void drawPreviewBox(byte *renderBitmap, int pitch, int width, int height);
- // View offset for chapter preview scrolling (DAT_0047abe2/DAT_0047abe4)
- int16 _previewOffsetX; // X offset = -90 for chapter select
- int16 _previewOffsetY; // Y offset = chapter * -50 + 75
+ int16 _previewOffsetX;
+ int16 _previewOffsetY;
- // Draw score/info display at bottom of chapter select - emulates FUN_00434cb0 calls
void drawChapterInfoLine(byte *renderBitmap, int pitch, int width, int height);
-
- // Rating-to-medal string conversion (FUN_0042001f)
Common::String getRankString(int rating);
-
- // Password table lookup (FUN_0041BCE0)
Common::String getChapterPassword(int level, int difficulty);
- // ---------------------------------------------------------------------------
- // Top Pilots Screen (FUN_00420116)
- // ---------------------------------------------------------------------------
- // Ranked pilot scores with animated reveal, played over menu video.
- // Original: DAT_00443b58 ranking table, 0x4a-byte records, max 15 entries.
-
+ // Top pilots.
static const int kMaxRankings = 15;
struct RankingEntry {
- char name[40]; // +0x04: Pilot name (or "-----" for defaults)
- int32 score; // +0x36: Total score
- int32 rating; // +0x3a: Total rating (converted to rank medals)
- int16 difficulty; // +0x3e: Difficulty tier (0-5), TRS index = difficulty + 155
- int16 chapter; // +0x40: Highest chapter completed (1-15)
+ char name[40];
+ int32 score;
+ int32 rating;
+ int16 difficulty;
+ int16 chapter;
};
RankingEntry _rankings[kMaxRankings];
int _numRankings;
- // Initialize ranking table with defaults (FUN_0040FF00)
void initDefaultRankings();
-
- // Insert pilot score into sorted ranking table (FUN_00410271)
void insertRanking(const char *name, int32 score, int32 rating, int16 difficulty, int16 chapter);
-
- // Run top pilots display - called from main menu "Show Top Pilots"
void showTopPilots();
-
- // Draw top pilots overlay on current frame during video playback
void drawTopPilotsOverlay(byte *renderBitmap, int pitch, int width, int height);
- int _topPilotsFrameCount; // Animation frame counter (pilots revealed one per frame)
- int _topPilotsMaxFrames; // Total frames to display (120 or 240)
-
- // ---------------------------------------------------------------------------
- // Options Menu (FUN_004167A6)
- // ---------------------------------------------------------------------------
- // TRS strings: 89 (title), 90-101 (toggle labels), 103 (volume), 107/109 (back)
- // Original settings array at DAT_00482e20[0..4]:
- // [0]=text, [1]=music, [2]=voices, [3]=sound, [4]=hidden abort flag
- // Additional flags: DAT_0047a7fe (controls normal/flipped), DAT_0047a80a (rapid fire)
- // Volume: DAT_0047a804 (0-127), brightness/gamma: DAT_0047a802 (127-768)
+ int _topPilotsFrameCount;
+ int _topPilotsMaxFrames;
+ // Options menu.
void showOptionsMenu();
void drawOptionsOverlay(byte *renderBitmap, int pitch, int width, int height);
int processOptionsInput();
@@ -234,30 +176,25 @@ public:
bool _optTextEnabled;
bool _optControlsFlipped;
bool _optRapidFire;
- int _optVolumeLevel; // 0-127 (DAT_0047a804)
-
- // ---------------------------------------------------------------------------
- // Pilot Data System (FUN_00411B9A / FUN_00411980 / FUN_00411A5D)
- // ---------------------------------------------------------------------------
- // 10 pilot slots à 0x118 (280) bytes at DAT_004568A8.
- // Stored via SaveFileManager in a custom save file.
+ int _optVolumeLevel;
+ // Pilot profiles.
static const int kMaxPilots = 10;
static const int kMaxPilotNameLen = 15;
static const int kNumLevels = 16;
struct PilotData {
- char name[kMaxPilotNameLen + 1]; // +0x04: Pilot name (15 chars + null)
- int32 score[kNumLevels]; // +0x2C: Per-level score (0 = default, 0xFF = unplayed)
- int32 lives[kNumLevels]; // +0x6C: Per-level lives (4 = default, 0xFF = unplayed)
- int32 damage[kNumLevels]; // +0xAC: Per-level damage (0xFF = unplayed)
- int16 rating[kNumLevels]; // +0xEC: Per-level difficulty rating (0-50)
- int16 difficulty; // +0x10C: Difficulty setting (0-5)
+ char name[kMaxPilotNameLen + 1];
+ int32 score[kNumLevels];
+ int32 lives[kNumLevels];
+ int32 damage[kNumLevels];
+ int16 rating[kNumLevels];
+ int16 difficulty;
void init() {
memset(name, 0, sizeof(name));
memset(rating, 0, sizeof(rating));
- difficulty = 2; // Default to 3rd option
+ difficulty = 2;
score[0] = 0;
lives[0] = 4;
damage[0] = 0;
@@ -269,141 +206,85 @@ public:
}
};
- PilotData _pilots[kMaxPilots]; // DAT_004568A8 pilot array
- int _numPilots; // DAT_00480318 number of valid pilots
- int _activePilot; // DAT_0047a7ea selected pilot index
+ PilotData _pilots[kMaxPilots];
+ int _numPilots;
+ int _activePilot;
- // Pilot save/load via SaveFileManager
bool loadPilots();
bool savePilots();
- // Pilot management (FUN_00411B9A, FUN_00411D29)
- int createNewPilot(); // Returns index of new pilot, or -1 if full
- void deletePilot(int index); // FUN_00411D29: shift remaining pilots down
- void copyPilot(int srcIndex); // Copy pilot to new slot
+ int createNewPilot();
+ void deletePilot(int index);
+ void copyPilot(int srcIndex);
- // Update pilot progress after level completion
void updatePilotProgress(int levelIndex, int32 score, int32 lives, int32 damage);
- // ---------------------------------------------------------------------------
- // Pilot Selection Menu (FUN_00414A41)
- // ---------------------------------------------------------------------------
- // Pilot/save selection menu (separate from chapter selection).
-
enum LevelSelectResult {
- kLevelSelectBack = 0, // Return to main menu
- kLevelSelectPlay = 1, // Play selected level
- kLevelSelectQuit = 2 // Quit game
+ kLevelSelectBack = 0,
+ kLevelSelectPlay = 1,
+ kLevelSelectQuit = 2
};
- // Pilot name input state (for NEW PILOT name entry)
enum PilotMenuMode {
- kPilotModeSelect = 0, // Normal pilot list selection
- kPilotModeNameInput = 1, // Typing a new pilot name
- kPilotModeDifficulty = 2, // Difficulty submenu
- kPilotModeCopySelect = 3, // Selecting source pilot to copy
- kPilotModeDeleteSelect = 4 // Selecting pilot to delete
+ kPilotModeSelect = 0,
+ kPilotModeNameInput = 1,
+ kPilotModeDifficulty = 2,
+ kPilotModeCopySelect = 3,
+ kPilotModeDeleteSelect = 4
};
PilotMenuMode _pilotMenuMode;
- Common::String _pilotNameInput; // Current name being typed
- int _pilotEditIndex; // Index of pilot being edited/created
+ Common::String _pilotNameInput;
+ int _pilotEditIndex;
- int _levelSelection; // Current level selection (0-based)
- int _levelItemCount; // Number of level items (levels + options)
- int _selectedLevel; // Final selected level ID (1-15)
- int _difficultySelection; // Current difficulty selection in submenu (0-based)
+ int _levelSelection;
+ int _levelItemCount;
+ int _selectedLevel;
+ int _difficultySelection;
- // Run pilot selection menu - emulates FUN_00414A41
int runLevelSelect();
-
- // Draw pilot selection overlay
void drawLevelSelectOverlay(byte *renderBitmap, int pitch, int width, int height);
-
- // Process pilot select input - returns -1 (no action) or action code
int processLevelSelectInput();
- // ---------------------------------------------------------------------------
- // Level Loading System
- // ---------------------------------------------------------------------------
- // Emulates the level handler functions FUN_00417E53 through FUN_0041BBE8.
- // Each level has: BEG (intro), gameplay SANs, END (completion), DIE variants,
- // RETRY, and OVER (game over) videos.
-
- // Level playback result (returned by runLevel)
+ // Level playback.
enum LevelResult {
- kLevelCompleted = 0, // Level completed successfully
- kLevelNextLevel = 1, // Proceed to next level
- kLevelGameOver = 2, // No lives remaining
- kLevelQuit = 3, // Player quit
- kLevelReturnToMenu = 4 // Return to main menu
+ kLevelCompleted = 0,
+ kLevelNextLevel = 1,
+ kLevelGameOver = 2,
+ kLevelQuit = 3,
+ kLevelReturnToMenu = 4
};
- // Main game entry point â full game loop (intro, menu, pilot, chapter, levels)
- // Emulates the original game flow from FUN_004142BD
void runGame();
-
- // Play the intro sequence (CREDITS/O_OPEN_C, O_OPEN_D, OPEN/O_OPEN_A, O_OPEN_B)
- // Emulates case 0 in FUN_004142BD
void playIntroSequence();
-
- // Play the mission briefing video (OPEN/O_LEVEL.SAN)
- // Emulates FUN_00415CF8
void playMissionBriefing();
-
- // Main level runner - plays a complete level by ID (1-15)
- // Handles all videos: BEG, gameplay, END/DIE/RETRY/OVER
- // Returns LevelResult
int runLevel(int levelId);
-
- // Play level beginning cinematic (LEVXX/XXBEG.SAN)
void playLevelBegin(int levelId);
-
- // Play level completion video (LEVXX/XXEND.SAN)
void playLevelEnd(int levelId);
-
- // Play retry prompt video (LEVXX/XXRETRY.SAN)
void playLevelRetry(int levelId);
-
- // Play game over video (LEVXX/XXOVER.SAN)
void playLevelGameOver(int levelId);
-
- // Play the full ending sequence: finale + credits + epilogue (FUN_0041bbe8)
void playEndingSequence();
-
- // Play main menu credits video (OPEN/O_CREDIT.SAN)
void playCreditsSequence();
-
- // Get the directory name for a level (e.g., "LEV01" for level 1)
Common::String getLevelDir(int levelId);
-
- // Get the file prefix for a level (e.g., "01" for level 1)
Common::String getLevelPrefix(int levelId);
- // Per-level handlers (emulate FUN_00417E53 through FUN_0041BBE8)
- // These implement the complete level logic including retry handling
- int runLevel1(); // FUN_00417E53 - Single gameplay phase
- int runLevel2(); // FUN_00418063 - Multiple parts with P1/P2/P3 subdirs
- int runLevel3(); // FUN_0041885F - Two phases with per-phase retry
- int runLevel4(); // Cutscene + single gameplay
- int runLevel5(); // FUN_00418EC6 - Single gameplay phase
- int runLevel6(); // FUN_004190D6 - Two phases with mid-level video switch
- int runLevel7(); // FUN_0041974C - TIE Training: single + fork at frame 1592
- int runLevel8(); // FUN_00419976 - Flight to Imdaar: single phase space battle
- int runLevel9(); // FUN_00419B86 - Mine Field: single phase with mid-events
- int runLevel10(); // FUN_00419E0A - Speeder Bikes: single phase
- int runLevel11(); // FUN_0041A00C - Inside the Terror: 3 phases + bridge (Handler 8)
- int runLevel12(); // FUN_0041AA14 - Sewers: 4 phases FPS (Handler 25)
- int runLevel13(); // FUN_0041B3E1 - Escaping Star Destroyer: two-phase AâB
- int runLevel14(); // FUN_0041B6E8 - TIE Attack: single phase
- int runLevel15(); // FUN_0041B8D7 - Imdaar Alpha: single + level ID switch
-
- // Wave state management (FUN_00417b61)
- // Waits for current video to finish, accumulates kill state, redistributes
- // kill credits from the budget.
- // mask: required enemy bits (0x36 for Phase 1, 0x3e for Phases 2/3)
- // budget: kill credit budget counter (decremented per credit transfer)
- // threshold: early-exit frame threshold (0=disabled, 0x14=20 for wave loops)
- // flags: bit 1 = add random unkilled types, bit 0 = limit credits to 2 (else 8)
+ // Per-level handlers.
+ int runLevel1();
+ int runLevel2();
+ int runLevel3();
+ int runLevel4();
+ int runLevel5();
+ int runLevel6();
+ int runLevel7();
+ int runLevel8();
+ int runLevel9();
+ int runLevel10();
+ int runLevel11();
+ int runLevel12();
+ int runLevel13();
+ int runLevel14();
+ int runLevel15();
+
+ // Wave bookkeeping shared by looping combat sections.
struct WaveEndResult {
WaveEndResult() : creditedBits(0), died(false), quit(false), completed(false), skipped(false) {}
@@ -418,18 +299,13 @@ public:
WaveEndResult processWaveEnd(int16 mask, int16 *budget, int16 threshold, uint16 flags);
- // Play a raw SAN segment from a scripted level handler.
- // The original reaches these call sites through different wrappers/direct paths; this
- // only collapses the shared dispatch step. Callers still choose the original
- // flags and when to call processWaveEnd(). recordFrame preserves the original
- // split between gameplay/wave calls and transition/init-only segments.
+ // Shared SAN segment dispatcher for scripted level handlers.
bool playLevelSegment(const char *filename, uint16 flags, bool recordFrame = true);
void enableIOSGamepadController();
void restoreIOSGamepadController();
int calculateAccuracy(int kills, int misses) const;
- // Play DIE/OVER/RETRY tail. Returns true when the caller should restart its retry loop.
bool handleLevelDeath(int levelId, int phase, const char *deathVideo, const char *retryVideo, int &levelResult);
void resetLevelAttemptState(int initialPhase);
void resetLevelPhaseState(bool clearEnemies);
@@ -438,74 +314,46 @@ public:
void resetExplosions();
void resetHandler7FlightState();
- // Random number helper (emulates FUN_004233a0)
int getRandomVariant(int max);
-
- // Select death video variant based on level, phase, and frame
- // Returns suffix like "A", "B", "C" for DIE_X.SAN
Common::String selectDeathVideoVariant(int levelId, int phase, int frame);
-
- // Play cinematic video by filename
void playCinematic(const char *filename);
- // Play cinematic with text overlay (emulates FUN_004171c5)
// Text is progressively revealed during [fadeInFrame, fadeOutFrame)
void playVideoWithText(const char *filename, int textID, int textX, int textY,
int fadeInFrame, int fadeOutFrame);
- // Text overlay state (active during playVideoWithText cinematics)
- bool _textOverlayActive; // True when text overlay should render
- int _textOverlayID; // TRS string ID
- int _textOverlayX; // X position for text rendering
- int _textOverlayY; // Y position for text rendering
- int _textOverlayFadeIn; // Frame to start progressive text reveal
- int _textOverlayFadeOut; // Frame to stop text rendering
+ bool _textOverlayActive;
+ int _textOverlayID;
+ int _textOverlayX;
+ int _textOverlayY;
+ int _textOverlayFadeIn;
+ int _textOverlayFadeOut;
- // Render chapter title text overlay (emulates FUN_004341a0 in FUN_004171c5)
void renderTextOverlay(byte *renderBitmap, int pitch, int width, int height, int curFrame);
-
- // Play death video with proper variant selection
void playLevelDeathVariant(int levelId, int phase, int frame);
-
- // Play retry video (phase-specific for multi-phase levels)
void playLevelRetryVariant(int levelId, int phase);
- // Reset gameplay aim to the original centered mouse/joystick baseline.
void centerGameplayAim();
- // Tracks consecutive recorded gameplay SANs so wave-loop videos do not recenter aim.
bool _gameplaySectionActive;
RebelIOSGamepadControllerState _iosGamepadControllerState;
- // Level state tracking for multi-phase levels
- int _currentPhase; // Current gameplay phase (1, 2, 3 for Level 2; 1, 2 for Level 3/6)
- int _deathFrame; // Frame number where player died (for death video selection)
- bool _skipSectionRequested; // Debug shortcut (Shift+S): force current gameplay section to end
-
- // ---------------------------------------------------------------------------
- // Resources and Fonts
- // ---------------------------------------------------------------------------
+ int _currentPhase;
+ int _deathFrame;
+ bool _skipSectionRequested;
+ // Resources and fonts.
NutRenderer *_smush_cockpitNut;
-
- // Font used for opcode 9 text/subtitle rendering (DIHIFONT / TALKFONT)
NutRenderer *_rebelMsgFont;
- // Menu system fonts (from info.md - FUN_403BD0 font loading)
- // Low resolution mode font list (stored in DAT_00485058 linked list):
- // Font 0 (^f00): TALKFONT.NUT - Default menu font
- // 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
- NutRenderer *_smush_talkfontNut; // Font 0 - primary menu font (DAT_00485058)
- NutRenderer *_smush_smalfontNut; // Font 1 - small font for ^f01 switching
- NutRenderer *_smush_titlefontNut; // Font 2 - title font
- NutRenderer *_smush_povfontNut; // Font 3 - POV font for Handler 8 overlay
-
- // Saved palette for pause overlay restoration (FUN_405A21)
+ NutRenderer *_smush_talkfontNut;
+ NutRenderer *_smush_smalfontNut;
+ NutRenderer *_smush_titlefontNut;
+ NutRenderer *_smush_povfontNut;
+
byte _savedPausePalette[768];
bool _pauseOverlayActive;
- bool _introCursorPushed; // true when we've pushed an invisible cursor for intro
+ bool _introCursorPushed;
int32 processMouse() override;
@@ -516,27 +364,16 @@ public:
bool handleMenuRawJoystickAxisEvent(const Common::Event &event);
void updateMenuGamepadAxisKey(int16 oldAxisX, int16 oldAxisY);
void queueMenuGamepadAxisKey(Common::KeyCode keycode);
- // Per-frame: pan the gameplay reticle incrementally from the held directional controls
- // (on-screen/physical gamepad dpad, keyboard arrows) instead of snapping it to a screen
- // edge. Call once per frame; getGameplayAimPoint() stays a pure getter.
void updateGameplayAimFromGamepad();
void warpGameplayMouseNow(int x, int y);
- // Analog stick state, ingested from EVENT_CUSTOM_BACKEND_ACTION_AXIS in notifyEvent()
- // and read (with a deadzone) by updateGameplayAimFromGamepad(). Signed; 0 = centered.
int16 _joystickAxisX;
int16 _joystickAxisY;
- // True once the gamepad has driven the gameplay reticle, until a genuine mouse/touch
- // motion takes over. While set, notifyEvent() drops stray pointer events so they can't
- // recenter the reticle (the cursor is locked ~center during gameplay, so a gamepad
- // "click" lands at center). See notifyEvent()/updateGameplayAimFromGamepad().
bool _gamepadAimActive;
- // Suppress delayed lock/warp mousemove artifacts after centering Handler 7 gameplay.
uint32 _gameplayMouseSettleUntil;
uint32 _lastGameplayMenuCloseTime;
uint32 _lastMenuGamepadNavigationTime;
- // Menu-only axis state. Gameplay aiming continues to use _joystickAxisX/Y.
int16 _menuGamepadAxisX;
int16 _menuGamepadAxisY;
int _menuGamepadRawAxis;
@@ -547,7 +384,6 @@ public:
bool isBitSet(int n) override;
void setBit(int n) override;
- // Get current handler ID (8, 25, 38 etc.) for SMUSH player to query
int getHandler() const { return _rebelHandler; }
int getHandler25GrdSpriteMode() const { return _grdSpriteMode; }
bool isHiRes() const;
@@ -556,7 +392,6 @@ public:
int32 setupsan13, Common::SeekableReadStream &b, int32 size, int32 flags,
int16 par1, int16 par2, int16 par3, int16 par4);
- // Handle IACT opcode subcases
void iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4);
void iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4);
void iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStream &b, int32 chunkSize, int16 par2, int16 par3, int16 par4);
@@ -585,12 +420,7 @@ public:
int32 setupsan13, Common::SeekableReadStream &b, int32 size, int32 flags,
int16 par1, int16 par2, int16 par3, int16 par4) override;
- // ---------------------------------------------------------------------------
- // Rendering Helper Functions
- // ---------------------------------------------------------------------------
- // Extracted from procPostRendering for better readability.
-
- // Fill status bar background area (FUN_004288c0 equivalent)
+ // Rendering helpers.
void renderStatusBarBackground(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY);
@@ -605,39 +435,23 @@ public:
void updateGameplayDamageRecovery(int32 curFrame);
void checkGameplayPostRenderCollisions(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
- // Draw NUT-based HUD overlays for Handler 0x26 turret modes
void renderTurretHudOverlays(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
-
- // Draw embedded SAN HUD overlays from IACT chunks
void renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int width, int height);
-
- // Draw DISPFONT.NUT status bar sprites (FUN_0041c012 equivalent)
void renderStatusBarSprites(byte *renderBitmap, int pitch, int width, int height,
int statusBarY, int32 curFrame);
- // Draw Handler 7 ship sprite (third-person ship - FLY sprites)
void renderHandler7Ship(byte *renderBitmap, int pitch, int width, int height);
void 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);
- // Draw Handler 8 ship sprite (third-person on foot - POV sprites)
void renderHandler8Ship(byte *renderBitmap, int pitch, int width, int height);
- void renderVehicleShotImpacts(byte *renderBitmap, int pitch, int width, int height); // FUN_402DA8 (Handler 8)
+ void renderVehicleShotImpacts(byte *renderBitmap, int pitch, int width, int height);
- // Draw fallback ship using embedded HUD frame
void renderFallbackShip(byte *renderBitmap, int pitch, int width, int height);
-
- // Draw per-enemy target indicators from the cockpit icon sheet.
void renderEnemyOverlays(byte *renderBitmap, int pitch, int width, int height, int videoWidth);
-
- // Draw explosion animations from 5-slot system (dispatcher)
void renderExplosions(byte *renderBitmap, int pitch, int width, int height);
-
- // Draw laser shot beams and impacts
void renderLaserShots(byte *renderBitmap, int pitch, int width, int height);
-
- // Update target lock state and draw crosshair/reticle
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);
@@ -647,95 +461,48 @@ public:
Common::Point getHandler7ShotTargetPoint();
Common::Point getHandler8ShotTargetPoint();
- // Reset enemy active flags and collision zones at frame end
void frameEndCleanup();
- // ---------------------------------------------------------------------------
- // Opcode 8 Helper Functions
- // ---------------------------------------------------------------------------
- // Resource loading extracted from iactRebel2Opcode8.
-
- // Load Handler 7 FLY NUT sprites from IACT data
+ // Opcode 8 resource loading.
bool loadHandler7FlySprites(Common::SeekableReadStream &b, int64 remaining, int16 par4);
bool loadHandler7ShotTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4);
- // Load turret HUD overlay NUT from ANIM data
bool loadTurretHudOverlay(byte *animData, int32 size, int16 selector);
-
- // Load Handler 8 ship POV NUT sprites from ANIM data (par4 = sprite type: 1,3,6,7)
bool loadHandler8ShipSprites(byte *animData, int32 size, int16 par4);
-
- // Load Handler 25 GRD NUT sprites from ANIM data (par4 = sprite type: 1,2)
bool loadHandler25GrdSprites(byte *animData, int32 size, int16 par4);
- // Parse Handler 25 shot-origin table text from opcode 8 (par4 = 8).
- // FUN_0041CADB stores values into DAT_004578a6 / DAT_004578c6 (indices 5..19).
+ // Handler 25 shot-origin table from opcode 8 text.
bool loadHandler25ShotOriginTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining);
- // Load Level 2 background from embedded ANIM
bool loadLevel2Background(byte *animData, int32 size, byte *renderBitmap);
- // Override procSKIP to disable Full Throttle's conditional frame skip mechanism
- // RA2 uses a different system for conditional frames via IACT opcodes
void procSKIP(int32 subSize, Common::SeekableReadStream &b) override;
-
- // Override procPreRendering to restore Level 2 background before FOBJ decoding
- // This is called at the start of each frame, before FOBJ sprites are decoded
void procPreRendering(byte *renderBitmap) override;
- // mask231: when true, color 231 is treated as transparent (legacy sprites). For laser beams set false.
+ // Laser beams use color 231 as an opaque texel.
void 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 = true);
- // ---------------------------------------------------------------------------
- // Laser Texture Buffer (DAT_0047fee4)
- // ---------------------------------------------------------------------------
- // Pre-rendered laser texture used by FUN_0040BBF6.
- // Initialized from CPITIMAG.NUT sprite 0 via initLaserTexture() (FUN_0040BAB0)
+ // Laser rendering.
struct LaserTexture {
- byte *pixels; // Pixel data (rendered from NUT sprite)
- int16 width; // Texture width
- int16 height; // Texture height (clamped to max 15)
+ byte *pixels;
+ int16 width;
+ int16 height;
};
- LaserTexture _laserTexture; // DAT_0047fee4
-
- // ---------------------------------------------------------------------------
- // Edge Blend Table (DAT_0046a7d0)
- // ---------------------------------------------------------------------------
- // 256x256 lookup table used by drawEdgeHighlightLine() (FUN_410962) to compute
- // glow colors at beam edges. For each pixel on the beam edge, the table maps
- // [adjacent_pixel_above][adjacent_pixel_below] -> output color, producing a
- // color-blended highlight that gives beams their distinctive glow.
- //
- // Default table (FUN_410510 with NULL): table[a][b] = min(a,b) (identity/transparent).
- // Per-level tables loaded via IACT opcode 8 par4=1000 (FUN_405663 -> FUN_410510).
- // The per-level table tunes the glow to the current palette (red for some levels,
- // green for others).
- //
- // _rebelDetailMode (DAT_0047a7fc): Controls whether edge highlights are drawn.
- // Set from IACT opcode 6. When >= 0, edge highlights are enabled.
- byte _edgeTable[256 * 256]; // DAT_0046a7d0 - primary edge blend table
- int16 _rebelDetailMode; // DAT_0047a7fc - edge highlight enable flag
-
- // Initialize edge blend table (FUN_410510)
- // data == nullptr: fill with default identity table (min(a,b))
- // data != nullptr: load 256x256 symmetric table from data (skips 8-byte header)
+ LaserTexture _laserTexture;
+
+ // Per-level palette lookup for laser edge glow.
+ byte _edgeTable[256 * 256];
+ int16 _rebelDetailMode;
+
void initEdgeTable(const byte *data);
- // Draw edge highlight line using the edge blend table (FUN_410962)
- // Each pixel is blended from its perpendicular neighbors via _edgeTable lookup.
- // For horizontal-dominant beams, reads pixels above/below the line.
- // For vertical-dominant beams, reads pixels left/right of the line.
void drawEdgeHighlightLine(byte *dst, int pitch, int width, int height,
int16 x0, int16 y0, int16 x1, int16 y1,
int16 clipLeft, int16 clipTop, int16 clipRight, int16 clipBottom);
- // Initialize laser texture from NUT sprite (FUN_0040BAB0)
void initLaserTexture(NutRenderer *nut, int spriteIdx);
void freeLaserTexture();
- // Draw laser beam using pre-initialized texture (FUN_0040BBF6)
- // Two-layer rendering: textured scanlines (beam body) + edge highlights (glow).
- // Edge highlights are only drawn when _rebelDetailMode >= 0.
void drawLaserBeam(byte *dst, int pitch, int width, int height,
int16 gunX, int16 gunY, int16 targetX, int16 targetY,
int16 animFrame, int16 maxFrames,
@@ -745,15 +512,15 @@ public:
struct enemy {
int id;
- int type; // Enemy type/group from IACT opcode 4 par3 (determines DAT_0047ab98 bit)
+ int type;
Common::Rect rect;
bool active;
- bool destroyed; // Set when enemy is shot - prevents re-activation
- int explosionFrame; // Current explosion animation frame (0-32, -1 = done)
- bool explosionComplete; // True when explosion animation has finished
- byte *savedBackground; // Saved background pixels at moment of destruction
- int savedBgWidth; // Width of saved background
- int savedBgHeight; // Height of saved background
+ bool destroyed;
+ int explosionFrame;
+ bool explosionComplete;
+ byte *savedBackground;
+ int savedBgWidth;
+ int savedBgHeight;
};
void initEnemyStruct(int id, int32 x, int32 y, int32 w, int32 h, bool active, bool destroyed, int32 explosionFrame, int type = 0);
@@ -761,58 +528,38 @@ public:
Common::List<enemy> _enemies;
- // Current handler type for Rebel Assault 2 (determines crosshair sprite)
- // Handler 0: Background only
- // Handler 7: Third-Person Ship - uses crosshair sprite 0x2F (47)
- // Handler 8: Third-Person On Foot - uses crosshair sprite 0x2E (46)
- // Handler 0x19: FPS/Mixed view - uses crosshair sprite 0x2F (47)
- // Handler 0x26: Turret/Cockpit - crosshair varies by level type
+ // Current gameplay handler.
int _rebelHandler;
-
- // Level type from IACT opcode 6 par3 (corresponds to DAT_004436de)
- // Determines crosshair variant for turret mode:
- // - levelType == 5: Use sprites 0x30+ (48+) for crosshair
- // - levelType != 5: Use sprites 0-3 for crosshair (with animation)
int _rebelLevelType;
-
- // Status bar sprite index (5 or 53) triggered by Opcode 6 par4
- // 0 = disabled
int _rebelStatusBarSprite;
- // Embedded SAN HUD overlays (extracted from IACT chunks)
- // These are decoded frame buffers from embedded ANIM data
- // Slots 1-4 correspond to userId in the IACT wrapper
+ // Embedded SAN HUD overlays.
struct EmbeddedSanFrame {
- byte *pixels; // Decoded frame pixels (8-bit indexed)
- int width; // Frame width
- int height; // Frame height
- int renderX; // X position to render (0 = centered based on slot)
- int renderY; // Y position to render
- bool valid; // True if this slot has valid data
+ byte *pixels;
+ int width;
+ int height;
+ int renderX;
+ int renderY;
+ bool valid;
};
- EmbeddedSanFrame _rebelEmbeddedHud[16]; // HUD overlay slots (userId 0-15)
+ EmbeddedSanFrame _rebelEmbeddedHud[16];
byte _rebelEmbeddedCodec45Palette[0x300];
byte _rebelEmbeddedCodec45Lookup[0x8000];
- // Load and decode an embedded SAN animation from IACT chunk data
- // userId: HUD slot (1-4), animData: raw ANIM data, size: data size, renderBitmap: current frame buffer
void loadEmbeddedSan(int userId, byte *animData, int32 size, byte *renderBitmap) override;
-
- // Render a decoded embedded frame to the video buffer
- // Handles transparency (color 0 and 231) and boundary checks
void renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFrame &frame, int userId);
void drawHandler25CorridorOverlay(byte *renderBitmap);
- int16 _rebelLinks[512][3]; // Dependency links: Slot 0 (Disable on death), Slot 1/2 (Enable on death)
+ int16 _rebelLinks[512][3];
void clearBit(int n);
- bool isShootingAllowed(); // FUN_0040d836/FUN_00401CCF: Check control mode before spawning shots
+ bool isShootingAllowed();
struct Explosion {
int x, y;
int width, height;
- int counter; // Duration counter (starts at 10)
- int scale; // Determines sprite set (small/med/large)
+ int counter;
+ int scale;
bool active;
};
@@ -824,66 +571,37 @@ public:
Explosion _explosions[5];
void spawnExplosion(int x, int y, int objectHalfWidth);
- // ---------------------------------------------------------------------------
- // Collision Zone System
- // ---------------------------------------------------------------------------
- // For Level 3 "pilot" ship obstacle avoidance (FUN_40E35E, FUN_40C3CC).
- // Collision zones are quadrilaterals defined by IACT Opcode 5
- // The player's ship position is tested against these zones each frame
- //
- // Zone Data Layout from IACT chunk:
- // +0x00: opcode (5)
- // +0x02: sub-opcode (0x0D = primary, 0x0E = secondary)
- // +0x04: par3 (flags)
- // +0x06: zoneType (e.g., 5 for damage zones)
- // +0x08: frameStart
- // +0x0A: frameEnd
- // +0x0C-0x1A: X1,Y1,X2,Y2,X3,Y3,X4,Y4 vertex coordinates
-
+ // Collision zones registered by IACT opcode 5.
struct CollisionZone {
- int16 x1, y1; // Vertex 1 (body[2], body[3])
- int16 x2, y2; // Vertex 2 (body[4], body[5])
- int16 x3, y3; // Vertex 3 (body[6], body[7])
- int16 x4, y4; // Vertex 4 (body[8], body[9])
- int16 field1; // body[0] - control field (frame check: field2 - 1 == field1)
- int16 field2; // body[1] - control field
- int16 filterValue; // par4 from IACT header - used for < 1000 filter
- int16 subOpcode; // 0x0D = primary, 0x0E = secondary
+ int16 x1, y1;
+ int16 x2, y2;
+ int16 x3, y3;
+ int16 x4, y4;
+ int16 field1;
+ int16 field2;
+ int16 filterValue;
+ int16 subOpcode;
bool active;
};
- // Two zone tables matching DAT_0043fb00 (primary) and DAT_0043f9c8 (secondary)
static const int kMaxCollisionZones = 5;
- CollisionZone _primaryZones[kMaxCollisionZones]; // Sub-opcode 0x0D zones
- CollisionZone _secondaryZones[kMaxCollisionZones]; // Sub-opcode 0x0E zones
+ CollisionZone _primaryZones[kMaxCollisionZones];
+ CollisionZone _secondaryZones[kMaxCollisionZones];
int _primaryZoneCount;
int _secondaryZoneCount;
- // Corridor boundaries from IACT opcode 7 sub-opcodes 1 and 2
- int16 _corridorLeftX; // DAT_00443b0a - Left X boundary
- int16 _corridorTopY; // DAT_00443b0c - Top Y boundary
- int16 _corridorRightX; // DAT_00443b0e - Right X boundary
- int16 _corridorBottomY; // DAT_00443b10 - Bottom Y boundary
+ int16 _corridorLeftX;
+ int16 _corridorTopY;
+ int16 _corridorRightX;
+ int16 _corridorBottomY;
- // Hit cooldown timer (DAT_0044374c) - prevents rapid damage stacking
int16 _hitCooldown;
- // Register a collision zone from IACT opcode 5 data
void registerCollisionZone(Common::SeekableReadStream &b, int16 subOpcode, int16 par4);
-
- // Reset collision zone counters (called at end of frame)
void resetCollisionZones();
-
- // Per-frame collision checking against registered zones (FUN_4092D9)
- // Tests aim/ship position against primary zone quadrilaterals
- // Applies collision damage from DAT_0047e0f6 when inside obstacle zone
void checkCollisionZones(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
- // Handler 7 collision system (FUN_40E35E)
- // Mode 0/2: Obstacle collision using secondary zones â inside quad = hit
- // Mode 1/3: Wall/boundary collision using primary zones â per-edge push-back
- // Uses ship position (_flyShipScreenX/_flyShipScreenY) in raw buffer coords
- // and draws proximity shadow cues for nearby danger zones.
+ // Handler 7 collision and warning cues.
void checkHandler7CollisionZones(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
bool isHandler7ShipInsideObstacleZone(const CollisionZone &zone, int margin);
void applyHandler7ObstacleHit(const CollisionZone &zone, int zoneIndex);
@@ -899,8 +617,8 @@ public:
void checkHandler7BoundaryZones(uint16 &warningMask);
void renderHandler7WarningCues(byte *renderBitmap, int pitch, int width, int height, int32 curFrame, uint16 warningMask);
- int16 _playerDamage; // Legacy damage counter (kept for compatibility/telemetry)
- int16 _playerShield; // Shields: 0..255 where 255 = full
+ int16 _playerDamage;
+ int16 _playerShield;
int16 _playerLives;
int32 _playerScore;
@@ -910,125 +628,79 @@ public:
int _hiResPresentationViewY;
int _gameplayPresentationClipBottom;
- // ---------------------------------------------------------------------------
- // Damage Visual Effect System
- // ---------------------------------------------------------------------------
- // Palette flash + screen shake on taking damage.
- // Original functions: FUN_420515, FUN_420562, FUN_420754, FUN_42073B
- //
- // FUN_42073B (triggerDamageEffect): Called on damage hit. Initiates palette
- // flash via FUN_420515 and sets screen shake counter to 10.
- // FUN_420515 (initDamageFlash): Saves current palette, sets 5-frame flash.
- // FUN_420562 (updateDamageFlashPalette): Per-frame palette inversion.
- // Normal hit: all RGB channels inverted toward white, fades over 5 frames.
- // High damage (>=255): red channel pulsing on even frames.
- // FUN_420754 (updateDamageEffect): Per-frame screen shake (random scanline
- // shifts) + calls FUN_420562. Called every frame from the render loop.
-
- void triggerDamageEffect(); // FUN_0042073B
- void initDamageFlash(); // FUN_00420515
- void updateDamageFlashPalette(); // FUN_00420562
- void updateDamageEffect(byte *renderBitmap, int pitch, int width, int height); // FUN_00420754
- void resetDamageFlash(); // FUN_00420501
+ // Palette flash and screen shake after damage.
+ void triggerDamageEffect();
+ void initDamageFlash();
+ void updateDamageFlashPalette();
+ void updateDamageEffect(byte *renderBitmap, int pitch, int width, int height);
+ void resetDamageFlash();
void restoreDamageFlashPalette();
- int16 _damageFlashCounter; // DAT_00482404 - palette flash countdown (0..5)
- int16 _damageHighFlashCounter; // DAT_00482408 - high-damage red flash (0..16)
- int16 _damageShakeCounter; // DAT_0048240c - screen shake countdown (0..10)
- byte _damageSavedPalette[0x300]; // DAT_00459990 - palette snapshot before flash
- byte _damageRestorePalette[0x300]; // Boundary restore snapshot
+ int16 _damageFlashCounter;
+ int16 _damageHighFlashCounter;
+ int16 _damageShakeCounter;
+ byte _damageSavedPalette[0x300];
+ byte _damageRestorePalette[0x300];
bool _damageRestorePaletteValid;
- // Rebel per-level counters / flags mapped from original globals
- bool _rebelOp6Initialized; // Guard: opcode 6 init block (clearBit/links/wave) runs once per video
- int _rebelHitCounter; // DAT_0047ab80 - hit counter / state tracker
- int _rebelKillCounter; // DAT_0047ab88 - enemies destroyed this phase
- bool _rebelYodaMode; // DAT_0047ab5a > 2 - unlocks Yoda-mode shortcuts
- bool _rebelMovieMode; // DAT_0047ab60 - Alt+M skips playable sections
- bool _rebelAutoPlay; // DAT_0047ab64 - Alt+P computer-controlled play
-
- // Enemy wave/phase state tracking (FUN_004028c5 / FUN_00417b61)
- // DAT_0047ab98: Per-wave enemy kill state. Bits set when enemy types are destroyed.
- // DAT_0047ab9c: Per-phase accumulated state. Copied from _rebelWaveState between waves.
- // Phase completion: (_rebelPhaseState & mask) == mask (all required enemy types killed)
- int _rebelWaveState; // DAT_0047ab98 - current wave enemy kill flags
- int _rebelPhaseState; // DAT_0047ab9c - accumulated phase enemy kill flags
-
- // Opcode 6 state variables (from FUN_41CADB case 4)
- int _rebelAutopilot; // DAT_00457904 - autopilot flag (0 or 1)
- int _rebelDamageLevel; // DAT_0045790a - damage level (0-5)
- int _rebelFlightDir; // DAT_00457902 - flight direction (0 or 1)
- int _rebelControlMode; // DAT_0047a7e4 - control mode flags
- int _rebelInputThrottle; // DAT_00482278 - frame counter for input throttling (every 5 frames)
-
- // View offset variables (calculated from level type)
- int _rebelViewOffsetX; // DAT_0045790c
- int _rebelViewOffsetY; // DAT_0045790e
- int _rebelViewOffset2X; // DAT_00457910
- int _rebelViewOffset2Y; // DAT_00457912
- int _rebelViewMode1; // DAT_00482270
- int _rebelViewMode2; // DAT_00482274
-
- // Original counters mirrored from DAT_00443618 (values 100..109) and DAT_004436e0 (mask counters 1..9)
- short _rebelValueCounters[10]; // Index 0 -> value 100, ... Index 9 -> 109
- short _rebelMaskCounters[10]; // Index 1..9 used; index 0 unused
- int _rebelLastCounter; // Mirrors DAT_0047ab90 (last updated counter)
+ // Per-level counters and debug flags.
+ bool _rebelOp6Initialized;
+ int _rebelHitCounter;
+ int _rebelKillCounter;
+ bool _rebelYodaMode;
+ bool _rebelMovieMode;
+ bool _rebelAutoPlay;
+
+ int _rebelWaveState;
+ int _rebelPhaseState;
+
+ int _rebelAutopilot;
+ int _rebelDamageLevel;
+ int _rebelFlightDir;
+ int _rebelControlMode;
+ int _rebelInputThrottle;
+
+ int _rebelViewOffsetX;
+ int _rebelViewOffsetY;
+ int _rebelViewOffset2X;
+ int _rebelViewOffset2Y;
+ int _rebelViewMode1;
+ int _rebelViewMode2;
+
+ short _rebelValueCounters[10];
+ short _rebelMaskCounters[10];
+ int _rebelLastCounter;
// Shield hit-point gauge: opcode-2 sets up a per-target counter; destroying a tracked
// target decrements it, and the looping attack run ends when it reaches 0.
- int8 _rebelGaugeSlot[512]; // per target ID: -1 none; 0..9 value-counter; 10+slot mask-counter
- bool _rebelShieldGateActive; // true while a shield-gated looping segment is playing
- bool _rebelShieldDestroyed; // set when the shield/reactor is destroyed during the gate
- bool _rebelReactorMode; // Level 13: finale ends when the last armed group is cleared
- bool _rebelGaugeArmed; // at least one gauge group has been set up this attempt
- int _rebelLastArmedSlot; // counter slot of the most recently armed group (-1 = none)
- bool _rebelGaugeCleared[10]; // value-counter slot fully destroyed (drives par3==2 surfaces)
+ int8 _rebelGaugeSlot[512];
+ bool _rebelShieldGateActive;
+ bool _rebelShieldDestroyed;
+ bool _rebelReactorMode;
+ bool _rebelGaugeArmed;
+ int _rebelLastArmedSlot;
+ bool _rebelGaugeCleared[10];
void resetShieldGauge();
-
- // ---------------------------------------------------------------------------
- // Handler 0x26 Turret Shot System
- // ---------------------------------------------------------------------------
- // Based on FUN_40AD63 disassembly - Turret laser rendering.
- // DAT_0044367a[2]: Shot duration counter (0=inactive)
- // DAT_0044367e[2]: Target X position
- // DAT_00443682[2]: Target Y position
- // DAT_0044368a[2]: Shot sequence number (for alternating gun pattern)
- // DAT_004436de: Level type (determines gun positions) - already have as _rebelLevelType
-
+ // Handler-specific shot state.
struct TurretShot {
- int16 counter; // DAT_0044367a[i] - duration counter, 0=inactive
- int16 targetX; // DAT_0044367e[i] - target X position
- int16 targetY; // DAT_00443682[i] - target Y position
- int16 seqNum; // DAT_0044368a[i] - shot sequence (for alternating)
- int16 gunX; // DAT_0045791c[i] - gun barrel X (Handler 25 base coords; render adds view offset)
- int16 gunY; // DAT_00457920[i] - gun barrel Y (Handler 25 base coords; render adds view offset)
+ int16 counter;
+ int16 targetX;
+ int16 targetY;
+ int16 seqNum;
+ int16 gunX;
+ int16 gunY;
};
TurretShot _turretShots[2];
- int16 _turretShotSeqCounter; // DAT_0047fe94 - global sequence counter
-
- // ---------------------------------------------------------------------------
- // Handler 8 Vehicle Shot System
- // ---------------------------------------------------------------------------
- // Based on FUN_402ED0 disassembly - Vehicle laser rendering.
- // DAT_0043e00a[2]: Shot duration counter
- // DAT_0043e00e[2]: Target X position
- // DAT_0043e012[2]: Target Y position
- // Gun position derived from ship position (_shipPosX, _shipPosY)
+ int16 _turretShotSeqCounter;
struct VehicleShot {
- int16 counter; // DAT_0043e00a[i] - duration counter, 0=inactive
- int16 targetX; // DAT_0043e00e[i] - target X position
- int16 targetY; // DAT_0043e012[i] - target Y position
+ int16 counter;
+ int16 targetX;
+ int16 targetY;
};
VehicleShot _vehicleShots[2];
- // Handler 8 shot impact animation state (FUN_402DA8 / FUN_402ED0)
- // DAT_0043f81a[7]: impact duration counter
- // DAT_0043f828[7]: impact world X
- // DAT_0043f836[7]: impact world Y
- // DAT_0043f844[7]: impact sprite index from DAT_0047e030 background mask
- // DAT_0043f852: ring-buffer index
struct VehicleShotImpact {
int16 counter;
int16 x;
@@ -1038,372 +710,186 @@ public:
VehicleShotImpact _vehicleShotImpacts[7];
int16 _vehicleShotImpactIndex;
- // ---------------------------------------------------------------------------
- // Handler 7 Third-Person Ship Shot System
- // ---------------------------------------------------------------------------
- // Based on FUN_40FADF disassembly - Third-Person Ship laser rendering.
- // DAT_00443750[2]: Shot duration counter
- // DAT_00443754[2]: Target X position
- // DAT_00443758[2]: Target Y position
- // DAT_0044375c[2]: Left gun X position
- // DAT_00443760[2]: Left gun Y position
- // DAT_00443764[2]: Right gun X position
- // DAT_00443768[2]: Right gun Y position
- // DAT_0044376c[2]: Shot variant
-
struct SpaceShot {
- int16 counter; // DAT_00443750[i] - duration counter, 0=inactive
- int16 targetX; // DAT_00443754[i] - target X position
- int16 targetY; // DAT_00443758[i] - target Y position
- int16 leftGunX; // DAT_0044375c[i] - left gun X
- int16 leftGunY; // DAT_00443760[i] - left gun Y
- int16 rightGunX; // DAT_00443764[i] - right gun X
- int16 rightGunY; // DAT_00443768[i] - right gun Y
- int16 variant; // DAT_0044376c[i] - shot variant
+ int16 counter;
+ int16 targetX;
+ int16 targetY;
+ int16 leftGunX;
+ int16 leftGunY;
+ int16 rightGunX;
+ int16 rightGunY;
+ int16 variant;
};
SpaceShot _spaceShots[2];
- int16 _spaceShotDirection; // DAT_0044374e - ship direction for gun lookup
- int16 _flyLeftGunX[35]; // DAT_004437c2 - handler 7 left muzzle X table
- int16 _flyLeftGunY[35]; // DAT_00443808 - handler 7 left muzzle Y table
- int16 _flyRightGunX[35]; // DAT_0044384e - handler 7 right muzzle X table
- int16 _flyRightGunY[35]; // DAT_00443894 - handler 7 right muzzle Y table
+ int16 _spaceShotDirection;
+ int16 _flyLeftGunX[35];
+ int16 _flyLeftGunY[35];
+ int16 _flyRightGunX[35];
+ int16 _flyRightGunY[35];
bool _flyLeftGunTableLoaded;
bool _flyRightGunTableLoaded;
- // Handler-specific shot spawning
- void spawnTurretShot(int x, int y); // Handler 0x26
- void spawnVehicleShot(int x, int y); // Handler 8
- void spawnSpaceShot(int x, int y); // Handler 7
- void spawnHandler25Shot(int x, int y); // Handler 25 (speeder bike)
- void spawnShot(int x, int y); // Dispatcher based on current handler
+ void spawnTurretShot(int x, int y);
+ void spawnVehicleShot(int x, int y);
+ void spawnSpaceShot(int x, int y);
+ void spawnHandler25Shot(int x, int y);
+ void spawnShot(int x, int y);
- // Handler-specific explosion rendering
void renderExplosionFrame(byte *renderBitmap, int pitch, int width, int height,
Explosion &explosion, int screenX, int screenY, ExplosionFrameAdvance advance,
bool resolutionDependentScale);
- void renderTurretExplosions(byte *renderBitmap, int pitch, int width, int height); // FUN_409FBC (Handler 0x26)
- void renderVehicleExplosions(byte *renderBitmap, int pitch, int width, int height); // FUN_402696 (Handler 8)
- void renderSpaceExplosions(byte *renderBitmap, int pitch, int width, int height); // FUN_40F1C5 (Handler 7)
- void renderHandler25Explosions(byte *renderBitmap, int pitch, int width, int height); // FUN_41F29A (Handler 25)
+ void renderTurretExplosions(byte *renderBitmap, int pitch, int width, int height);
+ void renderVehicleExplosions(byte *renderBitmap, int pitch, int width, int height);
+ void renderSpaceExplosions(byte *renderBitmap, int pitch, int width, int height);
+ void renderHandler25Explosions(byte *renderBitmap, int pitch, int width, int height);
- // Handler-specific laser rendering (FUN_40AD63, FUN_402ED0, FUN_40FADF, FUN_0041f004)
void renderTurretLaserShots(byte *renderBitmap, int pitch, int width, int height);
void renderVehicleLaserShots(byte *renderBitmap, int pitch, int width, int height);
void renderSpaceLaserShots(byte *renderBitmap, int pitch, int width, int height);
void renderHandler25LaserShots(byte *renderBitmap, int pitch, int width, int height);
- // Get max shot duration from level table (DAT_0047e0f0 indexed by DAT_0047a7fa/DAT_0047a7f8)
int16 getShotMaxDuration();
- // ---------------------------------------------------------------------------
- // Handler 8 Ship System
- // ---------------------------------------------------------------------------
- // For third-person on foot missions (Level 2, 11), the player controls Rookie One
- // that can turn in different directions. The ship sprite comes from
- // NUT files loaded via IACT opcode 8.
- //
- // Based on FUN_00401234 and FUN_00401ccf disassembly:
- // - DAT_0047e010: Primary ship sprite (POV001, subcase 1)
- // - DAT_0047e028: Secondary ship sprite (POV004, subcase 3)
- // - DAT_0047e020: Shot impact overlay (POV002, subcase 6)
- // - DAT_0047e018: Shot impact overlay (POV003, subcase 7)
- // - DAT_0043e006: Ship X position (raw, needs conversion for display)
- // - DAT_0043e008: Ship Y position (raw, needs conversion for display)
- // - DAT_0043e000: Level mode from opcode 6 par4
-
- NutRenderer *_shipSprite; // DAT_0047e010 - Primary ship NUT
- NutRenderer *_shipSprite2; // DAT_0047e028 - Secondary ship NUT
- NutRenderer *_shipOverlay1; // DAT_0047e020 - Shot impact overlay
- NutRenderer *_shipOverlay2; // DAT_0047e018 - Shot impact overlay
-
- // Level 2 background buffer (DAT_0047e030)
- // Loaded from IACT opcode 8, par4=5 - contains 320x200 background image
- // decoded from embedded ANIM in gameplay video frame 0
+ // Handler 8 ship and background state.
+ NutRenderer *_shipSprite;
+ NutRenderer *_shipSprite2;
+ NutRenderer *_shipOverlay1;
+ NutRenderer *_shipOverlay2;
+
byte *_level2Background;
bool _level2BackgroundLoaded;
- // Ship position tracking (matches DAT_0043e006/008)
- // These are "raw" positions that get converted for display
- int16 _shipPosX; // DAT_0043e006
- int16 _shipPosY; // DAT_0043e008
-
- // Ship target positions (where ship is trying to move to)
- // Set from mouse/joystick input in opcode 6 processing
- int16 _shipTargetX; // DAT_0043e002 - Target X
- int16 _shipTargetY; // DAT_0043e004 - Target Y
-
- // Level mode for handler 8 (different from _rebelLevelType)
- // Set by opcode 6 par4, affects ship rendering behavior
- // Mode 0/1/3: "Shooting" - full movement range (127)
- // Mode 2: "Covered" - restricted movement (41) - behind cover
- // Mode 4: "Autopilot" - no shooting, scripted movement
- // Mode 5: "Cutscene" - ship not rendered
- int16 _shipLevelMode; // DAT_0043e000
- char _handler8HudGlyph; // DAT_0047e048
- int16 _handler8HudMessageTimer; // DAT_0047e040
- int16 _handler8HudMessageIndex; // DAT_0047e044
-
- // Movement range limiter for Handler 8 (Level 2 covered/shooting states)
- // Controls horizontal movement range: 127 for shooting, 41 for covered
- // Gradually transitions by ±10 per frame for smooth animation
- int16 _movementRangeLimit; // DAT_0047e034
-
- // Control mode for Handler 7 (third-person ship) - DAT_004437c0
- // Set by IACT opcode 6 par3 when handler is 7
- // Determines shooting capability and collision zone type:
- // Mode 0: Flight/avoid mode - no shooting, uses secondary zones (sub-opcode 0x0E)
- // Mode 1: Alternate flight mode - no shooting, uses primary zones (sub-opcode 0x0D)
- // Mode 2: Combat mode - shooting ENABLED, crosshair shown, uses secondary zones
- // In Level 3's first sequence, par3=0 (no shooting - pure obstacle avoidance)
- // In combat sequences, par3=2 (shooting enabled)
- int16 _flyControlMode; // DAT_004437c0
-
- // Ship firing state (from mouse button)
- bool _shipFiring;
+ int16 _shipPosX;
+ int16 _shipPosY;
+ int16 _shipTargetX;
+ int16 _shipTargetY;
+ int16 _shipLevelMode;
+ char _handler8HudGlyph;
+ int16 _handler8HudMessageTimer;
+ int16 _handler8HudMessageIndex;
+
+ // Handler 8 movement range transitions between cover and shooting states.
+ int16 _movementRangeLimit;
+
+ // Handler 7 control mode selects shooting and collision behavior.
+ int16 _flyControlMode;
- // Previous mouse button state for edge detection (bit 0=left, bit 1=right, bit 2=middle)
+ bool _shipFiring;
uint32 _prevMouseButtons;
- // Ship direction index for sprite selection (Handler 7)
- // Calculated from ship position: horizontal * 7 + vertical
- // horizontal: 0-4 (left to right), vertical: 0-6 (up to down)
- // Used to select which embedded HUD userId to render
int16 _shipDirectionIndex;
- int16 _shipDirectionH; // Horizontal direction (0-4, center=2)
- int16 _shipDirectionV; // Vertical direction (0-6, center=3)
-
- // ---------------------------------------------------------------------------
- // Handler 7 FLY Ship System
- // ---------------------------------------------------------------------------
- // For third-person ship missions (Level 3, etc.), Handler 7 uses a 35-frame
- // direction-based ship sprite system. The ship visually banks and turns
- // based on player position using a 5x7 grid of sprites.
- //
- // Based on FUN_0040c3cc and FUN_0040d836 disassembly:
- // - DAT_0047fee8: Ship direction sprites (FLY001, par3=1, 35 frames)
- // - DAT_0047fef0: Ship effect sprites (FLY002, par3=3)
- // - DAT_0047fef8: Targeting overlay (FLY003, par3=2)
- // - DAT_0047ff00: High-res alternative (FLY004, par3=11)
- // - DAT_00443708: Ship X position, DAT_0044370a: Ship Y position
- // - DAT_0044370c: Smoothed horizontal velocity, DAT_0044370e: Vertical input
-
- NutRenderer *_flyShipSprite; // DAT_0047fee8 - FLY001 (35 direction frames)
- NutRenderer *_flyLaserSprite; // DAT_0047fef0 - FLY002 (danger/overlay effects)
- NutRenderer *_flyTargetSprite; // DAT_0047fef8 - FLY003
- NutRenderer *_flyHiResSprite; // DAT_0047ff00 - FLY004
- int16 _flyEffectAnimCounter; // DAT_0047ff1c - animated FLY002 cue counter
- int16 _flyOverlayRepeatCount; // DAT_00443b52 - repeats for ship overlay effect
-
- // Handler 7 ship state (FUN_40C3CC / FUN_0040d836)
- // Position in game coordinate space [20,404]x[20,240], center=(212,130)
- int16 _flyShipScreenX; // DAT_00443708 - Ship X game position
- int16 _flyShipScreenY; // DAT_0044370a - Ship Y game position
-
- // Physics state (velocity-based movement system from FUN_40C3CC case 4)
- int16 _smoothedVelocity; // DAT_0044370c - Averaged horizontal velocity (from history)
- int16 _verticalInput; // DAT_0044370e - Stored vertical input component
- int16 _velocityHistory[25]; // DAT_00443716 - Horizontal velocity ring buffer (25 entries)
- int16 _windHistoryX[15]; // DAT_00443b16 - Wind X history buffer
- int16 _windHistoryY[15]; // DAT_00443b34 - Wind Y history buffer
- int16 _windParamX; // DAT_00443b12 - Wind X (from opcode 7 par4=0)
- int16 _windParamY; // DAT_00443b14 - Wind Y (from opcode 7 par4=0)
-
- // Perspective view offsets (computed from ship position, used for rendering)
- int16 _perspectiveX; // DAT_00443712 - Perspective shift X
- int16 _perspectiveY; // DAT_00443714 - Perspective shift Y
- int16 _viewShift; // DAT_00443710 - Clamped smoothed velocity for view transform
- bool _facingRight; // DAT_0047ab8c - Ship facing right of center
-
- // ---------------------------------------------------------------------------
- // Handler 25 (0x19) GRD Ship System
- // ---------------------------------------------------------------------------
- // For mixed mode missions (Level 2 speeder bike, etc.), Handler 25 uses GRD NUT
- // sprites loaded via IACT opcode 8. The ship is rendered based on DAT_00457900 mode.
- //
- // Based on FUN_0041cadb case 6 and FUN_0041db5e disassembly:
- // - DAT_00482240: Primary ship sprite (GRD001, par4=1)
- // - DAT_00482238: Secondary ship sprite (GRD002, par4=2)
- // - DAT_00482258: Mode 3 overlay sprite (GRD005, par4=10)
- // - DAT_00457900: Sprite mode (1,2,3,4) controls which sprite to draw
- // - DAT_00457910: Ship X screen position
- // - DAT_00457912: Ship Y screen position
- // - DAT_00457902: Flight direction (affects GRD002 mirroring)
- // - DAT_0045790a: Damage level (affects rendering conditions)
-
- NutRenderer *_grd001Sprite; // DAT_00482240 - GRD001 primary ship NUT
- NutRenderer *_grd002Sprite; // DAT_00482238 - GRD002 secondary ship NUT
- NutRenderer *_grd005Sprite; // DAT_00482258 - GRD005 mode 3 overlay NUT
-
- // Handler 25 shot-origin lookup tables from opcode 8/par4=8 text payload.
- // Indices 5..19 are filled by the "%hd %hd ..." parser in FUN_0041CADB case 6.
- // Uncovered Level 2 firing uses indices 5..14.
- int16 _grdShotOriginX[30]; // DAT_004578a6 equivalent
- int16 _grdShotOriginY[30]; // DAT_004578c6 equivalent
+ int16 _shipDirectionH;
+ int16 _shipDirectionV;
+
+ // Handler 7 FLY ship state.
+ NutRenderer *_flyShipSprite;
+ NutRenderer *_flyLaserSprite;
+ NutRenderer *_flyTargetSprite;
+ NutRenderer *_flyHiResSprite;
+ int16 _flyEffectAnimCounter;
+ int16 _flyOverlayRepeatCount;
+
+ int16 _flyShipScreenX;
+ int16 _flyShipScreenY;
+
+ int16 _smoothedVelocity;
+ int16 _verticalInput;
+ int16 _velocityHistory[25];
+ int16 _windHistoryX[15];
+ int16 _windHistoryY[15];
+ int16 _windParamX;
+ int16 _windParamY;
+
+ int16 _perspectiveX;
+ int16 _perspectiveY;
+ int16 _viewShift;
+ bool _facingRight;
+
+ // Handler 25 GRD ship state.
+ NutRenderer *_grd001Sprite;
+ NutRenderer *_grd002Sprite;
+ NutRenderer *_grd005Sprite;
+
+ int16 _grdShotOriginX[30];
+ int16 _grdShotOriginY[30];
bool _grdShotOriginTableLoaded;
- // Handler 25 sprite mode (DAT_00457900) - set by opcode 6 par3
- // Controls which sprite variant to draw:
- // 1: Draw _grd001Sprite normally
- // 2: Draw _grd001Sprite only when damaged (DAT_0045790a != 0)
- // 3: Draw _grd001Sprite and GRD005 (DAT_00482258) overlay
- // 4: Draw _grd001Sprite with buffer offset
- int16 _grdSpriteMode; // DAT_00457900
-
- // Render Handler 25 ship sprites
- // renderHandler25ShipPre: Draw GRD001 BEFORE FOBJ (in procPreRendering)
- // renderHandler25Ship: Draw GRD002 and other overlays AFTER FOBJ (in procPostRendering)
+ int16 _grdSpriteMode;
+
void renderHandler25ShipPre(byte *renderBitmap, int pitch, int width, int height);
void renderHandler25Ship(byte *renderBitmap, int pitch, int width, int height);
- // ---------------------------------------------------------------------------
- // Handler 0x26 Turret HUD Overlays
- // ---------------------------------------------------------------------------
- // For turret missions (Level 1, etc.), Handler 0x26 uses NUT-based HUD overlays
- // loaded via IACT opcode 8. These contain animated cockpit panel elements.
- //
- // Based on FUN_00407fcb and FUN_004089ab disassembly:
- // - DAT_0047fe78: Primary HUD overlay (GRD001/002, par3=1 or 2, 6 animation frames)
- // - DAT_0047fe80: Secondary HUD overlay (GRD010, par3=3 or 4, static or animated)
- //
- // Animation: The HUD overlay cycles through 6 sprite frames for blinking lights
- // Formula: spriteIndex = (frameCounter / 2) % 6
- //
- // Position formula (from FUN_004089ab lines 203-222):
- // X = 160 + (mouseOffsetX >> 4) - (width / 2) - spriteOffsetX
- // Y = 182 - (mouseOffsetY >> 4) - height - spriteOffsetY
-
- NutRenderer *_hudOverlayNut; // DAT_0047fe78 - Primary HUD overlay (animated)
- NutRenderer *_hudOverlay2Nut; // DAT_0047fe80 - Secondary HUD overlay
-
- /* Difficulty Level (0-5, from pilot menu; maps directly to table rows) */
- int _difficulty;
+ // Handler 0x26 turret HUD overlays.
+ NutRenderer *_hudOverlayNut;
+ NutRenderer *_hudOverlay2Nut;
- // ---------------------------------------------------------------------------
- // Per-Level Difficulty Parameters
- // ---------------------------------------------------------------------------
- // Extracted from RA2WIN95.EXE at VA 0x47e0f0.
- // 2D table indexed by difficulty (0-5) Ã level type (0-16)
- // Original indexing: &DAT_0047e0f0 + chapter * 0x242 + levelType * 0x22
- // Level type (_rebelLevelType) is set by IACT opcode 6 par3
- // 17 entries: Lv1-5(0-4), Lv6A/6B(5-6), Lv7-14(7-14), Lv15A/15B(15-16)
- // -1 = not applicable for this level type
+ int _difficulty;
+ // Per-level difficulty parameters.
struct LevelDifficultyParams {
- int16 laserDelay; // +0x00: Laser fire delay (lower = faster)
- int16 snapDistance; // +0x02: Crosshair snap distance to targets
- int16 missDamage; // +0x04: Damage from enemy misses / grazing hits
- int16 dodgeDamage; // +0x06: Damage from wall/obstacle collisions
- int16 shotDamage; // +0x08: Damage from enemy projectile hits
- int16 specialDamage; // +0x0A: Damage from special attacks
- int16 shotAccuracy; // +0x0C: Enemy shot accuracy (0-100, -1=disabled)
- int16 hitPoints; // +0x0E: Points awarded for destroying an enemy
- int16 dodgePoints; // +0x10: Points awarded for dodging an obstacle
- int16 timePoints; // +0x12: Time bonus points
- int16 levelPoints; // +0x14: End-of-level bonus points
- int16 specialPoints; // +0x16: Special action bonus points
- int16 flags; // +0x18: Behavior flags bitfield
- int16 rollRate; // +0x1A: Ship roll rate (flight controls)
- int16 liftRate; // +0x1C: Ship lift rate (flight controls)
- int16 slideRate; // +0x1E: Ship slide rate (flight controls)
- int16 driftRate; // +0x20: Ship drift rate (flight controls)
+ int16 laserDelay;
+ int16 snapDistance;
+ int16 missDamage;
+ int16 dodgeDamage;
+ int16 shotDamage;
+ int16 specialDamage;
+ int16 shotAccuracy;
+ int16 hitPoints;
+ int16 dodgePoints;
+ int16 timePoints;
+ int16 levelPoints;
+ int16 specialPoints;
+ int16 flags;
+ int16 rollRate;
+ int16 liftRate;
+ int16 slideRate;
+ int16 driftRate;
};
- // Table: 6 difficulty levels à 17 level types.
- // Menu labels in GAME.TRS are: Beginner, Novice, Standard, Expert, Custom1, Custom2.
- // Custom1 is identical to Standard; Custom2 matches Custom1 except Lv15B drift fields.
static const LevelDifficultyParams kDifficultyTable[6][17];
- // Look up difficulty parameters for current _difficulty and _rebelLevelType
LevelDifficultyParams getDifficultyParams() const;
- // Score system (FUN_0041bf8d equivalent)
- // Adds points to score and awards bonus life when crossing threshold
void addScore(int points);
-
- // Score lookup uses LevelDifficultyParams fields:
- // hitPoints (DAT_0047e0fe), dodgePoints (DAT_0047e100), timePoints (DAT_0047e102)
-
- // Render score text to HUD (called from procPostRendering)
void renderScoreHUD(byte *renderBitmap, int pitch, int width, int height, int statusBarY);
- // ---------------------------------------------------------------------------
- // Pause Overlay
- // ---------------------------------------------------------------------------
- // Show pause overlay with dimming effect and "PAUSED" text.
- // Emulates FUN_405A21 pause rendering (lines 242-305)
void showPauseOverlay();
- // Target lock timer (DAT_00443676) - set to 7 when crosshair is over enemy
int _targetLockTimer;
- // ---------------------------------------------------------------------------
- // Audio Handling
- // ---------------------------------------------------------------------------
- // RA2 doesn't use iMUSE -- audio is handled directly through the mixer.
-
+ // Audio.
RebelAudio _audio;
- // Initialize audio system for RA2
void initAudio(int sampleRate);
-
- // Terminate audio system
void terminateAudio();
-
- // Reset streamed SAN audio at independent video boundaries.
void resetVideoAudio();
-
- // Process audio dispatches - called from SmushPlayer when iMUSE is null
- // This replaces the iMUSE audio path for RA2
void processAudioFrame(int16 feedSize);
-
- // Queue audio data for playback on a specific track
void queueAudioData(int trackIdx, uint8 *data, int32 size, int volume, int pan);
- // ---------------------------------------------------------------------------
- // Sound Effects (SAD files)
- // ---------------------------------------------------------------------------
- // 8 standalone SAUD files in SYSTM/ loaded at init for one-shot SFX.
- // Slot mapping (from FUN_0042a3b0 init):
- // 0=BLAST.SAD 1=CRASH.SAD 2=EXPLODE.SAD 3=ALERT.SAD
- // 4=LOCKON.SAD 5=BONUS.SAD 6=HBLAST.SAD 7=TBLAST.SAD
-
+ // One-shot SFX.
static const int kRA2NumSfx = 8;
- byte *_sfxData[kRA2NumSfx]; // Loaded PCM data for each SAD slot
- uint32 _sfxSize[kRA2NumSfx]; // PCM data size per slot
- Audio::SoundHandle _sfxHandles[kRA2NumSfx]; // Mixer handles for SFX playback
+ byte *_sfxData[kRA2NumSfx];
+ uint32 _sfxSize[kRA2NumSfx];
+ Audio::SoundHandle _sfxHandles[kRA2NumSfx];
- // Load all SAD files from SYSTM/ directory
void loadSfx();
-
- // Free all loaded SFX data
void freeSfx();
-
- // Play a one-shot sound effect
- // slot: 0-7 (SAD file index), volume: 0-127, pan: -127..+127
void playSfx(int slot, int volume, int pan);
- // ---------------------------------------------------------------------------
- // Auxiliary Sound Buffers
- // ---------------------------------------------------------------------------
- // 4 pre-allocated buffers (30000 bytes each) loaded from IACT stream data.
- // Original: DAT_00480308[0..3], loaded via FUN_004118df, played via FUN_00411931.
- // Used for embedded sound effects (e.g., soldier death sounds in handler 8 levels).
+ // Embedded sound effects.
static const int kRA2NumAuxSfx = 4;
static const int kRA2AuxBufSize = 30000;
- byte *_auxSfxData[kRA2NumAuxSfx]; // Pre-allocated buffer pointers
- uint32 _auxSfxSize[kRA2NumAuxSfx]; // Current data size in each buffer
- Audio::SoundHandle _auxSfxHandles[kRA2NumAuxSfx]; // Mixer handles
+ byte *_auxSfxData[kRA2NumAuxSfx];
+ uint32 _auxSfxSize[kRA2NumAuxSfx];
+ Audio::SoundHandle _auxSfxHandles[kRA2NumAuxSfx];
- // Load sound data into auxiliary buffer (FUN_004118df equivalent)
void loadAuxSfx(int buffer, const byte *data, uint32 size);
-
- // Play from auxiliary buffer (FUN_00411931 equivalent)
void playAuxSfx(int buffer, int volume, int pan);
};
-} // End of namespace Insane
+} // namespace Scumm
#endif
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index e7538874c1a..a153af2631b 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -42,9 +42,7 @@ extern void smushDecodeRLE(byte *dst, const byte *src, int left, int top, int wi
extern void smushDecodeUncompressed(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
int getRebel2IndicatorScale(int width, int height) {
- // Original only doubles these anchors in true high-res mode (DAT_0047a808 >= 2).
// RA2's 424x260 low-res gameplay buffer is still displayed through a 320x200
- // viewport, so it uses the original 320x200 indicator coordinates.
return (width >= 640 || height >= 400) ? 2 : 1;
}
@@ -185,9 +183,6 @@ static bool readEmbeddedSanChunkHeader(Common::SeekableReadStream &stream, int64
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:
- // - Handler 7/8: Ship direction sprites selected based on direction
- // - Handler 0x26: Cockpit HUD positioned based on mouse/crosshair
- //
// 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.
bool skipImmediateDraw = (_rebelHandler == 7 || _rebelHandler == 8 ||
@@ -195,8 +190,6 @@ void InsaneRebel2::renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFram
// Handler 25 overlays:
// - userId 4 (corridor overlay): draw immediately at the current view offset.
- // FUN_0041cadb case 6/par4=4 decodes DAT_00482268, then calls
- // FUN_00428a10(param_1, 0, DAT_0045790c, DAT_0045790e, DAT_00482268).
// - userId 6, 7 (static overlays): Draw immediately (they don't move)
if (_rebelHandler == 0x19 && userId == 4) {
drawHandler25CorridorOverlay(renderBitmap);
@@ -273,12 +266,9 @@ 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) {
@@ -388,29 +378,23 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
return;
}
- // Decode based on codec - use extracted helper functions (FUN_0042BD60, etc.)
if (codec == 1 || codec == 3) {
- // Codec 1/3: RLE - use existing decoder (FUN_0042C590)
smushDecodeRLE(frame.pixels, fobjData, 0, 0, width, height, width);
frame.valid = true;
debugC(DEBUG_INSANE, "Decoded embedded HUD (codec %d/RLE): %dx%d", codec, width, height);
} else if (codec == 20) {
- // Codec 20: Uncompressed (FUN_0042C400)
smushDecodeUncompressed(frame.pixels, fobjData, 0, 0, width, height, width);
frame.valid = true;
debugC(DEBUG_INSANE, "Decoded embedded HUD (codec 20/raw): %dx%d", width, height);
} else if (codec == 21 || codec == 44) {
- // Codec 21/44: Line update (FUN_0042BD60)
smushDecodeLineUpdate(frame.pixels, fobjData, 0, 0, width, height, width, dataSize);
frame.valid = true;
debugC(DEBUG_INSANE, "Decoded embedded HUD (codec %d/line update): %dx%d", codec, width, height);
} else if (codec == 45) {
- // Codec 45: blur/wipe mask (FUN_0042B460 -> FUN_0042B530 -> FUN_0042DDF0)
smushDecodeRA2Blur(frame.pixels, fobjData, 0, 0, width, height, width, dataSize,
_rebelEmbeddedCodec45Palette, _rebelEmbeddedCodec45Lookup);
frame.valid = true;
} else if (codec == 23) {
- // Codec 23: Skip/copy with embedded RLE (FUN_0042BBF0)
smushDecodeSkipRLE(frame.pixels, fobjData, 0, 0, width, height, width, dataSize);
frame.valid = true;
debugC(DEBUG_INSANE, "Decoded embedded HUD (codec 23/skip-RLE): %dx%d", width, height);
@@ -452,8 +436,6 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
// Spawn explosion into the shared 5-slot system.
// spawnExplosion -- Allocate an explosion slot at the given position.
-// Per-handler slot arrays: 0x26=DAT_0044368e[], 8=DAT_0043f854[],
-// 7=DAT_00443770[], 25=DAT_0045792c[]. All share same logic: find first
// 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++) {
@@ -468,11 +450,9 @@ void InsaneRebel2::spawnExplosion(int x, int y, int objectHalfWidth) {
}
}
-// getShotMaxDuration -- Shot duration from per-level difficulty table (DAT_0047e0f0).
// Used both as initial shot counter AND maxFrames for beam rendering.
int16 InsaneRebel2::getShotMaxDuration() {
LevelDifficultyParams params = getDifficultyParams();
- // laserDelay = DAT_0047e0f0 field: shot duration in frames
// Clamp to reasonable range to avoid division by zero or extreme beams
int16 duration = params.laserDelay;
if (duration <= 0)
@@ -500,29 +480,25 @@ void InsaneRebel2::spawnShot(int x, int y) {
}
}
-// spawnTurretShot -- Handler 0x26 turret shot spawn (FUN_4089AB).
void InsaneRebel2::spawnTurretShot(int x, int y) {
for (int i = 0; i < 2; i++) {
if (_turretShots[i].counter == 0) {
- // FUN_0041189e(-(ushort)(DAT_004436de == 5) & 7, i + 1, 0x7f, 0, 0)
// levelType 5: BLAST.SAD (slot 0), otherwise: TBLAST.SAD (slot 7)
playSfx((_rebelLevelType == 5) ? 0 : 7, 127, 0);
_turretShots[i].counter = getShotMaxDuration();
_turretShots[i].seqNum = _turretShotSeqCounter;
_turretShotSeqCounter++;
- _turretShots[i].targetX = x + _viewX; // DAT_0044366e in original
- _turretShots[i].targetY = y + _viewY; // DAT_00443670 in original
+ _turretShots[i].targetX = x + _viewX;
+ _turretShots[i].targetY = y + _viewY;
break;
}
}
}
-// spawnVehicleShot -- Handler 8 vehicle shot spawn (FUN_401CCF).
void InsaneRebel2::spawnVehicleShot(int x, int y) {
for (int i = 0; i < 2; i++) {
if (_vehicleShots[i].counter == 0) {
- // FUN_0041189e(6, local_c + 1, 0x7f, 0, 0) â HBLAST.SAD
playSfx(6, 127, 0);
_vehicleShots[i].counter = getShotMaxDuration();
_vehicleShots[i].targetX = x;
@@ -532,10 +508,7 @@ void InsaneRebel2::spawnVehicleShot(int x, int y) {
}
}
-// spawnHandler25Shot -- Handler 25 on-foot shot spawn (FUN_0041db5e).
// Gun position from GRD002 offset tables:
-// DAT_0045791c[i] = gunOffsetTable[spriteIdx] + DAT_00457910 - DAT_0045790c
-// DAT_00457920[i] = gunYTable[spriteIdx] + DAT_00457912 - DAT_0045790e
void InsaneRebel2::spawnHandler25Shot(int x, int y) {
// Handler 25 can only shoot when uncovered (damage == 0)
if (_rebelDamageLevel != 0) {
@@ -544,7 +517,6 @@ void InsaneRebel2::spawnHandler25Shot(int x, int y) {
for (int i = 0; i < 2; i++) {
if (_turretShots[i].counter == 0) {
- // FUN_0041189e(6, local_1c + 1, 0x7f, 0, 0) â HBLAST.SAD
playSfx(6, 127, 0);
_turretShots[i].counter = getShotMaxDuration();
@@ -555,10 +527,6 @@ void InsaneRebel2::spawnHandler25Shot(int x, int y) {
_turretShots[i].targetX = x + _viewX;
_turretShots[i].targetY = y + _viewY;
- // Compute gun position from original lookup tables.
- // Original (FUN_41DB5E) stores:
- // DAT_0045791c[i] = gunXTable[spriteIdx] + DAT_00457910 - DAT_0045790c
- // DAT_00457920[i] = gunYTable[spriteIdx] + DAT_00457912 - DAT_0045790e
// where gunXTable/gunYTable are loaded by opcode 8, par4=8.
if (_grdShotOriginTableLoaded) {
// Compute current sprite index (same logic as renderHandler25Ship)
@@ -598,7 +566,6 @@ void InsaneRebel2::spawnHandler25Shot(int x, int y) {
int16 gunXTable = _grdShotOriginX[spriteIdx];
int16 gunYTable = _grdShotOriginY[spriteIdx];
- // Mirrored X when DAT_00457902 != 0.
if (_rebelFlightDir != 0) {
gunXTable = 320 - gunXTable;
}
@@ -648,7 +615,6 @@ Common::Point InsaneRebel2::getHandler7ProjectedPoint() {
Common::Point InsaneRebel2::getHandler7ShotTargetPoint() {
// Handler 7 targets the projected ship/crosshair point computed in
- // FUN_0040d836; it does not use the generic mouse position for combat shots.
Common::Point projected = getHandler7ProjectedPoint();
return Common::Point(projected.x + _smoothedVelocity / 2,
@@ -657,16 +623,13 @@ Common::Point InsaneRebel2::getHandler7ShotTargetPoint() {
Common::Point InsaneRebel2::getHandler8ShotTargetPoint() {
// Handler 8 stores and draws the shot target from the damped ship
- // position in FUN_00401ccf, not from the current mouse/analog aim point.
return Common::Point(((_shipPosX - 0xa0) >> 3) + 0xa0,
((_shipPosY - 0x28) >> 2) + 0x69);
}
-// spawnSpaceShot -- Handler 7 space combat shot spawn (FUN_40D836).
void InsaneRebel2::spawnSpaceShot(int x, int y) {
for (int i = 0; i < 2; i++) {
if (_spaceShots[i].counter == 0) {
- // FUN_0041189e(6, local_2c + 1, 0x7f, 0, 0) â HBLAST.SAD
playSfx(6, 127, 0);
_spaceShots[i].counter = getShotMaxDuration();
@@ -677,7 +640,6 @@ void InsaneRebel2::spawnSpaceShot(int x, int y) {
_spaceShots[i].targetX = target.x;
_spaceShots[i].targetY = target.y;
- // FUN_0040d836 uses muzzle tables loaded by FUN_0040fcfa from opcode
// 8 par4=12/13. Values are centered FLY coordinates, adjusted by
// the projected ship point before drawing the line.
if (_flyLeftGunTableLoaded) {
@@ -752,11 +714,9 @@ void InsaneRebel2::drawTexturedLine(byte *dst, int pitch, int width, int height,
}
}
-// drawTexturedSegment -- Textured segment between two points (FUN_00429360 port).
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) {
- // Near-direct port of FUN_00429360.
// Only color 0 is transparent.
int sVar4 = clipLeft;
int sVar1 = clipTop;
@@ -1095,24 +1055,19 @@ void drawTexturedSegment(byte *dst, int pitch, int width, int height,
}
-// initLaserTexture -- Pre-render a NUT sprite into the laser texture buffer (FUN_0040BAB0).
void InsaneRebel2::initLaserTexture(NutRenderer *nut, int spriteIdx) {
if (!nut || spriteIdx >= nut->getNumChars())
return;
- // Get sprite dimensions (FUN_0040BAB0 lines 13-14)
int16 texWidth = nut->getCharWidth(spriteIdx);
int16 texHeight = nut->getCharHeight(spriteIdx);
- // Clamp height to max 15 pixels (FUN_0040BAB0 lines 15-17)
if (texHeight > 15) {
texHeight = 15;
}
- // Free existing texture if any (FUN_0040BAB0 lines 18-20)
freeLaserTexture();
- // Allocate new buffer (FUN_0040BAB0 line 21)
_laserTexture.width = texWidth;
_laserTexture.height = texHeight;
_laserTexture.pixels = (byte *)calloc(texWidth * texHeight, 1);
@@ -1120,7 +1075,6 @@ void InsaneRebel2::initLaserTexture(NutRenderer *nut, int spriteIdx) {
if (!_laserTexture.pixels)
return;
- // FUN_0040BAB0 draws the sprite through the normal NUT blitter (FUN_004236e0),
// 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);
@@ -1141,7 +1095,6 @@ void InsaneRebel2::initLaserTexture(NutRenderer *nut, int spriteIdx) {
continue;
byte px = srcRow[sx];
- // FUN_00429360 (beam raster) only treats 0 as transparent.
// Keep 231 pixels from the source texture to avoid dropping beam sub-segments.
if (px != 0) {
dstRow[dx] = px;
@@ -1154,7 +1107,6 @@ void InsaneRebel2::initLaserTexture(NutRenderer *nut, int spriteIdx) {
texWidth, texHeight, spriteIdx, srcXOff, srcYOff, srcWidth, srcHeight);
}
-// freeLaserTexture -- Emulates FUN_0040BBD1.
void InsaneRebel2::freeLaserTexture() {
free(_laserTexture.pixels);
_laserTexture.pixels = nullptr;
@@ -1162,16 +1114,11 @@ void InsaneRebel2::freeLaserTexture() {
_laserTexture.height = 0;
}
-//
-// initEdgeTable -- Initialize edge blend table (FUN_410510).
-//
// 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) {
- // Default table initialization (FUN_410510 param_1==NULL path, lines 12-36)
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)
@@ -1179,12 +1126,10 @@ void InsaneRebel2::initEdgeTable(const byte *data) {
_edgeTable[b + a * 256] = (byte)a;
}
}
- // Special entries (FUN_410510 lines 33-36)
- _edgeTable[0x42 * 256 + 0xf1] = 0x42; // DAT_00447ff1
- _edgeTable[0x42 + 0xf0 * 256] = 0x42; // DAT_004480f0 (symmetric)
- _edgeTable[0x41 * 256 + 0xb0] = 0x41; // DAT_00447fb0
+ _edgeTable[0x42 * 256 + 0xf1] = 0x42;
+ _edgeTable[0x42 + 0xf0 * 256] = 0x42;
+ _edgeTable[0x41 * 256 + 0xb0] = 0x41;
} else {
- // Load table from IACT data (FUN_410510 non-NULL path, lines 39-47)
// Data format: 8-byte header + upper triangle of 256x256 symmetric table
const byte *src = data + 8;
for (int a = 0; a < 256; a++) {
@@ -1197,22 +1142,14 @@ void InsaneRebel2::initEdgeTable(const byte *data) {
}
}
-//
-// drawEdgeHighlightLine -- Edge highlight line using the blend table (FUN_410962).
// 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.
-//
-// param_1 = dst buffer info (base pointer at [0], pitch at [1], width at word [1], height at byte offset 6)
-// param_2 = clip rect (or NULL for full buffer)
-// param_3..param_6 = x0, y0, x1, y1 line endpoints
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) {
- // Clip region (FUN_410962 lines 19-30). Clip is provided by caller (gameplay viewport).
int16 clipLeft = CLIP<int16>(clipLeftIn, 1, width - 2);
int16 clipTop = CLIP<int16>(clipTopIn, 1, height - 2);
int16 clipRight = CLIP<int16>(clipRightIn, 1, width - 2);
@@ -1220,7 +1157,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
if (clipLeft > clipRight || clipTop > clipBottom)
return;
- // Clip X endpoints (FUN_410962 lines 35-69)
if (x0 == x1) {
if (x0 < clipLeft || x0 > clipRight)
return;
@@ -1245,7 +1181,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
- // Clip Y endpoints (FUN_410962 lines 71-106)
if (y0 == y1) {
if (y0 < clipTop || y0 > clipBottom)
return;
@@ -1270,12 +1205,10 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
- // Calculate starting pixel address and deltas (FUN_410962 lines 107-110)
byte *pixel = dst + y0 * pitch + x0;
int16 dx = x1 - x0;
int16 dy = y1 - y0;
- // Bresenham line with perpendicular neighbor lookup (FUN_410962 lines 111-270)
// 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])
@@ -1315,7 +1248,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
} else if (dx < 0 || dy < 0) {
- // Mixed negative direction cases (FUN_410962 lines 149-240)
if (dy < 0) {
if (dx < 0) {
// Both negative: going up-left
@@ -1413,7 +1345,6 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
} else {
- // Both positive: going right-down (FUN_410962 lines 242-270)
if (dy < dx) {
// X-major: read above/below
int err = dx >> 1;
@@ -1446,22 +1377,10 @@ void InsaneRebel2::drawEdgeHighlightLine(byte *dst, int pitch, int width, int he
}
}
-//
-// drawLaserBeam -- Laser beam using pre-initialized texture (FUN_0040BBF6).
-//
// Two-layer rendering:
// Layer 1: Textured scanlines (beam body) via drawTexturedSegment()
// Layer 2: Edge highlights (glow) via drawEdgeHighlightLine(), gated by _rebelDetailMode >= 0
-//
-// Parameters (matching FUN_0040bbf6):
// dst, pitch, width, height: destination buffer info
-// gunX, gunY (param_3, param_4): gun/start position
-// targetX, targetY (param_5, param_6): target/end position
-// animFrame (param_7): current animation frame (shot counter)
-// maxFrames (param_8): max animation frames (shot duration)
-// widthScale (param_9): width scaling factor for perspective
-// heightScale (param_10): height/thickness multiplier
-// thickness (param_11): base line thickness
void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
int16 gunX, int16 gunY, int16 targetX, int16 targetY,
int16 animFrame, int16 maxFrames,
@@ -1482,18 +1401,15 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
targetY = (int16)((targetY - _hiResPresentationViewY) * 2);
}
- // FUN_0040BBF6 line 23: sVar7 = (thickness * animFrame * 16) / maxFrames
if (maxFrames == 0)
maxFrames = 1;
int16 sVar7 = (int16)(((int)thickness * (int)animFrame * 16) / (int)maxFrames);
- // FUN_0040BBF6 lines 24-25: Calculate delta with scaling
int16 dx = targetX - gunX;
int16 dy = targetY - gunY;
int16 sVar6 = (int16)(((int)dx * (thickness + 1)) / (int)thickness);
int16 sVar1 = (int16)(((int)dy * (thickness + 1)) / (int)thickness);
- // FUN_0040BBF6 lines 26-29: Calculate adjusted start and end points
// 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));
@@ -1501,10 +1417,8 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
int16 endX = (sVar6 + gunX) - (int16)(((int)sVar6 * 16) / (widthScale + sVar7 + 16));
int16 endY = (sVar1 + gunY) - (int16)(((int)sVar1 * 16) / (widthScale + sVar7 + 16));
- // FUN_0040BBF6 line 30: Get texture pixel pointer
byte *local_28 = texPixels;
- // Original callers pass a clip rect for the gameplay viewport (excluding status bar).
// 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);
@@ -1521,21 +1435,17 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
gunX, gunY, targetX, targetY, startX, startY, endX, endY,
animFrame, maxFrames, widthScale, heightScale, thickness);
- // FUN_0040BBF6 lines 31-32: Calculate abs differences (FUN_004356e4 = abs)
int iVar2 = abs(startY - endY); // |dy| of beam
int iVar3 = abs(startX - endX); // |dx| of beam
- // FUN_0040BBF6 line 33: Choose rendering path based on beam orientation
if (iVar2 < iVar3) {
// Mostly horizontal beam - draw vertical scanlines
- // FUN_0040BBF6 lines 34-37
iVar2 = abs(startX - endX);
int temp = iVar2 * texH * heightScale;
int16 numLines = (int16)((temp >> 3) / texW) + 2;
int16 local_24 = -numLines;
int16 halfLines = numLines >> 1;
- // FUN_0040BBF6 lines 39-46: Draw parallel textured scanlines (beam body)
for (int16 lineIdx = 0; lineIdx < numLines; lineIdx++) {
drawTexturedSegment(dst, pitch, width, height,
startX, (startY - halfLines) + lineIdx,
@@ -1549,7 +1459,6 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
}
}
- // FUN_0040BBF6 lines 47-51: Edge highlights along top and bottom beam edges
if (_rebelDetailMode >= 0 && edgeClipLeft <= edgeClipRight && edgeClipTop <= edgeClipBottom) {
drawEdgeHighlightLine(dst, pitch, width, height,
startX, startY - halfLines,
@@ -1562,19 +1471,16 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
}
} else {
// Mostly vertical beam - draw horizontal scanlines
- // FUN_0040BBF6 lines 54-56
iVar2 = abs(startY - endY);
int16 numLines = (int16)((iVar2 * texH) / texW) + 2;
int16 local_24 = -numLines;
- // FUN_0040BBF6 lines 58-60: Clamp to texture height
if (texH < numLines) {
numLines = texH;
}
int16 halfLines = numLines >> 1;
- // FUN_0040BBF6 lines 61-68: Draw parallel textured scanlines (beam body)
for (int16 lineIdx = 0; lineIdx < numLines; lineIdx++) {
drawTexturedSegment(dst, pitch, width, height,
(startX - halfLines) + lineIdx, startY,
@@ -1588,7 +1494,6 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
}
}
- // FUN_0040BBF6 lines 69-73: Edge highlights along left and right beam edges
if (_rebelDetailMode >= 0 && edgeClipLeft <= edgeClipRight && edgeClipTop <= edgeClipBottom) {
drawEdgeHighlightLine(dst, pitch, width, height,
startX - halfLines, startY,
@@ -1602,29 +1507,15 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
}
}
-// ---------------------------------------------------------------------------
// Collision Zone System
-// ---------------------------------------------------------------------------
// Level 3 pilot ship obstacle avoidance. Zones are quadrilaterals
-// registered via IACT opcode 5 (FUN_40E35E, FUN_40C3CC).
-//
-// registerCollisionZone -- Register a quad zone from IACT opcode 5 (FUN_4092D9 / FUN_4033CF).
-//
void InsaneRebel2::registerCollisionZone(Common::SeekableReadStream &b, int16 subOpcode, int16 par4) {
- // IACT Opcode 5 data layout â corrected from FUN_4033CF / FUN_4092D9 analysis:
- //
- // Original game stores pointer to full IACT data (starting at opcode).
// SmushPlayer reads the first 8 bytes as header (code/flags/unknown/userId),
- // so our stream starts at body[0] (IACT byte offset +8).
- //
- // FUN_4092D9 field mapping (byte offsets from stored pointer):
// +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
- // +0x08: body[0] (sVar1) â control field 1 (frame check: field2-1 == field1)
- // +0x0A: body[1] (sVar2) â control field 2
// +0x0C: body[2] â vertex 1 X
// +0x0E: body[3] â vertex 1 Y
// +0x10: body[4] â vertex 2 X
@@ -1660,7 +1551,6 @@ void InsaneRebel2::registerCollisionZone(Common::SeekableReadStream &b, int16 su
zone.subOpcode = subOpcode;
zone.active = true;
- // Register zone into appropriate table based on sub-opcode
if (subOpcode == 0x0D && _primaryZoneCount < kMaxCollisionZones) {
_primaryZones[_primaryZoneCount++] = zone;
debugC(DEBUG_INSANE, "Registered PRIMARY zone %d: filter=%d fields=[%d,%d] quad=(%d,%d)-(%d,%d)-(%d,%d)-(%d,%d)",
@@ -1677,44 +1567,28 @@ void InsaneRebel2::registerCollisionZone(Common::SeekableReadStream &b, int16 su
}
}
-// resetCollisionZones -- Clear zone tables at end of frame (FUN_403240).
void InsaneRebel2::resetCollisionZones() {
- // Reset zone counters at end of frame (FUN_403240 equivalent)
// This clears the zone tables so they can be rebuilt from the next frame's IACT chunks
_primaryZoneCount = 0;
_secondaryZoneCount = 0;
}
-//
-// checkCollisionZones -- Per-frame collision test against primary zones (FUN_4092D9).
-//
void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
- // Per-frame collision checking â FUN_4092D9.
// Tests aim/ship position against primary collision zone quadrilaterals.
- //
- // Original coordinate system:
// Zone vertices are in 424x260 buffer space, centered by subtracting (0xD4=212, 0x82=130).
- // Aim position (DAT_00443668/DAT_0044366a) is in centered coords [-52..52, -45..45].
- // In FUN_407FCB: DAT_00443668 is a smoothed mouse-derived position.
- //
// For our implementation:
- // Map mouse position to centered coords matching the original range.
- // Mouse X 0..320 â centered X â [-52..52] (with smoothing in original)
// Mouse Y 0..200 â centered Y â [-45..45]
if (_primaryZoneCount == 0)
return;
// Calculate aim position in centered coordinates.
- // Handler 0x26 applies the mouse-mode vertical inversion before DAT_0047a7fe
- // (FUN_407FCB lines 108-123), so this must not use getGameplayAimPoint().
const Common::Point aimPos = getGameplayAimPoint();
const int rawX = aimPos.x - 160;
const int rawY = aimPos.y - 100;
int16 aimX = (int16)(rawX * 52 / 160);
int16 aimY = (int16)((_optControlsFlipped ? -rawY : rawY) * 45 / 100);
- // Clamp to original ranges (DAT_0047a7fc < 1 path)
if (aimX > 0x34)
aimX = 0x34;
if (aimX < -0x34)
@@ -1736,12 +1610,10 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
continue;
// Filter: only process zones with filterValue < 1000 (par4 from IACT header)
- // Original: *(short *)(*local_c + 6) < 1000
if (zone.filterValue >= 1000)
continue;
// Center zone vertices by subtracting buffer center (0xD4=212, 0x82=130)
- // Original: sVar4 = x1 - 0xD4, sVar8 = y1 - 0x82, etc.
int cx1 = zone.x1 - 0xD4;
int cy1 = zone.y1 - 0x82;
int cx2 = zone.x2 - 0xD4;
@@ -1752,12 +1624,8 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
int cy4 = zone.y4 - 0x82;
// Frame check: field2 - 1 == field1
- // Original: sVar2 + -1 == (int)sVar1
if (zone.field2 - 1 == zone.field1) {
- // Point-in-quadrilateral test â FUN_4092D9 lines 119-128
// Tests if aim position is OUTSIDE the safe corridor (= collision with obstacle).
- // Original uses 4 edge interpolation tests connected by OR (any failure = collision).
- //
// 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
@@ -1792,26 +1660,21 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
if (collision) {
// Collision detected â apply damage from collision damage table
- // Original: DAT_0047a7ec += DAT_0047e0f6[chapter * 0x242 + level * 0x22]
int collisionDamage = (dparams.dodgeDamage >= 0) ? dparams.dodgeDamage : 0;
if (applyPlayerDamage(collisionDamage)) {
debugC(DEBUG_INSANE, "COLLISION damage! zone=%d aim=(%d,%d) damage=%d total=%d",
i, aimX, aimY, collisionDamage, _playerDamage);
}
- // Visual effect â FUN_00420515 (palette flash)
if (!_noDamage)
initDamageFlash();
- // TODO: FUN_0041189e sound based on collision direction
} else {
// Safely passed â award score bonus
- // Original: FUN_0041bf8d(DAT_0047e100[levelIdx])
if (dparams.dodgePoints > 0) {
addScore(dparams.dodgePoints);
}
}
} else if (warningFrame && zone.field2 - 0x0c < zone.field1) {
- // FUN_4092D9 near-collision indicators. Easy mode (flags bit 0x08)
// 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();
@@ -1842,22 +1705,15 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
}
}
-//
-// checkHandler7CollisionZones -- Handler 7 per-frame collision (FUN_40E35E).
-//
// 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;
-// they are not separate original functions.
-//
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;
- // Point-in-quad test (lines 75-89).
// Start assuming inside, clear if outside any edge (with margin).
bool inside = true;
@@ -1900,7 +1756,6 @@ void InsaneRebel2::applyHandler7ObstacleHit(const InsaneRebel2::CollisionZone &z
_rebelHitCounter++;
if (!_noDamage)
initDamageFlash();
- // Pan based on ship X position relative to screen center.
playSfx(1, 127, CLIP((_flyShipScreenX - 212) * 127 / 160, -127, 127));
debugC(DEBUG_INSANE, "Handler7 Mode0/2 OBSTACLE HIT zone=%d ship=(%d,%d) damage=%d",
zoneIndex, _flyShipScreenX, _flyShipScreenY, collisionDamage);
@@ -1908,7 +1763,6 @@ void InsaneRebel2::applyHandler7ObstacleHit(const InsaneRebel2::CollisionZone &z
void InsaneRebel2::awardHandler7DodgeScore() {
// Safely avoided obstacle - award score.
- // Original: FUN_0041bf8d(DAT_0047e100[levelIdx]).
LevelDifficultyParams scoreParams = getDifficultyParams();
if (scoreParams.dodgePoints > 0) {
addScore(scoreParams.dodgePoints);
@@ -1916,10 +1770,8 @@ void InsaneRebel2::awardHandler7DodgeScore() {
}
void InsaneRebel2::checkHandler7ObstacleZones(uint16 &warningMask) {
- // ---- Mode 0/2: Obstacle collision using SECONDARY zones (FUN_403b5b) ----
- // Original lines 52-132: Point-in-quad test with 15px inward margin.
// Inside the quad = collision with obstacle.
- const int margin = 15; // local_14 = 0x0f, local_20 = 0x0f
+ const int margin = 15;
for (int i = 0; i < _secondaryZoneCount; i++) {
CollisionZone &zone = _secondaryZones[i];
@@ -1928,25 +1780,21 @@ void InsaneRebel2::checkHandler7ObstacleZones(uint16 &warningMask) {
bool inside = isHandler7ShipInsideObstacleZone(zone, margin);
- // Frame match: field2 - 1 == field1 (line 90).
if (zone.field2 - 1 == zone.field1) {
if (inside) {
applyHandler7ObstacleHit(zone, i);
- break; // Only one collision per frame (original breaks).
+ break;
} else {
awardHandler7DodgeScore();
}
}
- // FUN_40E35E line 104: mark near-danger proximity for shadow cue rendering.
- // Uses the low byte of zone.filterValue (original local_1c) to pick direction bits.
if (zone.field2 - 13 < zone.field1) {
uint32 bit = 4u << ((byte)zone.filterValue & 0x1f);
warningMask = (uint16)(warningMask | (uint16)bit);
}
}
- // Corridor side proximity (FUN_40E35E lines 127-131).
if (_flyShipScreenX < _corridorLeftX + 0x28)
warningMask |= 1;
if (_corridorRightX - 0x28 < _flyShipScreenX)
@@ -1974,7 +1822,6 @@ void InsaneRebel2::checkHandler7TopBoundary(const InsaneRebel2::CollisionZone &z
int x1 = zone.x1, y1 = zone.y1;
int x2 = zone.x2, y2 = zone.y2;
- // Top edge: interpolate Y along v1->v2 at shipX (lines 152-166).
if (x2 != x1) {
int16 edgeY = (int16)((_flyShipScreenX - x1) * (y2 - y1) / (x2 - x1) + y1 + vMargin);
if (_flyShipScreenY < edgeY) {
@@ -1999,10 +1846,9 @@ void InsaneRebel2::checkHandler7BottomBoundary(const InsaneRebel2::CollisionZone
int x3 = zone.x3, y3 = zone.y3;
int x4 = zone.x4, y4 = zone.y4;
- // Bottom edge: interpolate Y along v4->v3 at shipX (lines 167-183).
if (x3 != x4) {
int16 edgeY = (int16)((_flyShipScreenX - x4) * (y3 - y4) / (x3 - x4) + y4 - vMargin);
- _corridorBottomY = vMargin + edgeY; // DAT_00443b10 update
+ _corridorBottomY = vMargin + edgeY;
if (edgeY < _flyShipScreenY) {
// Ship below bottom wall - push up.
const bool damageApplied = applyHandler7WallDamage(wallDamage);
@@ -2025,14 +1871,12 @@ void InsaneRebel2::checkHandler7LeftBoundary(const InsaneRebel2::CollisionZone &
int x1 = zone.x1, y1 = zone.y1;
int x4 = zone.x4, y4 = zone.y4;
- // Left edge: interpolate X along v1->v4 at shipY (lines 184-199).
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
- // FUN_40E35E resets horizontal history to force immediate rightward correction.
resetHandler7HorizontalVelocity(127);
const bool damageApplied = applyHandler7WallDamage(wallDamage);
@@ -2052,14 +1896,12 @@ void InsaneRebel2::checkHandler7RightBoundary(const InsaneRebel2::CollisionZone
int x2 = zone.x2, y2 = zone.y2;
int x3 = zone.x3, y3 = zone.y3;
- // Right edge: interpolate X along v2->v3 at shipY (lines 200-215).
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
- // FUN_40E35E resets horizontal history to force immediate leftward correction.
resetHandler7HorizontalVelocity(-127);
const bool damageApplied = applyHandler7WallDamage(wallDamage);
@@ -2076,11 +1918,9 @@ void InsaneRebel2::checkHandler7RightBoundary(const InsaneRebel2::CollisionZone
}
void InsaneRebel2::checkHandler7BoundaryZones(uint16 &warningMask) {
- // ---- Mode 1/3: Wall/boundary collision using PRIMARY zones (FUN_403b34) ----
- // Original lines 133-235: Per-edge interpolation with push-back.
// Ship position is clamped to wall boundaries when hitting.
- int16 hMargin = (_flyControlMode == 1) ? 0x28 : 0x0f; // local_14
- const int16 vMargin = 0x0f; // local_20
+ int16 hMargin = (_flyControlMode == 1) ? 0x28 : 0x0f;
+ const int16 vMargin = 0x0f;
LevelDifficultyParams wallParams = getDifficultyParams();
int wallDamage = (wallParams.dodgeDamage >= 0) ? wallParams.dodgeDamage : 0;
@@ -2097,7 +1937,6 @@ void InsaneRebel2::checkHandler7BoundaryZones(uint16 &warningMask) {
}
void InsaneRebel2::renderHandler7WarningCues(byte *renderBitmap, int pitch, int width, int height, int32 curFrame, uint16 warningMask) {
- // FUN_40E35E tail: draw proximity danger shadow cues when enabled by frame/flags.
// Note: These are cue sprites (often perceived as "shadows"), not the aiming reticle.
LevelDifficultyParams dparams = getDifficultyParams();
if ((curFrame & 2) != 0 && (dparams.flags & 8) != 0 && _smush_iconsNut) {
@@ -2123,16 +1962,11 @@ void InsaneRebel2::renderHandler7WarningCues(byte *renderBitmap, int pitch, int
}
void InsaneRebel2::checkHandler7CollisionZones(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
- // FUN_40E35E - Handler 7 per-frame collision system.
// 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)
- // Note: _hitCooldown is decremented in renderSpaceExplosions (FUN_40F1C5)
- // to match the original where the decrement happens during rendering.
- //
- // local_c in FUN_40E35E: proximity mask for nearby danger-zone shadow cues.
// bit 0=left, bit 1=right, bit 2=top, bit 3=bottom
uint16 warningMask = 0;
@@ -2272,7 +2106,6 @@ static void renderNutSpriteScaledClipped(byte *dst, int pitch, int width, int he
}
}
-// renderNutSpriteMirrored -- NUT sprite with optional horizontal flip (FUN_004236e0).
void InsaneRebel2::renderNutSpriteMirrored(byte *dst, int pitch, int width, int height, int x, int y, NutRenderer *nut, int spriteIdx, bool mirror) {
if (!nut || spriteIdx < 0 || spriteIdx >= nut->getNumChars())
return;
@@ -2340,9 +2173,6 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
}
if (_rebelHandler == 8) {
- // Handler 8 follows FUN_00401234/FUN_00401CCF: the camera is applied
- // through FUN_00424510 before FOBJ decoding, not by scrolling the final
- // buffer copied by FUN_00424540.
_viewX = 0;
_viewY = 0;
_player->setScrollOffset(0, 0);
@@ -2375,9 +2205,7 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
maxScrollY = 0;
if (_rebelHandler == 7) {
- // High-detail Handler 7 follows FUN_0041C5C0: the oversized 424x260
// flight buffer is presented through a perspective-derived 320x170
- // gameplay window at source offset (0x34,0x2d) + DAT_00443712/14.
// 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;
@@ -2397,8 +2225,6 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
// updatePostRenderDeath -- End gameplay playback when player damage reaches 255.
void InsaneRebel2::updatePostRenderDeath() {
- // Original game (FUN_417E53 line 25) exits video playback when
- // DAT_0047a7ec >= 0xff (damage accumulator reaches 255).
// Sync _playerShield from _playerDamage and break out of video on death.
if (_rebelHandler != 0) {
_playerShield = 255 - _playerDamage;
@@ -2411,10 +2237,6 @@ void InsaneRebel2::updatePostRenderDeath() {
// renderPostRenderMenuCursor -- Draw RA2's software cursor for menu videos.
void InsaneRebel2::renderPostRenderMenuCursor(byte *renderBitmap, int pitch, int width, int height) {
- // Original menu stages call FUN_0042a6d0() to show the game's software
- // cursor object. Slot 0 is initialized by FUN_0042a660() from the cursor
- // table embedded in RA2WIN95.EXE at VA 0x482f30 (7x10, hotspot 0,0).
- // FUN_0042a660() passes 1 as the transparent color to FUN_00430380().
static const byte kRa2MenuCursor[] = {
0, 0, 1, 1, 1, 1, 1,
0, 15, 0, 1, 1, 1, 1,
@@ -2458,10 +2280,8 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
bool pilotSelectMode = (introPlaying && (_gameState == kStatePilotSelect || _gameState == kStateDifficultySelect));
bool chapterSelectMode = (introPlaying && _gameState == kStateChapterSelect);
- // Handle pilot selection input and rendering (FUN_00414A41).
// This is the pilot/save slot selection screen with centered menu.
if (pilotSelectMode) {
- // Process pilot selection input - emulates FUN_00414A41 input handling.
int selection = processLevelSelectInput();
// Draw pilot selection overlay - centered menu like main menu.
@@ -2479,17 +2299,14 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
return true;
}
- // Handle chapter selection input and rendering (FUN_00415CF8).
// 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.
- // Process chapter selection input - emulates FUN_00415CF8 input handling.
int selection = processChapterSelectInput();
- // Draw chapter selection overlay - emulates FUN_00415CF8 rendering.
drawChapterSelectOverlay(renderBitmap, pitch, width, height);
renderPostRenderMenuCursor(renderBitmap, pitch, width, height);
@@ -2504,13 +2321,11 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
return true;
}
- // Handle Top Pilots screen (FUN_00420116).
if (introPlaying && _gameState == kStateTopPilots) {
drawTopPilotsOverlay(renderBitmap, pitch, width, height);
return true;
}
- // Handle Options menu (FUN_004167A6).
if (introPlaying && _gameState == kStateOptions) {
processOptionsInput();
drawOptionsOverlay(renderBitmap, pitch, width, height);
@@ -2527,9 +2342,7 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
_menuInactivityTimer++;
// Check for inactivity timeout.
- // From FUN_004147b2: 300 frames of inactivity returns 0 (exit to intro/attract mode).
// At 12fps video rate, 300 frames = ~25 seconds of inactivity.
- // The original checks: if (local_8 > 299) return 0.
if (_menuInactivityTimer > 300) {
debugC(DEBUG_INSANE, "Menu inactivity timeout - resuming intro/demo loop");
_menuInactivityTimer = 0;
@@ -2559,13 +2372,8 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
// 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:
- // - Hide the mouse cursor (original: ShowCursor(0) at startup in FUN_00420c70)
// - Skip all HUD/status bar/crosshair rendering
// - Skip mouse input processing (no shooting during intros)
- //
- // Original behavior from FUN_00403240:
- // - if (DAT_0047a814 == 0) { switch(DAT_0047ee84) { ... } }
- // - DAT_0047ee84 (handler) is only set by IACT opcode 6 during gameplay videos
// - 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) {
@@ -2579,7 +2387,6 @@ bool InsaneRebel2::handlePostRenderIntro(byte *renderBitmap, int pitch, int widt
_player->_curVideoFlags, _gameState);
}
- // Chapter title text overlay (FUN_004171c5).
if (_textOverlayActive)
renderTextOverlay(renderBitmap, pitch, width, height, curFrame);
@@ -2599,11 +2406,6 @@ bool InsaneRebel2::handlePostRenderIntro(byte *renderBitmap, int pitch, int widt
// updateGameplayDamageEffects -- Apply handler-specific damage visuals.
void InsaneRebel2::updateGameplayDamageEffects(byte *renderBitmap, int pitch, int width, int height) {
- // Damage visual effects - handler-specific per original architecture:
- // Handler 8: FUN_401CCF line 119 -> FUN_00420754 (palette flash + screen shake)
- // Handler 0x19: FUN_41DB5E line 192 -> FUN_00420562 (palette flash only, every frame)
- // Handler 0x26: FUN_4092D9 lines 135/225/237 -> FUN_00420515 trigger + palette flash
- // Handler 7: FUN_40E35E -> FUN_00420515 trigger + palette flash
if (_rebelHandler == 8) {
// Full damage effect: palette flash + screen shake.
// Suppressed during autopilot (mode 4) and cutscene (mode 5).
@@ -2618,9 +2420,6 @@ void InsaneRebel2::updateGameplayDamageEffects(byte *renderBitmap, int pitch, in
// updateGameplayDamageRecovery -- Apply RA2's damage auto-reduction.
void InsaneRebel2::updateGameplayDamageRecovery(int32 curFrame) {
- // Handler 0x26 (FUN_4089AB), Handler 8 (FUN_401CCF), and Handler 7
- // (FUN_40D836) decrement DAT_0047a7ec once every 16 frames after
- // gameplay collision processing. Handler 25's FUN_41DB5E only awards the
// timed score tick in the same slot and does not reduce damage.
if ((_rebelHandler != 0x26 && _rebelHandler != 8 && _rebelHandler != 7) ||
(curFrame & 0xf) != 0 || _playerDamage <= 0) {
@@ -2634,11 +2433,7 @@ void InsaneRebel2::updateGameplayDamageRecovery(int32 curFrame) {
// 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.
- //
- // Handler 0x26 (turret): FUN_4092D9 - aim position vs primary zones (centered coords)
// Zones with filterValue < 1000 tested via point-in-quad against mouse/aim position.
- //
- // Handler 7 (ship): FUN_40E35E - ship position vs zones per control mode:
// 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.
@@ -2649,12 +2444,10 @@ void InsaneRebel2::checkGameplayPostRenderCollisions(byte *renderBitmap, int pit
}
}
-// renderGameplayPostFrame -- Draw the gameplay post-render pipeline in original order.
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.
- // Original: FUN_00403240 only runs handlers when DAT_0047a814 == 0.
processMouse();
// Handler 7's high-detail flight view uses a 320x170 gameplay area after
@@ -2685,34 +2478,15 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
// SMUSH decodes the frame's FOBJ sprites. Handler 25 draws its corridor overlay
// from IACT opcode 6 instead. Redrawing either here would overwrite enemies.
- // --- HUD Drawing Order (from FUN_004089ab / FUN_40D836 assembly analysis) ---
- // Original assembly render order for handler 0x26:
- // 1. FUN_004288c0: Fill status bar background
- // 2. FUN_004092d9: Collision/hit processing
- // 3. FUN_00409fbc: Explosion rendering
- // 4. FUN_0040ad63: LASER SHOTS (drawn BEFORE cockpit overlays)
- // 5. FUN_004236e0: Crosshair + cockpit NUT overlays (drawn ON TOP of lasers)
- // 6. FUN_0041c012: Status bar text/numbers
// The cockpit frame covers laser beam edges, giving the appearance
// that beams emerge from behind the cockpit.
- // Based on FUN_004089ab:
- // 1. Line 156: FUN_004288c0 fills status bar background at Y=0xb4 (180)
// 2. Lines 171-226: Draw turret overlays, targeting reticle, crosshair
- // 3. Line 243: FUN_0041c012 draws status bar sprites LAST (on top)
- //
- // In FUN_0041c012:
- // - Sprites are drawn to buffer DAT_00482204 at position (0,0)
- // - Buffer is composited at Y=0xb4 (180) via FUN_0042f780
- // - DISPFONT.NUT (DAT_00482200) sprites 1-7 contain the status bar elements
- //
// We draw directly to screen at Y=180.
- // STEP 0: Fill status bar background (FUN_004288c0).
renderStatusBarBackground(renderBitmap, pitch, width, height, videoWidth, videoHeight, gameplayStatusBarY);
// Ship rendering. Handler 7 is drawn later, after its lasers, matching
- // FUN_0040d836's order so the ship covers the muzzle end of the beams.
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,
@@ -2725,7 +2499,6 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
// Enemy target indicators (handler-specific; sprite-based in turret mode).
renderEnemyOverlays(renderBitmap, pitch, width, height, videoWidth);
- // Explosion animations (FUN_409FBC) - drawn before lasers in original.
renderExplosions(renderBitmap, pitch, width, height);
// Laser shot beams - drawn BEFORE cockpit/HUD overlays so cockpit covers beam edges.
@@ -2733,57 +2506,42 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
renderHandler7Ship(renderBitmap, pitch, width, height);
- // Handler 25 GRD sprites drawn AFTER enemies/explosions/lasers per original FUN_0041DB5E:
- // Line 193: FUN_0041f29a (enemies)
- // Line 194: FUN_0041e7c2 (explosions)
- // Line 201: FUN_0041f004 (lasers)
// 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);
- // STEP 1A: Draw NUT-based HUD overlays for Handler 0x26 (FUN_004089ab lines 195-226).
// 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);
- // STEP 2: Draw DISPFONT.NUT status bar sprites (FUN_0041c012).
renderStatusBarSprites(renderBitmap, pitch, width, height, gameplayStatusBarY, curFrame);
updateGameplayDamageEffects(renderBitmap, pitch, width, height);
checkGameplayPostRenderCollisions(renderBitmap, pitch, width, height, curFrame);
updateGameplayDamageRecovery(curFrame);
- // Crosshair/reticle (FUN_004089ab, FUN_0040d836).
renderCrosshair(renderBitmap, pitch, width, height);
- // Handler 8 monitor scanline effect (FUN_0041C6EC/FUN_0041C6C3).
renderHandler8MonitorEffect(renderBitmap, pitch, width, height);
- // Handler 8 POV text overlay (FUN_00401CCF).
renderHandler8PovOverlay(renderBitmap, pitch, width, height);
- // HUD score/lives rendering (FUN_0041c012).
renderScoreHUD(renderBitmap, pitch, width, height, gameplayStatusBarY);
- // Reset FOBJ position offsets (FUN_00424510(0,0) in original FUN_0041DB5E line 271).
if (_player) {
_player->_fobjOffsetX = 0;
_player->_fobjOffsetY = 0;
}
- // Frame end cleanup: reset enemy active flags and collision zones (FUN_403240).
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) {
@@ -2843,13 +2601,9 @@ 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.
-// FUN_420515, FUN_420562, FUN_420754, FUN_42073B, FUN_420501.
-// resetDamageFlash -- Reset palette flash counter (FUN_00420501).
void InsaneRebel2::resetDamageFlash() {
_damageFlashCounter = 0;
}
@@ -2868,7 +2622,6 @@ void InsaneRebel2::restoreDamageFlashPalette() {
_damageRestorePaletteValid = false;
}
-// initDamageFlash -- Save palette and initiate 5-frame flash (FUN_00420515).
void InsaneRebel2::initDamageFlash() {
if (!_damageRestorePaletteValid && _player) {
memcpy(_damageRestorePalette, _player->_pal, 0x300);
@@ -2882,22 +2635,15 @@ void InsaneRebel2::initDamageFlash() {
_damageFlashCounter = 5;
}
-// triggerDamageEffect -- Trigger palette flash and screen shake (FUN_0042073B).
void InsaneRebel2::triggerDamageEffect() {
initDamageFlash();
_damageShakeCounter = 10;
}
-//
-// updateDamageFlashPalette -- Per-frame palette modification (FUN_00420562).
-//
// Normal hit flash (_damageHighFlashCounter == 0 or odd):
// Blend formula: output[i] = 0xFF - ((0xFF - saved[i]) * (0x10 - counter)) >> 4
-// Counter 5->4(apply)->3(skip)->2(apply)->1(skip)->0(apply=original) = strobe.
-//
// 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) {
@@ -2918,7 +2664,6 @@ void InsaneRebel2::updateDamageFlashPalette() {
if (_damageHighFlashCounter == 0 || (_damageHighFlashCounter & 1) != 0) {
// Normal hit flash path: decrement counter, apply on even values.
- // Original C: if ((counter != 0) && (counter--, (counter & 1) == 0))
if (_damageFlashCounter != 0) {
_damageFlashCounter--;
if ((_damageFlashCounter & 1) == 0) {
@@ -2946,15 +2691,12 @@ void InsaneRebel2::updateDamageFlashPalette() {
}
}
-// updateDamageEffect -- Per-frame screen shake + palette flash (FUN_00420754).
// Shifts counter*5 random scanlines per frame, diminishing over 10 frames.
-// Only called when not in cutscene modes (DAT_0043e000 != 4 && != 5).
void InsaneRebel2::updateDamageEffect(byte *renderBitmap, int pitch, int width, int height) {
if (_damageShakeCounter != 0) {
_damageShakeCounter--;
int numLines = _damageShakeCounter * 5;
- // Temporary buffer for scanline rotation (case 1 in original)
byte tempLine[640];
const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
const int maxY = MIN(height, renderHiRes ? 360 : 180);
@@ -2997,14 +2739,10 @@ void InsaneRebel2::updateDamageEffect(byte *renderBitmap, int pitch, int width,
updateDamageFlashPalette();
}
-// ---------------------------------------------------------------------------
// Rendering Helper Functions
-// ---------------------------------------------------------------------------
// Extracted from procPostRendering for better readability.
-// renderTextOverlay -- Progressive chapter title overlay (FUN_004171c5).
void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, int height, int curFrame) {
- // Emulates FUN_004171c5 text overlay: progressive chapter title during [fadeIn, fadeOut)
if (curFrame < _textOverlayFadeIn || curFrame >= _textOverlayFadeOut)
return;
@@ -3031,7 +2769,6 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
Common::Rect clipRect(0, 0, width, height);
- // Split into lines, then render each centered at textX (FUN_004341a0).
// Older RA2 text loading joined multi-line strings with spaces, leaving
// " ^f" as the separator; current TRES loading preserves real newlines.
Common::Array<Common::String> lines;
@@ -3122,11 +2859,8 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
}
}
-// renderStatusBarBackground -- Fill status bar area with color 4 (FUN_004288c0).
void InsaneRebel2::renderStatusBarBackground(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY) {
- // Fill status bar background (FUN_004288c0 equivalent)
- // Original assembly: FUN_004288c0(local_8, 0, 0, 0xb4, 0x140, 0x14, 4)
// This fills width=320, height=20 starting at Y=180 with color index 4
const byte statusBarBgColor = 4;
@@ -3143,11 +2877,8 @@ void InsaneRebel2::renderStatusBarBackground(byte *renderBitmap, int pitch, int
}
}
-// renderTurretHudOverlays -- NUT-based HUD for Handler 0x26 (FUN_004089ab).
void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
// Draw NUT-based HUD overlays for Handler 0x26 (turret modes)
- // From FUN_004089ab disassembly (lines 195-226):
- // - DAT_0047fe78 (_hudOverlayNut): Primary HUD overlay with 6 animation frames
// - Position formula (low-res):
// X = 160 + (mouseOffsetX >> 4) - (width / 2) - spriteOffsetX
// Y = 182 - (mouseOffsetY >> 4) - height - spriteOffsetY
@@ -3179,8 +2910,6 @@ void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int wi
const int hudScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
- // FUN_004089ab computes the moving overlay anchor from sprite 0's dimensions
- // and offsets, then FUN_004236e0 applies each rendered frame's own offsets.
const int baseSpriteW = _hudOverlayNut->getCharWidth(0);
const int baseSpriteH = _hudOverlayNut->getCharHeight(0);
const int baseSpriteXOff = _hudOverlayNut->getCharXOffset(0);
@@ -3210,7 +2939,6 @@ void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int wi
debugC(DEBUG_INSANE, "HUD: Drawing NUT overlay frame %d/%d at (%d,%d) mouseOffset=(%d,%d)",
animFrame, numSprites, hudX, hudY, mouseOffsetX, mouseOffsetY);
- // Draw secondary HUD overlay if present (DAT_0047fe80)
if (_hudOverlay2Nut && _hudOverlay2Nut->getNumChars() > 0) {
int spr2W = _hudOverlay2Nut->getCharWidth(0);
int spr2H = _hudOverlay2Nut->getCharHeight(0);
@@ -3232,7 +2960,6 @@ void InsaneRebel2::renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int
continue;
// Handler 25: skip slot 4 (corridor overlay) in post-rendering.
- // FUN_0041cadb draws it opaquely from opcode 6 and immediately after
// loading par4=4; drawing it here would cover enemies.
if (_rebelHandler == 25 && hudSlot == 4) {
continue;
@@ -3312,10 +3039,8 @@ void InsaneRebel2::renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int
}
}
-// renderStatusBarSprites -- DISPFONT.NUT status bar rendering (FUN_0041c012).
void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int width, int height,
int statusBarY, int32 curFrame) {
- // FUN_0041c012 equivalent â renders DISPFONT.NUT status bar sprites.
// DISPFONT.NUT sprite layout:
// Sprite 1: Status bar background
// Sprites 2-5: Difficulty variants (full status bar with 1-4 stars)
@@ -3328,14 +3053,11 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
int numSprites = _smush_cockpitNut->getNumChars();
const int statusScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
- // --- Sprite 1: Status bar background (always drawn first as base layer) ---
if (numSprites > 1) {
renderNutSprite(renderBitmap, pitch, width, height,
_viewX, statusBarY + _viewY, _smush_cockpitNut, 1);
}
- // --- Difficulty sprite (2-5) overlaid on top ---
- // FUN_0041c012 lines 33-43: sprite index = min(difficulty, 4) + 1
int difficulty = _difficulty;
if (difficulty > 3)
difficulty = 3;
@@ -3345,13 +3067,10 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
_viewX, statusBarY + _viewY, _smush_cockpitNut, difficultySprite);
}
- // --- Damage/shield bar (sprite 6 within damage clip rect) ---
- // FUN_0041c012 lines 44-76:
// 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) {
- // Bar width from assembly: param_1 >> 2 (low-res)
int damageBarWidth = (_playerDamage * statusScale) >> 2;
const byte *src = _smush_cockpitNut->getCharData(6);
@@ -3384,7 +3103,6 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
}
}
- // Damage alert overlay (sprite 7) â FUN_0041c012 lines 68-76:
// 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)) {
@@ -3418,8 +3136,6 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
}
}
- // --- Lives bar (sprite 6 within lives clip rect) ---
- // FUN_0041c012 lines 99-131:
// 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) {
@@ -3476,13 +3192,9 @@ void InsaneRebel2::renderHandler7FlySprite(byte *renderBitmap, int pitch, int wi
}
}
-// renderHandler7Ship -- Handler 7 third-person ship rendering (FUN_0040d836).
void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width, int height) {
// Handler 7 Ship Rendering (Third-Person Ship - FLY sprites)
- // Based on FUN_0040d836 lines 173-185:
- // FUN_004236e0(buf, frameInfo, screenX - 0xd4, screenY - 0x82, 0, sprite, frameIdx, 1, 0)
// The ship sprite is drawn at the perspective-transformed position offset from center.
- // FUN_0041c720 transforms game coords (shipX, shipY) using perspective offsets.
if (_rebelHandler != 7 || !_flyShipSprite || _shipLevelMode == 5)
return;
@@ -3514,7 +3226,6 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
int shipCenterX = projected.x;
int shipCenterY = projected.y;
- // FUN_40D836 lines 108-136: FLY002 proximity cues near corridor danger.
if (_flyLaserSprite && _flyLaserSprite->getNumChars() > 0) {
const int laserChars = _flyLaserSprite->getNumChars();
_flyEffectAnimCounter++;
@@ -3583,7 +3294,6 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
renderHiRes, renderScale, nativeViewX, nativeViewY,
shipDraw.x, shipDraw.y, _flyShipSprite, spriteIndex);
- // FUN_40D836 lines 176-180: optional FLY002 overlay pass at ship position.
if (_flyLaserSprite && _flyOverlayRepeatCount > 0) {
int overlayIndex = spriteIndex + 0x14;
if (overlayIndex >= 0 && overlayIndex < _flyLaserSprite->getNumChars()) {
@@ -3595,7 +3305,6 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
}
}
- // FUN_40D836 lines 181-183: optional FLY003 overlay in high detail mode.
if (_flyTargetSprite && _rebelDetailMode >= 0 &&
spriteIndex >= 0 && spriteIndex < _flyTargetSprite->getNumChars()) {
renderHandler7FlySprite(renderBitmap, pitch, width, height,
@@ -3609,35 +3318,25 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
_perspectiveX, _perspectiveY, _smoothedVelocity, _verticalInput, _flyEffectAnimCounter, _flyOverlayRepeatCount);
}
-// renderHandler8Ship -- Handler 8 third-person on-foot rendering (FUN_00401CCF).
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
- //
- // Original FUN_00401CCF lines 87-94:
- // FUN_004236e0(bitmap, param_2,
// (short)(shipPosX - 0xa0) >> 3, // small X offset
// (short)(shipPosY - 0x28) >> 2, // small Y offset
- // 0, DAT_0047e010, param_5 & 1, 1, 0);
- //
- // FUN_004236e0 adds the NUT sprite's internal X/Y offsets to the position
// 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;
- // Small position offsets from dampened ship movement (FUN_00401ccf lines 88-89)
int16 displayOffsetX = (_shipPosX - 0xa0) >> 3;
int16 displayOffsetY = (_shipPosY - 0x28) >> 2;
int numSprites = _shipSprite->getNumChars();
- // Original uses param_5 & 1 (firing flag) as sprite index â NOT direction-based
int spriteIndex = _shipFiring ? 1 : 0;
if (spriteIndex >= numSprites)
spriteIndex = 0;
- // FUN_004236e0 adds sprite internal offsets to the x/y parameters.
// 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);
@@ -3656,8 +3355,6 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
renderNutSprite(renderBitmap, pitch, width, height, drawX, drawY, _shipSprite, spriteIndex);
}
- // Shadow sprite (POV004 / DAT_0047e028): drawn at same position as primary ship.
- // Original FUN_401CCF lines 91-92 uses param_5 & 1 (firing flag) as sprite index.
if (_shipSprite2) {
int shadowIndex = _shipFiring ? 1 : 0;
if (shadowIndex < _shipSprite2->getNumChars()) {
@@ -3684,7 +3381,6 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
_viewX, _viewY, spriteIndex, numSprites, renderScale);
}
-// renderVehicleShotImpacts -- Handler 8 shot impact sprites (FUN_402DA8).
void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int width, int height) {
if (_rebelHandler != 8)
return;
@@ -3703,8 +3399,6 @@ void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int w
drawY = (drawY - _hiResPresentationViewY) * 2;
}
- // Original draws DAT_0047e020 repeatedly based on remaining life, then
- // DAT_0047e018 once, both using the sampled background-mask sprite index.
if (_shipOverlay1 && impact.spriteIndex >= 0 && impact.spriteIndex < _shipOverlay1->getNumChars()) {
int spriteX = drawX + _shipOverlay1->getCharXOffset(impact.spriteIndex) -
_shipOverlay1->getCharWidth(impact.spriteIndex) / 2;
@@ -3726,7 +3420,6 @@ void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int w
}
// Handler 25: Draw GRD001 (wall/cockpit overlay) in procPostRendering.
-// renderHandler25ShipPre -- Handler 25 GRD001 pre-rendering (FUN_0041DB5E lines 202-221).
// 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)
@@ -3749,26 +3442,20 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
const int nativeBufferViewY = renderHiRes ? nativeViewY : _viewY;
int renderHeight = renderHiRes ? MIN(height, 180 * renderScale) : MIN(height, 180 + _viewY);
- // Draw _grd001Sprite based on _grdSpriteMode (DAT_00457900)
- // Each mode has specific conditions from FUN_0041db5e:
bool shouldDraw = false;
bool useHalfWidth = false;
bool useRightHalf = false;
- // Mode 1 (lines 202-210): Draw with width halving when uncovered
if (_grdSpriteMode == 1) {
shouldDraw = true;
useHalfWidth = (_rebelDamageLevel == 0); // Half width when uncovered
}
- // Mode 2 (lines 222-224): Only draw when damaged (covered)
else if (_grdSpriteMode == 2 && _rebelDamageLevel != 0) {
shouldDraw = true;
}
- // Mode 3 (lines 225-228): Always draw full width
else if (_grdSpriteMode == 3) {
shouldDraw = true;
}
- // Mode 4 (lines 211-221): Draw to right half when uncovered
else if (_grdSpriteMode == 4) {
shouldDraw = true;
useHalfWidth = (_rebelDamageLevel == 0);
@@ -3790,7 +3477,6 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
int drawX = renderHiRes ? (nativeDrawX - nativeViewX) * renderScale : nativeDrawX;
int drawY = renderHiRes ? (nativeDrawY - nativeViewY) * renderScale : nativeDrawY;
- // Apply half-width clipping from FUN_41DB5E:
// - mode1 uncovered: left half
// - mode4 uncovered: right half
int clipLeft = 0;
@@ -3803,8 +3489,6 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
int scaledClipLeft = clipLeft * renderScale;
int scaledClipRight = clipRight * renderScale;
- // FUN_41DB5E mode-4 uncovered mutates DAT_00482230/34 (clip region),
- // not DAT_00457910 (draw X). Keep drawX unchanged and clip only.
renderNutSpriteScaledClipped(renderBitmap, pitch, width, renderHeight,
scaledClipLeft, 0, scaledClipRight, renderHeight,
drawX, drawY, _grd001Sprite, 0, false, renderScale, false);
@@ -3836,12 +3520,9 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
}
}
-// renderHandler25Ship -- Handler 25 GRD002 post-rendering (FUN_0041db5e).
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().
- //
- // From FUN_0041db5e disassembly (lines 230-248):
// GRD002 is drawn LAST (after enemies) so the character appears in front.
if (_rebelHandler != 25)
@@ -3856,27 +3537,20 @@ 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);
- // _grd002Sprite (GRD002) is always drawn if it exists (from FUN_41DB5E line 230)
- // The sprite index is calculated based on damage level and aiming position
- // From FUN_0041db5e lines 160-168:
// 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) {
- // Calculate sprite index based on damage level and direction
int spriteIdx;
int numSprites = _grd002Sprite->getNumChars();
- // Determine if we should mirror the sprite (from FUN_41DB5E lines 231-235)
// 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
- // From FUN_41DB5E lines 155-164
- //
// 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
@@ -3914,8 +3588,6 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
if (yZone > 1)
yZone = 1;
- // Direction-based sprite flip logic (line 161-162 in decompiled)
- // if (DAT_00457902 == (uVar7 & 1)) { local_58 = 4 - local_58; }
if (_rebelFlightDir == (yZone & 1)) {
xZone = 4 - xZone;
}
@@ -3923,8 +3595,6 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
spriteIdx = yZone * 5 + xZone + 5;
} else {
// Transitioning/covered state: use direction-based sprite
- // From FUN_41DB5E lines 166-168:
- // sVar8 = ((-(ushort)(DAT_00457902 == 0) & 0xffec) + 0x19) - DAT_0045790a
// direction == 0: 5 - damage
// direction != 0: 25 - damage
if (_rebelFlightDir == 0) {
@@ -3945,19 +3615,11 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
int spriteW = _grd002Sprite->getCharWidth(spriteIdx);
int spriteH = _grd002Sprite->getCharHeight(spriteIdx);
- // Position calculation from FUN_41DB5E lines 237-247:
// GRD002 explicitly adds sprite internal offsets from NUT header:
- //
// Normal case (direction==0 OR damage!=0):
- // local_60 = sprite_internal_x_offset (from NUT header +0x12)
- // X = DAT_00457910 + local_60
- // Y = sprite_internal_y_offset (from NUT header +0x14) + DAT_00457912
- //
+
// Mirrored case (direction!=0 AND damage==0):
- // local_60 = 320 - sprite_width - sprite_internal_x_offset
- // X = DAT_00457910 + local_60
- // Y = sprite_internal_y_offset + DAT_00457912
- //
+
// Now using actual NUT sprite offsets from NutRenderer!
int16 spriteXOffset = _grd002Sprite->getCharXOffset(spriteIdx);
int16 spriteYOffset = _grd002Sprite->getCharYOffset(spriteIdx);
@@ -3965,17 +3627,11 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
int nativeDrawX, nativeDrawY;
if (shouldMirror) {
- // Mirrored position: X = DAT_00457910 + (320 - sprite_width - sprite_x_offset)
- // From assembly lines 240-243
nativeDrawX = _rebelViewOffset2X + (320 - spriteW - spriteXOffset) + nativeBufferViewX;
} else {
- // Normal position: X = DAT_00457910 + sprite_internal_x_offset
- // From assembly line 238
nativeDrawX = _rebelViewOffset2X + spriteXOffset + nativeBufferViewX;
}
- // Y = sprite_internal_y_offset + DAT_00457912
- // From assembly line 246
nativeDrawY = spriteYOffset + _rebelViewOffset2Y + nativeBufferViewY;
int drawX = renderHiRes ? (nativeDrawX - nativeViewX) * renderScale : nativeDrawX;
int drawY = renderHiRes ? (nativeDrawY - nativeViewY) * renderScale : nativeDrawY;
@@ -4043,13 +3699,10 @@ void InsaneRebel2::renderFallbackShip(byte *renderBitmap, int pitch, int width,
drawX, drawY, srcX, srcY, numHorizontal, numVertical, _shipDirectionH, _shipDirectionV);
}
-// renderEnemyOverlays -- Handler 0x26 target indicator sprites (FUN_40A2E0).
void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width, int height, int videoWidth) {
- // Original per-enemy target indicator behavior comes from FUN_40A2E0 (handler 0x26):
// - 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;
@@ -4069,7 +3722,7 @@ void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width,
const int nativeVideoWidth = renderHiRes ? videoWidth / indicatorScale : 320;
Common::Rect viewRect(nativeViewX, nativeViewY, nativeViewX + nativeVideoWidth, nativeViewY + 200);
- const int sizeClamp = dparams.specialDamage; // DAT_0047e0fa in FUN_40A2E0
+ const int sizeClamp = dparams.specialDamage;
for (Common::List<enemy>::iterator it = _enemies.begin(); it != _enemies.end(); ++it) {
if (it->destroyed || !it->active || isBitSet(it->id))
@@ -4086,7 +3739,6 @@ void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width,
if (halfW <= 0 || halfH <= 0)
continue;
- // Match size-bucket selection from FUN_40A2E0:
// class 0..4 -> sprite 6..10.
int indicatorHalfW = halfW;
if (sizeClamp > 0)
@@ -4125,7 +3777,6 @@ void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width,
}
// renderExplosions -- Dispatch to per-handler explosion renderer.
-// DAT_0047e108 flags & 1: suppress explosion sprites (counters still tick).
void InsaneRebel2::renderExplosions(byte *renderBitmap, int pitch, int width, int height) {
// Check flags bit 0: suppress explosion sprite rendering
LevelDifficultyParams dparams = getDifficultyParams();
@@ -4162,7 +3813,6 @@ void InsaneRebel2::renderExplosions(byte *renderBitmap, int pitch, int width, in
}
// renderExplosionFrame -- Shared explosion sprite path.
-// The original handlers reach this through separate functions. In the
// 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,
@@ -4216,9 +3866,6 @@ void InsaneRebel2::renderExplosionFrame(byte *renderBitmap, int pitch, int width
explosion.counter--;
}
-// renderTurretExplosions -- Handler 0x26 turret explosion rendering (FUN_409FBC).
-// Position: FUN_0041c720 3D->2D projection (identity at low-res).
-// Scale thresholds: <11, <21. Secondary NUT: DAT_0047fe80 (if DAT_0047a7fc >= 0).
void InsaneRebel2::renderTurretExplosions(byte *renderBitmap, int pitch, int width, int height) {
if (!_smush_iconsNut)
return;
@@ -4227,7 +3874,6 @@ void InsaneRebel2::renderTurretExplosions(byte *renderBitmap, int pitch, int wid
if (!_explosions[i].active)
continue;
- // Position: world coords passed through FUN_0041c720 (3Dâ2D projection).
// At 320x200 low-res turret view, projection is effectively identity.
int screenX = _explosions[i].x;
int screenY = _explosions[i].y;
@@ -4236,8 +3882,6 @@ void InsaneRebel2::renderTurretExplosions(byte *renderBitmap, int pitch, int wid
}
}
-// renderVehicleExplosions -- Handler 8 on-foot explosion rendering (FUN_402696).
-// Position: world coords minus camera offset (DAT_0043e006/08 = _shipPosX/_shipPosY).
// Scale thresholds: <11, <21. No secondary NUT.
void InsaneRebel2::renderVehicleExplosions(byte *renderBitmap, int pitch, int width, int height) {
if (!_smush_iconsNut)
@@ -4247,7 +3891,6 @@ void InsaneRebel2::renderVehicleExplosions(byte *renderBitmap, int pitch, int wi
if (!_explosions[i].active)
continue;
- // FUN_402696 line 22-23: screenX = worldX - DAT_0043e006, screenY = worldY - DAT_0043e008
int screenX = _explosions[i].x - _shipPosX;
int screenY = _explosions[i].y - _shipPosY;
renderExplosionFrame(renderBitmap, pitch, width, height, _explosions[i],
@@ -4255,20 +3898,14 @@ void InsaneRebel2::renderVehicleExplosions(byte *renderBitmap, int pitch, int wi
}
}
-// renderSpaceExplosions -- Handler 7 space explosion rendering (FUN_40F1C5).
-// Position: FUN_0041c720 3D->2D projection.
-// Original scale thresholds are resolution-dependent (<11/<21, doubled in high-res).
-// Secondary NUT: DAT_0047ff00 (FLY004, if DAT_0047a7fc >= 0).
void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int width, int height) {
if (!_smush_iconsNut)
return;
- // --- Part 1: Space shot explosions (FUN_40F1C5 lines 19-60) ---
for (int i = 0; i < 5; i++) {
if (!_explosions[i].active)
continue;
- // Position: world coords through FUN_0041c720 (3Dâ2D projection).
// At low-res, this is close to identity for the ship view.
int screenX = _explosions[i].x;
int screenY = _explosions[i].y;
@@ -4276,10 +3913,6 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
screenX, screenY, kExplosionAdvanceAfterDraw, true);
}
- // --- Part 2: Corridor/zone hit explosion (FUN_40F1C5 lines 61-85) ---
- // Rendered when _hitCooldown > 0 (DAT_0044374c). Decrement happens HERE
- // (matching original where FUN_40F1C5 decrements DAT_0044374c during render).
- // _spaceShotDirection (DAT_0044374e) determines explosion side:
// 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)
@@ -4300,7 +3933,6 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
}
// Per-direction offset from ship center.
- // Original uses lookup tables (DAT_004438da etc.) indexed by
// _shipDirectionIndex (35 entries per direction). We approximate
// with fixed offsets since we don't have the table data.
int offsetX = 0, offsetY = 0;
@@ -4345,9 +3977,6 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
}
}
-// renderHandler25Explosions -- Handler 25 FPS explosion rendering (FUN_41F29A).
-// Position: world coords + view offset (DAT_0045790c/0e = _rebelViewOffsetX/Y).
-// Original scale thresholds follow Handler 7 (<11/<21, doubled in high-res). No sound panning.
void InsaneRebel2::renderHandler25Explosions(byte *renderBitmap, int pitch, int width, int height) {
if (!_smush_iconsNut)
return;
@@ -4356,7 +3985,6 @@ void InsaneRebel2::renderHandler25Explosions(byte *renderBitmap, int pitch, int
if (!_explosions[i].active)
continue;
- // FUN_41F29A line 22-23: screenX = worldX + DAT_0045790c, screenY = worldY + DAT_0045790e
int screenX = _explosions[i].x + _rebelViewOffsetX;
int screenY = _explosions[i].y + _rebelViewOffsetY;
renderExplosionFrame(renderBitmap, pitch, width, height, _explosions[i],
@@ -4367,16 +3995,16 @@ 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: // Turret - FUN_40AD63
+ case 0x26:
renderTurretLaserShots(renderBitmap, pitch, width, height);
break;
- case 8: // Vehicle - FUN_402ED0
+ case 8:
renderVehicleLaserShots(renderBitmap, pitch, width, height);
break;
- case 7: // Space combat - FUN_40FADF
+ case 7:
renderSpaceLaserShots(renderBitmap, pitch, width, height);
break;
- case 25: // Speeder bike - FUN_0041f004
+ case 25:
renderHandler25LaserShots(renderBitmap, pitch, width, height);
break;
default:
@@ -4385,7 +4013,6 @@ void InsaneRebel2::renderLaserShots(byte *renderBitmap, int pitch, int width, in
}
}
-// renderTurretLaserShots -- Handler 0x26 turret laser rendering (FUN_40AD63).
void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int width, int height) {
// Uses pre-initialized _laserTexture from sprite 5 of CPITIMAG.NUT
@@ -4398,8 +4025,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
if (_turretShots[i].counter <= 0)
continue;
- // Calculate sound panning from target X position (FUN_004262f0 call)
- // sVar1 = ((2 - counter) * (targetX - 160)) / 2, clamped to [-127, 127]
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
@@ -4408,8 +4033,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
int16 targetY = _turretShots[i].targetY;
int16 progress = maxDuration - _turretShots[i].counter;
- // Gun positions based on level type (from FUN_40AD63 switch statement)
- // Parameters from assembly: widthScale=0xC(12), heightScale=8, thickness=0xC(12)
switch (_rebelLevelType) {
case 1:
// Type 1: 3 guns (triple cannon configuration)
@@ -4459,7 +4082,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
break;
default:
- // Default: 2 guns with alternating pattern based on shot sequence
// 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)
@@ -4487,8 +4109,6 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
}
}
-// renderVehicleLaserShots -- Handler 8 vehicle laser rendering (FUN_402ED0).
-// In the original, the laser is a short muzzle flash, NOT a traveling projectile.
void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int width, int height) {
// No NUT check needed - uses pre-initialized _laserTexture
@@ -4498,17 +4118,12 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
if (_vehicleShots[i].counter <= 0)
continue;
- // Calculate sound panning from STORED target position (FUN_402ED0 lines 24-51)
// 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
- // Calculate positions from CURRENT ship position (FUN_402ED0 lines 53-122)
- // The original game draws the laser from gun position toward ship center,
// creating a short muzzle flash effect (7 pixels horizontal, 25 pixels vertical).
- //
- // Low-res formula (DAT_0047a808 < 2):
// 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
@@ -4521,7 +4136,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)
- // From FUN_402ED0: widthScale=0x14(20), heightScale=8, thickness=4
// Parameters: gunX, gunY -> shipScreenX, shipScreenY (NOT the stored target!)
drawLaserBeam(renderBitmap, pitch, width, height,
gunX, gunY,
@@ -4530,9 +4144,7 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
_vehicleShots[i].counter--;
- // FUN_402ED0 samples DAT_0047e030 after drawing the beam. Non-zero
// mask pixels select the POV002/POV003 impact sprite index rendered by
- // FUN_402DA8 on following frames. Mode 2 suppresses these impacts.
if (_shipLevelMode != 2 && _level2BackgroundLoaded && _level2Background) {
int impactX = ((_shipPosX - 160) >> 3) + _shipPosX + 160;
int impactY = ((_shipPosY - 40) >> 2) + _shipPosY + 105;
@@ -4558,7 +4170,6 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
}
}
-// renderSpaceLaserShots -- Handler 7 space laser rendering (FUN_40FADF).
void InsaneRebel2::renderSpaceLaserShots(byte *renderBitmap, int pitch, int width, int height) {
// No NUT check needed - uses pre-initialized _laserTexture
@@ -4582,7 +4193,6 @@ void InsaneRebel2::renderSpaceLaserShots(byte *renderBitmap, int pitch, int widt
int16 progress = maxDuration - _spaceShots[i].counter;
// Draw dual beams
- // From FUN_40FADF: widthScale=0xC(12), heightScale=4, thickness=6
// Left gun beam
drawLaserBeam(renderBitmap, pitch, width, height,
leftGunX, leftGunY, targetX, targetY,
@@ -4597,9 +4207,7 @@ void InsaneRebel2::renderSpaceLaserShots(byte *renderBitmap, int pitch, int widt
}
}
-// renderHandler25LaserShots -- Handler 25 speeder bike laser rendering (FUN_0041f004).
void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int width, int height) {
- // FUN_0041f004 uses turret-style shot slots with view offset adjustment
// Only render when player is uncovered (damage == 0)
if (_rebelDamageLevel != 0) {
return; // Can't shoot while taking cover
@@ -4611,23 +4219,18 @@ void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int
if (_turretShots[i].counter <= 0)
continue;
- // Calculate sound panning from target X position (FUN_004262f0)
- // sVar1 = ((2 - counter) * (targetX - 160)) / 2, clamped to [-127, 127]
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
- // FUN_00407FCB adds DAT_0045790c/0e at render time to both gun and target.
int16 targetX = _turretShots[i].targetX + _rebelViewOffsetX;
int16 targetY = _turretShots[i].targetY + _rebelViewOffsetY;
- // Gun position computed at spawn time in base coords (DAT_0045791c/20).
int16 gunX = _turretShots[i].gunX + _rebelViewOffsetX;
int16 gunY = _turretShots[i].gunY + _rebelViewOffsetY;
int16 progress = maxDuration - _turretShots[i].counter;
- // From FUN_0041f004 parameters for FUN_0040bbf6:
// widthScale=0xC(12), heightScale=4, thickness=6
drawLaserBeam(renderBitmap, pitch, width, height,
gunX, gunY, targetX, targetY,
@@ -4652,7 +4255,6 @@ void InsaneRebel2::renderHandler8MonitorEffect(byte *renderBitmap, int pitch, in
return;
if (highRes) {
- // FUN_0041C6C3/FUN_00413EC2: fill every other gameplay row with color 4.
for (int y = 1; y < effectHeight; y += 2) {
byte *row = renderBitmap + y * pitch;
memset(row, 4, effectWidth);
@@ -4663,9 +4265,6 @@ void InsaneRebel2::renderHandler8MonitorEffect(byte *renderBitmap, int pitch, in
if (_rebelDetailMode <= 0)
return;
- // FUN_0041C6EC/FUN_00413EFC: remap every other gameplay row through
- // FUN_00410721()+0x400. Use the primary edge table, matching
- // DAT_0047a81c == 0.
const byte *monitorTable = _edgeTable + 0x400;
for (int y = 1; y < effectHeight; y += 2) {
byte *row = renderBitmap + y * pitch;
@@ -4674,7 +4273,6 @@ void InsaneRebel2::renderHandler8MonitorEffect(byte *renderBitmap, int pitch, in
}
}
-// renderHandler8PovOverlay -- Draw Level 12 POV text overlay (FUN_00401CCF).
void InsaneRebel2::renderHandler8PovOverlay(byte *renderBitmap, int pitch, int width, int height) {
if (_rebelHandler != 8 || !renderBitmap || !_smush_talkfontNut || !_smush_povfontNut)
return;
@@ -4690,7 +4288,6 @@ void InsaneRebel2::renderHandler8PovOverlay(byte *renderBitmap, int pitch, int w
ScummEngine_v7 *vm = (ScummEngine_v7 *)_vm;
const int povScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
- // Original updates DAT_0047e048 with random(5)+0x23 when random(20)==0.
if (getHandler8PovOverlayRandom(vm, 20) == 0)
_handler8HudGlyph = (char)(getHandler8PovOverlayRandom(vm, 5) + 0x23);
@@ -4740,21 +4337,17 @@ 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) {
- // From FUN_0040d836 (Handler 7) line 167-168: crosshair only drawn when DAT_004437c0 == 2
// Don't draw crosshair when shooting is disabled (flight-only segments)
if (!isShootingAllowed()) {
return;
}
- // Handler 25 (0x19): From FUN_41DB5E lines 195-197, crosshair only drawn when
- // DAT_0045790a == 0 (fully uncovered). Hide crosshair during cover transition.
if (_rebelHandler == 25 && _rebelDamageLevel != 0) {
return;
}
// Update target lock state and draw crosshair/reticle
- // Target lock detection (DAT_00443676 equivalent)
Common::Point aimPos;
if (_rebelHandler == 7) {
aimPos = getHandler7ShotTargetPoint();
@@ -4829,13 +4422,11 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
}
// Handler 25 (0x19): Add view offset to crosshair position
- // From FUN_41DB5E lines 198-199: X = DAT_00457914 + DAT_0045790c, Y = DAT_00457916 + DAT_0045790e
if (_rebelHandler == 25) {
crosshairX += _rebelViewOffsetX * reticleScale;
crosshairY += _rebelViewOffsetY * reticleScale;
}
- // FUN_004236e0 with flags=2 applies the NUT frame offsets, then centers.
crosshairX += _smush_iconsNut->getCharXOffset(reticleIndex) - cw / 2;
crosshairY += _smush_iconsNut->getCharYOffset(reticleIndex) - ch / 2;
@@ -4845,10 +4436,8 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
}
}
-// frameEndCleanup -- Reset enemy flags and collision zones at frame end (FUN_403240).
void InsaneRebel2::frameEndCleanup() {
// Reset enemy active flags and collision zones at frame end
- // The original game rebuilds lists from scratch each frame
for (Common::List<enemy>::iterator it = _enemies.begin(); it != _enemies.end(); ++it) {
if (!it->destroyed) {
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 15ed6c0e9e3..6f835c396c9 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -29,42 +29,29 @@
namespace Scumm {
-// ---------------------------------------------------------------------------
-// Level 1 Handler - FUN_00417E53
-// Single gameplay phase (01P01.SAN)
-// ---------------------------------------------------------------------------
-
int InsaneRebel2::runLevel1() {
- // Play level beginning cinematic (01BEG.SAN)
playLevelBegin(1);
if (_vm->shouldQuit())
return kLevelQuit;
- // Main gameplay retry loop
while (!_vm->shouldQuit()) {
- // Reset shield for this attempt
_playerShield = 255;
_playerDamage = 0;
_deathFrame = 0;
resetExplosions();
- // Reset bit table before gameplay starts - FUN_00423880 calls FUN_00423a00(0)
- // This ensures all enemies are visible (not skipped by SKIP chunks)
clearBit(0);
- // Play gameplay (01P01.SAN with 0x28 flags)
if (!playLevelSegment("LEV01/01P01.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
- // Level completed!
debugC(DEBUG_INSANE, "Level 1 completed!");
playLevelEnd(1);
- _levelUnlocked[1] = true; // Unlock level 2
+ _levelUnlocked[1] = true;
return kLevelNextLevel;
}
- // Player died - play death video with random A/B variant
debugC(DEBUG_INSANE, "Level 1 death at frame %d, lives=%d", _deathFrame, _playerLives - 1);
playLevelDeathVariant(1, 1, _deathFrame);
@@ -77,7 +64,6 @@ int InsaneRebel2::runLevel1() {
return kLevelGameOver;
}
- // Play retry prompt and loop
playLevelRetry(1);
if (_vm->shouldQuit())
return kLevelQuit;
@@ -86,19 +72,7 @@ int InsaneRebel2::runLevel1() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Wave State Management - FUN_00417b61
-// Waits for video completion, accumulates kill state, redistributes kill credits.
-// Used by randomized multi-wave level handlers as the core wave loop primitive.
-// ---------------------------------------------------------------------------
-
InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budget, int16 threshold, uint16 flags) {
- // FUN_00417b61: Core wave management function
- // Called after each wave video plays. Handles:
- // 1. Waiting for video to finish (with early exit on enemy completion)
- // 2. Copying wave state to accumulated phase state
- // 3. Redistributing kill credits from the budget
-
WaveEndResult result;
if (_rebelMovieMode) {
@@ -122,25 +96,15 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
return result;
}
- // Step 1: Wait for video to finish (lines 21-32)
- // Original loop: while (damage < 0xff && frame < maxFrame-1 && !escPressed)
- // The SmushPlayer::play() call already blocks until video ends, so this step
- // is handled implicitly. The early-exit logic (threshold > 0: if frame > 50
- // AND all required enemy type bits are set, count up and break when > threshold)
- // requires per-frame callbacks to work precisely. The current implementation
- // covers the primary effect by playing the full video and accumulating state.
+ // 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.
- // Step 2: Copy wave state to phase state (line 33)
- // DAT_0047ab9c = DAT_0047ab98
_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);
- // Step 3: Kill redistribution - add random unkilled types (lines 34-47)
- // Only when (flags & 2) != 0. Level 2 always passes flags=0, so inactive for Level 2.
if ((flags & 2) != 0) {
- // Collect unkilled enemy type bits that are within the mask
byte unkilled[8];
int16 numUnkilled = 0;
for (byte b = 0; (2 << (b & 0x1f)) < (int)(mask & 0x0e); b++) {
@@ -150,7 +114,6 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
}
}
if (numUnkilled > 0) {
- // Randomly add one unkilled type to phase state
int idx = _vm->_rnd.getRandomNumber(numUnkilled - 1);
_rebelPhaseState |= unkilled[idx];
if (budget)
@@ -158,8 +121,6 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
}
}
- // Step 4: Kill credit transfer (lines 48-73)
- // Collect all SET enemy type bits from phase state
byte killed[8];
int16 numKilled = 0;
for (byte b = 0; (2 << (b & 0x1f)) < (int)(mask & 0x0e); b++) {
@@ -169,18 +130,15 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
}
}
- // Max credits: 8 normally, 2 if flag bit 0 set
int16 maxCredits = ((flags & 1) == 0) ? 8 : 2;
- // Transfer kills from phase state to result, limited by budget
int16 creditCount = 0;
while (creditCount < maxCredits && numKilled > 0 && budget && *budget > 0) {
int idx = _vm->_rnd.getRandomNumber(numKilled - 1);
- _rebelPhaseState -= killed[idx]; // Remove from accumulated state
- result.creditedBits |= killed[idx]; // Credit to return value
+ _rebelPhaseState -= killed[idx];
+ result.creditedBits |= killed[idx];
(*budget)--;
- // Remove from array (shift remaining elements)
for (int i = idx; i + 1 < numKilled; i++) {
killed[i] = killed[i + 1];
}
@@ -191,7 +149,6 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
debugC(DEBUG_INSANE, "processWaveEnd: result=0x%x phaseState=0x%x (after redistribution) budget=%d",
result.creditedBits, _rebelPhaseState, budget ? *budget : -1);
- // Step 5: Stop conditions (lines 74-78)
result.died = (_playerDamage >= 255);
result.completed = ((int16)_rebelPhaseState >= mask);
result.quit = _vm->shouldQuit();
@@ -337,7 +294,6 @@ void InsaneRebel2::resetLevelWaveState() {
_rebelWaveState = 0;
}
-// resetShieldGauge -- Clear the shield hit-point gauge before a looping attack-run segment.
void InsaneRebel2::resetShieldGauge() {
for (int i = 0; i < 10; ++i) {
_rebelValueCounters[i] = 0;
@@ -406,61 +362,45 @@ void InsaneRebel2::resetHandler7FlightState() {
}
}
-// ---------------------------------------------------------------------------
-// Level 2 Handler - FUN_00418063
// Multiple parts with P1/P2/P3 subdirectories
// Random animation variants for each part
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel2() {
- // FUN_00418063: Level 2 "Corellia Star" - Third-person on-foot shooting
// 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)
- //
- // Kill credit budget (from level data table DAT_0047e0e8):
// 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
- int bonusCount = 0; // local_1c: tracks bonus events (DAT_0047ab9c & 0x10)
- int totalKills = 0; // local_c: accumulated kill count across phases
- int totalMisses = 0; // Accumulated misses (sVar1 + sVar2 from hit counters)
- int prevWaveState = 0; // variantIdx: previous wave's state for Phase 3 randomization
+ int bonusCount = 0;
+ int totalKills = 0;
+ int totalMisses = 0;
+ int prevWaveState = 0;
- // Play cutscene (02CUT.SAN)
playCinematic("LEV02/02CUT.SAN");
if (_vm->shouldQuit())
return kLevelQuit;
- // Play level beginning cinematic (02BEG.SAN)
- // Original: FUN_004171c5("LEV02/02BEG.SAN", 0x20, 0xab, 0xa0, 10, 2, 0x46)
playLevelBegin(2);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_00407d10 + FUN_0040c040: Reset game state (before retry loop)
clearBit(0);
- // Main gameplay retry loop (restarts from beginning on death)
while (!_vm->shouldQuit()) {
resetLevelAttemptState(1);
bonusCount = 0;
totalKills = 0;
totalMisses = 0;
- // ----- PHASE 1: P1/02P01_X.SAN -----
- // FUN_0041c7d0: Reset per-phase counters
resetLevelPhaseState(false);
- // Initialize kill budget from level data table + random(3)
- // Original: sVar4 = levelData[phase1Offset]; local_14[0] = sVar4 + random(3)
int16 budget = kLevel2BudgetBase[0] + _vm->_rnd.getRandomNumber(2);
- // Play A.SAN (background loader) â flags 0x28 (preserve buffer, gameplay mode)
debugC(DEBUG_INSANE, "Level 2 Phase 1 - playing 02P01_A.SAN (background) budget=%d", budget);
if (!playLevelSegment("LEV02/P1/02P01_A.SAN", 0x28))
return kLevelQuit;
@@ -469,12 +409,10 @@ int InsaneRebel2::runLevel2() {
processWaveEnd(0x36, &budget, 0, 0);
// Phase 1 wave loop: random B/C/D until all type 1,2 enemies killed
- // Original: while (uVar3 >= 0 && (DAT_0047ab9c & 6) != 6)
while (_playerDamage < 255 && (_rebelPhaseState & 0x06) != 0x06) {
if (_vm->shouldQuit())
return kLevelQuit;
- // Random variant B, C, or D
int variant = _vm->_rnd.getRandomNumber(2); // 0-2
const char *variants[] = {
"LEV02/P1/02P01_B.SAN",
@@ -482,7 +420,6 @@ int InsaneRebel2::runLevel2() {
"LEV02/P1/02P01_D.SAN"
};
debugC(DEBUG_INSANE, "Phase 1 wave - playing %s (state=0x%x budget=%d)", variants[variant], _rebelPhaseState, budget);
- // Wave videos use flags 0x428 (original: FUN_0041f4d0 param_2=0x428)
if (!playLevelSegment(variants[variant], 0x428))
return kLevelQuit;
@@ -491,7 +428,6 @@ int InsaneRebel2::runLevel2() {
debugC(DEBUG_INSANE, "Phase 1 wave done - state=0x%x (need 0x06) budget=%d", _rebelPhaseState, budget);
}
- // Check for bonus (bit 4 = 0x10)
if ((_rebelPhaseState & 0x10) != 0)
bonusCount++;
@@ -504,9 +440,6 @@ int InsaneRebel2::runLevel2() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Post segment 1 (02PST1.SAN)
- // Original: FUN_00417168("02PST1.SAN", 0x20) â flags = 0x20 | 0x08 = 0x28
- // FUN_00417168 adds | 8 to preserve the screen buffer between gameplay and transition
// Reset handler to 0 so procPostRendering skips HUD/sprite drawing during cinematic
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
@@ -516,23 +449,18 @@ int InsaneRebel2::runLevel2() {
totalKills += _rebelKillCounter;
totalMisses += _rebelHitCounter;
- // ----- PHASE 2: P2/02P02_X.SAN -----
_currentPhase = 2;
resetLevelPhaseState(true);
- // Initialize Phase 2 budget
budget = kLevel2BudgetBase[1] + _vm->_rnd.getRandomNumber(2);
// Restore handler for gameplay â will be confirmed by IACT opcode 6
_rebelHandler = 8;
- // Play A.SAN (background loader)
debugC(DEBUG_INSANE, "Level 2 Phase 2 - playing 02P02_A.SAN (background) budget=%d", budget);
if (!playLevelSegment("LEV02/P2/02P02_A.SAN", 0x28))
return kLevelQuit;
- // Phase 2 wave loop: processWaveEnd at TOP of loop (matches assembly structure)
- // Original: local_10 = FUN_00417b61(0x3e, local_14, 0, 0); then switch(local_10)
while (true) {
WaveEndResult waveEnd = processWaveEnd(0x3e, &budget, 0, 0);
uint16 waveSelect = waveEnd.creditedBits;
@@ -541,13 +469,11 @@ int InsaneRebel2::runLevel2() {
if (_vm->shouldQuit())
return kLevelQuit;
- // If no specific pattern: randomize high bits (original lines 71-74)
- // When (local_10 & 0xc) == 0: add random 0x10/0x11/0x12
+
if ((waveSelect & 0x0c) == 0) {
waveSelect = _vm->_rnd.getRandomNumber(2) + 0x10;
}
- // Variant selection matching original switch (FUN_418063 lines 75-96)
const char *filename;
switch (waveSelect) {
case 4: case 6:
@@ -581,8 +507,6 @@ int InsaneRebel2::runLevel2() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Post segment 2 (02PST2.SAN)
- // Original: FUN_00417168("02PST2.SAN", 0x20) â flags = 0x20 | 0x08 = 0x28
// Reset handler to 0 so procPostRendering skips HUD/sprite drawing during cinematic
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
@@ -592,24 +516,20 @@ int InsaneRebel2::runLevel2() {
totalKills += _rebelKillCounter;
totalMisses += _rebelHitCounter;
- // ----- PHASE 3: P3/02P03_X.SAN -----
_currentPhase = 3;
resetLevelPhaseState(true);
prevWaveState = 0;
- // Initialize Phase 3 budget
budget = kLevel2BudgetBase[2] + _vm->_rnd.getRandomNumber(2);
// Restore handler for gameplay â will be confirmed by IACT opcode 6
_rebelHandler = 8;
- // Play A.SAN (background loader)
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
- // Original: local_10 = FUN_00417b61(0x3e, local_14, 0, 0); while (loop) { ...; local_10 = FUN_00417b61(0x3e, local_14, 0x14, 0); }
{
WaveEndResult waveEnd = processWaveEnd(0x3e, &budget, 0, 0);
@@ -619,14 +539,12 @@ int InsaneRebel2::runLevel2() {
uint16 waveSelect = waveEnd.creditedBits;
- // Phase 3 randomization (original lines 113-115):
// 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;
}
prevWaveState = waveSelect;
- // Variant selection matching original switch (FUN_418063 lines 117-144)
const char *filename;
switch (waveSelect) {
case 0:
@@ -672,8 +590,6 @@ int InsaneRebel2::runLevel2() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Level completed! Calculate accuracy score.
- // Original: FUN_00417327 with score thresholds and medal ranks
// Score presentation remains to be implemented.
{
totalMisses += _rebelHitCounter;
@@ -690,21 +606,16 @@ int InsaneRebel2::runLevel2() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 3 Handler - FUN_0041885F
// 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
- // Play level beginning cinematic (03BEG.SAN)
playLevelBegin(3);
if (_vm->shouldQuit())
return kLevelQuit;
- // ----- PHASE 1 retry loop -----
while (!_vm->shouldQuit()) {
_playerShield = 255;
_playerDamage = 0;
@@ -713,22 +624,18 @@ int InsaneRebel2::runLevel3() {
clearEmbeddedHudFrames();
- // Reset bit table before gameplay starts - FUN_00423880 calls FUN_00423a00(0)
clearBit(0);
resetHandler7FlightState();
- // Play phase 1 gameplay (03PLAY1.SAN)
debugC(DEBUG_INSANE, "Level 3 Phase 1");
if (!playLevelSegment("LEV03/03PLAY1.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
- // Phase 1 completed - save score and proceed to phase 2
phase1Score = _playerScore;
break;
}
- // Died in phase 1 - frame-based death video
debugC(DEBUG_INSANE, "Level 3 Phase 1 death at frame %d", _deathFrame);
playLevelDeathVariant(3, 1, _deathFrame);
if (_vm->shouldQuit())
@@ -740,7 +647,6 @@ int InsaneRebel2::runLevel3() {
return kLevelGameOver;
}
- // Phase 1 retry (03RETRY.SAN)
playLevelRetryVariant(3, 1);
if (_vm->shouldQuit())
return kLevelQuit;
@@ -749,15 +655,12 @@ int InsaneRebel2::runLevel3() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Post segment 1 (03POST1.SAN)
- // Original: FUN_00417168 adds | 8, so flags = 0x20 | 0x08 = 0x28
// Reset handler to 0 so procPostRendering skips HUD/sprite drawing during cinematic
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
if (!playLevelSegment("LEV03/03POST1.SAN", 0x28, false))
return kLevelQuit;
- // ----- PHASE 2 retry loop (preserves phase 1 score) -----
_currentPhase = 2;
while (!_vm->shouldQuit()) {
@@ -768,24 +671,20 @@ int InsaneRebel2::runLevel3() {
clearEmbeddedHudFrames();
- // Reset bit table before gameplay starts
clearBit(0);
resetHandler7FlightState();
- // Play phase 2 gameplay (03PLAY2.SAN)
debugC(DEBUG_INSANE, "Level 3 Phase 2");
if (!playLevelSegment("LEV03/03PLAY2.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
- // Level completed!
debugC(DEBUG_INSANE, "Level 3 completed!");
playLevelEnd(3);
_levelUnlocked[3] = true; // Unlock level 4
return kLevelNextLevel;
}
- // Died in phase 2 - frame-based death video
debugC(DEBUG_INSANE, "Level 3 Phase 2 death at frame %d", _deathFrame);
playLevelDeathVariant(3, 2, _deathFrame);
if (_vm->shouldQuit())
@@ -798,7 +697,6 @@ int InsaneRebel2::runLevel3() {
return kLevelGameOver;
}
- // Phase 2 retry (03RETRYB.SAN)
playLevelRetryVariant(3, 2);
if (_vm->shouldQuit())
return kLevelQuit;
@@ -807,46 +705,36 @@ int InsaneRebel2::runLevel3() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
// Level 4 Handler
// Cutscene + single gameplay phase
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel4() {
- // Play cutscene (04CUT.SAN)
- // Original: FUN_00417168 adds | 8, so flags = 0x20 | 0x08 = 0x28
if (!playLevelSegment("LEV04/04CUT.SAN", 0x28, false))
return kLevelQuit;
- // Play level beginning cinematic (04BEG.SAN)
playLevelBegin(4);
if (_vm->shouldQuit())
return kLevelQuit;
- // Main gameplay retry loop
while (!_vm->shouldQuit()) {
_playerShield = 255;
_playerDamage = 0;
_currentPhase = 1;
resetExplosions();
- // Reset bit table before gameplay starts
clearBit(0);
- // Play gameplay (04PLAY.SAN)
debugC(DEBUG_INSANE, "Level 4 gameplay");
if (!playLevelSegment("LEV04/04PLAY.SAN", 0x28))
return kLevelQuit;
if (_playerShield > 0) {
- // Level completed!
debugC(DEBUG_INSANE, "Level 4 completed!");
playLevelEnd(4);
_levelUnlocked[4] = true; // Unlock level 5
return kLevelNextLevel;
}
- // Died - play death video (04DIE.SAN, no variants)
debugC(DEBUG_INSANE, "Level 4 death");
playLevelDeathVariant(4, 1, _deathFrame);
if (_vm->shouldQuit())
@@ -866,43 +754,33 @@ int InsaneRebel2::runLevel4() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 5 Handler - FUN_00418EC6
// Single gameplay phase (05PLAY.SAN)
// Random A/B death video like Level 1
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel5() {
- // Play level beginning cinematic (05BEG.SAN)
playLevelBegin(5);
if (_vm->shouldQuit())
return kLevelQuit;
- // Main gameplay retry loop
while (!_vm->shouldQuit()) {
_playerShield = 255;
_playerDamage = 0;
_currentPhase = 1;
resetExplosions();
- // Reset bit table before gameplay starts
clearBit(0);
- // Play gameplay (05PLAY.SAN)
- // Original: FUN_0041f4d0("05PLAY.SAN", 8, -1, -1, 0)
debugC(DEBUG_INSANE, "Level 5 gameplay");
if (!playLevelSegment("LEV05/05PLAY.SAN", 0x08))
return kLevelQuit;
if (_playerShield > 0) {
- // Level completed!
debugC(DEBUG_INSANE, "Level 5 completed!");
playLevelEnd(5);
_levelUnlocked[5] = true; // Unlock level 6
return kLevelNextLevel;
}
- // Died - play death video with random A/B variant
debugC(DEBUG_INSANE, "Level 5 death at frame %d", _deathFrame);
playLevelDeathVariant(5, 1, _deathFrame);
if (_vm->shouldQuit())
@@ -922,42 +800,30 @@ int InsaneRebel2::runLevel5() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 6 Handler - FUN_00419317
// Two phases with per-phase retry (like Level 3)
// Phase 1: 06PLAY1.SAN, Phase 2: 06PLAY2.SAN
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel6() {
- // FUN_004190d6 â Mos Eisley: two-phase on-foot (Handler 8)
// Phase 1 (levelId=5): 06PLAY1.SAN, mid-switch to 06PLAY1B.SAN at frame 0x2a8
// Phase 2 (levelId=6): 06PLAY2.SAN, play until near-end
- // Original structure: outer do-while for phase 1 retries, inner while(true) for
// 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;
- // Play level beginning cinematic (06BEG.SAN)
- // Original: FUN_004171c5(s_LEV06_06BEG_SAN, 0x20, 0xaf, 0xa0, 10, 5, 0x4b)
playLevelBegin(6);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_0041c7d0 + FUN_0040c040 â handler init done by IACT opcode 6
// Outer retry loop â restarts phase 1 on phase 1 death
while (!_vm->shouldQuit()) {
- // FUN_00407d10 â reset shot/hit counters
clearBit(0);
resetExplosions();
- // DAT_0047ab9c = 0xffffffff â init phase state
_rebelPhaseState = 0xffffffff;
- // ----- PHASE 1: shield attack run -----
- // Play 06PLAY1 (approach), then loop the 06PLAY1B continuation until the player
// destroys the shield (its hit gauge reaches 0) or dies.
- _rebelLevelType = 5; // DAT_0047a7f8 = 5
+ _rebelLevelType = 5;
_currentPhase = 1;
debugC(DEBUG_INSANE, "Level 6 Phase 1 (shield attack run)");
@@ -979,7 +845,6 @@ int InsaneRebel2::runLevel6() {
_rebelShieldGateActive = false;
if (_playerShield <= 0) {
- // Died in phase 1
debugC(DEBUG_INSANE, "Level 6 Phase 1 death at frame %d", _deathFrame);
playLevelDeathVariant(6, 1, _deathFrame);
if (_vm->shouldQuit())
@@ -991,27 +856,24 @@ int InsaneRebel2::runLevel6() {
return kLevelGameOver;
}
- // Phase 1 retry (06RETRY.SAN) â restart outer loop
playLevelRetryVariant(6, 1);
if (_vm->shouldQuit())
return kLevelQuit;
continue;
}
- // Phase 1 survived â preserve this score across phase 2 retries.
- phase1Score = _playerScore; // variantIdx = DAT_0047ab84
+ phase1Score = _playerScore;
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
if (!playLevelSegment("LEV06/06POST1.SAN", 0x28, false))
return kLevelQuit;
- // ----- PHASE 2 retry loop (inner while(true) in original) -----
while (!_vm->shouldQuit()) {
- _rebelLevelType = 6; // DAT_0047a7f8 = 6
+ _rebelLevelType = 6;
_currentPhase = 2;
_playerScore = phase1Score;
- clearBit(0); // FUN_00407d10
+ clearBit(0);
resetExplosions();
debugC(DEBUG_INSANE, "Level 6 Phase 2");
@@ -1019,14 +881,12 @@ int InsaneRebel2::runLevel6() {
return kLevelQuit;
if (_playerShield > 0) {
- // Level completed!
debugC(DEBUG_INSANE, "Level 6 completed!");
playLevelEnd(6);
_levelUnlocked[6] = true;
return kLevelNextLevel;
}
- // Died in phase 2
debugC(DEBUG_INSANE, "Level 6 Phase 2 death at frame %d", _deathFrame);
playLevelDeathVariant(6, 2, _deathFrame);
if (_vm->shouldQuit())
@@ -1038,7 +898,6 @@ int InsaneRebel2::runLevel6() {
return kLevelGameOver;
}
- // Phase 2 retry (06RETRYB.SAN) â re-enter phase 2
playLevelRetryVariant(6, 2);
if (_vm->shouldQuit())
return kLevelQuit;
@@ -1050,27 +909,20 @@ int InsaneRebel2::runLevel6() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 7 Handler - FUN_0041974C
// "TIE Training" - Canyon flight with fork at frame 1592
// Single gameplay phase (07PLAY.SAN), optional second segment (07PLAYB.SAN)
-// Death: DAT_0047ab8c-based (fork reached â DIE_B, not reached â DIE_A)
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel7() {
- bool reachedFork = false; // DAT_0047ab8c equivalent â tracks if 07PLAYB was played
+ bool reachedFork = false;
- // Play cutscene (07CUT.SAN)
playCinematic("LEV07/07CUT.SAN");
if (_vm->shouldQuit())
return kLevelQuit;
- // Play level beginning cinematic (07BEG.SAN)
playLevelBegin(7);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_0041c7d0 + FUN_00407d10
clearBit(0);
while (!_vm->shouldQuit()) {
@@ -1082,9 +934,6 @@ int InsaneRebel2::runLevel7() {
clearBit(0);
- // Play gameplay (07PLAY.SAN)
- // Original: FUN_0041f4d0("07PLAY.SAN", 0x28, -1, -1, 0)
- // At frame 0x638 (1592), if DAT_0047ab8c != 0: play 07PLAYB.SAN (0x468)
// 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.
@@ -1098,8 +947,6 @@ int InsaneRebel2::runLevel7() {
return kLevelNextLevel;
}
- // Death video: DIE_B if fork reached, DIE_A if not
- // Original: s_LEV07_07DIE_B + ((DAT_0047ab8c != 0) - 1 & 0x14)
debugC(DEBUG_INSANE, "Level 7 death at frame %d, fork=%d", _deathFrame, reachedFork);
if (reachedFork) {
playCinematic("LEV07/07DIE_B.SAN");
@@ -1123,21 +970,13 @@ int InsaneRebel2::runLevel7() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 8 Handler - FUN_00419976
// "Flight to Imdaar" - Y-Wing space battle (single phase)
-// No cutscene (starts with BEG). flags=0x08 for gameplay.
-// Death: random A or B
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel8() {
- // No cutscene â starts directly with BEG
- // Original: FUN_004171c5("08BEG.SAN", 0x20, 0xb1, 0xa0, 10, 5, 0x4b)
playLevelBegin(8);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_0041c7d0 + FUN_0040c040
clearBit(0);
while (!_vm->shouldQuit()) {
@@ -1148,8 +987,6 @@ int InsaneRebel2::runLevel8() {
clearBit(0);
- // Play gameplay (08PLAY.SAN)
- // Original: FUN_0041f4d0("08PLAY.SAN", 8, -1, -1, 0) â note flags=0x08
if (!playLevelSegment("LEV08/08PLAY.SAN", 0x08))
return kLevelQuit;
@@ -1161,8 +998,6 @@ int InsaneRebel2::runLevel8() {
return kLevelNextLevel;
}
- // Death: random A or B
- // Original: random(2) â A or B via string pointer arithmetic
debugC(DEBUG_INSANE, "Level 8 death at frame %d", _deathFrame);
playLevelDeathVariant(8, 1, _deathFrame);
if (_vm->shouldQuit())
@@ -1182,22 +1017,13 @@ int InsaneRebel2::runLevel8() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 9 Handler - FUN_00419B86
// "The Mine Field" - Navigate through force fields (single phase)
-// No cutscene. Initial phaseState = 0xfffffffe (all bits set except bit 0).
-// Mid-events at frames 0x19f (415) and 0x352 (850): FUN_00407f55 (score checkpoint)
-// Death: DAT_0047ab94-based (0âA, 1âC, elseâB)
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel9() {
- // No cutscene â starts directly with BEG
- // Original: FUN_004171c5("09BEG.SAN", 0x20, 0xb2, 0xa0, 10, 200, 0x10e)
playLevelBegin(9);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_0041c7d0 + FUN_0040c040
clearBit(0);
while (!_vm->shouldQuit()) {
@@ -1208,12 +1034,8 @@ int InsaneRebel2::runLevel9() {
clearBit(0);
- // Original: DAT_0047ab9c = 0xfffffffe (initial phase state â all bits except 0)
_rebelPhaseState = 0xfffffffe;
- // Play gameplay (09PLAY.SAN)
- // Original: FUN_0041f4d0("09PLAY.SAN", 0x28, -1, -1, 0)
- // Mid-events at frames 415 and 850: FUN_00407f55 (score save)
// These are handled implicitly â the IACT callbacks manage scoring.
if (!playLevelSegment("LEV09/09PLAY.SAN", 0x28))
return kLevelQuit;
@@ -1226,8 +1048,6 @@ int InsaneRebel2::runLevel9() {
return kLevelNextLevel;
}
- // Death: based on DAT_0047ab94 (death cause tracking)
- // Original: 0âDIE_A, 1âDIE_C, elseâDIE_B
debugC(DEBUG_INSANE, "Level 9 death at frame %d", _deathFrame);
playLevelDeathVariant(9, 1, _deathFrame);
if (_vm->shouldQuit())
@@ -1247,26 +1067,18 @@ int InsaneRebel2::runLevel9() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 10 Handler - FUN_00419E0A
// "Speeder Bikes" - Forest speeder chase (single phase)
// Has cutscene. Single death video (10DIE.SAN, no variants).
-// Original plays DIE then RETRY in sequence (no separate check).
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel10() {
- // Play cutscene (10CUT.SAN)
playCinematic("LEV10/10CUT.SAN");
if (_vm->shouldQuit())
return kLevelQuit;
- // Play level beginning cinematic (10BEG.SAN)
- // Original: FUN_004171c5("10BEG.SAN", 0x20, 0xb3, 0xa0, 10, 2, 0x46)
playLevelBegin(10);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_0041c7d0 + FUN_00407d10
clearBit(0);
while (!_vm->shouldQuit()) {
@@ -1277,8 +1089,6 @@ int InsaneRebel2::runLevel10() {
clearBit(0);
- // Play gameplay (10PLAY.SAN)
- // Original: FUN_0041f4d0("10PLAY.SAN", 0x28, -1, -1, 0)
if (!playLevelSegment("LEV10/10PLAY.SAN", 0x28))
return kLevelQuit;
@@ -1290,8 +1100,6 @@ int InsaneRebel2::runLevel10() {
return kLevelNextLevel;
}
- // Death + Retry: original plays both in sequence
- // Original: lives--, if 0 break to game over, else DIE+RETRY
debugC(DEBUG_INSANE, "Level 10 death at frame %d", _deathFrame);
_playerLives--;
if (_playerLives <= 0) {
@@ -1310,52 +1118,41 @@ int InsaneRebel2::runLevel10() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 11 Handler - FUN_0041A00C
// "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;
int prevPhaseState = 0;
- // Kill credit budget bases per phase (from level data table DAT_0047e0e8)
const int16 kLevel11BudgetBase[4] = { 3, 3, 3, 3 };
- // Play cutscene (11CUT.SAN)
playCinematic("LEV11/11CUT.SAN");
if (_vm->shouldQuit())
return kLevelQuit;
- // Play level beginning cinematic (11BEG.SAN)
playLevelBegin(11);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_00407d10 + FUN_0040c040: Reset game state
clearBit(0);
- // Main gameplay retry loop (restarts from Phase 1 on death)
while (!_vm->shouldQuit()) {
resetLevelAttemptState(1);
totalKills = 0;
totalMisses = 0;
prevPhaseState = 0;
- // ----- PHASE 1: P1/11P01_X.SAN -----
resetLevelPhaseState(false);
int16 budget = kLevel11BudgetBase[0] + _vm->_rnd.getRandomNumber(2);
- // Play A.SAN (background loader)
debugC(DEBUG_INSANE, "Level 11 Phase 1 - playing 11P01_A.SAN budget=%d", budget);
if (!playLevelSegment("LEV11/P1/11P01_A.SAN", 0x28))
return kLevelQuit;
@@ -1373,7 +1170,6 @@ int InsaneRebel2::runLevel11() {
// Bonus sound check
if ((_rebelPhaseState & 0x10) != 0 && (prevPhaseState & 0x10) == 0) {
- // FUN_00411931 bonus sound â not yet implemented
}
prevPhaseState = _rebelPhaseState;
@@ -1403,7 +1199,6 @@ int InsaneRebel2::runLevel11() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Post segment 1 (11POST1.SAN)
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
if (!playLevelSegment("LEV11/11POST1.SAN", 0x28, false))
@@ -1412,14 +1207,12 @@ int InsaneRebel2::runLevel11() {
totalKills += _rebelKillCounter;
totalMisses += _rebelHitCounter;
- // ----- PHASE 2: P2/11P02_X.SAN -----
_currentPhase = 2;
resetLevelPhaseState(true);
budget = kLevel11BudgetBase[1] + _vm->_rnd.getRandomNumber(2);
_rebelHandler = 8;
- // Play A.SAN (background loader)
debugC(DEBUG_INSANE, "Level 11 Phase 2 - playing 11P02_A.SAN budget=%d", budget);
if (!playLevelSegment("LEV11/P2/11P02_A.SAN", 0x28))
return kLevelQuit;
@@ -1459,7 +1252,6 @@ int InsaneRebel2::runLevel11() {
if (_vm->shouldQuit())
return kLevelQuit;
- // Post segment 2 (11POST2.SAN)
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
if (!playLevelSegment("LEV11/11POST2.SAN", 0x28, false))
@@ -1468,7 +1260,6 @@ int InsaneRebel2::runLevel11() {
totalKills += _rebelKillCounter;
totalMisses += _rebelHitCounter;
- // ----- PHASE 3 FIRST HALF: P3/11P03_X (A-F) -----
// Bridge puzzle â exit when (phaseState & 0x70) == 0x70
_currentPhase = 3;
resetLevelPhaseState(true);
@@ -1492,7 +1283,6 @@ int InsaneRebel2::runLevel11() {
// Bonus sound: (phaseState & 0xe) == 0xe and previous wasn't
if ((_rebelPhaseState & 0x0e) == 0x0e && (prevPhaseState & 0x0e) != 0x0e) {
- // FUN_00411931 bonus sound â not yet implemented
}
prevPhaseState = _rebelPhaseState;
@@ -1519,7 +1309,6 @@ int InsaneRebel2::runLevel11() {
if (!playLevelSegment(filename, 0x428))
return kLevelQuit;
- // Threshold only for higher variants (original: (2 < variantIdx) - 1 & 0x14)
int16 threshold = (variantIdx > 2) ? 0x14 : 0;
waveEnd = processWaveEnd(0x7e, &budget, threshold, 0);
}
@@ -1534,14 +1323,11 @@ int InsaneRebel2::runLevel11() {
if (_vm->shouldQuit())
return kLevelQuit;
- // ----- PHASE 3 BRIDGE CINEMATICS -----
{
bool allBasicKilled = (_rebelPhaseState & 0x0e) >= 0x0e;
if (!allBasicKilled) {
// Normal bridge drop cinematic
playCinematic("LEV11/11POST3.SAN");
- // Bonus checks (FUN_0042aa70) are not implemented; play the standard path.
- // Original checks 0x77 and 0x62 for special POST3C cinematic
} else {
// All enemy types killed â bridge dropped successfully
playCinematic("LEV11/11POST3B.SAN");
@@ -1551,12 +1337,9 @@ int InsaneRebel2::runLevel11() {
if (_vm->shouldQuit())
return kLevelQuit;
- // ----- PHASE 3 SECOND HALF: P3/11P03_X (G-L) -----
- // Reset shots/explosions (FUN_0041ca6a equivalent)
resetExplosions();
_enemies.clear();
- // Preserve only bits 1-3 of phase state (original: DAT_0047ab9c &= 0xe)
_rebelPhaseState &= 0x0e;
_rebelWaveState &= 0x0e;
@@ -1564,7 +1347,6 @@ int InsaneRebel2::runLevel11() {
budget = kLevel11BudgetBase[3] + _vm->_rnd.getRandomNumber(2);
- // Play G.SAN (background loader for second half)
debugC(DEBUG_INSANE, "Level 11 Phase 3 second half - playing 11P03_G.SAN budget=%d", budget);
if (!playLevelSegment("LEV11/P3/11P03_G.SAN", 0x28))
return kLevelQuit;
@@ -1618,7 +1400,6 @@ int InsaneRebel2::runLevel11() {
if (_vm->shouldQuit())
return kLevelQuit;
- // ----- LEVEL COMPLETED -----
{
totalMisses += _rebelHitCounter;
int accuracy = calculateAccuracy(totalKills, totalMisses);
@@ -1634,52 +1415,40 @@ int InsaneRebel2::runLevel11() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 12 Handler - FUN_0041AA14
// "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 };
- // Play cutscene (12CUT.SAN)
playCinematic("LEV12/12CUT.SAN");
if (_vm->shouldQuit())
return kLevelQuit;
- // Play level beginning cinematic (12BEG.SAN)
playLevelBegin(12);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_0041c7d0 + FUN_00407d10 + FUN_0040c040: Reset game state
clearBit(0);
- // Main gameplay retry loop (restarts from Phase 1 on death)
while (!_vm->shouldQuit()) {
resetLevelAttemptState(1);
- // ----- PHASE 1: 12P05 â P1/12P01_X -----
- // FUN_00401000: Reset at top of each retry
resetLevelPhaseState(false);
int16 budget = kLevel12BudgetBase[0] + _vm->_rnd.getRandomNumber(2);
- // Initialization video (12P05.SAN)
debugC(DEBUG_INSANE, "Level 12 Phase 1 - init 12P05.SAN budget=%d", budget);
if (!playLevelSegment("LEV12/12P05.SAN", 0x28, false))
return kLevelQuit;
processWaveEnd(1, &budget, 0, 0);
- // First wave (P1/12P01_A.SAN)
if (!playLevelSegment("LEV12/P1/12P01_A.SAN", 0x428))
return kLevelQuit;
@@ -1718,19 +1487,16 @@ int InsaneRebel2::runLevel12() {
if (_vm->shouldQuit())
return kLevelQuit;
- // ----- PHASE 2: 12P06 â P2/12P02_X -----
_currentPhase = 2;
resetLevelWaveState();
budget = kLevel12BudgetBase[1] + _vm->_rnd.getRandomNumber(3);
- // Initialization video (12P06.SAN)
debugC(DEBUG_INSANE, "Level 12 Phase 2 - init 12P06.SAN budget=%d", budget);
if (!playLevelSegment("LEV12/12P06.SAN", 0x428, false))
return kLevelQuit;
processWaveEnd(1, &budget, 0, 0);
- // First wave (P2/12P02_A.SAN)
if (!playLevelSegment("LEV12/P2/12P02_A.SAN", 0x428))
return kLevelQuit;
@@ -1780,19 +1546,16 @@ int InsaneRebel2::runLevel12() {
if (_vm->shouldQuit())
return kLevelQuit;
- // ----- PHASE 3: 12P07 â P3/12P03_X -----
_currentPhase = 3;
resetLevelWaveState();
budget = kLevel12BudgetBase[2] + _vm->_rnd.getRandomNumber(3);
- // Initialization video (12P07.SAN)
debugC(DEBUG_INSANE, "Level 12 Phase 3 - init 12P07.SAN budget=%d", budget);
if (!playLevelSegment("LEV12/12P07.SAN", 0x428, false))
return kLevelQuit;
processWaveEnd(1, &budget, 0, 0);
- // First wave (P3/12P03_A.SAN)
if (!playLevelSegment("LEV12/P3/12P03_A.SAN", 0x428))
return kLevelQuit;
@@ -1838,19 +1601,16 @@ int InsaneRebel2::runLevel12() {
if (_vm->shouldQuit())
return kLevelQuit;
- // ----- PHASE 4: 12P08 â P4/12P04_X -----
_currentPhase = 4;
resetLevelWaveState();
budget = kLevel12BudgetBase[3] + _vm->_rnd.getRandomNumber(3);
- // Initialization video (12P08.SAN)
debugC(DEBUG_INSANE, "Level 12 Phase 4 - init 12P08.SAN budget=%d", budget);
if (!playLevelSegment("LEV12/12P08.SAN", 0x428, false))
return kLevelQuit;
processWaveEnd(1, &budget, 0, 0);
- // First wave (P4/12P04_A.SAN)
if (!playLevelSegment("LEV12/P4/12P04_A.SAN", 0x428))
return kLevelQuit;
@@ -1895,19 +1655,16 @@ int InsaneRebel2::runLevel12() {
if (_vm->shouldQuit())
return kLevelQuit;
- // ----- CLOSING: 12P09.SAN -----
if (!playLevelSegment("LEV12/12P09.SAN", 0x428, false))
return kLevelQuit;
processWaveEnd(1, &budget, 0, 0);
- // ----- LEVEL COMPLETED -----
{
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 12 completed! kills=%d misses=%d accuracy=%d%%",
_rebelKillCounter, _rebelHitCounter, accuracy);
}
- // Bonus checks: FUN_0042aa70(0x61), FUN_0042aa70(99), FUN_0042aa70(0x74)
// If all three bonuses found, play special ending (12END_Z.SAN)
// Deferred until bonus tracking is implemented
@@ -1919,23 +1676,15 @@ int InsaneRebel2::runLevel12() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 13 Handler - FUN_0041B3E1
// "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
-// (DAT_0047ab90 == 0 && DAT_0047ab7c == 0) meaning all targets destroyed.
-// Death: frame-based (A/B/C/B/D pattern)
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel13() {
- // No cutscene â starts directly with BEG
- // Original: FUN_004171c5("13BEG.SAN", 0x20, 0xb6, 0xa0, 10, 2, 0x46)
playLevelBegin(13);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_0041c7d0 + FUN_0040c040
clearBit(0);
while (!_vm->shouldQuit()) {
@@ -1978,7 +1727,6 @@ int InsaneRebel2::runLevel13() {
return kLevelNextLevel;
}
- // Death: frame-based variant selection (FUN_0041B3E1 lines 47-61)
debugC(DEBUG_INSANE, "Level 13 death at frame %d", _deathFrame);
playLevelDeathVariant(13, 1, _deathFrame);
if (_vm->shouldQuit())
@@ -1998,20 +1746,13 @@ int InsaneRebel2::runLevel13() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 14 Handler - FUN_0041B6E8
// "TIE Attack" - Final space battle (single phase)
-// No cutscene. Single death video (14DIE.SAN, no variants).
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel14() {
- // No cutscene â starts directly with BEG
- // Original: FUN_004171c5("14BEG.SAN", 0x20, 0xb7, 0xa0, 10, 2, 0x46)
playLevelBegin(14);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_0041c7d0 + FUN_0040c040
clearBit(0);
while (!_vm->shouldQuit()) {
@@ -2022,8 +1763,6 @@ int InsaneRebel2::runLevel14() {
clearBit(0);
- // Play gameplay (14PLAY.SAN)
- // Original: FUN_0041f4d0("14PLAY.SAN", 0x28, -1, -1, 0)
if (!playLevelSegment("LEV14/14PLAY.SAN", 0x28))
return kLevelQuit;
@@ -2035,7 +1774,6 @@ int InsaneRebel2::runLevel14() {
return kLevelNextLevel;
}
- // Death: single video (14DIE.SAN)
debugC(DEBUG_INSANE, "Level 14 death at frame %d", _deathFrame);
playCinematic("LEV14/14DIE.SAN");
if (_vm->shouldQuit())
@@ -2055,31 +1793,20 @@ int InsaneRebel2::runLevel14() {
return kLevelQuit;
}
-// ---------------------------------------------------------------------------
-// Level 15 Handler - FUN_0041B8D7
// "Imdaar Alpha" - Final mission (single long phase with level ID switch)
-// Has cutscene. Mid-level: DAT_0047a7f8 changes from 0xf to 0x10 at frame 0x21e.
// This represents a transition from the tunnel section to the core section.
-// Death: frame-based (A/B/C/B/C/B/D pattern with 7 thresholds)
-// On completion â FUN_0041BBE8 (credits/end game, not a playable level)
-// ---------------------------------------------------------------------------
int InsaneRebel2::runLevel15() {
- // Play cutscene (15CUT.SAN)
playCinematic("LEV15/15CUT.SAN");
if (_vm->shouldQuit())
return kLevelQuit;
- // Play level beginning cinematic (15BEG.SAN)
- // Original: FUN_004171c5("15BEG.SAN", 0x20, 0xb8, 0xa0, 10, 2, 0x46)
playLevelBegin(15);
if (_vm->shouldQuit())
return kLevelQuit;
- // FUN_00401000 + FUN_0041c7d0 + FUN_0040c040
clearBit(0);
- // Original sets DAT_0047a7f8 = 0xf before the gameplay loop
_rebelLevelType = 0xf;
while (!_vm->shouldQuit()) {
@@ -2090,13 +1817,10 @@ int InsaneRebel2::runLevel15() {
clearBit(0);
- // Original: DAT_0047a7f8 = 0xf again at start of each retry
// 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;
- // Play gameplay (15PLAY.SAN)
- // Original: FUN_0041f4d0("15PLAY.SAN", 0x28, -1, -1, 0)
if (!playLevelSegment("LEV15/15PLAY.SAN", 0x28))
return kLevelQuit;
@@ -2105,11 +1829,9 @@ int InsaneRebel2::runLevel15() {
debugC(DEBUG_INSANE, "Level 15 completed! accuracy=%d%%", accuracy);
playLevelEnd(15);
_levelUnlocked[15] = true;
- // Level 15 completion leads to credits (FUN_0041BBE8)
return kLevelNextLevel;
}
- // Death: frame-based variant selection (FUN_0041B8D7 lines 46-65)
debugC(DEBUG_INSANE, "Level 15 death at frame %d", _deathFrame);
playLevelDeathVariant(15, 1, _deathFrame);
if (_vm->shouldQuit())
Commit: afe64b518cdd6e9178ec8ae668e24f722cce390f
https://github.com/scummvm/scummvm/commit/afe64b518cdd6e9178ec8ae668e24f722cce390f
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-21T18:24:31+02:00
Commit Message:
SCUMM: RA1: clean-up of comments for INSANE code
Changed paths:
engines/scumm/insane/rebel1/audio.cpp
engines/scumm/insane/rebel1/iact.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
diff --git a/engines/scumm/insane/rebel1/audio.cpp b/engines/scumm/insane/rebel1/audio.cpp
index 482a764db67..83a52d9f50a 100644
--- a/engines/scumm/insane/rebel1/audio.cpp
+++ b/engines/scumm/insane/rebel1/audio.cpp
@@ -44,9 +44,7 @@ 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 92c798a81f0..a7473fff597 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -54,7 +54,7 @@ inline int16 stepRebel1Op0BReticleAxis(int axisValue) {
if (axisValue >= 0)
return (int16)(axisValue >> 4);
- // The 3DO ARM code integrates the standard control-pad path with ASR #4.
+ // Preserve arithmetic-shift rounding for negative movement.
return (int16)-((-axisValue + 15) >> 4);
}
@@ -89,8 +89,8 @@ const int kRA1EnhancedFlightDirectMaxX = 64;
const int kRA1EnhancedFlightDirectMaxY = 40;
const int kRA1MouseFlightRollTargetScale = 16;
const int kRA1ControlPadAxisStep = 0x1E;
-const int16 kOnFootCenterX = 0xA3; // g_perspectiveX in HandleGameOp19
-const int16 kOnFootCenterY = 0x82; // g_perspectiveY in HandleGameOp19
+const int16 kOnFootCenterX = 0xA3;
+const int16 kOnFootCenterY = 0x82;
const int16 kOnFootCursorBaseY = kOnFootCenterY - 0x32;
const int16 kOnFootGamepadStep = 7;
const int16 kOnFootCursorMinX = 0;
@@ -101,9 +101,7 @@ const int16 kRA1Level12TargetA = 195;
const int16 kRA1Level12TargetB = 197;
const int16 kRA1Level12TargetC = 199;
-// Level 15 final approach 0x5D damage/event codes consumed by
-// RunLevel1GameLoop. The latch stores the raw GAME parameter; no translation is
-// performed by HandleGameOp5D_SegmentLinkLatch.
+// Level 15 final approach 0x5D damage/event codes.
inline bool isLevel15FinalDamageLatch(uint16 code) {
switch (code) {
case 0x0049:
@@ -197,7 +195,6 @@ inline bool isLevel2DamageLatch(uint16 code) {
}
}
-// Level 2 asteroid-contact helper from FUN_00012d70. RunLevel2Flow calls it
// once per frontend frame and raises damage flag 0x20 when the current asteroid
// pass intersects the camera position.
inline bool hasLevel2AsteroidImpact(uint16 frameCounter, int16 perspectiveX, int16 perspectiveY) {
@@ -397,9 +394,7 @@ inline bool isLevel10DamageLatch(uint16 code) {
}
inline bool isLevel12DamageLatch(uint16 code) {
- // RunLevel12Flow treats these 0x5D latch values as safe; every other value
- // raises damage flag 0x40. This helper is intentionally the inverse of the
- // original branch shape so the update path reads as "is damage".
+ // These 0x5D latch values are safe; every other value raises damage flag 0x40.
switch (code) {
case 0x0000:
case 0x0037:
@@ -465,8 +460,6 @@ inline bool hasLevel6PerspectiveHazard(uint16 frame, int16 perspectiveX, int16 p
}
}
-// Named translations of the original Level 8 route collision helpers
-// FUN_12FE1/FUN_130C9/FUN_13195. The names are new to the port.
inline bool hasLevel8WalkerHazardRoute0(uint16 frame, int16 viewX, int16 viewY) {
switch (frame) {
case 0x00CD:
@@ -551,9 +544,7 @@ void InsaneRebel1::applyFrameObjectHitState(int16 targetIdx) {
if (targetIdx < 0)
return;
- // Protected targets (shield generators in Level 4, etc.) can be hit
- // repeatedly â skip event mask toggle. Original: DAT_00007732/7734 check
- // in HandleGameOp5A.
+ // Protected targets use separate hit counters.
if (targetIdx + 1 == _protectedTargetA || targetIdx + 1 == _protectedTargetB)
return;
@@ -599,17 +590,13 @@ void InsaneRebel1::clearFrameObjectPrimaryBits(int byteIndex, byte mask) {
_frameObjectState[byteIndex] &= ~mask;
}
-// Port helpers for RunLevel14Flow. The original checks DAT_7614..7616 and
-// DAT_7605..7606 inline, then increments a local 60-frame completion counter.
bool InsaneRebel1::areLevel14Phase1TargetsDestroyed() const {
- // g_gameplayPhaseFlags starts at primary byte 0; DAT_7614 is byte 0x12.
return areFrameObjectPrimaryBitsSet(0x12, 0x01) &&
areFrameObjectPrimaryBitsSet(0x13, 0xFF) &&
areFrameObjectPrimaryBitsSet(0x14, 0xFE);
}
bool InsaneRebel1::areLevel14Phase2TargetsDestroyed() const {
- // Phase 2 checks DAT_7605 low nibble and all of DAT_7606.
return areFrameObjectPrimaryBitsSet(0x03, 0x0F) &&
areFrameObjectPrimaryBitsSet(0x04, 0xFF);
}
@@ -732,14 +719,12 @@ void InsaneRebel1::checkDynamicLevelBranch(int32 curFrame) {
return;
if (_currentLevel == 6) {
- // RunLevel7Flow compares the branch table against g_frameCounter. The
- // playback callback writes that value from the ANM-local frame index,
- // not from the decoded GAME counter embedded in these non-linear files.
+ // Route branches use the ANM-local frame index, not the decoded GAME counter.
if (curFrame < 0)
return;
const uint32 routeFrame = (uint32)curFrame;
- // GAME 0x09 publishes its branch-tested position in g_shipPosX.
- // Keep the drawn ship center and the 0x09 aim cursor split,
+ // 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);
@@ -782,7 +767,6 @@ void InsaneRebel1::checkDynamicLevelBranch(int32 curFrame) {
}
// Level 8 owns its branch choice in updateLevel8WalkerState(), where the
- // original RunLevel8Flow also draws the timer/arrows and updates the local
// choice variable. This function only performs the delayed route cutover.
}
@@ -860,10 +844,7 @@ void InsaneRebel1::updateFlightVariantCursor() {
if (_rollAccum > 0)
xScale = -xScale;
- // Assembly-verified 0x09 layout:
- // ship sprite center = (_74B6 + _74BA, _74B8 + _74BC)
- // cursor center = (_74BE, _74C0)
- // The flight sprite center already lives in _shipPos.
+ // Keep the flight sprite center and aim cursor separate.
const int16 shipBaseX = _shipPosX;
const int16 shipBaseY = _shipPosY;
const int32 liftTerm = (int32)_liftSmooth - 0x0F;
@@ -876,11 +857,7 @@ void InsaneRebel1::updateFlightVariantCursor() {
_rollAccum, _liftSmooth, bucket, _shipDirIndex, _perspectiveX, _perspectiveY);
}
-// preprocessMouseAxes â FUN_231BE (0x231BE) centered-axis output law, adapted to
-// the absolute 320x200 mouse space. The old DOS virtual-mouse/recenter
-// control path is intentionally not used. For opcode 0x0B, gamepad input uses
-// the 3DO standard-pad reticle model: axis samples move the reticle, and
-// releasing the pad holds the last reticle position.
+// Opcode 0x0B gamepad input moves the reticle and holds position on release.
bool InsaneRebel1::isOp0BReticleControlLevel() const {
switch (_currentLevel) {
case 1: // Level 2
@@ -1057,16 +1034,11 @@ void InsaneRebel1::preprocessMouseAxes(int16 &inputX, int16 &inputY, bool *usedJ
inputY = -inputY;
}
-// updateShipPhysics â FUN_1DEB5 (0x1DEB5). Accumulator-based position system.
-// Roll accumulator (_74CA) driven by input, position accumulators (_74C2/_74C6)
-// driven by roll + drift + cross-coupling. Ship position = base + accum >> 8.
+// Roll input drives position accumulators. Ship position = base + (accum >> 8).
void InsaneRebel1::updateShipPhysics() {
_frameCounter++;
- // HandleGameOp07_ShipFlight resets the ship accumulators and camera when
- // the GAME 0x07 frame counter enters at 0. Level 1 happened to work because
- // its runlevel code pre-initialized the same state, but later 0x07-driven
- // stages like L3 rely on the handler to do this reset itself.
+ // Reset ship accumulators and camera when a 0x07 stream starts.
if (_gameCounter == 0) {
_posAccumX = 0;
_posAccumY = 0;
@@ -1088,8 +1060,6 @@ void InsaneRebel1::updateShipPhysics() {
if (_damageCooldown > 0)
_damageCooldown--;
- // --- Step 1: Gameplay axes from FUN_231BE ---
- // HandleGameOp07_ShipFlight consumes the preprocessed axes in DAT_756C/756E,
// not raw mouse coordinates. Reuse the same centered-axis law here.
const uint16 effectiveOpcode = getEffectiveGameOpcode();
int16 inputX = 0;
@@ -1106,7 +1076,6 @@ void InsaneRebel1::updateShipPhysics() {
else if (_activeInputSource == kInputSourceJoystickDigital)
inputSourceName = "joystick-dpad";
- // --- Step 2: Roll accumulator (_74CA) ---
// 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.
@@ -1119,12 +1088,10 @@ void InsaneRebel1::updateShipPhysics() {
}
_rollAccum = CLIP<int32>(_rollAccum, -0x47F, 0x47F);
- // --- Step 3: Vertical smoothing (_74CE) ---
// Exponential decay toward -inputY
_liftSmooth += (-_liftSmooth - (int32)inputY) >> 1;
_liftSmooth = CLIP<int32>(_liftSmooth, -0x20, 0x20);
- // --- Step 4: Position accumulator deltas ---
// X delta: drift + slide coupling - cross-coupling
int32 rng = 100; // RandScaleByte(200), centered at 100
int32 crossTermX;
@@ -1147,42 +1114,30 @@ void InsaneRebel1::updateShipPhysics() {
int32 deltaY = (absRoll >> 1) + crossTermY;
- // --- Step 5: Update position accumulators ---
_posAccumX += deltaX;
_posAccumX = CLIP<int32>(_posAccumX, -0x8200, 0x8200);
_posAccumY += deltaY;
_posAccumY = CLIP<int32>(_posAccumY, -0x3200, 0x4600);
- // --- Step 6: Derive pixel position from accumulators ---
- // Original: _74BA = _74C2 >> 8, _74BC = _74C6 >> 8
// Ship position = base + offset
_shipPosX = kRA1CenterX + (int16)(_posAccumX >> 8);
_shipPosY = kRA1CenterY + (int16)(_posAccumY >> 8);
- // Clamp to screen bounds
+ // Clamp to screen bounds.
_shipPosX = CLIP<int16>(_shipPosX, kRA1MinX, kRA1MaxX);
_shipPosY = CLIP<int16>(_shipPosY, kRA1MinY, kRA1MaxY);
- // --- Step 7: Perspective offsets (SetCameraOffset) ---
- // FUN_1DEB5 computes these linearly from ship offsets:
- // viewX = clamp((_74BA + 0x20), 0, 0x40)
- // viewY = clamp((_74BC + 0x17), 0, 0x2E)
_perspectiveX = CLIP<int16>((int16)(_shipPosX - kRA1CenterX + 0x20), 0, 0x40);
_perspectiveY = CLIP<int16>((int16)(_shipPosY - kRA1CenterY + 0x17), 0, 0x2E);
- // Screen shake: when enabled, add random ±2 jitter (original SetCameraOffset at 0x22514)
if (_screenShakeEnabled) {
_perspectiveX = CLIP<int16>((int16)(_perspectiveX + (_vm->_rnd.getRandomNumber(4) - 2)), 0, 0x40);
_perspectiveY = CLIP<int16>((int16)(_perspectiveY + (_vm->_rnd.getRandomNumber(4) - 2)), 0, 0x2E);
}
- // FUN_1DEB5 updates the curve table via FUN_22549 after SetCameraOffset.
- // The full DOS path blends a few roll-history terms; use the current roll
- // accumulator so side-looking still bends the gameplay projection. DOS
- // negates before the arithmetic shift.
+ // Side-looking bends the gameplay projection from the current roll.
rebuildProjectionTable((int16)CLIP<int32>((-_rollAccum) >> 7, -0x1A, 0x1A), 0x1A);
- // --- Step 8: Direction sprite index (FUN_1DEB5 LAB_1e23e) ---
- // Horizontal component from _74CA (rollAccum):
+ // Horizontal component from the roll accumulator:
// |rollAccum| <= 0x80: center (0)
// rollAccum > 0x80: ((rollAccum - 0x80) >> 8) * 5 + 5 (right: 5,10,15,20)
// rollAccum < -0x80: ((abs(rollAccum) - 0x80) >> 8) * 5 + 25 (left: 25,30,35,40)
@@ -1195,8 +1150,7 @@ void InsaneRebel1::updateShipPhysics() {
hComponent = 0;
}
- // Vertical component from _74CE (liftSmooth):
- // (_74CE + 0x20) * 5 / 0x41 â 0..4 (5 rows)
+ // Vertical component from lift smoothing.
int vComponent = (_liftSmooth + 0x20) * 5 / 0x41;
if (_shipBank.numSprites > 0)
@@ -1215,16 +1169,12 @@ void InsaneRebel1::updateShipPhysics() {
_shipPosX, _shipPosY, _perspectiveX, _perspectiveY, _shipDirIndex,
_currentLevel, _flyControlMode, _activeGameOpcode);
- // --- Step 9: Damage/event bit synthesis + damage processing ---
- // RA1 FUN_1B297-style latches from GAME opcodes:
- // 0x5D latch 0xFFFF -> bit 0x40 (obstacle/contact)
// 0x5F non-zero + RNG -> bit 0x80 (projectile-like hit)
if (_gameLatch5D == 0xFFFF)
_damageFlags |= 0x40;
if (_gameLatch5F != 0 && _vm->_rnd.getRandomNumber((uint16)(_gameLatch5F - 1)) == 0)
_damageFlags |= 0x80;
- // Damage guard/mask from FUN_1DEB5: (_damageFlags & 0x96) != 0
// damageFlags & 0x96 = bits 1,2,4,7 = wall collisions (0x16) + projectile hit (0x80)
if (!_noDamage && (_damageFlags & 0x96) != 0 && _damageCooldown == 0 &&
_health >= 0 && _deathTimer <= 0) {
@@ -1237,7 +1187,6 @@ void InsaneRebel1::updateShipPhysics() {
if (_health < 0) {
_deathTimer = kDeathTimerInit;
- // g_deathCauseIndicator (0x772E) â set based on damage source
if (_damageFlags & 0x80)
_deathCauseIndicator = 2; // Projectile hit death
else
@@ -1246,12 +1195,11 @@ void InsaneRebel1::updateShipPhysics() {
_prevDamageFlags = _damageFlags;
_damageCooldown = kDamageCooldownInit;
- // HandleGameOp07_ShipFlight dispatches g_sfxDamageHit here.
+ // Damage hit sound.
playSfx(kSfxBoom, 127, 0);
_screenFlash = 3;
}
- // Latches are per-frame event inputs in the original pipeline.
_gameLatch5D = 0;
_gameLatch5F = 0;
@@ -1267,7 +1215,6 @@ void InsaneRebel1::updateShipPhysics() {
_score += _tuning.time;
}
- // Screen flash decay â screen shake follows flash (EnableScreenShake/DisableScreenShake at 0x224ED)
if (_screenFlash > 0) {
_screenFlash--;
_screenShakeEnabled = (_screenFlash > 0);
@@ -1276,8 +1223,6 @@ void InsaneRebel1::updateShipPhysics() {
// Clear per-frame damage flags
_damageFlags = 0;
- // --- Path branching detection ---
- // Original (FUN_1B297): at GAME counter 394 (0x18A), sets nextSceneA=0x67/nextSceneB=0x69.
// 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.
@@ -1302,8 +1247,7 @@ void InsaneRebel1::updateShipPhysics() {
_corridorLeftX, _corridorTopY, _corridorRightX, _corridorBottomY);
}
-// Port helper: FUN_1E6A7 computes this direction bucket inline before applying
-// the current frame's movement update.
+// Update turret sprite direction from the current frame's movement.
void InsaneRebel1::updateTurretShipDirection(int16 offsetY) {
int dir = 0;
if (_flyControlMode == 2) {
@@ -1341,13 +1285,8 @@ void InsaneRebel1::updateTurretShipDirection(int16 offsetY) {
}
void InsaneRebel1::getCollisionShipCenter(int16 &x, int16 &y) const {
- // Original 0x0D/0x0E collision compares script zones transformed by
- // FUN_223FE against the gameplay-window ship center (base center +
- // g_shipOffset). This is DOS screen space; render overlays add the viewport
- // offset separately when drawing into the larger source buffer.
- //
- // In Level 1 part 2, HandleGameOp0A_TurretVariant reuses _shipPos for the
- // targeting cursor, so collision must read the movement accumulator instead.
+ // Level 1 part 2 reuses _shipPos for the targeting cursor, so collision
+ // must read the movement accumulator instead.
if (_currentLevel == 0 && _flyControlMode == 2) {
x = (int16)(kRA1CenterX + (int16)(_posAccumX >> 8));
y = (int16)(kRA1CenterY + (int16)(_posAccumY >> 8));
@@ -1357,12 +1296,9 @@ void InsaneRebel1::getCollisionShipCenter(int16 &x, int16 &y) const {
}
}
-// updateTurretPhysics â FUN_1E6A7 (0x1E6A7), opcode 0x08 path.
-// Stage-2 cockpit mode uses different smoothing/clamps than FUN_1DEB5.
void InsaneRebel1::updateTurretPhysics() {
_frameCounter++;
- // FUN_1E6A7 consumes GAME field1 as frame counter (arg6 in dispatcher call).
// The 0x10/0x40 gates come from dispatcher arg4 (callback control bits),
// not from GAME payload fields.
const int32 counter = _gameCounter;
@@ -1383,7 +1319,6 @@ void InsaneRebel1::updateTurretPhysics() {
_damageCooldown = 0;
}
- // GAME 0x0A appears before 0x08 in turret/combat ANMs. The original
// draws shots/targeting and the ship from this pre-physics center, then
// updates the camera offset at the end of 0x08 for the final viewport copy.
const int16 preMoveOffsetX = (int16)(_posAccumX >> 8);
@@ -1394,7 +1329,6 @@ void InsaneRebel1::updateTurretPhysics() {
_turretFrameShipCenterY = (int16)(kRA1CenterY + preMoveOffsetY);
_turretFrameShipCenterValid = true;
- // Damage gate from FUN_1E6A7.
if (!_noDamage && _damageFlags != 0 && _damageCooldown == 0 && _health >= 0 && _deathTimer <= 0) {
if (_damageFlags == 0x80)
_health -= _tuning.shot;
@@ -1411,7 +1345,7 @@ void InsaneRebel1::updateTurretPhysics() {
_prevDamageFlags = _damageFlags;
_damageCooldown = kDamageCooldownInit;
- // HandleGameOp08_TurretFlight dispatches g_sfxDamageHit here.
+ // Damage hit sound.
playSfx(kSfxBoom, 127, 0);
_screenFlash = 3;
}
@@ -1424,9 +1358,7 @@ void InsaneRebel1::updateTurretPhysics() {
updateTurretShipDirection(preMoveOffsetY);
- // FUN_1E6A7 movement gate: counter > 8 or flags bit 0x40.
if (counter > 8 || (modeFlags & 0x40)) {
- // FUN_1E6A7 consumes DAT_756C/DAT_756E from the shared input bridge,
// not raw mouse coordinates.
int16 inputX = 0;
int16 inputY = 0;
@@ -1438,7 +1370,6 @@ void InsaneRebel1::updateTurretPhysics() {
const int16 rawInputY = inputY;
if (usedJoystick && _flyControlMode == 2) {
- // Extra concession for Level 1 part 2. The original 0x08 handler
// uses raw axes directly; do not damp Level 13's surface controls.
inputX /= 2;
inputY /= 2;
@@ -1477,12 +1408,9 @@ void InsaneRebel1::updateTurretPhysics() {
_perspectiveX = CLIP<int16>((int16)(offsetX + 0x20), 0, 0x40);
_perspectiveY = CLIP<int16>((int16)(offsetY + 0x17), 0, 0x2E);
- // FUN_1E6A7 rebuilds the side-look curve with a shallower table than the
- // main flight handler, derived directly from roll.
- // DOS negates before the arithmetic shift.
+ // Keep projection bending tied to turret roll.
rebuildProjectionTable((int16)((-_rollAccum) >> 9), 0x0D);
- // Regeneration + survival bonus via FUN_1BB0E call in this path.
if ((_frameCounter & 0x1F) == 0) {
if (_health >= 0 && _health < kMaxHealth)
_health++;
@@ -1500,7 +1428,6 @@ void InsaneRebel1::updateTurretPhysics() {
_damageFlags = 0;
}
-// New port helpers for the palette-flash block that is inline in FUN_1CDA7.
void InsaneRebel1::restoreScreenFlashPalette() {
if (!_screenFlashBasePaletteValid)
return;
@@ -1533,18 +1460,13 @@ void InsaneRebel1::updateScreenFlashPalette() {
_player->setPalette(flashPalette);
}
-// updateGameOp0BPhysics â FUN_1CDA7 (0x1CDA7). GAME opcode 0x0B handler.
// Uses 10-frame input history averaging instead of accumulators.
// Ship position = averaged input + center offset.
// Viewport = second history buffer for smooth camera scrolling.
void InsaneRebel1::updateGameOp0BPhysics() {
- // Enhanced controls keep the original 0x0B pipeline but average fewer
- // samples for responsiveness.
+ // Keep smoothing short for responsiveness.
const int gameOp0BSmoothWindow = 2;
- // RA1 FUN_1B297-style per-frame latches for 0x0B sections:
- // 0x5D latch 0xFFFF -> bit 0x40 (scripted obstacle/contact)
- // 0x5F non-zero + RNG -> bit 0x80 (scripted random hit)
const bool level15Phase1 = (_currentLevel == 14 && _levelGameplayPhase == 1);
const bool level15FinalPhase = (_currentLevel == 14 && _levelGameplayPhase == 2);
const bool level14Phase1 = (_currentLevel == 13 && _levelGameplayPhase == 1);
@@ -1562,7 +1484,6 @@ void InsaneRebel1::updateGameOp0BPhysics() {
_damageFlags |= 0x40;
if (_gameLatch5F != 0 && !level14Phase2) {
bool randomProjectileHit = false;
- // Original level loops spell these fixed-probability cases separately.
// The shared 0x0B path collapses them into one branch.
if (level14Phase1)
randomProjectileHit = (_vm->_rnd.getRandomNumber(3) == 0);
@@ -1592,19 +1513,15 @@ void InsaneRebel1::updateGameOp0BPhysics() {
const uint16 walkerFrame = (uint16)_gameCounter;
level8WalkerPlayerHit = hasLevel8WalkerPlayerHit(_levelRouteIndex, walkerFrame,
_perspectiveX, _perspectiveY);
- // RunLevel8Flow sets damage flag 0x20 when the AT-AT route contact
- // helper hits the player; boss damage uses _walkerHealth separately.
+ // Player collision and boss damage are tracked separately.
if (level8WalkerPlayerHit)
_damageFlags |= 0x20;
}
- // Health regeneration (FUN_1BB0E): +1 every 32 frames when alive
if (_health >= 0 && _health < kMaxHealth && (_frameCounter & 0x1F) == 0) {
_health++;
}
- // Damage application (FUN_1CDA7 lines 20-41)
- // Original 0x0B mapping: 0x80 -> +0x13, 0x40 -> +0x0F, 0x20 -> +0x11.
// No cooldown â all three damage types can stack each frame
if (!_noDamage && _damageFlags != 0 && _health >= 0 && _deathTimer < 1) {
const int16 oldHealth = _health;
@@ -1623,7 +1540,6 @@ void InsaneRebel1::updateGameOp0BPhysics() {
else
_deathCauseIndicator = 1;
}
- // FUN_1CDA7 dispatches g_sfxDamageHit, initialized from SYS/BOOM.SAD.
playSfx(kSfxBoom, 127, 0);
if (_currentLevel == 1) {
debugC(DEBUG_INSANE, "L2 player hit: frame=%u view=(%d,%d) latch=%u asteroid=%d flags=0x%02x health=%d->%d",
@@ -1640,7 +1556,6 @@ void InsaneRebel1::updateGameOp0BPhysics() {
_damageFlags = 0;
}
- // Latches are frame-local event inputs in the original pipeline.
_gameLatch5D = 0;
_gameLatch5F = 0;
@@ -1656,8 +1571,6 @@ void InsaneRebel1::updateGameOp0BPhysics() {
}
updateScreenFlashPalette();
- // --- Cursor and perspective smoothing (FUN_1CDA7) ---
- // _inputHistory* maps to 0x7580/0x7594, _viewHistory* to 0x75A8/0x75BC.
int16 inputX = 0;
int16 inputY = 0;
bool usedJoystick = false;
@@ -1766,10 +1679,7 @@ void InsaneRebel1::updateGameOp0BPhysics() {
_frameCounter++;
if (_currentLevel == 11) {
- // RunLevel12Flow checks the three attackers around frame 0x550. The
// event-mask bits map to L12PLAY's one-based FOBJ IDs 195, 197, and 199
- // in the port's frame-object helper. On failure the original keeps
- // pumping L12PLAY until frame 0x564, plays L12RETRY, then restarts
// L12PLAY without resetting health.
if (_levelGameplayPhase == 0 && _frameCounter == 0x550) {
const bool targetADestroyed = isFrameObjectPrimarySet(kRA1Level12TargetA);
@@ -1797,8 +1707,7 @@ void InsaneRebel1::updateGameOp0BPhysics() {
_vm->_smushVideoShouldFinish = true;
}
- // Level 4 Phase 2: enable torpedo mode at frontend/movie frame 0x3E and
- // finish as soon as the torpedo registers a hit. The DOS loop exits on killCount.
+ // Level 4 Phase 2: enable torpedo mode and finish on hit.
if (_currentLevel == 3 && _levelGameplayPhase == 2) {
if (_currentSmushFrame == 0x3E)
_gameplayFlags75ff |= 2;
@@ -1807,7 +1716,6 @@ void InsaneRebel1::updateGameOp0BPhysics() {
}
// Level 4 Phase 1: track shield generator hits per frame.
- // Original (RunLevel4Flow): g_recentKillObjectIdPlus1 checked every frame.
// When enough hits accumulated (>0x30), generator is "destroyed" (clear protectedTarget).
// When both destroyed for 60 frames, phase ends.
if (_currentLevel == 3 && _levelGameplayPhase == 1) {
@@ -1830,9 +1738,7 @@ void InsaneRebel1::updateGameOp0BPhysics() {
}
}
- // Level 5 Phase 1: DOS RunLevel5Flow exits L5PLAY only after killCount stays
- // above 2 for 20 frontend frames. That countdown is carried by the runlevel,
- // not by opcode 0x07 itself.
+ // Level 5 Phase 1: require sustained kill count before ending the phase.
if (_currentLevel == 4 && _levelGameplayPhase == 1 &&
_level5SuccessFramesRemaining > 0 && _killCount > 2 && !_vm->_smushVideoShouldFinish) {
_level5SuccessFramesRemaining--;
@@ -1840,12 +1746,9 @@ void InsaneRebel1::updateGameOp0BPhysics() {
_vm->_smushVideoShouldFinish = true;
}
- // Level 15 Phase 2: enable torpedo at frontend/movie frame 0x18A, expose
- // the protected target IDs used by the original flow, and finish when
- // g_gameplayPhaseFlags & 2 becomes set.
+ // Final torpedo phase.
if (_currentLevel == 14 && _levelGameplayPhase == 2) {
if (_currentSmushFrame == 0x18A) {
- // Original writes the 16-bit flags word: g_hudDisableFlags |= 0x210.
// Low byte bit 0x10 suppresses normal hit feedback in the torpedo phase;
// high byte bit 0x02 switches targeting/shot rendering to torpedoes.
_gameplayFlags75fe |= 0x10;
@@ -1870,28 +1773,14 @@ bool InsaneRebel1::isTorpedoModeActive() const {
if ((_gameplayFlags75ff & 0x2) == 0)
return false;
- // The original high-byte flag is only intentionally armed by the level 4
- // torpedo run and the level 15 exhaust-port run. Gate the port's rendering
- // and shot behavior to those phases so stale route/retry state cannot turn
- // ordinary laser sections into torpedo mode.
+ // Torpedo rendering is valid only during torpedo-run phases.
return (_currentLevel == 3 && _levelGameplayPhase == 2) ||
(_currentLevel == 14 && _levelGameplayPhase == 2);
}
-// Helper splits for the original on-foot GAME handlers:
-// HandleGameOp19_OnFootSequence (0x19) and HandleGameOp1A_OnFootVariant (0x1A).
-// On-foot handler for Level 9 (Stormtroopers). Character walks left/right, crosshair tracks mouse.
-//
-// Original has TWO separate variable pairs:
-// DAT_000041a0/41a2 = camera offset (SetCameraOffset, ProjectPointToScreen)
-// g_perspectiveX/Y = crosshair center (on-foot targeting)
-// Our _perspectiveX/_perspectiveY maps to the camera offset (DAT_000041a0/41a2).
-// The crosshair center (0xA3, 0x82) is a separate constant for on-foot mode.
-// Port split matching HandleGameOp19_OnFootSequence. The helper name is new to
-// this implementation; the original code dispatches the opcode handler directly.
+// On-foot handler for Level 9.
void InsaneRebel1::initOnFootSequence() {
- // --- First-frame initialization (0x19 counter==0) ---
if (!_onFootInitialized) {
_onFootInitialized = true;
_shipDirIndex = 15; // Center facing
@@ -1978,11 +1867,9 @@ void InsaneRebel1::preprocessOnFootAim(int16 &inputX, int16 &inputY, bool *usedJ
}
// Port split matching HandleGameOp19_OnFootSequence. The helper name is new to
-// this implementation; the original code dispatches the opcode handler directly.
void InsaneRebel1::updateOnFootSequence() {
initOnFootSequence();
- // --- 0x19: Post-draw character walk animation + damage ---
// Track fire button for animation
if (!_playerFired)
_onFootAnimCounter = 0;
@@ -2003,18 +1890,14 @@ void InsaneRebel1::updateOnFootSequence() {
_shipDirIndex = 15;
_onFootCharX += 0x3A;
} else if (_onFootAnimCounter < 5 && !_playerSecondaryHeld) {
- // Original calls QuantizeDirection8Way with the cursor and character
- // center, but the DOS on-foot axis is mirrored relative to the screen
- // coordinates used by this port. Use the visual screen-space vector so
- // L9PILOT.NUT poses 11..14 aim left and 16..19 aim right.
+ // Use the visual screen-space vector so aim poses face the right way.
const int16 centerX = _onFootCharX + kOnFootCenterX;
const int16 centerY = _onFootCharY + kOnFootCenterY;
const int16 aimDir = CLIP<int16>(
(int16)ra1ShotDirection(centerX, centerY, _shipPosX, _shipPosY), -4, 4);
_shipDirIndex = aimDir + 15;
} else {
- // Walking based on input direction. The 3DO second held button skips the
- // early aim-pose branch above and reaches these walk tests immediately.
+ // The secondary held button skips the early aim-pose branch above.
int16 inputX = (int16)(_shipPosX - kOnFootCenterX);
int16 inputY = (int16)(_shipPosY - kOnFootCursorBaseY);
if (!hasFrameGameOpcode(0x1A))
@@ -2025,19 +1908,14 @@ void InsaneRebel1::updateOnFootSequence() {
_shipDirIndex = 4; // Walk left
}
- // --- Scripted damage latches â damageFlags ---
- // L9 on-foot trooper shots use ordinary 0x5D event ids, gated by the
- // 0x5D object bitmask handler, then consumed by FUN_1ED95 through 0x74D4.
- // The ship loops keep narrower level-specific 0x5D damage rules.
+ // L9 on-foot trooper shots use ordinary 0x5D event ids.
if (_gameLatch5D != 0)
_damageFlags |= 0x40;
if (_gameLatch5F != 0 &&
_vm->_rnd.getRandomNumber((uint16)(_gameLatch5F - 1)) == 0)
_damageFlags |= 0x80;
- // --- Damage handling (from HandleGameOp19_OnFootSequence) ---
- // On-foot damage uses the same heavy-damage tuning byte as ship shot/collision
- // damage in the original, not the miss penalty.
+ // On-foot damage uses the same heavy-damage tuning byte as ship shot/collision.
if (!_noDamage && _damageFlags != 0 && _damageCooldown == 0 && _health >= 0 && _deathTimer < 1) {
const int16 oldHealth = _health;
_health -= _tuning.shot;
@@ -2054,13 +1932,8 @@ void InsaneRebel1::updateOnFootSequence() {
}
}
-// Port split matching HandleGameOp1A_OnFootVariant. The helper name is new to
-// this implementation; the original code dispatches the opcode handler directly.
void InsaneRebel1::updateOnFootAimVariant() {
- // --- 0x1A: Crosshair positioning (HandleGameOp1A_OnFootVariant) ---
- // DOS used virtual-mouse axes relative to the character offset. The
- // mouse and gamepad reticle are screen-space controls so the cursor remains
- // able to cross the whole playfield while Luke is standing at either side.
+ // Screen-space controls let the cursor cross the playfield from either side.
int16 inputX = 0, inputY = 0;
preprocessOnFootAim(inputX, inputY);
_shipPosX = CLIP<int16>((int16)(inputX + kOnFootCenterX), kOnFootCursorMinX, kOnFootCursorMaxX);
@@ -2103,7 +1976,6 @@ void InsaneRebel1::procSKIP(int32 subSize, Common::SeekableReadStream &b) {
}
void InsaneRebel1::handleGameOpcode5EReset(uint32 param1) {
- // RA1 dispatcher inline reset/init path (FUN_1BE1B case 0x5E).
// This is not a pure control-mode assignment.
if (_frameDispatchFlags & 0x40) {
debugC(DEBUG_INSANE, "GAME 0x5E: reset suppressed by dispatch flags=0x%02x",
@@ -2159,7 +2031,6 @@ void InsaneRebel1::handleGameOpcode5EReset(uint32 param1) {
if (_walkerRoundReplay && _currentLevel == 7)
_killCount = walkerReplayKillCount;
- // Field1 == 0 corresponds to baseline recenter behavior in the original.
if ((int32)param1 == 0) {
_perspectiveX = 0x20;
_perspectiveY = 0x17;
@@ -2174,7 +2045,6 @@ void InsaneRebel1::handleGameOpcode5EReset(uint32 param1) {
if ((int32)param1 == 0)
syncViewportOffset(true);
- // Original RunLevel8Flow initializes its separate g_level8HitboxBuffer
// after the first L8PLAY runtime reset. We fold that mask into the
// secondary half of _frameObjectState; route resumes preserve it.
if (_currentLevel == 7)
@@ -2215,21 +2085,18 @@ void InsaneRebel1::handleGameOpcode07ShipFlight(int32 subSize, Common::SeekableR
_activeGameOpcode = 0x07;
_frameGameOpcodeMask |= (1u << 0x07);
// Per-frame corridor data: f1=frame counter, f2=max frames, f3=drift bias, f4=unused
- // f1 is the original's _DAT_7740 (game frame counter)
// f3 is the drift/wind parameter combined with tuning table
_gameCounter = param1;
if (subSize >= 20) {
b.readUint32BE(); // f2 (max frames, unused in physics)
_driftParam = (int16)(int32)b.readUint32BE();
- b.readUint32BE(); // f4 (unused in original assembly)
+ b.readUint32BE();
debugC(DEBUG_INSANE, "GAME 0x07: counter=%d driftParam=%d", _gameCounter, _driftParam);
}
}
void InsaneRebel1::handleGameOpcode0DCorridor(int32 subSize, Common::SeekableReadStream &b, uint32 param1) {
// Corridor boundaries: per-frame flight corridor
- // Original params: left, top, WIDTH, HEIGHT (not right/bottom!)
- // FUN_1C54D computes center = (left+width/2, top+height/2), transforms, then checks edges.
if (subSize < 20)
return;
@@ -2240,7 +2107,6 @@ void InsaneRebel1::handleGameOpcode0DCorridor(int32 subSize, Common::SeekableRea
int16 centerX = corridorLeft + corridorWidth / 2;
int16 centerY = corridorTop + corridorHeight / 2;
- // DOS FUN_1C54D calls FUN_223FE here, which projects the scripted
// rectangle center into gameplay-window space before testing it against the
// ship center.
projectGameplayPoint(centerX, centerY);
@@ -2301,8 +2167,6 @@ void InsaneRebel1::handleGameOpcode0DCorridor(int32 subSize, Common::SeekableRea
}
void InsaneRebel1::handleGameOpcode0EZone(int32 subSize, Common::SeekableReadStream &b, uint32 param1) {
- // Secondary collision zone (FUN_1C6E9): AABB test, sets damageFlags bit 4 (0x10)
- // Original params: left, top, WIDTH, HEIGHT (same as 0x0D)
if (subSize < 20)
return;
@@ -2315,7 +2179,6 @@ void InsaneRebel1::handleGameOpcode0EZone(int32 subSize, Common::SeekableReadStr
int16 centerX = zoneLeft + zoneWidth / 2;
int16 centerY = zoneTop + zoneHeight / 2;
- // Same gameplay-window FUN_223FE transform as opcode 0x0D/FUN_1C54D.
projectGameplayPoint(centerX, centerY);
zoneLeft = centerX - zoneWidth / 2;
@@ -2343,13 +2206,11 @@ void InsaneRebel1::handleGameOpcode0EZone(int32 subSize, Common::SeekableReadStr
void InsaneRebel1::handleGameOpcode0BFirstPerson(int32 subSize, Common::SeekableReadStream &b, uint32 param1) {
_activeGameOpcode = 0x0B;
_frameGameOpcodeMask |= (1u << 0x0B);
- // GAME 0x0B per-frame handler (FUN_1CDA7).
- // field1 = frame counter, field2 = max frames
_gameCounter = param1;
if (subSize >= 20) {
- uint32 maxFrames = b.readUint32BE(); // field2 (max frames)
- b.readUint32BE(); // field3
- b.readUint32BE(); // field4
+ uint32 maxFrames = b.readUint32BE();
+ b.readUint32BE();
+ b.readUint32BE();
// RA1 scripts drive progression with GAME counters. Finish 0x0B-driven
// interactive videos once the script counter reaches the terminal frame.
@@ -2368,8 +2229,6 @@ void InsaneRebel1::handleGameOpcode0BFirstPerson(int32 subSize, Common::Seekable
}
void InsaneRebel1::handleGameOpcode5ATarget(int32 subSize, Common::SeekableReadStream &b, uint32 param1) {
- // Target detection â HandleGameOp5A (0x1C0EF). AABB from video stream.
- // Original checks event mask: if target already killed, skip to GOST update.
if (subSize < 24)
return;
@@ -2421,7 +2280,6 @@ void InsaneRebel1::handleGameCounterOpcode(uint32 opcode, int32 subSize, Common:
}
}
-// handleGameChunk â FUN_1BE1B (0x1BE1B). Central GAME opcode dispatcher.
// 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) {
@@ -2432,9 +2290,7 @@ void InsaneRebel1::handleGameChunk(int32 subSize, Common::SeekableReadStream &b,
uint32 param1 = b.readUint32BE();
_frameHasGameChunk = true;
- // FUN_1BE1B applies two global gates before the opcode switch. Bit 0 of
- // g_combatModeFlags skips gameplay dispatch entirely; bit 5 of g_hudDisableFlags
- // suppresses the handlers while still requesting HUD refresh for a few opcodes.
+ // Combat-mode flags can suppress handlers while still requesting HUD refresh.
if (_gameplayFlags75ff & 1) {
debugC(DEBUG_INSANE, "GAME 0x%02x: skipped by combat mode flags=0x%02x",
opcode, _gameplayFlags75ff);
@@ -2496,11 +2352,9 @@ void InsaneRebel1::handleGameChunk(int32 subSize, Common::SeekableReadStream &b,
}
}
-// processShot â FUN_1CCA0 (0x1CCA0). Spawns shot into explosion slot when fired.
// Called once per frame during interactive rendering.
void InsaneRebel1::processShot() {
if (_optRapidFire) {
- // 3DO FUN_0000c3a4 advances this before testing fire state; a fresh press
// fires immediately, while held repeats are gated to phase 0.
_rapidFirePhase++;
if (_rapidFirePhase > 2)
@@ -2516,7 +2370,6 @@ void InsaneRebel1::processShot() {
}
// On-foot mode: only spawn when in aiming stance (dirIndex 11-19) or flags force it.
- // Original: if (((10 < g_shipDirIndex) && (g_shipDirIndex < 0x14)) || ((DAT_000075fe & 8) != 0))
const uint16 effectiveOpcode = getEffectiveGameOpcode();
const bool onFootMode = (effectiveOpcode == 0x19 || effectiveOpcode == 0x1A);
if (onFootMode) {
@@ -2524,7 +2377,6 @@ void InsaneRebel1::processShot() {
return;
}
- // Find first available slot (timer < 1 or > 5), matching FUN_1CCA0.
int slot = -1;
for (int i = 0; i < kMaxShotSlots; i++) {
if (_shotSlots[i].timer <= 0 || _shotSlots[i].timer > 5) {
@@ -2536,10 +2388,7 @@ void InsaneRebel1::processShot() {
return;
}
- // Shot origin depends on game mode:
- // On-foot: character position (g_shipOffsetX + g_perspectiveX)
- // Turret: ship center
- // Flight: cursor position
+ // Shot origin depends on game mode.
const bool turretMode = (effectiveOpcode == 0x08 || effectiveOpcode == 0x0A);
int16 originX, originY;
if (onFootMode) {
@@ -2574,8 +2423,6 @@ void InsaneRebel1::processShot() {
}
}
-// checkTargetHit â FUN_1C0EF (0x1C0EF). AABB target detection with snap tolerance.
-// The original compares raw FOBJ bounds against the cursor after
// UnprojectScreenPoint(). Keep that separate from 0x0D/0x0E collision, which projects
// zones into gameplay-window screen space before comparing against the ship center.
void InsaneRebel1::checkTargetHit(int16 targetIdx, int16 left, int16 top, int16 right, int16 bottom) {
@@ -2630,13 +2477,10 @@ void InsaneRebel1::checkTargetHit(int16 targetIdx, int16 left, int16 top, int16
setGameplayCursor(effectiveOpcode, snappedX, snappedY);
}
- // DOS uses g_recentKillObjectIdPlus1 as a frame-wide latch. Once one
- // target is hit this frame, overlapping FOBJ layers must not consume the
- // same shot again.
+ // 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
- // Hit! Record in GOST slot for explosion animation
int gi = _gostSlotIdx;
_gostSlots[gi].targetId = targetIdx + 1;
_gostSlots[gi].frame = 0;
diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index cfa683a1f60..9636215b453 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -60,8 +60,6 @@ const int kRA1GameplayMouseSettleEdgeMargin = 16;
const int kRA1GameplayMouseMaxX = 319;
const int kRA1GameplayMouseMaxY = 199;
const uint32 kRA1GameplayMouseSettleExtendMs = 1000;
-// Original picker traversal uses fixed max indices, not strlen(): passcodes
-// stop at 0x1d and high-score names stop at 0x37.
const char kRA1TextEntryPickerChars[] = "^`_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const int kRA1PasscodePickerCount = 0x1d + 1;
const int kRA1NamePickerCount = 0x37 + 1;
@@ -146,7 +144,6 @@ static int getRebel1PasscodeDifficulty(int passwordIndex) {
}
static int getRebel1PasscodeStartLevel(int passwordIndex) {
- // Original main-menu control flow groups passcodes by difficulty:
// 1-3 resume after chapter 3, 4-6 after chapter 6, 7-9 after chapter 10,
// 10-12 after chapter 14, and 13-15 jump to the final ending sequence.
switch ((passwordIndex - 1) / 3) {
@@ -165,9 +162,6 @@ static int getRebel1PasscodeStartLevel(int passwordIndex) {
}
}
-// 3DO Launchme_ARMv4 FUN_000092c4 compares the entered passcode against 45
-// XOR-0xAA encoded 20-byte slots at 0x262FC. The caller's Ghidra disassembly at
-// 0x12A58 maps those slots in three-code difficulty groups to the next chapter.
const char *const kRebel1ThreeDOPasswords[] = {
"BOSSK", "BOTHAN", "BORDOK",
"ENGRET", "HERGLIC", "SKYNX",
@@ -331,9 +325,9 @@ void InsaneRebel1::beginTextEntry(bool passcodeMode) {
_textEntryPasscodeMode = passcodeMode;
_textEntryDone = false;
_textEntryCanceled = false;
- _textEntryPickerIndex = 3; // First letter in the original picker string.
+ _textEntryPickerIndex = 3;
_textEntryPickerOffsetX = 0;
- // The DOS buffers include a leading '<' font marker and cap strlen() at 8.
+ // Text buffers include a leading '<' font marker and cap strlen() at 8.
_textEntryMaxChars = 8;
_textEntryBuffer[0] = '\0';
@@ -584,8 +578,6 @@ bool InsaneRebel1::handleControllerMenuAxis(int16 oldAxisX, int16 oldAxisY) {
// RA1 maps stick axes to backend axis events for analog gameplay.
// Menus still need edge-triggered digital navigation from those same axes.
- // Match the original raw-input convention: positive Y is stick-down in
- // menus, and the Y-flip option reverses that interpretation.
const int oldX = getRebel1MenuAxisDirection(oldAxisX);
const int oldY = getRebel1MenuAxisDirection(oldAxisY);
const int newX = getRebel1MenuAxisDirection(_joystickAxisX);
@@ -617,7 +609,6 @@ void InsaneRebel1::openGameplayMainMenu() {
_player->unpause();
}
-// Extra feature, not in the original game: let the player navigate and
// activate the front-end menus with the mouse. Hovering highlights an item and a left
// click activates it (same as pressing accept). The item hit-rectangles mirror the
// highlight frames drawn by the render*Overlay() functions, so they share the
@@ -915,7 +906,6 @@ bool InsaneRebel1::notifyEvent(const Common::Event &event) {
handleMenuCommand(getRebel1MenuCommandFromKey(event.kbd)))
return true;
- // Shooting: mouse button during interactive gameplay â FUN_1CCA0 (0x1CCA0)
if (_interactiveVideoActive && !_menuActive) {
if (event.type == Common::EVENT_LBUTTONDOWN) {
_vm->_mouse.x = event.mouse.x;
@@ -1018,8 +1008,6 @@ void InsaneRebel1::drawMenuTitleText(byte *dst, int pitch, int width, int height
}
void InsaneRebel1::renderHighScoresOverlay(byte *dst, int pitch, int width, int height) {
- // --- TOP PILOTS high score display ---
- // Original renders over O1SCORE.ANM. Title appears after frame 20,
// entries fade in one per frame. We show all immediately.
const int titleW = getMenuTalkTextWidth("TOP PILOTS");
drawMenuTalkText(dst, pitch, width, height, getRebel1MenuCenteredX(titleW), 10, "TOP PILOTS");
@@ -1028,7 +1016,6 @@ void InsaneRebel1::renderHighScoresOverlay(byte *dst, int pitch, int width, int
const int y = 25 + i * 14;
// Name (left side)
drawFontBankString(dst, pitch, width, height, 40, y, _highScores[i].name);
- // Score + difficulty glyph (right side) â original format "<%ld %c"
// Difficulty byte 0/1/2 + 0x7B = '{','|','}' tech font glyphs (easy/normal/hard)
char scoreLine[32];
Common::sprintf_s(scoreLine, "<%ld %c",
@@ -1039,7 +1026,6 @@ void InsaneRebel1::renderHighScoresOverlay(byte *dst, int pitch, int width, int
}
void InsaneRebel1::renderOptionsOverlay(byte *dst, int pitch, int width, int height) {
- // --- Options submenu (matching original RunGameOptionsMenu) ---
_optTextEnabled = ConfMan.getBool("subtitles");
_optVolume = CLIP<int>(ConfMan.getInt("music_volume") / 2, 0, 127);
@@ -1078,7 +1064,6 @@ void InsaneRebel1::renderOptionsOverlay(byte *dst, int pitch, int width, int hei
}
void InsaneRebel1::renderLevelSelectOverlay(byte *dst, int pitch, int width, int height) {
- // --- Extra level select submenu, styled like the original frontend menus ---
const int titleW = getFontBankStringWidth("LEVEL SELECT");
drawMenuTitleText(dst, pitch, width, height, getRebel1MenuCenteredX(titleW), 15, "LEVEL SELECT");
@@ -1129,7 +1114,6 @@ void InsaneRebel1::renderLevelSelectOverlay(byte *dst, int pitch, int width, int
}
void InsaneRebel1::renderMainMenuItems(byte *dst, int pitch, int width, int height) {
- // --- Main menu ---
const char *const kMenuItems[kRA1MainMenuItemCount] = {
"START NEW GAME",
"GAME OPTIONS",
@@ -1418,7 +1402,6 @@ int InsaneRebel1::runLevelSelectMenu() {
}
void InsaneRebel1::showHighScores() {
- // Original plays O1SCORE.ANM with TOP PILOTS overlay, dismissable by any key.
_highScoresActive = true;
_menuActive = true;
_menuFrameCounter = 0;
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 6a2ce686fb6..afee7436c2a 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -187,8 +187,6 @@ void InsaneRebel1::loadTuningForLevel(int level) {
_tuning.levelPts = kTuningTable[l][d][10];
_tuning.bonus = kTuningTable[l][d][11];
_tuning.flags = kTuningTable[l][d][12];
- // initLevelFromTuning (0x13E7B) writes the 16-bit tuning flags word across
- // 0x75FE/0x75FF, so we must preserve both bytes.
resetGameplayFlagsFromTuning();
_protectedTargetA = 0;
_protectedTargetB = 0;
@@ -238,7 +236,6 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_turretFrameShipCenterValid = false;
_driftParam = 0;
- // Start new games on the least punishing original tuning by default.
_difficulty = 0;
loadTuningForLevel(0);
@@ -359,7 +356,6 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_optRapidFire = true;
_optVolume = _vm->_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) * 127 / Audio::Mixer::kMaxChannelVolume;
- // Default high scores â from DS:0x1D0/0x298/0x2C0
const struct { const char *name; int32 score; byte difficulty; } kDefaultScores[kHighScoreCount] = {
{"Vince", 10000, 2}, {"Tamlynn", 9000, 2}, {"Chip", 8000, 2},
{"Brett", 7000, 1}, {"Casey", 6000, 1}, {"Justin", 5000, 1},
@@ -434,7 +430,6 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
warning("InsaneRebel1::InsaneRebel1(): failed to load title font bank (TITLFONT/TALKFONT)");
}
- // FUN_1CB22 uses "<<" layer markers that resolve to TECHFONT in the original.
// 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);
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 8a17aae30e8..60c078c3779 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -51,27 +51,25 @@ enum RA1MenuCommand {
kRA1MenuCommandSelect6
};
-// Simple sprite bank for RA1 NUT files (ANIM v1 with odd-alignment padding).
-// Separate from NutRenderer to avoid modifying shared NUT parsing code.
+// Sprite bank for RA1 NUT files.
struct RA1Sprite {
int16 xoffs;
int16 yoffs;
uint16 width;
uint16 height;
- byte *data; // Decoded pixel data (width * height bytes, 0 = transparent)
+ byte *data;
};
struct RA1SpriteBank {
int numSprites;
RA1Sprite *sprites;
- byte *decodedData; // Single allocation for all decoded pixels
+ byte *decodedData;
uint32 decodedSize;
RA1SpriteBank() : numSprites(0), sprites(nullptr), decodedData(nullptr), decodedSize(0) {}
~RA1SpriteBank() { delete[] sprites; free(decodedData); }
};
-// RA1 coordinate constants (scaled from RA2's 424x260 â 384x242)
static const int16 kRA1CenterX = 160;
static const int16 kRA1CenterY = 100;
static const int16 kRA1MinX = 20;
@@ -83,10 +81,6 @@ static const int16 kRA1FocalY = 25;
int ra1ShotDirection(int16 x1, int16 y1, int16 x2, int16 y2);
-/**
- * Star Wars: Rebel Assault (RA1) game logic.
- * Adapts RA2 Handler 7 (ship flight) physics for RA1's 384x242 resolution.
- */
class InsaneRebel1 : public Insane, public Common::EventObserver {
public:
InsaneRebel1(ScummEngine_v7 *scumm);
@@ -107,9 +101,7 @@ public:
void handleGameChunk(int32 subSize, Common::SeekableReadStream &b,
byte *renderBitmap = nullptr, int width = 0, int height = 0);
bool isInteractiveVideoActive() const { return _interactiveVideoActive; }
- // True on touchscreen devices (e.g. Android). RA1 skips DOS-style cursor
- // warping/locking there; direct touch uses absolute aiming, while on-screen
- // gamepad/joystick input continues through the joystick path.
+ // Touch devices use absolute aiming instead of cursor locking.
bool isTouchscreenActive() const;
void setFrameHasGameChunk(bool hasGameChunk) { _frameHasGameChunk = hasGameChunk; }
void setCurrentSmushFrame(int32 frame);
@@ -138,28 +130,20 @@ public:
int codec, uint8 &ra1Param);
void resetFrameObjectState();
- // Game flow (matching original at 0x15597)
void runGame();
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false);
Common::Error loadGameState(int slot, bool startupLoad = false);
bool shouldAbortGameFlow() const { return _vm->shouldQuit() || _loadRequested; }
private:
- // Intro sequence: O1LOGO â O1OPEN (0x155ef-0x158f8)
void playIntroSequence();
void clearVideoBuffer();
- // Main menu loop on O1OPTION.ANM background (0x15968)
- // Returns: 1=Start New Game, 2=Game Options, 3=Enter Passcode,
- // 4=Level Select, 5=Continue Demo, 6=Exit
int runMainMenu();
int runLevelSelectMenu();
- // Level 1 flow (0x16100): hangar â CU1 â gameplay â CU2 â turret â end
- // Returns true if level completed, false if player quit
bool runLevel1();
- // Level 2 flow: NEW â INTRO â PLAY (asteroid dodge) â END/DEATH
bool runLevel2();
bool runLevel3();
bool runLevel4();
@@ -175,8 +159,7 @@ private:
bool runLevel14();
bool runLevel15();
- // Play a passive cinematic (no game callback, skippable)
- // startFrame > 0: fast-forward (decode without display) to that frame
+ // Passive, skippable cinematic playback.
void playCinematic(const char *filename, int32 startFrame = 0);
void playLevelTransitionCutscene(int level);
void playChapterCompleteCinematic(const char *filename, int16 unlockedChapter,
@@ -206,7 +189,6 @@ private:
void resetLevelAttemptState(int16 flyControlMode, int16 gameplayPhase,
int16 shipDirIndex = 17, bool resetAxisDeltaX = false);
- // Play interactive gameplay video (with ship physics + HUD)
void playInteractiveVideo(const char *filename, int32 startFrame = 0);
void resetInteractiveVideoAudio();
void preserveInteractiveVideoAudioState();
@@ -279,8 +261,6 @@ private:
bool areLevel14Phase1TargetsDestroyed() const;
bool areLevel14Phase2TargetsDestroyed() const;
- // Shooting pipeline â FUN_1CCA0 (0x1CCA0) shot spawner,
- // FUN_1C0EF (0x1C0EF) target detection, FUN_1C940 (0x1C940) shot processing
bool isTorpedoModeActive() const;
void processShot();
void checkTargetHit(int16 targetIdx, int16 left, int16 top, int16 right, int16 bottom);
@@ -303,44 +283,35 @@ private:
ScummEngine_v7 *_vm;
+ // Sprite and font banks.
RA1SpriteBank _shipBank;
- RA1SpriteBank _shipBankAlt; // Secondary ship bank (e.g. L1BANK2 mode-2 sprites)
- RA1SpriteBank _displayBank; // SYS/DISPLAY.NUT â bottom status bar
- RA1SpriteBank _titleFontBank; // SYS/TITLFONT.NUT â default subtitle/title layer
- RA1SpriteBank _hudFontBank; // RA1 HUD text glyphs (TECHFONT/TALKFONT via RA1 loader)
- RA1SpriteBank _techFontBank; // SYS/TECHFONT.NUT â targeting glyph layer ("<<" markers)
- RA1SpriteBank _bangBank; // LxBANG.NUT â impact/explosion sprites (10 frames)
- RA1SpriteBank _laserBank; // LxLASER.NUT â laser/shot effect sprites
- RA1SpriteBank _enemyLaserBank; // LxLASR2.NUT â incoming projectile sprites
- SmushFont *_menuFont; // Use engine text renderer for correct TALKFONT character mapping
-
- // RA1 screen dimensions (384x242)
+ RA1SpriteBank _shipBankAlt;
+ RA1SpriteBank _displayBank;
+ RA1SpriteBank _titleFontBank;
+ RA1SpriteBank _hudFontBank;
+ RA1SpriteBank _techFontBank;
+ RA1SpriteBank _bangBank;
+ RA1SpriteBank _laserBank;
+ RA1SpriteBank _enemyLaserBank;
+ SmushFont *_menuFont;
+
int _screenWidth;
int _screenHeight;
- // Ship screen position = kCenter + (accumulator >> 8)
- // Original: _DAT_74B6/_74B8 (base=160,100) + _DAT_74BA/_74BC (offset)
+ // Gameplay input and projection state.
int16 _shipPosX;
int16 _shipPosY;
- // GAME opcode 0x09 uses a separate aim cursor; other handlers target via _shipPos.
int16 _flightAimX;
int16 _flightAimY;
-
- // Direction sprite index (5x7 grid = 35 sprites, vDir*7 + hDir)
int16 _shipDirIndex;
- // Corridor boundaries (set by GAME opcode 0x0D, computed as left+width, top+height)
int16 _corridorLeftX;
int16 _corridorTopY;
int16 _corridorRightX;
int16 _corridorBottomY;
- // Physics state (accumulator-based, matching FUN_1DEB5)
- // _74CA: horizontal roll accumulator, driven by input * roll_tuning
int32 _rollAccum;
- // _74CE: vertical smoothing, exponential decay toward -inputY
int32 _liftSmooth;
- // _74C2/_74C6: position accumulators (32-bit), pixel offset = accum >> 8
int32 _posAccumX;
int32 _posAccumY;
int16 _turretFrameShipOffsetX;
@@ -349,31 +320,28 @@ private:
int16 _turretFrameShipCenterY;
bool _turretFrameShipCenterValid;
- // Per-frame drift bias from GAME 0x07 field3
int16 _driftParam;
- // Perspective view offsets (0x74B6/0x74B8: viewport scroll base)
int16 _perspectiveX;
int16 _perspectiveY;
static const int kProjectionTableSize = 80;
int16 _projectionCurveExtent;
int16 _projectionTable[kProjectionTableSize];
- // Input history buffers for 0x0B handler (FUN_1CDA7) â 10-frame averaging
static const int kInputHistorySize = 10;
- int16 _inputHistoryX[kInputHistorySize]; // 0x7580: horizontal input history
- int16 _inputHistoryY[kInputHistorySize]; // 0x7594: vertical input history
- int16 _viewHistoryX[kInputHistorySize]; // 0x75A8: viewport horizontal history
- int16 _viewHistoryY[kInputHistorySize]; // 0x75BC: viewport vertical history
- int16 _inputAxisDeltaX; // Current 0x0B horizontal input sample, before history averaging
- int16 _avgInputX; // smoothed horizontal input (clamped to [-0xA0, 0xA0])
- int16 _avgInputY; // smoothed vertical input (clamped to [-0x46, 0x41])
- int16 _joystickAxisX; // Rebel-specific left-stick X captured from keymapper axis events
- int16 _joystickAxisY; // Rebel-specific left-stick Y captured from keymapper axis events
+ int16 _inputHistoryX[kInputHistorySize];
+ int16 _inputHistoryY[kInputHistorySize];
+ int16 _viewHistoryX[kInputHistorySize];
+ int16 _viewHistoryY[kInputHistorySize];
+ int16 _inputAxisDeltaX;
+ int16 _avgInputX;
+ int16 _avgInputY;
+ int16 _joystickAxisX;
+ int16 _joystickAxisY;
uint32 _lastJoystickAxisEventTime;
- int16 _level2JoystickFilteredX; // Smoothed Level 2 analog X input
- int16 _level2JoystickFilteredY; // Smoothed Level 2 analog Y input
- int16 _gamepadAimAxisX; // Relative gamepad 0x0B aim in preprocessed input space
+ int16 _level2JoystickFilteredX;
+ int16 _level2JoystickFilteredY;
+ int16 _gamepadAimAxisX;
int16 _gamepadAimAxisY;
bool _gamepadAimActive;
// Suppress delayed lock/warp mousemove artifacts after centering interactive gameplay.
@@ -397,22 +365,21 @@ private:
void updateOnFootSequence();
void updateOnFootAimVariant();
void finishOnFootFrame();
- int16 _onFootCharX; // Character draw X (g_shipOffsetX in original)
- int16 _onFootCharY; // Character draw Y (g_shipOffsetY in original)
- int16 _onFootAnimCounter; // DAT_0000828a: fire animation counter
- bool _onFootInitialized; // First-frame init flag for 0x19
+ int16 _onFootCharX;
+ int16 _onFootCharY;
+ int16 _onFootAnimCounter;
+ bool _onFootInitialized;
- // Current level index (0-based: 0=LVL1, 1=LVL2, etc.)
+ // Runtime level state.
int _currentLevel;
- int _resumeLevel; // 1-based level used when saving/restoring RA1 progress
- int _activeSaveSlot; // Last manually saved or loaded slot; -1 means use autosave slot
- bool _loadRequested; // Runtime load requested while unwinding the current video flow
+ int _resumeLevel;
+ int _activeSaveSlot;
+ bool _loadRequested;
- // Intro title overlay (RunTwoLineTextSplash from original)
bool _introTextActive;
- int32 _introTextStartFrame; // revealBaseY: frame at which text begins appearing
- int32 _introTextEndFrame; // stopY: frame after which text stops
- int _introTextLevel; // index into kLevelTitles
+ int32 _introTextStartFrame;
+ int32 _introTextEndFrame;
+ int _introTextLevel;
void beginLevelTitleOverlay(int level);
void drawLevelTitleOverlay(byte *dst, int pitch, int width, int height, int32 curFrame, int32 maxFrame);
@@ -438,17 +405,13 @@ private:
int passwordIndex);
void drawChapterSummaryOverlay(byte *dst, int pitch, int width, int height, int32 curFrame, int32 maxFrame);
- // Control mode (from GAME opcode 0x5E)
+ // Interactive GAME chunk state.
int16 _flyControlMode;
- // Mode-2 emitter offsets used by FUN_1D79C when _DAT_75E4 == 2.
int16 _turretEmitterLeftX;
int16 _turretEmitterLeftY;
int16 _turretEmitterRightX;
int16 _turretEmitterRightY;
- // Last per-frame GAME opcode observed in the current playback stream.
- // Kept for legacy call sites; frame-accurate dispatch uses _frameGameOpcodeMask.
uint16 _activeGameOpcode;
- // GAME opcodes pre-scanned from the current frame before FOBJ dispatch.
uint32 _frameGameOpcodeHintMask;
uint32 _frameGameOpcodeMask;
bool _frameHasGameChunk;
@@ -456,62 +419,58 @@ private:
bool _gameOp0BPhysicsUpdatedThisFrame;
bool _gameOp0BOverlayRenderedThisFrame;
- // Difficulty (0=easy, 1=normal, 2=hard) â matches original DAT_22BC
int _difficulty;
- // Per-difficulty tuning (from assault_data_3.bin, indexed: difficulty * 0x28B + level * 0x1F)
- // Original game loads from C:\rebltune.txt; use a hardcoded table.
// 21 sub-levels (1A,1B,2,3,4A,4B,5A,5B,6,7,8,9A,9B,10,11,12,13,14A,14B,15A,15B)
struct TuningParams {
- int16 roll; // +0x05: horizontal speed/sensitivity
- int16 lift; // +0x07: vertical speed/sensitivity
- int16 slide; // +0x09: cross-axis coupling
- int16 drift; // +0x0B: drift/turbulence multiplier
- int16 snap; // +0x0D: hit radius for shooting targets
- int16 miss; // +0x0F: light/scripted damage; FUN_1CDA7 uses it for bit 0x40
- int16 wham; // +0x11: medium/contact damage; FUN_1CDA7 uses it for bit 0x20
- int16 shot; // +0x13: heavy/projectile damage; FUN_1CDA7 uses it for bit 0x80
- int16 kill; // +0x15: score per target kill
- int16 time; // +0x17: survival bonus (added every 32 frames)
- int16 levelPts; // +0x19: chapter completion bonus (RunChapterCompleteSummaryScreen)
- int16 bonus; // +0x1B: per-level bonus multiplier for kills/accuracy
- int16 flags; // +0x1D: level behavior flags (loaded into DAT_75FE at level init)
+ int16 roll;
+ int16 lift;
+ int16 slide;
+ int16 drift;
+ int16 snap;
+ int16 miss;
+ int16 wham;
+ int16 shot;
+ int16 kill;
+ int16 time;
+ int16 levelPts;
+ int16 bonus;
+ int16 flags;
};
TuningParams _tuning;
void loadTuningForLevel(int level);
void resetGameplayFlagsFromTuning();
- // Damage system (from Ghidra decompilation of FUN_1DEB5)
- int16 _health; // 0x7560: current health (init=98, negative=dead, max=98)
- int16 _lives; // 0x7562: remaining extra lives
- int _score; // 0x7564: current score
- int _prevScore; // 0x8288: previous score (for extra life bonus at 10k intervals)
- byte _damageFlags; // 0x74D4: per-frame collision bitmask (cleared each frame)
- byte _prevDamageFlags; // 0x74D6: previous frame's damage flags (for explosion direction)
- uint16 _gameLatch5D; // 0x75D2: GAME 0x5D latch (scene/obstacle/event trigger)
- uint16 _gameLatch5F; // 0x75D4: GAME 0x5F latch (probabilistic hit trigger)
- int16 _damageCooldown; // 0x74D8: invulnerability timer (10 frames after hit)
- int16 _deathTimer; // 0x756A: death animation countdown (30 on death)
- int16 _screenFlash; // 0x7736: screen flash timer on hit
+ int16 _health;
+ int16 _lives;
+ int _score;
+ int _prevScore;
+ byte _damageFlags;
+ byte _prevDamageFlags;
+ uint16 _gameLatch5D;
+ uint16 _gameLatch5F;
+ int16 _damageCooldown;
+ int16 _deathTimer;
+ int16 _screenFlash;
uint32 _frameCounter; // Gameplay handler frame accumulator
- int32 _currentSmushFrame; // Current frontend/movie frame (original DAT_7740)
- bool _screenShakeEnabled; // 0x41AC: when true, SetCameraOffset adds ±2 random jitter
+ int32 _currentSmushFrame;
+ bool _screenShakeEnabled;
byte _screenFlashBasePalette[0x300];
bool _screenFlashBasePaletteValid;
- byte _deathCauseIndicator; // 0x772E: non-zero = player died; selects death animation variant
- byte _hudRenderFlag; // 0x7600: 0xFF when HUD should render (set by combat mode handlers)
- byte _hudDirtyFlag; // 0x7601: 0xFF after HUD redraw (set by renderHUD)
- int16 _maxChapterUnlocked; // 0x7730: highest unlocked passcode slot (0=none)
- bool _unlockAllLevels; // Option: expose level select without passcodes
- bool _noDamage; // Option: suppress player damage
+ byte _deathCauseIndicator;
+ byte _hudRenderFlag;
+ byte _hudDirtyFlag;
+ int16 _maxChapterUnlocked;
+ bool _unlockAllLevels;
+ bool _noDamage;
static const int16 kMaxHealth = 98;
static const int16 kDeathTimerInit = 30;
static const int16 kDamageCooldownInit = 10;
enum { kNumLevels = 15 };
- // Streamed SMUSH audio
+ // Streamed SMUSH audio.
RebelAudio _audio;
bool _restoreInteractiveVideoAudioState;
int16 _savedInteractiveVideoTrackState[SMUSH_MAX_TRACKS];
@@ -532,33 +491,29 @@ private:
uint32 _sfxSize[kNumSfx];
Audio::SoundHandle _sfxHandles[kNumSfx];
- // True only while an interactive gameplay SMUSH is running.
bool _interactiveVideoActive;
bool _preserveInteractiveRuntimeState;
bool _interactiveVideoCheatSkipped;
RebelIOSGamepadControllerState _iosGamepadControllerState;
// Path branching for levels with left/right alternative videos.
- // Original sets nextSceneA/nextSceneB when GAME 0x07 counter == 394 (0x18A).
- // We check ship position at that counter value to decide left vs right path.
- static const int32 kPathBranchCounter = 394; // GAME 0x07 field1 value
- int32 _gameCounter; // GAME chunk field1/logical counter
- bool _pathBranchEnabled; // True when branching is active for this video
- bool _rightPathSelected; // True if player branched into L1PLAY1R
- int _levelRouteIndex; // Current mid-level route/segment for branching levels
- int _pendingRouteIndex; // Next route requested by original frame-branch logic
- int32 _pendingRouteStartFrame; // Resume/frame-gate target for branch-driven route switches
- int32 _pendingRouteCutoverFrame; // Delayed inline route splice frame (branchFrame + 7)
- int32 _pendingRouteVideoStartFrame; // L7 destination ANM-local frame after gate adjustment
- int16 _level7WarningFrames; // RunLevel7Flow local_38: 30-frame branch warning countdown
- int16 _level7WarningThreshold; // Collapses RunLevel7Flow local_40 into its DAT_2361 threshold
- int _levelGameplayPhase; // Level-local interactive phase (e.g. LVL4 PLAY1 vs PLAY2)
- // RunLevel14Flow queues L14PLY2B at L14PLAY2 maxFrame-0x0F.
+ static const int32 kPathBranchCounter = 394;
+ int32 _gameCounter;
+ bool _pathBranchEnabled;
+ bool _rightPathSelected;
+ int _levelRouteIndex;
+ int _pendingRouteIndex;
+ int32 _pendingRouteStartFrame;
+ int32 _pendingRouteCutoverFrame;
+ int32 _pendingRouteVideoStartFrame;
+ int16 _level7WarningFrames;
+ int16 _level7WarningThreshold;
+ int _levelGameplayPhase;
bool _level14Play2BSplicePending;
bool _level14Play2BSpliced;
int32 _level14Play2BSpliceFrame;
- // Main menu / options state
+ // Main menu and options state.
void runOptionsMenu();
int runPasscodeEntryDialog();
bool runHighScoreNameEntry();
@@ -580,35 +535,32 @@ private:
const char *getChapterCompletePassword(int passwordIndex) const;
bool _menuActive;
bool _menuConfirmed;
- int _menuSelection; // Visible main-menu row; mapped to return values by getMainMenuResultForSelection().
+ int _menuSelection;
int _menuFrameCounter;
- // Options submenu state â RunGameOptionsMenu (0x14B42)
static const int kOptionsItemCount = 9;
- bool _optionsActive; // True when showing options instead of main menu
- int _optionsSel; // 0..8 selected option row
- bool _levelSelectActive; // True when showing level-select submenu
- int _levelSelectSel; // 0=Level1 ... N-1=Back
- int _startLevel; // 1-based start level for "Start New Game"
-
- // Per-option state (matching original RunGameOptionsMenu globals)
- bool _optRookieOneFemale; // DAT_22c3: Rookie One gender
- bool _optMusicEnabled; // DAT_22b7: music on/off
- bool _optSfxEnabled; // DAT_22b8: sfx+voice on/off
- bool _optTextEnabled; // DAT_22b9: dialogue text on/off
- bool _optControlsYFlip; // DAT_22be: Y-axis inversion
- bool _optRapidFire; // Option: held fire keeps shooting
- int _optVolume; // DAT_22c1: master volume 0..127
-
- // High scores / TOP PILOTS display â data at DS:0x1D0
+ bool _optionsActive;
+ int _optionsSel;
+ bool _levelSelectActive;
+ int _levelSelectSel;
+ int _startLevel;
+
+ bool _optRookieOneFemale;
+ bool _optMusicEnabled;
+ bool _optSfxEnabled;
+ bool _optTextEnabled;
+ bool _optControlsYFlip;
+ bool _optRapidFire;
+ int _optVolume;
+
static const int kHighScoreCount = 10;
struct HighScoreEntry {
- char name[20]; // 0x14 bytes per entry (includes '<' prefix)
+ char name[20];
int32 score;
- byte difficulty; // 0/1/2 â tech font glyph '{','|','}' (easy/normal/hard)
+ byte difficulty;
};
HighScoreEntry _highScores[kHighScoreCount];
- bool _highScoresActive; // True when showing TOP PILOTS overlay
+ bool _highScoresActive;
void showHighScores();
int getMenuTalkTextWidth(const char *text);
void drawMenuTalkText(byte *dst, int pitch, int width, int height, int x, int y, const char *text);
@@ -618,7 +570,7 @@ private:
void renderLevelSelectOverlay(byte *dst, int pitch, int width, int height);
void renderMainMenuItems(byte *dst, int pitch, int width, int height);
- // Character picker shared by RunPasscodeEntryDialog and RunHighScoreNameEntry.
+ // Character picker shared by passcode and high-score entry.
static const int kTextEntryBufferSize = 20;
bool _textEntryActive;
bool _textEntryPasscodeMode;
@@ -631,56 +583,52 @@ private:
int _highScoreEntryIndex;
char _textEntryBuffer[kTextEntryBufferSize];
- // Shooting state â FUN_1CCA0 (0x1CCA0)
- bool _playerFired; // 0x7570: current fire-button state
- // 3DO ControlB/second held button used by L9 on-foot controls.
+ bool _playerFired;
+ // Secondary held button used by L9 on-foot controls.
bool _playerSecondaryHeld;
- int16 _fireCooldown; // 0x757C: previous-frame fire-button state (edge gate when rapid fire is off)
- int16 _rapidFirePhase; // 3DO FUN_0000c3a4: held-fire modulo-3 shot gate
- uint16 _gameplayFlags75fe; // 0x75FE: gameplay mode flags
- uint16 _gameplayFlags75ff; // 0x75FF: targeting / shot-style flags
+ int16 _fireCooldown;
+ int16 _rapidFirePhase;
+ uint16 _gameplayFlags75fe;
+ uint16 _gameplayFlags75ff;
- // Explosion shot slots (2 slots) â FUN_1CCA0 (0x1CCA0)
static const int kMaxShotSlots = 2;
struct ShotSlot {
- int16 timer; // 0x75E6: countdown (5 or 2, 0=inactive)
- int16 posX; // 0x75F2: cursor X at time of shot
- int16 posY; // 0x75F6: cursor Y at time of shot
- int16 centerX; // 0x75EA: perspective-adjusted X
- int16 centerY; // 0x75EE: perspective-adjusted Y
- int16 variant; // 0x75FA: emitter table selector (DAT_241F snapshot)
+ int16 timer;
+ int16 posX;
+ int16 posY;
+ int16 centerX;
+ int16 centerY;
+ int16 variant;
};
ShotSlot _shotSlots[kMaxShotSlots];
- int16 _shotAlternator; // 0x241F: alternates between 0/1
- bool _shotSideToggle; // 0x2423: 0x0B side-toggle for mode-1 beam emitters
-
- // Targeting state â FUN_1C0EF (0x1C0EF)
- int16 _targetProximity; // 0x7558: 0=none, 1=near, 2=on-target
- int16 _prevTargetProx; // 0x755A: previous frame's proximity
- int16 _targetAnimCounter; // 0x755C: lock-marker animation counter
- int16 _targetCount; // 0x7552: active targets this frame
- int16 _prevTargetCount; // 0x7554: previous frame target count
+ int16 _shotAlternator;
+ bool _shotSideToggle;
+
+ int16 _targetProximity;
+ int16 _prevTargetProx;
+ int16 _targetAnimCounter;
+ int16 _targetCount;
+ int16 _prevTargetCount;
static const int kMaxTargetBoxes = 20;
- int16 _targetBoxX[kMaxTargetBoxes]; // 0x74DA: per-target overlay X
- int16 _targetBoxY[kMaxTargetBoxes]; // 0x7502: per-target overlay Y
- int16 _targetBoxVariant[kMaxTargetBoxes]; // 0x752A: size/near bucket ('i' + bucket)
+ int16 _targetBoxX[kMaxTargetBoxes];
+ int16 _targetBoxY[kMaxTargetBoxes];
+ int16 _targetBoxVariant[kMaxTargetBoxes];
- // GOST hit animation slots (10 slots) â FUN_1C9CD (0x1C9CD)
static const int kMaxGostSlots = 10;
struct GostSlot {
- int16 targetId; // 0x23C3: target identifier (0=empty)
- int16 frame; // 0x23D7: animation frame (0-9, >=10 = done)
- int16 posX; // 0x239B: screen X
- int16 posY; // 0x23AF: screen Y
+ int16 targetId;
+ int16 frame;
+ int16 posX;
+ int16 posY;
};
GostSlot _gostSlots[kMaxGostSlots];
- int16 _gostSlotIdx; // 0x23EB: next slot to write (circular 0-9)
+ int16 _gostSlotIdx;
- int16 _killCount; // 0x75D0: targets destroyed this stage
- int16 _lastHitTarget; // 0x75D6: recent-kill latch, allows at most one hit per frame
- bool _frameObjectHitRevealPending; // DispatchSmushFrameChunks local_14 high-id reveal latch
+ int16 _killCount;
+ int16 _lastHitTarget;
+ bool _frameObjectHitRevealPending;
- // Incoming enemy projectile slots used by Level 13 RunLevel13Flow.
+ // Incoming enemy projectile slots.
static const int kMaxEnemyShotSlots = 5;
struct EnemyShotSlot {
int16 timer;
@@ -693,31 +641,25 @@ private:
};
EnemyShotSlot _enemyShotSlots[kMaxEnemyShotSlots];
- // Protected target IDs â 0x7732/0x7734 in original
// Targets listed here can be hit repeatedly (no event mask toggle).
// Used by Level 4 (shield generators) and Level 15 (torpedo targets).
- int16 _protectedTargetA; // 0x7732: protected objectId+1 (0=none)
- int16 _protectedTargetB; // 0x7734: protected objectId+1 (0=none)
+ int16 _protectedTargetA;
+ int16 _protectedTargetB;
- // Per-target hit counters for shield generator tracking (Level 4)
- int16 _shieldGenHitsA; // Hits on _protectedTargetA
- int16 _shieldGenHitsB; // Hits on _protectedTargetB
- int16 _level5SuccessFramesRemaining; // DOS RunLevel5Flow: 20-frame hold after the third kill
- int16 _level14SuccessFrames; // RunLevel14Flow: 60-frame hold after required targets are destroyed
+ // Per-target hit counters for shield generator tracking.
+ int16 _shieldGenHitsA;
+ int16 _shieldGenHitsB;
+ int16 _level5SuccessFramesRemaining;
+ int16 _level14SuccessFrames;
- // Level 15 torpedo success latch. The original derives this from
- // g_gameplayPhaseFlags bit 1, which is _frameObjectState primary byte 0 bit 0x02.
bool _torpedoFired;
- // Level 8 walker-specific state â RunLevel8Flow (0x18546)
- int16 _walkerHealth; // Walker health percentage (0-100), init=100
- int16 _walkerTimer; // Attack window countdown (100â0)
- int16 _walkerBranchChoice; // Directional choice: 0=none, 1=left, 2=right
- bool _walkerRoundReplay; // Preserve walker damage across a replayed L8 route
+ int16 _walkerHealth;
+ int16 _walkerTimer;
+ int16 _walkerBranchChoice;
+ bool _walkerRoundReplay;
- // Attack window frame numbers per route (3 routes à 3 windows)
- // Route 0: 2588/1709/262, Route 1: 2323/1444/-2, Route 2: 877/-2/-2
- // -2 = disabled (no window at that slot for this route)
+ // Attack window frame numbers per route.
static const int16 kWalkerAttackWindow1[3];
static const int16 kWalkerAttackWindow2[3];
static const int16 kWalkerAttackWindow3[3];
@@ -726,6 +668,6 @@ private:
byte _frameObjectState[kFrameObjectStateBytes];
};
-} // End of namespace Scumm
+} // namespace Scumm
#endif
diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index 8e031771cbf..cad22408e14 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -47,10 +47,7 @@ int ra1GameplayWindowOffsetX(const InsaneRebel1 *rebel1) {
if (!rebel1 || !rebel1->isInteractiveVideoActive())
return 0;
- // Ship/cursor/shot coordinates are in DOS's 320x200 gameplay window.
- // FUN_224FD crop emulation shifts them into the 384x242 source
- // buffer so the final source-window crop presents them at the same screen
- // position DOS used for gameplay and collision.
+ // Draw gameplay overlays into the cropped buffer's coordinate space.
switch (rebel1->getEffectiveGameOpcode()) {
case 0x07:
case 0x08:
@@ -159,7 +156,6 @@ const RA1Sprite *lookupBankGlyph(const RA1SpriteBank &bank, char ch) {
return &glyph;
}
-// Glyph markers in FUN_1C940/FUN_1CB22 go through DrawStringEx(..., flags=3),
// which centers the glyph and ignores the NUT x/y offsets. Use the same anchor
// rules here instead of the generic left-anchored text path.
void drawCenteredBankGlyph(const RA1SpriteBank &bank, byte *dst, int pitch, int width, int height,
@@ -202,8 +198,6 @@ int getBankStringWidth(const RA1SpriteBank &bank, const char *text) {
return w;
}
-// Approximate FUN_221B7/FUN_20BD3 space-advance behavior from available NUT glyphs.
-// The original reads per-font space width from metadata tables and caps it to 8.
int getBankSpaceAdvance(const RA1SpriteBank &bank) {
const int exclWidth = getBankStringWidth(bank, "!");
if (exclWidth <= 0)
@@ -247,7 +241,6 @@ void drawCenteredRebel1String(InsaneRebel1 *rebel1, byte *dst, int pitch, int wi
}
int getBankSpaceHeight(const RA1SpriteBank &bank) {
- // In FUN_221B7 line advance is derived from the layer's space-glyph height (+4).
// With current NUT decoding we approximate that using the '!' glyph (index 0).
if (bank.numSprites > 0) {
const RA1Sprite &glyph = bank.sprites[0];
@@ -257,7 +250,6 @@ int getBankSpaceHeight(const RA1SpriteBank &bank) {
return 8;
}
-// FUN_1C794: direction bucket in range -4..4 from two points.
int ra1ShotDirection(int16 x1, int16 y1, int16 x2, int16 y2) {
int dx = x2 - x1;
int dy = y1 - y2;
@@ -290,7 +282,6 @@ int ra1ShotDirection(int16 x1, int16 y1, int16 x2, int16 y2) {
return -4;
}
-// FUN_1CDA7 maps abs(FUN_1C794) to sprite base index: <=1 -> 0, ==2 -> 5, else -> 10.
int ra1ShotDirectionBucket(int dir) {
const int absDir = ABS(dir);
if (absDir <= 1)
@@ -300,8 +291,7 @@ int ra1ShotDirectionBucket(int dir) {
return 10;
}
-// QuantizeDirectionWithAxisFlags from RunLevel13Flow: returns one of nine
-// direction sprites and publishes DrawFobjGlyph flip flags.
+// Return one of nine direction sprites and publish glyph flip flags.
int ra1ProjectileDirectionWithFlags(int16 srcX, int16 srcY, int16 dstX, int16 dstY, uint16 &flags) {
int dx = srcX - dstX;
int dy = dstY - srcY;
@@ -342,7 +332,6 @@ struct RA1ShotEmitterPair {
int16 y2;
};
-// DAT_244A and DAT_251A in ASSAULT.EXE data section, used by FUN_1D79C.
const RA1ShotEmitterPair kRA1ShotEmitters244A[27] = {
{ 11, -11, -11, 0 }, { 16, -9, -16, -1 }, { 20, -6, -19, -3 }, { 20, -5, -21, -4 }, { -20, -6, 20, -5 },
{ -18, -9, 16, -1 }, { -13, -11, 13, 0 }, { -7, -13, 8, 2 }, { 1, -10, 3, 2 }, { 11, -16, -11, 4 },
@@ -361,7 +350,6 @@ const RA1ShotEmitterPair kRA1ShotEmitters251A[27] = {
{ -15, -14, 16, 5 }, { 0, -38, -14, 37 }
};
-// DAT_25EC/DAT_25F0 and DAT_28BC in ASSAULT.EXE. GAME opcode 0x09 uses these
// emitter offsets instead of the generic edge-beam fallback.
const RA1ShotEmitterPair kRA1FlightShotEmitters25EC[45] = {
{ -38, -14, 37, 6 }, { 37, -14, 37, 7 }, { 42, -11, -40, 11 }, { -37, -6, 38, 14 }, { -37, -5, 38, 15 },
@@ -399,7 +387,6 @@ const RA1ShotEmitterPair kRA1FlightShotEmitters28BC[45] = {
{ -18, 3, 5, -13 }, { -17, 3, 7, -14 }, { -18, 3, 8, -15 }, { -19, 3, 9, -11 }, { -16, 5, 11, -11 }
};
-// Small subset of FUN_20D43 draw flags used by RA1 shot sprites.
void renderSpriteWithFlags(byte *dst, int pitch, int width, int height,
int x, int y, const RA1Sprite &spr, uint32 flags) {
if (!spr.data || spr.width <= 0 || spr.height <= 0)
@@ -453,7 +440,6 @@ void renderSpriteWithFlags(byte *dst, int pitch, int width, int height,
}
}
-// Helper only: the original keeps this shot-sprite math inline in
// several GAME handlers. It is collapsed here because the direction/lerp/render
// sequence is identical for one-beam shot sprites.
void renderAimedShotSprite(byte *dst, int pitch, int width, int height,
@@ -481,8 +467,7 @@ void renderAimedShotPair(byte *dst, int pitch, int width, int height,
start2X, start2Y, targetX, targetY, lerp);
}
-// Helper for the 0x0B fallback edge-beam path. The original keeps
-// the bucket lookup inline with the renderer, but both left/right beams share it.
+// Beam emitters share the same bucketed shot sprite lookup.
void renderBucketedShotSprite(byte *dst, int pitch, int width, int height,
const RA1SpriteBank &laserBank, int startX, int startY, int targetX, int targetY,
int lerp, int frame, uint32 flags) {
@@ -502,10 +487,7 @@ void renderBucketedShotSprite(byte *dst, int pitch, int width, int height,
}
void InsaneRebel1::getTurretShipCenter(int16 &x, int16 &y) const {
- // Port helper: original FUN_1E6A7 keeps g_perspectiveX/Y at screen center
- // and draws the ship at g_perspective + g_shipOffset. In this port
- // _perspectiveX/Y stores the clamped camera offset passed to SetCameraOffset(),
- // so recover the ship center from the movement accumulators instead.
+ // Recover the ship center from the movement accumulators.
if (_turretFrameShipCenterValid) {
x = _turretFrameShipCenterX;
y = _turretFrameShipCenterY;
@@ -516,8 +498,6 @@ void InsaneRebel1::getTurretShipCenter(int16 &x, int16 &y) const {
y = (int16)(kRA1CenterY + (int16)(_posAccumY >> 8));
}
-// procPreRendering â Sets viewport window offset (FUN_224FD at 0x224FD).
-// RA1 decodes FOBJs at chunk coordinates, then displays a scrolled 320x200
// window inside the 384x242 framebuffer.
void InsaneRebel1::procPreRendering(byte *renderBitmap) {
_frameGameOpcodeMask = 0;
@@ -534,8 +514,6 @@ void InsaneRebel1::procPreRendering(byte *renderBitmap) {
(_activeGameOpcode == 0x07 || _activeGameOpcode == 0x08 ||
_activeGameOpcode == 0x09 || _activeGameOpcode == 0x0A ||
_activeGameOpcode == 0x0B);
- // Only gameplay handlers that actually execute FUN_224FD own the scrolling
- // 320x200 window inside the 384x242 buffer. Interactive movies with no
// GAME stream (for example LVL4/L4PLAY2.ANM) keep a static camera.
if (usePerspectiveViewport) {
ra1Player()->_ra1ViewportOffsetX = _perspectiveX;
@@ -563,9 +541,7 @@ void InsaneRebel1::syncViewportOffset(bool usePerspectiveViewport) {
ra1Player()->_ra1ViewportOffsetX = _perspectiveX;
ra1Player()->_ra1ViewportOffsetY = _perspectiveY;
- // SetCameraOffset() applies random shake to the camera value used by the
- // visible source-window origin. Store it once so FTCH restore, overlays, and
- // the final crop share one viewport origin for this frontend frame.
+ // Store shake-adjusted viewport once so overlays and crop share one origin.
if (_screenFlash > 0) {
ra1Player()->_ra1ViewportOffsetX += (int16)(_vm->_rnd.getRandomNumber(4) - 2);
ra1Player()->_ra1ViewportOffsetY += (int16)(_vm->_rnd.getRandomNumber(4) - 2);
@@ -583,7 +559,7 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
renderMainMenuOverlay(renderBitmap, pitch, width, height);
}
- // Intro title overlay (RunTwoLineTextSplash) â drawn during intro cinematics
+ // Intro title overlay.
if (_introTextActive && renderBitmap) {
int w = _player ? _player->_width : _screenWidth;
int h = _player ? _player->_height : _screenHeight;
@@ -634,17 +610,13 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
bool drawShipAfterGameplayOverlays = false;
bool drawGameOp0BTargetingAfterFetch = false;
if (gameOp0BMode) {
- // GAME 0x0B scrolling cockpit/surface handler â FUN_1CDA7.
if (!_gameOp0BPhysicsUpdatedThisFrame) {
updateGameOp0BPhysics();
_gameOp0BPhysicsUpdatedThisFrame = true;
syncViewportOffset(true);
}
- // DOS 0x0B loops test health after each frontend frame and leave the
- // interactive movie as soon as it drops below zero. Mirror that here so
- // GAME 0x0B chapters transition to their retry/death clips like
- // the other gameplay families do.
+ // 0x0B chapters transition to retry/death clips as soon as health drops.
if (_health < 0) {
_fireCooldown = _playerFired ? 1 : 0;
_vm->_smushVideoShouldFinish = true;
@@ -664,17 +636,13 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
if (onFootSequenceMode)
initOnFootSequence();
- // Original LVL9 frames dispatch GAME 0x1A before 0x19. That means the
// shot overlay uses the current crosshair/pose and is then covered by the
// character DrawFobjGlyph from 0x19; the 0x19 pose update is for the next
// frame. Keep that ordering explicit here.
if (onFootAimMode) {
shotOverlayHandled = true;
if (_health >= 0) {
- // HandleGameOp5A_ObjectOrSceneTrigger can snap g_shipPos to the
- // target center before the shot overlay runs. The player
- // defers GAME 0x1A until after FOBJ dispatch, so preserve that
- // snap instead of immediately replacing it with raw input again.
+ // Preserve target snap until deferred 0x1A input runs.
const bool preserveTargetSnap = (_targetProximity == 2 && _tuning.snap > 0);
const int16 snappedTargetX = _shipPosX;
const int16 snappedTargetY = _shipPosY;
@@ -710,7 +678,6 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
}
} else {
// Dispatch movement path by GAME handler family:
- // 0x08/0x0A -> FUN_1E6A7/FUN_1D79C (turret/cockpit)
// 0x07/0x09 -> flight-family handlers
if (turretMode) {
updateTurretPhysics();
@@ -732,10 +699,8 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
drawShipAfterGameplayOverlays = (flightMode || turretMode);
}
- // GAME handlers in the original update FUN_224FD during the same frame that
- // the new control state is computed. Sync the current frame's viewport window
- // before HUD/screen copy so overlays and final crop observe the same camera.
- // On-foot mode uses SetCameraOffset(0,0) â no viewport crop.
+ // Sync viewport before HUD/screen copy so overlays and crop share the camera.
+ // On-foot mode uses no viewport crop.
if (_player) {
if (onFootMode || (!gameOp0BMode && !turretMode && !flightMode)) {
syncViewportOffset(false);
@@ -744,9 +709,7 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
}
}
- // Assembly dispatch (FUN_1BE1B) only runs the targeting/shot overlay pipeline
- // in handlers 0x09/0x0A/0x0B/0x1A. LVL1 stage-2 works because the stream emits
- // both 0x0A and 0x08 in the same frame, not because 0x08 owns the overlay path.
+ // Targeting overlay is owned by handlers 0x09/0x0A/0x0B/0x1A.
const bool hasTargetingPipeline =
!shotOverlayHandled &&
(hasFrameGameOpcode(0x09) || hasFrameGameOpcode(0x0A) ||
@@ -763,12 +726,11 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
(allowImplicitGameplayMode && _activeGameOpcode == 0x0A);
renderShotOverlayPipeline(renderBitmap, pitch, width, height, true);
- // FUN_1D79C (GAME 0x0A) owns the cursor center in turret/combat mode.
- // The preceding overlay/shot pass uses the previous frame's cursor; the
- // handler then publishes the next cursor position from the current
- // pre-physics ship offset. The following 0x08 handler updates the camera
- // afterward, so source-space anchors and the final viewport crop
- // intentionally observe different moments in the frame.
+ // The preceding overlay/shot pass uses the previous frame's cursor; the
+ // handler then publishes the next cursor position from the current
+ // pre-physics ship offset. The following 0x08 handler updates the camera
+ // afterward, so overlay anchors and the viewport crop intentionally
+ // observe different moments in the frame.
if (turretTargetingMode) {
const int16 shipOffsetX = _turretFrameShipCenterValid ?
_turretFrameShipOffsetX : (int16)(_posAccumX >> 8);
@@ -780,8 +742,7 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
updateFlightVariantCursor();
}
} else if (!shotOverlayHandled) {
- // Keep lock/target accumulators quiescent when current handler doesn't
- // execute FUN_1C940/FUN_1CCA0/FUN_1C9CD/FUN_1CB22.
+ // Keep lock/target accumulators quiescent outside targeting handlers.
_targetProximity = 0;
_prevTargetProx = 0;
_targetCount = 0;
@@ -789,8 +750,7 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
_lastHitTarget = 0;
}
- // RunLevel7Flow tests route branches after PumpFrontendFrame, using the
- // decoded logical route timeline and updated GAME 0x09 cursor state.
+ // Route branches use the decoded timeline and updated 0x09 cursor state.
if (_currentLevel == 6)
checkDynamicLevelBranch(curFrame);
@@ -820,7 +780,6 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
renderLevelHitsOverlay(renderBitmap, pitch, width, height, 0x16, true);
// Level 8 (Imperial Walkers) â walker-specific state update + UI overlay.
- // In the original, RunLevel8Flow runs the walker logic inline in the per-frame
// game loop. We call it from procPostRendering when _currentLevel == 7.
if (_currentLevel == 7) {
updateLevel8WalkerState();
@@ -835,9 +794,7 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
}
// Helper that groups the common shot/lock overlay calls used by the
-// original GAME handlers. GAME 0x1A uses the same pipeline without the target
// box draw; 0x09/0x0A/0x0B include DrawTargetIndicators first. GAME 0x0B
-// defers FUN_1CB22 targeting until post-render so FTCH can restore cockpit
// 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) {
@@ -850,7 +807,6 @@ void InsaneRebel1::renderShotOverlayPipeline(byte *dst, int pitch, int width, in
processShot();
renderLaserShots(dst, pitch, width, height);
- // Timer decrement AFTER rendering (original decrements inside the render loop).
// This ensures timer==5 first frame is rendered with gun barrel offset and lerp=1.
for (int i = 0; i < kMaxShotSlots; i++) {
if (_shotSlots[i].timer > 0)
@@ -869,8 +825,6 @@ void InsaneRebel1::renderGameOp0BOverlayDuringChunk(byte *dst, int pitch, int wi
_gameOp0BOverlayRenderedThisFrame = true;
}
-// renderTargetBoxes â FUN_1C940 (0x1C940). Per-target green box overlays.
-// Original gates on g_hudDisableFlags (0x75FE) bit 1: skip when set (Hard difficulty).
void InsaneRebel1::renderTargetBoxes(byte *dst, int pitch, int width, int height) {
if (_gameplayFlags75fe & 2) {
_prevTargetCount = _targetCount;
@@ -899,8 +853,6 @@ void InsaneRebel1::renderTargetBoxes(byte *dst, int pitch, int width, int height
_targetCount = 0;
}
-// renderTargeting â FUN_1CB22 (0x1CB22). Targeting/lock-on indicator.
-// The original does not draw a hardcoded pixel cross; it renders glyph markers
// whose state depends on _targetProximity.
void InsaneRebel1::renderTargeting(byte *dst, int pitch, int width, int height) {
const char kRA1TorpedoIndicator[] = "<<d";
@@ -914,14 +866,11 @@ void InsaneRebel1::renderTargeting(byte *dst, int pitch, int width, int height)
stopSfx(kSfxLockOn);
if (markerBank.numSprites > 0) {
- // FUN_1CB22 can switch marker sets via DAT_75FF bit 1.
// Baseline RA1 targeting uses '^' and animation e..h.
const bool altMarkerSet = isTorpedoModeActive();
- // DAT_75FF bit 2 suppresses the fixed lock/readiness overlay.
if ((_gameplayFlags75ff & 0x4) == 0) {
// Lock indicator at fixed center positions:
- // FUN_1CB22 draws marker strings at (0xA0,0x78) and (0xA0,0x7E).
if (_targetProximity > 0) {
drawCenteredBankGlyph(markerBank, dst, pitch, width, height, overlayX + 0xA0, overlayY + 0x78, ']');
if (_targetProximity > 1)
@@ -929,9 +878,7 @@ void InsaneRebel1::renderTargeting(byte *dst, int pitch, int width, int height)
}
}
- // DAT_75FE bit 2 suppresses the cursor glyph entirely.
if ((_gameplayFlags75fe & 0x4) == 0) {
- // Pointer glyph at current aim position. Original uses two variants:
// default marker ('^' or 'x') and animated lock marker (e..h or y..|).
char marker[2] = { (char)(altMarkerSet ? 'x' : '^'), '\0' };
if (_targetProximity > 1) {
@@ -957,9 +904,7 @@ void InsaneRebel1::renderTargeting(byte *dst, int pitch, int width, int height)
_lastHitTarget = 0;
}
-// handleLevel14Play2BSplice â RunLevel14Flow (0x1ACD1) queues L14PLY2B.ANM
// from inside the L14PLAY2 loop when the current clip reaches maxFrame - 0x0F.
-// This helper is an implementation extraction; the original keeps the PlayAnmFile
// 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 ||
@@ -973,26 +918,19 @@ void InsaneRebel1::handleLevel14Play2BSplice(int32 curFrame, int32 maxFrame) {
_level14Play2BSplicePending = true;
_level14Play2BSpliceFrame = curFrame;
- // DOS queues the continuation from inside the active playback loop, so the
- // STOR/FTCH video state remains live across the L14PLAY2 -> L14PLY2B jump.
+ // Keep video state live across the L14PLAY2 -> L14PLY2B jump.
if (_player)
_player->setPreserveGameVideoStateOnRelease(true);
- // Original after PlayAnmFile("LVL14/L14PLY2B.ANM", 0x860, maxFrame-0x0F, 1, -1):
- // g_extendedPhaseFlags &= 0xFA;
- // DAT_7604 &= 0xBF;
- // g_extendedPhaseFlags is primary byte 1 because g_gameplayPhaseFlags starts
- // at byte 0 of the original frame-object state array.
+ // Clear carried target flags before switching clips.
clearFrameObjectPrimaryBits(1, 0x05);
clearFrameObjectPrimaryBits(2, 0x40);
_vm->_smushVideoShouldFinish = true;
}
-// renderGostScorePopup â Per-kill score glyph from RenderGostOverlaySlots (0x1C9CD).
// 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) {
- // Score-to-glyph mapping from original (0x1CA5D-0x1CACB)
char glyphChar = '\0';
uint16 scoreValue = (uint16)_tuning.kill;
if (scoreValue == 10) glyphChar = 0x72; // 'r'
@@ -1005,14 +943,12 @@ void InsaneRebel1::renderGostScorePopup(byte *dst, int pitch, int width, int hei
if (glyphChar == '\0')
return;
- // Original: DrawStringEx(buf, colorMap, centerX-4, centerY-frame, 1, 100, 3, scoreText)
// "<<{glyph}" string selects tech font layer via the << markup
char scoreText[4] = { '<', '<', glyphChar, '\0' };
drawFontBankString(dst, pitch, width, height,
centerX - 4, centerY - frame, scoreText);
}
-// renderGostSlots â FUN_1C9CD (0x1C9CD). Hit explosion animations at target positions.
// 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)
@@ -1038,8 +974,6 @@ void InsaneRebel1::renderGostSlots(byte *dst, int pitch, int width, int height)
renderSprite(dst, pitch, width, height, drawX, drawY, spr);
}
- // Per-kill score popup glyph â RenderGostOverlaySlots (0x1CA35)
- // Suppressed when DAT_75FF bit 3 is set.
if ((_gameplayFlags75ff & 8) == 0) {
renderGostScorePopup(dst, pitch, width, height,
overlayX + centerX, centerY, _gostSlots[i].frame);
@@ -1050,8 +984,6 @@ void InsaneRebel1::renderGostSlots(byte *dst, int pitch, int width, int height)
}
}
-// renderLevelHitsOverlay â RunLevel1Flow (0x16421-0x16438) and RunLevel11Flow
-// (0x1A07A-0x1A090) call FormatAndDrawText with "<<HITS %02d" at x=0x119.
void InsaneRebel1::renderLevelHitsOverlay(byte *dst, int pitch, int width, int height, int y,
bool screenSpace) {
if (_hudFontBank.numSprites <= 0 && _techFontBank.numSprites <= 0)
@@ -1069,7 +1001,6 @@ void InsaneRebel1::renderLevelHitsOverlay(byte *dst, int pitch, int width, int h
drawFontBankString(dst, pitch, width, height, drawX, drawY, hitsStr);
}
-// renderLevel5Part2Overlay â RunLevel5Flow (0x176D0-0x1777E) draws the
// part-2 instruction reveal, then switches to the live target count.
void InsaneRebel1::renderLevel5Part2Overlay(byte *dst, int pitch, int width, int height, int32 curFrame) {
if (_hudFontBank.numSprites <= 0 && _techFontBank.numSprites <= 0)
@@ -1102,7 +1033,6 @@ void InsaneRebel1::resetEnemyShotSlots() {
memset(_enemyShotSlots, 0, sizeof(_enemyShotSlots));
}
-// Port helper for Level 7 RunLevel7Flow. The original keeps this warning and
// single incoming projectile slot inline in the L7PLAY frontend loop.
void InsaneRebel1::renderLevel7RouteOverlays(byte *dst, int pitch, int width, int height) {
if (_currentLevel != 6 || !_interactiveVideoActive || _health < 0)
@@ -1166,7 +1096,6 @@ void InsaneRebel1::renderLevel7RouteOverlays(byte *dst, int pitch, int width, in
}
}
-// Port helper for Level 13 RunLevel13Flow. The original stores this state in
// five local stack arrays around the L13PLAY frontend loop, not in a named function.
void InsaneRebel1::renderLevel13EnemyShots(byte *dst, int pitch, int width, int height) {
if (_currentLevel != 12 || !_interactiveVideoActive || _health < 0)
@@ -1236,7 +1165,6 @@ void InsaneRebel1::renderLevel13EnemyShots(byte *dst, int pitch, int width, int
}
}
-// renderLaserShots â FUN_1CDA7/FUN_1D79C/HandleGameOp1A shot visual path.
void InsaneRebel1::renderLaserShots(byte *dst, int pitch, int width, int height) {
const char kRA1TorpedoTrailLeft[] = "<<&";
const char kRA1TorpedoTrailRight[] = "<<'";
@@ -1245,18 +1173,14 @@ void InsaneRebel1::renderLaserShots(byte *dst, int pitch, int width, int height)
if (_laserBank.numSprites <= 0 && !torpedoMode)
return;
- // DAT_2407 lookup used by FUN_1CDA7/FUN_1D79C for timer 1..5 interpolation.
// Entry 0 unused.
const int kShotLerpByTimer[6] = { 0, 8, 7, 6, 4, 0 };
- // DAT_2413: on-foot lerp table (timer 5 = 1, not 0 like flight mode).
const int kOnFootShotLerp[6] = { 0, 8, 7, 6, 4, 1 };
- // DAT_240e: gun barrel X offset indexed by shipDirIndex (for timer==5 first frame).
// Indices 11..19 are the active firing poses from L9PILOT.NUT.
const int16 kOnFootGunBarrelX[20] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, -56, -47, -23, -13, 0, 13, 30, 54, 59
};
- // DAT_2420: gun barrel Y offset indexed by shipDirIndex (for timer==5 first frame).
const int16 kOnFootGunBarrelY[20] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, -3, -19, -24, -30, -28, -30, -29, -20, -5
@@ -1312,9 +1236,6 @@ void InsaneRebel1::renderLaserShots(byte *dst, int pitch, int width, int height)
const int lerp = kShotLerpByTimer[timer];
if (turretMode) {
- // FUN_1D79C chooses emitters in two ways:
- // - DAT_75E4 == 2: use DAT_75DC..DAT_75E2 fixed offsets
- // - otherwise: table path DAT_244A/DAT_251A keyed by DAT_74D2
int start1X = 0;
int start1Y = 0;
int start2X = 0;
@@ -1414,8 +1335,6 @@ void InsaneRebel1::renderLaserShots(byte *dst, int pitch, int width, int height)
}
}
-// Level intro title data (from RunTwoLineTextSplash calls in original binary).
-// titleText is drawn at y=10, subtitleText at y=25 (matching original DrawUiString positions).
// revealStartFrame/revealEndFrame control the frame range during which text is visible.
const struct {
const char *titleText; // Top line (chapter number)
@@ -1423,7 +1342,7 @@ const struct {
int16 revealStartFrame; // Frame at which text begins appearing
int16 revealEndFrame; // Frame after which text stops
} kLevelTitles[] = {
- { "Chapter 1", "Flight Training", -80, -30 }, // Original computes totalFrames-0x50..totalFrames-0x1e
+ { "Chapter 1", "Flight Training", -80, -30 },
{ "Chapter 2", "Asteroid Field Training", 40, 100 },
{ "Chapter 3", "Planet Kolaador", 0, 50 },
{ "Chapter 4", "Star Destroyer Attack", 5, 50 },
@@ -1471,12 +1390,10 @@ void InsaneRebel1::drawLevelTitleOverlay(byte *dst, int pitch, int width, int he
const char *title = kLevelTitles[_introTextLevel].titleText;
const char *subtitle = kLevelTitles[_introTextLevel].subtitleText;
- // Center horizontally (matching original DrawUiString x_center=0xA0=160)
int titleW = getFontBankStringWidth(title);
int subtitleW = getFontBankStringWidth(subtitle);
int centerX = width / 2;
- // Original positions: title at y=10, subtitle at y=25 (0x19)
drawFontBankString(dst, pitch, width, height, centerX - titleW / 2, 10, title);
drawFontBankString(dst, pitch, width, height, centerX - subtitleW / 2, 25, subtitle);
}
@@ -1505,8 +1422,7 @@ void InsaneRebel1::beginChapterSummaryOverlay(int revealOffsetFromEnd, int stopO
}
}
-// Passcodes are kept in clear text. The DOS table is XOR-0xAA
-// encoded in 15 20-byte slots at DS:0x00A4.
+// Passcodes are stored in clear text.
static const char *const kChapterCompletePasswords[] = {
"FALCON", "BIGGS", "ACKBAR", "ANOAT", "KAIBURR",
"FORNAX", "YUZZEM", "MYNOCK", "BESPIN", "BRIGIA",
@@ -1520,9 +1436,6 @@ const char *InsaneRebel1::getChapterCompletePassword(int passwordIndex) const {
return kChapterCompletePasswords[passwordIndex - 1];
}
-// drawChapterSummaryOverlay â RunChapterCompleteSummaryScreen (0x15E42), shared by
-// the RA1 runlevel flows that call it. This helper is an implementation extraction;
-// the original pumps frontend frames from the runlevel after queueing the END ANM.
void InsaneRebel1::drawChapterSummaryOverlay(byte *dst, int pitch, int width, int height,
int32 curFrame, int32 maxFrame) {
if (!_chapterSummary.active || !dst || maxFrame <= 0)
@@ -1573,13 +1486,11 @@ void InsaneRebel1::drawChapterSummaryOverlay(byte *dst, int pitch, int width, in
}
}
-// drawFontBankString â FUN_221B7 (0x221B7), partial parity:
// supports '<'/'>' layer markup and layer-2 space handling used by RA1 HUD/targeting strings.
void InsaneRebel1::drawFontBankString(byte *dst, int pitch, int width, int height, int x, int y, const char *text) {
if (!text || !dst)
return;
- // Original FUN_221B7 layer mapping is table-driven at DAT_2D56 (0x406-byte entries).
// Current RA1 integration maps subtitle/HUD-relevant layers as:
// layer 0 -> TITLE font bank
// layer 1 -> TALK/HUD font bank
@@ -1601,7 +1512,6 @@ void InsaneRebel1::drawFontBankString(byte *dst, int pitch, int width, int heigh
if (bank.numSprites <= 0)
return;
- // FUN_221B7 special-case: when layer==2 and char is space, remap to '!'.
if (ch == ' ') {
if (techLayer) {
drawBankString(bank, dst, pitch, width, height, x, y, "!");
@@ -1618,7 +1528,6 @@ void InsaneRebel1::drawFontBankString(byte *dst, int pitch, int width, int heigh
}
}
-// getFontBankStringWidth â Width pre-pass from FUN_221B7 (0x221B7), including '<'/'>' markup.
int InsaneRebel1::getFontBankStringWidth(const char *text) {
if (!text)
return 0;
@@ -1667,10 +1576,7 @@ int InsaneRebel1::getFontBankLineAdvance(const char *text) {
return getBankSpaceHeight(bank) + 4;
}
-// renderShip â Ship sprite rendering from FUN_1DEB5 (0x1DEB5) at LAB_1e2b2.
-// Also used by FUN_1E6A7 (0x1E6A7) turret handler via FUN_20BD3.
void InsaneRebel1::renderShip(byte *dst, int pitch, int width, int height) {
- // From FUN_1DEB5 LAB_1e2b2: ship drawn when health >= 0 OR deathTimer > 20
// Hidden during last 20 frames of death sequence (deathTimer 20â0)
if (_health < 0 && _deathTimer <= 20)
return;
@@ -1702,8 +1608,6 @@ void InsaneRebel1::renderShip(byte *dst, int pitch, int width, int height) {
renderSprite(dst, pitch, width, height, drawX, drawY, spr);
}
-// renderExplosions â Explosion sprites from FUN_1DEB5 (0x1DEB5) LAB_1e185 (damage hit)
-// and LAB_1e0e3 (death shake). See also FUN_1CCA0 (0x1CCA0) explosion spawner.
void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height) {
if (_bangBank.numSprites <= 0)
return;
@@ -1723,7 +1627,6 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
shipScreenY = overlayY + centerY;
}
- // --- Death shake explosions (FUN_1DEB5 LAB_1e0e3) ---
// 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
@@ -1731,16 +1634,14 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
intensity = 20 - intensity; // Triangle: 0â10â0
// di = intensity * 4 + 1 (vertical scatter range)
- // si = -20 + intensity * 4 (horizontal scatter range, DAT_75d8 is 0)
int rangeY = intensity * 4 + 1;
int rangeX = -20 + intensity * 4;
if (rangeX < 1) rangeX = 1;
for (int i = 0; i < intensity; i++) {
- // Random sprite from bang bank (FUN_21db0(10))
int sprIdx = _vm->_rnd.getRandomNumber(_bangBank.numSprites - 1);
- // Random position around ship (matching assembly random scatter)
+ // Random position around ship.
int randX = (int)_vm->_rnd.getRandomNumber(rangeX * 2) - rangeX;
int randY = (int)_vm->_rnd.getRandomNumber(rangeY * 2) - rangeY;
@@ -1754,7 +1655,6 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
return;
}
- // --- Damage hit explosion (FUN_1DEB5 LAB_1e185) ---
// When alive, in cooldown, and bang bank loaded
if (_health >= 0 && _damageCooldown > 0) {
// Sprite index = 10 - damageCooldown (frames 0â9 as cooldown 10â1)
@@ -1762,7 +1662,6 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
if (sprIdx < 0 || sprIdx >= _bangBank.numSprites)
return;
- // Position at ship center (DAT_75d8 is always 0 in RA1)
int drawX = shipScreenX;
int drawY = shipScreenY;
@@ -1772,9 +1671,7 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
}
}
-// renderHUD â FUN_1BBCB (0x1BBCB). Status bar from DISPLAY.NUT with health/lives/score overlays.
void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
- // Extra life bonus: every 10,000 points (FUN_1BBCB lines 11-27)
if (_score / 10000 > _prevScore / 10000) {
_lives++;
playSfx(kSfxBonus, 127, 0);
@@ -1786,11 +1683,7 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
const RA1Sprite &bar = _displayBank.sprites[0];
- // DISPLAY.NUT sprite is 320Ã19 at xoffs=0, yoffs=176 in the original game.
- // FUN_224FD (0x224FD) sets the 320x200 window origin inside the 384x242 buffer.
- // FUN_1BBCB (0x1BBCB) HUD coordinates are screen-space, so when we emulate
- // perspective via source-window cropping, anchor HUD at window origin to keep
- // it fixed on-screen.
+ // Anchor HUD at the window origin so perspective cropping does not move it.
int hudOriginX = 0;
int hudOriginY = 0;
if (_interactiveVideoActive && _player) {
@@ -1800,12 +1693,10 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
const int hudX = hudOriginX + bar.xoffs;
const int hudY = hudOriginY + bar.yoffs;
- // FUN_1BBCB draws DISPLAY.NUT at x=5 with a 4..315 clip rect. The HUD
// masks/text below use the unshifted screen-space coordinate origin.
const int hudPlateX = hudX + 5;
const int hudPlateY = hudY;
- // DOS RA1 draws the HUD plate through DrawFobjGlyph(..., flags=0x181),
// which selects the opaque blit path. Keep zero-valued pixels black instead
// of treating them as transparent.
if (bar.data && bar.width > 0 && bar.height > 0) {
@@ -1841,7 +1732,6 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
}
}
- // Draw health bar from FUN_1BBCB (0x1BBCB) + FUN_21D66 (0x21D66):
// fill rect at (0x92-health, 8), width=health, height=5, color=0.
// This is a black "remaining health" fill over the HUD template.
{
@@ -1861,7 +1751,6 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
}
// Lives: black out excess pilot icons embedded in DISPLAY.NUT background.
- // Original FUN_1BBCB: FUN_21D66(buf, lives*10+186, 6, 51-lives*10, 9, 0, 320)
// Icons are 5 slots at x=186..236, each ~10px wide. Cover unused slots with black.
if (_lives >= 0 && _lives < 5) {
int coverX = hudX + _lives * 10 + 186;
@@ -1878,15 +1767,12 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
}
}
- // Score: FUN_1BBCB (0x1BBCB) -> FUN_21FAF (0x21FAF) with format at 0x6713: "<<%06ld".
- // Keep the leading "<<" markup so FUN_221B7-equivalent path selects TECH font layer.
if (_hudFontBank.numSprites > 0 || _techFontBank.numSprites > 0) {
char scoreStr[24];
Common::sprintf_s(scoreStr, "<<%06d", MAX<int>(_score, 0));
drawFontBankString(dst, pitch, width, height, hudX + 273, hudY + 5, scoreStr);
}
- // Low-health indicator from FUN_1BBCB (0x1BBCB):
// if (health < miss*2 || health < wham*2 || health < shot*2) and (frame & 8),
// draw warning glyph at (0x49, 0x07). Two variants:
// "<<[" when above critical thresholds, "<<\\" when critical.
@@ -1901,7 +1787,6 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
(_health > _tuning.miss) &&
(_health > _tuning.wham) &&
(_health > _tuning.shot);
- // FUN_1BBCB pushes string pointers 0x671b ("<<[") or 0x671f ("<<\\") into FUN_221B7.
const char *warningStr = aboveCritical ? "<<[" : "<<\\";
drawFontBankString(dst, pitch, width, height, hudX + 0x49, hudY + 0x07, warningStr);
if (!aboveCritical && ((_frameCounter & 0x7) == 0))
@@ -1909,10 +1794,9 @@ void InsaneRebel1::renderHUD(byte *dst, int pitch, int width, int height) {
}
}
- _hudDirtyFlag = 0xFF; // Mark HUD as freshly drawn (0x7601)
+ _hudDirtyFlag = 0xFF;
}
-// Attack window frame tables â RunLevel8Flow (0x18546), data at 0x236D/0x2373/0x2379.
// Each route has up to 3 attack windows. -2 means disabled.
const int16 InsaneRebel1::kWalkerAttackWindow1[3] = { 2588, 2323, 877 };
const int16 InsaneRebel1::kWalkerAttackWindow2[3] = { 1709, 1444, -2 };
@@ -1921,27 +1805,23 @@ 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() {
- // Walker health computation â RunLevel8Flow (0x18634-0x18655)
if (_walkerHealth >= 11) {
_walkerHealth = (int16)(100 - (_killCount + (_killCount >> 2)));
} else if (_walkerHealth > 0 && (_gameCounter & 3) == 0) {
_walkerHealth--;
}
- // Walker destroyed â exit interactive video (original loop: `while (sVar6 != 0)`)
if (_walkerHealth <= 0) {
_vm->_smushVideoShouldFinish = true;
return;
}
- // FUN_12FE1/FUN_130C9/FUN_13195 test whether the route-specific walker
// 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;
- // Attack window logic â RunLevel8Flow (0x18778-0x18B4A)
const int16 *windows[3] = {
&kWalkerAttackWindow1[route],
&kWalkerAttackWindow2[route],
@@ -1990,8 +1870,6 @@ void InsaneRebel1::updateLevel8WalkerState() {
}
}
- // At window boundary: decide route branch â RunLevel8Flow (0x18A7F-0x18B4A)
- // Original: left branches unless at window3, right branches unless at window2.
for (int w = 0; w < 3; w++) {
int16 windowEnd = *windows[w];
if (windowEnd < 0) continue;
@@ -2024,11 +1902,8 @@ void InsaneRebel1::updateLevel8WalkerState() {
}
}
-// renderLevel8Overlay â Walker-specific UI from RunLevel8Flow (0x18660-0x18A7E).
// Draws walker health %, attack timer, directional arrows, and target reticle.
-// Original RunLevel8Flow projects these fixed cockpit-panel points and then uses
// 1/4 parallax compensation. We draw into the 384x242 SMUSH buffer, so add the
-// viewport offset to convert the original screen-space result back to buffer
// coordinates before the final RA1 crop.
void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int height,
int viewportX, int viewportY) {
@@ -2118,8 +1993,6 @@ void InsaneRebel1::renderLevel8Overlay(byte *dst, int pitch, int width, int heig
}
}
-// renderSprite â Simplified version of FUN_20BD3 (0x20BD3) glyph/sprite renderer.
-// Original dispatches through full codec pipeline; this does flat pixel blit with transparency.
void InsaneRebel1::renderSprite(byte *dst, int pitch, int width, int height,
int x, int y, const RA1Sprite &spr) {
if (!spr.data || spr.width <= 0 || spr.height <= 0)
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index f9e5fecdd56..efe0be88e1c 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -141,9 +141,6 @@ static int calculateThresholdBonus(int kills, int perfectThreshold, int perKillT
return bonus;
}
-// ---------------------------------------------------------------------------
-// Game flow (matching original at 0x15597)
-// ---------------------------------------------------------------------------
// Play a passive cinematic (no game callback, skippable).
// startFrame > 0: fast-forward (decode without display/audio) to that frame.
@@ -155,9 +152,7 @@ void InsaneRebel1::playCinematic(const char *filename, int32 startFrame) {
SmushPlayer *splayer = _vm->_splayer;
_player = splayer;
restoreScreenFlashPalette();
- // DOS PlayFrontendAnmAndWait keeps pumping until frontend audio clears.
- // Rebel queues outlive SmushPlayer::play(), so clear stale
- // passive-cinematic audio before chaining the next ANM.
+ // Clear passive-cinematic audio before chaining the next ANM.
_audio.reset();
splayer->resetAudioTracks();
applyAudioOptions();
@@ -174,8 +169,7 @@ void InsaneRebel1::playCinematic(const char *filename, int32 startFrame) {
_introTextActive = false;
}
-// The original runlevel flows repeat this pattern around RunChapterCompleteSummaryScreen:
-// queue the END ANM, draw the summary while the frontend pumps, then award points/unlock.
+// 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,
@@ -273,9 +267,7 @@ void InsaneRebel1::resetLevelAttemptState(int16 flyControlMode, int16 gameplayPh
void InsaneRebel1::playLevelTransitionCutscene(int level) {
switch (level) {
case 4:
- // Original successor path 0x6d7d, reached after chapter 3 and by the
- // FALCON/BIGGS/WEDGE passcode group. This is separate from RunLevel4Flow
- // (0x6ee4), which starts with LVL4/L4INTRO.ANM.
+ // FALCON/BIGGS/WEDGE passcode group.
playCinematic("CUT1/C1BLOCK.ANM");
if (shouldAbortGameFlow())
break;
@@ -288,13 +280,9 @@ void InsaneRebel1::playLevelTransitionCutscene(int level) {
playCinematic("CUT1/C1DARTH2.ANM");
break;
case 7:
- // Original successor path 0x7d52, reached after chapter 6 and by the
- // chapter-6 passcode group, before RunLevel7Flow (0x7dcc).
playCinematic("CUT2/C2CUT2.ANM");
break;
case 11:
- // Original successor path 0x9f3a, reached after chapter 10 and by the
- // chapter-10 passcode group, before RunLevel11Flow (0x9f9f).
playCinematic("CUT3/C3BOOM.ANM");
break;
default:
@@ -321,38 +309,14 @@ void InsaneRebel1::clearVideoBuffer() {
free(clearBuffer);
}
-// Intro sequence (0x155ef-0x158f8):
-// 1. O1LOGO.ANM â LucasArts logo
-// 2. O1OPEN.ANM â Star Wars opening crawl
void InsaneRebel1::playIntroSequence() {
- // LucasArts logo (original: PUSH 0x57cc, CALL FUN_1BA32 with flags 0x0420)
playCinematic("OPEN/O1LOGO.ANM");
if (shouldAbortGameFlow())
return;
- // Star Wars opening crawl (original: PUSH 0x5800, CALL FUN_1BA32)
playCinematic("OPEN/O1OPEN.ANM");
}
-// Main menu on O1OPTION.ANM background (0x15968).
-// Original renders text overlay with 5 menu items via FUN_21F7A.
-// For now, we play the menu video as a passive cinematic (non-interactive)
-// and return "Start New Game" immediately.
-
-// Level 1 flow (0x16100-0x167A2, from disassembly):
-// 1. Load NUTs (L1BANK1, L1BANK2, L1EXPLD, L1BANG, L1LASER)
-// 2. L1HANGAR.ANM â Full hangar departure cutscene (782 frames, flags 0x0420)
-// 3. L1CU1.ANM â Pre-flight cutscene (flags 0x0400)
-// 4. L1PLAY1L.ANM â Stage 1 flight, hard/left path (788 frames)
-// At frame 394, if player steers right â L1PLAY1R (hard path, 396 frames)
-// 5. L1CU2.ANM â Mid-level cutscene
-// 6. L1PLAY2.ANM â Stage 2 turret
-// If score < 5 (0x75D0): L1RETRY â retry Stage 2
-// 7. L1END.ANM â Level complete
-// Death (health<0): L1CRASHA/B â lives check:
-// lives>0: L1NEW â jump back to Stage 1 (skip L1HANGAR/L1CU1)
-// lives==0: L1DEATH â return to menu
-
bool InsaneRebel1::runLevel1() {
_currentLevel = 0;
loadTuningForLevel(0);
@@ -398,9 +362,7 @@ bool InsaneRebel1::runLevel1() {
return false;
while (!shouldAbortGameFlow()) {
- // RunLevel1Flow calls L1PLAY2 with gameplay selector 1. This is
- // the "1B" tuning row: snap/kill values are enabled and the
- // lock/fire text overlay is suppressed.
+ // Stage 2 uses the 1B tuning row.
loadTuningForLevel(1);
_flyControlMode = 2;
_turretEmitterLeftX = 10;
@@ -455,7 +417,6 @@ bool InsaneRebel1::runLevel1() {
bool InsaneRebel1::runLevel2() {
_currentLevel = 1;
loadLevelSprites(2);
- // DOS RunLevel2Flow launches L2PLAY.ANM with gameplay selector 2.
loadTuningForLevel(2);
beginLevelTitleOverlay(1);
@@ -494,7 +455,6 @@ bool InsaneRebel1::runLevel2() {
bool InsaneRebel1::runLevel3() {
_currentLevel = 2;
loadLevelSprites(3);
- // DOS RunLevel3Flow launches L3PLAY.ANM with gameplay selector 3.
loadTuningForLevel(3);
beginLevelTitleOverlay(2);
@@ -533,7 +493,6 @@ bool InsaneRebel1::runLevel3() {
bool InsaneRebel1::runLevel4() {
_currentLevel = 3;
loadLevelSprites(4);
- // DOS RunLevel4Flow launches L4PLAY1.ANM with selector 4 and L4PLAY2.ANM with selector 5.
loadTuningForLevel(4);
beginLevelTitleOverlay(3);
@@ -551,9 +510,7 @@ bool InsaneRebel1::runLevel4() {
_killCount = 0;
_levelGameplayPhase = 0;
- // Phase 1: Destroy two shield generators.
- // Original sets DAT_00007732=0x39, DAT_00007734=0x3A â protected target IDs
- // that can be hit repeatedly without event mask toggle.
+ // Phase 1: destroy two shield generators that can be hit repeatedly.
_protectedTargetA = 0x39;
_protectedTargetB = 0x3A;
_shieldGenHitsA = 0;
@@ -588,8 +545,7 @@ bool InsaneRebel1::runLevel4() {
return false;
if (_health >= 0 && shieldGeneratorsDestroyed) {
- // Phase 2: torpedo run. The DOS loop enables torpedo mode at
- // frontend/movie frame 0x3E and exits early as soon as killCount becomes nonzero.
+ // Phase 2: torpedo run.
_activeGameOpcode = 0;
_gameLatch5D = 0;
_gameLatch5F = 0;
@@ -623,7 +579,6 @@ bool InsaneRebel1::runLevel4() {
bool InsaneRebel1::runLevel5() {
_currentLevel = 4;
loadLevelSprites(5);
- // DOS RunLevel5Flow passes segment 6 for L5PLAY and segment 7 for L5PLAY2.
loadTuningForLevel(6);
beginLevelTitleOverlay(4);
@@ -703,8 +658,7 @@ bool InsaneRebel1::runLevel5() {
bool InsaneRebel1::runLevel6() {
_currentLevel = 5;
loadLevelSprites(6);
- // DOS RunLevel6Flow starts L6PLAY with PlayAnmFile(..., 8), so chapter 6
- // uses tuning slot 8 ("6"), not the chapter-4B slot 5.
+ // Chapter 6 uses tuning slot 8, not the chapter-4B slot 5.
loadTuningForLevel(8);
beginLevelTitleOverlay(5);
@@ -756,7 +710,6 @@ bool InsaneRebel1::runLevel7() {
_currentLevel = 6;
loadLevelSprites(7);
loadRA1Nut("LVL7/L7LASER2.NUT", _enemyLaserBank);
- // DOS RunLevel7Flow starts L7PLAY1.ANM with initLevelFlag=9.
loadTuningForLevel(9);
beginLevelTitleOverlay(6);
@@ -794,9 +747,7 @@ bool InsaneRebel1::runLevel7() {
route = _pendingRouteIndex;
routeSourceFrame = _pendingRouteStartFrame;
routeVideoStartFrame = _pendingRouteVideoStartFrame;
- // DOS does not seek the destination route ANM here. The ANM-local
- // decision frame is used by the playback gate/cutoff; this implementation
- // starts at the already-advanced gate target after the cutover.
+ // The ANM-local decision frame gates the route cutover.
routeStartFrame = 0;
}
@@ -834,9 +785,7 @@ bool InsaneRebel1::runLevel8() {
_currentLevel = 7;
loadLevelSprites(8);
- // RunLevel8Flow starts L8PLAY.ANM with initLevelFlag=10, so the walker
- // chapter uses the original "8" tuning row while still keeping chapter
- // index 7 for assets and Level 8-specific runtime logic.
+ // The walker uses tuning slot 10.
loadTuningForLevel(10);
beginLevelTitleOverlay(7);
@@ -847,7 +796,6 @@ bool InsaneRebel1::runLevel8() {
while (!shouldAbortGameFlow()) {
resetLevelAttemptState(3, 0, 17, true);
- // Walker-specific state â RunLevel8Flow (0x18546)
_walkerHealth = 100;
_walkerTimer = 0;
_walkerBranchChoice = 0;
@@ -879,20 +827,14 @@ bool InsaneRebel1::runLevel8() {
break;
if (_pendingRouteIndex >= 0 && _pendingRouteIndex != route) {
- // RunLevel8Flow uses PlayAnmFile(..., g_frameCounter, 1, -1)
- // when it branches from one walker route to another. That wrapper
- // keeps the current ANM alive for seven more gameplay frames, then
- // opens the destination route while preserving the active state.
+ // Branch to the next walker route while preserving active state.
routeStartFrame = _pendingRouteStartFrame;
route = _pendingRouteIndex;
replayingRound = false;
continue;
}
- // RunLevel8Flow keeps pumping the active route while the walker
- // shield register is nonzero. Blocking SMUSH play returns
- // when one route pass ends, so explicitly replay that route and
- // preserve the accumulated walker damage.
+ // Replay the current route while preserving accumulated walker damage.
debugC(DEBUG_INSANE, "L8 replaying route=%d walkerHealth=%d killCount=%d",
route, (int)_walkerHealth, (int)_killCount);
routeStartFrame = 0;
@@ -925,10 +867,7 @@ bool InsaneRebel1::runLevel8() {
}
bool InsaneRebel1::runLevel9() {
- // DOS RunLevel9Flow calls RandScaleByte(2) three times before the intro.
- // That helper advances a byte seed with seed = seed * 9 + 0x35 and returns
- // (2 * seed) >> 8. Do not use the session RNG here: it can turn the
- // original right-side route into the capture/restart branch.
+ // Preserve the game's deterministic three-bit route selection.
uint8 originalRouteSeed = 0;
auto getOriginalRouteBit = [&originalRouteSeed]() {
originalRouteSeed = (uint8)(originalRouteSeed * 9 + 0x35);
@@ -939,7 +878,6 @@ bool InsaneRebel1::runLevel9() {
const int randPath3 = getOriginalRouteBit();
auto playLevel9PathSelector = [&](const char *filename) {
while (!shouldAbortGameFlow()) {
- // DOS zeros g_shipOffsetX around the 0x1A-only selector clips.
// Keep the selector cursor centered instead of inheriting the last
// walking offset from the previous stormtrooper segment.
_onFootCharX = 0;
@@ -966,7 +904,7 @@ bool InsaneRebel1::runLevel9() {
_currentLevel = 8;
loadLevelSprites(9);
- // DOS RunLevel9Flow alternates selectors 0x0B and 0x0C across its playable routes.
+ // Playable routes alternate tuning selectors 0x0B and 0x0C.
loadTuningForLevel(0x0B);
beginLevelTitleOverlay(8);
@@ -1117,7 +1055,6 @@ bool InsaneRebel1::runLevel9() {
bool InsaneRebel1::runLevel10() {
_currentLevel = 9;
loadLevelSprites(10);
- // DOS RunLevel10Flow starts L10PLAY.ANM with initLevelFlag=0x0D.
loadTuningForLevel(0x0D);
beginLevelTitleOverlay(9);
@@ -1150,14 +1087,11 @@ bool InsaneRebel1::runLevel10() {
return false;
}
-// Level 11 flow (RunLevel11Flow, 0x19F67): Yavin Training
// Turret-style level. Single interactive phase with kill-count retry.
-// Original: L11INTRO â L11PLAY (turret, killCount>4 to pass) â L11RETRY â retry/L11END
bool InsaneRebel1::runLevel11() {
_currentLevel = 10;
loadLevelSprites(11);
- // DOS RunLevel11Flow starts L11PLAY.ANM with initLevelFlag=0x0E.
- // Row 10 has zero roll/slide tuning, which prevents horizontal aiming.
+ // This tuning row has zero roll/slide, which prevents horizontal aiming.
loadTuningForLevel(0x0E);
beginLevelTitleOverlay(10);
@@ -1178,7 +1112,6 @@ bool InsaneRebel1::runLevel11() {
if (_health < 0)
break;
- // Original: killCount > 4 means pass
if (_killCount > 4 || _interactiveVideoCheatSkipped)
break;
@@ -1205,13 +1138,10 @@ bool InsaneRebel1::runLevel11() {
return false;
}
-// Level 12 flow (RunLevel12Flow, 0x1A2DD): TIE Attack
// Single interactive phase with mid-level retry mechanism.
-// Original: L12INTRO â L12PLAY â (retry at specific frame) â L12END
bool InsaneRebel1::runLevel12() {
_currentLevel = 11;
loadLevelSprites(12);
- // DOS RunLevel12Flow starts L12PLAY.ANM with initLevelFlag=0x0F.
loadTuningForLevel(0x0F);
beginLevelTitleOverlay(11);
@@ -1266,14 +1196,10 @@ bool InsaneRebel1::runLevel12() {
return false;
}
-// Level 13 flow (RunLevel13Flow, 0x1A6E3): Death Star Surface
-// Flight level with enemy projectile system (original has 5-slot projectile tracking).
-// Original: L13INTRO â L13PLAY â L13END/L13NEW/L13DEATH
bool InsaneRebel1::runLevel13() {
_currentLevel = 12;
loadLevelSprites(13);
loadRA1Nut("LVL13/L13LASR2.NUT", _enemyLaserBank);
- // DOS RunLevel13Flow starts L13PLAY.ANM with initLevelFlag=0x10.
loadTuningForLevel(0x10);
beginLevelTitleOverlay(12);
@@ -1306,14 +1232,11 @@ bool InsaneRebel1::runLevel13() {
return false;
}
-// Level 14 flow (RunLevel14Flow, 0x1ACB0): Surface Cannon
// Two interactive phases: L14PLAY (targeting cannons) + L14PLAY2 (exhaust port approach).
-// Original: L14INTRO â L14PLAY â L14PLAY2 â optional L14PLY2B splice
// â L14END/L14NEW/L14DEATH
bool InsaneRebel1::runLevel14() {
_currentLevel = 13;
loadLevelSprites(14);
- // DOS RunLevel14Flow uses selector 0x11 for L14PLAY and 0x12 for L14PLAY2.
loadTuningForLevel(0x11);
beginLevelTitleOverlay(13);
@@ -1387,10 +1310,7 @@ bool InsaneRebel1::runLevel14() {
_level14Play2BSplicePending = false;
level14Phase2Video = "LVL14/L14PLY2B.ANM";
- // DOS queues L14PLY2B from the L14PLAY2 loop with startFrame
- // equal to L14PLAY2's old timeline frame. L14PLY2B itself is the
- // continuation clip, so the port starts it from frame 0 but uses
- // the non-zero frame argument to preserve gameplay/video state.
+ // Preserve gameplay/video state across the continuation clip splice.
playInteractiveVideo(level14Phase2Video, spliceFrame);
if (shouldAbortGameFlow())
return false;
@@ -1426,14 +1346,11 @@ bool InsaneRebel1::runLevel14() {
return false;
}
-// Level 15 flow (RunLevel1GameLoop, 0x1B283): Death Star Trench
// Two interactive phases with mid-level cutscene.
-// Original: L15INTRO â L15PLAY1 (trench run) â L15INTR2 (torpedo lock cutscene)
// â L15PLAY2 (final approach + torpedo) â L15END1/L15NEW/L15DEATH
bool InsaneRebel1::runLevel15() {
_currentLevel = 14;
loadLevelSprites(15);
- // DOS RunLevel1GameLoop uses selector 0x13 for L15PLAY1 and 0x14 for L15PLAY2.
loadTuningForLevel(0x13);
beginLevelTitleOverlay(14);
@@ -1457,9 +1374,7 @@ bool InsaneRebel1::runLevel15() {
if (shouldAbortGameFlow())
return false;
- // Phase 2: final approach and torpedo shot. The DOS flow enables
- // torpedo mode at frontend/movie frame 0x18A and completes only after object-state
- // bit 0x7602 & 2 is set by the exhaust-port hit.
+ // Phase 2: final approach and torpedo shot.
_activeGameOpcode = 0;
_gameLatch5D = 0;
_gameLatch5F = 0;
@@ -1504,7 +1419,6 @@ bool InsaneRebel1::runLevel15() {
}
// Main game entry point â called from ScummEngine::go().
-// Matches original flow at 0x15597: intro â menu â level.
void InsaneRebel1::runGame() {
typedef bool (InsaneRebel1::*RunLevelMethod)();
const RunLevelMethod kLevelRunners[] = {
@@ -1611,7 +1525,6 @@ void InsaneRebel1::runGame() {
runOptionsMenu();
break;
case 3: {
- // Enter Passcode â original RunPasscodeEntryDialog starts the
// game from the decoded chapter when a valid passcode is entered.
const int passcodeLevel = runPasscodeEntryDialog();
if (passcodeLevel >= 1 && passcodeLevel <= numLevels)
@@ -1630,7 +1543,6 @@ void InsaneRebel1::runGame() {
}
case 4: {
// Level Select: extra start point. Continue through the
- // original successor flow so post-level cinematics still play.
int selectedLevel = runLevelSelectMenu();
if (selectedLevel >= 1 && selectedLevel <= numLevels)
runLevelsFrom(selectedLevel, true);
@@ -1638,7 +1550,6 @@ void InsaneRebel1::runGame() {
}
case 5:
// CONTINUE DEMO â attract mode.
- // Original shows TOP PILOTS (O1SCORE.ANM) then loops O1OPEN.ANM.
showHighScores();
if (!shouldAbortGameFlow())
playCinematic("OPEN/O1OPEN.ANM");
@@ -1713,7 +1624,6 @@ void InsaneRebel1::setupInteractiveVideoState(int32 startFrame) {
resetGamepadReticleAim();
}
_vm->_smushVideoShouldFinish = false;
- // Route resumes stay in the same gameplay flow in the original executable.
// Preserve the previous video/runtime state, but keep the destination clip
// fully interactive from its first visible frame.
splayer->setPreserveVideoStateOnNextPlay(preserveVideoState);
@@ -1730,9 +1640,7 @@ void InsaneRebel1::resolveSeek(const char *filename, int32 startFrame, int32 &vi
videoOffset = 0;
if (_currentLevel == 6 && level7RouteSplice) {
- // DOS opens the route ANM from the beginning, then the armed frame gate
- // suppresses until the adjusted target. With the delayed cutover,
- // the destination must advance by the source tail already displayed.
+ // Advance the destination route by the previous clip tail already displayed.
videoStartFrame = (_pendingRouteVideoStartFrame > 0) ?
_pendingRouteVideoStartFrame : 1;
videoOffset = findAnimFrameChunkOffset(_vm, filename, videoStartFrame);
@@ -1764,10 +1672,7 @@ void InsaneRebel1::resolveSeek(const char *filename, int32 startFrame, int32 &vi
_levelRouteIndex, (int)startFrame, (int)videoStartFrame, (unsigned)videoOffset);
}
} else if (_currentLevel == 13 && resumingRoute) {
- // RunLevel14Flow calls PlayAnmFile("LVL14/L14PLY2B.ANM", 0x860,
- // oldMaxFrame-0x0F, 1, -1). That frame number belongs to L14PLAY2's
- // timeline; L14PLY2B is already the continuation clip and starts at its
- // matching lead-in frame. Preserve the current state, but do not seek.
+ // L14PLY2B is already the continuation clip. Preserve state, but do not seek.
debugC(DEBUG_INSANE, "L14 splice: L14PLAY2 timelineFrame=%d -> L14PLY2B frame 0",
(int)startFrame);
}
@@ -1779,14 +1684,10 @@ void InsaneRebel1::captureInteractiveVideoInput() {
enableIOSGamepadController();
- // Center mouse, hide system cursor (we draw our own), lock mouse to window.
- // Some replays happen inside one original gameplay loop, so keep the current
- // input state instead of recentering between route clips.
+ // Center mouse, hide system cursor, and lock mouse to window.
if (!preserveInputState) {
_gameplayMouseSettleUntil = 0;
- // On touchscreen devices the DOS recenter-the-cursor aiming model does not apply;
- // warping/locking the system mouse there injects spurious motion that drifts
- // direct touch aiming and on-screen controls.
+ // Touch input uses absolute aiming, so avoid synthetic mouse motion there.
if (!isTouchscreenActive()) {
warpGameplayMouseNow(kRA1CenterX, kRA1CenterY);
_gameplayMouseSettleUntil = _vm->_system->getMillis() + kRA1GameplayMouseSettleMs;
More information about the Scummvm-git-logs
mailing list