From 8247ba5b0796a652835109f2744b0943752a87d7 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Sat, 17 Jan 2026 17:34:09 +0000 Subject: [PATCH 1/2] refactor(pathfinder): Move obstacle handling functions from PathfindCell header to the cpp (#2140) --- .../GameEngine/Include/GameLogic/AIPathfind.h | 40 +++++-------- .../Source/GameLogic/AI/AIPathfind.cpp | 58 ++++++++++++++++++- .../GameEngine/Include/GameLogic/AIPathfind.h | 40 +++++-------- .../Source/GameLogic/AI/AIPathfind.cpp | 56 +++++++++++++++++- 4 files changed, 137 insertions(+), 57 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h index a9bd8b2cd77..6b1db75e3d9 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -288,9 +288,8 @@ class PathfindCell Bool isObstaclePresent( ObjectID objID ) const; ///< return true if the given object ID is registered as an obstacle in this cell - Bool isObstacleTransparent( ) const{return m_info?m_info->m_obstacleIsTransparent:false; } ///< return true if the obstacle in the cell is KINDOF_CAN_SEE_THROUGHT_STRUCTURE - - Bool isObstacleFence( void ) const {return m_info?m_info->m_obstacleIsFence:false; }///< return true if the given obstacle in the cell is a fence. + inline Bool isObstacleTransparent() const; + inline Bool isObstacleFence(void) const; /// Return estimated cost from given cell to reach goal cell UnsignedInt costToGoal( PathfindCell *goal ); @@ -322,8 +321,8 @@ class PathfindCell inline UnsignedShort getXIndex(void) const {return m_info->m_pos.x;} inline UnsignedShort getYIndex(void) const {return m_info->m_pos.y;} - inline Bool isBlockedByAlly(void) const {return m_info->m_blockedByAlly;} - inline void setBlockedByAlly(Bool blocked) {m_info->m_blockedByAlly = (blocked!=0);} + inline Bool isBlockedByAlly(void) const; + inline void setBlockedByAlly(Bool blocked); inline Bool getOpen(void) const {return m_info->m_open;} inline Bool getClosed(void) const {return m_info->m_closed;} @@ -354,7 +353,7 @@ class PathfindCell inline ObjectID getGoalAircraft(void) const {ObjectID id = m_info?m_info->m_goalAircraftID:INVALID_ID; return id;} inline ObjectID getPosUnit(void) const {ObjectID id = m_info?m_info->m_posUnitID:INVALID_ID; return id;} - inline ObjectID getObstacleID(void) const {ObjectID id = m_info?m_info->m_obstacleID:INVALID_ID; return id;} + inline ObjectID getObstacleID(void) const; void setLayer( PathfindLayerEnum layer ) { m_layer = layer; } ///< set the cell layer PathfindLayerEnum getLayer( void ) const { return (PathfindLayerEnum)m_layer; } ///< get the cell layer @@ -364,14 +363,14 @@ class PathfindCell private: PathfindCellInfo *m_info; - zoneStorageType m_zone:14; ///< Zone. Each zone is a set of adjacent terrain type. If from & to in the same zone, you can successfully pathfind. If not, - // you still may be able to if you can cross multiple terrain types. - UnsignedShort m_aircraftGoal:1; //< This is an aircraft goal cell. - UnsignedShort m_pinched:1; //< This cell is surrounded by obstacle cells. - UnsignedByte m_type:4; ///< what type of cell terrain this is. - UnsignedByte m_flags:4; ///< what type of units are in or moving through this cell. - UnsignedByte m_connectsToLayer:4; ///< This cell can pathfind onto this layer, if > LAYER_TOP. - UnsignedByte m_layer:4; ///< Layer of this cell. + zoneStorageType m_zone : 14; ///< Zone. Each zone is a set of adjacent terrain type. If from & to in the same zone, you can successfully pathfind. If not, + /// you still may be able to if you can cross multiple terrain types. + UnsignedShort m_aircraftGoal : 1; ///< This is an aircraft goal cell. + UnsignedShort m_pinched : 1; ///< This cell is surrounded by obstacle cells. + UnsignedByte m_type : 4; ///< what type of cell terrain this is. + UnsignedByte m_flags : 4; ///< what type of units are in or moving through this cell. + UnsignedByte m_connectsToLayer : 4; ///< This cell can pathfind onto this layer, if > LAYER_TOP. + UnsignedByte m_layer : 4; ///< Layer of this cell. }; typedef PathfindCell *PathfindCellP; @@ -964,16 +963,3 @@ inline Bool Pathfinder::worldToCell( const Coord3D *pos, ICoord2D *cell ) return overflow; } -/** - * Return true if the given object ID is registered as an obstacle in this cell - */ -inline Bool PathfindCell::isObstaclePresent( ObjectID objID ) const -{ - if (objID != INVALID_ID && (getType() == PathfindCell::CELL_OBSTACLE)) - { - DEBUG_ASSERTCRASH(m_info, ("Should have info to be obstacle.")); - return (m_info && m_info->m_obstacleID == objID); - } - - return false; -} diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index dcd1c7069a0..5d0f301875b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -1268,6 +1268,19 @@ Bool PathfindCell::startPathfind( PathfindCell *goalCell ) m_info->m_closed = FALSE; return true; } + +/** + * Set the blocked by ally flag on the pathfind cell info. + */ +inline Bool PathfindCell::isBlockedByAlly(void) const +{ + return m_info->m_blockedByAlly; +} +inline void PathfindCell::setBlockedByAlly(Bool blocked) +{ + m_info->m_blockedByAlly = (blocked != 0); +} + /** * Set the parent pointer. */ @@ -1471,7 +1484,16 @@ void PathfindCell::setPosUnit(ObjectID unitID, const ICoord2D &pos ) /** - * Flag this cell as an obstacle, from the given one + * Return the relevant obstacle ID. + */ +inline ObjectID PathfindCell::getObstacleID(void) const +{ + return m_info ? m_info->m_obstacleID : INVALID_ID; +} + + +/** + * Flag this cell as an obstacle, from the given one. */ void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoord2D &pos ) { @@ -1494,7 +1516,7 @@ void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo return; } - m_type = PathfindCell::CELL_OBSTACLE ; + m_type = PathfindCell::CELL_OBSTACLE; if (!m_info) { m_info = PathfindCellInfo::getACellInfo(this, pos); if (!m_info) { @@ -1733,6 +1755,38 @@ PathfindCell *PathfindCell::removeFromClosedList( PathfindCell *list ) return list; } +/** + * Return true if the given object ID is registered as an obstacle in this cell + */ +inline Bool PathfindCell::isObstaclePresent(ObjectID objID) const +{ + if (objID != INVALID_ID && (getType() == PathfindCell::CELL_OBSTACLE)) + { + DEBUG_ASSERTCRASH(m_info, ("Should have info to be obstacle.")); + return (m_info && m_info->m_obstacleID == objID); + } + + return false; +} + + +/** + * return true if the obstacle in the cell is KINDOF_CAN_SEE_THROUGHT_STRUCTURE + */ +inline Bool PathfindCell::isObstacleTransparent() const +{ + return m_info ? m_info->m_obstacleIsTransparent : false; +} + +/** + * return true if the given obstacle in the cell is a fence. + */ +inline Bool PathfindCell::isObstacleFence(void) const +{ + return m_info ? m_info->m_obstacleIsFence : false; +} + + const Int COST_ORTHOGONAL = 10; const Int COST_DIAGONAL = 14; const Real COST_TO_DISTANCE_FACTOR = 1.0f/10.0f; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h index 89f36104be3..20f0e9e12ae 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -289,9 +289,8 @@ class PathfindCell Bool isObstaclePresent( ObjectID objID ) const; ///< return true if the given object ID is registered as an obstacle in this cell - Bool isObstacleTransparent( ) const{return m_info?m_info->m_obstacleIsTransparent:false; } ///< return true if the obstacle in the cell is KINDOF_CAN_SEE_THROUGHT_STRUCTURE - - Bool isObstacleFence( void ) const {return m_info?m_info->m_obstacleIsFence:false; }///< return true if the given obstacle in the cell is a fence. + inline Bool isObstacleTransparent() const; + inline Bool isObstacleFence(void) const; /// Return estimated cost from given cell to reach goal cell UnsignedInt costToGoal( PathfindCell *goal ); @@ -323,8 +322,8 @@ class PathfindCell inline UnsignedShort getXIndex(void) const {return m_info->m_pos.x;} inline UnsignedShort getYIndex(void) const {return m_info->m_pos.y;} - inline Bool isBlockedByAlly(void) const {return m_info->m_blockedByAlly;} - inline void setBlockedByAlly(Bool blocked) {m_info->m_blockedByAlly = (blocked!=0);} + inline Bool isBlockedByAlly(void) const; + inline void setBlockedByAlly(Bool blocked); inline Bool getOpen(void) const {return m_info->m_open;} inline Bool getClosed(void) const {return m_info->m_closed;} @@ -355,7 +354,7 @@ class PathfindCell inline ObjectID getGoalAircraft(void) const {ObjectID id = m_info?m_info->m_goalAircraftID:INVALID_ID; return id;} inline ObjectID getPosUnit(void) const {ObjectID id = m_info?m_info->m_posUnitID:INVALID_ID; return id;} - inline ObjectID getObstacleID(void) const {ObjectID id = m_info?m_info->m_obstacleID:INVALID_ID; return id;} + inline ObjectID getObstacleID(void) const; void setLayer( PathfindLayerEnum layer ) { m_layer = layer; } ///< set the cell layer PathfindLayerEnum getLayer( void ) const { return (PathfindLayerEnum)m_layer; } ///< get the cell layer @@ -365,14 +364,14 @@ class PathfindCell private: PathfindCellInfo *m_info; - zoneStorageType m_zone:14; ///< Zone. Each zone is a set of adjacent terrain type. If from & to in the same zone, you can successfully pathfind. If not, - // you still may be able to if you can cross multiple terrain types. - UnsignedShort m_aircraftGoal:1; //< This is an aircraft goal cell. - UnsignedShort m_pinched:1; //< This cell is surrounded by obstacle cells. - UnsignedByte m_type:4; ///< what type of cell terrain this is. - UnsignedByte m_flags:4; ///< what type of units are in or moving through this cell. - UnsignedByte m_connectsToLayer:4; ///< This cell can pathfind onto this layer, if > LAYER_TOP. - UnsignedByte m_layer:4; ///< Layer of this cell. + zoneStorageType m_zone : 14; ///< Zone. Each zone is a set of adjacent terrain type. If from & to in the same zone, you can successfully pathfind. If not, + /// you still may be able to if you can cross multiple terrain types. + UnsignedShort m_aircraftGoal : 1; ///< This is an aircraft goal cell. + UnsignedShort m_pinched : 1; ///< This cell is surrounded by obstacle cells. + UnsignedByte m_type : 4; ///< what type of cell terrain this is. + UnsignedByte m_flags : 4; ///< what type of units are in or moving through this cell. + UnsignedByte m_connectsToLayer : 4; ///< This cell can pathfind onto this layer, if > LAYER_TOP. + UnsignedByte m_layer : 4; ///< Layer of this cell. }; typedef PathfindCell *PathfindCellP; @@ -972,16 +971,3 @@ inline Bool Pathfinder::worldToCell( const Coord3D *pos, ICoord2D *cell ) return overflow; } -/** - * Return true if the given object ID is registered as an obstacle in this cell - */ -inline Bool PathfindCell::isObstaclePresent( ObjectID objID ) const -{ - if (objID != INVALID_ID && (getType() == PathfindCell::CELL_OBSTACLE)) - { - DEBUG_ASSERTCRASH(m_info, ("Should have info to be obstacle.")); - return (m_info && m_info->m_obstacleID == objID); - } - - return false; -} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index e5a67328b24..92151f92410 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -1283,6 +1283,19 @@ Bool PathfindCell::startPathfind( PathfindCell *goalCell ) m_info->m_closed = FALSE; return true; } + +/** + * Set the blocked by ally flag on the pathfind cell info. + */ +inline Bool PathfindCell::isBlockedByAlly(void) const +{ + return m_info->m_blockedByAlly; +} +inline void PathfindCell::setBlockedByAlly(Bool blocked) +{ + m_info->m_blockedByAlly = (blocked != 0); +} + /** * Set the parent pointer. */ @@ -1485,6 +1498,15 @@ void PathfindCell::setPosUnit(ObjectID unitID, const ICoord2D &pos ) } +/** + * Return the relevant obstacle ID. + */ +inline ObjectID PathfindCell::getObstacleID(void) const +{ + return m_info ? m_info->m_obstacleID : INVALID_ID; +} + + /** * Flag this cell as an obstacle, from the given one. * Return true if cell was flagged. @@ -1510,7 +1532,7 @@ Bool PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo return true; } - m_type = PathfindCell::CELL_OBSTACLE ; + m_type = PathfindCell::CELL_OBSTACLE; if (!m_info) { m_info = PathfindCellInfo::getACellInfo(this, pos); if (!m_info) { @@ -1750,6 +1772,38 @@ PathfindCell *PathfindCell::removeFromClosedList( PathfindCell *list ) return list; } +/** + * Return true if the given object ID is registered as an obstacle in this cell + */ +inline Bool PathfindCell::isObstaclePresent(ObjectID objID) const +{ + if (objID != INVALID_ID && (getType() == PathfindCell::CELL_OBSTACLE)) + { + DEBUG_ASSERTCRASH(m_info, ("Should have info to be obstacle.")); + return (m_info && m_info->m_obstacleID == objID); + } + + return false; +} + + +/** + * return true if the obstacle in the cell is KINDOF_CAN_SEE_THROUGHT_STRUCTURE + */ +inline Bool PathfindCell::isObstacleTransparent() const +{ + return m_info ? m_info->m_obstacleIsTransparent : false; +} + +/** + * return true if the given obstacle in the cell is a fence. + */ +inline Bool PathfindCell::isObstacleFence(void) const +{ + return m_info ? m_info->m_obstacleIsFence : false; +} + + const Int COST_ORTHOGONAL = 10; const Int COST_DIAGONAL = 14; const Real COST_TO_DISTANCE_FACTOR = 1.0f/10.0f; From 3dbb30be2b92d968c55b04596b9d7c3083358009 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Sun, 18 Jan 2026 15:18:21 +0000 Subject: [PATCH 2/2] bugfix(pathfinder): Fix late game unit lockups and erroneous impassable terrain (#2140) --- Core/GameEngine/Include/Common/GameDefines.h | 9 +- .../GameEngine/Include/GameLogic/AIPathfind.h | 13 ++ .../Source/GameLogic/AI/AIPathfind.cpp | 128 +++++++++++++++++- .../GameEngine/Include/GameLogic/AIPathfind.h | 13 ++ .../Source/GameLogic/AI/AIPathfind.cpp | 118 ++++++++++++++++ 5 files changed, 273 insertions(+), 8 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index 667ea8aa9ff..47a661a963e 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -36,10 +36,13 @@ #endif // This is here to easily toggle between the retail compatible with fixed pathfinding fallback and pure fixed pathfinding mode -#if RETAIL_COMPATIBLE_CRC +#ifndef RETAIL_COMPATIBLE_PATHFINDING #define RETAIL_COMPATIBLE_PATHFINDING (1) -#else -#define RETAIL_COMPATIBLE_PATHFINDING (0) +#endif + +// This is here to easily toggle between the retail compatible pathfinding memory allocation and the new static allocated data mode +#ifndef RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION +#define RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION (1) #endif // This is essentially synonymous for RETAIL_COMPATIBLE_CRC. There is a lot wrong with AIGroup, such as use-after-free, double-free, leaks, diff --git a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h index 6b1db75e3d9..7649f6dce95 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -46,6 +46,9 @@ class PathfindZoneManager; #define INFANTRY_MOVES_THROUGH_INFANTRY +#if !RETAIL_COMPATIBLE_PATHFINDING +#undef RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION +#endif typedef UnsignedShort zoneStorageType; @@ -287,6 +290,11 @@ class PathfindCell Bool isAircraftGoal( void) const {return m_aircraftGoal != 0;} Bool isObstaclePresent( ObjectID objID ) const; ///< return true if the given object ID is registered as an obstacle in this cell +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + // TheSuperHackers @info isObstructionInvalid() and clearObstruction() only used during retail compatible pathfinding failover cleanup + Bool isObstructionInvalid() const { return m_obstacleID != INVALID_ID && m_info == nullptr && (m_type == CELL_OBSTACLE || m_type == CELL_IMPASSABLE); } + void clearObstruction() { m_type = CELL_CLEAR; m_obstacleID = INVALID_ID; m_obstacleIsFence = false; m_obstacleIsTransparent = false; } +#endif inline Bool isObstacleTransparent() const; inline Bool isObstacleFence(void) const; @@ -363,6 +371,11 @@ class PathfindCell private: PathfindCellInfo *m_info; + ObjectID m_obstacleID; ///< the object ID who overlaps this cell + UnsignedInt m_blockedByAlly : 1; ///< True if this cell is blocked by an allied unit. + UnsignedInt m_obstacleIsFence : 1; ///< True if occupied by a fence. + UnsignedInt m_obstacleIsTransparent : 1; ///< True if obstacle is transparent (undefined if obstacleid is invalid) + zoneStorageType m_zone : 14; ///< Zone. Each zone is a set of adjacent terrain type. If from & to in the same zone, you can successfully pathfind. If not, /// you still may be able to if you can cross multiple terrain types. UnsignedShort m_aircraftGoal : 1; ///< This is an aircraft goal cell. diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 5d0f301875b..b612106fa6b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -1111,6 +1111,15 @@ void Pathfinder::forceCleanCells() for (int j = 0; j <= m_extent.hi.y; ++j) { for (int i = 0; i <= m_extent.hi.x; ++i) { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + // TheSuperHackers @bugfix Mauller/DrGoldFish 20/01/2026 when pathfinding resources cannot be allocated to a pathfindCell, + // The function to remove an obstacle returns early and PathfindCells remain flagged as obstacles. + // We need to make sure to reset pathfindCells with a set m_obstacleID and no m_info. + // The use of PathfindCellInfo data for obstacle handling also exausted them resulting in pathfinding lockups. + if (m_map[i][j].isObstructionInvalid()) { + m_map[i][j].clearObstruction(); + } +#endif if (m_map[i][j].hasInfo()) { m_map[i][j].releaseInfo(); } @@ -1238,6 +1247,11 @@ void PathfindCell::reset( ) PathfindCellInfo::releaseACellInfo(m_info); m_info = nullptr; } + m_obstacleID = INVALID_ID; + m_blockedByAlly = false; + m_obstacleIsFence = false; + m_obstacleIsTransparent = false; + m_connectsToLayer = LAYER_INVALID; m_layer = LAYER_GROUND; @@ -1274,11 +1288,29 @@ Bool PathfindCell::startPathfind( PathfindCell *goalCell ) */ inline Bool PathfindCell::isBlockedByAlly(void) const { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_blockedByAlly; + } + return m_info->m_blockedByAlly; +#else + return m_blockedByAlly; +#endif } + inline void PathfindCell::setBlockedByAlly(Bool blocked) { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + m_blockedByAlly = (blocked != 0); + return; + } + m_info->m_blockedByAlly = (blocked != 0); +#else + m_blockedByAlly = (blocked != 0); +#endif } /** @@ -1488,7 +1520,15 @@ void PathfindCell::setPosUnit(ObjectID unitID, const ICoord2D &pos ) */ inline ObjectID PathfindCell::getObstacleID(void) const { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_obstacleID; + } + return m_info ? m_info->m_obstacleID : INVALID_ID; +#else + return m_obstacleID; +#endif } @@ -1509,14 +1549,33 @@ void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo if (isRubble) { m_type = PathfindCell::CELL_RUBBLE; + m_obstacleID = INVALID_ID; + m_obstacleIsFence = false; + m_obstacleIsTransparent = false; +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return; + } + if (m_info) { m_info->m_obstacleID = INVALID_ID; releaseInfo(); } +#endif return; } m_type = PathfindCell::CELL_OBSTACLE; + m_obstacleID = obstacle->getID(); + m_obstacleIsFence = isFence; + m_obstacleIsTransparent = obstacle->isKindOf(KINDOF_CAN_SEE_THROUGH_STRUCTURE); +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + // TheSuperHackers @info In retail mode we need to track orphaned cells set as obstacles so we can cleanup and failover properly + // So we always make sure to set and clear the local obstacle data on the PathfindCell regardless of retail compat or not + if (s_useFixedPathfinding) { + return; + } + if (!m_info) { m_info = PathfindCellInfo::getACellInfo(this, pos); if (!m_info) { @@ -1527,6 +1586,8 @@ void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo m_info->m_obstacleID = obstacle->getID(); m_info->m_obstacleIsFence = isFence; m_info->m_obstacleIsTransparent = obstacle->isKindOf(KINDOF_CAN_SEE_THROUGH_STRUCTURE); +#endif + return; } /** @@ -1534,29 +1595,62 @@ void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo */ void PathfindCell::setType( CellType type ) { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + if (m_obstacleID != INVALID_ID) { + DEBUG_ASSERTCRASH(type == PathfindCell::CELL_OBSTACLE, ("Wrong type.")); + m_type = PathfindCell::CELL_OBSTACLE; + return; + } + } + if (m_info && (m_info->m_obstacleID != INVALID_ID)) { DEBUG_ASSERTCRASH(type==PathfindCell::CELL_OBSTACLE, ("Wrong type.")); m_type = PathfindCell::CELL_OBSTACLE; return; } +#else + if (m_obstacleID != INVALID_ID) { + DEBUG_ASSERTCRASH(type == PathfindCell::CELL_OBSTACLE, ("Wrong type.")); + m_type = PathfindCell::CELL_OBSTACLE; + return; + } +#endif m_type = type; } /** - * Flag this cell as an obstacle, from the given one + * Unflag this cell as an obstacle, from the given one. */ void PathfindCell::removeObstacle( Object *obstacle ) { if (m_type == PathfindCell::CELL_RUBBLE) { m_type = PathfindCell::CELL_CLEAR; } +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + if (m_obstacleID != obstacle->getID()) return; + m_type = PathfindCell::CELL_CLEAR; + m_obstacleID = INVALID_ID; + m_obstacleIsFence = false; + m_obstacleIsTransparent = false; + return; + } + if (!m_info) return; if (m_info->m_obstacleID != obstacle->getID()) return; m_type = PathfindCell::CELL_CLEAR; - if (m_info) { - m_info->m_obstacleID = INVALID_ID; - releaseInfo(); - } + m_info->m_obstacleID = INVALID_ID; + releaseInfo(); + +#else + if (m_obstacleID != obstacle->getID()) return; + m_type = PathfindCell::CELL_CLEAR; +#endif + m_obstacleID = INVALID_ID; + m_obstacleIsFence = false; + m_obstacleIsTransparent = false; + return; } /// put self on "open" list in ascending cost order, return new list @@ -1762,8 +1856,16 @@ inline Bool PathfindCell::isObstaclePresent(ObjectID objID) const { if (objID != INVALID_ID && (getType() == PathfindCell::CELL_OBSTACLE)) { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_obstacleID == objID; + } + DEBUG_ASSERTCRASH(m_info, ("Should have info to be obstacle.")); return (m_info && m_info->m_obstacleID == objID); +#else + return m_obstacleID == objID; +#endif } return false; @@ -1775,7 +1877,15 @@ inline Bool PathfindCell::isObstaclePresent(ObjectID objID) const */ inline Bool PathfindCell::isObstacleTransparent() const { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_obstacleIsTransparent; + } + return m_info ? m_info->m_obstacleIsTransparent : false; +#else + return m_obstacleIsTransparent; +#endif } /** @@ -1783,7 +1893,15 @@ inline Bool PathfindCell::isObstacleTransparent() const */ inline Bool PathfindCell::isObstacleFence(void) const { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_obstacleIsFence; + } + return m_info ? m_info->m_obstacleIsFence : false; +#else + return m_obstacleIsFence; +#endif } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h index 20f0e9e12ae..d2c1f1bf692 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -47,6 +47,9 @@ class PathfindZoneManager; #define INFANTRY_MOVES_THROUGH_INFANTRY +#if !RETAIL_COMPATIBLE_PATHFINDING +#undef RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION +#endif typedef UnsignedShort zoneStorageType; @@ -288,6 +291,11 @@ class PathfindCell Bool isAircraftGoal( void) const {return m_aircraftGoal != 0;} Bool isObstaclePresent( ObjectID objID ) const; ///< return true if the given object ID is registered as an obstacle in this cell +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + // TheSuperHackers @info isObstructionInvalid() and clearObstruction() only used during retail compatible pathfinding failover cleanup + Bool isObstructionInvalid() const { return m_obstacleID != INVALID_ID && m_info == nullptr && (m_type == CELL_OBSTACLE || m_type == CELL_IMPASSABLE); } + void clearObstruction() { m_type = CELL_CLEAR; m_obstacleID = INVALID_ID; m_obstacleIsFence = false; m_obstacleIsTransparent = false; } +#endif inline Bool isObstacleTransparent() const; inline Bool isObstacleFence(void) const; @@ -364,6 +372,11 @@ class PathfindCell private: PathfindCellInfo *m_info; + ObjectID m_obstacleID; ///< the object ID who overlaps this cell + UnsignedInt m_blockedByAlly : 1; ///< True if this cell is blocked by an allied unit. + UnsignedInt m_obstacleIsFence : 1; ///< True if occupied by a fence. + UnsignedInt m_obstacleIsTransparent : 1; ///< True if obstacle is transparent (undefined if obstacleid is invalid) + zoneStorageType m_zone : 14; ///< Zone. Each zone is a set of adjacent terrain type. If from & to in the same zone, you can successfully pathfind. If not, /// you still may be able to if you can cross multiple terrain types. UnsignedShort m_aircraftGoal : 1; ///< This is an aircraft goal cell. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 92151f92410..9c2349fdfd3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -1126,6 +1126,15 @@ void Pathfinder::forceCleanCells() for (int j = 0; j <= m_extent.hi.y; ++j) { for (int i = 0; i <= m_extent.hi.x; ++i) { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + // TheSuperHackers @bugfix Mauller/DrGoldFish 20/01/2026 when pathfinding resources cannot be allocated to a pathfindCell, + // The function to remove an obstacle returns early and PathfindCells remain flagged as obstacles. + // We need to make sure to reset pathfindCells with a set m_obstacleID and no m_info. + // The use of PathfindCellInfo data for obstacle handling also exausted them resulting in pathfinding lockups. + if (m_map[i][j].isObstructionInvalid()) { + m_map[i][j].clearObstruction(); + } +#endif if (m_map[i][j].hasInfo()) { m_map[i][j].releaseInfo(); } @@ -1253,6 +1262,11 @@ void PathfindCell::reset( ) PathfindCellInfo::releaseACellInfo(m_info); m_info = nullptr; } + m_obstacleID = INVALID_ID; + m_blockedByAlly = false; + m_obstacleIsFence = false; + m_obstacleIsTransparent = false; + m_connectsToLayer = LAYER_INVALID; m_layer = LAYER_GROUND; @@ -1289,11 +1303,29 @@ Bool PathfindCell::startPathfind( PathfindCell *goalCell ) */ inline Bool PathfindCell::isBlockedByAlly(void) const { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_blockedByAlly; + } + return m_info->m_blockedByAlly; +#else + return m_blockedByAlly; +#endif } + inline void PathfindCell::setBlockedByAlly(Bool blocked) { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + m_blockedByAlly = (blocked != 0); + return; + } + m_info->m_blockedByAlly = (blocked != 0); +#else + m_blockedByAlly = (blocked != 0); +#endif } /** @@ -1503,7 +1535,15 @@ void PathfindCell::setPosUnit(ObjectID unitID, const ICoord2D &pos ) */ inline ObjectID PathfindCell::getObstacleID(void) const { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_obstacleID; + } + return m_info ? m_info->m_obstacleID : INVALID_ID; +#else + return m_obstacleID; +#endif } @@ -1525,14 +1565,33 @@ Bool PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo if (isRubble) { m_type = PathfindCell::CELL_RUBBLE; + m_obstacleID = INVALID_ID; + m_obstacleIsFence = false; + m_obstacleIsTransparent = false; +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return true; + } + if (m_info) { m_info->m_obstacleID = INVALID_ID; releaseInfo(); } +#endif return true; } m_type = PathfindCell::CELL_OBSTACLE; + m_obstacleID = obstacle->getID(); + m_obstacleIsFence = isFence; + m_obstacleIsTransparent = obstacle->isKindOf(KINDOF_CAN_SEE_THROUGH_STRUCTURE); +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + // TheSuperHackers @info In retail mode we need to track orphaned cells set as obstacles so we can cleanup and failover properly + // So we always make sure to set and clear the local obstacle data on the PathfindCell regardless of retail compat or not + if (s_useFixedPathfinding) { + return true; + } + if (!m_info) { m_info = PathfindCellInfo::getACellInfo(this, pos); if (!m_info) { @@ -1543,6 +1602,7 @@ Bool PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo m_info->m_obstacleID = obstacle->getID(); m_info->m_obstacleIsFence = isFence; m_info->m_obstacleIsTransparent = obstacle->isKindOf(KINDOF_CAN_SEE_THROUGH_STRUCTURE); +#endif return true; } @@ -1551,11 +1611,27 @@ Bool PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo */ void PathfindCell::setType( CellType type ) { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + if (m_obstacleID != INVALID_ID) { + DEBUG_ASSERTCRASH(type == PathfindCell::CELL_OBSTACLE, ("Wrong type.")); + m_type = PathfindCell::CELL_OBSTACLE; + return; + } + } + if (m_info && (m_info->m_obstacleID != INVALID_ID)) { DEBUG_ASSERTCRASH(type==PathfindCell::CELL_OBSTACLE, ("Wrong type.")); m_type = PathfindCell::CELL_OBSTACLE; return; } +#else + if (m_obstacleID != INVALID_ID) { + DEBUG_ASSERTCRASH(type == PathfindCell::CELL_OBSTACLE, ("Wrong type.")); + m_type = PathfindCell::CELL_OBSTACLE; + return; + } +#endif m_type = type; } @@ -1568,11 +1644,29 @@ Bool PathfindCell::removeObstacle( Object *obstacle ) if (m_type == PathfindCell::CELL_RUBBLE) { m_type = PathfindCell::CELL_CLEAR; } +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + if (m_obstacleID != obstacle->getID()) return false; + m_type = PathfindCell::CELL_CLEAR; + m_obstacleID = INVALID_ID; + m_obstacleIsFence = false; + m_obstacleIsTransparent = false; + return true; + } + if (!m_info) return false; if (m_info->m_obstacleID != obstacle->getID()) return false; m_type = PathfindCell::CELL_CLEAR; m_info->m_obstacleID = INVALID_ID; releaseInfo(); + +#else + if (m_obstacleID != obstacle->getID()) return false; + m_type = PathfindCell::CELL_CLEAR; +#endif + m_obstacleID = INVALID_ID; + m_obstacleIsFence = false; + m_obstacleIsTransparent = false; return true; } @@ -1779,8 +1873,16 @@ inline Bool PathfindCell::isObstaclePresent(ObjectID objID) const { if (objID != INVALID_ID && (getType() == PathfindCell::CELL_OBSTACLE)) { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_obstacleID == objID; + } + DEBUG_ASSERTCRASH(m_info, ("Should have info to be obstacle.")); return (m_info && m_info->m_obstacleID == objID); +#else + return m_obstacleID == objID; +#endif } return false; @@ -1792,7 +1894,15 @@ inline Bool PathfindCell::isObstaclePresent(ObjectID objID) const */ inline Bool PathfindCell::isObstacleTransparent() const { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_obstacleIsTransparent; + } + return m_info ? m_info->m_obstacleIsTransparent : false; +#else + return m_obstacleIsTransparent; +#endif } /** @@ -1800,7 +1910,15 @@ inline Bool PathfindCell::isObstacleTransparent() const */ inline Bool PathfindCell::isObstacleFence(void) const { +#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION + if (s_useFixedPathfinding) { + return m_obstacleIsFence; + } + return m_info ? m_info->m_obstacleIsFence : false; +#else + return m_obstacleIsFence; +#endif }