resource.pack
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,7 +1,6 @@
|
|||||||
.vscode
|
.vscode
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
data/config/config.txt
|
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
thumbs.db
|
thumbs.db
|
||||||
*.exe
|
*.exe
|
||||||
@@ -14,3 +13,5 @@ thumbs.db
|
|||||||
coffee_crisis
|
coffee_crisis
|
||||||
coffee_crisis_debug
|
coffee_crisis_debug
|
||||||
release/windows/coffee.res
|
release/windows/coffee.res
|
||||||
|
resources.pack
|
||||||
|
tools/pack_resources/pack_resources
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ Defined in `const.h`: block size (8px), virtual canvas (256×192), play area bou
|
|||||||
- `data/font/` — bitmap font files
|
- `data/font/` — bitmap font files
|
||||||
- `data/music/` and `data/sound/` — audio assets
|
- `data/music/` and `data/sound/` — audio assets
|
||||||
- `data/lang/` — language files (es_ES, ba_BA, en_UK)
|
- `data/lang/` — language files (es_ES, ba_BA, en_UK)
|
||||||
- `data/config/` — gamecontroller DB, demo recording data
|
- `data/demo/` — demo recording data (gamecontrollerdb.txt vive en la raíz del proyecto, fuera del pack)
|
||||||
- `data/menu/` — menu definition files
|
- `data/menu/` — menu definition files
|
||||||
|
|
||||||
## Language
|
## Language
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ elseif(APPLE)
|
|||||||
elseif(EMSCRIPTEN)
|
elseif(EMSCRIPTEN)
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD)
|
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD)
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
target_link_options(${PROJECT_NAME} PRIVATE
|
||||||
--preload-file ${CMAKE_SOURCE_DIR}/data@/data
|
--preload-file ${CMAKE_SOURCE_DIR}/resources.pack@/resources.pack
|
||||||
|
--preload-file ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt@/gamecontrollerdb.txt
|
||||||
-sALLOW_MEMORY_GROWTH=1
|
-sALLOW_MEMORY_GROWTH=1
|
||||||
-sMAX_WEBGL_VERSION=2
|
-sMAX_WEBGL_VERSION=2
|
||||||
)
|
)
|
||||||
|
|||||||
29
Makefile
29
Makefile
@@ -3,6 +3,7 @@
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
|
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
|
||||||
DIR_BIN := $(addsuffix /, $(DIR_ROOT))
|
DIR_BIN := $(addsuffix /, $(DIR_ROOT))
|
||||||
|
DIR_PACK_TOOL := tools/pack_resources
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# TARGET NAMES
|
# TARGET NAMES
|
||||||
@@ -60,14 +61,23 @@ endif
|
|||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COMPILACIÓN CON CMAKE
|
# COMPILACIÓN CON CMAKE
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
all:
|
all: resources.pack
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
@cmake --build build
|
@cmake --build build
|
||||||
|
|
||||||
debug:
|
debug: resources.pack
|
||||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
|
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
|
||||||
@cmake --build build
|
@cmake --build build
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# REGLAS PARA HERRAMIENTA DE EMPAQUETADO Y RESOURCES.PACK
|
||||||
|
# ==============================================================================
|
||||||
|
pack_tool:
|
||||||
|
@$(MAKE) -C $(DIR_PACK_TOOL)
|
||||||
|
|
||||||
|
resources.pack:
|
||||||
|
@$(MAKE) -C $(DIR_PACK_TOOL) pack
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# RELEASE AUTOMÁTICO (detecta SO)
|
# RELEASE AUTOMÁTICO (detecta SO)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -86,6 +96,7 @@ endif
|
|||||||
# COMPILACIÓN PARA WINDOWS (RELEASE)
|
# COMPILACIÓN PARA WINDOWS (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
windows_release:
|
windows_release:
|
||||||
|
@$(MAKE) resources.pack
|
||||||
@echo off
|
@echo off
|
||||||
@echo Creando release para Windows - Version: $(VERSION)
|
@echo Creando release para Windows - Version: $(VERSION)
|
||||||
|
|
||||||
@@ -99,7 +110,8 @@ windows_release:
|
|||||||
@powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}"
|
@powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}"
|
||||||
|
|
||||||
# Copia ficheros
|
# Copia ficheros
|
||||||
@powershell -Command "Copy-Item -Path 'data' -Destination '$(RELEASE_FOLDER)' -Recurse -Force"
|
@powershell -Command "Copy-Item 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
|
||||||
|
@powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'"
|
||||||
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
|
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
|
||||||
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
|
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
|
||||||
@powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'"
|
@powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'"
|
||||||
@@ -118,6 +130,7 @@ windows_release:
|
|||||||
# COMPILACIÓN PARA MACOS (RELEASE)
|
# COMPILACIÓN PARA MACOS (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
macos_release:
|
macos_release:
|
||||||
|
@$(MAKE) resources.pack
|
||||||
@echo "Creando release para macOS - Version: $(VERSION)"
|
@echo "Creando release para macOS - Version: $(VERSION)"
|
||||||
|
|
||||||
# Verificar e instalar create-dmg si es necesario
|
# Verificar e instalar create-dmg si es necesario
|
||||||
@@ -140,7 +153,8 @@ macos_release:
|
|||||||
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
|
|
||||||
# Copia carpetas y ficheros
|
# Copia carpetas y ficheros
|
||||||
cp -R data "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
|
cp gamecontrollerdb.txt "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
|
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
|
||||||
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||||
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
|
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
|
||||||
@@ -211,6 +225,7 @@ macos_release:
|
|||||||
# COMPILACIÓN PARA LINUX (RELEASE)
|
# COMPILACIÓN PARA LINUX (RELEASE)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
linux_release:
|
linux_release:
|
||||||
|
@$(MAKE) resources.pack
|
||||||
@echo "Creando release para Linux - Version: $(VERSION)"
|
@echo "Creando release para Linux - Version: $(VERSION)"
|
||||||
|
|
||||||
# Compila con cmake
|
# Compila con cmake
|
||||||
@@ -222,7 +237,8 @@ linux_release:
|
|||||||
$(MKDIR) "$(RELEASE_FOLDER)"
|
$(MKDIR) "$(RELEASE_FOLDER)"
|
||||||
|
|
||||||
# Copia ficheros
|
# Copia ficheros
|
||||||
cp -R data "$(RELEASE_FOLDER)"
|
cp resources.pack "$(RELEASE_FOLDER)"
|
||||||
|
cp gamecontrollerdb.txt "$(RELEASE_FOLDER)"
|
||||||
cp LICENSE "$(RELEASE_FOLDER)"
|
cp LICENSE "$(RELEASE_FOLDER)"
|
||||||
cp README.md "$(RELEASE_FOLDER)"
|
cp README.md "$(RELEASE_FOLDER)"
|
||||||
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
|
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
|
||||||
@@ -240,6 +256,7 @@ linux_release:
|
|||||||
# COMPILACIÓN PARA WEBASSEMBLY (requiere Docker)
|
# COMPILACIÓN PARA WEBASSEMBLY (requiere Docker)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
wasm:
|
wasm:
|
||||||
|
@$(MAKE) resources.pack
|
||||||
@echo "Compilando para WebAssembly - Version: $(VERSION)"
|
@echo "Compilando para WebAssembly - Version: $(VERSION)"
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--user $(shell id -u):$(shell id -g) \
|
--user $(shell id -u):$(shell id -g) \
|
||||||
@@ -295,4 +312,4 @@ help:
|
|||||||
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
||||||
@echo " make help - Mostrar esta ayuda"
|
@echo " make help - Mostrar esta ayuda"
|
||||||
|
|
||||||
.PHONY: all debug release windows_release macos_release linux_release wasm controllerdb show_version help
|
.PHONY: all debug release windows_release macos_release linux_release wasm controllerdb pack_tool resources.pack show_version help
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,28 +6,20 @@
|
|||||||
|
|
||||||
#include "texture.h" // for Texture
|
#include "texture.h" // for Texture
|
||||||
|
|
||||||
// Carga la animación desde un fichero
|
// Parser compartido: lee un istream con el formato .ani
|
||||||
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose) {
|
static animatedSprite_t parseAnimationStream(std::istream &file, Texture *texture, const std::string &filename, bool verbose) {
|
||||||
// Inicializa variables
|
|
||||||
animatedSprite_t as;
|
animatedSprite_t as;
|
||||||
as.texture = texture;
|
as.texture = texture;
|
||||||
int framesPerRow = 0;
|
int framesPerRow = 0;
|
||||||
int frameWidth = 0;
|
int frameWidth = 0;
|
||||||
int frameHeight = 0;
|
int frameHeight = 0;
|
||||||
int maxTiles = 0;
|
int maxTiles = 0;
|
||||||
|
|
||||||
const std::string filename = filePath.substr(filePath.find_last_of("\\/") + 1);
|
|
||||||
std::ifstream file(filePath);
|
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
// El fichero se puede abrir
|
|
||||||
if (file.good()) {
|
|
||||||
// Procesa el fichero linea a linea
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout << "Animation loaded: " << filename << std::endl;
|
std::cout << "Animation loaded: " << filename << std::endl;
|
||||||
}
|
}
|
||||||
while (std::getline(file, line)) {
|
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]") {
|
if (line == "[animation]") {
|
||||||
animation_t buffer;
|
animation_t buffer;
|
||||||
buffer.counter = 0;
|
buffer.counter = 0;
|
||||||
@@ -36,76 +28,47 @@ animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, b
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
std::getline(file, line);
|
std::getline(file, line);
|
||||||
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
int pos = line.find("=");
|
||||||
|
|
||||||
// Procesa las dos subcadenas
|
|
||||||
if (pos != (int)line.npos) {
|
if (pos != (int)line.npos) {
|
||||||
if (line.substr(0, pos) == "name") {
|
if (line.substr(0, pos) == "name") {
|
||||||
buffer.name = line.substr(pos + 1, line.length());
|
buffer.name = line.substr(pos + 1, line.length());
|
||||||
}
|
} else if (line.substr(0, pos) == "speed") {
|
||||||
|
|
||||||
else if (line.substr(0, pos) == "speed") {
|
|
||||||
buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
|
buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
}
|
} else if (line.substr(0, pos) == "loop") {
|
||||||
|
|
||||||
else if (line.substr(0, pos) == "loop") {
|
|
||||||
buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
|
buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
}
|
} else if (line.substr(0, pos) == "frames") {
|
||||||
|
|
||||||
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::stringstream ss(line.substr(pos + 1, line.length()));
|
||||||
std::string tmp;
|
std::string tmp;
|
||||||
SDL_Rect rect = {0, 0, frameWidth, frameHeight};
|
SDL_Rect rect = {0, 0, frameWidth, frameHeight};
|
||||||
while (getline(ss, tmp, ',')) {
|
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);
|
const int numTile = std::stoi(tmp) > maxTiles ? 0 : std::stoi(tmp);
|
||||||
rect.x = (numTile % framesPerRow) * frameWidth;
|
rect.x = (numTile % framesPerRow) * frameWidth;
|
||||||
rect.y = (numTile / framesPerRow) * frameHeight;
|
rect.y = (numTile / framesPerRow) * frameHeight;
|
||||||
buffer.frames.push_back(rect);
|
buffer.frames.push_back(rect);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
else {
|
|
||||||
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (line != "[/animation]");
|
} while (line != "[/animation]");
|
||||||
|
|
||||||
// Añade la animación al vector de animaciones
|
|
||||||
as.animations.push_back(buffer);
|
as.animations.push_back(buffer);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// En caso contrario se parsea el fichero para buscar las variables y los valores
|
|
||||||
else {
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
int pos = line.find("=");
|
||||||
|
|
||||||
// Procesa las dos subcadenas
|
|
||||||
if (pos != (int)line.npos) {
|
if (pos != (int)line.npos) {
|
||||||
if (line.substr(0, pos) == "framesPerRow") {
|
if (line.substr(0, pos) == "framesPerRow") {
|
||||||
framesPerRow = std::stoi(line.substr(pos + 1, line.length()));
|
framesPerRow = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
}
|
} else if (line.substr(0, pos) == "frameWidth") {
|
||||||
|
|
||||||
else if (line.substr(0, pos) == "frameWidth") {
|
|
||||||
frameWidth = std::stoi(line.substr(pos + 1, line.length()));
|
frameWidth = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
}
|
} else if (line.substr(0, pos) == "frameHeight") {
|
||||||
|
|
||||||
else if (line.substr(0, pos) == "frameHeight") {
|
|
||||||
frameHeight = std::stoi(line.substr(pos + 1, line.length()));
|
frameHeight = std::stoi(line.substr(pos + 1, line.length()));
|
||||||
}
|
} else {
|
||||||
|
|
||||||
else {
|
|
||||||
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normaliza valores
|
|
||||||
if (framesPerRow == 0 && frameWidth > 0) {
|
if (framesPerRow == 0 && frameWidth > 0) {
|
||||||
framesPerRow = texture->getWidth() / frameWidth;
|
framesPerRow = texture->getWidth() / frameWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
|
if (maxTiles == 0 && frameWidth > 0 && frameHeight > 0) {
|
||||||
const int w = texture->getWidth() / frameWidth;
|
const int w = texture->getWidth() / frameWidth;
|
||||||
const int h = texture->getHeight() / frameHeight;
|
const int h = texture->getHeight() / frameHeight;
|
||||||
@@ -115,17 +78,34 @@ animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cierra el fichero
|
return as;
|
||||||
file.close();
|
}
|
||||||
}
|
|
||||||
// El fichero no se puede abrir
|
// Carga la animación desde un fichero
|
||||||
else {
|
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) {
|
if (verbose) {
|
||||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
animatedSprite_t as;
|
||||||
|
as.texture = texture;
|
||||||
return as;
|
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
|
// Constructor
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string> // for string, basic_string
|
#include <string> // for string, basic_string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@ struct animatedSprite_t {
|
|||||||
// Carga la animación desde un fichero
|
// Carga la animación desde un fichero
|
||||||
animatedSprite_t loadAnimationFromFile(Texture *texture, std::string filePath, bool verbose = false);
|
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 {
|
class AnimatedSprite : public MovingSprite {
|
||||||
private:
|
private:
|
||||||
// Variables
|
// Variables
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||||
|
|
||||||
|
#include "resource_helper.h"
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Asset::Asset(std::string executablePath) {
|
Asset::Asset(std::string executablePath) {
|
||||||
this->executablePath = executablePath.substr(0, executablePath.find_last_of("\\/"));
|
this->executablePath = executablePath.substr(0, executablePath.find_last_of("\\/"));
|
||||||
@@ -96,15 +98,22 @@ bool Asset::checkFile(std::string path) {
|
|||||||
bool success = false;
|
bool success = false;
|
||||||
std::string result = "ERROR";
|
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);
|
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
||||||
|
if (ResourceHelper::shouldUseResourcePack(path)) {
|
||||||
|
auto bytes = ResourceHelper::loadFile(path);
|
||||||
|
if (!bytes.empty()) {
|
||||||
|
result = "OK";
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
SDL_IOStream *file = SDL_IOFromFile(path.c_str(), "rb");
|
SDL_IOStream *file = SDL_IOFromFile(path.c_str(), "rb");
|
||||||
|
|
||||||
if (file != nullptr) {
|
if (file != nullptr) {
|
||||||
result = "OK";
|
result = "OK";
|
||||||
success = true;
|
success = true;
|
||||||
SDL_CloseIO(file);
|
SDL_CloseIO(file);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
std::cout.setf(std::ios::left, std::ios::adjustfield);
|
std::cout.setf(std::ios::left, std::ios::adjustfield);
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ enum assetType {
|
|||||||
|
|
||||||
// Clase Asset
|
// Clase Asset
|
||||||
class Asset {
|
class Asset {
|
||||||
private:
|
public:
|
||||||
// Estructura para definir un item
|
// Estructura para definir un item
|
||||||
struct item_t {
|
struct item_t {
|
||||||
std::string file; // Ruta del fichero desde la raiz del directorio
|
std::string file; // Ruta del fichero desde la raiz del directorio
|
||||||
enum assetType type; // Indica el tipo de recurso
|
enum assetType type; // Indica el tipo de recurso
|
||||||
bool required; // Indica si es un fichero que debe de existir
|
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
|
// Variables
|
||||||
int longestName; // Contiene la longitud del nombre de fichero mas largo
|
int longestName; // Contiene la longitud del nombre de fichero mas largo
|
||||||
std::vector<item_t> fileList; // Listado con todas las rutas a los ficheros
|
std::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
|
// Devuelve un elemento de la lista a partir de una cadena
|
||||||
std::string get(std::string text);
|
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
|
// Comprueba que existen todos los elementos
|
||||||
bool check();
|
bool check();
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cstdlib> // for exit, EXIT_FAILURE, srand
|
#include <cstdlib> // for exit, EXIT_FAILURE, srand
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream> // for basic_ostream, operator<<, basi...
|
#include <fstream> // for basic_ostream, operator<<, basi...
|
||||||
#include <iostream> // for cout
|
#include <iostream> // for cout
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -23,6 +24,8 @@
|
|||||||
#include "intro.h" // for Intro
|
#include "intro.h" // for Intro
|
||||||
#include "jail_audio.hpp" // for JA_Init
|
#include "jail_audio.hpp" // for JA_Init
|
||||||
#include "lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
|
#include "lang.h" // for Lang, MAX_LANGUAGES, ba_BA, en_UK
|
||||||
|
#include "resource.h"
|
||||||
|
#include "resource_helper.h"
|
||||||
#include "logo.h" // for Logo
|
#include "logo.h" // for Logo
|
||||||
#include "mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
|
#include "mouse.hpp" // for Mouse::handleEvent, Mouse::upda...
|
||||||
#include "screen.h" // for FILTER_NEAREST, Screen, FILTER_...
|
#include "screen.h" // for FILTER_NEAREST, Screen, FILTER_...
|
||||||
@@ -55,6 +58,19 @@ Director::Director(int argc, const char *argv[]) {
|
|||||||
createSystemFolder("jailgames/coffee_crisis_debug");
|
createSystemFolder("jailgames/coffee_crisis_debug");
|
||||||
#endif
|
#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
|
// Crea el objeto que controla los ficheros de recursos
|
||||||
asset = new Asset(executablePath);
|
asset = new Asset(executablePath);
|
||||||
asset->setVerbose(options->console);
|
asset->setVerbose(options->console);
|
||||||
@@ -80,9 +96,25 @@ Director::Director(int argc, const char *argv[]) {
|
|||||||
lang = new Lang(asset);
|
lang = new Lang(asset);
|
||||||
lang->setLang(options->language);
|
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();
|
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);
|
screen = new Screen(window, renderer, asset, options);
|
||||||
|
|
||||||
activeSection = ActiveSection::None;
|
activeSection = ActiveSection::None;
|
||||||
@@ -99,9 +131,15 @@ Director::~Director() {
|
|||||||
title.reset();
|
title.reset();
|
||||||
game.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 asset;
|
||||||
delete input;
|
delete input;
|
||||||
delete screen;
|
|
||||||
delete lang;
|
delete lang;
|
||||||
delete options;
|
delete options;
|
||||||
delete section;
|
delete section;
|
||||||
@@ -111,6 +149,8 @@ Director::~Director() {
|
|||||||
|
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
|
||||||
|
ResourceHelper::shutdownResourceSystem();
|
||||||
|
|
||||||
std::cout << "\nBye!" << std::endl;
|
std::cout << "\nBye!" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,8 +280,7 @@ bool Director::setFileList() {
|
|||||||
// Ficheros de configuración
|
// Ficheros de configuración
|
||||||
asset->add(systemFolder + "/config.txt", t_data, false, true);
|
asset->add(systemFolder + "/config.txt", t_data, false, true);
|
||||||
asset->add(systemFolder + "/score.bin", 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/demo/demo.bin", t_data);
|
||||||
asset->add(prefix + "/data/config/gamecontrollerdb.txt", t_data);
|
|
||||||
|
|
||||||
// Musicas
|
// Musicas
|
||||||
asset->add(prefix + "/data/music/intro.ogg", t_music);
|
asset->add(prefix + "/data/music/intro.ogg", t_music);
|
||||||
|
|||||||
398
source/game.cpp
398
source/game.cpp
@@ -15,6 +15,7 @@
|
|||||||
#include "input.h" // for inputs_e, Input, REPEAT_TRUE, REPEAT_FALSE
|
#include "input.h" // for inputs_e, Input, REPEAT_TRUE, REPEAT_FALSE
|
||||||
#include "item.h" // for Item, ITEM_COFFEE_MACHINE, ITEM_CLOCK
|
#include "item.h" // for Item, ITEM_COFFEE_MACHINE, ITEM_CLOCK
|
||||||
#include "jail_audio.hpp" // for JA_PlaySound, JA_DeleteSound, JA_LoadSound
|
#include "jail_audio.hpp" // for JA_PlaySound, JA_DeleteSound, JA_LoadSound
|
||||||
|
#include "resource.h"
|
||||||
#include "lang.h" // for Lang
|
#include "lang.h" // for Lang
|
||||||
#include "menu.h" // for Menu
|
#include "menu.h" // for Menu
|
||||||
#include "movingsprite.h" // for MovingSprite
|
#include "movingsprite.h" // for MovingSprite
|
||||||
@@ -98,84 +99,19 @@ Game::~Game() {
|
|||||||
options->input[0].deviceType = onePlayerControl;
|
options->input[0].deviceType = onePlayerControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Elimina todos los objetos contenidos en vectores
|
// Elimina todos los objetos contenidos en vectores (jugadores, balas, etc.)
|
||||||
deleteAllVectorObjects();
|
deleteAllVectorObjects();
|
||||||
|
|
||||||
bulletTexture->unload();
|
// Las texturas, animaciones, Text, Menu, sonidos y música son propiedad
|
||||||
delete bulletTexture;
|
// de Resource — no se liberan aquí. Solo limpiamos nuestras vistas.
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
playerAnimations.clear();
|
playerAnimations.clear();
|
||||||
|
|
||||||
for (auto animation : balloonAnimations) {
|
|
||||||
delete animation;
|
|
||||||
}
|
|
||||||
balloonAnimations.clear();
|
balloonAnimations.clear();
|
||||||
|
|
||||||
for (auto animation : itemAnimations) {
|
|
||||||
delete animation;
|
|
||||||
}
|
|
||||||
itemAnimations.clear();
|
itemAnimations.clear();
|
||||||
|
|
||||||
// Texturas
|
|
||||||
for (auto texture : player1Textures) {
|
|
||||||
texture->unload();
|
|
||||||
delete texture;
|
|
||||||
}
|
|
||||||
player1Textures.clear();
|
player1Textures.clear();
|
||||||
|
|
||||||
for (auto texture : player2Textures) {
|
|
||||||
texture->unload();
|
|
||||||
delete texture;
|
|
||||||
}
|
|
||||||
player2Textures.clear();
|
player2Textures.clear();
|
||||||
|
|
||||||
for (auto texture : itemTextures) {
|
|
||||||
texture->unload();
|
|
||||||
delete texture;
|
|
||||||
}
|
|
||||||
itemTextures.clear();
|
itemTextures.clear();
|
||||||
|
|
||||||
for (auto texture : balloonTextures) {
|
|
||||||
texture->unload();
|
|
||||||
delete texture;
|
|
||||||
}
|
|
||||||
balloonTextures.clear();
|
balloonTextures.clear();
|
||||||
|
|
||||||
delete text;
|
|
||||||
delete textBig;
|
|
||||||
delete textScoreBoard;
|
|
||||||
delete textNokia2;
|
|
||||||
delete textNokiaBig2;
|
|
||||||
delete gameOverMenu;
|
|
||||||
delete pauseMenu;
|
|
||||||
delete fade;
|
delete fade;
|
||||||
delete eventHandler;
|
delete eventHandler;
|
||||||
delete clouds1A;
|
delete clouds1A;
|
||||||
@@ -192,24 +128,6 @@ Game::~Game() {
|
|||||||
delete powerMeterSprite;
|
delete powerMeterSprite;
|
||||||
delete gameOverSprite;
|
delete gameOverSprite;
|
||||||
delete gameOverEndSprite;
|
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'
|
// Inicializa las variables necesarias para la sección 'Game'
|
||||||
@@ -414,186 +332,106 @@ void Game::loadMedia() {
|
|||||||
<< "** LOADING RESOURCES FOR GAME SECTION" << std::endl;
|
<< "** LOADING RESOURCES FOR GAME SECTION" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Texturas
|
Resource *R = Resource::get();
|
||||||
bulletTexture = new Texture(renderer, asset->get("bullet.png"));
|
|
||||||
gameBuildingsTexture = new Texture(renderer, asset->get("game_buildings.png"));
|
// Texturas (handles compartidos — no se liberan aquí)
|
||||||
gameCloudsTexture = new Texture(renderer, asset->get("game_clouds.png"));
|
bulletTexture = R->getTexture("bullet.png");
|
||||||
gameGrassTexture = new Texture(renderer, asset->get("game_grass.png"));
|
gameBuildingsTexture = R->getTexture("game_buildings.png");
|
||||||
gamePowerMeterTexture = new Texture(renderer, asset->get("game_power_meter.png"));
|
gameCloudsTexture = R->getTexture("game_clouds.png");
|
||||||
gameSkyColorsTexture = new Texture(renderer, asset->get("game_sky_colors.png"));
|
gameGrassTexture = R->getTexture("game_grass.png");
|
||||||
gameTextTexture = new Texture(renderer, asset->get("game_text.png"));
|
gamePowerMeterTexture = R->getTexture("game_power_meter.png");
|
||||||
gameOverTexture = new Texture(renderer, asset->get("menu_game_over.png"));
|
gameSkyColorsTexture = R->getTexture("game_sky_colors.png");
|
||||||
gameOverEndTexture = new Texture(renderer, asset->get("menu_game_over_end.png"));
|
gameTextTexture = R->getTexture("game_text.png");
|
||||||
|
gameOverTexture = R->getTexture("menu_game_over.png");
|
||||||
|
gameOverEndTexture = R->getTexture("menu_game_over_end.png");
|
||||||
|
|
||||||
// Texturas - Globos
|
// Texturas - Globos
|
||||||
Texture *balloon1Texture = new Texture(renderer, asset->get("balloon1.png"));
|
balloonTextures.push_back(R->getTexture("balloon1.png"));
|
||||||
balloonTextures.push_back(balloon1Texture);
|
balloonTextures.push_back(R->getTexture("balloon2.png"));
|
||||||
|
balloonTextures.push_back(R->getTexture("balloon3.png"));
|
||||||
Texture *balloon2Texture = new Texture(renderer, asset->get("balloon2.png"));
|
balloonTextures.push_back(R->getTexture("balloon4.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);
|
|
||||||
|
|
||||||
// Texturas - Items
|
// Texturas - Items
|
||||||
Texture *item1 = new Texture(renderer, asset->get("item_points1_disk.png"));
|
itemTextures.push_back(R->getTexture("item_points1_disk.png"));
|
||||||
itemTextures.push_back(item1);
|
itemTextures.push_back(R->getTexture("item_points2_gavina.png"));
|
||||||
|
itemTextures.push_back(R->getTexture("item_points3_pacmar.png"));
|
||||||
Texture *item2 = new Texture(renderer, asset->get("item_points2_gavina.png"));
|
itemTextures.push_back(R->getTexture("item_clock.png"));
|
||||||
itemTextures.push_back(item2);
|
itemTextures.push_back(R->getTexture("item_coffee.png"));
|
||||||
|
itemTextures.push_back(R->getTexture("item_coffee_machine.png"));
|
||||||
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 - Player1
|
// Texturas - Player1
|
||||||
Texture *player1Head = new Texture(renderer, asset->get("player_bal1_head.png"));
|
player1Textures.push_back(R->getTexture("player_bal1_head.png"));
|
||||||
player1Textures.push_back(player1Head);
|
player1Textures.push_back(R->getTexture("player_bal1_body.png"));
|
||||||
|
player1Textures.push_back(R->getTexture("player_bal1_legs.png"));
|
||||||
Texture *player1Body = new Texture(renderer, asset->get("player_bal1_body.png"));
|
player1Textures.push_back(R->getTexture("player_bal1_death.png"));
|
||||||
player1Textures.push_back(player1Body);
|
player1Textures.push_back(R->getTexture("player_bal1_fire.png"));
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
playerTextures.push_back(player1Textures);
|
playerTextures.push_back(player1Textures);
|
||||||
|
|
||||||
// Texturas - Player2
|
// Texturas - Player2
|
||||||
Texture *player2Head = new Texture(renderer, asset->get("player_arounder_head.png"));
|
player2Textures.push_back(R->getTexture("player_arounder_head.png"));
|
||||||
player2Textures.push_back(player2Head);
|
player2Textures.push_back(R->getTexture("player_arounder_body.png"));
|
||||||
|
player2Textures.push_back(R->getTexture("player_arounder_legs.png"));
|
||||||
Texture *player2Body = new Texture(renderer, asset->get("player_arounder_body.png"));
|
player2Textures.push_back(R->getTexture("player_arounder_death.png"));
|
||||||
player2Textures.push_back(player2Body);
|
player2Textures.push_back(R->getTexture("player_arounder_fire.png"));
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
playerTextures.push_back(player2Textures);
|
playerTextures.push_back(player2Textures);
|
||||||
|
|
||||||
// Animaciones -- Jugador
|
// Animaciones (handles a los vectores raw de Resource — no se liberan)
|
||||||
std::vector<std::string> *playerHeadAnimation = new std::vector<std::string>;
|
playerAnimations.push_back(&R->getAnimationLines("player_head.ani"));
|
||||||
loadAnimations(asset->get("player_head.ani"), playerHeadAnimation);
|
playerAnimations.push_back(&R->getAnimationLines("player_body.ani"));
|
||||||
playerAnimations.push_back(playerHeadAnimation);
|
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>;
|
balloonAnimations.push_back(&R->getAnimationLines("balloon1.ani"));
|
||||||
loadAnimations(asset->get("player_body.ani"), playerBodyAnimation);
|
balloonAnimations.push_back(&R->getAnimationLines("balloon2.ani"));
|
||||||
playerAnimations.push_back(playerBodyAnimation);
|
balloonAnimations.push_back(&R->getAnimationLines("balloon3.ani"));
|
||||||
|
balloonAnimations.push_back(&R->getAnimationLines("balloon4.ani"));
|
||||||
|
|
||||||
std::vector<std::string> *playerLegsAnimation = new std::vector<std::string>;
|
itemAnimations.push_back(&R->getAnimationLines("item_points1_disk.ani"));
|
||||||
loadAnimations(asset->get("player_legs.ani"), playerLegsAnimation);
|
itemAnimations.push_back(&R->getAnimationLines("item_points2_gavina.ani"));
|
||||||
playerAnimations.push_back(playerLegsAnimation);
|
itemAnimations.push_back(&R->getAnimationLines("item_points3_pacmar.ani"));
|
||||||
|
itemAnimations.push_back(&R->getAnimationLines("item_clock.ani"));
|
||||||
std::vector<std::string> *playerDeathAnimation = new std::vector<std::string>;
|
itemAnimations.push_back(&R->getAnimationLines("item_coffee.ani"));
|
||||||
loadAnimations(asset->get("player_death.ani"), playerDeathAnimation);
|
itemAnimations.push_back(&R->getAnimationLines("item_coffee_machine.ani"));
|
||||||
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);
|
|
||||||
|
|
||||||
// Texto
|
// Texto
|
||||||
text = new Text(asset->get("smb2.png"), asset->get("smb2.txt"), renderer);
|
text = R->getText("smb2");
|
||||||
textScoreBoard = new Text(asset->get("8bithud.png"), asset->get("8bithud.txt"), renderer);
|
textScoreBoard = R->getText("8bithud");
|
||||||
textBig = new Text(asset->get("smb2_big.png"), asset->get("smb2_big.txt"), renderer);
|
textBig = R->getText("smb2_big");
|
||||||
textNokia2 = new Text(asset->get("nokia2.png"), asset->get("nokia2.txt"), renderer);
|
textNokia2 = R->getText("nokia2");
|
||||||
textNokiaBig2 = new Text(asset->get("nokia_big2.png"), asset->get("nokia_big2.txt"), renderer);
|
textNokiaBig2 = R->getText("nokia_big2");
|
||||||
|
|
||||||
// Menus
|
// Menus
|
||||||
gameOverMenu = new Menu(renderer, asset, input, asset->get("gameover.men"));
|
gameOverMenu = R->getMenu("gameover");
|
||||||
gameOverMenu->setItemCaption(0, lang->getText(48));
|
gameOverMenu->setItemCaption(0, lang->getText(48));
|
||||||
gameOverMenu->setItemCaption(1, lang->getText(49));
|
gameOverMenu->setItemCaption(1, lang->getText(49));
|
||||||
const int w = text->getCharacterSize() * lang->getText(45).length();
|
const int w = text->getCharacterSize() * lang->getText(45).length();
|
||||||
gameOverMenu->setRectSize(w, 0);
|
gameOverMenu->setRectSize(w, 0);
|
||||||
gameOverMenu->centerMenuOnX(199);
|
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(0, lang->getText(41));
|
||||||
pauseMenu->setItemCaption(1, lang->getText(46));
|
pauseMenu->setItemCaption(1, lang->getText(46));
|
||||||
pauseMenu->setItemCaption(2, lang->getText(47));
|
pauseMenu->setItemCaption(2, lang->getText(47));
|
||||||
|
|
||||||
// Sonidos
|
// Sonidos
|
||||||
balloonSound = JA_LoadSound(asset->get("balloon.wav").c_str());
|
balloonSound = R->getSound("balloon.wav");
|
||||||
bubble1Sound = JA_LoadSound(asset->get("bubble1.wav").c_str());
|
bubble1Sound = R->getSound("bubble1.wav");
|
||||||
bubble2Sound = JA_LoadSound(asset->get("bubble2.wav").c_str());
|
bubble2Sound = R->getSound("bubble2.wav");
|
||||||
bubble3Sound = JA_LoadSound(asset->get("bubble3.wav").c_str());
|
bubble3Sound = R->getSound("bubble3.wav");
|
||||||
bubble4Sound = JA_LoadSound(asset->get("bubble4.wav").c_str());
|
bubble4Sound = R->getSound("bubble4.wav");
|
||||||
bulletSound = JA_LoadSound(asset->get("bullet.wav").c_str());
|
bulletSound = R->getSound("bullet.wav");
|
||||||
clockSound = JA_LoadSound(asset->get("clock.wav").c_str());
|
clockSound = R->getSound("clock.wav");
|
||||||
coffeeOutSound = JA_LoadSound(asset->get("coffeeout.wav").c_str());
|
coffeeOutSound = R->getSound("coffeeout.wav");
|
||||||
hiScoreSound = JA_LoadSound(asset->get("hiscore.wav").c_str());
|
hiScoreSound = R->getSound("hiscore.wav");
|
||||||
itemDropSound = JA_LoadSound(asset->get("itemdrop.wav").c_str());
|
itemDropSound = R->getSound("itemdrop.wav");
|
||||||
itemPickUpSound = JA_LoadSound(asset->get("itempickup.wav").c_str());
|
itemPickUpSound = R->getSound("itempickup.wav");
|
||||||
playerCollisionSound = JA_LoadSound(asset->get("player_collision.wav").c_str());
|
playerCollisionSound = R->getSound("player_collision.wav");
|
||||||
powerBallSound = JA_LoadSound(asset->get("powerball.wav").c_str());
|
powerBallSound = R->getSound("powerball.wav");
|
||||||
stageChangeSound = JA_LoadSound(asset->get("stage_change.wav").c_str());
|
stageChangeSound = R->getSound("stage_change.wav");
|
||||||
coffeeMachineSound = JA_LoadSound(asset->get("title.wav").c_str());
|
coffeeMachineSound = R->getSound("title.wav");
|
||||||
|
|
||||||
// Musicas
|
// Musicas
|
||||||
gameMusic = JA_LoadMusic(asset->get("playing.ogg").c_str());
|
gameMusic = R->getMusic("playing.ogg");
|
||||||
|
|
||||||
if (options->console) {
|
if (options->console) {
|
||||||
std::cout << "** RESOURCES FOR GAME SECTION LOADED" << std::endl
|
std::cout << "** RESOURCES FOR GAME SECTION LOADED" << std::endl
|
||||||
@@ -666,26 +504,21 @@ bool Game::loadScoreFile() {
|
|||||||
|
|
||||||
// Carga el fichero de datos para la demo
|
// Carga el fichero de datos para la demo
|
||||||
bool Game::loadDemoFile() {
|
bool Game::loadDemoFile() {
|
||||||
// Indicador de éxito en la carga
|
// Lee los datos de la demo desde Resource (precargados al arrancar).
|
||||||
bool success = true;
|
const auto &bytes = Resource::get()->getDemoBytes();
|
||||||
const std::string p = asset->get("demo.bin");
|
const size_t expected = sizeof(demoKeys_t) * TOTAL_DEMO_DATA;
|
||||||
const std::string filename = p.substr(p.find_last_of("\\/") + 1);
|
if (bytes.size() >= expected) {
|
||||||
SDL_IOStream *file = SDL_IOFromFile(p.c_str(), "r+b");
|
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
|
||||||
|
memcpy(&demo.dataFile[i], bytes.data() + i * sizeof(demoKeys_t), sizeof(demoKeys_t));
|
||||||
// El fichero no existe
|
|
||||||
if (file == nullptr) {
|
|
||||||
if (options->console) {
|
|
||||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creamos el fichero para escritura
|
|
||||||
file = SDL_IOFromFile(p.c_str(), "w+b");
|
|
||||||
if (file != nullptr) {
|
|
||||||
if (options->console) {
|
if (options->console) {
|
||||||
std::cout << "New file (" << filename.c_str() << ") created!" << std::endl;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicializamos los datos
|
|
||||||
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
|
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
|
||||||
demo.keys.left = 0;
|
demo.keys.left = 0;
|
||||||
demo.keys.right = 0;
|
demo.keys.right = 0;
|
||||||
@@ -694,32 +527,9 @@ bool Game::loadDemoFile() {
|
|||||||
demo.keys.fireLeft = 0;
|
demo.keys.fireLeft = 0;
|
||||||
demo.keys.fireRight = 0;
|
demo.keys.fireRight = 0;
|
||||||
demo.dataFile[i] = demo.keys;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// El fichero existe
|
return true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guarda el fichero de puntos
|
// Guarda el fichero de puntos
|
||||||
@@ -1690,10 +1500,12 @@ void Game::updateDeath() {
|
|||||||
|
|
||||||
if ((deathCounter == 250) || (deathCounter == 200) || (deathCounter == 180) || (deathCounter == 120) || (deathCounter == 60)) {
|
if ((deathCounter == 250) || (deathCounter == 200) || (deathCounter == 180) || (deathCounter == 120) || (deathCounter == 60)) {
|
||||||
// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas
|
// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas
|
||||||
|
if (!demo.enabled) {
|
||||||
const Uint8 index = rand() % 4;
|
const Uint8 index = rand() % 4;
|
||||||
JA_Sound_t *sound[4] = {bubble1Sound, bubble2Sound, bubble3Sound, bubble4Sound};
|
JA_Sound_t *sound[4] = {bubble1Sound, bubble2Sound, bubble3Sound, bubble4Sound};
|
||||||
JA_PlaySound(sound[index], 0);
|
JA_PlaySound(sound[index], 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
section->subsection = SUBSECTION_GAME_GAMEOVER;
|
section->subsection = SUBSECTION_GAME_GAMEOVER;
|
||||||
}
|
}
|
||||||
@@ -2373,11 +2185,15 @@ void Game::killPlayer(Player *player) {
|
|||||||
if (player->hasExtraHit()) {
|
if (player->hasExtraHit()) {
|
||||||
player->removeExtraHit();
|
player->removeExtraHit();
|
||||||
throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2));
|
throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2));
|
||||||
|
if (!demo.enabled) {
|
||||||
JA_PlaySound(coffeeOutSound);
|
JA_PlaySound(coffeeOutSound);
|
||||||
|
}
|
||||||
} else if (deathSequence.phase == DeathPhase::None) {
|
} else if (deathSequence.phase == DeathPhase::None) {
|
||||||
|
if (!demo.enabled) {
|
||||||
JA_PauseMusic();
|
JA_PauseMusic();
|
||||||
stopAllBalloons(10);
|
|
||||||
JA_PlaySound(playerCollisionSound);
|
JA_PlaySound(playerCollisionSound);
|
||||||
|
}
|
||||||
|
stopAllBalloons(10);
|
||||||
shakeScreen();
|
shakeScreen();
|
||||||
deathSequence.phase = DeathPhase::Shaking;
|
deathSequence.phase = DeathPhase::Shaking;
|
||||||
deathSequence.phaseStartTicks = SDL_GetTicks();
|
deathSequence.phaseStartTicks = SDL_GetTicks();
|
||||||
@@ -2404,13 +2220,15 @@ void Game::updateDeathSequence() {
|
|||||||
case DeathPhase::Waiting:
|
case DeathPhase::Waiting:
|
||||||
// Espera 500ms antes de completar la muerte
|
// Espera 500ms antes de completar la muerte
|
||||||
if (SDL_GetTicks() - deathSequence.phaseStartTicks >= 500) {
|
if (SDL_GetTicks() - deathSequence.phaseStartTicks >= 500) {
|
||||||
|
if (!demo.enabled) {
|
||||||
JA_PlaySound(coffeeOutSound);
|
JA_PlaySound(coffeeOutSound);
|
||||||
deathSequence.player->setAlive(false);
|
|
||||||
if (allPlayersAreDead()) {
|
if (allPlayersAreDead()) {
|
||||||
JA_StopMusic();
|
JA_StopMusic();
|
||||||
} else {
|
} else {
|
||||||
JA_ResumeMusic();
|
JA_ResumeMusic();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
deathSequence.player->setAlive(false);
|
||||||
deathSequence.phase = DeathPhase::Done;
|
deathSequence.phase = DeathPhase::Done;
|
||||||
deathSequence.player = nullptr;
|
deathSequence.player = nullptr;
|
||||||
}
|
}
|
||||||
@@ -2981,8 +2799,8 @@ void Game::iterate() {
|
|||||||
|
|
||||||
// Si la música no está sonando
|
// Si la música no está sonando
|
||||||
if ((JA_GetMusicState() == JA_MUSIC_INVALID) || (JA_GetMusicState() == JA_MUSIC_STOPPED)) {
|
if ((JA_GetMusicState() == JA_MUSIC_INVALID) || (JA_GetMusicState() == JA_MUSIC_STOPPED)) {
|
||||||
// Reproduce la música
|
// Reproduce la música (nunca en modo demo: deja sonar la del título)
|
||||||
if (!gameCompleted) {
|
if (!gameCompleted && !demo.enabled) {
|
||||||
if (players[0]->isAlive()) {
|
if (players[0]->isAlive()) {
|
||||||
JA_PlayMusic(gameMusic);
|
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
|
// Elimina todos los objetos contenidos en vectores
|
||||||
void Game::deleteAllVectorObjects() {
|
void Game::deleteAllVectorObjects() {
|
||||||
for (auto player : players) {
|
for (auto player : players) {
|
||||||
|
|||||||
@@ -535,9 +535,6 @@ class Game {
|
|||||||
// Comprueba si todos los jugadores han muerto
|
// Comprueba si todos los jugadores han muerto
|
||||||
bool allPlayersAreDead();
|
bool allPlayersAreDead();
|
||||||
|
|
||||||
// Carga las animaciones
|
|
||||||
void loadAnimations(std::string filePath, std::vector<std::string> *buffer);
|
|
||||||
|
|
||||||
// Elimina todos los objetos contenidos en vectores
|
// Elimina todos los objetos contenidos en vectores
|
||||||
void deleteAllVectorObjects();
|
void deleteAllVectorObjects();
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||||
#include "jail_audio.hpp" // for JA_StopMusic
|
#include "jail_audio.hpp" // for JA_StopMusic
|
||||||
#include "lang.h" // for Lang
|
#include "lang.h" // for Lang
|
||||||
|
#include "resource.h"
|
||||||
#include "screen.h" // for Screen
|
#include "screen.h" // for Screen
|
||||||
#include "sprite.h" // for Sprite
|
#include "sprite.h" // for Sprite
|
||||||
#include "text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_SHADOW
|
#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->lang = lang;
|
||||||
this->section = section;
|
this->section = section;
|
||||||
|
|
||||||
// Reserva memoria para los punteros
|
// Texturas (handles compartidos de Resource)
|
||||||
Texture *item1 = new Texture(renderer, asset->get("item_points1_disk.png"));
|
Resource *R = Resource::get();
|
||||||
itemTextures.push_back(item1);
|
itemTextures.push_back(R->getTexture("item_points1_disk.png"));
|
||||||
|
itemTextures.push_back(R->getTexture("item_points2_gavina.png"));
|
||||||
Texture *item2 = new Texture(renderer, asset->get("item_points2_gavina.png"));
|
itemTextures.push_back(R->getTexture("item_points3_pacmar.png"));
|
||||||
itemTextures.push_back(item2);
|
itemTextures.push_back(R->getTexture("item_clock.png"));
|
||||||
|
itemTextures.push_back(R->getTexture("item_coffee.png"));
|
||||||
Texture *item3 = new Texture(renderer, asset->get("item_points3_pacmar.png"));
|
itemTextures.push_back(R->getTexture("item_coffee_machine.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);
|
|
||||||
|
|
||||||
eventHandler = new SDL_Event();
|
eventHandler = new SDL_Event();
|
||||||
|
|
||||||
sprite = new Sprite(0, 0, 16, 16, itemTextures[0], renderer);
|
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
|
// Crea un backbuffer para el renderizador
|
||||||
backbuffer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT);
|
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
|
// Destructor
|
||||||
Instructions::~Instructions() {
|
Instructions::~Instructions() {
|
||||||
for (auto texture : itemTextures) {
|
// itemTextures y text son propiedad de Resource — no liberar.
|
||||||
texture->unload();
|
|
||||||
delete texture;
|
|
||||||
}
|
|
||||||
itemTextures.clear();
|
itemTextures.clear();
|
||||||
|
|
||||||
delete sprite;
|
delete sprite;
|
||||||
delete eventHandler;
|
delete eventHandler;
|
||||||
delete text;
|
|
||||||
|
|
||||||
SDL_DestroyTexture(backbuffer);
|
SDL_DestroyTexture(backbuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza las variables
|
// Actualiza las variables
|
||||||
void Instructions::update() {
|
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
|
// Comprueba las entradas
|
||||||
checkInput();
|
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)) {
|
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) {
|
if (mode == m_auto) {
|
||||||
JA_StopMusic();
|
|
||||||
finished = true;
|
finished = true;
|
||||||
} else {
|
} else {
|
||||||
if (counter > 30) {
|
if (counter > 30) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||||
#include "jail_audio.hpp" // for JA_StopMusic, JA_DeleteMusic, JA_LoadMusic
|
#include "jail_audio.hpp" // for JA_StopMusic, JA_DeleteMusic, JA_LoadMusic
|
||||||
#include "lang.h" // for Lang
|
#include "lang.h" // for Lang
|
||||||
|
#include "resource.h"
|
||||||
#include "screen.h" // for Screen
|
#include "screen.h" // for Screen
|
||||||
#include "smartsprite.h" // for SmartSprite
|
#include "smartsprite.h" // for SmartSprite
|
||||||
#include "text.h" // for Text
|
#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
|
// Reserva memoria para los objetos
|
||||||
eventHandler = new SDL_Event();
|
eventHandler = new SDL_Event();
|
||||||
texture = new Texture(renderer, asset->get("intro.png"));
|
texture = Resource::get()->getTexture("intro.png");
|
||||||
text = new Text(asset->get("nokia.png"), asset->get("nokia.txt"), renderer);
|
text = Resource::get()->getText("nokia");
|
||||||
|
music = Resource::get()->getMusic("intro.ogg");
|
||||||
// Carga los recursos
|
|
||||||
loadMedia();
|
|
||||||
|
|
||||||
// Inicializa variables
|
// Inicializa variables
|
||||||
section->name = SECTION_PROG_INTRO;
|
section->name = SECTION_PROG_INTRO;
|
||||||
@@ -161,25 +160,18 @@ Intro::Intro(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input,
|
|||||||
Intro::~Intro() {
|
Intro::~Intro() {
|
||||||
delete eventHandler;
|
delete eventHandler;
|
||||||
|
|
||||||
texture->unload();
|
// texture, text, music son propiedad de Resource — no liberar aquí.
|
||||||
delete texture;
|
|
||||||
|
|
||||||
for (auto bitmap : bitmaps) {
|
for (auto bitmap : bitmaps) {
|
||||||
delete bitmap;
|
delete bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto text : texts) {
|
for (auto t : texts) {
|
||||||
delete text;
|
delete t;
|
||||||
}
|
}
|
||||||
|
|
||||||
JA_DeleteMusic(music);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga los recursos
|
// Carga los recursos (ya no carga nada, se mantiene por si hay callers)
|
||||||
bool Intro::loadMedia() {
|
bool Intro::loadMedia() {
|
||||||
// Musicas
|
|
||||||
music = JA_LoadMusic(asset->get("intro.ogg").c_str());
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#include "lang.h"
|
#include "lang.h"
|
||||||
|
|
||||||
#include <fstream> // for basic_ifstream, basic_istream, ifstream
|
#include <fstream> // for basic_ifstream, basic_istream, ifstream
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "asset.h" // for Asset
|
#include "asset.h" // for Asset
|
||||||
|
#include "resource_helper.h"
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
Lang::Lang(Asset *mAsset) {
|
Lang::Lang(Asset *mAsset) {
|
||||||
@@ -38,16 +40,17 @@ bool Lang::setLang(Uint8 lang) {
|
|||||||
for (int i = 0; i < MAX_TEXT_STRINGS; i++)
|
for (int i = 0; i < MAX_TEXT_STRINGS; i++)
|
||||||
mTextStrings[i] = "";
|
mTextStrings[i] = "";
|
||||||
|
|
||||||
bool success = false;
|
// Lee el fichero via ResourceHelper (pack o filesystem)
|
||||||
|
auto bytes = ResourceHelper::loadFile(file);
|
||||||
|
if (bytes.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::ifstream rfile(file);
|
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||||
if (rfile.is_open() && rfile.good()) {
|
std::stringstream ss(content);
|
||||||
success = true;
|
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
// lee el resto de datos del fichero
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (std::getline(rfile, line)) {
|
while (std::getline(ss, line)) {
|
||||||
// Almacena solo las lineas que no empiezan por # o no esten vacias
|
// Almacena solo las lineas que no empiezan por # o no esten vacias
|
||||||
const bool test1 = line.substr(0, 1) != "#";
|
const bool test1 = line.substr(0, 1) != "#";
|
||||||
const bool test2 = !line.empty();
|
const bool test2 = !line.empty();
|
||||||
@@ -55,10 +58,9 @@ bool Lang::setLang(Uint8 lang) {
|
|||||||
mTextStrings[index] = line;
|
mTextStrings[index] = line;
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene la cadena de texto del indice
|
// Obtiene la cadena de texto del indice
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "const.h" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
|
#include "const.h" // for bgColor, SECTION_PROG_LOGO, SECTION_PROG...
|
||||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||||
#include "jail_audio.hpp" // for JA_StopMusic
|
#include "jail_audio.hpp" // for JA_StopMusic
|
||||||
|
#include "resource.h"
|
||||||
#include "screen.h" // for Screen
|
#include "screen.h" // for Screen
|
||||||
#include "sprite.h" // for Sprite
|
#include "sprite.h" // for Sprite
|
||||||
#include "texture.h" // for Texture
|
#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
|
// Reserva memoria para los punteros
|
||||||
eventHandler = new SDL_Event();
|
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);
|
sprite = new Sprite(14, 75, 226, 44, texture, renderer);
|
||||||
|
|
||||||
// Inicializa variables
|
// Inicializa variables
|
||||||
@@ -44,9 +45,7 @@ Logo::Logo(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, s
|
|||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Logo::~Logo() {
|
Logo::~Logo() {
|
||||||
texture->unload();
|
// texture es propiedad de Resource — no liberar aquí.
|
||||||
delete texture;
|
|
||||||
|
|
||||||
delete sprite;
|
delete sprite;
|
||||||
delete eventHandler;
|
delete eventHandler;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "asset.h" // for Asset
|
#include "asset.h" // for Asset
|
||||||
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
#include "input.h" // for Input, REPEAT_FALSE, inputs_e
|
||||||
#include "jail_audio.hpp" // for JA_LoadSound, JA_PlaySound, JA_DeleteSound
|
#include "jail_audio.hpp" // for JA_LoadSound, JA_PlaySound, JA_DeleteSound
|
||||||
|
#include "resource_helper.h"
|
||||||
#include "text.h" // for Text
|
#include "text.h" // for Text
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
@@ -61,16 +62,14 @@ Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
|
|||||||
selector.itemColor = {0, 0, 0};
|
selector.itemColor = {0, 0, 0};
|
||||||
selector.a = 255;
|
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 != "") {
|
if (file != "") {
|
||||||
load(file);
|
load(file);
|
||||||
}
|
|
||||||
|
|
||||||
// Calcula los colores del selector para el degradado
|
|
||||||
setSelectorItemColors();
|
setSelectorItemColors();
|
||||||
|
|
||||||
// Deja el cursor en el primer elemento
|
|
||||||
reset();
|
reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
@@ -95,22 +94,13 @@ Menu::~Menu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga la configuración del menu desde un archivo de texto
|
// Parser compartido (recibe cualquier istream)
|
||||||
bool Menu::load(std::string file_path) {
|
bool Menu::parseFromStream(std::istream &file, const std::string &filename) {
|
||||||
// Indicador de éxito en la carga
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
// Indica si se ha creado ya el objeto de texto
|
|
||||||
bool textAllocated = false;
|
bool textAllocated = false;
|
||||||
|
|
||||||
const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
|
|
||||||
std::string line;
|
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)) {
|
while (std::getline(file, line)) {
|
||||||
if (line == "[item]") {
|
if (line == "[item]") {
|
||||||
item_t item;
|
item_t item;
|
||||||
@@ -123,54 +113,54 @@ bool Menu::load(std::string file_path) {
|
|||||||
item.line = false;
|
item.line = false;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Lee la siguiente linea
|
|
||||||
std::getline(file, line);
|
std::getline(file, line);
|
||||||
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
int pos = line.find("=");
|
||||||
|
|
||||||
// Procesa las dos subcadenas
|
|
||||||
if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
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;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (line != "[/item]");
|
} while (line != "[/item]");
|
||||||
|
|
||||||
addItem(item);
|
addItem(item);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// En caso contrario se parsea el fichero para buscar las variables y los valores
|
|
||||||
else {
|
|
||||||
// Encuentra la posición del caracter '='
|
|
||||||
int pos = line.find("=");
|
int pos = line.find("=");
|
||||||
// Procesa las dos subcadenas
|
|
||||||
if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
|
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;
|
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crea el objeto text tan pronto como se pueda. Necesario para añadir items
|
// 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) {
|
if (font_png != "" && font_txt != "" && !textAllocated) {
|
||||||
text = new Text(asset->get(font_png), asset->get(font_txt), renderer);
|
auto pngBytes = ResourceHelper::loadFile(asset->get(font_png));
|
||||||
|
auto txtBytes = ResourceHelper::loadFile(asset->get(font_txt));
|
||||||
|
text = new Text(pngBytes, txtBytes, renderer);
|
||||||
textAllocated = true;
|
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;
|
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
|
// Asigna variables a partir de dos cadenas
|
||||||
bool Menu::setItem(item_t *item, std::string var, std::string value) {
|
bool Menu::setItem(item_t *item, std::string var, std::string value) {
|
||||||
// Indicador de éxito en la asignación
|
// 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") {
|
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") {
|
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") {
|
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") {
|
else if (var == "name") {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string> // for string, basic_string
|
#include <string> // for string, basic_string
|
||||||
#include <vector> // for vector
|
#include <vector> // for vector
|
||||||
|
|
||||||
@@ -101,6 +102,9 @@ class Menu {
|
|||||||
// Carga la configuración del menu desde un archivo de texto
|
// Carga la configuración del menu desde un archivo de texto
|
||||||
bool load(std::string file_path);
|
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
|
// Asigna variables a partir de dos cadenas
|
||||||
bool setVars(std::string var, std::string value);
|
bool setVars(std::string var, std::string value);
|
||||||
|
|
||||||
@@ -147,6 +151,9 @@ class Menu {
|
|||||||
// Destructor
|
// Destructor
|
||||||
~Menu();
|
~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
|
// Carga los ficheros de audio
|
||||||
void loadAudioFile(std::string file, int sound);
|
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 "asset.h" // for Asset
|
||||||
#include "mouse.hpp" // for Mouse::cursorVisible, Mouse::lastMouseMoveTime
|
#include "mouse.hpp" // for Mouse::cursorVisible, Mouse::lastMouseMoveTime
|
||||||
|
#include "resource.h"
|
||||||
#include "text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE
|
#include "text.h" // for Text, TXT_CENTER, TXT_COLOR, TXT_STROKE
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
@@ -82,8 +83,8 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
|||||||
// Establece el modo de video
|
// Establece el modo de video
|
||||||
setVideoMode(options->videoMode != 0);
|
setVideoMode(options->videoMode != 0);
|
||||||
|
|
||||||
// Inicializa el sistema de notificaciones
|
// Inicializa el sistema de notificaciones (Text compartido de Resource)
|
||||||
notificationText = new Text(asset->get("8bithud.png"), asset->get("8bithud.txt"), renderer);
|
notificationText = Resource::get()->getText("8bithud");
|
||||||
notificationMessage = "";
|
notificationMessage = "";
|
||||||
notificationTextColor = {0xFF, 0xFF, 0xFF};
|
notificationTextColor = {0xFF, 0xFF, 0xFF};
|
||||||
notificationOutlineColor = {0x00, 0x00, 0x00};
|
notificationOutlineColor = {0x00, 0x00, 0x00};
|
||||||
@@ -96,7 +97,7 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
|||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Screen::~Screen() {
|
Screen::~Screen() {
|
||||||
delete notificationText;
|
// notificationText es propiedad de Resource — no liberar.
|
||||||
SDL_DestroyTexture(gameCanvas);
|
SDL_DestroyTexture(gameCanvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
104
source/text.cpp
104
source/text.cpp
@@ -3,30 +3,15 @@
|
|||||||
|
|
||||||
#include <fstream> // for char_traits, basic_ostream, basic_ifstream, ope...
|
#include <fstream> // for char_traits, basic_ostream, basic_ifstream, ope...
|
||||||
#include <iostream> // for cout
|
#include <iostream> // for cout
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "sprite.h" // for Sprite
|
#include "sprite.h" // for Sprite
|
||||||
#include "texture.h" // for Texture
|
#include "texture.h" // for Texture
|
||||||
#include "utils.h" // for color_t
|
#include "utils.h" // for color_t
|
||||||
|
|
||||||
// Llena una estructuta textFile_t desde un fichero
|
// Parser compartido: rellena un textFile_t desde cualquier istream
|
||||||
textFile_t LoadTextFile(std::string file, bool verbose) {
|
static void parseTextFileStream(std::istream &rfile, textFile_t &tf) {
|
||||||
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;
|
std::string buffer;
|
||||||
|
|
||||||
// Lee los dos primeros valores del fichero
|
|
||||||
std::getline(rfile, buffer);
|
std::getline(rfile, buffer);
|
||||||
std::getline(rfile, buffer);
|
std::getline(rfile, buffer);
|
||||||
tf.boxWidth = std::stoi(buffer);
|
tf.boxWidth = std::stoi(buffer);
|
||||||
@@ -35,40 +20,65 @@ textFile_t LoadTextFile(std::string file, bool verbose) {
|
|||||||
std::getline(rfile, buffer);
|
std::getline(rfile, buffer);
|
||||||
tf.boxHeight = std::stoi(buffer);
|
tf.boxHeight = std::stoi(buffer);
|
||||||
|
|
||||||
// lee el resto de datos del fichero
|
|
||||||
int index = 32;
|
int index = 32;
|
||||||
int line_read = 0;
|
int line_read = 0;
|
||||||
while (std::getline(rfile, buffer)) {
|
while (std::getline(rfile, buffer)) {
|
||||||
// Almacena solo las lineas impares
|
|
||||||
if (line_read % 2 == 1) {
|
if (line_read % 2 == 1) {
|
||||||
tf.offset[index++].w = std::stoi(buffer);
|
tf.offset[index++].w = std::stoi(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limpia el buffer
|
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
line_read++;
|
line_read++;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cierra el fichero
|
static void computeTextFileOffsets(textFile_t &tf) {
|
||||||
if (verbose) {
|
|
||||||
std::cout << "Text loaded: " << filename.c_str() << std::endl;
|
|
||||||
}
|
|
||||||
rfile.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// El fichero no se puede abrir
|
|
||||||
else {
|
|
||||||
if (verbose) {
|
|
||||||
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
|
|
||||||
for (int i = 32; i < 128; ++i) {
|
for (int i = 32; i < 128; ++i) {
|
||||||
tf.offset[i].x = ((i - 32) % 15) * tf.boxWidth;
|
tf.offset[i].x = ((i - 32) % 15) * tf.boxWidth;
|
||||||
tf.offset[i].y = ((i - 32) / 15) * tf.boxHeight;
|
tf.offset[i].y = ((i - 32) / 15) * tf.boxHeight;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llena una estructuta textFile_t desde un fichero
|
||||||
|
textFile_t LoadTextFile(std::string file, bool verbose) {
|
||||||
|
textFile_t tf;
|
||||||
|
for (int i = 0; i < 128; ++i) {
|
||||||
|
tf.offset[i].x = 0;
|
||||||
|
tf.offset[i].y = 0;
|
||||||
|
tf.offset[i].w = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filename = file.substr(file.find_last_of("\\/") + 1).c_str();
|
||||||
|
std::ifstream rfile(file);
|
||||||
|
if (rfile.is_open() && rfile.good()) {
|
||||||
|
parseTextFileStream(rfile, tf);
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "Text loaded: " << filename.c_str() << std::endl;
|
||||||
|
}
|
||||||
|
} else if (verbose) {
|
||||||
|
std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
computeTextFileOffsets(tf);
|
||||||
|
return tf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llena una estructura textFile_t desde bytes en memoria
|
||||||
|
textFile_t LoadTextFileFromMemory(const std::vector<uint8_t> &bytes, bool verbose) {
|
||||||
|
textFile_t tf;
|
||||||
|
for (int i = 0; i < 128; ++i) {
|
||||||
|
tf.offset[i].x = 0;
|
||||||
|
tf.offset[i].y = 0;
|
||||||
|
tf.offset[i].w = 0;
|
||||||
|
}
|
||||||
|
if (!bytes.empty()) {
|
||||||
|
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
|
||||||
|
std::stringstream ss(content);
|
||||||
|
parseTextFileStream(ss, tf);
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "Text loaded from memory" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
computeTextFileOffsets(tf);
|
||||||
return tf;
|
return tf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +145,24 @@ Text::Text(textFile_t *textFile, Texture *texture, SDL_Renderer *renderer) {
|
|||||||
fixedWidth = false;
|
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
|
// Destructor
|
||||||
Text::~Text() {
|
Text::~Text() {
|
||||||
delete sprite;
|
delete sprite;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
|
#include <vector>
|
||||||
class Sprite;
|
class Sprite;
|
||||||
class Texture;
|
class Texture;
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
@@ -28,6 +30,9 @@ struct textFile_t {
|
|||||||
// Llena una estructuta textFile_t desde un fichero
|
// Llena una estructuta textFile_t desde un fichero
|
||||||
textFile_t LoadTextFile(std::string file, bool verbose = false);
|
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
|
// Clase texto. Pinta texto en pantalla a partir de un bitmap
|
||||||
class Text {
|
class Text {
|
||||||
private:
|
private:
|
||||||
@@ -47,6 +52,9 @@ class Text {
|
|||||||
Text(std::string textFile, Texture *texture, SDL_Renderer *renderer);
|
Text(std::string textFile, Texture *texture, SDL_Renderer *renderer);
|
||||||
Text(textFile_t *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
|
// Destructor
|
||||||
~Text();
|
~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
|
// Destructor
|
||||||
Texture::~Texture() {
|
Texture::~Texture() {
|
||||||
// Libera memoria
|
// Libera memoria
|
||||||
unload();
|
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
|
// Carga una imagen desde un fichero
|
||||||
bool Texture::loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose) {
|
bool Texture::loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose) {
|
||||||
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
|
||||||
int req_format = STBI_rgb_alpha;
|
int req_format = STBI_rgb_alpha;
|
||||||
int width, height, orig_format;
|
int w, h, orig_format;
|
||||||
unsigned char *data = stbi_load(path.c_str(), &width, &height, &orig_format, req_format);
|
unsigned char *data = stbi_load(path.c_str(), &w, &h, &orig_format, req_format);
|
||||||
if (data == nullptr) {
|
if (data == nullptr) {
|
||||||
SDL_Log("Loading image failed: %s", stbi_failure_reason());
|
SDL_Log("Loading image failed: %s", stbi_failure_reason());
|
||||||
exit(1);
|
exit(1);
|
||||||
} else {
|
} else if (verbose) {
|
||||||
if (verbose) {
|
|
||||||
std::cout << "Image loaded: " << filename.c_str() << std::endl;
|
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();
|
unload();
|
||||||
|
SDL_Texture *newTexture = createTextureFromPixels(renderer, data, w, h, &this->width, &this->height);
|
||||||
// La textura final
|
if (newTexture == nullptr && verbose) {
|
||||||
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;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return success
|
|
||||||
stbi_image_free(data);
|
stbi_image_free(data);
|
||||||
texture = newTexture;
|
texture = newTexture;
|
||||||
return texture != nullptr;
|
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
|
// Crea una textura en blanco
|
||||||
bool Texture::createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess access) {
|
bool Texture::createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess access) {
|
||||||
// Crea una textura sin inicializar
|
// Crea una textura sin inicializar
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string> // for basic_string, string
|
#include <string> // for basic_string, string
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class Texture {
|
class Texture {
|
||||||
private:
|
private:
|
||||||
@@ -24,12 +26,18 @@ class Texture {
|
|||||||
// Constructor
|
// Constructor
|
||||||
Texture(SDL_Renderer *renderer, std::string path = "", bool verbose = false);
|
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
|
// Destructor
|
||||||
~Texture();
|
~Texture();
|
||||||
|
|
||||||
// Carga una imagen desde un fichero
|
// Carga una imagen desde un fichero
|
||||||
bool loadFromFile(std::string path, SDL_Renderer *renderer, bool verbose = false);
|
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
|
// Crea una textura en blanco
|
||||||
bool createBlank(SDL_Renderer *renderer, int width, int height, SDL_TextureAccess = SDL_TEXTUREACCESS_STREAMING);
|
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 "jail_audio.hpp" // for JA_StopMusic, JA_GetMusicState, JA_Play...
|
||||||
#include "lang.h" // for Lang, ba_BA, en_UK, es_ES
|
#include "lang.h" // for Lang, ba_BA, en_UK, es_ES
|
||||||
#include "menu.h" // for Menu
|
#include "menu.h" // for Menu
|
||||||
|
#include "resource.h"
|
||||||
#include "screen.h" // for Screen, FILTER_LINEAL, FILTER_NEAREST
|
#include "screen.h" // for Screen, FILTER_LINEAL, FILTER_NEAREST
|
||||||
#include "smartsprite.h" // for SmartSprite
|
#include "smartsprite.h" // for SmartSprite
|
||||||
#include "sprite.h" // for Sprite
|
#include "sprite.h" // for Sprite
|
||||||
@@ -36,28 +37,29 @@ Title::Title(SDL_Renderer *renderer, Screen *screen, Input *input, Asset *asset,
|
|||||||
eventHandler = new SDL_Event();
|
eventHandler = new SDL_Event();
|
||||||
fade = new Fade(renderer);
|
fade = new Fade(renderer);
|
||||||
|
|
||||||
dustTexture = new Texture(renderer, asset->get("title_dust.png"));
|
Resource *R = Resource::get();
|
||||||
coffeeTexture = new Texture(renderer, asset->get("title_coffee.png"));
|
dustTexture = R->getTexture("title_dust.png");
|
||||||
crisisTexture = new Texture(renderer, asset->get("title_crisis.png"));
|
coffeeTexture = R->getTexture("title_coffee.png");
|
||||||
gradientTexture = new Texture(renderer, asset->get("title_gradient.png"));
|
crisisTexture = R->getTexture("title_crisis.png");
|
||||||
|
gradientTexture = R->getTexture("title_gradient.png");
|
||||||
|
|
||||||
coffeeBitmap = new SmartSprite(coffeeTexture, renderer);
|
coffeeBitmap = new SmartSprite(coffeeTexture, renderer);
|
||||||
crisisBitmap = new SmartSprite(crisisTexture, renderer);
|
crisisBitmap = new SmartSprite(crisisTexture, renderer);
|
||||||
dustBitmapL = new AnimatedSprite(dustTexture, renderer, asset->get("title_dust.ani"));
|
dustBitmapL = new AnimatedSprite(dustTexture, renderer, "", &R->getAnimationLines("title_dust.ani"));
|
||||||
dustBitmapR = new AnimatedSprite(dustTexture, renderer, asset->get("title_dust.ani"));
|
dustBitmapR = new AnimatedSprite(dustTexture, renderer, "", &R->getAnimationLines("title_dust.ani"));
|
||||||
gradient = new Sprite({0, 0, 256, 192}, gradientTexture, renderer);
|
gradient = new Sprite({0, 0, 256, 192}, gradientTexture, renderer);
|
||||||
|
|
||||||
text1 = new Text(asset->get("smb2.png"), asset->get("smb2.txt"), renderer);
|
text1 = R->getText("smb2");
|
||||||
text2 = new Text(asset->get("8bithud.png"), asset->get("8bithud.txt"), renderer);
|
text2 = R->getText("8bithud");
|
||||||
|
|
||||||
#ifdef GAME_CONSOLE
|
#ifdef GAME_CONSOLE
|
||||||
menu.title = new Menu(renderer, asset, input, asset->get("title_gc.men"));
|
menu.title = R->getMenu("title_gc");
|
||||||
menu.options = new Menu(renderer, asset, input, asset->get("options_gc.men"));
|
menu.options = R->getMenu("options_gc");
|
||||||
#else
|
#else
|
||||||
menu.title = new Menu(renderer, asset, input, asset->get("title.men"));
|
menu.title = R->getMenu("title");
|
||||||
menu.options = new Menu(renderer, asset, input, asset->get("options.men"));
|
menu.options = R->getMenu("options");
|
||||||
#endif
|
#endif
|
||||||
menu.playerSelect = new Menu(renderer, asset, input, asset->get("player_select.men"));
|
menu.playerSelect = R->getMenu("player_select");
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
// En la versión web no se puede cerrar el programa: ocultamos la opción QUIT del menú de título
|
// 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);
|
menu.title->setSelectable(3, false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Sonidos
|
// Sonidos y música (handles compartidos)
|
||||||
crashSound = JA_LoadSound(asset->get("title.wav").c_str());
|
crashSound = R->getSound("title.wav");
|
||||||
|
titleMusic = R->getMusic("title.ogg");
|
||||||
// Musicas
|
|
||||||
titleMusic = JA_LoadMusic(asset->get("title.ogg").c_str());
|
|
||||||
|
|
||||||
// Inicializa los valores
|
// Inicializa los valores
|
||||||
init();
|
init();
|
||||||
@@ -80,34 +80,14 @@ Title::~Title() {
|
|||||||
delete eventHandler;
|
delete eventHandler;
|
||||||
delete fade;
|
delete fade;
|
||||||
|
|
||||||
dustTexture->unload();
|
// Las texturas, Text, Menu, sonido y música son propiedad de Resource —
|
||||||
delete dustTexture;
|
// no se liberan aquí. Solo los sprites que posee esta escena.
|
||||||
|
|
||||||
coffeeTexture->unload();
|
|
||||||
delete coffeeTexture;
|
|
||||||
|
|
||||||
crisisTexture->unload();
|
|
||||||
delete crisisTexture;
|
|
||||||
|
|
||||||
gradientTexture->unload();
|
|
||||||
delete gradientTexture;
|
|
||||||
|
|
||||||
delete coffeeBitmap;
|
delete coffeeBitmap;
|
||||||
delete crisisBitmap;
|
delete crisisBitmap;
|
||||||
delete dustBitmapL;
|
delete dustBitmapL;
|
||||||
delete dustBitmapR;
|
delete dustBitmapR;
|
||||||
delete gradient;
|
delete gradient;
|
||||||
|
|
||||||
delete text1;
|
|
||||||
delete text2;
|
|
||||||
|
|
||||||
delete menu.title;
|
|
||||||
delete menu.options;
|
|
||||||
delete menu.playerSelect;
|
|
||||||
|
|
||||||
JA_DeleteSound(crashSound);
|
|
||||||
JA_DeleteMusic(titleMusic);
|
|
||||||
|
|
||||||
SDL_DestroyTexture(background);
|
SDL_DestroyTexture(background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
tools/pack_resources/Makefile
Normal file
52
tools/pack_resources/Makefile
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Makefile para la herramienta de empaquetado de Coffee Crisis
|
||||||
|
# =============================================================
|
||||||
|
|
||||||
|
CXX := g++
|
||||||
|
CXXFLAGS := -std=c++20 -Wall -Os -I../../source
|
||||||
|
SOURCES := pack_resources.cpp ../../source/resource_pack.cpp
|
||||||
|
TARGET := pack_resources
|
||||||
|
CLEAN_FILES := pack_resources *.pack *.o
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
DETECTED_OS := Windows
|
||||||
|
TARGET := $(TARGET).exe
|
||||||
|
CLEAN_CMD := del /Q
|
||||||
|
FixPath = $(subst /,\\,$1)
|
||||||
|
else
|
||||||
|
DETECTED_OS := $(shell uname -s)
|
||||||
|
CLEAN_CMD := rm -f
|
||||||
|
FixPath = $1
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: all pack_tool pack clean help
|
||||||
|
|
||||||
|
all: pack_tool
|
||||||
|
|
||||||
|
pack_tool:
|
||||||
|
@echo "Compilando herramienta de empaquetado para $(DETECTED_OS)..."
|
||||||
|
$(CXX) $(CXXFLAGS) $(SOURCES) -o $(TARGET)
|
||||||
|
@echo "Herramienta compilada: $(TARGET)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "Limpiando archivos generados..."
|
||||||
|
$(CLEAN_CMD) $(call FixPath,$(CLEAN_FILES))
|
||||||
|
@echo "Archivos limpiados"
|
||||||
|
|
||||||
|
# Crear pack final (invocado desde el Makefile raíz)
|
||||||
|
pack: pack_tool
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
.\$(TARGET) ..\..\data ..\..\resources.pack
|
||||||
|
else
|
||||||
|
./$(TARGET) ../../data ../../resources.pack
|
||||||
|
endif
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Makefile para herramientas de Coffee Crisis"
|
||||||
|
@echo "==========================================="
|
||||||
|
@echo ""
|
||||||
|
@echo "Comandos disponibles:"
|
||||||
|
@echo " all - Compilar herramienta de empaquetado (por defecto)"
|
||||||
|
@echo " pack_tool - Compilar herramienta de empaquetado"
|
||||||
|
@echo " pack - Crear pack final en ../../resources.pack"
|
||||||
|
@echo " clean - Limpiar archivos generados"
|
||||||
|
@echo " help - Mostrar esta ayuda"
|
||||||
104
tools/pack_resources/pack_resources.cpp
Normal file
104
tools/pack_resources/pack_resources.cpp
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "resource_pack.h"
|
||||||
|
|
||||||
|
static constexpr const char* APP_NAME = "Coffee Crisis";
|
||||||
|
|
||||||
|
void showHelp() {
|
||||||
|
std::cout << APP_NAME << " - Resource Packer" << '\n';
|
||||||
|
std::cout << "===============================================" << '\n';
|
||||||
|
std::cout << "Usage: pack_resources [options] [input_dir] [output_file]" << '\n';
|
||||||
|
std::cout << '\n';
|
||||||
|
std::cout << "Options:" << '\n';
|
||||||
|
std::cout << " --help Show this help message" << '\n';
|
||||||
|
std::cout << " --list List contents of an existing pack file" << '\n';
|
||||||
|
std::cout << '\n';
|
||||||
|
std::cout << "Arguments:" << '\n';
|
||||||
|
std::cout << " input_dir Directory to pack (default: data)" << '\n';
|
||||||
|
std::cout << " output_file Pack file name (default: resources.pack)" << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
void listPackContents(const std::string& packFile) {
|
||||||
|
ResourcePack pack;
|
||||||
|
if (!pack.loadPack(packFile)) {
|
||||||
|
std::cerr << "Error: Cannot open pack file: " << packFile << '\n';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resources = pack.getResourceList();
|
||||||
|
std::cout << "Pack file: " << packFile << '\n';
|
||||||
|
std::cout << "Resources: " << resources.size() << '\n';
|
||||||
|
std::cout << "Contents:" << '\n';
|
||||||
|
|
||||||
|
for (const auto& resource : resources) {
|
||||||
|
std::cout << " " << resource << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
std::string dataDir = "data";
|
||||||
|
std::string outputFile = "resources.pack";
|
||||||
|
bool listMode = false;
|
||||||
|
bool dataDirSet = false;
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
std::string arg = argv[i];
|
||||||
|
if (arg == "--help" || arg == "-h") {
|
||||||
|
showHelp();
|
||||||
|
return 0;
|
||||||
|
} else if (arg == "--list") {
|
||||||
|
listMode = true;
|
||||||
|
if (i + 1 < argc) {
|
||||||
|
outputFile = argv[++i];
|
||||||
|
}
|
||||||
|
} else if (!arg.empty() && arg[0] != '-') {
|
||||||
|
if (!dataDirSet) {
|
||||||
|
dataDir = arg;
|
||||||
|
dataDirSet = true;
|
||||||
|
} else {
|
||||||
|
outputFile = arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listMode) {
|
||||||
|
listPackContents(outputFile);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << APP_NAME << " - Resource Packer" << '\n';
|
||||||
|
std::cout << "===============================================" << '\n';
|
||||||
|
std::cout << "Input directory: " << dataDir << '\n';
|
||||||
|
std::cout << "Output file: " << outputFile << '\n';
|
||||||
|
std::cout << '\n';
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(dataDir)) {
|
||||||
|
std::cerr << "Error: Input directory does not exist: " << dataDir << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourcePack pack;
|
||||||
|
|
||||||
|
std::cout << "Scanning and packing resources..." << '\n';
|
||||||
|
if (!pack.addDirectory(dataDir)) {
|
||||||
|
std::cerr << "Error: Failed to add directory to pack" << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Found " << pack.getResourceCount() << " resources" << '\n';
|
||||||
|
|
||||||
|
std::cout << "Saving pack file..." << '\n';
|
||||||
|
if (!pack.savePack(outputFile)) {
|
||||||
|
std::cerr << "Error: Failed to save pack file" << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path packPath(outputFile);
|
||||||
|
auto fileSize = std::filesystem::file_size(packPath);
|
||||||
|
|
||||||
|
std::cout << "Pack file created successfully!" << '\n';
|
||||||
|
std::cout << "File size: " << (fileSize / 1024.0 / 1024.0) << " MB" << '\n';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user