From d798d154aef57afc0a714241527a7fd8c66103ac Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Fri, 7 Feb 2025 19:32:00 -0500 Subject: [PATCH] We now have a full map that's basically the same mapping system from Roguish. There's a bug right now where it needs you to move once to calc the light and it's not being centered, but it does work. --- Makefile | 2 +- ansi_parser.cpp | 371 ++++++++++++++++++++++++++++++++++++++++++++++ ansi_parser.hpp | 23 +++ ansi_parser.rl | 163 ++++++++++++++++++++ assets/tiles.json | 6 +- color.hpp | 14 ++ components.cpp | 10 +- components.hpp | 7 + constants.hpp | 11 +- gui.cpp | 133 ++++++++++++++--- gui.hpp | 27 +++- main.cpp | 2 + meson.build | 8 +- panel.cpp | 64 ++++++++ panel.hpp | 60 ++++++++ render.cpp | 275 ++++++++++++++++++++++++++++++++++ render.hpp | 80 ++++++++++ systems.cpp | 25 ++++ systems.hpp | 2 + tilemap.cpp | 18 --- tilemap.hpp | 1 - wraps/ftxui.wrap | 15 ++ 22 files changed, 1270 insertions(+), 47 deletions(-) create mode 100644 ansi_parser.cpp create mode 100644 ansi_parser.hpp create mode 100644 ansi_parser.rl create mode 100644 color.hpp create mode 100644 panel.cpp create mode 100644 panel.hpp create mode 100644 render.cpp create mode 100644 render.hpp create mode 100644 wraps/ftxui.wrap diff --git a/Makefile b/Makefile index d5638ee..d1043ee 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ reset: %.cpp : %.rl ragel -o $@ $< -build: +build: ansi_parser.cpp meson compile -j 10 -C builddir release_build: diff --git a/ansi_parser.cpp b/ansi_parser.cpp new file mode 100644 index 0000000..5a1224a --- /dev/null +++ b/ansi_parser.cpp @@ -0,0 +1,371 @@ + +#line 1 "ansi_parser.rl" +#include +#include +#include "dbc.hpp" +#include +#include "ansi_parser.hpp" +#include + +using namespace fmt; + + +#line 122 "ansi_parser.rl" + + + +#line 13 "ansi_parser.cpp" +static const char _ansi_parser_actions[] = { + 0, 1, 0, 1, 3, 1, 4, 1, + 5, 1, 6, 1, 7, 1, 8, 1, + 9, 1, 10, 1, 11, 1, 15, 1, + 16, 2, 1, 12, 2, 1, 13, 2, + 6, 7, 2, 16, 5, 3, 1, 14, + 2 +}; + +static const char _ansi_parser_key_offsets[] = { + 0, 0, 1, 2, 11, 12, 14, 17, + 18, 22, 23, 27, 28, 29, 30, 31, + 33, 36, 38, 41, 43, 46, 47, 50, + 51, 52, 53, 54, 55 +}; + +static const int _ansi_parser_trans_keys[] = { + 27, 91, 48, 49, 50, 51, 52, 55, + 57, 53, 54, 109, 48, 109, 34, 48, + 55, 109, 50, 52, 55, 109, 109, 49, + 56, 57, 109, 109, 59, 50, 59, 48, + 57, 59, 48, 57, 48, 57, 59, 48, + 57, 48, 57, 109, 48, 57, 109, 56, + 57, 109, 59, 50, 109, 109, 27, 27, + 0 +}; + +static const char _ansi_parser_single_lengths[] = { + 0, 1, 1, 7, 1, 2, 3, 1, + 4, 1, 4, 1, 1, 1, 1, 0, + 1, 0, 1, 0, 1, 1, 3, 1, + 1, 1, 1, 1, 1 +}; + +static const char _ansi_parser_range_lengths[] = { + 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + +static const char _ansi_parser_index_offsets[] = { + 0, 0, 2, 4, 13, 15, 18, 22, + 24, 29, 31, 36, 38, 40, 42, 44, + 46, 49, 51, 54, 56, 59, 61, 65, + 67, 69, 71, 73, 75 +}; + +static const char _ansi_parser_trans_targs[] = { + 2, 1, 3, 0, 4, 5, 8, 10, + 22, 26, 6, 7, 0, 28, 0, 6, + 28, 0, 7, 7, 7, 0, 28, 0, + 7, 7, 9, 28, 0, 28, 0, 11, + 12, 21, 28, 0, 28, 0, 13, 0, + 14, 0, 15, 0, 16, 0, 17, 16, + 0, 18, 0, 19, 18, 0, 20, 0, + 28, 20, 0, 28, 0, 23, 25, 28, + 0, 24, 0, 14, 0, 28, 0, 28, + 0, 2, 1, 2, 1, 0 +}; + +static const char _ansi_parser_trans_actions[] = { + 0, 7, 0, 0, 21, 21, 21, 21, + 21, 21, 21, 21, 0, 31, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 17, 0, 15, 0, 0, + 0, 0, 0, 0, 19, 0, 0, 0, + 3, 0, 0, 0, 1, 0, 25, 0, + 0, 1, 0, 28, 0, 0, 1, 0, + 37, 0, 0, 9, 0, 0, 0, 0, + 0, 0, 0, 5, 0, 11, 0, 13, + 0, 0, 7, 23, 34, 0 +}; + +static const char _ansi_parser_eof_actions[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 23 +}; + +static const int ansi_parser_start = 27; +static const int ansi_parser_first_final = 27; +static const int ansi_parser_error = 0; + +static const int ansi_parser_en_main = 27; + + +#line 125 "ansi_parser.rl" + +#include + +ANSIParser::ANSIParser(sf::Color default_fg, sf::Color default_bg) : + $default_fg(default_fg), + $default_bg(default_bg) +{ +} + +bool ANSIParser::parse(std::wstring_view codes, ColorCB color_cb, WriteCB write_cb) { + const wchar_t *start = NULL; + int cs = 0; + unsigned int value = 0; + const wchar_t *p = codes.data(); + const wchar_t *pe = p + codes.size(); + const wchar_t *eof = pe; + sf::Color bgcolor($default_bg); + sf::Color color($default_fg); + sf::Color* target = &color; + + +#line 120 "ansi_parser.cpp" + { + cs = ansi_parser_start; + } + +#line 146 "ansi_parser.rl" + +#line 123 "ansi_parser.cpp" + { + int _klen; + unsigned int _trans; + const char *_acts; + unsigned int _nacts; + const int *_keys; + + if ( p == pe ) + goto _test_eof; + if ( cs == 0 ) + goto _out; +_resume: + _keys = _ansi_parser_trans_keys + _ansi_parser_key_offsets[cs]; + _trans = _ansi_parser_index_offsets[cs]; + + _klen = _ansi_parser_single_lengths[cs]; + if ( _klen > 0 ) { + const int *_lower = _keys; + const int *_mid; + const int *_upper = _keys + _klen - 1; + while (1) { + if ( _upper < _lower ) + break; + + _mid = _lower + ((_upper-_lower) >> 1); + if ( (*p) < *_mid ) + _upper = _mid - 1; + else if ( (*p) > *_mid ) + _lower = _mid + 1; + else { + _trans += (unsigned int)(_mid - _keys); + goto _match; + } + } + _keys += _klen; + _trans += _klen; + } + + _klen = _ansi_parser_range_lengths[cs]; + if ( _klen > 0 ) { + const int *_lower = _keys; + const int *_mid; + const int *_upper = _keys + (_klen<<1) - 2; + while (1) { + if ( _upper < _lower ) + break; + + _mid = _lower + (((_upper-_lower) >> 1) & ~1); + if ( (*p) < _mid[0] ) + _upper = _mid - 2; + else if ( (*p) > _mid[1] ) + _lower = _mid + 2; + else { + _trans += (unsigned int)((_mid - _keys)>>1); + goto _match; + } + } + _trans += _klen; + } + +_match: + cs = _ansi_parser_trans_targs[_trans]; + + if ( _ansi_parser_trans_actions[_trans] == 0 ) + goto _again; + + _acts = _ansi_parser_actions + _ansi_parser_trans_actions[_trans]; + _nacts = (unsigned int) *_acts++; + while ( _nacts-- > 0 ) + { + switch ( *_acts++ ) + { + case 0: +#line 14 "ansi_parser.rl" + { + start = p; + } + break; + case 1: +#line 18 "ansi_parser.rl" + { + value = 0; + size_t len = p - start; + dbc::check(start[0] != '-', "negative numbers not supported"); + + switch(len) { + case 10: value += (start[len-10] - '0') * 1000000000; [[fallthrough]]; + case 9: value += (start[len- 9] - '0') * 100000000; [[fallthrough]]; + case 8: value += (start[len- 8] - '0') * 10000000; [[fallthrough]]; + case 7: value += (start[len- 7] - '0') * 1000000; [[fallthrough]]; + case 6: value += (start[len- 6] - '0') * 100000; [[fallthrough]]; + case 5: value += (start[len- 5] - '0') * 10000; [[fallthrough]]; + case 4: value += (start[len- 4] - '0') * 1000; [[fallthrough]]; + case 3: value += (start[len- 3] - '0') * 100; [[fallthrough]]; + case 2: value += (start[len- 2] - '0') * 10; [[fallthrough]]; + case 1: value += (start[len- 1] - '0'); + break; + default: + dbc::sentinel("can't process > 10 digits"); + } + } + break; + case 2: +#line 40 "ansi_parser.rl" + { + color_cb(color, bgcolor); + } + break; + case 3: +#line 43 "ansi_parser.rl" + { + target = &color; + } + break; + case 4: +#line 46 "ansi_parser.rl" + { + target = &bgcolor; + } + break; + case 5: +#line 50 "ansi_parser.rl" + { + write_cb((*p)); + } + break; + case 6: +#line 54 "ansi_parser.rl" + { + color = $default_fg; + color_cb(color, bgcolor); + } + break; + case 7: +#line 58 "ansi_parser.rl" + { + bgcolor = $default_bg; + color_cb(color, bgcolor); + } + break; + case 8: +#line 62 "ansi_parser.rl" + { + color = $default_bg; + bgcolor = $default_fg; + color_cb(color, bgcolor); + } + break; + case 9: +#line 67 "ansi_parser.rl" + { + color = $default_fg; + bgcolor = $default_bg; + color_cb(color, bgcolor); + } + break; + case 10: +#line 72 "ansi_parser.rl" + { + color = sf::Color(100,100,100); + color_cb(color, bgcolor); + } + break; + case 11: +#line 76 "ansi_parser.rl" + { + color = sf::Color::Red; + color_cb(color, bgcolor); + } + break; + case 12: +#line 81 "ansi_parser.rl" + { target->r = value; } + break; + case 13: +#line 82 "ansi_parser.rl" + { target->g = value; } + break; + case 14: +#line 83 "ansi_parser.rl" + { target->b = value; } + break; + case 15: +#line 84 "ansi_parser.rl" + { value = 0; } + break; + case 16: +#line 85 "ansi_parser.rl" + {} + break; +#line 296 "ansi_parser.cpp" + } + } + +_again: + if ( cs == 0 ) + goto _out; + if ( ++p != pe ) + goto _resume; + _test_eof: {} + if ( p == eof ) + { + const char *__acts = _ansi_parser_actions + _ansi_parser_eof_actions[cs]; + unsigned int __nacts = (unsigned int) *__acts++; + while ( __nacts-- > 0 ) { + switch ( *__acts++ ) { + case 16: +#line 85 "ansi_parser.rl" + {} + break; +#line 314 "ansi_parser.cpp" + } + } + } + + _out: {} + } + +#line 147 "ansi_parser.rl" + + bool good = pe - p == 0; + + if(!good) { + p -= 10; + // dear cthuhlu, save me from the pain that is wstring + for(int i = 0; i < 100; i++) { + try { + print("{}", p[i] == 0x1B ? '^' : char(p[i])); + } catch(...) { + print("?=", int(p[i])); + } + } + } + + return good; +} diff --git a/ansi_parser.hpp b/ansi_parser.hpp new file mode 100644 index 0000000..9dd054c --- /dev/null +++ b/ansi_parser.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include +#include + +typedef std::function ColorCB; + +typedef std::function WriteCB; + +class ANSIParser { + sf::Color $default_fg; + sf::Color $default_bg; + std::wstring_convert> $converter; + +public: + ANSIParser(sf::Color default_fg, sf::Color default_bg); + + // disable copying + ANSIParser(ANSIParser& ap) = delete; + + bool parse(std::wstring_view codes, ColorCB color_cb, WriteCB write_cb); +}; diff --git a/ansi_parser.rl b/ansi_parser.rl new file mode 100644 index 0000000..dac3e03 --- /dev/null +++ b/ansi_parser.rl @@ -0,0 +1,163 @@ +#include +#include +#include "dbc.hpp" +#include +#include "ansi_parser.hpp" +#include + +using namespace fmt; + +%%{ + machine ansi_parser; + alphtype int; + + action tstart { + start = fpc; + } + + action number { + value = 0; + size_t len = fpc - start; + dbc::check(start[0] != '-', "negative numbers not supported"); + + switch(len) { + case 10: value += (start[len-10] - '0') * 1000000000; [[fallthrough]]; + case 9: value += (start[len- 9] - '0') * 100000000; [[fallthrough]]; + case 8: value += (start[len- 8] - '0') * 10000000; [[fallthrough]]; + case 7: value += (start[len- 7] - '0') * 1000000; [[fallthrough]]; + case 6: value += (start[len- 6] - '0') * 100000; [[fallthrough]]; + case 5: value += (start[len- 5] - '0') * 10000; [[fallthrough]]; + case 4: value += (start[len- 4] - '0') * 1000; [[fallthrough]]; + case 3: value += (start[len- 3] - '0') * 100; [[fallthrough]]; + case 2: value += (start[len- 2] - '0') * 10; [[fallthrough]]; + case 1: value += (start[len- 1] - '0'); + break; + default: + dbc::sentinel("can't process > 10 digits"); + } + } + + action color_out { + color_cb(color, bgcolor); + } + action is_fg { + target = &color; + } + action is_bg { + target = &bgcolor; + } + + action out { + write_cb(fc); + } + + action reset_fg { + color = $default_fg; + color_cb(color, bgcolor); + } + action reset_bg { + bgcolor = $default_bg; + color_cb(color, bgcolor); + } + action invert { + color = $default_bg; + bgcolor = $default_fg; + color_cb(color, bgcolor); + } + action reset_invert { + color = $default_fg; + bgcolor = $default_bg; + color_cb(color, bgcolor); + } + action half_bright { + color = sf::Color(100,100,100); + color_cb(color, bgcolor); + } + action red_text { + color = sf::Color::Red; + color_cb(color, bgcolor); + } + + action red { target->r = value; } + action blue { target->g = value; } + action green { target->b = value; } + action start { value = 0; } + action end {} + action log { println("command {}", (char)fc); } + + ESC = 0x1B; + start = ESC "["; + fg = "38;" %is_fg; + bg = "48;" %is_bg; + reset = ("39" %reset_fg | "49" %reset_bg); + num = digit+ >tstart %number; + color256 = "5;"; + color24b = "2;"; + + ansi = ( + start %start + ( + reset | + "0" %reset_fg %reset_bg | + "1" | + "2" %half_bright | + "3" | + "4" | + "5" | + "6" | + "7" %invert | + "31" %red_text | + "22" | + "24" | + "27" %reset_invert | + "9" ["0"-"7"] | + "10" ["0"-"7"] | + (fg|bg) (color24b num %red ";" num %blue ";" num %green ) %color_out + ) "m" %end + ); + + other = (any+ @out -- ESC)*; + + main := (other :> ansi)**; +}%% + +%% write data; + +#include + +ANSIParser::ANSIParser(sf::Color default_fg, sf::Color default_bg) : + $default_fg(default_fg), + $default_bg(default_bg) +{ +} + +bool ANSIParser::parse(std::wstring_view codes, ColorCB color_cb, WriteCB write_cb) { + const wchar_t *start = NULL; + int cs = 0; + unsigned int value = 0; + const wchar_t *p = codes.data(); + const wchar_t *pe = p + codes.size(); + const wchar_t *eof = pe; + sf::Color bgcolor($default_bg); + sf::Color color($default_fg); + sf::Color* target = &color; + + %% write init; + %% write exec; + + bool good = pe - p == 0; + + if(!good) { + p -= 10; + // dear cthuhlu, save me from the pain that is wstring + for(int i = 0; i < 100; i++) { + try { + print("{}", p[i] == 0x1B ? '^' : char(p[i])); + } catch(...) { + print("?=", int(p[i])); + } + } + } + + return good; +} diff --git a/assets/tiles.json b/assets/tiles.json index 40d0813..59806e4 100644 --- a/assets/tiles.json +++ b/assets/tiles.json @@ -11,20 +11,20 @@ "foreground": [230, 20, 30], "background": [230, 20, 120], "collision": true, - "display": "█" + "display": "\ua5b8" }, "WALL_VINES": { "texture": "assets/wall_with_vines-256.png", "foreground": [40, 15, 125], "background": [200, 29, 75], "collision": false, - "display":"#" + "display":"\u0799" }, "WALL_PILLAR": { "texture": "assets/wall_with_pillars-256.png", "foreground": [40, 15, 125], "background": [200, 29, 75], "collision": false, - "display":"%" + "display":"\u2274" } } diff --git a/color.hpp b/color.hpp new file mode 100644 index 0000000..204c135 --- /dev/null +++ b/color.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace ColorValue { + const sf::Color BLACK{1, 4, 2}; + const sf::Color DARK_DARK{9, 29, 16}; + const sf::Color DARK_MID{14, 50, 26}; + const sf::Color DARK_LIGHT{0, 109, 44}; + const sf::Color MID{63, 171, 92}; + const sf::Color LIGHT_DARK{161, 217, 155}; + const sf::Color LIGHT_MID{199, 233, 192}; + const sf::Color LIGHT_LIGHT{229, 245, 224}; + const sf::Color WHITE{255, 255, 255}; + const sf::Color TRANSPARENT = sf::Color::Transparent; +} diff --git a/components.cpp b/components.cpp index c5e3893..06b26d3 100644 --- a/components.cpp +++ b/components.cpp @@ -13,7 +13,15 @@ namespace components { } else if(comp_type == "Loot") { world.set(entity, {config["amount"]}); } else if(comp_type == "Tile") { - world.set(entity, {config["chr"]}); + world.set(entity, { + config["chr"], + entity_data["foreground"][0], + entity_data["foreground"][1], + entity_data["foreground"][2], + entity_data["background"][0], + entity_data["background"][1], + entity_data["background"][2]}); + } else if(comp_type == "EnemyConfig") { world.set(entity, {config["hearing_distance"]}); } else if(comp_type == "Combat") { diff --git a/components.hpp b/components.hpp index ee0ffae..33ea39f 100644 --- a/components.hpp +++ b/components.hpp @@ -32,6 +32,13 @@ namespace components { struct Tile { std::string chr; + uint8_t fg_h = 200; + uint8_t fg_s = 20; + uint8_t fg_v = 200; + uint8_t bg_h = 100; + uint8_t bg_s = 20; + uint8_t bg_v = 0; + DEFINE_SERIALIZABLE(Tile, chr); }; diff --git a/constants.hpp b/constants.hpp index 26ec1d9..04b7c73 100644 --- a/constants.hpp +++ b/constants.hpp @@ -6,10 +6,10 @@ constexpr const int TEXTURE_WIDTH=256; constexpr const int TEXTURE_HEIGHT=256; constexpr const int RAY_VIEW_WIDTH=960; constexpr const int RAY_VIEW_HEIGHT=720; -constexpr const int RAY_VIEW_X=(1280 - RAY_VIEW_WIDTH); +constexpr const int SCREEN_WIDTH=1280; +constexpr const int RAY_VIEW_X=(SCREEN_WIDTH - RAY_VIEW_WIDTH); constexpr const int RAY_VIEW_Y=0; constexpr const int SCREEN_HEIGHT=720; -constexpr const int SCREEN_WIDTH=1280; constexpr const bool VSYNC=false; constexpr const int FRAME_LIMIT=60; constexpr const int NUM_SPRITES=1; @@ -42,3 +42,10 @@ constexpr int MIN_FONT_SIZE = 20; constexpr int STATUS_UI_WIDTH = 40; constexpr int STATUS_UI_HEIGHT = 30; constexpr float PERCENT = 0.01f; + + +// for the panels/renderer +constexpr wchar_t BG_TILE = L'█'; +constexpr wchar_t UI_BASE_CHAR = L'█'; +constexpr int BG_BOX_OFFSET=5; +constexpr const char *FONT_FILE_NAME="./assets/text.otf"; diff --git a/gui.cpp b/gui.cpp index b17ca94..af940aa 100644 --- a/gui.cpp +++ b/gui.cpp @@ -10,21 +10,82 @@ using namespace components; namespace gui { + using ftxui::Color; + + MapViewUI::MapViewUI(GameLevel &level) : + Panel(RAY_VIEW_X, 0, 0, 0, true), + $level(level) + {} + + void MapViewUI::update_level(GameLevel &level) { + $level = level; + } + + void MapViewUI::draw_map() { + const auto& debug = $level.world->get_the(); + const auto& player = $level.world->get_the(); + const auto& player_position = $level.world->get(player.entity); + Point start = $level.map->center_camera(player_position.location, width, height); + auto &tiles = $level.map->tiles(); + auto &paths = $level.map->paths(); + auto &lighting = $level.lights->lighting(); + + // WARN: this is exploiting that -1 in size_t becomes largest + size_t end_x = std::min(size_t(width), $level.map->width() - start.x); + size_t end_y = std::min(size_t(height), $level.map->height() - start.y); + + for(size_t y = 0; y < end_y; ++y) { + for(size_t x = 0; x < end_x; ++x) + { + const TileCell& tile = tiles.at(start.x+x, start.y+y); + // light value is an integer that's a percent + float light_value = debug.LIGHT ? 80 * PERCENT : lighting[start.y+y][start.x+x] * PERCENT; + int dnum = debug.PATHS ? paths[start.y+y][start.x+x] : WALL_PATH_LIMIT; + + if(debug.PATHS && dnum != WALL_PATH_LIMIT) { + string num = dnum > 15 ? "*" : fmt::format("{:x}", dnum); + + $canvas.DrawText(x * 2, y * 4, num, [dnum, tile, light_value](auto &pixel) { + pixel.foreground_color = Color::HSV(dnum * 20, 150, 200); + pixel.background_color = Color::HSV(30, 20, tile.bg_v * 50 * PERCENT); + }); + } else { + $canvas.DrawText(x * 2, y * 4, tile.display, [tile, light_value](auto &pixel) { + pixel.foreground_color = Color::HSV(tile.fg_h, tile.fg_s, tile.fg_v * light_value); + pixel.background_color = Color::HSV(tile.bg_h, tile.bg_s, tile.bg_v * light_value); + }); + } + } + } + + System::draw_entities(*$level.world, *$level.map, lighting, $canvas, start, width, height); + } + + void MapViewUI::create_render() { + set_renderer(Renderer([&] { + draw_map(); + return canvas($canvas); + })); + } + + void MapViewUI::resize_canvas() { + // set canvas to best size + $canvas = Canvas(width * 2, height * 4); + } + FSM::FSM() : $window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Raycaster Thing"), - $font{"./assets/text.otf"}, + $renderer($window), + $level($levels.current()), + $map_view($level), + $font{FONT_FILE_NAME}, $text{$font}, - $map_display{$font}, $rayview($textures, RAY_VIEW_WIDTH, RAY_VIEW_HEIGHT) { $window.setVerticalSyncEnabled(VSYNC); $window.setFramerateLimit(FRAME_LIMIT); $text.setPosition({10,10}); $text.setFillColor({255,255,255}); - $map_display.setPosition({10, SCREEN_HEIGHT-300}); - $map_display.setFillColor({255,255,255}); - $map_display.setLetterSpacing(0.5); - $map_display.setLineSpacing(0.9); $textures.load_tiles(); $textures.load_sprites(); } @@ -33,6 +94,7 @@ namespace gui { switch($state) { FSM_STATE(State, START, ev); FSM_STATE(State, MOVING, ev); + FSM_STATE(State, MAPPING, ev); FSM_STATE(State, ROTATING, ev); FSM_STATE(State, IDLE, ev); FSM_STATE(State, END, ev); @@ -41,12 +103,36 @@ namespace gui { void FSM::START(Event ) { generate_map(); + $level.world->set_the({}); $rayview.init_shaders(); $rayview.set_position(RAY_VIEW_X, RAY_VIEW_Y); $rayview.position_camera($player.x + 0.5, $player.y + 0.5); + + $renderer.init_terminal(); + $map_view.create_render(); + + $renderer.resize_grid(MAX_FONT_SIZE, $map_view); + $map_view.resize_canvas(); + state(State::IDLE); } + void FSM::MAPPING(Event ev) { + // BUG: can't close window when in mapping + switch(ev) { + case Event::MAP_OPEN: + state(State::IDLE); + break; + case Event::CLOSE: + state(State::IDLE); + break; + case Event::TICK: + break; + default: + dbc::log("invalid event sent to MAPPING"); + } + } + void FSM::MOVING(Event ) { if($camera.play_move($rayview)) { System::plan_motion(*$level.world, {size_t($camera.targetX), size_t($camera.targetY)}); @@ -89,6 +175,12 @@ namespace gui { $camera.plan_rotate($rayview, -1); state(State::ROTATING); break; + case FU::MAP_OPEN: + state(State::MAPPING); + break; + case FU::CLOSE: + dbc::log("Nothing to close."); + break; default: dbc::sentinel("unhandled event in IDLE"); } @@ -140,6 +232,12 @@ namespace gui { case KEY::R: $stats.reset(); break; + case KEY::M: + event(Event::MAP_OPEN); + break; + case KEY::Escape: + event(Event::CLOSE); + break; default: break; // ignored } @@ -156,15 +254,9 @@ namespace gui { void FSM::draw_gui() { sf::RectangleShape rect({SCREEN_WIDTH - RAY_VIEW_WIDTH, SCREEN_HEIGHT}); - sf::RectangleShape map_rect({SCREEN_WIDTH - RAY_VIEW_WIDTH - 10, 300}); - rect.setPosition({0,0}); rect.setFillColor({50, 50, 50}); - - map_rect.setPosition({0, SCREEN_HEIGHT-300}); - map_rect.setFillColor({20, 20, 20}); $window.draw(rect); - $window.draw(map_rect); $text.setString( fmt::format("FPS\n" @@ -184,15 +276,19 @@ namespace gui { $rayview.$dirY, $rayview.$posX, $rayview.$posY)); $window.draw($text); - - std::wstring map = $level.map->tiles().minimap(int($rayview.$posX), int($rayview.$posY)); - $map_display.setString(map); - $window.draw($map_display); } void FSM::render() { auto start = std::chrono::high_resolution_clock::now(); - $rayview.draw($window); + + if(in_state(State::MAPPING)) { + $window.clear(); + $map_view.render(); + $renderer.draw($map_view); + } else { + $rayview.draw($window); + } + auto end = std::chrono::high_resolution_clock::now(); auto elapsed = std::chrono::duration(end - start); $stats.sample(1/elapsed.count()); @@ -211,7 +307,7 @@ namespace gui { } void FSM::generate_map() { - $level = $levels.current(); + // ZED: this should eventually go away now that level manager is in play auto& player = $level.world->get_the(); auto& player_position = $level.world->get(player.entity); $player = player_position.location; @@ -222,6 +318,7 @@ namespace gui { System::enemy_pathing($level); System::collision($level); System::motion($level); + System::lighting($level); System::death($level); } diff --git a/gui.hpp b/gui.hpp index 9b1531c..2644b58 100644 --- a/gui.hpp +++ b/gui.hpp @@ -5,11 +5,30 @@ #include "levelmanager.hpp" #include "camera.hpp" #include "fsm.hpp" +#include "render.hpp" +#include "panel.hpp" +#include + +using ftxui::Canvas; namespace gui { + class MapViewUI : public Panel { + public: + Canvas $canvas; + GameLevel $level; + + MapViewUI(GameLevel &level); + void create_render(); + void resize_canvas(); + void draw_map(); + void update_level(GameLevel &level); + }; + + enum class State { START, MOVING, + MAPPING, ROTATING, IDLE, END @@ -22,6 +41,8 @@ namespace gui { MOVE_BACK, MOVE_LEFT, MOVE_RIGHT, + MAP_OPEN, + CLOSE, ROTATE_LEFT, ROTATE_RIGHT, QUIT @@ -29,15 +50,16 @@ namespace gui { class FSM : public DeadSimpleFSM { public: - GameLevel $level; float $rotation = -30.0f; Point $player{0,0}; LevelManager $levels; sf::RenderWindow $window; + SFMLRender $renderer; + GameLevel $level; + MapViewUI $map_view; CameraLOL $camera; sf::Font $font; sf::Text $text; - sf::Text $map_display; Stats $stats; TexturePack $textures; Raycaster $rayview; @@ -48,6 +70,7 @@ namespace gui { void START(Event ); void MOVING(Event ); + void MAPPING(Event); void ROTATING(Event ); void IDLE(Event ev); void END(Event ev); diff --git a/main.cpp b/main.cpp index a42eaa9..3416bca 100644 --- a/main.cpp +++ b/main.cpp @@ -10,6 +10,8 @@ int main() { // ZED: need to sort out how to deal with this in the FSM if(main.in_state(gui::State::IDLE)) { main.keyboard(); + } else if(main.in_state(gui::State::MAPPING)) { + main.keyboard(); } else{ main.event(gui::Event::TICK); } diff --git a/meson.build b/meson.build index 5324b24..c331998 100644 --- a/meson.build +++ b/meson.build @@ -30,17 +30,21 @@ sfml_main = dependency('sfml_main') sfml_network = dependency('sfml_network') sfml_system = dependency('sfml_system') sfml_window = dependency('sfml_window') +ftxui_screen = dependency('ftxui-screen') +ftxui_dom = dependency('ftxui-dom') +ftxui_component = dependency('ftxui-component') dependencies = [ fmt, json, opengl32, freetype2, flac, ogg, vorbis, vorbisfile, vorbisenc, winmm, gdi32, sfml_audio, sfml_graphics, sfml_main, sfml_network, sfml_system, - sfml_window + sfml_window, ftxui_screen, ftxui_dom, ftxui_component ] sources = [ 'animator.cpp', + 'ansi_parser.cpp', 'camera.cpp', 'combat.cpp', 'components.cpp', @@ -54,9 +58,11 @@ sources = [ 'map.cpp', 'matrix.cpp', 'matrix.cpp', + 'panel.cpp', 'pathing.cpp', 'rand.cpp', 'raycaster.cpp', + 'render.cpp', 'save.cpp', 'shiterator.hpp', 'spatialmap.cpp', diff --git a/panel.cpp b/panel.cpp new file mode 100644 index 0000000..e3a3cbf --- /dev/null +++ b/panel.cpp @@ -0,0 +1,64 @@ +#include "panel.hpp" +#include "dbc.hpp" + +void Panel::resize(int w, int h) { + $dirty = true; + width = w; + height = h; + $screen = Screen(width, height); +} + +void Panel::set_renderer(Component renderer) { + $dirty = true; + $component = renderer; +} + +void Panel::add(Component child) { + dbc::pre("must set_renderer first", $component != nullptr); + $dirty = true; + $component->Add(child); +} + +void Panel::render() { + $dirty = true; + if(must_clear) $screen.Clear(); + Render($screen, $component->Render()); +} + +const std::wstring& Panel::to_string() { + if($dirty) { + std::string as_text = $screen.ToString(); + $screenout = $converter.from_bytes(as_text); + $dirty = false; + } + + return $screenout; +} + +void Panel::mouse_click(ftxui::Mouse::Button btn, Point pos) { + ftxui::Mouse mev{ + .button=btn, + .motion=ftxui::Mouse::Motion::Pressed, + .x=int(pos.x), .y=int(pos.y) + }; + + $component->OnEvent(ftxui::Event::Mouse("", mev)); +} + +void Panel::mouse_release(ftxui::Mouse::Button btn, Point pos) { + ftxui::Mouse mev{ + .button=btn, + .motion=ftxui::Mouse::Motion::Released, + .x=int(pos.x), .y=int(pos.y) + }; + + $component->OnEvent(ftxui::Event::Mouse("", mev)); +} + +const Screen& Panel::screen() { + return $screen; +} + +void Panel::key_press(ftxui::Event event) { + $component->OnEvent(event); +} diff --git a/panel.hpp b/panel.hpp new file mode 100644 index 0000000..e764708 --- /dev/null +++ b/panel.hpp @@ -0,0 +1,60 @@ +#pragma once +#include // for Render +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "color.hpp" +#include "point.hpp" + +const int UI_PANEL_BORDER_PX=5; + +using ftxui::Renderer, ftxui::Component, ftxui::Element, ftxui::Screen; + +class Panel { +public: + int x; + int y; + int width; + int height; + bool has_border = false; + bool must_clear = true; + bool grid = false; + sf::Color default_bg = ColorValue::BLACK; + sf::Color default_fg = ColorValue::LIGHT_LIGHT; + sf::Color border_color = ColorValue::MID; + int border_px = UI_PANEL_BORDER_PX; + + bool $dirty = true; + Component $component = nullptr; + Screen $screen; + std::wstring_convert> $converter; + std::wstring $screenout; + + Panel(int x, int y, int width, int height, bool is_grid=false) : + x(x), + y(y), + width(width), + height(height), + grid(is_grid), + $screen(Screen(width, height)) + { + must_clear = !is_grid; + }; + + void resize(int width, int height); + void set_renderer(Component renderer); + void add(Component child); + void render(); + void mouse_click(ftxui::Mouse::Button btn, Point pos); + void mouse_release(ftxui::Mouse::Button btn, Point pos); + void key_press(ftxui::Event event); + const std::wstring &to_string(); + const Screen &screen(); +}; diff --git a/render.cpp b/render.cpp new file mode 100644 index 0000000..adf4fd0 --- /dev/null +++ b/render.cpp @@ -0,0 +1,275 @@ +#include "render.hpp" +#include "ansi_parser.hpp" +#include +#include +#include +#include "map.hpp" +#include +#include "color.hpp" + +#if defined(_WIN64) || defined(_WIN32) + #include + #include + #include +#endif + +using namespace fmt; + +SFMLRender::SFMLRender(sf::RenderWindow &window) : + $window(window), + $map_font_size(0), + $line_spacing(0), + $default_fg(ColorValue::LIGHT_MID), + $default_bg(ColorValue::BLACK), + $bg_sprite($font_texture), + $font(FONT_FILE_NAME), + $ui_text($font), + $ansi($default_fg, $default_bg) +{ + // force true color, but maybe I want to support different color sets + $font.setSmooth(false); + $ui_text.setPosition({0,0}); + $ui_text.setCharacterSize($config.ui_font_size); + $ui_text.setFillColor(ColorValue::LIGHT_MID); + sf::Glyph glyph = $font.getGlyph($config.ui_base_char, $config.ui_font_size, false); + $text_bounds = glyph.bounds; + $cells_w = std::ceil($config.video_x / $text_bounds.size.x); + $cells_h = std::ceil($config.video_y / $text_bounds.size.y); +} + +sf::Sprite &SFMLRender::get_text_sprite(wchar_t tile) { + if(!$sprites.contains(tile)) { + sf::Glyph glyph = $font.getGlyph(tile, $map_font_size, false); + // WARNING! we actually have to do this here because SFML caches + // the glyphs on the font texture, so this gets loaded each time + // we get a new glyph from the font. + $font_texture = $font.getTexture($map_font_size); + $sprites.try_emplace(tile, $font_texture, glyph.textureRect); + } + + return $sprites.at(tile); +} + +void SFMLRender::clear_cache() { + $sprites.clear(); + bool good = $font.openFromFile(FONT_FILE_NAME); + dbc::check(good, "Failed to load the font."); + $font.setSmooth(false); + $ui_text.setFont($font); +} + +void SFMLRender::center_panel(Panel &panel) { + int cell_center_x = ($cells_w - panel.width) / 2; + int cell_center_y = ($cells_h - panel.height) / 2; + + panel.x = cell_center_x * $text_bounds.size.x; + panel.y = cell_center_y * $text_bounds.size.y; +} + +void SFMLRender::resize_grid(int new_size, Panel &panel_out) { + auto glyph = $font.getGlyph($config.bg_tile, new_size, false); + int view_x = std::ceil(($config.video_x - panel_out.x) / glyph.bounds.size.x); + int view_y = std::ceil(($config.video_y - panel_out.y) / glyph.bounds.size.y); + + // looks good, set 'em all + $base_glyph = glyph; + $map_font_size = new_size; + $sprites.clear(); // need to reset the sprites for the new size + $line_spacing = $font.getLineSpacing($map_font_size); + $bg_sprite = get_text_sprite($config.bg_tile); + $grid_bounds = $bg_sprite.getLocalBounds(); + panel_out.resize(view_x, view_y); +} + +inline void configure_tile(const sf::Sprite &sprite, sf::FloatRect &sp_bounds, sf::FloatRect grid_bounds, float &width_delta, float &height_delta) { + // BUG: I think I could create a struct that kept this info for all sprites loaded + // should look into caching all this instead of calcing it each time + sp_bounds = sprite.getLocalBounds(); + + // calculate where to center the sprite, but only if it's smaller + width_delta = grid_bounds.size.x > sp_bounds.size.x ? (grid_bounds.size.x - sp_bounds.size.x) / 2 : 0; + height_delta = grid_bounds.size.y > sp_bounds.size.x ? (grid_bounds.size.y - sp_bounds.size.y) / 2 : 0; +} + +void SFMLRender::render_grid(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float x, float y) { + wchar_t last_tile = $config.bg_tile; + sf::FloatRect sp_bounds; + float width_delta = 0; + float height_delta = 0; + sf::Sprite &sprite = get_text_sprite(last_tile); + const float start_x = x; + sf::Color cur_fg = default_fg; + sf::Color cur_bg = default_bg; + + $ansi.parse(text, [&](auto fg, auto bg) { + cur_fg = fg; + cur_bg = bg; + }, + + [&](wchar_t tile) { + switch(tile) { + case '\r': break; // ignore it + case '\n': { + // don't bother processing newlines, just skip + y += $line_spacing; + x = start_x; + } + break; + default: { + // only get a new sprite if the tile changed + if(last_tile != tile) { + sprite = get_text_sprite(tile); + configure_tile(sprite, sp_bounds, $grid_bounds, width_delta, height_delta); + last_tile = tile; // update last tile seen + } + + sprite.setPosition({x+width_delta, y+height_delta}); + sprite.setColor(cur_fg); + + // only draw background char if it's different from default + if(cur_bg != default_bg) { + $bg_sprite.setPosition({x, y}); + $bg_sprite.setColor(cur_bg); + $window.draw($bg_sprite); + } + + $window.draw(sprite); + // next cell + x += $base_glyph.advance; + } + } + }); +} + +inline sf::FloatRect draw_chunk(sf::RenderWindow& window, + sf::FloatRect text_bounds, sf::Text& text, sf::Color default_bg, + sf::Color bgcolor, int bg_box_offset, float x, float y, std::wstring &out) +{ + text.setString(out); + text.setPosition({x, y}); + // get a base character for the cell size + sf::FloatRect bounds({x, y}, {text_bounds.size.x * out.size(), text_bounds.size.y}); + + if(default_bg != bgcolor) { + sf::RectangleShape backing(bounds.size); + backing.setFillColor(bgcolor); + backing.setPosition({bounds.position.x, bounds.position.y + bg_box_offset}); + window.draw(backing); + } + + window.draw(text); + out.clear(); + return bounds; +} + +void SFMLRender::render_text(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float start_x, float start_y) { + std::wstring out; + float x = start_x; + float y = start_y; + sf::Color cur_bg = default_bg; + + // start with the default_fg until it's changed + $ui_text.setFillColor(default_fg); + + $ansi.parse(text, + [&](auto fg, auto bg) { + if(out.size() > 0 ) { + auto bounds = draw_chunk($window, + $text_bounds, $ui_text, + default_bg, cur_bg, $config.bg_box_offset, x, y, out); + x += bounds.size.x; + } + cur_bg = bg; + $ui_text.setFillColor(fg); + }, + [&](wchar_t tile) { + switch(tile) { + case '\r': break; // ignore it + case '\n': { + sf::FloatRect bounds; + + if(out.size() > 0) { + bounds = draw_chunk($window, $text_bounds, + $ui_text, default_bg, cur_bg, $config.bg_box_offset, x, y, out); + } else { + bounds = $ui_text.getLocalBounds(); + } + + y += bounds.size.y; + x = start_x; // reset to the original position + } + break; + default: + out += tile; + break; + } + } + ); + + if(out.size() > 0) { + draw_chunk($window, $text_bounds, $ui_text, default_bg, cur_bg, $config.bg_box_offset, x, y, out); + } +} + +void SFMLRender::draw_sprite(sf::Sprite &sprite, sf::Shader *shader) { + $window.draw(sprite, shader); +} + +/* + * Does not render the panel, you have to do that so you can control + * when things render. + */ +void SFMLRender::draw(Panel &panel, float x_offset, float y_offset) { + const std::wstring &panelout = panel.to_string(); + + auto bounds = panel.grid ? $grid_bounds : $text_bounds; + + sf::RectangleShape backing( + sf::Vector2f(bounds.size.x * panel.width + panel.border_px, + bounds.size.y * panel.height + panel.border_px)); + + backing.setFillColor(panel.default_bg); + + if(panel.has_border) { + backing.setOutlineColor(panel.border_color); + backing.setOutlineThickness(panel.border_px); + } + + backing.setPosition({panel.x + x_offset, panel.y + y_offset}); + $window.draw(backing); + + if(panel.grid) { + render_grid(panelout, panel.default_fg, panel.default_bg, panel.x + x_offset, panel.y + y_offset); + } else { + render_text(panelout, panel.default_fg, panel.default_bg, panel.x + x_offset, panel.y + y_offset); + } +} + +bool SFMLRender::mouse_position(Panel &panel, Point &out) { + // yes, you have to do this in sfml + sf::Vector2f pos = $window.mapPixelToCoords(sf::Mouse::getPosition($window)); + + auto bounds = panel.grid ? $grid_bounds : $text_bounds; + + if(pos.x >= panel.x && pos.y >= panel.y + && pos.x <= (panel.x + panel.width * bounds.size.x) + && pos.y <= (panel.y + panel.height * bounds.size.y)) + { + out = { + size_t((pos.x - panel.x) / bounds.size.x), + size_t((pos.y - panel.y) / bounds.size.y) + }; + + return true; + } + + return false; +} + +void SFMLRender::init_terminal() { +#if defined(_WIN64) || defined(_WIN32) + _setmode(_fileno(stdout), _O_U16TEXT); +#endif + + ftxui::Terminal::SetColorSupport(ftxui::Terminal::Color::TrueColor); +} diff --git a/render.hpp b/render.hpp new file mode 100644 index 0000000..8ebe793 --- /dev/null +++ b/render.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "point.hpp" +#include +#include "ansi_parser.hpp" +#include "panel.hpp" +#include "constants.hpp" +#include + +using ftxui::Canvas, ftxui::Screen; + +/* + * BUG: This could be so much better. + */ +struct RenderConfig { + unsigned int video_x = VIDEO_WINDOW_X; + unsigned int video_y = VIDEO_WINDOW_Y; + int ui_font_size=UI_FONT_SIZE; + int base_map_font_size=BASE_MAP_FONT_SIZE; + wchar_t bg_tile = BG_TILE; + wchar_t ui_base_char = UI_BASE_CHAR; + int bg_box_offset=BG_BOX_OFFSET; +}; + +struct SFMLRender { + int $cells_w = 0; + int $cells_h = 0; + RenderConfig $config; + sf::RenderWindow& $window; + int $map_font_size; + float $line_spacing; + sf::Color $default_fg; + sf::Color $default_bg; + sf::Texture $font_texture; + sf::Sprite $bg_sprite; + sf::Font $font; + sf::Text $ui_text; + ANSIParser $ansi; + + std::unordered_map $sprites; + sf::Glyph $base_glyph; + sf::FloatRect $grid_bounds; + std::wstring_convert> $converter; + sf::FloatRect $text_bounds; + + SFMLRender(sf::RenderWindow& window); + + // disable copy + SFMLRender(SFMLRender &other) = delete; + + sf::Sprite &get_text_sprite(wchar_t tile); + void resize_grid(int new_size, Panel &panel_out); + void render_grid(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float x, float y); + void render_text(const std::wstring &text, sf::Color default_fg, sf::Color default_bg, float x, float y); + + void draw(Panel &panel, float x_offset=0.0f, float y_offset=0.0f); + void draw_sprite(sf::Sprite &sprite, sf::Shader *shader); + void center_panel(Panel &panel); + + std::optional poll_event() { + return $window.pollEvent(); + } + + void close() { return $window.close(); } + + bool is_open() { return $window.isOpen(); } + + int font_size() { return $map_font_size; } + void clear() { $window.clear(); } + void display() { $window.display(); } + bool mouse_position(Panel &panel, Point &out); + void clear_cache(); + static void init_terminal(); +}; diff --git a/systems.cpp b/systems.cpp index 255fc70..b521954 100644 --- a/systems.cpp +++ b/systems.cpp @@ -12,6 +12,7 @@ using std::string; using namespace fmt; using namespace components; using lighting::LightSource; +using ftxui::Color; void System::lighting(GameLevel &level) { auto &light = *level.lights; @@ -221,3 +222,27 @@ void System::plan_motion(DinkyECS::World& world, Point move_to) { motion.dx = move_to.x - player_position.location.x; motion.dy = move_to.y - player_position.location.y; } + +/* + * This one is called inside the MapViewUI very often so + * just avoide GameMap unlike the others. + */ +void System::draw_entities(DinkyECS::World &world, Map &map, const Matrix &lights, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y) { + auto &tiles = map.tiles(); + + world.query([&](auto &ent[[maybe_unused]], auto &pos, auto &tile) { + if(pos.location.x >= cam_orig.x && pos.location.x <= cam_orig.x + view_x + && pos.location.y >= cam_orig.y && pos.location.y <= cam_orig.y + view_y) { + Point loc = map.map_to_camera(pos.location, cam_orig); + + float light_value = lights[pos.location.y][pos.location.x] * PERCENT; + const TileCell& cell = tiles.at(pos.location.x, pos.location.y); + + // the 2 and 4 are from ftxui::Canvas since it does a kind of "subpixel" drawing + canvas.DrawText(loc.x*2, loc.y*4, tile.chr, [tile, light_value, cell](auto &pixel) { + pixel.foreground_color = Color::HSV(tile.fg_h, tile.fg_s, tile.fg_v * light_value); + pixel.background_color = Color::HSV(cell.bg_h, cell.bg_s, cell.bg_v * light_value); + }); + } + }); +} diff --git a/systems.hpp b/systems.hpp index f837e7c..9d51547 100644 --- a/systems.hpp +++ b/systems.hpp @@ -1,6 +1,7 @@ #pragma once #include "components.hpp" #include "levelmanager.hpp" +#include namespace System { @@ -16,4 +17,5 @@ namespace System { void pickup(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item); void device(DinkyECS::World &world, DinkyECS::Entity actor, DinkyECS::Entity item); void plan_motion(DinkyECS::World& world, Point move_to); + void draw_entities(DinkyECS::World &world, Map &map, const Matrix &lights, ftxui::Canvas &canvas, const Point &cam_orig, size_t view_x, size_t view_y); } diff --git a/tilemap.cpp b/tilemap.cpp index fa49345..8987144 100644 --- a/tilemap.cpp +++ b/tilemap.cpp @@ -73,21 +73,3 @@ bool TileMap::INVARIANT() { dbc::check(matrix::width($tile_ids) == $width, "$tile_ids has wrong width"); return true; } - -std::wstring TileMap::minimap(size_t x, size_t y) { - string result; - - for(matrix::box it{$tile_ids, x, y, 5}; it.next();) { - const TileCell &cell = $display[it.y][it.x]; - if(it.x == x && it.y == y) { - result += "@"; - } else { - result += cell.display; - } - - if(it.x == it.right - 1) result += "\n"; - } - - std::wstring_convert> $converter; - return $converter.from_bytes(result); -} diff --git a/tilemap.hpp b/tilemap.hpp index e93d559..495300b 100644 --- a/tilemap.hpp +++ b/tilemap.hpp @@ -43,5 +43,4 @@ public: void dump(int show_x=-1, int show_y=-1); bool INVARIANT(); - std::wstring minimap(size_t x, size_t y); }; diff --git a/wraps/ftxui.wrap b/wraps/ftxui.wrap new file mode 100644 index 0000000..bec78a6 --- /dev/null +++ b/wraps/ftxui.wrap @@ -0,0 +1,15 @@ +[wrap-file] +directory = FTXUI-5.0.0 +source_url = https://github.com/ArthurSonzogni/FTXUI/archive/refs/tags/v5.0.0.tar.gz +source_filename = FTXUI-5.0.0.tar.gz +source_hash = a2991cb222c944aee14397965d9f6b050245da849d8c5da7c72d112de2786b5b +patch_filename = ftxui_5.0.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/ftxui_5.0.0-1/get_patch +patch_hash = 21c654e82739b90b95bd98c1d321264608d37c50d29fbcc3487f790fd5412909 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/ftxui_5.0.0-1/FTXUI-5.0.0.tar.gz +wrapdb_version = 5.0.0-1 + +[provide] +ftxui-screen = screen_dep +ftxui-dom = dom_dep +ftxui-component = component_dep