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

214 lines
5.8 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 <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"
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!"}}),
$view_port{0,0},
$screen(SCREEN_X, SCREEN_Y),
$map_screen(0,0),
$world(world),
$sounds("./assets"),
$renderer($canvas, $map_screen, $screen)
{
// this needs a config file soon
$sounds.load("hit", "hit.wav");
resize_map(BASE_MAP_FONT_SIZE);
}
void GUI::resize_map(int new_size) {
if($renderer.resize_map(new_size)) {
auto bounds = $renderer.$base_glyph.bounds;
$view_port = {
size_t(std::ceil((VIDEO_X - GAME_MAP_POS) / bounds.width)),
size_t(std::ceil(VIDEO_Y / bounds.height))
};
// set canvas to best size
$canvas = Canvas($view_port.x * 2, $view_port.y * 4);
$map_screen = Screen($view_port.x, $view_port.y);
}
}
void GUI::save_world() {
$log.log("Game saved!");
save::to_file("./savefile.world", $world, $game_map);
}
void GUI::create_renderer() {
Terminal::SetColorSupport(Terminal::Color::TrueColor);
auto player = $world.get_the<Player>();
$map_view = Renderer([&] {
System::draw_map($world, $game_map, $canvas, $view_port.x, $view_port.y);
return canvas($canvas);
});
$document = 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(),
});
});
}
void GUI::handle_world_events() {
using eGUI = Events::GUI;
auto player = $world.get_the<Player>();
while($world.has_event<eGUI>()) {
auto [evt, entity] = $world.recv<eGUI>();
switch(evt) {
case eGUI::HIT: {
auto combat = $world.get<Combat>(entity);
if(entity == player.entity) {
$log.log(format("Enemy HIT YOU, you have {} HP!", combat.hp));
$sounds.play("hit");
} else {
$log.log(format("You HIT enemy, they have {} HP!", combat.hp));
$sounds.play("hit");
}
} break;
case eGUI::MISS:
if(entity == player.entity) {
$log.log("You MISSED the enemy.");
} else {
$log.log("Enemy MISSED YOU.");
}
break;
case eGUI::DEAD:
$log.log("--- ENEMY DEAD!");
break;
case eGUI::LOOT: {
auto loot = $world.get<Loot>(entity);
auto inventory = $world.get<Inventory>(player.entity);
$log.log(format("You found {} gold. You have {} now.",
loot.amount, inventory.gold));
}
break;
default:
$log.log(format("INVALID EVENT! {},{}", evt, entity));
}
}
}
bool GUI::handle_ui_events() {
bool event_happened = false;
sf::Event event;
auto player = $world.get_the<Player>();
int map_font_size = $renderer.font_size();
while($renderer.poll_event(event)) {
if(event.type == sf::Event::Closed) {
$renderer.close();
} else if(event.type == sf::Event::KeyPressed) {
auto& player_motion = $world.get<Motion>(player.entity);
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
player_motion.dx = -1;
event_happened = true;
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
player_motion.dx = 1;
event_happened = true;
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
player_motion.dy = -1;
event_happened = true;
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) {
player_motion.dy = 1;
event_happened = true;
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Equal)) {
resize_map(map_font_size + 10);
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Hyphen)) {
resize_map(map_font_size - 10);
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
save_world();
}
}
}
return event_happened;
}
void GUI::run_systems() {
auto player = $world.get_the<Player>();
System::enemy_pathing($world, $game_map, player);
System::motion($world, $game_map);
System::combat($world, player);
System::death($world);
}
void GUI::render_scene() {
$screen.Clear();
$map_screen.Clear();
Render($map_screen, $map_view->Render());
Render($screen, $document->Render());
$renderer.draw_screen();
}
int GUI::main() {
create_renderer();
run_systems();
while($renderer.is_open()) {
render_scene();
if(handle_ui_events()) {
run_systems();
}
handle_world_events();
std::this_thread::sleep_for(10ms);
}
return 0;
}