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.

master
Zed A. Shaw 4 weeks ago
parent 55b67dcf5d
commit d798d154ae
  1. 2
      Makefile
  2. 371
      ansi_parser.cpp
  3. 23
      ansi_parser.hpp
  4. 163
      ansi_parser.rl
  5. 6
      assets/tiles.json
  6. 14
      color.hpp
  7. 10
      components.cpp
  8. 7
      components.hpp
  9. 11
      constants.hpp
  10. 133
      gui.cpp
  11. 27
      gui.hpp
  12. 2
      main.cpp
  13. 8
      meson.build
  14. 64
      panel.cpp
  15. 60
      panel.hpp
  16. 275
      render.cpp
  17. 80
      render.hpp
  18. 25
      systems.cpp
  19. 2
      systems.hpp
  20. 18
      tilemap.cpp
  21. 1
      tilemap.hpp
  22. 15
      wraps/ftxui.wrap

@ -6,7 +6,7 @@ reset:
%.cpp : %.rl
ragel -o $@ $<
build:
build: ansi_parser.cpp
meson compile -j 10 -C builddir
release_build:

@ -0,0 +1,371 @@
#line 1 "ansi_parser.rl"
#include <fmt/core.h>
#include <string_view>
#include "dbc.hpp"
#include <SFML/Graphics.hpp>
#include "ansi_parser.hpp"
#include <iostream>
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 <ftxui/screen/terminal.hpp>
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;
}

@ -0,0 +1,23 @@
#pragma once
#include <string_view>
#include <SFML/Graphics.hpp>
#include <codecvt>
#include <functional>
typedef std::function<void(sf::Color bgcolor, sf::Color color)> ColorCB;
typedef std::function<void(wchar_t ch)> WriteCB;
class ANSIParser {
sf::Color $default_fg;
sf::Color $default_bg;
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $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);
};

@ -0,0 +1,163 @@
#include <fmt/core.h>
#include <string_view>
#include "dbc.hpp"
#include <SFML/Graphics.hpp>
#include "ansi_parser.hpp"
#include <iostream>
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 <ftxui/screen/terminal.hpp>
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;
}

@ -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"
}
}

@ -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;
}

@ -13,7 +13,15 @@ namespace components {
} else if(comp_type == "Loot") {
world.set<Loot>(entity, {config["amount"]});
} else if(comp_type == "Tile") {
world.set<Tile>(entity, {config["chr"]});
world.set<Tile>(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<EnemyConfig>(entity, {config["hearing_distance"]});
} else if(comp_type == "Combat") {

@ -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);
};

@ -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";

@ -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<Debug>();
const auto& player = $level.world->get_the<Player>();
const auto& player_position = $level.world->get<Position>(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<Debug>({});
$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<double>(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<Player>();
auto& player_position = $level.world->get<Position>(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);
}

@ -5,11 +5,30 @@
#include "levelmanager.hpp"
#include "camera.hpp"
#include "fsm.hpp"
#include "render.hpp"
#include "panel.hpp"
#include <ftxui/dom/canvas.hpp>
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<State, Event> {
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);

@ -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);
}

@ -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',

@ -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);
}

@ -0,0 +1,60 @@
#pragma once
#include <ftxui/dom/node.hpp> // for Render
#include <ftxui/component/component.hpp>
#include <ftxui/component/mouse.hpp>
#include <ftxui/dom/canvas.hpp>
#include <ftxui/screen/screen.hpp>
#include <ftxui/dom/canvas.hpp>
#include <ftxui/screen/screen.hpp>
#include <ftxui/dom/canvas.hpp>
#include <SFML/Graphics/Color.hpp>
#include <locale>
#include <codecvt>
#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<std::codecvt_utf8_utf16<wchar_t>> $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();
};

@ -0,0 +1,275 @@
#include "render.hpp"
#include "ansi_parser.hpp"
#include <cmath>
#include <fmt/core.h>
#include <array>
#include "map.hpp"
#include <iostream>
#include "color.hpp"
#if defined(_WIN64) || defined(_WIN32)
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#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);
}

@ -0,0 +1,80 @@
#pragma once
#include <ftxui/screen/screen.hpp>
#include <ftxui/dom/canvas.hpp>
#include <SFML/Window.hpp>
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Graphics/Rect.hpp>
#include "point.hpp"
#include <codecvt>
#include "ansi_parser.hpp"
#include "panel.hpp"
#include "constants.hpp"
#include <optional>
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<wchar_t, sf::Sprite> $sprites;
sf::Glyph $base_glyph;
sf::FloatRect $grid_bounds;
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $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<sf::Event> 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();
};

@ -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<Position, Tile>([&](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);
});
}
});
}

@ -1,6 +1,7 @@
#pragma once
#include "components.hpp"
#include "levelmanager.hpp"
#include <ftxui/dom/canvas.hpp>
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);
}

@ -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<std::codecvt_utf8_utf16<wchar_t>> $converter;
return $converter.from_bytes(result);
}

@ -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);
};

@ -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
Loading…
Cancel
Save