resource.pack
This commit is contained in:
@@ -6,128 +6,108 @@
|
||||
|
||||
#include "texture.h" // for Texture
|
||||
|
||||
// Carga la animación desde un fichero
|
||||
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose) {
|
||||
// Inicializa variables
|
||||
// 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;
|
||||
|
||||
const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1);
|
||||
std::ifstream file(filePath);
|
||||
std::string line;
|
||||
|
||||
// El fichero se puede abrir
|
||||
if (file.good()) {
|
||||
// Procesa el fichero linea a linea
|
||||
if (verbose) {
|
||||
std::cout << "Animation loaded: " << filename << std::endl;
|
||||
}
|
||||
while (std::getline(file, line)) {
|
||||
// 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;
|
||||
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);
|
||||
|
||||
// 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: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||
}
|
||||
}
|
||||
} while (line != "[/animation]");
|
||||
|
||||
// Añade la animación al vector de animaciones
|
||||
as.animations.push_back(buffer);
|
||||
}
|
||||
|
||||
// En caso contrario se parsea el fichero para buscar las variables y los valores
|
||||
else {
|
||||
// Encuentra la posición del caracter '='
|
||||
do {
|
||||
std::getline(file, line);
|
||||
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 {
|
||||
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]");
|
||||
|
||||
// Normaliza valores
|
||||
if (framesPerRow == 0 && frameWidth > 0) {
|
||||
framesPerRow = texture->getWidth() / frameWidth;
|
||||
}
|
||||
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 (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
|
||||
const int w = texture->getWidth() / frameWidth;
|
||||
const int h = texture->getHeight() / frameHeight;
|
||||
maxTiles = w * h;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
file.close();
|
||||
}
|
||||
// El fichero no se puede abrir
|
||||
else {
|
||||
if (verbose) {
|
||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
@@ -26,6 +27,9 @@ struct animatedSprite_t {
|
||||
// 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
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||
|
||||
#include "resource_helper.h"
|
||||
|
||||
// Constructor
|
||||
Asset::Asset(std::string executablePath) {
|
||||
this->executablePath = executablePath.substr(0, executablePath.find_last_of("\\/"));
|
||||
@@ -96,14 +98,21 @@ bool Asset::checkFile(std::string path) {
|
||||
bool success = false;
|
||||
std::string result = "ERROR";
|
||||
|
||||
// Comprueba si existe el fichero
|
||||
// Comprueba si existe el fichero (pack o filesystem)
|
||||
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
||||
SDL_IOStream *file = SDL_IOFromFile(path.c_str(), "rb");
|
||||
|
||||
if (file != nullptr) {
|
||||
result = "OK";
|
||||
success = true;
|
||||
SDL_CloseIO(file);
|
||||
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) {
|
||||
|
||||
@@ -18,15 +18,15 @@ enum assetType {
|
||||
|
||||
// Clase Asset
|
||||
class Asset {
|
||||
private:
|
||||
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
|
||||
// bool absolute; // Indica si la ruta que se ha proporcionado es una ruta absoluta
|
||||
};
|
||||
|
||||
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
|
||||
@@ -49,6 +49,9 @@ class Asset {
|
||||
// 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();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#endif
|
||||
|
||||
#include <cstdlib> // for exit, EXIT_FAILURE, srand
|
||||
#include <filesystem>
|
||||
#include <fstream> // for basic_ostream, operator<<, basi...
|
||||
#include <iostream> // for cout
|
||||
#include <memory>
|
||||
@@ -23,6 +24,8 @@
|
||||
#include "intro.h" // for Intro
|
||||
#include "jail_audio.hpp" // for JA_Init
|
||||
#include "lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
|
||||
#include "resource.h"
|
||||
#include "resource_helper.h"
|
||||
#include "logo.h" // for Logo
|
||||
#include "mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
|
||||
#include "screen.h" // for FILTER_NEAREST, Screen, FILTER_...
|
||||
@@ -55,6 +58,19 @@ Director::Director(int argc, const char *argv[]) {
|
||||
createSystemFolder("jailgames/coffee_crisis_debug");
|
||||
#endif
|
||||
|
||||
// Inicializa el sistema de recursos (pack + fallback)
|
||||
{
|
||||
#ifdef 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);
|
||||
@@ -80,9 +96,25 @@ Director::Director(int argc, const char *argv[]) {
|
||||
lang = new Lang(asset);
|
||||
lang->setLang(options->language);
|
||||
|
||||
input = new Input(asset->get("gamecontrollerdb.txt"));
|
||||
#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;
|
||||
@@ -99,9 +131,15 @@ Director::~Director() {
|
||||
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 screen;
|
||||
delete lang;
|
||||
delete options;
|
||||
delete section;
|
||||
@@ -111,6 +149,8 @@ Director::~Director() {
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
ResourceHelper::shutdownResourceSystem();
|
||||
|
||||
std::cout << "\nBye!" << std::endl;
|
||||
}
|
||||
|
||||
@@ -240,8 +280,7 @@ bool Director::setFileList() {
|
||||
// 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/config/demo.bin", t_data);
|
||||
asset->add(prefix + "/data/config/gamecontrollerdb.txt", t_data);
|
||||
asset->add(prefix + "/data/demo/demo.bin", t_data);
|
||||
|
||||
// Musicas
|
||||
asset->add(prefix + "/data/music/intro.ogg", t_music);
|
||||
|
||||
438
source/game.cpp
438
source/game.cpp
@@ -15,6 +15,7 @@
|
||||
#include "input.h" // for inputs_e, Input, REPEAT_TRUE, REPEAT_FALSE
|
||||
#include "item.h" // for Item, ITEM_COFFEE_MACHINE, ITEM_CLOCK
|
||||
#include "jail_audio.hpp" // for JA_PlaySound, JA_DeleteSound, JA_LoadSound
|
||||
#include "resource.h"
|
||||
#include "lang.h" // for Lang
|
||||
#include "menu.h" // for Menu
|
||||
#include "movingsprite.h" // for MovingSprite
|
||||
@@ -98,84 +99,19 @@ Game::~Game() {
|
||||
options->input[0].deviceType = onePlayerControl;
|
||||
}
|
||||
|
||||
// Elimina todos los objetos contenidos en vectores
|
||||
// Elimina todos los objetos contenidos en vectores (jugadores, balas, etc.)
|
||||
deleteAllVectorObjects();
|
||||
|
||||
bulletTexture->unload();
|
||||
delete bulletTexture;
|
||||
|
||||
gameBuildingsTexture->unload();
|
||||
delete gameBuildingsTexture;
|
||||
|
||||
gameCloudsTexture->unload();
|
||||
delete gameCloudsTexture;
|
||||
|
||||
gameGrassTexture->unload();
|
||||
delete gameGrassTexture;
|
||||
|
||||
gamePowerMeterTexture->unload();
|
||||
delete gamePowerMeterTexture;
|
||||
|
||||
gameSkyColorsTexture->unload();
|
||||
delete gameSkyColorsTexture;
|
||||
|
||||
gameTextTexture->unload();
|
||||
delete gameTextTexture;
|
||||
|
||||
gameOverTexture->unload();
|
||||
delete gameOverTexture;
|
||||
|
||||
gameOverEndTexture->unload();
|
||||
delete gameOverEndTexture;
|
||||
|
||||
// Animaciones
|
||||
for (auto animation : playerAnimations) {
|
||||
delete animation;
|
||||
}
|
||||
// Las texturas, animaciones, Text, Menu, sonidos y música son propiedad
|
||||
// de Resource — no se liberan aquí. Solo limpiamos nuestras vistas.
|
||||
playerAnimations.clear();
|
||||
|
||||
for (auto animation : balloonAnimations) {
|
||||
delete animation;
|
||||
}
|
||||
balloonAnimations.clear();
|
||||
|
||||
for (auto animation : itemAnimations) {
|
||||
delete animation;
|
||||
}
|
||||
itemAnimations.clear();
|
||||
|
||||
// Texturas
|
||||
for (auto texture : player1Textures) {
|
||||
texture->unload();
|
||||
delete texture;
|
||||
}
|
||||
player1Textures.clear();
|
||||
|
||||
for (auto texture : player2Textures) {
|
||||
texture->unload();
|
||||
delete texture;
|
||||
}
|
||||
player2Textures.clear();
|
||||
|
||||
for (auto texture : itemTextures) {
|
||||
texture->unload();
|
||||
delete texture;
|
||||
}
|
||||
itemTextures.clear();
|
||||
|
||||
for (auto texture : balloonTextures) {
|
||||
texture->unload();
|
||||
delete texture;
|
||||
}
|
||||
balloonTextures.clear();
|
||||
|
||||
delete text;
|
||||
delete textBig;
|
||||
delete textScoreBoard;
|
||||
delete textNokia2;
|
||||
delete textNokiaBig2;
|
||||
delete gameOverMenu;
|
||||
delete pauseMenu;
|
||||
delete fade;
|
||||
delete eventHandler;
|
||||
delete clouds1A;
|
||||
@@ -192,24 +128,6 @@ Game::~Game() {
|
||||
delete powerMeterSprite;
|
||||
delete gameOverSprite;
|
||||
delete gameOverEndSprite;
|
||||
|
||||
JA_DeleteSound(balloonSound);
|
||||
JA_DeleteSound(bulletSound);
|
||||
JA_DeleteSound(playerCollisionSound);
|
||||
JA_DeleteSound(hiScoreSound);
|
||||
JA_DeleteSound(itemDropSound);
|
||||
JA_DeleteSound(itemPickUpSound);
|
||||
JA_DeleteSound(coffeeOutSound);
|
||||
JA_DeleteSound(stageChangeSound);
|
||||
JA_DeleteSound(bubble1Sound);
|
||||
JA_DeleteSound(bubble2Sound);
|
||||
JA_DeleteSound(bubble3Sound);
|
||||
JA_DeleteSound(bubble4Sound);
|
||||
JA_DeleteSound(clockSound);
|
||||
JA_DeleteSound(powerBallSound);
|
||||
JA_DeleteSound(coffeeMachineSound);
|
||||
|
||||
JA_DeleteMusic(gameMusic);
|
||||
}
|
||||
|
||||
// Inicializa las variables necesarias para la sección 'Game'
|
||||
@@ -414,186 +332,106 @@ void Game::loadMedia() {
|
||||
<< "** LOADING RESOURCES FOR GAME SECTION" << std::endl;
|
||||
}
|
||||
|
||||
// Texturas
|
||||
bulletTexture = new Texture(renderer, asset->get("bullet.png"));
|
||||
gameBuildingsTexture = new Texture(renderer, asset->get("game_buildings.png"));
|
||||
gameCloudsTexture = new Texture(renderer, asset->get("game_clouds.png"));
|
||||
gameGrassTexture = new Texture(renderer, asset->get("game_grass.png"));
|
||||
gamePowerMeterTexture = new Texture(renderer, asset->get("game_power_meter.png"));
|
||||
gameSkyColorsTexture = new Texture(renderer, asset->get("game_sky_colors.png"));
|
||||
gameTextTexture = new Texture(renderer, asset->get("game_text.png"));
|
||||
gameOverTexture = new Texture(renderer, asset->get("menu_game_over.png"));
|
||||
gameOverEndTexture = new Texture(renderer, asset->get("menu_game_over_end.png"));
|
||||
Resource *R = Resource::get();
|
||||
|
||||
// Texturas (handles compartidos — no se liberan aquí)
|
||||
bulletTexture = R->getTexture("bullet.png");
|
||||
gameBuildingsTexture = R->getTexture("game_buildings.png");
|
||||
gameCloudsTexture = R->getTexture("game_clouds.png");
|
||||
gameGrassTexture = R->getTexture("game_grass.png");
|
||||
gamePowerMeterTexture = R->getTexture("game_power_meter.png");
|
||||
gameSkyColorsTexture = R->getTexture("game_sky_colors.png");
|
||||
gameTextTexture = R->getTexture("game_text.png");
|
||||
gameOverTexture = R->getTexture("menu_game_over.png");
|
||||
gameOverEndTexture = R->getTexture("menu_game_over_end.png");
|
||||
|
||||
// Texturas - Globos
|
||||
Texture *balloon1Texture = new Texture(renderer, asset->get("balloon1.png"));
|
||||
balloonTextures.push_back(balloon1Texture);
|
||||
|
||||
Texture *balloon2Texture = new Texture(renderer, asset->get("balloon2.png"));
|
||||
balloonTextures.push_back(balloon2Texture);
|
||||
|
||||
Texture *balloon3Texture = new Texture(renderer, asset->get("balloon3.png"));
|
||||
balloonTextures.push_back(balloon3Texture);
|
||||
|
||||
Texture *balloon4Texture = new Texture(renderer, asset->get("balloon4.png"));
|
||||
balloonTextures.push_back(balloon4Texture);
|
||||
balloonTextures.push_back(R->getTexture("balloon1.png"));
|
||||
balloonTextures.push_back(R->getTexture("balloon2.png"));
|
||||
balloonTextures.push_back(R->getTexture("balloon3.png"));
|
||||
balloonTextures.push_back(R->getTexture("balloon4.png"));
|
||||
|
||||
// Texturas - Items
|
||||
Texture *item1 = new Texture(renderer, asset->get("item_points1_disk.png"));
|
||||
itemTextures.push_back(item1);
|
||||
|
||||
Texture *item2 = new Texture(renderer, asset->get("item_points2_gavina.png"));
|
||||
itemTextures.push_back(item2);
|
||||
|
||||
Texture *item3 = new Texture(renderer, asset->get("item_points3_pacmar.png"));
|
||||
itemTextures.push_back(item3);
|
||||
|
||||
Texture *item4 = new Texture(renderer, asset->get("item_clock.png"));
|
||||
itemTextures.push_back(item4);
|
||||
|
||||
Texture *item5 = new Texture(renderer, asset->get("item_coffee.png"));
|
||||
itemTextures.push_back(item5);
|
||||
|
||||
Texture *item6 = new Texture(renderer, asset->get("item_coffee_machine.png"));
|
||||
itemTextures.push_back(item6);
|
||||
itemTextures.push_back(R->getTexture("item_points1_disk.png"));
|
||||
itemTextures.push_back(R->getTexture("item_points2_gavina.png"));
|
||||
itemTextures.push_back(R->getTexture("item_points3_pacmar.png"));
|
||||
itemTextures.push_back(R->getTexture("item_clock.png"));
|
||||
itemTextures.push_back(R->getTexture("item_coffee.png"));
|
||||
itemTextures.push_back(R->getTexture("item_coffee_machine.png"));
|
||||
|
||||
// Texturas - Player1
|
||||
Texture *player1Head = new Texture(renderer, asset->get("player_bal1_head.png"));
|
||||
player1Textures.push_back(player1Head);
|
||||
|
||||
Texture *player1Body = new Texture(renderer, asset->get("player_bal1_body.png"));
|
||||
player1Textures.push_back(player1Body);
|
||||
|
||||
Texture *player1Legs = new Texture(renderer, asset->get("player_bal1_legs.png"));
|
||||
player1Textures.push_back(player1Legs);
|
||||
|
||||
Texture *player1Death = new Texture(renderer, asset->get("player_bal1_death.png"));
|
||||
player1Textures.push_back(player1Death);
|
||||
|
||||
Texture *player1Fire = new Texture(renderer, asset->get("player_bal1_fire.png"));
|
||||
player1Textures.push_back(player1Fire);
|
||||
|
||||
player1Textures.push_back(R->getTexture("player_bal1_head.png"));
|
||||
player1Textures.push_back(R->getTexture("player_bal1_body.png"));
|
||||
player1Textures.push_back(R->getTexture("player_bal1_legs.png"));
|
||||
player1Textures.push_back(R->getTexture("player_bal1_death.png"));
|
||||
player1Textures.push_back(R->getTexture("player_bal1_fire.png"));
|
||||
playerTextures.push_back(player1Textures);
|
||||
|
||||
// Texturas - Player2
|
||||
Texture *player2Head = new Texture(renderer, asset->get("player_arounder_head.png"));
|
||||
player2Textures.push_back(player2Head);
|
||||
|
||||
Texture *player2Body = new Texture(renderer, asset->get("player_arounder_body.png"));
|
||||
player2Textures.push_back(player2Body);
|
||||
|
||||
Texture *player2Legs = new Texture(renderer, asset->get("player_arounder_legs.png"));
|
||||
player2Textures.push_back(player2Legs);
|
||||
|
||||
Texture *player2Death = new Texture(renderer, asset->get("player_arounder_death.png"));
|
||||
player2Textures.push_back(player2Death);
|
||||
|
||||
Texture *player2Fire = new Texture(renderer, asset->get("player_arounder_fire.png"));
|
||||
player2Textures.push_back(player2Fire);
|
||||
|
||||
player2Textures.push_back(R->getTexture("player_arounder_head.png"));
|
||||
player2Textures.push_back(R->getTexture("player_arounder_body.png"));
|
||||
player2Textures.push_back(R->getTexture("player_arounder_legs.png"));
|
||||
player2Textures.push_back(R->getTexture("player_arounder_death.png"));
|
||||
player2Textures.push_back(R->getTexture("player_arounder_fire.png"));
|
||||
playerTextures.push_back(player2Textures);
|
||||
|
||||
// Animaciones -- Jugador
|
||||
std::vector<std::string> *playerHeadAnimation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("player_head.ani"), playerHeadAnimation);
|
||||
playerAnimations.push_back(playerHeadAnimation);
|
||||
// Animaciones (handles a los vectores raw de Resource — no se liberan)
|
||||
playerAnimations.push_back(&R->getAnimationLines("player_head.ani"));
|
||||
playerAnimations.push_back(&R->getAnimationLines("player_body.ani"));
|
||||
playerAnimations.push_back(&R->getAnimationLines("player_legs.ani"));
|
||||
playerAnimations.push_back(&R->getAnimationLines("player_death.ani"));
|
||||
playerAnimations.push_back(&R->getAnimationLines("player_fire.ani"));
|
||||
|
||||
std::vector<std::string> *playerBodyAnimation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("player_body.ani"), playerBodyAnimation);
|
||||
playerAnimations.push_back(playerBodyAnimation);
|
||||
balloonAnimations.push_back(&R->getAnimationLines("balloon1.ani"));
|
||||
balloonAnimations.push_back(&R->getAnimationLines("balloon2.ani"));
|
||||
balloonAnimations.push_back(&R->getAnimationLines("balloon3.ani"));
|
||||
balloonAnimations.push_back(&R->getAnimationLines("balloon4.ani"));
|
||||
|
||||
std::vector<std::string> *playerLegsAnimation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("player_legs.ani"), playerLegsAnimation);
|
||||
playerAnimations.push_back(playerLegsAnimation);
|
||||
|
||||
std::vector<std::string> *playerDeathAnimation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("player_death.ani"), playerDeathAnimation);
|
||||
playerAnimations.push_back(playerDeathAnimation);
|
||||
|
||||
std::vector<std::string> *playerFireAnimation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("player_fire.ani"), playerFireAnimation);
|
||||
playerAnimations.push_back(playerFireAnimation);
|
||||
|
||||
// Animaciones -- Globos
|
||||
std::vector<std::string> *balloon1Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("balloon1.ani"), balloon1Animation);
|
||||
balloonAnimations.push_back(balloon1Animation);
|
||||
|
||||
std::vector<std::string> *balloon2Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("balloon2.ani"), balloon2Animation);
|
||||
balloonAnimations.push_back(balloon2Animation);
|
||||
|
||||
std::vector<std::string> *balloon3Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("balloon3.ani"), balloon3Animation);
|
||||
balloonAnimations.push_back(balloon3Animation);
|
||||
|
||||
std::vector<std::string> *balloon4Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("balloon4.ani"), balloon4Animation);
|
||||
balloonAnimations.push_back(balloon4Animation);
|
||||
|
||||
// Animaciones -- Items
|
||||
std::vector<std::string> *item1Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("item_points1_disk.ani"), item1Animation);
|
||||
itemAnimations.push_back(item1Animation);
|
||||
|
||||
std::vector<std::string> *item2Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("item_points2_gavina.ani"), item2Animation);
|
||||
itemAnimations.push_back(item2Animation);
|
||||
|
||||
std::vector<std::string> *item3Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("item_points3_pacmar.ani"), item3Animation);
|
||||
itemAnimations.push_back(item3Animation);
|
||||
|
||||
std::vector<std::string> *item4Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("item_clock.ani"), item4Animation);
|
||||
itemAnimations.push_back(item4Animation);
|
||||
|
||||
std::vector<std::string> *item5Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("item_coffee.ani"), item5Animation);
|
||||
itemAnimations.push_back(item5Animation);
|
||||
|
||||
std::vector<std::string> *item6Animation = new std::vector<std::string>;
|
||||
loadAnimations(asset->get("item_coffee_machine.ani"), item6Animation);
|
||||
itemAnimations.push_back(item6Animation);
|
||||
itemAnimations.push_back(&R->getAnimationLines("item_points1_disk.ani"));
|
||||
itemAnimations.push_back(&R->getAnimationLines("item_points2_gavina.ani"));
|
||||
itemAnimations.push_back(&R->getAnimationLines("item_points3_pacmar.ani"));
|
||||
itemAnimations.push_back(&R->getAnimationLines("item_clock.ani"));
|
||||
itemAnimations.push_back(&R->getAnimationLines("item_coffee.ani"));
|
||||
itemAnimations.push_back(&R->getAnimationLines("item_coffee_machine.ani"));
|
||||
|
||||
// Texto
|
||||
text = new Text(asset->get("smb2.png"), asset->get("smb2.txt"), renderer);
|
||||
textScoreBoard = new Text(asset->get("8bithud.png"), asset->get("8bithud.txt"), renderer);
|
||||
textBig = new Text(asset->get("smb2_big.png"), asset->get("smb2_big.txt"), renderer);
|
||||
textNokia2 = new Text(asset->get("nokia2.png"), asset->get("nokia2.txt"), renderer);
|
||||
textNokiaBig2 = new Text(asset->get("nokia_big2.png"), asset->get("nokia_big2.txt"), renderer);
|
||||
text = R->getText("smb2");
|
||||
textScoreBoard = R->getText("8bithud");
|
||||
textBig = R->getText("smb2_big");
|
||||
textNokia2 = R->getText("nokia2");
|
||||
textNokiaBig2 = R->getText("nokia_big2");
|
||||
|
||||
// Menus
|
||||
gameOverMenu = new Menu(renderer, asset, input, asset->get("gameover.men"));
|
||||
gameOverMenu = R->getMenu("gameover");
|
||||
gameOverMenu->setItemCaption(0, lang->getText(48));
|
||||
gameOverMenu->setItemCaption(1, lang->getText(49));
|
||||
const int w = text->getCharacterSize() * lang->getText(45).length();
|
||||
gameOverMenu->setRectSize(w, 0);
|
||||
gameOverMenu->centerMenuOnX(199);
|
||||
pauseMenu = new Menu(renderer, asset, input, asset->get("pause.men"));
|
||||
pauseMenu = R->getMenu("pause");
|
||||
pauseMenu->setItemCaption(0, lang->getText(41));
|
||||
pauseMenu->setItemCaption(1, lang->getText(46));
|
||||
pauseMenu->setItemCaption(2, lang->getText(47));
|
||||
|
||||
// Sonidos
|
||||
balloonSound = JA_LoadSound(asset->get("balloon.wav").c_str());
|
||||
bubble1Sound = JA_LoadSound(asset->get("bubble1.wav").c_str());
|
||||
bubble2Sound = JA_LoadSound(asset->get("bubble2.wav").c_str());
|
||||
bubble3Sound = JA_LoadSound(asset->get("bubble3.wav").c_str());
|
||||
bubble4Sound = JA_LoadSound(asset->get("bubble4.wav").c_str());
|
||||
bulletSound = JA_LoadSound(asset->get("bullet.wav").c_str());
|
||||
clockSound = JA_LoadSound(asset->get("clock.wav").c_str());
|
||||
coffeeOutSound = JA_LoadSound(asset->get("coffeeout.wav").c_str());
|
||||
hiScoreSound = JA_LoadSound(asset->get("hiscore.wav").c_str());
|
||||
itemDropSound = JA_LoadSound(asset->get("itemdrop.wav").c_str());
|
||||
itemPickUpSound = JA_LoadSound(asset->get("itempickup.wav").c_str());
|
||||
playerCollisionSound = JA_LoadSound(asset->get("player_collision.wav").c_str());
|
||||
powerBallSound = JA_LoadSound(asset->get("powerball.wav").c_str());
|
||||
stageChangeSound = JA_LoadSound(asset->get("stage_change.wav").c_str());
|
||||
coffeeMachineSound = JA_LoadSound(asset->get("title.wav").c_str());
|
||||
balloonSound = R->getSound("balloon.wav");
|
||||
bubble1Sound = R->getSound("bubble1.wav");
|
||||
bubble2Sound = R->getSound("bubble2.wav");
|
||||
bubble3Sound = R->getSound("bubble3.wav");
|
||||
bubble4Sound = R->getSound("bubble4.wav");
|
||||
bulletSound = R->getSound("bullet.wav");
|
||||
clockSound = R->getSound("clock.wav");
|
||||
coffeeOutSound = R->getSound("coffeeout.wav");
|
||||
hiScoreSound = R->getSound("hiscore.wav");
|
||||
itemDropSound = R->getSound("itemdrop.wav");
|
||||
itemPickUpSound = R->getSound("itempickup.wav");
|
||||
playerCollisionSound = R->getSound("player_collision.wav");
|
||||
powerBallSound = R->getSound("powerball.wav");
|
||||
stageChangeSound = R->getSound("stage_change.wav");
|
||||
coffeeMachineSound = R->getSound("title.wav");
|
||||
|
||||
// Musicas
|
||||
gameMusic = JA_LoadMusic(asset->get("playing.ogg").c_str());
|
||||
gameMusic = R->getMusic("playing.ogg");
|
||||
|
||||
if (options->console) {
|
||||
std::cout << "** RESOURCES FOR GAME SECTION LOADED" << std::endl
|
||||
@@ -666,60 +504,32 @@ bool Game::loadScoreFile() {
|
||||
|
||||
// Carga el fichero de datos para la demo
|
||||
bool Game::loadDemoFile() {
|
||||
// Indicador de éxito en la carga
|
||||
bool success = true;
|
||||
const std::string p = asset->get("demo.bin");
|
||||
const std::string filename = p.substr(p.find_last_of("\\/") + 1);
|
||||
SDL_IOStream *file = SDL_IOFromFile(p.c_str(), "r+b");
|
||||
|
||||
// El fichero no existe
|
||||
if (file == nullptr) {
|
||||
if (options->console) {
|
||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||
// Lee los datos de la demo desde Resource (precargados al arrancar).
|
||||
const auto &bytes = Resource::get()->getDemoBytes();
|
||||
const size_t expected = sizeof(demoKeys_t) * TOTAL_DEMO_DATA;
|
||||
if (bytes.size() >= expected) {
|
||||
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
|
||||
memcpy(&demo.dataFile[i], bytes.data() + i * sizeof(demoKeys_t), sizeof(demoKeys_t));
|
||||
}
|
||||
|
||||
// Creamos el fichero para escritura
|
||||
file = SDL_IOFromFile(p.c_str(), "w+b");
|
||||
if (file != nullptr) {
|
||||
if (options->console) {
|
||||
std::cout << "New file (" << filename.c_str() << ") created!" << std::endl;
|
||||
}
|
||||
|
||||
// Inicializamos los datos
|
||||
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
|
||||
demo.keys.left = 0;
|
||||
demo.keys.right = 0;
|
||||
demo.keys.noInput = 0;
|
||||
demo.keys.fire = 0;
|
||||
demo.keys.fireLeft = 0;
|
||||
demo.keys.fireRight = 0;
|
||||
demo.dataFile[i] = demo.keys;
|
||||
SDL_WriteIO(file, &demo.dataFile[i], sizeof(demoKeys_t));
|
||||
}
|
||||
|
||||
// Cerramos el fichero
|
||||
SDL_CloseIO(file);
|
||||
} else {
|
||||
if (options->console) {
|
||||
std::cout << "Error: Unable to create file " << filename.c_str() << std::endl;
|
||||
}
|
||||
success = false;
|
||||
if (options->console) {
|
||||
std::cout << "Demo data loaded (" << bytes.size() << " bytes)" << std::endl;
|
||||
}
|
||||
} else {
|
||||
// Si no hay datos (bytes vacíos o tamaño inválido), inicializamos a cero.
|
||||
if (options->console) {
|
||||
std::cout << "Warning: demo data missing or too small, initializing to zero" << std::endl;
|
||||
}
|
||||
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
|
||||
demo.keys.left = 0;
|
||||
demo.keys.right = 0;
|
||||
demo.keys.noInput = 0;
|
||||
demo.keys.fire = 0;
|
||||
demo.keys.fireLeft = 0;
|
||||
demo.keys.fireRight = 0;
|
||||
demo.dataFile[i] = demo.keys;
|
||||
}
|
||||
}
|
||||
// El fichero existe
|
||||
else {
|
||||
// Cargamos los datos
|
||||
if (options->console) {
|
||||
std::cout << "Reading file " << filename.c_str() << std::endl;
|
||||
}
|
||||
for (int i = 0; i < TOTAL_DEMO_DATA; ++i)
|
||||
SDL_ReadIO(file, &demo.dataFile[i], sizeof(demoKeys_t));
|
||||
|
||||
// Cierra el fichero
|
||||
SDL_CloseIO(file);
|
||||
}
|
||||
|
||||
return success;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Guarda el fichero de puntos
|
||||
@@ -1690,9 +1500,11 @@ void Game::updateDeath() {
|
||||
|
||||
if ((deathCounter == 250) || (deathCounter == 200) || (deathCounter == 180) || (deathCounter == 120) || (deathCounter == 60)) {
|
||||
// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas
|
||||
const Uint8 index = rand() % 4;
|
||||
JA_Sound_t *sound[4] = {bubble1Sound, bubble2Sound, bubble3Sound, bubble4Sound};
|
||||
JA_PlaySound(sound[index], 0);
|
||||
if (!demo.enabled) {
|
||||
const Uint8 index = rand() % 4;
|
||||
JA_Sound_t *sound[4] = {bubble1Sound, bubble2Sound, bubble3Sound, bubble4Sound};
|
||||
JA_PlaySound(sound[index], 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
section->subsection = SUBSECTION_GAME_GAMEOVER;
|
||||
@@ -2373,11 +2185,15 @@ void Game::killPlayer(Player *player) {
|
||||
if (player->hasExtraHit()) {
|
||||
player->removeExtraHit();
|
||||
throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2));
|
||||
JA_PlaySound(coffeeOutSound);
|
||||
if (!demo.enabled) {
|
||||
JA_PlaySound(coffeeOutSound);
|
||||
}
|
||||
} else if (deathSequence.phase == DeathPhase::None) {
|
||||
JA_PauseMusic();
|
||||
if (!demo.enabled) {
|
||||
JA_PauseMusic();
|
||||
JA_PlaySound(playerCollisionSound);
|
||||
}
|
||||
stopAllBalloons(10);
|
||||
JA_PlaySound(playerCollisionSound);
|
||||
shakeScreen();
|
||||
deathSequence.phase = DeathPhase::Shaking;
|
||||
deathSequence.phaseStartTicks = SDL_GetTicks();
|
||||
@@ -2404,13 +2220,15 @@ void Game::updateDeathSequence() {
|
||||
case DeathPhase::Waiting:
|
||||
// Espera 500ms antes de completar la muerte
|
||||
if (SDL_GetTicks() - deathSequence.phaseStartTicks >= 500) {
|
||||
JA_PlaySound(coffeeOutSound);
|
||||
deathSequence.player->setAlive(false);
|
||||
if (allPlayersAreDead()) {
|
||||
JA_StopMusic();
|
||||
} else {
|
||||
JA_ResumeMusic();
|
||||
if (!demo.enabled) {
|
||||
JA_PlaySound(coffeeOutSound);
|
||||
if (allPlayersAreDead()) {
|
||||
JA_StopMusic();
|
||||
} else {
|
||||
JA_ResumeMusic();
|
||||
}
|
||||
}
|
||||
deathSequence.player->setAlive(false);
|
||||
deathSequence.phase = DeathPhase::Done;
|
||||
deathSequence.player = nullptr;
|
||||
}
|
||||
@@ -2981,8 +2799,8 @@ void Game::iterate() {
|
||||
|
||||
// Si la música no está sonando
|
||||
if ((JA_GetMusicState() == JA_MUSIC_INVALID) || (JA_GetMusicState() == JA_MUSIC_STOPPED)) {
|
||||
// Reproduce la música
|
||||
if (!gameCompleted) {
|
||||
// Reproduce la música (nunca en modo demo: deja sonar la del título)
|
||||
if (!gameCompleted && !demo.enabled) {
|
||||
if (players[0]->isAlive()) {
|
||||
JA_PlayMusic(gameMusic);
|
||||
}
|
||||
@@ -3445,22 +3263,6 @@ void Game::checkEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las animaciones
|
||||
void Game::loadAnimations(std::string filePath, std::vector<std::string> *buffer) {
|
||||
std::ifstream file(filePath);
|
||||
std::string line;
|
||||
|
||||
if (file) {
|
||||
if (options->console) {
|
||||
std::cout << "Animation loaded: " << filePath.substr(filePath.find_last_of("\\/") + 1).c_str() << std::endl;
|
||||
}
|
||||
while (std::getline(file, line)) {
|
||||
buffer->push_back(line);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Elimina todos los objetos contenidos en vectores
|
||||
void Game::deleteAllVectorObjects() {
|
||||
for (auto player : players) {
|
||||
|
||||
@@ -535,9 +535,6 @@ class Game {
|
||||
// Comprueba si todos los jugadores han muerto
|
||||
bool allPlayersAreDead();
|
||||
|
||||
// Carga las animaciones
|
||||
void loadAnimations(std::string filePath, std::vector<std::string> *buffer);
|
||||
|
||||
// Elimina todos los objetos contenidos en vectores
|
||||
void deleteAllVectorObjects();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||
#include "jail_audio.hpp" // for JA_StopMusic
|
||||
#include "lang.h" // for Lang
|
||||
#include "resource.h"
|
||||
#include "screen.h" // for Screen
|
||||
#include "sprite.h" // for Sprite
|
||||
#include "text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_SHADOW
|
||||
@@ -27,29 +28,19 @@ Instructions::Instructions(SDL_Renderer *renderer, Screen *screen, Asset *asset,
|
||||
this->lang = lang;
|
||||
this->section = section;
|
||||
|
||||
// Reserva memoria para los punteros
|
||||
Texture *item1 = new Texture(renderer, asset->get("item_points1_disk.png"));
|
||||
itemTextures.push_back(item1);
|
||||
|
||||
Texture *item2 = new Texture(renderer, asset->get("item_points2_gavina.png"));
|
||||
itemTextures.push_back(item2);
|
||||
|
||||
Texture *item3 = new Texture(renderer, asset->get("item_points3_pacmar.png"));
|
||||
itemTextures.push_back(item3);
|
||||
|
||||
Texture *item4 = new Texture(renderer, asset->get("item_clock.png"));
|
||||
itemTextures.push_back(item4);
|
||||
|
||||
Texture *item5 = new Texture(renderer, asset->get("item_coffee.png"));
|
||||
itemTextures.push_back(item5);
|
||||
|
||||
Texture *item6 = new Texture(renderer, asset->get("item_coffee_machine.png"));
|
||||
itemTextures.push_back(item6);
|
||||
// Texturas (handles compartidos de Resource)
|
||||
Resource *R = Resource::get();
|
||||
itemTextures.push_back(R->getTexture("item_points1_disk.png"));
|
||||
itemTextures.push_back(R->getTexture("item_points2_gavina.png"));
|
||||
itemTextures.push_back(R->getTexture("item_points3_pacmar.png"));
|
||||
itemTextures.push_back(R->getTexture("item_clock.png"));
|
||||
itemTextures.push_back(R->getTexture("item_coffee.png"));
|
||||
itemTextures.push_back(R->getTexture("item_coffee_machine.png"));
|
||||
|
||||
eventHandler = new SDL_Event();
|
||||
|
||||
sprite = new Sprite(0, 0, 16, 16, itemTextures[0], renderer);
|
||||
text = new Text(asset->get("smb2.png"), asset->get("smb2.txt"), renderer);
|
||||
text = R->getText("smb2");
|
||||
|
||||
// Crea un backbuffer para el renderizador
|
||||
backbuffer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
|
||||
@@ -71,21 +62,21 @@ Instructions::Instructions(SDL_Renderer *renderer, Screen *screen, Asset *asset,
|
||||
|
||||
// Destructor
|
||||
Instructions::~Instructions() {
|
||||
for (auto texture : itemTextures) {
|
||||
texture->unload();
|
||||
delete texture;
|
||||
}
|
||||
// itemTextures y text son propiedad de Resource — no liberar.
|
||||
itemTextures.clear();
|
||||
|
||||
delete sprite;
|
||||
delete eventHandler;
|
||||
delete text;
|
||||
|
||||
SDL_DestroyTexture(backbuffer);
|
||||
}
|
||||
|
||||
// Actualiza las variables
|
||||
void Instructions::update() {
|
||||
// Bombea el stream de música: si no se llama, el buffer se vacía y la
|
||||
// música se corta hasta que volvamos a una escena que sí lo haga.
|
||||
JA_Update();
|
||||
|
||||
// Comprueba las entradas
|
||||
checkInput();
|
||||
|
||||
@@ -243,7 +234,6 @@ void Instructions::checkInput() {
|
||||
|
||||
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
||||
if (mode == m_auto) {
|
||||
JA_StopMusic();
|
||||
finished = true;
|
||||
} else {
|
||||
if (counter > 30) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||
#include "jail_audio.hpp" // for JA_StopMusic, JA_DeleteMusic, JA_LoadMusic
|
||||
#include "lang.h" // for Lang
|
||||
#include "resource.h"
|
||||
#include "screen.h" // for Screen
|
||||
#include "smartsprite.h" // for SmartSprite
|
||||
#include "text.h" // for Text
|
||||
@@ -28,11 +29,9 @@ Intro::Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input,
|
||||
|
||||
// Reserva memoria para los objetos
|
||||
eventHandler = new SDL_Event();
|
||||
texture = new Texture(renderer, asset->get("intro.png"));
|
||||
text = new Text(asset->get("nokia.png"), asset->get("nokia.txt"), renderer);
|
||||
|
||||
// Carga los recursos
|
||||
loadMedia();
|
||||
texture = Resource::get()->getTexture("intro.png");
|
||||
text = Resource::get()->getText("nokia");
|
||||
music = Resource::get()->getMusic("intro.ogg");
|
||||
|
||||
// Inicializa variables
|
||||
section->name = SECTION_PROG_INTRO;
|
||||
@@ -161,25 +160,18 @@ Intro::Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input,
|
||||
Intro::~Intro() {
|
||||
delete eventHandler;
|
||||
|
||||
texture->unload();
|
||||
delete texture;
|
||||
|
||||
// texture, text, music son propiedad de Resource — no liberar aquí.
|
||||
for (auto bitmap : bitmaps) {
|
||||
delete bitmap;
|
||||
}
|
||||
|
||||
for (auto text : texts) {
|
||||
delete text;
|
||||
for (auto t : texts) {
|
||||
delete t;
|
||||
}
|
||||
|
||||
JA_DeleteMusic(music);
|
||||
}
|
||||
|
||||
// Carga los recursos
|
||||
// Carga los recursos (ya no carga nada, se mantiene por si hay callers)
|
||||
bool Intro::loadMedia() {
|
||||
// Musicas
|
||||
music = JA_LoadMusic(asset->get("intro.ogg").c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "lang.h"
|
||||
|
||||
#include <fstream> // for basic_ifstream, basic_istream, ifstream
|
||||
#include <sstream>
|
||||
|
||||
#include "asset.h" // for Asset
|
||||
#include "resource_helper.h"
|
||||
|
||||
// Constructor
|
||||
Lang::Lang(Asset *mAsset) {
|
||||
@@ -38,27 +40,27 @@ bool Lang::setLang(Uint8 lang) {
|
||||
for (int i = 0; i < MAX_TEXT_STRINGS; i++)
|
||||
mTextStrings[i] = "";
|
||||
|
||||
bool success = false;
|
||||
|
||||
std::ifstream rfile(file);
|
||||
if (rfile.is_open() && rfile.good()) {
|
||||
success = true;
|
||||
std::string line;
|
||||
|
||||
// lee el resto de datos del fichero
|
||||
int index = 0;
|
||||
while (std::getline(rfile, 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++;
|
||||
}
|
||||
};
|
||||
// Lee el fichero via ResourceHelper (pack o filesystem)
|
||||
auto bytes = ResourceHelper::loadFile(file);
|
||||
if (bytes.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return success;
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "const.h" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
|
||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||
#include "jail_audio.hpp" // for JA_StopMusic
|
||||
#include "resource.h"
|
||||
#include "screen.h" // for Screen
|
||||
#include "sprite.h" // for Sprite
|
||||
#include "texture.h" // for Texture
|
||||
@@ -29,7 +30,7 @@ Logo::Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, s
|
||||
|
||||
// Reserva memoria para los punteros
|
||||
eventHandler = new SDL_Event();
|
||||
texture = new Texture(renderer, asset->get("logo.png"));
|
||||
texture = Resource::get()->getTexture("logo.png");
|
||||
sprite = new Sprite(14, 75, 226, 44, texture, renderer);
|
||||
|
||||
// Inicializa variables
|
||||
@@ -44,9 +45,7 @@ Logo::Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, s
|
||||
|
||||
// Destructor
|
||||
Logo::~Logo() {
|
||||
texture->unload();
|
||||
delete texture;
|
||||
|
||||
// texture es propiedad de Resource — no liberar aquí.
|
||||
delete sprite;
|
||||
delete eventHandler;
|
||||
}
|
||||
|
||||
135
source/menu.cpp
135
source/menu.cpp
@@ -7,6 +7,7 @@
|
||||
#include "asset.h" // for Asset
|
||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||
#include "jail_audio.hpp" // for JA_LoadSound, JA_PlaySound, JA_DeleteSound
|
||||
#include "resource_helper.h"
|
||||
#include "text.h" // for Text
|
||||
|
||||
// Constructor
|
||||
@@ -61,16 +62,14 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
|
||||
selector.itemColor = {0, 0, 0};
|
||||
selector.a = 255;
|
||||
|
||||
// Inicializa las variables desde un fichero
|
||||
// Inicializa las variables desde un fichero. Si no se pasa fichero, el
|
||||
// llamante (p.ej. Resource::preloadAll) usará loadFromBytes después —
|
||||
// y ese método ya llama a setSelectorItemColors() y reset() al final.
|
||||
if (file != "") {
|
||||
load(file);
|
||||
setSelectorItemColors();
|
||||
reset();
|
||||
}
|
||||
|
||||
// Calcula los colores del selector para el degradado
|
||||
setSelectorItemColors();
|
||||
|
||||
// Deja el cursor en el primer elemento
|
||||
reset();
|
||||
}
|
||||
|
||||
Menu::~Menu() {
|
||||
@@ -95,82 +94,73 @@ Menu::~Menu() {
|
||||
}
|
||||
}
|
||||
|
||||
// Carga la configuración del menu desde un archivo de texto
|
||||
bool Menu::load(std::string file_path) {
|
||||
// Indicador de éxito en la carga
|
||||
// Parser compartido (recibe cualquier istream)
|
||||
bool Menu::parseFromStream(std::istream &file, const std::string &filename) {
|
||||
bool success = true;
|
||||
|
||||
// Indica si se ha creado ya el objeto de texto
|
||||
bool textAllocated = false;
|
||||
|
||||
const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
|
||||
std::string line;
|
||||
std::ifstream file(file_path);
|
||||
(void)filename;
|
||||
|
||||
// El fichero se puede abrir
|
||||
if (file.good()) {
|
||||
// Procesa el fichero linea a linea
|
||||
// std::cout << "Reading file " << filename.c_str() << std::endl;
|
||||
while (std::getline(file, line)) {
|
||||
if (line == "[item]") {
|
||||
item_t item;
|
||||
item.label = "";
|
||||
item.hPaddingDown = 1;
|
||||
item.selectable = true;
|
||||
item.greyed = false;
|
||||
item.linkedDown = false;
|
||||
item.visible = true;
|
||||
item.line = false;
|
||||
while (std::getline(file, line)) {
|
||||
if (line == "[item]") {
|
||||
item_t item;
|
||||
item.label = "";
|
||||
item.hPaddingDown = 1;
|
||||
item.selectable = true;
|
||||
item.greyed = false;
|
||||
item.linkedDown = false;
|
||||
item.visible = true;
|
||||
item.line = false;
|
||||
|
||||
do {
|
||||
// Lee la siguiente linea
|
||||
std::getline(file, line);
|
||||
|
||||
// Encuentra la posición del caracter '='
|
||||
int pos = line.find("=");
|
||||
|
||||
// Procesa las dos subcadenas
|
||||
if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
||||
// std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||
success = false;
|
||||
}
|
||||
|
||||
} while (line != "[/item]");
|
||||
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
// En caso contrario se parsea el fichero para buscar las variables y los valores
|
||||
else {
|
||||
// Encuentra la posición del caracter '='
|
||||
do {
|
||||
std::getline(file, line);
|
||||
int pos = line.find("=");
|
||||
// Procesa las dos subcadenas
|
||||
if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
||||
// std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||
if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
||||
success = false;
|
||||
}
|
||||
} while (line != "[/item]");
|
||||
|
||||
// Crea el objeto text tan pronto como se pueda. Necesario para añadir items
|
||||
if (font_png != "" && font_txt != "" && !textAllocated) {
|
||||
text = new Text(asset->get(font_png), asset->get(font_txt), renderer);
|
||||
textAllocated = true;
|
||||
}
|
||||
addItem(item);
|
||||
} else {
|
||||
int pos = line.find("=");
|
||||
if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Crea el objeto text tan pronto como se pueda. Necesario para añadir items.
|
||||
// Carga via ResourceHelper para que funcione tanto con pack como con filesystem.
|
||||
if (font_png != "" && font_txt != "" && !textAllocated) {
|
||||
auto pngBytes = ResourceHelper::loadFile(asset->get(font_png));
|
||||
auto txtBytes = ResourceHelper::loadFile(asset->get(font_txt));
|
||||
text = new Text(pngBytes, txtBytes, renderer);
|
||||
textAllocated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
// std::cout << "Closing file " << filename.c_str() << std::endl;
|
||||
file.close();
|
||||
}
|
||||
// El fichero no se puede abrir
|
||||
else {
|
||||
// std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Carga la configuración del menu desde un archivo de texto
|
||||
bool Menu::load(std::string file_path) {
|
||||
const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
|
||||
std::ifstream file(file_path);
|
||||
if (!file.good()) {
|
||||
return false;
|
||||
}
|
||||
return parseFromStream(file, filename);
|
||||
}
|
||||
|
||||
// Carga el menu desde bytes en memoria
|
||||
bool Menu::loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &nameForLogs) {
|
||||
if (bytes.empty()) return false;
|
||||
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||
std::stringstream ss(content);
|
||||
bool ok = parseFromStream(ss, nameForLogs);
|
||||
setSelectorItemColors();
|
||||
reset();
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Asigna variables a partir de dos cadenas
|
||||
bool Menu::setItem(item_t *item, std::string var, std::string value) {
|
||||
// Indicador de éxito en la asignación
|
||||
@@ -228,15 +218,18 @@ bool Menu::setVars(std::string var, std::string value) {
|
||||
}
|
||||
|
||||
else if (var == "sound_cancel") {
|
||||
soundCancel = JA_LoadSound(asset->get(value).c_str());
|
||||
auto bytes = ResourceHelper::loadFile(asset->get(value));
|
||||
if (!bytes.empty()) soundCancel = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
|
||||
}
|
||||
|
||||
else if (var == "sound_accept") {
|
||||
soundAccept = JA_LoadSound(asset->get(value).c_str());
|
||||
auto bytes = ResourceHelper::loadFile(asset->get(value));
|
||||
if (!bytes.empty()) soundAccept = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
|
||||
}
|
||||
|
||||
else if (var == "sound_move") {
|
||||
soundMove = JA_LoadSound(asset->get(value).c_str());
|
||||
auto bytes = ResourceHelper::loadFile(asset->get(value));
|
||||
if (!bytes.empty()) soundMove = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
|
||||
}
|
||||
|
||||
else if (var == "name") {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string> // for string, basic_string
|
||||
#include <vector> // for vector
|
||||
|
||||
@@ -101,6 +102,9 @@ class Menu {
|
||||
// Carga la configuración del menu desde un archivo de texto
|
||||
bool load(std::string file_path);
|
||||
|
||||
// Parser compartido (recibe cualquier istream)
|
||||
bool parseFromStream(std::istream &file, const std::string &filename);
|
||||
|
||||
// Asigna variables a partir de dos cadenas
|
||||
bool setVars(std::string var, std::string value);
|
||||
|
||||
@@ -147,6 +151,9 @@ class Menu {
|
||||
// Destructor
|
||||
~Menu();
|
||||
|
||||
// Carga el menu desde bytes en memoria
|
||||
bool loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &nameForLogs = "");
|
||||
|
||||
// Carga los ficheros de audio
|
||||
void loadAudioFile(std::string file, int sound);
|
||||
|
||||
|
||||
218
source/resource.cpp
Normal file
218
source/resource.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#include "resource.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "asset.h"
|
||||
#include "jail_audio.hpp"
|
||||
#include "menu.h"
|
||||
#include "resource_helper.h"
|
||||
#include "text.h"
|
||||
#include "texture.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/resource.h
Normal file
53
source/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/resource_helper.cpp
Normal file
77
source/resource_helper.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "resource_helper.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "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;
|
||||
}
|
||||
}
|
||||
15
source/resource_helper.h
Normal file
15
source/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);
|
||||
}
|
||||
133
source/resource_loader.cpp
Normal file
133
source/resource_loader.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "resource_loader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "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/resource_loader.h
Normal file
39
source/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/resource_pack.cpp
Normal file
223
source/resource_pack.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "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/resource_pack.h
Normal file
44
source/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;
|
||||
};
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "asset.h" // for Asset
|
||||
#include "mouse.hpp" // for Mouse::cursorVisible, Mouse::lastMouseMoveTime
|
||||
#include "resource.h"
|
||||
#include "text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
@@ -82,8 +83,8 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
||||
// Establece el modo de video
|
||||
setVideoMode(options->videoMode != 0);
|
||||
|
||||
// Inicializa el sistema de notificaciones
|
||||
notificationText = new Text(asset->get("8bithud.png"), asset->get("8bithud.txt"), renderer);
|
||||
// Inicializa el sistema de notificaciones (Text compartido de Resource)
|
||||
notificationText = Resource::get()->getText("8bithud");
|
||||
notificationMessage = "";
|
||||
notificationTextColor = {0xFF, 0xFF, 0xFF};
|
||||
notificationOutlineColor = {0x00, 0x00, 0x00};
|
||||
@@ -96,7 +97,7 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
||||
|
||||
// Destructor
|
||||
Screen::~Screen() {
|
||||
delete notificationText;
|
||||
// notificationText es propiedad de Resource — no liberar.
|
||||
SDL_DestroyTexture(gameCanvas);
|
||||
}
|
||||
|
||||
|
||||
110
source/text.cpp
110
source/text.cpp
@@ -3,72 +3,82 @@
|
||||
|
||||
#include <fstream> // for char_traits, basic_ostream, basic_ifstream, ope...
|
||||
#include <iostream> // for cout
|
||||
#include <sstream>
|
||||
|
||||
#include "sprite.h" // for Sprite
|
||||
#include "texture.h" // for Texture
|
||||
#include "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;
|
||||
|
||||
// Inicializa a cero el vector con las coordenadas
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
tf.offset[i].x = 0;
|
||||
tf.offset[i].y = 0;
|
||||
tf.offset[i].w = 0;
|
||||
}
|
||||
|
||||
// Abre el fichero para leer los valores
|
||||
const std::string filename = file.substr(file.find_last_of("\\/") + 1).c_str();
|
||||
std::ifstream rfile(file);
|
||||
|
||||
if (rfile.is_open() && rfile.good()) {
|
||||
std::string buffer;
|
||||
|
||||
// Lee los dos primeros valores del fichero
|
||||
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);
|
||||
|
||||
// lee el resto de datos del fichero
|
||||
int index = 32;
|
||||
int line_read = 0;
|
||||
while (std::getline(rfile, buffer)) {
|
||||
// Almacena solo las lineas impares
|
||||
if (line_read % 2 == 1) {
|
||||
tf.offset[index++].w = std::stoi(buffer);
|
||||
}
|
||||
|
||||
// Limpia el buffer
|
||||
buffer.clear();
|
||||
line_read++;
|
||||
};
|
||||
|
||||
// Cierra el fichero
|
||||
parseTextFileStream(rfile, tf);
|
||||
if (verbose) {
|
||||
std::cout << "Text loaded: " << filename.c_str() << std::endl;
|
||||
}
|
||||
rfile.close();
|
||||
} else if (verbose) {
|
||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||
}
|
||||
|
||||
// El fichero no se puede abrir
|
||||
else {
|
||||
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 << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||
std::cout << "Text loaded from memory" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
|
||||
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;
|
||||
}
|
||||
|
||||
computeTextFileOffsets(tf);
|
||||
return tf;
|
||||
}
|
||||
|
||||
@@ -135,6 +145,24 @@ Text::Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer) {
|
||||
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;
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string> // for string
|
||||
#include <vector>
|
||||
class Sprite;
|
||||
class Texture;
|
||||
#include "utils.h"
|
||||
@@ -28,6 +30,9 @@ struct textFile_t {
|
||||
// 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:
|
||||
@@ -47,6 +52,9 @@ class Text {
|
||||
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();
|
||||
|
||||
|
||||
@@ -31,75 +31,86 @@ Texture::Texture(SDL_Renderer *renderer, std::string path, bool 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 width, height, orig_format;
|
||||
unsigned char *data = stbi_load(path.c_str(), &width, &height, &orig_format, req_format);
|
||||
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;
|
||||
}
|
||||
} else if (verbose) {
|
||||
std::cout << "Image loaded: " << filename.c_str() << std::endl;
|
||||
}
|
||||
|
||||
int pitch;
|
||||
SDL_PixelFormat pixel_format;
|
||||
if (req_format == STBI_rgb) {
|
||||
pitch = 3 * width; // 3 bytes por pixel * pixels per linea
|
||||
pixel_format = SDL_PIXELFORMAT_RGB24;
|
||||
} else { // STBI_rgb_alpha (RGBA)
|
||||
pitch = 4 * width;
|
||||
pixel_format = SDL_PIXELFORMAT_RGBA32;
|
||||
}
|
||||
|
||||
// Limpia
|
||||
unload();
|
||||
|
||||
// La textura final
|
||||
SDL_Texture *newTexture = nullptr;
|
||||
|
||||
// Carga la imagen desde una ruta específica
|
||||
SDL_Surface *loadedSurface = SDL_CreateSurfaceFrom(width, height, pixel_format, (void *)data, pitch);
|
||||
if (loadedSurface == nullptr) {
|
||||
if (verbose) {
|
||||
std::cout << "Unable to load image " << path.c_str() << std::endl;
|
||||
}
|
||||
} else {
|
||||
// Crea la textura desde los pixels de la surface
|
||||
newTexture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
|
||||
if (newTexture == nullptr) {
|
||||
if (verbose) {
|
||||
std::cout << "Unable to create texture from " << path.c_str() << "! SDL Error: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
} else {
|
||||
// Obtiene las dimensiones de la imagen
|
||||
this->width = loadedSurface->w;
|
||||
this->height = loadedSurface->h;
|
||||
|
||||
// Aplica el modo de escalado
|
||||
SDL_SetTextureScaleMode(newTexture, currentScaleMode);
|
||||
}
|
||||
|
||||
// Elimina la textura cargada
|
||||
SDL_DestroySurface(loadedSurface);
|
||||
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;
|
||||
}
|
||||
|
||||
// Return success
|
||||
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
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string> // for basic_string, string
|
||||
#include <vector>
|
||||
|
||||
class Texture {
|
||||
private:
|
||||
@@ -24,12 +26,18 @@ class Texture {
|
||||
// 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);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "jail_audio.hpp" // for JA_StopMusic, JA_GetMusicState, JA_Play...
|
||||
#include "lang.h" // for Lang, ba_BA, en_UK, es_ES
|
||||
#include "menu.h" // for Menu
|
||||
#include "resource.h"
|
||||
#include "screen.h" // for Screen, FILTER_LINEAL, FILTER_NEAREST
|
||||
#include "smartsprite.h" // for SmartSprite
|
||||
#include "sprite.h" // for Sprite
|
||||
@@ -36,28 +37,29 @@ Title::Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset,
|
||||
eventHandler = new SDL_Event();
|
||||
fade = new Fade(renderer);
|
||||
|
||||
dustTexture = new Texture(renderer, asset->get("title_dust.png"));
|
||||
coffeeTexture = new Texture(renderer, asset->get("title_coffee.png"));
|
||||
crisisTexture = new Texture(renderer, asset->get("title_crisis.png"));
|
||||
gradientTexture = new Texture(renderer, asset->get("title_gradient.png"));
|
||||
Resource *R = Resource::get();
|
||||
dustTexture = R->getTexture("title_dust.png");
|
||||
coffeeTexture = R->getTexture("title_coffee.png");
|
||||
crisisTexture = R->getTexture("title_crisis.png");
|
||||
gradientTexture = R->getTexture("title_gradient.png");
|
||||
|
||||
coffeeBitmap = new SmartSprite(coffeeTexture, renderer);
|
||||
crisisBitmap = new SmartSprite(crisisTexture, renderer);
|
||||
dustBitmapL = new AnimatedSprite(dustTexture, renderer, asset->get("title_dust.ani"));
|
||||
dustBitmapR = new AnimatedSprite(dustTexture, renderer, asset->get("title_dust.ani"));
|
||||
dustBitmapL = new AnimatedSprite(dustTexture, renderer, "", &R->getAnimationLines("title_dust.ani"));
|
||||
dustBitmapR = new AnimatedSprite(dustTexture, renderer, "", &R->getAnimationLines("title_dust.ani"));
|
||||
gradient = new Sprite({0, 0, 256, 192}, gradientTexture, renderer);
|
||||
|
||||
text1 = new Text(asset->get("smb2.png"), asset->get("smb2.txt"), renderer);
|
||||
text2 = new Text(asset->get("8bithud.png"), asset->get("8bithud.txt"), renderer);
|
||||
text1 = R->getText("smb2");
|
||||
text2 = R->getText("8bithud");
|
||||
|
||||
#ifdef GAME_CONSOLE
|
||||
menu.title = new Menu(renderer, asset, input, asset->get("title_gc.men"));
|
||||
menu.options = new Menu(renderer, asset, input, asset->get("options_gc.men"));
|
||||
menu.title = R->getMenu("title_gc");
|
||||
menu.options = R->getMenu("options_gc");
|
||||
#else
|
||||
menu.title = new Menu(renderer, asset, input, asset->get("title.men"));
|
||||
menu.options = new Menu(renderer, asset, input, asset->get("options.men"));
|
||||
menu.title = R->getMenu("title");
|
||||
menu.options = R->getMenu("options");
|
||||
#endif
|
||||
menu.playerSelect = new Menu(renderer, asset, input, asset->get("player_select.men"));
|
||||
menu.playerSelect = R->getMenu("player_select");
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// En la versión web no se puede cerrar el programa: ocultamos la opción QUIT del menú de título
|
||||
@@ -65,11 +67,9 @@ Title::Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset,
|
||||
menu.title->setSelectable(3, false);
|
||||
#endif
|
||||
|
||||
// Sonidos
|
||||
crashSound = JA_LoadSound(asset->get("title.wav").c_str());
|
||||
|
||||
// Musicas
|
||||
titleMusic = JA_LoadMusic(asset->get("title.ogg").c_str());
|
||||
// Sonidos y música (handles compartidos)
|
||||
crashSound = R->getSound("title.wav");
|
||||
titleMusic = R->getMusic("title.ogg");
|
||||
|
||||
// Inicializa los valores
|
||||
init();
|
||||
@@ -80,34 +80,14 @@ Title::~Title() {
|
||||
delete eventHandler;
|
||||
delete fade;
|
||||
|
||||
dustTexture->unload();
|
||||
delete dustTexture;
|
||||
|
||||
coffeeTexture->unload();
|
||||
delete coffeeTexture;
|
||||
|
||||
crisisTexture->unload();
|
||||
delete crisisTexture;
|
||||
|
||||
gradientTexture->unload();
|
||||
delete gradientTexture;
|
||||
|
||||
// Las texturas, Text, Menu, sonido y música son propiedad de Resource —
|
||||
// no se liberan aquí. Solo los sprites que posee esta escena.
|
||||
delete coffeeBitmap;
|
||||
delete crisisBitmap;
|
||||
delete dustBitmapL;
|
||||
delete dustBitmapR;
|
||||
delete gradient;
|
||||
|
||||
delete text1;
|
||||
delete text2;
|
||||
|
||||
delete menu.title;
|
||||
delete menu.options;
|
||||
delete menu.playerSelect;
|
||||
|
||||
JA_DeleteSound(crashSound);
|
||||
JA_DeleteMusic(titleMusic);
|
||||
|
||||
SDL_DestroyTexture(background);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user