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
|
meson compile --clean -C builddir
|
||||||
|
|
||||||
debug_test: build
|
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:
|
win_installer:
|
||||||
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
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/guecstra.hpp"
|
||||||
#include "gui/dnd_loot.hpp"
|
#include "gui/dnd_loot.hpp"
|
||||||
#include "gui/uisystems.hpp"
|
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
|
|
||||||
|
@ -43,11 +41,11 @@ namespace gui {
|
||||||
END(Event::CLOSE);
|
END(Event::CLOSE);
|
||||||
break;
|
break;
|
||||||
case LOOT_SELECT:
|
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);
|
if($grab_source) state(DNDState::LOOT_GRAB);
|
||||||
break;
|
break;
|
||||||
case INV_SELECT:
|
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);
|
if($grab_source) state(DNDState::INV_GRAB);
|
||||||
break;
|
break;
|
||||||
case MOUSE_DRAG_START:
|
case MOUSE_DRAG_START:
|
||||||
|
@ -67,11 +65,11 @@ namespace gui {
|
||||||
END(Event::CLOSE);
|
END(Event::CLOSE);
|
||||||
break;
|
break;
|
||||||
case LOOT_SELECT:
|
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);
|
if($grab_source) state(DNDState::LOOTING);
|
||||||
break;
|
break;
|
||||||
case INV_SELECT:
|
case INV_SELECT:
|
||||||
if(UISystem::loot_drop($loot_ui.$gui,
|
if(commit_drop($loot_ui.$gui,
|
||||||
$status_ui.$gui, $grab_source, data))
|
$status_ui.$gui, $grab_source, data))
|
||||||
{
|
{
|
||||||
state(DNDState::LOOTING);
|
state(DNDState::LOOTING);
|
||||||
|
@ -90,14 +88,14 @@ namespace gui {
|
||||||
END(Event::CLOSE);
|
END(Event::CLOSE);
|
||||||
break;
|
break;
|
||||||
case LOOT_SELECT:
|
case LOOT_SELECT:
|
||||||
if(UISystem::loot_drop($status_ui.$gui,
|
if(commit_drop($status_ui.$gui,
|
||||||
$loot_ui.$gui, $grab_source, data))
|
$loot_ui.$gui, $grab_source, data))
|
||||||
{
|
{
|
||||||
state(DNDState::LOOTING);
|
state(DNDState::LOOTING);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case INV_SELECT:
|
case INV_SELECT:
|
||||||
$grab_source = UISystem::loot_grab($status_ui.$gui, data);
|
$grab_source = start_grab($status_ui.$gui, data);
|
||||||
state(DNDState::LOOTING);
|
state(DNDState::LOOTING);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -128,8 +126,7 @@ namespace gui {
|
||||||
|
|
||||||
switch(ev) {
|
switch(ev) {
|
||||||
case INV_SELECT:
|
case INV_SELECT:
|
||||||
if(UISystem::loot_drop($loot_ui.$gui,
|
commit_drop($loot_ui.$gui, $status_ui.$gui, $grab_source, data);
|
||||||
$status_ui.$gui, $grab_source, data))
|
|
||||||
{
|
{
|
||||||
END(Event::CLOSE);
|
END(Event::CLOSE);
|
||||||
}
|
}
|
||||||
|
@ -149,7 +146,7 @@ namespace gui {
|
||||||
case LOOT_ITEM: {
|
case LOOT_ITEM: {
|
||||||
// NOTE: if > 1 items, go to LOOT_OPEN instead
|
// NOTE: if > 1 items, go to LOOT_OPEN instead
|
||||||
auto gui_id = $loot_ui.$gui.entity("item_0");
|
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) {
|
if($grab_source) {
|
||||||
auto& source = $loot_ui.$gui.get<guecs::GrabSource>(*$grab_source);
|
auto& source = $loot_ui.$gui.get<guecs::GrabSource>(*$grab_source);
|
||||||
|
@ -160,7 +157,7 @@ namespace gui {
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case INV_SELECT: {
|
case INV_SELECT: {
|
||||||
$grab_source = UISystem::loot_grab($status_ui.$gui, data);
|
$grab_source = start_grab($status_ui.$gui, data);
|
||||||
|
|
||||||
if($grab_source) {
|
if($grab_source) {
|
||||||
auto& source = $status_ui.$gui.get<guecs::GrabSource>(*$grab_source);
|
auto& source = $status_ui.$gui.get<guecs::GrabSource>(*$grab_source);
|
||||||
|
@ -206,7 +203,6 @@ namespace gui {
|
||||||
case MOUSE_DRAG:
|
case MOUSE_DRAG:
|
||||||
case MOUSE_MOVE: {
|
case MOUSE_MOVE: {
|
||||||
if($grab_source) {
|
if($grab_source) {
|
||||||
fmt::println("MOVING that thing");
|
|
||||||
auto& source = gui.get<guecs::GrabSource>(*$grab_source);
|
auto& source = gui.get<guecs::GrabSource>(*$grab_source);
|
||||||
source.move($window.mapPixelToCoords($router.position));
|
source.move($window.mapPixelToCoords($router.position));
|
||||||
}
|
}
|
||||||
|
@ -240,4 +236,32 @@ namespace gui {
|
||||||
$window.draw(*$grab_sprite);
|
$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 END(Event ev, std::any data={});
|
||||||
void ITEM_PICKUP(Event ev, std::any data);
|
void ITEM_PICKUP(Event ev, std::any data);
|
||||||
void INV_PICKUP(Event ev, std::any data);
|
void INV_PICKUP(Event ev, std::any data);
|
||||||
|
|
||||||
void handle_mouse(Event ev, guecs::UI& gui);
|
void handle_mouse(Event ev, guecs::UI& gui);
|
||||||
void mouse_action(bool hover);
|
void mouse_action(bool hover);
|
||||||
void render();
|
void render();
|
||||||
void open();
|
void open();
|
||||||
void close();
|
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();
|
sf::Vector2f mouse_position();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
11
gui/fsm.cpp
11
gui/fsm.cpp
|
@ -6,7 +6,6 @@
|
||||||
#include "components.hpp"
|
#include "components.hpp"
|
||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include "systems.hpp"
|
#include "systems.hpp"
|
||||||
#include "gui/uisystems.hpp"
|
|
||||||
#include "gui/fsm_events.hpp"
|
#include "gui/fsm_events.hpp"
|
||||||
#include "events.hpp"
|
#include "events.hpp"
|
||||||
#include "sound.hpp"
|
#include "sound.hpp"
|
||||||
|
@ -69,7 +68,6 @@ namespace gui {
|
||||||
|
|
||||||
run_systems();
|
run_systems();
|
||||||
|
|
||||||
this_crap_must_die();
|
|
||||||
state(State::IDLE);
|
state(State::IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,8 +459,7 @@ namespace gui {
|
||||||
dbc::check(world.has<components::InventoryItem>(entity),
|
dbc::check(world.has<components::InventoryItem>(entity),
|
||||||
"INVALID LOOT_ITEM, that entity has no InventoryItem");
|
"INVALID LOOT_ITEM, that entity has no InventoryItem");
|
||||||
dbc::log("@@@@ SENDING LOOT_ITEM");
|
dbc::log("@@@@ SENDING LOOT_ITEM");
|
||||||
auto gui_id = $loot_ui.$gui.entity("item_0");
|
$loot_ui.contents.add("item_0", entity);
|
||||||
$loot_ui.contents.insert_or_assign(gui_id, entity);
|
|
||||||
$loot_ui.update();
|
$loot_ui.update();
|
||||||
event(Event::LOOT_ITEM);
|
event(Event::LOOT_ITEM);
|
||||||
} break;
|
} break;
|
||||||
|
@ -517,10 +514,4 @@ namespace gui {
|
||||||
|
|
||||||
run_systems();
|
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 handle_world_events();
|
||||||
void next_level();
|
void next_level();
|
||||||
void debug_render();
|
void debug_render();
|
||||||
void this_crap_must_die();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,10 @@ namespace gui {
|
||||||
make_button("destroy", L"DESTROY", Events::GUI::LOOT_CLOSE);
|
make_button("destroy", L"DESTROY", Events::GUI::LOOT_CLOSE);
|
||||||
|
|
||||||
for(int i = 0; i < INV_SLOTS; i++) {
|
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,
|
$gui.set<guecs::Rectangle>(id, {THEME.PADDING,
|
||||||
THEME.TRANSPARENT, THEME.LIGHT_MID });
|
THEME.TRANSPARENT, THEME.LIGHT_MID });
|
||||||
$gui.set<guecs::Effect>(id, {0.4f, "ui_shader"});
|
$gui.set<guecs::Effect>(id, {0.4f, "ui_shader"});
|
||||||
|
@ -54,13 +57,12 @@ namespace gui {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LootUI::update() {
|
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++) {
|
for(size_t i = 0; i < INV_SLOTS; i++) {
|
||||||
auto id = $gui.entity("item_", int(i));
|
auto id = $gui.entity("item_", int(i));
|
||||||
|
auto& slot_name = $slot_to_name.at(id);
|
||||||
|
|
||||||
if(contents.contains(id)) {
|
if(contents.has(slot_name)) {
|
||||||
auto item = contents.at(id);
|
auto item = contents.get(slot_name);
|
||||||
dbc::check($level.world->has<components::Sprite>(item),
|
dbc::check($level.world->has<components::Sprite>(item),
|
||||||
"item in inventory UI doesn't exist in world. New level?");
|
"item in inventory UI doesn't exist in world. New level?");
|
||||||
auto& sprite = $level.world->get<components::Sprite>(item);
|
auto& sprite = $level.world->get<components::Sprite>(item);
|
||||||
|
@ -85,13 +87,16 @@ namespace gui {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LootUI::remove_slot(DinkyECS::Entity slot_id) {
|
void LootUI::remove_slot(DinkyECS::Entity slot_id) {
|
||||||
contents.erase(slot_id);
|
auto& name = $slot_to_name.at(slot_id);
|
||||||
|
contents.remove(name);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LootUI::place_slot(DinkyECS::Entity id, DinkyECS::Entity world_entity) {
|
bool LootUI::place_slot(guecs::Entity id, DinkyECS::Entity world_entity) {
|
||||||
if(contents.size() < INV_SLOTS && !contents.contains(id)) {
|
auto& name = $slot_to_name.at(id);
|
||||||
contents.try_emplace(id, world_entity);
|
|
||||||
|
if(!contents.has(name)) {
|
||||||
|
contents.add(name, world_entity);
|
||||||
update();
|
update();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -105,7 +110,6 @@ namespace gui {
|
||||||
|
|
||||||
void LootUI::update_level(GameLevel &level) {
|
void LootUI::update_level(GameLevel &level) {
|
||||||
$level = level;
|
$level = level;
|
||||||
contents.clear();
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <SFML/Graphics/Font.hpp>
|
#include <SFML/Graphics/Font.hpp>
|
||||||
#include <guecs/ui.hpp>
|
#include <guecs/ui.hpp>
|
||||||
#include "events.hpp"
|
#include "events.hpp"
|
||||||
|
#include "inventory.hpp"
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
class LootUI {
|
class LootUI {
|
||||||
|
@ -12,7 +13,9 @@ namespace gui {
|
||||||
bool active = false;
|
bool active = false;
|
||||||
guecs::UI $gui;
|
guecs::UI $gui;
|
||||||
GameLevel $level;
|
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);
|
LootUI(GameLevel level);
|
||||||
void init();
|
void init();
|
||||||
|
@ -22,7 +25,7 @@ namespace gui {
|
||||||
bool mouse(float x, float y, bool hover);
|
bool mouse(float x, float y, bool hover);
|
||||||
void make_button(const std::string &name, const std::wstring& label, Events::GUI event);
|
void make_button(const std::string &name, const std::wstring& label, Events::GUI event);
|
||||||
|
|
||||||
void remove_slot(DinkyECS::Entity slot_id);
|
void remove_slot(guecs::Entity slot_id);
|
||||||
bool place_slot(DinkyECS::Entity gui_id, DinkyECS::Entity world_entity);
|
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 = textures::get("ritual_crafting_area");
|
||||||
$ritual_ui.sprite->setPosition($gui.get_position());
|
$ritual_ui.sprite->setPosition($gui.get_position());
|
||||||
$ritual_ui.sprite->setTextureRect($ritual_closed_rect);
|
$ritual_ui.sprite->setTextureRect($ritual_closed_rect);
|
||||||
|
// BUG: why am I doing this twice?
|
||||||
state(State::CLOSED);
|
state(State::CLOSED);
|
||||||
$ritual_anim = animation::load("ritual_blanket");
|
$ritual_anim = animation::load("ritual_blanket");
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace gui {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SelectedItem {
|
struct SelectedItem {
|
||||||
DinkyECS::Entity slot_id;
|
guecs::Entity slot_id;
|
||||||
DinkyECS::Entity item_id;
|
DinkyECS::Entity item_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <fmt/xchar.h>
|
#include <fmt/xchar.h>
|
||||||
#include "gui/guecstra.hpp"
|
#include "gui/guecstra.hpp"
|
||||||
#include "systems.hpp"
|
#include "systems.hpp"
|
||||||
|
#include "inventory.hpp"
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
using namespace guecs;
|
using namespace guecs;
|
||||||
|
@ -27,28 +28,29 @@ namespace gui {
|
||||||
$gui.set<Background>($gui.MAIN, {$gui.$parser});
|
$gui.set<Background>($gui.MAIN, {$gui.$parser});
|
||||||
|
|
||||||
for(auto& [name, cell] : $gui.cells()) {
|
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") {
|
if(name == "character_view") {
|
||||||
auto char_view = $gui.entity(name);
|
$gui.set<Rectangle>(gui_id, {});
|
||||||
$gui.set<Rectangle>(char_view, {});
|
$gui.set<Sprite>(gui_id, {"armored_knight"});
|
||||||
$gui.set<Sprite>(char_view, {"armored_knight"});
|
|
||||||
} else {
|
} else {
|
||||||
auto button = $gui.entity(name);
|
$gui.set<Rectangle>(gui_id, {});
|
||||||
$gui.set<Rectangle>(button, {});
|
$gui.set<ActionData>(gui_id, {make_any<string>(name)});
|
||||||
$gui.set<ActionData>(button, {make_any<string>(name)});
|
|
||||||
|
|
||||||
if(name == "ritual_ui") {
|
if(name == "ritual_ui") {
|
||||||
$gui.set<Clickable>(button, {
|
$gui.set<Clickable>(gui_id, {
|
||||||
[this](auto, auto){ select_ritual(); }
|
[this](auto, auto){ select_ritual(); }
|
||||||
});
|
});
|
||||||
$gui.set<Sound>(button, {"pickup"});
|
$gui.set<Sound>(gui_id, {"pickup"});
|
||||||
} else {
|
} else {
|
||||||
$gui.set<Textual>(button, {guecs::to_wstring(name)});
|
$gui.set<Textual>(gui_id, {guecs::to_wstring(name)});
|
||||||
$gui.set<Clickable>(button, {
|
$gui.set<Clickable>(gui_id, {
|
||||||
guecs::make_action($level, Events::GUI::INV_SELECT, {button})
|
guecs::make_action($level, Events::GUI::INV_SELECT, {gui_id})
|
||||||
});
|
});
|
||||||
$gui.set<DropTarget>(button, {
|
$gui.set<DropTarget>(gui_id, {
|
||||||
.commit=[&, button](DinkyECS::Entity world_target) -> bool {
|
.commit=[&, gui_id](DinkyECS::Entity world_target) -> bool {
|
||||||
return place_slot(button, world_target);
|
return place_slot(gui_id, world_target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -57,6 +59,7 @@ namespace gui {
|
||||||
|
|
||||||
$ritual_ui.event(ritual::Event::STARTED);
|
$ritual_ui.event(ritual::Event::STARTED);
|
||||||
$gui.init();
|
$gui.init();
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StatusUI::mouse(float x, float y, bool hover) {
|
bool StatusUI::mouse(float x, float y, bool hover) {
|
||||||
|
@ -72,7 +75,17 @@ namespace gui {
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatusUI::update() {
|
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) {
|
void StatusUI::render(sf::RenderWindow &window) {
|
||||||
|
@ -87,30 +100,30 @@ namespace gui {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StatusUI::place_slot(guecs::Entity gui_id, DinkyECS::Entity world_entity) {
|
bool StatusUI::place_slot(guecs::Entity gui_id, DinkyECS::Entity world_entity) {
|
||||||
if($level.world->has<components::Sprite>(world_entity)) {
|
auto& slot_name = $slot_to_name.at(gui_id);
|
||||||
auto& sprite = $level.world->get<components::Sprite>(world_entity);
|
auto& inventory = $level.world->get_the<inventory::Model>();
|
||||||
$gui.set_init<guecs::Sprite>(gui_id, {sprite.name});
|
inventory.add(slot_name, world_entity);
|
||||||
guecs::GrabSource grabber{ world_entity,
|
update();
|
||||||
[&, gui_id]() { return remove_slot(gui_id); }};
|
return true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StatusUI::drop_item(DinkyECS::Entity item_id) {
|
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) {
|
void StatusUI::remove_slot(guecs::Entity slot_id) {
|
||||||
if(contents.contains(slot_id)) {
|
// NOTE: really the System should coordinate either dropping on the
|
||||||
contents.erase(slot_id);
|
// ground or moving from one container or another, so when loot_ui
|
||||||
$gui.remove<guecs::GrabSource>(slot_id);
|
// moves to use an ECS id to a container I can have the System
|
||||||
$gui.remove<guecs::Sprite>(slot_id);
|
// 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:
|
public:
|
||||||
guecs::UI $gui;
|
guecs::UI $gui;
|
||||||
GameLevel $level;
|
GameLevel $level;
|
||||||
std::unordered_map<guecs::Entity, DinkyECS::Entity> contents;
|
std::unordered_map<guecs::Entity, std::string> $slot_to_name;
|
||||||
ritual::UI $ritual_ui;
|
ritual::UI $ritual_ui;
|
||||||
|
|
||||||
StatusUI(GameLevel level);
|
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/overlay_ui.cpp',
|
||||||
'gui/ritual_ui.cpp',
|
'gui/ritual_ui.cpp',
|
||||||
'gui/status_ui.cpp',
|
'gui/status_ui.cpp',
|
||||||
'gui/uisystems.cpp',
|
'inventory.cpp',
|
||||||
'levelmanager.cpp',
|
'levelmanager.cpp',
|
||||||
'lights.cpp',
|
'lights.cpp',
|
||||||
'map.cpp',
|
'map.cpp',
|
||||||
|
@ -139,6 +139,7 @@ executable('runtests', sources + [
|
||||||
'tests/easings.cpp',
|
'tests/easings.cpp',
|
||||||
'tests/event_router.cpp',
|
'tests/event_router.cpp',
|
||||||
'tests/fsm.cpp',
|
'tests/fsm.cpp',
|
||||||
|
'tests/inventory.cpp',
|
||||||
'tests/levelmanager.cpp',
|
'tests/levelmanager.cpp',
|
||||||
'tests/lighting.cpp',
|
'tests/lighting.cpp',
|
||||||
'tests/loot.cpp',
|
'tests/loot.cpp',
|
||||||
|
|
15
systems.cpp
15
systems.cpp
|
@ -15,6 +15,7 @@
|
||||||
#include "battle.hpp"
|
#include "battle.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "shaders.hpp"
|
#include "shaders.hpp"
|
||||||
|
#include "inventory.hpp"
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
using namespace fmt;
|
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");
|
Config config("assets/items.json");
|
||||||
auto& item_config = config[name];
|
auto& item_config = config[name];
|
||||||
auto item_id = level.world->entity();
|
auto item_id = world.entity();
|
||||||
level.world->set<InventoryItem>(item_id, {1, item_config});
|
world.set<InventoryItem>(item_id, {1, item_config});
|
||||||
components::configure_entity(*level.world, item_id, item_config["components"]);
|
components::configure_entity(world, item_id, item_config["components"]);
|
||||||
|
|
||||||
return item_id;
|
return item_id;
|
||||||
}
|
}
|
||||||
|
@ -478,13 +479,17 @@ bool System::drop_item(GameLevel& level, DinkyECS::Entity item) {
|
||||||
|
|
||||||
auto player = world.get_the<Player>();
|
auto player = world.get_the<Player>();
|
||||||
auto player_pos = world.get<Position>(player.entity);
|
auto player_pos = world.get<Position>(player.entity);
|
||||||
|
auto& player_inv = world.get_the<inventory::Model>();
|
||||||
|
|
||||||
// doesn't compass already avoid walls?
|
// 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};
|
Position pos{it.x, it.y};
|
||||||
if(map.can_move(pos.location) && !collision.occupied(pos.location)) {
|
if(map.can_move(pos.location) && !collision.occupied(pos.location)) {
|
||||||
world.set<Position>(item, pos);
|
world.set<Position>(item, pos);
|
||||||
collision.insert(pos.location, item);
|
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, {});
|
level.world->send<Events::GUI>(Events::GUI::ENEMY_SPAWN, item, {});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace System {
|
||||||
void device(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item);
|
void device(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item);
|
||||||
void plan_motion(DinkyECS::World& world, Point move_to);
|
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);
|
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);
|
bool drop_item(GameLevel& level, DinkyECS::Entity item);
|
||||||
|
|
||||||
void enemy_ai(GameLevel &level);
|
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;
|
using matrix::Matrix;
|
||||||
|
|
||||||
shared_ptr<Map> make_map() {
|
shared_ptr<Map> make_map() {
|
||||||
|
// BUG? I mean, it's a shared_ptr so it should keep it around but....
|
||||||
LevelManager levels;
|
LevelManager levels;
|
||||||
GameLevel level = levels.current();
|
GameLevel level = levels.current();
|
||||||
return level.map;
|
return level.map;
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "rituals.hpp"
|
#include "rituals.hpp"
|
||||||
#include "maze.hpp"
|
#include "maze.hpp"
|
||||||
#include "textures.hpp"
|
#include "textures.hpp"
|
||||||
|
#include "inventory.hpp"
|
||||||
|
#include "systems.hpp"
|
||||||
|
|
||||||
using namespace fmt;
|
using namespace fmt;
|
||||||
using namespace components;
|
using namespace components;
|
||||||
|
@ -172,6 +174,11 @@ void WorldBuilder::configure_starting_items(DinkyECS::World &world) {
|
||||||
ritual::JunkItem name = el;
|
ritual::JunkItem name = el;
|
||||||
blanket.add(name);
|
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) {
|
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<Player>(player);
|
||||||
world.set_the<ritual::Belt>({});
|
world.set_the<ritual::Belt>({});
|
||||||
world.set_the<ritual::Blanket>({});
|
world.set_the<ritual::Blanket>({});
|
||||||
|
world.set_the<inventory::Model>({});
|
||||||
configure_starting_items(world);
|
configure_starting_items(world);
|
||||||
world.make_constant(player.entity);
|
world.make_constant(player.entity);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue