Fix and integrate BT
This commit is contained in:
@@ -1,190 +0,0 @@
|
||||
#include "unit_actions.h"
|
||||
#include "game_state.h"
|
||||
#include "components.h"
|
||||
#include "systems/systems.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;
|
||||
}
|
||||
|
||||
void actionMoveTo(ecs_entity_t entity, Action *action, Game *game) {
|
||||
if (action->finished) return;
|
||||
const Vector2 target = action->as.moveTo.target;
|
||||
if (!ecs_has(ECS, entity, Path)) {
|
||||
entitySetPath(entity, target, game);
|
||||
return;
|
||||
}
|
||||
const f32 dt = GetFrameTime();
|
||||
const Vector2 pos = *ecs_get(ECS, entity, Position);
|
||||
if (ecs_has(ECS, entity, Orientation)) {
|
||||
Orientation *orientation = ecs_get_mut(ECS, entity, Orientation);
|
||||
f32 currentAngle = *orientation;
|
||||
f32 targetAngle = Vector2Angle(pos, target);
|
||||
f32 dif = shortestArc(currentAngle, targetAngle);
|
||||
dif = Clamp(dif, -1, 1) * dt * 10;
|
||||
*orientation += dif;
|
||||
*orientation = fmodf(*orientation + 180.0f, 360.0f) - 180.0f;
|
||||
}
|
||||
f32 dst = Vector2Distance(pos, target);
|
||||
if (dst < action->as.moveTo.proximityThreshold) {
|
||||
action->finished = true;
|
||||
ecs_remove(ECS, entity, Path);
|
||||
}
|
||||
}
|
||||
|
||||
void actionCollectResource(ecs_entity_t entity, Action *action, Game *game) {
|
||||
if (action->finished) return;
|
||||
BZ_ASSERT(ecs_has(ECS, entity, Worker));
|
||||
Worker *worker = ecs_get_mut(ECS, entity, Worker);
|
||||
|
||||
if (worker->carry >= worker->carryCapacity) {
|
||||
action->finished = true;
|
||||
}
|
||||
|
||||
ecs_entity_t target = action->as.collectResource.entity;
|
||||
bool targetAlive = ecs_is_alive(ECS, target);
|
||||
action->finished |= !targetAlive;
|
||||
|
||||
if (!action->finished && action->elapsed > worker->collectSpeed) {
|
||||
i32 spareCapacity = worker->carryCapacity - worker->carry;
|
||||
i32 collected = harvestEvent(target, (HarvestEvent) {
|
||||
.amount = BZ_MIN(1, spareCapacity),
|
||||
});
|
||||
worker->carry += collected;
|
||||
action->elapsed = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void actionDepositResource(ecs_entity_t entity, Action *action, Game *game) {
|
||||
if (action->finished) return;
|
||||
|
||||
BZ_ASSERT(ecs_has(ECS, entity, Worker));
|
||||
Worker *worker = ecs_get_mut(ECS, entity, Worker);
|
||||
|
||||
if (worker->carry == 0) {
|
||||
action->finished = true;
|
||||
}
|
||||
ecs_entity_t target = action->as.depositResource.entity;
|
||||
bool targetAlive = ecs_is_alive(ECS, target);
|
||||
action->finished |= !targetAlive;
|
||||
|
||||
if (!action->finished && action->elapsed > worker->depositSpeed) {
|
||||
depositEvent(target, (DepositEvent) {
|
||||
.amount = worker->carry
|
||||
});
|
||||
worker->carry = 0;
|
||||
action->finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
void handleAction(ecs_entity_t entity, UnitAction *unitAction, Game *game) {
|
||||
Action *action = unitAction->first;
|
||||
if (action == NULL) return;
|
||||
if (action->finished || action->failed) return;
|
||||
switch (action->type) {
|
||||
case ACTION_NONE:
|
||||
break;
|
||||
case ACTION_MOVE_TO:
|
||||
actionMoveTo(entity, action, game);
|
||||
break;
|
||||
case ACTION_COLLECT_RESOURCE:
|
||||
actionCollectResource(entity, action, game);
|
||||
break;
|
||||
case ACTION_DEPOSIT_RESOURCE:
|
||||
actionDepositResource(entity, action, game);
|
||||
break;
|
||||
default:
|
||||
BZ_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void updateAction(UnitAction *unitAction, Game *game) {
|
||||
Action *action = unitAction->first;
|
||||
if (action == NULL) return;
|
||||
if (action->failed) return;
|
||||
action->elapsed += GetFrameTime();
|
||||
if (action->finished) {
|
||||
unitAction->first = action->next;
|
||||
if (unitAction->last == action) {
|
||||
unitAction->last = NULL;
|
||||
BZ_ASSERT(unitAction->first == unitAction->last);
|
||||
}
|
||||
BzObjectPool *pool = game->pools.actions;
|
||||
bzObjectPoolRelease(pool, action);
|
||||
}
|
||||
}
|
||||
|
||||
void clearActions(ecs_entity_t entity, Game *game) {
|
||||
if (!ecs_has(ECS, entity, UnitAction)) return;
|
||||
UnitAction *unitAction = ecs_get_mut(ECS, entity, UnitAction);
|
||||
|
||||
BzObjectPool *pool = game->pools.actions;
|
||||
|
||||
Action *pAction = unitAction->first;
|
||||
while (pAction) {
|
||||
bzObjectPoolRelease(pool, pAction);
|
||||
pAction = pAction->next;
|
||||
}
|
||||
|
||||
unitAction->first = NULL;
|
||||
unitAction->last = NULL;
|
||||
}
|
||||
|
||||
void addAction(ecs_entity_t entity, Game *game, const Action *action) {
|
||||
BZ_ASSERT(action);
|
||||
BZ_ASSERT(ecs_has(ECS, entity, UnitAction));
|
||||
|
||||
UnitAction *unitAction = ecs_get_mut(ECS, entity, UnitAction);
|
||||
BzObjectPool *pool = game->pools.actions;
|
||||
|
||||
Action *newAction = bzObjectPool(pool);
|
||||
BZ_ASSERT(newAction);
|
||||
*newAction = *action;
|
||||
|
||||
newAction->next = NULL;
|
||||
if (unitAction->last) {
|
||||
Action *last = unitAction->last;
|
||||
last->next = newAction;
|
||||
unitAction->last = newAction;
|
||||
} else {
|
||||
unitAction->first = newAction;
|
||||
unitAction->last = newAction;
|
||||
}
|
||||
}
|
||||
|
||||
void prependAction(ecs_entity_t entity, Game *game, const Action *action) {
|
||||
BZ_ASSERT(action);
|
||||
BZ_ASSERT(ecs_has(ECS, entity, UnitAction));
|
||||
|
||||
UnitAction *unitAction = ecs_get_mut(ECS, entity, UnitAction);
|
||||
BzObjectPool *pool = game->pools.actions;
|
||||
|
||||
Action *newAction = bzObjectPool(pool);
|
||||
BZ_ASSERT(newAction);
|
||||
*newAction = *action;
|
||||
|
||||
newAction->next = unitAction->first;
|
||||
unitAction->first = newAction;
|
||||
}
|
||||
|
||||
const char *actionTypeToPrettyStr(ActionType type) {
|
||||
switch (type) {
|
||||
case ACTION_MOVE_TO:
|
||||
return "MOVE TO";
|
||||
case ACTION_COLLECT_RESOURCE:
|
||||
return "COLLECT RESOURCE";
|
||||
case ACTION_DEPOSIT_RESOURCE:
|
||||
return "DEPOSIT RESOURCE";
|
||||
default:
|
||||
return "NONE";
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
#ifndef PIXELDEFENSE_UNIT_ACTIONS_H
|
||||
#define PIXELDEFENSE_UNIT_ACTIONS_H
|
||||
|
||||
#include <breeze.h>
|
||||
#include <flecs.h>
|
||||
|
||||
typedef struct Game Game;
|
||||
|
||||
typedef enum ActionType {
|
||||
ACTION_NONE,
|
||||
ACTION_MOVE_TO,
|
||||
ACTION_COLLECT_RESOURCE,
|
||||
ACTION_DEPOSIT_RESOURCE,
|
||||
ACTION_COUNT,
|
||||
} ActionType;
|
||||
|
||||
typedef struct ActionMoveTo {
|
||||
Vector2 target;
|
||||
f32 proximityThreshold;
|
||||
} ActionMoveTo;
|
||||
typedef struct ActionCollectResource {
|
||||
ecs_entity_t entity;
|
||||
} ActionCollectResource;
|
||||
typedef struct ActionDepositResource {
|
||||
ecs_entity_t entity;
|
||||
} ActionDepositResource;
|
||||
|
||||
typedef struct Action Action;
|
||||
typedef void (*ActionCb)(ecs_entity_t entity, Action *action, Game *game);
|
||||
|
||||
typedef struct Action {
|
||||
ActionType type;
|
||||
union {
|
||||
ActionMoveTo moveTo;
|
||||
ActionCollectResource collectResource;
|
||||
ActionDepositResource depositResource;
|
||||
} as;
|
||||
|
||||
f32 elapsed;
|
||||
bool finished;
|
||||
bool failed;
|
||||
|
||||
ActionCb onBegin;
|
||||
ActionCb onFinish;
|
||||
|
||||
struct Action *next;
|
||||
} Action;
|
||||
|
||||
typedef struct UnitAction {
|
||||
Action *first;
|
||||
Action *last;
|
||||
} UnitAction;
|
||||
|
||||
void handleAction(ecs_entity_t entity, UnitAction *unitAction, Game *game);
|
||||
void updateAction(UnitAction *unitAction, Game *game);
|
||||
void clearActions(ecs_entity_t entity, Game *game);
|
||||
void addAction(ecs_entity_t entity, Game *game, const Action *action);
|
||||
void prependAction(ecs_entity_t entity, Game *game, const Action *action);
|
||||
|
||||
const char *actionTypeToPrettyStr(ActionType type);
|
||||
|
||||
#endif //PIXELDEFENSE_UNIT_ACTIONS_H
|
||||
163
game/unit_ai.c
163
game/unit_ai.c
@@ -1,163 +0,0 @@
|
||||
#include "unit_ai.h"
|
||||
#include "unit_actions.h"
|
||||
#include "game_state.h"
|
||||
#include "buildings.h"
|
||||
|
||||
#include <raymath.h>
|
||||
|
||||
static ecs_entity_t findNearestStorage(Position pos, ResourceType type, Position *outPos) {
|
||||
ecs_filter_t *storageFilter = ecs_filter(ECS, {
|
||||
.terms = {{ecs_id(Position)}, {ecs_id(Storage)}},
|
||||
});
|
||||
ecs_iter_t it = ecs_filter_iter(ECS, storageFilter);
|
||||
|
||||
ecs_entity_t closest = 0;
|
||||
f32 closestDst = INFINITY;
|
||||
Position closestPos = Vector2Zero();
|
||||
while (ecs_filter_next(&it)) {
|
||||
Position *storagePos = ecs_field(&it, Position, 1);
|
||||
|
||||
for (i32 i = 0; i < it.count; i++) {
|
||||
f32 dst = Vector2Distance(pos, storagePos[i]);
|
||||
if (dst < closestDst) {
|
||||
closest = it.entities[i];
|
||||
closestDst = dst;
|
||||
closestPos = storagePos[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
ecs_filter_fini(storageFilter);
|
||||
if (outPos) *outPos = closestPos;
|
||||
return closest;
|
||||
}
|
||||
static ecs_entity_t findNearestResource(Game *game, Position pos, ResourceType type, f32 range) {
|
||||
ecs_entity_t closest = 0;
|
||||
f32 closestDst = 10000.0f;
|
||||
|
||||
f32 hRange = range * 0.5f;
|
||||
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(ECS, entity, Resource) || !ecs_has(ECS, entity, Position))
|
||||
continue;
|
||||
const Resource *resource = ecs_get(ECS, entity, Resource);
|
||||
const Position *resPos = ecs_get(ECS, entity, Position);
|
||||
if (resource->type != type) continue;
|
||||
|
||||
f32 dst = Vector2Distance(pos, *resPos);
|
||||
if (dst < closestDst) {
|
||||
closest = entity;
|
||||
closestDst = dst;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
void aiWorkerBuild(ecs_entity_t entity, UnitAI *unitAI, Game *game) {
|
||||
|
||||
}
|
||||
/**********************************
|
||||
* Worker AI
|
||||
**********************************/
|
||||
|
||||
void actionCollectResourceOnFinish(ecs_entity_t entity, Action *action, Game *game) {
|
||||
Worker *worker = ecs_get_mut(ECS, entity, Worker);
|
||||
// Full, nothing to be done
|
||||
if (worker->carry >= worker->carryCapacity)
|
||||
return;
|
||||
|
||||
// Not full yet, find a new resource
|
||||
Position pos = *ecs_get(ECS, entity, Position);
|
||||
ecs_entity_t target = findNearestResource(game, pos, worker->carryRes, 20.0f);
|
||||
if (target) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void aiWorkerHarvestUpdate(ecs_entity_t entity, UnitAI *unitAI, Game *game) {
|
||||
const Action *action = unitAI->action;
|
||||
|
||||
BZ_ASSERT(ecs_has(ECS, entity, Worker));
|
||||
Worker *worker = ecs_get_mut(ECS, entity, Worker);
|
||||
|
||||
if (action && action->type == ACTION_COLLECT_RESOURCE && action->finished) {
|
||||
// Full, nothing to be done
|
||||
if (worker->carry >= worker->carryCapacity)
|
||||
return;
|
||||
|
||||
// TODO: Not full yet, find a new resource
|
||||
|
||||
}
|
||||
|
||||
if (action) return;
|
||||
|
||||
AIWorkerHarvest workerAI = unitAI->as.workerHarvest;
|
||||
|
||||
Position workerPos = *ecs_get(ECS, entity, Position);
|
||||
|
||||
// Finished all tasks, repeat
|
||||
ecs_entity_t target = workerAI.target;
|
||||
Position targetPos = workerAI.targetPosition;
|
||||
if (!ecs_is_alive(ECS, target)) {
|
||||
// Find new resource
|
||||
target = findNearestResource(game, targetPos, workerAI.resource, 20.0f);
|
||||
if (!target) return;
|
||||
|
||||
workerAI.target = target;
|
||||
workerAI.targetPosition = *ecs_get(ECS, target, Position);
|
||||
}
|
||||
// Find the closest warehouse
|
||||
Position warehousePos = Vector2Zero();
|
||||
ecs_entity_t warehouse = findNearestStorage(workerPos, workerAI.resource,
|
||||
&warehousePos);
|
||||
if (!warehouse) {
|
||||
return;
|
||||
}
|
||||
warehousePos = getPositionNearBuilding(warehouse, targetPos);
|
||||
|
||||
const f32 proximityThreshold = 8.0f;
|
||||
|
||||
addAction(entity, game, &(const Action) {
|
||||
.type = ACTION_MOVE_TO,
|
||||
.as.moveTo.target = targetPos,
|
||||
.as.moveTo.proximityThreshold = proximityThreshold,
|
||||
});
|
||||
addAction(entity, game, &(const Action) {
|
||||
.type = ACTION_COLLECT_RESOURCE,
|
||||
.as.collectResource.entity = target,
|
||||
});
|
||||
addAction(entity, game, &(const Action) {
|
||||
.type = ACTION_MOVE_TO,
|
||||
.as.moveTo.target = warehousePos,
|
||||
.as.moveTo.proximityThreshold = proximityThreshold,
|
||||
});
|
||||
addAction(entity, game, &(const Action) {
|
||||
.type = ACTION_DEPOSIT_RESOURCE,
|
||||
.as.depositResource.entity = warehouse
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void updateUnitAI(ecs_entity_t entity, UnitAI *unitAI, Game *game) {
|
||||
//if (unitAI->onUpdate) unitAI->onUpdate(entity, unitAI, game);
|
||||
aiWorkerHarvestUpdate(entity, unitAI, game);
|
||||
}
|
||||
void setUnitAI(ecs_entity_t entity, Game *game, const UnitAI *unitAI) {
|
||||
BZ_ASSERT(unitAI);
|
||||
if (ecs_has(ECS, entity, UnitAI)) {
|
||||
UnitAI *entityAI = ecs_get_mut(ECS, entity, UnitAI);
|
||||
if (entityAI->onRemove) entityAI->onRemove(entity, entityAI, game);
|
||||
}
|
||||
clearActions(entity, game);
|
||||
|
||||
UnitAI ai = *unitAI;
|
||||
if (unitAI->onSet) unitAI->onSet(entity, &ai, game);
|
||||
ecs_set_ptr(ECS, entity, UnitAI, &ai);
|
||||
if (unitAI->action != NULL)
|
||||
addAction(entity, game, unitAI->action);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifndef PIXELDEFENSE_UNIT_AI_H
|
||||
#define PIXELDEFENSE_UNIT_AI_H
|
||||
|
||||
#include <flecs.h>
|
||||
|
||||
#include "components.h"
|
||||
#include "unit_actions.h"
|
||||
|
||||
typedef struct Game Game;
|
||||
|
||||
typedef enum AIType {
|
||||
AI_NONE,
|
||||
AI_WORKER_BUILD,
|
||||
AI_WORKER_HARVEST,
|
||||
AI_COUNT,
|
||||
} AIType;
|
||||
|
||||
|
||||
typedef struct AIWorkerHarvest {
|
||||
ResourceType resource;
|
||||
ecs_entity_t target;
|
||||
Position targetPosition;
|
||||
} AIWorkerHarvest;
|
||||
|
||||
typedef struct AIWorkerBuild {
|
||||
ecs_entity_t buildTarget;
|
||||
} AIWorkerBuild;
|
||||
|
||||
typedef struct UnitAI UnitAI;
|
||||
|
||||
typedef void (*UnitAIFunc)(ecs_entity_t entity, UnitAI *ai, Game *game);
|
||||
|
||||
typedef struct UnitAI {
|
||||
AIType type;
|
||||
union {
|
||||
AIWorkerHarvest workerHarvest;
|
||||
AIWorkerBuild workerBuild;
|
||||
} as;
|
||||
|
||||
Action *action;
|
||||
|
||||
// vtable
|
||||
UnitAIFunc onSet;
|
||||
UnitAIFunc onRemove;
|
||||
UnitAIFunc onUpdate;
|
||||
} UnitAI;
|
||||
|
||||
void updateUnitAI(ecs_entity_t entity, UnitAI *unitAI, Game *game);
|
||||
void setUnitAI(ecs_entity_t entity, Game *game, const UnitAI *unitAI);
|
||||
|
||||
#endif //PIXELDEFENSE_UNIT_AI_H
|
||||
Reference in New Issue
Block a user