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, "reloadTime" : 0,
"shieldRechargeRate" : 0, "shieldRechargeRate" : 0,
"textureName" : "gfx/craft/civilian01.png", "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, "reloadTime" : 0,
"shieldRechargeRate" : 60, "shieldRechargeRate" : 60,
"textureName" : "gfx/craft/shuttle.png", "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 "y" : 0
} }
], ],
"flags" : "EF_HAS_ROPE" "flags" : "EF_HAS_ROPE",
"aiFlags" : "AIF_AVOIDS_COMBAT"
} }

View File

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

View File

@ -36,25 +36,53 @@ static void moveToPlayer(void);
static int canAttack(Entity *f); static int canAttack(Entity *f);
static int selectWeapon(int type); static int selectWeapon(int type);
static int nearExtractionPoint(void); static int nearExtractionPoint(void);
static void moveToExtractionPoint(void);
static int nearEnemies(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 lookForPlayer(void);
static void fleeEnemies(void); static void fleeEnemies(void);
static void moveToExtractionPoint(void);
static int getActionChance(int type); static int getActionChance(int type);
static void flee(void);
static void doFighterAI(void); static void doFighterAI(void);
static void doCivilianAI(void);
void doAI(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(); doFighterAI();
return;
} }
if (self->aiFlags & AIF_FOLLOWS_PLAYER)
{
lookForPlayer();
return;
}
/* no idea - just stay where you are */
applyFighterBrakes();
} }
static void doFighterAI(void) static void doFighterAI(void)
@ -74,11 +102,11 @@ static void doFighterAI(void)
if (self->target == NULL) if (self->target == NULL)
{ {
if (player != NULL && self->side == SIDE_ALLIES) if (self->aiFlags & AIF_FOLLOWS_PLAYER)
{ {
moveToPlayer(); moveToPlayer();
} }
else if (!(self->flags & EF_FLEEING)) else
{ {
applyFighterBrakes(); applyFighterBrakes();
} }
@ -119,17 +147,6 @@ static void doFighterAI(void)
self->action = huntAndAttackTarget; self->action = huntAndAttackTarget;
self->aiActionTime = FPS; 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) static int getActionChance(int type)
@ -137,19 +154,19 @@ static int getActionChance(int type)
switch (type) switch (type)
{ {
case AI_DODGE: case AI_DODGE:
return 40 - (self->aggression * 3); return 40 - (self->aiAggression * 3);
case AI_BOOST: case AI_BOOST:
return 50 - (self->aggression * 4); return 50 - (self->aiAggression * 4);
case AI_SLOW: case AI_SLOW:
return 60 - (self->aggression * 5); return 60 - (self->aiAggression * 5);
case AI_STRAIGHT: case AI_STRAIGHT:
return 70 - (self->aggression * 6); return 70 - (self->aiAggression * 6);
case AI_HUNT: case AI_HUNT:
return 80 - (self->aggression * 7); return 80 - (self->aiAggression * 7);
} }
return 100; return 100;
@ -193,7 +210,7 @@ static void findTarget(void)
Entity *e, **candidates; Entity *e, **candidates;
unsigned int dist, closest; 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); 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 (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; self->target = e;
closest = dist; 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) static int nearEnemies(void)
{ {
int i, numEnemies; int i, numEnemies;
@ -445,8 +453,6 @@ static void fleeEnemies(void)
} }
} }
/* ====== Ally AI ======= */
static void moveToPlayer(void) static void moveToPlayer(void)
{ {
int dist = getDistance(self->x, self->y, player->x, player->y); 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) static int nearExtractionPoint(void)
{ {
int i; int i;
Entity *e, **candidates; 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; self->target = NULL;
@ -507,9 +503,97 @@ static void moveToExtractionPoint(void)
applyFighterThrust(); 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) 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(); moveToPlayer();
return; return;

View File

@ -51,21 +51,26 @@ Entity *spawnFighter(char *name, int x, int y, int side)
switch (side) switch (side)
{ {
case SIDE_ALLIES: 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; break;
case SIDE_PIRATE: case SIDE_PIRATE:
f->aggression = rand() % 3; f->aiAggression = rand() % 3;
break; break;
case SIDE_PANDORAN: case SIDE_PANDORAN:
f->aggression = 3 + rand() % 2; f->aiAggression = 3 + rand() % 2;
break; break;
} }
if (strcmp(name, "ATAF") == 0) if (strcmp(name, "ATAF") == 0)
{ {
f->aggression = 4; f->aiAggression = 4;
} }
if (strcmp(name, "Dart") == 0) if (strcmp(name, "Dart") == 0)
@ -223,7 +228,7 @@ void doFighter(void)
addHudMessage(colors.red, "Mission target has escaped."); addHudMessage(colors.red, "Mission target has escaped.");
battle.stats[STAT_ENEMIES_ESCAPED]++; battle.stats[STAT_ENEMIES_ESCAPED]++;
} }
else if (self->flags & EF_CIVILIAN) else if (strcmp(self->defName, "Civilian") == 0)
{ {
battle.stats[STAT_CIVILIANS_RESCUED]++; battle.stats[STAT_CIVILIANS_RESCUED]++;
} }
@ -251,7 +256,7 @@ void doFighter(void)
} }
else else
{ {
if (self->flags & EF_CIVILIAN) if (strcmp(self->name, "Civilian") == 0)
{ {
battle.stats[STAT_CIVILIANS_KILLED]++; battle.stats[STAT_CIVILIANS_KILLED]++;
if (!battle.epic) if (!battle.epic)
@ -494,7 +499,7 @@ void fleeAllEnemies(void)
{ {
if (e->type == ET_FIGHTER && e->side != SIDE_ALLIES) 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); 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 = MAX(f->w, f->h);
f->separationRadius *= 2; 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_DISABLED (2 << 1)
#define EF_IMMORTAL (2 << 2) #define EF_IMMORTAL (2 << 2)
#define EF_MISSION_TARGET (2 << 3) #define EF_MISSION_TARGET (2 << 3)
#define EF_FLEES (2 << 4) #define EF_NO_MT_BOX (2 << 4)
#define EF_FLEEING (2 << 5) #define EF_HAS_ROPE (2 << 5)
#define EF_NO_MT_BOX (2 << 6) #define EF_COLLECTS_ITEMS (2 << 6)
#define EF_CIVILIAN (2 << 7) #define EF_MUST_DISABLE (2 << 7)
#define EF_HAS_ROPE (2 << 8) #define EF_FLEEING (2 << 8)
#define EF_ALWAYS_FLEES (2 << 9)
#define EF_COLLECTS_ITEMS (2 << 10) #define AIF_NONE 0
#define EF_MUST_DISABLE (2 << 11) #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 */ /* player abilities */
#define BOOST_RECHARGE_TIME (FPS * 7) #define BOOST_RECHARGE_TIME (FPS * 7)

View File

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

View File

@ -38,12 +38,19 @@ void initLookups(void)
addLookup("EF_MUST_DISABLE", EF_MUST_DISABLE); addLookup("EF_MUST_DISABLE", EF_MUST_DISABLE);
addLookup("EF_IMMORTAL", EF_IMMORTAL); addLookup("EF_IMMORTAL", EF_IMMORTAL);
addLookup("EF_MISSION_TARGET", EF_MISSION_TARGET); addLookup("EF_MISSION_TARGET", EF_MISSION_TARGET);
addLookup("EF_FLEES", EF_FLEES);
addLookup("EF_FLEEING", EF_FLEEING); addLookup("EF_FLEEING", EF_FLEEING);
addLookup("EF_CIVILIAN", EF_CIVILIAN);
addLookup("EF_HAS_ROPE", EF_HAS_ROPE); addLookup("EF_HAS_ROPE", EF_HAS_ROPE);
addLookup("EF_COLLECTS_ITEMS", EF_COLLECTS_ITEMS); 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_DESTROY", TT_DESTROY);
addLookup("TT_DISABLE", TT_DISABLE); addLookup("TT_DISABLE", TT_DISABLE);
addLookup("TT_WAYPOINT", TT_WAYPOINT); addLookup("TT_WAYPOINT", TT_WAYPOINT);