|
|
|
#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"
|
|
|
|
|
|
|
|
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("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(BASE_MAP_FONT_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GUI::resize_map(int new_size) {
|
|
|
|
if($renderer.resize_map(new_size)) {
|
|
|
|
auto bounds = $renderer.$base_glyph.bounds;
|
|
|
|
int view_x = std::ceil((VIDEO_X - GAME_MAP_POS) / bounds.width);
|
|
|
|
int view_y = std::ceil(VIDEO_Y / bounds.height);
|
|
|
|
|
|
|
|
// don't allow resizing beyond/below game map size
|
|
|
|
if(view_x > GAME_MAP_X || view_y > GAME_MAP_Y) return;
|
|
|
|
|
|
|
|
$view_port = {size_t(view_x), size_t(view_y)};
|
|
|
|
|
|
|
|
// 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, 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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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::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_screen(true, x, y);
|
|
|
|
std::this_thread::sleep_for(1ms);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
|
|
|
// $sounds.play("ambient");
|
|
|
|
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;
|
|
|
|
}
|