From 11903cc575444e112577b4ef0c1b0eda4ecdf86e Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:44:57 +0200 Subject: [PATCH 01/10] bugfix(physics): Fix diagonal movement speed discrepancy --- Core/GameEngine/Include/Common/GameDefines.h | 5 ++++ .../GameLogic/Object/Update/PhysicsUpdate.cpp | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index be20d29c292..8b0b7d1aad1 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -91,6 +91,11 @@ #define PRESERVE_RETAIL_PARTICLES (1) // Preserve original look of particles present in retail Generals 1.08 and Zero Hour 1.04 #endif +// Whether to preserve the 1.41x speed discrepancy between straight and diagonal movements of all objects. +#ifndef PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED (1) +#endif + #ifndef RETAIL_COMPATIBLE_CRC #define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04 #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 3ee2c96c1ff..b0b36977eb7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -960,15 +960,22 @@ Real PhysicsBehavior::getForwardSpeed2D() const Real dot = vx + vy; - Real speedSquared = vx*vx + vy*vy; -// DEBUG_ASSERTCRASH( speedSquared != 0, ("zero speedSquared will overflow sqrtf()!") );// lorenzen... sanity check - - Real speed = (Real)sqrtf( speedSquared ); +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED + Real speed = (Real)sqrtf( vx*vx + vy*vy ); if (dot >= 0.0f) return speed; return -speed; +#else + // Inverse scale len by (1 + sqrt(2)) / 2 to adjust to the average of the former min/max movement speed. + // The inverse looks intuitively wrong, but it is correct, because the value returned by this function is + // used to determine the additional velocity needed to reach the target speed. + constexpr const Real DiagonalCompensation = 1.0f / 1.20710678f; + dot *= DiagonalCompensation; + + return dot; +#endif } //------------------------------------------------------------------------------------------------- @@ -986,12 +993,22 @@ Real PhysicsBehavior::getForwardSpeed3D() const Real dot = vx + vy + vz; +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); if (dot >= 0.0f) return speed; return -speed; +#else + // Inverse scale len by (1 + sqrt(3)) / 2 to adjust to the average of the former min/max movement speed. + // The inverse looks intuitively wrong, but it is correct, because the value returned by this function is + // used to determine the additional velocity needed to reach the target speed. + constexpr const Real DiagonalCompensation = 1.0f / 1.36602540f; + dot *= DiagonalCompensation; + + return dot; +#endif } //------------------------------------------------------------------------------------------------- From d822138d4bd0fa91a291d004ced169da783aea5c Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Wed, 29 Jul 2026 21:50:33 +0200 Subject: [PATCH 02/10] Preserve legacy speeds for scripted movements --- Core/GameEngine/Include/Common/GameDefines.h | 6 +++ .../GameLogic/Object/Update/PhysicsUpdate.cpp | 46 ++++++++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index 8b0b7d1aad1..ba39f56369f 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -96,6 +96,12 @@ #define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED (1) #endif +// Whether to preserve the 1.41x speed discrepancy between straight and diagonal movements of all scripted objects. +// This setting is very relevant for legacy missions and cinematic sequences. +#ifndef PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED +#define PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED (1) +#endif + #ifndef RETAIL_COMPATIBLE_CRC #define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04 #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index b0b36977eb7..5c97bc6ff92 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -954,27 +954,38 @@ Real PhysicsBehavior::getVelocityMagnitude() const Real PhysicsBehavior::getForwardSpeed2D() const { const Coord3D *dir = getObject()->getUnitDirectionVector2D(); - Real vx = m_vel.x * dir->x; Real vy = m_vel.y * dir->y; - Real dot = vx + vy; #if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED - Real speed = (Real)sqrtf( vx*vx + vy*vy ); + Real speed = (Real)sqrtf( vx*vx + vy*vy ); if (dot >= 0.0f) return speed; - return -speed; + #else + +#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED + if (const AIUpdateInterface *ai = getObject()->getAIUpdateInterface()) + { + if (ai->getLastCommandSource() == CMD_FROM_SCRIPT) + { + Real speed = (Real)sqrtf( vx*vx + vy*vy ); + if (dot >= 0.0f) + return speed; + return -speed; + } + } +#endif + // Inverse scale len by (1 + sqrt(2)) / 2 to adjust to the average of the former min/max movement speed. // The inverse looks intuitively wrong, but it is correct, because the value returned by this function is // used to determine the additional velocity needed to reach the target speed. constexpr const Real DiagonalCompensation = 1.0f / 1.20710678f; - dot *= DiagonalCompensation; + return dot * DiagonalCompensation; - return dot; #endif } @@ -986,28 +997,39 @@ Real PhysicsBehavior::getForwardSpeed2D() const Real PhysicsBehavior::getForwardSpeed3D() const { Vector3 dir = getObject()->getTransformMatrix()->Get_X_Vector(); - Real vx = m_vel.x * dir.X; Real vy = m_vel.y * dir.Y; Real vz = m_vel.z * dir.Z; - Real dot = vx + vy + vz; #if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED - Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); + Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); if (dot >= 0.0f) return speed; - return -speed; + #else + +#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED + if (const AIUpdateInterface *ai = getObject()->getAIUpdateInterface()) + { + if (ai->getLastCommandSource() == CMD_FROM_SCRIPT) + { + Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); + if (dot >= 0.0f) + return speed; + return -speed; + } + } +#endif + // Inverse scale len by (1 + sqrt(3)) / 2 to adjust to the average of the former min/max movement speed. // The inverse looks intuitively wrong, but it is correct, because the value returned by this function is // used to determine the additional velocity needed to reach the target speed. constexpr const Real DiagonalCompensation = 1.0f / 1.36602540f; - dot *= DiagonalCompensation; + return dot * DiagonalCompensation; - return dot; #endif } From 9345a8cb4d4d55cc2014306810bb69e460f9d267 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:20:22 +0200 Subject: [PATCH 03/10] Polish comments --- .../Source/GameLogic/Object/Update/PhysicsUpdate.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 5c97bc6ff92..5f52df42fae 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -980,7 +980,8 @@ Real PhysicsBehavior::getForwardSpeed2D() const } #endif - // Inverse scale len by (1 + sqrt(2)) / 2 to adjust to the average of the former min/max movement speed. + // TheSuperHackers @bugfix xezon 30/07/2026 Now returns scaled dot product instead of +-sqrtf(vx*vx+vy*vy) + // Inverse scales len by (1 + sqrt(2)) / 2 to adjust to the average of the former min/max movement speed. // The inverse looks intuitively wrong, but it is correct, because the value returned by this function is // used to determine the additional velocity needed to reach the target speed. constexpr const Real DiagonalCompensation = 1.0f / 1.20710678f; @@ -1024,7 +1025,8 @@ Real PhysicsBehavior::getForwardSpeed3D() const } #endif - // Inverse scale len by (1 + sqrt(3)) / 2 to adjust to the average of the former min/max movement speed. + // TheSuperHackers @bugfix xezon 30/07/2026 Now returns scaled dot product instead of +-sqrtf(vx*vx+vy*vy+vz*vz) + // Inverse scales len by (1 + sqrt(3)) / 2 to adjust to the average of the former min/max movement speed. // The inverse looks intuitively wrong, but it is correct, because the value returned by this function is // used to determine the additional velocity needed to reach the target speed. constexpr const Real DiagonalCompensation = 1.0f / 1.36602540f; From a9ca29b16fd278349957f28d07510a19cd778fcd Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:44:43 +0200 Subject: [PATCH 04/10] Add more comments --- .../Source/GameLogic/Object/Update/PhysicsUpdate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 5f52df42fae..de7a6362759 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -987,7 +987,7 @@ Real PhysicsBehavior::getForwardSpeed2D() const constexpr const Real DiagonalCompensation = 1.0f / 1.20710678f; return dot * DiagonalCompensation; -#endif +#endif // RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED } //------------------------------------------------------------------------------------------------- @@ -1032,7 +1032,7 @@ Real PhysicsBehavior::getForwardSpeed3D() const constexpr const Real DiagonalCompensation = 1.0f / 1.36602540f; return dot * DiagonalCompensation; -#endif +#endif // RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED } //------------------------------------------------------------------------------------------------- From ff25f088495b61397110ca5d1d9a2fb127d7977b Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 22 Aug 2026 11:51:37 +0200 Subject: [PATCH 05/10] Fix Locomotor speeds --- .../GameEngine/Include/GameLogic/Locomotor.h | 42 +++- .../Source/GameLogic/AI/AIStates.cpp | 4 +- .../Source/GameLogic/Object/Locomotor.cpp | 226 +++++++++++++++--- .../GameLogic/Object/ObjectCreationList.cpp | 2 +- .../GameLogic/Object/Update/AIUpdate.cpp | 10 +- .../AIUpdate/DeliverPayloadAIUpdate.cpp | 2 +- .../Object/Update/AIUpdate/JetAIUpdate.cpp | 4 +- .../Update/AIUpdate/MissileAIUpdate.cpp | 2 +- .../GameLogic/Object/Update/PhysicsUpdate.cpp | 21 +- 9 files changed, 253 insertions(+), 60 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index 3649866a957..22004c2c750 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -40,6 +40,7 @@ class Locomotor; class LocomotorTemplate; class INI; +class Object; class PhysicsBehavior; enum BodyDamageType CPP_11(: Int); enum PhysicsTurningType CPP_11(: Int); @@ -139,10 +140,23 @@ class LocomotorTemplate : public Overridable void validate(); -protected: - + Real getMinSpeed() const { return m_minSpeed; } private: + + /// TheSuperHackers @bugfix Speeds authored in INI are understated by the forward speed the Locomotor measures + /// itself with, by up to 1/sqrt(2) in 2d and 1/sqrt(3) in 3d, which is what made objects move faster on diagonal + /// headings than on axis aligned ones. Each authored speed therefore also gets a "scaled" twin, in world distance + /// per logic frame, computed once at INI load. These accessors hand out whichever of the two the given object should + /// be commanded with. They all return the authored value in retail compatible builds. + Real getMaxSpeed(const Object* obj) const; + Real getMaxSpeedDamaged(const Object* obj) const; + Real getMinSpeed(const Object* obj) const; + Real getMinTurnSpeed(const Object* obj) const; + + /// Scale a speed that has no stored counterpart because it was not authored on this template. + Real scaleSpeed(Real speed) const; + /** Units check: @@ -163,6 +177,12 @@ class LocomotorTemplate : public Overridable Real m_liftDamaged; ///< max lift when damaged Real m_braking; ///< max braking (deceleration) Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + Real m_maxSpeedScaled; ///< real max speed + Real m_maxSpeedDamagedScaled;///< real speed when "damaged" + Real m_minSpeedScaled; ///< real min speed; we should never brake past this + Real m_minTurnSpeedScaled; ///< real min turn speed; we must be going >= this speed in order to turn +#endif Real m_preferredHeight; ///< our preferred height (if flying) Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) @@ -231,6 +251,8 @@ class Locomotor : public MemoryPoolObject, public Snapshot public: + const LocomotorTemplate *getTemplate() const { return m_template; } + void setPhysicsOptions(Object* obj); void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, @@ -244,7 +266,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot */ Bool locoUpdate_maintainCurrentPosition(Object* obj); - Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition + Real getMaxSpeedForCondition(BodyDamageType condition, const Object* obj) const; ///< get max speed given condition Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition @@ -258,7 +280,8 @@ class Locomotor : public MemoryPoolObject, public Snapshot LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } AsciiString getTemplateName() const { return m_template->m_name;} - Real getMinSpeed() const { return m_template->m_minSpeed;} + Real getMinSpeed(const Object* obj) const; + Real getMinTurnSpeed(const Object* obj) const; Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. @@ -302,7 +325,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} - Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; + Real calcMinTurnRadius(BodyDamageType condition, const Object* obj, Real* timeToTravelThatDist) const; /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. void setMaxLift(Real lift) { m_maxLift = lift; } @@ -310,6 +333,9 @@ class Locomotor : public MemoryPoolObject, public Snapshot { DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!")); m_maxSpeed = speed; +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + m_maxSpeedScaled = m_template->scaleSpeed(speed); +#endif } void setMaxAcceleration(Real accel) { m_maxAccel = accel; } void setMaxBraking(Real braking) { m_maxBraking = braking; } @@ -367,8 +393,9 @@ class Locomotor : public MemoryPoolObject, public Snapshot void startMove(); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. protected: + Real getMaxSpeedOverride(const Object* obj) const; + void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); @@ -445,6 +472,9 @@ class Locomotor : public MemoryPoolObject, public Snapshot Real m_brakingFactor; Real m_maxLift; Real m_maxSpeed; +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + Real m_maxSpeedScaled; +#endif Real m_maxAccel; Real m_maxBraking; Real m_maxTurnRate; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 93474df91f7..4babc56c893 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -4931,7 +4931,7 @@ StateReturnType AIAttackAimAtTargetState::onEnter() AIUpdateInterface* victimAI = victim ? victim->getAI() : nullptr; Locomotor* curLoco = sourceAI->getCurLocomotor(); - m_canTurnInPlace = curLoco ? curLoco->getMinSpeed() == 0.0f : false; + m_canTurnInPlace = curLoco ? curLoco->getMinSpeed(source) == 0.0f : false; // if (!victim) @@ -7459,7 +7459,7 @@ StateReturnType AIFaceState::onEnter() AIUpdateInterface* ai = source->getAI(); Locomotor* curLoco = ai->getCurLocomotor(); - m_canTurnInPlace = curLoco ? curLoco->getMinSpeed() == 0.0f : false; + m_canTurnInPlace = curLoco ? curLoco->getMinSpeed(source) == 0.0f : false; Object* target = getMachineGoalObject(); if (m_obj && target == nullptr ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 205c1a99457..29088983f10 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -77,6 +77,33 @@ static_assert(ARRAY_SIZE(TheLocomotorPriorityNames) == LOCOMOTOR_PRIORITY_COUNT // PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + +// TheSuperHackers @bugfix xezon 30/07/2026 The compensation that equalizes straight and diagonal +// movement speeds. Each constant is the average of the former minimum (straight) and maximum +// (diagonal) movement speed for its dimension, so the average movement speed of the game does not +// change; only the spread between headings does. +constexpr const Real DiagonalCompensation2D = 1.20710678f; // (1 + sqrt(2)) / 2 +constexpr const Real DiagonalCompensation3D = 1.36602540f; // (1 + sqrt(3)) / 2 + +static Real scaleSpeed2D(Real iniSpeed) { return iniSpeed * DiagonalCompensation2D; } +static Real scaleSpeed3D(Real iniSpeed) { return iniSpeed * DiagonalCompensation3D; } + +#endif + +#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED + +static Bool isScriptedMovement(const Object* obj) +{ + if (obj == nullptr) + return FALSE; + + const AIUpdateInterface* ai = obj->getAIUpdateInterface(); + return ai != nullptr && ai->getLastCommandSource() == CMD_FROM_SCRIPT; +} + +#endif + //------------------------------------------------------------------------------------------------- static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) { @@ -289,6 +316,12 @@ LocomotorTemplate::LocomotorTemplate() m_braking = BIGNUM; m_minSpeed = 0.0f; m_minTurnSpeed = BIGNUM; +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + m_maxSpeedScaled = 0.0f; + m_maxSpeedDamagedScaled = 0.0f; + m_minSpeedScaled = 0.0f; + m_minTurnSpeedScaled = BIGNUM; +#endif m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; m_appearance = LOCO_OTHER; m_movePriority = LOCO_MOVES_MIDDLE; @@ -429,6 +462,97 @@ void LocomotorTemplate::validate() if (m_decelPitchLimit == 0.0f) m_decelPitchLimit = m_accelPitchLimit; #endif + +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + // TheSuperHackers @bugfix xezon 30/07/2026 THRUST is the only appearance whose mover measures + // itself with getForwardSpeed3D, so the dimension is decided here, once, rather than every time a + // speed is read. This runs last so that the twins are computed from the healed and defaulted + // values above, and it is safe to run again on an INI override because each twin is assigned from + // its untouched source rather than multiplied in place. + if (m_appearance == LOCO_THRUST) + { + m_maxSpeedScaled = scaleSpeed3D(m_maxSpeed); + m_maxSpeedDamagedScaled = scaleSpeed3D(m_maxSpeedDamaged); + m_minSpeedScaled = scaleSpeed3D(m_minSpeed); + m_minTurnSpeedScaled = scaleSpeed3D(m_minTurnSpeed); + } + else + { + m_maxSpeedScaled = scaleSpeed2D(m_maxSpeed); + m_maxSpeedDamagedScaled = scaleSpeed2D(m_maxSpeedDamaged); + m_minSpeedScaled = scaleSpeed2D(m_minSpeed); + m_minTurnSpeedScaled = scaleSpeed2D(m_minTurnSpeed); + } +#endif +} + +//------------------------------------------------------------------------------------------------- +Real LocomotorTemplate::getMaxSpeed(const Object* obj) const +{ +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED + return m_maxSpeed; +#else +#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED + if (isScriptedMovement(obj)) + return m_maxSpeed; +#endif + return m_maxSpeedScaled; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real LocomotorTemplate::getMaxSpeedDamaged(const Object* obj) const +{ +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED + return m_maxSpeedDamaged; +#else +#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED + if (isScriptedMovement(obj)) + return m_maxSpeedDamaged; +#endif + return m_maxSpeedDamagedScaled; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real LocomotorTemplate::getMinSpeed(const Object* obj) const +{ +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED + return m_minSpeed; +#else +#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED + if (isScriptedMovement(obj)) + return m_minSpeed; +#endif + return m_minSpeedScaled; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real LocomotorTemplate::getMinTurnSpeed(const Object* obj) const +{ +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED + return m_minTurnSpeed; +#else +#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED + if (isScriptedMovement(obj)) + return m_minTurnSpeed; +#endif + return m_minTurnSpeedScaled; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real LocomotorTemplate::scaleSpeed(Real speed) const +{ +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED + return speed; +#else + if (m_appearance == LOCO_THRUST) + return scaleSpeed3D(speed); + + return scaleSpeed2D(speed); +#endif } //------------------------------------------------------------------------------------------------- @@ -658,6 +782,9 @@ Locomotor::Locomotor(const LocomotorTemplate* tmpl) m_brakingFactor = 1.0f; m_maxLift = BIGNUM; m_maxSpeed = BIGNUM; +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + m_maxSpeedScaled = BIGNUM; +#endif m_maxAccel = BIGNUM; m_maxBraking = BIGNUM; m_maxTurnRate = BIGNUM; @@ -685,6 +812,9 @@ Locomotor::Locomotor(const Locomotor& that) m_brakingFactor = that.m_brakingFactor; m_maxLift = that.m_maxLift; m_maxSpeed = that.m_maxSpeed; +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + m_maxSpeedScaled = that.m_maxSpeedScaled; +#endif m_maxAccel = that.m_maxAccel; m_maxBraking = that.m_maxBraking; m_maxTurnRate = that.m_maxTurnRate; @@ -708,6 +838,9 @@ Locomotor& Locomotor::operator=(const Locomotor& that) m_brakingFactor = that.m_brakingFactor; m_maxLift = that.m_maxLift; m_maxSpeed = that.m_maxSpeed; +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + m_maxSpeedScaled = that.m_maxSpeedScaled; +#endif m_maxAccel = that.m_maxAccel; m_maxBraking = that.m_maxBraking; m_maxTurnRate = that.m_maxTurnRate; @@ -755,6 +888,12 @@ void Locomotor::xfer( Xfer *xfer ) xfer->xferReal(&m_brakingFactor); xfer->xferReal(&m_maxLift); xfer->xferReal(&m_maxSpeed); +#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + // TheSuperHackers @bugfix xezon 30/07/2026 The scaled twin is derived, so recompute it rather + // than transfer it. That keeps the save format byte identical and lets saves written before this + // change load correctly. In the save direction this simply recomputes the value it already has. + m_maxSpeedScaled = m_template->scaleSpeed(m_maxSpeed); +#endif xfer->xferReal(&m_maxAccel); xfer->xferReal(&m_maxBraking); xfer->xferReal(&m_maxTurnRate); @@ -786,21 +925,48 @@ void Locomotor::startMove() } //------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const +Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition, const Object* obj) const { Real speed; if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - speed = m_template->m_maxSpeed; + speed = m_template->getMaxSpeed(obj); else - speed = m_template->m_maxSpeedDamaged; + speed = m_template->getMaxSpeedDamaged(obj); - if (speed > m_maxSpeed) - speed = m_maxSpeed; + Real maxSpeed = getMaxSpeedOverride(obj); + if (speed > maxSpeed) + speed = maxSpeed; return speed; } +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxSpeedOverride(const Object* obj) const +{ +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED + return m_maxSpeed; +#else +#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED + if (isScriptedMovement(obj)) + return m_maxSpeed; +#endif + return m_maxSpeedScaled; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMinSpeed(const Object* obj) const +{ + return m_template->getMinSpeed(obj); +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMinTurnSpeed(const Object* obj) const +{ + return m_template->getMinTurnSpeed(obj); +} + //------------------------------------------------------------------------------------------------- Real Locomotor::getMaxTurnRate(BodyDamageType condition) const { @@ -889,7 +1055,7 @@ void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) // DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); #endif - Real minSpeed = getMinSpeed(); + Real minSpeed = getMinSpeed(obj); if (minSpeed > 0) { // can't stay in one place; move in the desired direction at min speed. @@ -953,7 +1119,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP setFlag(MAINTAIN_POS_IS_VALID, false); BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); + Real maxSpeed = getMaxSpeedForCondition(bdt, obj); // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at if( desiredSpeed > maxSpeed ) @@ -1167,7 +1333,7 @@ void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); + Real maxSpeed = getMaxSpeedForCondition(bdt, obj); if( desiredSpeed > maxSpeed ) desiredSpeed = maxSpeed; @@ -1279,7 +1445,7 @@ void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) { BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); + Real maxSpeed = getMaxSpeedForCondition(bdt, obj); Real maxTurnRate = getMaxTurnRate(bdt); Real maxAcceleration = getMaxAcceleration(bdt); @@ -1291,7 +1457,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, // // See if we are turning. If so, use the min turn speed. // - Real turnSpeed = m_template->m_minTurnSpeed; + Real turnSpeed = getMinTurnSpeed(obj); Real angle = obj->getOrientation(); // Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); // Real desiredAngle = angle + relAngle; @@ -1585,9 +1751,9 @@ Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) } //------------------------------------------------------------------------------------------------- -Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const +Real Locomotor::calcMinTurnRadius(BodyDamageType condition, const Object* obj, Real* timeToTravelThatDist) const { - Real minSpeed = getMinSpeed(); // in dist/frame + Real minSpeed = getMinSpeed(obj); // in dist/frame Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame /* @@ -1622,7 +1788,7 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState(), obj ); if( desiredSpeed > maxSpeed ) desiredSpeed = maxSpeed; @@ -1667,10 +1833,10 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(obj), getBraking()); if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) { - goalSpeed = m_template->m_minSpeed; + goalSpeed = getMinSpeed(obj); } @@ -1713,7 +1879,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState(), obj ); if( desiredSpeed > maxSpeed ) desiredSpeed = maxSpeed; @@ -1794,10 +1960,10 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, } //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(obj), getBraking()); if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) { - goalSpeed = m_template->m_minSpeed; + goalSpeed = getMinSpeed(obj); } // @@ -1864,7 +2030,7 @@ void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, angleTowardPos += aimDir; BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = calcMinTurnRadius(bdt, nullptr) * 4; + Real turnRadius = calcMinTurnRadius(bdt, obj, nullptr) * 4; // project a spot "radius" dist away from it, in that dir Coord3D desiredPos = goalPos; @@ -1913,16 +2079,16 @@ void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, { BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxForwardSpeed = getMaxSpeedForCondition(bdt); - desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); + Real maxForwardSpeed = getMaxSpeedForCondition(bdt, obj); + desiredSpeed = clamp(getMinSpeed(obj), desiredSpeed, maxForwardSpeed); Real actualForwardSpeed = physics->getForwardSpeed3D(); if (getBraking() > 0) { //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); + Real slowDownDist = calcSlowDownDist(actualForwardSpeed, getMinSpeed(obj), getBraking()); if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - desiredSpeed = m_template->m_minSpeed; + desiredSpeed = getMinSpeed(obj); } Coord3D localGoalPos = goalPos; @@ -2350,7 +2516,7 @@ void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, Real maxAcceleration = getMaxAcceleration(bdt); // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition(bdt); + Real maxSpeed = getMaxSpeedForCondition(bdt, obj); if( desiredSpeed > maxSpeed ) desiredSpeed = maxSpeed; @@ -2388,10 +2554,10 @@ void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) { - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(obj), getBraking()); if (onPathDistToGoal < slowDownDist) { - goalSpeed = m_template->m_minSpeed; + goalSpeed = getMinSpeed(obj); } } @@ -2502,7 +2668,7 @@ void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *phys { DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); /// @todo srj -- should these also use the "circling radius" stuff, like wings? - moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); + moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed(obj)); } //------------------------------------------------------------------------------------------------- @@ -2517,7 +2683,7 @@ void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physi BodyDamageType bdt = obj->getBodyModule()->getDamageState(); Real turnRadius = m_template->m_circlingRadius; if (turnRadius == 0.0f) - turnRadius = calcMinTurnRadius(bdt, nullptr); + turnRadius = calcMinTurnRadius(bdt, obj, nullptr); // find the direction towards our "maintain pos" const Coord3D* pos = obj->getPosition(); @@ -2540,7 +2706,7 @@ void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physi Coord3D desiredPos = m_maintainPos; desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; - moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); + moveTowardsPositionWings(obj, physics, desiredPos, 0, getMinSpeed(obj)); } } @@ -2558,7 +2724,7 @@ void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physi // // Stop // - Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); + Real minSpeed = max( 1.0E-10f, getMinSpeed(obj) ); Real speedDelta = minSpeed - actualSpeed; if (fabs(speedDelta) > minSpeed) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index 9d9ffce18b1..afabc8462f9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -415,7 +415,7 @@ class DeliverPayloadNugget : public ObjectCreationNugget if (physics) { Coord3D startingForce = *transport->getUnitDirectionVector2D(); - Real maxSpeed = ai->getCurLocomotor()->getMaxSpeedForCondition(transport->getBodyModule()->getDamageState()); + Real maxSpeed = ai->getCurLocomotor()->getMaxSpeedForCondition(transport->getBodyModule()->getDamageState(), transport); Real factor = maxSpeed * physics->getMass(); startingForce.x *= factor; startingForce.y *= factor; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index 977aec18670..f806d77b6f7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -791,7 +791,7 @@ WhichTurretType AIUpdateInterface::getWhichTurretForWeaponSlot(WeaponSlotType ws Real AIUpdateInterface::getCurLocomotorSpeed() const { if (m_curLocomotor != nullptr) - return m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState()); + return m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState(), getObject()); DEBUG_LOG(("no current locomotor!")); return 0.0f; @@ -1486,10 +1486,10 @@ Bool AIUpdateInterface::processCollision(PhysicsBehavior *physics, Object *other #define dont_MOVE_AROUND // It just causes more problems than it fixes. jba. #ifdef MOVE_AROUND if (m_curLocomotor!= nullptr && (other->isKindOf(KINDOF_INFANTRY)==getObject()->isKindOf(KINDOF_INFANTRY))) { - Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState()); + Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState(), getObject()); Locomotor *hisLoco = aiOther->getCurLocomotor(); if (hisLoco) { - Real hisMaxSpeed = hisLoco->getMaxSpeedForCondition(other->getBodyModule()->getDamageState()); + Real hisMaxSpeed = hisLoco->getMaxSpeedForCondition(other->getBodyModule()->getDamageState(), other); if (hisMaxSpeed > 0.05 && hisMaxSpeed < 0.6f*myMaxSpeed) { aiOther->aiMoveAwayFromUnit(getObject(), CMD_FROM_AI); return FALSE; @@ -2163,7 +2163,7 @@ UpdateSleepTime AIUpdateInterface::doLocomotor() case POSITION_EXPLICIT: { Real speed = m_desiredSpeed; - Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState()); + Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState(), getObject()); if( speed == FAST_AS_POSSIBLE || speed > myMaxSpeed ) speed = myMaxSpeed; m_curLocomotor->locoUpdate_moveTowardsPosition(getObject(), @@ -2210,7 +2210,7 @@ UpdateSleepTime AIUpdateInterface::doLocomotor() } Real speed = m_desiredSpeed; - Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState()); + Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState(), getObject()); if( speed == FAST_AS_POSSIBLE || speed > myMaxSpeed ) speed = myMaxSpeed; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index ca618b60a80..7ca28879332 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -330,7 +330,7 @@ Real DeliverPayloadAIUpdate::calcMinTurnRadius(Real* timeToTravelThatDist) const const Locomotor* loco = getCurLocomotor(); BodyDamageType bdt = getObject()->getBodyModule()->getDamageState(); /// @todo srj -- this should probably use min-speed, not max-speed... fix after E3 - Real maxSpeed = loco->getMaxSpeedForCondition(bdt); // in dist/frame + Real maxSpeed = loco->getMaxSpeedForCondition(bdt, getObject()); // in dist/frame Real maxTurnRate = loco->getMaxTurnRate(bdt); // in rads/frame /* diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index 998825c9648..d45d166347d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -735,11 +735,11 @@ class JetTakeoffOrLandingState : public AIFollowPathState loco->setMaxLift(BIGNUM); BodyDamageType bdt = jet->getBodyModule()->getDamageState(); m_maxLift = loco->getMaxLift(bdt); - m_maxSpeed = loco->getMaxSpeedForCondition(bdt); + m_maxSpeed = loco->getMaxSpeedForCondition(bdt, jet); m_landingSoundPlayed = FALSE; if (m_landing) { - loco->setMaxSpeed(loco->getMinSpeed()); + loco->setMaxSpeed(loco->getTemplate()->getMinSpeed()); } else { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 78b44dc99ca..5f2faded46e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -608,7 +608,7 @@ void MissileAIUpdate::doKillState() Real closeEnough = 1.0f; if (curLoco) { - closeEnough = curLoco->getMaxSpeedForCondition(BODY_PRISTINE); + closeEnough = curLoco->getMaxSpeedForCondition(BODY_PRISTINE, getObject()); } Real distanceToTargetSq = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_BOUNDINGSPHERE_3D); //DEBUG_LOG(("Distance to target %f, closeEnough %f", sqrt(distanceToTargetSq), closeEnough)); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index de7a6362759..f0c4130fcd9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -980,12 +980,12 @@ Real PhysicsBehavior::getForwardSpeed2D() const } #endif - // TheSuperHackers @bugfix xezon 30/07/2026 Now returns scaled dot product instead of +-sqrtf(vx*vx+vy*vy) - // Inverse scales len by (1 + sqrt(2)) / 2 to adjust to the average of the former min/max movement speed. - // The inverse looks intuitively wrong, but it is correct, because the value returned by this function is - // used to determine the additional velocity needed to reach the target speed. - constexpr const Real DiagonalCompensation = 1.0f / 1.20710678f; - return dot * DiagonalCompensation; + // TheSuperHackers @bugfix xezon 30/07/2026 Now returns the dot product instead of +-sqrtf(vx*vx+vy*vy). + // The retail formula understates the forward speed by up to 1/sqrt(2) on diagonal headings, which made + // the Locomotor overshoot its goal speed there. The dot product is the true projection of the velocity + // onto the facing vector, so this now reports real distance per logic frame and can be used for distance + // and time calculations. The speeds the Locomotor commands are compensated to match, in LocomotorTemplate. + return dot; #endif // RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED } @@ -1025,12 +1025,9 @@ Real PhysicsBehavior::getForwardSpeed3D() const } #endif - // TheSuperHackers @bugfix xezon 30/07/2026 Now returns scaled dot product instead of +-sqrtf(vx*vx+vy*vy+vz*vz) - // Inverse scales len by (1 + sqrt(3)) / 2 to adjust to the average of the former min/max movement speed. - // The inverse looks intuitively wrong, but it is correct, because the value returned by this function is - // used to determine the additional velocity needed to reach the target speed. - constexpr const Real DiagonalCompensation = 1.0f / 1.36602540f; - return dot * DiagonalCompensation; + // TheSuperHackers @bugfix xezon 30/07/2026 Now returns the dot product instead of + // +-sqrtf(vx*vx+vy*vy+vz*vz). See getForwardSpeed2D for the rationale. + return dot; #endif // RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED } From 71c9f53433970f5619c7c96b4349ed6da4413abc Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:13:29 +0200 Subject: [PATCH 06/10] Optimize speed compensation values --- .../Source/GameLogic/Object/Locomotor.cpp | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 29088983f10..0aba867d103 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -80,11 +80,38 @@ static_assert(ARRAY_SIZE(TheLocomotorPriorityNames) == LOCOMOTOR_PRIORITY_COUNT #if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) // TheSuperHackers @bugfix xezon 30/07/2026 The compensation that equalizes straight and diagonal -// movement speeds. Each constant is the average of the former minimum (straight) and maximum -// (diagonal) movement speed for its dimension, so the average movement speed of the game does not -// change; only the spread between headings does. -constexpr const Real DiagonalCompensation2D = 1.20710678f; // (1 + sqrt(2)) / 2 -constexpr const Real DiagonalCompensation3D = 1.36602540f; // (1 + sqrt(3)) / 2 +// movement speeds. +// +// Retail measured forward speed as sqrt(sum of (vi * di)^2) rather than the true projection of the +// velocity onto the heading, sum of (vi * di). For a unit heading d that understates the speed by +// sqrt(sum of di^4), which is 1 on an axis aligned heading and falls to 1/sqrt(2) on a 2d diagonal +// and 1/sqrt(3) on a 3d body diagonal. The Locomotor therefore kept accelerating until the real +// speed was between 1x and sqrt(2)x (2d), or between 1x and sqrt(3)x (3d), the authored speed, +// decided by nothing but which way the object happened to face. +// +// getForwardSpeed2D and getForwardSpeed3D now report the true projection, so the real speed equals +// the commanded speed on every heading. The speeds commanded by the Locomotor are scaled by a single +// constant per dimension, which picks where inside the old range that now uniform speed sits. +// +// Each constant is the mean of the old factor over the headings the movers actually take, so the +// average movement speed of the game is preserved and only the spread between headings collapses. +// It is deliberately not the midpoint of the old range. The factor is weighted heavily toward its +// low end, spending far more of the circle near 1x than near sqrt(2)x, so the midpoint sits above +// the mean and would quietly speed the whole game up. +// +// 2d movers take an arbitrary heading, so the mean is taken over a uniformly random angle. It has a +// closed form as a complete elliptic integral of the first kind, and equals 1.18034060. +// +// 3d movers are THRUST only, which in practice means missiles. Those fly level most of the time and +// use all three axes only while arcing, so the mean is taken over a mix assumed to be 80% level +// flight and 20% an arbitrary 3d direction. Level flight has dz = 0, which makes the 3d factor +// degenerate to the 2d one exactly, so the level part contributes the 2d constant unchanged. The +// mean over a uniformly random direction on the sphere is 1.33122576. The result barely depends on +// the assumed split: anything from 90/10 to 70/30 lands between 1.195 and 1.226, and even spending +// the whole 20% at the worst possible heading would only reach 1.291. + +constexpr const Real DiagonalCompensation2D = 1.18034060f; // (2/pi)*K(1/2) = Gamma(1/4)^2 / (2*pi^(3/2)) +constexpr const Real DiagonalCompensation3D = 1.21051763f; // 0.8 * 1.18034060 + 0.2 * 1.33122576 static Real scaleSpeed2D(Real iniSpeed) { return iniSpeed * DiagonalCompensation2D; } static Real scaleSpeed3D(Real iniSpeed) { return iniSpeed * DiagonalCompensation3D; } From 2979988ec16b095ad4689e0061beb3de4097aa91 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 23 Aug 2026 11:50:55 +0200 Subject: [PATCH 07/10] Improve retail forward speed during cinematics --- Core/GameEngine/Include/Common/GameDefines.h | 16 +- Core/GameEngine/Include/Common/GameUtility.h | 2 + Core/GameEngine/Source/Common/GameUtility.cpp | 16 ++ .../GameEngine/Include/GameLogic/Locomotor.h | 27 ++-- .../Include/GameLogic/Module/PhysicsUpdate.h | 4 + .../Include/GameLogic/ScriptEngine.h | 4 + .../Source/GameLogic/AI/AIStates.cpp | 4 +- .../Source/GameLogic/Object/Locomotor.cpp | 148 +++++++++--------- .../GameLogic/Object/ObjectCreationList.cpp | 2 +- .../GameLogic/Object/Update/AIUpdate.cpp | 10 +- .../AIUpdate/DeliverPayloadAIUpdate.cpp | 2 +- .../Object/Update/AIUpdate/JetAIUpdate.cpp | 2 +- .../Update/AIUpdate/MissileAIUpdate.cpp | 2 +- .../GameLogic/Object/Update/PhysicsUpdate.cpp | 48 +++--- .../GameLogic/ScriptEngine/ScriptActions.cpp | 13 +- .../GameLogic/ScriptEngine/ScriptEngine.cpp | 35 +++++ 16 files changed, 199 insertions(+), 136 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index ba39f56369f..bec9c9f351f 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -96,12 +96,20 @@ #define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED (1) #endif -// Whether to preserve the 1.41x speed discrepancy between straight and diagonal movements of all scripted objects. -// This setting is very relevant for legacy missions and cinematic sequences. -#ifndef PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED -#define PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED (1) +// Whether to preserve the 1.41x speed discrepancy between straight and diagonal movements of all objects during cinematics. +// Is mostly relevant for the original campaign missions. +#ifndef PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS (1) #endif +// Whether the retail forward speed is used unconditionally, for every object at all times. +#define USE_RETAIL_PHYSICS_FORWARD_SPEED (RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) + +// Whether the retail forward speed is used for the duration of a scripted camera event. Is only +// meaningful when the retail forward speed is not already used unconditionally. +#define USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS (!USE_RETAIL_PHYSICS_FORWARD_SPEED && PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS) + + #ifndef RETAIL_COMPATIBLE_CRC #define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04 #endif diff --git a/Core/GameEngine/Include/Common/GameUtility.h b/Core/GameEngine/Include/Common/GameUtility.h index 86790ff4847..800a4b515fb 100644 --- a/Core/GameEngine/Include/Common/GameUtility.h +++ b/Core/GameEngine/Include/Common/GameUtility.h @@ -36,4 +36,6 @@ PlayerIndex getObservedOrLocalPlayerIndex_Safe(); ///< Get the current observed void changeLocalPlayer(Player* player); //< Change local player during game. Must not pass null. void changeObservedPlayer(Player* player); ///< Change observed player during game. Can pass null: is identical to passing the "ReplayObserver" player. +void enableLetterBox(Bool enable); ///< Enable or disable the letter box for cinematics. Hides the control bar. + } // namespace rts diff --git a/Core/GameEngine/Source/Common/GameUtility.cpp b/Core/GameEngine/Source/Common/GameUtility.cpp index cffb87f4842..9dfe64821ca 100644 --- a/Core/GameEngine/Source/Common/GameUtility.cpp +++ b/Core/GameEngine/Source/Common/GameUtility.cpp @@ -24,7 +24,9 @@ #include "Common/Radar.h" #include "GameClient/ControlBar.h" +#include "GameClient/Display.h" #include "GameClient/GameClient.h" +#include "GameClient/GUICallbacks.h" #include "GameClient/InGameUI.h" #include "GameClient/ParticleSys.h" @@ -130,4 +132,18 @@ void changeObservedPlayer(Player* player) } } +void enableLetterBox(Bool enable) +{ + if (enable) + { + HideControlBar(TRUE); + TheDisplay->enableLetterBox(TRUE); + } + else + { + ShowControlBar(FALSE); + TheDisplay->enableLetterBox(FALSE); + } +} + } // namespace rts diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index 22004c2c750..7b38a7f20a7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -149,10 +149,10 @@ class LocomotorTemplate : public Overridable /// headings than on axis aligned ones. Each authored speed therefore also gets a "scaled" twin, in world distance /// per logic frame, computed once at INI load. These accessors hand out whichever of the two the given object should /// be commanded with. They all return the authored value in retail compatible builds. - Real getMaxSpeed(const Object* obj) const; - Real getMaxSpeedDamaged(const Object* obj) const; - Real getMinSpeed(const Object* obj) const; - Real getMinTurnSpeed(const Object* obj) const; + Real getActualMaxSpeed() const; + Real getActualMaxSpeedDamaged() const; + Real getActualMinSpeed() const; + Real getActualMinTurnSpeed() const; /// Scale a speed that has no stored counterpart because it was not authored on this template. Real scaleSpeed(Real speed) const; @@ -177,7 +177,7 @@ class LocomotorTemplate : public Overridable Real m_liftDamaged; ///< max lift when damaged Real m_braking; ///< max braking (deceleration) Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED Real m_maxSpeedScaled; ///< real max speed Real m_maxSpeedDamagedScaled;///< real speed when "damaged" Real m_minSpeedScaled; ///< real min speed; we should never brake past this @@ -251,8 +251,6 @@ class Locomotor : public MemoryPoolObject, public Snapshot public: - const LocomotorTemplate *getTemplate() const { return m_template; } - void setPhysicsOptions(Object* obj); void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, @@ -266,7 +264,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot */ Bool locoUpdate_maintainCurrentPosition(Object* obj); - Real getMaxSpeedForCondition(BodyDamageType condition, const Object* obj) const; ///< get max speed given condition + Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition @@ -279,9 +277,10 @@ class Locomotor : public MemoryPoolObject, public Snapshot LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } + const LocomotorTemplate *getTemplate() const { return m_template; } AsciiString getTemplateName() const { return m_template->m_name;} - Real getMinSpeed(const Object* obj) const; - Real getMinTurnSpeed(const Object* obj) const; + Real getMinSpeed() const; + Real getMinTurnSpeed() const; Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. @@ -325,7 +324,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} - Real calcMinTurnRadius(BodyDamageType condition, const Object* obj, Real* timeToTravelThatDist) const; + Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. void setMaxLift(Real lift) { m_maxLift = lift; } @@ -333,7 +332,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot { DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!")); m_maxSpeed = speed; -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED m_maxSpeedScaled = m_template->scaleSpeed(speed); #endif } @@ -393,7 +392,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot void startMove(); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. protected: - Real getMaxSpeedOverride(const Object* obj) const; + Real getMaxSpeedOverride() const; void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); @@ -472,7 +471,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot Real m_brakingFactor; Real m_maxLift; Real m_maxSpeed; -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED Real m_maxSpeedScaled; #endif Real m_maxAccel; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h index 75a41c7da42..5f1de0fb198 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h @@ -141,6 +141,10 @@ class PhysicsBehavior : public UpdateModule, Real getForwardSpeed2D() const; ///< compute speed along object's 2d direction vector Real getForwardSpeed3D() const; ///< compute speed along object's 3d direction vector +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS + static Bool useLegacyForwardSpeed(); +#endif + ObjectID getCurrentOverlap() const; ///< return object(s) being overlapped ObjectID getPreviousOverlap() const; ///< return object(s) that were overlapped last frame ObjectID getLastCollidee() const; ///< return object that was last collided with... can be quite old diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h index 3ed3d9fc00c..602e8079390 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h @@ -310,6 +310,9 @@ class ScriptEngine : public SubsystemInterface, void doFreezeTime(); void doUnfreezeTime(); + void friend_notifyLetterBoxActive(Bool active); ///< Report the scripted letterbox bracket opening or closing + Bool isInCinematic() const; ///< Ask whether a cinematic is running right now + /// The following functions are used to update and query the debug window Bool isTimeFrozenDebug(); ///< Ask whether the debug window has requested a pause. Bool isTimeFast(); ///< Ask whether the debug window has requested a fast forward. @@ -433,6 +436,7 @@ class ScriptEngine : public SubsystemInterface, Int m_numAttackInfo; Int m_endGameTimer; Int m_closeWindowTimer; + Bool m_letterBoxActive; ///< A scripted letterbox sequence is running Team *m_callingTeam; ///< Team that is calling script, used for THIS_TEAM Object *m_callingObject; ///< Object that is calling script, used for THIS_OBJECT Team *m_conditionTeam; ///< Team that is being used to evaluate conditions, used for THIS_TEAM diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 4babc56c893..93474df91f7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -4931,7 +4931,7 @@ StateReturnType AIAttackAimAtTargetState::onEnter() AIUpdateInterface* victimAI = victim ? victim->getAI() : nullptr; Locomotor* curLoco = sourceAI->getCurLocomotor(); - m_canTurnInPlace = curLoco ? curLoco->getMinSpeed(source) == 0.0f : false; + m_canTurnInPlace = curLoco ? curLoco->getMinSpeed() == 0.0f : false; // if (!victim) @@ -7459,7 +7459,7 @@ StateReturnType AIFaceState::onEnter() AIUpdateInterface* ai = source->getAI(); Locomotor* curLoco = ai->getCurLocomotor(); - m_canTurnInPlace = curLoco ? curLoco->getMinSpeed(source) == 0.0f : false; + m_canTurnInPlace = curLoco ? curLoco->getMinSpeed() == 0.0f : false; Object* target = getMachineGoalObject(); if (m_obj && target == nullptr ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 0aba867d103..3ebabed95cb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -77,7 +77,7 @@ static_assert(ARRAY_SIZE(TheLocomotorPriorityNames) == LOCOMOTOR_PRIORITY_COUNT // PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED // TheSuperHackers @bugfix xezon 30/07/2026 The compensation that equalizes straight and diagonal // movement speeds. @@ -118,19 +118,6 @@ static Real scaleSpeed3D(Real iniSpeed) { return iniSpeed * DiagonalCompensation #endif -#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED - -static Bool isScriptedMovement(const Object* obj) -{ - if (obj == nullptr) - return FALSE; - - const AIUpdateInterface* ai = obj->getAIUpdateInterface(); - return ai != nullptr && ai->getLastCommandSource() == CMD_FROM_SCRIPT; -} - -#endif - //------------------------------------------------------------------------------------------------- static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) { @@ -343,7 +330,7 @@ LocomotorTemplate::LocomotorTemplate() m_braking = BIGNUM; m_minSpeed = 0.0f; m_minTurnSpeed = BIGNUM; -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED m_maxSpeedScaled = 0.0f; m_maxSpeedDamagedScaled = 0.0f; m_minSpeedScaled = 0.0f; @@ -490,7 +477,7 @@ void LocomotorTemplate::validate() m_decelPitchLimit = m_accelPitchLimit; #endif -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED // TheSuperHackers @bugfix xezon 30/07/2026 THRUST is the only appearance whose mover measures // itself with getForwardSpeed3D, so the dimension is decided here, once, rather than every time a // speed is read. This runs last so that the twins are computed from the healed and defaulted @@ -514,57 +501,65 @@ void LocomotorTemplate::validate() } //------------------------------------------------------------------------------------------------- -Real LocomotorTemplate::getMaxSpeed(const Object* obj) const +Real LocomotorTemplate::getActualMaxSpeed() const { -#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED return m_maxSpeed; #else -#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED - if (isScriptedMovement(obj)) + +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS + if (PhysicsBehavior::useLegacyForwardSpeed()) return m_maxSpeed; #endif + return m_maxSpeedScaled; #endif } //------------------------------------------------------------------------------------------------- -Real LocomotorTemplate::getMaxSpeedDamaged(const Object* obj) const +Real LocomotorTemplate::getActualMaxSpeedDamaged() const { -#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED return m_maxSpeedDamaged; #else -#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED - if (isScriptedMovement(obj)) + +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS + if (PhysicsBehavior::useLegacyForwardSpeed()) return m_maxSpeedDamaged; #endif + return m_maxSpeedDamagedScaled; #endif } //------------------------------------------------------------------------------------------------- -Real LocomotorTemplate::getMinSpeed(const Object* obj) const +Real LocomotorTemplate::getActualMinSpeed() const { -#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED return m_minSpeed; #else -#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED - if (isScriptedMovement(obj)) + +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS + if (PhysicsBehavior::useLegacyForwardSpeed()) return m_minSpeed; #endif + return m_minSpeedScaled; #endif } //------------------------------------------------------------------------------------------------- -Real LocomotorTemplate::getMinTurnSpeed(const Object* obj) const +Real LocomotorTemplate::getActualMinTurnSpeed() const { -#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED return m_minTurnSpeed; #else -#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED - if (isScriptedMovement(obj)) + +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS + if (PhysicsBehavior::useLegacyForwardSpeed()) return m_minTurnSpeed; #endif + return m_minTurnSpeedScaled; #endif } @@ -572,12 +567,13 @@ Real LocomotorTemplate::getMinTurnSpeed(const Object* obj) const //------------------------------------------------------------------------------------------------- Real LocomotorTemplate::scaleSpeed(Real speed) const { -#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED return speed; #else if (m_appearance == LOCO_THRUST) + { return scaleSpeed3D(speed); - + } return scaleSpeed2D(speed); #endif } @@ -809,7 +805,7 @@ Locomotor::Locomotor(const LocomotorTemplate* tmpl) m_brakingFactor = 1.0f; m_maxLift = BIGNUM; m_maxSpeed = BIGNUM; -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED m_maxSpeedScaled = BIGNUM; #endif m_maxAccel = BIGNUM; @@ -839,7 +835,7 @@ Locomotor::Locomotor(const Locomotor& that) m_brakingFactor = that.m_brakingFactor; m_maxLift = that.m_maxLift; m_maxSpeed = that.m_maxSpeed; -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED m_maxSpeedScaled = that.m_maxSpeedScaled; #endif m_maxAccel = that.m_maxAccel; @@ -865,7 +861,7 @@ Locomotor& Locomotor::operator=(const Locomotor& that) m_brakingFactor = that.m_brakingFactor; m_maxLift = that.m_maxLift; m_maxSpeed = that.m_maxSpeed; -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED m_maxSpeedScaled = that.m_maxSpeedScaled; #endif m_maxAccel = that.m_maxAccel; @@ -915,7 +911,7 @@ void Locomotor::xfer( Xfer *xfer ) xfer->xferReal(&m_brakingFactor); xfer->xferReal(&m_maxLift); xfer->xferReal(&m_maxSpeed); -#if !(RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#if !USE_RETAIL_PHYSICS_FORWARD_SPEED // TheSuperHackers @bugfix xezon 30/07/2026 The scaled twin is derived, so recompute it rather // than transfer it. That keeps the save format byte identical and lets saves written before this // change load correctly. In the save direction this simply recomputes the value it already has. @@ -952,16 +948,16 @@ void Locomotor::startMove() } //------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition, const Object* obj) const +Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const { Real speed; if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - speed = m_template->getMaxSpeed(obj); + speed = m_template->getActualMaxSpeed(); else - speed = m_template->getMaxSpeedDamaged(obj); + speed = m_template->getActualMaxSpeedDamaged(); - Real maxSpeed = getMaxSpeedOverride(obj); + Real maxSpeed = getMaxSpeedOverride(); if (speed > maxSpeed) speed = maxSpeed; @@ -969,29 +965,31 @@ Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition, const Object* } //------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxSpeedOverride(const Object* obj) const +Real Locomotor::getMaxSpeedOverride() const { -#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED return m_maxSpeed; #else -#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED - if (isScriptedMovement(obj)) + +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS + if (PhysicsBehavior::useLegacyForwardSpeed()) return m_maxSpeed; #endif + return m_maxSpeedScaled; #endif } //------------------------------------------------------------------------------------------------- -Real Locomotor::getMinSpeed(const Object* obj) const +Real Locomotor::getMinSpeed() const { - return m_template->getMinSpeed(obj); + return m_template->getActualMinSpeed(); } //------------------------------------------------------------------------------------------------- -Real Locomotor::getMinTurnSpeed(const Object* obj) const +Real Locomotor::getMinTurnSpeed() const { - return m_template->getMinTurnSpeed(obj); + return m_template->getActualMinTurnSpeed(); } //------------------------------------------------------------------------------------------------- @@ -1082,7 +1080,7 @@ void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) // DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); #endif - Real minSpeed = getMinSpeed(obj); + Real minSpeed = getMinSpeed(); if (minSpeed > 0) { // can't stay in one place; move in the desired direction at min speed. @@ -1146,7 +1144,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP setFlag(MAINTAIN_POS_IS_VALID, false); BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt, obj); + Real maxSpeed = getMaxSpeedForCondition(bdt); // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at if( desiredSpeed > maxSpeed ) @@ -1360,7 +1358,7 @@ void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt, obj); + Real maxSpeed = getMaxSpeedForCondition(bdt); if( desiredSpeed > maxSpeed ) desiredSpeed = maxSpeed; @@ -1472,7 +1470,7 @@ void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) { BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt, obj); + Real maxSpeed = getMaxSpeedForCondition(bdt); Real maxTurnRate = getMaxTurnRate(bdt); Real maxAcceleration = getMaxAcceleration(bdt); @@ -1484,7 +1482,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, // // See if we are turning. If so, use the min turn speed. // - Real turnSpeed = getMinTurnSpeed(obj); + Real turnSpeed = getMinTurnSpeed(); Real angle = obj->getOrientation(); // Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); // Real desiredAngle = angle + relAngle; @@ -1778,9 +1776,9 @@ Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) } //------------------------------------------------------------------------------------------------- -Real Locomotor::calcMinTurnRadius(BodyDamageType condition, const Object* obj, Real* timeToTravelThatDist) const +Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const { - Real minSpeed = getMinSpeed(obj); // in dist/frame + Real minSpeed = getMinSpeed(); // in dist/frame Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame /* @@ -1815,7 +1813,7 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState(), obj ); + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); if( desiredSpeed > maxSpeed ) desiredSpeed = maxSpeed; @@ -1860,10 +1858,10 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(obj), getBraking()); + Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(), getBraking()); if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) { - goalSpeed = getMinSpeed(obj); + goalSpeed = getMinSpeed(); } @@ -1906,7 +1904,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState(), obj ); + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); if( desiredSpeed > maxSpeed ) desiredSpeed = maxSpeed; @@ -1987,10 +1985,10 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, } //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(obj), getBraking()); + Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(), getBraking()); if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) { - goalSpeed = getMinSpeed(obj); + goalSpeed = getMinSpeed(); } // @@ -2057,7 +2055,7 @@ void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, angleTowardPos += aimDir; BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = calcMinTurnRadius(bdt, obj, nullptr) * 4; + Real turnRadius = calcMinTurnRadius(bdt, nullptr) * 4; // project a spot "radius" dist away from it, in that dir Coord3D desiredPos = goalPos; @@ -2106,16 +2104,16 @@ void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, { BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxForwardSpeed = getMaxSpeedForCondition(bdt, obj); - desiredSpeed = clamp(getMinSpeed(obj), desiredSpeed, maxForwardSpeed); + Real maxForwardSpeed = getMaxSpeedForCondition(bdt); + desiredSpeed = clamp(getMinSpeed(), desiredSpeed, maxForwardSpeed); Real actualForwardSpeed = physics->getForwardSpeed3D(); if (getBraking() > 0) { //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualForwardSpeed, getMinSpeed(obj), getBraking()); + Real slowDownDist = calcSlowDownDist(actualForwardSpeed, getMinSpeed(), getBraking()); if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - desiredSpeed = getMinSpeed(obj); + desiredSpeed = getMinSpeed(); } Coord3D localGoalPos = goalPos; @@ -2543,7 +2541,7 @@ void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, Real maxAcceleration = getMaxAcceleration(bdt); // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition(bdt, obj); + Real maxSpeed = getMaxSpeedForCondition(bdt); if( desiredSpeed > maxSpeed ) desiredSpeed = maxSpeed; @@ -2581,10 +2579,10 @@ void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) { - Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(obj), getBraking()); + Real slowDownDist = calcSlowDownDist(actualSpeed, getMinSpeed(), getBraking()); if (onPathDistToGoal < slowDownDist) { - goalSpeed = getMinSpeed(obj); + goalSpeed = getMinSpeed(); } } @@ -2695,7 +2693,7 @@ void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *phys { DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); /// @todo srj -- should these also use the "circling radius" stuff, like wings? - moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed(obj)); + moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); } //------------------------------------------------------------------------------------------------- @@ -2710,7 +2708,7 @@ void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physi BodyDamageType bdt = obj->getBodyModule()->getDamageState(); Real turnRadius = m_template->m_circlingRadius; if (turnRadius == 0.0f) - turnRadius = calcMinTurnRadius(bdt, obj, nullptr); + turnRadius = calcMinTurnRadius(bdt, nullptr); // find the direction towards our "maintain pos" const Coord3D* pos = obj->getPosition(); @@ -2733,7 +2731,7 @@ void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physi Coord3D desiredPos = m_maintainPos; desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; - moveTowardsPositionWings(obj, physics, desiredPos, 0, getMinSpeed(obj)); + moveTowardsPositionWings(obj, physics, desiredPos, 0, getMinSpeed()); } } @@ -2751,7 +2749,7 @@ void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physi // // Stop // - Real minSpeed = max( 1.0E-10f, getMinSpeed(obj) ); + Real minSpeed = max( 1.0E-10f, getMinSpeed() ); Real speedDelta = minSpeed - actualSpeed; if (fabs(speedDelta) > minSpeed) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index afabc8462f9..9d9ffce18b1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -415,7 +415,7 @@ class DeliverPayloadNugget : public ObjectCreationNugget if (physics) { Coord3D startingForce = *transport->getUnitDirectionVector2D(); - Real maxSpeed = ai->getCurLocomotor()->getMaxSpeedForCondition(transport->getBodyModule()->getDamageState(), transport); + Real maxSpeed = ai->getCurLocomotor()->getMaxSpeedForCondition(transport->getBodyModule()->getDamageState()); Real factor = maxSpeed * physics->getMass(); startingForce.x *= factor; startingForce.y *= factor; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index f806d77b6f7..977aec18670 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -791,7 +791,7 @@ WhichTurretType AIUpdateInterface::getWhichTurretForWeaponSlot(WeaponSlotType ws Real AIUpdateInterface::getCurLocomotorSpeed() const { if (m_curLocomotor != nullptr) - return m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState(), getObject()); + return m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState()); DEBUG_LOG(("no current locomotor!")); return 0.0f; @@ -1486,10 +1486,10 @@ Bool AIUpdateInterface::processCollision(PhysicsBehavior *physics, Object *other #define dont_MOVE_AROUND // It just causes more problems than it fixes. jba. #ifdef MOVE_AROUND if (m_curLocomotor!= nullptr && (other->isKindOf(KINDOF_INFANTRY)==getObject()->isKindOf(KINDOF_INFANTRY))) { - Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState(), getObject()); + Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState()); Locomotor *hisLoco = aiOther->getCurLocomotor(); if (hisLoco) { - Real hisMaxSpeed = hisLoco->getMaxSpeedForCondition(other->getBodyModule()->getDamageState(), other); + Real hisMaxSpeed = hisLoco->getMaxSpeedForCondition(other->getBodyModule()->getDamageState()); if (hisMaxSpeed > 0.05 && hisMaxSpeed < 0.6f*myMaxSpeed) { aiOther->aiMoveAwayFromUnit(getObject(), CMD_FROM_AI); return FALSE; @@ -2163,7 +2163,7 @@ UpdateSleepTime AIUpdateInterface::doLocomotor() case POSITION_EXPLICIT: { Real speed = m_desiredSpeed; - Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState(), getObject()); + Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState()); if( speed == FAST_AS_POSSIBLE || speed > myMaxSpeed ) speed = myMaxSpeed; m_curLocomotor->locoUpdate_moveTowardsPosition(getObject(), @@ -2210,7 +2210,7 @@ UpdateSleepTime AIUpdateInterface::doLocomotor() } Real speed = m_desiredSpeed; - Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState(), getObject()); + Real myMaxSpeed = m_curLocomotor->getMaxSpeedForCondition(getObject()->getBodyModule()->getDamageState()); if( speed == FAST_AS_POSSIBLE || speed > myMaxSpeed ) speed = myMaxSpeed; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index 7ca28879332..ca618b60a80 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -330,7 +330,7 @@ Real DeliverPayloadAIUpdate::calcMinTurnRadius(Real* timeToTravelThatDist) const const Locomotor* loco = getCurLocomotor(); BodyDamageType bdt = getObject()->getBodyModule()->getDamageState(); /// @todo srj -- this should probably use min-speed, not max-speed... fix after E3 - Real maxSpeed = loco->getMaxSpeedForCondition(bdt, getObject()); // in dist/frame + Real maxSpeed = loco->getMaxSpeedForCondition(bdt); // in dist/frame Real maxTurnRate = loco->getMaxTurnRate(bdt); // in rads/frame /* diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index d45d166347d..f9ff39be45a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -735,7 +735,7 @@ class JetTakeoffOrLandingState : public AIFollowPathState loco->setMaxLift(BIGNUM); BodyDamageType bdt = jet->getBodyModule()->getDamageState(); m_maxLift = loco->getMaxLift(bdt); - m_maxSpeed = loco->getMaxSpeedForCondition(bdt, jet); + m_maxSpeed = loco->getMaxSpeedForCondition(bdt); m_landingSoundPlayed = FALSE; if (m_landing) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 5f2faded46e..78b44dc99ca 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -608,7 +608,7 @@ void MissileAIUpdate::doKillState() Real closeEnough = 1.0f; if (curLoco) { - closeEnough = curLoco->getMaxSpeedForCondition(BODY_PRISTINE, getObject()); + closeEnough = curLoco->getMaxSpeedForCondition(BODY_PRISTINE); } Real distanceToTargetSq = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_BOUNDINGSPHERE_3D); //DEBUG_LOG(("Distance to target %f, closeEnough %f", sqrt(distanceToTargetSq), closeEnough)); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index f0c4130fcd9..4989cb60827 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -958,7 +958,7 @@ Real PhysicsBehavior::getForwardSpeed2D() const Real vy = m_vel.y * dir->y; Real dot = vx + vy; -#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED Real speed = (Real)sqrtf( vx*vx + vy*vy ); if (dot >= 0.0f) @@ -967,16 +967,13 @@ Real PhysicsBehavior::getForwardSpeed2D() const #else -#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED - if (const AIUpdateInterface *ai = getObject()->getAIUpdateInterface()) +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS + if (useLegacyForwardSpeed()) { - if (ai->getLastCommandSource() == CMD_FROM_SCRIPT) - { - Real speed = (Real)sqrtf( vx*vx + vy*vy ); - if (dot >= 0.0f) - return speed; - return -speed; - } + Real speed = (Real)sqrtf( vx*vx + vy*vy ); + if (dot >= 0.0f) + return speed; + return -speed; } #endif @@ -987,7 +984,7 @@ Real PhysicsBehavior::getForwardSpeed2D() const // and time calculations. The speeds the Locomotor commands are compensated to match, in LocomotorTemplate. return dot; -#endif // RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#endif // USE_RETAIL_PHYSICS_FORWARD_SPEED } //------------------------------------------------------------------------------------------------- @@ -1003,7 +1000,7 @@ Real PhysicsBehavior::getForwardSpeed3D() const Real vz = m_vel.z * dir.Z; Real dot = vx + vy + vz; -#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); if (dot >= 0.0f) @@ -1012,16 +1009,13 @@ Real PhysicsBehavior::getForwardSpeed3D() const #else -#if PRESERVE_RETAIL_SCRIPTED_PHYSICS_FORWARD_SPEED - if (const AIUpdateInterface *ai = getObject()->getAIUpdateInterface()) +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS + if (useLegacyForwardSpeed()) { - if (ai->getLastCommandSource() == CMD_FROM_SCRIPT) - { - Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); - if (dot >= 0.0f) - return speed; - return -speed; - } + Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); + if (dot >= 0.0f) + return speed; + return -speed; } #endif @@ -1029,9 +1023,19 @@ Real PhysicsBehavior::getForwardSpeed3D() const // +-sqrtf(vx*vx+vy*vy+vz*vz). See getForwardSpeed2D for the rationale. return dot; -#endif // RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED +#endif // USE_RETAIL_PHYSICS_FORWARD_SPEED } +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +//------------------------------------------------------------------------------------------------- +Bool PhysicsBehavior::useLegacyForwardSpeed() +{ + // TheSuperHackers @info The retail speeds are kept for the duration of a cinematic, because + // legacy missions time their cinematics against them. + return TheScriptEngine->isInCinematic(); +} +#endif + //------------------------------------------------------------------------------------------------- Bool PhysicsBehavior::isCurrentlyOverlapped(Object *obj) const { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index 023533d1b4a..d431ad9d446 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -33,6 +33,7 @@ #include "Common/AudioHandleSpecialValues.h" #include "Common/FramePacer.h" #include "Common/GameAudio.h" +#include "Common/GameUtility.h" #include "Common/MapObject.h" // For MAP_XY_FACTOR #include "Common/PartitionSolver.h" #include "Common/Player.h" @@ -3834,16 +3835,8 @@ void ScriptActions::doPlayerExitAllBuildings(const AsciiString& playerName) //------------------------------------------------------------------------------------------------- void ScriptActions::doLetterBoxMode(Bool startLetterbox) { - if (startLetterbox) - { - HideControlBar(TRUE); - TheDisplay->enableLetterBox(TRUE); - } - else - { - ShowControlBar(FALSE); - TheDisplay->enableLetterBox(FALSE); - } + TheScriptEngine->friend_notifyLetterBoxActive(startLetterbox); + rts::enableLetterBox(startLetterbox); } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index d10dff653a1..6c706cdba16 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -33,6 +33,7 @@ #include "Common/FileSystem.h" #include "Common/FramePacer.h" #include "Common/GameState.h" +#include "Common/GameUtility.h" #include "Common/LatchRestore.h" #include "Common/MessageStream.h" #include "Common/PerfTimer.h" @@ -446,6 +447,7 @@ m_fade(FADE_NONE), m_freezeByScript(FALSE), m_frameObjectCountChanged(0), m_closeWindowTimer(0), +m_letterBoxActive(FALSE), m_curFadeFrame(0), m_curFadeValue(0.0f), m_endGameTimer(0), @@ -5276,6 +5278,7 @@ void ScriptEngine::reset() m_numFlags = 1; m_endGameTimer = -1; m_closeWindowTimer = -1; + m_letterBoxActive = FALSE; m_callingTeam = nullptr; m_callingObject = nullptr; @@ -8429,6 +8432,22 @@ void ScriptEngine::doUnfreezeTime() m_freezeByScript = FALSE; } +//------------------------------------------------------------------------------------------------- +/** Report the scripted letterbox opening or closing. */ +//------------------------------------------------------------------------------------------------- +void ScriptEngine::friend_notifyLetterBoxActive(Bool active) +{ + m_letterBoxActive = active; +} + +//------------------------------------------------------------------------------------------------- +/** Is a cinematic running right now? */ +//------------------------------------------------------------------------------------------------- +Bool ScriptEngine::isInCinematic() const +{ + return m_letterBoxActive; +} + //------------------------------------------------------------------------------------------------- /** For Debug and Internal builds, returns whether to continue (!pause), for release, returns false */ //------------------------------------------------------------------------------------------------- @@ -8837,6 +8856,7 @@ void ScriptEngine::setGlobalDifficulty( GameDifficulty difficulty ) * 3: Added m_objectsShouldReceiveDifficultyBonus (JKMCD) * 4: current music track info * 5: add ChooseVictimAlwaysUsesNormal + * 6: TheSuperHackers @tweak Adds xfer for m_letterBoxActive */ // ------------------------------------------------------------------------------------------------ void ScriptEngine::xfer( Xfer *xfer ) @@ -8844,7 +8864,11 @@ void ScriptEngine::xfer( Xfer *xfer ) Int i; // version +#if RETAIL_COMPATIBLE_XFER_SAVE const XferVersion currentVersion = 5; +#else + const XferVersion currentVersion = 6; +#endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -9303,6 +9327,15 @@ void ScriptEngine::xfer( Xfer *xfer ) m_ChooseVictimAlwaysUsesNormal = false; } + if (version >= 6) + { + xfer->xferBool(&m_letterBoxActive); + } + else + { + m_letterBoxActive = FALSE; + } + if( xfer->getXferMode() == XFER_LOAD ) { // We are doing a load. If there is no fade active, do a black fade in to start. if (m_fade == FADE_NONE) { @@ -9335,6 +9368,8 @@ void ScriptEngine::loadPostProcess() TheAudio->addAudioEvent(&event); } + rts::enableLetterBox(m_letterBoxActive); + } //#if defined(RTS_DEBUG) From 39eb2c0680073b287e0466233779e9aba8fdafc1 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:28:58 +0200 Subject: [PATCH 08/10] Rename defines and allow to compile out the forward speed scaling --- Core/GameEngine/Include/Common/GameDefines.h | 31 +++++++++++----- .../GameEngine/Include/GameLogic/Locomotor.h | 6 ++-- .../Include/GameLogic/Module/PhysicsUpdate.h | 2 +- .../Source/GameLogic/Object/Locomotor.cpp | 36 +++++++++---------- .../GameLogic/Object/Update/PhysicsUpdate.cpp | 14 ++++---- 5 files changed, 52 insertions(+), 37 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index bec9c9f351f..c93325654b5 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -92,22 +92,37 @@ #endif // Whether to preserve the 1.41x speed discrepancy between straight and diagonal movements of all objects. -#ifndef PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED -#define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED (1) +// Set this to 0 when world objects need to move at consistent speed in all directions. +#ifndef PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY +#define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY (1) +#endif + +// Whether to preserve the arithmetic mean speed based on the original forward speed discrepancy bug. +// The locomotor speeds from the INI files are effectively scaled up a bit so that on average the world objects travel at comparable speeds. +// Set this to 0 when speeds are set correctly by INI settings (recommended). +#ifndef PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE +#define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE (1) #endif // Whether to preserve the 1.41x speed discrepancy between straight and diagonal movements of all objects during cinematics. // Is mostly relevant for the original campaign missions. -#ifndef PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS -#define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS (1) +#ifndef PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS +#define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS (1) #endif + // Whether the retail forward speed is used unconditionally, for every object at all times. -#define USE_RETAIL_PHYSICS_FORWARD_SPEED (RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED) +#define USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() \ + (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY || RETAIL_COMPATIBLE_CRC) + +// Whether the forward speed is scaled to a former averaged value. +#define USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() \ + (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE && !USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY()) -// Whether the retail forward speed is used for the duration of a scripted camera event. Is only -// meaningful when the retail forward speed is not already used unconditionally. -#define USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS (!USE_RETAIL_PHYSICS_FORWARD_SPEED && PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS) +// Whether the retail forward speed is used for the duration of a scripted camera event. +// Is only meaningful when the retail forward speed is not already used unconditionally. +#define USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() \ + (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS && !USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() && USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE()) #ifndef RETAIL_COMPATIBLE_CRC diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index 7b38a7f20a7..1548ba8a896 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -177,7 +177,7 @@ class LocomotorTemplate : public Overridable Real m_liftDamaged; ///< max lift when damaged Real m_braking; ///< max braking (deceleration) Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() Real m_maxSpeedScaled; ///< real max speed Real m_maxSpeedDamagedScaled;///< real speed when "damaged" Real m_minSpeedScaled; ///< real min speed; we should never brake past this @@ -332,7 +332,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot { DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!")); m_maxSpeed = speed; -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() m_maxSpeedScaled = m_template->scaleSpeed(speed); #endif } @@ -471,7 +471,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot Real m_brakingFactor; Real m_maxLift; Real m_maxSpeed; -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() Real m_maxSpeedScaled; #endif Real m_maxAccel; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h index 5f1de0fb198..c706a640ae9 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h @@ -141,7 +141,7 @@ class PhysicsBehavior : public UpdateModule, Real getForwardSpeed2D() const; ///< compute speed along object's 2d direction vector Real getForwardSpeed3D() const; ///< compute speed along object's 3d direction vector -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() static Bool useLegacyForwardSpeed(); #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 3ebabed95cb..c21e79dd147 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -77,7 +77,7 @@ static_assert(ARRAY_SIZE(TheLocomotorPriorityNames) == LOCOMOTOR_PRIORITY_COUNT // PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() // TheSuperHackers @bugfix xezon 30/07/2026 The compensation that equalizes straight and diagonal // movement speeds. @@ -330,7 +330,7 @@ LocomotorTemplate::LocomotorTemplate() m_braking = BIGNUM; m_minSpeed = 0.0f; m_minTurnSpeed = BIGNUM; -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() m_maxSpeedScaled = 0.0f; m_maxSpeedDamagedScaled = 0.0f; m_minSpeedScaled = 0.0f; @@ -477,7 +477,7 @@ void LocomotorTemplate::validate() m_decelPitchLimit = m_accelPitchLimit; #endif -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() // TheSuperHackers @bugfix xezon 30/07/2026 THRUST is the only appearance whose mover measures // itself with getForwardSpeed3D, so the dimension is decided here, once, rather than every time a // speed is read. This runs last so that the twins are computed from the healed and defaulted @@ -503,11 +503,11 @@ void LocomotorTemplate::validate() //------------------------------------------------------------------------------------------------- Real LocomotorTemplate::getActualMaxSpeed() const { -#if USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() || !USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() return m_maxSpeed; #else -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() if (PhysicsBehavior::useLegacyForwardSpeed()) return m_maxSpeed; #endif @@ -519,11 +519,11 @@ Real LocomotorTemplate::getActualMaxSpeed() const //------------------------------------------------------------------------------------------------- Real LocomotorTemplate::getActualMaxSpeedDamaged() const { -#if USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() || !USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() return m_maxSpeedDamaged; #else -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() if (PhysicsBehavior::useLegacyForwardSpeed()) return m_maxSpeedDamaged; #endif @@ -535,11 +535,11 @@ Real LocomotorTemplate::getActualMaxSpeedDamaged() const //------------------------------------------------------------------------------------------------- Real LocomotorTemplate::getActualMinSpeed() const { -#if USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() || !USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() return m_minSpeed; #else -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() if (PhysicsBehavior::useLegacyForwardSpeed()) return m_minSpeed; #endif @@ -551,11 +551,11 @@ Real LocomotorTemplate::getActualMinSpeed() const //------------------------------------------------------------------------------------------------- Real LocomotorTemplate::getActualMinTurnSpeed() const { -#if USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() || !USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() return m_minTurnSpeed; #else -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() if (PhysicsBehavior::useLegacyForwardSpeed()) return m_minTurnSpeed; #endif @@ -567,7 +567,7 @@ Real LocomotorTemplate::getActualMinTurnSpeed() const //------------------------------------------------------------------------------------------------- Real LocomotorTemplate::scaleSpeed(Real speed) const { -#if USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() || !USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() return speed; #else if (m_appearance == LOCO_THRUST) @@ -805,7 +805,7 @@ Locomotor::Locomotor(const LocomotorTemplate* tmpl) m_brakingFactor = 1.0f; m_maxLift = BIGNUM; m_maxSpeed = BIGNUM; -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() m_maxSpeedScaled = BIGNUM; #endif m_maxAccel = BIGNUM; @@ -835,7 +835,7 @@ Locomotor::Locomotor(const Locomotor& that) m_brakingFactor = that.m_brakingFactor; m_maxLift = that.m_maxLift; m_maxSpeed = that.m_maxSpeed; -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() m_maxSpeedScaled = that.m_maxSpeedScaled; #endif m_maxAccel = that.m_maxAccel; @@ -861,7 +861,7 @@ Locomotor& Locomotor::operator=(const Locomotor& that) m_brakingFactor = that.m_brakingFactor; m_maxLift = that.m_maxLift; m_maxSpeed = that.m_maxSpeed; -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() m_maxSpeedScaled = that.m_maxSpeedScaled; #endif m_maxAccel = that.m_maxAccel; @@ -911,7 +911,7 @@ void Locomotor::xfer( Xfer *xfer ) xfer->xferReal(&m_brakingFactor); xfer->xferReal(&m_maxLift); xfer->xferReal(&m_maxSpeed); -#if !USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() // TheSuperHackers @bugfix xezon 30/07/2026 The scaled twin is derived, so recompute it rather // than transfer it. That keeps the save format byte identical and lets saves written before this // change load correctly. In the save direction this simply recomputes the value it already has. @@ -967,11 +967,11 @@ Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const //------------------------------------------------------------------------------------------------- Real Locomotor::getMaxSpeedOverride() const { -#if USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() || !USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() return m_maxSpeed; #else -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() if (PhysicsBehavior::useLegacyForwardSpeed()) return m_maxSpeed; #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 4989cb60827..c164f3d4bf6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -958,7 +958,7 @@ Real PhysicsBehavior::getForwardSpeed2D() const Real vy = m_vel.y * dir->y; Real dot = vx + vy; -#if USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() Real speed = (Real)sqrtf( vx*vx + vy*vy ); if (dot >= 0.0f) @@ -967,7 +967,7 @@ Real PhysicsBehavior::getForwardSpeed2D() const #else -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() if (useLegacyForwardSpeed()) { Real speed = (Real)sqrtf( vx*vx + vy*vy ); @@ -984,7 +984,7 @@ Real PhysicsBehavior::getForwardSpeed2D() const // and time calculations. The speeds the Locomotor commands are compensated to match, in LocomotorTemplate. return dot; -#endif // USE_RETAIL_PHYSICS_FORWARD_SPEED +#endif } //------------------------------------------------------------------------------------------------- @@ -1000,7 +1000,7 @@ Real PhysicsBehavior::getForwardSpeed3D() const Real vz = m_vel.z * dir.Z; Real dot = vx + vy + vz; -#if USE_RETAIL_PHYSICS_FORWARD_SPEED +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); if (dot >= 0.0f) @@ -1009,7 +1009,7 @@ Real PhysicsBehavior::getForwardSpeed3D() const #else -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() if (useLegacyForwardSpeed()) { Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); @@ -1023,10 +1023,10 @@ Real PhysicsBehavior::getForwardSpeed3D() const // +-sqrtf(vx*vx+vy*vy+vz*vz). See getForwardSpeed2D for the rationale. return dot; -#endif // USE_RETAIL_PHYSICS_FORWARD_SPEED +#endif } -#if USE_RETAIL_PHYSICS_FORWARD_SPEED_IN_CINEMATICS +#if USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() //------------------------------------------------------------------------------------------------- Bool PhysicsBehavior::useLegacyForwardSpeed() { From 1b001494104627a9f2d84148fa3f9f0267e63020 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 23 Aug 2026 13:23:19 +0200 Subject: [PATCH 09/10] Code Review fixes --- Core/GameEngine/Include/Common/GameDefines.h | 32 ++++++++++--------- .../GameEngine/Include/GameLogic/Locomotor.h | 16 ++++------ .../Include/GameLogic/ScriptEngine.h | 4 +-- .../Source/GameLogic/Object/Locomotor.cpp | 4 +-- .../Object/Update/AIUpdate/JetAIUpdate.cpp | 2 +- .../GameLogic/Object/Update/PhysicsUpdate.cpp | 2 +- .../GameLogic/ScriptEngine/ScriptEngine.cpp | 14 +++++--- 7 files changed, 40 insertions(+), 34 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index c93325654b5..ee104b46ed3 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -91,7 +91,7 @@ #define PRESERVE_RETAIL_PARTICLES (1) // Preserve original look of particles present in retail Generals 1.08 and Zero Hour 1.04 #endif -// Whether to preserve the 1.41x speed discrepancy between straight and diagonal movements of all objects. +// Whether to preserve the 1.41x speed discrepancy between straight and diagonal movements of all objects that move via a Locomotor. // Set this to 0 when world objects need to move at consistent speed in all directions. #ifndef PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY #define PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY (1) @@ -111,20 +111,6 @@ #endif -// Whether the retail forward speed is used unconditionally, for every object at all times. -#define USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() \ - (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY || RETAIL_COMPATIBLE_CRC) - -// Whether the forward speed is scaled to a former averaged value. -#define USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() \ - (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE && !USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY()) - -// Whether the retail forward speed is used for the duration of a scripted camera event. -// Is only meaningful when the retail forward speed is not already used unconditionally. -#define USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() \ - (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS && !USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() && USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE()) - - #ifndef RETAIL_COMPATIBLE_CRC #define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04 #endif @@ -218,3 +204,19 @@ #define DEFAULT_DISPLAY_BIT_DEPTH 32 #define DEFAULT_DISPLAY_WIDTH 800 // The standard resolution this game was designed for #define DEFAULT_DISPLAY_HEIGHT 600 // The standard resolution this game was designed for + + +// NON-TWEAKABLE DEFINES ARE DOWN HERE + +// Whether the retail forward speed is used unconditionally, for every object at all times. +#define USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() \ + (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY || RETAIL_COMPATIBLE_CRC) + +// Whether the forward speed is scaled to a former averaged value. +#define USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() \ + (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE && !USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY()) + +// Whether the retail forward speed is used for the duration of a cinematic event. +// Is only meaningful when the retail forward speed discrepancy is not preserved and the forward speed is scaled. +#define USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS() \ + (PRESERVE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY_IN_CINEMATICS && !USE_RETAIL_PHYSICS_FORWARD_SPEED_DISCREPANCY() && USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE()) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index 1548ba8a896..731e7e9ccfa 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -140,15 +140,13 @@ class LocomotorTemplate : public Overridable void validate(); - Real getMinSpeed() const { return m_minSpeed; } - private: /// TheSuperHackers @bugfix Speeds authored in INI are understated by the forward speed the Locomotor measures /// itself with, by up to 1/sqrt(2) in 2d and 1/sqrt(3) in 3d, which is what made objects move faster on diagonal /// headings than on axis aligned ones. Each authored speed therefore also gets a "scaled" twin, in world distance - /// per logic frame, computed once at INI load. These accessors hand out whichever of the two the given object should - /// be commanded with. They all return the authored value in retail compatible builds. + /// per logic frame, computed once at INI load. These accessors hand out whichever of the two the mover should be + /// commanded with. They all return the authored value in retail compatible builds. Real getActualMaxSpeed() const; Real getActualMaxSpeedDamaged() const; Real getActualMinSpeed() const; @@ -178,10 +176,10 @@ class LocomotorTemplate : public Overridable Real m_braking; ///< max braking (deceleration) Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn #if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() - Real m_maxSpeedScaled; ///< real max speed - Real m_maxSpeedDamagedScaled;///< real speed when "damaged" - Real m_minSpeedScaled; ///< real min speed; we should never brake past this - Real m_minTurnSpeedScaled; ///< real min turn speed; we must be going >= this speed in order to turn + Real m_maxSpeedScaled; ///< compensated max speed + Real m_maxSpeedDamagedScaled;///< compensated speed when "damaged" + Real m_minSpeedScaled; ///< compensated min speed; we should never brake past this + Real m_minTurnSpeedScaled; ///< compensated min turn speed; we must be going >= this speed in order to turn #endif Real m_preferredHeight; ///< our preferred height (if flying) Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc @@ -277,7 +275,6 @@ class Locomotor : public MemoryPoolObject, public Snapshot LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } - const LocomotorTemplate *getTemplate() const { return m_template; } AsciiString getTemplateName() const { return m_template->m_name;} Real getMinSpeed() const; Real getMinTurnSpeed() const; @@ -336,6 +333,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot m_maxSpeedScaled = m_template->scaleSpeed(speed); #endif } + void setMaxSpeedToMinSpeed() { setMaxSpeed(m_template->m_minSpeed); } void setMaxAcceleration(Real accel) { m_maxAccel = accel; } void setMaxBraking(Real braking) { m_maxBraking = braking; } void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h index 602e8079390..88671d3ca54 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h @@ -310,8 +310,8 @@ class ScriptEngine : public SubsystemInterface, void doFreezeTime(); void doUnfreezeTime(); - void friend_notifyLetterBoxActive(Bool active); ///< Report the scripted letterbox bracket opening or closing - Bool isInCinematic() const; ///< Ask whether a cinematic is running right now + void friend_notifyLetterBoxActive(Bool active); + Bool isLetterBoxActive() const; ///< Ask whether the letterbox has been activated by a script; indicates an active cinematic /// The following functions are used to update and query the debug window Bool isTimeFrozenDebug(); ///< Ask whether the debug window has requested a pause. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index c21e79dd147..de44c2b985f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -80,7 +80,7 @@ static_assert(ARRAY_SIZE(TheLocomotorPriorityNames) == LOCOMOTOR_PRIORITY_COUNT #if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() // TheSuperHackers @bugfix xezon 30/07/2026 The compensation that equalizes straight and diagonal -// movement speeds. +// movement speeds by the arithmetic mean. // // Retail measured forward speed as sqrt(sum of (vi * di)^2) rather than the true projection of the // velocity onto the heading, sum of (vi * di). For a unit heading d that understates the speed by @@ -806,7 +806,7 @@ Locomotor::Locomotor(const LocomotorTemplate* tmpl) m_maxLift = BIGNUM; m_maxSpeed = BIGNUM; #if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() - m_maxSpeedScaled = BIGNUM; + m_maxSpeedScaled = m_template->scaleSpeed(BIGNUM); #endif m_maxAccel = BIGNUM; m_maxBraking = BIGNUM; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index f9ff39be45a..ccf173e9288 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -739,7 +739,7 @@ class JetTakeoffOrLandingState : public AIFollowPathState m_landingSoundPlayed = FALSE; if (m_landing) { - loco->setMaxSpeed(loco->getTemplate()->getMinSpeed()); + loco->setMaxSpeedToMinSpeed(); } else { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index c164f3d4bf6..8f7db86ce39 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -1032,7 +1032,7 @@ Bool PhysicsBehavior::useLegacyForwardSpeed() { // TheSuperHackers @info The retail speeds are kept for the duration of a cinematic, because // legacy missions time their cinematics against them. - return TheScriptEngine->isInCinematic(); + return TheScriptEngine->isLetterBoxActive(); } #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 6c706cdba16..f9b22fc2c3a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -8441,9 +8441,9 @@ void ScriptEngine::friend_notifyLetterBoxActive(Bool active) } //------------------------------------------------------------------------------------------------- -/** Is a cinematic running right now? */ +/** Is the letterbox active right now? */ //------------------------------------------------------------------------------------------------- -Bool ScriptEngine::isInCinematic() const +Bool ScriptEngine::isLetterBoxActive() const { return m_letterBoxActive; } @@ -9333,7 +9333,10 @@ void ScriptEngine::xfer( Xfer *xfer ) } else { - m_letterBoxActive = FALSE; + if (xfer->getXferMode() == XFER_LOAD) + { + m_letterBoxActive = FALSE; + } } if( xfer->getXferMode() == XFER_LOAD ) { @@ -9368,7 +9371,10 @@ void ScriptEngine::loadPostProcess() TheAudio->addAudioEvent(&event); } - rts::enableLetterBox(m_letterBoxActive); + if (m_letterBoxActive) + { + rts::enableLetterBox(TRUE); + } } From 4a53a8892eee348886afdf8f903d03fbb5a41b70 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 23 Aug 2026 13:57:44 +0200 Subject: [PATCH 10/10] Simplify comments, remove obsolete forward declare --- .../Code/GameEngine/Include/GameLogic/Locomotor.h | 1 - .../Source/GameLogic/Object/Locomotor.cpp | 13 +++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index 731e7e9ccfa..4382c3811de 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -40,7 +40,6 @@ class Locomotor; class LocomotorTemplate; class INI; -class Object; class PhysicsBehavior; enum BodyDamageType CPP_11(: Int); enum PhysicsTurningType CPP_11(: Int); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index de44c2b985f..fba9d09ecff 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -478,11 +478,10 @@ void LocomotorTemplate::validate() #endif #if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() - // TheSuperHackers @bugfix xezon 30/07/2026 THRUST is the only appearance whose mover measures - // itself with getForwardSpeed3D, so the dimension is decided here, once, rather than every time a - // speed is read. This runs last so that the twins are computed from the healed and defaulted - // values above, and it is safe to run again on an INI override because each twin is assigned from - // its untouched source rather than multiplied in place. + // TheSuperHackers @info THRUST is the only appearance whose mover measures itself with getForwardSpeed3D, + // so the dimension is decided here, once, rather than every time a speed is read. This runs last so that + // the twins are computed from the healed and defaulted values above, and it is safe to run again on an + // INI override because each twin is assigned from its untouched source rather than multiplied in place. if (m_appearance == LOCO_THRUST) { m_maxSpeedScaled = scaleSpeed3D(m_maxSpeed); @@ -912,9 +911,7 @@ void Locomotor::xfer( Xfer *xfer ) xfer->xferReal(&m_maxLift); xfer->xferReal(&m_maxSpeed); #if USE_RETAIL_PHYSICS_FORWARD_SPEED_AVERAGE() - // TheSuperHackers @bugfix xezon 30/07/2026 The scaled twin is derived, so recompute it rather - // than transfer it. That keeps the save format byte identical and lets saves written before this - // change load correctly. In the save direction this simply recomputes the value it already has. + // TheSuperHackers @info The scaled twin is derived, so recompute it rather than transfer it. m_maxSpeedScaled = m_template->scaleSpeed(m_maxSpeed); #endif xfer->xferReal(&m_maxAccel);