[Scummvm-git-logs] scummvm master -> 3dc821daaecb64a36ee648daafb3170f0cd750ff

neuromancer noreply at scummvm.org
Wed Jun 3 07:57:26 UTC 2026


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

Summary:
651c56dba8 SCUMM: RA1: better collision/shoot detection in level 1 part 2
87bf21fc04 SCUMM: RA2: fixed corruption in L1 -> L2
3dc821daae SCUMM: RA2: implement arrow hints to avoid collisions


Commit: 651c56dba8d022cad40c19d75f69ef6de4c68997
    https://github.com/scummvm/scummvm/commit/651c56dba8d022cad40c19d75f69ef6de4c68997
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T08:41:45+02:00

Commit Message:
SCUMM: RA1: better collision/shoot detection in level 1 part 2

Changed paths:
    engines/scumm/insane/rebel1/iact.cpp
    engines/scumm/insane/rebel1/render.cpp


diff --git a/engines/scumm/insane/rebel1/iact.cpp b/engines/scumm/insane/rebel1/iact.cpp
index b5d28d028d7..fb8e22f4b00 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -102,6 +102,21 @@ inline bool ra1DispatcherHudOnlyWhenDisabled(uint32 opcode) {
 	}
 }
 
+bool ra1TargetCursorUsesProjection(uint16 opcode) {
+	switch (opcode) {
+	// FOBJ target chunks can precede the paired 0x0A GAME chunk, leaving 0x08
+	// as the active opcode in turret sections such as L1PLAY2.
+	case 0x08:
+	case 0x09:
+	case 0x0A:
+	case 0x0B:
+	case 0x1A:
+		return true;
+	default:
+		return false;
+	}
+}
+
 inline bool isLevel2DamageLatch(uint16 code) {
 	switch (code) {
 	case 0x0003:
@@ -1108,8 +1123,9 @@ void InsaneRebel1::updateShipPhysics() {
 
 	// FUN_1DEB5 updates the curve table via FUN_22549 after SetCameraOffset.
 	// The full DOS path blends a few roll-history terms; use the current roll
-	// accumulator so side-looking still bends the gameplay projection.
-	rebuildProjectionTable(CLIP<int16>((int16)(-(_rollAccum >> 7)), -0x1A, 0x1A), 0x1A);
+	// accumulator so side-looking still bends the gameplay projection. DOS
+	// negates before the arithmetic shift.
+	rebuildProjectionTable((int16)CLIP<int32>((-_rollAccum) >> 7, -0x1A, 0x1A), 0x1A);
 
 	// --- Step 8: Direction sprite index (FUN_1DEB5 LAB_1e23e) ---
 	// Horizontal component from _74CA (rollAccum):
@@ -1272,9 +1288,9 @@ void InsaneRebel1::updateTurretShipDirection(int16 offsetY) {
 
 void InsaneRebel1::getCollisionShipCenter(int16 &x, int16 &y) const {
 	// Original 0x0D/0x0E collision compares script zones transformed by
-	// FUN_2248C against the source-buffer ship center (base center +
-	// g_shipOffset). Keep this in the same 384x242 space; the final viewport
-	// crop is only a presentation step.
+	// FUN_223FE against the gameplay-window ship center (base center +
+	// g_shipOffset). This is DOS screen space; render overlays add the viewport
+	// offset separately when drawing into ScummVM's larger source buffer.
 	//
 	// In Level 1 part 2, HandleGameOp0A_TurretVariant reuses _shipPos for the
 	// targeting cursor, so collision must read the movement accumulator instead.
@@ -1409,7 +1425,8 @@ void InsaneRebel1::updateTurretPhysics() {
 
 	// FUN_1E6A7 rebuilds the side-look curve with a shallower table than the
 	// main flight handler, derived directly from roll.
-	rebuildProjectionTable((int16)(-(_rollAccum >> 9)), 0x0D);
+	// DOS negates before the arithmetic shift.
+	rebuildProjectionTable((int16)((-_rollAccum) >> 9), 0x0D);
 
 	// Regeneration + survival bonus via FUN_1BB0E call in this path.
 	if ((_frameCounter & 0x1F) == 0) {
@@ -2090,10 +2107,10 @@ void InsaneRebel1::handleGameOpcode0DCorridor(int32 subSize, Common::SeekableRea
 
 	int16 centerX = corridorLeft + corridorWidth / 2;
 	int16 centerY = corridorTop + corridorHeight / 2;
-	// DOS FUN_1C54D calls FUN_2248C here, which adds the current
-	// camera offset to the scripted rectangle center before testing it
-	// against the source-buffer ship center.
-	unprojectGameplayPoint(centerX, centerY);
+	// DOS FUN_1C54D calls FUN_223FE here, which projects the scripted
+	// rectangle center into gameplay-window space before testing it against the
+	// ship center.
+	projectGameplayPoint(centerX, centerY);
 
 	_corridorLeftX = centerX - corridorWidth / 2;
 	_corridorTopY = centerY - corridorHeight / 2;
@@ -2165,8 +2182,8 @@ void InsaneRebel1::handleGameOpcode0EZone(int32 subSize, Common::SeekableReadStr
 
 	int16 centerX = zoneLeft + zoneWidth / 2;
 	int16 centerY = zoneTop + zoneHeight / 2;
-	// Same transform as opcode 0x0D/FUN_1C54D.
-	unprojectGameplayPoint(centerX, centerY);
+	// Same gameplay-window FUN_223FE transform as opcode 0x0D/FUN_1C54D.
+	projectGameplayPoint(centerX, centerY);
 
 	zoneLeft = centerX - zoneWidth / 2;
 	zoneTop = centerY - zoneHeight / 2;
@@ -2410,16 +2427,17 @@ void InsaneRebel1::processShot() {
 }
 
 // checkTargetHit — FUN_1C0EF (0x1C0EF). AABB target detection with snap tolerance.
-// The original compares target bounds against the cursor after UnprojectScreenPoint()
-// because g_shipPos is a screen-space cursor. Most handlers in this port draw the
-// cursor into the 384x242 source buffer before the viewport crop, so their cursor is
-// already in the same target space as raw FOBJ bounds. Opcode 0x0B remains screen-space
-// and still needs the original project/unproject conversion.
+// The original compares raw FOBJ bounds against the cursor after
+// UnprojectScreenPoint(). Keep that separate from 0x0D/0x0E collision, which projects
+// zones into gameplay-window screen space before comparing against the ship center.
 void InsaneRebel1::checkTargetHit(int16 targetIdx, int16 left, int16 top, int16 right, int16 bottom) {
 	int16 snap = _tuning.snap;
-	const bool screenSpaceCursor = (getEffectiveGameOpcode() == 0x0B);
-	int16 curX = getGameplayCursorX();
-	int16 curY = getGameplayCursorY();
+	const uint16 effectiveOpcode = getEffectiveGameOpcode();
+	const bool screenSpaceCursor = ra1TargetCursorUsesProjection(effectiveOpcode);
+	const int16 screenCursorX = getGameplayCursorX();
+	const int16 screenCursorY = getGameplayCursorY();
+	int16 curX = screenCursorX;
+	int16 curY = screenCursorY;
 	if (screenSpaceCursor)
 		unprojectGameplayPoint(curX, curY);
 	const int slot = _targetCount;
@@ -2447,6 +2465,11 @@ void InsaneRebel1::checkTargetHit(int16 targetIdx, int16 left, int16 top, int16
 		if (slot < kMaxTargetBoxes)
 			_targetBoxVariant[slot] = CLIP<int16>((int16)(_targetBoxVariant[slot] + 3), 0, 5);
 
+		debugC(DEBUG_INSANE, "RA1 target near: opcode=0x%02x target=%d raw=[%d,%d]-[%d,%d] cursorScreen=(%d,%d) cursorTest=(%d,%d) snap=%d prox=%d view=(%d,%d)",
+			effectiveOpcode, targetIdx, left, top, right, bottom,
+			screenCursorX, screenCursorY, curX, curY, snap, _targetProximity,
+			_perspectiveX, _perspectiveY);
+
 		// Check tight lock: cursor within target + snap (no extra margin)
 		if (curX > left - snap && curX < right + snap &&
 			curY > top - snap && curY < bottom + snap) {
diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index ac5c801a121..f428e495c39 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -31,9 +31,8 @@ inline int ra1OverlayViewOffsetX(const InsaneRebel1 *rebel1) {
 	if (!rebel1 || !rebel1->isInteractiveVideoActive())
 		return 0;
 
-	// In opcode 0x0B (FUN_1CDA7), marker/shot coordinates are in the gameplay
-	// window. Under ScummVM's FUN_224FD crop emulation, shift them into the
-	// 384-wide source buffer so they stay aligned after the source-window crop.
+	// Opcode 0x0B target/GOST markers are handled as raw/projected coordinates
+	// in their callers. Keep this helper scoped to that legacy path.
 	return (rebel1->getEffectiveGameOpcode() == 0x0B) ? rebel1->getPerspectiveX() : 0;
 }
 
@@ -44,6 +43,42 @@ inline int ra1OverlayViewOffsetY(const InsaneRebel1 *rebel1) {
 	return (rebel1->getEffectiveGameOpcode() == 0x0B) ? rebel1->getPerspectiveY() : 0;
 }
 
+int ra1GameplayWindowOffsetX(const InsaneRebel1 *rebel1) {
+	if (!rebel1 || !rebel1->isInteractiveVideoActive())
+		return 0;
+
+	// Ship/cursor/shot coordinates are in DOS's 320x200 gameplay window. Under
+	// ScummVM's FUN_224FD crop emulation, shift them into the 384x242 source
+	// buffer so the final source-window crop presents them at the same screen
+	// position DOS used for gameplay and collision.
+	switch (rebel1->getEffectiveGameOpcode()) {
+	case 0x07:
+	case 0x08:
+	case 0x09:
+	case 0x0A:
+	case 0x0B:
+		return rebel1->getPerspectiveX();
+	default:
+		return 0;
+	}
+}
+
+int ra1GameplayWindowOffsetY(const InsaneRebel1 *rebel1) {
+	if (!rebel1 || !rebel1->isInteractiveVideoActive())
+		return 0;
+
+	switch (rebel1->getEffectiveGameOpcode()) {
+	case 0x07:
+	case 0x08:
+	case 0x09:
+	case 0x0A:
+	case 0x0B:
+		return rebel1->getPerspectiveY();
+	default:
+		return 0;
+	}
+}
+
 void drawBankString(const RA1SpriteBank &bank, byte *dst, int pitch, int width, int height,
 	int x, int y, const char *text) {
 	if (!dst || !text || bank.numSprites <= 0)
@@ -840,8 +875,8 @@ void InsaneRebel1::renderTargetBoxes(byte *dst, int pitch, int width, int height
 void InsaneRebel1::renderTargeting(byte *dst, int pitch, int width, int height) {
 	const char kRA1TorpedoIndicator[] = "<<d";
 	const RA1SpriteBank &markerBank = (_techFontBank.numSprites > 0) ? _techFontBank : _hudFontBank;
-	const int overlayX = ra1OverlayViewOffsetX(this);
-	const int overlayY = ra1OverlayViewOffsetY(this);
+	const int overlayX = ra1GameplayWindowOffsetX(this);
+	const int overlayY = ra1GameplayWindowOffsetY(this);
 	if (markerBank.numSprites > 0) {
 		// FUN_1CB22 can switch marker sets via DAT_75FF bit 1.
 		// Baseline RA1 targeting uses '^' and animation e..h.
@@ -1155,21 +1190,21 @@ void InsaneRebel1::renderLaserShots(byte *dst, int pitch, int width, int height)
 		0, -3, -19, -24, -30, -28, -30, -29, -20, -5
 	};
 	const int spritesPerSet = 5;
-	const int overlayX = ra1OverlayViewOffsetX(this);
-	const int overlayY = ra1OverlayViewOffsetY(this);
+	const int overlayX = ra1GameplayWindowOffsetX(this);
+	const int overlayY = ra1GameplayWindowOffsetY(this);
 	const int leftStartX = 0;
 	const int rightStartX = 0x13F; // 319
 	const uint16 effectiveOpcode = getEffectiveGameOpcode();
 	const bool onFootMode = (effectiveOpcode == 0x19 || effectiveOpcode == 0x1A);
 	const bool turretMode = (effectiveOpcode == 0x08 || effectiveOpcode == 0x0A);
 	const bool flightVariantMode = (effectiveOpcode == 0x09);
-	int shipBaseX = _shipPosX;
-	int shipBaseY = flightVariantMode ? _shipPosY : (overlayY + _shipPosY);
+	int shipBaseX = overlayX + _shipPosX;
+	int shipBaseY = overlayY + _shipPosY;
 	if (turretMode) {
 		int16 centerX, centerY;
 		getTurretShipCenter(centerX, centerY);
-		shipBaseX = centerX;
-		shipBaseY = centerY;
+		shipBaseX = overlayX + centerX;
+		shipBaseY = overlayY + centerY;
 	}
 
 	for (int i = 0; i < kMaxShotSlots; i++) {
@@ -1589,8 +1624,8 @@ void InsaneRebel1::renderShip(byte *dst, int pitch, int width, int height) {
 		shipScreenY = centerY;
 	}
 
-	int drawX = shipScreenX - spr.width / 2;
-	int drawY = shipScreenY - spr.height / 2;
+	int drawX = ra1GameplayWindowOffsetX(this) + shipScreenX - spr.width / 2;
+	int drawY = ra1GameplayWindowOffsetY(this) + shipScreenY - spr.height / 2;
 
 	renderSprite(dst, pitch, width, height, drawX, drawY, spr);
 }
@@ -1601,8 +1636,8 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
 	if (_bangBank.numSprites <= 0)
 		return;
 
-	const int overlayX = ra1OverlayViewOffsetX(this);
-	const int overlayY = ra1OverlayViewOffsetY(this);
+	const int overlayX = ra1GameplayWindowOffsetX(this);
+	const int overlayY = ra1GameplayWindowOffsetY(this);
 	// In 0x08/0x0A turret handlers, explosion anchors use the ship center, not
 	// the targeting cursor stored in _shipPos. Flight handlers already keep the
 	// ship center in _shipPos.
@@ -1612,8 +1647,8 @@ void InsaneRebel1::renderExplosions(byte *dst, int pitch, int width, int height)
 	if (effectiveOpcode == 0x08 || effectiveOpcode == 0x0A) {
 		int16 centerX, centerY;
 		getTurretShipCenter(centerX, centerY);
-		shipScreenX = centerX;
-		shipScreenY = centerY;
+		shipScreenX = overlayX + centerX;
+		shipScreenY = overlayY + centerY;
 	}
 
 	// --- Death shake explosions (FUN_1DEB5 LAB_1e0e3) ---


Commit: 87bf21fc0426da2da7817f87072d774f8ee656c3
    https://github.com/scummvm/scummvm/commit/87bf21fc0426da2da7817f87072d774f8ee656c3
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T09:18:52+02:00

Commit Message:
SCUMM: RA2: fixed corruption in L1 -> L2

Changed paths:
    engines/scumm/insane/rebel2/runlevels.cpp


diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index a71a39285c5..77f5c4cbdb0 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -269,9 +269,8 @@ void InsaneRebel2::resetLevelPhaseState(bool clearEnemies) {
 	_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]];
+	for (uint i = 0; i < ARRAYSIZE(_rebelEmbeddedHud); ++i) {
+		EmbeddedSanFrame &frame = _rebelEmbeddedHud[i];
 		free(frame.pixels);
 		frame.pixels = nullptr;
 		frame.width = 0;


Commit: 3dc821daaecb64a36ee648daafb3170f0cd750ff
    https://github.com/scummvm/scummvm/commit/3dc821daaecb64a36ee648daafb3170f0cd750ff
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T09:53:01+02:00

Commit Message:
SCUMM: RA2: implement arrow hints to avoid collisions

Changed paths:
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/insane/rebel2/render.cpp


diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 6ccc021848f..c4ed62eb146 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -836,10 +836,10 @@ public:
 	// Reset collision zone counters (called at end of frame)
 	void resetCollisionZones();
 
-	// Per-frame collision checking against registered zones (FUN_4092D9 first loop)
+	// Per-frame collision checking against registered zones (FUN_4092D9)
 	// Tests aim/ship position against primary zone quadrilaterals
 	// Applies collision damage from DAT_0047e0f6 when inside obstacle zone
-	void checkCollisionZones();
+	void checkCollisionZones(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
 
 	// Handler 7 collision system (FUN_40E35E)
 	// Mode 0/2: Obstacle collision using secondary zones — inside quad = hit
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 9302279fbbd..e5a373e253f 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -39,6 +39,13 @@ namespace Scumm {
 extern void smushDecodeRLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
 extern void smushDecodeUncompressed(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
 
+int getRebel2IndicatorScale(int width, int height) {
+	// Retail only doubles these anchors in true high-res mode (DAT_0047a808 >= 2).
+	// RA2's 424x260 low-res gameplay buffer is still displayed through a 320x200
+	// viewport, so it uses the original 320x200 indicator coordinates.
+	return (width >= 640 || height >= 400) ? 2 : 1;
+}
+
 static bool isValidEmbeddedFrame(const InsaneRebel2::EmbeddedSanFrame &frame) {
 	return frame.valid && frame.pixels && frame.width > 0 && frame.height > 0;
 }
@@ -1545,8 +1552,8 @@ void InsaneRebel2::resetCollisionZones() {
 //
 // checkCollisionZones -- Per-frame collision test against primary zones (FUN_4092D9).
 //
-void InsaneRebel2::checkCollisionZones() {
-	// Per-frame collision checking — FUN_4092D9 first loop (lines 39-202).
+void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
+	// Per-frame collision checking — FUN_4092D9.
 	// Tests aim/ship position against primary collision zone quadrilaterals.
 	//
 	// Original coordinate system:
@@ -1580,6 +1587,12 @@ void InsaneRebel2::checkCollisionZones() {
 	if (aimY < -0x2d)
 		aimY = -0x2d;
 
+	LevelDifficultyParams dparams = getDifficultyParams();
+	const bool showDirectionalWarnings = (dparams.flags & 8) != 0;
+	const bool showGenericWarnings = !showDirectionalWarnings && ((dparams.flags & 0x10) != 0);
+	const bool warningFrame = ((curFrame & 2) != 0) && _smush_iconsNut && (showDirectionalWarnings || showGenericWarnings);
+	const int indicatorScale = getRebel2IndicatorScale(width, height);
+
 	for (int i = 0; i < _primaryZoneCount; i++) {
 		CollisionZone &zone = _primaryZones[i];
 		if (!zone.active)
@@ -1590,11 +1603,6 @@ void InsaneRebel2::checkCollisionZones() {
 		if (zone.filterValue >= 1000)
 			continue;
 
-		// Frame check: field2 - 1 == field1
-		// Original: sVar2 + -1 == (int)sVar1
-		if (zone.field2 - 1 != zone.field1)
-			continue;
-
 		// Center zone vertices by subtracting buffer center (0xD4=212, 0x82=130)
 		// Original: sVar4 = x1 - 0xD4, sVar8 = y1 - 0x82, etc.
 		int cx1 = zone.x1 - 0xD4;
@@ -1606,64 +1614,94 @@ void InsaneRebel2::checkCollisionZones() {
 		int cx4 = zone.x4 - 0xD4;
 		int cy4 = zone.y4 - 0x82;
 
-		// Point-in-quadrilateral test — FUN_4092D9 lines 119-128
-		// Tests if aim position is OUTSIDE the safe corridor (= collision with obstacle).
-		// Original uses 4 edge interpolation tests connected by OR (any failure = collision).
-		//
-		// Edge 1: interpolate Y along top edge (v1→v2) at aim X position
-		//   if aimY < interpolated Y → outside top edge → collision
-		// Edge 2: interpolate Y along bottom edge (v4→v3) at aim X position
-		//   if interpolated Y < aimY → outside bottom edge → collision
-		// Edge 3: interpolate X along left edge (v1→v4) at aim Y position
-		//   if aimX < interpolated X → outside left edge → collision
-		// Edge 4: interpolate X along right edge (v2→v3) at aim Y position
-		//   if interpolated X < aimX → outside right edge → collision
-		bool collision = false;
-
-		// Avoid division by zero for degenerate edges
-		if (cx2 != cx1) {
-			int interpY1 = ((aimX - cx1) * (cy2 - cy1)) / (cx2 - cx1) + cy1;
-			if (aimY < interpY1)
-				collision = true;
-		}
-		if (!collision && cx3 != cx4) {
-			int interpY2 = ((aimX - cx4) * (cy3 - cy4)) / (cx3 - cx4) + cy4;
-			if (interpY2 < aimY)
-				collision = true;
-		}
-		if (!collision && cy4 != cy1) {
-			int interpX1 = ((aimY - cy1) * (cx4 - cx1)) / (cy4 - cy1) + cx1;
-			if (aimX < interpX1)
-				collision = true;
-		}
-		if (!collision && cy3 != cy2) {
-			int interpX2 = ((aimY - cy2) * (cx3 - cx2)) / (cy3 - cy2) + cx2;
-			if (interpX2 < aimX)
-				collision = true;
-		}
+		// Frame check: field2 - 1 == field1
+		// Original: sVar2 + -1 == (int)sVar1
+		if (zone.field2 - 1 == zone.field1) {
+			// Point-in-quadrilateral test — FUN_4092D9 lines 119-128
+			// Tests if aim position is OUTSIDE the safe corridor (= collision with obstacle).
+			// Original uses 4 edge interpolation tests connected by OR (any failure = collision).
+			//
+			// Edge 1: interpolate Y along top edge (v1→v2) at aim X position
+			//   if aimY < interpolated Y → outside top edge → collision
+			// Edge 2: interpolate Y along bottom edge (v4→v3) at aim X position
+			//   if interpolated Y < aimY → outside bottom edge → collision
+			// Edge 3: interpolate X along left edge (v1→v4) at aim Y position
+			//   if aimX < interpolated X → outside left edge → collision
+			// Edge 4: interpolate X along right edge (v2→v3) at aim Y position
+			//   if interpolated X < aimX → outside right edge → collision
+			bool collision = false;
+
+			// Avoid division by zero for degenerate edges
+			if (cx2 != cx1) {
+				int interpY1 = ((aimX - cx1) * (cy2 - cy1)) / (cx2 - cx1) + cy1;
+				if (aimY < interpY1)
+					collision = true;
+			}
+			if (!collision && cx3 != cx4) {
+				int interpY2 = ((aimX - cx4) * (cy3 - cy4)) / (cx3 - cx4) + cy4;
+				if (interpY2 < aimY)
+					collision = true;
+			}
+			if (!collision && cy4 != cy1) {
+				int interpX1 = ((aimY - cy1) * (cx4 - cx1)) / (cy4 - cy1) + cx1;
+				if (aimX < interpX1)
+					collision = true;
+			}
+			if (!collision && cy3 != cy2) {
+				int interpX2 = ((aimY - cy2) * (cx3 - cx2)) / (cy3 - cy2) + cx2;
+				if (interpX2 < aimX)
+					collision = true;
+			}
 
-		if (collision) {
-			// Collision detected — apply damage from collision damage table
-			// Original: DAT_0047a7ec += DAT_0047e0f6[chapter * 0x242 + level * 0x22]
-			LevelDifficultyParams params = getDifficultyParams();
-			int collisionDamage = (params.dodgeDamage >= 0) ? params.dodgeDamage : 0;
-
-			if (!_rebelInvulnerable) {
-				_playerDamage += collisionDamage;
-				if (_playerDamage > 255)
-					_playerDamage = 255;
-				debug("Rebel2: COLLISION damage! zone=%d aim=(%d,%d) damage=%d total=%d",
-					i, aimX, aimY, collisionDamage, _playerDamage);
+			if (collision) {
+				// Collision detected — apply damage from collision damage table
+				// Original: DAT_0047a7ec += DAT_0047e0f6[chapter * 0x242 + level * 0x22]
+				int collisionDamage = (dparams.dodgeDamage >= 0) ? dparams.dodgeDamage : 0;
+
+				if (!_rebelInvulnerable) {
+					_playerDamage += collisionDamage;
+					if (_playerDamage > 255)
+						_playerDamage = 255;
+					debug("Rebel2: COLLISION damage! zone=%d aim=(%d,%d) damage=%d total=%d",
+						i, aimX, aimY, collisionDamage, _playerDamage);
+				}
+				// Visual effect — FUN_00420515 (palette flash)
+				initDamageFlash();
+				// TODO: FUN_0041189e sound based on collision direction
+			} else {
+				// Safely passed — award score bonus
+				// Original: FUN_0041bf8d(DAT_0047e100[levelIdx])
+				if (dparams.dodgePoints > 0) {
+					addScore(dparams.dodgePoints);
+				}
 			}
-			// Visual effect — FUN_00420515 (palette flash)
-			initDamageFlash();
-			// TODO: FUN_0041189e sound based on collision direction
-		} else {
-			// Safely passed — award score bonus
-			// Original: FUN_0041bf8d(DAT_0047e100[levelIdx])
-			LevelDifficultyParams scoreParams = getDifficultyParams();
-			if (scoreParams.dodgePoints > 0) {
-				addScore(scoreParams.dodgePoints);
+		} else if (warningFrame && zone.field2 - 0x0c < zone.field1) {
+			// FUN_4092D9 near-collision indicators. Easy mode (flags bit 0x08)
+			// draws directional arrows from cockpit icon slots 0x2a..0x2d.
+			// Novice mode (flags bit 0x10) draws a generic indicator from slot 0x36.
+			const int iconChars = _smush_iconsNut->getNumChars();
+			if (showDirectionalWarnings) {
+				int avgX = (cx1 + cx2 + cx3 + cx4) / 4;
+				int avgY = (cy1 + cy2 + cy3 + cy4) / 4;
+
+				if (avgX >= 0x15 && iconChars > 0x2d) {
+					renderNutSprite(renderBitmap, pitch, width, height,
+						0xd7 * indicatorScale + _viewX, 0x55 * indicatorScale + _viewY, _smush_iconsNut, 0x2d);
+				} else if (avgX < -0x14 && iconChars > 0x2c) {
+					renderNutSprite(renderBitmap, pitch, width, height,
+						0x69 * indicatorScale + _viewX, 0x55 * indicatorScale + _viewY, _smush_iconsNut, 0x2c);
+				}
+
+				if (avgY >= 0x15 && iconChars > 0x2b) {
+					renderNutSprite(renderBitmap, pitch, width, height,
+						0xa0 * indicatorScale + _viewX, 0x82 * indicatorScale + _viewY, _smush_iconsNut, 0x2b);
+				} else if (avgY < -0x14 && iconChars > 0x2a) {
+					renderNutSprite(renderBitmap, pitch, width, height,
+						0xa0 * indicatorScale + _viewX, 0x28 * indicatorScale + _viewY, _smush_iconsNut, 0x2a);
+				}
+			} else if (showGenericWarnings && iconChars > 0x36) {
+				renderNutSprite(renderBitmap, pitch, width, height,
+					0xa0 * indicatorScale + _viewX, 0x1e * indicatorScale + _viewY, _smush_iconsNut, 0x36);
 			}
 		}
 	}
@@ -1925,7 +1963,7 @@ void InsaneRebel2::renderHandler7WarningCues(byte *renderBitmap, int pitch, int
 	// Note: These are cue sprites (often perceived as "shadows"), not the aiming reticle.
 	LevelDifficultyParams dparams = getDifficultyParams();
 	if ((curFrame & 2) != 0 && (dparams.flags & 8) != 0 && _smush_iconsNut) {
-		int scale = (_vm->_screenWidth > 320 || _vm->_screenHeight > 200) ? 2 : 1;
+		int scale = getRebel2IndicatorScale(width, height);
 
 		if ((warningMask & 1) != 0 && _smush_iconsNut->getNumChars() > 0x2d) {
 			renderNutSprite(renderBitmap, pitch, width, height,
@@ -2340,7 +2378,7 @@ void InsaneRebel2::checkGameplayPostRenderCollisions(byte *renderBitmap, int pit
 	//   Mode 1/3: PRIMARY zones (0x0D) - wall/boundary per-edge with push-back
 	//   Uses ship position in raw buffer coords, hit cooldown, directional damage.
 	if (_rebelHandler == 0x26) {
-		checkCollisionZones();
+		checkCollisionZones(renderBitmap, pitch, width, height, curFrame);
 	} else if (_rebelHandler == 7) {
 		checkHandler7CollisionZones(renderBitmap, pitch, width, height, curFrame);
 	}




More information about the Scummvm-git-logs mailing list