[Scummvm-git-logs] scummvm master -> e997e181e40ae1b9469c26f5591ee1137dfc0e0d
neuromancer
noreply at scummvm.org
Fri Jun 5 19:36:44 UTC 2026
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
4f7951f86e SCUMM: RA1: center mouse at the start and default to easy dificulty
07cc2c95cc SCUMM: RA: improved mouse input for both game with more effective centering when they start
25c4577531 COLONY: better rendering of enemies
114ad8c194 COLONY: more fluent animations for grow/shrink enemies
8ff586d3dd COLONY: better detection for aiming enemies
e997e181e4 COLONY: better detection collision detection with static objects
Commit: 4f7951f86edbfbc2e8289bf35d71468861dff7d5
https://github.com/scummvm/scummvm/commit/4f7951f86edbfbc2e8289bf35d71468861dff7d5
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-05T16:17:57+02:00
Commit Message:
SCUMM: RA1: center mouse at the start and default to easy dificulty
Changed paths:
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/runlevels.cpp
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index c61b0c8aa65..828fb601216 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -238,8 +238,8 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_turretFrameShipCenterValid = false;
_driftParam = 0;
- // Original startup initializes the options difficulty/tuning index to 1.
- _difficulty = 1;
+ // Start new games on the least punishing original tuning by default.
+ _difficulty = 0;
loadTuningForLevel(0);
_perspectiveX = 0;
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 11499e20973..09f0ff68cd9 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -1774,8 +1774,11 @@ void InsaneRebel1::captureInteractiveVideoInput() {
// On touchscreen devices the DOS recenter-the-cursor aiming model does not apply;
// warping/locking the system mouse there injects spurious motion that drifts
// direct touch aiming and on-screen controls.
- if (!isTouchscreenActive())
+ if (!isTouchscreenActive()) {
+ _vm->_mouse.x = kRA1CenterX;
+ _vm->_mouse.y = kRA1CenterY;
smush_warpMouse(160, 100, -1);
+ }
_mouseVirtualRawX = 0x140;
_mouseVirtualRawY = 100;
_mouseVirtualPrevLogicalX = kRA1CenterX;
Commit: 07cc2c95cc0800c8201ecbb516326894f5231d8f
https://github.com/scummvm/scummvm/commit/07cc2c95cc0800c8201ecbb516326894f5231d8f
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-05T16:17:57+02:00
Commit Message:
SCUMM: RA: improved mouse input for both game with more effective centering when they start
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
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
diff --git a/engines/scumm/insane/rebel1/iact.cpp b/engines/scumm/insane/rebel1/iact.cpp
index 7065bdc8a8b..4ae3706e857 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -46,6 +46,10 @@ inline int16 smoothRebel1Op0BAnalogInput(int16 inputValue, int16 &filteredValue,
return filteredValue;
}
+inline int16 scaleRebel1CenteredMouseAxis(int16 value, int16 center, int axisMax) {
+ return (int16)CLIP<int32>(((int32)(value - center) * axisMax) / center, -axisMax, axisMax);
+}
+
int16 shapeRebel1Op0BGamepadAxis(int16 inputValue, int16 axisMax) {
const int axis = CLIP<int>(inputValue, -axisMax, axisMax);
const int absAxis = ABS(axis);
@@ -77,6 +81,10 @@ const int kRA1DosMouseSafeLeft = 0x0AA;
const int kRA1DosMouseSafeRight = 0x1D6;
const int kRA1DosMouseSafeTop = 0x32;
const int kRA1DosMouseSafeBottom = 0x96;
+const int kRA1DosFlightMouseMaxX = kRA1DosMouseCenterX >> 2;
+const int kRA1DosFlightMouseMaxY = kRA1DosMouseCenterY >> 1;
+const int kRA1EnhancedFlightDirectMaxX = (kRA1DosFlightMouseMaxX * 4) / 5;
+const int kRA1EnhancedFlightDirectMaxY = (kRA1DosFlightMouseMaxY * 4) / 5;
// Level 15 final approach 0x5D damage/event codes consumed by
// RunLevel1GameLoop. The latch stores the raw GAME parameter; no translation is
@@ -950,8 +958,9 @@ void InsaneRebel1::preprocessMouseAxes(int16 &inputX, int16 &inputY, bool *usedJ
if (usedJoystick)
*usedJoystick = true;
- inputX = joyX * 127;
- inputY = joyY * 127;
+ const bool enhancedFlightInput = _optEnhancedControls && getEffectiveGameOpcode() == 0x07;
+ inputX = joyX * (enhancedFlightInput ? kRA1EnhancedFlightDirectMaxX : 127);
+ inputY = joyY * (enhancedFlightInput ? kRA1EnhancedFlightDirectMaxY : 127);
if (_optControlsYFlip)
inputY = -inputY;
@@ -965,8 +974,11 @@ void InsaneRebel1::preprocessMouseAxes(int16 &inputX, int16 &inputY, bool *usedJ
*usedJoystick = true;
if (analogAxisX != 0 || analogAxisY != 0) {
- inputX = CLIP<int32>(((int32)analogAxisX * 127) / Common::JOYAXIS_MAX, -127, 127);
- inputY = CLIP<int32>(((int32)analogAxisY * 127) / Common::JOYAXIS_MAX, -127, 127);
+ const bool enhancedFlightInput = _optEnhancedControls && getEffectiveGameOpcode() == 0x07;
+ const int axisMaxX = enhancedFlightInput ? kRA1EnhancedFlightDirectMaxX : 127;
+ const int axisMaxY = enhancedFlightInput ? kRA1EnhancedFlightDirectMaxY : 127;
+ inputX = CLIP<int32>(((int32)analogAxisX * axisMaxX) / Common::JOYAXIS_MAX, -axisMaxX, axisMaxX);
+ inputY = CLIP<int32>(((int32)analogAxisY * axisMaxY) / Common::JOYAXIS_MAX, -axisMaxY, axisMaxY);
} else {
inputX = 0;
inputY = 0;
@@ -1006,8 +1018,15 @@ void InsaneRebel1::preprocessMouseAxes(int16 &inputX, int16 &inputY, bool *usedJ
_mousePrevBiasX = 0;
_mousePrevBiasY = 0;
- inputX = (int16)CLIP<int32>(((int32)(logicalX - kRA1CenterX) * 127) / kRA1CenterX, -127, 127);
- inputY = (int16)CLIP<int32>(((int32)(logicalY - kRA1CenterY) * 127) / kRA1CenterY, -127, 127);
+ if (getEffectiveGameOpcode() == 0x07) {
+ // Direct flight input lacks the DOS recenter path's offset decay, so keep
+ // held-edge steering below the raw DOS full-deflection bias.
+ inputX = scaleRebel1CenteredMouseAxis(logicalX, kRA1CenterX, kRA1EnhancedFlightDirectMaxX);
+ inputY = scaleRebel1CenteredMouseAxis(logicalY, kRA1CenterY, kRA1EnhancedFlightDirectMaxY);
+ } else {
+ inputX = (int16)CLIP<int32>(((int32)(logicalX - kRA1CenterX) * 127) / kRA1CenterX, -127, 127);
+ inputY = (int16)CLIP<int32>(((int32)(logicalY - kRA1CenterY) * 127) / kRA1CenterY, -127, 127);
+ }
// These handlers negate DAT_756E internally, so direct touch needs the
// opposite baseline while the menu option still toggles the result.
@@ -1053,9 +1072,7 @@ void InsaneRebel1::preprocessMouseAxes(int16 &inputX, int16 &inputY, bool *usedJ
_mouseVirtualRawY = rawY;
_mouseVirtualPrevLogicalX = kRA1CenterX;
_mouseVirtualPrevLogicalY = kRA1CenterY;
- _vm->_mouse.x = kRA1CenterX;
- _vm->_mouse.y = kRA1CenterY;
- smush_warpMouse(kRA1CenterX, kRA1CenterY, -1);
+ warpGameplayMouseNow(kRA1CenterX, kRA1CenterY);
debug(2, "RA1 original input virtual recenter: offset=(%d,%d) mouse=(%d,%d)",
_mouseOffsetX, _mouseOffsetY, logicalX, logicalY);
}
diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index 45eb5a1d6e5..9b7f374073e 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -54,6 +54,12 @@ const int kRA1LevelSelectLeftX = 20;
const int kRA1LevelSelectRightX = 170;
const int kRA1LevelSelectColW = 130;
const uint32 kRA1JoystickAxisEscGuardMs = 250;
+const int kRA1GameplayMouseSettleJumpThreshold = 40;
+const int kRA1GameplayMouseSettleRelativeThreshold = 40;
+const int kRA1GameplayMouseSettleEdgeMargin = 16;
+const int kRA1GameplayMouseMaxX = 319;
+const int kRA1GameplayMouseMaxY = 199;
+const uint32 kRA1GameplayMouseSettleExtendMs = 1000;
// Original picker traversal uses fixed max indices, not strlen(): passcodes
// stop at 0x1d and high-score names stop at 0x37.
const char kRA1TextEntryPickerChars[] = "^`_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -632,6 +638,42 @@ bool InsaneRebel1::notifyEvent(const Common::Event &event) {
return true;
}
+ if (_interactiveVideoActive && !_menuActive && !isTouchscreenActive() &&
+ getEffectiveGameOpcode() == 0x07 &&
+ event.type == Common::EVENT_MOUSEMOVE && _gameplayMouseSettleUntil != 0) {
+ const uint32 now = _vm->_system->getMillis();
+ if (now < _gameplayMouseSettleUntil) {
+ const int jumpX = event.mouse.x - _vm->_mouse.x;
+ const int jumpY = event.mouse.y - _vm->_mouse.y;
+ const bool largeAbsoluteJump =
+ ABS(jumpX) >= kRA1GameplayMouseSettleJumpThreshold ||
+ ABS(jumpY) >= kRA1GameplayMouseSettleJumpThreshold;
+ const bool smallRelativeMove =
+ ABS((int)event.relMouse.x) < kRA1GameplayMouseSettleRelativeThreshold &&
+ ABS((int)event.relMouse.y) < kRA1GameplayMouseSettleRelativeThreshold;
+ const bool nearWindowEdge =
+ event.mouse.x <= kRA1GameplayMouseSettleEdgeMargin ||
+ event.mouse.x >= kRA1GameplayMouseMaxX - kRA1GameplayMouseSettleEdgeMargin ||
+ event.mouse.y <= kRA1GameplayMouseSettleEdgeMargin ||
+ event.mouse.y >= kRA1GameplayMouseMaxY - kRA1GameplayMouseSettleEdgeMargin;
+
+ if (largeAbsoluteJump && smallRelativeMove && nearWindowEdge) {
+ const int recenterX = _vm->_mouse.x;
+ const int recenterY = _vm->_mouse.y;
+ _gameplayMouseSettleUntil = now + kRA1GameplayMouseSettleExtendMs;
+ warpGameplayMouseNow(recenterX, recenterY);
+
+ debugC(DEBUG_INSANE, "RA1 mouse settle: suppress pos=(%d,%d) rel=(%d,%d) current=(%d,%d) until=%u opcode=0x%X",
+ event.mouse.x, event.mouse.y, event.relMouse.x, event.relMouse.y,
+ _vm->_mouse.x, _vm->_mouse.y, _gameplayMouseSettleUntil,
+ getEffectiveGameOpcode());
+ return true;
+ }
+ }
+
+ _gameplayMouseSettleUntil = 0;
+ }
+
if (event.type == Common::EVENT_MOUSEMOVE && !_mouseRecentering) {
if (_gamepadAimActive && usesRelativeGamepadAimForCurrentLevel() &&
_interactiveVideoActive && !_menuActive &&
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 828fb601216..004840569e1 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -279,6 +279,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_gamepadAimAxisX = 0;
_gamepadAimAxisY = 0;
_gamepadAimActive = false;
+ _gameplayMouseSettleUntil = 0;
_activeInputSource = kInputSourceMouse;
_currentLevel = 0;
@@ -479,6 +480,20 @@ void InsaneRebel1::setCurrentSmushFrame(int32 frame) {
_currentSmushFrame = frame;
}
+void InsaneRebel1::warpGameplayMouseNow(int x, int y) {
+ Common::EventManager *eventMan = _vm->_system->getEventManager();
+ if (eventMan)
+ eventMan->purgeMouseEvents();
+
+ _vm->_mouse.x = x;
+ _vm->_mouse.y = y;
+ _vm->_system->warpMouse(_vm->_macScreen ? x * 2 : x,
+ _vm->_macScreen ? y * 2 + 2 * _vm->_macScreenDrawOffset : y);
+
+ if (eventMan)
+ eventMan->purgeMouseEvents();
+}
+
InsaneRebel1::~InsaneRebel1() {
_vm->_system->getEventManager()->getEventDispatcher()->unregisterObserver(this);
terminateAudio();
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 781d916bc74..6622e884873 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -119,6 +119,7 @@ public:
bool hasFrameGameOpcode(uint16 opcode) const {
return opcode < 32 && (_frameGameOpcodeMask & (1u << opcode)) != 0;
}
+ void warpGameplayMouseNow(int x, int y);
int16 getPerspectiveX() const { return _perspectiveX; }
int16 getPerspectiveY() const { return _perspectiveY; }
void projectGameplayPoint(int16 &x, int16 &y) const;
@@ -377,6 +378,8 @@ private:
int16 _gamepadAimAxisX; // Relative gamepad 0x0B aim in preprocessed input space
int16 _gamepadAimAxisY;
bool _gamepadAimActive;
+ // Suppress delayed lock/warp mousemove artifacts after centering interactive gameplay.
+ uint32 _gameplayMouseSettleUntil;
enum InputSource {
kInputSourceMouse,
kInputSourceJoystickAnalog,
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 09f0ff68cd9..29792d1ad45 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -33,6 +33,8 @@
namespace Scumm {
+static const uint32 kRA1GameplayMouseSettleMs = 1000;
+
int32 findAnimFrameChunkOffset(ScummEngine_v7 *vm, const char *filename, int32 targetFrame) {
if (targetFrame <= 0)
return 0;
@@ -1771,26 +1773,34 @@ void InsaneRebel1::captureInteractiveVideoInput() {
// Some replays happen inside one original gameplay loop, so keep the current
// input state instead of recentering between route clips.
if (!preserveInputState) {
+ _gameplayMouseSettleUntil = 0;
// On touchscreen devices the DOS recenter-the-cursor aiming model does not apply;
// warping/locking the system mouse there injects spurious motion that drifts
// direct touch aiming and on-screen controls.
if (!isTouchscreenActive()) {
- _vm->_mouse.x = kRA1CenterX;
- _vm->_mouse.y = kRA1CenterY;
- smush_warpMouse(160, 100, -1);
+ warpGameplayMouseNow(kRA1CenterX, kRA1CenterY);
+ _gameplayMouseSettleUntil = _vm->_system->getMillis() + kRA1GameplayMouseSettleMs;
}
_mouseVirtualRawX = 0x140;
_mouseVirtualRawY = 100;
_mouseVirtualPrevLogicalX = kRA1CenterX;
_mouseVirtualPrevLogicalY = kRA1CenterY;
_mouseVirtualValid = false;
+ } else {
+ _gameplayMouseSettleUntil = 0;
}
CursorMan.showMouse(false);
if (!isTouchscreenActive())
g_system->lockMouse(true);
+
+ debugC(DEBUG_INSANE, "RA1 centerGameplayAim: mouse=(%d,%d) joystick=(%d,%d) gamepadAim=%d settleUntil=%u preserve=%d",
+ _vm->_mouse.x, _vm->_mouse.y, _joystickAxisX, _joystickAxisY,
+ _gamepadAimActive ? 1 : 0, _gameplayMouseSettleUntil,
+ preserveInputState ? 1 : 0);
}
void InsaneRebel1::releaseInteractiveVideoInput() {
+ _gameplayMouseSettleUntil = 0;
g_system->lockMouse(false);
}
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 854fcf100df..8122855a018 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -34,6 +34,9 @@
namespace Scumm {
+const int kRA2Handler7DirectInputNumerator = 4;
+const int kRA2Handler7DirectInputDenominator = 5;
+
static bool readLevel2BackgroundChunkHeader(Common::SeekableReadStream &stream, int64 containerEnd, const char *context,
uint32 &tag, uint32 &chunkSize, int64 &dataEnd, int64 &nextChunkPos) {
const int64 headerPos = stream.pos();
@@ -830,8 +833,10 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
// Step 1: Raw mouse input as offset from screen center.
// DAT_0047a7e0 = mouseX - 160, DAT_0047a7e2 = mouseY - 100.
// Handler 7 applies DAT_0047a7fe to its local vertical input after clamping.
- int16 inputX = (int16)(_vm->_mouse.x - 160); // DAT_0047a7e0
- int16 inputY = (int16)(_vm->_mouse.y - 100); // DAT_0047a7e2
+ const int16 mouseX = _vm->_mouse.x;
+ const int16 mouseY = _vm->_mouse.y;
+ int16 inputX = (int16)(mouseX - 160); // DAT_0047a7e0
+ int16 inputY = (int16)(mouseY - 100); // DAT_0047a7e2
// Clamp: mouse mode uses [-160, 160] for X, [-127, 127] for Y (lines 55-70).
if (inputX > 160)
@@ -848,6 +853,14 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
int16 scaledInputX = (int16)((inputX * 127) / 160);
int16 scaledInputY = _optControlsFlipped ? (int16)-inputY : inputY; // local_14
+ // ScummVM direct mouse/touch/gamepad aiming can hold the cursor at an edge
+ // indefinitely. Keep this sensitivity concession local to Handler 7
+ // third-person ship steering.
+ scaledInputX = (int16)((scaledInputX * kRA2Handler7DirectInputNumerator) /
+ kRA2Handler7DirectInputDenominator);
+ scaledInputY = (int16)((scaledInputY * kRA2Handler7DirectInputNumerator) /
+ kRA2Handler7DirectInputDenominator);
+
// Step 3: Velocity history + smoothed average (lines 141-157).
for (int i = 24; i > 0; i--) {
_velocityHistory[i] = _velocityHistory[i - 1];
@@ -1063,7 +1076,9 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
((_vm->VAR(_vm->VAR_LEFTBTN_HOLD) != 0) ||
_vm->getActionState(kScummActionInsaneAttack));
- debug("Rebel2 H7: pos=(%d,%d) vel=%d vIn=%d dx=%d dir=%d mode=%d",
+ debugC(DEBUG_INSANE, "Rebel2 H7: mouse=(%d,%d) raw=(%d,%d) scaled=(%d,%d) pos=(%d,%d) "
+ "vel=%d vIn=%d dx=%d dir=%d mode=%d",
+ mouseX, mouseY, inputX, inputY, scaledInputX, scaledInputY,
_flyShipScreenX, _flyShipScreenY, _smoothedVelocity,
_verticalInput, positionDeltaX, _shipDirectionIndex, _flyControlMode);
}
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index 8b010dca334..062ec934395 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -34,6 +34,7 @@ namespace Scumm {
static const int kRebel2GameplayAimCenterX = 160;
static const int kRebel2GameplayAimCenterY = 100;
+static const uint32 kRebel2GameplayMouseSettleMs = 1000;
static void purgeRebel2GameplayInputEvents(Common::EventManager *eventMan) {
if (!eventMan)
@@ -385,15 +386,32 @@ void InsaneRebel2::centerGameplayAim() {
Common::EventManager *eventMan = _vm->_system->getEventManager();
purgeRebel2GameplayInputEvents(eventMan);
- _vm->_mouse.x = kRebel2GameplayAimCenterX;
- _vm->_mouse.y = kRebel2GameplayAimCenterY;
-
_joystickAxisX = 0;
_joystickAxisY = 0;
_gamepadAimActive = false;
- smush_warpMouse(kRebel2GameplayAimCenterX, kRebel2GameplayAimCenterY, -1);
+ warpGameplayMouseNow(kRebel2GameplayAimCenterX, kRebel2GameplayAimCenterY);
purgeRebel2GameplayInputEvents(eventMan);
+ _gameplayMouseSettleUntil = _vm->_system->getMillis() + kRebel2GameplayMouseSettleMs;
+
+ debugC(DEBUG_INSANE, "Rebel2 centerGameplayAim: mouse=(%d,%d) joystick=(%d,%d) gamepadAim=%d settleUntil=%u",
+ _vm->_mouse.x, _vm->_mouse.y,
+ _joystickAxisX, _joystickAxisY, _gamepadAimActive ? 1 : 0,
+ _gameplayMouseSettleUntil);
+}
+
+void InsaneRebel2::warpGameplayMouseNow(int x, int y) {
+ Common::EventManager *eventMan = _vm->_system->getEventManager();
+ if (eventMan)
+ eventMan->purgeMouseEvents();
+
+ _vm->_mouse.x = x;
+ _vm->_mouse.y = y;
+ _vm->_system->warpMouse(_vm->_macScreen ? x * 2 : x,
+ _vm->_macScreen ? y * 2 + 2 * _vm->_macScreenDrawOffset : y);
+
+ if (eventMan)
+ eventMan->purgeMouseEvents();
}
// runLevel -- Main level dispatcher, calls per-level handlers.
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 4d125c67016..ae1f86099ac 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -54,6 +54,12 @@ namespace Scumm {
const int kRA2MenuAxisThreshold = Common::JOYAXIS_MAX / 5;
const uint32 kRA2MenuGamepadNavigationDebounceMs = 250;
const uint32 kRA2MenuGamepadMouseSuppressMs = 250;
+const int kRA2Handler7MouseSettleJumpThreshold = 40;
+const int kRA2Handler7MouseSettleRelativeThreshold = 40;
+const int kRA2Handler7MouseSettleEdgeMargin = 16;
+const int kRA2GameplayMouseMaxX = 319;
+const int kRA2GameplayMouseMaxY = 199;
+const uint32 kRA2Handler7MouseSettleExtendMs = 1000;
bool rebel2UsesRelativeGamepadAim(int selectedLevel) {
return selectedLevel == 1 || selectedLevel == 5 || selectedLevel == 14;
@@ -542,6 +548,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_joystickAxisY = 0;
_gamepadAimActive = false;
_gameplaySectionActive = false;
+ _gameplayMouseSettleUntil = 0;
_lastGameplayMenuCloseTime = 0;
_lastMenuGamepadNavigationTime = 0;
_handler8HudGlyph = '#';
@@ -645,6 +652,46 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
if (_vm->isPaused())
return false;
+ if (_gameState == kStateGameplay && _rebelHandler == 7 &&
+ event.type == Common::EVENT_MOUSEMOVE) {
+ if (_gameplayMouseSettleUntil != 0) {
+ const uint32 now = _vm->_system->getMillis();
+ if (now < _gameplayMouseSettleUntil) {
+ const int jumpX = event.mouse.x - _vm->_mouse.x;
+ const int jumpY = event.mouse.y - _vm->_mouse.y;
+ const bool largeAbsoluteJump =
+ ABS(jumpX) >= kRA2Handler7MouseSettleJumpThreshold ||
+ ABS(jumpY) >= kRA2Handler7MouseSettleJumpThreshold;
+ const bool smallRelativeMove =
+ ABS((int)event.relMouse.x) < kRA2Handler7MouseSettleRelativeThreshold &&
+ ABS((int)event.relMouse.y) < kRA2Handler7MouseSettleRelativeThreshold;
+ const bool nearWindowEdge =
+ event.mouse.x <= kRA2Handler7MouseSettleEdgeMargin ||
+ event.mouse.x >= kRA2GameplayMouseMaxX - kRA2Handler7MouseSettleEdgeMargin ||
+ event.mouse.y <= kRA2Handler7MouseSettleEdgeMargin ||
+ event.mouse.y >= kRA2GameplayMouseMaxY - kRA2Handler7MouseSettleEdgeMargin;
+
+ if (largeAbsoluteJump && smallRelativeMove && nearWindowEdge) {
+ const int recenterX = _vm->_mouse.x;
+ const int recenterY = _vm->_mouse.y;
+ _gameplayMouseSettleUntil = now + kRA2Handler7MouseSettleExtendMs;
+ warpGameplayMouseNow(recenterX, recenterY);
+
+ debugC(DEBUG_INSANE, "Rebel2 H7 mouse settle: suppress pos=(%d,%d) rel=(%d,%d) current=(%d,%d) until=%u",
+ event.mouse.x, event.mouse.y, event.relMouse.x, event.relMouse.y,
+ _vm->_mouse.x, _vm->_mouse.y, _gameplayMouseSettleUntil);
+ return true;
+ }
+ }
+
+ _gameplayMouseSettleUntil = 0;
+ }
+
+ debugC(DEBUG_INSANE, "Rebel2 H7 mouse event: pos=(%d,%d) rel=(%d,%d) gamepadAim=%d menuInput=%d",
+ event.mouse.x, event.mouse.y, event.relMouse.x, event.relMouse.y,
+ _gamepadAimActive ? 1 : 0, _menuInputActive ? 1 : 0);
+ }
+
// 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 â
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 913e4465656..41cdec1c5be 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -509,6 +509,7 @@ public:
// (on-screen/physical gamepad dpad, keyboard arrows) instead of snapping it to a screen
// edge. Call once per frame; getGameplayAimPoint() stays a pure getter.
void updateGameplayAimFromGamepad();
+ void warpGameplayMouseNow(int x, int y);
// Analog stick state, ingested from EVENT_CUSTOM_BACKEND_ACTION_AXIS in notifyEvent()
// and read (with a deadzone) by updateGameplayAimFromGamepad(). Signed; 0 = centered.
@@ -520,6 +521,8 @@ public:
// recenter the reticle (the cursor is locked ~center during gameplay, so a gamepad
// "click" lands at center). See notifyEvent()/updateGameplayAimFromGamepad().
bool _gamepadAimActive;
+ // Suppress delayed lock/warp mousemove artifacts after centering Handler 7 gameplay.
+ uint32 _gameplayMouseSettleUntil;
uint32 _lastGameplayMenuCloseTime;
uint32 _lastMenuGamepadNavigationTime;
void openGameplayMainMenu(SmushPlayer *splayer);
Commit: 25c4577531182786f743f4d7599aec8d7db1f7cc
https://github.com/scummvm/scummvm/commit/25c4577531182786f743f4d7599aec8d7db1f7cc
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-05T17:50:51+02:00
Commit Message:
COLONY: better rendering of enemies
Changed paths:
engines/colony/animation.cpp
engines/colony/render_objects.cpp
diff --git a/engines/colony/animation.cpp b/engines/colony/animation.cpp
index e04f5c6629b..98fdba9fb25 100644
--- a/engines/colony/animation.cpp
+++ b/engines/colony/animation.cpp
@@ -610,12 +610,18 @@ void ColonyEngine::playAnimation() {
needsDraw = true;
}
} else if (event.type == Common::EVENT_KEYDOWN) {
+ if (event.kbd.keycode == Common::KEYCODE_RETURN) {
+ // Original Mac AnimControl(): Return closes the animation window.
+ _animationRunning = false;
+ continue;
+ }
+
int item = 0;
if (event.kbd.keycode >= Common::KEYCODE_0 && event.kbd.keycode <= Common::KEYCODE_9) {
item = 1 + (event.kbd.keycode - Common::KEYCODE_0);
} else if (event.kbd.keycode >= Common::KEYCODE_KP0 && event.kbd.keycode <= Common::KEYCODE_KP9) {
item = 1 + (event.kbd.keycode - Common::KEYCODE_KP0);
- } else if (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_KP_ENTER) {
+ } else if (event.kbd.keycode == Common::KEYCODE_KP_ENTER) {
item = 12; // Enter
} else if (event.kbd.keycode == Common::KEYCODE_BACKSPACE || event.kbd.keycode == Common::KEYCODE_DELETE) {
item = 11; // Clear
diff --git a/engines/colony/render_objects.cpp b/engines/colony/render_objects.cpp
index a93feac780c..60cbf8bfde0 100644
--- a/engines/colony/render_objects.cpp
+++ b/engines/colony/render_objects.cpp
@@ -43,6 +43,8 @@ int mapEyeOverlayColorToMacColor(int colorIdx, int level) {
case kColorIris: return 35; // c_iris
case kColorEyeIris: return 32; // c_eye
case kColorMiniEyeIris: return 33; // c_meye
+ case kColorDroneEye: return 51; // c_edrone
+ case kColorSoldierEye: return 53; // c_esoldier
case kColorQueenEye: return 49; // c_equeen
default: return 6; // c_dwall fallback
}
@@ -58,6 +60,8 @@ uint8 mapEyeOverlayColorToDOSFill(int colorIdx, int level) {
case kColorIris:
case kColorEyeIris:
case kColorMiniEyeIris:
+ case kColorDroneEye:
+ case kColorSoldierEye:
case kColorQueenEye:
return 1;
default:
@@ -79,6 +83,8 @@ int mapEyeOverlayColorToMacPattern(int colorIdx) {
case kColorIris:
case kColorEyeIris:
case kColorMiniEyeIris:
+ case kColorDroneEye:
+ case kColorSoldierEye:
case kColorQueenEye: // fallthrough: explicitly grouped with default
default:
return kPatternGray;
@@ -1100,7 +1106,7 @@ const int kDAbdomenSurf[8][8] = {
{kColorDrone, 3, 5, 2, 1, 5, 0, 0}, {kColorDrone, 3, 5, 3, 2, 5, 0, 0},
{kColorDrone, 3, 5, 4, 3, 5, 0, 0}, {kColorDrone, 3, 5, 1, 4, 5, 0, 0}
};
-// Drone static pincers (llPincer/rrPincer)
+// Base jaw points used by the Soldier pincer animation.
const int kDLLPincerPts[4][3] = {
{0, 0, 130}, {50, -2, 130}, {35, -20, 140}, {35, -20, 120}
};
@@ -1115,25 +1121,8 @@ const int kDRPincerSurf[4][8] = {
{kColorClaw1, 3, 0, 1, 2, 0, 0, 0}, {kColorClaw1, 3, 0, 3, 1, 0, 0, 0},
{kColorClaw2, 3, 0, 2, 3, 0, 0, 0}, {kColorClaw2, 3, 1, 3, 2, 1, 0, 0}
};
-// Drone eyes
-const int kDLEyePts[3][3] = {
- {60, 0, 150}, {60, 50, 130}, {60, 25, 150}
-};
-const int kDLEyeSurf[2][8] = {
- {kColorDroneEye, 3, 0, 1, 2, 0, 0, 0}, {kColorDroneEye, 3, 0, 2, 1, 0, 0, 0}
-};
-const int kDREyePts[3][3] = {
- {60, 0, 150}, {60, -50, 130}, {60, -25, 150}
-};
-const int kDREyeSurf[2][8] = {
- {kColorDroneEye, 3, 0, 1, 2, 0, 0, 0}, {kColorDroneEye, 3, 0, 2, 1, 0, 0, 0}
-};
const Colony::ColonyEngine::PrismPartDef kDAbdomenDef = {6, kDAbdomenPts, 8, kDAbdomenSurf};
-const Colony::ColonyEngine::PrismPartDef kDLLPincerDef = {4, kDLLPincerPts, 4, kDLPincerSurf};
-const Colony::ColonyEngine::PrismPartDef kDRRPincerDef = {4, kDRRPincerPts, 4, kDRPincerSurf};
-const Colony::ColonyEngine::PrismPartDef kDLEyeDef = {3, kDLEyePts, 2, kDLEyeSurf};
-const Colony::ColonyEngine::PrismPartDef kDREyeDef = {3, kDREyePts, 2, kDREyeSurf};
// --- Snoop (type 20) ---
const int kSnoopAbdomenPts[4][3] = {
@@ -1173,6 +1162,142 @@ void resetObjectBounds(const Common::Rect &screenR, Locate &loc) {
loc.zmx = screenR.top;
}
+void mergeObjectBounds(Locate &dst, const Locate &src) {
+ dst.xmn = MIN(dst.xmn, src.xmn);
+ dst.xmx = MAX(dst.xmx, src.xmx);
+ dst.zmn = MIN(dst.zmn, src.zmn);
+ dst.zmx = MAX(dst.zmx, src.zmx);
+}
+
+bool prismSurfaceVisible(const Common::Rect &screenR, const Colony::Thing &thing,
+ const Colony::ColonyEngine::PrismPartDef &def, int surfaceIndex,
+ uint8 cameraLook, int8 cameraLookY, int cameraX, int cameraY,
+ const int *sint, const int *cost) {
+ return isProjectedPrismSurfaceVisible(screenR, thing, def, false, surfaceIndex,
+ cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+}
+
+int droneBodyOrder(const Common::Rect &screenR, const Colony::Thing &thing,
+ uint8 cameraLook, int8 cameraLookY, int cameraX, int cameraY,
+ const int *sint, const int *cost) {
+ const bool v0 = prismSurfaceVisible(screenR, thing, kDAbdomenDef, 0, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+ const bool v1 = prismSurfaceVisible(screenR, thing, kDAbdomenDef, 1, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+ const bool v2 = prismSurfaceVisible(screenR, thing, kDAbdomenDef, 2, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+ const bool v3 = prismSurfaceVisible(screenR, thing, kDAbdomenDef, 3, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+
+ int body = 1;
+ if (v0 && v1)
+ body = 1;
+ if (v2 && v3)
+ body = 1;
+ if (v0 && v3)
+ body = 0;
+ if (v1 && v2)
+ body = 2;
+ return body;
+}
+
+int queenBodyOrder(const Common::Rect &screenR, const Colony::Thing &thing,
+ uint8 cameraLook, int8 cameraLookY, int cameraX, int cameraY,
+ const int *sint, const int *cost) {
+ const bool v0 = prismSurfaceVisible(screenR, thing, kQAbdomenDef, 0, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+ const bool v1 = prismSurfaceVisible(screenR, thing, kQAbdomenDef, 1, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+ const bool v4 = prismSurfaceVisible(screenR, thing, kQAbdomenDef, 4, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+ const bool v5 = prismSurfaceVisible(screenR, thing, kQAbdomenDef, 5, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+
+ int body = 1;
+ if (v0 && v4)
+ body = 1;
+ if (v1 && v5)
+ body = 1;
+ if (v0 && v1)
+ body = 0;
+ if (v4 && v5)
+ body = 2;
+ return body;
+}
+
+float prismPointForwardDepth(const Colony::Thing &thing, const Colony::ColonyEngine::PrismPartDef &def, int pointIndex,
+ uint8 cameraLook, int cameraX, int cameraY, const int *sint, const int *cost) {
+ if (pointIndex < 0 || pointIndex >= def.pointCount)
+ return 0.0f;
+
+ const uint8 objAng = thing.where.ang + 32;
+ const int32 rotCos = cost[objAng];
+ const int32 rotSin = sint[objAng];
+ const int ox = def.points[pointIndex][0];
+ const int oy = def.points[pointIndex][1];
+ const int32 rx = ((int32)ox * rotCos - (int32)oy * rotSin) >> 7;
+ const int32 ry = ((int32)ox * rotSin + (int32)oy * rotCos) >> 7;
+ const float dx = (float)(rx + thing.where.xloc - cameraX);
+ const float dy = (float)(ry + thing.where.yloc - cameraY);
+
+ return dx * (cost[cameraLook] / 128.0f) + dy * (sint[cameraLook] / 128.0f);
+}
+
+struct EnemyEyePair {
+ Thing left;
+ Thing right;
+ bool leftFirst;
+};
+
+int projectedEnemyEyeDiameter(const Common::Rect &screenR, const Colony::Thing &eye,
+ uint8 cameraLook, int8 cameraLookY, int cameraX, int cameraY,
+ const int *sint, const int *cost) {
+ int bottomX = 0;
+ int bottomY = 0;
+ int centerX = 0;
+ int centerY = 0;
+ if (!projectCorridorPointRaw(screenR, cameraLook, cameraLookY, sint, cost, cameraX, cameraY,
+ (float)eye.where.xloc, (float)eye.where.yloc, 130.0f - 160.0f, bottomX, bottomY)) {
+ return -1;
+ }
+ if (!projectCorridorPointRaw(screenR, cameraLook, cameraLookY, sint, cost, cameraX, cameraY,
+ (float)eye.where.xloc, (float)eye.where.yloc, 155.0f - 160.0f, centerX, centerY)) {
+ return -1;
+ }
+ return ABS(bottomY - centerY);
+}
+
+EnemyEyePair buildEnemyEyePair(const Common::Rect &screenR, const Colony::Thing &obj,
+ uint8 cameraLook, int8 cameraLookY, int cameraX, int cameraY,
+ const int *sint, const int *cost) {
+ EnemyEyePair eyes;
+ eyes.left = obj;
+ eyes.right = obj;
+
+ const int32 s1 = sint[obj.where.ang] >> 1;
+ const int32 c1 = cost[obj.where.ang] >> 1;
+ const int32 s2 = s1 >> 1;
+ const int32 c2 = c1 >> 1;
+ const int32 eyeBaseX = obj.where.xloc + c1;
+ const int32 eyeBaseY = obj.where.yloc + s1;
+
+ eyes.left.where.xloc = (int)(eyeBaseX - s2);
+ eyes.left.where.yloc = (int)(eyeBaseY + c2);
+ resetObjectBounds(screenR, eyes.left.where);
+
+ eyes.right.where.xloc = (int)(eyeBaseX + s2);
+ eyes.right.where.yloc = (int)(eyeBaseY - c2);
+ resetObjectBounds(screenR, eyes.right.where);
+
+ const int leftDiameter = projectedEnemyEyeDiameter(screenR, eyes.left, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+ const int rightDiameter = projectedEnemyEyeDiameter(screenR, eyes.right, cameraLook, cameraLookY, cameraX, cameraY, sint, cost);
+ if (leftDiameter >= 0 && rightDiameter >= 0) {
+ eyes.leftFirst = leftDiameter < rightDiameter;
+ } else {
+ const int32 leftDx = eyes.left.where.xloc - cameraX;
+ const int32 leftDy = eyes.left.where.yloc - cameraY;
+ const int32 rightDx = eyes.right.where.xloc - cameraX;
+ const int32 rightDy = eyes.right.where.yloc - cameraY;
+ const int64 leftDist = (int64)leftDx * leftDx + (int64)leftDy * leftDy;
+ const int64 rightDist = (int64)rightDx * rightDx + (int64)rightDy * rightDy;
+ eyes.leftFirst = leftDist >= rightDist;
+ }
+
+ return eyes;
+}
+
void ColonyEngine::drawStaticObjects() {
for (uint i = 0; i < _objects.size(); i++) {
Thing &obj = _objects[i];
@@ -1689,101 +1814,98 @@ bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
break;
case kRobQueen:
{
- const int32 s1 = _sint[obj.where.ang] >> 1;
- const int32 c1 = _cost[obj.where.ang] >> 1;
- const int32 s2 = s1 >> 1;
- const int32 c2 = c1 >> 1;
- const int32 eyeBaseX = obj.where.xloc + c1;
- const int32 eyeBaseY = obj.where.yloc + s1;
-
- Thing leftEye = obj;
- leftEye.where.xloc = (int)(eyeBaseX - s2);
- leftEye.where.yloc = (int)(eyeBaseY + c2);
- resetObjectBounds(_screenR, leftEye.where);
-
- Thing rightEye = obj;
- rightEye.where.xloc = (int)(eyeBaseX + s2);
- rightEye.where.yloc = (int)(eyeBaseY - c2);
- resetObjectBounds(_screenR, rightEye.where);
-
- const int32 leftDist = (leftEye.where.xloc - _me.xloc) * (leftEye.where.xloc - _me.xloc) +
- (leftEye.where.yloc - _me.yloc) * (leftEye.where.yloc - _me.yloc);
- const int32 rightDist = (rightEye.where.xloc - _me.xloc) * (rightEye.where.xloc - _me.xloc) +
- (rightEye.where.yloc - _me.yloc) * (rightEye.where.yloc - _me.yloc);
- const bool leftFirst = leftDist >= rightDist;
- Thing &farEye = leftFirst ? leftEye : rightEye;
- Thing &nearEye = leftFirst ? rightEye : leftEye;
- const PrismPartDef &farWing = leftFirst ? kQLWingDef : kQRWingDef;
- const PrismPartDef &nearWing = leftFirst ? kQRWingDef : kQLWingDef;
+ EnemyEyePair eyes = buildEnemyEyePair(_screenR, obj, _me.look, _me.lookY, _me.xloc, _me.yloc, _sint, _cost);
+ Thing &firstEye = eyes.leftFirst ? eyes.left : eyes.right;
+ Thing &secondEye = eyes.leftFirst ? eyes.right : eyes.left;
+ const PrismPartDef &firstWing = eyes.leftFirst ? kQLWingDef : kQRWingDef;
+ const PrismPartDef &secondWing = eyes.leftFirst ? kQRWingDef : kQLWingDef;
const int wingColor = (!isMacRenderMode() && _level == 7) ? kColorQueenWingRed : kColorClear;
-
- // DOS draweyes(): queen eyeball is RED fill + WHITE outline
- // (hardcoded, not from color table). Iris is GREEN.
- const int queenEyeballColor = 5; // vRED (EGA index 4 = kColorRed... use raw 5 = cRED index)
- draw3DPrism(obj, farWing, false, wingColor, true, true);
- draw3DSphere(farEye, 0, 0, 130, 0, 0, 155, queenEyeballColor, kColorBlack, true);
- drawEyeOverlays3D(farEye, kQIrisDef, -1, kQPupilDef, pupilColor, true);
- if (farEye.where.xmn < obj.where.xmn)
- obj.where.xmn = farEye.where.xmn;
- if (farEye.where.xmx > obj.where.xmx)
- obj.where.xmx = farEye.where.xmx;
- if (farEye.where.zmn < obj.where.zmn)
- obj.where.zmn = farEye.where.zmn;
- if (farEye.where.zmx > obj.where.zmx)
- obj.where.zmx = farEye.where.zmx;
-
- draw3DPrism(obj, kQThoraxDef, false, -1, true, false);
- draw3DPrism(obj, kQAbdomenDef, false, -1, true, false);
-
- draw3DPrism(obj, nearWing, false, wingColor, true, true);
- draw3DSphere(nearEye, 0, 0, 130, 0, 0, 155, queenEyeballColor, kColorBlack, true);
- drawEyeOverlays3D(nearEye, kQIrisDef, -1, kQPupilDef, pupilColor, true);
- if (nearEye.where.xmn < obj.where.xmn)
- obj.where.xmn = nearEye.where.xmn;
- if (nearEye.where.xmx > obj.where.xmx)
- obj.where.xmx = nearEye.where.xmx;
- if (nearEye.where.zmn < obj.where.zmn)
- obj.where.zmn = nearEye.where.zmn;
- if (nearEye.where.zmx > obj.where.zmx)
- obj.where.zmx = nearEye.where.zmx;
+ const int enemyEyeballColor = isMacRenderMode() ? eyeballColor : 5;
+ int layer = 12;
+ auto setNextDepthRange = [&]() {
+ _gfx->setDepthRange((layer--) * 0.002f, 1.0f);
+ };
+
+ auto drawEye = [&](Thing &eye) {
+ draw3DSphere(eye, 0, 0, 130, 0, 0, 155, enemyEyeballColor, kColorBlack, true);
+ drawEyeOverlays3D(eye, kQIrisDef, kColorQueenEye, kQPupilDef, pupilColor, true);
+ mergeObjectBounds(obj.where, eye.where);
+ };
+ auto drawWingEye = [&](Thing &eye, const PrismPartDef &wing) {
+ setNextDepthRange();
+ draw3DPrism(obj, wing, false, wingColor, true, true);
+ setNextDepthRange();
+ drawEye(eye);
+ };
+ auto drawThorax = [&]() {
+ setNextDepthRange();
+ draw3DPrism(obj, kQThoraxDef, false, -1, true, false);
+ };
+ auto drawAbdomen = [&]() {
+ setNextDepthRange();
+ draw3DPrism(obj, kQAbdomenDef, false, -1, true, false);
+ };
+
+ const int body = queenBodyOrder(_screenR, obj, _me.look, _me.lookY, _me.xloc, _me.yloc, _sint, _cost);
+ if (body == 0) {
+ drawThorax();
+ drawAbdomen();
+ }
+ drawWingEye(firstEye, firstWing);
+ if (body == 1) {
+ const bool abdomenBackVisible = prismSurfaceVisible(_screenR, obj, kQAbdomenDef, 8,
+ _me.look, _me.lookY, _me.xloc, _me.yloc, _sint, _cost);
+ if (abdomenBackVisible) {
+ drawAbdomen();
+ drawThorax();
+ } else {
+ drawThorax();
+ drawAbdomen();
+ }
+ }
+ drawWingEye(secondEye, secondWing);
+ if (body == 2) {
+ drawAbdomen();
+ drawThorax();
+ }
+ _gfx->setDepthRange(0.0f, 1.0f);
}
break;
case kRobDrone:
{
- // DOS DRONE.C: body + seteyes()/draweyes() â same two-eye
- // system as Queen, positioned at offsets from body center.
- const int32 s1 = _sint[obj.where.ang] >> 1;
- const int32 c1 = _cost[obj.where.ang] >> 1;
- const int32 s2 = s1 >> 1;
- const int32 c2 = c1 >> 1;
- const int32 eyeBaseX = obj.where.xloc + c1;
- const int32 eyeBaseY = obj.where.yloc + s1;
-
- Thing leftEye = obj;
- leftEye.where.xloc = (int)(eyeBaseX - s2);
- leftEye.where.yloc = (int)(eyeBaseY + c2);
- resetObjectBounds(_screenR, leftEye.where);
- Thing rightEye = obj;
- rightEye.where.xloc = (int)(eyeBaseX + s2);
- rightEye.where.yloc = (int)(eyeBaseY - c2);
- resetObjectBounds(_screenR, rightEye.where);
-
- // DOS draweyes(): shared with Queen â RED eyeball, GREEN iris
- draw3DPrism(obj, kDAbdomenDef, false, -1, true, false);
- draw3DPrism(obj, kDLLPincerDef, false, -1, true, false);
- draw3DPrism(obj, kDRRPincerDef, false, -1, true, false);
- draw3DPrism(obj, kDLEyeDef, false, -1, true, false);
- draw3DPrism(obj, kDREyeDef, false, -1, true, false);
- draw3DSphere(leftEye, 0, 0, 130, 0, 0, 155, 5, kColorBlack, true);
- drawEyeOverlays3D(leftEye, kQIrisDef, -1, kQPupilDef, pupilColor, true);
- draw3DSphere(rightEye, 0, 0, 130, 0, 0, 155, 5, kColorBlack, true);
- drawEyeOverlays3D(rightEye, kQIrisDef, -1, kQPupilDef, pupilColor, true);
+ EnemyEyePair eyes = buildEnemyEyePair(_screenR, obj, _me.look, _me.lookY, _me.xloc, _me.yloc, _sint, _cost);
+ Thing &firstEye = eyes.leftFirst ? eyes.left : eyes.right;
+ Thing &secondEye = eyes.leftFirst ? eyes.right : eyes.left;
+ const int enemyEyeballColor = isMacRenderMode() ? eyeballColor : 5;
+ int layer = 6;
+ auto setNextDepthRange = [&]() {
+ _gfx->setDepthRange((layer--) * 0.002f, 1.0f);
+ };
+ auto drawBody = [&]() {
+ setNextDepthRange();
+ draw3DPrism(obj, kDAbdomenDef, false, -1, true, false);
+ };
+ auto drawEye = [&](Thing &eye) {
+ setNextDepthRange();
+ draw3DSphere(eye, 0, 0, 130, 0, 0, 155, enemyEyeballColor, kColorBlack, true);
+ drawEyeOverlays3D(eye, kQIrisDef, kColorDroneEye, kQPupilDef, pupilColor, true);
+ mergeObjectBounds(obj.where, eye.where);
+ };
+
+ const int body = droneBodyOrder(_screenR, obj, _me.look, _me.lookY, _me.xloc, _me.yloc, _sint, _cost);
+ if (body == 0)
+ drawBody();
+ drawEye(firstEye);
+ if (body == 1)
+ drawBody();
+ drawEye(secondEye);
+ if (body == 2)
+ drawBody();
+ _gfx->setDepthRange(0.0f, 1.0f);
}
break;
case kRobSoldier:
{
- // DOS DRONE.C MakeSoldier(): body + animated pincers +
- // seteyes()/draweyes() â same two-eye system as Queen.
int leftPincerPts[4][3];
int rightPincerPts[4][3];
const int lookAmount = (obj.where.lookx < 0) ? -obj.where.lookx : obj.where.lookx;
@@ -1800,32 +1922,63 @@ bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
const PrismPartDef leftPincerDef = {4, leftPincerPts, 4, kDLPincerSurf};
const PrismPartDef rightPincerDef = {4, rightPincerPts, 4, kDRPincerSurf};
- const int32 s1 = _sint[obj.where.ang] >> 1;
- const int32 c1 = _cost[obj.where.ang] >> 1;
- const int32 s2 = s1 >> 1;
- const int32 c2 = c1 >> 1;
- const int32 eyeBaseX = obj.where.xloc + c1;
- const int32 eyeBaseY = obj.where.yloc + s1;
-
- Thing leftEye = obj;
- leftEye.where.xloc = (int)(eyeBaseX - s2);
- leftEye.where.yloc = (int)(eyeBaseY + c2);
- resetObjectBounds(_screenR, leftEye.where);
- Thing rightEye = obj;
- rightEye.where.xloc = (int)(eyeBaseX + s2);
- rightEye.where.yloc = (int)(eyeBaseY - c2);
- resetObjectBounds(_screenR, rightEye.where);
-
- draw3DPrism(obj, kDAbdomenDef, false, kColorSoldierBody, true, false);
- draw3DPrism(obj, leftPincerDef, false, -1, true, false);
- draw3DPrism(obj, rightPincerDef, false, -1, true, false);
- // DOS draweyes(): shared with Queen â RED eyeball, GREEN iris
- draw3DPrism(obj, kDLEyeDef, false, kColorSoldierEye, true, false);
- draw3DPrism(obj, kDREyeDef, false, kColorSoldierEye, true, false);
- draw3DSphere(leftEye, 0, 0, 130, 0, 0, 155, 5, kColorBlack, true);
- drawEyeOverlays3D(leftEye, kQIrisDef, -1, kQPupilDef, pupilColor, true);
- draw3DSphere(rightEye, 0, 0, 130, 0, 0, 155, 5, kColorBlack, true);
- drawEyeOverlays3D(rightEye, kQIrisDef, -1, kQPupilDef, pupilColor, true);
+ EnemyEyePair eyes = buildEnemyEyePair(_screenR, obj, _me.look, _me.lookY, _me.xloc, _me.yloc, _sint, _cost);
+ Thing &firstEye = eyes.leftFirst ? eyes.left : eyes.right;
+ Thing &secondEye = eyes.leftFirst ? eyes.right : eyes.left;
+ const int enemyEyeballColor = isMacRenderMode() ? eyeballColor : 5;
+ const float leftPincerDepth = prismPointForwardDepth(obj, leftPincerDef, 1, _me.look, _me.xloc, _me.yloc, _sint, _cost);
+ const float rightPincerDepth = prismPointForwardDepth(obj, rightPincerDef, 1, _me.look, _me.xloc, _me.yloc, _sint, _cost);
+ int layer = 8;
+ auto setNextDepthRange = [&]() {
+ _gfx->setDepthRange((layer--) * 0.002f, 1.0f);
+ };
+ auto drawBody = [&]() {
+ setNextDepthRange();
+ draw3DPrism(obj, kDAbdomenDef, false, kColorSoldierBody, true, false);
+ };
+ auto drawEye = [&](Thing &eye) {
+ setNextDepthRange();
+ draw3DSphere(eye, 0, 0, 130, 0, 0, 155, enemyEyeballColor, kColorBlack, true);
+ drawEyeOverlays3D(eye, kQIrisDef, kColorSoldierEye, kQPupilDef, pupilColor, true);
+ mergeObjectBounds(obj.where, eye.where);
+ };
+ auto drawPincers = [&]() {
+ if (leftPincerDepth < rightPincerDepth) {
+ setNextDepthRange();
+ draw3DPrism(obj, rightPincerDef, false, -1, true, false);
+ setNextDepthRange();
+ draw3DPrism(obj, leftPincerDef, false, -1, true, false);
+ } else {
+ setNextDepthRange();
+ draw3DPrism(obj, leftPincerDef, false, -1, true, false);
+ setNextDepthRange();
+ draw3DPrism(obj, rightPincerDef, false, -1, true, false);
+ }
+ };
+
+ const int body = droneBodyOrder(_screenR, obj, _me.look, _me.lookY, _me.xloc, _me.yloc, _sint, _cost);
+ switch (body) {
+ case 0:
+ drawBody();
+ drawEye(firstEye);
+ drawEye(secondEye);
+ drawPincers();
+ break;
+ case 1:
+ drawEye(firstEye);
+ drawBody();
+ drawPincers();
+ drawEye(secondEye);
+ break;
+ case 2:
+ default:
+ drawEye(firstEye);
+ drawEye(secondEye);
+ drawPincers();
+ drawBody();
+ break;
+ }
+ _gfx->setDepthRange(0.0f, 1.0f);
}
break;
case kRobSnoop:
Commit: 114ad8c194f440fc166840900be0b5c1cced3962
https://github.com/scummvm/scummvm/commit/114ad8c194f440fc166840900be0b5c1cced3962
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-05T19:28:08+02:00
Commit Message:
COLONY: more fluent animations for grow/shrink enemies
Changed paths:
engines/colony/colony.cpp
engines/colony/colony.h
engines/colony/render_objects.cpp
diff --git a/engines/colony/colony.cpp b/engines/colony/colony.cpp
index abdeed7de21..c0f53f56991 100644
--- a/engines/colony/colony.cpp
+++ b/engines/colony/colony.cpp
@@ -849,6 +849,7 @@ Common::Error ColonyEngine::run() {
bool mouseMoved = false;
uint32 lastMoveTick = _system->getMillis();
uint32 lastColonyTick = lastMoveTick;
+ _lastColonyThinkTime = lastColonyTick;
uint32 lastBattleTick = lastMoveTick;
uint32 lastCenterTick = lastMoveTick;
while (!shouldQuit()) {
@@ -876,6 +877,7 @@ Common::Error ColonyEngine::run() {
// 125ms (~8fps) matches the original balance for robot aggression.
if (_gameMode == kModeColony && now - lastColonyTick >= 125) {
lastColonyTick = now;
+ _lastColonyThinkTime = now;
cThink();
}
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index f3de2a2e2df..fa50a5435c8 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -557,6 +557,7 @@ private:
uint32 _blackoutColor = 0;
uint32 _lastClickTime = 0;
uint32 _displayCount = 0; // Frame counter for COLOR wall animation (Mac: count)
+ uint32 _lastColonyThinkTime = 0; // Last 125ms CThink tick, for render-only interpolation
uint32 _lastHotfootTime = 0; // Time-gate for HOTFOOT damage (~8fps)
uint32 _lastAnimUpdate = 0;
uint32 _lastWarningChimeTime = 0;
@@ -654,6 +655,10 @@ private:
void drawPrismOval3D(Thing &thing, const PrismPartDef &def, bool useLook, int colorOverride, bool forceVisible = false);
void drawEyeOverlays3D(Thing &thing, const PrismPartDef &irisDef, int irisColorOverride,
const PrismPartDef &pupilDef, int pupilColorOverride, bool useLook);
+ float growRenderTickFraction() const;
+ bool drawInterpolatedGrowRobot(Thing &obj, int eyeballColor, int pupilColor);
+ void drawInterpolatedGrowPrism(Thing &obj, const PrismPartDef &fromDef, const PrismPartDef &toDef, float progress);
+ void drawInterpolatedGrowEye(Thing &obj, int fromStage, int toStage, float progress, int eyeballColor, int pupilColor);
bool drawStaticObjectPrisms3D(Thing &obj);
void initRobots();
void renderCorridor3D();
diff --git a/engines/colony/render_objects.cpp b/engines/colony/render_objects.cpp
index 60cbf8bfde0..b5d55332ac9 100644
--- a/engines/colony/render_objects.cpp
+++ b/engines/colony/render_objects.cpp
@@ -1452,6 +1452,180 @@ void ColonyEngine::drawEyeOverlays3D(Thing &thing, const PrismPartDef &irisDef,
drawPrismOval3D(thing, pupilDef, useLook, pupilColorOverride, true);
}
+namespace {
+
+int interpolatedRobotPoint(int from, int to, float progress) {
+ return (int)roundf((float)from + ((float)to - (float)from) * progress);
+}
+
+bool robotGrowShapeAndStage(int type, int &shape, int &stage) {
+ if (type < kRobEye || type > kRobMUPyramid)
+ return false;
+
+ shape = (type - kRobEye) & 3;
+ stage = (type - kRobEye) >> 2;
+ return true;
+}
+
+const ColonyEngine::PrismPartDef *growPrismDefForStage(int shape, int stage) {
+ switch (shape) {
+ case 1:
+ switch (stage) {
+ case 0: return &kPyramidBodyDef;
+ case 1: return &kFPyramidBodyDef;
+ case 2: return &kSPyramidBodyDef;
+ case 3: return &kMPyramidBodyDef;
+ default: return nullptr;
+ }
+ case 2:
+ switch (stage) {
+ case 0: return &kCubeBodyDef;
+ case 1: return &kFCubeBodyDef;
+ case 2: return &kSCubeBodyDef;
+ case 3: return &kMCubeBodyDef;
+ default: return nullptr;
+ }
+ case 3:
+ switch (stage) {
+ case 0: return &kUPyramidBodyDef;
+ case 1: return &kFUPyramidBodyDef;
+ case 2: return &kSUPyramidBodyDef;
+ case 3: return &kMUPyramidBodyDef;
+ default: return nullptr;
+ }
+ default:
+ return nullptr;
+ }
+}
+
+const ColonyEngine::PrismPartDef &growEyeIrisDefForStage(int stage) {
+ switch (stage) {
+ case 0: return kEyeIrisDef;
+ case 1: return kFEyeIrisDef;
+ case 2: return kSEyeIrisDef;
+ case 3:
+ default:
+ return kMEyeIrisDef;
+ }
+}
+
+const ColonyEngine::PrismPartDef &growEyePupilDefForStage(int stage) {
+ switch (stage) {
+ case 0: return kEyePupilDef;
+ case 1: return kFEyePupilDef;
+ case 2: return kSEyePupilDef;
+ case 3:
+ default:
+ return kMEyePupilDef;
+ }
+}
+
+} // namespace
+
+float ColonyEngine::growRenderTickFraction() const {
+ const uint32 kColonyThinkIntervalMs = 125;
+ const uint32 now = _system->getMillis();
+
+ if (_lastColonyThinkTime == 0 || now <= _lastColonyThinkTime)
+ return 0.0f;
+
+ const uint32 elapsed = now - _lastColonyThinkTime;
+ if (elapsed >= kColonyThinkIntervalMs)
+ return 1.0f;
+
+ return (float)elapsed / (float)kColonyThinkIntervalMs;
+}
+
+void ColonyEngine::drawInterpolatedGrowPrism(Thing &obj, const PrismPartDef &fromDef, const PrismPartDef &toDef, float progress) {
+ if (fromDef.pointCount != toDef.pointCount || fromDef.surfaceCount != toDef.surfaceCount)
+ return;
+
+ int points[8][3];
+ assert(fromDef.pointCount <= ARRAYSIZE(points));
+ for (int i = 0; i < fromDef.pointCount; ++i) {
+ points[i][0] = interpolatedRobotPoint(fromDef.points[i][0], toDef.points[i][0], progress);
+ points[i][1] = interpolatedRobotPoint(fromDef.points[i][1], toDef.points[i][1], progress);
+ points[i][2] = interpolatedRobotPoint(fromDef.points[i][2], toDef.points[i][2], progress);
+ }
+
+ const PrismPartDef def = {fromDef.pointCount, points, fromDef.surfaceCount, fromDef.surfaces};
+ draw3DPrism(obj, def, false, -1, true, false);
+}
+
+void ColonyEngine::drawInterpolatedGrowEye(Thing &obj, int fromStage, int toStage, float progress, int eyeballColor, int pupilColor) {
+ const int sphereZ[4][2] = {
+ {100, 200},
+ {0, 100},
+ {0, 50},
+ {0, 25}
+ };
+ int irisPoints[4][3];
+ int pupilPoints[4][3];
+
+ fromStage = CLIP(fromStage, 0, 3);
+ toStage = CLIP(toStage, 0, 3);
+
+ const int z0 = interpolatedRobotPoint(sphereZ[fromStage][0], sphereZ[toStage][0], progress);
+ const int z1 = interpolatedRobotPoint(sphereZ[fromStage][1], sphereZ[toStage][1], progress);
+ draw3DSphere(obj, 0, 0, z0, 0, 0, z1, eyeballColor, kColorBlack, true);
+
+ const PrismPartDef &fromIris = growEyeIrisDefForStage(fromStage);
+ const PrismPartDef &toIris = growEyeIrisDefForStage(toStage);
+ const PrismPartDef &fromPupil = growEyePupilDefForStage(fromStage);
+ const PrismPartDef &toPupil = growEyePupilDefForStage(toStage);
+ for (int i = 0; i < 4; ++i) {
+ irisPoints[i][0] = interpolatedRobotPoint(fromIris.points[i][0], toIris.points[i][0], progress);
+ irisPoints[i][1] = interpolatedRobotPoint(fromIris.points[i][1], toIris.points[i][1], progress);
+ irisPoints[i][2] = interpolatedRobotPoint(fromIris.points[i][2], toIris.points[i][2], progress);
+ pupilPoints[i][0] = interpolatedRobotPoint(fromPupil.points[i][0], toPupil.points[i][0], progress);
+ pupilPoints[i][1] = interpolatedRobotPoint(fromPupil.points[i][1], toPupil.points[i][1], progress);
+ pupilPoints[i][2] = interpolatedRobotPoint(fromPupil.points[i][2], toPupil.points[i][2], progress);
+ }
+
+ const PrismPartDef irisDef = {4, irisPoints, fromIris.surfaceCount, fromIris.surfaces};
+ const PrismPartDef pupilDef = {4, pupilPoints, fromPupil.surfaceCount, fromPupil.surfaces};
+ const int irisColor = (toStage == 3 && progress >= 0.5f) ? kColorMiniEyeIris : -1;
+ drawEyeOverlays3D(obj, irisDef, irisColor, pupilDef, pupilColor, false);
+}
+
+bool ColonyEngine::drawInterpolatedGrowRobot(Thing &obj, int eyeballColor, int pupilColor) {
+ int shape = 0;
+ int stage = 0;
+ if (obj.grow == 0 || !robotGrowShapeAndStage(obj.type, shape, stage))
+ return false;
+
+ int targetStage = stage;
+ if (obj.grow > 0) {
+ if (stage <= 0 || stage >= 3)
+ return false;
+ targetStage = stage - 1;
+ } else {
+ if (stage <= 0 || stage >= 3)
+ return false;
+ targetStage = stage + 1;
+ }
+
+ const int count = CLIP(obj.count, 0, 3);
+ float progress = ((float)count + growRenderTickFraction()) * 0.25f;
+ if (progress < 0.0f)
+ progress = 0.0f;
+ if (progress > 1.0f)
+ progress = 1.0f;
+
+ if (shape == 0) {
+ drawInterpolatedGrowEye(obj, stage, targetStage, progress, eyeballColor, pupilColor);
+ return true;
+ }
+
+ const PrismPartDef *fromDef = growPrismDefForStage(shape, stage);
+ const PrismPartDef *toDef = growPrismDefForStage(shape, targetStage);
+ if (!fromDef || !toDef)
+ return false;
+
+ drawInterpolatedGrowPrism(obj, *fromDef, *toDef, progress);
+ return true;
+}
+
bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
const int eyeballColor = (_level == 1 || _level == 7) ? kColorPupil : kColorEyeball;
const int pupilColor = (_level == 1 || _level == 7) ? kColorEyeball : kColorPupil;
@@ -1774,42 +1948,66 @@ bool ColonyEngine::drawStaticObjectPrisms3D(Thing &obj) {
_gfx->setDepthRange(0.0f, 1.0f);
break;
case kRobFEye:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DSphere(obj, 0, 0, 0, 0, 0, 100, eyeballColor, kColorBlack, true);
drawEyeOverlays3D(obj, kFEyeIrisDef, -1, kFEyePupilDef, pupilColor, false);
break;
case kRobFPyramid:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kFPyramidBodyDef, false, -1, true, false);
break;
case kRobFCube:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kFCubeBodyDef, false, -1, true, false);
break;
case kRobFUPyramid:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kFUPyramidBodyDef, false, -1, true, false);
break;
case kRobSEye:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DSphere(obj, 0, 0, 0, 0, 0, 50, eyeballColor, kColorBlack, true);
drawEyeOverlays3D(obj, kSEyeIrisDef, -1, kSEyePupilDef, pupilColor, false);
break;
case kRobSPyramid:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kSPyramidBodyDef, false, -1, true, false);
break;
case kRobSCube:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kSCubeBodyDef, false, -1, true, false);
break;
case kRobSUPyramid:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kSUPyramidBodyDef, false, -1, true, false);
break;
case kRobMEye:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DSphere(obj, 0, 0, 0, 0, 0, 25, eyeballColor, kColorBlack, true);
drawEyeOverlays3D(obj, kMEyeIrisDef, kColorMiniEyeIris, kMEyePupilDef, pupilColor, false);
break;
case kRobMPyramid:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kMPyramidBodyDef, false, -1, true, false);
break;
case kRobMCube:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kMCubeBodyDef, false, -1, true, false);
break;
case kRobMUPyramid:
+ if (drawInterpolatedGrowRobot(obj, eyeballColor, pupilColor))
+ break;
draw3DPrism(obj, kMUPyramidBodyDef, false, -1, true, false);
break;
case kRobQueen:
Commit: 8ff586d3ddef50330ac7903bac42326b4b846bc6
https://github.com/scummvm/scummvm/commit/8ff586d3ddef50330ac7903bac42326b4b846bc6
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-05T19:42:52+02:00
Commit Message:
COLONY: better detection for aiming enemies
Changed paths:
engines/colony/colony.h
engines/colony/interaction.cpp
engines/colony/render_objects.cpp
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index fa50a5435c8..9650a513ad9 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -689,6 +689,10 @@ private:
// shoot.c: shooting and power management
void setPower(int p0, int p1, int p2);
void cShoot();
+ bool isShootableRobotType(int type) const;
+ bool isShotBlockingObjectType(int type) const;
+ int findAimedObject(const Common::Point &aim, bool *isBlocker = nullptr, int *targetDist = nullptr) const;
+ bool hasAimedRobotTarget() const;
void destroyRobot(int num);
void doShootCircles(int cx, int cy);
void doBurnHole(int cx, int cy, int radius);
diff --git a/engines/colony/interaction.cpp b/engines/colony/interaction.cpp
index 3f791c564d4..f786284222a 100644
--- a/engines/colony/interaction.cpp
+++ b/engines/colony/interaction.cpp
@@ -342,6 +342,62 @@ void ColonyEngine::setPower(int p0, int p1, int p2) {
}
}
+bool ColonyEngine::isShootableRobotType(int type) const {
+ return (type >= kRobEye && type <= kRobUPyramid) ||
+ type == kRobQueen || type == kRobDrone || type == kRobSoldier;
+}
+
+bool ColonyEngine::isShotBlockingObjectType(int type) const {
+ return type == kObjForkLift || type == kObjTeleport || type == kObjPToilet ||
+ type == kObjBox2 || type == kObjReactor || type == kObjScreen;
+}
+
+int ColonyEngine::findAimedObject(const Common::Point &aim, bool *isBlocker, int *targetDist) const {
+ int bestIdx = -1;
+ int bestDist = INT_MAX;
+ bool bestIsBlocker = false;
+
+ for (uint i = 0; i < _objects.size(); i++) {
+ const Thing &obj = _objects[i];
+ if (!obj.alive)
+ continue;
+
+ if (obj.where.xmn > obj.where.xmx || obj.where.zmn > obj.where.zmx)
+ continue;
+ if (obj.where.xmn > aim.x || obj.where.xmx < aim.x ||
+ obj.where.zmn > aim.y || obj.where.zmx < aim.y)
+ continue;
+
+ const int type = obj.type;
+ const bool isRobot = isShootableRobotType(type);
+ const bool isBlockerObject = isShotBlockingObjectType(type);
+ if (!isRobot && !isBlockerObject)
+ continue;
+
+ const int dx = obj.where.xloc - _me.xloc;
+ const int dy = obj.where.yloc - _me.yloc;
+ const int dist = (int)sqrtf((float)(dx * dx + dy * dy));
+
+ if (dist < bestDist) {
+ bestDist = dist;
+ bestIdx = (int)i + 1; // 1-based object index
+ bestIsBlocker = isBlockerObject;
+ }
+ }
+
+ if (isBlocker)
+ *isBlocker = bestIsBlocker;
+ if (targetDist)
+ *targetDist = bestDist;
+ return bestIdx;
+}
+
+bool ColonyEngine::hasAimedRobotTarget() const {
+ bool blocker = false;
+ const int target = findAimedObject(getAimPoint(), &blocker);
+ return target > 0 && !blocker && isShootableRobotType(_objects[target - 1].type);
+}
+
// shoot.c CShoot(): player fires weapon at the cursor or screen center.
// Original hit detection uses the rendered object bounds on screen.
void ColonyEngine::cShoot() {
@@ -368,39 +424,9 @@ void ColonyEngine::cShoot() {
// Drain weapons power: -(1 << level) per shot
setPower(-(1 << _level), 0, 0);
- int bestIdx = -1;
- int bestDist = INT_MAX;
bool bestIsBlocker = false;
-
- for (uint i = 0; i < _objects.size(); i++) {
- const Thing &obj = _objects[i];
- if (!obj.alive)
- continue;
-
- if (obj.where.xmn > obj.where.xmx || obj.where.zmn > obj.where.zmx)
- continue;
- if (obj.where.xmn > cx || obj.where.xmx < cx ||
- obj.where.zmn > cy || obj.where.zmx < cy)
- continue;
-
- int t = obj.type;
- bool isRobot = (t >= kRobEye && t <= kRobUPyramid) ||
- t == kRobQueen || t == kRobDrone || t == kRobSoldier;
- bool blocksShot = (t == kObjForkLift || t == kObjTeleport || t == kObjPToilet ||
- t == kObjBox2 || t == kObjReactor || t == kObjScreen);
- if (!isRobot && !blocksShot)
- continue;
-
- int dx = obj.where.xloc - _me.xloc;
- int dy = obj.where.yloc - _me.yloc;
- int dist = (int)sqrtf((float)(dx * dx + dy * dy));
-
- if (dist < bestDist) {
- bestDist = dist;
- bestIdx = (int)i + 1; // 1-based robot index
- bestIsBlocker = blocksShot;
- }
- }
+ int bestDist = INT_MAX;
+ const int bestIdx = findAimedObject(aim, &bestIsBlocker, &bestDist);
if (bestIdx > 0 && !bestIsBlocker) {
const Thing &target = _objects[bestIdx - 1];
diff --git a/engines/colony/render_objects.cpp b/engines/colony/render_objects.cpp
index b5d55332ac9..5d49db97a8e 100644
--- a/engines/colony/render_objects.cpp
+++ b/engines/colony/render_objects.cpp
@@ -1309,15 +1309,11 @@ void ColonyEngine::drawStaticObjects() {
if (ox < 0 || ox >= 32 || oy < 0 || oy >= 32 || !_visibleCell[ox][oy])
continue;
drawStaticObjectPrisms3D(obj);
- // MAKEROBO.C: if shootable robot straddles centerX, set insight
- // (narrows crosshair brackets to indicate a target is in the line of fire)
- int t = obj.type;
- if ((t >= kRobEye && t <= kRobUPyramid) ||
- (t >= kRobQueen && t <= kRobSoldier)) {
- if (obj.where.xmn < _centerX && obj.where.xmx > _centerX)
- _insight = true;
- }
}
+
+ // Use the same rendered-bounds target selection as cShoot() so the
+ // narrowed crosshair only appears for a shot that would select a robot.
+ _insight = _weapons > 0 && hasAimedRobotTarget();
}
void ColonyEngine::drawPrismOval3D(Thing &thing, const PrismPartDef &def, bool useLook, int colorOverride, bool forceVisible) {
Commit: e997e181e40ae1b9469c26f5591ee1137dfc0e0d
https://github.com/scummvm/scummvm/commit/e997e181e40ae1b9469c26f5591ee1137dfc0e0d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-05T21:36:22+02:00
Commit Message:
COLONY: better detection collision detection with static objects
Changed paths:
engines/colony/colony.cpp
engines/colony/colony.h
engines/colony/interaction.cpp
engines/colony/map.cpp
engines/colony/movement.cpp
engines/colony/render_objects.cpp
engines/colony/savegame.cpp
engines/colony/sound.cpp
diff --git a/engines/colony/colony.cpp b/engines/colony/colony.cpp
index c0f53f56991..df1733b3671 100644
--- a/engines/colony/colony.cpp
+++ b/engines/colony/colony.cpp
@@ -51,8 +51,6 @@
namespace Colony {
-namespace {
-
const float kMouseLookSensitivity = 0.25f;
const float kKeyboardTurnSpeed = 30.0f; // angle units per second
@@ -131,8 +129,6 @@ Graphics::Cursor *cloneAndScaleCursor(const Graphics::Cursor &src, int scale) {
src.getPaletteStartIndex(), src.getPaletteCount(), scale);
}
-} // anonymous namespace
-
ColonyEngine::ColonyEngine(OSystem *syst, const ADGameDescription *gd) : Engine(syst), _gameDescription(gd), _randomSource("colony") {
_level = 0;
_robotNum = 0;
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index 9650a513ad9..15678f332a5 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -466,6 +466,9 @@ public:
int checkwallMoveTo(int xnew, int ynew, int xind2, int yind2, Locate *pobject, uint8 trailCode);
int checkwallTryFeature(int xnew, int ynew, int xind2, int yind2, Locate *pobject, int dir);
int checkwall(int xnew, int ynew, Locate *pobject);
+ void clearPlayerCellMarker();
+ void setPlayerCellMarker();
+ bool playerIntersectsObjectFootprint(const Thing &obj, int xloc, int yloc) const;
void cCommand(int xnew, int ynew, bool allowInteraction);
bool scrollInfo(const Graphics::Font *macFont = nullptr);
bool checkSkipRequested();
@@ -668,7 +671,7 @@ private:
void getWallFace3D(int cellX, int cellY, int direction, float corners[4][3]);
void getCellFace3D(int cellX, int cellY, bool ceiling, float corners[4][3]);
- int occupiedObjectAt(int x, int y, const Locate *pobject);
+ int occupiedObjectAt(int xnew, int ynew, int x, int y, const Locate *pobject);
void interactWithObject(int objNum);
// Convert a mouse coord delivered by the event manager into engine
diff --git a/engines/colony/interaction.cpp b/engines/colony/interaction.cpp
index f786284222a..57709656628 100644
--- a/engines/colony/interaction.cpp
+++ b/engines/colony/interaction.cpp
@@ -175,10 +175,10 @@ void ColonyEngine::interactWithObject(int objNum) {
_me.xloc = (targetX << 8) + 128;
_me.yloc = (targetY << 8) + 128;
_me.ang = _me.look;
- if (oldX >= 0 && oldX < 32 && oldY >= 0 && oldY < 32)
+ if (oldX >= 0 && oldX < 32 && oldY >= 0 && oldY < 32 &&
+ _robotArray[oldX][oldY] == kMeNum)
_robotArray[oldX][oldY] = 0;
- if (_me.xindex >= 0 && _me.xindex < 32 && _me.yindex >= 0 && _me.yindex < 32)
- _robotArray[_me.xindex][_me.yindex] = kMeNum;
+ setPlayerCellMarker();
break;
}
case kObjDrawer:
diff --git a/engines/colony/map.cpp b/engines/colony/map.cpp
index 538cd08c28d..e488ed58ac5 100644
--- a/engines/colony/map.cpp
+++ b/engines/colony/map.cpp
@@ -164,8 +164,7 @@ void ColonyEngine::loadMap(int mnum) {
doPatch(); // apply object relocations from patch table
initRobots(); // spawn robot objects for this level
- if (_me.xindex >= 0 && _me.xindex < 32 && _me.yindex >= 0 && _me.yindex < 32)
- _robotArray[_me.xindex][_me.yindex] = kMeNum;
+ setPlayerCellMarker();
debugC(1, kColonyDebugMap, "Successfully loaded map %d (objects: %d)", mnum, (int)_objects.size());
}
diff --git a/engines/colony/movement.cpp b/engines/colony/movement.cpp
index fd550ab45ed..85179c9cdf8 100644
--- a/engines/colony/movement.cpp
+++ b/engines/colony/movement.cpp
@@ -151,6 +151,118 @@ int tunnelClipCode(const Common::Rect &rect, int x, int y) {
return code;
}
+struct ObjectFootprint {
+ int centerX;
+ int centerY;
+ int halfX;
+ int halfY;
+};
+
+bool objectFootprintForType(int type, ObjectFootprint &fp) {
+ fp.centerX = 0;
+ fp.centerY = 0;
+ fp.halfX = 128;
+ fp.halfY = 128;
+
+ switch (type) {
+ case kObjPlant:
+ fp.halfX = 45;
+ fp.halfY = 45;
+ return true;
+ case kObjCChair:
+ fp.halfX = 55;
+ fp.halfY = 65;
+ return true;
+ case kObjChair:
+ fp.centerX = -15;
+ fp.halfX = 70;
+ fp.halfY = 75;
+ return true;
+ case kObjCouch:
+ fp.centerX = -15;
+ fp.halfX = 80;
+ fp.halfY = 128;
+ return true;
+ case kObjTV:
+ fp.halfX = 35;
+ fp.halfY = 65;
+ return true;
+ case kObjScreen:
+ fp.halfX = 20;
+ fp.halfY = 70;
+ return true;
+ case kObjConsole:
+ fp.centerX = -55;
+ fp.halfX = 55;
+ fp.halfY = 75;
+ return true;
+ case kObjDrawer:
+ fp.centerX = -40;
+ fp.halfX = 45;
+ fp.halfY = 75;
+ return true;
+ case kObjTub:
+ fp.centerX = -64;
+ fp.halfX = 70;
+ fp.halfY = 128;
+ return true;
+ case kObjSink:
+ fp.centerX = -90;
+ fp.halfX = 45;
+ fp.halfY = 60;
+ return true;
+ case kObjToilet:
+ fp.centerX = -75;
+ fp.halfX = 60;
+ fp.halfY = 50;
+ return true;
+ case kObjBench:
+ fp.halfX = 65;
+ fp.halfY = 128;
+ return true;
+ case kObjCBench:
+ fp.centerX = 35;
+ fp.centerY = -35;
+ fp.halfX = 100;
+ fp.halfY = 100;
+ return true;
+ case kObjProjector:
+ fp.halfX = 60;
+ fp.halfY = 60;
+ return true;
+ case kObjPowerSuit:
+ fp.halfX = 120;
+ fp.halfY = 120;
+ return true;
+ case kObjBox1:
+ case kObjBox2:
+ fp.halfX = 55;
+ fp.halfY = 55;
+ return true;
+ case kObjForkLift:
+ fp.halfX = 100;
+ fp.halfY = 120;
+ return true;
+ case kObjCryo:
+ fp.halfX = 128;
+ fp.halfY = 75;
+ return true;
+ case kObjTeleport:
+ fp.halfX = 105;
+ fp.halfY = 105;
+ return true;
+ case kObjDesk:
+ case kObjBed:
+ case kObjBBed:
+ case kObjTable:
+ case kObjReactor:
+ case kObjPToilet:
+ return true;
+ default:
+ return false;
+ }
+}
+
void drawTunnelLine(Renderer *gfx, const Common::Rect &rect, int x1, int y1, int x2, int y2, uint32 color) {
if (rect.isEmpty())
return;
@@ -208,7 +320,44 @@ void drawTunnelLine(Renderer *gfx, const Common::Rect &rect, int x1, int y1, int
}
}
-int ColonyEngine::occupiedObjectAt(int x, int y, const Locate *pobject) {
+void ColonyEngine::clearPlayerCellMarker() {
+ if (_me.xindex >= 0 && _me.xindex < 32 && _me.yindex >= 0 && _me.yindex < 32 &&
+ _robotArray[_me.xindex][_me.yindex] == kMeNum)
+ _robotArray[_me.xindex][_me.yindex] = 0;
+}
+
+void ColonyEngine::setPlayerCellMarker() {
+ if (_me.xindex < 0 || _me.xindex >= 32 || _me.yindex < 0 || _me.yindex >= 32)
+ return;
+
+ const int rnum = _robotArray[_me.xindex][_me.yindex];
+ if (rnum > 0 && rnum != kMeNum && rnum <= (int)_objects.size()) {
+ const Thing &obj = _objects[rnum - 1];
+ if (obj.alive && obj.where.xindex == _me.xindex && obj.where.yindex == _me.yindex)
+ return;
+ }
+
+ _robotArray[_me.xindex][_me.yindex] = kMeNum;
+}
+
+bool ColonyEngine::playerIntersectsObjectFootprint(const Thing &obj, int xloc, int yloc) const {
+ ObjectFootprint fp;
+ if (!objectFootprintForType(obj.type, fp))
+ return true;
+
+ const int kPlayerObjectPad = 32;
+ const int objAng = (obj.where.ang + 32) & 0xFF;
+ const uint8 invAng = (uint8)(0 - objAng);
+ const int wx = xloc - obj.where.xloc;
+ const int wy = yloc - obj.where.yloc;
+ const int lx = (int)(((int32)wx * _cost[invAng] - (int32)wy * _sint[invAng]) >> 7) - fp.centerX;
+ const int ly = (int)(((int32)wx * _sint[invAng] + (int32)wy * _cost[invAng]) >> 7) - fp.centerY;
+
+ return ABS(lx) <= fp.halfX + kPlayerObjectPad &&
+ ABS(ly) <= fp.halfY + kPlayerObjectPad;
+}
+
+int ColonyEngine::occupiedObjectAt(int xnew, int ynew, int x, int y, const Locate *pobject) {
if (x < 0 || x >= 32 || y < 0 || y >= 32)
return -1;
const int rnum = _robotArray[x][y];
@@ -216,6 +365,12 @@ int ColonyEngine::occupiedObjectAt(int x, int y, const Locate *pobject) {
return 0;
if (pobject == &_me && rnum <= (int)_objects.size()) {
Thing &obj = _objects[rnum - 1];
+ // Only the player gets tight static-object footprints. Robots,
+ // snoops, and scan rays keep full-cell blocking so enemies cannot
+ // enter or path through cells occupied by objects.
+ if (obj.type > kBaseObject && obj.type != kObjCWall && obj.type != kObjFWall &&
+ !playerIntersectsObjectFootprint(obj, xnew, ynew))
+ return 0;
if (obj.type <= kBaseObject)
obj.where.look = obj.where.ang = _me.ang + 128;
}
@@ -320,7 +475,7 @@ void ColonyEngine::clampToDiagonalWalls(Locate *p) {
}
int ColonyEngine::checkwallMoveTo(int xnew, int ynew, int xind2, int yind2, Locate *pobject, uint8 trailCode) {
- const int rnum = occupiedObjectAt(xind2, yind2, pobject);
+ const int rnum = occupiedObjectAt(xnew, ynew, xind2, yind2, pobject);
if (rnum)
return rnum;
if (trailCode != 0 && pobject->type == kMeNum &&
@@ -345,7 +500,7 @@ int ColonyEngine::checkwallTryFeature(int xnew, int ynew, int xind2, int yind2,
if (r == 2)
return 0; // teleported position already updated by the feature
if (r == 1) {
- const int rnum = occupiedObjectAt(xind2, yind2, pobject);
+ const int rnum = occupiedObjectAt(xnew, ynew, xind2, yind2, pobject);
if (rnum) {
const bool showDoorText = (pobject == &_me && feature &&
(feature[0] == kWallFeatureDoor || feature[0] == kWallFeatureAirlock));
@@ -392,11 +547,18 @@ int ColonyEngine::checkwall(int xnew, int ynew, Locate *pobject) {
if (xind2 == pobject->xindex) {
if (yind2 == pobject->yindex) {
+ if (pobject == &_me) {
+ const int rnum = occupiedObjectAt(xnew, ynew, xind2, yind2, pobject);
+ if (rnum)
+ return rnum;
+ }
pobject->dx = xnew - pobject->xloc;
pobject->dy = ynew - pobject->yloc;
pobject->xloc = xnew;
pobject->yloc = ynew;
clampToWalls(pobject);
+ if (pobject == &_me)
+ clampToDiagonalWalls(pobject);
return 0;
}
@@ -588,9 +750,7 @@ int ColonyEngine::goToDestination(const uint8 *map, Locate *pobject) {
return 0;
}
- if (_me.xindex >= 0 && _me.xindex < 32 &&
- _me.yindex >= 0 && _me.yindex < 32)
- _robotArray[_me.xindex][_me.yindex] = 0;
+ clearPlayerCellMarker();
_gameMode = kModeBattle;
_projon = false;
@@ -1041,7 +1201,7 @@ void ColonyEngine::fallThroughHole() {
if (targetMap == 0 && _robotArray[targetX][targetY] != 0)
return;
- _robotArray[_me.xindex][_me.yindex] = 0;
+ clearPlayerCellMarker();
// Preserve sub-cell offset (DOS: xmod = xloc - (xindex<<8))
int xmod = _me.xloc - (_me.xindex << 8);
@@ -1051,7 +1211,7 @@ void ColonyEngine::fallThroughHole() {
_me.yloc = (targetY << 8) + ymod;
_me.yindex = targetY;
- _robotArray[targetX][targetY] = kMeNum;
+ setPlayerCellMarker();
}
// DOS: if(map) load_mapnum(map, TRUE) always reload when map != 0
@@ -1134,8 +1294,7 @@ void ColonyEngine::playCollisionSound() {
}
void ColonyEngine::cCommand(int xnew, int ynew, bool allowInteraction) {
- if (_me.xindex >= 0 && _me.xindex < 32 && _me.yindex >= 0 && _me.yindex < 32)
- _robotArray[_me.xindex][_me.yindex] = 0;
+ clearPlayerCellMarker();
const int oldXIndex = _me.xindex;
const int oldYIndex = _me.yindex;
@@ -1149,8 +1308,7 @@ void ColonyEngine::cCommand(int xnew, int ynew, bool allowInteraction) {
(_me.xloc != xnew || _me.yloc != ynew))
playCollisionSound();
- if (_me.xindex >= 0 && _me.xindex < 32 && _me.yindex >= 0 && _me.yindex < 32)
- _robotArray[_me.xindex][_me.yindex] = kMeNum;
+ setPlayerCellMarker();
_suppressCollisionSound = false;
}
diff --git a/engines/colony/render_objects.cpp b/engines/colony/render_objects.cpp
index 5d49db97a8e..afa5e4458e4 100644
--- a/engines/colony/render_objects.cpp
+++ b/engines/colony/render_objects.cpp
@@ -1448,8 +1448,6 @@ void ColonyEngine::drawEyeOverlays3D(Thing &thing, const PrismPartDef &irisDef,
drawPrismOval3D(thing, pupilDef, useLook, pupilColorOverride, true);
}
-namespace {
-
int interpolatedRobotPoint(int from, int to, float progress) {
return (int)roundf((float)from + ((float)to - (float)from) * progress);
}
@@ -1516,8 +1514,6 @@ const ColonyEngine::PrismPartDef &growEyePupilDefForStage(int stage) {
}
}
-} // namespace
-
float ColonyEngine::growRenderTickFraction() const {
const uint32 kColonyThinkIntervalMs = 125;
const uint32 now = _system->getMillis();
diff --git a/engines/colony/savegame.cpp b/engines/colony/savegame.cpp
index 1f423247887..f3117164866 100644
--- a/engines/colony/savegame.cpp
+++ b/engines/colony/savegame.cpp
@@ -28,8 +28,6 @@
namespace Colony {
-namespace {
-
const uint32 kSaveVersion = 1;
const uint32 kMaxSaveObjects = 4096;
const uint32 kMaxSavePatches = 100;
@@ -274,8 +272,6 @@ bool findInvalidActiveObjectSlot(const Common::Array<Thing> &objects, uint32 &in
return false;
}
-} // anonymous namespace
-
bool ColonyEngine::hasFeature(EngineFeature f) const {
return f == kSupportsReturnToLauncher ||
f == kSupportsLoadingDuringRuntime ||
diff --git a/engines/colony/sound.cpp b/engines/colony/sound.cpp
index 9ab0459219d..d72708bbe05 100644
--- a/engines/colony/sound.cpp
+++ b/engines/colony/sound.cpp
@@ -34,8 +34,6 @@
namespace Colony {
-namespace {
-
struct MelodyStep {
uint32 divider;
uint8 ticks;
@@ -99,8 +97,6 @@ const int kBeamMeRamp1Steps = 20;
const int kBeamMeRamp2Steps = 20;
const int kBeamMeRamp3Steps = 80;
-} // namespace
-
Sound::Sound(ColonyEngine *vm) : _vm(vm), _resMan(nullptr), _appResMan(nullptr) {
_speaker = new Audio::PCSpeaker();
_speaker->init();
More information about the Scummvm-git-logs
mailing list