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 <SFML/Graphics/Color.hpp> + +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 <fmt/core.h> + +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<std::codecvt_utf8_utf16<wchar_t>> $converter; + return $converter.from_bytes(str_val); +} + +std::vector<std::string> Config::keys() { + std::vector<std::string> 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 <nlohmann/json.hpp> +#include <fstream> +#include <codecvt> + +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<std::string> 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 <string> +#include "color.hpp" +#include <array> + +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 <iostream> -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<bool()> tester) { - dbc::pre(message, tester()); +void dbc::pre(const string &message, std::function<bool()> 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<bool()> tester) { - dbc::post(message, tester()); +void dbc::post(const string &message, std::function<bool()> 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 <string> #include <fmt/core.h> #include <functional> +#include <source_location> 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<bool()> tester); - void post(const string &message, bool test); - void post(const string &message, std::function<bool()> 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<bool()> 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<bool()> 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 <any> +#include <functional> +#include <queue> +#include <tuple> +#include <typeindex> +#include <typeinfo> +#include <unordered_map> +#include <optional> + +namespace DinkyECS +{ + typedef unsigned long Entity; + + using EntityMap = std::unordered_map<Entity, size_t>; + + template <typename T> + struct ComponentStorage { + std::vector<T> data; + std::queue<size_t> free_indices; + }; + + struct Event { + int event = 0; + Entity entity = 0; + std::any data; + }; + + typedef std::queue<Event> EventQueue; + + struct World { + unsigned long entity_count = 0; + std::unordered_map<std::type_index, EntityMap> $components; + std::unordered_map<std::type_index, std::any> $facts; + std::unordered_map<std::type_index, EventQueue> $events; + std::unordered_map<std::type_index, std::any> $component_storages; + std::vector<Entity> $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 <typename Comp> + size_t make_component() { + auto &storage = component_storage_for<Comp>(); + 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 <typename Comp> + ComponentStorage<Comp> &component_storage_for() { + auto type_index = std::type_index(typeid(Comp)); + $component_storages.try_emplace(type_index, ComponentStorage<Comp>{}); + return std::any_cast<ComponentStorage<Comp> &>( + $component_storages.at(type_index)); + } + + template <typename Comp> + EntityMap &entity_map_for() { + return $components[std::type_index(typeid(Comp))]; + } + + template <typename Comp> + EventQueue &queue_map_for() { + return $events[std::type_index(typeid(Comp))]; + } + + template <typename Comp> + void remove(Entity ent) { + EntityMap &map = entity_map_for<Comp>(); + + if(map.contains(ent)) { + size_t index = map.at(ent); + component_storage_for<Comp>().free_indices.push(index); + } + + map.erase(ent); + } + + template <typename Comp> + void set_the(Comp val) { + $facts.insert_or_assign(std::type_index(typeid(Comp)), val); + } + + template <typename Comp> + 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<Comp &>(res); + } + + template <typename Comp> + bool has_the() { + auto comp_id = std::type_index(typeid(Comp)); + return $facts.contains(comp_id); + } + + template <typename Comp> + void set(Entity ent, Comp val) { + EntityMap &map = entity_map_for<Comp>(); + + if(has<Comp>(ent)) { + get<Comp>(ent) = val; + return; + } + + map.insert_or_assign(ent, make_component<Comp>()); + get<Comp>(ent) = val; + } + + template <typename Comp> + Comp &get(Entity ent) { + EntityMap &map = entity_map_for<Comp>(); + auto &storage = component_storage_for<Comp>(); + auto index = map.at(ent); + return storage.data[index]; + } + + template <typename Comp> + bool has(Entity ent) { + EntityMap &map = entity_map_for<Comp>(); + return map.contains(ent); + } + + template <typename Comp> + void query(std::function<void(Entity, Comp &)> cb) { + EntityMap &map = entity_map_for<Comp>(); + + for(auto &[entity, index] : map) { + cb(entity, get<Comp>(entity)); + } + } + + template <typename CompA, typename CompB> + void query(std::function<void(Entity, CompA &, CompB &)> cb) { + EntityMap &map_a = entity_map_for<CompA>(); + EntityMap &map_b = entity_map_for<CompB>(); + + for(auto &[entity, index_a] : map_a) { + if(map_b.contains(entity)) { + cb(entity, get<CompA>(entity), get<CompB>(entity)); + } + } + } + + template <typename Comp> + void send(Comp event, Entity entity, std::any data) { + EventQueue &queue = queue_map_for<Comp>(); + queue.push({event, entity, data}); + } + + template <typename Comp> + Event recv() { + EventQueue &queue = queue_map_for<Comp>(); + Event evt = queue.front(); + queue.pop(); + return evt; + } + + template <typename Comp> + bool has_event() { + EventQueue &queue = queue_map_for<Comp>(); + 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 <typename Comp> + std::optional<Comp> get_if(DinkyECS::Entity entity) { + if(has<Comp>(entity)) { + return std::make_optional<Comp>(get<Comp>(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<sf::Font> 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<sf::Text>(*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<sf::Sprite>( + *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<sf::RectangleShape>(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<sf::RectangleShape>(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<sf::Clock>(); + } + + 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<sf::Shader> Effect::checkout_ptr() { + if(shaders::updated($shader_version)) { + $shader = shaders::get(name); + $shader_version = shaders::version(); + } + + return $shader; + } + + UI::UI() { + $font = make_shared<sf::Font>(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<lel::Cell>(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<CellName>(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<Background>()) { + auto& bg = $world.get_the<Background>(); + bg.init(); + } + + $world.query<Background>([](auto, auto& bg) { + bg.init(); + }); + + $world.query<lel::Cell, Rectangle>([](auto, auto& cell, auto& rect) { + rect.init(cell); + }); + + $world.query<lel::Cell, Effect>([](auto, auto& cell, auto& shader) { + shader.init(cell); + }); + + $world.query<Rectangle, Meter>([](auto, auto& bg, auto &) { + bg.shape->setFillColor(ColorValue::BLACK); + }); + + $world.query<lel::Cell, Meter>([](auto, auto &cell, auto& meter) { + meter.init(cell); + }); + + $world.query<lel::Cell, Textual>([this](auto, auto& cell, auto& text) { + text.init(cell, $font); + }); + + $world.query<lel::Cell, Label>([this](auto, auto& cell, auto& text) { + text.init(cell, $font); + }); + + $world.query<lel::Cell, Sprite>([&](auto, auto &cell, auto &sprite) { + sprite.init(cell); + }); + } + + void UI::debug_layout(sf::RenderWindow& window) { + $world.query<lel::Cell>([&](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<Background>()) { + auto& bg = $world.get_the<Background>(); + window.draw(*bg.shape); + } + + $world.query<Effect>([&](auto, auto& shader) { + if(shader.$active) shader.step(); + }); + + $world.query<Rectangle>([&](auto ent, auto& rect) { + render_helper(window, ent, true, rect.shape); + }); + + $world.query<lel::Cell, Meter>([&](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<Sprite>([&](auto ent, auto& sprite) { + render_helper(window, ent, false, sprite.sprite); + }); + + $world.query<Label>([&](auto ent, auto& text) { + render_helper(window, ent, false, text.text); + }); + + $world.query<Textual>([&](auto ent, auto& text) { + render_helper(window, ent, true, text.text); + }); + } + + bool UI::mouse(float x, float y, bool hover) { + int action_count = 0; + + $world.query<lel::Cell, Clickable>([&](auto ent, auto& cell, auto &clicked) { + if((x >= cell.x && x <= cell.x + cell.w) && + (y >= cell.y && y <= cell.y + cell.h)) + { + do_if<Effect>(ent, [hover](auto& effect) { + effect.$shader->setUniform("hover", hover); + effect.run(); + }); + + do_if<Sound>(ent, [hover](auto& sound) { + // here set that it played then only play once + sound.play(hover); + }); + + if(hover) return; // kinda gross + + if(auto action_data = get_if<ActionData>(ent)) { + clicked.action(ent, action_data->data); + } else { + clicked.action(ent, {}); + } + + action_count++; + } else { + // via ORBLISHJ + // just reset the hover trigger for all that aren't hit + // then in the ^^ positive branch play it and set it played + } + }); + + return action_count > 0; + } + + void UI::show_sprite(string region, string sprite_name) { + auto ent = entity(region); + + if(!has<Sprite>(ent)) { + Sprite to_show{sprite_name}; + auto& cell = cell_for(ent); + to_show.init(cell); + set<guecs::Sprite>(ent, to_show); + } + } + + void UI::show_text(string region, wstring content) { + auto ent = entity(region); + + if(auto text = get_if<Textual>(ent)) { + text->text->setString(content); + } else { + auto &cell = cell_for(ent); + Textual to_set{content, 20}; + to_set.init(cell, $font); + to_set.text->setFillColor(ColorValue::LIGHT_MID); + set<Textual>(ent, to_set); + } + } + + void UI::show_label(string region, wstring content) { + auto ent = entity(region); + + if(auto text = get_if<Label>(ent)) { + text->text->setString(content); + } else { + auto &cell = cell_for(ent); + Label to_set{content, 20}; + to_set.init(cell, $font); + to_set.text->setFillColor(ColorValue::LIGHT_MID); + set<Label>(ent, to_set); + } + } + + Clickable make_action(DinkyECS::World& target, Events::GUI event) { + return {[&, event](auto ent, auto data){ + // remember that ent is passed in from the UI::mouse handler + target.send<Events::GUI>(event, ent, data); + }}; + } + +} diff --git a/guecs.hpp b/guecs.hpp new file mode 100644 index 0000000..be2b431 --- /dev/null +++ b/guecs.hpp @@ -0,0 +1,235 @@ +#pragma once +#include "color.hpp" +#include "dinkyecs.hpp" +#include "lel.hpp" +#include <string> +#include <memory> +#include <SFML/Graphics.hpp> +#include "textures.hpp" +#include <functional> +#include "events.hpp" +#include "constants.hpp" +#include <any> +#include "shaders.hpp" + +namespace guecs { + using std::shared_ptr, std::make_shared, std::wstring, std::string; + + struct Textual { + std::wstring content; + unsigned int size = GUECS_FONT_SIZE; + sf::Color color = GUECS_TEXT_COLOR; + int padding = GUECS_PADDING; + bool centered = false; + shared_ptr<sf::Font> font = nullptr; + shared_ptr<sf::Text> text = nullptr; + + void init(lel::Cell &cell, shared_ptr<sf::Font> font_ptr); + void update(std::wstring& new_content); + }; + + struct Label : public Textual { + template<typename... Args> + Label(Args... args) : Textual(args...) + { + centered = true; + } + + Label() { + centered = true; + }; + }; + + struct Clickable { + /* This is actually called by UI::mouse and passed the entity ID of the + * button pressed so you can interact with it in the event handler. + */ + std::function<void(DinkyECS::Entity ent, std::any data)> action; + }; + + struct Sprite { + std::string name; + int padding = GUECS_PADDING; + std::shared_ptr<sf::Sprite> sprite = nullptr; + std::shared_ptr<sf::Texture> texture = nullptr; + + void init(lel::Cell &cell); + }; + + struct Rectangle { + int padding = GUECS_PADDING; + sf::Color color = GUECS_FILL_COLOR; + sf::Color border_color = GUECS_BORDER_COLOR; + int border_px = GUECS_BORDER_PX; + shared_ptr<sf::RectangleShape> shape = nullptr; + + void init(lel::Cell& cell); + }; + + struct Meter { + float percent = 1.0f; + Rectangle bar; + + void init(lel::Cell& cell); + }; + + struct ActionData { + std::any data; + }; + + struct CellName { + std::string name; + }; + + struct Effect { + float duration = 0.1f; + std::string name{"ui_shader"}; + float $u_time_end = 0.0; + bool $active = false; + std::shared_ptr<sf::Clock> $clock = nullptr; + std::shared_ptr<sf::Shader> $shader = nullptr; + int $shader_version = 0; + + void init(lel::Cell &cell); + void run(); + void step(); + shared_ptr<sf::Shader> checkout_ptr(); + }; + + struct Sound { + std::string on_click{"ui_click"}; + void play(bool hover); + }; + + struct Background { + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + sf::Color color = GUECS_BG_COLOR; + shared_ptr<sf::RectangleShape> shape = nullptr; + + Background(lel::Parser& parser, sf::Color bg_color=GUECS_BG_COLOR) : + x(parser.grid_x), + y(parser.grid_y), + w(parser.grid_w), + h(parser.grid_h), + color(bg_color) + {} + + Background() {} + + void init(); + }; + + class UI { + public: + DinkyECS::World $world; + std::unordered_map<std::string, DinkyECS::Entity> $name_ents; + shared_ptr<sf::Font> $font = nullptr; + lel::Parser $parser; + std::string $grid = ""; + + UI(); + + void position(int x, int y, int width, int height); + void layout(std::string grid); + DinkyECS::Entity init_entity(std::string name); + DinkyECS::Entity entity(std::string name); + + inline lel::CellMap& cells() { + return $parser.cells; + } + + inline DinkyECS::World& world() { + return $world; + } + + void init(); + void render(sf::RenderWindow& window); + bool mouse(float x, float y, bool hover); + void debug_layout(sf::RenderWindow& window); + + template <typename Comp> + void set(DinkyECS::Entity ent, Comp val) { + $world.set<Comp>(ent, val); + } + + template <typename Comp> + void set_init(DinkyECS::Entity ent, Comp val) { + dbc::check(has<lel::Cell>(ent),"WRONG! slot is missing its cell?!"); + auto& cell = get<lel::Cell>(ent); + val.init(cell); + $world.set<Comp>(ent, val); + } + + template <typename Comp> + void do_if(DinkyECS::Entity ent, std::function<void(Comp &)> cb) { + if($world.has<Comp>(ent)) { + cb($world.get<Comp>(ent)); + } + } + + lel::Cell& cell_for(DinkyECS::Entity ent) { + return $world.get<lel::Cell>(ent); + } + + lel::Cell& cell_for(std::string name) { + DinkyECS::Entity ent = entity(name); + return $world.get<lel::Cell>(ent); + } + + template <typename Comp> + Comp& get(DinkyECS::Entity entity) { + return $world.get<Comp>(entity); + } + + template <typename Comp> + std::optional<Comp> get_if(DinkyECS::Entity entity) { + return $world.get_if<Comp>(entity); + } + + template <typename Comp> + bool has(DinkyECS::Entity entity) { + return $world.has<Comp>(entity); + } + + template <typename Comp> + void remove(DinkyECS::Entity ent) { + $world.remove<Comp>(ent); + } + + template <typename Comp> + void close(string region) { + auto ent = entity(region); + remove<Comp>(ent); + } + + template<typename T> + void render_helper(sf::RenderWindow& window, DinkyECS::Entity ent, bool is_shape, T& target) { + sf::Shader *shader_ptr = nullptr; + + if($world.has<Effect>(ent)) { + auto& shader = $world.get<Effect>(ent); + + if(shader.$active) { + auto ptr = shader.checkout_ptr(); + ptr->setUniform("is_shape", is_shape); + // NOTE: this is needed because SFML doesn't handle shared_ptr + shader_ptr = ptr.get(); + } + } + + window.draw(*target, shader_ptr); + } + + void show_sprite(string region, string sprite_name); + void show_text(string region, wstring content); + void update_text(string region, wstring content); + void update_label(string region, wstring content); + void show_label(string region, wstring content); + }; + + Clickable make_action(DinkyECS::World& target, Events::GUI event); + +} diff --git a/lel.cpp b/lel.cpp new file mode 100644 index 0000000..e2579fd --- /dev/null +++ b/lel.cpp @@ -0,0 +1,117 @@ +#include "lel.hpp" +#include <fmt/core.h> +#include "dbc.hpp" +#include <numeric> + +#include "lel_parser.cpp" + +namespace lel { + + Parser::Parser(int x, int y, int width, int height) : + grid_x(x), + grid_y(y), + grid_w(width), + grid_h(height), + cur(0, 0) + { + } + + Parser::Parser() : cur(0, 0) { } + + void Parser::position(int x, int y, int width, int height) { + grid_x = x; + grid_y = y; + grid_w = width; + grid_h = height; + } + + void Parser::id(std::string name) { + if(name != "_") { + dbc::check(!cells.contains(name), + fmt::format("duplicate cell name {}", name)); + cells.insert_or_assign(name, cur); + } + + cur = {cur.col + 1, cur.row}; + auto& row = grid.back(); + row.push_back(name); + } + + void Parser::finalize() { + size_t rows = grid.size(); + int cell_height = grid_h / rows; + + for(auto& row : grid) { + size_t columns = row.size(); + int cell_width = grid_w / columns; + dbc::check(cell_width > 0, "invalid cell width calc"); + dbc::check(cell_height > 0, "invalid cell height calc"); + + for(auto& name : row) { + if(name == "_") continue; + auto& cell = cells.at(name); + + // ZED: getting a bit hairy but this should work + if(cell.percent) { + // when percent mode we have to take unset to 100% + if(cell.max_w == 0) cell.max_w = 100; + if(cell.max_h == 0) cell.max_h = 100; + cell.max_w *= cell_width * 0.01; + cell.max_h *= cell_height * 0.01; + } else { + if(cell.max_w == 0) cell.max_w = cell_width; + if(cell.max_h == 0) cell.max_h = cell_height; + } + + cell.w = cell.expand ? std::min(cell.max_w, grid_w) : std::min(cell_width, cell.max_w); + cell.h = cell.expand ? std::min(cell.max_h, grid_h) : std::min(cell_height, cell.max_h); + + dbc::check(cell.h > 0, fmt::format("invalid height cell {}", name)); + dbc::check(cell.w > 0, fmt::format("invalid width cell {}", name)); + + cell.x = grid_x + (cell.col * cell_width); + cell.y = grid_y + (cell.row * cell_height); + + // keep the midpoint since it is used a lot + cell.mid_x = std::midpoint(cell.x, cell.x + cell.w); + cell.mid_y = std::midpoint(cell.y, cell.y + cell.h); + + // perform alignments + if(cell.right) cell.x += cell_width - cell.w; + if(cell.bottom) cell.y += cell_height - cell.h; + if(cell.center) { + cell.x = cell.mid_x - cell.w / 2; + cell.y = cell.mid_y - cell.h / 2; + } + } + } + } + + void Parser::reset() { + cur = {0, 0}; + grid.clear(); + cells.clear(); + } + + std::optional<std::string> Parser::hit(int x, int y) { + for(auto& [name, cell] : cells) { + if((x >= cell.x && x <= cell.x + cell.w) && + (y >= cell.y && y <= cell.y + cell.h)) { + return name; + } + } + + return std::nullopt; + } + + Cell center(int width, int height, Cell &parent) { + Cell copy = parent; + + copy.x = parent.mid_x - width / 2; + copy.y = parent.mid_y - height / 2; + copy.w = width; + copy.h = height; + + return copy; + } +} diff --git a/lel.hpp b/lel.hpp new file mode 100644 index 0000000..136fcce --- /dev/null +++ b/lel.hpp @@ -0,0 +1,57 @@ +#pragma once +#include <string> +#include <unordered_map> +#include <functional> +#include <optional> +#include <vector> +#include "dbc.hpp" + +namespace lel { + + struct Cell { + int x = 0; + int y = 0; + int w = 0; + int h = 0; + int mid_x = 0; + int mid_y = 0; + int max_w = 0; + int max_h = 0; + int col = 0; + int row = 0; + bool right = false; + bool bottom = false; + bool expand = false; + bool center = false; + bool percent = false; + + Cell(int col, int row) : col(col), row(row) {} + Cell() {} + }; + + using Row = std::vector<std::string>; + using CellMap = std::unordered_map<std::string, Cell>; + + struct Parser { + int grid_x = 0; + int grid_y = 0; + int grid_w = 0; + int grid_h = 0; + Cell cur; + std::vector<Row> grid; + CellMap cells; + + Parser(int x, int y, int width, int height); + Parser(); + + void position(int x, int y, int width, int height); + void id(std::string name); + void reset(); + bool parse(std::string input); + void finalize(); + std::optional<std::string> hit(int x, int y); + }; + + + Cell center(int width, int height, Cell &parent); +} diff --git a/lel_parser.cpp b/lel_parser.cpp new file mode 100644 index 0000000..3abc243 --- /dev/null +++ b/lel_parser.cpp @@ -0,0 +1,263 @@ + +#line 1 "lel_parser.rl" +/***** !!!!!! THIS IS INCLUDED BY lel.cpp DO NOT PUT IN BUILD!!!!!! ******/ + +#include "lel.hpp" +#include <fmt/core.h> +#include <iostream> + +namespace lel { + + +#line 42 "lel_parser.rl" + + + +#line 12 "lel_parser.cpp" +static const char _Parser_actions[] = { + 0, 1, 1, 1, 2, 1, 3, 1, + 4, 1, 5, 1, 6, 1, 9, 1, + 10, 1, 11, 1, 12, 1, 13, 2, + 0, 7, 2, 0, 8, 2, 4, 1, + 2, 4, 5 +}; + +static const char _Parser_key_offsets[] = { + 0, 0, 4, 20, 33, 35, 39, 41, + 44, 56, 61 +}; + +static const char _Parser_trans_keys[] = { + 32, 91, 9, 13, 32, 37, 40, 42, + 46, 61, 94, 95, 9, 13, 60, 62, + 65, 90, 97, 122, 37, 40, 42, 46, + 61, 94, 95, 60, 62, 65, 90, 97, + 122, 48, 57, 41, 44, 48, 57, 48, + 57, 41, 48, 57, 32, 93, 95, 124, + 9, 13, 48, 57, 65, 90, 97, 122, + 32, 93, 124, 9, 13, 32, 91, 9, + 13, 0 +}; + +static const char _Parser_single_lengths[] = { + 0, 2, 8, 7, 0, 2, 0, 1, + 4, 3, 2 +}; + +static const char _Parser_range_lengths[] = { + 0, 1, 4, 3, 1, 1, 1, 1, + 4, 1, 1 +}; + +static const char _Parser_index_offsets[] = { + 0, 0, 4, 17, 28, 30, 34, 36, + 39, 48, 53 +}; + +static const char _Parser_indicies[] = { + 0, 2, 0, 1, 3, 4, 5, 6, + 7, 9, 7, 10, 3, 8, 10, 10, + 1, 4, 5, 6, 7, 9, 7, 10, + 8, 10, 10, 1, 11, 1, 12, 13, + 14, 1, 15, 1, 16, 17, 1, 18, + 20, 19, 21, 18, 19, 19, 19, 1, + 22, 23, 24, 22, 1, 25, 2, 25, + 1, 0 +}; + +static const char _Parser_trans_targs[] = { + 1, 0, 2, 2, 3, 4, 3, 3, + 3, 3, 8, 5, 3, 6, 5, 7, + 3, 7, 9, 8, 10, 2, 9, 10, + 2, 10 +}; + +static const char _Parser_trans_actions[] = { + 0, 0, 3, 0, 17, 0, 13, 5, + 11, 15, 21, 19, 23, 23, 0, 19, + 26, 0, 7, 0, 32, 29, 0, 9, + 1, 0 +}; + +static const int Parser_start = 1; +static const int Parser_first_final = 10; +static const int Parser_error = 0; + +static const int Parser_en_main = 1; + + +#line 45 "lel_parser.rl" + +bool Parser::parse(std::string input) { + reset(); + int cs = 0; + const char *start = nullptr; + const char *begin = input.data(); + const char *p = input.data(); + const char *pe = p + input.size(); + std::string tk; + + +#line 93 "lel_parser.cpp" + { + cs = Parser_start; + } + +#line 56 "lel_parser.rl" + +#line 96 "lel_parser.cpp" + { + int _klen; + unsigned int _trans; + const char *_acts; + unsigned int _nacts; + const char *_keys; + + if ( p == pe ) + goto _test_eof; + if ( cs == 0 ) + goto _out; +_resume: + _keys = _Parser_trans_keys + _Parser_key_offsets[cs]; + _trans = _Parser_index_offsets[cs]; + + _klen = _Parser_single_lengths[cs]; + if ( _klen > 0 ) { + const char *_lower = _keys; + const char *_mid; + const char *_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 = _Parser_range_lengths[cs]; + if ( _klen > 0 ) { + const char *_lower = _keys; + const char *_mid; + const char *_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: + _trans = _Parser_indicies[_trans]; + cs = _Parser_trans_targs[_trans]; + + if ( _Parser_trans_actions[_trans] == 0 ) + goto _again; + + _acts = _Parser_actions + _Parser_trans_actions[_trans]; + _nacts = (unsigned int) *_acts++; + while ( _nacts-- > 0 ) + { + switch ( *_acts++ ) + { + case 0: +#line 13 "lel_parser.rl" + {tk = input.substr(start - begin, p - start); } + break; + case 1: +#line 15 "lel_parser.rl" + {} + break; + case 2: +#line 16 "lel_parser.rl" + { grid.push_back(Row()); } + break; + case 3: +#line 17 "lel_parser.rl" + { cur.bottom = (*p) == '.'; } + break; + case 4: +#line 18 "lel_parser.rl" + { id(input.substr(start - begin, p - start)); } + break; + case 5: +#line 19 "lel_parser.rl" + { cur.col = 0; cur.row++; } + break; + case 6: +#line 20 "lel_parser.rl" + { cur.right = (*p) == '>'; } + break; + case 7: +#line 21 "lel_parser.rl" + {cur.max_w = std::stoi(tk); } + break; + case 8: +#line 22 "lel_parser.rl" + { cur.max_h = std::stoi(tk); } + break; + case 9: +#line 23 "lel_parser.rl" + { cur.expand = true; } + break; + case 10: +#line 24 "lel_parser.rl" + { cur.center = true; } + break; + case 11: +#line 25 "lel_parser.rl" + { cur.percent = true; } + break; + case 12: +#line 35 "lel_parser.rl" + { start = p; } + break; + case 13: +#line 38 "lel_parser.rl" + {start = p;} + break; +#line 211 "lel_parser.cpp" + } + } + +_again: + if ( cs == 0 ) + goto _out; + if ( ++p != pe ) + goto _resume; + _test_eof: {} + _out: {} + } + +#line 57 "lel_parser.rl" + + bool good = pe - p == 0; + if(good) { + finalize(); + } else { + dbc::log("error at:"); + std::cout << p; + } + return good; +} + +} diff --git a/lel_parser.rl b/lel_parser.rl new file mode 100644 index 0000000..5368ac2 --- /dev/null +++ b/lel_parser.rl @@ -0,0 +1,68 @@ +/***** !!!!!! THIS IS INCLUDED BY lel.cpp DO NOT PUT IN BUILD!!!!!! ******/ + +#include "lel.hpp" +#include <fmt/core.h> +#include <iostream> + +namespace lel { + +%%{ + machine Parser; + alphtype char; + + action token {tk = input.substr(start - begin, fpc - start); } + + action col {} + action ltab { grid.push_back(Row()); } + action valign { cur.bottom = fc == '.'; } + action id { id(input.substr(start - begin, fpc - start)); } + action row { cur.col = 0; cur.row++; } + action align { cur.right = fc == '>'; } + action setwidth {cur.max_w = std::stoi(tk); } + action setheight { cur.max_h = std::stoi(tk); } + action expand { cur.expand = true; } + action center { cur.center = true; } + action percent { cur.percent = true; } + + col = "|" $col; + ltab = "[" $ltab; + rtab = "]" $row; + valign = ("^" | ".") $valign; + expand = "*" $expand; + center = "=" $center; + percent = "%" $percent; + halign = ("<" | ">") $align; + number = digit+ >{ start = fpc; } %token; + setw = ("(" number %setwidth ("," number %setheight)? ")") ; + modifiers = (percent | center | expand | valign | halign | setw); + id = modifiers* ((alpha | '_')+ :>> (alnum | '_')*) >{start = fpc;} %id; + row = space* ltab space* id space* (col space* id space*)* space* rtab space*; + + main := row+; +}%% + +%% write data; + +bool Parser::parse(std::string input) { + reset(); + int cs = 0; + const char *start = nullptr; + const char *begin = input.data(); + const char *p = input.data(); + const char *pe = p + input.size(); + std::string tk; + + %% write init; + %% write exec; + + bool good = pe - p == 0; + if(good) { + finalize(); + } else { + dbc::log("error at:"); + std::cout << p; + } + return good; +} + +} diff --git a/matrix.cpp b/matrix.cpp new file mode 100644 index 0000000..72b3d51 --- /dev/null +++ b/matrix.cpp @@ -0,0 +1,31 @@ +#include "matrix.hpp" +#include "dbc.hpp" +#include <fmt/core.h> +#include <cmath> +#include <cstdlib> +#include "constants.hpp" + +using namespace fmt; +using std::min, std::max; + +namespace matrix { + void dump(const std::string &msg, Matrix &map, int show_x, int show_y) { + println("----------------- {}", msg); + + for(each_row it{map}; it.next();) { + int cell = map[it.y][it.x]; + + if(int(it.x) == show_x && int(it.y) == show_y) { + print("{:x}<", cell); + } else if(cell > 15 && cell < 32) { + print("{:x}+", cell - 16); + } else if(cell > 31) { + print("* "); + } else { + print("{:x} ", cell); + } + + if(it.row) print("\n"); + } + } +} diff --git a/matrix.hpp b/matrix.hpp new file mode 100644 index 0000000..bf40fe3 --- /dev/null +++ b/matrix.hpp @@ -0,0 +1,49 @@ +#pragma once +#include <vector> +#include <queue> +#include <string> +#include <array> +#include <numeric> +#include <algorithm> +#include <fmt/core.h> +#include "dbc.hpp" +#include "shiterator.hpp" + +namespace matrix { + using Row = shiterator::BaseRow<int>; + using Matrix = shiterator::Base<int>; + + using viewport = shiterator::viewport_t<Matrix>; + + using each_cell = shiterator::each_cell_t<Matrix>; + + using each_row = shiterator::each_row_t<Matrix>; + using box = shiterator::box_t<Matrix>; + using compass = shiterator::compass_t<Matrix>; + using circle = shiterator::circle_t<Matrix>; + using rectangle = shiterator::rectangle_t<Matrix>; + using rando_rect = shiterator::rando_rect_t<Matrix>; + using line = shiterator::line; + + void dump(const std::string &msg, Matrix &map, int show_x=-1, int show_y=-1); + + inline Matrix make(size_t width, size_t height) { + return shiterator::make<int>(width, height); + } + + inline bool inbounds(Matrix &mat, size_t x, size_t y) { + return shiterator::inbounds(mat, x, y); + } + + inline size_t width(Matrix &mat) { + return shiterator::width(mat); + } + + inline size_t height(Matrix &mat) { + return shiterator::height(mat); + } + + inline void assign(Matrix &out, int new_value) { + shiterator::assign(out, new_value); + } +} diff --git a/meson.build b/meson.build index a5d19ae..59793d6 100644 --- a/meson.build +++ b/meson.build @@ -93,16 +93,23 @@ dependencies += [ ] sources = [ - 'game_engine.cpp', + 'builder.cpp', + 'config.cpp', 'dbc.cpp', + 'game_engine.cpp', + 'guecs.cpp', 'gui.cpp', - 'watcher.cpp', - 'builder.cpp', + 'lel.cpp', + 'matrix.cpp', + 'rand.cpp', 'sfmlbackend.cpp', + 'shaders.cpp', + 'sound.cpp', + 'textures.cpp', + 'watcher.cpp', ] - -executable('escape_turings_tarpit', sources + [ +executable('ttpit', sources + [ 'main.cpp' ], cpp_args: cpp_args, diff --git a/point.hpp b/point.hpp new file mode 100644 index 0000000..8f8886b --- /dev/null +++ b/point.hpp @@ -0,0 +1,20 @@ +#pragma once +#include <vector> + +struct Point { + size_t x = 0; + size_t y = 0; + + bool operator==(const Point& other) const { + return other.x == x && other.y == y; + } +}; + +typedef std::vector<Point> PointList; + +template<> struct std::hash<Point> { + size_t operator()(const Point& p) const { + auto hasher = std::hash<int>(); + return hasher(p.x) ^ hasher(p.y); + } +}; diff --git a/rand.cpp b/rand.cpp new file mode 100644 index 0000000..25d77d7 --- /dev/null +++ b/rand.cpp @@ -0,0 +1,6 @@ +#include "rand.hpp" + +namespace Random { + std::random_device RNG; + std::mt19937 GENERATOR(RNG()); +} diff --git a/rand.hpp b/rand.hpp new file mode 100644 index 0000000..e639235 --- /dev/null +++ b/rand.hpp @@ -0,0 +1,28 @@ +#pragma once +#include <random> + + +namespace Random { + extern std::mt19937 GENERATOR; + + template<typename T> + T uniform(T from, T to) { + std::uniform_int_distribution<T> rand(from, to); + + return rand(GENERATOR); + } + + template<typename T> + T uniform_real(T from, T to) { + std::uniform_real_distribution<T> rand(from, to); + + return rand(GENERATOR); + } + + template<typename T> + T normal(T mean, T stddev) { + std::normal_distribution<T> rand(mean, stddev); + + return rand(GENERATOR); + } +} diff --git a/shaders.cpp b/shaders.cpp new file mode 100644 index 0000000..011a73a --- /dev/null +++ b/shaders.cpp @@ -0,0 +1,77 @@ +#include "shaders.hpp" +#include <SFML/Graphics/Image.hpp> +#include "dbc.hpp" +#include <fmt/core.h> +#include "config.hpp" +#include <memory> + +namespace shaders { + using std::shared_ptr, std::make_shared; + + static ShaderManager SMGR; + static bool INITIALIZED = false; + static int VERSION = 0; + + inline void configure_shader_defaults(std::shared_ptr<sf::Shader> ptr) { + ptr->setUniform("source", sf::Shader::CurrentTexture); + } + + bool load_shader(std::string name, nlohmann::json& settings) { + std::string file_name = settings["file_name"]; + auto ptr = std::make_shared<sf::Shader>(); + bool good = ptr->loadFromFile(file_name, sf::Shader::Type::Fragment); + + if(good) { + configure_shader_defaults(ptr); + SMGR.shaders.try_emplace(name, name, file_name, ptr); + } + return good; + } + + void init() { + if(!INITIALIZED) { + dbc::check(sf::Shader::isAvailable(), "no shaders?!"); + INITIALIZED = true; + Config config("assets/shaders.json"); + bool good = load_shader("ERROR", config["ERROR"]); + dbc::check(good, "Failed to load ERROR shader. Look in assets/shaders.json"); + + for(auto& [name, settings] : config.json().items()) { + if(name == "ERROR") continue; + + dbc::check(!SMGR.shaders.contains(name), + fmt::format("shader name '{}' duplicated in assets/shaders.json", name)); + good = load_shader(name, settings); + + if(!good) { + dbc::log(fmt::format("failed to load shader {}", name)); + SMGR.shaders.insert_or_assign(name, SMGR.shaders.at("ERROR")); + } + } + } + } + + std::shared_ptr<sf::Shader> get(const std::string& name) { + dbc::check(INITIALIZED, "you forgot to shaders::init()"); + dbc::check(SMGR.shaders.contains(name), + fmt::format("shader name '{}' not in assets/shaders.json", name)); + auto& rec = SMGR.shaders.at(name); + return rec.ptr; + } + + int reload() { + VERSION++; + INITIALIZED = false; + SMGR.shaders.clear(); + init(); + return VERSION; + } + + bool updated(int my_version) { + return my_version != VERSION; + } + + int version() { + return VERSION; + } +}; diff --git a/shaders.hpp b/shaders.hpp new file mode 100644 index 0000000..51565b2 --- /dev/null +++ b/shaders.hpp @@ -0,0 +1,28 @@ +#pragma once +#include <cstdint> +#include <vector> +#include <string> +#include <SFML/Graphics.hpp> +#include <unordered_map> +#include <memory> +#include "matrix.hpp" +#include <nlohmann/json.hpp> + +namespace shaders { + struct Record { + std::string name; + std::string file_name; + std::shared_ptr<sf::Shader> ptr = nullptr; + }; + + struct ShaderManager { + std::unordered_map<std::string, Record> shaders; + }; + + std::shared_ptr<sf::Shader> get(const std::string& name); + void init(); + bool load_shader(std::string& name, nlohmann::json& settings); + bool updated(int my_version); + int reload(); + int version(); +} diff --git a/shiterator.hpp b/shiterator.hpp new file mode 100644 index 0000000..434107d --- /dev/null +++ b/shiterator.hpp @@ -0,0 +1,607 @@ +#pragma once +#include <vector> +#include <queue> +#include <string> +#include <array> +#include <numeric> +#include <algorithm> +#include <fmt/core.h> +#include "point.hpp" +#include "rand.hpp" +#include "dbc.hpp" + +/* + * # What is This Shit? + * + * Announcing the Shape Iterators, or "shiterators" for short. The best shite + * for C++ for-loops since that [one youtube + * video](https://www.youtube.com/watch?v=rX0ItVEVjHc) told everyone to + * recreate SQL databases with structs. You could also say these are Shaw's + * Iterators, but either way they are the _shite_. Or are they shit? You decide. + * Maybe they're "shite"? + * + * A shiterator is a simple generator that converts 2D shapes into a 1D stream + * of x/y coordinates. You give it a matrix, some parameters like start, end, + * etc. and each time you call `next()` you get the next viable x/y coordinate to + * complete the shape. This makes them far superior to _any_ existing for-loop + * technology because shiterators operate _intelligently_ in shapes. Other + * [programming pundits](https://www.youtube.com/watch?v=tD5NrevFtbU) will say + * their 7000 line "easy to maintain" switch statements are better at drawing + * shapes, but they're wrong. My way of making a for-loop do stuff is vastly + * superior because it doesn't use a switch _or_ a virtual function _or_ + * inheritance at all. That means they have to be the _fastest_. Feel free to run + * them 1000 times and bask in the glory of 1 nanosecond difference performance. + * + * It's science and shite. + * + * More importantly, shiterators are simple and easy to use. They're so easy to + * use you _don't even use the 3rd part of the for-loop_. What? You read that right, + * not only have I managed to eliminate _both_ massive horrible to maintain switches, + * and also avoided virtual functions, but I've also _eliminated one entire part + * of the for-loop_. This obviously makes them way faster than other inferior + * three-clause-loop-trash. Just look at this comparison: + * + * ```cpp + * for(it = trash.begin(); it != trash.end(); it++) { + * std::cout << it << std::endl; + * } + * ``` + * + * ```cpp + * for(each_cell it{mat}; it.next();) { + * std::cout << mat[it.y][it.x] << std::endl; + * } + * ``` + * + * Obviously this will outperform _any_ iterator invented in the last 30 years, but the best + * thing about shiterators is their composability and ability to work simultaneously across + * multiple matrices in one loop: + * + * ```cpp + * for(line it{start, end}; it.next();) { + * for(compass neighbor{walls, it.x, it.y}; neighbor.next();) { + * if(walls[neighbor.y][neighbor.x] == 1) { + * wall_update[it.y][it.x] = walls[it.y][it.x] + 10; + * } + * } + * } + * ``` + * + * This code sample (maybe, because I didn't run it) draws a line from + * `start` to `end` then looks at each neighbor on a compass (north, south, east, west) + * at each point to see if it's set to 1. If it is then it copies that cell over to + * another matrix with +10. Why would you need this? Your Wizard just shot a fireball + * down a corridor and you need to see if anything in the path is within 1 square of it. + * + * You _also_ don't even need to use a for-loop. Yes, you can harken back to the old + * days when we did everything RAW inside a Duff's Device between a while-loop for + * that PERFORMANCE because who cares about maintenance? You're a game developer! Tests? + * Don't need a test if it runs fine on Sony Playstation only. Maintenance? You're moving + * on to the next project in two weeks anyway right?! Use that while-loop and a shiterator + * to really help that next guy: + * + * ```cpp + * box it{walls, center_x, center_y, 20}; + * while(it.next()) { + * walls[it.y][it.x] = 1; + * } + * ``` + * + * ## Shiterator "Guarantees" + * + * Just like Rust [guarantees no memory leaks](https://github.com/pop-os/cosmic-comp/issues/1133), + * a shiterator tries to ensure a few things, if it can: + * + * 1. All x/y values will be within the Matrix you give it. The `line` shiterator doesn't though. + * 2. They try to not store anything and only calculate the math necessary to linearlize the shape. + * 3. You can store them and incrementally call next to get the next value. + * 4. You should be able to compose them together on the same Matrix or different matrices of the same dimensions. + * 5. Most of them will only require 1 for-loop, the few that require 2 only do this so you can draw the inside of a shape. `circle` is like this. + * 6. They don't assume any particular classes or require subclassing. As long as the type given enables `mat[y][x]` (row major) access then it'll work. + * 7. The matrix given to a shiterator isn't actually attached to it, so you can use one matrix to setup an iterator, then apply the x/y values to any other matrix of the same dimensions. Great for smart copying and transforming. + * 8. More importantly, shiterators _do not return any values from the matrix_. They only do the math for coordinates and leave it to you to work your matrix. + * + * These shiterators are used all over the game to do map rendering, randomization, drawing, nearly everything that involves a shape. + * + * ## Algorithms I Need + * + * I'm currently looking for a few algorithms, so if you know how to do these let me know: + * + * 1. _Flood fill_ This turns out to be really hard because most algorithms require keeping track of visited cells with a queue, recursion, etc. + * 2. _Random rectangle fill_ I have something that mostly works but it's really only random across each y-axis, then separate y-axes are randomized. + * 3. _Dijkstra Map_ I have a Dijkstra algorithm but it's not in this style yet. Look in `worldbuilder.cpp` for my current implementation. + * 4. _Viewport_ Currently working on this but I need to have a rectangle I can move around as a viewport. + * + * + * ## Usage + * + * Check the `matrix.hpp` for an example if you want to make it more conventient for your own type. + * + * ## Thanks + * + * Special thanks to Amit and hirdrac for their help with the math and for + * giving me the initial idea. hirdrac doesn't want to be held responsible for + * this travesty but he showed me that you can do iteration and _not_ use the + * weird C++ iterators. Amit did a lot to show me how to do these calculations + * without branching. Thanks to you both--and to everyone else--for helping me while I + * stream my development. + * + * ### SERIOUS DISCLAIMER + * + * I am horribly bad at trigonometry and graphics algorithms, so if you've got an idea to improve them + * or find a bug shoot me an email at help@learncodethehardway.com. + */ +namespace shiterator { using std::vector, std::queue, std::array; using + std::min, std::max, std::floor; + + template<typename T> + using BaseRow = vector<T>; + + template<typename T> + using Base = vector<BaseRow<T>>; + + template<typename T> + inline Base<T> make(size_t width, size_t height) { + Base<T> result(height, BaseRow<T>(width)); + return result; + } + + /* + * Just a quick thing to reset a matrix to a value. + */ + template<typename MAT, typename VAL> + inline void assign(MAT &out, VAL new_value) { + for(auto &row : out) { + row.assign(row.size(), new_value); + } + } + + + /* + * Tells you if a coordinate is in bounds of the matrix + * and therefore safe to use. + */ + template<typename MAT> + inline bool inbounds(MAT &mat, size_t x, size_t y) { + // since Point.x and Point.y are size_t any negatives are massive + return (y < mat.size()) && (x < mat[0].size()); + } + + /* + * Gives the width of a matrix. Assumes row major (y/x) + * and vector API .size(). + */ + template<typename MAT> + inline size_t width(MAT &mat) { + return mat[0].size(); + } + + /* + * Same as shiterator::width but just the height. + */ + template<typename MAT> + inline size_t height(MAT &mat) { + return mat.size(); + } + + /* + * These are internal calculations that help + * with keeping track of the next x coordinate. + */ + inline size_t next_x(size_t x, size_t width) { + return (x + 1) * ((x + 1) < width); + } + + /* + * Same as next_x but updates the next y coordinate. + * It uses the fact that when x==0 you have a new + * line so increment y. + */ + inline size_t next_y(size_t x, size_t y) { + return y + (x == 0); + } + + /* + * Figures out if you're at the end of the shape, + * which is usually when y > height. + */ + inline bool at_end(size_t y, size_t height) { + return y < height; + } + + /* + * Determines if you're at the end of a row. + */ + inline bool end_row(size_t x, size_t width) { + return x == width - 1; + } + + /* + * Most basic shiterator. It just goes through + * every cell in the matrix in linear order + * with not tracking of anything else. + */ + template<typename MAT> + struct each_cell_t { + size_t x = ~0; + size_t y = ~0; + size_t width = 0; + size_t height = 0; + + each_cell_t(MAT &mat) + { + height = shiterator::height(mat); + width = shiterator::width(mat); + } + + bool next() { + x = next_x(x, width); + y = next_y(x, y); + return at_end(y, height); + } + }; + + /* + * This is just each_cell_t but it sets + * a boolean value `bool row` so you can + * tell when you've reached the end of a + * row. This is mostly used for printing + * out a matrix and similar just drawing the + * whole thing with its boundaries. + */ + template<typename MAT> + struct each_row_t { + size_t x = ~0; + size_t y = ~0; + size_t width = 0; + size_t height = 0; + bool row = false; + + each_row_t(MAT &mat) { + height = shiterator::height(mat); + width = shiterator::width(mat); + } + + bool next() { + x = next_x(x, width); + y = next_y(x, y); + row = end_row(x, width); + return at_end(y, height); + } + }; + + /* + * This is a CENTERED box, that will create + * a centered rectangle around a point of a + * certain dimension. This kind of needs a + * rewrite but if you want a rectangle from + * a upper corner then use rectangle_t type. + * + * Passing 1 parameter for the size will make + * a square. + */ + template<typename MAT> + struct box_t { + size_t from_x; + size_t from_y; + size_t x = 0; // these are set in constructor + size_t y = 0; // again, no fancy ~ trick needed + size_t left = 0; + size_t top = 0; + size_t right = 0; + size_t bottom = 0; + + box_t(MAT &mat, size_t at_x, size_t at_y, size_t size) : + box_t(mat, at_x, at_y, size, size) { + } + + box_t(MAT &mat, size_t at_x, size_t at_y, size_t width, size_t height) : + from_x(at_x), from_y(at_y) + { + size_t h = shiterator::height(mat); + size_t w = shiterator::width(mat); + + // keeps it from going below zero + // need extra -1 to compensate for the first next() + left = max(from_x, width) - width; + x = left - 1; // must be -1 for next() + // keeps it from going above width + right = min(from_x + width + 1, w); + + // same for these two + top = max(from_y, height) - height; + y = top - (left == 0); + bottom = min(from_y + height + 1, h); + } + + bool next() { + // calc next but allow to go to 0 for next + x = next_x(x, right); + // x will go to 0, which signals new line + y = next_y(x, y); // this must go here + // if x==0 then this moves it to min_x + x = max(x, left); + // and done + + return at_end(y, bottom); + } + + /* + * This was useful for doing quick lighting + * calculations, and I might need to implement + * it in other shiterators. It gives the distance + * to the center from the current x/y. + */ + float distance() { + int dx = from_x - x; + int dy = from_y - y; + + return sqrt((dx * dx) + (dy * dy)); + } + }; + + /* + * Stupid simple compass shape North/South/East/West. + * This comes up a _ton_ when doing searching, flood + * algorithms, collision, etc. Probably not the + * fastest way to do it but good enough. + */ + template<typename MAT> + struct compass_t { + size_t x = 0; // these are set in constructor + size_t y = 0; // again, no fancy ~ trick needed + array<int, 4> x_dirs{0, 1, 0, -1}; + array<int, 4> y_dirs{-1, 0, 1, 0}; + size_t max_dirs=0; + size_t dir = ~0; + + compass_t(MAT &mat, size_t x, size_t y) : + x(x), y(y) + { + array<int, 4> x_in{0, 1, 0, -1}; + array<int, 4> y_in{-1, 0, 1, 0}; + + for(size_t i = 0; i < 4; i++) { + int nx = x + x_in[i]; + int ny = y + y_in[i]; + if(shiterator::inbounds(mat, nx, ny)) { + x_dirs[max_dirs] = nx; + y_dirs[max_dirs] = ny; + max_dirs++; + } + } + } + + bool next() { + dir++; + if(dir < max_dirs) { + x = x_dirs[dir]; + y = y_dirs[dir]; + return true; + } else { + return false; + } + } + }; + + /* + * Draws a line from start to end using a algorithm from + * https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm + * No idea if the one I picked is best but it's the one + * that works in the shiterator requirements and produced + * good results. + * + * _WARNING_: This one doesn't check if the start/end are + * within your Matrix, as it's assumed _you_ did that + * already. + */ + struct line { + int x; + int y; + int x1; + int y1; + int sx; + int sy; + int dx; + int dy; + int error; + + line(Point start, Point end) : + x(start.x), y(start.y), + x1(end.x), y1(end.y) + { + dx = std::abs(x1 - x); + sx = x < x1 ? 1 : -1; + dy = std::abs(y1 - y) * -1; + sy = y < y1 ? 1 : -1; + error = dx + dy; + } + + bool next() { + if(x != x1 || y != y1) { + int e2 = 2 * error; + + if(e2 >= dy) { + error = error + dy; + x = x + sx; + } + + if(e2 <= dx) { + error = error + dx; + y = y + sy; + } + return true; + } else { + return false; + } + } + }; + + /* + * Draws a simple circle using a fairly naive algorithm + * but one that actually worked. So, so, so, so many + * circle drawing algorithms described online don't work + * or are flat wrong. Even the very best I could find + * did overdrawing of multiple lines or simply got the + * math wrong. Keep in mind, _I_ am bad at this trig math + * so if I'm finding errors in your circle drawing then + * you got problems. + * + * This one is real simple, and works. If you got better + * then take the challenge but be ready to get it wrong. + */ + template<typename MAT> + struct circle_t { + float center_x; + float center_y; + float radius = 0.0f; + int y = 0; + int dx = 0; + int dy = 0; + int left = 0; + int right = 0; + int top = 0; + int bottom = 0; + int width = 0; + int height = 0; + + circle_t(MAT &mat, Point center, float radius) : + center_x(center.x), center_y(center.y), radius(radius) + { + width = shiterator::width(mat); + height = shiterator::height(mat); + top = max(int(floor(center_y - radius)), 0); + bottom = min(int(floor(center_y + radius)), height - 1); + + y = top; + } + + bool next() { + y++; + if(y <= bottom) { + dy = y - center_y; + dx = floor(sqrt(radius * radius - dy * dy)); + left = max(0, int(center_x) - dx); + right = min(width, int(center_x) + dx + 1); + return true; + } else { + return false; + } + } + }; + + /* + * Basic rectangle shiterator, and like box and rando_rect_t you can + * pass only 1 parameter for size to do a square. + */ + template<typename MAT> + struct rectangle_t { + int x; + int y; + int top; + int left; + int width; + int height; + int right; + int bottom; + + rectangle_t(MAT &mat, size_t start_x, size_t start_y, size_t size) : + rectangle_t(mat, start_x, start_y, size, size) { + } + + rectangle_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) : + top(start_y), + left(start_x), + width(width), + height(height) + { + size_t h = shiterator::height(mat); + size_t w = shiterator::width(mat); + y = start_y - 1; + x = left - 1; // must be -1 for next() + right = min(start_x + width, w); + + y = start_y; + bottom = min(start_y + height, h); + } + + bool next() { + x = next_x(x, right); + y = next_y(x, y); + x = max(x, left); + return at_end(y, bottom); + } + }; + + /* + * WIP: This one is used to place entities randomly but + * could be used for effects like random destruction of floors. + * It simply "wraps" the rectangle_t but randomizes the x/y values + * using a random starting point. This makes it random across the + * x-axis but only partially random across the y. + */ + template<typename MAT> + struct rando_rect_t { + int x; + int y; + int x_offset; + int y_offset; + rectangle_t<MAT> it; + + rando_rect_t(MAT &mat, size_t start_x, size_t start_y, size_t size) : + rando_rect_t(mat, start_x, start_y, size, size) { + } + + rando_rect_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) : + it{mat, start_x, start_y, width, height} + { + x_offset = Random::uniform(0, int(width)); + y_offset = Random::uniform(0, int(height)); + } + + bool next() { + bool done = it.next(); + x = it.left + ((it.x + x_offset) % it.width); + y = it.top + ((it.y + y_offset) % it.height); + return done; + } + }; + + /* + * BROKEN: I'm actually not sure what I'm trying to + * do here yet. + */ + template<typename MAT> + struct viewport_t { + Point start; + // this is the point in the map + size_t x; + size_t y; + // this is the point inside the box, start at 0 + size_t view_x = ~0; + size_t view_y = ~0; + // viewport width/height + size_t width; + size_t height; + + viewport_t(MAT &mat, Point start, int max_x, int max_y) : + start(start), + x(start.x-1), + y(start.y-1) + { + width = std::min(size_t(max_x), shiterator::width(mat) - start.x); + height = std::min(size_t(max_y), shiterator::height(mat) - start.y); + fmt::println("viewport_t max_x, max_y {},{} vs matrix {},{}, x={}, y={}", + max_x, max_y, shiterator::width(mat), shiterator::height(mat), x, y); + } + + bool next() { + y = next_y(x, y); + x = next_x(x, width); + view_x = next_x(view_x, width); + view_y = next_y(view_x, view_y); + return at_end(y, height); + } + }; + +} diff --git a/sound.cpp b/sound.cpp new file mode 100644 index 0000000..cf8c48a --- /dev/null +++ b/sound.cpp @@ -0,0 +1,82 @@ +#include "sound.hpp" +#include "dbc.hpp" +#include <fmt/core.h> +#include "config.hpp" + +namespace sound { + static SoundManager SMGR; + static bool initialized = false; + static bool muted = false; + + using namespace fmt; + using std::make_shared; + namespace fs = std::filesystem; + + SoundPair& get_sound_pair(const std::string& name) { + dbc::check(initialized, "You need to call sound::init() first"); + + if(SMGR.sounds.contains(name)) { + // get the sound from the sound map + return SMGR.sounds.at(name); + } else { + dbc::log(fmt::format("Attempted to stop {} sound but not available.", + name)); + return SMGR.sounds.at("blank"); + } + } + + + void init() { + if(!initialized) { + Config assets("assets/config.json"); + + for(auto& el : assets["sounds"].items()) { + load(el.key(), el.value()); + } + initialized = true; + } + } + + void load(const std::string& name, const std::string& sound_path) { + dbc::check(fs::exists(sound_path), fmt::format("sound file {} does not exist", sound_path)); + + // create the buffer and keep in the buffer map + auto buffer = make_shared<sf::SoundBuffer>(sound_path); + + // set it on the sound and keep in the sound map + auto sound = make_shared<sf::Sound>(*buffer); + sound->setRelativeToListener(false); + sound->setPosition({0.0f, 0.0f, 1.0f}); + + SMGR.sounds.try_emplace(name, buffer, sound); + } + + void play(const std::string& name, bool loop) { + if(muted) return; + auto& pair = get_sound_pair(name); + pair.sound->setLooping(loop); + // play it + pair.sound->play(); + } + + void stop(const std::string& name) { + auto& pair = get_sound_pair(name); + pair.sound->stop(); + } + + bool playing(const std::string& name) { + auto& pair = get_sound_pair(name); + auto status = pair.sound->getStatus(); + return status == sf::SoundSource::Status::Playing; + } + + void play_at(const std::string& name, float x, float y, float z) { + auto& pair = get_sound_pair(name); + pair.sound->setPosition({x, y, z}); + pair.sound->play(); + } + + void mute(bool setting) { + muted = setting; + } +} diff --git a/sound.hpp b/sound.hpp new file mode 100644 index 0000000..3dc21cc --- /dev/null +++ b/sound.hpp @@ -0,0 +1,26 @@ +#pragma once +#include <string> +#include <filesystem> +#include <memory> +#include <unordered_map> +#include <SFML/Audio.hpp> + +namespace sound { + struct SoundPair { + std::shared_ptr<sf::SoundBuffer> buffer; + std::shared_ptr<sf::Sound> sound; + }; + + struct SoundManager { + std::unordered_map<std::string, SoundPair> sounds; + }; + + void init(); + void load(const std::string& name, const std::string& path); + void play(const std::string& name, bool loop=false); + void play_at(const std::string& name, float x, float y, float z); + void stop(const std::string& name); + void mute(bool setting); + bool playing(const std::string& name); + SoundPair& get_sound_pair(const std::string& name); +} diff --git a/textures.cpp b/textures.cpp new file mode 100644 index 0000000..c4af1cb --- /dev/null +++ b/textures.cpp @@ -0,0 +1,99 @@ +#include "textures.hpp" +#include <SFML/Graphics/Image.hpp> +#include "dbc.hpp" +#include <fmt/core.h> +#include "config.hpp" +#include <memory> + +namespace textures { + using std::shared_ptr, std::make_shared; + + static TextureManager TMGR; + static bool initialized = false; + + void load_sprites() { + Config assets("assets/config.json"); + + for(auto& [name, settings] : assets["sprites"].items()) { + auto texture = make_shared<sf::Texture>(settings["path"]); + + texture->setSmooth(assets["graphics"]["smooth_textures"]); + auto sprite = make_shared<sf::Sprite>(*texture); + + int width = settings["frame_width"]; + int height = settings["frame_height"]; + sprite->setTextureRect({{0,0}, {width, height}}); + + TMGR.sprite_textures.try_emplace(name, name, sprite, texture); + } + + TMGR.floor = load_image(assets["sprites"]["floor"]["path"]); + TMGR.ceiling = load_image(assets["sprites"]["ceiling"]["path"]); + } + + void load_tiles() { + Config assets("assets/tiles.json"); + auto &tiles = assets.json(); + + for(auto &el : tiles.items()) { + auto &config = el.value(); + TMGR.surfaces.emplace_back(load_image(config["texture"])); + wchar_t tid = config["display"]; + int surface_i = TMGR.surfaces.size() - 1; + TMGR.char_to_texture[tid] = surface_i; + } + } + + void init() { + if(!initialized) { + load_tiles(); + load_sprites(); + initialized = true; + } + } + + SpriteTexture get(std::string name) { + dbc::check(initialized, "you forgot to call textures::init()"); + dbc::check(TMGR.sprite_textures.contains(name), + fmt::format("!!!!! texture pack does not contain {} sprite", name)); + + auto result = TMGR.sprite_textures.at(name); + + dbc::check(result.sprite != nullptr, + fmt::format("bad sprite from textures::get named {}", name)); + dbc::check(result.texture != nullptr, + fmt::format("bad texture from textures::get named {}", name)); + + return result; + } + + sf::Image load_image(std::string filename) { + sf::Image texture; + bool good = texture.loadFromFile(filename); + dbc::check(good, fmt::format("failed to load {}", filename)); + return texture; + } + + const uint32_t* get_surface(size_t num) { + return (const uint32_t *)TMGR.surfaces[num].getPixelsPtr(); + } + + matrix::Matrix convert_char_to_texture(matrix::Matrix &tile_ids) { + auto result = matrix::make(matrix::width(tile_ids), matrix::height(tile_ids)); + + for(matrix::each_cell it(tile_ids); it.next();) { + wchar_t tid = tile_ids[it.y][it.x]; + result[it.y][it.x] = TMGR.char_to_texture.at(tid); + } + + return result; + } + + const uint32_t* get_floor() { + return (const uint32_t *)TMGR.floor.getPixelsPtr(); + } + + const uint32_t* get_ceiling() { + return (const uint32_t *)TMGR.ceiling.getPixelsPtr(); + } +}; diff --git a/textures.hpp b/textures.hpp new file mode 100644 index 0000000..129ff1c --- /dev/null +++ b/textures.hpp @@ -0,0 +1,39 @@ +#pragma once +#include <cstdint> +#include <vector> +#include <string> +#include <SFML/Graphics.hpp> +#include <unordered_map> +#include <memory> +#include "matrix.hpp" + +namespace textures { + + struct SpriteTexture { + std::string name; + std::shared_ptr<sf::Sprite> sprite = nullptr; + std::shared_ptr<sf::Texture> texture = nullptr; + }; + + struct TextureManager { + std::vector<sf::Image> surfaces; + std::unordered_map<std::string, SpriteTexture> sprite_textures; + std::unordered_map<wchar_t, int> char_to_texture; + sf::Image floor; + sf::Image ceiling; + }; + + void init(); + + SpriteTexture get(std::string name); + + sf::Image load_image(std::string filename); + + const uint32_t* get_surface(size_t num); + + matrix::Matrix convert_char_to_texture(matrix::Matrix &from); + + const uint32_t* get_floor(); + + const uint32_t* get_ceiling(); +}