diff --git a/Generals/Code/GameEngine/Include/GameLogic/AI.h b/Generals/Code/GameEngine/Include/GameLogic/AI.h index 304991bbe7..271e44e6bf 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/AI.h +++ b/Generals/Code/GameEngine/Include/GameLogic/AI.h @@ -74,6 +74,7 @@ enum AIDebugOptions CPP_11(: Int) AI_DEBUG_TERRAIN, AI_DEBUG_CELLS, AI_DEBUG_GROUND_PATHS, + AI_DEBUG_ZONES, AI_DEBUG_END }; diff --git a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h index 05f90a576f..5cdbbe819c 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -33,12 +33,13 @@ #include "Common/Snapshot.h" //#include "GameLogic/Locomotor.h" // no, do not include this, unless you like long recompiles #include "GameLogic/LocomotorSet.h" +#include "GameLogic/GameLogic.h" class Bridge; class Object; -class PathfindCell; -class PathfindZoneManager; class Weapon; +class PathfindZoneManager; +class PathfindCell; // How close is close enough when moving. @@ -277,13 +278,13 @@ class PathfindCell enum CellType { - CELL_CLEAR = 0x00, ///< clear, unobstructed ground - CELL_WATER = 0x01, ///< water area - CELL_CLIFF = 0x02, ///< steep altitude change - CELL_RUBBLE = 0x03, ///< Cell is occupied by rubble. - CELL_OBSTACLE = 0x04, ///< impassable area - CELL_unused = 0x08, ///< Unused. - CELL_IMPASSABLE = 0x0B ///< Just plain impassable except for aircraft. + CELL_CLEAR = 0x00, ///< clear, unobstructed ground + CELL_WATER = 0x01, ///< water area + CELL_CLIFF = 0x02, ///< steep altitude change + CELL_RUBBLE = 0x03, ///< Cell is occupied by rubble. + CELL_OBSTACLE = 0x04, ///< Occupied by a structure + CELL_BRIDGE_IMPASSABLE = 0x05, ///< Piece of a bridge that is impassable. + CELL_IMPASSABLE = 0x06 ///< Just plain impassable except for aircraft. }; enum CellFlags @@ -301,8 +302,8 @@ class PathfindCell PathfindCell(); ~PathfindCell(); - void setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoord2D &pos ); ///< flag this cell as an obstacle, from the given one - void removeObstacle( Object *obstacle ); ///< flag this cell as an obstacle, from the given one + Bool setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoord2D &pos ); ///< flag this cell as an obstacle, from the given one + Bool removeObstacle( Object *obstacle ); ///< unflag this cell as an obstacle, from the given one void setType( CellType type ); ///< set the cell type CellType getType() const { return (CellType)m_type; } ///< get the cell type CellFlags getFlags() const { return (CellFlags)m_flags; } ///< get the cell type @@ -356,13 +357,13 @@ class PathfindCell inline UnsignedInt getCostSoFar() const {return m_info->m_costSoFar;} inline UnsignedInt getTotalCost() const {return m_info->m_totalCost;} - inline void setCostSoFar(UnsignedInt cost) {m_info->m_costSoFar = cost;} - inline void setTotalCost(UnsignedInt cost) {m_info->m_totalCost = cost;} + inline void setCostSoFar(UnsignedInt cost) { if( m_info ) m_info->m_costSoFar = cost;} + inline void setTotalCost(UnsignedInt cost) { if( m_info ) m_info->m_totalCost = cost;} void setParentCell(PathfindCell* parent); void clearParentCell(); void setParentCellHierarchical(PathfindCell* parent); - inline PathfindCell* getParentCell() const {return m_info->m_pathParent?m_info->m_pathParent->m_cell: nullptr;} + inline PathfindCell* getParentCell() const {return m_info ? m_info->m_pathParent ? m_info->m_pathParent->m_cell : nullptr : nullptr;} Bool startPathfind( PathfindCell *goalCell ); Bool getPinched() const {return m_pinched;} @@ -529,14 +530,16 @@ class PathfindZoneManager public: enum {INITIAL_ZONES = 256}; enum {ZONE_BLOCK_SIZE = 10}; // Zones are calculated in blocks of 20x20. This way, the raw zone numbers can be used to + enum {UNINITIALIZED_ZONE = 0}; // compute hierarchically between the 20x20 blocks of cells. jba. PathfindZoneManager(); ~PathfindZoneManager(); void reset(); - Bool needToCalculateZones() const {return m_needToCalculateZones;} ///< Returns true if the zones need to be recalculated. - void markZonesDirty() ; ///< Called when the zones need to be recalculated. + Bool needToCalculateZones() const {return m_nextFrameToCalculateZones <= TheGameLogic->getFrame() ;} ///< Returns true if the zones need to be recalculated. + void markZonesDirty(); ///< Called when the zones need to be recalculated. + void updateZonesForModify( PathfindCell **map, PathfindLayer layers[], const IRegion2D &structureBounds, const IRegion2D &globalBounds ) ; ///< Called to recalculate an area when a structure has been removed. void calculateZones( PathfindCell **map, PathfindLayer layers[], const IRegion2D &bounds); ///< Does zone calculations. zoneStorageType getEffectiveZone(LocomotorSurfaceTypeMask acceptableSurfaces, Bool crusher, zoneStorageType zone) const; zoneStorageType getEffectiveTerrainZone(zoneStorageType zone) const; @@ -557,18 +560,18 @@ class PathfindZoneManager void setBridge(Int cellX, Int cellY, Bool bridge); Bool interactsWithBridge(Int cellX, Int cellY) const; -protected: +private: void allocateZones(); void freeZones(); void freeBlocks(); -protected: +private: ZoneBlock *m_blockOfZoneBlocks; ///< Zone blocks - Info for hierarchical pathfinding at a "blocky" level. ZoneBlock **m_zoneBlocks; ///< Zone blocks as a matrix - contains matrix indexing into the map. ICoord2D m_zoneBlockExtent; ///< Zone block extents. Not the same scale as the pathfind extents. UnsignedShort m_maxZone; ///< Max zone used. - Bool m_needToCalculateZones; ///< True if terrain has changed. + UnsignedInt m_nextFrameToCalculateZones; ///< When should I recalculate, next?. UnsignedShort m_zonesAllocated; zoneStorageType *m_groundCliffZones; zoneStorageType *m_groundWaterZones; @@ -590,7 +593,7 @@ class PathfindServicesInterface { virtual Path *findPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to )=0; ///< Find a short, valid path between given locations /** Find a short, valid path to a location NEAR the to location. - This succeds when the destination is unreachable (like inside a building). + This succeeds when the destination is unreachable (like inside a building). If the destination is unreachable, it will adjust the to point. */ virtual Path *findClosestPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, Coord3D *to, Bool blocked, Real pathCostMultiplier, Bool moveAllies )=0; @@ -619,7 +622,7 @@ class Pathfinder : PathfindServicesInterface, public Snapshot private: virtual Path *findPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to); ///< Find a short, valid path between given locations /** Find a short, valid path to a location NEAR the to location. - This succeds when the destination is unreachable (like inside a building). + This succeeds when the destination is unreachable (like inside a building). If the destination is unreachable, it will adjust the to point. */ virtual Path *findClosestPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, Coord3D *to, Bool blocked, Real pathCostMultiplier, Bool moveAllies ); @@ -648,7 +651,8 @@ class Pathfinder : PathfindServicesInterface, public Snapshot void xfer( Xfer *xfer ); void loadPostProcess(); - Bool quickDoesPathExist( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain & buildings check - fast) + Bool clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain & buildings check - fast) + Bool clientSafeQuickDoesPathExistForUI( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain only - fast) Bool slowDoesPathExist( Object *obj, const Coord3D *from, const Coord3D *to, ObjectID ignoreObject=INVALID_ID ); ///< Can we build any path at all between the locations (terrain, buildings & units check - slower) diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 6767643f7b..6708247cdd 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -58,7 +58,6 @@ #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. - #define no_INTENSE_DEBUG #define DEBUG_QPF @@ -76,6 +75,20 @@ //------------------------------------------------------------------------------------------------- +static inline Bool IS_IMPASSABLE(PathfindCell::CellType type) { + // Return true if cell is impassable to ground units. jba. [8/18/2003] + if (type==PathfindCell::CELL_IMPASSABLE) { + return true; + } + if (type==PathfindCell::CELL_OBSTACLE) { + return true; + } + if (type==PathfindCell::CELL_BRIDGE_IMPASSABLE) { + return true; + } + return false; +} + struct TCheckMovementInfo { @@ -98,6 +111,8 @@ inline Int IABS(Int x) { if (x>=0) return x; return -x;}; //----------------------------------------------------------------------------------- static Int frameToShowObstacles; + +constexpr const UnsignedInt ZONE_UPDATE_FREQUENCY = 300; constexpr const UnsignedInt MAX_CELL_COUNT = 500; constexpr const UnsignedInt MAX_ADJUSTMENT_CELL_COUNT = 400; constexpr const UnsignedInt MAX_SAFE_PATH_CELL_COUNT = 2000; @@ -1545,11 +1560,12 @@ inline ObjectID PathfindCell::getObstacleID() const /** * Flag this cell as an obstacle, from the given one. + * Return true if cell was flagged. */ -void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoord2D &pos ) +Bool PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoord2D &pos ) { if (m_type!=PathfindCell::CELL_CLEAR && m_type != PathfindCell::CELL_IMPASSABLE) { - return; + return false; } Bool isRubble = false; @@ -1565,7 +1581,7 @@ void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo m_obstacleIsTransparent = false; #if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION if (s_useFixedPathfinding) { - return; + return true; } if (m_info) { @@ -1573,7 +1589,7 @@ void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo releaseInfo(); } #endif - return; + return true; } m_type = PathfindCell::CELL_OBSTACLE; @@ -1584,21 +1600,21 @@ void PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoo // 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; + return true; } if (!m_info) { m_info = PathfindCellInfo::getACellInfo(this, pos); if (!m_info) { DEBUG_CRASH(("Not enough PathFindCellInfos in pool.")); - return; + return false; } } m_info->m_obstacleID = obstacle->getID(); m_info->m_obstacleIsFence = isFence; m_info->m_obstacleIsTransparent = obstacle->isKindOf(KINDOF_CAN_SEE_THROUGH_STRUCTURE); #endif - return; + return true; } /** @@ -1632,36 +1648,37 @@ void PathfindCell::setType( CellType type ) /** * Unflag this cell as an obstacle, from the given one. + * Return true if this cell was previously flagged as an obstacle by this object. */ -void PathfindCell::removeObstacle( Object *obstacle ) +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; + if (m_obstacleID != obstacle->getID()) return false; m_type = PathfindCell::CELL_CLEAR; m_obstacleID = INVALID_ID; m_obstacleIsFence = false; m_obstacleIsTransparent = false; - return; + return true; } - if (!m_info) return; - if (m_info->m_obstacleID != obstacle->getID()) return; + 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; + if (m_obstacleID != obstacle->getID()) return false; m_type = PathfindCell::CELL_CLEAR; #endif m_obstacleID = INVALID_ID; m_obstacleIsFence = false; m_obstacleIsTransparent = false; - return; + return true; } /// put self on "open" list in ascending cost order, return new list @@ -1941,7 +1958,12 @@ UnsignedInt PathfindCell::costToGoal( PathfindCell *goal ) UnsignedInt PathfindCell::costToHierGoal( PathfindCell *goal ) { - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); + if( !m_info ) + { + DEBUG_CRASH( ("Has to have info.") ); + return 100000; //...patch hack 1.01 + } + Int dx = m_info->m_pos.x - goal->getXIndex(); Int dy = m_info->m_pos.y - goal->getYIndex(); Int cost = REAL_TO_INT_FLOOR(COST_ORTHOGONAL*sqrt(dx*dx + dy*dy) + 0.5f); @@ -2304,6 +2326,12 @@ zoneStorageType ZoneBlock::getEffectiveZone( LocomotorSurfaceTypeMask acceptable Bool crusher, zoneStorageType zone) const { DEBUG_ASSERTCRASH(zone, ("Zone not set")); +#if RTS_ZEROHOUR + if (zone==PathfindZoneManager::UNINITIALIZED_ZONE) { + return zone; + } +#endif + if (acceptableSurfaces&LOCOMOTORSURFACE_AIR) return 1; // air is all zone 1. if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && @@ -2388,7 +2416,7 @@ void ZoneBlock::allocateZones() //------------------------ PathfindZoneManager ------------------------------- PathfindZoneManager::PathfindZoneManager() : m_maxZone(0), -m_needToCalculateZones(false), +m_nextFrameToCalculateZones(0), m_groundCliffZones(nullptr), m_groundWaterZones(nullptr), m_groundRubbleZones(nullptr), @@ -2485,17 +2513,25 @@ void PathfindZoneManager::allocateBlocks(const IRegion2D &globalBounds) } } -void PathfindZoneManager::markZonesDirty() ///< Called when the zones need to be recalculated. -{ - m_needToCalculateZones = true; -} - void PathfindZoneManager::reset() ///< Called when the map is reset. { freeZones(); freeBlocks(); } +void PathfindZoneManager::markZonesDirty() ///< Called when the zones need to be recalculated. +{ +#if RTS_GENERALS + m_nextFrameToCalculateZones = TheGameLogic->getFrame(); +#else + if (TheGameLogic->getFrame()<2) { + m_nextFrameToCalculateZones = 2; + return; + } + m_nextFrameToCalculateZones = MIN( m_nextFrameToCalculateZones, TheGameLogic->getFrame() + ZONE_UPDATE_FREQUENCY ); +#endif +} + /** * Calculate zones. A zone is an area of the same terrain - clear, water or cliff. * The utility of zones is that if current location and destination are in the same zone, @@ -2503,12 +2539,20 @@ void PathfindZoneManager::reset() ///< Called when the map is reset. * If you are a multiple terrain vehicle, like amphibious transport, the lookup is a little more * complicated. */ + +#define dont_forceRefreshCalling +#ifdef forceRefreshCalling +static Bool s_stopForceCalling = FALSE; +#endif + void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer layers[], const IRegion2D &globalBounds ) { #ifdef DEBUG_QPF #if defined(DEBUG_LOGGING) __int64 startTime64; - double timeToUpdate=0.0f; + static double timeToUpdate = 0.0f; + static double averageTimeToUpdate = 0.0f; + static Int updateSamples = 0; __int64 endTime64,freq64; QueryPerformanceFrequency((LARGE_INTEGER *)&freq64); QueryPerformanceCounter((LARGE_INTEGER *)&startTime64); @@ -2543,10 +2587,12 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye if (bounds.hi.y > globalBounds.hi.y) { bounds.hi.y = globalBounds.hi.y; } +#if RTS_GENERALS if (bounds.lo.x>bounds.hi.x || bounds.lo.y>bounds.hi.y) { DEBUG_CRASH(("Incorrect bounds calculation. Logic error, fix me. jba.")); continue; } +#endif m_zoneBlocks[xBlock][yBlock].setInteractsWithBridge(false); for( j=bounds.lo.y; j<=bounds.hi.y; j++ ) { for( i=bounds.lo.x; i<=bounds.hi.x; i++ ) { @@ -2566,10 +2612,12 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye if (cell->getZone()==0) { cell->setZone(m_maxZone); m_maxZone++; +#if RTS_GENERALS if (m_maxZone>= maxZones) { DEBUG_CRASH(("Ran out of pathfind zones. SERIOUS ERROR! jba.")); break; } +#endif } if (cell->getConnectLayer() > LAYER_GROUND) { m_zoneBlocks[xBlock][yBlock].setInteractsWithBridge(true); @@ -2581,9 +2629,6 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye } Int totalZones = m_maxZone; - if (totalZones>maxZones/2) { - DEBUG_LOG(("Max zones %d", m_maxZone)); - } // Collapse the zones into a 1,2,3... sequence, removing collapsed zones. m_maxZone = 1; @@ -2594,27 +2639,19 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye if (zone == i) { collapsedZones[i] = m_maxZone; ++m_maxZone; - } else { + } else { collapsedZones[i] = collapsedZones[zone]; } } -#ifdef DEBUG_QPF -#if defined(DEBUG_LOGGING) - QueryPerformanceCounter((LARGE_INTEGER *)&endTime64); - timeToUpdate = ((double)(endTime64-startTime64) / (double)(freq64)); - DEBUG_LOG(("Time to calculate first %f", timeToUpdate)); -#endif -#endif + // Now map the zones in the map back into the collapsed zones. for( j=globalBounds.lo.y; j<=globalBounds.hi.y; j++ ) { for( i=globalBounds.lo.x; i<=globalBounds.hi.x; i++ ) { PathfindCell &cell = map[i][j]; cell.setZone(collapsedZones[cell.getZone()]); - if (map[i][j].getZone()==0) { - DEBUG_CRASH(("Zone not set cell %d, %d", i, j)); - } } } + for (i=0; i<=LAYER_LAST; i++) { PathfindLayer &r_thisLayer = layers[i]; @@ -2625,9 +2662,6 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye } r_thisLayer.setZone( zone ); - if (!layers[i].isUnused() && !layers[i].isDestroyed() && layers[i].getZone()==0) { - DEBUG_CRASH(("Zone not set Layer %d", i)); - } r_thisLayer.applyZone(); if (!r_thisLayer.isUnused() && !r_thisLayer.isDestroyed()) { @@ -2640,7 +2674,7 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye } allocateZones(); - DEBUG_ASSERTCRASH(xBlock==m_zoneBlockExtent.x && yBlock==m_zoneBlockExtent.y, ("Inconsistent allocation - SERIOUS ERROR. jba")); + for (xBlock=0; xBlock globalBounds.hi.x) { + + if (bounds.hi.x > globalBounds.hi.x) bounds.hi.x = globalBounds.hi.x; - } - if (bounds.hi.y > globalBounds.hi.y) { + + if (bounds.hi.y > globalBounds.hi.y) bounds.hi.y = globalBounds.hi.y; - } + +#if RTS_GENERALS if (bounds.lo.x>bounds.hi.x || bounds.lo.y>bounds.hi.y) { DEBUG_CRASH(("Incorrect bounds calculation. Logic error, fix me. jba.")); continue; } +#endif m_zoneBlocks[xBlock][yBlock].blockCalculateZones(map, layers, bounds); } } -#ifdef DEBUG_QPF -#if defined(DEBUG_LOGGING) - QueryPerformanceCounter((LARGE_INTEGER *)&endTime64); - timeToUpdate = ((double)(endTime64-startTime64) / (double)(freq64)); - DEBUG_LOG(("Time to calculate second %f", timeToUpdate)); -#endif -#endif // Determine water/ground equivalent zones, and ground/cliff equivalent zones. for (i=0; i globalBounds.lo.x && r_thisCell.getZone() != map[i-1][j].getZone() ) { const PathfindCell &r_leftCell = map[i-1][j]; + //if this is true, skip all the ones below if (r_thisCell.getType() == r_leftCell.getType()) { applyZone(r_thisCell, r_leftCell, m_hierarchicalZones, m_maxZone); } - if (waterGround(r_thisCell, r_leftCell)) { - applyZone(r_thisCell, r_leftCell, m_groundWaterZones, m_maxZone); - } - if (groundRubble(r_thisCell, r_leftCell)) { - Int zone1 = r_thisCell.getZone(); - Int zone2 = r_leftCell.getZone(); - if (m_terrainZones[zone1] != m_terrainZones[zone2]) { - //DEBUG_LOG(("Matching terrain zone %d to %d.", zone1, zone2)); + else + { + Bool notTerrainOrCrusher = TRUE; // if this is false, skip the if-else-ladder below + + if (terrain(r_thisCell, r_leftCell)) { + applyZone(r_thisCell, r_leftCell, m_terrainZones, m_maxZone); + notTerrainOrCrusher = FALSE; + } + + if (crusherGround(r_thisCell, r_leftCell)) { + applyZone(r_thisCell, r_leftCell, m_crusherZones, m_maxZone); + notTerrainOrCrusher = FALSE; + } + + if ( notTerrainOrCrusher ) { + if (waterGround(r_thisCell, r_leftCell)) + applyZone(r_thisCell, r_leftCell, m_groundWaterZones, m_maxZone); + else if (groundRubble(r_thisCell, r_leftCell)) + applyZone(r_thisCell, r_leftCell, m_groundRubbleZones, m_maxZone); + else if (groundCliff(r_thisCell, r_leftCell)) + applyZone(r_thisCell, r_leftCell, m_groundCliffZones, m_maxZone); } - applyZone(r_thisCell, r_leftCell, m_groundRubbleZones, m_maxZone); - } - if (groundCliff(r_thisCell, r_leftCell)) { - applyZone(r_thisCell, r_leftCell, m_groundCliffZones, m_maxZone); - } - if (terrain(r_thisCell, r_leftCell)) { - applyZone(r_thisCell, r_leftCell, m_terrainZones, m_maxZone); - } - if (crusherGround(r_thisCell, r_leftCell)) { - applyZone(r_thisCell, r_leftCell, m_crusherZones, m_maxZone); } + } if (j>globalBounds.lo.y && r_thisCell.getZone()!=map[i][j-1].getZone()) { @@ -2724,15 +2759,11 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye if (r_thisCell.getType() == r_topCell.getType()) { applyZone(r_thisCell, r_topCell, m_hierarchicalZones, m_maxZone); } +#if RTS_GENERALS if (waterGround(r_thisCell, r_topCell)) { applyZone(r_thisCell, r_topCell, m_groundWaterZones, m_maxZone); } if (groundRubble(r_thisCell, r_topCell)) { - Int zone1 = r_thisCell.getZone(); - Int zone2 = r_topCell.getZone(); - if (m_terrainZones[zone1] != m_terrainZones[zone2]) { - //DEBUG_LOG(("Matching terrain zone %d to %d.", zone1, zone2)); - } applyZone(r_thisCell, r_topCell, m_groundRubbleZones, m_maxZone); } if (groundCliff(r_thisCell, r_topCell)) { @@ -2744,26 +2775,209 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye if (crusherGround(r_thisCell, r_topCell)) { applyZone(r_thisCell, r_topCell, m_crusherZones, m_maxZone); } +#else + else + { + Bool notTerrainOrCrusher = TRUE; // if this is false, skip the if-else-ladder below + + if (terrain(r_thisCell, r_topCell)) { + applyZone(r_thisCell, r_topCell, m_terrainZones, m_maxZone); + notTerrainOrCrusher = FALSE; + } + + if (crusherGround(r_thisCell, r_topCell)) { + applyZone(r_thisCell, r_topCell, m_crusherZones, m_maxZone); + notTerrainOrCrusher = FALSE; + } + + if ( notTerrainOrCrusher ) { + if (waterGround(r_thisCell, r_topCell)) + applyZone(r_thisCell, r_topCell, m_groundWaterZones, m_maxZone); + else if (groundRubble(r_thisCell, r_topCell)) + applyZone(r_thisCell, r_topCell, m_groundRubbleZones, m_maxZone); + else if (groundCliff(r_thisCell, r_topCell)) + applyZone(r_thisCell, r_topCell, m_groundCliffZones, m_maxZone); + } + } +#endif // RTS_GENERALS } DEBUG_ASSERTCRASH(r_thisCell.getZone() != 0, ("Cleared the zone.")); } } - + +#if DEBUG_LOGGING if (m_maxZone >= m_zonesAllocated) { RELEASE_CRASH("Pathfind allocation error - fatal. see jba."); } +#endif + + //FLATTEN HIERARCHICAL ZONES for (i=1; im_debugAI == AI_DEBUG_ZONES) + { + extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); + RGBColor color; + memset(&color, 0, sizeof(Color)); + addIcon(nullptr, 0, 0, color); + for( j=0; jgetLayerHeight( pos.x, pos.y, map[i][j].getLayer() ) + 0.5f; + addIcon(&pos, PATHFIND_CELL_SIZE_F*0.8f, 500, color); + } + } + } +#endif + m_nextFrameToCalculateZones = 0xffffffff; +} + +/** + * Update zones where a structure has been added or removed. + * This can be done by just updating the equivalency arrays, without rezoning the map.. + */ +void PathfindZoneManager::updateZonesForModify(PathfindCell **map, PathfindLayer layers[], const IRegion2D &structureBounds, const IRegion2D &globalBounds ) +{ + +#ifdef DEBUG_QPF +#if defined(DEBUG_LOGGING) + __int64 startTime64; + double timeToUpdate=0.0f; + __int64 endTime64,freq64; + QueryPerformanceFrequency((LARGE_INTEGER *)&freq64); + QueryPerformanceCounter((LARGE_INTEGER *)&startTime64); +#endif +#endif + IRegion2D bounds = structureBounds; + bounds.hi.x++; + bounds.hi.y++; + if (bounds.hi.x > globalBounds.hi.x) { + bounds.hi.x = globalBounds.hi.x; + } + if (bounds.hi.y > globalBounds.hi.y) { + bounds.hi.y = globalBounds.hi.y; + } + + Int xBlock, yBlock; + for (xBlock = 0; xBlock bounds.hi.x) { + blockBounds.hi.x = bounds.hi.x; + } + if (blockBounds.hi.y > bounds.hi.y) { + blockBounds.hi.y = bounds.hi.y; + } + if (blockBounds.lo.x < bounds.lo.x) { + blockBounds.lo.x = bounds.lo.x; + } + if (blockBounds.lo.y < bounds.lo.y) { + blockBounds.lo.y = bounds.lo.y; + } + if (blockBounds.lo.x>blockBounds.hi.x || blockBounds.lo.y>blockBounds.hi.y) { + continue; + } + m_zoneBlocks[xBlock][yBlock].setInteractsWithBridge(false); + Int i, j; + for( j=blockBounds.lo.y; j<=blockBounds.hi.y; j++ ) { + for( i=blockBounds.lo.x; i<=blockBounds.hi.x; i++ ) { + PathfindCell *cell = &map[i][j]; + if (cell->getZone()!=UNINITIALIZED_ZONE) continue; + if (i>blockBounds.lo.x) { + if (map[i][j].getType() == map[i-1][j].getType()) { + cell->setZone(map[i-1][j].getZone()); + if (cell->getZone()!=UNINITIALIZED_ZONE) continue; + } + } + if (j>blockBounds.lo.y) { + if (cell->getType() == map[i][j-1].getType()) { + cell->setZone(map[i][j-1].getZone()); + if (cell->getZone()!=UNINITIALIZED_ZONE) continue; + } + if (isetZone(map[i+1][j-1].getZone()); + if (cell->getZone()!=UNINITIALIZED_ZONE) continue; + } + } + } + } + } + for( j=blockBounds.hi.y; j>=blockBounds.lo.y; j-- ) { + for( i=blockBounds.hi.x; i>=blockBounds.lo.x; i-- ) { + PathfindCell *cell = &map[i][j]; + if (cell->getZone()!=UNINITIALIZED_ZONE) continue; + if (isetZone(map[i+1][j].getZone()); + if (cell->getZone()!=UNINITIALIZED_ZONE) continue; + } + } + if (jgetType() == map[i][j+1].getType()) { + cell->setZone(map[i][j+1].getZone()); + if (cell->getZone()!=UNINITIALIZED_ZONE) continue; + } + if (isetZone(map[i+1][j+1].getZone()); + if (cell->getZone()!=UNINITIALIZED_ZONE) continue; + } + } + } + } + } + } + } #ifdef DEBUG_QPF #if defined(DEBUG_LOGGING) QueryPerformanceCounter((LARGE_INTEGER *)&endTime64); @@ -2772,17 +2986,18 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye #endif #endif #if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI && false) + if (TheGlobalData->m_debugAI==AI_DEBUG_ZONES) { extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); RGBColor color; memset(&color, 0, sizeof(Color)); addIcon(nullptr, 0, 0, color); + Int i, j; for( j=0; jgetLayerHeight( pos.x, pos.y, map[i][j].getLayer() ) + 0.5f; - addIcon(&pos, PATHFIND_CELL_SIZE_F*0.8f, 500, color); + addIcon(&pos, PATHFIND_CELL_SIZE_F*0.8f, 200, color); } } } #endif - m_needToCalculateZones = false; } // @@ -2940,9 +3154,9 @@ zoneStorageType PathfindZoneManager::getBlockZone(LocomotorSurfaceTypeMask accep return 0; } zoneStorageType zone = m_zoneBlocks[blockX][blockY].getEffectiveZone(acceptableSurfaces, crusher, cell->getZone()); - if (zone > m_maxZone) { + if (zone >= m_maxZone) { DEBUG_CRASH(("Invalid zone.")); - return 0; + return UNINITIALIZED_ZONE; } return zone; } @@ -3099,6 +3313,10 @@ void PathfindLayer::doDebugIcons() { if (m_layer == LAYER_WALL) { bridgeHeight = TheAI->pathfinder()->getWallHeight(); } + static Int flash = 0; + flash--; + if (flash<1) flash = 20; + if (flash < 10) return; Bool showCells = TheGlobalData->m_debugAI==AI_DEBUG_CELLS; // show the pathfind grid for( int j=0; jgetType() == PathfindCell::CELL_IMPASSABLE) { color.red = color.green = color.blue = 1; + size = 0.2f; + empty = false; + } else if (cell->getType() == PathfindCell::CELL_BRIDGE_IMPASSABLE) { + color.blue = color.red = 1; empty = false; } else if (cell->getType() == PathfindCell::CELL_CLIFF) { color.red = 1; empty = false; + } else { + size = 0.2f; } } if (showCells) { @@ -3150,7 +3374,7 @@ void PathfindLayer::doDebugIcons() { loc.x = topLeftCorner.x + PATHFIND_CELL_SIZE_F/2.0f; loc.y = topLeftCorner.y + PATHFIND_CELL_SIZE_F/2.0f; loc.z = bridgeHeight; - addIcon(&loc, PATHFIND_CELL_SIZE_F*0.8f, 99, color); + addIcon(&loc, PATHFIND_CELL_SIZE_F*size, 99, color); } } } @@ -3344,7 +3568,11 @@ void PathfindLayer::classifyCells() groundCell->setConnectLayer(LAYER_INVALID); // disconnect it. } } +#if RTS_GENERALS cell->setType(PathfindCell::CELL_IMPASSABLE); +#else + cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); +#endif } } } @@ -3509,7 +3737,11 @@ void PathfindLayer::classifyLayerMapCell( Int i, Int j , PathfindCell *cell, Bri cell->setType(PathfindCell::CELL_CLEAR); } else { if (bridgeCount!=0) { +#if RTS_GENERALS cell->setType(PathfindCell::CELL_CLIFF); // it's off the bridge. +#else + cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); // it's off the bridge. +#endif } // check against the end lines. @@ -3520,12 +3752,21 @@ void PathfindLayer::classifyLayerMapCell( Int i, Int j , PathfindCell *cell, Bri cellBounds.hi.x = bottomRightCorner.x; cellBounds.hi.y = bottomRightCorner.y; +#if RTS_GENERALS if (m_bridge->isCellOnEnd(&cellBounds)) { cell->setType(PathfindCell::CELL_CLEAR); } if (m_bridge->isCellOnSide(&cellBounds)) { cell->setType(PathfindCell::CELL_CLIFF); } else { +#else + if (m_bridge->isCellOnSide(&cellBounds)) { + cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); + } else { + if (m_bridge->isCellOnEnd(&cellBounds)) { + cell->setType(PathfindCell::CELL_CLEAR); + } +#endif if (m_bridge->isCellEntryPoint(&cellBounds)) { cell->setType(PathfindCell::CELL_CLEAR); cell->setConnectLayer(LAYER_GROUND); @@ -3545,7 +3786,11 @@ void PathfindLayer::classifyLayerMapCell( Int i, Int j , PathfindCell *cell, Bri if (groundHeight+LAYER_Z_CLOSE_ENOUGH_F > bridgeHeight) { PathfindCell *groundCell = TheAI->pathfinder()->getCell(LAYER_GROUND,i, j); if (!(groundCell->getType()==PathfindCell::CELL_OBSTACLE)) { +#if RTS_GENERALS groundCell->setType(PathfindCell::CELL_IMPASSABLE); +#else + groundCell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); +#endif } } } @@ -3622,7 +3867,11 @@ void PathfindLayer::classifyWallMapCell( Int i, Int j , PathfindCell *cell, Obje cell->setType(PathfindCell::CELL_CLEAR); } else { if (bridgeCount!=0) { +#if RTS_GENERALS cell->setType(PathfindCell::CELL_CLIFF); // it's off the bridge. +#else + cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); // it's off the bridge. +#endif } } @@ -3812,8 +4061,9 @@ void Pathfinder::updateLayer(Object *obj, PathfindLayerEnum layer) */ void Pathfinder::classifyFence( Object *obj, Bool insert ) { +#if RTS_GENERALS m_zoneManager.markZonesDirty(); - +#endif const Coord3D *pos = obj->getPosition(); Real angle = obj->getOrientation(); @@ -3836,6 +4086,25 @@ void Pathfinder::classifyFence( Object *obj, Bool insert ) Real tl_x = pos->x - fenceOffset*c - halfsizeY*s; Real tl_y = pos->y + halfsizeY*c - fenceOffset*s; +#if RTS_ZEROHOUR + IRegion2D cellBounds; + 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 + //CRCDEBUG_LOG(("Pathfinder::classifyFence - (%d,%d)", cellBounds.hi.x, cellBounds.hi.y)); + + // In retail, the values in the stack often look like this. We set them + // to reduce the likelihood of mismatch. + cellBounds.hi.x = 253961804; + cellBounds.hi.y = 4202797; +#else + cellBounds.hi.x = REAL_TO_INT_CEIL((pos->x + 0.5f)/PATHFIND_CELL_SIZE_F); + cellBounds.hi.y = REAL_TO_INT_CEIL((pos->y + 0.5f)/PATHFIND_CELL_SIZE_F); +#endif + Bool didAnything = false; +#endif // RTS_ZEROHOUR + for (Int iy = 0; iy < numStepsY; ++iy, tl_x += ydx, tl_y += ydy) { Real x = tl_x; @@ -3850,13 +4119,39 @@ void Pathfinder::classifyFence( Object *obj, Bool insert ) ICoord2D pos; pos.x = cx; pos.y = cy; +#if RTS_GENERALS m_map[cx][cy].setTypeAsObstacle( obj, true, pos ); +#else + if (m_map[cx][cy].setTypeAsObstacle( obj, true, pos )) { + didAnything = true; + m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); + } +#endif } +#if RTS_GENERALS else m_map[cx][cy].removeObstacle(obj); +#else + else { + if (m_map[cx][cy].removeObstacle(obj)) { + didAnything = true; + m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); + } + } + if (cellBounds.lo.x>cx) cellBounds.lo.x = cx; + if (cellBounds.lo.y>cy) cellBounds.lo.y = cy; + if (cellBounds.hi.xisKindOf( KINDOF_BLAST_CRATER ) ) // since these footprints are permanent, never remove them + return; +#endif + removeUnitFromPathfindMap(obj); if (obj->isKindOf(KINDOF_WALK_ON_TOP_OF_WALL)) { if (!m_layers[LAYER_WALL].isUnused()) { @@ -3934,20 +4234,32 @@ void Pathfinder::classifyObjectFootprint( Object *obj, Bool insert ) return; } +#if RTS_GENERALS if (obj->getHeightAboveTerrain() > PATHFIND_CELL_SIZE_F) { return; // Don't add bounds that are up in the air. +#else + if (obj->getHeightAboveTerrain() > PATHFIND_CELL_SIZE_F && ( ! obj->isKindOf( KINDOF_BLAST_CRATER ) ) ) { + return; // Don't add bounds that are up in the air.... unless a blast crater wants to do just that +#endif } internal_classifyObjectFootprint(obj, insert); } void Pathfinder::internal_classifyObjectFootprint( Object *obj, Bool insert ) { + const Coord3D *pos = obj->getPosition(); +#if RTS_ZEROHOUR + IRegion2D cellBounds; + 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); + cellBounds.hi = cellBounds.lo; +#endif switch(obj->getGeometryInfo().getGeomType()) { case GEOMETRY_BOX: { m_zoneManager.markZonesDirty(); - const Coord3D *pos = obj->getPosition(); + Real angle = obj->getOrientation(); Real halfsizeX = obj->getGeometryInfo().getMajorRadius(); @@ -3983,10 +4295,28 @@ void Pathfinder::internal_classifyObjectFootprint( Object *obj, Bool insert ) ICoord2D pos; pos.x = cx; pos.y = cy; +#if RTS_GENERALS m_map[cx][cy].setTypeAsObstacle( obj, false, pos ); +#else + if (m_map[cx][cy].setTypeAsObstacle( obj, false, pos )) { + m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); + } +#endif } +#if RTS_GENERALS else m_map[cx][cy].removeObstacle(obj); +#else + else { + if (m_map[cx][cy].removeObstacle(obj)) { + m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); + } + } + if (cellBounds.lo.x>cx) cellBounds.lo.x = cx; + if (cellBounds.lo.y>cy) cellBounds.lo.y = cy; + if (cellBounds.hi.xgetPosition(); Real radius = obj->getGeometryInfo().getMajorRadius(); Real r2, size; @@ -4028,14 +4357,32 @@ void Pathfinder::internal_classifyObjectFootprint( Object *obj, Bool insert ) { if (i >= 0 && j >= 0 && i < m_extent.hi.x && j < m_extent.hi.y) { - if (insert) { + if (insert) { ICoord2D pos; pos.x = i; pos.y = j; +#if RTS_GENERALS m_map[i][j].setTypeAsObstacle( obj, false, pos ); +#else + if (m_map[i][j].setTypeAsObstacle( obj, false, pos )) { + m_map[i][j].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); + } +#endif } +#if RTS_GENERALS else m_map[i][j].removeObstacle( obj ); +#else + else { + if (m_map[i][j].removeObstacle(obj)) { + m_map[i][j].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); + } + } + if (cellBounds.lo.x>i) cellBounds.lo.x = i; + if (cellBounds.lo.y>j) cellBounds.lo.y = j; + if (cellBounds.hi.xgetGeometryInfo().get2DBounds(*obj->getPosition(), obj->getOrientation(), bounds); IRegion2D cellBounds; cellBounds.lo.x = REAL_TO_INT_FLOOR(bounds.lo.x/PATHFIND_CELL_SIZE_F)-1; cellBounds.lo.y = REAL_TO_INT_FLOOR(bounds.lo.y/PATHFIND_CELL_SIZE_F)-1; cellBounds.hi.x = REAL_TO_INT_CEIL(bounds.hi.x/PATHFIND_CELL_SIZE_F)+1; cellBounds.hi.y = REAL_TO_INT_CEIL(bounds.hi.y/PATHFIND_CELL_SIZE_F)+1; +#else + m_zoneManager.updateZonesForModify(m_map, m_layers, cellBounds, m_extent); + + cellBounds.lo.x -= 2; + cellBounds.lo.y -= 2; + cellBounds.hi.x += 2; + cellBounds.hi.y += 2; +#endif if (cellBounds.lo.x < m_extent.lo.x) { cellBounds.lo.x = m_extent.lo.x; } @@ -4422,6 +4779,7 @@ Locomotor* Pathfinder::chooseBestLocomotorForPosition(PathfindLayerEnum layer, L return LOCOMOTORSURFACE_RUBBLE | LOCOMOTORSURFACE_AIR; case PathfindCell::CELL_OBSTACLE: + case PathfindCell::CELL_BRIDGE_IMPASSABLE: case PathfindCell::CELL_IMPASSABLE: return LOCOMOTORSURFACE_AIR; @@ -4562,6 +4920,12 @@ Bool Pathfinder::checkDestination(const Object *obj, Int cellX, Int cellY, Pathf return false; } +#if RTS_ZEROHOUR + if (IS_IMPASSABLE(cell->getType())) { + return false; + } +#endif + if (cell->getFlags() == PathfindCell::NO_UNITS) { continue; // Nobody is here, so it's ok. } @@ -4692,9 +5056,11 @@ Bool Pathfinder::checkForMovement(const Object *obj, TCheckMovementInfo &info) if (!unit->getAIUpdateInterface()) { return false; // can't path through not-idle units. } +#if RTS_GENERALS if (!unit->getAIUpdateInterface()->isIdle()) { return false; // can't path through not-idle units. } +#endif Bool found = false; Int k; for (k=0; kgetPosition(), dest); - adjustedPathExists = quickDoesPathExist( locomotorSet, obj->getPosition(), &adjustDest); + pathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), dest); + adjustedPathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &adjustDest); if (!pathExists) { - if (quickDoesPathExist( locomotorSet, dest, &adjustDest)) { + if (clientSafeQuickDoesPathExist( locomotorSet, dest, &adjustDest)) { adjustedPathExists = true; } } @@ -5133,7 +5499,11 @@ Bool Pathfinder::checkForPossible(Bool isCrusher, Int fromZone, Bool center, co { PathfindCell *goalCell = getCell(layer, cellX, cellY); if (!goalCell) return false; +#if RTS_GENERALS if (goalCell->getType() == PathfindCell::CELL_OBSTACLE) return false; +#else + if (IS_IMPASSABLE(goalCell->getType())) return false; +#endif Int zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, goalCell->getZone()); if (startingInObstacle) { zone2 = m_zoneManager.getEffectiveTerrainZone(zone2); @@ -5345,6 +5715,12 @@ void Pathfinder::doDebugIcons() { color.red = 1; empty = false; break; + + case PathfindCell::CELL_BRIDGE_IMPASSABLE: + color.blue = color.red = 1; + empty = false; + break; + case PathfindCell::CELL_IMPASSABLE: color.green = 1; empty = false; @@ -5502,7 +5878,12 @@ void Pathfinder::processPathfindQueue() #endif #endif - if (m_zoneManager.needToCalculateZones()) { + if ( +#ifdef forceRefreshCalling +#pragma message("AHHHH!, forced calls to pathzonerefresh still in code... notify M Lorenzen") + s_stopForceCalling==FALSE || +#endif + m_zoneManager.needToCalculateZones()) { m_zoneManager.calculateZones(m_map, m_layers, m_extent); return; } @@ -5520,7 +5901,9 @@ void Pathfinder::processPathfindQueue() m_logicalExtent = bounds; m_cumulativeCellsAllocated = 0; // Number of pathfind cells examined. +#ifdef DEBUG_QPF Int pathsFound = 0; +#endif while (m_cumulativeCellsAllocated < PATHFIND_CELLS_PER_FRAME && m_queuePRTail!=m_queuePRHead) { Object *obj = TheGameLogic->findObjectByID(m_queuedPathfindRequests[m_queuePRHead]); @@ -5529,7 +5912,9 @@ void Pathfinder::processPathfindQueue() AIUpdateInterface *ai = obj->getAIUpdateInterface(); if (ai) { ai->doPathfind(this); +#ifdef DEBUG_QPF pathsFound++; +#endif } } m_queuePRHead = m_queuePRHead+1; @@ -5868,14 +6253,18 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * newCell->setBlockedByAlly(false); if (info.allyFixedCount>0) { +#if RTS_GENERALS newCostSoFar += 3*COST_DIAGONAL*info.allyFixedCount; +#else + newCostSoFar += 3*COST_DIAGONAL; +#endif if (!canPathThroughUnits) newCell->setBlockedByAlly(true); } Int costRemaining = 0; if (goalCell) { - if (attackDistance == 0) { + if (attackDistance == NO_ATTACK) { costRemaining = newCell->costToGoal( goalCell ); } else { dx = newCellCoord.x - goalCell->getXIndex(); @@ -5939,7 +6328,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * Path *Pathfinder::findPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *rawTo) { - if (!quickDoesPathExist(locomotorSet, from, rawTo)) { + if (!clientSafeQuickDoesPathExist(locomotorSet, from, rawTo)) { return nullptr; } Bool isHuman = true; @@ -6384,6 +6773,27 @@ Path *Pathfinder::buildHierarchicalPath( const Coord3D *fromPos, PathfindCell *g prependCells(path, fromPos, goalCell, true); +#if RTS_ZEROHOUR + // Expand the hierarchical path around the starting point. jba [8/24/2003] + // This allows the unit to get around friendly units that may be near it. + Coord3D pos = *path->getFirstNode()->getPosition(); + Coord3D minPos = pos; + minPos.x -= PathfindZoneManager::ZONE_BLOCK_SIZE*PATHFIND_CELL_SIZE_F; + minPos.y -= PathfindZoneManager::ZONE_BLOCK_SIZE*PATHFIND_CELL_SIZE_F; + Coord3D maxPos = pos; + maxPos.x += PathfindZoneManager::ZONE_BLOCK_SIZE*PATHFIND_CELL_SIZE_F; + maxPos.y += PathfindZoneManager::ZONE_BLOCK_SIZE*PATHFIND_CELL_SIZE_F; + ICoord2D cellNdxMin, cellNdxMax; + worldToCell(&minPos, &cellNdxMin); + worldToCell(&maxPos, &cellNdxMax); + Int i, j; + for (i=cellNdxMin.x; i<=cellNdxMax.x; i++) { + for (j=cellNdxMin.y; j<=cellNdxMax.y; j++) { + m_zoneManager.setPassable(i, j, true); + } + } +#endif + #if defined(RTS_DEBUG) if (TheGlobalData->m_debugAI==AI_DEBUG_PATHS) { @@ -6440,6 +6850,14 @@ struct MADStruct return 0; // Only move allies. } if (otherObj && otherObj->getAI() && !otherObj->getAI()->isMoving()) { +#if RTS_ZEROHOUR + //Kris: Patch 1.01 November 3, 2003 + //Black Lotus exploit fix -- moving while hacking. + if( otherObj->testStatus( OBJECT_STATUS_IS_USING_ABILITY ) || otherObj->getAI()->isBusy() ) + { + return 0; // Packing or unpacking objects for example + } +#endif //DEBUG_LOG(("Moving ally")); otherObj->getAI()->aiMoveAwayFromUnit(d->obj, CMD_FROM_AI); } @@ -6916,11 +7334,27 @@ void Pathfinder::processHierarchicalCell( const ICoord2D &scanCell, const ICoord scanCell.ym_extent.hi.y) { return; } +#if RTS_ZEROHOUR + if (parentZone == PathfindZoneManager::UNINITIALIZED_ZONE) { + return; + } +#endif if (parentZone == m_zoneManager.getBlockZone(LOCOMOTORSURFACE_GROUND, crusher, scanCell.x, scanCell.y, m_map)) { PathfindCell *newCell = getCell(LAYER_GROUND, scanCell.x, scanCell.y); +#if RTS_GENERALS if (newCell->hasInfo() && (newCell->getOpen() || newCell->getClosed())) return; // already looked at this one. - ICoord2D adjacentCell = scanCell; +#else + if( !newCell->hasInfo() ) + { + return; + } + + if( newCell->getOpen() || newCell->getClosed() ) + return; // already looked at this one. +#endif + + ICoord2D adjacentCell = scanCell; //DEBUG_ASSERTCRASH(parentZone==newCell->getZone(), ("Different zones?")); if (parentZone!=newCell->getZone()) return; adjacentCell.x += delta.x; @@ -6969,21 +7403,23 @@ void Pathfinder::processHierarchicalCell( const ICoord2D &scanCell, const ICoord } adjNewCell->allocateInfo(adjacentCell); - cellCount++; - Int curCost = adjNewCell->costToHierGoal(parentCell); - Int remCost = adjNewCell->costToHierGoal(goalCell); - if (adjNewCell->getPinched() || newCell->getPinched()) { - curCost += 2*COST_ORTHOGONAL; - } else { - examinedZones[numExZones] = newZone; - numExZones++; - } + if( adjNewCell->hasInfo() ) { + cellCount++; + Int curCost = adjNewCell->costToHierGoal(parentCell); + Int remCost = adjNewCell->costToHierGoal(goalCell); + if (adjNewCell->getPinched() || newCell->getPinched()) { + curCost += 2*COST_ORTHOGONAL; + } else { + examinedZones[numExZones] = newZone; + numExZones++; + } - adjNewCell->setCostSoFar(parentCell->getCostSoFar() + curCost); - adjNewCell->setTotalCost(adjNewCell->getCostSoFar()+remCost); - adjNewCell->setParentCellHierarchical(parentCell); - // insert newCell in open list such that open list is sorted, smallest total path cost first - adjNewCell->putOnSortedOpenList( m_openList ); + adjNewCell->setCostSoFar(parentCell->getCostSoFar() + curCost); + adjNewCell->setTotalCost(adjNewCell->getCostSoFar()+remCost); + adjNewCell->setParentCellHierarchical(parentCell); + // insert newCell in open list such that open list is sorted, smallest total path cost first + adjNewCell->putOnSortedOpenList( m_openList ); + } } } @@ -7638,12 +8074,17 @@ Bool Pathfinder::findBrokenBridge(const LocomotorSet& locoSet, * False means it is impossible to path. * True means it is possible given the terrain, but there may be units in the way. */ -Bool Pathfinder::quickDoesPathExist( const LocomotorSet& locomotorSet, +Bool Pathfinder::clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ) { // See if terrain or building is blocking the destination. PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); +#if RTS_ZEROHOUR + if (!validMovementPosition(false, destinationLayer, locomotorSet, to)) { + return false; + } +#endif PathfindLayerEnum fromLayer = TheTerrainLogic->getLayerForDestination(from); Int zone1, zone2; @@ -7657,6 +8098,13 @@ Bool Pathfinder::quickDoesPathExist( const LocomotorSet& locomotorSet, if (parentCell->getType() == PathfindCell::CELL_OBSTACLE) { doingTerrainZone = true; +#if RTS_ZEROHOUR + if (zone1 == PathfindZoneManager::UNINITIALIZED_ZONE) { + // We are in a building that just got placed, and zones haven't been updated yet. [8/8/2003] + // It is better to return a false positive than a false negative. jba. + return true; + } +#endif } zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, goalCell->getZone()); if (goalCell->getType() == PathfindCell::CELL_OBSTACLE) { @@ -7672,9 +8120,68 @@ Bool Pathfinder::quickDoesPathExist( const LocomotorSet& locomotorSet, zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, zone2); zone2 = m_zoneManager.getEffectiveTerrainZone(zone2); } +#if RTS_GENERALS if (!validMovementPosition(false, destinationLayer, locomotorSet, to)) { return false; } +#endif + // If the terrain is connected using this locomotor set, we can path somehow. + if (zone1 == zone2) { + // There is not terrain blocking the from & to. + return true; + } + return FALSE; // no path exists + +} + +/** + * Does any path exist from 'from' to 'to' given the locomotor set + * This is the quick check, only looks at whether the terrain is possible or + * impossible to path over. Doesn't take other units into account. + * False means it is impossible to path. + * True means it is possible given the terrain, but there may be units in the way. + */ +Bool Pathfinder::clientSafeQuickDoesPathExistForUI( const LocomotorSet& locomotorSet, + const Coord3D *from, + const Coord3D *to ) +{ + // See if terrain or building is blocking the destination. + PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); + PathfindLayerEnum fromLayer = TheTerrainLogic->getLayerForDestination(from); + Int zone1, zone2; + + PathfindCell *parentCell = getClippedCell(fromLayer, from); + PathfindCell *goalCell = getClippedCell(destinationLayer, to); + if (goalCell->getType()==PathfindCell::CELL_CLIFF) { + return false; // No goals on cliffs. + } + + zone1 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, parentCell->getZone()); + zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, goalCell->getZone()); + + if (zone1 == PathfindZoneManager::UNINITIALIZED_ZONE || + zone2 == PathfindZoneManager::UNINITIALIZED_ZONE) { + // We are in a building that just got placed, and zones haven't been updated yet. [8/8/2003] + // It is better to return a false positive than a false negative. jba. + return true; + } + /* Do the effective terrain zone. This feedback is for the ui, so we won't take structures into account, + because if they are visible it will be obvious, and if they are stealthed they should be invisible to the + pathing as well. jba. */ + zone1 = parentCell->getZone(); + zone1 = m_zoneManager.getEffectiveTerrainZone(zone1); + zone1 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, zone1); + zone1 = m_zoneManager.getEffectiveTerrainZone(zone1); + zone2 = goalCell->getZone(); + zone2 = m_zoneManager.getEffectiveTerrainZone(zone2); + zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, zone2); + zone2 = m_zoneManager.getEffectiveTerrainZone(zone2); + + if (zone1 == PathfindZoneManager::UNINITIALIZED_ZONE) { + // We are in a building that just got placed, and zones haven't been updated yet. [8/8/2003] + // It is better to return a false positive than a false negative. jba. + return true; + } // If the terrain is connected using this locomotor set, we can path somehow. if (zone1 == zone2) { // There is not terrain blocking the from & to. @@ -8102,7 +8609,11 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet PathfindCell *ignoreCell = getClippedCell(goalObj->getLayer(), goalObj->getPosition()); if ( (goalCell->getObstacleID()==ignoreCell->getObstacleID()) && (goalCell->getObstacleID() != INVALID_ID) ) { Object* newObstacle = TheGameLogic->findObjectByID(goalCell->getObstacleID()); +#if RTS_GENERALS if (newObstacle != nullptr && newObstacle->isKindOf(KINDOF_AIRFIELD)) +#else + if (newObstacle != nullptr && newObstacle->isKindOf(KINDOF_FS_AIRFIELD)) +#endif { m_ignoreObstacleID = goalCell->getObstacleID(); goalOnObstacle = true; @@ -8195,6 +8706,7 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet // Continue search until "open" list is empty, or // until goal is found. // + Bool foundGoal = false; while( !m_openList.empty() ) { Real dx; @@ -8210,7 +8722,13 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet if (!goalOnObstacle) { // See if the goal is a valid destination. If not, accept closest cell. if (closesetCell!=nullptr && !canPathThroughUnits && !checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), parentCell->getLayer(), radius, centerInCell)) { +#if RTS_GENERALS break; +#else + foundGoal = true; + // Continue processing the open list to find a possibly closer cell. jba. [8/25/2003] + continue; +#endif } } @@ -8291,12 +8809,12 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet continue; } } - - // Check to see if we can change layers in this cell. - checkChangeLayers(parentCell); - - - count += examineNeighboringCells(parentCell, goalCell, locomotorSet, isHuman, centerInCell, radius, startCellNdx, obj, NO_ATTACK); + // If we haven't already found the goal cell, continue examining. [8/25/2003] + if (!foundGoal) { + // Check to see if we can change layers in this cell. + checkChangeLayers(parentCell); + count += examineNeighboringCells(parentCell, goalCell, locomotorSet, isHuman, centerInCell, radius, startCellNdx, obj, NO_ATTACK); + } } if (closesetCell) { @@ -9176,8 +9694,17 @@ void Pathfinder::updateGoal( Object *obj, const Coord3D *newGoalPos, PathfindLay AIUpdateInterface *ai = obj->getAIUpdateInterface(); if (!ai) return; // only consider ai objects. if (!ai->isDoingGroundMovement()) { - updateAircraftGoal(obj, newGoalPos); - return; +#if RTS_GENERALS + Bool isUnmannedHelicopter = false; +#else + // exception:sniped choppers are on ground + Bool isUnmannedHelicopter = ( obj->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) && obj->isDisabledByType( DISABLED_UNMANNED ) ) ; +#endif + if ( ! isUnmannedHelicopter ) + { + updateAircraftGoal(obj, newGoalPos); + return; + } } PathfindLayerEnum originalLayer = obj->getDestinationLayer(); @@ -9617,6 +10144,18 @@ if (g_UT_startTiming) return false; if (!otherObj->getAI() || otherObj->getAI()->isMoving()) { continue; } + +#if RTS_ZEROHOUR + if (otherObj->getAI()->isAttacking()) { + continue; // Don't move units that are attacking. [8/14/2003] + } + + //Kris: Patch 1.01 November 3, 2003 + //Black Lotus exploit fix -- moving while hacking. + if( otherObj->testStatus( OBJECT_STATUS_IS_USING_ABILITY ) || otherObj->getAI()->isBusy() ) { + continue; // Packing or unpacking objects for example + } +#endif //DEBUG_LOG(("Moving ally")); otherObj->getAI()->aiMoveAwayFromUnit(obj, CMD_FROM_AI); @@ -10059,6 +10598,10 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot if (!m_isMapReady) return nullptr; // Should always be ok. +#ifdef DEBUG_LOGGING + Int startTimeMS = ::GetTickCount(); +#endif + Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; Int radius; Bool centerInCell; @@ -10257,10 +10800,72 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot #endif if (show) debugShowSearch(true); - #if defined(RTS_DEBUG) - //DEBUG_LOG(("Attack path took %d cells, %f sec", cellCount, (::GetTickCount()-startTimeMS)/1000.0f)); + #ifdef DEBUG_LOGGING + DEBUG_LOG(("Attack path took %d cells, %f sec", cellCount, (::GetTickCount()-startTimeMS)/1000.0f)); #endif + + #if RTS_ZEROHOUR + // put parent cell onto closed list - its evaluation is finished + parentCell->putOnClosedList( m_closedList ); // construct and return path + if (obj->isKindOf(KINDOF_VEHICLE)) { + // Strip backwards. + PathfindCell *lastBlocked = nullptr; + PathfindCell *cur = parentCell; + Bool useLargeRadius = false; + Int cellLimit = 12; // Magic number, yes I know - jba. It is about 4 * size of an average vehicle width (3 cells) [8/15/2003] + while (cur) { + cellLimit--; + if (cellLimit<0) { + break; + } + TCheckMovementInfo info; + info.cell.x = cur->getXIndex(); + info.cell.y = cur->getYIndex(); + info.layer = cur->getLayer(); + if (useLargeRadius) { + info.centerInCell = centerInCell; + info.radius = radius; + } else { + info.centerInCell = true; + info.radius = 0; + } + info.considerTransient = false; + info.acceptableSurfaces = locomotorSet.getValidSurfaces(); + PathfindCell *cell = getCell(info.layer,info.cell.x,info.cell.y); + Bool unitIdle = false; + if (cell) { + ObjectID posUnit = cell->getPosUnit(); + Object *unit = TheGameLogic->findObjectByID(posUnit); + if (unit && unit->getAI() && unit->getAI()->isIdle()) { + unitIdle = true; + } + } + Bool checkMovement = checkForMovement(obj, info); + Bool blockedByEnemy = info.enemyFixed; + Bool blockedByAllies = info.allyFixedCount || info.allyGoal; + if (unitIdle) { + // If the unit present is idle, it doesn't block allies. [8/18/2003] + blockedByAllies = false; + } + + + if (!checkMovement || blockedByEnemy || blockedByAllies) { + lastBlocked = cur; + useLargeRadius = true; + } else { + useLargeRadius = false; + } + cur = cur->getParentCell(); + } + if (lastBlocked) { + parentCell = lastBlocked; + if (lastBlocked->getParentCell()) { + parentCell = lastBlocked->getParentCell(); + } + } + } +#endif // RTS_ZEROHOUR Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) { diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index ace3419bde..b96483a33b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -605,7 +605,7 @@ Object *AIPlayer::buildStructureWithDozer(const ThingTemplate *bldgPlan, BuildLi } TheTerrainVisual->removeAllBibs(); // isLocationLegalToBuild adds bib feedback, turn it off. jba. - if (!TheAI->pathfinder()->quickDoesPathExist(dozer->getAI()->getLocomotorSet(), + if (!TheAI->pathfinder()->clientSafeQuickDoesPathExist(dozer->getAI()->getLocomotorSet(), dozer->getPosition(), &pos)) { AsciiString bldgName = bldgPlan->getName(); bldgName.concat(" - Dozer unable to reach building. Teleporting."); diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp index 5230b44554..afb876af06 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp @@ -711,7 +711,7 @@ Bool AISkirmishPlayer::checkBridges(Object *unit, Waypoint *way) const LocomotorSet& locoSet = ai->getLocomotorSet(); Waypoint *curWay; for (curWay = way; curWay; curWay = curWay->getNext()) { - if (TheAI->pathfinder()->quickDoesPathExist(locoSet, &unitPos, curWay->getLocation())) { + if (TheAI->pathfinder()->clientSafeQuickDoesPathExist(locoSet, &unitPos, curWay->getLocation())) { continue; } ObjectID brokenBridge = INVALID_ID; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 852009b0de..2bb8629276 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -3924,7 +3924,7 @@ Bool PartitionManager::tryPosition( const Coord3D *center, const AIUpdateInterface *ai = options->sourceToPathToDest->getAIUpdateInterface(); // check for path existence - if( ai && TheAI->pathfinder()->quickDoesPathExist( ai->getLocomotorSet(), + if( ai && TheAI->pathfinder()->clientSafeQuickDoesPathExist( ai->getLocomotorSet(), options->sourceToPathToDest->getPosition(), &pos ) == FALSE ) return FALSE; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index b69a8520a3..2ccd7b9fde 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -2043,7 +2043,7 @@ Bool AIUpdateInterface::isPathAvailable( const Coord3D *destination ) const const Coord3D *myPos = getObject()->getPosition(); - return TheAI->pathfinder()->quickDoesPathExist( m_locomotorSet, myPos, destination ); + return TheAI->pathfinder()->clientSafeQuickDoesPathExist( m_locomotorSet, myPos, destination ); } @@ -2060,7 +2060,7 @@ Bool AIUpdateInterface::isQuickPathAvailable( const Coord3D *destination ) const const Coord3D *myPos = getObject()->getPosition(); - return TheAI->pathfinder()->quickDoesPathExist( m_locomotorSet, myPos, destination ); + return TheAI->pathfinder()->clientSafeQuickDoesPathExist( m_locomotorSet, myPos, destination ); } diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 934f607b6c..8c54344396 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -149,7 +149,7 @@ static void doSetRallyPoint( Object *obj, const Coord3D& pos ) NameKeyType key = NAMEKEY( "BasicHumanLocomotor" ); LocomotorSet locomotorSet; locomotorSet.addLocomotor( TheLocomotorStore->findLocomotorTemplate( key ) ); - if( TheAI->pathfinder()->quickDoesPathExist( locomotorSet, obj->getPosition(), &pos ) == FALSE ) + if( TheAI->pathfinder()->clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &pos ) == FALSE ) { // user feedback diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h index f0032e944e..0e2f7df454 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -278,13 +278,13 @@ class PathfindCell enum CellType { - CELL_CLEAR = 0x00, ///< clear, unobstructed ground - CELL_WATER = 0x01, ///< water area - CELL_CLIFF = 0x02, ///< steep altitude change - CELL_RUBBLE = 0x03, ///< Cell is occupied by rubble. - CELL_OBSTACLE = 0x04, ///< Occupied by a structure + CELL_CLEAR = 0x00, ///< clear, unobstructed ground + CELL_WATER = 0x01, ///< water area + CELL_CLIFF = 0x02, ///< steep altitude change + CELL_RUBBLE = 0x03, ///< Cell is occupied by rubble. + CELL_OBSTACLE = 0x04, ///< Occupied by a structure CELL_BRIDGE_IMPASSABLE = 0x05, ///< Piece of a bridge that is impassable. - CELL_IMPASSABLE = 0x06 ///< Just plain impassable except for aircraft. + CELL_IMPASSABLE = 0x06 ///< Just plain impassable except for aircraft. }; enum CellFlags @@ -483,7 +483,6 @@ struct TCheckMovementInfo; class ZoneBlock { public: - ZoneBlock(); ~ZoneBlock(); // not virtual, please don't override without making virtual. jba. @@ -508,7 +507,6 @@ class ZoneBlock UnsignedShort m_numZones; // Number of zones in this block. If == 1, there is only one zone, and // no zone equivalency arrays will be allocated. - UnsignedShort m_zonesAllocated; zoneStorageType *m_groundCliffZones; zoneStorageType *m_groundWaterZones; @@ -540,14 +538,12 @@ class PathfindZoneManager void reset(); Bool needToCalculateZones() const {return m_nextFrameToCalculateZones <= TheGameLogic->getFrame() ;} ///< Returns true if the zones need to be recalculated. - void markZonesDirty( Bool insert ) ; ///< Called when the zones need to be recalculated. + void markZonesDirty(); ///< Called when the zones need to be recalculated. void updateZonesForModify( PathfindCell **map, PathfindLayer layers[], const IRegion2D &structureBounds, const IRegion2D &globalBounds ) ; ///< Called to recalculate an area when a structure has been removed. void calculateZones( PathfindCell **map, PathfindLayer layers[], const IRegion2D &bounds); ///< Does zone calculations. zoneStorageType getEffectiveZone(LocomotorSurfaceTypeMask acceptableSurfaces, Bool crusher, zoneStorageType zone) const; zoneStorageType getEffectiveTerrainZone(zoneStorageType zone) const; - zoneStorageType getNextZone(); - void getExtent(ICoord2D &extent) const {extent = m_zoneBlockExtent;} /// return zone relative the the block zone that this cell resides in. @@ -575,7 +571,7 @@ class PathfindZoneManager ICoord2D m_zoneBlockExtent; ///< Zone block extents. Not the same scale as the pathfind extents. UnsignedShort m_maxZone; ///< Max zone used. - UnsignedInt m_nextFrameToCalculateZones; ///< WHen should I recalculate, next?. + UnsignedInt m_nextFrameToCalculateZones; ///< When should I recalculate, next?. UnsignedShort m_zonesAllocated; zoneStorageType *m_groundCliffZones; zoneStorageType *m_groundWaterZones; @@ -656,7 +652,7 @@ class Pathfinder : PathfindServicesInterface, public Snapshot void loadPostProcess(); Bool clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain & buildings check - fast) - Bool clientSafeQuickDoesPathExistForUI( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain onlyk - fast) + Bool clientSafeQuickDoesPathExistForUI( const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to ); ///< Can we build any path at all between the locations (terrain only - fast) Bool slowDoesPathExist( Object *obj, const Coord3D *from, const Coord3D *to, ObjectID ignoreObject=INVALID_ID ); ///< Can we build any path at all between the locations (terrain, buildings & units check - slower) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 954cf11f49..ec80292aad 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -1963,6 +1963,7 @@ UnsignedInt PathfindCell::costToHierGoal( PathfindCell *goal ) DEBUG_CRASH( ("Has to have info.") ); return 100000; //...patch hack 1.01 } + Int dx = m_info->m_pos.x - goal->getXIndex(); Int dy = m_info->m_pos.y - goal->getYIndex(); Int cost = REAL_TO_INT_FLOOR(COST_ORTHOGONAL*sqrt(dx*dx + dy*dy) + 0.5f); @@ -2324,9 +2325,12 @@ void ZoneBlock::blockCalculateZones(PathfindCell **map, PathfindLayer layers[], zoneStorageType ZoneBlock::getEffectiveZone( LocomotorSurfaceTypeMask acceptableSurfaces, Bool crusher, zoneStorageType zone) const { + DEBUG_ASSERTCRASH(zone, ("Zone not set")); +#if RTS_ZEROHOUR if (zone==PathfindZoneManager::UNINITIALIZED_ZONE) { return zone; } +#endif if (acceptableSurfaces&LOCOMOTORSURFACE_AIR) return 1; // air is all zone 1. @@ -2515,15 +2519,17 @@ void PathfindZoneManager::reset() ///< Called when the map is reset. freeBlocks(); } - -void PathfindZoneManager::markZonesDirty( Bool insert ) ///< Called when the zones need to be recalculated. +void PathfindZoneManager::markZonesDirty() ///< Called when the zones need to be recalculated. { - +#if RTS_GENERALS + m_nextFrameToCalculateZones = TheGameLogic->getFrame(); +#else if (TheGameLogic->getFrame()<2) { m_nextFrameToCalculateZones = 2; return; } - m_nextFrameToCalculateZones = MIN( m_nextFrameToCalculateZones, TheGameLogic->getFrame() + ZONE_UPDATE_FREQUENCY ); + m_nextFrameToCalculateZones = MIN( m_nextFrameToCalculateZones, TheGameLogic->getFrame() + ZONE_UPDATE_FREQUENCY ); +#endif } /** @@ -2541,13 +2547,12 @@ static Bool s_stopForceCalling = FALSE; void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer layers[], const IRegion2D &globalBounds ) { - #ifdef DEBUG_QPF #if defined(DEBUG_LOGGING) __int64 startTime64; static double timeToUpdate = 0.0f; - static double averageTimeToUpdate = 0.0f; - static Int updateSamples = 0; + static double averageTimeToUpdate = 0.0f; + static Int updateSamples = 0; __int64 endTime64,freq64; QueryPerformanceFrequency((LARGE_INTEGER *)&freq64); QueryPerformanceCounter((LARGE_INTEGER *)&startTime64); @@ -2582,6 +2587,12 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye if (bounds.hi.y > globalBounds.hi.y) { bounds.hi.y = globalBounds.hi.y; } +#if RTS_GENERALS + if (bounds.lo.x>bounds.hi.x || bounds.lo.y>bounds.hi.y) { + DEBUG_CRASH(("Incorrect bounds calculation. Logic error, fix me. jba.")); + continue; + } +#endif m_zoneBlocks[xBlock][yBlock].setInteractsWithBridge(false); for( j=bounds.lo.y; j<=bounds.hi.y; j++ ) { for( i=bounds.lo.x; i<=bounds.hi.x; i++ ) { @@ -2601,6 +2612,12 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye if (cell->getZone()==0) { cell->setZone(m_maxZone); m_maxZone++; +#if RTS_GENERALS + if (m_maxZone>= maxZones) { + DEBUG_CRASH(("Ran out of pathfind zones. SERIOUS ERROR! jba.")); + break; + } +#endif } if (cell->getConnectLayer() > LAYER_GROUND) { m_zoneBlocks[xBlock][yBlock].setInteractsWithBridge(true); @@ -2617,15 +2634,14 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye m_maxZone = 1; Int collapsedZones[maxZones]; collapsedZones[0] = 0; - for (i=1; i globalBounds.hi.y) bounds.hi.y = globalBounds.hi.y; +#if RTS_GENERALS + if (bounds.lo.x>bounds.hi.x || bounds.lo.y>bounds.hi.y) { + DEBUG_CRASH(("Incorrect bounds calculation. Logic error, fix me. jba.")); + continue; + } +#endif m_zoneBlocks[xBlock][yBlock].blockCalculateZones(map, layers, bounds); } } + // Determine water/ground equivalent zones, and ground/cliff equivalent zones. for (i=0; i globalBounds.lo.x && r_thisCell.getZone() != map[i-1][j].getZone() ) { const PathfindCell &r_leftCell = map[i-1][j]; - if (r_thisCell.getType() == r_leftCell.getType()) - applyZone(r_thisCell, r_leftCell, m_hierarchicalZones, m_maxZone);//if this is true, skip all the ones below - else { + //if this is true, skip all the ones below + if (r_thisCell.getType() == r_leftCell.getType()) { + applyZone(r_thisCell, r_leftCell, m_hierarchicalZones, m_maxZone); + } + else + { Bool notTerrainOrCrusher = TRUE; // if this is false, skip the if-else-ladder below - + if (terrain(r_thisCell, r_leftCell)) { applyZone(r_thisCell, r_leftCell, m_terrainZones, m_maxZone); notTerrainOrCrusher = FALSE; } - + if (crusherGround(r_thisCell, r_leftCell)) { applyZone(r_thisCell, r_leftCell, m_crusherZones, m_maxZone); notTerrainOrCrusher = FALSE; } - + if ( notTerrainOrCrusher ) { if (waterGround(r_thisCell, r_leftCell)) applyZone(r_thisCell, r_leftCell, m_groundWaterZones, m_maxZone); @@ -2723,30 +2749,48 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye else if (groundCliff(r_thisCell, r_leftCell)) applyZone(r_thisCell, r_leftCell, m_groundCliffZones, m_maxZone); } - } - + } if (j>globalBounds.lo.y && r_thisCell.getZone()!=map[i][j-1].getZone()) { const PathfindCell &r_topCell = map[i][j-1]; - if (r_thisCell.getType() == r_topCell.getType()) + if (r_thisCell.getType() == r_topCell.getType()) { applyZone(r_thisCell, r_topCell, m_hierarchicalZones, m_maxZone); - else { + } +#if RTS_GENERALS + if (waterGround(r_thisCell, r_topCell)) { + applyZone(r_thisCell, r_topCell, m_groundWaterZones, m_maxZone); + } + if (groundRubble(r_thisCell, r_topCell)) { + applyZone(r_thisCell, r_topCell, m_groundRubbleZones, m_maxZone); + } + if (groundCliff(r_thisCell, r_topCell)) { + applyZone(r_thisCell, r_topCell, m_groundCliffZones, m_maxZone); + } + if (terrain(r_thisCell, r_topCell)) { + applyZone(r_thisCell, r_topCell, m_terrainZones, m_maxZone); + } + if (crusherGround(r_thisCell, r_topCell)) { + applyZone(r_thisCell, r_topCell, m_crusherZones, m_maxZone); + } +#else + else + { Bool notTerrainOrCrusher = TRUE; // if this is false, skip the if-else-ladder below - + if (terrain(r_thisCell, r_topCell)) { applyZone(r_thisCell, r_topCell, m_terrainZones, m_maxZone); notTerrainOrCrusher = FALSE; } - + if (crusherGround(r_thisCell, r_topCell)) { applyZone(r_thisCell, r_topCell, m_crusherZones, m_maxZone); notTerrainOrCrusher = FALSE; } - - if (notTerrainOrCrusher) { + + if ( notTerrainOrCrusher ) { if (waterGround(r_thisCell, r_topCell)) applyZone(r_thisCell, r_topCell, m_groundWaterZones, m_maxZone); else if (groundRubble(r_thisCell, r_topCell)) @@ -2754,13 +2798,18 @@ void PathfindZoneManager::calculateZones( PathfindCell **map, PathfindLayer laye else if (groundCliff(r_thisCell, r_topCell)) applyZone(r_thisCell, r_topCell, m_groundCliffZones, m_maxZone); } - } - +#endif // RTS_GENERALS } - + DEBUG_ASSERTCRASH(r_thisCell.getZone() != 0, ("Cleared the zone.")); } } + +#if DEBUG_LOGGING + if (m_maxZone >= m_zonesAllocated) { + RELEASE_CRASH("Pathfind allocation error - fatal. see jba."); + } +#endif //FLATTEN HIERARCHICAL ZONES for (i=1; isetConnectLayer(LAYER_INVALID); // disconnect it. } } +#if RTS_GENERALS + cell->setType(PathfindCell::CELL_IMPASSABLE); +#else cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); +#endif } } } @@ -3684,7 +3737,11 @@ void PathfindLayer::classifyLayerMapCell( Int i, Int j , PathfindCell *cell, Bri cell->setType(PathfindCell::CELL_CLEAR); } else { if (bridgeCount!=0) { +#if RTS_GENERALS + cell->setType(PathfindCell::CELL_CLIFF); // it's off the bridge. +#else cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); // it's off the bridge. +#endif } // check against the end lines. @@ -3695,12 +3752,21 @@ void PathfindLayer::classifyLayerMapCell( Int i, Int j , PathfindCell *cell, Bri cellBounds.hi.x = bottomRightCorner.x; cellBounds.hi.y = bottomRightCorner.y; +#if RTS_GENERALS + if (m_bridge->isCellOnEnd(&cellBounds)) { + cell->setType(PathfindCell::CELL_CLEAR); + } + if (m_bridge->isCellOnSide(&cellBounds)) { + cell->setType(PathfindCell::CELL_CLIFF); + } else { +#else if (m_bridge->isCellOnSide(&cellBounds)) { cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); } else { if (m_bridge->isCellOnEnd(&cellBounds)) { cell->setType(PathfindCell::CELL_CLEAR); } +#endif if (m_bridge->isCellEntryPoint(&cellBounds)) { cell->setType(PathfindCell::CELL_CLEAR); cell->setConnectLayer(LAYER_GROUND); @@ -3720,7 +3786,11 @@ void PathfindLayer::classifyLayerMapCell( Int i, Int j , PathfindCell *cell, Bri if (groundHeight+LAYER_Z_CLOSE_ENOUGH_F > bridgeHeight) { PathfindCell *groundCell = TheAI->pathfinder()->getCell(LAYER_GROUND,i, j); if (!(groundCell->getType()==PathfindCell::CELL_OBSTACLE)) { +#if RTS_GENERALS + groundCell->setType(PathfindCell::CELL_IMPASSABLE); +#else groundCell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); +#endif } } } @@ -3797,7 +3867,11 @@ void PathfindLayer::classifyWallMapCell( Int i, Int j , PathfindCell *cell, Obje cell->setType(PathfindCell::CELL_CLEAR); } else { if (bridgeCount!=0) { +#if RTS_GENERALS + cell->setType(PathfindCell::CELL_CLIFF); // it's off the bridge. +#else cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); // it's off the bridge. +#endif } } @@ -3987,6 +4061,9 @@ void Pathfinder::updateLayer(Object *obj, PathfindLayerEnum layer) */ void Pathfinder::classifyFence( Object *obj, Bool insert ) { +#if RTS_GENERALS + m_zoneManager.markZonesDirty(); +#endif const Coord3D *pos = obj->getPosition(); Real angle = obj->getOrientation(); @@ -4009,6 +4086,7 @@ void Pathfinder::classifyFence( Object *obj, Bool insert ) Real tl_x = pos->x - fenceOffset*c - halfsizeY*s; Real tl_y = pos->y + halfsizeY*c - fenceOffset*s; +#if RTS_ZEROHOUR IRegion2D cellBounds; 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); @@ -4025,6 +4103,7 @@ void Pathfinder::classifyFence( Object *obj, Bool insert ) cellBounds.hi.y = REAL_TO_INT_CEIL((pos->y + 0.5f)/PATHFIND_CELL_SIZE_F); #endif Bool didAnything = false; +#endif // RTS_ZEROHOUR for (Int iy = 0; iy < numStepsY; ++iy, tl_x += ydx, tl_y += ydy) { @@ -4040,11 +4119,19 @@ void Pathfinder::classifyFence( Object *obj, Bool insert ) ICoord2D pos; pos.x = cx; pos.y = cy; +#if RTS_GENERALS + m_map[cx][cy].setTypeAsObstacle( obj, true, pos ); +#else if (m_map[cx][cy].setTypeAsObstacle( obj, true, pos )) { didAnything = true; m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); } +#endif } +#if RTS_GENERALS + else + m_map[cx][cy].removeObstacle(obj); +#else else { if (m_map[cx][cy].removeObstacle(obj)) { didAnything = true; @@ -4055,13 +4142,16 @@ void Pathfinder::classifyFence( Object *obj, Bool insert ) if (cellBounds.lo.y>cy) cellBounds.lo.y = cy; if (cellBounds.hi.xisKindOf( KINDOF_BLAST_CRATER ) ) // since these footprints are permanent, never remove them return; - +#endif removeUnitFromPathfindMap(obj); if (obj->isKindOf(KINDOF_WALK_ON_TOP_OF_WALL)) { @@ -4144,26 +4234,31 @@ void Pathfinder::classifyObjectFootprint( Object *obj, Bool insert ) return; } - if (obj->getHeightAboveTerrain() > PATHFIND_CELL_SIZE_F && ( ! obj->isKindOf( KINDOF_BLAST_CRATER ) ) ) - { +#if RTS_GENERALS + if (obj->getHeightAboveTerrain() > PATHFIND_CELL_SIZE_F) { + return; // Don't add bounds that are up in the air. +#else + if (obj->getHeightAboveTerrain() > PATHFIND_CELL_SIZE_F && ( ! obj->isKindOf( KINDOF_BLAST_CRATER ) ) ) { return; // Don't add bounds that are up in the air.... unless a blast crater wants to do just that +#endif } internal_classifyObjectFootprint(obj, insert); } void Pathfinder::internal_classifyObjectFootprint( Object *obj, Bool insert ) { - IRegion2D cellBounds; const Coord3D *pos = obj->getPosition(); +#if RTS_ZEROHOUR + IRegion2D cellBounds; 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); cellBounds.hi = cellBounds.lo; - +#endif switch(obj->getGeometryInfo().getGeomType()) { case GEOMETRY_BOX: { - m_zoneManager.markZonesDirty( insert ); + m_zoneManager.markZonesDirty(); Real angle = obj->getOrientation(); @@ -4200,10 +4295,18 @@ void Pathfinder::internal_classifyObjectFootprint( Object *obj, Bool insert ) ICoord2D pos; pos.x = cx; pos.y = cy; +#if RTS_GENERALS + m_map[cx][cy].setTypeAsObstacle( obj, false, pos ); +#else if (m_map[cx][cy].setTypeAsObstacle( obj, false, pos )) { m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); } +#endif } +#if RTS_GENERALS + else + m_map[cx][cy].removeObstacle(obj); +#else else { if (m_map[cx][cy].removeObstacle(obj)) { m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); @@ -4213,6 +4316,7 @@ void Pathfinder::internal_classifyObjectFootprint( Object *obj, Bool insert ) if (cellBounds.lo.y>cy) cellBounds.lo.y = cy; if (cellBounds.hi.xj) cellBounds.lo.y = j; if (cellBounds.hi.xgetGeometryInfo().get2DBounds(*obj->getPosition(), obj->getOrientation(), bounds); + IRegion2D cellBounds; + cellBounds.lo.x = REAL_TO_INT_FLOOR(bounds.lo.x/PATHFIND_CELL_SIZE_F)-1; + cellBounds.lo.y = REAL_TO_INT_FLOOR(bounds.lo.y/PATHFIND_CELL_SIZE_F)-1; + cellBounds.hi.x = REAL_TO_INT_CEIL(bounds.hi.x/PATHFIND_CELL_SIZE_F)+1; + cellBounds.hi.y = REAL_TO_INT_CEIL(bounds.hi.y/PATHFIND_CELL_SIZE_F)+1; +#else m_zoneManager.updateZonesForModify(m_map, m_layers, cellBounds, m_extent); - Int i, j; cellBounds.lo.x -= 2; cellBounds.lo.y -= 2; cellBounds.hi.x += 2; cellBounds.hi.y += 2; +#endif if (cellBounds.lo.x < m_extent.lo.x) { cellBounds.lo.x = m_extent.lo.x; } @@ -4796,9 +4920,11 @@ Bool Pathfinder::checkDestination(const Object *obj, Int cellX, Int cellY, Pathf return false; } +#if RTS_ZEROHOUR if (IS_IMPASSABLE(cell->getType())) { return false; } +#endif if (cell->getFlags() == PathfindCell::NO_UNITS) { continue; // Nobody is here, so it's ok. @@ -4930,6 +5056,11 @@ Bool Pathfinder::checkForMovement(const Object *obj, TCheckMovementInfo &info) if (!unit->getAIUpdateInterface()) { return false; // can't path through not-idle units. } +#if RTS_GENERALS + if (!unit->getAIUpdateInterface()->isIdle()) { + return false; // can't path through not-idle units. + } +#endif Bool found = false; Int k; for (k=0; kgetType() == PathfindCell::CELL_OBSTACLE) return false; +#else if (IS_IMPASSABLE(goalCell->getType())) return false; +#endif Int zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, goalCell->getZone()); if (startingInObstacle) { zone2 = m_zoneManager.getEffectiveTerrainZone(zone2); @@ -5580,10 +5715,12 @@ void Pathfinder::doDebugIcons() { color.red = 1; empty = false; break; + case PathfindCell::CELL_BRIDGE_IMPASSABLE: color.blue = color.red = 1; empty = false; break; + case PathfindCell::CELL_IMPASSABLE: color.green = 1; empty = false; @@ -5746,8 +5883,7 @@ void Pathfinder::processPathfindQueue() #pragma message("AHHHH!, forced calls to pathzonerefresh still in code... notify M Lorenzen") s_stopForceCalling==FALSE || #endif - m_zoneManager.needToCalculateZones()) - { + m_zoneManager.needToCalculateZones()) { m_zoneManager.calculateZones(m_map, m_layers, m_extent); return; } @@ -6117,7 +6253,11 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * newCell->setBlockedByAlly(false); if (info.allyFixedCount>0) { +#if RTS_GENERALS + newCostSoFar += 3*COST_DIAGONAL*info.allyFixedCount; +#else newCostSoFar += 3*COST_DIAGONAL; +#endif if (!canPathThroughUnits) newCell->setBlockedByAlly(true); } @@ -6633,6 +6773,7 @@ Path *Pathfinder::buildHierarchicalPath( const Coord3D *fromPos, PathfindCell *g prependCells(path, fromPos, goalCell, true); +#if RTS_ZEROHOUR // Expand the hierarchical path around the starting point. jba [8/24/2003] // This allows the unit to get around friendly units that may be near it. Coord3D pos = *path->getFirstNode()->getPosition(); @@ -6651,6 +6792,7 @@ Path *Pathfinder::buildHierarchicalPath( const Coord3D *fromPos, PathfindCell *g m_zoneManager.setPassable(i, j, true); } } +#endif #if defined(RTS_DEBUG) if (TheGlobalData->m_debugAI==AI_DEBUG_PATHS) @@ -6707,14 +6849,15 @@ struct MADStruct if (d->obj->getRelationship(otherObj)!=ALLIES) { return 0; // Only move allies. } - if( otherObj && otherObj->getAI() && !otherObj->getAI()->isMoving() ) - { + if (otherObj && otherObj->getAI() && !otherObj->getAI()->isMoving()) { +#if RTS_ZEROHOUR //Kris: Patch 1.01 November 3, 2003 //Black Lotus exploit fix -- moving while hacking. if( otherObj->testStatus( OBJECT_STATUS_IS_USING_ABILITY ) || otherObj->getAI()->isBusy() ) { return 0; // Packing or unpacking objects for example } +#endif //DEBUG_LOG(("Moving ally")); otherObj->getAI()->aiMoveAwayFromUnit(d->obj, CMD_FROM_AI); } @@ -7191,12 +7334,17 @@ void Pathfinder::processHierarchicalCell( const ICoord2D &scanCell, const ICoord scanCell.ym_extent.hi.y) { return; } +#if RTS_ZEROHOUR if (parentZone == PathfindZoneManager::UNINITIALIZED_ZONE) { return; } +#endif if (parentZone == m_zoneManager.getBlockZone(LOCOMOTORSURFACE_GROUND, crusher, scanCell.x, scanCell.y, m_map)) { PathfindCell *newCell = getCell(LAYER_GROUND, scanCell.x, scanCell.y); +#if RTS_GENERALS + if (newCell->hasInfo() && (newCell->getOpen() || newCell->getClosed())) return; // already looked at this one. +#else if( !newCell->hasInfo() ) { return; @@ -7204,6 +7352,7 @@ void Pathfinder::processHierarchicalCell( const ICoord2D &scanCell, const ICoord if( newCell->getOpen() || newCell->getClosed() ) return; // already looked at this one. +#endif ICoord2D adjacentCell = scanCell; //DEBUG_ASSERTCRASH(parentZone==newCell->getZone(), ("Different zones?")); @@ -7254,9 +7403,7 @@ void Pathfinder::processHierarchicalCell( const ICoord2D &scanCell, const ICoord } adjNewCell->allocateInfo(adjacentCell); - if( adjNewCell->hasInfo() ) - { - + if( adjNewCell->hasInfo() ) { cellCount++; Int curCost = adjNewCell->costToHierGoal(parentCell); Int remCost = adjNewCell->costToHierGoal(goalCell); @@ -7933,9 +8080,11 @@ Bool Pathfinder::clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, { // See if terrain or building is blocking the destination. PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); +#if RTS_ZEROHOUR if (!validMovementPosition(false, destinationLayer, locomotorSet, to)) { return false; } +#endif PathfindLayerEnum fromLayer = TheTerrainLogic->getLayerForDestination(from); Int zone1, zone2; @@ -7949,11 +8098,13 @@ Bool Pathfinder::clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, if (parentCell->getType() == PathfindCell::CELL_OBSTACLE) { doingTerrainZone = true; +#if RTS_ZEROHOUR if (zone1 == PathfindZoneManager::UNINITIALIZED_ZONE) { // We are in a building that just got placed, and zones haven't been updated yet. [8/8/2003] // It is better to return a false positive than a false negative. jba. return true; } +#endif } zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, goalCell->getZone()); if (goalCell->getType() == PathfindCell::CELL_OBSTACLE) { @@ -7969,6 +8120,11 @@ Bool Pathfinder::clientSafeQuickDoesPathExist( const LocomotorSet& locomotorSet, zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, zone2); zone2 = m_zoneManager.getEffectiveTerrainZone(zone2); } +#if RTS_GENERALS + if (!validMovementPosition(false, destinationLayer, locomotorSet, to)) { + return false; + } +#endif // If the terrain is connected using this locomotor set, we can path somehow. if (zone1 == zone2) { // There is not terrain blocking the from & to. @@ -8453,7 +8609,11 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet PathfindCell *ignoreCell = getClippedCell(goalObj->getLayer(), goalObj->getPosition()); if ( (goalCell->getObstacleID()==ignoreCell->getObstacleID()) && (goalCell->getObstacleID() != INVALID_ID) ) { Object* newObstacle = TheGameLogic->findObjectByID(goalCell->getObstacleID()); +#if RTS_GENERALS + if (newObstacle != nullptr && newObstacle->isKindOf(KINDOF_AIRFIELD)) +#else if (newObstacle != nullptr && newObstacle->isKindOf(KINDOF_FS_AIRFIELD)) +#endif { m_ignoreObstacleID = goalCell->getObstacleID(); goalOnObstacle = true; @@ -8562,9 +8722,13 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet if (!goalOnObstacle) { // See if the goal is a valid destination. If not, accept closest cell. if (closesetCell!=nullptr && !canPathThroughUnits && !checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), parentCell->getLayer(), radius, centerInCell)) { +#if RTS_GENERALS + break; +#else foundGoal = true; // Continue processing the open list to find a possibly closer cell. jba. [8/25/2003] continue; +#endif } } @@ -9485,7 +9649,7 @@ void Pathfinder::changeBridgeState( PathfindLayerEnum layer, Bool repaired) { if (m_layers[layer].isUnused()) return; if (m_layers[layer].setDestroyed(!repaired)) { - m_zoneManager.markZonesDirty( repaired ); + m_zoneManager.markZonesDirty(); } } @@ -9529,22 +9693,23 @@ void Pathfinder::updateGoal( Object *obj, const Coord3D *newGoalPos, PathfindLay AIUpdateInterface *ai = obj->getAIUpdateInterface(); if (!ai) return; // only consider ai objects. - - - - if (!ai->isDoingGroundMovement()) // exception:sniped choppers are on ground - { - - Bool isUnmannedHelicopter = ( obj->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) && obj->isDisabledByType( DISABLED_UNMANNED ) ) ; - if ( ! isUnmannedHelicopter ) - { - updateAircraftGoal(obj, newGoalPos); - return; - } + if (!ai->isDoingGroundMovement()) { +#if RTS_GENERALS + Bool isUnmannedHelicopter = false; +#else + // exception:sniped choppers are on ground + Bool isUnmannedHelicopter = ( obj->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) && obj->isDisabledByType( DISABLED_UNMANNED ) ) ; +#endif + if ( ! isUnmannedHelicopter ) + { + updateAircraftGoal(obj, newGoalPos); + return; + } } PathfindLayerEnum originalLayer = obj->getDestinationLayer(); + //DEBUG_LOG(("Object Goal layer is %d", layer)); Bool layerChanged = originalLayer != layer; Bool doGround=false; @@ -9979,7 +10144,8 @@ if (g_UT_startTiming) return false; if (!otherObj->getAI() || otherObj->getAI()->isMoving()) { continue; } - + +#if RTS_ZEROHOUR if (otherObj->getAI()->isAttacking()) { continue; // Don't move units that are attacking. [8/14/2003] } @@ -9989,6 +10155,7 @@ if (g_UT_startTiming) return false; if( otherObj->testStatus( OBJECT_STATUS_IS_USING_ABILITY ) || otherObj->getAI()->isBusy() ) { continue; // Packing or unpacking objects for example } +#endif //DEBUG_LOG(("Moving ally")); otherObj->getAI()->aiMoveAwayFromUnit(obj, CMD_FROM_AI); @@ -10431,6 +10598,10 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot if (!m_isMapReady) return nullptr; // Should always be ok. +#ifdef DEBUG_LOGGING + Int startTimeMS = ::GetTickCount(); +#endif + Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; Int radius; Bool centerInCell; @@ -10629,7 +10800,11 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot #endif if (show) debugShowSearch(true); - + #ifdef DEBUG_LOGGING + DEBUG_LOG(("Attack path took %d cells, %f sec", cellCount, (::GetTickCount()-startTimeMS)/1000.0f)); + #endif + + #if RTS_ZEROHOUR // put parent cell onto closed list - its evaluation is finished parentCell->putOnClosedList( m_closedList ); // construct and return path @@ -10690,6 +10865,7 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot } } } +#endif // RTS_ZEROHOUR Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) {