ajustant el jugador
This commit is contained in:
28
CLAUDE.md
28
CLAUDE.md
@@ -18,11 +18,37 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## 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).
|
||||
- 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).
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -252,11 +252,9 @@ assets:
|
||||
player:
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/player/player.gif
|
||||
- ${PREFIX}/data/player/player2.gif
|
||||
- ${PREFIX}/data/player/player_game_over.gif
|
||||
ANIMATION:
|
||||
- ${PREFIX}/data/player/player.yaml
|
||||
- ${PREFIX}/data/player/player2.yaml
|
||||
- ${PREFIX}/data/player/player_game_over.yaml
|
||||
|
||||
# ITEMS
|
||||
@@ -276,30 +274,8 @@ assets:
|
||||
- ${PREFIX}/data/sound/item.wav
|
||||
- ${PREFIX}/data/sound/death.wav
|
||||
- ${PREFIX}/data/sound/notify.wav
|
||||
- ${PREFIX}/data/sound/jump1.wav
|
||||
- ${PREFIX}/data/sound/jump2.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
|
||||
- ${PREFIX}/data/sound/jump.wav
|
||||
- ${PREFIX}/data/sound/land.wav
|
||||
|
||||
# LOGO
|
||||
logo:
|
||||
|
||||
BIN
data/player/player.aseprite
Normal file
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 |
@@ -4,7 +4,17 @@ frameWidth: 8
|
||||
frameHeight: 16
|
||||
|
||||
animations:
|
||||
- name: stand
|
||||
speed: 0
|
||||
loop: -1
|
||||
frames: [0]
|
||||
|
||||
- name: default
|
||||
speed: 0.1333
|
||||
speed: 0.07
|
||||
loop: 0
|
||||
frames: [0, 1, 2, 3]
|
||||
|
||||
- name: jump
|
||||
speed: 0
|
||||
loop: -1
|
||||
frames: [4]
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 223 B |
@@ -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]
|
||||
@@ -1,9 +1,9 @@
|
||||
# VOID MAIN
|
||||
room:
|
||||
name_en: "VOID MAIN"
|
||||
name_ca: "VOID MAIN"
|
||||
name_en: "VOID MAIN"
|
||||
bgColor: black
|
||||
border: magenta
|
||||
border: bright_black
|
||||
tileSetFile: standard.gif
|
||||
|
||||
# Conexiones de la habitación (null = sin conexión)
|
||||
@@ -44,7 +44,7 @@ tilemap:
|
||||
enemies:
|
||||
- animation: code.yaml
|
||||
position: {x: 3, y: 2}
|
||||
velocity: {x: 24.0, y: 0}
|
||||
velocity: {x: 24, y: 0}
|
||||
boundaries:
|
||||
position1: {x: 3, y: 2}
|
||||
position2: {x: 27, y: 2}
|
||||
@@ -56,3 +56,4 @@ items:
|
||||
tile: 42
|
||||
position: {x: 21, y: 13}
|
||||
counter: 1
|
||||
|
||||
|
||||
BIN
data/sound/jump.wav
Normal file
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.
@@ -30,7 +30,7 @@ namespace Resource {
|
||||
Cache* Cache::cache = nullptr;
|
||||
|
||||
// [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
|
||||
void Cache::destroy() { delete Cache::cache; }
|
||||
@@ -39,8 +39,8 @@ namespace Resource {
|
||||
auto Cache::get() -> Cache* { return Cache::cache; }
|
||||
|
||||
// Constructor
|
||||
Cache::Cache()
|
||||
: loading_text_(Screen::get()->getText()) {
|
||||
Cache::Cache(LoadingMode mode)
|
||||
: loading_mode_(mode), loading_text_(Screen::get()->getText()) {
|
||||
load();
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace Resource {
|
||||
void Cache::load() {
|
||||
// Nota: el overlay de debug (RenderInfo) se inicializa después de esta carga,
|
||||
// por lo que updateZoomFactor() se llamará correctamente en RenderInfo::init().
|
||||
if (loading_mode_ == LoadingMode::EAGER) {
|
||||
calculateTotal();
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
std::cout << "\n** LOADING RESOURCES" << '\n';
|
||||
@@ -71,6 +72,12 @@ namespace Resource {
|
||||
loadRooms();
|
||||
createText();
|
||||
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
|
||||
@@ -80,10 +87,13 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// 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; });
|
||||
|
||||
if (it != sounds_.end()) {
|
||||
if (loading_mode_ == LoadingMode::LAZY && it->sound == nullptr) {
|
||||
loadSoundByName(name);
|
||||
}
|
||||
return it->sound;
|
||||
}
|
||||
|
||||
@@ -92,10 +102,13 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// 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; });
|
||||
|
||||
if (it != musics_.end()) {
|
||||
if (loading_mode_ == LoadingMode::LAZY && it->music == nullptr) {
|
||||
loadMusicByName(name);
|
||||
}
|
||||
return it->music;
|
||||
}
|
||||
|
||||
@@ -104,10 +117,13 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// 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; });
|
||||
|
||||
if (it != surfaces_.end()) {
|
||||
if (loading_mode_ == LoadingMode::LAZY && it->surface == nullptr) {
|
||||
loadSurfaceByName(name);
|
||||
}
|
||||
return it->surface;
|
||||
}
|
||||
|
||||
@@ -116,10 +132,13 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// 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; });
|
||||
|
||||
if (it != palettes_.end()) {
|
||||
if (loading_mode_ == LoadingMode::LAZY && !it->loaded) {
|
||||
loadPaletteByName(name);
|
||||
}
|
||||
return it->palette;
|
||||
}
|
||||
|
||||
@@ -128,10 +147,13 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// 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; });
|
||||
|
||||
if (it != text_files_.end()) {
|
||||
if (loading_mode_ == LoadingMode::LAZY && it->text_file == nullptr) {
|
||||
loadTextFileByName(name);
|
||||
}
|
||||
return it->text_file;
|
||||
}
|
||||
|
||||
@@ -152,10 +174,13 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// 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; });
|
||||
|
||||
if (it != animations_.end()) {
|
||||
if (loading_mode_ == LoadingMode::LAZY && it->yaml_data.empty()) {
|
||||
loadAnimationByName(name);
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
|
||||
@@ -164,10 +189,13 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// 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; });
|
||||
|
||||
if (it != rooms_.end()) {
|
||||
if (loading_mode_ == LoadingMode::LAZY && it->room == nullptr) {
|
||||
loadRoomByName(name);
|
||||
}
|
||||
return it->room;
|
||||
}
|
||||
|
||||
@@ -205,6 +233,11 @@ namespace Resource {
|
||||
|
||||
// Obtiene todas las habitaciones
|
||||
auto Cache::getRooms() -> std::vector<RoomResource>& {
|
||||
if (loading_mode_ == LoadingMode::LAZY) {
|
||||
for (auto& r : rooms_) {
|
||||
if (r.room == nullptr) { loadRoomByName(r.name); }
|
||||
}
|
||||
}
|
||||
return rooms_;
|
||||
}
|
||||
|
||||
@@ -530,4 +563,144 @@ namespace Resource {
|
||||
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
|
||||
|
||||
@@ -11,7 +11,12 @@ namespace Resource {
|
||||
|
||||
class Cache {
|
||||
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 auto get() -> Cache*; // Acceso al singleton
|
||||
|
||||
@@ -57,6 +62,18 @@ namespace Resource {
|
||||
void loadRooms();
|
||||
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
|
||||
void clear();
|
||||
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);
|
||||
|
||||
// Constructor y destructor
|
||||
Cache();
|
||||
explicit Cache(LoadingMode mode);
|
||||
~Cache() = default;
|
||||
|
||||
LoadingMode loading_mode_ = LoadingMode::EAGER;
|
||||
|
||||
// Singleton instance
|
||||
static Cache* cache;
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ struct SurfaceResource {
|
||||
struct ResourcePalette {
|
||||
std::string name; // Nombre de la surface
|
||||
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
|
||||
|
||||
@@ -134,6 +134,7 @@ void Debug::loadFromFile() {
|
||||
spawn_settings_.spawn_y = Defaults::Game::Player::SPAWN_Y;
|
||||
spawn_settings_.flip = Defaults::Game::Player::SPAWN_FLIP;
|
||||
initial_scene_ = SceneManager::Scene::GAME;
|
||||
lazy_loading_ = false;
|
||||
|
||||
std::ifstream file(debug_file_path_);
|
||||
if (!file.good()) {
|
||||
@@ -162,6 +163,9 @@ void Debug::loadFromFile() {
|
||||
if (yaml.contains("initial_scene")) {
|
||||
initial_scene_ = sceneFromString(yaml["initial_scene"].get_value<std::string>());
|
||||
}
|
||||
if (yaml.contains("lazy_loading")) {
|
||||
lazy_loading_ = yaml["lazy_loading"].get_value<bool>();
|
||||
}
|
||||
} catch (...) {
|
||||
// YAML inválido: resetear a defaults y sobreescribir
|
||||
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_.flip = Defaults::Game::Player::SPAWN_FLIP;
|
||||
initial_scene_ = SceneManager::Scene::GAME;
|
||||
lazy_loading_ = false;
|
||||
saveToFile();
|
||||
}
|
||||
}
|
||||
@@ -184,6 +189,7 @@ void Debug::saveToFile() const {
|
||||
file << "spawn_y: " << (spawn_settings_.spawn_y / Tile::SIZE) << " # en tiles\n";
|
||||
file << "spawn_flip: " << ((spawn_settings_.flip == Flip::RIGHT) ? "right" : "left") << "\n";
|
||||
file << "initial_scene: " << sceneToString(initial_scene_) << "\n";
|
||||
file << "lazy_loading: " << (lazy_loading_ ? "true" : "false") << " # carga perezosa de recursos (dev)\n";
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
@@ -47,6 +47,7 @@ class Debug {
|
||||
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
|
||||
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:
|
||||
static Debug* debug; // [SINGLETON] Objeto privado
|
||||
@@ -64,6 +65,7 @@ class Debug {
|
||||
std::string debug_file_path_; // Ruta del archivo debug.yaml
|
||||
SpawnSettings spawn_settings_; // Configuración de spawn para debug
|
||||
SceneManager::Scene initial_scene_ = SceneManager::Scene::GAME; // Escena inicial en debug
|
||||
bool lazy_loading_ = false; // Carga lazy de recursos (dev)
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
@@ -160,8 +160,21 @@ Director::Director() {
|
||||
// Crea los objetos
|
||||
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)
|
||||
#ifdef _DEBUG
|
||||
Resource::Cache::init(Debug::get()->getLazyLoading()
|
||||
? Resource::Cache::LoadingMode::LAZY
|
||||
: Resource::Cache::LoadingMode::EAGER);
|
||||
#else
|
||||
Resource::Cache::init();
|
||||
#endif
|
||||
Notifier::init("", "8bithud");
|
||||
RenderInfo::init();
|
||||
Console::init("8bithud");
|
||||
@@ -182,9 +195,6 @@ Director::Director() {
|
||||
Input::get()->applyGamepadBindingsFromOptions();
|
||||
|
||||
#ifdef _DEBUG
|
||||
Debug::init();
|
||||
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
|
||||
Debug::get()->loadFromFile();
|
||||
SceneManager::current = Debug::get()->getInitialScene();
|
||||
MapEditor::init();
|
||||
#endif
|
||||
|
||||
@@ -1131,6 +1131,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
|
||||
std::string val = toLower(value);
|
||||
|
||||
if (property == "BGCOLOR") {
|
||||
val = colorToString(stringToColor(val)); // Normaliza a nombre canónico (acepta nombres e índices)
|
||||
room_data_.bg_color = val;
|
||||
room_->setBgColor(val);
|
||||
autosave();
|
||||
@@ -1138,6 +1139,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
|
||||
}
|
||||
|
||||
if (property == "BORDER") {
|
||||
val = colorToString(stringToColor(val));
|
||||
room_data_.border_color = val;
|
||||
Screen::get()->setBorderColor(stringToColor(val));
|
||||
autosave();
|
||||
@@ -1145,6 +1147,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
|
||||
}
|
||||
|
||||
if (property == "ITEMCOLOR1") {
|
||||
val = colorToString(stringToColor(val));
|
||||
room_data_.item_color1 = val;
|
||||
room_->setItemColors(room_data_.item_color1, room_data_.item_color2);
|
||||
autosave();
|
||||
@@ -1152,6 +1155,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
|
||||
}
|
||||
|
||||
if (property == "ITEMCOLOR2") {
|
||||
val = colorToString(stringToColor(val));
|
||||
room_data_.item_color2 = val;
|
||||
room_->setItemColors(room_data_.item_color1, room_data_.item_color2);
|
||||
autosave();
|
||||
|
||||
@@ -76,11 +76,8 @@ void Player::move(float delta_time) {
|
||||
case State::ON_SLOPE:
|
||||
moveOnSlope(delta_time);
|
||||
break;
|
||||
case State::JUMPING:
|
||||
moveJumping(delta_time);
|
||||
break;
|
||||
case State::FALLING:
|
||||
moveFalling(delta_time);
|
||||
case State::ON_AIR:
|
||||
moveOnAir(delta_time);
|
||||
break;
|
||||
}
|
||||
syncSpriteAndCollider(); // Actualiza la posición del sprite y las colisiones
|
||||
@@ -95,11 +92,8 @@ void Player::move(float delta_time) {
|
||||
case State::ON_SLOPE:
|
||||
Debug::get()->set("P.STATE", "ON_SLOPE");
|
||||
break;
|
||||
case State::JUMPING:
|
||||
Debug::get()->set("P.STATE", "JUMPING");
|
||||
break;
|
||||
case State::FALLING:
|
||||
Debug::get()->set("P.STATE", "FALLING");
|
||||
case State::ON_AIR:
|
||||
Debug::get()->set("P.STATE", "ON_AIR");
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@@ -117,7 +111,8 @@ void Player::handleConveyorBelts() {
|
||||
|
||||
void Player::handleShouldFall() {
|
||||
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) {
|
||||
case State::ON_GROUND:
|
||||
vy_ = 0;
|
||||
handleDeathByFalling();
|
||||
resetSoundControllersOnLanding();
|
||||
if (previous_state_ == State::ON_AIR) {
|
||||
Audio::get()->playSound(land_sound_, Audio::Group::GAME);
|
||||
}
|
||||
current_slope_ = nullptr;
|
||||
break;
|
||||
case State::ON_SLOPE:
|
||||
vy_ = 0;
|
||||
handleDeathByFalling();
|
||||
resetSoundControllersOnLanding();
|
||||
if (previous_state_ == State::ON_AIR) {
|
||||
Audio::get()->playSound(land_sound_, Audio::Group::GAME);
|
||||
}
|
||||
updateCurrentSlope();
|
||||
if (current_slope_ == nullptr) {
|
||||
// Los pies no coinciden con ninguna rampa: tratar como suelo plano
|
||||
state_ = State::ON_GROUND;
|
||||
}
|
||||
break;
|
||||
case State::JUMPING:
|
||||
// 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_);
|
||||
case State::ON_AIR:
|
||||
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;
|
||||
break;
|
||||
}
|
||||
@@ -172,48 +154,48 @@ void Player::updateState(float delta_time) {
|
||||
case State::ON_SLOPE:
|
||||
updateOnSlope(delta_time);
|
||||
break;
|
||||
case State::JUMPING:
|
||||
updateJumping(delta_time);
|
||||
break;
|
||||
case State::FALLING:
|
||||
updateFalling(delta_time);
|
||||
case State::ON_AIR:
|
||||
updateOnAir(delta_time);
|
||||
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
|
||||
void Player::updateOnGround(float delta_time) {
|
||||
(void)delta_time; // No usado en este método, pero se mantiene por consistencia
|
||||
handleConveyorBelts(); // Gestiona las cintas transportadoras
|
||||
handleShouldFall(); // Verifica si debe caer (no tiene suelo)
|
||||
|
||||
// Verifica si el jugador quiere saltar
|
||||
if (wanna_jump_) { transitionToState(State::JUMPING); }
|
||||
// El salto tiene prioridad sobre la caída por falta de suelo
|
||||
if (wanna_jump_) {
|
||||
startJump();
|
||||
return;
|
||||
}
|
||||
handleShouldFall(); // Verifica si debe caer (no tiene suelo)
|
||||
}
|
||||
|
||||
// Actualización lógica del estado ON_SLOPE
|
||||
void Player::updateOnSlope(float delta_time) {
|
||||
(void)delta_time; // No usado en este método, pero se mantiene por consistencia
|
||||
|
||||
if (wanna_jump_) {
|
||||
startJump();
|
||||
return;
|
||||
}
|
||||
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
|
||||
void Player::updateJumping(float delta_time) {
|
||||
auto_movement_ = false; // Desactiva el movimiento automático durante el salto
|
||||
playJumpSound(delta_time); // Reproduce los sonidos de salto
|
||||
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
|
||||
// Actualización lógica del estado ON_AIR
|
||||
void Player::updateOnAir(float delta_time) {
|
||||
(void)delta_time;
|
||||
auto_movement_ = false; // Desactiva el movimiento automático en el aire
|
||||
}
|
||||
|
||||
// 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,
|
||||
// independientemente de si hay o no input (evita bloqueo con vx_=0 y slope null)
|
||||
if (current_slope_ == nullptr) {
|
||||
transitionToState(State::FALLING);
|
||||
vy_ = 0.0F;
|
||||
transitionToState(State::ON_AIR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -320,7 +303,8 @@ void Player::moveOnSlope(float delta_time) {
|
||||
transitionToState(State::ON_GROUND);
|
||||
} else {
|
||||
// Sin soporte: empezar a caer
|
||||
transitionToState(State::FALLING);
|
||||
vy_ = 0.0F;
|
||||
transitionToState(State::ON_AIR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -332,67 +316,37 @@ void Player::moveOnSlope(float delta_time) {
|
||||
}*/
|
||||
}
|
||||
|
||||
// Movimiento físico del estado JUMPING
|
||||
void Player::moveJumping(float delta_time) {
|
||||
// Movimiento horizontal
|
||||
// Movimiento físico del estado ON_AIR
|
||||
// El jugador puede moverse horizontalmente en el aire y la gravedad siempre actúa.
|
||||
void Player::moveOnAir(float delta_time) {
|
||||
// Movimiento horizontal libre según wanna_go_ (permite girar en el aire)
|
||||
updateVelocity();
|
||||
applyHorizontalMovement(delta_time);
|
||||
|
||||
// Movimiento vertical
|
||||
// Gravedad
|
||||
applyGravity(delta_time);
|
||||
|
||||
const float DISPLACEMENT_Y = vy_ * delta_time;
|
||||
// Movimiento vertical hacia arriba
|
||||
|
||||
// Subiendo: comprobar techo
|
||||
if (vy_ < 0.0F) {
|
||||
const SDL_FRect PROJECTION = getProjection(Direction::UP, DISPLACEMENT_Y);
|
||||
|
||||
// Comprueba la colisión
|
||||
const int POS = room_->checkBottomSurfaces(PROJECTION);
|
||||
|
||||
// Calcula la nueva posición
|
||||
if (POS == Collision::NONE) {
|
||||
// Si no hay colisión
|
||||
y_ += DISPLACEMENT_Y;
|
||||
} 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;
|
||||
transitionToState(State::FALLING);
|
||||
vy_ = 0.0F;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Movimiento vertical hacia abajo
|
||||
else if (vy_ > 0.0F) {
|
||||
// Crea el rectangulo de proyección en el eje Y para ver si colisiona
|
||||
|
||||
// Bajando: comprobar aterrizaje en superficies y rampas
|
||||
if (vy_ > 0.0F) {
|
||||
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);
|
||||
} 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
|
||||
@@ -410,10 +364,8 @@ auto Player::handleBorders() -> Room::Border {
|
||||
}
|
||||
|
||||
if (y_ + HEIGHT > PlayArea::BOTTOM) {
|
||||
// Si llega en estado terminal, muere y no cruza
|
||||
const bool SHOULD_DIE = static_cast<int>(y_) - last_grounded_position_ > MAX_FALLING_HEIGHT;
|
||||
if (SHOULD_DIE) { markAsDead(); }
|
||||
return is_alive_ ? Room::Border::BOTTOM : Room::Border::NONE;
|
||||
// Restricción de muerte por altura de caída desactivada
|
||||
return Room::Border::BOTTOM;
|
||||
}
|
||||
|
||||
return Room::Border::NONE;
|
||||
@@ -454,9 +406,8 @@ void Player::switchBorders() {
|
||||
|
||||
// Aplica gravedad al jugador
|
||||
void Player::applyGravity(float delta_time) {
|
||||
// La gravedad solo se aplica cuando el jugador esta saltando
|
||||
// Nunca mientras cae o esta de pie
|
||||
if (state_ == State::JUMPING) {
|
||||
// La gravedad solo se aplica cuando el jugador esta en el aire
|
||||
if (state_ == State::ON_AIR) {
|
||||
vy_ += GRAVITY_FORCE * delta_time;
|
||||
vy_ = std::min(vy_, MAX_VY);
|
||||
}
|
||||
@@ -464,37 +415,13 @@ void Player::applyGravity(float delta_time) {
|
||||
|
||||
// Establece la animación del jugador
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
} else {
|
||||
sprite_->setCurrentAnimation("stand");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,98 +592,10 @@ void Player::updateFeet() {
|
||||
.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)
|
||||
for (int i = 0; i < 24; ++i) {
|
||||
std::string sound_file = "jump" + std::to_string(i + 1) + ".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;
|
||||
jump_sound_ = Resource::Cache::get()->getSound("jump.wav");
|
||||
land_sound_ = Resource::Cache::get()->getSound("land.wav");
|
||||
}
|
||||
|
||||
// Aplica los valores de spawn al jugador
|
||||
@@ -810,14 +649,6 @@ void Player::placeSprite() {
|
||||
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
|
||||
void Player::updateVelocity() {
|
||||
if (auto_movement_) {
|
||||
@@ -900,12 +731,6 @@ auto Player::handleLandingFromAir(float displacement, const SDL_FRect& projectio
|
||||
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
|
||||
auto Player::getProjection(Direction direction, float displacement) -> SDL_FRect { // NOLINT(readability-convert-member-functions-to-static)
|
||||
switch (direction) {
|
||||
|
||||
@@ -21,8 +21,7 @@ class Player {
|
||||
enum class State {
|
||||
ON_GROUND, // En suelo plano o conveyor belt
|
||||
ON_SLOPE, // En rampa/pendiente
|
||||
JUMPING,
|
||||
FALLING,
|
||||
ON_AIR, // En el aire (saltando, cayendo o caminando al vacío)
|
||||
};
|
||||
|
||||
enum class Direction {
|
||||
@@ -34,10 +33,10 @@ class Player {
|
||||
};
|
||||
|
||||
// --- 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 MAX_VY = 80.0F; // Velocidad vertical máxima en pixels/segundo (1.2 * 66.67fps)
|
||||
static constexpr float JUMP_VELOCITY = -80.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 HORIZONTAL_VELOCITY = 60.0F; // Velocidad horizontal en pixels/segundo
|
||||
static constexpr float MAX_VY = 160.0F; // Velocidad vertical máxima en pixels/segundo
|
||||
static constexpr float JUMP_VELOCITY = -140.0F; // Velocidad inicial del salto en pixels/segundo
|
||||
static constexpr float GRAVITY_FORCE = 360.0F; // Fuerza de gravedad en pixels/segundo²
|
||||
|
||||
struct SpawnData {
|
||||
float x = 0;
|
||||
@@ -55,37 +54,6 @@ class Player {
|
||||
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 ---
|
||||
explicit Player(const Data& player);
|
||||
~Player() = default;
|
||||
@@ -156,11 +124,8 @@ class Player {
|
||||
|
||||
// --- Variables de renderizado y sonido ---
|
||||
Uint8 color_ = 0; // Color del jugador
|
||||
std::array<JA_Sound_t*, 24> jumping_sound_{}; // Array con todos los sonidos del salto
|
||||
std::array<JA_Sound_t*, 14> falling_sound_{}; // Array con todos los sonidos de la caída
|
||||
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
|
||||
JA_Sound_t* jump_sound_ = nullptr; // Sonido al iniciar el salto
|
||||
JA_Sound_t* land_sound_ = nullptr; // Sonido al aterrizar en el suelo
|
||||
|
||||
void handleConveyorBelts();
|
||||
void handleShouldFall();
|
||||
@@ -169,14 +134,12 @@ class Player {
|
||||
// --- Métodos de actualización por estado ---
|
||||
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 updateJumping(float delta_time); // Actualización lógica estado JUMPING
|
||||
void updateFalling(float delta_time); // Actualización lógica estado FALLING
|
||||
void updateOnAir(float delta_time); // Actualización lógica estado ON_AIR
|
||||
|
||||
// --- Métodos de movimiento por estado ---
|
||||
void moveOnGround(float delta_time); // Movimiento físico estado ON_GROUND
|
||||
void moveOnSlope(float delta_time); // Movimiento físico estado ON_SLOPE
|
||||
void moveJumping(float delta_time); // Movimiento físico estado JUMPING
|
||||
void moveFalling(float delta_time); // Movimiento físico estado FALLING
|
||||
void moveOnAir(float delta_time); // Movimiento físico estado ON_AIR
|
||||
|
||||
// --- Funciones de inicialización ---
|
||||
void initSprite(const std::string& animations_path); // Inicializa el sprite del jugador
|
||||
@@ -188,6 +151,7 @@ class Player {
|
||||
|
||||
// --- Funciones de gestión de estado ---
|
||||
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 ---
|
||||
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
|
||||
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
|
||||
void resetSoundControllersOnLanding(); // Resetea los controladores de sonido al aterrizar
|
||||
|
||||
// --- Funciones de detección de superficies ---
|
||||
auto isOnFloor() -> bool; // Comprueba si el jugador tiene suelo debajo de los pies
|
||||
@@ -216,11 +179,7 @@ class Player {
|
||||
// --- Funciones de finalización ---
|
||||
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
|
||||
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
|
||||
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 markAsDead(); // Marca al jugador como muerto
|
||||
};
|
||||
@@ -21,6 +21,13 @@ auto RoomLoader::convertRoomConnection(const std::string& value) -> std::string
|
||||
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
|
||||
auto RoomLoader::convertAutoSurface(const fkyaml::node& node) -> int { // NOLINT(readability-convert-member-functions-to-static)
|
||||
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>();
|
||||
}
|
||||
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")) {
|
||||
room.border_color = room_node["border"].get_value<std::string>();
|
||||
room.border_color = readColorNode(room_node["border"]);
|
||||
}
|
||||
if (room_node.contains("tileSetFile")) {
|
||||
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
|
||||
room.item_color1 = room_node.contains("itemColor1")
|
||||
? room_node["itemColor1"].get_value_or<std::string>("yellow")
|
||||
? readColorNode(room_node["itemColor1"])
|
||||
: "yellow";
|
||||
|
||||
room.item_color2 = room_node.contains("itemColor2")
|
||||
? room_node["itemColor2"].get_value_or<std::string>("magenta")
|
||||
? readColorNode(room_node["itemColor2"])
|
||||
: "magenta";
|
||||
|
||||
// Dirección de la cinta transportadora (left/none/right)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace Texts {
|
||||
constexpr const char* WINDOW_CAPTION = "© 2026 Projecte 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
|
||||
|
||||
// Tamaño de bloque
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm> // Para find, transform
|
||||
#include <cctype> // Para tolower
|
||||
#include <algorithm> // Para find, transform, ranges::all_of
|
||||
#include <array> // Para array
|
||||
#include <cctype> // Para tolower, isdigit
|
||||
#include <cmath> // Para round, abs
|
||||
#include <cstdlib> // Para abs
|
||||
#include <exception> // Para exception
|
||||
@@ -366,10 +367,36 @@ auto stringToColor(const std::string& str) -> Uint8 {
|
||||
auto it = PALETTE_MAP.find(str);
|
||||
if (it != PALETTE_MAP.end()) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
auto safeStoi(const std::string& value, int default_value) -> int {
|
||||
try {
|
||||
|
||||
@@ -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
|
||||
|
||||
// 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 stringToBool(const std::string& str) -> bool; // String a bool (true/1/yes/on)
|
||||
auto boolToString(bool value) -> std::string; // Bool a string (1/0)
|
||||
|
||||
Reference in New Issue
Block a user