From 0c0bcd5c358f42d3ca3f98a0003fcd2db314e3d3 Mon Sep 17 00:00:00 2001 From: Per Inge Mathisen Date: Sun, 17 Oct 2010 17:04:11 +0200 Subject: [PATCH] Add a danger map to the AIs. This will keep them from wandering into the line of fire of enemies while going from one place to another, and can also be used for quick lookups to see if a particular tile is threatened by potential enemy fire. Patch review by SafetyOff and with help from Cyp. --- src/astar.cpp | 18 ++++++- src/display.c | 5 +- src/init.c | 2 +- src/map.c | 120 +++++++++++++++++++++++++++++++++++++++++++++- src/map.h | 3 ++ src/scriptfuncs.c | 7 ++- src/scriptfuncs.h | 2 + 7 files changed, 151 insertions(+), 6 deletions(-) diff --git a/src/astar.cpp b/src/astar.cpp index 757104fb9..a8eaeaebd 100644 --- a/src/astar.cpp +++ b/src/astar.cpp @@ -113,6 +113,7 @@ struct PathBlockingMap PathBlockingType type; std::vector map; + std::vector dangerMap; // using threatBits }; // Data structures used for pathfinding, can contain cached results. @@ -124,6 +125,10 @@ struct PathfindContext // Not sure whether the out-of-bounds check is needed, can only happen if pathfinding is started on a blocking tile (or off the map). return x < 0 || y < 0 || x >= mapWidth || y >= mapWidth || blockingMap->map[x + y*mapWidth]; } + bool isDangerous(int x, int y) const + { + return !blockingMap->dangerMap.empty() && blockingMap->dangerMap[x + y*mapWidth]; + } bool matches(PathBlockingMap const *blockingMap_, PathCoord tileS_) const { // Must check myGameTime == blockingMap_->type.gameTime, otherwise blockingMap be a deleted pointer which coincidentally compares equal to the valid pointer blockingMap_. @@ -225,8 +230,9 @@ static inline void fpathNewNode(PathfindContext &context, PathCoord dest, PathCo // Create the node. PathNode node; + unsigned costFactor = context.isDangerous(pos.x, pos.y) ? 5 : 1; node.p = pos; - node.dist = prevDist + fpathEstimate(prevPos, pos); + node.dist = prevDist + fpathEstimate(prevPos, pos)*costFactor; node.est = node.dist + fpathEstimate(pos, dest); PathExploredTile &expl = context.map[pos.x + pos.y*mapWidth]; @@ -543,6 +549,16 @@ void fpathSetBlockingMap(PATHJOB *psJob) { map[x + y*mapWidth] = fpathBaseBlockingTile(x, y, type.propulsion, type.owner, type.moveType); } + if (!isHumanPlayer(type.owner) && type.moveType == FMT_MOVE) + { + std::vector &dangerMap = i->dangerMap; + dangerMap.resize(mapWidth*mapHeight); + for (int y = 0; y < mapHeight; ++y) + for (int x = 0; x < mapWidth; ++x) + { + dangerMap[x + y*mapWidth] = mapTile(x, y)->threatBits & (1 << type.owner); + } + } } // i now points to the correct map. Make psJob->blockingMap point to it. diff --git a/src/display.c b/src/display.c index 63db2cc79..11f984b27 100644 --- a/src/display.c +++ b/src/display.c @@ -2210,10 +2210,11 @@ void dealWithLMB( void ) { MAPTILE *psTile = mapTile(mouseTileX, mouseTileY); - CONPRINTF(ConsoleString, (ConsoleString, "%s tile %d, %d [%d, %d] continent(l%d, h%d) level %g illum %d", + CONPRINTF(ConsoleString, (ConsoleString, "%s tile %d, %d [%d, %d] continent(l%d, h%d) level %g illum %d %s %s", tileIsExplored(psTile) ? "Explored" : "Unexplored", mouseTileX, mouseTileY, world_coord(mouseTileX), world_coord(mouseTileY), - (int)psTile->limitedContinent, (int)psTile->hoverContinent, psTile->level, (int)psTile->illumination)); + (int)psTile->limitedContinent, (int)psTile->hoverContinent, psTile->level, (int)psTile->illumination, + psTile->dangerBits & (1 << selectedPlayer) ? "danger" : "", psTile->threatBits & (1 << selectedPlayer) ? "threat" : "")); } driveDisableTactical(); diff --git a/src/init.c b/src/init.c index 5863f6c65..15b3d1ebc 100644 --- a/src/init.c +++ b/src/init.c @@ -1063,8 +1063,8 @@ BOOL stageThreeInitialise(void) return false; } + mapInit(); clustInitialise(); - gridReset(); //if mission screen is up, close it. diff --git a/src/map.c b/src/map.c index c04043a3e..fecf4d5d5 100644 --- a/src/map.c +++ b/src/map.c @@ -2210,9 +2210,127 @@ bool fireOnLocation(unsigned int x, unsigned int y) return psTile != NULL && TileIsBurning(psTile); } +static void dangerFloodFill(int player) +{ + struct ffnode *open = NULL; + int i; + Vector2i pos = getPlayerStartPosition(player); + + pos.x = map_coord(pos.x); + pos.y = map_coord(pos.y); + + do + { + MAPTILE *currTile = mapTile(pos.x, pos.y); + + // Add accessible neighbouring tiles to the open list + for (i = 0; i < NUM_DIR; i++) + { + Vector2i npos = { pos.x + aDirOffset[i].x, pos.y + aDirOffset[i].y }; + MAPTILE *psTile; + + if (!tileOnMap(npos.x, npos.y) || (npos.x == pos.x && npos.y == pos.y)) + { + continue; + } + psTile = mapTile(npos.x, npos.y); + + if (!(psTile->threatBits & (1 << player)) && (psTile->dangerBits & (1 <next = open; // add to beginning of open list + node->x = npos.x; + node->y = npos.y; + open = node; + } + } + + // Clear danger + currTile->dangerBits &= ~(1 << player); + + // Pop the first open node off the list for the next iteration + if (open) + { + struct ffnode *tmp = open; + + pos.x = open->x; + pos.y = open->y; + open = open->next; + free(tmp); + } + } while (open); +} + +static inline void threatUpdate(int player, BASE_OBJECT *psObj) +{ + int i; + + if ((psObj->visible[player] || psObj->born == 2) && !aiCheckAlliances(player, psObj->player)) + { + for (i = 0; i < psObj->numWatchedTiles; i++) + { + const TILEPOS pos = psObj->watchedTiles[i]; + MAPTILE *psTile = mapTile(pos.x, pos.y); + psTile->threatBits |= (1 << player); // set threat for this tile + } + } +} + +static void dangerUpdate(int player) +{ + MAPTILE *psTile = psMapTiles; + int i; + + // Step 1: Clear our threat bits, set our danger bits + for (i = 0; i < mapWidth * mapHeight; i++, psTile++) + { + psTile->threatBits &= ~(1 << player); + psTile->dangerBits |= (1 << player); + } + + // Step 2: Set threat bits + for (i = 0; i < MAX_PLAYERS; i++) + { + DROID *psDroid; + STRUCTURE *psStruct; + + for (psDroid = apsDroidLists[i]; psDroid; psDroid = psDroid->psNext) + { + if (psDroid->droidType != DROID_CONSTRUCT && psDroid->droidType != DROID_CYBORG_CONSTRUCT + && psDroid->droidType != DROID_REPAIR && psDroid->droidType != DROID_CYBORG_REPAIR) + { + threatUpdate(player, (BASE_OBJECT *)psDroid); + } + } + + for (psStruct = apsStructLists[i]; psStruct; psStruct = psStruct->psNext) + { + if (psStruct->pStructureType->pSensor->location == LOC_TURRET || psStruct->numWeaps > 0) + { + threatUpdate(player, (BASE_OBJECT *)psStruct); + } + } + } + + // Step 3: Clear danger bits using a flood fill from start position + dangerFloodFill(player); +} + +void mapInit() +{ + int player; + + for (player = 0; player < MAX_PLAYERS; player++) + { + dangerUpdate(player); + } +} + void mapUpdate() { uint16_t currentTime = gameTime / GAME_TICKS_PER_UPDATE; + int updatedPlayer = currentTime % game.maxPlayers; int posX, posY; for (posY = 0; posY < mapHeight; ++posY) @@ -2229,5 +2347,5 @@ void mapUpdate() } } - // TODO Make waves in the water? + dangerUpdate(updatedPlayer); } diff --git a/src/map.h b/src/map.h index 29f3e683d..74a010707 100644 --- a/src/map.h +++ b/src/map.h @@ -103,6 +103,8 @@ typedef struct _maptile uint8_t tileInfoBits; uint8_t tileExploredBits; uint8_t sensorBits; // bit per player, who can see tile with sensor + uint8_t dangerBits; // bit per player, does AI sense danger going there? not always up to date + uint8_t threatBits; // bit per player, can hostile player shoot here? not always up to date float height; // The height at the top left of the tile uint8_t illumination; // How bright is this tile? uint16_t texture; // Which graphics texture is on this tile @@ -448,6 +450,7 @@ static inline bool hasSensorOnTile(MAPTILE *psTile, unsigned player) return ((player == selectedPlayer && godMode) || (alliancebits[selectedPlayer] & (satuplinkbits | psTile->sensorBits))); } +void mapInit(void); void mapUpdate(void); #ifdef __cplusplus diff --git a/src/scriptfuncs.c b/src/scriptfuncs.c index cbbf916ef..94d82baf5 100644 --- a/src/scriptfuncs.c +++ b/src/scriptfuncs.c @@ -153,6 +153,11 @@ BOOL scrGetPlayer() return true; } +Vector2i getPlayerStartPosition(int player) +{ + return positions[player]; +} + BOOL scrGetPlayerStartPosition(void) { SDWORD *x, *y, player; @@ -1455,7 +1460,7 @@ BOOL scrBuildDroid(void) if (productionRun != 1) { - debug(LOG_WARNING, "A script is trying to build a different number (%d) than 1 droid.", productionRun); + debug(LOG_ERROR, "A script is trying to build a different number (%d) than 1 droid.", productionRun); } structSetManufacture(psFactory, psTemplate); diff --git a/src/scriptfuncs.h b/src/scriptfuncs.h index 36c0a739c..f179bce3e 100644 --- a/src/scriptfuncs.h +++ b/src/scriptfuncs.h @@ -45,6 +45,8 @@ extern BOOL scrGetPlayer(void); extern BOOL scrScavengersActive(void); extern BOOL scrGetPlayerStartPosition(void); +extern Vector2i getPlayerStartPosition(int player); + // not used in scripts, but used in code. extern BOOL objectInRange(struct BASE_OBJECT *psList, SDWORD x, SDWORD y, SDWORD range);