Revert "skins: SkinManager + hot-swap (F7), classic/nes a data/skins/"

This reverts commit ebfcad6f22.
This commit is contained in:
2026-05-18 16:39:59 +02:00
parent ebfcad6f22
commit a8c0386355
125 changed files with 76 additions and 962 deletions
-25
View File
@@ -6,7 +6,6 @@
#include <iostream> // for basic_ostream, operator<<, cout, endl
#include "core/resources/resource_helper.h"
#include "core/resources/skin_manager.hpp"
// Instancia única
Asset *Asset::instance = nullptr;
@@ -42,30 +41,6 @@ void Asset::add(const std::string &file, Type type, bool required, bool absolute
longest_name_ = SDL_max(longest_name_, FILE_NAME.size());
}
// Afegeix un asset gfx skin-aware: el path complet es composa via
// SkinManager (que ja inclou el PREFIX de plataforma) i guardem el
// basename per a poder-lo recomposar quan canviï la skin activa.
void Asset::addSkinAware(const std::string &basename, Type type, bool required) {
Item temp;
temp.file = executable_path_ + SkinManager::get()->gfxPath(basename);
temp.type = type;
temp.required = required;
temp.skin_basename = basename;
file_list_.push_back(temp);
longest_name_ = SDL_max(longest_name_, basename.size());
}
// Recomposa els paths dels items skin-aware amb l'skin actual del
// SkinManager. Cridat per Resource::reloadForSkin després d'un canvi.
void Asset::onSkinChanged() {
for (auto &it : file_list_) {
if (!it.skin_basename.empty()) {
it.file = executable_path_ + SkinManager::get()->gfxPath(it.skin_basename);
}
}
}
// Devuelve el fichero de un elemento de la lista a partir de una cadena
auto Asset::get(const std::string &text) -> std::string {
for (const auto &f : file_list_) {
+3 -6
View File
@@ -23,10 +23,9 @@ class Asset {
// Estructura para definir un item
struct Item {
std::string file; // Ruta del fichero desde la raiz del directorio
Type type; // Indica el tipo de recurso
bool required; // Indica si es un fichero que debe de existir
std::string skin_basename; // No buit ⇒ item skin-aware (recomposable via SkinManager)
std::string file; // Ruta del fichero desde la raiz del directorio
Type type; // Indica el tipo de recurso
bool required; // Indica si es un fichero que debe de existir
};
// Singleton API
@@ -35,8 +34,6 @@ class Asset {
static auto get() -> Asset *; // Obtiene el puntero a la instancia
void add(const std::string &file, Type type, bool required = true, bool absolute = false); // Añade un elemento a la lista
void addSkinAware(const std::string &basename, Type type, bool required = true); // Afegeix un asset gfx que viu sota la skin activa (recomposable)
void onSkinChanged(); // Recomposa el path dels items skin-aware amb la skin actual
auto get(const std::string &text) -> std::string; // Devuelve un elemento de la lista a partir de una cadena
[[nodiscard]] auto getAll() const -> const std::vector<Item> & { return file_list_; } // Devuelve toda la lista de items registrados
auto check() -> bool; // Comprueba que existen todos los elementos
-40
View File
@@ -8,8 +8,6 @@
#include "core/rendering/texture.h"
#include "core/resources/asset.h"
#include "core/resources/resource_helper.h"
#include "core/resources/skin_manager.hpp"
#include "game/options.hpp"
#include "game/ui/menu.h"
// Nota: Asset::get() e Input::get() se consultan en preloadAll y al construir
@@ -265,41 +263,3 @@ auto Resource::getMenu(const std::string &name) -> Menu * {
}
return it->second;
}
void Resource::reloadForSkin(const std::string &skin_id) {
if (SkinManager::get() == nullptr || !SkinManager::get()->exists(skin_id)) {
return;
}
SkinManager::get()->setCurrent(skin_id);
Asset::get()->onSkinChanged();
// Recarrega cada textura cachejada des del nou path mantenint pointer
// identity perquè els Sprites no s'invaliden.
for (auto &[bname, tex] : textures_) {
const std::string NEW_PATH = Asset::get()->get(bname);
if (!NEW_PATH.empty()) {
tex->reLoadFromPath(NEW_PATH);
}
}
// Re-parseja els fitxers .ani de la nova skin.
for (auto &[bname, lines] : animation_lines_) {
const std::string NEW_PATH = Asset::get()->get(bname);
if (NEW_PATH.empty()) { continue; }
auto bytes = ResourceHelper::loadFile(NEW_PATH);
if (bytes.empty()) { continue; }
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
std::stringstream ss(content);
std::vector<std::string> new_lines;
std::string line;
while (std::getline(ss, line)) {
if (!line.empty() && line.back() == '\r') { line.pop_back(); }
new_lines.push_back(line);
}
lines = std::move(new_lines);
}
Options::settings.skin = skin_id;
Options::saveToFile();
}
-5
View File
@@ -31,11 +31,6 @@ class Resource {
auto getMenu(const std::string &name) -> Menu *; // name sin extensión: "title", "options", ...
auto getDemoBytes() const -> const std::vector<uint8_t> & { return demo_bytes_; }
// Recarrega tots els recursos skin-aware (textures + animacions) per a
// l'skin donada. Manté pointer identity dels Texture* perquè els Sprites
// existents continuïn vàlids.
void reloadForSkin(const std::string &skin_id);
private:
explicit Resource(SDL_Renderer *renderer);
~Resource();
-133
View File
@@ -1,133 +0,0 @@
#include "core/resources/skin_manager.hpp"
#include <algorithm>
#include <iostream>
#include <utility>
#include "core/resources/resource_helper.h"
#include "core/resources/resource_loader.h"
#include "external/fkyaml_node.hpp"
SkinManager *SkinManager::instance = nullptr;
void SkinManager::init(const std::string &executable_path, const std::string &asset_prefix) {
if (instance == nullptr) {
instance = new SkinManager(executable_path, asset_prefix);
instance->scan();
}
}
void SkinManager::destroy() {
delete instance;
instance = nullptr;
}
auto SkinManager::get() -> SkinManager * {
return instance;
}
SkinManager::SkinManager(std::string executable_path, std::string asset_prefix)
: executable_path_(std::move(executable_path)),
asset_prefix_(std::move(asset_prefix)) {
}
void SkinManager::scan() {
skins_.clear();
// ResourceLoader enumera tant entries del pack com fitxers de
// `data/` en mode fallback. Les claus retornades són relatives a
// `data/` (ex: "skins/classic/skin.yaml"). Buscar manifests
// amb aquest patró és més robust que `std::filesystem` perquè
// funciona igual a release (pack) que a dev (filesystem).
const auto ALL = ResourceLoader::getInstance().getAvailableResources();
const std::string PREFIX = "skins/";
const std::string SUFFIX = "/skin.yaml";
for (const auto &key : ALL) {
if (!key.starts_with(PREFIX) || !key.ends_with(SUFFIX)) { continue; }
const std::string ID = key.substr(PREFIX.size(), key.size() - PREFIX.size() - SUFFIX.size());
if (ID.find('/') != std::string::npos) { continue; } // només top-level dins skins/
// ResourceHelper espera un path que contingui "data/" — el construïm
// amb la convenció habitual del projecte.
const std::string FULL_PATH = executable_path_ + asset_prefix_ + "/data/" + key;
auto bytes = ResourceHelper::loadFile(FULL_PATH);
if (bytes.empty()) {
std::cerr << "SkinManager: cannot read manifest " << FULL_PATH << '\n';
continue;
}
SkinInfo info;
info.id = ID;
info.display_name = ID;
info.gfx_dir = "gfx";
try {
const std::string CONTENT(reinterpret_cast<const char *>(bytes.data()), bytes.size());
auto yaml = fkyaml::node::deserialize(CONTENT);
if (yaml.contains("display_name")) {
info.display_name = yaml["display_name"].get_value<std::string>();
}
if (yaml.contains("gfx_dir")) {
info.gfx_dir = yaml["gfx_dir"].get_value<std::string>();
}
} catch (const fkyaml::exception &e) {
std::cerr << "SkinManager: bad manifest " << FULL_PATH << ": " << e.what() << '\n';
continue;
}
skins_.push_back(std::move(info));
}
// Ordre alfabètic estable: garanteix que next/prev són deterministes.
std::ranges::sort(skins_, [](const SkinInfo &a, const SkinInfo &b) { return a.id < b.id; });
}
auto SkinManager::exists(const std::string &id) const -> bool {
return std::ranges::any_of(skins_, [&](const SkinInfo &s) { return s.id == id; });
}
auto SkinManager::currentDisplayName() const -> std::string {
return displayNameOf(current_id_);
}
auto SkinManager::displayNameOf(const std::string &id) const -> std::string {
const auto IT = std::ranges::find_if(skins_, [&](const SkinInfo &s) { return s.id == id; });
return (IT != skins_.end()) ? IT->display_name : id;
}
void SkinManager::setCurrent(const std::string &id) {
if (exists(id)) {
current_id_ = id;
}
}
auto SkinManager::gfxDirOf(const std::string &id) const -> std::string {
const auto IT = std::ranges::find_if(skins_, [&](const SkinInfo &s) { return s.id == id; });
return (IT != skins_.end()) ? IT->gfx_dir : std::string("gfx");
}
auto SkinManager::gfxPath(const std::string &basename) const -> std::string {
return asset_prefix_ + "/data/skins/" + current_id_ + "/" + gfxDirOf(current_id_) + "/" + basename;
}
auto SkinManager::nextSkinId() const -> std::string {
if (skins_.empty()) { return current_id_; }
for (size_t i = 0; i < skins_.size(); ++i) {
if (skins_[i].id == current_id_) {
return skins_[(i + 1) % skins_.size()].id;
}
}
return skins_.front().id;
}
auto SkinManager::prevSkinId() const -> std::string {
if (skins_.empty()) { return current_id_; }
for (size_t i = 0; i < skins_.size(); ++i) {
if (skins_[i].id == current_id_) {
return skins_[(i + skins_.size() - 1) % skins_.size()].id;
}
}
return skins_.front().id;
}
-66
View File
@@ -1,66 +0,0 @@
#pragma once
#include <string>
#include <vector>
// Gestor de skins. Una "skin" és un set complet de gràfics (PNGs + .ani)
// emmagatzemat sota `data/skins/<id>/gfx/`. El SkinManager:
//
// * descobreix les skins disponibles escanejant `data/skins/*/skin.yaml`,
// * recorda quina skin està activa (`current_id_`),
// * composa els paths perquè Asset/Resource sàpiguen on llegir.
//
// L'arrencada típica és: Director crida `init(executable_path, asset_prefix)`
// (escaneja manifests) i acte seguit `setCurrent(Options::settings.skin)`.
// Després registra els assets via `Asset::addSkinAware("balloon1.png", ...)`
// que utilitza internament `gfxPath()` del SkinManager.
//
// Per al canvi en calent, Resource crida `setCurrent(new_id)`, després
// `Asset::onSkinChanged()` per a actualitzar paths registrats, i recarrega
// cada Texture via `reLoadFromPath`.
class SkinManager {
public:
struct SkinInfo {
std::string id; // Carpeta dins data/skins/ (ex: "classic")
std::string display_name; // Etiqueta visible a la UI (ex: "Classic")
std::string gfx_dir; // Subcarpeta amb els gfx (default: "gfx")
};
// executable_path: acabat amb '/' (típicament SDL_GetBasePath()).
// asset_prefix: prefix relatiu per a paths registrats a Asset
// (ex: "" en Linux/Win, "/../Resources" dins el bundle de macOS).
static void init(const std::string &executable_path, const std::string &asset_prefix);
static void destroy();
static auto get() -> SkinManager *;
void scan();
[[nodiscard]] auto available() const -> const std::vector<SkinInfo> & { return skins_; }
[[nodiscard]] auto current() const -> const std::string & { return current_id_; }
[[nodiscard]] auto currentDisplayName() const -> std::string;
[[nodiscard]] auto displayNameOf(const std::string &id) const -> std::string;
[[nodiscard]] auto exists(const std::string &id) const -> bool;
void setCurrent(const std::string &id);
// Compon el path relatiu d'un gràfic dins la skin activa, amb el format
// que Asset::add espera (relatiu amb prefix de plataforma):
// "<asset_prefix>/data/skins/<id>/<gfx_dir>/<basename>"
[[nodiscard]] auto gfxPath(const std::string &basename) const -> std::string;
// Cíclic. Si només hi ha una skin, retorna la mateixa.
[[nodiscard]] auto nextSkinId() const -> std::string;
[[nodiscard]] auto prevSkinId() const -> std::string;
private:
SkinManager(std::string executable_path, std::string asset_prefix);
[[nodiscard]] auto gfxDirOf(const std::string &id) const -> std::string;
std::string executable_path_; // Acabat amb '/' (per a scan filesystem)
std::string asset_prefix_; // PREFIX per a paths registrats
std::vector<SkinInfo> skins_;
std::string current_id_{"classic"};
static SkinManager *instance;
};