Move systems into subdirectory, add tree shake animation

This commit is contained in:
2023-12-29 14:46:11 +01:00
parent aa3bfbf823
commit 31a9289770
15 changed files with 352 additions and 105 deletions

View File

@@ -9,6 +9,14 @@ add_subdirectory(engine/)
add_executable(PixelDefense
game/systems/s_ai.c
game/systems/s_animation.c
game/systems/s_entity.c
game/systems/s_event.c
game/systems/s_input.c
game/systems/s_ui.c
game/systems/systems.h
game/buildings.c
game/buildings.h
game/components.c
@@ -23,11 +31,6 @@ add_executable(PixelDefense
game/map_layers.h
game/pathfinding.c
game/pathfinding.h
game/systems.h
game/systems_ai.c
game/systems_entity.c
game/systems_input.c
game/systems_ui.c
game/ui_widgets.c
game/ui_widgets.h
game/unit_actions.c

View File

@@ -21,6 +21,9 @@ ECS_COMPONENT_DECLARE(Path);
ECS_COMPONENT_DECLARE(TextureRegion);
ECS_COMPONENT_DECLARE(Animation);
ECS_COMPONENT_DECLARE(Easing);
ECS_COMPONENT_DECLARE(HarvestEvent);
ECS_COMPONENT_DECLARE(UnitAI);
ECS_COMPONENT_DECLARE(UnitAction);
@@ -29,7 +32,7 @@ ECS_TAG_DECLARE(Selectable);
ECS_TAG_DECLARE(Selected);
ECS_TAG_DECLARE(Unit);
ECS_TAG_DECLARE(Worker);
ECS_COMPONENT_DECLARE(Worker);
ECS_TAG_DECLARE(Harvestable);
ECS_TAG_DECLARE(Buildable);
ECS_TAG_DECLARE(Workable);
@@ -56,6 +59,9 @@ void initComponentIDs(ecs_world_t *ecs) {
ECS_COMPONENT_DEFINE(ecs, TextureRegion);
ECS_COMPONENT_DEFINE(ecs, Animation);
ECS_COMPONENT_DEFINE(ecs, Easing);
ECS_COMPONENT_DEFINE(ecs, HarvestEvent);
ECS_COMPONENT_DEFINE(ecs, UnitAI);
ECS_COMPONENT_DEFINE(ecs, UnitAction);
@@ -64,7 +70,7 @@ void initComponentIDs(ecs_world_t *ecs) {
ECS_TAG_DEFINE(ecs, Selected);
ECS_TAG_DEFINE(ecs, Unit);
ECS_TAG_DEFINE(ecs, Worker);
ECS_COMPONENT_DEFINE(ecs, Worker);
ECS_TAG_DEFINE(ecs, Harvestable);
ECS_TAG_DEFINE(ecs, Buildable);
ECS_TAG_DEFINE(ecs, Workable);

View File

@@ -86,7 +86,6 @@ extern ECS_COMPONENT_DECLARE(Path);
typedef struct TextureRegion {
Texture2D texture;
Rectangle rec;
f32 rotation;
bool flipX : 1;
bool flipY : 1;
} TextureRegion;
@@ -109,12 +108,49 @@ typedef struct Animation {
} Animation;
extern ECS_COMPONENT_DECLARE(Animation);
typedef enum EasingType {
EASE_ROTATION,
EASE_POS_X,
EASE_POS_Y,
EASE_SIZE_X,
EASE_SIZE_Y,
} EasingType;
typedef struct Easing {
EasingType type;
BzEaseType easingFunc;
// start + target * (easeStart + (easeTarget * x) + easeOffset) + offset
f32 target;
f32 start;
f32 offset;
f32 easeTarget;
f32 easeStart;
f32 easeOffset;
f32 x;
f32 elapsed;
f32 duration;
//struct Easing *next;
} Easing;
extern ECS_COMPONENT_DECLARE(Easing);
typedef struct EntityArms {
ecs_entity_t left;
ecs_entity_t right;
} EntityArms;
//extern ECS_COMPONENT_DECLARE(EntityArms);
/**********************************************************
* Event components
*********************************************************/
typedef struct HarvestEvent {
i32 amount;
} HarvestEvent;
extern ECS_COMPONENT_DECLARE(HarvestEvent);
/**********************************************************
* Gameplay components
*********************************************************/
@@ -134,7 +170,16 @@ extern ECS_TAG_DECLARE(Unit);
// - Build
// - Work
// - Attack (since it is also a unit)
extern ECS_TAG_DECLARE(Worker);
typedef struct Worker {
// stats
f32 collectSpeed;
f32 depositSpeed;
i32 carry;
i32 carryCapacity;
ResourceType carryRes;
} Worker;
extern ECS_COMPONENT_DECLARE(Worker);
extern ECS_TAG_DECLARE(Harvestable);
extern ECS_TAG_DECLARE(Buildable);

View File

@@ -4,7 +4,7 @@
#include <raymath.h>
#include <stdlib.h>
#include "systems.h"
#include "systems/systems.h"
#include "components.h"
#include "game_state.h"
#include "game_tileset.h"
@@ -228,9 +228,10 @@ bool init(void *userData) {
ecs_set_hooks(ECS, Path, {
.dtor = ecs_dtor(Path)
});
ECS_OBSERVER(ECS, entityPathRemove, EcsOnRemove, Path);
//ECS_OBSERVER(ECS, entitySetAnimationState, EcsOnSet, Animation, AnimationType);
//setupSystems(ECS)
ECS_OBSERVER(ECS, entityPathRemove, EcsOnRemove, Path);
ECS_SYSTEM(ECS, entityUpdateSpatialID, EcsOnUpdate, Position, Size, Velocity, SpatialGridID);
ECS_SYSTEM(ECS, entityUpdateKinematic, EcsOnUpdate, Position, Rotation, Velocity, Steering);
@@ -238,14 +239,14 @@ bool init(void *userData) {
ECS_SYSTEM(ECS, entityMoveToTarget, EcsOnUpdate, Position, Rotation, Velocity, TargetPosition, Steering);
ECS_SYSTEM(ECS, entityFollowPath, EcsOnUpdate, Path);
//ECS_SYSTEM(ECS, entityHarvestTaskSystem, EcsOnUpdate, Position, Rotation, HarvestTask);
ECS_SYSTEM(ECS, handleUnitActionsSystem, EcsOnUpdate, UnitAction);
ECS_SYSTEM(ECS, updateUnitAISystem, EcsOnUpdate, UnitAI, UnitAction);
// Needs to be called after AI update, since it removes finished actions
ECS_SYSTEM(ECS, updateUnitActionsSystem, EcsOnUpdate, UnitAction);
ECS_SYSTEM(ECS, entityUpdateAnimationState, EcsOnUpdate, Animation, TextureRegion);
ECS_SYSTEM(ECS, entityUpdateAnimation, EcsOnUpdate, Animation, TextureRegion);
ECS_SYSTEM(ECS, updateAnimationState, EcsOnUpdate, Animation, TextureRegion);
ECS_SYSTEM(ECS, updateAnimation, EcsOnUpdate, Animation, TextureRegion);
ECS_SYSTEM(ECS, updateEasingSystem, EcsOnUpdate, Easing, Position, Size, Rotation);
ECS_SYSTEM(ECS, renderDebugPath, EcsOnUpdate, Path);
@@ -348,7 +349,9 @@ static void renderGame(Game *game, float dt) {
TextureRegion *t = ecs_field(&it, TextureRegion, 4);
for (i32 i = 0; i < it.count; i++) {
Rectangle dst = {p[i].x, p[i].y, s[i].x, s[i].y};
Vector2 origin = {dst.width * 0.5f, dst.height * 0.5f};
Vector2 origin = {dst.width * 0.5f, dst.height};
dst.x += origin.x - dst.width * 0.5f;
dst.y += origin.y - dst.height * 0.5f;
Rectangle src = t[i].rec;
if (t[i].flipX) src.width *= -1.0f;
if (t[i].flipY) src.height *= -1.0f;
@@ -357,7 +360,7 @@ static void renderGame(Game *game, float dt) {
.src = src,
.dst = dst,
.origin = origin,
.rotation = t[i].rotation
.rotation = r[i]
});
}
}

View File

@@ -160,6 +160,10 @@ ecs_entity_t createWorker(Position position, Size size, BzSpatialGrid *grid, BzT
ecs_set(ECS, e, UnitAction, {NULL, NULL});
ecs_add_id(ECS, e, Selectable);
ecs_add_id(ECS, e, Unit);
ecs_add_id(ECS, e, Worker);
ecs_set(ECS, e, Worker, {
.collectSpeed = 0.8f,
.depositSpeed = 0.2f,
.carryCapacity = 5,
});
return e;
}

View File

@@ -1,9 +1,9 @@
#include "systems.h"
#include "game_state.h"
#include "../game_state.h"
#include "unit_ai.h"
#include "unit_actions.h"
#include "../unit_ai.h"
#include "../unit_actions.h"
void handleUnitActionsSystem(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);

View File

@@ -0,0 +1,97 @@
#include "systems.h"
#include "../game_state.h"
#include <raymath.h>
void updateAnimationState(ecs_iter_t *it) {
Animation *anim = ecs_field(it, Animation, 1);
TextureRegion *text = ecs_field(it, TextureRegion, 2);
for (i32 i = 0; i < it->count; i++) {
ecs_entity_t entity = it->entities[i];
AnimType type = ANIM_IDLE;
if (ecs_has(ECS, entity, Velocity)) {
Velocity vel = *ecs_get(ECS, entity, Velocity);
f32 len = Vector2Length(vel);
if (len > 1.0f) {
type = ANIM_WALK;
text[i].flipX = vel.x < 0;
}
}
if (type != anim[i].animType) {
anim[i].animType = type;
anim[i].sequence = entityGetAnimationSequence(anim[i].entityType, type);
anim[i].curFrame = 0;
anim[i].elapsed = 0;
}
}
}
void updateAnimation(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
Animation *anim = ecs_field(it, Animation, 1);
TextureRegion *texture = ecs_field(it, TextureRegion, 2);
float dt = GetFrameTime();
for (i32 i = 0; i < it->count; i++) {
AnimationFrame frame = anim[i].frame;
AnimationSequence seq = anim[i].sequence;
anim[i].elapsed += dt;
if (anim[i].elapsed < frame.duration) continue;
i32 nextFrame = (anim[i].curFrame + 1) % seq.frameCount;
anim[i].curFrame = nextFrame;
anim[i].frame = entityGetAnimationFrame(anim[i].entityType, anim[i].animType, nextFrame);
anim[i].elapsed = 0.0f;
texture[i].rec = bzTilesetGetTileRegion(anim[i].tileset, anim[i].frame.frame);
}
}
void updateEasingSystem(ecs_iter_t *it) {
Easing *easing = ecs_field(it, Easing, 1);
Position *position = ecs_field(it, Position, 2);
Size *size = ecs_field(it, Size, 3);
Rotation *rotation = ecs_field(it, Rotation, 4);
f32 dt = GetFrameTime();
for (i32 i = 0; i < it->count; i++) {
ecs_entity_t entity = it->entities[i];
if (easing[i].elapsed > easing[i].duration) {
ecs_remove(ECS, entity, Easing);
continue;
}
easing[i].elapsed += dt;
f32 alpha = easing[i].elapsed / easing[i].duration;
alpha = BZ_MIN(1.0f, alpha);
easing[i].x = bzEase(easing[i].easingFunc, alpha);
const Easing *e = &easing[i];
f32 x = e->x;
// Inner
x = e->easeStart + e->easeTarget * x + e->easeOffset;
// Outer
x = e->start + e->target * x + e->offset;
switch (easing->type) {
case EASE_ROTATION:
rotation[i] = x;
break;
case EASE_POS_X:
position[i].x = x;
break;
case EASE_POS_Y:
position[i].y = x;
break;
case EASE_SIZE_X:
size[i].x = x;
break;
case EASE_SIZE_Y:
size[i].y = x;
break;
}
}
}

View File

@@ -1,9 +1,9 @@
#include "systems.h"
#include "game_state.h"
#include "input.h"
#include "pathfinding.h"
#include "../game_state.h"
#include "../input.h"
#include "../pathfinding.h"
#include <math.h>
#include <raymath.h>
@@ -88,7 +88,7 @@ void entityUpdateKinematic(ecs_iter_t *it) {
Vector2 mouse = input->mouseDownWorld;
f32 rot = Vector2Angle(position[i], mouse) + 270 * DEG2RAD;
rotation[i] = rot;
//rotation[i] = rot;
}
@@ -126,7 +126,7 @@ void entityMoveToTarget(ecs_iter_t *it) {
if (Vector2Length(velocity[i]) > 10.0f) {
f32 rot = Vector2Angle(position[i], target);
rotation[i] = rot;
//rotation[i] = rot;
}
if (dst < 8.0f) {
@@ -162,52 +162,6 @@ void entityFollowPath(ecs_iter_t *it) {
}
}
void entityUpdateAnimationState(ecs_iter_t *it) {
Animation *anim = ecs_field(it, Animation, 1);
TextureRegion *text = ecs_field(it, TextureRegion, 2);
for (i32 i = 0; i < it->count; i++) {
ecs_entity_t entity = it->entities[i];
AnimType type = ANIM_IDLE;
if (ecs_has(ECS, entity, Velocity)) {
Velocity vel = *ecs_get(ECS, entity, Velocity);
f32 len = Vector2Length(vel);
if (len > 1.0f) {
type = ANIM_WALK;
text[i].flipX = vel.x < 0;
}
}
if (type != anim[i].animType) {
anim[i].animType = type;
anim[i].sequence = entityGetAnimationSequence(anim[i].entityType, type);
anim[i].curFrame = 0;
anim[i].elapsed = 0;
}
}
}
void entityUpdateAnimation(ecs_iter_t *it) {
Game *game = ecs_singleton_get_mut(ECS, Game);
Animation *anim = ecs_field(it, Animation, 1);
TextureRegion *texture = ecs_field(it, TextureRegion, 2);
float dt = GetFrameTime();
for (i32 i = 0; i < it->count; i++) {
AnimationFrame frame = anim[i].frame;
AnimationSequence seq = anim[i].sequence;
anim[i].elapsed += dt;
if (anim[i].elapsed < frame.duration) continue;
i32 nextFrame = (anim[i].curFrame + 1) % seq.frameCount;
anim[i].curFrame = nextFrame;
anim[i].frame = entityGetAnimationFrame(anim[i].entityType, anim[i].animType, nextFrame);
anim[i].elapsed = 0.0f;
texture[i].rec = bzTilesetGetTileRegion(anim[i].tileset, anim[i].frame.frame);
}
}
void renderColliders(ecs_iter_t *it) {
Position *pos = ecs_field(it, Position, 1);
Size *size = ecs_field(it, Size, 2);

28
game/systems/s_event.c Normal file
View File

@@ -0,0 +1,28 @@
#include "systems.h"
#include "../game_state.h"
i32 harvestEvent(ecs_entity_t entity, HarvestEvent event) {
BZ_ASSERT(ecs_has_id(ECS, entity, Harvestable));
BZ_ASSERT(ecs_has(ECS, entity, Resource));
ecs_set(ECS, entity, Easing, {
.type = EASE_ROTATION,
.easingFunc = BZ_EASE_OUT_ELASTIC,
.duration = 0.4f,
// 45 * (1.0f + (-1.0f) * x)
.target = 45,
.easeTarget = -1.0f,
.easeStart = 1.0f
});
Resource *res = ecs_get_mut(ECS, entity, Resource);
event.amount = BZ_MIN(event.amount, res->amount);
res->amount -= event.amount;
if (res->amount <= 0)
ecs_delete(ECS, entity);
return event.amount;
}

View File

@@ -1,10 +1,11 @@
#include "systems.h"
#include "game_state.h"
#include "input.h"
#include "buildings.h"
#include "pathfinding.h"
#include "unit_ai.h"
#include "unit_actions.h"
#include "../game_state.h"
#include "../input.h"
#include "../buildings.h"
#include "../pathfinding.h"
#include "../unit_ai.h"
#include "../unit_actions.h"
#include <rlImGui.h>
#include <raymath.h>

View File

@@ -1,6 +1,6 @@
#include "systems.h"
#include "game_state.h"
#include "../game_state.h"
void uiTask(ecs_iter_t *it) {
const Game *game = ecs_singleton_get(ECS, Game);

View File

@@ -3,7 +3,7 @@
#include <flecs.h>
#include "components.h"
#include "../components.h"
typedef struct Game Game;
@@ -36,6 +36,30 @@ void updateUnitAISystem(ecs_iter_t *it);
*/
void updateUnitActionsSystem(ecs_iter_t *it);
/*
* 1: Easing
* 2: Position
* 3: Size
* 4: Rotation
*/
void updateEasingSystem(ecs_iter_t *it);
/**********************************
* Animation Systems
**********************************/
/*
* 1: Animation
* 2: TextureRegion
*/
void updateAnimationState(ecs_iter_t *it);
/*
* 0:
* 1: Animation
* 2: TextureRegion
*/
void updateAnimation(ecs_iter_t *it);
/**********************************
* Entity Systems
@@ -81,19 +105,6 @@ void entityMoveToTarget(ecs_iter_t *it);
void entityFollowPath(ecs_iter_t *it);
/*
* 1: Animation
* 2: TextureRegion
*/
void entityUpdateAnimationState(ecs_iter_t *it);
/*
* 0:
* 1: Animation
* 2: TextureRegion
*/
void entityUpdateAnimation(ecs_iter_t *it);
/*
* 1: Position
* 2: Size
@@ -147,4 +158,33 @@ void drawPlayerInputUI();
* UI systems
**********************************/
/**********************************
* MISC
**********************************/
static void setupSystems(ecs_world_t *ecs) {
ECS_OBSERVER(ecs, entityPathRemove, EcsOnRemove, Path);
ECS_SYSTEM(ecs, entityUpdateSpatialID, EcsOnUpdate, Position, Size, Velocity, SpatialGridID);
ECS_SYSTEM(ecs, entityUpdateKinematic, EcsOnUpdate, Position, Rotation, Velocity, Steering);
ECS_SYSTEM(ecs, entityMoveToTarget, EcsOnUpdate, Position, Rotation, Velocity, TargetPosition, Steering);
ECS_SYSTEM(ecs, entityFollowPath, EcsOnUpdate, Path);
ECS_SYSTEM(ecs, handleUnitActionsSystem, EcsOnUpdate, UnitAction);
ECS_SYSTEM(ecs, updateUnitAISystem, EcsOnUpdate, UnitAI, UnitAction);
// Needs to be called after AI update, since it removes finished actions
ECS_SYSTEM(ecs, updateUnitActionsSystem, EcsOnUpdate, UnitAction);
ECS_SYSTEM(ecs, updateAnimationState, EcsOnUpdate, Animation, TextureRegion);
ECS_SYSTEM(ecs, updateAnimation, EcsOnUpdate, Animation, TextureRegion);
ECS_SYSTEM(ecs, updateEasingSystem, EcsOnUpdate, Easing, Position, Size, Rotation);
ECS_SYSTEM(ecs, renderDebugPath, EcsOnUpdate, Path);
ECS_SYSTEM(ecs, renderColliders, EcsOnUpdate, Position, Size);
ECS_SYSTEM(ecs, renderRotationDirection, EcsOnUpdate, Position, Rotation);
}
#endif //PIXELDEFENSE_SYSTEMS_H

View File

@@ -1,7 +1,7 @@
#include "unit_actions.h"
#include "game_state.h"
#include "components.h"
#include "systems.h"
#include "systems/systems.h"
#include <raymath.h>
@@ -22,10 +22,24 @@ void actionMoveTo(ecs_entity_t entity, Action *action, Game *game) {
}
void actionCollectResource(ecs_entity_t entity, Action *action, Game *game) {
if (action->finished) return;
if (action->elapsed > 2.0f) {
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;
ecs_delete(ECS, target);
}
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;
}
}
@@ -107,6 +121,20 @@ void addAction(ecs_entity_t entity, Game *game, const Action *action) {
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) {

View File

@@ -25,6 +25,9 @@ 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 {
@@ -37,6 +40,9 @@ typedef struct Action {
bool finished;
bool failed;
ActionCb onBegin;
ActionCb onFinish;
struct Action *next;
} Action;
@@ -49,6 +55,7 @@ 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);

View File

@@ -62,28 +62,59 @@ static ecs_entity_t findNearestResource(Game *game, Position pos, ResourceType t
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 worker = unitAI->as.workerHarvest;
AIWorkerHarvest workerAI = unitAI->as.workerHarvest;
Position workerPos = *ecs_get(ECS, entity, Position);
// Finished all tasks, repeat
ecs_entity_t target = worker.target;
Position targetPos = worker.targetPosition;
ecs_entity_t target = workerAI.target;
Position targetPos = workerAI.targetPosition;
if (!ecs_is_alive(ECS, target)) {
// Find new resource
target = findNearestResource(game, targetPos, worker.resource, 20.0f);
target = findNearestResource(game, targetPos, workerAI.resource, 20.0f);
if (!target) return;
worker.target = target;
worker.targetPosition = *ecs_get(ECS, target, Position);
workerAI.target = target;
workerAI.targetPosition = *ecs_get(ECS, target, Position);
}
// Find the closest warehouse
Position warehousePos = Vector2Zero();
ecs_entity_t warehouse = findNearestStorage(workerPos, worker.resource,
ecs_entity_t warehouse = findNearestStorage(workerPos, workerAI.resource,
&warehousePos);
if (!warehouse) {
return;
@@ -99,7 +130,7 @@ void aiWorkerHarvestUpdate(ecs_entity_t entity, UnitAI *unitAI, Game *game) {
});
addAction(entity, game, &(const Action) {
.type = ACTION_COLLECT_RESOURCE,
.as.collectResource.entity = target
.as.collectResource.entity = target,
});
addAction(entity, game, &(const Action) {
.type = ACTION_MOVE_TO,