Compare commits

..

No commits in common. "master" and "version-0.1" have entirely different histories.

349 changed files with 5384 additions and 13723 deletions

2
.gitignore vendored
View file

@ -27,5 +27,3 @@ backup
*.dll
*.world
coverage
coverage/*
.venv

View file

@ -1 +1 @@
set makeprg=make\ -f\ ../Makefile\ build
set makeprg=meson\ compile\ -C\ .

View file

@ -1,72 +1,47 @@
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
all: build test
reset:
ifeq '$(OS)' 'Windows_NT'
powershell -executionpolicy bypass .\scripts\reset_build.ps1
else
sh -x ./scripts/reset_build.sh
endif
%.cpp : %.rl
ragel -I $(ROOT_DIR) -G1 -o $@ $<
ragel -o $@ $<
%.dot: %.rl
ragel -Vp -I $(ROOT_DIR) -o $@ $<
%.png: %.dot
dot -Tpng $< -o $@
build:
meson compile -j 10 -C $(ROOT_DIR)/builddir
asset_build:
./builddir/icongen
build: ansi_parser.cpp lel_parser.cpp
meson compile -j 10 -C builddir
release_build:
meson --wipe builddir -Db_ndebug=true --buildtype release
meson compile -j 10 -C builddir
debug_build:
meson setup --wipe builddir -Db_ndebug=true --buildtype debugoptimized
meson setup --wipe builddir --buildtype debug
meson compile -j 10 -C builddir
tracy_build:
meson setup --wipe builddir --buildtype debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true
meson compile -j 10 -C builddir
test:
./builddir/runtests -d yes
test: build
./builddir/runtests
run: build test
ifeq '$(OS)' 'Windows_NT'
powershell "cp ./builddir/zedcaster.exe ."
./zedcaster
else
./builddir/zedcaster
endif
debug: build
gdb --nx -x .gdbinit --ex run --args builddir/zedcaster
gdb --nx -x .gdbinit --ex run --args builddir/zedcaster.exe
debug_run: build
gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/zedcaster
gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/zedcaster.exe
debug_walk: build test
gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/zedcaster t
debug_walk: build
gdb --nx -x .gdbinit --batch --ex run --ex bt --ex q --args builddir/zedcaster.exe t
clean:
meson compile --clean -C builddir
debug_test: build
gdb --nx -x .gdbinit --ex run --ex bt --ex q --args builddir/runtests -e
gdb --nx -x .gdbinit --ex run --args builddir/runtests.exe -e
win_installer:
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
coverage_report:
powershell 'scripts/coverage_report.ps1'
money:
scc --exclude-dir subprojects
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" win_installer.ifp'

View file

@ -42,9 +42,8 @@ See? That's how Free Speech works. You don't need a LICENSE.
On all platforms you'll need these components:
* [Meson](https://mesonbuild.com/) -- which needs Python.
* C++ Compiler -- Tested with Clang and GCC 14.2.0. You can use my [Windows C++ Setup Guide](https://git.learnjsthehardway.com/learn-code-the-hard-way/lcthw-windows-installers) which features an automated installer for Windows.
* C++ Compiler -- Tested with Clang and G++. You can use my [Windows C++ Setup Guide](https://git.learnjsthehardway.com/learn-code-the-hard-way/lcthw-windows-installers) which features an automated installer for Windows.
* [GNU make](https://www.gnu.org/software/make/) -- For the convenience Makefile. On Windows you should have this if you used my setup scripts. Otherwise `winget install ezwinports.make` will set you up.
* [Ninja](https://ninja-build.org/) -- Meson uses this to do builds on most systems.
* [git](https://git-scm.com/) -- Which should be on almost every platform, and is installed by default with my Windows setup scripts.
### Windows Instructions
@ -89,7 +88,7 @@ cd raycaster
# first compile takes a while
make
./builddir/zedcaster
./builddir/raycaster
```
You don't need `make run` because Linux and OSX are sane operating systems that don't lock every
@ -128,18 +127,6 @@ I would also like statistics that show it's better, not just your word.
It's early so probably a bunch of bugs.
## Linux Build Notes
Libraries Needed:
* libxi-dev
* libfreetype-dev
It uses c++ so you may need to install a libg++ or libc++ for your system. Usually this is all you
need:
apt install build-essential
## OSX Build Notes
* Quite a bad experience. Need to install Python, cmake, meson, and ninja all which are in homebrew but if you don't use homebrew then this is a problem.

212
ai.cpp
View file

@ -1,212 +0,0 @@
#include "dbc.hpp"
#include "ai.hpp"
namespace ai {
using namespace nlohmann;
using namespace dbc;
static AIManager AIMGR;
static bool initialized = false;
inline void validate_profile(nlohmann::json& profile) {
for(auto& [name_key, value] : profile.items()) {
check(value < STATE_MAX,
fmt::format("profile field {} has value {} greater than STATE_MAX {}", (std::string)name_key, (int)value, STATE_MAX));
}
}
Action config_action(AIProfile& profile, nlohmann::json& config) {
check(config.contains("name"), "config_action: action config missing name");
check(config.contains("cost"), "config_action: action config missing cost");
Action result(config["name"], config["cost"]);
check(config.contains("needs"),
fmt::format("config_action: no 'needs' field", result.name));
check(config.contains("effects"),
fmt::format("config_action: no 'effects' field", result.name));
for(auto& [name_key, value] : config["needs"].items()) {
check(profile.contains(name_key), fmt::format("config_action({}): profile does not have need named {}", result.name, name_key));
result.needs(profile.at(name_key), bool(value));
}
for(auto& [name_key, value] : config["effects"].items()) {
check(profile.contains(name_key), fmt::format("config_action({}): profile does not have effect named {}", result.name, name_key));
result.effect(profile.at(name_key), bool(value));
}
return result;
}
State config_state(AIProfile& profile, nlohmann::json& config) {
State result;
for(auto& [name_key, value] : config.items()) {
check(profile.contains(name_key), fmt::format("config_state: profile does not have name {}", name_key));
int name_id = profile.at(name_key);
result[name_id] = bool(value);
}
return result;
}
/*
* This is only used in tests so I can load different fixtures.
*/
void reset() {
initialized = false;
AIMGR.actions.clear();
AIMGR.states.clear();
AIMGR.scripts.clear();
AIMGR.profile = json({});
}
void init(std::string config_path) {
if(!initialized) {
Config config(config_path);
// profile specifies what keys (bitset indexes) are allowed
// and how they map to the bitset of State
validate_profile(config["profile"]);
// relies on json conversion?
AIMGR.profile = config["profile"];
// load all actions
auto& actions = config["actions"];
for(auto& action_vars : actions) {
auto the_action = config_action(AIMGR.profile, action_vars);
AIMGR.actions.insert_or_assign(the_action.name, the_action);
}
// load all states
auto& states = config["states"];
for(auto& [name, state_vars] : states.items()) {
auto the_state = config_state(AIMGR.profile, state_vars);
AIMGR.states.insert_or_assign(name, the_state);
}
auto& scripts = config["scripts"];
for(auto& [script_name, action_names] : scripts.items()) {
std::vector<Action> the_script;
for(auto name : action_names) {
check(AIMGR.actions.contains(name),
fmt::format("ai::init(): script {} uses action {} that doesn't exist",
(std::string)script_name, (std::string)name));
the_script.push_back(AIMGR.actions.at(name));
}
AIMGR.scripts.insert_or_assign(script_name, the_script);
}
initialized = true;
} else {
dbc::sentinel("DOUBLE INIT: AI manager should only be intialized once if not in tests.");
}
}
void check_valid_action(std::string name, std::string msg) {
dbc::check(AIMGR.actions.contains(name),
fmt::format("{} tried to access action that doesn't exist {}",
msg, name));
}
State load_state(std::string state_name) {
check(initialized, "you forgot to initialize the AI first.");
check(AIMGR.states.contains(state_name), fmt::format(
"ai::load_state({}): state does not exist in config",
state_name));
return AIMGR.states.at(state_name);
}
Action load_action(std::string action_name) {
check(initialized, "you forgot to initialize the AI first.");
check(AIMGR.states.contains(action_name), fmt::format(
"ai::load_action({}): action does not exist in config",
action_name));
return AIMGR.actions.at(action_name);
}
std::vector<Action> load_script(std::string script_name) {
check(AIMGR.scripts.contains(script_name), fmt::format(
"ai::load_script(): no script named {} configured", script_name));
return AIMGR.scripts.at(script_name);
}
ActionPlan plan(std::string script_name, State start, State goal) {
// BUG: could probably memoize here, since:
// same script+same start+same goal will/should produce the same results
check(initialized, "you forgot to initialize the AI first.");
auto script = load_script(script_name);
return plan_actions(script, start, goal);
}
int state_id(std::string name) {
check(AIMGR.profile.contains(name), fmt::format(
"ai::state_id({}): id is not configured in profile",
name));
return AIMGR.profile.at(name);
}
void set(State& state, std::string name, bool value) {
// resort by best fit
state.set(state_id(name), value);
}
bool test(State state, std::string name) {
return state.test(state_id(name));
}
void EntityAI::fit_sort() {
if(active()) {
std::sort(plan.script.begin(), plan.script.end(),
[&](auto& l, auto& r) {
int l_cost = l.cost + ai::distance_to_goal(start, goal);
int r_cost = r.cost + ai::distance_to_goal(start, goal);
return l_cost < r_cost;
});
}
}
std::string& EntityAI::wants_to() {
return plan.script[0].name;
}
bool EntityAI::wants_to(std::string name) {
ai::check_valid_action(name, "EntityAI::wants_to");
return plan.script.size() > 0 && plan.script[0].name == name;
}
bool EntityAI::active() {
if(plan.script.size() == 1) {
return plan.script[0] != FINAL_ACTION;
} else {
return plan.script.size() != 0;
}
}
void EntityAI::set_state(std::string name, bool setting) {
fit_sort();
ai::set(start, name, setting);
}
bool EntityAI::get_state(std::string name) {
return ai::test(start, name);
}
void EntityAI::update() {
plan = ai::plan(script, start, goal);
fit_sort();
}
AIProfile* profile() {
return &AIMGR.profile;
}
}

65
ai.hpp
View file

@ -1,65 +0,0 @@
#pragma once
#include <vector>
#include "matrix.hpp"
#include <bitset>
#include <limits>
#include <optional>
#include <nlohmann/json.hpp>
#include "config.hpp"
#include "goap.hpp"
namespace ai {
struct EntityAI {
std::string script;
ai::State start;
ai::State goal;
ai::ActionPlan plan;
EntityAI(std::string script, ai::State start, ai::State goal) :
script(script), start(start), goal(goal)
{
}
EntityAI() {};
bool wants_to(std::string name);
std::string& wants_to();
void fit_sort();
bool active();
void set_state(std::string name, bool setting);
bool get_state(std::string name);
void update();
void dump();
std::string to_string();
};
struct AIManager {
AIProfile profile;
std::unordered_map<std::string, Action> actions;
std::unordered_map<std::string, State> states;
std::unordered_map<std::string, std::vector<Action>> scripts;
};
/* This is really only used in test to load different fixtures. */
void reset();
void init(std::string config_path);
Action config_action(AIProfile& profile, nlohmann::json& config);
State config_state(AIProfile& profile, nlohmann::json& config);
int state_id(std::string name);
State load_state(std::string state_name);
Action load_action(std::string action_name);
std::vector<Action> load_script(std::string script_name);
void set(State& state, std::string name, bool value=true);
bool test(State state, std::string name);
ActionPlan plan(std::string script_name, State start, State goal);
/* Mostly used for debugging and validation. */
void check_valid_action(std::string name, std::string msg);
}

View file

@ -1,74 +0,0 @@
#include "ai.hpp"
#include "ai_debug.hpp"
namespace ai {
/*
* Yeah this is weird but it's only to debug things like
* the preconditions which are weirdly done.
*/
void dump_only(State state, bool matching, bool show_as) {
AIProfile* profile = ai::profile();
for(auto& [name, name_id] : *profile) {
if(state.test(name_id) == matching) {
fmt::println("\t{}={}", name, show_as);
}
}
}
void dump_state(State state) {
AIProfile* profile = ai::profile();
for(auto& [name, name_id] : *profile) {
fmt::println("\t{}={}", name,
state.test(name_id));
}
}
void dump_action(Action& action) {
fmt::println(" --ACTION: {}, cost={}", action.name, action.cost);
fmt::println(" PRECONDS:");
dump_only(action.$positive_preconds, true, true);
dump_only(action.$negative_preconds, true, false);
fmt::println(" EFFECTS:");
dump_only(action.$positive_effects, true, true);
dump_only(action.$negative_effects, true, false);
}
State dump_script(std::string msg, State start, Script& script) {
fmt::println("--SCRIPT DUMP: {}", msg);
fmt::println("# STATE BEFORE:");
dump_state(start);
fmt::print("% ACTIONS PLANNED:");
for(auto& action : script) {
fmt::print("{} ", action.name);
}
fmt::print("\n");
for(auto& action : script) {
dump_action(action);
start = action.apply_effect(start);
fmt::println(" ## STATE AFTER:");
dump_state(start);
}
return start;
}
void EntityAI::dump() {
dump_script(script, start, plan.script);
}
std::string EntityAI::to_string() {
AIProfile* profile = ai::profile();
std::string result = wants_to();
for(auto& [name, name_id] : *profile) {
result += fmt::format("\n{}={}", name, start.test(name_id));
}
return result;
}
}

View file

@ -1,10 +0,0 @@
#pragma once
#include "goap.hpp"
namespace ai {
AIProfile* profile();
void dump_only(State state, bool matching, bool show_as);
void dump_state(State state);
void dump_action(Action& action);
State dump_script(std::string msg, State start, Script& script);
}

View file

@ -68,6 +68,7 @@ void Raycaster::position_camera(float player_x, float player_y) {
void Raycaster::draw_pixel_buffer() {
view_texture.update(pixels.to_raw_buf(), {(unsigned int)$width, (unsigned int)$height}, {0, 0});
// BUG: can I do this once and just update it?
$window.draw(view_sprite);
}

View file

@ -1,117 +0,0 @@
#include "animation.hpp"
namespace components {
void Animation::play() {
if(!playing) {
current = 0;
subframe = 0.0f;
playing = true;
}
}
float Animation::twitching() {
float tick = ease::sine(float(frames) / subframe * ease_rate);
switch(easing) {
case ease::NONE:
return 0.0;
case ease::SINE:
return tick;
case ease::OUT_CIRC:
return ease::out_circ(tick);
case ease::OUT_BOUNCE:
return ease::sine(ease::out_bounce(tick));
case ease::IN_OUT_BACK:
return ease::sine(ease::in_out_back(tick));
default:
dbc::sentinel(
fmt::format("Invalid easing {} given to animation",
int(easing)));
}
}
void Animation::step(sf::Vector2f& scale_out, sf::Vector2f& pos_out, sf::IntRect& rect_out) {
if(playing && current < frames) {
float tick = twitching();
scale_out.x = std::lerp(scale_out.x, scale_out.x + scale, tick);
scale_out.y = std::lerp(scale_out.y, scale_out.y + scale, tick);
if(stationary) {
pos_out.y = pos_out.y - (pos_out.y * scale_out.y - pos_out.y);
}
if(!simple) {
rect_out.position.x += current * frame_width;
}
subframe += speed;
current = int(subframe);
} else if(!looped) {
playing = false;
current = frames - 1;
subframe = float(frames - 1);
if(!simple) {
rect_out.position.x += current * frame_width;
}
} else {
playing = false;
current = 0;
subframe = 0.0f;
}
}
}
namespace animation {
using namespace components;
using namespace textures;
static AnimationManager MGR;
static bool initialized = false;
bool apply(Animation& anim, SpriteTexture& target) {
auto size = target.texture->getSize();
anim.frame_width = int(size.x) / (unsigned int)anim.frames;
sf::IntRect rect{{0,0}, {anim.frame_width, int(size.y)}};
sf::Vector2f scale{1.0, 1.0};
sf::Vector2f pos{0, 0};
anim.step(scale, pos, rect);
target.sprite->setTextureRect(rect);
target.sprite->setPosition(pos);
target.sprite->setScale(scale);
return anim.playing;
}
void rotate(sf::Sprite& target, float degrees) {
target.rotate(sf::degrees(degrees));
}
void center(sf::Sprite& target, sf::Vector2f pos) {
auto bounds = target.getLocalBounds();
target.setPosition({pos.x + bounds.size.x / 2,
pos.y + bounds.size.y / 2});
target.setOrigin({bounds.size.x / 2, bounds.size.y / 2});
}
void init() {
if(!initialized) {
Config config("assets/animations.json");
for(auto& [name, data] : config.json().items()) {
auto anim = components::convert<Animation>(data);
MGR.animations.insert_or_assign(name, anim);
}
initialized = true;
}
}
Animation load(std::string name) {
dbc::check(initialized, "You forgot to initialize animation.");
return MGR.animations.at(name);
}
}

View file

@ -1,17 +0,0 @@
#pragma once
#include "components.hpp"
#include "textures.hpp"
#include "easings.hpp"
namespace animation {
struct AnimationManager {
std::unordered_map<std::string, components::Animation> animations;
};
bool apply(components::Animation& anim, textures::SpriteTexture& target);
void rotate(sf::Sprite& target, float degrees);
void center(sf::Sprite& target, sf::Vector2f pos);
void init();
components::Animation load(std::string name);
}

376
ansi_parser.cpp Normal file
View file

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

23
ansi_parser.hpp Normal file
View file

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

167
ansi_parser.rl Normal file
View file

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

View file

@ -1,140 +0,0 @@
{
"profile": {
"enemy_found": 0,
"enemy_dead": 1,
"health_good": 2,
"no_more_items": 3,
"no_more_enemies": 4,
"in_combat": 5,
"have_item": 6,
"have_healing": 7,
"detect_enemy": 8,
"tough_personality": 9,
"cant_move": 10
},
"actions": [
{
"name": "find_enemy",
"cost": 5,
"needs": {
"detect_enemy": true,
"in_combat": false,
"no_more_enemies": false,
"enemy_found": false
},
"effects": {
"in_combat": true,
"enemy_found": true
}
},
{
"name": "run_away",
"cost": 0,
"needs": {
"tough_personality": false,
"in_combat": true,
"have_healing": false,
"health_good": false,
"cant_move": false
},
"effects": {
"in_combat": false
}
},
{
"name": "kill_enemy",
"cost": 10,
"needs": {
"no_more_enemies": false,
"in_combat": true,
"enemy_found": true,
"enemy_dead": false
},
"effects": {
"enemy_dead": true
}
},
{
"name": "collect_items",
"cost": 5,
"needs": {
"no_more_enemies": true,
"no_more_items": false
},
"effects": {
"no_more_items": true
}
},
{
"name": "find_healing",
"cost": 2,
"needs": {
"have_healing": false,
"in_combat": false,
"health_good": false
},
"effects": {
"health_good": true
}
},
{
"name": "use_healing",
"cost": 1,
"needs": {
"have_item": true,
"have_healing": true,
"health_good": false
},
"effects": {
"health_good": true
}
}
],
"states": {
"Host::initial_state": {
"enemy_found": false,
"enemy_dead": false,
"health_good": true,
"no_more_items": false,
"no_more_enemies": false,
"in_combat": false,
"have_item": false,
"have_healing": false,
"detect_enemy": true,
"tough_personality": true
},
"Host::final_state": {
"enemy_found": true,
"enemy_dead": true,
"health_good": true,
"no_more_items": true,
"in_combat": false,
"no_more_enemies": true
},
"Enemy::initial_state": {
"detect_enemy": false,
"tough_personality": true,
"enemy_found": false,
"enemy_dead": false,
"health_good": true,
"in_combat": false
},
"Enemy::final_state": {
"detect_enemy": true,
"enemy_found": true,
"enemy_dead": true,
"health_good": true
}
},
"scripts": {
"Host::actions":
["find_enemy",
"kill_enemy",
"collect_items",
"find_healing",
"use_healing"],
"Enemy::actions":
["find_enemy", "run_away", "kill_enemy", "use_healing"]
}
}

View file

@ -1,12 +0,0 @@
{
"ritual_blanket": {
"_type": "Animation",
"easing": 0,
"ease_rate": 0.5,
"scale": 1.0,
"simple": false,
"frames": 3,
"speed": 0.2,
"stationary": true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
assets/axe_ranger-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

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

BIN
assets/ceiling_test-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
assets/ceiling_test-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

BIN
assets/ceiling_worm-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
assets/cinqueda_1-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/cinqueda_1-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View file

@ -17,233 +17,45 @@
"Marmot_Scream_1": "assets/sounds/Creature_Sounds-Marmot_Scream_1.ogg",
"blank": "assets/sounds/blank.ogg",
"pickup": "assets/sounds/pickup.ogg",
"ambient_1": "assets/sounds/ambient_1.ogg",
"ui_click": "assets/sounds/ui_click.ogg",
"ui_hover": "assets/sounds/ui_hover.ogg",
"punch_cartoony": "assets/sounds/punch_cartoony.ogg",
"electric_shock_01": "assets/sounds/electric_shock_01.ogg",
"fireball_01": "assets/sounds/fireball_01.ogg",
"hp_status_80": "assets/sounds/hp_status_80.ogg",
"hp_status_60": "assets/sounds/hp_status_60.ogg",
"hp_status_30": "assets/sounds/hp_status_30.ogg",
"hp_status_10": "assets/sounds/hp_status_10.ogg",
"hp_status_00": "assets/sounds/hp_status_00.ogg"
"ambient_1": "assets/sounds/ambient_1.ogg"
},
"sprites": {
"gold_savior":
{"path": "assets/sprites/gold_savior.png",
"frame_width": 256,
"frame_height": 256
},
"armored_knight":
{"path": "assets/sprites/armored_knight_1.png",
"frame_width": 256,
"frame_height": 256
},
"axe_ranger":
{"path": "assets/sprites/axe_ranger.png",
"frame_width": 256,
"frame_height": 256
},
"hairy_spider":
{"path": "assets/sprites/hairy_spider.png",
"frame_width": 256,
"frame_height": 256
},
"rat_with_sword":
{"path": "assets/sprites/rat_with_sword.png",
"frame_width": 256,
"frame_height": 256
},
"rat_king_boss":
{"path": "assets/bossfights/rat_king_2_frame_animation.png",
"frame_width": 720,
"frame_height": 720
},
"barrel_small":
{"path": "assets/items/wood_barrel_small.png",
"frame_width": 256,
"frame_height": 256
},
"torch_pillar":
{"path": "assets/sprites/torch_pillar.png",
"frame_width": 256,
"frame_height": 256
},
"torch_crappy":
{"path": "assets/items/torch_crappy.png",
"frame_width": 256,
"frame_height": 256
},
"torch_horizontal_floor":
{"path": "assets/items/torch_horizontal_floor.png",
"frame_width": 256,
"frame_height": 256
},
"peasant_girl":
{"path": "assets/sprites/peasant_girl_2.png",
"frame_width": 256,
"frame_height": 256
},
"grave_stone":
{"path": "assets/sprites/grave_stone.png",
"frame_width": 256,
"frame_height": 256
},
"healing_potion_small":
{"path": "assets/items/healing_potion_small.png",
"frame_width": 256,
"frame_height": 256
},
"well_down":
{"path": "assets/sprites/well_down.png",
"frame_width": 256,
"frame_height": 256
},
"rope_vines_up":
{"path": "assets/sprites/rope_vines_up.png",
"frame_width": 256,
"frame_height": 256
},
"tripwire_trap":
{"path": "assets/sprites/tripwire_trap.png",
"frame_width": 256,
"frame_height": 256
},
"boss_fight_background":
{"path": "assets/bossfights/rat_king_boss_fight_background.jpg",
"frame_width": 1080,
"frame_height": 720
},
"devils_fingers_background":
{"path": "assets/bossfights/devils_fingers_background.jpg",
"frame_width": 1080,
"frame_height": 720
},
"devils_fingers_sprite":
{"path": "assets/bossfights/devils_fingers_sprite.png",
"frame_width": 720,
"frame_height": 720
},
"devils_fingers_stage":
{"path": "assets/bossfights/devils_fingers_stage.png",
"frame_width": 1080,
"frame_height": 720
},
"tunnel_with_rocks":
{"path": "assets/bossfights/tunnel_with_rocks.png",
"frame_width": 1080,
"frame_height": 720
},
"tunnel_with_rocks_stage":
{"path": "assets/bossfights/tunnel_with_rocks_stage.png",
"frame_width": 1080,
"frame_height": 720
},
"ritual_crafting_area":
{"path": "assets/ui/ritual_crafting_area.png",
"frame_width": 380,
"frame_height": 720
},
"full_screen_paper":
{"path": "assets/ui/full_screen_paper.png",
"frame_width": 1280,
"frame_height": 720
},
"broken_locket":
{"path": "assets/items/broken_locket.png",
"frame_width": 256,
"frame_height": 256
},
"broken_pen_knife":
{"path": "assets/items/broken_pen_knife.png",
"frame_width": 256,
"frame_height": 256
},
"broken_yoyo":
{"path": "assets/items/broken_yoyo.png",
"frame_width": 256,
"frame_height": 256
},
"chess_pawn":
{"path": "assets/items/chess_pawn.png",
"frame_width": 256,
"frame_height": 256
},
"dirty_kerchief":
{"path": "assets/items/dirty_kerchief.png",
"frame_width": 256,
"frame_height": 256
},
"leather_pouch":
{"path": "assets/items/leather_pouch.png",
"frame_width": 256,
"frame_height": 256
},
"mushroom":
{"path": "assets/items/mushroom.png",
"frame_width": 256,
"frame_height": 256
},
"pocket_watch":
{"path": "assets/items/pocket_watch.png",
"frame_width": 256,
"frame_height": 256
},
"rusty_nails":
{"path": "assets/items/rusty_nails.png",
"frame_width": 256,
"frame_height": 256
},
"severed_finger":
{"path": "assets/items/severed_finger.png",
"frame_width": 256,
"frame_height": 256
},
"stone_doll_cursed":
{"path": "assets/items/stone_doll_cursed.png",
"frame_width": 256,
"frame_height": 256
},
"dubious_combination":
{"path": "assets/items/dubious_combination.png",
"frame_width": 256,
"frame_height": 256
},
"dead_body":
{"path": "assets/sprites/dead_body.png",
"frame_width": 256,
"frame_height": 256
},
"dead_body_lootable":
{"path": "assets/sprites/dead_body_lootable.png",
"frame_width": 256,
"frame_height": 256
}
"armored_knight": "assets/armored_knight_1-256.png",
"sword": "assets/cinqueda_1-512.png",
"rat_with_sword": "assets/rat_with_sword-256.png",
"rat_king": "assets/rat_king-256.png",
"rat_king_boss": "assets/rat_king_2_frame_animation.png",
"barrel_small": "assets/wood_barrel_small-256.png",
"hanging_brazier": "assets/hanging_brazier-256.png",
"torch_pillar": "assets/torch_pillar-256.png",
"torch_crappy": "assets/torch_crappy-256.png",
"torch_horizontal_floor": "assets/torch_horizontal_floor-256.png",
"evil_eye": "assets/evil_eye-sprites.png",
"peasant_girl": "assets/undead_peasant-256.png",
"grave_stone": "assets/grave_stone-256.png",
"floor": "assets/floor_tile_test-256.png",
"ceiling": "assets/ceiling_test-256.png",
"healing_potion_small": "assets/healing_potion_small-256.png",
"well_down": "assets/well_down-256.png",
"rope_vines_up": "assets/rope_vines_up-256.png",
"tripwire_trap": "assets/tripwire_trap-256.png",
"cinqueda": "assets/cinqueda_1-256.png",
"left_gui": "assets/left_gui.png",
"blood_splatter": "assets/blood_splatter-256.png",
"trash_button": "assets/trash_button.png",
"axe_ranger": "assets/axe_ranger-256.png",
"hairy_spider": "assets/hairy_spider-256.png",
"down_the_well": "assets/down_the_well.jpg",
"boss_fight_background": "assets/rat_king_boss_fight_background.jpg",
"devils_fingers_background": "assets/devils_fingers_background.jpg",
"devils_fingers_sprite": "assets/devils_fingers_sprite.png",
"devils_fingers_stage": "assets/devils_fingers_stage.png",
"tunnel_with_rocks": "assets/tunnel_with_rocks.png",
"tunnel_with_rocks_stage": "assets/tunnel_with_rocks_stage.png"
},
"worldgen": {
"enemy_probability": 50,
"enemy_probability": 80,
"empty_room_probability": 10,
"device_probability": 10
},
"graphics": {
"smooth_textures": false
},
"compass": {
"N": 65514,
"NE": 8663,
"E": 8594,
"SE": 8600,
"S": 65516,
"SW": 8665,
"W": 8592,
"NW": 8598
},
"theme": {
"NOTE": "colors are in assets/palette.json",
"padding": 3,
"border_px": 1,
"text_size": 20,
"label_size": 20,
"font_file_name": "assets/text.otf"
}
}

View file

@ -7,13 +7,13 @@
"inventory_count": 0,
"randomized": false,
"components": [
{"_type": "Tile", "display": 6105,
"foreground": "devices/fg:stairs_down",
"background": "devices/bg:stairs_down"
{"_type": "Tile", "display": "\u2ac5",
"foreground": [24, 205, 189],
"background": [24, 205, 189]
},
{"_type": "Device",
"config": {},
"events": ["STAIRS_DOWN"]},
"config": {"test": true},
"events": ["Events::GUI::STAIRS_DOWN"]},
{"_type": "Sprite", "name": "well_down", "width": 256, "height": 256, "scale": 1.0}
]
},
@ -24,13 +24,13 @@
"inventory_count": 0,
"placement": "fixed",
"components": [
{"_type": "Tile", "display": 8793,
"foreground": "devices/fg:stairs_up",
"background": "devices/fg:stairs_up"
{"_type": "Tile", "display": "\u2259",
"foreground": [24, 205, 189],
"background": [24, 205, 189]
},
{"_type": "Device",
"config": {},
"events": ["STAIRS_UP"]},
"config": {"test": true},
"events": ["Events::GUI::STAIRS_UP"]},
{"_type": "Sprite", "name": "rope_vines_up", "width": 256, "height": 256, "scale": 1.0}
]
},
@ -40,66 +40,14 @@
"description": "Watch where you're going.",
"inventory_count": 0,
"components": [
{"_type": "Tile", "display": 95,
"foreground": "devices/fg:tripwire",
"background": "devices/bg:tripwire"
{"_type": "Tile", "display": "\u1ac7",
"foreground": [24, 205, 189],
"background": [24, 205, 189]
},
{"_type": "Device", "config": {}, "events": ["TRAP"]},
{"_type": "Device",
"config": {"test": true},
"events": ["Events::GUI::TRAP"]},
{"_type": "Sprite", "name": "tripwire_trap", "width": 256, "height": 256, "scale": 1.0}
]
},
"BARREL_SMALL": {
"id": "BARREL_SMALL",
"name": "Small Barrel",
"description": "A small rotten barrel that may hold things.",
"components": [
{"_type": "Tile", "display": 85,
"foreground": "devices/fg:barrel",
"background": "devices/bg:barrel"
},
{"_type": "Device", "config": {}, "events": ["LOOT_CONTAINER"]},
{"_type": "Sprite", "name": "barrel_small", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"GRAVE_STONE": {
"id": "GRAVE_STONE",
"name": "Grave Stone",
"description": "Something died here. Was this your doing?",
"components": [
{"_type": "Tile", "display": 8687,
"foreground": "devices/fg:grave_stone",
"background": "devices/bg:grave_stone"
},
{"_type": "Device", "config": {}, "events": ["LOOT_CONTAINER"]},
{"_type": "Sprite", "name": "grave_stone", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"DEAD_BODY_LOOTABLE": {
"id": "DEAD_BODY_LOOTABLE",
"name": "Grave Stone",
"description": "Something died here. Was this your doing?",
"components": [
{"_type": "Tile", "display": 1890,
"foreground": "devices/fg:dead_body_lootable",
"background": "devices/bg:dead_body_lootable"
},
{"_type": "Device", "config": {}, "events": ["LOOT_CONTAINER"]},
{"_type": "Sprite", "name": "dead_body_lootable", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"DEAD_BODY": {
"id": "DEAD_BODY",
"name": "Something Dead",
"description": "You can't loot this, weirdo.",
"components": [
{"_type": "Tile", "display": 1939,
"foreground": "devices/fg:dead_body",
"background": "devices/bg:dead_body"
},
{"_type": "Sprite", "name": "dead_body", "width": 256, "height": 256, "scale": 1.0}
]
}
}

View file

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 665 KiB

After

Width:  |  Height:  |  Size: 665 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Before After
Before After

View file

@ -2,43 +2,24 @@
"PLAYER_TILE": {
"placement": "fixed",
"components": [
{"_type": "Tile", "display": 10733,
"foreground": "enemies/fg:player",
"background": "color:transparent"
{"_type": "Tile", "display": "\ua66b",
"foreground": [255, 200, 125],
"background": [30, 20, 75]
},
{"_type": "Combat", "hp": 200, "max_hp": 200, "damage": 10, "dead": false},
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
{"_type": "Collision", "has": true},
{"_type": "LightSource", "strength": 35, "radius": 2.0}
]
},
"GOLD_SAVIOR": {
"components": [
{"_type": "Tile", "display": 42586,
"foreground": "enemies/fg:gold_savior",
"background": "color:transparent"
},
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 1, "dead": false},
{"_type": "Collision", "has": true},
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
{"_type": "Personality", "hearing_distance": 5, "tough": true},
{"_type": "Animation", "easing": 1, "ease_rate": 0.2, "scale": 0.1, "simple": true, "frames": 10, "speed": 0.3, "stationary": false},
{"_type": "Sprite", "name": "gold_savior", "width": 256, "height": 256, "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "Sword_Hit_2", "death": "Humanoid_Death_1"}
{"_type": "LightSource", "strength": 45, "radius": 2.0}
]
},
"KNIGHT": {
"components": [
{"_type": "Tile", "display": 2216,
"foreground": "enemies/fg:knight",
"background": "color:transparent"
{"_type": "Tile", "display": "\u088d",
"foreground": [131, 213, 238],
"background": [30, 20, 75]
},
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 1, "dead": false},
{"_type": "Collision", "has": true},
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
{"_type": "Personality", "hearing_distance": 5, "tough": true},
{"_type": "EnemyConfig", "hearing_distance": 5},
{"_type": "Animation", "easing": 1, "ease_rate": 0.2, "scale": 0.1, "simple": true, "frames": 10, "speed": 0.3, "stationary": false},
{"_type": "Sprite", "name": "armored_knight", "width": 256, "height": 256, "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "Sword_Hit_2", "death": "Humanoid_Death_1"}
@ -46,47 +27,41 @@
},
"AXE_RANGER": {
"components": [
{"_type": "Tile", "display": 1898,
"foreground": "enemies/fg:axe_ranger",
"background": "color:transparent"
{"_type": "Tile", "display": "\u076a",
"foreground": [156, 172, 197],
"background": [30, 20, 75]
},
{"_type": "Combat", "hp": 40, "max_hp": 40, "damage": 10, "dead": false},
{"_type": "Collision", "has": true},
{"_type": "Motion", "dx": 0, "dy": 0, "random": true},
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
{"_type": "Personality", "hearing_distance": 5, "tough": true},
{"_type": "EnemyConfig", "hearing_distance": 5},
{"_type": "Sprite", "name": "axe_ranger", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Animation", "easing": 3, "ease_rate": 0.5, "scale": 0.1, "simple": true, "frames": 1, "speed": 0.6, "stationary": false},
{"_type": "Animation", "easing": 3, "ease_rate": 0.5, "scale": 0.1, "simple": false, "frames": 2, "speed": 0.6, "stationary": false},
{"_type": "Sound", "attack": "Sword_Hit_2", "death": "Ranger_1"}
]
},
"RAT_GIANT": {
"components": [
{"_type": "Tile", "display": 2220,
"foreground": "enemies/fg:rat_giant",
"background": "color:transparent"
{"_type": "Tile", "display": "\u08ac",
"foreground": [205, 164, 246],
"background": [30, 20, 75]
},
{"_type": "Combat", "hp": 50, "max_hp": 50, "damage": 2, "dead": false},
{"_type": "Collision", "has": true},
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false},
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
{"_type": "Personality", "hearing_distance": 5, "tough": true},
{"_type": "Animation", "easing": 3, "ease_rate": 0.5, "scale": 0.1, "simple": true, "frames": 1, "speed": 1.0, "stationary": false},
{"_type": "EnemyConfig", "hearing_distance": 10},
{"_type": "Animation", "easing": 3, "ease_rate": 0.5, "scale": 0.1, "simple": true, "frames": 10, "speed": 1.0, "stationary": false},
{"_type": "Sprite", "name": "rat_with_sword", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "Small_Rat", "death": "Creature_Death_1"}
]
},
"SPIDER_GIANT_HAIRY": {
"components": [
{"_type": "Tile", "display": 1218,
"foreground": "enemies/fg:spider_giant",
"background": "color:transparent"
{"_type": "Tile", "display": "\u08ea",
"foreground": [205, 164, 246],
"background": [30, 20, 75]
},
{"_type": "Combat", "hp": 20, "max_hp": 20, "damage": 20, "dead": false},
{"_type": "Collision", "has": true},
{"_type": "Motion", "dx": 0, "dy": 0, "random": false},
{"_type": "EnemyConfig", "ai_script": "Enemy::actions", "ai_start_name": "Enemy::initial_state", "ai_goal_name": "Enemy::final_state"},
{"_type": "Personality", "hearing_distance": 5, "tough": true},
{"_type": "EnemyConfig", "hearing_distance": 10},
{"_type": "Animation", "easing": 2, "ease_rate": 0.5, "scale": 0.1, "simple": true, "frames": 10, "speed": 1.0, "stationary": false},
{"_type": "Sprite", "name": "hairy_spider", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "Spider_1", "death": "Spider_2"}

BIN
assets/evil_eye-sprites.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

BIN
assets/grave_stone-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/hairy_spider-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,12 +0,0 @@
{
"healing_potion_small":
{"path": "assets/icons/healing_potion_small.png",
"frame_width": 96,
"frame_height": 96
},
"torch_horizontal_floor":
{"path": "assets/icons/torch_horizontal_floor.png",
"frame_width": 96,
"frame_height": 96
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -6,27 +6,87 @@
"inventory_count": 1,
"components": [
{"_type": "LightSource", "strength": 50, "radius": 2.5},
{"_type": "Tile", "display": 3848,
"foreground": "items/fg:flame",
"background": "color:transparent"
{"_type": "Tile", "display": "\u0f08",
"foreground": [24, 120, 189],
"background": [230,120, 120]
},
{"_type": "Sprite", "name": "torch_horizontal_floor", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"SWORD_RUSTY": {
"id": "SWORD_RUSTY",
"name": "Rusty Junk Sword",
"description": "A sword left to rot in a deep hole where it acquired a patina of dirt and tetanus. You aren't sure if it's more deadly for you to hold it or for the people you stab with it.",
"inventory_count": 1,
"components": [
{"_type": "Weapon", "damage": 15},
{"_type": "Tile", "display": "\u1e37",
"foreground": [24, 120, 189],
"background": [24, 120, 189]
},
{"_type": "Sprite", "name": "cinqueda", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"BARREL_SMALL": {
"id": "BARREL_SMALL",
"name": "Small Barrel",
"description": "A small rotten barrel that may hold things.",
"components": [
{"_type": "Tile", "display": "\uaaea",
"foreground": [150, 100, 189],
"background": [150, 100, 189]
},
{"_type": "Loot", "amount": 10},
{"_type": "Sprite", "name": "barrel_small", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
],
"inventory_count": 1
},
"TORCH_PILLAR": {
"id": "TORCH_PILLAR",
"name": "Light Hanging from Ceiling",
"description": "Light Hanging from Ceiling",
"inventory_count": 0,
"components": [
{"_type": "Tile", "display": "\u077e",
"foreground": [24, 205, 210],
"background": [24, 205, 210]
},
{"_type": "LightSource", "strength": 50, "radius": 2.8},
{"_type": "Sprite", "name": "torch_pillar", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"POTION_HEALING_SMALL": {
"id": "POTION_HEALING_SMALL",
"name": "Small Healing Potion",
"description": "A small healing potion.",
"inventory_count": 1,
"components": [
{"_type": "Tile", "display": 1003,
"foreground": "items/fg:potion",
"background": "color:transparent"
{"_type": "Tile", "display": "\u03eb",
"foreground": [255, 205, 189],
"background": [255, 205, 189]
},
{"_type": "Curative", "hp": 20},
{"_type": "Sprite", "name": "healing_potion_small", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
},
"GRAVE_STONE": {
"id": "GRAVE_STONE",
"name": "Grave Stone",
"description": "Something died here. Was this your doing?",
"inventory_count": 1,
"components": [
{"_type": "Tile", "display": "\u21ef",
"foreground": [32, 123, 164],
"background": [24, 205, 189]
},
{"_type": "Loot", "amount": 10},
{"_type": "Sprite", "name": "grave_stone", "width": 256, "height": 256, "scale": 1.0},
{"_type": "Sound", "attack": "pickup", "death": "blank"}
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

BIN
assets/left_gui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View file

@ -1,140 +0,0 @@
[
{
"centered": false,
"display": 35,
"x": 0,
"y": 0
},
{
"centered": false,
"display": 8284,
"x": 64,
"y": 0
},
{
"centered": false,
"display": 11590,
"x": 128,
"y": 0
},
{
"centered": false,
"display": 10899,
"x": 192,
"y": 0
},
{
"centered": false,
"display": 9256,
"x": 256,
"y": 0
},
{
"centered": false,
"display": 9608,
"x": 320,
"y": 0
},
{
"centered": false,
"display": 10747,
"x": 384,
"y": 0
},
{
"centered": false,
"display": 8285,
"x": 448,
"y": 0
},
{
"centered": true,
"display": 1003,
"x": 512,
"y": 0
},
{
"centered": true,
"display": 3848,
"x": 576,
"y": 0
},
{
"centered": true,
"display": 85,
"x": 0,
"y": 64
},
{
"centered": true,
"display": 1939,
"x": 64,
"y": 64
},
{
"centered": true,
"display": 1890,
"x": 128,
"y": 64
},
{
"centered": true,
"display": 8687,
"x": 192,
"y": 64
},
{
"centered": true,
"display": 6105,
"x": 256,
"y": 64
},
{
"centered": true,
"display": 8793,
"x": 320,
"y": 64
},
{
"centered": true,
"display": 95,
"x": 384,
"y": 64
},
{
"centered": true,
"display": 1898,
"x": 448,
"y": 64
},
{
"centered": true,
"display": 42586,
"x": 512,
"y": 64
},
{
"centered": true,
"display": 2216,
"x": 576,
"y": 64
},
{
"centered": true,
"display": 10733,
"x": 0,
"y": 128
},
{
"centered": true,
"display": 2220,
"x": 64,
"y": 128
},
{
"centered": true,
"display": 1218,
"x": 128,
"y": 128
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -1,81 +0,0 @@
{
"color": {
"transparent": [100, 100, 100, 100],
"BAD": [255, 0, 0]
},
"gui/theme": {
"black": [0, 0, 0, 255],
"dark_dark": [10, 10, 10, 255],
"dark_mid": [30, 30, 30, 255],
"dark_light": [60, 60, 60, 255],
"mid": [100, 100, 100, 255],
"light_dark": [150, 150, 150, 255],
"light_mid": [200, 200, 200, 255],
"light_light": [230, 230, 230, 255],
"white": [255, 255, 255, 255],
"fill_color": "gui/theme:dark_mid",
"text_color": "gui/theme:light_light",
"bg_color": "gui/theme:mid",
"border_color": "gui/theme:dark_dark",
"bg_color_dark": "gui/theme:black"
},
"map/theme": {
"black": [0, 0, 0, 255],
"dark_dark": [10, 10, 10, 255],
"dark_mid": [30, 30, 30, 255],
"dark_light": [60, 60, 60, 255],
"mid": [100, 100, 100, 255],
"light_dark": [150, 150, 150, 255],
"light_mid": [200, 200, 200, 255],
"light_light": [230, 230, 230, 255],
"white": [255, 255, 255, 255]
},
"items/fg": {
"flame": "map/theme:white",
"potion": "map/theme:white"
},
"enemies/fg": {
"player": "map/theme:white",
"gold_savior": "map/theme:white",
"knight": "map/theme:white",
"axe_ranger": "map/theme:white",
"rat_giant": "map/theme:white",
"spider_giant": "map/theme:white"
},
"tiles/fg": {
"floor_tile": "map/theme:mid",
"wall_plain": "map/theme:dark_mid",
"wall_moss": "map/theme:dark_light",
"ceiling_black": "color:transparent",
"lava_floor": [200, 100, 100],
"gray_stone_floor_light": [40, 60, 180],
"wood_wall": "map/theme:dark_mid"
},
"tiles/bg": {
"floor_tile": "map/theme:dark_dark",
"wall_plain": "map/theme:dark_dark",
"wall_moss": "map/theme:light_dark",
"ceiling_black": "color:transparent",
"lava_floor": "map/theme:dark_dark",
"gray_stone_floor_light": "map/theme:dark_mid",
"wood_wall": "map/theme:dark_dark"
},
"devices/fg": {
"stairs_down": [24, 205, 189],
"stairs_up": [24, 205, 189],
"tripwire": [24, 205, 189],
"barrel": [150, 100, 189],
"grave_stone": [32, 123, 164],
"dead_body": [32, 123, 164],
"dead_body_lootable": [32, 123, 164]
},
"devices/bg": {
"stairs_down": [24, 205, 189],
"stairs_up": [24, 205, 189],
"tripwire": [24, 205, 189],
"barrel": [150, 100, 189],
"grave_stone": [24, 205, 189],
"dead_body": [24, 205, 189],
"dead_body_lootable": [24, 205, 189]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

BIN
assets/rat_king-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

Before

Width:  |  Height:  |  Size: 1,011 KiB

After

Width:  |  Height:  |  Size: 1,011 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 466 KiB

After

Width:  |  Height:  |  Size: 466 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,202 +0,0 @@
{
"profile": {
"has_spikes": 0,
"has_magick": 1,
"shiny_bauble": 2,
"cursed_item": 3,
"$does_physical": 4,
"$does_magick": 5,
"$does_damage": 6,
"$user_cursed": 7,
"$does_healing": 8,
"$damage_boost": 9,
"$large_boost": 10,
"$is_complete": 11
},
"actions": [
{
"name": "pierce_type",
"cost": 100,
"needs": {
"has_spikes": true,
"$is_complete": false
},
"effects": {
"$does_physical": true,
"$does_damage": true
}
},
{
"name": "magick_type",
"cost": 100,
"needs": {
"$is_complete": false,
"has_magick": true
},
"effects": {
"$does_magick": true,
"$does_damage": true
}
},
{
"name": "combined",
"cost": 0,
"needs": {
"$does_damage": true
},
"effects": {
"$is_complete": true
}
},
{
"name": "boost_magick",
"cost": 0,
"needs": {
"shiny_bauble": true,
"$does_magick": true,
"$does_damage": true,
"$is_complete": false,
"$user_cursed": false
},
"effects": {
"$damage_boost": true
}
},
{
"name": "boost_damage_large",
"cost": 0,
"needs": {
"$user_cursed": true,
"$is_complete": false,
"$does_damage": true
},
"effects": {
"$large_boost": true
}
},
{
"name": "curses_user",
"cost": 1000,
"needs": {
"$is_complete": false,
"cursed_item": true
},
"effects": {
"$user_cursed": true
}
},
{
"name": "heals_user",
"cost": 0,
"needs": {
"cursed_item": true,
"$does_damage": false
},
"effects": {
"$does_healing": true,
"$is_complete": true
}
}
],
"states": {
"initial": {
"shiny_bauble": false,
"cursed_item": false,
"has_spikes": false,
"has_magick": false,
"$user_cursed": false,
"$does_damage": false,
"$is_complete": false,
"$does_healing": false,
"$does_magick": false,
"$does_physical": false,
"$large_boost": false,
"$damage_boost": false
},
"final": {
"$user_cursed": true,
"$does_damage": true,
"$is_complete": true,
"$does_healing": true,
"$does_magick": true,
"$does_physical": true,
"$large_boost": true,
"$damage_boost": true
}
},
"scripts": {
"actions": [
"boost_magick",
"pierce_type",
"magick_type",
"heals_user",
"curses_user",
"boost_damage_large",
"combined"
]
},
"effects": {
"boost_magick": {
"damage": 10,
"kind": 2,
"element": 2,
"probability": 1.0
},
"pierce_type": {
"damage": 11,
"kind": 1,
"probability": 1.0
},
"magick_type": {
"damage": 12,
"kind": 2,
"element": 1,
"probability": 1.0
},
"heals_user": {
"damage": 13,
"probability": 1.0
},
"curses_user": {
"damage": 14,
"probability": 0.5
},
"boost_damage_large": {
"damage": 15,
"probability": 1.0
},
"combined": {
"damage": 16,
"probability": 1.0
}
},
"junk": {
"chess_pawn": {
"name": "chess_pawn",
"provides": ["cursed_item"]
},
"dirty_kerchief": {
"name": "dirty_kerchief",
"provides": ["has_magick"]
},
"mushroom": {
"name": "mushroom",
"provides": ["has_magick"]
},
"pocket_watch": {
"name": "pocket_watch",
"provides": ["shiny_bauble"]
},
"rusty_nails": {
"name": "rusty_nails",
"provides": ["has_spikes"]
},
"severed_finger": {
"name": "severed_finger",
"provides": ["cursed_item"]
}
},
"starting_junk": [
"pocket_watch", "mushroom", "rusty_nails"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Some files were not shown because too many files have changed in this diff Show more