diff --git a/source/core/audio/audio.cpp b/source/core/audio/audio.cpp index 633a767..ad58c6a 100644 --- a/source/core/audio/audio.cpp +++ b/source/core/audio/audio.cpp @@ -8,6 +8,7 @@ // Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp). // clang-format off #undef STB_VORBIS_HEADER_ONLY +// NOLINTNEXTLINE(bugprone-suspicious-include) — stb_vorbis és single-file: el TU principal inclou el .c per portar la implementació. #include "external/stb_vorbis.c" // stb_vorbis.c filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem // perquè xocarien amb noms de paràmetres de plantilla en altres headers. @@ -43,15 +44,15 @@ Audio::Audio() { initSDLAudio(); } // Destructor Audio::~Audio() { - JA_Quit(); + Ja::quit(); } // Método principal void Audio::update() { - JA_Update(); + Ja::update(); // Sincronizar estado: detectar cuando la música se para (ej. fade-out completado) - if (instance && instance->music_.state == MusicState::PLAYING && JA_GetMusicState() != JA_MUSIC_PLAYING) { + if (instance != nullptr && instance->music_.state == MusicState::PLAYING && Ja::getMusicState() != Ja::MusicState::PLAYING) { instance->music_.state = MusicState::STOPPED; } } @@ -65,18 +66,18 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa return; } - if (!music_enabled_) return; + if (!music_enabled_) { return; } auto* resource = AudioResource::getMusic(name); - if (resource == nullptr) return; + if (resource == nullptr) { return; } if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) { - JA_CrossfadeMusic(resource, crossfade_ms, loop); + Ja::crossfadeMusic(resource, crossfade_ms, loop); } else { if (music_.state == MusicState::PLAYING) { - JA_StopMusic(); + Ja::stopMusic(); } - JA_PlayMusic(resource, loop); + Ja::playMusic(resource, loop); } music_.name = name; @@ -85,16 +86,16 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa } // Reproduce la música por puntero (con crossfade opcional) -void Audio::playMusic(JA_Music_t* music, const int loop, const int crossfade_ms) { - if (!music_enabled_ || music == nullptr) return; +void Audio::playMusic(Ja::Music* music, const int loop, const int crossfade_ms) { + if (!music_enabled_ || music == nullptr) { return; } if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) { - JA_CrossfadeMusic(music, crossfade_ms, loop); + Ja::crossfadeMusic(music, crossfade_ms, loop); } else { if (music_.state == MusicState::PLAYING) { - JA_StopMusic(); + Ja::stopMusic(); } - JA_PlayMusic(music, loop); + Ja::playMusic(music, loop); } music_.name.clear(); // nom desconegut quan es passa per punter @@ -105,7 +106,7 @@ void Audio::playMusic(JA_Music_t* music, const int loop, const int crossfade_ms) // Pausa la música void Audio::pauseMusic() { if (music_enabled_ && music_.state == MusicState::PLAYING) { - JA_PauseMusic(); + Ja::pauseMusic(); music_.state = MusicState::PAUSED; } } @@ -113,7 +114,7 @@ void Audio::pauseMusic() { // Continua la música pausada void Audio::resumeMusic() { if (music_enabled_ && music_.state == MusicState::PAUSED) { - JA_ResumeMusic(); + Ja::resumeMusic(); music_.state = MusicState::PLAYING; } } @@ -121,7 +122,7 @@ void Audio::resumeMusic() { // Detiene la música void Audio::stopMusic() { if (music_enabled_) { - JA_StopMusic(); + Ja::stopMusic(); music_.state = MusicState::STOPPED; } } @@ -129,42 +130,42 @@ void Audio::stopMusic() { // Reproduce un sonido por nombre void Audio::playSound(const std::string& name, Group group) const { if (sound_enabled_) { - JA_PlaySound(AudioResource::getSound(name), 0, static_cast(group)); + Ja::playSound(AudioResource::getSound(name), 0, static_cast(group)); } } // Reproduce un sonido por puntero directo -void Audio::playSound(JA_Sound_t* sound, Group group) const { +void Audio::playSound(Ja::Sound* sound, Group group) const { if (sound_enabled_ && sound != nullptr) { - JA_PlaySound(sound, 0, static_cast(group)); + Ja::playSound(sound, 0, static_cast(group)); } } // Detiene todos los sonidos void Audio::stopAllSounds() const { if (sound_enabled_) { - JA_StopChannel(-1); + Ja::stopChannel(-1); } } // Realiza un fundido de salida de la música void Audio::fadeOutMusic(int milliseconds) const { if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) { - JA_FadeOutMusic(milliseconds); + Ja::fadeOutMusic(milliseconds); } } // Consulta directamente el estado real de la música en jailaudio auto Audio::getRealMusicState() -> MusicState { - JA_Music_state ja_state = JA_GetMusicState(); + Ja::MusicState ja_state = Ja::getMusicState(); switch (ja_state) { - case JA_MUSIC_PLAYING: + case Ja::MusicState::PLAYING: return MusicState::PLAYING; - case JA_MUSIC_PAUSED: + case Ja::MusicState::PAUSED: return MusicState::PAUSED; - case JA_MUSIC_STOPPED: - case JA_MUSIC_INVALID: - case JA_MUSIC_DISABLED: + case Ja::MusicState::STOPPED: + case Ja::MusicState::INVALID: + case Ja::MusicState::DISABLED: default: return MusicState::STOPPED; } @@ -175,7 +176,7 @@ void Audio::setSoundVolume(float sound_volume, Group group) const { if (sound_enabled_) { sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME); const float CONVERTED_VOLUME = sound_volume * Options::audio.volume; - JA_SetSoundVolume(CONVERTED_VOLUME, static_cast(group)); + Ja::setSoundVolume(CONVERTED_VOLUME, static_cast(group)); } } @@ -184,7 +185,7 @@ void Audio::setMusicVolume(float music_volume) const { if (music_enabled_) { music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME); const float CONVERTED_VOLUME = music_volume * Options::audio.volume; - JA_SetMusicVolume(CONVERTED_VOLUME); + Ja::setMusicVolume(CONVERTED_VOLUME); } } @@ -206,7 +207,7 @@ void Audio::initSDLAudio() { if (!SDL_Init(SDL_INIT_AUDIO)) { std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n'; } else { - JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2); + Ja::init(FREQUENCY, SDL_AUDIO_S16LE, 2); enable(Options::audio.enabled); } } diff --git a/source/core/audio/audio.hpp b/source/core/audio/audio.hpp index 9c5ea6f..7cb7656 100644 --- a/source/core/audio/audio.hpp +++ b/source/core/audio/audio.hpp @@ -1,8 +1,13 @@ #pragma once +#include // Para std::lround #include // Para int8_t, uint8_t #include // Para string -#include // Para move + +namespace Ja { + struct Music; + struct Sound; +} // namespace Ja // --- Clase Audio: gestor de audio (singleton) --- // Implementació canònica, byte-idèntica entre projectes. @@ -41,17 +46,17 @@ class Audio { static void update(); // Actualización del sistema de audio // --- Control de música --- - void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional) - void playMusic(struct JA_Music_t* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional) - void pauseMusic(); // Pausar reproducción de música - void resumeMusic(); // Continua la música pausada - void stopMusic(); // Detener completamente la música - void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música + void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional) + void playMusic(Ja::Music* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional) + void pauseMusic(); // Pausar reproducción de música + void resumeMusic(); // Continua la música pausada + void stopMusic(); // Detener completamente la música + void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música // --- Control de sonidos --- - void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre - void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero - void stopAllSounds() const; // Detener todos los sonidos + void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre + void playSound(Ja::Sound* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero + void stopAllSounds() const; // Detener todos los sonidos // --- Control de volumen (API interna: float 0.0..1.0) --- void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos @@ -59,8 +64,8 @@ class Audio { // --- Helpers de conversió per a la capa de presentació --- // UI (menús, notificacions) manega enters 0..100; internament viu float 0..1. - static constexpr auto toPercent(float volume) -> int { - return static_cast(volume * 100.0F + 0.5F); + static auto toPercent(float volume) -> int { + return static_cast(std::lround(volume * 100.0F)); } static constexpr auto fromPercent(int percent) -> float { return static_cast(percent) / 100.0F; diff --git a/source/core/audio/audio_adapter.cpp b/source/core/audio/audio_adapter.cpp index c46a008..a4026e0 100644 --- a/source/core/audio/audio_adapter.cpp +++ b/source/core/audio/audio_adapter.cpp @@ -3,11 +3,11 @@ #include "core/resources/resource.hpp" namespace AudioResource { - JA_Music_t* getMusic(const std::string& name) { + auto getMusic(const std::string& name) -> Ja::Music* { return Resource::get()->getMusic(name); } - JA_Sound_t* getSound(const std::string& name) { + auto getSound(const std::string& name) -> Ja::Sound* { return Resource::get()->getSound(name); } } // namespace AudioResource diff --git a/source/core/audio/audio_adapter.hpp b/source/core/audio/audio_adapter.hpp index a5eb16e..7feda5a 100644 --- a/source/core/audio/audio_adapter.hpp +++ b/source/core/audio/audio_adapter.hpp @@ -1,17 +1,19 @@ #pragma once // --- Audio Resource Adapter --- -// Aquest fitxer exposa una interfície comuna a Audio per obtenir JA_Music_t* / -// JA_Sound_t* per nom. Cada projecte la implementa en audio_adapter.cpp +// Aquest fitxer exposa una interfície comuna a Audio per obtenir Ja::Music* / +// Ja::Sound* per nom. Cada projecte la implementa en audio_adapter.cpp // delegant al seu singleton de recursos (Resource::get(), Resource::Cache::get(), // etc.). Això permet que audio.hpp/audio.cpp siguin idèntics entre projectes. #include // Para string -struct JA_Music_t; -struct JA_Sound_t; +namespace Ja { + struct Music; + struct Sound; +} // namespace Ja namespace AudioResource { - JA_Music_t* getMusic(const std::string& name); - JA_Sound_t* getSound(const std::string& name); + auto getMusic(const std::string& name) -> Ja::Music*; + auto getSound(const std::string& name) -> Ja::Sound*; } // namespace AudioResource diff --git a/source/core/audio/jail_audio.hpp b/source/core/audio/jail_audio.hpp index f6dc224..276841f 100644 --- a/source/core/audio/jail_audio.hpp +++ b/source/core/audio/jail_audio.hpp @@ -2,678 +2,698 @@ // --- Includes --- #include -#include // Para uint32_t, uint8_t -#include // Para NULL, fseek, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET -#include // Para free, malloc -#include // Para std::cout -#include // Para std::unique_ptr -#include // Para std::string -#include // Para std::vector +#include +#include +#include +#include +#include +#include +#include +#include #define STB_VORBIS_HEADER_ONLY +// NOLINTNEXTLINE(bugprone-suspicious-include) — stb_vorbis és single-file: la macro de dalt limita a només-declaracions. #include "external/stb_vorbis.c" // Para stb_vorbis_open_memory i streaming // Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`. -// Compatible amb `std::unique_ptr` — zero size +// Compatible amb `std::unique_ptr` — zero size // overhead gràcies a EBO, igual que un unique_ptr amb default_delete. -struct SDLFreeDeleter { +struct SdlFreeDeleter { void operator()(Uint8* p) const noexcept { - if (p) SDL_free(p); + if (p != nullptr) { SDL_free(p); } } }; -// --- Public Enums --- -enum JA_Channel_state { - JA_CHANNEL_INVALID, - JA_CHANNEL_FREE, - JA_CHANNEL_PLAYING, - JA_CHANNEL_PAUSED, - JA_SOUND_DISABLED, -}; -enum JA_Music_state { - JA_MUSIC_INVALID, - JA_MUSIC_PLAYING, - JA_MUSIC_PAUSED, - JA_MUSIC_STOPPED, - JA_MUSIC_DISABLED, -}; +namespace Ja { -// --- Struct Definitions --- -#define JA_MAX_SIMULTANEOUS_CHANNELS 20 -#define JA_MAX_GROUPS 2 + // --- Public Enums --- + enum class ChannelState : std::uint8_t { + INVALID, + FREE, + PLAYING, + PAUSED, + DISABLED, + }; -struct JA_Sound_t { - SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; - Uint32 length{0}; - // Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV - // via SDL_malloc; el deleter `SDLFreeDeleter` allibera amb SDL_free. - std::unique_ptr buffer; -}; + enum class MusicState : std::uint8_t { + INVALID, + PLAYING, + PAUSED, + STOPPED, + DISABLED, + }; -struct JA_Channel_t { - JA_Sound_t* sound{nullptr}; - int pos{0}; - int times{0}; - int group{0}; - SDL_AudioStream* stream{nullptr}; - JA_Channel_state state{JA_CHANNEL_FREE}; -}; + // --- Constants --- + inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20; + inline constexpr int MAX_GROUPS = 2; + inline constexpr SDL_AudioSpec DEFAULT_SPEC{.format = SDL_AUDIO_S16, .channels = 2, .freq = 48000}; -struct JA_Music_t { - SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; + // --- Struct Definitions --- + struct Sound { + SDL_AudioSpec spec{DEFAULT_SPEC}; + Uint32 length{0}; + // Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV + // via SDL_malloc; el deleter `SdlFreeDeleter` allibera amb SDL_free. + std::unique_ptr buffer; + }; - // OGG comprimit en memòria. Propietat nostra; es copia des del buffer - // d'entrada una sola vegada en JA_LoadMusic i es descomprimix en chunks - // per streaming. Com que stb_vorbis guarda un punter persistent al - // `.data()` d'aquest vector, no el podem resize'jar un cop establert - // (una reallocation invalidaria el punter que el decoder conserva). - std::vector ogg_data; - stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del JA_Music_t + // L'ordre (punters primer, ints després, enum de 8 bits al final) minimitza + // el padding a 64-bit (evita avisos de clang-analyzer-optin.performance.Padding). + struct Channel { + Sound* sound{nullptr}; + SDL_AudioStream* stream{nullptr}; + int pos{0}; + int times{0}; + int group{0}; + ChannelState state{ChannelState::FREE}; + }; - std::string filename; + struct Music { + SDL_AudioSpec spec{DEFAULT_SPEC}; - int times{0}; // loops restants (-1 = infinit, 0 = un sol play) - SDL_AudioStream* stream{nullptr}; - JA_Music_state state{JA_MUSIC_INVALID}; -}; + // OGG comprimit en memòria. Propietat nostra; es copia des del buffer + // d'entrada una sola vegada en loadMusic i es descomprimix en chunks + // per streaming. Com que stb_vorbis guarda un punter persistent al + // `.data()` d'aquest vector, no el podem resize'jar un cop establert + // (una reallocation invalidaria el punter que el decoder conserva). + std::vector ogg_data; + stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del Music -// --- Internal Global State (inline, C++17) --- + std::string filename; -inline JA_Music_t* current_music{nullptr}; -inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS]; + int times{0}; // loops restants (-1 = infinit, 0 = un sol play) + SDL_AudioStream* stream{nullptr}; + MusicState state{MusicState::INVALID}; + }; -inline SDL_AudioSpec JA_audioSpec{SDL_AUDIO_S16, 2, 48000}; -inline float JA_musicVolume{1.0f}; -inline float JA_soundVolume[JA_MAX_GROUPS]; -inline bool JA_musicEnabled{true}; -inline bool JA_soundEnabled{true}; -inline SDL_AudioDeviceID sdlAudioDevice{0}; + struct FadeState { + bool active{false}; + Uint64 start_time{0}; + int duration_ms{0}; + float initial_volume{0.0F}; + }; -// --- Crossfade / Fade State --- -struct JA_FadeState { - bool active{false}; - Uint64 start_time{0}; - int duration_ms{0}; - float initial_volume{0.0f}; -}; + struct OutgoingMusic { + SDL_AudioStream* stream{nullptr}; + FadeState fade; + }; -struct JA_OutgoingMusic { - SDL_AudioStream* stream{nullptr}; - JA_FadeState fade; -}; + // --- Internal Global State (inline, C++17) --- + inline Music* current_music{nullptr}; + inline Channel channels[MAX_SIMULTANEOUS_CHANNELS]; -inline JA_OutgoingMusic outgoing_music; -inline JA_FadeState incoming_fade; + inline SDL_AudioSpec audio_spec{DEFAULT_SPEC}; + inline float music_volume{1.0F}; + inline float sound_volume[MAX_GROUPS]; + inline bool music_enabled{true}; + inline bool sound_enabled{true}; + inline SDL_AudioDeviceID sdl_audio_device{0}; -// --- Forward Declarations --- -inline void JA_StopMusic(); -inline void JA_StopChannel(const int channel); -inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0); -inline void JA_CrossfadeMusic(JA_Music_t* music, int crossfade_ms, int loop = -1); + inline OutgoingMusic outgoing_music; + inline FadeState incoming_fade; -// --- Music streaming internals --- -// Bytes-per-sample per canal (sempre s16) -static constexpr int JA_MUSIC_BYTES_PER_SAMPLE = 2; -// Quants shorts decodifiquem per crida a get_samples_short_interleaved. -// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz. -static constexpr int JA_MUSIC_CHUNK_SHORTS = 8192; -// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a -// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns. -static constexpr float JA_MUSIC_LOW_WATER_SECONDS = 0.5f; + // --- Forward Declarations --- + inline void stopMusic(); + inline void stopChannel(int channel); + inline auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int; + inline void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1); -// Decodifica un chunk del vorbis i el volca a l'stream. Retorna samples -// decodificats per canal (0 = EOF de l'stream vorbis). -inline int JA_FeedMusicChunk(JA_Music_t* music) { - if (!music || !music->vorbis || !music->stream) return 0; + // --- Music streaming internals --- + // Bytes-per-sample per canal (sempre s16) + inline constexpr int MUSIC_BYTES_PER_SAMPLE = 2; + // Quants shorts decodifiquem per crida a get_samples_short_interleaved. + // 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz. + inline constexpr int MUSIC_CHUNK_SHORTS = 8192; + // Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a + // l'SDL_AudioStream per absorbir jitter de frame i evitar underruns. + inline constexpr float MUSIC_LOW_WATER_SECONDS = 0.5F; - short chunk[JA_MUSIC_CHUNK_SHORTS]; - const int num_channels = music->spec.channels; - const int samples_per_channel = stb_vorbis_get_samples_short_interleaved( - music->vorbis, - num_channels, - chunk, - JA_MUSIC_CHUNK_SHORTS); - if (samples_per_channel <= 0) return 0; + // Decodifica un chunk del vorbis i el volca a l'stream. Retorna samples + // decodificats per canal (0 = EOF de l'stream vorbis). + inline auto feedMusicChunk(Music* music) -> int { + if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return 0; } - const int bytes = samples_per_channel * num_channels * JA_MUSIC_BYTES_PER_SAMPLE; - SDL_PutAudioStreamData(music->stream, chunk, bytes); - return samples_per_channel; -} + short chunk[MUSIC_CHUNK_SHORTS]; + const int NUM_CHANNELS = music->spec.channels; + const int SAMPLES_PER_CHANNEL = stb_vorbis_get_samples_short_interleaved( + music->vorbis, + NUM_CHANNELS, + chunk, + MUSIC_CHUNK_SHORTS); + if (SAMPLES_PER_CHANNEL <= 0) { return 0; } -// Reompli l'stream fins que tinga ≥ JA_MUSIC_LOW_WATER_SECONDS bufferats. -// En arribar a EOF del vorbis, aplica el loop (times) o deixa drenar. -inline void JA_PumpMusic(JA_Music_t* music) { - if (!music || !music->vorbis || !music->stream) return; + const int BYTES = SAMPLES_PER_CHANNEL * NUM_CHANNELS * MUSIC_BYTES_PER_SAMPLE; + SDL_PutAudioStreamData(music->stream, chunk, BYTES); + return SAMPLES_PER_CHANNEL; + } - const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE; - const int low_water_bytes = static_cast(JA_MUSIC_LOW_WATER_SECONDS * static_cast(bytes_per_second)); + // Reompli l'stream fins que tinga ≥ MUSIC_LOW_WATER_SECONDS bufferats. + // En arribar a EOF del vorbis, aplica el loop (times) o deixa drenar. + inline void pumpMusic(Music* music) { + if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return; } - while (SDL_GetAudioStreamAvailable(music->stream) < low_water_bytes) { - const int decoded = JA_FeedMusicChunk(music); - if (decoded > 0) continue; + const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE; + const int LOW_WATER_BYTES = static_cast(MUSIC_LOW_WATER_SECONDS * static_cast(BYTES_PER_SECOND)); - // EOF: si queden loops, rebobinar; si no, tallar i deixar drenar. - if (music->times != 0) { - stb_vorbis_seek_start(music->vorbis); - if (music->times > 0) music->times--; - } else { - break; + while (SDL_GetAudioStreamAvailable(music->stream) < LOW_WATER_BYTES) { + const int DECODED = feedMusicChunk(music); + if (DECODED > 0) { continue; } + + // EOF: si queden loops, rebobinar; si no, tallar i deixar drenar. + if (music->times != 0) { + stb_vorbis_seek_start(music->vorbis); + if (music->times > 0) { music->times--; } + } else { + break; + } } } -} -// Pre-carrega `duration_ms` de so dins l'stream actual abans que l'stream -// siga robat per outgoing_music (crossfade o fade-out). Imprescindible amb -// streaming: l'stream robat no es pot re-alimentar perquè perd la referència -// al seu vorbis decoder. No aplica loop — si el vorbis s'esgota abans, parem. -inline void JA_PreFillOutgoing(JA_Music_t* music, int duration_ms) { - if (!music || !music->vorbis || !music->stream) return; + // Pre-carrega `duration_ms` de so dins l'stream actual abans que l'stream + // siga robat per outgoing_music (crossfade o fade-out). Imprescindible amb + // streaming: l'stream robat no es pot re-alimentar perquè perd la referència + // al seu vorbis decoder. No aplica loop — si el vorbis s'esgota abans, parem. + inline void preFillOutgoing(Music* music, int duration_ms) { + if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return; } - const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE; - const int needed_bytes = static_cast((static_cast(duration_ms) * bytes_per_second) / 1000); + const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE; + const int NEEDED_BYTES = static_cast((static_cast(duration_ms) * BYTES_PER_SECOND) / 1000); - while (SDL_GetAudioStreamAvailable(music->stream) < needed_bytes) { - const int decoded = JA_FeedMusicChunk(music); - if (decoded <= 0) break; // EOF: deixem drenar el que hi haja + while (SDL_GetAudioStreamAvailable(music->stream) < NEEDED_BYTES) { + const int DECODED = feedMusicChunk(music); + if (DECODED <= 0) { break; } // EOF: deixem drenar el que hi haja + } } -} -// --- Core Functions --- + // --- update() helpers --- + inline void updateOutgoingFade() { + if ((outgoing_music.stream == nullptr) || !outgoing_music.fade.active) { return; } -inline void JA_Update() { - // --- Outgoing music fade-out (crossfade o fade-out a silencio) --- - if (outgoing_music.stream && outgoing_music.fade.active) { - Uint64 now = SDL_GetTicks(); - Uint64 elapsed = now - outgoing_music.fade.start_time; - if (elapsed >= (Uint64)outgoing_music.fade.duration_ms) { + const Uint64 NOW = SDL_GetTicks(); + const Uint64 ELAPSED = NOW - outgoing_music.fade.start_time; + if (ELAPSED >= static_cast(outgoing_music.fade.duration_ms)) { SDL_DestroyAudioStream(outgoing_music.stream); outgoing_music.stream = nullptr; outgoing_music.fade.active = false; } else { - float percent = (float)elapsed / (float)outgoing_music.fade.duration_ms; - SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0f - percent)); + const float PERCENT = static_cast(ELAPSED) / static_cast(outgoing_music.fade.duration_ms); + SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0F - PERCENT)); } } - // --- Current music --- - if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) { - // Fade-in (parte de un crossfade) - if (incoming_fade.active) { - Uint64 now = SDL_GetTicks(); - Uint64 elapsed = now - incoming_fade.start_time; - if (elapsed >= (Uint64)incoming_fade.duration_ms) { - incoming_fade.active = false; - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); - } else { - float percent = (float)elapsed / (float)incoming_fade.duration_ms; - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * percent); - } + inline void updateIncomingFade() { + if (!incoming_fade.active) { return; } + + const Uint64 NOW = SDL_GetTicks(); + const Uint64 ELAPSED = NOW - incoming_fade.start_time; + if (ELAPSED >= static_cast(incoming_fade.duration_ms)) { + incoming_fade.active = false; + SDL_SetAudioStreamGain(current_music->stream, music_volume); + } else { + const float PERCENT = static_cast(ELAPSED) / static_cast(incoming_fade.duration_ms); + SDL_SetAudioStreamGain(current_music->stream, music_volume * PERCENT); } + } + + inline void updateCurrentMusic() { + if (!music_enabled || (current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; } + + updateIncomingFade(); // Streaming: rellenem l'stream fins al low-water-mark i parem si el // vorbis s'ha esgotat i no queden loops. - JA_PumpMusic(current_music); + pumpMusic(current_music); if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) { - JA_StopMusic(); + stopMusic(); } } - // --- Sound channels --- - if (JA_soundEnabled) { - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) - if (channels[i].state == JA_CHANNEL_PLAYING) { - if (channels[i].times != 0) { - if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) { - SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer.get(), channels[i].sound->length); - if (channels[i].times > 0) channels[i].times--; - } - } else { - if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i); + inline void updateSoundChannels() { + if (!sound_enabled) { return; } + + for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) { + auto& ch = channels[i]; + if (ch.state != ChannelState::PLAYING) { continue; } + + if (ch.times != 0) { + if (static_cast(SDL_GetAudioStreamAvailable(ch.stream)) < (ch.sound->length / 2)) { + SDL_PutAudioStreamData(ch.stream, ch.sound->buffer.get(), ch.sound->length); + if (ch.times > 0) { ch.times--; } } + } else { + if (SDL_GetAudioStreamAvailable(ch.stream) == 0) { stopChannel(i); } } - } -} - -inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) { - JA_audioSpec = {format, num_channels, freq}; - if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); - sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec); - if (sdlAudioDevice == 0) std::cout << "Failed to initialize SDL audio!" << '\n'; - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE; - for (int i = 0; i < JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f; -} - -inline void JA_Quit() { - if (outgoing_music.stream) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; - } - if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice); - sdlAudioDevice = 0; -} - -// --- Music Functions --- - -inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) { - if (!buffer || length == 0) return nullptr; - - // Allocem el JA_Music_t primer per aprofitar el seu `std::vector` - // com a propietari del OGG comprimit. stb_vorbis guarda un punter - // persistent al buffer; com que ací no el resize'jem, el .data() és - // estable durant tot el cicle de vida del music. - auto* music = new JA_Music_t(); - music->ogg_data.assign(buffer, buffer + length); - - int err_code = 0; - music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(), - static_cast(length), - &err_code, - nullptr); - if (!music->vorbis) { - std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << err_code << ")" << '\n'; - delete music; - return nullptr; + } } - const stb_vorbis_info info = stb_vorbis_get_info(music->vorbis); - music->spec.channels = info.channels; - music->spec.freq = static_cast(info.sample_rate); - music->spec.format = SDL_AUDIO_S16; - music->state = JA_MUSIC_STOPPED; - - return music; -} - -// Overload amb filename — els callers l'usen per poder comparar la música -// en curs amb JA_GetMusicFilename() i no rearrancar-la si ja és la mateixa. -inline JA_Music_t* JA_LoadMusic(Uint8* buffer, Uint32 length, const char* filename) { - JA_Music_t* music = JA_LoadMusic(static_cast(buffer), length); - if (music && filename) music->filename = filename; - return music; -} - -inline JA_Music_t* JA_LoadMusic(const char* filename) { - // Carreguem primer el arxiu en memòria i després el descomprimim. - FILE* f = fopen(filename, "rb"); - if (!f) return nullptr; - fseek(f, 0, SEEK_END); - long fsize = ftell(f); - fseek(f, 0, SEEK_SET); - auto* buffer = static_cast(malloc(fsize + 1)); - if (!buffer) { - fclose(f); - return nullptr; - } - if (fread(buffer, fsize, 1, f) != 1) { - fclose(f); - free(buffer); - return nullptr; - } - fclose(f); - - JA_Music_t* music = JA_LoadMusic(static_cast(buffer), static_cast(fsize)); - if (music) { - music->filename = filename; + inline void update() { + updateOutgoingFade(); + updateCurrentMusic(); + updateSoundChannels(); } - free(buffer); - - return music; -} - -inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) { - if (!JA_musicEnabled || !music || !music->vorbis) return; - - JA_StopMusic(); - - current_music = music; - current_music->state = JA_MUSIC_PLAYING; - current_music->times = loop; - - // Rebobinem l'stream de vorbis al principi. Cobreix tant play-per-primera- - // vegada com replays/canvis de track que tornen a la mateixa pista. - stb_vorbis_seek_start(current_music->vorbis); - - current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec); - if (!current_music->stream) { - std::cout << "Failed to create audio stream!" << '\n'; - current_music->state = JA_MUSIC_STOPPED; - return; + inline void init(int freq, SDL_AudioFormat format, int num_channels) { + audio_spec = {.format = format, .channels = num_channels, .freq = freq}; + if (sdl_audio_device != 0) { SDL_CloseAudioDevice(sdl_audio_device); } + sdl_audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec); + if (sdl_audio_device == 0) { std::cout << "Failed to initialize SDL audio!" << '\n'; } + for (auto& ch : channels) { ch.state = ChannelState::FREE; } + std::fill(std::begin(sound_volume), std::end(sound_volume), 0.5F); } - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); - // Pre-cargem el buffer abans de bindejar per evitar un underrun inicial. - JA_PumpMusic(current_music); - - if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) { - std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n'; + inline void quit() { + if (outgoing_music.stream != nullptr) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + } + if (sdl_audio_device != 0) { SDL_CloseAudioDevice(sdl_audio_device); } + sdl_audio_device = 0; } -} -inline const char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) { - if (!music) music = current_music; - if (!music || music->filename.empty()) return nullptr; - return music->filename.c_str(); -} + // --- Music Functions --- + inline auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* { + if ((buffer == nullptr) || length == 0) { return nullptr; } -inline void JA_PauseMusic() { - if (!JA_musicEnabled) return; - if (!current_music || current_music->state != JA_MUSIC_PLAYING) return; + // Allocem el Music primer per aprofitar el seu `std::vector` + // com a propietari del OGG comprimit. stb_vorbis guarda un punter + // persistent al buffer; com que ací no el resize'jem, el .data() és + // estable durant tot el cicle de vida del music. + auto* music = new Music(); + music->ogg_data.assign(buffer, buffer + length); - current_music->state = JA_MUSIC_PAUSED; - SDL_UnbindAudioStream(current_music->stream); -} + int vorbis_error = 0; + music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(), + static_cast(length), + &vorbis_error, + nullptr); + if (music->vorbis == nullptr) { + std::cout << "loadMusic: stb_vorbis_open_memory failed (error " << vorbis_error << ")" << '\n'; + delete music; + return nullptr; + } -inline void JA_ResumeMusic() { - if (!JA_musicEnabled) return; - if (!current_music || current_music->state != JA_MUSIC_PAUSED) return; + const stb_vorbis_info INFO = stb_vorbis_get_info(music->vorbis); + music->spec.channels = INFO.channels; + music->spec.freq = static_cast(INFO.sample_rate); + music->spec.format = SDL_AUDIO_S16; + music->state = MusicState::STOPPED; - current_music->state = JA_MUSIC_PLAYING; - SDL_BindAudioStream(sdlAudioDevice, current_music->stream); -} - -inline void JA_StopMusic() { - // Limpiar outgoing crossfade si existe - if (outgoing_music.stream) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; - outgoing_music.fade.active = false; + return music; } - incoming_fade.active = false; - if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return; - - current_music->state = JA_MUSIC_STOPPED; - if (current_music->stream) { - SDL_DestroyAudioStream(current_music->stream); - current_music->stream = nullptr; + // Overload amb filename — els callers l'usen per poder comparar la música + // en curs amb getMusicFilename() i no rearrancar-la si ja és la mateixa. + inline auto loadMusic(Uint8* buffer, Uint32 length, const char* filename) -> Music* { + Music* music = loadMusic(static_cast(buffer), length); + if ((music != nullptr) && (filename != nullptr)) { music->filename = filename; } + return music; } - // Deixem el handle de vorbis viu — es tanca en JA_DeleteMusic. - // Rebobinem perquè un futur JA_PlayMusic comence des del principi. - if (current_music->vorbis) { + + inline auto loadMusic(const char* filename) -> Music* { + // Carreguem primer el arxiu en memòria i després el descomprimim. + FILE* f = std::fopen(filename, "rb"); + if (f == nullptr) { return nullptr; } + std::fseek(f, 0, SEEK_END); + const long FSIZE = std::ftell(f); + std::fseek(f, 0, SEEK_SET); + if (FSIZE <= 0) { + std::fclose(f); + return nullptr; + } + auto* buffer = static_cast(std::malloc(static_cast(FSIZE) + 1)); + if (buffer == nullptr) { + std::fclose(f); + return nullptr; + } + if (std::fread(buffer, FSIZE, 1, f) != 1) { + std::fclose(f); + std::free(buffer); + return nullptr; + } + std::fclose(f); + + Music* music = loadMusic(static_cast(buffer), static_cast(FSIZE)); + if (music != nullptr) { music->filename = filename; } + + std::free(buffer); + + return music; + } + + inline void playMusic(Music* music, int loop = -1) { + if (!music_enabled || (music == nullptr) || (music->vorbis == nullptr)) { return; } + + stopMusic(); + + current_music = music; + current_music->state = MusicState::PLAYING; + current_music->times = loop; + + // Rebobinem l'stream de vorbis al principi. Cobreix tant play-per-primera- + // vegada com replays/canvis de track que tornen a la mateixa pista. stb_vorbis_seek_start(current_music->vorbis); - } -} -inline void JA_FadeOutMusic(const int milliseconds) { - if (!JA_musicEnabled) return; - if (!current_music || current_music->state != JA_MUSIC_PLAYING) return; + current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &audio_spec); + if (current_music->stream == nullptr) { + std::cout << "Failed to create audio stream!" << '\n'; + current_music->state = MusicState::STOPPED; + return; + } + SDL_SetAudioStreamGain(current_music->stream, music_volume); - // Destruir outgoing anterior si existe - if (outgoing_music.stream) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; + // Pre-cargem el buffer abans de bindejar per evitar un underrun inicial. + pumpMusic(current_music); + + if (!SDL_BindAudioStream(sdl_audio_device, current_music->stream)) { + std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n'; + } } - // Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no - // tindrà accés al vorbis decoder i només podrà drenar el que tinga. - JA_PreFillOutgoing(current_music, milliseconds); - - // Robar el stream del current_music al outgoing - outgoing_music.stream = current_music->stream; - outgoing_music.fade = {true, SDL_GetTicks(), milliseconds, JA_musicVolume}; - - // Dejar current_music sin stream (ya lo tiene outgoing) - current_music->stream = nullptr; - current_music->state = JA_MUSIC_STOPPED; - if (current_music->vorbis) stb_vorbis_seek_start(current_music->vorbis); - incoming_fade.active = false; -} - -inline void JA_CrossfadeMusic(JA_Music_t* music, const int crossfade_ms, const int loop) { - if (!JA_musicEnabled || !music || !music->vorbis) return; - - // Destruir outgoing anterior si existe (crossfade durante crossfade) - if (outgoing_music.stream) { - SDL_DestroyAudioStream(outgoing_music.stream); - outgoing_music.stream = nullptr; - outgoing_music.fade.active = false; + inline auto getMusicFilename(const Music* music = nullptr) -> const char* { + if (music == nullptr) { music = current_music; } + if ((music == nullptr) || music->filename.empty()) { return nullptr; } + return music->filename.c_str(); } - // Robar el stream de la musica actual al outgoing para el fade-out. - // Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci - // abans d'acabar el fade (l'stream robat ja no pot alimentar-se). - if (current_music && current_music->state == JA_MUSIC_PLAYING && current_music->stream) { - JA_PreFillOutgoing(current_music, crossfade_ms); + inline void pauseMusic() { + if (!music_enabled) { return; } + if ((current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; } + + current_music->state = MusicState::PAUSED; + SDL_UnbindAudioStream(current_music->stream); + } + + inline void resumeMusic() { + if (!music_enabled) { return; } + if ((current_music == nullptr) || current_music->state != MusicState::PAUSED) { return; } + + current_music->state = MusicState::PLAYING; + SDL_BindAudioStream(sdl_audio_device, current_music->stream); + } + + inline void stopMusic() { + // Limpiar outgoing crossfade si existe + if (outgoing_music.stream != nullptr) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + outgoing_music.fade.active = false; + } + incoming_fade.active = false; + + if ((current_music == nullptr) || current_music->state == MusicState::INVALID || current_music->state == MusicState::STOPPED) { return; } + + current_music->state = MusicState::STOPPED; + if (current_music->stream != nullptr) { + SDL_DestroyAudioStream(current_music->stream); + current_music->stream = nullptr; + } + // Deixem el handle de vorbis viu — es tanca en deleteMusic. + // Rebobinem perquè un futur playMusic comence des del principi. + if (current_music->vorbis != nullptr) { + stb_vorbis_seek_start(current_music->vorbis); + } + } + + inline void fadeOutMusic(int milliseconds) { + if (!music_enabled) { return; } + if ((current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; } + + // Destruir outgoing anterior si existe + if (outgoing_music.stream != nullptr) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + } + + // Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no + // tindrà accés al vorbis decoder i només podrà drenar el que tinga. + preFillOutgoing(current_music, milliseconds); + + // Robar el stream del current_music al outgoing outgoing_music.stream = current_music->stream; - outgoing_music.fade = {true, SDL_GetTicks(), crossfade_ms, JA_musicVolume}; + outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = milliseconds, .initial_volume = music_volume}; + + // Dejar current_music sin stream (ya lo tiene outgoing) current_music->stream = nullptr; - current_music->state = JA_MUSIC_STOPPED; - if (current_music->vorbis) stb_vorbis_seek_start(current_music->vorbis); + current_music->state = MusicState::STOPPED; + if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); } + incoming_fade.active = false; } - // Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente) - current_music = music; - current_music->state = JA_MUSIC_PLAYING; - current_music->times = loop; + inline void crossfadeMusic(Music* music, int crossfade_ms, int loop) { + if (!music_enabled || (music == nullptr) || (music->vorbis == nullptr)) { return; } - stb_vorbis_seek_start(current_music->vorbis); - current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec); - if (!current_music->stream) { - std::cout << "Failed to create audio stream for crossfade!" << '\n'; - current_music->state = JA_MUSIC_STOPPED; - return; - } - SDL_SetAudioStreamGain(current_music->stream, 0.0f); - JA_PumpMusic(current_music); // pre-carrega abans de bindejar - SDL_BindAudioStream(sdlAudioDevice, current_music->stream); - - // Configurar fade-in - incoming_fade = {true, SDL_GetTicks(), crossfade_ms, 0.0f}; -} - -inline JA_Music_state JA_GetMusicState() { - if (!JA_musicEnabled) return JA_MUSIC_DISABLED; - if (!current_music) return JA_MUSIC_INVALID; - - return current_music->state; -} - -inline void JA_DeleteMusic(JA_Music_t* music) { - if (!music) return; - if (current_music == music) { - JA_StopMusic(); - current_music = nullptr; - } - if (music->stream) SDL_DestroyAudioStream(music->stream); - if (music->vorbis) stb_vorbis_close(music->vorbis); - // ogg_data (std::vector) i filename (std::string) s'alliberen sols - // al destructor de JA_Music_t. - delete music; -} - -inline float JA_SetMusicVolume(float volume) { - JA_musicVolume = SDL_clamp(volume, 0.0f, 1.0f); - if (current_music && current_music->stream) { - SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume); - } - return JA_musicVolume; -} - -inline void JA_SetMusicPosition(float /*value*/) { - // No implementat amb el backend de streaming. -} - -inline float JA_GetMusicPosition() { - return 0.0f; -} - -inline void JA_EnableMusic(const bool value) { - if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic(); - JA_musicEnabled = value; -} - -// --- Sound Functions --- - -inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) { - auto sound = std::make_unique(); - Uint8* raw = nullptr; - if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &raw, &sound->length)) { - std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n'; - return nullptr; - } - sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer - return sound.release(); -} - -inline JA_Sound_t* JA_LoadSound(const char* filename) { - auto sound = std::make_unique(); - Uint8* raw = nullptr; - if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) { - std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n'; - return nullptr; - } - sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer - return sound.release(); -} - -inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) { - if (!JA_soundEnabled || !sound) return -1; - - int channel = 0; - while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; } - if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) { - // No hay canal libre, reemplazamos el primero - channel = 0; - } - - return JA_PlaySoundOnChannel(sound, channel, loop, group); -} - -inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop, const int group) { - if (!JA_soundEnabled || !sound) return -1; - if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1; - - JA_StopChannel(channel); - - channels[channel].sound = sound; - channels[channel].times = loop; - channels[channel].pos = 0; - channels[channel].group = group; - channels[channel].state = JA_CHANNEL_PLAYING; - channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec); - - if (!channels[channel].stream) { - std::cout << "Failed to create audio stream for sound!" << '\n'; - channels[channel].state = JA_CHANNEL_FREE; - return -1; - } - - SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length); - SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]); - SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); - - return channel; -} - -inline void JA_DeleteSound(JA_Sound_t* sound) { - if (!sound) return; - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { - if (channels[i].sound == sound) JA_StopChannel(i); - } - // buffer es destrueix automàticament via RAII (SDLFreeDeleter). - delete sound; -} - -inline void JA_PauseChannel(const int channel) { - if (!JA_soundEnabled) return; - - if (channel == -1) { - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) - if (channels[i].state == JA_CHANNEL_PLAYING) { - channels[i].state = JA_CHANNEL_PAUSED; - SDL_UnbindAudioStream(channels[i].stream); - } - } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { - if (channels[channel].state == JA_CHANNEL_PLAYING) { - channels[channel].state = JA_CHANNEL_PAUSED; - SDL_UnbindAudioStream(channels[channel].stream); + // Destruir outgoing anterior si existe (crossfade durante crossfade) + if (outgoing_music.stream != nullptr) { + SDL_DestroyAudioStream(outgoing_music.stream); + outgoing_music.stream = nullptr; + outgoing_music.fade.active = false; } - } -} -inline void JA_ResumeChannel(const int channel) { - if (!JA_soundEnabled) return; - - if (channel == -1) { - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) - if (channels[i].state == JA_CHANNEL_PAUSED) { - channels[i].state = JA_CHANNEL_PLAYING; - SDL_BindAudioStream(sdlAudioDevice, channels[i].stream); - } - } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { - if (channels[channel].state == JA_CHANNEL_PAUSED) { - channels[channel].state = JA_CHANNEL_PLAYING; - SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream); + // Robar el stream de la musica actual al outgoing para el fade-out. + // Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci + // abans d'acabar el fade (l'stream robat ja no pot alimentar-se). + if ((current_music != nullptr) && current_music->state == MusicState::PLAYING && (current_music->stream != nullptr)) { + preFillOutgoing(current_music, crossfade_ms); + outgoing_music.stream = current_music->stream; + outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = music_volume}; + current_music->stream = nullptr; + current_music->state = MusicState::STOPPED; + if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); } } - } -} -inline void JA_StopChannel(const int channel) { - if (channel == -1) { - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { - if (channels[i].state != JA_CHANNEL_FREE) { - if (channels[i].stream) SDL_DestroyAudioStream(channels[i].stream); - channels[i].stream = nullptr; - channels[i].state = JA_CHANNEL_FREE; - channels[i].pos = 0; - channels[i].sound = nullptr; + // Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente) + current_music = music; + current_music->state = MusicState::PLAYING; + current_music->times = loop; + + stb_vorbis_seek_start(current_music->vorbis); + current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &audio_spec); + if (current_music->stream == nullptr) { + std::cout << "Failed to create audio stream for crossfade!" << '\n'; + current_music->state = MusicState::STOPPED; + return; + } + SDL_SetAudioStreamGain(current_music->stream, 0.0F); + pumpMusic(current_music); // pre-carrega abans de bindejar + SDL_BindAudioStream(sdl_audio_device, current_music->stream); + + // Configurar fade-in + incoming_fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = 0.0F}; + } + + inline auto getMusicState() -> MusicState { + if (!music_enabled) { return MusicState::DISABLED; } + if (current_music == nullptr) { return MusicState::INVALID; } + + return current_music->state; + } + + inline void deleteMusic(Music* music) { + if (music == nullptr) { return; } + if (current_music == music) { + stopMusic(); + current_music = nullptr; + } + if (music->stream != nullptr) { SDL_DestroyAudioStream(music->stream); } + if (music->vorbis != nullptr) { stb_vorbis_close(music->vorbis); } + // ogg_data (std::vector) i filename (std::string) s'alliberen sols + // al destructor de Music. + delete music; + } + + inline auto setMusicVolume(float volume) -> float { + music_volume = SDL_clamp(volume, 0.0F, 1.0F); + if ((current_music != nullptr) && (current_music->stream != nullptr)) { + SDL_SetAudioStreamGain(current_music->stream, music_volume); + } + return music_volume; + } + + inline void setMusicPosition(float /*value*/) { + // No implementat amb el backend de streaming. + } + + inline auto getMusicPosition() -> float { + return 0.0F; + } + + inline void enableMusic(bool value) { + if (!value && (current_music != nullptr) && (current_music->state == MusicState::PLAYING)) { stopMusic(); } + music_enabled = value; + } + + // --- Sound Functions --- + inline auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound* { + auto sound = std::make_unique(); + Uint8* raw = nullptr; + if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), true, &sound->spec, &raw, &sound->length)) { + std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n'; + return nullptr; + } + sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer + return sound.release(); + } + + inline auto loadSound(const char* filename) -> Sound* { + auto sound = std::make_unique(); + Uint8* raw = nullptr; + if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) { + std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n'; + return nullptr; + } + sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer + return sound.release(); + } + + inline auto playSound(Sound* sound, int loop = 0, int group = 0) -> int { + if (!sound_enabled || (sound == nullptr)) { return -1; } + + int channel = 0; + while (channel < MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != ChannelState::FREE) { channel++; } + if (channel == MAX_SIMULTANEOUS_CHANNELS) { + // No hi ha canal lliure, reemplacem el primer + channel = 0; + } + + return playSoundOnChannel(sound, channel, loop, group); + } + + inline auto playSoundOnChannel(Sound* sound, int channel, int loop, int group) -> int { + if (!sound_enabled || (sound == nullptr)) { return -1; } + if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return -1; } + + stopChannel(channel); + + channels[channel].sound = sound; + channels[channel].times = loop; + channels[channel].pos = 0; + channels[channel].group = group; + channels[channel].state = ChannelState::PLAYING; + channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &audio_spec); + + if (channels[channel].stream == nullptr) { + std::cout << "Failed to create audio stream for sound!" << '\n'; + channels[channel].state = ChannelState::FREE; + return -1; + } + + SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length); + SDL_SetAudioStreamGain(channels[channel].stream, sound_volume[group]); + SDL_BindAudioStream(sdl_audio_device, channels[channel].stream); + + return channel; + } + + inline void deleteSound(Sound* sound) { + if (sound == nullptr) { return; } + for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; i++) { + if (channels[i].sound == sound) { stopChannel(i); } + } + // buffer es destrueix automàticament via RAII (SdlFreeDeleter). + delete sound; + } + + inline void pauseChannel(int channel) { + if (!sound_enabled) { return; } + + if (channel == -1) { + for (auto& ch : channels) { + if (ch.state == ChannelState::PLAYING) { + ch.state = ChannelState::PAUSED; + SDL_UnbindAudioStream(ch.stream); + } + } + } else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state == ChannelState::PLAYING) { + channels[channel].state = ChannelState::PAUSED; + SDL_UnbindAudioStream(channels[channel].stream); } } - } else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) { - if (channels[channel].state != JA_CHANNEL_FREE) { - if (channels[channel].stream) SDL_DestroyAudioStream(channels[channel].stream); - channels[channel].stream = nullptr; - channels[channel].state = JA_CHANNEL_FREE; - channels[channel].pos = 0; - channels[channel].sound = nullptr; - } - } -} - -inline JA_Channel_state JA_GetChannelState(const int channel) { - if (!JA_soundEnabled) return JA_SOUND_DISABLED; - if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID; - - return channels[channel].state; -} - -inline float JA_SetSoundVolume(float volume, const int group = -1) { - const float v = SDL_clamp(volume, 0.0f, 1.0f); - - if (group == -1) { - for (int i = 0; i < JA_MAX_GROUPS; ++i) { - JA_soundVolume[i] = v; - } - } else if (group >= 0 && group < JA_MAX_GROUPS) { - JA_soundVolume[group] = v; - } else { - return v; } - // Aplicar volum als canals actius. - for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) { - if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) { - if (group == -1 || channels[i].group == group) { - if (channels[i].stream) { - SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[channels[i].group]); + inline void resumeChannel(int channel) { + if (!sound_enabled) { return; } + + if (channel == -1) { + for (auto& ch : channels) { + if (ch.state == ChannelState::PAUSED) { + ch.state = ChannelState::PLAYING; + SDL_BindAudioStream(sdl_audio_device, ch.stream); + } + } + } else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state == ChannelState::PAUSED) { + channels[channel].state = ChannelState::PLAYING; + SDL_BindAudioStream(sdl_audio_device, channels[channel].stream); + } + } + } + + inline void stopChannel(int channel) { + if (channel == -1) { + for (auto& ch : channels) { + if (ch.state != ChannelState::FREE) { + if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); } + ch.stream = nullptr; + ch.state = ChannelState::FREE; + ch.pos = 0; + ch.sound = nullptr; + } + } + } else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) { + if (channels[channel].state != ChannelState::FREE) { + if (channels[channel].stream != nullptr) { SDL_DestroyAudioStream(channels[channel].stream); } + channels[channel].stream = nullptr; + channels[channel].state = ChannelState::FREE; + channels[channel].pos = 0; + channels[channel].sound = nullptr; + } + } + } + + inline auto getChannelState(int channel) -> ChannelState { + if (!sound_enabled) { return ChannelState::DISABLED; } + if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return ChannelState::INVALID; } + + return channels[channel].state; + } + + inline auto setSoundVolume(float volume, int group = -1) -> float { + const float V = SDL_clamp(volume, 0.0F, 1.0F); + + if (group == -1) { + std::fill(std::begin(sound_volume), std::end(sound_volume), V); + } else if (group >= 0 && group < MAX_GROUPS) { + sound_volume[group] = V; + } else { + return V; + } + + // Aplicar volum als canals actius. + for (auto& ch : channels) { + if ((ch.state == ChannelState::PLAYING) || (ch.state == ChannelState::PAUSED)) { + if (group == -1 || ch.group == group) { + if (ch.stream != nullptr) { + SDL_SetAudioStreamGain(ch.stream, sound_volume[ch.group]); + } } } } + return V; } - return v; -} -inline void JA_EnableSound(const bool value) { - if (!value) { - JA_StopChannel(-1); + inline void enableSound(bool value) { + if (!value) { + stopChannel(-1); + } + sound_enabled = value; } - JA_soundEnabled = value; -} -inline float JA_SetVolume(float volume) { - float v = JA_SetMusicVolume(volume); - JA_SetSoundVolume(v, -1); - return v; -} + inline auto setVolume(float volume) -> float { + const float V = setMusicVolume(volume); + setSoundVolume(V, -1); + return V; + } + +} // namespace Ja diff --git a/source/core/input/define_buttons.cpp b/source/core/input/define_buttons.cpp index 750b346..ca028fd 100644 --- a/source/core/input/define_buttons.cpp +++ b/source/core/input/define_buttons.cpp @@ -5,7 +5,6 @@ #include // Para unique_ptr, allocator, shared_ptr, operator==, make_unique #include "core/input/input.hpp" // Para Input -#include "core/input/input_types.hpp" // Para InputAction #include "core/locale/lang.hpp" // Para getText #include "core/resources/resource.hpp" // Para Resource #include "game/options.hpp" // Para Gamepad @@ -17,9 +16,9 @@ DefineButtons::DefineButtons() : input_(Input::get()) { clearButtons(); - const auto gamepads = input_->getGamepads(); - controller_names_.reserve(gamepads.size()); - std::ranges::transform(gamepads, std::back_inserter(controller_names_), Input::getControllerName); + const auto GAMEPADS = input_->getGamepads(); + controller_names_.reserve(GAMEPADS.size()); + std::ranges::transform(GAMEPADS, std::back_inserter(controller_names_), Input::getControllerName); // Crear la ventana de mensaje WindowMessage::Config config(param.service_menu.window_message); diff --git a/source/core/input/global_inputs.cpp b/source/core/input/global_inputs.cpp index 090e395..ba4cd70 100644 --- a/source/core/input/global_inputs.cpp +++ b/source/core/input/global_inputs.cpp @@ -2,14 +2,12 @@ #include // Para __any_of_fn, any_of #include // Para function -#include // Para pair #include // Para basic_string, operator+, allocator, char_traits, string, to_string #include // Para pair #include // Para vector #include "core/audio/audio.hpp" // Para Audio #include "core/input/input.hpp" // Para Input -#include "core/input/input_types.hpp" // Para InputAction #include "core/locale/lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile #include "core/rendering/screen.hpp" // Para Screen #include "core/system/section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode @@ -84,12 +82,12 @@ namespace GlobalInputs { void nextPreset() { if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) { Screen::nextCrtPiPreset(); - const std::string name = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast(Options::video.shader.current_crtpi_preset)).name; - Notifier::get()->show({"CrtPi: " + name}); + const std::string NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast(Options::video.shader.current_crtpi_preset)).name; + Notifier::get()->show({"CrtPi: " + NAME}); } else { Screen::nextPostFXPreset(); - const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast(Options::video.shader.current_postfx_preset)).name; - Notifier::get()->show({"PostFX: " + name}); + const std::string NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast(Options::video.shader.current_postfx_preset)).name; + Notifier::get()->show({"PostFX: " + NAME}); } } diff --git a/source/core/input/input.cpp b/source/core/input/input.cpp index 0ef77fe..9fd3f07 100644 --- a/source/core/input/input.cpp +++ b/source/core/input/input.cpp @@ -157,15 +157,15 @@ auto Input::getNumGamepads() const -> int { return gamepads_.size(); } // Obtiene el gamepad a partir de un event.id auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr { - const auto it = std::ranges::find_if(gamepads_, + const auto IT = std::ranges::find_if(gamepads_, [id](const auto& gamepad) { return gamepad->instance_id == id; }); - return it != gamepads_.end() ? *it : nullptr; + return IT != gamepads_.end() ? *IT : nullptr; } auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr { - const auto it = std::ranges::find_if(gamepads_, + const auto IT = std::ranges::find_if(gamepads_, [&name](const auto& gamepad) { return gamepad && gamepad->name == name; }); - return it != gamepads_.end() ? *it : nullptr; + return IT != gamepads_.end() ? *IT : nullptr; } // Obtiene el SDL_GamepadButton asignado a un action @@ -360,8 +360,9 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string { return addGamepad(event.gdevice.which); case SDL_EVENT_GAMEPAD_REMOVED: return removeGamepad(event.gdevice.which); + default: + return {}; } - return {}; } auto Input::addGamepad(int device_index) -> std::string { diff --git a/source/core/input/input.hpp b/source/core/input/input.hpp index 52bd6d0..faeb6f0 100644 --- a/source/core/input/input.hpp +++ b/source/core/input/input.hpp @@ -6,7 +6,6 @@ #include // Para shared_ptr #include // Para string, basic_string #include // Para unordered_map -#include // Para pair #include // Para vector #include "core/input/gamepad_config_manager.hpp" // for GamepadConfig (ptr only), GamepadConfigs @@ -109,8 +108,8 @@ class Input { // Evita nombres como "Retroid Controller (vendor: 1001) ..." en las notificaciones. static auto trimName(const char* raw) -> std::string { std::string s(raw != nullptr ? raw : ""); - const auto pos = s.find_first_of("(["); - if (pos != std::string::npos) { s.erase(pos); } + const auto POS = s.find_first_of("(["); + if (POS != std::string::npos) { s.erase(POS); } while (!s.empty() && s.back() == ' ') { s.pop_back(); } return s; } diff --git a/source/core/input/input_types.hpp b/source/core/input/input_types.hpp index c46ced7..acfa601 100644 --- a/source/core/input/input_types.hpp +++ b/source/core/input/input_types.hpp @@ -2,11 +2,12 @@ #include +#include #include #include // --- Enums --- -enum class InputAction : int { // Acciones de entrada posibles en el juego +enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el juego // Inputs de movimiento UP, DOWN, diff --git a/source/core/locale/lang.cpp b/source/core/locale/lang.cpp index 97eaa53..199b150 100644 --- a/source/core/locale/lang.cpp +++ b/source/core/locale/lang.cpp @@ -14,7 +14,7 @@ #include "game/gameplay/difficulty.hpp" // Para Difficulty #include "game/options.hpp" // Para SettingsOpt... -using json = nlohmann::json; +using Json = nlohmann::json; namespace Lang { std::unordered_map texts; @@ -33,12 +33,12 @@ namespace Lang { auto resource_data = ResourceHelper::loadFile(file_path); try { - json j; + Json j; if (!resource_data.empty()) { // Cargar desde datos del pack std::string content(resource_data.begin(), resource_data.end()); - j = json::parse(content); + j = Json::parse(content); } else { // Fallback a filesystem directo std::ifstream rfile(file_path); @@ -81,23 +81,23 @@ namespace Lang { // Obtiene un idioma del vector de idiomas a partir de un código auto getLanguage(Code code) -> Language { - const auto it = std::ranges::find_if(languages, + const auto IT = std::ranges::find_if(languages, [code](const auto& lang) { return lang.code == code; }); - return it != languages.end() ? *it : languages[0]; + return IT != languages.end() ? *IT : languages[0]; } // Devuelve el código de un idioma a partir de un nombre auto getCodeFromName(const std::string& name) -> Code { - const auto it = std::ranges::find_if(languages, + const auto IT = std::ranges::find_if(languages, [&name](const auto& lang) { return lang.name == name; }); - return it != languages.end() ? it->code : languages[0].code; + return IT != languages.end() ? IT->code : languages[0].code; } // Devuelve el nombre de un idioma a partir de un código auto getNameFromCode(Code code) -> std::string { - const auto it = std::ranges::find_if(languages, + const auto IT = std::ranges::find_if(languages, [code](const auto& lang) { return lang.code == code; }); - return it != languages.end() ? it->name : languages[0].name; + return IT != languages.end() ? IT->name : languages[0].name; } // Actualiza los nombres de los idiomas @@ -144,9 +144,9 @@ namespace Lang { // Obtiene una fichero a partir de un lang::Code auto getLanguageFileName(Lang::Code code) -> std::string { - const auto it = std::ranges::find_if(languages, + const auto IT = std::ranges::find_if(languages, [code](const auto& lang) { return lang.code == code; }); - const auto& file = (it != languages.end()) ? it->file_name : languages[0].file_name; + const auto& file = (IT != languages.end()) ? IT->file_name : languages[0].file_name; return Asset::get()->getPath(file); } diff --git a/source/core/locale/lang.hpp b/source/core/locale/lang.hpp index 35e6033..3afaf83 100644 --- a/source/core/locale/lang.hpp +++ b/source/core/locale/lang.hpp @@ -1,12 +1,13 @@ #pragma once +#include // Para std::uint8_t #include // Para string, basic_string #include // Para move // --- Namespace Lang: gestión de idiomas y textos --- namespace Lang { // --- Enums --- - enum class Code : int { + enum class Code : std::uint8_t { SPANISH = 0, // Español VALENCIAN = 1, // Valenciano ENGLISH = 2 // Inglés diff --git a/source/core/rendering/background.cpp b/source/core/rendering/background.cpp index 86cebcb..61c66bb 100644 --- a/source/core/rendering/background.cpp +++ b/source/core/rendering/background.cpp @@ -29,10 +29,10 @@ Background::Background(float total_progress_to_complete) moon_texture_(Resource::get()->getTexture("game_moon.png")), grass_sprite_(std::make_unique(Resource::get()->getTexture("game_grass.png"), Resource::get()->getAnimation("game_grass.ani"))), - total_progress_to_complete_(total_progress_to_complete), - progress_per_stage_(total_progress_to_complete_ / STAGES), - sun_completion_progress_(total_progress_to_complete_ * SUN_COMPLETION_FACTOR), - minimum_completed_progress_(total_progress_to_complete_ * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE), + TOTAL_PROGRESS_TO_COMPLETE(total_progress_to_complete), + PROGRESS_PER_STAGE(TOTAL_PROGRESS_TO_COMPLETE / STAGES), + SUM_COMPLETION_PROGRESS(TOTAL_PROGRESS_TO_COMPLETE * SUN_COMPLETION_FACTOR), + MINIMUM_COMPLETED_PROGRESS(TOTAL_PROGRESS_TO_COMPLETE * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE), rect_(SDL_FRect{.x = 0, .y = 0, .w = static_cast(gradients_texture_->getWidth() / 2), .h = static_cast(gradients_texture_->getHeight() / 2)}), src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}), @@ -167,7 +167,7 @@ void Background::incrementProgress(float amount) { if (state_ == State::NORMAL) { float old_progress = progress_; progress_ += amount; - progress_ = std::min(progress_, total_progress_to_complete_); + progress_ = std::min(progress_, TOTAL_PROGRESS_TO_COMPLETE); // Notifica el cambio si hay callback y el progreso cambió if (progress_callback_ && progress_ != old_progress) { @@ -179,7 +179,7 @@ void Background::incrementProgress(float amount) { // Establece la progresión absoluta void Background::setProgress(float absolute_progress) { float old_progress = progress_; - progress_ = std::clamp(absolute_progress, 0.0F, total_progress_to_complete_); + progress_ = std::clamp(absolute_progress, 0.0F, TOTAL_PROGRESS_TO_COMPLETE); // Notifica el cambio si hay callback y el progreso cambió if (progress_callback_ && progress_ != old_progress) { @@ -282,27 +282,27 @@ void Background::updateProgression(float delta_time) { float eased_t = easeOutCubic(static_cast(t)); // Interpolación desde progreso inicial hasta mínimo - float progress_range = completion_initial_progress_ - minimum_completed_progress_; + float progress_range = completion_initial_progress_ - MINIMUM_COMPLETED_PROGRESS; progress_ = completion_initial_progress_ - (progress_range * eased_t); } else { // Transición completada, fijar al valor mínimo - progress_ = minimum_completed_progress_; + progress_ = MINIMUM_COMPLETED_PROGRESS; } } // Calcula la transición de los diferentes fondos - const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / progress_per_stage_, 3.0F); + const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / PROGRESS_PER_STAGE, 3.0F); const float PERCENT = GRADIENT_NUMBER_FLOAT - static_cast(GRADIENT_NUMBER_FLOAT); gradient_number_ = static_cast(GRADIENT_NUMBER_FLOAT); transition_ = PERCENT; // Calcula la posición del sol - const float SUN_PROGRESSION = std::min(progress_ / sun_completion_progress_, 1.0F); + const float SUN_PROGRESSION = std::min(progress_ / SUM_COMPLETION_PROGRESS, 1.0F); sun_index_ = static_cast(SUN_PROGRESSION * (sun_path_.size() - 1)); // Calcula la posición de la luna - const float MOON_PROGRESSION = std::min(progress_ / total_progress_to_complete_, 1.0F); + const float MOON_PROGRESSION = std::min(progress_ / TOTAL_PROGRESS_TO_COMPLETE, 1.0F); moon_index_ = static_cast(MOON_PROGRESSION * (moon_path_.size() - 1)); // Actualiza la velocidad de las nubes @@ -318,12 +318,12 @@ void Background::updateCloudsSpeed() { // Velocidad base según progreso (de -3.0 a -120.0 píxeles/segundo, igual que la versión original) float base_clouds_speed = (-CLOUDS_INITIAL_SPEED_PX_PER_S) + - (-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / total_progress_to_complete_)); + (-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / TOTAL_PROGRESS_TO_COMPLETE)); // En estado completado, las nubes se ralentizan gradualmente if (state_ == State::COMPLETED) { - float completion_factor = (progress_ - minimum_completed_progress_) / - (total_progress_to_complete_ - minimum_completed_progress_); + float completion_factor = (progress_ - MINIMUM_COMPLETED_PROGRESS) / + (TOTAL_PROGRESS_TO_COMPLETE - MINIMUM_COMPLETED_PROGRESS); completion_factor = std::max(0.1F, completion_factor); base_clouds_speed *= completion_factor; } diff --git a/source/core/rendering/background.hpp b/source/core/rendering/background.hpp index bd5b05c..678d5d1 100644 --- a/source/core/rendering/background.hpp +++ b/source/core/rendering/background.hpp @@ -4,6 +4,7 @@ #include // Para array #include // Para size_t +#include // Para std::uint8_t #include // Para function #include // Para unique_ptr, shared_ptr #include // Para vector @@ -19,7 +20,7 @@ class AnimatedSprite; class Background { public: // --- Enums --- - enum class State { + enum class State : std::uint8_t { NORMAL, // Progresión normal del día COMPLETED // Reducción gradual de la actividad }; @@ -87,10 +88,10 @@ class Background { std::unique_ptr grass_sprite_; // Sprite con la hierba // --- Variables de configuración --- - const float total_progress_to_complete_; // Progreso total para completar - const float progress_per_stage_; // Progreso por etapa - const float sun_completion_progress_; // Progreso de completado del sol - const float minimum_completed_progress_; // Progreso mínimo calculado dinámicamente + const float TOTAL_PROGRESS_TO_COMPLETE; // Progreso total para completar + const float PROGRESS_PER_STAGE; // Progreso por etapa + const float SUM_COMPLETION_PROGRESS; // Progreso de completado del sol + const float MINIMUM_COMPLETED_PROGRESS; // Progreso mínimo calculado dinámicamente ProgressCallback progress_callback_; // Callback para notificar cambios de progreso // --- Variables de estado --- diff --git a/source/core/rendering/fade.cpp b/source/core/rendering/fade.cpp index bff32f1..6d2faa8 100644 --- a/source/core/rendering/fade.cpp +++ b/source/core/rendering/fade.cpp @@ -481,7 +481,7 @@ void Fade::activate() { case Type::DIAGONAL: { rect1_ = {.x = 0, .y = 0, .w = static_cast(param.game.width / num_squares_width_), .h = static_cast(param.game.height / num_squares_height_)}; square_.clear(); - square_age_.assign(num_squares_width_ * num_squares_height_, -1); + square_age_.assign(static_cast(num_squares_width_) * num_squares_height_, -1); for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) { rect1_.x = (i % num_squares_width_) * rect1_.w; rect1_.y = (i / num_squares_width_) * rect1_.h; diff --git a/source/core/rendering/gif.cpp b/source/core/rendering/gif.cpp index 5a9a37c..c15872d 100644 --- a/source/core/rendering/gif.cpp +++ b/source/core/rendering/gif.cpp @@ -8,145 +8,233 @@ #include // Para char_traits, operator==, basic_string, string namespace GIF { - inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) { - std::memcpy(dst, buffer, size); - buffer += size; - } - - void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) { - if (code_length < 2 || code_length > 12) { - std::cout << "Invalid LZW code length: " << code_length << '\n'; - throw std::runtime_error("Invalid LZW code length"); + namespace { + inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) { + std::memcpy(dst, buffer, size); + buffer += size; } - int i, bit; - int prev = -1; - std::vector dictionary; - int dictionary_ind; - unsigned int mask = 0x01; - int reset_code_length = code_length; - int clear_code = 1 << code_length; - int stop_code = clear_code + 1; - int match_len = 0; - - dictionary.resize(1 << (code_length + 1)); - for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) { - dictionary[dictionary_ind].byte = static_cast(dictionary_ind); - dictionary[dictionary_ind].prev = -1; - dictionary[dictionary_ind].len = 1; + // Llavor del diccionari LZW: 0..N-1 com a entrades base, i salta 2 (clear_code + stop_code). + void resetDictionary(std::vector &dict, int code_length, int &dictionary_ind) { + dict.resize(1 << (code_length + 1)); + for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) { + dict[dictionary_ind].byte = static_cast(dictionary_ind); + dict[dictionary_ind].prev = -1; + dict[dictionary_ind].len = 1; + } + dictionary_ind += 2; } - dictionary_ind += 2; - while (input_length > 0) { + // Llig `code_length + 1` bits LSB-first del flux d'entrada. Llança si s'acaba el buffer. + auto readNextCode(const uint8_t *&input, int &input_length, int code_length, unsigned int &mask) -> int { int code = 0; - for (i = 0; i < (code_length + 1); i++) { + for (int i = 0; i < code_length + 1; i++) { if (input_length <= 0) { std::cout << "Unexpected end of input in decompress" << '\n'; throw std::runtime_error("Unexpected end of input in decompress"); } - bit = ((*input & mask) != 0) ? 1 : 0; + const int BIT = ((*input & mask) != 0) ? 1 : 0; mask <<= 1; if (mask == 0x100) { mask = 0x01; input++; input_length--; } - code |= (bit << i); + code |= (BIT << i); } + return code; + } - if (code == clear_code) { - code_length = reset_code_length; - dictionary.resize(1 << (code_length + 1)); - for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) { - dictionary[dictionary_ind].byte = static_cast(dictionary_ind); - dictionary[dictionary_ind].prev = -1; - dictionary[dictionary_ind].len = 1; - } - dictionary_ind += 2; - prev = -1; - continue; - } else if (code == stop_code) { - break; + // Afig una nova entrada al diccionari. Resol el cas especial KwKwK (code == dictionary_ind) + // començant la cadena des de `prev` en lloc de des de `code`. + void addDictionaryEntry(std::vector &dict, int dictionary_ind, int code, int prev) { + int ptr = (code == dictionary_ind) ? prev : code; + while (dict[ptr].prev != -1) { + ptr = dict[ptr].prev; } + dict[dictionary_ind].byte = dict[ptr].byte; + dict[dictionary_ind].prev = prev; + dict[dictionary_ind].len = dict[prev].len + 1; + } - if (prev > -1 && code_length < 12) { - if (code > dictionary_ind) { - std::cout << "LZW error: code (" << code << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n'; - throw std::runtime_error("LZW error: code exceeds dictionary_ind."); - } - - int ptr; - if (code == dictionary_ind) { - ptr = prev; - while (dictionary[ptr].prev != -1) - ptr = dictionary[ptr].prev; - dictionary[dictionary_ind].byte = dictionary[ptr].byte; - } else { - ptr = code; - while (dictionary[ptr].prev != -1) - ptr = dictionary[ptr].prev; - dictionary[dictionary_ind].byte = dictionary[ptr].byte; - } - dictionary[dictionary_ind].prev = prev; - dictionary[dictionary_ind].len = dictionary[prev].len + 1; - dictionary_ind++; - - if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) { - code_length++; - dictionary.resize(1 << (code_length + 1)); - } - } - - prev = code; - - if (code < 0 || static_cast(code) >= dictionary.size()) { - std::cout << "Invalid LZW code " << code << ", dictionary size " << static_cast(dictionary.size()) << '\n'; - throw std::runtime_error("LZW error: invalid code encountered"); - } - - int curCode = code; - match_len = dictionary[curCode].len; - while (curCode != -1) { - out[dictionary[curCode].len - 1] = dictionary[curCode].byte; - if (dictionary[curCode].prev == curCode) { + // Escriu la cadena de bytes associada a `code` en `out` (en ordre invers seguint .prev). + // Retorna la longitud del match per avançar el cursor de l'eixida. + auto emitMatch(const std::vector &dict, int code, uint8_t *out) -> int { + const int MATCH_LEN = dict[code].len; + int cur_code = code; + while (cur_code != -1) { + out[dict[cur_code].len - 1] = dict[cur_code].byte; + if (dict[cur_code].prev == cur_code) { std::cout << "Internal error; self-reference detected." << '\n'; throw std::runtime_error("Internal error in decompress: self-reference"); } - curCode = dictionary[curCode].prev; + cur_code = dict[cur_code].prev; } - out += match_len; + return MATCH_LEN; } - } - std::vector Gif::readSubBlocks(const uint8_t *&buffer) { - std::vector data; - uint8_t block_size = *buffer; - buffer++; - while (block_size != 0) { - data.insert(data.end(), buffer, buffer + block_size); - buffer += block_size; - block_size = *buffer; + // Descompone (uncompress) el bloque comprimido usando LZW. + void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) { + if (code_length < 2 || code_length > 12) { + std::cout << "Invalid LZW code length: " << code_length << '\n'; + throw std::runtime_error("Invalid LZW code length"); + } + + int prev = -1; + std::vector dictionary; + int dictionary_ind = 0; + unsigned int mask = 0x01; + const int RESET_CODE_LENGTH = code_length; + const int CLEAR_CODE = 1 << code_length; + const int STOP_CODE = CLEAR_CODE + 1; + + resetDictionary(dictionary, code_length, dictionary_ind); + + while (input_length > 0) { + const int CODE = readNextCode(input, input_length, code_length, mask); + + if (CODE == CLEAR_CODE) { + code_length = RESET_CODE_LENGTH; + resetDictionary(dictionary, code_length, dictionary_ind); + prev = -1; + continue; + } + if (CODE == STOP_CODE) { break; } + + if (prev > -1 && code_length < 12) { + if (CODE > dictionary_ind) { + std::cout << "LZW error: code (" << CODE << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n'; + throw std::runtime_error("LZW error: code exceeds dictionary_ind."); + } + addDictionaryEntry(dictionary, dictionary_ind, CODE, prev); + dictionary_ind++; + + if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) { + code_length++; + dictionary.resize(1 << (code_length + 1)); + } + } + + prev = CODE; + + if (CODE < 0 || static_cast(CODE) >= dictionary.size()) { + std::cout << "Invalid LZW code " << CODE << ", dictionary size " << static_cast(dictionary.size()) << '\n'; + throw std::runtime_error("LZW error: invalid code encountered"); + } + + out += emitMatch(dictionary, CODE, out); + } + } + + // Lee los sub-bloques de datos y los acumula en un std::vector. + auto readSubBlocks(const uint8_t *&buffer) -> std::vector { + std::vector data; + uint8_t block_size = *buffer; buffer++; + while (block_size != 0) { + data.insert(data.end(), buffer, buffer + block_size); + buffer += block_size; + block_size = *buffer; + buffer++; + } + return data; } - return data; - } - std::vector Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector &gct, int resolution_bits) { - ImageDescriptor image_descriptor; - readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor)); + // Procesa el Image Descriptor y retorna el vector de datos sin comprimir. + auto processImageDescriptor(const uint8_t *&buffer, const std::vector &gct, int resolution_bits) -> std::vector { + ImageDescriptor image_descriptor; + readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor)); - uint8_t lzw_code_size; - readBytes(buffer, &lzw_code_size, sizeof(uint8_t)); + uint8_t lzw_code_size; + readBytes(buffer, &lzw_code_size, sizeof(uint8_t)); - std::vector compressed_data = readSubBlocks(buffer); - int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height; - std::vector uncompressed_data(uncompressed_data_length); + std::vector compressed_data = readSubBlocks(buffer); + int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height; + std::vector uncompressed_data(uncompressed_data_length); - decompress(lzw_code_size, compressed_data.data(), static_cast(compressed_data.size()), uncompressed_data.data()); - return uncompressed_data; - } + decompress(lzw_code_size, compressed_data.data(), static_cast(compressed_data.size()), uncompressed_data.data()); + return uncompressed_data; + } - std::vector Gif::loadPalette(const uint8_t *buffer) { + // Procesa el stream completo del GIF y devuelve los datos sin comprimir. + auto processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector { + uint8_t header[6]; + std::memcpy(header, buffer, 6); + buffer += 6; + + std::string header_str(reinterpret_cast(header), 6); + if (header_str != "GIF87a" && header_str != "GIF89a") { + std::cout << "Formato de archivo GIF inválido: " << header_str << '\n'; + throw std::runtime_error("Formato de archivo GIF inválido."); + } + + ScreenDescriptor screen_descriptor; + readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor)); + + w = screen_descriptor.width; + h = screen_descriptor.height; + + int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1; + std::vector global_color_table; + if ((screen_descriptor.fields & 0x80) != 0) { + const size_t GLOBAL_COLOR_TABLE_SIZE = 1U << (((screen_descriptor.fields & 0x07) + 1)); + global_color_table.resize(GLOBAL_COLOR_TABLE_SIZE); + std::memcpy(global_color_table.data(), buffer, 3 * GLOBAL_COLOR_TABLE_SIZE); + buffer += 3 * GLOBAL_COLOR_TABLE_SIZE; + } + + uint8_t block_type = *buffer++; + while (block_type != TRAILER) { + if (block_type == EXTENSION_INTRODUCER) { + uint8_t extension_label = *buffer++; + switch (extension_label) { + case GRAPHIC_CONTROL: { + uint8_t block_size = *buffer++; + buffer += block_size; + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + case APPLICATION_EXTENSION: + case COMMENT_EXTENSION: + case PLAINTEXT_EXTENSION: { + uint8_t block_size = *buffer++; + buffer += block_size; + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + default: { + uint8_t block_size = *buffer++; + buffer += block_size; + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + } + } else if (block_type == IMAGE_DESCRIPTOR) { + return processImageDescriptor(buffer, global_color_table, color_resolution_bits); + } else { + std::cout << "Unrecognized block type: 0x" << std::hex << static_cast(block_type) << std::dec << '\n'; + return std::vector{}; + } + block_type = *buffer++; + } + + return std::vector{}; + } + } // namespace + + auto loadPalette(const uint8_t *buffer) -> std::vector { uint8_t header[6]; std::memcpy(header, buffer, 6); buffer += 6; @@ -156,7 +244,7 @@ namespace GIF { buffer += sizeof(ScreenDescriptor); std::vector global_color_table; - if (screen_descriptor.fields & 0x80) { + if ((screen_descriptor.fields & 0x80) != 0) { int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1)); global_color_table.resize(global_color_table_size); for (int i = 0; i < global_color_table_size; ++i) { @@ -170,83 +258,7 @@ namespace GIF { return global_color_table; } - std::vector Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) { - uint8_t header[6]; - std::memcpy(header, buffer, 6); - buffer += 6; - - std::string headerStr(reinterpret_cast(header), 6); - if (headerStr != "GIF87a" && headerStr != "GIF89a") { - std::cout << "Formato de archivo GIF inválido: " << headerStr << '\n'; - throw std::runtime_error("Formato de archivo GIF inválido."); - } - - ScreenDescriptor screen_descriptor; - readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor)); - - w = screen_descriptor.width; - h = screen_descriptor.height; - - int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1; - std::vector global_color_table; - if (screen_descriptor.fields & 0x80) { - int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1)); - global_color_table.resize(global_color_table_size); - std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size); - buffer += 3 * global_color_table_size; - } - - uint8_t block_type = *buffer++; - while (block_type != TRAILER) { - if (block_type == EXTENSION_INTRODUCER) { - uint8_t extension_label = *buffer++; - switch (extension_label) { - case GRAPHIC_CONTROL: { - uint8_t blockSize = *buffer++; - buffer += blockSize; - uint8_t subBlockSize = *buffer++; - while (subBlockSize != 0) { - buffer += subBlockSize; - subBlockSize = *buffer++; - } - break; - } - case APPLICATION_EXTENSION: - case COMMENT_EXTENSION: - case PLAINTEXT_EXTENSION: { - uint8_t blockSize = *buffer++; - buffer += blockSize; - uint8_t subBlockSize = *buffer++; - while (subBlockSize != 0) { - buffer += subBlockSize; - subBlockSize = *buffer++; - } - break; - } - default: { - uint8_t blockSize = *buffer++; - buffer += blockSize; - uint8_t subBlockSize = *buffer++; - while (subBlockSize != 0) { - buffer += subBlockSize; - subBlockSize = *buffer++; - } - break; - } - } - } else if (block_type == IMAGE_DESCRIPTOR) { - return processImageDescriptor(buffer, global_color_table, color_resolution_bits); - } else { - std::cout << "Unrecognized block type: 0x" << std::hex << static_cast(block_type) << std::dec << '\n'; - return std::vector{}; - } - block_type = *buffer++; - } - - return std::vector{}; - } - - std::vector Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) { + auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector { return processGifStream(buffer, w, h); } diff --git a/source/core/rendering/gif.hpp b/source/core/rendering/gif.hpp index 8bb26c1..da618d8 100644 --- a/source/core/rendering/gif.hpp +++ b/source/core/rendering/gif.hpp @@ -64,29 +64,12 @@ namespace GIF { uint8_t foreground_color, background_color; }; - class Gif { - public: - // Descompone (uncompress) el bloque comprimido usando LZW. - // Este método puede lanzar std::runtime_error en caso de error. - void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out); + // Carga la paleta (global color table) a partir de un buffer, + // retornándola en un vector de uint32_t (cada color se compone de R, G, B). + auto loadPalette(const uint8_t *buffer) -> std::vector; - // Carga la paleta (global color table) a partir de un buffer, - // retornándola en un vector de uint32_t (cada color se compone de R, G, B). - std::vector loadPalette(const uint8_t *buffer); - - // Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y - // asigna el ancho y alto mediante referencias. - std::vector loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h); - - private: - // Lee los sub-bloques de datos y los acumula en un std::vector. - std::vector readSubBlocks(const uint8_t *&buffer); - - // Procesa el Image Descriptor y retorna el vector de datos sin comprimir. - std::vector processImageDescriptor(const uint8_t *&buffer, const std::vector &gct, int resolution_bits); - - // Procesa el stream completo del GIF y devuelve los datos sin comprimir. - std::vector processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h); - }; + // Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y + // asigna el ancho y alto mediante referencias. + auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector; } // namespace GIF diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 0120b38..9770478 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -320,49 +320,50 @@ void Screen::renderShake() { } } #ifdef _DEBUG +// Compone la línia d'informació de debug: "fps - driver - shader preset" +auto Screen::buildDebugInfoText() const -> std::string { + std::string info_text = std::to_string(fps_.last_value) + " fps"; + + // Driver GPU + if (shader_backend_ && shader_backend_->isHardwareAccelerated()) { + const std::string DRIVER = shader_backend_->getDriverName(); + info_text += DRIVER.empty() ? "" : " - " + toLower(DRIVER); + } else { + info_text += " - sdl"; + } + + // Shader + preset (només si està activat) + if (!Options::video.shader.enabled) { return info_text; } + + if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) { + const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast(Options::video.shader.current_crtpi_preset)).name; + info_text += " - crtpi " + toLower(PRESET_NAME); + } else { + const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast(Options::video.shader.current_postfx_preset)).name; + info_text += " - postfx " + toLower(PRESET_NAME); + if (Options::video.supersampling.enabled) { info_text += " (ss)"; } + } + return info_text; +} + // Muestra información por pantalla void Screen::renderInfo() const { - if (debug_info_.show) { - const Color GOLD(0xFF, 0xD7, 0x00); - const Color GOLD_SHADOW = GOLD.DARKEN(150); + if (!debug_info_.show) { return; } - // Construir texto: fps - driver - preset - std::string info_text = std::to_string(fps_.last_value) + " fps"; + const Color GOLD(0xFF, 0xD7, 0x00); + const Color GOLD_SHADOW = GOLD.darken(150); - // Driver GPU - if (shader_backend_ && shader_backend_->isHardwareAccelerated()) { - const std::string DRIVER = shader_backend_->getDriverName(); - if (!DRIVER.empty()) { - info_text += " - " + toLower(DRIVER); - } - } else { - info_text += " - sdl"; - } - - // Shader + preset - if (Options::video.shader.enabled) { - if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) { - const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast(Options::video.shader.current_crtpi_preset)).name; - info_text += " - crtpi " + toLower(PRESET_NAME); - } else { - const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast(Options::video.shader.current_postfx_preset)).name; - info_text += " - postfx " + toLower(PRESET_NAME); - if (Options::video.supersampling.enabled) { info_text += " (ss)"; } - } - } - - // Centrado arriba - const int TEXT_WIDTH = debug_info_.text->length(info_text); - const int X_POS = (static_cast(param.game.width) - TEXT_WIDTH) / 2; - debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, info_text, 1, GOLD, 1, GOLD_SHADOW); + const std::string INFO_TEXT = buildDebugInfoText(); + const int TEXT_WIDTH = debug_info_.text->length(INFO_TEXT); + const int X_POS = (static_cast(param.game.width) - TEXT_WIDTH) / 2; + debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, INFO_TEXT, 1, GOLD, 1, GOLD_SHADOW); #ifdef RECORDING - const std::string REC_TEXT = "recording"; - const int REC_WIDTH = debug_info_.text->length(REC_TEXT); - const int REC_X = (static_cast(param.game.width) - REC_WIDTH) / 2; - debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW); + const std::string REC_TEXT = "recording"; + const int REC_WIDTH = debug_info_.text->length(REC_TEXT); + const int REC_X = (static_cast(param.game.width) - REC_WIDTH) / 2; + debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW); #endif - } } #endif // Inicializa shaders (SDL3GPU) @@ -380,8 +381,8 @@ void Screen::initShaders() { Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER); } if (!self->shader_backend_->isHardwareAccelerated()) { - const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", ""); - std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n'; + const bool OK = self->shader_backend_->init(self->window_, self->game_canvas_, "", ""); + std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (OK ? "OK" : "FAILED") << '\n'; } if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) { self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale); diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index c34ee7f..c3ea96a 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -241,6 +241,7 @@ class Screen { void renderFlash(); // Dibuja el efecto de flash en la pantalla void renderShake(); // Aplica el efecto de agitar la pantalla void renderInfo() const; // Muestra información por pantalla + [[nodiscard]] auto buildDebugInfoText() const -> std::string; // Compone fps + driver + shader/preset para renderInfo void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp index e8a734f..b1df6a4 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp @@ -702,7 +702,7 @@ namespace Rendering { return; } - std::memcpy(mapped, pixels, static_cast(width * height * 4)); + std::memcpy(mapped, pixels, static_cast(width) * height * 4); SDL_UnmapGPUTransferBuffer(device_, upload_buffer_); } diff --git a/source/core/rendering/shader_backend.hpp b/source/core/rendering/shader_backend.hpp index e3dcd97..ed44de8 100644 --- a/source/core/rendering/shader_backend.hpp +++ b/source/core/rendering/shader_backend.hpp @@ -2,13 +2,14 @@ #include +#include #include #include namespace Rendering { /** @brief Identificador del shader de post-procesado activo */ - enum class ShaderType { POSTFX, + enum class ShaderType : std::uint8_t { POSTFX, CRTPI }; /** diff --git a/source/core/rendering/sprite/card_sprite.cpp b/source/core/rendering/sprite/card_sprite.cpp index 41d8e99..b68042b 100644 --- a/source/core/rendering/sprite/card_sprite.cpp +++ b/source/core/rendering/sprite/card_sprite.cpp @@ -31,7 +31,7 @@ auto CardSprite::enable() -> bool { // Ángulo inicial rotate_.angle = start_angle_; - rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F}; + rotate_.center = {.x = pos_.w / 2.0F, .y = pos_.h / 2.0F}; shadow_visible_ = true; return true; @@ -55,7 +55,7 @@ void CardSprite::startExit() { // Rotación continua rotate_.enabled = true; rotate_.amount = exit_rotate_amount_; - rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F}; + rotate_.center = {.x = pos_.w / 2.0F, .y = pos_.h / 2.0F}; } // Actualiza según el estado @@ -80,7 +80,7 @@ void CardSprite::updateEntering(float delta_time) { double eased = entry_easing_(static_cast(progress)); // Zoom: de start_zoom_ a 1.0 con rebote - auto current_zoom = static_cast(start_zoom_ + (1.0 - start_zoom_) * eased); + auto current_zoom = static_cast(start_zoom_ + ((1.0 - start_zoom_) * eased)); horizontal_zoom_ = current_zoom; vertical_zoom_ = current_zoom; @@ -90,8 +90,8 @@ void CardSprite::updateEntering(float delta_time) { // Posición: de entry_start a landing con easing suave (sin rebote) // Usamos easeOutCubic para que el desplazamiento sea fluido double pos_eased = easeOutCubic(static_cast(progress)); - auto current_x = static_cast(entry_start_x_ + (landing_x_ - entry_start_x_) * pos_eased); - auto current_y = static_cast(entry_start_y_ + (landing_y_ - entry_start_y_) * pos_eased); + auto current_x = static_cast(entry_start_x_ + ((landing_x_ - entry_start_x_) * pos_eased)); + auto current_y = static_cast(entry_start_y_ + ((landing_y_ - entry_start_y_) * pos_eased)); setPos(current_x, current_y); // Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez) @@ -117,12 +117,9 @@ void CardSprite::updateExiting(float delta_time) { // Ganar altura gradualmente (zoom hacia el objetivo) if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) { - float new_zoom = horizontal_zoom_ + exit_zoom_speed_ * delta_time; - if (new_zoom > exit_target_zoom_) { - new_zoom = exit_target_zoom_; - } - horizontal_zoom_ = new_zoom; - vertical_zoom_ = new_zoom; + const float NEW_ZOOM = std::min(horizontal_zoom_ + (exit_zoom_speed_ * delta_time), exit_target_zoom_); + horizontal_zoom_ = NEW_ZOOM; + vertical_zoom_ = NEW_ZOOM; } if (isOffScreen()) { @@ -164,8 +161,8 @@ void CardSprite::renderShadow() { // Offset respecto a la tarjeta: base + extra proporcional a la altura // La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0) - float offset_x = shadow_offset_x_ + height * SHADOW_HEIGHT_MULTIPLIER; - float offset_y = shadow_offset_y_ + height * SHADOW_HEIGHT_MULTIPLIER; + float offset_x = shadow_offset_x_ + (height * SHADOW_HEIGHT_MULTIPLIER); + float offset_y = shadow_offset_y_ + (height * SHADOW_HEIGHT_MULTIPLIER); shadow_texture_->render( pos_.x + offset_x, diff --git a/source/core/rendering/sprite/card_sprite.hpp b/source/core/rendering/sprite/card_sprite.hpp index 5c74eb6..6aa38b9 100644 --- a/source/core/rendering/sprite/card_sprite.hpp +++ b/source/core/rendering/sprite/card_sprite.hpp @@ -2,6 +2,7 @@ #include // Para SDL_FPoint +#include // Para std::uint8_t #include // Para function #include // Para shared_ptr @@ -10,7 +11,7 @@ class Texture; // --- Estados de la tarjeta --- -enum class CardState { +enum class CardState : std::uint8_t { IDLE, // No activada todavía ENTERING, // Animación de entrada (zoom + rotación + desplazamiento con rebote) LANDED, // En reposo sobre la mesa diff --git a/source/core/rendering/sprite/path_sprite.hpp b/source/core/rendering/sprite/path_sprite.hpp index a8689db..07457bd 100644 --- a/source/core/rendering/sprite/path_sprite.hpp +++ b/source/core/rendering/sprite/path_sprite.hpp @@ -2,6 +2,7 @@ #include // Para SDL_FPoint +#include // Para std::uint8_t #include // Para std::function #include // Para shared_ptr #include @@ -12,12 +13,12 @@ class Texture; // --- Enums --- -enum class PathType { // Tipos de recorrido +enum class PathType : std::uint8_t { // Tipos de recorrido VERTICAL, HORIZONTAL, }; -enum class PathCentered { // Centrado del recorrido +enum class PathCentered : std::uint8_t { // Centrado del recorrido ON_X, ON_Y, NONE, diff --git a/source/core/rendering/sprite/smart_sprite.cpp b/source/core/rendering/sprite/smart_sprite.cpp index 0e1dd84..a9bfdf6 100644 --- a/source/core/rendering/sprite/smart_sprite.cpp +++ b/source/core/rendering/sprite/smart_sprite.cpp @@ -1,7 +1,5 @@ #include "core/rendering/sprite/smart_sprite.hpp" -#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite - // Actualiza la posición y comprueba si ha llegado a su destino (time-based) void SmartSprite::update(float delta_time) { if (enabled_) { diff --git a/source/core/rendering/sprite/smart_sprite.hpp b/source/core/rendering/sprite/smart_sprite.hpp index 3b60e4b..2c2c943 100644 --- a/source/core/rendering/sprite/smart_sprite.hpp +++ b/source/core/rendering/sprite/smart_sprite.hpp @@ -3,16 +3,16 @@ #include // Para shared_ptr #include -#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite +#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite class Texture; -// --- Clase SmartSprite: sprite animado que se mueve hacia un destino y puede deshabilitarse automáticamente --- -class SmartSprite : public AnimatedSprite { +// --- Clase SmartSprite: sprite que se mueve hacia un destino y puede deshabilitarse automáticamente --- +class SmartSprite : public MovingSprite { public: // --- Constructor y destructor --- explicit SmartSprite(std::shared_ptr texture) - : AnimatedSprite(std::move(texture)) {} + : MovingSprite(std::move(texture)) {} ~SmartSprite() override = default; // --- Métodos principales --- diff --git a/source/core/rendering/texture.cpp b/source/core/rendering/texture.cpp index 118a2c6..e084bbd 100644 --- a/source/core/rendering/texture.cpp +++ b/source/core/rendering/texture.cpp @@ -251,11 +251,9 @@ auto Texture::loadSurface(const std::string& file_path) -> std::shared_ptr raw_pixels = gif.loadGif(buffer.data(), w, h); + std::vector raw_pixels = GIF::loadGif(buffer.data(), w, h); if (raw_pixels.empty()) { std::cout << "Error: No se pudo cargar el GIF " << file_path << '\n'; return nullptr; @@ -329,8 +327,7 @@ auto Texture::loadPaletteFromFile(const std::string& file_path) -> Palette { } // Usar la nueva función loadPalette, que devuelve un vector - GIF::Gif gif; - std::vector pal = gif.loadPalette(buffer.data()); + std::vector pal = GIF::loadPalette(buffer.data()); if (pal.empty()) { std::cout << "Advertencia: No se encontró paleta en el archivo " << file_path << '\n'; return palette; // Devuelve un vector vacío si no hay paleta diff --git a/source/core/rendering/tiled_bg.hpp b/source/core/rendering/tiled_bg.hpp index bf79bdb..f4c3c37 100644 --- a/source/core/rendering/tiled_bg.hpp +++ b/source/core/rendering/tiled_bg.hpp @@ -2,10 +2,12 @@ #include // Para SDL_FRect, SDL_SetTextureColorMod, SDL_Renderer, SDL_Texture +#include // Para std::uint8_t + #include "utils/color.hpp" // Para Color // --- Enums --- -enum class TiledBGMode : int { // Modos de funcionamiento para el tileado de fondo +enum class TiledBGMode : std::uint8_t { // Modos de funcionamiento para el tileado de fondo CIRCLE = 0, DIAGONAL = 1, RANDOM = 2, diff --git a/source/core/rendering/writer.cpp b/source/core/rendering/writer.cpp index 25eccfe..1bc824e 100644 --- a/source/core/rendering/writer.cpp +++ b/source/core/rendering/writer.cpp @@ -1,6 +1,7 @@ #include "core/rendering/writer.hpp" -#include "core/rendering/text.hpp" // Para Text +// Text es completat ací per `text_->write/length` via shared_ptr; include-cleaner no detecta l'ús indirecte. +#include "core/rendering/text.hpp" // IWYU pragma: keep // Actualiza el objeto (delta_time en ms) void Writer::update(float delta_time) { diff --git a/source/core/resources/asset.hpp b/source/core/resources/asset.hpp index 7b62633..31fbef1 100644 --- a/source/core/resources/asset.hpp +++ b/source/core/resources/asset.hpp @@ -10,7 +10,7 @@ class Asset { public: // --- Enums --- - enum class Type : int { + enum class Type : std::uint8_t { BITMAP, // Imágenes MUSIC, // Música SOUND, // Sonidos diff --git a/source/core/resources/asset_integrated.cpp b/source/core/resources/asset_integrated.cpp index 6c7cf88..36389ac 100644 --- a/source/core/resources/asset_integrated.cpp +++ b/source/core/resources/asset_integrated.cpp @@ -4,6 +4,8 @@ #include #include +#include "core/resources/resource_loader.hpp" + bool AssetIntegrated::resource_pack_enabled = false; void AssetIntegrated::initWithResourcePack(const std::string& executable_path, diff --git a/source/core/resources/asset_integrated.hpp b/source/core/resources/asset_integrated.hpp index 35da80c..b6ed522 100644 --- a/source/core/resources/asset_integrated.hpp +++ b/source/core/resources/asset_integrated.hpp @@ -1,9 +1,6 @@ #pragma once -#include - #include "core/resources/asset.hpp" -#include "core/resources/resource_loader.hpp" // Extensión de Asset que integra ResourceLoader class AssetIntegrated : public Asset { diff --git a/source/core/resources/resource.cpp b/source/core/resources/resource.cpp index 26b19e0..48dca82 100644 --- a/source/core/resources/resource.cpp +++ b/source/core/resources/resource.cpp @@ -5,16 +5,12 @@ #include // Para ranges::transform, ranges::find_if #include // Para array #include // Para exit -#include // Para exception -#include // Para exists, path, remove -#include // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream #include // Para std::cout #include // Para back_inserter -#include // Para __find_if_fn, find_if, __find_fn, find #include // Para runtime_error #include // Para move -#include "core/audio/jail_audio.hpp" // Para JA_LoadMusic, JA_LoadSound, JA_DeleteMusic, JA_DeleteSound +#include "core/audio/jail_audio.hpp" // Para Ja::loadMusic, Ja::loadSound, Ja::deleteMusic, Ja::deleteSound #include "core/locale/lang.hpp" // Para getText #include "core/rendering/screen.hpp" // Para Screen #include "core/rendering/text.hpp" // Para Text @@ -27,9 +23,6 @@ #include "utils/utils.hpp" // Para getFileName #include "version.h" // Para APP_NAME, GIT_HASH -struct JA_Music_t; // lines 11-11 -struct JA_Sound_t; // lines 12-12 - // Helper para cargar archivos de audio desde pack o filesystem en memoria namespace { struct AudioData { @@ -140,37 +133,37 @@ void Resource::loadEssentialTextures() { // Inicializa las listas de recursos sin cargar el contenido (modo lazy) void Resource::initResourceLists() { - const auto file_to_name = [](const auto& file) { return getFileName(file); }; + const auto FILE_TO_NAME = [](const auto& file) { return getFileName(file); }; // Inicializa lista de sonidos - const auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND); + const auto SOUND_LIST = Asset::get()->getListByType(Asset::Type::SOUND); sounds_.clear(); - sounds_.reserve(sound_list.size()); - std::ranges::transform(sound_list, std::back_inserter(sounds_), [&](const auto& file) { return ResourceSound(file_to_name(file)); }); + sounds_.reserve(SOUND_LIST.size()); + std::ranges::transform(SOUND_LIST, std::back_inserter(sounds_), [&](const auto& file) { return ResourceSound(FILE_TO_NAME(file)); }); // Inicializa lista de músicas - const auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC); + const auto MUSIC_LIST = Asset::get()->getListByType(Asset::Type::MUSIC); musics_.clear(); - musics_.reserve(music_list.size()); - std::ranges::transform(music_list, std::back_inserter(musics_), [&](const auto& file) { return ResourceMusic(file_to_name(file)); }); + musics_.reserve(MUSIC_LIST.size()); + std::ranges::transform(MUSIC_LIST, std::back_inserter(musics_), [&](const auto& file) { return ResourceMusic(FILE_TO_NAME(file)); }); // Inicializa lista de texturas - const auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP); + const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP); textures_.clear(); - textures_.reserve(texture_list.size()); - std::ranges::transform(texture_list, std::back_inserter(textures_), [&](const auto& file) { return ResourceTexture(file_to_name(file)); }); + textures_.reserve(TEXTURE_LIST.size()); + std::ranges::transform(TEXTURE_LIST, std::back_inserter(textures_), [&](const auto& file) { return ResourceTexture(FILE_TO_NAME(file)); }); // Inicializa lista de ficheros de texto - const auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT); + const auto TEXT_FILE_LIST = Asset::get()->getListByType(Asset::Type::FONT); text_files_.clear(); - text_files_.reserve(text_file_list.size()); - std::ranges::transform(text_file_list, std::back_inserter(text_files_), [&](const auto& file) { return ResourceTextFile(file_to_name(file)); }); + text_files_.reserve(TEXT_FILE_LIST.size()); + std::ranges::transform(TEXT_FILE_LIST, std::back_inserter(text_files_), [&](const auto& file) { return ResourceTextFile(FILE_TO_NAME(file)); }); // Inicializa lista de animaciones - const auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION); + const auto ANIMATION_LIST = Asset::get()->getListByType(Asset::Type::ANIMATION); animations_.clear(); - animations_.reserve(animation_list.size()); - std::ranges::transform(animation_list, std::back_inserter(animations_), [&](const auto& file) { return ResourceAnimation(file_to_name(file)); }); + animations_.reserve(ANIMATION_LIST.size()); + std::ranges::transform(ANIMATION_LIST, std::back_inserter(animations_), [&](const auto& file) { return ResourceAnimation(FILE_TO_NAME(file)); }); // Los demos se cargan directamente sin mostrar progreso (son pocos y pequeños) loadDemoDataQuiet(); @@ -196,7 +189,7 @@ void Resource::initResourceLists() { } // Obtiene el sonido a partir de un nombre (con carga perezosa) -auto Resource::getSound(const std::string& name) -> JA_Sound_t* { +auto Resource::getSound(const std::string& name) -> Ja::Sound* { auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> auto { return s.name == name; }); if (it != sounds_.end()) { @@ -212,7 +205,7 @@ auto Resource::getSound(const std::string& name) -> JA_Sound_t* { } // Obtiene la música a partir de un nombre (con carga perezosa) -auto Resource::getMusic(const std::string& name) -> JA_Music_t* { +auto Resource::getMusic(const std::string& name) -> Ja::Music* { auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> auto { return m.name == name; }); if (it != musics_.end()) { @@ -303,48 +296,48 @@ auto Resource::getDemoData(int index) -> DemoData& { // --- Métodos de carga perezosa --- -auto Resource::loadSoundLazy(const std::string& name) -> JA_Sound_t* { +auto Resource::loadSoundLazy(const std::string& name) -> Ja::Sound* { auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND); for (const auto& file : sound_list) { if (getFileName(file) == name) { auto audio_data = loadAudioData(file); if (!audio_data.data.empty()) { - return JA_LoadSound(audio_data.data.data(), audio_data.data.size()); + return Ja::loadSound(audio_data.data.data(), audio_data.data.size()); } // Fallback a cargar desde disco si no está en pack - return JA_LoadSound(file.c_str()); + return Ja::loadSound(file.c_str()); } } return nullptr; } -auto Resource::loadMusicLazy(const std::string& name) -> JA_Music_t* { +auto Resource::loadMusicLazy(const std::string& name) -> Ja::Music* { auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC); for (const auto& file : music_list) { if (getFileName(file) == name) { auto audio_data = loadAudioData(file); if (!audio_data.data.empty()) { - return JA_LoadMusic(audio_data.data.data(), audio_data.data.size()); + return Ja::loadMusic(audio_data.data.data(), audio_data.data.size()); } // Fallback a cargar desde disco si no está en pack - return JA_LoadMusic(file.c_str()); + return Ja::loadMusic(file.c_str()); } } return nullptr; } auto Resource::loadTextureLazy(const std::string& name) -> std::shared_ptr { - const auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP); - const auto it = std::ranges::find_if(texture_list, + const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP); + const auto IT = std::ranges::find_if(TEXTURE_LIST, [&name](const auto& file) { return getFileName(file) == name; }); - return it != texture_list.end() ? std::make_shared(Screen::get()->getRenderer(), *it) : nullptr; + return IT != TEXTURE_LIST.end() ? std::make_shared(Screen::get()->getRenderer(), *IT) : nullptr; } auto Resource::loadTextFileLazy(const std::string& name) -> std::shared_ptr { - const auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT); - const auto it = std::ranges::find_if(text_file_list, + const auto TEXT_FILE_LIST = Asset::get()->getListByType(Asset::Type::FONT); + const auto IT = std::ranges::find_if(TEXT_FILE_LIST, [&name](const auto& file) { return getFileName(file) == name; }); - return it != text_file_list.end() ? Text::loadFile(*it) : nullptr; + return IT != TEXT_FILE_LIST.end() ? Text::loadFile(*IT) : nullptr; } auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr { @@ -369,22 +362,22 @@ auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr { {.key = "smb2", .texture_file = "smb2.png", .text_file = "smb2.txt"}, {.key = "smb2_grad", .texture_file = "smb2_grad.png", .text_file = "smb2.txt"}}; - const auto it = std::ranges::find_if(TEXT_MAPPINGS, + const auto IT = std::ranges::find_if(TEXT_MAPPINGS, [&name](const auto& mapping) { return mapping.key == name; }); - if (it == TEXT_MAPPINGS.end()) { + if (IT == TEXT_MAPPINGS.end()) { return nullptr; } - auto texture = getTexture(it->texture_file); // Esto cargará la textura si no está cargada - auto text_file = getTextFile(it->text_file); // Esto cargará el archivo de texto si no está cargado + auto texture = getTexture(IT->texture_file); // Esto cargará la textura si no está cargada + auto text_file = getTextFile(IT->text_file); // Esto cargará el archivo de texto si no está cargado return (texture && text_file) ? std::make_shared(texture, text_file) : nullptr; } auto Resource::loadAnimationLazy(const std::string& name) -> AnimationsFileBuffer { - const auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION); - const auto it = std::ranges::find_if(animation_list, + const auto ANIMATION_LIST = Asset::get()->getListByType(Asset::Type::ANIMATION); + const auto IT = std::ranges::find_if(ANIMATION_LIST, [&name](const auto& file) { return getFileName(file) == name; }); - if (it != animation_list.end()) { - return loadAnimationsFromFile(*it); + if (IT != ANIMATION_LIST.end()) { + return loadAnimationsFromFile(*IT); } // Si no se encuentra, retorna vector vacío return AnimationsFileBuffer{}; @@ -427,80 +420,54 @@ auto Resource::isLoadDone() const -> bool { return stage_ == LoadStage::DONE; } +// Avança una etapa que descarrega una llista d'assets. +void Resource::advanceListLoadStage(const std::vector& list, void (Resource::*load_one)(size_t), LoadStage next_stage) { + if (stage_index_ >= list.size()) { + stage_ = next_stage; + stage_index_ = 0; + return; + } + (this->*load_one)(stage_index_++); +} + // Bombea la máquina de etapas hasta agotar el presupuesto de tiempo o completar la carga. // Devuelve true cuando ya no queda nada por cargar. auto Resource::loadStep(int budget_ms) -> bool { if (stage_ == LoadStage::DONE) { return true; } - const Uint64 start_ns = SDL_GetTicksNS(); - const Uint64 budget_ns = static_cast(budget_ms) * 1'000'000ULL; + const Uint64 START_NS = SDL_GetTicksNS(); + const Uint64 BUDGET_NS = static_cast(budget_ms) * 1'000'000ULL; while (stage_ != LoadStage::DONE) { switch (stage_) { case LoadStage::SOUNDS: { - auto list = Asset::get()->getListByType(Asset::Type::SOUND); if (stage_index_ == 0) { sounds_.clear(); } - if (stage_index_ >= list.size()) { - stage_ = LoadStage::MUSICS; - stage_index_ = 0; - break; - } - loadOneSound(stage_index_++); + advanceListLoadStage(Asset::get()->getListByType(Asset::Type::SOUND), &Resource::loadOneSound, LoadStage::MUSICS); break; } case LoadStage::MUSICS: { - auto list = Asset::get()->getListByType(Asset::Type::MUSIC); if (stage_index_ == 0) { musics_.clear(); } - if (stage_index_ >= list.size()) { - stage_ = LoadStage::TEXTURES; - stage_index_ = 0; - break; - } - loadOneMusic(stage_index_++); + advanceListLoadStage(Asset::get()->getListByType(Asset::Type::MUSIC), &Resource::loadOneMusic, LoadStage::TEXTURES); break; } case LoadStage::TEXTURES: { - auto list = Asset::get()->getListByType(Asset::Type::BITMAP); if (stage_index_ == 0) { textures_.clear(); } - if (stage_index_ >= list.size()) { - stage_ = LoadStage::TEXT_FILES; - stage_index_ = 0; - break; - } - loadOneTexture(stage_index_++); + advanceListLoadStage(Asset::get()->getListByType(Asset::Type::BITMAP), &Resource::loadOneTexture, LoadStage::TEXT_FILES); break; } case LoadStage::TEXT_FILES: { - auto list = Asset::get()->getListByType(Asset::Type::FONT); if (stage_index_ == 0) { text_files_.clear(); } - if (stage_index_ >= list.size()) { - stage_ = LoadStage::ANIMATIONS; - stage_index_ = 0; - break; - } - loadOneTextFile(stage_index_++); + advanceListLoadStage(Asset::get()->getListByType(Asset::Type::FONT), &Resource::loadOneTextFile, LoadStage::ANIMATIONS); break; } case LoadStage::ANIMATIONS: { - auto list = Asset::get()->getListByType(Asset::Type::ANIMATION); if (stage_index_ == 0) { animations_.clear(); } - if (stage_index_ >= list.size()) { - stage_ = LoadStage::DEMO_DATA; - stage_index_ = 0; - break; - } - loadOneAnimation(stage_index_++); + advanceListLoadStage(Asset::get()->getListByType(Asset::Type::ANIMATION), &Resource::loadOneAnimation, LoadStage::DEMO_DATA); break; } case LoadStage::DEMO_DATA: { - auto list = Asset::get()->getListByType(Asset::Type::DEMODATA); if (stage_index_ == 0) { demos_.clear(); } - if (stage_index_ >= list.size()) { - stage_ = LoadStage::CREATE_TEXT; - stage_index_ = 0; - break; - } - loadOneDemoData(stage_index_++); + advanceListLoadStage(Asset::get()->getListByType(Asset::Type::DEMODATA), &Resource::loadOneDemoData, LoadStage::CREATE_TEXT); break; } case LoadStage::CREATE_TEXT: @@ -519,7 +486,7 @@ auto Resource::loadStep(int budget_ms) -> bool { break; } - if ((SDL_GetTicksNS() - start_ns) >= budget_ns) { break; } + if ((SDL_GetTicksNS() - START_NS) >= BUDGET_NS) { break; } } return stage_ == LoadStage::DONE; @@ -542,11 +509,11 @@ void Resource::loadOneSound(size_t idx) { auto name = getFileName(path); updateLoadingProgress(name); auto audio_data = loadAudioData(path); - JA_Sound_t* sound = nullptr; + Ja::Sound* sound = nullptr; if (!audio_data.data.empty()) { - sound = JA_LoadSound(audio_data.data.data(), audio_data.data.size()); + sound = Ja::loadSound(audio_data.data.data(), audio_data.data.size()); } else { - sound = JA_LoadSound(path.c_str()); + sound = Ja::loadSound(path.c_str()); } if (sound == nullptr) { std::cout << "Sound load failed: " << name << '\n'; @@ -561,11 +528,11 @@ void Resource::loadOneMusic(size_t idx) { auto name = getFileName(path); updateLoadingProgress(name); auto audio_data = loadAudioData(path); - JA_Music_t* music = nullptr; + Ja::Music* music = nullptr; if (!audio_data.data.empty()) { - music = JA_LoadMusic(audio_data.data.data(), audio_data.data.size()); + music = Ja::loadMusic(audio_data.data.data(), audio_data.data.size()); } else { - music = JA_LoadMusic(path.c_str()); + music = Ja::loadMusic(path.c_str()); } if (music == nullptr) { std::cout << "Music load failed: " << name << '\n'; @@ -627,10 +594,10 @@ void Resource::createPlayerTextures() { const auto& player = players[player_idx]; // Obtenemos el jugador actual // Encontrar el archivo original de la textura - const auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP); - const auto it = std::ranges::find_if(texture_list, + const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP); + const auto IT = std::ranges::find_if(TEXTURE_LIST, [&player](const auto& file) { return getFileName(file) == player.base_texture; }); - const std::string texture_file_path = (it != texture_list.end()) ? *it : std::string{}; + const std::string TEXTURE_FILE_PATH = (IT != TEXTURE_LIST.end()) ? *IT : std::string{}; // Crear las 4 texturas con sus respectivas paletas for (int palette_idx = 0; palette_idx < 4; ++palette_idx) { @@ -639,14 +606,14 @@ void Resource::createPlayerTextures() { if (palette_idx == 0) { // Textura 0 - usar la ya cargada y modificar solo paleta 0 (default_shirt) texture = getTexture(player.base_texture); - texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.TO_UINT32()); - texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.TO_UINT32()); - texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.TO_UINT32()); - texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.TO_UINT32()); - texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].TO_UINT32()); + texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.toUint32()); + texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.toUint32()); + texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.toUint32()); + texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.toUint32()); + texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].toUint32()); } else { // Crear textura nueva desde archivo usando ResourceHelper - texture = std::make_shared(Screen::get()->getRenderer(), texture_file_path); + texture = std::make_shared(Screen::get()->getRenderer(), TEXTURE_FILE_PATH); // Añadir todas las paletas texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[0])); @@ -655,18 +622,18 @@ void Resource::createPlayerTextures() { if (palette_idx == 1) { // Textura 1 - modificar solo paleta 1 (one_coffee_shirt) - texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.TO_UINT32()); - texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.TO_UINT32()); - texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.TO_UINT32()); - texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.TO_UINT32()); - texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].TO_UINT32()); + texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.toUint32()); + texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.toUint32()); + texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.toUint32()); + texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.toUint32()); + texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].toUint32()); } else if (palette_idx == 2) { // Textura 2 - modificar solo paleta 2 (two_coffee_shirt) - texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.TO_UINT32()); - texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.TO_UINT32()); - texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.TO_UINT32()); - texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.TO_UINT32()); - texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].TO_UINT32()); + texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.toUint32()); + texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.toUint32()); + texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.toUint32()); + texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.toUint32()); + texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].toUint32()); } // Textura 3 (palette_idx == 3) - no modificar nada, usar colores originales } @@ -769,7 +736,7 @@ void Resource::createText() { void Resource::clearSounds() { for (auto& sound : sounds_) { if (sound.sound != nullptr) { - JA_DeleteSound(sound.sound); + Ja::deleteSound(sound.sound); sound.sound = nullptr; } } @@ -780,7 +747,7 @@ void Resource::clearSounds() { void Resource::clearMusics() { for (auto& music : musics_) { if (music.music != nullptr) { - JA_DeleteMusic(music.music); + Ja::deleteMusic(music.music); music.music = nullptr; } } @@ -830,7 +797,7 @@ void Resource::renderProgress() { const bool WAITING_FOR_INPUT = isLoadDone() && Options::loading.wait_for_input; auto text_color = param.resource.color; - auto bar_color = param.resource.color.DARKEN(100); + auto bar_color = param.resource.color.darken(100); const auto TEXT_HEIGHT = loading_text_->getCharacterSize(); // Dibuja el interior de la barra de progreso @@ -880,10 +847,10 @@ void Resource::renderProgress() { // Carga los datos para el modo demostración (sin mostrar progreso) void Resource::loadDemoDataQuiet() { - const auto list = Asset::get()->getListByType(Asset::Type::DEMODATA); + const auto LIST = Asset::get()->getListByType(Asset::Type::DEMODATA); demos_.clear(); - demos_.reserve(list.size()); - std::ranges::transform(list, std::back_inserter(demos_), [this](const auto& l) { return loadDemoDataFromFile(l); }); + demos_.reserve(LIST.size()); + std::ranges::transform(LIST, std::back_inserter(demos_), [](const auto& l) { return loadDemoDataFromFile(l); }); } // Inicializa los rectangulos que definen la barra de progreso diff --git a/source/core/resources/resource.hpp b/source/core/resources/resource.hpp index be69c07..0e92e96 100644 --- a/source/core/resources/resource.hpp +++ b/source/core/resources/resource.hpp @@ -3,6 +3,7 @@ #include // Para SDL_FRect #include // Para size_t +#include // Para std::uint8_t #include // Para shared_ptr #include // Para string #include // Para move @@ -13,14 +14,16 @@ #include "core/rendering/texture.hpp" // Para Texture #include "core/system/demo.hpp" // Para DemoData -struct JA_Music_t; -struct JA_Sound_t; +namespace Ja { + struct Music; + struct Sound; +} // namespace Ja // --- Clase Resource: gestiona todos los recursos del juego (singleton) --- class Resource { public: // --- Enum para el modo de carga --- - enum class LoadingMode { + enum class LoadingMode : std::uint8_t { PRELOAD, // Carga todos los recursos al inicio LAZY_LOAD // Carga los recursos bajo demanda }; @@ -31,8 +34,8 @@ class Resource { static auto get() -> Resource*; // Obtiene el puntero al objeto Resource // --- Métodos de acceso a recursos --- - auto getSound(const std::string& name) -> JA_Sound_t*; // Obtiene el sonido por nombre - auto getMusic(const std::string& name) -> JA_Music_t*; // Obtiene la música por nombre + auto getSound(const std::string& name) -> Ja::Sound*; // Obtiene el sonido por nombre + auto getMusic(const std::string& name) -> Ja::Music*; // Obtiene la música por nombre auto getTexture(const std::string& name) -> std::shared_ptr; // Obtiene la textura por nombre auto getTextFile(const std::string& name) -> std::shared_ptr; // Obtiene el fichero de texto por nombre auto getText(const std::string& name) -> std::shared_ptr; // Obtiene el objeto de texto por nombre @@ -58,18 +61,18 @@ class Resource { // --- Estructuras para recursos individuales --- struct ResourceSound { std::string name; // Nombre del sonido - JA_Sound_t* sound; // Objeto con el sonido + Ja::Sound* sound; // Objeto con el sonido - explicit ResourceSound(std::string name, JA_Sound_t* sound = nullptr) + explicit ResourceSound(std::string name, Ja::Sound* sound = nullptr) : name(std::move(name)), sound(sound) {} }; struct ResourceMusic { std::string name; // Nombre de la música - JA_Music_t* music; // Objeto con la música + Ja::Music* music; // Objeto con la música - explicit ResourceMusic(std::string name, JA_Music_t* music = nullptr) + explicit ResourceMusic(std::string name, Ja::Music* music = nullptr) : name(std::move(name)), music(music) {} }; @@ -156,7 +159,7 @@ class Resource { SDL_FRect loading_full_rect_; // --- Estado del cargador incremental --- - enum class LoadStage { + enum class LoadStage : std::uint8_t { SOUNDS, MUSICS, TEXTURES, @@ -187,8 +190,8 @@ class Resource { // --- Métodos para carga perezosa --- void initResourceLists(); // Inicializa las listas de recursos sin cargar el contenido - static auto loadSoundLazy(const std::string& name) -> JA_Sound_t*; // Carga un sonido específico bajo demanda - static auto loadMusicLazy(const std::string& name) -> JA_Music_t*; // Carga una música específica bajo demanda + static auto loadSoundLazy(const std::string& name) -> Ja::Sound*; // Carga un sonido específico bajo demanda + static auto loadMusicLazy(const std::string& name) -> Ja::Music*; // Carga una música específica bajo demanda static auto loadTextureLazy(const std::string& name) -> std::shared_ptr; // Carga una textura específica bajo demanda static auto loadTextFileLazy(const std::string& name) -> std::shared_ptr; // Carga un fichero de texto específico bajo demanda auto loadTextLazy(const std::string& name) -> std::shared_ptr; // Carga un objeto de texto específico bajo demanda @@ -200,6 +203,10 @@ class Resource { void initProgressBar(); // Inicializa los rectangulos que definen la barra de progreso void updateProgressBar(); // Actualiza la barra de estado + // Avança una etapa que descarrega una llista d'assets: si `stage_index_` desborda la mida, + // salta a `next_stage`; si no, crida `load_one` per a l'element actual i incrementa. + void advanceListLoadStage(const std::vector& list, void (Resource::*load_one)(size_t), LoadStage next_stage); + // --- Helpers del cargador incremental (cargan un único recurso) --- void loadOneSound(size_t idx); void loadOneMusic(size_t idx); diff --git a/source/core/system/demo.cpp b/source/core/system/demo.cpp index a0cd64f..eb30648 100644 --- a/source/core/system/demo.cpp +++ b/source/core/system/demo.cpp @@ -6,7 +6,6 @@ #include // Para runtime_error #include "core/resources/resource_helper.hpp" // Para ResourceHelper -#include "utils/utils.hpp" // Para getFileName // Carga el fichero de datos para la demo auto loadDemoDataFromFile(const std::string& file_path) -> DemoData { diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 7920d4e..09a687c 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -38,6 +38,38 @@ #include "game/ui/service_menu.hpp" // Para ServiceMenu #include "utils/param.hpp" // Para loadParamsFromFile +namespace { + // Llig un camp opcional d'un YAML cap a `dest`. Si no existeix, no toca `dest`. + // Si existeix però el tipus no encaixa, deixa el valor per defecte i avisa per stderr + // (un debug.yaml mal escrit no ha de tombar l'arrencada, però l'usuari ha de saber-ho). + template + void loadYamlField(const fkyaml::node& yaml, const std::string& key, T& dest) { + if (!yaml.contains(key)) { return; } + try { + dest = yaml[key].get_value(); + } catch (...) { + std::cerr << "debug.yaml: valor invàlid per a '" << key << "', es manté el valor per defecte\n"; + } + } + + auto parseInitialSection(const std::string& value) -> Section::Name { + if (value == "logo") { return Section::Name::LOGO; } + if (value == "intro") { return Section::Name::INTRO; } + if (value == "title") { return Section::Name::TITLE; } + if (value == "credits") { return Section::Name::CREDITS; } + if (value == "instructions") { return Section::Name::INSTRUCTIONS; } + if (value == "hiscore") { return Section::Name::HI_SCORE_TABLE; } + return Section::Name::GAME; // "game" i qualsevol valor desconegut + } + + auto parseInitialOptions(const std::string& value) -> Section::Options { + if (value == "none") { return Section::Options::NONE; } + if (value == "2p") { return Section::Options::GAME_PLAY_2P; } + if (value == "both") { return Section::Options::GAME_PLAY_BOTH; } + return Section::Options::GAME_PLAY_1P; // "1p" i qualsevol valor desconegut + } +} // namespace + // Constructor Director::Director() { Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO; @@ -275,78 +307,20 @@ void Director::loadDebugConfig() { file.close(); try { auto yaml = fkyaml::node::deserialize(content); - if (yaml.contains("initial_section")) { - try { - debug_config.initial_section = yaml["initial_section"].get_value(); - } catch (...) {} - } - if (yaml.contains("initial_options")) { - try { - debug_config.initial_options = yaml["initial_options"].get_value(); - } catch (...) {} - } - if (yaml.contains("initial_stage")) { - try { - debug_config.initial_stage = yaml["initial_stage"].get_value(); - } catch (...) {} - } - if (yaml.contains("show_render_info")) { - try { - debug_config.show_render_info = yaml["show_render_info"].get_value(); - } catch (...) {} - } - if (yaml.contains("resource_loading")) { - try { - debug_config.resource_loading = yaml["resource_loading"].get_value(); - } catch (...) {} - } - if (yaml.contains("autoplay")) { - try { - debug_config.autoplay = yaml["autoplay"].get_value(); - } catch (...) {} - } - if (yaml.contains("invincibility")) { - try { - debug_config.invincibility = yaml["invincibility"].get_value(); - } catch (...) {} - } + loadYamlField(yaml, "initial_section", debug_config.initial_section); + loadYamlField(yaml, "initial_options", debug_config.initial_options); + loadYamlField(yaml, "initial_stage", debug_config.initial_stage); + loadYamlField(yaml, "show_render_info", debug_config.show_render_info); + loadYamlField(yaml, "resource_loading", debug_config.resource_loading); + loadYamlField(yaml, "autoplay", debug_config.autoplay); + loadYamlField(yaml, "invincibility", debug_config.invincibility); } catch (...) { std::cout << "Error parsing debug.yaml, using defaults" << '\n'; } } - // Mapear strings a enums - const auto& sec = debug_config.initial_section; - if (sec == "logo") { - Section::name = Section::Name::LOGO; - } else if (sec == "intro") { - Section::name = Section::Name::INTRO; - } else if (sec == "title") { - Section::name = Section::Name::TITLE; - } else if (sec == "game") { - Section::name = Section::Name::GAME; - } else if (sec == "credits") { - Section::name = Section::Name::CREDITS; - } else if (sec == "instructions") { - Section::name = Section::Name::INSTRUCTIONS; - } else if (sec == "hiscore") { - Section::name = Section::Name::HI_SCORE_TABLE; - } else { - Section::name = Section::Name::GAME; - } - - const auto& opt = debug_config.initial_options; - if (opt == "none") { - Section::options = Section::Options::NONE; - } else if (opt == "1p") { - Section::options = Section::Options::GAME_PLAY_1P; - } else if (opt == "2p") { - Section::options = Section::Options::GAME_PLAY_2P; - } else if (opt == "both") { - Section::options = Section::Options::GAME_PLAY_BOTH; - } else { - Section::options = Section::Options::GAME_PLAY_1P; - } + Section::name = parseInitialSection(debug_config.initial_section); + Section::options = parseInitialOptions(debug_config.initial_options); } // Crea la carpeta del sistema donde guardar datos @@ -499,7 +473,7 @@ auto Director::iterate() -> SDL_AppResult { // Ejecuta un frame de la sección activa if (preload_) { - preload_->iterate(); + Preload::iterate(); } else if (logo_) { logo_->iterate(); } else if (intro_) { @@ -526,7 +500,7 @@ auto Director::handleEvent(const SDL_Event& event) -> SDL_AppResult { // Reenvía a la sección activa if (preload_) { - preload_->handleEvent(event); + Preload::handleEvent(event); } else if (logo_) { logo_->handleEvent(event); } else if (intro_) { diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp index 6d44ad7..3f54159 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -7,8 +7,10 @@ #include "core/system/section.hpp" // Para Section::Name +#include // Para std::uint8_t + namespace Lang { - enum class Code : int; + enum class Code : std::uint8_t; } // Declaraciones adelantadas de las secciones @@ -70,7 +72,7 @@ class Director { // --- Inicialización y cierre del sistema --- void init(); // Inicializa la aplicación (pre-boot) - void finishBoot(); // Post-boot: inicializa lo que depende de recursos cargados + static void finishBoot(); // Post-boot: inicializa lo que depende de recursos cargados static void close(); // Cierra y libera recursos // --- Configuración inicial --- diff --git a/source/core/system/section.hpp b/source/core/system/section.hpp index 158c9be..33f8ab9 100644 --- a/source/core/system/section.hpp +++ b/source/core/system/section.hpp @@ -1,5 +1,7 @@ #pragma once +#include + /* Namespace section: define los estados/secciones principales del programa, así como las opciones y modos especiales (como el Attract Mode). @@ -8,7 +10,7 @@ namespace Section { // --- Enumeraciones de secciones del programa --- - enum class Name { + enum class Name : std::uint8_t { RESET, // Inicialización PRELOAD, // Carga incremental de recursos LOGO, // Pantalla de logo @@ -23,7 +25,7 @@ namespace Section { }; // --- Opciones para la sección actual --- - enum class Options { + enum class Options : std::uint8_t { GAME_PLAY_1P, // Iniciar el juego con el jugador 1 GAME_PLAY_2P, // Iniciar el juego con el jugador 2 GAME_PLAY_BOTH, // Iniciar el juego con los dos jugadores @@ -37,7 +39,7 @@ namespace Section { }; // --- Modos para el Attract Mode --- - enum class AttractMode { + enum class AttractMode : std::uint8_t { TITLE_TO_DEMO, // Pasar de título a demo TITLE_TO_LOGO, // Pasar de título a logo }; diff --git a/source/core/system/shutdown.hpp b/source/core/system/shutdown.hpp index 87c178d..50dc352 100644 --- a/source/core/system/shutdown.hpp +++ b/source/core/system/shutdown.hpp @@ -1,10 +1,12 @@ #pragma once +#include + // --- Namespace SystemShutdown: utilidad multiplataforma para apagar el sistema de forma segura --- namespace SystemShutdown { // --- Enums --- - enum class ShutdownResult { + enum class ShutdownResult : std::uint8_t { SUCCESS = 0, // Éxito ERROR_PERMISSION, // Error de permisos insuficientes ERROR_SYSTEM_CALL, // Error en la llamada al sistema diff --git a/source/core/system/system_utils.hpp b/source/core/system/system_utils.hpp index 23902f3..30afadb 100644 --- a/source/core/system/system_utils.hpp +++ b/source/core/system/system_utils.hpp @@ -1,11 +1,12 @@ #pragma once +#include #include // --- Namespace SystemUtils: utilidades multiplataforma para operaciones del sistema --- namespace SystemUtils { // --- Enums --- - enum class Result { // Códigos de resultado para operaciones del sistema + enum class Result : std::uint8_t { // Códigos de resultado para operaciones del sistema SUCCESS = 0, PERMISSION_DENIED, // Sin permisos para crear la carpeta PATH_TOO_LONG, // Ruta demasiado larga diff --git a/source/game/entities/balloon.cpp b/source/game/entities/balloon.cpp index ac0b2f9..06130f9 100644 --- a/source/game/entities/balloon.cpp +++ b/source/game/entities/balloon.cpp @@ -7,7 +7,7 @@ #include "core/audio/audio.hpp" // Para Audio #include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite #include "core/rendering/sprite/sprite.hpp" // Para Sprite -#include "core/rendering/texture.hpp" // Para Texture +#include "core/rendering/texture.hpp" // IWYU pragma: keep #include "utils/param.hpp" // Para Param, ParamBalloon, param // Constructor diff --git a/source/game/entities/item.hpp b/source/game/entities/item.hpp index a17fde3..7a1f837 100644 --- a/source/game/entities/item.hpp +++ b/source/game/entities/item.hpp @@ -2,9 +2,10 @@ #include // Para SDL_FRect, Uint16 -#include // Para shared_ptr, unique_ptr -#include // Para string -#include // Para vector +#include // Para std::uint8_t +#include // Para shared_ptr, unique_ptr +#include // Para string +#include // Para vector #include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite #include "utils/utils.hpp" // Para Circle @@ -12,7 +13,7 @@ class Texture; // --- Enums --- -enum class ItemType : int { +enum class ItemType : std::uint8_t { DISK = 1, // Disco GAVINA = 2, // Gavina PACMAR = 3, // Pacman diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index ec8f5e8..fd23675 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -8,7 +8,6 @@ #include "core/audio/audio.hpp" // Para Audio #include "core/input/input.hpp" // Para Input -#include "core/input/input_types.hpp" // Para InputAction #include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite #include "core/rendering/texture.hpp" // Para Texture #include "core/resources/asset.hpp" // Para Asset @@ -1135,8 +1134,6 @@ void Player::updateFiringStateFromVisual() { firing_state_ = State::RECOILING_RIGHT; break; case State::FIRING_UP: - firing_state_ = State::RECOILING_UP; - break; default: firing_state_ = State::RECOILING_UP; break; @@ -1152,8 +1149,6 @@ void Player::updateFiringStateFromVisual() { firing_state_ = State::COOLING_RIGHT; break; case State::FIRING_UP: - firing_state_ = State::COOLING_UP; - break; default: firing_state_ = State::COOLING_UP; break; diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 1ab93ef..df612af 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -2,12 +2,12 @@ #include // Para SDL_FRect, SDL_FlipMode -#include // Para size_t -#include // Para pair -#include // Para shared_ptr, unique_ptr -#include // Para basic_string, string -#include // Para move, pair -#include // Para vector +#include // Para size_t +#include // Para std::uint8_t, std::int8_t +#include // Para shared_ptr, unique_ptr +#include // Para basic_string, string +#include // Para move, pair +#include // Para vector #include "core/input/input.hpp" // for Input #include "core/rendering/sprite/animated_sprite.hpp" // for AnimatedSprite @@ -49,14 +49,14 @@ class Player { }; // --- Enums --- - enum class Id : int { + enum class Id : std::int8_t { NO_PLAYER = -1, // Sin jugador BOTH_PLAYERS = 0, // Ambos jugadores PLAYER1 = 1, // Jugador 1 PLAYER2 = 2 // Jugador 2 }; - enum class State { + enum class State : std::uint8_t { // Estados de movimiento WALKING_LEFT, // Caminando hacia la izquierda WALKING_RIGHT, // Caminando hacia la derecha @@ -293,7 +293,7 @@ class Player { bool can_fire_new_system_ = true; // true si puede disparar ahora mismo // LÍNEA 2: SISTEMA VISUAL (Animaciones) - enum class VisualFireState { + enum class VisualFireState : std::uint8_t { NORMAL, // Brazo en posición neutral AIMING, // Brazo alzado (disparando) RECOILING, // Brazo en retroceso diff --git a/source/game/entities/tabe.hpp b/source/game/entities/tabe.hpp index 6161b81..d1e4910 100644 --- a/source/game/entities/tabe.hpp +++ b/source/game/entities/tabe.hpp @@ -2,6 +2,7 @@ #include // Para Uint32, SDL_GetTicks, SDL_FRect +#include // Para std::uint8_t #include // Para rand #include // Para unique_ptr @@ -11,12 +12,12 @@ class Tabe { public: // --- Enumeraciones para dirección y estado --- - enum class Direction : int { + enum class Direction : std::uint8_t { TO_THE_LEFT = 0, TO_THE_RIGHT = 1, }; - enum class State : int { + enum class State : std::uint8_t { FLY = 0, HIT = 1, }; diff --git a/source/game/gameplay/balloon_formations.cpp b/source/game/gameplay/balloon_formations.cpp index 8c66904..4cc3392 100644 --- a/source/game/gameplay/balloon_formations.cpp +++ b/source/game/gameplay/balloon_formations.cpp @@ -201,6 +201,8 @@ auto BalloonFormations::evaluateSimpleExpression(const std::string& expr, const return left_val * right_val; case '/': return right_val != 0 ? left_val / right_val : 0; + default: + break; // Inalcanzable: el if exterior solo deja pasar '+', '-', '*', '/' } } } diff --git a/source/game/gameplay/balloon_formations.hpp b/source/game/gameplay/balloon_formations.hpp index 58ea784..74eb78a 100644 --- a/source/game/gameplay/balloon_formations.hpp +++ b/source/game/gameplay/balloon_formations.hpp @@ -1,7 +1,6 @@ #pragma once #include // Para size_t -#include // Para pair #include // Para map #include // Para optional #include // Para string diff --git a/source/game/gameplay/bullet_manager.cpp b/source/game/gameplay/bullet_manager.cpp index 89629b3..72c395c 100644 --- a/source/game/gameplay/bullet_manager.cpp +++ b/source/game/gameplay/bullet_manager.cpp @@ -1,6 +1,5 @@ #include "game/gameplay/bullet_manager.hpp" -#include // Para remove_if #include #include "game/entities/bullet.hpp" // Para Bullet diff --git a/source/game/gameplay/bullet_manager.hpp b/source/game/gameplay/bullet_manager.hpp index c731be1..fe976e5 100644 --- a/source/game/gameplay/bullet_manager.hpp +++ b/source/game/gameplay/bullet_manager.hpp @@ -5,7 +5,6 @@ #include // Para function #include // Para list #include // Para shared_ptr -#include // Para vector #include "game/entities/bullet.hpp" // for Bullet diff --git a/source/game/gameplay/difficulty.cpp b/source/game/gameplay/difficulty.cpp index 05628ac..089736d 100644 --- a/source/game/gameplay/difficulty.cpp +++ b/source/game/gameplay/difficulty.cpp @@ -19,19 +19,19 @@ namespace Difficulty { } auto getNameFromCode(Code code) -> std::string { - const auto it = std::ranges::find_if(difficulties_list, + const auto IT = std::ranges::find_if(difficulties_list, [code](const auto& difficulty) { return difficulty.code == code; }); - if (it != difficulties_list.end()) { - return it->name; + if (IT != difficulties_list.end()) { + return IT->name; } return !difficulties_list.empty() ? difficulties_list.front().name : "Unknown"; } auto getCodeFromName(const std::string& name) -> Code { - const auto it = std::ranges::find_if(difficulties_list, + const auto IT = std::ranges::find_if(difficulties_list, [&name](const auto& difficulty) { return difficulty.name == name; }); - if (it != difficulties_list.end()) { - return it->code; + if (IT != difficulties_list.end()) { + return IT->code; } return !difficulties_list.empty() ? difficulties_list.front().code : Code::NORMAL; } diff --git a/source/game/gameplay/difficulty.hpp b/source/game/gameplay/difficulty.hpp index cb22370..d3f0411 100644 --- a/source/game/gameplay/difficulty.hpp +++ b/source/game/gameplay/difficulty.hpp @@ -1,12 +1,13 @@ #pragma once -#include // Para string -#include // Para vector +#include // Para std::uint8_t +#include // Para string +#include // Para vector namespace Difficulty { // --- Enums --- - enum class Code { + enum class Code : std::uint8_t { EASY = 0, // Dificultad fácil NORMAL = 1, // Dificultad normal HARD = 2, // Dificultad difícil diff --git a/source/game/gameplay/game_logo.hpp b/source/game/gameplay/game_logo.hpp index eaf2f3a..801cc3e 100644 --- a/source/game/gameplay/game_logo.hpp +++ b/source/game/gameplay/game_logo.hpp @@ -1,6 +1,7 @@ #pragma once -#include // Para unique_ptr, shared_ptr +#include // Para std::uint8_t +#include // Para unique_ptr, shared_ptr #include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite #include "core/rendering/sprite/smart_sprite.hpp" // Para SmartSprite @@ -36,7 +37,7 @@ class GameLogo { private: // --- Enums --- - enum class Status { + enum class Status : std::uint8_t { DISABLED, // Deshabilitado MOVING, // En movimiento SHAKING, // Temblando diff --git a/source/game/gameplay/manage_hiscore_table.cpp b/source/game/gameplay/manage_hiscore_table.cpp index b2cde74..e8c9483 100644 --- a/source/game/gameplay/manage_hiscore_table.cpp +++ b/source/game/gameplay/manage_hiscore_table.cpp @@ -2,15 +2,13 @@ #include // Para SDL_ReadIO, SDL_WriteIO, SDL_CloseIO, SDL_GetError, SDL_IOFromFile, SDL_LogError, SDL_LogCategory, SDL_LogInfo -#include // Para __sort_fn, sort -#include // Para array -#include // Para identity -#include // Para std::setw, std::setfill -#include // Para std::cout -#include // Para distance -#include // Para accumulate -#include // Para __find_if_fn, find_if -#include // Para move +#include // Para sort, ranges::find_if, ranges::sort +#include // Para array +#include // Para std::setw, std::setfill +#include // Para std::cout +#include // Para distance +#include // Para accumulate +#include // Para move #include "utils/utils.hpp" // Para getFileName diff --git a/source/game/gameplay/scoreboard.cpp b/source/game/gameplay/scoreboard.cpp index 94e9454..00a6184 100644 --- a/source/game/gameplay/scoreboard.cpp +++ b/source/game/gameplay/scoreboard.cpp @@ -79,7 +79,7 @@ Scoreboard::Scoreboard() fillBackgroundTexture(); // Inicializa el ciclo de colores para el nombre - name_color_cycle_ = Colors::generateMirroredCycle(color_.INVERSE(), ColorCycleStyle::VIBRANT); + name_color_cycle_ = Colors::generateMirroredCycle(color_.inverse(), ColorCycleStyle::VIBRANT); animated_color_ = name_color_cycle_.at(0); } @@ -337,13 +337,13 @@ void Scoreboard::render() { void Scoreboard::setColor(Color color) { // Actualiza las variables de colores color_ = color; - text_color1_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(100) : param.scoreboard.text_color1; - text_color2_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(150) : param.scoreboard.text_color2; + text_color1_ = param.scoreboard.text_autocolor ? color_.lighten(100) : param.scoreboard.text_color1; + text_color2_ = param.scoreboard.text_autocolor ? color_.lighten(150) : param.scoreboard.text_color2; // Aplica los colores power_meter_sprite_->getTexture()->setColor(text_color2_); fillBackgroundTexture(); - name_color_cycle_ = Colors::generateMirroredCycle(color_.INVERSE(), ColorCycleStyle::VIBRANT); + name_color_cycle_ = Colors::generateMirroredCycle(color_.inverse(), ColorCycleStyle::VIBRANT); } // Establece el valor de la variable @@ -383,9 +383,9 @@ void Scoreboard::fillPanelTextures() { } // Interpolar entre color base y color aclarado - Color target_color = color_.LIGHTEN(PANEL_PULSE_LIGHTEN_AMOUNT); + Color target_color = color_.lighten(PANEL_PULSE_LIGHTEN_AMOUNT); // Color target_color = color_.INVERSE(); - background_color = color_.LERP(target_color, pulse_intensity); + background_color = color_.lerp(target_color, pulse_intensity); background_color.a = 255; // Opaco durante el pulso } @@ -716,7 +716,7 @@ void Scoreboard::createPanelTextures() { // Dibuja la linea que separa la zona de juego del marcador void Scoreboard::renderSeparator() { // Dibuja la linea que separa el marcador de la zona de juego - auto color = param.scoreboard.separator_autocolor ? color_.DARKEN() : param.scoreboard.separator_color; + auto color = param.scoreboard.separator_autocolor ? color_.darken() : param.scoreboard.separator_color; SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 255); SDL_RenderLine(renderer_, 0, 0, rect_.w, 0); } @@ -755,7 +755,7 @@ void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) { } const float FRACTIONAL_OFFSET = frac; - const int PIXEL_OFFSET = static_cast((FRACTIONAL_OFFSET * CHAR_STEP) + 0.5F); + const int PIXEL_OFFSET = static_cast(std::lround(FRACTIONAL_OFFSET * CHAR_STEP)); // Índice base en la lista de caracteres (posición central) const int BASE_INDEX = static_cast(std::floor(carousel_pos)); @@ -790,13 +790,13 @@ void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) { if (DISTANCE_FROM_CENTER < 0.5F) { // Letra central → transiciona hacia animated_color_ float lerp_to_animated = DISTANCE_FROM_CENTER / 0.5F; // 0.0 a 1.0 - letter_color = animated_color_.LERP(text_color1_, lerp_to_animated); + letter_color = animated_color_.lerp(text_color1_, lerp_to_animated); } else { // Letras alejadas → degradan hacia color_ base float base_lerp = (DISTANCE_FROM_CENTER - 0.5F) / (HALF_VISIBLE - 0.5F); base_lerp = std::min(base_lerp, 1.0F); const float LERP_FACTOR = base_lerp * 0.85F; - letter_color = text_color1_.LERP(color_, LERP_FACTOR); + letter_color = text_color1_.lerp(color_, LERP_FACTOR); } // Calcular posición X de la letra diff --git a/source/game/gameplay/scoreboard.hpp b/source/game/gameplay/scoreboard.hpp index ba49747..77ca07d 100644 --- a/source/game/gameplay/scoreboard.hpp +++ b/source/game/gameplay/scoreboard.hpp @@ -4,6 +4,7 @@ #include // Para array #include // Para size_t +#include // Para std::uint8_t #include // Para shared_ptr, unique_ptr #include // Para string, basic_string #include // Para vector @@ -21,14 +22,14 @@ class Texture; class Scoreboard { public: // --- Enums --- - enum class Id : size_t { + enum class Id : std::uint8_t { LEFT = 0, CENTER = 1, RIGHT = 2, SIZE = 3 }; - enum class Mode : int { + enum class Mode : std::uint8_t { SCORE, STAGE_INFO, CONTINUE, diff --git a/source/game/gameplay/stage.hpp b/source/game/gameplay/stage.hpp index c0539ee..63fe7a4 100644 --- a/source/game/gameplay/stage.hpp +++ b/source/game/gameplay/stage.hpp @@ -1,6 +1,7 @@ #pragma once #include // Para size_t +#include // Para std::uint8_t #include // Para function #include // Para optional #include // Para basic_string, string @@ -9,12 +10,12 @@ #include "core/system/stage_interface.hpp" // for IStageInfo // --- Enums --- -enum class PowerCollectionState { +enum class PowerCollectionState : std::uint8_t { ENABLED, // Recolección habilitada DISABLED // Recolección deshabilitada }; -enum class StageStatus { +enum class StageStatus : std::uint8_t { LOCKED, // Fase bloqueada IN_PROGRESS, // Fase en progreso COMPLETED // Fase completada diff --git a/source/game/options.cpp b/source/game/options.cpp index e56c77c..467c5d8 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -26,12 +26,12 @@ namespace Options { Keyboard keyboard; // Opciones para el teclado PendingChanges pending_changes; // Opciones que se aplican al cerrar std::vector postfx_presets = { - {"CRT", 0.15F, 0.7F, 0.2F, 0.5F, 0.1F, 0.0F, 0.0F, 0.0F}, - {"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F}, - {"Curved", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F}, - {"Scanlines", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}, - {"Subtle", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F}, - {"CRT Live", 0.15F, 0.6F, 0.3F, 0.3F, 0.1F, 0.0F, 0.4F, 0.8F}, + {.name = "CRT", .vignette = 0.15F, .scanlines = 0.7F, .chroma = 0.2F, .mask = 0.5F, .gamma = 0.1F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F}, + {.name = "NTSC", .vignette = 0.4F, .scanlines = 0.5F, .chroma = 0.2F, .mask = 0.3F, .gamma = 0.3F, .curvature = 0.0F, .bleeding = 0.6F, .flicker = 0.0F}, + {.name = "Curved", .vignette = 0.5F, .scanlines = 0.6F, .chroma = 0.1F, .mask = 0.4F, .gamma = 0.4F, .curvature = 0.8F, .bleeding = 0.0F, .flicker = 0.0F}, + {.name = "Scanlines", .vignette = 0.0F, .scanlines = 0.8F, .chroma = 0.0F, .mask = 0.0F, .gamma = 0.0F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F}, + {.name = "Subtle", .vignette = 0.3F, .scanlines = 0.4F, .chroma = 0.05F, .mask = 0.0F, .gamma = 0.2F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F}, + {.name = "CRT Live", .vignette = 0.15F, .scanlines = 0.6F, .chroma = 0.3F, .mask = 0.3F, .gamma = 0.1F, .curvature = 0.0F, .bleeding = 0.4F, .flicker = 0.8F}, }; std::string postfx_file_path; std::vector crtpi_presets; @@ -50,11 +50,17 @@ namespace Options { void setCrtPiFile(const std::string& path) { crtpi_file_path = path; } // Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión - static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) { - if (node.contains(key)) { - try { - target = node[key].get_value(); - } catch (...) {} + // Llig un camp opcional d'un node YAML. Si no existeix, no toca `target`. + // Si existeix però el tipus no encaixa, manté el valor previ i avisa per stderr + // (un fitxer de configuració parcialment malformat no ha de tombar l'arrencada, + // però l'usuari ha de saber quin camp ha quedat ignorat). + template + void parseField(const fkyaml::node& node, const std::string& key, T& target) { + if (!node.contains(key)) { return; } + try { + target = node[key].get_value(); + } catch (...) { + std::cerr << "config YAML: valor invàlid per a '" << key << "', es manté el valor per defecte\n"; } } @@ -80,14 +86,14 @@ namespace Options { if (p.contains("name")) { preset.name = p["name"].get_value(); } - parseFloatField(p, "vignette", preset.vignette); - parseFloatField(p, "scanlines", preset.scanlines); - parseFloatField(p, "chroma", preset.chroma); - parseFloatField(p, "mask", preset.mask); - parseFloatField(p, "gamma", preset.gamma); - parseFloatField(p, "curvature", preset.curvature); - parseFloatField(p, "bleeding", preset.bleeding); - parseFloatField(p, "flicker", preset.flicker); + parseField(p, "vignette", preset.vignette); + parseField(p, "scanlines", preset.scanlines); + parseField(p, "chroma", preset.chroma); + parseField(p, "mask", preset.mask); + parseField(p, "gamma", preset.gamma); + parseField(p, "curvature", preset.curvature); + parseField(p, "bleeding", preset.bleeding); + parseField(p, "flicker", preset.flicker); postfx_presets.push_back(preset); } } @@ -212,24 +218,6 @@ namespace Options { return true; } - // Helper: extrae un campo bool de un nodo YAML si existe, ignorando errores - static void parseBoolField(const fkyaml::node& node, const std::string& key, bool& target) { - if (node.contains(key)) { - try { - target = node[key].get_value(); - } catch (...) {} - } - } - - // Helper: extrae un campo int de un nodo YAML si existe, ignorando errores - static void parseIntField(const fkyaml::node& node, const std::string& key, int& target) { - if (node.contains(key)) { - try { - target = node[key].get_value(); - } catch (...) {} - } - } - // Rellena los presets CrtPi por defecto static void populateDefaultCrtPiPresets() { crtpi_presets.clear(); @@ -289,20 +277,20 @@ namespace Options { if (p.contains("name")) { preset.name = p["name"].get_value(); } - parseFloatField(p, "scanline_weight", preset.scanline_weight); - parseFloatField(p, "scanline_gap_brightness", preset.scanline_gap_brightness); - parseFloatField(p, "bloom_factor", preset.bloom_factor); - parseFloatField(p, "input_gamma", preset.input_gamma); - parseFloatField(p, "output_gamma", preset.output_gamma); - parseFloatField(p, "mask_brightness", preset.mask_brightness); - parseFloatField(p, "curvature_x", preset.curvature_x); - parseFloatField(p, "curvature_y", preset.curvature_y); - parseIntField(p, "mask_type", preset.mask_type); - parseBoolField(p, "enable_scanlines", preset.enable_scanlines); - parseBoolField(p, "enable_multisample", preset.enable_multisample); - parseBoolField(p, "enable_gamma", preset.enable_gamma); - parseBoolField(p, "enable_curvature", preset.enable_curvature); - parseBoolField(p, "enable_sharper", preset.enable_sharper); + parseField(p, "scanline_weight", preset.scanline_weight); + parseField(p, "scanline_gap_brightness", preset.scanline_gap_brightness); + parseField(p, "bloom_factor", preset.bloom_factor); + parseField(p, "input_gamma", preset.input_gamma); + parseField(p, "output_gamma", preset.output_gamma); + parseField(p, "mask_brightness", preset.mask_brightness); + parseField(p, "curvature_x", preset.curvature_x); + parseField(p, "curvature_y", preset.curvature_y); + parseField(p, "mask_type", preset.mask_type); + parseField(p, "enable_scanlines", preset.enable_scanlines); + parseField(p, "enable_multisample", preset.enable_multisample); + parseField(p, "enable_gamma", preset.enable_gamma); + parseField(p, "enable_curvature", preset.enable_curvature); + parseField(p, "enable_sharper", preset.enable_sharper); crtpi_presets.push_back(preset); } } @@ -353,98 +341,46 @@ namespace Options { void loadWindowFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("window")) { return; } const auto& win = yaml["window"]; - if (win.contains("zoom")) { - try { - int val = win["zoom"].get_value(); - window.zoom = (val > 0) ? val : window.zoom; - } catch (...) {} - } + int zoom = window.zoom; + parseField(win, "zoom", zoom); + window.zoom = (zoom > 0) ? zoom : window.zoom; } void loadVideoFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("video")) { return; } const auto& vid = yaml["video"]; - if (vid.contains("fullscreen")) { - try { - video.fullscreen = vid["fullscreen"].get_value(); - } catch (...) {} - } - if (vid.contains("scale_mode")) { - try { - video.scale_mode = static_cast(vid["scale_mode"].get_value()); - } catch (...) {} - } - if (vid.contains("vsync")) { - try { - video.vsync = vid["vsync"].get_value(); - } catch (...) {} - } - if (vid.contains("integer_scale")) { - try { - video.integer_scale = vid["integer_scale"].get_value(); - } catch (...) {} - } + parseField(vid, "fullscreen", video.fullscreen); + parseField(vid, "vsync", video.vsync); + parseField(vid, "integer_scale", video.integer_scale); + + int scale_mode_int = static_cast(video.scale_mode); + parseField(vid, "scale_mode", scale_mode_int); + video.scale_mode = static_cast(scale_mode_int); - // --- GPU --- if (vid.contains("gpu")) { const auto& gpu_node = vid["gpu"]; - if (gpu_node.contains("acceleration")) { - try { - video.gpu.acceleration = gpu_node["acceleration"].get_value(); - } catch (...) {} - } - if (gpu_node.contains("preferred_driver")) { - try { - video.gpu.preferred_driver = gpu_node["preferred_driver"].get_value(); - } catch (...) {} - } + parseField(gpu_node, "acceleration", video.gpu.acceleration); + parseField(gpu_node, "preferred_driver", video.gpu.preferred_driver); } - // --- Shader config --- if (vid.contains("shader")) { const auto& sh = vid["shader"]; - if (sh.contains("enabled")) { - try { - video.shader.enabled = sh["enabled"].get_value(); - } catch (...) {} - } - if (sh.contains("current_shader")) { - try { - auto s = sh["current_shader"].get_value(); - video.shader.current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX; - } catch (...) {} - } - if (sh.contains("postfx_preset")) { - try { - video.shader.current_postfx_preset_name = sh["postfx_preset"].get_value(); - } catch (...) {} - } - if (sh.contains("crtpi_preset")) { - try { - video.shader.current_crtpi_preset_name = sh["crtpi_preset"].get_value(); - } catch (...) {} + parseField(sh, "enabled", video.shader.enabled); + parseField(sh, "postfx_preset", video.shader.current_postfx_preset_name); + parseField(sh, "crtpi_preset", video.shader.current_crtpi_preset_name); + std::string shader_name; + parseField(sh, "current_shader", shader_name); + if (!shader_name.empty()) { + video.shader.current_shader = (shader_name == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX; } } - // --- Supersampling --- if (vid.contains("supersampling")) { const auto& ss_node = vid["supersampling"]; - if (ss_node.contains("enabled")) { - try { - video.supersampling.enabled = ss_node["enabled"].get_value(); - } catch (...) {} - } - if (ss_node.contains("linear_upscale")) { - try { - video.supersampling.linear_upscale = ss_node["linear_upscale"].get_value(); - } catch (...) {} - } - if (ss_node.contains("downscale_algo")) { - try { - video.supersampling.downscale_algo = ss_node["downscale_algo"].get_value(); - } catch (...) {} - } + parseField(ss_node, "enabled", video.supersampling.enabled); + parseField(ss_node, "linear_upscale", video.supersampling.linear_upscale); + parseField(ss_node, "downscale_algo", video.supersampling.downscale_algo); } } @@ -452,50 +388,30 @@ namespace Options { if (!yaml.contains("audio")) { return; } const auto& aud = yaml["audio"]; - if (aud.contains("enabled")) { - try { - audio.enabled = aud["enabled"].get_value(); - } catch (...) {} - } - if (aud.contains("volume")) { - try { - audio.volume = std::clamp(aud["volume"].get_value(), 0.0F, 1.0F); - } catch (...) {} - } + parseField(aud, "enabled", audio.enabled); + parseField(aud, "volume", audio.volume); + audio.volume = std::clamp(audio.volume, 0.0F, 1.0F); + if (aud.contains("music")) { const auto& mus = aud["music"]; - if (mus.contains("enabled")) { - try { - audio.music.enabled = mus["enabled"].get_value(); - } catch (...) {} - } - if (mus.contains("volume")) { - try { - audio.music.volume = std::clamp(mus["volume"].get_value(), 0.0F, 1.0F); - } catch (...) {} - } + parseField(mus, "enabled", audio.music.enabled); + parseField(mus, "volume", audio.music.volume); + audio.music.volume = std::clamp(audio.music.volume, 0.0F, 1.0F); } if (aud.contains("sound")) { const auto& snd = aud["sound"]; - if (snd.contains("enabled")) { - try { - audio.sound.enabled = snd["enabled"].get_value(); - } catch (...) {} - } - if (snd.contains("volume")) { - try { - audio.sound.volume = std::clamp(snd["volume"].get_value(), 0.0F, 1.0F); - } catch (...) {} - } + parseField(snd, "enabled", audio.sound.enabled); + parseField(snd, "volume", audio.sound.volume); + audio.sound.volume = std::clamp(audio.sound.volume, 0.0F, 1.0F); } } void loadLoadingFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("loading")) { return; } const auto& ld = yaml["loading"]; - parseBoolField(ld, "show", loading.show); - parseBoolField(ld, "show_resource_name", loading.show_resource_name); - parseBoolField(ld, "wait_for_input", loading.wait_for_input); + parseField(ld, "show", loading.show); + parseField(ld, "show_resource_name", loading.show_resource_name); + parseField(ld, "wait_for_input", loading.wait_for_input); } void loadGameFromYaml(const fkyaml::node& yaml) { @@ -503,37 +419,23 @@ namespace Options { const auto& game = yaml["game"]; if (game.contains("language")) { - try { - auto lang = static_cast(game["language"].get_value()); - if (lang == Lang::Code::ENGLISH || lang == Lang::Code::VALENCIAN || lang == Lang::Code::SPANISH) { - settings.language = lang; - } else { - settings.language = Lang::Code::ENGLISH; - } - pending_changes.new_language = settings.language; - } catch (...) {} + int lang_int = static_cast(settings.language); + parseField(game, "language", lang_int); + const auto LANG = static_cast(lang_int); + settings.language = (LANG == Lang::Code::ENGLISH || LANG == Lang::Code::VALENCIAN || LANG == Lang::Code::SPANISH) + ? LANG + : Lang::Code::ENGLISH; + pending_changes.new_language = settings.language; } if (game.contains("difficulty")) { - try { - settings.difficulty = static_cast(game["difficulty"].get_value()); - pending_changes.new_difficulty = settings.difficulty; - } catch (...) {} - } - if (game.contains("autofire")) { - try { - settings.autofire = game["autofire"].get_value(); - } catch (...) {} - } - if (game.contains("shutdown_enabled")) { - try { - settings.shutdown_enabled = game["shutdown_enabled"].get_value(); - } catch (...) {} - } - if (game.contains("params_file")) { - try { - settings.params_file = game["params_file"].get_value(); - } catch (...) {} + int diff_int = static_cast(settings.difficulty); + parseField(game, "difficulty", diff_int); + settings.difficulty = static_cast(diff_int); + pending_changes.new_difficulty = settings.difficulty; } + parseField(game, "autofire", settings.autofire); + parseField(game, "shutdown_enabled", settings.shutdown_enabled); + parseField(game, "params_file", settings.params_file); } void loadControllersFromYaml(const fkyaml::node& yaml) { @@ -543,25 +445,14 @@ namespace Options { size_t i = 0; for (const auto& ctrl : controllers) { if (i >= GamepadManager::size()) { break; } - if (ctrl.contains("name")) { - try { - gamepad_manager[i].name = ctrl["name"].get_value(); - } catch (...) {} - } - if (ctrl.contains("path")) { - try { - gamepad_manager[i].path = ctrl["path"].get_value(); - } catch (...) {} - } - if (ctrl.contains("player")) { - try { - int player_int = ctrl["player"].get_value(); - if (player_int == 1) { - gamepad_manager[i].player_id = Player::Id::PLAYER1; - } else if (player_int == 2) { - gamepad_manager[i].player_id = Player::Id::PLAYER2; - } - } catch (...) {} + parseField(ctrl, "name", gamepad_manager[i].name); + parseField(ctrl, "path", gamepad_manager[i].path); + int player_int = 0; + parseField(ctrl, "player", player_int); + if (player_int == 1) { + gamepad_manager[i].player_id = Player::Id::PLAYER1; + } else if (player_int == 2) { + gamepad_manager[i].player_id = Player::Id::PLAYER2; } ++i; } @@ -570,11 +461,9 @@ namespace Options { void loadKeyboardFromYaml(const fkyaml::node& yaml) { if (!yaml.contains("keyboard")) { return; } const auto& kb = yaml["keyboard"]; - if (kb.contains("player")) { - try { - keyboard.player_id = static_cast(kb["player"].get_value()); - } catch (...) {} - } + int player_int = static_cast(keyboard.player_id); + parseField(kb, "player", player_int); + keyboard.player_id = static_cast(player_int); } // Carga el fichero de configuración @@ -595,11 +484,7 @@ namespace Options { // Comprobar versión: si no coincide, regenerar config por defecto int file_version = 0; - if (yaml.contains("version")) { - try { - file_version = yaml["version"].get_value(); - } catch (...) {} - } + parseField(yaml, "version", file_version); if (file_version != Settings::CURRENT_CONFIG_VERSION) { std::cout << "Config version " << file_version << " != expected " << Settings::CURRENT_CONFIG_VERSION << ". Recreating defaults." << '\n'; init(); @@ -820,14 +705,14 @@ namespace Options { continue; } - const auto it = std::ranges::find_if(physical_gamepads, - [this, &desired_path, &assigned_instances](const auto& pg) { + const auto IT = std::ranges::find_if(physical_gamepads, + [&desired_path, &assigned_instances](const auto& pg) { return pg->path == desired_path && !isGamepadAssigned(pg, assigned_instances); }); - if (it != physical_gamepads.end()) { - gamepads_[i].instance = *it; - gamepads_[i].name = (*it)->name; - assigned_instances.push_back(*it); + if (IT != physical_gamepads.end()) { + gamepads_[i].instance = *IT; + gamepads_[i].name = (*IT)->name; + assigned_instances.push_back(*IT); } } } @@ -849,15 +734,15 @@ namespace Options { continue; } - const auto it = std::ranges::find_if(physical_gamepads, - [this, &desired_name, &assigned_instances](const auto& pg) { + const auto IT = std::ranges::find_if(physical_gamepads, + [&desired_name, &assigned_instances](const auto& pg) { return pg->name == desired_name && !isGamepadAssigned(pg, assigned_instances); }); - if (it != physical_gamepads.end()) { - gamepads_[i].instance = *it; - gamepads_[i].name = (*it)->name; - gamepads_[i].path = (*it)->path; - assigned_instances.push_back(*it); + if (IT != physical_gamepads.end()) { + gamepads_[i].instance = *IT; + gamepads_[i].name = (*IT)->name; + gamepads_[i].path = (*IT)->path; + assigned_instances.push_back(*IT); } } } @@ -871,15 +756,15 @@ namespace Options { continue; } - const auto it = std::ranges::find_if(physical_gamepads, - [this, &assigned_instances](const auto& pg) { + const auto IT = std::ranges::find_if(physical_gamepads, + [&assigned_instances](const auto& pg) { return !isGamepadAssigned(pg, assigned_instances); }); - if (it != physical_gamepads.end()) { - gamepads_[i].instance = *it; - gamepads_[i].name = (*it)->name; - gamepads_[i].path = (*it)->path; - assigned_instances.push_back(*it); + if (IT != physical_gamepads.end()) { + gamepads_[i].instance = *IT; + gamepads_[i].name = (*IT)->name; + gamepads_[i].path = (*IT)->path; + assigned_instances.push_back(*IT); } } } diff --git a/source/game/scenes/credits.cpp b/source/game/scenes/credits.cpp index b1ccd73..968681e 100644 --- a/source/game/scenes/credits.cpp +++ b/source/game/scenes/credits.cpp @@ -726,7 +726,7 @@ void Credits::drawBorderRect() { return; // no dibujar } - const Color COLOR = color_.LIGHTEN(); + const Color COLOR = color_.lighten(); SDL_Renderer* rdr = Screen::get()->getRenderer(); SDL_SetRenderDrawColor(rdr, COLOR.r, COLOR.g, COLOR.b, 0xFF); diff --git a/source/game/scenes/game.cpp b/source/game/scenes/game.cpp index 988542a..54ed0e4 100644 --- a/source/game/scenes/game.cpp +++ b/source/game/scenes/game.cpp @@ -16,7 +16,6 @@ #include "core/audio/audio.hpp" // Para Audio #include "core/input/global_inputs.hpp" // Para check #include "core/input/input.hpp" // Para Input -#include "core/input/input_types.hpp" // Para InputAction #include "core/input/pause_manager.hpp" // Para PauseManager #include "core/locale/lang.hpp" // Para getText #include "core/rendering/background.hpp" // Para Background @@ -341,7 +340,7 @@ void Game::updateStage() { // Modificar color de fondo en la última fase if (current_stage_index == total_stages - 1) { // Última fase - background_->setColor(Color(0xdd, 0x19, 0x1d).DARKEN()); + background_->setColor(Color(0xdd, 0x19, 0x1d).darken()); background_->setAlpha(96); } } @@ -2183,10 +2182,10 @@ void Game::handleDebugEvents(const SDL_Event& event) { break; } case SDLK_8: { - const auto it = std::ranges::find_if(players_, + const auto IT = std::ranges::find_if(players_, [](const auto& player) { return player->isPlaying(); }); - if (it != players_.end()) { - createItem(ItemType::COFFEE_MACHINE, (*it)->getPosX(), param.game.game_area.rect.y - Item::COFFEE_MACHINE_HEIGHT); + if (IT != players_.end()) { + createItem(ItemType::COFFEE_MACHINE, (*IT)->getPosX(), param.game.game_area.rect.y - Item::COFFEE_MACHINE_HEIGHT); coffee_machine_enabled_ = true; } break; diff --git a/source/game/scenes/game.hpp b/source/game/scenes/game.hpp index 9f2ea99..4a0f801 100644 --- a/source/game/scenes/game.hpp +++ b/source/game/scenes/game.hpp @@ -2,10 +2,11 @@ #include // Para SDL_Event, SDL_Renderer, SDL_Texture, Uint64 -#include // Para list -#include // Para shared_ptr, unique_ptr -#include // Para string -#include // Para vector +#include // Para std::uint8_t +#include // Para list +#include // Para shared_ptr, unique_ptr +#include // Para string +#include // Para vector #include "core/system/demo.hpp" // for Demo #include "game/entities/bullet.hpp" // for Bullet @@ -31,7 +32,7 @@ class Texture; struct Path; namespace Difficulty { - enum class Code; + enum class Code : std::uint8_t; } // namespace Difficulty // --- Clase Game: núcleo principal del gameplay --- @@ -71,7 +72,7 @@ class Game { using Players = std::vector>; // --- Enums --- - enum class State { + enum class State : std::uint8_t { FADE_IN, // Transición de entrada ENTERING_PLAYER, // Jugador entrando SHOWING_GET_READY_MESSAGE, // Mostrando mensaje de preparado diff --git a/source/game/scenes/hiscore_table.cpp b/source/game/scenes/hiscore_table.cpp index f53ce43..38a9780 100644 --- a/source/game/scenes/hiscore_table.cpp +++ b/source/game/scenes/hiscore_table.cpp @@ -178,22 +178,17 @@ void HiScoreTable::updateFade(float delta_time) { // Convierte un entero a un string con separadores de miles auto HiScoreTable::format(int number) -> std::string { - const std::string SEPARATOR = "."; const std::string SCORE = std::to_string(number); + const size_t SIZE = SCORE.size(); - auto index = static_cast(SCORE.size()) - 1; std::string result; - auto i = 0; - while (index >= 0) { - result = SCORE.at(index) + result; - index--; - i++; - if (i == 3) { - i = 0; - result = SEPARATOR + result; + result.reserve(SIZE + (SIZE / 3)); + for (size_t i = 0; i < SIZE; ++i) { + if (i > 0 && (SIZE - i) % 3 == 0) { + result += '.'; } + result += SCORE[i]; } - return result; } @@ -215,7 +210,7 @@ void HiScoreTable::createSprites() { const int FIRST_LINE = (param.game.height - SIZE) / 2; // Crea el sprite para el texto de cabecera - header_ = std::make_unique(header_text->writeDXToTexture(Text::COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.INVERSE().LIGHTEN(25))); + header_ = std::make_unique(header_text->writeDXToTexture(Text::COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.inverse().lighten(25))); header_->setPosition(param.game.game_area.center_x - (header_->getWidth() / 2), FIRST_LINE); // Crea los sprites para las entradas en la tabla de puntuaciones @@ -228,13 +223,14 @@ void HiScoreTable::createSprites() { const auto SCORE = format(Options::settings.hi_score_table.at(i).score); const auto NUM_DOTS = ENTRY_LENGTH - Options::settings.hi_score_table.at(i).name.size() - SCORE.size(); const auto* const ONE_CC = Options::settings.hi_score_table.at(i).one_credit_complete ? " }" : ""; - std::string dots; - for (int j = 0; std::cmp_less(j, NUM_DOTS); ++j) { - dots = dots + "."; - } - const auto LINE = TABLE_POSITION + Options::settings.hi_score_table.at(i).name + dots + SCORE + ONE_CC; + const std::string DOTS(NUM_DOTS, '.'); + std::string line = TABLE_POSITION; + line += Options::settings.hi_score_table.at(i).name; + line += DOTS; + line += SCORE; + line += ONE_CC; - entry_names_.emplace_back(std::make_shared(entry_text->writeDXToTexture(Text::SHADOW, LINE, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT))); + entry_names_.emplace_back(std::make_shared(entry_text->writeDXToTexture(Text::SHADOW, line, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT))); const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2; const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize(); const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER; @@ -367,10 +363,10 @@ auto HiScoreTable::getEntryColor(int counter) -> Color { // Inicializa los colores de las entradas void HiScoreTable::iniEntryColors() { entry_colors_.clear(); - entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(75)); - entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(50)); - entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(25)); - entry_colors_.emplace_back(background_fade_color_.INVERSE()); + entry_colors_.emplace_back(background_fade_color_.inverse().lighten(75)); + entry_colors_.emplace_back(background_fade_color_.inverse().lighten(50)); + entry_colors_.emplace_back(background_fade_color_.inverse().lighten(25)); + entry_colors_.emplace_back(background_fade_color_.inverse()); } // Hace brillar los nombres de la tabla de records @@ -387,7 +383,7 @@ void HiScoreTable::glowEntryNames() { // Gestiona el contador void HiScoreTable::updateCounter() { if (elapsed_time_ >= BACKGROUND_CHANGE_S && !hiscore_flags_.background_changed) { - background_->setColor(background_fade_color_.DARKEN()); + background_->setColor(background_fade_color_.darken()); background_->setAlpha(96); hiscore_flags_.background_changed = true; } diff --git a/source/game/scenes/intro.cpp b/source/game/scenes/intro.cpp index f4d3768..2a9cadc 100644 --- a/source/game/scenes/intro.cpp +++ b/source/game/scenes/intro.cpp @@ -392,17 +392,17 @@ void Intro::initSprites() { const CardConfig CARD_CONFIGS[] = { // 0: Entra desde la izquierda. La 1 entra desde la derecha → sale empujada hacia la izquierda - {-CARD_WIDTH, Y_DEST - 20.0F, CARD_ANGLE_0, -S, S * 0.1F, -A, 0.0F, -R}, + {.entry_x = -CARD_WIDTH, .entry_y = Y_DEST - 20.0F, .entry_angle = CARD_ANGLE_0, .exit_vx = -S, .exit_vy = S * 0.1F, .exit_ax = -A, .exit_ay = 0.0F, .exit_rotation = -R}, // 1: Entra desde la derecha. La 2 entra desde arriba → sale empujada hacia abajo - {W + CARD_WIDTH, Y_DEST + 15.0F, CARD_ANGLE_1, S * 0.15F, S, 0.0F, A, R * 1.1}, + {.entry_x = W + CARD_WIDTH, .entry_y = Y_DEST + 15.0F, .entry_angle = CARD_ANGLE_1, .exit_vx = S * 0.15F, .exit_vy = S, .exit_ax = 0.0F, .exit_ay = A, .exit_rotation = R * 1.1}, // 2: Entra desde arriba. La 3 entra desde abajo → sale empujada hacia arriba - {X_DEST + 30.0F, -CARD_HEIGHT, CARD_ANGLE_2, -S * 0.15F, -S, 0.0F, -A, -R * 0.9}, + {.entry_x = X_DEST + 30.0F, .entry_y = -CARD_HEIGHT, .entry_angle = CARD_ANGLE_2, .exit_vx = -S * 0.15F, .exit_vy = -S, .exit_ax = 0.0F, .exit_ay = -A, .exit_rotation = -R * 0.9}, // 3: Entra desde abajo. La 4 entra desde arriba-izquierda → sale empujada hacia abajo-derecha - {X_DEST - 25.0F, H + CARD_HEIGHT, CARD_ANGLE_3, S * 0.8F, S * 0.6F, A * 0.5F, A * 0.4F, R}, + {.entry_x = X_DEST - 25.0F, .entry_y = H + CARD_HEIGHT, .entry_angle = CARD_ANGLE_3, .exit_vx = S * 0.8F, .exit_vy = S * 0.6F, .exit_ax = A * 0.5F, .exit_ay = A * 0.4F, .exit_rotation = R}, // 4: Entra desde arriba-izquierda. La 5 entra desde derecha-abajo → sale empujada hacia arriba-izquierda - {-CARD_WIDTH * 0.5F, -CARD_HEIGHT, CARD_ANGLE_4, -S * 0.7F, -S * 0.5F, -A * 0.5F, -A * 0.3F, -R * 1.2}, + {.entry_x = -CARD_WIDTH * 0.5F, .entry_y = -CARD_HEIGHT, .entry_angle = CARD_ANGLE_4, .exit_vx = -S * 0.7F, .exit_vy = -S * 0.5F, .exit_ax = -A * 0.5F, .exit_ay = -A * 0.3F, .exit_rotation = -R * 1.2}, // 5: Entra desde la derecha-abajo. Última: sale hacia la izquierda suave (viento) - {W + CARD_WIDTH, H * 0.6F, CARD_ANGLE_5, -S * 0.6F, -S * 0.1F, -A * 0.5F, 0.0F, -R * 0.7}, + {.entry_x = W + CARD_WIDTH, .entry_y = H * 0.6F, .entry_angle = CARD_ANGLE_5, .exit_vx = -S * 0.6F, .exit_vy = -S * 0.1F, .exit_ax = -A * 0.5F, .exit_ay = 0.0F, .exit_rotation = -R * 0.7}, }; // Inicializa los CardSprites @@ -534,15 +534,15 @@ void Intro::updatePostState() { if (ELAPSED_TIME >= POST_BG_STOP_DELAY_S) { tiled_bg_->stopGracefully(); - if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) { - bg_color_ = bg_color_.APPROACH_TO(param.title.bg_color, 1); + if (!bg_color_.isEqualTo(param.title.bg_color)) { + bg_color_ = bg_color_.approachTo(param.title.bg_color, 1); } tiled_bg_->setColor(bg_color_); } // Cambia de estado si el fondo se ha detenido y recuperado el color - if (tiled_bg_->isStopped() && bg_color_.IS_EQUAL_TO(param.title.bg_color)) { + if (tiled_bg_->isStopped() && bg_color_.isEqualTo(param.title.bg_color)) { post_state_ = PostState::END; state_start_time_ = SDL_GetTicks() / 1000.0F; } diff --git a/source/game/scenes/intro.hpp b/source/game/scenes/intro.hpp index 497435d..4cdd31c 100644 --- a/source/game/scenes/intro.hpp +++ b/source/game/scenes/intro.hpp @@ -2,8 +2,9 @@ #include // Para Uint32, Uint64 -#include // Para unique_ptr -#include // Para vector +#include // Para std::uint8_t +#include // Para unique_ptr +#include // Para vector #include "core/rendering/sprite/card_sprite.hpp" // Para CardSprite #include "core/rendering/tiled_bg.hpp" // Para TiledBG @@ -79,12 +80,12 @@ class Intro { static constexpr double CARD_ANGLE_5 = -7.0; // --- Estados internos --- - enum class State { + enum class State : std::uint8_t { SCENES, POST, }; - enum class PostState { + enum class PostState : std::uint8_t { STOP_BG, END, }; diff --git a/source/game/scenes/preload.hpp b/source/game/scenes/preload.hpp index 86b5afa..2bcc592 100644 --- a/source/game/scenes/preload.hpp +++ b/source/game/scenes/preload.hpp @@ -14,6 +14,6 @@ class Preload { ~Preload() = default; // --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS --- - void iterate(); // Repinta la barra de progreso - void handleEvent(const SDL_Event& event); // Detecta pulsación en modo wait_for_input + static void iterate(); // Repinta la barra de progreso + static void handleEvent(const SDL_Event& event); // Detecta pulsación en modo wait_for_input }; diff --git a/source/game/scenes/title.cpp b/source/game/scenes/title.cpp index 646bda9..9ba4e61 100644 --- a/source/game/scenes/title.cpp +++ b/source/game/scenes/title.cpp @@ -2,14 +2,12 @@ #include // Para SDL_GetTicks, SDL_Event, SDL_Keycode, SDL_PollEvent, SDLK_A, SDLK_C, SDLK_D, SDLK_F, SDLK_S, SDLK_V, SDLK_X, SDLK_Z, SDL_EventType, Uint64 -#include // Para __find_if_fn, find_if #include // Para basic_string, char_traits, operator+, to_string, string #include // Para vector #include "core/audio/audio.hpp" // Para Audio #include "core/input/global_inputs.hpp" // Para check #include "core/input/input.hpp" // Para Input -#include "core/input/input_types.hpp" // Para InputAction #include "core/locale/lang.hpp" // Para getText #include "core/rendering/fade.hpp" // Para Fade #include "core/rendering/screen.hpp" // Para Screen @@ -300,6 +298,9 @@ void Title::updateFade() { Section::options = Section::Options::GAME_PLAY_BOTH; Audio::get()->stopMusic(); break; + + default: + break; // COMBO és un bitmask 2-bit (0..3); arribar ací és impossible. } } } diff --git a/source/game/scenes/title.hpp b/source/game/scenes/title.hpp index 04e09c1..fa18d98 100644 --- a/source/game/scenes/title.hpp +++ b/source/game/scenes/title.hpp @@ -2,6 +2,7 @@ #include // Para SDL_Keycode, SDL_Event, Uint64 +#include // Para std::uint8_t #include // Para shared_ptr, unique_ptr #include // Para string_view #include // Para vector @@ -69,7 +70,7 @@ class Title { static constexpr bool ALLOW_TITLE_ANIMATION_SKIP = false; // Permite saltar la animación del título // --- Enums --- - enum class State { + enum class State : std::uint8_t { LOGO_ANIMATING, // El logo está animándose LOGO_FINISHED, // El logo ha terminado de animarse START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start diff --git a/source/game/ui/menu_option.hpp b/source/game/ui/menu_option.hpp index bbf3c19..8e817ba 100644 --- a/source/game/ui/menu_option.hpp +++ b/source/game/ui/menu_option.hpp @@ -1,7 +1,9 @@ #pragma once #include // Para max, clamp +#include // Para std::lround #include // Para size_t +#include // Para std::uint8_t #include // Para function #include // Para accumulate #include // Para allocator, string, basic_string, to_string, operator==, char_traits @@ -16,7 +18,7 @@ class MenuOption { public: // --- Enums --- - enum class Behavior { + enum class Behavior : std::uint8_t { ADJUST, // Solo puede ajustar valor (como IntOption, BoolOption, ListOption) SELECT, // Solo puede ejecutar acción (como ActionOption, FolderOption) BOTH // Puede tanto ajustar como ejecutar acción (como ActionListOption) @@ -119,11 +121,11 @@ class VolumeOption : public MenuOption { [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; } [[nodiscard]] auto getValueAsString() const -> std::string override { - int pct = static_cast(*linked_variable_ * 100.0F + 0.5F); + int pct = static_cast(std::lround(*linked_variable_ * 100.0F)); return std::to_string(pct); } void adjustValue(bool adjust_up) override { - int current = static_cast(*linked_variable_ * 100.0F + 0.5F); + int current = static_cast(std::lround(*linked_variable_ * 100.0F)); int new_value = std::clamp(current + (adjust_up ? step_value_ : -step_value_), 0, 100); *linked_variable_ = static_cast(new_value) / 100.0F; } diff --git a/source/game/ui/menu_renderer.cpp b/source/game/ui/menu_renderer.cpp index 4099e0e..7171372 100644 --- a/source/game/ui/menu_renderer.cpp +++ b/source/game/ui/menu_renderer.cpp @@ -69,7 +69,7 @@ void MenuRenderer::render(const ServiceMenu* menu_state) { SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_); // Dibuja el borde - const Color BORDER_COLOR = param.service_menu.title_color.DARKEN(); + const Color BORDER_COLOR = param.service_menu.title_color.darken(); SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255); SDL_RenderRect(Screen::get()->getRenderer(), &rect_); SDL_RenderRect(Screen::get()->getRenderer(), &border_rect_); @@ -111,7 +111,7 @@ void MenuRenderer::render(const ServiceMenu* menu_state) { // Dibuja las opciones y = options_y_; const auto& option_pairs = menu_state->getOptionPairs(); - const float ROW_HEIGHT = static_cast(options_height_ + options_padding_); + const auto ROW_HEIGHT = static_cast(options_height_ + options_padding_); for (size_t i = 0; i < option_pairs.size(); ++i) { const bool IS_SELECTED = (i == menu_state->getSelectedIndex()); diff --git a/source/game/ui/menu_renderer.hpp b/source/game/ui/menu_renderer.hpp index b8849a0..7859c2e 100644 --- a/source/game/ui/menu_renderer.hpp +++ b/source/game/ui/menu_renderer.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -17,7 +18,7 @@ class Text; class MenuRenderer { public: // --- Nuevo: Enum para el modo de posicionamiento --- - enum class PositionMode { + enum class PositionMode : std::uint8_t { CENTERED, // La ventana se centra en el punto especificado FIXED // La esquina superior izquierda coincide con el punto }; @@ -94,7 +95,7 @@ class MenuRenderer { } resize_animation_; struct ShowHideAnimation { - enum class Type { NONE, + enum class Type : std::uint8_t { NONE, SHOWING, HIDING }; Type type = Type::NONE; diff --git a/source/game/ui/notifier.hpp b/source/game/ui/notifier.hpp index 489d94f..f81a2c0 100644 --- a/source/game/ui/notifier.hpp +++ b/source/game/ui/notifier.hpp @@ -2,9 +2,10 @@ #include // Para SDL_FRect, SDL_Renderer -#include // Para shared_ptr -#include // Para basic_string, string -#include // Para vector +#include // Para std::uint8_t +#include // Para shared_ptr +#include // Para basic_string, string +#include // Para vector #include "utils/color.hpp" // Para stringInVector, Color #include "utils/utils.hpp" @@ -17,7 +18,7 @@ class Texture; class Notifier { public: // --- Enums --- - enum class Position { + enum class Position : std::uint8_t { TOP, // Parte superior BOTTOM, // Parte inferior LEFT, // Lado izquierdo @@ -46,14 +47,14 @@ class Notifier { static constexpr float ANIMATION_SPEED_PX_PER_S = 60.0F; // Velocidad de animación (1 pixel/frame @ 60fps) // --- Enums privados --- - enum class State { + enum class State : std::uint8_t { RISING, // Apareciendo STAY, // Visible VANISHING, // Desapareciendo FINISHED, // Terminada }; - enum class Shape { + enum class Shape : std::uint8_t { ROUNDED, // Forma redondeada SQUARED, // Forma cuadrada }; diff --git a/source/game/ui/service_menu.cpp b/source/game/ui/service_menu.cpp index 527f3d1..27d72f6 100644 --- a/source/game/ui/service_menu.cpp +++ b/source/game/ui/service_menu.cpp @@ -8,7 +8,6 @@ #include "core/audio/audio.hpp" // Para Audio #include "core/input/define_buttons.hpp" // Para DefineButtons #include "core/input/input.hpp" // Para Input -#include "core/input/input_types.hpp" // Para InputAction #include "core/locale/lang.hpp" // Para getText, getCodeFromName, getNameFromCode #include "core/rendering/screen.hpp" // Para Screen #include "core/resources/resource.hpp" // Para Resource @@ -252,9 +251,9 @@ void ServiceMenu::applySettingsSettings() { } auto ServiceMenu::getOptionByCaption(const std::string& caption) const -> MenuOption* { - const auto it = std::ranges::find_if(options_, + const auto IT = std::ranges::find_if(options_, [&caption](const auto& option) { return option->getCaption() == caption; }); - return it != options_.end() ? it->get() : nullptr; + return IT != options_.end() ? IT->get() : nullptr; } // --- Getters y otros --- @@ -279,8 +278,16 @@ auto ServiceMenu::countOptionsInGroup(SettingsGroup group) const -> size_t { // Inicializa todas las opciones del menú void ServiceMenu::initializeOptions() { options_.clear(); + addControlsOptions(); + addVideoOptions(); + addAudioOptions(); + addSettingsOptions(); + addSystemOptions(); + addMainMenuOptions(); + setHiddenOptions(); +} - // CONTROLS - Usando ActionListOption para mandos +void ServiceMenu::addControlsOptions() { options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] CONTROLLER1"), SettingsGroup::CONTROLS, @@ -292,7 +299,6 @@ void ServiceMenu::initializeOptions() { Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER1, Input::get()->getGamepadByName(val), val); }, [this]() -> void { - // Acción: configurar botones del mando del jugador 1 auto* gamepad = &Options::gamepad_manager.getGamepad(Player::Id::PLAYER1); if (gamepad->instance != nullptr) { define_buttons_->enable(gamepad); @@ -310,14 +316,12 @@ void ServiceMenu::initializeOptions() { Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER2, Input::get()->getGamepadByName(val), val); }, [this]() -> void { - // Acción: configurar botones del mando del jugador 2 auto* gamepad = &Options::gamepad_manager.getGamepad(Player::Id::PLAYER2); if (gamepad->instance != nullptr) { define_buttons_->enable(gamepad); } })); - // CONTROLS - Opción para teclado (solo lista, sin acción) options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] KEYBOARD"), SettingsGroup::CONTROLS, @@ -325,26 +329,22 @@ void ServiceMenu::initializeOptions() { Lang::getText("[SERVICE_MENU] PLAYER1"), Lang::getText("[SERVICE_MENU] PLAYER2")}, []() -> std::string { - // Devolver el jugador actual asignado al teclado return Options::playerIdToString(Options::getPlayerWhoUsesKeyboard()); }, [](const std::string& val) -> void { - // Asignar el teclado al jugador seleccionado Options::keyboard.assignTo(Options::stringToPlayerId(val)); })); - // CONTROLS - Acción para intercambiar mandos options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] SWAP_CONTROLLERS"), SettingsGroup::CONTROLS, [this]() -> void { Options::gamepad_manager.swapPlayers(); - adjustListValues(); // Sincroniza el valor de las opciones de lista (como MANDO1) con los datos reales - updateOptionPairs(); // Actualiza los pares de texto que se van a dibujar + adjustListValues(); + updateOptionPairs(); - // Feedback visual: anima el intercambio de los valores entre - // las filas de MANDO 1 y MANDO 2, imprescindible cuando los dos - // mandos tienen el mismo nombre (el texto no cambia al swap). + // Feedback visual: anima el intercambio de las filas de MANDO 1 y MANDO 2, + // imprescindible cuando ambos mandos tienen el mismo nombre (el texto no cambia). const std::string CAPTION1 = Lang::getText("[SERVICE_MENU] CONTROLLER1"); const std::string CAPTION2 = Lang::getText("[SERVICE_MENU] CONTROLLER2"); size_t idx1 = display_options_.size(); @@ -358,8 +358,9 @@ void ServiceMenu::initializeOptions() { renderer_->startSwapAnimation(idx1, idx2); } })); +} - // VIDEO +void ServiceMenu::addVideoOptions() { options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] FULLSCREEN"), SettingsGroup::VIDEO, @@ -373,76 +374,8 @@ void ServiceMenu::initializeOptions() { Options::window.max_zoom, 1)); - // Shader: Desactivat / PostFX / CrtPi - { - std::string disabled_text = Lang::getText("[SERVICE_MENU] SHADER_DISABLED"); - std::vector shader_values = {disabled_text, "PostFX", "CrtPi"}; - auto shader_getter = [disabled_text]() -> std::string { - // NOLINTNEXTLINE(performance-no-automatic-move) -- captura por valor en lambda const, no se puede mover - if (!Options::video.shader.enabled) { return disabled_text; } - return (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) ? "CrtPi" : "PostFX"; - }; - auto shader_setter = [disabled_text](const std::string& val) { - if (val == disabled_text) { - Options::video.shader.enabled = false; - } else { - Options::video.shader.enabled = true; - const auto TYPE = (val == "CrtPi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX; - Options::video.shader.current_shader = TYPE; - auto* screen = Screen::get(); - if (screen != nullptr) { - screen->applySettings(); - } - } - Screen::initShaders(); - }; - options_.push_back(std::make_unique( - Lang::getText("[SERVICE_MENU] SHADER"), - SettingsGroup::VIDEO, - shader_values, - shader_getter, - shader_setter)); - } - - // Preset: muestra nombre, cicla circularmente entre presets del shader activo - { - auto preset_getter = []() -> std::string { - if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) { - if (Options::crtpi_presets.empty()) { return ""; } - return Options::crtpi_presets.at(static_cast(Options::video.shader.current_crtpi_preset)).name; - } - if (Options::postfx_presets.empty()) { return ""; } - return Options::postfx_presets.at(static_cast(Options::video.shader.current_postfx_preset)).name; - }; - auto preset_adjuster = [](bool up) { - if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) { - if (Options::crtpi_presets.empty()) { return; } - const int SIZE = static_cast(Options::crtpi_presets.size()); - Options::video.shader.current_crtpi_preset = up - ? (Options::video.shader.current_crtpi_preset + 1) % SIZE - : (Options::video.shader.current_crtpi_preset + SIZE - 1) % SIZE; - } else { - if (Options::postfx_presets.empty()) { return; } - const int SIZE = static_cast(Options::postfx_presets.size()); - Options::video.shader.current_postfx_preset = up - ? (Options::video.shader.current_postfx_preset + 1) % SIZE - : (Options::video.shader.current_postfx_preset + SIZE - 1) % SIZE; - } - Screen::initShaders(); - }; - auto preset_max_width = [](const Text* text) -> int { - const auto presets_length = [text](int max_w, const auto& p) { return std::max(max_w, text->length(p.name, -2)); }; - int max_w = std::accumulate(Options::postfx_presets.begin(), Options::postfx_presets.end(), 0, presets_length); - return std::accumulate(Options::crtpi_presets.begin(), Options::crtpi_presets.end(), max_w, presets_length); - }; - - options_.push_back(std::make_unique( - Lang::getText("[SERVICE_MENU] SHADER_PRESET"), - SettingsGroup::VIDEO, - preset_getter, - preset_adjuster, - preset_max_width)); - } + addVideoShaderOption(); + addVideoPresetOption(); options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] SUPERSAMPLING"), @@ -459,25 +392,97 @@ void ServiceMenu::initializeOptions() { SettingsGroup::VIDEO, &Options::video.integer_scale)); - // FILTER: Nearest / Linear (solo visible en el fallback SDL, sin GPU acelerada) - { - std::vector filter_values = {"Nearest", "Linear"}; - auto filter_getter = []() -> std::string { - return (Options::video.scale_mode == SDL_SCALEMODE_LINEAR) ? "Linear" : "Nearest"; - }; - auto filter_setter = [](const std::string& val) { - Options::video.scale_mode = (val == "Linear") ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST; - if (Screen::get() != nullptr) { Screen::get()->applyFilter(); } - }; - options_.push_back(std::make_unique( - Lang::getText("[SERVICE_MENU] FILTER"), - SettingsGroup::VIDEO, - filter_values, - filter_getter, - filter_setter)); - } + addVideoFilterOption(); +} - // AUDIO +void ServiceMenu::addVideoShaderOption() { + std::string disabled_text = Lang::getText("[SERVICE_MENU] SHADER_DISABLED"); + std::vector shader_values = {disabled_text, "PostFX", "CrtPi"}; + auto shader_getter = [disabled_text]() -> std::string { + // NOLINTNEXTLINE(performance-no-automatic-move) -- captura por valor en lambda const, no se puede mover + if (!Options::video.shader.enabled) { return disabled_text; } + return (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) ? "CrtPi" : "PostFX"; + }; + auto shader_setter = [disabled_text](const std::string& val) { + if (val == disabled_text) { + Options::video.shader.enabled = false; + } else { + Options::video.shader.enabled = true; + const auto TYPE = (val == "CrtPi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX; + Options::video.shader.current_shader = TYPE; + auto* screen = Screen::get(); + if (screen != nullptr) { + screen->applySettings(); + } + } + Screen::initShaders(); + }; + options_.push_back(std::make_unique( + Lang::getText("[SERVICE_MENU] SHADER"), + SettingsGroup::VIDEO, + shader_values, + shader_getter, + shader_setter)); +} + +void ServiceMenu::addVideoPresetOption() { + auto preset_getter = []() -> std::string { + if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) { + if (Options::crtpi_presets.empty()) { return ""; } + return Options::crtpi_presets.at(static_cast(Options::video.shader.current_crtpi_preset)).name; + } + if (Options::postfx_presets.empty()) { return ""; } + return Options::postfx_presets.at(static_cast(Options::video.shader.current_postfx_preset)).name; + }; + auto preset_adjuster = [](bool up) { + if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) { + if (Options::crtpi_presets.empty()) { return; } + const int SIZE = static_cast(Options::crtpi_presets.size()); + Options::video.shader.current_crtpi_preset = up + ? (Options::video.shader.current_crtpi_preset + 1) % SIZE + : (Options::video.shader.current_crtpi_preset + SIZE - 1) % SIZE; + } else { + if (Options::postfx_presets.empty()) { return; } + const int SIZE = static_cast(Options::postfx_presets.size()); + Options::video.shader.current_postfx_preset = up + ? (Options::video.shader.current_postfx_preset + 1) % SIZE + : (Options::video.shader.current_postfx_preset + SIZE - 1) % SIZE; + } + Screen::initShaders(); + }; + auto preset_max_width = [](const Text* text) -> int { + const auto PRESETS_LENGTH = [text](int max_w, const auto& p) { return std::max(max_w, text->length(p.name, -2)); }; + int max_w = std::accumulate(Options::postfx_presets.begin(), Options::postfx_presets.end(), 0, PRESETS_LENGTH); + return std::accumulate(Options::crtpi_presets.begin(), Options::crtpi_presets.end(), max_w, PRESETS_LENGTH); + }; + + options_.push_back(std::make_unique( + Lang::getText("[SERVICE_MENU] SHADER_PRESET"), + SettingsGroup::VIDEO, + preset_getter, + preset_adjuster, + preset_max_width)); +} + +void ServiceMenu::addVideoFilterOption() { + // FILTER: Nearest / Linear (solo visible en el fallback SDL, sin GPU acelerada) + std::vector filter_values = {"Nearest", "Linear"}; + auto filter_getter = []() -> std::string { + return (Options::video.scale_mode == SDL_SCALEMODE_LINEAR) ? "Linear" : "Nearest"; + }; + auto filter_setter = [](const std::string& val) { + Options::video.scale_mode = (val == "Linear") ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST; + if (Screen::get() != nullptr) { Screen::get()->applyFilter(); } + }; + options_.push_back(std::make_unique( + Lang::getText("[SERVICE_MENU] FILTER"), + SettingsGroup::VIDEO, + filter_values, + filter_getter, + filter_setter)); +} + +void ServiceMenu::addAudioOptions() { options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] AUDIO"), SettingsGroup::AUDIO, @@ -500,8 +505,9 @@ void ServiceMenu::initializeOptions() { SettingsGroup::AUDIO, &Options::audio.sound.volume, 5)); +} - // SETTINGS +void ServiceMenu::addSettingsOptions() { options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] AUTOFIRE"), SettingsGroup::SETTINGS, @@ -541,8 +547,9 @@ void ServiceMenu::initializeOptions() { Lang::getText("[SERVICE_MENU] ENABLE_SHUTDOWN"), SettingsGroup::SETTINGS, &Options::settings.shutdown_enabled)); +} - // SYSTEM +void ServiceMenu::addSystemOptions() { options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] RESET"), SettingsGroup::SYSTEM, @@ -567,8 +574,9 @@ void ServiceMenu::initializeOptions() { Section::options = Section::Options::SHUTDOWN; }, !Options::settings.shutdown_enabled)); +} - // MAIN MENU +void ServiceMenu::addMainMenuOptions() { options_.push_back(std::make_unique( Lang::getText("[SERVICE_MENU] CONTROLS"), SettingsGroup::MAIN, @@ -593,9 +601,6 @@ void ServiceMenu::initializeOptions() { Lang::getText("[SERVICE_MENU] SYSTEM"), SettingsGroup::MAIN, SettingsGroup::SYSTEM)); - - // Oculta opciones según configuración - setHiddenOptions(); } // Sincroniza los valores de las opciones tipo lista diff --git a/source/game/ui/service_menu.hpp b/source/game/ui/service_menu.hpp index 3536e06..8facf81 100644 --- a/source/game/ui/service_menu.hpp +++ b/source/game/ui/service_menu.hpp @@ -5,7 +5,6 @@ #include // Para size_t #include // Para std::uint8_t #include // Para function -#include // Para pair #include // Para unique_ptr #include // Para basic_string, string #include // Para pair @@ -104,6 +103,15 @@ class ServiceMenu { void updateDisplayOptions(); void updateOptionPairs(); void initializeOptions(); + void addControlsOptions(); // CONTROLS: mandos 1/2, teclat, swap + void addVideoOptions(); // VIDEO: orquestra els blocs de vídeo + void addVideoShaderOption(); // VIDEO: tria de shader (Disabled/PostFX/CrtPi) + void addVideoPresetOption(); // VIDEO: cicla presets del shader actiu + void addVideoFilterOption(); // VIDEO: filtre Nearest/Linear (fallback SDL) + void addAudioOptions(); // AUDIO: enabled + tres volums + void addSettingsOptions(); // SETTINGS: autofire, idioma, dificultat, shutdown + void addSystemOptions(); // SYSTEM: reset, quit, shutdown + void addMainMenuOptions(); // MAIN: carpetes de menú void updateMenu(); void applySettings(); void applyControlsSettings(); diff --git a/source/main.cpp b/source/main.cpp index 7d5ff49..4f7ec68 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -12,16 +12,16 @@ Actualizando a la versión "Arcade Edition" en 08/05/2024 #include "core/system/director.hpp" // Para Director -SDL_AppResult SDL_AppInit(void** appstate, int /*argc*/, char** /*argv*/) { +auto SDL_AppInit(void** appstate, int /*argc*/, char** /*argv*/) -> SDL_AppResult { *appstate = new Director(); return SDL_APP_CONTINUE; } -SDL_AppResult SDL_AppIterate(void* appstate) { +auto SDL_AppIterate(void* appstate) -> SDL_AppResult { return static_cast(appstate)->iterate(); } -SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { +auto SDL_AppEvent(void* appstate, SDL_Event* event) -> SDL_AppResult { return static_cast(appstate)->handleEvent(*event); } diff --git a/source/utils/color.cpp b/source/utils/color.cpp index fee3bbb..4c4c0ba 100644 --- a/source/utils/color.cpp +++ b/source/utils/color.cpp @@ -42,7 +42,7 @@ auto Color::fromHex(const std::string& hex_str) -> Color { } // Implementaciones de métodos estáticos de Color -constexpr auto Color::RGB_TO_HSV(Color color) -> HSV { +constexpr auto Color::rgbToHsv(Color color) -> HSV { float r = color.r / 255.0F; float g = color.g / 255.0F; float b = color.b / 255.0F; @@ -72,7 +72,7 @@ constexpr auto Color::RGB_TO_HSV(Color color) -> HSV { return {.h = h, .s = s, .v = v}; } -constexpr auto Color::HSV_TO_RGB(HSV hsv) -> Color { +constexpr auto Color::hsvToRgb(HSV hsv) -> Color { float c = hsv.v * hsv.s; float x = c * (1 - std::abs(std::fmod(hsv.h / 60.0F, 2) - 1)); float m = hsv.v - c; @@ -132,7 +132,7 @@ namespace Colors { auto generateMirroredCycle(Color base, ColorCycleStyle style) -> Cycle { Cycle result{}; - HSV base_hsv = Color::RGB_TO_HSV(base); + HSV base_hsv = Color::rgbToHsv(base); for (size_t i = 0; i < CYCLE_SIZE; ++i) { float t = static_cast(i) / (CYCLE_SIZE - 1); // 0 → 1 @@ -175,7 +175,7 @@ namespace Colors { .s = fminf(1.0F, fmaxf(0.0F, base_hsv.s + sat_shift)), .v = fminf(1.0F, fmaxf(0.0F, base_hsv.v + val_shift))}; - Color c = Color::HSV_TO_RGB(adjusted); + Color c = Color::hsvToRgb(adjusted); result[i] = c; result[(2 * CYCLE_SIZE) - 1 - i] = c; // espejo } diff --git a/source/utils/color.hpp b/source/utils/color.hpp index f14ea56..f305b70 100644 --- a/source/utils/color.hpp +++ b/source/utils/color.hpp @@ -5,6 +5,7 @@ #include // Para max, min #include // Para array +#include // Para std::uint8_t #include // Para size_t, abs #include // Para string #include // Para vector @@ -48,11 +49,11 @@ struct Color { b(blue), a(alpha) {} - [[nodiscard]] constexpr auto INVERSE() const -> Color { + [[nodiscard]] constexpr auto inverse() const -> Color { return Color(MAX_COLOR_VALUE - r, MAX_COLOR_VALUE - g, MAX_COLOR_VALUE - b, a); } - [[nodiscard]] constexpr auto LIGHTEN(int amount = DEFAULT_LIGHTEN_AMOUNT) const -> Color { + [[nodiscard]] constexpr auto lighten(int amount = DEFAULT_LIGHTEN_AMOUNT) const -> Color { return Color( std::min(static_cast(MAX_COLOR_VALUE), r + amount), std::min(static_cast(MAX_COLOR_VALUE), g + amount), @@ -60,7 +61,7 @@ struct Color { a); } - [[nodiscard]] constexpr auto DARKEN(int amount = DEFAULT_DARKEN_AMOUNT) const -> Color { + [[nodiscard]] constexpr auto darken(int amount = DEFAULT_DARKEN_AMOUNT) const -> Color { return Color( std::max(static_cast(MIN_COLOR_VALUE), r - amount), std::max(static_cast(MIN_COLOR_VALUE), g - amount), @@ -72,14 +73,14 @@ struct Color { static auto fromHex(const std::string& hex_str) -> Color; // Conversiones de formato de color - [[nodiscard]] constexpr static auto RGB_TO_HSV(Color color) -> HSV; - [[nodiscard]] constexpr static auto HSV_TO_RGB(HSV hsv) -> Color; + [[nodiscard]] constexpr static auto rgbToHsv(Color color) -> HSV; + [[nodiscard]] constexpr static auto hsvToRgb(HSV hsv) -> Color; - [[nodiscard]] constexpr auto IS_EQUAL_TO(const Color& other) const -> bool { + [[nodiscard]] constexpr auto isEqualTo(const Color& other) const -> bool { return r == other.r && g == other.g && b == other.b && a == other.a; } - [[nodiscard]] constexpr auto APPROACH_TO(const Color& target, int step = DEFAULT_APPROACH_STEP) const -> Color { + [[nodiscard]] constexpr auto approachTo(const Color& target, int step = DEFAULT_APPROACH_STEP) const -> Color { auto approach_component = [step](Uint8 current, Uint8 target_val) -> Uint8 { if (std::abs(current - target_val) <= step) { return target_val; @@ -96,7 +97,7 @@ struct Color { } // Interpolación lineal hacia otro color (t=0.0: this, t=1.0: target) - [[nodiscard]] constexpr auto LERP(const Color& target, float t) const -> Color { + [[nodiscard]] constexpr auto lerp(const Color& target, float t) const -> Color { // Asegurar que t esté en el rango [0.0, 1.0] t = std::clamp(t, 0.0F, 1.0F); @@ -113,12 +114,12 @@ struct Color { } // Sobrecarga para aceptar componentes RGBA directamente - [[nodiscard]] constexpr auto LERP(Uint8 red, Uint8 green, Uint8 blue, Uint8 alpha, float t) const -> Color { - return LERP(Color(red, green, blue, alpha), t); + [[nodiscard]] constexpr auto lerp(Uint8 red, Uint8 green, Uint8 blue, Uint8 alpha, float t) const -> Color { + return lerp(Color(red, green, blue, alpha), t); } // Convierte el color a un entero de 32 bits en formato RGBA - [[nodiscard]] constexpr auto TO_UINT32() const -> Uint32 { + [[nodiscard]] constexpr auto toUint32() const -> Uint32 { return (static_cast(r) << 24) | (static_cast(g) << 16) | (static_cast(b) << 8) | @@ -127,7 +128,7 @@ struct Color { }; // --- Enum ColorCycleStyle: define estilos de ciclo de color --- -enum class ColorCycleStyle { +enum class ColorCycleStyle : std::uint8_t { SUBTLE_PULSE, // Variación leve en brillo (por defecto) HUE_WAVE, // Variación suave en tono (sin verde) VIBRANT, // Cambios agresivos en tono y brillo diff --git a/source/utils/param.cpp b/source/utils/param.cpp index a41ab1a..2078dba 100644 --- a/source/utils/param.cpp +++ b/source/utils/param.cpp @@ -9,7 +9,6 @@ #include // Para runtime_error #include // Para string, basic_string, stoi, stof, hash, allocator, operator==, char_traits, operator+, operator>>, getline #include // Para unordered_map, operator==, _Node_iterator_base -#include // Para pair #include "game/ui/notifier.hpp" // Para Notifier #include "utils/color.hpp" // Para Color diff --git a/source/utils/param.hpp b/source/utils/param.hpp index c6fdac8..a3f7d64 100644 --- a/source/utils/param.hpp +++ b/source/utils/param.hpp @@ -167,51 +167,51 @@ struct ParamPlayer { }; // Inicialización con valores por defecto - const Shirt default_player0_shirt = Shirt( + const Shirt DEFAULT_PLAYER0_SHIRT = Shirt( Color::fromHex(Defaults::Player::DefaultShirt::PLAYER0_DARKEST), Color::fromHex(Defaults::Player::DefaultShirt::PLAYER0_DARK), Color::fromHex(Defaults::Player::DefaultShirt::PLAYER0_BASE), Color::fromHex(Defaults::Player::DefaultShirt::PLAYER0_LIGHT)); - const Shirt default_player1_shirt = Shirt( + const Shirt DEFAULT_PLAYER1_SHIRT = Shirt( Color::fromHex(Defaults::Player::DefaultShirt::PLAYER1_DARKEST), Color::fromHex(Defaults::Player::DefaultShirt::PLAYER1_DARK), Color::fromHex(Defaults::Player::DefaultShirt::PLAYER1_BASE), Color::fromHex(Defaults::Player::DefaultShirt::PLAYER1_LIGHT)); - std::array default_shirt = {default_player0_shirt, default_player1_shirt}; + std::array default_shirt = {DEFAULT_PLAYER0_SHIRT, DEFAULT_PLAYER1_SHIRT}; - const Shirt one_coffee_player0_shirt = Shirt( + const Shirt ONE_COFFEE_PLAYER0_SHIRT = Shirt( Color::fromHex(Defaults::Player::OneCoffeeShirt::PLAYER0_DARKEST), Color::fromHex(Defaults::Player::OneCoffeeShirt::PLAYER0_DARK), Color::fromHex(Defaults::Player::OneCoffeeShirt::PLAYER0_BASE), Color::fromHex(Defaults::Player::OneCoffeeShirt::PLAYER0_LIGHT)); - const Shirt one_coffee_player1_shirt = Shirt( + const Shirt ONE_COFFEE_PLAYER1_SHIRT = Shirt( Color::fromHex(Defaults::Player::OneCoffeeShirt::PLAYER1_DARKEST), Color::fromHex(Defaults::Player::OneCoffeeShirt::PLAYER1_DARK), Color::fromHex(Defaults::Player::OneCoffeeShirt::PLAYER1_BASE), Color::fromHex(Defaults::Player::OneCoffeeShirt::PLAYER1_LIGHT)); - std::array one_coffee_shirt = {one_coffee_player0_shirt, one_coffee_player1_shirt}; + std::array one_coffee_shirt = {ONE_COFFEE_PLAYER0_SHIRT, ONE_COFFEE_PLAYER1_SHIRT}; - const Shirt two_coffee_player0_shirt = Shirt( + const Shirt TWO_COFFEE_PLAYER0_SHIRT = Shirt( Color::fromHex(Defaults::Player::TwoCoffeeShirt::PLAYER0_DARKEST), Color::fromHex(Defaults::Player::TwoCoffeeShirt::PLAYER0_DARK), Color::fromHex(Defaults::Player::TwoCoffeeShirt::PLAYER0_BASE), Color::fromHex(Defaults::Player::TwoCoffeeShirt::PLAYER0_LIGHT)); - const Shirt two_coffee_player1_shirt = Shirt( + const Shirt TWO_COFFEE_PLAYER1_SHIRT = Shirt( Color::fromHex(Defaults::Player::TwoCoffeeShirt::PLAYER1_DARKEST), Color::fromHex(Defaults::Player::TwoCoffeeShirt::PLAYER1_DARK), Color::fromHex(Defaults::Player::TwoCoffeeShirt::PLAYER1_BASE), Color::fromHex(Defaults::Player::TwoCoffeeShirt::PLAYER1_LIGHT)); - std::array two_coffee_shirt = {two_coffee_player0_shirt, two_coffee_player1_shirt}; + std::array two_coffee_shirt = {TWO_COFFEE_PLAYER0_SHIRT, TWO_COFFEE_PLAYER1_SHIRT}; - const Color outline_player0_color = Color::fromHex(Defaults::Player::OutlineColor::PLAYER0); - const Color outline_player1_color = Color::fromHex(Defaults::Player::OutlineColor::PLAYER1); - std::array outline_color = {outline_player0_color, outline_player1_color}; + const Color OUTLINE_PLAYER0_COLOR = Color::fromHex(Defaults::Player::OutlineColor::PLAYER0); + const Color OUTLINE_PLAYER1_COLOR = Color::fromHex(Defaults::Player::OutlineColor::PLAYER1); + std::array outline_color = {OUTLINE_PLAYER0_COLOR, OUTLINE_PLAYER1_COLOR}; }; // --- Estructura Param: almacena todos los parámetros del juego --- diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index 67f780d..d48f412 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -7,7 +7,6 @@ #include // Para clamp, __transform_fn, transform #include // Para tolower, isspace #include // Para pow, sin, M_PI, cos, sqrt -#include // Para operator< #include // Para path #include // Para __find_if_not_fn, find_if_not, reverse_view, __find_fn, find, ref_view #include // Para basic_string, string, allocator, char_traits, operator==, operator+