diff --git a/assets/items.json b/assets/items.json index 153d318..bd1585b 100644 --- a/assets/items.json +++ b/assets/items.json @@ -1,22 +1,34 @@ { - "TORCH": { + "TORCH_BAD": { + "id": "TORCH_BAD", + "name": "Crappy Torch", "foreground": [24, 205, 189], "background": [230, 20, 120], + "description": "Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. Summus brains sit​​, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris. Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium.", "display": "\u0f08" }, - "SWORD": { + "SWORD_RUSTY": { + "id": "SWORD_RUSTY", + "name": "Rusty Junk Sword", "foreground": [24, 205, 189], "background": [24, 205, 189], + "description": "Shoreditch pickled readymade tousled tumeric. Chicharrones same jawn irony woke echo park jianbing artisan ethical praxis grailed portland. Banjo solarpunk yes plz, offal Brooklyn beard bushwick letterpress celiac sartorial.", "display":"\u1e37" }, - "CHEST": { + "CHEST_SMALL": { + "id": "CHEST_SMALL", + "name": "Small Chest", "foreground": [24, 205, 189], "background": [24, 205, 189], - "display":"\uaaea" + "display":"\uaaea", + "description": "Tote bag sustainable crucifix gentrify kombucha. Try-hard single-origin coffee meh pork belly cliche aesthetic scenester disrupt banjo af." }, "WALL_TORCH": { + "id": "WALL_TORCH", + "name": "Basic Wall Torch", "foreground": [24, 205, 189], "background": [24, 205, 189], + "description": "A torch on a wall you can't pick up.", "display": "☀" } } diff --git a/components.hpp b/components.hpp index 5d0ce74..49887c0 100644 --- a/components.hpp +++ b/components.hpp @@ -2,6 +2,7 @@ #include "dinkyecs.hpp" #include "map.hpp" #include "combat.hpp" +#include "inventory.hpp" #include #include "tser.hpp" @@ -27,12 +28,6 @@ namespace components { DEFINE_SERIALIZABLE(Loot, amount); }; - struct Inventory { - int gold; - LightSource light; - DEFINE_SERIALIZABLE(Inventory, gold, light); - }; - struct Tile { std::string chr; DEFINE_SERIALIZABLE(Tile, chr); diff --git a/gui.cpp b/gui.cpp index 8047685..852e48e 100644 --- a/gui.cpp +++ b/gui.cpp @@ -240,11 +240,10 @@ void GUI::handle_world_events() { } } break; case eGUI::LOOT: { - auto &loot = std::any_cast(data); - auto inventory = $world.get(player.entity); + auto &item = std::any_cast(data); + auto &inventory = $world.get(player.entity); + fmt::println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!! UPDATE INVENTORY HERE."); $sounds.play("loot_gold"); - $status_ui.log(format("You found {} gold. You have {} now.", - loot.amount, inventory.gold)); } break; default: diff --git a/inventory.cpp b/inventory.cpp new file mode 100644 index 0000000..7b2fb4e --- /dev/null +++ b/inventory.cpp @@ -0,0 +1,32 @@ +#include "inventory.hpp" + + +namespace components { + void Inventory::add(InventoryItem item) { + std::string id = item.data["id"]; + + if(items.contains(id)) { + auto &slot = items[id]; + slot.count += item.count; + } else { + items[id] = item; + } + } + + InventoryItem& Inventory::get(std::string id) { + dbc::check(items.contains(id), fmt::format("item id {} is not in inventory", id)); + return items[id]; + } + + bool Inventory::decrease(std::string id, int count) { + dbc::check(items.contains(id), fmt::format("item id {} is not in inventory", id)); + auto &slot = items[id]; + slot.count -= count; + return slot.count > 0; + } + + void Inventory::remove_all(std::string id) { + dbc::check(items.contains(id), fmt::format("item id {} is not in inventory", id)); + items.erase(id); + } +} diff --git a/inventory.hpp b/inventory.hpp new file mode 100644 index 0000000..4d90fd3 --- /dev/null +++ b/inventory.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "lights.hpp" +#include +#include + + +namespace components { + using namespace nlohmann; + using lighting::LightSource; + + struct InventoryItem { + int count; + json data; + }; + + struct Inventory { + int gold; + LightSource light; + std::unordered_map items; + + size_t count() { return items.size(); } + + void add(InventoryItem item); + + bool decrease(std::string id, int count); + + InventoryItem& get(std::string id); + + void remove_all(std::string id); + }; +} diff --git a/main.cpp b/main.cpp index e348355..000aa58 100644 --- a/main.cpp +++ b/main.cpp @@ -13,6 +13,7 @@ #include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor #include #include +#include using namespace ftxui; using namespace components; @@ -33,8 +34,15 @@ void configure_world(DinkyECS::World &world, Map &game_map) { world.set(player.entity, {0, 0}); world.set(player.entity, {100, 10}); world.set(player.entity, {config.enemies["PLAYER_TILE"]["display"]}); - world.set(player.entity, {5}); world.set(player.entity, {70,1.0}); + world.set(player.entity, {5}); + + auto sword = world.entity(); + auto pos = game_map.place_entity(1); + world.set(sword, {pos.x+1, pos.y+1}); + world.set(sword, {config.items["SWORD_RUSTY"]["display"]}); + world.set(sword, {1, config.items["SWORD_RUSTY"]}); + world.set(sword, {20}); auto enemy = world.entity(); world.set(enemy, {game_map.place_entity(1)}); @@ -52,26 +60,12 @@ void configure_world(DinkyECS::World &world, Map &game_map) { auto gold = world.entity(); world.set(gold, {game_map.place_entity(3)}); world.set(gold, {100}); - world.set(gold, {config.items["CHEST"]["display"]}); + world.set(gold, {config.items["CHEST_SMALL"]["display"]}); auto wall_torch = world.entity(); world.set(wall_torch, {game_map.place_entity(4)}); world.set(wall_torch, {90,3.0f}); world.set(wall_torch, {config.items["WALL_TORCH"]["display"]}); - - auto torch = world.entity(); - Point at = game_map.place_entity(2); - world.set(torch, {{at.x+1, at.y+1}}); - world.set(torch, {{0}}); - world.set(torch, {70,1.5f}); - world.set(torch, {config.items["TORCH"]["display"]}); - - auto sword = world.entity(); - at = game_map.place_entity(1); - world.set(sword, {at.x+1, at.y+1}); - world.set(sword, {.damage=20}); - world.set(sword, {{0}}); - world.set(sword, {config.items["SWORD"]["display"]}); } int main(int argc, char *argv[]) { diff --git a/meson.build b/meson.build index 606b1f7..6e06026 100644 --- a/meson.build +++ b/meson.build @@ -11,8 +11,7 @@ sfml = dependency('sfml') freetype2 = dependency('freetype2') dependencies = [ - catch2, fmt, - ftxui_screen, ftxui_dom, + fmt, ftxui_screen, ftxui_dom, ftxui_component, json, sfml, freetype2 ] @@ -36,6 +35,7 @@ runtests = executable('runtests', [ 'systems.cpp', 'gui.cpp', 'worldbuilder.cpp', + 'inventory.cpp', 'tests/tilemap.cpp', 'tests/matrix.cpp', 'tests/fsm.cpp', @@ -54,8 +54,9 @@ runtests = executable('runtests', [ 'tests/lighting.cpp', 'tests/gui.cpp', 'tests/worldbuilder.cpp', + 'tests/inventory.cpp', ], - dependencies: dependencies) + dependencies: dependencies + catch2) roguish = executable('roguish', [ 'dbc.cpp', @@ -77,6 +78,7 @@ roguish = executable('roguish', [ 'pathing.cpp', 'lights.cpp', 'worldbuilder.cpp', + 'inventory.cpp', ], dependencies: dependencies) diff --git a/save.cpp b/save.cpp index 11ab79b..c87e4e0 100644 --- a/save.cpp +++ b/save.cpp @@ -30,7 +30,7 @@ void save::to_file(fs::path path, DinkyECS::World &world, Map &map) { extract(world, save_data.combat); extract(world, save_data.motion); extract(world, save_data.tile); - extract(world, save_data.inventory); + // extract(world, save_data.inventory); archive.save(save_data); std::string_view archive_view = archive.get_buffer(); @@ -72,7 +72,7 @@ void save::from_file(fs::path path, DinkyECS::World &world_out, Map &map_out) { inject(world_out, save_data.combat); inject(world_out, save_data.motion); inject(world_out, save_data.tile); - inject(world_out, save_data.inventory); + // inject(world_out, save_data.inventory); size_t width = save_data.map.width; size_t height = save_data.map.height; diff --git a/save.hpp b/save.hpp index 9ece6f1..84771df 100644 --- a/save.hpp +++ b/save.hpp @@ -33,9 +33,9 @@ namespace save { std::map motion; std::map combat; std::map tile; - std::map inventory; + // std::map inventory; - DEFINE_SERIALIZABLE(SaveData, facts, map, position, motion, combat, tile, inventory); + DEFINE_SERIALIZABLE(SaveData, facts, map, position, motion, combat, tile); }; void to_file(fs::path path, DinkyECS::World &world, Map &map); diff --git a/status.txt b/status.txt index 74168b3..6c5176b 100644 --- a/status.txt +++ b/status.txt @@ -1,5 +1,7 @@ TODAY'S GOAL: +* Make Map::place_entity handle entity overlap and also walls. +* Config loader should setup the "id" based on the key to avoid errors. * Colision fails when you place two entities on the same square, but the init_positions adds them and one deletes the other. * Config needs to do asserts that the key exists * Create a move function for iterators that recalculates their position to make it easy to move them inside the matrix. This can then be used in lighting. Just make an iterator once, and move it around after. diff --git a/systems.cpp b/systems.cpp index faab231..15cc257 100644 --- a/systems.cpp +++ b/systems.cpp @@ -62,7 +62,7 @@ void System::init_positions(DinkyECS::World &world) { } }); - world.query([&](const auto &ent, auto &pos, auto &loot) { + world.query([&](const auto &ent, auto &pos, auto &item) { collider.insert(pos.location, ent); }); } @@ -134,10 +134,12 @@ void System::collision(DinkyECS::World &world, Player &player) { }; world.send(Events::GUI::COMBAT, entity, result); - } else if(world.has(entity)) { - auto loot = world.get(entity); - auto &loot_pos = world.get(entity); - auto &inventory = world.get(player.entity); + } else if(world.has(entity)) { + auto& item = world.get(entity); + auto& item_pos = world.get(entity); + auto& inventory = world.get(player.entity); + + inventory.add(item); if(world.has(entity)) { auto &new_light = world.get(entity); @@ -148,15 +150,12 @@ void System::collision(DinkyECS::World &world, Player &player) { auto &weapon = world.get(entity); player_combat.damage = weapon.damage; world.remove(entity); - } else { - // it's just gold - inventory.gold += loot.amount; } - collider.remove(loot_pos.location); + collider.remove(item_pos.location); world.remove(entity); - world.remove(entity); - world.send(Events::GUI::LOOT, entity, loot); + world.remove(entity); + world.send(Events::GUI::LOOT, entity, item); } else { println("UNKNOWN COLLISION TYPE {}", entity); } @@ -183,3 +182,10 @@ void System::draw_entities(DinkyECS::World &world, Map &game_map, const Matrix & } }); } + +void System::pickup(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item) { + auto& inventory = world.get(actor); + auto& invitem = world.get(item); + + inventory.add(invitem); +} diff --git a/systems.hpp b/systems.hpp index f053b2e..a6be026 100644 --- a/systems.hpp +++ b/systems.hpp @@ -16,4 +16,5 @@ namespace System { void enemy_pathing(DinkyECS::World &world, Map &game_map, Player &player); void draw_entities(DinkyECS::World &world, Map &game_map, const Matrix &lighting, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y); void init_positions(DinkyECS::World &world); + void pickup(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item); } diff --git a/tests/inventory.cpp b/tests/inventory.cpp new file mode 100644 index 0000000..2bd2c1f --- /dev/null +++ b/tests/inventory.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include "rand.hpp" +#include +#include +#include "components.hpp" +#include "dinkyecs.hpp" +#include "save.hpp" +#include "systems.hpp" + +using namespace nlohmann; +using namespace fmt; +using std::string; +using namespace components; + + +DinkyECS::Entity add_items(DinkyECS::World &world, GameConfig &config) { + auto sword = world.entity(); + world.set(sword, {1, config.items["SWORD_RUSTY"]}); + world.set(sword, {config.items["SWORD_RUSTY"]["display"]}); + + return sword; +} + +TEST_CASE("basic inventory test", "[inventory]") { + DinkyECS::World world; + save::load_configs(world); + auto& config = world.get_the(); + auto sword = add_items(world, config); + + auto player = world.entity(); + world.set(player, {}); + + auto &inventory = world.get(player); + + System::pickup(world, player, sword); + REQUIRE(inventory.count() == 1); + // get the item and confirm there is 1 + auto &item1 = inventory.get("SWORD_RUSTY"); + REQUIRE(item1.count == 1); + + System::pickup(world, player, sword); + System::pickup(world, player, sword); + System::pickup(world, player, sword); + REQUIRE(inventory.count() == 1); + REQUIRE(item1.count == 4); + + inventory.decrease("SWORD_RUSTY", 1); + REQUIRE(item1.count == 3); + + inventory.decrease("SWORD_RUSTY", 2); + REQUIRE(item1.count == 1); + + bool active = inventory.decrease("SWORD_RUSTY", 1); + REQUIRE(item1.count == 0); + REQUIRE(active == false); + + inventory.remove_all("SWORD_RUSTY"); + REQUIRE(inventory.count() == 0); +} diff --git a/tests/matrix.cpp b/tests/matrix.cpp index 770304e..3533524 100644 --- a/tests/matrix.cpp +++ b/tests/matrix.cpp @@ -252,6 +252,7 @@ TEST_CASE("prototype circle algorithm", "[matrix:circle]") { size_t height = Random::uniform(10, 15); int pos_mod = Random::uniform(-3,3); Map map(width,height); + // create a target for the paths Point start{.x=map.width() / 2 + pos_mod, .y=map.height()/2 + pos_mod}; @@ -275,3 +276,31 @@ TEST_CASE("prototype circle algorithm", "[matrix:circle]") { } } } + +TEST_CASE("viewport iterator", "[matrix:viewport]") { + size_t width = Random::uniform(20, 22); + size_t height = Random::uniform(21, 25); + Map map(width,height); + WorldBuilder builder(map); + builder.generate(); + + size_t view_width = width/2; + size_t view_height = height/2; + Point player = map.place_entity(1); + Point start = map.center_camera(player, view_width, view_height); + + size_t end_x = std::min(view_width, map.width() - start.x); + size_t end_y = std::min(view_height, map.height() - start.y); + + matrix::viewport it{map.walls(), start, int(view_width), int(view_height)}; + + for(size_t y = 0; y < end_y; ++y) { + for(size_t x = 0; x < end_x && it.next(); ++x) { + + println("view x/y={},{}; w/h={},{}; start={},{}", + it.x, it.y, it.width, it.height, it.start.x, it.start.y); + println("orig x/y={},{}; w/h={},{}; start={},{}\n", + x+start.x, y+start.y, view_width, view_height, start.x, start.y); + } + } +}