The next little game in the series where I make a fancy rogue game.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
roguish/gui.cpp

306 lines
8.7 KiB

#include <chrono> // for operator""s, chrono_literals
#include <iostream> // for cout, ostream
#include <fstream>
#include <memory> // for allocator, shared_ptr
#include <string> // for string, operator<<
#include <thread> // for sleep_for
#include <array>
#include <algorithm>
#include <ftxui/dom/elements.hpp> // for hflow, paragraph, separator, hbox, vbox, filler, operator|, border, Element
#include <ftxui/dom/node.hpp> // for Render
#include <ftxui/screen/box.hpp> // for ftxui
#include <ftxui/component/loop.hpp>
#include <ftxui/screen/color.hpp>
#include <fmt/core.h>
#include "dbc.hpp"
#include "gui.hpp"
#include "rand.hpp"
#include "systems.hpp"
#include "events.hpp"
#include "render.hpp"
#include "save.hpp"
#include "constants.hpp"
using std::string;
using namespace fmt;
using namespace std::chrono_literals;
using namespace ftxui;
using namespace components;
GUI::GUI(DinkyECS::World &world, Map& game_map) :
$game_map(game_map),
$log({{"Welcome to the game!"}}),
$status_ui(0, 0, STATUS_UI_WIDTH, STATUS_UI_HEIGHT),
$map_view(GAME_MAP_PIXEL_POS, 0, 0, 0, true),
$inventory_ui(INVENTORY_PIXEL_X, INVENTORY_PIXEL_Y, INVENTORY_WIDTH, INVENTORY_HEIGHT),
$lights(game_map.width(), game_map.height()),
$world(world),
$sounds("./assets"),
$renderer()
{
// this needs a config file soon
// $sounds.load("ambient", "ambient_sound.mp3");
$sounds.load("loot_gold", "loot_gold.mp3");
$sounds.load("combat_player_hit", "combat_player_hit.mp3");
$sounds.load("combat_enemy_hit", "combat_enemy_hit.mp3");
$sounds.load("combat_miss", "combat_miss.mp3");
resize_map(MAX_FONT_SIZE);
init_shaders();
}
void GUI::resize_map(int new_size) {
$renderer.resize_grid(new_size, $map_view);
// set canvas to best size
$canvas = Canvas($map_view.width * 2, $map_view.height * 4);
}
void GUI::save_world() {
$log.log("Game saved!");
save::to_file("./savefile.world", $world, $game_map);
}
void GUI::create_renderer() {
$renderer.init_terminal();
auto player = $world.get_the<Player>();
$map_view.set_renderer(Renderer([&] {
System::draw_map($world, $game_map, $lights.lighting(), $canvas, $map_view.width, $map_view.height);
return canvas($canvas);
}));
auto cell = [](const char* t) { return text(t) | border; };
auto inventory_test = Renderer([cell] {
return hflow({
gridbox({
{cell("one"), cell("two"), cell("three")},
{cell("four"), cell("five"), cell("six")},
{cell("seven"), cell("eight"), cell("nine")},
}) | yflex_grow,
separator() | yflex_grow,
paragraph("Item UI Goes Here") | yflex_grow
}) | border | flex;
});
auto status_rend = Renderer([&, player]{
const auto& player_combat = $world.get<Combat>(player.entity);
const auto& inventory = $world.get<Inventory>(player.entity);
$status_text = player_combat.hp > 0 ? "NOT DEAD" : "DEAD!!!!!!";
std::vector<Element> log_list;
for(auto msg : $log.messages) {
log_list.push_back(text(msg));
}
auto log_box = vbox(log_list) | yflex_grow | border;
return hbox({
hflow(
vbox(
text(format("HP: {: >3} GOLD: {: >3}",
player_combat.hp, inventory.gold)) | border,
text($status_text) | border,
separator(),
log_box
) | flex_grow
),
separator(),
hbox(),
});
});
$status_ui.set_renderer(status_rend);
$inventory_ui.set_renderer(inventory_test);
}
void GUI::handle_world_events() {
using eGUI = Events::GUI;
auto player = $world.get_the<Player>();
while($world.has_event<eGUI>()) {
auto [evt, entity, data] = $world.recv<eGUI>();
switch(evt) {
case eGUI::COMBAT: {
auto &damage = std::any_cast<Events::Combat&>(data);
auto enemy_combat = $world.get<Combat>(entity);
if(damage.enemy_did > 0) {
$log.log(format("Enemy HIT YOU for {} damage!", damage.enemy_did));
$log.log(format("-- Enemy has {} HP left.", enemy_combat.hp));
$sounds.play("combat_enemy_hit");
shake();
} else {
$log.log("Enemy MISSED YOU.");
}
if(damage.player_did > 0) {
$log.log(format("You HIT enemy for {} damage!", damage.player_did));
$sounds.play("combat_player_hit");
} else {
$sounds.play("combat_miss");
$log.log("You MISSED the enemy.");
}
} break;
case eGUI::LOOT: {
auto &loot = std::any_cast<Loot&>(data);
auto inventory = $world.get<Inventory>(player.entity);
$sounds.play("loot_gold");
$log.log(format("You found {} gold. You have {} now.",
loot.amount, inventory.gold));
}
break;
default:
$log.log(format("INVALID EVENT! {},{}", evt, entity));
}
}
}
void GUI::shutdown() {
$renderer.close();
}
bool GUI::handle_ui_events() {
using KB = sf::Keyboard;
using MOUSE = sf::Mouse;
bool event_happened = false;
sf::Event event;
auto player = $world.get_the<Player>();
int map_font_size = $renderer.font_size();
auto& player_motion = $world.get<Motion>(player.entity);
Point pos;
while($renderer.poll_event(event)) {
if(event.type == sf::Event::Closed) {
shutdown();
} else if(event.type == sf::Event::KeyPressed) {
if(KB::isKeyPressed(KB::Left)) {
player_motion.dx = -1;
event_happened = true;
} else if(KB::isKeyPressed(KB::Right)) {
player_motion.dx = 1;
event_happened = true;
} else if(KB::isKeyPressed(KB::Up)) {
player_motion.dy = -1;
event_happened = true;
} else if(KB::isKeyPressed(KB::Down)) {
player_motion.dy = 1;
event_happened = true;
} else if(KB::isKeyPressed(KB::Equal)) {
resize_map(map_font_size + 10);
} else if(KB::isKeyPressed(KB::Hyphen)) {
resize_map(map_font_size - 10);
} else if(KB::isKeyPressed(KB::L)) {
auto &debug = $world.get_the<Debug>();
debug.LIGHT = !debug.LIGHT;
} else if(KB::isKeyPressed(KB::I)) {
// yes, using an if to avoid double grabbing screen
if($show_modal) {
$show_modal = false;
} else {
pause_screen();
$show_modal = true;
}
} else if(KB::isKeyPressed(KB::P)) {
auto &debug = $world.get_the<Debug>();
debug.PATHS = !debug.PATHS;
} else if(KB::isKeyPressed(KB::S)) {
save_world();
} else if(KB::isKeyPressed(KB::Tab)) {
$status_ui.key_press(Event::Tab);
} else if(KB::isKeyPressed(KB::Enter)) {
$status_ui.key_press(Event::Return);
}
} else if($renderer.mouse_position($status_ui, pos)) {
if(MOUSE::isButtonPressed(MOUSE::Left)) {
$status_ui.mouse_click(Mouse::Button::Left, pos);
event_happened = true;
} else {
$status_ui.mouse_release(Mouse::Button::Left, pos);
}
}
}
return event_happened;
}
void GUI::init_shaders() {
auto& shader = $paused.load_shader("./shaders/modal.frag");
shader.setUniform("offsetFactor", sf::Glsl::Vec2{0.001f, 0.001f});
shader.setUniform("darkness", 0.05f);
}
void GUI::pause_screen() {
auto &window = $renderer.$window;
auto size = window.getSize();
$paused.texture.create(size.x, size.y);
$paused.texture.update(window);
$paused.sprite.setTexture($paused.texture);
$paused.sprite.setPosition(0,0);
}
void GUI::draw_paused() {
$renderer.draw_sprite($paused.sprite, &$paused.shader);
}
void GUI::run_systems() {
auto player = $world.get_the<Player>();
System::motion($world, $game_map);
System::enemy_pathing($world, $game_map, player);
System::lighting($world, $game_map, $lights, player);
System::collision($world, player);
System::death($world);
}
void GUI::shake() {
for(int i = 0; i < 10; ++i) {
int x = Random::uniform<int>(-10,10);
int y = Random::uniform<int>(-10,10);
// add x/y back to draw screen
$renderer.draw($map_view, x, y);
$renderer.display();
std::this_thread::sleep_for(1ms);
}
}
void GUI::render_scene() {
$renderer.clear();
if($show_modal) {
draw_paused();
$inventory_ui.render();
$renderer.draw($inventory_ui);
} else {
$map_view.render();
$renderer.draw($map_view);
$status_ui.render();
$renderer.draw($status_ui);
}
$renderer.display();
}
int GUI::main(bool run_once) {
$world.set_the<Debug>({});
create_renderer();
run_systems();
do {
render_scene();
if(handle_ui_events()) {
run_systems();
handle_world_events();
}
std::this_thread::sleep_for(10ms);
} while(!run_once && $renderer.is_open());
return 0;
}