reestructuració

This commit is contained in:
2026-04-14 13:26:22 +02:00
parent 4ac34b8583
commit 4429cd92c1
143 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
#include "asset.hpp"
#include <SDL3/SDL.h> // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError
#include <algorithm> // Para sort
#include <cstddef> // Para size_t
#include <exception> // Para exception
#include <filesystem> // Para exists, path
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, ifstream, istringstream, endl
#include <iostream> // Para cout
#include <sstream> // Para basic_istringstream
#include <stdexcept> // Para runtime_error
#include "resource_helper.hpp" // Para loadFile
#include "utils.hpp" // Para getFileName
// Singleton
Asset* Asset::instance = nullptr;
void Asset::init(const std::string& executable_path) {
Asset::instance = new Asset(executable_path);
}
void Asset::destroy() {
delete Asset::instance;
}
auto Asset::get() -> Asset* {
return Asset::instance;
}
// Añade un elemento al mapa (función auxiliar)
void Asset::addToMap(const std::string& file_path, Type type, bool required, bool absolute) {
std::string full_path = absolute ? file_path : executable_path_ + file_path;
std::string filename = getFileName(full_path);
// Verificar si ya existe el archivo
if (file_list_.contains(filename)) {
std::cout << "Warning: Asset '" << filename << "' already exists, overwriting" << '\n';
}
file_list_.emplace(filename, Item{std::move(full_path), type, required});
}
// Añade un elemento a la lista
void Asset::add(const std::string& file_path, Type type, bool required, bool absolute) {
addToMap(file_path, type, required, absolute);
}
// Carga recursos desde un archivo de configuración con soporte para variables
void Asset::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) {
std::ifstream file(config_file_path);
if (!file.is_open()) {
std::cout << "Error: Cannot open config file: " << config_file_path << '\n';
return;
}
std::string line;
int line_number = 0;
while (std::getline(file, line)) {
++line_number;
// Limpiar espacios en blanco al principio y final
line.erase(0, line.find_first_not_of(" \t\r"));
line.erase(line.find_last_not_of(" \t\r") + 1);
// DEBUG: mostrar línea leída (opcional, puedes comentar esta línea)
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Line %d: '%s'", line_number, line.c_str());
// Ignorar líneas vacías y comentarios
if (line.empty() || line[0] == '#' || line[0] == ';') {
continue;
}
// Dividir la línea por el separador '|'
std::vector<std::string> parts;
std::istringstream iss(line);
std::string part;
while (std::getline(iss, part, '|')) {
parts.push_back(part);
}
// Verificar que tenemos al menos tipo y ruta
if (parts.size() < 2) {
std::cout << "Warning: Malformed line " << line_number << " in config file (insufficient fields)" << '\n';
continue;
}
try {
const std::string& type_str = parts[0];
std::string path = parts[1];
// Valores por defecto
bool required = true;
bool absolute = false;
// Si hay opciones en el tercer campo, parsearlas
if (parts.size() >= 3) {
parseOptions(parts[2], required, absolute);
}
// Reemplazar variables en la ruta
path = replaceVariables(path, prefix, system_folder);
// Parsear el tipo de asset
Type type = parseAssetType(type_str);
// Añadir al mapa
addToMap(path, type, required, absolute);
} catch (const std::exception& e) {
std::cout << "Error parsing line " << line_number << " in config file: " << e.what() << '\n';
}
}
file.close();
}
// Devuelve la ruta completa a un fichero (búsqueda O(1))
auto Asset::getPath(const std::string& filename) const -> std::string {
auto it = file_list_.find(filename);
if (it != file_list_.end()) {
return it->second.file;
}
std::cout << "Warning: file " << filename << " not found" << '\n';
return "";
}
// Carga datos del archivo usando ResourceHelper
auto Asset::loadData(const std::string& filename) const -> std::vector<uint8_t> {
auto it = file_list_.find(filename);
if (it != file_list_.end()) {
return ResourceHelper::loadFile(it->second.file);
}
std::cout << "Warning: file " << filename << " not found for data loading" << '\n';
return {};
}
// Verifica si un recurso existe
auto Asset::exists(const std::string& filename) const -> bool {
return file_list_.contains(filename);
}
// Comprueba que existen todos los elementos
auto Asset::check() const -> bool {
bool success = true;
// Agrupar por tipo para mostrar organizado
std::unordered_map<Type, std::vector<const Item*>> by_type;
for (const auto& [filename, item] : file_list_) {
if (item.required) {
by_type[item.type].push_back(&item);
}
}
// Verificar por tipo
for (int type = 0; type < static_cast<int>(Type::SIZE); ++type) {
Type asset_type = static_cast<Type>(type);
if (by_type.contains(asset_type)) {
for (const auto* item : by_type[asset_type]) {
if (!checkFile(item->file)) {
success = false;
}
}
}
}
return success;
}
// Comprueba que existe un fichero
auto Asset::checkFile(const std::string& path) const -> bool {
// Construir ruta del pack usando executable_path_ (misma lógica que Director::init)
#ifdef MACOS_BUNDLE
std::string pack_path = executable_path_ + "../Resources/resources.pack";
#else
std::string pack_path = executable_path_ + "resources.pack";
#endif
bool pack_exists = std::filesystem::exists(pack_path);
if (pack_exists) {
// MODO PACK: Usar ResourceHelper (igual que la carga real)
auto data = ResourceHelper::loadFile(path);
return !data.empty();
} // MODO FILESYSTEM: Verificación directa (modo desarrollo)
std::ifstream file(path);
bool success = file.good();
file.close();
if (!success) {
std::cout << "Error: Could not open file: " << path << '\n';
}
return success;
}
// Parsea string a Type
auto Asset::parseAssetType(const std::string& type_str) -> Type {
if (type_str == "BITMAP") {
return Type::BITMAP;
}
if (type_str == "MUSIC") {
return Type::MUSIC;
}
if (type_str == "SOUND") {
return Type::SOUND;
}
if (type_str == "FONT") {
return Type::FONT;
}
if (type_str == "LANG") {
return Type::LANG;
}
if (type_str == "DATA") {
return Type::DATA;
}
if (type_str == "DEMODATA") {
return Type::DEMODATA;
}
if (type_str == "ANIMATION") {
return Type::ANIMATION;
}
if (type_str == "PALETTE") {
return Type::PALETTE;
}
throw std::runtime_error("Unknown asset type: " + type_str);
}
// Devuelve el nombre del tipo de recurso
auto Asset::getTypeName(Type type) -> std::string {
switch (type) {
case Type::BITMAP:
return "BITMAP";
case Type::MUSIC:
return "MUSIC";
case Type::SOUND:
return "SOUND";
case Type::FONT:
return "FONT";
case Type::LANG:
return "LANG";
case Type::DATA:
return "DATA";
case Type::DEMODATA:
return "DEMODATA";
case Type::ANIMATION:
return "ANIMATION";
case Type::PALETTE:
return "PALETTE";
default:
return "ERROR";
}
}
// Devuelve la lista de recursos de un tipo
auto Asset::getListByType(Type type) const -> std::vector<std::string> {
std::vector<std::string> list;
for (const auto& [filename, item] : file_list_) {
if (item.type == type) {
list.push_back(item.file);
}
}
// Ordenar alfabéticamente para garantizar orden consistente
std::ranges::sort(list);
return list;
}
// Reemplaza variables en las rutas
auto Asset::replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string {
std::string result = path;
// Reemplazar ${PREFIX}
size_t pos = 0;
while ((pos = result.find("${PREFIX}", pos)) != std::string::npos) {
result.replace(pos, 9, prefix); // 9 = longitud de "${PREFIX}"
pos += prefix.length();
}
// Reemplazar ${SYSTEM_FOLDER}
pos = 0;
while ((pos = result.find("${SYSTEM_FOLDER}", pos)) != std::string::npos) {
result.replace(pos, 16, system_folder); // 16 = longitud de "${SYSTEM_FOLDER}"
pos += system_folder.length();
}
return result;
}
// Parsea las opciones de una línea de configuración
auto Asset::parseOptions(const std::string& options, bool& required, bool& absolute) -> void {
if (options.empty()) {
return;
}
std::istringstream iss(options);
std::string option;
while (std::getline(iss, option, ',')) {
// Eliminar espacios
option.erase(0, option.find_first_not_of(" \t"));
option.erase(option.find_last_not_of(" \t") + 1);
if (option == "optional") {
required = false;
} else if (option == "absolute") {
absolute = true;
}
}
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <cstdint> // Para uint8_t
#include <string> // Para string
#include <unordered_map> // Para unordered_map
#include <utility> // Para move
#include <vector> // Para vector
// --- Clase Asset: gestor optimizado de recursos (singleton) ---
class Asset {
public:
// --- Enums ---
enum class Type : int {
BITMAP, // Imágenes
MUSIC, // Música
SOUND, // Sonidos
FONT, // Fuentes
LANG, // Idiomas
DATA, // Datos
DEMODATA, // Datos de demo
ANIMATION, // Animaciones
PALETTE, // Paletas
SIZE, // Tamaño
};
// --- Métodos de singleton ---
static void init(const std::string& executable_path);
static void destroy();
static auto get() -> Asset*;
Asset(const Asset&) = delete;
auto operator=(const Asset&) -> Asset& = delete;
// --- Métodos para la gestión de recursos ---
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
[[nodiscard]] auto getPath(const std::string& filename) const -> std::string;
[[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo
[[nodiscard]] auto check() const -> bool;
[[nodiscard]] auto getListByType(Type type) const -> std::vector<std::string>;
[[nodiscard]] auto exists(const std::string& filename) const -> bool; // Nueva función para verificar existencia
private:
// --- Estructuras privadas ---
struct Item {
std::string file; // Ruta completa del archivo
Type type; // Tipo de recurso
bool required; // Indica si el archivo es obligatorio
Item(std::string path, Type asset_type, bool is_required)
: file(std::move(path)),
type(asset_type),
required(is_required) {}
};
// --- Variables internas ---
std::unordered_map<std::string, Item> file_list_; // Mapa para búsqueda O(1)
std::string executable_path_; // Ruta del ejecutable
// --- Métodos internos ---
[[nodiscard]] auto checkFile(const std::string& path) const -> bool; // Verifica si un archivo existe
[[nodiscard]] static auto getTypeName(Type type) -> std::string; // Obtiene el nombre del tipo
[[nodiscard]] static auto parseAssetType(const std::string& type_str) -> Type; // Convierte string a tipo
void addToMap(const std::string& file_path, Type type, bool required, bool absolute); // Añade archivo al mapa
[[nodiscard]] static auto replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string; // Reemplaza variables en la ruta
static auto parseOptions(const std::string& options, bool& required, bool& absolute) -> void; // Parsea opciones
// --- Constructores y destructor privados (singleton) ---
explicit Asset(std::string executable_path) // Constructor privado
: executable_path_(std::move(executable_path)) {}
~Asset() = default; // Destructor privado
// --- Instancia singleton ---
static Asset* instance; // Instancia única de Asset
};

View File

@@ -0,0 +1,105 @@
#include "asset_integrated.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
bool AssetIntegrated::resource_pack_enabled = false;
void AssetIntegrated::initWithResourcePack(const std::string& executable_path,
const std::string& resource_pack_path) {
// Inicializar Asset normal
Asset::init(executable_path);
// Inicializar ResourceLoader
auto& loader = ResourceLoader::getInstance();
resource_pack_enabled = loader.initialize(resource_pack_path, true);
if (resource_pack_enabled) {
std::cout << "Asset system initialized with resource pack: " << resource_pack_path << '\n';
} else {
std::cout << "Asset system initialized in fallback mode (filesystem)" << '\n';
}
}
auto AssetIntegrated::loadFile(const std::string& filename) -> std::vector<uint8_t> {
if (shouldUseResourcePack(filename) && resource_pack_enabled) {
// Intentar cargar del pack de recursos
auto& loader = ResourceLoader::getInstance();
// Convertir ruta completa a ruta relativa para el pack
std::string relative_path = filename;
// Si la ruta contiene "data/", extraer la parte relativa
size_t data_pos = filename.find("data/");
if (data_pos != std::string::npos) {
relative_path = filename.substr(data_pos + 5); // +5 para saltar "data/"
}
auto data = loader.loadResource(relative_path);
if (!data.empty()) {
return data;
}
}
// Fallback: cargar del filesystem
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Error: Could not open file: " << filename << '\n';
return {};
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(file_size);
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
std::cerr << "Error: Could not read file: " << filename << '\n';
return {};
}
return data;
}
auto AssetIntegrated::fileExists(const std::string& filename) -> bool {
if (shouldUseResourcePack(filename) && resource_pack_enabled) {
auto& loader = ResourceLoader::getInstance();
// Convertir ruta completa a ruta relativa para el pack
std::string relative_path = filename;
size_t data_pos = filename.find("data/");
if (data_pos != std::string::npos) {
relative_path = filename.substr(data_pos + 5);
}
if (loader.resourceExists(relative_path)) {
return true;
}
}
// Verificar en filesystem
return std::filesystem::exists(filename);
}
auto AssetIntegrated::getSystemPath(const std::string& filename) -> std::string {
// Los archivos de sistema/config siempre van al filesystem
return filename;
}
auto AssetIntegrated::shouldUseResourcePack(const std::string& filepath) -> bool {
// Los archivos que NO van al pack:
// - Archivos de config/ (ahora están fuera de data/)
// - Archivos con absolute=true en assets.txt
// - Archivos de sistema (${SYSTEM_FOLDER})
if (filepath.find("/config/") != std::string::npos ||
filepath.starts_with("config/")) {
return false;
}
if (filepath.find("/data/") != std::string::npos ||
filepath.starts_with("data/")) {
return true;
}
return false;
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include <memory>
#include "asset.hpp"
#include "resource_loader.hpp"
// Extensión de Asset que integra ResourceLoader
class AssetIntegrated : public Asset {
public:
// Inicializa Asset con ResourceLoader
static void initWithResourcePack(const std::string& executable_path,
const std::string& resource_pack_path = "resources.pack");
// Carga un archivo usando ResourceLoader como primera opción
static auto loadFile(const std::string& filename) -> std::vector<uint8_t>;
// Verifica si un archivo existe (pack o filesystem)
static auto fileExists(const std::string& filename) -> bool;
// Obtiene la ruta completa para archivos del sistema/config
static auto getSystemPath(const std::string& filename) -> std::string;
private:
static bool resource_pack_enabled;
// Determina si un archivo debe cargarse del pack o del filesystem
static auto shouldUseResourcePack(const std::string& filepath) -> bool;
};

View File

@@ -0,0 +1,849 @@
#include "resource.hpp"
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_LogError, SDL_SetRenderDrawColor, SDL_EventType, SDL_PollEvent, SDL_RenderFillRect, SDL_RenderRect, SDLK_ESCAPE, SDL_Event
#include <array> // Para array
#include <cstdlib> // Para exit
#include <exception> // Para exception
#include <filesystem> // Para exists, path, remove
#include <fstream> // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream
#include <iostream> // Para std::cout
#include <ranges> // Para __find_if_fn, find_if, __find_fn, find
#include <stdexcept> // Para runtime_error
#include <utility> // Para move
#include "asset.hpp" // Para Asset
#include "color.hpp" // Para Color, NO_COLOR_MOD
#include "external/jail_audio.hpp" // Para JA_LoadMusic, JA_LoadSound, JA_DeleteMusic, JA_DeleteSound
#include "lang.hpp" // Para getText
#include "param.hpp" // Para Param, param, ParamPlayer, ParamResource, ParamGame
#include "resource_helper.hpp" // Para loadFile
#include "screen.hpp" // Para Screen
#include "text.hpp" // Para Text
#include "utils.hpp" // Para getFileName
#include "version.h" // Para APP_NAME, GIT_HASH
struct JA_Music_t; // lines 11-11
struct JA_Sound_t; // lines 12-12
// Helper para cargar archivos de audio desde pack o filesystem en memoria
namespace {
struct AudioData {
std::vector<uint8_t> data;
std::string filepath;
};
auto loadAudioData(const std::string& file_path) -> AudioData {
auto resource_data = ResourceHelper::loadFile(file_path);
return AudioData{.data = std::move(resource_data), .filepath = file_path};
}
} // namespace
// Declaraciones de funciones que necesitas implementar en otros archivos
// Singleton
Resource* Resource::instance = nullptr;
// Inicializa la instancia única del singleton con modo de carga
void Resource::init(LoadingMode mode) {
Resource::instance = new Resource(mode);
}
// Libera la instancia
void Resource::destroy() {
delete Resource::instance;
Resource::instance = nullptr;
}
// Obtiene la instancia
auto Resource::get() -> Resource* { return Resource::instance; }
// Constructor con modo de carga
Resource::Resource(LoadingMode mode)
: loading_mode_(mode),
loading_text_(nullptr) {
Screen::get()->show();
if (loading_mode_ == LoadingMode::PRELOAD) {
loading_text_ = Screen::get()->getText();
load();
} else {
// En modo lazy, cargamos lo mínimo indispensable
initResourceLists();
loadEssentialResources();
}
}
// Destructor
Resource::~Resource() {
clear();
}
// Carga los recursos esenciales que siempre se necesitan (modo lazy)
void Resource::loadEssentialResources() {
// Cargar recursos de texto básicos que se usan para crear texturas
loadTextFilesQuiet(); // <- VERSIÓN SILENCIOSA
loadEssentialTextures(); // Ya es silenciosa
createText(); // Crear objetos de texto
createTextTextures(); // Crear texturas generadas (game_text_xxx)
createPlayerTextures(); // Crea las texturas de jugadores con todas sus variantes de paleta
}
// Carga los ficheros de texto del juego (versión silenciosa)
void Resource::loadTextFilesQuiet() {
auto list = Asset::get()->getListByType(Asset::Type::FONT);
for (const auto& l : list) {
auto name = getFileName(l);
// Buscar en nuestra lista y cargar directamente
auto it = std::ranges::find_if(text_files_, [&name](const auto& t) -> auto { return t.name == name; });
if (it != text_files_.end()) {
it->text_file = Text::loadFile(l);
}
}
}
// Carga solo las texturas esenciales (fuentes)
void Resource::loadEssentialTextures() {
const std::vector<std::string> ESSENTIAL_TEXTURES = {
"04b_25.png",
"04b_25_2x.png",
"04b_25_metal.png",
"04b_25_grey.png",
"04b_25_flat.png",
"04b_25_reversed.png",
"04b_25_flat_2x.png",
"04b_25_reversed_2x.png",
"8bithud.png",
"aseprite.png",
"smb2.png",
"smb2_grad.png"};
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
for (const auto& file : texture_list) {
auto name = getFileName(file);
// Solo cargar texturas esenciales
if (std::ranges::find(ESSENTIAL_TEXTURES, name) != ESSENTIAL_TEXTURES.end()) {
// Buscar en nuestra lista y cargar
auto it = std::ranges::find_if(textures_, [&name](const auto& t) -> auto { return t.name == name; });
if (it != textures_.end()) {
it->texture = std::make_shared<Texture>(Screen::get()->getRenderer(), file);
}
}
}
}
// Inicializa las listas de recursos sin cargar el contenido (modo lazy)
void Resource::initResourceLists() {
// Inicializa lista de sonidos
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
sounds_.clear();
for (const auto& file : sound_list) {
sounds_.emplace_back(getFileName(file));
}
// Inicializa lista de músicas
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
musics_.clear();
for (const auto& file : music_list) {
musics_.emplace_back(getFileName(file));
}
// Inicializa lista de texturas
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
textures_.clear();
for (const auto& file : texture_list) {
textures_.emplace_back(getFileName(file));
}
// Inicializa lista de ficheros de texto
auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
text_files_.clear();
for (const auto& file : text_file_list) {
text_files_.emplace_back(getFileName(file));
}
// Inicializa lista de animaciones
auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
animations_.clear();
for (const auto& file : animation_list) {
animations_.emplace_back(getFileName(file));
}
// Los demos se cargan directamente sin mostrar progreso (son pocos y pequeños)
loadDemoDataQuiet();
// Inicializa lista de objetos de texto (sin cargar el contenido)
const std::vector<std::string> TEXT_OBJECTS = {
"04b_25",
"04b_25_2x",
"04b_25_metal",
"04b_25_grey",
"04b_25_flat",
"04b_25_reversed",
"04b_25_flat_2x",
"04b_25_reversed_2x",
"8bithud",
"aseprite",
"smb2",
"smb2_grad"};
texts_.clear();
for (const auto& text_name : TEXT_OBJECTS) {
texts_.emplace_back(text_name); // Constructor con nullptr por defecto
}
}
// Obtiene el sonido a partir de un nombre (con carga perezosa)
auto Resource::getSound(const std::string& name) -> JA_Sound_t* {
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> auto { return s.name == name; });
if (it != sounds_.end()) {
// Si está en modo lazy y no se ha cargado aún, lo carga ahora
if (loading_mode_ == LoadingMode::LAZY_LOAD && it->sound == nullptr) {
it->sound = loadSoundLazy(name);
}
return it->sound;
}
std::cout << "Error: Sonido no encontrado " << name << '\n';
throw std::runtime_error("Sonido no encontrado: " + name);
}
// Obtiene la música a partir de un nombre (con carga perezosa)
auto Resource::getMusic(const std::string& name) -> JA_Music_t* {
auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> auto { return m.name == name; });
if (it != musics_.end()) {
// Si está en modo lazy y no se ha cargado aún, lo carga ahora
if (loading_mode_ == LoadingMode::LAZY_LOAD && it->music == nullptr) {
it->music = loadMusicLazy(name);
}
return it->music;
}
std::cout << "Error: Música no encontrada " << name << '\n';
throw std::runtime_error("Música no encontrada: " + name);
}
// Obtiene la textura a partir de un nombre (con carga perezosa)
auto Resource::getTexture(const std::string& name) -> std::shared_ptr<Texture> {
auto it = std::ranges::find_if(textures_, [&name](const auto& t) -> auto { return t.name == name; });
if (it != textures_.end()) {
// Si está en modo lazy y no se ha cargado aún, lo carga ahora
if (loading_mode_ == LoadingMode::LAZY_LOAD && it->texture == nullptr) {
it->texture = loadTextureLazy(name);
}
return it->texture;
}
std::cout << "Error: Imagen no encontrada " << name << '\n';
throw std::runtime_error("Imagen no encontrada: " + name);
}
// Obtiene el fichero de texto a partir de un nombre (con carga perezosa)
auto Resource::getTextFile(const std::string& name) -> std::shared_ptr<Text::File> {
auto it = std::ranges::find_if(text_files_, [&name](const auto& t) -> auto { return t.name == name; });
if (it != text_files_.end()) {
// Si está en modo lazy y no se ha cargado aún, lo carga ahora
if (loading_mode_ == LoadingMode::LAZY_LOAD && it->text_file == nullptr) {
it->text_file = loadTextFileLazy(name);
}
return it->text_file;
}
std::cout << "Error: TextFile no encontrado " << name << '\n';
throw std::runtime_error("TextFile no encontrado: " + name);
}
// Obtiene el objeto de texto a partir de un nombre (con carga perezosa)
auto Resource::getText(const std::string& name) -> std::shared_ptr<Text> {
auto it = std::ranges::find_if(texts_, [&name](const auto& t) -> auto { return t.name == name; });
if (it != texts_.end()) {
// Si está en modo lazy y no se ha cargado aún, lo carga ahora
if (loading_mode_ == LoadingMode::LAZY_LOAD && it->text == nullptr) {
it->text = loadTextLazy(name);
}
return it->text;
}
std::cout << "Error: Text no encontrado " << name << '\n';
throw std::runtime_error("Text no encontrado: " + name);
}
// Obtiene la animación a partir de un nombre (con carga perezosa)
auto Resource::getAnimation(const std::string& name) -> AnimationsFileBuffer& {
auto it = std::ranges::find_if(animations_, [&name](const auto& a) -> auto { return a.name == name; });
if (it != animations_.end()) {
// Si está en modo lazy y no se ha cargado aún (vector vacío), lo carga ahora
if (loading_mode_ == LoadingMode::LAZY_LOAD && it->animation.empty()) {
it->animation = loadAnimationLazy(name);
}
return it->animation;
}
std::cout << "Error: Animación no encontrada " << name << '\n';
throw std::runtime_error("Animación no encontrada: " + name);
}
// Obtiene el fichero con los datos para el modo demostración a partir de un índice
auto Resource::getDemoData(int index) -> DemoData& {
if (index < 0 || std::cmp_greater_equal(index, demos_.size())) {
std::cout << "Index " << index << " out of range for demo data (size: " << static_cast<int>(demos_.size()) << ")" << '\n';
static DemoData empty_demo_;
return empty_demo_;
}
return demos_.at(index);
}
// --- Métodos de carga perezosa ---
auto Resource::loadSoundLazy(const std::string& name) -> JA_Sound_t* {
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
for (const auto& file : sound_list) {
if (getFileName(file) == name) {
auto audio_data = loadAudioData(file);
if (!audio_data.data.empty()) {
return JA_LoadSound(audio_data.data.data(), audio_data.data.size());
}
// Fallback a cargar desde disco si no está en pack
return JA_LoadSound(file.c_str());
}
}
return nullptr;
}
auto Resource::loadMusicLazy(const std::string& name) -> JA_Music_t* {
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
for (const auto& file : music_list) {
if (getFileName(file) == name) {
auto audio_data = loadAudioData(file);
if (!audio_data.data.empty()) {
return JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
}
// Fallback a cargar desde disco si no está en pack
return JA_LoadMusic(file.c_str());
}
}
return nullptr;
}
auto Resource::loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture> {
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
for (const auto& file : texture_list) {
if (getFileName(file) == name) {
return std::make_shared<Texture>(Screen::get()->getRenderer(), file);
}
}
return nullptr;
}
auto Resource::loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File> {
auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
for (const auto& file : text_file_list) {
if (getFileName(file) == name) {
return Text::loadFile(file);
}
}
return nullptr;
}
auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
// Mapeo de objetos de texto a sus recursos
struct TextMapping {
std::string key;
std::string texture_file;
std::string text_file;
};
const std::vector<TextMapping> TEXT_MAPPINGS = {
{.key = "04b_25", .texture_file = "04b_25.png", .text_file = "04b_25.txt"},
{.key = "04b_25_2x", .texture_file = "04b_25_2x.png", .text_file = "04b_25_2x.txt"},
{.key = "04b_25_metal", .texture_file = "04b_25_metal.png", .text_file = "04b_25.txt"},
{.key = "04b_25_grey", .texture_file = "04b_25_grey.png", .text_file = "04b_25.txt"},
{.key = "04b_25_flat", .texture_file = "04b_25_flat.png", .text_file = "04b_25.txt"},
{.key = "04b_25_reversed", .texture_file = "04b_25_reversed.png", .text_file = "04b_25.txt"},
{.key = "04b_25_flat_2x", .texture_file = "04b_25_flat_2x.png", .text_file = "04b_25_2x.txt"},
{.key = "04b_25_reversed_2x", .texture_file = "04b_25_reversed_2x.png", .text_file = "04b_25_2x.txt"},
{.key = "8bithud", .texture_file = "8bithud.png", .text_file = "8bithud.txt"},
{.key = "aseprite", .texture_file = "aseprite.png", .text_file = "aseprite.txt"},
{.key = "smb2", .texture_file = "smb2.png", .text_file = "smb2.txt"},
{.key = "smb2_grad", .texture_file = "smb2_grad.png", .text_file = "smb2.txt"}};
for (const auto& mapping : TEXT_MAPPINGS) {
if (mapping.key == name) {
// Cargar las dependencias automáticamente
auto texture = getTexture(mapping.texture_file); // Esto cargará la textura si no está cargada
auto text_file = getTextFile(mapping.text_file); // Esto cargará el archivo de texto si no está cargado
if (texture && text_file) {
return std::make_shared<Text>(texture, text_file);
}
}
}
return nullptr;
}
auto Resource::loadAnimationLazy(const std::string& name) -> AnimationsFileBuffer {
auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
for (const auto& file : animation_list) {
if (getFileName(file) == name) {
return loadAnimationsFromFile(file);
}
}
// Si no se encuentra, retorna vector vacío
return AnimationsFileBuffer{};
}
// Vacia todos los vectores de recursos
void Resource::clear() {
clearSounds();
clearMusics();
textures_.clear();
text_files_.clear();
texts_.clear();
animations_.clear();
demos_.clear();
}
// Carga todos los recursos del juego y muestra el progreso de carga
void Resource::load() {
// Prepara la gestión del progreso de carga
calculateTotalResources();
initProgressBar();
// Muerstra la ventana y desactiva el sincronismo vertical
auto* screen = Screen::get();
auto vsync = Screen::getVSync();
screen->setVSync(false);
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** LOADING RESOURCES");
loadSounds(); // Carga sonidos
loadMusics(); // Carga músicas
loadTextures(); // Carga texturas
loadTextFiles(); // Carga ficheros de texto
loadAnimations(); // Carga animaciones
loadDemoData(); // Carga datos de demo
createText(); // Crea objetos de texto
createTextTextures(); // Crea texturas a partir de texto
createPlayerTextures(); // Crea las texturas de jugadores con todas sus variantes de paleta
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** RESOURCES LOADED");
// Restablece el sincronismo vertical a su valor original
screen->setVSync(vsync);
}
// Recarga todos los recursos (limpia y vuelve a cargar)
void Resource::reload() {
clear();
if (loading_mode_ == LoadingMode::PRELOAD) {
load();
} else {
initResourceLists();
}
}
// Carga los sonidos del juego
void Resource::loadSounds() {
auto list = Asset::get()->getListByType(Asset::Type::SOUND);
sounds_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
auto audio_data = loadAudioData(l);
JA_Sound_t* sound = nullptr;
if (!audio_data.data.empty()) {
sound = JA_LoadSound(audio_data.data.data(), audio_data.data.size());
} else {
sound = JA_LoadSound(l.c_str());
}
if (sound == nullptr) {
std::cout << "Sound load failed: " << name << '\n';
}
sounds_.emplace_back(name, sound);
}
}
// Carga las músicas del juego
void Resource::loadMusics() {
auto list = Asset::get()->getListByType(Asset::Type::MUSIC);
musics_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
auto audio_data = loadAudioData(l);
JA_Music_t* music = nullptr;
if (!audio_data.data.empty()) {
music = JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
} else {
music = JA_LoadMusic(l.c_str());
}
if (music == nullptr) {
std::cout << "Music load failed: " << name << '\n';
}
musics_.emplace_back(name, music);
}
}
// Carga las texturas del juego
void Resource::loadTextures() {
auto list = Asset::get()->getListByType(Asset::Type::BITMAP);
textures_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
textures_.emplace_back(name, std::make_shared<Texture>(Screen::get()->getRenderer(), l));
}
}
// Carga los ficheros de texto del juego
void Resource::loadTextFiles() {
auto list = Asset::get()->getListByType(Asset::Type::FONT);
text_files_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
text_files_.emplace_back(name, Text::loadFile(l));
}
}
// Carga las animaciones del juego
void Resource::loadAnimations() {
auto list = Asset::get()->getListByType(Asset::Type::ANIMATION);
animations_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
animations_.emplace_back(name, loadAnimationsFromFile(l));
}
}
// Carga los datos para el modo demostración
void Resource::loadDemoData() {
auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
demos_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
demos_.emplace_back(loadDemoDataFromFile(l));
}
}
// Crea las texturas de jugadores con todas sus variantes de paleta
void Resource::createPlayerTextures() {
// Configuración de jugadores y sus paletas
struct PlayerConfig {
std::string base_texture;
std::vector<std::string> palette_files;
std::string name_prefix;
};
std::vector<PlayerConfig> players = {
{.base_texture = "player1.gif", .palette_files = {"player1_coffee1.pal", "player1_coffee2.pal", "player1_invencible.pal"}, .name_prefix = "player1"},
{.base_texture = "player2.gif", .palette_files = {"player2_coffee1.pal", "player2_coffee2.pal", "player2_invencible.pal"}, .name_prefix = "player2"}};
// Bucle principal
for (size_t player_idx = 0; player_idx < players.size(); ++player_idx) {
const auto& player = players[player_idx]; // Obtenemos el jugador actual
// Encontrar el archivo original de la textura
std::string texture_file_path;
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
for (const auto& file : texture_list) {
if (getFileName(file) == player.base_texture) {
texture_file_path = file;
break;
}
}
// Crear las 4 texturas con sus respectivas paletas
for (int palette_idx = 0; palette_idx < 4; ++palette_idx) {
std::shared_ptr<Texture> texture;
if (palette_idx == 0) {
// Textura 0 - usar la ya cargada y modificar solo paleta 0 (default_shirt)
texture = getTexture(player.base_texture);
texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.TO_UINT32());
texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.TO_UINT32());
texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.TO_UINT32());
texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.TO_UINT32());
texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].TO_UINT32());
} else {
// Crear textura nueva desde archivo usando ResourceHelper
texture = std::make_shared<Texture>(Screen::get()->getRenderer(), texture_file_path);
// Añadir todas las paletas
texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[0]));
texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[1]));
texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[2]));
if (palette_idx == 1) {
// Textura 1 - modificar solo paleta 1 (one_coffee_shirt)
texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.TO_UINT32());
texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.TO_UINT32());
texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.TO_UINT32());
texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.TO_UINT32());
texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].TO_UINT32());
} else if (palette_idx == 2) {
// Textura 2 - modificar solo paleta 2 (two_coffee_shirt)
texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.TO_UINT32());
texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.TO_UINT32());
texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.TO_UINT32());
texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.TO_UINT32());
texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].TO_UINT32());
}
// Textura 3 (palette_idx == 3) - no modificar nada, usar colores originales
}
// Asignar la paleta correspondiente
texture->setPalette(palette_idx);
// Guardar con nombre específico
std::string texture_name = player.name_prefix + "_pal" + std::to_string(palette_idx);
textures_.emplace_back(texture_name, texture);
}
}
}
// Crea texturas a partir de textos para mostrar puntuaciones y mensajes
void Resource::createTextTextures() {
struct NameAndText {
std::string name;
std::string text;
NameAndText(std::string name_init, std::string text_init)
: name(std::move(name_init)),
text(std::move(text_init)) {}
};
// Texturas de tamaño normal con outline
std::vector<NameAndText> strings1 = {
{"game_text_1000_points", "1.000"},
{"game_text_2500_points", "2.500"},
{"game_text_5000_points", "5.000"},
{"game_text_powerup", Lang::getText("[GAME_TEXT] 4")},
{"game_text_one_hit", Lang::getText("[GAME_TEXT] 5")},
{"game_text_stop", Lang::getText("[GAME_TEXT] 6")},
{"game_text_1000000_points", Lang::getText("[GAME_TEXT] 8")}};
auto text1 = getText("04b_25_enhanced");
for (const auto& s : strings1) {
textures_.emplace_back(s.name, text1->writeDXToTexture(Text::STROKE, s.text, -2, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
}
// Texturas de tamaño doble
std::vector<NameAndText> strings2 = {
{"game_text_100000_points", "100.000"},
{"game_text_get_ready", Lang::getText("[GAME_TEXT] 7")},
{"game_text_last_stage", Lang::getText("[GAME_TEXT] 3")},
{"game_text_congratulations", Lang::getText("[GAME_TEXT] 1")},
{"game_text_new_record", Lang::getText("[GAME_TEXT] NEW_RECORD")},
{"game_text_game_over", "Game Over"}};
auto text2 = getText("04b_25_2x_enhanced");
for (const auto& s : strings2) {
textures_.emplace_back(s.name, text2->writeDXToTexture(Text::STROKE, s.text, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
}
}
// Crea los objetos de texto a partir de los archivos de textura y texto
void Resource::createText() {
struct ResourceInfo {
std::string key;
std::string texture_file;
std::string text_file;
std::string white_texture_file; // Textura blanca opcional
ResourceInfo(std::string k, std::string t_file, std::string txt_file, std::string w_file = "")
: key(std::move(k)),
texture_file(std::move(t_file)),
text_file(std::move(txt_file)),
white_texture_file(std::move(w_file)) {}
};
std::vector<ResourceInfo> resources = {
{"04b_25", "04b_25.png", "04b_25.txt"},
{"04b_25_enhanced", "04b_25.png", "04b_25.txt", "04b_25_white.png"}, // Nueva fuente con textura blanca
{"04b_25_white", "04b_25_white.png", "04b_25.txt"},
{"04b_25_2x", "04b_25_2x.png", "04b_25_2x.txt"},
{"04b_25_2x_enhanced", "04b_25_2x.png", "04b_25_2x.txt", "04b_25_2x_white.png"}, // Nueva fuente con textura blanca
{"04b_25_metal", "04b_25_metal.png", "04b_25.txt"},
{"04b_25_grey", "04b_25_grey.png", "04b_25.txt"},
{"04b_25_flat", "04b_25_flat.png", "04b_25.txt"},
{"04b_25_reversed", "04b_25_reversed.png", "04b_25.txt"},
{"04b_25_flat_2x", "04b_25_flat_2x.png", "04b_25_2x.txt"},
{"04b_25_reversed_2x", "04b_25_reversed_2x.png", "04b_25_2x.txt"},
{"8bithud", "8bithud.png", "8bithud.txt"},
{"aseprite", "aseprite.png", "aseprite.txt"},
{"smb2", "smb2.png", "smb2.txt"},
{"smb2_grad", "smb2_grad.png", "smb2.txt"}};
for (const auto& resource : resources) {
if (!resource.white_texture_file.empty()) {
// Crear texto con textura blanca
texts_.emplace_back(resource.key, std::make_shared<Text>(getTexture(resource.texture_file), getTexture(resource.white_texture_file), getTextFile(resource.text_file)));
} else {
// Crear texto normal
texts_.emplace_back(resource.key, std::make_shared<Text>(getTexture(resource.texture_file), getTextFile(resource.text_file)));
}
}
}
// Vacía el vector de sonidos y libera la memoria asociada
void Resource::clearSounds() {
for (auto& sound : sounds_) {
if (sound.sound != nullptr) {
JA_DeleteSound(sound.sound);
sound.sound = nullptr;
}
}
sounds_.clear();
}
// Vacía el vector de músicas y libera la memoria asociada
void Resource::clearMusics() {
for (auto& music : musics_) {
if (music.music != nullptr) {
JA_DeleteMusic(music.music);
music.music = nullptr;
}
}
musics_.clear();
}
// Calcula el número total de recursos a cargar y reinicia el contador de carga
void Resource::calculateTotalResources() {
const std::array<Asset::Type, 6> ASSET_TYPES = {
Asset::Type::SOUND,
Asset::Type::MUSIC,
Asset::Type::BITMAP,
Asset::Type::FONT,
Asset::Type::ANIMATION,
Asset::Type::DEMODATA};
size_t total = 0;
for (const auto& asset_type : ASSET_TYPES) {
auto list = Asset::get()->getListByType(asset_type);
total += list.size();
}
loading_count_ = ResourceCount(total);
}
// Muestra el progreso de carga en pantalla (barra y texto)
void Resource::renderProgress() {
// Obtiene la pantalla y el renderer
auto* screen = Screen::get();
auto* renderer = screen->getRenderer();
// Actualiza la lógica principal de la pantalla (input, etc.)
screen->coreUpdate();
// Inicia el frame y limpia la pantalla
screen->start();
screen->clean();
auto text_color = param.resource.color;
auto bar_color = param.resource.color.DARKEN(100);
const auto TEXT_HEIGHT = loading_text_->getCharacterSize();
// Dibuja el interior de la barra de progreso
SDL_SetRenderDrawColor(renderer, bar_color.r, bar_color.g, bar_color.b, bar_color.a);
SDL_RenderFillRect(renderer, &loading_full_rect_);
// Dibuja el marco de la barra de progreso
SDL_SetRenderDrawColor(renderer, bar_color.r, bar_color.g, bar_color.b, bar_color.a);
SDL_RenderRect(renderer, &loading_wired_rect_);
// Escribe el texto de carga encima de la barra
/*
loading_text_->writeColored(
loading_wired_rect_.x,
loading_wired_rect_.y - 9,
Lang::getText("[RESOURCE] LOADING") + " : " + loading_resource_name_,
text_color);
*/
// Muestra nombre de la aplicación
loading_text_->writeDX(
Text::CENTER | Text::COLOR,
param.game.game_area.center_x,
param.game.game_area.center_y - TEXT_HEIGHT,
spaceBetweenLetters(std::string(Version::APP_NAME)),
1,
text_color);
// Muestra la versión
loading_text_->writeDX(
Text::CENTER | Text::COLOR,
param.game.game_area.center_x,
param.game.game_area.center_y + TEXT_HEIGHT,
"(" + std::string(Version::GIT_HASH) + ")",
1,
text_color);
// Muestra información del monitor desplazada hacia abajo
/*loading_text_->writeColored(
X_PADDING,
Y_PADDING + 18,
screen->getDisplayMonitorName(),
text_color);
loading_text_->writeColored(
X_PADDING,
Y_PADDING + 27,
std::to_string(screen->getDisplayMonitorWidth()) + "x" + std::to_string(screen->getDisplayMonitorHeight()),
text_color);
loading_text_->writeColored(
X_PADDING,
Y_PADDING + 36,
std::to_string(screen->getDisplayMonitorRefreshRate()) + "Hz",
text_color);*/
// Renderiza el frame en pantalla
screen->coreRender();
}
// Carga los datos para el modo demostración (sin mostrar progreso)
void Resource::loadDemoDataQuiet() {
auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
demos_.clear();
for (const auto& l : list) {
demos_.emplace_back(loadDemoDataFromFile(l));
}
}
// Inicializa los rectangulos que definen la barra de progreso
void Resource::initProgressBar() {
const float BAR_Y_POSITION = param.game.height - BAR_HEIGHT - Y_PADDING;
const float WIRED_BAR_WIDTH = param.game.width - (X_PADDING * 2);
loading_wired_rect_ = {.x = X_PADDING, .y = BAR_Y_POSITION, .w = WIRED_BAR_WIDTH, .h = BAR_HEIGHT};
const float FULL_BAR_WIDTH = WIRED_BAR_WIDTH * loading_count_.getPercentage();
loading_full_rect_ = {.x = X_PADDING, .y = BAR_Y_POSITION, .w = FULL_BAR_WIDTH, .h = BAR_HEIGHT};
}
// Actualiza el progreso de carga y muestra la barra
void Resource::updateLoadingProgress(std::string name) {
loading_resource_name_ = std::move(name);
loading_count_.increase();
updateProgressBar();
renderProgress();
}
// Actualiza la barra de estado
void Resource::updateProgressBar() {
loading_full_rect_.w = loading_wired_rect_.w * loading_count_.getPercentage();
}

View File

@@ -0,0 +1,187 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_FRect
#include <cstddef> // Para size_t
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <utility> // Para move
#include <vector> // Para vector
#include "animated_sprite.hpp" // Para AnimationsFileBuffer
#include "demo.hpp" // Para DemoData
#include "text.hpp" // Para Text, TextFile
#include "texture.hpp" // Para Texture
struct JA_Music_t;
struct JA_Sound_t;
// --- Clase Resource: gestiona todos los recursos del juego (singleton) ---
class Resource {
public:
// --- Enum para el modo de carga ---
enum class LoadingMode {
PRELOAD, // Carga todos los recursos al inicio
LAZY_LOAD // Carga los recursos bajo demanda
};
// --- Métodos de singleton ---
static void init(LoadingMode mode = LoadingMode::PRELOAD); // Inicializa el objeto Resource con modo de carga
static void destroy(); // Libera el objeto Resource
static auto get() -> Resource*; // Obtiene el puntero al objeto Resource
// --- Métodos de acceso a recursos ---
auto getSound(const std::string& name) -> JA_Sound_t*; // Obtiene el sonido por nombre
auto getMusic(const std::string& name) -> JA_Music_t*; // Obtiene la música por nombre
auto getTexture(const std::string& name) -> std::shared_ptr<Texture>; // Obtiene la textura por nombre
auto getTextFile(const std::string& name) -> std::shared_ptr<Text::File>; // Obtiene el fichero de texto por nombre
auto getText(const std::string& name) -> std::shared_ptr<Text>; // Obtiene el objeto de texto por nombre
auto getAnimation(const std::string& name) -> AnimationsFileBuffer&; // Obtiene la animación por nombre
auto getDemoData(int index) -> DemoData&; // Obtiene los datos de demo por índice
// --- Métodos de recarga de recursos ---
void reload(); // Recarga todos los recursos
// --- Método para obtener el modo de carga actual ---
[[nodiscard]] auto getLoadingMode() const -> LoadingMode { return loading_mode_; }
private:
// --- Estructuras para recursos individuales ---
struct ResourceSound {
std::string name; // Nombre del sonido
JA_Sound_t* sound; // Objeto con el sonido
ResourceSound(std::string name, JA_Sound_t* sound = nullptr)
: name(std::move(name)),
sound(sound) {}
};
struct ResourceMusic {
std::string name; // Nombre de la música
JA_Music_t* music; // Objeto con la música
ResourceMusic(std::string name, JA_Music_t* music = nullptr)
: name(std::move(name)),
music(music) {}
};
struct ResourceTexture {
std::string name; // Nombre de la textura
std::shared_ptr<Texture> texture; // Objeto con la textura
ResourceTexture(std::string name, std::shared_ptr<Texture> texture = nullptr)
: name(std::move(name)),
texture(std::move(texture)) {}
};
struct ResourceTextFile {
std::string name; // Nombre del fichero
std::shared_ptr<Text::File> text_file; // Objeto con los descriptores de la fuente de texto
ResourceTextFile(std::string name, std::shared_ptr<Text::File> text_file = nullptr)
: name(std::move(name)),
text_file(std::move(text_file)) {}
};
struct ResourceText {
std::string name; // Nombre del objeto
std::shared_ptr<Text> text; // Objeto de texto
ResourceText(std::string name, std::shared_ptr<Text> text = nullptr)
: name(std::move(name)),
text(std::move(text)) {}
};
struct ResourceAnimation {
std::string name; // Nombre de la animación
AnimationsFileBuffer animation; // Objeto con las animaciones
ResourceAnimation(std::string name, AnimationsFileBuffer animation = {})
: name(std::move(name)),
animation(std::move(animation)) {}
};
// --- Estructura para el progreso de carga ---
struct ResourceCount {
size_t total{0}; // Número total de recursos
size_t loaded{0}; // Número de recursos cargados
ResourceCount() = default;
ResourceCount(size_t total)
: total(total) {}
void add(size_t amount) { loaded += amount; }
void increase() { loaded++; }
[[nodiscard]] auto getPercentage() const -> float {
return total > 0 ? static_cast<float>(loaded) / static_cast<float>(total) : 0.0F;
}
};
// --- Constantes para la pantalla de carga ---
static constexpr float X_PADDING = 60.0F;
static constexpr float Y_PADDING = 20.0F;
static constexpr float BAR_HEIGHT = 5.0F;
static constexpr Color BAR_COLOR = Color(128, 128, 128);
static constexpr Color TEXT_COLOR = Color(255, 255, 255);
// --- Modo de carga ---
LoadingMode loading_mode_;
// --- Vectores de recursos ---
std::vector<ResourceSound> sounds_; // Vector con los sonidos
std::vector<ResourceMusic> musics_; // Vector con las músicas
std::vector<ResourceTexture> textures_; // Vector con las texturas
std::vector<ResourceTextFile> text_files_; // Vector con los ficheros de texto
std::vector<ResourceText> texts_; // Vector con los objetos de texto
std::vector<ResourceAnimation> animations_; // Vector con las animaciones
std::vector<DemoData> demos_; // Vector con los ficheros de datos para el modo demostración
// --- Progreso de carga ---
ResourceCount loading_count_; // Contador de recursos cargados
std::shared_ptr<Text> loading_text_; // Texto para escribir en pantalla
std::string loading_resource_name_; // Nombre del recurso que se está cargando
SDL_FRect loading_wired_rect_;
SDL_FRect loading_full_rect_;
// --- Métodos internos de carga y gestión ---
void loadSounds(); // Carga los sonidos
void loadMusics(); // Carga las músicas
void loadTextures(); // Carga las texturas
void loadTextFiles(); // Carga los ficheros de texto
void loadAnimations(); // Carga las animaciones
void loadDemoData(); // Carga los datos para el modo demostración
void loadDemoDataQuiet(); // Carga los datos de demo sin mostrar progreso (para modo lazy)
void loadEssentialResources(); // Carga recursos esenciales en modo lazy
void loadEssentialTextures(); // Carga solo las texturas esenciales (fuentes)
void loadTextFilesQuiet(); // Carga ficheros de texto sin mostrar progreso (para modo lazy)
void createPlayerTextures(); // Crea las texturas de jugadores con todas sus variantes de paleta
void createTextTextures(); // Crea las texturas a partir de los datos cargados
void createText(); // Crea los objetos de texto
void clear(); // Vacía todos los vectores de recursos
void load(); // Carga todos los recursos
void clearSounds(); // Vacía el vector de sonidos
void clearMusics(); // Vacía el vector de músicas
// --- Métodos para carga perezosa ---
void initResourceLists(); // Inicializa las listas de recursos sin cargar el contenido
static auto loadSoundLazy(const std::string& name) -> JA_Sound_t*; // Carga un sonido específico bajo demanda
static auto loadMusicLazy(const std::string& name) -> JA_Music_t*; // Carga una música específica bajo demanda
static auto loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture>; // Carga una textura específica bajo demanda
static auto loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File>; // Carga un fichero de texto específico bajo demanda
auto loadTextLazy(const std::string& name) -> std::shared_ptr<Text>; // Carga un objeto de texto específico bajo demanda
static auto loadAnimationLazy(const std::string& name) -> AnimationsFileBuffer; // Carga una animación específica bajo demanda
// --- Métodos internos para gestionar el progreso ---
void calculateTotalResources(); // Calcula el número de recursos para cargar
void renderProgress(); // Muestra el progreso de carga
void updateLoadingProgress(std::string name); // Actualiza el progreso de carga
void initProgressBar(); // Inicializa los rectangulos que definen la barra de progreso
void updateProgressBar(); // Actualiza la barra de estado
// --- Constructores y destructor privados (singleton) ---
explicit Resource(LoadingMode mode); // Constructor privado con modo de carga
~Resource(); // Destructor privado
// --- Instancia singleton ---
static Resource* instance; // Instancia única de Resource
};

View File

@@ -0,0 +1,98 @@
#include "resource_helper.hpp"
#include <algorithm> // Para replace
#include <cstddef> // Para size_t
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ios, operator<<, ios, basic_istream, endl, operator|, basic_istream::read, basic_istream::seekg, basic_istream::tellg, ifstream, streamsize
#include <iostream> // Para cout
#include "resource_loader.hpp" // Para ResourceLoader
namespace ResourceHelper {
static bool resource_system_initialized = false;
auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback) -> bool {
auto& loader = ResourceLoader::getInstance();
resource_system_initialized = loader.initialize(pack_file, enable_fallback);
if (resource_system_initialized) {
std::cout << "Resource system initialized with pack: " << pack_file << '\n';
} else {
std::cout << "Resource system using fallback mode (filesystem only)" << '\n';
}
return true; // Always return true as fallback is acceptable
}
void shutdownResourceSystem() {
if (resource_system_initialized) {
ResourceLoader::getInstance().shutdown();
resource_system_initialized = false;
}
}
auto loadFile(const std::string& filepath) -> std::vector<uint8_t> {
if (resource_system_initialized && shouldUseResourcePack(filepath)) {
auto& loader = ResourceLoader::getInstance();
std::string pack_path = getPackPath(filepath);
auto data = loader.loadResource(pack_path);
if (!data.empty()) {
return data;
}
}
// Fallback a filesystem
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(file_size);
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
return {};
}
return data;
}
auto shouldUseResourcePack(const std::string& filepath) -> bool {
// Archivos que NO van al pack:
// - config/ (ahora está fuera de data/)
// - archivos absolutos del sistema
if (filepath.find("config/") != std::string::npos) {
return false;
}
// Si contiene "data/" es candidato para el pack
if (filepath.find("data/") != std::string::npos) {
return true;
}
return false;
}
auto getPackPath(const std::string& asset_path) -> std::string {
std::string pack_path = asset_path;
// Normalizar separadores de path a '/'
std::ranges::replace(pack_path, '\\', '/');
// Remover prefijo "data/" si existe
size_t data_pos = pack_path.find("data/");
if (data_pos != std::string::npos) {
pack_path = pack_path.substr(data_pos + 5); // +5 para saltar "data/"
}
// Remover cualquier prefijo de path absoluto
size_t last_data = pack_path.rfind("data/");
if (last_data != std::string::npos) {
pack_path = pack_path.substr(last_data + 5);
}
return pack_path;
}
} // namespace ResourceHelper

View File

@@ -0,0 +1,45 @@
#pragma once
#include <cstdint> // Para uint8_t
#include <filesystem> // Para remove, path
#include <fstream> // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream
#include <string> // Para string, basic_string, hash, operator+, to_string, __str_hash_base
#include <vector> // Para vector
// Helper functions para integrar ResourceLoader con el sistema existente
namespace ResourceHelper {
// Inicializa ResourceLoader (llamar al inicio del programa)
auto initializeResourceSystem(const std::string& pack_file = "resources.pack", bool enable_fallback = true) -> bool;
// Cierra ResourceLoader
void shutdownResourceSystem();
// Carga un archivo usando ResourceLoader o fallback a filesystem
auto loadFile(const std::string& filepath) -> std::vector<uint8_t>;
// Verifica si un archivo debería cargarse del pack vs filesystem
auto shouldUseResourcePack(const std::string& filepath) -> bool;
// Convierte ruta Asset a ruta relativa para ResourceLoader
auto getPackPath(const std::string& asset_path) -> std::string;
// Wrappea la carga de archivos para mantener compatibilidad
template <typename T>
auto loadResourceFile(const std::string& asset_path, T* (*loader_func)(const char*)) -> T* {
auto data = loadFile(asset_path);
if (data.empty()) {
return loader_func(asset_path.c_str());
}
// Crear archivo temporal para funciones que esperan path
std::string temp_path = "/tmp/ccae_" + std::to_string(std::hash<std::string>{}(asset_path));
std::ofstream temp_file(temp_path, std::ios::binary);
temp_file.write(reinterpret_cast<const char*>(data.data()), data.size());
temp_file.close();
T* result = loader_func(temp_path.c_str());
std::filesystem::remove(temp_path);
return result;
}
} // namespace ResourceHelper

View File

@@ -0,0 +1,133 @@
#include "resource_loader.hpp"
#include <algorithm> // Para replace
#include <filesystem> // Para exists, path, recursive_directory_iterator, directory_entry, relative
#include <fstream> // Para basic_ostream, basic_ifstream, operator<<, basic_ios, endl, ios, basic_istream, operator|, basic_istream::read, basic_istream::seekg, basic_istream::tellg, ifstream, streamsize
#include <iostream> // Para cerr, cout
#include "resource_pack.hpp" // Para ResourcePack
std::unique_ptr<ResourceLoader> ResourceLoader::instance = nullptr;
ResourceLoader::ResourceLoader()
: resource_pack_(nullptr),
fallback_to_files_(true) {}
auto ResourceLoader::getInstance() -> ResourceLoader& {
if (!instance) {
instance = std::unique_ptr<ResourceLoader>(new ResourceLoader());
}
return *instance;
}
ResourceLoader::~ResourceLoader() {
shutdown();
}
auto ResourceLoader::initialize(const std::string& pack_file, bool enable_fallback) -> bool {
shutdown();
fallback_to_files_ = enable_fallback;
pack_path_ = pack_file;
if (std::filesystem::exists(pack_file)) {
resource_pack_ = new ResourcePack();
if (resource_pack_->loadPack(pack_file)) {
return true;
}
delete resource_pack_;
resource_pack_ = nullptr;
std::cerr << "Failed to load resource pack: " << pack_file << '\n';
}
if (fallback_to_files_) {
return true;
}
std::cerr << "Resource pack not found and fallback disabled: " << pack_file << '\n';
return false;
}
void ResourceLoader::shutdown() {
if (resource_pack_ != nullptr) {
delete resource_pack_;
resource_pack_ = nullptr;
}
}
auto ResourceLoader::loadResource(const std::string& filename) -> std::vector<uint8_t> {
if ((resource_pack_ != nullptr) && resource_pack_->hasResource(filename)) {
return resource_pack_->getResource(filename);
}
if (fallback_to_files_) {
return loadFromFile(filename);
}
std::cerr << "Resource not found: " << filename << '\n';
return {};
}
auto ResourceLoader::resourceExists(const std::string& filename) -> bool {
if ((resource_pack_ != nullptr) && resource_pack_->hasResource(filename)) {
return true;
}
if (fallback_to_files_) {
std::string full_path = getDataPath(filename);
return std::filesystem::exists(full_path);
}
return false;
}
auto ResourceLoader::loadFromFile(const std::string& filename) -> std::vector<uint8_t> {
std::string full_path = getDataPath(filename);
std::ifstream file(full_path, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Error: Could not open file: " << full_path << '\n';
return {};
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(file_size);
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
std::cerr << "Error: Could not read file: " << full_path << '\n';
return {};
}
return data;
}
auto ResourceLoader::getDataPath(const std::string& filename) -> std::string {
return "data/" + filename;
}
auto ResourceLoader::getLoadedResourceCount() const -> size_t {
if (resource_pack_ != nullptr) {
return resource_pack_->getResourceCount();
}
return 0;
}
auto ResourceLoader::getAvailableResources() const -> std::vector<std::string> {
if (resource_pack_ != nullptr) {
return resource_pack_->getResourceList();
}
std::vector<std::string> result;
if (fallback_to_files_ && std::filesystem::exists("data")) {
for (const auto& entry : std::filesystem::recursive_directory_iterator("data")) {
if (entry.is_regular_file()) {
std::string filename = std::filesystem::relative(entry.path(), "data").string();
std::ranges::replace(filename, '\\', '/');
result.push_back(filename);
}
}
}
return result;
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <cstddef> // Para size_t
#include <cstdint> // Para uint8_t
#include <memory> // Para unique_ptr
#include <string> // Para string, basic_string
#include <vector> // Para vector
class ResourcePack;
class ResourceLoader {
private:
static std::unique_ptr<ResourceLoader> instance;
ResourcePack* resource_pack_;
std::string pack_path_;
bool fallback_to_files_;
ResourceLoader();
public:
static auto getInstance() -> ResourceLoader&;
~ResourceLoader();
auto initialize(const std::string& pack_file, bool enable_fallback = true) -> bool;
void shutdown();
auto loadResource(const std::string& filename) -> std::vector<uint8_t>;
auto resourceExists(const std::string& filename) -> bool;
void setFallbackToFiles(bool enable) { fallback_to_files_ = enable; }
[[nodiscard]] auto getFallbackToFiles() const -> bool { return fallback_to_files_; }
[[nodiscard]] auto getLoadedResourceCount() const -> size_t;
[[nodiscard]] auto getAvailableResources() const -> std::vector<std::string>;
private:
static auto loadFromFile(const std::string& filename) -> std::vector<uint8_t>;
static auto getDataPath(const std::string& filename) -> std::string;
};

View File

@@ -0,0 +1,228 @@
#include "resource_pack.hpp"
#include <algorithm> // Para replace
#include <array> // Para array
#include <filesystem> // Para path, recursive_directory_iterator, directory_entry, exists, relative
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ofstream, operator<<, basic_ios, basic_istream::read, basic_ostream::write, endl, ios, basic_istream, ifstream, operator|, basic_istream::seekg, basic_istream::tellg, ofstream, streamsize
#include <iostream> // Para cerr
#include <utility> // Para pair
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCAE_RESOURCES__2024";
ResourcePack::ResourcePack()
: loaded_(false) {}
ResourcePack::~ResourcePack() {
clear();
}
auto ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t {
uint32_t checksum = 0x12345678;
for (unsigned char i : data) {
checksum = ((checksum << 5) + checksum) + i;
}
return checksum;
}
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
if (key.empty()) {
return;
}
for (size_t i = 0; i < data.size(); ++i) {
data[i] ^= key[i % key.length()];
}
}
void ResourcePack::decryptData(std::vector<uint8_t>& data, const std::string& key) {
encryptData(data, key);
}
auto ResourcePack::loadPack(const std::string& pack_file) -> bool {
std::ifstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "Error: Could not open pack file: " << pack_file << '\n';
return false;
}
std::array<char, 4> header;
file.read(header.data(), 4);
if (std::string(header.data(), 4) != "CCAE") {
std::cerr << "Error: Invalid pack file format" << '\n';
return false;
}
uint32_t version;
file.read(reinterpret_cast<char*>(&version), sizeof(version));
if (version != 1) {
std::cerr << "Error: Unsupported pack version: " << version << '\n';
return false;
}
uint32_t resource_count;
file.read(reinterpret_cast<char*>(&resource_count), sizeof(resource_count));
resources_.clear();
resources_.reserve(resource_count);
for (uint32_t i = 0; i < resource_count; ++i) {
uint32_t filename_length;
file.read(reinterpret_cast<char*>(&filename_length), sizeof(filename_length));
std::string filename(filename_length, '\0');
file.read(filename.data(), filename_length);
ResourceEntry entry;
entry.filename = filename;
file.read(reinterpret_cast<char*>(&entry.offset), sizeof(entry.offset));
file.read(reinterpret_cast<char*>(&entry.size), sizeof(entry.size));
file.read(reinterpret_cast<char*>(&entry.checksum), sizeof(entry.checksum));
resources_[filename] = entry;
}
uint64_t data_size;
file.read(reinterpret_cast<char*>(&data_size), sizeof(data_size));
data_.resize(data_size);
file.read(reinterpret_cast<char*>(data_.data()), data_size);
decryptData(data_, DEFAULT_ENCRYPT_KEY);
loaded_ = true;
return true;
}
auto ResourcePack::savePack(const std::string& pack_file) -> bool {
std::ofstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "Error: Could not create pack file: " << pack_file << '\n';
return false;
}
file.write("CCAE", 4);
uint32_t version = 1;
file.write(reinterpret_cast<const char*>(&version), sizeof(version));
auto resource_count = static_cast<uint32_t>(resources_.size());
file.write(reinterpret_cast<const char*>(&resource_count), sizeof(resource_count));
for (const auto& [filename, entry] : resources_) {
auto filename_length = static_cast<uint32_t>(filename.length());
file.write(reinterpret_cast<const char*>(&filename_length), sizeof(filename_length));
file.write(filename.c_str(), filename_length);
file.write(reinterpret_cast<const char*>(&entry.offset), sizeof(entry.offset));
file.write(reinterpret_cast<const char*>(&entry.size), sizeof(entry.size));
file.write(reinterpret_cast<const char*>(&entry.checksum), sizeof(entry.checksum));
}
std::vector<uint8_t> encrypted_data = data_;
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
uint64_t data_size = encrypted_data.size();
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
file.write(reinterpret_cast<const char*>(encrypted_data.data()), data_size);
return true;
}
auto ResourcePack::addFile(const std::string& filename, const std::string& filepath) -> bool {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Error: Could not open file: " << filepath << '\n';
return false;
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> file_data(file_size);
if (!file.read(reinterpret_cast<char*>(file_data.data()), file_size)) {
std::cerr << "Error: Could not read file: " << filepath << '\n';
return false;
}
ResourceEntry entry;
entry.filename = filename;
entry.offset = data_.size();
entry.size = file_data.size();
entry.checksum = calculateChecksum(file_data);
data_.insert(data_.end(), file_data.begin(), file_data.end());
resources_[filename] = entry;
return true;
}
auto ResourcePack::addDirectory(const std::string& directory) -> bool {
if (!std::filesystem::exists(directory)) {
std::cerr << "Error: Directory does not exist: " << directory << '\n';
return false; // NOLINT(readability-simplify-boolean-expr)
}
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
if (entry.is_regular_file()) {
std::string filepath = entry.path().string();
std::string filename = std::filesystem::relative(entry.path(), directory).string();
std::ranges::replace(filename, '\\', '/');
if (!addFile(filename, filepath)) {
return false;
}
}
}
return true;
}
auto ResourcePack::getResource(const std::string& filename) -> std::vector<uint8_t> {
auto it = resources_.find(filename);
if (it == resources_.end()) {
std::cerr << "Error: Resource not found: " << filename << '\n';
return {};
}
const ResourceEntry& entry = it->second;
if (entry.offset + entry.size > data_.size()) {
std::cerr << "Error: Invalid resource data: " << filename << '\n';
return {};
}
std::vector<uint8_t> result(data_.begin() + entry.offset,
data_.begin() + entry.offset + entry.size);
uint32_t checksum = calculateChecksum(result);
if (checksum != entry.checksum) {
std::cerr << "Warning: Checksum mismatch for resource: " << filename << '\n';
}
return result;
}
auto ResourcePack::hasResource(const std::string& filename) const -> bool {
return resources_.contains(filename);
}
void ResourcePack::clear() {
resources_.clear();
data_.clear();
loaded_ = false;
}
auto ResourcePack::getResourceCount() const -> size_t {
return resources_.size();
}
auto ResourcePack::getResourceList() const -> std::vector<std::string> {
std::vector<std::string> result;
result.reserve(resources_.size());
for (const auto& [filename, entry] : resources_) {
result.push_back(filename);
}
return result;
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <cstddef> // Para size_t
#include <cstdint> // Para uint8_t, uint32_t, uint64_t
#include <string> // Para string, basic_string, hash
#include <unordered_map> // Para unordered_map
#include <vector> // Para vector
struct ResourceEntry {
std::string filename;
uint64_t offset;
uint64_t size;
uint32_t checksum;
};
class ResourcePack {
private:
std::unordered_map<std::string, ResourceEntry> resources_;
std::vector<uint8_t> data_;
bool loaded_;
static auto calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t;
static void encryptData(std::vector<uint8_t>& data, const std::string& key);
static void decryptData(std::vector<uint8_t>& data, const std::string& key);
public:
ResourcePack();
~ResourcePack();
auto loadPack(const std::string& pack_file) -> bool;
auto savePack(const std::string& pack_file) -> bool;
auto addFile(const std::string& filename, const std::string& filepath) -> bool;
auto addDirectory(const std::string& directory) -> bool;
[[nodiscard]] auto getResource(const std::string& filename) -> std::vector<uint8_t>;
[[nodiscard]] auto hasResource(const std::string& filename) const -> bool;
void clear();
[[nodiscard]] auto getResourceCount() const -> size_t;
[[nodiscard]] auto getResourceList() const -> std::vector<std::string>;
static const std::string DEFAULT_ENCRYPT_KEY;
};