Did a full code coverage review and improved many of the tests and a bunch of code. I'll do one more final walk through all the code before getting back to work on the new combat system.

master
Zed A. Shaw 11 months ago
parent 113a4a3b3e
commit d3158291f7
  1. 2
      Makefile
  2. 376
      ansi_parser.cpp
  3. 23
      ansi_parser.hpp
  4. 167
      ansi_parser.rl
  5. 4
      assets/bosses.json
  6. 11
      autowalker.cpp
  7. 6
      components.cpp
  8. 2
      components.hpp
  9. 7
      config.cpp
  10. 1
      config.hpp
  11. 4
      easings.hpp
  12. 11
      gui_fsm.cpp
  13. 2
      gui_fsm.hpp
  14. 51
      map_view.cpp
  15. 9
      map_view.hpp
  16. 15
      meson.build
  17. 64
      panel.cpp
  18. 60
      panel.hpp
  19. 275
      render.cpp
  20. 80
      render.hpp
  21. 21
      save.cpp
  22. 24
      save.hpp
  23. 5
      scripts/coverage_report.ps1
  24. 16
      systems.cpp
  25. 3
      systems.hpp
  26. 64
      tests/components.cpp
  27. 2
      tests/config.cpp
  28. 17
      tests/easings.cpp
  29. 15
      wraps/ftxui.wrap

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

@ -1,376 +0,0 @@
#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 = nullptr;
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]));
}
}
}
(void)ansi_parser_first_final;
(void)ansi_parser_error;
(void)ansi_parser_en_main;
return good;
}

@ -1,23 +0,0 @@
#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);
};

@ -1,167 +0,0 @@
#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 = nullptr;
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]));
}
}
}
(void)ansi_parser_first_final;
(void)ansi_parser_error;
(void)ansi_parser_en_main;
return good;
}

@ -3,7 +3,7 @@
"components": [ "components": [
{"_type": "BossFight", {"_type": "BossFight",
"background": "boss_fight_background", "background": "boss_fight_background",
"stage": false, "stage": null,
"weapon_sound": "Sword_Hit_2" "weapon_sound": "Sword_Hit_2"
}, },
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false}, {"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false},
@ -24,7 +24,7 @@
"components": [ "components": [
{"_type": "BossFight", {"_type": "BossFight",
"background": "devils_fingers_background", "background": "devils_fingers_background",
"stage": false, "stage": "devils_fingers_stage",
"weapon_sound": "Sword_Hit_2" "weapon_sound": "Sword_Hit_2"
}, },
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false}, {"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false},

@ -178,6 +178,8 @@ void Autowalker::rotate_player(Point current, Point target) {
facing = fsm.$main_ui.$compass_dir; facing = fsm.$main_ui.$compass_dir;
} }
while(fsm.in_state(gui::State::ROTATING)) send_event(gui::Event::TICK);
dbc::check(fsm.$main_ui.$compass_dir == target_facing, dbc::check(fsm.$main_ui.$compass_dir == target_facing,
"player isn't facing the correct direction"); "player isn't facing the correct direction");
} }
@ -229,6 +231,14 @@ void Autowalker::handle_player_walk(ai::State& start, ai::State& goal) {
send_event(gui::Event::ATTACK); send_event(gui::Event::ATTACK);
} else if(action.name == "kill_enemy") { } else if(action.name == "kill_enemy") {
status("KILLING ENEMY"); status("KILLING ENEMY");
// TODO: find the enemy and then rotate toward them
Point current = get_current_position();
if(fsm.in_state(gui::State::IN_COMBAT)) {
rotate_player(current, {current.x - 1, current.y - 1});
dbc::log("TODO: you should find the enemy and face them instead of THIS GARBAGE!");
}
process_combat(); process_combat();
} else if(action.name == "use_healing") { } else if(action.name == "use_healing") {
status("USING HEALING"); status("USING HEALING");
@ -283,7 +293,6 @@ void Autowalker::process_move(Pathing& paths) {
} }
rotate_player(current, target); rotate_player(current, target);
while(fsm.in_state(gui::State::ROTATING)) send_event(gui::Event::TICK);
send_event(gui::Event::MOVE_FORWARD); send_event(gui::Event::MOVE_FORWARD);
while(fsm.in_state(gui::State::MOVING)) send_event(gui::Event::TICK); while(fsm.in_state(gui::State::MOVING)) send_event(gui::Event::TICK);

@ -43,11 +43,11 @@ namespace components {
case ease::SINE: case ease::SINE:
return ease::sine(float(frames) / subframe * ease_rate); return ease::sine(float(frames) / subframe * ease_rate);
case ease::OUT_CIRC: case ease::OUT_CIRC:
return ease::sine(ease::out_circ(float(frames) / subframe * ease_rate)); return ease::out_circ(ease::sine(float(frames) / subframe * ease_rate));
case ease::OUT_BOUNCE: case ease::OUT_BOUNCE:
return ease::sine(ease::out_bounce(float(frames) / subframe * ease_rate)); return ease::out_bounce(ease::sine(float(frames) / subframe * ease_rate));
case ease::IN_OUT_BACK: case ease::IN_OUT_BACK:
return ease::sine(ease::in_out_back(float(frames) / subframe * ease_rate)); return ease::in_out_back(ease::sine(float(frames) / subframe * ease_rate));
default: default:
dbc::sentinel( dbc::sentinel(
fmt::format("Invalid easing {} given to animation", fmt::format("Invalid easing {} given to animation",

@ -120,7 +120,7 @@ namespace components {
bool stationary = false; bool stationary = false;
int current = 0; int current = 0;
bool playing = false; bool playing = false;
float subframe = 0; float subframe = 1.0f;
int texture_width = TEXTURE_WIDTH; int texture_width = TEXTURE_WIDTH;
void play(); void play();

@ -15,13 +15,6 @@ json &Config::operator[](const std::string &key) {
return $config[key]; return $config[key];
} }
std::wstring Config::wstring(const std::string key) {
dbc::check($config.contains(key), fmt::format("ERROR wstring in config, key {} doesn't exist.", key));
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter;
const std::string& str_val = $config[key];
return $converter.from_bytes(str_val);
}
std::wstring Config::wstring(const std::string main_key, const std::string sub_key) { std::wstring Config::wstring(const std::string main_key, const std::string sub_key) {
dbc::check($config.contains(main_key), fmt::format("ERROR wstring main/key in config, main_key {} doesn't exist.", main_key)); dbc::check($config.contains(main_key), fmt::format("ERROR wstring main/key in config, main_key {} doesn't exist.", main_key));
dbc::check($config[main_key].contains(sub_key), fmt::format("ERROR wstring in config, main_key/key {}/{} doesn't exist.", main_key, sub_key)); dbc::check($config[main_key].contains(sub_key), fmt::format("ERROR wstring in config, main_key/key {}/{} doesn't exist.", main_key, sub_key));

@ -14,7 +14,6 @@ struct Config {
nlohmann::json &operator[](const std::string &key); nlohmann::json &operator[](const std::string &key);
nlohmann::json &json() { return $config; }; nlohmann::json &json() { return $config; };
std::wstring wstring(const std::string main_key);
std::wstring wstring(const std::string main_key, const std::string sub_key); std::wstring wstring(const std::string main_key, const std::string sub_key);
std::vector<std::string> keys(); std::vector<std::string> keys();
}; };

@ -4,7 +4,7 @@
namespace ease { namespace ease {
enum Style { enum Style {
NONE, SINE, OUT_CIRC, OUT_BOUNCE, IN_OUT_BACK NONE, SINE, OUT_CIRC, OUT_BOUNCE, IN_OUT_BACK, FUCKFACE
}; };
inline double sine(double x) { inline double sine(double x) {
@ -12,7 +12,7 @@ namespace ease {
} }
inline double out_circ(double x) { inline double out_circ(double x) {
return std::sqrt(1.0f - std::pow(x - 1.0f, 2.0f)); return std::sqrt(1.0f - ((x - 1.0f) * (x - 1.0f)));
} }
inline double out_bounce(double x) { inline double out_bounce(double x) {

@ -15,7 +15,6 @@ namespace gui {
FSM::FSM() : FSM::FSM() :
$window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Raycaster Thing"), $window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Raycaster Thing"),
$main_ui($window), $main_ui($window),
$renderer($window),
$level($levels.current()), $level($levels.current()),
$map_ui($level), $map_ui($level),
$combat_ui($level), $combat_ui($level),
@ -53,11 +52,6 @@ namespace gui {
$boss_fight_ui = $levels.create_bossfight($level.world); $boss_fight_ui = $levels.create_bossfight($level.world);
$boss_fight_ui->init(); $boss_fight_ui->init();
$renderer.init_terminal();
$map_ui.create_render();
$map_ui.resize_canvas();
$renderer.resize_grid(BASE_MAP_FONT_SIZE, $map_ui);
run_systems(); run_systems();
state(State::IDLE); state(State::IDLE);
} }
@ -106,6 +100,7 @@ namespace gui {
break; break;
default: default:
dbc::log(fmt::format("In ATTACKING state, unhandled event {}", (int)ev)); dbc::log(fmt::format("In ATTACKING state, unhandled event {}", (int)ev));
state(State::IDLE);
} }
} }
@ -152,8 +147,6 @@ namespace gui {
state(State::ROTATING); state(State::ROTATING);
break; break;
case MAP_OPEN: case MAP_OPEN:
$renderer.resize_grid(BASE_MAP_FONT_SIZE, $map_ui);
$map_ui.resize_canvas();
state(State::MAPPING); state(State::MAPPING);
break; break;
case ATTACK: case ATTACK:
@ -327,8 +320,6 @@ namespace gui {
void FSM::render() { void FSM::render() {
if(in_state(State::MAPPING)) { if(in_state(State::MAPPING)) {
$window.clear(); $window.clear();
$map_ui.render();
$renderer.draw($map_ui);
} else if(in_state(State::NEXT_LEVEL)) { } else if(in_state(State::NEXT_LEVEL)) {
$window.clear(); $window.clear();
$boss_fight_ui->render($window); $boss_fight_ui->render($window);

@ -3,7 +3,6 @@
#include "stats.hpp" #include "stats.hpp"
#include "levelmanager.hpp" #include "levelmanager.hpp"
#include "fsm.hpp" #include "fsm.hpp"
#include "render.hpp"
#include "map_view.hpp" #include "map_view.hpp"
#include "main_ui.hpp" #include "main_ui.hpp"
#include "combat_ui.hpp" #include "combat_ui.hpp"
@ -49,7 +48,6 @@ namespace gui {
bool autowalking = false; bool autowalking = false;
LevelManager $levels; LevelManager $levels;
MainUI $main_ui; MainUI $main_ui;
SFMLRender $renderer;
GameLevel $level; GameLevel $level;
shared_ptr<BossFightUI> $boss_fight_ui = nullptr; shared_ptr<BossFightUI> $boss_fight_ui = nullptr;
MapViewUI $map_ui; MapViewUI $map_ui;

@ -1,14 +1,11 @@
#include "map_view.hpp" #include "map_view.hpp"
#include <functional> #include <functional>
#include <string> #include <string>
#include "systems.hpp"
namespace gui { namespace gui {
using namespace components; using namespace components;
using ftxui::Color;
MapViewUI::MapViewUI(GameLevel &level) : MapViewUI::MapViewUI(GameLevel &level) :
Panel(0, 0, 0, 0, true),
$level(level) $level(level)
{ {
} }
@ -18,54 +15,6 @@ namespace gui {
} }
void MapViewUI::draw_map() { 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 Tile& 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](auto &pixel) {
pixel.foreground_color = Color::HSV(dnum * 20, 150, 200);
pixel.background_color = Color::HSV(30, 20, tile.foreground[2] * 50 * PERCENT);
});
} else {
$canvas.DrawText(x * 2, y * 4, tile.display, [tile, light_value](auto &pixel) {
pixel.foreground_color = Color::HSV(tile.foreground[0], tile.foreground[1], tile.foreground[2] * light_value);
pixel.background_color = Color::HSV(tile.background[0], tile.background[1], tile.background[2] * 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);
} }
} }

@ -1,19 +1,12 @@
#pragma once #pragma once
#include "panel.hpp"
#include <ftxui/dom/canvas.hpp>
#include "levelmanager.hpp" #include "levelmanager.hpp"
using ftxui::Canvas;
namespace gui { namespace gui {
class MapViewUI : public Panel { class MapViewUI {
public: public:
Canvas $canvas;
GameLevel $level; GameLevel $level;
MapViewUI(GameLevel &level); MapViewUI(GameLevel &level);
void create_render();
void resize_canvas();
void draw_map(); void draw_map();
void update_level(GameLevel &level); void update_level(GameLevel &level);
}; };

@ -68,22 +68,17 @@ sfml_system = dependency('sfml_system')
sfml_window = dependency('sfml_window', sfml_window = dependency('sfml_window',
default_options: ['default_library=shared']) default_options: ['default_library=shared'])
ftxui_screen = dependency('ftxui-screen')
ftxui_dom = dependency('ftxui-dom')
ftxui_component = dependency('ftxui-component')
dependencies += [ dependencies += [
fmt, json, freetype2, fmt, json, freetype2,
flac, ogg, vorbis, vorbisfile, vorbisenc, flac, ogg, vorbis, vorbisfile, vorbisenc,
sfml_audio, sfml_graphics, sfml_audio, sfml_graphics,
sfml_network, sfml_system, sfml_network, sfml_system,
sfml_window, ftxui_screen, ftxui_dom, ftxui_component sfml_window
] ]
sources = [ sources = [
'ai.cpp', 'ai.cpp',
'ai_debug.cpp', 'ai_debug.cpp',
'ansi_parser.cpp',
'autowalker.cpp', 'autowalker.cpp',
'boss_fight_ui.cpp', 'boss_fight_ui.cpp',
'camera.cpp', 'camera.cpp',
@ -106,11 +101,9 @@ sources = [
'matrix.cpp', 'matrix.cpp',
'matrix.cpp', 'matrix.cpp',
'overlay_ui.cpp', 'overlay_ui.cpp',
'panel.cpp',
'pathing.cpp', 'pathing.cpp',
'rand.cpp', 'rand.cpp',
'raycaster.cpp', 'raycaster.cpp',
'render.cpp',
'rituals.cpp', 'rituals.cpp',
'save.cpp', 'save.cpp',
'shiterator.hpp', 'shiterator.hpp',
@ -125,15 +118,14 @@ sources = [
] ]
executable('runtests', sources + [ executable('runtests', sources + [
'tests/ansi_parser.cpp', 'tests/ai.cpp',
'tests/base.cpp', 'tests/base.cpp',
'tests/rituals.cpp',
'tests/components.cpp', 'tests/components.cpp',
'tests/config.cpp', 'tests/config.cpp',
'tests/dbc.cpp', 'tests/dbc.cpp',
'tests/dinkyecs.cpp', 'tests/dinkyecs.cpp',
'tests/easings.cpp',
'tests/fsm.cpp', 'tests/fsm.cpp',
'tests/ai.cpp',
'tests/guecs.cpp', 'tests/guecs.cpp',
'tests/inventory.cpp', 'tests/inventory.cpp',
'tests/lel.cpp', 'tests/lel.cpp',
@ -142,6 +134,7 @@ executable('runtests', sources + [
'tests/map.cpp', 'tests/map.cpp',
'tests/matrix.cpp', 'tests/matrix.cpp',
'tests/pathing.cpp', 'tests/pathing.cpp',
'tests/rituals.cpp',
'tests/sound.cpp', 'tests/sound.cpp',
'tests/spatialmap.cpp', 'tests/spatialmap.cpp',
'tests/textures.cpp', 'tests/textures.cpp',

@ -1,64 +0,0 @@
#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);
}

@ -1,60 +0,0 @@
#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();
};

@ -1,275 +0,0 @@
#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);
}

@ -1,80 +0,0 @@
#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 = SCREEN_WIDTH;
unsigned int video_y = SCREEN_HEIGHT;
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();
};

@ -8,27 +8,6 @@
using namespace components; using namespace components;
using namespace fmt; using namespace fmt;
template<typename CompT>
inline void extract(DinkyECS::World &world, std::map<DinkyECS::Entity, CompT> &into) {
auto from_world = world.entity_map_for<CompT>();
for(auto [entity, value] : from_world) {
into[entity] = std::any_cast<CompT>(value);
}
}
void save::to_file(fs::path path, DinkyECS::World &world, Map &map) {
(void)path;
(void)world;
(void)map;
}
void save::from_file(fs::path path, DinkyECS::World &world_out, Map &map_out) {
(void)path;
(void)world_out;
(void)map_out;
}
void save::load_configs(DinkyECS::World &world) { void save::load_configs(DinkyECS::World &world) {
Config game("./assets/config.json"); Config game("./assets/config.json");
Config enemies("./assets/enemies.json"); Config enemies("./assets/enemies.json");

@ -10,29 +10,5 @@
namespace save { namespace save {
namespace fs = std::filesystem; namespace fs = std::filesystem;
struct MapData {
size_t width;
size_t height;
std::vector<Room> rooms;
Matrix walls;
};
struct Facts {
components::Player player;
};
struct SaveData {
Facts facts;
MapData map;
std::map<DinkyECS::Entity, components::Position> position;
std::map<DinkyECS::Entity, components::Motion> motion;
std::map<DinkyECS::Entity, components::Combat> combat;
std::map<DinkyECS::Entity, components::Tile> tile;
// std::map<DinkyECS::Entity, components::Inventory> inventory;
};
void to_file(fs::path path, DinkyECS::World &world, Map &map);
void from_file(fs::path path, DinkyECS::World &world_out, Map &map);
void load_configs(DinkyECS::World &world); void load_configs(DinkyECS::World &world);
} }

@ -4,7 +4,10 @@ cp *.cpp,*.hpp,*.rl builddir
. .venv/Scripts/activate . .venv/Scripts/activate
rm -recurse -force coverage rm -recurse -force coverage
cp scripts\gcovr_patched_coverage.py .venv\Lib\site-packages\gcovr\coverage.py
gcovr -o coverage/ --html --html-details --gcov-ignore-errors all -e builddir/subprojects -e subprojects . gcovr -o coverage/ --html --html-details --html-theme github.dark-blue --gcov-ignore-errors all --gcov-ignore-parse-errors negative_hits.warn_once_per_file -e builddir/subprojects -e builddir -e subprojects -j 10 .
rm *.gcov.json.gz
start .\coverage\coverage_details.html start .\coverage\coverage_details.html

@ -16,7 +16,6 @@ using std::string;
using namespace fmt; using namespace fmt;
using namespace components; using namespace components;
using lighting::LightSource; using lighting::LightSource;
using ftxui::Color;
void System::lighting(GameLevel &level) { void System::lighting(GameLevel &level) {
auto &light = *level.lights; auto &light = *level.lights;
@ -331,11 +330,9 @@ void System::plan_motion(DinkyECS::World& world, Point move_to) {
/* /*
* This one is called inside the MapViewUI very often so * This one is called inside the MapViewUI very often so
* just avoide GameMap unlike the others. * just avoid GameMap unlike the others.
*
* BUG: Just get rid of this and use the new UI to do the map
*/ */
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) { void System::draw_entities(DinkyECS::World &world, Map &map, const Matrix &lights, const Point &cam_orig, size_t view_x, size_t view_y) {
auto &tiles = map.tiles(); auto &tiles = map.tiles();
world.query<Position, Tile>([&](auto, auto &pos, auto &tile) { world.query<Position, Tile>([&](auto, auto &pos, auto &tile) {
@ -346,11 +343,10 @@ void System::draw_entities(DinkyECS::World &world, Map &map, const Matrix &light
float light_value = lights[pos.location.y][pos.location.x] * PERCENT; float light_value = lights[pos.location.y][pos.location.x] * PERCENT;
const Tile& cell = tiles.at(pos.location.x, pos.location.y); const Tile& 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 (void)loc; // not used yet, this after ripping out map so needs rewrite
canvas.DrawText(loc.x*2, loc.y*4, tile.display, [tile, light_value, cell](auto &pixel) { (void)light_value;
pixel.foreground_color = Color::HSV(tile.foreground[0], tile.foreground[1], tile.foreground[2] * light_value); (void)cell;
pixel.background_color = Color::HSV(cell.background[0], cell.background[1], cell.background[2] * light_value); (void)tile;
});
} }
}); });
} }

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "components.hpp" #include "components.hpp"
#include "levelmanager.hpp" #include "levelmanager.hpp"
#include <ftxui/dom/canvas.hpp>
namespace System { namespace System {
@ -18,7 +17,7 @@ namespace System {
void init_positions(DinkyECS::World &world, SpatialMap &collider); void init_positions(DinkyECS::World &world, SpatialMap &collider);
void device(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 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); void draw_entities(DinkyECS::World &world, Map &map, const Matrix &lights, const Point &cam_orig, size_t view_x, size_t view_y);
void enemy_ai(GameLevel &level); void enemy_ai(GameLevel &level);
void combat(GameLevel &level); void combat(GameLevel &level);

@ -3,6 +3,7 @@
#include "dinkyecs.hpp" #include "dinkyecs.hpp"
#include "config.hpp" #include "config.hpp"
#include <iostream> #include <iostream>
#include "easings.hpp"
using namespace components; using namespace components;
using namespace DinkyECS; using namespace DinkyECS;
@ -27,3 +28,66 @@ TEST_CASE("confirm component loading works", "[components]") {
} }
} }
} }
TEST_CASE("make sure json_mods works", "[components]") {
Config config("assets/bosses.json");
// this confirms that loading something with an optional
// field works with the json conversions in json_mods.hpp
for(auto& comp_data : config["RAT_KING"]["components"]) {
if(comp_data["_type"] == "BossFight") {
auto comp = components::convert<components::BossFight>(comp_data);
// the boss fight for the rat king doesn't have a stage so false=optional
REQUIRE(comp.stage == std::nullopt);
}
}
// this then confirms everything else about the json conversion
ComponentMap comp_map;
components::configure(comp_map);
DinkyECS::World world;
auto rat_king = world.entity();
components::configure_entity(comp_map, world, rat_king, config["RAT_KING"]["components"]);
auto boss = world.get<BossFight>(rat_king);
REQUIRE(boss.stage == std::nullopt);
// now load the other one for the other way optional is used
auto devils_fingers = world.entity();
components::configure_entity(comp_map, world, devils_fingers, config["DEVILS_FINGERS"]["components"]);
auto boss2 = world.get<BossFight>(devils_fingers);
REQUIRE(boss2.stage != std::nullopt);
}
TEST_CASE("animation component special cases", "[components]") {
Animation anim;
anim.easing = ease::NONE;
float res = anim.twitching();
REQUIRE(res == 0.0);
anim.easing = ease::SINE;
anim.subframe = 1.0f;
res = anim.twitching();
REQUIRE(!std::isnan(res));
anim.easing = ease::OUT_CIRC;
res = anim.twitching();
REQUIRE(!std::isnan(res));
anim.easing = ease::OUT_BOUNCE;
res = anim.twitching();
REQUIRE(!std::isnan(res));
anim.easing = ease::IN_OUT_BACK;
res = anim.twitching();
REQUIRE(!std::isnan(res));
anim.easing = ease::FUCKFACE;
bool throws = false;
try { anim.twitching(); } catch(...) { throws = true; }
REQUIRE(throws);
}

@ -5,7 +5,9 @@
TEST_CASE("confirm basic config loader ops", "[config]") { TEST_CASE("confirm basic config loader ops", "[config]") {
Config config("assets/devices.json"); Config config("assets/devices.json");
auto data_list = config.json(); auto data_list = config.json();
auto the_keys = config.keys();
REQUIRE(the_keys.size() > 0);
for(auto& [key, data] : data_list.items()) { for(auto& [key, data] : data_list.items()) {
auto wide1 = config.wstring(key, "name"); auto wide1 = config.wstring(key, "name");

@ -0,0 +1,17 @@
#include <catch2/catch_test_macros.hpp>
#include "easings.hpp"
#include <cmath>
TEST_CASE("make sure the easing functions at least run", "[easings]") {
double out = ease::sine(1.3);
REQUIRE(out <= 1.0);
out = ease::out_circ(3.444);
REQUIRE(std::isnan(out));
out = ease::out_bounce(1.13);
REQUIRE(out <= 10 );
out = ease::in_out_back(3.4);
REQUIRE(out < 250.0);
}

@ -1,15 +0,0 @@
[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