134 lines
4.6 KiB
C++
134 lines
4.6 KiB
C++
#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;
|
|
}
|