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.
master
Per Inge Mathisen 2010-10-17 17:04:11 +02:00
parent 860d70715d
commit 0c0bcd5c35
7 changed files with 151 additions and 6 deletions

View File

@ -113,6 +113,7 @@ struct PathBlockingMap
PathBlockingType type;
std::vector<bool> map;
std::vector<bool> 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<bool> &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.

View File

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

View File

@ -1063,8 +1063,8 @@ BOOL stageThreeInitialise(void)
return false;
}
mapInit();
clustInitialise();
gridReset();
//if mission screen is up, close it.

120
src/map.c
View File

@ -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 <<player)) && !fpathBlockingTile(npos.x, npos.y, PROPULSION_TYPE_WHEELED))
{
struct ffnode *node = malloc(sizeof(*node));
node->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);
}

View File

@ -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

View File

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

View File

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