Basic loot UI mostly working. Each time you open there's a torch and you can place it visually on any slot on your character.

master
Zed A. Shaw 3 days ago
parent 4b34de2109
commit 5c47a0151c
  1. 4
      Makefile
  2. 2
      constants.hpp
  3. 3
      events.hpp
  4. 34
      gui/fsm.cpp
  5. 7
      gui/fsm.hpp
  6. 4
      gui/guecstra.cpp
  7. 63
      gui/loot_ui.cpp
  8. 2
      gui/loot_ui.hpp
  9. 35
      gui/status_ui.cpp
  10. 7
      gui/status_ui.hpp
  11. 2
      tests/map.cpp
  12. 3
      tests/matrix.cpp
  13. 1
      tests/textures.cpp

@ -26,7 +26,7 @@ tracy_build:
meson compile -j 10 -C builddir
test: build
./builddir/runtests "[loot]"
./builddir/runtests
run: build test
ifeq '$(OS)' 'Windows_NT'
@ -49,7 +49,7 @@ clean:
meson compile --clean -C builddir
debug_test: build
gdb --nx -x .gdbinit --ex run --args builddir/runtests -e "[loot]"
gdb --nx -x .gdbinit --ex run --args builddir/runtests -e
win_installer:
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'

@ -3,7 +3,7 @@
#include <string>
#include <array>
constexpr const int INV_SLOTS=20;
constexpr const int INV_SLOTS=16;
constexpr const int TEXTURE_WIDTH=256;
constexpr const int TEXTURE_HEIGHT=256;
constexpr const int RAY_VIEW_WIDTH=900;

@ -5,7 +5,8 @@ namespace Events {
START, COMBAT, LOOT, DEATH, STAIRS_UP, STAIRS_DOWN, TRAP,
COMBAT_START, NO_NEIGHBORS, HP_STATUS,
ATTACK, BLOCK, EVADE, NEW_RITUAL,
UPDATE_SPRITE, ENEMY_SPAWN, NOOP, LOOT_CLOSE
UPDATE_SPRITE, ENEMY_SPAWN, NOOP,
LOOT_CLOSE, LOOT_SELECT, LOOT_PLACE
};
struct Combat {

@ -38,8 +38,16 @@ namespace gui {
FSM_STATE(State, IN_COMBAT, ev);
FSM_STATE(State, COMBAT_ROTATE, ev);
FSM_STATE(State, NEXT_LEVEL, ev);
FSM_STATE(State, LOOTING, ev);
FSM_STATE(State, END, ev);
FSM_STATE(State, LOOTING, ev, std::make_any<bool>(true));
}
}
void FSM::event(Event ev, std::any data) {
switch($state) {
FSM_STATE(State, LOOTING, ev, data);
default:
dbc::log(fmt::format("event given with data but event={} is not handled", int(ev)));
}
}
@ -110,7 +118,7 @@ namespace gui {
}
}
void FSM::LOOTING(Event ev) {
void FSM::LOOTING(Event ev, std::any data) {
using enum Event;
switch(ev) {
@ -118,6 +126,19 @@ namespace gui {
$loot_ui.active = false;
state(State::IDLE);
break;
case LOOT_SELECT: {
int slot_id = std::any_cast<int>(data);
if(auto entity = $loot_ui.select_slot(slot_id)) {
fmt::println("LOOT SELECT slot={} has entity={}", slot_id, entity.value());
$status_ui.select_slot(slot_id, entity.value());
}
} break;
case LOOT_PLACE: {
std::string slot_name = std::any_cast<std::string>(data);
int slot_id = $status_ui.place_slot(slot_name);
$loot_ui.remove_slot(slot_id);
} break;
case TICK:
// do nothing
break;
@ -190,6 +211,9 @@ namespace gui {
state(State::LOOTING);
} break;
case LOOT_PLACE:
// ignored
break;
default:
dbc::sentinel("unhandled event in IDLE");
}
@ -439,6 +463,12 @@ namespace gui {
// BUG: need to resolve GUI events vs. FSM events better
event(Event::LOOT_OPEN);
break;
case eGUI::LOOT_SELECT:
event(Event::LOOT_SELECT, data);
break;
case eGUI::LOOT_PLACE:
event(Event::LOOT_PLACE, data);
break;
case eGUI::LOOT: {
$map_ui.log(L"You picked up an item.");
} break;

@ -41,7 +41,9 @@ namespace gui {
STOP_COMBAT = 12,
STAIRS_DOWN = 13,
LOOT_OPEN=14,
QUIT = 15
LOOT_SELECT=15,
LOOT_PLACE=16,
QUIT = 17
};
class FSM : public DeadSimpleFSM<State, Event> {
@ -66,6 +68,7 @@ namespace gui {
FSM();
void event(Event ev);
void event(Event ev, std::any data);
void autowalk();
void start_autowalk(double rot_speed);
@ -78,7 +81,7 @@ namespace gui {
void IN_COMBAT(Event ev);
void COMBAT_ROTATE(Event ev);
void NEXT_LEVEL(Event ev);
void LOOTING(Event ev);
void LOOTING(Event ev, std::any data);
void END(Event ev);
void try_move(int dir, bool strafe);

@ -4,6 +4,8 @@ namespace guecs {
Clickable make_action(DinkyECS::World& target, Events::GUI event) {
return {[&, event](auto ent, auto data){
// BUG: I think entityt here shifted and isn't part of the world anymore
// BUG: it's actually coming from the GUI so passing it here is wrong
// remember that ent is passed in from the UI::mouse handler
target.send<Events::GUI>(event, ent, data);
}};
@ -11,6 +13,8 @@ namespace guecs {
Clickable make_action(DinkyECS::World& target, Events::GUI event, std::any data) {
return {[&, event, data](auto ent, auto){
// BUG: I think entityt here shifted and isn't part of the world anymore
// BUG: it's actually coming from the GUI so passing it here is wrong
// remember that ent is passed in from the UI::mouse handler
target.send<Events::GUI>(event, ent, data);
}};

@ -33,34 +33,57 @@ namespace gui {
$gui.set<guecs::Clickable>(close,
guecs::make_action(*$level.world, Events::GUI::LOOT_CLOSE));
for(int i = 0; i < INV_SLOTS; i++) {
auto id = $gui.entity("item_", i);
$gui.set<guecs::Rectangle>(id, {THEME.PADDING,
THEME.TRANSPARENT, THEME.LIGHT_MID });
$gui.set<guecs::Effect>(id, {0.4f, "ui_shader"});
$gui.set<guecs::Clickable>(id, {
guecs::make_action(*$level.world, Events::GUI::LOOT_SELECT, {i})
});
}
$gui.init();
update();
}
std::optional<DinkyECS::Entity> LootUI::select_slot(int slot_id) {
if(size_t(slot_id) < contents.size()) {
return contents.at(slot_id);
} else {
return std::nullopt;
}
}
void LootUI::remove_slot(int slot_id) {
dbc::check(size_t(slot_id) < contents.size(),
fmt::format("invalid slot id {} give, contents.size={}",
slot_id, contents.size()));
contents.erase(contents.begin() + slot_id);
update();
}
void LootUI::update() {
dbc::check(contents.size() < 16, "too many items in loot contents, must be < 16");
for(int i = 0; i < 16; i++) {
auto id = $gui.entity("item_", i);
if($gui.has<guecs::Rectangle>(id)) {
$gui.remove<guecs::Rectangle>(id);
$gui.remove<guecs::Effect>(id);
$gui.remove<guecs::Clickable>(id);
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));
fmt::println("checking for sprite at {}", id);
if($gui.has<guecs::Sprite>(id)) {
fmt::println("REMOVING SPRITE {}", id);
$gui.remove<guecs::Sprite>(id);
} else {
fmt::println("nothing at {}", id);
}
}
for(size_t item_i = 0; item_i < contents.size(); item_i++) {
auto id = $gui.entity("item_", int(item_i));
$gui.set_init<guecs::Rectangle>(id, {THEME.PADDING,
THEME.TRANSPARENT, THEME.LIGHT_MID });
$gui.set_init<guecs::Effect>(id, {0.4f, "ui_shader"});
$gui.set<guecs::Clickable>(id, {
[=](auto, auto) { fmt::println("clicked button"); }
});
auto item = contents.at(item_i);
auto& sprite = $level.world->get<components::Sprite>(item);
$gui.set_init<guecs::Sprite>(id, {sprite.name});
if(i < contents.size()) {
auto item = contents.at(i);
auto& sprite = $level.world->get<components::Sprite>(item);
fmt::println("NEW SPRITE SPRITE {}", sprite.name);
$gui.set_init<guecs::Sprite>(id, {sprite.name});
}
}
}

@ -20,5 +20,7 @@ namespace gui {
void render(sf::RenderWindow& window);
void update_level(GameLevel &level);
bool mouse(float x, float y, bool hover);
std::optional<DinkyECS::Entity> select_slot(int slot);
void remove_slot(int slot_id);
};
}

@ -3,6 +3,7 @@
#include <guecs/ui.hpp>
#include "rand.hpp"
#include <fmt/xchar.h>
#include "gui/guecstra.hpp"
namespace gui {
using namespace guecs;
@ -19,13 +20,6 @@ namespace gui {
"[hand_r|_|_ |hand_l]"
"[ring_r|_|_ |ring_l]"
"[pocket_r|armor_leg|pocket_l]");
size_t inv_id = 0;
for(auto [name, entity] : $gui.$name_ents) {
if(name.starts_with("inv_")) {
$slots[name] = inv_id++;
}
}
}
void StatusUI::init() {
@ -49,7 +43,7 @@ namespace gui {
} else {
$gui.set<Textual>(button, {guecs::to_wstring(name)});
$gui.set<Clickable>(button, {
[this](auto ent, auto data){ select_slot(ent, data); }
guecs::make_action(*$level.world, Events::GUI::LOOT_PLACE, {name})
});
}
}
@ -71,13 +65,6 @@ namespace gui {
$ritual_ui.event(ritual::Event::TOGGLE);
}
void StatusUI::select_slot(DinkyECS::Entity ent, any slot_name) {
(void)ent;
(void)slot_name;
dbc::log("REWRITE!");
}
/* WARNING: This is really not the greatest way to do this. */
void StatusUI::update() {
dbc::log("REWRITE ME!");
}
@ -92,4 +79,22 @@ namespace gui {
$level = level;
init();
}
void StatusUI::select_slot(int slot_id, DinkyECS::Entity entity) {
$selected_slot = slot_id;
$selected_entity = entity;
}
int StatusUI::place_slot(const std::string &name) {
fmt::println("LOOT slot={}, entity={} PLACE into slot={}",
$selected_slot, $selected_entity, name);
auto& sprite = $level.world->get<components::Sprite>($selected_entity);
auto gui_id = $gui.entity(name);
$gui.set_init<guecs::Sprite>(gui_id, {sprite.name});
$slots.insert_or_assign(name, $selected_entity);
return $selected_slot;
}
}

@ -10,17 +10,20 @@ namespace gui {
class StatusUI {
public:
guecs::UI $gui;
std::map<std::string, size_t> $slots;
std::unordered_map<std::string, DinkyECS::Entity> $slots;
GameLevel $level;
ritual::UI $ritual_ui;
int $selected_slot;
DinkyECS::Entity $selected_entity;
StatusUI(GameLevel level);
void select_slot(DinkyECS::Entity ent, std::any data);
void select_ritual();
void update_level(GameLevel &level);
bool mouse(float x, float y, bool hover);
void init();
void render(sf::RenderWindow &window);
void update();
void select_slot(int slot_id, DinkyECS::Entity entity);
int place_slot(const std::string &name);
};
}

@ -15,6 +15,7 @@ json load_test_data(const string &fname) {
}
TEST_CASE("camera control", "[map]") {
components::init();
LevelManager levels;
GameLevel level = levels.current();
auto &map = *level.map;
@ -32,6 +33,7 @@ TEST_CASE("camera control", "[map]") {
}
TEST_CASE("map placement test", "[map:placement]") {
components::init();
for(int i = 0; i < 20; i++) {
LevelManager levels;
GameLevel level = levels.current();

@ -256,6 +256,7 @@ TEST_CASE("prototype circle algorithm", "[matrix:circle]") {
}
TEST_CASE("viewport iterator", "[matrix:viewport]") {
components::init();
size_t width = Random::uniform<size_t>(20, 22);
size_t height = Random::uniform<size_t>(21, 25);
shared_ptr<Map> map = make_map();
@ -279,6 +280,7 @@ TEST_CASE("viewport iterator", "[matrix:viewport]") {
}
TEST_CASE("random rectangle", "[matrix:rando_rect]") {
components::init();
for(int i = 0; i < 5; i++) {
shared_ptr<Map> map = make_map();
map->invert_space();
@ -303,6 +305,7 @@ TEST_CASE("random rectangle", "[matrix:rando_rect]") {
}
TEST_CASE("standard rectangle", "[matrix:rectangle]") {
components::init();
for(int i = 0; i < 5; i++) {
shared_ptr<Map> map = make_map();
auto wall_copy = map->walls();

@ -7,6 +7,7 @@
using namespace fmt;
TEST_CASE("test texture management", "[textures]") {
components::init();
textures::init();
auto spider = textures::get("hairy_spider");
REQUIRE(spider.sprite != nullptr);

Loading…
Cancel
Save