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:
Zed A. Shaw 2025-06-20 13:17:12 -04:00
parent 119b3ed11d
commit a0eff927b6
21 changed files with 270 additions and 123 deletions

View file

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

View file

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

View file

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

View file

@ -79,6 +79,5 @@ namespace gui {
void handle_world_events();
void next_level();
void debug_render();
void this_crap_must_die();
};
}

View file

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

View file

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

View file

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

View file

@ -27,7 +27,7 @@ namespace gui {
};
struct SelectedItem {
DinkyECS::Entity slot_id;
guecs::Entity slot_id;
DinkyECS::Entity item_id;
};

View file

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

View file

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

View file

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

View file

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