diff --git a/Makefile b/Makefile index 0f0c968..32cbe3c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,10 @@ reset: patch: powershell "cp ./patches/process.h ./subprojects/libgit2-1.9.0/src/util/process.h" -build: +%.cpp : %.rl + ragel -o $@ $< + +build: lel_parser.cpp meson compile -C builddir config: @@ -19,10 +22,10 @@ test: build install: build test powershell "cp ./builddir/subprojects/libgit2-1.9.0/liblibgit2package.dll ." powershell "cp ./builddir/subprojects/efsw/libefsw.dll ." - powershell "cp builddir/escape_turings_tarpit.exe ." + powershell "cp builddir/ttpit.exe ." run: install - ./escape_turings_tarpit.exe + ./ttpit.exe clean: meson compile --clean -C builddir diff --git a/color.hpp b/color.hpp new file mode 100644 index 0000000..4026ba5 --- /dev/null +++ b/color.hpp @@ -0,0 +1,15 @@ +#pragma once +#include + +namespace ColorValue { + const sf::Color BLACK{0, 0, 0}; + const sf::Color DARK_DARK{10, 10, 10}; + const sf::Color DARK_MID{30, 30, 30}; + const sf::Color DARK_LIGHT{60, 60, 60}; + const sf::Color MID{100, 100, 100}; + const sf::Color LIGHT_DARK{150, 150, 150}; + const sf::Color LIGHT_MID{200, 200, 200}; + const sf::Color LIGHT_LIGHT{230, 230, 230}; + const sf::Color WHITE{255, 255, 255}; + const sf::Color TRANSPARENT = sf::Color::Transparent; +} diff --git a/config.cpp b/config.cpp new file mode 100644 index 0000000..ea3ef62 --- /dev/null +++ b/config.cpp @@ -0,0 +1,35 @@ +#include "config.hpp" +#include "dbc.hpp" +#include + +using nlohmann::json; +using fmt::format; + +Config::Config(const std::string src_path) : $src_path(src_path) { + std::ifstream infile($src_path); + $config = json::parse(infile); +} + +json &Config::operator[](const std::string &key) { + dbc::check($config.contains(key), fmt::format("ERROR in config, key {} doesn't exist.", key)); + return $config[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[main_key].contains(sub_key), fmt::format("ERROR wstring in config, main_key/key {}/{} doesn't exist.", main_key, sub_key)); + + const std::string& str_val = $config[main_key][sub_key]; + std::wstring_convert> $converter; + return $converter.from_bytes(str_val); +} + +std::vector Config::keys() { + std::vector the_fucking_keys; + + for(auto& [key, value] : $config.items()) { + the_fucking_keys.push_back(key); + } + + return the_fucking_keys; +} diff --git a/config.hpp b/config.hpp new file mode 100644 index 0000000..52bf664 --- /dev/null +++ b/config.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include + +struct Config { + nlohmann::json $config; + std::string $src_path; + + Config(const std::string src_path); + + Config(nlohmann::json config, std::string src_path) + : $config(config), $src_path(src_path) {} + + nlohmann::json &operator[](const std::string &key); + nlohmann::json &json() { return $config; }; + std::wstring wstring(const std::string main_key, const std::string sub_key); + std::vector keys(); +}; diff --git a/constants.hpp b/constants.hpp new file mode 100644 index 0000000..8c653dd --- /dev/null +++ b/constants.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include "color.hpp" +#include + +constexpr const int TEXTURE_WIDTH=256; +constexpr const int TEXTURE_HEIGHT=256; +constexpr const int SCREEN_WIDTH=1280; +constexpr const int SCREEN_HEIGHT=720; + +constexpr const bool VSYNC=false; +constexpr const int FRAME_LIMIT=60; + +constexpr const int GUECS_PADDING = 3; +constexpr const int GUECS_BORDER_PX = 1; +constexpr const int GUECS_FONT_SIZE = 30; +const sf::Color GUECS_FILL_COLOR = ColorValue::DARK_MID; +const sf::Color GUECS_TEXT_COLOR = ColorValue::LIGHT_LIGHT; +const sf::Color GUECS_BG_COLOR = ColorValue::MID; +const sf::Color GUECS_BORDER_COLOR = ColorValue::MID; +constexpr const char *FONT_FILE_NAME="assets/text.otf"; + +#ifdef NDEBUG +constexpr const bool DEBUG_BUILD=false; +#else +constexpr const bool DEBUG_BUILD=true; +#endif diff --git a/dbc.cpp b/dbc.cpp index c25d32a..6b17faf 100644 --- a/dbc.cpp +++ b/dbc.cpp @@ -1,40 +1,47 @@ #include "dbc.hpp" +#include -void dbc::log(const string &message) { - fmt::print("{}\n", message); +void dbc::log(const string &message, const std::source_location location) { + std::cout << '[' << location.file_name() << ':' + << location.line() << "|" + << location.function_name() << "] " + << message << std::endl; } -void dbc::sentinel(const string &message) { - string err = fmt::format("[SENTINEL!] {}\n", message); +void dbc::sentinel(const string &message, const std::source_location location) { + string err = fmt::format("[SENTINEL!] {}", message); + dbc::log(err, location); throw dbc::SentinelError{err}; } -void dbc::pre(const string &message, bool test) { +void dbc::pre(const string &message, bool test, const std::source_location location) { if(!test) { - string err = fmt::format("[PRE!] {}\n", message); + string err = fmt::format("[PRE!] {}", message); + dbc::log(err, location); throw dbc::PreCondError{err}; } } -void dbc::pre(const string &message, std::function tester) { - dbc::pre(message, tester()); +void dbc::pre(const string &message, std::function tester, const std::source_location location) { + dbc::pre(message, tester(), location); } -void dbc::post(const string &message, bool test) { +void dbc::post(const string &message, bool test, const std::source_location location) { if(!test) { - string err = fmt::format("[POST!] {}\n", message); + string err = fmt::format("[POST!] {}", message); + dbc::log(err, location); throw dbc::PostCondError{err}; } } -void dbc::post(const string &message, std::function tester) { - dbc::post(message, tester()); +void dbc::post(const string &message, std::function tester, const std::source_location location) { + dbc::post(message, tester(), location); } -void dbc::check(bool test, const string &message) { +void dbc::check(bool test, const string &message, const std::source_location location) { if(!test) { string err = fmt::format("[CHECK!] {}\n", message); - fmt::println("{}", err); + dbc::log(err, location); throw dbc::CheckError{err}; } } diff --git a/dbc.hpp b/dbc.hpp index 919d729..87e4fb0 100644 --- a/dbc.hpp +++ b/dbc.hpp @@ -3,6 +3,7 @@ #include #include #include +#include using std::string; @@ -19,11 +20,31 @@ namespace dbc { class PreCondError : public Error {}; class PostCondError : public Error {}; - void log(const string &message); - void sentinel(const string &message); - void pre(const string &message, bool test); - void pre(const string &message, std::function tester); - void post(const string &message, bool test); - void post(const string &message, std::function tester); - void check(bool test, const string &message); + void log(const string &message, + const std::source_location location = + std::source_location::current()); + + [[noreturn]] void sentinel(const string &message, + const std::source_location location = + std::source_location::current()); + + void pre(const string &message, bool test, + const std::source_location location = + std::source_location::current()); + + void pre(const string &message, std::function tester, + const std::source_location location = + std::source_location::current()); + + void post(const string &message, bool test, + const std::source_location location = + std::source_location::current()); + + void post(const string &message, std::function tester, + const std::source_location location = + std::source_location::current()); + + void check(bool test, const string &message, + const std::source_location location = + std::source_location::current()); } diff --git a/dinkyecs.hpp b/dinkyecs.hpp new file mode 100644 index 0000000..1434558 --- /dev/null +++ b/dinkyecs.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include "dbc.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DinkyECS +{ + typedef unsigned long Entity; + + using EntityMap = std::unordered_map; + + template + struct ComponentStorage { + std::vector data; + std::queue free_indices; + }; + + struct Event { + int event = 0; + Entity entity = 0; + std::any data; + }; + + typedef std::queue EventQueue; + + struct World { + unsigned long entity_count = 0; + std::unordered_map $components; + std::unordered_map $facts; + std::unordered_map $events; + std::unordered_map $component_storages; + std::vector $constants; + + Entity entity() { return ++entity_count; } + + void clone_into(DinkyECS::World &to_world) { + to_world.$constants = $constants; + to_world.$facts = $facts; + to_world.entity_count = entity_count; + to_world.$component_storages = $component_storages; + + for(auto eid : $constants) { + for(const auto &[tid, eid_map] : $components) { + auto &their_map = to_world.$components[tid]; + if(eid_map.contains(eid)) { + their_map.insert_or_assign(eid, eid_map.at(eid)); + } + } + } + } + + void make_constant(DinkyECS::Entity entity) { + $constants.push_back(entity); + } + + template + size_t make_component() { + auto &storage = component_storage_for(); + size_t index; + + if(!storage.free_indices.empty()) { + index = storage.free_indices.front(); + storage.free_indices.pop(); + } else { + storage.data.emplace_back(); + index = storage.data.size() - 1; + } + + return index; + } + + template + ComponentStorage &component_storage_for() { + auto type_index = std::type_index(typeid(Comp)); + $component_storages.try_emplace(type_index, ComponentStorage{}); + return std::any_cast &>( + $component_storages.at(type_index)); + } + + template + EntityMap &entity_map_for() { + return $components[std::type_index(typeid(Comp))]; + } + + template + EventQueue &queue_map_for() { + return $events[std::type_index(typeid(Comp))]; + } + + template + void remove(Entity ent) { + EntityMap &map = entity_map_for(); + + if(map.contains(ent)) { + size_t index = map.at(ent); + component_storage_for().free_indices.push(index); + } + + map.erase(ent); + } + + template + void set_the(Comp val) { + $facts.insert_or_assign(std::type_index(typeid(Comp)), val); + } + + template + Comp &get_the() { + auto comp_id = std::type_index(typeid(Comp)); + dbc::check($facts.contains(comp_id), + fmt::format("!!!! ATTEMPT to access world fact that hasn't " + "been set yet: {}", + typeid(Comp).name())); + + // use .at to get std::out_of_range if fact not set + std::any &res = $facts.at(comp_id); + return std::any_cast(res); + } + + template + bool has_the() { + auto comp_id = std::type_index(typeid(Comp)); + return $facts.contains(comp_id); + } + + template + void set(Entity ent, Comp val) { + EntityMap &map = entity_map_for(); + + if(has(ent)) { + get(ent) = val; + return; + } + + map.insert_or_assign(ent, make_component()); + get(ent) = val; + } + + template + Comp &get(Entity ent) { + EntityMap &map = entity_map_for(); + auto &storage = component_storage_for(); + auto index = map.at(ent); + return storage.data[index]; + } + + template + bool has(Entity ent) { + EntityMap &map = entity_map_for(); + return map.contains(ent); + } + + template + void query(std::function cb) { + EntityMap &map = entity_map_for(); + + for(auto &[entity, index] : map) { + cb(entity, get(entity)); + } + } + + template + void query(std::function cb) { + EntityMap &map_a = entity_map_for(); + EntityMap &map_b = entity_map_for(); + + for(auto &[entity, index_a] : map_a) { + if(map_b.contains(entity)) { + cb(entity, get(entity), get(entity)); + } + } + } + + template + void send(Comp event, Entity entity, std::any data) { + EventQueue &queue = queue_map_for(); + queue.push({event, entity, data}); + } + + template + Event recv() { + EventQueue &queue = queue_map_for(); + Event evt = queue.front(); + queue.pop(); + return evt; + } + + template + bool has_event() { + EventQueue &queue = queue_map_for(); + return !queue.empty(); + } + + /* std::optional can't do references. Don't try it! + * Actually, this sucks, either delete it or have it + * return pointers (assuming optional can handle pointers) + */ + template + std::optional get_if(DinkyECS::Entity entity) { + if(has(entity)) { + return std::make_optional(get(entity)); + } else { + return std::nullopt; + } + } + }; +} // namespace DinkyECS diff --git a/events.hpp b/events.hpp new file mode 100644 index 0000000..984b732 --- /dev/null +++ b/events.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace Events { + enum GUI { + START, NOOP + }; +} diff --git a/guecs.cpp b/guecs.cpp new file mode 100644 index 0000000..360fe6b --- /dev/null +++ b/guecs.cpp @@ -0,0 +1,310 @@ +#include "guecs.hpp" +#include "shaders.hpp" +#include "sound.hpp" + +namespace guecs { + + void Textual::init(lel::Cell &cell, shared_ptr font_ptr) { + dbc::check(font_ptr != nullptr, "you failed to initialize this WideText"); + if(font == nullptr) font = font_ptr; + if(text == nullptr) text = make_shared(*font, content, size); + text->setFillColor(color); + + if(centered) { + auto bounds = text->getLocalBounds(); + auto text_cell = lel::center(bounds.size.x, bounds.size.y, cell); + // this stupid / 2 is because SFML renders from baseline rather than from the claimed bounding box + text->setPosition({float(text_cell.x), float(text_cell.y) - text_cell.h / 2}); + } else { + text->setPosition({float(cell.x + padding * 2), float(cell.y + padding * 2)}); + } + + text->setCharacterSize(size); + } + + void Textual::update(std::wstring& new_content) { + content = new_content; + text->setString(content); + } + + void Sprite::init(lel::Cell &cell) { + auto sprite_texture = textures::get(name); + + sprite = make_shared( + *sprite_texture.texture, + sprite_texture.sprite->getTextureRect()); + + sprite->setPosition({ + float(cell.x + padding), + float(cell.y + padding)}); + + auto bounds = sprite->getLocalBounds(); + + sprite->setScale({ + float(cell.w - padding * 2) / bounds.size.x, + float(cell.h - padding * 2) / bounds.size.y}); + } + + void Rectangle::init(lel::Cell& cell) { + sf::Vector2f size{float(cell.w) - padding * 2, float(cell.h) - padding * 2}; + if(shape == nullptr) shape = make_shared(size); + shape->setPosition({float(cell.x + padding), float(cell.y + padding)}); + shape->setFillColor(color); + shape->setOutlineColor(border_color); + shape->setOutlineThickness(border_px); + } + + + void Meter::init(lel::Cell& cell) { + bar.init(cell); + } + + void Sound::play(bool hover) { + if(!hover) { + sound::play(on_click); + } + } + + void Background::init() { + sf::Vector2f size{float(w), float(h)}; + if(shape == nullptr) shape = make_shared(size); + shape->setPosition({float(x), float(y)}); + shape->setFillColor(color); + } + + void Effect::init(lel::Cell &cell) { + $shader_version = shaders::version(); + $shader = shaders::get(name); + $shader->setUniform("u_resolution", sf::Vector2f({float(cell.w), float(cell.h)})); + $clock = std::make_shared(); + } + + void Effect::step() { + sf::Time cur_time = $clock->getElapsedTime(); + float u_time = cur_time.asSeconds(); + + if(u_time < $u_time_end) { + $shader->setUniform("u_duration", duration); + $shader->setUniform("u_time_end", $u_time_end); + $shader->setUniform("u_time", u_time); + } else { + $active = false; + } + } + + void Effect::run() { + $active = true; + sf::Time u_time = $clock->getElapsedTime(); + $u_time_end = u_time.asSeconds() + duration; + } + + shared_ptr Effect::checkout_ptr() { + if(shaders::updated($shader_version)) { + $shader = shaders::get(name); + $shader_version = shaders::version(); + } + + return $shader; + } + + UI::UI() { + $font = make_shared(FONT_FILE_NAME); + } + + void UI::position(int x, int y, int width, int height) { + $parser.position(x, y, width, height); + } + + void UI::layout(std::string grid) { + $grid = grid; + bool good = $parser.parse($grid); + dbc::check(good, "LEL parsing failed."); + + for(auto& [name, cell] : $parser.cells) { + auto ent = init_entity(name); + $world.set(ent, cell); + } + } + + DinkyECS::Entity UI::init_entity(std::string name) { + auto entity = $world.entity(); + // this lets you look up an entity by name + $name_ents.insert_or_assign(name, entity); + // this makes it easier to get the name during querying + $world.set(entity, {name}); + return entity; + } + + DinkyECS::Entity UI::entity(std::string name) { + dbc::check($name_ents.contains(name), + fmt::format("GUECS entity {} does not exist. Forgot to init_entity?", name)); + return $name_ents.at(name); + } + + void UI::init() { + if($world.has_the()) { + auto& bg = $world.get_the(); + bg.init(); + } + + $world.query([](auto, auto& bg) { + bg.init(); + }); + + $world.query([](auto, auto& cell, auto& rect) { + rect.init(cell); + }); + + $world.query([](auto, auto& cell, auto& shader) { + shader.init(cell); + }); + + $world.query([](auto, auto& bg, auto &) { + bg.shape->setFillColor(ColorValue::BLACK); + }); + + $world.query([](auto, auto &cell, auto& meter) { + meter.init(cell); + }); + + $world.query([this](auto, auto& cell, auto& text) { + text.init(cell, $font); + }); + + $world.query([this](auto, auto& cell, auto& text) { + text.init(cell, $font); + }); + + $world.query([&](auto, auto &cell, auto &sprite) { + sprite.init(cell); + }); + } + + void UI::debug_layout(sf::RenderWindow& window) { + $world.query([&](const auto, auto &cell) { + sf::RectangleShape rect{{float(cell.w), float(cell.h)}}; + rect.setPosition({float(cell.x), float(cell.y)}); + rect.setFillColor(sf::Color::Transparent); + rect.setOutlineColor(sf::Color::Red); + rect.setOutlineThickness(2.0f); + window.draw(rect); + }); + } + + void UI::render(sf::RenderWindow& window) { + if($world.has_the()) { + auto& bg = $world.get_the(); + window.draw(*bg.shape); + } + + $world.query([&](auto, auto& shader) { + if(shader.$active) shader.step(); + }); + + $world.query([&](auto ent, auto& rect) { + render_helper(window, ent, true, rect.shape); + }); + + $world.query([&](auto ent, auto& cell, const auto &meter) { + float level = std::clamp(meter.percent, 0.0f, 1.0f) * float(cell.w); + // ZED: this 6 is a border width, make it a thing + meter.bar.shape->setSize({std::max(level, 0.0f), float(cell.h - 6)}); + render_helper(window, ent, true, meter.bar.shape); + }); + + $world.query([&](auto ent, auto& sprite) { + render_helper(window, ent, false, sprite.sprite); + }); + + $world.query