diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 078f3de1cbe..fe0f700648c 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -239,7 +239,7 @@ set(GAMEENGINE_SRC # Include/GameLogic/AIDock.h # Include/GameLogic/AIGuard.h # Include/GameLogic/AIGuardRetaliate.h -# Include/GameLogic/AIPathfind.h + Include/GameLogic/AIPathfind.h # Include/GameLogic/AIPlayer.h # Include/GameLogic/AISkirmishPlayer.h # Include/GameLogic/AIStateMachine.h @@ -843,7 +843,7 @@ set(GAMEENGINE_SRC # Source/GameLogic/AI/AIGroup.cpp # Source/GameLogic/AI/AIGuard.cpp # Source/GameLogic/AI/AIGuardRetaliate.cpp -# Source/GameLogic/AI/AIPathfind.cpp + Source/GameLogic/AI/AIPathfind.cpp # Source/GameLogic/AI/AIPlayer.cpp # Source/GameLogic/AI/AISkirmishPlayer.cpp # Source/GameLogic/AI/AIStates.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h b/Core/GameEngine/Include/GameLogic/AIPathfind.h similarity index 100% rename from GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h rename to Core/GameEngine/Include/GameLogic/AIPathfind.h diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp similarity index 100% rename from GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp rename to Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp diff --git a/Generals/Code/GameEngine/CMakeLists.txt b/Generals/Code/GameEngine/CMakeLists.txt index 044a2ec6952..124246f68f4 100644 --- a/Generals/Code/GameEngine/CMakeLists.txt +++ b/Generals/Code/GameEngine/CMakeLists.txt @@ -222,7 +222,7 @@ set(GAMEENGINE_SRC Include/GameLogic/AI.h Include/GameLogic/AIDock.h Include/GameLogic/AIGuard.h - Include/GameLogic/AIPathfind.h +# Include/GameLogic/AIPathfind.h Include/GameLogic/AIPlayer.h Include/GameLogic/AISkirmishPlayer.h Include/GameLogic/AIStateMachine.h @@ -781,7 +781,7 @@ set(GAMEENGINE_SRC Source/GameLogic/AI/AIDock.cpp Source/GameLogic/AI/AIGroup.cpp Source/GameLogic/AI/AIGuard.cpp - Source/GameLogic/AI/AIPathfind.cpp +# Source/GameLogic/AI/AIPathfind.cpp Source/GameLogic/AI/AIPlayer.cpp Source/GameLogic/AI/AISkirmishPlayer.cpp Source/GameLogic/AI/AIStates.cpp diff --git a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h deleted file mode 100644 index 59fdf0ad70d..00000000000 --- a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ /dev/null @@ -1,998 +0,0 @@ -/* -** Command & Conquer Generals(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// AIPathfind.h -// AI pathfinding system -// Author: Michael S. Booth, October 2001 - -#pragma once - -#include "Common/GameType.h" -#include "Common/GameMemory.h" -#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 Weapon; -class PathfindZoneManager; -class PathfindCell; - -// How close is close enough when moving. - -#define PATHFIND_CLOSE_ENOUGH 1.0f -#define PATH_MAX_PRIORITY 0x7FFFFFFF - -#define INFANTRY_MOVES_THROUGH_INFANTRY - -#if !RETAIL_COMPATIBLE_PATHFINDING -#undef RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION -#endif - - typedef UnsignedShort zoneStorageType; - - -//---------------------------------------------------------------------------------------------------------- - -/** - * PathNodes are used to create a final Path to return from the - * pathfinder. Note that these are not used during the A* search. - */ -class PathNode : public MemoryPoolObject -{ -public: - PathNode(); - - Coord3D *getPosition() { return &m_pos; } ///< return position of this node - const Coord3D *getPosition() const { return &m_pos; } ///< return position of this node - void setPosition( const Coord3D *pos ) { m_pos = *pos; } ///< set the position of this path node - - const Coord3D *computeDirectionVector(); ///< compute direction to next node - - PathNode *getNext() { return m_next; } ///< return next node in the path - PathNode *getPrevious() { return m_prev; } ///< return previous node in the path - const PathNode *getNext() const { return m_next; } ///< return next node in the path - const PathNode *getPrevious() const { return m_prev; } ///< return previous node in the path - - PathfindLayerEnum getLayer() const { return m_layer; } ///< return layer of this node. - void setLayer( PathfindLayerEnum layer ) { m_layer = layer; } ///< set the layer of this path node - - void setNextOptimized( PathNode *node ); - - PathNode *getNextOptimized(Coord2D* dir = nullptr, Real* dist = nullptr) ///< return next node in optimized path - { - if (dir) - *dir = m_nextOptiDirNorm2D; - if (dist) - *dist = m_nextOptiDist2D; - return m_nextOpti; - } - - const PathNode *getNextOptimized(Coord2D* dir = nullptr, Real* dist = nullptr) const ///< return next node in optimized path - { - if (dir) - *dir = m_nextOptiDirNorm2D; - if (dist) - *dist = m_nextOptiDist2D; - return m_nextOpti; - } - - void setCanOptimize(Bool canOpt) { m_canOptimize = canOpt;} - Bool getCanOptimize() const { return m_canOptimize;} - - /// given a list, prepend this node, return new list - PathNode *prependToList( PathNode *list ); - - /// given a list, append this node, return new list. slow implementation. - /// @todo optimize this - PathNode *appendToList( PathNode *list ); - - /// given a node, append to this node - void append( PathNode *list ); - -public: - mutable Int m_id; // Used in Path::xfer() to save & recreate the path list. - -private: - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( PathNode, "PathNodePool" ); ///< @todo Set real numbers for mem alloc - - PathNode* m_nextOpti; ///< next node in the optimized path - PathNode* m_next; ///< next node in the path - PathNode* m_prev; ///< previous node in the path - Coord3D m_pos; ///< position of node in space - PathfindLayerEnum m_layer; ///< Layer for this section. - Bool m_canOptimize; ///< True if this cell can be optimized out. - - Real m_nextOptiDist2D; ///< if nextOpti is nonnull, the dist to it. - Coord2D m_nextOptiDirNorm2D; ///< if nextOpti is nonnull, normalized dir vec towards it. - -}; - -// this doesn't actually seem to be a particularly useful win, -// performance-wise, so I didn't enable it. (srj) -#define NO_CPOP_STARTS_FROM_PREV_SEG - -struct ClosestPointOnPathInfo -{ - Real distAlongPath; - Coord3D posOnPath; - PathfindLayerEnum layer; -}; - -/** - * This class encapsulates a "path" returned by the Pathfinder. - */ -class Path : public MemoryPoolObject, public Snapshot -{ -public: - Path(); - - PathNode *getFirstNode() { return m_path; } - PathNode *getLastNode() { return m_pathTail; } - - void updateLastNode( const Coord3D *pos ); - - void prependNode( const Coord3D *pos, PathfindLayerEnum layer ); ///< Create a new node at the head of the path - void appendNode( const Coord3D *pos, PathfindLayerEnum layer ); ///< Create a new node at the end of the path - void setBlockedByAlly(Bool blocked) {m_blockedByAlly = blocked;} - Bool getBlockedByAlly() {return m_blockedByAlly;} - void optimize( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, Bool blocked ); ///< Optimize the path to discard redundant nodes - - void optimizeGroundPath( Bool crusher, Int diameter ); ///< Optimize the ground path to discard redundant nodes - - /// Given a location, return nearest location on path, and along-path dist to end as function result - void computePointOnPath( const Object *obj, const LocomotorSet& locomotorSet, const Coord3D& pos, ClosestPointOnPathInfo& out); - - /// Given a location, return nearest location on path, and along-path dist to end as function result - void peekCachedPointOnPath( Coord3D& pos ) const {pos = m_cpopOut.posOnPath;} - - /// Given a flight path, compute the distance to goal (0 if we are past it) & return the goal pos. - Real computeFlightDistToGoal( const Coord3D *pos, Coord3D& goalPos ); - - /// Given a location, return closest location on path, and along-path dist to end as function result - void markOptimized() {m_isOptimized = true;} - -protected: - // snapshot interface - virtual void crc( Xfer *xfer ); - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess(); - -protected: - enum {MAX_CPOP=20}; ///< Max times we will return the cached cpop. - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( Path, "PathPool" ); ///< @todo Set real numbers for mem alloc - - PathNode* m_path; ///< The list of PathNode objects that define the path - PathNode* m_pathTail; - Bool m_isOptimized; ///< True if the path has been optimized - Bool m_blockedByAlly; ///< An ally needs to move off of this path. - // caching info for computePointOnPath. - Bool m_cpopValid; - Int m_cpopCountdown; ///< We only return the same cpop MAX_CPOP times. It is occasionally possible to get stuck. - Coord3D m_cpopIn; - ClosestPointOnPathInfo m_cpopOut; - const PathNode* m_cpopRecentStart; -}; - -//---------------------------------------------------------------------------------------------------------- - -// See GameType.h for -// enum {LAYER_INVALID = 0, LAYER_GROUND = 1, LAYER_TOP=2 }; - -// Fits in 4 bits for now -enum {MAX_WALL_PIECES = 128}; - -class PathfindCellInfo -{ - friend class PathfindCell; -public: -#if RETAIL_COMPATIBLE_PATHFINDING - static void forceCleanPathFindCellInfos(); -#endif - static void allocateCellInfos(); - static void releaseCellInfos(); - - static PathfindCellInfo * getACellInfo(PathfindCell *cell, const ICoord2D &pos); - static void releaseACellInfo(PathfindCellInfo *theInfo); - -protected: - static PathfindCellInfo *s_infoArray; - static PathfindCellInfo *s_firstFree; ///< - - - PathfindCellInfo *m_nextOpen, *m_prevOpen; ///< for A* "open" list, shared by closed list - - PathfindCellInfo *m_pathParent; ///< "parent" cell from pathfinder - PathfindCell *m_cell; ///< Cell this info belongs to currently. - - UnsignedShort m_totalCost, m_costSoFar; ///< cost estimates for A* search - - /// have to include cell's coordinates, since cells are often accessed via pointer only - ICoord2D m_pos; - - ObjectID m_goalUnitID; ///< The objectID of the ground unit whose goal this is. - ObjectID m_posUnitID; ///< The objectID of the ground unit that is occupying this cell. - ObjectID m_goalAircraftID; ///< The objectID of the aircraft whose goal this is. - - ObjectID m_obstacleID; ///< the object ID who overlaps this cell - - UnsignedInt m_isFree:1; - UnsignedInt m_blockedByAlly:1;///< True if this cell is blocked by an allied unit. - UnsignedInt m_obstacleIsFence:1;///< True if occupied by a fence. - UnsignedInt m_obstacleIsTransparent:1;///< True if obstacle is transparent (undefined if obstacleid is invalid) - /// @todo Do we need both mark values in this cell? Can't store a single value and compare it? - UnsignedInt m_open:1; ///< place for marking this cell as on the open list - UnsignedInt m_closed:1; ///< place for marking this cell as on the closed list -}; - -// TheSuperHackers @info The PathfindCellList class acts as a new management class for the pathfindcell open and closed lists -class PathfindCellList -{ - friend class PathfindCell; - -public: - PathfindCellList() : m_head(nullptr) {} - - void reset(PathfindCell* newHead = nullptr) { m_head = newHead; } - - PathfindCell* getHead() const { return m_head; } - - Bool empty() const { return m_head == nullptr; } - -private: - PathfindCell* m_head; -}; - -/** - * This represents one cell in the pathfinding grid. - * These cells categorize the world into idealized cellular states, - * and are also used for efficient A* pathfinding. - * @todo Optimize memory usage of pathfind grid. - */ -class PathfindCell -{ -public: - - 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_BRIDGE_IMPASSABLE = 0x05, ///< Piece of a bridge that is impassable. - CELL_IMPASSABLE = 0x06 ///< Just plain impassable except for aircraft. - }; - - enum CellFlags - { - NO_UNITS = 0x00, ///< No units in this cell. - UNIT_GOAL = 0x01, ///< A unit is heading to this cell. - UNIT_PRESENT_MOVING = 0x02, ///< A unit is moving through this cell. - UNIT_PRESENT_FIXED = 0x03, ///< A unit is stationary in this cell. - UNIT_GOAL_OTHER_MOVING = 0x05 ///< A unit is moving through this cell, and another unit has this as it's goal. - }; - - /// reset the cell - void reset(); - - PathfindCell(); - ~PathfindCell(); - - 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 - Bool isAircraftGoal() const {return m_aircraftGoal != 0;} - - Bool isObstaclePresent( ObjectID objID ) const; ///< return true if the given object ID is registered as an obstacle in this cell -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - // TheSuperHackers @info isObstructionInvalid() and clearObstruction() only used during retail compatible pathfinding failover cleanup - Bool isObstructionInvalid() const { return m_obstacleID != INVALID_ID && m_info == nullptr && (m_type == CELL_OBSTACLE || m_type == CELL_IMPASSABLE); } - void clearObstruction() { m_type = CELL_CLEAR; m_obstacleID = INVALID_ID; m_obstacleIsFence = false; m_obstacleIsTransparent = false; } -#endif - - inline Bool isObstacleTransparent() const; - inline Bool isObstacleFence() const; - - /// Return estimated cost from given cell to reach goal cell - UnsignedInt costToGoal( PathfindCell *goal ); - - UnsignedInt costToHierGoal( PathfindCell *goal ); - - UnsignedInt costSoFar( PathfindCell *parent ); - - /// put self on "open" list in ascending cost order - void putOnSortedOpenList( PathfindCellList &list ); - - /// remove self from "open" list - void removeFromOpenList( PathfindCellList &list ); - - /// put self on "closed" list, return new list - void putOnClosedList( PathfindCellList &list ); - - /// remove self from "closed" list - void removeFromClosedList( PathfindCellList &list ); - - /// remove all cells from closed list. - static Int releaseClosedList( PathfindCellList &list ); - - /// remove all cells from closed list. - static Int releaseOpenList( PathfindCellList &list ); - - inline PathfindCell *getNextOpen() {return m_info->m_nextOpen?m_info->m_nextOpen->m_cell: nullptr;} - - inline UnsignedShort getXIndex() const {return m_info->m_pos.x;} - inline UnsignedShort getYIndex() const {return m_info->m_pos.y;} - - inline Bool isBlockedByAlly() const; - inline void setBlockedByAlly(Bool blocked); - - inline Bool getOpen() const {return m_info->m_open;} - inline Bool getClosed() const {return m_info->m_closed;} - inline UnsignedInt getCostSoFar() const {return m_info->m_costSoFar;} - inline UnsignedInt getTotalCost() const {return m_info->m_totalCost;} - - 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_info->m_pathParent ? m_info->m_pathParent->m_cell : nullptr : nullptr;} - - Bool startPathfind( PathfindCell *goalCell ); - Bool getPinched() const {return m_pinched;} - void setPinched(Bool pinch) {m_pinched = pinch; } - - Bool allocateInfo(const ICoord2D &pos); - void releaseInfo(); - Bool hasInfo() const {return m_info!=nullptr;} - zoneStorageType getZone() const {return m_zone;} - void setZone(zoneStorageType zone) {m_zone = zone;} - void setGoalUnit(ObjectID unit, const ICoord2D &pos ); - void setGoalAircraft(ObjectID unit, const ICoord2D &pos ); - void setPosUnit(ObjectID unit, const ICoord2D &pos ); - inline ObjectID getGoalUnit() const {ObjectID id = m_info?m_info->m_goalUnitID:INVALID_ID; return id;} - inline ObjectID getGoalAircraft() const {ObjectID id = m_info?m_info->m_goalAircraftID:INVALID_ID; return id;} - inline ObjectID getPosUnit() const {ObjectID id = m_info?m_info->m_posUnitID:INVALID_ID; return id;} - - inline ObjectID getObstacleID() const; - - void setLayer( PathfindLayerEnum layer ) { m_layer = layer; } ///< set the cell layer - PathfindLayerEnum getLayer() const { return (PathfindLayerEnum)m_layer; } ///< get the cell layer - - void setConnectLayer( PathfindLayerEnum layer ) { m_connectsToLayer = layer; } ///< set the cell layer connect id - PathfindLayerEnum getConnectLayer() const { return (PathfindLayerEnum)m_connectsToLayer; } ///< get the cell layer connect id - -private: - PathfindCellInfo *m_info; - ObjectID m_obstacleID; ///< the object ID who overlaps this cell - UnsignedInt m_blockedByAlly : 1; ///< True if this cell is blocked by an allied unit. - UnsignedInt m_obstacleIsFence : 1; ///< True if occupied by a fence. - UnsignedInt m_obstacleIsTransparent : 1; ///< True if obstacle is transparent (undefined if obstacleid is invalid) - - zoneStorageType m_zone : 14; ///< Zone. Each zone is a set of adjacent terrain type. If from & to in the same zone, you can successfully pathfind. If not, - /// you still may be able to if you can cross multiple terrain types. - UnsignedShort m_aircraftGoal : 1; ///< This is an aircraft goal cell. - UnsignedShort m_pinched : 1; ///< This cell is surrounded by obstacle cells. - UnsignedByte m_type : 4; ///< what type of cell terrain this is. - UnsignedByte m_flags : 4; ///< what type of units are in or moving through this cell. - UnsignedByte m_connectsToLayer : 4; ///< This cell can pathfind onto this layer, if > LAYER_TOP. - UnsignedByte m_layer : 4; ///< Layer of this cell. -}; - -typedef PathfindCell *PathfindCellP; - - -// how close a unit has to be in z to interact with the layer. -#define LAYER_Z_CLOSE_ENOUGH_F 10.0f -/** - * This class represents a bridge in the map. This is effectively - * a sub-rectangle of the big pathfind map. - */ -class PathfindLayer -{ -public: - PathfindLayer(); - ~PathfindLayer(); -public: - void reset(); - Bool init(Bridge *theBridge, PathfindLayerEnum layer); - void allocateCells(const IRegion2D *extent); - void allocateCellsForWallLayer(const IRegion2D *extent, ObjectID *wallPieces, Int numPieces); - void classifyCells(); - void classifyWallCells(ObjectID *wallPieces, Int numPieces); - Bool setDestroyed(Bool destroyed); - Bool isUnused(); // True if it doesn't contain a bridge. - Bool isDestroyed() {return m_destroyed;} // True if it has been destroyed. - PathfindCell *getCell(Int x, Int y); - Int getZone() {return m_zone;} - void setZone(Int zone) {m_zone = zone;} - void applyZone(); // Propagates m_zone to all cells. - void getStartCellIndex(ICoord2D *start) {*start = m_startCell;} - void getEndCellIndex(ICoord2D *end) {*end = m_endCell;} - - ObjectID getBridgeID(); - Bool connectsZones(PathfindZoneManager *zm, const LocomotorSet& locomotorSet,Int zone1, Int zone2); - Bool isPointOnWall(ObjectID *wallPieces, Int numPieces, const Coord3D *pt); - -#if defined(RTS_DEBUG) - void doDebugIcons() ; -#endif -protected: - void classifyLayerMapCell( Int i, Int j , PathfindCell *cell, Bridge *theBridge); - void classifyWallMapCell( Int i, Int j, PathfindCell *cell , ObjectID *wallPieces, Int numPieces); - -private: - PathfindCell *m_blockOfMapCells; ///< Pathfinding map - contains iconic representation of the map - PathfindCell **m_layerCells; ///< Pathfinding map indexes - contains matrix indexing into the map. - Int m_width; // Number of cells in x - Int m_height; // Number of cells in y - Int m_xOrigin; // Index of first cell in x - Int m_yOrigin; // Index of first cell in y - ICoord2D m_startCell; // pathfind cell indexes for center cell on the from side. - ICoord2D m_endCell; // pathfind cell indexes for center cell on the to side. - - PathfindLayerEnum m_layer; - Int m_zone; // Whole bridge is in same zone. - Bridge *m_bridge; // Corresponding bridge in TerrainLogic. - Bool m_destroyed; - - -}; - - -#define PATHFIND_CELL_SIZE 10 -#define PATHFIND_CELL_SIZE_F 10.0f - -enum { PATHFIND_QUEUE_LEN=512}; - -struct TCheckMovementInfo; - -/** - * This class is a helper class for zone manager. It maintains information regarding the - * LocomotorSurfaceTypeMask equivalencies within a ZONE_BLOCK_SIZE x ZONE_BLOCK_SIZE area of - * cells. This is used in hierarchical pathfinding to find the best coarse path at the - * block level. - */ -class ZoneBlock -{ -public: - ZoneBlock(); - ~ZoneBlock(); // not virtual, please don't override without making virtual. jba. - - void blockCalculateZones( PathfindCell **map, PathfindLayer layers[], const IRegion2D &bounds); ///< Does zone calculations. - zoneStorageType getEffectiveZone(LocomotorSurfaceTypeMask acceptableSurfaces, Bool crusher, zoneStorageType zone) const; - - void clearMarkedPassable() {m_markedPassable = false;} - Bool isPassable() {return m_markedPassable;} - void setPassable(Bool pass) {m_markedPassable = pass;} - - Bool getInteractsWithBridge() const {return m_interactsWithBridge;} - void setInteractsWithBridge(Bool interacts) {m_interactsWithBridge = interacts;} - -protected: - void allocateZones(); - void freeZones(); - -protected: - ICoord2D m_cellOrigin; - - zoneStorageType m_firstZone; // First zone in this block. - 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; - zoneStorageType *m_groundRubbleZones; - zoneStorageType *m_crusherZones; - Bool m_interactsWithBridge; - Bool m_markedPassable; -}; -typedef ZoneBlock *ZoneBlockP; - -/** - * This class manages the zones in the map. A zone is an area in the map that - * is one contiguous type of terrain (clear, cliff, water, building). If - * a unit is in a zone, and wants to move to another location, the destination - * zone has to be the same, or it can't get there. - * There are equivalency tables for meta-zones. For example, an amphibious craft can - * travel through water and clear cells. - */ -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_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; - - void getExtent(ICoord2D &extent) const {extent = m_zoneBlockExtent;} - - /// return zone relative the the block zone that this cell resides in. - zoneStorageType getBlockZone(LocomotorSurfaceTypeMask acceptableSurfaces, Bool crusher, Int cellX, Int cellY, PathfindCell **map) const; - void allocateBlocks(const IRegion2D &globalBounds); - - void clearPassableFlags(); - Bool isPassable(Int cellX, Int cellY) const; - Bool clipIsPassable(Int cellX, Int cellY) const; - void setPassable(Int cellX, Int cellY, Bool passable); - - void setAllPassable(); - - void setBridge(Int cellX, Int cellY, Bool bridge); - Bool interactsWithBridge(Int cellX, Int cellY) const; - -private: - void allocateZones(); - void freeZones(); - void freeBlocks(); - -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. - UnsignedInt m_nextFrameToCalculateZones; ///< When should I recalculate, next?. - UnsignedShort m_zonesAllocated; - zoneStorageType *m_groundCliffZones; - zoneStorageType *m_groundWaterZones; - zoneStorageType *m_groundRubbleZones; - zoneStorageType *m_terrainZones; - zoneStorageType *m_crusherZones; - zoneStorageType *m_hierarchicalZones; -}; - -/** - * The pathfinding services interface provides access to the 3 expensive path find calls: - * findPath, findClosestPath, and findAttackPath. - * It is only available to units when their ai interface doPathfind method is called. - * This allows the pathfinder to spread out the pathfinding over a number of frames - * when a lot of units are trying to pathfind all at the same time. - */ -class PathfindServicesInterface { -public: - 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 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; - - /** Find a short, valid path to a location that obj can attack victim from. */ - virtual Path *findAttackPath( const Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, - const Object *victim, const Coord3D* victimPos, const Weapon *weapon )=0; - - /** Patch to the exiting path from the current position, either because we became blocked, - or because we had to move off the path to avoid other units. */ - virtual Path *patchPath( const Object *obj, const LocomotorSet& locomotorSet, - Path *originalPath, Bool blocked ) = 0; - - /** Find a short, valid path to a location that is away from the repulsors. */ - virtual Path *findSafePath( const Object *obj, const LocomotorSet& locomotorSet, - const Coord3D *from, const Coord3D* repulsorPos1, const Coord3D* repulsorPos2, Real repulsorRadius ) = 0; - -}; - -/** - * The Pathfinding engine itself. - */ -class Pathfinder : PathfindServicesInterface, public Snapshot -{ -// The following routines are private, but available through the doPathfind callback to aiInterface. jba. -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 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 ); - - /** Find a short, valid path to a location that obj can attack victim from. */ - virtual Path *findAttackPath( const Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, - const Object *victim, const Coord3D* victimPos, const Weapon *weapon ); - - /** Find a short, valid path to a location that is away from the repulsors. */ - virtual Path *findSafePath( const Object *obj, const LocomotorSet& locomotorSet, - const Coord3D *from, const Coord3D* repulsorPos1, const Coord3D* repulsorPos2, Real repulsorRadius ); - - /** Patch to the exiting path from the current position, either because we became blocked, - or because we had to move off the path to avoid other units. */ - virtual Path *patchPath( const Object *obj, const LocomotorSet& locomotorSet, - Path *originalPath, Bool blocked ); - -public: - Pathfinder(); - ~Pathfinder() ; - - void reset(); ///< Reset system in preparation for new map - - // --------------- inherited from Snapshot interface -------------- - void crc( Xfer *xfer ); - void xfer( Xfer *xfer ); - 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 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) - - Bool queueForPath(ObjectID id); ///< The object wants to request a pathfind, so put it on the list to process. - void processPathfindQueue(); ///< Process some or all of the queued pathfinds. - void forceMapRecalculation(); ///< Force pathfind map recomputation. If region is given, only that area is recomputed - - /** Returns an aircraft path to the goal. */ - Path *getAircraftPath( const Object *obj, const Coord3D *to); - Path *findGroundPath( const Coord3D *from, const Coord3D *to, Int pathRadius, - Bool crusher); ///< Find a short, valid path of the desired width on the ground. - - void addObjectToPathfindMap( class Object *obj ); ///< Classify the given object's cells in the map - void removeObjectFromPathfindMap( class Object *obj ); ///< De-classify the given object's cells in the map - - void removeUnitFromPathfindMap( Object *obj ); ///< De-classify the given mobile unit's cells in the map - void updateGoal( Object *obj, const Coord3D *newGoalPos, PathfindLayerEnum layer); ///< Update the given mobile unit's cells in the map - void updateAircraftGoal( Object *obj, const Coord3D *newGoalPos); ///< Update the given aircraft unit's cells in the map - void removeGoal( Object *obj); ///< Removes the given mobile unit's goal cells in the map - void updatePos( Object *obj, const Coord3D *newPos); ///< Update the given mobile unit's cells in the map - void removePos( Object *obj); ///< Removes the unit's position cells from the map - - Bool moveAllies(Object *obj, Path *path); - - // NOTE - The object MUST NOT MOVE between the call to createAWall... and removeWall... - // or BAD THINGS will happen. jba. - void createAWallFromMyFootprint( Object *obj ) {internal_classifyObjectFootprint(obj, true);} // Temporarily treat this object as an obstacle. - void removeWallFromMyFootprint( Object *obj ){internal_classifyObjectFootprint(obj, false);} // Undo createAWallFromMyFootprint. - - Path *getMoveAwayFromPath(Object *obj, Object *otherObj, Path *pathToAvoid, Object *otherObj2, Path *pathToAvoid2); - - void changeBridgeState( PathfindLayerEnum layer, Bool repaired ); - - Bool findBrokenBridge(const LocomotorSet &locomotorSet, const Coord3D *from, const Coord3D *to, ObjectID *bridgeID); - - void newMap(); - - PathfindCell *getCell( PathfindLayerEnum layer, Int x, Int y ); ///< Return the cell at grid coords (x,y) - PathfindCell *getCell( PathfindLayerEnum layer, const Coord3D *pos ); ///< Given a position, return associated grid cell - PathfindCell *getClippedCell( PathfindLayerEnum layer, const Coord3D *pos ); ///< Given a position, return associated grid cell - void clip(Coord3D *from, Coord3D *to); - Bool worldToCell( const Coord3D *pos, ICoord2D *cell ); ///< Given a world position, return grid cell coordinate - - const ICoord2D *getExtent() const {return &m_extent.hi;} - - void setIgnoreObstacleID( ObjectID objID ); ///< if non-zero, the pathfinder will ignore the given obstacle - - Bool validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask acceptableSurfaces, PathfindCell *toCell, PathfindCell *fromCell = nullptr ); ///< Return true if given position is a valid movement location - Bool validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Int x, Int y ); ///< Return true if given position is a valid movement location - Bool validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, const Coord3D *pos ); ///< Return true if given position is a valid movement location - Bool validMovementTerrain( PathfindLayerEnum layer, const Locomotor* locomotor, const Coord3D *pos ); ///< Return true if given position is a valid movement location - - Locomotor* chooseBestLocomotorForPosition(PathfindLayerEnum layer, LocomotorSet* locomotorSet, const Coord3D* pos ); - - Bool isViewBlockedByObstacle(const Object* obj, const Object* objOther); ///< Return true if the straight line between the given points contains any obstacle, and thus blocks vision - - Bool isAttackViewBlockedByObstacle(const Object* obj, const Coord3D& attackerPos, const Object* victim, const Coord3D& victimPos); ///< Return true if the straight line between the given points contains any obstacle, and thus blocks vision - - Bool isLinePassable( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, - PathfindLayerEnum layer, const Coord3D& startWorld, const Coord3D& endWorld, - Bool blocked, Bool allowPinched ); ///< Return true if the straight line between the given points is passable - - void moveAlliesAwayFromDestination( Object *obj,const Coord3D& destination); - - Bool isGroundPathPassable( Bool isCrusher, const Coord3D& startWorld, PathfindLayerEnum startLayer, - const Coord3D& endWorld, Int pathDiameter); ///< Return true if the straight line between the given points is passable - - // for debugging - const Coord3D *getDebugPathPosition(); - void setDebugPathPosition( const Coord3D *pos ); - Path *getDebugPath(); - void setDebugPath( Path *debugpath ); - -#if RETAIL_COMPATIBLE_PATHFINDING - void forceCleanCells(); -#endif - void cleanOpenAndClosedLists(); - - // Adjusts the destination to a spot near dest that is not occupied by other units. - Bool adjustDestination(Object *obj, const LocomotorSet& locomotorSet, - Coord3D *dest, const Coord3D *groupDest=nullptr); - - // Adjusts the destination to a spot near dest for landing that is not occupied by other units. - Bool adjustToLandingDestination(Object *obj, Coord3D *dest); - - // Adjusts the destination to a spot that can attack target that is not occupied by other units. - Bool adjustTargetDestination(const Object *obj, const Object *target, const Coord3D *targetPos, - const Weapon *weapon, Coord3D *dest); - - // Adjusts destination to a spot near dest that is possible to path to. - Bool adjustToPossibleDestination(Object *obj, const LocomotorSet& locomotorSet, Coord3D *dest); - - void snapPosition(Object *obj, Coord3D *pos); // Snaps the current position to it's grid location. - void snapClosestGoalPosition(Object *obj, Coord3D *pos); // Snaps the current position to a good goal position. - Bool goalPosition(Object *obj, Coord3D *pos); // Returns the goal position on the grid. - - PathfindLayerEnum addBridge(Bridge *theBridge); // Adds a bridge layer, and returns the layer id. - - void addWallPiece(Object *wallPiece); // Adds a wall piece. - void removeWallPiece(Object *wallPiece); // Removes a wall piece. - Real getWallHeight() {return m_wallHeight;} - Bool isPointOnWall(const Coord3D *pos); - - void updateLayer(Object *obj, PathfindLayerEnum layer); ///< Updates object's layer. - - static void classifyMapCell( Int x, Int y, PathfindCell *cell); ///< Classify the given map cell - Int clearCellForDiameter( Bool crusher, Int cellX, Int cellY, PathfindLayerEnum layer, Int pathDiameter ); ///< Return true if given position is a valid movement location - -protected: - virtual Path *internalFindPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to); ///< Find a short, valid path between given locations - Path *findHierarchicalPath( Bool isHuman, const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to, Bool crusher); - Path *findClosestHierarchicalPath( Bool isHuman, const LocomotorSet& locomotorSet, const Coord3D *from, const Coord3D *to, Bool crusher); - Path *internal_findHierarchicalPath( Bool isHuman, const LocomotorSurfaceTypeMask locomotorSurface, const Coord3D *from, const Coord3D *to, Bool crusher, Bool closestOK); - void processHierarchicalCell( const ICoord2D &scanCell, const ICoord2D &deltaPathfindCell, - PathfindCell *parentCell, - PathfindCell *goalCell, zoneStorageType parentZone, - zoneStorageType *examinedZones, Int &numExZones, - Bool crusher, Int &cellCount); - Bool checkForAdjust(Object *, const LocomotorSet& locomotorSet, Bool isHuman, Int cellX, Int cellY, - PathfindLayerEnum layer, Int iRadius, Bool center,Coord3D *dest, const Coord3D *groupDest) ; - Bool checkForLanding(Int cellX, Int cellY, - PathfindLayerEnum layer, Int iRadius, Bool center,Coord3D *dest) ; - Bool checkForTarget(const Object *obj, Int cellX, Int cellY, const Weapon *weapon, - const Object *victim, const Coord3D *victimPos, - Int iRadius, Bool center,Coord3D *dest) ; - Bool checkForPossible(Bool isCrusher, Int fromZone, Bool center, const LocomotorSet& locomotorSet, - Int cellX, Int cellY, PathfindLayerEnum layer, Coord3D *dest, Bool startingInObstacle) ; - void getRadiusAndCenter(const Object *obj, Int &iRadius, Bool ¢er); - void adjustCoordToCell(Int cellX, Int cellY, Bool centerInCell, Coord3D &pos, PathfindLayerEnum layer); - Bool checkDestination(const Object *obj, Int cellX, Int cellY, PathfindLayerEnum layer, Int iRadius, Bool centerInCell); - Bool checkForMovement(const Object *obj, TCheckMovementInfo &info); - Bool segmentIntersectsTallBuilding(const PathNode *curNode, PathNode *nextNode, - ObjectID ignoreBuilding, Coord3D *insertPos1, Coord3D *insertPos2, Coord3D *insertPos3); ///< Return true if the straight line between the given points intersects a tall building. - Bool circleClipsTallBuilding(const Coord3D *from, const Coord3D *to, Real radius, ObjectID ignoreBuilding, Coord3D *adjustTo); ///< Return true if the circle at the end of the line between the given points intersects a tall building. - - enum {NO_ATTACK=0}; - Int examineNeighboringCells(PathfindCell *parentCell, PathfindCell *goalCell, - const LocomotorSet& locomotorSet, Bool isHumanPlayer, - Bool centerInCell, Int radius, const ICoord2D &startCellNdx, - const Object *obj, Int attackDistance); - - Int checkPathCost(Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, - const Coord3D *to); - - void tightenPath(Object *obj, const LocomotorSet& locomotorSet, Coord3D *from, - const Coord3D *to); - - /** - return 0 to continue iterating the line, nonzero to terminate the iteration. - the nonzero result will be returned as the result of iterateCellsAlongLine(). - iterateCellsAlongLine will return zero if it completes. - */ - typedef Int (*CellAlongLineProc)(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - Int iterateCellsAlongLine(const Coord3D& startWorld, const Coord3D& endWorld, - PathfindLayerEnum layer, CellAlongLineProc proc, void* userData); - - Int iterateCellsAlongLine(const ICoord2D &start, const ICoord2D &end, - PathfindLayerEnum layer, CellAlongLineProc proc, void* userData); - - static Int linePassableCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - static Int groundPathPassableCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - static Int lineBlockedByObstacleCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - static Int tightenPathCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - static Int attackBlockedByObstacleCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - static Int examineCellsCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - static Int groundCellsCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - static Int moveAlliesDestinationCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - - static Int segmentIntersectsBuildingCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData); - - void classifyMap(); ///< Classify all cells in grid as obstacles, etc - void classifyObjectFootprint( Object *obj, Bool insert ); /** Classify the cells under the given object - If 'insert' is true, object is being added - If 'insert' is false, object is being removed */ - void internal_classifyObjectFootprint( Object *obj, Bool insert ); /** Classify the cells under the given object - If 'insert' is true, object is being added - If 'insert' is false, object is being removed */ - void classifyFence( Object *obj, Bool insert ); /** Classify the cells under the given fence object. */ - void classifyUnitFootprint( Object *obj, Bool insert, Bool remove, Bool update ); /** Classify the cells under the given object If 'insert' is true, object is being added */ - /// Convert world coordinate to array index - void worldToGrid( const Coord3D *pos, ICoord2D *cellIndex ); - - Bool evaluateCell(PathfindCell* newCell, PathfindCell *parentCell, - const LocomotorSet& locomotorSet, - Bool centerInCell, Int radius, - const Object *obj, Int attackDistance); - - Path *buildActualPath( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, - const Coord3D *fromPos, PathfindCell *goalCell, Bool center, Bool blocked ); ///< Work backwards from goal cell to construct final path - Path *buildGroundPath( Bool isCrusher,const Coord3D *fromPos, PathfindCell *goalCell, - Bool center, Int pathDiameter ); ///< Work backwards from goal cell to construct final path - Path *buildHierarchicalPath( const Coord3D *fromPos, PathfindCell *goalCell); ///< Work backwards from goal cell to construct final path - - void prependCells( Path *path, const Coord3D *fromPos, - PathfindCell *goalCell, Bool center ); ///< Add pathfind cells to a path. - - void debugShowSearch( Bool pathFound ); ///< Show all cells touched in the last search - static LocomotorSurfaceTypeMask validLocomotorSurfacesForCellType(PathfindCell::CellType t); - - void checkChangeLayers(PathfindCell *parentCell); - - bool checkCellOutsideExtents(ICoord2D& cell); - -#if defined(RTS_DEBUG) - void doDebugIcons() ; -#endif - -private: - /// This uses WAY too much memory. Should at least be array of pointers to cells w/ many fewer cells - PathfindCell *m_blockOfMapCells; ///< Pathfinding map - contains iconic representation of the map - PathfindCell **m_map; ///< Pathfinding map indexes - contains matrix indexing into the map. - IRegion2D m_extent; ///< Grid extent limits - IRegion2D m_logicalExtent; ///< Logical grid extent limits - - PathfindCellList m_openList; ///< Cells ready to be explored - PathfindCellList m_closedList; ///< Cells already explored - - Bool m_isMapReady; ///< True if all cells of map have been classified - Bool m_isTunneling; ///< True if path started in an obstacle - - Int m_frameToShowObstacles; ///< Time to redraw obstacles. For debug output. - - Coord3D debugPathPos; ///< Used for visual debugging - Path *debugPath; ///< Used for visual debugging - - ObjectID m_ignoreObstacleID; ///< Ignore the given obstacle - - PathfindZoneManager m_zoneManager; ///< Handles the pathfind zones. - - PathfindLayer m_layers[LAYER_LAST+1]; - - ObjectID m_wallPieces[MAX_WALL_PIECES]; - Int m_numWallPieces; - Real m_wallHeight; - - Int m_moveAlliesDepth; - - - // Pathfind queue - ObjectID m_queuedPathfindRequests[PATHFIND_QUEUE_LEN]; - Int m_queuePRHead; - Int m_queuePRTail; - Int m_cumulativeCellsAllocated; -}; - - -inline void Pathfinder::setIgnoreObstacleID( ObjectID objID ) -{ - m_ignoreObstacleID = objID; -} - -inline void Pathfinder::worldToGrid( const Coord3D *pos, ICoord2D *cellIndex ) -{ - cellIndex->x = REAL_TO_INT(pos->x/PATHFIND_CELL_SIZE); - cellIndex->y = REAL_TO_INT(pos->y/PATHFIND_CELL_SIZE); -} - -inline Bool Pathfinder::validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Int x, Int y ) -{ - return validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), getCell( layer, x, y ) ); -} - -inline Bool Pathfinder::validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, const Coord3D *pos ) -{ - - Int x = REAL_TO_INT(pos->x/PATHFIND_CELL_SIZE); - Int y = REAL_TO_INT(pos->y/PATHFIND_CELL_SIZE); - - return validMovementPosition( isCrusher, layer, locomotorSet, x, y ); -} - -inline const Coord3D *Pathfinder::getDebugPathPosition() -{ - return &debugPathPos; -} - -inline void Pathfinder::setDebugPathPosition( const Coord3D *pos ) -{ - debugPathPos = *pos; -} - -inline Path *Pathfinder::getDebugPath() -{ - return debugPath; -} - -inline void Pathfinder::addObjectToPathfindMap( class Object *obj ) -{ - classifyObjectFootprint( obj, true ); -} - -inline void Pathfinder::removeObjectFromPathfindMap( class Object *obj ) -{ - classifyObjectFootprint( obj, false ); -} - -inline PathfindCell *Pathfinder::getCell( PathfindLayerEnum layer, Int x, Int y ) -{ - if (x >= m_extent.lo.x && x <= m_extent.hi.x && - y >= m_extent.lo.y && y <= m_extent.hi.y) - { - PathfindCell *cell = nullptr; - if (layer > LAYER_GROUND && layer <= LAYER_LAST) - { - cell = m_layers[layer].getCell(x, y); - if (cell) - return cell; - } - return &m_map[x][y]; - } - else - { - return nullptr; - } -} - -inline PathfindCell *Pathfinder::getCell( PathfindLayerEnum layer, const Coord3D *pos ) -{ - ICoord2D cell; - Bool overflow = worldToCell( pos, &cell ); - if (overflow) return nullptr; - return getCell( layer, cell.x, cell.y ); -} - -inline PathfindCell *Pathfinder::getClippedCell( PathfindLayerEnum layer, const Coord3D *pos) -{ - ICoord2D cell; - worldToCell( pos, &cell ); - return getCell( layer, cell.x, cell.y ); -} - -inline Bool Pathfinder::worldToCell( const Coord3D *pos, ICoord2D *cell ) -{ - cell->x = REAL_TO_INT_FLOOR(pos->x/PATHFIND_CELL_SIZE); - cell->y = REAL_TO_INT_FLOOR(pos->y/PATHFIND_CELL_SIZE); - Bool overflow = false; - if (cell->x < m_extent.lo.x) {overflow = true; cell->x = m_extent.lo.x;} - if (cell->y < m_extent.lo.y) {overflow = true; cell->y = m_extent.lo.y;} - if (cell->x > m_extent.hi.x) {overflow = true; cell->x = m_extent.hi.x;} - if (cell->y > m_extent.hi.y) {overflow = true; cell->y = m_extent.hi.y;} - return overflow; -} - diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp deleted file mode 100644 index 022b56d6d75..00000000000 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ /dev/null @@ -1,11225 +0,0 @@ -/* -** Command & Conquer Generals(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// AIPathfind.cpp -// AI pathfinding system -// Author: Michael S. Booth, October 2001 -#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine - -#include "GameLogic/AIPathfind.h" - -#include "Common/PerfTimer.h" -#include "Common/Player.h" -#include "Common/CRCDebug.h" -#include "Common/GlobalData.h" -#include "Common/LatchRestore.h" -#include "Common/ThingTemplate.h" -#include "Common/ThingFactory.h" - -#include "GameClient/Line2D.h" - -#include "GameLogic/AI.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Locomotor.h" -#include "GameLogic/Module/ContainModule.h" -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#include "GameLogic/Object.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/TerrainLogic.h" -#include "GameLogic/Weapon.h" -#if RETAIL_COMPATIBLE_PATHFINDING -#include "GameClient/InGameUI.h" -#include "GameClient/GameText.h" -#include "Common/GameAudio.h" -#include "Common/MiscAudio.h" -#endif - -#include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. - -#define no_INTENSE_DEBUG - -#define DEBUG_QPF - -#ifdef INTENSE_DEBUG -#include "GameLogic/ScriptEngine.h" -#endif - -#include "Common/Xfer.h" -#include "Common/XferCRC.h" - -//------------------------------------------------------------------------------ Performance Timers -#include "Common/PerfMetrics.h" - -//------------------------------------------------------------------------------------------------- - - -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 -{ - // Input - ICoord2D cell; - PathfindLayerEnum layer; - Int radius; - Bool centerInCell; - Bool considerTransient; - LocomotorSurfaceTypeMask acceptableSurfaces; - // Output - Int allyFixedCount; - Bool enemyFixed; - Bool allyMoving; - Bool allyGoal; -}; - -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; - -constexpr const UnsignedInt PATHFIND_CELLS_PER_FRAME = 5000; // Number of cells we will search pathfinding per frame. -constexpr const UnsignedInt CELL_INFOS_TO_ALLOCATE = 30000; - -//----------------------------------------------------------------------------------- -PathNode::PathNode() : - m_nextOpti(nullptr), - m_next(nullptr), - m_prev(nullptr), - m_nextOptiDist2D(0), - m_canOptimize(false), - m_id(-1) -{ - m_nextOptiDirNorm2D.x = 0; - m_nextOptiDirNorm2D.y = 0; - m_pos.zero(); - m_layer = LAYER_INVALID; -} - -//----------------------------------------------------------------------------------- -PathNode::~PathNode() -{ -} - -//----------------------------------------------------------------------------------- -void PathNode::setNextOptimized(PathNode *node) -{ - m_nextOpti = node; - if (node) - { - m_nextOptiDirNorm2D.x = node->getPosition()->x - getPosition()->x; - m_nextOptiDirNorm2D.y = node->getPosition()->y - getPosition()->y; - m_nextOptiDist2D = m_nextOptiDirNorm2D.length(); - if (m_nextOptiDist2D == 0.0f) - { - //DEBUG_LOG(("Warning - Path Seg length == 0, adjusting. john a.")); - m_nextOptiDist2D = 0.01f; - } - m_nextOptiDirNorm2D.x /= m_nextOptiDist2D; - m_nextOptiDirNorm2D.y /= m_nextOptiDist2D; - } - else - { - m_nextOptiDist2D = 0; - } -} - -//----------------------------------------------------------------------------------- -/// given a list, prepend this node, return new list -PathNode *PathNode::prependToList( PathNode *list ) -{ - m_next = list; - if (list) - list->m_prev = this; - m_prev = nullptr; - return this; -} - -//----------------------------------------------------------------------------------- -/// given a list, append this node, return new list. slow implementation. -/// @todo optimize this -PathNode *PathNode::appendToList( PathNode *list ) -{ - if (list == nullptr) - { - m_next = nullptr; - m_prev = nullptr; - return this; - } - - PathNode *tail; - for( tail = list; tail->m_next; tail = tail->m_next ) - ; - - tail->m_next = this; - m_prev = tail; - m_next = nullptr; - - return list; -} - -//----------------------------------------------------------------------------------- -/// given a node, append new node to this. -void PathNode::append( PathNode *newNode ) -{ - newNode->m_next = this->m_next; - newNode->m_prev = this; - if (newNode->m_next) { - newNode->m_next->m_prev = newNode; - } - this->m_next = newNode; - -} - -//----------------------------------------------------------------------------------- -/** - * Compute direction vector to next node - */ -const Coord3D *PathNode::computeDirectionVector() -{ - static Coord3D dir; - - if (m_next == nullptr) - { - if (m_prev == nullptr) - { - // only one node on whole path - no direction - dir.x = 0.0f; - dir.y = 0.0f; - dir.z = 0.0f; - } - else - { - // tail node - continue prior direction - return m_prev->computeDirectionVector(); - } - } - else - { - dir.x = m_next->m_pos.x - m_pos.x; - dir.y = m_next->m_pos.y - m_pos.y; - dir.z = m_next->m_pos.z - m_pos.z; - } - - return &dir; -} - - -//----------------------------------------------------------------------------------- -Path::Path(): -m_path(nullptr), -m_pathTail(nullptr), -m_isOptimized(FALSE), -m_blockedByAlly(FALSE), -m_cpopRecentStart(nullptr), -m_cpopCountdown(MAX_CPOP), -m_cpopValid(FALSE) -{ - m_cpopIn.zero(); - m_cpopOut.distAlongPath=0; - m_cpopOut.layer = LAYER_GROUND; - m_cpopOut.posOnPath.zero(); -} - -Path::~Path() -{ - PathNode *node, *nextNode; - - // delete all of the path nodes - for( node = m_path; node; node = nextNode ) - { - nextNode = node->getNext(); - deleteInstance(node); - } -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void Path::crc( Xfer *xfer ) -{ -} - -// ------------------------------------------------------------------------------------------------ -/** Xfer Method */ -// ------------------------------------------------------------------------------------------------ -void Path::xfer( Xfer *xfer ) -{ - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - PathNode *node = m_path; - Int count = 0; - while (node) { - count++; - node = node->getNext(); - } - xfer->xferInt(&count); - - if (xfer->getXferMode() == XFER_SAVE) { - node = m_pathTail; // Write them out backwards. - while (node) { - node->m_id = count; - xfer->xferInt(&count); - Coord3D pos = *node->getPosition(); - xfer->xferCoord3D(&pos); - PathfindLayerEnum layer = node->getLayer(); - xfer->xferUser(&layer, sizeof(layer)); - Bool canOpt = node->getCanOptimize(); - xfer->xferBool(&canOpt); - Int id = -1; - if (node->getNextOptimized()) { - id = node->getNextOptimized()->m_id; - } - xfer->xferInt(&id); - count--; - node = node->getPrevious(); - } - DEBUG_ASSERTCRASH(count==0, ("Wrong data count")); - } else { - m_cpopValid = FALSE; - while (count) { - Int nodeId; - xfer->xferInt(&nodeId); - DEBUG_ASSERTCRASH(nodeId==count, ("Bad data")); - Coord3D pos; - xfer->xferCoord3D(&pos); - PathfindLayerEnum layer; - xfer->xferUser(&layer, sizeof(layer)); - Bool canOpt; - xfer->xferBool(&canOpt); - Int optID = -1; - xfer->xferInt(&optID); - PathNode *node = newInstance(PathNode); - node->m_id = nodeId; - node->setPosition(&pos); - node->setLayer(layer); - node->setCanOptimize(canOpt); - PathNode *optNode = nullptr; - if (optID > 0) { - optNode = m_path; - while (optNode && optNode->m_id != optID) { - optNode = optNode->getNext(); - } - DEBUG_ASSERTCRASH (optNode && optNode->m_id == optID, ("Could not find optimized link.")); - } - m_path = node->prependToList(m_path); - if (m_pathTail == nullptr) - m_pathTail = node; - if (optNode) { - node->setNextOptimized(optNode); - } - count--; - } - } - - xfer->xferBool(&m_isOptimized); - Int obsolete1 = 0; - xfer->xferInt(&obsolete1); - UnsignedInt obsolete2; - xfer->xferUnsignedInt(&obsolete2); - xfer->xferBool(&m_blockedByAlly); - - -#if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI == AI_DEBUG_PATHS) - { - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - RGBColor color; - color.blue = 0; - color.red = color.green = 1; - Coord3D pos; - addIcon(nullptr, 0, 0, color); // erase feedback. - for( PathNode *node = getFirstNode(); node; node = node->getNext() ) - { - - // create objects to show path - they decay - - pos = *node->getPosition(); - addIcon(&pos, PATHFIND_CELL_SIZE_F*.25f, 200, color); - } - - // show optimized path - for( node = getFirstNode(); node; node = node->getNextOptimized() ) - { - pos = *node->getPosition(); - addIcon(&pos, PATHFIND_CELL_SIZE_F*.8f, 200, color); - } - TheAI->pathfinder()->setDebugPath(this); - } -#endif -} - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void Path::loadPostProcess() -{ -} - -/** - * Create a new node at the head of the path - */ -void Path::prependNode( const Coord3D *pos, PathfindLayerEnum layer ) -{ - PathNode *node = newInstance(PathNode); - - node->setPosition( pos ); - node->setLayer(layer); - - m_path = node->prependToList( m_path ); - - if (m_pathTail == nullptr) - m_pathTail = node; - - m_isOptimized = false; - -#ifdef CPOP_STARTS_FROM_PREV_SEG - m_cpopRecentStart = nullptr; -#endif -} - -/** - * Create a new node at the tail of the path - */ -void Path::appendNode( const Coord3D *pos, PathfindLayerEnum layer ) -{ - if (m_isOptimized && m_pathTail) - { - /* Check for duplicates. */ - if (pos->x == m_pathTail->getPosition()->x && pos->y == m_pathTail->getPosition()->y) { - DEBUG_LOG(("Warning - Path Seg length == 0, ignoring. john a.")); - return; - } - } - PathNode *node = newInstance(PathNode); - - node->setPosition( pos ); - node->setLayer(layer); - - m_path = node->appendToList( m_path ); - - if (m_isOptimized && m_pathTail) - { - m_pathTail->setNextOptimized(node); - } - - m_pathTail = node; - -#ifdef CPOP_STARTS_FROM_PREV_SEG - m_cpopRecentStart = nullptr; -#endif -} -/** - * Create a new node at the tail of the path - */ -void Path::updateLastNode( const Coord3D *pos ) -{ - PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(pos); - if (m_pathTail) { - m_pathTail->setPosition(pos); - m_pathTail->setLayer(layer); - } - if (m_isOptimized && m_pathTail) - { - PathNode *node = m_path; - while(node && node->getNextOptimized() != m_pathTail) { - node = node->getNextOptimized(); - } - if (node && node->getNextOptimized() == m_pathTail) { - node->setNextOptimized(m_pathTail); - } - } -} - -/** - * Optimize the path by checking line of sight - */ -void Path::optimize( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, Bool blocked ) -{ - PathNode *node, *anchor; - - // start with first node in the path - anchor = getFirstNode(); - - Bool firstNode = true; - PathfindLayerEnum firstLayer = anchor->getLayer(); - - // backwards. - - // - // For each node in the path, check LOS from last node in path, working forward. - // When a clear LOS is found, keep the resulting straight line segment. - // - while( anchor != getLastNode() ) - { - // find the farthest node in the path that has a clear line-of-sight to this anchor - Bool optimizedSegment = false; - PathfindLayerEnum layer = anchor->getLayer(); - PathfindLayerEnum curLayer = anchor->getLayer(); - Int count = 0; - const Int ALLOWED_STEPS = 3; // we can optimize 3 steps to or from a bridge. Otherwise, we need to insert a point. jba. - for (node = anchor->getNext(); node->getNext(); node=node->getNext()) { - count++; - if (curLayer==LAYER_GROUND) { - if (node->getLayer() != curLayer) { - layer = node->getLayer(); - curLayer = layer; - if (count > ALLOWED_STEPS) break; - } - } else { - if (node->getNext()->getLayer() != curLayer) { - if (count > ALLOWED_STEPS) break; - } - } - curLayer = node->getLayer(); - if (node->getCanOptimize()==false) { - break; - } - } - if (firstNode) { - layer = firstLayer; - firstNode = false; - } - //PathfindLayerEnum curLayer = LAYER_GROUND; - for( ; node != anchor; node = node->getPrevious() ) - { - Bool isPassable = false; - //CRCDEBUG_LOG(("Path::optimize() calling isLinePassable()")); - if (TheAI->pathfinder()->isLinePassable( obj, acceptableSurfaces, layer, *anchor->getPosition(), - *node->getPosition(), blocked, false)) - { - isPassable = true; - } - PathfindCell* cell = TheAI->pathfinder()->getCell( layer, node->getPosition()); - if (cell && cell->getType()==PathfindCell::CELL_CLIFF && !cell->getPinched()) { - isPassable = true; - } - // Horizontal, diagonal, and vertical steps are passable. - if (!isPassable) { - Int dx = node->getPosition()->x - anchor->getPosition()->x; - Int dy = node->getPosition()->y - anchor->getPosition()->y; - Bool mightBePassable = false; - if (IABS(dx)==PATHFIND_CELL_SIZE && IABS(dy)==PATHFIND_CELL_SIZE) { - isPassable = true; - } - PathNode *tmpNode; - if (dx==0) { - mightBePassable = true; - for (tmpNode = node->getPrevious(); tmpNode && tmpNode != anchor; tmpNode = tmpNode->getPrevious()) { - dx = tmpNode->getNext()->getPosition()->x - tmpNode->getPosition()->x; - if (dx!=0) mightBePassable = false; - } - } - if (dy==0) { - mightBePassable = true; - for (tmpNode = node->getPrevious(); tmpNode && tmpNode != anchor; tmpNode = tmpNode->getPrevious()) { - dy = tmpNode->getNext()->getPosition()->y - tmpNode->getPosition()->y; - if (dy!=0) mightBePassable = false; - } - } - if (dx == dy) { - mightBePassable = true; - for (tmpNode = node->getPrevious(); tmpNode && tmpNode != anchor; tmpNode = tmpNode->getPrevious()) { - dx = tmpNode->getNext()->getPosition()->x - tmpNode->getPosition()->x; - dy = tmpNode->getNext()->getPosition()->y - tmpNode->getPosition()->y; - if (dy!=dx) mightBePassable = false; - } - } - if (dx == -dy) { - mightBePassable = true; - for (tmpNode = node->getPrevious(); tmpNode && tmpNode != anchor; tmpNode = tmpNode->getPrevious()) { - dx = tmpNode->getNext()->getPosition()->x - tmpNode->getPosition()->x; - dy = tmpNode->getNext()->getPosition()->y - tmpNode->getPosition()->y; - if (dy!=-dx) mightBePassable = false; - } - } - if (mightBePassable) { - isPassable = true; - } - } - if (isPassable) - { - // anchor can directly see this node, make it next in the optimized path - anchor->setNextOptimized( node ); - anchor = node; - optimizedSegment = true; - break; - } - } - - if (optimizedSegment == false) - { - // for some reason, there is no clear LOS between the anchor node and the very next node - anchor->setNextOptimized( anchor->getNext() ); - anchor = anchor->getNext(); - } - } - - // the path has been optimized - m_isOptimized = true; -} - -/** - * Optimize the path by checking line of sight - */ -void Path::optimizeGroundPath( Bool crusher, Int pathDiameter ) -{ - PathNode *node, *anchor; - - // start with first node in the path - anchor = getFirstNode(); - - // - // For each node in the path, check LOS from last node in path, working forward. - // When a clear LOS is found, keep the resulting straight line segment. - // - while( anchor != getLastNode() ) - { - // find the farthest node in the path that has a clear line-of-sight to this anchor - Bool optimizedSegment = false; - PathfindLayerEnum layer = anchor->getLayer(); - PathfindLayerEnum curLayer = anchor->getLayer(); - Int count = 0; - const Int ALLOWED_STEPS = 3; // we can optimize 3 steps to or from a bridge. Otherwise, we need to insert a point. jba. - for (node = anchor->getNext(); node->getNext(); node=node->getNext()) { - count++; - if (curLayer==LAYER_GROUND) { - if (node->getLayer() != curLayer) { - layer = node->getLayer(); - curLayer = layer; - if (count > ALLOWED_STEPS) break; - } - } else { - if (node->getNext()->getLayer() != curLayer) { - if (count > ALLOWED_STEPS) break; - } - } - curLayer = node->getLayer(); - } - - // find the farthest node in the path that has a clear line-of-sight to this anchor - for( ; node != anchor; node = node->getPrevious() ) - { - Bool isPassable = false; - //CRCDEBUG_LOG(("Path::optimize() calling isLinePassable()")); - if (TheAI->pathfinder()->isGroundPathPassable( crusher, *anchor->getPosition(), layer, - *node->getPosition(), pathDiameter)) - { - isPassable = true; - } - // Horizontal, diagonal, and vertical steps are passable. - if (!isPassable) { - Int dx = node->getPosition()->x - anchor->getPosition()->x; - Int dy = node->getPosition()->y - anchor->getPosition()->y; - Bool mightBePassable = false; - PathNode *tmpNode; - if (dx==0) { - mightBePassable = true; - for (tmpNode = node->getPrevious(); tmpNode && tmpNode != anchor; tmpNode = tmpNode->getPrevious()) { - dx = tmpNode->getNext()->getPosition()->x - tmpNode->getPosition()->x; - if (dx!=0) mightBePassable = false; - } - } - if (dy==0) { - mightBePassable = true; - for (tmpNode = node->getPrevious(); tmpNode && tmpNode != anchor; tmpNode = tmpNode->getPrevious()) { - dy = tmpNode->getNext()->getPosition()->y - tmpNode->getPosition()->y; - if (dy!=0) mightBePassable = false; - } - } - if (dx == dy) { - mightBePassable = true; - for (tmpNode = node->getPrevious(); tmpNode && tmpNode != anchor; tmpNode = tmpNode->getPrevious()) { - dx = tmpNode->getNext()->getPosition()->x - tmpNode->getPosition()->x; - dy = tmpNode->getNext()->getPosition()->y - tmpNode->getPosition()->y; - if (dy!=dx) mightBePassable = false; - } - } - if (dx == -dy) { - mightBePassable = true; - for (tmpNode = node->getPrevious(); tmpNode && tmpNode != anchor; tmpNode = tmpNode->getPrevious()) { - dx = tmpNode->getNext()->getPosition()->x - tmpNode->getPosition()->x; - dy = tmpNode->getNext()->getPosition()->y - tmpNode->getPosition()->y; - if (dy!=-dx) mightBePassable = false; - } - } - if (mightBePassable) { - isPassable = true; - } - } - if (isPassable) - { - // anchor can directly see this node, make it next in the optimized path - anchor->setNextOptimized( node ); - anchor = node; - optimizedSegment = true; - break; - } - } - - if (optimizedSegment == false) - { - // for some reason, there is no clear LOS between the anchor node and the very next node - anchor->setNextOptimized( anchor->getNext() ); - anchor = anchor->getNext(); - } - } - - // Remove jig/jogs :) jba. - for (anchor=getFirstNode(); anchor!=nullptr; anchor=anchor->getNextOptimized()) { - node = anchor->getNextOptimized(); - if (node && node->getNextOptimized()) { - Real dx = node->getPosition()->x - anchor->getPosition()->x; - Real dy = node->getPosition()->y - anchor->getPosition()->y; - // If the x & y offsets are less than 2 pathfind cells, kill it. - if (dx*dx+dy*dy < sqr(PATHFIND_CELL_SIZE_F)*3.9f) { - anchor->setNextOptimized(node->getNextOptimized()); - } - } - } - - // the path has been optimized - m_isOptimized = true; -} - -inline Bool isReallyClose(const Coord3D& a, const Coord3D& b) -{ - const Real CLOSE_ENOUGH = 0.1f; - return - fabs(a.x-b.x) <= CLOSE_ENOUGH && - fabs(a.y-b.y) <= CLOSE_ENOUGH && - fabs(a.z-b.z) <= CLOSE_ENOUGH; -} - -/** - * Given a location, return the closest position on the path. - * If 'allowBacktrack' is true, the entire path is considered. - * If it is false, the point computed cannot be prior to previously returned non-backtracking points on this path. - * Because the path "knows" the direction of travel, it will "lead" the given position a bit - * to ensure the path is followed in the intended direction. - * - * Note: The path cleanup does not take into account rolling terrain, so we can end up with - * these situations: - * - * B - * ###### - * ########## - * A-##----------##---C - * ####################### - * - * - * When an agent gets to B, he seems far off of the path, but it really not. - * There are similar problems with valleys. - * - * Since agents track the closest path, if a high hill gets close to the underside of - * a bridge, an agent may 'jump' to the higher path. This must be avoided in maps. - * - * return along-path distance to the end will be returned as function result - */ -void Path::computePointOnPath( - const Object* obj, - const LocomotorSet& locomotorSet, - const Coord3D& pos, - ClosestPointOnPathInfo& out -) -{ - CRCDEBUG_LOG(("Path::computePointOnPath() for %s", DebugDescribeObject(obj).str())); - - out.layer = LAYER_GROUND; - out.posOnPath.zero(); - out.distAlongPath = 0; - - if (m_path == nullptr) - { - m_cpopValid = false; - return; - } - out.layer = m_path->getLayer(); - - if (m_cpopValid && m_cpopCountdown>0 && isReallyClose(pos, m_cpopIn)) - { - out = m_cpopOut; - m_cpopCountdown--; - CRCDEBUG_LOG(("Path::computePointOnPath() end because we're really close")); - return; - } - m_cpopCountdown = MAX_CPOP; - - // default pathPos to end of the path - out.posOnPath = *getLastNode()->getPosition(); - - const PathNode* closeNode = nullptr; - Coord2D toPos; - Real closeDistSqr = 99999999.9f; - Real totalPathLength = 0.0f; - Real lengthAlongPathToPos = 0.0f; - - // - // Find the closest segment of the path - // -#ifdef CPOP_STARTS_FROM_PREV_SEG - const PathNode* prevNode = m_cpopRecentStart; - if (prevNode == nullptr) - prevNode = m_path; -#else - const PathNode* prevNode = m_path; -#endif - Coord2D segmentDirNorm; - Real segmentLength; - - // note that the seg dir and len returned by this is the dist & vec from 'prevNode' to 'node' - for ( const PathNode* node = prevNode->getNextOptimized(&segmentDirNorm, &segmentLength); - node != nullptr; - node = node->getNextOptimized(&segmentDirNorm, &segmentLength) ) - { - const Coord3D* prevNodePos = prevNode->getPosition(); - const Coord3D* nodePos = node->getPosition(); - - // compute vector from start of segment to pos - toPos.x = pos.x - prevNodePos->x; - toPos.y = pos.y - prevNodePos->y; - - // compute distance projection of 'toPos' onto segment - Real alongPathDist = segmentDirNorm.x * toPos.x + segmentDirNorm.y * toPos.y; - - Coord3D pointOnPath; - if (alongPathDist < 0.0f) - { - // projected point is before start of segment, use starting point - alongPathDist = 0.0f; - pointOnPath = *prevNodePos; - } - else if (alongPathDist > segmentLength) - { - // projected point is beyond end of segment, use end point - if (node->getNextOptimized() == nullptr) - { - alongPathDist = segmentLength; - pointOnPath = *nodePos; - } - else - { - // beyond the end of this segment, skip this segment - // if bend is sharp, start of next segment will grab this point - // if bend is gradual, the point will project into the next segment - totalPathLength += segmentLength; - prevNode = node; - continue; - } - } - else - { - // projected point is on this segment, compute it - pointOnPath.x = prevNodePos->x + alongPathDist * segmentDirNorm.x; - pointOnPath.y = prevNodePos->y + alongPathDist * segmentDirNorm.y; - pointOnPath.z = 0; - } - - // compute distance to point on path, and track the closest we've found so far - Coord2D offset; - offset.x = pos.x - pointOnPath.x; - offset.y = pos.y - pointOnPath.y; - - Real offsetDistSqr = offset.x*offset.x + offset.y*offset.y; - if (offsetDistSqr < closeDistSqr) - { - closeDistSqr = offsetDistSqr; - closeNode = prevNode; - out.posOnPath = pointOnPath; - - lengthAlongPathToPos = totalPathLength + alongPathDist; - } - - // add this segment's length to find total path length - /// @todo Precompute this and store in path - totalPathLength += segmentLength; - prevNode = node; - DUMPCOORD3D(&pointOnPath); - } - -#ifdef CPOP_STARTS_FROM_PREV_SEG - m_cpopRecentStart = closeNode; -#endif - - // - // Compute the goal movement position for this agent - // - if (closeNode && closeNode->getNextOptimized()) - { - // note that the seg dir and len returned by this is the dist & vec from 'closeNode' to 'closeNext' - const PathNode* closeNext = closeNode->getNextOptimized(&segmentDirNorm, &segmentLength); - const Coord3D* nextNodePos = closeNext->getPosition(); - const Coord3D* closeNodePos = closeNode->getPosition(); - - const PathNode* closePrev = closeNode->getPrevious(); - if (closePrev && closePrev->getLayer() > LAYER_GROUND) - { - out.layer = closeNode->getLayer(); - } - if (closeNode->getLayer() > LAYER_GROUND) - { - out.layer = closeNode->getLayer(); - } - - if (closeNext->getLayer() > LAYER_GROUND) - { - out.layer = closeNext->getLayer(); - } - - // compute vector from start of segment to pos - toPos.x = pos.x - closeNodePos->x; - toPos.y = pos.y - closeNodePos->y; - - // compute distance projection of 'toPos' onto segment - Real alongPathDist = segmentDirNorm.x * toPos.x + segmentDirNorm.y * toPos.y; - - // we know this is the closest segment, so don't allow farther back than the start node - if (alongPathDist < 0.0f) - alongPathDist = 0.0f; - - // compute distance of point from this path segment - Real toDistSqr = sqr(toPos.x) + sqr(toPos.y); - Real offsetDistSq = toDistSqr - sqr(alongPathDist); - Real offsetDist = (offsetDistSq <= 0.0) ? 0.0 : sqrt(offsetDistSq); - - // If we are basically on the path, return the next path node as the movement goal. - // However, the farther off the path we get, the movement goal becomes closer to our - // projected position on the path. If we are very far off the path, we will move - // directly towards the nearest point on the path, and not the next path node. - const Real maxPathError = 3.0f * PATHFIND_CELL_SIZE_F; - const Real maxPathErrorInv = 1.0 / maxPathError; - Real k = offsetDist * maxPathErrorInv; - if (k > 1.0f) - k = 1.0f; - - Bool gotPos = false; - CRCDEBUG_LOG(("Path::computePointOnPath() calling isLinePassable() 1")); - if (TheAI->pathfinder()->isLinePassable( obj, locomotorSet.getValidSurfaces(), out.layer, pos, *nextNodePos, - false, true )) - { - out.posOnPath = *nextNodePos; - gotPos = true; - - Bool tryAhead = alongPathDist > segmentLength * 0.5; - if (closeNext->getCanOptimize() == false) - { - tryAhead = false; // don't go past no-opt nodes. - } - if (closeNode->getLayer() != closeNext->getLayer()) - { - tryAhead = false; // don't go past layers. - } - if (obj->getLayer()!=LAYER_GROUND) { - tryAhead = false; - } - Bool veryClose = false; - if (segmentLength-alongPathDist<1.0f) { - tryAhead = true; - veryClose = true; - } - if (tryAhead) - { - // try next segment middle. - const PathNode *next = closeNext->getNextOptimized(); - if (next) - { - Coord3D tryPos; - tryPos.x = (nextNodePos->x + next->getPosition()->x) * 0.5; - tryPos.y = (nextNodePos->y + next->getPosition()->y) * 0.5; - tryPos.z = nextNodePos->z; - CRCDEBUG_LOG(("Path::computePointOnPath() calling isLinePassable() 2")); - if (veryClose || TheAI->pathfinder()->isLinePassable( obj, locomotorSet.getValidSurfaces(), closeNext->getLayer(), pos, tryPos, false, true )) - { - gotPos = true; - out.posOnPath = tryPos; - } - } - } - } - else if (k > 0.5f) - { - Real tryDist = alongPathDist + (0.5) * (segmentLength - alongPathDist); - - // projected point is on this segment, compute it - out.posOnPath.x = closeNodePos->x + tryDist * segmentDirNorm.x; - out.posOnPath.y = closeNodePos->y + tryDist * segmentDirNorm.y; - out.posOnPath.z = closeNodePos->z; - - CRCDEBUG_LOG(("Path::computePointOnPath() calling isLinePassable() 3")); - if (TheAI->pathfinder()->isLinePassable( obj, locomotorSet.getValidSurfaces(), out.layer, pos, out.posOnPath, false, true )) - { - k = 0.5f; - gotPos = true; - } - } - - // if we are on the path (k == 0), then alongPathDist == segmentLength - // if we are way off the path (k == 1), then alongPathDist is unchanged, and it projection of actual pos - alongPathDist += (1.0f - k) * (segmentLength - alongPathDist); - - if (!gotPos) - { - if (alongPathDist > segmentLength) - { - alongPathDist = segmentLength; - out.posOnPath = *nextNodePos; - } - else - { - // projected point is on this segment, compute it - out.posOnPath.x = closeNodePos->x + alongPathDist * segmentDirNorm.x; - out.posOnPath.y = closeNodePos->y + alongPathDist * segmentDirNorm.y; - out.posOnPath.z = closeNodePos->z; - Real dx = fabs(pos.x - out.posOnPath.x); - Real dy = fabs(pos.y - out.posOnPath.y); - if (dx<1 && dy<1 && closeNode->getNextOptimized() && closeNode->getNextOptimized()->getNextOptimized()) { - out.posOnPath = *closeNode->getNextOptimized()->getNextOptimized()->getPosition(); - } - } - } - } - - TheAI->pathfinder()->setDebugPathPosition( &out.posOnPath ); - - out.distAlongPath = totalPathLength - lengthAlongPathToPos; - - Coord3D delta; - delta.x = out.posOnPath.x - pos.x; - delta.y = out.posOnPath.y - pos.y; - delta.z = 0; - Real lenDelta = delta.length(); - if (lenDelta > out.distAlongPath && out.distAlongPath > PATHFIND_CLOSE_ENOUGH) - { - out.distAlongPath = lenDelta; - } - - m_cpopIn = pos; - m_cpopOut = out; - m_cpopValid = true; - CRCDEBUG_LOG(("Path::computePointOnPath() end")); - -} - - -/** - Given a position, computes the distance to the goal. Returns 0 if we are past the goal. - Returns the goal position in goalPos. This is intended for use with flying paths, that go - directly to the goal and don't consider obstacles. jba. - */ -Real Path::computeFlightDistToGoal( const Coord3D *pos, Coord3D& goalPos ) -{ - if (m_path == nullptr) - { - goalPos.x = 0.0f; - goalPos.y = 0.0f; - goalPos.z = 0.0f; - return 0.0f; - } - const PathNode *curNode = getFirstNode(); - if (m_cpopRecentStart) { - curNode = m_cpopRecentStart; - } else { - m_cpopRecentStart = curNode; - } - const PathNode *nextNode = curNode->getNextOptimized(); - goalPos = *curNode->getPosition(); - Real distance = 0; - Bool useNext = true; - while (nextNode) { - - if (useNext) { - goalPos = *nextNode->getPosition(); - } - - Coord3D startPos = *curNode->getPosition(); - Coord3D endPos = *nextNode->getPosition(); - - Coord2D posToGoalVector; - // posToGoalVector is pos to goalPos vector. - posToGoalVector.x = endPos.x - pos->x; - posToGoalVector.y = endPos.y - pos->y; - - // pathVector is the startPos to goal pos vector. - Coord2D pathVector; - pathVector.x = endPos.x - startPos.x; - pathVector.y = endPos.y - startPos.y; - - // Normalize pathVector - pathVector.normalize(); - - // Dot product is the posToGoal vector projected onto the path vector. - Real dotProduct = posToGoalVector.x*pathVector.x + posToGoalVector.y*pathVector.y; - if (dotProduct>=0) { - distance += dotProduct; - useNext = false; - } else if (useNext) { - m_cpopRecentStart = nextNode; - } - curNode = nextNode; - nextNode = curNode->getNextOptimized(); - } - return distance; - -} -//----------------------------------------------------------------------------------- - -PathfindCellInfo *PathfindCellInfo::s_infoArray = nullptr; -PathfindCellInfo *PathfindCellInfo::s_firstFree = nullptr; - -#if RETAIL_COMPATIBLE_PATHFINDING -// TheSuperHackers @info This variable is here so the code will run down the retail compatible path till a failure mode is hit -// The pathfinding will then switch over to the corrected pathfinding code for SH clients -Bool s_useFixedPathfinding = false; -Bool s_forceCleanCells = false; - -void PathfindCellInfo::forceCleanPathFindCellInfos() -{ - for (Int i = 0; i < CELL_INFOS_TO_ALLOCATE - 1; i++) { - s_infoArray[i].m_nextOpen = nullptr; - s_infoArray[i].m_prevOpen = nullptr; - s_infoArray[i].m_open = FALSE; - s_infoArray[i].m_closed = FALSE; - } -} - -void Pathfinder::forceCleanCells() -{ - UnicodeString pathfinderFailoverMessage = TheGameText->FETCH_OR_SUBSTITUTE("GUI:PathfindingCrashPrevented", L"A pathfinding crash was prevented, now switching to the crash fixed pathfinding."); - TheInGameUI->message(pathfinderFailoverMessage); - - TheAudio->addAudioEvent(&TheAudio->getMiscAudio()->m_allCheerSound); - - PathfindCellInfo::forceCleanPathFindCellInfos(); - m_openList.reset(); - m_closedList.reset(); - - for (int j = 0; j <= m_extent.hi.y; ++j) { - for (int i = 0; i <= m_extent.hi.x; ++i) { -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - // TheSuperHackers @bugfix Mauller/DrGoldFish 20/01/2026 when pathfinding resources cannot be allocated to a pathfindCell, - // The function to remove an obstacle returns early and PathfindCells remain flagged as obstacles. - // We need to make sure to reset pathfindCells with a set m_obstacleID and no m_info. - // The use of PathfindCellInfo data for obstacle handling also exausted them resulting in pathfinding lockups. - if (m_map[i][j].isObstructionInvalid()) { - m_map[i][j].clearObstruction(); - } -#endif - if (m_map[i][j].hasInfo()) { - m_map[i][j].releaseInfo(); - } - } - } -} -#endif - -/** - * Allocates a pool of pathfind cell infos. - */ -void PathfindCellInfo::allocateCellInfos() -{ - releaseCellInfos(); - s_infoArray = MSGNEW("PathfindCellInfo") PathfindCellInfo[CELL_INFOS_TO_ALLOCATE]; // pool[]ify - s_infoArray[CELL_INFOS_TO_ALLOCATE-1].m_pathParent = nullptr; - s_infoArray[CELL_INFOS_TO_ALLOCATE-1].m_isFree = true; - s_firstFree = s_infoArray; - for (Int i=0; im_isFree, ("Should be freed.")); - s_firstFree = s_firstFree->m_pathParent; - } - DEBUG_ASSERTCRASH(count==CELL_INFOS_TO_ALLOCATE, ("Error - Allocated cellinfos.")); - delete[] s_infoArray; - s_infoArray = nullptr; - s_firstFree = nullptr; -} - -/** - * Gets a pathfindcellinfo. - */ -PathfindCellInfo *PathfindCellInfo::getACellInfo(PathfindCell *cell,const ICoord2D &pos) -{ - PathfindCellInfo *info = s_firstFree; - if (s_firstFree) { - DEBUG_ASSERTCRASH(s_firstFree->m_isFree, ("Should be freed.")); - s_firstFree = s_firstFree->m_pathParent; - info->m_isFree = false; // Just allocated it. - info->m_cell = cell; - info->m_pos = pos; - - info->m_nextOpen = nullptr; - info->m_prevOpen = nullptr; - info->m_pathParent = nullptr; - info->m_costSoFar = 0; - info->m_totalCost = 0; - info->m_open = 0; - info->m_closed = 0; - info->m_obstacleID = INVALID_ID; - info->m_goalUnitID = INVALID_ID; - info->m_posUnitID = INVALID_ID; - info->m_goalAircraftID = INVALID_ID; - info->m_obstacleIsFence = false; - info->m_obstacleIsTransparent = false; - info->m_blockedByAlly = false; - } - return info; -} - -/** - * Returns a pathfindcellinfo. - */ -void PathfindCellInfo::releaseACellInfo(PathfindCellInfo *theInfo) -{ - DEBUG_ASSERTCRASH(!theInfo->m_isFree, ("Shouldn't be free.")); - //@ todo -fix this assert on usa04. jba. - //DEBUG_ASSERTCRASH(theInfo->m_obstacleID==0, ("Shouldn't be obstacle.")); - theInfo->m_pathParent = s_firstFree; - s_firstFree = theInfo; - s_firstFree->m_isFree = true; -} - -//----------------------------------------------------------------------------------- - -/** - * Constructor - */ -PathfindCell::PathfindCell() :m_info(nullptr) -{ - reset(); -} - -/** - * Destructor - */ -PathfindCell::~PathfindCell() -{ - if (m_info) PathfindCellInfo::releaseACellInfo(m_info); - m_info = nullptr; - static Bool warn = true; - if (warn) { - warn = false; - DEBUG_LOG( ("PathfindCell::~PathfindCell m_info Allocated.")); - } -} - -/** - * Reset the cell to default values - */ -void PathfindCell::reset() -{ - m_type = PathfindCell::CELL_CLEAR; - m_flags = PathfindCell::NO_UNITS; - m_zone = 0; - m_aircraftGoal = false; - m_pinched = false; - if (m_info) { - m_info->m_obstacleID = INVALID_ID; - PathfindCellInfo::releaseACellInfo(m_info); - m_info = nullptr; - } - m_obstacleID = INVALID_ID; - m_blockedByAlly = false; - m_obstacleIsFence = false; - m_obstacleIsTransparent = false; - - m_connectsToLayer = LAYER_INVALID; - m_layer = LAYER_GROUND; - -} - -/** - * Reset the pathfinding values in the cell. - */ -Bool PathfindCell::startPathfind( PathfindCell *goalCell ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - m_info->m_nextOpen = nullptr; - m_info->m_prevOpen = nullptr; - m_info->m_pathParent = nullptr; - m_info->m_costSoFar = 0; // start node, no cost to get here - m_info->m_totalCost = 0; - if (goalCell) { - m_info->m_totalCost = costToGoal( goalCell ); - } -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_info->m_open = TRUE; - } else -#endif - { - m_info->m_open = FALSE; - } - m_info->m_closed = FALSE; - return true; -} - -/** - * Set the blocked by ally flag on the pathfind cell info. - */ -inline Bool PathfindCell::isBlockedByAlly() const -{ -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - return m_blockedByAlly; - } - - return m_info->m_blockedByAlly; -#else - return m_blockedByAlly; -#endif -} - -inline void PathfindCell::setBlockedByAlly(Bool blocked) -{ -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - m_blockedByAlly = (blocked != 0); - return; - } - - m_info->m_blockedByAlly = (blocked != 0); -#else - m_blockedByAlly = (blocked != 0); -#endif -} - -/** - * Set the parent pointer. - */ -void PathfindCell::setParentCell( PathfindCell* parent ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - m_info->m_pathParent = parent->m_info; - Int dx = m_info->m_pos.x - parent->m_info->m_pos.x; - Int dy = m_info->m_pos.y - parent->m_info->m_pos.y; - if (dx<-1 || dx>1 || dy<-1 || dy>1) { - DEBUG_CRASH(("Invalid parent index.")); - } -} - -/** - * Set the parent pointer. - */ -void PathfindCell::setParentCellHierarchical( PathfindCell* parent ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - m_info->m_pathParent = parent->m_info; -} - -/** - * Reset the parent cell. - */ -void PathfindCell::clearParentCell( ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - m_info->m_pathParent = nullptr; -} - - -/** - * Allocates an info record for a cell. - */ -Bool PathfindCell::allocateInfo( const ICoord2D &pos ) -{ - if (!m_info) { - m_info = PathfindCellInfo::getACellInfo(this, pos); - return (m_info != nullptr); - } - return true; -} - -/** - * Releases an info record for a cell. - */ -void PathfindCell::releaseInfo() -{ - // TheSuperHackers @bugfix Mauller/SkyAero 05/06/2025 Parent cell links need clearing to prevent dangling pointers on starting points that can link them to an invalid parent cell. - // Parent cells are only cleared within Pathfinder::prependCells, so cells that do not make it onto the final path do not get their parent cell cleared. - // Cells with a special flags also do not get their PathfindCellInfo cleared and therefore can leave a parent cell set on a starting cell. -#if RETAIL_COMPATIBLE_PATHFINDING - if (s_useFixedPathfinding) -#endif - { - if (m_info) { - m_info->m_pathParent = nullptr; - } - } - - if (m_type == PathfindCell::CELL_OBSTACLE || m_flags != NO_UNITS || m_aircraftGoal) { - return; - } - - if (!m_info) { - return; - } - - DEBUG_ASSERTCRASH(m_info->m_prevOpen==nullptr && m_info->m_nextOpen==nullptr, ("Shouldn't be linked.")); - DEBUG_ASSERTCRASH(m_info->m_open==0 && m_info->m_closed==0, ("Shouldn't be linked.")); - DEBUG_ASSERTCRASH(m_info->m_goalUnitID==INVALID_ID && m_info->m_posUnitID==INVALID_ID, ("Shouldn't be occupied.")); - DEBUG_ASSERTCRASH(m_info->m_goalAircraftID==INVALID_ID , ("Shouldn't be occupied by aircraft.")); - if (m_info->m_prevOpen || m_info->m_nextOpen || m_info->m_open || m_info->m_closed) { - // Bad release. Skip for now, better leak than crash. jba. - return; - } - - PathfindCellInfo::releaseACellInfo(m_info); - m_info = nullptr; - -} - -/** - * Sets the goal unit into the info record for a cell. - */ -void PathfindCell::setGoalUnit(ObjectID unitID, const ICoord2D &pos ) -{ - if (unitID==INVALID_ID) { - // removing goal. - if (m_info) { - m_info->m_goalUnitID = INVALID_ID; - if (m_info->m_posUnitID == INVALID_ID) { - // No units here. - DEBUG_ASSERTCRASH(m_flags==UNIT_GOAL, ("Bad flags.")); - m_flags = NO_UNITS; - releaseInfo(); - } else{ - m_flags = UNIT_PRESENT_MOVING; - } - } else { - DEBUG_ASSERTCRASH(m_flags == NO_UNITS, ("Bad flags.")); - } - } else { - // adding goal. - if (!m_info) { - DEBUG_ASSERTCRASH(m_flags == NO_UNITS, ("Bad flags.")); - allocateInfo(pos); - } - if (!m_info) { - DEBUG_CRASH(("Ran out of pathfind cells - fatal error!!!!! jba.")); - return; - } - m_info->m_goalUnitID = unitID; - if (unitID==m_info->m_posUnitID) { - m_flags = UNIT_PRESENT_FIXED; - } else if (m_info->m_posUnitID==INVALID_ID) { - m_flags = UNIT_GOAL; - } else { - m_flags = UNIT_GOAL_OTHER_MOVING; - } - } -} - - -/** - * Sets the goal aircraft into the info record for a cell. - */ -void PathfindCell::setGoalAircraft(ObjectID unitID, const ICoord2D &pos ) -{ - if (unitID==INVALID_ID) { - // removing goal. - if (m_info) { - m_info->m_goalAircraftID = INVALID_ID; - m_aircraftGoal = false; - releaseInfo(); - } else { - DEBUG_ASSERTCRASH(m_aircraftGoal==false, ("Bad flags.")); - } - } else { - // adding goal. - if (!m_info) { - DEBUG_ASSERTCRASH(m_aircraftGoal==false, ("Bad flags.")); - allocateInfo(pos); - } - if (!m_info) { - DEBUG_CRASH(("Ran out of pathfind cells - fatal error!!!!! jba.")); - return; - } - m_info->m_goalAircraftID = unitID; - m_aircraftGoal = true; - } -} - - -/** - * Sets the position unit into the info record for a cell. - */ -void PathfindCell::setPosUnit(ObjectID unitID, const ICoord2D &pos ) -{ - if (unitID==INVALID_ID) { - // removing position. - if (m_info) { - m_info->m_posUnitID = INVALID_ID; - if (m_info->m_goalUnitID == INVALID_ID) { - // No units here. - DEBUG_ASSERTCRASH(m_flags==UNIT_PRESENT_MOVING, ("Bad flags.")); - m_flags = NO_UNITS; - releaseInfo(); - } else { - m_flags = UNIT_GOAL; - } - } else { - DEBUG_ASSERTCRASH(m_flags == NO_UNITS, ("Bad flags.")); - } - } else { - // adding goal. - if (!m_info) { - DEBUG_ASSERTCRASH(m_flags == NO_UNITS, ("Bad flags.")); - allocateInfo(pos); - } - if (!m_info) { - DEBUG_CRASH(("Ran out of pathfind cells - fatal error!!!!! jba.")); - return; - } - if (m_info->m_goalUnitID!=INVALID_ID && (m_info->m_goalUnitID==m_info->m_posUnitID)) { - // A unit is already occupying this cell. - return; - } - m_info->m_posUnitID = unitID; - if (unitID==m_info->m_goalUnitID) { - m_flags = UNIT_PRESENT_FIXED; - } else if (m_info->m_goalUnitID==INVALID_ID) { - m_flags = UNIT_PRESENT_MOVING; - } else { - m_flags = UNIT_GOAL_OTHER_MOVING; - } - } -} - - -/** - * Return the relevant obstacle ID. - */ -inline ObjectID PathfindCell::getObstacleID() const -{ -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - return m_obstacleID; - } - - return m_info ? m_info->m_obstacleID : INVALID_ID; -#else - return m_obstacleID; -#endif -} - - -/** - * Flag this cell as an obstacle, from the given one. - * Return true if cell was flagged. - */ -Bool PathfindCell::setTypeAsObstacle( Object *obstacle, Bool isFence, const ICoord2D &pos ) -{ - if (m_type!=PathfindCell::CELL_CLEAR && m_type != PathfindCell::CELL_IMPASSABLE) { - return false; - } - - Bool isRubble = false; - if (obstacle->getBodyModule() && obstacle->getBodyModule()->getDamageState() == BODY_RUBBLE) - { - isRubble = true; - } - - if (isRubble) { - m_type = PathfindCell::CELL_RUBBLE; - m_obstacleID = INVALID_ID; - m_obstacleIsFence = false; - m_obstacleIsTransparent = false; -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - return true; - } - - if (m_info) { - m_info->m_obstacleID = INVALID_ID; - releaseInfo(); - } -#endif - return true; - } - - m_type = PathfindCell::CELL_OBSTACLE; - m_obstacleID = obstacle->getID(); - m_obstacleIsFence = isFence; - m_obstacleIsTransparent = obstacle->isKindOf(KINDOF_CAN_SEE_THROUGH_STRUCTURE); -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - // TheSuperHackers @info In retail mode we need to track orphaned cells set as obstacles so we can cleanup and failover properly - // So we always make sure to set and clear the local obstacle data on the PathfindCell regardless of retail compat or not - if (s_useFixedPathfinding) { - return true; - } - - if (!m_info) { - m_info = PathfindCellInfo::getACellInfo(this, pos); - if (!m_info) { - DEBUG_CRASH(("Not enough PathFindCellInfos in pool.")); - 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 true; -} - -/** - * Flag this cell as given type. - */ -void PathfindCell::setType( CellType type ) -{ -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - if (m_obstacleID != INVALID_ID) { - DEBUG_ASSERTCRASH(type == PathfindCell::CELL_OBSTACLE, ("Wrong type.")); - m_type = PathfindCell::CELL_OBSTACLE; - return; - } - } - - if (m_info && (m_info->m_obstacleID != INVALID_ID)) { - DEBUG_ASSERTCRASH(type==PathfindCell::CELL_OBSTACLE, ("Wrong type.")); - m_type = PathfindCell::CELL_OBSTACLE; - return; - } -#else - if (m_obstacleID != INVALID_ID) { - DEBUG_ASSERTCRASH(type == PathfindCell::CELL_OBSTACLE, ("Wrong type.")); - m_type = PathfindCell::CELL_OBSTACLE; - return; - } -#endif - m_type = type; -} - -/** - * Unflag this cell as an obstacle, from the given one. - * Return true if this cell was previously flagged as an obstacle by this object. - */ -Bool PathfindCell::removeObstacle( Object *obstacle ) -{ - if (m_type == PathfindCell::CELL_RUBBLE) { - m_type = PathfindCell::CELL_CLEAR; - } -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - if (m_obstacleID != obstacle->getID()) return false; - m_type = PathfindCell::CELL_CLEAR; - m_obstacleID = INVALID_ID; - m_obstacleIsFence = false; - m_obstacleIsTransparent = false; - return true; - } - - if (!m_info) return false; - if (m_info->m_obstacleID != obstacle->getID()) return false; - m_type = PathfindCell::CELL_CLEAR; - m_info->m_obstacleID = INVALID_ID; - releaseInfo(); - -#else - if (m_obstacleID != obstacle->getID()) return false; - m_type = PathfindCell::CELL_CLEAR; -#endif - m_obstacleID = INVALID_ID; - m_obstacleIsFence = false; - m_obstacleIsTransparent = false; - return true; -} - -/// put self on "open" list in ascending cost order, return new list -void PathfindCell::putOnSortedOpenList( PathfindCellList &list ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - DEBUG_ASSERTCRASH(m_info->m_closed == FALSE && m_info->m_open == FALSE, ("Serious error - Invalid flags. jba")); - - // mark the newCell as being on the open list - m_info->m_open = true; - m_info->m_closed = false; - - if (list.m_head == nullptr) - { - list.m_head = this; - m_info->m_prevOpen = nullptr; - m_info->m_nextOpen = nullptr; - return; - } - - // insertion sort - PathfindCell* currentCell = list.m_head; - PathfindCell* previousCell = nullptr; -#if RETAIL_COMPATIBLE_PATHFINDING - // TheSuperHackers @bugfix In the retail compatible pathfinding, on rare occasions, we get stuck in an infinite loop - // External code should pickup on the bad behaviour and cleanup properly, but we need to explicitly break out here - // The fixed pathfinding does not have this issue due to the proper cleanup of pathfindCells and their pathfindCellInfos - UnsignedInt cellCount = 0; - while (currentCell && cellCount < PATHFIND_CELLS_PER_FRAME && currentCell->m_info->m_totalCost <= m_info->m_totalCost) - { - cellCount++; -#else - while (currentCell && currentCell->m_info->m_totalCost <= m_info->m_totalCost) - { -#endif - previousCell = currentCell; - currentCell = currentCell->getNextOpen(); - } - - if (currentCell) - { - // insert just before "currentCell" - if (currentCell->m_info->m_prevOpen) - currentCell->m_info->m_prevOpen->m_nextOpen = this->m_info; - else - list.m_head = this; - - m_info->m_prevOpen = currentCell->m_info->m_prevOpen; - currentCell->m_info->m_prevOpen = this->m_info; - - m_info->m_nextOpen = currentCell->m_info; - - } - else - { - // append after "previousCell" - we are at the end of the list - previousCell->m_info->m_nextOpen = this->m_info; - m_info->m_prevOpen = previousCell->m_info; - m_info->m_nextOpen = nullptr; - } -} - -/// remove self from "open" list -void PathfindCell::removeFromOpenList( PathfindCellList &list ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - DEBUG_ASSERTCRASH(m_info->m_closed==FALSE && m_info->m_open==TRUE, ("Serious error - Invalid flags. jba")); - if (m_info->m_nextOpen) - m_info->m_nextOpen->m_prevOpen = m_info->m_prevOpen; - - if (m_info->m_prevOpen) - m_info->m_prevOpen->m_nextOpen = m_info->m_nextOpen; - else - list.m_head = getNextOpen(); - - m_info->m_open = false; - m_info->m_nextOpen = nullptr; - m_info->m_prevOpen = nullptr; - -} - -/// remove all cells from "open" list -Int PathfindCell::releaseOpenList( PathfindCellList &list ) -{ - Int count = 0; - while (list.m_head) { - count++; - DEBUG_ASSERTCRASH(list.m_head->m_info, ("Has to have info.")); - DEBUG_ASSERTCRASH(list.m_head->m_info->m_closed==FALSE && list.m_head->m_info->m_open==TRUE, ("Serious error - Invalid flags. jba")); - PathfindCell *cur = list.m_head; - PathfindCellInfo *curInfo = list.m_head->m_info; - -#if RETAIL_COMPATIBLE_PATHFINDING - // TheSuperHackers @info This is only here to catch a crash point in the retail compatible pathfinding - // One crash mode is where a cell has no PathfindCellInfo, resulting in a nullptr access and a crash. - // Therefore we signal that we need to clean the maps cells and the PathfindCellInfos - if(!curInfo && !s_useFixedPathfinding) { - s_useFixedPathfinding = true; - s_forceCleanCells = true; - return count; - } -#endif - - if (curInfo->m_nextOpen) { - list.m_head = curInfo->m_nextOpen->m_cell; - } else { - list.m_head = nullptr; - } - DEBUG_ASSERTCRASH(cur == curInfo->m_cell, ("Bad backpointer in PathfindCellInfo")); - curInfo->m_nextOpen = nullptr; - curInfo->m_prevOpen = nullptr; - curInfo->m_open = FALSE; - cur->releaseInfo(); - } - return count; -} - -/// remove all cells from "closed" list -Int PathfindCell::releaseClosedList( PathfindCellList &list ) -{ - Int count = 0; - while (list.m_head) { - count++; - DEBUG_ASSERTCRASH(list.m_head->m_info, ("Has to have info.")); - DEBUG_ASSERTCRASH(list.m_head->m_info->m_closed==TRUE && list.m_head->m_info->m_open==FALSE, ("Serious error - Invalid flags. jba")); - PathfindCell *cur = list.m_head; - PathfindCellInfo *curInfo = list.m_head->m_info; -#if RETAIL_COMPATIBLE_PATHFINDING - // TheSuperHackers @info This is only here to catch a crash point in the retail compatible pathfinding - // One crash mode is where a cell has no PathfindCellInfo, resulting in a nullptr access and a crash. - // Therefore we signal that we need to clean the maps cells and the PathfindCellInfos - if(!curInfo && !s_useFixedPathfinding) { - s_useFixedPathfinding = true; - s_forceCleanCells = true; - return count; - } -#endif - - if (curInfo->m_nextOpen) { - list.m_head = curInfo->m_nextOpen->m_cell; - } else { - list.m_head = nullptr; - } - DEBUG_ASSERTCRASH(cur == curInfo->m_cell, ("Bad backpointer in PathfindCellInfo")); - curInfo->m_nextOpen = nullptr; - curInfo->m_prevOpen = nullptr; - curInfo->m_closed = FALSE; - cur->releaseInfo(); - } - return count; -} - -/// put self on "closed" list, return new list -void PathfindCell::putOnClosedList( PathfindCellList &list ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - DEBUG_ASSERTCRASH(m_info->m_closed==FALSE && m_info->m_open==FALSE, ("Serious error - Invalid flags. jba")); - // only put on list if not already on it - if (m_info->m_closed == FALSE) - { - m_info->m_closed = FALSE; - m_info->m_closed = TRUE; - - m_info->m_prevOpen = nullptr; - m_info->m_nextOpen = list.m_head ? list.m_head->m_info : nullptr; - if (list.m_head) - list.m_head->m_info->m_prevOpen = this->m_info; - - list.m_head = this; - } - -} - -/// remove self from "closed" list -void PathfindCell::removeFromClosedList( PathfindCellList &list ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - DEBUG_ASSERTCRASH(m_info->m_closed==TRUE && m_info->m_open==FALSE, ("Serious error - Invalid flags. jba")); - if (m_info->m_nextOpen) - m_info->m_nextOpen->m_prevOpen = m_info->m_prevOpen; - - if (m_info->m_prevOpen) - m_info->m_prevOpen->m_nextOpen = m_info->m_nextOpen; - else - list.m_head = getNextOpen(); - - m_info->m_closed = false; - m_info->m_nextOpen = nullptr; - m_info->m_prevOpen = nullptr; - -} - -/** - * Return true if the given object ID is registered as an obstacle in this cell - */ -inline Bool PathfindCell::isObstaclePresent(ObjectID objID) const -{ - if (objID != INVALID_ID && (getType() == PathfindCell::CELL_OBSTACLE)) - { -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - return m_obstacleID == objID; - } - - DEBUG_ASSERTCRASH(m_info, ("Should have info to be obstacle.")); - return (m_info && m_info->m_obstacleID == objID); -#else - return m_obstacleID == objID; -#endif - } - - return false; -} - - -/** - * return true if the obstacle in the cell is KINDOF_CAN_SEE_THROUGHT_STRUCTURE - */ -inline Bool PathfindCell::isObstacleTransparent() const -{ -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - return m_obstacleIsTransparent; - } - - return m_info ? m_info->m_obstacleIsTransparent : false; -#else - return m_obstacleIsTransparent; -#endif -} - -/** - * return true if the given obstacle in the cell is a fence. - */ -inline Bool PathfindCell::isObstacleFence() const -{ -#if RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION - if (s_useFixedPathfinding) { - return m_obstacleIsFence; - } - - return m_info ? m_info->m_obstacleIsFence : false; -#else - return m_obstacleIsFence; -#endif -} - - -const Int COST_ORTHOGONAL = 10; -const Int COST_DIAGONAL = 14; -const Real COST_TO_DISTANCE_FACTOR = 1.0f/10.0f; -const Real COST_TO_DISTANCE_FACTOR_SQR = COST_TO_DISTANCE_FACTOR*COST_TO_DISTANCE_FACTOR; - -UnsignedInt PathfindCell::costToGoal( PathfindCell *goal ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - Int dx = m_info->m_pos.x - goal->getXIndex(); - Int dy = m_info->m_pos.y - goal->getYIndex(); -#define NO_REAL_DIST -#ifdef REAL_DIST - Int cost = COST_ORTHOGONAL*sqrt(dx*dx + dy*dy); -#else - if (dx<0) dx = -dx; - if (dy<0) dy = -dy; - Int cost; - if (dx>dy) { - cost= COST_ORTHOGONAL*dx + (COST_ORTHOGONAL*dy)/2; - } else { - cost= COST_ORTHOGONAL*dy + (COST_ORTHOGONAL*dx)/2; - } - -#endif - - - return cost; -} - -UnsignedInt PathfindCell::costToHierGoal( PathfindCell *goal ) -{ - 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); - return cost; -} - -UnsignedInt PathfindCell::costSoFar( PathfindCell *parent ) -{ - DEBUG_ASSERTCRASH(m_info, ("Has to have info.")); - // very first node in path - no turns, no cost - if (parent == nullptr) - return 0; - - // add in number of turns in path so far - ICoord2D prevDir; - Int cost; - - prevDir.x = parent->getXIndex() - m_info->m_pos.x; - prevDir.y = parent->getYIndex() - m_info->m_pos.y; - - // diagonal moves cost a bit more than orthogonal ones - if (prevDir.x == 0 || prevDir.y == 0) - cost = parent->getCostSoFar() + COST_ORTHOGONAL; - else - cost = parent->getCostSoFar() + COST_DIAGONAL; - if (getPinched()) { - cost += 1*COST_DIAGONAL; - } - -#if 1 - // Increase cost of turns. - Int numTurns = 0; - PathfindCell *prevCell = parent->getParentCell(); - if (prevCell) { - -#if RETAIL_COMPATIBLE_PATHFINDING - // TheSuperHackers @info this is a possible crash point in the retail pathfinding, we just prevent the crash at this point - // External code should catch the issue in another block and cleanup the pathfinding before switching to the fixed pathfinding. - if (!prevCell->hasInfo()) - { - return cost; - } -#endif - - ICoord2D dir; - dir.x = prevCell->getXIndex() - parent->getXIndex(); - dir.y = prevCell->getYIndex() - parent->getYIndex(); - - // count number of direction changes - if (dir.x != prevDir.x || dir.y != prevDir.y) - { - Int dot = dir.x * prevDir.x + dir.y * prevDir.y; - if (dot > 0) - numTurns=4; // 45 degree turn - else if (dot == 0) - numTurns = 8; // 90 degree turn - else - numTurns = 16; // 135 degree turn - } - } - - return cost + numTurns; -#else - return cost; -#endif - -} - - -inline Bool typesMatch(const PathfindCell &targetCell, const PathfindCell &sourceCell) { - PathfindCell::CellType targetType = targetCell.getType(); - PathfindCell::CellType srcType = sourceCell.getType(); - if (targetType == srcType) return true; - - return false; -} - -inline Bool waterGround(const PathfindCell &targetCell, const PathfindCell &sourceCell) { - PathfindCell::CellType targetType = targetCell.getType(); - PathfindCell::CellType srcType = sourceCell.getType(); - if ( (targetType==PathfindCell::CELL_CLEAR && - (srcType&PathfindCell::CELL_WATER ))) { - return true; - } - if ( (srcType==PathfindCell::CELL_CLEAR && - (targetType&PathfindCell::CELL_WATER ))) { - return true; - } - - return false; -} - -inline Bool groundRubble(const PathfindCell &targetCell, const PathfindCell &sourceCell) { - PathfindCell::CellType targetType = targetCell.getType(); - PathfindCell::CellType srcType = sourceCell.getType(); - if ( (targetType==PathfindCell::CELL_CLEAR && - (srcType==PathfindCell::CELL_RUBBLE ))) { - return true; - } - if ( (srcType==PathfindCell::CELL_CLEAR && - (targetType==PathfindCell::CELL_RUBBLE ))) { - return true; - } - - return false; -} - -inline Bool terrain(const PathfindCell &targetCell, const PathfindCell &sourceCell) { - Int targetType = targetCell.getType(); - Int srcType = sourceCell.getType(); - if (targetType == PathfindCell::CELL_OBSTACLE) targetType = PathfindCell::CELL_CLEAR; - if (srcType == PathfindCell::CELL_OBSTACLE) srcType = PathfindCell::CELL_CLEAR; - if (targetType==srcType) { - return true; - } - return false; -} - -inline Bool crusherGround(const PathfindCell &targetCell, const PathfindCell &sourceCell) { - Int targetType = targetCell.getType(); - Int srcType = sourceCell.getType(); - if (targetType==PathfindCell::CELL_OBSTACLE) { - if (targetCell.isObstacleFence()) { - if (srcType == PathfindCell::CELL_CLEAR) { - return true; - } - } - } - if (srcType==PathfindCell::CELL_OBSTACLE) { - if (sourceCell.isObstacleFence()) { - if (targetType == PathfindCell::CELL_CLEAR) { - return true; - } - } - } - return false; -} - -inline Bool groundCliff(const PathfindCell &targetCell, const PathfindCell &sourceCell) { - PathfindCell::CellType targetType = targetCell.getType(); - PathfindCell::CellType srcType = sourceCell.getType(); - - if ( (targetType==PathfindCell::CELL_CLIFF ) && - (srcType==PathfindCell::CELL_CLEAR) ) { - return true; - } - if ( (targetType==PathfindCell::CELL_CLEAR ) && - (srcType==PathfindCell::CELL_CLIFF) ) { - return true; - } - return false; -} - -static void __fastcall resolveBlockZones(Int srcZone, Int targetZone, zoneStorageType *zoneEquivalency, Int sizeOfZE) -{ - Int i; - // We have two zones being combined now. Keep the lower zone. - DEBUG_ASSERTCRASH(srcZone!=0 && targetZone!=0, ("Bad resolve zones .")); - if (targetZone=firstZone && sourceCell.getZone()=firstZone && sourceCell.getZone()getZone(); - if (minZone>zone) minZone=zone; - if (maxZonebounds.lo.x && map[i][j].getZone()!=map[i-1][j].getZone()) { - - if (waterGround(map[i][j], map[i-1][j])) { - applyBlockZone(map[i][j], map[i-1][j], m_groundWaterZones, m_firstZone, m_numZones); - } - if (groundRubble(map[i][j], map[i-1][j])) { - applyBlockZone(map[i][j], map[i-1][j], m_groundRubbleZones, m_firstZone, m_numZones); - } - if (groundCliff(map[i][j], map[i-1][j])) { - applyBlockZone(map[i][j], map[i-1][j], m_groundCliffZones, m_firstZone, m_numZones); - } - if (crusherGround(map[i][j], map[i-1][j])) { - applyBlockZone(map[i][j], map[i-1][j], m_crusherZones, m_firstZone, m_numZones); - } - } - if (j>bounds.lo.y && map[i][j].getZone()!=map[i][j-1].getZone()) { - if (waterGround(map[i][j],map[i][j-1])) { - applyBlockZone(map[i][j], map[i][j-1], m_groundWaterZones, m_firstZone, m_numZones); - } - if (groundRubble(map[i][j], map[i][j-1])) { - applyBlockZone(map[i][j], map[i][j-1], m_groundRubbleZones, m_firstZone, m_numZones); - } - if (groundCliff(map[i][j],map[i][j-1])) { - applyBlockZone(map[i][j], map[i][j-1], m_groundCliffZones, m_firstZone, m_numZones); - } - if (crusherGround(map[i][j], map[i][j-1])) { - applyBlockZone(map[i][j], map[i][j-1], m_crusherZones, m_firstZone, m_numZones); - } - } - DEBUG_ASSERTCRASH(map[i][j].getZone() != 0, ("Cleared the zone.")); - } - } - -} - -// -// Return the zone at this location. -// -zoneStorageType ZoneBlock::getEffectiveZone( LocomotorSurfaceTypeMask acceptableSurfaces, - Bool crusher, zoneStorageType zone) const -{ -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - if (zone==PathfindZoneManager::UNINITIALIZED_ZONE) { - return zone; - } -#endif - - if (acceptableSurfaces&LOCOMOTORSURFACE_AIR) return 1; // air is all zone 1. - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && - (acceptableSurfaces&LOCOMOTORSURFACE_WATER) && - (acceptableSurfaces&LOCOMOTORSURFACE_CLIFF)) { - // Locomotors can go on ground, water & cliff, so all is zone 1. - return 1; - } - if (m_numZones<2) { - return m_firstZone; // if we only got 1 zone, it's all the same zone. - } - DEBUG_ASSERTCRASH(zone >=m_firstZone && zone < m_firstZone+m_numZones, ("Invalid range.")); - if (zone= m_firstZone+m_numZones) { - return m_firstZone; - } - zone -= m_firstZone; - if (crusher) { - zone = m_crusherZones[zone]; - DEBUG_ASSERTCRASH(zone >=m_firstZone && zone < m_firstZone+m_numZones, ("Invalid range.")); - zone -= m_firstZone; - } - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && - (acceptableSurfaces&LOCOMOTORSURFACE_CLIFF)) { - // Locomotors can go on ground & cliff, so use the ground cliff combiner. - zone = m_groundCliffZones[zone]; - DEBUG_ASSERTCRASH(zone >=m_firstZone && zone < m_firstZone+m_numZones, ("Invalid range.")); - return zone; - } - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && - (acceptableSurfaces&LOCOMOTORSURFACE_WATER)) { - // Locomotors can go on ground & water, so use the ground water combiner. - zone = m_groundWaterZones[zone]; - DEBUG_ASSERTCRASH(zone >=m_firstZone && zone < m_firstZone+m_numZones, ("Invalid range.")); - return zone; - } - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && - (acceptableSurfaces&LOCOMOTORSURFACE_RUBBLE)) { - // Locomotors can go on ground & rubble, so use the ground rubble combiner. - zone = m_groundRubbleZones[zone]; - return zone; - } - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_CLIFF) && - (acceptableSurfaces&LOCOMOTORSURFACE_WATER)) { - // Locomotors can go on ground & cliff, so use the ground cliff combiner. - DEBUG_CRASH(("Cliff water only locomotor sets not supported yet.")); - } - - return zone+m_firstZone; -} - - -/* Allocate zone equivalency arrays large enough to hold m_maxZone entries. If the arrays are already -large enough, just return. */ -void ZoneBlock::allocateZones() -{ - if (m_zonesAllocated>m_numZones && m_groundCliffZones!=nullptr) { - return; - } - freeZones(); - - if (m_numZones==1) { - return; // we don't need any zone equivalency tables. - } - - if (m_zonesAllocated == 0) { - m_zonesAllocated = 4; - } - while (m_zonesAllocated <= m_numZones) { - m_zonesAllocated *= 2; - } - // pool[]ify - m_groundCliffZones = MSGNEW("PathfindZoneInfo") zoneStorageType [m_zonesAllocated]; - m_groundWaterZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; - m_groundRubbleZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; - m_crusherZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; -} - - -//------------------------ PathfindZoneManager ------------------------------- -PathfindZoneManager::PathfindZoneManager() : m_maxZone(0), -m_nextFrameToCalculateZones(0), -m_groundCliffZones(nullptr), -m_groundWaterZones(nullptr), -m_groundRubbleZones(nullptr), -m_terrainZones(nullptr), -m_crusherZones(nullptr), -m_hierarchicalZones(nullptr), -m_blockOfZoneBlocks(nullptr), -m_zoneBlocks(nullptr), -m_zonesAllocated(0) -{ - m_zoneBlockExtent.x = 0; - m_zoneBlockExtent.y = 0; -} - -PathfindZoneManager::~PathfindZoneManager() -{ - freeZones(); - freeBlocks(); -} - -void PathfindZoneManager::freeZones() -{ - delete [] m_groundCliffZones; - m_groundCliffZones = nullptr; - - delete [] m_groundWaterZones; - m_groundWaterZones = nullptr; - - delete [] m_groundRubbleZones; - m_groundRubbleZones = nullptr; - - delete [] m_terrainZones; - m_terrainZones = nullptr; - - delete [] m_crusherZones; - m_crusherZones = nullptr; - - delete [] m_hierarchicalZones; - m_hierarchicalZones = nullptr; - - m_zonesAllocated = 0; -} - -void PathfindZoneManager::freeBlocks() -{ - delete [] m_blockOfZoneBlocks; - m_blockOfZoneBlocks = nullptr; - - delete [] m_zoneBlocks; - m_zoneBlocks = nullptr; - - m_zoneBlockExtent.x = 0; - m_zoneBlockExtent.y = 0; -} - -/* Allocate zone equivalency arrays large enough to hold m_maxZone entries. If the arrays are already -large enough, just return. */ -void PathfindZoneManager::allocateZones() -{ - if (m_zonesAllocated>m_maxZone && m_groundCliffZones!=nullptr) { - return; - } - freeZones(); - - if (m_zonesAllocated == 0) { - m_zonesAllocated = INITIAL_ZONES; - } - while (m_zonesAllocated <= m_maxZone) { - m_zonesAllocated *= 2; - } - DEBUG_LOG(("Allocating zone tables of size %d", m_zonesAllocated)); - // pool[]ify - m_groundCliffZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; - m_groundWaterZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; - m_groundRubbleZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; - m_terrainZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; - m_crusherZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; - m_hierarchicalZones = MSGNEW("PathfindZoneInfo") zoneStorageType[m_zonesAllocated]; -} - -/* Allocate zone blocks for hierarchical pathfinding. */ -void PathfindZoneManager::allocateBlocks(const IRegion2D &globalBounds) -{ - freeBlocks(); - - m_zoneBlockExtent.x = (globalBounds.hi.x-globalBounds.lo.x+1+ZONE_BLOCK_SIZE-1)/ZONE_BLOCK_SIZE; - m_zoneBlockExtent.y = (globalBounds.hi.y-globalBounds.lo.y+1+ZONE_BLOCK_SIZE-1)/ZONE_BLOCK_SIZE; - - m_blockOfZoneBlocks = MSGNEW("PathfindZoneBlocks") ZoneBlock[(m_zoneBlockExtent.x)*(m_zoneBlockExtent.y)]; - m_zoneBlocks = MSGNEW("PathfindZoneBlocks") ZoneBlockP[m_zoneBlockExtent.x]; - Int i; - for (i=0; igetFrame(); -#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, - * you can successfully pathfind. - * If you are a multiple terrain vehicle, like amphibious transport, the lookup is a little more - * complicated. - */ -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; - __int64 endTime64,freq64; - QueryPerformanceFrequency((LARGE_INTEGER *)&freq64); - QueryPerformanceCounter((LARGE_INTEGER *)&startTime64); -#endif -#endif - - m_maxZone = 1; // we start using zone 0 as a flag. - const Int maxZones=24000; - zoneStorageType zoneEquivalency[maxZones]; - Int i, j; - for (i=0; i globalBounds.hi.x) { - bounds.hi.x = globalBounds.hi.x; - } - if (bounds.hi.y > globalBounds.hi.y) { - bounds.hi.y = globalBounds.hi.y; - } -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - 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++ ) { - PathfindCell *cell = &map[i][j]; - cell->setZone(0); - - if (i>bounds.lo.x) { - if (map[i][j].getType() == map[i-1][j].getType()) { - applyZone(map[i][j], map[i-1][j], zoneEquivalency, m_maxZone); - } - } - if (j>bounds.lo.y) { - if (map[i][j].getType() == map[i][j-1].getType()) { - applyZone(map[i][j], map[i][j-1], zoneEquivalency, m_maxZone); - } - } - if (cell->getZone()==0) { - cell->setZone(m_maxZone); - m_maxZone++; -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - 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); - } - - } - } - } - } - - Int totalZones = m_maxZone; - - // Collapse the zones into a 1,2,3... sequence, removing collapsed zones. - m_maxZone = 1; - Int collapsedZones[maxZones]; - collapsedZones[0] = 0; - for (i=1; i globalBounds.hi.x) - bounds.hi.x = globalBounds.hi.x; - - if (bounds.hi.y > globalBounds.hi.y) - bounds.hi.y = globalBounds.hi.y; -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - 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 LAYER_GROUND) && - (r_thisCell.getType() == PathfindCell::CELL_CLEAR) ) { - PathfindLayer *layer = layers + r_thisCell.getConnectLayer(); - resolveZones(r_thisCell.getZone(), layer->getZone(), m_hierarchicalZones, m_maxZone); - } - - if ( i > globalBounds.lo.x && r_thisCell.getZone() != map[i-1][j].getZone() ) { - const PathfindCell &r_leftCell = map[i-1][j]; - -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - 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)) { - 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); - } -#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); - 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); - } - - } -#endif - - } - - if (j>globalBounds.lo.y && r_thisCell.getZone()!=map[i][j-1].getZone()) { - const PathfindCell &r_topCell = map[i][j-1]; - -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - if (r_thisCell.getType() == r_topCell.getType()) { - applyZone(r_thisCell, r_topCell, m_hierarchicalZones, m_maxZone); - } - 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 - //if this is true, skip all the ones below - if (r_thisCell.getType() == r_topCell.getType()) - applyZone(r_thisCell, r_topCell, m_hierarchicalZones, m_maxZone); - 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 - - } - - } - } - - //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); - timeToUpdate = ((double)(endTime64-startTime64) / (double)(freq64)); -#endif -#endif -#if defined(RTS_DEBUG) - 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, 200, color); - } - } - } -#endif - -} - -// -// Clear the passable flags. -// -void PathfindZoneManager::clearPassableFlags() -{ Int blockX; - Int blockY; - for (blockX = 0; blockX=m_zoneBlockExtent.x) { - DEBUG_CRASH(("Invalid block.")); - return; - } - if (blockY<0 || blockY>=m_zoneBlockExtent.y) { - DEBUG_CRASH(("Invalid block.")); - return; - } - m_zoneBlocks[blockX][blockY].setPassable(passable); -} - -// -// Get the passable flag for the block at this location. -// -Bool PathfindZoneManager::isPassable(Int cellX, Int cellY) const -{ - Int blockX = cellX/ZONE_BLOCK_SIZE; - Int blockY = cellY/ZONE_BLOCK_SIZE; - - if (blockX<0 || blockX>=m_zoneBlockExtent.x) { - DEBUG_CRASH(("Invalid block.")); - return false; - } - if (blockY<0 || blockY>=m_zoneBlockExtent.y) { - DEBUG_CRASH(("Invalid block.")); - return false; - } - return m_zoneBlocks[blockX][blockY].isPassable(); -} - -// -// Get the passable flag for the block at this location. -// -Bool PathfindZoneManager::clipIsPassable(Int cellX, Int cellY) const -{ - Int blockX = cellX/ZONE_BLOCK_SIZE; - Int blockY = cellY/ZONE_BLOCK_SIZE; - - if (blockX<0 || blockX>=m_zoneBlockExtent.x) { - return false; - } - if (blockY<0 || blockY>=m_zoneBlockExtent.y) { - return false; - } - return m_zoneBlocks[blockX][blockY].isPassable(); -} - -// -// Set the bridge flag for the block at this location. -// -void PathfindZoneManager::setBridge(Int cellX, Int cellY, Bool bridge) -{ - Int blockX = cellX/ZONE_BLOCK_SIZE; - Int blockY = cellY/ZONE_BLOCK_SIZE; - - if (blockX<0 || blockX>=m_zoneBlockExtent.x) { - // DEBUG_CRASH(("Invalid block.")); Bridges can be off the playable grid, so don't crash. jba. - return; - } - if (blockY<0 || blockY>=m_zoneBlockExtent.y) { - // DEBUG_CRASH(("Invalid block.")); Bridges can be off the playable grid, so don't crash. jba. - return; - } - m_zoneBlocks[blockX][blockY].setInteractsWithBridge(bridge); -} - - -// -// Set the bridge flag for the block at this location. -// -Bool PathfindZoneManager::interactsWithBridge(Int cellX, Int cellY) const -{ - Int blockX = cellX/ZONE_BLOCK_SIZE; - Int blockY = cellY/ZONE_BLOCK_SIZE; - - if (blockX<0 || blockX>=m_zoneBlockExtent.x) { - DEBUG_CRASH(("Invalid block.")); - return false; - } - if (blockY<0 || blockY>=m_zoneBlockExtent.y) { - DEBUG_CRASH(("Invalid block.")); - return false; - } - return m_zoneBlocks[blockX][blockY].getInteractsWithBridge(); -} - - -// -// Return the zone at this location. -// -zoneStorageType PathfindZoneManager::getBlockZone(LocomotorSurfaceTypeMask acceptableSurfaces, Bool crusher,Int cellX, Int cellY, PathfindCell **map) const -{ - PathfindCell *cell = &(map[cellX][cellY]); - Int blockX = cellX/ZONE_BLOCK_SIZE; - Int blockY = cellY/ZONE_BLOCK_SIZE; - - if (blockX<0 || blockX>=m_zoneBlockExtent.x) { - DEBUG_CRASH(("Invalid block.")); - return 0; - } - if (blockY<0 || blockY>=m_zoneBlockExtent.y) { - DEBUG_CRASH(("Invalid block.")); - return 0; - } - zoneStorageType zone = m_zoneBlocks[blockX][blockY].getEffectiveZone(acceptableSurfaces, crusher, cell->getZone()); -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - if (zone > m_maxZone) { -#else - if (zone >= m_maxZone) { -#endif - DEBUG_CRASH(("Invalid zone.")); - return UNINITIALIZED_ZONE; - } - return zone; -} - -// -// Return the zone at this location. -// -zoneStorageType PathfindZoneManager::getEffectiveTerrainZone(zoneStorageType zone) const -{ - return m_hierarchicalZones[m_terrainZones[zone]]; -} - -// -// Return the zone at this location. -// -zoneStorageType PathfindZoneManager::getEffectiveZone( LocomotorSurfaceTypeMask acceptableSurfaces, - Bool crusher, zoneStorageType zone) const -{ - //DEBUG_ASSERTCRASH(zone, ("Zone not set")); - if (zone>m_maxZone) { - DEBUG_CRASH(("Invalid zone")); - return (0); - } - if (zone>m_maxZone) { - DEBUG_CRASH(("Invalid zone")); - return (0); - } - if (acceptableSurfaces&LOCOMOTORSURFACE_AIR) return 1; // air is all zone 1. - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && - (acceptableSurfaces&LOCOMOTORSURFACE_WATER) && - (acceptableSurfaces&LOCOMOTORSURFACE_CLIFF)) { - // Locomotors can go on ground, water & cliff, so all is zone 1. - return 1; - } - - if (crusher) { - zone = m_crusherZones[zone]; - } - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && - (acceptableSurfaces&LOCOMOTORSURFACE_CLIFF)) { - // Locomotors can go on ground & cliff, so use the ground cliff combiner. - zone = m_groundCliffZones[zone]; - return zone; - } - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && - (acceptableSurfaces&LOCOMOTORSURFACE_WATER)) { - // Locomotors can go on ground & water, so use the ground water combiner. - zone = m_groundWaterZones[zone]; - return zone; - } - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_GROUND) && - (acceptableSurfaces&LOCOMOTORSURFACE_RUBBLE)) { - // Locomotors can go on ground & rubble, so use the ground rubble combiner. - zone = m_groundRubbleZones[zone]; - return zone; - } - - if ( (acceptableSurfaces&LOCOMOTORSURFACE_CLIFF) && - (acceptableSurfaces&LOCOMOTORSURFACE_WATER)) { - // Locomotors can go on ground & cliff, so use the ground cliff combiner. - DEBUG_CRASH(("Cliff water only locomotor sets not supported yet.")); - } - zone = m_hierarchicalZones[zone]; - - return zone; -} -//-------------------- PathfindLayer ---------------------------------------- -PathfindLayer::PathfindLayer() : m_blockOfMapCells(nullptr), m_layerCells(nullptr), m_bridge(nullptr), -m_destroyed(FALSE), -m_height(0), -m_width(0), -m_xOrigin(0), -m_yOrigin(0), -m_zone(0) -{ - m_startCell.x = -1; - m_startCell.y = -1; - m_endCell.x = -1; - m_endCell.y = -1; -} - -PathfindLayer::~PathfindLayer() -{ - reset(); -} - -/** - * Returns true if the layer is available for use. - */ -void PathfindLayer::reset() -{ - m_bridge = nullptr; - if (m_layerCells) { - Int i, j; - for (i=0; ireset(); - } - } - delete [] m_layerCells; - m_layerCells = nullptr; - } - - delete [] m_blockOfMapCells; - m_blockOfMapCells = nullptr; - - m_width = 0; - m_height = 0; - m_xOrigin = 0; - m_yOrigin = 0; - m_startCell.x = -1; - m_startCell.y = -1; - m_endCell.x = -1; - m_endCell.y = -1; - m_layer = LAYER_GROUND; -} - -/** - * Returns true if the layer is available for use. - */ -Bool PathfindLayer::isUnused() -{ - // Special case - wall layer is built from not a bridge. jba. - if (m_layer == LAYER_WALL && m_width>0) return false; - - if (m_bridge==nullptr) return true; - return false; -} - - - -/** - * Draws debug cell info. - */ -#if defined(RTS_DEBUG) -void PathfindLayer::doDebugIcons() { - if (isUnused()) return; - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - // render AI debug information - { - Coord3D topLeftCorner; - RGBColor color; - color.red = color.green = color.blue = 0; - Coord3D center; - center.x = (m_xOrigin+m_width/2)*PATHFIND_CELL_SIZE_F; - center.y = (m_yOrigin+m_height/2)*PATHFIND_CELL_SIZE_F; - center.z = 0; - Real bridgeHeight = TheTerrainLogic->getLayerHeight(center.x , center.y, m_layer); - 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; jgetConnectLayer()==LAYER_GROUND) { - color.green = 1; - color.blue = 1; - empty = false; - } else if (cell->getType() == 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) { - empty = true; - color.red = color.green = color.blue = 0; - if (empty && cell) { - if (cell->getFlags()!=PathfindCell::NO_UNITS) { - empty = false; - if (cell->getFlags() == PathfindCell::UNIT_GOAL) { - color.red = 1; - } else if (cell->getFlags() == PathfindCell::UNIT_PRESENT_FIXED) { - color.green = color.blue = color.red = 1; - } else if (cell->getFlags() == PathfindCell::UNIT_PRESENT_MOVING) { - color.green = 1; - } else { - color.green = color.red = 1; - } - } - } - } - if (!empty) { - Coord3D loc; - 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*size, 99, color); - } - } - } - - } -} -#endif - -/** - * Sets the bridge & layer number for a layer. - */ -Bool PathfindLayer::init(Bridge *theBridge, PathfindLayerEnum layer) -{ - if (m_bridge!=nullptr) return false; - m_bridge = theBridge; - m_layer = layer; - m_destroyed = false; - return true; -} - -/** - * Allocates the pathfind cells for the bridge layer. - */ -void PathfindLayer::allocateCells(const IRegion2D *extent) -{ - if (m_bridge == nullptr) return; - Region2D bridgeBounds = *m_bridge->getBounds(); - Int maxX, maxY; - m_xOrigin = REAL_TO_INT_FLOOR((bridgeBounds.lo.x-PATHFIND_CELL_SIZE/100)/PATHFIND_CELL_SIZE); - m_yOrigin = REAL_TO_INT_FLOOR((bridgeBounds.lo.y-PATHFIND_CELL_SIZE/100)/PATHFIND_CELL_SIZE); - m_width = 0; - m_height = 0; - maxX = REAL_TO_INT_CEIL((bridgeBounds.hi.x+PATHFIND_CELL_SIZE/100)/PATHFIND_CELL_SIZE); - maxY = REAL_TO_INT_CEIL((bridgeBounds.hi.y+PATHFIND_CELL_SIZE/100)/PATHFIND_CELL_SIZE); - // Pad with 1 extra; - m_xOrigin--; - m_yOrigin--; - maxX++; - maxY++; - - if (m_xOrigin < extent->lo.x) m_xOrigin = extent->lo.x; - if (m_yOrigin < extent->lo.y) m_yOrigin = extent->lo.y; - if (maxX > extent->hi.x) maxX = extent->hi.x; - if (maxY > extent->hi.y) maxY = extent->hi.y; - if (maxX <= m_xOrigin) return; - if (maxY <= m_yOrigin) return; - m_width = maxX - m_xOrigin; - m_height = maxY - m_yOrigin; - - // Allocate cells. - // pool[]ify - m_blockOfMapCells = MSGNEW("PathfindMapCells") PathfindCell[m_width*m_height]; - m_layerCells = MSGNEW("PathfindMapCells") PathfindCellP[m_width]; - Int i; - for (i=0; ifindObjectByID(wallPieces[i]); - Region2D objBounds; - if (obj==nullptr) continue; - obj->getGeometryInfo().get2DBounds(*obj->getPosition(), obj->getOrientation(), objBounds); - if (first) { - bridgeBounds = objBounds; - first = false; - } else { - if (bridgeBounds.lo.x>objBounds.lo.x) bridgeBounds.lo.x = objBounds.lo.x; - if (bridgeBounds.lo.y>objBounds.lo.y) bridgeBounds.lo.y = objBounds.lo.y; - if (bridgeBounds.hi.xlo.x) m_xOrigin = extent->lo.x; - if (m_yOrigin < extent->lo.y) m_yOrigin = extent->lo.y; - if (maxX > extent->hi.x) maxX = extent->hi.x; - if (maxY > extent->hi.y) maxY = extent->hi.y; - if (maxX <= m_xOrigin) return; - if (maxY <= m_yOrigin) return; - m_width = maxX - m_xOrigin; - m_height = maxY - m_yOrigin; - - // Allocate cells. - m_blockOfMapCells = MSGNEW("PathfindMapCells") PathfindCell[m_width*m_height]; - m_layerCells = MSGNEW("PathfindMapCells") PathfindCellP[m_width]; - - for (i=0; igetConnectLayer()==LAYER_GROUND) { - PathfindCell *groundCell = TheAI->pathfinder()->getCell(LAYER_GROUND, i+m_xOrigin, j+m_yOrigin); - DEBUG_ASSERTCRASH(groundCell, ("Should have cell.")); - if (groundCell) { - zoneStorageType zone = zm->getEffectiveZone(locoSet.getValidSurfaces(), - true, groundCell->getZone()); - zone = zm->getEffectiveTerrainZone(zone); - if (zone == zone1) found1 = true; - if (zone == zone2) found2 = true; - } - } - } - } - return found1 && found2; -} - -/** - * Classifies the pathfind cells for the bridge layer. - */ -void PathfindLayer::classifyCells() -{ - m_startCell.x = -1; - m_startCell.y = -1; - m_endCell.x = -1; - m_endCell.y = -1; - Int i, j; - for (i=0; isetConnectLayer(LAYER_INVALID); - cell->setLayer(m_layer); - classifyLayerMapCell(i+m_xOrigin, j+m_yOrigin, cell, m_bridge); - } - BridgeInfo info; - m_bridge->getBridgeInfo(&info); - Coord3D bridgeDir = info.to; - bridgeDir.x -= info.from.x; - bridgeDir.y -= info.from.y; - bridgeDir.z -= info.from.z; - bridgeDir.normalize(); - bridgeDir.x *= PATHFIND_CELL_SIZE_F*0.7f; - bridgeDir.y *= PATHFIND_CELL_SIZE_F*0.7f; - - m_startCell.x = REAL_TO_INT_FLOOR((info.from.x-bridgeDir.x) / PATHFIND_CELL_SIZE_F); - m_startCell.y = REAL_TO_INT_FLOOR((info.from.y-bridgeDir.y) / PATHFIND_CELL_SIZE_F); - m_endCell.x = REAL_TO_INT_FLOOR((info.to.x+bridgeDir.x) / PATHFIND_CELL_SIZE_F); - m_endCell.y = REAL_TO_INT_FLOOR((info.to.y+bridgeDir.y) / PATHFIND_CELL_SIZE_F); - } - if (m_destroyed) { - Int i, j; - for (i=0; igetConnectLayer() == LAYER_GROUND) { - PathfindCell *groundCell = TheAI->pathfinder()->getCell(LAYER_GROUND, i+m_xOrigin, j+m_yOrigin); - DEBUG_ASSERTCRASH(groundCell, ("Should have cell.")); - if (groundCell) { - DEBUG_ASSERTCRASH(groundCell->getConnectLayer()==m_layer, ("Should connect to this layer.jba.")); - groundCell->setConnectLayer(LAYER_INVALID); // disconnect it. - } - } -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - cell->setType(PathfindCell::CELL_IMPASSABLE); -#else - cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); -#endif - } - } - } -} - -/** - * Classifies the pathfind cells for the wall bridge layer. - */ -void PathfindLayer::classifyWallCells(ObjectID *wallPieces, Int numPieces) -{ - DEBUG_ASSERTCRASH(m_layer==LAYER_WALL, ("Wrong layer for wall.")); - if (m_layer != LAYER_WALL) return; - if (m_layerCells == nullptr) return; - - Int i, j; - for (i=0; isetConnectLayer(LAYER_INVALID); - cell->setLayer(m_layer); - classifyWallMapCell(i+m_xOrigin, j+m_yOrigin, cell, wallPieces, numPieces); - cell->setPinched(false); - } - } - if (m_destroyed) { - Int i, j; - for (i=0; igetConnectLayer() == LAYER_GROUND) { - PathfindCell *groundCell = TheAI->pathfinder()->getCell(LAYER_GROUND, i+m_xOrigin, j+m_yOrigin); - DEBUG_ASSERTCRASH(groundCell, ("Should have cell.")); - if (groundCell) { - DEBUG_ASSERTCRASH(groundCell->getConnectLayer()==m_layer, ("Should connect to this layer.jba.")); - groundCell->setConnectLayer(LAYER_INVALID); // disconnect it. - } - } - cell->setType(PathfindCell::CELL_IMPASSABLE); - } - } - } - - // Tighten up 1 cell. - for (i=1; igetType() != PathfindCell::CELL_CLEAR) { - cell->setPinched(true); - } - } - } - } - } - for (i=0; igetPinched() && cell->getType() == PathfindCell::CELL_CLEAR) { - cell->setType(PathfindCell::CELL_CLIFF); - } - cell->setPinched(false); - } - } -} - -/** - * Relassifies the pathfind cells for the destroyed bridge layer. - */ -Bool PathfindLayer::setDestroyed(Bool destroyed) -{ - if (destroyed == m_destroyed) return false; - - m_destroyed = destroyed; - classifyCells(); - - return true; -} - -/** - * Copies m_zone into the zone for all the member cells. - */ -void PathfindLayer::applyZone() -{ - Int i, j; - for (i=0; isetZone(m_zone); - } - } -} - - -/** - * Return the bridge's object id. - */ -ObjectID PathfindLayer::getBridgeID() -{ - return m_bridge->peekBridgeInfo()->bridgeObjectID; -} - -/** - * Return the cell at the index location. - */ -PathfindCell *PathfindLayer::getCell(Int x, Int y) -{ - DEBUG_ASSERTCRASH(m_layerCells, ("no data in layer, why get cells?")); - if (m_layerCells==nullptr) { - return nullptr; - } - x -= m_xOrigin; - y -= m_yOrigin; - if (x<0 || x>=m_width) return nullptr; - if (y<0 || y>=m_height) return nullptr; - PathfindCell *cell = &m_layerCells[x][y]; - if (cell->getType() == PathfindCell::CELL_IMPASSABLE) { - return nullptr; // Impassable cells are ignored. - } - return cell; -} - - -/** - * Classify the given map cell as clear, or not, etc. - */ -void PathfindLayer::classifyLayerMapCell( Int i, Int j , PathfindCell *cell, Bridge *theBridge) -{ - Coord3D topLeftCorner, bottomRightCorner; - - topLeftCorner.y = (Real)j * PATHFIND_CELL_SIZE_F; - bottomRightCorner.y = topLeftCorner.y + PATHFIND_CELL_SIZE_F; - - topLeftCorner.x = (Real)i * PATHFIND_CELL_SIZE_F; - bottomRightCorner.x = topLeftCorner.x + PATHFIND_CELL_SIZE_F; - - - Int bridgeCount = 0; - Coord3D pt; - if (theBridge->isPointOnBridge(&topLeftCorner) ) { - bridgeCount++; - } - pt = topLeftCorner; - pt.y = bottomRightCorner.y; - if (theBridge->isPointOnBridge(&pt) ) { - bridgeCount++; - } - if (theBridge->isPointOnBridge(&bottomRightCorner) ) { - bridgeCount++; - } - pt = topLeftCorner; - pt.x = bottomRightCorner.x; - if (theBridge->isPointOnBridge(&pt) ) { - bridgeCount++; - } - cell->reset(); - cell->setLayer(m_layer); - cell->setType(PathfindCell::CELL_IMPASSABLE); - if (bridgeCount == 4) { - cell->setType(PathfindCell::CELL_CLEAR); - } else { - if (bridgeCount!=0) { -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - 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. - - Region2D cellBounds; - cellBounds.lo.x = topLeftCorner.x; - cellBounds.lo.y = topLeftCorner.y; - cellBounds.hi.x = bottomRightCorner.x; - cellBounds.hi.y = bottomRightCorner.y; - -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - if (m_bridge->isCellOnEnd(&cellBounds)) { - cell->setType(PathfindCell::CELL_CLEAR); - } - if (m_bridge->isCellOnSide(&cellBounds)) { - cell->setType(PathfindCell::CELL_CLIFF); - } else { - if (m_bridge->isCellEntryPoint(&cellBounds)) { - cell->setType(PathfindCell::CELL_CLEAR); - cell->setConnectLayer(LAYER_GROUND); - PathfindCell *groundCell = TheAI->pathfinder()->getCell(LAYER_GROUND, i, j ); - groundCell->setConnectLayer(cell->getLayer()); - } - } -#else - if (m_bridge->isCellOnSide(&cellBounds)) { - cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); - } else { - if (m_bridge->isCellOnEnd(&cellBounds)) { - cell->setType(PathfindCell::CELL_CLEAR); - } - if (m_bridge->isCellEntryPoint(&cellBounds)) { - cell->setType(PathfindCell::CELL_CLEAR); - cell->setConnectLayer(LAYER_GROUND); - PathfindCell *groundCell = TheAI->pathfinder()->getCell(LAYER_GROUND, i, j ); - groundCell->setConnectLayer(cell->getLayer()); - } - } -#endif - } - Coord3D center = topLeftCorner; - center.x += PATHFIND_CELL_SIZE/2; - center.y += PATHFIND_CELL_SIZE/2; - if (cell->getType()!=PathfindCell::CELL_IMPASSABLE) { - if (!(cell->getConnectLayer()==LAYER_GROUND) ) { - // Check for bridge clearance. If the ground isn't 1 pathfind cells below, mark impassable. - Real groundHeight = TheTerrainLogic->getLayerHeight( center.x, center.y, LAYER_GROUND ); - Real bridgeHeight = theBridge->getBridgeHeight( ¢er, nullptr ); - 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 && RETAIL_COMPATIBLE_PATHFINDING - groundCell->setType(PathfindCell::CELL_IMPASSABLE); -#else - groundCell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); -#endif - } - } - } - } - return; -} - - -Bool PathfindLayer::isPointOnWall(ObjectID *wallPieces, Int numPieces, const Coord3D *pt) -{ - Int i; - for (i=0; ifindObjectByID(wallPieces[i]); - if (obj==nullptr) continue; - Real major = obj->getGeometryInfo().getMajorRadius(); - Real minor = (obj->getGeometryInfo().getGeomType() == GEOMETRY_SPHERE) ? obj->getGeometryInfo().getMajorRadius() : obj->getGeometryInfo().getMinorRadius(); - - Real c = (Real)Cos(-obj->getOrientation()); - Real s = (Real)Sin(-obj->getOrientation()); - - // convert to a delta relative to rect ctr - Real ptx = pt->x - obj->getPosition()->x; - Real pty = pt->y - obj->getPosition()->y; - - // inverse-rotate it to the right coord system - Real ptx_new = (Real)fabs(ptx*c - pty*s); - Real pty_new = (Real)fabs(ptx*s + pty*c); - - if (ptx_new <= major && pty_new <= minor) - { - return true; - } - } - return false; -} - - -/** - * Classify the given map cell as clear, or not, etc. - */ -void PathfindLayer::classifyWallMapCell( Int i, Int j , PathfindCell *cell, ObjectID *wallPieces, Int numPieces) -{ - Coord3D topLeftCorner, bottomRightCorner; - - topLeftCorner.y = (Real)j * PATHFIND_CELL_SIZE_F; - bottomRightCorner.y = topLeftCorner.y + PATHFIND_CELL_SIZE_F; - - topLeftCorner.x = (Real)i * PATHFIND_CELL_SIZE_F; - bottomRightCorner.x = topLeftCorner.x + PATHFIND_CELL_SIZE_F; - - - Int bridgeCount = 0; - Coord3D pt; - if (isPointOnWall(wallPieces, numPieces, &topLeftCorner) ) { - bridgeCount++; - } - pt = topLeftCorner; - pt.y = bottomRightCorner.y; - if (isPointOnWall(wallPieces, numPieces, &pt) ) { - bridgeCount++; - } - if (isPointOnWall(wallPieces, numPieces, &bottomRightCorner) ) { - bridgeCount++; - } - pt = topLeftCorner; - pt.x = bottomRightCorner.x; - if (isPointOnWall(wallPieces, numPieces, &pt) ) { - bridgeCount++; - } - cell->reset(); - cell->setLayer(m_layer); - cell->setType(PathfindCell::CELL_IMPASSABLE); - if (bridgeCount == 4) { - cell->setType(PathfindCell::CELL_CLEAR); - } else { - if (bridgeCount!=0) { -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - cell->setType(PathfindCell::CELL_CLIFF); // it's off the bridge. -#else - cell->setType(PathfindCell::CELL_BRIDGE_IMPASSABLE); // it's off the bridge. -#endif - } - - } -} - -//----------------------- Pathfinder --------------------------------------- - -Pathfinder::Pathfinder() :m_map(nullptr) -{ - debugPath = nullptr; - PathfindCellInfo::allocateCellInfos(); - reset(); -} - -Pathfinder::~Pathfinder() -{ - PathfindCellInfo::releaseCellInfos(); -} - -void Pathfinder::reset() -{ - frameToShowObstacles = 0; - DEBUG_LOG(("Pathfind cell is %d bytes, PathfindCellInfo is %d bytes", sizeof(PathfindCell), sizeof(PathfindCellInfo))); - - delete [] m_blockOfMapCells; - m_blockOfMapCells = nullptr; - - delete [] m_map; - m_map = nullptr; - - Int i; - for (i=0; i<=LAYER_LAST; i++) { - m_layers[i].reset(); - } - - // reset the pathfind grid - m_extent.lo.x=m_extent.lo.y=m_extent.hi.x=m_extent.hi.y=0; - m_logicalExtent.lo.x=m_logicalExtent.lo.y=m_logicalExtent.hi.x=m_logicalExtent.hi.y=0; - m_openList.reset(); - m_closedList.reset(); - - m_ignoreObstacleID = INVALID_ID; - m_isTunneling = false; - - m_moveAlliesDepth = 0; - - // pathfind grid cells have not been classified yet - m_isMapReady = false; - m_cumulativeCellsAllocated = 0; - - debugPathPos.x = 0.0f; - debugPathPos.y = 0.0f; - debugPathPos.z = 0.0f; - - deleteInstance(debugPath); - debugPath = nullptr; - - m_frameToShowObstacles = 0; - - for (m_queuePRHead=0; m_queuePRHeadgetAiData()) { - m_wallHeight = TheAI->getAiData()->m_wallHeight; - } - else - { - m_wallHeight = 0.0f; - } - m_zoneManager.reset(); - -#if RETAIL_COMPATIBLE_PATHFINDING - s_useFixedPathfinding = false; - s_forceCleanCells = false; -#endif -} - -/** - * Adds a piece of a wall. - */ -void Pathfinder::addWallPiece(Object *wallPiece) -{ - if (m_numWallPiecesgetID(); - m_numWallPieces++; - } -} - -/** - * Removes a piece of a wall - */ -void Pathfinder::removeWallPiece(Object *wallPiece) -{ - - // sanity - if( wallPiece == nullptr ) - return; - - // find entry - for( Int i = 0; i < m_numWallPieces; ++i ) - { - - // match by id - if( m_wallPieces[ i ] == wallPiece->getID() ) - { - - // put the last id in the wall piece array here - m_wallPieces[ i ] = m_wallPieces[ m_numWallPieces - 1 ]; - - // we now have one less entry - m_numWallPieces--; - - // all done - return; - - } - - } - -} - -/** - * Checks if a point is on the wall. - */ -Bool Pathfinder::isPointOnWall(const Coord3D *pos) -{ - if (m_numWallPieces==0) return false; - if (m_layers[LAYER_WALL].isUnused()) return false; - PathfindLayerEnum layer = (PathfindLayerEnum)LAYER_WALL; - PathfindCell *cell = getCell(layer, pos); - // make sure the layer matches, since getCell can return ground layer cells if the pos is 'off' the bridge/wall - if (cell && cell->getLayer() == layer) { - if (cell->getType() == PathfindCell::CELL_CLEAR) { - return true; - } - } - return false; -} - -/** - * Adds a bridge & returns the layer. - */ -PathfindLayerEnum Pathfinder::addBridge(Bridge *theBridge) -{ - Int layer = LAYER_GROUND+1; - while (layer<=LAYER_WALL) { - if (m_layers[layer].isUnused()) { - if (m_layers[layer].init(theBridge, (PathfindLayerEnum)layer) ) { - return (PathfindLayerEnum)layer; - } - DEBUG_LOG(("WARNING: Bridge failed to init in pathfinder")); - return LAYER_GROUND; // failed to init, usually cause off of the map. jba. - } - layer++; - } - DEBUG_CRASH(("Ran out of bridge layers.")); - return LAYER_GROUND; -} - -/** - * Updates an object's layer, making sure the object is actually on the bridge first. - */ -void Pathfinder::updateLayer(Object *obj, PathfindLayerEnum layer) -{ - if (layer != LAYER_GROUND) { - if (!TheTerrainLogic->objectInteractsWithBridgeLayer(obj, layer)) { - layer = LAYER_GROUND; - } - } - //DEBUG_LOG(("Object layer is %d", layer)); - obj->setLayer(layer); -} - -/** - * Classify the cells under the given object - * If 'insert' is true, object is being added - * If 'insert' is false, object is being removed - */ -void Pathfinder::classifyFence( Object *obj, Bool insert ) -{ -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - m_zoneManager.markZonesDirty(); -#endif - - const Coord3D *pos = obj->getPosition(); - Real angle = obj->getOrientation(); - - Real halfsizeX = obj->getTemplate()->getFenceWidth()/2; - Real halfsizeY = PATHFIND_CELL_SIZE_F/10.0f; - Real fenceOffset = obj->getTemplate()->getFenceXOffset(); - - Real c = (Real)Cos(angle); - Real s = (Real)Sin(angle); - - const Real STEP_SIZE = PATHFIND_CELL_SIZE_F * 0.5f; // in theory, should be PATHFIND_CELL_SIZE_F exactly, but needs to be smaller to avoid aliasing problems - Real ydx = s * STEP_SIZE; - Real ydy = -c * STEP_SIZE; - Real xdx = c * STEP_SIZE; - Real xdy = s * STEP_SIZE; - - Int numStepsX = REAL_TO_INT_CEIL(2.0f * halfsizeX / STEP_SIZE); - Int numStepsY = REAL_TO_INT_CEIL(2.0f * halfsizeY / STEP_SIZE); - - Real tl_x = pos->x - fenceOffset*c - halfsizeY*s; - Real tl_y = pos->y + halfsizeY*c - fenceOffset*s; - -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - 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_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - - for (Int iy = 0; iy < numStepsY; ++iy, tl_x += ydx, tl_y += ydy) - { - Real x = tl_x; - Real y = tl_y; - for (Int ix = 0; ix < numStepsX; ++ix, x += xdx, y += xdy) - { - Int cx = REAL_TO_INT_FLOOR((x + 0.5f)/PATHFIND_CELL_SIZE_F); - Int cy = REAL_TO_INT_FLOOR((y + 0.5f)/PATHFIND_CELL_SIZE_F); - if (cx >= 0 && cy >= 0 && cx < m_extent.hi.x && cy < m_extent.hi.y) - { -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - if (insert) { - ICoord2D pos; - pos.x = cx; - pos.y = cy; - m_map[cx][cy].setTypeAsObstacle( obj, true, pos ); - } - else - m_map[cx][cy].removeObstacle(obj); -#else - if (insert) { - ICoord2D pos; - pos.x = cx; - pos.y = cy; - if (m_map[cx][cy].setTypeAsObstacle( obj, true, pos )) { - didAnything = true; - m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); - } - } - 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_MINE)) { - return; // don't pathfind around mines. - } - - if (obj->isKindOf(KINDOF_PROJECTILE)) { - return; // don't care about projectiles. - } - - if (obj->isKindOf(KINDOF_BRIDGE_TOWER)) { - return; // It is important to not abuse bridge towers. - } - - if (obj->getTemplate()->getFenceWidth() > 0.0f) - { - if (!obj->isKindOf(KINDOF_DEFENSIVE_WALL)) - { - classifyFence(obj, insert); - return; - } - } - - if (!insert) { - // Just in case, remove the object. Remove checks that the object has been added before - // removing, so it's safer to just remove it, as by the time some units "die", they've become - // lifeless immobile husks of debris, but we still need to remove them. jba. - -#if !RTS_GENERALS - if ( obj->isKindOf( 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()) { - Int i; - ObjectID curID = obj->getID(); - for (i=0; igetFirstObject(); obj; obj=obj->getNextObject()) { - if (obj->getLayer() == LAYER_WALL) { - if (m_layers[LAYER_WALL].isPointOnWall(&curID, 1, obj->getPosition())) - { - // The object fell off the wall. - // Destroy it. - DamageInfo extraDamageInfo; - extraDamageInfo.in.m_damageType = DAMAGE_FALLING; - extraDamageInfo.in.m_deathType = DEATH_SPLATTED; - extraDamageInfo.in.m_sourceID = obj->getID(); - extraDamageInfo.in.m_amount = HUGE_DAMAGE_AMOUNT; - obj->attemptDamage(&extraDamageInfo); - } - } - } - // recalc the wall. - m_layers[LAYER_WALL].classifyWallCells(m_wallPieces, m_numWallPieces); - } - } - } - if (!obj->isKindOf(KINDOF_STRUCTURE)) { - return; // Only path around structures. - } - if (obj->isMobile()) { - return; // mobile aren't obstacles. - } - /// For now, all small objects will not be obstacles - if (obj->getGeometryInfo().getIsSmall()) { - 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_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - 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(); - Real angle = obj->getOrientation(); - - Real halfsizeX = obj->getGeometryInfo().getMajorRadius(); - Real halfsizeY = obj->getGeometryInfo().getMinorRadius(); - - Real c = (Real)Cos(angle); - Real s = (Real)Sin(angle); - - const Real STEP_SIZE = PATHFIND_CELL_SIZE_F * 0.5f; // in theory, should be PATHFIND_CELL_SIZE_F exactly, but needs to be smaller to avoid aliasing problems - Real ydx = s * STEP_SIZE; - Real ydy = -c * STEP_SIZE; - Real xdx = c * STEP_SIZE; - Real xdy = s * STEP_SIZE; - - Int numStepsX = REAL_TO_INT_CEIL(2.0f * halfsizeX / STEP_SIZE); - Int numStepsY = REAL_TO_INT_CEIL(2.0f * halfsizeY / STEP_SIZE); - - Real tl_x = pos->x - halfsizeX*c - halfsizeY*s; - Real tl_y = pos->y + halfsizeY*c - halfsizeX*s; - - for (Int iy = 0; iy < numStepsY; ++iy, tl_x += ydx, tl_y += ydy) - { - Real x = tl_x; - Real y = tl_y; - for (Int ix = 0; ix < numStepsX; ++ix, x += xdx, y += xdy) - { - Int cx = REAL_TO_INT_FLOOR((x + 0.5f)/PATHFIND_CELL_SIZE_F); - Int cy = REAL_TO_INT_FLOOR((y + 0.5f)/PATHFIND_CELL_SIZE_F); - -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - if (cx >= 0 && cy >= 0 && cx < m_extent.hi.x && cy < m_extent.hi.y) - { - if (insert) { - ICoord2D pos; - pos.x = cx; - pos.y = cy; - m_map[cx][cy].setTypeAsObstacle( obj, false, pos ); - } - else - m_map[cx][cy].removeObstacle(obj); - } -#else - if (cx >= 0 && cy >= 0 && cx < m_extent.hi.x && cy < m_extent.hi.y) - { - if (insert) { - ICoord2D pos; - pos.x = cx; - pos.y = cy; - if (m_map[cx][cy].setTypeAsObstacle( obj, false, pos )) { - m_map[cx][cy].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); - } - } - 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.xgetGeometryInfo().getMajorRadius(); - Real r2, size; - - topLeft.x = REAL_TO_INT_FLOOR(0.5f + (pos->x - radius)/PATHFIND_CELL_SIZE_F)-1; - topLeft.y = REAL_TO_INT_FLOOR(0.5f + (pos->y - radius)/PATHFIND_CELL_SIZE_F)-1; - size = (radius/PATHFIND_CELL_SIZE_F); - center.x = (pos->x/PATHFIND_CELL_SIZE_F); - center.y = (pos->y/PATHFIND_CELL_SIZE_F); - - size += 0.4f; - r2 = size*size; - - bottomRight.x = topLeft.x + 2*size + 2; - bottomRight.y = topLeft.y + 2*size + 2; - - for( int j = topLeft.y; j < bottomRight.y; j++ ) - { - for( int i = topLeft.x; i < bottomRight.x; i++ ) - { - delta.x = i+0.5f - center.x; - delta.y = j+0.5f - center.y; - - if (delta.x*delta.x + delta.y*delta.y <= r2) - { -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - if (i >= 0 && j >= 0 && i < m_extent.hi.x && j < m_extent.hi.y) - { - if (insert) { - ICoord2D pos; - pos.x = i; - pos.y = j; - m_map[i][j].setTypeAsObstacle( obj, false, pos ); - } - else - m_map[i][j].removeObstacle( obj ); - } -#else - if (i >= 0 && j >= 0 && i < m_extent.hi.x && j < m_extent.hi.y) - { - if (insert) { - ICoord2D pos; - pos.x = i; - pos.y = j; - if (m_map[i][j].setTypeAsObstacle( obj, false, pos )) { - m_map[i][j].setZone(PathfindZoneManager::UNINITIALIZED_ZONE); - } - } - 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 - - Int i, j; - - if (cellBounds.lo.x < m_extent.lo.x) { - cellBounds.lo.x = m_extent.lo.x; - } - if (cellBounds.lo.y < m_extent.lo.y) { - cellBounds.lo.y = m_extent.lo.y; - } - if (cellBounds.lo.y < m_extent.lo.y) { - cellBounds.lo.y = m_extent.lo.y; - } - if (cellBounds.hi.x > m_extent.hi.x) { - cellBounds.hi.x = m_extent.hi.x; - } - if (cellBounds.hi.y > m_extent.hi.y) { - cellBounds.hi.y = m_extent.hi.y; - } - - if (!insert) { - for( j=cellBounds.lo.y; j<=cellBounds.hi.y; j++ ) - { - for( i=cellBounds.lo.x; i<=cellBounds.hi.x; i++ ) - { - if (m_map[i][j].getType()==PathfindCell::CELL_IMPASSABLE) { - m_map[i][j].setType(PathfindCell::CELL_CLEAR); - } - } - } - } - // Check for pinched cells, and close them off. - - for( j=cellBounds.lo.y; j<=cellBounds.hi.y; j++ ) - { - for( i=cellBounds.lo.x; i<=cellBounds.hi.x; i++ ) - { - m_map[i][j].setPinched(false); - if (m_map[i][j].getType() == PathfindCell::CELL_CLEAR) { - Int totalCount = 0; - Int orthogonalCount = 0; - Int k, l; - for (k=i-1; k m_extent.hi.x) continue; - for (l=j-1; l m_extent.hi.y) continue; - if ((k==i) && (j==l)) continue; - if (m_map[k][l].getType() == PathfindCell::CELL_CLEAR) { - totalCount++; - if ((k==i) || (l==j)) { - orthogonalCount++; - } - } - - } - } - // If the total open cells are < 2 or total cells < 4, we are pinched. - if (orthogonalCount<2 || totalCount<4) { - m_map[i][j].setPinched(true); - } - } - } - } - -#if RETAIL_COMPATIBLE_PATHFINDING - for( j=cellBounds.lo.y; j<=cellBounds.hi.y; j++ ) - { - for( i=cellBounds.lo.x; i<=cellBounds.hi.x; i++ ) - { - if (m_map[i][j].getPinched() && (m_map[i][j].getType() == PathfindCell::CELL_CLEAR)) { - m_map[i][j].setType(PathfindCell::CELL_IMPASSABLE); - m_map[i][j].setPinched(false); - } - } - } -#endif - - // Expand building bounds 1 cell. - for( j=cellBounds.lo.y; j<=cellBounds.hi.y; j++ ) - { - for( i=cellBounds.lo.x; i<=cellBounds.hi.x; i++ ) - { - if (m_map[i][j].getType() == PathfindCell::CELL_CLEAR) { - Bool objectAdjacent = false; - Int k, l; - for (k=i-1; k m_extent.hi.x) continue; - for (l=j-1; l m_extent.hi.y) continue; - if ((k==i) && (l==j)) continue; - if ((k!=i) && (l!=j)) continue; - if (m_map[k][l].getType() == PathfindCell::CELL_OBSTACLE) { - objectAdjacent = true; - break; - } - - } - } - if (objectAdjacent) { - m_map[i][j].setPinched(true); - } - } - } - } -} - -/** - * Classify the given map cell as WATER, CLIFF, etc. - * Note that this does NOT classify cells as OBSTACLES. - * OBSTACLE cells are classified only via objects. - * @todo optimize this - lots of redundant computation - */ -void Pathfinder::classifyMapCell( Int i, Int j , PathfindCell *cell) -{ - Coord3D topLeftCorner, bottomRightCorner; - - - Bool hasObstacle = (cell->getType() == PathfindCell::CELL_OBSTACLE) ; - - topLeftCorner.y = (Real)j * PATHFIND_CELL_SIZE_F; - bottomRightCorner.y = topLeftCorner.y + PATHFIND_CELL_SIZE_F; - - topLeftCorner.x = (Real)i * PATHFIND_CELL_SIZE_F; - bottomRightCorner.x = topLeftCorner.x + PATHFIND_CELL_SIZE_F; - - cell->setPinched(false); - - PathfindCell::CellType type = PathfindCell::CELL_CLEAR; - if (TheTerrainLogic->isCliffCell(topLeftCorner.x, topLeftCorner.y)) - { - type = PathfindCell::CELL_CLIFF; - } - - // - // If any corners are underwater, this is a water cell - // - if (TheTerrainLogic->isUnderwater( topLeftCorner.x, topLeftCorner.y ) ) type = PathfindCell::CELL_WATER; - if (TheTerrainLogic->isUnderwater( topLeftCorner.x, bottomRightCorner.y) ) type = PathfindCell::CELL_WATER; - if (TheTerrainLogic->isUnderwater( bottomRightCorner.x, bottomRightCorner.y ) ) type = PathfindCell::CELL_WATER; - if (TheTerrainLogic->isUnderwater( bottomRightCorner.x, topLeftCorner.y ) ) type = PathfindCell::CELL_WATER; - - if (hasObstacle) { - type = PathfindCell::CELL_OBSTACLE; - } - cell->setType( type ); - cell->releaseInfo(); -} - -/** - * Set up for a new map. - */ -void Pathfinder::newMap() -{ - m_wallHeight = TheAI->getAiData()->m_wallHeight; // may be updated by map.ini. - Region3D terrainExtent; - TheTerrainLogic->getMaximumPathfindExtent( &terrainExtent ); - IRegion2D bounds; - bounds.lo.x = REAL_TO_INT_FLOOR(terrainExtent.lo.x / PATHFIND_CELL_SIZE_F); - bounds.hi.x = REAL_TO_INT_FLOOR(terrainExtent.hi.x / PATHFIND_CELL_SIZE_F); - bounds.lo.y = REAL_TO_INT_FLOOR(terrainExtent.lo.y / PATHFIND_CELL_SIZE_F); - bounds.hi.y = REAL_TO_INT_FLOOR(terrainExtent.hi.y / PATHFIND_CELL_SIZE_F); - bounds.hi.x--; - bounds.hi.y--; - Bool dataAllocated = false; - if (m_extent.hi.x==bounds.hi.x && m_extent.hi.y==bounds.hi.y) { - if (m_blockOfMapCells != nullptr && m_map!=nullptr) { - dataAllocated = true; - } - } - // For map load from file, we have to call newMap twice to do sequencing issues. - // so the second time through, dataAllocated==TRUE, so we skip the allocate. - if (!dataAllocated) { - m_extent = bounds; - DEBUG_ASSERTCRASH(m_map == nullptr, ("Can't reallocate pathfind cells.")); - m_zoneManager.allocateBlocks(m_extent); - // Allocate cells. - m_blockOfMapCells = MSGNEW("PathfindMapCells") PathfindCell[(bounds.hi.x+1)*(bounds.hi.y+1)]; - m_map = MSGNEW("PathfindMapCells") PathfindCellP[bounds.hi.x+1]; - Int i; - for (i=0; i<=bounds.hi.x; i++) { - m_map[i] = &m_blockOfMapCells[i*(bounds.hi.y+1)]; - } - for (i=0; i0) { - m_layers[LAYER_WALL].init(nullptr, LAYER_WALL); - m_layers[LAYER_WALL].allocateCellsForWallLayer(&m_extent, m_wallPieces, m_numWallPieces); - } - } - classifyMap(); - // Add existing objects. - Object *obj; - for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) - { - classifyObjectFootprint(obj, true); - } - - m_isMapReady = true; -} - -/** - * Classify all cells in grid as obstacles, etc. - */ -void Pathfinder::classifyMap() -{ - - Int i, j; - // for now, sample cell corners and classify cell accordingly - for( j=m_extent.lo.y; j<=m_extent.hi.y; j++ ) - { - for( i=m_extent.lo.x; i<=m_extent.hi.x; i++ ) - { - classifyMapCell( i, j, &m_map[i][j]); - } - } -#if 1 - // Expand all cliff cells one step (mark pinched) - for( j=m_extent.lo.y; j<=m_extent.hi.y; j++ ) - { - for( i=m_extent.lo.x; i<=m_extent.hi.x; i++ ) - { - if (m_map[i][j].getType() & PathfindCell::CELL_CLIFF) { - Int k, l; - for (k=i-1; k m_extent.hi.x) continue; - for (l=j-1; l m_extent.hi.y) continue; - if (m_map[k][l].getType() == PathfindCell::CELL_CLEAR) { - m_map[k][l].setPinched(true); - } - - } - } - } - } - } - // Convert pinched to cliff. - for( j=m_extent.lo.y; j<=m_extent.hi.y; j++ ) - { - for( i=m_extent.lo.x; i<=m_extent.hi.x; i++ ) - { - if (m_map[i][j].getPinched()) { - if (m_map[i][j].getType()==PathfindCell::CELL_CLEAR) { - m_map[i][j].setType(PathfindCell::CELL_CLIFF); - } - } - } - } - // Add a border of pinched cells to cliffs. - for( j=m_extent.lo.y; j<=m_extent.hi.y; j++ ) - { - for( i=m_extent.lo.x; i<=m_extent.hi.x; i++ ) - { - if (m_map[i][j].getType() & PathfindCell::CELL_CLIFF) { - Int k, l; - for (k=i-1; k m_extent.hi.x) continue; - for (l=j-1; l m_extent.hi.y) continue; - if (m_map[k][l].getType() == PathfindCell::CELL_CLEAR) { - m_map[k][l].setPinched(true); - } - - } - } - } - } - } -#endif - for (i=0; im_debugAI) { - return; - } -#if defined(RTS_DEBUG) - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - - // show all explored cells for debugging - PathfindCell *s; - RGBColor color; - color.red = color.blue = color.green = 1; - if (!pathFound) { - addIcon(nullptr, 0, 0, color); // erase. - } - - for( s = m_openList.getHead(); s; s=s->getNextOpen() ) - { - // create objects to show path - they decay - RGBColor color; - color.red = color.green = 0; - color.blue = 1; - - Coord3D pos; - pos.x = ((Real)s->getXIndex() + 0.5f) * PATHFIND_CELL_SIZE_F; - pos.y = ((Real)s->getYIndex() + 0.5f) * PATHFIND_CELL_SIZE_F; - pos.z = TheTerrainLogic->getLayerHeight( pos.x, pos.y, s->getLayer() ) + 0.5f; - addIcon(&pos, PATHFIND_CELL_SIZE_F*.6f, 200, color); - } - - for( s = m_closedList.getHead(); s; s=s->getNextOpen() ) - { - // create objects to show path - they decay - RGBColor color; - color.red = color.blue = 1; - color.green = 0; - if (!pathFound) color.blue = 0; - - Int length=200; - if (!pathFound) - length *= 2; - - Coord3D pos; - pos.x = ((Real)s->getXIndex() + 0.5f) * PATHFIND_CELL_SIZE_F; - pos.y = ((Real)s->getYIndex() + 0.5f) * PATHFIND_CELL_SIZE_F; - pos.z = TheTerrainLogic->getLayerHeight( pos.x, pos.y, s->getLayer()) + 0.5f; - addIcon(&pos, PATHFIND_CELL_SIZE_F*.6f, length, color); - } -#endif -} - -Locomotor* Pathfinder::chooseBestLocomotorForPosition(PathfindLayerEnum layer, LocomotorSet* locomotorSet, const Coord3D* pos ) -{ - Int x = REAL_TO_INT_FLOOR(pos->x/PATHFIND_CELL_SIZE); - Int y = REAL_TO_INT_FLOOR(pos->y/PATHFIND_CELL_SIZE); - PathfindCell* cell = getCell(layer, x, y ); - // off the map? call it CELL_CLEAR... - PathfindCell::CellType celltype = cell ? cell->getType() : PathfindCell::CELL_CLEAR; - - LocomotorSurfaceTypeMask acceptableSurfaces = validLocomotorSurfacesForCellType(celltype); - return locomotorSet->findLocomotor(acceptableSurfaces); -} - -/*static*/ LocomotorSurfaceTypeMask Pathfinder::validLocomotorSurfacesForCellType(PathfindCell::CellType t) -{ - switch (t) - { - case PathfindCell::CELL_CLEAR: - return LOCOMOTORSURFACE_GROUND | LOCOMOTORSURFACE_AIR; - - case PathfindCell::CELL_WATER: - return LOCOMOTORSURFACE_WATER | LOCOMOTORSURFACE_AIR; - - case PathfindCell::CELL_CLIFF: - return LOCOMOTORSURFACE_CLIFF | LOCOMOTORSURFACE_AIR; - - case PathfindCell::CELL_RUBBLE: - return LOCOMOTORSURFACE_RUBBLE | LOCOMOTORSURFACE_AIR; - - case PathfindCell::CELL_OBSTACLE: - case PathfindCell::CELL_BRIDGE_IMPASSABLE: - case PathfindCell::CELL_IMPASSABLE: - return LOCOMOTORSURFACE_AIR; - - default: - return NO_SURFACES; - } -} - -// -// Return true if we can move onto this position -// -Bool Pathfinder::validMovementTerrain( PathfindLayerEnum layer, const Locomotor* locomotor, const Coord3D *pos) -{ - Int x = REAL_TO_INT_FLOOR(pos->x/PATHFIND_CELL_SIZE); - Int y = REAL_TO_INT_FLOOR(pos->y/PATHFIND_CELL_SIZE); - - PathfindCell *toCell = nullptr; - toCell = getCell( layer, x, y ); - - if (toCell == nullptr) - return false; - // Only do terrain, not obstacle cells. jba. - if (toCell->getType()==PathfindCell::CELL_OBSTACLE) return true; - if (toCell->getType()==PathfindCell::CELL_IMPASSABLE) return true; - if (toCell->getLayer()!=LAYER_GROUND && toCell->getLayer() == PathfindCell::CELL_CLEAR) { - return true; - } - // check validity of destination cell - LocomotorSurfaceTypeMask acceptableSurfaces = validLocomotorSurfacesForCellType(toCell->getType()); - if ((locomotor->getLegalSurfaces() & acceptableSurfaces) == 0) - return false; - return true; -} - -// -// Releases the cells on the open & closed lists. -// -void Pathfinder::cleanOpenAndClosedLists() { - Int count = 0; - if (!m_openList.empty()) { - count += PathfindCell::releaseOpenList(m_openList); - m_openList.reset(); - } - -#if RETAIL_COMPATIBLE_PATHFINDING - // TheSuperHackers @info this is here as the map cells are contained within the pathfinder and cannot be cleaned externally. - // If the crash mode within PathfindCell::releaseOpenList is hit, it will set s_forceCleanCells to allow the system to cleanly recover. - if (s_forceCleanCells) { - forceCleanCells(); - // TheSuperHackers @info cells on the closed list are forcefully cleaned up by this point - s_forceCleanCells = false; - } -#endif - - if (!m_closedList.empty()) { - count += PathfindCell::releaseClosedList(m_closedList); - m_closedList.reset(); - } - -#if RETAIL_COMPATIBLE_PATHFINDING - // TheSuperHackers @info this is here and performs the same function as the above block, but for when the crash occurs within the closed list. - // If the crash mode within PathfindCell::releaseClosedList is hit, it will set s_forceCleanCells to allow the system to cleanly recover. - if (s_forceCleanCells) { - forceCleanCells(); - s_forceCleanCells = false; - } -#endif - - m_cumulativeCellsAllocated += count; -} - - -// -// Return true if we can move onto this position -// -Bool Pathfinder::validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask acceptableSurfaces, - PathfindCell *toCell, PathfindCell *fromCell ) -{ - if (toCell == nullptr) - return false; - - // check if the destination cell is classified as an obstacle, - // and we happen to be ignoring it - if (toCell->isObstaclePresent( m_ignoreObstacleID )) - return true; - - if (isCrusher && toCell->isObstacleFence()) { - return true; - } - - // check validity of destination cell - LocomotorSurfaceTypeMask cellSurfaces = validLocomotorSurfacesForCellType(toCell->getType()); - if ((cellSurfaces & acceptableSurfaces) == 0) - return false; - - return true; -} - -/** - * Checks to see if obj can occupy the pathfind cell at x,y. - * Returns false if there is another unit's goal already there. - * Assumes your locomotor already said you can go there. - */ -Bool Pathfinder::checkDestination(const Object *obj, Int cellX, Int cellY, PathfindLayerEnum layer, Int iRadius, Bool centerInCell) -{ - // If obj==nullptr, means we are checking for any ground units present. jba. - Int numCellsAbove = iRadius; - if (centerInCell) numCellsAbove++; - Bool checkForAircraft = false; - Int i, j; - ObjectID ignoreId = INVALID_ID; - ObjectID objID = INVALID_ID; - if (obj && obj->getAIUpdateInterface()) { - ignoreId = obj->getAIUpdateInterface()->getIgnoredObstacleID(); - checkForAircraft = obj->getAI()->isAircraftThatAdjustsDestination(); - objID = obj->getID(); - } - for (i=cellX-iRadius; iisAircraftGoal()) { - continue; - } - if (cell->getGoalAircraft() == objID) { - continue; - } - return false; - } - - if (cell->getType()==PathfindCell::CELL_OBSTACLE) { - if (cell->isObstaclePresent( ignoreId )) - continue; - return false; - } - -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - if (IS_IMPASSABLE(cell->getType())) { - return false; - } -#endif - - if (cell->getFlags() == PathfindCell::NO_UNITS) { - continue; // Nobody is here, so it's ok. - } - - ObjectID goalUnitID = cell->getGoalUnit(); - if (goalUnitID == objID) { - continue; // we got it. - } - - if (goalUnitID == ignoreId) { - continue; // we are ignoring it. - } - - if (goalUnitID == INVALID_ID) { - continue; - } - - if (!obj) { - return false; - } - Object *unit = TheGameLogic->findObjectByID(goalUnitID); - if (!unit) { - continue; - } - - // order matters: we want to know if I consider it to be an ally, not vice versa - if (obj->getRelationship(unit) == ALLIES) { - return false; // Don't usurp your allies goals. jba. - } - if (cell->getFlags()==PathfindCell::UNIT_PRESENT_FIXED) { - Bool canCrush = obj->canCrushOrSquish(unit, TEST_CRUSH_OR_SQUISH); - if (!canCrush) { - return false; // Don't move to an occupied cell. - } - } - } - } - return true; -} - -/** - * Checks to see if obj can move through the pathfind cell at x,y. - * Returns false if there are other units already there. - * Assumes your locomotor already said you can go there. - */ -Bool Pathfinder::checkForMovement(const Object *obj, TCheckMovementInfo &info) -{ - info.allyFixedCount = 0; - info.allyMoving = false; - info.allyGoal = false; - info.enemyFixed = false; - - const Int maxAlly = 5; - ObjectID allies[maxAlly]; - Int numAlly = 0; - - if (!obj) { - return true; // not object can move there. - } - - ObjectID ignoreId = INVALID_ID; - if (obj->getAIUpdateInterface()) { - ignoreId = obj->getAIUpdateInterface()->getIgnoredObstacleID(); - } - - Int numCellsAbove = info.radius; - if (info.centerInCell) numCellsAbove++; - Int i, j; -// Bool isInfantry = obj->isKindOf(KINDOF_INFANTRY); - for (i=info.cell.x-info.radius; igetFlags(); - if ((flags == PathfindCell::UNIT_GOAL) || (flags == PathfindCell::UNIT_GOAL_OTHER_MOVING)) { - info.allyGoal = true; - } else if (flags == PathfindCell::NO_UNITS) { - continue; // Nobody is here, so it's ok. - } - - ObjectID posUnit = cell->getPosUnit(); - if (posUnit == obj->getID()) { - continue; // we got it. - } - - if (posUnit == ignoreId) { - continue; // we are ignoring this one. - } - - Bool check = false; - Object *unit = nullptr; - if (flags == PathfindCell::UNIT_PRESENT_MOVING || flags == PathfindCell::UNIT_GOAL_OTHER_MOVING) { - unit = TheGameLogic->findObjectByID(posUnit); - // order matters: we want to know if I consider it to be an ally, not vice versa - if (unit && obj->getRelationship(unit) == ALLIES) { - info.allyMoving = true; - } - if (info.considerTransient) { - check = true; - } - } - if (flags == PathfindCell::UNIT_PRESENT_FIXED) { - check = true; - unit = TheGameLogic->findObjectByID(posUnit); - } - if (check && unit!=nullptr) { - if (obj->getAIUpdateInterface() && obj->getAIUpdateInterface()->getIgnoredObstacleID()==unit->getID()) { - // Don't check if it's the ignored obstacle. - check = false; - } - } - if (!check || !unit) { - continue; - } - -#ifdef INFANTRY_MOVES_THROUGH_INFANTRY - if (obj->isKindOf(KINDOF_INFANTRY) && unit->isKindOf(KINDOF_INFANTRY)) { - // Infantry can run through infantry. - continue; // - } -#endif - // See if it is an ally. - // order matters: we want to know if I consider it to be an ally, not vice versa - if (obj->getRelationship(unit) == ALLIES) { - if (!unit->getAIUpdateInterface()) { - return false; // can't path through not-idle units. - } -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - if (!unit->getAIUpdateInterface()->isIdle()) { - return false; // can't path through not-idle units. - } -#endif - Bool found = false; - Int k; - for (k=0; kgetID()) { - found = true; - } - } - if (!found) { - info.allyFixedCount++; - if (numAlly < maxAlly) { - allies[numAlly] = unit->getID(); - numAlly++; - } - } - } else { - Bool canCrush = obj->canCrushOrSquish( unit, TEST_CRUSH_OR_SQUISH ); - if (!canCrush) { - info.enemyFixed = true; - } - } - } - } - return true; -} - -/** - * Adjusts a coordinate to the center of it's cell. - */ -// Snaps the current position to it's grid location. -void Pathfinder::snapPosition(Object *obj, Coord3D *pos) -{ - Int iRadius; - Bool center; - getRadiusAndCenter(obj, iRadius, center); - ICoord2D cell; - Coord3D adjustDest = *pos; - if (!center) { - adjustDest.x += PATHFIND_CELL_SIZE_F/2; - adjustDest.y += PATHFIND_CELL_SIZE_F/2; - } - worldToCell( &adjustDest, &cell ); - adjustCoordToCell(cell.x, cell.y, center, *pos, LAYER_GROUND); -} - -/** - * Adjusts a goal position to the center of it's cell. - */ -// Snaps the current position to it's grid location. -void Pathfinder::snapClosestGoalPosition(Object *obj, Coord3D *pos) -{ - Int iRadius; - Bool center; - getRadiusAndCenter(obj, iRadius, center); - ICoord2D cell; - Coord3D adjustDest = *pos; - if (!center) { - adjustDest.x += PATHFIND_CELL_SIZE_F/2; - adjustDest.y += PATHFIND_CELL_SIZE_F/2; - } - PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(pos); - worldToCell( &adjustDest, &cell ); - adjustCoordToCell(cell.x, cell.y, center, *pos, LAYER_GROUND); - if (checkDestination(obj, cell.x, cell.y , layer, iRadius, center)) { - return; - } - - // Try adjusting by 1. - Int i,j; - for (i = cell.x - 1; i < cell.x + 2; i++) { - for (j = cell.y - 1; j < cell.y + 2; j++) { - if (checkDestination(obj, i, j, layer, iRadius, center)) { - adjustCoordToCell(i, j, center, *pos, layer); - return; - } - } - } - - if (iRadius > 0) - return; - - // Try to find an unoccupied cell. - for (i = cell.x - 1; i < cell.x + 2; i++) { - for (j = cell.y - 1; j < cell.y + 2; j++) { - PathfindCell* newCell = getCell(layer, i, j); - if (!newCell) - continue; - - if (newCell->getGoalUnit() == INVALID_ID || newCell->getGoalUnit() == obj->getID()) { - adjustCoordToCell(i, j, center, *pos, layer); - return; - } - } - } - - for (i = cell.x - 1; i < cell.x + 2; i++) { - for (j = cell.y - 1; j < cell.y + 2; j++) { - PathfindCell* newCell = getCell(layer, i, j); - if (!newCell) - continue; - - if (newCell->getFlags()!=PathfindCell::UNIT_PRESENT_FIXED) { - adjustCoordToCell(i, j, center, *pos, layer); - return; - } - } - } -} - -/** - * Returns coordinates of goal. - * - */ -Bool Pathfinder::goalPosition(Object *obj, Coord3D *pos) -{ - Int iRadius; - Bool center; - AIUpdateInterface *ai = obj->getAIUpdateInterface(); - if (!ai) return false; // only consider ai objects. - getRadiusAndCenter(obj, iRadius, center); - ICoord2D cell = *ai->getPathfindGoalCell(); - pos->zero(); - if (cell.x<0 || cell.y<0) return false; - adjustCoordToCell(cell.x, cell.y, center, *pos, LAYER_GROUND); - return true; -} - - -Bool Pathfinder::checkForAdjust(Object *obj, const LocomotorSet& locomotorSet, Bool isHuman, - Int cellX, Int cellY, PathfindLayerEnum layer, - Int iRadius, Bool center, Coord3D *dest, const Coord3D *groupDest) -{ - Coord3D adjustDest; - PathfindCell *cellP = getCell(layer, cellX, cellY); - if (cellP==nullptr) return false; - if (cellP && cellP->getType() == PathfindCell::CELL_CLIFF) { - return false; // no final destinations on cliffs. - } - if (isHuman) { - // check if new cell is in logical map. (computer can move off logical map) - if (cellX < m_logicalExtent.lo.x || - cellY < m_logicalExtent.lo.y || - cellX > m_logicalExtent.hi.x || - cellY > m_logicalExtent.hi.y) return false; - } - if (checkDestination(obj, cellX, cellY, layer, iRadius, center)) { - adjustCoordToCell(cellX, cellY, center, adjustDest, cellP->getLayer()); - Bool pathExists; - Bool adjustedPathExists; - if (obj->isKindOf(KINDOF_AIRCRAFT)) { - pathExists = true; - adjustedPathExists = true; - } else { - pathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), dest); - adjustedPathExists = clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &adjustDest); - if (!pathExists) { - if (clientSafeQuickDoesPathExist( locomotorSet, dest, &adjustDest)) { - adjustedPathExists = true; - } - } - } - if ( adjustedPathExists ) { - if (groupDest) { - tightenPath(obj, locomotorSet, &adjustDest, groupDest); - // Check to see if it is a long way to get to the adjusted destination. - Int cost = checkPathCost(obj, locomotorSet, groupDest, &adjustDest); - Int dx = IABS(groupDest->x-adjustDest.x); - Int dy = IABS(groupDest->y-adjustDest.y); - if (1.4f*(dx+dy)getType()) - { - case PathfindCell::CELL_CLIFF: - case PathfindCell::CELL_WATER: - case PathfindCell::CELL_IMPASSABLE: - return false; // no final destinations on cliffs, water, etc. - } - if (checkDestination(nullptr, cellX, cellY, layer, iRadius, center)) { - adjustCoordToCell(cellX, cellY, center, adjustDest, cellP->getLayer()); - *dest = adjustDest; - return true; - } - return false; -} - -/** - * Find an unoccupied spot for a unit to land at. - * Returns false if there are no spots available within a reasonable radius. - */ -Bool Pathfinder::adjustToLandingDestination(Object *obj, Coord3D *dest) -{ - Int iRadius; - Bool center; - getRadiusAndCenter(obj, iRadius, center); - ICoord2D cell; - Coord3D adjustDest = *dest; - - Region3D extent; - TheTerrainLogic->getMaximumPathfindExtent(&extent); - // If the object is off the map & the goal is off the map, it is a scripted setup, so just - // go to the dest. - if (!extent.isInRegionNoZ(dest)) { - if (!extent.isInRegionNoZ(obj->getPosition())) { - return true; - } - } - - if (!center) { - adjustDest.x += PATHFIND_CELL_SIZE_F/2; - adjustDest.y += PATHFIND_CELL_SIZE_F/2; - } - worldToCell( &adjustDest, &cell ); - - Int limit = MAX_ADJUSTMENT_CELL_COUNT; - Int i, j; - i = cell.x; - j = cell.y; - PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(dest); - if (checkForLanding(i,j, layer, iRadius, center, dest)) { - return true; - } - - Int delta=1; - Int count; - while (limit>0) { - for (count = delta; count>0; count--) { - i++; - limit--; - if (checkForLanding(i,j, layer, iRadius, center, dest)) { - return true; - } - } - for (count = delta; count>0; count--) { - j++; - limit--; - if (checkForLanding(i,j, layer, iRadius, center, dest)) { - return true; - } - } - delta++; - for (count = delta; count>0; count--) { - i--; - limit--; - if (checkForLanding(i,j, layer, iRadius, center, dest)) { - return true; - } - } - for (count = delta; count>0; count--) { - j--; - limit--; - if (checkForLanding(i,j, layer, iRadius, center, dest)) { - return true; - } - } - delta++; - } - return false; -} - - -/** - * Find an unoccupied spot for a unit to move to. - * Returns false if there are no spots available within a reasonable radius. - */ -Bool Pathfinder::adjustDestination(Object *obj, const LocomotorSet& locomotorSet, Coord3D *dest, const Coord3D *groupDest) -{ - if( obj->isKindOf(KINDOF_PROJECTILE) ) - { - return true; // missiles can go wherever they want to. jba. - } - - Bool isHuman = true; - if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { - isHuman = false; // computer gets to cheat. - } - Int iRadius; - Bool center; - getRadiusAndCenter(obj, iRadius, center); - ICoord2D cell; - Coord3D adjustDest = *dest; - if (!center) { - adjustDest.x += PATHFIND_CELL_SIZE_F/2; - adjustDest.y += PATHFIND_CELL_SIZE_F/2; - } - worldToCell( &adjustDest, &cell ); - PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(dest); - if (groupDest) { - layer = TheTerrainLogic->getLayerForDestination(groupDest); - } - - Int limit = MAX_ADJUSTMENT_CELL_COUNT; - Int i, j; - i = cell.x; - j = cell.y; - if (checkForAdjust(obj, locomotorSet, isHuman, i,j, layer, iRadius, center, dest, groupDest)) { - return true; - } - - Int delta=1; - Int count; - while (limit>0) { - for (count = delta; count>0; count--) { - i++; - limit--; - if (checkForAdjust(obj, locomotorSet, isHuman, i,j, layer, iRadius, center, dest, groupDest)) { - return true; - } - } - for (count = delta; count>0; count--) { - j++; - limit--; - if (checkForAdjust(obj, locomotorSet, isHuman, i,j, layer, iRadius, center, dest, groupDest)) { - return true; - } - } - delta++; - for (count = delta; count>0; count--) { - i--; - limit--; - if (checkForAdjust(obj, locomotorSet, isHuman, i,j, layer, iRadius, center, dest, groupDest)) { - return true; - } - } - for (count = delta; count>0; count--) { - j--; - limit--; - if (checkForAdjust(obj, locomotorSet, isHuman, i,j, layer, iRadius, center, dest, groupDest)) { - return true; - } - } - delta++; - } - if (groupDest) { - // Didn't work, so just do simple adjust. - return(adjustDestination(obj, locomotorSet, dest, nullptr)); - } - return false; -} - -Bool Pathfinder::checkForTarget(const Object *obj, Int cellX, Int cellY, const Weapon *weapon, - const Object *victim, const Coord3D *victimPos, - Int iRadius, Bool center,Coord3D *dest) -{ - Coord3D adjustDest; - if (checkDestination(obj, cellX, cellY, LAYER_GROUND, iRadius, center)) { - adjustCoordToCell(cellX, cellY, center, adjustDest, LAYER_GROUND); - if (weapon->isGoalPosWithinAttackRange( obj, &adjustDest, victim, victimPos )) { - *dest = adjustDest; - return true; - } - } - return false; -} - -/** - * Find an unoccupied spot for a unit to move to that can fire at victim. - * Returns false if there are no spots available within a reasonable radius. - */ -Bool Pathfinder::adjustTargetDestination(const Object *obj, const Object *target, const Coord3D *targetPos, - const Weapon *weapon, Coord3D *dest) -{ - Int iRadius; - Bool center; - getRadiusAndCenter(obj, iRadius, center); - ICoord2D cell; - Coord3D adjustDest = *dest; - if (!center) { - adjustDest.x += PATHFIND_CELL_SIZE_F/2; - adjustDest.y += PATHFIND_CELL_SIZE_F/2; - } - if (worldToCell( &adjustDest, &cell )) { - return false; // outside of bounds. - } - - Int limit = MAX_ADJUSTMENT_CELL_COUNT; - Int i, j; - i = cell.x; - j = cell.y; - if (checkForTarget(obj, i,j, weapon, target, targetPos, iRadius, center, dest)) { - return true; - } - - Int delta=1; - Int count; - while (limit>0) { - for (count = delta; count>0; count--) { - i++; - limit--; - if (checkForTarget(obj, i,j, weapon, target, targetPos, iRadius, center, dest)) { - return true; - } - } - for (count = delta; count>0; count--) { - j++; - limit--; - if (checkForTarget(obj, i,j, weapon, target, targetPos, iRadius, center, dest)) { - return true; - } - } - delta++; - for (count = delta; count>0; count--) { - i--; - limit--; - if (checkForTarget(obj, i,j, weapon, target, targetPos, iRadius, center, dest)) { - return true; - } - } - for (count = delta; count>0; count--) { - j--; - limit--; - if (checkForTarget(obj, i,j, weapon, target, targetPos, iRadius, center, dest)) { - return true; - } - } - delta++; - } - return false; -} - -Bool Pathfinder::checkForPossible(Bool isCrusher, Int fromZone, Bool center, const LocomotorSet& locomotorSet, - Int cellX, Int cellY, PathfindLayerEnum layer, Coord3D *dest, Bool startingInObstacle) -{ - PathfindCell *goalCell = getCell(layer, cellX, cellY); - if (!goalCell) return false; -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - 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); - } - if (fromZone==zone2) { - adjustCoordToCell(cellX, cellY, center, *dest, layer); - return true; - } - return false; -} - -/** - * Find a pathable spot near the destination. - * Returns false if there are no spots available within a reasonable radius. - */ -Bool Pathfinder::adjustToPossibleDestination(Object *obj, const LocomotorSet& locomotorSet, - Coord3D *dest) -{ - Int radius; - Bool center; - getRadiusAndCenter(obj, radius, center); - ICoord2D goalCellNdx; - Coord3D adjustDest = *dest; - if (!center) { - adjustDest.x += PATHFIND_CELL_SIZE_F/2; - adjustDest.y += PATHFIND_CELL_SIZE_F/2; - } - if (worldToCell( &adjustDest, &goalCellNdx )) { - return false; // outside of bounds. - } - - // determine goal cell - PathfindCell *goalCell; - PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(dest); - - goalCell = getCell(destinationLayer, goalCellNdx.x, goalCellNdx.y); - - - Coord3D from = *obj->getPosition(); - - // determine start cell - ICoord2D startCellNdx; - worldToCell(&from, &startCellNdx); - PathfindLayerEnum layer = LAYER_GROUND; - if (obj) { - layer = obj->getLayer(); - } - PathfindCell *parentCell = getClippedCell( layer, &from ); - if (parentCell == nullptr) { - return false; - } - - Int zone1, zone2; - Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; - zone1 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, parentCell->getZone()); - Bool isObstacle = false; - if (parentCell->getType() == PathfindCell::CELL_OBSTACLE) { - isObstacle = true; - } - if (isObstacle) { - zone1 = m_zoneManager.getEffectiveTerrainZone(zone1); - zone1 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, zone1); - } - - zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, goalCell->getZone()); - - if (zone1 == zone2) { - if (checkDestination(obj, goalCellNdx.x, goalCellNdx.y, destinationLayer, radius, center)) { - return true; - } - } - - Int limit = MAX_ADJUSTMENT_CELL_COUNT; - Int i, j; - i = goalCellNdx.x; - j = goalCellNdx.y; - - Int delta=1; - Int count; - while (limit>0) { - for (count = delta; count>0; count--) { - i++; - limit--; - if (checkForPossible(isCrusher, zone1, center, locomotorSet, i,j, destinationLayer, dest, isObstacle)) { - if (checkDestination(obj, i, j, destinationLayer, radius, center)) { - return true; - } - } - } - for (count = delta; count>0; count--) { - j++; - limit--; - if (checkForPossible(isCrusher, zone1, center, locomotorSet, i,j, destinationLayer, dest, isObstacle)) { - if (checkDestination(obj, i, j, destinationLayer, radius, center)) { - return true; - } - } - } - delta++; - for (count = delta; count>0; count--) { - i--; - limit--; - if (checkForPossible(isCrusher, zone1, center, locomotorSet, i,j, destinationLayer, dest, isObstacle)) { - if (checkDestination(obj, i, j, destinationLayer, radius, center)) { - return true; - } - } - } - for (count = delta; count>0; count--) { - j--; - limit--; - if (checkForPossible(isCrusher, zone1, center, locomotorSet, i,j, destinationLayer, dest, isObstacle)) { - if (checkDestination(obj, i, j, destinationLayer, radius, center)) { - return true; - } - } - } - delta++; - } - return false; -} - - -/** - * Queues an object to do a pathfind. - * It will call the object's ai update->doPathfind() during processPathfindQueue(). - */ -Bool Pathfinder::queueForPath(ObjectID id) -{ -#ifdef DEBUG_LOGGING - { - Object *tmpObj = TheGameLogic->findObjectByID(id); - if (tmpObj) { - AIUpdateInterface *tmpAI = tmpObj->getAIUpdateInterface(); - if (tmpAI) { - const Coord3D* pos = tmpAI->friend_getRequestedDestination(); - DEBUG_ASSERTLOG(pos->x != 0.0 && pos->y != 0.0, ("Queueing pathfind to (0, 0), usually a bug. (Unit Name: '%s', Type: '%s')", tmpObj->getName().str(), tmpObj->getTemplate()->getName().str())); - } - } - } -#endif - - /* Check & see if we are already queued. */ - Int slot = m_queuePRHead; - while (slot != m_queuePRTail) { - if (m_queuedPathfindRequests[slot] == id) { - return true; - } - slot++; - if (slot >= PATHFIND_QUEUE_LEN) { - slot = 0; - } - } - - // Tail is the first available slot. - Int nextSlot = m_queuePRTail+1; - if (nextSlot >= PATHFIND_QUEUE_LEN) { - nextSlot = 0; - } - if (nextSlot==m_queuePRHead) { - DEBUG_CRASH(("Ran out of pathfind queue slots.")); - return false; - } - m_queuedPathfindRequests[m_queuePRTail] = id; - m_queuePRTail = nextSlot; - return true; -} - -#if defined(RTS_DEBUG) -void Pathfinder::doDebugIcons() { - const Int FRAMES_TO_SHOW_OBSTACLES = 100; - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - // render AI debug information - if (TheGlobalData->m_debugAI!=AI_DEBUG_CELLS && TheGlobalData->m_debugAI!=AI_DEBUG_TERRAIN) { - return; - } - - RGBColor color; - color.red = color.green = color.blue = 0; - addIcon(nullptr, 0, 0, color); // clear. - Coord3D topLeftCorner; - Bool showCells = TheGlobalData->m_debugAI==AI_DEBUG_CELLS; - Int i; - for (i=0; i<=LAYER_LAST; i++) { - m_layers[i].doDebugIcons(); - } - if (!showCells) { - frameToShowObstacles = TheGameLogic->getFrame()+FRAMES_TO_SHOW_OBSTACLES; - //return; - } - // show the pathfind grid - for( int j=0; jy; j++ ) - { - topLeftCorner.y = (Real)j * PATHFIND_CELL_SIZE_F; - - for( int i=0; ix; i++ ) - { - topLeftCorner.x = (Real)i * PATHFIND_CELL_SIZE_F; - - color.red = color.green = color.blue = 0; - Bool empty = true; - - const PathfindCell *cell = TheAI->pathfinder()->getCell( LAYER_GROUND, i, j ); - if (cell) - { - switch (cell->getType()) - { - case PathfindCell::CELL_CLIFF: - 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; - break; - - case PathfindCell::CELL_WATER: - color.blue = 1; - empty = false; - break; - - case PathfindCell::CELL_RUBBLE: - color.red = 1; - color.green = 0.5; - empty = false; - break; - - case PathfindCell::CELL_OBSTACLE: - color.red = color.green = 1; - empty = false; - break; - default: - if (cell->getPinched()) { - color.blue = color.green = 0.7f; - empty = false; - } - break; - } - } - if (showCells) { - empty = true; - color.red = color.green = color.blue = 0; - if (empty && cell) { - if (cell->getFlags()!=PathfindCell::NO_UNITS) { - empty = false; - if (cell->getFlags() == PathfindCell::UNIT_GOAL) { - color.red = 1; - } else if (cell->getFlags() == PathfindCell::UNIT_PRESENT_FIXED) { - color.green = color.blue = color.red = 1; - } else if (cell->getFlags() == PathfindCell::UNIT_PRESENT_MOVING) { - color.green = 1; - } else { - color.green = color.red = 1; - } - } - if (cell->isAircraftGoal()) { - empty = false; - color.red = 0; - color.green = color.blue = 1; - } - } - } - if (!empty) { - Coord3D loc; - loc.x = topLeftCorner.x + PATHFIND_CELL_SIZE_F/2.0f; - loc.y = topLeftCorner.y + PATHFIND_CELL_SIZE_F/2.0f; - loc.z = TheTerrainLogic->getGroundHeight(loc.x , loc.y); - addIcon(&loc, PATHFIND_CELL_SIZE_F*0.8f, FRAMES_TO_SHOW_OBSTACLES-1, color); - } - } - - } -} -#endif - - -//------------------------------------------------------------------------------------------------- -/** - * Create an aircraft path. Just jogs around tall buildings marked with KINDOF_AIRCRAFT_PATH_AROUND. - */ -Path *Pathfinder::getAircraftPath( const Object *obj, const Coord3D *to ) -{ - // for now, quick path objects don't pathfind, generally airborne units - // build a trivial one-node path containing destination, then avoid buildings. - Path *thePath = newInstance(Path); - const AIUpdateInterface *ai = obj->getAI(); - ObjectID avoidObject = INVALID_ID; - if (ai) { - avoidObject = ai->getBuildingToNotPathAround(); - } - - // If it is an aircraft that circles (like raptors & migs) we need to adjust the destination - // to one that doesn't clip buildings. - Bool checkClips = false; - if (ai && ai->getCurLocomotor()) { - if (ai->getCurLocomotor()->getAppearance() == LOCO_WINGS) { - checkClips = true; - } - } - - Real radius = 100; - Coord3D adjDest = *to; - if (checkClips) { - circleClipsTallBuilding(obj->getPosition(), to, radius, avoidObject, &adjDest); - } - thePath->prependNode(&adjDest, LAYER_GROUND); - Coord3D pos = *obj->getPosition(); - pos.z = to->z; - thePath->prependNode( &pos, LAYER_GROUND ); - Int limit = 20; - PathNode *curNode = thePath->getFirstNode(); - while (curNode && curNode->getNext()) { - Coord3D newPos1, newPos2, newPos3; - if (segmentIntersectsTallBuilding(curNode, curNode->getNext(), avoidObject, &newPos1, &newPos2, &newPos3)) { - PathNode *newNode3 = newInstance(PathNode); - newNode3->setPosition( &newPos3 ); - newNode3->setLayer(LAYER_GROUND); - curNode->append(newNode3); - PathNode *newNode2 = newInstance(PathNode); - newNode2->setPosition( &newPos2 ); - newNode2->setLayer(LAYER_GROUND); - curNode->append(newNode2); - PathNode *newNode1 = newInstance(PathNode); - newNode1->setPosition( &newPos1 ); - newNode1->setLayer(LAYER_GROUND); - curNode->append(newNode1); - curNode = newNode2; - } - curNode = curNode->getNext(); - limit--; - if (limit<0) break; - } - - curNode = thePath->getFirstNode(); - while (curNode && curNode->getNext()) { - curNode->setNextOptimized(curNode->getNext()); - curNode = curNode->getNext(); - } - thePath->markOptimized(); - if (TheGlobalData->m_debugAI==AI_DEBUG_PATHS) { - TheAI->pathfinder()->setDebugPath(thePath); - } - - return thePath; -} - - -/** - * Process some path requests in the pathfind queue. - */ -//DECLARE_PERF_TIMER(processPathfindQueue) -void Pathfinder::processPathfindQueue() -{ - //USE_PERF_TIMER(processPathfindQueue) - if (!m_isMapReady) { - return; - } -#ifdef DEBUG_QPF -#ifdef DEBUG_LOGGING - Int startTimeMS = ::GetTickCount(); - __int64 startTime64; - double timeToUpdate=0.0f; - __int64 endTime64,freq64; - QueryPerformanceFrequency((LARGE_INTEGER *)&freq64); - QueryPerformanceCounter((LARGE_INTEGER *)&startTime64); -#endif -#endif - - if (m_zoneManager.needToCalculateZones()) { - m_zoneManager.calculateZones(m_map, m_layers, m_extent); - return; - } - - // Get the current logical extent. - Region3D terrainExtent; - TheTerrainLogic->getExtent( &terrainExtent ); - IRegion2D bounds; - bounds.lo.x = REAL_TO_INT_FLOOR(terrainExtent.lo.x / PATHFIND_CELL_SIZE_F); - bounds.hi.x = REAL_TO_INT_FLOOR(terrainExtent.hi.x / PATHFIND_CELL_SIZE_F); - bounds.lo.y = REAL_TO_INT_FLOOR(terrainExtent.lo.y / PATHFIND_CELL_SIZE_F); - bounds.hi.y = REAL_TO_INT_FLOOR(terrainExtent.hi.y / PATHFIND_CELL_SIZE_F); - bounds.hi.x--; - bounds.hi.y--; - 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]); - m_queuedPathfindRequests[m_queuePRHead] = INVALID_ID; - if (obj) { - AIUpdateInterface *ai = obj->getAIUpdateInterface(); - if (ai) { - ai->doPathfind(this); -#ifdef DEBUG_QPF - pathsFound++; -#endif - } - } - m_queuePRHead = m_queuePRHead+1; - if (m_queuePRHead >= PATHFIND_QUEUE_LEN) { - m_queuePRHead = 0; - } - } -#ifdef DEBUG_QPF - if (pathsFound>0) { -#ifdef DEBUG_LOGGING - QueryPerformanceCounter((LARGE_INTEGER *)&endTime64); - timeToUpdate = ((double)(endTime64-startTime64) / (double)(freq64)); - if (timeToUpdate>0.01f) - { - DEBUG_LOG(("%d Pathfind queue: %d paths, %d cells --", TheGameLogic->getFrame(), pathsFound, m_cumulativeCellsAllocated)); - DEBUG_LOG(("time %f (%f)", timeToUpdate, (::GetTickCount()-startTimeMS)/1000.0f)); - } -#endif - } -#endif -#if defined(RTS_DEBUG) - doDebugIcons(); -#endif - -} - - -void Pathfinder::checkChangeLayers(PathfindCell *parentCell) -{ - if (parentCell->getConnectLayer() == LAYER_INVALID) - return; - - ICoord2D newCellCoord = { parentCell->getXIndex(), parentCell->getYIndex() }; - PathfindCell *newCell = getCell(parentCell->getConnectLayer(), newCellCoord.x, newCellCoord.y ); - - if (!newCell) { - DEBUG_CRASH(("Couldn't find cell.")); - return; - } - - // already on one of the lists - if (newCell->hasInfo() && (newCell->getOpen() || newCell->getClosed())) { - return; - } - - if (!newCell->allocateInfo(newCellCoord)) { - // Out of cells for pathing... - return; - } - // compute cost of path thus far - // keep track of path we're building - point back to cell we moved here from - newCell->setParentCell(parentCell) ; - // store cost of this path - newCell->setCostSoFar(parentCell->getCostSoFar()); // same as parent cost - newCell->setTotalCost(parentCell->getTotalCost()); - // insert newCell in open list such that open list is sorted, smallest total path cost first - newCell->putOnSortedOpenList( m_openList ); -} - -bool Pathfinder::checkCellOutsideExtents(ICoord2D& cell) { - return cell.x < m_logicalExtent.lo.x || - cell.x > m_logicalExtent.hi.x || - cell.y < m_logicalExtent.lo.y || - cell.y > m_logicalExtent.hi.y; -} - - -struct ExamineCellsStruct -{ - Pathfinder *thePathfinder; - const LocomotorSet *theLoco; - Bool centerInCell; - Bool isHuman; - Int radius; - const Object *obj; - PathfindCell *goalCell; -}; - -/*static*/ Int Pathfinder::examineCellsCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - ExamineCellsStruct* d = (ExamineCellsStruct*)userData; - Bool isCrusher = d->obj ? d->obj->getCrusherLevel() > 0 : false; - if (d->thePathfinder->m_isTunneling) return 1; // abort. - if (from && to) { - if (!d->thePathfinder->validMovementPosition( isCrusher, d->theLoco->getValidSurfaces(), to, from )) { - return 1; - } - if ( (to->getLayer() == LAYER_GROUND) && !d->thePathfinder->m_zoneManager.isPassable(to_x, to_y) ) { - return 1; - } - - if (to->getPinched()) { - return 1; // abort. - } - if (d->isHuman) { - // check if new cell is in logical map. (computer can move off logical map) - if (to_x < d->thePathfinder->m_logicalExtent.lo.x) return 1; // abort - if (to_y < d->thePathfinder->m_logicalExtent.lo.y) return 1; // abort - if (to_x > d->thePathfinder->m_logicalExtent.hi.x) return 1; // abort - if (to_y > d->thePathfinder->m_logicalExtent.hi.y) return 1; // abort - } - TCheckMovementInfo info; - info.cell.x = to_x; - info.cell.y = to_y; - info.layer = from->getLayer(); - info.centerInCell = d->centerInCell; - info.radius = d->radius; - info.considerTransient = false; - info.acceptableSurfaces = d->theLoco->getValidSurfaces(); - if (!d->thePathfinder->checkForMovement(d->obj, info)) { - return 1; //abort. - } - - if (info.enemyFixed) { - return 1; //abort. - } - - if (info.allyFixedCount) { - return 1; //abort. - } - - UnsignedInt newCostSoFar = from->getCostSoFar() + 0.5f*COST_ORTHOGONAL; - if (to->getType() == PathfindCell::CELL_CLIFF ) { - return 1; - } - - ICoord2D newCellCoord; - newCellCoord.x = to_x; - newCellCoord.y = to_y; - - if (!to->allocateInfo(newCellCoord)) { - // Out of cells for pathing... - return 1; - } - to->setBlockedByAlly(false); - Int costRemaining = 0; - costRemaining = to->costToGoal( d->goalCell ); - - // check if this neighbor cell is already on the open (waiting to be tried) - // or closed (already tried) lists - if ( to->hasInfo() && (to->getOpen() || to->getClosed()) ) - { - // already on one of the lists - if existing costSoFar is less, - // the new cell is on a longer path, so skip it - if (to->getCostSoFar() <= newCostSoFar) - return 0; // keep going. - } - - to->setCostSoFar(newCostSoFar); - // keep track of path we're building - point back to cell we moved here from - to->setParentCell(from) ; - to->setTotalCost(to->getCostSoFar() + costRemaining) ; - - // if to was on closed list, remove it from the list - if (to->getClosed()) - to->removeFromClosedList( d->thePathfinder->m_closedList ); - - // if the to was already on the open list, remove it so it can be re-inserted in order - if (to->getOpen()) - to->removeFromOpenList( d->thePathfinder->m_openList ); - - // insert to in open list such that open list is sorted, smallest total path cost first - to->putOnSortedOpenList( d->thePathfinder->m_openList ); - } - - return 0; // keep going -} - - -Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell *goalCell, const LocomotorSet& locomotorSet, - Bool isHuman, Bool centerInCell, Int radius, const ICoord2D &startCellNdx, - const Object *obj, Int attackDistance) -{ - Bool canPathThroughUnits = false; - if (obj && obj->getAIUpdateInterface()) { - canPathThroughUnits = obj->getAIUpdateInterface()->canPathThroughUnits(); - } - Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; - if (attackDistance==NO_ATTACK && !m_isTunneling && !locomotorSet.isDownhillOnly() && goalCell) { - ExamineCellsStruct info; - info.thePathfinder = this; - info.theLoco = &locomotorSet; - info.centerInCell = centerInCell; - info.radius = radius; - info.obj = obj; - info.isHuman = isHuman; - info.goalCell = goalCell; - ICoord2D start, end; - start.x = parentCell->getXIndex(); - start.y = parentCell->getYIndex(); - end.x = goalCell->getXIndex(); - end.y = goalCell->getYIndex(); - iterateCellsAlongLine(start, end, parentCell->getLayer(), examineCellsCallback, &info); - } - - Int cellCount = 0; - // expand search to neighboring orthogonal cells - static ICoord2D delta[] = - { - { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, - { 1, 1 }, { -1, 1 }, { -1, -1 }, { 1, -1 } - }; - const Int numNeighbors = 8; - const Int firstDiagonal = 4; - ICoord2D newCellCoord; - PathfindCell *newCell; - const Int adjacent[5] = {0, 1, 2, 3, 0}; - Bool neighborFlags[8] = { 0 }; - - UnsignedInt newCostSoFar = 0; - - for( int i=0; igetXIndex() + delta[i].x; - newCellCoord.y = parentCell->getYIndex() + delta[i].y; - - // get the neighboring cell - newCell = getCell(parentCell->getLayer(), newCellCoord.x, newCellCoord.y ); - - // check if cell is on the map - if (!newCell) - continue; - - Bool notZonePassable = false; - if ((newCell->getLayer()==LAYER_GROUND) && !m_zoneManager.isPassable(newCellCoord.x, newCellCoord.y)) { - notZonePassable = true; - } - - // check if new cell is in logical map. (computer can move off logical map) - if (isHuman && checkCellOutsideExtents(newCellCoord)) - continue; - - // check if this neighbor cell is already on the open (waiting to be tried) - // or closed (already tried) lists - if ( newCell->hasInfo() && (newCell->getOpen() || newCell->getClosed()) ) - continue; - - if (i>=firstDiagonal) { - // make sure one of the adjacent sides is open. - if (!neighborFlags[adjacent[i-4]] && !neighborFlags[adjacent[i-3]]) { - continue; - } - } - - // do the gravity check here - if ( locomotorSet.isDownhillOnly() ) - { - Coord3D fromPos; - fromPos.x = parentCell->getXIndex() * PATHFIND_CELL_SIZE_F ; - fromPos.y = parentCell->getYIndex() * PATHFIND_CELL_SIZE_F ; - fromPos.z = TheTerrainLogic->getGroundHeight(fromPos.x , fromPos.y); - - Coord3D toPos; - toPos.x = newCellCoord.x * PATHFIND_CELL_SIZE_F ; - toPos.y = newCellCoord.y * PATHFIND_CELL_SIZE_F ; - toPos.z = TheTerrainLogic->getGroundHeight(toPos.x , toPos.y); - - if ( fromPos.z < toPos.z ) - continue; - } - - Bool movementValid = validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), newCell, parentCell); - Bool dozerHack = false; - if (!movementValid && obj->isKindOf(KINDOF_DOZER) && newCell->getType() == PathfindCell::CELL_OBSTACLE) { - Object* obstacle = TheGameLogic->findObjectByID(newCell->getObstacleID()); - if (obstacle && !(obj->getRelationship(obstacle) == ENEMIES)) { - movementValid = true; - dozerHack = true; - } - } - - if (!movementValid && !m_isTunneling) { - continue; - } - - if (!dozerHack) - neighborFlags[i] = true; - - TCheckMovementInfo info; - info.cell = newCellCoord; - info.layer = parentCell->getLayer(); - info.centerInCell = centerInCell; - info.radius = radius; - info.considerTransient = false; - info.acceptableSurfaces = locomotorSet.getValidSurfaces(); - Int dx = newCellCoord.x-startCellNdx.x; - Int dy = newCellCoord.y-startCellNdx.y; - if (dx<0) dx = -dx; - if (dy<0) dy = -dy; - if (dx>1+radius) info.considerTransient = false; - if (dy>1+radius) info.considerTransient = false; - if (!checkForMovement(obj, info) || info.enemyFixed) { - if (!m_isTunneling) { - continue; - } - movementValid = false; - } - - if (movementValid && !newCell->getPinched()) { - //Note to self - only turn off tunneling after check for movement.jba. - m_isTunneling = false; - } - - if (!newCell->hasInfo()) { - if (!newCell->allocateInfo(newCellCoord)) { - // Out of cells for pathing... - return cellCount; - } - cellCount++; - } - - newCostSoFar = newCell->costSoFar( parentCell ); - if (info.allyMoving && dx<10 && dy<10) { - newCostSoFar += 3*COST_DIAGONAL; - } - - if (newCell->getType() == PathfindCell::CELL_CLIFF && !newCell->getPinched() ) { - Coord3D fromPos; - fromPos.x = parentCell->getXIndex() * PATHFIND_CELL_SIZE_F ; - fromPos.y = parentCell->getYIndex() * PATHFIND_CELL_SIZE_F ; - fromPos.z = TheTerrainLogic->getGroundHeight(fromPos.x , fromPos.y); - - Coord3D toPos; - toPos.x = newCellCoord.x * PATHFIND_CELL_SIZE_F ; - toPos.y = newCellCoord.y * PATHFIND_CELL_SIZE_F ; - toPos.z = TheTerrainLogic->getGroundHeight(toPos.x , toPos.y); - - if ( fabs(fromPos.z - toPos.z)getPinched()) { - newCostSoFar += COST_ORTHOGONAL; - } - - newCell->setBlockedByAlly(false); - if (info.allyFixedCount>0) { -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - newCostSoFar += 3*COST_DIAGONAL*info.allyFixedCount; -#else - newCostSoFar += 3*COST_DIAGONAL; -#endif - if (!canPathThroughUnits) - newCell->setBlockedByAlly(true); - } - - Int costRemaining = 0; - if (goalCell) { - if (attackDistance == NO_ATTACK) { - costRemaining = newCell->costToGoal( goalCell ); - } else { - dx = newCellCoord.x - goalCell->getXIndex(); - dy = newCellCoord.y - goalCell->getYIndex(); - costRemaining = COST_ORTHOGONAL*sqrt(dx*dx + dy*dy); - costRemaining -= attackDistance/2; - if (costRemaining<0) - costRemaining=0; - if (info.allyGoal) { - if (obj->isKindOf(KINDOF_VEHICLE)) { - newCostSoFar += 3*COST_ORTHOGONAL; - } else { - // Infantry can pass through infantry. - newCostSoFar += COST_ORTHOGONAL; - } - } - } - } - - if (notZonePassable) { - newCostSoFar += 100*COST_ORTHOGONAL; - } - - if (newCell->getType()==PathfindCell::CELL_OBSTACLE) { - newCostSoFar += 100*COST_ORTHOGONAL; - } - - if (m_isTunneling) { - if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), newCell, parentCell )) { - newCostSoFar += 10*COST_ORTHOGONAL; - } - } - - newCell->setCostSoFar(newCostSoFar); - // keep track of path we're building - point back to cell we moved here from - newCell->setParentCell(parentCell) ; - if (m_isTunneling) { - costRemaining = 0; // find the closest valid cell. - } - newCell->setTotalCost(newCell->getCostSoFar() + costRemaining) ; - - // if newCell was on closed list, remove it from the list - if (newCell->getClosed()) - newCell->removeFromClosedList( m_closedList ); - - // if the newCell was already on the open list, remove it so it can be re-inserted in order - if (newCell->getOpen()) - newCell->removeFromOpenList( m_openList ); - - // insert newCell in open list such that open list is sorted, smallest total path cost first - newCell->putOnSortedOpenList( m_openList ); - } - return cellCount; -} - - -/** - * Find a short, valid path between given locations. - * Uses A* algorithm. - */ -Path *Pathfinder::findPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, - const Coord3D *rawTo) -{ - if (!clientSafeQuickDoesPathExist(locomotorSet, from, rawTo)) { - return nullptr; - } - Bool isHuman = true; - if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { - isHuman = false; // computer gets to cheat. - } - - m_zoneManager.clearPassableFlags(); - Path *hPat = findHierarchicalPath(isHuman, locomotorSet, from, rawTo, false); - if (hPat) { - deleteInstance(hPat); - } else { - m_zoneManager.setAllPassable(); - } - - Path *pat = internalFindPath(obj, locomotorSet, from, rawTo); - if (pat!=nullptr) { - return pat; - } - - return nullptr; -} -/** - * Find a short, valid path between given locations. - * Uses A* algorithm. - */ -Path *Pathfinder::internalFindPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, - const Coord3D *rawTo) -{ - //CRCDEBUG_LOG(("Pathfinder::findPath()")); -#ifdef INTENSE_DEBUG - DEBUG_LOG(("internal find path...")); -#endif - -#ifdef DEBUG_LOGGING - Int startTimeMS = ::GetTickCount(); -#endif - Bool centerInCell = true; - Int radius = 0; - if (obj) { - getRadiusAndCenter(obj, radius, centerInCell); - } - - Bool isHuman = true; - if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { - isHuman = false; // computer gets to cheat. - } - - if (rawTo->x == 0.0f && rawTo->y == 0.0f) { - DEBUG_LOG(("Attempting pathfind to 0,0, generally a bug.")); - return nullptr; - } - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - if (m_isMapReady == false) { - return nullptr; - } - - Coord3D adjustTo = *rawTo; - Coord3D *to = &adjustTo; - Coord3D clipFrom = *from; - clip(&clipFrom, &adjustTo); - - if (!centerInCell) { - adjustTo.x += PATHFIND_CELL_SIZE_F/2; - adjustTo.y += PATHFIND_CELL_SIZE_F/2; - } - - m_isTunneling = false; - - PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); - // determine goal cell - PathfindCell *goalCell = getCell( destinationLayer, to ); - if (goalCell == nullptr) { - return nullptr; - } - - ICoord2D cell; - worldToCell( to, &cell ); - - if (!checkDestination(obj, cell.x, cell.y, destinationLayer, radius, centerInCell)) { - return nullptr; - } - // determine start cell - ICoord2D startCellNdx; - worldToCell(&clipFrom, &startCellNdx); - PathfindLayerEnum layer = LAYER_GROUND; - if (obj) { - layer = obj->getLayer(); - } - PathfindCell *parentCell = getClippedCell( layer,&clipFrom ); - if (parentCell == nullptr) { - return nullptr; - } - - ICoord2D pos2d; - worldToCell(to, &pos2d); - if (!goalCell->allocateInfo(pos2d)) { - return nullptr; - } - if (parentCell!=goalCell) { - worldToCell(&clipFrom, &pos2d); - if (!parentCell->allocateInfo(pos2d)) { - goalCell->releaseInfo(); - return nullptr; - } - } - // - // Determine if this pathfind is "tunneling" or not. - // A tunneling pathfind starts from within an obstacle, and is allowed - // to ignore obstacle cells until it reaches a cell that is no longer - // classified as an obstacle. At that point, the pathfind behaves normally. - // - m_isTunneling = parentCell->getType() == PathfindCell::CELL_OBSTACLE; - - Int zone1, zone2; - Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; - zone1 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, parentCell->getZone()); - zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, goalCell->getZone()); - - if ( (layer==LAYER_WALL && zone1 == 0) || (destinationLayer==LAYER_WALL && zone2 == 0) ) { -#if RETAIL_COMPATIBLE_PATHFINDING - if (s_useFixedPathfinding) -#endif - { - goalCell->releaseInfo(); - parentCell->releaseInfo(); - } - return nullptr; - } - - if (goalCell->isObstaclePresent(m_ignoreObstacleID) || m_isTunneling) { - // Use terrain zones instead of building zones, since we are moving into or out of a building. - zone2 = m_zoneManager.getEffectiveTerrainZone(zone2); - zone2 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, zone2); - zone1 = m_zoneManager.getEffectiveTerrainZone(zone1); - zone1 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), isCrusher, zone1); - } - - //DEBUG_LOG(("Zones %d to %d", zone1, zone2)); - - if ( zone1 != zone2) { - //DEBUG_LOG(("Intense Debug Info - Pathfind Zone screen failed-cannot reach desired location.")); - goalCell->releaseInfo(); - parentCell->releaseInfo(); - return nullptr; - } - - // sanity check - if destination is invalid, can't path there - if (!validMovementPosition( isCrusher, destinationLayer, locomotorSet, to )) { - m_isTunneling = false; - goalCell->releaseInfo(); - parentCell->releaseInfo(); - return nullptr; - } - - // sanity check - if source is invalid, we have to cheat - if (!validMovementPosition( isCrusher, layer, locomotorSet, from )) { - // somehow we got to an impassable location. - m_isTunneling = true; - } - - parentCell->startPathfind(goalCell); - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - // "closed" list is initially empty - m_closedList.reset(); - - Int cellCount = 0; - - // - // Continue search until "open" list is empty, or - // until goal is found. - // - while( !m_openList.empty() ) - { - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - if (parentCell == goalCell) - { - // success - found a path to the goal - Bool show = TheGlobalData->m_debugAI==AI_DEBUG_PATHS; -#ifdef INTENSE_DEBUG - DEBUG_LOG(("internal find path SUCCESS...")); - Int count = 0; - if (cellCount>1000 && obj) { - show = true; - DEBUG_LOG(("cells %d obj %s %x from (%f,%f) to(%f, %f)", count, obj->getTemplate()->getName().str(), obj, from->x, from->y, to->x, to->y)); -#ifdef STATE_MACHINE_DEBUG - if( obj->getAIUpdateInterface() ) - { - DEBUG_LOG(("State %s", obj->getAIUpdateInterface()->getCurrentStateName().str())); - } -#endif - TheScriptEngine->AppendDebugMessage("Big path", false); - } -#endif - if (show) - debugShowSearch(true); - - m_isTunneling = false; - // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, goalCell, centerInCell, false ); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - parentCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - } - return path; - } - - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - - // Check to see if we can change layers in this cell. - checkChangeLayers(parentCell); - - cellCount += examineNeighboringCells(parentCell, goalCell, locomotorSet, isHuman, centerInCell, radius, startCellNdx, obj, NO_ATTACK); - - } - - // failure - goal cannot be reached -#if defined(RTS_DEBUG) -#ifdef INTENSE_DEBUG - DEBUG_LOG(("internal find path FAILURE...")); -#endif - if (TheGlobalData->m_debugAI == AI_DEBUG_PATHS) - { - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - RGBColor color; - color.blue = 0; - color.red = color.green = 1; - addIcon(nullptr, 0, 0, color); - debugShowSearch(false); - Coord3D pos; - pos = *from; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, 3*PATHFIND_CELL_SIZE_F, 600, color); - pos = *to; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, 3*PATHFIND_CELL_SIZE_F, 600, color); - Real dx, dy; - dx = from->x - to->x; - dy = from->y - to->y; - - Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); - if (count<2) count = 2; - Int i; - color.green = 0; - for (i=1; ix + (to->x-from->x)*i/count; - pos.y = from->y + (to->y-from->y)*i/count; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, PATHFIND_CELL_SIZE_F/2, 60, color); - - } - } - - if (obj) { -#ifdef DUMP_PERF_STATS - TheGameLogic->incrementOverallFailedPathfinds(); -#endif -#ifdef STATE_MACHINE_DEBUG - if( obj->getAIUpdateInterface() ) - { - DEBUG_LOG(("state %s", obj->getAIUpdateInterface()->getCurrentStateName().str())); - } -#endif - } -#endif - -#ifdef DEBUG_LOGGING - if (obj) - { - Bool valid; - valid = validMovementPosition( isCrusher, obj->getLayer(), locomotorSet, to ) ; - - DEBUG_LOG(("%d Pathfind failed from (%f,%f) to (%f,%f), OV %d --", TheGameLogic->getFrame(), from->x, from->y, to->x, to->y, valid)); - DEBUG_LOG(("Unit '%s', time %f, cells %d", obj->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f,cellCount)); - } -#endif - - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - goalCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return nullptr; -} - -/** - * Checks to see if there is enough path width at this cell for ground - * movement. Returns the width available. - */ -Int Pathfinder::clearCellForDiameter(Bool crusher, Int cellX, Int cellY, PathfindLayerEnum layer, Int pathDiameter) -{ - Int radius = pathDiameter/2; - Int numCellsAbove = radius; - if (radius==0) numCellsAbove++; - Int i, j; - Bool clear = true; - Bool cutCorners = false; - if (radius>1) { - cutCorners = true; - // We remove the outside corner cells from the check. - } - for (i=cellX-radius; igetType() != PathfindCell::CELL_CLEAR) { - if (cell->getType() == PathfindCell::CELL_OBSTACLE) { - if (cell->isObstacleFence()) { - if (!crusher) { - clear = false; - } - } else { - clear = false; - } - } else { - clear = false; - } - } - if (cell->getFlags() == PathfindCell::UNIT_PRESENT_FIXED && pathDiameter>=2) { - Object *obj = TheGameLogic->findObjectByID(cell->getPosUnit()); - if (obj) { - if (crusher) { - if (obj->getCrushableLevel()>1) { - clear = false; - } - } else { - if (obj->getCrushableLevel()>0) { - clear = false; - } - } - } - } - } else { - return false; // off the map. - } - if (!clear) break; - } - } - if (clear) { - if (radius==0) return 1; - return 2*radius; - } - if (pathDiameter < 2) return 0; - return clearCellForDiameter(crusher, cellX, cellY, layer, pathDiameter-2); -} - -/** - * Work backwards from goal cell to construct final path. - */ -Path *Pathfinder::buildGroundPath(Bool isCrusher, const Coord3D *fromPos, PathfindCell *goalCell, Bool center, Int pathDiameter ) -{ - DEBUG_ASSERTCRASH( goalCell, ("Pathfinder::buildActualPath: goalCell == nullptr") ); - - Path *path = newInstance(Path); - - prependCells(path, fromPos, goalCell, center); - - // cleanup the path by checking line of sight - path->optimizeGroundPath( isCrusher, pathDiameter ); - - -#if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI==AI_DEBUG_GROUND_PATHS) - { - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - RGBColor color; - color.blue = 0; - color.red = color.green = 1; - Coord3D pos; - PathNode *node = path->getFirstNode(); - for( ; node; node = node->getNext() ) - { - - // create objects to show path - they decay - - pos = *node->getPosition(); - color.red = color.green = 1; - if (node->getLayer() != LAYER_GROUND) { - color.red = 0; - } - addIcon(&pos, PATHFIND_CELL_SIZE_F*.25f, 200, color); - } - - // show optimized path - for( node = path->getFirstNode(); node; node = node->getNextOptimized() ) - { - pos = *node->getPosition(); - addIcon(&pos, PATHFIND_CELL_SIZE_F*.8f, 200, color); - } - setDebugPath(path); - } -#endif - return path; -} - -/** - * Work backwards from goal cell to construct final path. - */ -Path *Pathfinder::buildHierarchicalPath( const Coord3D *fromPos, PathfindCell *goalCell ) -{ - DEBUG_ASSERTCRASH( goalCell, ("Pathfinder::buildHierarchicalPath: goalCell == nullptr") ); - - Path *path = newInstance(Path); - - prependCells(path, fromPos, goalCell, true); - -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - // 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) - { - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - RGBColor color; - color.blue = 0; - color.red = color.green = 1; - Coord3D pos; - Int i; - for (i=0; i<3; i++) - for( PathNode *node = path->getFirstNode(); node; node = node->getNext() ) - { - - // create objects to show path - they decay - - pos = *node->getPosition(); - color.red = 1; - color.green = 0.4f; - if (node->getLayer() != LAYER_GROUND) { - color.red = 0; - } - addIcon(&pos, PATHFIND_CELL_SIZE_F, 200, color); - } - setDebugPath(path); - } -#endif - return path; -} - - -struct MADStruct -{ - Pathfinder *thePathfinder; - Object *obj; - ObjectID ignoreID; -}; - -/*static*/ Int Pathfinder::moveAlliesDestinationCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - MADStruct* d = (MADStruct*)userData; - if (to) { - if (to->getPosUnit()==INVALID_ID) { - return 0; - } - if (to->getPosUnit()==d->obj->getID()) { - return 0; // It's us. - } - if (to->getPosUnit()==d->ignoreID) { - return 0; // It's the one we are ignoring. - } - Object *otherObj = TheGameLogic->findObjectByID(to->getPosUnit()); - if (otherObj==nullptr) return 0; - if (d->obj->getRelationship(otherObj)!=ALLIES) { - return 0; // Only move allies. - } - if (otherObj && otherObj->getAI() && !otherObj->getAI()->isMoving()) { -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - //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); - } - } - - return 0; // keep going -} - -void Pathfinder::moveAlliesAwayFromDestination(Object *obj,const Coord3D& destination) -{ - MADStruct info; - info.obj = obj; - info.ignoreID = obj->getAI()->getIgnoredObstacleID(); - info.thePathfinder = this; - PathfindLayerEnum layer = obj->getLayer(); - if (layer==LAYER_GROUND) { - layer = TheTerrainLogic->getLayerForDestination(&destination); - } - iterateCellsAlongLine(*obj->getPosition(), destination, layer, moveAlliesDestinationCallback, &info); - -} - - -struct GroundCellsStruct -{ - Pathfinder *thePathfinder; - Bool centerInCell; - Int pathDiameter; - PathfindCell *goalCell; - Bool crusher; -}; - -/*static*/ Int Pathfinder::groundCellsCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - GroundCellsStruct* d = (GroundCellsStruct*)userData; - if (from && to) { - if (to->hasInfo()) { - if (to->getOpen() || to->getClosed()) - { - // already on one of the lists - return 1; // abort. - } - } - // See how wide the cell is. - Int clearDiameter = d->thePathfinder->clearCellForDiameter(d->crusher, to_x, to_y, to->getLayer(), d->pathDiameter); - if (clearDiameter != d->pathDiameter) { - return 1; - } - ICoord2D newCellCoord; - newCellCoord.x = to_x; - newCellCoord.y = to_y; - if (!to->allocateInfo(newCellCoord)) { - // Out of cells for pathing... - return 1; - } - - UnsignedInt newCostSoFar = from->getCostSoFar() + 0.5f*COST_ORTHOGONAL; - to->setBlockedByAlly(false); - - Int costRemaining = 0; - costRemaining = to->costToGoal( d->goalCell ); - to->setCostSoFar(newCostSoFar); - // keep track of path we're building - point back to cell we moved here from - to->setParentCell(from) ; - to->setTotalCost(to->getCostSoFar() + costRemaining) ; - - // insert to in open list such that open list is sorted, smallest total path cost first - to->putOnSortedOpenList( d->thePathfinder->m_openList ); - } - - return 0; // keep going -} - -/** - * Find a short, valid path between given locations. - * Uses A* algorithm. - */ -Path *Pathfinder::findGroundPath( const Coord3D *from, - const Coord3D *rawTo, Int pathDiameter, Bool crusher) -{ - //CRCDEBUG_LOG(("Pathfinder::findGroundPath()")); -#ifdef DEBUG_LOGGING - Int startTimeMS = ::GetTickCount(); -#endif -#ifdef INTENSE_DEBUG - DEBUG_LOG(("Find ground path...")); -#endif - Bool centerInCell = false; - - m_zoneManager.clearPassableFlags(); - Bool isHuman = true; - - Path *hPat = internal_findHierarchicalPath(isHuman, LOCOMOTORSURFACE_GROUND, from, rawTo, false, false); - if (hPat) { - deleteInstance(hPat); - } else { - m_zoneManager.setAllPassable(); - } - - if (rawTo->x == 0.0f && rawTo->y == 0.0f) { - DEBUG_LOG(("Attempting pathfind to 0,0, generally a bug.")); - return nullptr; - } - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - if (m_isMapReady == false) { - return nullptr; - } - - Coord3D adjustTo = *rawTo; - Coord3D *to = &adjustTo; - Coord3D clipFrom = *from; - clip(&clipFrom, &adjustTo); - - m_isTunneling = false; - - PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); - - ICoord2D cell; - worldToCell( to, &cell ); - - if (pathDiameter!=clearCellForDiameter(crusher, cell.x, cell.y, destinationLayer, pathDiameter)) { - Int offset=1; - ICoord2D newCell; - const Int MAX_OFFSET = 8; - while (offset= MAX_OFFSET) { - return nullptr; - } - } - - // determine goal cell - PathfindCell *goalCell = getCell( destinationLayer, cell.x, cell.y ); - if (goalCell == nullptr) { - return nullptr; - } - if (!goalCell->allocateInfo(cell)) { - return nullptr; - } - - // determine start cell - ICoord2D startCellNdx; - PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(from); - PathfindCell *parentCell = getClippedCell( layer,&clipFrom ); - if (parentCell == nullptr) { -#if RETAIL_COMPATIBLE_PATHFINDING - if (s_useFixedPathfinding) -#endif - { - goalCell->releaseInfo(); - } - return nullptr; - } - if (parentCell!=goalCell) { - worldToCell(&clipFrom, &startCellNdx); - if (!parentCell->allocateInfo(startCellNdx)) { - goalCell->releaseInfo(); - return nullptr; - } - } - - - Int zone1, zone2; - // m_isCrusher = false; - zone1 = m_zoneManager.getEffectiveZone(LOCOMOTORSURFACE_GROUND, false, parentCell->getZone()); - zone2 = m_zoneManager.getEffectiveZone(LOCOMOTORSURFACE_GROUND, false, goalCell->getZone()); - - //DEBUG_LOG(("Zones %d to %d", zone1, zone2)); - - if ( zone1 != zone2) { - goalCell->releaseInfo(); - parentCell->releaseInfo(); - return nullptr; - } - parentCell->startPathfind(goalCell); - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - // "closed" list is initially empty - m_closedList.reset(); - - // TheSuperHackers @fix helmutbuhler This was originally uninitialized and in the loop below. -#if RETAIL_COMPATIBLE_CRC - UnsignedInt newCostSoFar = 0; -#endif - - // - // Continue search until "open" list is empty, or - // until goal is found. - // - Int cellCount = 0; - while( !m_openList.empty() ) - { - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - if (parentCell == goalCell) - { - // success - found a path to the goal -#ifdef INTENSE_DEBUG - DEBUG_LOG((" time %d msec %d cells", (::GetTickCount()-startTimeMS), cellCount)); - DEBUG_LOG((" SUCCESS")); -#endif -#if defined(RTS_DEBUG) - Bool show = TheGlobalData->m_debugAI==AI_DEBUG_GROUND_PATHS; - if (show) - debugShowSearch(true); -#endif - m_isTunneling = false; - // construct and return path - Path *path = buildGroundPath(crusher, from, goalCell, centerInCell, pathDiameter ); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - parentCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - } - return path; - } - - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - - // Check to see if we can change layers in this cell. - checkChangeLayers(parentCell); - - GroundCellsStruct info; - info.thePathfinder = this; - info.centerInCell = centerInCell; - info.pathDiameter = pathDiameter; - info.goalCell = goalCell; - info.crusher = crusher; - ICoord2D start, end; - start.x = parentCell->getXIndex(); - start.y = parentCell->getYIndex(); - end.x = goalCell->getXIndex(); - end.y = goalCell->getYIndex(); - iterateCellsAlongLine(start, end, parentCell->getLayer(), groundCellsCallback, &info); - - // expand search to neighboring orthogonal cells - static ICoord2D delta[] = - { - { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, - { 1, 1 }, { -1, 1 }, { -1, -1 }, { 1, -1 } - }; - const Int numNeighbors = 8; - const Int firstDiagonal = 4; - ICoord2D newCellCoord; - PathfindCell *newCell; - const Int adjacent[5] = {0, 1, 2, 3, 0}; - Bool neighborFlags[8] = { 0 }; - - // TheSuperHackers @fix Mauller 23/05/2025 Fixes uninitialized variable. -#if RETAIL_COMPATIBLE_CRC - // newCostSoFar defined in outer block. -#else - UnsignedInt newCostSoFar = 0; -#endif - - for( int i=0; igetXIndex() + delta[i].x; - newCellCoord.y = parentCell->getYIndex() + delta[i].y; - - // get the neighboring cell - newCell = getCell(parentCell->getLayer(), newCellCoord.x, newCellCoord.y ); - - // check if cell is on the map - if (newCell == nullptr) - continue; - - if ((newCell->getLayer()==LAYER_GROUND) && !m_zoneManager.isPassable(newCellCoord.x, newCellCoord.y)) { - // check if we are within 3. - Bool passable = false; - if (m_zoneManager.clipIsPassable(newCellCoord.x+3, newCellCoord.y+3)) passable = true; - if (m_zoneManager.clipIsPassable(newCellCoord.x-3, newCellCoord.y+3)) passable = true; - if (m_zoneManager.clipIsPassable(newCellCoord.x+3, newCellCoord.y-3)) passable = true; - if (m_zoneManager.clipIsPassable(newCellCoord.x-3, newCellCoord.y-3)) passable = true; - if (!passable) continue; - } - - // check if this neighbor cell is already on the open (waiting to be tried) - // or closed (already tried) lists - Bool onList = false; - if (newCell->hasInfo()) { - if (newCell->getOpen() || newCell->getClosed()) - { - // already on one of the lists - onList = true; - } - } - Int clearDiameter = 0; - if (newCell!=goalCell) { - - if (i>=firstDiagonal) { - // make sure one of the adjacent sides is open. - if (!neighborFlags[adjacent[i-4]] && !neighborFlags[adjacent[i-3]]) { - continue; - } - } - - // See how wide the cell is. - clearDiameter = clearCellForDiameter(crusher, newCellCoord.x, newCellCoord.y, newCell->getLayer(), pathDiameter); - if (newCell->getType() != PathfindCell::CELL_CLEAR) { - continue; - } - if (newCell->getPinched()) { - continue; - } - neighborFlags[i] = true; - - if (!newCell->allocateInfo(newCellCoord)) { - // Out of cells for pathing... - continue; - } - cellCount++; - -#if RETAIL_COMPATIBLE_CRC - // TheSuperHackers @fix helmutbuhler 11/06/2025 The indentation was wrong on retail here. - newCostSoFar = newCell->costSoFar( parentCell ); - if (clearDiametersetBlockedByAlly(false); - } -#else - } - newCostSoFar = newCell->costSoFar( parentCell ); - if (clearDiametersetBlockedByAlly(false); -#endif - Int costRemaining = 0; - costRemaining = newCell->costToGoal( goalCell ); - - // check if this neighbor cell is already on the open (waiting to be tried) - // or closed (already tried) lists - if (onList) - { - // already on one of the lists - if existing costSoFar is less, - // the new cell is on a longer path, so skip it - if (newCell->getCostSoFar() <= newCostSoFar) - continue; - } - newCell->setCostSoFar(newCostSoFar); - // keep track of path we're building - point back to cell we moved here from - newCell->setParentCell(parentCell) ; - newCell->setTotalCost(newCell->getCostSoFar() + costRemaining) ; - - // if newCell was on closed list, remove it from the list - if (newCell->getClosed()) - newCell->removeFromClosedList( m_closedList ); - - // if the newCell was already on the open list, remove it so it can be re-inserted in order - if (newCell->getOpen()) - newCell->removeFromOpenList( m_openList ); - - // insert newCell in open list such that open list is sorted, smallest total path cost first - newCell->putOnSortedOpenList( m_openList ); - } - } - // failure - goal cannot be reached -#ifdef INTENSE_DEBUG - DEBUG_LOG((" FAILURE")); -#endif -#if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI) - { - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - RGBColor color; - color.blue = 0; - color.red = color.green = 1; - addIcon(nullptr, 0, 0, color); - debugShowSearch(false); - Coord3D pos; - pos = *from; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, 3*PATHFIND_CELL_SIZE_F, 600, color); - pos = *to; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, 3*PATHFIND_CELL_SIZE_F, 600, color); - Real dx, dy; - dx = from->x - to->x; - dy = from->y - to->y; - - Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); - if (count<2) count = 2; - Int i; - color.green = 0; - for (i=1; ix + (to->x-from->x)*i/count; - pos.y = from->y + (to->y-from->y)*i/count; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, PATHFIND_CELL_SIZE_F/2, 60, color); - - } - } -#endif - - DEBUG_LOG(("%d FindGroundPath failed from (%f,%f) to (%f,%f) --", TheGameLogic->getFrame(), from->x, from->y, to->x, to->y)); - DEBUG_LOG(("time %f", (::GetTickCount()-startTimeMS)/1000.0f)); - -#ifdef DUMP_PERF_STATS - TheGameLogic->incrementOverallFailedPathfinds(); -#endif - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - goalCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return nullptr; -} - -/** - * Find a short, valid path between given locations. - * Uses A* algorithm. - */ -void Pathfinder::processHierarchicalCell( const ICoord2D &scanCell, const ICoord2D &delta, PathfindCell *parentCell, - PathfindCell *goalCell, zoneStorageType parentZone, - zoneStorageType *examinedZones, Int &numExZones, - Bool crusher, Int &cellCount) -{ - if (scanCell.xm_extent.hi.x || - scanCell.ym_extent.hi.y) { - return; - } -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - 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 && RETAIL_COMPATIBLE_PATHFINDING - if (newCell->hasInfo() && (newCell->getOpen() || newCell->getClosed())) return; // already looked at this one. -#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; - adjacentCell.y += delta.y; - if (adjacentCell.xm_extent.hi.x || - adjacentCell.ym_extent.hi.y) { - return; - } - PathfindCell *adjNewCell = getCell(LAYER_GROUND, adjacentCell.x, adjacentCell.y); - if (adjNewCell->hasInfo() && (adjNewCell->getOpen() || adjNewCell->getClosed())) return; // already looked at this one. - zoneStorageType parentGlobalZone = m_zoneManager.getEffectiveZone(LOCOMOTORSURFACE_GROUND, crusher, parentZone); - - /// @todo - somehow out of bounds or bogus newZone. - zoneStorageType newZone = m_zoneManager.getBlockZone(LOCOMOTORSURFACE_GROUND, - crusher, adjacentCell.x, adjacentCell.y, m_map); - zoneStorageType newGlobalZone = m_zoneManager.getEffectiveZone(LOCOMOTORSURFACE_GROUND, crusher, newZone); - if (newGlobalZone != parentGlobalZone) { - return; // can't step over. jba. - } - Int j; - Bool found=false; - for (j=0; jallocateInfo(scanCell); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - if (!newCell->getClosed() && !newCell->getOpen()) { - newCell->putOnClosedList(m_closedList); - } - } - else -#endif - { - if (newCell->hasInfo() && !newCell->getClosed() && !newCell->getOpen()) { - newCell->putOnClosedList(m_closedList); - } - } - - adjNewCell->allocateInfo(adjacentCell); - 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 ); - } - - } -} - - -/** - * Find a short, valid path between given locations. - * Uses A* algorithm. - */ -Path *Pathfinder::findHierarchicalPath( Bool isHuman, const LocomotorSet& locomotorSet, const Coord3D *from, - const Coord3D *to, Bool crusher) -{ - return internal_findHierarchicalPath(isHuman, locomotorSet.getValidSurfaces(), from, to, crusher, FALSE); -} - - -/** - * Find a short, valid path between given locations. - * Uses A* algorithm. - */ -Path *Pathfinder::findClosestHierarchicalPath( Bool isHuman, const LocomotorSet& locomotorSet, const Coord3D *from, - const Coord3D *to, Bool crusher) -{ - return internal_findHierarchicalPath(isHuman, locomotorSet.getValidSurfaces(), from, to, crusher, TRUE); -} - - - -/** - * Find a short, valid path between given locations. - * Uses A* algorithm. - */ -Path *Pathfinder::internal_findHierarchicalPath( Bool isHuman, const LocomotorSurfaceTypeMask locomotorSurface, const Coord3D *from, - const Coord3D *rawTo, Bool crusher, Bool closestOK) -{ - //CRCDEBUG_LOG(("Pathfinder::findGroundPath()")); -#ifdef DEBUG_LOGGING - Int startTimeMS = ::GetTickCount(); -#endif - - if (rawTo->x == 0.0f && rawTo->y == 0.0f) { - DEBUG_LOG(("Attempting pathfind to 0,0, generally a bug.")); - return nullptr; - } - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - if (m_isMapReady == false) { - return nullptr; - } - - Coord3D adjustTo = *rawTo; - Coord3D *to = &adjustTo; - Coord3D clipFrom = *from; - clip(&clipFrom, &adjustTo); - - m_isTunneling = false; - - PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); - - ICoord2D cell; - worldToCell( to, &cell ); - - // determine goal cell - PathfindCell *goalCell = getCell( destinationLayer, cell.x, cell.y ); - if (!goalCell) { - return nullptr; - } - - if (!goalCell->allocateInfo(cell)) { - return nullptr; - } - - // determine start cell - ICoord2D startCellNdx; - PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(from); - PathfindCell *parentCell = getClippedCell( layer,&clipFrom ); - if (!parentCell) { - return nullptr; - } - - if (parentCell!=goalCell) { - worldToCell(&clipFrom, &startCellNdx); - if (!parentCell->allocateInfo(startCellNdx)) { - goalCell->releaseInfo(); - return nullptr; - } - } - - Int zone1, zone2; - // m_isCrusher = false; - zone1 = m_zoneManager.getEffectiveZone(locomotorSurface, false, parentCell->getZone()); - zone2 = m_zoneManager.getEffectiveZone(locomotorSurface, false, goalCell->getZone()); - - if ( zone1 != zone2) { - goalCell->releaseInfo(); - parentCell->releaseInfo(); - return nullptr; - } - - parentCell->startPathfind(goalCell); - - // "closed" list is initially empty - m_closedList.reset(); - - Int cellCount = 0; - - zoneStorageType goalBlockZone; - ICoord2D goalBlockNdx; - if (goalCell->getLayer()==LAYER_GROUND) { - goalBlockZone = m_zoneManager.getBlockZone(locomotorSurface, - crusher, goalCell->getXIndex(), goalCell->getYIndex(), m_map); - - goalBlockNdx.x = goalCell->getXIndex()/PathfindZoneManager::ZONE_BLOCK_SIZE; - goalBlockNdx.y = goalCell->getYIndex()/PathfindZoneManager::ZONE_BLOCK_SIZE; - } else { - goalBlockZone = goalCell->getZone(); - goalBlockNdx.x = -1; - goalBlockNdx.y = -1; - } - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - if (parentCell->getLayer()!=LAYER_GROUND) { - PathfindLayerEnum layer = parentCell->getLayer(); - // We're starting on a bridge, so link to land at the bridge end points. - ICoord2D ndx; - ICoord2D toNdx; - m_layers[layer].getStartCellIndex(&ndx); - m_layers[layer].getEndCellIndex(&toNdx); - PathfindCell *cell = getCell(LAYER_GROUND, toNdx.x, toNdx.y); - PathfindCell *startCell = getCell(LAYER_GROUND, ndx.x, ndx.y); - if (cell && startCell) { - // Close parent cell; - parentCell->removeFromOpenList(m_openList); - parentCell->putOnClosedList(m_closedList); - if (!startCell->allocateInfo(ndx)) { - // TheSuperHackers @info We need to forcefully cleanup dangling pathfinding cells if this failure condition is hit in retail - // Retail clients will crash beyond this point, but we attempt to recover by performing a full cleanup then enabling the fixed pathfinding codepath -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - s_useFixedPathfinding = true; - forceCleanCells(); - } - else -#endif - { - cleanOpenAndClosedLists(); - goalCell->releaseInfo(); - } - return nullptr; - } - startCell->setParentCellHierarchical(parentCell); - cellCount++; - Int curCost = startCell->costToHierGoal(parentCell); - Int remCost = startCell->costToHierGoal(goalCell); - startCell->setCostSoFar(curCost); - startCell->setTotalCost(remCost); - startCell->setParentCellHierarchical(parentCell); - // insert newCell in open list such that open list is sorted, smallest total path cost first - startCell->putOnSortedOpenList( m_openList ); - - cellCount++; - if(!cell->allocateInfo(toNdx)) { - // TheSuperHackers @info We need to forcefully cleanup dangling pathfinding cells if this failure condition is hit in retail - // Retail clients will crash beyond this point, but we attempt to recover by performing a full cleanup then enabling the fixed pathfinding codepath -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - s_useFixedPathfinding = true; - forceCleanCells(); - } - else -#endif - { - cleanOpenAndClosedLists(); - goalCell->releaseInfo(); - } - return nullptr; - } - curCost = cell->costToHierGoal(parentCell); - remCost = cell->costToHierGoal(goalCell); - cell->setCostSoFar(curCost); - cell->setTotalCost(remCost); - cell->setParentCellHierarchical(parentCell); - // insert newCell in open list such that open list is sorted, smallest total path cost first - cell->putOnSortedOpenList( m_openList ); - } - } - - PathfindCell *closestCell = nullptr; - Real closestDistSqr = sqr(HUGE_DIST); - - // - // Continue search until "open" list is empty, or - // until goal is found. - // - while( !m_openList.empty() ) - { - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - zoneStorageType parentZone; - if (parentCell->getLayer()==LAYER_GROUND) { - parentZone = m_zoneManager.getBlockZone(locomotorSurface, - crusher, parentCell->getXIndex(), parentCell->getYIndex(), m_map); - } else { - parentZone = parentCell->getZone(); - } - - Bool reachedGoal = false; - - Int blockX = parentCell->getXIndex()/PathfindZoneManager::ZONE_BLOCK_SIZE; - Int blockY = parentCell->getYIndex()/PathfindZoneManager::ZONE_BLOCK_SIZE; - if (parentZone == goalBlockZone) { - if (goalBlockNdx.x == -1 || (blockX==goalBlockNdx.x && blockY == goalBlockNdx.y)) { - reachedGoal = true; - } else { - DEBUG_LOG(("Hmm, got match before correct cell.")); - } - } - - ICoord2D zoneBlockExtent; - m_zoneManager.getExtent(zoneBlockExtent); - - if (!reachedGoal && m_zoneManager.interactsWithBridge(parentCell->getXIndex(), parentCell->getYIndex())) { - Int i; - for (i=0; i<=LAYER_LAST; i++) { - if (m_layers[i].isUnused() || m_layers[i].isDestroyed()) { - continue; - } - ICoord2D ndx; - ICoord2D toNdx; - m_layers[i].getStartCellIndex(&ndx); - m_layers[i].getEndCellIndex(&toNdx); - if (ndx.x/PathfindZoneManager::ZONE_BLOCK_SIZE != blockX || - ndx.y/PathfindZoneManager::ZONE_BLOCK_SIZE != blockY) { - m_layers[i].getStartCellIndex(&toNdx); - m_layers[i].getEndCellIndex(&ndx); - } - if (ndx.x<0 || ndx.y<0) continue; - if (toNdx.x<0 || toNdx.y<0) continue; - if (ndx.x/PathfindZoneManager::ZONE_BLOCK_SIZE == blockX && - ndx.y/PathfindZoneManager::ZONE_BLOCK_SIZE == blockY) { - // Bridge connects to this block. - Int bridgeZone = m_zoneManager.getBlockZone(locomotorSurface, crusher, ndx.x, ndx.y, m_map); - if (bridgeZone != parentZone) { - continue; - } - // We have a winner. - if (m_layers[i].getZone() == goalBlockZone) { - reachedGoal = true; - break; - } - PathfindCell *cell = getCell(LAYER_GROUND, toNdx.x, toNdx.y); - if (!cell) - continue; - - if (cell->hasInfo() && (cell->getClosed() || cell->getOpen())) - continue; - - PathfindCell *startCell = getCell(LAYER_GROUND, ndx.x, ndx.y); - if (!startCell) - continue; - - if (startCell != parentCell) { - if(!startCell->allocateInfo(ndx)) { - // TheSuperHackers @info We need to forcefully cleanup dangling pathfinding cells if this failure condition is hit in retail - // Retail clients will crash beyond this point, but we attempt to recover by performing a full cleanup then enabling the fixed pathfinding codepath -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - s_useFixedPathfinding = true; - forceCleanCells(); - } - else -#endif - { - cleanOpenAndClosedLists(); - goalCell->releaseInfo(); - } - return nullptr; - } - startCell->setParentCellHierarchical(parentCell); - if (!startCell->getClosed() && !startCell->getOpen()) { - startCell->putOnClosedList(m_closedList); - } - } - if(!cell->allocateInfo(toNdx)) { - // TheSuperHackers @info We need to forcefully cleanup dangling pathfinding cells if this failure condition is hit in retail - // Retail clients will crash beyond this point, but we attempt to recover by performing a full cleanup then enabling the fixed pathfinding codepath -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - s_useFixedPathfinding = true; - forceCleanCells(); - } - else -#endif - { - cleanOpenAndClosedLists(); - goalCell->releaseInfo(); - } - return nullptr; - } - cell->setParentCellHierarchical(startCell); - - cellCount++; - Int curCost = cell->costToHierGoal(startCell); - Int remCost = cell->costToHierGoal(goalCell); - - cell->setCostSoFar(startCell->getCostSoFar() + curCost); - cell->setTotalCost(cell->getCostSoFar()+remCost); - cell->setParentCellHierarchical(startCell); - // insert newCell in open list such that open list is sorted, smallest total path cost first - cell->putOnSortedOpenList( m_openList ); - - } - } - } - - if (reachedGoal) - { - if (parentCell != goalCell) { - goalCell->setParentCellHierarchical(parentCell); - } - // success - found a path to the goal - - m_isTunneling = false; - // construct and return path - Path *path = buildHierarchicalPath( from, goalCell ); -#if defined(RTS_DEBUG) - Bool show = TheGlobalData->m_debugAI==AI_DEBUG_PATHS; - show |= (TheGlobalData->m_debugAI==AI_DEBUG_GROUND_PATHS); - if (show) { - debugShowSearch(true); - } -#endif -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - if (goalCell->hasInfo() && !goalCell->getClosed() && !goalCell->getOpen()) { - goalCell->releaseInfo(); - } - parentCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return path; - } - -#if defined(RTS_DEBUG) -#if 0 - Bool show = TheGlobalData->m_debugAI==AI_DEBUG_PATHS; - show |= (TheGlobalData->m_debugAI==AI_DEBUG_GROUND_PATHS); - if (show) { - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - RGBColor color; - color.blue = 1; - color.red = 1; - color.green = 0; - Coord3D pos; - pos.x = ((blockX+0.5f)*PathfindZoneManager::ZONE_BLOCK_SIZE)*PATHFIND_CELL_SIZE_F ; - pos.y = ((blockY+0.5f)*PathfindZoneManager::ZONE_BLOCK_SIZE)*PATHFIND_CELL_SIZE_F ; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, 5*PATHFIND_CELL_SIZE_F, 300, color); - } -#endif -#endif - Real dx = IABS(goalCell->getXIndex()-parentCell->getXIndex()); - Real dy = IABS(goalCell->getYIndex()-parentCell->getYIndex()); - Real distSqr = dx*dx+dy*dy; - if (distSqr < closestDistSqr) { - closestCell = parentCell; - closestDistSqr = distSqr; - } - - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - - Int i; - zoneStorageType examinedZones[PathfindZoneManager::ZONE_BLOCK_SIZE]; - Int numExZones = 0; - // Left side. - if (blockX>0) { - for (i=1; i<=PathfindZoneManager::ZONE_BLOCK_SIZE; i++) { - ICoord2D scanCell; - scanCell.x = blockX*PathfindZoneManager::ZONE_BLOCK_SIZE; - scanCell.y = (blockY*PathfindZoneManager::ZONE_BLOCK_SIZE); - scanCell.y += PathfindZoneManager::ZONE_BLOCK_SIZE/2; - Int offset = i>>1; - if (i&1) offset = -offset; - scanCell.y += offset; - ICoord2D delta; - delta.x = -1; // left side moves -1. - delta.y = 0; - - PathfindCell *cell = getCell(LAYER_GROUND, scanCell.x, scanCell.y); - if (!cell) - continue; - - if ( cell->hasInfo() && (cell->getClosed() || cell->getOpen()) ) { - if (parentZone == m_zoneManager.getBlockZone(locomotorSurface, crusher, scanCell.x, scanCell.y, m_map)) - break; - } - - if (isHuman && checkCellOutsideExtents(scanCell)) - continue; - - processHierarchicalCell(scanCell, delta, parentCell, - goalCell, parentZone, examinedZones, numExZones, crusher, cellCount); - } - } - // Right side. - if (blockX>1; - if (i&1) offset = -offset; - scanCell.y += offset; - ICoord2D delta; - delta.x = 1; // right side moves +1. - delta.y = 0; - - PathfindCell *cell = getCell(LAYER_GROUND, scanCell.x, scanCell.y); - if (!cell) - continue; - - if ( cell->hasInfo() && (cell->getClosed() || cell->getOpen()) ) { - if (parentZone == m_zoneManager.getBlockZone(locomotorSurface, crusher, scanCell.x, scanCell.y, m_map)) - break; - } - - if (isHuman && checkCellOutsideExtents(scanCell)) - continue; - - processHierarchicalCell(scanCell, delta, parentCell, - goalCell, parentZone, examinedZones, numExZones, crusher, cellCount); - } - } - // Top side. - if (blockY>0) { - numExZones = 0; - for (i=1; i<=PathfindZoneManager::ZONE_BLOCK_SIZE; i++) { - ICoord2D scanCell; - scanCell.y = blockY*PathfindZoneManager::ZONE_BLOCK_SIZE; - scanCell.x = (blockX*PathfindZoneManager::ZONE_BLOCK_SIZE); - scanCell.x += PathfindZoneManager::ZONE_BLOCK_SIZE/2; - Int offset = i>>1; - if (i&1) offset = -offset; - scanCell.x += offset; - ICoord2D delta; - delta.x = 0; - delta.y = -1; // Top side moves -1. - - PathfindCell *cell = getCell(LAYER_GROUND, scanCell.x, scanCell.y); - if (!cell) - continue; - - if ( cell->hasInfo() && (cell->getClosed() || cell->getOpen()) ) { - if (parentZone == m_zoneManager.getBlockZone(locomotorSurface, crusher, scanCell.x, scanCell.y, m_map)) - break; - } - - if (isHuman && checkCellOutsideExtents(scanCell)) - continue; - - processHierarchicalCell(scanCell, delta, parentCell, - goalCell, parentZone, examinedZones, numExZones, crusher, cellCount); - } - } - // Bottom side. - if (blockY>1; - if (i&1) offset = -offset; - scanCell.x += offset; - ICoord2D delta; - delta.x = 0; - delta.y = 1; // Top side moves +1. - - PathfindCell *cell = getCell(LAYER_GROUND, scanCell.x, scanCell.y); - if (!cell) - continue; - - if ( cell->hasInfo() && (cell->getClosed() || cell->getOpen()) ) { - if (parentZone == m_zoneManager.getBlockZone(locomotorSurface, crusher, scanCell.x, scanCell.y, m_map)) - break; - } - - if (isHuman && checkCellOutsideExtents(scanCell)) - continue; - - processHierarchicalCell(scanCell, delta, parentCell, - goalCell, parentZone, examinedZones, numExZones, crusher, cellCount); - } - } - } - - if (closestOK && closestCell) { - m_isTunneling = false; - // construct and return path - Path *path = buildHierarchicalPath( from, closestCell ); - -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - if (goalCell->hasInfo() && !goalCell->getClosed() && !goalCell->getOpen()) { - goalCell->releaseInfo(); - } - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return path; - } - - // failure - goal cannot be reached -#if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI) - { - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - RGBColor color; - color.blue = 0; - color.red = color.green = 1; - addIcon(nullptr, 0, 0, color); - debugShowSearch(false); - Coord3D pos; - pos = *from; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, 3*PATHFIND_CELL_SIZE_F, 600, color); - pos = *to; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, 3*PATHFIND_CELL_SIZE_F, 600, color); - Real dx, dy; - dx = from->x - to->x; - dy = from->y - to->y; - - Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); - if (count<2) count = 2; - Int i; - color.green = 0; - for (i=1; ix + (to->x-from->x)*i/count; - pos.y = from->y + (to->y-from->y)*i/count; - pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 0.5f; - addIcon(&pos, PATHFIND_CELL_SIZE_F/2, 60, color); - - } - } -#endif - - DEBUG_LOG(("%d FindHierarchicalPath failed from (%f,%f) to (%f,%f) --", TheGameLogic->getFrame(), from->x, from->y, to->x, to->y)); - DEBUG_LOG(("time %f", (::GetTickCount()-startTimeMS)/1000.0f)); - -#ifdef DUMP_PERF_STATS - TheGameLogic->incrementOverallFailedPathfinds(); -#endif - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - goalCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - - return nullptr; -} - - -/** - * Does any broken bridge join from and to? - * True means that if bridge BridgeID is repaired, there is a land path from to to.. - */ -Bool Pathfinder::findBrokenBridge(const LocomotorSet& locoSet, - const Coord3D *from, const Coord3D *to, ObjectID *bridgeID) -{ - // See if terrain or building is blocking the destination. - PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); - PathfindLayerEnum fromLayer = TheTerrainLogic->getLayerForDestination(from); - - Int zone1, zone2; - *bridgeID = INVALID_ID; - - PathfindCell *parentCell = getClippedCell(fromLayer, from); - PathfindCell *goalCell = getClippedCell(destinationLayer, to); - zone1 = m_zoneManager.getEffectiveZone(locoSet.getValidSurfaces(), false, parentCell->getZone()); - zone2 = m_zoneManager.getEffectiveZone(locoSet.getValidSurfaces(), false, goalCell->getZone()); - zone1 = m_zoneManager.getEffectiveTerrainZone(zone1); - zone2 = m_zoneManager.getEffectiveTerrainZone(zone2); - zone1 = m_zoneManager.getEffectiveZone(locoSet.getValidSurfaces(), false, zone1); - zone2 = m_zoneManager.getEffectiveZone(locoSet.getValidSurfaces(), false, zone2); - - // 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 false; - } - - // Check broken bridges. - Int i; - for (i=0; i<=LAYER_LAST; i++) { - if (m_layers[i].isDestroyed()) { - if (m_layers[i].connectsZones(&m_zoneManager, locoSet, zone1, zone2)) { - *bridgeID = m_layers[i].getBridgeID(); - return true; - } - } - } - return false; -} - -/** - * 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::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 (!validMovementPosition(false, destinationLayer, locomotorSet, to)) { - return false; - } - 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. - } - Bool doingTerrainZone = false; - zone1 = m_zoneManager.getEffectiveZone(locomotorSet.getValidSurfaces(), false, parentCell->getZone()); - - if (parentCell->getType() == PathfindCell::CELL_OBSTACLE) { - doingTerrainZone = true; -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - 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) { - doingTerrainZone = true; - } - if (doingTerrainZone) { - 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 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. - return true; - } - return FALSE; // no path exists - -} - -/** - * Does any path exist from 'from' to 'to' given the locomotor set - * This is the careful check, looks at whether the terrain, buindings and units are possible or - * impossible to path over. Takes other units into account. - * False means it is impossible to path. - * True means it is possible to path. - */ -Bool Pathfinder::slowDoesPathExist( Object *obj, - const Coord3D *from, - const Coord3D *to, - ObjectID ignoreObject) -{ - AIUpdateInterface *ai = obj->getAI(); - if (ai==nullptr) { - return false; - } - const LocomotorSet &locoSet = ai->getLocomotorSet(); - m_ignoreObstacleID = ignoreObject; - Path *path = findPath(obj, locoSet, from, to); - m_ignoreObstacleID = INVALID_ID; - Bool found = (path!=nullptr); - - deleteInstance(path); - path = nullptr; - - return found; -} - -void Pathfinder::clip( Coord3D *from, Coord3D *to ) -{ - ICoord2D fromCell, toCell; - ICoord2D clipFromCell, clipToCell; - fromCell.x = REAL_TO_INT_FLOOR(from->x/PATHFIND_CELL_SIZE); - fromCell.y = REAL_TO_INT_FLOOR(from->y/PATHFIND_CELL_SIZE); - toCell.x = REAL_TO_INT_FLOOR(to->x/PATHFIND_CELL_SIZE); - toCell.y = REAL_TO_INT_FLOOR(to->y/PATHFIND_CELL_SIZE); - if (ClipLine2D(&fromCell, &toCell, &clipFromCell, &clipToCell,&m_extent)) { - if (fromCell.x!=clipFromCell.x || fromCell.y != clipFromCell.y) { - from->x = clipFromCell.x*PATHFIND_CELL_SIZE_F + 0.05f; - from->y = clipFromCell.y*PATHFIND_CELL_SIZE_F + 0.05f; - } - if (toCell.x!=clipToCell.x || toCell.y != clipToCell.y) { - to->x = clipToCell.x*PATHFIND_CELL_SIZE_F + 0.05f; - to->y = clipToCell.y*PATHFIND_CELL_SIZE_F + 0.05f; - } - } - -} - -struct TightenPathStruct -{ - Object *obj; - const LocomotorSet *locomotorSet; - PathfindLayerEnum layer; - Int radius; - Bool center; - Bool foundNewDest; - Coord3D orgDestPos; - Coord3D newDestPos; -}; - - -/*static*/ Int Pathfinder::tightenPathCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - TightenPathStruct* d = (TightenPathStruct*)userData; - if (from == nullptr || to==nullptr) return 0; // failure - if (d->layer != to->getLayer()) { - return 0; // failure - } - -#if RETAIL_COMPATIBLE_CRC - // TheSuperHackers @bugfix Caball009 27/02/2026 This was originally uninitialized. - // The uninitialized values that retail uses here are usually close to zero as long as foundNewDest == false, otherwise it uses the new values of newDestPos. - // newDestPos is zero initialized by the caller, so there is no need to check foundNewDest here. - Coord3D pos = d->newDestPos; -#else - Coord3D pos = d->orgDestPos; -#endif - - if (!TheAI->pathfinder()->checkForAdjust(d->obj, *d->locomotorSet, true, to_x, to_y, to->getLayer(), d->radius, d->center, &pos, nullptr)) - { - return 0; // failure - } - d->foundNewDest = true; - d->newDestPos = pos; - - return 0; // success but continue -} - -/* Returns the cost, which is in the same units as coord3d distance. */ -void Pathfinder::tightenPath(Object *obj, const LocomotorSet& locomotorSet, Coord3D *from, - const Coord3D *to) -{ - 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 - info.newDestPos.zero(); -#endif - iterateCellsAlongLine(*from, *to, info.layer, tightenPathCallback, &info); - if (info.foundNewDest) { - *from = info.newDestPos; - } -} - - -/* Returns the cost, which is in the same units as coord3d distance. */ -Int Pathfinder::checkPathCost(Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, - const Coord3D *rawTo) -{ - //CRCDEBUG_LOG(("Pathfinder::checkPathCost()")); - if (m_isMapReady == false) return 0; - enum {MAX_COST = 0x7fff0000}; - if (!obj) return MAX_COST; - - Int cellCount = 0; - - Coord3D adjustTo = *rawTo; - Coord3D *to = &adjustTo; - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - // create unique "mark" values for open and closed cells for this pathfind invocation - - Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; - - PathfindLayerEnum goalLayer = TheTerrainLogic->getLayerForDestination(to); - // determine goal cell - PathfindCell *goalCell = getClippedCell( goalLayer, to ); - if (goalCell == nullptr) - return MAX_COST; - - - Bool center; - Int radius; - getRadiusAndCenter(obj, radius, center); - - // determine start cell - ICoord2D startCellNdx; - worldToCell(from, &startCellNdx); - PathfindLayerEnum fromLayer = TheTerrainLogic->getLayerForDestination(from); - PathfindCell *parentCell = getCell( fromLayer, from ); - if (parentCell == nullptr) - return MAX_COST; - ICoord2D pos2d; - worldToCell(to, &pos2d); - if (!goalCell->allocateInfo(pos2d)) { - return MAX_COST; - } - - if (parentCell!=goalCell) { - if (!parentCell->allocateInfo(startCellNdx)) { - goalCell->releaseInfo(); - return MAX_COST; - } - } - - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell ) == false) { - parentCell->releaseInfo(); - goalCell->releaseInfo(); - return MAX_COST; - } - - parentCell->startPathfind(goalCell); - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - // "closed" list is initially empty - m_closedList.reset(); - - // - // Continue search until "open" list is empty, or - // until goal is found. - // - while( !m_openList.empty() ) - { - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - // put parent cell onto closed list - its evaluation is finished - Retail compatible behaviour -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - parentCell->putOnClosedList( m_closedList ); - } -#endif - - if (parentCell==goalCell) { - Int cost = parentCell->getTotalCost(); - m_isTunneling = false; - cleanOpenAndClosedLists(); -#if RETAIL_COMPATIBLE_PATHFINDING - if (s_useFixedPathfinding) -#endif - { - parentCell->releaseInfo(); - } - return cost; - } - - // put parent cell onto closed list - its evaluation is finished - Fixed behaviour -#if RETAIL_COMPATIBLE_PATHFINDING - if (s_useFixedPathfinding) -#endif - { - parentCell->putOnClosedList( m_closedList ); - } - - if (cellCount > MAX_CELL_COUNT) { - continue; - } - // Check to see if we can change layers in this cell. - checkChangeLayers(parentCell); - - // expand search to neighboring orthogonal cells - static ICoord2D delta[] = - { - { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, - { 1, 1 }, { -1, 1 }, { -1, -1 }, { 1, -1 } - }; - const Int numNeighbors = 8; - const Int firstDiagonal = 4; - ICoord2D newCellCoord; - PathfindCell *newCell; - const Int adjacent[5] = {0, 1, 2, 3, 0}; - Bool neighborFlags[8] = { 0 }; - - UnsignedInt newCostSoFar = 0; - - for( int i=0; igetXIndex() + delta[i].x; - newCellCoord.y = parentCell->getYIndex() + delta[i].y; - - // get the neighboring cell - newCell = getCell(parentCell->getLayer(), newCellCoord.x, newCellCoord.y ); - - // check if cell is on the map - if (newCell == nullptr) - continue; - - // check if this neighbor cell is already on the open (waiting to be tried) - // or closed (already tried) lists - Bool onList = false; - if (newCell->hasInfo()) { - if (newCell->getOpen() || newCell->getClosed()) - { - // already on one of the lists - onList = true; - } - } - if (i>=firstDiagonal) { - // make sure one of the adjacent sides is open. - if (!neighborFlags[adjacent[i-4]] && !neighborFlags[adjacent[i-3]]) { - continue; - } - } - - if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), newCell, parentCell )) { - continue; - } - - neighborFlags[i] = true; - - if (!newCell->allocateInfo(newCellCoord)) { - // Out of cells for pathing... -#if RETAIL_COMPATIBLE_PATHFINDING - if (s_useFixedPathfinding) -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return cellCount; - } - cellCount++; - - newCostSoFar = newCell->costSoFar( parentCell ); - newCell->setBlockedByAlly(false); - - // check if this neighbor cell is already on the open (waiting to be tried) - // or closed (already tried) lists - if (onList) - { - // already on one of the lists - if existing costSoFar is less, - // the new cell is on a longer path, so skip it - if (newCell->getCostSoFar() <= newCostSoFar) - continue; - } - - // keep track of path we're building - point back to cell we moved here from - newCell->setParentCell(parentCell) ; - - // store cost of this path - newCell->setCostSoFar(newCostSoFar); - - Int costRemaining = 0; - if (goalCell) { - costRemaining = newCell->costToGoal( goalCell ); - } - - newCell->setTotalCost(newCell->getCostSoFar() + costRemaining) ; - - // if newCell was on closed list, remove it from the list - if (newCell->getClosed()) - newCell->removeFromClosedList( m_closedList ); - - // if the newCell was already on the open list, remove it so it can be re-inserted in order - if (newCell->getOpen()) - newCell->removeFromOpenList( m_openList ); - -#if RETAIL_COMPATIBLE_PATHFINDING - // TheSuperHacker @info This is here to catch a retail pathfinding crash point and to recover from it - // A cell has gotten onto the open list without pathfinding info due to a danling m_open pointer on the previous listed cell so we need to force a cleanup - if (!s_useFixedPathfinding && m_openList.getHead() && !m_openList.getHead()->hasInfo()) { - s_useFixedPathfinding = true; - forceCleanCells(); - return MAX_COST; - } -#endif - - // insert newCell in open list such that open list is sorted, smallest total path cost first - newCell->putOnSortedOpenList( m_openList ); - } - } - - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - if (goalCell->hasInfo() && !goalCell->getClosed() && !goalCell->getOpen()) { - goalCell->releaseInfo(); - } - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return MAX_COST; -} - - -/** - * Find a short, valid path between the FROM location and a location NEAR the to location. - * Uses A* algorithm. - */ -Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, - Coord3D *rawTo, Bool blocked, Real pathCostMultiplier, Bool moveAllies) -{ - //CRCDEBUG_LOG(("Pathfinder::findClosestPath()")); -#ifdef DEBUG_LOGGING - Int startTimeMS = ::GetTickCount(); -#endif - Bool isHuman = true; - if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { - isHuman = false; // computer gets to cheat. - } - - if (locomotorSet.getValidSurfaces() == 0) { - DEBUG_CRASH(("Attempting to path immobile unit.")); - return nullptr; - } - - if (m_isMapReady == false) return nullptr; - - m_isTunneling = false; - - if (!obj) return nullptr; - - Bool canPathThroughUnits = false; - if (obj && obj->getAIUpdateInterface()) { - canPathThroughUnits = obj->getAIUpdateInterface()->canPathThroughUnits(); - } - Bool centerInCell; - Int radius; - getRadiusAndCenter(obj, radius, centerInCell); - - Coord3D adjustTo = *rawTo; - Coord3D *to = &adjustTo; - if (!centerInCell) { - adjustTo.x += PATHFIND_CELL_SIZE_F/2; - adjustTo.y += PATHFIND_CELL_SIZE_F/2; - } - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - // create unique "mark" values for open and closed cells for this pathfind invocation - - Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; - - - Coord3D clipFrom = *from; - clip(&clipFrom, &adjustTo); - - PathfindLayerEnum destinationLayer = TheTerrainLogic->getLayerForDestination(to); - // determine goal cell - PathfindCell *goalCell = getClippedCell( destinationLayer, to ); - if (goalCell == nullptr) - return nullptr; - - if (goalCell->getZone()==0 && destinationLayer==LAYER_WALL) { - return nullptr; - } - - Bool goalOnObstacle = false; - if (m_ignoreObstacleID != INVALID_ID) { - // Check for object on structure. - // srj sez: check for obstacle on AIRFIELD... only want to do this for things - // that are "parked" on the airfield, but not for things hovering over an obstacle - // (eg, a chinook over a supply dock). - Object *goalObj = TheGameLogic->findObjectByID(m_ignoreObstacleID); - if (goalObj) { - 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 && RETAIL_COMPATIBLE_PATHFINDING - if (newObstacle != nullptr && newObstacle->isKindOf(KINDOF_AIRFIELD)) -#else - if (newObstacle != nullptr && newObstacle->isKindOf(KINDOF_FS_AIRFIELD)) -#endif - { - m_ignoreObstacleID = goalCell->getObstacleID(); - goalOnObstacle = true; - } - else - { - if (m_ignoreObstacleID == goalCell->getObstacleID()) { - goalOnObstacle = true; - } - } - } - } - } - - // determine start cell - ICoord2D startCellNdx; - worldToCell(from, &startCellNdx); - PathfindCell *parentCell = getClippedCell( obj->getLayer(), &clipFrom ); - if (parentCell == nullptr) - return nullptr; - - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell ) == false) { - m_isTunneling = true; // We can't move from our current location. So relax the constraints. - } - TCheckMovementInfo info; - info.cell = startCellNdx; - info.layer = obj->getLayer(); - info.centerInCell = centerInCell; - info.radius = radius; - info.considerTransient = blocked; - info.acceptableSurfaces = locomotorSet.getValidSurfaces(); - if (!checkForMovement(obj, info) || info.enemyFixed) { - m_isTunneling = true; // We can't move from our current location. So relax the constraints. - } - - Bool gotHierarchicalPath = false; - if (m_isTunneling) { - m_zoneManager.setAllPassable(); // can't optimize. - } else { - m_zoneManager.clearPassableFlags(); - Path *hPat = findClosestHierarchicalPath(isHuman, locomotorSet, from, rawTo, false); - if (hPat) { - deleteInstance(hPat); - gotHierarchicalPath = true; - } else { - m_zoneManager.setAllPassable(); - } - } - const Bool startedStuck = m_isTunneling; - - ICoord2D pos2d; - worldToCell(to, &pos2d); - if (!goalCell->allocateInfo(pos2d)) { - return nullptr; - } - if (parentCell!=goalCell) { - worldToCell(&clipFrom, &pos2d); - if (!parentCell->allocateInfo(pos2d)) { -#if RETAIL_COMPATIBLE_PATHFINDING - if (s_useFixedPathfinding) -#endif - { - goalCell->releaseInfo(); - } - return nullptr; - } - } - parentCell->startPathfind(goalCell); - - PathfindCell *closesetCell = nullptr; - Real closestDistanceSqr = FLT_MAX; - Real closestDistScreenSqr = FLT_MAX; - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - // "closed" list is initially empty - m_closedList.reset(); - Int count = 0; - // - // Continue search until "open" list is empty, or - // until goal is found. - // - Bool foundGoal = false; - while( !m_openList.empty() ) - { - Real dx; - Real dy; - Real distSqr; - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - if (parentCell == goalCell) - { - // success - found a path to the goal - 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 && RETAIL_COMPATIBLE_PATHFINDING - break; -#else - foundGoal = true; - // Continue processing the open list to find a possibly closer cell. jba. [8/25/2003] - continue; -#endif - } - } - - Bool show = TheGlobalData->m_debugAI; -#ifdef INTENSE_DEBUG - Int count = 0; - PathfindCell *cur; - for (cur = m_closedList.getHead(); cur; cur=cur->getNextOpen()) { - count++; - } - if (count>1000) { - show = true; - DEBUG_LOG(("FCP - cells %d obj %s %x", count, obj->getTemplate()->getName().str(), obj)); -#ifdef STATE_MACHINE_DEBUG - if( obj->getAIUpdateInterface() ) - { - DEBUG_LOG(("State %s", obj->getAIUpdateInterface()->getCurrentStateName().str())); - } -#endif - TheScriptEngine->AppendDebugMessage("Big path FCP", false); - } -#endif - if (show) - debugShowSearch(true); - m_isTunneling = false; - // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, goalCell, centerInCell, blocked); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - parentCell->releaseInfo(); - goalCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - - return path; - } - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - if (!m_isTunneling && checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), parentCell->getLayer(), radius, centerInCell)) { - if (!startedStuck || validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell )) { - dx = IABS(goalCell->getXIndex()-parentCell->getXIndex()); - dy = IABS(goalCell->getYIndex()-parentCell->getYIndex()); - distSqr = dx*dx+dy*dy; - if (distSqrgetCostSoFar()*(parentCell->getCostSoFar()*COST_TO_DISTANCE_FACTOR_SQR))*pathCostMultiplier; - if (distSqr < closestDistanceSqr) { - closesetCell = parentCell; - closestDistanceSqr = distSqr; - } - } - } - - dx = IABS(goalCell->getXIndex()-parentCell->getXIndex()); - dy = IABS(goalCell->getYIndex()-parentCell->getYIndex()); - distSqr = dx*dx+dy*dy; - // If we are 2x farther than the closest location already found, don't continue. - if (distSqr > closestDistScreenSqr*4) { - Bool skip = false; - if (!gotHierarchicalPath) { - skip = true; - } - if (count>2000) { - skip = true; - } - if (closestDistScreenSqr < 10*10*PATHFIND_CELL_SIZE_F) { - skip = true; - } - if (skip) { - continue; - } - } - // 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) { - // success - found a path to near the goal - - Bool show = TheGlobalData->m_debugAI; - -#ifdef INTENSE_DEBUG - if (count>5000) { - show = true; - DEBUG_LOG(("FCP CC cells %d obj %s %x", count, obj->getTemplate()->getName().str(), obj)); -#ifdef STATE_MACHINE_DEBUG - if( obj->getAIUpdateInterface() ) - { - DEBUG_LOG(("State %s", obj->getAIUpdateInterface()->getCurrentStateName().str())); - } -#endif - - DEBUG_LOG(("%d Pathfind(findClosestPath) chugged from (%f,%f) to (%f,%f) --", TheGameLogic->getFrame(), from->x, from->y, to->x, to->y)); - DEBUG_LOG(("Unit '%s', time %f", obj->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f)); -#ifdef INTENSE_DEBUG - TheScriptEngine->AppendDebugMessage("Big path FCP CC", false); -#endif - } -#endif - if (show) - debugShowSearch(true); - - m_isTunneling = false; - rawTo->x = closesetCell->getXIndex()*PATHFIND_CELL_SIZE_F + PATHFIND_CELL_SIZE_F/2.0f; - rawTo->y = closesetCell->getYIndex()*PATHFIND_CELL_SIZE_F + PATHFIND_CELL_SIZE_F/2.0f; - // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, closesetCell, centerInCell, blocked ); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - goalCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return path; - } - - // failure - goal cannot be reached -#ifdef DEBUG_LOGGING - Bool valid; - valid = validMovementPosition( isCrusher, obj->getLayer(), locomotorSet, to ) ; - - DEBUG_LOG(("Pathfind(findClosestPath) failed from (%f,%f) to (%f,%f), original valid %d --", TheGameLogic->getFrame(), from->x, from->y, to->x, to->y, valid)); - DEBUG_LOG(("Unit '%s', time %f", obj->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f)); -#endif -#if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI) - debugShowSearch(false); -#endif -#ifdef DUMP_PERF_STATS - TheGameLogic->incrementOverallFailedPathfinds(); -#endif - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - goalCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return nullptr; -} - - -void Pathfinder::adjustCoordToCell(Int cellX, Int cellY, Bool centerInCell, Coord3D &pos, PathfindLayerEnum layer) -{ - if (centerInCell) { - pos.x = ((Real)cellX + 0.5f) * PATHFIND_CELL_SIZE_F; - pos.y = ((Real)cellY + 0.5f) * PATHFIND_CELL_SIZE_F; - } else { - pos.x = ((Real)cellX+0.05) * PATHFIND_CELL_SIZE_F; - pos.y = ((Real)cellY+0.05) * PATHFIND_CELL_SIZE_F; - } - pos.z = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layer ); -} - - -/** - * Work backwards from goal cell to construct final path. - */ -Path *Pathfinder::buildActualPath( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, const Coord3D *fromPos, - PathfindCell *goalCell, Bool center, Bool blocked ) -{ - DEBUG_ASSERTCRASH( goalCell, ("Pathfinder::buildActualPath: goalCell == nullptr") ); - - Path *path = newInstance(Path); - - if (goalCell->getPinched() && goalCell->getParentCell() && !goalCell->getParentCell()->getPinched()) { - goalCell = goalCell->getParentCell(); - } - - prependCells(path, fromPos, goalCell, center); - - // cleanup the path by checking line of sight - path->optimize(obj, acceptableSurfaces, blocked); - -#if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI==AI_DEBUG_PATHS) - { - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - RGBColor color; - color.blue = 0; - color.red = color.green = 1; - Coord3D pos; - PathNode *node = path->getFirstNode(); - for( ; node; node = node->getNext() ) - { - - // create objects to show path - they decay - - pos = *node->getPosition(); - color.red = color.green = 1; - if (node->getLayer() != LAYER_GROUND) { - color.red = 0; - } - addIcon(&pos, PATHFIND_CELL_SIZE_F*.25f, 200, color); - } - - // show optimized path - for( node = path->getFirstNode(); node; node = node->getNextOptimized() ) - { - pos = *node->getPosition(); - addIcon(&pos, PATHFIND_CELL_SIZE_F*.8f, 200, color); - } - setDebugPath(path); - } -#endif - return path; -} - -/** - * Work backwards from goal cell to construct final path. - */ -void Pathfinder::prependCells( Path *path, const Coord3D *fromPos, - PathfindCell *goalCell, Bool center ) -{ - // traverse path cells in REVERSE order, creating path in desired order - // skip the LAST node, as that will be in the same cell as the unit itself - so use the unit's position - Coord3D pos; - PathfindCell *cell, *prevCell = nullptr; - Bool goalCellNull = (goalCell->getParentCell()==nullptr); - for( cell = goalCell; cell->getParentCell(); cell = cell->getParentCell() ) - { - m_zoneManager.setPassable(cell->getXIndex(), cell->getYIndex(), true); - adjustCoordToCell(cell->getXIndex(), cell->getYIndex(), center, pos, cell->getLayer()); - if (prevCell && cell->getXIndex()==prevCell->getXIndex() && cell->getYIndex()==prevCell->getYIndex()) { - // transitioning layers. - PathfindLayerEnum layer = cell->getLayer(); - if (layer==LAYER_GROUND) { - layer = prevCell->getLayer(); - } - DEBUG_ASSERTCRASH(layer!=LAYER_GROUND, ("Should have at 1 non-ground layer. jba")); - path->getFirstNode()->setLayer(layer); - continue; - } - - Bool canOptimize = true; - if (cell->getType() == PathfindCell::CELL_CLIFF) { - if (prevCell && prevCell->getType() != PathfindCell::CELL_CLIFF) { - if (path->getFirstNode()) { - path->getFirstNode()->setCanOptimize(false); - } - } - } else { - if (prevCell && prevCell->getType() == PathfindCell::CELL_CLIFF) { - canOptimize = false; - } - } - - path->prependNode( &pos, cell->getLayer() ); - path->getFirstNode()->setCanOptimize(canOptimize); - if (cell->isBlockedByAlly()) { - path->setBlockedByAlly(true); - } - if (prevCell) { - prevCell->clearParentCell(); - } - prevCell = cell; - } - -#if RETAIL_COMPATIBLE_PATHFINDING - // TheSuperHackers @info This pathway is here for retail compatibility, it is to catch when a starting cell has a dangling parent that contains no pathing information - // Beyond this point a retail client will crash due to a null pointer access within cell->getXIndex() - // To recover from this we set the cell to the previous cell, which should be the actual starting cell then set to use the fixed pathing and perform a forced cleanup - if (!s_useFixedPathfinding) { - if (!cell->hasInfo()) { - cell = prevCell; - - m_zoneManager.setPassable(cell->getXIndex(), cell->getYIndex(), true); - if (goalCellNull) { - // Very short path. - adjustCoordToCell(cell->getXIndex(), cell->getYIndex(), center, pos, cell->getLayer()); - path->prependNode( &pos, cell->getLayer() ); - } - // put actual start position as first node on the path, so it begins right at the unit's feet - if (fromPos->x != path->getFirstNode()->getPosition()->x || fromPos->y != path->getFirstNode()->getPosition()->y) { - path->prependNode( fromPos, cell->getLayer() ); - } - - s_useFixedPathfinding = true; - forceCleanCells(); - return; - } - } -#endif - - m_zoneManager.setPassable(cell->getXIndex(), cell->getYIndex(), true); - if (goalCellNull) { - // Very short path. - adjustCoordToCell(cell->getXIndex(), cell->getYIndex(), center, pos, cell->getLayer()); - path->prependNode( &pos, cell->getLayer() ); - } - // put actual start position as first node on the path, so it begins right at the unit's feet - if (fromPos->x != path->getFirstNode()->getPosition()->x || fromPos->y != path->getFirstNode()->getPosition()->y) { - path->prependNode( fromPos, cell->getLayer() ); - } - -} - -void Pathfinder::setDebugPath(Path *newDebugpath) -{ - if (TheGlobalData->m_debugAI) - { - // copy the path for debugging - deleteInstance(debugPath); - debugPath = newInstance(Path); - - for( PathNode *copyNode = newDebugpath->getFirstNode(); copyNode; copyNode = copyNode->getNextOptimized() ) - debugPath->appendNode( copyNode->getPosition(), copyNode->getLayer() ); - } - -} - -/** - * Given two world-space points, call callback for each cell. - * Uses Bresenham line algorithm from www.gamedev.net. - */ -Int Pathfinder::iterateCellsAlongLine( const Coord3D& startWorld, const Coord3D& endWorld, - PathfindLayerEnum layer, CellAlongLineProc proc, void* userData ) -{ - ICoord2D start, end; - worldToCell( &startWorld, &start ); - worldToCell( &endWorld, &end ); - return iterateCellsAlongLine(start, end, layer, proc, userData); -} -/** - * Given two world-space points, call callback for each cell. - * Uses Bresenham line algorithm from www.gamedev.net. - */ -Int Pathfinder::iterateCellsAlongLine( const ICoord2D &start, const ICoord2D &end, - PathfindLayerEnum layer, CellAlongLineProc proc, void* userData ) -{ - Int delta_x = abs(end.x - start.x); // The difference between the x's - Int delta_y = abs(end.y - start.y); // The difference between the y's - Int x = start.x; // Start x off at the first pixel - Int y = start.y; // Start y off at the first pixel - - Int xinc1, xinc2; - if (end.x >= start.x) // The x-values are increasing - { - xinc1 = 1; - xinc2 = 1; - } - else // The x-values are decreasing - { - xinc1 = -1; - xinc2 = -1; - } - - Int yinc1, yinc2; - if (end.y >= start.y) // The y-values are increasing - { - yinc1 = 1; - yinc2 = 1; - } - else // The y-values are decreasing - { - yinc1 = -1; - yinc2 = -1; - } - - Bool checkY = true; - Int den, num, numadd, numpixels; - if (delta_x >= delta_y) // There is at least one x-value for every y-value - { - xinc1 = 0; // Don't change the x when numerator >= denominator - yinc2 = 0; // Don't change the y for every iteration - den = delta_x; - num = delta_x / 2; - numadd = delta_y; - numpixels = delta_x; // There are more x-values than y-values - } - else // There is at least one y-value for every x-value - { - checkY = false; - xinc2 = 0; // Don't change the x for every iteration - yinc1 = 0; // Don't change the y when numerator >= denominator - den = delta_y; - num = delta_y / 2; - numadd = delta_x; - numpixels = delta_y; // There are more y-values than x-values - } - - PathfindCell* from = nullptr; - for (Int curpixel = 0; curpixel <= numpixels; curpixel++) - { - PathfindCell* to = getCell( layer, x, y ); - if (to==nullptr) return 0; - - Int ret = (*proc)(this, from, to, x, y, userData); - if (ret != 0) - return ret; - - num += numadd; // Increase the numerator by the top of the fraction - if (num >= den) // Check if numerator >= denominator - { - num -= den; // Calculate the new numerator value - x += xinc1; // Change the x as appropriate - y += yinc1; // Change the y as appropriate - from = to; - to = getCell( layer, x, y ); - if (to==nullptr) return 0; - Int ret = (*proc)(this, from, to, x, y, userData); - if (ret != 0) - return ret; - } - x += xinc2; // Change the x as appropriate - y += yinc2; // Change the y as appropriate - - from = to; - } - - return 0; -} - -//----------------------------------------------------------------------------- - -static ObjectID getSlaverID(const Object* o) -{ - for (BehaviorModule** update = o->getBehaviorModules(); *update; ++update) - { - SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface(); - if (sdu != nullptr) - { - return sdu->getSlaverID(); - } - } - - return INVALID_ID; -} - -static ObjectID getContainerID(const Object* o) -{ - const Object* container = o ? o->getContainedBy() : nullptr; - return container ? container->getID() : INVALID_ID; -} - -struct segmentIntersectsStruct -{ - Object *theTallBuilding; - ObjectID ignoreBuilding; -}; - -/*static*/ Int Pathfinder::segmentIntersectsBuildingCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - segmentIntersectsStruct* d = (segmentIntersectsStruct*)userData; - - if (to != nullptr && (to->getType() == PathfindCell::CELL_OBSTACLE)) - { - Object *obj = TheGameLogic->findObjectByID(to->getObstacleID()); - if (obj && obj->isKindOf(KINDOF_AIRCRAFT_PATH_AROUND)) { - if (obj->getID() == d->ignoreBuilding) { - return 0; - } - d->theTallBuilding = obj; - return 1; - } - } - - return 0; // keep going -} - - - -struct ViewBlockedStruct -{ - const Object *obj; - const Object *objOther; -}; - - -/*static*/ Int Pathfinder::lineBlockedByObstacleCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - const ViewBlockedStruct* d = (const ViewBlockedStruct*)userData; - - if (to != nullptr && (to->getType() == PathfindCell::CELL_OBSTACLE)) - { - - // we never block our own view! - if (to->isObstaclePresent(d->obj->getID())) - return 0; - - // nor does the object we're trying to see! - if (to->isObstaclePresent(d->objOther->getID())) - return 0; - - // if the obstacle is our container, ignore it as an obstacle. - if (to->isObstaclePresent(getContainerID(d->obj))) - return 0; - - // @todo: if the obstacle is objOther's container, AND it's a "visible" container, ignore it. - - // if the obstacle is the item to which we are slaved, ignore it as an obstacle. - if (to->isObstaclePresent(getSlaverID(d->obj))) - return 0; - - // if the obstacle is the item to which objOther is slaved, ignore it as an obstacle. - if (to->isObstaclePresent(getSlaverID(d->objOther))) - return 0; - - // if the obstacle is transparent, ignore it, since this callback is only used for line-of-sight. (srj) - if (to->isObstacleTransparent()) - return 0; - - return 1; // bail early - } - - return 0; // keep going -} - -struct ViewAttackBlockedStruct -{ - const Object *obj; - const Object *victim; - const PathfindCell *victimCell; - Int skipCount; -}; - -/*static*/ Int Pathfinder::attackBlockedByObstacleCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - ViewAttackBlockedStruct* d = (ViewAttackBlockedStruct*)userData; - - if (d->skipCount>0) { - d->skipCount--; - return 0; - } - if (to != nullptr && (to->getType() == PathfindCell::CELL_OBSTACLE)) - { - // we never block our own view! - if (to->isObstaclePresent(d->obj->getID())) - return 0; - - if (d->victim) { - // nor does the object we're trying to attack! - if (to->isObstaclePresent(d->victim->getID())) - return 0; - // if the obstacle is the item to which objOther is slaved, ignore it as an obstacle. - if (to->isObstaclePresent(getSlaverID(d->victim))) - return 0; - } - - // if the obstacle is our container, ignore it as an obstacle. - if (to->isObstaclePresent(getContainerID(d->obj))) - return 0; - - // @todo: if the obstacle is objOther's container, AND it's a "visible" container, ignore it. - - // if the obstacle is the item to which we are slaved, ignore it as an obstacle. - if (to->isObstaclePresent(getSlaverID(d->obj))) - return 0; - - if (to->isObstacleTransparent()) - return 0; - //Kris: Added the check for victimCell because in China01 -- after the intro, NW of your - //base is a cream colored building that lies in a negative coord. When you order units to - //force attack it, it crashes. - if( d->victimCell && to->isObstaclePresent( d->victimCell->getObstacleID() ) ) - { - // Victim is inside the bounds of another object. We don't let this block us, - // as usually it is on the edge and it looks like we should be able to shoot it. jba. - return 0; - } - return 1; // bail early - } - - return 0; // keep going -} - -//----------------------------------------------------------------------------- -Bool Pathfinder::isViewBlockedByObstacle(const Object* obj, const Object* objOther) -{ - ViewBlockedStruct info; - info.obj = obj; - info.objOther = objOther; - if (objOther && objOther->isSignificantlyAboveTerrain()) { - return false; // We don't check los to flying objects. jba. - } -#if 1 - return isAttackViewBlockedByObstacle(obj, *obj->getPosition(), objOther, *objOther->getPosition()); -#else - PathfindLayerEnum layer = objOther->getLayer(); - if (layer==LAYER_GROUND) { - layer = obj->getLayer(); - } - Int ret = iterateCellsAlongLine(*obj->getPosition(), *objOther->getPosition(), - layer, lineBlockedByObstacleCallback, &info); - return ret != 0; -#endif -} - - -//----------------------------------------------------------------------------- -Bool Pathfinder::isAttackViewBlockedByObstacle(const Object* attacker, const Coord3D& attackerPos, const Object* victim, const Coord3D& victimPos) -{ - //CRCDEBUG_LOG(("Pathfinder::isAttackViewBlockedByObstacle() - attackerPos is (%g,%g,%g) (%X,%X,%X)", - // attackerPos.x, attackerPos.y, attackerPos.z, - // AS_INT(attackerPos.x),AS_INT(attackerPos.y),AS_INT(attackerPos.z))); - //CRCDEBUG_LOG(("Pathfinder::isAttackViewBlockedByObstacle() - victimPos is (%g,%g,%g) (%X,%X,%X)", - // victimPos.x, victimPos.y, victimPos.z, - // AS_INT(victimPos.x),AS_INT(victimPos.y),AS_INT(victimPos.z))); - // Global switch to turn this off in case it doesn't work. - if (!TheAI->getAiData()->m_attackUsesLineOfSight) - { - //CRCDEBUG_LOG(("Pathfinder::isAttackViewBlockedByObstacle() 1")); - return false; - } - - // If the attacker doesn't need line of sight, isn't blocked. - if (!attacker->isKindOf(KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT)) - { - //CRCDEBUG_LOG(("Pathfinder::isAttackViewBlockedByObstacle() 2")); - return false; - } - -// srj sez: this is a good start at taking terrain into account for attacks, but findAttackPath needs to be smartened also -#define LOS_TERRAIN -#ifdef LOS_TERRAIN - const Weapon* w = attacker->getCurrentWeapon(); - if (attacker->isKindOf(KINDOF_IMMOBILE)) { - // Don't take terrain blockage into account, since we can't move around it. jba. - w = nullptr; - } - if (w) - { - Bool viewBlocked; - if (victim) - viewBlocked = !w->isClearGoalFiringLineOfSightTerrain(attacker, attackerPos, victim); - else - viewBlocked = !w->isClearGoalFiringLineOfSightTerrain(attacker, attackerPos, victimPos); - - if (viewBlocked) - { - //CRCDEBUG_LOG(("Pathfinder::isAttackViewBlockedByObstacle() 3")); - return true; - } - } -#endif - - ViewAttackBlockedStruct info; - info.obj = attacker; - info.victim = victim; - PathfindLayerEnum layer = LAYER_GROUND; - if (victim) { - layer = victim->getLayer(); - } - info.victimCell = getCell(layer, &victimPos); - - info.skipCount = 0; - if (attacker->getLayer() != LAYER_GROUND) - { - info.skipCount = 3; /// srj -- someone wanna tell me what this magic number means? - /// jba - Yes, it means that if someone is on a bridge, or rooftop, they can see - /// 3 pathfind cells out of whatever they are standing on. - /// srj -- awesome! thank you very much :-) - if (layer==LAYER_GROUND) { - layer = attacker->getLayer(); - } - } - - Int ret = iterateCellsAlongLine(attackerPos, victimPos, layer, attackBlockedByObstacleCallback, &info); - //CRCDEBUG_LOG(("Pathfinder::isAttackViewBlockedByObstacle() 4")); - return ret != 0; -} - -static void computeNormalRadialOffset(const Coord3D& from, Coord3D& insert, const Coord3D& to, - Object *obj, Real radius) -{ - Real crossProduct; - Real dx = to.x - from.x; - Real dy = to.y -from.y; - Coord3D objPos = *obj->getPosition(); - - - Real objDx = objPos.x - from.x; - Real objDy = objPos.y - from.y; - - crossProduct = dx*objDy - dy*objDx; - - Coord3D fromToNormal; - fromToNormal.z = 0; - if (crossProduct>0) { - fromToNormal.x = dy; - fromToNormal.y = -dx; - } else { - fromToNormal.x = -dy; - fromToNormal.y = dx; - } - fromToNormal.normalize(); - Real length = radius; - insert = *obj->getPosition(); - insert.x += fromToNormal.x*length; - insert.y += fromToNormal.y*length; - -} - -//----------------------------------------------------------------------------- -Bool Pathfinder::segmentIntersectsTallBuilding(const PathNode *curNode, - PathNode *nextNode, ObjectID ignoreBuilding, Coord3D *insertPos1, Coord3D *insertPos2, Coord3D *insertPos3 ) -{ - segmentIntersectsStruct info; - info.theTallBuilding = nullptr; - info.ignoreBuilding = ignoreBuilding; - - Coord3D fromPos = *curNode->getPosition(); - Coord3D toPos = *nextNode->getPosition(); - - Int i; - for (i=0; i<2; i++) { - Int ret = iterateCellsAlongLine(fromPos, toPos, LAYER_GROUND, segmentIntersectsBuildingCallback, &info); - if (ret!=0 && info.theTallBuilding) { - // see if toPos is inside the radius of the tall building. - Coord3D bldgPos = *info.theTallBuilding->getPosition(); - Coord2D delta; - Real radius = info.theTallBuilding->getGeometryInfo().getBoundingCircleRadius() + 2*PATHFIND_CELL_SIZE_F; - delta.x = toPos.x - bldgPos.x; - delta.y = toPos.y - bldgPos.y; - if (delta.length() <= radius*0.98) { - if (delta.length() < 0.1) { - delta.x = 1; - } - delta.normalize(); - delta.x *= radius; - delta.y *= radius; - toPos.x = bldgPos.x+delta.x; - toPos.y = bldgPos.y+delta.y; - nextNode->setPosition(&toPos); - continue; - } - delta.x = fromPos.x - bldgPos.x; - delta.y = fromPos.y - bldgPos.y; - if (delta.length() <= radius*0.98) { - if (delta.length() < 0.1) { - delta.x = 1; - } - delta.normalize(); - delta.x *= radius; - delta.y *= radius; - fromPos.x = bldgPos.x+delta.x; - fromPos.y = bldgPos.y+delta.y; - } - - - computeNormalRadialOffset(fromPos, *insertPos2, toPos, info.theTallBuilding, radius); - computeNormalRadialOffset(fromPos, *insertPos1, *insertPos2, info.theTallBuilding, radius); - computeNormalRadialOffset(*insertPos2, *insertPos3, toPos, info.theTallBuilding, radius); - - return true; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -Bool Pathfinder::circleClipsTallBuilding( const Coord3D *from, const Coord3D *to, Real circleRadius, ObjectID ignoreBuilding, Coord3D *adjustTo) -{ - PartitionFilterAcceptByKindOf filterKindof(MAKE_KINDOF_MASK(KINDOF_AIRCRAFT_PATH_AROUND), KINDOFMASK_NONE); - PartitionFilter *filters[] = { &filterKindof, nullptr }; - Object* tallBuilding = ThePartitionManager->getClosestObject(to, circleRadius, FROM_BOUNDINGSPHERE_2D, filters); - if (tallBuilding) { - Real radius = tallBuilding->getGeometryInfo().getBoundingCircleRadius() + 2*PATHFIND_CELL_SIZE_F; - computeNormalRadialOffset(*from, *adjustTo, *to, tallBuilding, circleRadius+radius); - Object* otherTallBuilding = ThePartitionManager->getClosestObject(adjustTo, circleRadius, FROM_BOUNDINGSPHERE_2D, filters); - if (otherTallBuilding && otherTallBuilding!=tallBuilding) { - radius = otherTallBuilding->getGeometryInfo().getBoundingCircleRadius() + 2*PATHFIND_CELL_SIZE_F; - Coord3D tmpTo = *adjustTo; - computeNormalRadialOffset(*from, *adjustTo, tmpTo, otherTallBuilding, circleRadius+radius); - } - return true; - } - return false; -} - -//----------------------------------------------------------------------------- - -struct LinePassableStruct -{ - const Object *obj; - LocomotorSurfaceTypeMask acceptableSurfaces; - Int radius; - Bool centerInCell; - Bool blocked; - Bool allowPinched; -}; - -/*static*/ Int Pathfinder::linePassableCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - const LinePassableStruct* d = (const LinePassableStruct*)userData; - - Bool isCrusher = d->obj ? d->obj->getCrusherLevel() > 0 : false; - TCheckMovementInfo info; - info.cell.x = to_x; - info.cell.y = to_y; - info.layer = to->getLayer(); - info.centerInCell = d->centerInCell; - info.radius = d->radius; - info.considerTransient = d->blocked; - info.acceptableSurfaces = d->acceptableSurfaces; - if (!pathfinder->checkForMovement(d->obj, info)) - { - return 1; // bail out - } - - if (info.allyFixedCount || info.enemyFixed) - { - return 1; // bail out - } - - if (!d->allowPinched && to->getPinched()) { - return 1; // bail out. - } - - if (from && to->getLayer() != LAYER_GROUND && from->getLayer() == to->getLayer()) { - if (to->getType() == PathfindCell::CELL_CLEAR) { - return 0; - } - } - - if (pathfinder->validMovementPosition( isCrusher, d->acceptableSurfaces, to, from ) == false) - { - return 1; // bail out - } - - return 0; // keep going -} - -//----------------------------------------------------------------------------- - -struct GroundPathPassableStruct -{ - Int diameter; - Bool crusher; -}; - -/*static*/ Int Pathfinder::groundPathPassableCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) -{ - const GroundPathPassableStruct* d = (const GroundPathPassableStruct*)userData; - - Int curDiameter = pathfinder->clearCellForDiameter(d->crusher, to_x, to_y, to->getLayer(), d->diameter); - if (curDiameter==d->diameter) return 0; // good to go. - if (from && to->getLayer() != LAYER_GROUND && from->getLayer() == to->getLayer()) { - return 0; - } - - return 1; // failed. -} - -//----------------------------------------------------------------------------- - -/** - * Given two world-space points, check the line of sight between them for any impassible cells. - * Uses Bresenham line algorithm from www.gamedev.net. - */ -Bool Pathfinder::isLinePassable( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, - PathfindLayerEnum layer, const Coord3D& startWorld, - const Coord3D& endWorld, Bool blocked, - Bool allowPinched) -{ - LinePassableStruct info; - //CRCDEBUG_LOG(("Pathfinder::isLinePassable(): %d %d %d ", m_ignoreObstacleID, m_isMapReady, m_isTunneling)); - - info.obj = obj; - info.acceptableSurfaces = acceptableSurfaces; - getRadiusAndCenter(obj, info.radius, info.centerInCell); - info.blocked = blocked; - info.allowPinched = allowPinched; - - Int ret = iterateCellsAlongLine(startWorld, endWorld, layer, linePassableCallback, (void*)&info); - return ret == 0; -} - -//----------------------------------------------------------------------------- - -/** - * Given two world-space points, check the line of sight between them for any impassible cells. - * Uses Bresenham line algorithm from www.gamedev.net. - */ -Bool Pathfinder::isGroundPathPassable( Bool isCrusher, const Coord3D& startWorld, PathfindLayerEnum startLayer, - const Coord3D& endWorld, Int pathDiameter) -{ - GroundPathPassableStruct info; - - info.diameter = pathDiameter; - info.crusher = isCrusher; - - Int ret = iterateCellsAlongLine(startWorld, endWorld, startLayer, groundPathPassableCallback, (void*)&info); - return ret == 0; -} - -/** - * Classify the cells under the bridge - * If 'repaired' is true, bridge is repaired - * If 'repaired' is false, bridge has been damaged to be impassable - */ -void Pathfinder::changeBridgeState( PathfindLayerEnum layer, Bool repaired) -{ - if (m_layers[layer].isUnused()) return; - if (m_layers[layer].setDestroyed(!repaired)) { - m_zoneManager.markZonesDirty(); - } -} - -void Pathfinder::getRadiusAndCenter(const Object *obj, Int &iRadius, Bool ¢er) -{ - enum {MAX_RADIUS = 2}; - if (!obj) - { - center = true; - iRadius = 0; - return; - } - Real diameter = 2*obj->getGeometryInfo().getBoundingCircleRadius(); - if (diameter>PATHFIND_CELL_SIZE_F && diameter<2.0f*PATHFIND_CELL_SIZE_F) { - diameter = 2.0f*PATHFIND_CELL_SIZE_F; - } - iRadius = REAL_TO_INT_FLOOR(diameter/PATHFIND_CELL_SIZE_F+0.3f); - center = false; - if (iRadius==0) iRadius++; - if (iRadius&1) - { - center = true; - } - iRadius /= 2; - if (iRadius > MAX_RADIUS) - { - iRadius = MAX_RADIUS; - center = true; - } -} - -/** - * Updates the goal cell for an ai unit. - */ -void Pathfinder::updateGoal( Object *obj, const Coord3D *newGoalPos, PathfindLayerEnum layer) -{ - if (obj->isKindOf(KINDOF_IMMOBILE)) { - // Only consider mobile. - return; - } - - AIUpdateInterface *ai = obj->getAIUpdateInterface(); - if (!ai) return; // only consider ai objects. - if (!ai->isDoingGroundMovement()) { -#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING - 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(); - - Bool layerChanged = originalLayer != layer; - - Bool doGround=false; - Bool doLayer=false; - if (layer==LAYER_GROUND) { - doGround = true; - } else { - doLayer = true; - if (TheTerrainLogic->objectInteractsWithBridgeEnd(obj, layer)) { - doGround = true; - } - } - - ICoord2D goalCell = *ai->getPathfindGoalCell(); - - Bool centerInCell; - Int radius; - ICoord2D newCell; - getRadiusAndCenter(obj, radius, centerInCell); - Int numCellsAbove = radius; - if (centerInCell) numCellsAbove++; - if (centerInCell) { - newCell.x = REAL_TO_INT_FLOOR(newGoalPos->x/PATHFIND_CELL_SIZE_F); - newCell.y = REAL_TO_INT_FLOOR(newGoalPos->y/PATHFIND_CELL_SIZE_F); - } else { - newCell.x = REAL_TO_INT_FLOOR(0.5f+newGoalPos->x/PATHFIND_CELL_SIZE_F); - newCell.y = REAL_TO_INT_FLOOR(0.5f+newGoalPos->y/PATHFIND_CELL_SIZE_F); - } - if (!layerChanged && newCell.x==goalCell.x && newCell.y == goalCell.y) { - return; - } - removeGoal(obj); - - obj->setDestinationLayer(layer); - ai->setPathfindGoalCell(newCell); - Int i,j; - ICoord2D cellNdx; - - Bool warn = true; - for (i=newCell.x-radius; igetGoalUnit()!=INVALID_ID && cell->getGoalUnit() != obj->getID()) { - warn = false; - //Units got stuck close to each other. jba - } - cellNdx.x = i; - cellNdx.y = j; - cell->setGoalUnit(obj->getID(), cellNdx); - } - } - if (doGround) { - cell = getCell(LAYER_GROUND, i, j); - if (cell) { - if (warn && cell->getGoalUnit()!=INVALID_ID && cell->getGoalUnit() != obj->getID()) { - warn = false; - //Units got stuck close to each other. jba - } - cellNdx.x = i; - cellNdx.y = j; - cell->setGoalUnit(obj->getID(), cellNdx); - } - } - } - } - -} - -/** - * Updates the goal cell for an ai unit. - */ -void Pathfinder::updateAircraftGoal( Object *obj, const Coord3D *newGoalPos) -{ - if (obj->isKindOf(KINDOF_IMMOBILE)) { - // Only consider mobile. - return; - } - removeGoal(obj); - AIUpdateInterface *ai = obj->getAIUpdateInterface(); - if (!ai) return; // only consider ai objects. - if (ai->isDoingGroundMovement()) { - return; // shouldn't really happen, but just in case. - } - - // For now, we are only doing HOVER, and WINGS. - if (!ai->isAircraftThatAdjustsDestination()) return; - - ICoord2D goalCell = *ai->getPathfindGoalCell(); - - Bool centerInCell; - Int radius; - ICoord2D newCell; - getRadiusAndCenter(obj, radius, centerInCell); - Int numCellsAbove = radius; - if (centerInCell) numCellsAbove++; - if (centerInCell) { - newCell.x = REAL_TO_INT_FLOOR(newGoalPos->x/PATHFIND_CELL_SIZE_F); - newCell.y = REAL_TO_INT_FLOOR(newGoalPos->y/PATHFIND_CELL_SIZE_F); - } else { - newCell.x = REAL_TO_INT_FLOOR(0.5f+newGoalPos->x/PATHFIND_CELL_SIZE_F); - newCell.y = REAL_TO_INT_FLOOR(0.5f+newGoalPos->y/PATHFIND_CELL_SIZE_F); - } - if (newCell.x==goalCell.x && newCell.y == goalCell.y) { - return; - } - - ai->setPathfindGoalCell(newCell); - Int i,j; - ICoord2D cellNdx; - - for (i=newCell.x-radius; isetGoalAircraft(obj->getID(), cellNdx); - } - } - } - -} - -/** - * Removes the goal cell for an ai unit. - * Used for a unit that is going to be moving several times, like following a waypoint path, - * or intentionally collides with other units (like a car bomb). jba - */ -void Pathfinder::removeGoal( Object *obj) -{ - if (obj->isKindOf(KINDOF_IMMOBILE)) { - // Only consider mobile. - return; - } - AIUpdateInterface *ai = obj->getAIUpdateInterface(); - if (!ai) return; // only consider ai objects. - ICoord2D goalCell = *ai->getPathfindGoalCell(); - - Bool centerInCell; - Int radius; - ICoord2D newCell; - getRadiusAndCenter(obj, radius, centerInCell); - if (radius==0) { - radius++; - } - Int numCellsAbove = radius; - if (centerInCell) numCellsAbove++; - newCell.x = newCell.y = -1; - if (newCell.x==goalCell.x && newCell.y == goalCell.y) { - return; - } - ICoord2D cellNdx; - ai->setPathfindGoalCell(newCell); - Int i,j; - if (goalCell.x>=0 && goalCell.y>=0) { - for (i=goalCell.x-radius; igetGoalUnit()==obj->getID()) { - cellNdx.x = i; - cellNdx.y = j; - cell->setGoalUnit(INVALID_ID, cellNdx); - } - if (cell->getGoalAircraft()==obj->getID()) { - cellNdx.x = i; - cellNdx.y = j; - cell->setGoalAircraft(INVALID_ID, cellNdx); - } - } - if (obj->getDestinationLayer()!=LAYER_GROUND) { - cell = getCell( obj->getDestinationLayer(), i, j); - if (cell) { - if (cell->getGoalUnit()==obj->getID()) { - cellNdx.x = i; - cellNdx.y = j; - cell->setGoalUnit(INVALID_ID, cellNdx); - } - } - } - } - } - } -} - -/** - * Updates the position cell for an ai unit. - */ -void Pathfinder::updatePos( Object *obj, const Coord3D *newPos) -{ - if (obj->isKindOf(KINDOF_IMMOBILE)) - { - // Only consider mobile. - return; - } - if (!m_isMapReady) - return; - - AIUpdateInterface *ai = obj->getAIUpdateInterface(); - if (!ai) - return; // only consider ai objects. - - ICoord2D curCell = *ai->getCurPathfindCell(); - if (!ai->isDoingGroundMovement()) - { - if (curCell.x>=0 && curCell.y>=0) - { - removePos(obj); - } - return; - } - - Bool centerInCell; - Int radius; - ICoord2D newCell; - getRadiusAndCenter(obj, radius, centerInCell); - Int numCellsAbove = radius; - if (centerInCell) - numCellsAbove++; - if (centerInCell) - { - newCell.x = REAL_TO_INT_FLOOR(newPos->x/PATHFIND_CELL_SIZE_F); - newCell.y = REAL_TO_INT_FLOOR(newPos->y/PATHFIND_CELL_SIZE_F); - } - else - { - newCell.x = REAL_TO_INT_FLOOR(0.5f+newPos->x/PATHFIND_CELL_SIZE_F); - newCell.y = REAL_TO_INT_FLOOR(0.5f+newPos->y/PATHFIND_CELL_SIZE_F); - } - if (newCell.x==curCell.x && newCell.y == curCell.y) - { - return; - } - - PathfindLayerEnum layer = obj->getLayer(); - Bool doGround=false; - Bool doLayer=false; - if (layer==LAYER_GROUND) { - doGround = true; // just have to do ground - } else { - doLayer = true; // have to do the layer - if (TheTerrainLogic->objectInteractsWithBridgeEnd(obj, layer)) { - doGround = true; // In this case, have to both layer & ground, as they overlap here. - } - } - - ai->setCurPathfindCell(newCell); - Int i,j; - ICoord2D cellNdx; - //DEBUG_LOG(("Updating unit pos at cell %d, %d", newCell.x, newCell.y)); - if (curCell.x>=0 && curCell.y>=0) { - for (i=curCell.x-radius; igetPosUnit()==obj->getID()) { - cell->setPosUnit(INVALID_ID, cellNdx); - } - } - if (layer!=LAYER_GROUND) { - // Remove from the ground, if present. - cell = getCell(LAYER_GROUND, i, j); - if (cell) { - if (cell->getPosUnit()==obj->getID()) { - cell->setPosUnit(INVALID_ID, cellNdx); - } - } - } - } - } - } - for (i=newCell.x-radius; isetPosUnit(obj->getID(), cellNdx); - } - } - if (doGround) { - cell = getCell(LAYER_GROUND, i, j); - if (cell) { - cell->setPosUnit(obj->getID(), cellNdx); - } - } - } - } -} - -/** - * Removes the position cell flags for an ai unit. - */ -void Pathfinder::removePos( Object *obj) -{ - if (obj->isKindOf(KINDOF_IMMOBILE)) { - // Only consider mobile. - return; - } - if (!m_isMapReady) return; - AIUpdateInterface *ai = obj->getAIUpdateInterface(); - if (!ai) return; // only consider ai objects. - ICoord2D curCell = *ai->getCurPathfindCell(); - Bool centerInCell; - Int radius; - getRadiusAndCenter(obj, radius, centerInCell); - Int numCellsAbove = radius; - if (centerInCell) numCellsAbove++; - PathfindLayerEnum layer = obj->getLayer(); - - ICoord2D newCell; - newCell.x = newCell.y = -1; - ai->setCurPathfindCell(newCell); - - Int i,j; - ICoord2D cellNdx; - //DEBUG_LOG(("Updating unit pos at cell %d, %d", newCell.x, newCell.y)); - if (curCell.x>=0 && curCell.y>=0) { - for (i=curCell.x-radius; igetPosUnit()==obj->getID()) { - cell->setPosUnit(INVALID_ID, cellNdx); - } - } - if (layer!=LAYER_GROUND) { - // Remove from the ground, if present. - cell = getCell(LAYER_GROUND, i, j); - if (cell) { - if (cell->getPosUnit()==obj->getID()) { - cell->setPosUnit(INVALID_ID, cellNdx); - } - } - } - } - } - } -} - -/** - * Removes a mobile unit from the pathfind grid. - */ -void Pathfinder::removeUnitFromPathfindMap( Object *obj ) -{ - removePos(obj); - removeGoal(obj); -} - -Bool Pathfinder::moveAllies(Object *obj, Path *path) -{ - -#ifdef DO_UNIT_TIMINGS -#pragma MESSAGE("*** WARNING *** DOING DO_UNIT_TIMINGS!!!!") -extern Bool g_UT_startTiming; -if (g_UT_startTiming) return false; -#endif - if (!obj->isKindOf(KINDOF_DOZER) && !obj->isKindOf(KINDOF_HARVESTER)) { - // Harvesters & dozers want a clear path. - if (!path->getBlockedByAlly()) { - return FALSE; // Only move units if it is required. - } - } - LatchRestore recursiveDepth(m_moveAlliesDepth, m_moveAlliesDepth+1); - if (m_moveAlliesDepth > 2) { - return false; - } - - Bool centerInCell; - Int radius; - getRadiusAndCenter(obj, radius, centerInCell); - Int numCellsAbove = radius; - if (centerInCell) numCellsAbove++; - PathNode *node; - ObjectID ignoreId = INVALID_ID; - if (obj->getAIUpdateInterface()) { - ignoreId = obj->getAIUpdateInterface()->getIgnoredObstacleID(); - } - for( node = path->getLastNode(); node && node != path->getFirstNode(); node = node->getPrevious() ) { - ICoord2D curCell; - worldToCell(node->getPosition(), &curCell); - Int i, j; - for (i=curCell.x-radius; igetLayer(), i, j); - if (!cell) { - continue; // Cell is not on the pathfinding grid - } - - ObjectID unitId = cell->getPosUnit(); - if (unitId==INVALID_ID) { - continue; - } - - if (unitId==obj->getID()) { - continue; // It's us. - } - - if (unitId==ignoreId) { - continue; // It's the one we are ignoring. - } - - Object *otherObj = TheGameLogic->findObjectByID(unitId); - if (!otherObj) { - continue; - } - - if (obj->getRelationship(otherObj)!=ALLIES) { - continue; // Only move allies. - } - - if (obj->isKindOf(KINDOF_INFANTRY) && otherObj->isKindOf(KINDOF_INFANTRY)) { - continue; // infantry can walk through other infantry, so just let them. - } - if (obj->isKindOf(KINDOF_INFANTRY) && !otherObj->isKindOf(KINDOF_INFANTRY)) { - // If this is a general clear operation, don't let infantry push vehicles. - if (!path->getBlockedByAlly()) { - continue; - } - } - - if (!otherObj->getAI() || otherObj->getAI()->isMoving()) { - continue; - } - -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - 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); - } - } - } - return true; -} - - -/** - * Moves an allied unit out of the path of another unit. - * Uses A* algorithm. - */ -Path *Pathfinder::getMoveAwayFromPath(Object* obj, Object *otherObj, - Path *pathToAvoid, Object *otherObj2, Path *pathToAvoid2) -{ - if (!m_isMapReady) - return nullptr; // Should always be ok. - -#ifdef DEBUG_LOGGING - Int startTimeMS = ::GetTickCount(); -#endif - Bool isHuman = true; - if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { - isHuman = false; // computer gets to cheat. - } - Bool otherCenter; - Int otherRadius; - getRadiusAndCenter(otherObj, otherRadius, otherCenter); - - Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; - - m_zoneManager.setAllPassable(); - - Bool centerInCell; - Int radius; - getRadiusAndCenter(obj, radius, centerInCell); - - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - - // determine start cell - ICoord2D startCellNdx; - Coord3D startPos = *obj->getPosition(); - if (!centerInCell) { - startPos.x += PATHFIND_CELL_SIZE_F*0.5f; - startPos.x += PATHFIND_CELL_SIZE_F*0.5f; - } - worldToCell(&startPos, &startCellNdx); - PathfindCell *parentCell = getClippedCell( obj->getLayer(), obj->getPosition() ); - if (!parentCell) - return nullptr; - - if (!obj->getAIUpdateInterface()) // shouldn't happen, but can't move it without an ai. - return nullptr; - - const LocomotorSet& locomotorSet = obj->getAIUpdateInterface()->getLocomotorSet(); - - m_isTunneling = false; - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell ) == false) { - m_isTunneling = true; // We can't move from our current location. So relax the constraints. - } - - TCheckMovementInfo info; - info.cell = startCellNdx; - info.layer = obj->getLayer(); - info.centerInCell = centerInCell; - info.radius = radius; - info.considerTransient = false; - info.acceptableSurfaces = locomotorSet.getValidSurfaces(); - if (!checkForMovement(obj, info) || info.enemyFixed) { - m_isTunneling = true; // We can't move from our current location. So relax the constraints. - } - - if (!parentCell->allocateInfo(startCellNdx)) { - return nullptr; - } - parentCell->startPathfind(nullptr); - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - // "closed" list is initially empty - m_closedList.reset(); - - // - // Continue search until "open" list is empty, or - // until goal is found. - // - - Real boxHalfWidth = radius*PATHFIND_CELL_SIZE_F - (PATHFIND_CELL_SIZE_F/4.0f); - if (centerInCell) boxHalfWidth+=PATHFIND_CELL_SIZE_F/2; - boxHalfWidth += otherRadius*PATHFIND_CELL_SIZE_F; - if (otherCenter) boxHalfWidth+=PATHFIND_CELL_SIZE_F/2; - - while( !m_openList.empty() ) - { - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - Region2D bounds; - Coord3D cellCenter; - adjustCoordToCell(parentCell->getXIndex(), parentCell->getYIndex(), centerInCell, cellCenter, parentCell->getLayer()); - bounds.lo.x = cellCenter.x-boxHalfWidth; - bounds.lo.y = cellCenter.y-boxHalfWidth; - bounds.hi.x = cellCenter.x+boxHalfWidth; - bounds.hi.y = cellCenter.y+boxHalfWidth; - PathNode *node; - Bool overlap = false; - - for( node = pathToAvoid->getFirstNode(); node && node->getNextOptimized(); node = node->getNextOptimized() ) { - Coord2D start, end; - start.x = node->getPosition()->x; - start.y = node->getPosition()->y; - end.x = node->getNextOptimized()->getPosition()->x; - end.y = node->getNextOptimized()->getPosition()->y; - if (LineInRegion(&start, &end, &bounds)) { - overlap = true; - break; - } - } - - if (!overlap && pathToAvoid2) { - for( node = pathToAvoid2->getFirstNode(); node && node->getNextOptimized(); node = node->getNextOptimized() ) { - Coord2D start, end; - start.x = node->getPosition()->x; - start.y = node->getPosition()->y; - end.x = node->getNextOptimized()->getPosition()->x; - end.y = node->getNextOptimized()->getPosition()->y; - if (LineInRegion(&start, &end, &bounds)) { - overlap = true; - break; - } - } - } - if (!overlap) { - if (startCellNdx.x == parentCell->getXIndex() && startCellNdx.y == parentCell->getYIndex()) { - // we didn't move. Always move at least 1 cell. jba. - overlap = true; - } - } - ///@todo - Adjust cost intersecting path - closer to front is more expensive. jba. - if (!overlap && checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), - parentCell->getLayer(), radius, centerInCell)) { - // success - found a path to the goal - if (false && TheGlobalData->m_debugAI) - debugShowSearch(true); - m_isTunneling = false; - // construct and return path - Path *newPath = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - parentCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - } - return newPath; - } - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - - // Check to see if we can change layers in this cell. - checkChangeLayers(parentCell); - - examineNeighboringCells(parentCell, nullptr, locomotorSet, isHuman, centerInCell, radius, startCellNdx, obj, NO_ATTACK); - - } - -#if defined(RTS_DEBUG) - debugShowSearch(true); -#endif - - DEBUG_LOG(("%d getMoveAwayFromPath pathfind failed --", TheGameLogic->getFrame())); - DEBUG_LOG(("Unit '%s', time %f", obj->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f)); - - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - } - return nullptr; -} - - -/** Patch to the exiting path from the current position, either because we became blocked, - or because we had to move off the path to avoid other units. */ -Path *Pathfinder::patchPath( const Object *obj, const LocomotorSet& locomotorSet, - Path *originalPath, Bool blocked ) -{ - //CRCDEBUG_LOG(("Pathfinder::patchPath()")); -#ifdef DEBUG_LOGGING - Int startTimeMS = ::GetTickCount(); -#endif - if (originalPath==nullptr) return nullptr; - Bool centerInCell; - Int radius; - getRadiusAndCenter(obj, radius, centerInCell); - Bool isHuman = true; - if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { - isHuman = false; // computer gets to cheat. - } - - m_zoneManager.setAllPassable(); - - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - - enum {CELL_LIMIT = 2000}; // max cells to examine. - Int cellCount = 0; - - Coord3D currentPosition = *obj->getPosition(); - - // determine start cell - ICoord2D startCellNdx; - Coord3D startPos = *obj->getPosition(); - if (!centerInCell) { - startPos.x += PATHFIND_CELL_SIZE_F*0.5f; - startPos.x += PATHFIND_CELL_SIZE_F*0.5f; - } - worldToCell(&startPos, &startCellNdx); - //worldToCell(obj->getPosition(), &startCellNdx); - PathfindCell *parentCell = getClippedCell( obj->getLayer(), ¤tPosition); - if (parentCell == nullptr) - return nullptr; - if (!obj->getAIUpdateInterface()) { - return nullptr; // shouldn't happen, but can't move it without an ai. - } - - m_isTunneling = false; - - if (!parentCell->allocateInfo(startCellNdx)) { - return nullptr; - } - parentCell->startPathfind( nullptr); - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - // "closed" list is initially empty - m_closedList.reset(); - - // - // Continue search until "open" list is empty, or - // until goal is found. - // - -#if defined(RTS_DEBUG) - extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - if (TheGlobalData->m_debugAI) - { - RGBColor color; - color.setFromInt(0); - addIcon(nullptr, 0,0,color); - } -#endif - - PathNode *startNode; - Coord3D goalPos = *originalPath->getLastNode()->getPosition(); - Real goalDeltaSqr = sqr(goalPos.x-currentPosition.x) + sqr(goalPos.y - currentPosition.y); - for( startNode = originalPath->getLastNode(); startNode != originalPath->getFirstNode(); startNode = startNode->getPrevious() ) { - ICoord2D cellCoord; - worldToCell(startNode->getPosition(), &cellCoord); - TCheckMovementInfo info; - info.cell = cellCoord; - info.layer = startNode->getLayer(); - info.centerInCell = centerInCell; - info.radius = radius; - info.considerTransient = blocked; - info.acceptableSurfaces = locomotorSet.getValidSurfaces(); -#if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI) { - RGBColor color; - color.setFromInt(0); - color.green = 1; - addIcon(startNode->getPosition(), PATHFIND_CELL_SIZE_F*0.5f, 100, color); - } -#endif - Int dx = cellCoord.x-startCellNdx.x; - Int dy = cellCoord.y-startCellNdx.y; - if (dx<-2 || dx>2) info.considerTransient = false; - if (dy<-2 || dy>2) info.considerTransient = false; - if (!checkForMovement(obj, info)) { - break; - } - if (info.allyFixedCount || info.enemyFixed) { - break; // Don't patch through cells that are occupied. - } - Real curSqr = sqr(startNode->getPosition()->x-currentPosition.x) + sqr(startNode->getPosition()->y - currentPosition.y); - if (curSqr < goalDeltaSqr) { - goalPos = *startNode->getPosition(); - goalDeltaSqr = curSqr; - } - - } - if (startNode == originalPath->getLastNode()) { -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - cleanOpenAndClosedLists(); - } - else -#endif - { - parentCell->releaseInfo(); - } - return nullptr; // no open nodes. - } - PathfindCell *candidateGoal; - candidateGoal = getCell(LAYER_GROUND, &goalPos); // just using for cost estimates. - ICoord2D goalCellNdx; - worldToCell(&goalPos, &goalCellNdx); - if (!candidateGoal->allocateInfo(goalCellNdx)) { -#if RETAIL_COMPATIBLE_PATHFINDING - if (s_useFixedPathfinding) -#endif - { - parentCell->releaseInfo(); - } - return nullptr; - } - - while( !m_openList.empty() ) - { - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - Coord3D cellCenter; - adjustCoordToCell(parentCell->getXIndex(), parentCell->getYIndex(), centerInCell, cellCenter, parentCell->getLayer()); - PathNode *matchNode; - Bool found = false; - for( matchNode = originalPath->getLastNode(); matchNode != startNode; matchNode = matchNode->getPrevious() ) { - if (cellCenter.x == matchNode->getPosition()->x && cellCenter.y == matchNode->getPosition()->y) { - found = true; - break; - } - } - if (found ) { - // success - found a path to the goal - if ( TheGlobalData->m_debugAI) - debugShowSearch(true); - m_isTunneling = false; - // construct and return path - Path *path = newInstance(Path); - PathNode *node; - for( node = originalPath->getLastNode(); node != matchNode; node = node->getPrevious() ) { - path->prependNode(node->getPosition(), node->getLayer()); - } - prependCells(path, obj->getPosition(), parentCell, centerInCell); - - // cleanup the path by checking line of sight - path->optimize(obj, locomotorSet.getValidSurfaces(), blocked); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - parentCell->releaseInfo(); - cleanOpenAndClosedLists(); - candidateGoal->releaseInfo(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - candidateGoal->releaseInfo(); - } - - return path; - } - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - - if (cellCount < CELL_LIMIT) { - // Check to see if we can change layers in this cell. - checkChangeLayers(parentCell); - cellCount += examineNeighboringCells(parentCell, nullptr, locomotorSet, isHuman, centerInCell, radius, startCellNdx, obj, NO_ATTACK); - } - } - - DEBUG_LOG(("%d patchPath Pathfind failed --", TheGameLogic->getFrame())); - DEBUG_LOG(("Unit '%s', time %f", obj->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f)); - -#if defined(RTS_DEBUG) - if (TheGlobalData->m_debugAI) { - debugShowSearch(true); - } -#endif - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - if (!candidateGoal->getOpen() && !candidateGoal->getClosed()) - { - // Not on one of the lists - candidateGoal->releaseInfo(); - } - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - candidateGoal->releaseInfo(); - } - return nullptr; -} - - -/** Find a short, valid path to a location that obj can attack victim from. */ -Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomotorSet, const Coord3D *from, - const Object *victim, const Coord3D* victimPos, const Weapon *weapon ) -{ - if (!m_isMapReady) - return nullptr; // Should always be ok. - - Bool isCrusher = obj ? obj->getCrusherLevel() > 0 : false; - Int radius; - Bool centerInCell; - getRadiusAndCenter(obj, radius, centerInCell); - - // Quick check: See if moving couple of cells towards the victim will work. - { - Coord3D curPos = *obj->getPosition(); - Coord3D goalPos = victim?*victim->getPosition():*victimPos; - Coord3D delta; - delta.set(goalPos.x-curPos.x, goalPos.y-curPos.y, 0); - delta.normalize(); - delta.x *= PATHFIND_CELL_SIZE_F; - delta.y *= PATHFIND_CELL_SIZE_F; - Int i; - for (i=1; i<10; i++) { - Coord3D testPos = curPos; - testPos.x += delta.x*i*0.5f; - testPos.y += delta.y*i*0.5f; - - ICoord2D cellNdx; - worldToCell(&testPos, &cellNdx); - PathfindCell *aCell = getCell(obj->getLayer(), cellNdx.x, cellNdx.y); - if (!aCell) - break; - - if (!validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), aCell)) - break; - - if (!checkDestination(obj, cellNdx.x, cellNdx.y, obj->getLayer(), radius, centerInCell)) - break; - - if (!weapon->isGoalPosWithinAttackRange(obj, &testPos, victim, victimPos)) - continue; - - if (isAttackViewBlockedByObstacle(obj, testPos, victim, *victimPos)) - continue; - - // return path. - Path *path = newInstance(Path); - path->prependNode( &testPos, obj->getLayer() ); - path->prependNode( &curPos, obj->getLayer() ); - path->getFirstNode()->setNextOptimized(path->getFirstNode()->getNext()); - if (TheGlobalData->m_debugAI==AI_DEBUG_PATHS) { - setDebugPath(path); - } - return path; - } - } - - const Int ATTACK_CELL_LIMIT = 2500; // this is a rather expensive operation, so limit the search. - - Bool isHuman = true; - if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { - isHuman = false; // computer gets to cheat. - } - m_zoneManager.clearPassableFlags(); - Path *hPat = findClosestHierarchicalPath(isHuman, locomotorSet, from, victimPos, isCrusher); - if (hPat) { - deleteInstance(hPat); - } else { - m_zoneManager.setAllPassable(); - } - - Int cellCount = 0; - - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - - Int attackDistance = weapon->getAttackDistance(obj, victim, victimPos); - attackDistance += 3*PATHFIND_CELL_SIZE; - - // determine start cell - ICoord2D startCellNdx; - Coord3D objPos = *obj->getPosition(); - // since worldtocell truncates, add. - if (centerInCell) { - objPos.x += PATHFIND_CELL_SIZE_F/2.0f; - objPos.y += PATHFIND_CELL_SIZE_F/2.0f; - } - - if (!obj->getAIUpdateInterface()) // shouldn't happen, but can't move without an ai. - return nullptr; - - worldToCell(&objPos, &startCellNdx); - PathfindCell *parentCell = getClippedCell( obj->getLayer(), &objPos ); - if (!parentCell) - return nullptr; - - if (!parentCell->allocateInfo(startCellNdx)) - return nullptr; - - const PathfindCell *startCell = parentCell; - parentCell->startPathfind(nullptr); - - // determine start cell - ICoord2D victimCellNdx; - worldToCell(victim ? victim->getPosition() : victimPos, &victimCellNdx); - - // determine goal cell - PathfindCell *goalCell = getCell( LAYER_GROUND, victimCellNdx.x, victimCellNdx.y ); - if (!goalCell) - return nullptr; - - if (!goalCell->allocateInfo(victimCellNdx)) { - return nullptr; - } - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - // "closed" list is initially empty - m_closedList.reset(); - - // - // Continue search until "open" list is empty, or - // until goal is found. - // - - PathfindCell *closestCell = nullptr; - Real closestDistanceSqr = FLT_MAX; - Bool checkLOS = false; - if (!victim) { - checkLOS = true; - } - if (victim && !victim->isSignificantlyAboveTerrain()) { - checkLOS = true; - } - - while( !m_openList.empty() ) - { - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - Coord3D cellCenter; - adjustCoordToCell(parentCell->getXIndex(), parentCell->getYIndex(), centerInCell, cellCenter, parentCell->getLayer()); - - ///@todo - Adjust cost intersecting path - closer to front is more expensive. jba. - if (weapon->isGoalPosWithinAttackRange(obj, &cellCenter, victim, victimPos) && - checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), - parentCell->getLayer(), radius, centerInCell)) { - // check line of sight. - Bool viewBlocked = false; - if (checkLOS) - { - viewBlocked = isAttackViewBlockedByObstacle(obj, cellCenter, victim, *victimPos); - } - if (startCell == parentCell) { - // We never want to accept our starting cell. - // If we could attack from there, we wouldn't be calling - // FindAttackPath. Usually happens cause the cell is valid for attack, but - // a point near the cell center isn't, and that happens to be where the - // attacker is standing, and it's too close to move to. - viewBlocked = true; - } else { - // If through some unfortunate rounding, we end up moving near ourselves, - // don't want it. - Coord3D cellPos; - adjustCoordToCell(parentCell->getXIndex(), parentCell->getYIndex(), centerInCell, cellPos, parentCell->getLayer()); - Real dx = (cellPos.x - objPos.x); - Real dy = (cellPos.y - objPos.y); - if (sqr(dx) + sqr(dy) < sqr(PATHFIND_CELL_SIZE_F*0.5f)) { - viewBlocked = true; - } - } - if (!viewBlocked) - { - // success - found a path to the goal - Bool show = TheGlobalData->m_debugAI; - #ifdef INTENSE_DEBUG - Int count = 0; - PathfindCell *cur; - for (cur = m_closedList.getHead(); cur; cur=cur->getNextOpen()) { - count++; - } - if (count>1000) { - show = true; - DEBUG_LOG(("FAP cells %d obj %s %x", count, obj->getTemplate()->getName().str(), obj)); - #ifdef STATE_MACHINE_DEBUG - if( obj->getAIUpdateInterface() ) - { - DEBUG_LOG(("State %s", obj->getAIUpdateInterface()->getCurrentStateName().str())); - } - #endif - TheScriptEngine->AppendDebugMessage("Big Attack path", false); - } - #endif - if (show) - debugShowSearch(true); - -#if !(RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING) - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - - 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 - // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - if (goalCell->hasInfo() && !goalCell->getClosed() && !goalCell->getOpen()) { - goalCell->releaseInfo(); - } - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return path; - } - } - if (checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), parentCell->getLayer(), radius, centerInCell)) { - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell )) { - Real dx = IABS(victimCellNdx.x-parentCell->getXIndex()); - Real dy = IABS(victimCellNdx.y-parentCell->getYIndex()); - Real distSqr = dx*dx+dy*dy; - if (distSqr < closestDistanceSqr) { - closestCell = parentCell; - closestDistanceSqr = distSqr; - } - } - } - - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - - if (cellCount < ATTACK_CELL_LIMIT) { - // Check to see if we can change layers in this cell. - checkChangeLayers(parentCell); - cellCount += examineNeighboringCells(parentCell, goalCell, locomotorSet, isHuman, centerInCell, - radius, startCellNdx, obj, attackDistance); - } - - } - -#ifdef INTENSE_DEBUG - DEBUG_LOG(("obj %s %x", obj->getTemplate()->getName().str(), obj)); -#ifdef STATE_MACHINE_DEBUG - if( obj->getAIUpdateInterface() ) - { - DEBUG_LOG(("State %s", obj->getAIUpdateInterface()->getCurrentStateName().str())); - } -#endif - debugShowSearch(true); - TheScriptEngine->AppendDebugMessage("Overflowed attack path", false); -#endif -#if 0 - if (closestCell) { - // construct and return path - Path *path = buildActualPath( obj, locomotorSet, obj->getPosition(), closestCell, centerInCell, false); - cleanOpenAndClosedLists(); - return path; - } -#if defined(RTS_DEBUG) - DEBUG_LOG(("%d (%d cells) Attack Pathfind failed from (%f,%f) to (%f,%f) --", TheGameLogic->getFrame(), cellCount, from->x, from->y, victim->getPosition()->x, victim->getPosition()->y)); - DEBUG_LOG(("Unit '%s', attacking '%s' time %f", obj->getTemplate()->getName().str(), victim->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f)); -#endif -#endif -#ifdef DUMP_PERF_STATS - TheGameLogic->incrementOverallFailedPathfinds(); -#endif - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - if (goalCell->hasInfo() && !goalCell->getClosed() && !goalCell->getOpen()) { - goalCell->releaseInfo(); - } - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - goalCell->releaseInfo(); - } - return nullptr; -} - -/** Find a short, valid path to a location that is safe from the repulsors. */ -Path *Pathfinder::findSafePath( const Object *obj, const LocomotorSet& locomotorSet, - const Coord3D *from, const Coord3D* repulsorPos1, const Coord3D* repulsorPos2, Real repulsorRadius) -{ - //CRCDEBUG_LOG(("Pathfinder::findSafePath()")); - if (m_isMapReady == false) return nullptr; // Should always be ok. -#if defined(RTS_DEBUG) -// Int startTimeMS = ::GetTickCount(); -#endif - - const Int MAX_CELLS = MAX_SAFE_PATH_CELL_COUNT; // this is a rather expensive operation, so limit the search. - - Bool centerInCell; - Int radius; - getRadiusAndCenter(obj, radius, centerInCell); - Real repulsorDistSqr = repulsorRadius*repulsorRadius; - Int cellCount = 0; - Bool isHuman = true; - if (obj && obj->getControllingPlayer() && (obj->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)) { - isHuman = false; // computer gets to cheat. - } - - DEBUG_ASSERTCRASH(m_openList.empty() && m_closedList.empty(), ("Dangling lists.")); - // create unique "mark" values for open and closed cells for this pathfind invocation - - m_zoneManager.setAllPassable(); - // determine start cell - ICoord2D startCellNdx; - worldToCell(obj->getPosition(), &startCellNdx); - PathfindCell *parentCell = getClippedCell( obj->getLayer(), obj->getPosition() ); - if (parentCell == nullptr) - return nullptr; - if (!obj->getAIUpdateInterface()) { - return nullptr; // shouldn't happen, but can't move it without an ai. - } - if (!parentCell->allocateInfo(startCellNdx)) { - return nullptr; - } - parentCell->startPathfind( nullptr); - - // initialize "open" list to contain start cell -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - m_openList.reset(parentCell); - } - else -#endif - { - m_openList.reset(); - parentCell->putOnSortedOpenList(m_openList); - } - - // "closed" list is initially empty - m_closedList.reset(); - - // - // Continue search until "open" list is empty, or - // until goal is found. - // - - Real farthestDistanceSqr = 0; - - while( !m_openList.empty() ) - { - // take head cell off of open list - it has lowest estimated total path cost - parentCell = m_openList.getHead(); - parentCell->removeFromOpenList(m_openList); - - Coord3D cellCenter; - adjustCoordToCell(parentCell->getXIndex(), parentCell->getYIndex(), centerInCell, cellCenter, parentCell->getLayer()); - - ///@todo - Adjust cost intersecting path - closer to front is more expensive. jba. - Real dx = cellCenter.x-repulsorPos1->x; - Real dy = cellCenter.y-repulsorPos1->y; - Bool ok = false; - Real distSqr = dx*dx+dy*dy; - dx = cellCenter.x-repulsorPos2->x; - dy = cellCenter.y-repulsorPos2->y; - Real distSqr2 = dx*dx+dy*dy; - if (distSqr2repulsorDistSqr) { - ok = true; - } - if (m_openList.empty() && cellCount>0) { - ok = true; // exhausted the search space, just take the last cell. - } - if (distSqr > farthestDistanceSqr) { - farthestDistanceSqr = distSqr; - if (cellCount > MAX_CELLS) { -#ifdef INTENSE_DEBUG - DEBUG_LOG(("Took intermediate path, dist %f, goal dist %f", sqrt(farthestDistanceSqr), repulsorRadius)); -#endif - ok = true; // Already a big search, just take this one. - } - } - if ( ok && - checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), - parentCell->getLayer(), radius, centerInCell)) { - // success - found a path to the goal - Bool show = TheGlobalData->m_debugAI; -#ifdef INTENSE_DEBUG - Int count = 0; - PathfindCell *cur; - for (cur = m_closedList.getHead(); cur; cur=cur->getNextOpen()) { - count++; - } - if (count>2000) { - show = true; - DEBUG_LOG(("cells %d obj %s %x", count, obj->getTemplate()->getName().str(), obj)); -#ifdef STATE_MACHINE_DEBUG - if( obj->getAIUpdateInterface() ) - { - DEBUG_LOG(("State %s", obj->getAIUpdateInterface()->getCurrentStateName().str())); - } -#endif - TheScriptEngine->AppendDebugMessage("Big Safe path", false); - } -#endif - if (show) - debugShowSearch(true); -#if defined(RTS_DEBUG) - //DEBUG_LOG(("Attack path took %d cells, %f sec", cellCount, (::GetTickCount()-startTimeMS)/1000.0f)); -#endif - // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - parentCell->releaseInfo(); - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - } - return path; - } - - // put parent cell onto closed list - its evaluation is finished - parentCell->putOnClosedList( m_closedList ); - - // Check to see if we can change layers in this cell. - checkChangeLayers(parentCell); - - cellCount += examineNeighboringCells(parentCell, nullptr, locomotorSet, isHuman, centerInCell, radius, startCellNdx, obj, NO_ATTACK); - - } - -#ifdef INTENSE_DEBUG - DEBUG_LOG(("obj %s %x count %d", obj->getTemplate()->getName().str(), obj, cellCount)); -#ifdef STATE_MACHINE_DEBUG - if( obj->getAIUpdateInterface() ) - { - DEBUG_LOG(("State %s", obj->getAIUpdateInterface()->getCurrentStateName().str())); - } -#endif - TheScriptEngine->AppendDebugMessage("Overflowed Safe path", false); -#endif -#if 0 -#if defined(RTS_DEBUG) - DEBUG_LOG(("%d (%d cells) Attack Pathfind failed from (%f,%f) to (%f,%f) --", TheGameLogic->getFrame(), cellCount, from->x, from->y, victim->getPosition()->x, victim->getPosition()->y)); - DEBUG_LOG(("Unit '%s', attacking '%s' time %f", obj->getTemplate()->getName().str(), victim->getTemplate()->getName().str(), (::GetTickCount()-startTimeMS)/1000.0f)); -#endif -#endif -#ifdef DUMP_PERF_STATS - TheGameLogic->incrementOverallFailedPathfinds(); -#endif - m_isTunneling = false; -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) { - cleanOpenAndClosedLists(); - } - else -#endif - { - cleanOpenAndClosedLists(); - parentCell->releaseInfo(); - } - return nullptr; -} - -//----------------------------------------------------------------------------- -void Pathfinder::crc( Xfer *xfer ) -{ - CRCDEBUG_LOG(("Pathfinder::crc() on frame %d", TheGameLogic->getFrame())); - CRCDEBUG_LOG(("beginning CRC: %8.8X", ((XferCRC *)xfer)->getCRC())); - - xfer->xferUser( &m_extent, sizeof(IRegion2D) ); - CRCDEBUG_LOG(("m_extent: %8.8X", ((XferCRC *)xfer)->getCRC())); - - xfer->xferBool( &m_isMapReady ); - CRCDEBUG_LOG(("m_isMapReady: %8.8X", ((XferCRC *)xfer)->getCRC())); - xfer->xferBool( &m_isTunneling ); - CRCDEBUG_LOG(("m_isTunneling: %8.8X", ((XferCRC *)xfer)->getCRC())); - - Int obsolete1 = 0; - xfer->xferInt( &obsolete1 ); - - xfer->xferUser(&m_ignoreObstacleID, sizeof(ObjectID)); - CRCDEBUG_LOG(("m_ignoreObstacleID: %8.8X", ((XferCRC *)xfer)->getCRC())); - - xfer->xferUser(m_queuedPathfindRequests, sizeof(ObjectID)*PATHFIND_QUEUE_LEN); - CRCDEBUG_LOG(("m_queuedPathfindRequests: %8.8X", ((XferCRC *)xfer)->getCRC())); - xfer->xferInt(&m_queuePRHead); - CRCDEBUG_LOG(("m_queuePRHead: %8.8X", ((XferCRC *)xfer)->getCRC())); - xfer->xferInt(&m_queuePRTail); - CRCDEBUG_LOG(("m_queuePRTail: %8.8X", ((XferCRC *)xfer)->getCRC())); - - xfer->xferInt(&m_numWallPieces); - CRCDEBUG_LOG(("m_numWallPieces: %8.8X", ((XferCRC *)xfer)->getCRC())); - for (Int i=0; ixferObjectID(&m_wallPieces[MAX_WALL_PIECES]); - } - CRCDEBUG_LOG(("m_wallPieces: %8.8X", ((XferCRC *)xfer)->getCRC())); - - xfer->xferReal(&m_wallHeight); - CRCDEBUG_LOG(("m_wallHeight: %8.8X", ((XferCRC *)xfer)->getCRC())); - xfer->xferInt(&m_cumulativeCellsAllocated); - CRCDEBUG_LOG(("m_cumulativeCellsAllocated: %8.8X", ((XferCRC *)xfer)->getCRC())); - -} - -//----------------------------------------------------------------------------- -void Pathfinder::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - -} - -//----------------------------------------------------------------------------- -void Pathfinder::loadPostProcess() -{ - -} diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 85aa9ff8db0..e594d69ac80 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -232,7 +232,7 @@ set(GAMEENGINE_SRC Include/GameLogic/AIDock.h Include/GameLogic/AIGuard.h Include/GameLogic/AIGuardRetaliate.h - Include/GameLogic/AIPathfind.h +# Include/GameLogic/AIPathfind.h Include/GameLogic/AIPlayer.h Include/GameLogic/AISkirmishPlayer.h Include/GameLogic/AIStateMachine.h @@ -828,7 +828,7 @@ set(GAMEENGINE_SRC Source/GameLogic/AI/AIGroup.cpp Source/GameLogic/AI/AIGuard.cpp Source/GameLogic/AI/AIGuardRetaliate.cpp - Source/GameLogic/AI/AIPathfind.cpp +# Source/GameLogic/AI/AIPathfind.cpp Source/GameLogic/AI/AIPlayer.cpp Source/GameLogic/AI/AISkirmishPlayer.cpp Source/GameLogic/AI/AIStates.cpp diff --git a/scripts/cpp/unify_move_files.py b/scripts/cpp/unify_move_files.py index 009fe4f7dab..f76cde1c410 100644 --- a/scripts/cpp/unify_move_files.py +++ b/scripts/cpp/unify_move_files.py @@ -409,6 +409,9 @@ def main(): #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/SelectionInfo.cpp", Game.CORE, "GameEngine/Source/GameClient/SelectionInfo.cpp") #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Statistics.cpp", Game.CORE, "GameEngine/Source/GameClient/Statistics.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/AI/AIPathfind.cpp", Game.CORE, "GameEngine/Source/GameLogic/AI/AIPathfind.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/AIPathfind.h", Game.CORE, "GameEngine/Include/GameLogic/AIPathfind.h") + return