fase 1: jail i game a c++ idiomàtic (raii, info::ctx, cheats arreglats)

This commit is contained in:
2026-04-15 18:03:46 +02:00
parent 2c833d086e
commit 7f85b50c63
18 changed files with 834 additions and 804 deletions

View File

@@ -17,113 +17,131 @@
#include <pwd.h>
#endif
#define DEFAULT_FILENAME "data.jf2"
#define DEFAULT_FOLDER "data/"
#define CONFIG_FILENAME "config.txt"
namespace {
struct file_t {
constexpr const char* DEFAULT_FILENAME = "data.jf2";
constexpr const char* DEFAULT_FOLDER = "data/";
struct file_entry {
std::string path;
uint32_t size;
uint32_t offset;
};
std::vector<file_t> toc;
/* El std::map me fa coses rares, vaig a usar un good old std::vector amb una estructura key,value propia i au, que sempre funciona */
struct keyvalue_t {
std::string key, value;
struct keyvalue {
std::string key;
std::string value;
};
char* resource_filename = NULL;
char* resource_folder = NULL;
std::vector<file_entry> toc;
std::vector<keyvalue> config;
std::string resource_filename;
std::string resource_folder;
std::string config_folder;
int file_source = SOURCE_FILE;
char scratch[255];
static std::string config_folder;
std::vector<keyvalue_t> config;
void file_setresourcefilename(const char* str) {
if (resource_filename != NULL) free(resource_filename);
resource_filename = (char*)malloc(strlen(str) + 1);
strcpy(resource_filename, str);
}
void file_setresourcefolder(const char* str) {
if (resource_folder != NULL) free(resource_folder);
resource_folder = (char*)malloc(strlen(str) + 1);
strcpy(resource_folder, str);
}
void file_setsource(const int src) {
file_source = src % 2; // mod 2 so it always is a valid value, 0 (file) or 1 (folder)
if (src == SOURCE_FOLDER && resource_folder == NULL) file_setresourcefolder(DEFAULT_FOLDER);
}
bool file_getdictionary() {
if (resource_filename == NULL) file_setresourcefilename(DEFAULT_FILENAME);
bool dictionary_loaded() {
if (resource_filename.empty()) resource_filename = DEFAULT_FILENAME;
std::ifstream fi(resource_filename, std::ios::binary);
if (!fi.is_open()) return false;
char header[4];
fi.read(header, 4);
uint32_t num_files, toc_offset;
fi.read((char*)&num_files, 4);
fi.read((char*)&toc_offset, 4);
fi.read(reinterpret_cast<char*>(&num_files), 4);
fi.read(reinterpret_cast<char*>(&toc_offset), 4);
fi.seekg(toc_offset);
for (uint32_t i = 0; i < num_files; ++i) {
uint32_t file_offset, file_size;
fi.read((char*)&file_offset, 4);
fi.read((char*)&file_size, 4);
fi.read(reinterpret_cast<char*>(&file_offset), 4);
fi.read(reinterpret_cast<char*>(&file_size), 4);
uint8_t path_size;
fi.read((char*)&path_size, 1);
fi.read(reinterpret_cast<char*>(&path_size), 1);
char file_name[256];
fi.read(file_name, path_size);
file_name[path_size] = 0;
std::string filename = file_name;
toc.push_back({filename, file_size, file_offset});
toc.push_back({std::string(file_name), file_size, file_offset});
}
fi.close();
return true;
}
char* file_getfilenamewithfolder(const char* filename) {
strcpy(scratch, resource_folder);
strcat(scratch, filename);
return scratch;
std::string filename_with_folder(const char* filename) {
return resource_folder + filename;
}
void load_config_values() {
config.clear();
const std::string config_file = config_folder + "/config.txt";
std::ifstream fi(config_file);
if (!fi.is_open()) return;
std::string line;
while (std::getline(fi, line)) {
const auto eq = line.find('=');
if (eq == std::string::npos) continue;
config.push_back({line.substr(0, eq), line.substr(eq + 1)});
}
}
void save_config_values() {
const std::string config_file = config_folder + "/config.txt";
std::ofstream fo(config_file);
if (!fo.is_open()) return;
for (const auto& pair : config) {
fo << pair.key << '=' << pair.value << '\n';
}
}
} // namespace
void file_setresourcefilename(const char* str) {
resource_filename = str;
}
void file_setresourcefolder(const char* str) {
resource_folder = str;
}
void file_setsource(const int src) {
file_source = src % 2;
if (src == SOURCE_FOLDER && resource_folder.empty()) file_setresourcefolder(DEFAULT_FOLDER);
}
FILE* file_getfilepointer(const char* resourcename, int& filesize, const bool binary) {
if (file_source == SOURCE_FILE and toc.size() == 0) {
if (not file_getdictionary()) file_setsource(SOURCE_FOLDER);
if (file_source == SOURCE_FILE && toc.empty()) {
if (!dictionary_loaded()) file_setsource(SOURCE_FOLDER);
}
FILE* f;
FILE* f = nullptr;
if (file_source == SOURCE_FILE) {
bool found = false;
uint32_t count = 0;
while (!found && count < toc.size()) {
found = (std::string(resourcename) == toc[count].path);
if (!found) count++;
const std::string name(resourcename);
size_t count = 0;
for (; count < toc.size(); ++count) {
if (toc[count].path == name) break;
}
if (!found) {
if (count == toc.size()) {
perror("El recurs no s'ha trobat en l'arxiu de recursos");
exit(1);
}
filesize = toc[count].size;
filesize = static_cast<int>(toc[count].size);
f = fopen(resource_filename, binary ? "rb" : "r");
if (not f) {
f = fopen(resource_filename.c_str(), binary ? "rb" : "r");
if (!f) {
perror("No s'ha pogut obrir l'arxiu de recursos");
exit(1);
}
fseek(f, toc[count].offset, SEEK_SET);
} else {
f = fopen(file_getfilenamewithfolder(resourcename), binary ? "rb" : "r");
const std::string full = filename_with_folder(resourcename);
f = fopen(full.c_str(), binary ? "rb" : "r");
if (!f) return nullptr;
fseek(f, 0, SEEK_END);
filesize = ftell(f);
filesize = static_cast<int>(ftell(f));
fseek(f, 0, SEEK_SET);
}
return f;
@@ -131,15 +149,16 @@ FILE* file_getfilepointer(const char* resourcename, int& filesize, const bool bi
char* file_getfilebuffer(const char* resourcename, int& filesize, const bool zero_terminate) {
FILE* f = file_getfilepointer(resourcename, filesize, true);
char* buffer = (char*)malloc(zero_terminate ? filesize : filesize + 1);
if (!f) return nullptr;
char* buffer = static_cast<char*>(malloc(zero_terminate ? filesize + 1 : filesize));
fread(buffer, filesize, 1, f);
if (zero_terminate) buffer[filesize] = 0;
fclose(f);
return buffer;
}
// Crea la carpeta del sistema donde guardar datos.
// Acepta rutas con subdirectorios (ej: "jailgames/aee") y crea toda la jerarquía.
// Crea la carpeta del sistema on guardar les dades.
// Accepta rutes amb subdirectoris (ex: "jailgames/aee") i crea tota la jerarquia.
void file_setconfigfolder(const char* foldername) {
#ifdef _WIN32
config_folder = std::string(getenv("APPDATA")) + "/" + foldername;
@@ -157,62 +176,32 @@ void file_setconfigfolder(const char* foldername) {
}
const char* file_getconfigfolder() {
static std::string folder;
thread_local std::string folder;
folder = config_folder + "/";
return folder.c_str();
}
void file_loadconfigvalues() {
config.clear();
std::string config_file = config_folder + "/config.txt";
FILE* f = fopen(config_file.c_str(), "r");
if (!f) return;
char line[1024];
while (fgets(line, sizeof(line), f)) {
char* value = strchr(line, '=');
if (value) {
*value = '\0';
value++;
value[strlen(value) - 1] = '\0';
config.push_back({line, value});
}
}
fclose(f);
}
void file_saveconfigvalues() {
std::string config_file = config_folder + "/config.txt";
FILE* f = fopen(config_file.c_str(), "w");
if (f) {
for (auto pair : config) {
fprintf(f, "%s=%s\n", pair.key.c_str(), pair.value.c_str());
}
fclose(f);
}
}
const char* file_getconfigvalue(const char* key) {
if (config.empty()) file_loadconfigvalues();
for (auto pair : config) {
if (pair.key == std::string(key)) {
strcpy(scratch, pair.value.c_str());
return scratch;
if (config.empty()) load_config_values();
for (const auto& pair : config) {
if (pair.key == key) {
thread_local std::string value_cache;
value_cache = pair.value;
return value_cache.c_str();
}
}
return NULL;
return nullptr;
}
void file_setconfigvalue(const char* key, const char* value) {
if (config.empty()) file_loadconfigvalues();
if (config.empty()) load_config_values();
for (auto& pair : config) {
if (pair.key == std::string(key)) {
if (pair.key == key) {
pair.value = value;
file_saveconfigvalues();
save_config_values();
return;
}
}
config.push_back({key, value});
file_saveconfigvalues();
return;
config.push_back({std::string(key), std::string(value)});
save_config_values();
}

View File

@@ -1,42 +1,54 @@
#include "core/jail/jgame.hpp"
bool eixir = false;
Uint32 updateTicks = 0;
Uint32 updateTime = 0;
Uint32 cycle_counter = 0;
void JG_Init() {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
// SDL_WM_SetCaption( title, NULL );
updateTime = SDL_GetTicks();
}
void JG_Finalize() {
SDL_Quit();
}
void JG_QuitSignal() {
eixir = true;
}
bool JG_Quitting() {
return eixir;
}
void JG_SetUpdateTicks(Uint32 milliseconds) {
updateTicks = milliseconds;
}
bool JG_ShouldUpdate() {
if (SDL_GetTicks() - updateTime > updateTicks) {
updateTime = SDL_GetTicks();
cycle_counter++;
return true;
} else {
return false;
}
}
Uint32 JG_GetCycleCounter() {
return cycle_counter;
}
#include "core/jail/jgame.hpp"
namespace {
bool quitting = false;
Uint32 update_ticks = 0;
Uint32 update_time = 0;
Uint32 cycle_counter = 0;
Uint32 last_delta_time = 0;
} // namespace
void JG_Init() {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
update_time = SDL_GetTicks();
last_delta_time = update_time;
}
void JG_Finalize() {
SDL_Quit();
}
void JG_QuitSignal() {
quitting = true;
}
bool JG_Quitting() {
return quitting;
}
void JG_SetUpdateTicks(Uint32 milliseconds) {
update_ticks = milliseconds;
}
bool JG_ShouldUpdate() {
const Uint32 now = SDL_GetTicks();
if (now - update_time > update_ticks) {
update_time = now;
cycle_counter++;
return true;
}
return false;
}
Uint32 JG_GetCycleCounter() {
return cycle_counter;
}
Uint32 JG_GetDeltaMs() {
const Uint32 now = SDL_GetTicks();
const Uint32 delta = now - last_delta_time;
last_delta_time = now;
return delta;
}

View File

@@ -14,3 +14,7 @@ void JG_SetUpdateTicks(Uint32 milliseconds);
bool JG_ShouldUpdate();
Uint32 JG_GetCycleCounter();
// Temps transcorregut (en ms) des de l'última crida a JG_GetDeltaMs.
// Helper per a la migració progressiva a time-based (Fase 4+).
Uint32 JG_GetDeltaMs();

View File

@@ -1,39 +1,63 @@
#include "core/jail/jinput.hpp"
#include <string>
#include <cstring>
#include "core/system/director.hpp"
namespace {
// keystates és actualitzat per SDL internament. Des del joc només fem lectures.
const bool* keystates = nullptr;
Uint8 cheat[5];
bool key_pressed = false;
int waitTime = 0;
void JI_DisableKeyboard(Uint32 time) {
waitTime = time;
// Buffer dels últims 5 caràcters tecle. Emmagatzemem caràcters ASCII
// lowercase (traduïts des de SDL_Scancode) per a poder comparar directament
// amb les cadenes dels cheats ("reviu", "alone", "obert").
Uint8 cheat[5] = {0, 0, 0, 0, 0};
bool key_pressed = false;
// Temps restant en mil·lisegons durant el qual JI_KeyPressed/JI_AnyKey
// retornen false. Utilitzat per a evitar que pulsacions fortuïtes
// saltin cinemàtiques al començament.
float wait_ms = 0.0f;
// Per a calcular el delta entre crides a JI_Update sense que els callers
// hagen de passar-lo explícitament. Es reinicia a la primera crida.
Uint64 last_update_tick = 0;
bool input_blocked = false;
Uint8 virtual_keystates[JI_VSRC_COUNT][SDL_SCANCODE_COUNT] = {{0}};
Uint8 scancode_to_ascii(Uint8 scancode) {
if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) {
return static_cast<Uint8>('a' + (scancode - SDL_SCANCODE_A));
}
return 0;
}
static bool input_blocked = false;
} // namespace
void JI_DisableKeyboard(Uint32 time) {
wait_ms = static_cast<float>(time);
}
void JI_SetInputBlocked(bool blocked) {
input_blocked = blocked;
}
static Uint8 virtual_keystates[JI_VSRC_COUNT][SDL_SCANCODE_COUNT] = {{0}};
void JI_SetVirtualKey(int scancode, int source, bool pressed) {
if (scancode < 0 || scancode >= SDL_SCANCODE_COUNT) return;
if (source < 0 || source >= JI_VSRC_COUNT) return;
virtual_keystates[source][scancode] = pressed ? 1 : 0;
}
void JI_moveCheats(Uint8 new_key) {
void JI_moveCheats(Uint8 scancode) {
cheat[0] = cheat[1];
cheat[1] = cheat[2];
cheat[2] = cheat[3];
cheat[3] = cheat[4];
cheat[4] = new_key;
cheat[4] = scancode_to_ascii(scancode);
}
void JI_Update() {
@@ -43,14 +67,22 @@ void JI_Update() {
keystates = SDL_GetKeyboardState(NULL);
}
if (waitTime > 0) waitTime--;
const Uint64 now = SDL_GetTicks();
if (last_update_tick == 0) last_update_tick = now;
const float delta_ms = static_cast<float>(now - last_update_tick);
last_update_tick = now;
if (wait_ms > 0.0f) {
wait_ms -= delta_ms;
if (wait_ms < 0.0f) wait_ms = 0.0f;
}
// Consumim el flag de "alguna tecla no-GUI polsada" del director
key_pressed = Director::get()->consumeKeyPressed();
}
bool JI_KeyPressed(int key) {
if (waitTime > 0 || keystates == nullptr) return false;
if (wait_ms > 0.0f || keystates == nullptr) return false;
// Input bloquejat (p.ex. menú flotant obert)
if (input_blocked) return false;
// ESC bloquejada pel Director (primera pulsació mostra notificació)
@@ -64,13 +96,17 @@ bool JI_KeyPressed(int key) {
}
bool JI_CheatActivated(const char* cheat_code) {
bool found = true;
for (size_t i = 0; i < strlen(cheat_code); i++) {
if (cheat[i] != cheat_code[i]) found = false;
const size_t len = std::strlen(cheat_code);
if (len > sizeof(cheat)) return false;
// Compara contra els últims `len` caràcters del buffer. El buffer té
// mida fixa 5 i acumula sempre el darrer tecle a la posició 4.
const size_t offset = sizeof(cheat) - len;
for (size_t i = 0; i < len; i++) {
if (cheat[offset + i] != static_cast<Uint8>(cheat_code[i])) return false;
}
return found;
return true;
}
bool JI_AnyKey() {
return waitTime > 0 ? false : key_pressed;
return wait_ms > 0.0f ? false : key_pressed;
}

View File

@@ -72,10 +72,10 @@ void Director::run() {
Mouse::updateCursorVisibility();
// Dispara els crèdits cinematogràfics la primera vegada que el joc
// arriba al menú del títol (info::num_piramide == 0). Lectura no
// arriba al menú del títol (info::ctx.num_piramide == 0). Lectura no
// atòmica d'un int global: race benigna, tard d'1 frame en el pitjor cas.
static bool credits_triggered = false;
if (!credits_triggered && info::num_piramide == 0) {
if (!credits_triggered && info::ctx.num_piramide == 0) {
if (Options::game.show_title_credits) {
Overlay::startCredits();
}
@@ -279,18 +279,18 @@ auto Director::consumeKeyPressed() -> bool {
}
void Director::gameThreadFunc() {
info::num_habitacio = Options::game.habitacio_inicial;
info::num_piramide = Options::game.piramide_inicial;
info::diners = 0;
info::diamants = 0;
info::vida = Options::game.vides;
info::momies = 0;
info::nou_personatge = false;
info::pepe_activat = false;
info::ctx.num_habitacio = Options::game.habitacio_inicial;
info::ctx.num_piramide = Options::game.piramide_inicial;
info::ctx.diners = 0;
info::ctx.diamants = 0;
info::ctx.vida = Options::game.vides;
info::ctx.momies = 0;
info::ctx.nou_personatge = false;
info::ctx.pepe_activat = false;
FILE* ini = fopen("trick.ini", "rb");
if (ini != nullptr) {
info::nou_personatge = true;
info::ctx.nou_personatge = true;
fclose(ini);
}