diff --git a/Core/GameEngine/Include/GameLogic/AIPathfind.h b/Core/GameEngine/Include/GameLogic/AIPathfind.h index e654942cd32..d13cbf90798 100644 --- a/Core/GameEngine/Include/GameLogic/AIPathfind.h +++ b/Core/GameEngine/Include/GameLogic/AIPathfind.h @@ -388,9 +388,11 @@ class Pathfinder : PathfindServicesInterface, public Snapshot Int m_queuePRTail; Int m_cumulativeCellsAllocated; -#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_CRC +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING public: Bool m_classifyFenceZeroInit; + UnsignedByte m_tightenPathSpecialInit; + Coord3D m_tightenPathSpecialInitCoords; #endif }; diff --git a/Core/GameEngine/Include/GameLogic/TerrainLogic.h b/Core/GameEngine/Include/GameLogic/TerrainLogic.h index 90dda7f2411..5b005587cc2 100644 --- a/Core/GameEngine/Include/GameLogic/TerrainLogic.h +++ b/Core/GameEngine/Include/GameLogic/TerrainLogic.h @@ -205,6 +205,11 @@ class Bridge : public MemoryPoolObject void setBridgeObjectID( ObjectID id ) { m_bridgeInfo.bridgeObjectID = id; } void setTowerObjectID( ObjectID id, BridgeTowerType which ) { m_bridgeInfo.towerObjectID[ which ] = id; } +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING +private: + Bool isPointOnBridge_SpecialInit(const Coord3D& pos, Bool specialInit, Coord3D& out) const; +#endif + }; //------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 1b16e444058..8a69e78f9d0 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -225,8 +225,10 @@ void Pathfinder::reset() s_forceCleanCells = false; #endif -#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_CRC +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING m_classifyFenceZeroInit = false; + m_tightenPathSpecialInit = 0; + m_tightenPathSpecialInitCoords.zero(); #endif } @@ -364,7 +366,7 @@ void Pathfinder::classifyFence( Object *obj, Bool insert ) cellBounds.lo.x = REAL_TO_INT_FLOOR((pos->x + 0.5f)/PATHFIND_CELL_SIZE_F); cellBounds.lo.y = REAL_TO_INT_FLOOR((pos->y + 0.5f)/PATHFIND_CELL_SIZE_F); // TheSuperHackers @fix Mauller 16/06/2025 Fixes uninitialized variables. -#if RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_PATHFINDING //CRCDEBUG_LOG(("Pathfinder::classifyFence - (%d,%d)", cellBounds.hi.x, cellBounds.hi.y)); // For retail the values on the stack are often either 0 or larger than the map size. @@ -1496,12 +1498,20 @@ Bool Pathfinder::checkForAdjust(Object *obj, const LocomotorSet& locomotorSet, B if (obj->isKindOf(KINDOF_AIRCRAFT)) { adjustedPathExists = true; } else { +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING + m_tightenPathSpecialInit = groupDest != nullptr; +#endif + Bool pathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), dest); if (!pathExists && clientSafeQuickDoesPathExist( locomotorSet, dest, &adjustDest)) { adjustedPathExists = true; } else { adjustedPathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &adjustDest); } + +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING + m_tightenPathSpecialInit = 0; +#endif } if ( adjustedPathExists ) { if (groupDest) { @@ -1647,6 +1657,10 @@ Bool Pathfinder::adjustDestination(Object *obj, const LocomotorSet& locomotorSet layer = TheTerrainLogic->getLayerForDestination(groupDest); } +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING + m_tightenPathSpecialInitCoords.zero(); +#endif + Int i = cell.x; Int j = cell.y; // Check the center cell @@ -4569,14 +4583,26 @@ void Pathfinder::tightenPath(Object *obj, const LocomotorSet& locomotorSet, Coor TightenPathStruct info; getRadiusAndCenter(obj, info.radius, info.center); - info.layer = TheTerrainLogic->getLayerForDestination(from); info.obj = obj; info.locomotorSet = &locomotorSet; info.foundNewDest = false; info.orgDestPos = *to; -#if RETAIL_COMPATIBLE_CRC +#if RETAIL_COMPATIBLE_PATHFINDING + +#if RTS_ZEROHOUR + m_tightenPathSpecialInit = 2; + info.layer = TheTerrainLogic->getLayerForDestination(from); + m_tightenPathSpecialInit = 0; + + info.newDestPos = m_tightenPathSpecialInitCoords; +#else + info.layer = TheTerrainLogic->getLayerForDestination(from); info.newDestPos.zero(); #endif + +#else + info.layer = TheTerrainLogic->getLayerForDestination(from); +#endif iterateCellsAlongLine(*from, *to, info.layer, tightenPathCallback, &info); if (info.foundNewDest) { *from = info.newDestPos; diff --git a/Core/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/Core/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 863564360dc..fb8985015f1 100644 --- a/Core/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/Core/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -435,6 +435,14 @@ Bool Bridge::isPointOnBridge(const Coord3D *pLoc) if (pLoc->y < m_bounds.lo.y) return(false); if (pLoc->y > m_bounds.hi.y) return(false); +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING + Pathfinder& pathfinder = *TheAI->pathfinder(); + if (pathfinder.m_tightenPathSpecialInit) + { + return isPointOnBridge_SpecialInit(*pLoc, pathfinder.m_tightenPathSpecialInit == 2, pathfinder.m_tightenPathSpecialInitCoords); + } +#endif + Vector3 testPt(pLoc->x, pLoc->y, pLoc->z); Vector3 left1(m_bridgeInfo.fromLeft.x, m_bridgeInfo.fromLeft.y, m_bridgeInfo.fromLeft.z); Vector3 right1(m_bridgeInfo.fromRight.x, m_bridgeInfo.fromRight.y, m_bridgeInfo.fromRight.z); @@ -452,6 +460,68 @@ Bool Bridge::isPointOnBridge(const Coord3D *pLoc) return(false); } +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING +// TheSuperHackers @info Caball009 11/11/2026 Special version of function Bridge::isPointOnBridge for retail compatibility. +// The purpose of this function is to get the values for the originally uninitialized variable in Pathfinder::tightenPathCallback. +Bool Bridge::isPointOnBridge_SpecialInit(const Coord3D& pos, Bool specialInit, Coord3D& out) const +{ + const Vector3 testPt(pos.x, pos.y, pos.z); + const Vector3 left1(m_bridgeInfo.fromLeft.x, m_bridgeInfo.fromLeft.y, m_bridgeInfo.fromLeft.z); + const Vector3 right1(m_bridgeInfo.fromRight.x, m_bridgeInfo.fromRight.y, m_bridgeInfo.fromRight.z); + const Vector3 left2(m_bridgeInfo.toLeft.x, m_bridgeInfo.toLeft.y, m_bridgeInfo.toLeft.z); + const Vector3 right2(m_bridgeInfo.toRight.x, m_bridgeInfo.toRight.y, m_bridgeInfo.toRight.z); + + unsigned char flags; + + if (Point_In_Triangle_2D(left1, right1, left2, testPt, 0, 1, flags)) + { + if (specialInit) + { + const Vector3& tri_point0 = left1; + const Vector3& tri_point1 = right1; + + const Vector2 p0p1(tri_point1[0] - tri_point0[0], tri_point1[1] - tri_point0[1]); + const Vector2 p0pT(testPt[0] - tri_point0[0], testPt[1] - tri_point0[1]); + const float z = Vector2::Perp_Dot_Product(p0p1, p0pT); + + out.set(p0p1.X, p0p1.Y, z); + } + else + { + out.set(0.0f, 1000000000.0f, 0.0f); + } + + return true; + } + else + { + const bool inTriangle = Point_In_Triangle_2D(right1, left2, right2, testPt, 0, 1, flags); + + if (specialInit) + { + const Vector3& tri_point0 = right1; + const Vector3& tri_point1 = left2; + + const Vector2 p0p1(tri_point1[0] - tri_point0[0], tri_point1[1] - tri_point0[1]); + const Vector2 p0pT(testPt[0] - tri_point0[0], testPt[1] - tri_point0[1]); + const float z = Vector2::Perp_Dot_Product(p0p1, p0pT); + + out.set(p0p1.X, p0p1.Y, z); + } + else if (inTriangle) + { + out.set(0.0f, 1000000000.0f, 0.0f); + } + else + { + out.set(0.0f, 0.0f, 0.0f); + } + + return inTriangle; + } +} +#endif // RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING + /*------------------------------------------------------------------------------------------------- /** Clip a floating point line to the region provided. The source line runs from p1 to p2, and is clipped * using the clipRegion. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 5dd5c761723..6f80e684773 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -2537,7 +2537,7 @@ void GameLogic::processDestroyList() { //USE_PERF_TIMER(processDestroyList) -#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_CRC +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING // TheSuperHackers @info Set m_classifyFenceZeroInit to true for the first object. It's set to false when this function exits. // Pathfinder::classifyFence may be called indirectly from Object::~Object. TheAI->pathfinder()->m_classifyFenceZeroInit = !m_objectsToDestroy.empty(); @@ -2602,7 +2602,7 @@ void GameLogic::processDestroyList() Object::friend_deleteInstance(currentObject);//actual delete -#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_CRC +#if RTS_ZEROHOUR && RETAIL_COMPATIBLE_PATHFINDING TheAI->pathfinder()->m_classifyFenceZeroInit = false; #endif }