Skip to content

Commit

Permalink
Coop AI don't shoot friendlies (fixes #295)
Browse files Browse the repository at this point in the history
Fix loading static wrecks in map version <= 3
  • Loading branch information
cxong committed Aug 19, 2017
1 parent c463396 commit cee0e0e
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 58 deletions.
44 changes: 1 addition & 43 deletions src/cdogs/ai_coop.c
Original file line number Diff line number Diff line change
Expand Up @@ -288,49 +288,7 @@ static int AICoopGetCmdNormal(TActor *actor)
// Still attacking the same enemy; inch closer
actor->aiContext->GunRangeScalar *= 0.99;
}
// Move to the ideal distance for the weapon
int cmd = 0;
const GunDescription *gun = ActorGetGun(actor)->Gun;
const int gunRange = GunGetRange(gun);
const int distanceSquared = DistanceSquared(
Vec2iFull2Real(actor->Pos), Vec2iFull2Real(closestEnemy->Pos));
const bool canFire = gun->CanShoot && ActorGetGun(actor)->lock <= 0;
if ((double)distanceSquared >
SQUARED(gunRange * 3 / 4) * actor->aiContext->GunRangeScalar)
{
// Move towards the enemy, fire if able
// But don't bother firing if too far away

#define MINIMUM_GUN_DISTANCE 30
// Cap the minimum distance for moving away; if we are extremely
// close to the target, fire anyway regardless of the gun range.
// This is to prevent us from not firing with a melee-range gun.
if (canFire &&
(distanceSquared < SQUARED(gunRange * 2) ||
distanceSquared > SQUARED(MINIMUM_GUN_DISTANCE)))
{
cmd = AIHunt(actor, closestEnemy->Pos) | CMD_BUTTON1;
}
else
{
// Track so that we end up in a favorable angle
cmd = AITrack(actor, closestEnemy->Pos);
}
}
else if ((double)distanceSquared <
SQUARED(gunRange * 3) * actor->aiContext->GunRangeScalar &&
!canFire)
{
// Move away from the enemy because we're too close
// Only move away if we can't fire; otherwise turn to fire
cmd = AIRetreatFrom(actor, closestEnemy->Pos);
}
else if (canFire)
{
// We're not too close and not too far; fire if able
cmd = AIHunt(actor, closestEnemy->Pos) | CMD_BUTTON1;
}
return cmd;
return AIAttack(actor, closestEnemy->Pos);
}
}

Expand Down
123 changes: 120 additions & 3 deletions src/cdogs/ai_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,64 @@ bool AIIsFacing(const TActor *a, const Vec2i targetFull, const direction_e d)
return false;
}

typedef struct
{
int ActorUID;
CollisionTeam CT;
bool HasFriendly;
} FindFriendliesInTileData;
static bool FindFriendliesInTile(void *data, const Vec2i tile);
// Whether there are friendlies in the direct line of the gun's range
static bool AIHasFriendliesInLine(const TActor *a, const direction_e d)
{
const Vec2i tileStart = Vec2iToTile(Vec2iFull2Real(a->Pos));
const Vec2i dFull = GetFullVectorsForRadians(dir2radians[d]);
const GunDescription *gun = ActorGetGun(a)->Gun;
const int gunRange = GunGetRange(gun);
const Vec2i dvFull = Vec2iScale(dFull, gunRange);
const Vec2i posFullEnd = Vec2iAdd(a->Pos, dvFull);
const Vec2i tileEnd = Vec2iToTile(Vec2iFull2Real(posFullEnd));

HasClearLineData data;
data.IsBlocked = FindFriendliesInTile;
FindFriendliesInTileData tData;
tData.ActorUID = a->uid;
tData.CT = CalcCollisionTeam(true, a);
tData.CT = CalcCollisionTeam(true, a);
tData.HasFriendly = false;
data.data = &tData;
HasClearLineJMRaytrace(tileStart, tileEnd, &data);
return tData.HasFriendly;
}
static bool FindFriendliesInTile(void *data, const Vec2i tile)
{
const Tile *t = MapGetTile(&gMap, tile);
if (t == NULL) return true;
FindFriendliesInTileData *tData = data;
CA_FOREACH(const ThingId, tid, t->things)
if (tid->Kind != KIND_CHARACTER) continue;
const TActor *other = CArrayGet(&gActors, tid->Id);
// Don't worry about self
if (tData->ActorUID == other->uid) continue;
// Never shoot prisoners/victims... it's not nice ;)
if (other->flags & (FLAGS_PRISONER | FLAGS_VICTIM))
{
tData->HasFriendly = true;
return true;
}
const CollisionTeam ctOther = CalcCollisionTeam(true, other);
if (tData->CT != COLLISIONTEAM_NONE && ctOther != COLLISIONTEAM_NONE &&
tData->CT == ctOther)
{
tData->HasFriendly = true;
return true;
}
// If it's an enemy, do shoot!
return true;
CA_FOREACH_END()
return false;
}


// Use pathfinding to check that there is a path between
// source and destination tiles
Expand Down Expand Up @@ -575,7 +633,7 @@ int AIGoto(TActor *actor, Vec2i p, bool ignoreObjects)
// x xxx
// xxxxxxxxxxxxxxxxxxxxxxx
// Those in slice A will move down-left and those in slice B will move left.
int AIHunt(TActor *actor, Vec2i targetPos)
int AIHunt(const TActor *actor, const Vec2i targetPos)
{
const Vec2i fullPos = Vec2iAdd(actor->Pos, ActorGetGunMuzzleOffset(actor));
const int dx = abs(targetPos.x - fullPos.x);
Expand Down Expand Up @@ -620,9 +678,68 @@ int AIHuntClosest(TActor *actor)
return AIHunt(actor, targetPos);
}

// Smarter attack routine:
// - Move/position towards target, keeping ideal distance from it
// - Fire if
// - has clear view to target, and
// - no friendlies in the way
int AIAttack(const TActor *a, const Vec2i targetPosFull)
{
// Move to the ideal distance for the weapon
int cmd = 0;
const GunDescription *gun = ActorGetGun(a)->Gun;
const int gunRange = GunGetRange(gun);
const int distanceSquared = DistanceSquared(
Vec2iFull2Real(a->Pos), Vec2iFull2Real(targetPosFull));
const bool canFire = gun->CanShoot && ActorGetGun(a)->lock <= 0;
if ((double)distanceSquared <
SQUARED(gunRange * 3) * a->aiContext->GunRangeScalar &&
!canFire)
{
// Move away from the enemy because we're too close
// Only move away if we can't fire; otherwise turn to fire
cmd = AIRetreatFrom(a, targetPosFull);
}
else
{
// Move towards the enemy, fire if able
// But don't bother firing if too far away

if ((double)distanceSquared > SQUARED(gunRange * 2))
{
// Too far away; approach using most efficient method
cmd = AIHunt(a, targetPosFull);
}
else
{
#define MINIMUM_GUN_DISTANCE 30
const bool willFire = canFire &&
(distanceSquared < SQUARED(gunRange * 2) ||
distanceSquared > SQUARED(MINIMUM_GUN_DISTANCE));
if (willFire)
{
// Hunt; this is the best direction to attack in
cmd = AIHunt(a, targetPosFull);
}
else
{
// Track so that we end up in a favorable angle
cmd = AITrack(a, targetPosFull);
}
// Don't fire if there's a friendly in the way
const direction_e d = cmd2dir[cmd & CMD_DIRECTIONS];
if (willFire && !AIHasFriendliesInLine(a, d))
{
cmd |= CMD_BUTTON1;
}
}
}
return cmd;
}

// Move away from the target
// Usually used for a simple flee
int AIRetreatFrom(TActor *actor, const Vec2i from)
int AIRetreatFrom(const TActor *actor, const Vec2i from)
{
return AIReverseDirection(AIHunt(actor, from));
}
Expand All @@ -637,7 +754,7 @@ int AIRetreatFrom(TActor *actor, const Vec2i from)
// x xxx
// xxxxxxxxxxxxxxxxxxxxxxx
// Those in slice A will move left and those in slice B will move down-left.
int AITrack(TActor *actor, const Vec2i targetPos)
int AITrack(const TActor *actor, const Vec2i targetPos)
{
const Vec2i fullPos = Vec2iAdd(actor->Pos, ActorGetGunMuzzleOffset(actor));
const int dx = abs(targetPos.x - fullPos.x);
Expand Down
8 changes: 5 additions & 3 deletions src/cdogs/ai_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,19 @@ bool AIHasClearPath(
bool AIHasPath(const Vec2i from, const Vec2i to, const bool ignoreObjects);
TObject *AIGetObjectRunningInto(TActor *a, int cmd);
bool AIIsFacing(const TActor *a, const Vec2i targetFull, const direction_e d);
bool AIIsInLine(const Vec2i tile, const Vec2i tileStart, const Vec2i tileEnd);

// Find path to target
// destroyObjects - if true, ignore obstructing objects
// - if false, will pathfind around them
int AIGoto(TActor *actor, Vec2i target, bool ignoreObjects);
int AIGotoDirect(const Vec2i a, const Vec2i p);
int AIHunt(TActor *actor, Vec2i targetPos);
int AIHunt(const TActor *actor, const Vec2i targetPos);
int AIAttack(const TActor *a, const Vec2i targetPosFull);
int AIHuntClosest(TActor *actor);
int AIRetreatFrom(TActor *actor, const Vec2i from);
int AIRetreatFrom(const TActor *actor, const Vec2i from);
// Like Hunt but biases towards 8 axis movement
int AITrack(TActor *actor, const Vec2i targetPos);
int AITrack(const TActor *actor, const Vec2i targetPos);
int AIMoveAwayFromLine(
const Vec2i fullPos, const Vec2i lineStartFull, const direction_e lineD);

Expand Down
6 changes: 3 additions & 3 deletions src/cdogs/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2014-2015, Cong Xu
Copyright (c) 2014-2015, 2017 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -73,8 +73,8 @@
#define Button1(x) (((x) & CMD_BUTTON1) != 0)
#define Button2(x) (((x) & CMD_BUTTON2) != 0)
#define AnyButton(x) (((x) & (CMD_BUTTON1 | CMD_BUTTON2)) != 0)
#define CMD_HAS_DIRECTION(x)\
((x) & (CMD_LEFT | CMD_RIGHT | CMD_UP | CMD_DOWN))
#define CMD_DIRECTIONS (CMD_LEFT | CMD_RIGHT | CMD_UP | CMD_DOWN)
#define CMD_HAS_DIRECTION(x) ((x) & CMD_DIRECTIONS)

// Reverse directions for command
int CmdGetReverse(int cmd);
Expand Down
22 changes: 16 additions & 6 deletions src/cdogs/map_new.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ void LoadMissions(CArray *missions, json_t *missionsNode, int version)
}
static void LoadStaticItems(
Mission *m, json_t *node, const char *name, const int version);
static void LoadStaticWrecks(Mission *m, json_t *node, const char *name);
static void LoadStaticWrecks(
Mission *m, json_t *node, const char *name, const int version);
static void LoadStaticCharacters(Mission *m, json_t *node, char *name);
static void LoadStaticObjectives(Mission *m, json_t *node, char *name);
static void LoadStaticKeys(Mission *m, json_t *node, char *name);
Expand Down Expand Up @@ -409,7 +410,7 @@ static bool TryLoadStaticMap(Mission *m, json_t *node, int version)
LoadStaticItems(m, node, "StaticItems", version);
if (version < 13)
{
LoadStaticWrecks(m, node, "StaticWrecks");
LoadStaticWrecks(m, node, "StaticWrecks", version);
}
LoadStaticCharacters(m, node, "StaticCharacters");
LoadStaticObjectives(m, node, "StaticObjectives");
Expand Down Expand Up @@ -515,7 +516,8 @@ static void LoadClassicDoors(Mission *m, json_t *node, char *name)
LoadInt(&m->u.Classic.Doors.Max, child, "Max");
}
static const MapObject *LoadMapObjectRef(json_t *node, const int version);
static const MapObject *LoadMapObjectWreckRef(json_t *itemNode);
static const MapObject *LoadMapObjectWreckRef(
json_t *itemNode, const int version);
static void LoadStaticItems(
Mission *m, json_t *node, const char *name, const int version)
{
Expand Down Expand Up @@ -554,7 +556,8 @@ static void LoadStaticItems(
CArrayPushBack(&m->u.Static.Items, &mop);
}
}
static void LoadStaticWrecks(Mission *m, json_t *node, const char *name)
static void LoadStaticWrecks(
Mission *m, json_t *node, const char *name, const int version)
{
json_t *items = json_find_first_label(node, name);
if (!items || !items->child)
Expand All @@ -565,7 +568,7 @@ static void LoadStaticWrecks(Mission *m, json_t *node, const char *name)
for (items = items->child; items; items = items->next)
{
MapObjectPositions mop;
mop.M = LoadMapObjectWreckRef(items);
mop.M = LoadMapObjectWreckRef(items, version);
if (mop.M == NULL)
{
continue;
Expand Down Expand Up @@ -621,8 +624,15 @@ static const MapObject *LoadMapObjectRef(json_t *itemNode, const int version)
return mo;
}
}
static const MapObject *LoadMapObjectWreckRef(json_t *itemNode)
static const MapObject *LoadMapObjectWreckRef(
json_t *itemNode, const int version)
{
if (version <= 3)
{
int idx;
LoadInt(&idx, itemNode, "Index");
return IntMapObject(idx);
}
const char *moName =
json_find_first_label(itemNode, "MapObject")->child->text;
const MapObject *mo = StrMapObject(moName);
Expand Down

0 comments on commit cee0e0e

Please sign in to comment.