First cut of pulling out the relevant parts of my original game to make a little framework.
This commit is contained in:
commit
6a0c9e8d46
177 changed files with 18197 additions and 0 deletions
78
src/gui/backend.cpp
Normal file
78
src/gui/backend.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#include "backend.hpp"
|
||||
#include "graphics/shaders.hpp"
|
||||
#include "game/sound.hpp"
|
||||
#include "graphics/textures.hpp"
|
||||
#include "game/config.hpp"
|
||||
#include "graphics/palette.hpp"
|
||||
|
||||
namespace gui {
|
||||
using namespace nlohmann;
|
||||
|
||||
guecs::SpriteTexture Backend::get_sprite(const string& name) {
|
||||
auto sp = textures::get_sprite(name);
|
||||
return {sp.sprite, sp.texture, sp.frame_size};
|
||||
}
|
||||
|
||||
guecs::SpriteTexture Backend::get_icon(const string& name) {
|
||||
auto sp = textures::get_icon(name);
|
||||
return {sp.sprite, sp.texture, sp.frame_size};
|
||||
}
|
||||
|
||||
Backend::Backend() {
|
||||
sound::init();
|
||||
shaders::init();
|
||||
textures::init();
|
||||
}
|
||||
|
||||
void Backend::sound_play(const string& name) {
|
||||
sound::play(name);
|
||||
}
|
||||
|
||||
void Backend::sound_stop(const string& name) {
|
||||
sound::stop(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<sf::Shader> Backend::get_shader(const std::string& name) {
|
||||
return shaders::get(name);
|
||||
}
|
||||
|
||||
bool Backend::shader_updated() {
|
||||
if(shaders::updated($shaders_version)) {
|
||||
$shaders_version = shaders::version();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
guecs::Theme Backend::theme() {
|
||||
palette::init();
|
||||
auto config = settings::Config("assets/config.json")["theme"];
|
||||
|
||||
guecs::Theme theme {
|
||||
.BLACK=palette::get("gui/theme:black"),
|
||||
.DARK_DARK=palette::get("gui/theme:dark_dark"),
|
||||
.DARK_MID=palette::get("gui/theme:dark_mid"),
|
||||
.DARK_LIGHT=palette::get("gui/theme:dark_light"),
|
||||
.MID=palette::get("gui/theme:mid"),
|
||||
.LIGHT_DARK=palette::get("gui/theme:light_dark"),
|
||||
.LIGHT_MID=palette::get("gui/theme:light_mid"),
|
||||
.LIGHT_LIGHT=palette::get("gui/theme:light_light"),
|
||||
.WHITE=palette::get("gui/theme:white"),
|
||||
.TRANSPARENT = palette::get("color:transparent")
|
||||
};
|
||||
|
||||
theme.PADDING = config["padding"];
|
||||
theme.BORDER_PX = config["border_px"];
|
||||
theme.TEXT_SIZE = config["text_size"];
|
||||
theme.LABEL_SIZE = config["label_size"];
|
||||
theme.FILL_COLOR = palette::get("gui/theme:fill_color");
|
||||
theme.TEXT_COLOR = palette::get("gui/theme:text_color");
|
||||
theme.BG_COLOR = palette::get("gui/theme:bg_color");
|
||||
theme.BORDER_COLOR = palette::get("gui/theme:border_color");
|
||||
theme.BG_COLOR_DARK = palette::get("gui/theme:bg_color_dark");
|
||||
theme.FONT_FILE_NAME = settings::Config::path_to(config["font_file_name"]).string();
|
||||
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
20
src/gui/backend.hpp
Normal file
20
src/gui/backend.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#include "guecs/ui.hpp"
|
||||
|
||||
namespace gui {
|
||||
using std::string;
|
||||
|
||||
class Backend : public guecs::Backend {
|
||||
int $shaders_version = 0;
|
||||
|
||||
public:
|
||||
|
||||
Backend();
|
||||
guecs::SpriteTexture get_sprite(const string& name);
|
||||
guecs::SpriteTexture get_icon(const string& name);
|
||||
void sound_play(const string& name);
|
||||
void sound_stop(const string& name);
|
||||
std::shared_ptr<sf::Shader> get_shader(const std::string& name);
|
||||
bool shader_updated();
|
||||
guecs::Theme theme();
|
||||
};
|
||||
}
|
||||
102
src/gui/debug_ui.cpp
Normal file
102
src/gui/debug_ui.cpp
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#include "gui/debug_ui.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "events.hpp"
|
||||
#include <optional>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/xchar.h>
|
||||
#include "game/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("RAT_GIANT", "rat_with_sword", "spawn4");
|
||||
|
||||
$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 = $F(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 "algos/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($F("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($F("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 "algos/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($F("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($F("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 "algos/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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
515
src/gui/fsm.cpp
Normal file
515
src/gui/fsm.cpp
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
#include "gui/fsm.hpp"
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <numeric>
|
||||
#include <functional>
|
||||
#include "game/components.hpp"
|
||||
#include <numbers>
|
||||
#include "game/systems.hpp"
|
||||
#include "events.hpp"
|
||||
#include "game/sound.hpp"
|
||||
#include "graphics/shaders.hpp"
|
||||
#include <fmt/xchar.h>
|
||||
#include "gui/guecstra.hpp"
|
||||
#include "game/level.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),
|
||||
$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, 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);
|
||||
|
||||
$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($F("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, DEFAULT_ROTATE);
|
||||
state(State::ROTATING);
|
||||
break;
|
||||
case ROTATE_RIGHT:
|
||||
$main_ui.plan_rotate(1, DEFAULT_ROTATE);
|
||||
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 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::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($F("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);
|
||||
$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::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);
|
||||
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::update() {
|
||||
}
|
||||
|
||||
void FSM::render() {
|
||||
$window.clear();
|
||||
|
||||
// this clears any attack animations, like fire
|
||||
System::clear_attack();
|
||||
// BUG: this is the render for this class, and where I add an update
|
||||
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_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($F(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($F(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::ATTACK:
|
||||
event(Event::ATTACK, data);
|
||||
break;
|
||||
case eGUI::STAIRS_DOWN:
|
||||
dbc::sentinel("make me!");
|
||||
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($F(L"NOOP EVENT! {},{}", evt, entity));
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
dbc::log($F("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() {
|
||||
GameDB::create_level();
|
||||
$status_ui.update_level();
|
||||
$main_ui.update_level();
|
||||
$loot_ui.update_level();
|
||||
|
||||
run_systems();
|
||||
}
|
||||
}
|
||||
73
src/gui/fsm.hpp
Normal file
73
src/gui/fsm.hpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "algos/simplefsm.hpp"
|
||||
#include "gui/debug_ui.hpp"
|
||||
#include "gui/main_ui.hpp"
|
||||
#include "gui/status_ui.hpp"
|
||||
#include "gui/loot_ui.hpp"
|
||||
#include "gui/map_view.hpp"
|
||||
#include "events.hpp"
|
||||
#include "gui/event_router.hpp"
|
||||
#include "gui/dnd_loot.hpp"
|
||||
#include "events.hpp"
|
||||
|
||||
namespace gui {
|
||||
enum class State {
|
||||
START=__LINE__,
|
||||
MOVING=__LINE__,
|
||||
IN_COMBAT=__LINE__,
|
||||
COMBAT_ROTATE=__LINE__,
|
||||
ATTACKING=__LINE__,
|
||||
ROTATING=__LINE__,
|
||||
LOOTING=__LINE__,
|
||||
IDLE=__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;
|
||||
StatusUI $status_ui{STATUS_UI_X, STATUS_UI_Y, STATUS_UI_WIDTH, STATUS_UI_HEIGHT};
|
||||
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 LOOTING(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 update();
|
||||
void render();
|
||||
bool active();
|
||||
void run_systems();
|
||||
void handle_world_events();
|
||||
void next_level();
|
||||
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 "game/components.hpp"
|
||||
#include "events.hpp"
|
||||
#include <guecs/ui.hpp>
|
||||
#include "graphics/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 "game/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 "game/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 "game/components.hpp"
|
||||
#include <fmt/xchar.h>
|
||||
#include "graphics/animation.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "game/level.hpp"
|
||||
#include "ai/ai.hpp"
|
||||
|
||||
namespace gui {
|
||||
using namespace components;
|
||||
|
||||
MainUI::MainUI(sf::RenderWindow& window) :
|
||||
$window(window),
|
||||
$rayview(std::make_shared<Raycaster>(RAY_VIEW_X, RAY_VIEW_Y, 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->position_camera(player.x + 0.5, player.y + 0.5);
|
||||
|
||||
$overlay_ui.init();
|
||||
}
|
||||
|
||||
void MainUI::render() {
|
||||
// BUG: bring back the $needs_render optimization
|
||||
$rayview->update();
|
||||
$rayview->render($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);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/gui/main_ui.hpp
Normal file
54
src/gui/main_ui.hpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
#include <SFML/Graphics/RenderWindow.hpp>
|
||||
#include <SFML/System/Clock.hpp>
|
||||
#include "algos/stats.hpp"
|
||||
#include <guecs/ui.hpp>
|
||||
#include "gui/overlay_ui.hpp"
|
||||
#include "gui/debug_ui.hpp"
|
||||
#include "graphics/raycaster.hpp"
|
||||
#include "graphics/animation.hpp"
|
||||
#include "game/components.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 "game/components.hpp"
|
||||
#include "algos/rand.hpp"
|
||||
#include "game/systems.hpp"
|
||||
#include "algos/rand.hpp"
|
||||
#include <codecvt>
|
||||
#include <iostream>
|
||||
#include <fmt/xchar.h>
|
||||
#include <fstream>
|
||||
#include "graphics/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 "graphics/textures.hpp"
|
||||
#include "algos/matrix.hpp"
|
||||
#include <guecs/ui.hpp>
|
||||
#include <string>
|
||||
#include "algos/dinkyecs.hpp"
|
||||
#include "game/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();
|
||||
};
|
||||
}
|
||||
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);
|
||||
};
|
||||
}
|
||||
132
src/gui/status_ui.cpp
Normal file
132
src/gui/status_ui.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#include "gui/status_ui.hpp"
|
||||
#include "game/components.hpp"
|
||||
#include <guecs/ui.hpp>
|
||||
#include "algos/rand.hpp"
|
||||
#include <fmt/xchar.h>
|
||||
#include "gui/guecstra.hpp"
|
||||
#include "game/systems.hpp"
|
||||
#include "game/inventory.hpp"
|
||||
#include "game/level.hpp"
|
||||
|
||||
namespace gui {
|
||||
using namespace guecs;
|
||||
using std::any, std::any_cast, std::string, std::make_any;
|
||||
|
||||
StatusUI::StatusUI(size_t x, size_t y, size_t width, size_t height)
|
||||
{
|
||||
$gui.position(x, y, width, height);
|
||||
$gui.layout(
|
||||
"[=slot1]"
|
||||
"[=slot2]"
|
||||
"[=hand_r]"
|
||||
"[=pocket_l]");
|
||||
}
|
||||
|
||||
void StatusUI::init() {
|
||||
$gui.set<Background>($gui.MAIN, {$gui.$parser, });
|
||||
|
||||
for(auto& [name, cell] : $gui.cells()) {
|
||||
auto gui_id = $gui.entity(name);
|
||||
|
||||
$gui.set<Rectangle>(gui_id, {});
|
||||
|
||||
$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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$gui.init();
|
||||
update();
|
||||
}
|
||||
|
||||
bool StatusUI::mouse(float x, float y, guecs::Modifiers mods) {
|
||||
return $gui.mouse(x, y, mods);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
32
src/gui/status_ui.hpp
Normal file
32
src/gui/status_ui.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
#include "constants.hpp"
|
||||
#include <deque>
|
||||
#include "graphics/textures.hpp"
|
||||
#include <guecs/ui.hpp>
|
||||
#include "gui/guecstra.hpp"
|
||||
|
||||
namespace gui {
|
||||
class StatusUI {
|
||||
public:
|
||||
guecs::UI $gui;
|
||||
|
||||
explicit StatusUI(size_t x, size_t y, size_t width, size_t height);
|
||||
|
||||
StatusUI(const StatusUI& other) = delete;
|
||||
StatusUI(StatusUI&& other) = delete;
|
||||
~StatusUI() = default;
|
||||
|
||||
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