diff --git a/code/game/src/entity.c b/code/game/src/entity.c index 8a6c8b6..cd66789 100644 --- a/code/game/src/entity.c +++ b/code/game/src/entity.c @@ -10,9 +10,10 @@ uint64_t entity_spawn(uint16_t class_id) { ecs_entity_t e = ecs_new(world_ecs(), 0); - + ecs_set(world_ecs(), e, Classify, { .id = class_id }); - + entity_wake(e); + if (class_id != EKIND_SERVER) { ecs_set(world_ecs(), e, Velocity, {0}); Position *pos = ecs_get_mut(world_ecs(), e, Position); @@ -23,12 +24,12 @@ uint64_t entity_spawn(uint16_t class_id) { pos->x=350.0f; pos->y=88.0f; #endif - + librg_entity_track(world_tracker(), e); librg_entity_chunk_set(world_tracker(), e, librg_chunk_from_realpos(world_tracker(), pos->x, pos->y, 0)); librg_entity_owner_set(world_tracker(), e, (int64_t)e); } - + return (uint64_t)e; } @@ -43,3 +44,39 @@ void entity_despawn(uint64_t ent_id) { librg_entity_untrack(world_tracker(), ent_id); ecs_delete(world_ecs(), ent_id); } + +void entity_wake(uint64_t ent_id) { + StreamInfo *si = ecs_get_mut(world_ecs(), ent_id, StreamInfo); + si->tick_delay = 0.0f; + si->last_update = 0.0f; +} + +static ecs_query_t *ecs_streaminfo = NULL; + +void entity_update_action_timers() { + static double last_update_time = 0.0f; + if (!ecs_streaminfo) { + ecs_streaminfo = ecs_query_new(world_ecs(), "components.StreamInfo"); + last_update_time = zpl_time_rel(); + } + + ecs_iter_t it = ecs_query_iter(world_ecs(), ecs_streaminfo); + + while (ecs_query_next(&it)) { + StreamInfo *si = ecs_field(&it, StreamInfo, 1); + + for (size_t i = 0; i < it.count; i++) { + if (si[i].last_update < zpl_time_rel()) { + si[i].last_update = zpl_time_rel() + si[i].tick_delay; + si[i].tick_delay += (zpl_time_rel() - last_update_time) * 0.5f; + } + } + } + + last_update_time = zpl_time_rel(); +} + +bool entity_can_stream(uint64_t ent_id) { + StreamInfo *si = ecs_get_mut(world_ecs(), ent_id, StreamInfo); + return (si->last_update < zpl_time_rel()); +} diff --git a/code/game/src/entity.h b/code/game/src/entity.h index 5030ee1..848c946 100644 --- a/code/game/src/entity.h +++ b/code/game/src/entity.h @@ -1,6 +1,13 @@ #pragma once #include "system.h" +#define ENTITY_ACTION_VELOCITY_THRESHOLD 0.05f + uint64_t entity_spawn(uint16_t class_id /* 0 = no streaming */); void entity_batch_despawn(uint64_t *ids, size_t num_ids); void entity_despawn(uint64_t ent_id); + +// NOTE(zaklaus): action-based entity stream throttling +void entity_wake(uint64_t ent_id); +void entity_update_action_timers(); +bool entity_can_stream(uint64_t ent_id); diff --git a/code/game/src/items.c b/code/game/src/items.c index 8bf3e02..da202fd 100644 --- a/code/game/src/items.c +++ b/code/game/src/items.c @@ -26,14 +26,14 @@ static inline asset_id item_fix_kind(asset_id id) { uint64_t item_spawn(asset_id kind, uint32_t qty) { ecs_entity_t e = entity_spawn(EKIND_ITEM); - + ItemDrop *d = ecs_get_mut(world_ecs(), e, ItemDrop); *d = (ItemDrop){ .kind = item_fix_kind(kind), .quantity = qty, .merger_time = 0, }; - + return (uint64_t)e; } @@ -58,18 +58,18 @@ void item_use(ecs_world_t *ecs, ItemDrop *it, Position p, uint64_t udata) { asset_id item_asset = blocks_get_asset(l.bid); item_id item_asset_id = item_find(item_asset); if (item_asset_id == ASSET_INVALID) return; - + // NOTE(zaklaus): If we replace the same item, refund 1 qty and let it replace it if (item_asset_id == it_id) { it->quantity++; } else { return; } - } + } // NOTE(zaklaus): This is an inner layer block, we can't build over it if it has a collision! else if (l.bid > 0 && blocks_get_flags(l.bid) & (BLOCK_FLAG_COLLISION|BLOCK_FLAG_ESSENTIAL)) { return; - } + } world_chunk_replace_block(l.chunk_id, l.id, blocks_find(desc->place.kind + (asset_id)udata)); it->quantity--; }break; diff --git a/code/game/src/world/world.c b/code/game/src/world/world.c index a964495..9d9432b 100644 --- a/code/game/src/world/world.c +++ b/code/game/src/world/world.c @@ -10,6 +10,7 @@ #include "platform.h" #include "profiler.h" #include "game.h" +#include "entity.h" #include "packets/pkt_send_librg_update.h" @@ -134,6 +135,13 @@ int32_t tracker_write_update(librg_world *w, librg_event *e) { } } + // NOTE(zaklaus): action-based updates + { + if (view.kind != EKIND_CHUNK && !entity_can_stream(entity_id)) { + return LIBRG_WRITE_REJECT; + } + } + return (int32_t)entity_view_pack_struct(buffer, actual_length, view); } @@ -333,6 +341,7 @@ int32_t world_update() { world_tracker_update(1, normal_ms, 2); world_tracker_update(2, slow_ms, 3); + entity_update_action_timers(); debug_replay_update(); return 0; } diff --git a/code/modules/modules/components.c b/code/modules/modules/components.c index 61c9ee2..af56185 100644 --- a/code/modules/modules/components.c +++ b/code/modules/modules/components.c @@ -14,6 +14,7 @@ ECS_COMPONENT_DECLARE(IsInVehicle); ECS_COMPONENT_DECLARE(ItemDrop); ECS_COMPONENT_DECLARE(Inventory); ECS_COMPONENT_DECLARE(DemoNPC); +ECS_COMPONENT_DECLARE(StreamInfo); void ComponentsImport(ecs_world_t *ecs) { ECS_MODULE(ecs, Components); @@ -32,4 +33,5 @@ void ComponentsImport(ecs_world_t *ecs) { ECS_COMPONENT_DEFINE(ecs, ItemDrop); ECS_COMPONENT_DEFINE(ecs, Inventory); ECS_COMPONENT_DEFINE(ecs, DemoNPC); + ECS_COMPONENT_DEFINE(ecs, StreamInfo); } diff --git a/code/modules/modules/components.h b/code/modules/modules/components.h index 80ee1c0..825840d 100644 --- a/code/modules/modules/components.h +++ b/code/modules/modules/components.h @@ -96,6 +96,11 @@ typedef struct { float pickup_time; } Inventory; +typedef struct { + double last_update; + double tick_delay; +} StreamInfo; + typedef struct {char _unused;} DemoNPC; extern ECS_COMPONENT_DECLARE(Vector2D); @@ -112,5 +117,6 @@ extern ECS_COMPONENT_DECLARE(IsInVehicle); extern ECS_COMPONENT_DECLARE(ItemDrop); extern ECS_COMPONENT_DECLARE(Inventory); extern ECS_COMPONENT_DECLARE(DemoNPC); +extern ECS_COMPONENT_DECLARE(StreamInfo); void ComponentsImport(ecs_world_t *ecs); diff --git a/code/modules/modules/systems.c b/code/modules/modules/systems.c index a2e3b5e..7763227 100644 --- a/code/modules/modules/systems.c +++ b/code/modules/modules/systems.c @@ -114,6 +114,7 @@ void RegenerateHP(ecs_iter_t *it) { h[i].heal_time = HP_REGEN_TIME; h[i].hp += HP_REGEN_RECOVERY; h[i].hp = zpl_min(h[i].max_hp, h[i].hp); + entity_wake(it->entities[i]); } else { h[i].heal_time -= safe_dt(it); } diff --git a/code/modules/source/system_items.c b/code/modules/source/system_items.c index 885e60d..fd6ef45 100644 --- a/code/modules/source/system_items.c +++ b/code/modules/source/system_items.c @@ -8,17 +8,17 @@ void PickItem(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 2); Inventory *inv = ecs_field(it, Inventory, 3); - + for (int i = 0; i < it->count; i++) { if (inv[i].pickup_time > game_time()) continue; size_t ents_count; int64_t *ents = world_chunk_query_entities(it->entities[i], &ents_count, 2); - + for (size_t j = 0; j < ents_count; j++) { ItemDrop *drop = 0; if ((drop = ecs_get_mut_if(it->world, ents[j], ItemDrop))) { Position *p2 = ecs_get_mut(it->world, ents[j], Position); - + float dx = p2->x - p[i].x; float dy = p2->y - p[i].y; float range = zpl_sqrt(dx*dx + dy*dy); @@ -33,7 +33,8 @@ void PickItem(ecs_iter_t *it) { item->quantity += picked_count; drop->quantity -= picked_count; item->kind = drop->kind; - + entity_wake(ents[j]); + if (drop->quantity == 0) item_despawn(ents[j]); break; @@ -42,6 +43,7 @@ void PickItem(ecs_iter_t *it) { } else if (range <= ITEM_ATTRACT_RADIUS) { p2->x = zpl_lerp(p2->x, p[i].x, ITEM_ATTRACT_FORCE*it->delta_time); p2->y = zpl_lerp(p2->y, p[i].y, ITEM_ATTRACT_FORCE*it->delta_time); + entity_wake(ents[j]); } } } @@ -55,45 +57,45 @@ void DropItem(ecs_iter_t *it) { Input *in = ecs_field(it, Input, 1); Position *p = ecs_field(it, Position, 2); Inventory *inv = ecs_field(it, Inventory, 3); - + for (int i = 0; i < it->count; i++) { if (!in[i].drop) continue; - + ItemDrop *item = &inv[i].items[in[i].selected_item]; - + if (item->quantity <= 0) continue; - + uint32_t dropped_count = item->quantity; if (in[i].sprint) { dropped_count /= 2; } else if (in[i].ctrl) { dropped_count = dropped_count > 0 ? 1 : 0; } - + if (dropped_count == 0) continue; - + ecs_entity_t te = item_spawn(item->kind, dropped_count); item->quantity -= dropped_count; - + ItemDrop *d = ecs_get_mut(world_ecs(), te, ItemDrop); *d = (ItemDrop){ .kind = item->kind, .quantity = dropped_count, .merger_time = game_time() + ITEM_DROP_MERGER_TIME, }; - + Position *ipos = ecs_get_mut(it->world, te, Position); *ipos = p[i]; - + Velocity *v = ecs_get_mut(it->world, te, Velocity); v->x = in[i].mx * 800.0f; v->y = in[i].my * 800.0f; - + inv[i].pickup_time = game_time() + ITEM_DROP_PICKUP_TIME; in[i].drop = false; - + if (item->quantity == 0) { item->kind = 0; } @@ -103,24 +105,24 @@ void DropItem(ecs_iter_t *it) { void MergeItems(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 1); ItemDrop *id = ecs_field(it, ItemDrop, 2); - + for (int i = 0; i < it->count; i += 1) { ItemDrop *item = &id[i]; - + if (item->merger_time < game_time()) continue; - + size_t ents_count; int64_t *ents = world_chunk_query_entities(it->entities[i], &ents_count, 1); - + for (size_t j = 0; j < ents_count; j++) { ItemDrop *drop = 0; if ((drop = ecs_get_mut_if(it->world, ents[j], ItemDrop))) { if (drop->kind != item->kind || (ecs_entity_t)ents[j] == it->entities[i] || drop->quantity == 0 || item->quantity == 0) continue; - + Position const* p2 = ecs_get(it->world, ents[j], Position); - + float dx = p2->x - (p[i].x); float dy = p2->y - (p[i].y); float range = zpl_sqrt(dx*dx + dy*dy); @@ -137,14 +139,14 @@ void MergeItems(ecs_iter_t *it) { void SwapItems(ecs_iter_t *it) { Input *in = ecs_field(it, Input, 1); Inventory *inv = ecs_field(it, Inventory, 2); - + for (int i = 0; i < it->count; i++) { if (!in[i].swap) continue; - + ItemDrop *to = &inv[i].items[in[i].swap_to]; ItemDrop *from = &inv[i].items[in[i].swap_from]; uint16_t to_id = item_find(to->kind); - + if (to == from) { // NOTE(zaklaus): do nothing } else if (to->kind == from->kind && to->quantity > 0) { @@ -157,7 +159,7 @@ void SwapItems(ecs_iter_t *it) { swapped_count = zpl_clamp(swapped_count, 0, item_max_quantity(to_id) - to->quantity); to->quantity += swapped_count; from->quantity -= swapped_count; - + if (swapped_count == 0) { ItemDrop tmp = *to; *to = *from; @@ -177,7 +179,7 @@ void SwapItems(ecs_iter_t *it) { *to = *from; *from = tmp; } - + in[i].swap = false; } } @@ -186,15 +188,15 @@ void UseItem(ecs_iter_t *it) { Input *in = ecs_field(it, Input, 1); Position *p = ecs_field(it, Position, 2); Inventory *inv = ecs_field(it, Inventory, 3); - + for (int i = 0; i < it->count; i++) { if (!in[i].use && !in[i].num_placements) continue; - + ItemDrop *item = &inv[i].items[in[i].selected_item]; if (!item || item->quantity <= 0) continue; uint16_t item_id = item_find(item->kind); item_usage usage = item_get_usage(item_id); - + if (in[i].use && usage > UKIND_END_PLACE) item_use(it->world, item, p[i], 0); else if (in[i].num_placements > 0 && usage < UKIND_END_PLACE) { @@ -214,13 +216,15 @@ void UseItem(ecs_iter_t *it) { // NOTE(zaklaus): ensure we pick the first variant ofs = 1; } - + for (size_t j = 0; j < in[i].num_placements; j++) { Position pos = {.x = in[i].placements_x[j], .y = in[i].placements_y[j]}; item_use(it->world, item, pos, ofs); } - + in[i].num_placements = 0; } + + entity_wake(it->entities[i]); } } diff --git a/code/modules/source/system_onfoot.c b/code/modules/source/system_onfoot.c index 777615a..9a54ab2 100644 --- a/code/modules/source/system_onfoot.c +++ b/code/modules/source/system_onfoot.c @@ -1,3 +1,5 @@ +#include "entity.h" + #define PLR_MOVE_SPEED 800.0f #define PLR_MOVE_SPEED_MULT 1.5f @@ -5,12 +7,17 @@ void MovementImpulse(ecs_iter_t *it) { Input *in = ecs_field(it, Input, 1); Velocity *v = ecs_field(it, Velocity, 2); Position *p = ecs_field(it, Position, 3); - + for (int i = 0; i < it->count; i++) { world_block_lookup lookup = world_block_from_realpos(p[i].x, p[i].y); float drag = zpl_clamp(blocks_get_drag(lookup.bid), 0.0f, 1.0f); float speed = PLR_MOVE_SPEED * (in[i].sprint ? PLR_MOVE_SPEED_MULT : 1.0f); v[i].x += in[i].x*speed*drag*safe_dt(it); v[i].y -= in[i].y*speed*drag*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]); + } } } diff --git a/code/modules/source/system_vehicle.c b/code/modules/source/system_vehicle.c index a05f629..be2993f 100644 --- a/code/modules/source/system_vehicle.c +++ b/code/modules/source/system_vehicle.c @@ -1,4 +1,5 @@ #include "debug_draw.h" +#include "entity.h" #define VEH_ENTER_RADIUS 45.0f @@ -6,10 +7,10 @@ void LeaveVehicle(ecs_iter_t *it) { Input *in = ecs_field(it, Input, 1); IsInVehicle *vehp = ecs_field(it, IsInVehicle, 2); Velocity *v = ecs_field(it, Velocity, 3); - + for (int i = 0; i < it->count; i++) { if (!in[i].use) continue; - + Vehicle *veh = 0; if ((veh = ecs_get_mut_if(it->world, vehp->veh, Vehicle))) { for (int k = 0; k < 4; k++) { @@ -18,10 +19,10 @@ void LeaveVehicle(ecs_iter_t *it) { break; } } - + in[i].use = false; ecs_remove(it->world, it->entities[i], IsInVehicle); - + // NOTE(zaklaus): push passenger out { float px = zpl_cos(veh->heading)*400.0f; @@ -38,29 +39,29 @@ void LeaveVehicle(ecs_iter_t *it) { void EnterVehicle(ecs_iter_t *it) { Input *in = ecs_field(it, Input, 1); Position *p = ecs_field(it, Position, 2); - + for (int i = 0; i < it->count; i++) { if (!in[i].use) continue; - + size_t ents_count; int64_t *ents = world_chunk_query_entities(it->entities[i], &ents_count, 2); bool has_entered_veh = false; - + for (size_t j = 0; j < ents_count; j++) { Vehicle *veh = 0; - + if (has_entered_veh) break; - + if ((veh = ecs_get_mut_if(it->world, ents[j], Vehicle))) { Position const* p2 = ecs_get(it->world, ents[j], Position); - + float dx = p2->x - p[i].x; float dy = p2->y - p[i].y; float range = zpl_sqrt(dx*dx + dy*dy); if (range <= VEH_ENTER_RADIUS) { for (int k = 0; k < 4; k++) { if (veh->seats[k] != 0) continue; - + // NOTE(zaklaus): We can enter the vehicle, yay! veh->seats[k] = it->entities[i]; ecs_set(it->world, it->entities[i], IsInVehicle, { @@ -90,27 +91,27 @@ void VehicleHandling(ecs_iter_t *it) { Vehicle *veh = ecs_field(it, Vehicle, 1); Position *p = ecs_field(it, Position, 2); Velocity *v = ecs_field(it, Velocity, 3); - + for (int i = 0; i < it->count; i++) { Vehicle *car = &veh[i]; - + for (int j = 0; j < 4; j++) { // NOTE(zaklaus): Perform seat cleanup if (!world_entity_valid(veh[i].seats[j])) { veh[i].seats[j] = 0; continue; } - + ecs_entity_t pe = veh[i].seats[j]; - + // NOTE(zaklaus): Handle driver input if (j == 0) { Input const* in = ecs_get(it->world, pe, Input); - + car->force += zpl_lerp(0.0f, in->y * VEHICLE_FORCE, (zpl_sign(in->y) == zpl_sign(car->force) ? 1.0f : 3.0f) * VEHICLE_ACCEL*safe_dt(it)); if (in->sprint) { car->force = zpl_lerp(car->force, 0.0f, VEHICLE_BRAKE_FORCE*safe_dt(it)); - + if (zpl_abs(car->force) < 5.5f) car->force = 0.0f; } @@ -120,58 +121,63 @@ void VehicleHandling(ecs_iter_t *it) { car->steer = zpl_clamp(car->steer, -60.0f, 60.0f); } } - + car->force = zpl_clamp(car->force, car->reverse_speed, car->speed); - + // NOTE(zaklaus): Vehicle physics float fr_x = p[i].x + (car->wheel_base/2.0f) * zpl_cos(car->heading); float fr_y = p[i].y + (car->wheel_base/2.0f) * zpl_sin(car->heading); - + float bk_x = p[i].x - (car->wheel_base/2.0f) * zpl_cos(car->heading); float bk_y = p[i].y - (car->wheel_base/2.0f) * zpl_sin(car->heading); - + world_block_lookup lookup = world_block_from_realpos(p[i].x, p[i].y); float drag = zpl_clamp(blocks_get_drag(lookup.bid), 0.0f, 1.0f); - + bk_x += car->force * drag * zpl_cos(car->heading) * safe_dt(it)*VEHICLE_POWER; bk_y += car->force * drag * zpl_sin(car->heading) * safe_dt(it)*VEHICLE_POWER; fr_x += car->force * drag * zpl_cos(car->heading + zpl_to_radians(car->steer)) * safe_dt(it)*VEHICLE_POWER; fr_y += car->force * drag * zpl_sin(car->heading + zpl_to_radians(car->steer)) * safe_dt(it)*VEHICLE_POWER; - + v[i].x += ((fr_x + bk_x) / 2.0f - p[i].x); v[i].y += ((fr_y + bk_y) / 2.0f - p[i].y); car->heading = zpl_arctan2(fr_y - bk_y, fr_x - bk_x); - + world_block_lookup lookahead = world_block_from_realpos(p[i].x+PHY_LOOKAHEAD(v[i].x), p[i].y+PHY_LOOKAHEAD(v[i].y)); uint32_t flags = blocks_get_flags(lookahead.bid); if (flags & BLOCK_FLAG_COLLISION) { car->force = 0.0f; } - + for (int j = 0; j < 4; j++) { if (!world_entity_valid(veh[i].seats[j])) continue; ecs_entity_t pe = veh[i].seats[j]; - + // NOTE(zaklaus): Update passenger position { Position *p2 = ecs_get_mut(it->world, pe, Position); Velocity *v2 = ecs_get_mut(it->world, pe, Velocity); *p2 = p[i]; *v2 = v[i]; + entity_wake(pe); } } - + + if (zpl_abs(car->force) > ENTITY_ACTION_VELOCITY_THRESHOLD) { + entity_wake(it->entities[i]); + } + { debug_v2 b2 = {p[i].x + zpl_cos(car->heading)*(car->wheel_base), p[i].y + zpl_sin(car->heading)*(car->wheel_base)}; debug_push_line((debug_v2){p[i].x, p[i].y}, b2, 0x0000FFFF); - + // NOTE(zaklaus): force { float dx = zpl_cos(car->heading); float dy = zpl_sin(car->heading); debug_push_circle((debug_v2){p[i].x+dx*car->force, p[i].y+dy*car->force}, 5.0f, 0x00FF00FF); } - + // NOTE(zaklaus): steer { float dx = zpl_sin(car->heading); @@ -185,7 +191,7 @@ void VehicleHandling(ecs_iter_t *it) { void ClearVehicle(ecs_iter_t *it) { Vehicle *veh = ecs_field(it, Vehicle, 1); - + for (int i = 0; i < it->count; i++) { for (int k = 0; k < 4; k++) { if (world_entity_valid(veh[i].seats[k])) {