diff --git a/CMakeLists.txt b/CMakeLists.txt index c89249b..5468827 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,7 @@ option(BUILD_TESTS "Build smoke tests" ON) if(BUILD_TESTS) add_executable(test_entities_smoke tests/test_entities_smoke.cpp + source/core/resources/resource_helper.cpp source/game/map/map.cpp ${ENTITY_SOURCES} ) @@ -288,6 +289,7 @@ if(BUILD_TESTS) source/game/debug_config.cpp source/game/scenes/placeholder_input.cpp source/core/input/key_config.cpp + source/core/resources/resource_helper.cpp ) target_include_directories(test_scene_flow PRIVATE "${CMAKE_SOURCE_DIR}/source" diff --git a/source/core/input/key_config.cpp b/source/core/input/key_config.cpp index 36e4e38..c9a915f 100644 --- a/source/core/input/key_config.cpp +++ b/source/core/input/key_config.cpp @@ -4,6 +4,7 @@ #include #include +#include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" auto KeyConfig::get() -> KeyConfig& { @@ -12,16 +13,17 @@ auto KeyConfig::get() -> KeyConfig& { } void KeyConfig::load(const std::string& file_path) { - std::ifstream file(file_path); + const auto FULL = Resource::Helper::resolve(file_path); + std::ifstream file(FULL); if (!file.is_open()) { - std::cerr << "KeyConfig: no s'ha pogut obrir " << file_path << '\n'; + std::cerr << "KeyConfig: no s'ha pogut obrir " << FULL << '\n'; return; } try { auto yaml = fkyaml::node::deserialize(file); if (!yaml.contains("keys") || !yaml["keys"].is_sequence()) { - std::cerr << "KeyConfig: falta la secció 'keys' a " << file_path << '\n'; + std::cerr << "KeyConfig: falta la secció 'keys' a " << FULL << '\n'; return; } bindings_.clear(); @@ -36,9 +38,9 @@ void KeyConfig::load(const std::string& file_path) { } bindings_[ID] = SC; } - std::cout << "KeyConfig: " << bindings_.size() << " bindings des de " << file_path << '\n'; + std::cout << "KeyConfig: " << bindings_.size() << " bindings des de " << FULL << '\n'; } catch (const std::exception& e) { - std::cerr << "KeyConfig: error parsejant " << file_path << ": " << e.what() << '\n'; + std::cerr << "KeyConfig: error parsejant " << FULL << ": " << e.what() << '\n'; } } diff --git a/source/core/locale/locale.cpp b/source/core/locale/locale.cpp index 4a208c1..0a3241a 100644 --- a/source/core/locale/locale.cpp +++ b/source/core/locale/locale.cpp @@ -4,6 +4,7 @@ #include #include +#include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" namespace { @@ -33,9 +34,10 @@ auto Locale::get() -> Locale& { } void Locale::load(const std::string& file_path) { - std::ifstream file(file_path); + const auto FULL = Resource::Helper::resolve(file_path); + std::ifstream file(FULL); if (!file.is_open()) { - std::cerr << "Locale: no s'ha pogut obrir " << file_path << '\n'; + std::cerr << "Locale: no s'ha pogut obrir " << FULL << '\n'; return; } @@ -43,9 +45,9 @@ void Locale::load(const std::string& file_path) { auto yaml = fkyaml::node::deserialize(file); strings_.clear(); flatten(yaml, "", strings_); - std::cout << "Locale: " << strings_.size() << " traduccions des de " << file_path << '\n'; + std::cout << "Locale: " << strings_.size() << " traduccions des de " << FULL << '\n'; } catch (const std::exception& e) { - std::cerr << "Locale: error parsejant " << file_path << ": " << e.what() << '\n'; + std::cerr << "Locale: error parsejant " << FULL << ": " << e.what() << '\n'; } } diff --git a/source/core/resources/resource_helper.cpp b/source/core/resources/resource_helper.cpp index 9e7f6a5..ef3e869 100644 --- a/source/core/resources/resource_helper.cpp +++ b/source/core/resources/resource_helper.cpp @@ -2,20 +2,41 @@ #include #include +#include namespace Resource::Helper { + namespace { + + std::string g_data_root; + + } // namespace + + void setDataRoot(const std::string& absolute_path) { + g_data_root = absolute_path; + if (!g_data_root.empty() && g_data_root.back() != '/') { + g_data_root += '/'; + } + } + + auto resolve(const std::string& filepath) -> std::string { + if (filepath.empty() || filepath.front() == '/') { return filepath; } + if (g_data_root.empty()) { return filepath; } + return g_data_root + filepath; + } + auto loadFile(const std::string& filepath) -> std::vector { - std::ifstream file(filepath, std::ios::binary | std::ios::ate); + const auto FULL = resolve(filepath); + std::ifstream file(FULL, std::ios::binary | std::ios::ate); if (!file) { - std::cerr << "Resource::Helper: cannot open " << filepath << '\n'; + std::cerr << "Resource::Helper: cannot open " << FULL << '\n'; return {}; } std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); std::vector data(static_cast(size)); if (!file.read(reinterpret_cast(data.data()), size)) { - std::cerr << "Resource::Helper: failed to read " << filepath << '\n'; + std::cerr << "Resource::Helper: failed to read " << FULL << '\n'; return {}; } return data; diff --git a/source/core/resources/resource_helper.hpp b/source/core/resources/resource_helper.hpp index 6a3bf69..2ddc4f2 100644 --- a/source/core/resources/resource_helper.hpp +++ b/source/core/resources/resource_helper.hpp @@ -6,7 +6,23 @@ namespace Resource::Helper { + // Estableix el prefix absolut des d'on es resolen totes les rutes + // relatives passades a `loadFile`/`resolve`. Pensat per a cridar-se una + // sola vegada al primer pas del Director amb `SDL_GetBasePath()`: així el + // binari troba `data/` sense dependre del CWD des d'on s'ha llançat (és + // obligatori per a .app de macOS i útil a Linux per no haver d'estar + // dins de `build/`). + void setDataRoot(const std::string& absolute_path); + + // Resol una ruta relativa contra el data root. Si la ruta ja és absoluta + // (comença per '/') la retorna sense modificar — així els fitxers de + // config (system_folder_ + "config.yaml") segueixen funcionant sense + // canvis al call site. + [[nodiscard]] auto resolve(const std::string& filepath) -> std::string; + // Carga un fitxer del filesystem (a aquesta fase encara no hi ha pack). + // Les rutes relatives es resolen via `resolve()`; les absolutes s'obrin + // directament. auto loadFile(const std::string& filepath) -> std::vector; } // namespace Resource::Helper diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index dd6ebc3..03cdc18 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -19,6 +19,7 @@ #include "core/rendering/surface.hpp" #include "core/rendering/text.hpp" #include "core/resources/resource_cache.hpp" +#include "core/resources/resource_helper.hpp" #include "core/scene/scene.hpp" #include "core/system/event_buffer.hpp" #include "game/debug_config.hpp" @@ -35,6 +36,15 @@ Director::Director() { std::fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); } + // Rutes dels recursos relatives a l'executable, no al CWD. SDL_GetBasePath + // retorna la carpeta del binari al desplegament portable i Contents/ + // Resources/ dins d'un .app a macOS. Ha de fer-se abans de qualsevol + // loader que passi per Resource::Helper::resolve (Locale, KeyConfig, + // Resource::Cache, PaletteLoader, etc.). + if (const char* base = SDL_GetBasePath()) { + Resource::Helper::setDataRoot(base); + } + // Prepara la carpeta de sistema (~/.config/jailgames/aee_2026/) i carrega // les opcions; si no existeix config.yaml el crea amb els defaults. createSystemFolder(); diff --git a/source/game/entities/entity_loader.cpp b/source/game/entities/entity_loader.cpp index 8bdfa59..4ff809d 100644 --- a/source/game/entities/entity_loader.cpp +++ b/source/game/entities/entity_loader.cpp @@ -4,6 +4,7 @@ #include #include +#include "core/resources/resource_helper.hpp" #include "game/entities/entity.hpp" #include "game/entities/entity_factory.hpp" @@ -30,10 +31,10 @@ namespace EntityLoader { auto it = cache.find(name); if (it != cache.end()) { return it->second; } - const std::string PATH = templatePath(name); - std::ifstream file(PATH); + const std::string FULL = Resource::Helper::resolve(templatePath(name)); + std::ifstream file(FULL); if (!file.is_open()) { - throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + PATH); + throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + FULL); } fkyaml::node node = fkyaml::node::deserialize(file); cache.emplace(name, node); @@ -41,9 +42,10 @@ namespace EntityLoader { } auto readSpawns(const std::string& level_yaml_path) -> std::vector { - std::ifstream file(level_yaml_path); + const auto FULL = Resource::Helper::resolve(level_yaml_path); + std::ifstream file(FULL); if (!file.is_open()) { - throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + level_yaml_path); + throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + FULL); } fkyaml::node root = fkyaml::node::deserialize(file); std::vector spawns; diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index 4fb5767..d06fe35 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -9,6 +9,7 @@ #include #include "core/resources/resource_cache.hpp" +#include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" #include "game/entities/event_bus.hpp" #include "utils/defaults.hpp" @@ -21,9 +22,10 @@ namespace { } // namespace void Player::loadFrom(const std::string& yaml_path) { - std::ifstream file(yaml_path); + const auto FULL = Resource::Helper::resolve(yaml_path); + std::ifstream file(FULL); if (!file.is_open()) { - throw std::runtime_error("Player: no s'ha pogut obrir " + yaml_path); + throw std::runtime_error("Player: no s'ha pogut obrir " + FULL); } auto yaml = fkyaml::node::deserialize(file); @@ -31,7 +33,7 @@ void Player::loadFrom(const std::string& yaml_path) { sprite_h = yaml["frameHeight"].get_value(); if (!yaml.contains("collision")) { - throw std::runtime_error("Player: manca bloc `collision:` a " + yaml_path); + throw std::runtime_error("Player: manca bloc `collision:` a " + FULL); } const auto& c = yaml["collision"]; collision_size = c["size"].get_value(); diff --git a/source/game/map/map.cpp b/source/game/map/map.cpp index a3abf1b..fc48bf9 100644 --- a/source/game/map/map.cpp +++ b/source/game/map/map.cpp @@ -4,6 +4,7 @@ #include #include +#include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" namespace { @@ -21,13 +22,14 @@ namespace { namespace Map { void loadFrom(const std::string& yaml_path) { - std::ifstream file(yaml_path); + const auto FULL = Resource::Helper::resolve(yaml_path); + std::ifstream file(FULL); if (!file.is_open()) { - throw std::runtime_error("Map: no s'ha pogut obrir " + yaml_path); + throw std::runtime_error("Map: no s'ha pogut obrir " + FULL); } auto yaml = fkyaml::node::deserialize(file); if (!yaml.contains("geometry")) { - throw std::runtime_error("Map: manca bloc `geometry:` a " + yaml_path); + throw std::runtime_error("Map: manca bloc `geometry:` a " + FULL); } const auto& g = yaml["geometry"]; @@ -76,7 +78,7 @@ namespace Map { std::cout << "Map: geometria " << grid_cols << "x" << grid_rows << " sarcs " << sarc_w << "x" << sarc_h << " pitch " << pitch_x << "x" << pitch_y - << " des de " << yaml_path << '\n'; + << " des de " << FULL << '\n'; } auto getSarcRect(int col, int row) -> SDL_FRect { diff --git a/source/game/map/tilemap.cpp b/source/game/map/tilemap.cpp index 4df152f..7cdf5a8 100644 --- a/source/game/map/tilemap.cpp +++ b/source/game/map/tilemap.cpp @@ -12,6 +12,7 @@ #include "core/rendering/screen.hpp" #include "core/rendering/surface.hpp" #include "core/resources/resource_cache.hpp" +#include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" namespace { @@ -67,9 +68,10 @@ namespace { } // namespace Tilemap::Tilemap(const std::string& yaml_path) { - std::ifstream file(yaml_path); + const auto FULL = Resource::Helper::resolve(yaml_path); + std::ifstream file(FULL); if (!file.is_open()) { - throw std::runtime_error("Tilemap: no s'ha pogut obrir " + yaml_path); + throw std::runtime_error("Tilemap: no s'ha pogut obrir " + FULL); } auto yaml = fkyaml::node::deserialize(file); @@ -111,7 +113,7 @@ Tilemap::Tilemap(const std::string& yaml_path) { } std::cout << "Tilemap: " << COLS << "x" << ROWS << " tiles (" - << width_px_ << "x" << height_px_ << " px) des de " << yaml_path << '\n'; + << width_px_ << "x" << height_px_ << " px) des de " << FULL << '\n'; } void Tilemap::render() const {