Files
PixelDefense/game/ai_actions.c
2024-02-13 19:01:51 +01:00

329 lines
11 KiB
C

#include "ai_actions.h"
#include "game_state.h"
#include "components.h"
#include "systems/systems.h"
#include "building_factory.h"
#include <raymath.h>
float shortestArc(float a, float b) {
if (fabs(b - a) < M_PI)
return b - a;
if (b > a)
return b - a - M_PI * 2.0f;
return b - a + M_PI * 2.0f;
}
BzBTStatus aiMoveTo(AIBlackboard *data, f32 dt) {
if (!data->shouldMoveTo)
return BZ_BT_FAIL;
Game *game = ecs_singleton_get_mut(ECS, Game);
const Vector2 pos = *ecs_get(ECS, data->entity, Position);
const HitBox hb = *ecs_get(ECS, data->entity, HitBox);
const Vector2 target = data->moveToPos;
Vector2 center = entityGetCenter(pos, hb);
f32 dst = Vector2Distance(center, target);
if (dst < data->proximity) {
ecs_remove(ECS, data->entity, Path);
data->shouldMoveTo = false;
return BZ_BT_SUCCESS;
}
if (!ecs_has(ECS, data->entity, Path)) {
bool pathfindSuccessful = entitySetPath(data->entity, target, game);
if (!pathfindSuccessful) {
data->shouldMoveTo = false;
return BZ_BT_FAIL;
}
}
if (ecs_has(ECS, data->entity, Orientation)) {
Orientation *orientation = ecs_get_mut(ECS, data->entity, Orientation);
f32 currentAngle = *orientation;
f32 targetAngle = Vector2Angle(center, target);
f32 dif = shortestArc(currentAngle, targetAngle);
dif = Clamp(dif, -1, 1) * dt * 10;
*orientation += dif;
*orientation = fmodf(*orientation + 180.0f, 360.0f) - 180.0f;
}
return BZ_BT_RUNNING;
}
BzBTStatus aiResetElapsed(AIBlackboard *data, f32 dt) {
data->elapsed = 0.0f;
return BZ_BT_SUCCESS;
}
#define ENEMY_NEARBY_DST 30.0f
#define ENEMY_CHASE_THRESH 25.0f
#define ENEMY_EVADE_THRESH 30.0f
BzBTStatus aiIsEnemyNearby(AIBlackboard *data, f32 dt) {
if (data->seenEnemy && ecs_is_alive(ECS, data->seenEnemy)) {
Position enemyPos = *ecs_get(ECS, data->seenEnemy, Position);
Position pos = *ecs_get(ECS, data->entity, Position);
if (Vector2Distance(enemyPos, pos) < ENEMY_NEARBY_DST)
return BZ_BT_SUCCESS;
data->seenEnemy = 0;
}
const f32 range = 30.0f;
Position pos = *ecs_get(ECS, data->entity, Position);
HitBox hb = *ecs_get(ECS, data->entity, HitBox);
Vector2 center = entityGetCenter(pos, hb);
Owner owner = *ecs_get(ECS, data->entity, Owner);
ecs_entity_t closest = 0;
f32 closestDst = 10000.0f;
Game *game = ecs_singleton_get_mut(ECS, Game);
BzSpatialGridIter it = bzSpatialGridIter(game->entityGrid,
center.x - range, center.y - range,
center.x + range, center.y + range);
while (bzSpatialGridQueryNext(&it)) {
ecs_entity_t other = *(ecs_entity_t *) it.data;
if (!ecs_is_alive(ECS, other)) continue;
if (!ecs_has(ECS, other, Owner) ||
ecs_get(ECS, other, Owner)->player == owner.player)
continue;
Position otherPos = *ecs_get(ECS, other, Position);
HitBox otherHB = *ecs_get(ECS, other, HitBox);
Vector2 otherCenter = entityGetCenter(otherPos, otherHB);
f32 dst = Vector2Distance(center, otherCenter);
if (dst > range) continue;
if (!bzTileMapCanRayCastLine(&game->map, center, otherCenter))
continue;
if (dst < closestDst) {
closest = other;
closestDst = dst;
}
}
if (closest) {
data->seenEnemy = closest;
return BZ_BT_SUCCESS;
}
return BZ_BT_FAIL;
}
BzBTStatus aiAttackEnemy(AIBlackboard *data, f32 dt) {
if (!ecs_is_alive(ECS, data->seenEnemy))
return BZ_BT_SUCCESS;
Position enemyPos = *ecs_get(ECS, data->seenEnemy, Position);
Position pos = *ecs_get(ECS, data->entity, Position);
f32 dst = Vector2Distance(pos, enemyPos);
if (dst > ENEMY_CHASE_THRESH)
return BZ_BT_FAIL;
Vector2 dif = Vector2Subtract(enemyPos, pos);
// Overload steering
Steering *steering = ecs_get_mut(ECS, data->entity, Steering);
*steering = Vector2Normalize(dif);
return BZ_BT_RUNNING;
}
BzBTStatus aiEvadeEnemy(AIBlackboard *data, f32 dt) {
if (!ecs_is_alive(ECS, data->seenEnemy))
return BZ_BT_SUCCESS;
Position enemyPos = *ecs_get(ECS, data->seenEnemy, Position);
Position pos = *ecs_get(ECS, data->entity, Position);
f32 dst = Vector2Distance(pos, enemyPos);
if (dst > ENEMY_EVADE_THRESH)
return BZ_BT_FAIL;
Vector2 dif = Vector2Subtract(pos, enemyPos);
// Overload steering
Steering *steering = ecs_get_mut(ECS, data->entity, Steering);
*steering = Vector2Normalize(dif);
return BZ_BT_RUNNING;
}
BzBTStatus aiFindNextHarvestable(AIBlackboard *data, f32 dt) {
ecs_entity_t harvestTarget = data->as.worker.harvestTarget;
if (ecs_is_alive(ECS, harvestTarget)) {
BZ_ASSERT(ecs_has_id(ECS, harvestTarget, ecs_id(Harvestable)));
Harvestable harvestable = *ecs_get(ECS, harvestTarget, Harvestable);
if (harvestable.harvestCount < harvestable.harvestLimit) {
// Target still alive, no need to find next harvestable
data->moveToPos = data->as.worker.harvestPos;
data->shouldMoveTo = true;
return BZ_BT_SUCCESS;
}
}
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
Game *game = ecs_singleton_get_mut(ECS, Game);
ResourceType harvestType = data->as.worker.harvestType;
// Perform spatial search
ecs_entity_t closest = 0;
f32 closestDst = INFINITY;
Vector2 closestPos = Vector2Zero();
const f32 range = 20.0f;
f32 hRange = range * 0.5f;
Vector2 pos = data->as.worker.harvestPos; // Last know harvest pos
Rectangle area = {pos.x - hRange, pos.y + hRange, hRange, hRange};
BzSpatialGridIter it = bzSpatialGridIter(game->entityGrid,
area.x, area.y,
area.width, area.height);
while (bzSpatialGridQueryNext(&it)) {
ecs_entity_t entity = *(ecs_entity_t *) it.data;
if (!ecs_is_alive(ECS, entity)) continue;
if (!ecs_has_id(ECS, entity, ecs_id(Harvestable)) ||
!ecs_has(ECS, entity, Resource) ||
!ecs_has(ECS, entity, Position))
continue;
Harvestable harvestable = *ecs_get(ECS, entity, Harvestable);
if (harvestable.harvestCount >= harvestable.harvestLimit)
continue;
Resource resource = *ecs_get(ECS, entity, Resource);
Position resPos = *ecs_get(ECS, entity, Position);
if (resource.type != harvestType) continue;
f32 dst = Vector2Distance(pos, resPos);
if (dst < closestDst) {
closest = entity;
closestDst = dst;
closestPos = resPos;
}
}
if (closest) {
data->as.worker.harvestTarget = closest;
data->as.worker.harvestPos = closestPos;
data->moveToPos = closestPos;
data->shouldMoveTo = true;
return BZ_BT_SUCCESS;
}
return BZ_BT_FAIL;
}
BzBTStatus aiFindNearestStorage(AIBlackboard *data, f32 dt) {
if (!ecs_has(ECS, data->entity, Worker))
return BZ_BT_FAIL;
ecs_filter_t *storageFilter = ecs_filter(ECS, {
.terms = {{ecs_id(Position)}, {ecs_id(Storage)}},
});
ecs_iter_t it = ecs_filter_iter(ECS, storageFilter);
const Vector2 pos = *ecs_get(ECS, data->entity, Position);
ResourceType resType = data->as.worker.harvestType;
ecs_entity_t closest = 0;
f32 closestDst = INFINITY;
Position closestPos = {INFINITY, INFINITY};
while (ecs_filter_next(&it)) {
Position *storagePos = ecs_field(&it, Position, 1);
Storage *storage = ecs_field(&it, Storage, 2);
for (i32 i = 0; i < it.count; i++) {
if (resType < 0 || resType >= RES_COUNT) continue;
if (!storage[i].stores[resType]) continue;
f32 dst = Vector2Distance(pos, storagePos[i]);
if (closestDst == INFINITY || dst < closestDst) {
closest = it.entities[i];
closestDst = dst;
closestPos = storagePos[i];
}
}
}
ecs_filter_fini(storageFilter);
if (!closest) {
return BZ_BT_FAIL;
}
data->as.worker.depositTarget = closest;
data->moveToPos = getPositionNearBuilding(closest, pos);
data->shouldMoveTo = true;
return BZ_BT_SUCCESS;
}
BzBTStatus aiHarvestRes(AIBlackboard *data, f32 dt) {
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
if (worker->carry >= worker->carryCapacity)
return BZ_BT_FAIL;
ecs_entity_t harvestTarget = data->as.worker.harvestTarget;
if (!ecs_is_alive(ECS, harvestTarget))
return BZ_BT_FAIL;
BZ_ASSERT(ecs_has_id(ECS, harvestTarget, ecs_id(Harvestable)));
Harvestable *harvestable = ecs_get_mut(ECS, harvestTarget, Harvestable);
if (harvestable->harvestCount >= harvestable->harvestLimit)
return BZ_BT_FAIL;
harvestable->harvestCount++;
if (data->elapsed < worker->collectSpeed) {
data->elapsed += dt;
return BZ_BT_RUNNING;
}
data->elapsed = 0;
// Collect
i32 spareCapacity = worker->carryCapacity - worker->carry;
BZ_ASSERT(spareCapacity >= 0);
i32 collected = harvestEvent(harvestTarget, (HarvestEvent) {
.amount = BZ_MIN(1, spareCapacity),
.type = worker->carryRes
});
worker->carry += collected;
return BZ_BT_SUCCESS;
}
BzBTStatus aiDepositRes(AIBlackboard *data, f32 dt) {
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
if (worker->carry == 0)
return BZ_BT_SUCCESS;
ecs_entity_t depositTarget = data->as.worker.depositTarget;
if (!ecs_is_alive(ECS, depositTarget))
return BZ_BT_FAIL;
if (data->elapsed < worker->depositSpeed) {
data->elapsed += dt;
return BZ_BT_RUNNING;
}
depositEvent(depositTarget, (DepositEvent) {
.amount = worker->carry,
.type = worker->carryRes
});
worker->carry = 0;
return BZ_BT_SUCCESS;
}
BzBTStatus aiCarryCapacityFull(AIBlackboard *data, f32 dt) {
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
if (worker->carry >= worker->carryCapacity)
return BZ_BT_SUCCESS;
return BZ_BT_FAIL;
}
BzBTStatus aiCarryCapacityEmpty(AIBlackboard *data, f32 dt) {
BZ_ASSERT(ecs_has(ECS, data->entity, Worker));
Worker *worker = ecs_get_mut(ECS, data->entity, Worker);
if (worker->carry == 0)
return BZ_BT_SUCCESS;
return BZ_BT_FAIL;
}