diff --git a/Generals/Code/GameEngine/Include/GameLogic/Weapon.h b/Generals/Code/GameEngine/Include/GameLogic/Weapon.h index 6f5f44336c4..c6e70a56c81 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Weapon.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Weapon.h @@ -613,6 +613,11 @@ class Weapon : public MemoryPoolObject, Bool isWithinAttackRange(const Object *source, const Object *target) const; Bool isWithinAttackRange(const Object *source, const Coord3D* pos) const; + /** return true if the target is within attack range or the weapon has leech range, false otherwise. + */ + Bool isWithinEffectiveAttackRange(const Object* source, const Object* target) const; + Bool isWithinEffectiveAttackRange(const Object* source, const Coord3D* pos) const; + Bool isTooClose(const Object *source, const Object *target) const; Bool isTooClose(const Object *source, const Coord3D *pos) const; diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index b14c99de5a0..838e86139b1 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -1138,7 +1138,7 @@ Bool outOfWeaponRangeObject( State *thisState, void* userData ) // victim->getID(), victim->getTemplate()->getName().str())); return true; } - if (!weapon->hasLeechRange() && !weapon->isWithinAttackRange(obj, victim)) + if (!weapon->isWithinEffectiveAttackRange(obj, victim)) { //CRCDEBUG_LOG(("outOfWeaponRangeObject() - object %d (%s) is out of range for attacking %d (%s)", // obj->getID(), obj->getTemplate()->getName().str(), @@ -5084,6 +5084,17 @@ StateReturnType AIAttackFireWeaponState::update() return STATE_FAILURE; } +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Stubbjax 28/09/2026 The weapon may have gone out of range since we entered this + // state, so we check the range again to avoid partially firing the weapon. + Bool inRange = m_att->isAttackingObject() + ? weapon->isWithinEffectiveAttackRange(obj, victim) + : weapon->isWithinEffectiveAttackRange(obj, getMachineGoalPosition()); + + if (!inRange) + return STATE_FAILURE; +#endif + /** this is the weird case where we have multi turrets, and turret 'a' wants to fire, but someone has changed the current weapon to be one not on him. diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 2c8cc417691..59e480545ce 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -2137,6 +2137,24 @@ Bool Weapon::isWithinAttackRange(const Object *source, const Object *target) con return false; } +//------------------------------------------------------------------------------------------------- +Bool Weapon::isWithinEffectiveAttackRange(const Object* source, const Object* target) const +{ + if (hasLeechRange()) + return true; + + return isWithinAttackRange(source, target); +} + +//------------------------------------------------------------------------------------------------- +Bool Weapon::isWithinEffectiveAttackRange(const Object* source, const Coord3D* pos) const +{ + if (hasLeechRange()) + return true; + + return isWithinAttackRange(source, pos); +} + //------------------------------------------------------------------------------------------------- Bool Weapon::isTooClose(const Object *source, const Object *target) const { diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h index c5997707ba7..e299dbb94fa 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h @@ -632,6 +632,11 @@ class Weapon : public MemoryPoolObject, Bool isWithinAttackRange(const Object *source, const Object *target) const; Bool isWithinAttackRange(const Object *source, const Coord3D* pos) const; + /** return true if the target is within attack range or the weapon has leech range, false otherwise. + */ + Bool isWithinEffectiveAttackRange(const Object* source, const Object* target) const; + Bool isWithinEffectiveAttackRange(const Object* source, const Coord3D* pos) const; + Bool isTooClose(const Object *source, const Object *target) const; Bool isTooClose(const Object *source, const Coord3D *pos) const; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 0075d169a3f..dd38be8e202 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -1143,7 +1143,7 @@ Bool outOfWeaponRangeObject( State *thisState, void* userData ) // victim->getID(), victim->getTemplate()->getName().str())); return true; } - if (!weapon->hasLeechRange() && !weapon->isWithinAttackRange(obj, victim)) + if (!weapon->isWithinEffectiveAttackRange(obj, victim)) { //CRCDEBUG_LOG(("outOfWeaponRangeObject() - object %d (%s) is out of range for attacking %d (%s)", // obj->getID(), obj->getTemplate()->getName().str(), @@ -5240,6 +5240,17 @@ StateReturnType AIAttackFireWeaponState::update() return STATE_FAILURE; } +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Stubbjax 28/09/2026 The weapon may have gone out of range since we entered this + // state, so we check the range again to avoid partially firing the weapon. + Bool inRange = m_att->isAttackingObject() + ? weapon->isWithinEffectiveAttackRange(obj, victim) + : weapon->isWithinEffectiveAttackRange(obj, getMachineGoalPosition()); + + if (!inRange) + return STATE_FAILURE; +#endif + /** this is the weird case where we have multi turrets, and turret 'a' wants to fire, but someone has changed the current weapon to be one not on him. @@ -5318,6 +5329,15 @@ StateReturnType AIAttackFireWeaponState::update() Weapon *weapon = obj->getWeaponInWeaponSlot( (WeaponSlotType)slot ); if ( weapon ) { +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Stubbjax 28/09/2026 The weapon may have gone out of range since we entered this + // state, so we check the range again to avoid partially firing the weapon. + Bool inRange = weapon->isWithinEffectiveAttackRange(obj, getMachineGoalPosition()); + + if (!inRange) + continue; +#endif + if ( weapon->fireWeapon(obj, getMachineGoalPosition()) ) //fire() returns 'reloaded' obj->releaseWeaponLock(LOCKED_TEMPORARILY);// unlock, 'cause we're loaded diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 0cd912d485c..66c1e52d34f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -2323,6 +2323,24 @@ Bool Weapon::isWithinAttackRange(const Object *source, const Object *target) con return false; } +//------------------------------------------------------------------------------------------------- +Bool Weapon::isWithinEffectiveAttackRange(const Object* source, const Object *target) const +{ + if (hasLeechRange()) + return true; + + return isWithinAttackRange(source, target); +} + +//------------------------------------------------------------------------------------------------- +Bool Weapon::isWithinEffectiveAttackRange(const Object* source, const Coord3D* pos) const +{ + if (hasLeechRange()) + return true; + + return isWithinAttackRange(source, pos); +} + //------------------------------------------------------------------------------------------------- Bool Weapon::isTooClose(const Object *source, const Object *target) const {