From 4cb85f069f69df3908992910d33278b21f543d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Mon, 17 Oct 2022 19:44:28 +0200 Subject: [PATCH] ItemRouter system --- code/foundation/src/models/components.c | 4 +- code/foundation/src/models/components.h | 5 + code/foundation/src/models/prefabs/furnace.c | 6 +- .../src/systems/modules/system_logistics.c | 110 ++++++++++++++++++ .../src/systems/modules/system_producer.c | 68 +---------- code/foundation/src/systems/systems.c | 83 +++++++------ 6 files changed, 168 insertions(+), 108 deletions(-) create mode 100644 code/foundation/src/systems/modules/system_logistics.c diff --git a/code/foundation/src/models/components.c b/code/foundation/src/models/components.c index fbb158b..32b0868 100644 --- a/code/foundation/src/models/components.c +++ b/code/foundation/src/models/components.c @@ -18,6 +18,7 @@ ECS_COMPONENT_DECLARE(ItemContainer); ECS_COMPONENT_DECLARE(Producer); ECS_COMPONENT_DECLARE(EnergySource); ECS_COMPONENT_DECLARE(Ingredient); +ECS_COMPONENT_DECLARE(ItemRouter); ECS_COMPONENT_DECLARE(Device); ECS_COMPONENT_DECLARE(Blueprint); ECS_COMPONENT_DECLARE(DemoNPC); @@ -25,7 +26,7 @@ ECS_COMPONENT_DECLARE(StreamInfo); void ComponentsImport(ecs_world_t *ecs) { ECS_MODULE(ecs, Components); - + ECS_COMPONENT_DEFINE(ecs, Vector2D); ECS_COMPONENT_DEFINE(ecs, Position); ECS_COMPONENT_DEFINE(ecs, Velocity); @@ -44,6 +45,7 @@ void ComponentsImport(ecs_world_t *ecs) { ECS_COMPONENT_DEFINE(ecs, Producer); ECS_COMPONENT_DEFINE(ecs, EnergySource); ECS_COMPONENT_DEFINE(ecs, Ingredient); + ECS_COMPONENT_DEFINE(ecs, ItemRouter); ECS_COMPONENT_DEFINE(ecs, Device); ECS_COMPONENT_DEFINE(ecs, Blueprint); ECS_COMPONENT_DEFINE(ecs, DemoNPC); diff --git a/code/foundation/src/models/components.h b/code/foundation/src/models/components.h index 1e7726a..20ec98b 100644 --- a/code/foundation/src/models/components.h +++ b/code/foundation/src/models/components.h @@ -135,6 +135,10 @@ typedef struct { float energy_level; } Producer; +typedef struct { + char _unused; +} ItemRouter; + typedef struct { asset_id kind; float energy_level; @@ -185,6 +189,7 @@ extern ECS_COMPONENT_DECLARE(ItemContainer); extern ECS_COMPONENT_DECLARE(Producer); extern ECS_COMPONENT_DECLARE(EnergySource); extern ECS_COMPONENT_DECLARE(Ingredient); +extern ECS_COMPONENT_DECLARE(ItemRouter); extern ECS_COMPONENT_DECLARE(Device); extern ECS_COMPONENT_DECLARE(Blueprint); extern ECS_COMPONENT_DECLARE(DemoNPC); diff --git a/code/foundation/src/models/prefabs/furnace.c b/code/foundation/src/models/prefabs/furnace.c index 437a95e..a8bce8b 100644 --- a/code/foundation/src/models/prefabs/furnace.c +++ b/code/foundation/src/models/prefabs/furnace.c @@ -7,13 +7,15 @@ uint64_t furnace_spawn(void) { ecs_entity_t e = device_spawn(ASSET_FURNACE); - + ItemContainer *storage = ecs_get_mut(world_ecs(), e, ItemContainer); *storage = (ItemContainer){0}; - + Producer *producer = ecs_get_mut(world_ecs(), e, Producer); *producer = (Producer){0}; producer->energy_level = 69.0f; + + ecs_add(world_ecs(), e, ItemRouter); return (uint64_t)e; } diff --git a/code/foundation/src/systems/modules/system_logistics.c b/code/foundation/src/systems/modules/system_logistics.c new file mode 100644 index 0000000..336316a --- /dev/null +++ b/code/foundation/src/systems/modules/system_logistics.c @@ -0,0 +1,110 @@ +static inline +asset_id FetchAssetAtPos(float x, float y) { + world_block_lookup lookup = world_block_from_realpos(x, y); + if (lookup.is_outer) { + return blocks_get_asset(lookup.bid); + } + + return ASSET_INVALID; +} + + +// NOTE(zaklaus): Expects float[4] dx, dy +static inline +uint8_t CheckForNearbyBelts(Position *p, float *dx, float *dy) { + asset_id bid = ASSET_INVALID; + float o = WORLD_BLOCK_SIZE; + uint8_t nodes = 0x0; + + // up + bid = FetchAssetAtPos(p->x + 0, p->y - o); + if (bid == ASSET_BELT_UP) { + dx[0] = 0; + dy[0] = -o; + nodes |= ZPL_BIT(0); + } + + // down + bid = FetchAssetAtPos(p->x + 0, p->y + o); + if (bid == ASSET_BELT_DOWN) { + dx[1] = 0; + dy[1] = o; + nodes |= ZPL_BIT(1); + } + + // left + bid = FetchAssetAtPos(p->x - o, p->y + 0); + if (bid == ASSET_BELT_LEFT) { + dx[2] = -o; + dy[2] = 0; + nodes |= ZPL_BIT(2); + } + + // right + bid = FetchAssetAtPos(p->x + o, p->y + 0); + if (bid == ASSET_BELT_RIGHT) { + dx[3] = o; + dy[3] = 0; + nodes |= ZPL_BIT(3); + } + + return nodes; +} + +void PushItemsOnNodes(ecs_iter_t *it) { + ItemContainer *storage = ecs_field(it, ItemContainer, 1); + Position *p = ecs_field(it, Position, 2); + Device *d = ecs_field(it, Device, 3); + ItemRouter *r = ecs_field(it, ItemRouter, 4); + + for (int i = 0; i < it->count; i++) { + // TODO(zaklaus): Cache output nodes so we avoid + // calling world_block_from_realpos each time. + // We need a way to refer to specific blocks in the world so we can do easy block ID checks + // and re-build the cache when a change is detected. + + float push_dx[4], push_dy[4]; + uint8_t nodes = CheckForNearbyBelts(&p[i], push_dx, push_dy); + uint8_t num_nodes = (uint8_t)zpl_count_set_bits(nodes); + uint8_t counter = 0; + + if (num_nodes == 0) { + // NOTE(zaklaus): We don't have any output nodes yet. + continue; + } + + for (int j = 0; j < ITEMS_CONTAINER_SIZE; j++) { + ecs_entity_t item_slot_ent = storage[i].items[j]; + if (item_slot_ent == 0) continue; + Item *item = item_get_data(item_slot_ent); + + const Ingredient *ing = 0; + // NOTE(zaklaus): Make sure we don't push out items from input node + if ((ing = ecs_get_if(it->world, item_slot_ent, Ingredient))) { + if (ing->producer == d->asset) { + continue; + } + } + + while (item->quantity > 0 && num_nodes > 0) { + // NOTE(zaklaus): Use a rolling counter to select an output node. + while (!(nodes & (1 << counter)) && counter < 4) ++counter; + if (counter > 3) { + counter = 0; + continue; + } + + uint64_t e = item_spawn(item->kind, 1); + entity_set_position(e, p[i].x + push_dx[counter], p[i].y + push_dy[counter]); + + Velocity *e_vel = ecs_get_mut_ex(it->world, e, Velocity); + e_vel->x = push_dx[counter]; + e_vel->y = push_dy[counter]; + + --item->quantity; + --num_nodes; + ++counter; + } + } + } +} \ No newline at end of file diff --git a/code/foundation/src/systems/modules/system_producer.c b/code/foundation/src/systems/modules/system_producer.c index 184ef12..a8a4ba4 100644 --- a/code/foundation/src/systems/modules/system_producer.c +++ b/code/foundation/src/systems/modules/system_producer.c @@ -1,58 +1,3 @@ -static inline -asset_id FetchAssetAtPos(float x, float y) { - world_block_lookup lookup = world_block_from_realpos(x, y); - if (lookup.is_outer) { - return blocks_get_asset(lookup.bid); - } - - return ASSET_INVALID; -} - -static inline -bool CheckForNearbyBelt(Position *p, float *dx, float *dy) { - asset_id bid = ASSET_INVALID; - float o = WORLD_BLOCK_SIZE; - - // up - bid = FetchAssetAtPos(p->x + 0, p->y - o); - { - debug_v2 a = {p->x, p->y}; - debug_v2 b = {p->x, p->y-WORLD_BLOCK_SIZE}; - debug_push_line(a, b, 0xFFFFFFFF); - } - if (bid == ASSET_BELT_UP) { - *dx = 0; - *dy = -o; - return true; - } - - // down - bid = FetchAssetAtPos(p->x + 0, p->y + o); - if (bid == ASSET_BELT_DOWN) { - *dx = 0; - *dy = o; - return true; - } - - // left - bid = FetchAssetAtPos(p->x - o, p->y + 0); - if (bid == ASSET_BELT_LEFT) { - *dx = -o; - *dy = 0; - return true; - } - - // right - bid = FetchAssetAtPos(p->x + o, p->y + 0); - if (bid == ASSET_BELT_RIGHT) { - *dx = o; - *dy = 0; - return true; - } - - return false; -} - void ProduceItems(ecs_iter_t *it) { ItemContainer *storage = ecs_field(it, ItemContainer, 1); Producer *producer = ecs_field(it, Producer, 2); @@ -60,9 +5,6 @@ void ProduceItems(ecs_iter_t *it) { Device *d = ecs_field(it, Device, 4); for (int i = 0; i < it->count; i++) { - float push_dx=0.0f, push_dy=0.0f; - bool has_output_node = CheckForNearbyBelt(&p[i], &push_dx, &push_dy); - for (int j = 0; j < ITEMS_CONTAINER_SIZE; j++) { ecs_entity_t item_slot_ent = storage[i].items[j]; Item *item = item_get_data(item_slot_ent); @@ -81,16 +23,8 @@ void ProduceItems(ecs_iter_t *it) { if (producer[i].process_time < game_time()) { if (producer[i].processed_item > 0) { uint64_t e = item_spawn(producer[i].processed_item, 1); + entity_set_position(e, p[i].x, p[i].y); producer[i].processed_item = 0; - - if (has_output_node) { - entity_set_position(e, p[i].x+push_dx, p[i].y+push_dy); - Velocity *e_vel = ecs_get_mut_ex(it->world, e, Velocity); - e_vel->x = push_dx; - e_vel->y = push_dy; - } else { - entity_set_position(e, p[i].x, p[i].y); - } } else { const Ingredient *ing = 0; if ((ing = ecs_get_if(it->world, item_slot_ent, Ingredient))) { diff --git a/code/foundation/src/systems/systems.c b/code/foundation/src/systems/systems.c index 69e15ea..d16d034 100644 --- a/code/foundation/src/systems/systems.c +++ b/code/foundation/src/systems/systems.c @@ -16,6 +16,7 @@ #include "modules/system_demo.c" #include "modules/system_vehicle.c" #include "modules/system_items.c" +#include "modules/system_logistics.c" #include "modules/system_producer.c" #include "modules/system_blueprint.c" @@ -32,7 +33,7 @@ void IntegratePositions(ecs_iter_t *it) { profile(PROF_INTEGRATE_POS) { Position *p = ecs_field(it, Position, 1); Velocity *v = ecs_field(it, Velocity, 2); - + for (int i = 0; i < it->count; i++) { if (ecs_get(it->world, it->entities[i], IsInVehicle)) { continue; @@ -45,59 +46,59 @@ void IntegratePositions(ecs_iter_t *it) { p[i].x = zpl_clamp(p[i].x, 0, w-1); p[i].y = zpl_clamp(p[i].y, 0, w-1); } - - #if PHY_BLOCK_COLLISION==1 + +#if PHY_BLOCK_COLLISION==1 // NOTE(zaklaus): X axis { world_block_lookup lookup = world_block_from_realpos(p[i].x+PHY_LOOKAHEAD(v[i].x), p[i].y); uint32_t flags = blocks_get_flags(lookup.bid); float bounce = blocks_get_bounce(lookup.bid); if (flags & BLOCK_FLAG_COLLISION && physics_check_aabb(p[i].x-WORLD_BLOCK_SIZE/4, p[i].x+WORLD_BLOCK_SIZE/4, p[i].y-0.5f, p[i].y+0.5f, lookup.aox-WORLD_BLOCK_SIZE/2, lookup.aox+WORLD_BLOCK_SIZE/2, lookup.aoy-WORLD_BLOCK_SIZE/2, lookup.aoy+WORLD_BLOCK_SIZE/2)) { - #if 1 +#if 1 { debug_v2 a = {p[i].x-WORLD_BLOCK_SIZE/4 + PHY_LOOKAHEAD(v[i].x), p[i].y-0.5f}; debug_v2 b = {p[i].x+WORLD_BLOCK_SIZE/4 + PHY_LOOKAHEAD(v[i].x), p[i].y+0.5f}; debug_push_rect(a, b, 0xFF0000FF); } - #endif +#endif v[i].x = physics_correction(lookup.ox, v[i].x, bounce, WORLD_BLOCK_SIZE/2); } } - + // NOTE(zaklaus): Y axis { world_block_lookup lookup = world_block_from_realpos(p[i].x, p[i].y+PHY_LOOKAHEAD(v[i].y)); uint32_t flags = blocks_get_flags(lookup.bid); float bounce = blocks_get_bounce(lookup.bid); - #if 0 +#if 0 { debug_v2 a = {lookup.aox-WORLD_BLOCK_SIZE/2, lookup.aoy-WORLD_BLOCK_SIZE/2}; debug_v2 b = {lookup.aox+WORLD_BLOCK_SIZE/2, lookup.aoy+WORLD_BLOCK_SIZE/2}; debug_push_rect(a, b, 0xFFFFFFFF); } - #endif +#endif if (flags & BLOCK_FLAG_COLLISION && physics_check_aabb(p[i].x-WORLD_BLOCK_SIZE/4, p[i].x+WORLD_BLOCK_SIZE/4, p[i].y-0.5f, p[i].y+0.5f, lookup.aox-WORLD_BLOCK_SIZE/2, lookup.aox+WORLD_BLOCK_SIZE/2, lookup.aoy-WORLD_BLOCK_SIZE/2, lookup.aoy+WORLD_BLOCK_SIZE/2)) { - #if 1 +#if 1 { debug_v2 a = {p[i].x-WORLD_BLOCK_SIZE/4, p[i].y-0.5f + PHY_LOOKAHEAD(v[i].y)}; debug_v2 b = {p[i].x+WORLD_BLOCK_SIZE/4, p[i].y+0.5f + PHY_LOOKAHEAD(v[i].y)}; debug_push_rect(a, b, 0xFF0000FF); } - #endif +#endif v[i].y = physics_correction(lookup.oy, v[i].y, bounce, WORLD_BLOCK_SIZE/4); } } - #endif - +#endif + entity_set_position(it->entities[i], p[i].x+v[i].x*safe_dt(it), p[i].y+v[i].y*safe_dt(it)); } - + { debug_v2 a = {p[i].x, p[i].y}; debug_v2 b = {p[i].x+v[i].x, p[i].y+v[i].y}; debug_push_line(a, b, 0xFFFFFFFF); } - + { debug_v2 a = {p[i].x-WORLD_BLOCK_SIZE/4, p[i].y-0.5f}; debug_v2 b = {p[i].x+WORLD_BLOCK_SIZE/4, p[i].y+0.5f}; @@ -113,7 +114,7 @@ void IntegratePositions(ecs_iter_t *it) { void HurtOnHazardBlock(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 1); Health *h = ecs_field(it, Health, 2); - + for (int i = 0; i < it->count; i++) { if (h->pain_time < 0.0f) { h->pain_time = HAZARD_BLOCK_TIME; @@ -132,7 +133,7 @@ void HurtOnHazardBlock(ecs_iter_t *it) { void RegenerateHP(ecs_iter_t *it) { Health *h = ecs_field(it, Health, 1); - + for (int i = 0; i < it->count; i++) { if (h[i].pain_time < 0.0f) { if (h[i].heal_time < 0.0f && h[i].hp < h[i].max_hp) { @@ -151,7 +152,7 @@ void RegenerateHP(ecs_iter_t *it) { void ResetActivators(ecs_iter_t *it) { Input *in = ecs_field(it, Input, 1); - + for (int i = 0; i < it->count; i++) { in[i].use = false; in[i].swap = false; @@ -164,7 +165,7 @@ void ResetActivators(ecs_iter_t *it) { void ApplyWorldDragOnVelocity(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 1); Velocity *v = ecs_field(it, Velocity, 2); - + for (int i = 0; i < it->count; i++) { if (zpl_abs(v[i].x) < 0.001f && zpl_abs(v[i].y) < 0.001f) continue; if (ecs_get(it->world, it->entities[i], IsInVehicle)) { @@ -177,7 +178,7 @@ void ApplyWorldDragOnVelocity(ecs_iter_t *it) { float vely = blocks_get_vely(lookup.bid); v[i].x = zpl_lerp(v[i].x, zpl_max(0.0f, zpl_abs(velx))*zpl_sign(velx), PHY_WALK_DRAG*drag*friction*safe_dt(it)); v[i].y = zpl_lerp(v[i].y, zpl_max(0.0f, zpl_abs(vely))*zpl_sign(vely), PHY_WALK_DRAG*drag*friction*safe_dt(it)); - + if ( zpl_abs(v[i].x) > ENTITY_ACTION_VELOCITY_THRESHOLD || zpl_abs(v[i].y) > ENTITY_ACTION_VELOCITY_THRESHOLD) { entity_wake(it->entities[i]); @@ -189,18 +190,18 @@ void ApplyWorldDragOnVelocity(ecs_iter_t *it) { void PlayerClosestInteractable(ecs_iter_t *it){ Input *in = ecs_field(it, Input, 1); - + for (int i = 0; i < it->count; ++i) { size_t ents_count; int64_t *ents = world_chunk_query_entities(it->entities[i], &ents_count, 2); - + ecs_entity_t closest_pick = 0; float min_pick = ZPL_F32_MAX; - + for (size_t j = 0; j < ents_count; j++) { const Position *p2 = ecs_get(it->world, ents[j], Position); if (!p2) continue; - + float dx = p2->x - in[i].bx; float dy = p2->y - in[i].by; float range = zpl_sqrt(dx*dx + dy*dy); @@ -209,9 +210,9 @@ void PlayerClosestInteractable(ecs_iter_t *it){ closest_pick = ents[j]; } } - + in[i].pick_ent = closest_pick; - + if (in[i].pick) in[i].sel_ent = (in[i].sel_ent == closest_pick) ? 0 : closest_pick; } @@ -229,28 +230,33 @@ void DisableWorldEdit(ecs_iter_t *it) { #define ECO2D_TICK_RATE (1.0f/20.f) #define ECS_SYSTEM_TICKED(world, id, stage, ...)\ - ECS_SYSTEM(world, id, stage, __VA_ARGS__);\ - ecs_set_tick_source(world, id, timer); +ECS_SYSTEM(world, id, stage, __VA_ARGS__);\ +ecs_set_tick_source(world, id, timer); + +#define ECS_SYSTEM_TICKED_EX(world, id, stage, time, ...)\ +ECS_SYSTEM(world, id, stage, __VA_ARGS__);\ +ecs_entity_t timer_##id = ecs_set_interval(ecs, 0, ECO2D_TICK_RATE*time);\ +ecs_set_tick_source(world, id, timer_##id); void SystemsImport(ecs_world_t *ecs) { ECS_MODULE(ecs, Systems); - + ecs_entity_t timer = ecs_set_interval(ecs, 0, ECO2D_TICK_RATE); - + ECS_SYSTEM(ecs, EnableWorldEdit, EcsOnLoad); ECS_SYSTEM(ecs, MovementImpulse, EcsOnLoad, components.Input, components.Velocity, components.Position, !components.IsInVehicle); ECS_SYSTEM(ecs, DemoNPCMoveAround, EcsOnLoad, components.Velocity, components.DemoNPC); - + ECS_SYSTEM(ecs, ApplyWorldDragOnVelocity, EcsOnUpdate, components.Position, components.Velocity); ECS_SYSTEM_TICKED(ecs, HurtOnHazardBlock, EcsOnUpdate, components.Position, components.Health); ECS_SYSTEM_TICKED(ecs, RegenerateHP, EcsOnUpdate, components.Health); ECS_SYSTEM(ecs, VehicleHandling, EcsOnUpdate, components.Vehicle, components.Position, components.Velocity); - + ECS_SYSTEM(ecs, IntegratePositions, EcsOnValidate, components.Position, components.Velocity); - + ECS_SYSTEM(ecs, EnterVehicle, EcsPostUpdate, components.Input, components.Position, !components.IsInVehicle); ECS_SYSTEM(ecs, LeaveVehicle, EcsPostUpdate, components.Input, components.IsInVehicle, components.Velocity); - + ECS_SYSTEM(ecs, PlayerClosestInteractable, EcsPostUpdate, components.Input); ECS_SYSTEM(ecs, PickItem, EcsPostUpdate, components.Input, components.Position, components.Inventory, !components.IsInVehicle); ECS_SYSTEM(ecs, DropItem, EcsPostUpdate, components.Input, components.Position, components.Inventory, !components.IsInVehicle); @@ -258,16 +264,17 @@ void SystemsImport(ecs_world_t *ecs) { //ECS_SYSTEM(ecs, MergeItems, EcsPostUpdate, components.Position, components.ItemDrop); ECS_SYSTEM(ecs, UseItem, EcsPostUpdate, components.Input, components.Position, components.Inventory, !components.IsInVehicle); ECS_SYSTEM(ecs, InspectContainers, EcsPostUpdate, components.Input, !components.IsInVehicle); - + ECS_SYSTEM_TICKED(ecs, HarvestIntoContainers, EcsPostUpdate, components.ItemContainer, components.Position, !components.BlockHarvest); ECS_SYSTEM_TICKED(ecs, ProduceItems, EcsPostUpdate, components.ItemContainer, components.Producer, components.Position, components.Device); + ECS_SYSTEM_TICKED_EX(ecs, PushItemsOnNodes, EcsPostUpdate, 20, components.ItemContainer, components.Position, components.Device, components.ItemRouter); ECS_SYSTEM_TICKED(ecs, BuildBlueprints, EcsPostUpdate, components.Blueprint, components.Device, components.Position); - + ECS_SYSTEM(ecs, ResetActivators, EcsPostUpdate, components.Input); - + ECS_SYSTEM(ecs, ClearVehicle, EcsUnSet, components.Vehicle); ECS_SYSTEM(ecs, ThrowItemsOut, EcsUnSet, components.ItemContainer, components.Position); - + ECS_SYSTEM(ecs, DisableWorldEdit, EcsPostUpdate); - + }