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:
parent
4778677647
commit
1d4ae911b9
108 changed files with 94 additions and 83 deletions
102
src/gui/combat_ui.cpp
Normal file
102
src/gui/combat_ui.cpp
Normal 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
23
src/gui/combat_ui.hpp
Normal 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
106
src/gui/debug_ui.cpp
Normal 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
26
src/gui/debug_ui.hpp
Normal 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
328
src/gui/dnd_loot.cpp
Normal 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
66
src/gui/dnd_loot.hpp
Normal 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
143
src/gui/event_router.cpp
Normal 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
49
src/gui/event_router.hpp
Normal 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
577
src/gui/fsm.cpp
Normal 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
83
src/gui/fsm.hpp
Normal 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
40
src/gui/guecstra.cpp
Normal 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
24
src/gui/guecstra.hpp
Normal 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
154
src/gui/loot_ui.cpp
Normal 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
37
src/gui/loot_ui.hpp
Normal 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
155
src/gui/main_ui.cpp
Normal 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
52
src/gui/main_ui.hpp
Normal 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
75
src/gui/map_view.cpp
Normal 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
27
src/gui/map_view.hpp
Normal 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
33
src/gui/mini_map.cpp
Normal 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
17
src/gui/mini_map.hpp
Normal 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
63
src/gui/overlay_ui.cpp
Normal 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
25
src/gui/overlay_ui.hpp
Normal 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
264
src/gui/ritual_ui.cpp
Normal 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
69
src/gui/ritual_ui.hpp
Normal 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
156
src/gui/status_ui.cpp
Normal 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
35
src/gui/status_ui.hpp
Normal 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);
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue