From 6269d10807ac7c06f2be7d15c6e5ced1c123abcc Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" <zed.shaw@gmail.com> Date: Thu, 1 May 2025 14:40:24 -0400 Subject: [PATCH] The ritual UI is now a lot better using a FSM to control everything. Probably one more session to work out the remaining functionality. --- constants.hpp | 1 + guecs.cpp | 9 +++ guecs.hpp | 1 + ritual_ui.cpp | 161 ++++++++++++++++++++++++++++++++++++++++++++------ ritual_ui.hpp | 32 +++++++--- rituals.cpp | 4 ++ rituals.hpp | 1 + 7 files changed, 184 insertions(+), 25 deletions(-) diff --git a/constants.hpp b/constants.hpp index 30d62a6..a5db392 100644 --- a/constants.hpp +++ b/constants.hpp @@ -4,6 +4,7 @@ #include "color.hpp" #include <array> +constexpr const int INV_SLOTS=24; constexpr const int TEXTURE_WIDTH=256; constexpr const int TEXTURE_HEIGHT=256; constexpr const int RAY_VIEW_WIDTH=900; diff --git a/guecs.cpp b/guecs.cpp index 47b8af8..57113df 100644 --- a/guecs.cpp +++ b/guecs.cpp @@ -27,6 +27,15 @@ namespace guecs { text->setString(content); } + void Sprite::update(const std::string& new_name) { + if(new_name != name) { + name = new_name; + auto sprite_texture = textures::get(name); + sprite->setTexture(*sprite_texture.texture); + sprite->setTextureRect(sprite_texture.sprite->getTextureRect()); + } + } + void Sprite::init(lel::Cell &cell) { auto sprite_texture = textures::get(name); diff --git a/guecs.hpp b/guecs.hpp index 679f127..ee5f568 100644 --- a/guecs.hpp +++ b/guecs.hpp @@ -55,6 +55,7 @@ namespace guecs { std::shared_ptr<sf::Texture> texture = nullptr; void init(lel::Cell &cell); + void update(const std::string& new_name); }; struct Rectangle { diff --git a/ritual_ui.cpp b/ritual_ui.cpp index 1df238a..b0d635a 100644 --- a/ritual_ui.cpp +++ b/ritual_ui.cpp @@ -11,10 +11,11 @@ namespace gui { using namespace guecs; using std::any, std::any_cast, std::string, std::make_any; - void UI::event(Event ev) { + void UI::event(Event ev, std::any data) { switch($state) { FSM_STATE(State, START, ev); - FSM_STATE(State, OPENED, ev); + FSM_STATE(State, OPENED, ev, data); + FSM_STATE(State, CRAFTING, ev, data); FSM_STATE(State, CLOSED, ev); FSM_STATE(State, OPENING, ev); FSM_STATE(State, CLOSING, ev); @@ -28,17 +29,16 @@ namespace gui { state(State::CLOSED); $ritual_anim = animation::load("ritual_blanket"); - for(auto& [name, cell] : $gui.cells()) { - auto button = $gui.entity(name); - $gui.set<Rectangle>(button, {GUECS_PADDING, {50, 50, 50, 150}}); - $gui.set<Clickable>(button, { - [](auto ent, auto) { fmt::println("clicked {}", ent); } - }); - } - auto open_close_toggle = $gui.entity("ritual_ui"); $gui.set<Clickable>(open_close_toggle, { - [&](auto, auto){ event(Event::TOGGLE); } + [&](auto, auto){ event(Event::TOGGLE); } + }); + + auto combine = $gui.entity("combine"); + $gui.set<Rectangle>(combine, {}); + $gui.set<Label>(combine, {L"combine"}); + $gui.set<Clickable>(combine, { + [&](auto, auto){ event(Event::COMBINE); } }); $gui.init(); @@ -46,15 +46,99 @@ namespace gui { state(State::CLOSED); } - void UI::OPENED(Event ev) { + void UI::OPENED(Event ev, std::any data) { + if(ev == Event::TOGGLE) { + clear_blanket(); + state(State::CLOSING); + } else if(ev == Event::SELECT) { + // do this before transitioning + $craft_state = $ritual_engine.start(); + state(State::CRAFTING); + UI::CRAFTING(ev, data); + } + } + + void UI::CRAFTING(Event ev, std::any data) { if(ev == Event::TOGGLE) { + clear_blanket(); state(State::CLOSING); + } else if(ev == Event::COMBINE) { + if($craft_state.is_combined()) { + auto ritual = $ritual_engine.finalize($craft_state); + auto& belt = $level.world->get_the<::ritual::Belt>(); + belt.equip(belt.next(), ritual); + $level.world->send<Events::GUI>(Events::GUI::NEW_RITUAL, $level.player, {}); + clear_craft_result(); + $blanket.reset(); + // BUG: need a way to clear selections + load_blanket(); + $craft_state.reset(); + state(State::OPENED); + } + } else if(ev == Event::SELECT) { + dbc::check(data.has_value(), "OPENED state given SELECT with no data"); + auto pair = std::any_cast<SelectedItem>(data); + select_item(pair); + + if($blanket.no_selections()) { + $craft_state.reset(); + clear_craft_result(); + state(State::OPENED); + } else { + run_crafting_engine(); + + if(!$craft_state.is_combined()) { + show_craft_failure(); + } else { + show_craft_result(); + } + } + } + } + + void UI::run_crafting_engine() { + $craft_state.reset(); + + for(auto [item_id, setting] : $blanket.selected) { + auto& item = $blanket.get(item_id); + $ritual_engine.load_junk($craft_state, item); } + + $ritual_engine.plan($craft_state); + } + + void UI::show_craft_result() { + using enum ::ritual::Element; + auto ritual = $ritual_engine.finalize($craft_state); + + switch(ritual.element) { + case FIRE: + $gui.show_sprite("result_image", "broken_yoyo-64"); + break; + case LIGHTNING: + $gui.show_sprite("result_image", "pocket_watch-64"); + break; + default: + $gui.show_sprite("result_image", "severed_finger-64"); + } + + $gui.show_label("result_text", L"CRAFTING"); + } + + void UI::clear_craft_result() { + $gui.close<Label>("result_text"); + $gui.close<Sprite>("result_image"); + } + + void UI::show_craft_failure() { + $gui.close<Sprite>("result_image"); + $gui.show_label("result_text", L"FAILED!"); } void UI::CLOSED(Event ev) { if(ev == Event::TOGGLE) { $ritual_anim.play(); + load_blanket(); state(State::OPENING); } } @@ -75,7 +159,8 @@ namespace gui { } UI::UI(GameLevel level) : - $level(level) + $level(level), + $blanket($level.world->get_the<::ritual::Blanket>()) { $gui.position(STATUS_UI_X, STATUS_UI_Y, STATUS_UI_WIDTH, STATUS_UI_HEIGHT); $gui.layout( @@ -84,12 +169,12 @@ namespace gui { "[inv_slot4 | inv_slot5 | inv_slot6| inv_slot7]" "[inv_slot8 | inv_slot9 | inv_slot10| inv_slot11]" "[inv_slot12 | inv_slot13 | inv_slot14| inv_slot15]" + "[inv_slot16 | inv_slot17 | inv_slot18| inv_slot19]" + "[inv_slot20 | inv_slot21 | inv_slot22| inv_slot23]" "[reset |*%(200,400)result_text|_]" "[*%(100,200)result_image|_ |_]" "[_|_|_]" "[combine|_|_]" - "[_|craft0|craft1|craft2|craft3|_]" - "[_|craft4|craft5|craft6|craft7|_]" "[ ritual_ui ]"); } @@ -106,9 +191,51 @@ namespace gui { window.draw(*$ritual_ui.sprite); - if(in_state(State::OPENED)) { + if(in_state(State::OPENED) || in_state(State::CRAFTING)) { $gui.render(window); - $gui.debug_layout(window); + // $gui.debug_layout(window); + } + } + + void UI::clear_blanket() { + for(int i = 0; i < INV_SLOTS; i++) { + auto slot_id = $gui.entity("inv_slot", i); + + if($gui.has<Sprite>(slot_id)) { + $gui.remove<Sprite>(slot_id); + $gui.remove<Clickable>(slot_id); + } + } + + $blanket.reset(); + } + + void UI::select_item(SelectedItem pair) { + auto& sprite = $gui.get<Sprite>(pair.slot_id); + + if($blanket.is_selected(pair.item_id)) { + $blanket.deselect(pair.item_id); + sprite.sprite->setColor({255, 255, 255, 255}); + } else { + $blanket.select(pair.item_id); + sprite.sprite->setColor({255, 200, 200, 200}); + } + } + + void UI::load_blanket() { + // update the list of available items + int i = 0; + for(auto& [item_id, item] : $blanket.contents) { + auto slot_id = $gui.entity("inv_slot", i++); + auto icon_name = fmt::format("{}-64", item); + + $gui.set_init<Sprite>(slot_id, {icon_name}); + $gui.set<Clickable>(slot_id, { + [&, slot_id, item_id](auto, auto) { + auto data = std::make_any<SelectedItem>(slot_id, item_id); + event(Event::SELECT, data); + } + }); } } } diff --git a/ritual_ui.hpp b/ritual_ui.hpp index 30cc3ae..bd0ef0d 100644 --- a/ritual_ui.hpp +++ b/ritual_ui.hpp @@ -8,24 +8,30 @@ #include "fsm.hpp" namespace gui { - namespace ritual { enum class State { START=0, OPENED=1, CLOSED=2, OPENING=3, - CLOSING=4 + CLOSING=4, + CRAFTING=5 }; - enum class Event { STARTED=0, TOGGLE=1, - TICK=2 + TICK=2, + SELECT=3, + COMBINE=4 }; - class UI : public DeadSimpleFSM<State, Event>{ + struct SelectedItem { + DinkyECS::Entity slot_id; + DinkyECS::Entity item_id; + }; + + class UI : public DeadSimpleFSM<State, Event> { public: sf::IntRect $ritual_closed_rect{{0,0},{380,720}}; sf::IntRect $ritual_open_rect{{380 * 2,0},{380,720}}; @@ -33,12 +39,16 @@ namespace gui { guecs::UI $gui; GameLevel $level; textures::SpriteTexture $ritual_ui; + ::ritual::Blanket& $blanket; + ::ritual::Engine $ritual_engine; + ::ritual::CraftingState $craft_state; UI(GameLevel level); - void event(Event ev); + void event(Event ev, std::any data={}); void START(Event); - void OPENED(Event); + void OPENED(Event, std::any data={}); + void CRAFTING(Event, std::any data={}); void CLOSED(Event); void OPENING(Event); void CLOSING(Event); @@ -46,7 +56,13 @@ namespace gui { bool mouse(float x, float y, bool hover); void render(sf::RenderWindow &window); bool is_open(); + void load_blanket(); + void clear_blanket(); + void select_item(SelectedItem pair); + void show_craft_result(); + void clear_craft_result(); + void show_craft_failure(); + void run_crafting_engine(); }; - } } diff --git a/rituals.cpp b/rituals.cpp index 1f9f4b1..0555e95 100644 --- a/rituals.cpp +++ b/rituals.cpp @@ -188,4 +188,8 @@ namespace ritual { void Blanket::reset() { selected.clear(); } + + bool Blanket::no_selections() { + return selected.size() == 0; + } } diff --git a/rituals.hpp b/rituals.hpp index d242aea..22b92f2 100644 --- a/rituals.hpp +++ b/rituals.hpp @@ -93,5 +93,6 @@ namespace ritual { void deselect(DinkyECS::Entity ent); void reset(); bool is_selected(DinkyECS::Entity ent); + bool no_selections(); }; }