From a776005a65fd9f299a30368bcd8aa06f0844db61 Mon Sep 17 00:00:00 2001 From: Steve Date: Wed, 18 Nov 2015 11:27:05 +0000 Subject: [PATCH] Reworked AI system, to make it more generic and flexible. --- data/fighters/civilian.json | 3 +- data/fighters/shuttle.json | 3 +- data/fighters/tug.json | 3 +- makefile | 3 +- src/battle/ai.c | 182 ++++++++++++++++++++++++++---------- src/battle/fighters.c | 24 +++-- src/defs.h | 22 +++-- src/structs.h | 3 +- src/system/lookup.c | 11 ++- 9 files changed, 183 insertions(+), 71 deletions(-) diff --git a/data/fighters/civilian.json b/data/fighters/civilian.json index 7e4bd1f..05dd15d 100644 --- a/data/fighters/civilian.json +++ b/data/fighters/civilian.json @@ -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" } diff --git a/data/fighters/shuttle.json b/data/fighters/shuttle.json index 88af47e..88b7805 100644 --- a/data/fighters/shuttle.json +++ b/data/fighters/shuttle.json @@ -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" } diff --git a/data/fighters/tug.json b/data/fighters/tug.json index 21c459f..47251a3 100644 --- a/data/fighters/tug.json +++ b/data/fighters/tug.json @@ -13,5 +13,6 @@ "y" : 0 } ], - "flags" : "EF_HAS_ROPE" + "flags" : "EF_HAS_ROPE", + "aiFlags" : "AIF_AVOIDS_COMBAT" } diff --git a/makefile b/makefile index d6f4a38..b33db96 100644 --- a/makefile +++ b/makefile @@ -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) diff --git a/src/battle/ai.c b/src/battle/ai.c index 40e8968..8286670 100644 --- a/src/battle/ai.c +++ b/src/battle/ai.c @@ -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; diff --git a/src/battle/fighters.c b/src/battle/fighters.c index 9c6eebc..c8eda0a 100644 --- a/src/battle/fighters.c +++ b/src/battle/fighters.c @@ -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; diff --git a/src/defs.h b/src/defs.h index 02321fd..1c279ea 100644 --- a/src/defs.h +++ b/src/defs.h @@ -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) diff --git a/src/structs.h b/src/structs.h index 05e54b7..5d3ab29 100644 --- a/src/structs.h +++ b/src/structs.h @@ -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; diff --git a/src/system/lookup.c b/src/system/lookup.c index 98f3a71..317ed00 100644 --- a/src/system/lookup.c +++ b/src/system/lookup.c @@ -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);