|  |  |  | #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"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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::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);
 | 
					
						
							|  |  |  |     $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}", player_combat.hp)) | border,
 | 
					
						
							|  |  |  |               text($status_text) | border,
 | 
					
						
							|  |  |  |               separator(),
 | 
					
						
							|  |  |  |               log_box
 | 
					
						
							|  |  |  |               )  | flex_grow
 | 
					
						
							|  |  |  |         ),
 | 
					
						
							|  |  |  |         separator(),
 | 
					
						
							|  |  |  |         hbox(),
 | 
					
						
							|  |  |  |     });
 | 
					
						
							|  |  |  |   });
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GUI::handle_world_events() {
 | 
					
						
							|  |  |  |   auto player = $world.get_the<Player>();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while($world.has_event<Events::GUI>()) {
 | 
					
						
							|  |  |  |     auto [evt, entity] = $world.recv<Events::GUI>();
 | 
					
						
							|  |  |  |     switch(evt) {
 | 
					
						
							|  |  |  |       case Events::GUI::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 Events::GUI::MISS:
 | 
					
						
							|  |  |  |         if(entity == player.entity) {
 | 
					
						
							|  |  |  |           $log.log("You MISSED the enemy.");
 | 
					
						
							|  |  |  |         } else {
 | 
					
						
							|  |  |  |           $log.log("Enemy MISSED YOU.");
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |         break;
 | 
					
						
							|  |  |  |       case Events::GUI::DEAD:
 | 
					
						
							|  |  |  |         $log.log("--- ENEMY DEAD!");
 | 
					
						
							|  |  |  |         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);
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   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;
 | 
					
						
							|  |  |  | }
 |