Basic inventory system working and can pick up items but needs to be reflected in the UI next.

main
Zed A. Shaw 5 days ago
parent d7353a02df
commit 135d9a128b
  1. 20
      assets/items.json
  2. 7
      components.hpp
  3. 7
      gui.cpp
  4. 32
      inventory.cpp
  5. 31
      inventory.hpp
  6. 26
      main.cpp
  7. 8
      meson.build
  8. 4
      save.cpp
  9. 4
      save.hpp
  10. 2
      status.txt
  11. 28
      systems.cpp
  12. 1
      systems.hpp
  13. 61
      tests/inventory.cpp
  14. 29
      tests/matrix.cpp

@ -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": "☀"
}
}

@ -2,6 +2,7 @@
#include "dinkyecs.hpp"
#include "map.hpp"
#include "combat.hpp"
#include "inventory.hpp"
#include <deque>
#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);

@ -240,11 +240,10 @@ void GUI::handle_world_events() {
}
} break;
case eGUI::LOOT: {
auto &loot = std::any_cast<Loot&>(data);
auto inventory = $world.get<Inventory>(player.entity);
auto &item = std::any_cast<InventoryItem&>(data);
auto &inventory = $world.get<Inventory>(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:

@ -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);
}
}

@ -0,0 +1,31 @@
#pragma once
#include "lights.hpp"
#include <nlohmann/json.hpp>
#include <fmt/core.h>
namespace components {
using namespace nlohmann;
using lighting::LightSource;
struct InventoryItem {
int count;
json data;
};
struct Inventory {
int gold;
LightSource light;
std::unordered_map<std::string, InventoryItem> 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);
};
}

@ -13,6 +13,7 @@
#include "ftxui/screen/terminal.hpp" // for SetColorSupport, Color, TrueColor
#include <filesystem>
#include <fcntl.h>
#include <fmt/core.h>
using namespace ftxui;
using namespace components;
@ -33,8 +34,15 @@ void configure_world(DinkyECS::World &world, Map &game_map) {
world.set<Motion>(player.entity, {0, 0});
world.set<Combat>(player.entity, {100, 10});
world.set<Tile>(player.entity, {config.enemies["PLAYER_TILE"]["display"]});
world.set<Inventory>(player.entity, {5});
world.set<LightSource>(player.entity, {70,1.0});
world.set<Inventory>(player.entity, {5});
auto sword = world.entity();
auto pos = game_map.place_entity(1);
world.set<Position>(sword, {pos.x+1, pos.y+1});
world.set<Tile>(sword, {config.items["SWORD_RUSTY"]["display"]});
world.set<InventoryItem>(sword, {1, config.items["SWORD_RUSTY"]});
world.set<Weapon>(sword, {20});
auto enemy = world.entity();
world.set<Position>(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<Position>(gold, {game_map.place_entity(3)});
world.set<Loot>(gold, {100});
world.set<Tile>(gold, {config.items["CHEST"]["display"]});
world.set<Tile>(gold, {config.items["CHEST_SMALL"]["display"]});
auto wall_torch = world.entity();
world.set<Position>(wall_torch, {game_map.place_entity(4)});
world.set<LightSource>(wall_torch, {90,3.0f});
world.set<Tile>(wall_torch, {config.items["WALL_TORCH"]["display"]});
auto torch = world.entity();
Point at = game_map.place_entity(2);
world.set<Position>(torch, {{at.x+1, at.y+1}});
world.set<Loot>(torch, {{0}});
world.set<LightSource>(torch, {70,1.5f});
world.set<Tile>(torch, {config.items["TORCH"]["display"]});
auto sword = world.entity();
at = game_map.place_entity(1);
world.set<Position>(sword, {at.x+1, at.y+1});
world.set<Weapon>(sword, {.damage=20});
world.set<Loot>(sword, {{0}});
world.set<Tile>(sword, {config.items["SWORD"]["display"]});
}
int main(int argc, char *argv[]) {

@ -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)

@ -30,7 +30,7 @@ void save::to_file(fs::path path, DinkyECS::World &world, Map &map) {
extract<Combat>(world, save_data.combat);
extract<Motion>(world, save_data.motion);
extract<Tile>(world, save_data.tile);
extract<Inventory>(world, save_data.inventory);
// extract<Inventory>(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<Combat>(world_out, save_data.combat);
inject<Motion>(world_out, save_data.motion);
inject<Tile>(world_out, save_data.tile);
inject<Inventory>(world_out, save_data.inventory);
// inject<Inventory>(world_out, save_data.inventory);
size_t width = save_data.map.width;
size_t height = save_data.map.height;

@ -33,9 +33,9 @@ namespace save {
std::map<DinkyECS::Entity, components::Motion> motion;
std::map<DinkyECS::Entity, components::Combat> combat;
std::map<DinkyECS::Entity, components::Tile> tile;
std::map<DinkyECS::Entity, components::Inventory> inventory;
// std::map<DinkyECS::Entity, components::Inventory> 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);

@ -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.

@ -62,7 +62,7 @@ void System::init_positions(DinkyECS::World &world) {
}
});
world.query<Position, Loot>([&](const auto &ent, auto &pos, auto &loot) {
world.query<Position, InventoryItem>([&](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>(Events::GUI::COMBAT, entity, result);
} else if(world.has<Loot>(entity)) {
auto loot = world.get<Loot>(entity);
auto &loot_pos = world.get<Position>(entity);
auto &inventory = world.get<Inventory>(player.entity);
} else if(world.has<InventoryItem>(entity)) {
auto& item = world.get<InventoryItem>(entity);
auto& item_pos = world.get<Position>(entity);
auto& inventory = world.get<Inventory>(player.entity);
inventory.add(item);
if(world.has<LightSource>(entity)) {
auto &new_light = world.get<LightSource>(entity);
@ -148,15 +150,12 @@ void System::collision(DinkyECS::World &world, Player &player) {
auto &weapon = world.get<Weapon>(entity);
player_combat.damage = weapon.damage;
world.remove<Weapon>(entity);
} else {
// it's just gold
inventory.gold += loot.amount;
}
collider.remove(loot_pos.location);
collider.remove(item_pos.location);
world.remove<Tile>(entity);
world.remove<Loot>(entity);
world.send<Events::GUI>(Events::GUI::LOOT, entity, loot);
world.remove<InventoryItem>(entity);
world.send<Events::GUI>(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<Inventory>(actor);
auto& invitem = world.get<InventoryItem>(item);
inventory.add(invitem);
}

@ -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);
}

@ -0,0 +1,61 @@
#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>
#include <string>
#include "rand.hpp"
#include <nlohmann/json.hpp>
#include <fstream>
#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<InventoryItem>(sword, {1, config.items["SWORD_RUSTY"]});
world.set<Tile>(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<GameConfig>();
auto sword = add_items(world, config);
auto player = world.entity();
world.set<Inventory>(player, {});
auto &inventory = world.get<Inventory>(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);
}

@ -252,6 +252,7 @@ TEST_CASE("prototype circle algorithm", "[matrix:circle]") {
size_t height = Random::uniform<size_t>(10, 15);
int pos_mod = Random::uniform<int>(-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<size_t>(20, 22);
size_t height = Random::uniform<size_t>(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);
}
}
}

Loading…
Cancel
Save