Files are now in a src directory and I'm using a src/meson.build and tests/meson.build to specify what to build.

This commit is contained in:
Zed A. Shaw 2026-02-27 10:49:19 -05:00
parent 4778677647
commit 1d4ae911b9
108 changed files with 94 additions and 83 deletions

102
src/gui/combat_ui.cpp Normal file
View file

@ -0,0 +1,102 @@
#include "constants.hpp"
#include "rituals.hpp"
#include <fmt/xchar.h>
#include "gui/guecstra.hpp"
#include "game_level.hpp"
#include "gui/combat_ui.hpp"
namespace gui {
using guecs::THEME;
using namespace guecs;
CombatUI::CombatUI(bool boss_style) :
$boss_style(boss_style)
{
}
guecs::Entity CombatUI::make_button(
guecs::Entity button,
game::Event event,
int action,
const std::string &icon_name,
const std::string &sound,
const std::string &effect_name)
{
$gui.set<Sprite>(button, {icon_name});
$gui.set<Sound>(button, {sound});
$gui.set<Effect>(button, {.duration=0.5f, .name=effect_name});
$gui.set<Clickable>(button,
guecs::make_action(button, event, {action}));
return button;
}
void CombatUI::init(int x, int y, int w, int h) {
$gui.position(x, y, w, h);
if($boss_style) {
$gui.layout(
"[action0|action1]"
"[action2|action3]"
"[action4|action5]"
"[action6|action7]");
} else {
$gui.layout(
"[action0 | action1 | action2 | action3"
"|action4 | action5 | action6 | action7 | =hp_gauge ]");
}
auto world = GameDB::current_world();
$gui.set<Background>($gui.MAIN, {$gui.$parser, THEME.DARK_MID});
auto& the_belt = world->get_the<ritual::Belt>();
for(int slot = 0; slot < the_belt.max_slots; slot++) {
fmt::println("combat ui belt slot {}", slot);
if(the_belt.has(slot)) {
auto button = $gui.entity("action", slot);
fmt::println("combat ui has belt slot {} with id {}", slot, button);
auto& ritual = the_belt.get(slot);
using enum ritual::Element;
switch(ritual.element) {
case FIRE:
make_button(button, game::Event::ATTACK,
slot, "broken_yoyo", "fireball_01", "flame");
break;
case LIGHTNING:
make_button(button, game::Event::ATTACK,
slot, "pocket_watch", "electric_shock_01", "lightning");
break;
default:
make_button(button, game::Event::ATTACK,
slot, "severed_finger", "punch_cartoony", "ui_shader");
}
}
}
if(!$boss_style) {
auto hp_gauge = $gui.entity("hp_gauge");
$gui.set<Sprite>(hp_gauge, {"stone_doll_cursed"});
$gui.set<Clickable>(hp_gauge,
guecs::make_action(hp_gauge, game::Event::HP_STATUS, {}));
}
$gui.init();
}
void CombatUI::render(sf::RenderWindow& window) {
$gui.render(window);
// $gui.debug_layout(window);
}
void CombatUI::update_level() {
init(COMBAT_UI_X, COMBAT_UI_Y, COMBAT_UI_WIDTH, COMBAT_UI_HEIGHT);
}
bool CombatUI::mouse(float x, float y, guecs::Modifiers mods) {
return $gui.mouse(x, y, mods);
}
}

23
src/gui/combat_ui.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Font.hpp>
#include <guecs/ui.hpp>
#include "events.hpp"
namespace gui {
class CombatUI {
public:
bool $boss_style;
guecs::UI $gui;
CombatUI(bool boss_style);
void init(int x, int y, int w, int h);
void render(sf::RenderWindow& window);
void update_level();
bool mouse(float x, float y, guecs::Modifiers mods);
guecs::Entity make_button(guecs::Entity button, game::Event event,
int action, const std::string &icon_name,
const std::string &sound, const std::string &effect_name);
};
}

106
src/gui/debug_ui.cpp Normal file
View file

@ -0,0 +1,106 @@
#include "gui/debug_ui.hpp"
#include "constants.hpp"
#include "events.hpp"
#include <optional>
#include <fmt/core.h>
#include <fmt/xchar.h>
#include "components.hpp"
namespace gui {
using namespace guecs;
void DebugUI::init(lel::Cell cell) {
$gui.position(cell.x, cell.y, cell.w, cell.h);
$gui.layout(
"[*%(100,400)debug_text]"
"[_]"
"[_]"
"[_]"
"[spawn1|spawn2|spawn3]"
"[spawn4|spawn5|spawn6]");
add_spawn_button("AXE_RANGER", "axe_ranger", "spawn1");
add_spawn_button("KNIGHT","armored_knight", "spawn2");
add_spawn_button("SPIDER_GIANT_HAIRY", "hairy_spider", "spawn3");
add_spawn_button("RAT_GIANT", "rat_with_sword", "spawn4");
add_spawn_button("GOLD_SAVIOR", "gold_savior", "spawn5");
$gui.init();
}
void DebugUI::add_spawn_button(std::string enemy_key, std::string sprite_name, std::string region) {
auto button = $gui.entity(region);
$gui.set<guecs::Clickable>(button, {
[this, enemy_key](auto){ spawn(enemy_key); }
});
$gui.set<guecs::Sprite>(button, { sprite_name});
}
void DebugUI::spawn(const std::string& enemy_key) {
(void)enemy_key;
dbc::log("THIS FUNCTION NEEDS A REWRITE");
// auto ent = $level_mgr.spawn_enemy(enemy_key);
// auto& level = $level_mgr.current();
// level.world->send<game::Event>(game::Event::ENTITY_SPAWN, ent, {});
}
void DebugUI::render(sf::RenderWindow& window) {
if(active) {
auto& level = GameDB::current_level();
auto player = level.world->get_the<components::Player>();
auto player_combat = level.world->get<components::Combat>(player.entity);
auto map = level.map;
std::wstring stats = fmt::format(L"STATS\n"
L"HP: {}\n"
L"mean:{:>8.5}\n"
L"sdev: {:>8.5}\n"
L"min: {:>8.5}\n"
L"max: {:>8.5}\n"
L"count:{:<10}\n"
L"level: {} size: {}x{}\n\n"
L"VSync? {}\n"
L"FR Limit: {}\n"
L"Debug? {}\n\n",
player_combat.hp, $stats.mean(), $stats.stddev(), $stats.min,
$stats.max, $stats.n, level.index, map->width(), map->height(),
VSYNC, FRAME_LIMIT, DEBUG_BUILD);
$gui.show_text("debug_text", stats);
$gui.render(window);
// $gui.debug_layout(window);
}
}
void DebugUI::debug() {
active = !active;
if(active) {
auto& level = GameDB::current_level();
// it's on now, enable things
auto player = level.world->get_the<components::Player>();
auto& player_combat = level.world->get<components::Combat>(player.entity);
player_combat.hp = player_combat.max_hp;
$gui.show_text("debug_text", L"STATS");
} else {
// it's off now, close it
$gui.close<Text>("debug_text");
}
}
bool DebugUI::mouse(float x, float y, guecs::Modifiers mods) {
return $gui.mouse(x, y, mods);
}
Stats::TimeBullshit DebugUI::time_start() {
return $stats.time_start();
}
void DebugUI::sample_time(Stats::TimeBullshit start) {
$stats.sample_time(start);
}
void DebugUI::reset_stats() {
$stats.reset();
}
}

26
src/gui/debug_ui.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include "game_level.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Font.hpp>
#include <guecs/ui.hpp>
#include "stats.hpp"
namespace gui {
class DebugUI {
public:
Stats $stats;
guecs::UI $gui;
bool active = false;
void init(lel::Cell cell);
void render(sf::RenderWindow& window);
bool mouse(float x, float y, guecs::Modifiers mods);
void debug();
void spawn(const std::string& enemy_key);
void add_spawn_button(std::string enemy_key, std::string sprite_name, std::string region);
Stats::TimeBullshit time_start();
void sample_time(Stats::TimeBullshit start);
void reset_stats();
};
}

328
src/gui/dnd_loot.cpp Normal file
View file

@ -0,0 +1,328 @@
#include "gui/guecstra.hpp"
#include "gui/dnd_loot.hpp"
namespace gui {
using Event = game::Event;
DNDLoot::DNDLoot(StatusUI& status_ui, LootUI& loot_ui, sf::RenderWindow &window, routing::Router& router) :
$status_ui(status_ui),
$loot_ui(loot_ui),
$window(window),
$router(router)
{
event(Event::START);
}
bool DNDLoot::event(Event ev, std::any data) {
switch($state) {
FSM_STATE(DNDState, START, ev);
FSM_STATE(DNDState, LOOTING, ev, data);
FSM_STATE(DNDState, LOOT_GRAB, ev, data);
FSM_STATE(DNDState, INV_GRAB, ev, data);
FSM_STATE(DNDState, ITEM_PICKUP, ev, data);
FSM_STATE(DNDState, INV_PICKUP, ev, data);
FSM_STATE(DNDState, END, ev, data);
default:
dbc::log(fmt::format("event received with data but state={} is not handled", int($state)));
}
return !in_state(DNDState::END);
}
void DNDLoot::START(Event ev) {
using enum Event;
dbc::check(ev == START, "START not given a STARTED event.");
END(CLOSE);
}
void DNDLoot::LOOTING(Event ev, std::any data) {
using enum Event;
switch(ev) {
case LOOT_OPEN:
END(CLOSE);
break;
case LOOT_SELECT:
$grab_source = start_grab($loot_ui.$gui, data);
if($grab_source) state(DNDState::LOOT_GRAB);
break;
case INV_SELECT:
$grab_source = start_grab($status_ui.$gui, data);
if($grab_source) state(DNDState::INV_GRAB);
break;
default:
break; // ignore
}
}
void DNDLoot::LOOT_GRAB(Event ev, std::any data) {
using enum Event;
switch(ev) {
case LOOT_OPEN:
END(CLOSE);
break;
case LOOT_SELECT: {
auto drop_id = std::any_cast<guecs::Entity>(data);
if(move_or_swap($loot_ui, drop_id)) {
state(DNDState::LOOTING);
}
} break;
case INV_SELECT:
if(commit_drop($loot_ui.$gui,
$status_ui.$gui, $grab_source, data))
{
state(DNDState::LOOTING);
}
break;
default:
handle_mouse(ev, $loot_ui.$gui);
}
}
void DNDLoot::INV_GRAB(Event ev, std::any data) {
using enum Event;
switch(ev) {
case LOOT_OPEN:
END(CLOSE);
break;
case LOOT_SELECT:
if(commit_drop($status_ui.$gui,
$loot_ui.$gui, $grab_source, data))
{
state(DNDState::LOOTING);
}
break;
case INV_SELECT: {
auto drop_id = std::any_cast<guecs::Entity>(data);
if(move_or_swap($status_ui, drop_id)) {
state(DNDState::LOOTING);
}
} break;
default:
handle_mouse(ev, $status_ui.$gui);
}
}
void DNDLoot::INV_PICKUP(Event ev, std::any data) {
using enum Event;
switch(ev) {
case AIM_CLICK: {
// take from inventory, drop on floor
throw_on_floor($status_ui.$gui, true);
END(CLOSE);
} break;
case INV_SELECT: {
auto drop_id = std::any_cast<guecs::Entity>(data);
if(move_or_swap($status_ui, drop_id)) {
END(CLOSE);
}
} break;
default:
handle_mouse(ev, $status_ui.$gui);
}
}
void DNDLoot::ITEM_PICKUP(Event ev, std::any data) {
using enum Event;
switch(ev) {
case INV_SELECT:
if(commit_drop($loot_ui.$gui, $status_ui.$gui, $grab_source, data))
{
END(CLOSE);
}
break;
case AIM_CLICK: {
// THIS IS PUT IT BACK ON THE FLOOR
throw_on_floor($loot_ui.$gui, false);
END(CLOSE);
} break;
default:
handle_mouse(ev, $loot_ui.$gui);
}
}
void DNDLoot::END(Event ev, std::any data) {
using enum Event;
switch(ev) {
case LOOT_ITEM: {
auto gui_id = $loot_ui.$gui.entity("item_0");
if(hold_item($loot_ui.$gui, gui_id)) {
state(DNDState::ITEM_PICKUP);
}
} break;
case INV_SELECT: {
auto gui_id = std::any_cast<guecs::Entity>(data);
if(hold_item($status_ui.$gui, gui_id)) {
state(DNDState::INV_PICKUP);
}
} break;
case LOOT_OPEN:
open();
state(DNDState::LOOTING);
break;
case CLOSE:
// called the first time transitioning to END
close();
state(DNDState::END);
break;
case TICK: // ignored
break;
default:
dbc::log(fmt::format("invalid event: {}", int(ev)));
}
}
void DNDLoot::handle_mouse(Event ev, guecs::UI& gui) {
using enum Event;
switch(ev) {
case MOUSE_DRAG:
case MOUSE_MOVE: {
if($grab_source) {
auto& source = gui.get<guecs::GrabSource>(*$grab_source);
source.move($window.mapPixelToCoords($router.position));
}
} break;
default:
break; // ignored
}
}
void DNDLoot::clear_grab() {
$grab_source = std::nullopt;
$grab_sprite = nullptr;
}
void DNDLoot::open() {
$loot_ui.active = true;
}
void DNDLoot::close() {
$loot_ui.active = false;
}
void DNDLoot::render() {
if($grab_source && $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)) {
$grab_sprite = source->sprite;
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);
dbc::check(target.has<guecs::DropTarget>(target_id),
"gui does not have a DropTarget at that slot");
dbc::check(source.has<guecs::GrabSource>(*source_id),
"gui does not have a GrabSource at that slot");
auto& grab = source.get<guecs::GrabSource>(*source_id);
auto& drop = target.get<guecs::DropTarget>(target_id);
if(drop.commit(grab.world_entity)) {
grab.commit();
clear_grab();
return true;
} else {
return false;
}
}
bool DNDLoot::commit_move(guecs::UI& gui, std::optional<guecs::Entity> source_id, guecs::Entity drop_id) {
dbc::check(source_id != std::nullopt, "source_id must exist");
auto& grab = gui.get<guecs::GrabSource>(*source_id);
grab.commit();
auto& drop = gui.get<guecs::DropTarget>(drop_id);
if(drop.commit(grab.world_entity)) {
clear_grab();
return true;
} else {
// swap with the target instead
return false;
}
}
bool DNDLoot::hold_item(guecs::UI &gui, guecs::Entity gui_id) {
// NOTE: if > 1 items, go to LOOT_OPEN instead
$grab_source = start_grab(gui, gui_id);
if($grab_source) {
auto& source = gui.get<guecs::GrabSource>(*$grab_source);
$grab_sprite = source.sprite;
// call this once to properly position the sprite
handle_mouse(Event::MOUSE_MOVE, gui);
}
return $grab_source != std::nullopt;
}
/*
* Dropping on the ground is only possible from the
* status_ui for now.
*/
void DNDLoot::throw_on_floor(guecs::UI& gui, bool from_status) {
dbc::check($grab_source != std::nullopt, "attempt to commit_drop but no grab_source set");
dbc::check(gui.has<guecs::GrabSource>(*$grab_source),
"StatusUI doesn't actually have that GrabSource in the gui.");
auto& grab = gui.get<guecs::GrabSource>(*$grab_source);
if(from_status) {
$status_ui.drop_item(grab.world_entity);
} else {
$loot_ui.drop_item(grab.world_entity);
}
grab.commit();
clear_grab();
}
/*
* If I refactored everything to use a levelmanager module then
* this and many other things could go away. Access to $level is
* making this too complicated. Do this for now, but fix bug #59.
*/
bool DNDLoot::move_or_swap(StatusUI& ui, guecs::Entity drop_id) {
if(ui.occupied(drop_id)) {
ui.swap(*$grab_source, drop_id);
clear_grab();
return true;
} else {
return commit_move(ui.$gui, $grab_source, drop_id);
}
}
bool DNDLoot::move_or_swap(LootUI& ui, guecs::Entity drop_id) {
if(ui.occupied(drop_id)) {
ui.swap(*$grab_source, drop_id);
clear_grab();
return true;
} else {
return commit_move(ui.$gui, $grab_source, drop_id);
}
}
}

66
src/gui/dnd_loot.hpp Normal file
View file

@ -0,0 +1,66 @@
#pragma once
#include "simplefsm.hpp"
#include <guecs/ui.hpp>
#include "gui/status_ui.hpp"
#include "gui/loot_ui.hpp"
#include "gui/event_router.hpp"
#include "events.hpp"
namespace gui {
enum class DNDState {
START=100,
LOOTING=101,
LOOT_GRAB=102,
INV_GRAB=103,
ITEM_PICKUP=104,
INV_PICKUP=105,
END=106
};
class DNDLoot : public DeadSimpleFSM<DNDState, game::Event> {
public:
std::optional<guecs::Entity> $grab_source = std::nullopt;
std::shared_ptr<sf::Sprite> $grab_sprite = nullptr;
StatusUI& $status_ui;
LootUI& $loot_ui;
sf::RenderWindow& $window;
routing::Router& $router;
DNDLoot(StatusUI& status_ui,
LootUI& loot_ui, sf::RenderWindow& window,
routing::Router& router);
bool event(game::Event ev, std::any data={});
void START(game::Event ev);
void LOOTING(game::Event ev, std::any data);
void LOOT_GRAB(game::Event ev, std::any data);
void INV_GRAB(game::Event ev, std::any data);
void END(game::Event ev, std::any data={});
void ITEM_PICKUP(game::Event ev, std::any data);
void INV_PICKUP(game::Event ev, std::any data);
void handle_mouse(game::Event ev, guecs::UI& gui);
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);
bool commit_move(guecs::UI& gui,
std::optional<guecs::Entity> source_id, guecs::Entity drop_id);
bool hold_item(guecs::UI& gui, guecs::Entity gui_id);
void throw_on_floor(guecs::UI& gui, bool from_status);
void clear_grab();
bool move_or_swap(StatusUI& status_ui, guecs::Entity drop_id);
bool move_or_swap(LootUI& ui, guecs::Entity drop_id);
sf::Vector2f mouse_position();
};
}

143
src/gui/event_router.cpp Normal file
View file

@ -0,0 +1,143 @@
#include "event_router.hpp"
#include "dbc.hpp"
#include "events.hpp"
namespace gui {
namespace routing {
using enum Event;
using enum State;
game::Event Router::process_event(std::optional<sf::Event> ev) {
$next_event = game::Event::TICK;
if(ev->is<sf::Event::Closed>()) {
return game::Event::QUIT;
}
if(const auto* mouse = ev->getIf<sf::Event::MouseButtonPressed>()) {
if(mouse->button == sf::Mouse::Button::Left || mouse->button == sf::Mouse::Button::Right) {
left_button = mouse->button == sf::Mouse::Button::Left;
position = mouse->position;
event(MOUSE_DOWN);
}
} else if(const auto* mouse = ev->getIf<sf::Event::MouseButtonReleased>()) {
// need to sort this out but if you don't do this it thinks you're always pressing it
if(mouse->button == sf::Mouse::Button::Left || mouse->button == sf::Mouse::Button::Right) {
left_button = mouse->button == sf::Mouse::Button::Left;
position = mouse->position;
event(MOUSE_UP);
}
} else if(const auto* mouse = ev->getIf<sf::Event::MouseMoved>()) {
position = mouse->position;
event(MOUSE_MOVE);
}
if(const auto* key = ev->getIf<sf::Event::KeyPressed>()) {
scancode = key->scancode;
event(KEY_PRESS);
}
return $next_event;
}
void Router::event(Event ev) {
switch($state) {
FSM_STATE(State, START, ev);
FSM_STATE(State, IDLE, ev);
FSM_STATE(State, MOUSE_ACTIVE, ev);
FSM_STATE(State, MOUSE_MOVING, ev);
FSM_STATE(State, MOUSE_DRAGGING, ev);
}
}
void Router::START(Event ) {
state(State::IDLE);
}
void Router::IDLE(Event ev) {
switch(ev) {
case MOUSE_DOWN:
move_count=0;
set_event(game::Event::TICK);
state(State::MOUSE_ACTIVE);
break;
case MOUSE_UP:
set_event(game::Event::MOUSE_CLICK);
state(State::IDLE);
break;
case MOUSE_MOVE:
set_event(game::Event::MOUSE_MOVE);
break;
case KEY_PRESS:
set_event(game::Event::KEY_PRESS);
break;
default:
dbc::sentinel(fmt::format("invalid event: {}", int(ev)));
}
}
void Router::MOUSE_ACTIVE(Event ev) {
switch(ev) {
case MOUSE_UP:
set_event(game::Event::MOUSE_CLICK);
state(State::IDLE);
break;
case MOUSE_MOVE:
move_count++;
set_event(game::Event::MOUSE_DRAG);
state(State::MOUSE_MOVING);
break;
case KEY_PRESS:
set_event(game::Event::KEY_PRESS);
state(State::IDLE);
break;
default:
dbc::sentinel("invalid event");
}
}
void Router::MOUSE_MOVING(Event ev) {
switch(ev) {
case MOUSE_UP: {
dbc::check(move_count < $drag_tolerance, "mouse up but not in dragging state");
set_event(game::Event::MOUSE_CLICK);
state(State::IDLE);
} break;
case MOUSE_MOVE:
move_count++;
if(move_count < $drag_tolerance) {
set_event(game::Event::MOUSE_DRAG);
} else {
set_event(game::Event::MOUSE_DRAG_START);
state(State::MOUSE_DRAGGING);
}
break;
case KEY_PRESS:
set_event(game::Event::KEY_PRESS);
break;
default:
dbc::sentinel("invalid event");
}
}
void Router::MOUSE_DRAGGING(Event ev) {
switch(ev) {
case MOUSE_UP:
set_event(game::Event::MOUSE_DROP);
state(State::IDLE);
break;
case MOUSE_MOVE:
move_count++;
set_event(game::Event::MOUSE_DRAG);
break;
case KEY_PRESS:
set_event(game::Event::KEY_PRESS);
break;
default:
// invalid events: 1
dbc::sentinel(fmt::format("invalid events: {}", int(ev)));
}
}
}
}

49
src/gui/event_router.hpp Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include "events.hpp"
#include "events.hpp"
#include "simplefsm.hpp"
#include <SFML/Graphics.hpp>
namespace gui {
namespace routing {
enum class State {
START,
IDLE,
MOUSE_ACTIVE,
MOUSE_MOVING,
MOUSE_DRAGGING
};
enum class Event {
STARTED=0,
MOUSE_DOWN=1,
MOUSE_UP=2,
MOUSE_MOVE=3,
KEY_PRESS=4
};
class Router : public DeadSimpleFSM<State, Event> {
public:
sf::Vector2i position;
sf::Keyboard::Scancode scancode;
game::Event $next_event = game::Event::TICK;
int move_count = 0;
bool left_button = true;
int $drag_tolerance = 4;
void event(Event ev);
void START(Event ev);
void IDLE(Event ev);
void MOUSE_ACTIVE(Event ev);
void MOUSE_MOVING(Event ev);
void MOUSE_DRAGGING(Event ev);
game::Event process_event(std::optional<sf::Event> ev);
void set_event(game::Event ev) {
$next_event = ev;
}
};
}
}

577
src/gui/fsm.cpp Normal file
View file

@ -0,0 +1,577 @@
#include "gui/fsm.hpp"
#include <iostream>
#include <chrono>
#include <numeric>
#include <functional>
#include "components.hpp"
#include <numbers>
#include "systems.hpp"
#include "events.hpp"
#include "sound.hpp"
#include "shaders.hpp"
#include <fmt/xchar.h>
#include "gui/guecstra.hpp"
#include "game_level.hpp"
#include "boss/system.hpp"
namespace gui {
using namespace components;
using game::Event;
FSM::FSM() :
$window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Raycaster Thing"),
$main_ui($window),
$combat_ui(false),
$dnd_loot($status_ui, $loot_ui, $window, $router)
{
$window.setVerticalSyncEnabled(VSYNC);
if(FRAME_LIMIT) $window.setFramerateLimit(FRAME_LIMIT);
$window.setPosition({0,0});
}
void FSM::event(Event ev, std::any data) {
switch($state) {
FSM_STATE(State, START, ev);
FSM_STATE(State, MOVING, ev);
FSM_STATE(State, ATTACKING, ev, data);
FSM_STATE(State, ROTATING, ev);
FSM_STATE(State, IDLE, ev, data);
FSM_STATE(State, IN_COMBAT, ev);
FSM_STATE(State, COMBAT_ROTATE, ev);
FSM_STATE(State, CUT_SCENE_PLAYING, ev, data);
FSM_STATE(State, BOSS_FIGHT, ev, data);
FSM_STATE(State, LOOTING, ev, data);
FSM_STATE(State, END, ev);
}
}
void FSM::START(Event ) {
$main_ui.update_level();
$main_ui.init();
$loot_ui.init();
// BUG: maybe this is a function on main_ui?
auto cell = $main_ui.overlay_cell("left");
$debug_ui.init(cell);
$combat_ui.init(COMBAT_UI_X, COMBAT_UI_Y, COMBAT_UI_WIDTH, COMBAT_UI_HEIGHT);
$status_ui.init();
$map_ui.init();
$map_ui.log(L"Welcome to the game!");
run_systems();
state(State::IDLE);
}
void FSM::MOVING(Event ) {
// this should be an optional that returns a point
if(auto move_to = $main_ui.play_move()) {
System::move_player(*move_to);
run_systems();
$main_ui.dirty();
state(State::IDLE);
}
}
void FSM::ATTACKING(Event ev, std::any data) {
using enum Event;
switch(ev) {
case TICK: {
dbc::log("!!!!!! FIX System::combat(0) doesn't use any weapons, only first");
System::combat(0);
run_systems();
state(State::IN_COMBAT);
} break;
case COMBAT_STOP:
state(State::IDLE);
break;
case ATTACK: {
int attack_id = std::any_cast<int>(data);
System::combat(attack_id);
run_systems();
} break;
default:
dbc::log(fmt::format("In ATTACKING state, unhandled event {}", (int)ev));
state(State::IDLE);
}
}
void FSM::ROTATING(Event) {
if(auto aim = $main_ui.play_rotate()) {
auto& player_pos = GameDB::player_position();
player_pos.aiming_at = *aim;
state(State::IDLE);
}
}
void FSM::COMBAT_ROTATE(Event) {
if(auto aim = $main_ui.play_rotate()) {
auto& player_pos = GameDB::player_position();
player_pos.aiming_at = *aim;
state(State::IN_COMBAT);
}
}
void FSM::LOOTING(Event ev, std::any data) {
using enum Event;
switch(ev) {
case MOUSE_DRAG_START:
case MOUSE_CLICK:
case MOUSE_DROP:
mouse_action(guecs::NO_MODS);
break;
default:
if(!$dnd_loot.event(ev, data)) {
state(State::IDLE);
}
}
}
void FSM::IDLE(Event ev, std::any data) {
using enum Event;
sound::stop("walk");
switch(ev) {
case QUIT:
$window.close();
state(State::END);
return; // done
case MOVE_FORWARD:
try_move(1, false);
break;
case MOVE_BACK:
try_move(-1, false);
break;
case MOVE_LEFT:
try_move(-1, true);
break;
case MOVE_RIGHT:
try_move(1, true);
break;
case ROTATE_LEFT:
$main_ui.plan_rotate(-1, 0.25f);
state(State::ROTATING);
break;
case ROTATE_RIGHT:
$main_ui.plan_rotate(1, 0.25f);
state(State::ROTATING);
break;
case MAP_OPEN:
$map_open = !$map_open;
break;
case ATTACK:
state(State::ATTACKING);
break;
case COMBAT_START:
$map_open = false;
state(State::IN_COMBAT);
break;
case CLOSE:
dbc::log("Nothing to close.");
break;
case BOSS_START:
sound::stop("ambient");
next_level(true);
state(State::CUT_SCENE_PLAYING);
break;
case LOOT_ITEM:
$dnd_loot.event(Event::LOOT_ITEM);
state(State::LOOTING);
break;
case LOOT_OPEN:
$dnd_loot.event(Event::LOOT_OPEN);
state(State::LOOTING);
break;
case INV_SELECT:
$dnd_loot.event(Event::INV_SELECT, data);
state(State::LOOTING);
break;
case USE_ITEM: {
auto gui_id = std::any_cast<guecs::Entity>(data);
auto& slot_name = $status_ui.$gui.name_for(gui_id);
if(System::use_item(slot_name)) {
$status_ui.update();
}
} break;
case MOUSE_CLICK:
mouse_action(guecs::NO_MODS);
break;
case MOUSE_MOVE: {
mouse_action({1 << guecs::ModBit::hover});
} break;
case AIM_CLICK:
System::pickup();
break;
default:
break; // ignore everything else
}
}
void FSM::CUT_SCENE_PLAYING(Event ev, std::any data) {
if($boss_scene->playing()) {
if(ev == game::Event::MOUSE_CLICK) {
dbc::log("exiting cut scene");
$boss_scene->mouse(0,0, 0);
state(State::BOSS_FIGHT);
}
} else {
state(State::BOSS_FIGHT);
}
}
void FSM::BOSS_FIGHT(Event ev, std::any data) {
dbc::log("this should not run, it's handled in handle_boss_fight_events");
}
void FSM::IN_COMBAT(Event ev) {
using enum Event;
switch(ev) {
case MOUSE_CLICK:
mouse_action(guecs::NO_MODS);
break;
case MOUSE_MOVE: {
mouse_action({1 << guecs::ModBit::hover});
} break;
case TICK:
run_systems();
break;
case ATTACK:
$main_ui.play_hands();
$main_ui.dirty();
sound::play("Sword_Hit_1");
state(State::ATTACKING);
break;
case ROTATE_LEFT:
$main_ui.plan_rotate(-1, DEFAULT_ROTATE);
state(State::COMBAT_ROTATE);
break;
case ROTATE_RIGHT:
$main_ui.plan_rotate(1, DEFAULT_ROTATE);
state(State::COMBAT_ROTATE);
break;
case COMBAT_STOP:
$main_ui.$overlay_ui.close_sprite("top_right");
state(State::IDLE);
break;
case QUIT:
$window.close();
state(State::END);
return;
default:
break;
}
}
void FSM::try_move(int dir, bool strafe) {
auto& level = GameDB::current_level();
using enum State;
// prevent moving into occupied space
Point move_to = $main_ui.plan_move(dir, strafe);
if(level.map->can_move(move_to) && !level.collision->occupied(move_to)) {
sound::play("walk");
state(MOVING);
} else {
state(IDLE);
$main_ui.abort_plan();
}
}
void FSM::END(Event ev) {
dbc::log(fmt::format("END: received event after done: {}", int(ev)));
}
sf::Vector2f FSM::mouse_position() {
return $window.mapPixelToCoords($router.position);
}
void FSM::mouse_action(guecs::Modifiers mods) {
sf::Vector2f pos = mouse_position();
if($debug_ui.active) $debug_ui.mouse(pos.x, pos.y, mods);
$combat_ui.mouse(pos.x, pos.y, mods);
$status_ui.mouse(pos.x, pos.y, mods);
if($loot_ui.active) {
$loot_ui.mouse(pos.x, pos.y, mods);
} else {
$main_ui.mouse(pos.x, pos.y, mods);
}
}
void FSM::handle_keyboard_mouse() {
while(const auto ev = $window.pollEvent()) {
auto gui_ev = $router.process_event(ev);
if(gui_ev == Event::KEY_PRESS) {
using KEY = sf::Keyboard::Scan;
switch($router.scancode) {
case KEY::W:
event(Event::MOVE_FORWARD);
break;
case KEY::S:
event(Event::MOVE_BACK);
break;
case KEY::Q:
event(Event::ROTATE_LEFT);
break;
case KEY::E:
event(Event::ROTATE_RIGHT);
break;
case KEY::D:
event(Event::MOVE_RIGHT);
break;
case KEY::A:
event(Event::MOVE_LEFT);
break;
case KEY::R:
dbc::log("HEY! DIPSHIT! You need to move debug ui so you can rest stats.");
break;
case KEY::M:
event(Event::MAP_OPEN);
break;
case KEY::Escape:
event(Event::CLOSE);
break;
case KEY::Space:
event(Event::ATTACK);
break;
case KEY::P:
sound::mute(false);
if(!sound::playing("ambient_1")) sound::play("ambient_1", true);
$debug_ui.debug();
shaders::reload();
break;
case KEY::O:
autowalking = true;
break;
case KEY::L:
// This will go away as soon as containers work
$loot_ui.set_target($loot_ui.$temp_loot);
$loot_ui.update();
event(Event::LOOT_OPEN);
break;
case KEY::Z:
$main_ui.toggle_mind_reading();
break;
case KEY::X:
event(Event::BOSS_START);
break;
case KEY::F5:
take_screenshot();
break;
default:
break; // ignored
}
} else {
event(gui_ev);
}
}
}
void FSM::debug_render() {
auto start = $debug_ui.time_start();
$main_ui.render();
$debug_ui.sample_time(start);
$debug_ui.render($window);
}
void FSM::draw_gui() {
if($debug_ui.active) {
debug_render();
} else {
$main_ui.render();
}
$status_ui.render($window);
$combat_ui.render($window);
if($loot_ui.active) $loot_ui.render($window);
if(in_state(State::LOOTING)) $dnd_loot.render();
if($map_open) {
$map_ui.render($window, $main_ui.$compass_dir);
}
}
void FSM::render() {
$window.clear();
if(in_state(State::BOSS_FIGHT)) {
$boss_fight->render($window);
// this clears any attack animations, like fire
System::clear_attack();
} else if(in_state(State::CUT_SCENE_PLAYING)) {
$boss_scene->render($window);
} else {
// this clears any attack animations, like fire
System::clear_attack();
draw_gui();
}
$window.display();
}
void FSM::run_systems() {
System::generate_paths();
System::enemy_ai_initialize();
System::enemy_pathing();
System::motion();
System::collision();
System::lighting();
System::death();
}
bool FSM::active() {
return !in_state(State::END);
}
void FSM::handle_boss_fight_events() {
dbc::check($boss_fight != nullptr, "$boss_fight not initialized");
// true means State::END in $boss_fight
if($boss_fight->handle_world_events() || $boss_fight->handle_keyboard_mouse()) {
dbc::log("boss fight ended, transition to IDLE");
// fight is over, go back to regular game
sound::play("ambient");
next_level(false);
state(State::IDLE);
}
}
void FSM::handle_world_events() {
using eGUI = game::Event;
auto world = GameDB::current_world();
while(world->has_event<eGUI>()) {
auto [evt, entity, data] = world->recv<eGUI>();
auto player = world->get_the<Player>();
// HERE: this has to go, unify these events and just use them in the state machine directly
switch(evt) {
case eGUI::COMBAT: {
auto &damage = std::any_cast<components::CombatResult&>(data);
if(damage.enemy_did > 0) {
$map_ui.log(fmt::format(L"Enemy HIT YOU for {} damage!", damage.enemy_did));
} else {
$map_ui.log(L"Enemy MISSED YOU.");
}
if(damage.player_did > 0) {
$map_ui.log(fmt::format(L"You HIT enemy for {} damage!", damage.player_did));
} else {
$map_ui.log(L"You MISSED the enemy.");
}
}
break;
case eGUI::COMBAT_START:
event(Event::COMBAT_START);
break;
case eGUI::ENTITY_SPAWN: {
auto& sprite = world->get<components::Sprite>(entity);
$main_ui.$rayview->update_sprite(entity, sprite);
$main_ui.dirty();
run_systems();
} break;
case eGUI::NO_NEIGHBORS:
event(Event::COMBAT_STOP);
break;
case eGUI::LOOT_CLOSE:
// 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::INV_SELECT: {
if($router.left_button) {
event(Event::INV_SELECT, data);
} else {
event(Event::USE_ITEM, data);
}
} break;
case eGUI::AIM_CLICK:
event(Event::AIM_CLICK);
break;
case eGUI::LOOT_ITEM: {
dbc::check(world->has<components::InventoryItem>(entity),
"INVALID LOOT_ITEM, that entity has no InventoryItem");
$loot_ui.add_loose_item(entity);
event(Event::LOOT_ITEM);
} break;
case eGUI::LOOT_CONTAINER: {
$loot_ui.set_target($loot_ui.$temp_loot);
$loot_ui.update();
event(Event::LOOT_OPEN);
} break;
case eGUI::HP_STATUS:
System::player_status();
break;
case eGUI::NEW_RITUAL:
$combat_ui.init(COMBAT_UI_X, COMBAT_UI_Y, COMBAT_UI_WIDTH, COMBAT_UI_HEIGHT);
break;
case eGUI::ATTACK:
event(Event::ATTACK, data);
break;
case eGUI::STAIRS_DOWN:
event(Event::BOSS_START);
break;
case eGUI::DEATH: {
$status_ui.update();
if(entity != player.entity) {
$main_ui.dead_entity(entity);
} else {
dbc::log("NEED TO HANDLE PLAYER DYING.");
}
} break;
case eGUI::NOOP: {
if(data.type() == typeid(std::string)) {
auto name = std::any_cast<std::string>(data);
$map_ui.log(fmt::format(L"NOOP EVENT! {},{}", evt, entity));
}
} break;
default:
dbc::log(fmt::format("Unhandled event: evt={}; enemy={}; data={}",
evt, entity, data.type().name()));
event(game::Event(evt), data);
}
}
}
void FSM::take_screenshot() {
auto size = $window.getSize();
sf::Texture shot{size};
shot.update($window);
sf::Image out_img = shot.copyToImage();
bool worked = out_img.saveToFile("./screenshot.png");
dbc::check(worked, "Failed to write screenshot.png");
}
void FSM::next_level(bool bossfight) {
if(bossfight) {
$boss_scene = std::make_shared<storyboard::UI>("rat_king");
$boss_scene->init();
$boss_fight = boss::System::create_bossfight();
$boss_fight->set_window(&$window);
dbc::check($boss_scene->playing(), "boss scene doesn't play");
} else {
GameDB::create_level();
$status_ui.update_level();
$combat_ui.update_level();
$main_ui.update_level();
$loot_ui.update_level();
}
run_systems();
}
}

83
src/gui/fsm.hpp Normal file
View file

@ -0,0 +1,83 @@
#pragma once
#include "constants.hpp"
#include "simplefsm.hpp"
#include "gui/debug_ui.hpp"
#include "gui/main_ui.hpp"
#include "gui/combat_ui.hpp"
#include "gui/status_ui.hpp"
#include "gui/loot_ui.hpp"
#include "boss/fight.hpp"
#include "gui/map_view.hpp"
#include "events.hpp"
#include "gui/event_router.hpp"
#include "gui/dnd_loot.hpp"
#include "storyboard/ui.hpp"
#include "events.hpp"
namespace gui {
enum class State {
START=__LINE__,
MOVING=__LINE__,
IN_COMBAT=__LINE__,
COMBAT_ROTATE=__LINE__,
ATTACKING=__LINE__,
ROTATING=__LINE__,
BOSS_FIGHT=__LINE__,
LOOTING=__LINE__,
IDLE=__LINE__,
CUT_SCENE_PLAYING=__LINE__,
END=__LINE__,
};
class FSM : public DeadSimpleFSM<State, game::Event> {
public:
sf::RenderWindow $window;
bool $draw_stats = false;
bool autowalking = false;
bool $map_open = false;
DebugUI $debug_ui;
MainUI $main_ui;
std::shared_ptr<boss::Fight> $boss_fight = nullptr;
std::shared_ptr<storyboard::UI> $boss_scene = nullptr;
CombatUI $combat_ui;
StatusUI $status_ui;
MapViewUI $map_ui;
LootUI $loot_ui;
gui::routing::Router $router;
DNDLoot $dnd_loot;
FSM();
void event(game::Event ev, std::any data={});
void autowalk();
void start_autowalk(double rot_speed);
void START(game::Event ev);
void MOVING(game::Event ev);
void ATTACKING(game::Event ev, std::any data);
void MAPPING(game::Event ev);
void ROTATING(game::Event ev);
void IDLE(game::Event ev, std::any data);
void IN_COMBAT(game::Event ev);
void COMBAT_ROTATE(game::Event ev);
void BOSS_FIGHT(game::Event ev, std::any data);
void LOOTING(game::Event ev, std::any data);
void CUT_SCENE_PLAYING(game::Event ev, std::any data);
void END(game::Event ev);
void try_move(int dir, bool strafe);
sf::Vector2f mouse_position();
void mouse_action(guecs::Modifiers mods);
void handle_keyboard_mouse();
void draw_gui();
void render();
bool active();
void run_systems();
void handle_world_events();
void handle_boss_fight_events();
void next_level(bool bossfight);
void debug_render();
void take_screenshot();
};
}

40
src/gui/guecstra.cpp Normal file
View file

@ -0,0 +1,40 @@
#include "gui/guecstra.hpp"
#include "game_level.hpp"
namespace guecs {
Clickable make_action(guecs::Entity gui_id, game::Event event) {
return {[&, gui_id, event](auto){
auto world = GameDB::current_world();
world->send<game::Event>(event, gui_id, {});
}};
}
Clickable make_action(guecs::Entity gui_id, game::Event event, std::any data) {
return {[&, event, data](auto){
auto world = GameDB::current_world();
world->send<game::Event>(event, gui_id, data);
}};
}
DinkyECS::Entity GrabSource::grab() {
fmt::println("> Grab entity {}", world_entity);
return world_entity;
}
void GrabSource::setSprite(guecs::UI& gui, guecs::Entity gui_id) {
if(auto sp = gui.get_if<guecs::Icon>(gui_id)) {
sprite = sp->sprite;
} else if(auto sp = gui.get_if<guecs::Sprite>(gui_id)) {
sprite = sp->sprite;
} else {
dbc::sentinel("GrabSource given sprite gui_id that doesn't exist");
}
}
void GrabSource::move(sf::Vector2f pos) {
if(sprite) {
sprite->setPosition(pos);
}
}
}

24
src/gui/guecstra.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include "components.hpp"
#include "events.hpp"
#include <guecs/ui.hpp>
#include "textures.hpp"
namespace guecs {
Clickable make_action(guecs::Entity gui_id, game::Event event);
Clickable make_action(guecs::Entity gui_id, game::Event event, std::any data);
struct GrabSource {
DinkyECS::Entity world_entity;
std::function<void()> commit;
std::shared_ptr<sf::Sprite> sprite = nullptr;
DinkyECS::Entity grab();
void setSprite(guecs::UI& gui, guecs::Entity gui_id);
void move(sf::Vector2f pos);
};
struct DropTarget {
std::function<bool(DinkyECS::Entity world_entity)> commit;
};
}

154
src/gui/loot_ui.cpp Normal file
View file

@ -0,0 +1,154 @@
#include "gui/loot_ui.hpp"
#include "constants.hpp"
#include <fmt/xchar.h>
#include "systems.hpp"
#include "game_level.hpp"
namespace gui {
using namespace guecs;
LootUI::LootUI() :
$temp_loot(GameDB::current_world()->entity()),
$target($temp_loot)
{
$gui.position(RAY_VIEW_X+RAY_VIEW_WIDTH/2-200,
RAY_VIEW_Y+RAY_VIEW_HEIGHT/2-200, 400, 400);
$gui.layout(
"[=item_0 | =item_1 |=item_2 |=item_3 ]"
"[=item_4 | =item_5 |=item_6 |=item_7 ]"
"[=item_8 | =item_9 |=item_10|=item_11]"
"[=item_12| =item_13|=item_14|=item_15 ]"
"[ =take_all | =close| =destroy]");
auto world = GameDB::current_world();
world->set<inventory::Model>($temp_loot, {});
world->make_constant($temp_loot);
}
void LootUI::make_button(const std::string &name, const std::wstring& label, game::Event event) {
auto button = $gui.entity(name);
$gui.set<guecs::Rectangle>(button, {});
$gui.set<guecs::Text>(button, {label});
$gui.set<guecs::Clickable>(button,
guecs::make_action(button, event));
}
void LootUI::init() {
using guecs::THEME;
auto bg_color = THEME.DARK_LIGHT;
bg_color.a = 140;
$gui.set<Background>($gui.MAIN, {$gui.$parser, bg_color});
make_button("close", L"CLOSE", game::Event::LOOT_CLOSE);
make_button("take_all", L"TAKE ALL", game::Event::LOOT_CLOSE);
make_button("destroy", L"DESTROY", game::Event::LOOT_CLOSE);
for(int i = 0; i < INV_SLOTS; i++) {
auto name = fmt::format("item_{}", i);
auto id = $gui.entity(name);
$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(id, game::Event::LOOT_SELECT, {id})
});
}
$gui.init();
update();
}
void LootUI::update() {
auto world = GameDB::current_world();
dbc::check(world->has<inventory::Model>($target),
"update called but $target isn't in world");
auto& contents = world->get<inventory::Model>($target);
for(size_t i = 0; i < INV_SLOTS; i++) {
auto id = $gui.entity("item_", int(i));
auto& slot_name = $gui.name_for(id);
if(contents.has(slot_name)) {
auto item = contents.get(slot_name);
dbc::check(world->has<components::Sprite>(item),
"item in inventory UI doesn't exist in world. New level?");
auto& sprite = world->get<components::Sprite>(item);
$gui.set_init<guecs::Icon>(id, {sprite.name});
guecs::GrabSource grabber{
item, [&, id]() { return remove_slot(id); }};
grabber.setSprite($gui, id);
$gui.set<guecs::GrabSource>(id, grabber);
} else {
// BUG: fix remove so it's safe to call on empty
if($gui.has<guecs::GrabSource>(id)) {
$gui.remove<guecs::Icon>(id);
$gui.remove<guecs::GrabSource>(id);
}
$gui.set<guecs::DropTarget>(id, {
[&, id](DinkyECS::Entity world_entity) -> bool { return place_slot(id, world_entity); }
});
}
}
}
void LootUI::remove_slot(guecs::Entity slot_id) {
auto& name = $gui.name_for(slot_id);
fmt::println("LootUI remove slot inv::Model id={} slot={}", $target, name);
System::remove_from_container($target, name);
update();
}
bool LootUI::place_slot(guecs::Entity id, DinkyECS::Entity world_entity) {
fmt::println("LootUI target={} placing world entity {} in slot id {}",
$target, id, world_entity);
auto& name = $gui.name_for(id);
bool worked = System::place_in_container($target, name, world_entity);
if(worked) update();
return worked;
}
void LootUI::render(sf::RenderWindow& window) {
$gui.render(window);
}
void LootUI::update_level() {
init();
}
void LootUI::add_loose_item(DinkyECS::Entity entity) {
System::place_in_container($temp_loot, "item_0", entity);
set_target($temp_loot);
update();
}
void LootUI::drop_item(DinkyECS::Entity item_id) {
System::drop_item(item_id);
update();
}
bool LootUI::mouse(float x, float y, guecs::Modifiers mods) {
return $gui.mouse(x, y, mods);
}
bool LootUI::occupied(guecs::Entity slot) {
return System::inventory_occupied($target, $gui.name_for(slot));
}
void LootUI::swap(guecs::Entity gui_a, guecs::Entity gui_b) {
if(gui_a != gui_b) {
auto& a_name = $gui.name_for(gui_a);
auto& b_name = $gui.name_for(gui_b);
System::inventory_swap($target, a_name, b_name);
}
update();
}
}

37
src/gui/loot_ui.hpp Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include "gui/guecstra.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Font.hpp>
#include <guecs/ui.hpp>
#include "events.hpp"
#include "inventory.hpp"
namespace gui {
class LootUI {
public:
bool active = false;
guecs::UI $gui;
DinkyECS::Entity $temp_loot = DinkyECS::NONE;
DinkyECS::Entity $target = DinkyECS::NONE;
LootUI();
void set_target(DinkyECS::Entity entity) {
$target = entity;
}
void init();
void update();
void render(sf::RenderWindow& window);
void update_level();
bool mouse(float x, float y, guecs::Modifiers mods);
void make_button(const std::string &name, const std::wstring& label, game::Event event);
void remove_slot(guecs::Entity slot_id);
bool place_slot(guecs::Entity gui_id, DinkyECS::Entity world_entity);
void add_loose_item(DinkyECS::Entity entity);
void drop_item(DinkyECS::Entity item_id);
bool occupied(guecs::Entity gui_id);
void swap(guecs::Entity gui_a, guecs::Entity gui_b);
};
}

155
src/gui/main_ui.cpp Normal file
View file

@ -0,0 +1,155 @@
#include "gui/main_ui.hpp"
#include "components.hpp"
#include <fmt/xchar.h>
#include "animation.hpp"
#include "constants.hpp"
#include "game_level.hpp"
#include "ai.hpp"
namespace gui {
using namespace components;
MainUI::MainUI(sf::RenderWindow& window) :
$window(window),
$rayview(std::make_shared<Raycaster>(RAY_VIEW_WIDTH, RAY_VIEW_HEIGHT))
{
$window.setVerticalSyncEnabled(VSYNC);
$window.setFramerateLimit(FRAME_LIMIT);
auto config = settings::get("config");
$hand = textures::get_sprite(config["player"]["hands"]);
$hand_anim = animation::load("assets/animation.json", config["player"]["hands"]);
}
void MainUI::dirty() {
$needs_render = true;
}
void MainUI::init() {
auto& player_position = GameDB::player_position();
auto player = player_position.location;
$rayview->init_shaders();
$rayview->set_position(RAY_VIEW_X, RAY_VIEW_Y);
$rayview->position_camera(player.x + 0.5, player.y + 0.5);
$overlay_ui.init();
}
void MainUI::render() {
if($needs_render) $rayview->render();
$rayview->draw($window);
if($mind_reading) render_mind_reading();
$overlay_ui.render($window);
if($hand_anim.playing) render_hands();
}
lel::Cell MainUI::overlay_cell(const std::string& name) {
return $overlay_ui.$gui.cell_for(name);
}
std::optional<Point> MainUI::play_rotate() {
if($rayview->play_rotate()) {
$needs_render = false;
return std::make_optional<Point>($rayview->aiming_at);
} else {
$needs_render = true;
return std::nullopt;
}
}
std::optional<components::Position> MainUI::play_move() {
if($rayview->play_move()) {
$needs_render = false;
return std::make_optional<Position>(
$rayview->camera_at,
$rayview->aiming_at);
} else {
$needs_render = true;
return std::nullopt;
}
}
void MainUI::plan_rotate(int dir, float amount) {
// -1 is left, 1 is right
int extra = (amount == 0.5) * dir;
$compass_dir = ($compass_dir + dir + extra) % COMPASS.size();
$rayview->plan_rotate(dir, amount);
}
Point MainUI::plan_move(int dir, bool strafe) {
return $rayview->plan_move(dir, strafe);
}
void MainUI::abort_plan() {
$rayview->abort_plan();
}
void MainUI::dead_entity(DinkyECS::Entity entity) {
auto world = GameDB::current_world();
if(world->has<components::Sprite>(entity)) {
auto &sprite = world->get<components::Sprite>(entity);
$rayview->update_sprite(entity, sprite);
}
}
void MainUI::toggle_mind_reading() {
$mind_reading = !$mind_reading;
if($mind_reading) {
render_mind_reading();
} else {
$overlay_ui.close_text("left");
}
}
void MainUI::render_mind_reading() {
auto level = GameDB::current_level();
if(auto entity = level.collision->occupied_by($rayview->aiming_at)) {
if(auto enemy_ai = level.world->get_if<ai::EntityAI>(entity)) {
$overlay_ui.show_text("left", fmt::format(L"AI: {}",
guecs::to_wstring(enemy_ai->to_string())));
} else {
$overlay_ui.show_text("left", L"no mind to read");
}
} else {
$overlay_ui.show_text("left", L"nothing there");
}
}
void MainUI::update_level() {
auto& level = GameDB::current_level();
auto& player_position = GameDB::player_position();
auto player = player_position.location;
$rayview->update_level(level);
$rayview->position_camera(player.x + 0.5, player.y + 0.5);
player_position.aiming_at = $rayview->aiming_at;
$compass_dir = 0;
$overlay_ui.update_level();
dirty();
}
void MainUI::mouse(int x, int y, guecs::Modifiers mods) {
$overlay_ui.$gui.mouse(x, y, mods);
}
void MainUI::play_hands() {
if(!$hand_anim.playing) $hand_anim.play();
}
void MainUI::render_hands() {
if($hand_anim.playing) {
$hand_anim.update();
$hand_anim.apply(*$hand.sprite);
$hand.sprite->setPosition({RAY_VIEW_X, RAY_VIEW_Y});
$window.draw(*$hand.sprite);
}
}
}

52
src/gui/main_ui.hpp Normal file
View file

@ -0,0 +1,52 @@
#pragma once
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/System/Clock.hpp>
#include "stats.hpp"
#include <guecs/ui.hpp>
#include "gui/overlay_ui.hpp"
#include "gui/debug_ui.hpp"
#include "raycaster.hpp"
#include <optional>
namespace animation {
class Animation;
}
namespace gui {
class MainUI {
public:
int $compass_dir = 0;
bool $needs_render = true;
bool $mind_reading = false;
sf::Clock $clock;
sf::RenderWindow& $window;
OverlayUI $overlay_ui;
std::shared_ptr<Raycaster> $rayview;
textures::SpriteTexture $hand;
animation::Animation $hand_anim;
MainUI(sf::RenderWindow& window);
void mouse(int x, int y, guecs::Modifiers mods);
void debug();
void render_debug();
void plan_rotate(int dir, float amount);
std::optional<Point> play_rotate();
std::optional<components::Position> play_move();
Point plan_move(int dir, bool strafe);
void abort_plan();
void update_level();
void init();
void render();
void dirty();
lel::Cell overlay_cell(const std::string& name);
void dead_entity(DinkyECS::Entity entity);
void toggle_mind_reading();
void render_mind_reading();
void play_hands();
void render_hands();
};
}

75
src/gui/map_view.cpp Normal file
View file

@ -0,0 +1,75 @@
#include "map_view.hpp"
#include <functional>
#include <string>
#include "dbc.hpp"
#include "components.hpp"
#include "rand.hpp"
#include "systems.hpp"
#include "rand.hpp"
#include <codecvt>
#include <iostream>
#include <fmt/xchar.h>
#include <fstream>
#include "palette.hpp"
#include "game_level.hpp"
constexpr const int MAP_WIDTH=13;
constexpr const int MAP_HEIGHT=13;
namespace gui {
using namespace components;
using namespace guecs;
MapViewUI::MapViewUI() :
$map_render(std::make_shared<sf::RenderTexture>()),
$map_sprite($map_render->getTexture()),
$map_tiles(matrix::make(MAP_WIDTH, MAP_HEIGHT))
{
auto world = GameDB::current_world();
auto player = GameDB::the_player();
$player_display = world->get<Tile>(player).display;
}
void MapViewUI::init() {
$gui.position(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
$gui.layout("[log_view| *%(200)map_grid | _ ]");
$gui.set<Background>($gui.MAIN, {$gui.$parser, palette::get("tiles/fg:wall_plain")});
$log_to = $gui.entity("log_view");
$gui.set<Rectangle>($log_to, {10, THEME.DARK_MID, THEME.BORDER_COLOR, 10});
$gui.set<Text>($log_to, {L"Welcome to the Game!", 25, THEME.TEXT_COLOR, 10});
auto map_cell = lel::center(MAP_TILE_DIM * MAP_WIDTH, MAP_TILE_DIM * MAP_HEIGHT, $gui.cell_for("map_grid"));
$map_sprite.setPosition({(float)map_cell.x, (float)map_cell.y + 30});
$gui.init();
}
void MapViewUI::render(sf::RenderWindow &window, int compass_dir) {
$gui.render(window);
System::draw_map($map_tiles, $entity_map);
System::render_map($map_tiles, $entity_map, *$map_render, compass_dir, $player_display);
$map_sprite.setTexture($map_render->getTexture(), true);
window.draw($map_sprite);
// $gui.debug_layout(window);
}
void MapViewUI::update() {
if(auto text = $gui.get_if<Text>($log_to)) {
//BUG: I'm calling this what it is, fix it
wstring log_garbage;
for(auto msg : $messages) {
log_garbage += msg + L"\n";
}
text->update(log_garbage);
}
}
void MapViewUI::log(wstring msg) {
$messages.push_front(msg);
if($messages.size() > MAX_LOG_MESSAGES) {
$messages.pop_back();
}
update();
}
}

27
src/gui/map_view.hpp Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "textures.hpp"
#include "matrix.hpp"
#include <guecs/ui.hpp>
#include <string>
#include "dinkyecs.hpp"
#include "map.hpp"
namespace gui {
class MapViewUI {
public:
guecs::UI $gui;
wchar_t $player_display = L'@';
DinkyECS::Entity $log_to;
EntityGrid $entity_map;
std::deque<std::wstring> $messages;
std::shared_ptr<sf::RenderTexture> $map_render;
sf::Sprite $map_sprite;
matrix::Matrix $map_tiles;
MapViewUI();
void init();
void render(sf::RenderWindow &window, int compass_dir);
void log(std::wstring msg);
void update();
};
}

33
src/gui/mini_map.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "mini_map.hpp"
#include <functional>
#include <string>
#include "dbc.hpp"
#include "components.hpp"
#include "rand.hpp"
#include "systems.hpp"
#include "rand.hpp"
#include <codecvt>
#include <iostream>
#include <memory>
namespace gui {
using namespace components;
MiniMapUI::MiniMapUI() :
$map_grid{L"...", 45, {200, 200, 200, 100}, 10}
{
$font = std::make_shared<sf::Font>(FONT_FILE_NAME);
}
void MiniMapUI::init(guecs::UI& overlay) {
auto top_right = overlay.entity("top_right");
auto cell = overlay.cell_for(top_right);
$map_grid.init(cell, $font);
}
void MiniMapUI::render(sf::RenderWindow &window, int compass_dir) {
(void)compass_dir;
$map_grid.update(L"I'M BROKEN");
window.draw(*$map_grid.text);
}
}

17
src/gui/mini_map.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "textures.hpp"
#include <guecs/ui.hpp>
#include <memory>
namespace gui {
class MiniMapUI {
public:
guecs::Text $map_grid;
guecs::UI $gui;
std::shared_ptr<sf::Font> $font = nullptr;
MiniMapUI();
void init(guecs::UI& overlay);
void render(sf::RenderWindow &window, int compass_dir);
};
}

63
src/gui/overlay_ui.cpp Normal file
View file

@ -0,0 +1,63 @@
#include "gui/overlay_ui.hpp"
#include "gui/guecstra.hpp"
#include "constants.hpp"
#include "events.hpp"
#include <optional>
#include "game_level.hpp"
namespace gui {
using namespace guecs;
OverlayUI::OverlayUI() {
$gui.position(RAY_VIEW_X, RAY_VIEW_Y, RAY_VIEW_WIDTH, RAY_VIEW_HEIGHT);
$gui.layout(
"[*%(100,300)left|=top|>(170,170)top_right]"
"[_|=middle|=middle_right]"
"[_|=bottom|=bottom_right]"
);
$gui.init();
}
inline void make_clickable_area(guecs::UI &gui, const std::string &name) {
auto area = gui.entity(name);
gui.set<Clickable>(area, {
[&](auto) {
auto world = GameDB::current_world();
world->send<game::Event>(game::Event::AIM_CLICK, area, {});
}
});
}
void OverlayUI::init() {
// gui.init is in the constructor
make_clickable_area($gui, "top");
make_clickable_area($gui, "middle");
make_clickable_area($gui, "bottom");
}
void OverlayUI::render(sf::RenderWindow& window) {
$gui.render(window);
// $gui.debug_layout(window);
}
void OverlayUI::show_sprite(string region, string sprite_name) {
$gui.show_sprite(region, sprite_name);
}
void OverlayUI::close_sprite(string region) {
$gui.close<Sprite>(region);
}
void OverlayUI::show_text(string region, wstring content) {
$gui.show_text(region, content);
}
void OverlayUI::close_text(string region) {
$gui.close<Text>(region);
}
void OverlayUI::update_level() {
init();
}
}

25
src/gui/overlay_ui.hpp Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Font.hpp>
#include <guecs/ui.hpp>
namespace gui {
using std::string;
class OverlayUI {
public:
guecs::UI $gui;
OverlayUI();
void init();
void update_level();
void render(sf::RenderWindow& window);
void show_sprite(string region, string sprite_name);
void close_sprite(string region);
void show_text(std::string region, std::wstring content);
void update_text(std::string region, std::wstring content);
void close_text(std::string region);
};
}

264
src/gui/ritual_ui.cpp Normal file
View file

@ -0,0 +1,264 @@
#include "gui/ritual_ui.hpp"
#include <guecs/ui.hpp>
#include "rand.hpp"
#include "sound.hpp"
#include "events.hpp"
#include "game_level.hpp"
namespace gui {
namespace ritual {
using namespace guecs;
using std::any, std::any_cast, std::string, std::make_any;
UI::UI() {
$gui.position(STATUS_UI_X, STATUS_UI_Y, STATUS_UI_WIDTH, STATUS_UI_HEIGHT);
$gui.layout(
"[_]"
"[inv_slot0 | inv_slot1 | inv_slot2| inv_slot3]"
"[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]"
"[_ |=*%(200,400)result_text|_]"
"[*%(100,200)result_image|_ |_]"
"[_|_|_]"
"[_|_|_]"
"[_]"
"[ ritual_ui ]");
}
void UI::event(Event ev, std::any data) {
switch($state) {
FSM_STATE(State, START, 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);
}
}
void UI::START(Event) {
$ritual_ui = textures::get_sprite("ritual_crafting_area");
$ritual_ui.sprite->setPosition($gui.get_position());
$ritual_ui.sprite->setTextureRect($ritual_closed_rect);
$ritual_anim = animation::load("assets/animation.json", "ritual_crafting_area");
auto open_close_toggle = $gui.entity("ritual_ui");
$gui.set<Clickable>(open_close_toggle, {
[&](auto){ event(Event::TOGGLE); }
});
$craft_state = $ritual_engine.start();
$gui.init();
play_blanket("idle");
state(State::CLOSED);
}
void UI::OPENED(Event ev, std::any data) {
if(ev == Event::TOGGLE) {
clear_blanket();
play_blanket("close");
state(State::CLOSING);
} else if(ev == Event::SELECT) {
// do this before transitioning
state(State::CRAFTING);
UI::CRAFTING(ev, data);
}
}
void UI::CRAFTING(Event ev, std::any data) {
if(ev == Event::TOGGLE) {
clear_blanket();
play_blanket("close");
state(State::CLOSING);
} else if(ev == Event::COMBINE) {
complete_combine();
} 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);
update_selection_state();
}
}
void UI::CLOSED(Event ev) {
if(ev == Event::TOGGLE) {
load_blanket();
play_blanket("open");
state(State::OPENING);
}
}
void UI::OPENING(Event ev) {
if(ev == Event::TICK) {
if($ritual_anim.playing) {
$ritual_anim.update();
$ritual_anim.apply(*$ritual_ui.sprite);
} else {
state(State::OPENED);
}
}
}
void UI::CLOSING(Event ev) {
if($ritual_anim.playing) {
$ritual_anim.update();
$ritual_anim.apply(*$ritual_ui.sprite);
} else {
state(State::CLOSED);
}
}
bool UI::mouse(float x, float y, guecs::Modifiers mods) {
return $gui.mouse(x, y, mods);
}
bool UI::is_open() {
return !in_state(State::CLOSED);
}
void UI::render(sf::RenderWindow &window) {
event(Event::TICK);
window.draw(*$ritual_ui.sprite);
if(in_state(State::OPENED) || in_state(State::CRAFTING)) {
$gui.render(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::update_selection_state() {
if(blanket().no_selections()) {
clear_craft_result();
state(State::OPENED);
} else {
run_crafting_engine();
show_craft_result();
}
}
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++);
$gui.set_init<Sprite>(slot_id, {item});
$gui.set<Clickable>(slot_id, {
[&, slot_id, item_id](auto) {
auto data = std::make_any<SelectedItem>(slot_id, item_id);
event(Event::SELECT, data);
}
});
}
for(; i < INV_SLOTS; i++) {
auto slot_id = $gui.entity("inv_slot", i);
$gui.remove<Sprite>(slot_id);
$gui.remove<Clickable>(slot_id);
}
}
void UI::complete_combine() {
auto world = GameDB::current_world();
auto player = GameDB::the_player();
if($craft_state.is_combined()) {
auto ritual = $ritual_engine.finalize($craft_state);
auto& belt = world->get_the<::ritual::Belt>();
belt.equip(belt.next(), ritual);
world->send<game::Event>(game::Event::NEW_RITUAL, player, {});
blanket().consume_crafting();
clear_craft_result();
load_blanket();
state(State::OPENED);
}
}
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);
auto combine = $gui.entity("result_image");
if($craft_state.is_combined()) {
$gui.show_text("result_text", L"This might work...");
switch(ritual.element) {
case FIRE:
$gui.show_sprite("result_image", "broken_yoyo");
break;
case LIGHTNING:
$gui.show_sprite("result_image", "pocket_watch");
break;
default:
$gui.show_sprite("result_image", "severed_finger");
}
$gui.set<Clickable>(combine, {
[&](auto){ event(Event::COMBINE); }
});
} else {
$gui.show_text("result_text", L"That won't work.");
$gui.show_sprite("result_image", "dubious_combination");
$gui.remove<Clickable>(combine);
return;
}
}
void UI::clear_craft_result() {
blanket().reset();
$gui.close<Text>("result_text");
$gui.close<Sprite>("result_image");
}
void UI::play_blanket(const std::string& form) {
$ritual_anim.set_form(form);
$ritual_anim.play();
}
::ritual::Blanket& UI::blanket() {
auto world = GameDB::current_world();
return world->get_the<::ritual::Blanket>();
}
}
}

69
src/gui/ritual_ui.hpp Normal file
View file

@ -0,0 +1,69 @@
#pragma once
#include "constants.hpp"
#include <deque>
#include "textures.hpp"
#include <guecs/ui.hpp>
#include "rituals.hpp"
#include "simplefsm.hpp"
#include "animation.hpp"
namespace gui {
namespace ritual {
enum class State {
START=0,
OPENED=1,
CLOSED=2,
OPENING=3,
CLOSING=4,
CRAFTING=5
};
enum class Event {
STARTED=0,
TOGGLE=1,
TICK=2,
SELECT=3,
COMBINE=4
};
struct SelectedItem {
guecs::Entity slot_id;
::ritual::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}};
animation::Animation $ritual_anim;
guecs::UI $gui;
textures::SpriteTexture $ritual_ui;
::ritual::Engine $ritual_engine;
::ritual::CraftingState $craft_state;
UI();
void event(Event ev, std::any data={});
void START(Event);
void OPENED(Event, std::any data={});
void CRAFTING(Event, std::any data={});
void CLOSED(Event);
void OPENING(Event);
void CLOSING(Event);
bool mouse(float x, float y, guecs::Modifiers mods);
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 run_crafting_engine();
void complete_combine();
void update_selection_state();
void play_blanket(const std::string& form);
::ritual::Blanket& blanket();
};
}
}

156
src/gui/status_ui.cpp Normal file
View file

@ -0,0 +1,156 @@
#include "gui/status_ui.hpp"
#include "components.hpp"
#include <guecs/ui.hpp>
#include "rand.hpp"
#include <fmt/xchar.h>
#include "gui/guecstra.hpp"
#include "systems.hpp"
#include "inventory.hpp"
#include "game_level.hpp"
namespace gui {
using namespace guecs;
using std::any, std::any_cast, std::string, std::make_any;
StatusUI::StatusUI()
{
$gui.position(STATUS_UI_X, STATUS_UI_Y, STATUS_UI_WIDTH, STATUS_UI_HEIGHT);
$gui.layout(
"[ritual_ui]"
"[=earring|=armor_head|=amulet]"
"[=back|=*%(200,300)character_view|_|=armor_bdy]"
"[=hand_r|_|_ |=hand_l]"
"[=ring_r|_|_ |=ring_l]"
"[=pocket_r|=armor_leg|=pocket_l]");
}
void StatusUI::init() {
$gui.set<Background>($gui.MAIN, {$gui.$parser, });
for(auto& [name, cell] : $gui.cells()) {
auto gui_id = $gui.entity(name);
if(name == "character_view") {
$gui.set<Rectangle>(gui_id, {});
$gui.set<Sprite>(gui_id, {"peasant_girl"});
} else {
$gui.set<Rectangle>(gui_id, {});
if(name == "ritual_ui") {
$gui.set<Clickable>(gui_id, {
[this](auto){ select_ritual(); }
});
$gui.set<Sound>(gui_id, {"pickup"});
} else {
$gui.set<Text>(gui_id, {guecs::to_wstring(name)});
$gui.set<Clickable>(gui_id, {
guecs::make_action(gui_id, game::Event::INV_SELECT, {gui_id})
});
$gui.set<DropTarget>(gui_id, {
.commit=[&, gui_id](DinkyECS::Entity world_target) -> bool {
return place_slot(gui_id, world_target);
}
});
}
}
}
$ritual_ui.event(ritual::Event::STARTED);
$gui.init();
update();
}
bool StatusUI::mouse(float x, float y, guecs::Modifiers mods) {
if($ritual_ui.is_open()) {
return $ritual_ui.mouse(x, y, mods);
} else {
return $gui.mouse(x, y, mods);
}
}
void StatusUI::select_ritual() {
$ritual_ui.event(ritual::Event::TOGGLE);
}
void StatusUI::update() {
auto world = GameDB::current_world();
auto player = world->get_the<components::Player>();
auto& inventory = world->get<inventory::Model>(player.entity);
for(const auto& [slot, cell] : $gui.cells()) {
if(inventory.has(slot)) {
auto gui_id = $gui.entity(slot);
auto world_entity = inventory.get(slot);
auto& sprite = world->get<components::Sprite>(world_entity);
$gui.set_init<guecs::Icon>(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);
} else {
auto gui_id = $gui.entity(slot);
if($gui.has<guecs::GrabSource>(gui_id)) {
$gui.remove<guecs::GrabSource>(gui_id);
$gui.remove<guecs::Icon>(gui_id);
}
}
}
}
void StatusUI::render(sf::RenderWindow &window) {
$gui.render(window);
// $gui.debug_layout(window);
$ritual_ui.render(window);
}
void StatusUI::update_level() {
init();
}
bool StatusUI::place_slot(guecs::Entity gui_id, DinkyECS::Entity world_entity) {
auto& level = GameDB::current_level();
auto& slot_name = $gui.name_for(gui_id);
auto& inventory = level.world->get<inventory::Model>(level.player);
if(inventory.add(slot_name, world_entity)) {
level.world->make_constant(world_entity);
update();
return true;
} else {
dbc::log("there's something there already");
return false;
}
}
void StatusUI::drop_item(DinkyECS::Entity item_id) {
System::drop_item(item_id);
update();
}
// NOTE: do I need this or how does it relate to drop_item?
void StatusUI::remove_slot(guecs::Entity slot_id) {
auto player = GameDB::the_player();
auto& slot_name = $gui.name_for(slot_id);
System::remove_from_container(player, slot_name);
update();
}
void StatusUI::swap(guecs::Entity gui_a, guecs::Entity gui_b) {
if(gui_a != gui_b) {
auto player = GameDB::the_player();
auto& a_name = $gui.name_for(gui_a);
auto& b_name = $gui.name_for(gui_b);
System::inventory_swap(player, a_name, b_name);
}
update();
}
bool StatusUI::occupied(guecs::Entity slot) {
auto player = GameDB::the_player();
return System::inventory_occupied(player, $gui.name_for(slot));
}
}

35
src/gui/status_ui.hpp Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include "constants.hpp"
#include <deque>
#include "textures.hpp"
#include <guecs/ui.hpp>
#include "gui/ritual_ui.hpp"
#include "gui/guecstra.hpp"
namespace gui {
class StatusUI {
public:
guecs::UI $gui;
ritual::UI $ritual_ui;
explicit StatusUI();
StatusUI(const StatusUI& other) = delete;
StatusUI(StatusUI&& other) = delete;
~StatusUI() = default;
void select_ritual();
void update_level();
void init();
void render(sf::RenderWindow &window);
void update();
bool mouse(float x, float y, guecs::Modifiers mods);
void remove_slot(guecs::Entity slot_id);
bool place_slot(guecs::Entity gui_id, DinkyECS::Entity world_entity);
void drop_item(DinkyECS::Entity item_id);
void swap(guecs::Entity gui_a, guecs::Entity gui_b);
bool occupied(guecs::Entity slot);
};
}