From 11832ec1cc6d34c87b18659f8b64c72f860ddbc4 Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Sun, 10 Dec 2023 11:08:40 +0100 Subject: [PATCH] Basic wood chopping --- game/components.c | 8 ++++ game/components.h | 22 +++++++++ game/game_state.h | 1 - game/main.c | 15 ++++-- game/map_init.c | 57 +++++++++++++++-------- game/map_init.h | 6 +++ game/pathfinding.c | 6 ++- game/systems.h | 16 ++++++- game/systems_entity.c | 104 +++++++++++++++++++++++++++++++++++++++++- game/systems_input.c | 34 ++++++++------ 10 files changed, 224 insertions(+), 45 deletions(-) diff --git a/game/components.c b/game/components.c index 1247216..3538180 100644 --- a/game/components.c +++ b/game/components.c @@ -34,6 +34,10 @@ ECS_TAG_DECLARE(Buildable); ECS_TAG_DECLARE(Workable); ECS_TAG_DECLARE(Attackable); +ECS_COMPONENT_DECLARE(Storage); + +ECS_COMPONENT_DECLARE(HarvestTask); + void initComponentIDs(ecs_world_t *ecs) { ECS_TAG_DEFINE(ecs, TextureTerrain); ECS_TAG_DEFINE(ecs, TextureBuildings); @@ -68,4 +72,8 @@ void initComponentIDs(ecs_world_t *ecs) { ECS_TAG_DEFINE(ecs, Buildable); ECS_TAG_DEFINE(ecs, Workable); ECS_TAG_DEFINE(ecs, Attackable); + + ECS_COMPONENT_DEFINE(ecs, Storage); + + ECS_COMPONENT_DEFINE(ecs, HarvestTask); } diff --git a/game/components.h b/game/components.h index 434e324..196d0e0 100644 --- a/game/components.h +++ b/game/components.h @@ -146,6 +146,28 @@ extern ECS_TAG_DECLARE(Buildable); extern ECS_TAG_DECLARE(Workable); extern ECS_TAG_DECLARE(Attackable); +typedef struct Storage { + int capacity[RES_COUNT]; + int amount[RES_COUNT]; + int reserved[RES_COUNT]; + int pending[RES_COUNT]; +} Storage; +extern ECS_COMPONENT_DECLARE(Storage); + +typedef struct HarvestTask { + ecs_entity_t entity; +} HarvestTask; +extern ECS_COMPONENT_DECLARE(HarvestTask); + +typedef struct WorkerTask { + enum { + HARVEST, + BUILD, + } type; + ecs_entity_t target; + //struct WorkerTask *next; +} WorkerTask; + void initComponentIDs(ecs_world_t *ecs); diff --git a/game/game_state.h b/game/game_state.h index 4f2234d..bf45211 100644 --- a/game/game_state.h +++ b/game/game_state.h @@ -12,7 +12,6 @@ typedef struct Game { BzTileMap map; BzSpatialGrid *entityGrid; f32 frameDuration; - ecs_entity_t entity; struct { i64 wood; i64 iron; diff --git a/game/main.c b/game/main.c index da57b0f..37ca2c0 100644 --- a/game/main.c +++ b/game/main.c @@ -131,7 +131,7 @@ bool init(void *userData) { .userDataSize=sizeof(ecs_entity_t) }); - ECS_OBSERVER(ECS, entitySpatialRemove, EcsOnRemove, Position, SpatialGridID); + ECS_OBSERVER(ECS, entitySpatialRemove, EcsOnRemove, SpatialGridID); ECS_OBSERVER(ECS, entityPathRemove, EcsOnRemove, Path); ECS_OBSERVER(ECS, entitySetAnimationState, EcsOnSet, Animation, AnimationType); @@ -150,6 +150,8 @@ 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, entityUpdateAnimationState, EcsOnUpdate, Velocity, AnimationType); ECS_SYSTEM(ECS, entityUpdateAnimation, EcsOnUpdate, Animation, TextureRegion); @@ -246,6 +248,10 @@ void imguiRender(float dt, void *userData) { igSetNextWindowSize((ImVec2){300, 400}, ImGuiCond_FirstUseEver); igBegin("Debug Menu", NULL, 0); + if (igSmallButton("Recruit worker [50 food]")) { + createWorker((Position) {1100, 400}, (Size) {10, 10}, game->entityGrid, + &game->map.tilesets[2], 1322); + } igText("Num paths from pool available: %llu", bzObjectPoolCalcNumFree(game->pools.pathData)); const char *inputState = "NONE"; switch (input->state) { @@ -287,9 +293,10 @@ void imguiRender(float dt, void *userData) { ecs_entity_t entity = it.entities[i]; if (ecs_has(ECS, entity, Harvestable) && ecs_has(ECS, entity, Resource)) { - Resource res = *ecs_get(ECS, entity, Resource); - const char *resName = getResourceTypePrettyName(res.type); - igText("\tEntity %llu: %d %s", entity, res.amount, resName); + Resource *res = ecs_get_mut(ECS, entity, Resource); + const char *resName = getResourceTypePrettyName(res->type); + igText("\tEntity %llu:", entity); + igSliderInt("\t\twood", &res->amount, 0, 20, NULL, 0); } } } diff --git a/game/map_init.c b/game/map_init.c index d553f10..47c3ac8 100644 --- a/game/map_init.c +++ b/game/map_init.c @@ -18,6 +18,8 @@ bool initGameObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { return true; } + + bool initEntityObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { Game *game = ecs_singleton_get_mut(ECS, Game); BzTileset *objectTileset = bzTileObjectGroupGetTileset(&game->map, objectGroup); @@ -26,19 +28,9 @@ bool initEntityObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { for (i32 i = 0; i < objectGroup->objectCount; i++) { if (i == 1) break; BzTileObject object = objectGroup->objects[i]; - ecs_entity_t e = ecs_new_id(ECS); - game->entity = e; - ecs_add(ECS, e, TextureEntities); - ecs_set(ECS, e, Position, {object.shape.x, object.shape.y}); - ecs_set(ECS, e, Size, {object.shape.sizeX, object.shape.sizeY}); - BzSpatialGridID spatialID = bzSpatialGridInsert(game->entityGrid, &e, - object.shape.x, object.shape.y, - object.shape.sizeX, object.shape.sizeY); - ecs_set(ECS, e, SpatialGridID, {spatialID}); - ecs_set(ECS, e, Rotation, {0.0f}); - ecs_set(ECS, e, Velocity, {}); - ecs_set(ECS, e, Steering, {}); - ecs_set(ECS, e, TextureRegion, {objectTileset->tiles, bzTilesetGetTileRegion(objectTileset, object.gid)}); + ecs_entity_t e = createWorker((Position) {object.shape.x, object.shape.y}, + (Size) {object.shape.sizeX, object.shape.sizeY}, + game->entityGrid, objectTileset, object.gid); ecs_set(ECS, e, Animation, { .entityType=ENTITY_WORKER, .tileset = objectTileset, @@ -46,10 +38,7 @@ bool initEntityObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup) { .frameDuration=0.6f + (i % 10) * 0.05f, .elapsed=i * 0.1f, }); - ecs_set(ECS, e, AnimationType, {ANIM_IDLE}); - ecs_add_id(ECS, e, Selectable); - ecs_add_id(ECS, e, Unit); - ecs_add_id(ECS, e, Worker); + bzLogInfo("%d %.2f %.2f", object.gid, object.shape.sizeX, object.shape.sizeY); //EntityArms arms = { // .left=ecs_new_id(ECS), // .right=ecs_new_id(ECS), @@ -81,13 +70,18 @@ bool initBuildingsLayer(BzTileMap *map, BzTileLayer *layer) { getBuildingSize(buildingTile, &size.sizeX, &size.sizeY); bzTileLayerSetTile(ownershipLayer, 0, x, y, size.sizeX, size.sizeY); + const i32 tileWidth = map->tileWidth; + const i32 tileHeight = map->tileHeight; ecs_entity_t e = ecs_new_id(ECS); - ecs_set(ECS, e, TilePosition, {.x=x, .y=y}); - ecs_set(ECS, e, TileSize, {.sizeX=size.sizeX, .sizeY=size.sizeY}); + ecs_set(ECS, e, Position, {.x = x * tileWidth, .y = y * tileHeight}); + ecs_set(ECS, e, TileSize, {.sizeX = size.sizeX * tileWidth, .sizeY = size.sizeY * tileHeight}); ownerTile = bzTilesetGetTile(buildingTileset, ownerTile); ownerTile = getTileBuilding(ownerTile); ecs_set(ECS, e, Owner, {.playerID=ownerTile}); ecs_add_id(ECS, e, Selectable); + if (buildingTile == BUILDINGS_WAREHOUSE) { + ecs_set(ECS, e, Storage, {}); + } //bzTileMapUpdateCollider(&GAME.map, x, y); } @@ -129,3 +123,28 @@ bool initTreesLayer(BzTileMap *map, BzTileLayer *layer) { } return true; } + +ecs_entity_t createWorker(Position position, Size size, BzSpatialGrid *grid, BzTileset *tileset, BzTile gid) { + ecs_entity_t e = ecs_new_id(ECS); + ecs_add(ECS, e, TextureEntities); + ecs_set_ptr(ECS, e, Position, &position); + ecs_set_ptr(ECS, e, Size, &size); + BzSpatialGridID spatialID = bzSpatialGridInsert(grid, &e, + position.x, position.y, + size.x, size.y); + ecs_set(ECS, e, SpatialGridID, {spatialID}); + ecs_set(ECS, e, Rotation, {0.0f}); + ecs_set(ECS, e, Velocity, {}); + ecs_set(ECS, e, Steering, {}); + ecs_set(ECS, e, TextureRegion, {tileset->tiles, bzTilesetGetTileRegion(tileset, gid)}); + ecs_set(ECS, e, Animation, { + .entityType=ENTITY_WORKER, + .tileset = tileset, + .curFrame=0, + }); + ecs_set(ECS, e, AnimationType, {ANIM_IDLE}); + ecs_add_id(ECS, e, Selectable); + ecs_add_id(ECS, e, Unit); + ecs_add_id(ECS, e, Worker); + return e; +} diff --git a/game/map_init.h b/game/map_init.h index 1451b0e..47772e8 100644 --- a/game/map_init.h +++ b/game/map_init.h @@ -2,6 +2,9 @@ #define PIXELDEFENSE_MAP_INITIALIZATION_H #include +#include + +#include "components.h" bool initGameObjectsLayer(BzTileMap *map, BzTileObjectGroup *objectGroup); @@ -11,4 +14,7 @@ bool initBuildingsLayer(BzTileMap *map, BzTileLayer *layer); bool initTreesLayer(BzTileMap *map, BzTileLayer *layer); +ecs_entity_t createWorker(Position position, Size size, BzSpatialGrid *grid, BzTileset *tileset, BzTile gid); + + #endif //PIXELDEFENSE_MAP_INITIALIZATION_H diff --git a/game/pathfinding.c b/game/pathfinding.c index 4dd7824..940ab27 100644 --- a/game/pathfinding.c +++ b/game/pathfinding.c @@ -139,7 +139,9 @@ bool pathfindAStar(const PathfindingDesc *desc) { pathData->waypoints[0] = desc->target; pathData->numWaypoints = 1; pathData->next = NULL; - *desc->outPath = (Path) {pathData, 0}; + + if (desc->outPath) + *desc->outPath = (Path) {pathData, 0}; return true; } @@ -272,7 +274,7 @@ bool pathfindAStar(const PathfindingDesc *desc) { bzStackAllocFree(desc->alloc, openSet.arr); bzStackAllocFree(desc->alloc, closedSet); - return foundPath ? pathLen : -1; + return foundPath; } static void heapSwap(Heap *heap, i32 left, i32 right) { diff --git a/game/systems.h b/game/systems.h index 7208686..dd88113 100644 --- a/game/systems.h +++ b/game/systems.h @@ -5,6 +5,11 @@ #include "components.h" +typedef struct Game Game; + +void entityClearTasks(const ecs_entity_t entity); +bool entitySetPath(const ecs_entity_t entity, const Vector2 target, Game *game); + /********************************** * Entity Systems @@ -12,8 +17,7 @@ /* Observer (for unregistering collision) * 0: Game (singleton) - * 1: Position - * 3: SpatialGridID + * 1: SpatialGridID */ void entitySpatialRemove(ecs_iter_t *it); @@ -63,6 +67,14 @@ void entityMoveToTarget(ecs_iter_t *it); */ void entityFollowPath(ecs_iter_t *it); +/* + * 0: Game (for pathfinding) + * 1: Position + * 2: Rotation + * 3: HarvestTask + */ +void entityHarvestTaskSystem(ecs_iter_t *it); + /* * 1: Velocity * 2: AnimationType diff --git a/game/systems_entity.c b/game/systems_entity.c index 69acafb..9050bde 100644 --- a/game/systems_entity.c +++ b/game/systems_entity.c @@ -3,18 +3,44 @@ #include "game_state.h" #include "input.h" +#include "pathfinding.h" #include #include +void entityClearTasks(const ecs_entity_t entity) { + ecs_remove(ECS, entity, HarvestTask); +} +bool entitySetPath(const ecs_entity_t entity, const Vector2 target, Game *game) { + const Vector2 *pPath = ecs_get(ECS, entity, Position); + BZ_ASSERT(pPath); + const Vector2 start = *pPath; + + Path path = {NULL, 0}; + const bool foundPath = pathfindAStar(&(PathfindingDesc) { + .start = start, + .target = target, + .map = &game->map, + .outPath = &path, + .pool = game->pools.pathData, + .alloc = &game->stackAlloc, + }); + if (foundPath) { + BZ_ASSERT(path.paths); + ecs_set_ptr(ECS, entity, Path, &path); + return true; + } + return false; +} + + static Position getBottomLeftPos(Position pos, Size size) { return (Position) {pos.x - size.x * 0.5f, pos.y - size.y * 0.5f}; } void entitySpatialRemove(ecs_iter_t *it) { Game *game = ecs_singleton_get_mut(ECS, Game); - Position *pos = ecs_field(it, Position, 1); - SpatialGridID *spatialID = ecs_field(it, SpatialGridID , 2); + SpatialGridID *spatialID = ecs_field(it, SpatialGridID, 1); for (i32 i = 0; i < it->count; i++) { bzSpatialGridRemove(game->entityGrid, spatialID[i]); @@ -169,6 +195,80 @@ void entityFollowPath(ecs_iter_t *it) { } } +static ecs_entity_t findNearestStorage(ResourceType type) { + ecs_filter_t *storageFilter = ecs_filter(ECS, { + .terms = {{ecs_id(Storage)} } + }); + ecs_iter_t it = ecs_filter_iter(ECS, storageFilter); + + ecs_entity_t closest = 0; + while (ecs_filter_next(&it)) { + Storage *storage = ecs_field(&it, Storage, 1); + + for (i32 i = 0; i < it.count; i++) { + if (true || storage[i].capacity[type]) { + closest = it.entities[i]; + } + } + } + ecs_filter_fini(storageFilter); + return closest; +} + +void entityHarvestTaskSystem(ecs_iter_t *it) { + Game *game = ecs_singleton_get_mut(ECS, Game); + + const Position *position = ecs_field(it, Position, 1); + const Rotation *rotation = ecs_field(it, Rotation, 2); + + const HarvestTask *harvestTask = ecs_field(it, HarvestTask, 3); + + for (i32 i = 0; i < it->count; i++) { + const ecs_entity_t entity = it->entities[i]; + const ecs_entity_t targetEntity = harvestTask[i].entity; + + const Position *pTarget = ecs_get(ECS, targetEntity, Position); + BZ_ASSERT(pTarget); + const Position target = *pTarget; + + const f32 DST_LIMIT = 10.0f; + + Resource *resource = ecs_get_mut(ECS, targetEntity, Resource); + + if (resource->amount <= 0) { + ecs_delete(ECS, targetEntity); + ecs_remove(ECS, entity, HarvestTask); + continue; + } + + const f32 dst = Vector2Distance(position[i], target); + if (!ecs_has(ECS, entity, Path) && dst > DST_LIMIT) { + bzLogInfo("%.2f", dst); + // Pathfind to target + entitySetPath(entity, target, game); + continue; + } else if (dst < DST_LIMIT && !ecs_has(ECS, entity, Path)) { + if (!ecs_has(ECS, entity, Path)) { + bzLogInfo("Mine"); + resource->amount -= 5; + } + ecs_remove(ECS, entity, Path); + // MINE + // find nearest warehouse for wood + ecs_entity_t storage = findNearestStorage(RES_WOOD); + if (storage) { + const Position *storagePos = ecs_get(ECS, storage, Position); + BZ_ASSERT(storagePos); + entitySetPath(entity, *storagePos, game); + } + } + + // Harvest + const i32 carryCapacity = 5; + + } +} + void entityUpdateAnimationState(ecs_iter_t *it) { Velocity *velocity = ecs_field(it, Velocity, 1); //AnimationType *animType = ecs_field(it, AnimationType , 2); diff --git a/game/systems_input.c b/game/systems_input.c index 077b25d..c1f8558 100644 --- a/game/systems_input.c +++ b/game/systems_input.c @@ -74,14 +74,27 @@ void inputUnitAction(Game *game, InputState *input) { const MouseButton actionBtn = input->mapping.secondaryBtn; input->cursor = CURSOR_NONE; - if (queryEntity(game->entityGrid, input->mouseWorld, Harvestable)) { + ecs_entity_t taskEntity; + if ((taskEntity = queryEntity(game->entityGrid, input->mouseWorld, Harvestable))) { input->cursor = CURSOR_COLLECT_WOOD; if (isInputBtnJustUp(input, actionBtn)) { - bzLogInfo("Chop wood!"); + ecs_defer_begin(ECS); + ecs_iter_t it = ecs_query_iter(ECS, query); + while (ecs_query_next(&it)) { + for (i32 i = 0; i < it.count; i++) { + const ecs_entity_t entity = it.entities[i]; + ecs_set(ECS, entity, HarvestTask, {taskEntity}); + goto while_break; + } + } + while_break: + ecs_iter_fini(&it); + ecs_defer_end(ECS); } + return; } - if (isInputBtnJustDown(input, actionBtn)) { + if (isInputBtnJustUp(input, actionBtn)) { // Note: We mustn't use ecs ecs_remove_all since this will also // remove ongoing paths that are not part of this query. iterateSelectedUnits(input->queries.selected, iterRemovePaths); @@ -91,21 +104,11 @@ void inputUnitAction(Game *game, InputState *input) { ecs_iter_t it = ecs_query_iter(ECS, query); ecs_defer_begin(ECS); while (ecs_iter_next(&it)) { - Position *pos = ecs_field(&it, Position, 1); + const Position *pos = ecs_field(&it, Position, 1); for (i32 i = 0; i < it.count; i++) { const ecs_entity_t entity = it.entities[i]; - Path path = {NULL, 0}; - pathfindAStar(&(PathfindingDesc) { - .start = pos[i], - .target = target, - .map = map, - .outPath = &path, - .pool = game->pools.pathData, - .alloc = &game->stackAlloc - }); - if (!path.paths) continue; - ecs_set_ptr(ECS, entity, Path, &path); + entitySetPath(entity, target, game); } } ecs_defer_end(ECS); @@ -380,6 +383,7 @@ ecs_entity_t queryEntity(BzSpatialGrid *entityGrid, Vector2 point, ecs_entity_t ecs_entity_t closest = 0; 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, Selectable)) continue; if (!ecs_has_id(ECS, entity, tag)) continue; Vector2 pos;