#include // for operator""s, chrono_literals #include // for cout, ostream #include #include // for allocator, shared_ptr #include // for string, operator<< #include // for sleep_for #include #include #include // for hflow, paragraph, separator, hbox, vbox, filler, operator|, border, Element #include // for Render #include // for ftxui #include #include #include #include "dbc.hpp" #include "gui.hpp" #include "rand.hpp" #include "systems.hpp" #include "events.hpp" #include "render.hpp" #include "save.hpp" const int MAX_FONT_SIZE = 140; const int MIN_FONT_SIZE = 20; 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, SCREEN_WIDTH, SCREEN_HEIGHT), $map_view(GAME_MAP_POS, 0, 0, 0, true), $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); } void GUI::resize_map(int new_size) { if($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() { Terminal::SetColorSupport(Terminal::Color::TrueColor); auto player = $world.get_the(); $map_view.set_renderer(Renderer([&] { System::draw_map($world, $game_map, $canvas, $map_view.width, $map_view.height); return canvas($canvas); })); auto modal_buttons = Container::Horizontal({ Button("OK", [&]{ $show_modal = false; }), Button("CANCEL", [&]{ $show_modal = false; }), }); auto modal_test = Renderer([&, modal_buttons] { return hbox({ hflow( vbox( text("Hello!"), modal_buttons->Render() ))}) | border; }); modal_test->Add(modal_buttons); auto test_button = Container::Horizontal({ Button("Open Test Modal", [&]{ $show_modal = true; }), Button("Close It", [&]{ $show_modal = false; }), }); auto status_rend = Renderer(test_button, [&, modal_test, test_button, player]{ const auto& player_combat = $world.get(player.entity); const auto& inventory = $world.get(player.entity); $status_text = player_combat.hp > 0 ? "NOT DEAD" : "DEAD!!!!!!"; std::vector 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( test_button->Render(), text(format("HP: {: >3} GOLD: {: >3}", player_combat.hp, inventory.gold)) | border, text($status_text) | border, separator(), log_box ) | flex_grow ), separator(), hbox(), }); }); status_rend |= Modal(modal_test, &$show_modal); $status_ui.set_renderer(status_rend); $status_ui.add(modal_test); } void GUI::handle_world_events() { using eGUI = Events::GUI; auto player = $world.get_the(); while($world.has_event()) { auto [evt, entity, data] = $world.recv(); switch(evt) { case eGUI::COMBAT: { auto &damage = std::any_cast(data); auto enemy_combat = $world.get(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(data); auto inventory = $world.get(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() { using KB = sf::Keyboard; using MOUSE = sf::Mouse; bool event_happened = false; sf::Event event; auto player = $world.get_the(); int map_font_size = $renderer.font_size(); auto& player_motion = $world.get(player.entity); while($renderer.poll_event(event)) { if(event.type == sf::Event::Closed) { $renderer.close(); } 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::S)) { save_world(); } else if(KB::isKeyPressed(KB::Tab)) { $status_ui.$component->OnEvent(Event::Tab); } else if(KB::isKeyPressed(KB::Enter)) { $status_ui.$component->OnEvent(Event::Return); } } else if(MOUSE::isButtonPressed(MOUSE::Left)) { sf::Vector2i pos = MOUSE::getPosition($renderer.$window); Mouse mev; mev.button = Mouse::Button::Left, mev.x=pos.x / $renderer.$ui_bounds.width; // this needs to be in...panel coordinates? mev.y=pos.y / $renderer.$ui_bounds.height; $status_ui.$component->OnEvent(Event::Mouse("", mev)); } } return event_happened; } void GUI::run_systems() { auto player = $world.get_the(); 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(-20,20); int y = Random::uniform(-20,20); // 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(); $renderer.draw($status_ui); $renderer.draw($map_view); $renderer.display(); } 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; }