Simple Loot UI started.

This commit is contained in:
Zed A. Shaw 2025-05-14 00:51:53 -04:00
parent 8a3046e141
commit 8545b8cf1d
23 changed files with 139 additions and 29 deletions

156
gui/boss_fight_ui.cpp Normal file
View file

@ -0,0 +1,156 @@
#include "gui/boss_fight_ui.hpp"
#include "easings.hpp"
#include "sound.hpp"
#include <fmt/xchar.h>
namespace gui {
using namespace guecs;
BossFightUI::BossFightUI(shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id)
: $world(world),
$boss_id(boss_id),
$config(world->get_the<components::GameConfig>())
{
$status.position(0, 0, BOSS_VIEW_X, SCREEN_HEIGHT);
$status.layout(
"[main_status]"
"[(150)status_3|(150)status_4]"
"[(150)status_5|(150)status_6]"
"[(150)status_7|(150)status_8]");
$overlay.position(BOSS_VIEW_X, BOSS_VIEW_Y,
BOSS_VIEW_WIDTH, BOSS_VIEW_HEIGHT);
$overlay.layout("[overlay_1|overlay_2|overlay_4]"
"[overlay_5|overlay_6|overlay_8]"
"[overlay_9|overlay_10|overlay_12]"
"[overlay_13|overlay_14|overlay_16]");
$sounds = $world->get<components::Sound>($boss_id);
$combat = $world->get<components::Combat>($boss_id);
}
void BossFightUI::configure_sprite() {
$sprite_config = $world->get<components::Sprite>($boss_id);
$animation = $world->get<components::Animation>($boss_id);
$animation.frame_width = $sprite_config.width;
$boss_image = textures::get($sprite_config.name);
sf::IntRect frame_rect{{0,0},{$sprite_config.width,$sprite_config.height}};
$boss_image.sprite->setTextureRect(frame_rect);
$boss_image.sprite->setScale({$sprite_config.scale, $sprite_config.scale});
auto bounds = $boss_image.sprite->getLocalBounds();
auto bg_bounds = $boss_background.sprite->getLocalBounds();
float x_diff = bg_bounds.size.x / 2;
$boss_pos = {float(BOSS_VIEW_X) + x_diff, bounds.size.y / 2};
$boss_image.sprite->setOrigin({bounds.size.x / 2, bounds.size.y / 2});
$boss_image.sprite->setPosition($boss_pos);
}
void BossFightUI::configure_background() {
auto& boss = $world->get<components::BossFight>($boss_id);
$boss_background = textures::get(boss.background);
$boss_background.sprite->setPosition({BOSS_VIEW_X, BOSS_VIEW_Y});
$status.set<Background>($status.MAIN, {$status.$parser});
if(boss.stage) {
$boss_has_stage = true;
$boss_stage = textures::get(*boss.stage);
$boss_stage.sprite->setPosition({BOSS_VIEW_X, BOSS_VIEW_Y});
}
}
void BossFightUI::configure_gui() {
for(auto& [name, cell] : $status.cells()) {
auto button = $status.entity(name);
$status.set<Rectangle>(button, {});
$status.set<Clickable>(button, {
[this, name](auto, auto){
dbc::log(fmt::format("STATUS: {}", name));
}
});
if(name == "main_status") {
$status.set<Textual>(button, {fmt::format(L"HP: {}", $combat.hp)});
} else {
$status.set<Label>(button, {L"Attack"});
}
}
$status.init();
for(auto& [name, cell] : $overlay.cells()) {
auto region = $overlay.entity(name);
$overlay.set<Clickable>(region, {
[this, name](auto, auto){
dbc::log(fmt::format("OVERLAY: {}", name));
}
});
}
$overlay.init();
}
void BossFightUI::init() {
// background must come first
configure_background();
configure_sprite();
configure_gui();
}
void BossFightUI::bounce_boss(sf::RenderWindow& window) {
sf::IntRect frame_rect{{0,0},{$sprite_config.width,$sprite_config.height}};
sf::Vector2f scale{$sprite_config.scale, $sprite_config.scale};
sf::Vector2f pos{$boss_pos.x, $boss_pos.y};
$animation.step(scale, pos, frame_rect);
$boss_image.sprite->setScale(scale);
if($animation.stationary) $boss_image.sprite->setPosition(pos);
if(!sound::playing($sounds.attack) && $animation.current == 1) {
sound::play($sounds.attack);
}
$boss_image.sprite->setTextureRect(frame_rect);
window.draw(*$boss_image.sprite);
}
void BossFightUI::render(sf::RenderWindow& window) {
window.draw(*$boss_background.sprite);
if($boss_hit) {
bounce_boss(window);
} else {
window.draw(*$boss_image.sprite);
}
if($boss_has_stage) {
window.draw(*$boss_stage.sprite);
}
if($combat.hp == 0) {
$overlay.show_label("overlay_1", L"YOU WON!");
$overlay.show_label("overlay_4", L"CLICK TO CONTINUE...");
}
$status.render(window);
$overlay.render(window);
}
bool BossFightUI::mouse(float x, float y, bool hover) {
if($status.mouse(x, y, hover)) {
dbc::log("STATUS button pressed");
}
if($overlay.mouse(x, y, hover)) {
$animation.play();
sound::play("Sword_Hit_1");
$boss_hit = !$boss_hit;
$combat.hp--;
}
return false;
}
}

48
gui/boss_fight_ui.hpp Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Font.hpp>
#include <guecs/ui.hpp>
#include "textures.hpp"
#include "components.hpp"
#include <SFML/System/Clock.hpp>
// aspect ratio of art is 3/2 so 1.5
// possible sizes: 900/600; 1620/1080; 1800/1200
// To calculate it do short side * 1.5 so 1080 * 1.5 == 1620
//
// Side panel = 300/1080
namespace gui {
using std::string;
class BossFightUI {
public:
sf::Clock $clock;
bool $boss_hit = false;
sf::Vector2f $boss_pos;
components::Combat $combat;
components::Sprite $sprite_config;
components::Sound $sounds;
components::Animation $animation;
guecs::UI $status;
guecs::UI $overlay;
textures::SpriteTexture $boss_image;
textures::SpriteTexture $boss_background;
bool $boss_has_stage = false;
textures::SpriteTexture $boss_stage;
std::shared_ptr<DinkyECS::World> $world = nullptr;
DinkyECS::Entity $boss_id;
components::GameConfig& $config;
BossFightUI(std::shared_ptr<DinkyECS::World> world, DinkyECS::Entity boss_id);
void init();
void render(sf::RenderWindow& window);
bool mouse(float x, float y, bool hover);
void bounce_boss(sf::RenderWindow& window);
bool boss_dead() { return $combat.hp < 0; }
void configure_sprite();
void configure_background();
void configure_gui();
};
}

86
gui/combat_ui.cpp Normal file
View file

@ -0,0 +1,86 @@
#include "gui/combat_ui.hpp"
#include "constants.hpp"
#include "color.hpp"
#include "rituals.hpp"
#include <fmt/xchar.h>
#include "guecstra.hpp"
namespace gui {
using namespace guecs;
CombatUI::CombatUI(GameLevel level) :
$level(level)
{
$gui.position(COMBAT_UI_X, COMBAT_UI_Y, COMBAT_UI_WIDTH, COMBAT_UI_HEIGHT);
$gui.layout(
"[button_0 | button_1 | button_2 | button_3"
"|button_4 | button_5 | button_6 | hp_gauge ]"
);
}
DinkyECS::Entity CombatUI::make_button(
std::string name,
Events::GUI event,
int action,
const std::string &icon_name,
const std::string &sound,
const std::string &effect_name)
{
auto button = $gui.entity(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(*$level.world, event, {action}));
return button;
}
void CombatUI::init() {
$gui.set<Background>($gui.MAIN, {$gui.$parser, ColorValue::DARK_MID});
auto& the_belt = $level.world->get_the<ritual::Belt>();
for(int slot = 0; slot < the_belt.max_slots; slot++) {
if(the_belt.has(slot)) {
std::string name = fmt::format("button_{}", slot);
auto& ritual = the_belt.get(slot);
using enum ritual::Element;
switch(ritual.element) {
case FIRE:
make_button(name, Events::GUI::ATTACK,
slot, "broken_yoyo-64", "fireball_01", "flame");
break;
case LIGHTNING:
make_button(name, Events::GUI::ATTACK,
slot, "pocket_watch-64", "electric_shock_01", "lightning");
break;
default:
make_button(name, Events::GUI::ATTACK,
slot, "severed_finger-64", "punch_cartoony", "ui_shader");
}
}
}
auto hp_gauge = $gui.entity("hp_gauge");
$gui.set<Sprite>(hp_gauge, {"stone_doll_cursed-64"});
$gui.set<Clickable>(hp_gauge,
guecs::make_action(*$level.world, Events::GUI::HP_STATUS, {}));
$gui.init();
}
void CombatUI::render(sf::RenderWindow& window) {
$gui.render(window);
}
void CombatUI::update_level(GameLevel &level) {
$level = level;
init();
}
bool CombatUI::mouse(float x, float y, bool hover) {
return $gui.mouse(x, y, hover);
}
}

24
gui/combat_ui.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include "levelmanager.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Font.hpp>
#include <guecs/ui.hpp>
#include "events.hpp"
namespace gui {
class CombatUI {
public:
guecs::UI $gui;
GameLevel $level;
CombatUI(GameLevel level);
void init();
void render(sf::RenderWindow& window);
void update_level(GameLevel &level);
bool mouse(float x, float y, bool hover);
DinkyECS::Entity make_button(std::string name, Events::GUI event,
int action, const std::string &icon_name,
const std::string &sound, const std::string &effect_name);
};
}

118
gui/debug_ui.cpp Normal file
View file

@ -0,0 +1,118 @@
#include "gui/debug_ui.hpp"
#include "constants.hpp"
#include "color.hpp"
#include "events.hpp"
#include <optional>
#include <fmt/core.h>
#include <fmt/xchar.h>
#include "components.hpp"
namespace gui {
using namespace guecs;
DebugUI::DebugUI(LevelManager& level_mgr) :
$level_mgr(level_mgr)
{
}
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, auto){ spawn(enemy_key); } });
$gui.set<guecs::Sprite>(button, { sprite_name});
}
void DebugUI::spawn(std::string enemy_key) {
auto ent = $level_mgr.spawn_enemy(enemy_key);
auto& level = $level_mgr.current();
level.world->send<Events::GUI>(Events::GUI::ENEMY_SPAWN, ent, {});
}
void DebugUI::render(sf::RenderWindow& window) {
auto& level = $level_mgr.current();
auto debug_settings = level.world->get_the<components::Debug>();
if(debug_settings.FPS) {
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() {
auto& level = $level_mgr.current();
auto& dbg = level.world->get_the<components::Debug>();
dbg.FPS = !dbg.FPS;
dbg.PATHS = !dbg.PATHS;
if(dbg.FPS) {
// 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<Textual>("debug_text");
}
}
bool DebugUI::mouse(float x, float y, bool hover) {
return $gui.mouse(x, y, hover);
}
void DebugUI::update_level(GameLevel &level) {
$level = level;
}
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();
}
}

30
gui/debug_ui.hpp Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include "levelmanager.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;
GameLevel $level;
LevelManager& $level_mgr;
DebugUI(LevelManager& level_mgr);
void init(lel::Cell cell);
void render(sf::RenderWindow& window);
bool mouse(float x, float y, bool hover);
void debug();
void update_level(GameLevel &level);
void spawn(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();
};
}

481
gui/gui_fsm.cpp Normal file
View file

@ -0,0 +1,481 @@
#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>
namespace gui {
using namespace components;
FSM::FSM() :
$window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Raycaster Thing"),
$debug_ui($levels),
$main_ui($window),
$level($levels.current()),
$combat_ui($level),
$status_ui($level),
$map_ui($level),
$mini_map($level),
$loot_ui($level),
$font{FONT_FILE_NAME}
{
}
void FSM::event(Event ev) {
switch($state) {
FSM_STATE(State, START, ev);
FSM_STATE(State, MOVING, ev);
FSM_STATE(State, ATTACKING, ev);
FSM_STATE(State, ROTATING, ev);
FSM_STATE(State, IDLE, ev);
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);
}
}
void FSM::START(Event ) {
$main_ui.update_level($level);
$level.world->set_the<Debug>({});
$main_ui.init();
$loot_ui.init();
// BUG: maybe this is a function on main_ui?
auto cell = $main_ui.$overlay_ui.$gui.cell_for("left");
$debug_ui.init(cell);
$debug_ui.update_level($level);
$combat_ui.init();
$status_ui.init();
$status_ui.log(L"Welcome to the game!");
$boss_fight_ui = $levels.create_bossfight($level.world);
$boss_fight_ui->init();
$map_ui.init();
$mini_map.init($main_ui.$overlay_ui.$gui);
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::plan_motion(*$level.world, *move_to);
run_systems();
$main_ui.dirty();
state(State::IDLE);
}
}
void FSM::ATTACKING(Event ev) {
using enum Event;
switch(ev) {
case TICK: {
System::combat($level, $temp_attack_id);
run_systems();
state(State::IN_COMBAT);
} break;
case STOP_COMBAT:
dbc::log("Exiting ATTACKING STATE");
state(State::IDLE);
break;
case ATTACK:
// ignore these since they're just from SFML not having discrete events
break;
default:
dbc::log(fmt::format("In ATTACKING state, unhandled event {}", (int)ev));
state(State::IDLE);
}
}
void FSM::ROTATING(Event ) {
if($main_ui.play_rotate()) {
state(State::IDLE);
}
}
void FSM::COMBAT_ROTATE(Event ) {
if($main_ui.play_rotate()) {
state(State::IN_COMBAT);
}
}
void FSM::LOOTING(Event ev) {
using enum Event;
switch(ev) {
case LOOT_OPEN:
$loot_ui.active = false;
state(State::IDLE);
break;
case TICK:
// do nothing
break;
default:
state(State::LOOTING);
}
}
void FSM::IDLE(Event ev) {
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);
state(State::ROTATING);
break;
case ROTATE_RIGHT:
$main_ui.plan_rotate(1);
state(State::ROTATING);
break;
case MAP_OPEN:
$map_open = !$map_open;
break;
case ATTACK:
state(State::ATTACKING);
break;
case START_COMBAT:
$map_open = false;
state(State::IN_COMBAT);
break;
case CLOSE:
dbc::log("Nothing to close.");
break;
case STAIRS_DOWN:
sound::stop("ambient");
state(State::NEXT_LEVEL);
break;
case STOP_COMBAT:
case TICK:
// do nothing
break;
case LOOT_OPEN:
$loot_ui.active = true;
state(State::LOOTING);
break;
default:
dbc::sentinel("unhandled event in IDLE");
}
}
void FSM::NEXT_LEVEL(Event ev) {
using enum Event;
switch(ev) {
case STAIRS_DOWN:
sound::play("ambient");
next_level();
state(State::IDLE);
default:
break; // do nothing for now
}
}
void FSM::IN_COMBAT(Event ev) {
using enum Event;
switch(ev) {
case ATTACK:
$main_ui.dirty();
sound::play("Sword_Hit_1");
state(State::ATTACKING);
break;
case ROTATE_LEFT:
$main_ui.plan_rotate(-1);
state(State::COMBAT_ROTATE);
break;
case ROTATE_RIGHT:
$main_ui.plan_rotate(1);
state(State::COMBAT_ROTATE);
break;
case STOP_COMBAT:
$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) {
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)));
}
void FSM::keyboard_mouse() {
while(const auto ev = $window.pollEvent()) {
if(ev->is<sf::Event::Closed>()) {
event(Event::QUIT);
}
if(const auto* mouse = ev->getIf<sf::Event::MouseButtonPressed>()) {
if(mouse->button == sf::Mouse::Button::Left) {
sf::Vector2f pos = $window.mapPixelToCoords(mouse->position);
if(in_state(State::NEXT_LEVEL)) {
$boss_fight_ui->mouse(pos.x, pos.y, false);
if($boss_fight_ui->boss_dead()) {
event(Event::STAIRS_DOWN);
}
} else {
$debug_ui.mouse(pos.x, pos.y, false);
$combat_ui.mouse(pos.x, pos.y, false);
$status_ui.mouse(pos.x, pos.y, false);
$main_ui.mouse(pos.x, pos.y, false);
if($loot_ui.active) $loot_ui.mouse(pos.x, pos.y, false);
}
}
} else if(const auto* mouse = ev->getIf<sf::Event::MouseMoved>()) {
sf::Vector2f pos = $window.mapPixelToCoords(mouse->position);
$debug_ui.mouse(pos.x, pos.y, true);
$combat_ui.mouse(pos.x, pos.y, true);
$status_ui.mouse(pos.x, pos.y, true);
$main_ui.mouse(pos.x, pos.y, true);
}
if(const auto* key = ev->getIf<sf::Event::KeyPressed>()) {
using KEY = sf::Keyboard::Scan;
switch(key->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);
$debug_ui.debug();
shaders::reload();
break;
case KEY::O:
autowalking = true;
break;
case KEY::L:
event(Event::STAIRS_DOWN);
break;
case KEY::X:
event(Event::LOOT_OPEN);
break;
default:
break; // ignored
}
}
}
}
void FSM::draw_gui() {
if(in_state(State::NEXT_LEVEL)) {
$boss_fight_ui->render($window);
} else {
// BUG: maybe pass the stats to main_ui for this?
auto start = $debug_ui.time_start();
$main_ui.render();
$debug_ui.sample_time(start);
$debug_ui.render($window);
$status_ui.render($window);
$combat_ui.render($window);
if($loot_ui.active) $loot_ui.render($window);
if($map_open) {
$map_ui.render($window, $main_ui.$compass_dir);
} else {
$mini_map.render($window, $main_ui.$compass_dir);
}
}
}
void FSM::render() {
if(in_state(State::NEXT_LEVEL)) {
$window.clear();
$boss_fight_ui->render($window);
} else {
draw_gui();
}
$window.display();
}
void FSM::run_systems() {
System::generate_paths($level);
System::enemy_ai_initialize($level);
System::enemy_pathing($level);
System::collision($level);
System::motion($level);
System::lighting($level);
System::death($level, $levels.$components);
}
bool FSM::active() {
return !in_state(State::END);
}
void FSM::handle_world_events() {
using eGUI = Events::GUI;
auto& world = *$level.world;
while(world.has_event<eGUI>()) {
auto [evt, entity, data] = world.recv<eGUI>();
auto player = world.get_the<Player>();
switch(evt) {
case eGUI::COMBAT: {
auto &damage = std::any_cast<Events::Combat&>(data);
if(damage.enemy_did > 0) {
$status_ui.log(fmt::format(L"Enemy HIT YOU for {} damage!", damage.enemy_did));
} else {
$status_ui.log(L"Enemy MISSED YOU.");
}
if(damage.player_did > 0) {
$status_ui.log(fmt::format(L"You HIT enemy for {} damage!", damage.player_did));
} else {
$status_ui.log(L"You MISSED the enemy.");
}
}
break;
case eGUI::COMBAT_START:
event(Event::START_COMBAT);
break;
case eGUI::ENEMY_SPAWN:
$debug_ui.update_level($level);
$main_ui.update_level($level);
run_systems();
break;
case eGUI::NO_NEIGHBORS:
event(Event::STOP_COMBAT);
break;
case eGUI::LOOT: {
// auto &item = std::any_cast<InventoryItem&>(data);
// $status_ui.log(fmt::format("You picked up a {}.",
// std::string(item.data["name"])));
$status_ui.log(L"You picked up an item.");
} break;
case eGUI::HP_STATUS:
System::player_status($level);
break;
case eGUI::NEW_RITUAL:
$combat_ui.init();
break;
case eGUI::EVADE:
case eGUI::BLOCK:
dbc::log("YOU NEED TO IMPLEMENT THIS!!!!!");
break;
case eGUI::ATTACK:
$temp_attack_id = std::any_cast<int>(data);
event(Event::ATTACK);
break;
case eGUI::STAIRS_DOWN:
event(Event::STAIRS_DOWN);
break;
case eGUI::DEATH: {
$status_ui.update();
if(entity != player.entity) {
$main_ui.dead_entity(entity);
}
} break;
case eGUI::NOOP: {
if(data.type() == typeid(std::string)) {
auto name = std::any_cast<std::string>(data);
$status_ui.log(fmt::format(L"NOOP EVENT! {},{}", evt, entity));
}
} break;
default:
$status_ui.log(fmt::format(L"INVALID EVENT! {},{}", evt, entity));
}
}
}
void FSM::next_level() {
$levels.create_level($level.world);
$level = $levels.next();
$debug_ui.update_level($level);
$status_ui.update_level($level);
$map_ui.update_level($level);
$mini_map.update_level($level);
$combat_ui.update_level($level);
$main_ui.update_level($level);
$loot_ui.update_level($level);
$boss_fight_ui = $levels.create_bossfight($level.world);
$boss_fight_ui->init();
run_systems();
}
}

94
gui/gui_fsm.hpp Normal file
View file

@ -0,0 +1,94 @@
#pragma once
#include "constants.hpp"
#include "stats.hpp"
#include "levelmanager.hpp"
#include "fsm.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 "gui/boss_fight_ui.hpp"
#include "map_view.hpp"
#include "mini_map.hpp"
namespace gui {
enum class State {
START,
MOVING,
IN_COMBAT,
COMBAT_ROTATE,
ATTACKING,
ROTATING,
NEXT_LEVEL,
LOOTING,
IDLE,
END
};
enum class Event {
STARTED=0,
TICK=1,
MOVE_FORWARD = 2,
MOVE_BACK = 3,
MOVE_LEFT = 4,
MOVE_RIGHT = 5,
MAP_OPEN = 6,
CLOSE = 7,
ROTATE_LEFT = 8,
ROTATE_RIGHT = 9,
ATTACK = 10,
START_COMBAT = 11,
STOP_COMBAT = 12,
STAIRS_DOWN = 13,
LOOT_OPEN=14,
QUIT = 15
};
class FSM : public DeadSimpleFSM<State, Event> {
public:
sf::RenderWindow $window;
bool $draw_stats = false;
bool autowalking = false;
bool $map_open = false;
int $temp_attack_id = 0;
LevelManager $levels;
DebugUI $debug_ui;
MainUI $main_ui;
GameLevel $level;
shared_ptr<BossFightUI> $boss_fight_ui = nullptr;
CombatUI $combat_ui;
StatusUI $status_ui;
MapViewUI $map_ui;
MiniMapUI $mini_map;
LootUI $loot_ui;
sf::Font $font;
FSM();
void event(Event ev);
void autowalk();
void start_autowalk(double rot_speed);
void START(Event );
void MOVING(Event );
void ATTACKING(Event );
void MAPPING(Event);
void ROTATING(Event );
void IDLE(Event ev);
void IN_COMBAT(Event ev);
void COMBAT_ROTATE(Event ev);
void NEXT_LEVEL(Event ev);
void LOOTING(Event ev);
void END(Event ev);
void try_move(int dir, bool strafe);
void keyboard_mouse();
void draw_gui();
void render();
bool active();
void run_systems();
void handle_world_events();
void next_level();
};
}

51
gui/loot_ui.cpp Normal file
View file

@ -0,0 +1,51 @@
#include "gui/loot_ui.hpp"
#include "constants.hpp"
#include "color.hpp"
#include <fmt/xchar.h>
#include "guecstra.hpp"
namespace gui {
using namespace guecs;
LootUI::LootUI(GameLevel level) :
$level(level)
{
$gui.position(RAY_VIEW_X+RAY_VIEW_WIDTH/2-200,
RAY_VIEW_Y+RAY_VIEW_HEIGHT/2-200, 400, 400);
$gui.layout(
"[button_0 | button_1|button_2 | button_3]"
"[button_4 | button_5|button_6 | button_7]"
"[button_8 | button_9|button_10 | button_11]"
"[button_12 | button_13|button_14 | button_15]"
);
}
void LootUI::init() {
$gui.set<Background>($gui.MAIN, {$gui.$parser, ColorValue::DARK_MID});
for(auto [name, cell] : $gui.cells()) {
auto id = $gui.entity(name);
$gui.set<guecs::Rectangle>(id, {});
if(id < 4) {
$gui.set<guecs::Clickable>(id, {
[=](auto, auto) { fmt::println("clicked {}", name); }
});
$gui.set<guecs::Sprite>(id, {"broken_yoyo-64"});
}
}
$gui.init();
}
void LootUI::render(sf::RenderWindow& window) {
$gui.render(window);
}
void LootUI::update_level(GameLevel &level) {
$level = level;
init();
}
bool LootUI::mouse(float x, float y, bool hover) {
return $gui.mouse(x, y, hover);
}
}

22
gui/loot_ui.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include "levelmanager.hpp"
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Font.hpp>
#include <guecs/ui.hpp>
#include "events.hpp"
namespace gui {
class LootUI {
public:
bool active = false;
guecs::UI $gui;
GameLevel $level;
LootUI(GameLevel level);
void init();
void render(sf::RenderWindow& window);
void update_level(GameLevel &level);
bool mouse(float x, float y, bool hover);
};
}

115
gui/main_ui.cpp Normal file
View file

@ -0,0 +1,115 @@
#include "gui/main_ui.hpp"
#include "components.hpp"
#include "easings.hpp"
#include <fmt/xchar.h>
#include "constants.hpp"
namespace gui {
using namespace components;
MainUI::MainUI(sf::RenderWindow& window) :
$window(window),
$rayview(RAY_VIEW_WIDTH, RAY_VIEW_HEIGHT),
$camera($rayview)
{
$window.setVerticalSyncEnabled(VSYNC);
$window.setFramerateLimit(FRAME_LIMIT);
}
void MainUI::dirty() {
$needs_render = true;
}
void MainUI::init() {
auto& player_position = $level.world->get<Position>($level.player);
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);
auto st = textures::get("down_the_well");
auto bounds = st.sprite->getLocalBounds();
st.sprite->setPosition({RAY_VIEW_X + bounds.size.x / 2,
RAY_VIEW_Y + bounds.size.y / 2});
st.sprite->setOrigin({bounds.size.x / 2, bounds.size.y / 2});
$overlay_ui.init();
}
void MainUI::render() {
auto aimed_at = $camera.aimed_at();
if($level.collision->occupied(aimed_at)) {
$rayview.aiming_at = $level.collision->get(aimed_at);
} else {
$rayview.aiming_at = 0;
}
if($needs_render) $rayview.render();
$rayview.draw($window);
$overlay_ui.render($window);
}
void MainUI::health_low() {
$overlay_ui.show_sprite("middle", "blood_splatter");
}
bool MainUI::play_rotate() {
bool done = $camera.play_rotate();
$needs_render = !done;
return done;
}
// this could be an optional that returs a Point
std::optional<Point> MainUI::play_move() {
if($camera.play_move()) {
$needs_render = false;
Point pos{
size_t($camera.target_x),
size_t($camera.target_y)};
return std::make_optional<Point>(pos);
} else {
$needs_render = true;
return std::nullopt;
}
}
void MainUI::plan_rotate(int dir) {
// -1 is left, 1 is right
$compass_dir = ($compass_dir + dir) % COMPASS.size();
$camera.plan_rotate(dir);
}
Point MainUI::plan_move(int dir, bool strafe) {
return $camera.plan_move(dir, strafe);
}
void MainUI::abort_plan() {
$camera.abort_plan();
}
void MainUI::dead_entity(DinkyECS::Entity entity) {
auto &sprite = $level.world->get<components::Sprite>(entity);
$rayview.update_sprite(entity, sprite);
}
void MainUI::update_level(GameLevel level) {
$level = level;
auto& player_position = $level.world->get<Position>($level.player);
auto player = player_position.location;
$rayview.update_level($level);
$rayview.position_camera(player.x + 0.5, player.y + 0.5);
$compass_dir = 0;
dirty();
}
void MainUI::mouse(int x, int y, bool hover) {
$overlay_ui.$gui.mouse(x, y, hover);
}
}

46
gui/main_ui.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include "levelmanager.hpp"
#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 "camera.hpp"
#include <optional>
namespace gui {
class MainUI {
public:
int $compass_dir = 0;
bool $needs_render = true;
sf::Clock $clock;
sf::RenderWindow& $window;
GameLevel $level;
OverlayUI $overlay_ui;
Raycaster $rayview;
CameraLOL $camera;
MainUI(sf::RenderWindow& window);
void mouse(int x, int y, bool hover);
void debug();
void render_debug();
void plan_rotate(int dir);
bool play_rotate();
std::optional<Point> play_move();
Point plan_move(int dir, bool strafe);
void abort_plan();
void update_level(GameLevel level);
void init();
void render();
void dirty();
void health_low();
void dead_entity(DinkyECS::Entity entity);
};
}

51
gui/overlay_ui.cpp Normal file
View file

@ -0,0 +1,51 @@
#include "gui/overlay_ui.hpp"
#include "constants.hpp"
#include "color.hpp"
#include "events.hpp"
#include <optional>
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]"
);
}
void OverlayUI::init() {
$gui.init();
}
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<Textual>(region);
}
void OverlayUI::show_label(string region, wstring content) {
$gui.show_label(region, content);
}
void OverlayUI::close_label(string region) {
$gui.close<Label>(region);
}
}

26
gui/overlay_ui.hpp Normal file
View file

@ -0,0 +1,26 @@
#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 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);
void show_label(std::string region, std::wstring content);
void update_label(std::string region, std::wstring content);
void close_label(std::string region);
};
}

252
gui/ritual_ui.cpp Normal file
View file

@ -0,0 +1,252 @@
#include "gui/ritual_ui.hpp"
#include "components.hpp"
#include <guecs/ui.hpp>
#include "rand.hpp"
#include "animation.hpp"
#include "rand.hpp"
#include "sound.hpp"
#include "events.hpp"
namespace gui {
namespace ritual {
using namespace guecs;
using std::any, std::any_cast, std::string, std::make_any;
UI::UI(GameLevel 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(
"[_]"
"[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("ritual_crafting_area");
$ritual_ui.sprite->setPosition($gui.get_position());
$ritual_ui.sprite->setTextureRect($ritual_closed_rect);
state(State::CLOSED);
$ritual_anim = animation::load("ritual_blanket");
auto open_close_toggle = $gui.entity("ritual_ui");
$gui.set<Clickable>(open_close_toggle, {
[&](auto, auto){ event(Event::TOGGLE); }
});
$craft_state = $ritual_engine.start();
$gui.init();
state(State::CLOSED);
}
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
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) {
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) {
$ritual_anim.play();
load_blanket();
state(State::OPENING);
}
}
void UI::OPENING(Event ev) {
if(ev == Event::TICK) {
if(!animation::apply($ritual_anim, $ritual_ui)) {
state(State::OPENED);
}
}
}
void UI::CLOSING(Event ev) {
if(ev == Event::TICK) {
$ritual_ui.sprite->setTextureRect($ritual_closed_rect);
state(State::CLOSED);
}
}
bool UI::mouse(float x, float y, bool hover) {
return $gui.mouse(x, y, hover);
}
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++);
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);
}
});
}
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() {
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, {});
$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_label("result_text", L"This might work...");
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.set<Clickable>(combine, {
[&](auto, auto){ event(Event::COMBINE); }
});
} else {
$gui.show_label("result_text", L"That won't work.");
$gui.show_sprite("result_image", "dubious_combination-128");
$gui.remove<Clickable>(combine);
return;
}
}
void UI::clear_craft_result() {
$blanket.reset();
$gui.close<Label>("result_text");
$gui.close<Sprite>("result_image");
}
}
}

69
gui/ritual_ui.hpp Normal file
View file

@ -0,0 +1,69 @@
#pragma once
#include "levelmanager.hpp"
#include "constants.hpp"
#include <deque>
#include "textures.hpp"
#include <guecs/ui.hpp>
#include "rituals.hpp"
#include "fsm.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 {
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}};
components::Animation $ritual_anim;
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, 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, 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 run_crafting_engine();
void complete_combine();
void update_selection_state();
};
}
}

155
gui/status_ui.cpp Normal file
View file

@ -0,0 +1,155 @@
#include "gui/status_ui.hpp"
#include "components.hpp"
#include "inventory.hpp"
#include "color.hpp"
#include <guecs/ui.hpp>
#include "rand.hpp"
#include <fmt/xchar.h>
namespace gui {
using namespace guecs;
using std::any, std::any_cast, std::string, std::make_any;
StatusUI::StatusUI(GameLevel level) :
$level(level), $ritual_ui(level)
{
$gui.position(STATUS_UI_X, STATUS_UI_Y, STATUS_UI_WIDTH, STATUS_UI_HEIGHT);
$gui.layout(
"[ ritual_ui ]"
"[inv_slot1 | inv_slot2 | inv_slot3]"
"[inv_slot4 | inv_slot5 | inv_slot6]"
"[*%(100,300)log_view]"
"[_]"
"[_]");
size_t inv_id = 0;
for(auto [name, entity] : $gui.$name_ents) {
if(name.starts_with("inv_")) {
$slots[name] = inv_id++;
}
}
}
void StatusUI::init() {
$gui.set<Background>($gui.MAIN, {$gui.$parser});
for(auto& [name, cell] : $gui.cells()) {
if(name == "log_view") {
$log_to = $gui.entity("log_view");
$gui.set<Rectangle>($log_to, {});
$gui.set<Textual>($log_to, {L"Welcome to the Game!", 20});
} else {
auto button = $gui.entity(name);
$gui.set<Rectangle>(button, {});
$gui.set<Textual>(button, {L""});
$gui.set<ActionData>(button, {make_any<string>(name)});
if(name == "ritual_ui") {
$gui.set<Clickable>(button, {
[this](auto, auto){ select_ritual(); }
});
$gui.set<Sound>(button, {"pickup"});
} else {
$gui.set<Clickable>(button, {
[this](auto ent, auto data){ select_slot(ent, data); }
});
}
}
}
$ritual_ui.event(ritual::Event::STARTED);
$gui.init();
}
bool StatusUI::mouse(float x, float y, bool hover) {
if($ritual_ui.is_open()) {
return $ritual_ui.mouse(x, y, hover);
} else {
return $gui.mouse(x, y, hover);
}
}
void StatusUI::select_ritual() {
$ritual_ui.event(ritual::Event::TOGGLE);
}
void StatusUI::select_slot(DinkyECS::Entity ent, any slot_name) {
dbc::check(slot_name.has_value(), "passed select_slot an any without a value");
auto cn = $gui.get<CellName>(ent);
auto world = $level.world;
if(world->has<components::Inventory>($level.player)) {
auto& inventory = world->get<components::Inventory>($level.player);
size_t inv_id = $slots[any_cast<string>(slot_name)];
if(inventory.has_item(inv_id)) {
auto [used, name] = inventory.use($level, inv_id);
if(used) {
// log(fmt::format(L"Used item: {}", name));
log(fmt::format(L"Used item: {}", L"FIX ME ZED"));
} else {
// log(fmt::format(L"You are out of {}.", name));
log(fmt::format(L"Used item: {}", L"FIX ME ZED"));
}
}
}
}
/* WARNING: This is really not the greatest way to do this. */
void StatusUI::update() {
if($gui.has<Textual>($log_to)) {
auto& text = $gui.get<Textual>($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);
}
auto world = $level.world;
if(world->has<components::Inventory>($level.player)) {
auto& inventory = world->get<components::Inventory>($level.player);
for(auto& [slot_name, inv_id] : $slots) {
if(inventory.has_item(inv_id)) {
auto slot = $gui.entity(slot_name);
auto& item = inventory.get(inv_id);
auto comp_sprite = components::get<components::Sprite>(item.data);
$gui.set_init<guecs::Sprite>(slot, {comp_sprite.name});
string count_label = fmt::format("{}", item.count);
auto& label = $gui.get<Textual>(slot);
label.text->setString(count_label);
auto& sprite = $gui.get<guecs::Sprite>(slot);
if(item.count == 0) {
sprite.sprite->setColor({125, 125, 125});
} else {
sprite.sprite->setColor({255, 255, 255});
}
}
}
}
}
void StatusUI::render(sf::RenderWindow &window) {
$gui.render(window);
$ritual_ui.render(window);
}
void StatusUI::log(wstring msg) {
$messages.push_front(msg);
if($messages.size() > MAX_LOG_MESSAGES) {
$messages.pop_back();
}
update();
}
void StatusUI::update_level(GameLevel &level) {
$level = level;
init();
}
}

29
gui/status_ui.hpp Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include "levelmanager.hpp"
#include "constants.hpp"
#include <deque>
#include "textures.hpp"
#include <guecs/ui.hpp>
#include "gui/ritual_ui.hpp"
namespace gui {
class StatusUI {
public:
guecs::UI $gui;
DinkyECS::Entity $log_to;
std::map<std::string, size_t> $slots;
std::deque<std::wstring> $messages;
GameLevel $level;
ritual::UI $ritual_ui;
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 log(std::wstring msg);
void init();
void render(sf::RenderWindow &window);
void update();
};
}