From ec1ed23c527680645eece0512b5c6a74bbc41947 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sat, 26 Oct 2024 04:33:23 -0400 Subject: [PATCH] Now using a simple collision map to track entities and then determine if they're near the player for attacking. --- collider.cpp | 35 ++++++++++++++++++++++------------- collider.hpp | 11 ++++------- gui.cpp | 6 ++++++ systems.cpp | 44 +++++++++++++++++++++++++++++--------------- systems.hpp | 1 + tests/collider.cpp | 25 +++++++++++-------------- 6 files changed, 73 insertions(+), 49 deletions(-) diff --git a/collider.cpp b/collider.cpp index a68f43f..43b1595 100644 --- a/collider.cpp +++ b/collider.cpp @@ -15,23 +15,32 @@ void SpatialHashTable::move(Point from, Point to, Entity ent) { insert(to, ent); } -bool SpatialHashTable::occupied(Point at) { - return table[at]; +bool SpatialHashTable::occupied(Point at) const { + return table.contains(at); } -std::tuple SpatialHashTable::neighbors(Point cell) { +inline void find_neighbor(const PointEntityMap &table, FoundList &result, Point at) { + auto it = table.find(at); + if (it != table.end()) { + result.insert(result.end(), it->second); + } +} + +std::tuple SpatialHashTable::neighbors(Point cell, bool diag) const { FoundList result; - // Check the current cell and its 8 neighbors - // BUG: this can sign underflow, assert it won't - for (size_t x = cell.x - 1; x <= cell.x + 1; x++) { - for (size_t y = cell.y - 1; y <= cell.y + 1; y++) { - Point neighborCell = {x, y}; - auto it = table.find(neighborCell); - if (it != table.end()) { - result.insert(result.end(), it->second); - } - } + // just unroll the loop since we only check four directions + // this also solves the problem that it was detecting that the cell was automatically included as a "neighbor" but it's not + find_neighbor(table, result, {cell.x, cell.y+1}); // north + find_neighbor(table, result, {cell.x, cell.y-1}); // south + find_neighbor(table, result, {cell.x+1, cell.y}); // east + find_neighbor(table, result, {cell.x-1, cell.y}); // west + find_neighbor(table, result, {cell.x+1, cell.y-1}); // south east + + if(diag) { + find_neighbor(table, result, {cell.x-1, cell.y-1}); // south west + find_neighbor(table, result, {cell.x+1, cell.y+1}); // north east + find_neighbor(table, result, {cell.x-1, cell.y+1}); // north west } return std::tuple(!result.empty(), result); diff --git a/collider.hpp b/collider.hpp index 60ff2af..12d8e57 100644 --- a/collider.hpp +++ b/collider.hpp @@ -12,21 +12,18 @@ struct PointHash { }; typedef std::vector FoundList; +typedef std::unordered_map PointEntityMap; class SpatialHashTable { public: SpatialHashTable() {} - // disable copying, I think? - SpatialHashTable(SpatialHashTable &other) = delete; - void insert(Point pos, DinkyECS::Entity obj); void move(Point from, Point to, DinkyECS::Entity ent); void remove(Point pos); - bool occupied(Point pos); - - std::tuple neighbors(Point position); + bool occupied(Point pos) const; + std::tuple neighbors(Point position, bool diag=false) const; private: - std::unordered_map table; + PointEntityMap table; }; diff --git a/gui.cpp b/gui.cpp index 48c1922..fcdd7fa 100644 --- a/gui.cpp +++ b/gui.cpp @@ -22,6 +22,7 @@ #include "rand.hpp" #include "components.hpp" #include "systems.hpp" +#include "collider.hpp" using std::string; using namespace fmt; @@ -277,6 +278,9 @@ void GUI::configure_world() { ActionLog log{{"Welcome to the game!"}}; $world.set(log); + SpatialHashTable collider; + $world.set(collider); + $world.assign(player.entity, {$game_map.place_entity(0)}); $world.assign(player.entity, {0, 0}); $world.assign(player.entity, {100, 10}); @@ -298,6 +302,8 @@ void GUI::configure_world() { $world.assign(gold, {$game_map.place_entity($game_map.room_count() - 1)}); $world.assign(gold, {100}); $world.assign(gold, {"$"}); + + System::init_positions($world); } void GUI::render_scene() { diff --git a/systems.cpp b/systems.cpp index ae317b6..7d11975 100644 --- a/systems.cpp +++ b/systems.cpp @@ -3,6 +3,7 @@ #include #include #include "rand.hpp" +#include "collider.hpp" using std::string; using namespace fmt; @@ -24,8 +25,17 @@ void System::enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player game_map.clear_target(player_position.location); } +void System::init_positions(DinkyECS::World &world) { + auto &collider = world.get(); + + world.system([&](const auto &ent, auto &pos) { + collider.insert(pos.location, ent); + }); +} void System::motion(DinkyECS::World &world, Map &game_map) { + auto &collider = world.get(); + world.system([&](const auto &ent, auto &position, auto &motion) { // don't process entities that don't move if(motion.dx != 0 || motion.dy != 0) { @@ -36,8 +46,10 @@ void System::motion(DinkyECS::World &world, Map &game_map) { motion = {0,0}; // clear it after getting it if(game_map.inmap(move_to.x, move_to.y) && - !game_map.iswall(move_to.x,move_to.y)) + !game_map.iswall(move_to.x, move_to.y) && + !collider.occupied(move_to)) { + collider.move(position.location, move_to, ent); position.location = move_to; } } @@ -46,25 +58,27 @@ void System::motion(DinkyECS::World &world, Map &game_map) { void System::combat(DinkyECS::World &world, Player &player) { + const auto& collider = world.get(); const auto& player_position = world.component(player.entity); auto& player_combat = world.component(player.entity); auto& log = world.get(); - world.system([&](const auto &ent, auto &pos, auto &combat) { - if(ent != player.entity) { - int dx = std::abs(int(pos.location.x) - int(player_position.location.x)); - int dy = std::abs(int(pos.location.y) - int(player_position.location.y)); - - if(dx <= 1 && dy <= 1) { - if(player_combat.hp > -1) { - int dmg = Random::uniform(1, combat.damage); - player_combat.hp -= dmg; - - log.log(format("HIT! You took {} damage.", dmg)); - } - } + // this is guaranteed to not return the given position + auto [found, nearby] = collider.neighbors(player_position.location); + + if(found) { + for(auto entity : nearby) { + int attack = Random::uniform(0,1); + if(attack) { + const auto& enemy_dmg = world.component(entity); + int dmg = Random::uniform(1, enemy_dmg.damage); + player_combat.hp -= dmg; + log.log(format("HIT! You took {} damage.", dmg)); + } else { + log.log("You dodged! Run!"); } - }); + } + } }; void System::draw_entities(DinkyECS::World &world, Map &game_map, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y) { diff --git a/systems.hpp b/systems.hpp index 0aa4b81..e572523 100644 --- a/systems.hpp +++ b/systems.hpp @@ -12,4 +12,5 @@ namespace System { void enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player); void draw_map(DinkyECS::World &world, Map &game_map, ftxui::Canvas &canvas, size_t view_x, size_t view_y); void draw_entities(DinkyECS::World &world, Map &game_map, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y); + void init_positions(DinkyECS::World &world); } diff --git a/tests/collider.cpp b/tests/collider.cpp index ae39691..e079179 100644 --- a/tests/collider.cpp +++ b/tests/collider.cpp @@ -23,7 +23,7 @@ TEST_CASE("confirm basic collision operations", "[collision]") { } { // found - auto [found, nearby] = coltable.neighbors({10,10}); + auto [found, nearby] = coltable.neighbors({10,10}, true); REQUIRE(found); REQUIRE(nearby[0] == player); @@ -31,7 +31,7 @@ TEST_CASE("confirm basic collision operations", "[collision]") { { // removed coltable.remove({11,11}); - auto [found, nearby] = coltable.neighbors({10,10}); + auto [found, nearby] = coltable.neighbors({10,10}, true); REQUIRE(!found); REQUIRE(nearby.empty()); } @@ -40,13 +40,13 @@ TEST_CASE("confirm basic collision operations", "[collision]") { { // moving coltable.move({11,11}, {12, 12}, player); - auto [found, nearby] = coltable.neighbors({10,10}); + auto [found, nearby] = coltable.neighbors({10,10}, true); REQUIRE(!found); REQUIRE(nearby.empty()); } { // find it after move - auto [found, nearby] = coltable.neighbors({11,11}); + auto [found, nearby] = coltable.neighbors({11,11}, true); REQUIRE(found); REQUIRE(nearby[0] == player); } @@ -59,7 +59,6 @@ TEST_CASE("confirm basic collision operations", "[collision]") { } - TEST_CASE("confirm multiple entities moving", "[collision]") { DinkyECS::World world; Entity player = world.entity(); @@ -74,22 +73,20 @@ TEST_CASE("confirm multiple entities moving", "[collision]") { coltable.insert({21,21}, e1); { // find e3 and e2 - auto [found, nearby] = coltable.neighbors({11, 11}); + auto [found, nearby] = coltable.neighbors({11, 11}, true); REQUIRE(found); - REQUIRE(nearby.size() == 3); + REQUIRE(nearby.size() == 2); // BUG: replace this with std::find/std::search - REQUIRE(nearby[0] == e2); - REQUIRE(nearby[1] == e3); - REQUIRE(nearby[2] == player); + REQUIRE(nearby[0] == e3); + REQUIRE(nearby[1] == e2); } coltable.move({11,11}, {20,20}, player); { // should only find the e1 - auto [found, nearby] = coltable.neighbors({20,20}); + auto [found, nearby] = coltable.neighbors({20,20}, true); REQUIRE(found); - REQUIRE(nearby.size() == 2); + REQUIRE(nearby.size() == 1); // BUG: replace this with std::find/std::search - REQUIRE(nearby[0] == player); - REQUIRE(nearby[1] == e1); + REQUIRE(nearby[0] == e1); } }