Reworked AI system, to make it more generic and flexible.

This commit is contained in:
Steve 2015-11-18 11:27:05 +00:00
parent dfc6cc30fb
commit a776005a65
9 changed files with 183 additions and 71 deletions

View File

@ -6,5 +6,6 @@
"reloadTime" : 0,
"shieldRechargeRate" : 0,
"textureName" : "gfx/craft/civilian01.png",
"flags" : "EF_CIVILIAN+EF_FLEEING+EF_MISSION_TARGET"
"flags" : "EF_MISSION_TARGET+EF_FLEEING",
"aiFlags" : "AIF_GOAL_EXTRACTION+AIF_AVOIDS_COMBAT"
}

View File

@ -6,5 +6,6 @@
"reloadTime" : 0,
"shieldRechargeRate" : 60,
"textureName" : "gfx/craft/shuttle.png",
"flags" : "EF_COLLECTS_ITEMS"
"flags" : "EF_COLLECTS_ITEMS",
"aiFlags" : "AIF_AVOIDS_COMBAT+AIF_COLLECTS_ITEMS+AIF_UNLIMITED_RANGE"
}

View File

@ -13,5 +13,6 @@
"y" : 0
}
],
"flags" : "EF_HAS_ROPE"
"flags" : "EF_HAS_ROPE",
"aiFlags" : "AIF_AVOIDS_COMBAT"
}

View File

@ -1,6 +1,6 @@
PROG = tbftss
VERSION = 0.3
VERSION = 0.4
REVISION = $(shell date +"%y%m%d")
DEBUG = 0
@ -57,6 +57,7 @@ dist:
git log --oneline --decorate >$(PROG)-$(VERSION)/CHANGELOG.raw
tar czf $(PROG)-$(VERSION).$(REVISION)-src.tar.gz $(PROG)-$(VERSION)
mkdir -p dist
$(RM) -rf dist
mv $(PROG)-$(VERSION).$(REVISION)-src.tar.gz dist
$(RM) -rf $(PROG)-$(VERSION)

View File

@ -36,25 +36,53 @@ static void moveToPlayer(void);
static int canAttack(Entity *f);
static int selectWeapon(int type);
static int nearExtractionPoint(void);
static void moveToExtractionPoint(void);
static int nearEnemies(void);
static int nearItems(void);
static void moveToItem(void);
static int nearTowableCraft(void);
static void moveToTowableCraft(void);
static void lookForPlayer(void);
static void fleeEnemies(void);
static void moveToExtractionPoint(void);
static int getActionChance(int type);
static void flee(void);
static void doFighterAI(void);
static void doCivilianAI(void);
void doAI(void)
{
if (self->flags & EF_CIVILIAN)
if ((self->aiFlags & AIF_GOAL_EXTRACTION) && nearExtractionPoint())
{
doCivilianAI();
return;
}
else
if ((self->aiFlags & AIF_AVOIDS_COMBAT) && nearEnemies())
{
return;
}
if ((self->aiFlags & AIF_COLLECTS_ITEMS) && nearItems())
{
return;
}
if ((self->aiFlags & AIF_TOWS) && nearTowableCraft())
{
return;
}
if (!(self->aiFlags & AIF_AVOIDS_COMBAT))
{
doFighterAI();
return;
}
if (self->aiFlags & AIF_FOLLOWS_PLAYER)
{
lookForPlayer();
return;
}
/* no idea - just stay where you are */
applyFighterBrakes();
}
static void doFighterAI(void)
@ -74,11 +102,11 @@ static void doFighterAI(void)
if (self->target == NULL)
{
if (player != NULL && self->side == SIDE_ALLIES)
if (self->aiFlags & AIF_FOLLOWS_PLAYER)
{
moveToPlayer();
}
else if (!(self->flags & EF_FLEEING))
else
{
applyFighterBrakes();
}
@ -119,17 +147,6 @@ static void doFighterAI(void)
self->action = huntAndAttackTarget;
self->aiActionTime = FPS;
}
if ((player != NULL && battle.numEnemies <= 2 && self->flags & EF_FLEES) || (self->flags & EF_ALWAYS_FLEES))
{
self->action = flee;
self->aiActionTime = FPS * 3;
if (!(self->flags & EF_FLEEING) && (self->flags & EF_MISSION_TARGET) && self->side != SIDE_ALLIES)
{
addHudMessage(colors.cyan, "Mission target is escaping!");
self->flags |= EF_FLEEING;
}
}
}
static int getActionChance(int type)
@ -137,19 +154,19 @@ static int getActionChance(int type)
switch (type)
{
case AI_DODGE:
return 40 - (self->aggression * 3);
return 40 - (self->aiAggression * 3);
case AI_BOOST:
return 50 - (self->aggression * 4);
return 50 - (self->aiAggression * 4);
case AI_SLOW:
return 60 - (self->aggression * 5);
return 60 - (self->aiAggression * 5);
case AI_STRAIGHT:
return 70 - (self->aggression * 6);
return 70 - (self->aiAggression * 6);
case AI_HUNT:
return 80 - (self->aggression * 7);
return 80 - (self->aiAggression * 7);
}
return 100;
@ -193,7 +210,7 @@ static void findTarget(void)
Entity *e, **candidates;
unsigned int dist, closest;
dist = closest = (!battle.epic) ? 2000 : MAX_TARGET_RANGE;
dist = closest = (!battle.epic || (!(self->aiFlags & AIF_UNLIMITED_RANGE))) ? 2000 : MAX_TARGET_RANGE;
candidates = getAllEntsWithin(self->x - (self->w / 2) - (dist / 2), self->y - (self->h / 2) - (dist / 2), self->w + dist, self->h + dist, self);
@ -207,7 +224,7 @@ static void findTarget(void)
if (dist < closest)
{
if (self->target == NULL || ((self->target->flags & EF_CIVILIAN) == 0) || ((self->target->flags & EF_CIVILIAN) && rand() % 10) == 0)
if (self->target == NULL || ((self->target->aiFlags & AIF_AVOIDS_COMBAT) == 0) || ((self->target->aiFlags & AIF_AVOIDS_COMBAT) && rand() % 10) == 0)
{
self->target = e;
closest = dist;
@ -378,15 +395,6 @@ static void nextAction(void)
}
}
static void flee(void)
{
if (!nearEnemies() && battle.extractionPoint)
{
self->target = battle.extractionPoint;
moveToExtractionPoint();
}
}
static int nearEnemies(void)
{
int i, numEnemies;
@ -445,8 +453,6 @@ static void fleeEnemies(void)
}
}
/* ====== Ally AI ======= */
static void moveToPlayer(void)
{
int dist = getDistance(self->x, self->y, player->x, player->y);
@ -465,22 +471,12 @@ static void moveToPlayer(void)
}
}
/* ====== Civilian AI ======= */
void doCivilianAI(void)
{
if (!nearExtractionPoint() && !nearEnemies())
{
lookForPlayer();
}
}
static int nearExtractionPoint(void)
{
int i;
Entity *e, **candidates;
candidates = getAllEntsWithin(self->x - (self->w / 2) - 500, self->y - (self->h / 2) - 500, 1000, 1000, self);
candidates = getAllEntsWithin(self->x - (self->w / 2), self->y - (self->h / 2), GRID_CELL_WIDTH, GRID_CELL_HEIGHT, self);
self->target = NULL;
@ -507,9 +503,97 @@ static void moveToExtractionPoint(void)
applyFighterThrust();
}
static int nearItems(void)
{
int i;
long closest, distance;
Entity *e, **candidates;
closest = MAX_TARGET_RANGE;
candidates = getAllEntsWithin(self->x - (self->w / 2) - (GRID_CELL_WIDTH / 2), self->y - (self->h / 2) - (GRID_CELL_HEIGHT / 2), GRID_CELL_WIDTH, GRID_CELL_HEIGHT, self);
self->target = NULL;
for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i])
{
if (e->type == ET_ITEM)
{
distance = getDistance(self->x, self->y, e->x, e->y);
if (distance < closest)
{
self->target = e;
closest = distance;
}
}
}
if (self->target != NULL)
{
self->action = moveToItem;
}
return self->target != NULL;
}
static void moveToItem(void)
{
if (self->target->alive == ALIVE_ALIVE)
{
faceTarget(self->target);
applyFighterThrust();
return;
}
self->target = NULL;
self->action = doAI;
}
static int nearTowableCraft(void)
{
int i;
long closest, distance;
Entity *e, **candidates;
candidates = getAllEntsWithin(self->x - (self->w / 2) - (GRID_CELL_WIDTH / 2), self->y - (self->h / 2) - (GRID_CELL_HEIGHT / 2), GRID_CELL_WIDTH, GRID_CELL_HEIGHT, self);
self->target = NULL;
for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i])
{
if (e->type == ET_FIGHTER && (e->flags & EF_DISABLED))
{
distance = getDistance(self->x, self->y, e->x, e->y);
if (distance < closest)
{
self->target = e;
closest = distance;
}
}
}
if (self->target != NULL)
{
self->action = moveToTowableCraft;
}
return self->target != NULL;
}
static void moveToTowableCraft(void)
{
faceTarget(self->target);
applyFighterThrust();
}
static void lookForPlayer(void)
{
if (player != NULL && getDistance(self->x, self->y, player->x, player->y) < 1000)
long range = (self->aiFlags & AIF_UNLIMITED_RANGE) ? MAX_TARGET_RANGE : 1000;
if (player != NULL && getDistance(self->x, self->y, player->x, player->y) < range)
{
moveToPlayer();
return;

View File

@ -51,21 +51,26 @@ Entity *spawnFighter(char *name, int x, int y, int side)
switch (side)
{
case SIDE_ALLIES:
f->aggression = 2 + rand() % 3;
f->aiAggression = 2 + rand() % 3;
f->aiFlags |= AIF_FOLLOWS_PLAYER;
if (!(f->aiFlags & AIF_AVOIDS_COMBAT))
{
f->aiFlags |= AIF_UNLIMITED_RANGE;
}
break;
case SIDE_PIRATE:
f->aggression = rand() % 3;
f->aiAggression = rand() % 3;
break;
case SIDE_PANDORAN:
f->aggression = 3 + rand() % 2;
f->aiAggression = 3 + rand() % 2;
break;
}
if (strcmp(name, "ATAF") == 0)
{
f->aggression = 4;
f->aiAggression = 4;
}
if (strcmp(name, "Dart") == 0)
@ -223,7 +228,7 @@ void doFighter(void)
addHudMessage(colors.red, "Mission target has escaped.");
battle.stats[STAT_ENEMIES_ESCAPED]++;
}
else if (self->flags & EF_CIVILIAN)
else if (strcmp(self->defName, "Civilian") == 0)
{
battle.stats[STAT_CIVILIANS_RESCUED]++;
}
@ -251,7 +256,7 @@ void doFighter(void)
}
else
{
if (self->flags & EF_CIVILIAN)
if (strcmp(self->name, "Civilian") == 0)
{
battle.stats[STAT_CIVILIANS_KILLED]++;
if (!battle.epic)
@ -494,7 +499,7 @@ void fleeAllEnemies(void)
{
if (e->type == ET_FIGHTER && e->side != SIDE_ALLIES)
{
e->flags |= EF_ALWAYS_FLEES;
e->aiFlags |= AIF_AVOIDS_COMBAT;
}
}
}
@ -607,6 +612,11 @@ static void loadFighterDef(char *filename)
f->flags = flagsToLong(cJSON_GetObjectItem(root, "flags")->valuestring);
}
if (cJSON_GetObjectItem(root, "aiFlags"))
{
f->aiFlags = flagsToLong(cJSON_GetObjectItem(root, "aiFlags")->valuestring);
}
f->separationRadius = MAX(f->w, f->h);
f->separationRadius *= 2;

View File

@ -67,14 +67,20 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#define EF_DISABLED (2 << 1)
#define EF_IMMORTAL (2 << 2)
#define EF_MISSION_TARGET (2 << 3)
#define EF_FLEES (2 << 4)
#define EF_FLEEING (2 << 5)
#define EF_NO_MT_BOX (2 << 6)
#define EF_CIVILIAN (2 << 7)
#define EF_HAS_ROPE (2 << 8)
#define EF_ALWAYS_FLEES (2 << 9)
#define EF_COLLECTS_ITEMS (2 << 10)
#define EF_MUST_DISABLE (2 << 11)
#define EF_NO_MT_BOX (2 << 4)
#define EF_HAS_ROPE (2 << 5)
#define EF_COLLECTS_ITEMS (2 << 6)
#define EF_MUST_DISABLE (2 << 7)
#define EF_FLEEING (2 << 8)
#define AIF_NONE 0
#define AIF_FOLLOWS_PLAYER (2 << 0)
#define AIF_UNLIMITED_RANGE (2 << 1)
#define AIF_COLLECTS_ITEMS (2 << 2)
#define AIF_TOWS (2 << 3)
#define AIF_RETREATS (2 << 4)
#define AIF_GOAL_EXTRACTION (2 << 5)
#define AIF_AVOIDS_COMBAT (2 << 6)
/* player abilities */
#define BOOST_RECHARGE_TIME (FPS * 7)

View File

@ -103,11 +103,12 @@ struct Entity {
int systemHit;
int thinkTime;
int aiActionTime;
int aggression;
int aiAggression;
int separationRadius;
Weapon guns[MAX_FIGHTER_GUNS];
Weapon missiles;
long flags;
long aiFlags;
SDL_Point targetLocation;
Entity *towing;
Entity *target;

View File

@ -38,12 +38,19 @@ void initLookups(void)
addLookup("EF_MUST_DISABLE", EF_MUST_DISABLE);
addLookup("EF_IMMORTAL", EF_IMMORTAL);
addLookup("EF_MISSION_TARGET", EF_MISSION_TARGET);
addLookup("EF_FLEES", EF_FLEES);
addLookup("EF_FLEEING", EF_FLEEING);
addLookup("EF_CIVILIAN", EF_CIVILIAN);
addLookup("EF_HAS_ROPE", EF_HAS_ROPE);
addLookup("EF_COLLECTS_ITEMS", EF_COLLECTS_ITEMS);
addLookup("AIF_NONE", AIF_NONE);
addLookup("AIF_FOLLOWS_PLAYER", AIF_FOLLOWS_PLAYER);
addLookup("AIF_UNLIMITED_RANGE", AIF_UNLIMITED_RANGE);
addLookup("AIF_COLLECTS_ITEMS", AIF_COLLECTS_ITEMS);
addLookup("AIF_TOWS", AIF_TOWS);
addLookup("AIF_RETREATS", AIF_RETREATS);
addLookup("AIF_GOAL_EXTRACTION", AIF_GOAL_EXTRACTION);
addLookup("AIF_AVOIDS_COMBAT", AIF_AVOIDS_COMBAT);
addLookup("TT_DESTROY", TT_DESTROY);
addLookup("TT_DISABLE", TT_DISABLE);
addLookup("TT_WAYPOINT", TT_WAYPOINT);