reestructuració
This commit is contained in:
549
source/core/audio/jail_audio.hpp
Normal file
549
source/core/audio/jail_audio.hpp
Normal file
@@ -0,0 +1,549 @@
|
||||
#pragma once
|
||||
|
||||
// --- Includes ---
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdint.h> // Para uint32_t, uint8_t
|
||||
#include <stdio.h> // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
|
||||
#include <stdlib.h> // Para free, malloc
|
||||
#include <string.h> // Para strcpy, strlen
|
||||
|
||||
#define STB_VORBIS_HEADER_ONLY
|
||||
#include "external/stb_vorbis.c"
|
||||
|
||||
// --- Public Enums ---
|
||||
enum JA_Channel_state { JA_CHANNEL_INVALID,
|
||||
JA_CHANNEL_FREE,
|
||||
JA_CHANNEL_PLAYING,
|
||||
JA_CHANNEL_PAUSED,
|
||||
JA_SOUND_DISABLED };
|
||||
enum JA_Music_state { JA_MUSIC_INVALID,
|
||||
JA_MUSIC_PLAYING,
|
||||
JA_MUSIC_PAUSED,
|
||||
JA_MUSIC_STOPPED,
|
||||
JA_MUSIC_DISABLED };
|
||||
|
||||
// --- Struct Definitions ---
|
||||
#define JA_MAX_SIMULTANEOUS_CHANNELS 20
|
||||
#define JA_MAX_GROUPS 2
|
||||
|
||||
struct JA_Sound_t {
|
||||
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
||||
Uint32 length{0};
|
||||
Uint8* buffer{NULL};
|
||||
};
|
||||
|
||||
struct JA_Channel_t {
|
||||
JA_Sound_t* sound{nullptr};
|
||||
int pos{0};
|
||||
int times{0};
|
||||
int group{0};
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
JA_Channel_state state{JA_CHANNEL_FREE};
|
||||
};
|
||||
|
||||
struct JA_Music_t {
|
||||
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
||||
|
||||
// OGG comprimit en memòria. Propietat nostra; es copia des del fitxer una
|
||||
// sola vegada en JA_LoadMusic i es descomprimix en chunks per streaming.
|
||||
Uint8* ogg_data{nullptr};
|
||||
Uint32 ogg_length{0};
|
||||
stb_vorbis* vorbis{nullptr}; // Handle del decoder, viu tot el cicle del JA_Music_t
|
||||
|
||||
char* filename{nullptr};
|
||||
|
||||
int times{0}; // Loops restants (-1 = infinit, 0 = un sol play)
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
JA_Music_state state{JA_MUSIC_INVALID};
|
||||
};
|
||||
|
||||
// --- Internal Global State ---
|
||||
// Marcado 'inline' (C++17) para asegurar una única instancia.
|
||||
|
||||
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_soundVolume[JA_MAX_GROUPS];
|
||||
inline bool JA_musicEnabled{true};
|
||||
inline bool JA_soundEnabled{true};
|
||||
inline SDL_AudioDeviceID sdlAudioDevice{0};
|
||||
|
||||
inline bool fading{false};
|
||||
inline int fade_start_time{0};
|
||||
inline int fade_duration{0};
|
||||
inline float fade_initial_volume{0.0f};
|
||||
|
||||
// --- 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);
|
||||
|
||||
// --- Music streaming internals ---
|
||||
// Bytes-per-sample per canal (sempre s16)
|
||||
static constexpr int JA_MUSIC_BYTES_PER_SAMPLE = 2;
|
||||
// Quants shorts decodifiquem per crida a get_samples_short_interleaved.
|
||||
// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz.
|
||||
static constexpr int JA_MUSIC_CHUNK_SHORTS = 8192;
|
||||
// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a
|
||||
// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns.
|
||||
static constexpr float JA_MUSIC_LOW_WATER_SECONDS = 0.5f;
|
||||
|
||||
// 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;
|
||||
|
||||
short chunk[JA_MUSIC_CHUNK_SHORTS];
|
||||
const int channels = music->spec.channels;
|
||||
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
|
||||
music->vorbis,
|
||||
channels,
|
||||
chunk,
|
||||
JA_MUSIC_CHUNK_SHORTS);
|
||||
if (samples_per_channel <= 0) return 0;
|
||||
|
||||
const int bytes = samples_per_channel * channels * JA_MUSIC_BYTES_PER_SAMPLE;
|
||||
SDL_PutAudioStreamData(music->stream, chunk, bytes);
|
||||
return samples_per_channel;
|
||||
}
|
||||
|
||||
// Reompli l'stream fins que tinga ≥ JA_MUSIC_LOW_WATER_SECONDS bufferats.
|
||||
// En arribar a EOF del vorbis, aplica el loop (times) o deixa drenar.
|
||||
inline void JA_PumpMusic(JA_Music_t* music) {
|
||||
if (!music || !music->vorbis || !music->stream) return;
|
||||
|
||||
const int bytes_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;
|
||||
|
||||
// EOF: si queden loops, rebobinar; si no, tallar i deixar drenar.
|
||||
if (music->times != 0) {
|
||||
stb_vorbis_seek_start(music->vorbis);
|
||||
if (music->times > 0) music->times--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Core Functions ---
|
||||
|
||||
inline void JA_Update() {
|
||||
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING) {
|
||||
if (fading) {
|
||||
int time = SDL_GetTicks();
|
||||
if (time > (fade_start_time + fade_duration)) {
|
||||
fading = false;
|
||||
JA_StopMusic();
|
||||
return;
|
||||
} else {
|
||||
const int time_passed = time - fade_start_time;
|
||||
const float percent = (float)time_passed / (float)fade_duration;
|
||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume * (1.0 - percent));
|
||||
}
|
||||
}
|
||||
|
||||
// Streaming: rellenem l'stream fins al low-water-mark i parem si el
|
||||
// vorbis s'ha esgotat i no queden loops.
|
||||
JA_PumpMusic(current_music);
|
||||
if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) {
|
||||
JA_StopMusic();
|
||||
}
|
||||
}
|
||||
|
||||
if (JA_soundEnabled) {
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
|
||||
if (channels[i].state == JA_CHANNEL_PLAYING) {
|
||||
if (channels[i].times != 0) {
|
||||
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length / 2)) {
|
||||
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
|
||||
if (channels[i].times > 0) channels[i].times--;
|
||||
}
|
||||
} else {
|
||||
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels) {
|
||||
#ifdef DEBUG
|
||||
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
|
||||
#endif
|
||||
|
||||
JA_audioSpec = {format, num_channels, freq};
|
||||
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
|
||||
if (sdlAudioDevice == 0) SDL_Log("Failed to initialize SDL audio!");
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE;
|
||||
for (int i = 0; i < JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f;
|
||||
}
|
||||
|
||||
inline void JA_Quit() {
|
||||
if (sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||
sdlAudioDevice = 0;
|
||||
}
|
||||
|
||||
// --- Music Functions ---
|
||||
|
||||
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
|
||||
if (!buffer || length == 0) return nullptr;
|
||||
|
||||
// Còpia del OGG comprimit: stb_vorbis llig de forma persistent aquesta
|
||||
// memòria mentre el handle estiga viu, així que hem de posseir-la nosaltres.
|
||||
Uint8* ogg_copy = static_cast<Uint8*>(SDL_malloc(length));
|
||||
if (!ogg_copy) return nullptr;
|
||||
SDL_memcpy(ogg_copy, buffer, length);
|
||||
|
||||
int error = 0;
|
||||
stb_vorbis* vorbis = stb_vorbis_open_memory(ogg_copy, static_cast<int>(length), &error, nullptr);
|
||||
if (!vorbis) {
|
||||
SDL_free(ogg_copy);
|
||||
SDL_Log("JA_LoadMusic: stb_vorbis_open_memory failed (error %d)", error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* music = new JA_Music_t();
|
||||
music->ogg_data = ogg_copy;
|
||||
music->ogg_length = length;
|
||||
music->vorbis = vorbis;
|
||||
|
||||
const stb_vorbis_info info = stb_vorbis_get_info(vorbis);
|
||||
music->spec.channels = info.channels;
|
||||
music->spec.freq = static_cast<int>(info.sample_rate);
|
||||
music->spec.format = SDL_AUDIO_S16;
|
||||
music->state = JA_MUSIC_STOPPED;
|
||||
|
||||
return music;
|
||||
}
|
||||
|
||||
inline JA_Music_t* JA_LoadMusic(const char* filename) {
|
||||
FILE* f = fopen(filename, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fsize = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
auto* buffer = static_cast<Uint8*>(malloc(fsize + 1));
|
||||
if (!buffer) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
if (fread(buffer, fsize, 1, f) != 1) {
|
||||
fclose(f);
|
||||
free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
JA_Music_t* music = JA_LoadMusic(buffer, fsize);
|
||||
if (music) {
|
||||
music->filename = static_cast<char*>(malloc(strlen(filename) + 1));
|
||||
if (music->filename) {
|
||||
strcpy(music->filename, filename);
|
||||
}
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
|
||||
return music;
|
||||
}
|
||||
|
||||
inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
|
||||
if (!JA_musicEnabled || !music || !music->vorbis) return;
|
||||
|
||||
JA_StopMusic();
|
||||
|
||||
current_music = music;
|
||||
current_music->state = JA_MUSIC_PLAYING;
|
||||
current_music->times = loop;
|
||||
|
||||
// Rebobinem l'stream de vorbis al principi. Cobreix tant play-per-primera-
|
||||
// vegada com replays/canvis de track que tornen a la mateixa pista.
|
||||
stb_vorbis_seek_start(current_music->vorbis);
|
||||
|
||||
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||
if (!current_music->stream) {
|
||||
SDL_Log("Failed to create audio stream!");
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
return;
|
||||
}
|
||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||
|
||||
// Pre-cargem el buffer abans de bindejar per evitar un underrun inicial.
|
||||
JA_PumpMusic(current_music);
|
||||
|
||||
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n");
|
||||
}
|
||||
|
||||
inline char* JA_GetMusicFilename(const JA_Music_t* music = nullptr) {
|
||||
if (!music) music = current_music;
|
||||
if (!music) return nullptr;
|
||||
return music->filename;
|
||||
}
|
||||
|
||||
inline void JA_PauseMusic() {
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || 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;
|
||||
|
||||
current_music->state = JA_MUSIC_PLAYING;
|
||||
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
|
||||
}
|
||||
|
||||
inline void JA_StopMusic() {
|
||||
if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
|
||||
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
if (current_music->stream) {
|
||||
SDL_DestroyAudioStream(current_music->stream);
|
||||
current_music->stream = nullptr;
|
||||
}
|
||||
// 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) {
|
||||
stb_vorbis_seek_start(current_music->vorbis);
|
||||
}
|
||||
}
|
||||
|
||||
inline void JA_FadeOutMusic(const int milliseconds) {
|
||||
if (!JA_musicEnabled) return;
|
||||
if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
fading = true;
|
||||
fade_start_time = SDL_GetTicks();
|
||||
fade_duration = milliseconds;
|
||||
fade_initial_volume = JA_musicVolume;
|
||||
}
|
||||
|
||||
inline JA_Music_state JA_GetMusicState() {
|
||||
if (!JA_musicEnabled) return JA_MUSIC_DISABLED;
|
||||
if (!current_music) return JA_MUSIC_INVALID;
|
||||
|
||||
return current_music->state;
|
||||
}
|
||||
|
||||
inline void JA_DeleteMusic(JA_Music_t* music) {
|
||||
if (!music) return;
|
||||
if (current_music == music) {
|
||||
JA_StopMusic();
|
||||
current_music = nullptr;
|
||||
}
|
||||
if (music->stream) SDL_DestroyAudioStream(music->stream);
|
||||
if (music->vorbis) stb_vorbis_close(music->vorbis);
|
||||
SDL_free(music->ogg_data);
|
||||
free(music->filename);
|
||||
delete music;
|
||||
}
|
||||
|
||||
inline float JA_SetMusicVolume(float volume) {
|
||||
JA_musicVolume = SDL_clamp(volume, 0.0f, 1.0f);
|
||||
if (current_music && current_music->stream) {
|
||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||
}
|
||||
return JA_musicVolume;
|
||||
}
|
||||
|
||||
inline void JA_SetMusicPosition(float /*value*/) {
|
||||
// No implementat amb el backend de streaming. Mai va arribar a usar-se
|
||||
// en el codi existent, així que es manté com a stub.
|
||||
}
|
||||
|
||||
inline float JA_GetMusicPosition() {
|
||||
// Veure nota a JA_SetMusicPosition.
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline void JA_EnableMusic(const bool value) {
|
||||
if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING)) JA_StopMusic();
|
||||
|
||||
JA_musicEnabled = value;
|
||||
}
|
||||
|
||||
// --- Sound Functions ---
|
||||
|
||||
inline JA_Sound_t* JA_NewSound(Uint8* buffer, Uint32 length) {
|
||||
JA_Sound_t* sound = new JA_Sound_t();
|
||||
sound->buffer = buffer;
|
||||
sound->length = length;
|
||||
return sound;
|
||||
}
|
||||
|
||||
inline JA_Sound_t* JA_LoadSound(uint8_t* buffer, uint32_t size) {
|
||||
JA_Sound_t* sound = new JA_Sound_t();
|
||||
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length)) {
|
||||
SDL_Log("Failed to load WAV from memory: %s", SDL_GetError());
|
||||
delete sound;
|
||||
return nullptr;
|
||||
}
|
||||
return sound;
|
||||
}
|
||||
|
||||
inline JA_Sound_t* JA_LoadSound(const char* filename) {
|
||||
JA_Sound_t* sound = new JA_Sound_t();
|
||||
if (!SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length)) {
|
||||
SDL_Log("Failed to load WAV file: %s", SDL_GetError());
|
||||
delete sound;
|
||||
return nullptr;
|
||||
}
|
||||
return sound;
|
||||
}
|
||||
|
||||
inline int JA_PlaySound(JA_Sound_t* sound, const int loop = 0, const int group = 0) {
|
||||
if (!JA_soundEnabled || !sound) return -1;
|
||||
|
||||
int channel = 0;
|
||||
while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; }
|
||||
if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||
channel = 0;
|
||||
}
|
||||
|
||||
return JA_PlaySoundOnChannel(sound, channel, loop, group);
|
||||
}
|
||||
|
||||
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop, const int group) {
|
||||
if (!JA_soundEnabled || !sound) return -1;
|
||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1;
|
||||
|
||||
JA_StopChannel(channel);
|
||||
|
||||
channels[channel].sound = sound;
|
||||
channels[channel].times = loop;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].group = group;
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
|
||||
|
||||
if (!channels[channel].stream) {
|
||||
SDL_Log("Failed to create audio stream for sound!");
|
||||
channels[channel].state = JA_CHANNEL_FREE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
||||
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
inline void JA_DeleteSound(JA_Sound_t* sound) {
|
||||
if (!sound) return;
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||
if (channels[i].sound == sound) JA_StopChannel(i);
|
||||
}
|
||||
SDL_free(sound->buffer);
|
||||
delete sound;
|
||||
}
|
||||
|
||||
inline void JA_PauseChannel(const int channel) {
|
||||
if (!JA_soundEnabled) return;
|
||||
|
||||
if (channel == -1) {
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
if (channels[i].state == JA_CHANNEL_PLAYING) {
|
||||
channels[i].state = JA_CHANNEL_PAUSED;
|
||||
SDL_UnbindAudioStream(channels[i].stream);
|
||||
}
|
||||
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||
if (channels[channel].state == JA_CHANNEL_PLAYING) {
|
||||
channels[channel].state = JA_CHANNEL_PAUSED;
|
||||
SDL_UnbindAudioStream(channels[channel].stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void JA_ResumeChannel(const int channel) {
|
||||
if (!JA_soundEnabled) return;
|
||||
|
||||
if (channel == -1) {
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
if (channels[i].state == JA_CHANNEL_PAUSED) {
|
||||
channels[i].state = JA_CHANNEL_PLAYING;
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
|
||||
}
|
||||
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||
if (channels[channel].state == JA_CHANNEL_PAUSED) {
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = NULL;
|
||||
}
|
||||
}
|
||||
} else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS) {
|
||||
if (channels[channel].state != JA_CHANNEL_FREE) {
|
||||
if (channels[channel].stream) SDL_DestroyAudioStream(channels[channel].stream);
|
||||
channels[channel].stream = nullptr;
|
||||
channels[channel].state = JA_CHANNEL_FREE;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].sound = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline JA_Channel_state JA_GetChannelState(const int channel) {
|
||||
if (!JA_soundEnabled) return JA_SOUND_DISABLED;
|
||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID;
|
||||
|
||||
return channels[channel].state;
|
||||
}
|
||||
|
||||
inline float JA_SetSoundVolume(float volume, const int group = -1) {
|
||||
const float v = SDL_clamp(volume, 0.0f, 1.0f);
|
||||
|
||||
if (group == -1) {
|
||||
for (int i = 0; i < JA_MAX_GROUPS; ++i) {
|
||||
JA_soundVolume[i] = v;
|
||||
}
|
||||
} else if (group >= 0 && group < JA_MAX_GROUPS) {
|
||||
JA_soundVolume[group] = v;
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
inline void JA_EnableSound(const bool value) {
|
||||
if (!value) {
|
||||
JA_StopChannel(-1);
|
||||
}
|
||||
JA_soundEnabled = value;
|
||||
}
|
||||
|
||||
inline float JA_SetVolume(float volume) {
|
||||
float v = JA_SetMusicVolume(volume);
|
||||
JA_SetSoundVolume(v, -1);
|
||||
return v;
|
||||
}
|
||||
389
source/core/input/input.cpp
Normal file
389
source/core/input/input.cpp
Normal file
@@ -0,0 +1,389 @@
|
||||
#include "core/input/input.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <iostream> // for basic_ostream, operator<<, cout, basi...
|
||||
|
||||
// Emscripten-only: SDL 3.4+ ja no casa el GUID dels mandos de Chrome Android
|
||||
// amb gamecontrollerdb (el gamepad.id d'Android no porta Vendor/Product, el
|
||||
// parser extreu valors escombraries, el GUID resultant no està a la db i el
|
||||
// gamepad queda obert amb un mapping incorrecte). Com el W3C Gamepad API
|
||||
// garanteix el layout estàndard quan el navegador reporta mapping=="standard",
|
||||
// injectem un mapping SDL amb eixe layout per al GUID del joystick abans
|
||||
// d'obrir-lo com gamepad. Fora d'Emscripten és un no-op.
|
||||
static void installWebStandardMapping(SDL_JoystickID jid) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
SDL_GUID guid = SDL_GetJoystickGUIDForID(jid);
|
||||
char guidStr[33];
|
||||
SDL_GUIDToString(guid, guidStr, sizeof(guidStr));
|
||||
const char *name = SDL_GetJoystickNameForID(jid);
|
||||
if (!name || !*name) name = "Standard Gamepad";
|
||||
|
||||
char mapping[512];
|
||||
SDL_snprintf(mapping, sizeof(mapping),
|
||||
"%s,%s,"
|
||||
"a:b0,b:b1,x:b2,y:b3,"
|
||||
"leftshoulder:b4,rightshoulder:b5,"
|
||||
"lefttrigger:b6,righttrigger:b7,"
|
||||
"back:b8,start:b9,"
|
||||
"leftstick:b10,rightstick:b11,"
|
||||
"dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,"
|
||||
"guide:b16,"
|
||||
"leftx:a0,lefty:a1,rightx:a2,righty:a3,"
|
||||
"platform:Emscripten",
|
||||
guidStr,
|
||||
name);
|
||||
SDL_AddGamepadMapping(mapping);
|
||||
#else
|
||||
(void)jid;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Input::Input(std::string file) {
|
||||
// Fichero gamecontrollerdb.txt
|
||||
dbPath = file;
|
||||
|
||||
// Inicializa las variables
|
||||
keyBindings_t kb;
|
||||
kb.scancode = 0;
|
||||
kb.active = false;
|
||||
keyBindings.resize(input_number_of_inputs, kb);
|
||||
|
||||
GameControllerBindings_t gcb;
|
||||
gcb.button = SDL_GAMEPAD_BUTTON_INVALID;
|
||||
gcb.active = false;
|
||||
gameControllerBindings.resize(input_number_of_inputs, gcb);
|
||||
|
||||
numGamepads = 0;
|
||||
verbose = true;
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Input::~Input() {
|
||||
for (auto *pad : connectedControllers) {
|
||||
if (pad != nullptr) {
|
||||
SDL_CloseGamepad(pad);
|
||||
}
|
||||
}
|
||||
connectedControllers.clear();
|
||||
connectedControllerIds.clear();
|
||||
controllerNames.clear();
|
||||
numGamepads = 0;
|
||||
}
|
||||
|
||||
// Actualiza el estado del objeto
|
||||
void Input::update() {
|
||||
if (disabledUntil == d_keyPressed && !checkAnyInput()) {
|
||||
enable();
|
||||
}
|
||||
}
|
||||
|
||||
// Asigna inputs a teclas
|
||||
void Input::bindKey(Uint8 input, SDL_Scancode code) {
|
||||
keyBindings[input].scancode = code;
|
||||
}
|
||||
|
||||
// Asigna inputs a botones del mando
|
||||
void Input::bindGameControllerButton(Uint8 input, SDL_GamepadButton button) {
|
||||
gameControllerBindings[input].button = button;
|
||||
}
|
||||
|
||||
// Comprueba si un input esta activo
|
||||
bool Input::checkInput(Uint8 input, bool repeat, int device, int index) {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool successKeyboard = false;
|
||||
bool successGameController = false;
|
||||
|
||||
if (device == INPUT_USE_ANY) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (device == INPUT_USE_KEYBOARD || device == INPUT_USE_ANY) {
|
||||
const bool *keyStates = SDL_GetKeyboardState(nullptr);
|
||||
|
||||
if (repeat) {
|
||||
if (keyStates[keyBindings[input].scancode]) {
|
||||
successKeyboard = true;
|
||||
} else {
|
||||
successKeyboard = false;
|
||||
}
|
||||
} else {
|
||||
if (!keyBindings[input].active) {
|
||||
if (keyStates[keyBindings[input].scancode]) {
|
||||
keyBindings[input].active = true;
|
||||
successKeyboard = true;
|
||||
} else {
|
||||
successKeyboard = false;
|
||||
}
|
||||
} else {
|
||||
if (!keyStates[keyBindings[input].scancode]) {
|
||||
keyBindings[input].active = false;
|
||||
successKeyboard = false;
|
||||
} else {
|
||||
successKeyboard = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameControllerFound() && index >= 0 && index < (int)connectedControllers.size())
|
||||
if ((device == INPUT_USE_GAMECONTROLLER) || (device == INPUT_USE_ANY)) {
|
||||
if (repeat) {
|
||||
if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[input].button)) {
|
||||
successGameController = true;
|
||||
} else {
|
||||
successGameController = false;
|
||||
}
|
||||
} else {
|
||||
if (!gameControllerBindings[input].active) {
|
||||
if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[input].button)) {
|
||||
gameControllerBindings[input].active = true;
|
||||
successGameController = true;
|
||||
} else {
|
||||
successGameController = false;
|
||||
}
|
||||
} else {
|
||||
if (!SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[input].button)) {
|
||||
gameControllerBindings[input].active = false;
|
||||
successGameController = false;
|
||||
} else {
|
||||
successGameController = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (successKeyboard || successGameController);
|
||||
}
|
||||
|
||||
// Comprueba si hay almenos un input activo
|
||||
bool Input::checkAnyInput(int device, int index) {
|
||||
if (device == INPUT_USE_ANY) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (device == INPUT_USE_KEYBOARD || device == INPUT_USE_ANY) {
|
||||
const bool *mKeystates = SDL_GetKeyboardState(nullptr);
|
||||
|
||||
for (int i = 0; i < (int)keyBindings.size(); ++i) {
|
||||
if (mKeystates[keyBindings[i].scancode]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameControllerFound() && index >= 0 && index < (int)connectedControllers.size()) {
|
||||
if (device == INPUT_USE_GAMECONTROLLER || device == INPUT_USE_ANY) {
|
||||
for (int i = 0; i < (int)gameControllerBindings.size(); ++i) {
|
||||
if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[i].button)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Construye el nombre visible de un mando.
|
||||
// Recorta des del primer '(' o '[' (per a evitar coses tipus
|
||||
// "Retroid Controller (vendor: 1001) ...") i talla a 25 caràcters.
|
||||
std::string Input::buildControllerName(SDL_Gamepad *pad, int padIndex) {
|
||||
(void)padIndex;
|
||||
const char *padName = SDL_GetGamepadName(pad);
|
||||
std::string name = padName ? padName : "Unknown";
|
||||
const auto pos = name.find_first_of("([");
|
||||
if (pos != std::string::npos) {
|
||||
name.erase(pos);
|
||||
}
|
||||
while (!name.empty() && name.back() == ' ') {
|
||||
name.pop_back();
|
||||
}
|
||||
if (name.size() > 25) {
|
||||
name.resize(25);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// Busca si hay un mando conectado. Cierra y limpia el estado previo para
|
||||
// que la función sea idempotente si se invoca más de una vez.
|
||||
bool Input::discoverGameController() {
|
||||
// Cierra los mandos ya abiertos y limpia los vectores paralelos
|
||||
for (auto *pad : connectedControllers) {
|
||||
if (pad != nullptr) {
|
||||
SDL_CloseGamepad(pad);
|
||||
}
|
||||
}
|
||||
connectedControllers.clear();
|
||||
connectedControllerIds.clear();
|
||||
controllerNames.clear();
|
||||
numGamepads = 0;
|
||||
|
||||
bool found = false;
|
||||
|
||||
if (SDL_WasInit(SDL_INIT_GAMEPAD) != SDL_INIT_GAMEPAD) {
|
||||
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
||||
}
|
||||
|
||||
if (SDL_AddGamepadMappingsFromFile(dbPath.c_str()) < 0) {
|
||||
if (verbose) {
|
||||
std::cout << "Error, could not load " << dbPath.c_str() << " file: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int nJoysticks = 0;
|
||||
SDL_JoystickID *joysticks = SDL_GetJoysticks(&nJoysticks);
|
||||
|
||||
if (joysticks) {
|
||||
int gamepadCount = 0;
|
||||
for (int i = 0; i < nJoysticks; ++i) {
|
||||
if (SDL_IsGamepad(joysticks[i])) {
|
||||
gamepadCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "\nChecking for game controllers...\n";
|
||||
std::cout << nJoysticks << " joysticks found, " << gamepadCount << " are gamepads\n";
|
||||
}
|
||||
|
||||
if (gamepadCount > 0) {
|
||||
found = true;
|
||||
int padIndex = 0;
|
||||
|
||||
for (int i = 0; i < nJoysticks; i++) {
|
||||
if (!SDL_IsGamepad(joysticks[i])) continue;
|
||||
|
||||
installWebStandardMapping(joysticks[i]);
|
||||
SDL_Gamepad *pad = SDL_OpenGamepad(joysticks[i]);
|
||||
if (pad != nullptr) {
|
||||
const std::string name = buildControllerName(pad, padIndex);
|
||||
connectedControllers.push_back(pad);
|
||||
connectedControllerIds.push_back(joysticks[i]);
|
||||
controllerNames.push_back(name);
|
||||
numGamepads++;
|
||||
padIndex++;
|
||||
if (verbose) {
|
||||
std::cout << name << std::endl;
|
||||
}
|
||||
} else {
|
||||
if (verbose) {
|
||||
std::cout << "SDL_GetError() = " << SDL_GetError() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetGamepadEventsEnabled(true);
|
||||
}
|
||||
|
||||
SDL_free(joysticks);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
// Procesa un evento SDL_EVENT_GAMEPAD_ADDED
|
||||
bool Input::handleGamepadAdded(SDL_JoystickID jid, std::string &outName) {
|
||||
if (!SDL_IsGamepad(jid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si el mando ya está registrado no hace nada (ej. evento retroactivo tras el scan inicial)
|
||||
for (SDL_JoystickID existing : connectedControllerIds) {
|
||||
if (existing == jid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
installWebStandardMapping(jid);
|
||||
SDL_Gamepad *pad = SDL_OpenGamepad(jid);
|
||||
if (pad == nullptr) {
|
||||
if (verbose) {
|
||||
std::cout << "Failed to open gamepad " << jid << ": " << SDL_GetError() << std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const int padIndex = (int)connectedControllers.size();
|
||||
const std::string name = buildControllerName(pad, padIndex);
|
||||
connectedControllers.push_back(pad);
|
||||
connectedControllerIds.push_back(jid);
|
||||
controllerNames.push_back(name);
|
||||
numGamepads++;
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Gamepad connected: " << name << std::endl;
|
||||
}
|
||||
|
||||
outName = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Procesa un evento SDL_EVENT_GAMEPAD_REMOVED
|
||||
bool Input::handleGamepadRemoved(SDL_JoystickID jid, std::string &outName) {
|
||||
for (size_t i = 0; i < connectedControllerIds.size(); ++i) {
|
||||
if (connectedControllerIds[i] != jid) continue;
|
||||
|
||||
outName = controllerNames[i];
|
||||
if (connectedControllers[i] != nullptr) {
|
||||
SDL_CloseGamepad(connectedControllers[i]);
|
||||
}
|
||||
connectedControllers.erase(connectedControllers.begin() + i);
|
||||
connectedControllerIds.erase(connectedControllerIds.begin() + i);
|
||||
controllerNames.erase(controllerNames.begin() + i);
|
||||
numGamepads--;
|
||||
if (numGamepads < 0) numGamepads = 0;
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Gamepad disconnected: " << outName << std::endl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si hay algun mando conectado
|
||||
bool Input::gameControllerFound() {
|
||||
if (numGamepads > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Obten el nombre de un mando de juego
|
||||
std::string Input::getControllerName(int index) {
|
||||
if (numGamepads > 0) {
|
||||
return controllerNames[index];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Obten el numero de mandos conectados
|
||||
int Input::getNumControllers() {
|
||||
return numGamepads;
|
||||
}
|
||||
|
||||
// Establece si ha de mostrar mensajes
|
||||
void Input::setVerbose(bool value) {
|
||||
verbose = value;
|
||||
}
|
||||
|
||||
// Deshabilita las entradas durante un periodo de tiempo
|
||||
void Input::disableUntil(i_disable_e value) {
|
||||
disabledUntil = value;
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
// Hablita las entradas
|
||||
void Input::enable() {
|
||||
enabled = true;
|
||||
disabledUntil = d_notDisabled;
|
||||
}
|
||||
126
source/core/input/input.h
Normal file
126
source/core/input/input.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
// Valores de repetición
|
||||
constexpr bool REPEAT_TRUE = true;
|
||||
constexpr bool REPEAT_FALSE = false;
|
||||
|
||||
// Métodos de entrada
|
||||
constexpr int INPUT_USE_KEYBOARD = 0;
|
||||
constexpr int INPUT_USE_GAMECONTROLLER = 1;
|
||||
constexpr int INPUT_USE_ANY = 2;
|
||||
|
||||
enum inputs_e {
|
||||
// Inputs obligatorios
|
||||
input_null,
|
||||
input_up,
|
||||
input_down,
|
||||
input_left,
|
||||
input_right,
|
||||
input_pause,
|
||||
input_exit,
|
||||
input_accept,
|
||||
input_cancel,
|
||||
|
||||
// Inputs personalizados
|
||||
input_fire_left,
|
||||
input_fire_center,
|
||||
input_fire_right,
|
||||
input_window_fullscreen,
|
||||
input_window_inc_size,
|
||||
input_window_dec_size,
|
||||
|
||||
// Input obligatorio
|
||||
input_number_of_inputs
|
||||
};
|
||||
|
||||
enum i_disable_e {
|
||||
d_notDisabled,
|
||||
d_forever,
|
||||
d_keyPressed
|
||||
};
|
||||
|
||||
class Input {
|
||||
private:
|
||||
struct keyBindings_t {
|
||||
Uint8 scancode; // Scancode asociado
|
||||
bool active; // Indica si está activo
|
||||
};
|
||||
|
||||
struct GameControllerBindings_t {
|
||||
SDL_GamepadButton button; // GameControllerButton asociado
|
||||
bool active; // Indica si está activo
|
||||
};
|
||||
|
||||
// Objetos y punteros
|
||||
std::vector<SDL_Gamepad *> connectedControllers; // Vector con todos los mandos conectados
|
||||
std::vector<SDL_JoystickID> connectedControllerIds; // Instance IDs paralelos para mapear eventos
|
||||
|
||||
// Variables
|
||||
std::vector<keyBindings_t> keyBindings; // Vector con las teclas asociadas a los inputs predefinidos
|
||||
std::vector<GameControllerBindings_t> gameControllerBindings; // Vector con las teclas asociadas a los inputs predefinidos
|
||||
std::vector<std::string> controllerNames; // Vector con los nombres de los mandos
|
||||
int numGamepads; // Numero de mandos conectados
|
||||
std::string dbPath; // Ruta al archivo gamecontrollerdb.txt
|
||||
bool verbose; // Indica si ha de mostrar mensajes
|
||||
i_disable_e disabledUntil; // Tiempo que esta deshabilitado
|
||||
bool enabled; // Indica si está habilitado
|
||||
|
||||
// Construye el nombre visible de un mando (name truncado + sufijo #N)
|
||||
std::string buildControllerName(SDL_Gamepad *pad, int padIndex);
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Input(std::string file);
|
||||
|
||||
// Destructor
|
||||
~Input();
|
||||
|
||||
// Actualiza el estado del objeto
|
||||
void update();
|
||||
|
||||
// Asigna inputs a teclas
|
||||
void bindKey(Uint8 input, SDL_Scancode code);
|
||||
|
||||
// Asigna inputs a botones del mando
|
||||
void bindGameControllerButton(Uint8 input, SDL_GamepadButton button);
|
||||
|
||||
// Comprueba si un input esta activo
|
||||
bool checkInput(Uint8 input, bool repeat = true, int device = INPUT_USE_ANY, int index = 0);
|
||||
|
||||
// Comprueba si hay almenos un input activo
|
||||
bool checkAnyInput(int device = INPUT_USE_ANY, int index = 0);
|
||||
|
||||
// Busca si hay un mando conectado
|
||||
bool discoverGameController();
|
||||
|
||||
// Procesa un evento SDL_EVENT_GAMEPAD_ADDED. Devuelve true si el mando se ha añadido
|
||||
// (no estaba ya registrado) y escribe el nombre visible en outName.
|
||||
bool handleGamepadAdded(SDL_JoystickID jid, std::string &outName);
|
||||
|
||||
// Procesa un evento SDL_EVENT_GAMEPAD_REMOVED. Devuelve true si se ha encontrado y
|
||||
// eliminado, y escribe el nombre visible en outName.
|
||||
bool handleGamepadRemoved(SDL_JoystickID jid, std::string &outName);
|
||||
|
||||
// Comprueba si hay algun mando conectado
|
||||
bool gameControllerFound();
|
||||
|
||||
// Obten el numero de mandos conectados
|
||||
int getNumControllers();
|
||||
|
||||
// Obten el nombre de un mando de juego
|
||||
std::string getControllerName(int index);
|
||||
|
||||
// Establece si ha de mostrar mensajes
|
||||
void setVerbose(bool value);
|
||||
|
||||
// Deshabilita las entradas durante un periodo de tiempo
|
||||
void disableUntil(i_disable_e value);
|
||||
|
||||
// Hablita las entradas
|
||||
void enable();
|
||||
};
|
||||
35
source/core/input/mouse.cpp
Normal file
35
source/core/input/mouse.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "core/input/mouse.hpp"
|
||||
|
||||
namespace Mouse {
|
||||
Uint32 cursorHideTime = 3000; // Tiempo en milisegundos para ocultar el cursor por inactividad
|
||||
Uint32 lastMouseMoveTime = 0; // Última vez que el ratón se movió
|
||||
bool cursorVisible = true; // Estado del cursor
|
||||
|
||||
void handleEvent(const SDL_Event &event, bool fullscreen) {
|
||||
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
||||
lastMouseMoveTime = SDL_GetTicks();
|
||||
if (!cursorVisible && !fullscreen) {
|
||||
SDL_ShowCursor();
|
||||
cursorVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateCursorVisibility(bool fullscreen) {
|
||||
// En pantalla completa el cursor siempre está oculto
|
||||
if (fullscreen) {
|
||||
if (cursorVisible) {
|
||||
SDL_HideCursor();
|
||||
cursorVisible = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// En modo ventana, lo oculta tras el periodo de inactividad
|
||||
const Uint32 currentTime = SDL_GetTicks();
|
||||
if (cursorVisible && (currentTime - lastMouseMoveTime > cursorHideTime)) {
|
||||
SDL_HideCursor();
|
||||
cursorVisible = false;
|
||||
}
|
||||
}
|
||||
} // namespace Mouse
|
||||
18
source/core/input/mouse.hpp
Normal file
18
source/core/input/mouse.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace Mouse {
|
||||
extern Uint32 cursorHideTime; // Tiempo en milisegundos para ocultar el cursor por inactividad
|
||||
extern Uint32 lastMouseMoveTime; // Última vez que el ratón se movió
|
||||
extern bool cursorVisible; // Estado del cursor
|
||||
|
||||
// Procesa un evento de ratón. En pantalla completa ignora el movimiento
|
||||
// para no volver a mostrar el cursor.
|
||||
void handleEvent(const SDL_Event &event, bool fullscreen);
|
||||
|
||||
// Actualiza la visibilidad del cursor. En modo ventana lo oculta
|
||||
// después de cursorHideTime ms sin movimiento. En pantalla completa
|
||||
// lo mantiene siempre oculto.
|
||||
void updateCursorVisibility(bool fullscreen);
|
||||
} // namespace Mouse
|
||||
69
source/core/locale/lang.cpp
Normal file
69
source/core/locale/lang.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "core/locale/lang.h"
|
||||
|
||||
#include <fstream> // for basic_ifstream, basic_istream, ifstream
|
||||
#include <sstream>
|
||||
|
||||
#include "core/resources/asset.h" // for Asset
|
||||
#include "core/resources/resource_helper.h"
|
||||
|
||||
// Constructor
|
||||
Lang::Lang(Asset *mAsset) {
|
||||
this->mAsset = mAsset;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Lang::~Lang() {
|
||||
}
|
||||
|
||||
// Inicializa los textos del juego en el idioma seleccionado
|
||||
bool Lang::setLang(Uint8 lang) {
|
||||
std::string file;
|
||||
|
||||
switch (lang) {
|
||||
case es_ES:
|
||||
file = mAsset->get("es_ES.txt");
|
||||
break;
|
||||
|
||||
case en_UK:
|
||||
file = mAsset->get("en_UK.txt");
|
||||
break;
|
||||
|
||||
case ba_BA:
|
||||
file = mAsset->get("ba_BA.txt");
|
||||
break;
|
||||
|
||||
default:
|
||||
file = mAsset->get("en_UK.txt");
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_TEXT_STRINGS; i++)
|
||||
mTextStrings[i] = "";
|
||||
|
||||
// Lee el fichero via ResourceHelper (pack o filesystem)
|
||||
auto bytes = ResourceHelper::loadFile(file);
|
||||
if (bytes.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||
std::stringstream ss(content);
|
||||
std::string line;
|
||||
int index = 0;
|
||||
while (std::getline(ss, line)) {
|
||||
// Almacena solo las lineas que no empiezan por # o no esten vacias
|
||||
const bool test1 = line.substr(0, 1) != "#";
|
||||
const bool test2 = !line.empty();
|
||||
if (test1 && test2) {
|
||||
mTextStrings[index] = line;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Obtiene la cadena de texto del indice
|
||||
std::string Lang::getText(int index) {
|
||||
return mTextStrings[index];
|
||||
}
|
||||
35
source/core/locale/lang.h
Normal file
35
source/core/locale/lang.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // for string, basic_string
|
||||
class Asset;
|
||||
|
||||
// Códigos de idioma
|
||||
constexpr int es_ES = 0;
|
||||
constexpr int ba_BA = 1;
|
||||
constexpr int en_UK = 2;
|
||||
constexpr int MAX_LANGUAGES = 3;
|
||||
|
||||
// Textos
|
||||
constexpr int MAX_TEXT_STRINGS = 100;
|
||||
|
||||
// Clase Lang
|
||||
class Lang {
|
||||
private:
|
||||
Asset *mAsset; // Objeto que gestiona todos los ficheros de recursos
|
||||
std::string mTextStrings[MAX_TEXT_STRINGS]; // Vector con los textos
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Lang(Asset *mAsset);
|
||||
|
||||
// Destructor
|
||||
~Lang();
|
||||
|
||||
// Inicializa los textos del juego en el idioma seleccionado
|
||||
bool setLang(Uint8 lang);
|
||||
|
||||
// Obtiene la cadena de texto del indice
|
||||
std::string getText(int index);
|
||||
};
|
||||
438
source/core/rendering/animatedsprite.cpp
Normal file
438
source/core/rendering/animatedsprite.cpp
Normal file
@@ -0,0 +1,438 @@
|
||||
#include "core/rendering/animatedsprite.h"
|
||||
|
||||
#include <fstream> // for basic_ostream, operator<<, basic_istream, basic...
|
||||
#include <iostream> // for cout
|
||||
#include <sstream> // for basic_stringstream
|
||||
|
||||
#include "core/rendering/texture.h" // for Texture
|
||||
|
||||
// Parser compartido: lee un istream con el formato .ani
|
||||
static animatedSprite_t parseAnimationStream(std::istream &file, Texture *texture, const std::string &filename, bool verbose) {
|
||||
animatedSprite_t as;
|
||||
as.texture = texture;
|
||||
int framesPerRow = 0;
|
||||
int frameWidth = 0;
|
||||
int frameHeight = 0;
|
||||
int maxTiles = 0;
|
||||
std::string line;
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Animation loaded: " << filename << std::endl;
|
||||
}
|
||||
while (std::getline(file, line)) {
|
||||
if (line == "[animation]") {
|
||||
animation_t buffer;
|
||||
buffer.counter = 0;
|
||||
buffer.currentFrame = 0;
|
||||
buffer.completed = false;
|
||||
|
||||
do {
|
||||
std::getline(file, line);
|
||||
int pos = line.find("=");
|
||||
if (pos != (int)line.npos) {
|
||||
if (line.substr(0, pos) == "name") {
|
||||
buffer.name = line.substr(pos + 1, line.length());
|
||||
} else if (line.substr(0, pos) == "speed") {
|
||||
buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
|
||||
} else if (line.substr(0, pos) == "loop") {
|
||||
buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
|
||||
} else if (line.substr(0, pos) == "frames") {
|
||||
std::stringstream ss(line.substr(pos + 1, line.length()));
|
||||
std::string tmp;
|
||||
SDL_Rect rect = {0, 0, frameWidth, frameHeight};
|
||||
while (getline(ss, tmp, ',')) {
|
||||
const int numTile = std::stoi(tmp) > maxTiles ? 0 : std::stoi(tmp);
|
||||
rect.x = (numTile % framesPerRow) * frameWidth;
|
||||
rect.y = (numTile / framesPerRow) * frameHeight;
|
||||
buffer.frames.push_back(rect);
|
||||
}
|
||||
} else {
|
||||
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||
}
|
||||
}
|
||||
} while (line != "[/animation]");
|
||||
|
||||
as.animations.push_back(buffer);
|
||||
} else {
|
||||
int pos = line.find("=");
|
||||
if (pos != (int)line.npos) {
|
||||
if (line.substr(0, pos) == "framesPerRow") {
|
||||
framesPerRow = std::stoi(line.substr(pos + 1, line.length()));
|
||||
} else if (line.substr(0, pos) == "frameWidth") {
|
||||
frameWidth = std::stoi(line.substr(pos + 1, line.length()));
|
||||
} else if (line.substr(0, pos) == "frameHeight") {
|
||||
frameHeight = std::stoi(line.substr(pos + 1, line.length()));
|
||||
} else {
|
||||
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||
}
|
||||
|
||||
if (framesPerRow == 0 && frameWidth > 0) {
|
||||
framesPerRow = texture->getWidth() / frameWidth;
|
||||
}
|
||||
if (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
|
||||
const int w = texture->getWidth() / frameWidth;
|
||||
const int h = texture->getHeight() / frameHeight;
|
||||
maxTiles = w * h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return as;
|
||||
}
|
||||
|
||||
// Carga la animación desde un fichero
|
||||
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose) {
|
||||
const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1);
|
||||
std::ifstream file(filePath);
|
||||
if (!file.good()) {
|
||||
if (verbose) {
|
||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||
}
|
||||
animatedSprite_t as;
|
||||
as.texture = texture;
|
||||
return as;
|
||||
}
|
||||
return parseAnimationStream(file, texture, filename, verbose);
|
||||
}
|
||||
|
||||
// Carga la animación desde bytes en memoria
|
||||
animatedSprite_t loadAnimationFromMemory(Texture *texture, const std::vector<uint8_t> &bytes, const std::string &nameForLogs, bool verbose) {
|
||||
if (bytes.empty()) {
|
||||
animatedSprite_t as;
|
||||
as.texture = texture;
|
||||
return as;
|
||||
}
|
||||
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||
std::stringstream ss(content);
|
||||
return parseAnimationStream(ss, texture, nameForLogs, verbose);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
AnimatedSprite::AnimatedSprite(Texture *texture, SDL_Renderer *renderer, std::string file, std::vector<std::string> *buffer) {
|
||||
// Copia los punteros
|
||||
setTexture(texture);
|
||||
setRenderer(renderer);
|
||||
|
||||
// Carga las animaciones
|
||||
if (file != "") {
|
||||
animatedSprite_t as = loadAnimationFromFile(texture, file);
|
||||
|
||||
// Copia los datos de las animaciones
|
||||
for (auto animation : as.animations) {
|
||||
this->animation.push_back(animation);
|
||||
}
|
||||
}
|
||||
|
||||
else if (buffer) {
|
||||
loadFromVector(buffer);
|
||||
}
|
||||
|
||||
// Inicializa variables
|
||||
currentAnimation = 0;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
AnimatedSprite::AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation) {
|
||||
// Copia los punteros
|
||||
setTexture(animation->texture);
|
||||
setRenderer(renderer);
|
||||
|
||||
// Inicializa variables
|
||||
currentAnimation = 0;
|
||||
|
||||
// Copia los datos de las animaciones
|
||||
for (auto a : animation->animations) {
|
||||
this->animation.push_back(a);
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
AnimatedSprite::~AnimatedSprite() {
|
||||
for (auto &a : animation) {
|
||||
a.frames.clear();
|
||||
}
|
||||
animation.clear();
|
||||
}
|
||||
|
||||
// Obtiene el indice de la animación a partir del nombre
|
||||
int AnimatedSprite::getIndex(std::string name) {
|
||||
int index = -1;
|
||||
|
||||
for (auto a : animation) {
|
||||
index++;
|
||||
if (a.name == name) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calcula el frame correspondiente a la animación
|
||||
void AnimatedSprite::animate() {
|
||||
if (!enabled || animation[currentAnimation].speed == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calcula el frame actual a partir del contador
|
||||
animation[currentAnimation].currentFrame = animation[currentAnimation].counter / animation[currentAnimation].speed;
|
||||
|
||||
// Si alcanza el final de la animación, reinicia el contador de la animación
|
||||
// en función de la variable loop y coloca el nuevo frame
|
||||
if (animation[currentAnimation].currentFrame >= (int)animation[currentAnimation].frames.size()) {
|
||||
if (animation[currentAnimation].loop == -1) { // Si no hay loop, deja el último frame
|
||||
animation[currentAnimation].currentFrame = animation[currentAnimation].frames.size();
|
||||
animation[currentAnimation].completed = true;
|
||||
} else { // Si hay loop, vuelve al frame indicado
|
||||
animation[currentAnimation].counter = 0;
|
||||
animation[currentAnimation].currentFrame = animation[currentAnimation].loop;
|
||||
}
|
||||
}
|
||||
// En caso contrario
|
||||
else {
|
||||
// Escoge el frame correspondiente de la animación
|
||||
setSpriteClip(animation[currentAnimation].frames[animation[currentAnimation].currentFrame]);
|
||||
|
||||
// Incrementa el contador de la animacion
|
||||
animation[currentAnimation].counter++;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el numero de frames de la animación actual
|
||||
int AnimatedSprite::getNumFrames() {
|
||||
return (int)animation[currentAnimation].frames.size();
|
||||
}
|
||||
|
||||
// Establece el frame actual de la animación
|
||||
void AnimatedSprite::setCurrentFrame(int num) {
|
||||
// Descarta valores fuera de rango
|
||||
if (num >= (int)animation[currentAnimation].frames.size()) {
|
||||
num = 0;
|
||||
}
|
||||
|
||||
// Cambia el valor de la variable
|
||||
animation[currentAnimation].currentFrame = num;
|
||||
animation[currentAnimation].counter = 0;
|
||||
|
||||
// Escoge el frame correspondiente de la animación
|
||||
setSpriteClip(animation[currentAnimation].frames[animation[currentAnimation].currentFrame]);
|
||||
}
|
||||
|
||||
// Establece el valor del contador
|
||||
void AnimatedSprite::setAnimationCounter(std::string name, int num) {
|
||||
animation[getIndex(name)].counter = num;
|
||||
}
|
||||
|
||||
// Establece la velocidad de una animación
|
||||
void AnimatedSprite::setAnimationSpeed(std::string name, int speed) {
|
||||
animation[getIndex(name)].counter = speed;
|
||||
}
|
||||
|
||||
// Establece la velocidad de una animación
|
||||
void AnimatedSprite::setAnimationSpeed(int index, int speed) {
|
||||
animation[index].counter = speed;
|
||||
}
|
||||
|
||||
// Establece si la animación se reproduce en bucle
|
||||
void AnimatedSprite::setAnimationLoop(std::string name, int loop) {
|
||||
animation[getIndex(name)].loop = loop;
|
||||
}
|
||||
|
||||
// Establece si la animación se reproduce en bucle
|
||||
void AnimatedSprite::setAnimationLoop(int index, int loop) {
|
||||
animation[index].loop = loop;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void AnimatedSprite::setAnimationCompleted(std::string name, bool value) {
|
||||
animation[getIndex(name)].completed = value;
|
||||
}
|
||||
|
||||
// OLD - Establece el valor de la variable
|
||||
void AnimatedSprite::setAnimationCompleted(int index, bool value) {
|
||||
animation[index].completed = value;
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la animación
|
||||
bool AnimatedSprite::animationIsCompleted() {
|
||||
return animation[currentAnimation].completed;
|
||||
}
|
||||
|
||||
// Devuelve el rectangulo de una animación y frame concreto
|
||||
SDL_Rect AnimatedSprite::getAnimationClip(std::string name, Uint8 index) {
|
||||
return animation[getIndex(name)].frames[index];
|
||||
}
|
||||
|
||||
// Devuelve el rectangulo de una animación y frame concreto
|
||||
SDL_Rect AnimatedSprite::getAnimationClip(int indexA, Uint8 indexF) {
|
||||
return animation[indexA].frames[indexF];
|
||||
}
|
||||
|
||||
// Carga la animación desde un vector
|
||||
bool AnimatedSprite::loadFromVector(std::vector<std::string> *source) {
|
||||
// Inicializa variables
|
||||
int framesPerRow = 0;
|
||||
int frameWidth = 0;
|
||||
int frameHeight = 0;
|
||||
int maxTiles = 0;
|
||||
|
||||
// Indicador de éxito en el proceso
|
||||
bool success = true;
|
||||
std::string line;
|
||||
|
||||
// Recorre todo el vector
|
||||
int index = 0;
|
||||
while (index < (int)source->size()) {
|
||||
// Lee desde el vector
|
||||
line = source->at(index);
|
||||
|
||||
// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
|
||||
if (line == "[animation]") {
|
||||
animation_t buffer;
|
||||
buffer.counter = 0;
|
||||
buffer.currentFrame = 0;
|
||||
buffer.completed = false;
|
||||
|
||||
do {
|
||||
// Aumenta el indice para leer la siguiente linea
|
||||
index++;
|
||||
line = source->at(index);
|
||||
|
||||
// Encuentra la posición del caracter '='
|
||||
int pos = line.find("=");
|
||||
|
||||
// Procesa las dos subcadenas
|
||||
if (pos != (int)line.npos) {
|
||||
if (line.substr(0, pos) == "name") {
|
||||
buffer.name = line.substr(pos + 1, line.length());
|
||||
}
|
||||
|
||||
else if (line.substr(0, pos) == "speed") {
|
||||
buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
|
||||
}
|
||||
|
||||
else if (line.substr(0, pos) == "loop") {
|
||||
buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
|
||||
}
|
||||
|
||||
else if (line.substr(0, pos) == "frames") {
|
||||
// Se introducen los valores separados por comas en un vector
|
||||
std::stringstream ss(line.substr(pos + 1, line.length()));
|
||||
std::string tmp;
|
||||
SDL_Rect rect = {0, 0, frameWidth, frameHeight};
|
||||
while (getline(ss, tmp, ',')) {
|
||||
// Comprueba que el tile no sea mayor que el maximo indice permitido
|
||||
const int numTile = std::stoi(tmp) > maxTiles ? 0 : std::stoi(tmp);
|
||||
rect.x = (numTile % framesPerRow) * frameWidth;
|
||||
rect.y = (numTile / framesPerRow) * frameHeight;
|
||||
buffer.frames.push_back(rect);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
std::cout << "Warning: unknown parameter " << line.substr(0, pos).c_str() << std::endl;
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
} while (line != "[/animation]");
|
||||
|
||||
// Añade la animación al vector de animaciones
|
||||
animation.push_back(buffer);
|
||||
}
|
||||
|
||||
// En caso contrario se parsea el fichero para buscar las variables y los valores
|
||||
else {
|
||||
// Encuentra la posición del caracter '='
|
||||
int pos = line.find("=");
|
||||
|
||||
// Procesa las dos subcadenas
|
||||
if (pos != (int)line.npos) {
|
||||
if (line.substr(0, pos) == "framesPerRow") {
|
||||
framesPerRow = std::stoi(line.substr(pos + 1, line.length()));
|
||||
}
|
||||
|
||||
else if (line.substr(0, pos) == "frameWidth") {
|
||||
frameWidth = std::stoi(line.substr(pos + 1, line.length()));
|
||||
}
|
||||
|
||||
else if (line.substr(0, pos) == "frameHeight") {
|
||||
frameHeight = std::stoi(line.substr(pos + 1, line.length()));
|
||||
}
|
||||
|
||||
else {
|
||||
std::cout << "Warning: unknown parameter " << line.substr(0, pos).c_str() << std::endl;
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Normaliza valores
|
||||
if (framesPerRow == 0 && frameWidth > 0) {
|
||||
framesPerRow = texture->getWidth() / frameWidth;
|
||||
}
|
||||
|
||||
if (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
|
||||
const int w = texture->getWidth() / frameWidth;
|
||||
const int h = texture->getHeight() / frameHeight;
|
||||
maxTiles = w * h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Una vez procesada la linea, aumenta el indice para pasar a la siguiente
|
||||
index++;
|
||||
}
|
||||
|
||||
// Pone un valor por defecto
|
||||
setRect({0, 0, frameWidth, frameHeight});
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void AnimatedSprite::setCurrentAnimation(std::string name) {
|
||||
const int newAnimation = getIndex(name);
|
||||
if (currentAnimation != newAnimation) {
|
||||
currentAnimation = newAnimation;
|
||||
animation[currentAnimation].currentFrame = 0;
|
||||
animation[currentAnimation].counter = 0;
|
||||
animation[currentAnimation].completed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void AnimatedSprite::setCurrentAnimation(int index) {
|
||||
const int newAnimation = index;
|
||||
if (currentAnimation != newAnimation) {
|
||||
currentAnimation = newAnimation;
|
||||
animation[currentAnimation].currentFrame = 0;
|
||||
animation[currentAnimation].counter = 0;
|
||||
animation[currentAnimation].completed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void AnimatedSprite::update() {
|
||||
animate();
|
||||
MovingSprite::update();
|
||||
}
|
||||
|
||||
// Establece el rectangulo para un frame de una animación
|
||||
void AnimatedSprite::setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h) {
|
||||
animation[index_animation].frames.push_back({x, y, w, h});
|
||||
}
|
||||
|
||||
// OLD - Establece el contador para todas las animaciones
|
||||
void AnimatedSprite::setAnimationCounter(int value) {
|
||||
for (auto &a : animation) {
|
||||
a.counter = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Reinicia la animación
|
||||
void AnimatedSprite::resetAnimation() {
|
||||
animation[currentAnimation].currentFrame = 0;
|
||||
animation[currentAnimation].counter = 0;
|
||||
animation[currentAnimation].completed = false;
|
||||
}
|
||||
99
source/core/rendering/animatedsprite.h
Normal file
99
source/core/rendering/animatedsprite.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "core/rendering/movingsprite.h" // for MovingSprite
|
||||
class Texture;
|
||||
|
||||
struct animation_t {
|
||||
std::string name; // Nombre de la animacion
|
||||
std::vector<SDL_Rect> frames; // Cada uno de los frames que componen la animación
|
||||
int speed; // Velocidad de la animación
|
||||
int loop; // Indica a que frame vuelve la animación al terminar. -1 para que no vuelva
|
||||
bool completed; // Indica si ha finalizado la animación
|
||||
int currentFrame; // Frame actual
|
||||
int counter; // Contador para las animaciones
|
||||
};
|
||||
|
||||
struct animatedSprite_t {
|
||||
std::vector<animation_t> animations; // Vector con las diferentes animaciones
|
||||
Texture *texture; // Textura con los graficos para el sprite
|
||||
};
|
||||
|
||||
// Carga la animación desde un fichero
|
||||
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose = false);
|
||||
|
||||
// Carga la animación desde bytes en memoria
|
||||
animatedSprite_t loadAnimationFromMemory(Texture *texture, const std::vector<uint8_t> &bytes, const std::string &nameForLogs = "", bool verbose = false);
|
||||
|
||||
class AnimatedSprite : public MovingSprite {
|
||||
private:
|
||||
// Variables
|
||||
std::vector<animation_t> animation; // Vector con las diferentes animaciones
|
||||
int currentAnimation; // Animacion activa
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
AnimatedSprite(Texture *texture = nullptr, SDL_Renderer *renderer = nullptr, std::string file = "", std::vector<std::string> *buffer = nullptr);
|
||||
AnimatedSprite(SDL_Renderer *renderer, animatedSprite_t *animation);
|
||||
|
||||
// Destructor
|
||||
~AnimatedSprite();
|
||||
|
||||
// Calcula el frame correspondiente a la animación actual
|
||||
void animate();
|
||||
|
||||
// Obtiene el numero de frames de la animación actual
|
||||
int getNumFrames();
|
||||
|
||||
// Establece el frame actual de la animación
|
||||
void setCurrentFrame(int num);
|
||||
|
||||
// Establece el valor del contador
|
||||
void setAnimationCounter(std::string name, int num);
|
||||
|
||||
// Establece la velocidad de una animación
|
||||
void setAnimationSpeed(std::string name, int speed);
|
||||
void setAnimationSpeed(int index, int speed);
|
||||
|
||||
// Establece el frame al que vuelve la animación al finalizar
|
||||
void setAnimationLoop(std::string name, int loop);
|
||||
void setAnimationLoop(int index, int loop);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setAnimationCompleted(std::string name, bool value);
|
||||
void setAnimationCompleted(int index, bool value);
|
||||
|
||||
// Comprueba si ha terminado la animación
|
||||
bool animationIsCompleted();
|
||||
|
||||
// Devuelve el rectangulo de una animación y frame concreto
|
||||
SDL_Rect getAnimationClip(std::string name = "default", Uint8 index = 0);
|
||||
SDL_Rect getAnimationClip(int indexA = 0, Uint8 indexF = 0);
|
||||
|
||||
// Obtiene el indice de la animación a partir del nombre
|
||||
int getIndex(std::string name);
|
||||
|
||||
// Carga la animación desde un vector
|
||||
bool loadFromVector(std::vector<std::string> *source);
|
||||
|
||||
// Establece la animacion actual
|
||||
void setCurrentAnimation(std::string name = "default");
|
||||
void setCurrentAnimation(int index = 0);
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void update();
|
||||
|
||||
// OLD - Establece el rectangulo para un frame de una animación
|
||||
void setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h);
|
||||
|
||||
// OLD - Establece el contador para todas las animaciones
|
||||
void setAnimationCounter(int value);
|
||||
|
||||
// Reinicia la animación
|
||||
void resetAnimation();
|
||||
};
|
||||
180
source/core/rendering/fade.cpp
Normal file
180
source/core/rendering/fade.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "core/rendering/fade.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdlib.h> // for rand
|
||||
|
||||
#include <iostream> // for char_traits, basic_ostream, operator<<
|
||||
|
||||
#include "game/defaults.hpp" // for GAMECANVAS_HEIGHT, GAMECANVAS_WIDTH
|
||||
|
||||
// Constructor
|
||||
Fade::Fade(SDL_Renderer *renderer) {
|
||||
mRenderer = renderer;
|
||||
|
||||
mBackbuffer = SDL_CreateTexture(mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
|
||||
if (mBackbuffer != nullptr) {
|
||||
SDL_SetTextureScaleMode(mBackbuffer, SDL_SCALEMODE_NEAREST);
|
||||
}
|
||||
if (mBackbuffer == nullptr) {
|
||||
std::cout << "Error: textTexture could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Fade::~Fade() {
|
||||
SDL_DestroyTexture(mBackbuffer);
|
||||
mBackbuffer = nullptr;
|
||||
}
|
||||
|
||||
// Inicializa las variables
|
||||
void Fade::init(Uint8 r, Uint8 g, Uint8 b) {
|
||||
mFadeType = FADE_CENTER;
|
||||
mEnabled = false;
|
||||
mFinished = false;
|
||||
mCounter = 0;
|
||||
mR = r;
|
||||
mG = g;
|
||||
mB = b;
|
||||
mROriginal = r;
|
||||
mGOriginal = g;
|
||||
mBOriginal = b;
|
||||
mLastSquareTicks = 0;
|
||||
mSquaresDrawn = 0;
|
||||
mFullscreenDone = false;
|
||||
}
|
||||
|
||||
// Pinta una transición en pantalla
|
||||
void Fade::render() {
|
||||
if (mEnabled && !mFinished) {
|
||||
switch (mFadeType) {
|
||||
case FADE_FULLSCREEN: {
|
||||
if (!mFullscreenDone) {
|
||||
SDL_FRect fRect1 = {0, 0, (float)GAMECANVAS_WIDTH, (float)GAMECANVAS_HEIGHT};
|
||||
|
||||
int alpha = mCounter * 4;
|
||||
if (alpha >= 255) {
|
||||
alpha = 255;
|
||||
mFullscreenDone = true;
|
||||
|
||||
// Deja todos los buffers del mismo color
|
||||
SDL_SetRenderTarget(mRenderer, mBackbuffer);
|
||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255);
|
||||
SDL_RenderClear(mRenderer);
|
||||
|
||||
SDL_SetRenderTarget(mRenderer, nullptr);
|
||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255);
|
||||
SDL_RenderClear(mRenderer);
|
||||
|
||||
mFinished = true;
|
||||
} else {
|
||||
// Dibujamos sobre el renderizador
|
||||
SDL_SetRenderTarget(mRenderer, nullptr);
|
||||
|
||||
// Copia el backbuffer con la imagen que había al renderizador
|
||||
SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr);
|
||||
|
||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, alpha);
|
||||
SDL_RenderFillRect(mRenderer, &fRect1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FADE_CENTER: {
|
||||
SDL_FRect fR1 = {0, 0, (float)GAMECANVAS_WIDTH, 0};
|
||||
SDL_FRect fR2 = {0, 0, (float)GAMECANVAS_WIDTH, 0};
|
||||
|
||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 64);
|
||||
|
||||
for (int i = 0; i < mCounter; i++) {
|
||||
fR1.h = fR2.h = (float)(i * 4);
|
||||
fR2.y = (float)(GAMECANVAS_HEIGHT - (i * 4));
|
||||
|
||||
SDL_RenderFillRect(mRenderer, &fR1);
|
||||
SDL_RenderFillRect(mRenderer, &fR2);
|
||||
}
|
||||
|
||||
if ((mCounter * 4) > GAMECANVAS_HEIGHT)
|
||||
mFinished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case FADE_RANDOM_SQUARE: {
|
||||
Uint32 now = SDL_GetTicks();
|
||||
if (mSquaresDrawn < 50 && now - mLastSquareTicks >= 100) {
|
||||
mLastSquareTicks = now;
|
||||
|
||||
SDL_FRect fRs = {0, 0, 32, 32};
|
||||
|
||||
// Crea un color al azar
|
||||
Uint8 r = 255 * (rand() % 2);
|
||||
Uint8 g = 255 * (rand() % 2);
|
||||
Uint8 b = 255 * (rand() % 2);
|
||||
SDL_SetRenderDrawColor(mRenderer, r, g, b, 64);
|
||||
|
||||
// Dibujamos sobre el backbuffer
|
||||
SDL_SetRenderTarget(mRenderer, mBackbuffer);
|
||||
|
||||
fRs.x = (float)(rand() % (GAMECANVAS_WIDTH - 32));
|
||||
fRs.y = (float)(rand() % (GAMECANVAS_HEIGHT - 32));
|
||||
SDL_RenderFillRect(mRenderer, &fRs);
|
||||
|
||||
// Volvemos a usar el renderizador de forma normal
|
||||
SDL_SetRenderTarget(mRenderer, nullptr);
|
||||
|
||||
mSquaresDrawn++;
|
||||
}
|
||||
|
||||
// Copiamos el backbuffer al renderizador
|
||||
SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr);
|
||||
|
||||
if (mSquaresDrawn >= 50) {
|
||||
mFinished = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mFinished) {
|
||||
SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255);
|
||||
SDL_RenderClear(mRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables internas
|
||||
void Fade::update() {
|
||||
if (mEnabled)
|
||||
mCounter++;
|
||||
}
|
||||
|
||||
// Activa el fade
|
||||
void Fade::activateFade() {
|
||||
mEnabled = true;
|
||||
mFinished = false;
|
||||
mCounter = 0;
|
||||
mSquaresDrawn = 0;
|
||||
mLastSquareTicks = 0;
|
||||
mFullscreenDone = false;
|
||||
mR = mROriginal;
|
||||
mG = mGOriginal;
|
||||
mB = mBOriginal;
|
||||
}
|
||||
|
||||
// Comprueba si está activo
|
||||
bool Fade::isEnabled() {
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la transicion
|
||||
bool Fade::hasEnded() {
|
||||
return mFinished;
|
||||
}
|
||||
|
||||
// Establece el tipo de fade
|
||||
void Fade::setFadeType(Uint8 fadeType) {
|
||||
mFadeType = fadeType;
|
||||
}
|
||||
54
source/core/rendering/fade.h
Normal file
54
source/core/rendering/fade.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
// Tipos de fundido
|
||||
constexpr int FADE_FULLSCREEN = 0;
|
||||
constexpr int FADE_CENTER = 1;
|
||||
constexpr int FADE_RANDOM_SQUARE = 2;
|
||||
|
||||
// Clase Fade
|
||||
class Fade {
|
||||
private:
|
||||
SDL_Renderer *mRenderer; // El renderizador de la ventana
|
||||
SDL_Texture *mBackbuffer; // Textura para usar como backbuffer
|
||||
Uint8 mFadeType; // Tipo de fade a realizar
|
||||
Uint16 mCounter; // Contador interno
|
||||
bool mEnabled; // Indica si el fade está activo
|
||||
bool mFinished; // Indica si ha terminado la transición
|
||||
Uint8 mR, mG, mB; // Colores para el fade
|
||||
Uint8 mROriginal, mGOriginal, mBOriginal; // Colores originales para FADE_RANDOM_SQUARE
|
||||
Uint32 mLastSquareTicks; // Ticks del último cuadrado dibujado (FADE_RANDOM_SQUARE)
|
||||
Uint16 mSquaresDrawn; // Número de cuadrados dibujados (FADE_RANDOM_SQUARE)
|
||||
bool mFullscreenDone; // Indica si el fade fullscreen ha terminado la fase de fundido
|
||||
SDL_Rect mRect1; // Rectangulo usado para crear los efectos de transición
|
||||
SDL_Rect mRect2; // Rectangulo usado para crear los efectos de transición
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Fade(SDL_Renderer *renderer);
|
||||
|
||||
// Destructor
|
||||
~Fade();
|
||||
|
||||
// Inicializa las variables
|
||||
void init(Uint8 r, Uint8 g, Uint8 b);
|
||||
|
||||
// Pinta una transición en pantalla
|
||||
void render();
|
||||
|
||||
// Actualiza las variables internas
|
||||
void update();
|
||||
|
||||
// Activa el fade
|
||||
void activateFade();
|
||||
|
||||
// Comprueba si ha terminado la transicion
|
||||
bool hasEnded();
|
||||
|
||||
// Comprueba si está activo
|
||||
bool isEnabled();
|
||||
|
||||
// Establece el tipo de fade
|
||||
void setFadeType(Uint8 fadeType);
|
||||
};
|
||||
312
source/core/rendering/movingsprite.cpp
Normal file
312
source/core/rendering/movingsprite.cpp
Normal file
@@ -0,0 +1,312 @@
|
||||
#include "core/rendering/movingsprite.h"
|
||||
|
||||
#include "core/rendering/texture.h" // for Texture
|
||||
|
||||
// Constructor
|
||||
MovingSprite::MovingSprite(float x, float y, int w, int h, float velx, float vely, float accelx, float accely, Texture *texture, SDL_Renderer *renderer) {
|
||||
// Copia los punteros
|
||||
this->texture = texture;
|
||||
this->renderer = renderer;
|
||||
|
||||
// Establece el alto y el ancho del sprite
|
||||
this->w = w;
|
||||
this->h = h;
|
||||
|
||||
// Establece la posición X,Y del sprite
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
xPrev = x;
|
||||
yPrev = y;
|
||||
|
||||
// Establece la velocidad X,Y del sprite
|
||||
vx = velx;
|
||||
vy = vely;
|
||||
|
||||
// Establece la aceleración X,Y del sprite
|
||||
ax = accelx;
|
||||
ay = accely;
|
||||
|
||||
// Establece el zoom W,H del sprite
|
||||
zoomW = 1;
|
||||
zoomH = 1;
|
||||
|
||||
// Establece el angulo con el que se dibujará
|
||||
angle = (double)0;
|
||||
|
||||
// Establece los valores de rotacion
|
||||
rotateEnabled = false;
|
||||
rotateSpeed = 0;
|
||||
rotateAmount = (double)0;
|
||||
|
||||
// Contador interno
|
||||
counter = 0;
|
||||
|
||||
// Establece el rectangulo de donde coger la imagen
|
||||
spriteClip = {0, 0, w, h};
|
||||
|
||||
// Establece el centro de rotación
|
||||
center = nullptr;
|
||||
|
||||
// Establece el tipo de volteado
|
||||
currentFlip = SDL_FLIP_NONE;
|
||||
};
|
||||
|
||||
// Reinicia todas las variables
|
||||
void MovingSprite::clear() {
|
||||
x = 0.0f; // Posición en el eje X
|
||||
y = 0.0f; // Posición en el eje Y
|
||||
|
||||
vx = 0.0f; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
|
||||
vy = 0.0f; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
|
||||
|
||||
ax = 0.0f; // Aceleración en el eje X. Variación de la velocidad
|
||||
ay = 0.0f; // Aceleración en el eje Y. Variación de la velocidad
|
||||
|
||||
zoomW = 1.0f; // Zoom aplicado a la anchura
|
||||
zoomH = 1.0f; // Zoom aplicado a la altura
|
||||
|
||||
angle = 0.0; // Angulo para dibujarlo
|
||||
rotateEnabled = false; // Indica si ha de rotar
|
||||
center = nullptr; // Centro de rotación
|
||||
rotateSpeed = 0; // Velocidad de giro
|
||||
rotateAmount = 0.0; // Cantidad de grados a girar en cada iteración
|
||||
counter = 0; // Contador interno
|
||||
|
||||
currentFlip = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
|
||||
}
|
||||
|
||||
// Mueve el sprite
|
||||
void MovingSprite::move() {
|
||||
if (enabled) {
|
||||
xPrev = x;
|
||||
yPrev = y;
|
||||
|
||||
x += vx;
|
||||
y += vy;
|
||||
|
||||
vx += ax;
|
||||
vy += ay;
|
||||
}
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void MovingSprite::render() {
|
||||
if (enabled) {
|
||||
texture->render(renderer, (int)x, (int)y, &spriteClip, zoomW, zoomH, angle, center, currentFlip);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
float MovingSprite::getPosX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
float MovingSprite::getPosY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
float MovingSprite::getVelX() {
|
||||
return vx;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
float MovingSprite::getVelY() {
|
||||
return vy;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
float MovingSprite::getAccelX() {
|
||||
return ax;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
float MovingSprite::getAccelY() {
|
||||
return ay;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
float MovingSprite::getZoomW() {
|
||||
return zoomW;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
float MovingSprite::getZoomH() {
|
||||
return zoomH;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
double MovingSprite::getAngle() {
|
||||
return angle;
|
||||
}
|
||||
|
||||
// Establece la posición y el tamaño del objeto
|
||||
void MovingSprite::setRect(SDL_Rect rect) {
|
||||
x = (float)rect.x;
|
||||
y = (float)rect.y;
|
||||
w = rect.w;
|
||||
h = rect.h;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setPosX(float value) {
|
||||
x = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setPosY(float value) {
|
||||
y = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setVelX(float value) {
|
||||
vx = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setVelY(float value) {
|
||||
vy = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setAccelX(float value) {
|
||||
ax = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setAccelY(float value) {
|
||||
ay = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setZoomW(float value) {
|
||||
zoomW = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setZoomH(float value) {
|
||||
zoomH = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setAngle(double value) {
|
||||
angle = value;
|
||||
}
|
||||
|
||||
// Incrementa el valor de la variable
|
||||
void MovingSprite::incAngle(double value) {
|
||||
angle += value;
|
||||
}
|
||||
|
||||
// Decrementa el valor de la variable
|
||||
void MovingSprite::decAngle(double value) {
|
||||
angle -= value;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool MovingSprite::getRotate() {
|
||||
return rotateEnabled;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
Uint16 MovingSprite::getRotateSpeed() {
|
||||
return rotateSpeed;
|
||||
}
|
||||
|
||||
// Establece la rotacion
|
||||
void MovingSprite::rotate() {
|
||||
if (enabled)
|
||||
if (rotateEnabled) {
|
||||
if (counter % rotateSpeed == 0) {
|
||||
incAngle(rotateAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setRotate(bool value) {
|
||||
rotateEnabled = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setRotateSpeed(int value) {
|
||||
if (value < 1) {
|
||||
rotateSpeed = 1;
|
||||
} else {
|
||||
rotateSpeed = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setRotateAmount(double value) {
|
||||
rotateAmount = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::disableRotate() {
|
||||
rotateEnabled = false;
|
||||
angle = (double)0;
|
||||
}
|
||||
|
||||
// Actualiza las variables internas del objeto
|
||||
void MovingSprite::update() {
|
||||
move();
|
||||
rotate();
|
||||
|
||||
if (enabled) {
|
||||
++counter %= 60000;
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia el sentido de la rotación
|
||||
void MovingSprite::switchRotate() {
|
||||
rotateAmount *= -1;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void MovingSprite::setFlip(SDL_FlipMode flip) {
|
||||
currentFlip = flip;
|
||||
}
|
||||
|
||||
// Gira el sprite horizontalmente
|
||||
void MovingSprite::flip() {
|
||||
currentFlip = (currentFlip == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
SDL_FlipMode MovingSprite::getFlip() {
|
||||
return currentFlip;
|
||||
}
|
||||
|
||||
// Devuelve el rectangulo donde está el sprite
|
||||
SDL_Rect MovingSprite::getRect() {
|
||||
const SDL_Rect rect = {(int)x, (int)y, w, h};
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Deshace el último movimiento
|
||||
void MovingSprite::undoMove() {
|
||||
x = xPrev;
|
||||
y = yPrev;
|
||||
}
|
||||
|
||||
// Deshace el último movimiento en el eje X
|
||||
void MovingSprite::undoMoveX() {
|
||||
x = xPrev;
|
||||
}
|
||||
|
||||
// Deshace el último movimiento en el eje Y
|
||||
void MovingSprite::undoMoveY() {
|
||||
y = yPrev;
|
||||
}
|
||||
|
||||
// Pone a cero las velocidades de desplacamiento
|
||||
void MovingSprite::clearVel() {
|
||||
vx = vy = 0.0f;
|
||||
}
|
||||
|
||||
// Devuelve el incremento en el eje X en pixels
|
||||
int MovingSprite::getIncX() {
|
||||
return (int)x - (int)xPrev;
|
||||
}
|
||||
163
source/core/rendering/movingsprite.h
Normal file
163
source/core/rendering/movingsprite.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "core/rendering/sprite.h" // for Sprite
|
||||
class Texture;
|
||||
|
||||
// Clase MovingSprite. Añade posicion y velocidad en punto flotante
|
||||
class MovingSprite : public Sprite {
|
||||
protected:
|
||||
float x; // Posición en el eje X
|
||||
float y; // Posición en el eje Y
|
||||
|
||||
float xPrev; // Posición anterior en el eje X
|
||||
float yPrev; // Posición anterior en el eje Y
|
||||
|
||||
float vx; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
|
||||
float vy; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
|
||||
|
||||
float ax; // Aceleración en el eje X. Variación de la velocidad
|
||||
float ay; // Aceleración en el eje Y. Variación de la velocidad
|
||||
|
||||
float zoomW; // Zoom aplicado a la anchura
|
||||
float zoomH; // Zoom aplicado a la altura
|
||||
|
||||
double angle; // Angulo para dibujarlo
|
||||
bool rotateEnabled; // Indica si ha de rotar
|
||||
int rotateSpeed; // Velocidad de giro
|
||||
double rotateAmount; // Cantidad de grados a girar en cada iteración
|
||||
int counter; // Contador interno
|
||||
SDL_Point *center; // Centro de rotación
|
||||
SDL_FlipMode currentFlip; // Indica como se voltea el sprite
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
MovingSprite(float x = 0, float y = 0, int w = 0, int h = 0, float velx = 0, float vely = 0, float accelx = 0, float accely = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr);
|
||||
|
||||
// Mueve el sprite
|
||||
void move();
|
||||
|
||||
// Rota el sprite
|
||||
void rotate();
|
||||
|
||||
// Actualiza las variables internas del objeto
|
||||
void update();
|
||||
|
||||
// Reinicia todas las variables
|
||||
void clear();
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void render();
|
||||
|
||||
// Obten el valor de la variable
|
||||
float getPosX();
|
||||
|
||||
// Obten el valor de la variable
|
||||
float getPosY();
|
||||
|
||||
// Obten el valor de la variable
|
||||
float getVelX();
|
||||
|
||||
// Obten el valor de la variable
|
||||
float getVelY();
|
||||
|
||||
// Obten el valor de la variable
|
||||
float getAccelX();
|
||||
|
||||
// Obten el valor de la variable
|
||||
float getAccelY();
|
||||
|
||||
// Obten el valor de la variable
|
||||
float getZoomW();
|
||||
|
||||
// Obten el valor de la variable
|
||||
float getZoomH();
|
||||
|
||||
// Obten el valor de la variable
|
||||
double getAngle();
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool getRotate();
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
Uint16 getRotateSpeed();
|
||||
|
||||
// Establece la posición y el tamaño del objeto
|
||||
void setRect(SDL_Rect rect);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setPosX(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setPosY(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setVelX(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setVelY(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setAccelX(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setAccelY(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setZoomW(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setZoomH(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setAngle(double vaue);
|
||||
|
||||
// Incrementa el valor de la variable
|
||||
void incAngle(double value);
|
||||
|
||||
// Decrementa el valor de la variable
|
||||
void decAngle(double value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setRotate(bool value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setRotateSpeed(int value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setRotateAmount(double value);
|
||||
|
||||
// Quita el efecto de rotación y deja el sprite en su angulo inicial.
|
||||
void disableRotate();
|
||||
|
||||
// Cambia el sentido de la rotación
|
||||
void switchRotate();
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setFlip(SDL_FlipMode flip);
|
||||
|
||||
// Gira el sprite horizontalmente
|
||||
void flip();
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
SDL_FlipMode getFlip();
|
||||
|
||||
// Devuelve el rectangulo donde está el sprite
|
||||
SDL_Rect getRect();
|
||||
|
||||
// Deshace el último movimiento
|
||||
void undoMove();
|
||||
|
||||
// Deshace el último movimiento en el eje X
|
||||
void undoMoveX();
|
||||
|
||||
// Deshace el último movimiento en el eje Y
|
||||
void undoMoveY();
|
||||
|
||||
// Pone a cero las velocidades de desplacamiento
|
||||
void clearVel();
|
||||
|
||||
// Devuelve el incremento en el eje X en pixels
|
||||
int getIncX();
|
||||
};
|
||||
369
source/core/rendering/screen.cpp
Normal file
369
source/core/rendering/screen.cpp
Normal file
@@ -0,0 +1,369 @@
|
||||
#include "core/rendering/screen.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // for max, min
|
||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||
#include <string> // for basic_string, char_traits, string
|
||||
|
||||
#include "core/input/mouse.hpp" // for Mouse::cursorVisible, Mouse::lastMouseMoveTime
|
||||
#include "core/rendering/text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE
|
||||
#include "core/resources/asset.h" // for Asset
|
||||
#include "core/resources/resource.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
|
||||
// --- Fix per a fullscreen/resize en Emscripten ---
|
||||
//
|
||||
// SDL3 + Emscripten no emet de forma fiable SDL_EVENT_WINDOW_LEAVE_FULLSCREEN
|
||||
// (libsdl-org/SDL#13300) ni SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED /
|
||||
// SDL_EVENT_DISPLAY_ORIENTATION (libsdl-org/SDL#11389). Quan l'usuari ix de
|
||||
// fullscreen amb Esc o rota el mòbil, el canvas HTML torna al tamany correcte
|
||||
// però l'estat intern de SDL creu que segueix en fullscreen amb la resolució
|
||||
// anterior i el viewport queda desencuadrat.
|
||||
//
|
||||
// Solució: registrar callbacks natius d'Emscripten, diferir la feina un tick
|
||||
// del event loop (el canvas encara no està estable en el moment del callback)
|
||||
// i cridar setVideoMode() amb el flag de fullscreen actualitzat. La crida
|
||||
// interna a SDL_SetWindowFullscreen(false) és la peça que realment fa eixir
|
||||
// SDL del seu estat intern de fullscreen — sense això res més funciona.
|
||||
namespace {
|
||||
Screen *g_screen_instance = nullptr;
|
||||
|
||||
void deferredCanvasResize(void * /*userData*/) {
|
||||
if (g_screen_instance) {
|
||||
g_screen_instance->handleCanvasResized();
|
||||
}
|
||||
}
|
||||
|
||||
EM_BOOL onEmFullscreenChange(int /*eventType*/, const EmscriptenFullscreenChangeEvent *event, void * /*userData*/) {
|
||||
if (g_screen_instance && event) {
|
||||
g_screen_instance->syncFullscreenFlagFromBrowser(event->isFullscreen != 0);
|
||||
}
|
||||
emscripten_async_call(deferredCanvasResize, nullptr, 0);
|
||||
return EM_FALSE;
|
||||
}
|
||||
|
||||
EM_BOOL onEmOrientationChange(int /*eventType*/, const EmscriptenOrientationChangeEvent * /*event*/, void * /*userData*/) {
|
||||
emscripten_async_call(deferredCanvasResize, nullptr, 0);
|
||||
return EM_FALSE;
|
||||
}
|
||||
} // namespace
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
// Constructor
|
||||
Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options_t *options) {
|
||||
// Inicializa variables
|
||||
this->window = window;
|
||||
this->renderer = renderer;
|
||||
this->options = options;
|
||||
this->asset = asset;
|
||||
|
||||
gameCanvasWidth = options->gameWidth;
|
||||
gameCanvasHeight = options->gameHeight;
|
||||
borderWidth = options->borderWidth * 2;
|
||||
borderHeight = options->borderHeight * 2;
|
||||
|
||||
// Define el color del borde para el modo de pantalla completa
|
||||
borderColor = {0x00, 0x00, 0x00};
|
||||
|
||||
// Crea la textura donde se dibujan los graficos del juego
|
||||
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
|
||||
if (gameCanvas != nullptr) {
|
||||
SDL_SetTextureScaleMode(gameCanvas, options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
|
||||
}
|
||||
if (gameCanvas == nullptr) {
|
||||
if (options->console) {
|
||||
std::cout << "gameCanvas could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el modo de video
|
||||
setVideoMode(options->videoMode != 0);
|
||||
|
||||
// Inicializa el sistema de notificaciones (Text compartido de Resource)
|
||||
notificationText = Resource::get()->getText("8bithud");
|
||||
notificationMessage = "";
|
||||
notificationTextColor = {0xFF, 0xFF, 0xFF};
|
||||
notificationOutlineColor = {0x00, 0x00, 0x00};
|
||||
notificationEndTime = 0;
|
||||
notificationY = 2;
|
||||
|
||||
// Registra callbacks natius d'Emscripten per a fullscreen/orientation
|
||||
registerEmscriptenEventCallbacks();
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Screen::~Screen() {
|
||||
// notificationText es propiedad de Resource — no liberar.
|
||||
SDL_DestroyTexture(gameCanvas);
|
||||
}
|
||||
|
||||
// Limpia la pantalla
|
||||
void Screen::clean(color_t color) {
|
||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 0xFF);
|
||||
SDL_RenderClear(renderer);
|
||||
}
|
||||
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
void Screen::start() {
|
||||
SDL_SetRenderTarget(renderer, gameCanvas);
|
||||
}
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
void Screen::blit() {
|
||||
// Dibuja la notificación activa sobre el gameCanvas antes de presentar
|
||||
SDL_SetRenderTarget(renderer, gameCanvas);
|
||||
renderNotification();
|
||||
|
||||
// Vuelve a dejar el renderizador en modo normal
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
|
||||
// Borra el contenido previo
|
||||
SDL_SetRenderDrawColor(renderer, borderColor.r, borderColor.g, borderColor.b, 0xFF);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// Copia la textura de juego en el renderizador en la posición adecuada
|
||||
SDL_FRect fdest = {(float)dest.x, (float)dest.y, (float)dest.w, (float)dest.h};
|
||||
SDL_RenderTexture(renderer, gameCanvas, nullptr, &fdest);
|
||||
|
||||
// Muestra por pantalla el renderizador
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Video y ventana
|
||||
// ============================================================================
|
||||
|
||||
// Establece el modo de video
|
||||
void Screen::setVideoMode(bool fullscreen) {
|
||||
applyFullscreen(fullscreen);
|
||||
if (fullscreen) {
|
||||
applyFullscreenLayout();
|
||||
} else {
|
||||
applyWindowedLayout();
|
||||
}
|
||||
applyLogicalPresentation(fullscreen);
|
||||
}
|
||||
|
||||
// Cambia entre pantalla completa y ventana
|
||||
void Screen::toggleVideoMode() {
|
||||
setVideoMode(options->videoMode == 0);
|
||||
}
|
||||
|
||||
// Reduce el zoom de la ventana
|
||||
auto Screen::decWindowZoom() -> bool {
|
||||
if (options->videoMode != 0) { return false; }
|
||||
const int PREV = options->windowSize;
|
||||
options->windowSize = std::max(options->windowSize - 1, WINDOW_ZOOM_MIN);
|
||||
if (options->windowSize == PREV) { return false; }
|
||||
setVideoMode(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Aumenta el zoom de la ventana
|
||||
auto Screen::incWindowZoom() -> bool {
|
||||
if (options->videoMode != 0) { return false; }
|
||||
const int PREV = options->windowSize;
|
||||
options->windowSize = std::min(options->windowSize + 1, WINDOW_ZOOM_MAX);
|
||||
if (options->windowSize == PREV) { return false; }
|
||||
setVideoMode(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Establece el zoom de la ventana directamente
|
||||
auto Screen::setWindowZoom(int zoom) -> bool {
|
||||
if (options->videoMode != 0) { return false; }
|
||||
if (zoom < WINDOW_ZOOM_MIN || zoom > WINDOW_ZOOM_MAX) { return false; }
|
||||
if (zoom == options->windowSize) { return false; }
|
||||
options->windowSize = zoom;
|
||||
setVideoMode(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Establece el escalado entero
|
||||
void Screen::setIntegerScale(bool enabled) {
|
||||
if (options->integerScale == enabled) { return; }
|
||||
options->integerScale = enabled;
|
||||
setVideoMode(options->videoMode != 0);
|
||||
}
|
||||
|
||||
// Alterna el escalado entero
|
||||
void Screen::toggleIntegerScale() {
|
||||
setIntegerScale(!options->integerScale);
|
||||
}
|
||||
|
||||
// Establece el V-Sync
|
||||
void Screen::setVSync(bool enabled) {
|
||||
options->vSync = enabled;
|
||||
SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
}
|
||||
|
||||
// Alterna el V-Sync
|
||||
void Screen::toggleVSync() {
|
||||
setVSync(!options->vSync);
|
||||
}
|
||||
|
||||
// Cambia el color del borde
|
||||
void Screen::setBorderColor(color_t color) {
|
||||
borderColor = color;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helpers privados de setVideoMode
|
||||
// ============================================================================
|
||||
|
||||
// SDL_SetWindowFullscreen + visibilidad del cursor
|
||||
void Screen::applyFullscreen(bool fullscreen) {
|
||||
SDL_SetWindowFullscreen(window, fullscreen);
|
||||
if (fullscreen) {
|
||||
SDL_HideCursor();
|
||||
Mouse::cursorVisible = false;
|
||||
} else {
|
||||
SDL_ShowCursor();
|
||||
Mouse::cursorVisible = true;
|
||||
Mouse::lastMouseMoveTime = SDL_GetTicks();
|
||||
}
|
||||
}
|
||||
|
||||
// Calcula windowWidth/Height/dest para el modo ventana y aplica SDL_SetWindowSize
|
||||
void Screen::applyWindowedLayout() {
|
||||
if (options->borderEnabled) {
|
||||
windowWidth = gameCanvasWidth + borderWidth;
|
||||
windowHeight = gameCanvasHeight + borderHeight;
|
||||
dest = {0 + (borderWidth / 2), 0 + (borderHeight / 2), gameCanvasWidth, gameCanvasHeight};
|
||||
} else {
|
||||
windowWidth = gameCanvasWidth;
|
||||
windowHeight = gameCanvasHeight;
|
||||
dest = {0, 0, gameCanvasWidth, gameCanvasHeight};
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
windowWidth *= WASM_RENDER_SCALE;
|
||||
windowHeight *= WASM_RENDER_SCALE;
|
||||
dest.w *= WASM_RENDER_SCALE;
|
||||
dest.h *= WASM_RENDER_SCALE;
|
||||
#endif
|
||||
|
||||
// Modifica el tamaño de la ventana
|
||||
SDL_SetWindowSize(window, windowWidth * options->windowSize, windowHeight * options->windowSize);
|
||||
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
}
|
||||
|
||||
// Obtiene el tamaño de la ventana en fullscreen y calcula el rect del juego
|
||||
void Screen::applyFullscreenLayout() {
|
||||
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
|
||||
computeFullscreenGameRect();
|
||||
}
|
||||
|
||||
// Calcula el rectángulo dest para fullscreen: integerScale / keepAspect / stretched
|
||||
void Screen::computeFullscreenGameRect() {
|
||||
if (options->integerScale) {
|
||||
// Calcula el tamaño de la escala máxima
|
||||
int scale = 0;
|
||||
while (((gameCanvasWidth * (scale + 1)) <= windowWidth) && ((gameCanvasHeight * (scale + 1)) <= windowHeight)) {
|
||||
scale++;
|
||||
}
|
||||
|
||||
dest.w = gameCanvasWidth * scale;
|
||||
dest.h = gameCanvasHeight * scale;
|
||||
dest.x = (windowWidth - dest.w) / 2;
|
||||
dest.y = (windowHeight - dest.h) / 2;
|
||||
} else if (options->keepAspect) {
|
||||
float ratio = (float)gameCanvasWidth / (float)gameCanvasHeight;
|
||||
if ((windowWidth - gameCanvasWidth) >= (windowHeight - gameCanvasHeight)) {
|
||||
dest.h = windowHeight;
|
||||
dest.w = (int)((windowHeight * ratio) + 0.5f);
|
||||
dest.x = (windowWidth - dest.w) / 2;
|
||||
dest.y = (windowHeight - dest.h) / 2;
|
||||
} else {
|
||||
dest.w = windowWidth;
|
||||
dest.h = (int)((windowWidth / ratio) + 0.5f);
|
||||
dest.x = (windowWidth - dest.w) / 2;
|
||||
dest.y = (windowHeight - dest.h) / 2;
|
||||
}
|
||||
} else {
|
||||
dest.w = windowWidth;
|
||||
dest.h = windowHeight;
|
||||
dest.x = dest.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica la logical presentation y persiste el estado en options
|
||||
void Screen::applyLogicalPresentation(bool fullscreen) {
|
||||
SDL_SetRenderLogicalPresentation(renderer, windowWidth, windowHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
|
||||
// Actualiza las opciones
|
||||
options->videoMode = fullscreen ? SDL_WINDOW_FULLSCREEN : 0;
|
||||
options->screen.windowWidth = windowWidth;
|
||||
options->screen.windowHeight = windowHeight;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Notificaciones
|
||||
// ============================================================================
|
||||
|
||||
// Muestra una notificación en la línea superior durante durationMs
|
||||
void Screen::notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs) {
|
||||
notificationMessage = text;
|
||||
notificationTextColor = textColor;
|
||||
notificationOutlineColor = outlineColor;
|
||||
notificationEndTime = SDL_GetTicks() + durationMs;
|
||||
}
|
||||
|
||||
// Limpia la notificación actual
|
||||
void Screen::clearNotification() {
|
||||
notificationEndTime = 0;
|
||||
notificationMessage.clear();
|
||||
}
|
||||
|
||||
// Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||
void Screen::renderNotification() {
|
||||
if (SDL_GetTicks() >= notificationEndTime) {
|
||||
return;
|
||||
}
|
||||
notificationText->writeDX(TXT_CENTER | TXT_COLOR | TXT_STROKE,
|
||||
gameCanvasWidth / 2,
|
||||
notificationY,
|
||||
notificationMessage,
|
||||
1,
|
||||
notificationTextColor,
|
||||
1,
|
||||
notificationOutlineColor);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Emscripten — fix per a fullscreen/resize (veure el bloc de comentaris al
|
||||
// principi del fitxer i l'anonymous namespace amb els callbacks natius).
|
||||
// ============================================================================
|
||||
|
||||
void Screen::handleCanvasResized() {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// La crida a SDL_SetWindowFullscreen + SDL_SetRenderLogicalPresentation
|
||||
// que fa setVideoMode és l'única manera de resincronitzar l'estat intern
|
||||
// de SDL amb el canvas HTML real.
|
||||
setVideoMode(options->videoMode != 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Screen::syncFullscreenFlagFromBrowser(bool isFullscreen) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
options->videoMode = isFullscreen ? SDL_WINDOW_FULLSCREEN : 0;
|
||||
#else
|
||||
(void)isFullscreen;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Screen::registerEmscriptenEventCallbacks() {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// IMPORTANT: NO registrem resize callback. En mòbil, fer scroll fa que el
|
||||
// navegador oculti/mostri la barra d'URL, disparant un resize del DOM per
|
||||
// cada scroll. Això portava a cridar setVideoMode per cada scroll, que
|
||||
// re-aplicava la logical presentation i corrompia el viewport intern de SDL.
|
||||
g_screen_instance = this;
|
||||
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange);
|
||||
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
|
||||
#endif
|
||||
}
|
||||
94
source/core/rendering/screen.h
Normal file
94
source/core/rendering/screen.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // for string
|
||||
|
||||
#include "utils/utils.h" // for color_t
|
||||
class Asset;
|
||||
class Text;
|
||||
|
||||
// Tipos de filtro
|
||||
constexpr int FILTER_NEAREST = 0;
|
||||
constexpr int FILTER_LINEAL = 1;
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
// Constantes
|
||||
static constexpr int WINDOW_ZOOM_MIN = 1;
|
||||
static constexpr int WINDOW_ZOOM_MAX = 4;
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// En WASM el tamaño de ventana está fijado a 1x, así que escalamos el
|
||||
// renderizado por 3 aprovechando el modo NEAREST de la textura del juego
|
||||
// para que los píxeles salgan nítidos.
|
||||
static constexpr int WASM_RENDER_SCALE = 3;
|
||||
#endif
|
||||
|
||||
// Constructor y destructor
|
||||
Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options_t *options);
|
||||
~Screen();
|
||||
|
||||
// Render loop
|
||||
void clean(color_t color = {0x00, 0x00, 0x00}); // Limpia la pantalla
|
||||
void start(); // Prepara para empezar a dibujar en la textura de juego
|
||||
void blit(); // Vuelca el contenido del renderizador en pantalla
|
||||
|
||||
// Video y ventana
|
||||
void setVideoMode(bool fullscreen); // Establece el modo de video
|
||||
void toggleVideoMode(); // Cambia entre pantalla completa y ventana
|
||||
void handleCanvasResized(); // En Emscripten, reaplica setVideoMode tras un cambio del navegador (salida de fullscreen con Esc, rotación). No-op fuera de Emscripten
|
||||
void syncFullscreenFlagFromBrowser(bool isFullscreen); // Sincroniza el flag interno de fullscreen con el estado real del navegador. Debe llamarse antes de diferir handleCanvasResized. No-op fuera de Emscripten
|
||||
void toggleIntegerScale(); // Alterna el escalado entero
|
||||
void setIntegerScale(bool enabled); // Establece el escalado entero
|
||||
void toggleVSync(); // Alterna el V-Sync
|
||||
void setVSync(bool enabled); // Establece el V-Sync
|
||||
auto decWindowZoom() -> bool; // Reduce el zoom de la ventana (devuelve true si cambió)
|
||||
auto incWindowZoom() -> bool; // Aumenta el zoom de la ventana (devuelve true si cambió)
|
||||
auto setWindowZoom(int zoom) -> bool; // Establece el zoom de la ventana (devuelve true si cambió)
|
||||
|
||||
// Borde
|
||||
void setBorderColor(color_t color); // Cambia el color del borde
|
||||
|
||||
// Notificaciones
|
||||
void notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
|
||||
void clearNotification(); // Limpia la notificación actual
|
||||
|
||||
private:
|
||||
// Helpers internos de setVideoMode
|
||||
void applyFullscreen(bool fullscreen); // SDL_SetWindowFullscreen + visibilidad del cursor
|
||||
void applyWindowedLayout(); // Calcula windowWidth/Height/dest + SDL_SetWindowSize + SDL_SetWindowPosition
|
||||
void applyFullscreenLayout(); // SDL_GetWindowSize + delegación a computeFullscreenGameRect
|
||||
void computeFullscreenGameRect(); // Calcula dest en fullscreen (integerScale / keepAspect / stretched)
|
||||
void applyLogicalPresentation(bool fullscreen); // SDL_SetRenderLogicalPresentation + persistencia a options
|
||||
|
||||
// Emscripten
|
||||
void registerEmscriptenEventCallbacks(); // Registra los callbacks nativos de Emscripten para fullscreenchange y orientationchange. No-op fuera de Emscripten
|
||||
|
||||
// Notificaciones
|
||||
void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||
|
||||
// Objetos y punteros
|
||||
SDL_Window *window; // Ventana de la aplicación
|
||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||
Asset *asset; // Objeto con el listado de recursos
|
||||
SDL_Texture *gameCanvas; // Textura para completar la ventana de juego hasta la pantalla completa
|
||||
options_t *options; // Variable con todas las opciones del programa
|
||||
|
||||
// Variables
|
||||
int windowWidth; // Ancho de la pantalla o ventana
|
||||
int windowHeight; // Alto de la pantalla o ventana
|
||||
int gameCanvasWidth; // Resolución interna del juego. Es el ancho de la textura donde se dibuja el juego
|
||||
int gameCanvasHeight; // Resolución interna del juego. Es el alto de la textura donde se dibuja el juego
|
||||
int borderWidth; // Anchura del borde
|
||||
int borderHeight; // Altura del borde
|
||||
SDL_Rect dest; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
|
||||
color_t borderColor; // Color del borde añadido a la textura de juego para rellenar la pantalla
|
||||
|
||||
// Notificaciones - una sola activa, sin apilación ni animaciones
|
||||
Text *notificationText; // Fuente 8bithud dedicada a las notificaciones
|
||||
std::string notificationMessage; // Texto a mostrar
|
||||
color_t notificationTextColor; // Color del texto
|
||||
color_t notificationOutlineColor; // Color del outline
|
||||
Uint32 notificationEndTime; // SDL_GetTicks() hasta el cual se muestra
|
||||
int notificationY; // Fila vertical en el canvas virtual
|
||||
};
|
||||
164
source/core/rendering/smartsprite.cpp
Normal file
164
source/core/rendering/smartsprite.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "core/rendering/smartsprite.h"
|
||||
|
||||
#include "core/rendering/movingsprite.h" // for MovingSprite
|
||||
class Texture;
|
||||
|
||||
// Constructor
|
||||
SmartSprite::SmartSprite(Texture *texture, SDL_Renderer *renderer) {
|
||||
// Copia punteros
|
||||
setTexture(texture);
|
||||
setRenderer(renderer);
|
||||
|
||||
// Inicializa el objeto
|
||||
init();
|
||||
}
|
||||
|
||||
// Inicializa el objeto
|
||||
void SmartSprite::init() {
|
||||
enabled = false;
|
||||
enabledCounter = 0;
|
||||
onDestination = false;
|
||||
destX = 0;
|
||||
destY = 0;
|
||||
counter = 0;
|
||||
finished = false;
|
||||
}
|
||||
|
||||
// Actualiza la posición y comprueba si ha llegado a su destino
|
||||
void SmartSprite::update() {
|
||||
if (enabled) {
|
||||
// Actualiza las variables internas del objeto
|
||||
MovingSprite::update();
|
||||
|
||||
// Comprueba el movimiento
|
||||
checkMove();
|
||||
|
||||
// Comprueba si ha terminado
|
||||
checkFinished();
|
||||
}
|
||||
}
|
||||
|
||||
// Pinta el objeto en pantalla
|
||||
void SmartSprite::render() {
|
||||
if (enabled) {
|
||||
// Muestra el sprite por pantalla
|
||||
MovingSprite::render();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool SmartSprite::isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void SmartSprite::setEnabled(bool enabled) {
|
||||
this->enabled = enabled;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
int SmartSprite::getEnabledCounter() {
|
||||
return enabledCounter;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void SmartSprite::setEnabledCounter(int value) {
|
||||
enabledCounter = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void SmartSprite::setDestX(int x) {
|
||||
destX = x;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void SmartSprite::setDestY(int y) {
|
||||
destY = y;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
int SmartSprite::getDestX() {
|
||||
return destX;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
int SmartSprite::getDestY() {
|
||||
return destY;
|
||||
}
|
||||
|
||||
// Comprueba el movimiento
|
||||
void SmartSprite::checkMove() {
|
||||
// Comprueba si se desplaza en el eje X hacia la derecha
|
||||
if (getAccelX() > 0 || getVelX() > 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosX() > destX) {
|
||||
// Lo coloca en posición
|
||||
setPosX(destX);
|
||||
|
||||
// Lo detiene
|
||||
setVelX(0.0f);
|
||||
setAccelX(0.0f);
|
||||
}
|
||||
}
|
||||
// Comprueba si se desplaza en el eje X hacia la izquierda
|
||||
else if (getAccelX() < 0 || getVelX() < 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosX() < destX) {
|
||||
// Lo coloca en posición
|
||||
setPosX(destX);
|
||||
|
||||
// Lo detiene
|
||||
setVelX(0.0f);
|
||||
setAccelX(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si se desplaza en el eje Y hacia abajo
|
||||
if (getAccelY() > 0 || getVelY() > 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosY() > destY) {
|
||||
// Lo coloca en posición
|
||||
setPosY(destY);
|
||||
|
||||
// Lo detiene
|
||||
setVelY(0.0f);
|
||||
setAccelY(0.0f);
|
||||
}
|
||||
}
|
||||
// Comprueba si se desplaza en el eje Y hacia arriba
|
||||
else if (getAccelY() < 0 || getVelY() < 0) {
|
||||
// Comprueba si ha llegado al destino
|
||||
if (getPosY() < destY) {
|
||||
// Lo coloca en posición
|
||||
setPosY(destY);
|
||||
|
||||
// Lo detiene
|
||||
setVelY(0.0f);
|
||||
setAccelY(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado
|
||||
void SmartSprite::checkFinished() {
|
||||
// Comprueba si ha llegado a su destino
|
||||
onDestination = (getPosX() == destX && getPosY() == destY) ? true : false;
|
||||
|
||||
if (onDestination) { // Si esta en el destino comprueba su contador
|
||||
if (enabledCounter == 0) { // Si ha llegado a cero, deshabilita el objeto y lo marca como finalizado
|
||||
finished = true;
|
||||
} else { // Si no ha llegado a cero, decrementa el contador
|
||||
enabledCounter--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool SmartSprite::isOnDestination() {
|
||||
return onDestination;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool SmartSprite::hasFinished() {
|
||||
return finished;
|
||||
}
|
||||
67
source/core/rendering/smartsprite.h
Normal file
67
source/core/rendering/smartsprite.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "core/rendering/animatedsprite.h" // for AnimatedSprite
|
||||
class Texture;
|
||||
|
||||
// Clase SmartSprite
|
||||
class SmartSprite : public AnimatedSprite {
|
||||
private:
|
||||
// Variables
|
||||
bool enabled; // Indica si esta habilitado
|
||||
bool onDestination; // Indica si está en el destino
|
||||
int destX; // Posicion de destino en el eje X
|
||||
int destY; // Posicion de destino en el eje Y
|
||||
int enabledCounter; // Contador para deshabilitarlo
|
||||
bool finished; // Indica si ya ha terminado
|
||||
|
||||
// Comprueba el movimiento
|
||||
void checkMove();
|
||||
|
||||
// Comprueba si ha terminado
|
||||
void checkFinished();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
SmartSprite(Texture *texture, SDL_Renderer *renderer);
|
||||
|
||||
// Inicializa el objeto
|
||||
void init();
|
||||
|
||||
// Actualiza la posición y comprueba si ha llegado a su destino
|
||||
void update();
|
||||
|
||||
// Pinta el objeto en pantalla
|
||||
void render();
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool isEnabled();
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
int getEnabledCounter();
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setEnabledCounter(int value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setDestX(int x);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setDestY(int y);
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
int getDestX();
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
int getDestY();
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool isOnDestination();
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool hasFinished();
|
||||
};
|
||||
166
source/core/rendering/sprite.cpp
Normal file
166
source/core/rendering/sprite.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "core/rendering/sprite.h"
|
||||
|
||||
#include "core/rendering/texture.h" // for Texture
|
||||
|
||||
// Constructor
|
||||
Sprite::Sprite(int x, int y, int w, int h, Texture *texture, SDL_Renderer *renderer) {
|
||||
// Establece la posición X,Y del sprite
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
|
||||
// Establece el alto y el ancho del sprite
|
||||
this->w = w;
|
||||
this->h = h;
|
||||
|
||||
// Establece el puntero al renderizador de la ventana
|
||||
this->renderer = renderer;
|
||||
|
||||
// Establece la textura donde están los gráficos para el sprite
|
||||
this->texture = texture;
|
||||
|
||||
// Establece el rectangulo de donde coger la imagen
|
||||
spriteClip = {0, 0, w, h};
|
||||
|
||||
// Inicializa variables
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
Sprite::Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer) {
|
||||
// Establece la posición X,Y del sprite
|
||||
x = rect.x;
|
||||
y = rect.y;
|
||||
|
||||
// Establece el alto y el ancho del sprite
|
||||
w = rect.w;
|
||||
h = rect.h;
|
||||
|
||||
// Establece el puntero al renderizador de la ventana
|
||||
this->renderer = renderer;
|
||||
|
||||
// Establece la textura donde están los gráficos para el sprite
|
||||
this->texture = texture;
|
||||
|
||||
// Establece el rectangulo de donde coger la imagen
|
||||
spriteClip = {0, 0, w, h};
|
||||
|
||||
// Inicializa variables
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Sprite::~Sprite() {
|
||||
texture = nullptr;
|
||||
renderer = nullptr;
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void Sprite::render() {
|
||||
if (enabled) {
|
||||
texture->render(renderer, x, y, &spriteClip);
|
||||
}
|
||||
}
|
||||
|
||||
// Obten el valor de la variable
|
||||
int Sprite::getPosX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
// Obten el valor de la variable
|
||||
int Sprite::getPosY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
// Obten el valor de la variable
|
||||
int Sprite::getWidth() {
|
||||
return w;
|
||||
}
|
||||
|
||||
// Obten el valor de la variable
|
||||
int Sprite::getHeight() {
|
||||
return h;
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void Sprite::setPos(SDL_Rect rect) {
|
||||
this->x = rect.x;
|
||||
this->y = rect.y;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setPosX(int x) {
|
||||
this->x = x;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setPosY(int y) {
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setWidth(int w) {
|
||||
this->w = w;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setHeight(int h) {
|
||||
this->h = h;
|
||||
}
|
||||
|
||||
// Obten el valor de la variable
|
||||
SDL_Rect Sprite::getSpriteClip() {
|
||||
return spriteClip;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setSpriteClip(SDL_Rect rect) {
|
||||
spriteClip = rect;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setSpriteClip(int x, int y, int w, int h) {
|
||||
spriteClip = {x, y, w, h};
|
||||
}
|
||||
|
||||
// Obten el valor de la variable
|
||||
Texture *Sprite::getTexture() {
|
||||
return texture;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setTexture(Texture *texture) {
|
||||
this->texture = texture;
|
||||
}
|
||||
|
||||
// Obten el valor de la variable
|
||||
SDL_Renderer *Sprite::getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setRenderer(SDL_Renderer *renderer) {
|
||||
this->renderer = renderer;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Sprite::setEnabled(bool value) {
|
||||
enabled = value;
|
||||
}
|
||||
|
||||
// Comprueba si el objeto está habilitado
|
||||
bool Sprite::isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
// Devuelve el rectangulo donde está el sprite
|
||||
SDL_Rect Sprite::getRect() {
|
||||
SDL_Rect rect = {x, y, w, h};
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Establece los valores de posición y tamaño del sprite
|
||||
void Sprite::setRect(SDL_Rect rect) {
|
||||
x = rect.x;
|
||||
y = rect.y;
|
||||
w = rect.w;
|
||||
h = rect.h;
|
||||
}
|
||||
90
source/core/rendering/sprite.h
Normal file
90
source/core/rendering/sprite.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
class Texture;
|
||||
|
||||
// Clase sprite
|
||||
class Sprite {
|
||||
protected:
|
||||
int x; // Posición en el eje X donde dibujar el sprite
|
||||
int y; // Posición en el eje Y donde dibujar el sprite
|
||||
int w; // Ancho del sprite
|
||||
int h; // Alto del sprite
|
||||
|
||||
SDL_Renderer *renderer; // Puntero al renderizador de la ventana
|
||||
Texture *texture; // Textura donde estan todos los dibujos del sprite
|
||||
SDL_Rect spriteClip; // Rectangulo de origen de la textura que se dibujará en pantalla
|
||||
|
||||
bool enabled; // Indica si el sprite esta habilitado
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Sprite(int x = 0, int y = 0, int w = 0, int h = 0, Texture *texture = nullptr, SDL_Renderer *renderer = nullptr);
|
||||
Sprite(SDL_Rect rect, Texture *texture, SDL_Renderer *renderer);
|
||||
|
||||
// Destructor
|
||||
~Sprite();
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void render();
|
||||
|
||||
// Obten el valor de la variable
|
||||
int getPosX();
|
||||
|
||||
// Obten el valor de la variable
|
||||
int getPosY();
|
||||
|
||||
// Obten el valor de la variable
|
||||
int getWidth();
|
||||
|
||||
// Obten el valor de la variable
|
||||
int getHeight();
|
||||
|
||||
// Establece la posición del objeto
|
||||
void setPos(SDL_Rect rect);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setPosX(int x);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setPosY(int y);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setWidth(int w);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setHeight(int h);
|
||||
|
||||
// Obten el valor de la variable
|
||||
SDL_Rect getSpriteClip();
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setSpriteClip(SDL_Rect rect);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setSpriteClip(int x, int y, int w, int h);
|
||||
|
||||
// Obten el valor de la variable
|
||||
Texture *getTexture();
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setTexture(Texture *texture);
|
||||
|
||||
// Obten el valor de la variable
|
||||
SDL_Renderer *getRenderer();
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setRenderer(SDL_Renderer *renderer);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setEnabled(bool value);
|
||||
|
||||
// Comprueba si el objeto está habilitado
|
||||
bool isEnabled();
|
||||
|
||||
// Devuelve el rectangulo donde está el sprite
|
||||
SDL_Rect getRect();
|
||||
|
||||
// Establece los valores de posición y tamaño del sprite
|
||||
void setRect(SDL_Rect rect);
|
||||
};
|
||||
271
source/core/rendering/text.cpp
Normal file
271
source/core/rendering/text.cpp
Normal file
@@ -0,0 +1,271 @@
|
||||
|
||||
#include "core/rendering/text.h"
|
||||
|
||||
#include <fstream> // for char_traits, basic_ostream, basic_ifstream, ope...
|
||||
#include <iostream> // for cout
|
||||
#include <sstream>
|
||||
|
||||
#include "core/rendering/sprite.h" // for Sprite
|
||||
#include "core/rendering/texture.h" // for Texture
|
||||
#include "utils/utils.h" // for color_t
|
||||
|
||||
// Parser compartido: rellena un textFile_t desde cualquier istream
|
||||
static void parseTextFileStream(std::istream &rfile, textFile_t &tf) {
|
||||
std::string buffer;
|
||||
std::getline(rfile, buffer);
|
||||
std::getline(rfile, buffer);
|
||||
tf.boxWidth = std::stoi(buffer);
|
||||
|
||||
std::getline(rfile, buffer);
|
||||
std::getline(rfile, buffer);
|
||||
tf.boxHeight = std::stoi(buffer);
|
||||
|
||||
int index = 32;
|
||||
int line_read = 0;
|
||||
while (std::getline(rfile, buffer)) {
|
||||
if (line_read % 2 == 1) {
|
||||
tf.offset[index++].w = std::stoi(buffer);
|
||||
}
|
||||
buffer.clear();
|
||||
line_read++;
|
||||
}
|
||||
}
|
||||
|
||||
static void computeTextFileOffsets(textFile_t &tf) {
|
||||
for (int i = 32; i < 128; ++i) {
|
||||
tf.offset[i].x = ((i - 32) % 15) * tf.boxWidth;
|
||||
tf.offset[i].y = ((i - 32) / 15) * tf.boxHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Llena una estructuta textFile_t desde un fichero
|
||||
textFile_t LoadTextFile(std::string file, bool verbose) {
|
||||
textFile_t tf;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
tf.offset[i].x = 0;
|
||||
tf.offset[i].y = 0;
|
||||
tf.offset[i].w = 0;
|
||||
}
|
||||
|
||||
const std::string filename = file.substr(file.find_last_of("\\/") + 1).c_str();
|
||||
std::ifstream rfile(file);
|
||||
if (rfile.is_open() && rfile.good()) {
|
||||
parseTextFileStream(rfile, tf);
|
||||
if (verbose) {
|
||||
std::cout << "Text loaded: " << filename.c_str() << std::endl;
|
||||
}
|
||||
} else if (verbose) {
|
||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||
}
|
||||
|
||||
computeTextFileOffsets(tf);
|
||||
return tf;
|
||||
}
|
||||
|
||||
// Llena una estructura textFile_t desde bytes en memoria
|
||||
textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose) {
|
||||
textFile_t tf;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
tf.offset[i].x = 0;
|
||||
tf.offset[i].y = 0;
|
||||
tf.offset[i].w = 0;
|
||||
}
|
||||
if (!bytes.empty()) {
|
||||
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||
std::stringstream ss(content);
|
||||
parseTextFileStream(ss, tf);
|
||||
if (verbose) {
|
||||
std::cout << "Text loaded from memory" << std::endl;
|
||||
}
|
||||
}
|
||||
computeTextFileOffsets(tf);
|
||||
return tf;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Text::Text(std::string bitmapFile, std::string textFile, SDL_Renderer *renderer) {
|
||||
// Carga los offsets desde el fichero
|
||||
textFile_t tf = LoadTextFile(textFile);
|
||||
|
||||
// Inicializa variables desde la estructura
|
||||
boxHeight = tf.boxHeight;
|
||||
boxWidth = tf.boxWidth;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset[i].x = tf.offset[i].x;
|
||||
offset[i].y = tf.offset[i].y;
|
||||
offset[i].w = tf.offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
texture = new Texture(renderer, bitmapFile);
|
||||
sprite = new Sprite({0, 0, boxWidth, boxHeight}, texture, renderer);
|
||||
|
||||
// Inicializa variables
|
||||
fixedWidth = false;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Text::Text(std::string textFile, Texture *texture, SDL_Renderer *renderer) {
|
||||
// Carga los offsets desde el fichero
|
||||
textFile_t tf = LoadTextFile(textFile);
|
||||
|
||||
// Inicializa variables desde la estructura
|
||||
boxHeight = tf.boxHeight;
|
||||
boxWidth = tf.boxWidth;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset[i].x = tf.offset[i].x;
|
||||
offset[i].y = tf.offset[i].y;
|
||||
offset[i].w = tf.offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
this->texture = nullptr;
|
||||
sprite = new Sprite({0, 0, boxWidth, boxHeight}, texture, renderer);
|
||||
|
||||
// Inicializa variables
|
||||
fixedWidth = false;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Text::Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer) {
|
||||
// Inicializa variables desde la estructura
|
||||
boxHeight = textFile->boxHeight;
|
||||
boxWidth = textFile->boxWidth;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset[i].x = textFile->offset[i].x;
|
||||
offset[i].y = textFile->offset[i].y;
|
||||
offset[i].w = textFile->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
this->texture = nullptr;
|
||||
sprite = new Sprite({0, 0, boxWidth, boxHeight}, texture, renderer);
|
||||
|
||||
// Inicializa variables
|
||||
fixedWidth = false;
|
||||
}
|
||||
|
||||
// Constructor desde bytes
|
||||
Text::Text(const std::vector<uint8_t> &pngBytes, const std::vector<uint8_t> &txtBytes, SDL_Renderer *renderer) {
|
||||
textFile_t tf = LoadTextFileFromMemory(txtBytes);
|
||||
boxHeight = tf.boxHeight;
|
||||
boxWidth = tf.boxWidth;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset[i].x = tf.offset[i].x;
|
||||
offset[i].y = tf.offset[i].y;
|
||||
offset[i].w = tf.offset[i].w;
|
||||
}
|
||||
|
||||
// Crea la textura desde bytes (Text es dueño en este overload)
|
||||
texture = new Texture(renderer, pngBytes);
|
||||
sprite = new Sprite({0, 0, boxWidth, boxHeight}, texture, renderer);
|
||||
|
||||
fixedWidth = false;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Text::~Text() {
|
||||
delete sprite;
|
||||
if (texture != nullptr) {
|
||||
delete texture;
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe texto en pantalla
|
||||
void Text::write(int x, int y, std::string text, int kerning, int lenght) {
|
||||
int shift = 0;
|
||||
|
||||
if (lenght == -1) {
|
||||
lenght = text.length();
|
||||
}
|
||||
|
||||
sprite->setPosY(y);
|
||||
const int width = sprite->getWidth();
|
||||
const int height = sprite->getHeight();
|
||||
for (int i = 0; i < lenght; ++i) {
|
||||
const int index = text[i];
|
||||
sprite->setSpriteClip(offset[index].x, offset[index].y, width, height);
|
||||
sprite->setPosX(x + shift);
|
||||
sprite->render();
|
||||
shift += fixedWidth ? boxWidth : (offset[int(text[i])].w + kerning);
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe el texto con colores
|
||||
void Text::writeColored(int x, int y, std::string text, color_t color, int kerning, int lenght) {
|
||||
sprite->getTexture()->setColor(color.r, color.g, color.b);
|
||||
write(x, y, text, kerning, lenght);
|
||||
sprite->getTexture()->setColor(255, 255, 255);
|
||||
}
|
||||
|
||||
// Escribe el texto con sombra
|
||||
void Text::writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance, int kerning, int lenght) {
|
||||
sprite->getTexture()->setColor(color.r, color.g, color.b);
|
||||
write(x + shadowDistance, y + shadowDistance, text, kerning, lenght);
|
||||
sprite->getTexture()->setColor(255, 255, 255);
|
||||
write(x, y, text, kerning, lenght);
|
||||
}
|
||||
|
||||
// Escribe el texto centrado en un punto x
|
||||
void Text::writeCentered(int x, int y, std::string text, int kerning, int lenght) {
|
||||
x -= (Text::lenght(text, kerning) / 2);
|
||||
write(x, y, text, kerning, lenght);
|
||||
}
|
||||
|
||||
// Escribe texto con extras
|
||||
void Text::writeDX(Uint8 flags, int x, int y, std::string text, int kerning, color_t textColor, Uint8 shadowDistance, color_t shadowColor, int lenght) {
|
||||
const bool centered = ((flags & TXT_CENTER) == TXT_CENTER);
|
||||
const bool shadowed = ((flags & TXT_SHADOW) == TXT_SHADOW);
|
||||
const bool colored = ((flags & TXT_COLOR) == TXT_COLOR);
|
||||
const bool stroked = ((flags & TXT_STROKE) == TXT_STROKE);
|
||||
|
||||
if (centered) {
|
||||
x -= (Text::lenght(text, kerning) / 2);
|
||||
}
|
||||
|
||||
if (shadowed) {
|
||||
writeColored(x + shadowDistance, y + shadowDistance, text, shadowColor, kerning, lenght);
|
||||
}
|
||||
|
||||
if (stroked) {
|
||||
for (int dist = 1; dist <= shadowDistance; ++dist) {
|
||||
for (int dy = -dist; dy <= dist; ++dy) {
|
||||
for (int dx = -dist; dx <= dist; ++dx) {
|
||||
writeColored(x + dx, y + dy, text, shadowColor, kerning, lenght);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (colored) {
|
||||
writeColored(x, y, text, textColor, kerning, lenght);
|
||||
} else {
|
||||
write(x, y, text, kerning, lenght);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene la longitud en pixels de una cadena
|
||||
int Text::lenght(std::string text, int kerning) {
|
||||
int shift = 0;
|
||||
|
||||
for (int i = 0; i < (int)text.length(); ++i)
|
||||
shift += (offset[int(text[i])].w + kerning);
|
||||
|
||||
// Descuenta el kerning del último caracter
|
||||
return shift - kerning;
|
||||
}
|
||||
|
||||
// Devuelve el valor de la variable
|
||||
int Text::getCharacterSize() {
|
||||
return boxWidth;
|
||||
}
|
||||
|
||||
// Recarga la textura
|
||||
void Text::reLoadTexture() {
|
||||
sprite->getTexture()->reLoad();
|
||||
}
|
||||
|
||||
// Establece si se usa un tamaño fijo de letra
|
||||
void Text::setFixedWidth(bool value) {
|
||||
fixedWidth = value;
|
||||
}
|
||||
87
source/core/rendering/text.h
Normal file
87
source/core/rendering/text.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string> // for string
|
||||
#include <vector>
|
||||
class Sprite;
|
||||
class Texture;
|
||||
#include "utils/utils.h"
|
||||
|
||||
// Opciones de texto
|
||||
constexpr int TXT_COLOR = 1;
|
||||
constexpr int TXT_SHADOW = 2;
|
||||
constexpr int TXT_CENTER = 4;
|
||||
constexpr int TXT_STROKE = 8;
|
||||
|
||||
struct offset_t {
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
};
|
||||
|
||||
struct textFile_t {
|
||||
int boxWidth; // Anchura de la caja de cada caracter en el png
|
||||
int boxHeight; // Altura de la caja de cada caracter en el png
|
||||
offset_t offset[128]; // Vector con las posiciones y ancho de cada letra
|
||||
};
|
||||
|
||||
// Llena una estructuta textFile_t desde un fichero
|
||||
textFile_t LoadTextFile(std::string file, bool verbose = false);
|
||||
|
||||
// Llena una estructura textFile_t desde bytes en memoria
|
||||
textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose = false);
|
||||
|
||||
// Clase texto. Pinta texto en pantalla a partir de un bitmap
|
||||
class Text {
|
||||
private:
|
||||
// Objetos y punteros
|
||||
Sprite *sprite; // Objeto con los graficos para el texto
|
||||
Texture *texture; // Textura con los bitmaps del texto
|
||||
|
||||
// Variables
|
||||
int boxWidth; // Anchura de la caja de cada caracter en el png
|
||||
int boxHeight; // Altura de la caja de cada caracter en el png
|
||||
bool fixedWidth; // Indica si el texto se ha de escribir con longitud fija en todas las letras
|
||||
offset_t offset[128]; // Vector con las posiciones y ancho de cada letra
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Text(std::string bitmapFile, std::string textFile, SDL_Renderer *renderer);
|
||||
Text(std::string textFile, Texture *texture, SDL_Renderer *renderer);
|
||||
Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer);
|
||||
|
||||
// Constructor desde bytes en memoria: comparte ownership del texture (no lo libera)
|
||||
Text(const std::vector<uint8_t> &pngBytes, const std::vector<uint8_t> &txtBytes, SDL_Renderer *renderer);
|
||||
|
||||
// Destructor
|
||||
~Text();
|
||||
|
||||
// Escribe el texto en pantalla
|
||||
void write(int x, int y, std::string text, int kerning = 1, int lenght = -1);
|
||||
|
||||
// Escribe el texto con colores
|
||||
void writeColored(int x, int y, std::string text, color_t color, int kerning = 1, int lenght = -1);
|
||||
|
||||
// Escribe el texto con sombra
|
||||
void writeShadowed(int x, int y, std::string text, color_t color, Uint8 shadowDistance = 1, int kerning = 1, int lenght = -1);
|
||||
|
||||
// Escribe el texto centrado en un punto x
|
||||
void writeCentered(int x, int y, std::string text, int kerning = 1, int lenght = -1);
|
||||
|
||||
// Escribe texto con extras
|
||||
void writeDX(Uint8 flags, int x, int y, std::string text, int kerning = 1, color_t textColor = color_t(255, 255, 255), Uint8 shadowDistance = 1, color_t shadowColor = color_t(0, 0, 0), int lenght = -1);
|
||||
|
||||
// Obtiene la longitud en pixels de una cadena
|
||||
int lenght(std::string text, int kerning = 1);
|
||||
|
||||
// Devuelve el valor de la variable
|
||||
int getCharacterSize();
|
||||
|
||||
// Recarga la textura
|
||||
void reLoadTexture();
|
||||
|
||||
// Establece si se usa un tamaño fijo de letra
|
||||
void setFixedWidth(bool value);
|
||||
};
|
||||
212
source/core/rendering/texture.cpp
Normal file
212
source/core/rendering/texture.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
|
||||
#include "core/rendering/texture.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdlib.h> // for exit
|
||||
|
||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "external/stb_image.h" // for stbi_failure_reason, stbi_image_free
|
||||
|
||||
SDL_ScaleMode Texture::currentScaleMode = SDL_SCALEMODE_NEAREST;
|
||||
|
||||
void Texture::setGlobalScaleMode(SDL_ScaleMode mode) {
|
||||
currentScaleMode = mode;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Texture::Texture(SDL_Renderer *renderer, std::string path, bool verbose) {
|
||||
// Copia punteros
|
||||
this->renderer = renderer;
|
||||
this->path = path;
|
||||
|
||||
// Inicializa
|
||||
texture = nullptr;
|
||||
width = 0;
|
||||
height = 0;
|
||||
|
||||
// Carga el fichero en la textura
|
||||
if (path != "") {
|
||||
loadFromFile(path, renderer, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor desde bytes
|
||||
Texture::Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose) {
|
||||
this->renderer = renderer;
|
||||
this->path = "";
|
||||
texture = nullptr;
|
||||
width = 0;
|
||||
height = 0;
|
||||
|
||||
if (!bytes.empty()) {
|
||||
loadFromMemory(bytes.data(), bytes.size(), renderer, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Texture::~Texture() {
|
||||
// Libera memoria
|
||||
unload();
|
||||
}
|
||||
|
||||
// Helper: convierte píxeles RGBA decodificados por stbi en SDL_Texture
|
||||
static SDL_Texture *createTextureFromPixels(SDL_Renderer *renderer, unsigned char *data, int w, int h, int *out_w, int *out_h) {
|
||||
const int pitch = 4 * w;
|
||||
SDL_Surface *loadedSurface = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_RGBA32, (void *)data, pitch);
|
||||
if (loadedSurface == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
SDL_Texture *newTexture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
|
||||
if (newTexture != nullptr) {
|
||||
*out_w = loadedSurface->w;
|
||||
*out_h = loadedSurface->h;
|
||||
SDL_SetTextureScaleMode(newTexture, Texture::currentScaleMode);
|
||||
}
|
||||
SDL_DestroySurface(loadedSurface);
|
||||
return newTexture;
|
||||
}
|
||||
|
||||
// Carga una imagen desde un fichero
|
||||
bool Texture::loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose) {
|
||||
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
||||
int req_format = STBI_rgb_alpha;
|
||||
int w, h, orig_format;
|
||||
unsigned char *data = stbi_load(path.c_str(), &w, &h, &orig_format, req_format);
|
||||
if (data == nullptr) {
|
||||
SDL_Log("Loading image failed: %s", stbi_failure_reason());
|
||||
exit(1);
|
||||
} else if (verbose) {
|
||||
std::cout << "Image loaded: " << filename.c_str() << std::endl;
|
||||
}
|
||||
|
||||
unload();
|
||||
SDL_Texture *newTexture = createTextureFromPixels(renderer, data, w, h, &this->width, &this->height);
|
||||
if (newTexture == nullptr && verbose) {
|
||||
std::cout << "Unable to load image " << path.c_str() << std::endl;
|
||||
}
|
||||
|
||||
stbi_image_free(data);
|
||||
texture = newTexture;
|
||||
return texture != nullptr;
|
||||
}
|
||||
|
||||
// Carga una imagen desde bytes en memoria
|
||||
bool Texture::loadFromMemory(const uint8_t *data, size_t size, SDL_Renderer *renderer, bool verbose) {
|
||||
int w, h, orig_format;
|
||||
unsigned char *pixels = stbi_load_from_memory(data, (int)size, &w, &h, &orig_format, STBI_rgb_alpha);
|
||||
if (pixels == nullptr) {
|
||||
SDL_Log("Loading image from memory failed: %s", stbi_failure_reason());
|
||||
return false;
|
||||
}
|
||||
|
||||
unload();
|
||||
SDL_Texture *newTexture = createTextureFromPixels(renderer, pixels, w, h, &this->width, &this->height);
|
||||
if (newTexture == nullptr && verbose) {
|
||||
std::cout << "Unable to create texture from memory" << std::endl;
|
||||
}
|
||||
|
||||
stbi_image_free(pixels);
|
||||
texture = newTexture;
|
||||
return texture != nullptr;
|
||||
}
|
||||
|
||||
// Crea una textura en blanco
|
||||
bool Texture::createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess access) {
|
||||
// Crea una textura sin inicializar
|
||||
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, access, width, height);
|
||||
if (texture == nullptr) {
|
||||
std::cout << "Unable to create blank texture! SDL Error: " << SDL_GetError() << std::endl;
|
||||
} else {
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
SDL_SetTextureScaleMode(texture, currentScaleMode);
|
||||
}
|
||||
|
||||
return texture != nullptr;
|
||||
}
|
||||
|
||||
// Libera la memoria de la textura
|
||||
void Texture::unload() {
|
||||
// Libera la textura si existe
|
||||
if (texture != nullptr) {
|
||||
SDL_DestroyTexture(texture);
|
||||
texture = nullptr;
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el color para la modulacion
|
||||
void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) {
|
||||
SDL_SetTextureColorMod(texture, red, green, blue);
|
||||
}
|
||||
|
||||
// Establece el blending
|
||||
void Texture::setBlendMode(SDL_BlendMode blending) {
|
||||
SDL_SetTextureBlendMode(texture, blending);
|
||||
}
|
||||
|
||||
// Establece el alpha para la modulación
|
||||
void Texture::setAlpha(Uint8 alpha) {
|
||||
SDL_SetTextureAlphaMod(texture, alpha);
|
||||
}
|
||||
|
||||
// Renderiza la textura en un punto específico
|
||||
void Texture::render(SDL_Renderer *renderer, int x, int y, SDL_Rect *clip, float zoomW, float zoomH, double angle, SDL_Point *center, SDL_FlipMode flip) {
|
||||
// Establece el destino de renderizado en la pantalla
|
||||
SDL_FRect renderQuad = {(float)x, (float)y, (float)width, (float)height};
|
||||
|
||||
// Obtiene las dimesiones del clip de renderizado
|
||||
if (clip != nullptr) {
|
||||
renderQuad.w = (float)clip->w;
|
||||
renderQuad.h = (float)clip->h;
|
||||
}
|
||||
|
||||
renderQuad.w = renderQuad.w * zoomW;
|
||||
renderQuad.h = renderQuad.h * zoomH;
|
||||
|
||||
// Convierte el clip a SDL_FRect
|
||||
SDL_FRect srcRect;
|
||||
SDL_FRect *srcRectPtr = nullptr;
|
||||
if (clip != nullptr) {
|
||||
srcRect = {(float)clip->x, (float)clip->y, (float)clip->w, (float)clip->h};
|
||||
srcRectPtr = &srcRect;
|
||||
}
|
||||
|
||||
// Convierte el centro a SDL_FPoint
|
||||
SDL_FPoint fCenter;
|
||||
SDL_FPoint *fCenterPtr = nullptr;
|
||||
if (center != nullptr) {
|
||||
fCenter = {(float)center->x, (float)center->y};
|
||||
fCenterPtr = &fCenter;
|
||||
}
|
||||
|
||||
// Renderiza a pantalla
|
||||
SDL_RenderTextureRotated(renderer, texture, srcRectPtr, &renderQuad, angle, fCenterPtr, flip);
|
||||
}
|
||||
|
||||
// Establece la textura como objetivo de renderizado
|
||||
void Texture::setAsRenderTarget(SDL_Renderer *renderer) {
|
||||
SDL_SetRenderTarget(renderer, texture);
|
||||
}
|
||||
|
||||
// Obtiene el ancho de la imagen
|
||||
int Texture::getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
// Obtiene el alto de la imagen
|
||||
int Texture::getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
// Recarga la textura
|
||||
bool Texture::reLoad() {
|
||||
return loadFromFile(path, renderer);
|
||||
}
|
||||
|
||||
// Obtiene la textura
|
||||
SDL_Texture *Texture::getSDLTexture() {
|
||||
return texture;
|
||||
}
|
||||
73
source/core/rendering/texture.h
Normal file
73
source/core/rendering/texture.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string> // for basic_string, string
|
||||
#include <vector>
|
||||
|
||||
class Texture {
|
||||
private:
|
||||
// Objetos y punteros
|
||||
SDL_Texture *texture; // La textura
|
||||
SDL_Renderer *renderer; // Renderizador donde dibujar la textura
|
||||
|
||||
// Variables
|
||||
int width; // Ancho de la imagen
|
||||
int height; // Alto de la imagen
|
||||
std::string path; // Ruta de la imagen de la textura
|
||||
|
||||
public:
|
||||
static SDL_ScaleMode currentScaleMode; // Modo de escalado global para nuevas texturas
|
||||
|
||||
// Establece el modo de escalado global para nuevas texturas
|
||||
static void setGlobalScaleMode(SDL_ScaleMode mode);
|
||||
|
||||
// Constructor
|
||||
Texture(SDL_Renderer *renderer, std::string path = "", bool verbose = false);
|
||||
|
||||
// Constructor desde bytes (PNG en memoria)
|
||||
Texture(SDL_Renderer *renderer, const std::vector<uint8_t> &bytes, bool verbose = false);
|
||||
|
||||
// Destructor
|
||||
~Texture();
|
||||
|
||||
// Carga una imagen desde un fichero
|
||||
bool loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose = false);
|
||||
|
||||
// Carga una imagen desde bytes en memoria
|
||||
bool loadFromMemory(const uint8_t *data, size_t size, SDL_Renderer *renderer, bool verbose = false);
|
||||
|
||||
// Crea una textura en blanco
|
||||
bool createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess = SDL_TEXTUREACCESS_STREAMING);
|
||||
|
||||
// Libera la memoria de la textura
|
||||
void unload();
|
||||
|
||||
// Establece el color para la modulacion
|
||||
void setColor(Uint8 red, Uint8 green, Uint8 blue);
|
||||
|
||||
// Establece el blending
|
||||
void setBlendMode(SDL_BlendMode blending);
|
||||
|
||||
// Establece el alpha para la modulación
|
||||
void setAlpha(Uint8 alpha);
|
||||
|
||||
// Renderiza la textura en un punto específico
|
||||
void render(SDL_Renderer *renderer, int x, int y, SDL_Rect *clip = nullptr, float zoomW = 1, float zoomH = 1, double angle = 0.0, SDL_Point *center = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
|
||||
|
||||
// Establece la textura como objetivo de renderizado
|
||||
void setAsRenderTarget(SDL_Renderer *renderer);
|
||||
|
||||
// Obtiene el ancho de la imagen
|
||||
int getWidth();
|
||||
|
||||
// Obtiene el alto de la imagen
|
||||
int getHeight();
|
||||
|
||||
// Recarga la textura
|
||||
bool reLoad();
|
||||
|
||||
// Obtiene la textura
|
||||
SDL_Texture *getSDLTexture();
|
||||
};
|
||||
115
source/core/rendering/writer.cpp
Normal file
115
source/core/rendering/writer.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "core/rendering/writer.h"
|
||||
|
||||
#include "core/rendering/text.h" // for Text
|
||||
|
||||
// Constructor
|
||||
Writer::Writer(Text *text) {
|
||||
// Copia los punteros
|
||||
this->text = text;
|
||||
|
||||
// Inicializa variables
|
||||
posX = 0;
|
||||
posY = 0;
|
||||
kerning = 0;
|
||||
caption = "";
|
||||
speed = 0;
|
||||
writingCounter = 0;
|
||||
index = 0;
|
||||
lenght = 0;
|
||||
completed = false;
|
||||
enabled = false;
|
||||
enabledCounter = 0;
|
||||
finished = false;
|
||||
}
|
||||
|
||||
// Actualiza el objeto
|
||||
void Writer::update() {
|
||||
if (enabled) {
|
||||
if (!completed) { // No completado
|
||||
if (writingCounter > 0) {
|
||||
writingCounter--;
|
||||
}
|
||||
|
||||
else if (writingCounter == 0) {
|
||||
index++;
|
||||
writingCounter = speed;
|
||||
}
|
||||
|
||||
if (index == lenght) {
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (completed) { // Completado
|
||||
if (enabledCounter > 0) {
|
||||
enabledCounter--;
|
||||
} else if (enabledCounter == 0) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el objeto en pantalla
|
||||
void Writer::render() {
|
||||
if (enabled) {
|
||||
text->write(posX, posY, caption, kerning, index);
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setPosX(int value) {
|
||||
posX = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setPosY(int value) {
|
||||
posY = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setKerning(int value) {
|
||||
kerning = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setCaption(std::string text) {
|
||||
caption = text;
|
||||
lenght = text.length();
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setSpeed(int value) {
|
||||
speed = value;
|
||||
writingCounter = value;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setEnabled(bool value) {
|
||||
enabled = value;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool Writer::IsEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void Writer::setEnabledCounter(int time) {
|
||||
enabledCounter = time;
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
int Writer::getEnabledCounter() {
|
||||
return enabledCounter;
|
||||
}
|
||||
|
||||
// Centra la cadena de texto a un punto X
|
||||
void Writer::center(int x) {
|
||||
setPosX(x - (text->lenght(caption, kerning) / 2));
|
||||
}
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool Writer::hasFinished() {
|
||||
return finished;
|
||||
}
|
||||
68
source/core/rendering/writer.h
Normal file
68
source/core/rendering/writer.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // for string, basic_string
|
||||
class Text;
|
||||
|
||||
// Clase Writer. Pinta texto en pantalla letra a letra a partir de una cadena y un bitmap
|
||||
class Writer {
|
||||
private:
|
||||
// Objetos y punteros
|
||||
Text *text; // Objeto encargado de escribir el texto
|
||||
|
||||
// Variables
|
||||
int posX; // Posicion en el eje X donde empezar a escribir el texto
|
||||
int posY; // Posicion en el eje Y donde empezar a escribir el texto
|
||||
int kerning; // Kerning del texto, es decir, espaciado entre caracteres
|
||||
std::string caption; // El texto para escribir
|
||||
int speed; // Velocidad de escritura
|
||||
int writingCounter; // Temporizador de escritura para cada caracter
|
||||
int index; // Posición del texto que se está escribiendo
|
||||
int lenght; // Longitud de la cadena a escribir
|
||||
bool completed; // Indica si se ha escrito todo el texto
|
||||
bool enabled; // Indica si el objeto está habilitado
|
||||
int enabledCounter; // Temporizador para deshabilitar el objeto
|
||||
bool finished; // Indica si ya ha terminado
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Writer(Text *text);
|
||||
|
||||
// Actualiza el objeto
|
||||
void update();
|
||||
|
||||
// Dibuja el objeto en pantalla
|
||||
void render();
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setPosX(int value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setPosY(int value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setKerning(int value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setCaption(std::string text);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setSpeed(int value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setEnabled(bool value);
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool IsEnabled();
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setEnabledCounter(int time);
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
int getEnabledCounter();
|
||||
|
||||
// Centra la cadena de texto a un punto X
|
||||
void center(int x);
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
bool hasFinished();
|
||||
};
|
||||
178
source/core/resources/asset.cpp
Normal file
178
source/core/resources/asset.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "core/resources/asset.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stddef.h> // for size_t
|
||||
|
||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||
|
||||
#include "core/resources/resource_helper.h"
|
||||
|
||||
// Constructor
|
||||
Asset::Asset(std::string executablePath) {
|
||||
this->executablePath = executablePath.substr(0, executablePath.find_last_of("\\/"));
|
||||
longestName = 0;
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
// Añade un elemento a la lista
|
||||
void Asset::add(std::string file, enum assetType type, bool required, bool absolute) {
|
||||
item_t temp;
|
||||
temp.file = absolute ? file : executablePath + file;
|
||||
temp.type = type;
|
||||
temp.required = required;
|
||||
fileList.push_back(temp);
|
||||
|
||||
const std::string filename = file.substr(file.find_last_of("\\/") + 1);
|
||||
longestName = SDL_max(longestName, filename.size());
|
||||
}
|
||||
|
||||
// Devuelve el fichero de un elemento de la lista a partir de una cadena
|
||||
std::string Asset::get(std::string text) {
|
||||
for (auto f : fileList) {
|
||||
const size_t lastIndex = f.file.find_last_of("/") + 1;
|
||||
const std::string file = f.file.substr(lastIndex, std::string::npos);
|
||||
|
||||
if (file == text) {
|
||||
return f.file;
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Warning: file " << text.c_str() << " not found" << std::endl;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Comprueba que existen todos los elementos
|
||||
bool Asset::check() {
|
||||
bool success = true;
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "\n** Checking files" << std::endl;
|
||||
|
||||
std::cout << "Executable path is: " << executablePath << std::endl;
|
||||
std::cout << "Sample filepath: " << fileList.back().file << std::endl;
|
||||
}
|
||||
|
||||
// Comprueba la lista de ficheros clasificandolos por tipo
|
||||
for (int type = 0; type < t_maxAssetType; ++type) {
|
||||
// Comprueba si hay ficheros de ese tipo
|
||||
bool any = false;
|
||||
|
||||
for (auto f : fileList) {
|
||||
if ((f.required) && (f.type == type)) {
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Si hay ficheros de ese tipo, comprueba si existen
|
||||
if (any) {
|
||||
if (verbose) {
|
||||
std::cout << "\n>> " << getTypeName(type).c_str() << " FILES" << std::endl;
|
||||
}
|
||||
|
||||
for (auto f : fileList) {
|
||||
if ((f.required) && (f.type == type)) {
|
||||
success &= checkFile(f.file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resultado
|
||||
if (verbose) {
|
||||
if (success) {
|
||||
std::cout << "\n** All files OK.\n"
|
||||
<< std::endl;
|
||||
} else {
|
||||
std::cout << "\n** A file is missing. Exiting.\n"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Comprueba que existe un fichero
|
||||
bool Asset::checkFile(std::string path) {
|
||||
bool success = false;
|
||||
std::string result = "ERROR";
|
||||
|
||||
// Comprueba si existe el fichero (pack o filesystem)
|
||||
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
||||
if (ResourceHelper::shouldUseResourcePack(path)) {
|
||||
auto bytes = ResourceHelper::loadFile(path);
|
||||
if (!bytes.empty()) {
|
||||
result = "OK";
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
SDL_IOStream *file = SDL_IOFromFile(path.c_str(), "rb");
|
||||
if (file != nullptr) {
|
||||
result = "OK";
|
||||
success = true;
|
||||
SDL_CloseIO(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
std::cout.setf(std::ios::left, std::ios::adjustfield);
|
||||
std::cout << "Checking file: ";
|
||||
std::cout.width(longestName + 2);
|
||||
std::cout.fill('.');
|
||||
std::cout << filename + " ";
|
||||
std::cout << " [" + result + "]" << std::endl;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Devuelve el nombre del tipo de recurso
|
||||
std::string Asset::getTypeName(int type) {
|
||||
switch (type) {
|
||||
case t_bitmap:
|
||||
return "BITMAP";
|
||||
break;
|
||||
|
||||
case t_music:
|
||||
return "MUSIC";
|
||||
break;
|
||||
|
||||
case t_sound:
|
||||
return "SOUND";
|
||||
break;
|
||||
|
||||
case t_font:
|
||||
return "FONT";
|
||||
break;
|
||||
|
||||
case t_lang:
|
||||
return "LANG";
|
||||
break;
|
||||
|
||||
case t_data:
|
||||
return "DATA";
|
||||
break;
|
||||
|
||||
case t_room:
|
||||
return "ROOM";
|
||||
break;
|
||||
|
||||
case t_enemy:
|
||||
return "ENEMY";
|
||||
break;
|
||||
|
||||
case t_item:
|
||||
return "ITEM";
|
||||
break;
|
||||
|
||||
default:
|
||||
return "ERROR";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece si ha de mostrar texto por pantalla
|
||||
void Asset::setVerbose(bool value) {
|
||||
verbose = value;
|
||||
}
|
||||
60
source/core/resources/asset.h
Normal file
60
source/core/resources/asset.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
enum assetType {
|
||||
t_bitmap,
|
||||
t_music,
|
||||
t_sound,
|
||||
t_font,
|
||||
t_lang,
|
||||
t_data,
|
||||
t_room,
|
||||
t_enemy,
|
||||
t_item,
|
||||
t_maxAssetType
|
||||
};
|
||||
|
||||
// Clase Asset
|
||||
class Asset {
|
||||
public:
|
||||
// Estructura para definir un item
|
||||
struct item_t {
|
||||
std::string file; // Ruta del fichero desde la raiz del directorio
|
||||
enum assetType type; // Indica el tipo de recurso
|
||||
bool required; // Indica si es un fichero que debe de existir
|
||||
};
|
||||
|
||||
private:
|
||||
// Variables
|
||||
int longestName; // Contiene la longitud del nombre de fichero mas largo
|
||||
std::vector<item_t> fileList; // Listado con todas las rutas a los ficheros
|
||||
std::string executablePath; // Ruta al ejecutable
|
||||
bool verbose; // Indica si ha de mostrar información por pantalla
|
||||
|
||||
// Comprueba que existe un fichero
|
||||
bool checkFile(std::string executablePath);
|
||||
|
||||
// Devuelve el nombre del tipo de recurso
|
||||
std::string getTypeName(int type);
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Asset(std::string path);
|
||||
|
||||
// Añade un elemento a la lista
|
||||
void add(std::string file, enum assetType type, bool required = true, bool absolute = false);
|
||||
|
||||
// Devuelve un elemento de la lista a partir de una cadena
|
||||
std::string get(std::string text);
|
||||
|
||||
// Devuelve toda la lista de items registrados
|
||||
const std::vector<item_t> &getAll() const { return fileList; }
|
||||
|
||||
// Comprueba que existen todos los elementos
|
||||
bool check();
|
||||
|
||||
// Establece si ha de mostrar texto por pantalla
|
||||
void setVerbose(bool value);
|
||||
};
|
||||
218
source/core/resources/resource.cpp
Normal file
218
source/core/resources/resource.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#include "core/resources/resource.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "core/audio/jail_audio.hpp"
|
||||
#include "core/rendering/text.h"
|
||||
#include "core/rendering/texture.h"
|
||||
#include "core/resources/asset.h"
|
||||
#include "core/resources/resource_helper.h"
|
||||
#include "game/ui/menu.h"
|
||||
|
||||
Resource *Resource::instance_ = nullptr;
|
||||
|
||||
static std::string basename(const std::string &path) {
|
||||
return path.substr(path.find_last_of("\\/") + 1);
|
||||
}
|
||||
|
||||
static std::string stem(const std::string &path) {
|
||||
std::string b = basename(path);
|
||||
size_t dot = b.find_last_of('.');
|
||||
if (dot == std::string::npos) return b;
|
||||
return b.substr(0, dot);
|
||||
}
|
||||
|
||||
void Resource::init(SDL_Renderer *renderer, Asset *asset, Input *input) {
|
||||
if (instance_ == nullptr) {
|
||||
instance_ = new Resource(renderer, asset, input);
|
||||
instance_->preloadAll();
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::destroy() {
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
Resource *Resource::get() {
|
||||
return instance_;
|
||||
}
|
||||
|
||||
Resource::Resource(SDL_Renderer *renderer, Asset *asset, Input *input)
|
||||
: renderer_(renderer),
|
||||
asset_(asset),
|
||||
input_(input) {}
|
||||
|
||||
Resource::~Resource() {
|
||||
for (auto &[name, m] : menus_) delete m;
|
||||
menus_.clear();
|
||||
|
||||
for (auto &[name, t] : texts_) delete t;
|
||||
texts_.clear();
|
||||
|
||||
for (auto &[name, t] : textures_) delete t;
|
||||
textures_.clear();
|
||||
|
||||
for (auto &[name, s] : sounds_) JA_DeleteSound(s);
|
||||
sounds_.clear();
|
||||
|
||||
for (auto &[name, m] : musics_) JA_DeleteMusic(m);
|
||||
musics_.clear();
|
||||
}
|
||||
|
||||
void Resource::preloadAll() {
|
||||
const auto &items = asset_->getAll();
|
||||
|
||||
// Pass 1: texturas, sonidos, músicas, animaciones (raw lines), demo, lenguajes
|
||||
for (const auto &it : items) {
|
||||
if (!ResourceHelper::shouldUseResourcePack(it.file) && it.type != t_lang) {
|
||||
// Ficheros absolutos (config.txt, score.bin, systemFolder) — no se precargan
|
||||
continue;
|
||||
}
|
||||
auto bytes = ResourceHelper::loadFile(it.file);
|
||||
if (bytes.empty()) continue;
|
||||
|
||||
const std::string bname = basename(it.file);
|
||||
|
||||
switch (it.type) {
|
||||
case t_bitmap: {
|
||||
auto *tex = new Texture(renderer_, bytes);
|
||||
textures_[bname] = tex;
|
||||
break;
|
||||
}
|
||||
case t_sound: {
|
||||
JA_Sound_t *s = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
|
||||
if (s) sounds_[bname] = s;
|
||||
break;
|
||||
}
|
||||
case t_music: {
|
||||
JA_Music_t *m = JA_LoadMusic(bytes.data(), (Uint32)bytes.size());
|
||||
if (m) musics_[bname] = m;
|
||||
break;
|
||||
}
|
||||
case t_data: {
|
||||
if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".ani") {
|
||||
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||
std::stringstream ss(content);
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
while (std::getline(ss, line)) {
|
||||
lines.push_back(line);
|
||||
}
|
||||
animationLines_[bname] = std::move(lines);
|
||||
} else if (bname == "demo.bin") {
|
||||
demoBytes_ = bytes;
|
||||
} else if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".men") {
|
||||
// Menús: se construyen en pass 2 porque dependen de textos y sonidos
|
||||
}
|
||||
break;
|
||||
}
|
||||
case t_font:
|
||||
// Fonts: se emparejan en pass 2
|
||||
break;
|
||||
case t_lang:
|
||||
// Lenguaje: lo sigue leyendo la clase Lang a través de ResourceHelper
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: Text (fuentes emparejadas png+txt) y Menus (dependen de Text+sonidos)
|
||||
// Fuentes: construimos un Text por cada par basename.png + basename.txt
|
||||
// Acumulamos los bytes encontrados por stem (basename sin ext.)
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> fontPngs;
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> fontTxts;
|
||||
for (const auto &it : items) {
|
||||
if (it.type != t_font) continue;
|
||||
auto bytes = ResourceHelper::loadFile(it.file);
|
||||
if (bytes.empty()) continue;
|
||||
const std::string s = stem(it.file);
|
||||
const std::string bname = basename(it.file);
|
||||
if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".png") {
|
||||
fontPngs[s] = std::move(bytes);
|
||||
} else if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".txt") {
|
||||
fontTxts[s] = std::move(bytes);
|
||||
}
|
||||
}
|
||||
for (const auto &[s, png] : fontPngs) {
|
||||
auto itTxt = fontTxts.find(s);
|
||||
if (itTxt == fontTxts.end()) continue;
|
||||
Text *t = new Text(png, itTxt->second, renderer_);
|
||||
texts_[s] = t;
|
||||
}
|
||||
|
||||
// Menus: usan aún Menu::loadFromBytes que internamente llama a asset->get() y
|
||||
// Text/JA_LoadSound por path. Funciona en modo fallback; en pack estricto
|
||||
// requiere que Menu se adapte a cargar desde ResourceHelper. Por ahora
|
||||
// lo dejamos así y será una migración del paso 7.
|
||||
for (const auto &it : items) {
|
||||
if (it.type != t_data) continue;
|
||||
const std::string bname = basename(it.file);
|
||||
if (bname.size() < 4 || bname.substr(bname.size() - 4) != ".men") continue;
|
||||
auto bytes = ResourceHelper::loadFile(it.file);
|
||||
if (bytes.empty()) continue;
|
||||
Menu *m = new Menu(renderer_, asset_, input_, "");
|
||||
m->loadFromBytes(bytes, bname);
|
||||
const std::string s = stem(it.file);
|
||||
menus_[s] = m;
|
||||
}
|
||||
}
|
||||
|
||||
Texture *Resource::getTexture(const std::string &name) {
|
||||
auto it = textures_.find(name);
|
||||
if (it == textures_.end()) {
|
||||
std::cerr << "Resource::getTexture: missing " << name << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
JA_Sound_t *Resource::getSound(const std::string &name) {
|
||||
auto it = sounds_.find(name);
|
||||
if (it == sounds_.end()) {
|
||||
std::cerr << "Resource::getSound: missing " << name << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
JA_Music_t *Resource::getMusic(const std::string &name) {
|
||||
auto it = musics_.find(name);
|
||||
if (it == musics_.end()) {
|
||||
std::cerr << "Resource::getMusic: missing " << name << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::vector<std::string> &Resource::getAnimationLines(const std::string &name) {
|
||||
auto it = animationLines_.find(name);
|
||||
if (it == animationLines_.end()) {
|
||||
static std::vector<std::string> empty;
|
||||
std::cerr << "Resource::getAnimationLines: missing " << name << '\n';
|
||||
return empty;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Text *Resource::getText(const std::string &name) {
|
||||
auto it = texts_.find(name);
|
||||
if (it == texts_.end()) {
|
||||
std::cerr << "Resource::getText: missing " << name << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Menu *Resource::getMenu(const std::string &name) {
|
||||
auto it = menus_.find(name);
|
||||
if (it == menus_.end()) {
|
||||
std::cerr << "Resource::getMenu: missing " << name << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
53
source/core/resources/resource.h
Normal file
53
source/core/resources/resource.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class Asset;
|
||||
class Input;
|
||||
class Menu;
|
||||
class Text;
|
||||
class Texture;
|
||||
struct JA_Music_t;
|
||||
struct JA_Sound_t;
|
||||
|
||||
// Precarga y posee todos los recursos del juego durante toda la vida de la app.
|
||||
// Singleton inicializado desde Director; las escenas consultan handles via get*().
|
||||
class Resource {
|
||||
public:
|
||||
static void init(SDL_Renderer *renderer, Asset *asset, Input *input);
|
||||
static void destroy();
|
||||
static Resource *get();
|
||||
|
||||
Texture *getTexture(const std::string &name);
|
||||
JA_Sound_t *getSound(const std::string &name);
|
||||
JA_Music_t *getMusic(const std::string &name);
|
||||
std::vector<std::string> &getAnimationLines(const std::string &name);
|
||||
Text *getText(const std::string &name); // name sin extensión: "smb2", "nokia2", ...
|
||||
Menu *getMenu(const std::string &name); // name sin extensión: "title", "options", ...
|
||||
const std::vector<uint8_t> &getDemoBytes() const { return demoBytes_; }
|
||||
|
||||
private:
|
||||
Resource(SDL_Renderer *renderer, Asset *asset, Input *input);
|
||||
~Resource();
|
||||
|
||||
void preloadAll();
|
||||
|
||||
SDL_Renderer *renderer_;
|
||||
Asset *asset_;
|
||||
Input *input_;
|
||||
|
||||
std::unordered_map<std::string, Texture *> textures_;
|
||||
std::unordered_map<std::string, JA_Sound_t *> sounds_;
|
||||
std::unordered_map<std::string, JA_Music_t *> musics_;
|
||||
std::unordered_map<std::string, std::vector<std::string>> animationLines_;
|
||||
std::unordered_map<std::string, Text *> texts_;
|
||||
std::unordered_map<std::string, Menu *> menus_;
|
||||
std::vector<uint8_t> demoBytes_;
|
||||
|
||||
static Resource *instance_;
|
||||
};
|
||||
77
source/core/resources/resource_helper.cpp
Normal file
77
source/core/resources/resource_helper.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "core/resources/resource_helper.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "core/resources/resource_loader.h"
|
||||
|
||||
namespace ResourceHelper {
|
||||
static bool resource_system_initialized = false;
|
||||
|
||||
bool initializeResourceSystem(const std::string& pack_file, bool enable_fallback) {
|
||||
auto& loader = ResourceLoader::getInstance();
|
||||
bool ok = loader.initialize(pack_file, enable_fallback);
|
||||
resource_system_initialized = ok;
|
||||
|
||||
if (ok && loader.getLoadedResourceCount() > 0) {
|
||||
std::cout << "Resource system initialized with pack: " << pack_file << '\n';
|
||||
} else if (ok) {
|
||||
std::cout << "Resource system using fallback mode (filesystem only)" << '\n';
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void shutdownResourceSystem() {
|
||||
if (resource_system_initialized) {
|
||||
ResourceLoader::getInstance().shutdown();
|
||||
resource_system_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> loadFile(const std::string& filepath) {
|
||||
if (resource_system_initialized && shouldUseResourcePack(filepath)) {
|
||||
auto& loader = ResourceLoader::getInstance();
|
||||
std::string pack_path = getPackPath(filepath);
|
||||
|
||||
auto data = loader.loadResource(pack_path);
|
||||
if (!data.empty()) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::streamsize file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> data(file_size);
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool shouldUseResourcePack(const std::string& filepath) {
|
||||
// Solo entran al pack los ficheros dentro de data/
|
||||
return filepath.find("data/") != std::string::npos;
|
||||
}
|
||||
|
||||
std::string getPackPath(const std::string& asset_path) {
|
||||
std::string pack_path = asset_path;
|
||||
std::replace(pack_path.begin(), pack_path.end(), '\\', '/');
|
||||
|
||||
// Toma la última aparición de "data/" como prefijo a quitar
|
||||
size_t last_data = pack_path.rfind("data/");
|
||||
if (last_data != std::string::npos) {
|
||||
pack_path = pack_path.substr(last_data + 5);
|
||||
}
|
||||
return pack_path;
|
||||
}
|
||||
} // namespace ResourceHelper
|
||||
15
source/core/resources/resource_helper.h
Normal file
15
source/core/resources/resource_helper.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ResourceHelper {
|
||||
bool initializeResourceSystem(const std::string& pack_file = "resources.pack", bool enable_fallback = true);
|
||||
void shutdownResourceSystem();
|
||||
|
||||
std::vector<uint8_t> loadFile(const std::string& filepath);
|
||||
|
||||
bool shouldUseResourcePack(const std::string& filepath);
|
||||
std::string getPackPath(const std::string& asset_path);
|
||||
} // namespace ResourceHelper
|
||||
133
source/core/resources/resource_loader.cpp
Normal file
133
source/core/resources/resource_loader.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "core/resources/resource_loader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "core/resources/resource_pack.h"
|
||||
|
||||
std::unique_ptr<ResourceLoader> ResourceLoader::instance = nullptr;
|
||||
|
||||
ResourceLoader::ResourceLoader()
|
||||
: resource_pack_(nullptr),
|
||||
fallback_to_files_(true) {}
|
||||
|
||||
ResourceLoader& ResourceLoader::getInstance() {
|
||||
if (!instance) {
|
||||
instance = std::unique_ptr<ResourceLoader>(new ResourceLoader());
|
||||
}
|
||||
return *instance;
|
||||
}
|
||||
|
||||
ResourceLoader::~ResourceLoader() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool ResourceLoader::initialize(const std::string& pack_file, bool enable_fallback) {
|
||||
shutdown();
|
||||
|
||||
fallback_to_files_ = enable_fallback;
|
||||
pack_path_ = pack_file;
|
||||
|
||||
if (std::filesystem::exists(pack_file)) {
|
||||
resource_pack_ = new ResourcePack();
|
||||
if (resource_pack_->loadPack(pack_file)) {
|
||||
return true;
|
||||
}
|
||||
delete resource_pack_;
|
||||
resource_pack_ = nullptr;
|
||||
std::cerr << "Failed to load resource pack: " << pack_file << '\n';
|
||||
}
|
||||
|
||||
if (fallback_to_files_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << "Resource pack not found and fallback disabled: " << pack_file << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
void ResourceLoader::shutdown() {
|
||||
if (resource_pack_ != nullptr) {
|
||||
delete resource_pack_;
|
||||
resource_pack_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ResourceLoader::loadResource(const std::string& filename) {
|
||||
if ((resource_pack_ != nullptr) && resource_pack_->hasResource(filename)) {
|
||||
return resource_pack_->getResource(filename);
|
||||
}
|
||||
|
||||
if (fallback_to_files_) {
|
||||
return loadFromFile(filename);
|
||||
}
|
||||
|
||||
std::cerr << "Resource not found: " << filename << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ResourceLoader::resourceExists(const std::string& filename) {
|
||||
if ((resource_pack_ != nullptr) && resource_pack_->hasResource(filename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fallback_to_files_) {
|
||||
std::string full_path = getDataPath(filename);
|
||||
return std::filesystem::exists(full_path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ResourceLoader::loadFromFile(const std::string& filename) {
|
||||
std::string full_path = getDataPath(filename);
|
||||
|
||||
std::ifstream file(full_path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
std::cerr << "Error: Could not open file: " << full_path << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
std::streamsize file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> data(file_size);
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
|
||||
std::cerr << "Error: Could not read file: " << full_path << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string ResourceLoader::getDataPath(const std::string& filename) {
|
||||
return "data/" + filename;
|
||||
}
|
||||
|
||||
size_t ResourceLoader::getLoadedResourceCount() const {
|
||||
if (resource_pack_ != nullptr) {
|
||||
return resource_pack_->getResourceCount();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<std::string> ResourceLoader::getAvailableResources() const {
|
||||
if (resource_pack_ != nullptr) {
|
||||
return resource_pack_->getResourceList();
|
||||
}
|
||||
|
||||
std::vector<std::string> result;
|
||||
if (fallback_to_files_ && std::filesystem::exists("data")) {
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator("data")) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::string filename = std::filesystem::relative(entry.path(), "data").string();
|
||||
std::replace(filename.begin(), filename.end(), '\\', '/');
|
||||
result.push_back(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
39
source/core/resources/resource_loader.h
Normal file
39
source/core/resources/resource_loader.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ResourcePack;
|
||||
|
||||
class ResourceLoader {
|
||||
private:
|
||||
static std::unique_ptr<ResourceLoader> instance;
|
||||
ResourcePack* resource_pack_;
|
||||
std::string pack_path_;
|
||||
bool fallback_to_files_;
|
||||
|
||||
ResourceLoader();
|
||||
|
||||
public:
|
||||
static ResourceLoader& getInstance();
|
||||
~ResourceLoader();
|
||||
|
||||
bool initialize(const std::string& pack_file, bool enable_fallback = true);
|
||||
void shutdown();
|
||||
|
||||
std::vector<uint8_t> loadResource(const std::string& filename);
|
||||
bool resourceExists(const std::string& filename);
|
||||
|
||||
void setFallbackToFiles(bool enable) { fallback_to_files_ = enable; }
|
||||
bool getFallbackToFiles() const { return fallback_to_files_; }
|
||||
|
||||
size_t getLoadedResourceCount() const;
|
||||
std::vector<std::string> getAvailableResources() const;
|
||||
|
||||
private:
|
||||
static std::vector<uint8_t> loadFromFile(const std::string& filename);
|
||||
static std::string getDataPath(const std::string& filename);
|
||||
};
|
||||
223
source/core/resources/resource_pack.cpp
Normal file
223
source/core/resources/resource_pack.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "core/resources/resource_pack.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCRS_RESOURCES__2026";
|
||||
|
||||
ResourcePack::ResourcePack()
|
||||
: loaded_(false) {}
|
||||
|
||||
ResourcePack::~ResourcePack() {
|
||||
clear();
|
||||
}
|
||||
|
||||
uint32_t ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) {
|
||||
uint32_t checksum = 0x12345678;
|
||||
for (unsigned char i : data) {
|
||||
checksum = ((checksum << 5) + checksum) + i;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
|
||||
if (key.empty()) return;
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
data[i] ^= key[i % key.length()];
|
||||
}
|
||||
}
|
||||
|
||||
void ResourcePack::decryptData(std::vector<uint8_t>& data, const std::string& key) {
|
||||
encryptData(data, key);
|
||||
}
|
||||
|
||||
bool ResourcePack::loadPack(const std::string& pack_file) {
|
||||
std::ifstream file(pack_file, std::ios::binary);
|
||||
if (!file) {
|
||||
std::cerr << "Error: Could not open pack file: " << pack_file << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<char, 4> header;
|
||||
file.read(header.data(), 4);
|
||||
if (std::string(header.data(), 4) != "CCRS") {
|
||||
std::cerr << "Error: Invalid pack file format" << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t version;
|
||||
file.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||
if (version != 1) {
|
||||
std::cerr << "Error: Unsupported pack version: " << version << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t resource_count;
|
||||
file.read(reinterpret_cast<char*>(&resource_count), sizeof(resource_count));
|
||||
|
||||
resources_.clear();
|
||||
resources_.reserve(resource_count);
|
||||
|
||||
for (uint32_t i = 0; i < resource_count; ++i) {
|
||||
uint32_t filename_length;
|
||||
file.read(reinterpret_cast<char*>(&filename_length), sizeof(filename_length));
|
||||
|
||||
std::string filename(filename_length, '\0');
|
||||
file.read(filename.data(), filename_length);
|
||||
|
||||
ResourceEntry entry;
|
||||
entry.filename = filename;
|
||||
file.read(reinterpret_cast<char*>(&entry.offset), sizeof(entry.offset));
|
||||
file.read(reinterpret_cast<char*>(&entry.size), sizeof(entry.size));
|
||||
file.read(reinterpret_cast<char*>(&entry.checksum), sizeof(entry.checksum));
|
||||
|
||||
resources_[filename] = entry;
|
||||
}
|
||||
|
||||
uint64_t data_size;
|
||||
file.read(reinterpret_cast<char*>(&data_size), sizeof(data_size));
|
||||
|
||||
data_.resize(data_size);
|
||||
file.read(reinterpret_cast<char*>(data_.data()), data_size);
|
||||
|
||||
decryptData(data_, DEFAULT_ENCRYPT_KEY);
|
||||
|
||||
loaded_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourcePack::savePack(const std::string& pack_file) {
|
||||
std::ofstream file(pack_file, std::ios::binary);
|
||||
if (!file) {
|
||||
std::cerr << "Error: Could not create pack file: " << pack_file << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write("CCRS", 4);
|
||||
|
||||
uint32_t version = 1;
|
||||
file.write(reinterpret_cast<const char*>(&version), sizeof(version));
|
||||
|
||||
uint32_t resource_count = static_cast<uint32_t>(resources_.size());
|
||||
file.write(reinterpret_cast<const char*>(&resource_count), sizeof(resource_count));
|
||||
|
||||
for (const auto& [filename, entry] : resources_) {
|
||||
uint32_t filename_length = static_cast<uint32_t>(filename.length());
|
||||
file.write(reinterpret_cast<const char*>(&filename_length), sizeof(filename_length));
|
||||
file.write(filename.c_str(), filename_length);
|
||||
|
||||
file.write(reinterpret_cast<const char*>(&entry.offset), sizeof(entry.offset));
|
||||
file.write(reinterpret_cast<const char*>(&entry.size), sizeof(entry.size));
|
||||
file.write(reinterpret_cast<const char*>(&entry.checksum), sizeof(entry.checksum));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> encrypted_data = data_;
|
||||
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
|
||||
|
||||
uint64_t data_size = encrypted_data.size();
|
||||
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
|
||||
file.write(reinterpret_cast<const char*>(encrypted_data.data()), data_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourcePack::addFile(const std::string& filename, const std::string& filepath) {
|
||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
std::cerr << "Error: Could not open file: " << filepath << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
std::streamsize file_size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> file_data(file_size);
|
||||
if (!file.read(reinterpret_cast<char*>(file_data.data()), file_size)) {
|
||||
std::cerr << "Error: Could not read file: " << filepath << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceEntry entry;
|
||||
entry.filename = filename;
|
||||
entry.offset = data_.size();
|
||||
entry.size = file_data.size();
|
||||
entry.checksum = calculateChecksum(file_data);
|
||||
|
||||
data_.insert(data_.end(), file_data.begin(), file_data.end());
|
||||
resources_[filename] = entry;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourcePack::addDirectory(const std::string& directory) {
|
||||
if (!std::filesystem::exists(directory)) {
|
||||
std::cerr << "Error: Directory does not exist: " << directory << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
|
||||
if (entry.is_regular_file()) {
|
||||
std::string filepath = entry.path().string();
|
||||
std::string filename = std::filesystem::relative(entry.path(), directory).string();
|
||||
|
||||
std::replace(filename.begin(), filename.end(), '\\', '/');
|
||||
|
||||
if (!addFile(filename, filepath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ResourcePack::getResource(const std::string& filename) {
|
||||
auto it = resources_.find(filename);
|
||||
if (it == resources_.end()) {
|
||||
std::cerr << "Error: Resource not found: " << filename << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
const ResourceEntry& entry = it->second;
|
||||
if (entry.offset + entry.size > data_.size()) {
|
||||
std::cerr << "Error: Invalid resource data: " << filename << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> result(data_.begin() + entry.offset,
|
||||
data_.begin() + entry.offset + entry.size);
|
||||
|
||||
uint32_t checksum = calculateChecksum(result);
|
||||
if (checksum != entry.checksum) {
|
||||
std::cerr << "Warning: Checksum mismatch for resource: " << filename << '\n';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ResourcePack::hasResource(const std::string& filename) const {
|
||||
return resources_.find(filename) != resources_.end();
|
||||
}
|
||||
|
||||
void ResourcePack::clear() {
|
||||
resources_.clear();
|
||||
data_.clear();
|
||||
loaded_ = false;
|
||||
}
|
||||
|
||||
size_t ResourcePack::getResourceCount() const {
|
||||
return resources_.size();
|
||||
}
|
||||
|
||||
std::vector<std::string> ResourcePack::getResourceList() const {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(resources_.size());
|
||||
for (const auto& [filename, entry] : resources_) {
|
||||
result.push_back(filename);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
44
source/core/resources/resource_pack.h
Normal file
44
source/core/resources/resource_pack.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct ResourceEntry {
|
||||
std::string filename;
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
uint32_t checksum;
|
||||
};
|
||||
|
||||
class ResourcePack {
|
||||
private:
|
||||
std::unordered_map<std::string, ResourceEntry> resources_;
|
||||
std::vector<uint8_t> data_;
|
||||
bool loaded_;
|
||||
|
||||
static uint32_t calculateChecksum(const std::vector<uint8_t>& data);
|
||||
static void encryptData(std::vector<uint8_t>& data, const std::string& key);
|
||||
static void decryptData(std::vector<uint8_t>& data, const std::string& key);
|
||||
|
||||
public:
|
||||
ResourcePack();
|
||||
~ResourcePack();
|
||||
|
||||
bool loadPack(const std::string& pack_file);
|
||||
bool savePack(const std::string& pack_file);
|
||||
|
||||
bool addFile(const std::string& filename, const std::string& filepath);
|
||||
bool addDirectory(const std::string& directory);
|
||||
|
||||
std::vector<uint8_t> getResource(const std::string& filename);
|
||||
bool hasResource(const std::string& filename) const;
|
||||
|
||||
void clear();
|
||||
size_t getResourceCount() const;
|
||||
std::vector<std::string> getResourceList() const;
|
||||
|
||||
static const std::string DEFAULT_ENCRYPT_KEY;
|
||||
};
|
||||
864
source/core/system/director.cpp
Normal file
864
source/core/system/director.cpp
Normal file
@@ -0,0 +1,864 @@
|
||||
#include "core/system/director.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <errno.h> // for errno, EEXIST, EACCES, ENAMETOO...
|
||||
#include <stdio.h> // for printf, perror
|
||||
#include <string.h> // for strcmp
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#include <sys/stat.h> // for mkdir, stat, S_IRWXU
|
||||
#include <unistd.h> // for getuid
|
||||
#endif
|
||||
|
||||
#include <cstdlib> // for exit, EXIT_FAILURE, srand
|
||||
#include <filesystem>
|
||||
#include <fstream> // for basic_ostream, operator<<, basi...
|
||||
#include <iostream> // for cout
|
||||
#include <memory>
|
||||
#include <string> // for basic_string, operator+, char_t...
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "core/audio/jail_audio.hpp" // for JA_Init
|
||||
#include "core/input/input.h" // for Input, inputs_e, INPUT_USE_GAME...
|
||||
#include "core/input/mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
|
||||
#include "core/locale/lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
|
||||
#include "core/rendering/screen.h" // for FILTER_NEAREST, Screen, FILTER_...
|
||||
#include "core/rendering/texture.h" // for Texture
|
||||
#include "core/resources/asset.h" // for Asset, assetType
|
||||
#include "core/resources/resource.h"
|
||||
#include "core/resources/resource_helper.h"
|
||||
#include "game/defaults.hpp" // for SECTION_PROG_LOGO, GAMECANVAS_H...
|
||||
#include "game/game.h" // for Game
|
||||
#include "game/scenes/intro.h" // for Intro
|
||||
#include "game/scenes/logo.h" // for Logo
|
||||
#include "game/scenes/title.h" // for Title
|
||||
#include "utils/utils.h" // for options_t, input_t, boolToString
|
||||
|
||||
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
// Constructor
|
||||
Director::Director(int argc, const char *argv[]) {
|
||||
std::cout << "Game start" << std::endl;
|
||||
// Inicializa variables
|
||||
section = new section_t();
|
||||
section->name = SECTION_PROG_LOGO;
|
||||
|
||||
// Inicializa las opciones del programa
|
||||
initOptions();
|
||||
|
||||
// Comprueba los parametros del programa
|
||||
checkProgramArguments(argc, argv);
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
createSystemFolder("jailgames");
|
||||
#ifndef DEBUG
|
||||
createSystemFolder("jailgames/coffee_crisis");
|
||||
#else
|
||||
createSystemFolder("jailgames/coffee_crisis_debug");
|
||||
#endif
|
||||
|
||||
// Inicializa el sistema de recursos (pack + fallback).
|
||||
// En wasm siempre se usa filesystem (MEMFS) porque el propio --preload-file
|
||||
// de emscripten ya empaqueta data/ — no hay resources.pack.
|
||||
{
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
const bool enable_fallback = true;
|
||||
#elif defined(RELEASE_BUILD)
|
||||
const bool enable_fallback = false;
|
||||
#else
|
||||
const bool enable_fallback = true;
|
||||
#endif
|
||||
if (!ResourceHelper::initializeResourceSystem("resources.pack", enable_fallback)) {
|
||||
std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
// Crea el objeto que controla los ficheros de recursos
|
||||
asset = new Asset(executablePath);
|
||||
asset->setVerbose(options->console);
|
||||
|
||||
// Si falta algún fichero no inicia el programa
|
||||
if (!setFileList()) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Carga el fichero de configuración
|
||||
loadConfigFile();
|
||||
|
||||
// Inicializa SDL
|
||||
initSDL();
|
||||
|
||||
// Inicializa JailAudio
|
||||
initJailAudio();
|
||||
|
||||
// Establece el modo de escalado de texturas
|
||||
Texture::setGlobalScaleMode(options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
|
||||
|
||||
// Crea los objetos
|
||||
lang = new Lang(asset);
|
||||
lang->setLang(options->language);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
input = new Input("/gamecontrollerdb.txt");
|
||||
#else
|
||||
{
|
||||
const std::string binDir = std::filesystem::path(executablePath).parent_path().string();
|
||||
#ifdef MACOS_BUNDLE
|
||||
input = new Input(binDir + "/../Resources/gamecontrollerdb.txt");
|
||||
#else
|
||||
input = new Input(binDir + "/gamecontrollerdb.txt");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
initInput();
|
||||
|
||||
// Precarga todos los recursos en memoria (texturas, sonidos, música, ...).
|
||||
// Vivirán durante toda la vida de la app; las escenas leen handles compartidos.
|
||||
// Debe ir antes de crear Screen porque Screen usa Text (propiedad de Resource).
|
||||
Resource::init(renderer, asset, input);
|
||||
|
||||
screen = new Screen(window, renderer, asset, options);
|
||||
|
||||
activeSection = ActiveSection::None;
|
||||
}
|
||||
|
||||
Director::~Director() {
|
||||
saveConfigFile();
|
||||
|
||||
// Libera las secciones primero: sus destructores tocan audio/render SDL
|
||||
// (p.ej. Intro::~Intro llama a JA_DeleteMusic) y deben ejecutarse antes
|
||||
// de SDL_Quit().
|
||||
logo.reset();
|
||||
intro.reset();
|
||||
title.reset();
|
||||
game.reset();
|
||||
|
||||
// Screen puede tener referencias a Text propiedad de Resource: destruir
|
||||
// Screen antes que Resource.
|
||||
delete screen;
|
||||
|
||||
// Libera todos los recursos precargados antes de cerrar SDL.
|
||||
Resource::destroy();
|
||||
|
||||
delete asset;
|
||||
delete input;
|
||||
delete lang;
|
||||
delete options;
|
||||
delete section;
|
||||
|
||||
SDL_DestroyRenderer(renderer);
|
||||
SDL_DestroyWindow(window);
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
ResourceHelper::shutdownResourceSystem();
|
||||
|
||||
std::cout << "\nBye!" << std::endl;
|
||||
}
|
||||
|
||||
// Inicializa el objeto input
|
||||
void Director::initInput() {
|
||||
// Establece si ha de mostrar mensajes
|
||||
input->setVerbose(options->console);
|
||||
|
||||
// Busca si hay un mando conectado
|
||||
input->discoverGameController();
|
||||
|
||||
// Teclado - Movimiento del jugador
|
||||
input->bindKey(input_up, SDL_SCANCODE_UP);
|
||||
input->bindKey(input_down, SDL_SCANCODE_DOWN);
|
||||
input->bindKey(input_left, SDL_SCANCODE_LEFT);
|
||||
input->bindKey(input_right, SDL_SCANCODE_RIGHT);
|
||||
input->bindKey(input_fire_left, SDL_SCANCODE_Q);
|
||||
input->bindKey(input_fire_center, SDL_SCANCODE_W);
|
||||
input->bindKey(input_fire_right, SDL_SCANCODE_E);
|
||||
|
||||
// Teclado - Otros
|
||||
input->bindKey(input_accept, SDL_SCANCODE_RETURN);
|
||||
input->bindKey(input_cancel, SDL_SCANCODE_ESCAPE);
|
||||
input->bindKey(input_pause, SDL_SCANCODE_ESCAPE);
|
||||
input->bindKey(input_exit, SDL_SCANCODE_ESCAPE);
|
||||
input->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
|
||||
input->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
|
||||
input->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
|
||||
|
||||
// Mando - Movimiento del jugador
|
||||
input->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
||||
input->bindGameControllerButton(input_down, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
||||
input->bindGameControllerButton(input_left, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
|
||||
input->bindGameControllerButton(input_right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
|
||||
input->bindGameControllerButton(input_fire_left, SDL_GAMEPAD_BUTTON_WEST);
|
||||
input->bindGameControllerButton(input_fire_center, SDL_GAMEPAD_BUTTON_NORTH);
|
||||
input->bindGameControllerButton(input_fire_right, SDL_GAMEPAD_BUTTON_EAST);
|
||||
|
||||
// Mando - Otros
|
||||
// SOUTH queda sin asignar para evitar salidas accidentales: pausa/cancel se hace con START/BACK.
|
||||
input->bindGameControllerButton(input_accept, SDL_GAMEPAD_BUTTON_EAST);
|
||||
#ifdef GAME_CONSOLE
|
||||
input->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_BACK);
|
||||
input->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_START);
|
||||
#else
|
||||
input->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_START);
|
||||
input->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_BACK);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Inicializa JailAudio
|
||||
void Director::initJailAudio() {
|
||||
JA_Init(48000, SDL_AUDIO_S16, 2);
|
||||
}
|
||||
|
||||
// Arranca SDL y crea la ventana
|
||||
bool Director::initSDL() {
|
||||
// Indicador de éxito
|
||||
bool success = true;
|
||||
|
||||
// Inicializa SDL
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
|
||||
if (options->console) {
|
||||
std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
success = false;
|
||||
} else {
|
||||
// Inicia el generador de numeros aleatorios
|
||||
std::srand(static_cast<unsigned int>(SDL_GetTicks()));
|
||||
|
||||
// Crea la ventana
|
||||
int incW = 0;
|
||||
int incH = 0;
|
||||
if (options->borderEnabled) {
|
||||
incW = options->borderWidth * 2;
|
||||
incH = options->borderHeight * 2;
|
||||
}
|
||||
window = SDL_CreateWindow(WINDOW_CAPTION, (options->gameWidth + incW) * options->windowSize, (options->gameHeight + incH) * options->windowSize, 0);
|
||||
if (window == nullptr) {
|
||||
if (options->console) {
|
||||
std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
success = false;
|
||||
} else {
|
||||
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
|
||||
// Crea un renderizador para la ventana
|
||||
renderer = SDL_CreateRenderer(window, NULL);
|
||||
|
||||
if (renderer == nullptr) {
|
||||
if (options->console) {
|
||||
std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
success = false;
|
||||
} else {
|
||||
// Activa vsync si es necesario
|
||||
if (options->vSync) {
|
||||
SDL_SetRenderVSync(renderer, 1);
|
||||
}
|
||||
|
||||
// Inicializa el color de renderizado
|
||||
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
|
||||
|
||||
// Establece el tamaño del buffer de renderizado
|
||||
SDL_SetRenderLogicalPresentation(renderer, options->gameWidth, options->gameHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
|
||||
// Establece el modo de mezcla
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options->console) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Crea el indice de ficheros
|
||||
bool Director::setFileList() {
|
||||
#ifdef MACOS_BUNDLE
|
||||
const std::string prefix = "/../Resources";
|
||||
#else
|
||||
const std::string prefix = "";
|
||||
#endif
|
||||
|
||||
// Ficheros de configuración
|
||||
asset->add(systemFolder + "/config.txt", t_data, false, true);
|
||||
asset->add(systemFolder + "/score.bin", t_data, false, true);
|
||||
asset->add(prefix + "/data/demo/demo.bin", t_data);
|
||||
|
||||
// Musicas
|
||||
asset->add(prefix + "/data/music/intro.ogg", t_music);
|
||||
asset->add(prefix + "/data/music/playing.ogg", t_music);
|
||||
asset->add(prefix + "/data/music/title.ogg", t_music);
|
||||
|
||||
// Sonidos
|
||||
asset->add(prefix + "/data/sound/balloon.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/bubble1.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/bubble2.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/bubble3.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/bubble4.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/bullet.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/coffeeout.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/hiscore.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/itemdrop.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/itempickup.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/menu_move.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/menu_select.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/player_collision.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/stage_change.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/title.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/clock.wav", t_sound);
|
||||
asset->add(prefix + "/data/sound/powerball.wav", t_sound);
|
||||
|
||||
// Texturas
|
||||
asset->add(prefix + "/data/gfx/balloon1.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/balloon1.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/balloon2.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/balloon2.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/balloon3.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/balloon3.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/balloon4.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/balloon4.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/bullet.png", t_bitmap);
|
||||
|
||||
asset->add(prefix + "/data/gfx/game_buildings.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/game_clouds.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/game_grass.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/game_power_meter.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/game_sky_colors.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/game_text.png", t_bitmap);
|
||||
|
||||
asset->add(prefix + "/data/gfx/intro.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/logo.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/menu_game_over.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/menu_game_over_end.png", t_bitmap);
|
||||
|
||||
asset->add(prefix + "/data/gfx/item_points1_disk.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/item_points1_disk.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/item_points2_gavina.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/item_points2_gavina.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/item_points3_pacmar.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/item_points3_pacmar.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/item_clock.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/item_clock.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/item_coffee.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/item_coffee.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/item_coffee_machine.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/item_coffee_machine.ani", t_data);
|
||||
|
||||
asset->add(prefix + "/data/gfx/title_bg_tile.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/title_coffee.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/title_crisis.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/title_dust.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/title_dust.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/title_gradient.png", t_bitmap);
|
||||
|
||||
asset->add(prefix + "/data/gfx/player_head.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/player_body.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/player_legs.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/player_death.ani", t_data);
|
||||
asset->add(prefix + "/data/gfx/player_fire.ani", t_data);
|
||||
|
||||
asset->add(prefix + "/data/gfx/player_bal1_head.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/player_bal1_body.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/player_bal1_legs.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/player_bal1_death.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/player_bal1_fire.png", t_bitmap);
|
||||
|
||||
asset->add(prefix + "/data/gfx/player_arounder_head.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/player_arounder_body.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/player_arounder_legs.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/player_arounder_death.png", t_bitmap);
|
||||
asset->add(prefix + "/data/gfx/player_arounder_fire.png", t_bitmap);
|
||||
|
||||
// Fuentes
|
||||
asset->add(prefix + "/data/font/8bithud.png", t_font);
|
||||
asset->add(prefix + "/data/font/8bithud.txt", t_font);
|
||||
asset->add(prefix + "/data/font/nokia.png", t_font);
|
||||
asset->add(prefix + "/data/font/nokia_big2.png", t_font);
|
||||
asset->add(prefix + "/data/font/nokia.txt", t_font);
|
||||
asset->add(prefix + "/data/font/nokia2.png", t_font);
|
||||
asset->add(prefix + "/data/font/nokia2.txt", t_font);
|
||||
asset->add(prefix + "/data/font/nokia_big2.txt", t_font);
|
||||
asset->add(prefix + "/data/font/smb2_big.png", t_font);
|
||||
asset->add(prefix + "/data/font/smb2_big.txt", t_font);
|
||||
asset->add(prefix + "/data/font/smb2.png", t_font);
|
||||
asset->add(prefix + "/data/font/smb2.txt", t_font);
|
||||
|
||||
// Textos
|
||||
asset->add(prefix + "/data/lang/es_ES.txt", t_lang);
|
||||
asset->add(prefix + "/data/lang/en_UK.txt", t_lang);
|
||||
asset->add(prefix + "/data/lang/ba_BA.txt", t_lang);
|
||||
|
||||
// Menus
|
||||
asset->add(prefix + "/data/menu/title.men", t_data);
|
||||
asset->add(prefix + "/data/menu/title_gc.men", t_data);
|
||||
asset->add(prefix + "/data/menu/options.men", t_data);
|
||||
asset->add(prefix + "/data/menu/options_gc.men", t_data);
|
||||
asset->add(prefix + "/data/menu/pause.men", t_data);
|
||||
asset->add(prefix + "/data/menu/gameover.men", t_data);
|
||||
asset->add(prefix + "/data/menu/player_select.men", t_data);
|
||||
|
||||
return asset->check();
|
||||
}
|
||||
|
||||
// Inicializa las opciones del programa
|
||||
void Director::initOptions() {
|
||||
// Crea el puntero a la estructura de opciones
|
||||
options = new options_t;
|
||||
|
||||
// Pone unos valores por defecto para las opciones de control
|
||||
options->input.clear();
|
||||
|
||||
input_t inp;
|
||||
inp.id = 0;
|
||||
inp.name = "KEYBOARD";
|
||||
inp.deviceType = INPUT_USE_KEYBOARD;
|
||||
options->input.push_back(inp);
|
||||
|
||||
inp.id = 0;
|
||||
inp.name = "GAME CONTROLLER";
|
||||
inp.deviceType = INPUT_USE_GAMECONTROLLER;
|
||||
options->input.push_back(inp);
|
||||
|
||||
// Opciones de video
|
||||
options->gameWidth = GAMECANVAS_WIDTH;
|
||||
options->gameHeight = GAMECANVAS_HEIGHT;
|
||||
options->videoMode = 0;
|
||||
options->windowSize = 3;
|
||||
options->filter = FILTER_NEAREST;
|
||||
options->vSync = true;
|
||||
options->integerScale = true;
|
||||
options->keepAspect = true;
|
||||
options->borderWidth = 0;
|
||||
options->borderHeight = 0;
|
||||
options->borderEnabled = false;
|
||||
|
||||
// Opciones varios
|
||||
options->playerSelected = 0;
|
||||
options->difficulty = DIFFICULTY_NORMAL;
|
||||
options->language = ba_BA;
|
||||
options->console = false;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// En Emscripten la ventana la gestiona el navegador
|
||||
options->windowSize = 4;
|
||||
options->videoMode = 0;
|
||||
options->integerScale = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Comprueba los parametros del programa
|
||||
void Director::checkProgramArguments(int argc, const char *argv[]) {
|
||||
// Establece la ruta del programa
|
||||
executablePath = argv[0];
|
||||
|
||||
// Comprueba el resto de parametros
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (strcmp(argv[i], "--console") == 0) {
|
||||
options->console = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
void Director::createSystemFolder(const std::string &folder) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// En Emscripten usamos una carpeta en MEMFS (no persistente)
|
||||
systemFolder = "/config/" + folder;
|
||||
#elif _WIN32
|
||||
systemFolder = std::string(getenv("APPDATA")) + "/" + folder;
|
||||
#elif __APPLE__
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
const char *homedir = pw->pw_dir;
|
||||
systemFolder = std::string(homedir) + "/Library/Application Support" + "/" + folder;
|
||||
#elif __linux__
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
const char *homedir = pw->pw_dir;
|
||||
systemFolder = std::string(homedir) + "/.config/" + folder;
|
||||
|
||||
{
|
||||
// Intenta crear ".config", per si no existeix
|
||||
std::string config_base_folder = std::string(homedir) + "/.config";
|
||||
int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
|
||||
if (ret == -1 && errno != EEXIST) {
|
||||
printf("ERROR CREATING CONFIG BASE FOLDER.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// En Emscripten no necesitamos crear carpetas (MEMFS las crea automáticamente)
|
||||
(void)folder;
|
||||
#else
|
||||
struct stat st = {0};
|
||||
if (stat(systemFolder.c_str(), &st) == -1) {
|
||||
errno = 0;
|
||||
#ifdef _WIN32
|
||||
int ret = mkdir(systemFolder.c_str());
|
||||
#else
|
||||
int ret = mkdir(systemFolder.c_str(), S_IRWXU);
|
||||
#endif
|
||||
|
||||
if (ret == -1) {
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
printf("the parent directory does not allow write");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
case EEXIST:
|
||||
printf("pathname already exists");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
case ENAMETOOLONG:
|
||||
printf("pathname is too long");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
default:
|
||||
perror("mkdir");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Carga el fichero de configuración
|
||||
bool Director::loadConfigFile() {
|
||||
// Indicador de éxito en la carga
|
||||
bool success = true;
|
||||
|
||||
// Variables para manejar el fichero
|
||||
const std::string filePath = "config.txt";
|
||||
std::string line;
|
||||
std::ifstream file(asset->get(filePath));
|
||||
|
||||
// Si el fichero se puede abrir
|
||||
if (file.good()) {
|
||||
// Procesa el fichero linea a linea
|
||||
if (options->console) {
|
||||
std::cout << "Reading file " << filePath << std::endl;
|
||||
}
|
||||
while (std::getline(file, line)) {
|
||||
// Comprueba que la linea no sea un comentario
|
||||
if (line.substr(0, 1) != "#") {
|
||||
// Encuentra la posición del caracter '='
|
||||
int pos = line.find("=");
|
||||
// Procesa las dos subcadenas
|
||||
if (!setOptions(options, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
||||
if (options->console) {
|
||||
std::cout << "Warning: file " << filePath << std::endl;
|
||||
std::cout << "Unknown parameter " << line.substr(0, pos).c_str() << std::endl;
|
||||
}
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
if (options->console) {
|
||||
std::cout << "Closing file " << filePath << std::endl;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
// El fichero no existe
|
||||
else { // Crea el fichero con los valores por defecto
|
||||
saveConfigFile();
|
||||
}
|
||||
|
||||
// Normaliza los valores
|
||||
if (options->videoMode != 0 && options->videoMode != SDL_WINDOW_FULLSCREEN) {
|
||||
options->videoMode = 0;
|
||||
}
|
||||
|
||||
if (options->windowSize < 1 || options->windowSize > 4) {
|
||||
options->windowSize = 3;
|
||||
}
|
||||
|
||||
if (options->language < 0 || options->language > MAX_LANGUAGES) {
|
||||
options->language = en_UK;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Guarda el fichero de configuración
|
||||
bool Director::saveConfigFile() {
|
||||
bool success = true;
|
||||
|
||||
// Crea y abre el fichero de texto
|
||||
std::ofstream file(asset->get("config.txt"));
|
||||
|
||||
if (file.good()) {
|
||||
if (options->console) {
|
||||
std::cout << asset->get("config.txt") << " open for writing" << std::endl;
|
||||
}
|
||||
} else {
|
||||
if (options->console) {
|
||||
std::cout << asset->get("config.txt") << " can't be opened" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Opciones g´raficas
|
||||
file << "## VISUAL OPTIONS\n";
|
||||
if (options->videoMode == 0) {
|
||||
file << "videoMode=0\n";
|
||||
}
|
||||
|
||||
else if (options->videoMode == SDL_WINDOW_FULLSCREEN) {
|
||||
file << "videoMode=SDL_WINDOW_FULLSCREEN\n";
|
||||
}
|
||||
|
||||
file << "windowSize=" + std::to_string(options->windowSize) + "\n";
|
||||
|
||||
if (options->filter == FILTER_NEAREST) {
|
||||
file << "filter=FILTER_NEAREST\n";
|
||||
} else {
|
||||
file << "filter=FILTER_LINEAL\n";
|
||||
}
|
||||
|
||||
file << "vSync=" + boolToString(options->vSync) + "\n";
|
||||
file << "integerScale=" + boolToString(options->integerScale) + "\n";
|
||||
file << "keepAspect=" + boolToString(options->keepAspect) + "\n";
|
||||
file << "borderEnabled=" + boolToString(options->borderEnabled) + "\n";
|
||||
file << "borderWidth=" + std::to_string(options->borderWidth) + "\n";
|
||||
file << "borderHeight=" + std::to_string(options->borderHeight) + "\n";
|
||||
|
||||
// Otras opciones del programa
|
||||
file << "\n## OTHER OPTIONS\n";
|
||||
file << "language=" + std::to_string(options->language) + "\n";
|
||||
file << "difficulty=" + std::to_string(options->difficulty) + "\n";
|
||||
file << "input0=" + std::to_string(options->input[0].deviceType) + "\n";
|
||||
file << "input1=" + std::to_string(options->input[1].deviceType) + "\n";
|
||||
|
||||
// Cierra el fichero
|
||||
file.close();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Gestiona las transiciones entre secciones
|
||||
void Director::handleSectionTransition() {
|
||||
// Determina qué sección debería estar activa
|
||||
ActiveSection targetSection = ActiveSection::None;
|
||||
switch (section->name) {
|
||||
case SECTION_PROG_LOGO:
|
||||
targetSection = ActiveSection::Logo;
|
||||
break;
|
||||
case SECTION_PROG_INTRO:
|
||||
targetSection = ActiveSection::Intro;
|
||||
break;
|
||||
case SECTION_PROG_TITLE:
|
||||
targetSection = ActiveSection::Title;
|
||||
break;
|
||||
case SECTION_PROG_GAME:
|
||||
targetSection = ActiveSection::Game;
|
||||
break;
|
||||
}
|
||||
|
||||
// Si no ha cambiado, no hay nada que hacer
|
||||
if (targetSection == activeSection) return;
|
||||
|
||||
// Destruye la sección anterior
|
||||
logo.reset();
|
||||
intro.reset();
|
||||
title.reset();
|
||||
game.reset();
|
||||
|
||||
// Crea la nueva sección
|
||||
activeSection = targetSection;
|
||||
switch (activeSection) {
|
||||
case ActiveSection::Logo:
|
||||
logo = std::make_unique<Logo>(renderer, screen, asset, input, section);
|
||||
break;
|
||||
case ActiveSection::Intro:
|
||||
intro = std::make_unique<Intro>(renderer, screen, asset, input, lang, section);
|
||||
break;
|
||||
case ActiveSection::Title:
|
||||
title = std::make_unique<Title>(renderer, screen, input, asset, options, lang, section);
|
||||
break;
|
||||
case ActiveSection::Game: {
|
||||
const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
|
||||
game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, options, section);
|
||||
break;
|
||||
}
|
||||
case ActiveSection::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ejecuta un frame del juego
|
||||
SDL_AppResult Director::iterate() {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// En WASM no se puede salir: reinicia al logo
|
||||
if (section->name == SECTION_PROG_QUIT) {
|
||||
section->name = SECTION_PROG_LOGO;
|
||||
}
|
||||
#else
|
||||
if (section->name == SECTION_PROG_QUIT) {
|
||||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Actualiza la visibilidad del cursor del ratón
|
||||
Mouse::updateCursorVisibility(options->videoMode != 0);
|
||||
|
||||
// Gestiona las transiciones entre secciones
|
||||
handleSectionTransition();
|
||||
|
||||
// Ejecuta un frame de la sección activa
|
||||
switch (activeSection) {
|
||||
case ActiveSection::Logo:
|
||||
logo->iterate();
|
||||
break;
|
||||
case ActiveSection::Intro:
|
||||
intro->iterate();
|
||||
break;
|
||||
case ActiveSection::Title:
|
||||
title->iterate();
|
||||
break;
|
||||
case ActiveSection::Game:
|
||||
game->iterate();
|
||||
break;
|
||||
case ActiveSection::None:
|
||||
break;
|
||||
}
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
// Procesa un evento
|
||||
SDL_AppResult Director::handleEvent(SDL_Event *event) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
// Evento de salida de la aplicación
|
||||
if (event->type == SDL_EVENT_QUIT) {
|
||||
section->name = SECTION_PROG_QUIT;
|
||||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Hot-plug de mandos
|
||||
if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
|
||||
std::string name;
|
||||
if (input->handleGamepadAdded(event->gdevice.which, name)) {
|
||||
screen->notify(name + " " + lang->getText(94),
|
||||
color_t{0x40, 0xFF, 0x40},
|
||||
color_t{0, 0, 0},
|
||||
2500);
|
||||
}
|
||||
} else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
std::string name;
|
||||
if (input->handleGamepadRemoved(event->gdevice.which, name)) {
|
||||
screen->notify(name + " " + lang->getText(95),
|
||||
color_t{0xFF, 0x50, 0x50},
|
||||
color_t{0, 0, 0},
|
||||
2500);
|
||||
}
|
||||
}
|
||||
|
||||
// Gestiona la visibilidad del cursor según el movimiento del ratón
|
||||
Mouse::handleEvent(*event, options->videoMode != 0);
|
||||
|
||||
// Reenvía el evento a la sección activa
|
||||
switch (activeSection) {
|
||||
case ActiveSection::Logo:
|
||||
logo->handleEvent(event);
|
||||
break;
|
||||
case ActiveSection::Intro:
|
||||
intro->handleEvent(event);
|
||||
break;
|
||||
case ActiveSection::Title:
|
||||
title->handleEvent(event);
|
||||
break;
|
||||
case ActiveSection::Game:
|
||||
game->handleEvent(event);
|
||||
break;
|
||||
case ActiveSection::None:
|
||||
break;
|
||||
}
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
// Asigna variables a partir de dos cadenas
|
||||
bool Director::setOptions(options_t *options, std::string var, std::string value) {
|
||||
// Indicador de éxito en la asignación
|
||||
bool success = true;
|
||||
|
||||
// Opciones de video
|
||||
if (var == "videoMode") {
|
||||
if (value == "SDL_WINDOW_FULLSCREEN" || value == "SDL_WINDOW_FULLSCREEN_DESKTOP") {
|
||||
options->videoMode = SDL_WINDOW_FULLSCREEN;
|
||||
} else {
|
||||
options->videoMode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
else if (var == "windowSize") {
|
||||
options->windowSize = std::stoi(value);
|
||||
if ((options->windowSize < 1) || (options->windowSize > 4)) {
|
||||
options->windowSize = 3;
|
||||
}
|
||||
}
|
||||
|
||||
else if (var == "filter") {
|
||||
if (value == "FILTER_LINEAL") {
|
||||
options->filter = FILTER_LINEAL;
|
||||
} else {
|
||||
options->filter = FILTER_NEAREST;
|
||||
}
|
||||
}
|
||||
|
||||
else if (var == "vSync") {
|
||||
options->vSync = stringToBool(value);
|
||||
}
|
||||
|
||||
else if (var == "integerScale") {
|
||||
options->integerScale = stringToBool(value);
|
||||
}
|
||||
|
||||
else if (var == "keepAspect") {
|
||||
options->keepAspect = stringToBool(value);
|
||||
}
|
||||
|
||||
else if (var == "borderEnabled") {
|
||||
options->borderEnabled = stringToBool(value);
|
||||
}
|
||||
|
||||
else if (var == "borderWidth") {
|
||||
options->borderWidth = std::stoi(value);
|
||||
}
|
||||
|
||||
else if (var == "borderHeight") {
|
||||
options->borderHeight = std::stoi(value);
|
||||
}
|
||||
|
||||
// Opciones varias
|
||||
else if (var == "language") {
|
||||
options->language = std::stoi(value);
|
||||
}
|
||||
|
||||
else if (var == "difficulty") {
|
||||
options->difficulty = std::stoi(value);
|
||||
}
|
||||
|
||||
else if (var == "input0") {
|
||||
options->input[0].deviceType = std::stoi(value);
|
||||
}
|
||||
|
||||
else if (var == "input1") {
|
||||
options->input[1].deviceType = std::stoi(value);
|
||||
}
|
||||
|
||||
// Lineas vacias o que empiezan por comentario
|
||||
else if (var == "" || var.substr(0, 1) == "#") {
|
||||
}
|
||||
|
||||
else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
96
source/core/system/director.h
Normal file
96
source/core/system/director.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string> // for string, basic_string
|
||||
class Asset;
|
||||
class Game;
|
||||
class Input;
|
||||
class Intro;
|
||||
class Lang;
|
||||
class Logo;
|
||||
class Screen;
|
||||
class Title;
|
||||
struct options_t;
|
||||
struct section_t;
|
||||
|
||||
// Textos
|
||||
constexpr const char *WINDOW_CAPTION = "© 2020 Coffee Crisis — JailDesigner";
|
||||
|
||||
// Secciones activas del Director
|
||||
enum class ActiveSection { None,
|
||||
Logo,
|
||||
Intro,
|
||||
Title,
|
||||
Game };
|
||||
|
||||
class Director {
|
||||
private:
|
||||
// Objetos y punteros
|
||||
SDL_Window *window; // La ventana donde dibujamos
|
||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||
Screen *screen; // Objeto encargado de dibujar en pantalla
|
||||
Input *input; // Objeto Input para gestionar las entradas
|
||||
Lang *lang; // Objeto para gestionar los textos en diferentes idiomas
|
||||
Asset *asset; // Objeto que gestiona todos los ficheros de recursos
|
||||
section_t *section; // Sección y subsección actual del programa;
|
||||
|
||||
// Secciones del juego
|
||||
ActiveSection activeSection;
|
||||
std::unique_ptr<Logo> logo;
|
||||
std::unique_ptr<Intro> intro;
|
||||
std::unique_ptr<Title> title;
|
||||
std::unique_ptr<Game> game;
|
||||
|
||||
// Variables
|
||||
struct options_t *options; // Variable con todas las opciones del programa
|
||||
std::string executablePath; // Path del ejecutable
|
||||
std::string systemFolder; // Carpeta del sistema donde guardar datos
|
||||
|
||||
// Inicializa jail_audio
|
||||
void initJailAudio();
|
||||
|
||||
// Arranca SDL y crea la ventana
|
||||
bool initSDL();
|
||||
|
||||
// Inicializa el objeto input
|
||||
void initInput();
|
||||
|
||||
// Inicializa las opciones del programa
|
||||
void initOptions();
|
||||
|
||||
// Asigna variables a partir de dos cadenas
|
||||
bool setOptions(options_t *options, std::string var, std::string value);
|
||||
|
||||
// Crea el indice de ficheros
|
||||
bool setFileList();
|
||||
|
||||
// Carga el fichero de configuración
|
||||
bool loadConfigFile();
|
||||
|
||||
// Guarda el fichero de configuración
|
||||
bool saveConfigFile();
|
||||
|
||||
// Comprueba los parametros del programa
|
||||
void checkProgramArguments(int argc, const char *argv[]);
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
void createSystemFolder(const std::string &folder);
|
||||
|
||||
// Gestiona las transiciones entre secciones
|
||||
void handleSectionTransition();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Director(int argc, const char *argv[]);
|
||||
|
||||
// Destructor
|
||||
~Director();
|
||||
|
||||
// Ejecuta un frame del juego
|
||||
SDL_AppResult iterate();
|
||||
|
||||
// Procesa un evento
|
||||
SDL_AppResult handleEvent(SDL_Event *event);
|
||||
};
|
||||
Reference in New Issue
Block a user