tidy-fix automàtic (sense naming)
This commit is contained in:
@@ -62,10 +62,14 @@ 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);
|
||||
@@ -83,7 +87,9 @@ 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;
|
||||
if (!music_enabled_ || music == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
|
||||
JA_CrossfadeMusic(music, crossfade_ms, loop);
|
||||
|
||||
@@ -62,7 +62,7 @@ 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<int>(volume * 100.0F + 0.5F);
|
||||
return static_cast<int>((volume * 100.0F) + 0.5F);
|
||||
}
|
||||
static constexpr auto fromPercent(int percent) -> float {
|
||||
return static_cast<float>(percent) / 100.0F;
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
namespace AudioResource {
|
||||
|
||||
JA_Music_t* getMusic(const std::string& name) {
|
||||
auto getMusic(const std::string& name) -> JA_Music_t* {
|
||||
return Resource::Cache::get()->getMusic(name);
|
||||
}
|
||||
|
||||
JA_Sound_t* getSound(const std::string& name) {
|
||||
auto getSound(const std::string& name) -> JA_Sound_t* {
|
||||
return Resource::Cache::get()->getSound(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,6 @@ struct JA_Music_t;
|
||||
struct JA_Sound_t;
|
||||
|
||||
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_t*;
|
||||
auto getSound(const std::string& name) -> JA_Sound_t*;
|
||||
} // namespace AudioResource
|
||||
|
||||
+221
-120
@@ -2,10 +2,10 @@
|
||||
|
||||
// --- Includes ---
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdint.h> // Para uint32_t, uint8_t
|
||||
#include <stdio.h> // Para NULL, fseek, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
|
||||
#include <stdlib.h> // Para free, malloc
|
||||
|
||||
#include <cstdint> // Para uint32_t, uint8_t
|
||||
#include <cstdio> // Para NULL, fseek, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
|
||||
#include <cstdlib> // Para free, malloc
|
||||
#include <iostream> // Para std::cout
|
||||
#include <memory> // Para std::unique_ptr
|
||||
#include <string> // Para std::string
|
||||
@@ -19,7 +19,9 @@
|
||||
// overhead gràcies a EBO, igual que un unique_ptr amb default_delete.
|
||||
struct SDLFreeDeleter {
|
||||
void operator()(Uint8* p) const noexcept {
|
||||
if (p) SDL_free(p);
|
||||
if (p != nullptr) {
|
||||
SDL_free(p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,8 +42,10 @@ enum JA_Music_state {
|
||||
};
|
||||
|
||||
// --- Struct Definitions ---
|
||||
#define JA_MAX_SIMULTANEOUS_CHANNELS 20
|
||||
#define JA_MAX_GROUPS 2
|
||||
enum {
|
||||
JA_MAX_SIMULTANEOUS_CHANNELS = 20,
|
||||
JA_MAX_GROUPS = 2
|
||||
};
|
||||
|
||||
struct JA_Sound_t {
|
||||
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
||||
@@ -84,7 +88,7 @@ inline JA_Music_t* current_music{nullptr};
|
||||
inline JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
|
||||
|
||||
inline SDL_AudioSpec JA_audioSpec{SDL_AUDIO_S16, 2, 48000};
|
||||
inline float JA_musicVolume{1.0f};
|
||||
inline float JA_musicVolume{1.0F};
|
||||
inline float JA_soundVolume[JA_MAX_GROUPS];
|
||||
inline bool JA_musicEnabled{true};
|
||||
inline bool JA_soundEnabled{true};
|
||||
@@ -95,7 +99,7 @@ struct JA_FadeState {
|
||||
bool active{false};
|
||||
Uint64 start_time{0};
|
||||
int duration_ms{0};
|
||||
float initial_volume{0.0f};
|
||||
float initial_volume{0.0F};
|
||||
};
|
||||
|
||||
struct JA_OutgoingMusic {
|
||||
@@ -108,8 +112,8 @@ inline JA_FadeState incoming_fade;
|
||||
|
||||
// --- 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_StopChannel(int channel);
|
||||
inline auto JA_PlaySoundOnChannel(JA_Sound_t* sound, int channel, int loop = 0, int group = 0) -> int;
|
||||
inline void JA_CrossfadeMusic(JA_Music_t* music, int crossfade_ms, int loop = -1);
|
||||
|
||||
// --- Music streaming internals ---
|
||||
@@ -120,12 +124,14 @@ static constexpr int JA_MUSIC_BYTES_PER_SAMPLE = 2;
|
||||
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;
|
||||
static constexpr float JA_MUSIC_LOW_WATER_SECONDS = 0.5F;
|
||||
|
||||
// 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;
|
||||
inline auto JA_FeedMusicChunk(JA_Music_t* music) -> int {
|
||||
if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
short chunk[JA_MUSIC_CHUNK_SHORTS];
|
||||
const int num_channels = music->spec.channels;
|
||||
@@ -134,7 +140,9 @@ inline int JA_FeedMusicChunk(JA_Music_t* music) {
|
||||
num_channels,
|
||||
chunk,
|
||||
JA_MUSIC_CHUNK_SHORTS);
|
||||
if (samples_per_channel <= 0) return 0;
|
||||
if (samples_per_channel <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int bytes = samples_per_channel * num_channels * JA_MUSIC_BYTES_PER_SAMPLE;
|
||||
SDL_PutAudioStreamData(music->stream, chunk, bytes);
|
||||
@@ -144,19 +152,25 @@ inline int JA_FeedMusicChunk(JA_Music_t* music) {
|
||||
// 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;
|
||||
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 low_water_bytes = static_cast<int>(JA_MUSIC_LOW_WATER_SECONDS * static_cast<float>(bytes_per_second));
|
||||
|
||||
while (SDL_GetAudioStreamAvailable(music->stream) < low_water_bytes) {
|
||||
const int decoded = JA_FeedMusicChunk(music);
|
||||
if (decoded > 0) continue;
|
||||
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--;
|
||||
if (music->times > 0) {
|
||||
music->times--;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -168,14 +182,18 @@ inline void JA_PumpMusic(JA_Music_t* music) {
|
||||
// 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;
|
||||
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<int>((static_cast<int64_t>(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
|
||||
if (decoded <= 0) {
|
||||
break; // EOF: deixem drenar el que hi haja
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +201,7 @@ inline void JA_PreFillOutgoing(JA_Music_t* music, int duration_ms) {
|
||||
|
||||
inline void JA_Update() {
|
||||
// --- Outgoing music fade-out (crossfade o fade-out a silencio) ---
|
||||
if (outgoing_music.stream && outgoing_music.fade.active) {
|
||||
if ((outgoing_music.stream != nullptr) && outgoing_music.fade.active) {
|
||||
Uint64 now = SDL_GetTicks();
|
||||
Uint64 elapsed = now - outgoing_music.fade.start_time;
|
||||
if (elapsed >= (Uint64)outgoing_music.fade.duration_ms) {
|
||||
@@ -192,12 +210,12 @@ inline void JA_Update() {
|
||||
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));
|
||||
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) {
|
||||
if (JA_musicEnabled && (current_music != nullptr) && current_music->state == JA_MUSIC_PLAYING) {
|
||||
// Fade-in (parte de un crossfade)
|
||||
if (incoming_fade.active) {
|
||||
Uint64 now = SDL_GetTicks();
|
||||
@@ -221,42 +239,59 @@ inline void JA_Update() {
|
||||
|
||||
// --- Sound channels ---
|
||||
if (JA_soundEnabled) {
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
|
||||
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--;
|
||||
if (channels[i].times > 0) {
|
||||
channels[i].times--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i);
|
||||
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) {
|
||||
JA_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);
|
||||
JA_audioSpec = {.format = format, .channels = num_channels, .freq = freq};
|
||||
if (sdlAudioDevice != 0u) {
|
||||
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;
|
||||
if (sdlAudioDevice == 0) {
|
||||
std::cout << "Failed to initialize SDL audio!" << '\n';
|
||||
}
|
||||
for (auto& channel : channels) {
|
||||
channel.state = JA_CHANNEL_FREE;
|
||||
}
|
||||
for (float& i : JA_soundVolume) {
|
||||
i = 0.5F;
|
||||
}
|
||||
}
|
||||
|
||||
inline void JA_Quit() {
|
||||
if (outgoing_music.stream) {
|
||||
if (outgoing_music.stream != nullptr) {
|
||||
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||
outgoing_music.stream = nullptr;
|
||||
}
|
||||
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||
if (sdlAudioDevice != 0u) {
|
||||
SDL_CloseAudioDevice(sdlAudioDevice);
|
||||
}
|
||||
sdlAudioDevice = 0;
|
||||
}
|
||||
|
||||
// --- Music Functions ---
|
||||
|
||||
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
|
||||
if (!buffer || length == 0) return nullptr;
|
||||
inline auto JA_LoadMusic(const Uint8* buffer, Uint32 length) -> JA_Music_t* {
|
||||
if ((buffer == nullptr) || length == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocem el JA_Music_t primer per aprofitar el seu `std::vector<Uint8>`
|
||||
// com a propietari del OGG comprimit. stb_vorbis guarda un punter
|
||||
@@ -270,7 +305,7 @@ inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
|
||||
static_cast<int>(length),
|
||||
&vorbis_error,
|
||||
nullptr);
|
||||
if (!music->vorbis) {
|
||||
if (music->vorbis == nullptr) {
|
||||
std::cout << "JA_LoadMusic: stb_vorbis_open_memory failed (error " << vorbis_error << ")" << '\n';
|
||||
delete music;
|
||||
return nullptr;
|
||||
@@ -287,21 +322,25 @@ inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
|
||||
|
||||
// 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) {
|
||||
inline auto JA_LoadMusic(Uint8* buffer, Uint32 length, const char* filename) -> JA_Music_t* {
|
||||
JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), length);
|
||||
if (music && filename) music->filename = filename;
|
||||
if ((music != nullptr) && (filename != nullptr)) {
|
||||
music->filename = filename;
|
||||
}
|
||||
return music;
|
||||
}
|
||||
|
||||
inline JA_Music_t* JA_LoadMusic(const char* filename) {
|
||||
inline auto JA_LoadMusic(const char* filename) -> JA_Music_t* {
|
||||
// Carreguem primer el arxiu en memòria i després el descomprimim.
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (!f) return nullptr;
|
||||
if (f == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fsize = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
auto* buffer = static_cast<Uint8*>(malloc(fsize + 1));
|
||||
if (!buffer) {
|
||||
if (buffer == nullptr) {
|
||||
fclose(f);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -313,7 +352,7 @@ inline JA_Music_t* JA_LoadMusic(const char* filename) {
|
||||
fclose(f);
|
||||
|
||||
JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), static_cast<Uint32>(fsize));
|
||||
if (music) {
|
||||
if (music != nullptr) {
|
||||
music->filename = filename;
|
||||
}
|
||||
|
||||
@@ -323,7 +362,9 @@ inline JA_Music_t* JA_LoadMusic(const char* filename) {
|
||||
}
|
||||
|
||||
inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
|
||||
if (!JA_musicEnabled || !music || !music->vorbis) return;
|
||||
if (!JA_musicEnabled || (music == nullptr) || (music->vorbis == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
JA_StopMusic();
|
||||
|
||||
@@ -336,7 +377,7 @@ inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
|
||||
stb_vorbis_seek_start(current_music->vorbis);
|
||||
|
||||
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||
if (!current_music->stream) {
|
||||
if (current_music->stream == nullptr) {
|
||||
std::cout << "Failed to create audio stream!" << '\n';
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
return;
|
||||
@@ -351,23 +392,35 @@ inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
|
||||
}
|
||||
}
|
||||
|
||||
inline const char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) {
|
||||
if (!music) music = current_music;
|
||||
if (!music || music->filename.empty()) return nullptr;
|
||||
inline auto JA_GetMusicFilename(const JA_Music_t* music = nullptr) -> const char* {
|
||||
if (music == nullptr) {
|
||||
music = current_music;
|
||||
}
|
||||
if ((music == nullptr) || music->filename.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return music->filename.c_str();
|
||||
}
|
||||
|
||||
inline void JA_PauseMusic() {
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || current_music->state != JA_MUSIC_PLAYING) return;
|
||||
if (!JA_musicEnabled) {
|
||||
return;
|
||||
}
|
||||
if ((current_music == nullptr) || current_music->state != JA_MUSIC_PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
current_music->state = JA_MUSIC_PAUSED;
|
||||
SDL_UnbindAudioStream(current_music->stream);
|
||||
}
|
||||
|
||||
inline void JA_ResumeMusic() {
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || current_music->state != JA_MUSIC_PAUSED) return;
|
||||
if (!JA_musicEnabled) {
|
||||
return;
|
||||
}
|
||||
if ((current_music == nullptr) || current_music->state != JA_MUSIC_PAUSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
current_music->state = JA_MUSIC_PLAYING;
|
||||
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
|
||||
@@ -375,33 +428,39 @@ inline void JA_ResumeMusic() {
|
||||
|
||||
inline void JA_StopMusic() {
|
||||
// Limpiar outgoing crossfade si existe
|
||||
if (outgoing_music.stream) {
|
||||
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 || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
|
||||
if ((current_music == nullptr) || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) {
|
||||
return;
|
||||
}
|
||||
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
if (current_music->stream) {
|
||||
if (current_music->stream != nullptr) {
|
||||
SDL_DestroyAudioStream(current_music->stream);
|
||||
current_music->stream = nullptr;
|
||||
}
|
||||
// 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) {
|
||||
if (current_music->vorbis != nullptr) {
|
||||
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;
|
||||
if (!JA_musicEnabled) {
|
||||
return;
|
||||
}
|
||||
if ((current_music == nullptr) || current_music->state != JA_MUSIC_PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Destruir outgoing anterior si existe
|
||||
if (outgoing_music.stream) {
|
||||
if (outgoing_music.stream != nullptr) {
|
||||
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||
outgoing_music.stream = nullptr;
|
||||
}
|
||||
@@ -412,20 +471,24 @@ inline void JA_FadeOutMusic(const int milliseconds) {
|
||||
|
||||
// Robar el stream del current_music al outgoing
|
||||
outgoing_music.stream = current_music->stream;
|
||||
outgoing_music.fade = {true, SDL_GetTicks(), milliseconds, JA_musicVolume};
|
||||
outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = milliseconds, .initial_volume = 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);
|
||||
if (current_music->vorbis != nullptr) {
|
||||
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;
|
||||
if (!JA_musicEnabled || (music == nullptr) || (music->vorbis == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Destruir outgoing anterior si existe (crossfade durante crossfade)
|
||||
if (outgoing_music.stream) {
|
||||
if (outgoing_music.stream != nullptr) {
|
||||
SDL_DestroyAudioStream(outgoing_music.stream);
|
||||
outgoing_music.stream = nullptr;
|
||||
outgoing_music.fade.active = false;
|
||||
@@ -434,13 +497,15 @@ inline void JA_CrossfadeMusic(JA_Music_t* music, const int crossfade_ms, const i
|
||||
// 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) {
|
||||
if ((current_music != nullptr) && current_music->state == JA_MUSIC_PLAYING && (current_music->stream != nullptr)) {
|
||||
JA_PreFillOutgoing(current_music, crossfade_ms);
|
||||
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 = crossfade_ms, .initial_volume = JA_musicVolume};
|
||||
current_music->stream = nullptr;
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
if (current_music->vorbis) stb_vorbis_seek_start(current_music->vorbis);
|
||||
if (current_music->vorbis != nullptr) {
|
||||
stb_vorbis_seek_start(current_music->vorbis);
|
||||
}
|
||||
}
|
||||
|
||||
// Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente)
|
||||
@@ -450,42 +515,52 @@ inline void JA_CrossfadeMusic(JA_Music_t* music, const int crossfade_ms, const i
|
||||
|
||||
stb_vorbis_seek_start(current_music->vorbis);
|
||||
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||
if (!current_music->stream) {
|
||||
if (current_music->stream == nullptr) {
|
||||
std::cout << "Failed to create audio stream for crossfade!" << '\n';
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
return;
|
||||
}
|
||||
SDL_SetAudioStreamGain(current_music->stream, 0.0f);
|
||||
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};
|
||||
incoming_fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = 0.0F};
|
||||
}
|
||||
|
||||
inline JA_Music_state JA_GetMusicState() {
|
||||
if (!JA_musicEnabled) return JA_MUSIC_DISABLED;
|
||||
if (!current_music) return JA_MUSIC_INVALID;
|
||||
inline auto JA_GetMusicState() -> JA_Music_state {
|
||||
if (!JA_musicEnabled) {
|
||||
return JA_MUSIC_DISABLED;
|
||||
}
|
||||
if (current_music == nullptr) {
|
||||
return JA_MUSIC_INVALID;
|
||||
}
|
||||
|
||||
return current_music->state;
|
||||
}
|
||||
|
||||
inline void JA_DeleteMusic(JA_Music_t* music) {
|
||||
if (!music) return;
|
||||
if (music == nullptr) {
|
||||
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);
|
||||
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 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) {
|
||||
inline auto JA_SetMusicVolume(float volume) -> float {
|
||||
JA_musicVolume = SDL_clamp(volume, 0.0F, 1.0F);
|
||||
if ((current_music != nullptr) && (current_music->stream != nullptr)) {
|
||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||
}
|
||||
return JA_musicVolume;
|
||||
@@ -495,18 +570,20 @@ inline void JA_SetMusicPosition(float /*value*/) {
|
||||
// No implementat amb el backend de streaming.
|
||||
}
|
||||
|
||||
inline float JA_GetMusicPosition() {
|
||||
return 0.0f;
|
||||
inline auto JA_GetMusicPosition() -> float {
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
inline void JA_EnableMusic(const bool value) {
|
||||
if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic();
|
||||
if (!value && (current_music != nullptr) && (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) {
|
||||
inline auto JA_LoadSound(uint8_t* buffer, uint32_t size) -> JA_Sound_t* {
|
||||
auto sound = std::make_unique<JA_Sound_t>();
|
||||
Uint8* raw = nullptr;
|
||||
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &raw, &sound->length)) {
|
||||
@@ -517,7 +594,7 @@ inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) {
|
||||
return sound.release();
|
||||
}
|
||||
|
||||
inline JA_Sound_t* JA_LoadSound(const char* filename) {
|
||||
inline auto JA_LoadSound(const char* filename) -> JA_Sound_t* {
|
||||
auto sound = std::make_unique<JA_Sound_t>();
|
||||
Uint8* raw = nullptr;
|
||||
if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) {
|
||||
@@ -528,8 +605,10 @@ inline JA_Sound_t* JA_LoadSound(const char* filename) {
|
||||
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;
|
||||
inline auto JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) -> int {
|
||||
if (!JA_soundEnabled || (sound == nullptr)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int channel = 0;
|
||||
while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; }
|
||||
@@ -541,9 +620,13 @@ inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group =
|
||||
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;
|
||||
inline auto JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop, const int group) -> int {
|
||||
if (!JA_soundEnabled || (sound == nullptr)) {
|
||||
return -1;
|
||||
}
|
||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
JA_StopChannel(channel);
|
||||
|
||||
@@ -554,7 +637,7 @@ inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
|
||||
|
||||
if (!channels[channel].stream) {
|
||||
if (channels[channel].stream == nullptr) {
|
||||
std::cout << "Failed to create audio stream for sound!" << '\n';
|
||||
channels[channel].state = JA_CHANNEL_FREE;
|
||||
return -1;
|
||||
@@ -568,23 +651,30 @@ inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int
|
||||
}
|
||||
|
||||
inline void JA_DeleteSound(JA_Sound_t* sound) {
|
||||
if (!sound) return;
|
||||
if (sound == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||
if (channels[i].sound == sound) JA_StopChannel(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 (!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);
|
||||
for (auto& channel : channels) {
|
||||
if (channel.state == JA_CHANNEL_PLAYING) {
|
||||
channel.state = JA_CHANNEL_PAUSED;
|
||||
SDL_UnbindAudioStream(channel.stream);
|
||||
}
|
||||
}
|
||||
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||
if (channels[channel].state == JA_CHANNEL_PLAYING) {
|
||||
channels[channel].state = JA_CHANNEL_PAUSED;
|
||||
@@ -594,14 +684,17 @@ inline void JA_PauseChannel(const int channel) {
|
||||
}
|
||||
|
||||
inline void JA_ResumeChannel(const int channel) {
|
||||
if (!JA_soundEnabled) return;
|
||||
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);
|
||||
for (auto& channel : channels) {
|
||||
if (channel.state == JA_CHANNEL_PAUSED) {
|
||||
channel.state = JA_CHANNEL_PLAYING;
|
||||
SDL_BindAudioStream(sdlAudioDevice, channel.stream);
|
||||
}
|
||||
}
|
||||
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||
if (channels[channel].state == JA_CHANNEL_PAUSED) {
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
@@ -612,18 +705,22 @@ inline void JA_ResumeChannel(const int channel) {
|
||||
|
||||
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;
|
||||
for (auto& channel : channels) {
|
||||
if (channel.state != JA_CHANNEL_FREE) {
|
||||
if (channel.stream != nullptr) {
|
||||
SDL_DestroyAudioStream(channel.stream);
|
||||
}
|
||||
channel.stream = nullptr;
|
||||
channel.state = JA_CHANNEL_FREE;
|
||||
channel.pos = 0;
|
||||
channel.sound = nullptr;
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
if (channels[channel].stream != nullptr) {
|
||||
SDL_DestroyAudioStream(channels[channel].stream);
|
||||
}
|
||||
channels[channel].stream = nullptr;
|
||||
channels[channel].state = JA_CHANNEL_FREE;
|
||||
channels[channel].pos = 0;
|
||||
@@ -632,19 +729,23 @@ inline void JA_StopChannel(const int channel) {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
inline auto JA_GetChannelState(const int channel) -> JA_Channel_state {
|
||||
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);
|
||||
inline auto JA_SetSoundVolume(float volume, const int group = -1) -> float {
|
||||
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;
|
||||
for (float& i : JA_soundVolume) {
|
||||
i = v;
|
||||
}
|
||||
} else if (group >= 0 && group < JA_MAX_GROUPS) {
|
||||
JA_soundVolume[group] = v;
|
||||
@@ -653,11 +754,11 @@ inline float JA_SetSoundVolume(float volume, const int group = -1) {
|
||||
}
|
||||
|
||||
// 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]);
|
||||
for (auto& channel : channels) {
|
||||
if ((channel.state == JA_CHANNEL_PLAYING) || (channel.state == JA_CHANNEL_PAUSED)) {
|
||||
if (group == -1 || channel.group == group) {
|
||||
if (channel.stream != nullptr) {
|
||||
SDL_SetAudioStreamGain(channel.stream, JA_soundVolume[channel.group]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -672,7 +773,7 @@ inline void JA_EnableSound(const bool value) {
|
||||
JA_soundEnabled = value;
|
||||
}
|
||||
|
||||
inline float JA_SetVolume(float volume) {
|
||||
inline auto JA_SetVolume(float volume) -> float {
|
||||
float v = JA_SetMusicVolume(volume);
|
||||
JA_SetSoundVolume(v, -1);
|
||||
return v;
|
||||
|
||||
@@ -52,8 +52,8 @@ namespace Gamepad {
|
||||
// Recorta el nom visible del mando: trim des del primer '(' o '['
|
||||
// (per a evitar coses com "Retroid Controller (vendor: 1001) ..."),
|
||||
// elimina espais finals i talla a 25 caràcters.
|
||||
static std::string prettyName(const char* raw) {
|
||||
std::string name = (raw && *raw) ? raw : "Gamepad";
|
||||
static auto prettyName(const char* raw) -> std::string {
|
||||
std::string name = ((raw != nullptr) && (*raw != 0)) ? raw : "Gamepad";
|
||||
const auto pos = name.find_first_of("([");
|
||||
if (pos != std::string::npos) {
|
||||
name.erase(pos);
|
||||
@@ -64,7 +64,9 @@ namespace Gamepad {
|
||||
if (name.size() > 25) {
|
||||
name.resize(25);
|
||||
}
|
||||
if (name.empty()) name = "Gamepad";
|
||||
if (name.empty()) {
|
||||
name = "Gamepad";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -98,12 +100,14 @@ namespace Gamepad {
|
||||
static void openFirstGamepad() {
|
||||
int count = 0;
|
||||
SDL_JoystickID* ids = SDL_GetJoysticks(&count);
|
||||
if (ids) {
|
||||
if (ids != nullptr) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
installWebStandardMapping(ids[i]);
|
||||
if (!SDL_IsGamepad(ids[i])) continue;
|
||||
if (!SDL_IsGamepad(ids[i])) {
|
||||
continue;
|
||||
}
|
||||
pad_ = SDL_OpenGamepad(ids[i]);
|
||||
if (pad_) {
|
||||
if (pad_ != nullptr) {
|
||||
pad_id_ = ids[i];
|
||||
SDL_Log("Gamepad connectat: %s", SDL_GetGamepadName(pad_));
|
||||
break;
|
||||
@@ -128,7 +132,7 @@ namespace Gamepad {
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (pad_) {
|
||||
if (pad_ != nullptr) {
|
||||
SDL_CloseGamepad(pad_);
|
||||
pad_ = nullptr;
|
||||
pad_id_ = 0;
|
||||
@@ -145,12 +149,14 @@ namespace Gamepad {
|
||||
// GAMEPAD_ADDED) perquè SDL no reconeix el GUID. Escoltem els dos i
|
||||
// injectem el mapping estàndard abans d'obrir el mando.
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_JOYSTICK_ADDED) {
|
||||
if (!pad_) {
|
||||
if (pad_ == nullptr) {
|
||||
SDL_JoystickID jid = event.jdevice.which;
|
||||
installWebStandardMapping(jid);
|
||||
if (!SDL_IsGamepad(jid)) return;
|
||||
if (!SDL_IsGamepad(jid)) {
|
||||
return;
|
||||
}
|
||||
pad_ = SDL_OpenGamepad(jid);
|
||||
if (pad_) {
|
||||
if (pad_ != nullptr) {
|
||||
pad_id_ = jid;
|
||||
std::string name = prettyName(SDL_GetGamepadName(pad_));
|
||||
SDL_Log("Gamepad connectat: %s", name.c_str());
|
||||
@@ -158,7 +164,7 @@ namespace Gamepad {
|
||||
}
|
||||
}
|
||||
} else if (event.type == SDL_EVENT_GAMEPAD_REMOVED || event.type == SDL_EVENT_JOYSTICK_REMOVED) {
|
||||
if (pad_ && event.jdevice.which == pad_id_) {
|
||||
if ((pad_ != nullptr) && event.jdevice.which == pad_id_) {
|
||||
std::string saved_name = prettyName(SDL_GetGamepadName(pad_));
|
||||
SDL_Log("Gamepad desconnectat: %s", saved_name.c_str());
|
||||
SDL_CloseGamepad(pad_);
|
||||
@@ -190,7 +196,9 @@ namespace Gamepad {
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (!pad_) return;
|
||||
if (pad_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// D-pad
|
||||
bool dup = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
||||
@@ -220,19 +228,35 @@ namespace Gamepad {
|
||||
bool back = SDL_GetGamepadButton(pad_, SDL_GAMEPAD_BUTTON_BACK);
|
||||
|
||||
// Select (Back) → obre/tanca menú de servei (flanc)
|
||||
if (back && !prev_back_) pushKey(KeyConfig::scancode("menu_toggle"));
|
||||
if (back && !prev_back_) {
|
||||
pushKey(KeyConfig::scancode("menu_toggle"));
|
||||
}
|
||||
// Start → pausa (flanc)
|
||||
if (start && !prev_start_) pushKey(KeyConfig::scancode("pause_toggle"));
|
||||
if (start && !prev_start_) {
|
||||
pushKey(KeyConfig::scancode("pause_toggle"));
|
||||
}
|
||||
|
||||
if (Menu::isOpen()) {
|
||||
// Navegació del menú per flanc
|
||||
if (up && !prev_up_) pushKey(SDL_SCANCODE_UP);
|
||||
if (dn && !prev_down_) pushKey(SDL_SCANCODE_DOWN);
|
||||
if (lt && !prev_left_) pushKey(SDL_SCANCODE_LEFT);
|
||||
if (rt && !prev_right_) pushKey(SDL_SCANCODE_RIGHT);
|
||||
if (up && !prev_up_) {
|
||||
pushKey(SDL_SCANCODE_UP);
|
||||
}
|
||||
if (dn && !prev_down_) {
|
||||
pushKey(SDL_SCANCODE_DOWN);
|
||||
}
|
||||
if (lt && !prev_left_) {
|
||||
pushKey(SDL_SCANCODE_LEFT);
|
||||
}
|
||||
if (rt && !prev_right_) {
|
||||
pushKey(SDL_SCANCODE_RIGHT);
|
||||
}
|
||||
// EAST accepta, SOUTH cancela / endarrere
|
||||
if (east && !prev_east_) pushKey(SDL_SCANCODE_RETURN);
|
||||
if (south && !prev_south_) pushKey(SDL_SCANCODE_BACKSPACE);
|
||||
if (east && !prev_east_) {
|
||||
pushKey(SDL_SCANCODE_RETURN);
|
||||
}
|
||||
if (south && !prev_south_) {
|
||||
pushKey(SDL_SCANCODE_BACKSPACE);
|
||||
}
|
||||
|
||||
// Assegura que el joc no rep tecles de moviment mentre el menú està obert
|
||||
JI_SetVirtualKey(SDL_SCANCODE_UP, JI_VSRC_GAMEPAD, false);
|
||||
|
||||
@@ -34,7 +34,9 @@ namespace GlobalInputs {
|
||||
snprintf(msg, sizeof(msg), Locale::get("notifications.zoom_fmt"), Screen::get()->getZoom());
|
||||
Overlay::showNotification(msg);
|
||||
}
|
||||
if (dec_zoom) consumed = true;
|
||||
if (dec_zoom) {
|
||||
consumed = true;
|
||||
}
|
||||
dec_zoom_prev = dec_zoom;
|
||||
|
||||
// F2 — Augmentar zoom
|
||||
@@ -45,7 +47,9 @@ namespace GlobalInputs {
|
||||
snprintf(msg, sizeof(msg), Locale::get("notifications.zoom_fmt"), Screen::get()->getZoom());
|
||||
Overlay::showNotification(msg);
|
||||
}
|
||||
if (inc_zoom) consumed = true;
|
||||
if (inc_zoom) {
|
||||
consumed = true;
|
||||
}
|
||||
inc_zoom_prev = inc_zoom;
|
||||
|
||||
// F3 — Toggle pantalla completa
|
||||
@@ -54,7 +58,9 @@ namespace GlobalInputs {
|
||||
Screen::get()->toggleFullscreen();
|
||||
Overlay::showNotification(Screen::get()->isFullscreen() ? Locale::get("notifications.fullscreen") : Locale::get("notifications.windowed"));
|
||||
}
|
||||
if (fullscreen) consumed = true;
|
||||
if (fullscreen) {
|
||||
consumed = true;
|
||||
}
|
||||
fullscreen_prev = fullscreen;
|
||||
|
||||
// F4 — Toggle shaders
|
||||
@@ -63,7 +69,9 @@ namespace GlobalInputs {
|
||||
Screen::get()->toggleShaders();
|
||||
Overlay::showNotification(Options::video.shader_enabled ? Locale::get("notifications.shader_on") : Locale::get("notifications.shader_off"));
|
||||
}
|
||||
if (shader) consumed = true;
|
||||
if (shader) {
|
||||
consumed = true;
|
||||
}
|
||||
shader_prev = shader;
|
||||
|
||||
// F5 — Toggle aspect ratio 4:3
|
||||
@@ -72,7 +80,9 @@ namespace GlobalInputs {
|
||||
Screen::get()->toggleAspectRatio();
|
||||
Overlay::showNotification(Options::video.aspect_ratio_4_3 ? Locale::get("notifications.aspect_43") : Locale::get("notifications.aspect_square"));
|
||||
}
|
||||
if (aspect) consumed = true;
|
||||
if (aspect) {
|
||||
consumed = true;
|
||||
}
|
||||
aspect_prev = aspect;
|
||||
|
||||
// F6 — Toggle supersampling
|
||||
@@ -82,7 +92,9 @@ namespace GlobalInputs {
|
||||
Overlay::showNotification(Options::video.supersampling ? Locale::get("notifications.ss_on") : Locale::get("notifications.ss_off"));
|
||||
}
|
||||
}
|
||||
if (ss) consumed = true;
|
||||
if (ss) {
|
||||
consumed = true;
|
||||
}
|
||||
ss_prev = ss;
|
||||
|
||||
// F7 — Canviar tipus de shader (PostFX ↔ CrtPi)
|
||||
@@ -94,7 +106,9 @@ namespace GlobalInputs {
|
||||
Overlay::showNotification(msg);
|
||||
}
|
||||
}
|
||||
if (next_shader) consumed = true;
|
||||
if (next_shader) {
|
||||
consumed = true;
|
||||
}
|
||||
next_shader_prev = next_shader;
|
||||
|
||||
// F8 — Pròxim preset del shader actiu
|
||||
@@ -106,7 +120,9 @@ namespace GlobalInputs {
|
||||
Overlay::showNotification(msg);
|
||||
}
|
||||
}
|
||||
if (next_preset) consumed = true;
|
||||
if (next_preset) {
|
||||
consumed = true;
|
||||
}
|
||||
next_preset_prev = next_preset;
|
||||
|
||||
// F9 — Cicla filtre de textura (NEAREST ↔ LINEAR), sempre aplicat
|
||||
@@ -117,7 +133,9 @@ namespace GlobalInputs {
|
||||
? Locale::get("notifications.filter_linear")
|
||||
: Locale::get("notifications.filter_nearest"));
|
||||
}
|
||||
if (texture_filter) consumed = true;
|
||||
if (texture_filter) {
|
||||
consumed = true;
|
||||
}
|
||||
texture_filter_prev = texture_filter;
|
||||
|
||||
// F10 — Toggle render info (FPS, driver, shader)
|
||||
@@ -125,7 +143,9 @@ namespace GlobalInputs {
|
||||
if (render_info && !render_info_prev) {
|
||||
Overlay::toggleRenderInfo();
|
||||
}
|
||||
if (render_info) consumed = true;
|
||||
if (render_info) {
|
||||
consumed = true;
|
||||
}
|
||||
render_info_prev = render_info;
|
||||
|
||||
return consumed;
|
||||
|
||||
@@ -16,7 +16,9 @@ namespace KeyConfig {
|
||||
|
||||
auto findIndex(const std::string& id) -> size_t {
|
||||
auto it = index_.find(id);
|
||||
if (it == index_.end()) return SIZE_MAX;
|
||||
if (it == index_.end()) {
|
||||
return SIZE_MAX;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
@@ -30,7 +32,9 @@ namespace KeyConfig {
|
||||
std::string content(buf.begin(), buf.end());
|
||||
try {
|
||||
auto yaml = fkyaml::node::deserialize(content);
|
||||
if (!yaml.contains("keys")) return;
|
||||
if (!yaml.contains("keys")) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& node : yaml["keys"]) {
|
||||
KeyEntry entry;
|
||||
@@ -59,7 +63,9 @@ namespace KeyConfig {
|
||||
|
||||
void applyOverrides(const std::string& disk_path) {
|
||||
std::ifstream file(disk_path);
|
||||
if (!file.good()) return;
|
||||
if (!file.good()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
@@ -67,7 +73,9 @@ namespace KeyConfig {
|
||||
|
||||
try {
|
||||
auto yaml = fkyaml::node::deserialize(content);
|
||||
if (!yaml.contains("overrides")) return;
|
||||
if (!yaml.contains("overrides")) {
|
||||
return;
|
||||
}
|
||||
|
||||
int applied = 0;
|
||||
for (const auto& kv : yaml["overrides"].as_map()) {
|
||||
@@ -118,28 +126,38 @@ namespace KeyConfig {
|
||||
|
||||
auto scancode(const std::string& id) -> SDL_Scancode {
|
||||
auto idx = findIndex(id);
|
||||
if (idx == SIZE_MAX) return SDL_SCANCODE_UNKNOWN;
|
||||
if (idx == SIZE_MAX) {
|
||||
return SDL_SCANCODE_UNKNOWN;
|
||||
}
|
||||
return entries_[idx].scancode;
|
||||
}
|
||||
|
||||
auto scancodePtr(const std::string& id) -> SDL_Scancode* {
|
||||
auto idx = findIndex(id);
|
||||
if (idx == SIZE_MAX) return nullptr;
|
||||
if (idx == SIZE_MAX) {
|
||||
return nullptr;
|
||||
}
|
||||
return &entries_[idx].scancode;
|
||||
}
|
||||
|
||||
void setScancode(const std::string& id, SDL_Scancode sc) {
|
||||
auto idx = findIndex(id);
|
||||
if (idx == SIZE_MAX) return;
|
||||
if (idx == SIZE_MAX) {
|
||||
return;
|
||||
}
|
||||
entries_[idx].scancode = sc;
|
||||
const char* name = SDL_GetScancodeName(sc);
|
||||
entries_[idx].code = (name != nullptr) ? name : "";
|
||||
}
|
||||
|
||||
auto isGuiKey(SDL_Scancode sc) -> bool {
|
||||
if (sc == SDL_SCANCODE_UNKNOWN) return false;
|
||||
if (sc == SDL_SCANCODE_UNKNOWN) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& e : entries_) {
|
||||
if (e.scancode == sc) return true;
|
||||
if (e.scancode == sc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -149,12 +167,16 @@ namespace KeyConfig {
|
||||
}
|
||||
|
||||
auto saveOverrides() -> bool {
|
||||
if (overrides_path_.empty()) return false;
|
||||
if (overrides_path_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recull només les entrades remapeades.
|
||||
std::vector<const KeyEntry*> changed;
|
||||
for (const auto& e : entries_) {
|
||||
if (e.scancode != e.default_scancode) changed.push_back(&e);
|
||||
if (e.scancode != e.default_scancode) {
|
||||
changed.push_back(&e);
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream file(overrides_path_);
|
||||
|
||||
@@ -17,7 +17,9 @@ namespace KeyRemap {
|
||||
|
||||
void update() {
|
||||
const bool* ks = SDL_GetKeyboardState(nullptr);
|
||||
if (!ks) return;
|
||||
if (ks == nullptr) {
|
||||
return;
|
||||
}
|
||||
mirror(Options::keys_game.up, SDL_SCANCODE_UP, ks);
|
||||
mirror(Options::keys_game.down, SDL_SCANCODE_DOWN, ks);
|
||||
mirror(Options::keys_game.left, SDL_SCANCODE_LEFT, ks);
|
||||
|
||||
+43
-24
@@ -42,18 +42,18 @@ void JD8_ClearScreen(Uint8 color) {
|
||||
memset(screen, color, 64000);
|
||||
}
|
||||
|
||||
JD8_Surface JD8_NewSurface() {
|
||||
auto JD8_NewSurface() -> JD8_Surface {
|
||||
return new Uint8[64000]{};
|
||||
}
|
||||
|
||||
// Helper intern: deriva el basename d'una ruta per a buscar al Cache.
|
||||
static std::string jd8_basename(const char* file) {
|
||||
static auto jd8_basename(const char* file) -> std::string {
|
||||
std::string s = file;
|
||||
auto pos = s.find_last_of("/\\");
|
||||
return pos == std::string::npos ? s : s.substr(pos + 1);
|
||||
}
|
||||
|
||||
JD8_Surface JD8_LoadSurface(const char* file) {
|
||||
auto JD8_LoadSurface(const char* file) -> JD8_Surface {
|
||||
// Prova primer el Resource::Cache. Si l'asset és precarregat, copiem
|
||||
// els 64KB des del cache (microsegons) i ens estalviem la decodificació
|
||||
// GIF. Mantenim el contracte de la funció: el caller rep un buffer
|
||||
@@ -70,7 +70,8 @@ JD8_Surface JD8_LoadSurface(const char* file) {
|
||||
}
|
||||
|
||||
auto buffer = ResourceHelper::loadFile(file);
|
||||
unsigned short w, h;
|
||||
unsigned short w;
|
||||
unsigned short h;
|
||||
Uint8* pixels = LoadGif(buffer.data(), &w, &h);
|
||||
if (pixels == nullptr) {
|
||||
printf("Unable to load bitmap: %s\n", SDL_GetError());
|
||||
@@ -82,11 +83,11 @@ JD8_Surface JD8_LoadSurface(const char* file) {
|
||||
return image;
|
||||
}
|
||||
|
||||
JD8_Palette JD8_LoadPalette(const char* file) {
|
||||
auto JD8_LoadPalette(const char* file) -> JD8_Palette {
|
||||
// Sempre retorna un buffer de 256 colors reservat amb `new Color[256]`
|
||||
// — el caller és responsable d'alliberar-lo amb `delete[]` (o lliurar-ne
|
||||
// l'ownership a `JD8_SetScreenPalette`).
|
||||
JD8_Palette palette = new Color[256];
|
||||
auto palette = new Color[256];
|
||||
|
||||
if (Resource::Cache::get() != nullptr) {
|
||||
try {
|
||||
@@ -106,7 +107,9 @@ JD8_Palette JD8_LoadPalette(const char* file) {
|
||||
}
|
||||
|
||||
void JD8_SetScreenPalette(JD8_Palette palette) {
|
||||
if (main_palette == palette) return;
|
||||
if (main_palette == palette) {
|
||||
return;
|
||||
}
|
||||
delete[] main_palette;
|
||||
main_palette = palette;
|
||||
}
|
||||
@@ -126,9 +129,15 @@ void JD8_FillRect(int x, int y, int w, int h, Uint8 color) {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + w > 320) w = 320 - x;
|
||||
if (y + h > 200) h = 200 - y;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
if (x + w > 320) {
|
||||
w = 320 - x;
|
||||
}
|
||||
if (y + h > 200) {
|
||||
h = 200 - y;
|
||||
}
|
||||
if (w <= 0 || h <= 0) {
|
||||
return;
|
||||
}
|
||||
for (int row = y; row < y + h; ++row) {
|
||||
memset(&screen[x + (row * 320)], color, w);
|
||||
}
|
||||
@@ -158,47 +167,55 @@ void JD8_BlitToSurface(int x, int y, JD8_Surface surface, int sx, int sy, int sw
|
||||
}
|
||||
}
|
||||
|
||||
void JD8_BlitCK(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, Uint8 colorkey) {
|
||||
void JD8_BlitCK(int x, int y, const JD8_Surface surface, int sx, int sy, int sw, int sh, Uint8 colorkey) {
|
||||
int src_pointer = sx + (sy * 320);
|
||||
int dst_pointer = x + (y * 320);
|
||||
for (int j = 0; j < sh; j++) {
|
||||
for (int i = 0; i < sw; i++) {
|
||||
if (surface[src_pointer + i] != colorkey) screen[dst_pointer + i] = surface[src_pointer + i];
|
||||
if (surface[src_pointer + i] != colorkey) {
|
||||
screen[dst_pointer + i] = surface[src_pointer + i];
|
||||
}
|
||||
}
|
||||
src_pointer += 320;
|
||||
dst_pointer += 320;
|
||||
}
|
||||
}
|
||||
|
||||
void JD8_BlitCKCut(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, Uint8 colorkey) {
|
||||
void JD8_BlitCKCut(int x, int y, const JD8_Surface surface, int sx, int sy, int sw, int sh, Uint8 colorkey) {
|
||||
int src_pointer = sx + (sy * 320);
|
||||
int dst_pointer = x + (y * 320);
|
||||
for (int j = 0; j < sh; j++) {
|
||||
for (int i = 0; i < sw; i++) {
|
||||
if (surface[src_pointer + i] != colorkey && (x + i >= 0) && (y + j >= 0) && (x + i < 320) && (y + j < 200)) screen[dst_pointer + i] = surface[src_pointer + i];
|
||||
if (surface[src_pointer + i] != colorkey && (x + i >= 0) && (y + j >= 0) && (x + i < 320) && (y + j < 200)) {
|
||||
screen[dst_pointer + i] = surface[src_pointer + i];
|
||||
}
|
||||
}
|
||||
src_pointer += 320;
|
||||
dst_pointer += 320;
|
||||
}
|
||||
}
|
||||
|
||||
void JD8_BlitCKScroll(int y, JD8_Surface surface, int sx, int sy, int sh, Uint8 colorkey) {
|
||||
void JD8_BlitCKScroll(int y, const JD8_Surface surface, int sx, int sy, int sh, Uint8 colorkey) {
|
||||
int dst_pointer = y * 320;
|
||||
for (int j = sy; j < sy + sh; j++) {
|
||||
for (int i = 0; i < 320; i++) {
|
||||
int x = (i + sx) % 320;
|
||||
if (surface[x + j * 320] != colorkey) screen[dst_pointer] = surface[x + j * 320];
|
||||
if (surface[x + (j * 320)] != colorkey) {
|
||||
screen[dst_pointer] = surface[x + (j * 320)];
|
||||
}
|
||||
dst_pointer++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JD8_BlitCKToSurface(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, JD8_Surface dest, Uint8 colorkey) {
|
||||
void JD8_BlitCKToSurface(int x, int y, const JD8_Surface surface, int sx, int sy, int sw, int sh, JD8_Surface dest, Uint8 colorkey) {
|
||||
int src_pointer = sx + (sy * 320);
|
||||
int dst_pointer = x + (y * 320);
|
||||
for (int j = 0; j < sh; j++) {
|
||||
for (int i = 0; i < sw; i++) {
|
||||
if (surface[src_pointer + i] != colorkey) dest[dst_pointer + i] = surface[src_pointer + i];
|
||||
if (surface[src_pointer + i] != colorkey) {
|
||||
dest[dst_pointer + i] = surface[src_pointer + i];
|
||||
}
|
||||
}
|
||||
src_pointer += 320;
|
||||
dst_pointer += 320;
|
||||
@@ -218,15 +235,15 @@ void JD8_Flip() {
|
||||
}
|
||||
}
|
||||
|
||||
Uint32* JD8_GetFramebuffer() {
|
||||
auto JD8_GetFramebuffer() -> Uint32* {
|
||||
return pixel_data;
|
||||
}
|
||||
|
||||
void JD8_FreeSurface(JD8_Surface surface) {
|
||||
void JD8_FreeSurface(const JD8_Surface surface) {
|
||||
delete[] surface;
|
||||
}
|
||||
|
||||
Uint8 JD8_GetPixel(JD8_Surface surface, int x, int y) {
|
||||
auto JD8_GetPixel(JD8_Surface surface, int x, int y) -> Uint8 {
|
||||
return surface[x + (y * 320)];
|
||||
}
|
||||
|
||||
@@ -292,12 +309,14 @@ void JD8_FadeStartToPal(JD8_Palette pal) {
|
||||
fade_step = 0;
|
||||
}
|
||||
|
||||
bool JD8_FadeIsActive() {
|
||||
auto JD8_FadeIsActive() -> bool {
|
||||
return fade_type != FadeType::None;
|
||||
}
|
||||
|
||||
bool JD8_FadeTickStep() {
|
||||
if (fade_type == FadeType::None) return true;
|
||||
auto JD8_FadeTickStep() -> bool {
|
||||
if (fade_type == FadeType::None) {
|
||||
return true;
|
||||
}
|
||||
|
||||
apply_fade_step();
|
||||
fade_step++;
|
||||
|
||||
+12
-12
@@ -16,11 +16,11 @@ void JD8_Quit();
|
||||
|
||||
void JD8_ClearScreen(Uint8 color);
|
||||
|
||||
JD8_Surface JD8_NewSurface();
|
||||
auto JD8_NewSurface() -> JD8_Surface;
|
||||
|
||||
JD8_Surface JD8_LoadSurface(const char* file);
|
||||
auto JD8_LoadSurface(const char* file) -> JD8_Surface;
|
||||
|
||||
JD8_Palette JD8_LoadPalette(const char* file);
|
||||
auto JD8_LoadPalette(const char* file) -> JD8_Palette;
|
||||
|
||||
void JD8_SetScreenPalette(JD8_Palette palette);
|
||||
|
||||
@@ -36,13 +36,13 @@ void JD8_Blit(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh)
|
||||
|
||||
void JD8_BlitToSurface(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, JD8_Surface dest);
|
||||
|
||||
void JD8_BlitCK(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, Uint8 colorkey);
|
||||
void JD8_BlitCK(int x, int y, const JD8_Surface surface, int sx, int sy, int sw, int sh, Uint8 colorkey);
|
||||
|
||||
void JD8_BlitCKCut(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, Uint8 colorkey);
|
||||
void JD8_BlitCKCut(int x, int y, const JD8_Surface surface, int sx, int sy, int sw, int sh, Uint8 colorkey);
|
||||
|
||||
void JD8_BlitCKScroll(int y, JD8_Surface surface, int sx, int sy, int sh, Uint8 colorkey);
|
||||
void JD8_BlitCKScroll(int y, const JD8_Surface surface, int sx, int sy, int sh, Uint8 colorkey);
|
||||
|
||||
void JD8_BlitCKToSurface(int x, int y, JD8_Surface surface, int sx, int sy, int sw, int sh, JD8_Surface dest, Uint8 colorkey);
|
||||
void JD8_BlitCKToSurface(int x, int y, const JD8_Surface surface, int sx, int sy, int sw, int sh, JD8_Surface dest, Uint8 colorkey);
|
||||
|
||||
// Converteix la pantalla indexada a ARGB. El Director crida aquesta
|
||||
// funció al final de cada tick i després llegeix el framebuffer via
|
||||
@@ -51,11 +51,11 @@ void JD8_Flip();
|
||||
|
||||
// Accés al framebuffer ARGB de 320x200 actualitzat per l'última crida a
|
||||
// JD8_Flip(). Propietat de jdraw8 — el caller no ha de lliberar-lo.
|
||||
Uint32* JD8_GetFramebuffer();
|
||||
auto JD8_GetFramebuffer() -> Uint32*;
|
||||
|
||||
void JD8_FreeSurface(JD8_Surface surface);
|
||||
void JD8_FreeSurface(const JD8_Surface surface);
|
||||
|
||||
Uint8 JD8_GetPixel(JD8_Surface surface, int x, int y);
|
||||
auto JD8_GetPixel(JD8_Surface surface, int x, int y) -> Uint8;
|
||||
|
||||
void JD8_PutPixel(JD8_Surface surface, int x, int y, Uint8 pixel);
|
||||
|
||||
@@ -70,8 +70,8 @@ void JD8_SetPaletteColor(Uint8 index, Uint8 r, Uint8 g, Uint8 b);
|
||||
// L'embolcall `scenes::PaletteFade` ho fa més idiomàtic per a escenes.
|
||||
void JD8_FadeStartOut();
|
||||
void JD8_FadeStartToPal(JD8_Palette pal);
|
||||
bool JD8_FadeTickStep();
|
||||
bool JD8_FadeIsActive();
|
||||
auto JD8_FadeTickStep() -> bool;
|
||||
auto JD8_FadeIsActive() -> bool;
|
||||
|
||||
// JD_Font JD_LoadFont( char *file, int width, int height);
|
||||
|
||||
|
||||
+25
-11
@@ -27,12 +27,16 @@ namespace {
|
||||
config.clear();
|
||||
const std::string config_file = config_folder + "/config.txt";
|
||||
std::ifstream fi(config_file);
|
||||
if (!fi.is_open()) return;
|
||||
if (!fi.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(fi, line)) {
|
||||
const auto eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
if (eq == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
config.push_back({line.substr(0, eq), line.substr(eq + 1)});
|
||||
}
|
||||
}
|
||||
@@ -40,7 +44,9 @@ namespace {
|
||||
void save_config_values() {
|
||||
const std::string config_file = config_folder + "/config.txt";
|
||||
std::ofstream fo(config_file);
|
||||
if (!fo.is_open()) return;
|
||||
if (!fo.is_open()) {
|
||||
return;
|
||||
}
|
||||
for (const auto& pair : config) {
|
||||
fo << pair.key << '=' << pair.value << '\n';
|
||||
}
|
||||
@@ -52,7 +58,7 @@ void file_setresourcefolder(const char* str) {
|
||||
resource_folder = str;
|
||||
}
|
||||
|
||||
const char* file_getresourcefolder() {
|
||||
auto file_getresourcefolder() -> const char* {
|
||||
return resource_folder.c_str();
|
||||
}
|
||||
|
||||
@@ -76,9 +82,13 @@ void file_setconfigfolder(const char* foldername) {
|
||||
// arranque dins del navegador. La config no persistirà entre recàrregues
|
||||
// (MEMFS és volàtil); caldria IDBFS si volguéssem persistència a web.
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
const char* homedir = (pw && pw->pw_dir && pw->pw_dir[0]) ? pw->pw_dir : nullptr;
|
||||
if (!homedir || !homedir[0]) homedir = getenv("HOME");
|
||||
if (!homedir || !homedir[0]) homedir = "/tmp";
|
||||
const char* homedir = ((pw != nullptr) && (pw->pw_dir != nullptr) && (pw->pw_dir[0] != 0)) ? pw->pw_dir : nullptr;
|
||||
if ((homedir == nullptr) || (homedir[0] == 0)) {
|
||||
homedir = getenv("HOME");
|
||||
}
|
||||
if ((homedir == nullptr) || (homedir[0] == 0)) {
|
||||
homedir = "/tmp";
|
||||
}
|
||||
config_folder = std::string(homedir) + "/.config/" + foldername;
|
||||
#endif
|
||||
|
||||
@@ -92,14 +102,16 @@ void file_setconfigfolder(const char* foldername) {
|
||||
// volàtil al navegador de totes formes: ignorem l'error i continuem.
|
||||
}
|
||||
|
||||
const char* file_getconfigfolder() {
|
||||
auto file_getconfigfolder() -> const char* {
|
||||
thread_local std::string folder;
|
||||
folder = config_folder + "/";
|
||||
return folder.c_str();
|
||||
}
|
||||
|
||||
const char* file_getconfigvalue(const char* key) {
|
||||
if (config.empty()) load_config_values();
|
||||
auto file_getconfigvalue(const char* key) -> const char* {
|
||||
if (config.empty()) {
|
||||
load_config_values();
|
||||
}
|
||||
for (const auto& pair : config) {
|
||||
if (pair.key == key) {
|
||||
thread_local std::string value_cache;
|
||||
@@ -111,7 +123,9 @@ const char* file_getconfigvalue(const char* key) {
|
||||
}
|
||||
|
||||
void file_setconfigvalue(const char* key, const char* value) {
|
||||
if (config.empty()) load_config_values();
|
||||
if (config.empty()) {
|
||||
load_config_values();
|
||||
}
|
||||
for (auto& pair : config) {
|
||||
if (pair.key == key) {
|
||||
pair.value = value;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
void file_setconfigfolder(const char* foldername);
|
||||
const char* file_getconfigfolder();
|
||||
auto file_getconfigfolder() -> const char*;
|
||||
|
||||
void file_setresourcefolder(const char* str);
|
||||
const char* file_getresourcefolder();
|
||||
auto file_getresourcefolder() -> const char*;
|
||||
|
||||
const char* file_getconfigvalue(const char* key);
|
||||
auto file_getconfigvalue(const char* key) -> const char*;
|
||||
void file_setconfigvalue(const char* key, const char* value);
|
||||
|
||||
@@ -24,7 +24,7 @@ void JG_QuitSignal() {
|
||||
quitting = true;
|
||||
}
|
||||
|
||||
bool JG_Quitting() {
|
||||
auto JG_Quitting() -> bool {
|
||||
return quitting;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ void JG_SetUpdateTicks(Uint32 milliseconds) {
|
||||
update_ticks = milliseconds;
|
||||
}
|
||||
|
||||
bool JG_ShouldUpdate() {
|
||||
auto JG_ShouldUpdate() -> bool {
|
||||
const Uint32 now = SDL_GetTicks();
|
||||
if (now - update_time > update_ticks) {
|
||||
update_time = now;
|
||||
@@ -45,11 +45,11 @@ bool JG_ShouldUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint32 JG_GetCycleCounter() {
|
||||
auto JG_GetCycleCounter() -> Uint32 {
|
||||
return cycle_counter;
|
||||
}
|
||||
|
||||
Uint32 JG_GetDeltaMs() {
|
||||
auto JG_GetDeltaMs() -> Uint32 {
|
||||
const Uint32 now = SDL_GetTicks();
|
||||
const Uint32 delta = now - last_delta_time;
|
||||
last_delta_time = now;
|
||||
|
||||
@@ -7,14 +7,14 @@ void JG_Finalize();
|
||||
|
||||
void JG_QuitSignal();
|
||||
|
||||
bool JG_Quitting();
|
||||
auto JG_Quitting() -> bool;
|
||||
|
||||
void JG_SetUpdateTicks(Uint32 milliseconds);
|
||||
|
||||
bool JG_ShouldUpdate();
|
||||
auto JG_ShouldUpdate() -> bool;
|
||||
|
||||
Uint32 JG_GetCycleCounter();
|
||||
auto JG_GetCycleCounter() -> Uint32;
|
||||
|
||||
// Temps transcorregut (en ms) des de l'última crida a JG_GetDeltaMs.
|
||||
// Helper per a la migració progressiva a time-based (Fase 4+).
|
||||
Uint32 JG_GetDeltaMs();
|
||||
auto JG_GetDeltaMs() -> Uint32;
|
||||
|
||||
+45
-22
@@ -1,5 +1,6 @@
|
||||
#include "core/jail/jinput.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "core/system/director.hpp"
|
||||
@@ -19,7 +20,7 @@ namespace {
|
||||
// Temps restant en mil·lisegons durant el qual JI_KeyPressed/JI_AnyKey
|
||||
// retornen false. Utilitzat per a evitar que pulsacions fortuïtes
|
||||
// saltin cinemàtiques al començament.
|
||||
float wait_ms = 0.0f;
|
||||
float wait_ms = 0.0F;
|
||||
|
||||
// Per a calcular el delta entre crides a JI_Update sense que els callers
|
||||
// hagen de passar-lo explícitament. Es reinicia a la primera crida.
|
||||
@@ -29,7 +30,7 @@ namespace {
|
||||
|
||||
Uint8 virtual_keystates[JI_VSRC_COUNT][SDL_SCANCODE_COUNT] = {{0}};
|
||||
|
||||
Uint8 scancode_to_ascii(Uint8 scancode) {
|
||||
auto scancode_to_ascii(Uint8 scancode) -> Uint8 {
|
||||
if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) {
|
||||
return static_cast<Uint8>('a' + (scancode - SDL_SCANCODE_A));
|
||||
}
|
||||
@@ -47,8 +48,12 @@ void JI_SetInputBlocked(bool blocked) {
|
||||
}
|
||||
|
||||
void JI_SetVirtualKey(int scancode, int source, bool pressed) {
|
||||
if (scancode < 0 || scancode >= SDL_SCANCODE_COUNT) return;
|
||||
if (source < 0 || source >= JI_VSRC_COUNT) return;
|
||||
if (scancode < 0 || scancode >= SDL_SCANCODE_COUNT) {
|
||||
return;
|
||||
}
|
||||
if (source < 0 || source >= JI_VSRC_COUNT) {
|
||||
return;
|
||||
}
|
||||
virtual_keystates[source][scancode] = pressed ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -64,49 +69,67 @@ void JI_Update() {
|
||||
// El director ha processat tots els events. Ací només refresquem
|
||||
// el snapshot del teclat i consumim el flag de tecla polsada.
|
||||
if (keystates == nullptr) {
|
||||
keystates = SDL_GetKeyboardState(NULL);
|
||||
keystates = SDL_GetKeyboardState(nullptr);
|
||||
}
|
||||
|
||||
const Uint64 now = SDL_GetTicks();
|
||||
if (last_update_tick == 0) last_update_tick = now;
|
||||
const float delta_ms = static_cast<float>(now - last_update_tick);
|
||||
if (last_update_tick == 0) {
|
||||
last_update_tick = now;
|
||||
}
|
||||
const auto delta_ms = static_cast<float>(now - last_update_tick);
|
||||
last_update_tick = now;
|
||||
|
||||
if (wait_ms > 0.0f) {
|
||||
if (wait_ms > 0.0F) {
|
||||
wait_ms -= delta_ms;
|
||||
if (wait_ms < 0.0f) wait_ms = 0.0f;
|
||||
wait_ms = std::max(wait_ms, 0.0f);
|
||||
}
|
||||
|
||||
// Consumim el flag de "alguna tecla no-GUI polsada" del director
|
||||
key_pressed = Director::get()->consumeKeyPressed();
|
||||
}
|
||||
|
||||
bool JI_KeyPressed(int key) {
|
||||
if (wait_ms > 0.0f || keystates == nullptr) return false;
|
||||
auto JI_KeyPressed(int key) -> bool {
|
||||
if (wait_ms > 0.0F || keystates == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// Input bloquejat (p.ex. menú flotant obert)
|
||||
if (input_blocked) return false;
|
||||
if (input_blocked) {
|
||||
return false;
|
||||
}
|
||||
// ESC bloquejada pel Director (primera pulsació mostra notificació)
|
||||
if (key == SDL_SCANCODE_ESCAPE && Director::get()->isEscBlocked()) return false;
|
||||
if (key < 0 || key >= SDL_SCANCODE_COUNT) return false;
|
||||
if (keystates[key] != 0) return true;
|
||||
for (int src = 0; src < JI_VSRC_COUNT; src++) {
|
||||
if (virtual_keystates[src][key] != 0) return true;
|
||||
if (key == SDL_SCANCODE_ESCAPE && Director::get()->isEscBlocked()) {
|
||||
return false;
|
||||
}
|
||||
if (key < 0 || key >= SDL_SCANCODE_COUNT) {
|
||||
return false;
|
||||
}
|
||||
if (static_cast<int>(keystates[key]) != 0) {
|
||||
return true;
|
||||
}
|
||||
for (auto& virtual_keystate : virtual_keystates) {
|
||||
if (virtual_keystate[key] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool JI_CheatActivated(const char* cheat_code) {
|
||||
auto JI_CheatActivated(const char* cheat_code) -> bool {
|
||||
const size_t len = std::strlen(cheat_code);
|
||||
if (len > sizeof(cheat)) return false;
|
||||
if (len > sizeof(cheat)) {
|
||||
return false;
|
||||
}
|
||||
// Compara contra els últims `len` caràcters del buffer. El buffer té
|
||||
// mida fixa 5 i acumula sempre el darrer tecle a la posició 4.
|
||||
const size_t offset = sizeof(cheat) - len;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (cheat[offset + i] != static_cast<Uint8>(cheat_code[i])) return false;
|
||||
if (cheat[offset + i] != static_cast<Uint8>(cheat_code[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JI_AnyKey() {
|
||||
return wait_ms > 0.0f ? false : key_pressed;
|
||||
auto JI_AnyKey() -> bool {
|
||||
return wait_ms > 0.0F ? false : key_pressed;
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ void JI_SetInputBlocked(bool blocked);
|
||||
enum JI_VirtualSource {
|
||||
JI_VSRC_GAMEPAD = 0,
|
||||
JI_VSRC_REMAP = 1,
|
||||
JI_VSRC_COUNT
|
||||
JI_VSRC_COUNT = 2
|
||||
};
|
||||
void JI_SetVirtualKey(int scancode, int source, bool pressed);
|
||||
|
||||
void JI_Update();
|
||||
|
||||
bool JI_KeyPressed(int key);
|
||||
auto JI_KeyPressed(int key) -> bool;
|
||||
|
||||
bool JI_CheatActivated(const char* cheat_code);
|
||||
auto JI_CheatActivated(const char* cheat_code) -> bool;
|
||||
|
||||
bool JI_AnyKey();
|
||||
auto JI_AnyKey() -> bool;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Locale {
|
||||
static void traverse(const fkyaml::node& node, const std::string& prefix) {
|
||||
if (node.is_mapping()) {
|
||||
for (auto it = node.begin(); it != node.end(); ++it) {
|
||||
std::string key = it.key().get_value<std::string>();
|
||||
auto key = it.key().get_value<std::string>();
|
||||
std::string full = prefix.empty() ? key : prefix + "." + key;
|
||||
traverse(it.value(), full);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Locale {
|
||||
}
|
||||
}
|
||||
|
||||
bool load(const char* filename) {
|
||||
auto load(const char* filename) -> bool {
|
||||
auto buffer = ResourceHelper::loadFile(filename);
|
||||
if (buffer.empty()) {
|
||||
std::cerr << "Locale: unable to load " << filename << '\n';
|
||||
@@ -48,7 +48,9 @@ namespace Locale {
|
||||
|
||||
auto get(const char* key) -> const char* {
|
||||
auto it = strings_.find(key);
|
||||
if (it != strings_.end()) return it->second.c_str();
|
||||
if (it != strings_.end()) {
|
||||
return it->second.c_str();
|
||||
}
|
||||
return key; // fallback: retorna la clau mateixa
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Les claus són nested amb notació punt ("menu.items.zoom").
|
||||
// Si una clau no existeix, Locale::get torna la clau mateixa (útil per debug).
|
||||
namespace Locale {
|
||||
bool load(const char* filename);
|
||||
auto load(const char* filename) -> bool;
|
||||
|
||||
// Retorna la cadena associada a la clau. El punter és estable durant tota la
|
||||
// sessió (no canvia), per tant es pot guardar en const char*.
|
||||
|
||||
+114
-72
@@ -1,5 +1,6 @@
|
||||
#include "core/rendering/menu.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
@@ -72,16 +73,20 @@ namespace Menu {
|
||||
std::string subtitle; // opcional — si no buit, es dibuixa sota el títol
|
||||
};
|
||||
|
||||
static bool isVisible(const Item& it) { return !it.visible || it.visible(); }
|
||||
static auto isVisible(const Item& it) -> bool { return !it.visible || it.visible(); }
|
||||
|
||||
// Troba el pròxim ítem visible en direcció `dir` (±1) a partir de `from`.
|
||||
// Si cap és visible retorna `from`.
|
||||
static int nextVisibleCursor(const Page& p, int from, int dir) {
|
||||
static auto nextVisibleCursor(const Page& p, int from, int dir) -> int {
|
||||
const int n = static_cast<int>(p.items.size());
|
||||
if (n <= 0) return from;
|
||||
if (n <= 0) {
|
||||
return from;
|
||||
}
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
int idx = ((from + dir * i) % n + n) % n;
|
||||
if (isVisible(p.items[idx])) return idx;
|
||||
if (isVisible(p.items[idx])) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
return from;
|
||||
}
|
||||
@@ -97,7 +102,7 @@ namespace Menu {
|
||||
|
||||
// --- Transició entre pàgines ---
|
||||
static constexpr float TRANSITION_SPEED = 5.5F; // ~180 ms
|
||||
static Page transition_outgoing_{"", {}, 0};
|
||||
static Page transition_outgoing_{.title = "", .items = {}, .cursor = 0};
|
||||
static bool transition_active_{false};
|
||||
static float transition_progress_{1.0F};
|
||||
static int transition_dir_{+1}; // +1 endavant, -1 enrere
|
||||
@@ -120,19 +125,19 @@ namespace Menu {
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
static std::string yesNo(bool b) { return b ? Locale::get("menu.values.yes") : Locale::get("menu.values.no"); }
|
||||
static std::string onOff(bool b) { return b ? Locale::get("menu.values.on") : Locale::get("menu.values.off"); }
|
||||
static auto yesNo(bool b) -> std::string { return b ? Locale::get("menu.values.yes") : Locale::get("menu.values.no"); }
|
||||
static auto onOff(bool b) -> std::string { return b ? Locale::get("menu.values.on") : Locale::get("menu.values.off"); }
|
||||
|
||||
// --- Builders de pàgines ---
|
||||
|
||||
static Page buildVideo();
|
||||
static Page buildAudio();
|
||||
static Page buildControls();
|
||||
static Page buildGame();
|
||||
static Page buildSystem();
|
||||
static auto buildVideo() -> Page;
|
||||
static auto buildAudio() -> Page;
|
||||
static auto buildControls() -> Page;
|
||||
static auto buildGame() -> Page;
|
||||
static auto buildSystem() -> Page;
|
||||
|
||||
static Page buildRoot() {
|
||||
Page p{Locale::get("menu.titles.root"), {}, 0};
|
||||
static auto buildRoot() -> Page {
|
||||
Page p{.title = Locale::get("menu.titles.root"), .items = {}, .cursor = 0};
|
||||
p.items.push_back({Locale::get("menu.items.video"), ItemKind::Submenu, nullptr, nullptr, [] { pushPage(buildVideo()); }, nullptr});
|
||||
p.items.push_back({Locale::get("menu.items.audio"), ItemKind::Submenu, nullptr, nullptr, [] { pushPage(buildAudio()); }, nullptr});
|
||||
p.items.push_back({Locale::get("menu.items.controls"), ItemKind::Submenu, nullptr, nullptr, [] { pushPage(buildControls()); }, nullptr});
|
||||
@@ -141,8 +146,8 @@ namespace Menu {
|
||||
return p;
|
||||
}
|
||||
|
||||
static Page buildVideo() {
|
||||
Page p{Locale::get("menu.titles.video"), {}, 0};
|
||||
static auto buildVideo() -> Page {
|
||||
Page p{.title = Locale::get("menu.titles.video"), .items = {}, .cursor = 0};
|
||||
|
||||
// Zoom i fullscreen: sense sentit a WASM (el navegador posseix el canvas)
|
||||
#ifndef __EMSCRIPTEN__
|
||||
@@ -150,8 +155,9 @@ namespace Menu {
|
||||
char buf[16];
|
||||
std::snprintf(buf, sizeof(buf), "%dX", Screen::get()->getZoom());
|
||||
return std::string(buf); }, [](int dir) {
|
||||
if (dir < 0) Screen::get()->decZoom();
|
||||
else if (dir > 0) Screen::get()->incZoom(); }, nullptr, nullptr});
|
||||
if (dir < 0) { Screen::get()->decZoom();
|
||||
} else if (dir > 0) { Screen::get()->incZoom();
|
||||
} }, nullptr, nullptr});
|
||||
|
||||
p.items.push_back({Locale::get("menu.items.screen"), ItemKind::Toggle, [] { return std::string(Screen::get()->isFullscreen() ? Locale::get("menu.values.fullscreen") : Locale::get("menu.values.windowed")); }, [](int) { Screen::get()->toggleFullscreen(); }, nullptr, nullptr, nullptr});
|
||||
#endif
|
||||
@@ -185,15 +191,18 @@ namespace Menu {
|
||||
p.items.push_back({Locale::get("menu.items.shader"), ItemKind::Toggle, [] { return onOff(Options::video.shader_enabled); }, [](int) { Screen::get()->toggleShaders(); }, nullptr, nullptr, nullptr});
|
||||
|
||||
p.items.push_back({Locale::get("menu.items.shader_type"), ItemKind::Cycle, [] { return std::string(Screen::get()->getActiveShaderName()); }, [](int dir) {
|
||||
if (dir < 0) Screen::get()->prevShaderType();
|
||||
else Screen::get()->nextShaderType(); }, nullptr, nullptr, [] { return Options::video.shader_enabled; }});
|
||||
if (dir < 0) { Screen::get()->prevShaderType();
|
||||
} else { Screen::get()->nextShaderType();
|
||||
} }, nullptr, nullptr, [] { return Options::video.shader_enabled; }});
|
||||
|
||||
p.items.push_back({Locale::get("menu.items.preset"), ItemKind::Cycle, [] { return std::string(Screen::get()->getCurrentPresetName()); }, [](int dir) {
|
||||
if (dir < 0) Screen::get()->prevPreset();
|
||||
else Screen::get()->nextPreset(); }, nullptr, nullptr, [] { return Options::video.shader_enabled; }});
|
||||
if (dir < 0) { Screen::get()->prevPreset();
|
||||
} else { Screen::get()->nextPreset();
|
||||
} }, nullptr, nullptr, [] { return Options::video.shader_enabled; }});
|
||||
|
||||
p.items.push_back({Locale::get("menu.items.supersampling"), ItemKind::Toggle, [] { return onOff(Options::video.supersampling); }, [](int) { Screen::get()->toggleSupersampling(); }, nullptr, nullptr, [] {
|
||||
if (!Options::video.shader_enabled) return false;
|
||||
if (!Options::video.shader_enabled) { return false;
|
||||
}
|
||||
const char* name = Screen::get()->getActiveShaderName();
|
||||
return name && std::string(name) == "POSTFX"; }});
|
||||
#endif
|
||||
@@ -213,10 +222,10 @@ namespace Menu {
|
||||
}
|
||||
|
||||
// Converteix volum 0..1 a percentatge i ho formata com "50%"
|
||||
static std::string volPct(float v) {
|
||||
int pct = static_cast<int>(v * 100.0F + 0.5F);
|
||||
if (pct < 0) pct = 0;
|
||||
if (pct > 100) pct = 100;
|
||||
static auto volPct(float v) -> std::string {
|
||||
int pct = static_cast<int>((v * 100.0F) + 0.5F);
|
||||
pct = std::max(pct, 0);
|
||||
pct = std::min(pct, 100);
|
||||
char buf[8];
|
||||
std::snprintf(buf, sizeof(buf), "%d%%", pct);
|
||||
return std::string(buf);
|
||||
@@ -225,13 +234,13 @@ namespace Menu {
|
||||
// Canvi +/- d'un volum en steps de 0.05 (5%) amb clamping
|
||||
static void stepVolume(float& v, int dir) {
|
||||
v += (dir >= 0 ? 0.05F : -0.05F);
|
||||
if (v < 0.0F) v = 0.0F;
|
||||
if (v > 1.0F) v = 1.0F;
|
||||
v = std::max(v, 0.0F);
|
||||
v = std::min(v, 1.0F);
|
||||
Options::applyAudio();
|
||||
}
|
||||
|
||||
static Page buildControls() {
|
||||
Page p{Locale::get("menu.titles.controls"), {}, 0};
|
||||
static auto buildControls() -> Page {
|
||||
Page p{.title = Locale::get("menu.titles.controls"), .items = {}, .cursor = 0};
|
||||
p.items.push_back({Locale::get("menu.items.move_up"), ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.up});
|
||||
p.items.push_back({Locale::get("menu.items.move_down"), ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.down});
|
||||
p.items.push_back({Locale::get("menu.items.move_left"), ItemKind::KeyBind, nullptr, nullptr, nullptr, &Options::keys_game.left});
|
||||
@@ -240,8 +249,8 @@ namespace Menu {
|
||||
return p;
|
||||
}
|
||||
|
||||
static Page buildAudio() {
|
||||
Page p{Locale::get("menu.titles.audio"), {}, 0};
|
||||
static auto buildAudio() -> Page {
|
||||
Page p{.title = Locale::get("menu.titles.audio"), .items = {}, .cursor = 0};
|
||||
|
||||
p.items.push_back({Locale::get("menu.items.master_enable"), ItemKind::Toggle, [] { return onOff(Options::audio.enabled); }, [](int) {
|
||||
Options::audio.enabled = !Options::audio.enabled;
|
||||
@@ -264,8 +273,8 @@ namespace Menu {
|
||||
return p;
|
||||
}
|
||||
|
||||
static Page buildGame() {
|
||||
Page p{Locale::get("menu.titles.game"), {}, 0};
|
||||
static auto buildGame() -> Page {
|
||||
Page p{.title = Locale::get("menu.titles.game"), .items = {}, .cursor = 0};
|
||||
|
||||
p.items.push_back({Locale::get("menu.items.use_new_logo"), ItemKind::Toggle, [] { return yesNo(Options::game.use_new_logo); }, [](int) { Options::game.use_new_logo = !Options::game.use_new_logo; }, nullptr});
|
||||
|
||||
@@ -276,19 +285,23 @@ namespace Menu {
|
||||
return p;
|
||||
}
|
||||
|
||||
static Page buildSystem() {
|
||||
Page p{Locale::get("menu.titles.system"), {}, 0};
|
||||
static auto buildSystem() -> Page {
|
||||
Page p{.title = Locale::get("menu.titles.system"), .items = {}, .cursor = 0};
|
||||
p.subtitle = std::string("v") + Texts::VERSION + " (" + Version::GIT_HASH + ")";
|
||||
|
||||
p.items.push_back({Locale::get("menu.items.restart"), ItemKind::Action, nullptr, nullptr, [] {
|
||||
if (Director::get()) Director::get()->requestRestart();
|
||||
if (Director::get()) {
|
||||
Director::get()->requestRestart();
|
||||
}
|
||||
},
|
||||
nullptr,
|
||||
nullptr});
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
p.items.push_back({Locale::get("menu.items.exit_game"), ItemKind::Action, nullptr, nullptr, [] {
|
||||
if (Director::get()) Director::get()->requestQuit();
|
||||
if (Director::get()) {
|
||||
Director::get()->requestQuit();
|
||||
}
|
||||
},
|
||||
nullptr,
|
||||
nullptr});
|
||||
@@ -307,10 +320,14 @@ namespace Menu {
|
||||
const Uint8 sb = (src_argb >> 16) & 0xFF;
|
||||
const Uint8 inv = 255 - sa;
|
||||
for (int row = y; row < y + h; row++) {
|
||||
if (row < 0 || row >= SCREEN_H) continue;
|
||||
if (row < 0 || row >= SCREEN_H) {
|
||||
continue;
|
||||
}
|
||||
for (int col = x; col < x + w; col++) {
|
||||
if (col < 0 || col >= SCREEN_W) continue;
|
||||
Uint32* p = &buf[col + row * SCREEN_W];
|
||||
if (col < 0 || col >= SCREEN_W) {
|
||||
continue;
|
||||
}
|
||||
Uint32* p = &buf[col + (row * SCREEN_W)];
|
||||
Uint32 dst = *p;
|
||||
Uint8 dr = dst & 0xFF;
|
||||
Uint8 dg = (dst >> 8) & 0xFF;
|
||||
@@ -318,17 +335,21 @@ namespace Menu {
|
||||
Uint8 r = (sr * sa + dr * inv) / 255;
|
||||
Uint8 g = (sg * sa + dg * inv) / 255;
|
||||
Uint8 b = (sb * sa + db * inv) / 255;
|
||||
*p = 0xFF000000u | (static_cast<Uint32>(b) << 16) | (static_cast<Uint32>(g) << 8) | r;
|
||||
*p = 0xFF000000U | (static_cast<Uint32>(b) << 16) | (static_cast<Uint32>(g) << 8) | r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fillRect(Uint32* buf, int x, int y, int w, int h, Uint32 color) {
|
||||
for (int row = y; row < y + h; row++) {
|
||||
if (row < 0 || row >= SCREEN_H) continue;
|
||||
if (row < 0 || row >= SCREEN_H) {
|
||||
continue;
|
||||
}
|
||||
for (int col = x; col < x + w; col++) {
|
||||
if (col < 0 || col >= SCREEN_W) continue;
|
||||
buf[col + row * SCREEN_W] = color;
|
||||
if (col < 0 || col >= SCREEN_W) {
|
||||
continue;
|
||||
}
|
||||
buf[col + (row * SCREEN_W)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,12 +364,14 @@ namespace Menu {
|
||||
// Mida final de la caixa segons el nombre d'items *visibles*.
|
||||
// body = (N-1) * ITEM_SPACING + charH — així BOTTOM_PAD és el buit real
|
||||
// sota el text del darrer ítem, no un buit extra per sobre d'un "slot" buit.
|
||||
static int boxHeight(const Page& page) {
|
||||
static auto boxHeight(const Page& page) -> int {
|
||||
int n = 0;
|
||||
for (const auto& it : page.items) {
|
||||
if (isVisible(it)) ++n;
|
||||
if (isVisible(it)) {
|
||||
++n;
|
||||
}
|
||||
}
|
||||
int body = (n == 0) ? 8 : (n - 1) * ITEM_SPACING + 8;
|
||||
int body = (n == 0) ? 8 : ((n - 1) * ITEM_SPACING) + 8;
|
||||
int header = HEADER_H + (page.subtitle.empty() ? 0 : SUBTITLE_H);
|
||||
return header + body + BOTTOM_PAD;
|
||||
}
|
||||
@@ -403,7 +426,9 @@ namespace Menu {
|
||||
// render() faça decréixer open_anim_ fins a 0. En aquell moment es neteja
|
||||
// l'estat. Si es crida estant ja tancat o tancant-se, no-op.
|
||||
void close() {
|
||||
if (stack_.empty() || closing_) return;
|
||||
if (stack_.empty() || closing_) {
|
||||
return;
|
||||
}
|
||||
closing_ = true;
|
||||
capturing_ = nullptr;
|
||||
transition_active_ = false;
|
||||
@@ -416,7 +441,9 @@ namespace Menu {
|
||||
}
|
||||
|
||||
void captureKey(SDL_Scancode sc) {
|
||||
if (!capturing_) return;
|
||||
if (capturing_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (sc == SDL_SCANCODE_ESCAPE) {
|
||||
// Cancel·la
|
||||
capturing_ = nullptr;
|
||||
@@ -427,15 +454,18 @@ namespace Menu {
|
||||
}
|
||||
|
||||
void handleKey(SDL_Scancode sc) {
|
||||
if (!isOpen()) return;
|
||||
if (!isOpen()) {
|
||||
return;
|
||||
}
|
||||
Page& page = stack_.back();
|
||||
if (page.items.empty()) {
|
||||
// Pàgina buida — només backspace surt
|
||||
if (sc == SDL_SCANCODE_BACKSPACE) {
|
||||
if (stack_.size() > 1)
|
||||
if (stack_.size() > 1) {
|
||||
popPage();
|
||||
else
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -467,7 +497,9 @@ namespace Menu {
|
||||
case SDL_SCANCODE_KP_ENTER:
|
||||
if (page.items[page.cursor].kind == ItemKind::Submenu ||
|
||||
page.items[page.cursor].kind == ItemKind::Action) {
|
||||
if (page.items[page.cursor].enter) page.items[page.cursor].enter();
|
||||
if (page.items[page.cursor].enter) {
|
||||
page.items[page.cursor].enter();
|
||||
}
|
||||
} else if (page.items[page.cursor].kind == ItemKind::KeyBind) {
|
||||
capturing_ = page.items[page.cursor].scancode;
|
||||
} else if (page.items[page.cursor].change) {
|
||||
@@ -475,10 +507,11 @@ namespace Menu {
|
||||
}
|
||||
break;
|
||||
case SDL_SCANCODE_BACKSPACE:
|
||||
if (stack_.size() > 1)
|
||||
if (stack_.size() > 1) {
|
||||
popPage();
|
||||
else
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -500,7 +533,7 @@ namespace Menu {
|
||||
static void renderPageContent(Uint32* pixel_data, const Page& page, int box_x, int box_y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) {
|
||||
// Títol
|
||||
int title_w = font_->width(page.title);
|
||||
int title_x = box_x + (BOX_W - title_w) / 2 + x_offset;
|
||||
int title_x = box_x + ((BOX_W - title_w) / 2) + x_offset;
|
||||
font_->drawClipped(pixel_data, title_x, box_y + TITLE_PAD_Y, page.title, TITLE_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
|
||||
// Línia sota el títol (també lliscada) — clippada manualment
|
||||
@@ -519,26 +552,31 @@ namespace Menu {
|
||||
int items_y = title_line_y + 4;
|
||||
if (!page.subtitle.empty()) {
|
||||
int sub_w = font_->width(page.subtitle.c_str());
|
||||
int sub_x = box_x + (BOX_W - sub_w) / 2 + x_offset;
|
||||
int sub_x = box_x + ((BOX_W - sub_w) / 2) + x_offset;
|
||||
font_->drawClipped(pixel_data, sub_x, items_y, page.subtitle.c_str(), LABEL_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
items_y += SUBTITLE_H;
|
||||
}
|
||||
// Compta visibles — si cap, dibuixa placeholder (caixa totalment col·lapsada però oberta)
|
||||
int visible_count = 0;
|
||||
for (const auto& it : page.items)
|
||||
if (isVisible(it)) ++visible_count;
|
||||
for (const auto& it : page.items) {
|
||||
if (isVisible(it)) {
|
||||
++visible_count;
|
||||
}
|
||||
}
|
||||
if (visible_count == 0) {
|
||||
const char* empty_text = Locale::get("menu.values.empty");
|
||||
int ew = font_->width(empty_text);
|
||||
font_->drawClipped(pixel_data, box_x + (BOX_W - ew) / 2 + x_offset, items_y + 2, empty_text, EMPTY_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
font_->drawClipped(pixel_data, box_x + ((BOX_W - ew) / 2) + x_offset, items_y + 2, empty_text, EMPTY_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
return;
|
||||
}
|
||||
|
||||
int y_slot = 0; // índex de fila visible (independent de l'índex real de l'ítem)
|
||||
for (size_t i = 0; i < page.items.size(); i++) {
|
||||
const Item& item = page.items[i];
|
||||
if (!isVisible(item)) continue;
|
||||
int y = items_y + y_slot * ITEM_SPACING;
|
||||
if (!isVisible(item)) {
|
||||
continue;
|
||||
}
|
||||
int y = items_y + (y_slot * ITEM_SPACING);
|
||||
++y_slot;
|
||||
bool selected = (static_cast<int>(i) == page.cursor);
|
||||
Uint32 label_color = selected ? CURSOR_COLOR : LABEL_COLOR;
|
||||
@@ -546,7 +584,7 @@ namespace Menu {
|
||||
// Action: sense valor a la dreta — centrem el label amb el cursor just a l'esquerra.
|
||||
if (item.kind == ItemKind::Action) {
|
||||
int lw = font_->width(item.label);
|
||||
int lx = box_x + (BOX_W - lw) / 2 + x_offset;
|
||||
int lx = box_x + ((BOX_W - lw) / 2) + x_offset;
|
||||
if (selected) {
|
||||
font_->drawClipped(pixel_data, lx - font_->width("> "), y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
@@ -567,8 +605,10 @@ namespace Menu {
|
||||
font_->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - aw + x_offset, y, arrow, ac, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
} else if (item.kind == ItemKind::KeyBind) {
|
||||
bool this_capturing = (capturing_ == item.scancode);
|
||||
const char* text = this_capturing ? Locale::get("menu.values.press_key") : (item.scancode ? SDL_GetScancodeName(*item.scancode) : "");
|
||||
if (!text || !*text) text = Locale::get("menu.values.unknown");
|
||||
const char* text = this_capturing ? Locale::get("menu.values.press_key") : ((item.scancode != nullptr) ? SDL_GetScancodeName(*item.scancode) : "");
|
||||
if ((text == nullptr) || (*text == 0)) {
|
||||
text = Locale::get("menu.values.unknown");
|
||||
}
|
||||
int tw = font_->width(text);
|
||||
Uint32 tc = this_capturing ? 0xFF00FFFF : (selected ? CURSOR_COLOR : VALUE_COLOR);
|
||||
font_->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - tw + x_offset, y, text, tc, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
@@ -582,7 +622,9 @@ namespace Menu {
|
||||
}
|
||||
|
||||
void render(Uint32* pixel_data) {
|
||||
if (!isVisible() || !font_ || !pixel_data) return;
|
||||
if (!isVisible() || !font_ || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delta time
|
||||
Uint32 now = SDL_GetTicks();
|
||||
@@ -600,7 +642,7 @@ namespace Menu {
|
||||
}
|
||||
} else if (open_anim_ < 1.0F) {
|
||||
open_anim_ += OPEN_SPEED * dt;
|
||||
if (open_anim_ > 1.0F) open_anim_ = 1.0F;
|
||||
open_anim_ = std::min(open_anim_, 1.0F);
|
||||
}
|
||||
|
||||
// Avança transició
|
||||
@@ -626,7 +668,7 @@ namespace Menu {
|
||||
animated_h_ = static_cast<float>(current_h);
|
||||
} else {
|
||||
float t = HEIGHT_RATE * dt;
|
||||
if (t > 1.0F) t = 1.0F;
|
||||
t = std::min(t, 1.0F);
|
||||
animated_h_ += diff * t;
|
||||
}
|
||||
}
|
||||
@@ -643,12 +685,12 @@ namespace Menu {
|
||||
|
||||
// Caixa creix verticalment durant l'obertura
|
||||
int box_h = static_cast<int>(target_h * eased);
|
||||
if (box_h < 2) box_h = 2;
|
||||
box_h = std::max(box_h, 2);
|
||||
int box_x = (SCREEN_W - BOX_W) / 2;
|
||||
int box_y = (SCREEN_H - box_h) / 2;
|
||||
|
||||
// Fons semi-transparent (alpha escalat per l'animació d'obertura)
|
||||
Uint8 alpha = static_cast<Uint8>(BG_ALPHA * eased);
|
||||
auto alpha = static_cast<Uint8>(BG_ALPHA * eased);
|
||||
blendRect(pixel_data, box_x, box_y, BOX_W, box_h, BG_COLOR, alpha);
|
||||
|
||||
// El contingut només apareix quan la caixa és prou gran
|
||||
|
||||
@@ -102,16 +102,22 @@ namespace Overlay {
|
||||
// Pinta un rectangle sòlid dins els límits de la pantalla
|
||||
static void drawRect(Uint32* pixel_data, int rx, int ry, int rw, int rh, Uint32 color) {
|
||||
for (int row = ry; row < ry + rh; row++) {
|
||||
if (row < 0 || row >= SCREEN_H) continue;
|
||||
if (row < 0 || row >= SCREEN_H) {
|
||||
continue;
|
||||
}
|
||||
for (int col = rx; col < rx + rw; col++) {
|
||||
if (col < 0 || col >= SCREEN_W) continue;
|
||||
pixel_data[col + row * SCREEN_W] = color;
|
||||
if (col < 0 || col >= SCREEN_W) {
|
||||
continue;
|
||||
}
|
||||
pixel_data[col + (row * SCREEN_W)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void render(Uint32* pixel_data) {
|
||||
if (!font_ || !pixel_data) return;
|
||||
if (!font_ || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calcula delta time
|
||||
Uint32 now = SDL_GetTicks();
|
||||
@@ -148,7 +154,9 @@ namespace Overlay {
|
||||
break;
|
||||
}
|
||||
|
||||
if (notif.status == Status::FINISHED) continue;
|
||||
if (notif.status == Status::FINISHED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Posició segons el tipus
|
||||
int box_x = 0;
|
||||
@@ -180,7 +188,7 @@ namespace Overlay {
|
||||
int line_y = box_y + NOTIF_PADDING_V;
|
||||
for (const auto& line : notif.lines) {
|
||||
int line_w = font_->width(line.c_str());
|
||||
int line_x = box_x + (notif.box_w - line_w) / 2; // centrat dins la caixa
|
||||
int line_x = box_x + ((notif.box_w - line_w) / 2); // centrat dins la caixa
|
||||
if (notif.style == NotifStyle::SHADOW) {
|
||||
font_->draw(pixel_data, line_x + 1, line_y + 1, line.c_str(), notif.accent_color);
|
||||
} else if (notif.style == NotifStyle::OUTLINE) {
|
||||
@@ -203,7 +211,7 @@ namespace Overlay {
|
||||
// Mateix lloc: entra fins a 1
|
||||
if (info_anim_ < 1.0F) {
|
||||
info_anim_ += INFO_SLIDE_SPEED * dt;
|
||||
if (info_anim_ > 1.0F) info_anim_ = 1.0F;
|
||||
info_anim_ = std::min(info_anim_, 1.0F);
|
||||
}
|
||||
} else {
|
||||
// Canvi: si visible_pos està OFF, commuta directament
|
||||
@@ -225,10 +233,10 @@ namespace Overlay {
|
||||
float target = seg.visible ? 1.0F : 0.0F;
|
||||
if (seg.anim < target) {
|
||||
seg.anim += SEG_SPEED * dt;
|
||||
if (seg.anim > target) seg.anim = target;
|
||||
seg.anim = std::min(seg.anim, target);
|
||||
} else if (seg.anim > target) {
|
||||
seg.anim -= SEG_SPEED * dt;
|
||||
if (seg.anim < target) seg.anim = target;
|
||||
seg.anim = std::max(seg.anim, target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,9 +291,7 @@ namespace Overlay {
|
||||
}
|
||||
|
||||
// Elimina les acabades
|
||||
notifications_.erase(
|
||||
std::remove_if(notifications_.begin(), notifications_.end(), [](const Notification& n) { return n.status == Status::FINISHED; }),
|
||||
notifications_.end());
|
||||
std::erase_if(notifications_, [](const Notification& n) { return n.status == Status::FINISHED; });
|
||||
|
||||
// Si la notificació d'ESC ha desaparegut, reseteja l'estat
|
||||
if (esc_waiting_ && notifications_.empty()) {
|
||||
@@ -293,7 +299,7 @@ namespace Overlay {
|
||||
}
|
||||
|
||||
// Indicador de pausa persistent (cantó superior dret)
|
||||
if (Director::get() && Director::get()->isPaused()) {
|
||||
if ((Director::get() != nullptr) && Director::get()->isPaused()) {
|
||||
const char* pause_text = Locale::get("notifications.pause");
|
||||
int w = font_->width(pause_text);
|
||||
int x = SCREEN_W - w - 4;
|
||||
@@ -357,9 +363,7 @@ namespace Overlay {
|
||||
}
|
||||
|
||||
// Neteja notificacions finalitzades
|
||||
notifications_.erase(
|
||||
std::remove_if(notifications_.begin(), notifications_.end(), [](const Notification& n) { return n.status == Status::FINISHED; }),
|
||||
notifications_.end());
|
||||
std::erase_if(notifications_, [](const Notification& n) { return n.status == Status::FINISHED; });
|
||||
|
||||
// Menú flotant per damunt de tot (isVisible inclou l'animació de tancament)
|
||||
if (Menu::isVisible()) {
|
||||
@@ -392,7 +396,7 @@ namespace Overlay {
|
||||
int max_w = 0;
|
||||
for (const auto& line : lines) {
|
||||
int w = font_->width(line.c_str());
|
||||
if (w > max_w) max_w = w;
|
||||
max_w = std::max(w, max_w);
|
||||
}
|
||||
notif.box_w = max_w + NOTIF_PADDING_H * 2;
|
||||
int line_h = font_->charHeight();
|
||||
@@ -414,7 +418,7 @@ namespace Overlay {
|
||||
void setRenderInfoSegments(const char* s0, const char* s1, const char* s2, const char* s3, unsigned int mono_mask) {
|
||||
const char* segs[INFO_SEGMENT_COUNT] = {s0, s1, s2, s3};
|
||||
for (int i = 0; i < INFO_SEGMENT_COUNT; i++) {
|
||||
info_segments_[i].mono_digits = (mono_mask >> i) & 1u;
|
||||
info_segments_[i].mono_digits = (((mono_mask >> i) & 1U) != 0u);
|
||||
if (segs[i] != nullptr && *segs[i] != '\0') {
|
||||
info_segments_[i].text = segs[i];
|
||||
info_segments_[i].visible = true;
|
||||
@@ -425,7 +429,9 @@ namespace Overlay {
|
||||
}
|
||||
|
||||
void startCredits() {
|
||||
if (credits_phase_ != CreditsPhase::IDLE) return;
|
||||
if (credits_phase_ != CreditsPhase::IDLE) {
|
||||
return;
|
||||
}
|
||||
credits_phase_ = CreditsPhase::DELAY;
|
||||
credits_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "core/rendering/screen.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
@@ -76,15 +77,15 @@ Screen::Screen() {
|
||||
|
||||
calculateMaxZoom();
|
||||
|
||||
if (zoom_ < 1) zoom_ = 1;
|
||||
if (zoom_ > max_zoom_) zoom_ = max_zoom_;
|
||||
zoom_ = std::max(zoom_, 1);
|
||||
zoom_ = std::min(zoom_, max_zoom_);
|
||||
|
||||
// Clamp de la resolució interna a [1, max_zoom_]. Llegir del YAML i
|
||||
// ajustar aquí és l'únic moment en què es fa — el menú re-clampa cada
|
||||
// canvi. Si la pantalla és més petita que el valor desat (p.ex. canvi
|
||||
// de monitor), baixem al màxim suportat.
|
||||
if (Options::video.internal_resolution < 1) Options::video.internal_resolution = 1;
|
||||
if (Options::video.internal_resolution > max_zoom_) Options::video.internal_resolution = max_zoom_;
|
||||
Options::video.internal_resolution = std::max(Options::video.internal_resolution, 1);
|
||||
Options::video.internal_resolution = std::min(Options::video.internal_resolution, max_zoom_);
|
||||
|
||||
int w = GAME_WIDTH * zoom_;
|
||||
int h = Options::video.aspect_ratio_4_3 ? static_cast<int>(GAME_HEIGHT * 1.2F) * zoom_ : GAME_HEIGHT * zoom_;
|
||||
@@ -124,15 +125,25 @@ Screen::~Screen() {
|
||||
if (shader_backend_) {
|
||||
#ifndef NO_SHADERS
|
||||
auto* gpu = dynamic_cast<Rendering::SDL3GPUShader*>(shader_backend_.get());
|
||||
if (gpu) gpu->destroy();
|
||||
if (gpu != nullptr) {
|
||||
gpu->destroy();
|
||||
}
|
||||
#endif
|
||||
shader_backend_.reset();
|
||||
}
|
||||
|
||||
if (internal_texture_sdl_) SDL_DestroyTexture(internal_texture_sdl_);
|
||||
if (texture_) SDL_DestroyTexture(texture_);
|
||||
if (renderer_) SDL_DestroyRenderer(renderer_);
|
||||
if (window_) SDL_DestroyWindow(window_);
|
||||
if (internal_texture_sdl_ != nullptr) {
|
||||
SDL_DestroyTexture(internal_texture_sdl_);
|
||||
}
|
||||
if (texture_ != nullptr) {
|
||||
SDL_DestroyTexture(texture_);
|
||||
}
|
||||
if (renderer_ != nullptr) {
|
||||
SDL_DestroyRenderer(renderer_);
|
||||
}
|
||||
if (window_ != nullptr) {
|
||||
SDL_DestroyWindow(window_);
|
||||
}
|
||||
}
|
||||
|
||||
void Screen::initShaders() {
|
||||
@@ -143,7 +154,9 @@ void Screen::initShaders() {
|
||||
// curtcircuiten cap al fallback SDL_Renderer.
|
||||
return;
|
||||
#else
|
||||
if (!Options::video.gpu_acceleration) return;
|
||||
if (!Options::video.gpu_acceleration) {
|
||||
return;
|
||||
}
|
||||
|
||||
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
|
||||
@@ -282,19 +295,25 @@ void Screen::toggleFullscreen() {
|
||||
}
|
||||
|
||||
void Screen::incZoom() {
|
||||
if (fullscreen_ || zoom_ >= max_zoom_) return;
|
||||
if (fullscreen_ || zoom_ >= max_zoom_) {
|
||||
return;
|
||||
}
|
||||
zoom_++;
|
||||
adjustWindowSize();
|
||||
}
|
||||
|
||||
void Screen::decZoom() {
|
||||
if (fullscreen_ || zoom_ <= 1) return;
|
||||
if (fullscreen_ || zoom_ <= 1) {
|
||||
return;
|
||||
}
|
||||
zoom_--;
|
||||
adjustWindowSize();
|
||||
}
|
||||
|
||||
void Screen::setZoom(int zoom) {
|
||||
if (zoom < 1 || zoom > max_zoom_ || fullscreen_) return;
|
||||
if (zoom < 1 || zoom > max_zoom_ || fullscreen_) {
|
||||
return;
|
||||
}
|
||||
zoom_ = zoom;
|
||||
adjustWindowSize();
|
||||
}
|
||||
@@ -310,9 +329,15 @@ auto Screen::toggleSupersampling() -> bool {
|
||||
// SS només té sentit amb shaders on i pipeline PostFX (el Lanczos downscale
|
||||
// i el camí SS s'apliquen al pas de PostFX; CRTPI fa el seu propi
|
||||
// submostreig intern i no usa aquesta via).
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return false;
|
||||
if (!Options::video.shader_enabled) return false;
|
||||
if (shader_backend_->getActiveShader() != Rendering::ShaderType::POSTFX) return false;
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||
return false;
|
||||
}
|
||||
if (!Options::video.shader_enabled) {
|
||||
return false;
|
||||
}
|
||||
if (shader_backend_->getActiveShader() != Rendering::ShaderType::POSTFX) {
|
||||
return false;
|
||||
}
|
||||
Options::video.supersampling = !Options::video.supersampling;
|
||||
shader_backend_->setOversample(Options::video.supersampling ? 3 : 1);
|
||||
return true;
|
||||
@@ -366,9 +391,11 @@ void Screen::cycleTextureFilter(int dir) {
|
||||
|
||||
void Screen::changeInternalResolution(int dir) {
|
||||
int next = Options::video.internal_resolution + (dir >= 0 ? 1 : -1);
|
||||
if (next < 1) next = 1;
|
||||
if (next > max_zoom_) next = max_zoom_;
|
||||
if (next == Options::video.internal_resolution) return;
|
||||
next = std::max(next, 1);
|
||||
next = std::min(next, max_zoom_);
|
||||
if (next == Options::video.internal_resolution) {
|
||||
return;
|
||||
}
|
||||
Options::video.internal_resolution = next;
|
||||
|
||||
// Propaga al backend actiu. Al fallback path, la textura es recrea al
|
||||
@@ -381,8 +408,12 @@ void Screen::changeInternalResolution(int dir) {
|
||||
}
|
||||
|
||||
auto Screen::nextShaderType() -> bool {
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return false;
|
||||
if (!Options::video.shader_enabled) return false;
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||
return false;
|
||||
}
|
||||
if (!Options::video.shader_enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
|
||||
shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
|
||||
@@ -397,16 +428,24 @@ auto Screen::nextShaderType() -> bool {
|
||||
}
|
||||
|
||||
auto Screen::nextPreset() -> bool {
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return false;
|
||||
if (!Options::video.shader_enabled) return false;
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||
return false;
|
||||
}
|
||||
if (!Options::video.shader_enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
|
||||
if (Options::postfx_presets.empty()) return false;
|
||||
if (Options::postfx_presets.empty()) {
|
||||
return false;
|
||||
}
|
||||
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
||||
Options::video.current_postfx_preset = Options::postfx_presets[Options::current_postfx_preset].name;
|
||||
applyCurrentPostFXPreset();
|
||||
} else {
|
||||
if (Options::crtpi_presets.empty()) return false;
|
||||
if (Options::crtpi_presets.empty()) {
|
||||
return false;
|
||||
}
|
||||
Options::current_crtpi_preset = (Options::current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
|
||||
Options::video.current_crtpi_preset = Options::crtpi_presets[Options::current_crtpi_preset].name;
|
||||
applyCurrentCrtPiPreset();
|
||||
@@ -420,17 +459,25 @@ auto Screen::prevShaderType() -> bool {
|
||||
}
|
||||
|
||||
auto Screen::prevPreset() -> bool {
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return false;
|
||||
if (!Options::video.shader_enabled) return false;
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||
return false;
|
||||
}
|
||||
if (!Options::video.shader_enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
|
||||
if (Options::postfx_presets.empty()) return false;
|
||||
if (Options::postfx_presets.empty()) {
|
||||
return false;
|
||||
}
|
||||
int n = static_cast<int>(Options::postfx_presets.size());
|
||||
Options::current_postfx_preset = (Options::current_postfx_preset - 1 + n) % n;
|
||||
Options::video.current_postfx_preset = Options::postfx_presets[Options::current_postfx_preset].name;
|
||||
applyCurrentPostFXPreset();
|
||||
} else {
|
||||
if (Options::crtpi_presets.empty()) return false;
|
||||
if (Options::crtpi_presets.empty()) {
|
||||
return false;
|
||||
}
|
||||
int n = static_cast<int>(Options::crtpi_presets.size());
|
||||
Options::current_crtpi_preset = (Options::current_crtpi_preset - 1 + n) % n;
|
||||
Options::video.current_crtpi_preset = Options::crtpi_presets[Options::current_crtpi_preset].name;
|
||||
@@ -440,13 +487,17 @@ auto Screen::prevPreset() -> bool {
|
||||
}
|
||||
|
||||
auto Screen::getCurrentPresetName() const -> const char* {
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return "---";
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||
return "---";
|
||||
}
|
||||
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
|
||||
if (Options::current_postfx_preset < static_cast<int>(Options::postfx_presets.size()))
|
||||
if (Options::current_postfx_preset < static_cast<int>(Options::postfx_presets.size())) {
|
||||
return Options::postfx_presets[Options::current_postfx_preset].name.c_str();
|
||||
}
|
||||
} else {
|
||||
if (Options::current_crtpi_preset < static_cast<int>(Options::crtpi_presets.size()))
|
||||
if (Options::current_crtpi_preset < static_cast<int>(Options::crtpi_presets.size())) {
|
||||
return Options::crtpi_presets[Options::current_crtpi_preset].name.c_str();
|
||||
}
|
||||
}
|
||||
return "---";
|
||||
}
|
||||
@@ -458,7 +509,9 @@ void Screen::setActiveShader(Rendering::ShaderType type) {
|
||||
}
|
||||
|
||||
void Screen::applyCurrentPostFXPreset() {
|
||||
if (!shader_backend_ || Options::postfx_presets.empty()) return;
|
||||
if (!shader_backend_ || Options::postfx_presets.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto& preset = Options::postfx_presets[Options::current_postfx_preset];
|
||||
Rendering::PostFXParams p;
|
||||
p.vignette = preset.vignette;
|
||||
@@ -473,7 +526,9 @@ void Screen::applyCurrentPostFXPreset() {
|
||||
}
|
||||
|
||||
void Screen::applyCurrentCrtPiPreset() {
|
||||
if (!shader_backend_ || Options::crtpi_presets.empty()) return;
|
||||
if (!shader_backend_ || Options::crtpi_presets.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto& preset = Options::crtpi_presets[Options::current_crtpi_preset];
|
||||
Rendering::CrtPiParams p;
|
||||
p.scanline_weight = preset.scanline_weight;
|
||||
@@ -498,7 +553,9 @@ auto Screen::isHardwareAccelerated() const -> bool {
|
||||
}
|
||||
|
||||
auto Screen::getActiveShaderName() const -> const char* {
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return "SENSE GPU";
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||
return "SENSE GPU";
|
||||
}
|
||||
return shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX ? "POSTFX" : "CRT-PI";
|
||||
}
|
||||
|
||||
@@ -534,7 +591,7 @@ void Screen::updateRenderInfo() {
|
||||
fps_driver.c_str(),
|
||||
shader_seg.empty() ? nullptr : shader_seg.c_str(),
|
||||
ss_seg,
|
||||
time_buf[0] ? time_buf : nullptr,
|
||||
(time_buf[0] != 0) ? time_buf : nullptr,
|
||||
0b1001);
|
||||
}
|
||||
|
||||
@@ -544,7 +601,9 @@ void Screen::applyFallbackPresentation() {
|
||||
SDL_ScaleMode scale = (Options::video.texture_filter == Options::TextureFilter::LINEAR)
|
||||
? SDL_SCALEMODE_LINEAR
|
||||
: SDL_SCALEMODE_NEAREST;
|
||||
if (texture_) SDL_SetTextureScaleMode(texture_, scale);
|
||||
if (texture_ != nullptr) {
|
||||
SDL_SetTextureScaleMode(texture_, scale);
|
||||
}
|
||||
|
||||
// Si 4:3 actiu, la finestra ja té aspect 4:3 (alçada × 1.2); STRETCH és
|
||||
// l'única opció viable al path fallback (el GPU path fa l'upscale 4:3 abans
|
||||
@@ -578,7 +637,9 @@ void Screen::applyFallbackPresentation() {
|
||||
}
|
||||
|
||||
void Screen::ensureFallbackInternalTexture() {
|
||||
if (renderer_ == nullptr) return;
|
||||
if (renderer_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
const int mult = Options::video.internal_resolution;
|
||||
if (mult <= 1) {
|
||||
// No cal textura intermèdia — recicla si la teníem.
|
||||
@@ -589,7 +650,9 @@ void Screen::ensureFallbackInternalTexture() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (internal_texture_sdl_ != nullptr && internal_texture_mult_ == mult) return;
|
||||
if (internal_texture_sdl_ != nullptr && internal_texture_mult_ == mult) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (internal_texture_sdl_ != nullptr) {
|
||||
SDL_DestroyTexture(internal_texture_sdl_);
|
||||
@@ -620,11 +683,11 @@ void Screen::adjustWindowSize() {
|
||||
void Screen::calculateMaxZoom() {
|
||||
SDL_DisplayID display = SDL_GetPrimaryDisplay();
|
||||
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
|
||||
if (mode) {
|
||||
if (mode != nullptr) {
|
||||
int max_w = mode->w / GAME_WIDTH;
|
||||
int max_h = mode->h / GAME_HEIGHT;
|
||||
max_zoom_ = (max_w < max_h) ? max_w : max_h;
|
||||
if (max_zoom_ < 1) max_zoom_ = 1;
|
||||
max_zoom_ = std::max(max_zoom_, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -906,8 +906,8 @@ namespace Rendering {
|
||||
|
||||
// ---- Calcular viewport (dimensions lògiques del canvas) ----
|
||||
// Si 4:3 actiu, effective_height ja és 240 (la textura estirada)
|
||||
const float logical_w = static_cast<float>(game_width_);
|
||||
const float logical_h = static_cast<float>(effective_height);
|
||||
const auto logical_w = static_cast<float>(game_width_);
|
||||
const auto logical_h = static_cast<float>(effective_height);
|
||||
|
||||
float vx = 0.0F;
|
||||
float vy = 0.0F;
|
||||
@@ -1276,7 +1276,9 @@ namespace Rendering {
|
||||
// Recrea la textura intermèdia amb les noves dimensions (320·N × 200·N).
|
||||
void SDL3GPUShader::setInternalResolution(int multiplier) {
|
||||
const int NEW = std::max(1, multiplier);
|
||||
if (NEW == internal_res_) return;
|
||||
if (NEW == internal_res_) {
|
||||
return;
|
||||
}
|
||||
internal_res_ = NEW;
|
||||
if (is_initialized_ && device_ != nullptr) {
|
||||
SDL_WaitForGPUIdle(device_);
|
||||
@@ -1286,7 +1288,9 @@ namespace Rendering {
|
||||
|
||||
void SDL3GPUShader::setStretch4_3(bool enabled) {
|
||||
stretch_4_3_ = enabled;
|
||||
if (!is_initialized_ || device_ == nullptr) return;
|
||||
if (!is_initialized_ || device_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recrear scaled_texture_ perquè tinga les dimensions correctes (amb o sense 4:3)
|
||||
if (oversample_ > 1 && ss_factor_ > 0) {
|
||||
@@ -1464,7 +1468,9 @@ namespace Rendering {
|
||||
SDL_ReleaseGPUTexture(device_, internal_texture_);
|
||||
internal_texture_ = nullptr;
|
||||
}
|
||||
if (internal_res_ <= 1 || device_ == nullptr) return true;
|
||||
if (internal_res_ <= 1 || device_ == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const int W = game_width_ * internal_res_;
|
||||
const int H = game_height_ * internal_res_;
|
||||
|
||||
+127
-57
@@ -12,7 +12,7 @@
|
||||
|
||||
// Forward declarations de gif.h (inclòs des de jdraw8.cpp, no es pot incloure dos vegades)
|
||||
struct rgb;
|
||||
extern unsigned char* LoadGif(unsigned char* data, unsigned short* w, unsigned short* h);
|
||||
extern auto LoadGif(unsigned char* data, unsigned short* w, unsigned short* h) -> unsigned char*;
|
||||
|
||||
Text::Text(const char* fnt_file, const char* gif_file) {
|
||||
loadBitmap(gif_file);
|
||||
@@ -23,7 +23,9 @@ Text::Text(const char* fnt_file, const char* gif_file) {
|
||||
|
||||
auto Text::nextCodepoint(const char*& ptr) -> uint32_t {
|
||||
auto byte = static_cast<uint8_t>(*ptr);
|
||||
if (byte == 0) return 0;
|
||||
if (byte == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t cp = 0;
|
||||
int extra = 0;
|
||||
@@ -47,7 +49,9 @@ auto Text::nextCodepoint(const char*& ptr) -> uint32_t {
|
||||
ptr++;
|
||||
for (int i = 0; i < extra; i++) {
|
||||
auto cont = static_cast<uint8_t>(*ptr);
|
||||
if ((cont & 0xC0) != 0x80) return 0xFFFD;
|
||||
if ((cont & 0xC0) != 0x80) {
|
||||
return 0xFFFD;
|
||||
}
|
||||
cp = (cp << 6) | (cont & 0x3F);
|
||||
ptr++;
|
||||
}
|
||||
@@ -71,7 +75,9 @@ void Text::loadFont(const char* fnt_file) {
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
// Ignora comentaris i línies buides
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Elimina comentaris inline
|
||||
auto comment_pos = line.find('#');
|
||||
@@ -133,9 +139,10 @@ void Text::loadBitmap(const char* gif_file) {
|
||||
int w = raw[6] | (raw[7] << 8);
|
||||
int h = raw[8] | (raw[9] << 8);
|
||||
|
||||
unsigned short gw = 0, gh = 0;
|
||||
unsigned short gw = 0;
|
||||
unsigned short gh = 0;
|
||||
Uint8* pixels = LoadGif(raw, &gw, &gh);
|
||||
if (!pixels) {
|
||||
if (pixels == nullptr) {
|
||||
std::cerr << "Text: unable to decode GIF: " << gif_file << '\n';
|
||||
return;
|
||||
}
|
||||
@@ -151,14 +158,18 @@ void Text::loadBitmap(const char* gif_file) {
|
||||
// --- Renderitzat ---
|
||||
|
||||
void Text::draw(Uint32* pixel_data, int x, int y, const char* text, Uint32 color) const {
|
||||
if (bitmap_.empty() || !pixel_data) return;
|
||||
if (bitmap_.empty() || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* ptr = text;
|
||||
int cursor_x = x;
|
||||
|
||||
while (*ptr) {
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) break;
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
@@ -174,21 +185,27 @@ void Text::draw(Uint32* pixel_data, int x, int y, const char* text, Uint32 color
|
||||
// Pinta glifo pixel a pixel
|
||||
for (int gy = 0; gy < box_height_; gy++) {
|
||||
int dst_y = y + gy;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) continue;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int gx = 0; gx < glyph.w; gx++) {
|
||||
int dst_x = cursor_x + gx;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) continue;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int src_x = glyph.x + gx;
|
||||
int src_y = glyph.y + gy;
|
||||
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) continue;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Uint8 pixel = bitmap_[src_x + src_y * bitmap_width_];
|
||||
Uint8 pixel = bitmap_[src_x + (src_y * bitmap_width_)];
|
||||
// Píxel no transparent (índex 0 és fons típicament)
|
||||
if (pixel != 0) {
|
||||
pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color;
|
||||
pixel_data[dst_x + (dst_y * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,17 +221,23 @@ void Text::drawCentered(Uint32* pixel_data, int y, const char* text, Uint32 colo
|
||||
}
|
||||
|
||||
void Text::drawClipped(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) const {
|
||||
if (bitmap_.empty() || !pixel_data) return;
|
||||
if (bitmap_.empty() || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Descart ràpid si el glifo sencer cau fora verticalment
|
||||
if (y + box_height_ <= clip_y_min || y >= clip_y_max) return;
|
||||
if (y + box_height_ <= clip_y_min || y >= clip_y_max) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* ptr = text;
|
||||
int cursor_x = x;
|
||||
|
||||
while (*ptr) {
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) break;
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
@@ -235,21 +258,31 @@ void Text::drawClipped(Uint32* pixel_data, int x, int y, const char* text, Uint3
|
||||
|
||||
for (int gy = 0; gy < box_height_; gy++) {
|
||||
int dst_y = y + gy;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) continue;
|
||||
if (dst_y < clip_y_min || dst_y >= clip_y_max) continue;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
if (dst_y < clip_y_min || dst_y >= clip_y_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int gx = 0; gx < glyph.w; gx++) {
|
||||
int dst_x = cursor_x + gx;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) continue;
|
||||
if (dst_x < clip_x_min || dst_x >= clip_x_max) continue;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
if (dst_x < clip_x_min || dst_x >= clip_x_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int src_x = glyph.x + gx;
|
||||
int src_y = glyph.y + gy;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) continue;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Uint8 pixel = bitmap_[src_x + src_y * bitmap_width_];
|
||||
Uint8 pixel = bitmap_[src_x + (src_y * bitmap_width_)];
|
||||
if (pixel != 0) {
|
||||
pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color;
|
||||
pixel_data[dst_x + (dst_y * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,14 +292,18 @@ void Text::drawClipped(Uint32* pixel_data, int x, int y, const char* text, Uint3
|
||||
}
|
||||
|
||||
void Text::drawMono(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int cell_w) const {
|
||||
if (bitmap_.empty() || !pixel_data) return;
|
||||
if (bitmap_.empty() || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* ptr = text;
|
||||
int cursor_x = x;
|
||||
|
||||
while (*ptr) {
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) break;
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
@@ -279,23 +316,29 @@ void Text::drawMono(Uint32* pixel_data, int x, int y, const char* text, Uint32 c
|
||||
|
||||
const auto& glyph = it->second;
|
||||
// Centra el glif dins la cel·la
|
||||
int glyph_x = cursor_x + (cell_w - glyph.w) / 2;
|
||||
int glyph_x = cursor_x + ((cell_w - glyph.w) / 2);
|
||||
|
||||
for (int gy = 0; gy < box_height_; gy++) {
|
||||
int dst_y = y + gy;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) continue;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int gx = 0; gx < glyph.w; gx++) {
|
||||
int dst_x = glyph_x + gx;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) continue;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int src_x = glyph.x + gx;
|
||||
int src_y = glyph.y + gy;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) continue;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Uint8 pixel = bitmap_[src_x + src_y * bitmap_width_];
|
||||
Uint8 pixel = bitmap_[src_x + (src_y * bitmap_width_)];
|
||||
if (pixel != 0) {
|
||||
pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color;
|
||||
pixel_data[dst_x + (dst_y * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,21 +348,27 @@ void Text::drawMono(Uint32* pixel_data, int x, int y, const char* text, Uint32 c
|
||||
}
|
||||
|
||||
void Text::drawMonoDigits(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int digit_cell_w) const {
|
||||
if (bitmap_.empty() || !pixel_data) return;
|
||||
if (bitmap_.empty() || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* ptr = text;
|
||||
int cursor_x = x;
|
||||
bool first = true;
|
||||
|
||||
while (*ptr) {
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) break;
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
it = glyphs_.find('?');
|
||||
if (it == glyphs_.end()) {
|
||||
if (!first) cursor_x += 1;
|
||||
if (!first) {
|
||||
cursor_x += 1;
|
||||
}
|
||||
cursor_x += box_width_;
|
||||
first = false;
|
||||
continue;
|
||||
@@ -329,22 +378,30 @@ void Text::drawMonoDigits(Uint32* pixel_data, int x, int y, const char* text, Ui
|
||||
const auto& glyph = it->second;
|
||||
bool is_digit = (cp >= '0' && cp <= '9');
|
||||
|
||||
if (!first) cursor_x += 1; // kerning
|
||||
if (!first) {
|
||||
cursor_x += 1; // kerning
|
||||
}
|
||||
|
||||
int glyph_x = is_digit ? cursor_x + (digit_cell_w - glyph.w) / 2 : cursor_x;
|
||||
int glyph_x = is_digit ? cursor_x + ((digit_cell_w - glyph.w) / 2) : cursor_x;
|
||||
|
||||
for (int gy = 0; gy < box_height_; gy++) {
|
||||
int dst_y = y + gy;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) continue;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
for (int gx = 0; gx < glyph.w; gx++) {
|
||||
int dst_x = glyph_x + gx;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) continue;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
int src_x = glyph.x + gx;
|
||||
int src_y = glyph.y + gy;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) continue;
|
||||
Uint8 pixel = bitmap_[src_x + src_y * bitmap_width_];
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
Uint8 pixel = bitmap_[src_x + (src_y * bitmap_width_)];
|
||||
if (pixel != 0) {
|
||||
pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color;
|
||||
pixel_data[dst_x + (dst_y * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,32 +415,41 @@ auto Text::widthMonoDigits(const char* text, int digit_cell_w) const -> int {
|
||||
const char* ptr = text;
|
||||
int w = 0;
|
||||
bool first = true;
|
||||
while (*ptr) {
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) break;
|
||||
if (!first) w += 1; // kerning
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
if (!first) {
|
||||
w += 1; // kerning
|
||||
}
|
||||
first = false;
|
||||
bool is_digit = (cp >= '0' && cp <= '9');
|
||||
if (is_digit) {
|
||||
w += digit_cell_w;
|
||||
} else {
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) it = glyphs_.find('?');
|
||||
if (it != glyphs_.end())
|
||||
if (it == glyphs_.end()) {
|
||||
it = glyphs_.find('?');
|
||||
}
|
||||
if (it != glyphs_.end()) {
|
||||
w += it->second.w;
|
||||
else
|
||||
} else {
|
||||
w += box_width_;
|
||||
}
|
||||
}
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
auto Text::widthMono(const char* text, int cell_w) const -> int {
|
||||
auto Text::widthMono(const char* text, int cell_w) -> int {
|
||||
const char* ptr = text;
|
||||
int count = 0;
|
||||
while (*ptr) {
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) break;
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count * cell_w;
|
||||
@@ -394,16 +460,20 @@ auto Text::width(const char* text) const -> int {
|
||||
int w = 0;
|
||||
bool first = true;
|
||||
|
||||
while (*ptr) {
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) break;
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
it = glyphs_.find('?');
|
||||
}
|
||||
|
||||
if (!first) w += 1; // kerning
|
||||
if (!first) {
|
||||
w += 1; // kerning
|
||||
}
|
||||
first = false;
|
||||
|
||||
if (it != glyphs_.end()) {
|
||||
|
||||
@@ -28,7 +28,7 @@ class Text {
|
||||
// Calcula ancho en píxeles d'un text
|
||||
[[nodiscard]] auto width(const char* text) const -> int;
|
||||
// Amplada mono: nombre de codepoints × cell_w
|
||||
[[nodiscard]] auto widthMono(const char* text, int cell_w) const -> int;
|
||||
[[nodiscard]] static auto widthMono(const char* text, int cell_w) -> int;
|
||||
// Amplada mono-dígits: amplada natural, però substituint els dígits per digit_cell_w
|
||||
[[nodiscard]] auto widthMonoDigits(const char* text, int digit_cell_w) const -> int;
|
||||
[[nodiscard]] auto charHeight() const -> int { return box_height_; }
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
// ni inline, així que no podem tornar-lo a incloure aquí. Ens fiem de les
|
||||
// declaracions extern dels símbols que ens calen (linkatge C++ normal,
|
||||
// igual que fa text.cpp).
|
||||
extern unsigned char* LoadGif(unsigned char* data, unsigned short* w, unsigned short* h);
|
||||
extern unsigned char* LoadPalette(unsigned char* data);
|
||||
extern auto LoadGif(unsigned char* data, unsigned short* w, unsigned short* h) -> unsigned char*;
|
||||
extern auto LoadPalette(unsigned char* data) -> unsigned char*;
|
||||
|
||||
namespace Resource {
|
||||
|
||||
@@ -90,7 +90,9 @@ namespace Resource {
|
||||
}
|
||||
|
||||
auto Cache::getProgress() const -> float {
|
||||
if (total_count_ == 0) return 1.0F;
|
||||
if (total_count_ == 0) {
|
||||
return 1.0F;
|
||||
}
|
||||
return static_cast<float>(loaded_count_) / static_cast<float>(total_count_);
|
||||
}
|
||||
|
||||
@@ -102,7 +104,9 @@ namespace Resource {
|
||||
}
|
||||
|
||||
auto Cache::loadStep(int budget_ms) -> bool {
|
||||
if (stage_ == LoadStage::DONE) return true;
|
||||
if (stage_ == LoadStage::DONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Uint64 start_ns = SDL_GetTicksNS();
|
||||
const Uint64 budget_ns = static_cast<Uint64>(budget_ms) * 1'000'000ULL;
|
||||
@@ -112,7 +116,9 @@ namespace Resource {
|
||||
switch (stage_) {
|
||||
case LoadStage::MUSICS: {
|
||||
auto items = list->getListByType(List::Type::MUSIC);
|
||||
if (stage_index_ == 0) musics_.clear();
|
||||
if (stage_index_ == 0) {
|
||||
musics_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::SOUNDS;
|
||||
stage_index_ = 0;
|
||||
@@ -123,7 +129,9 @@ namespace Resource {
|
||||
}
|
||||
case LoadStage::SOUNDS: {
|
||||
auto items = list->getListByType(List::Type::SOUND);
|
||||
if (stage_index_ == 0) sounds_.clear();
|
||||
if (stage_index_ == 0) {
|
||||
sounds_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::BITMAPS;
|
||||
stage_index_ = 0;
|
||||
@@ -134,7 +142,9 @@ namespace Resource {
|
||||
}
|
||||
case LoadStage::BITMAPS: {
|
||||
auto items = list->getListByType(List::Type::BITMAP);
|
||||
if (stage_index_ == 0) surfaces_.clear();
|
||||
if (stage_index_ == 0) {
|
||||
surfaces_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::TEXT_FILES;
|
||||
stage_index_ = 0;
|
||||
@@ -148,7 +158,9 @@ namespace Resource {
|
||||
auto font_items = list->getListByType(List::Type::FONT);
|
||||
auto items = data_items;
|
||||
items.insert(items.end(), font_items.begin(), font_items.end());
|
||||
if (stage_index_ == 0) text_files_.clear();
|
||||
if (stage_index_ == 0) {
|
||||
text_files_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::DONE;
|
||||
stage_index_ = 0;
|
||||
@@ -161,7 +173,9 @@ namespace Resource {
|
||||
case LoadStage::DONE:
|
||||
break;
|
||||
}
|
||||
if ((SDL_GetTicksNS() - start_ns) >= budget_ns) break;
|
||||
if ((SDL_GetTicksNS() - start_ns) >= budget_ns) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stage_ == LoadStage::DONE;
|
||||
|
||||
@@ -16,13 +16,17 @@ namespace ResourceHelper {
|
||||
auto readFromDisk(const std::string& relative_path) -> std::vector<uint8_t> {
|
||||
const std::string full = std::string(file_getresourcefolder()) + relative_path;
|
||||
std::ifstream file(full, std::ios::binary | std::ios::ate);
|
||||
if (!file) return {};
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> data(size);
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), size)) return {};
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
|
||||
return {};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -95,11 +95,21 @@ namespace Resource {
|
||||
}
|
||||
|
||||
auto List::parseAssetType(const std::string& type_str) -> Type {
|
||||
if (type_str == "DATA") return Type::DATA;
|
||||
if (type_str == "BITMAP") return Type::BITMAP;
|
||||
if (type_str == "MUSIC") return Type::MUSIC;
|
||||
if (type_str == "SOUND") return Type::SOUND;
|
||||
if (type_str == "FONT") return Type::FONT;
|
||||
if (type_str == "DATA") {
|
||||
return Type::DATA;
|
||||
}
|
||||
if (type_str == "BITMAP") {
|
||||
return Type::BITMAP;
|
||||
}
|
||||
if (type_str == "MUSIC") {
|
||||
return Type::MUSIC;
|
||||
}
|
||||
if (type_str == "SOUND") {
|
||||
return Type::SOUND;
|
||||
}
|
||||
if (type_str == "FONT") {
|
||||
return Type::FONT;
|
||||
}
|
||||
throw std::runtime_error("Unknown asset type: " + type_str);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@ auto ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) -> uint32
|
||||
}
|
||||
|
||||
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
|
||||
if (key.empty()) return;
|
||||
if (key.empty()) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
data[i] ^= static_cast<uint8_t>(key[i % key.length()]);
|
||||
}
|
||||
@@ -162,7 +164,9 @@ auto ResourcePack::addDirectory(const std::string& directory) -> bool {
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
|
||||
if (!entry.is_regular_file()) continue;
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string filepath = entry.path().string();
|
||||
std::string filename = std::filesystem::relative(entry.path(), directory).string();
|
||||
@@ -177,7 +181,9 @@ auto ResourcePack::addDirectory(const std::string& directory) -> bool {
|
||||
|
||||
auto ResourcePack::getResource(const std::string& filename) -> std::vector<uint8_t> {
|
||||
auto it = resources_.find(filename);
|
||||
if (it == resources_.end()) return {};
|
||||
if (it == resources_.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const ResourceEntry& entry = it->second;
|
||||
if (entry.offset + entry.size > data_.size()) {
|
||||
|
||||
@@ -56,7 +56,7 @@ void Director::initGameContext() {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<scenes::Scene> Director::createNextScene() {
|
||||
auto Director::createNextScene() const -> std::unique_ptr<scenes::Scene> {
|
||||
// Mentre el Resource::Cache no haja acabat de precarregar, executem
|
||||
// el BootLoaderScene — pinta una barra de progrés i avança la
|
||||
// càrrega per pressupost de temps. Quan acaba, retorna i tornem ací
|
||||
@@ -139,7 +139,7 @@ void Director::setup() {
|
||||
has_frame_ = false;
|
||||
}
|
||||
|
||||
bool Director::iterate() {
|
||||
auto Director::iterate() -> bool {
|
||||
if (quit_requested_) {
|
||||
JG_QuitSignal();
|
||||
current_scene_.reset(); // destrueix l'escena actual ordenadament
|
||||
@@ -216,9 +216,13 @@ bool Director::iterate() {
|
||||
// game_state_ i info::ctx. Si és impossible (game_state_ == -1,
|
||||
// quit, o state no registrat), eixim del loop.
|
||||
if (!current_scene_) {
|
||||
if (game_state_ == -1 || JG_Quitting()) return false;
|
||||
if (game_state_ == -1 || JG_Quitting()) {
|
||||
return false;
|
||||
}
|
||||
current_scene_ = createNextScene();
|
||||
if (!current_scene_) return false;
|
||||
if (!current_scene_) {
|
||||
return false;
|
||||
}
|
||||
current_scene_->onEnter();
|
||||
last_tick_ms_ = SDL_GetTicks();
|
||||
}
|
||||
@@ -270,7 +274,9 @@ void Director::run() {
|
||||
setup();
|
||||
while (true) {
|
||||
pollAllEvents();
|
||||
if (!iterate()) break;
|
||||
if (!iterate()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
teardown();
|
||||
}
|
||||
@@ -379,16 +385,14 @@ void Director::handleEvent(const SDL_Event& event) {
|
||||
// Ja processat a KEY_DOWN, només deixem netejar el bloqueig
|
||||
// quan l'overlay faça timeout
|
||||
return;
|
||||
} else {
|
||||
// Comprova si és una tecla d'UI registrada (no passa al joc).
|
||||
// KeyConfig::isGuiKey cobreix totes les tecles GUI a la vegada,
|
||||
// incloent pause_toggle i menu_toggle (defensa en profunditat:
|
||||
// aquestes ja s'haurien hagut de menjar al swallow d'amunt).
|
||||
const auto sc = event.key.scancode;
|
||||
if (!KeyConfig::isGuiKey(sc)) {
|
||||
key_pressed_ = true;
|
||||
JI_moveCheats(sc);
|
||||
}
|
||||
} // Comprova si és una tecla d'UI registrada (no passa al joc).
|
||||
// KeyConfig::isGuiKey cobreix totes les tecles GUI a la vegada,
|
||||
// incloent pause_toggle i menu_toggle (defensa en profunditat:
|
||||
// aquestes ja s'haurien hagut de menjar al swallow d'amunt).
|
||||
const auto sc = event.key.scancode;
|
||||
if (!KeyConfig::isGuiKey(sc)) {
|
||||
key_pressed_ = true;
|
||||
JI_moveCheats(sc);
|
||||
}
|
||||
}
|
||||
Mouse::handleEvent(event);
|
||||
|
||||
@@ -28,13 +28,13 @@ class Director {
|
||||
// per l'event loop de SDL3 en lloc d'un bucle propi — imprescindible
|
||||
// per al port a emscripten, on el runtime posseïx el main loop.
|
||||
void setup();
|
||||
bool iterate(); // torna false quan el joc vol eixir
|
||||
auto iterate() -> bool; // torna false quan el joc vol eixir
|
||||
void teardown();
|
||||
void handleEvent(const SDL_Event& event);
|
||||
|
||||
// Demana l'eixida (ex: segona pulsació d'ESC o SDL_QUIT)
|
||||
void requestQuit();
|
||||
auto isQuitRequested() const -> bool { return quit_requested_; }
|
||||
[[nodiscard]] auto isQuitRequested() const -> bool { return quit_requested_; }
|
||||
|
||||
// Demana un reinici "suau": para música i sons, reseteja info::ctx i
|
||||
// torna a l'intro (state 255). Es processa al començament del pròxim
|
||||
@@ -45,14 +45,13 @@ class Director {
|
||||
auto consumeKeyPressed() -> bool;
|
||||
|
||||
// Indica si ESC està bloquejada (el joc no l'ha de veure)
|
||||
auto isEscBlocked() const -> bool { return esc_blocked_ || esc_swallow_until_release_; }
|
||||
[[nodiscard]] auto isEscBlocked() const -> bool { return esc_blocked_ || esc_swallow_until_release_; }
|
||||
|
||||
// Pausa: mentre està activa, iterate() no avança l'escena — es
|
||||
// continua presentant el darrer frame amb overlay fresc.
|
||||
void togglePause();
|
||||
auto isPaused() const -> bool { return paused_; }
|
||||
[[nodiscard]] auto isPaused() const -> bool { return paused_; }
|
||||
|
||||
public:
|
||||
~Director();
|
||||
|
||||
private:
|
||||
@@ -64,10 +63,10 @@ class Director {
|
||||
|
||||
// Inicialitza info::ctx a partir de Options::game.* i comprova trick.ini.
|
||||
// Es crida una sola vegada des d'iterate() a la primera invocació.
|
||||
void initGameContext();
|
||||
static void initGameContext();
|
||||
// Construeix l'escena apropiada segons game_state_ i info::ctx.
|
||||
// Retorna nullptr si l'state actual no té escena registrada (bug).
|
||||
std::unique_ptr<scenes::Scene> createNextScene();
|
||||
auto createNextScene() const -> std::unique_ptr<scenes::Scene>;
|
||||
|
||||
// Buffers persistents entre iteracions. Abans eren locals a run(),
|
||||
// ara són membres perquè iterate() els pot reutilitzar sense tornar-los
|
||||
|
||||
+16
-6
@@ -1,6 +1,6 @@
|
||||
#include "game/bola.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/jail/jgame.hpp"
|
||||
|
||||
@@ -24,29 +24,39 @@ Bola::Bola(JD8_Surface gfx, Prota* sam)
|
||||
}
|
||||
|
||||
void Bola::draw() {
|
||||
if (this->contador == 0) Sprite::draw();
|
||||
if (this->contador == 0) {
|
||||
Sprite::draw();
|
||||
}
|
||||
}
|
||||
|
||||
void Bola::update() {
|
||||
if (this->contador == 0) {
|
||||
// Augmentem la x
|
||||
this->x++;
|
||||
if (this->x == 280) this->contador = 200;
|
||||
if (this->x == 280) {
|
||||
this->contador = 200;
|
||||
}
|
||||
|
||||
// Augmentem el frame
|
||||
if (JG_GetCycleCounter() % this->cycles_per_frame == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) this->cur_frame = 0;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Comprovem si ha tocat a Sam
|
||||
if (this->x > (this->sam->x - 7) && this->x < (this->sam->x + 7) && this->y > (this->sam->y - 7) && this->y < (this->sam->y + 7)) {
|
||||
this->contador = 200;
|
||||
info::ctx.vida--;
|
||||
if (info::ctx.vida == 0) this->sam->o = 5;
|
||||
if (info::ctx.vida == 0) {
|
||||
this->sam->o = 5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this->contador--;
|
||||
if (this->contador == 0) this->x = 20;
|
||||
if (this->contador == 0) {
|
||||
this->x = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "game/engendro.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/jail/jgame.hpp"
|
||||
|
||||
@@ -29,16 +29,20 @@ Engendro::Engendro(JD8_Surface gfx, Uint16 x, Uint16 y)
|
||||
this->cycles_per_frame = 30;
|
||||
}
|
||||
|
||||
bool Engendro::update() {
|
||||
auto Engendro::update() -> bool {
|
||||
bool mort = false;
|
||||
|
||||
if (JG_GetCycleCounter() % this->cycles_per_frame == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) this->cur_frame = 0;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
this->vida--;
|
||||
}
|
||||
|
||||
if (vida == 0) mort = true;
|
||||
if (vida == 0) {
|
||||
mort = true;
|
||||
}
|
||||
|
||||
return mort;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ class Engendro : public Sprite {
|
||||
public:
|
||||
explicit Engendro(JD8_Surface gfx, Uint16 x, Uint16 y);
|
||||
|
||||
bool update();
|
||||
auto update() -> bool;
|
||||
|
||||
protected:
|
||||
Uint8 vida;
|
||||
|
||||
+44
-25
@@ -1,6 +1,6 @@
|
||||
#include "game/mapa.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/jail/jgame.hpp"
|
||||
#include "core/jail/jinput.hpp"
|
||||
@@ -21,7 +21,7 @@ Mapa::Mapa(JD8_Surface gfx, Prota* sam) {
|
||||
this->nova_momia = false;
|
||||
}
|
||||
|
||||
Mapa::~Mapa(void) {
|
||||
Mapa::~Mapa() {
|
||||
JD8_FreeSurface(this->fondo);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ void Mapa::draw() {
|
||||
// Pinta tombes
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 4; x++) {
|
||||
JD8_BlitCK(35 + (x * 65), 45 + (y * 35), this->gfx, this->tombes[x + y * 4].x, this->tombes[x + y * 4].y, 50, 20, 255);
|
||||
JD8_BlitCK(35 + (x * 65), 45 + (y * 35), this->gfx, this->tombes[x + (y * 4)].x, this->tombes[x + (y * 4)].y, 50, 20, 255);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,9 @@ void Mapa::update() {
|
||||
if (((sam->x - 20) % 65 == 0) && ((sam->y - 30) % 35 == 0) && ((this->ultim_vertex.columna != (sam->x - 20) / 65) || (this->ultim_vertex.fila != (sam->y - 30) / 35))) {
|
||||
this->vertex.columna = (sam->x - 20) / 65;
|
||||
this->vertex.fila = (sam->y - 30) / 35;
|
||||
if (this->ultim_vertex.columna != 255) this->comprovaUltimCami();
|
||||
if (this->ultim_vertex.columna != 255) {
|
||||
this->comprovaUltimCami();
|
||||
}
|
||||
this->ultim_vertex = this->vertex;
|
||||
}
|
||||
|
||||
@@ -79,7 +81,7 @@ void Mapa::update() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Mapa::novaMomia() {
|
||||
auto Mapa::novaMomia() -> bool {
|
||||
bool resultat = nova_momia;
|
||||
nova_momia = false;
|
||||
return resultat;
|
||||
@@ -96,7 +98,9 @@ void Mapa::preparaFondoEstatic() {
|
||||
}
|
||||
JD8_BlitToSurface(130, 2, this->gfx, 225, 192, 19, 8, this->fondo); // Montonet de monedes + signe '='
|
||||
JD8_BlitToSurface(220, 2, this->gfx, 160, 185, 48, 7, this->fondo); // Text "ENERGIA"
|
||||
if (info::ctx.diners >= 200) JD8_BlitToSurface(175, 3, this->gfx, 60, 193, 7, 6, this->fondo);
|
||||
if (info::ctx.diners >= 200) {
|
||||
JD8_BlitToSurface(175, 3, this->gfx, 60, 193, 7, 6, this->fondo);
|
||||
}
|
||||
|
||||
// Pinta taulells
|
||||
for (int y = 0; y < 11; y++) {
|
||||
@@ -150,7 +154,7 @@ void Mapa::preparaFondoEstatic() {
|
||||
}
|
||||
}
|
||||
|
||||
void swap(Uint8& a, Uint8& b) {
|
||||
void swap(Uint8& a, Uint8& b) noexcept {
|
||||
Uint8 temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
@@ -161,28 +165,36 @@ void Mapa::preparaTombes() {
|
||||
int cx = info::ctx.num_piramide == 6 ? 270 : 0;
|
||||
int cy = info::ctx.num_piramide == 6 ? 50 : 0;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
this->tombes[i].contingut = contingut;
|
||||
this->tombes[i].oberta = false;
|
||||
this->tombes[i].costat[0] = false;
|
||||
this->tombes[i].costat[1] = false;
|
||||
this->tombes[i].costat[2] = false;
|
||||
this->tombes[i].costat[3] = false;
|
||||
this->tombes[i].x = cx;
|
||||
this->tombes[i].y = cy;
|
||||
for (auto& tombe : this->tombes) {
|
||||
tombe.contingut = contingut;
|
||||
tombe.oberta = false;
|
||||
tombe.costat[0] = false;
|
||||
tombe.costat[1] = false;
|
||||
tombe.costat[2] = false;
|
||||
tombe.costat[3] = false;
|
||||
tombe.x = cx;
|
||||
tombe.y = cy;
|
||||
}
|
||||
if (info::ctx.num_piramide == 6) {
|
||||
return;
|
||||
}
|
||||
if (info::ctx.num_piramide == 6) return;
|
||||
this->tombes[0].contingut = CONTE_FARAO;
|
||||
this->tombes[1].contingut = CONTE_CLAU;
|
||||
this->tombes[2].contingut = CONTE_PERGAMI;
|
||||
this->tombes[3].contingut = CONTE_MOMIA;
|
||||
for (int i = 4; i < 8; i++) this->tombes[i].contingut = CONTE_RES;
|
||||
for (int i = 8; i < 16; i++) this->tombes[i].contingut = CONTE_TRESOR;
|
||||
for (int i = 4; i < 8; i++) {
|
||||
this->tombes[i].contingut = CONTE_RES;
|
||||
}
|
||||
for (int i = 8; i < 16; i++) {
|
||||
this->tombes[i].contingut = CONTE_TRESOR;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 50; i++) swap(this->tombes[rand() % 16].contingut, this->tombes[rand() % 16].contingut);
|
||||
for (int i = 0; i < 50; i++) {
|
||||
swap(this->tombes[rand() % 16].contingut, this->tombes[rand() % 16].contingut);
|
||||
}
|
||||
}
|
||||
|
||||
Uint8 minim(Uint8 a, Uint8 b) {
|
||||
auto minim(Uint8 a, Uint8 b) -> Uint8 {
|
||||
return (a < b) ? a : b;
|
||||
}
|
||||
|
||||
@@ -225,11 +237,16 @@ void Mapa::comprovaUltimCami() {
|
||||
|
||||
void Mapa::comprovaCaixa(Uint8 num) {
|
||||
// Si la tomba ja està oberta, no hi ha res que mirar
|
||||
if (this->tombes[num].oberta) return;
|
||||
if (this->tombes[num].oberta) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Si algun costat encara no està passat, no hi ha res que fer
|
||||
for (int i = 0; i < 4; i++)
|
||||
if (!this->tombes[num].costat[i]) return;
|
||||
for (bool i : this->tombes[num].costat) {
|
||||
if (!i) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sinó, pos la acabem d'obrir
|
||||
this->tombes[num].oberta = true;
|
||||
@@ -263,7 +280,9 @@ void Mapa::comprovaCaixa(Uint8 num) {
|
||||
this->tombes[num].y = 70;
|
||||
info::ctx.diamants++;
|
||||
info::ctx.diners += VALOR_DIAMANT;
|
||||
if (info::ctx.diamants == 16) this->farao = this->clau = true;
|
||||
if (info::ctx.diamants == 16) {
|
||||
this->farao = this->clau = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+14
-12
@@ -4,14 +4,16 @@
|
||||
#include "game/info.hpp"
|
||||
#include "game/prota.hpp"
|
||||
|
||||
#define CONTE_RES 0
|
||||
#define CONTE_TRESOR 1
|
||||
#define CONTE_FARAO 2
|
||||
#define CONTE_CLAU 3
|
||||
#define CONTE_MOMIA 4
|
||||
#define CONTE_PERGAMI 5
|
||||
#define CONTE_DIAMANT 6
|
||||
#define VALOR_DIAMANT 5
|
||||
enum {
|
||||
CONTE_RES = 0,
|
||||
CONTE_TRESOR = 1,
|
||||
CONTE_FARAO = 2,
|
||||
CONTE_CLAU = 3,
|
||||
CONTE_MOMIA = 4,
|
||||
CONTE_PERGAMI = 5,
|
||||
CONTE_DIAMANT = 6,
|
||||
VALOR_DIAMANT = 5
|
||||
};
|
||||
|
||||
struct Tomba {
|
||||
bool costat[4];
|
||||
@@ -28,16 +30,16 @@ struct Vertex {
|
||||
class Mapa {
|
||||
public:
|
||||
explicit Mapa(JD8_Surface gfx, Prota* sam);
|
||||
~Mapa(void);
|
||||
~Mapa();
|
||||
|
||||
Mapa(const Mapa&) = delete;
|
||||
Mapa& operator=(const Mapa&) = delete;
|
||||
auto operator=(const Mapa&) -> Mapa& = delete;
|
||||
Mapa(Mapa&&) = delete;
|
||||
Mapa& operator=(Mapa&&) = delete;
|
||||
auto operator=(Mapa&&) -> Mapa& = delete;
|
||||
|
||||
void draw();
|
||||
void update();
|
||||
bool novaMomia();
|
||||
auto novaMomia() -> bool;
|
||||
void comprovaCaixa(Uint8 num);
|
||||
|
||||
Tomba tombes[16];
|
||||
|
||||
@@ -5,8 +5,7 @@ Marcador::Marcador(JD8_Surface gfx, Prota* sam) {
|
||||
this->sam = sam;
|
||||
}
|
||||
|
||||
Marcador::~Marcador(void) {
|
||||
}
|
||||
Marcador::~Marcador() = default;
|
||||
|
||||
void Marcador::draw() {
|
||||
if (info::ctx.num_piramide < 6) {
|
||||
@@ -18,10 +17,14 @@ void Marcador::draw() {
|
||||
this->pintaNumero(156, 2, (info::ctx.diners % 100) / 10);
|
||||
this->pintaNumero(163, 2, info::ctx.diners % 10);
|
||||
|
||||
if (this->sam->pergami) JD8_BlitCK(190, 1, this->gfx, 209, 185, 15, 14, 255);
|
||||
if (this->sam->pergami) {
|
||||
JD8_BlitCK(190, 1, this->gfx, 209, 185, 15, 14, 255);
|
||||
}
|
||||
|
||||
JD8_BlitCK(271, 1, this->gfx, 0, 20, 15, info::ctx.vida * 3, 255);
|
||||
if (info::ctx.vida < 5) JD8_BlitCK(271, 1 + (info::ctx.vida * 3), this->gfx, 75, 20, 15, 15 - (info::ctx.vida * 3), 255);
|
||||
if (info::ctx.vida < 5) {
|
||||
JD8_BlitCK(271, 1 + (info::ctx.vida * 3), this->gfx, 75, 20, 15, 15 - (info::ctx.vida * 3), 255);
|
||||
}
|
||||
}
|
||||
|
||||
void Marcador::pintaNumero(Uint16 x, Uint16 y, Uint8 num) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class Marcador {
|
||||
public:
|
||||
explicit Marcador(JD8_Surface gfx, Prota* sam);
|
||||
~Marcador(void);
|
||||
~Marcador();
|
||||
|
||||
void draw();
|
||||
|
||||
|
||||
+29
-11
@@ -54,7 +54,9 @@ void ModuleGame::tick(int delta_ms) {
|
||||
// per la Draw() d'onEnter. Només el JD8_Flip del caller muta
|
||||
// pixel_data segons la paleta que avança pas a pas.
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) phase_ = Phase::Playing;
|
||||
if (fade_.done()) {
|
||||
phase_ = Phase::Playing;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Playing:
|
||||
@@ -75,7 +77,9 @@ void ModuleGame::tick(int delta_ms) {
|
||||
// mostraria l'estat post-Update del sprite (p.ex. el prota
|
||||
// "tornant" davant la porta després d'haver eixit).
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) phase_ = Phase::Done;
|
||||
if (fade_.done()) {
|
||||
phase_ = Phase::Done;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Done:
|
||||
@@ -83,8 +87,10 @@ void ModuleGame::tick(int delta_ms) {
|
||||
}
|
||||
}
|
||||
|
||||
int ModuleGame::nextState() const {
|
||||
if (JG_Quitting()) return -1;
|
||||
auto ModuleGame::nextState() const -> int {
|
||||
if (JG_Quitting()) {
|
||||
return -1;
|
||||
}
|
||||
if (info::ctx.num_habitacio == 1 ||
|
||||
info::ctx.num_piramide == 100 ||
|
||||
info::ctx.num_piramide == 7) {
|
||||
@@ -93,14 +99,16 @@ int ModuleGame::nextState() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ModuleGame::applyFinalTransitions() {
|
||||
void ModuleGame::applyFinalTransitions() const {
|
||||
if (this->final_ == 1) {
|
||||
info::ctx.num_habitacio++;
|
||||
if (info::ctx.num_habitacio == 6) {
|
||||
info::ctx.num_habitacio = 1;
|
||||
info::ctx.num_piramide++;
|
||||
}
|
||||
if (info::ctx.num_piramide == 6 && info::ctx.num_habitacio == 2) info::ctx.num_piramide++;
|
||||
if (info::ctx.num_piramide == 6 && info::ctx.num_habitacio == 2) {
|
||||
info::ctx.num_piramide++;
|
||||
}
|
||||
} else if (this->final_ == 2) {
|
||||
info::ctx.num_piramide = 100;
|
||||
}
|
||||
@@ -112,8 +120,12 @@ void ModuleGame::Draw() {
|
||||
this->mapa->draw();
|
||||
this->marcador->draw();
|
||||
this->sam->draw();
|
||||
for (auto& m : this->momies) m->draw();
|
||||
if (this->bola) this->bola->draw();
|
||||
for (auto& m : this->momies) {
|
||||
m->draw();
|
||||
}
|
||||
if (this->bola) {
|
||||
this->bola->draw();
|
||||
}
|
||||
}
|
||||
|
||||
void ModuleGame::Update() {
|
||||
@@ -123,14 +135,18 @@ void ModuleGame::Update() {
|
||||
this->final_ = this->sam->update();
|
||||
const auto erased = std::erase_if(this->momies, [](auto& m) { return m->update(); });
|
||||
info::ctx.momies -= static_cast<int>(erased);
|
||||
if (this->bola) this->bola->update();
|
||||
if (this->bola) {
|
||||
this->bola->update();
|
||||
}
|
||||
this->mapa->update();
|
||||
if (this->mapa->novaMomia()) {
|
||||
this->momies.emplace_back(std::make_unique<Momia>(this->gfx, true, 0, 0, this->sam.get()));
|
||||
info::ctx.momies++;
|
||||
}
|
||||
|
||||
if (JI_CheatActivated("reviu")) info::ctx.vida = 5;
|
||||
if (JI_CheatActivated("reviu")) {
|
||||
info::ctx.vida = 5;
|
||||
}
|
||||
if (JI_CheatActivated("alone")) {
|
||||
this->momies.clear();
|
||||
info::ctx.momies = 0;
|
||||
@@ -157,7 +173,9 @@ void ModuleGame::iniciarMomies() {
|
||||
} else {
|
||||
info::ctx.momies++;
|
||||
}
|
||||
if (info::ctx.num_piramide == 6) info::ctx.momies = 8;
|
||||
if (info::ctx.num_piramide == 6) {
|
||||
info::ctx.momies = 8;
|
||||
}
|
||||
|
||||
int x = 20;
|
||||
int y = 170;
|
||||
|
||||
@@ -32,14 +32,14 @@ class ModuleGame : public scenes::Scene {
|
||||
~ModuleGame() override;
|
||||
|
||||
ModuleGame(const ModuleGame&) = delete;
|
||||
ModuleGame& operator=(const ModuleGame&) = delete;
|
||||
auto operator=(const ModuleGame&) -> ModuleGame& = delete;
|
||||
ModuleGame(ModuleGame&&) = delete;
|
||||
ModuleGame& operator=(ModuleGame&&) = delete;
|
||||
auto operator=(ModuleGame&&) -> ModuleGame& = delete;
|
||||
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
int nextState() const override;
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto nextState() const -> int override;
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
@@ -53,7 +53,7 @@ class ModuleGame : public scenes::Scene {
|
||||
void Update(); // gated per JG_ShouldUpdate
|
||||
|
||||
void iniciarMomies();
|
||||
void applyFinalTransitions(); // muta info::ctx quan final_ passa a !=0
|
||||
void applyFinalTransitions() const; // muta info::ctx quan final_ passa a !=0
|
||||
|
||||
Phase phase_{Phase::FadingIn};
|
||||
scenes::PaletteFade fade_;
|
||||
|
||||
+34
-18
@@ -1,6 +1,6 @@
|
||||
#include "game/momia.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/jail/jgame.hpp"
|
||||
|
||||
@@ -15,9 +15,13 @@ Momia::Momia(JD8_Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam)
|
||||
Frame f;
|
||||
f.w = 15;
|
||||
f.h = 15;
|
||||
if (info::ctx.num_piramide == 4) f.h -= 5;
|
||||
if (info::ctx.num_piramide == 4) {
|
||||
f.h -= 5;
|
||||
}
|
||||
f.x = (col * 15) + 75;
|
||||
if (this->dimoni) f.x += 75;
|
||||
if (this->dimoni) {
|
||||
f.x += 75;
|
||||
}
|
||||
f.y = 20 + (row * 15);
|
||||
entitat.frames.push_back(f);
|
||||
}
|
||||
@@ -26,14 +30,14 @@ Momia::Momia(JD8_Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam)
|
||||
entitat.animacions.resize(4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
entitat.animacions[i].frames = {
|
||||
static_cast<Uint8>(0 + i * 5),
|
||||
static_cast<Uint8>(1 + i * 5),
|
||||
static_cast<Uint8>(2 + i * 5),
|
||||
static_cast<Uint8>(1 + i * 5),
|
||||
static_cast<Uint8>(0 + i * 5),
|
||||
static_cast<Uint8>(3 + i * 5),
|
||||
static_cast<Uint8>(4 + i * 5),
|
||||
static_cast<Uint8>(3 + i * 5),
|
||||
static_cast<Uint8>(0 + (i * 5)),
|
||||
static_cast<Uint8>(1 + (i * 5)),
|
||||
static_cast<Uint8>(2 + (i * 5)),
|
||||
static_cast<Uint8>(1 + (i * 5)),
|
||||
static_cast<Uint8>(0 + (i * 5)),
|
||||
static_cast<Uint8>(3 + (i * 5)),
|
||||
static_cast<Uint8>(4 + (i * 5)),
|
||||
static_cast<Uint8>(3 + (i * 5)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,7 +83,7 @@ void Momia::draw() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Momia::update() {
|
||||
auto Momia::update() -> bool {
|
||||
bool morta = false;
|
||||
|
||||
if (this->engendro) {
|
||||
@@ -120,22 +124,32 @@ bool Momia::update() {
|
||||
|
||||
switch (this->o) {
|
||||
case 0:
|
||||
if (y < 170) this->y++;
|
||||
if (y < 170) {
|
||||
this->y++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (y > 30) this->y--;
|
||||
if (y > 30) {
|
||||
this->y--;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (x < 280) this->x++;
|
||||
if (x < 280) {
|
||||
this->x++;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (x > 20) this->x--;
|
||||
if (x > 20) {
|
||||
this->x--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (JG_GetCycleCounter() % this->cycles_per_frame == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) this->cur_frame = 0;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->x > (this->sam->x - 7) && this->x < (this->sam->x + 7) && this->y > (this->sam->y - 7) && this->y < (this->sam->y + 7)) {
|
||||
@@ -144,7 +158,9 @@ bool Momia::update() {
|
||||
this->sam->pergami = false;
|
||||
} else {
|
||||
info::ctx.vida--;
|
||||
if (info::ctx.vida == 0) this->sam->o = 5;
|
||||
if (info::ctx.vida == 0) {
|
||||
this->sam->o = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class Momia : public Sprite {
|
||||
explicit Momia(JD8_Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam);
|
||||
|
||||
void draw() override;
|
||||
bool update();
|
||||
auto update() -> bool;
|
||||
|
||||
bool dimoni;
|
||||
|
||||
|
||||
+119
-55
@@ -1,5 +1,6 @@
|
||||
#include "game/options.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
@@ -58,16 +59,21 @@ namespace Options {
|
||||
auto yaml = fkyaml::node::deserialize(content);
|
||||
if (yaml.contains("game")) {
|
||||
const auto& node = yaml["game"];
|
||||
if (node.contains("habitacio_inicial"))
|
||||
if (node.contains("habitacio_inicial")) {
|
||||
game.habitacio_inicial = node["habitacio_inicial"].get_value<int>();
|
||||
if (node.contains("piramide_inicial"))
|
||||
}
|
||||
if (node.contains("piramide_inicial")) {
|
||||
game.piramide_inicial = node["piramide_inicial"].get_value<int>();
|
||||
if (node.contains("vides"))
|
||||
}
|
||||
if (node.contains("vides")) {
|
||||
game.vides = node["vides"].get_value<int>();
|
||||
if (node.contains("diamants_inicial"))
|
||||
}
|
||||
if (node.contains("diamants_inicial")) {
|
||||
game.diamants_inicial = node["diamants_inicial"].get_value<int>();
|
||||
if (node.contains("diners_inicial"))
|
||||
}
|
||||
if (node.contains("diners_inicial")) {
|
||||
game.diners_inicial = node["diners_inicial"].get_value<int>();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (const fkyaml::exception& e) {
|
||||
@@ -80,7 +86,9 @@ namespace Options {
|
||||
// com a punt d'entrada únic per als callsites legacy del menú; el cos
|
||||
// ja no toca jail_audio directament.
|
||||
void applyAudio() {
|
||||
if (::Audio::get() == nullptr) return;
|
||||
if (::Audio::get() == nullptr) {
|
||||
return;
|
||||
}
|
||||
::Audio::get()->enable(audio.enabled);
|
||||
::Audio::get()->enableMusic(audio.music.enabled);
|
||||
::Audio::get()->enableSound(audio.sound.enabled);
|
||||
@@ -91,114 +99,148 @@ namespace Options {
|
||||
// --- Funcions helper de càrrega ---
|
||||
|
||||
static void loadAudioConfigFromYaml(const fkyaml::node& yaml) {
|
||||
if (!yaml.contains("audio")) return;
|
||||
if (!yaml.contains("audio")) {
|
||||
return;
|
||||
}
|
||||
const auto& node = yaml["audio"];
|
||||
|
||||
if (node.contains("enabled"))
|
||||
if (node.contains("enabled")) {
|
||||
audio.enabled = node["enabled"].get_value<bool>();
|
||||
}
|
||||
|
||||
if (node.contains("volume"))
|
||||
if (node.contains("volume")) {
|
||||
audio.volume = node["volume"].get_value<float>();
|
||||
}
|
||||
|
||||
if (node.contains("music")) {
|
||||
const auto& music = node["music"];
|
||||
if (music.contains("enabled"))
|
||||
if (music.contains("enabled")) {
|
||||
audio.music.enabled = music["enabled"].get_value<bool>();
|
||||
if (music.contains("volume"))
|
||||
}
|
||||
if (music.contains("volume")) {
|
||||
audio.music.volume = music["volume"].get_value<float>();
|
||||
}
|
||||
}
|
||||
|
||||
if (node.contains("sound")) {
|
||||
const auto& sound = node["sound"];
|
||||
if (sound.contains("enabled"))
|
||||
if (sound.contains("enabled")) {
|
||||
audio.sound.enabled = sound["enabled"].get_value<bool>();
|
||||
if (sound.contains("volume"))
|
||||
}
|
||||
if (sound.contains("volume")) {
|
||||
audio.sound.volume = sound["volume"].get_value<float>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void loadVideoConfigFromYaml(const fkyaml::node& yaml) {
|
||||
if (!yaml.contains("video")) return;
|
||||
if (!yaml.contains("video")) {
|
||||
return;
|
||||
}
|
||||
const auto& node = yaml["video"];
|
||||
|
||||
if (node.contains("gpu_acceleration"))
|
||||
if (node.contains("gpu_acceleration")) {
|
||||
video.gpu_acceleration = node["gpu_acceleration"].get_value<bool>();
|
||||
if (node.contains("shader_enabled"))
|
||||
}
|
||||
if (node.contains("shader_enabled")) {
|
||||
video.shader_enabled = node["shader_enabled"].get_value<bool>();
|
||||
if (node.contains("supersampling"))
|
||||
}
|
||||
if (node.contains("supersampling")) {
|
||||
video.supersampling = node["supersampling"].get_value<bool>();
|
||||
}
|
||||
if (node.contains("scaling_mode")) {
|
||||
auto s = node["scaling_mode"].get_value<std::string>();
|
||||
if (s == "disabled")
|
||||
if (s == "disabled") {
|
||||
video.scaling_mode = ScalingMode::DISABLED;
|
||||
else if (s == "stretch")
|
||||
} else if (s == "stretch") {
|
||||
video.scaling_mode = ScalingMode::STRETCH;
|
||||
else if (s == "letterbox")
|
||||
} else if (s == "letterbox") {
|
||||
video.scaling_mode = ScalingMode::LETTERBOX;
|
||||
else if (s == "overscan")
|
||||
} else if (s == "overscan") {
|
||||
video.scaling_mode = ScalingMode::OVERSCAN;
|
||||
else
|
||||
} else {
|
||||
video.scaling_mode = ScalingMode::INTEGER;
|
||||
}
|
||||
}
|
||||
if (node.contains("vsync"))
|
||||
if (node.contains("vsync")) {
|
||||
video.vsync = node["vsync"].get_value<bool>();
|
||||
if (node.contains("aspect_ratio_4_3"))
|
||||
}
|
||||
if (node.contains("aspect_ratio_4_3")) {
|
||||
video.aspect_ratio_4_3 = node["aspect_ratio_4_3"].get_value<bool>();
|
||||
}
|
||||
if (node.contains("texture_filter")) {
|
||||
auto s = node["texture_filter"].get_value<std::string>();
|
||||
video.texture_filter = (s == "linear") ? TextureFilter::LINEAR : TextureFilter::NEAREST;
|
||||
}
|
||||
if (node.contains("downscale_algo"))
|
||||
if (node.contains("downscale_algo")) {
|
||||
video.downscale_algo = node["downscale_algo"].get_value<int>();
|
||||
}
|
||||
if (node.contains("internal_resolution")) {
|
||||
video.internal_resolution = node["internal_resolution"].get_value<int>();
|
||||
if (video.internal_resolution < 1) video.internal_resolution = 1;
|
||||
video.internal_resolution = std::max(video.internal_resolution, 1);
|
||||
}
|
||||
if (node.contains("current_shader"))
|
||||
if (node.contains("current_shader")) {
|
||||
video.current_shader = node["current_shader"].get_value<std::string>();
|
||||
if (node.contains("current_postfx_preset"))
|
||||
}
|
||||
if (node.contains("current_postfx_preset")) {
|
||||
video.current_postfx_preset = node["current_postfx_preset"].get_value<std::string>();
|
||||
if (node.contains("current_crtpi_preset"))
|
||||
}
|
||||
if (node.contains("current_crtpi_preset")) {
|
||||
video.current_crtpi_preset = node["current_crtpi_preset"].get_value<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
static void loadRenderInfoFromYaml(const fkyaml::node& yaml) {
|
||||
if (!yaml.contains("render_info")) return;
|
||||
if (!yaml.contains("render_info")) {
|
||||
return;
|
||||
}
|
||||
const auto& node = yaml["render_info"];
|
||||
|
||||
if (node.contains("position")) {
|
||||
auto pos = node["position"].get_value<std::string>();
|
||||
if (pos == "top")
|
||||
if (pos == "top") {
|
||||
render_info.position = RenderInfoPosition::TOP;
|
||||
else if (pos == "bottom")
|
||||
} else if (pos == "bottom") {
|
||||
render_info.position = RenderInfoPosition::BOTTOM;
|
||||
else
|
||||
} else {
|
||||
render_info.position = RenderInfoPosition::OFF;
|
||||
}
|
||||
}
|
||||
if (node.contains("show_time"))
|
||||
if (node.contains("show_time")) {
|
||||
render_info.show_time = node["show_time"].get_value<bool>();
|
||||
if (node.contains("text_color"))
|
||||
}
|
||||
if (node.contains("text_color")) {
|
||||
render_info.text_color = static_cast<Uint32>(node["text_color"].get_value<uint64_t>());
|
||||
if (node.contains("shadow_color"))
|
||||
}
|
||||
if (node.contains("shadow_color")) {
|
||||
render_info.shadow_color = static_cast<Uint32>(node["shadow_color"].get_value<uint64_t>());
|
||||
}
|
||||
}
|
||||
|
||||
static void loadWindowConfigFromYaml(const fkyaml::node& yaml) {
|
||||
if (!yaml.contains("window")) return;
|
||||
if (!yaml.contains("window")) {
|
||||
return;
|
||||
}
|
||||
const auto& node = yaml["window"];
|
||||
|
||||
if (node.contains("zoom"))
|
||||
if (node.contains("zoom")) {
|
||||
window.zoom = node["zoom"].get_value<int>();
|
||||
if (node.contains("fullscreen"))
|
||||
}
|
||||
if (node.contains("fullscreen")) {
|
||||
window.fullscreen = node["fullscreen"].get_value<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: carrega una SDL_Scancode des d'un string (nom SDL de la tecla).
|
||||
static void loadScancodeField(const fkyaml::node& node, const std::string& key, SDL_Scancode& target) {
|
||||
if (!node.contains(key)) return;
|
||||
if (!node.contains(key)) {
|
||||
return;
|
||||
}
|
||||
auto name = node[key].get_value<std::string>();
|
||||
SDL_Scancode sc = SDL_GetScancodeFromName(name.c_str());
|
||||
if (sc != SDL_SCANCODE_UNKNOWN) target = sc;
|
||||
if (sc != SDL_SCANCODE_UNKNOWN) {
|
||||
target = sc;
|
||||
}
|
||||
}
|
||||
|
||||
static void loadControlsFromYaml(const fkyaml::node& yaml) {
|
||||
@@ -212,15 +254,20 @@ namespace Options {
|
||||
}
|
||||
|
||||
static void loadGameConfigFromYaml(const fkyaml::node& yaml) {
|
||||
if (!yaml.contains("game")) return;
|
||||
if (!yaml.contains("game")) {
|
||||
return;
|
||||
}
|
||||
const auto& node = yaml["game"];
|
||||
|
||||
if (node.contains("use_new_logo"))
|
||||
if (node.contains("use_new_logo")) {
|
||||
game.use_new_logo = node["use_new_logo"].get_value<bool>();
|
||||
if (node.contains("show_title_credits"))
|
||||
}
|
||||
if (node.contains("show_title_credits")) {
|
||||
game.show_title_credits = node["show_title_credits"].get_value<bool>();
|
||||
if (node.contains("show_preload"))
|
||||
}
|
||||
if (node.contains("show_preload")) {
|
||||
game.show_preload = node["show_preload"].get_value<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
// Carrega les opcions des del fitxer configurat
|
||||
@@ -334,10 +381,11 @@ namespace Options {
|
||||
file << "render_info:\n";
|
||||
{
|
||||
const char* pos = "off";
|
||||
if (render_info.position == RenderInfoPosition::TOP)
|
||||
if (render_info.position == RenderInfoPosition::TOP) {
|
||||
pos = "top";
|
||||
else if (render_info.position == RenderInfoPosition::BOTTOM)
|
||||
} else if (render_info.position == RenderInfoPosition::BOTTOM) {
|
||||
pos = "bottom";
|
||||
}
|
||||
file << " position: " << pos << " # off/top/bottom\n";
|
||||
}
|
||||
file << " show_time: " << (render_info.show_time ? "true" : "false") << "\n";
|
||||
@@ -438,7 +486,9 @@ namespace Options {
|
||||
if (yaml.contains("presets")) {
|
||||
for (const auto& p : yaml["presets"]) {
|
||||
PostFXPreset preset;
|
||||
if (p.contains("name")) preset.name = p["name"].get_value<std::string>();
|
||||
if (p.contains("name")) {
|
||||
preset.name = p["name"].get_value<std::string>();
|
||||
}
|
||||
parseFloatField(p, "vignette", preset.vignette);
|
||||
parseFloatField(p, "scanlines", preset.scanlines);
|
||||
parseFloatField(p, "chroma", preset.chroma);
|
||||
@@ -497,7 +547,9 @@ namespace Options {
|
||||
if (yaml.contains("presets")) {
|
||||
for (const auto& p : yaml["presets"]) {
|
||||
CrtPiPreset preset;
|
||||
if (p.contains("name")) preset.name = p["name"].get_value<std::string>();
|
||||
if (p.contains("name")) {
|
||||
preset.name = p["name"].get_value<std::string>();
|
||||
}
|
||||
parseFloatField(p, "scanline_weight", preset.scanline_weight);
|
||||
parseFloatField(p, "scanline_gap_brightness", preset.scanline_gap_brightness);
|
||||
parseFloatField(p, "bloom_factor", preset.bloom_factor);
|
||||
@@ -506,24 +558,36 @@ namespace Options {
|
||||
parseFloatField(p, "mask_brightness", preset.mask_brightness);
|
||||
parseFloatField(p, "curvature_x", preset.curvature_x);
|
||||
parseFloatField(p, "curvature_y", preset.curvature_y);
|
||||
if (p.contains("mask_type")) try {
|
||||
if (p.contains("mask_type")) {
|
||||
try {
|
||||
preset.mask_type = p["mask_type"].get_value<int>();
|
||||
} catch (...) {}
|
||||
if (p.contains("enable_scanlines")) try {
|
||||
}
|
||||
if (p.contains("enable_scanlines")) {
|
||||
try {
|
||||
preset.enable_scanlines = p["enable_scanlines"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
if (p.contains("enable_multisample")) try {
|
||||
}
|
||||
if (p.contains("enable_multisample")) {
|
||||
try {
|
||||
preset.enable_multisample = p["enable_multisample"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
if (p.contains("enable_gamma")) try {
|
||||
}
|
||||
if (p.contains("enable_gamma")) {
|
||||
try {
|
||||
preset.enable_gamma = p["enable_gamma"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
if (p.contains("enable_curvature")) try {
|
||||
}
|
||||
if (p.contains("enable_curvature")) {
|
||||
try {
|
||||
preset.enable_curvature = p["enable_curvature"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
if (p.contains("enable_sharper")) try {
|
||||
}
|
||||
if (p.contains("enable_sharper")) {
|
||||
try {
|
||||
preset.enable_sharper = p["enable_sharper"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
}
|
||||
crtpi_presets.push_back(preset);
|
||||
}
|
||||
}
|
||||
|
||||
+59
-27
@@ -1,6 +1,6 @@
|
||||
#include "game/prota.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/jail/jgame.hpp"
|
||||
#include "core/jail/jinput.hpp"
|
||||
@@ -14,7 +14,9 @@ Prota::Prota(JD8_Surface gfx)
|
||||
Frame f;
|
||||
f.w = 15;
|
||||
f.h = 15;
|
||||
if (info::ctx.num_piramide == 4) f.h -= 5;
|
||||
if (info::ctx.num_piramide == 4) {
|
||||
f.h -= 5;
|
||||
}
|
||||
f.x = x * 15;
|
||||
f.y = 20 + (y * 15);
|
||||
entitat.frames.push_back(f);
|
||||
@@ -26,7 +28,9 @@ Prota::Prota(JD8_Surface gfx)
|
||||
Frame f;
|
||||
f.w = 15;
|
||||
f.h = 30;
|
||||
if (info::ctx.num_piramide == 4) f.h -= 5;
|
||||
if (info::ctx.num_piramide == 4) {
|
||||
f.h -= 5;
|
||||
}
|
||||
f.x = x;
|
||||
f.y = y;
|
||||
entitat.frames.push_back(f);
|
||||
@@ -38,7 +42,9 @@ Prota::Prota(JD8_Surface gfx)
|
||||
Frame f;
|
||||
f.w = 15;
|
||||
f.h = 15;
|
||||
if (info::ctx.num_piramide == 4) f.h -= 5;
|
||||
if (info::ctx.num_piramide == 4) {
|
||||
f.h -= 5;
|
||||
}
|
||||
f.x = x;
|
||||
f.y = y;
|
||||
entitat.frames.push_back(f);
|
||||
@@ -48,23 +54,29 @@ Prota::Prota(JD8_Surface gfx)
|
||||
entitat.animacions.resize(6);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
entitat.animacions[i].frames = {
|
||||
static_cast<Uint8>(0 + i * 5),
|
||||
static_cast<Uint8>(1 + i * 5),
|
||||
static_cast<Uint8>(2 + i * 5),
|
||||
static_cast<Uint8>(1 + i * 5),
|
||||
static_cast<Uint8>(0 + i * 5),
|
||||
static_cast<Uint8>(3 + i * 5),
|
||||
static_cast<Uint8>(4 + i * 5),
|
||||
static_cast<Uint8>(3 + i * 5),
|
||||
static_cast<Uint8>(0 + (i * 5)),
|
||||
static_cast<Uint8>(1 + (i * 5)),
|
||||
static_cast<Uint8>(2 + (i * 5)),
|
||||
static_cast<Uint8>(1 + (i * 5)),
|
||||
static_cast<Uint8>(0 + (i * 5)),
|
||||
static_cast<Uint8>(3 + (i * 5)),
|
||||
static_cast<Uint8>(4 + (i * 5)),
|
||||
static_cast<Uint8>(3 + (i * 5)),
|
||||
};
|
||||
}
|
||||
|
||||
entitat.animacions[4].frames.resize(50);
|
||||
for (int i = 0; i < 50; i++) entitat.animacions[4].frames[i] = i + 20;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
entitat.animacions[4].frames[i] = i + 20;
|
||||
}
|
||||
|
||||
entitat.animacions[5].frames.resize(48);
|
||||
for (int i = 0; i < 12; i++) entitat.animacions[5].frames[i] = i + 70;
|
||||
for (int i = 12; i < 48; i++) entitat.animacions[5].frames[i] = 81;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
entitat.animacions[5].frames[i] = i + 70;
|
||||
}
|
||||
for (int i = 12; i < 48; i++) {
|
||||
entitat.animacions[5].frames[i] = 81;
|
||||
}
|
||||
|
||||
cur_frame = 0;
|
||||
x = 150;
|
||||
@@ -87,40 +99,56 @@ void Prota::draw() {
|
||||
}
|
||||
}
|
||||
|
||||
Uint8 Prota::update() {
|
||||
auto Prota::update() -> Uint8 {
|
||||
Uint8 eixir = 0;
|
||||
|
||||
if (this->o < 4) {
|
||||
Uint8 dir = 4;
|
||||
if (JI_KeyPressed(SDL_SCANCODE_DOWN)) {
|
||||
if ((this->x - 20) % 65 == 0) this->o = 0;
|
||||
if ((this->x - 20) % 65 == 0) {
|
||||
this->o = 0;
|
||||
}
|
||||
dir = this->o;
|
||||
}
|
||||
if (JI_KeyPressed(SDL_SCANCODE_UP)) {
|
||||
if ((this->x - 20) % 65 == 0) this->o = 1;
|
||||
if ((this->x - 20) % 65 == 0) {
|
||||
this->o = 1;
|
||||
}
|
||||
dir = this->o;
|
||||
}
|
||||
if (JI_KeyPressed(SDL_SCANCODE_RIGHT)) {
|
||||
if ((this->y - 30) % 35 == 0) this->o = 2;
|
||||
if ((this->y - 30) % 35 == 0) {
|
||||
this->o = 2;
|
||||
}
|
||||
dir = this->o;
|
||||
}
|
||||
if (JI_KeyPressed(SDL_SCANCODE_LEFT)) {
|
||||
if ((this->y - 30) % 35 == 0) this->o = 3;
|
||||
if ((this->y - 30) % 35 == 0) {
|
||||
this->o = 3;
|
||||
}
|
||||
dir = this->o;
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case 0:
|
||||
if (this->y < 170) this->y++;
|
||||
if (this->y < 170) {
|
||||
this->y++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (this->y > 30) this->y--;
|
||||
if (this->y > 30) {
|
||||
this->y--;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (this->x < 280) this->x++;
|
||||
if (this->x < 280) {
|
||||
this->x++;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (this->x > 20) this->x--;
|
||||
if (this->x > 20) {
|
||||
this->x--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -128,13 +156,17 @@ Uint8 Prota::update() {
|
||||
this->cur_frame = 0;
|
||||
} else {
|
||||
this->frame_pejades++;
|
||||
if (this->frame_pejades == 15) this->frame_pejades = 0;
|
||||
if (this->frame_pejades == 15) {
|
||||
this->frame_pejades = 0;
|
||||
}
|
||||
if (JG_GetCycleCounter() % this->cycles_per_frame == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) this->cur_frame = 0;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
eixir = false;
|
||||
eixir = 0u;
|
||||
} else {
|
||||
if (JG_GetCycleCounter() % this->cycles_per_frame == 0) {
|
||||
this->cur_frame++;
|
||||
|
||||
@@ -8,7 +8,7 @@ class Prota : public Sprite {
|
||||
explicit Prota(JD8_Surface gfx);
|
||||
|
||||
void draw() override;
|
||||
Uint8 update();
|
||||
auto update() -> Uint8;
|
||||
|
||||
Uint8 frame_pejades;
|
||||
bool pergami;
|
||||
|
||||
@@ -42,7 +42,9 @@ namespace scenes {
|
||||
switch (phase_) {
|
||||
case Phase::FadingIn:
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) phase_ = Phase::Showing;
|
||||
if (fade_.done()) {
|
||||
phase_ = Phase::Showing;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Showing:
|
||||
@@ -60,7 +62,9 @@ namespace scenes {
|
||||
|
||||
case Phase::FadingOut:
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) phase_ = Phase::Done;
|
||||
if (fade_.done()) {
|
||||
phase_ = Phase::Done;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Done:
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace scenes {
|
||||
public:
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
int nextState() const override { return 0; }
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto nextState() const -> int override { return 0; }
|
||||
|
||||
private:
|
||||
enum class Phase { FadingIn,
|
||||
|
||||
@@ -35,10 +35,12 @@ namespace scenes {
|
||||
render();
|
||||
}
|
||||
|
||||
void BootLoaderScene::render() const {
|
||||
void BootLoaderScene::render() {
|
||||
JD8_ClearScreen(BG_COLOR);
|
||||
|
||||
if (!Options::game.show_preload) return;
|
||||
if (!Options::game.show_preload) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float pct = Resource::Cache::get()->getProgress();
|
||||
const int filled = static_cast<int>(static_cast<float>(BAR_W) * pct);
|
||||
|
||||
@@ -15,10 +15,10 @@ namespace scenes {
|
||||
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return done_; }
|
||||
[[nodiscard]] auto done() const -> bool override { return done_; }
|
||||
|
||||
private:
|
||||
void render() const;
|
||||
static void render();
|
||||
|
||||
bool done_{false};
|
||||
};
|
||||
|
||||
@@ -18,14 +18,14 @@ namespace {
|
||||
};
|
||||
|
||||
constexpr CocheFrame COCHE_FRAMES[8] = {
|
||||
{214, 152},
|
||||
{214, 104},
|
||||
{214, 56},
|
||||
{214, 104},
|
||||
{214, 152},
|
||||
{214, 8},
|
||||
{108, 152},
|
||||
{214, 8},
|
||||
{.x = 214, .y = 152},
|
||||
{.x = 214, .y = 104},
|
||||
{.x = 214, .y = 56},
|
||||
{.x = 214, .y = 104},
|
||||
{.x = 214, .y = 152},
|
||||
{.x = 214, .y = 8},
|
||||
{.x = 108, .y = 152},
|
||||
{.x = 214, .y = 8},
|
||||
};
|
||||
|
||||
constexpr int CONTADOR_MAX = 3100; // ~62 s de crèdits a 20 ms/tick
|
||||
@@ -101,7 +101,7 @@ namespace scenes {
|
||||
|
||||
void CreditsScene::writeTrickIni() {
|
||||
FILE* ini = std::fopen("trick.ini", "wb");
|
||||
if (ini) {
|
||||
if (ini != nullptr) {
|
||||
std::fwrite("1", 1, 1, ini);
|
||||
std::fclose(ini);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace scenes {
|
||||
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
|
||||
private:
|
||||
enum class Phase { Rolling,
|
||||
@@ -36,7 +36,7 @@ namespace scenes {
|
||||
Done };
|
||||
|
||||
void render();
|
||||
void writeTrickIni();
|
||||
static void writeTrickIni();
|
||||
|
||||
SurfaceHandle vaddr2_; // gfx/final.gif (sprites i coches)
|
||||
SurfaceHandle vaddr3_; // gfx/finals.gif (fons / parallax)
|
||||
|
||||
@@ -10,7 +10,9 @@ namespace scenes {
|
||||
loop_(loop) {}
|
||||
|
||||
void FrameAnimator::tick(int delta_ms) {
|
||||
if (finished_) return;
|
||||
if (finished_) {
|
||||
return;
|
||||
}
|
||||
elapsed_ms_ += delta_ms;
|
||||
while (elapsed_ms_ >= frame_ms_) {
|
||||
elapsed_ms_ -= frame_ms_;
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace scenes {
|
||||
|
||||
void tick(int delta_ms);
|
||||
|
||||
int frame() const { return current_frame_; }
|
||||
bool done() const { return !loop_ && finished_; }
|
||||
int numFrames() const { return num_frames_; }
|
||||
[[nodiscard]] auto frame() const -> int { return current_frame_; }
|
||||
[[nodiscard]] auto done() const -> bool { return !loop_ && finished_; }
|
||||
[[nodiscard]] auto numFrames() const -> int { return num_frames_; }
|
||||
|
||||
void reset();
|
||||
void setFrameMs(int frame_ms) { frame_ms_ = frame_ms; }
|
||||
|
||||
@@ -108,14 +108,28 @@ namespace scenes {
|
||||
// els índexs 16..31 (grup del verd brillant del logo).
|
||||
for (int i = 16; i < 32; i++) {
|
||||
if (i == 17) {
|
||||
if (pal_[i].r < 255) pal_[i].r++;
|
||||
if (pal_[i].g < 255) pal_[i].g++;
|
||||
if (pal_[i].b < 255) pal_[i].b++;
|
||||
if (pal_[i].r < 255) {
|
||||
pal_[i].r++;
|
||||
}
|
||||
if (pal_[i].g < 255) {
|
||||
pal_[i].g++;
|
||||
}
|
||||
if (pal_[i].b < 255) {
|
||||
pal_[i].b++;
|
||||
}
|
||||
}
|
||||
if (pal_[i].b < pal_[i].g) {
|
||||
pal_[i].b++;
|
||||
}
|
||||
if (pal_[i].b > pal_[i].g) {
|
||||
pal_[i].b--;
|
||||
}
|
||||
if (pal_[i].r < pal_[i].g) {
|
||||
pal_[i].r++;
|
||||
}
|
||||
if (pal_[i].r > pal_[i].g) {
|
||||
pal_[i].r--;
|
||||
}
|
||||
if (pal_[i].b < pal_[i].g) pal_[i].b++;
|
||||
if (pal_[i].b > pal_[i].g) pal_[i].b--;
|
||||
if (pal_[i].r < pal_[i].g) pal_[i].r++;
|
||||
if (pal_[i].r > pal_[i].g) pal_[i].r--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace scenes {
|
||||
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
|
||||
@@ -27,21 +27,21 @@ namespace {
|
||||
};
|
||||
|
||||
constexpr RevealStep REVEAL_STEPS[] = {
|
||||
{100, 27, 68, false, false}, // J
|
||||
{100, 53, 96, false, false}, // JA
|
||||
{100, 66, 109, false, false}, // JAI
|
||||
{200, 92, 136, false, false}, // JAIL
|
||||
{200, 92, -1, true, false}, // JAIL (clear, sense avió — parpelleig)
|
||||
{100, 118, 160, false, false}, // JAILG
|
||||
{100, 145, 188, false, false}, // JAILGA
|
||||
{100, 178, 221, false, false}, // JAILGAM
|
||||
{100, 205, 248, false, false}, // JAILGAME
|
||||
{200, 0, 274, false, true}, // JAILGAMES (wordmark complet) + avió
|
||||
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
|
||||
{200, 0, 274, false, true}, // JAILGAMES + avió (sense clear)
|
||||
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
|
||||
{200, 0, 274, false, true}, // JAILGAMES + avió (sense clear)
|
||||
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
|
||||
{.duration_ms = 100, .body_w = 27, .plane_x = 68, .clear = false, .wordmark = false}, // J
|
||||
{.duration_ms = 100, .body_w = 53, .plane_x = 96, .clear = false, .wordmark = false}, // JA
|
||||
{.duration_ms = 100, .body_w = 66, .plane_x = 109, .clear = false, .wordmark = false}, // JAI
|
||||
{.duration_ms = 200, .body_w = 92, .plane_x = 136, .clear = false, .wordmark = false}, // JAIL
|
||||
{.duration_ms = 200, .body_w = 92, .plane_x = -1, .clear = true, .wordmark = false}, // JAIL (clear, sense avió — parpelleig)
|
||||
{.duration_ms = 100, .body_w = 118, .plane_x = 160, .clear = false, .wordmark = false}, // JAILG
|
||||
{.duration_ms = 100, .body_w = 145, .plane_x = 188, .clear = false, .wordmark = false}, // JAILGA
|
||||
{.duration_ms = 100, .body_w = 178, .plane_x = 221, .clear = false, .wordmark = false}, // JAILGAM
|
||||
{.duration_ms = 100, .body_w = 205, .plane_x = 248, .clear = false, .wordmark = false}, // JAILGAME
|
||||
{.duration_ms = 200, .body_w = 0, .plane_x = 274, .clear = false, .wordmark = true}, // JAILGAMES (wordmark complet) + avió
|
||||
{.duration_ms = 200, .body_w = 0, .plane_x = -1, .clear = true, .wordmark = true}, // JAILGAMES (clear, sense avió)
|
||||
{.duration_ms = 200, .body_w = 0, .plane_x = 274, .clear = false, .wordmark = true}, // JAILGAMES + avió (sense clear)
|
||||
{.duration_ms = 200, .body_w = 0, .plane_x = -1, .clear = true, .wordmark = true}, // JAILGAMES (clear, sense avió)
|
||||
{.duration_ms = 200, .body_w = 0, .plane_x = 274, .clear = false, .wordmark = true}, // JAILGAMES + avió (sense clear)
|
||||
{.duration_ms = 200, .body_w = 0, .plane_x = -1, .clear = true, .wordmark = true}, // JAILGAMES (clear, sense avió)
|
||||
};
|
||||
constexpr int REVEAL_COUNT = sizeof(REVEAL_STEPS) / sizeof(REVEAL_STEPS[0]);
|
||||
|
||||
@@ -88,7 +88,9 @@ namespace scenes {
|
||||
|
||||
case Phase::Reveal: {
|
||||
const RevealStep& s = REVEAL_STEPS[reveal_index_];
|
||||
if (s.clear) JD8_ClearScreen(0);
|
||||
if (s.clear) {
|
||||
JD8_ClearScreen(0);
|
||||
}
|
||||
if (s.wordmark) {
|
||||
drawWordmark(gfx_);
|
||||
} else if (s.body_w > 0) {
|
||||
@@ -121,14 +123,28 @@ namespace scenes {
|
||||
// els altres convergeixen cap al mateix gris mitjà.
|
||||
for (int i = 16; i < 32; i++) {
|
||||
if (i == 17) {
|
||||
if (pal_[i].r < 255) pal_[i].r++;
|
||||
if (pal_[i].g < 255) pal_[i].g++;
|
||||
if (pal_[i].b < 255) pal_[i].b++;
|
||||
if (pal_[i].r < 255) {
|
||||
pal_[i].r++;
|
||||
}
|
||||
if (pal_[i].g < 255) {
|
||||
pal_[i].g++;
|
||||
}
|
||||
if (pal_[i].b < 255) {
|
||||
pal_[i].b++;
|
||||
}
|
||||
}
|
||||
if (pal_[i].b < pal_[i].g) {
|
||||
pal_[i].b++;
|
||||
}
|
||||
if (pal_[i].b > pal_[i].g) {
|
||||
pal_[i].b--;
|
||||
}
|
||||
if (pal_[i].r < pal_[i].g) {
|
||||
pal_[i].r++;
|
||||
}
|
||||
if (pal_[i].r > pal_[i].g) {
|
||||
pal_[i].r--;
|
||||
}
|
||||
if (pal_[i].b < pal_[i].g) pal_[i].b++;
|
||||
if (pal_[i].b > pal_[i].g) pal_[i].b--;
|
||||
if (pal_[i].r < pal_[i].g) pal_[i].r++;
|
||||
if (pal_[i].r > pal_[i].g) pal_[i].r--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace scenes {
|
||||
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
|
||||
@@ -146,17 +146,17 @@ namespace {
|
||||
}
|
||||
|
||||
constexpr SpritePhase variant_0[] = {
|
||||
{0, 200, v0_walk_right, true},
|
||||
{0, 200, v0_pull_map_right, true},
|
||||
{200, 0, v0_pull_map_right, true}, // guarda el mapa (reprodueix inversament)
|
||||
{200, 80, v0_walk_left_to_80, true},
|
||||
{0, 200, v0_pull_map_left, true},
|
||||
{300, 95, v0_momia_left, true},
|
||||
{0, 50, v0_turn, true},
|
||||
{0, 49, v0_jump1, true},
|
||||
{50, 99, v0_jump2, true},
|
||||
{80, 0, v0_walk_final, true},
|
||||
{0, 150, v0_final, true},
|
||||
{.start_i = 0, .end_i = 200, .render = v0_walk_right, .skippable = true},
|
||||
{.start_i = 0, .end_i = 200, .render = v0_pull_map_right, .skippable = true},
|
||||
{.start_i = 200, .end_i = 0, .render = v0_pull_map_right, .skippable = true}, // guarda el mapa (reprodueix inversament)
|
||||
{.start_i = 200, .end_i = 80, .render = v0_walk_left_to_80, .skippable = true},
|
||||
{.start_i = 0, .end_i = 200, .render = v0_pull_map_left, .skippable = true},
|
||||
{.start_i = 300, .end_i = 95, .render = v0_momia_left, .skippable = true},
|
||||
{.start_i = 0, .end_i = 50, .render = v0_turn, .skippable = true},
|
||||
{.start_i = 0, .end_i = 49, .render = v0_jump1, .skippable = true},
|
||||
{.start_i = 50, .end_i = 99, .render = v0_jump2, .skippable = true},
|
||||
{.start_i = 80, .end_i = 0, .render = v0_walk_final, .skippable = true},
|
||||
{.start_i = 0, .end_i = 150, .render = v0_final, .skippable = true},
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
@@ -224,13 +224,13 @@ namespace {
|
||||
}
|
||||
|
||||
constexpr SpritePhase variant_1[] = {
|
||||
{0, 200, v1_walk_right, true},
|
||||
{0, 300, v1_pull_map, true},
|
||||
{0, 100, v1_interrogant, true},
|
||||
{0, 200, v1_drop_map, true},
|
||||
{0, 75, v1_stone_fall, true},
|
||||
{0, 19, v1_stone_break, true},
|
||||
{0, 200, v1_final, true},
|
||||
{.start_i = 0, .end_i = 200, .render = v1_walk_right, .skippable = true},
|
||||
{.start_i = 0, .end_i = 300, .render = v1_pull_map, .skippable = true},
|
||||
{.start_i = 0, .end_i = 100, .render = v1_interrogant, .skippable = true},
|
||||
{.start_i = 0, .end_i = 200, .render = v1_drop_map, .skippable = true},
|
||||
{.start_i = 0, .end_i = 75, .render = v1_stone_fall, .skippable = true},
|
||||
{.start_i = 0, .end_i = 19, .render = v1_stone_break, .skippable = true},
|
||||
{.start_i = 0, .end_i = 200, .render = v1_final, .skippable = true},
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
@@ -268,17 +268,17 @@ namespace {
|
||||
}
|
||||
|
||||
constexpr SpritePhase variant_2[] = {
|
||||
{0, 145, v2_approach, true},
|
||||
{0, 100, v2_still, true},
|
||||
{0, 50, v2_horn, true},
|
||||
{0, 800, v2_ball, true},
|
||||
{.start_i = 0, .end_i = 145, .render = v2_approach, .skippable = true},
|
||||
{.start_i = 0, .end_i = 100, .render = v2_still, .skippable = true},
|
||||
{.start_i = 0, .end_i = 50, .render = v2_horn, .skippable = true},
|
||||
{.start_i = 0, .end_i = 800, .render = v2_ball, .skippable = true},
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Dispatch per variant
|
||||
// =========================================================================
|
||||
|
||||
const SpritePhase* variant_table(int variant) {
|
||||
auto variant_table(int variant) -> const SpritePhase* {
|
||||
switch (variant) {
|
||||
case 0:
|
||||
return variant_0;
|
||||
@@ -290,7 +290,7 @@ namespace {
|
||||
return variant_0;
|
||||
}
|
||||
|
||||
int variant_length(int variant) {
|
||||
auto variant_length(int variant) -> int {
|
||||
switch (variant) {
|
||||
case 0:
|
||||
return sizeof(variant_0) / sizeof(variant_0[0]);
|
||||
@@ -302,11 +302,11 @@ namespace {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int phase_step_count(const SpritePhase& p) {
|
||||
auto phase_step_count(const SpritePhase& p) -> int {
|
||||
return std::abs(p.end_i - p.start_i) + 1;
|
||||
}
|
||||
|
||||
int phase_current_i(const SpritePhase& p, int step) {
|
||||
auto phase_current_i(const SpritePhase& p, int step) -> int {
|
||||
return p.end_i >= p.start_i ? p.start_i + step : p.start_i - step;
|
||||
}
|
||||
|
||||
@@ -334,7 +334,9 @@ namespace scenes {
|
||||
}
|
||||
|
||||
void IntroSpritesScene::tick(int delta_ms) {
|
||||
if (done_) return;
|
||||
if (done_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SpritePhase* phases = variant_table(variant_);
|
||||
const int num_phases = variant_length(variant_);
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace scenes {
|
||||
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return done_; }
|
||||
[[nodiscard]] auto done() const -> bool override { return done_; }
|
||||
|
||||
private:
|
||||
SurfaceHandle gfx_;
|
||||
|
||||
@@ -62,7 +62,9 @@ namespace scenes {
|
||||
switch (phase_) {
|
||||
case Phase::FadingIn:
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) phase_ = Phase::Showing;
|
||||
if (fade_.done()) {
|
||||
phase_ = Phase::Showing;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Showing: {
|
||||
@@ -70,20 +72,26 @@ namespace scenes {
|
||||
palmeres_acc_ms_ += delta_ms;
|
||||
while (palmeres_acc_ms_ >= 80) {
|
||||
palmeres_acc_ms_ -= 80;
|
||||
if (--palmeres_ < 0) palmeres_ = 319;
|
||||
if (--palmeres_ < 0) {
|
||||
palmeres_ = 319;
|
||||
}
|
||||
}
|
||||
|
||||
// Horitzó: 1 pixel cada 320 ms (= cada 16 ticks × 20 ms)
|
||||
horitzo_acc_ms_ += delta_ms;
|
||||
while (horitzo_acc_ms_ >= 320) {
|
||||
horitzo_acc_ms_ -= 320;
|
||||
if (--horitzo_ < 0) horitzo_ = 319;
|
||||
if (--horitzo_ < 0) {
|
||||
horitzo_ = 319;
|
||||
}
|
||||
}
|
||||
|
||||
camello_.tick(delta_ms);
|
||||
|
||||
blink_ms_ += delta_ms;
|
||||
if (blink_ms_ >= 2000) blink_ms_ %= 2000;
|
||||
if (blink_ms_ >= 2000) {
|
||||
blink_ms_ %= 2000;
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
@@ -102,7 +110,9 @@ namespace scenes {
|
||||
|
||||
case Phase::FadingOut:
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) phase_ = Phase::Done;
|
||||
if (fade_.done()) {
|
||||
phase_ = Phase::Done;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Done:
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace scenes {
|
||||
public:
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
|
||||
private:
|
||||
enum class Phase { FadingIn,
|
||||
|
||||
@@ -32,7 +32,9 @@ namespace scenes {
|
||||
switch (phase_) {
|
||||
case Phase::FadingIn:
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) phase_ = Phase::Showing;
|
||||
if (fade_.done()) {
|
||||
phase_ = Phase::Showing;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Showing:
|
||||
@@ -53,7 +55,9 @@ namespace scenes {
|
||||
|
||||
case Phase::FadingOut:
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) phase_ = Phase::Done;
|
||||
if (fade_.done()) {
|
||||
phase_ = Phase::Done;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::Done:
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace scenes {
|
||||
public:
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
|
||||
private:
|
||||
enum class Phase { FadingIn,
|
||||
|
||||
@@ -13,7 +13,9 @@ namespace scenes {
|
||||
}
|
||||
|
||||
void PaletteFade::tick(int /*delta_ms*/) {
|
||||
if (!active_) return;
|
||||
if (!active_) {
|
||||
return;
|
||||
}
|
||||
// El fade té 32 passos interns. Amb un tick per frame (~16ms)
|
||||
// dura ~512ms — el mateix temps que la versió bloquejant original.
|
||||
// Si en el futur volem fer-lo genuinament time-based (p.ex. "fade
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace scenes {
|
||||
|
||||
void tick(int delta_ms);
|
||||
|
||||
bool active() const { return active_; }
|
||||
bool done() const { return !active_; }
|
||||
[[nodiscard]] auto active() const -> bool { return active_; }
|
||||
[[nodiscard]] auto done() const -> bool { return !active_; }
|
||||
|
||||
private:
|
||||
bool active_{false};
|
||||
|
||||
@@ -24,14 +24,14 @@ namespace scenes {
|
||||
|
||||
virtual void tick(int delta_ms) = 0;
|
||||
|
||||
virtual bool done() const = 0;
|
||||
[[nodiscard]] virtual auto done() const -> bool = 0;
|
||||
|
||||
// Valor retornat al caller quan l'escena acaba — equivalent al int
|
||||
// que retornaven les velles funcions `Go()` de ModuleSequence:
|
||||
// 1 = continuar amb la següent escena segons info::ctx
|
||||
// 0 = entrar al gameplay (ModuleGame)
|
||||
// -1 = eixir del joc
|
||||
virtual int nextState() const { return 1; }
|
||||
[[nodiscard]] virtual auto nextState() const -> int { return 1; }
|
||||
};
|
||||
|
||||
} // namespace scenes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace scenes {
|
||||
|
||||
SceneRegistry& SceneRegistry::instance() {
|
||||
auto SceneRegistry::instance() -> SceneRegistry& {
|
||||
static SceneRegistry inst;
|
||||
return inst;
|
||||
}
|
||||
@@ -11,9 +11,11 @@ namespace scenes {
|
||||
factories_[state_key] = std::move(factory);
|
||||
}
|
||||
|
||||
std::unique_ptr<Scene> SceneRegistry::tryCreate(int state_key) const {
|
||||
auto SceneRegistry::tryCreate(int state_key) const -> std::unique_ptr<Scene> {
|
||||
const auto it = factories_.find(state_key);
|
||||
if (it == factories_.end()) return nullptr;
|
||||
if (it == factories_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ namespace scenes {
|
||||
public:
|
||||
using Factory = std::function<std::unique_ptr<Scene>()>;
|
||||
|
||||
static SceneRegistry& instance();
|
||||
static auto instance() -> SceneRegistry&;
|
||||
|
||||
void registerScene(int state_key, Factory factory);
|
||||
|
||||
// Retorna `nullptr` si no hi ha cap escena registrada per a aquest
|
||||
// state. El caller hauria de caure al path legacy en aquest cas.
|
||||
std::unique_ptr<Scene> tryCreate(int state_key) const;
|
||||
auto tryCreate(int state_key) const -> std::unique_ptr<Scene>;
|
||||
|
||||
private:
|
||||
SceneRegistry() = default;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
namespace scenes {
|
||||
|
||||
namespace {
|
||||
std::string basename(const char* path) {
|
||||
auto basename(const char* path) -> std::string {
|
||||
std::string s = path;
|
||||
auto pos = s.find_last_of("/\\");
|
||||
return pos == std::string::npos ? s : s.substr(pos + 1);
|
||||
@@ -15,7 +15,9 @@ namespace scenes {
|
||||
} // namespace
|
||||
|
||||
void playMusic(const char* filename, int loop) {
|
||||
if (!filename) return;
|
||||
if (filename == nullptr) {
|
||||
return;
|
||||
}
|
||||
Audio::get()->playMusic(basename(filename), loop);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace scenes {
|
||||
|
||||
case Phase::Tomba1ScrollIn: {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
const int contador = std::min(128, phase_acc_ms_ / TICK_MS + 1);
|
||||
const int contador = std::min(128, (phase_acc_ms_ / TICK_MS) + 1);
|
||||
// Dos blits solapats: el primer avança a velocitat completa,
|
||||
// el segon (contingut de la dreta del src) a meitat (contador>>1).
|
||||
JD8_Blit(70, 60, gfx_, 0, contador, 178, 70);
|
||||
@@ -130,7 +130,7 @@ namespace scenes {
|
||||
|
||||
case Phase::Tomba2ScrollIn: {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
const int contador = std::min(94, phase_acc_ms_ / TICK_MS + 1);
|
||||
const int contador = std::min(94, (phase_acc_ms_ / TICK_MS) + 1);
|
||||
JD8_Blit(55, 53, gfx_, 0, 158 - contador, 211, contador);
|
||||
if (phase_acc_ms_ >= TOMBA2_SCROLL_MS) {
|
||||
phase_ = Phase::Tomba2Hold;
|
||||
@@ -150,7 +150,7 @@ namespace scenes {
|
||||
|
||||
case Phase::Tomba2Reveal: {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
const int contador = std::min(80, phase_acc_ms_ / TICK_MS + 1);
|
||||
const int contador = std::min(80, (phase_acc_ms_ / TICK_MS) + 1);
|
||||
// Revelat horitzontal simètric: l'amplada creix 2px per tick
|
||||
// i el src_x es desplaça a l'esquerra el mateix.
|
||||
JD8_Blit(80, 68, gfx_, 160 - (contador * 2), 0, contador * 2, 64);
|
||||
|
||||
@@ -31,8 +31,8 @@ namespace scenes {
|
||||
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
int nextState() const override { return 0; }
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto nextState() const -> int override { return 0; }
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
@@ -50,7 +50,7 @@ namespace scenes {
|
||||
};
|
||||
|
||||
void swapToTomba2();
|
||||
void beginRedPulseSetup();
|
||||
static void beginRedPulseSetup();
|
||||
void beginFinalFade();
|
||||
|
||||
SurfaceHandle gfx_;
|
||||
|
||||
@@ -105,7 +105,9 @@ namespace scenes {
|
||||
// el final natural crida JA_FadeOutMusic (beginFinalFade() distingeix).
|
||||
if (!skip_triggered_ && JI_AnyKey()) {
|
||||
skip_triggered_ = true;
|
||||
if (num_piramide_at_start_ != 7) Audio::get()->fadeOutMusic(250);
|
||||
if (num_piramide_at_start_ != 7) {
|
||||
Audio::get()->fadeOutMusic(250);
|
||||
}
|
||||
fade_.startFadeOut();
|
||||
phase_ = Phase::FadeFinal;
|
||||
}
|
||||
@@ -118,7 +120,7 @@ namespace scenes {
|
||||
const int slide_idx = (phase_ == Phase::Slide1Enter ? 0
|
||||
: phase_ == Phase::Slide2Enter ? 1
|
||||
: 2);
|
||||
const float t = std::min(1.0f, static_cast<float>(phase_acc_ms_) / static_cast<float>(SCROLL_MS));
|
||||
const float t = std::min(1.0F, static_cast<float>(phase_acc_ms_) / static_cast<float>(SCROLL_MS));
|
||||
const float eased = Easing::outCubic(t);
|
||||
const int pos_x = Easing::lerpInt(SLIDE_START_X[slide_idx], 0, eased);
|
||||
drawSlide(slide_idx, pos_x);
|
||||
@@ -126,12 +128,13 @@ namespace scenes {
|
||||
if (phase_acc_ms_ >= SCROLL_MS) {
|
||||
// Garanteix posició final exacta (pos_x=0).
|
||||
drawSlide(slide_idx, 0);
|
||||
if (phase_ == Phase::Slide1Enter)
|
||||
if (phase_ == Phase::Slide1Enter) {
|
||||
phase_ = Phase::Slide1Hold;
|
||||
else if (phase_ == Phase::Slide2Enter)
|
||||
} else if (phase_ == Phase::Slide2Enter) {
|
||||
phase_ = Phase::Slide2Hold;
|
||||
else
|
||||
} else {
|
||||
phase_ = Phase::Slide3Hold;
|
||||
}
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
break;
|
||||
@@ -142,10 +145,11 @@ namespace scenes {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
if (phase_acc_ms_ >= HOLD_MS) {
|
||||
fade_.startFadeOut();
|
||||
if (phase_ == Phase::Slide1Hold)
|
||||
if (phase_ == Phase::Slide1Hold) {
|
||||
phase_ = Phase::FadeOut1;
|
||||
else
|
||||
} else {
|
||||
phase_ = Phase::FadeOut2;
|
||||
}
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
break;
|
||||
@@ -163,10 +167,11 @@ namespace scenes {
|
||||
if (fade_.done()) {
|
||||
restorePalette();
|
||||
JD8_ClearScreen(BG_COLOR_INDEX);
|
||||
if (phase_ == Phase::FadeOut1)
|
||||
if (phase_ == Phase::FadeOut1) {
|
||||
phase_ = Phase::Slide2Enter;
|
||||
else
|
||||
} else {
|
||||
phase_ = Phase::Slide3Enter;
|
||||
}
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace scenes {
|
||||
|
||||
void onEnter() override;
|
||||
void tick(int delta_ms) override;
|
||||
bool done() const override { return phase_ == Phase::Done; }
|
||||
int nextState() const override { return next_state_; }
|
||||
[[nodiscard]] auto done() const -> bool override { return phase_ == Phase::Done; }
|
||||
[[nodiscard]] auto nextState() const -> int override { return next_state_; }
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace scenes {
|
||||
y1_ = y1;
|
||||
duration_ms_ = std::max(0, duration_ms);
|
||||
elapsed_ms_ = 0;
|
||||
ease_ = ease ? ease : Easing::linear;
|
||||
ease_ = (ease != nullptr) ? ease : Easing::linear;
|
||||
cur_x_ = x0;
|
||||
cur_y_ = y0;
|
||||
}
|
||||
@@ -38,8 +38,10 @@ namespace scenes {
|
||||
cur_y_ = Easing::lerpInt(y0_, y1_, eased);
|
||||
}
|
||||
|
||||
float SpriteMover::progress() const {
|
||||
if (duration_ms_ <= 0) return 1.0f;
|
||||
auto SpriteMover::progress() const -> float {
|
||||
if (duration_ms_ <= 0) {
|
||||
return 1.0F;
|
||||
}
|
||||
return static_cast<float>(elapsed_ms_) / static_cast<float>(duration_ms_);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace scenes {
|
||||
|
||||
void tick(int delta_ms);
|
||||
|
||||
int x() const { return cur_x_; }
|
||||
int y() const { return cur_y_; }
|
||||
bool done() const { return elapsed_ms_ >= duration_ms_; }
|
||||
float progress() const; // 0..1 sense easing aplicat
|
||||
[[nodiscard]] auto x() const -> int { return cur_x_; }
|
||||
[[nodiscard]] auto y() const -> int { return cur_y_; }
|
||||
[[nodiscard]] auto done() const -> bool { return elapsed_ms_ >= duration_ms_; }
|
||||
[[nodiscard]] auto progress() const -> float; // 0..1 sense easing aplicat
|
||||
|
||||
private:
|
||||
int x0_{0}, y0_{0}, x1_{0}, y1_{0};
|
||||
|
||||
@@ -6,7 +6,9 @@ namespace scenes {
|
||||
: surface_(JD8_LoadSurface(file)) {}
|
||||
|
||||
SurfaceHandle::~SurfaceHandle() {
|
||||
if (surface_) JD8_FreeSurface(surface_);
|
||||
if (surface_ != nullptr) {
|
||||
JD8_FreeSurface(surface_);
|
||||
}
|
||||
}
|
||||
|
||||
SurfaceHandle::SurfaceHandle(SurfaceHandle&& other) noexcept
|
||||
@@ -14,9 +16,11 @@ namespace scenes {
|
||||
other.surface_ = nullptr;
|
||||
}
|
||||
|
||||
SurfaceHandle& SurfaceHandle::operator=(SurfaceHandle&& other) noexcept {
|
||||
auto SurfaceHandle::operator=(SurfaceHandle&& other) noexcept -> SurfaceHandle& {
|
||||
if (this != &other) {
|
||||
if (surface_) JD8_FreeSurface(surface_);
|
||||
if (surface_ != nullptr) {
|
||||
JD8_FreeSurface(surface_);
|
||||
}
|
||||
surface_ = other.surface_;
|
||||
other.surface_ = nullptr;
|
||||
}
|
||||
@@ -24,16 +28,20 @@ namespace scenes {
|
||||
}
|
||||
|
||||
void SurfaceHandle::reset(const char* file) {
|
||||
if (surface_) JD8_FreeSurface(surface_);
|
||||
surface_ = file ? JD8_LoadSurface(file) : nullptr;
|
||||
if (surface_ != nullptr) {
|
||||
JD8_FreeSurface(surface_);
|
||||
}
|
||||
surface_ = (file != nullptr) ? JD8_LoadSurface(file) : nullptr;
|
||||
}
|
||||
|
||||
void SurfaceHandle::adopt(JD8_Surface raw) {
|
||||
if (surface_) JD8_FreeSurface(surface_);
|
||||
if (surface_ != nullptr) {
|
||||
JD8_FreeSurface(surface_);
|
||||
}
|
||||
surface_ = raw;
|
||||
}
|
||||
|
||||
JD8_Surface SurfaceHandle::release() {
|
||||
auto SurfaceHandle::release() -> JD8_Surface {
|
||||
JD8_Surface r = surface_;
|
||||
surface_ = nullptr;
|
||||
return r;
|
||||
|
||||
@@ -15,10 +15,10 @@ namespace scenes {
|
||||
~SurfaceHandle();
|
||||
|
||||
SurfaceHandle(const SurfaceHandle&) = delete;
|
||||
SurfaceHandle& operator=(const SurfaceHandle&) = delete;
|
||||
auto operator=(const SurfaceHandle&) -> SurfaceHandle& = delete;
|
||||
|
||||
SurfaceHandle(SurfaceHandle&& other) noexcept;
|
||||
SurfaceHandle& operator=(SurfaceHandle&& other) noexcept;
|
||||
auto operator=(SurfaceHandle&& other) noexcept -> SurfaceHandle&;
|
||||
|
||||
// Allibera la surface actual (si n'hi ha) i carrega una nova.
|
||||
// Usat per escenes que recarreguen assets a mitja cinemàtica
|
||||
@@ -34,13 +34,13 @@ namespace scenes {
|
||||
// altre propietari). Usat quan una escena delega a codi legacy que
|
||||
// també allibera la mateixa surface — cal "soltar" el ownership per
|
||||
// evitar double free.
|
||||
[[nodiscard]] JD8_Surface release();
|
||||
[[nodiscard]] auto release() -> JD8_Surface;
|
||||
|
||||
// Conversió implícita per al confort d'ús: JD8_Blit(handle)
|
||||
// en lloc de JD8_Blit(handle.get()).
|
||||
operator JD8_Surface() const { return surface_; }
|
||||
JD8_Surface get() const { return surface_; }
|
||||
bool valid() const { return surface_ != nullptr; }
|
||||
[[nodiscard]] auto get() const -> JD8_Surface { return surface_; }
|
||||
[[nodiscard]] auto valid() const -> bool { return surface_ != nullptr; }
|
||||
|
||||
private:
|
||||
JD8_Surface surface_{nullptr};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace scenes {
|
||||
|
||||
Timeline& Timeline::step(int duration_ms, StepFn fn) {
|
||||
auto Timeline::step(int duration_ms, StepFn fn) -> Timeline& {
|
||||
Step s;
|
||||
s.duration_ms = duration_ms;
|
||||
s.continuous = std::move(fn);
|
||||
@@ -12,7 +12,7 @@ namespace scenes {
|
||||
return *this;
|
||||
}
|
||||
|
||||
Timeline& Timeline::once(OnceFn fn) {
|
||||
auto Timeline::once(OnceFn fn) -> Timeline& {
|
||||
Step s;
|
||||
s.duration_ms = 0;
|
||||
s.oneshot = std::move(fn);
|
||||
@@ -25,7 +25,9 @@ namespace scenes {
|
||||
auto& s = steps_[current_];
|
||||
if (!s.entered) {
|
||||
s.entered = true;
|
||||
if (s.oneshot) s.oneshot();
|
||||
if (s.oneshot) {
|
||||
s.oneshot();
|
||||
}
|
||||
}
|
||||
++current_;
|
||||
elapsed_in_step_ = 0;
|
||||
@@ -33,21 +35,29 @@ namespace scenes {
|
||||
}
|
||||
|
||||
void Timeline::tick(int delta_ms) {
|
||||
if (skipped_) return;
|
||||
if (skipped_) {
|
||||
return;
|
||||
}
|
||||
flushOneShots();
|
||||
if (current_ >= steps_.size()) return;
|
||||
if (current_ >= steps_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& s = steps_[current_];
|
||||
if (!s.entered) {
|
||||
s.entered = true;
|
||||
// Primer tick dins del pas: cridem amb progress=0 si hi ha callback.
|
||||
if (s.continuous) s.continuous(0.0f);
|
||||
if (s.continuous) {
|
||||
s.continuous(0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
elapsed_in_step_ += delta_ms;
|
||||
if (elapsed_in_step_ >= s.duration_ms) {
|
||||
// Tancament del pas: una crida final amb progress=1.
|
||||
if (s.continuous) s.continuous(1.0f);
|
||||
if (s.continuous) {
|
||||
s.continuous(1.0F);
|
||||
}
|
||||
++current_;
|
||||
elapsed_in_step_ = 0;
|
||||
// Pot ser que el següent pas siga una cadena de one-shots.
|
||||
@@ -65,20 +75,26 @@ namespace scenes {
|
||||
}
|
||||
|
||||
void Timeline::reset() {
|
||||
for (auto& s : steps_) s.entered = false;
|
||||
for (auto& s : steps_) {
|
||||
s.entered = false;
|
||||
}
|
||||
current_ = 0;
|
||||
elapsed_in_step_ = 0;
|
||||
skipped_ = false;
|
||||
}
|
||||
|
||||
bool Timeline::done() const {
|
||||
auto Timeline::done() const -> bool {
|
||||
return skipped_ || current_ >= steps_.size();
|
||||
}
|
||||
|
||||
float Timeline::currentProgress() const {
|
||||
if (current_ >= steps_.size()) return 1.0f;
|
||||
auto Timeline::currentProgress() const -> float {
|
||||
if (current_ >= steps_.size()) {
|
||||
return 1.0F;
|
||||
}
|
||||
const auto& s = steps_[current_];
|
||||
if (s.duration_ms <= 0) return 0.0f;
|
||||
if (s.duration_ms <= 0) {
|
||||
return 0.0F;
|
||||
}
|
||||
return static_cast<float>(elapsed_in_step_) / static_cast<float>(s.duration_ms);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,16 +25,16 @@ namespace scenes {
|
||||
using StepFn = std::function<void(float progress_0_1)>;
|
||||
using OnceFn = std::function<void()>;
|
||||
|
||||
Timeline& step(int duration_ms, StepFn fn = nullptr);
|
||||
Timeline& once(OnceFn fn);
|
||||
auto step(int duration_ms, StepFn fn = nullptr) -> Timeline&;
|
||||
auto once(OnceFn fn) -> Timeline&;
|
||||
|
||||
void tick(int delta_ms);
|
||||
void skip();
|
||||
void reset();
|
||||
|
||||
bool done() const;
|
||||
int currentStepIndex() const { return static_cast<int>(current_); }
|
||||
float currentProgress() const;
|
||||
[[nodiscard]] auto done() const -> bool;
|
||||
[[nodiscard]] auto currentStepIndex() const -> int { return static_cast<int>(current_); }
|
||||
[[nodiscard]] auto currentProgress() const -> float;
|
||||
|
||||
private:
|
||||
struct Step {
|
||||
|
||||
+7
-5
@@ -26,7 +26,7 @@
|
||||
#include "core/system/director.hpp"
|
||||
#include "game/options.hpp"
|
||||
|
||||
SDL_AppResult SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) {
|
||||
auto SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) -> SDL_AppResult {
|
||||
srand(unsigned(time(nullptr)));
|
||||
|
||||
// Crea la carpeta de configuració i carrega les opcions
|
||||
@@ -37,7 +37,7 @@ SDL_AppResult SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) {
|
||||
// (retorna Contents/Resources/) o en un executable normal (carpeta del binari).
|
||||
const char* base_path = SDL_GetBasePath();
|
||||
std::string resource_pack_path;
|
||||
if (base_path) {
|
||||
if (base_path != nullptr) {
|
||||
const std::string data_path = std::string(base_path) + "data/";
|
||||
file_setresourcefolder(data_path.c_str());
|
||||
resource_pack_path = std::string(base_path) + "resources.pack";
|
||||
@@ -109,7 +109,7 @@ SDL_AppResult SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) {
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppIterate(void* /*appstate*/) {
|
||||
auto SDL_AppIterate(void* /*appstate*/) -> SDL_AppResult {
|
||||
// Una iteració del bucle del Director. Abans els events es drenaven
|
||||
// amb SDL_PollEvent dins d'aquesta funció; ara SDL ens els lliura
|
||||
// d'un en un via SDL_AppEvent, així que iterate() no els toca.
|
||||
@@ -119,8 +119,10 @@ SDL_AppResult SDL_AppIterate(void* /*appstate*/) {
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppEvent(void* /*appstate*/, SDL_Event* event) {
|
||||
if (!event) return SDL_APP_CONTINUE;
|
||||
auto SDL_AppEvent(void* /*appstate*/, SDL_Event* event) -> SDL_AppResult {
|
||||
if (event == nullptr) {
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
Director::get()->handleEvent(*event);
|
||||
if (Director::get()->isQuitRequested()) {
|
||||
return SDL_APP_SUCCESS;
|
||||
|
||||
@@ -4,19 +4,19 @@ namespace Easing {
|
||||
|
||||
auto linear(float t) -> float { return t; }
|
||||
|
||||
auto outQuad(float t) -> float { return 1.0F - (1.0F - t) * (1.0F - t); }
|
||||
auto outQuad(float t) -> float { return 1.0F - ((1.0F - t) * (1.0F - t)); }
|
||||
auto inQuad(float t) -> float { return t * t; }
|
||||
auto inOutQuad(float t) -> float {
|
||||
return t < 0.5F ? 2.0F * t * t : 1.0F - (-2.0F * t + 2.0F) * (-2.0F * t + 2.0F) / 2.0F;
|
||||
return t < 0.5F ? 2.0F * t * t : 1.0F - ((-2.0F * t + 2.0F) * (-2.0F * t + 2.0F) / 2.0F);
|
||||
}
|
||||
|
||||
auto outCubic(float t) -> float {
|
||||
const float inv = 1.0F - t;
|
||||
return 1.0F - inv * inv * inv;
|
||||
return 1.0F - (inv * inv * inv);
|
||||
}
|
||||
auto inCubic(float t) -> float { return t * t * t; }
|
||||
|
||||
auto lerp(float a, float b, float t) -> float { return a + (b - a) * t; }
|
||||
auto lerp(float a, float b, float t) -> float { return a + ((b - a) * t); }
|
||||
auto lerpInt(int a, int b, float t) -> int {
|
||||
return a + static_cast<int>((b - a) * t);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user