From d55ed29f97bd9b111b509a10471a215080e062ff Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Mon, 5 Feb 2024 09:19:59 +0100 Subject: [PATCH] Limit how many workers can harvest the same resource --- game/ai_actions.c | 23 +++++++++++++++++------ game/buildings.c | 15 +++++++++++++-- game/components.c | 4 ++-- game/components.h | 6 +++++- game/main.c | 2 -- game/map_init.c | 8 ++++++-- game/systems/s_ai.c | 7 +++++++ game/systems/s_event.c | 2 +- game/systems/s_input.c | 33 ++++++++++++++++++++++++++++++--- game/systems/systems.c | 1 + game/systems/systems.h | 5 +++++ 11 files changed, 87 insertions(+), 19 deletions(-) diff --git a/game/ai_actions.c b/game/ai_actions.c index 713b5c6..9baae27 100644 --- a/game/ai_actions.c +++ b/game/ai_actions.c @@ -53,10 +53,14 @@ BzBTStatus aiEvadeTarget(AIBlackboard *data, f32 dt) { 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, Harvestable)); - // Target still alive, no need to find next harvestable - data->moveToPos = data->as.worker.harvestPos; - return BZ_BT_SUCCESS; + 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; + return BZ_BT_SUCCESS; + } } BZ_ASSERT(ecs_has(ECS, data->entity, Worker)); @@ -82,10 +86,13 @@ BzBTStatus aiFindNextHarvestable(AIBlackboard *data, f32 dt) { 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, Harvestable) || + 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; @@ -151,7 +158,11 @@ BzBTStatus aiHarvestRes(AIBlackboard *data, f32 dt) { if (!ecs_is_alive(ECS, harvestTarget)) return BZ_BT_FAIL; - BZ_ASSERT(ecs_has_id(ECS, harvestTarget, Harvestable)); + 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; diff --git a/game/buildings.c b/game/buildings.c index 6121a2c..5f33ded 100644 --- a/game/buildings.c +++ b/game/buildings.c @@ -95,10 +95,21 @@ ecs_entity_t placeBuilding(Game *game, BuildingType type, ecs_set(ECS, building, AddPopCapacity, {10}); ecs_add_id(ECS, building, Storage); break; + case BUILDING_HOUSE_01: + case BUILDING_HOUSE_02: + case BUILDING_HOUSE_03: + case BUILDING_HOUSE_04: + case BUILDING_HOUSE_05: + case BUILDING_HOUSE_06: + ecs_set(ECS, building, AddPopCapacity, {5}); + ecs_add_id(ECS, building, Storage); + break; case BUILDING_WHEAT_0: case BUILDING_WHEAT_1: hasCollision = false; - ecs_add_id(ECS, building, Harvestable); + ecs_set(ECS, building, Harvestable, { + .harvestLimit = 1 + }); ecs_set(ECS, building, Resource, {RES_FOOD, INT32_MAX}); break; default: @@ -178,7 +189,7 @@ Vector2 getPositionNearBuilding(ecs_entity_t building, Vector2 fromPos) { Vector2 center = entityGetCenter(pos, hitbox); Vector2 size = {hitbox.width, hitbox.height}; - size = Vector2SubtractValue(size, 10.0f); + size = Vector2SubtractValue(size, 5.0f); Vector2 dir = Vector2Normalize(Vector2Subtract(fromPos, center)); dir = Vector2Multiply(dir, size); diff --git a/game/components.c b/game/components.c index 0285f71..2b8dfa5 100644 --- a/game/components.c +++ b/game/components.c @@ -43,7 +43,7 @@ ECS_COMPONENT_DECLARE(Worker); ECS_COMPONENT_DECLARE(Building); ECS_COMPONENT_DECLARE(Unit); ECS_TAG_DECLARE(Storage); -ECS_TAG_DECLARE(Harvestable); +ECS_COMPONENT_DECLARE(Harvestable); ECS_TAG_DECLARE(Buildable); ECS_TAG_DECLARE(Workable); ECS_TAG_DECLARE(Attackable); @@ -90,7 +90,7 @@ void initComponentIDs(ecs_world_t *ecs) { ECS_COMPONENT_DEFINE(ecs, Building); ECS_COMPONENT_DEFINE(ecs, Unit); ECS_TAG_DEFINE(ecs, Storage); - ECS_TAG_DEFINE(ecs, Harvestable); + ECS_COMPONENT_DEFINE(ecs, Harvestable); ECS_TAG_DEFINE(ecs, Buildable); ECS_TAG_DEFINE(ecs, Workable); ECS_TAG_DEFINE(ecs, Attackable); diff --git a/game/components.h b/game/components.h index dfe86d3..57ebcee 100644 --- a/game/components.h +++ b/game/components.h @@ -270,7 +270,11 @@ typedef struct Building { } Building; extern ECS_TAG_DECLARE(Storage); extern ECS_COMPONENT_DECLARE(Building); -extern ECS_TAG_DECLARE(Harvestable); +typedef struct Harvestable { + i32 harvestLimit; + i32 harvestCount; +} Harvestable; +extern ECS_COMPONENT_DECLARE(Harvestable); extern ECS_TAG_DECLARE(Buildable); extern ECS_TAG_DECLARE(Attackable); diff --git a/game/main.c b/game/main.c index 1a947f1..801d20e 100644 --- a/game/main.c +++ b/game/main.c @@ -487,7 +487,6 @@ static void renderGame(Game *game, float dt) { Rectangle dst = {p[i].x, p[i].y - s[i].y, s[i].x, s[i].y}; - DrawCircleV(p[i], 1.0f, BLUE); Vector2 origin = {dst.width * 0.5f, dst.height}; dst.x += origin.x; dst.y += origin.y; @@ -666,7 +665,6 @@ void igInspectWindow(ecs_entity_t entity, bool *open) { igTagCheckbox("Selectable", ECS, entity, Selectable); igTagCheckbox("Selected", ECS, entity, Selected); igTagCheckbox("Storage", ECS, entity, Storage); - igTagCheckbox("Harvestable", ECS, entity, Harvestable); igTagCheckbox("Attackable", ECS, entity, Attackable); } if (ecs_has(ECS, entity, BzBTState) && diff --git a/game/map_init.c b/game/map_init.c index c84f616..2153509 100644 --- a/game/map_init.c +++ b/game/map_init.c @@ -144,7 +144,9 @@ bool initRocksLayer(BzTileMap *map, BzTileLayer *layer) { ecs_set(ECS, e, TextureRegion, {tileset->tiles, getTextureRect(tileID)}); ecs_set(ECS, e, Resource, {RES_GOLD, 80}); ecs_add_id(ECS, e, Selectable); - ecs_add_id(ECS, e, Harvestable); + ecs_set(ECS, e, Harvestable, { + .harvestLimit = 4, + }); } } return true; @@ -185,7 +187,9 @@ bool initTreesLayer(BzTileMap *map, BzTileLayer *layer) { ecs_set(ECS, e, TextureRegion, {tileset->tiles, getTextureRect(tileID)}); ecs_set(ECS, e, Resource, {RES_WOOD, 20}); ecs_add_id(ECS, e, Selectable); - ecs_add_id(ECS, e, Harvestable); + ecs_set(ECS, e, Harvestable, { + .harvestLimit = 4 + }); } } diff --git a/game/systems/s_ai.c b/game/systems/s_ai.c index a65beaa..41f2898 100644 --- a/game/systems/s_ai.c +++ b/game/systems/s_ai.c @@ -3,6 +3,13 @@ #include "../game_state.h" #include "../ai_actions.h" +void resetHarvestCount(ecs_iter_t *it) { + Harvestable *harvestable = ecs_field(it, Harvestable, 1); + + for (i32 i = 0; i < it->count; i++) { + harvestable[i].harvestCount = 0; + } +} void updateAISystem(ecs_iter_t *it) { Game *game = ecs_singleton_get_mut(ECS, Game); diff --git a/game/systems/s_event.c b/game/systems/s_event.c index 9fa3aa9..eb241a6 100644 --- a/game/systems/s_event.c +++ b/game/systems/s_event.c @@ -4,7 +4,7 @@ #include "../sounds.h" i32 harvestEvent(ecs_entity_t entity, HarvestEvent event) { - BZ_ASSERT(ecs_has_id(ECS, entity, Harvestable)); + BZ_ASSERT(ecs_has_id(ECS, entity, ecs_id(Harvestable))); BZ_ASSERT(ecs_has(ECS, entity, Resource)); ecs_set(ECS, entity, Easing, { diff --git a/game/systems/s_input.c b/game/systems/s_input.c index 50400c0..a249897 100644 --- a/game/systems/s_input.c +++ b/game/systems/s_input.c @@ -84,7 +84,7 @@ void inputUnitAction(Game *game, InputState *input) { input->cursor = CURSOR_NONE; ecs_entity_t taskEntity; bool isWorker = selectedAnyHasID(query, ecs_id(Worker)); - if (isWorker && (taskEntity = queryEntity(game->entityGrid, input->mouseWorld, Harvestable))) { + if (isWorker && (taskEntity = queryEntity(game->entityGrid, input->mouseWorld, ecs_id(Harvestable)))) { Resource resource = *ecs_get(ECS, taskEntity, Resource); switch (resource.type) { case RES_WOOD: @@ -99,12 +99,39 @@ void inputUnitAction(Game *game, InputState *input) { default:; } if (isInputBtnJustUp(input, actionBtn)) { + const f32 hRadius = 10.0f; + const Vector2 mPos = input->mouseWorld; + BzSpatialGridIter gridIt = bzSpatialGridIter(game->entityGrid, + mPos.x - hRadius, mPos.y - hRadius, + mPos.x + hRadius, mPos.y + hRadius); 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++) { ecs_entity_t entity = it.entities[i]; - Position target = *ecs_get(ECS, taskEntity, Position); + + bool hasNext = false; + ecs_entity_t harvestEntity = 0; + do { + hasNext = bzSpatialGridQueryNext(&gridIt); + if (!hasNext) break; + harvestEntity = *(ecs_entity_t *) gridIt.data; + if (!ecs_has(ECS, harvestEntity, Resource)) + continue; + const Resource *res = ecs_get(ECS, harvestEntity, Resource); + if (res->type != resource.type) + continue; + if (!ecs_has(ECS, harvestEntity, Harvestable)) + continue; + Harvestable *harvestable = ecs_get_mut(ECS, harvestEntity, Harvestable); + if (harvestable->harvestCount >= harvestable->harvestLimit) + continue; + harvestable->harvestCount++; + break; + } while (hasNext); + + if (!hasNext) break; + Position target = *ecs_get(ECS, harvestEntity, Position); f32 proximity = 6.0f; if (resource.type == RES_FOOD) @@ -116,7 +143,7 @@ void inputUnitAction(Game *game, InputState *input) { setAIBehaviour(entity, game->BTs.workerHarvest, &(AIBlackboard) { .as.worker = { .harvestType = resource.type, - .harvestTarget = taskEntity, + .harvestTarget = harvestEntity, .harvestPos = target, }, .proximity = proximity, diff --git a/game/systems/systems.c b/game/systems/systems.c index ec65c0e..6cf2a39 100644 --- a/game/systems/systems.c +++ b/game/systems/systems.c @@ -125,6 +125,7 @@ void setupSystems() { ECS_SYSTEM(ECS, entityFollowPath, EcsOnUpdate, Path); ECS_SYSTEM(ECS, entityUpdateArms, EcsOnUpdate, Position, Velocity, Rotation, Orientation, Arms); + ECS_SYSTEM(ECS, resetHarvestCount, EcsOnUpdate, Harvestable); ECS_SYSTEM(ECS, updateAISystem, EcsOnUpdate, BzBTState); ECS_SYSTEM(ECS, updateAnimationState, EcsOnUpdate, Animation, TextureRegion); diff --git a/game/systems/systems.h b/game/systems/systems.h index fcd6de6..c3ff3b6 100644 --- a/game/systems/systems.h +++ b/game/systems/systems.h @@ -18,6 +18,11 @@ bool entitySetPath(const ecs_entity_t entity, const Vector2 target, Game *game); * AI systems **********************************/ +/* + * 1. Harvestable + */ +void resetHarvestCount(ecs_iter_t *it); + /* * 0: Game (singleton) * 1: BzBTState