[Scummvm-git-logs] scummvm master -> 793b12545ac64040201408a037438c2525579849
neuromancer
noreply at scummvm.org
Mon Jun 1 14:35:04 UTC 2026
This automated email contains information about 5 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
06333e5f71 SCUMM: RA1: make it touchpad friendly
790e05d816 SCUMM: RA2: fixed invalid palette handling in L2
ec305dee37 SCUMM: RA2: improved gamepad support
0025c4e108 SCUMM: RA2: match the joytick/gamepad input speed
793b12545a SCUMM: RA2: make sure input starts centered
Commit: 06333e5f71e11b2a9e9b332c2c42a89e87ed69a1
https://github.com/scummvm/scummvm/commit/06333e5f71e11b2a9e9b332c2c42a89e87ed69a1
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-01T13:46:31+02:00
Commit Message:
SCUMM: RA1: make it touchpad friendly
Changed paths:
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/runlevels.cpp
diff --git a/engines/scumm/insane/rebel1/iact.cpp b/engines/scumm/insane/rebel1/iact.cpp
index 68b0e2afac4..f3d2a507789 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -857,6 +857,16 @@ void InsaneRebel1::preprocessMouseAxes(int16 &inputX, int16 &inputY, bool *usedJ
return;
}
+ // On touchscreen devices aiming comes from the on-screen gamepad (joystick), never the
+ // absolute touch/cursor position; and we must not recenter the system cursor
+ // (smush_warpMouse below), which would inject spurious motion and drift the reticle.
+ if (isTouchscreenActive()) {
+ inputX = 0;
+ inputY = 0;
+ _mouseVirtualValid = false;
+ return;
+ }
+
int16 logicalX = (int16)CLIP<int>(_vm->_mouse.x, 0, 319);
int16 logicalY = (int16)CLIP<int>(_vm->_mouse.y, 0, 199);
diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index 35ecd26bc26..0dbc31baf07 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -577,7 +577,9 @@ bool InsaneRebel1::notifyEvent(const Common::Event &event) {
if (_vm->isPaused())
return false;
- if (event.type == Common::EVENT_MOUSEMOVE && !_mouseRecentering) {
+ // On touchscreen devices, finger drags arrive as mouse-move events; do not let them
+ // hijack RA1 into absolute-cursor aiming (aiming there comes from the on-screen gamepad).
+ if (event.type == Common::EVENT_MOUSEMOVE && !_mouseRecentering && !isTouchscreenActive()) {
_activeInputSource = kInputSourceMouse;
}
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 9b4a89b174f..8341fa0c1cd 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -200,6 +200,10 @@ void InsaneRebel1::loadTuningForLevel(int level) {
_tuning.time, _tuning.levelPts, _tuning.bonus, _tuning.flags);
}
+bool InsaneRebel1::isTouchscreenActive() const {
+ return g_system->hasFeature(OSystem::kFeatureTouchscreen);
+}
+
void InsaneRebel1::resetGameplayFlagsFromTuning() {
const uint16 tuningFlags = (uint16)_tuning.flags;
_gameplayFlags75fe = tuningFlags & 0x00FF;
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 8bedfdff0e0..0b7d2da299b 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -105,6 +105,10 @@ public:
void handleGameChunk(int32 subSize, Common::SeekableReadStream &b);
bool isInteractiveVideoActive() const { return _interactiveVideoActive; }
+ // True on touchscreen devices (e.g. Android). RA1 then aims from the on-screen gamepad
+ // joystick instead of the DOS absolute-mouse model, and skips cursor warping/locking,
+ // which otherwise inject spurious motion that drifts the reticle and on-screen buttons.
+ bool isTouchscreenActive() const;
void setFrameHasGameChunk(bool hasGameChunk) { _frameHasGameChunk = hasGameChunk; }
int getCurrentLevel() const { return _currentLevel; }
uint16 getActiveGameOpcode() const { return _activeGameOpcode; }
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index cb9c68b8058..f1d67c8e49b 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -1636,7 +1636,11 @@ void InsaneRebel1::captureInteractiveVideoInput() {
// Level 7 route splices happen inside one original gameplay loop, so keep
// the current input state instead of recentering between route clips.
if (!level7RouteSplice) {
- smush_warpMouse(160, 100, -1);
+ // On touchscreen devices the DOS recenter-the-cursor aiming model does not apply
+ // (aiming uses the on-screen gamepad); warping/locking the system mouse there only
+ // injects spurious motion that drifts the reticle and on-screen buttons.
+ if (!isTouchscreenActive())
+ smush_warpMouse(160, 100, -1);
_mouseVirtualRawX = 0x140;
_mouseVirtualRawY = 100;
_mouseVirtualPrevLogicalX = kRA1CenterX;
@@ -1644,7 +1648,8 @@ void InsaneRebel1::captureInteractiveVideoInput() {
_mouseVirtualValid = false;
}
CursorMan.showMouse(false);
- g_system->lockMouse(true);
+ if (!isTouchscreenActive())
+ g_system->lockMouse(true);
}
void InsaneRebel1::releaseInteractiveVideoInput() {
Commit: 790e05d8162689cbf73cb223a32bf9a912a87bc8
https://github.com/scummvm/scummvm/commit/790e05d8162689cbf73cb223a32bf9a912a87bc8
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-01T14:36:44+02:00
Commit Message:
SCUMM: RA2: fixed invalid palette handling in L2
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/render.cpp
engines/scumm/insane/rebel2/runlevels.cpp
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
engines/scumm/smush/smush_player.cpp
engines/scumm/smush/smush_player.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 114ecc49136..2ed784f0480 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -74,7 +74,7 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
_rebelOp6Initialized = false;
}
- // For Level 2 gameplay (Handler 8 only), restore the background BEFORE FOBJ decoding.
+ // For Level 2 handler 8 gameplay, restore the background BEFORE FOBJ decoding.
// The tiny FOBJ sprites (7x10, 9x38 pixels) only draw new sprite positions but don't
// clear old ones. By restoring the full background each frame, we ensure old sprite
// positions are erased before new ones are drawn.
@@ -2267,10 +2267,9 @@ bool InsaneRebel2::loadLevel2Background(byte *animData, int32 size, byte *render
_level2BackgroundLoaded = true;
foundBackground = true;
- // Copy to render bitmap immediately if provided.
- // Only copy when render buffer pitch is 320 (standard screen size).
- // For oversized buffers, the FOBJ/FETCH system handles background rendering.
- if (renderBitmap) {
+ // Handler 25 uses this buffer as a lookup mask; the retail code 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;
if (bufferPitch == 320) {
for (int by = 0; by < 200; by++) {
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index e9b35a408b6..c93f1c24aa0 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -2339,11 +2339,9 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
// Original: FUN_00403240 only runs handlers when DAT_0047a814 == 0.
processMouse();
- // NOTE: Level 2 background is drawn ONCE during IACT opcode 8 par4=5 processing
- // (in procIACT when the background ANIM is first loaded). The 0x08 video flag
- // (preserve background) prevents the frame buffer from being cleared, so the
- // background persists. FOBJ sprites (enemies) are then decoded on top by SMUSH.
- // We do NOT redraw the background here as that would overwrite FOBJ content.
+ // NOTE: Level 2 handler 8's background is restored in procPreRendering before
+ // SMUSH decodes the frame's FOBJ sprites. Handler 25 draws its corridor overlay
+ // from IACT opcode 6 instead. Redrawing either here would overwrite enemies.
// --- HUD Drawing Order (from FUN_004089ab / FUN_40D836 assembly analysis) ---
// Original assembly render order for handler 0x26:
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 93a763db401..673a67def8a 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -235,6 +235,9 @@ void InsaneRebel2::resetLevelAttemptState(int initialPhase) {
_playerShield = 255;
_playerDamage = 0;
_currentPhase = initialPhase;
+ resetDamageFlash();
+ _damageHighFlashCounter = 0;
+ _damageShakeCounter = 0;
_rebelAutopilot = 0;
_rebelDamageLevel = 0;
@@ -253,6 +256,28 @@ void InsaneRebel2::resetLevelPhaseState(bool clearEnemies) {
_rebelHitCounter = 0;
resetLevelWaveState();
+ delete _grd001Sprite;
+ _grd001Sprite = nullptr;
+ delete _grd002Sprite;
+ _grd002Sprite = nullptr;
+ _grdShotOriginTableLoaded = false;
+
+ static const int handler25FrameSlots[] = { 4, 6, 7, 10, 12, 13 };
+ for (uint i = 0; i < ARRAYSIZE(handler25FrameSlots); ++i) {
+ EmbeddedSanFrame &frame = _rebelEmbeddedHud[handler25FrameSlots[i]];
+ free(frame.pixels);
+ frame.pixels = nullptr;
+ frame.width = 0;
+ frame.height = 0;
+ frame.renderX = 0;
+ frame.renderY = 0;
+ frame.valid = false;
+ }
+
+ free(_level2Background);
+ _level2Background = nullptr;
+ _level2BackgroundLoaded = false;
+
if (clearEnemies)
_enemies.clear();
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 1299f5034b0..87fb6db2eef 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -387,6 +387,56 @@ void SmushPlayerRebel2::adjustGamePalette() {
memset(_deltaPal, 0, sizeof(_deltaPal));
}
+bool SmushPlayerRebel2::shouldLoadAnimHeaderPalette() const {
+ // Original RA2 AHDR handler skips the embedded palette when video flag 0x400 is set.
+ return (_curVideoFlags & 0x400) == 0;
+}
+
+void SmushPlayerRebel2::ra2HandleDeltaPalette(int32 subSize, Common::SeekableReadStream &b) {
+ if (subSize < 4) {
+ b.skip(subSize);
+ return;
+ }
+
+ b.readUint16LE();
+ const uint16 xpalCommand = b.readUint16LE();
+
+ if (xpalCommand != 0 && xpalCommand != 512) {
+ if (subSize > 4)
+ b.skip(subSize - 4);
+
+ for (int i = 0; i < 768; ++i) {
+ _shiftedDeltaPal[i] += _deltaPal[i];
+ _pal[i] = CLIP<int32>(_shiftedDeltaPal[i] >> 7, 0, 255);
+ }
+ setDirtyColors(0, 255);
+ return;
+ }
+
+ const int32 deltaBytes = 0x300 * 2;
+ if (subSize < 4 + deltaBytes) {
+ b.skip(subSize - 4);
+ return;
+ }
+
+ int16 deltaPal[0x300];
+ for (int i = 0; i < 768; ++i)
+ deltaPal[i] = b.readSint16LE();
+
+ if (xpalCommand == 512 && subSize >= 4 + deltaBytes + 0x300) {
+ b.read(_pal, 0x300);
+ } else if (subSize > 4 + deltaBytes) {
+ b.skip(subSize - 4 - deltaBytes);
+ }
+
+ for (int i = 0; i < 768; ++i) {
+ _shiftedDeltaPal[i] = _pal[i] << 7;
+ _deltaPal[i] = deltaPal[i];
+ }
+
+ setDirtyColors(0, 255);
+}
+
/**
* RA2-specific handleAnimHeader fixup: when AHDR reports 0x0 dimensions,
* use screen dimensions instead.
@@ -510,18 +560,31 @@ void SmushPlayerRebel2::ra2HandleTextResource(const char *str, int fontId, int c
/**
* RA2-specific buffer selection for non-standard FOBJ dimensions.
- * Returns the destination buffer to use and updates _dst, _width, _height.
+ * Returns true when the dimensions are valid and updates _dst, _width, _height as needed.
*/
-void SmushPlayerRebel2::ra2SelectFrameBuffer(int width, int height) {
+bool SmushPlayerRebel2::ra2SelectFrameBuffer(int width, int height) {
// Rebel2 uses a special buffer for all non-matching frames.
// Level 1: First frame is 424x260 (background), small sprites reuse same buffer
// Level 2: Uses virtual screen directly (handled below when _specialBuffer stays null)
- int bufSize = width * height;
- if (bufSize > _vm->_screenWidth * _vm->_screenHeight) {
+ const int64 bufSize64 = (int64)width * height;
+ if (width <= 0 || height <= 0 || bufSize64 > INT_MAX) {
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Skipping invalid FOBJ dimensions %dx%d", width, height);
+ return false;
+ }
+
+ const int bufSize = (int)bufSize64;
+ const int screenSize = _vm->_screenWidth * _vm->_screenHeight;
+ if (bufSize > screenSize) {
// Frame is larger than screen - need special buffer
if (_specialBuffer == nullptr || bufSize > _specialBufferSize) {
+ byte *newSpecialBuffer = (byte *)malloc(bufSize);
+ if (newSpecialBuffer == nullptr) {
+ warning("SmushPlayerRebel2::ra2SelectFrameBuffer: Failed to allocate %d bytes for FOBJ %dx%d",
+ bufSize, width, height);
+ return false;
+ }
free(_specialBuffer);
- _specialBuffer = (byte *)malloc(bufSize);
+ _specialBuffer = newSpecialBuffer;
_specialBufferSize = bufSize;
_width = width;
_height = height;
@@ -531,7 +594,7 @@ void SmushPlayerRebel2::ra2SelectFrameBuffer(int width, int height) {
}
}
- if (bufSize > _vm->_screenWidth * _vm->_screenHeight &&
+ if (bufSize > screenSize &&
_specialBuffer != nullptr && _specialBufferSize >= bufSize) {
_dst = _specialBuffer;
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using _specialBuffer for oversized FOBJ %dx%d", width, height);
@@ -548,6 +611,8 @@ void SmushPlayerRebel2::ra2SelectFrameBuffer(int width, int height) {
width, height);
}
}
+
+ return true;
}
/**
@@ -656,8 +721,7 @@ void SmushPlayerRebel2::handleGameParseNextFrame() {
bool SmushPlayerRebel2::handleGameFrameBufferSelect(int codec, int width, int height) {
if ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)) {
- ra2SelectFrameBuffer(width, height);
- return true;
+ return ra2SelectFrameBuffer(width, height);
}
return false;
}
@@ -719,6 +783,12 @@ bool SmushPlayerRebel2::handleGameSkipChunk(uint32 subType, int32 subSize, Commo
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleGameSkipChunk: SKIP consumed chunk %s frame=%d", tag2str(subType), _frame);
return true;
}
+
+ if (subType == MKTAG('X','P','A','L')) {
+ ra2HandleDeltaPalette(subSize, b);
+ return true;
+ }
+
return false;
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 7af86cf66f0..084a727fb3d 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -42,6 +42,7 @@ protected:
bool shouldAlwaysShowSubtitles() const override { return true; }
SmushFont *getGameFont(int font) override;
void adjustGamePalette() override;
+ bool shouldLoadAnimHeaderPalette() const override;
bool handleGameAnimHeader(byte *headerContent) override;
bool handleGameSetupStrings() override;
void handleGameParseNextFrame() override;
@@ -65,9 +66,10 @@ private:
void ra2HandleTextResource(const char *str, int fontId, int color,
int pos_x, int pos_y, int left, int top,
int width, int height, TextStyleFlags flg);
- void ra2SelectFrameBuffer(int width, int height);
+ bool ra2SelectFrameBuffer(int width, int height);
bool ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
int width, int height, int pitch, int dataSize);
+ void ra2HandleDeltaPalette(int32 subSize, Common::SeekableReadStream &b);
void ra2StoreFobjData(int codec, const byte *data, int32 dataSize,
int left, int top, int width, int height);
void ra2HandleGost(int32 subSize, Common::SeekableReadStream &b);
diff --git a/engines/scumm/smush/smush_player.cpp b/engines/scumm/smush/smush_player.cpp
index 795a90be08f..a6db75dfc8e 100644
--- a/engines/scumm/smush/smush_player.cpp
+++ b/engines/scumm/smush/smush_player.cpp
@@ -1155,7 +1155,7 @@ void SmushPlayer::handleAnimHeader(int32 subSize, Common::SeekableReadStream &b)
}
}
- if (!_skipPalette) {
+ if (!_skipPalette && shouldLoadAnimHeaderPalette()) {
byte *palettePtr = &headerContent[6];
memcpy(_pal, palettePtr, sizeof(_pal));
adjustGamePalette();
diff --git a/engines/scumm/smush/smush_player.h b/engines/scumm/smush/smush_player.h
index 4f967932c5a..a5dc1ff6f79 100644
--- a/engines/scumm/smush/smush_player.h
+++ b/engines/scumm/smush/smush_player.h
@@ -311,6 +311,7 @@ protected:
virtual bool shouldAlwaysShowSubtitles() const { return false; }
virtual SmushFont *getGameFont(int font) { return nullptr; }
virtual void adjustGamePalette() {}
+ virtual bool shouldLoadAnimHeaderPalette() const { return true; }
virtual bool handleGameAnimHeader(byte *headerContent) { return false; }
virtual bool handleGameSetupStrings() { return false; }
virtual void handleGameParseNextFrame() {}
Commit: ec305dee3713343ba93643eb27ab1d0534f16100
https://github.com/scummvm/scummvm/commit/ec305dee3713343ba93643eb27ab1d0534f16100
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-01T16:13:54+02:00
Commit Message:
SCUMM: RA2: improved gamepad support
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/metaengine.cpp
engines/scumm/scumm.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 2ed784f0480..fe30e70a2dc 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -67,6 +67,10 @@ static bool readLevel2BackgroundChunkHeader(Common::SeekableReadStream &stream,
void InsaneRebel2::procPreRendering(byte *renderBitmap) {
Insane::procPreRendering(renderBitmap);
+ // Pan the reticle from held directional controls (on-screen/physical gamepad dpad,
+ // keyboard arrows) once per frame. No-op outside gameplay; mouse aiming is unaffected.
+ updateGameplayAimFromGamepad();
+
// Reset opcode 6 init flag at the start of each new video.
// This ensures the per-wave init (clearBit, link table reset, wave state)
// fires exactly once per wave video, not every frame.
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index d0d2368c7d7..cef25c6b9e2 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -403,6 +403,9 @@ int InsaneRebel2::runLevel(int levelId) {
smush_warpMouse(160, 100, -1);
CursorMan.showMouse(false);
g_system->lockMouse(true);
+ // Start each level with the centered cursor as the authoritative aim source;
+ // the first gamepad input reclaims it (see updateGameplayAimFromGamepad).
+ _gamepadAimActive = false;
// Initialize common player state
_playerLives = 3;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index c843a74296b..a91e46c276c 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -465,6 +465,13 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// Initialize menu input capture system
_menuInputActive = false;
+ // Analog stick state for gamepad aiming (mirrors RA1's analog model).
+ // Ingested from EVENT_CUSTOM_BACKEND_ACTION_AXIS, read with a deadzone and
+ // integrated as velocity into the reticle in updateGameplayAimFromGamepad().
+ _joystickAxisX = 0;
+ _joystickAxisY = 0;
+ _gamepadAimActive = false;
+
// Initialize level state tracking for multi-phase levels
_currentPhase = 1;
_deathFrame = 0;
@@ -524,6 +531,67 @@ InsaneRebel2::~InsaneRebel2() {
bool InsaneRebel2::notifyEvent(const Common::Event &event) {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
+ // Keep the gamepad reticle authoritative against stray pointer events. During
+ // gameplay the cursor is locked ~screen-center (levels.cpp lockMouse), so any
+ // spurious pointer event â e.g. a gamepad "fire" surfacing as a button-down â
+ // carries that centered position, and SCUMM's input layer copies it straight
+ // into _mouse on button-down (scumm/input.cpp), yanking the reticle to center on
+ // every shot. We observe before ScummEngine (priority 1 > kEventManPriority), so
+ // dropping the event here prevents the clobber. Firing is driven by the
+ // kScummActionInsaneAttack action, not the pointer, so this does not affect shots.
+ // A genuine mouse/touch motion (nonzero relative delta) hands control back.
+ if (_gamepadAimActive && _gameState == kStateGameplay && !_menuInputActive) {
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ if (event.relMouse.x != 0 || event.relMouse.y != 0) {
+ _gamepadAimActive = false; // real pointer motion takes over
+ break;
+ }
+ return true; // drop zero-delta artifact (e.g. locked-cursor recentre)
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_RBUTTONDOWN:
+ return true; // drop stray button-down that would recenter the reticle
+ // Let LBUTTONUP/RBUTTONUP fall through so the backend _buttonState latch
+ // always clears and can never stick when a real mouse later takes over.
+ default:
+ break;
+ }
+ }
+
+ // Analog stick â reticle velocity (mirrors RA1's analog model). The mapped
+ // axis actions carry a signed position; a centered stick reports position 0.
+ // We ignore a recentre that would fight the opposite direction so a quick
+ // flick doesn't get cancelled by the trailing zero of the other axis half.
+ if (event.type == Common::EVENT_CUSTOM_BACKEND_ACTION_AXIS) {
+ const int16 axisPosition = (event.joystick.position == Common::JOYAXIS_MIN)
+ ? Common::JOYAXIS_MAX : event.joystick.position;
+
+ switch (event.customType) {
+ case kScummBackendActionRebel2AxisUp:
+ if (event.joystick.position == 0 && _joystickAxisY > 0)
+ return true;
+ _joystickAxisY = -axisPosition;
+ return true;
+ case kScummBackendActionRebel2AxisDown:
+ if (event.joystick.position == 0 && _joystickAxisY < 0)
+ return true;
+ _joystickAxisY = axisPosition;
+ return true;
+ case kScummBackendActionRebel2AxisLeft:
+ if (event.joystick.position == 0 && _joystickAxisX > 0)
+ return true;
+ _joystickAxisX = -axisPosition;
+ return true;
+ case kScummBackendActionRebel2AxisRight:
+ if (event.joystick.position == 0 && _joystickAxisX < 0)
+ return true;
+ _joystickAxisX = axisPosition;
+ return true;
+ default:
+ break;
+ }
+ }
+
if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START ||
event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_END) {
const bool pressed = (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START);
@@ -593,10 +661,12 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
}
}
- if (event.customType == kScummActionInsaneAttack ||
- event.customType == kScummActionInsaneSwitch) {
- return true;
- }
+ // Do NOT consume Attack/Switch during gameplay: like the dpad actions, these
+ // custom-action events must fall through to ScummEngine::parseEvent() so it keeps
+ // _actionMap in sync (input.cpp). processMouse() reads getActionState() for the
+ // gamepad trigger; consuming here breaks the dispatch loop (events.cpp) before the
+ // map is updated, leaving fire dead. The menu/paused branches above already
+ // consumed (and returned true for) the cases they handle.
}
if (event.type == Common::EVENT_KEYDOWN) {
@@ -1285,29 +1355,58 @@ int32 InsaneRebel2::processMouse() {
}
Common::Point InsaneRebel2::getGameplayAimPoint() {
- Common::Point aimPos(_vm->_mouse.x, _vm->_mouse.y);
+ // Pure getter (queried many times per frame): the aim/reticle follows the virtual
+ // mouse position. Directional controls pan that position incrementally once per frame
+ // via updateGameplayAimFromGamepad(), rather than snapping the reticle to a screen edge.
+ return Common::Point(_vm->_mouse.x, _vm->_mouse.y);
+}
+// Apply the user's configured analog deadzone so a resting stick reports no
+// motion. Mirrors RA1's applyRebel1AnalogDeadzone (iact.cpp).
+static inline int16 applyRebel2AnalogDeadzone(int16 axisValue) {
+ const int deadZone = MAX(0, ConfMan.getInt("joystick_deadzone")) * 1000;
+ return (ABS((int)axisValue) <= deadZone) ? 0 : axisValue;
+}
+
+void InsaneRebel2::updateGameplayAimFromGamepad() {
if (_menuInputActive || _gameState != kStateGameplay)
- return aimPos;
-
- int dx = 0;
- int dy = 0;
-
- if (_vm->getActionState(kScummActionInsaneLeft))
- dx--;
- if (_vm->getActionState(kScummActionInsaneRight))
- dx++;
- if (_vm->getActionState(kScummActionInsaneUp))
- dy--;
- if (_vm->getActionState(kScummActionInsaneDown))
- dy++;
-
- if (dx || dy) {
- aimPos.x = (dx < 0) ? 0 : (dx > 0) ? 319 : 160;
- aimPos.y = (dy < 0) ? 0 : (dy > 0) ? 199 : 100;
+ return;
+
+ // Velocity model ported from RA1 (insane/rebel1/iact.cpp preprocessMouseAxes):
+ // the digital dpad pans at full rate, while the analog stick pans proportionally
+ // to its deflection past the deadzone. This replaces the old edge-snap that pinned
+ // the reticle to the screen borders. Mouse input is untouched: with nothing held the
+ // reticle simply follows _vm->_mouse as before.
+ int velX = 0;
+ int velY = 0;
+
+ const int dpadX = (_vm->getActionState(kScummActionInsaneRight) ? 1 : 0) -
+ (_vm->getActionState(kScummActionInsaneLeft) ? 1 : 0);
+ const int dpadY = (_vm->getActionState(kScummActionInsaneDown) ? 1 : 0) -
+ (_vm->getActionState(kScummActionInsaneUp) ? 1 : 0);
+
+ if (dpadX || dpadY) {
+ velX = dpadX * 127;
+ velY = dpadY * 127;
+ } else {
+ const int16 ax = applyRebel2AnalogDeadzone(_joystickAxisX);
+ const int16 ay = applyRebel2AnalogDeadzone(_joystickAxisY);
+ velX = CLIP<int>((int)ax * 127 / Common::JOYAXIS_MAX, -127, 127);
+ velY = CLIP<int>((int)ay * 127 / Common::JOYAXIS_MAX, -127, 127);
}
- return aimPos;
+ if (!velX && !velY)
+ return;
+
+ // The gamepad is now driving the reticle: mark it the active aim source so
+ // notifyEvent() suppresses stray pointer events that would recenter it on fire.
+ _gamepadAimActive = true;
+
+ // Integrate velocity into the reticle, clamped to the 320x200 play area.
+ // kStep sets the max pixels-per-frame at full deflection; tune for feel.
+ const int kStep = 8;
+ _vm->_mouse.x = (int16)CLIP<int>(_vm->_mouse.x + velX * kStep / 127, 0, 319);
+ _vm->_mouse.y = (int16)CLIP<int>(_vm->_mouse.y + velY * kStep / 127, 0, 199);
}
bool InsaneRebel2::isBitSet(int n) {
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index bf19ea2af37..28161f25106 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -492,6 +492,21 @@ public:
int32 processMouse() override;
Common::Point getGameplayAimPoint();
+ // 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();
+
+ // 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;
bool isBitSet(int n) override;
void setBit(int n) override;
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index 5c8c0025a02..93e61776248 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -1170,25 +1170,45 @@ Common::KeymapArray ScummMetaEngine::initKeymaps(const char *target) const {
act = new Action("RA2UP", _("Aim up / menu up"));
act->setCustomEngineActionEvent(kScummActionInsaneUp);
act->addDefaultInputMapping("JOY_UP");
- act->addDefaultInputMapping("JOY_LEFT_STICK_Y-");
rebel2Keymap->addAction(act);
act = new Action("RA2DOWN", _("Aim down / menu down"));
act->setCustomEngineActionEvent(kScummActionInsaneDown);
act->addDefaultInputMapping("JOY_DOWN");
- act->addDefaultInputMapping("JOY_LEFT_STICK_Y+");
rebel2Keymap->addAction(act);
act = new Action("RA2LEFT", _("Aim left / menu left"));
act->setCustomEngineActionEvent(kScummActionInsaneLeft);
act->addDefaultInputMapping("JOY_LEFT");
- act->addDefaultInputMapping("JOY_LEFT_STICK_X-");
rebel2Keymap->addAction(act);
act = new Action("RA2RIGHT", _("Aim right / menu right"));
act->setCustomEngineActionEvent(kScummActionInsaneRight);
act->addDefaultInputMapping("JOY_RIGHT");
+ rebel2Keymap->addAction(act);
+
+ act = new Action("RA2STICKUP", _("Stick up"));
+ act->setCustomBackendActionAxisEvent(kScummBackendActionRebel2AxisUp);
+ act->addDefaultInputMapping("JOY_LEFT_STICK_Y-");
+ act->addDefaultInputMapping("JOY_RIGHT_STICK_Y-");
+ rebel2Keymap->addAction(act);
+
+ act = new Action("RA2STICKDOWN", _("Stick down"));
+ act->setCustomBackendActionAxisEvent(kScummBackendActionRebel2AxisDown);
+ act->addDefaultInputMapping("JOY_LEFT_STICK_Y+");
+ act->addDefaultInputMapping("JOY_RIGHT_STICK_Y+");
+ rebel2Keymap->addAction(act);
+
+ act = new Action("RA2STICKLEFT", _("Stick left"));
+ act->setCustomBackendActionAxisEvent(kScummBackendActionRebel2AxisLeft);
+ act->addDefaultInputMapping("JOY_LEFT_STICK_X-");
+ act->addDefaultInputMapping("JOY_RIGHT_STICK_X-");
+ rebel2Keymap->addAction(act);
+
+ act = new Action("RA2STICKRIGHT", _("Stick right"));
+ act->setCustomBackendActionAxisEvent(kScummBackendActionRebel2AxisRight);
act->addDefaultInputMapping("JOY_LEFT_STICK_X+");
+ act->addDefaultInputMapping("JOY_RIGHT_STICK_X+");
rebel2Keymap->addAction(act);
act = new Action("RA2FIRE", _("Fire / select"));
diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h
index 4a7b88a8421..94ebcfc4a7a 100644
--- a/engines/scumm/scumm.h
+++ b/engines/scumm/scumm.h
@@ -507,7 +507,11 @@ enum ScummBackendAction {
kScummBackendActionRebel1AxisUp = 11000,
kScummBackendActionRebel1AxisDown,
kScummBackendActionRebel1AxisLeft,
- kScummBackendActionRebel1AxisRight
+ kScummBackendActionRebel1AxisRight,
+ kScummBackendActionRebel2AxisUp,
+ kScummBackendActionRebel2AxisDown,
+ kScummBackendActionRebel2AxisLeft,
+ kScummBackendActionRebel2AxisRight
};
extern const char *const insaneKeymapId;
Commit: 0025c4e108654c7f9224366be4fab93f6c0a7a8c
https://github.com/scummvm/scummvm/commit/0025c4e108654c7f9224366be4fab93f6c0a7a8c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-01T16:26:14+02:00
Commit Message:
SCUMM: RA2: match the joytick/gamepad input speed
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 a91e46c276c..3b34d0bd1cc 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -1372,13 +1372,12 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
if (_menuInputActive || _gameState != kStateGameplay)
return;
- // Velocity model ported from RA1 (insane/rebel1/iact.cpp preprocessMouseAxes):
- // the digital dpad pans at full rate, while the analog stick pans proportionally
- // to its deflection past the deadzone. This replaces the old edge-snap that pinned
- // the reticle to the screen borders. Mouse input is untouched: with nothing held the
- // reticle simply follows _vm->_mouse as before.
- int velX = 0;
- int velY = 0;
+ // The analog stick still reports the original joystick-style centered axis range
+ // [-127,127], while the dpad follows the original keyboard handler's normal
+ // movement step (FUN_405822: 3 units, or 5 with the DOS fast modifier).
+ int deltaX = 0;
+ int deltaY = 0;
+ bool activeGamepadAim = false;
const int dpadX = (_vm->getActionState(kScummActionInsaneRight) ? 1 : 0) -
(_vm->getActionState(kScummActionInsaneLeft) ? 1 : 0);
@@ -1386,16 +1385,25 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
(_vm->getActionState(kScummActionInsaneUp) ? 1 : 0);
if (dpadX || dpadY) {
- velX = dpadX * 127;
- velY = dpadY * 127;
+ const int kOriginalDigitalStep = 3;
+ deltaX = dpadX * kOriginalDigitalStep;
+ deltaY = dpadY * kOriginalDigitalStep;
+ activeGamepadAim = true;
} else {
const int16 ax = applyRebel2AnalogDeadzone(_joystickAxisX);
const int16 ay = applyRebel2AnalogDeadzone(_joystickAxisY);
- velX = CLIP<int>((int)ax * 127 / Common::JOYAXIS_MAX, -127, 127);
- velY = CLIP<int>((int)ay * 127 / Common::JOYAXIS_MAX, -127, 127);
+ const int velX = CLIP<int>((int)ax * 127 / Common::JOYAXIS_MAX, -127, 127);
+ const int velY = CLIP<int>((int)ay * 127 / Common::JOYAXIS_MAX, -127, 127);
+
+ if (velX || velY) {
+ const int kAnalogMaxStep = 8;
+ deltaX = velX * kAnalogMaxStep / 127;
+ deltaY = velY * kAnalogMaxStep / 127;
+ activeGamepadAim = true;
+ }
}
- if (!velX && !velY)
+ if (!activeGamepadAim)
return;
// The gamepad is now driving the reticle: mark it the active aim source so
@@ -1403,10 +1411,8 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
_gamepadAimActive = true;
// Integrate velocity into the reticle, clamped to the 320x200 play area.
- // kStep sets the max pixels-per-frame at full deflection; tune for feel.
- const int kStep = 8;
- _vm->_mouse.x = (int16)CLIP<int>(_vm->_mouse.x + velX * kStep / 127, 0, 319);
- _vm->_mouse.y = (int16)CLIP<int>(_vm->_mouse.y + velY * kStep / 127, 0, 199);
+ _vm->_mouse.x = (int16)CLIP<int>(_vm->_mouse.x + deltaX, 0, 319);
+ _vm->_mouse.y = (int16)CLIP<int>(_vm->_mouse.y + deltaY, 0, 199);
}
bool InsaneRebel2::isBitSet(int n) {
Commit: 793b12545ac64040201408a037438c2525579849
https://github.com/scummvm/scummvm/commit/793b12545ac64040201408a037438c2525579849
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-01T16:34:43+02:00
Commit Message:
SCUMM: RA2: make sure input starts centered
Changed paths:
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/runlevels.cpp
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index cef25c6b9e2..ad3f32077be 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -31,6 +31,9 @@
namespace Scumm {
+static const int kRebel2GameplayAimCenterX = 160;
+static const int kRebel2GameplayAimCenterY = 100;
+
// ---------------------------------------------------------------------------
// Level Loading System
// ---------------------------------------------------------------------------
@@ -363,6 +366,17 @@ void InsaneRebel2::playCreditsSequence() {
splayer->play("OPEN/O_CREDIT.SAN", 12);
}
+void InsaneRebel2::centerGameplayAim() {
+ _vm->_mouse.x = kRebel2GameplayAimCenterX;
+ _vm->_mouse.y = kRebel2GameplayAimCenterY;
+
+ _joystickAxisX = 0;
+ _joystickAxisY = 0;
+ _gamepadAimActive = false;
+
+ smush_warpMouse(kRebel2GameplayAimCenterX, kRebel2GameplayAimCenterY, -1);
+}
+
// runLevel -- Main level dispatcher, calls per-level handlers.
int InsaneRebel2::runLevel(int levelId) {
@@ -400,12 +414,9 @@ int InsaneRebel2::runLevel(int levelId) {
// The original hides the cursor (ShowCursor(0)) and relies on Windows confining
// the mouse to the game window. Without locking, the cursor can escape the
// ScummVM window making the ship uncontrollable.
- smush_warpMouse(160, 100, -1);
+ centerGameplayAim();
CursorMan.showMouse(false);
g_system->lockMouse(true);
- // Start each level with the centered cursor as the authoritative aim source;
- // the first gamepad input reclaims it (see updateGameplayAimFromGamepad).
- _gamepadAimActive = false;
// Initialize common player state
_playerLives = 3;
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 28161f25106..fefd80a6f96 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -459,6 +459,9 @@ public:
// 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();
+
// 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)
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 673a67def8a..2d054d0c888 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -190,6 +190,13 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
bool InsaneRebel2::playLevelSegment(const char *filename, uint16 flags, bool recordFrame) {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
+
+ // Retail reset helpers clear the centered input axes before gameplay starts.
+ // Do the same for playable segments, but keep seamless continuation videos
+ // (0x40, e.g. 13PLAY_B) from snapping the aim point mid-sequence.
+ if (recordFrame && (flags & 0x08) != 0 && (flags & 0x40) == 0)
+ centerGameplayAim();
+
splayer->setCurVideoFlags(flags);
splayer->play(filename, 12);
if (recordFrame)
More information about the Scummvm-git-logs
mailing list