ajustant el jugador

This commit is contained in:
2026-04-05 22:47:12 +02:00
parent 20ad7d778f
commit 6305280e62
51 changed files with 487 additions and 450 deletions

View File

@@ -18,11 +18,37 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Estado del renombrado ## Estado del renombrado
Se han renombrado las referencias de `JailDoctor's Dilemma``Projecte 2026` y `jaildoctors_dilemma``projecte_2026` en código, configs, Makefile, CMake, LICENSE, Info.plist, scripts, locales, etc. Se han mantenido como placeholders: Se han renombrado las referencias de `JailDoctor's Dilemma``Projecte 2026` y `jaildoctors_dilemma``projecte_2026` en código, configs, Makefile, CMake, LICENSE, Info.plist, scripts, locales, etc. Copyright actualizado a 2026. Se han mantenido como placeholders:
- La clave de locale `jaildoctor:` en `data/locale/*.yaml` y su uso en `source/game/scenes/ending2.cpp` (es contenido de juego, pendiente de rediseño). - La clave de locale `jaildoctor:` en `data/locale/*.yaml` y su uso en `source/game/scenes/ending2.cpp` (es contenido de juego, pendiente de rediseño).
- El publisher `jailgames` (carpeta de config de sistema: `~/.config/jailgames/projecte_2026`). - El publisher `jailgames` (carpeta de config de sistema: `~/.config/jailgames/projecte_2026`).
- Los archivos `release/windows/jdd.rc` y `jdd.res` (solo bundlean el icono). - Los archivos `release/windows/jdd.rc` y `jdd.res` (solo bundlean el icono).
## Log de cambios realizados (sesiones de trabajo)
### Eliminaciones
- **Clase `Stats` (persistencia CSV de muertes/visitas)** eliminada por completo: archivos `source/game/gameplay/stats.hpp/cpp`, entrada en CMakeLists, referencias en `Game` scene (`stats_`, `initStats()`, `addVisit/addDeath`), ficheros `stats.csv`/`stats_buffer.csv` en `config/assets.yaml`, patrón `*stats.txt` en `.gitignore`, campo `worst_nightmare` en `Options::Stats` + `Defaults::Stats::WORST_NIGHTMARE`, display de "worst nightmare" en `game_over.cpp`/`.hpp`, traducciones en locales, menciones en docs.
- **Mantenido:** `Options::stats.items` y `Options::stats.rooms` — son contadores de runtime del marcador/game_over, no tienen persistencia CSV. Coincidencia de nombre con la clase Stats eliminada.
### Música
- Eliminadas las 10 pistas originales (14 MB) de `data/music/`.
- Copiadas 2 pistas de `../pollo/data/music/`: `574070_KUVO_Farewell_to_school.ogg` y `574071_EA_DTV.ogg` (7 MB total).
- `config/assets.yaml` reducido a esas 2 entradas.
- Las 10 llamadas `playMusic()` existentes (title/game/loading_*/ending*/game_over) remapeadas alternadamente a una de las 2 pistas. Mapeo concreto en esta misma documentación si hace falta consultarlo (ver git log del cambio).
### Paleta
- Eliminadas las 23 paletas ZX Spectrum/otras de `data/palette/`.
- Traída `cpc.pal` de `../pollo/data/palettes/` (JASC-PAL, 28 entradas: CLEAR+27 colores CPC).
- `config/assets.yaml`: solo `cpc.pal`.
- `Defaults::Video::PALETTE_NAME = "cpc"`.
- Traída la clase `Color` de pollo a `source/utils/color.hpp` + `color.cpp` (enum `Color::Cpc` con 28 valores 0-27 + `Color::fromString()`).
- Struct RGB `Color` en `utils.hpp` **renombrada a `Rgb`** para evitar conflicto con la nueva clase. Propagado a `utils.cpp` (`colorAreEqual`), `screen.hpp`/`.cpp` (`clearRenderer`).
- Añadido `source/utils/color.cpp` a `CMakeLists.txt`.
- **Pendiente de revisar:** enum `PaletteColor` en `utils.hpp` sigue con los 16 índices antiguos (ZX Spectrum). Los índices 0-4 coinciden con CPC, pero 5+ mapean a colores diferentes (ej. `PaletteColor::BRIGHT_RED=5` ahora renderiza MAGENTA en CPC). `stringToColor()` también sin actualizar. `SPECTRUM_REFERENCE` en `palette_manager.cpp` sigue siendo la referencia ZX Spectrum de 16 colores. Falta decidir cómo migrar estos tres puntos cuando se vea el resultado visual.
- **`next()`/`previous()` en PaletteManager**: con una sola paleta hacen wrap al mismo índice (no crashean). El concepto de ciclar paletas queda desactivado de facto; posible futura reutilización para ciclar modos de ordenación.
### Otros
- Añadidos `desktop.ini` y `Thumbs.db` al `.gitignore`.
--- ---
## Overview (legacy) ## Overview (legacy)

View File

@@ -252,11 +252,9 @@ assets:
player: player:
BITMAP: BITMAP:
- ${PREFIX}/data/player/player.gif - ${PREFIX}/data/player/player.gif
- ${PREFIX}/data/player/player2.gif
- ${PREFIX}/data/player/player_game_over.gif - ${PREFIX}/data/player/player_game_over.gif
ANIMATION: ANIMATION:
- ${PREFIX}/data/player/player.yaml - ${PREFIX}/data/player/player.yaml
- ${PREFIX}/data/player/player2.yaml
- ${PREFIX}/data/player/player_game_over.yaml - ${PREFIX}/data/player/player_game_over.yaml
# ITEMS # ITEMS
@@ -276,30 +274,8 @@ assets:
- ${PREFIX}/data/sound/item.wav - ${PREFIX}/data/sound/item.wav
- ${PREFIX}/data/sound/death.wav - ${PREFIX}/data/sound/death.wav
- ${PREFIX}/data/sound/notify.wav - ${PREFIX}/data/sound/notify.wav
- ${PREFIX}/data/sound/jump1.wav - ${PREFIX}/data/sound/jump.wav
- ${PREFIX}/data/sound/jump2.wav - ${PREFIX}/data/sound/land.wav
- ${PREFIX}/data/sound/jump3.wav
- ${PREFIX}/data/sound/jump4.wav
- ${PREFIX}/data/sound/jump5.wav
- ${PREFIX}/data/sound/jump6.wav
- ${PREFIX}/data/sound/jump7.wav
- ${PREFIX}/data/sound/jump8.wav
- ${PREFIX}/data/sound/jump9.wav
- ${PREFIX}/data/sound/jump10.wav
- ${PREFIX}/data/sound/jump11.wav
- ${PREFIX}/data/sound/jump12.wav
- ${PREFIX}/data/sound/jump13.wav
- ${PREFIX}/data/sound/jump14.wav
- ${PREFIX}/data/sound/jump15.wav
- ${PREFIX}/data/sound/jump16.wav
- ${PREFIX}/data/sound/jump17.wav
- ${PREFIX}/data/sound/jump18.wav
- ${PREFIX}/data/sound/jump19.wav
- ${PREFIX}/data/sound/jump20.wav
- ${PREFIX}/data/sound/jump21.wav
- ${PREFIX}/data/sound/jump22.wav
- ${PREFIX}/data/sound/jump23.wav
- ${PREFIX}/data/sound/jump24.wav
# LOGO # LOGO
logo: logo:

BIN
data/player/player.aseprite Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 323 B

View File

@@ -4,7 +4,17 @@ frameWidth: 8
frameHeight: 16 frameHeight: 16
animations: animations:
- name: stand
speed: 0
loop: -1
frames: [0]
- name: default - name: default
speed: 0.1333 speed: 0.07
loop: 0 loop: 0
frames: [0, 1, 2, 3] frames: [0, 1, 2, 3]
- name: jump
speed: 0
loop: -1
frames: [4]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

View File

@@ -1,10 +0,0 @@
# player2 animation
tileSetFile: player2.gif
frameWidth: 8
frameHeight: 16
animations:
- name: default
speed: 0.1333
loop: 0
frames: [0, 1, 2, 3, 4, 5, 6, 7]

View File

@@ -1,9 +1,9 @@
# VOID MAIN # VOID MAIN
room: room:
name_en: "VOID MAIN"
name_ca: "VOID MAIN" name_ca: "VOID MAIN"
name_en: "VOID MAIN"
bgColor: black bgColor: black
border: magenta border: bright_black
tileSetFile: standard.gif tileSetFile: standard.gif
# Conexiones de la habitación (null = sin conexión) # Conexiones de la habitación (null = sin conexión)
@@ -44,7 +44,7 @@ tilemap:
enemies: enemies:
- animation: code.yaml - animation: code.yaml
position: {x: 3, y: 2} position: {x: 3, y: 2}
velocity: {x: 24.0, y: 0} velocity: {x: 24, y: 0}
boundaries: boundaries:
position1: {x: 3, y: 2} position1: {x: 3, y: 2}
position2: {x: 27, y: 2} position2: {x: 27, y: 2}
@@ -56,3 +56,4 @@ items:
tile: 42 tile: 42
position: {x: 21, y: 13} position: {x: 21, y: 13}
counter: 1 counter: 1

BIN
data/sound/jump.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -30,7 +30,7 @@ namespace Resource {
Cache* Cache::cache = nullptr; Cache* Cache::cache = nullptr;
// [SINGLETON] Crearemos el objeto cache con esta función estática // [SINGLETON] Crearemos el objeto cache con esta función estática
void Cache::init() { Cache::cache = new Cache(); } void Cache::init(LoadingMode mode) { Cache::cache = new Cache(mode); }
// [SINGLETON] Destruiremos el objeto cache con esta función estática // [SINGLETON] Destruiremos el objeto cache con esta función estática
void Cache::destroy() { delete Cache::cache; } void Cache::destroy() { delete Cache::cache; }
@@ -39,8 +39,8 @@ namespace Resource {
auto Cache::get() -> Cache* { return Cache::cache; } auto Cache::get() -> Cache* { return Cache::cache; }
// Constructor // Constructor
Cache::Cache() Cache::Cache(LoadingMode mode)
: loading_text_(Screen::get()->getText()) { : loading_mode_(mode), loading_text_(Screen::get()->getText()) {
load(); load();
} }
@@ -59,6 +59,7 @@ namespace Resource {
void Cache::load() { void Cache::load() {
// Nota: el overlay de debug (RenderInfo) se inicializa después de esta carga, // Nota: el overlay de debug (RenderInfo) se inicializa después de esta carga,
// por lo que updateZoomFactor() se llamará correctamente en RenderInfo::init(). // por lo que updateZoomFactor() se llamará correctamente en RenderInfo::init().
if (loading_mode_ == LoadingMode::EAGER) {
calculateTotal(); calculateTotal();
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK)); Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
std::cout << "\n** LOADING RESOURCES" << '\n'; std::cout << "\n** LOADING RESOURCES" << '\n';
@@ -71,6 +72,12 @@ namespace Resource {
loadRooms(); loadRooms();
createText(); createText();
std::cout << "\n** RESOURCES LOADED" << '\n'; std::cout << "\n** RESOURCES LOADED" << '\n';
} else {
std::cout << "\n** LAZY LOADING MODE **\n";
initLazyStubs();
createText(); // Carga fuentes bajo demanda a través de los getters lazy
std::cout << "\n** RESOURCE STUBS READY" << '\n';
}
} }
// Recarga todos los recursos // Recarga todos los recursos
@@ -80,10 +87,13 @@ namespace Resource {
} }
// Obtiene el sonido a partir de un nombre // Obtiene el sonido a partir de un nombre
auto Cache::getSound(const std::string& name) -> JA_Sound_t* { // NOLINT(readability-convert-member-functions-to-static) auto Cache::getSound(const std::string& name) -> JA_Sound_t* {
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> bool { return s.name == name; }); auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> bool { return s.name == name; });
if (it != sounds_.end()) { if (it != sounds_.end()) {
if (loading_mode_ == LoadingMode::LAZY && it->sound == nullptr) {
loadSoundByName(name);
}
return it->sound; return it->sound;
} }
@@ -92,10 +102,13 @@ namespace Resource {
} }
// Obtiene la música a partir de un nombre // Obtiene la música a partir de un nombre
auto Cache::getMusic(const std::string& name) -> JA_Music_t* { // NOLINT(readability-convert-member-functions-to-static) auto Cache::getMusic(const std::string& name) -> JA_Music_t* {
auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> bool { return m.name == name; }); auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> bool { return m.name == name; });
if (it != musics_.end()) { if (it != musics_.end()) {
if (loading_mode_ == LoadingMode::LAZY && it->music == nullptr) {
loadMusicByName(name);
}
return it->music; return it->music;
} }
@@ -104,10 +117,13 @@ namespace Resource {
} }
// Obtiene la surface a partir de un nombre // Obtiene la surface a partir de un nombre
auto Cache::getSurface(const std::string& name) -> std::shared_ptr<Surface> { // NOLINT(readability-convert-member-functions-to-static) auto Cache::getSurface(const std::string& name) -> std::shared_ptr<Surface> {
auto it = std::ranges::find_if(surfaces_, [&name](const auto& t) -> bool { return t.name == name; }); auto it = std::ranges::find_if(surfaces_, [&name](const auto& t) -> bool { return t.name == name; });
if (it != surfaces_.end()) { if (it != surfaces_.end()) {
if (loading_mode_ == LoadingMode::LAZY && it->surface == nullptr) {
loadSurfaceByName(name);
}
return it->surface; return it->surface;
} }
@@ -116,10 +132,13 @@ namespace Resource {
} }
// Obtiene la paleta a partir de un nombre // Obtiene la paleta a partir de un nombre
auto Cache::getPalette(const std::string& name) -> Palette { // NOLINT(readability-convert-member-functions-to-static) auto Cache::getPalette(const std::string& name) -> Palette {
auto it = std::ranges::find_if(palettes_, [&name](const auto& t) -> bool { return t.name == name; }); auto it = std::ranges::find_if(palettes_, [&name](const auto& t) -> bool { return t.name == name; });
if (it != palettes_.end()) { if (it != palettes_.end()) {
if (loading_mode_ == LoadingMode::LAZY && !it->loaded) {
loadPaletteByName(name);
}
return it->palette; return it->palette;
} }
@@ -128,10 +147,13 @@ namespace Resource {
} }
// Obtiene el fichero de texto a partir de un nombre // Obtiene el fichero de texto a partir de un nombre
auto Cache::getTextFile(const std::string& name) -> std::shared_ptr<Text::File> { // NOLINT(readability-convert-member-functions-to-static) auto Cache::getTextFile(const std::string& name) -> std::shared_ptr<Text::File> {
auto it = std::ranges::find_if(text_files_, [&name](const auto& t) -> bool { return t.name == name; }); auto it = std::ranges::find_if(text_files_, [&name](const auto& t) -> bool { return t.name == name; });
if (it != text_files_.end()) { if (it != text_files_.end()) {
if (loading_mode_ == LoadingMode::LAZY && it->text_file == nullptr) {
loadTextFileByName(name);
}
return it->text_file; return it->text_file;
} }
@@ -152,10 +174,13 @@ namespace Resource {
} }
// Obtiene los datos de animación parseados a partir de un nombre // Obtiene los datos de animación parseados a partir de un nombre
auto Cache::getAnimationData(const std::string& name) -> const AnimationResource& { // NOLINT(readability-convert-member-functions-to-static) auto Cache::getAnimationData(const std::string& name) -> const AnimationResource& {
auto it = std::ranges::find_if(animations_, [&name](const auto& a) -> bool { return a.name == name; }); auto it = std::ranges::find_if(animations_, [&name](const auto& a) -> bool { return a.name == name; });
if (it != animations_.end()) { if (it != animations_.end()) {
if (loading_mode_ == LoadingMode::LAZY && it->yaml_data.empty()) {
loadAnimationByName(name);
}
return *it; return *it;
} }
@@ -164,10 +189,13 @@ namespace Resource {
} }
// Obtiene la habitación a partir de un nombre // Obtiene la habitación a partir de un nombre
auto Cache::getRoom(const std::string& name) -> std::shared_ptr<Room::Data> { // NOLINT(readability-convert-member-functions-to-static) auto Cache::getRoom(const std::string& name) -> std::shared_ptr<Room::Data> {
auto it = std::ranges::find_if(rooms_, [&name](const auto& r) -> bool { return r.name == name; }); auto it = std::ranges::find_if(rooms_, [&name](const auto& r) -> bool { return r.name == name; });
if (it != rooms_.end()) { if (it != rooms_.end()) {
if (loading_mode_ == LoadingMode::LAZY && it->room == nullptr) {
loadRoomByName(name);
}
return it->room; return it->room;
} }
@@ -205,6 +233,11 @@ namespace Resource {
// Obtiene todas las habitaciones // Obtiene todas las habitaciones
auto Cache::getRooms() -> std::vector<RoomResource>& { auto Cache::getRooms() -> std::vector<RoomResource>& {
if (loading_mode_ == LoadingMode::LAZY) {
for (auto& r : rooms_) {
if (r.room == nullptr) { loadRoomByName(r.name); }
}
}
return rooms_; return rooms_;
} }
@@ -530,4 +563,144 @@ namespace Resource {
checkEvents(); checkEvents();
} }
// --- Modo lazy: stubs y cargadores bajo demanda ---------------------------
// Rellena los vectores de recursos con entradas name-only (sin contenido)
void Cache::initLazyStubs() {
sounds_.clear();
musics_.clear();
surfaces_.clear();
palettes_.clear();
text_files_.clear();
animations_.clear();
rooms_.clear();
for (const auto& l : List::get()->getListByType(List::Type::SOUND)) {
sounds_.emplace_back(SoundResource{.name = getFileName(l), .sound = nullptr});
}
for (const auto& l : List::get()->getListByType(List::Type::MUSIC)) {
musics_.emplace_back(MusicResource{.name = getFileName(l), .music = nullptr});
}
for (const auto& l : List::get()->getListByType(List::Type::BITMAP)) {
surfaces_.emplace_back(SurfaceResource{.name = getFileName(l), .surface = nullptr});
}
for (const auto& l : List::get()->getListByType(List::Type::PALETTE)) {
palettes_.emplace_back(ResourcePalette{.name = getFileName(l)});
}
for (const auto& l : List::get()->getListByType(List::Type::FONT)) {
text_files_.emplace_back(TextFileResource{.name = getFileName(l), .text_file = nullptr});
}
for (const auto& l : List::get()->getListByType(List::Type::ANIMATION)) {
animations_.emplace_back(AnimationResource{.name = getFileName(l), .yaml_data = {}});
}
for (const auto& l : List::get()->getListByType(List::Type::ROOM)) {
rooms_.emplace_back(RoomResource{.name = getFileName(l), .room = nullptr});
}
}
void Cache::loadSoundByName(const std::string& name) {
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) { return s.name == name; });
if (it == sounds_.end()) { return; }
auto path = List::get()->get(name);
try {
auto bytes = Helper::loadFile(path);
JA_Sound_t* sound = nullptr;
if (!bytes.empty()) { sound = JA_LoadSound(bytes.data(), static_cast<Uint32>(bytes.size())); }
if (sound == nullptr) { sound = JA_LoadSound(path.c_str()); }
if (sound == nullptr) { throw std::runtime_error("Failed to decode audio file"); }
it->sound = sound;
std::cout << "[lazy] Sound loaded: " << name << '\n';
} catch (const std::exception& e) {
throwLoadError("SOUND", path, e);
}
}
void Cache::loadMusicByName(const std::string& name) {
auto it = std::ranges::find_if(musics_, [&name](const auto& m) { return m.name == name; });
if (it == musics_.end()) { return; }
auto path = List::get()->get(name);
try {
auto bytes = Helper::loadFile(path);
JA_Music_t* music = nullptr;
if (!bytes.empty()) { music = JA_LoadMusic(bytes.data(), static_cast<Uint32>(bytes.size())); }
if (music == nullptr) { music = JA_LoadMusic(path.c_str()); }
if (music == nullptr) { throw std::runtime_error("Failed to decode music file"); }
it->music = music;
std::cout << "[lazy] Music loaded: " << name << '\n';
} catch (const std::exception& e) {
throwLoadError("MUSIC", path, e);
}
}
void Cache::loadSurfaceByName(const std::string& name) {
auto it = std::ranges::find_if(surfaces_, [&name](const auto& s) { return s.name == name; });
if (it == surfaces_.end()) { return; }
auto path = List::get()->get(name);
try {
it->surface = std::make_shared<Surface>(path);
it->surface->setTransparentColor(0);
// Superficies con color transparente específico (replica el ajuste de loadSurfaces)
if (name == "loading_screen_color.gif" || name == "ending1.gif" || name == "ending2.gif" ||
name == "ending3.gif" || name == "ending4.gif" || name == "ending5.gif") {
it->surface->setTransparentColor();
} else if (name == "standard.gif") {
it->surface->setTransparentColor(16);
}
std::cout << "[lazy] Surface loaded: " << name << '\n';
} catch (const std::exception& e) {
throwLoadError("BITMAP", path, e);
}
}
void Cache::loadPaletteByName(const std::string& name) {
auto it = std::ranges::find_if(palettes_, [&name](const auto& p) { return p.name == name; });
if (it == palettes_.end()) { return; }
auto path = List::get()->get(name);
try {
it->palette = readPalFile(path);
it->loaded = true;
std::cout << "[lazy] Palette loaded: " << name << '\n';
} catch (const std::exception& e) {
throwLoadError("PALETTE", path, e);
}
}
void Cache::loadTextFileByName(const std::string& name) {
auto it = std::ranges::find_if(text_files_, [&name](const auto& t) { return t.name == name; });
if (it == text_files_.end()) { return; }
auto path = List::get()->get(name);
try {
it->text_file = Text::loadTextFile(path);
std::cout << "[lazy] TextFile loaded: " << name << '\n';
} catch (const std::exception& e) {
throwLoadError("FONT", path, e);
}
}
void Cache::loadAnimationByName(const std::string& name) {
auto it = std::ranges::find_if(animations_, [&name](const auto& a) { return a.name == name; });
if (it == animations_.end()) { return; }
auto path = List::get()->get(name);
try {
auto bytes = Helper::loadFile(path);
if (bytes.empty()) { throw std::runtime_error("File is empty or could not be loaded"); }
it->yaml_data = bytes;
std::cout << "[lazy] Animation loaded: " << name << '\n';
} catch (const std::exception& e) {
throwLoadError("ANIMATION", path, e);
}
}
void Cache::loadRoomByName(const std::string& name) {
auto it = std::ranges::find_if(rooms_, [&name](const auto& r) { return r.name == name; });
if (it == rooms_.end()) { return; }
auto path = List::get()->get(name);
try {
it->room = std::make_shared<Room::Data>(Room::loadYAML(path));
std::cout << "[lazy] Room loaded: " << name << '\n';
} catch (const std::exception& e) {
throwLoadError("ROOM", path, e);
}
}
} // namespace Resource } // namespace Resource

View File

@@ -11,7 +11,12 @@ namespace Resource {
class Cache { class Cache {
public: public:
static void init(); // Inicialización singleton enum class LoadingMode {
EAGER, // Carga todos los recursos en init() (comportamiento por defecto, producción)
LAZY // Sólo registra nombres; carga cada recurso la primera vez que se pide (desarrollo)
};
static void init(LoadingMode mode = LoadingMode::EAGER); // Inicialización singleton
static void destroy(); // Destrucción singleton static void destroy(); // Destrucción singleton
static auto get() -> Cache*; // Acceso al singleton static auto get() -> Cache*; // Acceso al singleton
@@ -57,6 +62,18 @@ namespace Resource {
void loadRooms(); void loadRooms();
void createText(); void createText();
// Registro de stubs (modo lazy): sólo nombres, sin contenido
void initLazyStubs();
// Cargadores bajo demanda (modo lazy): cargan un recurso individual por nombre
void loadSoundByName(const std::string& name);
void loadMusicByName(const std::string& name);
void loadSurfaceByName(const std::string& name);
void loadPaletteByName(const std::string& name);
void loadTextFileByName(const std::string& name);
void loadAnimationByName(const std::string& name);
void loadRoomByName(const std::string& name);
// Métodos de limpieza // Métodos de limpieza
void clear(); void clear();
void clearSounds(); void clearSounds();
@@ -73,9 +90,11 @@ namespace Resource {
[[noreturn]] static void throwLoadError(const std::string& asset_type, const std::string& file_path, const std::exception& e); [[noreturn]] static void throwLoadError(const std::string& asset_type, const std::string& file_path, const std::exception& e);
// Constructor y destructor // Constructor y destructor
Cache(); explicit Cache(LoadingMode mode);
~Cache() = default; ~Cache() = default;
LoadingMode loading_mode_ = LoadingMode::EAGER;
// Singleton instance // Singleton instance
static Cache* cache; static Cache* cache;

View File

@@ -35,6 +35,7 @@ struct SurfaceResource {
struct ResourcePalette { struct ResourcePalette {
std::string name; // Nombre de la surface std::string name; // Nombre de la surface
Palette palette{}; // Paleta Palette palette{}; // Paleta
bool loaded{false}; // Usado por el modo lazy para saber si ya se ha leído del disco
}; };
// Estructura para almacenar ficheros TextFile y su nombre // Estructura para almacenar ficheros TextFile y su nombre

View File

@@ -134,6 +134,7 @@ void Debug::loadFromFile() {
spawn_settings_.spawn_y = Defaults::Game::Player::SPAWN_Y; spawn_settings_.spawn_y = Defaults::Game::Player::SPAWN_Y;
spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP; spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP;
initial_scene_ = SceneManager::Scene::GAME; initial_scene_ = SceneManager::Scene::GAME;
lazy_loading_ = false;
std::ifstream file(debug_file_path_); std::ifstream file(debug_file_path_);
if (!file.good()) { if (!file.good()) {
@@ -162,6 +163,9 @@ void Debug::loadFromFile() {
if (yaml.contains("initial_scene")) { if (yaml.contains("initial_scene")) {
initial_scene_ = sceneFromString(yaml["initial_scene"].get_value<std::string>()); initial_scene_ = sceneFromString(yaml["initial_scene"].get_value<std::string>());
} }
if (yaml.contains("lazy_loading")) {
lazy_loading_ = yaml["lazy_loading"].get_value<bool>();
}
} catch (...) { } catch (...) {
// YAML inválido: resetear a defaults y sobreescribir // YAML inválido: resetear a defaults y sobreescribir
spawn_settings_.room = Defaults::Game::Room::INITIAL; spawn_settings_.room = Defaults::Game::Room::INITIAL;
@@ -169,6 +173,7 @@ void Debug::loadFromFile() {
spawn_settings_.spawn_y = Defaults::Game::Player::SPAWN_Y; spawn_settings_.spawn_y = Defaults::Game::Player::SPAWN_Y;
spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP; spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP;
initial_scene_ = SceneManager::Scene::GAME; initial_scene_ = SceneManager::Scene::GAME;
lazy_loading_ = false;
saveToFile(); saveToFile();
} }
} }
@@ -184,6 +189,7 @@ void Debug::saveToFile() const {
file << "spawn_y: " << (spawn_settings_.spawn_y / Tile::SIZE) << " # en tiles\n"; file << "spawn_y: " << (spawn_settings_.spawn_y / Tile::SIZE) << " # en tiles\n";
file << "spawn_flip: " << ((spawn_settings_.flip == Flip::RIGHT) ? "right" : "left") << "\n"; file << "spawn_flip: " << ((spawn_settings_.flip == Flip::RIGHT) ? "right" : "left") << "\n";
file << "initial_scene: " << sceneToString(initial_scene_) << "\n"; file << "initial_scene: " << sceneToString(initial_scene_) << "\n";
file << "lazy_loading: " << (lazy_loading_ ? "true" : "false") << " # carga perezosa de recursos (dev)\n";
} }
#endif // _DEBUG #endif // _DEBUG

View File

@@ -47,6 +47,7 @@ class Debug {
void setSpawnSettings(const SpawnSettings& s) { spawn_settings_ = s; } // Establece los valores de spawn void setSpawnSettings(const SpawnSettings& s) { spawn_settings_ = s; } // Establece los valores de spawn
[[nodiscard]] auto getInitialScene() const -> SceneManager::Scene { return initial_scene_; } // Obtiene la escena inicial de debug [[nodiscard]] auto getInitialScene() const -> SceneManager::Scene { return initial_scene_; } // Obtiene la escena inicial de debug
void setInitialScene(SceneManager::Scene s) { initial_scene_ = s; } // Establece la escena inicial de debug void setInitialScene(SceneManager::Scene s) { initial_scene_ = s; } // Establece la escena inicial de debug
[[nodiscard]] auto getLazyLoading() const -> bool { return lazy_loading_; } // Indica si el modo lazy de recursos está activo
private: private:
static Debug* debug; // [SINGLETON] Objeto privado static Debug* debug; // [SINGLETON] Objeto privado
@@ -64,6 +65,7 @@ class Debug {
std::string debug_file_path_; // Ruta del archivo debug.yaml std::string debug_file_path_; // Ruta del archivo debug.yaml
SpawnSettings spawn_settings_; // Configuración de spawn para debug SpawnSettings spawn_settings_; // Configuración de spawn para debug
SceneManager::Scene initial_scene_ = SceneManager::Scene::GAME; // Escena inicial en debug SceneManager::Scene initial_scene_ = SceneManager::Scene::GAME; // Escena inicial en debug
bool lazy_loading_ = false; // Carga lazy de recursos (dev)
}; };
#endif // _DEBUG #endif // _DEBUG

View File

@@ -160,8 +160,21 @@ Director::Director() {
// Crea los objetos // Crea los objetos
Screen::init(); Screen::init();
#ifdef _DEBUG
// En debug inicializamos Debug antes de Cache para leer el flag lazy_loading
Debug::init();
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
Debug::get()->loadFromFile();
#endif
// Initialize resources (works for both release and development) // Initialize resources (works for both release and development)
#ifdef _DEBUG
Resource::Cache::init(Debug::get()->getLazyLoading()
? Resource::Cache::LoadingMode::LAZY
: Resource::Cache::LoadingMode::EAGER);
#else
Resource::Cache::init(); Resource::Cache::init();
#endif
Notifier::init("", "8bithud"); Notifier::init("", "8bithud");
RenderInfo::init(); RenderInfo::init();
Console::init("8bithud"); Console::init("8bithud");
@@ -182,9 +195,6 @@ Director::Director() {
Input::get()->applyGamepadBindingsFromOptions(); Input::get()->applyGamepadBindingsFromOptions();
#ifdef _DEBUG #ifdef _DEBUG
Debug::init();
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
Debug::get()->loadFromFile();
SceneManager::current = Debug::get()->getInitialScene(); SceneManager::current = Debug::get()->getInitialScene();
MapEditor::init(); MapEditor::init();
#endif #endif

View File

@@ -1131,6 +1131,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
std::string val = toLower(value); std::string val = toLower(value);
if (property == "BGCOLOR") { if (property == "BGCOLOR") {
val = colorToString(stringToColor(val)); // Normaliza a nombre canónico (acepta nombres e índices)
room_data_.bg_color = val; room_data_.bg_color = val;
room_->setBgColor(val); room_->setBgColor(val);
autosave(); autosave();
@@ -1138,6 +1139,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
} }
if (property == "BORDER") { if (property == "BORDER") {
val = colorToString(stringToColor(val));
room_data_.border_color = val; room_data_.border_color = val;
Screen::get()->setBorderColor(stringToColor(val)); Screen::get()->setBorderColor(stringToColor(val));
autosave(); autosave();
@@ -1145,6 +1147,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
} }
if (property == "ITEMCOLOR1") { if (property == "ITEMCOLOR1") {
val = colorToString(stringToColor(val));
room_data_.item_color1 = val; room_data_.item_color1 = val;
room_->setItemColors(room_data_.item_color1, room_data_.item_color2); room_->setItemColors(room_data_.item_color1, room_data_.item_color2);
autosave(); autosave();
@@ -1152,6 +1155,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
} }
if (property == "ITEMCOLOR2") { if (property == "ITEMCOLOR2") {
val = colorToString(stringToColor(val));
room_data_.item_color2 = val; room_data_.item_color2 = val;
room_->setItemColors(room_data_.item_color1, room_data_.item_color2); room_->setItemColors(room_data_.item_color1, room_data_.item_color2);
autosave(); autosave();

View File

@@ -76,11 +76,8 @@ void Player::move(float delta_time) {
case State::ON_SLOPE: case State::ON_SLOPE:
moveOnSlope(delta_time); moveOnSlope(delta_time);
break; break;
case State::JUMPING: case State::ON_AIR:
moveJumping(delta_time); moveOnAir(delta_time);
break;
case State::FALLING:
moveFalling(delta_time);
break; break;
} }
syncSpriteAndCollider(); // Actualiza la posición del sprite y las colisiones syncSpriteAndCollider(); // Actualiza la posición del sprite y las colisiones
@@ -95,11 +92,8 @@ void Player::move(float delta_time) {
case State::ON_SLOPE: case State::ON_SLOPE:
Debug::get()->set("P.STATE", "ON_SLOPE"); Debug::get()->set("P.STATE", "ON_SLOPE");
break; break;
case State::JUMPING: case State::ON_AIR:
Debug::get()->set("P.STATE", "JUMPING"); Debug::get()->set("P.STATE", "ON_AIR");
break;
case State::FALLING:
Debug::get()->set("P.STATE", "FALLING");
break; break;
} }
#endif #endif
@@ -117,7 +111,8 @@ void Player::handleConveyorBelts() {
void Player::handleShouldFall() { void Player::handleShouldFall() {
if (!isOnFloor() and (state_ == State::ON_GROUND || state_ == State::ON_SLOPE)) { if (!isOnFloor() and (state_ == State::ON_GROUND || state_ == State::ON_SLOPE)) {
transitionToState(State::FALLING); vy_ = 0.0F;
transitionToState(State::ON_AIR);
} }
} }
@@ -128,37 +123,24 @@ void Player::transitionToState(State state) {
switch (state) { switch (state) {
case State::ON_GROUND: case State::ON_GROUND:
vy_ = 0; vy_ = 0;
handleDeathByFalling(); if (previous_state_ == State::ON_AIR) {
resetSoundControllersOnLanding(); Audio::get()->playSound(land_sound_, Audio::Group::GAME);
}
current_slope_ = nullptr; current_slope_ = nullptr;
break; break;
case State::ON_SLOPE: case State::ON_SLOPE:
vy_ = 0; vy_ = 0;
handleDeathByFalling(); if (previous_state_ == State::ON_AIR) {
resetSoundControllersOnLanding(); Audio::get()->playSound(land_sound_, Audio::Group::GAME);
}
updateCurrentSlope(); updateCurrentSlope();
if (current_slope_ == nullptr) { if (current_slope_ == nullptr) {
// Los pies no coinciden con ninguna rampa: tratar como suelo plano // Los pies no coinciden con ninguna rampa: tratar como suelo plano
state_ = State::ON_GROUND; state_ = State::ON_GROUND;
} }
break; break;
case State::JUMPING: case State::ON_AIR:
// Puede saltar desde ON_GROUND o ON_SLOPE
if (previous_state_ == State::ON_GROUND || previous_state_ == State::ON_SLOPE) {
vy_ = -MAX_VY;
last_grounded_position_ = y_;
updateVelocity();
jump_sound_ctrl_.start();
current_slope_ = nullptr;
}
break;
case State::FALLING:
fall_start_position_ = static_cast<int>(y_);
last_grounded_position_ = static_cast<int>(y_); last_grounded_position_ = static_cast<int>(y_);
vy_ = MAX_VY;
vx_ = 0.0F;
jump_sound_ctrl_.reset();
fall_sound_ctrl_.start(y_);
current_slope_ = nullptr; current_slope_ = nullptr;
break; break;
} }
@@ -172,48 +154,48 @@ void Player::updateState(float delta_time) {
case State::ON_SLOPE: case State::ON_SLOPE:
updateOnSlope(delta_time); updateOnSlope(delta_time);
break; break;
case State::JUMPING: case State::ON_AIR:
updateJumping(delta_time); updateOnAir(delta_time);
break;
case State::FALLING:
updateFalling(delta_time);
break; break;
} }
} }
// Inicia un salto desde ON_GROUND/ON_SLOPE: velocidad inicial hacia arriba + sonido + transición
void Player::startJump() {
vy_ = JUMP_VELOCITY;
last_grounded_position_ = y_;
Audio::get()->playSound(jump_sound_, Audio::Group::GAME);
transitionToState(State::ON_AIR);
}
// Actualización lógica del estado ON_GROUND // Actualización lógica del estado ON_GROUND
void Player::updateOnGround(float delta_time) { void Player::updateOnGround(float delta_time) {
(void)delta_time; // No usado en este método, pero se mantiene por consistencia (void)delta_time; // No usado en este método, pero se mantiene por consistencia
handleConveyorBelts(); // Gestiona las cintas transportadoras handleConveyorBelts(); // Gestiona las cintas transportadoras
handleShouldFall(); // Verifica si debe caer (no tiene suelo)
// Verifica si el jugador quiere saltar // El salto tiene prioridad sobre la caída por falta de suelo
if (wanna_jump_) { transitionToState(State::JUMPING); } if (wanna_jump_) {
startJump();
return;
}
handleShouldFall(); // Verifica si debe caer (no tiene suelo)
} }
// Actualización lógica del estado ON_SLOPE // Actualización lógica del estado ON_SLOPE
void Player::updateOnSlope(float delta_time) { void Player::updateOnSlope(float delta_time) {
(void)delta_time; // No usado en este método, pero se mantiene por consistencia (void)delta_time; // No usado en este método, pero se mantiene por consistencia
if (wanna_jump_) {
startJump();
return;
}
handleShouldFall(); handleShouldFall();
// NOTA: No llamamos handleShouldFall() aquí porque moveOnSlope() ya maneja
// todas las condiciones de salida de la rampa (out of bounds, transición a superficie plana)
// Verifica si el jugador quiere saltar
if (wanna_jump_) { transitionToState(State::JUMPING); }
} }
// Actualización lógica del estado JUMPING // Actualización lógica del estado ON_AIR
void Player::updateJumping(float delta_time) { void Player::updateOnAir(float delta_time) {
auto_movement_ = false; // Desactiva el movimiento automático durante el salto (void)delta_time;
playJumpSound(delta_time); // Reproduce los sonidos de salto auto_movement_ = false; // Desactiva el movimiento automático en el aire
handleJumpEnd(); // Verifica si el salto ha terminado (alcanzó la altura inicial)
}
// Actualización lógica del estado FALLING
void Player::updateFalling(float delta_time) {
auto_movement_ = false; // Desactiva el movimiento automático durante la caída
playFallSound(delta_time); // Reproduce los sonidos de caída
} }
// Movimiento físico del estado ON_GROUND // Movimiento físico del estado ON_GROUND
@@ -256,7 +238,8 @@ void Player::moveOnSlope(float delta_time) {
// Verificar rampa válida antes de comprobar velocidad: si no hay rampa siempre caer, // Verificar rampa válida antes de comprobar velocidad: si no hay rampa siempre caer,
// independientemente de si hay o no input (evita bloqueo con vx_=0 y slope null) // independientemente de si hay o no input (evita bloqueo con vx_=0 y slope null)
if (current_slope_ == nullptr) { if (current_slope_ == nullptr) {
transitionToState(State::FALLING); vy_ = 0.0F;
transitionToState(State::ON_AIR);
return; return;
} }
@@ -320,7 +303,8 @@ void Player::moveOnSlope(float delta_time) {
transitionToState(State::ON_GROUND); transitionToState(State::ON_GROUND);
} else { } else {
// Sin soporte: empezar a caer // Sin soporte: empezar a caer
transitionToState(State::FALLING); vy_ = 0.0F;
transitionToState(State::ON_AIR);
} }
return; return;
} }
@@ -332,67 +316,37 @@ void Player::moveOnSlope(float delta_time) {
}*/ }*/
} }
// Movimiento físico del estado JUMPING // Movimiento físico del estado ON_AIR
void Player::moveJumping(float delta_time) { // El jugador puede moverse horizontalmente en el aire y la gravedad siempre actúa.
// Movimiento horizontal void Player::moveOnAir(float delta_time) {
// Movimiento horizontal libre según wanna_go_ (permite girar en el aire)
updateVelocity();
applyHorizontalMovement(delta_time); applyHorizontalMovement(delta_time);
// Movimiento vertical // Gravedad
applyGravity(delta_time); applyGravity(delta_time);
const float DISPLACEMENT_Y = vy_ * delta_time; const float DISPLACEMENT_Y = vy_ * delta_time;
// Movimiento vertical hacia arriba
// Subiendo: comprobar techo
if (vy_ < 0.0F) { if (vy_ < 0.0F) {
const SDL_FRect PROJECTION = getProjection(Direction::UP, DISPLACEMENT_Y); const SDL_FRect PROJECTION = getProjection(Direction::UP, DISPLACEMENT_Y);
// Comprueba la colisión
const int POS = room_->checkBottomSurfaces(PROJECTION); const int POS = room_->checkBottomSurfaces(PROJECTION);
// Calcula la nueva posición
if (POS == Collision::NONE) { if (POS == Collision::NONE) {
// Si no hay colisión
y_ += DISPLACEMENT_Y; y_ += DISPLACEMENT_Y;
} else { } else {
// Si hay colisión lo mueve hasta donde no colisiona -> FALLING // Choque con techo: se pega por debajo y empieza a caer
y_ = POS + 1; y_ = POS + 1;
transitionToState(State::FALLING); vy_ = 0.0F;
} }
return;
} }
// Movimiento vertical hacia abajo
else if (vy_ > 0.0F) { // Bajando: comprobar aterrizaje en superficies y rampas
// Crea el rectangulo de proyección en el eje Y para ver si colisiona if (vy_ > 0.0F) {
const SDL_FRect PROJECTION = getProjection(Direction::DOWN, DISPLACEMENT_Y); const SDL_FRect PROJECTION = getProjection(Direction::DOWN, DISPLACEMENT_Y);
// JUMPING colisiona con rampas solo si vx_ == 0
if (vx_ == 0.0F) {
handleLandingFromAir(DISPLACEMENT_Y, PROJECTION); handleLandingFromAir(DISPLACEMENT_Y, PROJECTION);
} else {
// Comprueba la colisión con las superficies y las cintas transportadoras (sin rampas)
// Extendemos 1px hacia arriba para detectar suelos traversados ligeramente al
// entrar horizontalmente (consecuencia del margen h=HEIGHT-1 en la proyección horizontal)
const SDL_FRect ADJ = {.x = PROJECTION.x, .y = PROJECTION.y - 1.0F, .w = PROJECTION.w, .h = PROJECTION.h + 1.0F};
const float POS = std::max(room_->checkTopSurfaces(ADJ), room_->checkAutoSurfaces(ADJ));
if (POS != Collision::NONE) {
// Si hay colisión lo mueve hasta donde no colisiona y pasa a estar sobre la superficie
y_ = POS - HEIGHT;
transitionToState(State::ON_GROUND);
} else {
// Esta saltando con movimiento horizontal y no hay colisión con los muros
// Calcula la nueva posición (atraviesa rampas)
y_ += DISPLACEMENT_Y;
} }
}
}
}
// Movimiento físico del estado FALLING
void Player::moveFalling(float delta_time) {
// Crea el rectangulo de proyección en el eje Y para ver si colisiona
const float DISPLACEMENT = vy_ * delta_time;
const SDL_FRect PROJECTION = getProjection(Direction::DOWN, DISPLACEMENT);
// Comprueba aterrizaje en superficies y rampas
handleLandingFromAir(DISPLACEMENT, PROJECTION);
} }
// Comprueba si está situado en alguno de los cuatro bordes de la habitación // Comprueba si está situado en alguno de los cuatro bordes de la habitación
@@ -410,10 +364,8 @@ auto Player::handleBorders() -> Room::Border {
} }
if (y_ + HEIGHT > PlayArea::BOTTOM) { if (y_ + HEIGHT > PlayArea::BOTTOM) {
// Si llega en estado terminal, muere y no cruza // Restricción de muerte por altura de caída desactivada
const bool SHOULD_DIE = static_cast<int>(y_) - last_grounded_position_ > MAX_FALLING_HEIGHT; return Room::Border::BOTTOM;
if (SHOULD_DIE) { markAsDead(); }
return is_alive_ ? Room::Border::BOTTOM : Room::Border::NONE;
} }
return Room::Border::NONE; return Room::Border::NONE;
@@ -454,9 +406,8 @@ void Player::switchBorders() {
// Aplica gravedad al jugador // Aplica gravedad al jugador
void Player::applyGravity(float delta_time) { void Player::applyGravity(float delta_time) {
// La gravedad solo se aplica cuando el jugador esta saltando // La gravedad solo se aplica cuando el jugador esta en el aire
// Nunca mientras cae o esta de pie if (state_ == State::ON_AIR) {
if (state_ == State::JUMPING) {
vy_ += GRAVITY_FORCE * delta_time; vy_ += GRAVITY_FORCE * delta_time;
vy_ = std::min(vy_, MAX_VY); vy_ = std::min(vy_, MAX_VY);
} }
@@ -464,37 +415,13 @@ void Player::applyGravity(float delta_time) {
// Establece la animación del jugador // Establece la animación del jugador
void Player::animate(float delta_time) { // NOLINT(readability-make-member-function-const) void Player::animate(float delta_time) { // NOLINT(readability-make-member-function-const)
if (vx_ != 0) { if (state_ == State::ON_AIR) {
sprite_->setCurrentAnimation("jump");
} else if (vx_ != 0) {
sprite_->setCurrentAnimation("default");
sprite_->update(delta_time); sprite_->update(delta_time);
} } else {
} sprite_->setCurrentAnimation("stand");
// Comprueba si ha finalizado el salto al alcanzar la altura de inicio
void Player::handleJumpEnd() {
// Si el jugador vuelve EXACTAMENTE a la altura inicial, debe CONTINUAR en JUMPING
// Solo cuando la SUPERA (desciende más allá) cambia a FALLING
if (state_ == State::JUMPING && vy_ > 0.0F && static_cast<int>(y_) > last_grounded_position_) {
transitionToState(State::FALLING);
}
}
// Calcula y reproduce el sonido de salto basado en tiempo transcurrido
void Player::playJumpSound(float delta_time) { // NOLINT(readability-convert-member-functions-to-static)
size_t sound_index;
if (jump_sound_ctrl_.shouldPlay(delta_time, sound_index)) {
if (sound_index < jumping_sound_.size()) {
Audio::get()->playSound(jumping_sound_[sound_index], Audio::Group::GAME);
}
}
}
// Calcula y reproduce el sonido de caída basado en distancia vertical recorrida
void Player::playFallSound(float delta_time) { // NOLINT(readability-convert-member-functions-to-static)
size_t sound_index;
if (fall_sound_ctrl_.shouldPlay(delta_time, y_, sound_index)) {
if (sound_index < falling_sound_.size()) {
Audio::get()->playSound(falling_sound_[sound_index], Audio::Group::GAME);
}
} }
} }
@@ -665,98 +592,10 @@ void Player::updateFeet() {
.y = y_ + HEIGHT}; .y = y_ + HEIGHT};
} }
// Inicializa los sonidos de salto y caida // Inicializa los sonidos de salto y aterrizaje
void Player::initSounds() { // NOLINT(readability-convert-member-functions-to-static) void Player::initSounds() { // NOLINT(readability-convert-member-functions-to-static)
for (int i = 0; i < 24; ++i) { jump_sound_ = Resource::Cache::get()->getSound("jump.wav");
std::string sound_file = "jump" + std::to_string(i + 1) + ".wav"; land_sound_ = Resource::Cache::get()->getSound("land.wav");
jumping_sound_[i] = Resource::Cache::get()->getSound(sound_file);
if (i >= 10) { // i+1 >= 11
falling_sound_[i - 10] = Resource::Cache::get()->getSound(sound_file);
}
}
}
// Implementación de JumpSoundController::start
void Player::JumpSoundController::start() {
current_index = 0;
elapsed_time = 0.0F;
active = true;
}
// Implementación de JumpSoundController::reset
void Player::JumpSoundController::reset() {
active = false;
current_index = 0;
elapsed_time = 0.0F;
}
// Implementación de JumpSoundController::shouldPlay
auto Player::JumpSoundController::shouldPlay(float delta_time, size_t& out_index) -> bool {
if (!active) {
return false;
}
// Acumula el tiempo transcurrido durante el salto
elapsed_time += delta_time;
// Calcula qué sonido debería estar sonando según el tiempo
size_t target_index = FIRST_SOUND + static_cast<size_t>((elapsed_time / SECONDS_PER_SOUND));
target_index = std::min(target_index, LAST_SOUND);
// Reproduce si hemos avanzado a un nuevo sonido
if (target_index > current_index) {
current_index = target_index;
out_index = current_index;
return true; // NOLINT(readability-simplify-boolean-expr)
}
return false;
}
// Implementación de FallSoundController::start
void Player::FallSoundController::start(float start_y) {
current_index = 0;
distance_traveled = 0.0F;
last_y = start_y;
active = true;
}
// Implementación de FallSoundController::reset
void Player::FallSoundController::reset() {
active = false;
current_index = 0;
distance_traveled = 0.0F;
}
// Implementación de FallSoundController::shouldPlay
auto Player::FallSoundController::shouldPlay(float delta_time, float current_y, size_t& out_index) -> bool {
(void)delta_time; // No usado actualmente, pero recibido por consistencia
if (!active) {
return false;
}
// Acumula la distancia recorrida (solo hacia abajo)
if (current_y > last_y) {
distance_traveled += (current_y - last_y);
}
last_y = current_y;
// Calcula qué sonido debería estar sonando según el intervalo
size_t target_index = FIRST_SOUND + static_cast<size_t>((distance_traveled / PIXELS_PER_SOUND));
// El sonido a reproducir se limita a LAST_SOUND (13), pero el índice interno sigue creciendo
size_t sound_to_play = std::min(target_index, LAST_SOUND);
// Reproduce si hemos avanzado a un nuevo índice (permite repetición de sonido 13)
if (target_index > current_index) {
current_index = target_index; // Guardamos el índice real (puede ser > LAST_SOUND)
out_index = sound_to_play; // Pero reproducimos LAST_SOUND cuando corresponde
return true;
}
return false;
} }
// Aplica los valores de spawn al jugador // Aplica los valores de spawn al jugador
@@ -810,14 +649,6 @@ void Player::placeSprite() {
sprite_->setPos(x_, y_); sprite_->setPos(x_, y_);
} }
// Gestiona la muerta al ccaer desde muy alto
void Player::handleDeathByFalling() {
const int FALL_DISTANCE = static_cast<int>(y_) - last_grounded_position_;
if (previous_state_ == State::FALLING && FALL_DISTANCE > MAX_FALLING_HEIGHT) {
markAsDead(); // Muere si cae más de 32 píxeles
}
}
// Calcula la velocidad en x // Calcula la velocidad en x
void Player::updateVelocity() { void Player::updateVelocity() {
if (auto_movement_) { if (auto_movement_) {
@@ -900,12 +731,6 @@ auto Player::handleLandingFromAir(float displacement, const SDL_FRect& projectio
return false; return false;
} }
// Resetea los controladores de sonido al aterrizar
void Player::resetSoundControllersOnLanding() {
jump_sound_ctrl_.reset();
fall_sound_ctrl_.reset();
}
// Devuelve el rectangulo de proyeccion // Devuelve el rectangulo de proyeccion
auto Player::getProjection(Direction direction, float displacement) -> SDL_FRect { // NOLINT(readability-convert-member-functions-to-static) auto Player::getProjection(Direction direction, float displacement) -> SDL_FRect { // NOLINT(readability-convert-member-functions-to-static)
switch (direction) { switch (direction) {

View File

@@ -21,8 +21,7 @@ class Player {
enum class State { enum class State {
ON_GROUND, // En suelo plano o conveyor belt ON_GROUND, // En suelo plano o conveyor belt
ON_SLOPE, // En rampa/pendiente ON_SLOPE, // En rampa/pendiente
JUMPING, ON_AIR, // En el aire (saltando, cayendo o caminando al vacío)
FALLING,
}; };
enum class Direction { enum class Direction {
@@ -34,10 +33,10 @@ class Player {
}; };
// --- Constantes de física (públicas para permitir cálculos en structs) --- // --- Constantes de física (públicas para permitir cálculos en structs) ---
static constexpr float HORIZONTAL_VELOCITY = 40.0F; // Velocidad horizontal en pixels/segundo (0.6 * 66.67fps) static constexpr float HORIZONTAL_VELOCITY = 60.0F; // Velocidad horizontal en pixels/segundo
static constexpr float MAX_VY = 80.0F; // Velocidad vertical máxima en pixels/segundo (1.2 * 66.67fps) static constexpr float MAX_VY = 160.0F; // Velocidad vertical máxima en pixels/segundo
static constexpr float JUMP_VELOCITY = -80.0F; // Velocidad inicial del salto en pixels/segundo static constexpr float JUMP_VELOCITY = -140.0F; // Velocidad inicial del salto en pixels/segundo
static constexpr float GRAVITY_FORCE = 155.6F; // Fuerza de gravedad en pixels/segundo² (0.035 * 66.67²) static constexpr float GRAVITY_FORCE = 360.0F; // Fuerza de gravedad en pixels/segundo²
struct SpawnData { struct SpawnData {
float x = 0; float x = 0;
@@ -55,37 +54,6 @@ class Player {
std::shared_ptr<Room> room = nullptr; std::shared_ptr<Room> room = nullptr;
}; };
struct JumpSoundController {
// Duración del salto calculada automáticamente con física: t = 2 * v0 / g
static constexpr float JUMP_DURATION = (2.0F * MAX_VY) / GRAVITY_FORCE;
static constexpr size_t FIRST_SOUND = 1; // Primer sonido a reproducir (índice 1)
static constexpr size_t LAST_SOUND = 17; // Último sonido a reproducir (índice 17)
static constexpr float SECONDS_PER_SOUND = JUMP_DURATION / (LAST_SOUND - FIRST_SOUND + 1);
size_t current_index = 0; // Índice del sonido actual
float elapsed_time = 0.0F; // Tiempo transcurrido durante el salto
bool active = false; // Indica si el controlador está activo
void start(); // Inicia el controlador
void reset(); // Resetea el controlador
auto shouldPlay(float delta_time, size_t& out_index) -> bool; // Comprueba si debe reproducir un sonido
};
struct FallSoundController {
static constexpr float PIXELS_PER_SOUND = 5.0F; // Intervalo de píxeles por sonido (configurable)
static constexpr size_t FIRST_SOUND = 1; // Primer sonido a reproducir (índice 1)
static constexpr size_t LAST_SOUND = 13; // Último sonido a reproducir (índice 13)
size_t current_index = 0; // Índice del sonido actual
float distance_traveled = 0.0F; // Distancia acumulada durante la caída
float last_y = 0.0F; // Última posición Y registrada
bool active = false; // Indica si el controlador está activo
void start(float start_y); // Inicia el controlador
void reset(); // Resetea el controlador
auto shouldPlay(float delta_time, float current_y, size_t& out_index) -> bool; // Comprueba si debe reproducir un sonido
};
// --- Constructor y Destructor --- // --- Constructor y Destructor ---
explicit Player(const Data& player); explicit Player(const Data& player);
~Player() = default; ~Player() = default;
@@ -156,11 +124,8 @@ class Player {
// --- Variables de renderizado y sonido --- // --- Variables de renderizado y sonido ---
Uint8 color_ = 0; // Color del jugador Uint8 color_ = 0; // Color del jugador
std::array<JA_Sound_t*, 24> jumping_sound_{}; // Array con todos los sonidos del salto JA_Sound_t* jump_sound_ = nullptr; // Sonido al iniciar el salto
std::array<JA_Sound_t*, 14> falling_sound_{}; // Array con todos los sonidos de la caída JA_Sound_t* land_sound_ = nullptr; // Sonido al aterrizar en el suelo
JumpSoundController jump_sound_ctrl_; // Controlador de sonidos de salto
FallSoundController fall_sound_ctrl_; // Controlador de sonidos de caída
int fall_start_position_ = 0; // Posición Y al iniciar la caída
void handleConveyorBelts(); void handleConveyorBelts();
void handleShouldFall(); void handleShouldFall();
@@ -169,14 +134,12 @@ class Player {
// --- Métodos de actualización por estado --- // --- Métodos de actualización por estado ---
void updateOnGround(float delta_time); // Actualización lógica estado ON_GROUND void updateOnGround(float delta_time); // Actualización lógica estado ON_GROUND
void updateOnSlope(float delta_time); // Actualización lógica estado ON_SLOPE void updateOnSlope(float delta_time); // Actualización lógica estado ON_SLOPE
void updateJumping(float delta_time); // Actualización lógica estado JUMPING void updateOnAir(float delta_time); // Actualización lógica estado ON_AIR
void updateFalling(float delta_time); // Actualización lógica estado FALLING
// --- Métodos de movimiento por estado --- // --- Métodos de movimiento por estado ---
void moveOnGround(float delta_time); // Movimiento físico estado ON_GROUND void moveOnGround(float delta_time); // Movimiento físico estado ON_GROUND
void moveOnSlope(float delta_time); // Movimiento físico estado ON_SLOPE void moveOnSlope(float delta_time); // Movimiento físico estado ON_SLOPE
void moveJumping(float delta_time); // Movimiento físico estado JUMPING void moveOnAir(float delta_time); // Movimiento físico estado ON_AIR
void moveFalling(float delta_time); // Movimiento físico estado FALLING
// --- Funciones de inicialización --- // --- Funciones de inicialización ---
void initSprite(const std::string& animations_path); // Inicializa el sprite del jugador void initSprite(const std::string& animations_path); // Inicializa el sprite del jugador
@@ -188,6 +151,7 @@ class Player {
// --- Funciones de gestión de estado --- // --- Funciones de gestión de estado ---
void transitionToState(State state); // Cambia el estado del jugador void transitionToState(State state); // Cambia el estado del jugador
void startJump(); // Inicia el salto: velocidad inicial + sonido + transición a ON_AIR
// --- Funciones de física --- // --- Funciones de física ---
void applyGravity(float delta_time); // Aplica gravedad al jugador void applyGravity(float delta_time); // Aplica gravedad al jugador
@@ -197,7 +161,6 @@ class Player {
auto getProjection(Direction direction, float displacement) -> SDL_FRect; // Devuelve el rectangulo de proyeccion auto getProjection(Direction direction, float displacement) -> SDL_FRect; // Devuelve el rectangulo de proyeccion
void applyHorizontalMovement(float delta_time); // Aplica movimiento horizontal con colisión de muros void applyHorizontalMovement(float delta_time); // Aplica movimiento horizontal con colisión de muros
auto handleLandingFromAir(float displacement, const SDL_FRect& projection) -> bool; // Detecta aterrizaje en superficies y rampas auto handleLandingFromAir(float displacement, const SDL_FRect& projection) -> bool; // Detecta aterrizaje en superficies y rampas
void resetSoundControllersOnLanding(); // Resetea los controladores de sonido al aterrizar
// --- Funciones de detección de superficies --- // --- Funciones de detección de superficies ---
auto isOnFloor() -> bool; // Comprueba si el jugador tiene suelo debajo de los pies auto isOnFloor() -> bool; // Comprueba si el jugador tiene suelo debajo de los pies
@@ -216,11 +179,7 @@ class Player {
// --- Funciones de finalización --- // --- Funciones de finalización ---
void animate(float delta_time); // Establece la animación del jugador void animate(float delta_time); // Establece la animación del jugador
auto handleBorders() -> Room::Border; // Comprueba si se halla en alguno de los cuatro bordes auto handleBorders() -> Room::Border; // Comprueba si se halla en alguno de los cuatro bordes
void handleJumpEnd(); // Comprueba si ha finalizado el salto al alcanzar la altura de inicio
auto handleKillingTiles() -> bool; // Comprueba que el jugador no toque ningun tile de los que matan auto handleKillingTiles() -> bool; // Comprueba que el jugador no toque ningun tile de los que matan
void playJumpSound(float delta_time); // Calcula y reproduce el sonido de salto
void playFallSound(float delta_time); // Calcula y reproduce el sonido de caer
void handleDeathByFalling(); // Gestiona la muerte al caer desde muy alto
void updateVelocity(); // Calcula la velocidad en x void updateVelocity(); // Calcula la velocidad en x
void markAsDead(); // Marca al jugador como muerto void markAsDead(); // Marca al jugador como muerto
}; };

View File

@@ -21,6 +21,13 @@ auto RoomLoader::convertRoomConnection(const std::string& value) -> std::string
return value + ".yaml"; return value + ".yaml";
} }
// Lee un nodo de color tolerando tanto string ("red", "bright_blue") como entero (índice de paleta)
static auto readColorNode(const fkyaml::node& node) -> std::string {
if (node.is_string()) { return node.get_value<std::string>(); }
if (node.is_integer()) { return std::to_string(node.get_value<int>()); }
return "black";
}
// Convierte string de autoSurface a int // Convierte string de autoSurface a int
auto RoomLoader::convertAutoSurface(const fkyaml::node& node) -> int { // NOLINT(readability-convert-member-functions-to-static) auto RoomLoader::convertAutoSurface(const fkyaml::node& node) -> int { // NOLINT(readability-convert-member-functions-to-static)
if (node.is_integer()) { if (node.is_integer()) {
@@ -71,10 +78,10 @@ void RoomLoader::parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, con
room.name = room_node["name"].get_value<std::string>(); room.name = room_node["name"].get_value<std::string>();
} }
if (room_node.contains("bgColor")) { if (room_node.contains("bgColor")) {
room.bg_color = room_node["bgColor"].get_value<std::string>(); room.bg_color = readColorNode(room_node["bgColor"]);
} }
if (room_node.contains("border")) { if (room_node.contains("border")) {
room.border_color = room_node["border"].get_value<std::string>(); room.border_color = readColorNode(room_node["border"]);
} }
if (room_node.contains("tileSetFile")) { if (room_node.contains("tileSetFile")) {
room.tile_set_file = room_node["tileSetFile"].get_value<std::string>(); room.tile_set_file = room_node["tileSetFile"].get_value<std::string>();
@@ -87,11 +94,11 @@ void RoomLoader::parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, con
// Item colors // Item colors
room.item_color1 = room_node.contains("itemColor1") room.item_color1 = room_node.contains("itemColor1")
? room_node["itemColor1"].get_value_or<std::string>("yellow") ? readColorNode(room_node["itemColor1"])
: "yellow"; : "yellow";
room.item_color2 = room_node.contains("itemColor2") room.item_color2 = room_node.contains("itemColor2")
? room_node["itemColor2"].get_value_or<std::string>("magenta") ? readColorNode(room_node["itemColor2"])
: "magenta"; : "magenta";
// Dirección de la cinta transportadora (left/none/right) // Dirección de la cinta transportadora (left/none/right)

View File

@@ -6,7 +6,7 @@
namespace Texts { namespace Texts {
constexpr const char* WINDOW_CAPTION = "© 2026 Projecte 2026 — JailDesigner"; constexpr const char* WINDOW_CAPTION = "© 2026 Projecte 2026 — JailDesigner";
constexpr const char* COPYRIGHT = "@2026 JailDesigner"; constexpr const char* COPYRIGHT = "@2026 JailDesigner";
constexpr const char* VERSION = "1.13"; // Versión por defecto constexpr const char* VERSION = "0.1"; // Versión por defecto
} // namespace Texts } // namespace Texts
// Tamaño de bloque // Tamaño de bloque

View File

@@ -1,7 +1,8 @@
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include <algorithm> // Para find, transform #include <algorithm> // Para find, transform, ranges::all_of
#include <cctype> // Para tolower #include <array> // Para array
#include <cctype> // Para tolower, isdigit
#include <cmath> // Para round, abs #include <cmath> // Para round, abs
#include <cstdlib> // Para abs #include <cstdlib> // Para abs
#include <exception> // Para exception #include <exception> // Para exception
@@ -366,10 +367,36 @@ auto stringToColor(const std::string& str) -> Uint8 {
auto it = PALETTE_MAP.find(str); auto it = PALETTE_MAP.find(str);
if (it != PALETTE_MAP.end()) { if (it != PALETTE_MAP.end()) {
return it->second; return it->second;
} // Si no se encuentra el color, devolvemos negro por defecto }
// Fallback: si el string es numérico (p.ej. "4"), lo tratamos como índice de paleta
if (!str.empty() && std::ranges::all_of(str, [](char c) { return std::isdigit(static_cast<unsigned char>(c)) != 0; })) {
const int IDX = safeStoi(str, 0);
if (IDX >= 0 && IDX <= 255) {
return static_cast<Uint8>(IDX);
}
}
// Si no se encuentra el color, devolvemos negro por defecto
return 0; return 0;
} }
// Inverso de stringToColor: devuelve el nombre canónico para un índice de paleta (o el propio número si no hay)
auto colorToString(Uint8 index) -> std::string {
static const std::array<const char*, 16> NAMES = {
"black", "bright_black",
"blue", "bright_blue",
"red", "bright_red",
"magenta", "bright_magenta",
"green", "bright_green",
"cyan", "bright_cyan",
"yellow", "bright_yellow",
"white", "bright_white"};
if (index < NAMES.size()) { return NAMES[index]; }
if (index == 255) { return "transparent"; }
return std::to_string(index);
}
// Convierte una cadena a un entero de forma segura // Convierte una cadena a un entero de forma segura
auto safeStoi(const std::string& value, int default_value) -> int { auto safeStoi(const std::string& value, int default_value) -> int {
try { try {

View File

@@ -92,7 +92,8 @@ auto toSDLRect(const SDL_FRect& frect) -> SDL_Rect; // Convierte SDL_FRect
auto toSDLPoint(const SDL_FPoint& fpoint) -> SDL_Point; // Convierte SDL_FPoint a SDL_Point auto toSDLPoint(const SDL_FPoint& fpoint) -> SDL_Point; // Convierte SDL_FPoint a SDL_Point
// CONVERSIONES DE STRING // CONVERSIONES DE STRING
auto stringToColor(const std::string& str) -> Uint8; // String a índice de paleta auto stringToColor(const std::string& str) -> Uint8; // String a índice de paleta (acepta nombres o números)
auto colorToString(Uint8 index) -> std::string; // Índice de paleta a nombre canónico
auto safeStoi(const std::string& value, int default_value = 0) -> int; // String a int seguro (sin excepciones) auto safeStoi(const std::string& value, int default_value = 0) -> int; // String a int seguro (sin excepciones)
auto stringToBool(const std::string& str) -> bool; // String a bool (true/1/yes/on) auto stringToBool(const std::string& str) -> bool; // String a bool (true/1/yes/on)
auto boolToString(bool value) -> std::string; // Bool a string (1/0) auto boolToString(bool value) -> std::string; // Bool a string (1/0)