[Scummvm-git-logs] scummvm master -> 9bd7e608b0a40d727173fe58e33eee9a62c00143

neuromancer noreply at scummvm.org
Thu Jul 2 07:36:13 UTC 2026


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
dcf866ae5e SCUMM: RA2: implemented missing turret shakes
52e384bc91 SCUMM: RA2: fixed swap of BLAST and TBLASH
c5caf2532b SCUMM: RA2: improved death video selection in some levels
9bd7e608b0 SCUMM: RA2: fixed explosions position in high resolution mode for L10


Commit: dcf866ae5e0d9124b4168e96ecba1ad4bdcb0c96
    https://github.com/scummvm/scummvm/commit/dcf866ae5e0d9124b4168e96ecba1ad4bdcb0c96
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T09:35:53+02:00

Commit Message:
SCUMM: RA2: implemented missing turret shakes

Changed paths:
    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/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index aec354ca8de..99d12243603 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -246,6 +246,10 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 		_rebelValueCounters[i] = 0;
 		_rebelGaugeBlink[i] = 0;
 	}
+	for (int i = 0; i < 15; ++i) {
+		_turretShakeRingX[i] = 0;
+		_turretShakeRingY[i] = 0;
+	}
 	_rebelLastCounter = 0;
 
 	_rebelShieldGateActive = false;
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index edd517494b4..9be6c5d3464 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -698,6 +698,10 @@ public:
 	short _rebelGaugeBlink[10];
 	int _rebelLastCounter;
 
+	// Turret dodge-fail view-shake impulses, ring-filtered into the scroll offset.
+	int16 _turretShakeRingX[15];
+	int16 _turretShakeRingY[15];
+
 	bool _rebelShieldGateActive;
 	bool _rebelShieldDestroyed;
 	bool _rebelReactorMode;
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 23a73c2c0a7..04f2d73238f 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -1587,7 +1587,12 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
 				}
 				if (!_noDamage)
 					initDamageFlash();
-				playSfx(1, 127, CLIP(((cx1 + cx2 + cx3 + cx4) / 4) * -4, -127, 127));
+				const int hitAvgX = (cx1 + cx2 + cx3 + cx4) / 4;
+				const int hitAvgY = (cy1 + cy2 + cy3 + cy4) / 4;
+				playSfx(1, 127, CLIP(hitAvgX * -4, -127, 127));
+				// The amplified impulse enters the shake ring and decays over the next 14 frames.
+				_turretShakeRingX[14] = (int16)(CLIP(hitAvgX, -0x2b, 0x2b) * 8);
+				_turretShakeRingY[14] = (int16)(CLIP(hitAvgY, -0x19, 0x19) * 8);
 			} else {
 				if (dparams.dodgePoints > 0) {
 					addScore(dparams.dodgePoints);
@@ -2106,6 +2111,30 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
 	_viewX = (aimPos.x * maxScrollX) / viewportWidth;
 	_viewY = (aimPos.y * maxScrollY) / viewportHeight;
 
+	if (_rebelHandler == 0x26) {
+		// Dodge-fail view shake: the hit impulse travels a 15-slot weighted ring
+		// and nudges the scroll, with a +-5 jitter while the damage flash runs.
+		static const int16 kShakeWeights[14] = { 1, 1, 2, 2, 3, 3, 4, 8, 14, 16, 16, 14, 12, 6 };
+		int shakeX = 0, shakeY = 0, weightSum = 2;
+		for (int i = 0; i < 14; i++) {
+			_turretShakeRingX[i] = _turretShakeRingX[i + 1];
+			_turretShakeRingY[i] = _turretShakeRingY[i + 1];
+			shakeX += _turretShakeRingX[i] * kShakeWeights[i];
+			shakeY += _turretShakeRingY[i] * kShakeWeights[i];
+			weightSum += kShakeWeights[i];
+		}
+		_turretShakeRingX[14] = 0;
+		_turretShakeRingY[14] = 0;
+		shakeX /= weightSum;
+		shakeY /= weightSum;
+		if (_damageFlashCounter > 0) {
+			shakeX += _vm->_rnd.getRandomNumber(9) - 5;
+			shakeY += _vm->_rnd.getRandomNumber(9) - 5;
+		}
+		_viewX = CLIP<int>(_viewX + shakeX, 0, maxScrollX);
+		_viewY = CLIP<int>(_viewY + shakeY, 0, maxScrollY);
+	}
+
 	_player->setScrollOffset(_viewX, _viewY);
 }
 
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 3789770b50a..973143de9e7 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -298,6 +298,10 @@ void InsaneRebel2::resetShieldGauge() {
 		_rebelValueCounters[i] = 0;
 		_rebelGaugeBlink[i] = 0;
 	}
+	for (int i = 0; i < 15; ++i) {
+		_turretShakeRingX[i] = 0;
+		_turretShakeRingY[i] = 0;
+	}
 	_rebelLastCounter = -1;        // non-zero sentinel: gauge not yet depleted
 	_rebelShieldDestroyed = false;
 	_rebelGaugeArmed = false;


Commit: 52e384bc91b2b166293ca3d64bdb9ea82260093b
    https://github.com/scummvm/scummvm/commit/52e384bc91b2b166293ca3d64bdb9ea82260093b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T09:35:53+02:00

Commit Message:
SCUMM: RA2: fixed swap of BLAST and TBLASH

Changed paths:
    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/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 99d12243603..1b63fcff4ae 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -191,6 +191,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 	_enemies.clear();
 	_rebelHandler = 0;
 	_rebelLevelType = 0;
+	_level15SecondHalf = false;
 	_rebelStatusBarSprite = 0;
 	_introCursorPushed = false;
 
@@ -1281,29 +1282,26 @@ const InsaneRebel2::LevelDifficultyParams InsaneRebel2::kDifficultyTable[6][17]
 	},
 };
 
-InsaneRebel2::LevelDifficultyParams InsaneRebel2::getDifficultyParams() const {
-	int diff = CLIP(_difficulty, 0, 5);
-	int lvIdx = 0;
-
+int InsaneRebel2::getDifficultyRow() const {
 	// Index mapping reconstructed from level handlers:
 	//   Lv1->0, Lv2->1, Lv3->2, Lv4->3, Lv5->4,
 	//   Lv6A->5, Lv6B->6, Lv7->7 ... Lv14->14, Lv15A->15, Lv15B->16.
 	// Our Level 6 phase flow sets _currentPhase to 1/2 accordingly.
-	// Level 15 switches to type 0x10 at frame 0x21e (updateLevel15TypeSwitch).
-	if (_selectedLevel <= 0) {
-		lvIdx = CLIP((int)_rebelLevelType, 0, 16);
-	} else if (_selectedLevel <= 5) {
-		lvIdx = _selectedLevel - 1;
-	} else if (_selectedLevel == 6) {
-		lvIdx = (_currentPhase >= 2) ? 6 : 5;
-	} else if (_selectedLevel <= 14) {
-		lvIdx = _selectedLevel;
-	} else { // _selectedLevel == 15
-		lvIdx = (_rebelLevelType >= 0x10) ? 16 : 15;
-	}
+	// Level 15 hardens at frame 0x21e (updateLevel15TypeSwitch). Do not key on
+	// _rebelLevelType here: the turret opcode-6 overwrites it with the ship type.
+	if (_selectedLevel <= 0)
+		return CLIP((int)_rebelLevelType, 0, 16);
+	if (_selectedLevel <= 5)
+		return _selectedLevel - 1;
+	if (_selectedLevel == 6)
+		return (_currentPhase >= 2) ? 6 : 5;
+	if (_selectedLevel <= 14)
+		return _selectedLevel;
+	return _level15SecondHalf ? 16 : 15;   // _selectedLevel == 15
+}
 
-	lvIdx = CLIP(lvIdx, 0, 16);
-	return kDifficultyTable[diff][lvIdx];
+InsaneRebel2::LevelDifficultyParams InsaneRebel2::getDifficultyParams() const {
+	return kDifficultyTable[CLIP(_difficulty, 0, 5)][getDifficultyRow()];
 }
 
 bool InsaneRebel2::applyPlayerDamage(int damage) {
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 9be6c5d3464..bf3bfe4c275 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -880,8 +880,10 @@ public:
 
 	static const LevelDifficultyParams kDifficultyTable[6][17];
 
+	int getDifficultyRow() const;
 	LevelDifficultyParams getDifficultyParams() const;
 	int16 getWaveBudgetBase(int phase) const;
+	bool _level15SecondHalf;
 
 	void addScore(int points);
 	void renderScoreHUD(byte *renderBitmap, int pitch, int width, int height, int statusBarY);
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 04f2d73238f..7746b496701 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -500,7 +500,8 @@ void InsaneRebel2::spawnShot(int x, int y) {
 void InsaneRebel2::spawnTurretShot(int x, int y) {
 	for (int i = 0; i < 2; i++) {
 		if (_turretShots[i].counter == 0) {
-			playSfx((_rebelLevelType == 5) ? 0 : 7, 127, 0);
+			// Ship type 5 (the turbolaser levels 13-15) fires TBLAST, the rest BLAST.
+			playSfx((_rebelLevelType == 5) ? 7 : 0, 127, 0);
 
 			_turretShots[i].counter = getShotMaxDuration();
 			_turretShots[i].seqNum = _turretShotSeqCounter;
@@ -2165,9 +2166,10 @@ void InsaneRebel2::updateLevel7Fork(int32 curFrame) {
 }
 
 void InsaneRebel2::updateLevel15TypeSwitch(int32 curFrame) {
-	// From frame 0x21e level 15 uses the type-0x10 difficulty column; runLevel15 resets to 0xf per attempt.
+	// From frame 0x21e level 15 uses the type-0x10 difficulty column; runLevel15 resets per attempt.
+	// Tracked as a flag because the turret opcode-6 rewrites _rebelLevelType every frame.
 	if (_selectedLevel == 15 && curFrame >= 0x21e)
-		_rebelLevelType = 0x10;
+		_level15SecondHalf = true;
 }
 
 void InsaneRebel2::renderPostRenderMenuCursor(byte *renderBitmap, int pitch, int width, int height) {
@@ -3006,10 +3008,10 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
 	int numSprites = _smush_cockpitNut->getNumChars();
 	const int statusScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
 
-	// Critical-damage warning beep every 25 gameplay frames; volume depends on the level type.
+	// Critical-damage warning beep every 25 gameplay frames; volume depends on the difficulty row.
 	if (_rebelHandler != 0 && _playerDamage > 170 && (curFrame % 25) == 0) {
 		int alertVolume;
-		switch (_rebelLevelType) {
+		switch (getDifficultyRow()) {
 		case 1: case 11: case 12:
 			alertVolume = 40;
 			break;
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 973143de9e7..fc04c86d6c7 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -1791,6 +1791,7 @@ int InsaneRebel2::runLevel15() {
 		_rebelHitCounter = 0;
 
 		_rebelLevelType = 0xf;
+		_level15SecondHalf = false;
 
 		if (!playLevelSegment("LEV15/15PLAY.SAN", 0x28))
 			return kLevelQuit;


Commit: c5caf2532bdc4183a476a3ab181ddf9d22abecab
    https://github.com/scummvm/scummvm/commit/c5caf2532bdc4183a476a3ab181ddf9d22abecab
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T09:35:53+02:00

Commit Message:
SCUMM: RA2: improved death video selection in some levels

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/insane/rebel2/render.cpp
    engines/scumm/insane/rebel2/runlevels.cpp


diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index c693f280798..377217722cd 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -396,6 +396,7 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
 				}
 
 				if (shouldDamage) {
+					_rebelDeathCause = 1;
 					if (applyPlayerDamage(directHitDamage)) {
 						debugC(DEBUG_INSANE, "DIRECT HIT damage from enemy %d. par3=%d par4=%d damage=%d total=%d",
 							srcId, par3, par4, directHitDamage, _playerDamage);
@@ -419,6 +420,7 @@ void InsaneRebel2::iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2,
 			debugC(DEBUG_INSANE, "Opcode3: probability=%d roll=%d (need roll < prob)", probability, roll);
 
 			if (roll < probability) {
+				_rebelDeathCause = 0;
 				int damageAmount = (params.shotDamage >= 0) ? params.shotDamage : 0;
 				if (applyPlayerDamage(damageAmount)) {
 					debugC(DEBUG_INSANE, "PROBABILISTIC damage from enemy %d. Damage=%d total=%d",
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index 48c4b69ac29..a58012ad465 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -693,7 +693,10 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
 		return (getRandomVariant(2) == 0) ? "B" : "A";
 
 	case 9:
-		return "A";
+		// Picked by what killed the player: shot -> A, direct hit -> C, collision -> B.
+		if (_rebelDeathCause == 0)
+			return "A";
+		return (_rebelDeathCause == 1) ? "C" : "B";
 
 	case 10:
 		return "";
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 1b63fcff4ae..d2287908bb4 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -560,6 +560,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 
 	_currentPhase = 1;
 	_deathFrame = 0;
+	_rebelDeathCause = 0;
 	_skipSectionRequested = false;
 
 	_vm->_system->getEventManager()->getEventDispatcher()->registerObserver(this, 1, false);
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index bf3bfe4c275..5dd09443b3f 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -363,6 +363,7 @@ public:
 
 	int _currentPhase;
 	int _deathFrame;
+	int _rebelDeathCause;   // 0 = enemy shot, 1 = direct hit, 2 = collision; picks the death video
 	bool _skipSectionRequested;
 
 	// Resources and fonts.
@@ -641,6 +642,7 @@ public:
 	void renderHandler7WarningCues(byte *renderBitmap, int pitch, int width, int height, int32 curFrame, uint16 warningMask);
 	void updateLevel7Fork(int32 curFrame);
 	void updateLevel15TypeSwitch(int32 curFrame);
+	void updateLevel9WaveReset(int32 curFrame);
 
 	int16 _playerDamage;
 	int16 _playerShield;
@@ -707,6 +709,7 @@ public:
 	bool _rebelReactorMode;
 	bool _rebelGaugeArmed;
 	int _rebelLastArmedSlot;
+	void resetGaugeCounters();
 	void resetShieldGauge();
 	void decrementGaugeGroup(int slot, int targetId);
 
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 7746b496701..c2a5abd1ff6 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -1580,6 +1580,7 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
 			}
 
 			if (collision) {
+				_rebelDeathCause = 2;
 				int collisionDamage = (dparams.dodgeDamage >= 0) ? dparams.dodgeDamage : 0;
 
 				if (applyPlayerDamage(collisionDamage)) {
@@ -2159,7 +2160,7 @@ void InsaneRebel2::updateLevel7Fork(int32 curFrame) {
 		return;
 
 	_level7ForkActive = false;
-	if (_flyShipScreenX > 0xd4) {
+	if (_flyShipScreenX + _smoothedVelocity > 0xd4) {
 		_level7TookRightFork = true;
 		_vm->_smushVideoShouldFinish = true;
 	}
@@ -2172,6 +2173,12 @@ void InsaneRebel2::updateLevel15TypeSwitch(int32 curFrame) {
 		_level15SecondHalf = true;
 }
 
+void InsaneRebel2::updateLevel9WaveReset(int32 curFrame) {
+	// 09PLAY re-arms its gauge groups per wave; the counters clear between waves.
+	if (_selectedLevel == 9 && (curFrame == 0x19f || curFrame == 0x352))
+		resetGaugeCounters();
+}
+
 void InsaneRebel2::renderPostRenderMenuCursor(byte *renderBitmap, int pitch, int width, int height) {
 	static const byte kRa2MenuCursor[] = {
 		 0,  0,  1,  1,  1,  1,  1,
@@ -2461,6 +2468,7 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
 	updatePostRenderDeath();
 	updateLevel7Fork(curFrame);
 	updateLevel15TypeSwitch(curFrame);
+	updateLevel9WaveReset(curFrame);
 
 	// End the looping attack-run segment once the shield/reactor is destroyed.
 	if (_rebelShieldGateActive) {
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index fc04c86d6c7..960b6689ae3 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -293,16 +293,20 @@ void InsaneRebel2::resetLevelWaveState() {
 	_rebelWaveState = 0;
 }
 
-void InsaneRebel2::resetShieldGauge() {
+void InsaneRebel2::resetGaugeCounters() {
 	for (int i = 0; i < 10; ++i) {
 		_rebelValueCounters[i] = 0;
 		_rebelGaugeBlink[i] = 0;
 	}
+	_rebelLastCounter = -1;        // non-zero sentinel: gauge not yet depleted
+}
+
+void InsaneRebel2::resetShieldGauge() {
+	resetGaugeCounters();
 	for (int i = 0; i < 15; ++i) {
 		_turretShakeRingX[i] = 0;
 		_turretShakeRingY[i] = 0;
 	}
-	_rebelLastCounter = -1;        // non-zero sentinel: gauge not yet depleted
 	_rebelShieldDestroyed = false;
 	_rebelGaugeArmed = false;
 	_rebelLastArmedSlot = -1;
@@ -743,16 +747,18 @@ int InsaneRebel2::runLevel4() {
 		}
 
 		debugC(DEBUG_INSANE, "Level 4 death");
-		playLevelDeathVariant(4, 1, _deathFrame);
-		if (_vm->shouldQuit())
-			return kLevelQuit;
-
+		// The death video only plays on the retry path; the last life goes
+		// straight to the game-over cinematic.
 		_playerLives--;
 		if (_playerLives <= 0) {
 			playLevelGameOver(4);
 			return kLevelGameOver;
 		}
 
+		playLevelDeathVariant(4, 1, _deathFrame);
+		if (_vm->shouldQuit())
+			return kLevelQuit;
+
 		playLevelRetry(4);
 		if (_vm->shouldQuit())
 			return kLevelQuit;
@@ -949,7 +955,7 @@ int InsaneRebel2::runLevel7() {
 		// Right fork: continue into the alternate corridor segment (0x40 =
 		// continuation, so the ship position carries over instead of recentering).
 		if (_level7TookRightFork && _playerShield > 0) {
-			if (!playLevelSegment("LEV07/07PLAYB.SAN", 0x68))
+			if (!playLevelSegment("LEV07/07PLAYB.SAN", 0x468))
 				return kLevelQuit;
 		}
 
@@ -963,7 +969,7 @@ int InsaneRebel2::runLevel7() {
 		// Death cinematic depends on which corridor side the player died on
 		// (the original keys 07DIE_B/07DIE_A on DAT_0047ab8c), independent of
 		// the fork frame; taking the right fork implies the right side.
-		const bool diedOnRight = _level7TookRightFork || (_flyShipScreenX > 0xd4);
+		const bool diedOnRight = _level7TookRightFork || (_flyShipScreenX + _smoothedVelocity > 0xd4);
 		debugC(DEBUG_INSANE, "Level 7 death at frame %d, right=%d", _deathFrame, diedOnRight);
 		if (diedOnRight) {
 			playCinematic("LEV07/07DIE_B.SAN");


Commit: 9bd7e608b0a40d727173fe58e33eee9a62c00143
    https://github.com/scummvm/scummvm/commit/9bd7e608b0a40d727173fe58e33eee9a62c00143
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T09:35:53+02:00

Commit Message:
SCUMM: RA2: fixed explosions position in high resolution mode for L10

Changed paths:
    engines/scumm/insane/rebel2/iact.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/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 377217722cd..0a3c2dd89ab 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -2171,6 +2171,8 @@ void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b
 	Common::List<enemy>::iterator it;
 	for (it = _enemies.begin(); it != _enemies.end(); ++it) {
 		if (it->id == enemyId) {
+			it->velX = x - it->rect.left;
+			it->velY = y - it->rect.top;
 			it->rect = Common::Rect(x, y, x + w, y + h);
 			it->type = par4;
 			// IACT bit state is authoritative; clear stale destruction state here.
@@ -2190,6 +2192,8 @@ void InsaneRebel2::initEnemyStruct(int id, int32 x, int32 y, int32 w, int32 h, b
 	e.id = id;
 	e.type = type;
 	e.rect = Common::Rect(x, y, x + w, y + h);
+	e.velX = 0;
+	e.velY = 0;
 	e.active = active;
 	e.destroyed = destroyed;
 	e.explosionFrame = explosionFrame;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index d2287908bb4..acc4da6582d 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -1597,7 +1597,7 @@ int32 InsaneRebel2::processMouse() {
 				if (_rebelHandler != 8 && _rebelHandler != 25) {
 					spawnExplosion((it->rect.left + it->rect.right) / 2,
 								   (it->rect.top + it->rect.bottom) / 2,
-								   explosionHalfWidth);
+								   explosionHalfWidth, it->velX, it->velY);
 				} else if (_rebelHandler == 8 && it->type == 0) {
 					spawnExplosion((it->rect.left + it->rect.right) / 2,
 								   (it->rect.top + it->rect.bottom) / 2,
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 5dd09443b3f..580cc74101f 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -538,6 +538,7 @@ public:
 		int id;
 		int type;
 		Common::Rect rect;
+		int velX, velY;   // last per-frame motion, feeds the death explosion drift
 		bool active;
 		bool destroyed;
 		int explosionFrame;
@@ -584,6 +585,7 @@ public:
 		int width, height;
 		int counter;
 		int scale;
+		int dx, dy;   // carries the victim's last motion (flight buffer space)
 		bool active;
 	};
 
@@ -593,7 +595,7 @@ public:
 	};
 
 	Explosion _explosions[5];
-	void spawnExplosion(int x, int y, int objectHalfWidth);
+	void spawnExplosion(int x, int y, int objectHalfWidth, int dx = 0, int dy = 0);
 
 	// Collision zones registered by IACT opcode 5.
 	struct CollisionZone {
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index c2a5abd1ff6..ba1fdc120f2 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -457,7 +457,7 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
 	debugC(DEBUG_INSANE, "No FOBJ found in embedded SAN userId=%d", userId);
 }
 
-void InsaneRebel2::spawnExplosion(int x, int y, int objectHalfWidth) {
+void InsaneRebel2::spawnExplosion(int x, int y, int objectHalfWidth, int dx, int dy) {
 	for (int i = 0; i < 5; i++) {
 		if (!_explosions[i].active || _explosions[i].counter <= 0) {
 			_explosions[i].active = true;
@@ -465,6 +465,8 @@ void InsaneRebel2::spawnExplosion(int x, int y, int objectHalfWidth) {
 			_explosions[i].x = x;
 			_explosions[i].y = y;
 			_explosions[i].scale = objectHalfWidth;
+			_explosions[i].dx = dx;
+			_explosions[i].dy = dy;
 			break;
 		}
 	}
@@ -3826,14 +3828,24 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
 	if (!_smush_iconsNut)
 		return;
 
+	const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+
 	for (int i = 0; i < 5; i++) {
 		if (!_explosions[i].active)
 			continue;
 
-		int screenX = _explosions[i].x;
-		int screenY = _explosions[i].y;
+		// Explosions live in raw flight-buffer coordinates: keep them moving
+		// with the victim's last motion and project into the presented window
+		// with the current perspective every frame.
+		_explosions[i].x += _explosions[i].dx;
+		_explosions[i].y += _explosions[i].dy;
+		Common::Point p = getHandler7ProjectedPointFor(_explosions[i].x, _explosions[i].y);
+		if (renderHiRes) {
+			p.x += _hiResPresentationViewX;
+			p.y += _hiResPresentationViewY;
+		}
 		renderExplosionFrame(renderBitmap, pitch, width, height, _explosions[i],
-			screenX, screenY, kExplosionAdvanceAfterDraw, true);
+			p.x, p.y, kExplosionAdvanceBeforeDraw, true);
 	}
 
 	if (_hitCooldown != 0) {
@@ -3841,7 +3853,6 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
 
 		int numChars = _smush_iconsNut->getNumChars();
 		int spriteIndex = 0x15 - _hitCooldown;
-		const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
 		const int nativeViewX = renderHiRes ? _hiResPresentationViewX : _viewX;
 		const int nativeViewY = renderHiRes ? _hiResPresentationViewY : _viewY;
 
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 960b6689ae3..f7e62caca15 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -350,6 +350,8 @@ void InsaneRebel2::resetExplosions() {
 		_explosions[i].width = 0;
 		_explosions[i].height = 0;
 		_explosions[i].scale = 0;
+		_explosions[i].dx = 0;
+		_explosions[i].dy = 0;
 	}
 }
 




More information about the Scummvm-git-logs mailing list