Big BIG refactor to make inventory use a model that's placed into the world, following a more sane MVC style.
This commit is contained in:
parent
119b3ed11d
commit
a0eff927b6
21 changed files with 270 additions and 123 deletions
2
Makefile
2
Makefile
|
@ -57,7 +57,7 @@ clean:
|
|||
meson compile --clean -C builddir
|
||||
|
||||
debug_test: build
|
||||
gdb --nx -x .gdbinit --ex run --args builddir/runtests -e
|
||||
gdb --nx -x .gdbinit --ex run --args builddir/runtests -e "[matrix:viewport]"
|
||||
|
||||
win_installer:
|
||||
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#define FSM_DEBUG 1
|
||||
#include "gui/guecstra.hpp"
|
||||
#include "gui/dnd_loot.hpp"
|
||||
#include "gui/uisystems.hpp"
|
||||
|
||||
namespace gui {
|
||||
|
||||
|
@ -43,11 +41,11 @@ namespace gui {
|
|||
END(Event::CLOSE);
|
||||
break;
|
||||
case LOOT_SELECT:
|
||||
$grab_source = UISystem::loot_grab($loot_ui.$gui, data);
|
||||
$grab_source = start_grab($loot_ui.$gui, data);
|
||||
if($grab_source) state(DNDState::LOOT_GRAB);
|
||||
break;
|
||||
case INV_SELECT:
|
||||
$grab_source = UISystem::loot_grab($status_ui.$gui, data);
|
||||
$grab_source = start_grab($status_ui.$gui, data);
|
||||
if($grab_source) state(DNDState::INV_GRAB);
|
||||
break;
|
||||
case MOUSE_DRAG_START:
|
||||
|
@ -67,11 +65,11 @@ namespace gui {
|
|||
END(Event::CLOSE);
|
||||
break;
|
||||
case LOOT_SELECT:
|
||||
$grab_source = UISystem::loot_grab($loot_ui.$gui, data);
|
||||
$grab_source = start_grab($loot_ui.$gui, data);
|
||||
if($grab_source) state(DNDState::LOOTING);
|
||||
break;
|
||||
case INV_SELECT:
|
||||
if(UISystem::loot_drop($loot_ui.$gui,
|
||||
if(commit_drop($loot_ui.$gui,
|
||||
$status_ui.$gui, $grab_source, data))
|
||||
{
|
||||
state(DNDState::LOOTING);
|
||||
|
@ -90,14 +88,14 @@ namespace gui {
|
|||
END(Event::CLOSE);
|
||||
break;
|
||||
case LOOT_SELECT:
|
||||
if(UISystem::loot_drop($status_ui.$gui,
|
||||
if(commit_drop($status_ui.$gui,
|
||||
$loot_ui.$gui, $grab_source, data))
|
||||
{
|
||||
state(DNDState::LOOTING);
|
||||
}
|
||||
break;
|
||||
case INV_SELECT:
|
||||
$grab_source = UISystem::loot_grab($status_ui.$gui, data);
|
||||
$grab_source = start_grab($status_ui.$gui, data);
|
||||
state(DNDState::LOOTING);
|
||||
break;
|
||||
default:
|
||||
|
@ -128,8 +126,7 @@ namespace gui {
|
|||
|
||||
switch(ev) {
|
||||
case INV_SELECT:
|
||||
if(UISystem::loot_drop($loot_ui.$gui,
|
||||
$status_ui.$gui, $grab_source, data))
|
||||
commit_drop($loot_ui.$gui, $status_ui.$gui, $grab_source, data);
|
||||
{
|
||||
END(Event::CLOSE);
|
||||
}
|
||||
|
@ -149,7 +146,7 @@ namespace gui {
|
|||
case LOOT_ITEM: {
|
||||
// NOTE: if > 1 items, go to LOOT_OPEN instead
|
||||
auto gui_id = $loot_ui.$gui.entity("item_0");
|
||||
$grab_source = UISystem::loot_grab($loot_ui.$gui, gui_id);
|
||||
$grab_source = start_grab($loot_ui.$gui, gui_id);
|
||||
|
||||
if($grab_source) {
|
||||
auto& source = $loot_ui.$gui.get<guecs::GrabSource>(*$grab_source);
|
||||
|
@ -160,7 +157,7 @@ namespace gui {
|
|||
}
|
||||
} break;
|
||||
case INV_SELECT: {
|
||||
$grab_source = UISystem::loot_grab($status_ui.$gui, data);
|
||||
$grab_source = start_grab($status_ui.$gui, data);
|
||||
|
||||
if($grab_source) {
|
||||
auto& source = $status_ui.$gui.get<guecs::GrabSource>(*$grab_source);
|
||||
|
@ -206,7 +203,6 @@ namespace gui {
|
|||
case MOUSE_DRAG:
|
||||
case MOUSE_MOVE: {
|
||||
if($grab_source) {
|
||||
fmt::println("MOVING that thing");
|
||||
auto& source = gui.get<guecs::GrabSource>(*$grab_source);
|
||||
source.move($window.mapPixelToCoords($router.position));
|
||||
}
|
||||
|
@ -240,4 +236,32 @@ namespace gui {
|
|||
$window.draw(*$grab_sprite);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<guecs::Entity> DNDLoot::start_grab(guecs::UI& gui, std::any data) {
|
||||
auto gui_id = std::any_cast<guecs::Entity>(data);
|
||||
|
||||
if(auto source = gui.get_if<guecs::GrabSource>(gui_id)) {
|
||||
source->grab();
|
||||
return gui_id;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool DNDLoot::commit_drop(guecs::UI& source, guecs::UI& target,
|
||||
std::optional<guecs::Entity> source_id, std::any data)
|
||||
{
|
||||
if(!source_id) return false;
|
||||
auto target_id = std::any_cast<guecs::Entity>(data);
|
||||
|
||||
auto& drop = target.get<guecs::DropTarget>(target_id);
|
||||
auto& grab = source.get<guecs::GrabSource>(*source_id);
|
||||
|
||||
if(drop.commit(grab.world_entity)) {
|
||||
grab.commit();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,11 +38,17 @@ namespace gui {
|
|||
void END(Event ev, std::any data={});
|
||||
void ITEM_PICKUP(Event ev, std::any data);
|
||||
void INV_PICKUP(Event ev, std::any data);
|
||||
|
||||
void handle_mouse(Event ev, guecs::UI& gui);
|
||||
void mouse_action(bool hover);
|
||||
void render();
|
||||
void open();
|
||||
void close();
|
||||
|
||||
std::optional<guecs::Entity> start_grab(guecs::UI& gui, std::any data);
|
||||
bool commit_drop(guecs::UI& source, guecs::UI& target,
|
||||
std::optional<guecs::Entity> source_id, std::any data);
|
||||
|
||||
sf::Vector2f mouse_position();
|
||||
};
|
||||
}
|
||||
|
|
11
gui/fsm.cpp
11
gui/fsm.cpp
|
@ -6,7 +6,6 @@
|
|||
#include "components.hpp"
|
||||
#include <numbers>
|
||||
#include "systems.hpp"
|
||||
#include "gui/uisystems.hpp"
|
||||
#include "gui/fsm_events.hpp"
|
||||
#include "events.hpp"
|
||||
#include "sound.hpp"
|
||||
|
@ -69,7 +68,6 @@ namespace gui {
|
|||
|
||||
run_systems();
|
||||
|
||||
this_crap_must_die();
|
||||
state(State::IDLE);
|
||||
}
|
||||
|
||||
|
@ -461,8 +459,7 @@ namespace gui {
|
|||
dbc::check(world.has<components::InventoryItem>(entity),
|
||||
"INVALID LOOT_ITEM, that entity has no InventoryItem");
|
||||
dbc::log("@@@@ SENDING LOOT_ITEM");
|
||||
auto gui_id = $loot_ui.$gui.entity("item_0");
|
||||
$loot_ui.contents.insert_or_assign(gui_id, entity);
|
||||
$loot_ui.contents.add("item_0", entity);
|
||||
$loot_ui.update();
|
||||
event(Event::LOOT_ITEM);
|
||||
} break;
|
||||
|
@ -517,10 +514,4 @@ namespace gui {
|
|||
|
||||
run_systems();
|
||||
}
|
||||
|
||||
void FSM::this_crap_must_die() {
|
||||
auto torch_id = System::spawn_item($level, "TORCH_BAD");
|
||||
auto hand_l = $status_ui.$gui.entity("hand_l");
|
||||
$status_ui.place_slot(hand_l, torch_id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,5 @@ namespace gui {
|
|||
void handle_world_events();
|
||||
void next_level();
|
||||
void debug_render();
|
||||
void this_crap_must_die();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -40,7 +40,10 @@ namespace gui {
|
|||
make_button("destroy", L"DESTROY", Events::GUI::LOOT_CLOSE);
|
||||
|
||||
for(int i = 0; i < INV_SLOTS; i++) {
|
||||
auto id = $gui.entity("item_", i);
|
||||
auto name = fmt::format("item_{}", i);
|
||||
auto id = $gui.entity(name);
|
||||
$slot_to_name.insert_or_assign(id, name);
|
||||
|
||||
$gui.set<guecs::Rectangle>(id, {THEME.PADDING,
|
||||
THEME.TRANSPARENT, THEME.LIGHT_MID });
|
||||
$gui.set<guecs::Effect>(id, {0.4f, "ui_shader"});
|
||||
|
@ -54,13 +57,12 @@ namespace gui {
|
|||
}
|
||||
|
||||
void LootUI::update() {
|
||||
dbc::check(contents.size() < INV_SLOTS, "too many items in loot contents, must be < 16");
|
||||
|
||||
for(size_t i = 0; i < INV_SLOTS; i++) {
|
||||
auto id = $gui.entity("item_", int(i));
|
||||
auto& slot_name = $slot_to_name.at(id);
|
||||
|
||||
if(contents.contains(id)) {
|
||||
auto item = contents.at(id);
|
||||
if(contents.has(slot_name)) {
|
||||
auto item = contents.get(slot_name);
|
||||
dbc::check($level.world->has<components::Sprite>(item),
|
||||
"item in inventory UI doesn't exist in world. New level?");
|
||||
auto& sprite = $level.world->get<components::Sprite>(item);
|
||||
|
@ -85,13 +87,16 @@ namespace gui {
|
|||
}
|
||||
|
||||
void LootUI::remove_slot(DinkyECS::Entity slot_id) {
|
||||
contents.erase(slot_id);
|
||||
auto& name = $slot_to_name.at(slot_id);
|
||||
contents.remove(name);
|
||||
update();
|
||||
}
|
||||
|
||||
bool LootUI::place_slot(DinkyECS::Entity id, DinkyECS::Entity world_entity) {
|
||||
if(contents.size() < INV_SLOTS && !contents.contains(id)) {
|
||||
contents.try_emplace(id, world_entity);
|
||||
bool LootUI::place_slot(guecs::Entity id, DinkyECS::Entity world_entity) {
|
||||
auto& name = $slot_to_name.at(id);
|
||||
|
||||
if(!contents.has(name)) {
|
||||
contents.add(name, world_entity);
|
||||
update();
|
||||
return true;
|
||||
} else {
|
||||
|
@ -105,7 +110,6 @@ namespace gui {
|
|||
|
||||
void LootUI::update_level(GameLevel &level) {
|
||||
$level = level;
|
||||
contents.clear();
|
||||
init();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <SFML/Graphics/Font.hpp>
|
||||
#include <guecs/ui.hpp>
|
||||
#include "events.hpp"
|
||||
#include "inventory.hpp"
|
||||
|
||||
namespace gui {
|
||||
class LootUI {
|
||||
|
@ -12,7 +13,9 @@ namespace gui {
|
|||
bool active = false;
|
||||
guecs::UI $gui;
|
||||
GameLevel $level;
|
||||
std::unordered_map<DinkyECS::Entity, DinkyECS::Entity> contents;
|
||||
std::unordered_map<guecs::Entity, std::string> $slot_to_name;
|
||||
// NOTE: this should then become just an ECS id for a container
|
||||
inventory::Model contents;
|
||||
|
||||
LootUI(GameLevel level);
|
||||
void init();
|
||||
|
@ -22,7 +25,7 @@ namespace gui {
|
|||
bool mouse(float x, float y, bool hover);
|
||||
void make_button(const std::string &name, const std::wstring& label, Events::GUI event);
|
||||
|
||||
void remove_slot(DinkyECS::Entity slot_id);
|
||||
bool place_slot(DinkyECS::Entity gui_id, DinkyECS::Entity world_entity);
|
||||
void remove_slot(guecs::Entity slot_id);
|
||||
bool place_slot(guecs::Entity gui_id, DinkyECS::Entity world_entity);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace gui {
|
|||
$ritual_ui = textures::get("ritual_crafting_area");
|
||||
$ritual_ui.sprite->setPosition($gui.get_position());
|
||||
$ritual_ui.sprite->setTextureRect($ritual_closed_rect);
|
||||
// BUG: why am I doing this twice?
|
||||
state(State::CLOSED);
|
||||
$ritual_anim = animation::load("ritual_blanket");
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace gui {
|
|||
};
|
||||
|
||||
struct SelectedItem {
|
||||
DinkyECS::Entity slot_id;
|
||||
guecs::Entity slot_id;
|
||||
DinkyECS::Entity item_id;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <fmt/xchar.h>
|
||||
#include "gui/guecstra.hpp"
|
||||
#include "systems.hpp"
|
||||
#include "inventory.hpp"
|
||||
|
||||
namespace gui {
|
||||
using namespace guecs;
|
||||
|
@ -27,28 +28,29 @@ namespace gui {
|
|||
$gui.set<Background>($gui.MAIN, {$gui.$parser});
|
||||
|
||||
for(auto& [name, cell] : $gui.cells()) {
|
||||
auto gui_id = $gui.entity(name);
|
||||
$slot_to_name.insert_or_assign(gui_id, name);
|
||||
|
||||
if(name == "character_view") {
|
||||
auto char_view = $gui.entity(name);
|
||||
$gui.set<Rectangle>(char_view, {});
|
||||
$gui.set<Sprite>(char_view, {"armored_knight"});
|
||||
$gui.set<Rectangle>(gui_id, {});
|
||||
$gui.set<Sprite>(gui_id, {"armored_knight"});
|
||||
} else {
|
||||
auto button = $gui.entity(name);
|
||||
$gui.set<Rectangle>(button, {});
|
||||
$gui.set<ActionData>(button, {make_any<string>(name)});
|
||||
$gui.set<Rectangle>(gui_id, {});
|
||||
$gui.set<ActionData>(gui_id, {make_any<string>(name)});
|
||||
|
||||
if(name == "ritual_ui") {
|
||||
$gui.set<Clickable>(button, {
|
||||
$gui.set<Clickable>(gui_id, {
|
||||
[this](auto, auto){ select_ritual(); }
|
||||
});
|
||||
$gui.set<Sound>(button, {"pickup"});
|
||||
$gui.set<Sound>(gui_id, {"pickup"});
|
||||
} else {
|
||||
$gui.set<Textual>(button, {guecs::to_wstring(name)});
|
||||
$gui.set<Clickable>(button, {
|
||||
guecs::make_action($level, Events::GUI::INV_SELECT, {button})
|
||||
$gui.set<Textual>(gui_id, {guecs::to_wstring(name)});
|
||||
$gui.set<Clickable>(gui_id, {
|
||||
guecs::make_action($level, Events::GUI::INV_SELECT, {gui_id})
|
||||
});
|
||||
$gui.set<DropTarget>(button, {
|
||||
.commit=[&, button](DinkyECS::Entity world_target) -> bool {
|
||||
return place_slot(button, world_target);
|
||||
$gui.set<DropTarget>(gui_id, {
|
||||
.commit=[&, gui_id](DinkyECS::Entity world_target) -> bool {
|
||||
return place_slot(gui_id, world_target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -57,6 +59,7 @@ namespace gui {
|
|||
|
||||
$ritual_ui.event(ritual::Event::STARTED);
|
||||
$gui.init();
|
||||
update();
|
||||
}
|
||||
|
||||
bool StatusUI::mouse(float x, float y, bool hover) {
|
||||
|
@ -72,7 +75,17 @@ namespace gui {
|
|||
}
|
||||
|
||||
void StatusUI::update() {
|
||||
dbc::log("REWRITE ME!");
|
||||
auto& inventory = $level.world->get_the<inventory::Model>();
|
||||
for(auto& [slot, world_entity] : inventory.by_slot) {
|
||||
auto gui_id = $gui.entity(slot);
|
||||
|
||||
auto& sprite = $level.world->get<components::Sprite>(world_entity);
|
||||
$gui.set_init<guecs::Sprite>(gui_id, {sprite.name});
|
||||
guecs::GrabSource grabber{ world_entity,
|
||||
[&, gui_id]() { return remove_slot(gui_id); }};
|
||||
grabber.setSprite($gui, gui_id);
|
||||
$gui.set<guecs::GrabSource>(gui_id, grabber);
|
||||
}
|
||||
}
|
||||
|
||||
void StatusUI::render(sf::RenderWindow &window) {
|
||||
|
@ -87,30 +100,30 @@ namespace gui {
|
|||
}
|
||||
|
||||
bool StatusUI::place_slot(guecs::Entity gui_id, DinkyECS::Entity world_entity) {
|
||||
if($level.world->has<components::Sprite>(world_entity)) {
|
||||
auto& sprite = $level.world->get<components::Sprite>(world_entity);
|
||||
$gui.set_init<guecs::Sprite>(gui_id, {sprite.name});
|
||||
guecs::GrabSource grabber{ world_entity,
|
||||
[&, gui_id]() { return remove_slot(gui_id); }};
|
||||
grabber.setSprite($gui, gui_id);
|
||||
$gui.set<guecs::GrabSource>(gui_id, grabber);
|
||||
|
||||
contents.insert_or_assign(gui_id, world_entity);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
auto& slot_name = $slot_to_name.at(gui_id);
|
||||
auto& inventory = $level.world->get_the<inventory::Model>();
|
||||
inventory.add(slot_name, world_entity);
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StatusUI::drop_item(DinkyECS::Entity item_id) {
|
||||
return System::drop_item($level, item_id);
|
||||
bool dropped = System::drop_item($level, item_id);
|
||||
if(dropped) update();
|
||||
return dropped;
|
||||
}
|
||||
|
||||
// NOTE: do I need this or how does it relate to drop_item?
|
||||
void StatusUI::remove_slot(guecs::Entity slot_id) {
|
||||
if(contents.contains(slot_id)) {
|
||||
contents.erase(slot_id);
|
||||
$gui.remove<guecs::GrabSource>(slot_id);
|
||||
$gui.remove<guecs::Sprite>(slot_id);
|
||||
}
|
||||
// NOTE: really the System should coordinate either dropping on the
|
||||
// ground or moving from one container or another, so when loot_ui
|
||||
// moves to use an ECS id to a container I can have the System
|
||||
// do it.
|
||||
auto& slot_name = $slot_to_name.at(slot_id);
|
||||
auto& inventory = $level.world->get_the<inventory::Model>();
|
||||
inventory.remove(slot_name);
|
||||
|
||||
$gui.remove<guecs::GrabSource>(slot_id);
|
||||
$gui.remove<guecs::Sprite>(slot_id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace gui {
|
|||
public:
|
||||
guecs::UI $gui;
|
||||
GameLevel $level;
|
||||
std::unordered_map<guecs::Entity, DinkyECS::Entity> contents;
|
||||
std::unordered_map<guecs::Entity, std::string> $slot_to_name;
|
||||
ritual::UI $ritual_ui;
|
||||
|
||||
StatusUI(GameLevel level);
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
#include "gui/uisystems.hpp"
|
||||
#include "gui/guecstra.hpp"
|
||||
|
||||
namespace UISystem {
|
||||
std::optional<guecs::Entity> loot_grab(guecs::UI& gui, std::any data) {
|
||||
auto gui_id = std::any_cast<guecs::Entity>(data);
|
||||
|
||||
if(auto source = gui.get_if<guecs::GrabSource>(gui_id)) {
|
||||
source->grab();
|
||||
return gui_id;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool loot_drop(guecs::UI& source, guecs::UI& target,
|
||||
std::optional<guecs::Entity> source_id, std::any data)
|
||||
{
|
||||
if(!source_id) return false;
|
||||
auto target_id = std::any_cast<guecs::Entity>(data);
|
||||
|
||||
auto& drop = target.get<guecs::DropTarget>(target_id);
|
||||
auto& grab = source.get<guecs::GrabSource>(*source_id);
|
||||
|
||||
if(drop.commit(grab.world_entity)) {
|
||||
grab.commit();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
#pragma once
|
||||
#include <guecs/ui.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace UISystem {
|
||||
std::optional<guecs::Entity> loot_grab(guecs::UI& gui, std::any data);
|
||||
|
||||
bool loot_drop(guecs::UI& source, guecs::UI& target,
|
||||
std::optional<guecs::Entity> source_id, std::any data);
|
||||
}
|
65
inventory.cpp
Normal file
65
inventory.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include "inventory.hpp"
|
||||
|
||||
namespace inventory {
|
||||
void Model::add(const Slot &in_slot, DinkyECS::Entity ent) {
|
||||
if(by_entity.contains(ent)) {
|
||||
// doing it this way so we can get the offending entity, otherwise it
|
||||
// crashes on the by_entity.at when this test _passes_
|
||||
dbc::sentinel(fmt::format("failed to add item to inventory, entity {} is already in the inventory slot {}", ent, by_entity.at(ent)));
|
||||
}
|
||||
|
||||
by_entity.insert_or_assign(ent, in_slot);
|
||||
by_slot.insert_or_assign(in_slot, ent);
|
||||
|
||||
invariant();
|
||||
}
|
||||
|
||||
Slot& Model::get(DinkyECS::Entity ent) {
|
||||
return by_entity.at(ent);
|
||||
}
|
||||
|
||||
DinkyECS::Entity Model::get(const Slot& slot) {
|
||||
return by_slot.at(slot);
|
||||
}
|
||||
|
||||
bool Model::has(DinkyECS::Entity ent) {
|
||||
return by_entity.contains(ent);
|
||||
}
|
||||
|
||||
bool Model::has(const Slot& slot) {
|
||||
return by_slot.contains(slot);
|
||||
}
|
||||
|
||||
void Model::remove(const Slot& slot, DinkyECS::Entity ent) {
|
||||
by_entity.erase(ent);
|
||||
by_slot.erase(slot);
|
||||
invariant();
|
||||
}
|
||||
|
||||
void Model::remove(DinkyECS::Entity ent) {
|
||||
auto& slot = by_entity.at(ent);
|
||||
remove(slot, ent);
|
||||
invariant();
|
||||
}
|
||||
|
||||
void Model::remove(const Slot& slot) {
|
||||
auto ent = by_slot.at(slot);
|
||||
remove(slot, ent);
|
||||
invariant();
|
||||
}
|
||||
|
||||
void Model::invariant() {
|
||||
dbc::check(by_slot.size() == by_entity.size(), "by_slot and by_entity have differing sizes");
|
||||
// std::unordered_map<DinkyECS::Entity, Slot> find_dupes;
|
||||
|
||||
for(auto& [slot, ent] : by_slot) {
|
||||
dbc::check(by_entity.at(ent) == slot,
|
||||
fmt::format("mismatched slot {} in by_slot doesn't match entity {}", slot, ent));
|
||||
}
|
||||
|
||||
for(auto& [ent, slot] : by_entity) {
|
||||
dbc::check(by_slot.at(slot) == ent,
|
||||
fmt::format("mismatched entity {} in by_entity doesn't match entity {}", ent, slot));
|
||||
}
|
||||
}
|
||||
}
|
21
inventory.hpp
Normal file
21
inventory.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "dinkyecs.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace inventory {
|
||||
using Slot = std::string;
|
||||
|
||||
struct Model {
|
||||
std::unordered_map<Slot, DinkyECS::Entity> by_slot;
|
||||
std::unordered_map<DinkyECS::Entity, Slot> by_entity;
|
||||
|
||||
void add(const Slot &in_slot, DinkyECS::Entity ent);
|
||||
Slot& get(DinkyECS::Entity ent);
|
||||
DinkyECS::Entity get(const Slot& slot);
|
||||
bool has(DinkyECS::Entity ent);
|
||||
bool has(const Slot& slot);
|
||||
void remove(const Slot& slot, DinkyECS::Entity ent);
|
||||
void remove(DinkyECS::Entity ent);
|
||||
void remove(const Slot& slot);
|
||||
void invariant();
|
||||
};
|
||||
}
|
|
@ -106,7 +106,7 @@ sources = [
|
|||
'gui/overlay_ui.cpp',
|
||||
'gui/ritual_ui.cpp',
|
||||
'gui/status_ui.cpp',
|
||||
'gui/uisystems.cpp',
|
||||
'inventory.cpp',
|
||||
'levelmanager.cpp',
|
||||
'lights.cpp',
|
||||
'map.cpp',
|
||||
|
@ -139,6 +139,7 @@ executable('runtests', sources + [
|
|||
'tests/easings.cpp',
|
||||
'tests/event_router.cpp',
|
||||
'tests/fsm.cpp',
|
||||
'tests/inventory.cpp',
|
||||
'tests/levelmanager.cpp',
|
||||
'tests/lighting.cpp',
|
||||
'tests/loot.cpp',
|
||||
|
|
15
systems.cpp
15
systems.cpp
|
@ -15,6 +15,7 @@
|
|||
#include "battle.hpp"
|
||||
#include <iostream>
|
||||
#include "shaders.hpp"
|
||||
#include "inventory.hpp"
|
||||
|
||||
using std::string;
|
||||
using namespace fmt;
|
||||
|
@ -461,12 +462,12 @@ std::shared_ptr<sf::Shader> System::sprite_effect(GameLevel &level, DinkyECS::En
|
|||
}
|
||||
}
|
||||
|
||||
DinkyECS::Entity System::spawn_item(GameLevel& level, const std::string& name) {
|
||||
DinkyECS::Entity System::spawn_item(DinkyECS::World& world, const std::string& name) {
|
||||
Config config("assets/items.json");
|
||||
auto& item_config = config[name];
|
||||
auto item_id = level.world->entity();
|
||||
level.world->set<InventoryItem>(item_id, {1, item_config});
|
||||
components::configure_entity(*level.world, item_id, item_config["components"]);
|
||||
auto item_id = world.entity();
|
||||
world.set<InventoryItem>(item_id, {1, item_config});
|
||||
components::configure_entity(world, item_id, item_config["components"]);
|
||||
|
||||
return item_id;
|
||||
}
|
||||
|
@ -478,13 +479,17 @@ bool System::drop_item(GameLevel& level, DinkyECS::Entity item) {
|
|||
|
||||
auto player = world.get_the<Player>();
|
||||
auto player_pos = world.get<Position>(player.entity);
|
||||
auto& player_inv = world.get_the<inventory::Model>();
|
||||
|
||||
// doesn't compass already avoid walls?
|
||||
for(matrix::box it{map.walls(), player_pos.location.x, player_pos.location.y, 1}; it.next();) {
|
||||
for(matrix::box it{map.walls(), player_pos.location.x, player_pos.location.y, 1}; it.next();)
|
||||
{
|
||||
Position pos{it.x, it.y};
|
||||
if(map.can_move(pos.location) && !collision.occupied(pos.location)) {
|
||||
world.set<Position>(item, pos);
|
||||
collision.insert(pos.location, item);
|
||||
// BUG: really there should be another system that handles loot->inv moves
|
||||
if(player_inv.has(item)) player_inv.remove(item);
|
||||
level.world->send<Events::GUI>(Events::GUI::ENEMY_SPAWN, item, {});
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace System {
|
|||
void device(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item);
|
||||
void plan_motion(DinkyECS::World& world, Point move_to);
|
||||
std::wstring draw_map(GameLevel level, size_t view_x, size_t view_y, int compass_dir);
|
||||
DinkyECS::Entity spawn_item(GameLevel& level, const std::string& name);
|
||||
DinkyECS::Entity spawn_item(DinkyECS::World& world, const std::string& name);
|
||||
bool drop_item(GameLevel& level, DinkyECS::Entity item);
|
||||
|
||||
void enemy_ai(GameLevel &level);
|
||||
|
|
47
tests/inventory.cpp
Normal file
47
tests/inventory.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <string>
|
||||
#include "inventory.hpp"
|
||||
|
||||
using namespace fmt;
|
||||
|
||||
TEST_CASE("base test", "[inventory]") {
|
||||
inventory::Model inv;
|
||||
DinkyECS::Entity test_ent = 1;
|
||||
|
||||
inv.add("hand_l", test_ent);
|
||||
inv.invariant();
|
||||
|
||||
auto& slot = inv.get(test_ent);
|
||||
REQUIRE(slot == "hand_l");
|
||||
|
||||
auto ent = inv.get(slot);
|
||||
REQUIRE(ent == test_ent);
|
||||
|
||||
REQUIRE(inv.has(ent));
|
||||
REQUIRE(inv.has(slot));
|
||||
|
||||
// test base remove
|
||||
inv.remove(slot, ent);
|
||||
REQUIRE(!inv.has(slot));
|
||||
REQUIRE(!inv.has(ent));
|
||||
|
||||
// test remove just by slot
|
||||
inv.add("hand_r", test_ent);
|
||||
REQUIRE(inv.has("hand_r"));
|
||||
REQUIRE(inv.has(test_ent));
|
||||
inv.invariant();
|
||||
|
||||
inv.remove("hand_r");
|
||||
REQUIRE(!inv.has("hand_r"));
|
||||
REQUIRE(!inv.has(test_ent));
|
||||
|
||||
// test remove just by entity
|
||||
inv.add("pocket_l", test_ent);
|
||||
REQUIRE(inv.has("pocket_l"));
|
||||
REQUIRE(inv.has(test_ent));
|
||||
inv.invariant();
|
||||
inv.remove(test_ent);
|
||||
REQUIRE(!inv.has("pocket_l"));
|
||||
REQUIRE(!inv.has(test_ent));
|
||||
}
|
|
@ -14,6 +14,7 @@ using std::string;
|
|||
using matrix::Matrix;
|
||||
|
||||
shared_ptr<Map> make_map() {
|
||||
// BUG? I mean, it's a shared_ptr so it should keep it around but....
|
||||
LevelManager levels;
|
||||
GameLevel level = levels.current();
|
||||
return level.map;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include "rituals.hpp"
|
||||
#include "maze.hpp"
|
||||
#include "textures.hpp"
|
||||
#include "inventory.hpp"
|
||||
#include "systems.hpp"
|
||||
|
||||
using namespace fmt;
|
||||
using namespace components;
|
||||
|
@ -172,6 +174,11 @@ void WorldBuilder::configure_starting_items(DinkyECS::World &world) {
|
|||
ritual::JunkItem name = el;
|
||||
blanket.add(name);
|
||||
};
|
||||
|
||||
auto torch_id = System::spawn_item(world, "TORCH_BAD");
|
||||
|
||||
auto &inventory = world.get_the<inventory::Model>();
|
||||
inventory.add("hand_r", torch_id);
|
||||
}
|
||||
|
||||
void WorldBuilder::place_entities(DinkyECS::World &world) {
|
||||
|
@ -202,6 +209,7 @@ void WorldBuilder::place_entities(DinkyECS::World &world) {
|
|||
world.set_the<Player>(player);
|
||||
world.set_the<ritual::Belt>({});
|
||||
world.set_the<ritual::Blanket>({});
|
||||
world.set_the<inventory::Model>({});
|
||||
configure_starting_items(world);
|
||||
world.make_constant(player.entity);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue