[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