From f84b63f0e672e3c4acf79d17c5d15ed5bc0df413 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Thu, 7 Aug 2025 12:13:39 -0400 Subject: [PATCH] The problem with picking up items under a dead body is fixed but now need to fix combat. --- Makefile | 2 +- gui/fsm.cpp | 16 +++++----- spatialmap.cpp | 11 +++++++ spatialmap.hpp | 2 ++ systems.cpp | 72 +++++++++++++++++++++++++------------------- systems.hpp | 4 +-- tests/spatialmap.cpp | 29 ++++++++++++++++++ 7 files changed, 93 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index 54a92f6..ca4ba17 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ clean: meson compile --clean -C builddir debug_test: build - gdb --nx -x .gdbinit --ex run --args builddir/runtests -e "[map]" + gdb --nx -x .gdbinit --ex run --args builddir/runtests -e win_installer: powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp' diff --git a/gui/fsm.cpp b/gui/fsm.cpp index 1d8e22d..e37012b 100644 --- a/gui/fsm.cpp +++ b/gui/fsm.cpp @@ -74,7 +74,7 @@ namespace gui { void FSM::MOVING(Event ) { // this should be an optional that returns a point if(auto move_to = $main_ui.play_move()) { - System::plan_motion(*$level.world, *move_to); + System::plan_motion($level, *move_to); run_systems(); $main_ui.dirty(); state(State::IDLE); @@ -198,14 +198,8 @@ namespace gui { case MOUSE_MOVE: mouse_action(true); break; - case AIM_CLICK: { - auto aimed_at = $main_ui.camera_aim(); - - if(aimed_at) { - // this will then send LOOT_ITEM if it's valid - System::pickup($level, aimed_at); - } - } break; + case AIM_CLICK: + System::pickup($level); default: break; // ignore everything else } @@ -357,6 +351,10 @@ namespace gui { $loot_ui.update(); event(Event::LOOT_OPEN); break; + case KEY::Z: { + auto& player_pos = System::player_position($level); + System::distribute_loot($level, {player_pos.aiming_at}); + } break; case KEY::X: event(Event::STAIRS_DOWN); break; diff --git a/spatialmap.cpp b/spatialmap.cpp index 4fe143f..dcdbf1d 100644 --- a/spatialmap.cpp +++ b/spatialmap.cpp @@ -66,6 +66,7 @@ inline void find_neighbor(const PointEntityMap &table, EntityList &result, Point Point cell = {at.x + dx, at.y + dy}; + // Bug #81, should actually for-loop through these and only add ones with collision auto it = table.find(cell); if (it != table.end()) { result.insert(result.end(), it->second.entity); @@ -117,6 +118,16 @@ inline void update_sorted(SortedEntities& sprite_distance, PointEntityMap& table } } +Entity SpatialMap::find(Point at, std::function cb) const { + auto [begin, end] = $collision.equal_range(at); + + for(auto it = begin; it != end; ++it) { + if(cb(it->second)) return it->second.entity; + } + + return DinkyECS::NONE; +} + void SpatialMap::distance_sorted(SortedEntities& sprite_distance, Point from, int max_dist) { sprite_distance.clear(); diff --git a/spatialmap.hpp b/spatialmap.hpp index d7aa218..2740552 100644 --- a/spatialmap.hpp +++ b/spatialmap.hpp @@ -38,6 +38,8 @@ class SpatialMap { bool occupied(Point pos) const; bool something_there(Point at) const; DinkyECS::Entity get(Point at) const; + DinkyECS::Entity find(Point at, std::function cb) const; + FoundEntities neighbors(Point position, bool diag=false) const; void distance_sorted(SortedEntities& sorted_sprites, Point from, int max_distance); diff --git a/systems.cpp b/systems.cpp index d4d5165..33e8013 100644 --- a/systems.cpp +++ b/systems.cpp @@ -53,10 +53,9 @@ void System::lighting(GameLevel &level) { } void System::generate_paths(GameLevel &level) { - auto player = level.world->get_the(); - const auto &player_position = level.world->get(player.entity); + const auto &player_pos = player_position(level); - level.map->set_target(player_position.location); + level.map->set_target(player_pos.location); level.map->make_paths(); } @@ -90,12 +89,10 @@ void System::enemy_ai_initialize(GameLevel &level) { void System::enemy_pathing(GameLevel &level) { auto &world = *level.world; auto &map = *level.map; - auto player = world.get_the(); - - const auto &player_position = world.get(player.entity); + const auto &player_pos = player_position(level); world.query([&](auto ent, auto &position, auto &motion) { - if(ent != player.entity) { + if(ent != level.player) { auto& enemy_ai = world.get(ent); Point out = position.location; // copy @@ -109,7 +106,7 @@ void System::enemy_pathing(GameLevel &level) { } }); - map.clear_target(player_position.location); + map.clear_target(player_pos.location); } @@ -240,17 +237,16 @@ inline void animate_entity(World &world, Entity entity) { void System::combat(GameLevel &level, int attack_id) { auto &collider = *level.collision; auto &world = *level.world; - auto player = world.get_the(); auto& the_belt = world.get_the(); if(!the_belt.has(attack_id)) return; auto& ritual = the_belt.get(attack_id); - const auto& player_position = world.get(player.entity); - auto& player_combat = world.get(player.entity); + const auto& player_pos = player_position(level); + auto& player_combat = world.get(level.player); // this is guaranteed to not return the given position - auto [found, nearby] = collider.neighbors(player_position.location); + auto [found, nearby] = collider.neighbors(player_pos.location); combat::BattleEngine battle; if(found) { @@ -297,12 +293,10 @@ void System::combat(GameLevel &level, int attack_id) { void System::collision(GameLevel &level) { auto &collider = *level.collision; auto &world = *level.world; - auto player = world.get_the(); - - const auto& player_position = world.get(player.entity); + const auto& player_pos = player_position(level); // this is guaranteed to not return the given position - auto [found, nearby] = collider.neighbors(player_position.location); + auto [found, nearby] = collider.neighbors(player_pos.location); int combat_count = 0; // AI: I think also this would a possible place to run AI decisions @@ -320,7 +314,7 @@ void System::collision(GameLevel &level) { if(combat_count == 0) { // BUG: this is probably how we get stuck in combat - world.send(Events::GUI::NO_NEIGHBORS, player.entity, player.entity); + world.send(Events::GUI::NO_NEIGHBORS, level.player, level.player); } } @@ -337,13 +331,29 @@ void System::remove_from_world(GameLevel &level, Entity entity) { level.world->remove(entity); } -void System::pickup(GameLevel &level, Entity entity) { +void System::pickup(GameLevel &level) { auto &world = *level.world; - auto player = world.get_the(); + auto &collision = *level.collision; + auto pos = player_position(level); + + if(!collision.something_there(pos.aiming_at)) { + dbc::log("nothing there"); + return; + } + + auto entity = level.collision->find(pos.aiming_at, [&](auto data) -> bool { + return (world.has(data.entity) || + world.has(data.entity)); + }); - if(world.has(entity)) { + if(entity == DinkyECS::NONE) { + dbc::log("no inventory or devices there"); + return; + } + + // use spatial find to find an item with inventory... + if(auto item = world.get_if(entity)) { // NOTE: this might need to be a separate system so that people can leave stuff alone - auto item = world.get(entity); remove_from_world(level, entity); if(world.has(entity)) { @@ -358,14 +368,15 @@ void System::pickup(GameLevel &level, Entity entity) { // NOTE: chests are different from say a torch, maybe 2 events or the // GUI figures out which it is, then when you click either pick it up // and move it or show the loot container UI - world.send(Events::GUI::LOOT_ITEM, entity, item); + world.send(Events::GUI::LOOT_ITEM, entity, *item); } } else if(world.has(entity)) { - System::device(world, player.entity, entity); + System::device(world, level.player, entity); + } else { + // Bug #81 is related to this } } - void System::device(World &world, Entity actor, Entity item) { auto& device = world.get(item); dbc::log(fmt::format("entity {} INTERACTED WITH DEVICE {}", actor, item)); @@ -387,15 +398,14 @@ void System::device(World &world, Entity actor, Entity item) { } } -void System::plan_motion(World& world, Position move_to) { - auto& player = world.get_the(); - auto& player_position = world.get(player.entity); +void System::plan_motion(GameLevel& level, Position move_to) { + auto& player_pos = player_position(level); - player_position.aiming_at = move_to.aiming_at; + player_pos.aiming_at = move_to.aiming_at; - auto& motion = world.get(player.entity); - motion.dx = move_to.location.x - player_position.location.x; - motion.dy = move_to.location.y - player_position.location.y; + auto& motion = level.world->get(level.player); + motion.dx = move_to.location.x - player_pos.location.x; + motion.dy = move_to.location.y - player_pos.location.y; } diff --git a/systems.hpp b/systems.hpp index 46dcc51..5632d17 100644 --- a/systems.hpp +++ b/systems.hpp @@ -17,7 +17,7 @@ namespace System { void enemy_ai_initialize(GameLevel &level); void device(World &world, Entity actor, Entity item); - void plan_motion(World& world, Position move_to); + void plan_motion(GameLevel& level, Position move_to); Entity spawn_item(World& world, const string& name); void drop_item(GameLevel& level, Entity item); @@ -28,7 +28,7 @@ namespace System { void player_status(GameLevel &level); void distribute_loot(GameLevel &level, Position target_pos); - void pickup(GameLevel &level, Entity entity); + void pickup(GameLevel &level); bool place_in_container(World& world, Entity cont_id, const string& name, Entity world_entity); diff --git a/tests/spatialmap.cpp b/tests/spatialmap.cpp index 2633ce0..70af120 100644 --- a/tests/spatialmap.cpp +++ b/tests/spatialmap.cpp @@ -5,6 +5,7 @@ #include "dinkyecs.hpp" #include "rand.hpp" #include +#include using DinkyECS::Entity; using namespace fmt; @@ -157,6 +158,34 @@ TEST_CASE("SpatialMap::get", "[spatialmap]") { REQUIRE(entity == item); } +TEST_CASE("SpatialMap::find", "[spatialmap-find]") { + DinkyECS::World world; + SpatialMap map; + Point at{101, 31}; + DinkyECS::Entity should_collide = DinkyECS::NONE; + + for(int i = 0; i < 10; i++) { + auto ent = world.entity(); + map.insert(at, ent, i == 8); + + if(i == 8) { + should_collide = ent; + } + } + + auto collision = map.find(at, [&](auto data) -> bool { + return data.collision; + }); + + REQUIRE(collision == should_collide); + + auto no_collide = map.find(at, [&](auto data) -> bool { + return !data.collision; + }); + + REQUIRE(no_collide != should_collide); +} + TEST_CASE("SpatialMap::neighbors", "[spatialmap]") { DinkyECS::World world; SpatialMap map;