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);