forked from jaildesigner-jailgames/jaildoctors_dilemma
creada carpeta source2
This commit is contained in:
108
organizar.sh
Executable file
108
organizar.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==============================================================================
|
||||
# Script para reorganizar la carpeta 'source' a una nueva estructura en 'source2'
|
||||
# ==============================================================================
|
||||
#
|
||||
# INSTRUCCIONES:
|
||||
# 1. Guarda este archivo como 'organizar.sh' en el directorio padre de 'source'.
|
||||
# 2. Abre una terminal en ese directorio.
|
||||
# 3. Dale permisos de ejecución con: chmod +x organizar.sh
|
||||
# 4. Ejecútalo con: ./organizar.sh
|
||||
#
|
||||
|
||||
# --- Configuración ---
|
||||
SRC_DIR="source"
|
||||
DEST_DIR="source2"
|
||||
|
||||
# --- Comprobaciones Previas ---
|
||||
if [ ! -d "$SRC_DIR" ]; then
|
||||
echo "❌ Error: El directorio '$SRC_DIR' no se encuentra."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🚀 Iniciando la reorganización de '$SRC_DIR' a '$DEST_DIR'..."
|
||||
|
||||
# --- Limpieza y Creación de la Estructura Base ---
|
||||
rm -rf "$DEST_DIR"
|
||||
mkdir -p "$DEST_DIR"
|
||||
echo " - Creado directorio raíz '$DEST_DIR'."
|
||||
|
||||
# Creando directorios principales
|
||||
mkdir -p "$DEST_DIR/Core/Input"
|
||||
mkdir -p "$DEST_DIR/Core/Rendering"
|
||||
mkdir -p "$DEST_DIR/Core/Resources"
|
||||
mkdir -p "$DEST_DIR/Core/System"
|
||||
mkdir -p "$DEST_DIR/Game/Entities"
|
||||
mkdir -p "$DEST_DIR/Game/Gameplay"
|
||||
mkdir -p "$DEST_DIR/Game/Scenes"
|
||||
mkdir -p "$DEST_DIR/Game/UI"
|
||||
mkdir -p "$DEST_DIR/Utils"
|
||||
echo " - Creada la estructura de carpetas principal."
|
||||
|
||||
# --- Función para copiar archivos ---
|
||||
copy_file() {
|
||||
local dest_sub_dir="$1"
|
||||
shift
|
||||
for file in "$@"; do
|
||||
if [ -f "$SRC_DIR/$file" ]; then
|
||||
cp "$SRC_DIR/$file" "$DEST_DIR/$dest_sub_dir/"
|
||||
else
|
||||
echo " ⚠️ Advertencia: No se encontró el archivo '$file' en '$SRC_DIR/'."
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# --- Copiando Archivos a su Nueva Ubicación ---
|
||||
|
||||
echo " - Copiando archivos del motor (Core)..."
|
||||
# Core/Input
|
||||
copy_file "Core/Input" "input.h" "input.cpp" "global_inputs.h" "global_inputs.cpp" "mouse.h" "mouse.cpp"
|
||||
|
||||
# Core/Rendering
|
||||
copy_file "Core/Rendering" "texture.h" "texture.cpp" "surface.h" "surface.cpp" "screen.h" "screen.cpp" "text.h" "text.cpp" "gif.h" "gif.cpp"
|
||||
# Mover carpetas de rendering existentes
|
||||
if [ -d "$SRC_DIR/sprite" ]; then cp -r "$SRC_DIR/sprite/"* "$DEST_DIR/Core/Rendering/"; fi
|
||||
if [ -d "$SRC_DIR/rendering" ]; then cp -r "$SRC_DIR/rendering" "$DEST_DIR/Core/"; fi
|
||||
|
||||
# Core/Resources
|
||||
copy_file "Core/Resources" "resource.h" "resource.cpp" "asset.h" "asset.cpp"
|
||||
|
||||
# Core/System
|
||||
copy_file "Core/System" "director.h" "director.cpp" "debug.h" "debug.cpp"
|
||||
|
||||
echo " - Copiando archivos de lógica de juego (Game)..."
|
||||
# Game/Entities
|
||||
copy_file "Game/Entities" "player.h" "player.cpp" "enemy.h" "enemy.cpp" "item.h" "item.cpp"
|
||||
|
||||
# Game/Gameplay
|
||||
copy_file "Game/Gameplay" "room.h" "room.cpp" "room_tracker.h" "room_tracker.cpp" "item_tracker.h" "item_tracker.cpp" "stats.h" "stats.cpp" "scoreboard.h" "scoreboard.cpp" "cheevos.h" "cheevos.cpp" "options.h" "options.cpp"
|
||||
|
||||
# Game/Scenes (tu antigua 'sections')
|
||||
if [ -d "$SRC_DIR/sections" ]; then
|
||||
cp -r "$SRC_DIR/sections/"* "$DEST_DIR/Game/Scenes/"
|
||||
fi
|
||||
|
||||
# Game/UI (tu antigua 'ui')
|
||||
if [ -d "$SRC_DIR/ui" ]; then
|
||||
cp -r "$SRC_DIR/ui/"* "$DEST_DIR/Game/UI/"
|
||||
fi
|
||||
|
||||
echo " - Copiando utilidades y ficheros externos..."
|
||||
# Utils
|
||||
copy_file "Utils" "utils.h" "utils.cpp" "defines.h" "global_events.h" "global_events.cpp"
|
||||
|
||||
# External (copia la carpeta entera)
|
||||
if [ -d "$SRC_DIR/external" ]; then
|
||||
cp -r "$SRC_DIR/external" "$DEST_DIR/"
|
||||
fi
|
||||
|
||||
# Archivos de la raíz del proyecto
|
||||
copy_file "" "main.cpp" "version.h.in"
|
||||
|
||||
echo ""
|
||||
echo "✅ ¡Proceso completado!"
|
||||
echo "Tu proyecto ha sido reorganizado en la carpeta '$DEST_DIR'."
|
||||
echo "La carpeta original '$SRC_DIR' no ha sido modificada."
|
||||
|
||||
echo "🧠 Recuerda: El siguiente paso crucial es actualizar tus '#includes' y tu sistema de compilación (CMake, Makefile, etc.) para que apunten a las nuevas rutas."
|
||||
103
source2/Core/Input/global_inputs.cpp
Normal file
103
source2/Core/Input/global_inputs.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "global_inputs.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para allocator, operator+, char_traits, string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "input.h" // Para Input, InputAction, INPUT_DO_NOT_ALLOW_REPEAT
|
||||
#include "options.h" // Para Options, options, OptionsVideo, Section
|
||||
#include "screen.h" // Para Screen
|
||||
#include "ui/notifier.h" // Para Notifier, NotificationText
|
||||
#include "utils.h" // Para stringInVector
|
||||
|
||||
namespace globalInputs {
|
||||
void quit() {
|
||||
const std::string code = options.section.section == Section::GAME ? "PRESS AGAIN TO RETURN TO MENU" : "PRESS AGAIN TO EXIT";
|
||||
auto code_found = stringInVector(Notifier::get()->getCodes(), code);
|
||||
if (code_found) {
|
||||
// Si la notificación de salir está activa, cambia de sección
|
||||
options.section.section = options.section.section == Section::GAME ? Section::TITLE : Section::QUIT;
|
||||
} else {
|
||||
// Si la notificación de salir no está activa, muestra la notificación
|
||||
Notifier::get()->show({code}, NotificationText::CENTER, 2000, -1, true, code);
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia de seccion
|
||||
void skip_section() {
|
||||
switch (options.section.section) {
|
||||
case Section::LOGO:
|
||||
case Section::LOADING_SCREEN:
|
||||
case Section::CREDITS:
|
||||
case Section::DEMO:
|
||||
case Section::GAME_OVER:
|
||||
case Section::ENDING:
|
||||
case Section::ENDING2:
|
||||
options.section.section = Section::TITLE;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
|
||||
void check() {
|
||||
if (Input::get()->checkInput(InputAction::EXIT, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
quit();
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::ACCEPT, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
skip_section();
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::TOGGLE_BORDER, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
Screen::get()->toggleBorder();
|
||||
Notifier::get()->show({"BORDER " + std::string(options.video.border.enabled ? "ENABLED" : "DISABLED")}, NotificationText::CENTER);
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::TOGGLE_VIDEOMODE, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
Screen::get()->toggleVideoMode();
|
||||
Notifier::get()->show({"FULLSCREEN " + std::string(options.video.fullscreen == 0 ? "DISABLED" : "ENABLED")}, NotificationText::CENTER);
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::WINDOW_DEC_ZOOM, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
if (Screen::get()->decWindowZoom()) {
|
||||
Notifier::get()->show({"WINDOW ZOOM x" + std::to_string(options.window.zoom)}, NotificationText::CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::WINDOW_INC_ZOOM, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
if (Screen::get()->incWindowZoom()) {
|
||||
Notifier::get()->show({"WINDOW ZOOM x" + std::to_string(options.window.zoom)}, NotificationText::CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::TOGGLE_SHADERS, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
Screen::get()->toggleShaders();
|
||||
Notifier::get()->show({"SHADERS " + std::string(options.video.shaders ? "ENABLED" : "DISABLED")}, NotificationText::CENTER);
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::NEXT_PALETTE, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
Screen::get()->nextPalette();
|
||||
Notifier::get()->show({"PALETTE " + options.video.palette}, NotificationText::CENTER);
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::PREVIOUS_PALETTE, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
Screen::get()->previousPalette();
|
||||
Notifier::get()->show({"PALETTE " + options.video.palette}, NotificationText::CENTER);
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::TOGGLE_INTEGER_SCALE, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
Screen::get()->toggleIntegerScale();
|
||||
Screen::get()->setVideoMode(options.video.fullscreen);
|
||||
Notifier::get()->show({"INTEGER SCALE " + std::string(options.video.integer_scale ? "ENABLED" : "DISABLED")}, NotificationText::CENTER);
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::SHOW_DEBUG_INFO, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
Screen::get()->toggleDebugInfo();
|
||||
}
|
||||
}
|
||||
} // namespace globalInputs
|
||||
7
source2/Core/Input/global_inputs.h
Normal file
7
source2/Core/Input/global_inputs.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace globalInputs
|
||||
{
|
||||
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
|
||||
void check();
|
||||
}
|
||||
289
source2/Core/Input/input.cpp
Normal file
289
source2/Core/Input/input.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
#include "input.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para find
|
||||
#include <iostream> // Para basic_ostream, operator<<, cout, endl
|
||||
#include <iterator> // Para distance
|
||||
#include <unordered_map> // Para unordered_map, operator==, _Node_cons...
|
||||
#include <utility> // Para pair
|
||||
|
||||
// [SINGLETON]
|
||||
Input* Input::input_ = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
void Input::init(const std::string& game_controller_db_path) {
|
||||
Input::input_ = new Input(game_controller_db_path);
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
void Input::destroy() {
|
||||
delete Input::input_;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
Input* Input::get() {
|
||||
return Input::input_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Input::Input(const std::string& game_controller_db_path)
|
||||
: game_controller_db_path_(game_controller_db_path) {
|
||||
// Busca si hay mandos conectados
|
||||
discoverGameControllers();
|
||||
|
||||
// Inicializa los vectores
|
||||
key_bindings_.resize(static_cast<int>(InputAction::SIZE), KeyBindings());
|
||||
controller_bindings_.resize(num_gamepads_, std::vector<ControllerBindings>(static_cast<int>(InputAction::SIZE), ControllerBindings()));
|
||||
|
||||
// Listado de los inputs para jugar que utilizan botones, ni palancas ni crucetas
|
||||
button_inputs_ = {InputAction::JUMP};
|
||||
}
|
||||
|
||||
// Asigna inputs a teclas
|
||||
void Input::bindKey(InputAction input, SDL_Scancode code) {
|
||||
key_bindings_.at(static_cast<int>(input)).scancode = code;
|
||||
}
|
||||
|
||||
// Asigna inputs a botones del mando
|
||||
void Input::bindGameControllerButton(int controller_index, InputAction input, SDL_GamepadButton button) {
|
||||
if (controller_index < num_gamepads_) {
|
||||
controller_bindings_.at(controller_index).at(static_cast<int>(input)).button = button;
|
||||
}
|
||||
}
|
||||
|
||||
// Asigna inputs a botones del mando
|
||||
void Input::bindGameControllerButton(int controller_index, InputAction input_target, InputAction input_source) {
|
||||
if (controller_index < num_gamepads_) {
|
||||
controller_bindings_.at(controller_index).at(static_cast<int>(input_target)).button = controller_bindings_.at(controller_index).at(static_cast<int>(input_source)).button;
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si un input esta activo
|
||||
bool Input::checkInput(InputAction input, bool repeat, InputDeviceToUse device, int controller_index) {
|
||||
bool success_keyboard = false;
|
||||
bool success_controller = false;
|
||||
const int input_index = static_cast<int>(input);
|
||||
|
||||
if (device == InputDeviceToUse::KEYBOARD || device == InputDeviceToUse::ANY) {
|
||||
auto key_states = SDL_GetKeyboardState(nullptr);
|
||||
|
||||
if (repeat) {
|
||||
success_keyboard = key_states[key_bindings_[input_index].scancode] != 0;
|
||||
} else {
|
||||
if (!key_bindings_[input_index].active) {
|
||||
if (key_states[key_bindings_[input_index].scancode] != 0) {
|
||||
key_bindings_[input_index].active = true;
|
||||
success_keyboard = true;
|
||||
} else {
|
||||
success_keyboard = false;
|
||||
}
|
||||
} else {
|
||||
if (key_states[key_bindings_[input_index].scancode] == 0) {
|
||||
key_bindings_[input_index].active = false;
|
||||
}
|
||||
success_keyboard = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameControllerFound() && controller_index >= 0 && controller_index < num_gamepads_) {
|
||||
if ((device == InputDeviceToUse::CONTROLLER) || (device == InputDeviceToUse::ANY)) {
|
||||
success_controller = checkAxisInput(input, controller_index, repeat);
|
||||
|
||||
if (!success_controller) {
|
||||
if (repeat) {
|
||||
success_controller = SDL_GetGamepadButton(connected_controllers_.at(controller_index), controller_bindings_.at(controller_index).at(input_index).button) != 0;
|
||||
} else {
|
||||
if (!controller_bindings_.at(controller_index).at(input_index).active) {
|
||||
if (SDL_GetGamepadButton(connected_controllers_.at(controller_index), controller_bindings_.at(controller_index).at(input_index).button) != 0) {
|
||||
controller_bindings_.at(controller_index).at(input_index).active = true;
|
||||
success_controller = true;
|
||||
} else {
|
||||
success_controller = false;
|
||||
}
|
||||
} else {
|
||||
if (SDL_GetGamepadButton(connected_controllers_.at(controller_index), controller_bindings_.at(controller_index).at(input_index).button) == 0) {
|
||||
controller_bindings_.at(controller_index).at(input_index).active = false;
|
||||
}
|
||||
success_controller = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (success_keyboard || success_controller);
|
||||
}
|
||||
|
||||
// Comprueba si hay almenos un input activo
|
||||
bool Input::checkAnyInput(InputDeviceToUse device, int controller_index) {
|
||||
if (device == InputDeviceToUse::KEYBOARD || device == InputDeviceToUse::ANY) {
|
||||
const bool *key_states = SDL_GetKeyboardState(nullptr);
|
||||
|
||||
for (int i = 0; i < (int)key_bindings_.size(); ++i) {
|
||||
if (key_states[key_bindings_[i].scancode] != 0 && !key_bindings_[i].active) {
|
||||
key_bindings_[i].active = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameControllerFound()) {
|
||||
if (device == InputDeviceToUse::CONTROLLER || device == InputDeviceToUse::ANY) {
|
||||
for (int i = 0; i < (int)controller_bindings_.size(); ++i) {
|
||||
if (SDL_GetGamepadButton(connected_controllers_[controller_index], controller_bindings_[controller_index][i].button) != 0 && !controller_bindings_[controller_index][i].active) {
|
||||
controller_bindings_[controller_index][i].active = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Busca si hay mandos conectados
|
||||
bool Input::discoverGameControllers() {
|
||||
bool found = false;
|
||||
|
||||
// Asegúrate de que el subsistema de gamepads está inicializado
|
||||
if (SDL_WasInit(SDL_INIT_GAMEPAD) != 1) {
|
||||
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
||||
}
|
||||
|
||||
// Carga el mapping de mandos desde archivo
|
||||
if (SDL_AddGamepadMappingsFromFile(game_controller_db_path_.c_str()) < 0) {
|
||||
std::cout << "Error, could not load " << game_controller_db_path_.c_str()
|
||||
<< " file: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
|
||||
// En SDL3 ya no existe SDL_NumJoysticks()
|
||||
// Ahora se obtiene un array dinámico de IDs
|
||||
int num_joysticks = 0;
|
||||
SDL_JoystickID* joystick_ids = SDL_GetJoysticks(&num_joysticks);
|
||||
|
||||
num_joysticks_ = num_joysticks;
|
||||
num_gamepads_ = 0;
|
||||
joysticks_.clear();
|
||||
|
||||
// Recorremos todos los joysticks detectados
|
||||
for (int i = 0; i < num_joysticks_; ++i) {
|
||||
SDL_Joystick* joy = SDL_OpenJoystick(joystick_ids[i]);
|
||||
joysticks_.push_back(joy);
|
||||
|
||||
if (SDL_IsGamepad(joystick_ids[i])) {
|
||||
num_gamepads_++;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n** LOOKING FOR GAME CONTROLLERS" << std::endl;
|
||||
if (num_joysticks_ != num_gamepads_) {
|
||||
std::cout << "Joysticks found: " << num_joysticks_ << std::endl;
|
||||
std::cout << "Gamepads found : " << num_gamepads_ << std::endl;
|
||||
} else {
|
||||
std::cout << "Gamepads found: " << num_gamepads_ << std::endl;
|
||||
}
|
||||
|
||||
if (num_gamepads_ > 0) {
|
||||
found = true;
|
||||
|
||||
for (int i = 0; i < num_joysticks_; i++) {
|
||||
if (SDL_IsGamepad(joystick_ids[i])) {
|
||||
SDL_Gamepad* pad = SDL_OpenGamepad(joystick_ids[i]);
|
||||
if (pad && SDL_GamepadConnected(pad)) {
|
||||
connected_controllers_.push_back(pad);
|
||||
|
||||
const char* name = SDL_GetGamepadName(pad);
|
||||
std::cout << "#" << i << ": " << (name ? name : "Unknown") << std::endl;
|
||||
controller_names_.push_back(name ? name : "Unknown");
|
||||
} else {
|
||||
std::cout << "SDL_GetError() = " << SDL_GetError() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// En SDL3 ya no hace falta SDL_GameControllerEventState()
|
||||
// Los eventos de gamepad están siempre habilitados
|
||||
}
|
||||
|
||||
SDL_free(joystick_ids);
|
||||
|
||||
std::cout << "\n** FINISHED LOOKING FOR GAME CONTROLLERS" << std::endl;
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
// Comprueba si hay algun mando conectado
|
||||
bool Input::gameControllerFound() { return num_gamepads_ > 0 ? true : false; }
|
||||
|
||||
// Obten el nombre de un mando de juego
|
||||
std::string Input::getControllerName(int controller_index) const { return num_gamepads_ > 0 ? controller_names_.at(controller_index) : std::string(); }
|
||||
|
||||
// Obten el número de mandos conectados
|
||||
int Input::getNumControllers() const { return num_gamepads_; }
|
||||
|
||||
// Obtiene el indice del controlador a partir de un event.id
|
||||
int Input::getJoyIndex(SDL_JoystickID id) const {
|
||||
for (int i = 0; i < num_joysticks_; ++i) {
|
||||
if (SDL_GetJoystickID(joysticks_[i]) == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Obtiene el SDL_GamepadButton asignado a un input
|
||||
SDL_GamepadButton Input::getControllerBinding(int controller_index, InputAction input) const {
|
||||
return controller_bindings_[controller_index][static_cast<int>(input)].button;
|
||||
}
|
||||
|
||||
// Obtiene el indice a partir del nombre del mando
|
||||
int Input::getIndexByName(const std::string& name) const {
|
||||
auto it = std::find(controller_names_.begin(), controller_names_.end(), name);
|
||||
return it != controller_names_.end() ? std::distance(controller_names_.begin(), it) : -1;
|
||||
}
|
||||
|
||||
// Comprueba el eje del mando
|
||||
bool Input::checkAxisInput(InputAction input, int controller_index, bool repeat) {
|
||||
// Umbral para considerar el eje como activo
|
||||
const Sint16 threshold = 30000;
|
||||
bool axis_active_now = false;
|
||||
|
||||
switch (input) {
|
||||
case InputAction::LEFT:
|
||||
axis_active_now = SDL_GetGamepadAxis(connected_controllers_[controller_index], SDL_GAMEPAD_AXIS_LEFTX) < -threshold;
|
||||
break;
|
||||
case InputAction::RIGHT:
|
||||
axis_active_now = SDL_GetGamepadAxis(connected_controllers_[controller_index], SDL_GAMEPAD_AXIS_LEFTX) > threshold;
|
||||
break;
|
||||
case InputAction::UP:
|
||||
axis_active_now = SDL_GetGamepadAxis(connected_controllers_[controller_index], SDL_GAMEPAD_AXIS_LEFTY) < -threshold;
|
||||
break;
|
||||
case InputAction::DOWN:
|
||||
axis_active_now = SDL_GetGamepadAxis(connected_controllers_[controller_index], SDL_GAMEPAD_AXIS_LEFTY) > threshold;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Referencia al binding correspondiente
|
||||
auto& binding = controller_bindings_.at(controller_index).at(static_cast<int>(input));
|
||||
|
||||
if (repeat) {
|
||||
// Si se permite repetir, simplemente devolvemos el estado actual
|
||||
return axis_active_now;
|
||||
} else {
|
||||
// Si no se permite repetir, aplicamos la lógica de transición
|
||||
if (axis_active_now && !binding.axis_active) {
|
||||
// Transición de inactivo a activo
|
||||
binding.axis_active = true;
|
||||
return true;
|
||||
} else if (!axis_active_now && binding.axis_active) {
|
||||
// Transición de activo a inactivo
|
||||
binding.axis_active = false;
|
||||
}
|
||||
// Mantener el estado actual
|
||||
return false;
|
||||
}
|
||||
}
|
||||
138
source2/Core/Input/input.h
Normal file
138
source2/Core/Input/input.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string, basic_string
|
||||
#include <vector> // Para vector
|
||||
|
||||
// Definiciones de repetición
|
||||
constexpr bool INPUT_ALLOW_REPEAT = true;
|
||||
constexpr bool INPUT_DO_NOT_ALLOW_REPEAT = false;
|
||||
|
||||
// Tipos de entrada
|
||||
enum class InputDeviceToUse : int {
|
||||
KEYBOARD = 0,
|
||||
CONTROLLER = 1,
|
||||
ANY = 2,
|
||||
};
|
||||
|
||||
enum class InputAction {
|
||||
// Inputs obligatorios
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
PAUSE,
|
||||
EXIT,
|
||||
ACCEPT,
|
||||
CANCEL,
|
||||
|
||||
// Inputs personalizados
|
||||
JUMP,
|
||||
WINDOW_INC_ZOOM,
|
||||
WINDOW_DEC_ZOOM,
|
||||
TOGGLE_VIDEOMODE,
|
||||
TOGGLE_INTEGER_SCALE,
|
||||
TOGGLE_BORDER,
|
||||
TOGGLE_MUSIC,
|
||||
NEXT_PALETTE,
|
||||
PREVIOUS_PALETTE,
|
||||
TOGGLE_SHADERS,
|
||||
SHOW_DEBUG_INFO,
|
||||
|
||||
// Input obligatorio
|
||||
NONE,
|
||||
SIZE
|
||||
};
|
||||
|
||||
class Input {
|
||||
private:
|
||||
// [SINGLETON] Objeto privado
|
||||
static Input* input_;
|
||||
|
||||
struct KeyBindings {
|
||||
Uint8 scancode; // Scancode asociado
|
||||
bool active; // Indica si está activo
|
||||
|
||||
// Constructor
|
||||
explicit KeyBindings(Uint8 sc = 0, bool act = false)
|
||||
: scancode(sc),
|
||||
active(act) {}
|
||||
};
|
||||
|
||||
struct ControllerBindings {
|
||||
SDL_GamepadButton button; // GameControllerButton asociado
|
||||
bool active; // Indica si está activo
|
||||
bool axis_active; // Estado del eje
|
||||
|
||||
// Constructor
|
||||
explicit ControllerBindings(SDL_GamepadButton btn = SDL_GAMEPAD_BUTTON_INVALID, bool act = false, bool axis_act = false)
|
||||
: button(btn),
|
||||
active(act),
|
||||
axis_active(axis_act) {}
|
||||
};
|
||||
|
||||
// Variables
|
||||
std::vector<SDL_Gamepad*> connected_controllers_; // Vector con todos los mandos conectados
|
||||
std::vector<SDL_Joystick*> joysticks_; // Vector con todos los joysticks conectados
|
||||
std::vector<KeyBindings> key_bindings_; // Vector con las teclas asociadas a los inputs predefinidos
|
||||
std::vector<std::vector<ControllerBindings>> controller_bindings_; // Vector con los botones asociadas a los inputs predefinidos para cada mando
|
||||
std::vector<std::string> controller_names_; // Vector con los nombres de los mandos
|
||||
std::vector<InputAction> button_inputs_; // Inputs asignados al jugador y a botones, excluyendo direcciones
|
||||
int num_joysticks_ = 0; // Número de joysticks conectados
|
||||
int num_gamepads_ = 0; // Número de mandos conectados
|
||||
std::string game_controller_db_path_; // Ruta al archivo gamecontrollerdb.txt
|
||||
|
||||
// Comprueba el eje del mando
|
||||
bool checkAxisInput(InputAction input, int controller_index, bool repeat);
|
||||
|
||||
// Constructor
|
||||
explicit Input(const std::string& game_controller_db_path);
|
||||
|
||||
// Destructor
|
||||
~Input() = default;
|
||||
|
||||
public:
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
static void init(const std::string& game_controller_db_path);
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
static void destroy();
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
static Input* get();
|
||||
|
||||
// Asigna inputs a teclas
|
||||
void bindKey(InputAction input, SDL_Scancode code);
|
||||
|
||||
// Asigna inputs a botones del mando
|
||||
void bindGameControllerButton(int controller_index, InputAction input, SDL_GamepadButton button);
|
||||
void bindGameControllerButton(int controller_index, InputAction inputTarget, InputAction inputSource);
|
||||
|
||||
// Comprueba si un input esta activo
|
||||
bool checkInput(InputAction input, bool repeat = true, InputDeviceToUse device = InputDeviceToUse::ANY, int controller_index = 0);
|
||||
|
||||
// Comprueba si hay almenos un input activo
|
||||
bool checkAnyInput(InputDeviceToUse device = InputDeviceToUse::ANY, int controller_index = 0);
|
||||
|
||||
// Busca si hay mandos conectados
|
||||
bool discoverGameControllers();
|
||||
|
||||
// Comprueba si hay algun mando conectado
|
||||
bool gameControllerFound();
|
||||
|
||||
// Obten el número de mandos conectados
|
||||
int getNumControllers() const;
|
||||
|
||||
// Obten el nombre de un mando de juego
|
||||
std::string getControllerName(int controller_index) const;
|
||||
|
||||
// Obtiene el indice del controlador a partir de un event.id
|
||||
int getJoyIndex(SDL_JoystickID id) const;
|
||||
|
||||
// Obtiene el SDL_GamepadButton asignado a un input
|
||||
SDL_GamepadButton getControllerBinding(int controller_index, InputAction input) const;
|
||||
|
||||
// Obtiene el indice a partir del nombre del mando
|
||||
int getIndexByName(const std::string& name) const;
|
||||
};
|
||||
26
source2/Core/Input/mouse.cpp
Normal file
26
source2/Core/Input/mouse.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "mouse.h"
|
||||
|
||||
|
||||
namespace Mouse {
|
||||
Uint32 cursor_hide_time = 3000; // Tiempo en milisegundos para ocultar el cursor
|
||||
Uint32 last_mouse_move_time = 0; // Última vez que el ratón se movió
|
||||
bool cursor_visible = true; // Estado del cursor
|
||||
|
||||
void handleEvent(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
||||
last_mouse_move_time = SDL_GetTicks();
|
||||
if (!cursor_visible) {
|
||||
SDL_ShowCursor();
|
||||
cursor_visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateCursorVisibility() {
|
||||
Uint32 current_time = SDL_GetTicks();
|
||||
if (cursor_visible && (current_time - last_mouse_move_time > cursor_hide_time)) {
|
||||
SDL_HideCursor();
|
||||
cursor_visible = false;
|
||||
}
|
||||
}
|
||||
} // namespace Mouse
|
||||
12
source2/Core/Input/mouse.h
Normal file
12
source2/Core/Input/mouse.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace Mouse {
|
||||
extern Uint32 cursor_hide_time; // Tiempo en milisegundos para ocultar el cursor
|
||||
extern Uint32 last_mouse_move_time; // Última vez que el ratón se movió
|
||||
extern bool cursor_visible; // Estado del cursor
|
||||
|
||||
void handleEvent(const SDL_Event& event);
|
||||
void updateCursorVisibility();
|
||||
} // namespace Mouse
|
||||
316
source2/Core/Rendering/gif.cpp
Normal file
316
source2/Core/Rendering/gif.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
#include "gif.h"
|
||||
#include <iostream> // Para std::cout
|
||||
#include <cstring> // Para memcpy, size_t
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string> // Para allocator, char_traits, operator==, basic_string
|
||||
|
||||
namespace GIF
|
||||
{
|
||||
|
||||
// Función inline para reemplazar el macro READ.
|
||||
// Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'.
|
||||
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size)
|
||||
{
|
||||
std::memcpy(dst, buffer, size);
|
||||
buffer += size;
|
||||
}
|
||||
|
||||
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out)
|
||||
{
|
||||
// Verifica que el code_length tenga un rango razonable.
|
||||
if (code_length < 2 || code_length > 12)
|
||||
{
|
||||
throw std::runtime_error("Invalid LZW code length");
|
||||
}
|
||||
|
||||
int i, bit;
|
||||
int prev = -1;
|
||||
std::vector<DictionaryEntry> dictionary;
|
||||
int dictionary_ind;
|
||||
unsigned int mask = 0x01;
|
||||
int reset_code_length = code_length;
|
||||
int clear_code = 1 << code_length;
|
||||
int stop_code = clear_code + 1;
|
||||
int match_len = 0;
|
||||
|
||||
// Inicializamos el diccionario con el tamaño correspondiente.
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
|
||||
{
|
||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dictionary[dictionary_ind].prev = -1;
|
||||
dictionary[dictionary_ind].len = 1;
|
||||
}
|
||||
dictionary_ind += 2; // Reservamos espacio para clear y stop codes
|
||||
|
||||
// Bucle principal: procesar el stream comprimido.
|
||||
while (input_length > 0)
|
||||
{
|
||||
int code = 0;
|
||||
// Lee (code_length + 1) bits para formar el código.
|
||||
for (i = 0; i < (code_length + 1); i++)
|
||||
{
|
||||
if (input_length <= 0)
|
||||
{
|
||||
throw std::runtime_error("Unexpected end of input in decompress");
|
||||
}
|
||||
bit = ((*input & mask) != 0) ? 1 : 0;
|
||||
mask <<= 1;
|
||||
if (mask == 0x100)
|
||||
{
|
||||
mask = 0x01;
|
||||
input++;
|
||||
input_length--;
|
||||
}
|
||||
code |= (bit << i);
|
||||
}
|
||||
|
||||
if (code == clear_code)
|
||||
{
|
||||
// Reinicia el diccionario.
|
||||
code_length = reset_code_length;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
|
||||
{
|
||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dictionary[dictionary_ind].prev = -1;
|
||||
dictionary[dictionary_ind].len = 1;
|
||||
}
|
||||
dictionary_ind += 2;
|
||||
prev = -1;
|
||||
continue;
|
||||
}
|
||||
else if (code == stop_code)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (prev > -1 && code_length < 12)
|
||||
{
|
||||
if (code > dictionary_ind)
|
||||
{
|
||||
std::cerr << "code = " << std::hex << code
|
||||
<< ", but dictionary_ind = " << dictionary_ind << std::endl;
|
||||
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
||||
}
|
||||
|
||||
int ptr;
|
||||
if (code == dictionary_ind)
|
||||
{
|
||||
ptr = prev;
|
||||
while (dictionary[ptr].prev != -1)
|
||||
ptr = dictionary[ptr].prev;
|
||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr = code;
|
||||
while (dictionary[ptr].prev != -1)
|
||||
ptr = dictionary[ptr].prev;
|
||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
||||
}
|
||||
dictionary[dictionary_ind].prev = prev;
|
||||
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
|
||||
dictionary_ind++;
|
||||
|
||||
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11))
|
||||
{
|
||||
code_length++;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
}
|
||||
}
|
||||
|
||||
prev = code;
|
||||
|
||||
// Verifica que 'code' sea un índice válido antes de usarlo.
|
||||
if (code < 0 || static_cast<size_t>(code) >= dictionary.size())
|
||||
{
|
||||
std::cerr << "Invalid LZW code " << code
|
||||
<< ", dictionary size " << dictionary.size() << std::endl;
|
||||
throw std::runtime_error("LZW error: invalid code encountered");
|
||||
}
|
||||
|
||||
int curCode = code; // Variable temporal para recorrer la cadena.
|
||||
match_len = dictionary[curCode].len;
|
||||
while (curCode != -1)
|
||||
{
|
||||
// Se asume que dictionary[curCode].len > 0.
|
||||
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
|
||||
if (dictionary[curCode].prev == curCode)
|
||||
{
|
||||
std::cerr << "Internal error; self-reference detected." << std::endl;
|
||||
throw std::runtime_error("Internal error in decompress: self-reference");
|
||||
}
|
||||
curCode = dictionary[curCode].prev;
|
||||
}
|
||||
out += match_len;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer)
|
||||
{
|
||||
std::vector<uint8_t> data;
|
||||
uint8_t block_size = *buffer;
|
||||
buffer++;
|
||||
while (block_size != 0)
|
||||
{
|
||||
data.insert(data.end(), buffer, buffer + block_size);
|
||||
buffer += block_size;
|
||||
block_size = *buffer;
|
||||
buffer++;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits)
|
||||
{
|
||||
ImageDescriptor image_descriptor;
|
||||
// Lee 9 bytes para el image descriptor.
|
||||
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
||||
|
||||
uint8_t lzw_code_size;
|
||||
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
|
||||
|
||||
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
|
||||
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
|
||||
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
|
||||
|
||||
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer)
|
||||
{
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
|
||||
ScreenDescriptor screen_descriptor;
|
||||
std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
|
||||
buffer += sizeof(ScreenDescriptor);
|
||||
|
||||
std::vector<uint32_t> global_color_table;
|
||||
if (screen_descriptor.fields & 0x80)
|
||||
{
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(global_color_table_size);
|
||||
for (int i = 0; i < global_color_table_size; ++i)
|
||||
{
|
||||
uint8_t r = buffer[0];
|
||||
uint8_t g = buffer[1];
|
||||
uint8_t b = buffer[2];
|
||||
global_color_table[i] = (r << 16) | (g << 8) | b;
|
||||
buffer += 3;
|
||||
}
|
||||
}
|
||||
return global_color_table;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h)
|
||||
{
|
||||
// Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a")
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
|
||||
// Opcional: Validar header
|
||||
std::string headerStr(reinterpret_cast<char *>(header), 6);
|
||||
if (headerStr != "GIF87a" && headerStr != "GIF89a")
|
||||
{
|
||||
throw std::runtime_error("Formato de archivo GIF inválido.");
|
||||
}
|
||||
|
||||
// Leer el Screen Descriptor (7 bytes, empaquetado sin padding)
|
||||
ScreenDescriptor screen_descriptor;
|
||||
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
||||
|
||||
// Asigna ancho y alto
|
||||
w = screen_descriptor.width;
|
||||
h = screen_descriptor.height;
|
||||
|
||||
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
||||
std::vector<RGB> global_color_table;
|
||||
if (screen_descriptor.fields & 0x80)
|
||||
{
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(global_color_table_size);
|
||||
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
|
||||
buffer += 3 * global_color_table_size;
|
||||
}
|
||||
|
||||
// Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B
|
||||
uint8_t block_type = *buffer++;
|
||||
while (block_type != TRAILER)
|
||||
{
|
||||
if (block_type == EXTENSION_INTRODUCER) // 0x21
|
||||
{
|
||||
// Se lee la etiqueta de extensión, la cual indica el tipo de extensión.
|
||||
uint8_t extension_label = *buffer++;
|
||||
switch (extension_label)
|
||||
{
|
||||
case GRAPHIC_CONTROL: // 0xF9
|
||||
{
|
||||
// Procesar Graphic Control Extension:
|
||||
uint8_t blockSize = *buffer++; // Normalmente, blockSize == 4
|
||||
buffer += blockSize; // Saltamos los 4 bytes del bloque fijo
|
||||
// Saltar los sub-bloques
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0)
|
||||
{
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APPLICATION_EXTENSION: // 0xFF
|
||||
case COMMENT_EXTENSION: // 0xFE
|
||||
case PLAINTEXT_EXTENSION: // 0x01
|
||||
{
|
||||
// Para estas extensiones, saltamos el bloque fijo y los sub-bloques.
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0)
|
||||
{
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Si la etiqueta de extensión es desconocida, saltarla también:
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0)
|
||||
{
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (block_type == IMAGE_DESCRIPTOR)
|
||||
{
|
||||
// Procesar el Image Descriptor y retornar los datos de imagen
|
||||
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << std::endl;
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
block_type = *buffer++;
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h)
|
||||
{
|
||||
return processGifStream(buffer, w, h);
|
||||
}
|
||||
|
||||
} // namespace GIF
|
||||
102
source2/Core/Rendering/gif.h
Normal file
102
source2/Core/Rendering/gif.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // Para uint8_t, uint16_t, uint32_t
|
||||
#include <vector> // Para vector
|
||||
|
||||
namespace GIF
|
||||
{
|
||||
|
||||
// Constantes definidas con constexpr, en lugar de macros
|
||||
constexpr uint8_t EXTENSION_INTRODUCER = 0x21;
|
||||
constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C;
|
||||
constexpr uint8_t TRAILER = 0x3B;
|
||||
constexpr uint8_t GRAPHIC_CONTROL = 0xF9;
|
||||
constexpr uint8_t APPLICATION_EXTENSION = 0xFF;
|
||||
constexpr uint8_t COMMENT_EXTENSION = 0xFE;
|
||||
constexpr uint8_t PLAINTEXT_EXTENSION = 0x01;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ScreenDescriptor
|
||||
{
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t fields;
|
||||
uint8_t background_color_index;
|
||||
uint8_t pixel_aspect_ratio;
|
||||
};
|
||||
|
||||
struct RGB
|
||||
{
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
struct ImageDescriptor
|
||||
{
|
||||
uint16_t image_left_position;
|
||||
uint16_t image_top_position;
|
||||
uint16_t image_width;
|
||||
uint16_t image_height;
|
||||
uint8_t fields;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct DictionaryEntry
|
||||
{
|
||||
uint8_t byte;
|
||||
int prev;
|
||||
int len;
|
||||
};
|
||||
|
||||
struct Extension
|
||||
{
|
||||
uint8_t extension_code;
|
||||
uint8_t block_size;
|
||||
};
|
||||
|
||||
struct GraphicControlExtension
|
||||
{
|
||||
uint8_t fields;
|
||||
uint16_t delay_time;
|
||||
uint8_t transparent_color_index;
|
||||
};
|
||||
|
||||
struct ApplicationExtension
|
||||
{
|
||||
uint8_t application_id[8];
|
||||
uint8_t version[3];
|
||||
};
|
||||
|
||||
struct PlaintextExtension
|
||||
{
|
||||
uint16_t left, top, width, height;
|
||||
uint8_t cell_width, cell_height;
|
||||
uint8_t foreground_color, background_color;
|
||||
};
|
||||
|
||||
class Gif
|
||||
{
|
||||
public:
|
||||
// Descompone (uncompress) el bloque comprimido usando LZW.
|
||||
// Este método puede lanzar std::runtime_error en caso de error.
|
||||
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out);
|
||||
|
||||
// Carga la paleta (global color table) a partir de un buffer,
|
||||
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
|
||||
std::vector<uint32_t> loadPalette(const uint8_t *buffer);
|
||||
|
||||
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
|
||||
// asigna el ancho y alto mediante referencias.
|
||||
std::vector<uint8_t> loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
||||
|
||||
private:
|
||||
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
|
||||
std::vector<uint8_t> readSubBlocks(const uint8_t *&buffer);
|
||||
|
||||
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
||||
std::vector<uint8_t> processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits);
|
||||
|
||||
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
||||
std::vector<uint8_t> processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
||||
};
|
||||
|
||||
} // namespace GIF
|
||||
585
source2/Core/Rendering/screen.cpp
Normal file
585
source2/Core/Rendering/screen.cpp
Normal file
@@ -0,0 +1,585 @@
|
||||
#include "screen.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <ctype.h> // Para toupper
|
||||
|
||||
#include <algorithm> // Para max, min, transform
|
||||
#include <fstream> // Para basic_ostream, operator<<, endl, basic_...
|
||||
#include <iostream> // Para cerr
|
||||
#include <iterator> // Para istreambuf_iterator, operator==
|
||||
#include <string> // Para char_traits, string, operator+, operator==
|
||||
|
||||
#include "asset.h" // Para Asset, AssetType
|
||||
#include "mouse.h" // Para updateCursorVisibility
|
||||
#include "options.h" // Para Options, options, OptionsVideo, Border
|
||||
#include "rendering/opengl/opengl_shader.h" // Para OpenGLShader
|
||||
#include "resource.h" // Para Resource
|
||||
#include "surface.h" // Para Surface, readPalFile
|
||||
#include "text.h" // Para Text
|
||||
#include "ui/notifier.h" // Para Notifier
|
||||
|
||||
// [SINGLETON]
|
||||
Screen* Screen::screen_ = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
void Screen::init() {
|
||||
Screen::screen_ = new Screen();
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
void Screen::destroy() {
|
||||
delete Screen::screen_;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
Screen* Screen::get() {
|
||||
return Screen::screen_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Screen::Screen()
|
||||
: palettes_(Asset::get()->getListByType(AssetType::PALETTE)) {
|
||||
// Arranca SDL VIDEO, crea la ventana y el renderizador
|
||||
initSDLVideo();
|
||||
|
||||
// Ajusta los tamaños
|
||||
game_surface_dstrect_ = {options.video.border.width, options.video.border.height, options.game.width, options.game.height};
|
||||
//adjustWindowSize();
|
||||
current_palette_ = findPalette(options.video.palette);
|
||||
|
||||
// Define el color del borde para el modo de pantalla completa
|
||||
border_color_ = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
|
||||
// Crea la textura donde se dibujan los graficos del juego
|
||||
game_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, options.game.width, options.game.height);
|
||||
if (!game_texture_) {
|
||||
// Registrar el error si está habilitado
|
||||
if (options.console) {
|
||||
std::cerr << "Error: game_texture_ could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
}
|
||||
SDL_SetTextureScaleMode(game_texture_, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
// Crea la textura donde se dibuja el borde que rodea el area de juego
|
||||
border_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, options.game.width + options.video.border.width * 2, options.game.height + options.video.border.height * 2);
|
||||
if (!border_texture_) {
|
||||
// Registrar el error si está habilitado
|
||||
if (options.console) {
|
||||
std::cerr << "Error: border_texture_ could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
}
|
||||
SDL_SetTextureScaleMode(border_texture_, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
// Crea la surface donde se dibujan los graficos del juego
|
||||
game_surface_ = std::make_shared<Surface>(options.game.width, options.game.height);
|
||||
game_surface_->setPalette(readPalFile(palettes_.at(current_palette_)));
|
||||
game_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Crea la surface para el borde de colores
|
||||
border_surface_ = std::make_shared<Surface>(options.game.width + options.video.border.width * 2, options.game.height + options.video.border.height * 2);
|
||||
border_surface_->setPalette(readPalFile(palettes_.at(current_palette_)));
|
||||
border_surface_->clear(border_color_);
|
||||
|
||||
// Establece la surface que actuará como renderer para recibir las llamadas a render()
|
||||
renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
|
||||
|
||||
// Extrae el nombre de las paletas desde su ruta
|
||||
processPaletteList();
|
||||
|
||||
// Renderizar una vez la textura vacía para que tenga contenido válido
|
||||
// antes de inicializar los shaders (evita pantalla negra)
|
||||
SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr);
|
||||
SDL_RenderTexture(renderer_, border_texture_, nullptr, nullptr);
|
||||
|
||||
// Ahora sí inicializar los shaders
|
||||
initShaders();
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Screen::~Screen() {
|
||||
SDL_DestroyTexture(game_texture_);
|
||||
SDL_DestroyTexture(border_texture_);
|
||||
}
|
||||
|
||||
// Limpia el renderer
|
||||
void Screen::clearRenderer(Color color) {
|
||||
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF);
|
||||
SDL_RenderClear(renderer_);
|
||||
}
|
||||
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
void Screen::start() { setRendererSurface(nullptr); }
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
void Screen::render() {
|
||||
fps_.increment();
|
||||
|
||||
// Renderiza todos los overlays
|
||||
renderOverlays();
|
||||
|
||||
// Copia la surface a la textura
|
||||
surfaceToTexture();
|
||||
|
||||
// Copia la textura al renderizador
|
||||
textureToRenderer();
|
||||
}
|
||||
|
||||
// Establece el modo de video
|
||||
void Screen::setVideoMode(bool mode) {
|
||||
// Actualiza las opciones
|
||||
options.video.fullscreen = mode;
|
||||
|
||||
// Configura el modo de pantalla y ajusta la ventana
|
||||
SDL_SetWindowFullscreen(window_, options.video.fullscreen);
|
||||
adjustWindowSize();
|
||||
adjustRenderLogicalSize();
|
||||
}
|
||||
|
||||
// Camibia entre pantalla completa y ventana
|
||||
void Screen::toggleVideoMode() {
|
||||
options.video.fullscreen = !options.video.fullscreen;
|
||||
setVideoMode(options.video.fullscreen);
|
||||
}
|
||||
|
||||
// Reduce el tamaño de la ventana
|
||||
bool Screen::decWindowZoom() {
|
||||
if (options.video.fullscreen == 0) {
|
||||
const int PREVIOUS_ZOOM = options.window.zoom;
|
||||
--options.window.zoom;
|
||||
options.window.zoom = std::max(options.window.zoom, 1);
|
||||
|
||||
if (options.window.zoom != PREVIOUS_ZOOM) {
|
||||
setVideoMode(options.video.fullscreen);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Aumenta el tamaño de la ventana
|
||||
bool Screen::incWindowZoom() {
|
||||
if (options.video.fullscreen == 0) {
|
||||
const int PREVIOUS_ZOOM = options.window.zoom;
|
||||
++options.window.zoom;
|
||||
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
|
||||
|
||||
if (options.window.zoom != PREVIOUS_ZOOM) {
|
||||
setVideoMode(options.video.fullscreen);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cambia el color del borde
|
||||
void Screen::setBorderColor(Uint8 color) {
|
||||
border_color_ = color;
|
||||
border_surface_->clear(border_color_);
|
||||
}
|
||||
|
||||
// Cambia entre borde visible y no visible
|
||||
void Screen::toggleBorder() {
|
||||
options.video.border.enabled = !options.video.border.enabled;
|
||||
setVideoMode(options.video.fullscreen);
|
||||
initShaders();
|
||||
}
|
||||
|
||||
// Dibuja las notificaciones
|
||||
void Screen::renderNotifications() {
|
||||
if (notifications_enabled_) {
|
||||
Notifier::get()->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia el estado de los shaders
|
||||
void Screen::toggleShaders() {
|
||||
options.video.shaders = !options.video.shaders;
|
||||
initShaders();
|
||||
}
|
||||
|
||||
// Actualiza la lógica de la clase
|
||||
void Screen::update() {
|
||||
fps_.calculate(SDL_GetTicks());
|
||||
Notifier::get()->update();
|
||||
Mouse::updateCursorVisibility();
|
||||
}
|
||||
|
||||
// Calcula el tamaño de la ventana
|
||||
void Screen::adjustWindowSize() {
|
||||
window_width_ = options.game.width + (options.video.border.enabled ? options.video.border.width * 2 : 0);
|
||||
window_height_ = options.game.height + (options.video.border.enabled ? options.video.border.height * 2 : 0);
|
||||
|
||||
// Establece el nuevo tamaño
|
||||
if (options.video.fullscreen == 0) {
|
||||
int old_width, old_height;
|
||||
SDL_GetWindowSize(window_, &old_width, &old_height);
|
||||
|
||||
int old_pos_x, old_pos_y;
|
||||
SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y);
|
||||
|
||||
const int NEW_POS_X = old_pos_x + (old_width - (window_width_ * options.window.zoom)) / 2;
|
||||
const int NEW_POS_Y = old_pos_y + (old_height - (window_height_ * options.window.zoom)) / 2;
|
||||
|
||||
SDL_SetWindowSize(window_, window_width_ * options.window.zoom, window_height_ * options.window.zoom);
|
||||
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS), std::max(NEW_POS_Y, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Ajusta el tamaño lógico del renderizador
|
||||
void Screen::adjustRenderLogicalSize() {
|
||||
SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
}
|
||||
|
||||
// Establece el renderizador para las surfaces
|
||||
void Screen::setRendererSurface(std::shared_ptr<Surface> surface) {
|
||||
(surface) ? renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(surface) : renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
|
||||
}
|
||||
|
||||
// Cambia la paleta
|
||||
void Screen::nextPalette() {
|
||||
++current_palette_;
|
||||
if (current_palette_ == static_cast<int>(palettes_.size())) {
|
||||
current_palette_ = 0;
|
||||
}
|
||||
|
||||
setPalete();
|
||||
}
|
||||
|
||||
// Cambia la paleta
|
||||
void Screen::previousPalette() {
|
||||
if (current_palette_ > 0) {
|
||||
--current_palette_;
|
||||
} else {
|
||||
current_palette_ = static_cast<Uint8>(palettes_.size() - 1);
|
||||
}
|
||||
|
||||
setPalete();
|
||||
}
|
||||
|
||||
// Establece la paleta
|
||||
void Screen::setPalete() {
|
||||
game_surface_->loadPalette(Resource::get()->getPalette(palettes_.at(current_palette_)));
|
||||
border_surface_->loadPalette(Resource::get()->getPalette(palettes_.at(current_palette_)));
|
||||
|
||||
options.video.palette = palettes_.at(current_palette_);
|
||||
|
||||
// Eliminar ".gif"
|
||||
size_t pos = options.video.palette.find(".pal");
|
||||
if (pos != std::string::npos) {
|
||||
options.video.palette.erase(pos, 4);
|
||||
}
|
||||
|
||||
// Convertir a mayúsculas
|
||||
std::transform(options.video.palette.begin(), options.video.palette.end(), options.video.palette.begin(), ::toupper);
|
||||
}
|
||||
|
||||
// Extrae los nombres de las paletas
|
||||
void Screen::processPaletteList() {
|
||||
for (auto& palette : palettes_) {
|
||||
palette = getFileName(palette);
|
||||
}
|
||||
}
|
||||
|
||||
// Copia la surface a la textura
|
||||
void Screen::surfaceToTexture() {
|
||||
if (options.video.border.enabled) {
|
||||
border_surface_->copyToTexture(renderer_, border_texture_);
|
||||
game_surface_->copyToTexture(renderer_, border_texture_, nullptr, &game_surface_dstrect_);
|
||||
} else {
|
||||
game_surface_->copyToTexture(renderer_, game_texture_);
|
||||
}
|
||||
}
|
||||
|
||||
// Copia la textura al renderizador
|
||||
void Screen::textureToRenderer() {
|
||||
SDL_Texture* texture_to_render = options.video.border.enabled ? border_texture_ : game_texture_;
|
||||
|
||||
if (options.video.shaders && shader_backend_) {
|
||||
shader_backend_->render();
|
||||
} else {
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_RenderTexture(renderer_, texture_to_render, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza todos los overlays
|
||||
void Screen::renderOverlays() {
|
||||
renderNotifications();
|
||||
renderInfo();
|
||||
}
|
||||
|
||||
// Localiza la paleta dentro del vector de paletas
|
||||
size_t Screen::findPalette(const std::string& name) {
|
||||
std::string upper_name = toUpper(name + ".pal");
|
||||
|
||||
for (size_t i = 0; i < palettes_.size(); ++i) {
|
||||
if (toUpper(getFileName(palettes_[i])) == upper_name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return static_cast<size_t>(0);
|
||||
}
|
||||
|
||||
// Muestra información por pantalla
|
||||
void Screen::renderInfo() {
|
||||
if (show_debug_info_ && Resource::get()) {
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
auto color = static_cast<Uint8>(PaletteColor::YELLOW);
|
||||
|
||||
// FPS
|
||||
const std::string FPS_TEXT = std::to_string(fps_.lastValue) + " FPS";
|
||||
text->writeColored(options.game.width - text->lenght(FPS_TEXT), 0, FPS_TEXT, color);
|
||||
|
||||
// Resolution
|
||||
text->writeColored(0, 0, info_resolution_, color);
|
||||
}
|
||||
}
|
||||
|
||||
// Limpia la game_surface_
|
||||
void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); }
|
||||
|
||||
// Establece el tamaño del borde
|
||||
void Screen::setBorderWidth(int width) { options.video.border.width = width; }
|
||||
|
||||
// Establece el tamaño del borde
|
||||
void Screen::setBorderHeight(int height) { options.video.border.height = height; }
|
||||
|
||||
// Establece si se ha de ver el borde en el modo ventana
|
||||
void Screen::setBorderEnabled(bool value) { options.video.border.enabled = value; }
|
||||
|
||||
// Muestra la ventana
|
||||
void Screen::show() { SDL_ShowWindow(window_); }
|
||||
|
||||
// Oculta la ventana
|
||||
void Screen::hide() { SDL_HideWindow(window_); }
|
||||
|
||||
// Establece la visibilidad de las notificaciones
|
||||
void Screen::setNotificationsEnabled(bool value) { notifications_enabled_ = value; }
|
||||
|
||||
// Activa / desactiva la información de debug
|
||||
void Screen::toggleDebugInfo() { show_debug_info_ = !show_debug_info_; }
|
||||
|
||||
// Alterna entre activar y desactivar el escalado entero
|
||||
void Screen::toggleIntegerScale() {
|
||||
options.video.integer_scale = !options.video.integer_scale;
|
||||
SDL_SetRenderLogicalPresentation(renderer_, options.game.width, options.game.height, options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
}
|
||||
|
||||
// Getters
|
||||
SDL_Renderer* Screen::getRenderer() { return renderer_; }
|
||||
std::shared_ptr<Surface> Screen::getRendererSurface() { return (*renderer_surface_); }
|
||||
std::shared_ptr<Surface> Screen::getBorderSurface() { return border_surface_; }
|
||||
|
||||
std::vector<uint8_t> loadData(const std::string& filepath) {
|
||||
// Fallback a filesystem
|
||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::streamsize fileSize = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> data(fileSize);
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), fileSize)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Carga el contenido de los archivos GLSL
|
||||
void Screen::loadShaders() {
|
||||
if (vertex_shader_source_.empty()) {
|
||||
// Detectar si necesitamos OpenGL ES (Raspberry Pi)
|
||||
// Intentar cargar versión ES primero si existe
|
||||
std::string VERTEX_FILE = "crtpi_vertex_es.glsl";
|
||||
auto data = loadData(Asset::get()->get(VERTEX_FILE));
|
||||
|
||||
if (data.empty()) {
|
||||
// Si no existe versión ES, usar versión Desktop
|
||||
VERTEX_FILE = "crtpi_vertex.glsl";
|
||||
data = loadData(Asset::get()->get(VERTEX_FILE));
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Usando shaders OpenGL Desktop 3.3");
|
||||
} else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Usando shaders OpenGL ES 3.0 (Raspberry Pi)");
|
||||
}
|
||||
|
||||
if (!data.empty()) {
|
||||
vertex_shader_source_ = std::string(data.begin(), data.end());
|
||||
}
|
||||
}
|
||||
if (fragment_shader_source_.empty()) {
|
||||
// Intentar cargar versión ES primero si existe
|
||||
std::string FRAGMENT_FILE = "crtpi_fragment_es.glsl";
|
||||
auto data = loadData(Asset::get()->get(FRAGMENT_FILE));
|
||||
|
||||
if (data.empty()) {
|
||||
// Si no existe versión ES, usar versión Desktop
|
||||
FRAGMENT_FILE = "crtpi_fragment.glsl";
|
||||
data = loadData(Asset::get()->get(FRAGMENT_FILE));
|
||||
}
|
||||
|
||||
if (!data.empty()) {
|
||||
fragment_shader_source_ = std::string(data.begin(), data.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa los shaders
|
||||
void Screen::initShaders() {
|
||||
#ifndef __APPLE__
|
||||
if (options.video.shaders) {
|
||||
loadShaders();
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::OpenGLShader>();
|
||||
}
|
||||
shader_backend_->init(window_, options.video.border.enabled ? border_texture_ : game_texture_, vertex_shader_source_, fragment_shader_source_);
|
||||
//shader_backend_->init(window_, shaders_texture_, vertex_shader_source_, fragment_shader_source_);
|
||||
}
|
||||
#else
|
||||
// En macOS, OpenGL está deprecated y rinde mal
|
||||
// TODO: Implementar backend de Metal para shaders en macOS
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Shaders no disponibles en macOS (OpenGL deprecated). Usa Metal backend.");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Obtiene información sobre la pantalla
|
||||
void Screen::getDisplayInfo() {
|
||||
int i;
|
||||
int num_displays = 0;
|
||||
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
|
||||
if (displays != nullptr) {
|
||||
for (i = 0; i < num_displays; ++i) {
|
||||
SDL_DisplayID instance_id = displays[i];
|
||||
const char* name = SDL_GetDisplayName(instance_id);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Display %" SDL_PRIu32 ": %s", instance_id, (name != nullptr) ? name : "Unknown");
|
||||
}
|
||||
|
||||
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
|
||||
|
||||
// Guarda información del monitor en display_monitor_
|
||||
const char* first_display_name = SDL_GetDisplayName(displays[0]);
|
||||
display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown";
|
||||
display_monitor_.width = static_cast<int>(dm->w);
|
||||
display_monitor_.height = static_cast<int>(dm->h);
|
||||
display_monitor_.refresh_rate = static_cast<int>(dm->refresh_rate);
|
||||
|
||||
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
|
||||
options.window.max_zoom = std::min(dm->w / options.game.width, dm->h / options.game.height);
|
||||
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
|
||||
|
||||
// Muestra información sobre el tamaño de la pantalla y de la ventana de juego
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Current display mode: %dx%d @ %dHz", static_cast<int>(dm->w), static_cast<int>(dm->h), static_cast<int>(dm->refresh_rate));
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Window resolution: %dx%d x%d", static_cast<int>(options.game.width), static_cast<int>(options.game.height), options.window.zoom);
|
||||
|
||||
options.video.info = std::to_string(static_cast<int>(dm->w)) + "x" +
|
||||
std::to_string(static_cast<int>(dm->h)) + " @ " +
|
||||
std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
|
||||
|
||||
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
|
||||
const int MAX_ZOOM = std::min(dm->w / options.game.width, (dm->h - WINDOWS_DECORATIONS) / options.game.height);
|
||||
|
||||
// Normaliza los valores de zoom
|
||||
options.window.zoom = std::min(options.window.zoom, MAX_ZOOM);
|
||||
|
||||
SDL_free(displays);
|
||||
}
|
||||
}
|
||||
|
||||
// Arranca SDL VIDEO y crea la ventana
|
||||
auto Screen::initSDLVideo() -> bool {
|
||||
// Inicializar SDL
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"FATAL: Failed to initialize SDL_VIDEO! SDL Error: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtener información de la pantalla
|
||||
getDisplayInfo();
|
||||
|
||||
// Configurar hint para renderizado
|
||||
#ifdef __APPLE__
|
||||
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Failed to set Metal hint!");
|
||||
}
|
||||
#else
|
||||
// Configurar hint de render driver
|
||||
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Failed to set OpenGL hint!");
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows: Pedir explícitamente OpenGL 3.3 Core Profile
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Solicitando OpenGL 3.3 Core Profile");
|
||||
#else
|
||||
// Linux: Dejar que SDL elija (Desktop 3.3 en PC, ES 3.0 en RPi automáticamente)
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Usando OpenGL por defecto del sistema");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Crear ventana
|
||||
const auto WINDOW_WIDTH = options.video.border.enabled ? options.game.width + options.video.border.width * 2 : options.game.width;
|
||||
const auto WINDOW_HEIGHT = options.video.border.enabled ? options.game.height + options.video.border.height * 2 : options.game.height;
|
||||
#ifdef __APPLE__
|
||||
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
|
||||
#else
|
||||
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL;
|
||||
#endif
|
||||
if (options.video.fullscreen) {
|
||||
window_flags |= SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
window_ = SDL_CreateWindow(options.window.caption.c_str(), WINDOW_WIDTH * options.window.zoom, WINDOW_HEIGHT * options.window.zoom, window_flags);
|
||||
|
||||
if (window_ == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"FATAL: Failed to create window! SDL Error: %s",
|
||||
SDL_GetError());
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Crear renderer
|
||||
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
||||
if (renderer_ == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"FATAL: Failed to create renderer! SDL Error: %s",
|
||||
SDL_GetError());
|
||||
SDL_DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configurar renderer
|
||||
const int EXTRA_WIDTH = options.video.border.enabled ? options.video.border.width * 2 : 0;
|
||||
const int EXTRA_HEIGHT = options.video.border.enabled ? options.video.border.height * 2 : 0;
|
||||
SDL_SetRenderLogicalPresentation(
|
||||
renderer_,
|
||||
options.game.width + EXTRA_WIDTH,
|
||||
options.game.height + EXTRA_HEIGHT,
|
||||
options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderVSync(renderer_, options.video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Video system initialized successfully");
|
||||
return true;
|
||||
}
|
||||
215
source2/Core/Rendering/screen.h
Normal file
215
source2/Core/Rendering/screen.h
Normal file
@@ -0,0 +1,215 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stddef.h> // Para size_t
|
||||
|
||||
#include <memory> // Para shared_ptr, __shared_ptr_access
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "utils.h" // Para Color
|
||||
struct Surface;
|
||||
namespace Rendering {
|
||||
class ShaderBackend;
|
||||
}
|
||||
|
||||
// Tipos de filtro
|
||||
enum class ScreenFilter : Uint32 {
|
||||
NEAREST = 0,
|
||||
LINEAR = 1,
|
||||
};
|
||||
|
||||
class Screen {
|
||||
private:
|
||||
// Constantes
|
||||
static constexpr int WINDOWS_DECORATIONS = 35; // Decoraciones de la ventana
|
||||
|
||||
struct DisplayMonitor {
|
||||
std::string name;
|
||||
int width;
|
||||
int height;
|
||||
int refresh_rate;
|
||||
};
|
||||
|
||||
struct FPS {
|
||||
Uint32 ticks; // Tiempo en milisegundos desde que se comenzó a contar.
|
||||
int frameCount; // Número acumulado de frames en el intervalo.
|
||||
int lastValue; // Número de frames calculado en el último segundo.
|
||||
|
||||
// Constructor para inicializar la estructura.
|
||||
FPS()
|
||||
: ticks(0),
|
||||
frameCount(0),
|
||||
lastValue(0) {}
|
||||
|
||||
// Incrementador que se llama en cada frame.
|
||||
void increment() {
|
||||
frameCount++;
|
||||
}
|
||||
|
||||
// Método para calcular y devolver el valor de FPS.
|
||||
int calculate(Uint32 currentTicks) {
|
||||
if (currentTicks - ticks >= 1000) // Si ha pasado un segundo o más.
|
||||
{
|
||||
lastValue = frameCount; // Actualizamos el valor del último FPS.
|
||||
frameCount = 0; // Reiniciamos el contador de frames.
|
||||
ticks = currentTicks; // Actualizamos el tiempo base.
|
||||
}
|
||||
return lastValue;
|
||||
}
|
||||
};
|
||||
|
||||
// [SINGLETON] Objeto privado
|
||||
static Screen* screen_;
|
||||
|
||||
// Objetos y punteros
|
||||
SDL_Window* window_; // Ventana de la aplicación
|
||||
SDL_Renderer* renderer_; // El renderizador de la ventana
|
||||
SDL_Texture* game_texture_; // Textura donde se dibuja el juego
|
||||
SDL_Texture* border_texture_; // Textura donde se dibuja el borde del juego
|
||||
std::shared_ptr<Surface> game_surface_; // Surface principal para manejar game_surface_data_
|
||||
std::shared_ptr<Surface> border_surface_; // Surface para pintar el el borde de la pantalla
|
||||
std::shared_ptr<std::shared_ptr<Surface>> renderer_surface_; // Puntero a la Surface que actua
|
||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (OpenGL/Metal/Vulkan)
|
||||
|
||||
// Variables
|
||||
int window_width_; // Ancho de la pantalla o ventana
|
||||
int window_height_; // Alto de la pantalla o ventana
|
||||
SDL_FRect game_surface_dstrect_; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
|
||||
Uint8 border_color_; // Color del borde añadido a la textura de juego para rellenar la pantalla
|
||||
std::vector<std::string> palettes_; // Listado de los ficheros de paletta disponibles
|
||||
Uint8 current_palette_ = 0; // Indice para el vector de paletas
|
||||
bool notifications_enabled_ = false; // indica si se muestran las notificaciones
|
||||
FPS fps_; // Variable para gestionar los frames por segundo
|
||||
std::string info_resolution_; // Texto con la informacion de la pantalla
|
||||
std::string vertex_shader_source_; // Almacena el vertex shader
|
||||
std::string fragment_shader_source_; // Almacena el fragment shader
|
||||
DisplayMonitor display_monitor_; // Informacion de la pantalla
|
||||
|
||||
#ifdef DEBUG
|
||||
bool show_debug_info_ = false; // Indica si ha de mostrar/ocultar la información de la pantalla
|
||||
#else
|
||||
bool show_debug_info_ = false; // Indica si ha de mostrar/ocultar la información de la pantalla
|
||||
#endif
|
||||
|
||||
// Dibuja las notificaciones
|
||||
void renderNotifications();
|
||||
|
||||
// Calcula el tamaño de la ventana
|
||||
void adjustWindowSize();
|
||||
|
||||
// Ajusta el tamaño lógico del renderizador
|
||||
void adjustRenderLogicalSize();
|
||||
|
||||
// Extrae los nombres de las paletas
|
||||
void processPaletteList();
|
||||
|
||||
// Copia la surface a la textura
|
||||
void surfaceToTexture();
|
||||
|
||||
// Copia la textura al renderizador
|
||||
void textureToRenderer();
|
||||
|
||||
// Renderiza todos los overlays
|
||||
void renderOverlays();
|
||||
|
||||
// Localiza la paleta dentro del vector de paletas
|
||||
size_t findPalette(const std::string& name);
|
||||
|
||||
void initShaders(); // Inicializa los shaders
|
||||
void loadShaders(); // Carga el contenido del archivo GLSL
|
||||
void renderInfo(); // Muestra información por pantalla
|
||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||
|
||||
// Constructor
|
||||
Screen();
|
||||
|
||||
// Destructor
|
||||
~Screen();
|
||||
|
||||
public:
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
static void init();
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
static void destroy();
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
static Screen* get();
|
||||
|
||||
// Limpia el renderer
|
||||
void clearRenderer(Color color = {0x00, 0x00, 0x00});
|
||||
|
||||
// Limpia la game_surface_
|
||||
void clearSurface(Uint8 index);
|
||||
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
void start();
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
void render();
|
||||
|
||||
// Actualiza la lógica de la clase
|
||||
void update();
|
||||
|
||||
// Establece el modo de video
|
||||
void setVideoMode(bool mode);
|
||||
|
||||
// Camibia entre pantalla completa y ventana
|
||||
void toggleVideoMode();
|
||||
|
||||
// Alterna entre activar y desactivar el escalado entero
|
||||
void toggleIntegerScale();
|
||||
|
||||
// Reduce el tamaño de la ventana
|
||||
bool decWindowZoom();
|
||||
|
||||
// Aumenta el tamaño de la ventana
|
||||
bool incWindowZoom();
|
||||
|
||||
// Cambia el color del borde
|
||||
void setBorderColor(Uint8 color);
|
||||
|
||||
// Establece el tamaño del borde
|
||||
void setBorderWidth(int width);
|
||||
|
||||
// Establece el tamaño del borde
|
||||
void setBorderHeight(int height);
|
||||
|
||||
// Establece si se ha de ver el borde en el modo ventana
|
||||
void setBorderEnabled(bool value);
|
||||
|
||||
// Cambia entre borde visible y no visible
|
||||
void toggleBorder();
|
||||
|
||||
// Cambia el estado de los shaders
|
||||
void toggleShaders();
|
||||
|
||||
// Muestra la ventana
|
||||
void show();
|
||||
|
||||
// Oculta la ventana
|
||||
void hide();
|
||||
|
||||
// Establece el renderizador para las surfaces
|
||||
void setRendererSurface(std::shared_ptr<Surface> surface = nullptr);
|
||||
|
||||
// Cambia la paleta
|
||||
void nextPalette();
|
||||
void previousPalette();
|
||||
|
||||
// Establece la paleta
|
||||
void setPalete();
|
||||
|
||||
// Establece la visibilidad de las notificaciones
|
||||
void setNotificationsEnabled(bool value);
|
||||
|
||||
// Activa o desactiva la información de debug
|
||||
void toggleDebugInfo();
|
||||
|
||||
// Getters
|
||||
SDL_Renderer* getRenderer();
|
||||
std::shared_ptr<Surface> getRendererSurface();
|
||||
std::shared_ptr<Surface> getBorderSurface();
|
||||
};
|
||||
564
source2/Core/Rendering/surface.cpp
Normal file
564
source2/Core/Rendering/surface.cpp
Normal file
@@ -0,0 +1,564 @@
|
||||
// IWYU pragma: no_include <bits/std_abs.h>
|
||||
#include "surface.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para min, max, copy_n, fill
|
||||
#include <cmath> // Para abs
|
||||
#include <cstdint> // Para uint32_t
|
||||
#include <cstring> // Para memcpy, size_t
|
||||
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ist...
|
||||
#include <iostream> // Para cerr
|
||||
#include <memory> // Para shared_ptr, __shared_ptr_access, default...
|
||||
#include <sstream> // Para basic_istringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "gif.h" // Para Gif
|
||||
#include "screen.h" // Para Screen
|
||||
|
||||
// Carga una paleta desde un archivo .gif
|
||||
Palette loadPalette(const std::string& file_path) {
|
||||
// Abrir el archivo en modo binario
|
||||
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Error opening file: " + file_path);
|
||||
}
|
||||
|
||||
// Obtener el tamaño del archivo y leerlo en un buffer
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<Uint8> buffer(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
throw std::runtime_error("Error reading file: " + file_path);
|
||||
}
|
||||
|
||||
// Cargar la paleta usando los datos del buffer
|
||||
GIF::Gif gif;
|
||||
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
|
||||
if (pal.empty()) {
|
||||
throw std::runtime_error("No palette found in GIF file: " + file_path);
|
||||
}
|
||||
|
||||
// Crear la paleta y copiar los datos desde 'pal'
|
||||
Palette palette = {}; // Inicializa la paleta con ceros
|
||||
std::copy_n(pal.begin(), std::min(pal.size(), palette.size()), palette.begin());
|
||||
|
||||
// Mensaje de depuración
|
||||
printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
// Carga una paleta desde un archivo .pal
|
||||
Palette readPalFile(const std::string& file_path) {
|
||||
Palette palette{};
|
||||
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
|
||||
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("No se pudo abrir el archivo .pal");
|
||||
}
|
||||
|
||||
std::string line;
|
||||
int line_number = 0;
|
||||
int color_index = 0;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
++line_number;
|
||||
|
||||
// Ignorar las tres primeras líneas del archivo
|
||||
if (line_number <= 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Procesar las líneas restantes con valores RGB
|
||||
std::istringstream ss(line);
|
||||
int r, g, b;
|
||||
if (ss >> r >> g >> b) {
|
||||
// Construir el color ARGB (A = 255 por defecto)
|
||||
Uint32 color = (255 << 24) | (r << 16) | (g << 8) | b;
|
||||
palette[color_index++] = color;
|
||||
|
||||
// Limitar a un máximo de 256 colores (opcional)
|
||||
if (color_index >= 256) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return palette;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Surface::Surface(int w, int h)
|
||||
: surface_data_(std::make_shared<SurfaceData>(w, h)),
|
||||
transparent_color_(static_cast<Uint8>(PaletteColor::TRANSPARENT)) { initializeSubPalette(sub_palette_); }
|
||||
|
||||
Surface::Surface(const std::string& file_path)
|
||||
: transparent_color_(static_cast<Uint8>(PaletteColor::TRANSPARENT)) {
|
||||
SurfaceData loadedData = loadSurface(file_path);
|
||||
surface_data_ = std::make_shared<SurfaceData>(std::move(loadedData));
|
||||
|
||||
initializeSubPalette(sub_palette_);
|
||||
}
|
||||
|
||||
// Carga una superficie desde un archivo
|
||||
SurfaceData Surface::loadSurface(const std::string& file_path) {
|
||||
// Abrir el archivo usando std::ifstream para manejo automático del recurso
|
||||
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Error opening file: " << file_path << std::endl;
|
||||
throw std::runtime_error("Error opening file");
|
||||
}
|
||||
|
||||
// Obtener el tamaño del archivo
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Leer el contenido del archivo en un buffer
|
||||
std::vector<Uint8> buffer(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
std::cerr << "Error reading file: " << file_path << std::endl;
|
||||
throw std::runtime_error("Error reading file");
|
||||
}
|
||||
|
||||
// Crear un objeto Gif y llamar a la función loadGif
|
||||
GIF::Gif gif;
|
||||
Uint16 w = 0, h = 0;
|
||||
std::vector<Uint8> rawPixels = gif.loadGif(buffer.data(), w, h);
|
||||
if (rawPixels.empty()) {
|
||||
std::cerr << "Error loading GIF from file: " << file_path << std::endl;
|
||||
throw std::runtime_error("Error loading GIF");
|
||||
}
|
||||
|
||||
// Si el constructor de Surface espera un std::shared_ptr<Uint8[]>,
|
||||
// reservamos un bloque dinámico y copiamos los datos del vector.
|
||||
size_t pixelCount = rawPixels.size();
|
||||
auto pixels = std::shared_ptr<Uint8[]>(new Uint8[pixelCount], std::default_delete<Uint8[]>());
|
||||
std::memcpy(pixels.get(), rawPixels.data(), pixelCount);
|
||||
|
||||
// Crear y devolver directamente el objeto SurfaceData
|
||||
printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
|
||||
return SurfaceData(w, h, pixels);
|
||||
}
|
||||
|
||||
// Carga una paleta desde un archivo
|
||||
void Surface::loadPalette(const std::string& file_path) {
|
||||
palette_ = ::loadPalette(file_path);
|
||||
}
|
||||
|
||||
// Carga una paleta desde otra paleta
|
||||
void Surface::loadPalette(Palette palette) {
|
||||
palette_ = palette;
|
||||
}
|
||||
|
||||
// Establece un color en la paleta
|
||||
void Surface::setColor(int index, Uint32 color) {
|
||||
palette_.at(index) = color;
|
||||
}
|
||||
|
||||
// Rellena la superficie con un color
|
||||
void Surface::clear(Uint8 color) {
|
||||
const size_t total_pixels = surface_data_->width * surface_data_->height;
|
||||
Uint8* data_ptr = surface_data_->data.get();
|
||||
std::fill(data_ptr, data_ptr + total_pixels, color);
|
||||
}
|
||||
|
||||
// Pone un pixel en la SurfaceData
|
||||
void Surface::putPixel(int x, int y, Uint8 color) {
|
||||
if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) {
|
||||
return; // Coordenadas fuera de rango
|
||||
}
|
||||
|
||||
const int index = x + y * surface_data_->width;
|
||||
surface_data_->data.get()[index] = color;
|
||||
}
|
||||
|
||||
// Obtiene el color de un pixel de la surface_data
|
||||
Uint8 Surface::getPixel(int x, int y) { return surface_data_->data.get()[x + y * static_cast<int>(surface_data_->width)]; }
|
||||
|
||||
// Dibuja un rectangulo relleno
|
||||
void Surface::fillRect(const SDL_FRect* rect, Uint8 color) {
|
||||
// Limitar los valores del rectángulo al tamaño de la superficie
|
||||
float x_start = std::max(0.0F, rect->x);
|
||||
float y_start = std::max(0.0F, rect->y);
|
||||
float x_end = std::min(rect->x + rect->w, surface_data_->width);
|
||||
float y_end = std::min(rect->y + rect->h, surface_data_->height);
|
||||
|
||||
// Recorrer cada píxel dentro del rectángulo directamente
|
||||
for (int y = y_start; y < y_end; ++y) {
|
||||
for (int x = x_start; x < x_end; ++x) {
|
||||
const int INDEX = x + y * surface_data_->width;
|
||||
surface_data_->data.get()[INDEX] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el borde de un rectangulo
|
||||
void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) {
|
||||
// Limitar los valores del rectángulo al tamaño de la superficie
|
||||
float x_start = std::max(0.0F, rect->x);
|
||||
float y_start = std::max(0.0F, rect->y);
|
||||
float x_end = std::min(rect->x + rect->w, surface_data_->width);
|
||||
float y_end = std::min(rect->y + rect->h, surface_data_->height);
|
||||
|
||||
// Dibujar bordes horizontales
|
||||
for (int x = x_start; x < x_end; ++x) {
|
||||
// Borde superior
|
||||
const int top_index = x + y_start * surface_data_->width;
|
||||
surface_data_->data.get()[top_index] = color;
|
||||
|
||||
// Borde inferior
|
||||
const int bottom_index = x + (y_end - 1) * surface_data_->width;
|
||||
surface_data_->data.get()[bottom_index] = color;
|
||||
}
|
||||
|
||||
// Dibujar bordes verticales
|
||||
for (int y = y_start; y < y_end; ++y) {
|
||||
// Borde izquierdo
|
||||
const int LEFT_INDEX = x_start + y * surface_data_->width;
|
||||
surface_data_->data.get()[LEFT_INDEX] = color;
|
||||
|
||||
// Borde derecho
|
||||
const int RIGHT_INDEX = (x_end - 1) + y * surface_data_->width;
|
||||
surface_data_->data.get()[RIGHT_INDEX] = color;
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja una linea
|
||||
void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) {
|
||||
// Calcula las diferencias
|
||||
float dx = std::abs(x2 - x1);
|
||||
float dy = std::abs(y2 - y1);
|
||||
|
||||
// Determina la dirección del incremento
|
||||
float sx = (x1 < x2) ? 1 : -1;
|
||||
float sy = (y1 < y2) ? 1 : -1;
|
||||
|
||||
float err = dx - dy;
|
||||
|
||||
while (true) {
|
||||
// Asegúrate de no dibujar fuera de los límites de la superficie
|
||||
if (x1 >= 0 && x1 < surface_data_->width && y1 >= 0 && y1 < surface_data_->height) {
|
||||
surface_data_->data.get()[static_cast<size_t>(x1 + y1 * surface_data_->width)] = color;
|
||||
}
|
||||
|
||||
// Si alcanzamos el punto final, salimos
|
||||
if (x1 == x2 && y1 == y2)
|
||||
break;
|
||||
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
|
||||
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango en origen
|
||||
w = std::min(w, surface_data_->width - sx);
|
||||
h = std::min(h, surface_data_->height - sy);
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango en destino
|
||||
w = std::min(w, surface_data->width - dx);
|
||||
h = std::min(h, surface_data->height - dy);
|
||||
|
||||
for (int iy = 0; iy < h; ++iy) {
|
||||
for (int ix = 0; ix < w; ++ix) {
|
||||
// Verificar que las coordenadas de destino están dentro de los límites
|
||||
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) {
|
||||
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) {
|
||||
int src_x = sx + ix;
|
||||
int src_y = sy + iy;
|
||||
|
||||
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
|
||||
if (color != transparent_color_) {
|
||||
surface_data->data.get()[static_cast<size_t>(dest_x + dest_y * surface_data->width)] = sub_palette_[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Surface::render(int x, int y, SDL_FRect* srcRect, SDL_FlipMode flip) {
|
||||
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
|
||||
|
||||
// Determina la región de origen (clip) a renderizar
|
||||
float sx = (srcRect) ? srcRect->x : 0;
|
||||
float sy = (srcRect) ? srcRect->y : 0;
|
||||
float w = (srcRect) ? srcRect->w : surface_data_->width;
|
||||
float h = (srcRect) ? srcRect->h : surface_data_->height;
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango en origen
|
||||
w = std::min(w, surface_data_->width - sx);
|
||||
h = std::min(h, surface_data_->height - sy);
|
||||
w = std::min(w, surface_data_dest->width - x);
|
||||
h = std::min(h, surface_data_dest->height - y);
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango en destino
|
||||
w = std::min(w, surface_data_dest->width - x);
|
||||
h = std::min(h, surface_data_dest->height - y);
|
||||
|
||||
// Renderiza píxel por píxel aplicando el flip si es necesario
|
||||
for (int iy = 0; iy < h; ++iy) {
|
||||
for (int ix = 0; ix < w; ++ix) {
|
||||
// Coordenadas de origen
|
||||
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
|
||||
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
|
||||
|
||||
// Coordenadas de destino
|
||||
int dest_x = x + ix;
|
||||
int dest_y = y + iy;
|
||||
|
||||
// Verificar que las coordenadas de destino están dentro de los límites
|
||||
if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) {
|
||||
// Copia el píxel si no es transparente
|
||||
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
|
||||
if (color != transparent_color_) {
|
||||
surface_data_dest->data[dest_x + dest_y * surface_data_dest->width] = sub_palette_[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copia una región de la superficie de origen a la de destino
|
||||
void Surface::render(SDL_FRect* srcRect, SDL_FRect* dstRect, SDL_FlipMode flip) {
|
||||
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
|
||||
|
||||
// Si srcRect es nullptr, tomar toda la superficie fuente
|
||||
float sx = (srcRect) ? srcRect->x : 0;
|
||||
float sy = (srcRect) ? srcRect->y : 0;
|
||||
float sw = (srcRect) ? srcRect->w : surface_data_->width;
|
||||
float sh = (srcRect) ? srcRect->h : surface_data_->height;
|
||||
|
||||
// Si dstRect es nullptr, asignar las mismas dimensiones que srcRect
|
||||
float dx = (dstRect) ? dstRect->x : 0;
|
||||
float dy = (dstRect) ? dstRect->y : 0;
|
||||
float dw = (dstRect) ? dstRect->w : sw;
|
||||
float dh = (dstRect) ? dstRect->h : sh;
|
||||
|
||||
// Asegurarse de que srcRect y dstRect tienen las mismas dimensiones
|
||||
if (sw != dw || sh != dh) {
|
||||
dw = sw; // Respetar las dimensiones de srcRect
|
||||
dh = sh;
|
||||
}
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango en src y dst
|
||||
sw = std::min(sw, surface_data_->width - sx);
|
||||
sh = std::min(sh, surface_data_->height - sy);
|
||||
dw = std::min(dw, surface_data->width - dx);
|
||||
dh = std::min(dh, surface_data->height - dy);
|
||||
|
||||
int final_width = std::min(sw, dw);
|
||||
int final_height = std::min(sh, dh);
|
||||
|
||||
// Renderiza píxel por píxel aplicando el flip si es necesario
|
||||
for (int iy = 0; iy < final_height; ++iy) {
|
||||
for (int ix = 0; ix < final_width; ++ix) {
|
||||
// Coordenadas de origen
|
||||
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + final_width - 1 - ix) : (sx + ix);
|
||||
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + final_height - 1 - iy) : (sy + iy);
|
||||
|
||||
// Coordenadas de destino
|
||||
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) {
|
||||
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) {
|
||||
// Copiar el píxel si no es transparente
|
||||
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
|
||||
if (color != transparent_color_) {
|
||||
surface_data->data[dest_x + dest_y * surface_data->width] = sub_palette_[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
|
||||
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* srcRect, SDL_FlipMode flip) {
|
||||
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
|
||||
|
||||
// Determina la región de origen (clip) a renderizar
|
||||
float sx = (srcRect) ? srcRect->x : 0;
|
||||
float sy = (srcRect) ? srcRect->y : 0;
|
||||
float w = (srcRect) ? srcRect->w : surface_data_->width;
|
||||
float h = (srcRect) ? srcRect->h : surface_data_->height;
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango
|
||||
w = std::min(w, surface_data_->width - sx);
|
||||
h = std::min(h, surface_data_->height - sy);
|
||||
|
||||
// Renderiza píxel por píxel aplicando el flip si es necesario
|
||||
for (int iy = 0; iy < h; ++iy) {
|
||||
for (int ix = 0; ix < w; ++ix) {
|
||||
// Coordenadas de origen
|
||||
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
|
||||
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
|
||||
|
||||
// Coordenadas de destino
|
||||
int dest_x = x + ix;
|
||||
int dest_y = y + iy;
|
||||
|
||||
// Verifica que las coordenadas de destino estén dentro de los límites
|
||||
if (dest_x < 0 || dest_y < 0 || dest_x >= surface_data->width || dest_y >= surface_data->height) {
|
||||
continue; // Saltar píxeles fuera del rango del destino
|
||||
}
|
||||
|
||||
// Copia el píxel si no es transparente
|
||||
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
|
||||
if (color != transparent_color_) {
|
||||
surface_data->data[dest_x + dest_y * surface_data->width] =
|
||||
(color == source_color) ? target_color : color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vuelca la superficie a una textura
|
||||
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
|
||||
if (!renderer || !texture || !surface_data_) {
|
||||
throw std::runtime_error("Renderer or texture is null.");
|
||||
}
|
||||
|
||||
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data.get()) {
|
||||
throw std::runtime_error("Invalid surface dimensions or data.");
|
||||
}
|
||||
|
||||
Uint32* pixels = nullptr;
|
||||
int pitch = 0;
|
||||
|
||||
// Bloquea la textura para modificar los píxeles directamente
|
||||
if (!SDL_LockTexture(texture, nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
|
||||
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
|
||||
}
|
||||
|
||||
// Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
|
||||
int row_stride = pitch / sizeof(Uint32);
|
||||
|
||||
for (int y = 0; y < surface_data_->height; ++y) {
|
||||
for (int x = 0; x < surface_data_->width; ++x) {
|
||||
// Calcular la posición correcta en la textura teniendo en cuenta el stride
|
||||
int texture_index = y * row_stride + x;
|
||||
int surface_index = y * surface_data_->width + x;
|
||||
|
||||
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockTexture(texture); // Desbloquea la textura
|
||||
|
||||
// Renderiza la textura en la pantalla completa
|
||||
if (!SDL_RenderTexture(renderer, texture, nullptr, nullptr)) {
|
||||
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
// Vuelca la superficie a una textura
|
||||
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* srcRect, SDL_FRect* destRect) {
|
||||
if (!renderer || !texture || !surface_data_) {
|
||||
throw std::runtime_error("Renderer or texture is null.");
|
||||
}
|
||||
|
||||
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data.get()) {
|
||||
throw std::runtime_error("Invalid surface dimensions or data.");
|
||||
}
|
||||
|
||||
Uint32* pixels = nullptr;
|
||||
int pitch = 0;
|
||||
|
||||
SDL_Rect lockRect;
|
||||
if (destRect) {
|
||||
lockRect.x = static_cast<int>(destRect->x);
|
||||
lockRect.y = static_cast<int>(destRect->y);
|
||||
lockRect.w = static_cast<int>(destRect->w);
|
||||
lockRect.h = static_cast<int>(destRect->h);
|
||||
}
|
||||
|
||||
// Usa lockRect solo si destRect no es nulo
|
||||
if (!SDL_LockTexture(texture, destRect ? &lockRect : nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
|
||||
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
|
||||
}
|
||||
|
||||
int row_stride = pitch / sizeof(Uint32);
|
||||
|
||||
for (int y = 0; y < surface_data_->height; ++y) {
|
||||
for (int x = 0; x < surface_data_->width; ++x) {
|
||||
int texture_index = y * row_stride + x;
|
||||
int surface_index = y * surface_data_->width + x;
|
||||
|
||||
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockTexture(texture);
|
||||
|
||||
// Renderiza la textura con los rectángulos especificados
|
||||
if (!SDL_RenderTexture(renderer, texture, srcRect, destRect)) {
|
||||
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
// Realiza un efecto de fundido en la paleta principal
|
||||
bool Surface::fadePalette() {
|
||||
// Verificar que el tamaño mínimo de palette_ sea adecuado
|
||||
static constexpr int palette_size = 19;
|
||||
if (sizeof(palette_) / sizeof(palette_[0]) < palette_size) {
|
||||
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
|
||||
}
|
||||
|
||||
// Desplazar colores (pares e impares)
|
||||
for (int i = 18; i > 1; --i) {
|
||||
palette_[i] = palette_[i - 2];
|
||||
}
|
||||
|
||||
// Ajustar el primer color
|
||||
palette_[1] = palette_[0];
|
||||
|
||||
// Devolver si el índice 15 coincide con el índice 0
|
||||
return palette_[15] == palette_[0];
|
||||
}
|
||||
|
||||
// Realiza un efecto de fundido en la paleta secundaria
|
||||
bool Surface::fadeSubPalette(Uint32 delay) {
|
||||
// Variable estática para almacenar el último tick
|
||||
static Uint32 last_tick = 0;
|
||||
|
||||
// Obtener el tiempo actual
|
||||
Uint32 current_tick = SDL_GetTicks();
|
||||
|
||||
// Verificar si ha pasado el tiempo de retardo
|
||||
if (current_tick - last_tick < delay) {
|
||||
return false; // No se realiza el fade
|
||||
}
|
||||
|
||||
// Actualizar el último tick
|
||||
last_tick = current_tick;
|
||||
|
||||
// Verificar que el tamaño mínimo de sub_palette_ sea adecuado
|
||||
static constexpr int sub_palette_size = 19;
|
||||
if (sizeof(sub_palette_) / sizeof(sub_palette_[0]) < sub_palette_size) {
|
||||
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
|
||||
}
|
||||
|
||||
// Desplazar colores (pares e impares)
|
||||
for (int i = 18; i > 1; --i) {
|
||||
sub_palette_[i] = sub_palette_[i - 2];
|
||||
}
|
||||
|
||||
// Ajustar el primer color
|
||||
sub_palette_[1] = sub_palette_[0];
|
||||
|
||||
// Devolver si el índice 15 coincide con el índice 0
|
||||
return sub_palette_[15] == sub_palette_[0];
|
||||
}
|
||||
133
source2/Core/Rendering/surface.h
Normal file
133
source2/Core/Rendering/surface.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <array> // Para array
|
||||
#include <memory> // Para default_delete, shared_ptr, __shared_pt...
|
||||
#include <numeric> // Para iota
|
||||
#include <string> // Para string
|
||||
#include <utility> // Para move
|
||||
|
||||
#include "utils.h" // Para PaletteColor
|
||||
|
||||
// Alias
|
||||
using Palette = std::array<Uint32, 256>;
|
||||
using SubPalette = std::array<Uint8, 256>;
|
||||
|
||||
// Carga una paleta desde un archivo .gif
|
||||
Palette loadPalette(const std::string& file_path);
|
||||
|
||||
// Carga una paleta desde un archivo .pal
|
||||
Palette readPalFile(const std::string& file_path);
|
||||
|
||||
struct SurfaceData {
|
||||
std::shared_ptr<Uint8[]> data; // Usa std::shared_ptr para gestión automática
|
||||
float width; // Ancho de la imagen
|
||||
float height; // Alto de la imagen
|
||||
|
||||
// Constructor por defecto
|
||||
SurfaceData()
|
||||
: data(nullptr),
|
||||
width(0),
|
||||
height(0) {}
|
||||
|
||||
// Constructor que inicializa dimensiones y asigna memoria
|
||||
SurfaceData(float w, float h)
|
||||
: data(std::shared_ptr<Uint8[]>(new Uint8[static_cast<size_t>(w * h)](), std::default_delete<Uint8[]>())),
|
||||
width(w),
|
||||
height(h) {}
|
||||
|
||||
// Constructor para inicializar directamente con datos
|
||||
SurfaceData(float w, float h, std::shared_ptr<Uint8[]> pixels)
|
||||
: data(std::move(pixels)),
|
||||
width(w),
|
||||
height(h) {}
|
||||
|
||||
// Constructor de movimiento
|
||||
SurfaceData(SurfaceData&& other) noexcept = default;
|
||||
|
||||
// Operador de movimiento
|
||||
SurfaceData& operator=(SurfaceData&& other) noexcept = default;
|
||||
|
||||
// Evita copias accidentales
|
||||
SurfaceData(const SurfaceData&) = delete;
|
||||
SurfaceData& operator=(const SurfaceData&) = delete;
|
||||
};
|
||||
|
||||
class Surface {
|
||||
private:
|
||||
std::shared_ptr<SurfaceData> surface_data_; // Datos a dibujar
|
||||
Palette palette_; // Paleta para volcar la SurfaceData a una Textura
|
||||
SubPalette sub_palette_; // Paleta para reindexar colores
|
||||
int transparent_color_; // Indice de la paleta que se omite en la copia de datos
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Surface(int w, int h);
|
||||
explicit Surface(const std::string& file_path);
|
||||
|
||||
// Destructor
|
||||
~Surface() = default;
|
||||
|
||||
// Carga una SurfaceData desde un archivo
|
||||
SurfaceData loadSurface(const std::string& file_path);
|
||||
|
||||
// Carga una paleta desde un archivo
|
||||
void loadPalette(const std::string& file_path);
|
||||
void loadPalette(Palette palette);
|
||||
|
||||
// Copia una región de la SurfaceData de origen a la SurfaceData de destino
|
||||
void render(float dx, float dy, float sx, float sy, float w, float h);
|
||||
void render(int x, int y, SDL_FRect* clip = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
|
||||
void render(SDL_FRect* srcRect = nullptr, SDL_FRect* dstRect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
|
||||
|
||||
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
|
||||
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* srcRect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
|
||||
|
||||
// Establece un color en la paleta
|
||||
void setColor(int index, Uint32 color);
|
||||
|
||||
// Rellena la SurfaceData con un color
|
||||
void clear(Uint8 color);
|
||||
|
||||
// Vuelca la SurfaceData a una textura
|
||||
void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture);
|
||||
void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* srcRect, SDL_FRect* destRect);
|
||||
|
||||
// Realiza un efecto de fundido en las paletas
|
||||
bool fadePalette();
|
||||
bool fadeSubPalette(Uint32 delay = 0);
|
||||
|
||||
// Pone un pixel en la SurfaceData
|
||||
void putPixel(int x, int y, Uint8 color);
|
||||
|
||||
// Obtiene el color de un pixel de la surface_data
|
||||
Uint8 getPixel(int x, int y);
|
||||
|
||||
// Dibuja un rectangulo relleno
|
||||
void fillRect(const SDL_FRect* rect, Uint8 color);
|
||||
|
||||
// Dibuja el borde de un rectangulo
|
||||
void drawRectBorder(const SDL_FRect* rect, Uint8 color);
|
||||
|
||||
// Dibuja una linea
|
||||
void drawLine(float x1, float y1, float x2, float y2, Uint8 color);
|
||||
|
||||
// Metodos para gestionar surface_data_
|
||||
std::shared_ptr<SurfaceData> getSurfaceData() const { return surface_data_; }
|
||||
void setSurfaceData(std::shared_ptr<SurfaceData> new_data) { surface_data_ = new_data; }
|
||||
|
||||
// Obtien ancho y alto
|
||||
float getWidth() const { return surface_data_->width; }
|
||||
float getHeight() const { return surface_data_->height; }
|
||||
|
||||
// Color transparente
|
||||
Uint8 getTransparentColor() const { return transparent_color_; }
|
||||
void setTransparentColor(Uint8 color = 255) { transparent_color_ = color; }
|
||||
|
||||
// Paleta
|
||||
void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; }
|
||||
|
||||
// Inicializa la sub paleta
|
||||
void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); }
|
||||
};
|
||||
236
source2/Core/Rendering/surface_animated_sprite.cpp
Normal file
236
source2/Core/Rendering/surface_animated_sprite.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
#include "sprite/surface_animated_sprite.h"
|
||||
|
||||
#include <stddef.h> // Para size_t
|
||||
|
||||
#include <fstream> // Para basic_ostream, basic_istream, operator<<, basic...
|
||||
#include <iostream> // Para cout, cerr
|
||||
#include <sstream> // Para basic_stringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
|
||||
#include "surface.h" // Para Surface
|
||||
#include "utils.h" // Para printWithDots
|
||||
|
||||
// Carga las animaciones en un vector(Animations) desde un fichero
|
||||
Animations loadAnimationsFromFile(const std::string& file_path) {
|
||||
std::ifstream file(file_path);
|
||||
if (!file) {
|
||||
std::cerr << "Error: Fichero no encontrado " << file_path << std::endl;
|
||||
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
||||
}
|
||||
|
||||
printWithDots("Animation : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
|
||||
|
||||
std::vector<std::string> buffer;
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (!line.empty())
|
||||
buffer.push_back(line);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
SAnimatedSprite::SAnimatedSprite(std::shared_ptr<Surface> surface, const std::string& file_path)
|
||||
: SMovingSprite(surface) {
|
||||
// Carga las animaciones
|
||||
if (!file_path.empty()) {
|
||||
Animations v = loadAnimationsFromFile(file_path);
|
||||
setAnimations(v);
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor
|
||||
SAnimatedSprite::SAnimatedSprite(std::shared_ptr<Surface> surface, const Animations& animations)
|
||||
: SMovingSprite(surface) {
|
||||
if (!animations.empty()) {
|
||||
setAnimations(animations);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el indice de la animación a partir del nombre
|
||||
int SAnimatedSprite::getIndex(const std::string& name) {
|
||||
auto index = -1;
|
||||
|
||||
for (const auto& a : animations_) {
|
||||
index++;
|
||||
if (a.name == name) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calcula el frame correspondiente a la animación
|
||||
void SAnimatedSprite::animate() {
|
||||
if (animations_[current_animation_].speed == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calcula el frame actual a partir del contador
|
||||
animations_[current_animation_].current_frame = animations_[current_animation_].counter / animations_[current_animation_].speed;
|
||||
|
||||
// Si alcanza el final de la animación, reinicia el contador de la animación
|
||||
// en función de la variable loop y coloca el nuevo frame
|
||||
if (animations_[current_animation_].current_frame >= static_cast<int>(animations_[current_animation_].frames.size())) {
|
||||
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
|
||||
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size();
|
||||
animations_[current_animation_].completed = true;
|
||||
} else { // Si hay loop, vuelve al frame indicado
|
||||
animations_[current_animation_].counter = 0;
|
||||
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
|
||||
}
|
||||
}
|
||||
// En caso contrario
|
||||
else {
|
||||
// Escoge el frame correspondiente de la animación
|
||||
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
||||
|
||||
// Incrementa el contador de la animacion
|
||||
animations_[current_animation_].counter++;
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la animación
|
||||
bool SAnimatedSprite::animationIsCompleted() {
|
||||
return animations_[current_animation_].completed;
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void SAnimatedSprite::setCurrentAnimation(const std::string& name) {
|
||||
const auto new_animation = getIndex(name);
|
||||
if (current_animation_ != new_animation) {
|
||||
current_animation_ = new_animation;
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].counter = 0;
|
||||
animations_[current_animation_].completed = false;
|
||||
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void SAnimatedSprite::setCurrentAnimation(int index) {
|
||||
const auto new_animation = index;
|
||||
if (current_animation_ != new_animation) {
|
||||
current_animation_ = new_animation;
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].counter = 0;
|
||||
animations_[current_animation_].completed = false;
|
||||
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void SAnimatedSprite::update() {
|
||||
animate();
|
||||
SMovingSprite::update();
|
||||
}
|
||||
|
||||
// Reinicia la animación
|
||||
void SAnimatedSprite::resetAnimation() {
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].counter = 0;
|
||||
animations_[current_animation_].completed = false;
|
||||
}
|
||||
|
||||
// Carga la animación desde un vector de cadenas
|
||||
void SAnimatedSprite::setAnimations(const Animations& animations) {
|
||||
float frame_width = 1.0F;
|
||||
float frame_height = 1.0F;
|
||||
int frames_per_row = 1;
|
||||
int max_tiles = 1;
|
||||
|
||||
size_t index = 0;
|
||||
while (index < animations.size()) {
|
||||
std::string line = animations.at(index);
|
||||
|
||||
// Parsea el fichero para buscar variables y valores
|
||||
if (line != "[animation]") {
|
||||
// Encuentra la posición del caracter '='
|
||||
size_t pos = line.find("=");
|
||||
|
||||
// Procesa las dos subcadenas
|
||||
if (pos != std::string::npos) {
|
||||
std::string key = line.substr(0, pos);
|
||||
int value = std::stoi(line.substr(pos + 1));
|
||||
if (key == "frame_width")
|
||||
frame_width = value;
|
||||
else if (key == "frame_height")
|
||||
frame_height = value;
|
||||
else
|
||||
std::cout << "Warning: unknown parameter " << key << std::endl;
|
||||
|
||||
frames_per_row = surface_->getWidth() / frame_width;
|
||||
const int w = surface_->getWidth() / frame_width;
|
||||
const int h = surface_->getHeight() / frame_height;
|
||||
max_tiles = w * h;
|
||||
}
|
||||
}
|
||||
|
||||
// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
|
||||
if (line == "[animation]") {
|
||||
AnimationData animation;
|
||||
do {
|
||||
index++;
|
||||
line = animations.at(index);
|
||||
size_t pos = line.find("=");
|
||||
|
||||
if (pos != std::string::npos) {
|
||||
std::string key = line.substr(0, pos);
|
||||
std::string value = line.substr(pos + 1);
|
||||
|
||||
if (key == "name")
|
||||
animation.name = value;
|
||||
else if (key == "speed")
|
||||
animation.speed = std::stoi(value);
|
||||
else if (key == "loop")
|
||||
animation.loop = std::stoi(value);
|
||||
else if (key == "frames") {
|
||||
// Se introducen los valores separados por comas en un vector
|
||||
std::stringstream ss(value);
|
||||
std::string tmp;
|
||||
SDL_FRect rect = {0.0F, 0.0F, frame_width, frame_height};
|
||||
while (getline(ss, tmp, ',')) {
|
||||
// Comprueba que el tile no sea mayor que el maximo indice permitido
|
||||
const int num_tile = std::stoi(tmp);
|
||||
if (num_tile <= max_tiles) {
|
||||
rect.x = (num_tile % frames_per_row) * frame_width;
|
||||
rect.y = (num_tile / frames_per_row) * frame_height;
|
||||
animation.frames.emplace_back(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
std::cout << "Warning: unknown parameter " << key << std::endl;
|
||||
}
|
||||
} while (line != "[/animation]");
|
||||
|
||||
// Añade la animación al vector de animaciones
|
||||
animations_.emplace_back(animation);
|
||||
}
|
||||
|
||||
// Una vez procesada la linea, aumenta el indice para pasar a la siguiente
|
||||
index++;
|
||||
}
|
||||
|
||||
// Pone un valor por defecto
|
||||
setWidth(frame_width);
|
||||
setHeight(frame_height);
|
||||
}
|
||||
|
||||
// Establece el frame actual de la animación
|
||||
void SAnimatedSprite::setCurrentAnimationFrame(int num) {
|
||||
// Descarta valores fuera de rango
|
||||
if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size())) {
|
||||
num = 0;
|
||||
}
|
||||
|
||||
// Cambia el valor de la variable
|
||||
animations_[current_animation_].current_frame = num;
|
||||
animations_[current_animation_].counter = 0;
|
||||
|
||||
// Escoge el frame correspondiente de la animación
|
||||
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
|
||||
}
|
||||
78
source2/Core/Rendering/surface_animated_sprite.h
Normal file
78
source2/Core/Rendering/surface_animated_sprite.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "sprite/surface_moving_sprite.h" // Para SMovingSprite
|
||||
class Surface; // lines 9-9
|
||||
|
||||
struct AnimationData {
|
||||
std::string name; // Nombre de la animacion
|
||||
std::vector<SDL_FRect> frames; // Cada uno de los frames que componen la animación
|
||||
int speed; // Velocidad de la animación
|
||||
int loop; // Indica a que frame vuelve la animación al terminar. -1 para que no vuelva
|
||||
bool completed; // Indica si ha finalizado la animación
|
||||
int current_frame; // Frame actual
|
||||
int counter; // Contador para las animaciones
|
||||
|
||||
AnimationData()
|
||||
: name(std::string()),
|
||||
speed(5),
|
||||
loop(0),
|
||||
completed(false),
|
||||
current_frame(0),
|
||||
counter(0) {}
|
||||
};
|
||||
|
||||
using Animations = std::vector<std::string>;
|
||||
|
||||
// Carga las animaciones en un vector(Animations) desde un fichero
|
||||
Animations loadAnimationsFromFile(const std::string& file_path);
|
||||
|
||||
class SAnimatedSprite : public SMovingSprite {
|
||||
protected:
|
||||
// Variables
|
||||
std::vector<AnimationData> animations_; // Vector con las diferentes animaciones
|
||||
int current_animation_ = 0; // Animacion activa
|
||||
|
||||
// Calcula el frame correspondiente a la animación actual
|
||||
void animate();
|
||||
|
||||
// Carga la animación desde un vector de cadenas
|
||||
void setAnimations(const Animations& animations);
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
SAnimatedSprite(std::shared_ptr<Surface> surface, const std::string& file_path);
|
||||
SAnimatedSprite(std::shared_ptr<Surface> surface, const Animations& animations);
|
||||
explicit SAnimatedSprite(std::shared_ptr<Surface> surface)
|
||||
: SMovingSprite(surface) {}
|
||||
|
||||
// Destructor
|
||||
virtual ~SAnimatedSprite() override = default;
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void update() override;
|
||||
|
||||
// Comprueba si ha terminado la animación
|
||||
bool animationIsCompleted();
|
||||
|
||||
// Obtiene el indice de la animación a partir del nombre
|
||||
int getIndex(const std::string& name);
|
||||
|
||||
// Establece la animacion actual
|
||||
void setCurrentAnimation(const std::string& name = "default");
|
||||
void setCurrentAnimation(int index = 0);
|
||||
|
||||
// Reinicia la animación
|
||||
void resetAnimation();
|
||||
|
||||
// Establece el frame actual de la animación
|
||||
void setCurrentAnimationFrame(int num);
|
||||
|
||||
// Obtiene el numero de frames de la animación actual
|
||||
int getCurrentAnimationSize() { return static_cast<int>(animations_[current_animation_].frames.size()); }
|
||||
};
|
||||
94
source2/Core/Rendering/surface_moving_sprite.cpp
Normal file
94
source2/Core/Rendering/surface_moving_sprite.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "sprite/surface_moving_sprite.h"
|
||||
|
||||
#include "surface.h" // Para Surface
|
||||
|
||||
// Constructor
|
||||
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip)
|
||||
: SSprite(surface, pos),
|
||||
x_(pos.x),
|
||||
y_(pos.y),
|
||||
flip_(flip) { SSprite::pos_ = pos; }
|
||||
|
||||
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
||||
: SSprite(surface, pos),
|
||||
x_(pos.x),
|
||||
y_(pos.y),
|
||||
flip_(SDL_FLIP_NONE) { SSprite::pos_ = pos; }
|
||||
|
||||
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface)
|
||||
: SSprite(surface),
|
||||
x_(0.0f),
|
||||
y_(0.0f),
|
||||
flip_(SDL_FLIP_NONE) { SSprite::clear(); }
|
||||
|
||||
// Reinicia todas las variables
|
||||
void SMovingSprite::clear() {
|
||||
x_ = 0.0f; // Posición en el eje X
|
||||
y_ = 0.0f; // Posición en el eje Y
|
||||
|
||||
vx_ = 0.0f; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
|
||||
vy_ = 0.0f; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
|
||||
|
||||
ax_ = 0.0f; // Aceleración en el eje X. Variación de la velocidad
|
||||
ay_ = 0.0f; // Aceleración en el eje Y. Variación de la velocidad
|
||||
|
||||
flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
|
||||
|
||||
SSprite::clear();
|
||||
}
|
||||
|
||||
// Mueve el sprite
|
||||
void SMovingSprite::move() {
|
||||
x_ += vx_;
|
||||
y_ += vy_;
|
||||
|
||||
vx_ += ax_;
|
||||
vy_ += ay_;
|
||||
|
||||
pos_.x = static_cast<int>(x_);
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
|
||||
// Actualiza las variables internas del objeto
|
||||
void SMovingSprite::update() {
|
||||
move();
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void SMovingSprite::render() {
|
||||
surface_->render(pos_.x, pos_.y, &clip_, flip_);
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void SMovingSprite::render(Uint8 source_color, Uint8 target_color) {
|
||||
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_, flip_);
|
||||
}
|
||||
|
||||
// Establece la posición y_ el tamaño del objeto
|
||||
void SMovingSprite::setPos(SDL_FRect rect) {
|
||||
x_ = static_cast<float>(rect.x);
|
||||
y_ = static_cast<float>(rect.y);
|
||||
|
||||
pos_ = rect;
|
||||
}
|
||||
|
||||
// Establece el valor de las variables
|
||||
void SMovingSprite::setPos(float x, float y) {
|
||||
x_ = x;
|
||||
y_ = y;
|
||||
|
||||
pos_.x = static_cast<int>(x_);
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void SMovingSprite::setPosX(float value) {
|
||||
x_ = value;
|
||||
pos_.x = static_cast<int>(x_);
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void SMovingSprite::setPosY(float value) {
|
||||
y_ = value;
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
81
source2/Core/Rendering/surface_moving_sprite.h
Normal file
81
source2/Core/Rendering/surface_moving_sprite.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
class Surface; // lines 8-8
|
||||
|
||||
// Clase SMovingSprite. Añade movimiento y flip al sprite
|
||||
class SMovingSprite : public SSprite {
|
||||
public:
|
||||
protected:
|
||||
float x_; // Posición en el eje X
|
||||
float y_; // Posición en el eje Y
|
||||
|
||||
float vx_ = 0.0f; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
|
||||
float vy_ = 0.0f; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
|
||||
|
||||
float ax_ = 0.0f; // Aceleración en el eje X. Variación de la velocidad
|
||||
float ay_ = 0.0f; // Aceleración en el eje Y. Variación de la velocidad
|
||||
|
||||
SDL_FlipMode flip_; // Indica como se voltea el sprite
|
||||
|
||||
// Mueve el sprite
|
||||
void move();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip);
|
||||
SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
|
||||
explicit SMovingSprite(std::shared_ptr<Surface> surface);
|
||||
|
||||
// Destructor
|
||||
virtual ~SMovingSprite() override = default;
|
||||
|
||||
// Actualiza las variables internas del objeto
|
||||
virtual void update();
|
||||
|
||||
// Reinicia todas las variables a cero
|
||||
void clear() override;
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void render() override;
|
||||
void render(Uint8 source_color, Uint8 target_color) override;
|
||||
|
||||
// Obtiene la variable
|
||||
float getPosX() const { return x_; }
|
||||
float getPosY() const { return y_; }
|
||||
float getVelX() const { return vx_; }
|
||||
float getVelY() const { return vy_; }
|
||||
float getAccelX() const { return ax_; }
|
||||
float getAccelY() const { return ay_; }
|
||||
|
||||
// Establece la variable
|
||||
void setVelX(float value) { vx_ = value; }
|
||||
void setVelY(float value) { vy_ = value; }
|
||||
void setAccelX(float value) { ax_ = value; }
|
||||
void setAccelY(float value) { ay_ = value; }
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
|
||||
|
||||
// Gira el sprite horizontalmente
|
||||
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; }
|
||||
|
||||
// Obtiene el valor de la variable
|
||||
SDL_FlipMode getFlip() { return flip_; }
|
||||
|
||||
// Establece la posición y_ el tamaño del objeto
|
||||
void setPos(SDL_FRect rect);
|
||||
|
||||
// Establece el valor de las variables
|
||||
void setPos(float x, float y);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setPosX(float value);
|
||||
|
||||
// Establece el valor de la variable
|
||||
void setPosY(float value);
|
||||
};
|
||||
46
source2/Core/Rendering/surface_sprite.cpp
Normal file
46
source2/Core/Rendering/surface_sprite.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "sprite/surface_sprite.h"
|
||||
|
||||
#include "surface.h" // Para Surface
|
||||
|
||||
// Constructor
|
||||
SSprite::SSprite(std::shared_ptr<Surface> surface, float x, float y, float w, float h)
|
||||
: surface_(surface),
|
||||
pos_((SDL_FRect){x, y, w, h}),
|
||||
clip_((SDL_FRect){0.0F, 0.0F, pos_.w, pos_.h}) {}
|
||||
|
||||
SSprite::SSprite(std::shared_ptr<Surface> surface, SDL_FRect rect)
|
||||
: surface_(surface),
|
||||
pos_(rect),
|
||||
clip_((SDL_FRect){0, 0, pos_.w, pos_.h}) {}
|
||||
|
||||
SSprite::SSprite(std::shared_ptr<Surface> surface)
|
||||
: surface_(surface),
|
||||
pos_((SDL_FRect){0.0F, 0.0F, surface_->getWidth(), surface_->getHeight()}),
|
||||
clip_(pos_) {}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void SSprite::render() {
|
||||
surface_->render(pos_.x, pos_.y, &clip_);
|
||||
}
|
||||
|
||||
void SSprite::render(Uint8 source_color, Uint8 target_color) {
|
||||
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_);
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void SSprite::setPosition(float x, float y) {
|
||||
pos_.x = x;
|
||||
pos_.y = y;
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void SSprite::setPosition(SDL_FPoint p) {
|
||||
pos_.x = p.x;
|
||||
pos_.y = p.y;
|
||||
}
|
||||
|
||||
// Reinicia las variables a cero
|
||||
void SSprite::clear() {
|
||||
pos_ = {0, 0, 0, 0};
|
||||
clip_ = {0, 0, 0, 0};
|
||||
}
|
||||
69
source2/Core/Rendering/surface_sprite.h
Normal file
69
source2/Core/Rendering/surface_sprite.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
class Surface; // lines 5-5
|
||||
|
||||
// Clase SSprite
|
||||
class SSprite {
|
||||
protected:
|
||||
// Variables
|
||||
std::shared_ptr<Surface> surface_; // Surface donde estan todos los dibujos del sprite
|
||||
SDL_FRect pos_; // Posición y tamaño donde dibujar el sprite
|
||||
SDL_FRect clip_; // Rectangulo de origen de la surface que se dibujará en pantalla
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
SSprite(std::shared_ptr<Surface>, float x, float y, float w, float h);
|
||||
SSprite(std::shared_ptr<Surface>, SDL_FRect rect);
|
||||
explicit SSprite(std::shared_ptr<Surface>);
|
||||
|
||||
// Destructor
|
||||
virtual ~SSprite() = default;
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
virtual void render();
|
||||
virtual void render(Uint8 source_color, Uint8 target_color);
|
||||
|
||||
// Reinicia las variables a cero
|
||||
virtual void clear();
|
||||
|
||||
// Obtiene la posición y el tamaño
|
||||
float getX() const { return pos_.x; }
|
||||
float getY() const { return pos_.y; }
|
||||
float getWidth() const { return pos_.w; }
|
||||
float getHeight() const { return pos_.h; }
|
||||
|
||||
// Devuelve el rectangulo donde está el sprite
|
||||
SDL_FRect getPosition() const { return pos_; }
|
||||
SDL_FRect& getRect() { return pos_; }
|
||||
|
||||
// Establece la posición y el tamaño
|
||||
void setX(float x) { pos_.x = x; }
|
||||
void setY(float y) { pos_.y = y; }
|
||||
void setWidth(float w) { pos_.w = w; }
|
||||
void setHeight(float h) { pos_.h = h; }
|
||||
|
||||
// Establece la posición del objeto
|
||||
void setPosition(float x, float y);
|
||||
void setPosition(SDL_FPoint p);
|
||||
void setPosition(SDL_FRect r) { pos_ = r; }
|
||||
|
||||
// Aumenta o disminuye la posición
|
||||
void incX(float value) { pos_.x += value; }
|
||||
void incY(float value) { pos_.y += value; }
|
||||
|
||||
// Obtiene el rectangulo que se dibuja de la surface
|
||||
SDL_FRect getClip() const { return clip_; }
|
||||
|
||||
// Establece el rectangulo que se dibuja de la surface
|
||||
void setClip(SDL_FRect rect) { clip_ = rect; }
|
||||
void setClip(float x, float y, float w, float h) { clip_ = (SDL_FRect){x, y, w, h}; }
|
||||
|
||||
// Obtiene un puntero a la surface
|
||||
std::shared_ptr<Surface> getSurface() const { return surface_; }
|
||||
|
||||
// Establece la surface a utilizar
|
||||
void setSurface(std::shared_ptr<Surface> surface) { surface_ = surface; }
|
||||
};
|
||||
241
source2/Core/Rendering/text.cpp
Normal file
241
source2/Core/Rendering/text.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
#include "text.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stddef.h> // Para size_t
|
||||
|
||||
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream
|
||||
#include <iostream> // Para cerr
|
||||
#include <stdexcept> // Para runtime_error
|
||||
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "utils.h" // Para getFileName, stringToColor, printWithDots
|
||||
|
||||
// Llena una estructuta TextFile desde un fichero
|
||||
std::shared_ptr<TextFile> loadTextFile(const std::string& file_path) {
|
||||
auto tf = std::make_shared<TextFile>();
|
||||
|
||||
// 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;
|
||||
tf->box_width = 0;
|
||||
tf->box_height = 0;
|
||||
}
|
||||
|
||||
// Abre el fichero para leer los valores
|
||||
std::ifstream file(file_path);
|
||||
|
||||
if (file.is_open() && file.good()) {
|
||||
std::string buffer;
|
||||
|
||||
// Lee los dos primeros valores del fichero
|
||||
std::getline(file, buffer);
|
||||
std::getline(file, buffer);
|
||||
tf->box_width = std::stoi(buffer);
|
||||
|
||||
std::getline(file, buffer);
|
||||
std::getline(file, buffer);
|
||||
tf->box_height = std::stoi(buffer);
|
||||
|
||||
// lee el resto de datos del fichero
|
||||
auto index = 32;
|
||||
auto line_read = 0;
|
||||
while (std::getline(file, buffer)) {
|
||||
// Almacena solo las lineas impares
|
||||
if (line_read % 2 == 1)
|
||||
tf->offset[index++].w = std::stoi(buffer);
|
||||
|
||||
// Limpia el buffer
|
||||
buffer.clear();
|
||||
line_read++;
|
||||
};
|
||||
|
||||
// Cierra el fichero
|
||||
printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]");
|
||||
file.close();
|
||||
}
|
||||
|
||||
// El fichero no se puede abrir
|
||||
else {
|
||||
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << std::endl;
|
||||
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
||||
}
|
||||
|
||||
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
|
||||
for (int i = 32; i < 128; ++i) {
|
||||
tf->offset[i].x = ((i - 32) % 15) * tf->box_width;
|
||||
tf->offset[i].y = ((i - 32) / 15) * tf->box_height;
|
||||
}
|
||||
|
||||
return tf;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Text::Text(std::shared_ptr<Surface> surface, const std::string& text_file) {
|
||||
// Carga los offsets desde el fichero
|
||||
auto tf = loadTextFile(text_file);
|
||||
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = tf->box_height;
|
||||
box_width_ = tf->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = tf->offset[i].x;
|
||||
offset_[i].y = tf->offset[i].y;
|
||||
offset_[i].w = tf->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<SSprite>(surface, (SDL_FRect){0.0F, 0.0F, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Text::Text(std::shared_ptr<Surface> surface, std::shared_ptr<TextFile> text_file) {
|
||||
// Inicializa variables desde la estructura
|
||||
box_height_ = text_file->box_height;
|
||||
box_width_ = text_file->box_width;
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
offset_[i].x = text_file->offset[i].x;
|
||||
offset_[i].y = text_file->offset[i].y;
|
||||
offset_[i].w = text_file->offset[i].w;
|
||||
}
|
||||
|
||||
// Crea los objetos
|
||||
sprite_ = std::make_unique<SSprite>(surface, (SDL_FRect){0.0F, 0.0F, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
||||
|
||||
// Inicializa variables
|
||||
fixed_width_ = false;
|
||||
}
|
||||
|
||||
// Escribe texto en pantalla
|
||||
void Text::write(int x, int y, const std::string& text, int kerning, int lenght) {
|
||||
int shift = 0;
|
||||
|
||||
if (lenght == -1)
|
||||
lenght = text.length();
|
||||
|
||||
sprite_->setY(y);
|
||||
for (int i = 0; i < lenght; ++i) {
|
||||
auto index = static_cast<int>(text[i]);
|
||||
sprite_->setClip(offset_[index].x, offset_[index].y, box_width_, box_height_);
|
||||
sprite_->setX(x + shift);
|
||||
sprite_->render(1, 15);
|
||||
shift += offset_[static_cast<int>(text[i])].w + kerning;
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe el texto en una surface
|
||||
std::shared_ptr<Surface> Text::writeToSurface(const std::string& text, int zoom, int kerning) {
|
||||
auto width = lenght(text, kerning) * zoom;
|
||||
auto height = box_height_ * zoom;
|
||||
auto surface = std::make_shared<Surface>(width, height);
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface);
|
||||
surface->clear(stringToColor("transparent"));
|
||||
write(0, 0, text, kerning);
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
// Escribe el texto con extras en una surface
|
||||
std::shared_ptr<Surface> Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, Uint8 textColor, Uint8 shadow_distance, Uint8 shadow_color, int lenght) {
|
||||
auto width = Text::lenght(text, kerning) + shadow_distance;
|
||||
auto height = box_height_ + shadow_distance;
|
||||
auto surface = std::make_shared<Surface>(width, height);
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface);
|
||||
surface->clear(stringToColor("transparent"));
|
||||
writeDX(flags, 0, 0, text, kerning, textColor, shadow_distance, shadow_color, lenght);
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
// Escribe el texto con colores
|
||||
void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int lenght) {
|
||||
int shift = 0;
|
||||
|
||||
if (lenght == -1) {
|
||||
lenght = text.length();
|
||||
}
|
||||
|
||||
sprite_->setY(y);
|
||||
for (int i = 0; i < lenght; ++i) {
|
||||
auto index = static_cast<int>(text[i]);
|
||||
sprite_->setClip(offset_[index].x, offset_[index].y, box_width_, box_height_);
|
||||
sprite_->setX(x + shift);
|
||||
sprite_->render(1, color);
|
||||
shift += offset_[static_cast<int>(text[i])].w + kerning;
|
||||
}
|
||||
}
|
||||
|
||||
// Escribe el texto con sombra
|
||||
void Text::writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance, int kerning, int lenght) {
|
||||
writeColored(x + shadow_distance, y + shadow_distance, text, color, kerning, lenght);
|
||||
write(x, y, text, kerning, lenght);
|
||||
}
|
||||
|
||||
// Escribe el texto centrado en un punto x
|
||||
void Text::writeCentered(int x, int y, const std::string& text, int kerning, int lenght) {
|
||||
x -= (Text::lenght(text, kerning) / 2);
|
||||
write(x, y, text, kerning, lenght);
|
||||
}
|
||||
|
||||
// Escribe texto con extras
|
||||
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 textColor, Uint8 shadow_distance, Uint8 shadow_color, int lenght) {
|
||||
const auto centered = ((flags & TEXT_CENTER) == TEXT_CENTER);
|
||||
const auto shadowed = ((flags & TEXT_SHADOW) == TEXT_SHADOW);
|
||||
const auto colored = ((flags & TEXT_COLOR) == TEXT_COLOR);
|
||||
const auto stroked = ((flags & TEXT_STROKE) == TEXT_STROKE);
|
||||
|
||||
if (centered) {
|
||||
x -= (Text::lenght(text, kerning) / 2);
|
||||
}
|
||||
|
||||
if (shadowed) {
|
||||
writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, lenght);
|
||||
}
|
||||
|
||||
if (stroked) {
|
||||
for (int dist = 1; dist <= shadow_distance; ++dist) {
|
||||
for (int dy = -dist; dy <= dist; ++dy) {
|
||||
for (int dx = -dist; dx <= dist; ++dx) {
|
||||
writeColored(x + dx, y + dy, text, shadow_color, kerning, lenght);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (colored) {
|
||||
writeColored(x, y, text, textColor, kerning, lenght);
|
||||
} else {
|
||||
writeColored(x, y, text, textColor, kerning, lenght);
|
||||
// write(x, y, text, kerning, lenght);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene la longitud en pixels de una cadena
|
||||
int Text::lenght(const std::string& text, int kerning) const {
|
||||
int shift = 0;
|
||||
for (size_t i = 0; i < text.length(); ++i)
|
||||
shift += (offset_[static_cast<int>(text[i])].w + kerning);
|
||||
|
||||
// Descuenta el kerning del último caracter
|
||||
return shift - kerning;
|
||||
}
|
||||
|
||||
// Devuelve el valor de la variable
|
||||
int Text::getCharacterSize() const {
|
||||
return box_width_;
|
||||
}
|
||||
|
||||
// Establece si se usa un tamaño fijo de letra
|
||||
void Text::setFixedWidth(bool value) {
|
||||
fixed_width_ = value;
|
||||
}
|
||||
78
source2/Core/Rendering/text.h
Normal file
78
source2/Core/Rendering/text.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
class Surface; // lines 8-8
|
||||
|
||||
constexpr int TEXT_COLOR = 1;
|
||||
constexpr int TEXT_SHADOW = 2;
|
||||
constexpr int TEXT_CENTER = 4;
|
||||
constexpr int TEXT_STROKE = 8;
|
||||
|
||||
struct TextOffset {
|
||||
int x, y, w;
|
||||
};
|
||||
|
||||
struct TextFile {
|
||||
int box_width; // Anchura de la caja de cada caracter en el png
|
||||
int box_height; // Altura de la caja de cada caracter en el png
|
||||
TextOffset offset[128]; // Vector con las posiciones y ancho de cada letra
|
||||
};
|
||||
|
||||
// Llena una estructuta TextFile desde un fichero
|
||||
std::shared_ptr<TextFile> loadTextFile(const std::string& file_path);
|
||||
|
||||
// Clase texto. Pinta texto en pantalla a partir de un bitmap
|
||||
class Text {
|
||||
private:
|
||||
// Objetos y punteros
|
||||
std::unique_ptr<SSprite> sprite_ = nullptr; // Objeto con los graficos para el texto
|
||||
|
||||
// Variables
|
||||
int box_width_ = 0; // Anchura de la caja de cada caracter en el png
|
||||
int box_height_ = 0; // Altura de la caja de cada caracter en el png
|
||||
bool fixed_width_ = false; // Indica si el texto se ha de escribir con longitud fija en todas las letras
|
||||
TextOffset offset_[128] = {}; // Vector con las posiciones y ancho de cada letra
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Text(std::shared_ptr<Surface> surface, const std::string& text_file);
|
||||
Text(std::shared_ptr<Surface> surface, std::shared_ptr<TextFile> text_file);
|
||||
|
||||
// Destructor
|
||||
~Text() = default;
|
||||
|
||||
// Escribe el texto en pantalla
|
||||
void write(int x, int y, const std::string& text, int kerning = 1, int lenght = -1);
|
||||
|
||||
// Escribe el texto en una textura
|
||||
std::shared_ptr<Surface> writeToSurface(const std::string& text, int zoom = 1, int kerning = 1);
|
||||
|
||||
// Escribe el texto con extras en una textura
|
||||
std::shared_ptr<Surface> writeDXToSurface(Uint8 flags, const std::string& text, int kerning = 1, Uint8 textColor = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1);
|
||||
|
||||
// Escribe el texto con colores
|
||||
void writeColored(int x, int y, const std::string& text, Uint8 color, int kerning = 1, int lenght = -1);
|
||||
|
||||
// Escribe el texto con sombra
|
||||
void writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance = 1, int kerning = 1, int lenght = -1);
|
||||
|
||||
// Escribe el texto centrado en un punto x
|
||||
void writeCentered(int x, int y, const std::string& text, int kerning = 1, int lenght = -1);
|
||||
|
||||
// Escribe texto con extras
|
||||
void writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning = 1, Uint8 textColor = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1);
|
||||
|
||||
// Obtiene la longitud en pixels de una cadena
|
||||
int lenght(const std::string& text, int kerning = 1) const;
|
||||
|
||||
// Devuelve el valor de la variable
|
||||
int getCharacterSize() const;
|
||||
|
||||
// Establece si se usa un tamaño fijo de letra
|
||||
void setFixedWidth(bool value);
|
||||
};
|
||||
167
source2/Core/Rendering/texture.cpp
Normal file
167
source2/Core/Rendering/texture.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
|
||||
#include "texture.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
|
||||
#include <iostream> // Para basic_ostream, operator<<, endl, cout
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string> // Para char_traits, operator<<, string, opera...
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "utils.h" // Para getFileName, Color, printWithDots
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "external/stb_image.h" // para stbi_failure_reason, stbi_image_free
|
||||
|
||||
// Constructor
|
||||
Texture::Texture(SDL_Renderer* renderer, const std::string& path)
|
||||
: renderer_(renderer),
|
||||
path_(path) {
|
||||
// Carga el fichero en la textura
|
||||
if (!path_.empty()) {
|
||||
// Obtiene la extensión
|
||||
const std::string extension = path_.substr(path_.find_last_of(".") + 1);
|
||||
|
||||
// .png
|
||||
if (extension == "png") {
|
||||
loadFromFile(path_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Texture::~Texture() {
|
||||
unloadTexture();
|
||||
palettes_.clear();
|
||||
}
|
||||
|
||||
// Carga una imagen desde un fichero
|
||||
bool Texture::loadFromFile(const std::string& file_path) {
|
||||
if (file_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int req_format = STBI_rgb_alpha;
|
||||
int width, height, orig_format;
|
||||
unsigned char* data = stbi_load(file_path.c_str(), &width, &height, &orig_format, req_format);
|
||||
if (!data) {
|
||||
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << std::endl;
|
||||
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
||||
} else {
|
||||
printWithDots("Image : ", getFileName(file_path), "[ LOADED ]");
|
||||
}
|
||||
|
||||
int pitch;
|
||||
SDL_PixelFormat pixel_format;
|
||||
// STBI_rgb_alpha (RGBA)
|
||||
pitch = 4 * width;
|
||||
pixel_format = SDL_PIXELFORMAT_RGBA32;
|
||||
|
||||
// Limpia
|
||||
unloadTexture();
|
||||
|
||||
// La textura final
|
||||
SDL_Texture* newTexture = nullptr;
|
||||
|
||||
// Carga la imagen desde una ruta específica
|
||||
auto *loaded_surface = SDL_CreateSurfaceFrom(width, height, pixel_format, static_cast<void *>(data), pitch);
|
||||
if (loaded_surface == nullptr) {
|
||||
std::cout << "Unable to load image " << file_path << std::endl;
|
||||
} else {
|
||||
// Crea la textura desde los pixels de la surface
|
||||
newTexture = SDL_CreateTextureFromSurface(renderer_, loaded_surface);
|
||||
if (newTexture == nullptr) {
|
||||
std::cout << "Unable to create texture from " << file_path << "! SDL Error: " << SDL_GetError() << std::endl;
|
||||
} else {
|
||||
// Obtiene las dimensiones de la imagen
|
||||
width_ = loaded_surface->w;
|
||||
height_ = loaded_surface->h;
|
||||
}
|
||||
|
||||
// Elimina la textura cargada
|
||||
SDL_DestroySurface(loaded_surface);
|
||||
}
|
||||
|
||||
// Return success
|
||||
stbi_image_free(data);
|
||||
texture_ = newTexture;
|
||||
return texture_ != nullptr;
|
||||
}
|
||||
|
||||
// Crea una textura en blanco
|
||||
auto Texture::createBlank(int width, int height, SDL_PixelFormat format, SDL_TextureAccess access) -> bool {
|
||||
// Crea una textura sin inicializar
|
||||
texture_ = SDL_CreateTexture(renderer_, format, access, width, height);
|
||||
if (texture_ == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create blank texture! SDL Error: %s", SDL_GetError());
|
||||
} else {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
return texture_ != nullptr;
|
||||
}
|
||||
|
||||
// Libera la memoria de la textura
|
||||
void Texture::unloadTexture() {
|
||||
// Libera la textura
|
||||
if (texture_) {
|
||||
SDL_DestroyTexture(texture_);
|
||||
texture_ = nullptr;
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el color para la modulacion
|
||||
void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) { SDL_SetTextureColorMod(texture_, red, green, blue); }
|
||||
void Texture::setColor(Color color) { SDL_SetTextureColorMod(texture_, color.r, color.g, color.b); }
|
||||
|
||||
// Establece el blending
|
||||
void Texture::setBlendMode(SDL_BlendMode blending) { SDL_SetTextureBlendMode(texture_, blending); }
|
||||
|
||||
// Establece el alpha para la modulación
|
||||
void Texture::setAlpha(Uint8 alpha) { SDL_SetTextureAlphaMod(texture_, alpha); }
|
||||
|
||||
// Renderiza la textura en un punto específico
|
||||
void Texture::render(float x, float y, SDL_FRect* clip, float zoomW, float zoomH, double angle, SDL_FPoint* center, SDL_FlipMode flip) {
|
||||
// Establece el destino de renderizado en la pantalla
|
||||
SDL_FRect render_quad = {x, y, width_, height_};
|
||||
|
||||
// Obtiene las dimesiones del clip de renderizado
|
||||
if (clip != nullptr) {
|
||||
render_quad.w = clip->w;
|
||||
render_quad.h = clip->h;
|
||||
}
|
||||
|
||||
// Calcula el zoom y las coordenadas
|
||||
if (zoomH != 1.0f || zoomW != 1.0f) {
|
||||
render_quad.x = render_quad.x + (render_quad.w / 2);
|
||||
render_quad.y = render_quad.y + (render_quad.h / 2);
|
||||
render_quad.w = render_quad.w * zoomW;
|
||||
render_quad.h = render_quad.h * zoomH;
|
||||
render_quad.x = render_quad.x - (render_quad.w / 2);
|
||||
render_quad.y = render_quad.y - (render_quad.h / 2);
|
||||
}
|
||||
|
||||
// Renderiza a pantalla
|
||||
SDL_RenderTextureRotated(renderer_, texture_, clip, &render_quad, angle, center, flip);
|
||||
}
|
||||
|
||||
// Establece la textura como objetivo de renderizado
|
||||
void Texture::setAsRenderTarget(SDL_Renderer* renderer) { SDL_SetRenderTarget(renderer, texture_); }
|
||||
|
||||
// Obtiene el ancho de la imagen
|
||||
int Texture::getWidth() { return width_; }
|
||||
|
||||
// Obtiene el alto de la imagen
|
||||
int Texture::getHeight() { return height_; }
|
||||
|
||||
// Recarga la textura
|
||||
bool Texture::reLoad() { return loadFromFile(path_); }
|
||||
|
||||
// Obtiene la textura
|
||||
SDL_Texture* Texture::getSDLTexture() { return texture_; }
|
||||
|
||||
// Obtiene el renderizador
|
||||
SDL_Renderer* Texture::getRenderer() { return renderer_; }
|
||||
67
source2/Core/Rendering/texture.h
Normal file
67
source2/Core/Rendering/texture.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
struct Color; // lines 11-11
|
||||
|
||||
class Texture {
|
||||
private:
|
||||
// Objetos y punteros
|
||||
SDL_Renderer* renderer_; // Renderizador donde dibujar la textura
|
||||
SDL_Texture* texture_ = nullptr; // La textura
|
||||
|
||||
// Variables
|
||||
std::string path_; // Ruta de la imagen de la textura
|
||||
float width_ = 0.0F; // Ancho de la imagen
|
||||
float height_ = 0.0F; // Alto de la imagen
|
||||
std::vector<std::vector<Uint32>> palettes_; // Vector con las diferentes paletas
|
||||
|
||||
// Libera la memoria de la textura
|
||||
void unloadTexture();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
explicit Texture(SDL_Renderer* renderer, const std::string& path = std::string());
|
||||
|
||||
// Destructor
|
||||
~Texture();
|
||||
|
||||
// Carga una imagen desde un fichero
|
||||
bool loadFromFile(const std::string& path);
|
||||
|
||||
// Crea una textura en blanco
|
||||
auto createBlank(int width, int height, SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA8888, SDL_TextureAccess access = SDL_TEXTUREACCESS_STREAMING) -> bool;
|
||||
|
||||
// Establece el color para la modulacion
|
||||
void setColor(Uint8 red, Uint8 green, Uint8 blue);
|
||||
void setColor(Color color);
|
||||
|
||||
// Establece el blending
|
||||
void setBlendMode(SDL_BlendMode blending);
|
||||
|
||||
// Establece el alpha para la modulación
|
||||
void setAlpha(Uint8 alpha);
|
||||
|
||||
// Renderiza la textura en un punto específico
|
||||
void render(float x, float y, SDL_FRect* clip = nullptr, float zoomW = 1, float zoomH = 1, double angle = 0.0, SDL_FPoint* center = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
|
||||
|
||||
// Establece la textura como objetivo de renderizado
|
||||
void setAsRenderTarget(SDL_Renderer* renderer);
|
||||
|
||||
// Obtiene el ancho de la imagen
|
||||
int getWidth();
|
||||
|
||||
// Obtiene el alto de la imagen
|
||||
int getHeight();
|
||||
|
||||
// Recarga la textura
|
||||
bool reLoad();
|
||||
|
||||
// Obtiene la textura
|
||||
SDL_Texture* getSDLTexture();
|
||||
|
||||
// Obtiene el renderizador
|
||||
SDL_Renderer* getRenderer();
|
||||
};
|
||||
179
source2/Core/Resources/asset.cpp
Normal file
179
source2/Core/Resources/asset.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include "asset.h"
|
||||
#include <algorithm> // Para find_if, max
|
||||
#include <fstream> // Para basic_ostream, operator<<, basic_ifstream, endl
|
||||
#include <iostream> // Para cout
|
||||
#include <string> // Para allocator, char_traits, string, operator+, oper...
|
||||
#include "utils.h" // Para getFileName, printWithDots
|
||||
|
||||
// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
|
||||
Asset *Asset::asset_ = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto asset con esta función estática
|
||||
void Asset::init(const std::string &executable_path)
|
||||
{
|
||||
Asset::asset_ = new Asset(executable_path);
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto asset con esta función estática
|
||||
void Asset::destroy()
|
||||
{
|
||||
delete Asset::asset_;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto asset y podemos trabajar con él
|
||||
Asset *Asset::get()
|
||||
{
|
||||
return Asset::asset_;
|
||||
}
|
||||
|
||||
// Añade un elemento a la lista
|
||||
void Asset::add(const std::string &file, AssetType type, bool required, bool absolute)
|
||||
{
|
||||
file_list_.emplace_back(absolute ? file : executable_path_ + file, type, required);
|
||||
longest_name_ = std::max(longest_name_, static_cast<int>(file_list_.back().file.size()));
|
||||
}
|
||||
|
||||
// Devuelve la ruta completa a un fichero a partir de una cadena
|
||||
std::string Asset::get(const std::string &text) const
|
||||
{
|
||||
auto it = std::find_if(file_list_.begin(), file_list_.end(),
|
||||
[&text](const auto &f)
|
||||
{
|
||||
return getFileName(f.file) == text;
|
||||
});
|
||||
|
||||
if (it != file_list_.end())
|
||||
{
|
||||
return it->file;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Warning: file " << text << " not found" << std::endl;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba que existen todos los elementos
|
||||
bool Asset::check() const
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
std::cout << "\n** CHECKING FILES" << std::endl;
|
||||
|
||||
// std::cout << "Executable path is: " << executable_path_ << std::endl;
|
||||
// std::cout << "Sample filepath: " << file_list_.back().file << std::endl;
|
||||
|
||||
// Comprueba la lista de ficheros clasificandolos por tipo
|
||||
for (int type = 0; type < static_cast<int>(AssetType::MAX_ASSET_TYPE); ++type)
|
||||
{
|
||||
// Comprueba si hay ficheros de ese tipo
|
||||
bool any = false;
|
||||
|
||||
for (const auto &f : file_list_)
|
||||
{
|
||||
if (f.required && f.type == static_cast<AssetType>(type))
|
||||
{
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Si hay ficheros de ese tipo, comprueba si existen
|
||||
if (any)
|
||||
{
|
||||
std::cout << "\n>> " << getTypeName(static_cast<AssetType>(type)).c_str() << " FILES" << std::endl;
|
||||
|
||||
for (const auto &f : file_list_)
|
||||
{
|
||||
if (f.required && f.type == static_cast<AssetType>(type))
|
||||
{
|
||||
success &= checkFile(f.file);
|
||||
}
|
||||
}
|
||||
if (success)
|
||||
std::cout << " All files are OK." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Resultado
|
||||
std::cout << (success ? "\n** CHECKING FILES COMPLETED.\n" : "\n** CHECKING FILES FAILED.\n") << std::endl;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Comprueba que existe un fichero
|
||||
bool Asset::checkFile(const std::string &path) const
|
||||
{
|
||||
std::ifstream file(path);
|
||||
bool success = file.good();
|
||||
file.close();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
printWithDots("Checking file : ", getFileName(path), "[ ERROR ]");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Devuelve el nombre del tipo de recurso
|
||||
std::string Asset::getTypeName(AssetType type) const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case AssetType::DATA:
|
||||
return "DATA";
|
||||
break;
|
||||
|
||||
case AssetType::BITMAP:
|
||||
return "BITMAP";
|
||||
break;
|
||||
|
||||
case AssetType::ANIMATION:
|
||||
return "ANIMATION";
|
||||
break;
|
||||
|
||||
case AssetType::MUSIC:
|
||||
return "MUSIC";
|
||||
break;
|
||||
|
||||
case AssetType::SOUND:
|
||||
return "SOUND";
|
||||
break;
|
||||
|
||||
case AssetType::FONT:
|
||||
return "FONT";
|
||||
break;
|
||||
|
||||
case AssetType::ROOM:
|
||||
return "ROOM";
|
||||
break;
|
||||
|
||||
case AssetType::TILEMAP:
|
||||
return "TILEMAP";
|
||||
break;
|
||||
|
||||
case AssetType::PALETTE:
|
||||
return "PALETTE";
|
||||
break;
|
||||
|
||||
default:
|
||||
return "ERROR";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Devuelve la lista de recursos de un tipo
|
||||
std::vector<std::string> Asset::getListByType(AssetType type) const
|
||||
{
|
||||
std::vector<std::string> list;
|
||||
|
||||
for (auto f : file_list_)
|
||||
{
|
||||
if (f.type == type)
|
||||
{
|
||||
list.push_back(f.file);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
79
source2/Core/Resources/asset.h
Normal file
79
source2/Core/Resources/asset.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // para string, basic_string
|
||||
#include <vector> // para vector
|
||||
#include "utils.h"
|
||||
|
||||
enum class AssetType : int
|
||||
{
|
||||
DATA,
|
||||
BITMAP,
|
||||
ANIMATION,
|
||||
MUSIC,
|
||||
SOUND,
|
||||
FONT,
|
||||
ROOM,
|
||||
TILEMAP,
|
||||
PALETTE,
|
||||
MAX_ASSET_TYPE
|
||||
};
|
||||
|
||||
// Clase Asset
|
||||
class Asset
|
||||
{
|
||||
private:
|
||||
// [SINGLETON] Objeto asset privado para Don Melitón
|
||||
static Asset *asset_;
|
||||
|
||||
// Estructura para definir un item
|
||||
struct AssetItem
|
||||
{
|
||||
std::string file; // Ruta del fichero desde la raíz del directorio
|
||||
AssetType type; // Indica el tipo de recurso
|
||||
bool required; // Indica si es un fichero que debe de existir
|
||||
|
||||
// Constructor
|
||||
AssetItem(const std::string &filePath, AssetType assetType, bool isRequired)
|
||||
: file(filePath), type(assetType), required(isRequired) {}
|
||||
};
|
||||
|
||||
// Variables
|
||||
int longest_name_ = 0; // Contiene la longitud del nombre de fichero mas largo
|
||||
std::vector<AssetItem> file_list_; // Listado con todas las rutas a los ficheros
|
||||
std::string executable_path_; // Ruta al ejecutable
|
||||
|
||||
// Comprueba que existe un fichero
|
||||
bool checkFile(const std::string &path) const;
|
||||
|
||||
// Devuelve el nombre del tipo de recurso
|
||||
std::string getTypeName(AssetType type) const;
|
||||
|
||||
// Constructor
|
||||
explicit Asset(const std::string &executable_path)
|
||||
: executable_path_(getPath(executable_path)) {}
|
||||
|
||||
// Destructor
|
||||
~Asset() = default;
|
||||
|
||||
public:
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
static void init(const std::string &executable_path);
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
static void destroy();
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
static Asset *get();
|
||||
|
||||
// Añade un elemento a la lista
|
||||
void add(const std::string &file, AssetType type, bool required = true, bool absolute = false);
|
||||
|
||||
// Devuelve la ruta completa a un fichero a partir de una cadena
|
||||
std::string get(const std::string &text) const;
|
||||
|
||||
// Comprueba que existen todos los elementos
|
||||
bool check() const;
|
||||
|
||||
// Devuelve la lista de recursos de un tipo
|
||||
std::vector<std::string> getListByType(AssetType type) const;
|
||||
};
|
||||
418
source2/Core/Resources/resource.cpp
Normal file
418
source2/Core/Resources/resource.cpp
Normal file
@@ -0,0 +1,418 @@
|
||||
#include "resource.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdlib.h> // Para exit, size_t
|
||||
|
||||
#include <algorithm> // Para find_if
|
||||
#include <iostream> // Para basic_ostream, operator<<, endl, cout
|
||||
#include <stdexcept> // Para runtime_error
|
||||
|
||||
#include "asset.h" // Para AssetType, Asset
|
||||
#include "external/jail_audio.h" // Para JA_DeleteMusic, JA_DeleteSound, JA_Loa...
|
||||
#include "options.h" // Para Options, OptionsGame, options
|
||||
#include "room.h" // Para RoomData, loadRoomFile, loadRoomTileFile
|
||||
#include "screen.h" // Para Screen
|
||||
#include "text.h" // Para Text, loadTextFile
|
||||
#include "utils.h" // Para getFileName, printWithDots, PaletteColor
|
||||
struct JA_Music_t; // lines 17-17
|
||||
struct JA_Sound_t; // lines 18-18
|
||||
|
||||
// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
|
||||
Resource* Resource::resource_ = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto screen con esta función estática
|
||||
void Resource::init() { Resource::resource_ = new Resource(); }
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto screen con esta función estática
|
||||
void Resource::destroy() { delete Resource::resource_; }
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto screen y podemos trabajar con él
|
||||
Resource* Resource::get() { return Resource::resource_; }
|
||||
|
||||
// Constructor
|
||||
Resource::Resource() { load(); }
|
||||
|
||||
// Vacia todos los vectores de recursos
|
||||
void Resource::clear() {
|
||||
clearSounds();
|
||||
clearMusics();
|
||||
surfaces_.clear();
|
||||
palettes_.clear();
|
||||
text_files_.clear();
|
||||
texts_.clear();
|
||||
animations_.clear();
|
||||
}
|
||||
|
||||
// Carga todos los recursos
|
||||
void Resource::load() {
|
||||
calculateTotal();
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
std::cout << "** LOADING RESOURCES" << std::endl;
|
||||
loadSounds();
|
||||
loadMusics();
|
||||
loadSurfaces();
|
||||
loadPalettes();
|
||||
loadTextFiles();
|
||||
loadAnimations();
|
||||
loadTileMaps();
|
||||
loadRooms();
|
||||
createText();
|
||||
std::cout << "\n** RESOURCES LOADED" << std::endl;
|
||||
}
|
||||
|
||||
// Recarga todos los recursos
|
||||
void Resource::reload() {
|
||||
clear();
|
||||
load();
|
||||
}
|
||||
|
||||
// Obtiene el sonido a partir de un nombre
|
||||
JA_Sound_t* Resource::getSound(const std::string& name) {
|
||||
auto it = std::find_if(sounds_.begin(), sounds_.end(), [&name](const auto& s) { return s.name == name; });
|
||||
|
||||
if (it != sounds_.end()) {
|
||||
return it->sound;
|
||||
}
|
||||
|
||||
std::cerr << "Error: Sonido no encontrado " << name << std::endl;
|
||||
throw std::runtime_error("Sonido no encontrado: " + name);
|
||||
}
|
||||
|
||||
// Obtiene la música a partir de un nombre
|
||||
JA_Music_t* Resource::getMusic(const std::string& name) {
|
||||
auto it = std::find_if(musics_.begin(), musics_.end(), [&name](const auto& m) { return m.name == name; });
|
||||
|
||||
if (it != musics_.end()) {
|
||||
return it->music;
|
||||
}
|
||||
|
||||
std::cerr << "Error: Música no encontrada " << name << std::endl;
|
||||
throw std::runtime_error("Música no encontrada: " + name);
|
||||
}
|
||||
|
||||
// Obtiene la surface a partir de un nombre
|
||||
std::shared_ptr<Surface> Resource::getSurface(const std::string& name) {
|
||||
auto it = std::find_if(surfaces_.begin(), surfaces_.end(), [&name](const auto& t) { return t.name == name; });
|
||||
|
||||
if (it != surfaces_.end()) {
|
||||
return it->surface;
|
||||
}
|
||||
|
||||
std::cerr << "Error: Imagen no encontrada " << name << std::endl;
|
||||
throw std::runtime_error("Imagen no encontrada: " + name);
|
||||
}
|
||||
|
||||
// Obtiene la paleta a partir de un nombre
|
||||
Palette Resource::getPalette(const std::string& name) {
|
||||
auto it = std::find_if(palettes_.begin(), palettes_.end(), [&name](const auto& t) { return t.name == name; });
|
||||
|
||||
if (it != palettes_.end()) {
|
||||
return it->palette;
|
||||
}
|
||||
|
||||
std::cerr << "Error: Paleta no encontrada " << name << std::endl;
|
||||
throw std::runtime_error("Paleta no encontrada: " + name);
|
||||
}
|
||||
|
||||
// Obtiene el fichero de texto a partir de un nombre
|
||||
std::shared_ptr<TextFile> Resource::getTextFile(const std::string& name) {
|
||||
auto it = std::find_if(text_files_.begin(), text_files_.end(), [&name](const auto& t) { return t.name == name; });
|
||||
|
||||
if (it != text_files_.end()) {
|
||||
return it->text_file;
|
||||
}
|
||||
|
||||
std::cerr << "Error: TextFile no encontrado " << name << std::endl;
|
||||
throw std::runtime_error("TextFile no encontrado: " + name);
|
||||
}
|
||||
|
||||
// Obtiene el objeto de texto a partir de un nombre
|
||||
std::shared_ptr<Text> Resource::getText(const std::string& name) {
|
||||
auto it = std::find_if(texts_.begin(), texts_.end(), [&name](const auto& t) { return t.name == name; });
|
||||
|
||||
if (it != texts_.end()) {
|
||||
return it->text;
|
||||
}
|
||||
|
||||
std::cerr << "Error: Text no encontrado " << name << std::endl;
|
||||
throw std::runtime_error("Texto no encontrado: " + name);
|
||||
}
|
||||
|
||||
// Obtiene la animación a partir de un nombre
|
||||
Animations& Resource::getAnimations(const std::string& name) {
|
||||
auto it = std::find_if(animations_.begin(), animations_.end(), [&name](const auto& a) { return a.name == name; });
|
||||
|
||||
if (it != animations_.end()) {
|
||||
return it->animation;
|
||||
}
|
||||
|
||||
std::cerr << "Error: Animación no encontrada " << name << std::endl;
|
||||
throw std::runtime_error("Animación no encontrada: " + name);
|
||||
}
|
||||
|
||||
// Obtiene el mapa de tiles a partir de un nombre
|
||||
std::vector<int>& Resource::getTileMap(const std::string& name) {
|
||||
auto it = std::find_if(tile_maps_.begin(), tile_maps_.end(), [&name](const auto& t) { return t.name == name; });
|
||||
|
||||
if (it != tile_maps_.end()) {
|
||||
return it->tileMap;
|
||||
}
|
||||
|
||||
std::cerr << "Error: Mapa de tiles no encontrado " << name << std::endl;
|
||||
throw std::runtime_error("Mapa de tiles no encontrado: " + name);
|
||||
}
|
||||
|
||||
// Obtiene la habitación a partir de un nombre
|
||||
std::shared_ptr<RoomData> Resource::getRoom(const std::string& name) {
|
||||
auto it = std::find_if(rooms_.begin(), rooms_.end(), [&name](const auto& r) { return r.name == name; });
|
||||
|
||||
if (it != rooms_.end()) {
|
||||
return it->room;
|
||||
}
|
||||
|
||||
std::cerr << "Error: Habitación no encontrada " << name << std::endl;
|
||||
throw std::runtime_error("Habitación no encontrada: " + name);
|
||||
}
|
||||
|
||||
// Obtiene todas las habitaciones
|
||||
std::vector<ResourceRoom>& Resource::getRooms() {
|
||||
return rooms_;
|
||||
}
|
||||
|
||||
// Carga los sonidos
|
||||
void Resource::loadSounds() {
|
||||
std::cout << "\n>> SOUND FILES" << std::endl;
|
||||
auto list = Asset::get()->getListByType(AssetType::SOUND);
|
||||
sounds_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
sounds_.emplace_back(ResourceSound(name, JA_LoadSound(l.c_str())));
|
||||
printWithDots("Sound : ", name, "[ LOADED ]");
|
||||
updateLoadingProgress();
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las musicas
|
||||
void Resource::loadMusics() {
|
||||
std::cout << "\n>> MUSIC FILES" << std::endl;
|
||||
auto list = Asset::get()->getListByType(AssetType::MUSIC);
|
||||
musics_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
musics_.emplace_back(ResourceMusic(name, JA_LoadMusic(l.c_str())));
|
||||
printWithDots("Music : ", name, "[ LOADED ]");
|
||||
updateLoadingProgress(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las texturas
|
||||
void Resource::loadSurfaces() {
|
||||
std::cout << "\n>> SURFACES" << std::endl;
|
||||
auto list = Asset::get()->getListByType(AssetType::BITMAP);
|
||||
surfaces_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
surfaces_.emplace_back(ResourceSurface(name, std::make_shared<Surface>(l)));
|
||||
surfaces_.back().surface->setTransparentColor(0);
|
||||
updateLoadingProgress();
|
||||
}
|
||||
|
||||
// Reconfigura el color transparente de algunas surfaces
|
||||
getSurface("loading_screen_color.gif")->setTransparentColor();
|
||||
getSurface("ending1.gif")->setTransparentColor();
|
||||
getSurface("ending2.gif")->setTransparentColor();
|
||||
getSurface("ending3.gif")->setTransparentColor();
|
||||
getSurface("ending4.gif")->setTransparentColor();
|
||||
getSurface("ending5.gif")->setTransparentColor();
|
||||
getSurface("standard.gif")->setTransparentColor(16);
|
||||
}
|
||||
|
||||
// Carga las paletas
|
||||
void Resource::loadPalettes() {
|
||||
std::cout << "\n>> PALETTES" << std::endl;
|
||||
auto list = Asset::get()->getListByType(AssetType::PALETTE);
|
||||
palettes_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
palettes_.emplace_back(ResourcePalette(name, readPalFile(l)));
|
||||
updateLoadingProgress();
|
||||
}
|
||||
}
|
||||
|
||||
// Carga los ficheros de texto
|
||||
void Resource::loadTextFiles() {
|
||||
std::cout << "\n>> TEXT FILES" << std::endl;
|
||||
auto list = Asset::get()->getListByType(AssetType::FONT);
|
||||
text_files_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
text_files_.emplace_back(ResourceTextFile(name, loadTextFile(l)));
|
||||
updateLoadingProgress();
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las animaciones
|
||||
void Resource::loadAnimations() {
|
||||
std::cout << "\n>> ANIMATIONS" << std::endl;
|
||||
auto list = Asset::get()->getListByType(AssetType::ANIMATION);
|
||||
animations_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
animations_.emplace_back(ResourceAnimation(name, loadAnimationsFromFile(l)));
|
||||
updateLoadingProgress();
|
||||
}
|
||||
}
|
||||
|
||||
// Carga los mapas de tiles
|
||||
void Resource::loadTileMaps() {
|
||||
std::cout << "\n>> TILE MAPS" << std::endl;
|
||||
auto list = Asset::get()->getListByType(AssetType::TILEMAP);
|
||||
tile_maps_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
tile_maps_.emplace_back(ResourceTileMap(name, loadRoomTileFile(l)));
|
||||
printWithDots("TileMap : ", name, "[ LOADED ]");
|
||||
updateLoadingProgress();
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las habitaciones
|
||||
void Resource::loadRooms() {
|
||||
std::cout << "\n>> ROOMS" << std::endl;
|
||||
auto list = Asset::get()->getListByType(AssetType::ROOM);
|
||||
rooms_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
rooms_.emplace_back(ResourceRoom(name, std::make_shared<RoomData>(loadRoomFile(l))));
|
||||
printWithDots("Room : ", name, "[ LOADED ]");
|
||||
updateLoadingProgress();
|
||||
}
|
||||
}
|
||||
|
||||
void Resource::createText() {
|
||||
struct ResourceInfo {
|
||||
std::string key; // Identificador del recurso
|
||||
std::string textureFile; // Nombre del archivo de textura
|
||||
std::string textFile; // Nombre del archivo de texto
|
||||
|
||||
// Constructor para facilitar la creación de objetos ResourceInfo
|
||||
ResourceInfo(const std::string& k, const std::string& tFile, const std::string& txtFile)
|
||||
: key(k),
|
||||
textureFile(tFile),
|
||||
textFile(txtFile) {}
|
||||
};
|
||||
|
||||
std::cout << "\n>> CREATING TEXT_OBJECTS" << std::endl;
|
||||
|
||||
std::vector<ResourceInfo> resources = {
|
||||
{"debug", "debug.gif", "debug.txt"},
|
||||
{"gauntlet", "gauntlet.gif", "gauntlet.txt"},
|
||||
{"smb2", "smb2.gif", "smb2.txt"},
|
||||
{"subatomic", "subatomic.gif", "subatomic.txt"},
|
||||
{"8bithud", "8bithud.gif", "8bithud.txt"}};
|
||||
|
||||
for (const auto& resource : resources) {
|
||||
texts_.emplace_back(ResourceText(resource.key, std::make_shared<Text>(getSurface(resource.textureFile), getTextFile(resource.textFile))));
|
||||
printWithDots("Text : ", resource.key, "[ DONE ]");
|
||||
}
|
||||
}
|
||||
|
||||
// Vacía el vector de sonidos
|
||||
void Resource::clearSounds() {
|
||||
// Itera sobre el vector y libera los recursos asociados a cada JA_Sound_t
|
||||
for (auto& sound : sounds_) {
|
||||
if (sound.sound) {
|
||||
JA_DeleteSound(sound.sound);
|
||||
sound.sound = nullptr;
|
||||
}
|
||||
}
|
||||
sounds_.clear(); // Limpia el vector después de liberar todos los recursos
|
||||
}
|
||||
|
||||
// Vacía el vector de musicas
|
||||
void Resource::clearMusics() {
|
||||
// Itera sobre el vector y libera los recursos asociados a cada JA_Music_t
|
||||
for (auto& music : musics_) {
|
||||
if (music.music) {
|
||||
JA_DeleteMusic(music.music);
|
||||
music.music = nullptr;
|
||||
}
|
||||
}
|
||||
musics_.clear(); // Limpia el vector después de liberar todos los recursos
|
||||
}
|
||||
|
||||
// Calcula el numero de recursos para cargar
|
||||
void Resource::calculateTotal() {
|
||||
std::vector<AssetType> assetTypes = {
|
||||
AssetType::SOUND,
|
||||
AssetType::MUSIC,
|
||||
AssetType::BITMAP,
|
||||
AssetType::PALETTE,
|
||||
AssetType::FONT,
|
||||
AssetType::ANIMATION,
|
||||
AssetType::TILEMAP,
|
||||
AssetType::ROOM};
|
||||
|
||||
size_t total = 0;
|
||||
for (const auto& assetType : assetTypes) {
|
||||
auto list = Asset::get()->getListByType(assetType);
|
||||
total += list.size();
|
||||
}
|
||||
|
||||
count_ = ResourceCount(total, 0);
|
||||
}
|
||||
|
||||
// Muestra el progreso de carga
|
||||
void Resource::renderProgress() {
|
||||
constexpr float X_PADDING = 10;
|
||||
constexpr float Y_PADDING = 10;
|
||||
constexpr float BAR_HEIGHT = 10;
|
||||
const float bar_position = options.game.height - BAR_HEIGHT - Y_PADDING;
|
||||
Screen::get()->start();
|
||||
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
auto surface = Screen::get()->getRendererSurface();
|
||||
const float WIRED_BAR_WIDTH = options.game.width - (X_PADDING * 2);
|
||||
SDL_FRect rect_wired = {X_PADDING, bar_position, WIRED_BAR_WIDTH, X_PADDING};
|
||||
surface->drawRectBorder(&rect_wired, static_cast<Uint8>(PaletteColor::WHITE));
|
||||
|
||||
const float FULL_BAR_WIDTH = WIRED_BAR_WIDTH * count_.getPercentage();
|
||||
SDL_FRect rect_full = {X_PADDING, bar_position, FULL_BAR_WIDTH, X_PADDING};
|
||||
surface->fillRect(&rect_full, static_cast<Uint8>(PaletteColor::WHITE));
|
||||
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Comprueba los eventos de la pantalla de carga
|
||||
void Resource::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
exit(0);
|
||||
break;
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
if (event.key.key == SDLK_ESCAPE) {
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el progreso de carga
|
||||
void Resource::updateLoadingProgress(int steps) {
|
||||
count_.add(1);
|
||||
if (count_.loaded % steps == 0 || count_.loaded == count_.total) {
|
||||
renderProgress();
|
||||
}
|
||||
checkEvents();
|
||||
}
|
||||
257
source2/Core/Resources/resource.h
Normal file
257
source2/Core/Resources/resource.h
Normal file
@@ -0,0 +1,257 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "room.h" // Para room_t
|
||||
#include "sprite/surface_animated_sprite.h" // Para AnimationsFileBuffer
|
||||
#include "surface.h" // Para Surface
|
||||
#include "text.h" // Para Text, TextFile
|
||||
struct JA_Music_t; // lines 11-11
|
||||
struct JA_Sound_t; // lines 12-12
|
||||
|
||||
// Estructura para almacenar ficheros de sonido y su nombre
|
||||
struct ResourceSound {
|
||||
std::string name; // Nombre del sonido
|
||||
JA_Sound_t* sound; // Objeto con el sonido
|
||||
|
||||
// Constructor
|
||||
ResourceSound(const std::string& name, JA_Sound_t* sound)
|
||||
: name(name),
|
||||
sound(sound) {}
|
||||
};
|
||||
|
||||
// Estructura para almacenar ficheros musicales y su nombre
|
||||
struct ResourceMusic {
|
||||
std::string name; // Nombre de la musica
|
||||
JA_Music_t* music; // Objeto con la música
|
||||
|
||||
// Constructor
|
||||
ResourceMusic(const std::string& name, JA_Music_t* music)
|
||||
: name(name),
|
||||
music(music) {}
|
||||
};
|
||||
|
||||
// Estructura para almacenar objetos Surface y su nombre
|
||||
struct ResourceSurface {
|
||||
std::string name; // Nombre de la surface
|
||||
std::shared_ptr<Surface> surface; // Objeto con la surface
|
||||
|
||||
// Constructor
|
||||
ResourceSurface(const std::string& name, std::shared_ptr<Surface> surface)
|
||||
: name(name),
|
||||
surface(surface) {}
|
||||
};
|
||||
|
||||
// Estructura para almacenar objetos Palette y su nombre
|
||||
struct ResourcePalette {
|
||||
std::string name; // Nombre de la surface
|
||||
Palette palette; // Paleta
|
||||
|
||||
// Constructor
|
||||
ResourcePalette(const std::string& name, Palette palette)
|
||||
: name(name),
|
||||
palette(palette) {}
|
||||
};
|
||||
|
||||
// Estructura para almacenar ficheros TextFile y su nombre
|
||||
struct ResourceTextFile {
|
||||
std::string name; // Nombre del fichero
|
||||
std::shared_ptr<TextFile> text_file; // Objeto con los descriptores de la fuente de texto
|
||||
|
||||
// Constructor
|
||||
ResourceTextFile(const std::string& name, std::shared_ptr<TextFile> text_file)
|
||||
: name(name),
|
||||
text_file(text_file) {}
|
||||
};
|
||||
|
||||
// Estructura para almacenar objetos Text y su nombre
|
||||
struct ResourceText {
|
||||
std::string name; // Nombre del objeto
|
||||
std::shared_ptr<Text> text; // Objeto
|
||||
|
||||
// Constructor
|
||||
ResourceText(const std::string& name, std::shared_ptr<Text> text)
|
||||
: name(name),
|
||||
text(text) {}
|
||||
};
|
||||
|
||||
// Estructura para almacenar ficheros animaciones y su nombre
|
||||
struct ResourceAnimation {
|
||||
std::string name; // Nombre del fichero
|
||||
Animations animation; // Objeto con las animaciones
|
||||
|
||||
// Constructor
|
||||
ResourceAnimation(const std::string& name, const Animations& animation)
|
||||
: name(name),
|
||||
animation(animation) {}
|
||||
};
|
||||
|
||||
// Estructura para almacenar ficheros con el mapa de tiles de una habitación y su nombre
|
||||
struct ResourceTileMap {
|
||||
std::string name; // Nombre del mapa de tiles
|
||||
std::vector<int> tileMap; // Vector con los indices del mapa de tiles
|
||||
|
||||
// Constructor
|
||||
ResourceTileMap(const std::string& name, const std::vector<int>& tileMap)
|
||||
: name(name),
|
||||
tileMap(tileMap) {}
|
||||
};
|
||||
|
||||
// Estructura para almacenar habitaciones y su nombre
|
||||
struct ResourceRoom {
|
||||
std::string name; // Nombre de la habitación
|
||||
std::shared_ptr<RoomData> room; // Habitación
|
||||
|
||||
// Constructor
|
||||
ResourceRoom(const std::string& name, std::shared_ptr<RoomData> room)
|
||||
: name(name),
|
||||
room(room) {}
|
||||
};
|
||||
|
||||
// Estructura para llevar la cuenta de los recursos cargados
|
||||
struct ResourceCount {
|
||||
int total; // Número total de recursos
|
||||
int loaded; // Número de recursos cargados
|
||||
|
||||
// Constructor
|
||||
ResourceCount()
|
||||
: total(0),
|
||||
loaded(0) {}
|
||||
|
||||
// Constructor
|
||||
ResourceCount(int total, int loaded)
|
||||
: total(total),
|
||||
loaded(loaded) {}
|
||||
|
||||
// Añade una cantidad a los recursos cargados
|
||||
void add(int amount) {
|
||||
loaded += amount;
|
||||
}
|
||||
|
||||
// Obtiene el porcentaje de recursos cargados
|
||||
float getPercentage() {
|
||||
return static_cast<float>(loaded) / static_cast<float>(total);
|
||||
}
|
||||
};
|
||||
|
||||
class Resource {
|
||||
private:
|
||||
// [SINGLETON] Objeto resource privado para Don Melitón
|
||||
static Resource* resource_;
|
||||
|
||||
std::vector<ResourceSound> sounds_; // Vector con los sonidos
|
||||
std::vector<ResourceMusic> musics_; // Vector con las musicas
|
||||
std::vector<ResourceSurface> surfaces_; // Vector con las surfaces
|
||||
std::vector<ResourcePalette> palettes_; // Vector con las paletas
|
||||
std::vector<ResourceTextFile> text_files_; // Vector con los ficheros de texto
|
||||
std::vector<ResourceText> texts_; // Vector con los objetos de texto
|
||||
std::vector<ResourceAnimation> animations_; // Vector con las animaciones
|
||||
std::vector<ResourceTileMap> tile_maps_; // Vector con los mapas de tiles
|
||||
std::vector<ResourceRoom> rooms_; // Vector con las habitaciones
|
||||
|
||||
ResourceCount count_; // Contador de recursos
|
||||
|
||||
// Carga los sonidos
|
||||
void loadSounds();
|
||||
|
||||
// Carga las musicas
|
||||
void loadMusics();
|
||||
|
||||
// Carga las surfaces
|
||||
void loadSurfaces();
|
||||
|
||||
// Carga las paletas
|
||||
void loadPalettes();
|
||||
|
||||
// Carga los ficheros de texto
|
||||
void loadTextFiles();
|
||||
|
||||
// Carga las animaciones
|
||||
void loadAnimations();
|
||||
|
||||
// Carga los mapas de tiles
|
||||
void loadTileMaps();
|
||||
|
||||
// Carga las habitaciones
|
||||
void loadRooms();
|
||||
|
||||
// Crea los objetos de texto
|
||||
void createText();
|
||||
|
||||
// Vacia todos los vectores de recursos
|
||||
void clear();
|
||||
|
||||
// Carga todos los recursos
|
||||
void load();
|
||||
|
||||
// Vacía el vector de sonidos
|
||||
void clearSounds();
|
||||
|
||||
// Vacía el vector de musicas
|
||||
void clearMusics();
|
||||
|
||||
// Calcula el numero de recursos para cargar
|
||||
void calculateTotal();
|
||||
|
||||
// Muestra el progreso de carga
|
||||
void renderProgress();
|
||||
|
||||
// Comprueba los eventos
|
||||
void checkEvents();
|
||||
|
||||
// Actualiza el progreso de carga
|
||||
void updateLoadingProgress(int steps = 5);
|
||||
|
||||
// [SINGLETON] Ahora el constructor y el destructor son privados, para no poder crear objetos resource desde fuera
|
||||
|
||||
// Constructor
|
||||
Resource();
|
||||
|
||||
// Destructor
|
||||
~Resource() = default;
|
||||
|
||||
public:
|
||||
// [SINGLETON] Crearemos el objeto resource con esta función estática
|
||||
static void init();
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto resource con esta función estática
|
||||
static void destroy();
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto resource y podemos trabajar con él
|
||||
static Resource* get();
|
||||
|
||||
// Obtiene el sonido a partir de un nombre
|
||||
JA_Sound_t* getSound(const std::string& name);
|
||||
|
||||
// Obtiene la música a partir de un nombre
|
||||
JA_Music_t* getMusic(const std::string& name);
|
||||
|
||||
// Obtiene la surface a partir de un nombre
|
||||
std::shared_ptr<Surface> getSurface(const std::string& name);
|
||||
|
||||
// Obtiene la paleta a partir de un nombre
|
||||
Palette getPalette(const std::string& name);
|
||||
|
||||
// Obtiene el fichero de texto a partir de un nombre
|
||||
std::shared_ptr<TextFile> getTextFile(const std::string& name);
|
||||
|
||||
// Obtiene el objeto de texto a partir de un nombre
|
||||
std::shared_ptr<Text> getText(const std::string& name);
|
||||
|
||||
// Obtiene la animación a partir de un nombre
|
||||
Animations& getAnimations(const std::string& name);
|
||||
|
||||
// Obtiene el mapa de tiles a partir de un nombre
|
||||
std::vector<int>& getTileMap(const std::string& name);
|
||||
|
||||
// Obtiene la habitación a partir de un nombre
|
||||
std::shared_ptr<RoomData> getRoom(const std::string& name);
|
||||
|
||||
// Obtiene todas las habitaciones
|
||||
std::vector<ResourceRoom>& getRooms();
|
||||
|
||||
// Recarga todos los recursos
|
||||
void reload();
|
||||
};
|
||||
55
source2/Core/System/debug.cpp
Normal file
55
source2/Core/System/debug.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "debug.h"
|
||||
|
||||
#include <algorithm> // Para max
|
||||
#include <memory> // Para __shared_ptr_access, shared_ptr
|
||||
|
||||
#include "resource.h" // Para Resource
|
||||
#include "text.h" // Para Text
|
||||
#include "utils.h" // Para Color
|
||||
|
||||
// [SINGLETON]
|
||||
Debug* Debug::debug_ = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
void Debug::init() {
|
||||
Debug::debug_ = new Debug();
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
void Debug::destroy() {
|
||||
delete Debug::debug_;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
Debug* Debug::get() {
|
||||
return Debug::debug_;
|
||||
}
|
||||
|
||||
// Dibuja en pantalla
|
||||
void Debug::render() {
|
||||
auto text = Resource::get()->getText("debug");
|
||||
int y = y_;
|
||||
int w = 0;
|
||||
|
||||
for (const auto& s : slot_) {
|
||||
text->write(x_, y, s);
|
||||
w = (std::max(w, (int)s.length()));
|
||||
y += text->getCharacterSize() + 1;
|
||||
if (y > 192 - text->getCharacterSize()) {
|
||||
y = y_;
|
||||
x_ += w * text->getCharacterSize() + 2;
|
||||
}
|
||||
}
|
||||
|
||||
y = 0;
|
||||
for (const auto& l : log_) {
|
||||
text->writeColored(x_ + 10, y, l, static_cast<Uint8>(PaletteColor::WHITE));
|
||||
y += text->getCharacterSize() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece la posición donde se colocará la información de debug
|
||||
void Debug::setPos(SDL_FPoint p) {
|
||||
x_ = p.x;
|
||||
y_ = p.y;
|
||||
}
|
||||
53
source2/Core/System/debug.h
Normal file
53
source2/Core/System/debug.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
// Clase Debug
|
||||
class Debug {
|
||||
private:
|
||||
// [SINGLETON] Objeto privado
|
||||
static Debug* debug_;
|
||||
|
||||
// Variables
|
||||
std::vector<std::string> slot_; // Vector con los textos a escribir
|
||||
std::vector<std::string> log_; // Vector con los textos a escribir
|
||||
int x_ = 0; // Posicion donde escribir el texto de debug
|
||||
int y_ = 0; // Posición donde escribir el texto de debug
|
||||
bool enabled_ = false; // Indica si esta activo el modo debug
|
||||
|
||||
// Constructor
|
||||
Debug() = default;
|
||||
|
||||
// Destructor
|
||||
~Debug() = default;
|
||||
|
||||
public:
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
static void init();
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
static void destroy();
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
static Debug* get();
|
||||
|
||||
// Dibuja en pantalla
|
||||
void render();
|
||||
|
||||
// Establece la posición donde se colocará la información de debug
|
||||
void setPos(SDL_FPoint p);
|
||||
|
||||
// Getters
|
||||
bool getEnabled() { return enabled_; }
|
||||
|
||||
// Setters
|
||||
void add(std::string text) { slot_.push_back(text); }
|
||||
void clear() { slot_.clear(); }
|
||||
void addToLog(std::string text) { log_.push_back(text); }
|
||||
void clearLog() { log_.clear(); }
|
||||
void setEnabled(bool value) { enabled_ = value; }
|
||||
void toggleEnabled() { enabled_ = !enabled_; }
|
||||
};
|
||||
589
source2/Core/System/director.cpp
Normal file
589
source2/Core/System/director.cpp
Normal file
@@ -0,0 +1,589 @@
|
||||
#include "director.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <errno.h> // Para errno, EEXIST, EACCES, ENAMETOO...
|
||||
#include <stdio.h> // Para printf, perror
|
||||
#include <sys/stat.h> // Para mkdir, stat, S_IRWXU
|
||||
#include <unistd.h> // Para getuid
|
||||
|
||||
#include <cstdlib> // Para exit, EXIT_FAILURE, srand
|
||||
#include <iostream> // Para basic_ostream, operator<<, cout
|
||||
#include <memory> // Para make_unique, unique_ptr
|
||||
#include <string> // Para operator+, allocator, char_traits
|
||||
|
||||
#include "asset.h" // Para Asset, AssetType
|
||||
#include "cheevos.h" // Para Cheevos
|
||||
#include "debug.h" // Para Debug
|
||||
#include "defines.h" // Para WINDOW_CAPTION
|
||||
#include "external/jail_audio.h" // Para JA_SetMusicVolume, JA_SetSoundV...
|
||||
#include "input.h" // Para Input, InputAction
|
||||
#include "options.h" // Para Options, options, OptionsVideo
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sections/credits.h" // Para Credits
|
||||
#include "sections/ending.h" // Para Ending
|
||||
#include "sections/ending2.h" // Para Ending2
|
||||
#include "sections/game.h" // Para Game, GameMode
|
||||
#include "sections/game_over.h" // Para GameOver
|
||||
#include "sections/loading_screen.h" // Para LoadingScreen
|
||||
#include "sections/logo.h" // Para Logo
|
||||
#include "sections/title.h" // Para Title
|
||||
#include "ui/notifier.h" // Para Notifier
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
// Constructor
|
||||
Director::Director(int argc, const char* argv[]) {
|
||||
std::cout << "Game start" << std::endl;
|
||||
|
||||
// Crea e inicializa las opciones del programa
|
||||
initOptions();
|
||||
|
||||
// Comprueba los parametros del programa
|
||||
executable_path_ = checkProgramArguments(argc, argv);
|
||||
|
||||
// Crea el objeto que controla los ficheros de recursos
|
||||
Asset::init(executable_path_);
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
createSystemFolder("jailgames");
|
||||
createSystemFolder("jailgames/jaildoctors_dilemma");
|
||||
|
||||
// Si falta algún fichero no inicia el programa
|
||||
if (!setFileList()) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Carga las opciones desde un fichero
|
||||
loadOptionsFromFile(Asset::get()->get("config.txt"));
|
||||
|
||||
// Inicializa JailAudio
|
||||
initJailAudio();
|
||||
|
||||
// Crea los objetos
|
||||
Screen::init();
|
||||
SDL_HideCursor();
|
||||
Resource::init();
|
||||
Notifier::init("", "8bithud");
|
||||
Screen::get()->setNotificationsEnabled(true);
|
||||
Input::init(Asset::get()->get("gamecontrollerdb.txt"));
|
||||
initInput();
|
||||
Debug::init();
|
||||
Cheevos::init(Asset::get()->get("cheevos.bin"));
|
||||
}
|
||||
|
||||
Director::~Director() {
|
||||
// Guarda las opciones a un fichero
|
||||
saveOptionsToFile(Asset::get()->get("config.txt"));
|
||||
|
||||
// Destruye los singletones
|
||||
Cheevos::destroy();
|
||||
Debug::destroy();
|
||||
Input::destroy();
|
||||
Notifier::destroy();
|
||||
Resource::destroy();
|
||||
Screen::destroy();
|
||||
Asset::destroy();
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
std::cout << "\nBye!" << std::endl;
|
||||
}
|
||||
|
||||
// Comprueba los parametros del programa
|
||||
std::string Director::checkProgramArguments(int argc, const char* argv[]) {
|
||||
// Iterar sobre los argumentos del programa
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string argument(argv[i]);
|
||||
|
||||
if (argument == "--console") {
|
||||
options.console = true;
|
||||
} else if (argument == "--infiniteLives") {
|
||||
options.cheats.infinite_lives = Cheat::CheatState::ENABLED;
|
||||
} else if (argument == "--invincible") {
|
||||
options.cheats.invincible = Cheat::CheatState::ENABLED;
|
||||
} else if (argument == "--jailEnabled") {
|
||||
options.cheats.jail_is_open = Cheat::CheatState::ENABLED;
|
||||
} else if (argument == "--altSkin") {
|
||||
options.cheats.alternate_skin = Cheat::CheatState::ENABLED;
|
||||
}
|
||||
}
|
||||
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
void Director::createSystemFolder(const std::string& folder) {
|
||||
#ifdef _WIN32
|
||||
system_folder_ = std::string(getenv("APPDATA")) + "/" + folder;
|
||||
#elif __APPLE__
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
const char* homedir = pw->pw_dir;
|
||||
system_folder_ = std::string(homedir) + "/Library/Application Support" + "/" + folder;
|
||||
#elif __linux__
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
const char* homedir = pw->pw_dir;
|
||||
system_folder_ = std::string(homedir) + "/.config/" + folder;
|
||||
|
||||
{
|
||||
// Intenta crear ".config", per si no existeix
|
||||
std::string config_base_folder = std::string(homedir) + "/.config";
|
||||
int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
|
||||
if (ret == -1 && errno != EEXIST) {
|
||||
printf("ERROR CREATING CONFIG BASE FOLDER.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
struct stat st = {0};
|
||||
if (stat(system_folder_.c_str(), &st) == -1) {
|
||||
errno = 0;
|
||||
#ifdef _WIN32
|
||||
int ret = mkdir(system_folder_.c_str());
|
||||
#else
|
||||
int ret = mkdir(system_folder_.c_str(), S_IRWXU);
|
||||
#endif
|
||||
|
||||
if (ret == -1) {
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
printf("the parent directory does not allow write");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
case EEXIST:
|
||||
printf("pathname already exists");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
case ENAMETOOLONG:
|
||||
printf("pathname is too long");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
default:
|
||||
perror("mkdir");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inicia las variables necesarias para arrancar el programa
|
||||
void Director::initInput() {
|
||||
// Busca si hay un mando conectado
|
||||
Input::get()->discoverGameControllers();
|
||||
|
||||
// Teclado - Movimiento
|
||||
if (options.keys == ControlScheme::CURSOR) {
|
||||
Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_UP);
|
||||
Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_LEFT);
|
||||
Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_RIGHT);
|
||||
Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_UP);
|
||||
Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_DOWN);
|
||||
} else if (options.keys == ControlScheme::OPQA) {
|
||||
Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_Q);
|
||||
Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_O);
|
||||
Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_P);
|
||||
Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_Q);
|
||||
Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_A);
|
||||
} else if (options.keys == ControlScheme::WASD) {
|
||||
Input::get()->bindKey(InputAction::JUMP, SDL_SCANCODE_W);
|
||||
Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_A);
|
||||
Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_D);
|
||||
Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_W);
|
||||
Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_S);
|
||||
}
|
||||
|
||||
// Teclado - Otros
|
||||
Input::get()->bindKey(InputAction::ACCEPT, SDL_SCANCODE_RETURN);
|
||||
Input::get()->bindKey(InputAction::CANCEL, SDL_SCANCODE_ESCAPE);
|
||||
Input::get()->bindKey(InputAction::PAUSE, SDL_SCANCODE_H);
|
||||
Input::get()->bindKey(InputAction::EXIT, SDL_SCANCODE_ESCAPE);
|
||||
Input::get()->bindKey(InputAction::WINDOW_DEC_ZOOM, SDL_SCANCODE_F1);
|
||||
Input::get()->bindKey(InputAction::WINDOW_INC_ZOOM, SDL_SCANCODE_F2);
|
||||
Input::get()->bindKey(InputAction::TOGGLE_VIDEOMODE, SDL_SCANCODE_F3);
|
||||
Input::get()->bindKey(InputAction::TOGGLE_SHADERS, SDL_SCANCODE_F4);
|
||||
Input::get()->bindKey(InputAction::NEXT_PALETTE, SDL_SCANCODE_F5);
|
||||
Input::get()->bindKey(InputAction::PREVIOUS_PALETTE, SDL_SCANCODE_F6);
|
||||
Input::get()->bindKey(InputAction::TOGGLE_INTEGER_SCALE, SDL_SCANCODE_F7);
|
||||
Input::get()->bindKey(InputAction::SHOW_DEBUG_INFO, SDL_SCANCODE_F12);
|
||||
Input::get()->bindKey(InputAction::TOGGLE_MUSIC, SDL_SCANCODE_M);
|
||||
Input::get()->bindKey(InputAction::TOGGLE_BORDER, SDL_SCANCODE_B);
|
||||
|
||||
// MANDO
|
||||
const int NUM_GAMEPADS = Input::get()->getNumControllers();
|
||||
for (int i = 0; i < NUM_GAMEPADS; ++i) {
|
||||
// Movimiento
|
||||
Input::get()->bindGameControllerButton(i, InputAction::JUMP, SDL_GAMEPAD_BUTTON_SOUTH);
|
||||
Input::get()->bindGameControllerButton(i, InputAction::LEFT, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
|
||||
Input::get()->bindGameControllerButton(i, InputAction::RIGHT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
|
||||
|
||||
// Otros
|
||||
Input::get()->bindGameControllerButton(i, InputAction::ACCEPT, SDL_GAMEPAD_BUTTON_SOUTH);
|
||||
Input::get()->bindGameControllerButton(i, InputAction::CANCEL, SDL_GAMEPAD_BUTTON_EAST);
|
||||
#ifdef GAME_CONSOLE
|
||||
Input::get()->bindGameControllerButton(i, InputAction::input_pause, SDL_GAMEPAD_BUTTON_BACK);
|
||||
Input::get()->bindGameControllerButton(i, InputAction::input_exit, SDL_GAMEPAD_BUTTON_START);
|
||||
#else
|
||||
Input::get()->bindGameControllerButton(i, InputAction::PAUSE, SDL_GAMEPAD_BUTTON_START);
|
||||
Input::get()->bindGameControllerButton(i, InputAction::EXIT, SDL_GAMEPAD_BUTTON_BACK);
|
||||
#endif
|
||||
Input::get()->bindGameControllerButton(i, InputAction::NEXT_PALETTE, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
|
||||
Input::get()->bindGameControllerButton(i, InputAction::TOGGLE_MUSIC, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
|
||||
Input::get()->bindGameControllerButton(i, InputAction::TOGGLE_BORDER, SDL_GAMEPAD_BUTTON_NORTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa JailAudio
|
||||
void Director::initJailAudio() {
|
||||
if (!SDL_Init(SDL_INIT_AUDIO)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError());
|
||||
} else {
|
||||
JA_Init(48000, SDL_AUDIO_S16LE, 2);
|
||||
if (options.audio.enabled) {
|
||||
JA_SetMusicVolume(options.audio.music.volume);
|
||||
JA_SetSoundVolume(options.audio.sound.volume);
|
||||
} else {
|
||||
JA_SetMusicVolume(0);
|
||||
JA_SetSoundVolume(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Crea el indice de ficheros
|
||||
bool Director::setFileList() {
|
||||
#ifdef MACOS_BUNDLE
|
||||
const std::string prefix = "/../Resources";
|
||||
#else
|
||||
const std::string prefix = "";
|
||||
#endif
|
||||
|
||||
// Texto
|
||||
Asset::get()->add(prefix + "/data/font/smb2.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/font/smb2.txt", AssetType::FONT);
|
||||
Asset::get()->add(prefix + "/data/font/debug.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/font/debug.txt", AssetType::FONT);
|
||||
Asset::get()->add(prefix + "/data/font/gauntlet.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/font/gauntlet.txt", AssetType::FONT);
|
||||
Asset::get()->add(prefix + "/data/font/subatomic.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/font/subatomic.txt", AssetType::FONT);
|
||||
Asset::get()->add(prefix + "/data/font/8bithud.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/font/8bithud.txt", AssetType::FONT);
|
||||
|
||||
// Paletas
|
||||
Asset::get()->add(prefix + "/data/palette/zx-spectrum.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/zx-spectrum-adjusted.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/zxarne-5-2.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/black-and-white.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/green-phosphor.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/orange-screen.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/ruzx-spectrum.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/ruzx-spectrum-revision-2.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/pico-8.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/sweetie-16.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/island-joy-16.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/lost-century.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/na16.pal", AssetType::PALETTE);
|
||||
Asset::get()->add(prefix + "/data/palette/steam-lords.pal", AssetType::PALETTE);
|
||||
|
||||
// Shaders
|
||||
Asset::get()->add(prefix + "/data/shaders/crtpi_vertex.glsl", AssetType::DATA);
|
||||
Asset::get()->add(prefix + "/data/shaders/crtpi_fragment.glsl", AssetType::DATA);
|
||||
Asset::get()->add(prefix + "/data/shaders/crtpi_vertex_es.glsl", AssetType::DATA);
|
||||
Asset::get()->add(prefix + "/data/shaders/crtpi_fragment_es.glsl", AssetType::DATA);
|
||||
|
||||
// Datos
|
||||
Asset::get()->add(prefix + "/data/input/gamecontrollerdb.txt", AssetType::DATA);
|
||||
|
||||
// Ficheros de sistema
|
||||
Asset::get()->add(system_folder_ + "/config.txt", AssetType::DATA, false, true);
|
||||
Asset::get()->add(system_folder_ + "/stats_buffer.csv", AssetType::DATA, false, true);
|
||||
Asset::get()->add(system_folder_ + "/stats.csv", AssetType::DATA, false, true);
|
||||
Asset::get()->add(system_folder_ + "/cheevos.bin", AssetType::DATA, false, true);
|
||||
|
||||
// Tilemaps y Rooms
|
||||
for (int i = 1; i <= 60; ++i) {
|
||||
std::string index = (i < 10 ? "0" : "") + std::to_string(i);
|
||||
Asset::get()->add(prefix + "/data/room/" + index + ".tmx", AssetType::TILEMAP);
|
||||
Asset::get()->add(prefix + "/data/room/" + index + ".room", AssetType::ROOM);
|
||||
}
|
||||
|
||||
// Tilesets
|
||||
Asset::get()->add(prefix + "/data/tilesets/standard.gif", AssetType::BITMAP);
|
||||
|
||||
// Enemigos
|
||||
Asset::get()->add(prefix + "/data/enemies/abad_bell.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/abad_bell.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/abad.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/abad.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/amstrad_cs.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/amstrad_cs.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/flying_arounder.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/flying_arounder.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/stopped_arounder.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/stopped_arounder.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/walking_arounder.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/walking_arounder.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/arounders_door.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/arounders_door.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/arounders_machine.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/arounders_machine.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/bat.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/bat.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/batman_bell.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/batman_bell.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/batman_fire.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/batman_fire.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/batman.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/batman.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/bell.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/bell.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/bin.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/bin.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/bird.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/bird.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/breakout.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/breakout.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/bry.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/bry.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/chip.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/chip.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/code.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/code.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/congo.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/congo.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/crosshair.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/crosshair.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/demon.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/demon.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/dimallas.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/dimallas.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/floppy.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/floppy.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/dong.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/dong.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/guitar.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/guitar.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/heavy.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/heavy.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailer_#1.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailer_#1.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailer_#2.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailer_#2.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailer_#3.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailer_#3.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailbattle_alien.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailbattle_alien.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailbattle_human.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/jailbattle_human.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/jeannine.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/jeannine.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/lamp.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/lamp.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/lord_abad.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/lord_abad.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/matatunos.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/matatunos.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/mummy.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/mummy.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/paco.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/paco.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/elsa.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/elsa.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/qvoid.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/qvoid.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/robot.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/robot.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/sam.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/sam.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/shock.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/shock.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/sigmasua.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/sigmasua.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/spark.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/spark.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/special/aerojailer.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/special/aerojailer.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/special/arounder.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/special/arounder.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/special/pepe_rosita_job.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/special/pepe_rosita_job.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/special/shooting_star.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/special/shooting_star.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/spider.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/spider.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/tree_thing.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/tree_thing.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/tuno.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/tuno.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/tv_panel.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/tv_panel.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/tv.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/tv.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/upv_student.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/upv_student.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/wave.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/wave.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/enemies/z80.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/enemies/z80.gif", AssetType::BITMAP);
|
||||
|
||||
// Jugador
|
||||
Asset::get()->add(prefix + "/data/player/player.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/player/player.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/player/player2.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/player/player2.ani", AssetType::ANIMATION);
|
||||
Asset::get()->add(prefix + "/data/player/player_game_over.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/player/player_game_over.ani", AssetType::ANIMATION);
|
||||
|
||||
// Items
|
||||
Asset::get()->add(prefix + "/data/items/items.gif", AssetType::BITMAP);
|
||||
|
||||
// Musicas
|
||||
Asset::get()->add(prefix + "/data/music/title.ogg", AssetType::MUSIC);
|
||||
Asset::get()->add(prefix + "/data/music/game.ogg", AssetType::MUSIC);
|
||||
Asset::get()->add(prefix + "/data/music/loading_sound1.ogg", AssetType::MUSIC);
|
||||
Asset::get()->add(prefix + "/data/music/loading_sound2.ogg", AssetType::MUSIC);
|
||||
Asset::get()->add(prefix + "/data/music/loading_sound3.ogg", AssetType::MUSIC);
|
||||
Asset::get()->add(prefix + "/data/music/ending1.ogg", AssetType::MUSIC);
|
||||
Asset::get()->add(prefix + "/data/music/ending2.ogg", AssetType::MUSIC);
|
||||
Asset::get()->add(prefix + "/data/music/game_over.ogg", AssetType::MUSIC);
|
||||
|
||||
// Efectos de sonido
|
||||
Asset::get()->add(prefix + "/data/sound/item.wav", AssetType::SOUND);
|
||||
Asset::get()->add(prefix + "/data/sound/death.wav", AssetType::SOUND);
|
||||
Asset::get()->add(prefix + "/data/sound/notify.wav", AssetType::SOUND);
|
||||
|
||||
// Efectos de sonido para el salto
|
||||
for (int i = 1; i <= 24; ++i) {
|
||||
std::string jump_index = std::to_string(i);
|
||||
Asset::get()->add(prefix + "/data/sound/jump" + jump_index + ".wav", AssetType::SOUND);
|
||||
}
|
||||
|
||||
// Logo
|
||||
Asset::get()->add(prefix + "/data/logo/jailgames.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/logo/since_1998.gif", AssetType::BITMAP);
|
||||
|
||||
// Loading
|
||||
Asset::get()->add(prefix + "/data/loading/loading_screen_bn.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/loading/loading_screen_color.gif", AssetType::BITMAP);
|
||||
|
||||
// Title
|
||||
Asset::get()->add(prefix + "/data/title/title_logo.gif", AssetType::BITMAP);
|
||||
|
||||
// Ending
|
||||
Asset::get()->add(prefix + "/data/ending/ending1.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/ending/ending2.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/ending/ending3.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/ending/ending4.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/ending/ending5.gif", AssetType::BITMAP);
|
||||
|
||||
// Credits
|
||||
Asset::get()->add(prefix + "/data/credits/shine.gif", AssetType::BITMAP);
|
||||
Asset::get()->add(prefix + "/data/credits/shine.ani", AssetType::ANIMATION);
|
||||
|
||||
return Asset::get()->check();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion de juego con el logo
|
||||
void Director::runLogo() {
|
||||
auto logo = std::make_unique<Logo>();
|
||||
logo->run();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion de juego de la pantalla de carga
|
||||
void Director::runLoadingScreen() {
|
||||
auto loadingScreen = std::make_unique<LoadingScreen>();
|
||||
loadingScreen->run();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion de juego con el titulo y los menus
|
||||
void Director::runTitle() {
|
||||
auto title = std::make_unique<Title>();
|
||||
title->run();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion de los creditos del juego
|
||||
void Director::runCredits() {
|
||||
auto credits = std::make_unique<Credits>();
|
||||
credits->run();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion de la demo, donde se ven pantallas del juego
|
||||
void Director::runDemo() {
|
||||
auto game = std::make_unique<Game>(GameMode::DEMO);
|
||||
game->run();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion del final del juego
|
||||
void Director::runEnding() {
|
||||
auto ending = std::make_unique<Ending>();
|
||||
ending->run();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion del final del juego
|
||||
void Director::runEnding2() {
|
||||
auto ending2 = std::make_unique<Ending2>();
|
||||
ending2->run();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion del final de la partida
|
||||
void Director::runGameOver() {
|
||||
auto gameOver = std::make_unique<GameOver>();
|
||||
gameOver->run();
|
||||
}
|
||||
|
||||
// Ejecuta la seccion de juego donde se juega
|
||||
void Director::runGame() {
|
||||
JA_StopMusic();
|
||||
auto game = std::make_unique<Game>(GameMode::GAME);
|
||||
game->run();
|
||||
}
|
||||
|
||||
int Director::run() {
|
||||
// Bucle principal
|
||||
while (options.section.section != Section::QUIT) {
|
||||
switch (options.section.section) {
|
||||
case Section::LOGO:
|
||||
runLogo();
|
||||
break;
|
||||
|
||||
case Section::LOADING_SCREEN:
|
||||
runLoadingScreen();
|
||||
break;
|
||||
|
||||
case Section::TITLE:
|
||||
runTitle();
|
||||
break;
|
||||
|
||||
case Section::CREDITS:
|
||||
runCredits();
|
||||
break;
|
||||
|
||||
case Section::DEMO:
|
||||
runDemo();
|
||||
break;
|
||||
|
||||
case Section::GAME:
|
||||
runGame();
|
||||
break;
|
||||
|
||||
case Section::GAME_OVER:
|
||||
runGameOver();
|
||||
break;
|
||||
|
||||
case Section::ENDING:
|
||||
runEnding();
|
||||
break;
|
||||
|
||||
case Section::ENDING2:
|
||||
runEnding2();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
64
source2/Core/System/director.h
Normal file
64
source2/Core/System/director.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string
|
||||
|
||||
class Director {
|
||||
private:
|
||||
// Variables
|
||||
std::string executable_path_; // Path del ejecutable
|
||||
std::string system_folder_; // Carpeta del sistema donde guardar datos
|
||||
|
||||
// Comprueba los parametros del programa
|
||||
std::string checkProgramArguments(int argc, const char* argv[]);
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
void createSystemFolder(const std::string& folder);
|
||||
|
||||
// Inicializa jail_audio
|
||||
void initJailAudio();
|
||||
|
||||
// Inicializa el objeto Input
|
||||
void initInput();
|
||||
|
||||
// Crea el indice de ficheros
|
||||
bool setFileList();
|
||||
|
||||
// Ejecuta la seccion de juego con el logo
|
||||
void runLogo();
|
||||
|
||||
// Ejecuta la seccion de juego de la pantalla de carga
|
||||
void runLoadingScreen();
|
||||
|
||||
// Ejecuta la seccion de juego con el titulo y los menus
|
||||
void runTitle();
|
||||
|
||||
// Ejecuta la seccion de los creditos del juego
|
||||
void runCredits();
|
||||
|
||||
// Ejecuta la seccion de la demo, donde se ven pantallas del juego
|
||||
void runDemo();
|
||||
|
||||
// Ejecuta la seccion del final del juego
|
||||
void runEnding();
|
||||
|
||||
// Ejecuta la seccion del final del juego
|
||||
void runEnding2();
|
||||
|
||||
// Ejecuta la seccion del final de la partida
|
||||
void runGameOver();
|
||||
|
||||
// Ejecuta la seccion de juego donde se juega
|
||||
void runGame();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Director(int argc, const char* argv[]);
|
||||
|
||||
// Destructor
|
||||
~Director();
|
||||
|
||||
// Bucle principal
|
||||
int run();
|
||||
};
|
||||
462
source2/Core/rendering/opengl/opengl_shader.cpp
Normal file
462
source2/Core/rendering/opengl/opengl_shader.cpp
Normal file
@@ -0,0 +1,462 @@
|
||||
#include "opengl_shader.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
OpenGLShader::~OpenGLShader() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
bool OpenGLShader::initGLExtensions() {
|
||||
glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader");
|
||||
glShaderSource = (PFNGLSHADERSOURCEPROC)SDL_GL_GetProcAddress("glShaderSource");
|
||||
glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader");
|
||||
glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv");
|
||||
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog");
|
||||
glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader");
|
||||
glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader");
|
||||
glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram");
|
||||
glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram");
|
||||
glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)SDL_GL_GetProcAddress("glValidateProgram");
|
||||
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv");
|
||||
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog");
|
||||
glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");
|
||||
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram");
|
||||
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation");
|
||||
glUniform2f = (PFNGLUNIFORM2FPROC)SDL_GL_GetProcAddress("glUniform2f");
|
||||
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glGenVertexArrays");
|
||||
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)SDL_GL_GetProcAddress("glBindVertexArray");
|
||||
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glDeleteVertexArrays");
|
||||
glGenBuffers = (PFNGLGENBUFFERSPROC)SDL_GL_GetProcAddress("glGenBuffers");
|
||||
glBindBuffer = (PFNGLBINDBUFFERPROC)SDL_GL_GetProcAddress("glBindBuffer");
|
||||
glBufferData = (PFNGLBUFFERDATAPROC)SDL_GL_GetProcAddress("glBufferData");
|
||||
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteBuffers");
|
||||
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)SDL_GL_GetProcAddress("glVertexAttribPointer");
|
||||
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)SDL_GL_GetProcAddress("glEnableVertexAttribArray");
|
||||
|
||||
return glCreateShader && glShaderSource && glCompileShader && glGetShaderiv &&
|
||||
glGetShaderInfoLog && glDeleteShader && glAttachShader && glCreateProgram &&
|
||||
glLinkProgram && glValidateProgram && glGetProgramiv && glGetProgramInfoLog &&
|
||||
glUseProgram && glDeleteProgram && glGetUniformLocation && glUniform2f &&
|
||||
glGenVertexArrays && glBindVertexArray && glDeleteVertexArrays &&
|
||||
glGenBuffers && glBindBuffer && glBufferData && glDeleteBuffers &&
|
||||
glVertexAttribPointer && glEnableVertexAttribArray;
|
||||
}
|
||||
#endif
|
||||
|
||||
void OpenGLShader::checkGLError(const char* operation) {
|
||||
GLenum error = glGetError();
|
||||
if (error != GL_NO_ERROR) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error OpenGL en %s: 0x%x", operation, error);
|
||||
}
|
||||
}
|
||||
|
||||
GLuint OpenGLShader::compileShader(const std::string& source, GLenum shader_type) {
|
||||
if (source.empty()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"ERROR: El código fuente del shader está vacío");
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLuint shader_id = glCreateShader(shader_type);
|
||||
if (shader_id == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al crear shader");
|
||||
checkGLError("glCreateShader");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* sources[1] = {source.c_str()};
|
||||
glShaderSource(shader_id, 1, sources, nullptr);
|
||||
checkGLError("glShaderSource");
|
||||
|
||||
glCompileShader(shader_id);
|
||||
checkGLError("glCompileShader");
|
||||
|
||||
// Verificar compilación
|
||||
GLint compiled = GL_FALSE;
|
||||
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compiled);
|
||||
if (compiled != GL_TRUE) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error en compilación del shader");
|
||||
GLint log_length;
|
||||
glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length);
|
||||
if (log_length > 0) {
|
||||
std::vector<char> log(log_length);
|
||||
glGetShaderInfoLog(shader_id, log_length, &log_length, log.data());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Log de compilación: %s", log.data());
|
||||
}
|
||||
glDeleteShader(shader_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return shader_id;
|
||||
}
|
||||
|
||||
GLuint OpenGLShader::linkProgram(GLuint vertex_shader, GLuint fragment_shader) {
|
||||
GLuint program = glCreateProgram();
|
||||
if (program == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al crear programa de shaders");
|
||||
return 0;
|
||||
}
|
||||
|
||||
glAttachShader(program, vertex_shader);
|
||||
checkGLError("glAttachShader(vertex)");
|
||||
glAttachShader(program, fragment_shader);
|
||||
checkGLError("glAttachShader(fragment)");
|
||||
|
||||
glLinkProgram(program);
|
||||
checkGLError("glLinkProgram");
|
||||
|
||||
// Verificar enlace
|
||||
GLint linked = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &linked);
|
||||
if (linked != GL_TRUE) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al enlazar programa");
|
||||
GLint log_length;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
|
||||
if (log_length > 0) {
|
||||
std::vector<char> log(log_length);
|
||||
glGetProgramInfoLog(program, log_length, &log_length, log.data());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Log de enlace: %s", log.data());
|
||||
}
|
||||
glDeleteProgram(program);
|
||||
return 0;
|
||||
}
|
||||
|
||||
glValidateProgram(program);
|
||||
checkGLError("glValidateProgram");
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
void OpenGLShader::createQuadGeometry() {
|
||||
// Datos del quad: posición (x, y) + coordenadas de textura (u, v)
|
||||
// Formato: x, y, u, v
|
||||
float vertices[] = {
|
||||
// Posición // TexCoords
|
||||
-1.0f, -1.0f, 0.0f, 0.0f, // Inferior izquierda
|
||||
1.0f, -1.0f, 1.0f, 0.0f, // Inferior derecha
|
||||
1.0f, 1.0f, 1.0f, 1.0f, // Superior derecha
|
||||
-1.0f, 1.0f, 0.0f, 1.0f // Superior izquierda
|
||||
};
|
||||
|
||||
// Índices para dibujar el quad con dos triángulos
|
||||
unsigned int indices[] = {
|
||||
0, 1, 2, // Primer triángulo
|
||||
2, 3, 0 // Segundo triángulo
|
||||
};
|
||||
|
||||
// Generar y configurar VAO
|
||||
glGenVertexArrays(1, &vao_);
|
||||
glBindVertexArray(vao_);
|
||||
checkGLError("glBindVertexArray");
|
||||
|
||||
// Generar y configurar VBO
|
||||
glGenBuffers(1, &vbo_);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
||||
checkGLError("glBufferData(VBO)");
|
||||
|
||||
// Generar y configurar EBO
|
||||
glGenBuffers(1, &ebo_);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
|
||||
checkGLError("glBufferData(EBO)");
|
||||
|
||||
// Atributo 0: Posición (2 floats)
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
checkGLError("glVertexAttribPointer(position)");
|
||||
|
||||
// Atributo 1: Coordenadas de textura (2 floats)
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
|
||||
glEnableVertexAttribArray(1);
|
||||
checkGLError("glVertexAttribPointer(texcoord)");
|
||||
|
||||
// Desvincular
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
GLuint OpenGLShader::getTextureID(SDL_Texture* texture) {
|
||||
if (!texture) return 1;
|
||||
|
||||
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
|
||||
GLuint texture_id = 0;
|
||||
|
||||
// Intentar obtener ID de textura OpenGL
|
||||
texture_id = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "SDL.texture.opengl.texture", nullptr);
|
||||
|
||||
if (texture_id == 0) {
|
||||
texture_id = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "texture.opengl.texture", nullptr);
|
||||
}
|
||||
|
||||
if (texture_id == 0) {
|
||||
texture_id = (GLuint)SDL_GetNumberProperty(props, "SDL.texture.opengl.texture", 1);
|
||||
}
|
||||
|
||||
if (texture_id == 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No se pudo obtener ID de textura OpenGL, usando 1 por defecto");
|
||||
texture_id = 1;
|
||||
}
|
||||
|
||||
return texture_id;
|
||||
}
|
||||
|
||||
bool OpenGLShader::init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) {
|
||||
window_ = window;
|
||||
back_buffer_ = texture;
|
||||
renderer_ = SDL_GetRenderer(window);
|
||||
|
||||
if (!renderer_) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error: No se pudo obtener el renderer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtener tamaños
|
||||
SDL_GetWindowSize(window_, &window_width_, &window_height_);
|
||||
SDL_GetTextureSize(back_buffer_, &texture_width_, &texture_height_);
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Inicializando shaders: ventana=%dx%d, textura=%.0fx%.0f",
|
||||
window_width_, window_height_, texture_width_, texture_height_);
|
||||
|
||||
// Verificar que es OpenGL
|
||||
const char* renderer_name = SDL_GetRendererName(renderer_);
|
||||
if (!renderer_name || strncmp(renderer_name, "opengl", 6) != 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Renderer no es OpenGL: %s", renderer_name ? renderer_name : "unknown");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
// Inicializar extensiones OpenGL en Windows/Linux
|
||||
if (!initGLExtensions()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al inicializar extensiones OpenGL");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Limpiar shader anterior si existe
|
||||
if (program_id_ != 0) {
|
||||
glDeleteProgram(program_id_);
|
||||
program_id_ = 0;
|
||||
}
|
||||
|
||||
// Compilar shaders
|
||||
GLuint vertex_shader = compileShader(vertex_source, GL_VERTEX_SHADER);
|
||||
GLuint fragment_shader = compileShader(fragment_source, GL_FRAGMENT_SHADER);
|
||||
|
||||
if (vertex_shader == 0 || fragment_shader == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al compilar shaders");
|
||||
if (vertex_shader != 0) glDeleteShader(vertex_shader);
|
||||
if (fragment_shader != 0) glDeleteShader(fragment_shader);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enlazar programa
|
||||
program_id_ = linkProgram(vertex_shader, fragment_shader);
|
||||
|
||||
// Limpiar shaders (ya no necesarios tras el enlace)
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
if (program_id_ == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al crear programa de shaders");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Crear geometría del quad
|
||||
createQuadGeometry();
|
||||
|
||||
// Obtener ubicación del uniform TextureSize
|
||||
glUseProgram(program_id_);
|
||||
texture_size_location_ = glGetUniformLocation(program_id_, "TextureSize");
|
||||
if (texture_size_location_ != -1) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Configurando TextureSize uniform: %.0fx%.0f",
|
||||
texture_width_, texture_height_);
|
||||
glUniform2f(texture_size_location_, texture_width_, texture_height_);
|
||||
checkGLError("glUniform2f(TextureSize)");
|
||||
} else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Uniform 'TextureSize' no encontrado en shader");
|
||||
}
|
||||
glUseProgram(0);
|
||||
|
||||
is_initialized_ = true;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"** OpenGL 3.3 Shader Backend inicializado correctamente");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLShader::render() {
|
||||
if (!is_initialized_ || program_id_ == 0) {
|
||||
// Fallback: renderizado SDL normal
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_RenderTexture(renderer_, back_buffer_, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener tamaño actual de ventana (puede haber cambiado)
|
||||
int current_width, current_height;
|
||||
SDL_GetWindowSize(window_, ¤t_width, ¤t_height);
|
||||
|
||||
// Guardar estados OpenGL
|
||||
GLint old_program;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, &old_program);
|
||||
|
||||
GLint old_viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, old_viewport);
|
||||
|
||||
GLboolean was_texture_enabled = glIsEnabled(GL_TEXTURE_2D);
|
||||
GLint old_texture;
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture);
|
||||
|
||||
GLint old_vao;
|
||||
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao);
|
||||
|
||||
// Preparar renderizado
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
SDL_RenderClear(renderer_);
|
||||
|
||||
// Obtener y bindear textura
|
||||
GLuint texture_id = getTextureID(back_buffer_);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, texture_id);
|
||||
checkGLError("glBindTexture");
|
||||
|
||||
// Usar nuestro programa
|
||||
glUseProgram(program_id_);
|
||||
checkGLError("glUseProgram");
|
||||
|
||||
// Configurar viewport (obtener tamaño lógico de SDL)
|
||||
int logical_w, logical_h;
|
||||
SDL_RendererLogicalPresentation mode;
|
||||
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &mode);
|
||||
|
||||
if (logical_w == 0 || logical_h == 0) {
|
||||
logical_w = current_width;
|
||||
logical_h = current_height;
|
||||
}
|
||||
|
||||
// Calcular viewport considerando aspect ratio
|
||||
int viewport_x = 0, viewport_y = 0;
|
||||
int viewport_w = current_width, viewport_h = current_height;
|
||||
|
||||
if (mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) {
|
||||
int scale_x = current_width / logical_w;
|
||||
int scale_y = current_height / logical_h;
|
||||
int scale = (scale_x < scale_y) ? scale_x : scale_y;
|
||||
if (scale < 1) scale = 1;
|
||||
|
||||
viewport_w = logical_w * scale;
|
||||
viewport_h = logical_h * scale;
|
||||
viewport_x = (current_width - viewport_w) / 2;
|
||||
viewport_y = (current_height - viewport_h) / 2;
|
||||
} else {
|
||||
float window_aspect = static_cast<float>(current_width) / current_height;
|
||||
float logical_aspect = static_cast<float>(logical_w) / logical_h;
|
||||
|
||||
if (window_aspect > logical_aspect) {
|
||||
viewport_w = static_cast<int>(logical_aspect * current_height);
|
||||
viewport_x = (current_width - viewport_w) / 2;
|
||||
} else {
|
||||
viewport_h = static_cast<int>(current_width / logical_aspect);
|
||||
viewport_y = (current_height - viewport_h) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
glViewport(viewport_x, viewport_y, viewport_w, viewport_h);
|
||||
checkGLError("glViewport");
|
||||
|
||||
// Dibujar quad usando VAO
|
||||
glBindVertexArray(vao_);
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
|
||||
checkGLError("glDrawElements");
|
||||
|
||||
// Presentar
|
||||
SDL_GL_SwapWindow(window_);
|
||||
|
||||
// Restaurar estados OpenGL
|
||||
glUseProgram(old_program);
|
||||
glBindTexture(GL_TEXTURE_2D, old_texture);
|
||||
if (!was_texture_enabled) {
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
glBindVertexArray(old_vao);
|
||||
glViewport(old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3]);
|
||||
}
|
||||
|
||||
void OpenGLShader::setTextureSize(float width, float height) {
|
||||
if (!is_initialized_ || program_id_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
texture_width_ = width;
|
||||
texture_height_ = height;
|
||||
|
||||
GLint old_program;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, &old_program);
|
||||
|
||||
glUseProgram(program_id_);
|
||||
|
||||
if (texture_size_location_ != -1) {
|
||||
glUniform2f(texture_size_location_, width, height);
|
||||
checkGLError("glUniform2f(TextureSize)");
|
||||
}
|
||||
|
||||
glUseProgram(old_program);
|
||||
}
|
||||
|
||||
void OpenGLShader::cleanup() {
|
||||
if (vao_ != 0) {
|
||||
glDeleteVertexArrays(1, &vao_);
|
||||
vao_ = 0;
|
||||
}
|
||||
|
||||
if (vbo_ != 0) {
|
||||
glDeleteBuffers(1, &vbo_);
|
||||
vbo_ = 0;
|
||||
}
|
||||
|
||||
if (ebo_ != 0) {
|
||||
glDeleteBuffers(1, &ebo_);
|
||||
ebo_ = 0;
|
||||
}
|
||||
|
||||
if (program_id_ != 0) {
|
||||
glDeleteProgram(program_id_);
|
||||
program_id_ = 0;
|
||||
}
|
||||
|
||||
is_initialized_ = false;
|
||||
window_ = nullptr;
|
||||
renderer_ = nullptr;
|
||||
back_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
98
source2/Core/rendering/opengl/opengl_shader.h
Normal file
98
source2/Core/rendering/opengl/opengl_shader.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "../shader_backend.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl3.h>
|
||||
#else
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
#endif
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Backend de shaders usando OpenGL 3.3 Core Profile
|
||||
*
|
||||
* Implementa el renderizado de shaders usando APIs modernas de OpenGL:
|
||||
* - VAO (Vertex Array Objects)
|
||||
* - VBO (Vertex Buffer Objects)
|
||||
* - Shaders GLSL #version 330 core
|
||||
*/
|
||||
class OpenGLShader : public ShaderBackend {
|
||||
public:
|
||||
OpenGLShader() = default;
|
||||
~OpenGLShader() override;
|
||||
|
||||
bool init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) override;
|
||||
|
||||
void render() override;
|
||||
void setTextureSize(float width, float height) override;
|
||||
void cleanup() override;
|
||||
bool isHardwareAccelerated() const override { return is_initialized_; }
|
||||
|
||||
private:
|
||||
// Funciones auxiliares
|
||||
bool initGLExtensions();
|
||||
GLuint compileShader(const std::string& source, GLenum shader_type);
|
||||
GLuint linkProgram(GLuint vertex_shader, GLuint fragment_shader);
|
||||
void createQuadGeometry();
|
||||
GLuint getTextureID(SDL_Texture* texture);
|
||||
void checkGLError(const char* operation);
|
||||
|
||||
// Estado SDL
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_Renderer* renderer_ = nullptr;
|
||||
SDL_Texture* back_buffer_ = nullptr;
|
||||
|
||||
// Estado OpenGL
|
||||
GLuint program_id_ = 0;
|
||||
GLuint vao_ = 0; // Vertex Array Object
|
||||
GLuint vbo_ = 0; // Vertex Buffer Object
|
||||
GLuint ebo_ = 0; // Element Buffer Object
|
||||
|
||||
// Ubicaciones de uniforms
|
||||
GLint texture_size_location_ = -1;
|
||||
|
||||
// Tamaños
|
||||
int window_width_ = 0;
|
||||
int window_height_ = 0;
|
||||
float texture_width_ = 0.0f;
|
||||
float texture_height_ = 0.0f;
|
||||
|
||||
// Estado
|
||||
bool is_initialized_ = false;
|
||||
|
||||
#ifndef __APPLE__
|
||||
// Punteros a funciones OpenGL en Windows/Linux
|
||||
PFNGLCREATESHADERPROC glCreateShader = nullptr;
|
||||
PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
|
||||
PFNGLCOMPILESHADERPROC glCompileShader = nullptr;
|
||||
PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
|
||||
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
|
||||
PFNGLDELETESHADERPROC glDeleteShader = nullptr;
|
||||
PFNGLATTACHSHADERPROC glAttachShader = nullptr;
|
||||
PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr;
|
||||
PFNGLLINKPROGRAMPROC glLinkProgram = nullptr;
|
||||
PFNGLVALIDATEPROGRAMPROC glValidateProgram = nullptr;
|
||||
PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
|
||||
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
|
||||
PFNGLUSEPROGRAMPROC glUseProgram = nullptr;
|
||||
PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
|
||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
|
||||
PFNGLUNIFORM2FPROC glUniform2f = nullptr;
|
||||
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
|
||||
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
|
||||
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr;
|
||||
PFNGLGENBUFFERSPROC glGenBuffers = nullptr;
|
||||
PFNGLBINDBUFFERPROC glBindBuffer = nullptr;
|
||||
PFNGLBUFFERDATAPROC glBufferData = nullptr;
|
||||
PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr;
|
||||
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
|
||||
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
55
source2/Core/rendering/shader_backend.h
Normal file
55
source2/Core/rendering/shader_backend.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||
*
|
||||
* Esta interfaz define el contrato que todos los backends de shaders
|
||||
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
|
||||
*/
|
||||
class ShaderBackend {
|
||||
public:
|
||||
virtual ~ShaderBackend() = default;
|
||||
|
||||
/**
|
||||
* @brief Inicializa el backend de shaders
|
||||
* @param window Ventana SDL
|
||||
* @param texture Textura de backbuffer a la que aplicar shaders
|
||||
* @param vertex_source Código fuente del vertex shader
|
||||
* @param fragment_source Código fuente del fragment shader
|
||||
* @return true si la inicialización fue exitosa
|
||||
*/
|
||||
virtual bool init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) = 0;
|
||||
|
||||
/**
|
||||
* @brief Renderiza la textura con los shaders aplicados
|
||||
*/
|
||||
virtual void render() = 0;
|
||||
|
||||
/**
|
||||
* @brief Establece el tamaño de la textura como parámetro del shader
|
||||
* @param width Ancho de la textura
|
||||
* @param height Alto de la textura
|
||||
*/
|
||||
virtual void setTextureSize(float width, float height) = 0;
|
||||
|
||||
/**
|
||||
* @brief Limpia y libera recursos del backend
|
||||
*/
|
||||
virtual void cleanup() = 0;
|
||||
|
||||
/**
|
||||
* @brief Verifica si el backend está usando aceleración por hardware
|
||||
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||
*/
|
||||
virtual bool isHardwareAccelerated() const = 0;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
97
source2/Game/Entities/enemy.cpp
Normal file
97
source2/Game/Entities/enemy.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "enemy.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdlib.h> // Para rand
|
||||
|
||||
#include "resource.h" // Para Resource
|
||||
#include "sprite/surface_animated_sprite.h" // Para SAnimatedSprite
|
||||
#include "utils.h" // Para stringToColor
|
||||
|
||||
// Constructor
|
||||
Enemy::Enemy(const EnemyData& enemy)
|
||||
: sprite_(std::make_shared<SAnimatedSprite>(Resource::get()->getSurface(enemy.surface_path), Resource::get()->getAnimations(enemy.animation_path))),
|
||||
color_string_(enemy.color),
|
||||
x1_(enemy.x1),
|
||||
x2_(enemy.x2),
|
||||
y1_(enemy.y1),
|
||||
y2_(enemy.y2),
|
||||
should_flip_(enemy.flip),
|
||||
should_mirror_(enemy.mirror) {
|
||||
// Obten el resto de valores
|
||||
sprite_->setPosX(enemy.x);
|
||||
sprite_->setPosY(enemy.y);
|
||||
sprite_->setVelX(enemy.vx);
|
||||
sprite_->setVelY(enemy.vy);
|
||||
sprite_->setWidth(enemy.w);
|
||||
sprite_->setHeight(enemy.h);
|
||||
|
||||
const SDL_FlipMode FLIP = (should_flip_ && enemy.vx < 0.0f) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
|
||||
const SDL_FlipMode MIRROR = should_mirror_ ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
|
||||
sprite_->setFlip(static_cast<SDL_FlipMode>(FLIP | MIRROR));
|
||||
|
||||
collider_ = getRect();
|
||||
|
||||
color_ = stringToColor(color_string_);
|
||||
|
||||
// Coloca un frame al azar o el designado
|
||||
sprite_->setCurrentAnimationFrame((enemy.frame == -1) ? (rand() % sprite_->getCurrentAnimationSize()) : enemy.frame);
|
||||
}
|
||||
|
||||
// Pinta el enemigo en pantalla
|
||||
void Enemy::render() {
|
||||
sprite_->render(1, color_);
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void Enemy::update() {
|
||||
sprite_->update();
|
||||
checkPath();
|
||||
collider_ = getRect();
|
||||
}
|
||||
|
||||
// Comprueba si ha llegado al limite del recorrido para darse media vuelta
|
||||
void Enemy::checkPath() {
|
||||
if (sprite_->getPosX() > x2_ || sprite_->getPosX() < x1_) {
|
||||
// Recoloca
|
||||
if (sprite_->getPosX() > x2_) {
|
||||
sprite_->setPosX(x2_);
|
||||
} else {
|
||||
sprite_->setPosX(x1_);
|
||||
}
|
||||
|
||||
// Cambia el sentido
|
||||
sprite_->setVelX(sprite_->getVelX() * (-1));
|
||||
|
||||
// Invierte el sprite
|
||||
if (should_flip_) {
|
||||
sprite_->flip();
|
||||
}
|
||||
}
|
||||
|
||||
if (sprite_->getPosY() > y2_ || sprite_->getPosY() < y1_) {
|
||||
// Recoloca
|
||||
if (sprite_->getPosY() > y2_) {
|
||||
sprite_->setPosY(y2_);
|
||||
} else {
|
||||
sprite_->setPosY(y1_);
|
||||
}
|
||||
|
||||
// Cambia el sentido
|
||||
sprite_->setVelY(sprite_->getVelY() * (-1));
|
||||
|
||||
// Invierte el sprite
|
||||
if (should_flip_) {
|
||||
sprite_->flip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Devuelve el rectangulo que contiene al enemigo
|
||||
SDL_FRect Enemy::getRect() {
|
||||
return sprite_->getRect();
|
||||
}
|
||||
|
||||
// Obtiene el rectangulo de colision del enemigo
|
||||
SDL_FRect& Enemy::getCollider() {
|
||||
return collider_;
|
||||
}
|
||||
66
source2/Game/Entities/enemy.h
Normal file
66
source2/Game/Entities/enemy.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
class SAnimatedSprite; // lines 7-7
|
||||
|
||||
// Estructura para pasar los datos de un enemigo
|
||||
struct EnemyData {
|
||||
std::string surface_path; // Ruta al fichero con la textura
|
||||
std::string animation_path; // Ruta al fichero con la animación
|
||||
int w; // Anchura del enemigo
|
||||
int h; // Altura del enemigo
|
||||
float x; // Posición inicial en el eje X
|
||||
float y; // Posición inicial en el eje Y
|
||||
float vx; // Velocidad en el eje X
|
||||
float vy; // Velocidad en el eje Y
|
||||
int x1; // Limite izquierdo de la ruta en el eje X
|
||||
int x2; // Limite derecho de la ruta en el eje X
|
||||
int y1; // Limite superior de la ruta en el eje Y
|
||||
int y2; // Limite inferior de la ruta en el eje Y
|
||||
bool flip; // Indica si el enemigo hace flip al terminar su ruta
|
||||
bool mirror; // Indica si el enemigo está volteado verticalmente
|
||||
int frame; // Frame inicial para la animación del enemigo
|
||||
std::string color; // Color del enemigo
|
||||
};
|
||||
|
||||
class Enemy {
|
||||
private:
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<SAnimatedSprite> sprite_; // Sprite del enemigo
|
||||
|
||||
// Variables
|
||||
Uint8 color_; // Color del enemigo
|
||||
std::string color_string_; // Color del enemigo en formato texto
|
||||
int x1_; // Limite izquierdo de la ruta en el eje X
|
||||
int x2_; // Limite derecho de la ruta en el eje X
|
||||
int y1_; // Limite superior de la ruta en el eje Y
|
||||
int y2_; // Limite inferior de la ruta en el eje Y
|
||||
SDL_FRect collider_; // Caja de colisión
|
||||
bool should_flip_; // Indica si el enemigo hace flip al terminar su ruta
|
||||
bool should_mirror_; // Indica si el enemigo se dibuja volteado verticalmente
|
||||
|
||||
// Comprueba si ha llegado al limite del recorrido para darse media vuelta
|
||||
void checkPath();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
explicit Enemy(const EnemyData& enemy);
|
||||
|
||||
// Destructor
|
||||
~Enemy() = default;
|
||||
|
||||
// Pinta el enemigo en pantalla
|
||||
void render();
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void update();
|
||||
|
||||
// Devuelve el rectangulo que contiene al enemigo
|
||||
SDL_FRect getRect();
|
||||
|
||||
// Obtiene el rectangulo de colision del enemigo
|
||||
SDL_FRect& getCollider();
|
||||
};
|
||||
47
source2/Game/Entities/item.cpp
Normal file
47
source2/Game/Entities/item.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "item.h"
|
||||
|
||||
#include "resource.h" // Para Resource
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
|
||||
// Constructor
|
||||
Item::Item(ItemData item)
|
||||
: sprite_(std::make_shared<SSprite>(Resource::get()->getSurface(item.tile_set_file), item.x, item.y, ITEM_SIZE_, ITEM_SIZE_)),
|
||||
change_color_speed(4) {
|
||||
// Inicia variables
|
||||
sprite_->setClip((item.tile % 10) * ITEM_SIZE_, (item.tile / 10) * ITEM_SIZE_, ITEM_SIZE_, ITEM_SIZE_);
|
||||
collider_ = sprite_->getRect();
|
||||
counter_ = item.counter * change_color_speed;
|
||||
|
||||
// Inicializa los colores
|
||||
color_.push_back(item.color1);
|
||||
color_.push_back(item.color1);
|
||||
|
||||
color_.push_back(item.color2);
|
||||
color_.push_back(item.color2);
|
||||
}
|
||||
|
||||
// Pinta el objeto en pantalla
|
||||
void Item::render() {
|
||||
const int INDEX = (counter_ / change_color_speed) % color_.size();
|
||||
sprite_->render(1, color_.at(INDEX));
|
||||
}
|
||||
|
||||
// Obtiene su ubicación
|
||||
SDL_FPoint Item::getPos() {
|
||||
const SDL_FPoint p = {sprite_->getX(), sprite_->getY()};
|
||||
return p;
|
||||
}
|
||||
|
||||
// Asigna los colores del objeto
|
||||
void Item::setColors(Uint8 col1, Uint8 col2) {
|
||||
// Reinicializa el vector de colores
|
||||
color_.clear();
|
||||
|
||||
// Añade el primer color
|
||||
color_.push_back(col1);
|
||||
color_.push_back(col1);
|
||||
|
||||
// Añade el segundo color
|
||||
color_.push_back(col2);
|
||||
color_.push_back(col2);
|
||||
}
|
||||
64
source2/Game/Entities/item.h
Normal file
64
source2/Game/Entities/item.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
class SSprite;
|
||||
|
||||
struct ItemData {
|
||||
std::string tile_set_file; // Ruta al fichero con los gráficos del item
|
||||
float x; // Posición del item en pantalla
|
||||
float y; // Posición del item en pantalla
|
||||
int tile; // Número de tile dentro de la textura
|
||||
int counter; // Contador inicial. Es el que lo hace cambiar de color
|
||||
Uint8 color1; // Uno de los dos colores que se utiliza para el item
|
||||
Uint8 color2; // Uno de los dos colores que se utiliza para el item
|
||||
|
||||
// Constructor
|
||||
ItemData()
|
||||
: x(0),
|
||||
y(0),
|
||||
tile(0),
|
||||
counter(0),
|
||||
color1(),
|
||||
color2() {}
|
||||
};
|
||||
|
||||
class Item {
|
||||
private:
|
||||
// Constantes
|
||||
static constexpr float ITEM_SIZE_ = 8;
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<SSprite> sprite_; // SSprite del objeto
|
||||
|
||||
// Variables
|
||||
std::vector<Uint8> color_; // Vector con los colores del objeto
|
||||
int counter_; // Contador interno
|
||||
SDL_FRect collider_; // Rectangulo de colisión
|
||||
int change_color_speed; // Cuanto mas alto, mas tarda en cambiar de color
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
explicit Item(ItemData item);
|
||||
|
||||
// Destructor
|
||||
~Item() = default;
|
||||
|
||||
// Pinta el objeto en pantalla
|
||||
void render();
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void update() { counter_++; }
|
||||
|
||||
// Obtiene el rectangulo de colision del objeto
|
||||
SDL_FRect& getCollider() { return collider_; }
|
||||
|
||||
// Obtiene su ubicación
|
||||
SDL_FPoint getPos();
|
||||
|
||||
// Asigna los colores del objeto
|
||||
void setColors(Uint8 col1, Uint8 col2);
|
||||
};
|
||||
656
source2/Game/Entities/player.cpp
Normal file
656
source2/Game/Entities/player.cpp
Normal file
@@ -0,0 +1,656 @@
|
||||
// IWYU pragma: no_include <bits/std_abs.h>
|
||||
#include "player.h"
|
||||
|
||||
#include <algorithm> // Para max, min
|
||||
#include <cmath> // Para ceil, abs
|
||||
|
||||
#include "debug.h" // Para Debug
|
||||
#include "defines.h" // Para RoomBorder::BOTTOM, RoomBorder::LEFT, RoomBorder::RIGHT
|
||||
#include "external/jail_audio.h" // Para JA_PlaySound
|
||||
#include "input.h" // Para Input, InputAction
|
||||
#include "options.h" // Para Cheat, Options, options
|
||||
#include "resource.h" // Para Resource
|
||||
#include "room.h" // Para Room, TileType
|
||||
#include "sprite/surface_animated_sprite.h" // Para SAnimatedSprite
|
||||
|
||||
// Constructor
|
||||
Player::Player(const PlayerData& player)
|
||||
: room_(player.room) {
|
||||
// Inicializa algunas variables
|
||||
initSprite(player.texture_path, player.animations_path);
|
||||
setColor();
|
||||
applySpawnValues(player.spawn);
|
||||
placeSprite();
|
||||
initSounds();
|
||||
|
||||
previous_state_ = state_;
|
||||
last_position_ = getRect();
|
||||
collider_box_ = getRect();
|
||||
collider_points_.resize(collider_points_.size() + 8, {0, 0});
|
||||
under_feet_.resize(under_feet_.size() + 2, {0, 0});
|
||||
feet_.resize(feet_.size() + 2, {0, 0});
|
||||
|
||||
#ifdef DEBUG
|
||||
debug_rect_x_ = {0, 0, 0, 0};
|
||||
debug_rect_y_ = {0, 0, 0, 0};
|
||||
debug_color_ = static_cast<Uint8>(PaletteColor::GREEN);
|
||||
debug_point_ = {0, 0};
|
||||
#endif
|
||||
}
|
||||
|
||||
// Pinta el jugador en pantalla
|
||||
void Player::render() {
|
||||
sprite_->render(1, color_);
|
||||
|
||||
#ifdef DEBUG
|
||||
renderDebugInfo();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void Player::update() {
|
||||
if (!is_paused_) {
|
||||
checkInput(); // Comprueba las entradas y modifica variables
|
||||
move(); // Recalcula la posición del jugador
|
||||
animate(); // Establece la animación del jugador
|
||||
checkBorders(); // Comprueba si está situado en alguno de los cuatro bordes de la habitación
|
||||
checkJumpEnd(); // Comprueba si ha finalizado el salto al alcanzar la altura de inicio
|
||||
checkKillingTiles(); // Comprueba que el jugador no toque ningun tile de los que matan}
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las entradas y modifica variables
|
||||
void Player::checkInput() {
|
||||
// Solo comprueba las entradas de dirección cuando está sobre una superficie
|
||||
if (state_ != PlayerState::STANDING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!auto_movement_) {
|
||||
// Comprueba las entradas de desplazamiento lateral solo en el caso de no estar enganchado a una superficie automatica
|
||||
if (Input::get()->checkInput(InputAction::LEFT)) {
|
||||
vx_ = -0.6f;
|
||||
sprite_->setFlip(SDL_FLIP_HORIZONTAL);
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::RIGHT)) {
|
||||
vx_ = 0.6f;
|
||||
sprite_->setFlip(SDL_FLIP_NONE);
|
||||
}
|
||||
|
||||
else {
|
||||
// No se pulsa ninguna dirección
|
||||
vx_ = 0.0f;
|
||||
if (isOnAutoSurface()) {
|
||||
// Si deja de moverse sobre una superficie se engancha
|
||||
auto_movement_ = true;
|
||||
}
|
||||
}
|
||||
} else { // El movimiento lo proporciona la superficie
|
||||
vx_ = 0.6f * room_->getAutoSurfaceDirection();
|
||||
|
||||
if (vx_ > 0.0f) {
|
||||
sprite_->setFlip(SDL_FLIP_NONE);
|
||||
} else {
|
||||
sprite_->setFlip(SDL_FLIP_HORIZONTAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (Input::get()->checkInput(InputAction::JUMP)) {
|
||||
// Solo puede saltar si ademas de estar (state == s_standing)
|
||||
// Esta sobre el suelo, rampa o suelo que se mueve
|
||||
// Esto es para evitar el salto desde el vacio al cambiar de pantalla verticalmente
|
||||
// Ya que se coloca el estado s_standing al cambiar de pantalla
|
||||
|
||||
if (isOnFloor() || isOnAutoSurface()) {
|
||||
setState(PlayerState::JUMPING);
|
||||
vy_ = -MAX_VY_;
|
||||
jump_init_pos_ = y_;
|
||||
jumping_counter_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si está situado en alguno de los cuatro bordes de la habitación
|
||||
void Player::checkBorders() {
|
||||
if (x_ < PLAY_AREA_LEFT) {
|
||||
border_ = RoomBorder::LEFT;
|
||||
is_on_border_ = true;
|
||||
}
|
||||
|
||||
else if (x_ + WIDTH_ > PLAY_AREA_RIGHT) {
|
||||
border_ = RoomBorder::RIGHT;
|
||||
is_on_border_ = true;
|
||||
}
|
||||
|
||||
else if (y_ < PLAY_AREA_TOP) {
|
||||
border_ = RoomBorder::TOP;
|
||||
is_on_border_ = true;
|
||||
}
|
||||
|
||||
else if (y_ + HEIGHT_ > PLAY_AREA_BOTTOM) {
|
||||
border_ = RoomBorder::BOTTOM;
|
||||
is_on_border_ = true;
|
||||
}
|
||||
|
||||
else {
|
||||
is_on_border_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba el estado del jugador
|
||||
void Player::checkState() {
|
||||
// Actualiza las variables en función del estado
|
||||
if (state_ == PlayerState::FALLING) {
|
||||
vx_ = 0.0f;
|
||||
vy_ = MAX_VY_;
|
||||
falling_counter_++;
|
||||
playFallSound();
|
||||
}
|
||||
|
||||
else if (state_ == PlayerState::STANDING) {
|
||||
if (previous_state_ == PlayerState::FALLING && falling_counter_ > MAX_FALLING_HEIGHT_) { // Si cae de muy alto, el jugador muere
|
||||
is_alive_ = false;
|
||||
}
|
||||
vy_ = 0.0f;
|
||||
jumping_counter_ = 0;
|
||||
falling_counter_ = 0;
|
||||
if (!isOnFloor() && !isOnAutoSurface() && !isOnDownSlope()) {
|
||||
setState(PlayerState::FALLING);
|
||||
vx_ = 0.0f;
|
||||
vy_ = MAX_VY_;
|
||||
falling_counter_++;
|
||||
playFallSound();
|
||||
}
|
||||
}
|
||||
|
||||
else if (state_ == PlayerState::JUMPING) {
|
||||
falling_counter_ = 0;
|
||||
jumping_counter_++;
|
||||
playJumpSound();
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia al jugador de un borde al opuesto. Util para el cambio de pantalla
|
||||
void Player::switchBorders() {
|
||||
switch (border_) {
|
||||
case RoomBorder::TOP:
|
||||
y_ = PLAY_AREA_BOTTOM - HEIGHT_ - BLOCK;
|
||||
setState(PlayerState::STANDING);
|
||||
break;
|
||||
|
||||
case RoomBorder::BOTTOM:
|
||||
y_ = PLAY_AREA_TOP;
|
||||
setState(PlayerState::STANDING);
|
||||
break;
|
||||
|
||||
case RoomBorder::RIGHT:
|
||||
x_ = PLAY_AREA_LEFT;
|
||||
break;
|
||||
|
||||
case RoomBorder::LEFT:
|
||||
x_ = PLAY_AREA_RIGHT - WIDTH_;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
is_on_border_ = false;
|
||||
placeSprite();
|
||||
collider_box_ = getRect();
|
||||
}
|
||||
|
||||
// Aplica gravedad al jugador
|
||||
void Player::applyGravity() {
|
||||
constexpr float GRAVITY_FORCE = 0.035f;
|
||||
|
||||
// La gravedad solo se aplica cuando el jugador esta saltando
|
||||
// Nunca mientras cae o esta de pie
|
||||
if (state_ == PlayerState::JUMPING) {
|
||||
vy_ += GRAVITY_FORCE;
|
||||
if (vy_ > MAX_VY_) {
|
||||
vy_ = MAX_VY_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recalcula la posición del jugador y su animación
|
||||
void Player::move() {
|
||||
last_position_ = {x_, y_}; // Guarda la posicion actual antes de modificarla
|
||||
applyGravity(); // Aplica gravedad al jugador
|
||||
checkState(); // Comprueba el estado del jugador
|
||||
|
||||
#ifdef DEBUG
|
||||
debug_color_ = static_cast<Uint8>(PaletteColor::GREEN);
|
||||
#endif
|
||||
|
||||
// Se mueve hacia la izquierda
|
||||
if (vx_ < 0.0f) {
|
||||
// Crea el rectangulo de proyección en el eje X para ver si colisiona
|
||||
SDL_FRect proj;
|
||||
proj.x = static_cast<int>(x_ + vx_);
|
||||
proj.y = static_cast<int>(y_);
|
||||
proj.h = HEIGHT_;
|
||||
proj.w = static_cast<int>(std::ceil(std::fabs(vx_))); // Para evitar que tenga un ancho de 0 pixels
|
||||
|
||||
#ifdef DEBUG
|
||||
debug_rect_x_ = proj;
|
||||
#endif
|
||||
|
||||
// Comprueba la colisión con las superficies
|
||||
const int POS = room_->checkRightSurfaces(&proj);
|
||||
|
||||
// Calcula la nueva posición
|
||||
if (POS == -1) {
|
||||
// Si no hay colisión
|
||||
x_ += vx_;
|
||||
} else {
|
||||
// Si hay colisión lo mueve hasta donde no colisiona
|
||||
x_ = POS + 1;
|
||||
}
|
||||
|
||||
// Si ha tocado alguna rampa mientras camina (sin saltar), asciende
|
||||
if (state_ != PlayerState::JUMPING) {
|
||||
const LineVertical LEFT_SIDE = {static_cast<int>(x_), static_cast<int>(y_) + static_cast<int>(HEIGHT_) - 2, static_cast<int>(y_) + static_cast<int>(HEIGHT_) - 1}; // Comprueba solo los dos pixels de abajo
|
||||
const int LY = room_->checkLeftSlopes(&LEFT_SIDE);
|
||||
if (LY > -1) {
|
||||
y_ = LY - HEIGHT_;
|
||||
}
|
||||
}
|
||||
|
||||
// Si está bajando la rampa, recoloca al jugador
|
||||
if (isOnDownSlope() && state_ != PlayerState::JUMPING) {
|
||||
y_ += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Se mueve hacia la derecha
|
||||
else if (vx_ > 0.0f) {
|
||||
// Crea el rectangulo de proyección en el eje X para ver si colisiona
|
||||
SDL_FRect proj;
|
||||
proj.x = x_ + WIDTH_;
|
||||
proj.y = y_;
|
||||
proj.h = HEIGHT_;
|
||||
proj.w = ceil(vx_); // Para evitar que tenga un ancho de 0 pixels
|
||||
|
||||
#ifdef DEBUG
|
||||
debug_rect_x_ = proj;
|
||||
#endif
|
||||
|
||||
// Comprueba la colisión
|
||||
const int POS = room_->checkLeftSurfaces(&proj);
|
||||
|
||||
// Calcula la nueva posición
|
||||
if (POS == -1) {
|
||||
// Si no hay colisión
|
||||
x_ += vx_;
|
||||
} else {
|
||||
// Si hay colisión lo mueve hasta donde no colisiona
|
||||
x_ = POS - WIDTH_;
|
||||
}
|
||||
|
||||
// Si ha tocado alguna rampa mientras camina (sin saltar), asciende
|
||||
if (state_ != PlayerState::JUMPING) {
|
||||
const LineVertical RIGHT_SIDE = {static_cast<int>(x_) + static_cast<int>(WIDTH_) - 1, static_cast<int>(y_) + static_cast<int>(HEIGHT_) - 2, static_cast<int>(y_) + static_cast<int>(HEIGHT_) - 1}; // Comprueba solo los dos pixels de abajo
|
||||
const int RY = room_->checkRightSlopes(&RIGHT_SIDE);
|
||||
if (RY > -1) {
|
||||
y_ = RY - HEIGHT_;
|
||||
}
|
||||
}
|
||||
|
||||
// Si está bajando la rampa, recoloca al jugador
|
||||
if (isOnDownSlope() && state_ != PlayerState::JUMPING) {
|
||||
y_ += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Si ha salido del suelo, el jugador cae
|
||||
if (state_ == PlayerState::STANDING && !isOnFloor()) {
|
||||
setState(PlayerState::FALLING);
|
||||
|
||||
// Deja de estar enganchado a la superficie automatica
|
||||
auto_movement_ = false;
|
||||
}
|
||||
|
||||
// Si ha salido de una superficie automatica, detiene el movimiento automatico
|
||||
if (state_ == PlayerState::STANDING && isOnFloor() && !isOnAutoSurface()) {
|
||||
// Deja de estar enganchado a la superficie automatica
|
||||
auto_movement_ = false;
|
||||
}
|
||||
|
||||
// Se mueve hacia arriba
|
||||
if (vy_ < 0.0f) {
|
||||
// Crea el rectangulo de proyección en el eje Y para ver si colisiona
|
||||
SDL_FRect proj;
|
||||
proj.x = static_cast<int>(x_);
|
||||
proj.y = static_cast<int>(y_ + vy_);
|
||||
proj.h = static_cast<int>(std::ceil(std::fabs(vy_))); // Para evitar que tenga una altura de 0 pixels
|
||||
proj.w = WIDTH_;
|
||||
|
||||
#ifdef DEBUG
|
||||
debug_rect_y_ = proj;
|
||||
#endif
|
||||
|
||||
// Comprueba la colisión
|
||||
const int POS = room_->checkBottomSurfaces(&proj);
|
||||
|
||||
// Calcula la nueva posición
|
||||
if (POS == -1) {
|
||||
// Si no hay colisión
|
||||
y_ += vy_;
|
||||
} else {
|
||||
// Si hay colisión lo mueve hasta donde no colisiona y entra en caída
|
||||
y_ = POS + 1;
|
||||
setState(PlayerState::FALLING);
|
||||
}
|
||||
}
|
||||
|
||||
// Se mueve hacia abajo
|
||||
else if (vy_ > 0.0f) {
|
||||
// Crea el rectangulo de proyección en el eje Y para ver si colisiona
|
||||
SDL_FRect proj;
|
||||
proj.x = x_;
|
||||
proj.y = y_ + HEIGHT_;
|
||||
proj.h = ceil(vy_); // Para evitar que tenga una altura de 0 pixels
|
||||
proj.w = WIDTH_;
|
||||
|
||||
#ifdef DEBUG
|
||||
debug_rect_y_ = proj;
|
||||
#endif
|
||||
|
||||
// Comprueba la colisión con las superficies normales y las automáticas
|
||||
const float POS = std::max(room_->checkTopSurfaces(&proj), room_->checkAutoSurfaces(&proj));
|
||||
if (POS > -1) {
|
||||
// Si hay colisión lo mueve hasta donde no colisiona y pasa a estar sobre la superficie
|
||||
y_ = POS - HEIGHT_;
|
||||
setState(PlayerState::STANDING);
|
||||
|
||||
// Deja de estar enganchado a la superficie automatica
|
||||
auto_movement_ = false;
|
||||
} else {
|
||||
// Si no hay colisión con los muros, comprueba la colisión con las rampas
|
||||
if (state_ != PlayerState::JUMPING) { // Las rampas no se miran si se está saltando
|
||||
auto rect = toSDLRect(proj);
|
||||
const LineVertical LEFT_SIDE = {rect.x, rect.y, rect.y + rect.h - 1};
|
||||
const LineVertical RIGHT_SIDE = {rect.x + rect.w - 1, rect.y, rect.y + rect.h - 1};
|
||||
const float POINT = std::max(room_->checkRightSlopes(&RIGHT_SIDE), room_->checkLeftSlopes(&LEFT_SIDE));
|
||||
if (POINT > -1) {
|
||||
// No está saltando y hay colisión con una rampa
|
||||
// Calcula la nueva posición
|
||||
y_ = POINT - HEIGHT_;
|
||||
setState(PlayerState::STANDING);
|
||||
#ifdef DEBUG
|
||||
debug_color_ = static_cast<Uint8>(PaletteColor::YELLOW);
|
||||
debug_point_ = {x_ + (WIDTH_ / 2), POINT};
|
||||
#endif
|
||||
} else {
|
||||
// No está saltando y no hay colisón con una rampa
|
||||
// Calcula la nueva posición
|
||||
y_ += vy_;
|
||||
#ifdef DEBUG
|
||||
debug_color_ = static_cast<Uint8>(PaletteColor::RED);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// Esta saltando y no hay colisión con los muros
|
||||
// Calcula la nueva posición
|
||||
y_ += vy_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
placeSprite(); // Coloca el sprite en la nueva posición
|
||||
collider_box_ = getRect(); // Actualiza el rectangulo de colisión
|
||||
|
||||
#ifdef DEBUG
|
||||
Debug::get()->add("RECT_X: " + std::to_string(debug_rect_x_.x) + "," + std::to_string(debug_rect_x_.y) + "," + std::to_string(debug_rect_x_.w) + "," + std::to_string(debug_rect_x_.h));
|
||||
Debug::get()->add("RECT_Y: " + std::to_string(debug_rect_y_.x) + "," + std::to_string(debug_rect_y_.y) + "," + std::to_string(debug_rect_y_.w) + "," + std::to_string(debug_rect_y_.h));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Establece la animación del jugador
|
||||
void Player::animate() {
|
||||
if (vx_ != 0) {
|
||||
sprite_->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha finalizado el salto al alcanzar la altura de inicio
|
||||
void Player::checkJumpEnd() {
|
||||
if (state_ == PlayerState::JUMPING) {
|
||||
if (vy_ > 0) {
|
||||
if (y_ >= jump_init_pos_) {
|
||||
// Si alcanza la altura de salto inicial, pasa al estado de caída
|
||||
setState(PlayerState::FALLING);
|
||||
vy_ = MAX_VY_;
|
||||
jumping_counter_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calcula y reproduce el sonido de salto
|
||||
void Player::playJumpSound() {
|
||||
if (jumping_counter_ % 4 == 0) {
|
||||
JA_PlaySound(jumping_sound_[jumping_counter_ / 4]);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
Debug::get()->add("JUMP: " + std::to_string(jumping_counter_ / 4));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Calcula y reproduce el sonido de caer
|
||||
void Player::playFallSound() {
|
||||
if (falling_counter_ % 4 == 0) {
|
||||
JA_PlaySound(falling_sound_[std::min((falling_counter_ / 4), (int)falling_sound_.size() - 1)]);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
Debug::get()->add("FALL: " + std::to_string(falling_counter_ / 4));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Comprueba si el jugador tiene suelo debajo de los pies
|
||||
bool Player::isOnFloor() {
|
||||
bool on_floor = false;
|
||||
bool on_slope_l = false;
|
||||
bool on_slope_r = false;
|
||||
|
||||
updateFeet();
|
||||
|
||||
// Comprueba las superficies
|
||||
for (auto f : under_feet_) {
|
||||
on_floor |= room_->checkTopSurfaces(&f);
|
||||
on_floor |= room_->checkAutoSurfaces(&f);
|
||||
}
|
||||
|
||||
// Comprueba las rampas
|
||||
on_slope_l = room_->checkLeftSlopes(&under_feet_[0]);
|
||||
on_slope_r = room_->checkRightSlopes(&under_feet_[1]);
|
||||
|
||||
#ifdef DEBUG
|
||||
if (on_floor) {
|
||||
Debug::get()->add("ON_FLOOR");
|
||||
}
|
||||
|
||||
if (on_slope_l) {
|
||||
Debug::get()->add("ON_SLOPE_L: " + std::to_string(under_feet_[0].x) + "," + std::to_string(under_feet_[0].y));
|
||||
}
|
||||
|
||||
if (on_slope_r) {
|
||||
Debug::get()->add("ON_SLOPE_R: " + std::to_string(under_feet_[1].x) + "," + std::to_string(under_feet_[1].y));
|
||||
}
|
||||
#endif
|
||||
|
||||
return on_floor || on_slope_l || on_slope_r;
|
||||
}
|
||||
|
||||
// Comprueba si el jugador esta sobre una superficie automática
|
||||
bool Player::isOnAutoSurface() {
|
||||
bool on_auto_surface = false;
|
||||
|
||||
updateFeet();
|
||||
|
||||
// Comprueba las superficies
|
||||
for (auto f : under_feet_) {
|
||||
on_auto_surface |= room_->checkAutoSurfaces(&f);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if (on_auto_surface) {
|
||||
Debug::get()->add("ON_AUTO_SURFACE");
|
||||
}
|
||||
#endif
|
||||
|
||||
return on_auto_surface;
|
||||
}
|
||||
|
||||
// Comprueba si el jugador está sobre una rampa hacia abajo
|
||||
bool Player::isOnDownSlope() {
|
||||
bool on_slope = false;
|
||||
|
||||
updateFeet();
|
||||
|
||||
// Cuando el jugador baja una escalera, se queda volando
|
||||
// Hay que mirar otro pixel más por debajo
|
||||
under_feet_[0].y += 1;
|
||||
under_feet_[1].y += 1;
|
||||
|
||||
// Comprueba las rampas
|
||||
on_slope |= room_->checkLeftSlopes(&under_feet_[0]);
|
||||
on_slope |= room_->checkRightSlopes(&under_feet_[1]);
|
||||
|
||||
#ifdef DEBUG
|
||||
if (on_slope) {
|
||||
Debug::get()->add("ON_DOWN_SLOPE");
|
||||
}
|
||||
#endif
|
||||
|
||||
return on_slope;
|
||||
}
|
||||
|
||||
// Comprueba que el jugador no toque ningun tile de los que matan
|
||||
bool Player::checkKillingTiles() {
|
||||
// Actualiza los puntos de colisión
|
||||
updateColliderPoints();
|
||||
|
||||
// Comprueba si hay contacto y retorna en cuanto se encuentra colisión
|
||||
for (const auto& c : collider_points_) {
|
||||
if (room_->getTile(c) == TileType::KILL) {
|
||||
is_alive_ = false; // Mata al jugador inmediatamente
|
||||
return true; // Retorna en cuanto se detecta una colisión
|
||||
}
|
||||
}
|
||||
|
||||
return false; // No se encontró ninguna colisión
|
||||
}
|
||||
|
||||
// Establece el color del jugador
|
||||
void Player::setColor() {
|
||||
if (options.cheats.invincible == Cheat::CheatState::ENABLED) {
|
||||
color_ = static_cast<Uint8>(PaletteColor::CYAN);
|
||||
} else if (options.cheats.infinite_lives == Cheat::CheatState::ENABLED) {
|
||||
color_ = static_cast<Uint8>(PaletteColor::YELLOW);
|
||||
} else {
|
||||
color_ = static_cast<Uint8>(PaletteColor::WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza los puntos de colisión
|
||||
void Player::updateColliderPoints() {
|
||||
const SDL_FRect rect = getRect();
|
||||
collider_points_[0] = {rect.x, rect.y};
|
||||
collider_points_[1] = {rect.x + 7, rect.y};
|
||||
collider_points_[2] = {rect.x + 7, rect.y + 7};
|
||||
collider_points_[3] = {rect.x, rect.y + 7};
|
||||
collider_points_[4] = {rect.x, rect.y + 8};
|
||||
collider_points_[5] = {rect.x + 7, rect.y + 8};
|
||||
collider_points_[6] = {rect.x + 7, rect.y + 15};
|
||||
collider_points_[7] = {rect.x, rect.y + 15};
|
||||
}
|
||||
|
||||
// Actualiza los puntos de los pies
|
||||
void Player::updateFeet() {
|
||||
const SDL_FPoint p = {x_, y_};
|
||||
|
||||
under_feet_[0] = {p.x, p.y + HEIGHT_};
|
||||
under_feet_[1] = {p.x + 7, p.y + HEIGHT_};
|
||||
|
||||
feet_[0] = {p.x, p.y + HEIGHT_ - 1};
|
||||
feet_[1] = {p.x + 7, p.y + HEIGHT_ - 1};
|
||||
}
|
||||
|
||||
// Cambia el estado del jugador
|
||||
void Player::setState(PlayerState value) {
|
||||
previous_state_ = state_;
|
||||
state_ = value;
|
||||
|
||||
checkState();
|
||||
}
|
||||
|
||||
// Inicializa los sonidos de salto y caida
|
||||
void Player::initSounds() {
|
||||
jumping_sound_.clear();
|
||||
falling_sound_.clear();
|
||||
|
||||
for (int i = 1; i <= 24; ++i) {
|
||||
std::string soundFile = "jump" + std::to_string(i) + ".wav";
|
||||
jumping_sound_.push_back(Resource::get()->getSound(soundFile));
|
||||
|
||||
if (i >= 11) {
|
||||
falling_sound_.push_back(Resource::get()->getSound(soundFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica los valores de spawn al jugador
|
||||
void Player::applySpawnValues(const PlayerSpawn& spawn) {
|
||||
x_ = spawn.x;
|
||||
y_ = spawn.y;
|
||||
vx_ = spawn.vx;
|
||||
vy_ = spawn.vy;
|
||||
jump_init_pos_ = spawn.jump_init_pos;
|
||||
state_ = spawn.state;
|
||||
sprite_->setFlip(spawn.flip);
|
||||
}
|
||||
|
||||
// Inicializa el sprite del jugador
|
||||
void Player::initSprite(const std::string& surface_path, const std::string& animations_path) {
|
||||
auto surface = Resource::get()->getSurface(surface_path);
|
||||
auto animations = Resource::get()->getAnimations(animations_path);
|
||||
|
||||
sprite_ = std::make_shared<SAnimatedSprite>(surface, animations);
|
||||
sprite_->setWidth(WIDTH_);
|
||||
sprite_->setHeight(HEIGHT_);
|
||||
sprite_->setCurrentAnimation("walk");
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// Pinta la información de debug del jugador
|
||||
void Player::renderDebugInfo() {
|
||||
if (Debug::get()->getEnabled()) {
|
||||
auto surface = Screen::get()->getRendererSurface();
|
||||
|
||||
// Pinta los underfeet
|
||||
surface->putPixel(under_feet_[0].x, under_feet_[0].y, static_cast<Uint8>(PaletteColor::BRIGHT_MAGENTA));
|
||||
surface->putPixel(under_feet_[1].x, under_feet_[1].y, static_cast<Uint8>(PaletteColor::BRIGHT_MAGENTA));
|
||||
|
||||
// Pinta rectangulo del jugador
|
||||
SDL_FRect rect = getRect();
|
||||
surface->drawRectBorder(&rect, static_cast<Uint8>(PaletteColor::BRIGHT_CYAN));
|
||||
|
||||
// Pinta el rectangulo de movimiento
|
||||
if (vx_ != 0.0f) {
|
||||
surface->fillRect(&debug_rect_x_, static_cast<Uint8>(PaletteColor::BRIGHT_RED));
|
||||
}
|
||||
if (vy_ != 0.0f) {
|
||||
surface->fillRect(&debug_rect_y_, static_cast<Uint8>(PaletteColor::BRIGHT_RED));
|
||||
}
|
||||
|
||||
// Pinta el punto de debug
|
||||
surface->putPixel(debug_point_.x, debug_point_.y, rand() % 16);
|
||||
}
|
||||
}
|
||||
#endif // DEBUG
|
||||
215
source2/Game/Entities/player.h
Normal file
215
source2/Game/Entities/player.h
Normal file
@@ -0,0 +1,215 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr, __shared_ptr_access
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "defines.h" // Para BORDER_TOP, BLOCK
|
||||
#include "room.h"
|
||||
#include "sprite/surface_animated_sprite.h" // Para SAnimatedSprite
|
||||
#include "utils.h" // Para Color
|
||||
struct JA_Sound_t; // lines 13-13
|
||||
|
||||
enum class PlayerState {
|
||||
STANDING,
|
||||
JUMPING,
|
||||
FALLING,
|
||||
};
|
||||
|
||||
struct PlayerSpawn {
|
||||
float x;
|
||||
float y;
|
||||
float vx;
|
||||
float vy;
|
||||
int jump_init_pos;
|
||||
PlayerState state;
|
||||
SDL_FlipMode flip;
|
||||
|
||||
// Constructor por defecto
|
||||
PlayerSpawn()
|
||||
: x(0),
|
||||
y(0),
|
||||
vx(0),
|
||||
vy(0),
|
||||
jump_init_pos(0),
|
||||
state(PlayerState::STANDING),
|
||||
flip(SDL_FLIP_NONE) {}
|
||||
|
||||
// Constructor
|
||||
PlayerSpawn(float x, float y, float vx, float vy, int jump_init_pos, PlayerState state, SDL_FlipMode flip)
|
||||
: x(x),
|
||||
y(y),
|
||||
vx(vx),
|
||||
vy(vy),
|
||||
jump_init_pos(jump_init_pos),
|
||||
state(state),
|
||||
flip(flip) {}
|
||||
};
|
||||
|
||||
struct PlayerData {
|
||||
PlayerSpawn spawn;
|
||||
std::string texture_path;
|
||||
std::string animations_path;
|
||||
std::shared_ptr<Room> room;
|
||||
|
||||
// Constructor
|
||||
PlayerData(PlayerSpawn spawn, std::string texture_path, std::string animations_path, std::shared_ptr<Room> room)
|
||||
: spawn(spawn),
|
||||
texture_path(texture_path),
|
||||
animations_path(animations_path),
|
||||
room(room) {}
|
||||
};
|
||||
|
||||
class Player {
|
||||
public:
|
||||
// Constantes
|
||||
static constexpr int WIDTH_ = 8; // Ancho del jugador
|
||||
static constexpr int HEIGHT_ = 16; // ALto del jugador
|
||||
static constexpr int MAX_FALLING_HEIGHT_ = BLOCK * 4; // Altura maxima permitida de caída.
|
||||
static constexpr float MAX_VY_ = 1.2f; // Velocidad máxima que puede alcanzar al desplazarse en vertical
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<Room> room_; // Objeto encargado de gestionar cada habitación del juego
|
||||
std::shared_ptr<SAnimatedSprite> sprite_; // Sprite del jugador
|
||||
|
||||
// Variables
|
||||
float x_; // Posición del jugador en el eje X
|
||||
float y_; // Posición del jugador en el eje Y
|
||||
float vx_; // Velocidad/desplazamiento del jugador en el eje X
|
||||
float vy_; // Velocidad/desplazamiento del jugador en el eje Y
|
||||
Uint8 color_; // Color del jugador
|
||||
SDL_FRect collider_box_; // Caja de colisión con los enemigos u objetos
|
||||
std::vector<SDL_FPoint> collider_points_; // Puntos de colisión con el mapa
|
||||
std::vector<SDL_FPoint> under_feet_; // Contiene los puntos que hay bajo cada pie del jugador
|
||||
std::vector<SDL_FPoint> feet_; // Contiene los puntos que hay en el pie del jugador
|
||||
PlayerState state_; // Estado en el que se encuentra el jugador. Util apara saber si está saltando o cayendo
|
||||
PlayerState previous_state_; // Estado previo en el que se encontraba el jugador
|
||||
bool is_on_border_ = false; // Indica si el jugador esta en uno de los cuatro bordes de la pantalla
|
||||
bool is_alive_ = true; // Indica si el jugador esta vivo o no
|
||||
bool is_paused_ = false; // Indica si el jugador esta en modo pausa
|
||||
bool auto_movement_ = false; // Indica si esta siendo arrastrado por una superficie automatica
|
||||
RoomBorder border_ = RoomBorder::TOP; // Indica en cual de los cuatro bordes se encuentra
|
||||
SDL_FRect last_position_; // Contiene la ultima posición del jugador, por si hay que deshacer algun movimiento
|
||||
int jump_init_pos_; // Valor del eje Y en el que se inicia el salto
|
||||
std::vector<JA_Sound_t*> jumping_sound_; // Vecor con todos los sonidos del salto
|
||||
std::vector<JA_Sound_t*> falling_sound_; // Vecor con todos los sonidos de la caída
|
||||
int jumping_counter_ = 0; // Cuenta el tiempo de salto
|
||||
int falling_counter_ = 0; // Cuenta el tiempo de caida
|
||||
|
||||
#ifdef DEBUG
|
||||
SDL_FRect debug_rect_x_; // Rectangulo de desplazamiento para el modo debug
|
||||
SDL_FRect debug_rect_y_; // Rectangulo de desplazamiento para el modo debug
|
||||
Uint8 debug_color_; // Color del recuadro de debug del jugador
|
||||
SDL_FPoint debug_point_; // Punto para debug
|
||||
#endif
|
||||
|
||||
// Comprueba las entradas y modifica variables
|
||||
void checkInput();
|
||||
|
||||
// Comprueba si se halla en alguno de los cuatro bordes
|
||||
void checkBorders();
|
||||
|
||||
// Comprueba el estado del jugador
|
||||
void checkState();
|
||||
|
||||
// Aplica gravedad al jugador
|
||||
void applyGravity();
|
||||
|
||||
// Recalcula la posición del jugador y su animación
|
||||
void move();
|
||||
|
||||
// Establece la animación del jugador
|
||||
void animate();
|
||||
|
||||
// Comprueba si ha finalizado el salto al alcanzar la altura de inicio
|
||||
void checkJumpEnd();
|
||||
|
||||
// Calcula y reproduce el sonido de salto
|
||||
void playJumpSound();
|
||||
|
||||
// Calcula y reproduce el sonido de caer
|
||||
void playFallSound();
|
||||
|
||||
// Comprueba si el jugador tiene suelo debajo de los pies
|
||||
bool isOnFloor();
|
||||
|
||||
// Comprueba si el jugador esta sobre una superficie automática
|
||||
bool isOnAutoSurface();
|
||||
|
||||
// Comprueba si el jugador está sobre una rampa hacia abajo
|
||||
bool isOnDownSlope();
|
||||
|
||||
// Comprueba que el jugador no toque ningun tile de los que matan
|
||||
bool checkKillingTiles();
|
||||
|
||||
// Actualiza los puntos de colisión
|
||||
void updateColliderPoints();
|
||||
|
||||
// Actualiza los puntos de los pies
|
||||
void updateFeet();
|
||||
|
||||
// Cambia el estado del jugador
|
||||
void setState(PlayerState value);
|
||||
|
||||
// Inicializa los sonidos de salto y caida
|
||||
void initSounds();
|
||||
|
||||
// Coloca el sprite en la posición del jugador
|
||||
void placeSprite() { sprite_->setPos(x_, y_); }
|
||||
|
||||
// Aplica los valores de spawn al jugador
|
||||
void applySpawnValues(const PlayerSpawn& spawn);
|
||||
|
||||
// Inicializa el sprite del jugador
|
||||
void initSprite(const std::string& texture_path, const std::string& animations_path);
|
||||
|
||||
#ifdef DEBUG
|
||||
// Pinta la información de debug del jugador
|
||||
void renderDebugInfo();
|
||||
#endif
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
explicit Player(const PlayerData& player);
|
||||
|
||||
// Destructor
|
||||
~Player() = default;
|
||||
|
||||
// Pinta el enemigo en pantalla
|
||||
void render();
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void update();
|
||||
|
||||
// Indica si el jugador esta en uno de los cuatro bordes de la pantalla
|
||||
bool getOnBorder() { return is_on_border_; }
|
||||
|
||||
// Indica en cual de los cuatro bordes se encuentra
|
||||
RoomBorder getBorder() { return border_; }
|
||||
|
||||
// Cambia al jugador de un borde al opuesto. Util para el cambio de pantalla
|
||||
void switchBorders();
|
||||
|
||||
// Obtiene el rectangulo que delimita al jugador
|
||||
SDL_FRect getRect() { return {x_, y_, WIDTH_, HEIGHT_}; }
|
||||
|
||||
// Obtiene el rectangulo de colision del jugador
|
||||
SDL_FRect& getCollider() { return collider_box_; }
|
||||
|
||||
// Obtiene el estado de reaparición del jugador
|
||||
PlayerSpawn getSpawnParams() { return {x_, y_, vx_, vy_, jump_init_pos_, state_, sprite_->getFlip()}; }
|
||||
|
||||
// Establece el color del jugador
|
||||
void setColor();
|
||||
|
||||
// Establece la habitación en la que se encuentra el jugador
|
||||
void setRoom(std::shared_ptr<Room> room) { room_ = room; }
|
||||
|
||||
// Comprueba si el jugador esta vivo
|
||||
bool isAlive() { return is_alive_; }
|
||||
|
||||
// Pone el jugador en modo pausa
|
||||
void setPaused(bool value) { is_paused_ = value; }
|
||||
};
|
||||
175
source2/Game/Gameplay/cheevos.cpp
Normal file
175
source2/Game/Gameplay/cheevos.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "cheevos.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stddef.h> // Para NULL
|
||||
|
||||
#include <fstream> // Para basic_ostream, operator<<, basic_ofstream
|
||||
#include <iostream> // Para cout, cerr
|
||||
|
||||
#include "options.h" // Para Options, options
|
||||
#include "ui/notifier.h" // Para Notifier
|
||||
|
||||
// [SINGLETON]
|
||||
Cheevos* Cheevos::cheevos_ = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
void Cheevos::init(const std::string& file) {
|
||||
Cheevos::cheevos_ = new Cheevos(file);
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
void Cheevos::destroy() {
|
||||
delete Cheevos::cheevos_;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
Cheevos* Cheevos::get() {
|
||||
return Cheevos::cheevos_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Cheevos::Cheevos(const std::string& file)
|
||||
: file_(file) {
|
||||
init();
|
||||
loadFromFile();
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Cheevos::~Cheevos() {
|
||||
saveToFile();
|
||||
}
|
||||
|
||||
// Inicializa los logros
|
||||
void Cheevos::init() {
|
||||
cheevos_list_.clear();
|
||||
cheevos_list_.emplace_back(1, "SHINY THINGS", "Get 25% of the items", 2);
|
||||
cheevos_list_.emplace_back(2, "HALF THE WORK", "Get 50% of the items", 2);
|
||||
cheevos_list_.emplace_back(3, "GETTING THERE", "Get 75% of the items", 2);
|
||||
cheevos_list_.emplace_back(4, "THE COLLECTOR", "Get 100% of the items", 2);
|
||||
cheevos_list_.emplace_back(5, "WANDERING AROUND", "Visit 20 rooms", 2);
|
||||
cheevos_list_.emplace_back(6, "I GOT LOST", "Visit 40 rooms", 2);
|
||||
cheevos_list_.emplace_back(7, "I LIKE TO EXPLORE", "Visit all rooms", 2);
|
||||
cheevos_list_.emplace_back(8, "FINISH THE GAME", "Complete the game", 2);
|
||||
cheevos_list_.emplace_back(9, "I WAS SUCKED BY A HOLE", "Complete the game without entering the jail", 2);
|
||||
cheevos_list_.emplace_back(10, "MY LITTLE PROJECTS", "Complete the game with all items", 2);
|
||||
cheevos_list_.emplace_back(11, "I LIKE MY MULTICOLOURED FRIENDS", "Complete the game without dying", 2);
|
||||
cheevos_list_.emplace_back(12, "SHIT PROJECTS DONE FAST", "Complete the game in under 30 minutes", 2);
|
||||
}
|
||||
|
||||
// Busca un logro por id y devuelve el indice
|
||||
int Cheevos::find(int id) {
|
||||
for (int i = 0; i < (int)cheevos_list_.size(); ++i) {
|
||||
if (cheevos_list_[i].id == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Desbloquea un logro
|
||||
void Cheevos::unlock(int id) {
|
||||
const int INDEX = find(id);
|
||||
|
||||
// Si el índice es inválido, el logro no es válido, ya está completado o el sistema de logros no está habilitado, no hacemos nada
|
||||
if (INDEX == -1 || !cheevos_list_.at(INDEX).obtainable || cheevos_list_.at(INDEX).completed || !enabled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Marcar el logro como completado
|
||||
cheevos_list_.at(INDEX).completed = true;
|
||||
|
||||
// Mostrar notificación en la pantalla
|
||||
Notifier::get()->show({"ACHIEVEMENT UNLOCKED!", cheevos_list_.at(INDEX).caption}, NotificationText::CENTER, CHEEVO_NOTIFICATION_DURATION /*, cheevos_list_.at(INDEX).icon*/);
|
||||
|
||||
// Guardar el estado de los logros
|
||||
saveToFile();
|
||||
}
|
||||
|
||||
// Invalida un logro
|
||||
void Cheevos::setUnobtainable(int id) {
|
||||
const int index = find(id);
|
||||
|
||||
// Si el índice es válido, se invalida el logro
|
||||
if (index != -1) {
|
||||
cheevos_list_.at(index).obtainable = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Carga el estado de los logros desde un fichero
|
||||
void Cheevos::loadFromFile() {
|
||||
std::ifstream file(file_, std::ios::binary);
|
||||
|
||||
// El fichero no existe
|
||||
if (!file) {
|
||||
if (options.console) {
|
||||
std::cout << "Warning: Unable to open " << file_ << "! Creating new file..." << std::endl;
|
||||
}
|
||||
|
||||
// Crea el fichero en modo escritura (binario)
|
||||
std::ofstream newFile(file_, std::ios::binary);
|
||||
|
||||
if (newFile) {
|
||||
if (options.console) {
|
||||
std::cout << "New " << file_ << " created!" << std::endl;
|
||||
}
|
||||
|
||||
// Guarda la información
|
||||
for (const auto& cheevo : cheevos_list_) {
|
||||
newFile.write(reinterpret_cast<const char*>(&cheevo.completed), sizeof(bool));
|
||||
}
|
||||
} else {
|
||||
if (options.console) {
|
||||
std::cerr << "Error: Unable to create " << file_ << "!" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
// El fichero existe
|
||||
else {
|
||||
if (options.console) {
|
||||
std::cout << "Reading " << file_ << std::endl;
|
||||
}
|
||||
|
||||
// Carga los datos
|
||||
for (auto& cheevo : cheevos_list_) {
|
||||
file.read(reinterpret_cast<char*>(&cheevo.completed), sizeof(bool));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Guarda el estado de los logros en un fichero
|
||||
void Cheevos::saveToFile() {
|
||||
// Abre el fichero en modo escritura (binario)
|
||||
SDL_IOStream* file = SDL_IOFromFile(this->file_.c_str(), "w+b");
|
||||
if (file != nullptr) {
|
||||
// Guarda la información
|
||||
for (int i = 0; i < (int)cheevos_list_.size(); ++i) {
|
||||
SDL_WriteIO(file, &cheevos_list_[i].completed, sizeof(bool));
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
SDL_CloseIO(file);
|
||||
} else {
|
||||
if (options.console) {
|
||||
std::cout << "Error: Unable to save file! " << SDL_GetError() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Devuelve el número total de logros desbloqueados
|
||||
int Cheevos::getTotalUnlockedAchievements() {
|
||||
int count = 0;
|
||||
for (const auto& cheevo : cheevos_list_) {
|
||||
if (cheevo.completed) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Elimina el estado "no obtenible"
|
||||
void Cheevos::clearUnobtainableState() {
|
||||
for (auto& cheevo : cheevos_list_) {
|
||||
cheevo.obtainable = true;
|
||||
}
|
||||
}
|
||||
83
source2/Game/Gameplay/cheevos.h
Normal file
83
source2/Game/Gameplay/cheevos.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
// Struct para los logros
|
||||
struct Achievement
|
||||
{
|
||||
int id; // Identificador del logro
|
||||
std::string caption; // Texto con el nombre del logro
|
||||
std::string description; // Texto que describe el logro
|
||||
int icon; // Indice del icono a utilizar en la notificación
|
||||
bool completed; // Indica si se ha obtenido el logro
|
||||
bool obtainable; // Indica si se puede obtener el logro
|
||||
|
||||
// Constructor vacío
|
||||
Achievement() : id(0), icon(0), completed(false), obtainable(true) {}
|
||||
|
||||
// Constructor parametrizado
|
||||
Achievement(int id, const std::string &caption, const std::string &description, int icon, bool completed = false, bool obtainable = true)
|
||||
: id(id), caption(caption), description(description), icon(icon), completed(completed), obtainable(obtainable) {}
|
||||
};
|
||||
|
||||
class Cheevos
|
||||
{
|
||||
private:
|
||||
// [SINGLETON] Objeto privado
|
||||
static Cheevos *cheevos_;
|
||||
|
||||
// Variables
|
||||
std::vector<Achievement> cheevos_list_; // Listado de logros
|
||||
bool enabled_ = true; // Indica si los logros se pueden obtener
|
||||
std::string file_; // Fichero donde leer/almacenar el estado de los logros
|
||||
|
||||
// Inicializa los logros
|
||||
void init();
|
||||
|
||||
// Busca un logro por id y devuelve el índice
|
||||
int find(int id);
|
||||
|
||||
// Carga el estado de los logros desde un fichero
|
||||
void loadFromFile();
|
||||
|
||||
// Guarda el estado de los logros en un fichero
|
||||
void saveToFile();
|
||||
|
||||
// Constructor
|
||||
explicit Cheevos(const std::string &file);
|
||||
|
||||
// Destructor
|
||||
~Cheevos();
|
||||
|
||||
public:
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
static void init(const std::string &file);
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
static void destroy();
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
static Cheevos *get();
|
||||
|
||||
// Desbloquea un logro
|
||||
void unlock(int id);
|
||||
|
||||
// Invalida un logro
|
||||
void setUnobtainable(int id);
|
||||
|
||||
// Elimina el estado "no obtenible"
|
||||
void clearUnobtainableState();
|
||||
|
||||
// Habilita o deshabilita los logros
|
||||
void enable(bool value) { enabled_ = value; }
|
||||
|
||||
// Lista los logros
|
||||
const std::vector<Achievement>& list() const { return cheevos_list_; }
|
||||
|
||||
// Devuelve el número total de logros desbloqueados
|
||||
int getTotalUnlockedAchievements();
|
||||
|
||||
// Devuelve el número total de logros
|
||||
int size() { return cheevos_list_.size(); }
|
||||
};
|
||||
75
source2/Game/Gameplay/item_tracker.cpp
Normal file
75
source2/Game/Gameplay/item_tracker.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "item_tracker.h"
|
||||
|
||||
// [SINGLETON]
|
||||
ItemTracker* ItemTracker::item_tracker_ = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
void ItemTracker::init() {
|
||||
ItemTracker::item_tracker_ = new ItemTracker();
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
void ItemTracker::destroy() {
|
||||
delete ItemTracker::item_tracker_;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
ItemTracker* ItemTracker::get() {
|
||||
return ItemTracker::item_tracker_;
|
||||
}
|
||||
|
||||
// Comprueba si el objeto ya ha sido cogido
|
||||
bool ItemTracker::hasBeenPicked(const std::string& name, SDL_FPoint pos) {
|
||||
// Primero busca si ya hay una entrada con ese nombre
|
||||
if (const int index = findByName(name); index != -1) {
|
||||
// Luego busca si existe ya una entrada con esa posición
|
||||
if (findByPos(index, pos) != -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Añade el objeto a la lista de objetos cogidos
|
||||
void ItemTracker::addItem(const std::string& name, SDL_FPoint pos) {
|
||||
// Comprueba si el objeto no ha sido recogido con anterioridad
|
||||
if (!hasBeenPicked(name, pos)) {
|
||||
// Primero busca si ya hay una entrada con ese nombre
|
||||
if (const int index = findByName(name); index != -1) {
|
||||
item_list_.at(index).pos.push_back(pos);
|
||||
}
|
||||
// En caso contrario crea la entrada
|
||||
else {
|
||||
item_list_.emplace_back(name, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Busca una entrada en la lista por nombre
|
||||
int ItemTracker::findByName(const std::string& name) {
|
||||
int i = 0;
|
||||
|
||||
for (const auto& l : item_list_) {
|
||||
if (l.name == name) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Busca una entrada en la lista por posición
|
||||
int ItemTracker::findByPos(int index, SDL_FPoint pos) {
|
||||
int i = 0;
|
||||
|
||||
for (const auto& l : item_list_[index].pos) {
|
||||
if ((l.x == pos.x) && (l.y == pos.y)) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
54
source2/Game/Gameplay/item_tracker.h
Normal file
54
source2/Game/Gameplay/item_tracker.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string, basic_string
|
||||
#include <vector> // Para vector
|
||||
|
||||
struct ItemTrackerData {
|
||||
std::string name; // Nombre de la habitación donde se encuentra el objeto
|
||||
std::vector<SDL_FPoint> pos; // Lista de objetos cogidos de la habitación
|
||||
|
||||
// Constructor
|
||||
ItemTrackerData(const std::string& name, const SDL_FPoint& position)
|
||||
: name(name) {
|
||||
pos.push_back(position);
|
||||
}
|
||||
};
|
||||
|
||||
class ItemTracker {
|
||||
private:
|
||||
// [SINGLETON] Objeto privado
|
||||
static ItemTracker* item_tracker_;
|
||||
|
||||
// Variables
|
||||
std::vector<ItemTrackerData> item_list_; // Lista con todos los objetos recogidos
|
||||
|
||||
// Busca una entrada en la lista por nombre
|
||||
int findByName(const std::string& name);
|
||||
|
||||
// Busca una entrada en la lista por posición
|
||||
int findByPos(int index, SDL_FPoint pos);
|
||||
|
||||
// Constructor
|
||||
ItemTracker() = default;
|
||||
|
||||
// Destructor
|
||||
~ItemTracker() = default;
|
||||
|
||||
public:
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
static void init();
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
static void destroy();
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
static ItemTracker* get();
|
||||
|
||||
// Comprueba si el objeto ya ha sido cogido
|
||||
bool hasBeenPicked(const std::string& name, SDL_FPoint pos);
|
||||
|
||||
// Añade el objeto a la lista de objetos cogidos
|
||||
void addItem(const std::string& name, SDL_FPoint pos);
|
||||
};
|
||||
219
source2/Game/Gameplay/options.cpp
Normal file
219
source2/Game/Gameplay/options.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
#include "options.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para find_if
|
||||
#include <cctype> // Para isspace
|
||||
#include <fstream> // Para basic_ostream, operator<<, basic_ofstream
|
||||
#include <functional> // Para function
|
||||
#include <iostream> // Para cout, cerr
|
||||
#include <sstream> // Para basic_istringstream
|
||||
#include <string> // Para char_traits, string, operator<<, hash
|
||||
#include <unordered_map> // Para unordered_map, operator==, _Node_const_i...
|
||||
#include <utility> // Para pair
|
||||
|
||||
// Variables
|
||||
Options options;
|
||||
|
||||
bool setOptions(const std::string& var, const std::string& value);
|
||||
|
||||
// Crea e inicializa las opciones del programa
|
||||
void initOptions() {
|
||||
options = Options();
|
||||
|
||||
#ifdef DEBUG
|
||||
options.section = SectionState(Section::ENDING2, Subsection::LOGO_TO_INTRO);
|
||||
options.console = true;
|
||||
#else
|
||||
options.section = SectionState(Section::LOGO, Subsection::LOGO_TO_INTRO);
|
||||
options.console = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Carga las opciones desde un fichero
|
||||
bool loadOptionsFromFile(const std::string& file_path) {
|
||||
// Indicador de éxito en la carga
|
||||
bool success = true;
|
||||
|
||||
// Versión actual del fichero
|
||||
const std::string configVersion = options.version;
|
||||
options.version = "";
|
||||
|
||||
// Variables para manejar el fichero
|
||||
std::ifstream file(file_path);
|
||||
|
||||
// Si el fichero se puede abrir
|
||||
if (file.good()) {
|
||||
// Procesa el fichero línea a línea
|
||||
if (options.console) {
|
||||
std::cout << "Reading file config.txt\n";
|
||||
}
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
// Elimina espacios en blanco iniciales y finales
|
||||
line = std::string(std::find_if(line.begin(), line.end(), [](int ch) { return !std::isspace(ch); }),
|
||||
line.end());
|
||||
line.erase(std::find_if(line.rbegin(), line.rend(), [](int ch) { return !std::isspace(ch); })
|
||||
.base(),
|
||||
line.end());
|
||||
|
||||
// Ignora líneas vacías o comentarios
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Usa un stringstream para dividir la línea en dos partes
|
||||
std::istringstream iss(line);
|
||||
std::string key, value;
|
||||
|
||||
if (iss >> key >> value) {
|
||||
if (!setOptions(key, value)) {
|
||||
if (options.console) {
|
||||
std::cout << "Warning: file config.txt\n";
|
||||
std::cout << "unknown parameter " << key << std::endl;
|
||||
}
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
if (options.console) {
|
||||
std::cout << "Closing file config.txt\n\n";
|
||||
}
|
||||
file.close();
|
||||
} else {
|
||||
// Crea el fichero con los valores por defecto
|
||||
saveOptionsToFile(file_path);
|
||||
}
|
||||
|
||||
// Si la versión de fichero no coincide, crea un fichero nuevo con los valores por defecto
|
||||
if (configVersion != options.version) {
|
||||
initOptions();
|
||||
saveOptionsToFile(file_path);
|
||||
if (options.console) {
|
||||
std::cout << "Wrong config file: initializing options.\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Guarda las opciones en un fichero
|
||||
bool saveOptionsToFile(const std::string& file_path) {
|
||||
// Crea y abre el fichero de texto
|
||||
std::ofstream file(file_path);
|
||||
bool success = file.is_open(); // Verifica si el archivo se abrió correctamente
|
||||
|
||||
if (!success) // Si no se pudo abrir el archivo, muestra un mensaje de error y devuelve false
|
||||
{
|
||||
if (options.console) {
|
||||
std::cerr << "Error: Unable to open file " << file_path << " for writing." << std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.console) {
|
||||
std::cout << file_path << " open for writing" << std::endl;
|
||||
}
|
||||
|
||||
// Escribe en el fichero
|
||||
file << "# Versión de la configuración\n";
|
||||
file << "version " << options.version << "\n";
|
||||
|
||||
file << "\n## CONTROL\n";
|
||||
file << "# Esquema de control: 0 = Cursores, 1 = OPQ, 2 = WAD\n";
|
||||
file << "keys " << static_cast<int>(options.keys) << "\n";
|
||||
|
||||
file << "\n## WINDOW\n";
|
||||
file << "# Zoom de la ventana: 1 = Normal, 2 = Doble, 3 = Triple, ...\n";
|
||||
file << "window.zoom " << options.window.zoom << "\n";
|
||||
|
||||
file << "\n## VIDEO\n";
|
||||
file << "# Modo de video: 0 = Ventana, 1 = Pantalla completa, 2 = Pantalla completa (escritorio)\n";
|
||||
file << "video.mode " << options.video.fullscreen << "\n\n";
|
||||
file << "# Filtro de pantalla: 0 = Nearest, 1 = Linear\n";
|
||||
file << "video.filter " << static_cast<int>(options.video.filter) << "\n\n";
|
||||
file << "# Shaders: 1 = Activado, 0 = Desactivado\n";
|
||||
file << "video.shaders " << boolToString(options.video.shaders) << "\n\n";
|
||||
file << "# Sincronización vertical: 1 = Activado, 0 = Desactivado\n";
|
||||
file << "video.vertical_sync " << boolToString(options.video.vertical_sync) << "\n\n";
|
||||
file << "# Escalado entero: 1 = Activado, 0 = Desactivado\n";
|
||||
file << "video.integer_scale " << boolToString(options.video.integer_scale) << "\n\n";
|
||||
file << "# Mantener aspecto: 1 = Activado, 0 = Desactivado\n";
|
||||
file << "video.keep_aspect " << boolToString(options.video.keep_aspect) << "\n\n";
|
||||
file << "# Borde: 1 = Activado, 0 = Desactivado\n";
|
||||
file << "video.border.enabled " << boolToString(options.video.border.enabled) << "\n\n";
|
||||
file << "# Ancho del borde\n";
|
||||
file << "video.border.width " << options.video.border.width << "\n\n";
|
||||
file << "# Alto del borde\n";
|
||||
file << "video.border.height " << options.video.border.height << "\n\n";
|
||||
file << "# Paleta\n";
|
||||
file << "video.palette " << options.video.palette << "\n";
|
||||
|
||||
// Cierra el fichero
|
||||
file.close();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool setOptions(const std::string& var, const std::string& value) {
|
||||
static const std::unordered_map<std::string, std::function<void(const std::string&)>> optionHandlers = {
|
||||
{"version", [](const std::string& v) { options.version = v; }},
|
||||
{"keys", [](const std::string& v) {
|
||||
int val = safeStoi(v, static_cast<int>(DEFAULT_CONTROL_SCHEME));
|
||||
if (val == static_cast<int>(ControlScheme::CURSOR) || val == static_cast<int>(ControlScheme::OPQA) || val == static_cast<int>(ControlScheme::WASD)) {
|
||||
options.keys = static_cast<ControlScheme>(val);
|
||||
} else {
|
||||
options.keys = DEFAULT_CONTROL_SCHEME;
|
||||
}
|
||||
}},
|
||||
{"window.zoom", [](const std::string& v) {
|
||||
int val = safeStoi(v, DEFAULT_WINDOW_ZOOM);
|
||||
if (val > 0) {
|
||||
options.window.zoom = val;
|
||||
} else {
|
||||
options.window.zoom = DEFAULT_WINDOW_ZOOM;
|
||||
}
|
||||
}},
|
||||
{"video.mode", [](const std::string& v) { options.video.fullscreen = stringToBool(v); }},
|
||||
{"video.filter", [](const std::string& v) {
|
||||
int val = safeStoi(v, static_cast<int>(DEFAULT_VIDEO_FILTER));
|
||||
if (val == static_cast<int>(ScreenFilter::NEAREST) || val == static_cast<int>(ScreenFilter::LINEAR)) {
|
||||
options.video.filter = static_cast<ScreenFilter>(val);
|
||||
} else {
|
||||
options.video.filter = DEFAULT_VIDEO_FILTER;
|
||||
}
|
||||
}},
|
||||
{"video.shaders", [](const std::string& v) { options.video.shaders = stringToBool(v); }},
|
||||
{"video.vertical_sync", [](const std::string& v) { options.video.vertical_sync = stringToBool(v); }},
|
||||
{"video.integer_scale", [](const std::string& v) { options.video.integer_scale = stringToBool(v); }},
|
||||
{"video.keep_aspect", [](const std::string& v) { options.video.keep_aspect = stringToBool(v); }},
|
||||
{"video.border.enabled", [](const std::string& v) { options.video.border.enabled = stringToBool(v); }},
|
||||
{"video.border.width", [](const std::string& v) {
|
||||
int val = safeStoi(v, DEFAULT_BORDER_WIDTH);
|
||||
if (val > 0) {
|
||||
options.video.border.width = val;
|
||||
} else {
|
||||
options.video.border.width = DEFAULT_BORDER_WIDTH;
|
||||
}
|
||||
}},
|
||||
{"video.border.height", [](const std::string& v) {
|
||||
int val = safeStoi(v, DEFAULT_BORDER_HEIGHT);
|
||||
if (val > 0) {
|
||||
options.video.border.height = val;
|
||||
} else {
|
||||
options.video.border.height = DEFAULT_BORDER_HEIGHT;
|
||||
}
|
||||
}},
|
||||
{"video.palette", [](const std::string& v) {
|
||||
options.video.palette = v;
|
||||
}}};
|
||||
|
||||
auto it = optionHandlers.find(var);
|
||||
if (it != optionHandlers.end()) {
|
||||
it->second(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
424
source2/Game/Gameplay/options.h
Normal file
424
source2/Game/Gameplay/options.h
Normal file
@@ -0,0 +1,424 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string> // Para string, basic_string
|
||||
|
||||
#include "screen.h" // Para ScreenFilter
|
||||
#include "utils.h" // Para Color, Palette
|
||||
|
||||
// Secciones del programa
|
||||
enum class Section {
|
||||
LOGO,
|
||||
LOADING_SCREEN,
|
||||
TITLE,
|
||||
CREDITS,
|
||||
GAME,
|
||||
DEMO,
|
||||
GAME_OVER,
|
||||
ENDING,
|
||||
ENDING2,
|
||||
QUIT
|
||||
};
|
||||
|
||||
// Subsecciones
|
||||
enum class Subsection {
|
||||
NONE,
|
||||
LOGO_TO_INTRO,
|
||||
LOGO_TO_TITLE,
|
||||
TITLE_WITH_LOADING_SCREEN,
|
||||
TITLE_WITHOUT_LOADING_SCREEN
|
||||
};
|
||||
|
||||
// Posiciones de las notificaciones
|
||||
enum class NotificationPosition {
|
||||
UPPER_LEFT,
|
||||
UPPER_CENTER,
|
||||
UPPER_RIGHT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM_CENTER,
|
||||
BOTTOM_RIGHT,
|
||||
TOP,
|
||||
BOTTOM,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
CENTER,
|
||||
UNKNOWN,
|
||||
};
|
||||
|
||||
// Tipos de control de teclado
|
||||
enum class ControlScheme {
|
||||
CURSOR,
|
||||
OPQA,
|
||||
WASD
|
||||
};
|
||||
|
||||
// Constantes
|
||||
constexpr int DEFAULT_GAME_WIDTH = 256; // Ancho de la ventana por defecto
|
||||
constexpr int DEFAULT_GAME_HEIGHT = 192; // Alto de la ventana por defecto
|
||||
constexpr int DEFAULT_WINDOW_ZOOM = 2; // Zoom de la ventana por defecto
|
||||
constexpr bool DEFAULT_VIDEO_MODE = false; // Modo de pantalla completa por defecto
|
||||
constexpr ScreenFilter DEFAULT_VIDEO_FILTER = ScreenFilter::NEAREST; // Filtro por defecto
|
||||
constexpr bool DEFAULT_VIDEO_VERTICAL_SYNC = true; // Vsync activado por defecto
|
||||
constexpr bool DEFAULT_VIDEO_SHADERS = false; // Shaders desactivados por defecto
|
||||
constexpr bool DEFAULT_VIDEO_INTEGER_SCALE = true; // Escalado entero activado por defecto
|
||||
constexpr bool DEFAULT_VIDEO_KEEP_ASPECT = true; // Mantener aspecto activado por defecto
|
||||
constexpr bool DEFAULT_BORDER_ENABLED = true; // Borde activado por defecto
|
||||
constexpr int DEFAULT_BORDER_WIDTH = 32; // Ancho del borde por defecto
|
||||
constexpr int DEFAULT_BORDER_HEIGHT = 24; // Alto del borde por defecto
|
||||
constexpr int DEFAULT_SOUND_VOLUME = 100; // Volumen por defecto de los efectos de sonido
|
||||
constexpr bool DEFAULT_SOUND_ENABLED = true; // Sonido habilitado por defecto
|
||||
constexpr int DEFAULT_MUSIC_VOLUME = 80; // Volumen por defecto de la musica
|
||||
constexpr bool DEFAULT_MUSIC_ENABLED = true; // Musica habilitada por defecto
|
||||
constexpr int DEFAULT_AUDIO_VOLUME = 100; // Volumen por defecto
|
||||
constexpr bool DEFAULT_AUDIO_ENABLED = true; // Audio por defecto
|
||||
constexpr const char* DEFAULT_PALETTE = "zx-spectrum"; // Paleta por defecto
|
||||
constexpr Section DEFAULT_SECTION = Section::LOGO; // Sección por defecto
|
||||
constexpr Subsection DEFAULT_SUBSECTION = Subsection::LOGO_TO_INTRO; // Subsección por defecto
|
||||
constexpr ControlScheme DEFAULT_CONTROL_SCHEME = ControlScheme::CURSOR; // Control por defecto
|
||||
constexpr NotificationPosition DEFAULT_NOTIFICATION_POSITION = NotificationPosition::UPPER_LEFT; // Posición de las notificaciones por defecto
|
||||
constexpr bool DEFAULT_NOTIFICATION_SOUND = true; // Sonido de las notificaciones por defecto
|
||||
const Uint8 DEFAULT_NOTIFICATION_COLOR = static_cast<Uint8>(PaletteColor::BLUE); // Color de las notificaciones por defecto
|
||||
constexpr bool DEFAULT_CONSOLE = false; // Consola desactivada por defecto
|
||||
constexpr const char* DEFAULT_VERSION = "1.10"; // Versión por defecto
|
||||
|
||||
// Estructura para las opciones de las notificaciones
|
||||
struct OptionsNotification {
|
||||
NotificationPosition pos; // Ubicación de las notificaciones en pantalla
|
||||
bool sound; // Indica si las notificaciones suenan
|
||||
Uint8 color; // Color de las notificaciones
|
||||
|
||||
// Constructor por defecto
|
||||
OptionsNotification()
|
||||
: pos(DEFAULT_NOTIFICATION_POSITION),
|
||||
sound(DEFAULT_NOTIFICATION_SOUND),
|
||||
color(DEFAULT_NOTIFICATION_COLOR) {}
|
||||
|
||||
// Constructor
|
||||
OptionsNotification(NotificationPosition p, bool s, Uint8 c)
|
||||
: pos(p),
|
||||
sound(s),
|
||||
color(c) {}
|
||||
|
||||
// Método que devuelve la posición horizontal
|
||||
NotificationPosition getHorizontalPosition() const {
|
||||
switch (pos) {
|
||||
case NotificationPosition::UPPER_LEFT:
|
||||
case NotificationPosition::BOTTOM_LEFT:
|
||||
return NotificationPosition::LEFT;
|
||||
case NotificationPosition::UPPER_CENTER:
|
||||
case NotificationPosition::BOTTOM_CENTER:
|
||||
return NotificationPosition::CENTER;
|
||||
case NotificationPosition::UPPER_RIGHT:
|
||||
case NotificationPosition::BOTTOM_RIGHT:
|
||||
return NotificationPosition::RIGHT;
|
||||
default:
|
||||
return NotificationPosition::UNKNOWN;
|
||||
}
|
||||
return NotificationPosition::UNKNOWN;
|
||||
}
|
||||
|
||||
// Método que devuelve la posición vertical
|
||||
NotificationPosition getVerticalPosition() const {
|
||||
switch (pos) {
|
||||
case NotificationPosition::UPPER_LEFT:
|
||||
case NotificationPosition::UPPER_CENTER:
|
||||
case NotificationPosition::UPPER_RIGHT:
|
||||
return NotificationPosition::TOP;
|
||||
case NotificationPosition::BOTTOM_LEFT:
|
||||
case NotificationPosition::BOTTOM_CENTER:
|
||||
case NotificationPosition::BOTTOM_RIGHT:
|
||||
return NotificationPosition::BOTTOM;
|
||||
default:
|
||||
return NotificationPosition::UNKNOWN;
|
||||
}
|
||||
return NotificationPosition::UNKNOWN;
|
||||
}
|
||||
};
|
||||
|
||||
// Estructura para saber la seccion y subseccion del programa
|
||||
struct SectionState {
|
||||
Section section;
|
||||
Subsection subsection;
|
||||
|
||||
// Constructor por defecto
|
||||
SectionState()
|
||||
: section(DEFAULT_SECTION),
|
||||
subsection(DEFAULT_SUBSECTION) {}
|
||||
|
||||
// Constructor
|
||||
SectionState(Section s, Subsection ss)
|
||||
: section(s),
|
||||
subsection(ss) {}
|
||||
};
|
||||
|
||||
// Estructura para albergar trucos
|
||||
struct Cheat {
|
||||
enum class CheatState : bool {
|
||||
DISABLED = false,
|
||||
ENABLED = true
|
||||
};
|
||||
|
||||
CheatState infinite_lives; // Indica si el jugador dispone de vidas infinitas
|
||||
CheatState invincible; // Indica si el jugador puede morir
|
||||
CheatState jail_is_open; // Indica si la Jail está abierta
|
||||
CheatState alternate_skin; // Indica si se usa una skin diferente para el jugador
|
||||
|
||||
// Constructor por defecto
|
||||
Cheat()
|
||||
: infinite_lives(CheatState::DISABLED),
|
||||
invincible(CheatState::DISABLED),
|
||||
jail_is_open(CheatState::DISABLED),
|
||||
alternate_skin(CheatState::DISABLED) {}
|
||||
|
||||
// Constructor
|
||||
Cheat(CheatState il, CheatState i, CheatState je, CheatState as)
|
||||
: infinite_lives(il),
|
||||
invincible(i),
|
||||
jail_is_open(je),
|
||||
alternate_skin(as) {}
|
||||
|
||||
// Método para comprobar si alguno de los tres primeros trucos está activo
|
||||
bool enabled() const {
|
||||
return infinite_lives == CheatState::ENABLED ||
|
||||
invincible == CheatState::ENABLED ||
|
||||
jail_is_open == CheatState::ENABLED;
|
||||
}
|
||||
};
|
||||
|
||||
// Estructura para almacenar estadísticas
|
||||
struct OptionsStats {
|
||||
int rooms; // Cantidad de habitaciones visitadas
|
||||
int items; // Cantidad de items obtenidos
|
||||
std::string worst_nightmare; // Habitación con más muertes acumuladas
|
||||
|
||||
// Constructor por defecto
|
||||
OptionsStats()
|
||||
: rooms(0),
|
||||
items(0),
|
||||
worst_nightmare("") {}
|
||||
|
||||
// Constructor
|
||||
OptionsStats(int r, int i, const std::string& wn)
|
||||
: rooms(r),
|
||||
items(i),
|
||||
worst_nightmare(wn) {}
|
||||
};
|
||||
|
||||
// Estructura con opciones de la ventana
|
||||
struct OptionsWindow {
|
||||
std::string caption = "JailDoctor's Dilemma"; // Texto que aparece en la barra de título de la ventana
|
||||
int zoom; // Zoom de la ventana
|
||||
int max_zoom; // Máximo tamaño de zoom para la ventana
|
||||
|
||||
// Constructor por defecto
|
||||
OptionsWindow()
|
||||
: zoom(DEFAULT_WINDOW_ZOOM),
|
||||
max_zoom(DEFAULT_WINDOW_ZOOM) {}
|
||||
|
||||
// Constructor
|
||||
OptionsWindow(int z, int mz)
|
||||
: zoom(z),
|
||||
max_zoom(mz) {}
|
||||
};
|
||||
|
||||
// Estructura para gestionar el borde de la pantalla
|
||||
struct Border {
|
||||
bool enabled; // Indica si se ha de mostrar el borde
|
||||
float width; // Ancho del borde
|
||||
float height; // Alto del borde
|
||||
|
||||
// Constructor por defecto
|
||||
Border()
|
||||
: enabled(DEFAULT_BORDER_ENABLED),
|
||||
width(DEFAULT_BORDER_WIDTH),
|
||||
height(DEFAULT_BORDER_HEIGHT) {}
|
||||
|
||||
// Constructor
|
||||
Border(bool e, float w, float h)
|
||||
: enabled(e),
|
||||
width(w),
|
||||
height(h) {}
|
||||
};
|
||||
|
||||
// Estructura para las opciones de video
|
||||
struct OptionsVideo {
|
||||
bool fullscreen; // Contiene el valor del modo de pantalla completa
|
||||
ScreenFilter filter; // Filtro usado para el escalado de la imagen
|
||||
bool vertical_sync; // Indica si se quiere usar vsync o no
|
||||
bool shaders; // Indica si se van a usar shaders o no
|
||||
bool integer_scale; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa
|
||||
bool keep_aspect; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
|
||||
Border border; // Borde de la pantalla
|
||||
std::string palette; // Paleta de colores a usar en el juego
|
||||
std::string info; // Información sobre el modo de vídeo
|
||||
|
||||
// Constructor por defecto
|
||||
OptionsVideo()
|
||||
: fullscreen(DEFAULT_VIDEO_MODE),
|
||||
filter(DEFAULT_VIDEO_FILTER),
|
||||
vertical_sync(DEFAULT_VIDEO_VERTICAL_SYNC),
|
||||
shaders(DEFAULT_VIDEO_SHADERS),
|
||||
integer_scale(DEFAULT_VIDEO_INTEGER_SCALE),
|
||||
keep_aspect(DEFAULT_VIDEO_KEEP_ASPECT),
|
||||
border(Border()),
|
||||
palette(DEFAULT_PALETTE) {}
|
||||
|
||||
// Constructor
|
||||
OptionsVideo(Uint32 m, ScreenFilter f, bool vs, bool s, bool is, bool ka, Border b, const std::string& p)
|
||||
: fullscreen(m),
|
||||
filter(f),
|
||||
vertical_sync(vs),
|
||||
shaders(s),
|
||||
integer_scale(is),
|
||||
keep_aspect(ka),
|
||||
border(b),
|
||||
palette(p) {}
|
||||
};
|
||||
|
||||
// Estructura para las opciones de musica
|
||||
struct OptionsMusic {
|
||||
bool enabled; // Indica si la música suena o no
|
||||
int volume; // Volumen al que suena la música (0 a 128 internamente)
|
||||
|
||||
// Constructor por defecto
|
||||
OptionsMusic()
|
||||
: enabled(DEFAULT_MUSIC_ENABLED),
|
||||
volume(convertVolume(DEFAULT_MUSIC_VOLUME)) {} // Usa el método estático para la conversión
|
||||
|
||||
// Constructor con parámetros
|
||||
OptionsMusic(bool e, int v)
|
||||
: enabled(e),
|
||||
volume(convertVolume(v)) {} // Convierte el volumen usando el método estático
|
||||
|
||||
// Método para establecer el volumen
|
||||
void setVolume(int v) {
|
||||
v = std::clamp(v, 0, 100); // Ajusta v al rango [0, 100]
|
||||
volume = convertVolume(v); // Convierte al rango interno
|
||||
}
|
||||
|
||||
// Método estático para convertir de 0-100 a 0-128
|
||||
static int convertVolume(int v) {
|
||||
return (v * 128) / 100;
|
||||
}
|
||||
};
|
||||
|
||||
// Estructura para las opciones de sonido
|
||||
struct OptionsSound {
|
||||
bool enabled; // Indica si los sonidos suenan o no
|
||||
int volume; // Volumen al que suenan los sonidos (0 a 128 internamente)
|
||||
|
||||
// Constructor por defecto
|
||||
OptionsSound()
|
||||
: enabled(DEFAULT_SOUND_ENABLED),
|
||||
volume(convertVolume(DEFAULT_SOUND_VOLUME)) {} // Usa el método estático para la conversión
|
||||
|
||||
// Constructor con parámetros
|
||||
OptionsSound(bool e, int v)
|
||||
: enabled(e),
|
||||
volume(convertVolume(v)) {} // También lo integra aquí
|
||||
|
||||
// Método para establecer el volumen
|
||||
void setVolume(int v) {
|
||||
v = std::clamp(v, 0, 100); // Ajusta v al rango [0, 100]
|
||||
volume = convertVolume(v); // Convierte al rango interno
|
||||
}
|
||||
|
||||
// Método estático para convertir de 0-100 a 0-128
|
||||
static int convertVolume(int v) {
|
||||
return (v * 128) / 100;
|
||||
}
|
||||
};
|
||||
|
||||
// Estructura para las opciones de audio
|
||||
struct OptionsAudio {
|
||||
OptionsMusic music; // Opciones para la música
|
||||
OptionsSound sound; // Opciones para los efectos de sonido
|
||||
bool enabled; // Indica si el audio está activo o no
|
||||
int volume; // Volumen al que suenan el audio
|
||||
|
||||
// Constructor por defecto
|
||||
OptionsAudio()
|
||||
: music(OptionsMusic()),
|
||||
sound(OptionsSound()),
|
||||
enabled(DEFAULT_AUDIO_ENABLED),
|
||||
volume(DEFAULT_AUDIO_VOLUME) {}
|
||||
|
||||
// Constructor
|
||||
OptionsAudio(OptionsMusic m, OptionsSound s, bool e, int v)
|
||||
: music(m),
|
||||
sound(s),
|
||||
enabled(e),
|
||||
volume(v) {}
|
||||
};
|
||||
|
||||
// Estructura para las opciones de juego
|
||||
struct OptionsGame {
|
||||
float width; // Ancho de la resolucion del juego
|
||||
float height; // Alto de la resolucion del juego
|
||||
|
||||
// Constructor por defecto
|
||||
OptionsGame()
|
||||
: width(DEFAULT_GAME_WIDTH),
|
||||
height(DEFAULT_GAME_HEIGHT) {}
|
||||
|
||||
// Constructor
|
||||
OptionsGame(float w, float h)
|
||||
: width(w),
|
||||
height(h) {}
|
||||
};
|
||||
|
||||
// Estructura con todas las opciones de configuración del programa
|
||||
struct Options {
|
||||
std::string version; // Versión del fichero de configuración. Sirve para saber si las opciones son compatibles
|
||||
bool console; // Indica si ha de mostrar información por la consola de texto
|
||||
Cheat cheats; // Contiene trucos y ventajas para el juego
|
||||
OptionsGame game; // Opciones de juego
|
||||
OptionsVideo video; // Opciones de video
|
||||
OptionsStats stats; // Datos con las estadisticas de juego
|
||||
OptionsNotification notifications; // Opciones relativas a las notificaciones;
|
||||
OptionsWindow window; // Opciones relativas a la ventana
|
||||
OptionsAudio audio; // Opciones relativas al audio
|
||||
ControlScheme keys; // Teclas usadas para jugar
|
||||
SectionState section; // Sección actual del programa
|
||||
|
||||
// Constructor por defecto
|
||||
Options()
|
||||
: version(DEFAULT_VERSION),
|
||||
console(DEFAULT_CONSOLE),
|
||||
cheats(Cheat()),
|
||||
game(OptionsGame()),
|
||||
video(OptionsVideo()),
|
||||
stats(OptionsStats()),
|
||||
notifications(OptionsNotification()),
|
||||
window(OptionsWindow()),
|
||||
audio(OptionsAudio()),
|
||||
keys(DEFAULT_CONTROL_SCHEME),
|
||||
section(SectionState()) {}
|
||||
|
||||
// Constructor
|
||||
Options(std::string cv, bool c, Cheat ch, OptionsGame g, OptionsVideo v, OptionsStats s, OptionsNotification n, OptionsWindow sw, OptionsAudio a, ControlScheme k, SectionState sec)
|
||||
: version(cv),
|
||||
console(c),
|
||||
cheats(ch),
|
||||
game(g),
|
||||
video(v),
|
||||
stats(s),
|
||||
notifications(n),
|
||||
window(sw),
|
||||
audio(a),
|
||||
keys(k),
|
||||
section(sec) {}
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
|
||||
// Crea e inicializa las opciones del programa
|
||||
void initOptions();
|
||||
|
||||
// Carga las opciones desde un fichero
|
||||
bool loadOptionsFromFile(const std::string& file_path);
|
||||
|
||||
// Guarda las opciones a un fichero
|
||||
bool saveOptionsToFile(const std::string& file_path);
|
||||
1118
source2/Game/Gameplay/room.cpp
Normal file
1118
source2/Game/Gameplay/room.cpp
Normal file
File diff suppressed because it is too large
Load Diff
241
source2/Game/Gameplay/room.h
Normal file
241
source2/Game/Gameplay/room.h
Normal file
@@ -0,0 +1,241 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "enemy.h" // Para EnemyData
|
||||
#include "item.h" // Para ItemData
|
||||
#include "utils.h" // Para LineHorizontal, LineDiagonal, LineVertical
|
||||
class SSprite; // lines 12-12
|
||||
class Surface; // lines 13-13
|
||||
struct ScoreboardData; // lines 15-15
|
||||
|
||||
enum class TileType {
|
||||
EMPTY,
|
||||
WALL,
|
||||
PASSABLE,
|
||||
SLOPE_L,
|
||||
SLOPE_R,
|
||||
KILL,
|
||||
ANIMATED
|
||||
};
|
||||
|
||||
enum class RoomBorder : int {
|
||||
TOP = 0,
|
||||
RIGHT = 1,
|
||||
BOTTOM = 2,
|
||||
LEFT = 3
|
||||
};
|
||||
|
||||
struct AnimatedTile {
|
||||
std::shared_ptr<SSprite> sprite; // SSprite para dibujar el tile
|
||||
int x_orig; // Poicion X donde se encuentra el primer tile de la animacion en la tilesheet
|
||||
};
|
||||
|
||||
struct RoomData {
|
||||
std::string number; // Numero de la habitación
|
||||
std::string name; // Nombre de la habitación
|
||||
std::string bg_color; // Color de fondo de la habitación
|
||||
std::string border_color; // Color del borde de la pantalla
|
||||
std::string item_color1; // Color 1 para los items de la habitación
|
||||
std::string item_color2; // Color 2 para los items de la habitación
|
||||
std::string upper_room; // Identificador de la habitación que se encuentra arriba
|
||||
std::string lower_room; // Identificador de la habitación que se encuentra abajp
|
||||
std::string left_room; // Identificador de la habitación que se encuentra a la izquierda
|
||||
std::string right_room; // Identificador de la habitación que se encuentra a la derecha
|
||||
std::string tile_set_file; // Imagen con los graficos para la habitación
|
||||
std::string tile_map_file; // Fichero con el mapa de indices de tile
|
||||
int conveyor_belt_direction; // Sentido en el que arrastran las superficies automáticas de la habitación
|
||||
std::vector<int> tile_map; // Indice de los tiles a dibujar en la habitación
|
||||
std::vector<EnemyData> enemies; // Listado con los enemigos de la habitación
|
||||
std::vector<ItemData> items; // Listado con los items que hay en la habitación
|
||||
};
|
||||
|
||||
// Carga las variables desde un fichero de mapa
|
||||
RoomData loadRoomFile(const std::string& file_path, bool verbose = false);
|
||||
|
||||
// Carga las variables y texturas desde un fichero de mapa de tiles
|
||||
std::vector<int> loadRoomTileFile(const std::string& file_path, bool verbose = false);
|
||||
|
||||
// Asigna variables a una estructura RoomData
|
||||
bool setRoom(RoomData* room, const std::string& key, const std::string& value);
|
||||
|
||||
// Asigna variables a una estructura EnemyData
|
||||
bool setEnemy(EnemyData* enemy, const std::string& key, const std::string& value);
|
||||
|
||||
// Asigna variables a una estructura ItemData
|
||||
bool setItem(ItemData* item, const std::string& key, const std::string& value);
|
||||
|
||||
class Room {
|
||||
private:
|
||||
// Constantes
|
||||
static constexpr int TILE_SIZE_ = 8; // Ancho del tile en pixels
|
||||
static constexpr int MAP_WIDTH_ = 32; // Ancho del mapa en tiles
|
||||
static constexpr int MAP_HEIGHT_ = 16; // Alto del mapa en tiles
|
||||
|
||||
// Objetos y punteros
|
||||
std::vector<std::shared_ptr<Enemy>> enemies_; // Listado con los enemigos de la habitación
|
||||
std::vector<std::shared_ptr<Item>> items_; // Listado con los items que hay en la habitación
|
||||
std::shared_ptr<Surface> surface_; // Textura con los graficos de la habitación
|
||||
std::shared_ptr<Surface> map_surface_; // Textura para dibujar el mapa de la habitación
|
||||
std::shared_ptr<ScoreboardData> data_; // Puntero a los datos del marcador
|
||||
|
||||
// Variables
|
||||
std::string number_; // Numero de la habitación
|
||||
std::string name_; // Nombre de la habitación
|
||||
std::string bg_color_; // Color de fondo de la habitación
|
||||
std::string border_color_; // Color del borde de la pantalla
|
||||
std::string item_color1_; // Color 1 para los items de la habitación
|
||||
std::string item_color2_; // Color 2 para los items de la habitación
|
||||
std::string upper_room_; // Identificador de la habitación que se encuentra arriba
|
||||
std::string lower_room_; // Identificador de la habitación que se encuentra abajp
|
||||
std::string left_room_; // Identificador de la habitación que se encuentra a la izquierda
|
||||
std::string right_room_; // Identificador de la habitación que se encuentra a la derecha
|
||||
std::string tile_set_file_; // Imagen con los graficos para la habitación
|
||||
std::string tile_map_file_; // Fichero con el mapa de indices de tile
|
||||
std::vector<int> tile_map_; // Indice de los tiles a dibujar en la habitación
|
||||
int conveyor_belt_direction_; // Sentido en el que arrastran las superficies automáticas de la habitación
|
||||
std::vector<LineHorizontal> bottom_floors_; // Lista con las superficies inferiores de la habitación
|
||||
std::vector<LineHorizontal> top_floors_; // Lista con las superficies superiores de la habitación
|
||||
std::vector<LineVertical> left_walls_; // Lista con las superficies laterales de la parte izquierda de la habitación
|
||||
std::vector<LineVertical> right_walls_; // Lista con las superficies laterales de la parte derecha de la habitación
|
||||
std::vector<LineDiagonal> left_slopes_; // Lista con todas las rampas que suben hacia la izquierda
|
||||
std::vector<LineDiagonal> right_slopes_; // Lista con todas las rampas que suben hacia la derecha
|
||||
int counter_; // Contador para lo que haga falta
|
||||
bool is_paused_; // Indica si el mapa esta en modo pausa
|
||||
std::vector<AnimatedTile> animated_tiles_; // Vector con los indices de tiles animados
|
||||
std::vector<LineHorizontal> conveyor_belt_floors_; // Lista con las superficies automaticas de la habitación
|
||||
int tile_set_width_; // Ancho del tileset en tiles
|
||||
|
||||
void initializeRoom(const RoomData& room);
|
||||
|
||||
// Pinta el mapa de la habitación en la textura
|
||||
void fillMapTexture();
|
||||
|
||||
// Calcula las superficies inferiores
|
||||
void setBottomSurfaces();
|
||||
|
||||
// Calcula las superficies superiores
|
||||
void setTopSurfaces();
|
||||
|
||||
// Calcula las superficies laterales izquierdas
|
||||
void setLeftSurfaces();
|
||||
|
||||
// Calcula las superficies laterales derechas
|
||||
void setRightSurfaces();
|
||||
|
||||
// Encuentra todas las rampas que suben hacia la izquierda
|
||||
void setLeftSlopes();
|
||||
|
||||
// Encuentra todas las rampas que suben hacia la derecha
|
||||
void setRightSlopes();
|
||||
|
||||
// Calcula las superficies automaticas
|
||||
void setAutoSurfaces();
|
||||
|
||||
// Localiza todos los tiles animados de la habitación
|
||||
void setAnimatedTiles();
|
||||
|
||||
// Actualiza los tiles animados
|
||||
void updateAnimatedTiles();
|
||||
|
||||
// Pinta los tiles animados en pantalla
|
||||
void renderAnimatedTiles();
|
||||
|
||||
// Devuelve el tipo de tile que hay en ese indice
|
||||
TileType getTile(int index);
|
||||
|
||||
// Abre la jail para poder entrar
|
||||
void openTheJail();
|
||||
|
||||
// Inicializa las superficies de colision
|
||||
void initRoomSurfaces();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Room(const std::string& room_path, std::shared_ptr<ScoreboardData> data);
|
||||
|
||||
// Destructor
|
||||
~Room() = default;
|
||||
|
||||
// Devuelve el nombre de la habitación
|
||||
const std::string& getName() const { return name_; }
|
||||
|
||||
// Devuelve el color de la habitación
|
||||
Uint8 getBGColor() const { return stringToColor(bg_color_); }
|
||||
|
||||
// Devuelve el color del borde
|
||||
Uint8 getBorderColor() const { return stringToColor(border_color_); }
|
||||
|
||||
// Dibuja el mapa en pantalla
|
||||
void renderMap();
|
||||
|
||||
// Dibuja los enemigos en pantalla
|
||||
void renderEnemies();
|
||||
|
||||
// Dibuja los objetos en pantalla
|
||||
void renderItems();
|
||||
|
||||
// Actualiza las variables y objetos de la habitación
|
||||
void update();
|
||||
|
||||
// Devuelve la cadena del fichero de la habitación contigua segun el borde
|
||||
std::string getRoom(RoomBorder border);
|
||||
|
||||
// Devuelve el tipo de tile que hay en ese pixel
|
||||
TileType getTile(SDL_FPoint point);
|
||||
|
||||
// Indica si hay colision con un enemigo a partir de un rectangulo
|
||||
bool enemyCollision(SDL_FRect& rect);
|
||||
|
||||
// Indica si hay colision con un objeto a partir de un rectangulo
|
||||
bool itemCollision(SDL_FRect& rect);
|
||||
|
||||
// Obten el tamaño del tile
|
||||
int getTileSize() const { return TILE_SIZE_; }
|
||||
|
||||
// Obten la coordenada de la cuesta a partir de un punto perteneciente a ese tile
|
||||
int getSlopeHeight(SDL_FPoint p, TileType slope);
|
||||
|
||||
// Comprueba las colisiones
|
||||
int checkRightSurfaces(SDL_FRect* rect);
|
||||
|
||||
// Comprueba las colisiones
|
||||
int checkLeftSurfaces(SDL_FRect* rect);
|
||||
|
||||
// Comprueba las colisiones
|
||||
int checkTopSurfaces(SDL_FRect* rect);
|
||||
|
||||
// Comprueba las colisiones
|
||||
int checkBottomSurfaces(SDL_FRect* rect);
|
||||
|
||||
// Comprueba las colisiones
|
||||
int checkAutoSurfaces(SDL_FRect* rect);
|
||||
|
||||
// Comprueba las colisiones
|
||||
bool checkTopSurfaces(SDL_FPoint* p);
|
||||
|
||||
// Comprueba las colisiones
|
||||
bool checkAutoSurfaces(SDL_FPoint* p);
|
||||
|
||||
// Comprueba las colisiones
|
||||
int checkLeftSlopes(const LineVertical* line);
|
||||
|
||||
// Comprueba las colisiones
|
||||
bool checkLeftSlopes(SDL_FPoint* p);
|
||||
|
||||
// Comprueba las colisiones
|
||||
int checkRightSlopes(const LineVertical* line);
|
||||
|
||||
// Comprueba las colisiones
|
||||
bool checkRightSlopes(SDL_FPoint* p);
|
||||
|
||||
// Pone el mapa en modo pausa
|
||||
void setPaused(bool value) { is_paused_ = value; };
|
||||
|
||||
// Obten la direccion de las superficies automaticas
|
||||
int getAutoSurfaceDirection() const { return conveyor_belt_direction_; }
|
||||
};
|
||||
29
source2/Game/Gameplay/room_tracker.cpp
Normal file
29
source2/Game/Gameplay/room_tracker.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "room_tracker.h"
|
||||
|
||||
// Comprueba si la habitación ya ha sido visitada
|
||||
bool RoomTracker::hasBeenVisited(const std::string &name)
|
||||
{
|
||||
for (const auto &l : list)
|
||||
{
|
||||
if (l == name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Añade la habitación a la lista
|
||||
bool RoomTracker::addRoom(const std::string &name)
|
||||
{
|
||||
// Comprueba si la habitación ya ha sido visitada
|
||||
if (!hasBeenVisited(name))
|
||||
{
|
||||
// En caso contrario añádela a la lista
|
||||
list.push_back(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
24
source2/Game/Gameplay/room_tracker.h
Normal file
24
source2/Game/Gameplay/room_tracker.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
class RoomTracker
|
||||
{
|
||||
private:
|
||||
// Variables
|
||||
std::vector<std::string> list; // Lista con las habitaciones visitadas
|
||||
|
||||
// Comprueba si la habitación ya ha sido visitada
|
||||
bool hasBeenVisited(const std::string &name);
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
RoomTracker() = default;
|
||||
|
||||
// Destructor
|
||||
~RoomTracker() = default;
|
||||
|
||||
// Añade la habitación a la lista
|
||||
bool addRoom(const std::string &name);
|
||||
};
|
||||
163
source2/Game/Gameplay/scoreboard.cpp
Normal file
163
source2/Game/Gameplay/scoreboard.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "scoreboard.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "defines.h" // Para BLOCK
|
||||
#include "options.h" // Para Options, options, Cheat, OptionsGame
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_animated_sprite.h" // Para SAnimatedSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "text.h" // Para Text
|
||||
#include "utils.h" // Para stringToColor
|
||||
|
||||
// Constructor
|
||||
Scoreboard::Scoreboard(std::shared_ptr<ScoreboardData> data)
|
||||
: item_surface_(Resource::get()->getSurface("items.gif")),
|
||||
data_(data),
|
||||
clock_(ClockData()) {
|
||||
const float SURFACE_WIDTH_ = options.game.width;
|
||||
constexpr float SURFACE_HEIGHT_ = 6.0F * BLOCK;
|
||||
|
||||
// Reserva memoria para los objetos
|
||||
auto player_texture = Resource::get()->getSurface(options.cheats.alternate_skin == Cheat::CheatState::ENABLED ? "player2.gif" : "player.gif");
|
||||
auto player_animations = Resource::get()->getAnimations(options.cheats.alternate_skin == Cheat::CheatState::ENABLED ? "player2.ani" : "player.ani");
|
||||
player_sprite_ = std::make_shared<SAnimatedSprite>(player_texture, player_animations);
|
||||
player_sprite_->setCurrentAnimation("walk_menu");
|
||||
|
||||
surface_ = std::make_shared<Surface>(SURFACE_WIDTH_, SURFACE_HEIGHT_);
|
||||
surface_dest_ = {0, options.game.height - SURFACE_HEIGHT_, SURFACE_WIDTH_, SURFACE_HEIGHT_};
|
||||
|
||||
// Inicializa las variables
|
||||
counter_ = 0;
|
||||
change_color_speed_ = 4;
|
||||
is_paused_ = false;
|
||||
paused_time_ = 0;
|
||||
paused_time_elapsed_ = 0;
|
||||
items_color_ = stringToColor("white");
|
||||
|
||||
// Inicializa el vector de colores
|
||||
const std::vector<std::string> COLORS = {"blue", "magenta", "green", "cyan", "yellow", "white", "bright_blue", "bright_magenta", "bright_green", "bright_cyan", "bright_yellow", "bright_white"};
|
||||
for (const auto& color : COLORS) {
|
||||
color_.push_back(stringToColor(color));
|
||||
}
|
||||
}
|
||||
|
||||
// Pinta el objeto en pantalla
|
||||
void Scoreboard::render() {
|
||||
surface_->render(nullptr, &surface_dest_);
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void Scoreboard::update() {
|
||||
counter_++;
|
||||
player_sprite_->update();
|
||||
|
||||
// Actualiza el color de la cantidad de items recogidos
|
||||
updateItemsColor();
|
||||
|
||||
// Dibuja la textura
|
||||
fillTexture();
|
||||
|
||||
if (!is_paused_) {
|
||||
// Si está en pausa no se actualiza el reloj
|
||||
clock_ = getTime();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el tiempo transcurrido de partida
|
||||
Scoreboard::ClockData Scoreboard::getTime() {
|
||||
const Uint32 timeElapsed = SDL_GetTicks() - data_->ini_clock - paused_time_elapsed_;
|
||||
|
||||
ClockData time;
|
||||
time.hours = timeElapsed / 3600000;
|
||||
time.minutes = timeElapsed / 60000;
|
||||
time.seconds = timeElapsed / 1000;
|
||||
time.separator = (timeElapsed % 1000 <= 500) ? ":" : " ";
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
// Pone el marcador en modo pausa
|
||||
void Scoreboard::setPaused(bool value) {
|
||||
if (is_paused_ == value) {
|
||||
// Evita ejecutar lógica si el estado no cambia
|
||||
return;
|
||||
}
|
||||
|
||||
is_paused_ = value;
|
||||
|
||||
if (is_paused_) {
|
||||
// Guarda el tiempo actual al pausar
|
||||
paused_time_ = SDL_GetTicks();
|
||||
} else {
|
||||
// Calcula el tiempo pausado acumulado al reanudar
|
||||
paused_time_elapsed_ += SDL_GetTicks() - paused_time_;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el color de la cantidad de items recogidos
|
||||
void Scoreboard::updateItemsColor() {
|
||||
if (!data_->jail_is_open) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (counter_ % 20 < 10) {
|
||||
items_color_ = stringToColor("white");
|
||||
} else {
|
||||
items_color_ = stringToColor("magenta");
|
||||
}
|
||||
}
|
||||
|
||||
// Devuelve la cantidad de minutos de juego transcurridos
|
||||
int Scoreboard::getMinutes() {
|
||||
return getTime().minutes;
|
||||
}
|
||||
|
||||
// Dibuja los elementos del marcador en la textura
|
||||
void Scoreboard::fillTexture() {
|
||||
// Empieza a dibujar en la textura
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface_);
|
||||
|
||||
// Limpia la textura
|
||||
surface_->clear(stringToColor("black"));
|
||||
|
||||
// Anclas
|
||||
constexpr int LINE1 = BLOCK;
|
||||
constexpr int LINE2 = 3 * BLOCK;
|
||||
|
||||
// Dibuja las vidas
|
||||
const int desp = (counter_ / 40) % 8;
|
||||
const int frame = desp % 4;
|
||||
player_sprite_->setCurrentAnimationFrame(frame);
|
||||
player_sprite_->setPosY(LINE2);
|
||||
for (int i = 0; i < data_->lives; ++i) {
|
||||
player_sprite_->setPosX(8 + (16 * i) + desp);
|
||||
const int index = i % color_.size();
|
||||
player_sprite_->render(1, color_.at(index));
|
||||
}
|
||||
|
||||
// Muestra si suena la música
|
||||
if (data_->music) {
|
||||
const Uint8 c = data_->color;
|
||||
SDL_FRect clip = {0, 8, 8, 8};
|
||||
item_surface_->renderWithColorReplace(20 * BLOCK, LINE2, 1, c, &clip);
|
||||
}
|
||||
|
||||
// Escribe los textos
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
const std::string TIME_TEXT = std::to_string((clock_.minutes % 100) / 10) + std::to_string(clock_.minutes % 10) + clock_.separator + std::to_string((clock_.seconds % 60) / 10) + std::to_string(clock_.seconds % 10);
|
||||
const std::string ITEMS_TEXT = std::to_string(data_->items / 100) + std::to_string((data_->items % 100) / 10) + std::to_string(data_->items % 10);
|
||||
text->writeColored(BLOCK, LINE1, "Items collected ", data_->color);
|
||||
text->writeColored(17 * BLOCK, LINE1, ITEMS_TEXT, items_color_);
|
||||
text->writeColored(20 * BLOCK, LINE1, " Time ", data_->color);
|
||||
text->writeColored(26 * BLOCK, LINE1, TIME_TEXT, stringToColor("white"));
|
||||
|
||||
const std::string ROOMS_TEXT = std::to_string(data_->rooms / 100) + std::to_string((data_->rooms % 100) / 10) + std::to_string(data_->rooms % 10);
|
||||
text->writeColored(22 * BLOCK, LINE2, "Rooms", stringToColor("white"));
|
||||
text->writeColored(28 * BLOCK, LINE2, ROOMS_TEXT, stringToColor("white"));
|
||||
|
||||
// Deja el renderizador como estaba
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
108
source2/Game/Gameplay/scoreboard.h
Normal file
108
source2/Game/Gameplay/scoreboard.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string, basic_string
|
||||
#include <vector> // Para vector
|
||||
class SAnimatedSprite; // lines 10-10
|
||||
class Surface; // lines 11-11
|
||||
|
||||
struct ScoreboardData {
|
||||
int items; // Lleva la cuenta de los objetos recogidos
|
||||
int lives; // Lleva la cuenta de las vidas restantes del jugador
|
||||
int rooms; // Lleva la cuenta de las habitaciones visitadas
|
||||
bool music; // Indica si ha de sonar la música durante el juego
|
||||
Uint8 color; // Color para escribir el texto del marcador
|
||||
Uint32 ini_clock; // Tiempo inicial para calcular el tiempo transcurrido
|
||||
bool jail_is_open; // Indica si se puede entrar a la Jail
|
||||
|
||||
// Constructor por defecto
|
||||
ScoreboardData()
|
||||
: items(0),
|
||||
lives(0),
|
||||
rooms(0),
|
||||
music(true),
|
||||
color(0),
|
||||
ini_clock(0),
|
||||
jail_is_open(false) {}
|
||||
|
||||
// Constructor parametrizado
|
||||
ScoreboardData(int items, int lives, int rooms, bool music, Uint8 color, Uint32 ini_clock, bool jail_is_open)
|
||||
: items(items),
|
||||
lives(lives),
|
||||
rooms(rooms),
|
||||
music(music),
|
||||
color(color),
|
||||
ini_clock(ini_clock),
|
||||
jail_is_open(jail_is_open) {}
|
||||
};
|
||||
|
||||
class Scoreboard {
|
||||
private:
|
||||
struct ClockData {
|
||||
int hours;
|
||||
int minutes;
|
||||
int seconds;
|
||||
std::string separator;
|
||||
|
||||
// Constructor por defecto
|
||||
ClockData()
|
||||
: hours(0),
|
||||
minutes(0),
|
||||
seconds(0),
|
||||
separator(":") {}
|
||||
|
||||
// Constructor parametrizado
|
||||
ClockData(int h, int m, int s, const std::string& sep)
|
||||
: hours(h),
|
||||
minutes(m),
|
||||
seconds(s),
|
||||
separator(sep) {}
|
||||
};
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<SAnimatedSprite> player_sprite_; // Sprite para mostrar las vidas en el marcador
|
||||
std::shared_ptr<Surface> item_surface_; // Surface con los graficos para los elementos del marcador
|
||||
std::shared_ptr<ScoreboardData> data_; // Contiene las variables a mostrar en el marcador
|
||||
std::shared_ptr<Surface> surface_; // Surface donde dibujar el marcador;
|
||||
|
||||
// Variables
|
||||
std::vector<Uint8> color_; // Vector con los colores del objeto
|
||||
int counter_; // Contador interno
|
||||
int change_color_speed_; // Cuanto mas alto, mas tarda en cambiar de color
|
||||
bool is_paused_; // Indica si el marcador esta en modo pausa
|
||||
Uint32 paused_time_; // Milisegundos que ha estado el marcador en pausa
|
||||
Uint32 paused_time_elapsed_; // Tiempo acumulado en pausa
|
||||
ClockData clock_; // Contiene las horas, minutos y segundos transcurridos desde el inicio de la partida
|
||||
Uint8 items_color_; // Color de la cantidad de items recogidos
|
||||
SDL_FRect surface_dest_; // Rectangulo donde dibujar la surface del marcador
|
||||
|
||||
// Obtiene el tiempo transcurrido de partida
|
||||
ClockData getTime();
|
||||
|
||||
// Actualiza el color de la cantidad de items recogidos
|
||||
void updateItemsColor();
|
||||
|
||||
// Dibuja los elementos del marcador en la surface
|
||||
void fillTexture();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
explicit Scoreboard(std::shared_ptr<ScoreboardData> data);
|
||||
|
||||
// Destructor
|
||||
~Scoreboard() = default;
|
||||
|
||||
// Pinta el objeto en pantalla
|
||||
void render();
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void update();
|
||||
|
||||
// Pone el marcador en modo pausa
|
||||
void setPaused(bool value);
|
||||
|
||||
// Devuelve la cantidad de minutos de juego transcurridos
|
||||
int getMinutes();
|
||||
};
|
||||
216
source2/Game/Gameplay/stats.cpp
Normal file
216
source2/Game/Gameplay/stats.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include "stats.h"
|
||||
#include <fstream> // Para basic_ostream, basic_ifstream, basic_istream
|
||||
#include <sstream> // Para basic_stringstream
|
||||
#include "options.h" // Para Options, OptionsStats, options
|
||||
|
||||
// Constructor
|
||||
Stats::Stats(const std::string &file, const std::string &buffer)
|
||||
: bufferPath(buffer),
|
||||
filePath(file) {}
|
||||
|
||||
// Destructor
|
||||
Stats::~Stats()
|
||||
{
|
||||
// Vuelca los datos del buffer en la lista de estadisticas
|
||||
updateListFromBuffer();
|
||||
|
||||
// Calcula cual es la habitación con más muertes
|
||||
checkWorstNightmare();
|
||||
|
||||
// Guarda las estadísticas
|
||||
saveToFile(bufferPath, bufferList);
|
||||
saveToFile(filePath, list);
|
||||
|
||||
bufferList.clear();
|
||||
list.clear();
|
||||
dictionary.clear();
|
||||
}
|
||||
|
||||
// Inicializador
|
||||
void Stats::init()
|
||||
// Se debe llamar a este procedimiento una vez se haya creado el diccionario numero-nombre
|
||||
{
|
||||
loadFromFile(bufferPath, bufferList);
|
||||
loadFromFile(filePath, list);
|
||||
|
||||
// Vuelca los datos del buffer en la lista de estadisticas
|
||||
updateListFromBuffer();
|
||||
}
|
||||
|
||||
// Añade una muerte a las estadisticas
|
||||
void Stats::addDeath(const std::string &name)
|
||||
{
|
||||
// Primero busca si ya hay una entrada con ese nombre
|
||||
const int index = findByName(name, bufferList);
|
||||
if (index != -1)
|
||||
{
|
||||
bufferList[index].died++;
|
||||
}
|
||||
|
||||
// En caso contrario crea la entrada
|
||||
else
|
||||
{
|
||||
StatsData item;
|
||||
item.name = name;
|
||||
item.visited = 0;
|
||||
item.died = 1;
|
||||
bufferList.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Añade una visita a las estadisticas
|
||||
void Stats::addVisit(const std::string &name)
|
||||
{
|
||||
// Primero busca si ya hay una entrada con ese nombre
|
||||
const int index = findByName(name, bufferList);
|
||||
if (index != -1)
|
||||
{
|
||||
bufferList[index].visited++;
|
||||
}
|
||||
|
||||
// En caso contrario crea la entrada
|
||||
else
|
||||
{
|
||||
StatsData item;
|
||||
item.name = name;
|
||||
item.visited = 1;
|
||||
item.died = 0;
|
||||
bufferList.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Busca una entrada en la lista por nombre
|
||||
int Stats::findByName(const std::string &name, const std::vector<StatsData> &list)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (const auto &l : list)
|
||||
{
|
||||
if (l.name == name)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Carga las estadisticas desde un fichero
|
||||
bool Stats::loadFromFile(const std::string &file_path, std::vector<StatsData> &list)
|
||||
{
|
||||
list.clear();
|
||||
|
||||
// Indicador de éxito en la carga
|
||||
bool success = true;
|
||||
|
||||
// Variables para manejar el fichero
|
||||
std::ifstream file(file_path);
|
||||
|
||||
// Si el fichero se puede abrir
|
||||
if (file.good())
|
||||
{
|
||||
std::string line;
|
||||
// Procesa el fichero linea a linea
|
||||
while (std::getline(file, line))
|
||||
{
|
||||
// Comprueba que la linea no sea un comentario
|
||||
if (line.substr(0, 1) != "#")
|
||||
{
|
||||
StatsData stat;
|
||||
std::stringstream ss(line);
|
||||
std::string tmp;
|
||||
|
||||
// Obtiene el nombre
|
||||
getline(ss, tmp, ';');
|
||||
stat.name = tmp;
|
||||
|
||||
// Obtiene las visitas
|
||||
getline(ss, tmp, ';');
|
||||
stat.visited = std::stoi(tmp);
|
||||
|
||||
// Obtiene las muertes
|
||||
getline(ss, tmp, ';');
|
||||
stat.died = std::stoi(tmp);
|
||||
|
||||
list.push_back(stat);
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
file.close();
|
||||
}
|
||||
|
||||
// El fichero no existe
|
||||
else
|
||||
{
|
||||
// Crea el fichero con los valores por defecto
|
||||
saveToFile(file_path, list);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Guarda las estadisticas en un fichero
|
||||
void Stats::saveToFile(const std::string &file_path, const std::vector<StatsData> &list)
|
||||
{
|
||||
// Crea y abre el fichero de texto
|
||||
std::ofstream file(file_path);
|
||||
|
||||
// Escribe en el fichero
|
||||
file << "# ROOM NAME;VISITS;DEATHS" << std::endl;
|
||||
for (const auto &item : list)
|
||||
{
|
||||
file << item.name << ";" << item.visited << ";" << item.died << std::endl;
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Calcula cual es la habitación con más muertes
|
||||
void Stats::checkWorstNightmare()
|
||||
{
|
||||
int deaths = 0;
|
||||
for (const auto &item : list)
|
||||
{
|
||||
if (item.died > deaths)
|
||||
{
|
||||
deaths = item.died;
|
||||
options.stats.worst_nightmare = item.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Añade una entrada al diccionario
|
||||
void Stats::addDictionary(const std::string &number, const std::string &name)
|
||||
{
|
||||
dictionary.push_back({number, name});
|
||||
}
|
||||
|
||||
// Vuelca los datos del buffer en la lista de estadisticas
|
||||
void Stats::updateListFromBuffer()
|
||||
{
|
||||
// Actualiza list desde bufferList
|
||||
for (const auto &buffer : bufferList)
|
||||
{
|
||||
int index = findByName(buffer.name, list);
|
||||
|
||||
if (index != -1)
|
||||
{ // Encontrado. Aumenta sus estadisticas
|
||||
list[index].visited += buffer.visited;
|
||||
list[index].died += buffer.died;
|
||||
}
|
||||
else
|
||||
{ // En caso contrario crea la entrada
|
||||
StatsData item;
|
||||
item.name = buffer.name;
|
||||
item.visited = buffer.visited;
|
||||
item.died = buffer.died;
|
||||
list.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
saveToFile(bufferPath, bufferList);
|
||||
saveToFile(filePath, list);
|
||||
}
|
||||
63
source2/Game/Gameplay/stats.h
Normal file
63
source2/Game/Gameplay/stats.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
class Stats
|
||||
{
|
||||
private:
|
||||
struct StatsData
|
||||
{
|
||||
std::string name; // Nombre de la habitación
|
||||
int visited; // Cuenta las veces que se ha visitado una habitación
|
||||
int died; // Cuenta las veces que se ha muerto en una habitación
|
||||
};
|
||||
|
||||
struct StatsDictionary
|
||||
{
|
||||
std::string number; // Numero de la habitación
|
||||
std::string name; // Nombre de la habitación
|
||||
};
|
||||
|
||||
// Variables
|
||||
std::vector<StatsDictionary> dictionary; // Lista con la equivalencia nombre-numero de habitacion
|
||||
std::vector<StatsData> bufferList; // Lista con las estadisticas temporales por habitación
|
||||
std::vector<StatsData> list; // Lista con las estadisticas completas por habitación
|
||||
std::string bufferPath; // Fichero con las estadísticas temporales
|
||||
std::string filePath; // Fichero con las estadísticas completas
|
||||
|
||||
// Busca una entrada en la lista por nombre
|
||||
int findByName(const std::string &name, const std::vector<StatsData> &list);
|
||||
|
||||
// Carga las estadisticas desde un fichero
|
||||
bool loadFromFile(const std::string &filePath, std::vector<StatsData> &list);
|
||||
|
||||
// Guarda las estadisticas en un fichero
|
||||
void saveToFile(const std::string &filePath, const std::vector<StatsData> &list);
|
||||
|
||||
// Calcula cual es la habitación con más muertes
|
||||
void checkWorstNightmare();
|
||||
|
||||
// Vuelca los datos del buffer en la lista de estadisticas
|
||||
void updateListFromBuffer();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Stats(const std::string &file, const std::string &buffer);
|
||||
|
||||
// Destructor
|
||||
~Stats();
|
||||
|
||||
// Inicializador
|
||||
// Se debe llamar a este procedimiento una vez se haya creado el diccionario numero-nombre
|
||||
void init();
|
||||
|
||||
// Añade una muerte a las estadisticas
|
||||
void addDeath(const std::string &name);
|
||||
|
||||
// Añade una visita a las estadisticas
|
||||
void addVisit(const std::string &name);
|
||||
|
||||
// Añade una entrada al diccionario
|
||||
void addDictionary(const std::string &number, const std::string &name);
|
||||
};
|
||||
257
source2/Game/Scenes/credits.cpp
Normal file
257
source2/Game/Scenes/credits.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "credits.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para min
|
||||
|
||||
#include "defines.h" // Para GAME_SPEED, PLAY_AREA_CENTER_X, PLAY_...
|
||||
#include "global_events.h" // Para check
|
||||
#include "global_inputs.h" // Para check
|
||||
#include "options.h" // Para Options, options, OptionsGame, Sectio...
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_animated_sprite.h" // Para SAnimatedSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "text.h" // Para Text, TEXT_CENTER, TEXT_COLOR
|
||||
#include "utils.h" // Para PaletteColor
|
||||
|
||||
// Constructor
|
||||
Credits::Credits()
|
||||
: shining_sprite_(std::make_shared<SAnimatedSprite>(Resource::get()->getSurface("shine.gif"), Resource::get()->getAnimations("shine.ani"))) {
|
||||
// Inicializa variables
|
||||
options.section.section = Section::CREDITS;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
shining_sprite_->setPos({194, 174, 8, 8});
|
||||
|
||||
// Cambia el color del borde
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Crea la textura para el texto que se escribe en pantalla
|
||||
text_surface_ = std::make_shared<Surface>(options.game.width, options.game.height);
|
||||
|
||||
// Crea la textura para cubrir el rexto
|
||||
cover_surface_ = std::make_shared<Surface>(options.game.width, options.game.height);
|
||||
|
||||
// Escribe el texto en la textura
|
||||
fillTexture();
|
||||
}
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void Credits::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
globalEvents::check(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las entradas
|
||||
void Credits::checkInput() {
|
||||
globalInputs::check();
|
||||
}
|
||||
|
||||
// Inicializa los textos
|
||||
void Credits::iniTexts() {
|
||||
#ifndef GAME_CONSOLE
|
||||
std::string keys = "";
|
||||
|
||||
switch (options.keys) {
|
||||
case ControlScheme::CURSOR:
|
||||
keys = "CURSORS";
|
||||
break;
|
||||
case ControlScheme::OPQA:
|
||||
keys = "O,P AND Q";
|
||||
break;
|
||||
case ControlScheme::WASD:
|
||||
keys = "A,D AND W";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
texts_.clear();
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"INSTRUCTIONS:", static_cast<Uint8>(PaletteColor::YELLOW)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"HELP JAILDOC TO GET BACK ALL", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"HIS PROJECTS AND GO TO THE", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"JAIL TO FINISH THEM", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
|
||||
texts_.push_back({"KEYS:", static_cast<Uint8>(PaletteColor::YELLOW)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({keys + " TO MOVE AND JUMP", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"M TO SWITCH THE MUSIC", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"H TO PAUSE THE GAME", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"F1-F2 TO CHANGE WINDOWS SIZE", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"F3 TO SWITCH TO FULLSCREEN", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"B TO TOOGLE THE BORDER SCREEN", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
|
||||
texts_.push_back({"A GAME BY JAILDESIGNER", static_cast<Uint8>(PaletteColor::YELLOW)});
|
||||
texts_.push_back({"MADE ON SUMMER/FALL 2022", static_cast<Uint8>(PaletteColor::YELLOW)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
|
||||
texts_.push_back({"I LOVE JAILGAMES! ", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
#else
|
||||
texts.clear();
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"INSTRUCTIONS:", static_cast<Uint8>(PaletteColor::YELLOW)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"HELP JAILDOC TO GET BACK ALL", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"HIS PROJECTS AND GO TO THE", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"JAIL TO FINISH THEM", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
|
||||
texts.push_back({"KEYS:", static_cast<Uint8>(PaletteColor::YELLOW)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"B TO JUMP", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"R TO SWITCH THE MUSIC", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"L TO SWAP THE COLOR PALETTE", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"START TO PAUSE", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"SELECT TO EXIT", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
|
||||
texts.push_back({"A GAME BY JAILDESIGNER", static_cast<Uint8>(PaletteColor::YELLOW)});
|
||||
texts.push_back({"MADE ON SUMMER/FALL 2022", static_cast<Uint8>(PaletteColor::YELLOW)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
|
||||
texts.push_back({"I LOVE JAILGAMES! ", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
texts.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
|
||||
#endif
|
||||
}
|
||||
|
||||
// Escribe el texto en la textura
|
||||
void Credits::fillTexture() {
|
||||
// Inicializa los textos
|
||||
iniTexts();
|
||||
|
||||
// Rellena la textura de texto
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(text_surface_);
|
||||
text_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
|
||||
// Escribe el texto en la textura
|
||||
const int SIZE = text->getCharacterSize();
|
||||
int pos_y = 0;
|
||||
|
||||
for (const auto& t : texts_) {
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, pos_y * SIZE, t.label, 1, t.color);
|
||||
pos_y++;
|
||||
}
|
||||
|
||||
// Escribe el corazón
|
||||
const int TEXT_LENGHT = text->lenght(texts_[22].label, 1) - text->lenght(" ", 1); // Se resta el ultimo caracter que es un espacio
|
||||
const int POS_X = ((PLAY_AREA_WIDTH - TEXT_LENGHT) / 2) + TEXT_LENGHT;
|
||||
text->writeColored(POS_X, 176, "}", static_cast<Uint8>(PaletteColor::BRIGHT_RED));
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
|
||||
// Recoloca el sprite del brillo
|
||||
shining_sprite_->setPosX(POS_X + 2);
|
||||
|
||||
// Rellena la textura que cubre el texto con color transparente
|
||||
cover_surface_->clear(static_cast<Uint8>(PaletteColor::TRANSPARENT));
|
||||
|
||||
// Los primeros 8 pixels crea una malla
|
||||
auto color = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
for (int i = 0; i < 256; i += 2) {
|
||||
cover_surface_->putPixel(i, 0, color);
|
||||
cover_surface_->putPixel(i, 2, color);
|
||||
cover_surface_->putPixel(i, 4, color);
|
||||
cover_surface_->putPixel(i, 6, color);
|
||||
|
||||
cover_surface_->putPixel(i + 1, 5, color);
|
||||
cover_surface_->putPixel(i + 1, 7, color);
|
||||
}
|
||||
|
||||
// El resto se rellena de color sólido
|
||||
SDL_FRect rect = {0, 8, 256, 192};
|
||||
cover_surface_->fillRect(&rect, color);
|
||||
}
|
||||
|
||||
// Actualiza el contador
|
||||
void Credits::updateCounter() {
|
||||
// Incrementa el contador
|
||||
if (counter_enabled_) {
|
||||
counter_++;
|
||||
if (counter_ == 224 || counter_ == 544 || counter_ == 672) {
|
||||
counter_enabled_ = false;
|
||||
}
|
||||
} else {
|
||||
sub_counter_++;
|
||||
if (sub_counter_ == 100) {
|
||||
counter_enabled_ = true;
|
||||
sub_counter_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la sección
|
||||
if (counter_ > 1200) {
|
||||
options.section.section = Section::DEMO;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables
|
||||
void Credits::update() {
|
||||
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
|
||||
if (SDL_GetTicks() - ticks_ > GAME_SPEED) {
|
||||
// Actualiza el contador de ticks
|
||||
ticks_ = SDL_GetTicks();
|
||||
|
||||
// Comprueba las entradas
|
||||
checkInput();
|
||||
|
||||
// Actualiza el contador
|
||||
updateCounter();
|
||||
|
||||
Screen::get()->update();
|
||||
|
||||
// Actualiza el sprite con el brillo
|
||||
if (counter_ > 770) {
|
||||
shining_sprite_->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja en pantalla
|
||||
void Credits::render() {
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
Screen::get()->start();
|
||||
|
||||
// Limpia la pantalla
|
||||
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
if (counter_ < 1150) {
|
||||
// Dibuja la textura con el texto en pantalla
|
||||
text_surface_->render(0, 0);
|
||||
|
||||
// Dibuja la textura que cubre el texto
|
||||
const int offset = std::min(counter_ / 8, 192 / 2);
|
||||
SDL_FRect srcRect = {0.0F, 0.0F, 256.0F, 192.0F - (offset * 2.0F)};
|
||||
cover_surface_->render(0, offset * 2, &srcRect);
|
||||
|
||||
// Dibuja el sprite con el brillo
|
||||
shining_sprite_->render(1, static_cast<Uint8>(PaletteColor::BRIGHT_WHITE));
|
||||
}
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Bucle para el logo del juego
|
||||
void Credits::run() {
|
||||
while (options.section.section == Section::CREDITS) {
|
||||
update();
|
||||
checkEvents();
|
||||
render();
|
||||
}
|
||||
}
|
||||
60
source2/Game/Scenes/credits.h
Normal file
60
source2/Game/Scenes/credits.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
class SAnimatedSprite; // lines 11-11
|
||||
class Surface;
|
||||
|
||||
class Credits {
|
||||
private:
|
||||
struct Captions {
|
||||
std::string label; // Texto a escribir
|
||||
Uint8 color; // Color del texto
|
||||
};
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<Surface> text_surface_; // Textura para dibujar el texto
|
||||
std::shared_ptr<Surface> cover_surface_; // Textura para cubrir el texto
|
||||
std::shared_ptr<SAnimatedSprite> shining_sprite_; // Sprite para el brillo del corazón
|
||||
|
||||
// Variables
|
||||
int counter_ = 0; // Contador
|
||||
bool counter_enabled_ = true; // Indica si esta activo el contador
|
||||
int sub_counter_ = 0; // Contador secundario
|
||||
Uint32 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
|
||||
std::vector<Captions> texts_; // Vector con los textos
|
||||
|
||||
// Actualiza las variables
|
||||
void update();
|
||||
|
||||
// Dibuja en pantalla
|
||||
void render();
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void checkEvents();
|
||||
|
||||
// Comprueba las entradas
|
||||
void checkInput();
|
||||
|
||||
// Actualiza el contador
|
||||
void updateCounter();
|
||||
|
||||
// Inicializa los textos
|
||||
void iniTexts();
|
||||
|
||||
// Escribe el texto en la textura
|
||||
void fillTexture();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Credits();
|
||||
|
||||
// Destructor
|
||||
~Credits() = default;
|
||||
|
||||
// Bucle principal
|
||||
void run();
|
||||
};
|
||||
480
source2/Game/Scenes/ending.cpp
Normal file
480
source2/Game/Scenes/ending.cpp
Normal file
@@ -0,0 +1,480 @@
|
||||
#include "ending.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para min
|
||||
|
||||
#include "defines.h" // Para GAME_SPEED
|
||||
#include "external/jail_audio.h" // Para JA_SetVolume, JA_PlayMusic, JA_StopMusic
|
||||
#include "global_events.h" // Para check
|
||||
#include "global_inputs.h" // Para check
|
||||
#include "options.h" // Para Options, options, OptionsGame, SectionS...
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "text.h" // Para Text, TEXT_STROKE
|
||||
#include "utils.h" // Para PaletteColor
|
||||
|
||||
// Constructor
|
||||
Ending::Ending()
|
||||
: counter_(-1),
|
||||
pre_counter_(0),
|
||||
cover_counter_(0),
|
||||
ticks_(0),
|
||||
current_scene_(0) {
|
||||
options.section.section = Section::ENDING;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
|
||||
// Inicializa los textos
|
||||
iniTexts();
|
||||
|
||||
// Inicializa las imagenes
|
||||
iniPics();
|
||||
|
||||
// Inicializa las escenas
|
||||
iniScenes();
|
||||
|
||||
// Cambia el color del borde
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Crea la textura para cubrir el texto
|
||||
cover_surface_ = std::make_shared<Surface>(options.game.width, options.game.height + 8);
|
||||
|
||||
// Rellena la textura para la cortinilla
|
||||
fillCoverTexture();
|
||||
}
|
||||
|
||||
// Actualiza el objeto
|
||||
void Ending::update() {
|
||||
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
|
||||
if (SDL_GetTicks() - ticks_ > GAME_SPEED) {
|
||||
// Actualiza el contador de ticks
|
||||
ticks_ = SDL_GetTicks();
|
||||
|
||||
// Comprueba las entradas
|
||||
checkInput();
|
||||
|
||||
// Actualiza el contador
|
||||
updateCounters();
|
||||
|
||||
// Actualiza las cortinillas de los elementos
|
||||
updateSpriteCovers();
|
||||
|
||||
// Comprueba si se ha de cambiar de escena
|
||||
checkChangeScene();
|
||||
|
||||
// Actualiza el volumen de la musica
|
||||
updateMusicVolume();
|
||||
|
||||
// Actualiza el objeto Screen
|
||||
Screen::get()->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el final en pantalla
|
||||
void Ending::render() {
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
Screen::get()->start();
|
||||
|
||||
// Limpia la pantalla
|
||||
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Dibuja las imagenes de la escena
|
||||
sprite_pics_.at(current_scene_).image_sprite->render();
|
||||
sprite_pics_.at(current_scene_).cover_sprite->render();
|
||||
|
||||
// Dibuja los textos de la escena
|
||||
for (const auto& ti : scenes_.at(current_scene_).text_index) {
|
||||
if (counter_ > ti.trigger) {
|
||||
sprite_texts_.at(ti.index).image_sprite->render();
|
||||
sprite_texts_.at(ti.index).cover_sprite->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja la cortinilla de cambio de escena
|
||||
renderCoverTexture();
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void Ending::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
globalEvents::check(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las entradas
|
||||
void Ending::checkInput() {
|
||||
globalInputs::check();
|
||||
}
|
||||
|
||||
// Inicializa los textos
|
||||
void Ending::iniTexts() {
|
||||
// Vector con los textos
|
||||
std::vector<TextAndPosition> texts;
|
||||
|
||||
// Escena #0
|
||||
texts.push_back({"HE FINALLY MANAGED", 32});
|
||||
texts.push_back({"TO GET TO THE JAIL", 42});
|
||||
texts.push_back({"WITH ALL HIS PROJECTS", 142});
|
||||
texts.push_back({"READY TO BE FREED", 152});
|
||||
|
||||
// Escena #1
|
||||
texts.push_back({"ALL THE JAILERS WERE THERE", 1});
|
||||
texts.push_back({"WAITING FOR THE JAILGAMES", 11});
|
||||
texts.push_back({"TO BE RELEASED", 21});
|
||||
|
||||
texts.push_back({"THERE WERE EVEN BARRULLS AND", 161});
|
||||
texts.push_back({"BEGINNERS AMONG THE CROWD", 171});
|
||||
|
||||
texts.push_back({"BRY WAS CRYING...", 181});
|
||||
|
||||
// Escena #2
|
||||
texts.push_back({"BUT SUDDENLY SOMETHING", 19});
|
||||
texts.push_back({"CAUGHT HIS ATTENTION", 29});
|
||||
|
||||
// Escena #3
|
||||
texts.push_back({"A PILE OF JUNK!", 36});
|
||||
texts.push_back({"FULL OF NON WORKING TRASH!!", 46});
|
||||
|
||||
// Escena #4
|
||||
texts.push_back({"AND THEN,", 36});
|
||||
texts.push_back({"FOURTY NEW PROJECTS", 46});
|
||||
texts.push_back({"WERE BORN...", 158});
|
||||
|
||||
// Crea los sprites
|
||||
sprite_texts_.clear();
|
||||
|
||||
for (const auto& txt : texts) {
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
|
||||
const float WIDTH = text->lenght(txt.caption, 1) + 2 + 2;
|
||||
const float HEIGHT = text->getCharacterSize() + 2 + 2;
|
||||
auto text_color = static_cast<Uint8>(PaletteColor::WHITE);
|
||||
auto shadow_color = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
|
||||
EndingSurface st;
|
||||
|
||||
// Crea la textura
|
||||
st.image_surface = std::make_shared<Surface>(WIDTH, HEIGHT);
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(st.image_surface);
|
||||
text->writeDX(TEXT_STROKE, 2, 2, txt.caption, 1, text_color, 2, shadow_color);
|
||||
|
||||
// Crea el sprite
|
||||
st.image_sprite = std::make_shared<SSprite>(st.image_surface, 0, 0, st.image_surface->getWidth(), st.image_surface->getHeight());
|
||||
st.image_sprite->setPosition((options.game.width - st.image_surface->getWidth()) / 2, txt.pos);
|
||||
|
||||
// Crea la cover_surface
|
||||
st.cover_surface = std::make_shared<Surface>(WIDTH, HEIGHT + 8);
|
||||
Screen::get()->setRendererSurface(st.cover_surface);
|
||||
|
||||
// Rellena la cover_surface con color transparente
|
||||
st.cover_surface->clear(static_cast<Uint8>(PaletteColor::TRANSPARENT));
|
||||
|
||||
// Crea una malla de 8 pixels de alto
|
||||
auto surface = Screen::get()->getRendererSurface();
|
||||
auto color = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
for (int i = 0; i < WIDTH; i += 2) {
|
||||
surface->putPixel(i, 0, color);
|
||||
surface->putPixel(i, 2, color);
|
||||
surface->putPixel(i, 4, color);
|
||||
surface->putPixel(i, 6, color);
|
||||
|
||||
surface->putPixel(i + 1, 5, color);
|
||||
surface->putPixel(i + 1, 7, color);
|
||||
}
|
||||
|
||||
// El resto se rellena de color sólido
|
||||
SDL_FRect rect = {0, 8, WIDTH, HEIGHT};
|
||||
surface->fillRect(&rect, color);
|
||||
|
||||
// Crea el sprite
|
||||
st.cover_sprite = std::make_shared<SSprite>(st.cover_surface, 0, 0, st.cover_surface->getWidth(), st.cover_surface->getHeight() - 8);
|
||||
st.cover_sprite->setPosition((options.game.width - st.cover_surface->getWidth()) / 2, txt.pos);
|
||||
st.cover_sprite->setClip(0, 8, st.cover_surface->getWidth(), st.cover_surface->getHeight());
|
||||
|
||||
// Inicializa variables
|
||||
st.cover_clip_desp = 8;
|
||||
st.cover_clip_height = HEIGHT;
|
||||
|
||||
sprite_texts_.push_back(st);
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa las imagenes
|
||||
void Ending::iniPics() {
|
||||
// Vector con las rutas y la posición
|
||||
std::vector<TextAndPosition> pics;
|
||||
|
||||
pics.push_back({"ending1.gif", 48});
|
||||
pics.push_back({"ending2.gif", 26});
|
||||
pics.push_back({"ending3.gif", 29});
|
||||
pics.push_back({"ending4.gif", 63});
|
||||
pics.push_back({"ending5.gif", 53});
|
||||
|
||||
// Crea los sprites
|
||||
sprite_pics_.clear();
|
||||
|
||||
for (const auto& pic : pics) {
|
||||
EndingSurface sp;
|
||||
|
||||
// Crea la texture
|
||||
sp.image_surface = Resource::get()->getSurface(pic.caption);
|
||||
sp.image_surface->setTransparentColor();
|
||||
const float WIDTH = sp.image_surface->getWidth();
|
||||
const float HEIGHT = sp.image_surface->getHeight();
|
||||
|
||||
// Crea el sprite
|
||||
sp.image_sprite = std::make_shared<SSprite>(sp.image_surface, 0, 0, WIDTH, HEIGHT);
|
||||
sp.image_sprite->setPosition((options.game.width - WIDTH) / 2, pic.pos);
|
||||
|
||||
// Crea la cover_surface
|
||||
sp.cover_surface = std::make_shared<Surface>(WIDTH, HEIGHT + 8);
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(sp.cover_surface);
|
||||
|
||||
// Rellena la cover_surface con color transparente
|
||||
sp.cover_surface->clear(static_cast<Uint8>(PaletteColor::TRANSPARENT));
|
||||
|
||||
// Crea una malla en los primeros 8 pixels
|
||||
auto surface = Screen::get()->getRendererSurface();
|
||||
auto color = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
for (int i = 0; i < WIDTH; i += 2) {
|
||||
surface->putPixel(i, 0, color);
|
||||
surface->putPixel(i, 2, color);
|
||||
surface->putPixel(i, 4, color);
|
||||
surface->putPixel(i, 6, color);
|
||||
|
||||
surface->putPixel(i + 1, 5, color);
|
||||
surface->putPixel(i + 1, 7, color);
|
||||
}
|
||||
|
||||
// El resto se rellena de color sólido
|
||||
SDL_FRect rect = {0.0F, 8.0F, WIDTH, HEIGHT};
|
||||
surface->fillRect(&rect, color);
|
||||
|
||||
// Crea el sprite
|
||||
sp.cover_sprite = std::make_shared<SSprite>(sp.cover_surface, 0, 0, sp.cover_surface->getWidth(), sp.cover_surface->getHeight() - 8);
|
||||
sp.cover_sprite->setPosition((options.game.width - sp.cover_surface->getWidth()) / 2, pic.pos);
|
||||
sp.cover_sprite->setClip(0, 8, sp.cover_surface->getWidth(), sp.cover_surface->getHeight());
|
||||
|
||||
// Inicializa variables
|
||||
sp.cover_clip_desp = 8;
|
||||
sp.cover_clip_height = HEIGHT;
|
||||
|
||||
sprite_pics_.push_back(sp);
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa las escenas
|
||||
void Ending::iniScenes() {
|
||||
// Variable para los tiempos
|
||||
int trigger;
|
||||
constexpr int LAPSE = 80;
|
||||
|
||||
// Crea el contenedor
|
||||
SceneData sc;
|
||||
|
||||
// Inicializa el vector
|
||||
scenes_.clear();
|
||||
|
||||
// Crea la escena #0
|
||||
sc.counter_end = 1000;
|
||||
sc.picture_index = 0;
|
||||
sc.text_index.clear();
|
||||
trigger = 85 * 2;
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({0, trigger});
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({1, trigger});
|
||||
trigger += LAPSE * 3;
|
||||
sc.text_index.push_back({2, trigger});
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({3, trigger});
|
||||
scenes_.push_back(sc);
|
||||
|
||||
// Crea la escena #1
|
||||
sc.counter_end = 1400;
|
||||
sc.picture_index = 1;
|
||||
sc.text_index.clear();
|
||||
trigger = 140 * 2;
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({4, trigger});
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({5, trigger});
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({6, trigger});
|
||||
trigger += LAPSE * 3;
|
||||
sc.text_index.push_back({7, trigger});
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({8, trigger});
|
||||
trigger += LAPSE * 3;
|
||||
sc.text_index.push_back({9, trigger});
|
||||
scenes_.push_back(sc);
|
||||
|
||||
// Crea la escena #2
|
||||
sc.counter_end = 1000;
|
||||
sc.picture_index = 2;
|
||||
sc.text_index.clear();
|
||||
trigger = 148 / 2;
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({10, trigger});
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({11, trigger});
|
||||
scenes_.push_back(sc);
|
||||
|
||||
// Crea la escena #3
|
||||
sc.counter_end = 800;
|
||||
sc.picture_index = 3;
|
||||
sc.text_index.clear();
|
||||
trigger = 87 / 2;
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({12, trigger});
|
||||
trigger += LAPSE / 2;
|
||||
sc.text_index.push_back({13, trigger});
|
||||
scenes_.push_back(sc);
|
||||
|
||||
// Crea la escena #4
|
||||
sc.counter_end = 1000;
|
||||
sc.picture_index = 4;
|
||||
sc.text_index.clear();
|
||||
trigger = 91 * 2;
|
||||
trigger += LAPSE;
|
||||
sc.text_index.push_back({14, trigger});
|
||||
trigger += LAPSE * 2;
|
||||
sc.text_index.push_back({15, trigger});
|
||||
trigger += LAPSE * 3;
|
||||
sc.text_index.push_back({16, trigger});
|
||||
scenes_.push_back(sc);
|
||||
}
|
||||
|
||||
// Bucle principal
|
||||
void Ending::run() {
|
||||
JA_PlayMusic(Resource::get()->getMusic("ending1.ogg"));
|
||||
|
||||
while (options.section.section == Section::ENDING) {
|
||||
update();
|
||||
checkEvents();
|
||||
render();
|
||||
}
|
||||
|
||||
JA_StopMusic();
|
||||
JA_SetVolume(128);
|
||||
}
|
||||
|
||||
// Actualiza los contadores
|
||||
void Ending::updateCounters() {
|
||||
// Incrementa el contador
|
||||
if (pre_counter_ < 200) {
|
||||
pre_counter_++;
|
||||
} else {
|
||||
counter_++;
|
||||
}
|
||||
|
||||
if (counter_ > scenes_[current_scene_].counter_end - 100) {
|
||||
cover_counter_++;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las cortinillas de los elementos
|
||||
void Ending::updateSpriteCovers() {
|
||||
// Actualiza la cortinilla de los textos
|
||||
if (counter_ % 4 == 0) {
|
||||
for (auto ti : scenes_.at(current_scene_).text_index) {
|
||||
if (counter_ > ti.trigger) {
|
||||
if (sprite_texts_.at(ti.index).cover_clip_desp > 0) {
|
||||
sprite_texts_.at(ti.index).cover_clip_desp -= 2;
|
||||
} else if (sprite_texts_.at(ti.index).cover_clip_height > 0) {
|
||||
sprite_texts_.at(ti.index).cover_clip_height -= 2;
|
||||
sprite_texts_.at(ti.index).cover_sprite->setY(sprite_texts_.at(ti.index).cover_sprite->getY() + 2);
|
||||
}
|
||||
sprite_texts_.at(ti.index).cover_sprite->setClip(0, sprite_texts_.at(ti.index).cover_clip_desp, sprite_texts_.at(ti.index).cover_sprite->getWidth(), sprite_texts_.at(ti.index).cover_clip_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza la cortinilla de las imágenes
|
||||
if (counter_ % 2 == 0) {
|
||||
if (sprite_pics_.at(current_scene_).cover_clip_desp > 0) {
|
||||
sprite_pics_.at(current_scene_).cover_clip_desp -= 2;
|
||||
} else if (sprite_pics_.at(current_scene_).cover_clip_height > 0) {
|
||||
sprite_pics_.at(current_scene_).cover_clip_height -= 2;
|
||||
if (sprite_pics_.at(current_scene_).cover_clip_height < 0) {
|
||||
sprite_pics_.at(current_scene_).cover_clip_height = 0;
|
||||
}
|
||||
sprite_pics_.at(current_scene_).cover_sprite->setY(sprite_pics_.at(current_scene_).cover_sprite->getY() + 2);
|
||||
}
|
||||
sprite_pics_.at(current_scene_).cover_sprite->setClip(0, sprite_pics_.at(current_scene_).cover_clip_desp, sprite_pics_.at(current_scene_).cover_sprite->getWidth(), sprite_pics_.at(current_scene_).cover_clip_height);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si se ha de cambiar de escena
|
||||
void Ending::checkChangeScene() {
|
||||
if (counter_ > scenes_[current_scene_].counter_end) {
|
||||
current_scene_++;
|
||||
counter_ = 0;
|
||||
cover_counter_ = 0;
|
||||
if (current_scene_ == 5) {
|
||||
// Termina el bucle
|
||||
options.section.section = Section::ENDING2;
|
||||
|
||||
// Mantiene los valores anteriores
|
||||
current_scene_ = 4;
|
||||
cover_counter_ = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rellena la textura para la cortinilla
|
||||
void Ending::fillCoverTexture() {
|
||||
// Rellena la textura que cubre el texto con color transparente
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(cover_surface_);
|
||||
cover_surface_->clear(static_cast<Uint8>(PaletteColor::TRANSPARENT));
|
||||
|
||||
// Los primeros 8 pixels crea una malla
|
||||
const Uint8 color = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
auto surface = Screen::get()->getRendererSurface();
|
||||
for (int i = 0; i < 256; i += 2) {
|
||||
surface->putPixel(i + 0, options.game.height + 0, color);
|
||||
surface->putPixel(i + 1, options.game.height + 1, color);
|
||||
surface->putPixel(i + 0, options.game.height + 2, color);
|
||||
surface->putPixel(i + 1, options.game.height + 3, color);
|
||||
|
||||
surface->putPixel(i, options.game.height + 4, color);
|
||||
surface->putPixel(i, options.game.height + 6, color);
|
||||
}
|
||||
|
||||
// El resto se rellena de color sólido
|
||||
SDL_FRect rect = {0, 0, 256, options.game.height};
|
||||
surface->fillRect(&rect, color);
|
||||
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
|
||||
// Dibuja la cortinilla de cambio de escena
|
||||
void Ending::renderCoverTexture() {
|
||||
if (cover_counter_ > 0) {
|
||||
// Dibuja la textura que cubre el texto
|
||||
const int OFFSET = std::min(cover_counter_, 100);
|
||||
SDL_FRect srcRect = {0.0F, 200.0F - (cover_counter_ * 2.0F), 256.0F, OFFSET * 2.0F};
|
||||
SDL_FRect dstRect = {0.0F, 0.0F, 256.0F, OFFSET * 2.0F};
|
||||
cover_surface_->render(&srcRect, &dstRect);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el volumen de la musica
|
||||
void Ending::updateMusicVolume() {
|
||||
if (current_scene_ == 4 && cover_counter_ > 0) {
|
||||
const float step = (100.0f - cover_counter_) / 100.0f;
|
||||
const int volume = 128 * step;
|
||||
JA_SetVolume(volume);
|
||||
}
|
||||
}
|
||||
103
source2/Game/Scenes/ending.h
Normal file
103
source2/Game/Scenes/ending.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
class SSprite; // lines 8-8
|
||||
class Surface; // lines 9-9
|
||||
|
||||
class Ending {
|
||||
private:
|
||||
// Estructuras
|
||||
struct EndingSurface // Estructura con dos texturas y sprites, uno para mostrar y el otro hace de cortinilla
|
||||
{
|
||||
std::shared_ptr<Surface> image_surface; // Surface a mostrar
|
||||
std::shared_ptr<SSprite> image_sprite; // SSprite para mostrar la textura
|
||||
std::shared_ptr<Surface> cover_surface; // Surface que cubre a la otra textura
|
||||
std::shared_ptr<SSprite> cover_sprite; // SSprite para mostrar la textura que cubre a la otra textura
|
||||
int cover_clip_desp; // Desplazamiento del spriteClip de la textura de cobertura
|
||||
int cover_clip_height; // Altura del spriteClip de la textura de cobertura
|
||||
};
|
||||
|
||||
struct TextAndPosition // Estructura con un texto y su posición en el eje Y
|
||||
{
|
||||
std::string caption; // Texto
|
||||
int pos; // Posición
|
||||
};
|
||||
|
||||
struct TextIndex {
|
||||
int index;
|
||||
int trigger;
|
||||
};
|
||||
|
||||
struct SceneData // Estructura para crear cada una de las escenas del final
|
||||
{
|
||||
std::vector<TextIndex> text_index; // Indices del vector de textos a mostrar y su disparador
|
||||
int picture_index; // Indice del vector de imagenes a mostrar
|
||||
int counter_end; // Valor del contador en el que finaliza la escena
|
||||
};
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<Surface> cover_surface_; // Surface para cubrir el texto
|
||||
|
||||
// Variables
|
||||
int counter_; // Contador
|
||||
int pre_counter_; // Contador previo
|
||||
int cover_counter_; // Contador para la cortinilla
|
||||
Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa
|
||||
std::vector<EndingSurface> sprite_texts_; // Vector con los sprites de texto con su cortinilla
|
||||
std::vector<EndingSurface> sprite_pics_; // Vector con los sprites de texto con su cortinilla
|
||||
int current_scene_; // Escena actual
|
||||
std::vector<SceneData> scenes_; // Vector con los textos e imagenes de cada escena
|
||||
|
||||
// Actualiza el objeto
|
||||
void update();
|
||||
|
||||
// Dibuja el final en pantalla
|
||||
void render();
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void checkEvents();
|
||||
|
||||
// Comprueba las entradas
|
||||
void checkInput();
|
||||
|
||||
// Inicializa los textos
|
||||
void iniTexts();
|
||||
|
||||
// Inicializa las imagenes
|
||||
void iniPics();
|
||||
|
||||
// Inicializa las escenas
|
||||
void iniScenes();
|
||||
|
||||
// Actualiza los contadores
|
||||
void updateCounters();
|
||||
|
||||
// Actualiza las cortinillas de los elementos
|
||||
void updateSpriteCovers();
|
||||
|
||||
// Comprueba si se ha de cambiar de escena
|
||||
void checkChangeScene();
|
||||
|
||||
// Rellena la textura para la cortinilla
|
||||
void fillCoverTexture();
|
||||
|
||||
// Dibuja la cortinilla de cambio de escena
|
||||
void renderCoverTexture();
|
||||
|
||||
// Actualiza el volumen de la musica
|
||||
void updateMusicVolume();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Ending();
|
||||
|
||||
// Destructor
|
||||
~Ending() = default;
|
||||
|
||||
// Bucle principal
|
||||
void run();
|
||||
};
|
||||
492
source2/Game/Scenes/ending2.cpp
Normal file
492
source2/Game/Scenes/ending2.cpp
Normal file
@@ -0,0 +1,492 @@
|
||||
#include "ending2.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para max, replace
|
||||
|
||||
#include "defines.h" // Para GAMECANVAS_CENTER_X, GAMECANVAS_CENTER_Y
|
||||
#include "external/jail_audio.h" // Para JA_SetVolume, JA_PlayMusic, JA_StopMusic
|
||||
#include "global_events.h" // Para check
|
||||
#include "global_inputs.h" // Para check
|
||||
#include "options.h" // Para Options, options, OptionsGame, Sectio...
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_animated_sprite.h" // Para SAnimatedSprite
|
||||
#include "sprite/surface_moving_sprite.h" // Para SMovingSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "text.h" // Para Text
|
||||
#include "utils.h" // Para PaletteColor, stringToColor
|
||||
|
||||
// Constructor
|
||||
Ending2::Ending2()
|
||||
: state_(EndingState::PRE_CREDITS, SDL_GetTicks(), STATE_PRE_CREDITS_DURATION_) {
|
||||
options.section.section = Section::ENDING2;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
|
||||
// Inicializa el vector de colores
|
||||
const std::vector<std::string> COLORS = {"white", "yellow", "cyan", "green", "magenta", "red", "blue", "black"};
|
||||
for (const auto& color : COLORS) {
|
||||
colors_.push_back(stringToColor(color));
|
||||
}
|
||||
|
||||
// Cambia el color del borde
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Inicializa la lista de sprites
|
||||
iniSpriteList();
|
||||
|
||||
// Carga todos los sprites desde una lista
|
||||
loadSprites();
|
||||
|
||||
// Coloca los sprites en su sito
|
||||
placeSprites();
|
||||
|
||||
// Crea los sprites con las texturas con los textos
|
||||
createSpriteTexts();
|
||||
|
||||
// Crea los sprites con las texturas con los textos del final
|
||||
createTexts();
|
||||
}
|
||||
|
||||
// Actualiza el objeto
|
||||
void Ending2::update() {
|
||||
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
|
||||
if (SDL_GetTicks() - ticks_ > GAME_SPEED) {
|
||||
// Actualiza el contador de ticks
|
||||
ticks_ = SDL_GetTicks();
|
||||
|
||||
// Comprueba las entradas
|
||||
checkInput();
|
||||
|
||||
// Actualiza el estado
|
||||
updateState();
|
||||
|
||||
switch (state_.state) {
|
||||
case EndingState::CREDITS:
|
||||
// Actualiza los sprites, los textos y los textos del final
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
updateSprites();
|
||||
updateTextSprites();
|
||||
updateTexts();
|
||||
}
|
||||
break;
|
||||
|
||||
case EndingState::FADING:
|
||||
// Actualiza el fade final y el volumen de la música
|
||||
updateFinalFade();
|
||||
updateMusicVolume();
|
||||
break;
|
||||
|
||||
default:
|
||||
// No hacer nada si el estado no corresponde a un caso manejado
|
||||
break;
|
||||
}
|
||||
|
||||
// Actualiza el objeto
|
||||
Screen::get()->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el final en pantalla
|
||||
void Ending2::render() {
|
||||
// Prepara para empezar a dibujar en la surface de juego
|
||||
Screen::get()->start();
|
||||
|
||||
// Limpia la pantalla
|
||||
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Dibuja los sprites
|
||||
renderSprites();
|
||||
|
||||
// Dibuja los sprites con el texto
|
||||
renderSpriteTexts();
|
||||
|
||||
// Dibuja los sprites con el texto del final
|
||||
renderTexts();
|
||||
|
||||
// Dibuja una trama arriba y abajo
|
||||
Uint8 color = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
auto surface = Screen::get()->getRendererSurface();
|
||||
for (int i = 0; i < 256; i += 2) {
|
||||
surface->putPixel(i + 0, 0, color);
|
||||
surface->putPixel(i + 1, 1, color);
|
||||
surface->putPixel(i + 0, 2, color);
|
||||
surface->putPixel(i + 1, 3, color);
|
||||
|
||||
surface->putPixel(i, 4, color);
|
||||
surface->putPixel(i, 6, color);
|
||||
|
||||
surface->putPixel(i + 0, 191, color);
|
||||
surface->putPixel(i + 1, 190, color);
|
||||
surface->putPixel(i + 0, 189, color);
|
||||
surface->putPixel(i + 1, 188, color);
|
||||
|
||||
surface->putPixel(i, 187, color);
|
||||
surface->putPixel(i, 185, color);
|
||||
}
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void Ending2::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
globalEvents::check(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las entradas
|
||||
void Ending2::checkInput() {
|
||||
globalInputs::check();
|
||||
}
|
||||
|
||||
// Bucle principal
|
||||
void Ending2::run() {
|
||||
JA_PlayMusic(Resource::get()->getMusic("ending2.ogg"));
|
||||
|
||||
while (options.section.section == Section::ENDING2) {
|
||||
update();
|
||||
checkEvents();
|
||||
render();
|
||||
}
|
||||
|
||||
JA_StopMusic();
|
||||
JA_SetVolume(128);
|
||||
}
|
||||
|
||||
// Actualiza el estado
|
||||
void Ending2::updateState() {
|
||||
switch (state_.state) {
|
||||
case EndingState::PRE_CREDITS:
|
||||
if (state_.hasEnded(EndingState::PRE_CREDITS)) {
|
||||
state_.set(EndingState::CREDITS, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case EndingState::CREDITS:
|
||||
if (texts_.back()->getPosY() <= GAMECANVAS_CENTER_Y) {
|
||||
state_.set(EndingState::POST_CREDITS, STATE_POST_CREDITS_DURATION_);
|
||||
}
|
||||
break;
|
||||
|
||||
case EndingState::POST_CREDITS:
|
||||
if (state_.hasEnded(EndingState::POST_CREDITS)) {
|
||||
state_.set(EndingState::FADING, STATE_FADE_DURATION_);
|
||||
}
|
||||
break;
|
||||
|
||||
case EndingState::FADING:
|
||||
if (state_.hasEnded(EndingState::FADING)) {
|
||||
options.section.section = Section::LOGO;
|
||||
options.section.subsection = Subsection::LOGO_TO_INTRO;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa la lista de sprites
|
||||
void Ending2::iniSpriteList() {
|
||||
// Reinicia el vector
|
||||
sprite_list_.clear();
|
||||
|
||||
// Añade los valores
|
||||
sprite_list_.push_back("bin");
|
||||
sprite_list_.push_back("floppy");
|
||||
sprite_list_.push_back("bird");
|
||||
sprite_list_.push_back("chip");
|
||||
sprite_list_.push_back("jeannine");
|
||||
sprite_list_.push_back("spark");
|
||||
sprite_list_.push_back("code");
|
||||
sprite_list_.push_back("paco");
|
||||
sprite_list_.push_back("elsa");
|
||||
sprite_list_.push_back("z80");
|
||||
|
||||
sprite_list_.push_back("bell");
|
||||
sprite_list_.push_back("dong");
|
||||
|
||||
sprite_list_.push_back("amstrad_cs");
|
||||
sprite_list_.push_back("breakout");
|
||||
|
||||
sprite_list_.push_back("flying_arounder");
|
||||
sprite_list_.push_back("stopped_arounder");
|
||||
sprite_list_.push_back("walking_arounder");
|
||||
sprite_list_.push_back("arounders_door");
|
||||
sprite_list_.push_back("arounders_machine");
|
||||
|
||||
sprite_list_.push_back("abad");
|
||||
sprite_list_.push_back("abad_bell");
|
||||
sprite_list_.push_back("lord_abad");
|
||||
|
||||
sprite_list_.push_back("bat");
|
||||
sprite_list_.push_back("batman_bell");
|
||||
sprite_list_.push_back("batman_fire");
|
||||
sprite_list_.push_back("batman");
|
||||
|
||||
sprite_list_.push_back("demon");
|
||||
sprite_list_.push_back("heavy");
|
||||
sprite_list_.push_back("dimallas");
|
||||
sprite_list_.push_back("guitar");
|
||||
|
||||
sprite_list_.push_back("jailbattle_alien");
|
||||
sprite_list_.push_back("jailbattle_human");
|
||||
|
||||
sprite_list_.push_back("jailer_#1");
|
||||
sprite_list_.push_back("jailer_#2");
|
||||
sprite_list_.push_back("jailer_#3");
|
||||
sprite_list_.push_back("bry");
|
||||
sprite_list_.push_back("upv_student");
|
||||
|
||||
sprite_list_.push_back("lamp");
|
||||
sprite_list_.push_back("robot");
|
||||
sprite_list_.push_back("congo");
|
||||
sprite_list_.push_back("crosshair");
|
||||
sprite_list_.push_back("tree_thing");
|
||||
|
||||
sprite_list_.push_back("matatunos");
|
||||
sprite_list_.push_back("tuno");
|
||||
|
||||
sprite_list_.push_back("mummy");
|
||||
sprite_list_.push_back("sam");
|
||||
|
||||
sprite_list_.push_back("qvoid");
|
||||
sprite_list_.push_back("sigmasua");
|
||||
|
||||
sprite_list_.push_back("tv_panel");
|
||||
sprite_list_.push_back("tv");
|
||||
|
||||
sprite_list_.push_back("spider");
|
||||
sprite_list_.push_back("shock");
|
||||
sprite_list_.push_back("wave");
|
||||
|
||||
sprite_list_.push_back("player");
|
||||
}
|
||||
|
||||
// Carga todos los sprites desde una lista
|
||||
void Ending2::loadSprites() {
|
||||
// Inicializa variables
|
||||
sprite_max_width_ = 0;
|
||||
sprite_max_height_ = 0;
|
||||
|
||||
// Carga los sprites
|
||||
for (const auto& file : sprite_list_) {
|
||||
sprites_.emplace_back(std::make_shared<SAnimatedSprite>(Resource::get()->getSurface(file + ".gif"), Resource::get()->getAnimations(file + ".ani")));
|
||||
sprite_max_width_ = std::max(sprites_.back()->getWidth(), sprite_max_width_);
|
||||
sprite_max_height_ = std::max(sprites_.back()->getHeight(), sprite_max_height_);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza los sprites
|
||||
void Ending2::updateSprites() {
|
||||
for (auto sprite : sprites_) {
|
||||
sprite->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza los sprites de texto
|
||||
void Ending2::updateTextSprites() {
|
||||
for (auto sprite : sprite_texts_) {
|
||||
sprite->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza los sprites de texto del final
|
||||
void Ending2::updateTexts() {
|
||||
for (auto sprite : texts_) {
|
||||
sprite->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja los sprites
|
||||
void Ending2::renderSprites() {
|
||||
const Uint8 colorA = static_cast<Uint8>(PaletteColor::RED);
|
||||
for (auto sprite : sprites_) {
|
||||
const bool A = sprite->getRect().y + sprite->getRect().h > 0;
|
||||
const bool B = sprite->getRect().y < options.game.height;
|
||||
if (A && B) {
|
||||
sprite->render(1, colorA);
|
||||
}
|
||||
}
|
||||
|
||||
// Pinta el ultimo elemento de otro color
|
||||
const Uint8 colorB = static_cast<Uint8>(PaletteColor::WHITE);
|
||||
sprites_.back()->render(1, colorB);
|
||||
}
|
||||
|
||||
// Dibuja los sprites con el texto
|
||||
void Ending2::renderSpriteTexts() {
|
||||
const Uint8 color = static_cast<Uint8>(PaletteColor::WHITE);
|
||||
for (auto sprite : sprite_texts_) {
|
||||
const bool A = sprite->getRect().y + sprite->getRect().h > 0;
|
||||
const bool B = sprite->getRect().y < options.game.height;
|
||||
if (A && B) {
|
||||
sprite->render(1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja los sprites con el texto del final
|
||||
void Ending2::renderTexts() {
|
||||
for (auto sprite : texts_) {
|
||||
const bool A = sprite->getRect().y + sprite->getRect().h > 0;
|
||||
const bool B = sprite->getRect().y < options.game.height;
|
||||
if (A && B) {
|
||||
sprite->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Coloca los sprites en su sito
|
||||
void Ending2::placeSprites() {
|
||||
for (int i = 0; i < static_cast<int>(sprites_.size()); ++i) {
|
||||
const float X = i % 2 == 0 ? FIRST_COL_ : SECOND_COL_;
|
||||
const float Y = (i / 1) * (sprite_max_height_ + DIST_SPRITE_TEXT_ + Resource::get()->getText("smb2")->getCharacterSize() + DIST_SPRITE_SPRITE_) + options.game.height + 40;
|
||||
const float W = sprites_.at(i)->getWidth();
|
||||
const float H = sprites_.at(i)->getHeight();
|
||||
const float DX = -(W / 2);
|
||||
const float DY = sprite_max_height_ - H;
|
||||
|
||||
sprites_.at(i)->setPos({X + DX, Y + DY, W, H});
|
||||
sprites_.at(i)->setVelY(SPRITE_DESP_SPEED_);
|
||||
}
|
||||
|
||||
// Recoloca el sprite del jugador, que es el último de la lista
|
||||
const float X = (options.game.width - sprites_.back()->getWidth()) / 2;
|
||||
const float Y = sprites_.back()->getPosY() + sprite_max_height_ * 2;
|
||||
sprites_.back()->setPos(X, Y);
|
||||
sprites_.back()->setCurrentAnimation("walk");
|
||||
}
|
||||
|
||||
// Crea los sprites con las texturas con los textos
|
||||
void Ending2::createSpriteTexts() {
|
||||
// Crea los sprites de texto a partir de la lista
|
||||
for (size_t i = 0; i < sprite_list_.size(); ++i) {
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
|
||||
// Procesa y ajusta el texto del sprite actual
|
||||
std::string txt = sprite_list_[i];
|
||||
std::replace(txt.begin(), txt.end(), '_', ' '); // Reemplaza '_' por ' '
|
||||
if (txt == "player") {
|
||||
txt = "JAILDOCTOR"; // Reemplaza "player" por "JAILDOCTOR"
|
||||
}
|
||||
|
||||
// Calcula las dimensiones del texto
|
||||
const float W = text->lenght(txt, 1);
|
||||
const float H = text->getCharacterSize();
|
||||
|
||||
// Determina la columna y la posición X del texto
|
||||
const float X = (i == sprite_list_.size() - 1)
|
||||
? (GAMECANVAS_CENTER_X - (W / 2))
|
||||
: ((i % 2 == 0 ? FIRST_COL_ : SECOND_COL_) - (W / 2));
|
||||
|
||||
// Calcula la posición Y del texto en base a la posición y altura del sprite
|
||||
const float Y = sprites_.at(i)->getPosY() + sprites_.at(i)->getHeight() + DIST_SPRITE_TEXT_;
|
||||
|
||||
// Crea la surface
|
||||
auto surface = std::make_shared<Surface>(W, H);
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface);
|
||||
text->write(0, 0, txt);
|
||||
|
||||
// Crea el sprite
|
||||
SDL_FRect pos = {X, Y, W, H};
|
||||
sprite_texts_.emplace_back(std::make_shared<SMovingSprite>(surface, pos));
|
||||
sprite_texts_.back()->setVelY(SPRITE_DESP_SPEED_);
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
}
|
||||
|
||||
// Crea los sprites con las texturas con los textos del final
|
||||
void Ending2::createTexts() {
|
||||
// Crea los primeros textos
|
||||
std::vector<std::string> list;
|
||||
list.push_back("STARRING");
|
||||
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
|
||||
// Crea los sprites de texto a partir de la lista
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
// Calcula constantes
|
||||
const float W = text->lenght(list[i], 1);
|
||||
const float H = text->getCharacterSize();
|
||||
const float X = GAMECANVAS_CENTER_X;
|
||||
const float DX = -(W / 2);
|
||||
const float Y = options.game.height + (text->getCharacterSize() * (i * 2));
|
||||
|
||||
// Crea la surface
|
||||
auto surface = std::make_shared<Surface>(W, H);
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface);
|
||||
text->write(0, 0, list[i]);
|
||||
|
||||
// Crea el sprite
|
||||
SDL_FRect pos = {X + DX, Y, W, H};
|
||||
texts_.emplace_back(std::make_shared<SMovingSprite>(surface, pos));
|
||||
texts_.back()->setVelY(SPRITE_DESP_SPEED_);
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
|
||||
// Crea los últimos textos
|
||||
// El primer texto va a continuación del ultimo spriteText
|
||||
const int START = sprite_texts_.back()->getPosY() + text->getCharacterSize() * 15;
|
||||
list.clear();
|
||||
list.push_back("THANK YOU");
|
||||
list.push_back("FOR PLAYING!");
|
||||
|
||||
// Crea los sprites de texto a partir de la lista
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
// Calcula constantes
|
||||
const float W = text->lenght(list[i], 1);
|
||||
const float H = text->getCharacterSize();
|
||||
const float X = GAMECANVAS_CENTER_X;
|
||||
const float DX = -(W / 2);
|
||||
const float Y = START + (text->getCharacterSize() * (i * 2));
|
||||
|
||||
// Crea la surface
|
||||
auto surface = std::make_shared<Surface>(W, H);
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface);
|
||||
text->write(0, 0, list[i]);
|
||||
|
||||
// Crea el sprite
|
||||
SDL_FRect pos = {X + DX, Y, W, H};
|
||||
texts_.emplace_back(std::make_shared<SMovingSprite>(surface, pos));
|
||||
texts_.back()->setVelY(SPRITE_DESP_SPEED_);
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el fade final
|
||||
void Ending2::updateFinalFade() {
|
||||
for (auto sprite : texts_) {
|
||||
sprite->getSurface()->fadeSubPalette(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el volumen de la musica
|
||||
void Ending2::updateMusicVolume() {
|
||||
// Constante para la duración en milisegundos
|
||||
constexpr Uint32 VOLUME_FADE_DURATION = 3000;
|
||||
|
||||
// Tiempo actual
|
||||
const Uint32 CURRENT_TICKS = SDL_GetTicks();
|
||||
|
||||
// Calcular el tiempo transcurrido desde init_ticks
|
||||
Uint32 elapsed_ticks = CURRENT_TICKS - state_.init_ticks;
|
||||
|
||||
// Limitar el tiempo máximo a la duración definida
|
||||
elapsed_ticks = std::min(elapsed_ticks, VOLUME_FADE_DURATION);
|
||||
|
||||
// Calcular el step basado en la duración
|
||||
const float STEP = (static_cast<float>(VOLUME_FADE_DURATION) - elapsed_ticks) / VOLUME_FADE_DURATION;
|
||||
|
||||
// Calcular el volumen en función del step
|
||||
const int VOLUME = static_cast<int>(128 * STEP);
|
||||
|
||||
// Actualizar el volumen
|
||||
JA_SetVolume(VOLUME);
|
||||
}
|
||||
140
source2/Game/Scenes/ending2.h
Normal file
140
source2/Game/Scenes/ending2.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "defines.h" // Para GAMECANVAS_WIDTH, GAMECANVAS_FIRST_QUAR...
|
||||
class SAnimatedSprite; // lines 9-9
|
||||
class SMovingSprite; // lines 10-10
|
||||
|
||||
class Ending2 {
|
||||
private:
|
||||
// Enum para representar los estados del final
|
||||
enum class EndingState : int {
|
||||
PRE_CREDITS, // Estado previo a los créditos
|
||||
CREDITS, // Estado de los créditos
|
||||
POST_CREDITS, // Estado posterior a los créditos
|
||||
FADING, // Estado de fundido de los textos a negrp
|
||||
};
|
||||
|
||||
// Estructura para controlar los estados y su duración
|
||||
struct State {
|
||||
EndingState state; // Estado actual
|
||||
Uint32 init_ticks; // Ticks en los que se inicializó el estado
|
||||
Uint32 duration; // Duración en milisegundos para el estado actual
|
||||
|
||||
// Constructor parametrizado para inicializar la estructura
|
||||
State(EndingState initialState, Uint32 initialTicks, Uint32 stateDuration)
|
||||
: state(initialState),
|
||||
init_ticks(initialTicks),
|
||||
duration(stateDuration) {}
|
||||
|
||||
// Método para comprobar si el estado ha terminado y verifica el nombre del estado
|
||||
bool hasEnded(EndingState expectedState) const {
|
||||
// Comprobar si el estado actual coincide con el estado esperado
|
||||
if (state != expectedState) {
|
||||
return false; // Si no coincide, considerar que no ha terminado
|
||||
}
|
||||
|
||||
// Comprobar si el tiempo transcurrido excede la duración
|
||||
return (SDL_GetTicks() - init_ticks) >= duration;
|
||||
}
|
||||
|
||||
// Método para establecer un nuevo estado
|
||||
void set(EndingState newState, Uint32 newDuration) {
|
||||
state = newState; // Actualizar el estado
|
||||
init_ticks = SDL_GetTicks(); // Reiniciar el tiempo de inicio
|
||||
duration = newDuration; // Actualizar la duración
|
||||
}
|
||||
};
|
||||
|
||||
// Constantes
|
||||
static constexpr int FIRST_COL_ = GAMECANVAS_FIRST_QUARTER_X + (GAMECANVAS_WIDTH / 16); // Primera columna por donde desfilan los sprites
|
||||
static constexpr int SECOND_COL_ = GAMECANVAS_THIRD_QUARTER_X - (GAMECANVAS_WIDTH / 16); // Segunda columna por donde desfilan los sprites
|
||||
static constexpr int DIST_SPRITE_TEXT_ = 8; // Distancia entre el sprite y el texto que lo acompaña
|
||||
static constexpr int DIST_SPRITE_SPRITE_ = 0; // Distancia entre dos sprites de la misma columna
|
||||
static constexpr float SPRITE_DESP_SPEED_ = -0.2f; // Velocidad de desplazamiento de los sprites
|
||||
static constexpr int STATE_PRE_CREDITS_DURATION_ = 3000;
|
||||
static constexpr int STATE_POST_CREDITS_DURATION_ = 5000;
|
||||
static constexpr int STATE_FADE_DURATION_ = 5000;
|
||||
|
||||
// Objetos y punteros
|
||||
std::vector<std::shared_ptr<SAnimatedSprite>> sprites_; // Vector con todos los sprites a dibujar
|
||||
std::vector<std::shared_ptr<SMovingSprite>> sprite_texts_; // Vector con los sprites de texto de los sprites
|
||||
std::vector<std::shared_ptr<SMovingSprite>> texts_; // Vector con los sprites de texto
|
||||
|
||||
// Variables
|
||||
Uint32 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
|
||||
std::vector<std::string> sprite_list_; // Lista con todos los sprites a dibujar
|
||||
std::vector<Uint8> colors_; // Vector con los colores para el fade
|
||||
float sprite_max_width_ = 0; // El valor de ancho del sprite mas ancho
|
||||
float sprite_max_height_ = 0; // El valor de alto del sprite mas alto
|
||||
State state_; // Controla el estado de la clase
|
||||
|
||||
// Actualiza el objeto
|
||||
void update();
|
||||
|
||||
// Dibuja el final en pantalla
|
||||
void render();
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void checkEvents();
|
||||
|
||||
// Comprueba las entradas
|
||||
void checkInput();
|
||||
|
||||
// Actualiza el estado
|
||||
void updateState();
|
||||
|
||||
// Inicializa la lista de sprites
|
||||
void iniSpriteList();
|
||||
|
||||
// Carga todos los sprites desde una lista
|
||||
void loadSprites();
|
||||
|
||||
// Actualiza los sprites
|
||||
void updateSprites();
|
||||
|
||||
// Actualiza los sprites de texto
|
||||
void updateTextSprites();
|
||||
|
||||
// Actualiza los sprites de texto del final
|
||||
void updateTexts();
|
||||
|
||||
// Dibuja los sprites
|
||||
void renderSprites();
|
||||
|
||||
// Dibuja los sprites con el texto
|
||||
void renderSpriteTexts();
|
||||
|
||||
// Dibuja los sprites con el texto del final
|
||||
void renderTexts();
|
||||
|
||||
// Coloca los sprites en su sito
|
||||
void placeSprites();
|
||||
|
||||
// Crea los sprites con las texturas con los textos
|
||||
void createSpriteTexts();
|
||||
|
||||
// Crea los sprites con las texturas con los textos del final
|
||||
void createTexts();
|
||||
|
||||
// Actualiza el fade final
|
||||
void updateFinalFade();
|
||||
|
||||
// Actualiza el volumen de la musica
|
||||
void updateMusicVolume();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Ending2();
|
||||
|
||||
// Destructor
|
||||
~Ending2() = default;
|
||||
|
||||
// Bucle principal
|
||||
void run();
|
||||
};
|
||||
620
source2/Game/Scenes/game.cpp
Normal file
620
source2/Game/Scenes/game.cpp
Normal file
@@ -0,0 +1,620 @@
|
||||
#include "game.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "asset.h" // Para Asset
|
||||
#include "cheevos.h" // Para Cheevos
|
||||
#include "debug.h" // Para Debug
|
||||
#include "defines.h" // Para BLOCK, PLAY_AREA_HEIGHT, RoomBorder::BOTTOM
|
||||
#include "external/jail_audio.h" // Para JA_PauseMusic, JA_GetMusicState, JA_P...
|
||||
#include "global_events.h" // Para check
|
||||
#include "global_inputs.h" // Para check
|
||||
#include "input.h" // Para Input, InputAction, INPUT_DO_NOT_ALLOW_REPEAT
|
||||
#include "item_tracker.h" // Para ItemTracker
|
||||
#include "options.h" // Para Options, options, Cheat, SectionState
|
||||
#include "resource.h" // Para ResourceRoom, Resource
|
||||
#include "room.h" // Para Room, RoomData
|
||||
#include "room_tracker.h" // Para RoomTracker
|
||||
#include "scoreboard.h" // Para ScoreboardData, Scoreboard
|
||||
#include "screen.h" // Para Screen
|
||||
#include "stats.h" // Para Stats
|
||||
#include "surface.h" // Para Surface
|
||||
#include "text.h" // Para Text, TEXT_CENTER, TEXT_COLOR
|
||||
#include "ui/notifier.h" // Para Notifier, NotificationText, CHEEVO_NO...
|
||||
#include "utils.h" // Para PaletteColor, stringToColor
|
||||
|
||||
// Constructor
|
||||
Game::Game(GameMode mode)
|
||||
: board_(std::make_shared<ScoreboardData>(0, 9, 0, true, 0, SDL_GetTicks(), options.cheats.jail_is_open == Cheat::CheatState::ENABLED)),
|
||||
scoreboard_(std::make_shared<Scoreboard>(board_)),
|
||||
room_tracker_(std::make_shared<RoomTracker>()),
|
||||
stats_(std::make_shared<Stats>(Asset::get()->get("stats.csv"), Asset::get()->get("stats_buffer.csv"))),
|
||||
mode_(mode),
|
||||
#ifdef DEBUG
|
||||
current_room_("03.room"),
|
||||
spawn_point_(PlayerSpawn(25 * BLOCK, 13 * BLOCK, 0, 0, 0, PlayerState::STANDING, SDL_FLIP_HORIZONTAL))
|
||||
#else
|
||||
current_room_("03.room"),
|
||||
spawn_point_(PlayerSpawn(25 * BLOCK, 13 * BLOCK, 0, 0, 0, PlayerState::STANDING, SDL_FLIP_HORIZONTAL))
|
||||
#endif
|
||||
{
|
||||
#ifdef DEBUG
|
||||
Debug::get()->setEnabled(false);
|
||||
#endif
|
||||
|
||||
// Crea objetos e inicializa variables
|
||||
ItemTracker::init();
|
||||
DEMO_init();
|
||||
room_ = std::make_shared<Room>(current_room_, board_);
|
||||
initPlayer(spawn_point_, room_);
|
||||
initStats();
|
||||
total_items_ = getTotalItems();
|
||||
|
||||
createRoomNameTexture();
|
||||
changeRoom(current_room_);
|
||||
|
||||
Cheevos::get()->enable(!options.cheats.enabled()); // Deshabilita los logros si hay trucos activados
|
||||
Cheevos::get()->clearUnobtainableState();
|
||||
|
||||
options.section.section = (mode_ == GameMode::GAME) ? Section::GAME : Section::DEMO;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
}
|
||||
|
||||
Game::~Game() {
|
||||
ItemTracker::destroy();
|
||||
}
|
||||
|
||||
// Comprueba los eventos de la cola
|
||||
void Game::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
globalEvents::check(event);
|
||||
#ifdef DEBUG
|
||||
checkDebugEvents(event);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba el teclado
|
||||
void Game::checkInput() {
|
||||
if (Input::get()->checkInput(InputAction::TOGGLE_MUSIC, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
board_->music = !board_->music;
|
||||
board_->music ? JA_ResumeMusic() : JA_PauseMusic();
|
||||
Notifier::get()->show({"MUSIC " + std::string(board_->music ? "ENABLED" : "DISABLED")}, NotificationText::CENTER);
|
||||
}
|
||||
|
||||
else if (Input::get()->checkInput(InputAction::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
togglePause();
|
||||
Notifier::get()->show({std::string(paused_ ? "GAME PAUSED" : "GAME RUNNING")}, NotificationText::CENTER);
|
||||
}
|
||||
|
||||
globalInputs::check();
|
||||
}
|
||||
|
||||
// Bucle para el juego
|
||||
void Game::run() {
|
||||
keepMusicPlaying();
|
||||
if (!board_->music && mode_ == GameMode::GAME) {
|
||||
JA_PauseMusic();
|
||||
}
|
||||
|
||||
while (options.section.section == Section::GAME || options.section.section == Section::DEMO) {
|
||||
update();
|
||||
checkEvents();
|
||||
render();
|
||||
}
|
||||
|
||||
if (mode_ == GameMode::GAME) {
|
||||
JA_StopMusic();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el juego, las variables, comprueba la entrada, etc.
|
||||
void Game::update() {
|
||||
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
|
||||
if (SDL_GetTicks() - ticks_ > GAME_SPEED) {
|
||||
// Actualiza el contador de ticks
|
||||
ticks_ = SDL_GetTicks();
|
||||
|
||||
// Comprueba el teclado
|
||||
checkInput();
|
||||
|
||||
#ifdef DEBUG
|
||||
Debug::get()->clear();
|
||||
#endif
|
||||
|
||||
// Actualiza los objetos
|
||||
room_->update();
|
||||
if (mode_ == GameMode::GAME) {
|
||||
player_->update();
|
||||
checkPlayerIsOnBorder();
|
||||
checkPlayerAndItems();
|
||||
checkPlayerAndEnemies();
|
||||
checkIfPlayerIsAlive();
|
||||
checkGameOver();
|
||||
checkEndGame();
|
||||
checkRestoringJail();
|
||||
checkSomeCheevos();
|
||||
}
|
||||
DEMO_checkRoomChange();
|
||||
scoreboard_->update();
|
||||
keepMusicPlaying();
|
||||
updateBlackScreen();
|
||||
|
||||
Screen::get()->update();
|
||||
|
||||
#ifdef DEBUG
|
||||
updateDebugInfo();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Pinta los objetos en pantalla
|
||||
void Game::render() {
|
||||
// Prepara para dibujar el frame
|
||||
Screen::get()->start();
|
||||
|
||||
// Dibuja los elementos del juego en orden
|
||||
room_->renderMap();
|
||||
room_->renderEnemies();
|
||||
room_->renderItems();
|
||||
if (mode_ == GameMode::GAME) {
|
||||
player_->render();
|
||||
}
|
||||
renderRoomName();
|
||||
scoreboard_->render();
|
||||
renderBlackScreen();
|
||||
|
||||
#ifdef DEBUG
|
||||
// Debug info
|
||||
renderDebugInfo();
|
||||
#endif
|
||||
|
||||
// Actualiza la pantalla
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// Pasa la información de debug
|
||||
void Game::updateDebugInfo() {
|
||||
Debug::get()->add("X = " + std::to_string(static_cast<int>(player_->x_)) + ", Y = " + std::to_string(static_cast<int>(player_->y_)));
|
||||
Debug::get()->add("VX = " + std::to_string(player_->vx_).substr(0, 4) + ", VY = " + std::to_string(player_->vy_).substr(0, 4));
|
||||
Debug::get()->add("STATE = " + std::to_string(static_cast<int>(player_->state_)));
|
||||
}
|
||||
|
||||
// Pone la información de debug en pantalla
|
||||
void Game::renderDebugInfo() {
|
||||
if (!Debug::get()->getEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto surface = Screen::get()->getRendererSurface();
|
||||
|
||||
// Borra el marcador
|
||||
SDL_FRect rect = {0, 18 * BLOCK, PLAY_AREA_WIDTH, GAMECANVAS_HEIGHT - PLAY_AREA_HEIGHT};
|
||||
surface->fillRect(&rect, static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Pinta la rejilla
|
||||
/*for (int i = 0; i < PLAY_AREA_BOTTOM; i += 8)
|
||||
{
|
||||
// Lineas horizontales
|
||||
surface->drawLine(0, i, PLAY_AREA_RIGHT, i, static_cast<Uint8>(PaletteColor::BRIGHT_BLACK));
|
||||
}
|
||||
for (int i = 0; i < PLAY_AREA_RIGHT; i += 8)
|
||||
{
|
||||
// Lineas verticales
|
||||
surface->drawLine(i, 0, i, PLAY_AREA_BOTTOM - 1, static_cast<Uint8>(PaletteColor::BRIGHT_BLACK));
|
||||
}*/
|
||||
|
||||
// Pinta el texto
|
||||
Debug::get()->setPos({1, 18 * 8});
|
||||
Debug::get()->render();
|
||||
}
|
||||
|
||||
// Comprueba los eventos
|
||||
void Game::checkDebugEvents(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0) {
|
||||
switch (event.key.key) {
|
||||
case SDL_SCANCODE_G:
|
||||
Debug::get()->toggleEnabled();
|
||||
options.cheats.invincible = static_cast<Cheat::CheatState>(Debug::get()->getEnabled());
|
||||
board_->music = !Debug::get()->getEnabled();
|
||||
board_->music ? JA_ResumeMusic() : JA_PauseMusic();
|
||||
break;
|
||||
|
||||
case SDL_SCANCODE_R:
|
||||
Resource::get()->reload();
|
||||
break;
|
||||
|
||||
case SDL_SCANCODE_W:
|
||||
changeRoom(room_->getRoom(RoomBorder::TOP));
|
||||
break;
|
||||
|
||||
case SDL_SCANCODE_A:
|
||||
changeRoom(room_->getRoom(RoomBorder::LEFT));
|
||||
break;
|
||||
|
||||
case SDL_SCANCODE_S:
|
||||
changeRoom(room_->getRoom(RoomBorder::BOTTOM));
|
||||
break;
|
||||
|
||||
case SDL_SCANCODE_D:
|
||||
changeRoom(room_->getRoom(RoomBorder::RIGHT));
|
||||
break;
|
||||
|
||||
case SDL_SCANCODE_7:
|
||||
Notifier::get()->show({"ACHIEVEMENT UNLOCKED!", "I LIKE MY MULTICOLOURED FRIENDS"}, NotificationText::CENTER, CHEEVO_NOTIFICATION_DURATION, -1, false, "F7");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Escribe el nombre de la pantalla
|
||||
void Game::renderRoomName() {
|
||||
// Dibuja la textura con el nombre de la habitación
|
||||
room_name_surface_->render(nullptr, &room_name_rect_);
|
||||
}
|
||||
|
||||
// Cambia de habitación
|
||||
bool Game::changeRoom(const std::string& room_path) {
|
||||
// En las habitaciones los limites tienen la cadena del fichero o un 0 en caso de no limitar con nada
|
||||
if (room_path == "0") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verifica que exista el fichero que se va a cargar
|
||||
if (Asset::get()->get(room_path) != "") {
|
||||
// Crea un objeto habitación nuevo a partir del fichero
|
||||
room_ = std::make_shared<Room>(room_path, board_);
|
||||
|
||||
// Pone el nombre de la habitación en la textura
|
||||
fillRoomNameTexture();
|
||||
|
||||
// Pone el color del marcador en función del color del borde de la habitación
|
||||
setScoreBoardColor();
|
||||
|
||||
if (room_tracker_->addRoom(room_path)) {
|
||||
// Incrementa el contador de habitaciones visitadas
|
||||
board_->rooms++;
|
||||
options.stats.rooms = board_->rooms;
|
||||
|
||||
// Actualiza las estadisticas
|
||||
stats_->addVisit(room_->getName());
|
||||
}
|
||||
|
||||
// Pasa la nueva habitación al jugador
|
||||
player_->setRoom(room_);
|
||||
|
||||
// Cambia la habitación actual
|
||||
current_room_ = room_path;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el jugador esta en el borde de la pantalla
|
||||
void Game::checkPlayerIsOnBorder() {
|
||||
if (player_->getOnBorder()) {
|
||||
const std::string roomName = room_->getRoom(player_->getBorder());
|
||||
if (changeRoom(roomName)) {
|
||||
player_->switchBorders();
|
||||
spawn_point_ = player_->getSpawnParams();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las colisiones del jugador con los enemigos
|
||||
bool Game::checkPlayerAndEnemies() {
|
||||
const bool death = room_->enemyCollision(player_->getCollider());
|
||||
if (death) {
|
||||
killPlayer();
|
||||
}
|
||||
return death;
|
||||
}
|
||||
|
||||
// Comprueba las colisiones del jugador con los objetos
|
||||
void Game::checkPlayerAndItems() {
|
||||
room_->itemCollision(player_->getCollider());
|
||||
}
|
||||
|
||||
// Comprueba si el jugador esta vivo
|
||||
void Game::checkIfPlayerIsAlive() {
|
||||
if (!player_->isAlive()) {
|
||||
killPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la partida
|
||||
void Game::checkGameOver() {
|
||||
if (board_->lives < 0 && black_screen_counter_ > 17) {
|
||||
options.section.section = Section::GAME_OVER;
|
||||
}
|
||||
}
|
||||
|
||||
// Mata al jugador
|
||||
void Game::killPlayer() {
|
||||
if (options.cheats.invincible == Cheat::CheatState::ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resta una vida al jugador
|
||||
if (options.cheats.infinite_lives == Cheat::CheatState::DISABLED) {
|
||||
--board_->lives;
|
||||
}
|
||||
|
||||
// Actualiza las estadisticas
|
||||
stats_->addDeath(room_->getName());
|
||||
|
||||
// Invalida el logro de pasarse el juego sin morir
|
||||
Cheevos::get()->setUnobtainable(11);
|
||||
|
||||
// Sonido
|
||||
JA_PlaySound(Resource::get()->getSound("death.wav"));
|
||||
|
||||
// Pone la pantalla en negro un tiempo
|
||||
setBlackScreen();
|
||||
|
||||
// Crea la nueva habitación y el nuevo jugador
|
||||
room_ = std::make_shared<Room>(current_room_, board_);
|
||||
initPlayer(spawn_point_, room_);
|
||||
|
||||
// Pone los objetos en pausa mientras esta la habitación en negro
|
||||
room_->setPaused(true);
|
||||
player_->setPaused(true);
|
||||
}
|
||||
|
||||
// Establece la pantalla en negro
|
||||
void Game::setBlackScreen() {
|
||||
black_screen_ = true;
|
||||
}
|
||||
|
||||
// Actualiza las variables relativas a la pantalla en negro
|
||||
void Game::updateBlackScreen() {
|
||||
if (black_screen_) {
|
||||
black_screen_counter_++;
|
||||
if (black_screen_counter_ > 20) {
|
||||
black_screen_ = false;
|
||||
black_screen_counter_ = 0;
|
||||
|
||||
player_->setPaused(false);
|
||||
room_->setPaused(false);
|
||||
Screen::get()->setBorderColor(room_->getBorderColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja la pantalla negra
|
||||
void Game::renderBlackScreen() {
|
||||
if (black_screen_) {
|
||||
auto const color = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
Screen::get()->setRendererSurface();
|
||||
Screen::get()->clearSurface(color);
|
||||
Screen::get()->setBorderColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
// Pone el color del marcador en función del color del borde de la habitación
|
||||
void Game::setScoreBoardColor() {
|
||||
// Obtiene el color del borde
|
||||
const Uint8 BORDER_COLOR = room_->getBorderColor();
|
||||
|
||||
const bool IS_BLACK = BORDER_COLOR == static_cast<Uint8>(PaletteColor::BLACK);
|
||||
const bool IS_BRIGHT_BLACK = BORDER_COLOR == static_cast<Uint8>(PaletteColor::BRIGHT_BLACK);
|
||||
|
||||
// Si el color del borde es negro o negro brillante cambia el texto del marcador a blanco
|
||||
board_->color = IS_BLACK || IS_BRIGHT_BLACK ? static_cast<Uint8>(PaletteColor::WHITE) : BORDER_COLOR;
|
||||
}
|
||||
|
||||
// Comprueba si ha finalizado el juego
|
||||
bool Game::checkEndGame() {
|
||||
const bool isOnTheRoom = room_->getName() == "THE JAIL"; // Estar en la habitación que toca
|
||||
const bool haveTheItems = board_->items >= int(total_items_ * 0.9f) || options.cheats.jail_is_open == Cheat::CheatState::ENABLED; // Con mas del 90% de los items recogidos
|
||||
const bool isOnTheDoor = player_->getRect().x <= 128; // Y en la ubicación que toca (En la puerta)
|
||||
|
||||
if (haveTheItems) {
|
||||
board_->jail_is_open = true;
|
||||
}
|
||||
|
||||
if (haveTheItems && isOnTheRoom && isOnTheDoor) {
|
||||
// Comprueba los logros de completar el juego
|
||||
checkEndGameCheevos();
|
||||
|
||||
options.section.section = Section::ENDING;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtiene la cantidad total de items que hay en el mapeado del juego
|
||||
int Game::getTotalItems() {
|
||||
int items = 0;
|
||||
auto rooms = Resource::get()->getRooms();
|
||||
|
||||
for (const auto& room : rooms) {
|
||||
items += room.room->items.size();
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
// Pone el juego en pausa
|
||||
void Game::togglePause() {
|
||||
paused_ = !paused_;
|
||||
|
||||
player_->setPaused(paused_);
|
||||
room_->setPaused(paused_);
|
||||
scoreboard_->setPaused(paused_);
|
||||
}
|
||||
|
||||
// Da vidas al jugador cuando está en la Jail
|
||||
void Game::checkRestoringJail() {
|
||||
if (room_->getName() != "THE JAIL" || board_->lives == 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
static int counter = 0;
|
||||
|
||||
if (!paused_) {
|
||||
counter++;
|
||||
}
|
||||
|
||||
// Incrementa el numero de vidas
|
||||
if (counter == 100) {
|
||||
counter = 0;
|
||||
board_->lives++;
|
||||
JA_PlaySound(Resource::get()->getSound("death.wav"));
|
||||
|
||||
// Invalida el logro de completar el juego sin entrar a la jail
|
||||
const bool haveTheItems = board_->items >= int(total_items_ * 0.9f);
|
||||
if (!haveTheItems) {
|
||||
Cheevos::get()->setUnobtainable(9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa el diccionario de las estadísticas
|
||||
void Game::initStats() {
|
||||
auto rooms = Resource::get()->getRooms();
|
||||
|
||||
for (const auto& room : rooms) {
|
||||
stats_->addDictionary(room.room->number, room.room->name);
|
||||
}
|
||||
|
||||
stats_->init();
|
||||
}
|
||||
|
||||
// Crea la textura con el nombre de la habitación
|
||||
void Game::fillRoomNameTexture() {
|
||||
// Pone la textura como destino de renderizado
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(room_name_surface_);
|
||||
|
||||
// Rellena la textura de color
|
||||
room_name_surface_->clear(stringToColor("white"));
|
||||
|
||||
// Escribe el texto en la textura
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, GAMECANVAS_CENTER_X, text->getCharacterSize() / 2, room_->getName(), 1, room_->getBGColor());
|
||||
|
||||
// Deja el renderizador por defecto
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
|
||||
// Comprueba algunos logros
|
||||
void Game::checkSomeCheevos() {
|
||||
auto cheevos = Cheevos::get();
|
||||
|
||||
// Logros sobre la cantidad de items
|
||||
if (board_->items == total_items_) {
|
||||
cheevos->unlock(4);
|
||||
cheevos->unlock(3);
|
||||
cheevos->unlock(2);
|
||||
cheevos->unlock(1);
|
||||
} else if (board_->items >= total_items_ * 0.75f) {
|
||||
cheevos->unlock(3);
|
||||
cheevos->unlock(2);
|
||||
cheevos->unlock(1);
|
||||
} else if (board_->items >= total_items_ * 0.5f) {
|
||||
cheevos->unlock(2);
|
||||
cheevos->unlock(1);
|
||||
} else if (board_->items >= total_items_ * 0.25f) {
|
||||
cheevos->unlock(1);
|
||||
}
|
||||
|
||||
// Logros sobre las habitaciones visitadas
|
||||
if (board_->rooms >= 60) {
|
||||
cheevos->unlock(7);
|
||||
cheevos->unlock(6);
|
||||
cheevos->unlock(5);
|
||||
} else if (board_->rooms >= 40) {
|
||||
cheevos->unlock(6);
|
||||
cheevos->unlock(5);
|
||||
} else if (board_->rooms >= 20) {
|
||||
cheevos->unlock(5);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba los logros de completar el juego
|
||||
void Game::checkEndGameCheevos() {
|
||||
auto cheevos = Cheevos::get();
|
||||
|
||||
// "Complete the game"
|
||||
cheevos->unlock(8);
|
||||
|
||||
// "Complete the game without entering the jail"
|
||||
cheevos->unlock(9);
|
||||
|
||||
// "Complete the game with all items"
|
||||
if (board_->items == total_items_) {
|
||||
cheevos->unlock(10);
|
||||
}
|
||||
|
||||
// "Complete the game without dying"
|
||||
cheevos->unlock(11);
|
||||
|
||||
// "Complete the game in under 30 minutes"
|
||||
if (scoreboard_->getMinutes() < 30) {
|
||||
cheevos->unlock(12);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa al jugador
|
||||
void Game::initPlayer(const PlayerSpawn& spawn_point, std::shared_ptr<Room> room) {
|
||||
std::string player_texture = options.cheats.alternate_skin == Cheat::CheatState::ENABLED ? "player2.gif" : "player.gif";
|
||||
std::string player_animations = options.cheats.alternate_skin == Cheat::CheatState::ENABLED ? "player2.ani" : "player.ani";
|
||||
const PlayerData player(spawn_point, player_texture, player_animations, room);
|
||||
player_ = std::make_shared<Player>(player);
|
||||
}
|
||||
|
||||
// Crea la textura para poner el nombre de la habitación
|
||||
void Game::createRoomNameTexture() {
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
room_name_surface_ = std::make_shared<Surface>(options.game.width, text->getCharacterSize() * 2);
|
||||
|
||||
// Establece el destino de la textura
|
||||
room_name_rect_ = {0.0F, PLAY_AREA_HEIGHT, options.game.width, text->getCharacterSize() * 2.0F};
|
||||
}
|
||||
|
||||
// Hace sonar la música
|
||||
void Game::keepMusicPlaying() {
|
||||
const std::string music_path = mode_ == GameMode::GAME ? "game.ogg" : "title.ogg";
|
||||
|
||||
// Si la música no está sonando
|
||||
if (JA_GetMusicState() == JA_MUSIC_INVALID || JA_GetMusicState() == JA_MUSIC_STOPPED) {
|
||||
JA_PlayMusic(Resource::get()->getMusic(music_path));
|
||||
}
|
||||
}
|
||||
|
||||
// DEMO MODE: Inicializa las variables para el modo demo
|
||||
void Game::DEMO_init() {
|
||||
if (mode_ == GameMode::DEMO) {
|
||||
demo_ = DemoData(0, 400, 0, {"04.room", "54.room", "20.room", "09.room", "05.room", "11.room", "31.room", "44.room"});
|
||||
current_room_ = demo_.rooms.front();
|
||||
}
|
||||
}
|
||||
|
||||
// DEMO MODE: Comprueba si se ha de cambiar de habitación
|
||||
void Game::DEMO_checkRoomChange() {
|
||||
if (mode_ == GameMode::DEMO) {
|
||||
demo_.counter++;
|
||||
if (demo_.counter == demo_.room_time) {
|
||||
demo_.counter = 0;
|
||||
demo_.room_index++;
|
||||
if (demo_.room_index == (int)demo_.rooms.size()) {
|
||||
options.section.section = Section::LOGO;
|
||||
options.section.subsection = Subsection::LOGO_TO_TITLE;
|
||||
} else {
|
||||
changeRoom(demo_.rooms[demo_.room_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
175
source2/Game/Scenes/game.h
Normal file
175
source2/Game/Scenes/game.h
Normal file
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <initializer_list> // Para initializer_list
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "player.h" // Para PlayerSpawn
|
||||
class Room; // lines 12-12
|
||||
class RoomTracker; // lines 13-13
|
||||
class Scoreboard; // lines 14-14
|
||||
class Stats; // lines 15-15
|
||||
class Surface;
|
||||
struct ScoreboardData; // lines 16-16
|
||||
|
||||
enum class GameMode {
|
||||
DEMO,
|
||||
GAME
|
||||
};
|
||||
|
||||
class Game {
|
||||
private:
|
||||
// Estructuras
|
||||
struct DemoData {
|
||||
int counter; // Contador para el modo demo
|
||||
int room_time; // Tiempo que se muestra cada habitación
|
||||
int room_index; // Índice para el vector de habitaciones
|
||||
std::vector<std::string> rooms; // Listado con los mapas de la demo
|
||||
|
||||
// Constructor por defecto
|
||||
DemoData()
|
||||
: counter(0),
|
||||
room_time(0),
|
||||
room_index(0),
|
||||
rooms({}) {}
|
||||
|
||||
// Constructor parametrizado
|
||||
DemoData(int counter, int room_time, int room_index, const std::vector<std::string>& rooms)
|
||||
: counter(counter),
|
||||
room_time(room_time),
|
||||
room_index(room_index),
|
||||
rooms(rooms) {}
|
||||
};
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<ScoreboardData> board_; // Estructura con los datos del marcador
|
||||
std::shared_ptr<Scoreboard> scoreboard_; // Objeto encargado de gestionar el marcador
|
||||
std::shared_ptr<RoomTracker> room_tracker_; // Lleva el control de las habitaciones visitadas
|
||||
std::shared_ptr<Room> room_; // Objeto encargado de gestionar cada habitación del juego
|
||||
std::shared_ptr<Player> player_; // Objeto con el jugador
|
||||
std::shared_ptr<Stats> stats_; // Objeto encargado de gestionar las estadísticas
|
||||
std::shared_ptr<Surface> room_name_surface_; // Textura para escribir el nombre de la habitación
|
||||
|
||||
// Variables
|
||||
GameMode mode_; // Modo del juego
|
||||
DemoData demo_; // Variables para el modo demo
|
||||
Uint32 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
|
||||
std::string current_room_; // Fichero de la habitación actual
|
||||
PlayerSpawn spawn_point_; // Lugar de la habitación donde aparece el jugador
|
||||
bool paused_ = false; // Indica si el juego se encuentra en pausa
|
||||
bool black_screen_ = false; // Indica si la pantalla está en negro. Se utiliza para la muerte del jugador
|
||||
int black_screen_counter_ = 0; // Contador para temporizar la pantalla en negro
|
||||
int total_items_; // Cantidad total de items que hay en el mapeado del juego
|
||||
SDL_FRect room_name_rect_; // Rectangulo donde pintar la textura con el nombre de la habitación
|
||||
|
||||
// Actualiza el juego, las variables, comprueba la entrada, etc.
|
||||
void update();
|
||||
|
||||
// Pinta los objetos en pantalla
|
||||
void render();
|
||||
|
||||
// Comprueba los eventos de la cola
|
||||
void checkEvents();
|
||||
|
||||
#ifdef DEBUG
|
||||
// Pone la información de debug en pantalla
|
||||
void updateDebugInfo();
|
||||
|
||||
// Pone la información de debug en pantalla
|
||||
void renderDebugInfo();
|
||||
|
||||
// Comprueba los eventos
|
||||
void checkDebugEvents(const SDL_Event& event);
|
||||
#endif
|
||||
|
||||
// Escribe el nombre de la pantalla
|
||||
void renderRoomName();
|
||||
|
||||
// Cambia de habitación
|
||||
bool changeRoom(const std::string& file);
|
||||
|
||||
// Comprueba el teclado
|
||||
void checkInput();
|
||||
|
||||
// Comprueba si el jugador esta en el borde de la pantalla y actua
|
||||
void checkPlayerIsOnBorder();
|
||||
|
||||
// Comprueba las colisiones del jugador con los enemigos
|
||||
bool checkPlayerAndEnemies();
|
||||
|
||||
// Comprueba las colisiones del jugador con los objetos
|
||||
void checkPlayerAndItems();
|
||||
|
||||
// Comprueba si el jugador esta vivo
|
||||
void checkIfPlayerIsAlive();
|
||||
|
||||
// Comprueba si ha terminado la partida
|
||||
void checkGameOver();
|
||||
|
||||
// Mata al jugador
|
||||
void killPlayer();
|
||||
|
||||
// Establece la pantalla en negro
|
||||
void setBlackScreen();
|
||||
|
||||
// Actualiza las variables relativas a la pantalla en negro
|
||||
void updateBlackScreen();
|
||||
|
||||
// Dibuja la pantalla negra
|
||||
void renderBlackScreen();
|
||||
|
||||
// Pone el color del marcador en función del color del borde de la habitación
|
||||
void setScoreBoardColor();
|
||||
|
||||
// Comprueba si ha finalizado el juego
|
||||
bool checkEndGame();
|
||||
|
||||
// Obtiene la cantidad total de items que hay en el mapeado del juego
|
||||
int getTotalItems();
|
||||
|
||||
// Pone el juego en pausa
|
||||
void togglePause();
|
||||
|
||||
// Da vidas al jugador cuando está en la Jail
|
||||
void checkRestoringJail();
|
||||
|
||||
// Inicializa el diccionario de las estadísticas
|
||||
void initStats();
|
||||
|
||||
// Pone el nombre de la habitación en la textura
|
||||
void fillRoomNameTexture();
|
||||
|
||||
// Comprueba algunos logros
|
||||
void checkSomeCheevos();
|
||||
|
||||
// Comprueba los logros de completar el juego
|
||||
void checkEndGameCheevos();
|
||||
|
||||
// Inicializa al jugador
|
||||
void initPlayer(const PlayerSpawn& spawn_point, std::shared_ptr<Room> room);
|
||||
|
||||
// Crea la textura para poner el nombre de la habitación
|
||||
void createRoomNameTexture();
|
||||
|
||||
// Hace sonar la música
|
||||
void keepMusicPlaying();
|
||||
|
||||
// DEMO MODE: Inicializa las variables para el modo demo
|
||||
void DEMO_init();
|
||||
|
||||
// DEMO MODE: Comprueba si se ha de cambiar de habitación
|
||||
void DEMO_checkRoomChange();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
explicit Game(GameMode mode);
|
||||
|
||||
// Destructor
|
||||
~Game();
|
||||
|
||||
// Bucle para el juego
|
||||
void run();
|
||||
};
|
||||
162
source2/Game/Scenes/game_over.cpp
Normal file
162
source2/Game/Scenes/game_over.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "game_over.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para min, max
|
||||
#include <string> // Para basic_string, operator+, to_string
|
||||
|
||||
#include "defines.h" // Para GAMECANVAS_CENTER_X, GAME_SPEED
|
||||
#include "external/jail_audio.h" // Para JA_PlayMusic
|
||||
#include "global_events.h" // Para check
|
||||
#include "global_inputs.h" // Para check
|
||||
#include "options.h" // Para Options, options, OptionsStats, Secti...
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_animated_sprite.h" // Para SAnimatedSprite
|
||||
#include "text.h" // Para TEXT_CENTER, TEXT_COLOR, Text
|
||||
#include "utils.h" // Para PaletteColor, stringToColor
|
||||
|
||||
// Constructor
|
||||
GameOver::GameOver()
|
||||
: player_sprite_(std::make_shared<SAnimatedSprite>(Resource::get()->getSurface("player_game_over.gif"), Resource::get()->getAnimations("player_game_over.ani"))),
|
||||
tv_sprite_(std::make_shared<SAnimatedSprite>(Resource::get()->getSurface("tv.gif"), Resource::get()->getAnimations("tv.ani"))),
|
||||
pre_counter_(0),
|
||||
counter_(0),
|
||||
ticks_(0) {
|
||||
options.section.section = Section::GAME_OVER;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
|
||||
player_sprite_->setPosX(GAMECANVAS_CENTER_X + 10);
|
||||
player_sprite_->setPosY(30);
|
||||
tv_sprite_->setPosX(GAMECANVAS_CENTER_X - tv_sprite_->getWidth() - 10);
|
||||
tv_sprite_->setPosY(30);
|
||||
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Inicializa el vector de colores
|
||||
const std::vector<std::string> COLORS = {"white", "yellow", "cyan", "green", "magenta", "red", "blue", "black"};
|
||||
for (const auto& color : COLORS) {
|
||||
colors_.push_back(stringToColor(color));
|
||||
}
|
||||
color_ = colors_.back();
|
||||
}
|
||||
|
||||
// Actualiza el objeto
|
||||
void GameOver::update() {
|
||||
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
|
||||
if (SDL_GetTicks() - ticks_ > GAME_SPEED) {
|
||||
// Actualiza el contador de ticks
|
||||
ticks_ = SDL_GetTicks();
|
||||
|
||||
// Comprueba las entradas
|
||||
checkInput();
|
||||
|
||||
// Actualiza el color usado para renderizar los textos e imagenes
|
||||
updateColor();
|
||||
|
||||
// Actualiza los contadores
|
||||
updateCounters();
|
||||
|
||||
// Actualiza los dos sprites
|
||||
player_sprite_->update();
|
||||
tv_sprite_->update();
|
||||
|
||||
// Actualiza el objeto Screen
|
||||
Screen::get()->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el final en pantalla
|
||||
void GameOver::render() {
|
||||
constexpr int Y = 32;
|
||||
|
||||
Screen::get()->start();
|
||||
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
|
||||
// Escribe el texto de GAME OVER
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, GAMECANVAS_CENTER_X, Y, "G A M E O V E R", 1, color_);
|
||||
|
||||
// Dibuja los sprites
|
||||
player_sprite_->setPosY(Y + 30);
|
||||
tv_sprite_->setPosY(Y + 30);
|
||||
renderSprites();
|
||||
|
||||
// Escribe el texto con las habitaciones y los items
|
||||
const std::string ITEMS_TEXT = std::to_string(options.stats.items / 100) + std::to_string((options.stats.items % 100) / 10) + std::to_string(options.stats.items % 10);
|
||||
const std::string ROOMS_TEXT = std::to_string(options.stats.rooms / 100) + std::to_string((options.stats.rooms % 100) / 10) + std::to_string(options.stats.rooms % 10);
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, GAMECANVAS_CENTER_X, Y + 80, "ITEMS: " + ITEMS_TEXT, 1, color_);
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, GAMECANVAS_CENTER_X, Y + 90, "ROOMS: " + ROOMS_TEXT, 1, color_);
|
||||
|
||||
// Escribe el texto con "Tu peor pesadilla"
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, GAMECANVAS_CENTER_X, Y + 110, "YOUR WORST NIGHTMARE IS", 1, color_);
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, GAMECANVAS_CENTER_X, Y + 120, options.stats.worst_nightmare, 1, color_);
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void GameOver::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
globalEvents::check(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las entradas
|
||||
void GameOver::checkInput() {
|
||||
globalInputs::check();
|
||||
}
|
||||
|
||||
// Bucle principal
|
||||
void GameOver::run() {
|
||||
while (options.section.section == Section::GAME_OVER) {
|
||||
update();
|
||||
checkEvents();
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el color usado para renderizar los textos e imagenes
|
||||
void GameOver::updateColor() {
|
||||
const int half = COUNTER_SECTION_END_ / 2;
|
||||
|
||||
if (counter_ < half) {
|
||||
const float STEP = std::min(counter_, COUNTER_FADE_LENGHT_) / (float)COUNTER_FADE_LENGHT_;
|
||||
const int INDEX = (colors_.size() - 1) - int((colors_.size() - 1) * STEP);
|
||||
color_ = colors_[INDEX];
|
||||
} else {
|
||||
const float STEP = std::min(std::max(counter_, COUNTER_INIT_FADE_) - COUNTER_INIT_FADE_, COUNTER_FADE_LENGHT_) / (float)COUNTER_FADE_LENGHT_;
|
||||
const int INDEX = (colors_.size() - 1) * STEP;
|
||||
color_ = colors_[INDEX];
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja los sprites
|
||||
void GameOver::renderSprites() {
|
||||
player_sprite_->render(1, color_);
|
||||
tv_sprite_->render(1, color_);
|
||||
}
|
||||
|
||||
// Actualiza los contadores
|
||||
void GameOver::updateCounters() {
|
||||
// Actualiza el contador
|
||||
if (pre_counter_ < 50) {
|
||||
pre_counter_++;
|
||||
} else {
|
||||
counter_++;
|
||||
}
|
||||
|
||||
// Hace sonar la música
|
||||
if (counter_ == 1) {
|
||||
JA_PlayMusic(Resource::get()->getMusic("game_over.ogg"), 0);
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la sección
|
||||
else if (counter_ == COUNTER_SECTION_END_) {
|
||||
options.section.section = Section::LOGO;
|
||||
options.section.subsection = Subsection::LOGO_TO_TITLE;
|
||||
}
|
||||
}
|
||||
57
source2/Game/Scenes/game_over.h
Normal file
57
source2/Game/Scenes/game_over.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <vector> // Para vector
|
||||
class SAnimatedSprite; // lines 7-7
|
||||
|
||||
class GameOver {
|
||||
private:
|
||||
// Constantes
|
||||
static constexpr int COUNTER_SECTION_END_ = 400; // Contador: cuando acaba la sección
|
||||
static constexpr int COUNTER_INIT_FADE_ = 310; // Contador: cuando emiepza el fade
|
||||
static constexpr int COUNTER_FADE_LENGHT_ = 20; // Contador: duración del fade
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<SAnimatedSprite> player_sprite_; // Sprite con el jugador
|
||||
std::shared_ptr<SAnimatedSprite> tv_sprite_; // Sprite con el televisor
|
||||
|
||||
// Variables
|
||||
int pre_counter_ = 0; // Contador previo
|
||||
int counter_ = 0; // Contador
|
||||
Uint32 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
|
||||
std::vector<Uint8> colors_; // Vector con los colores para el fade
|
||||
Uint8 color_; // Color usado para el texto y los sprites
|
||||
|
||||
// Actualiza el objeto
|
||||
void update();
|
||||
|
||||
// Dibuja el final en pantalla
|
||||
void render();
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void checkEvents();
|
||||
|
||||
// Comprueba las entradas
|
||||
void checkInput();
|
||||
|
||||
// Actualiza el color usado para renderizar los textos e imagenes
|
||||
void updateColor();
|
||||
|
||||
// Dibuja los sprites
|
||||
void renderSprites();
|
||||
|
||||
// Actualiza los contadores
|
||||
void updateCounters();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
GameOver();
|
||||
|
||||
// Destructor
|
||||
~GameOver() = default;
|
||||
|
||||
// Bucle principal
|
||||
void run();
|
||||
};
|
||||
199
source2/Game/Scenes/loading_screen.cpp
Normal file
199
source2/Game/Scenes/loading_screen.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "loading_screen.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdlib.h> // Para rand
|
||||
|
||||
#include "defines.h" // Para GAME_SPEED
|
||||
#include "external/jail_audio.h" // Para JA_PlayMusic, JA_SetVolume, JA_StopMusic
|
||||
#include "global_events.h" // Para check
|
||||
#include "global_inputs.h" // Para check
|
||||
#include "options.h" // Para Options, options, SectionState, Options...
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "utils.h" // Para stringToColor, PaletteColor
|
||||
|
||||
// Constructor
|
||||
LoadingScreen::LoadingScreen()
|
||||
: mono_loading_screen_surface_(Resource::get()->getSurface("loading_screen_bn.gif")),
|
||||
color_loading_screen_surface_(Resource::get()->getSurface("loading_screen_color.gif")),
|
||||
mono_loading_screen_sprite_(std::make_shared<SSprite>(mono_loading_screen_surface_, 0, 0, mono_loading_screen_surface_->getWidth(), mono_loading_screen_surface_->getHeight())),
|
||||
color_loading_screen_sprite_(std::make_shared<SSprite>(color_loading_screen_surface_, 0, 0, color_loading_screen_surface_->getWidth(), color_loading_screen_surface_->getHeight())),
|
||||
screen_surface_(std::make_shared<Surface>(options.game.width, options.game.height)) {
|
||||
// Configura la superficie donde se van a pintar los sprites
|
||||
screen_surface_->clear(static_cast<Uint8>(PaletteColor::WHITE));
|
||||
|
||||
// Inicializa variables
|
||||
options.section.section = Section::LOADING_SCREEN;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
|
||||
// Establece el orden de las lineas para imitar el direccionamiento de memoria del spectrum
|
||||
for (int i = 0; i < 192; ++i) {
|
||||
if (i < 64) { // Primer bloque de 2K
|
||||
line_index_[i] = ((i % 8) * 8) + (i / 8);
|
||||
} else if (i < 128) { // Segundo bloque de 2K
|
||||
line_index_[i] = 64 + ((i % 8) * 8) + ((i - 64) / 8);
|
||||
} else { // Tercer bloque de 2K
|
||||
line_index_[i] = 128 + ((i % 8) * 8) + ((i - 128) / 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia el color del borde
|
||||
Screen::get()->setBorderColor(stringToColor("black"));
|
||||
}
|
||||
|
||||
// Destructor
|
||||
LoadingScreen::~LoadingScreen() {
|
||||
JA_StopMusic();
|
||||
}
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void LoadingScreen::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
globalEvents::check(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las entradas
|
||||
void LoadingScreen::checkInput() {
|
||||
globalInputs::check();
|
||||
}
|
||||
|
||||
// Gestiona el contador de carga
|
||||
void LoadingScreen::updateLoad() {
|
||||
// Primera parte de la carga, la parte en blanco y negro
|
||||
if (loading_first_part_) {
|
||||
// Cada 5 pasos el load_counter_ se incrementa en uno
|
||||
constexpr int NUM_STEPS = 5;
|
||||
constexpr int STEPS = 51;
|
||||
load_counter_ = counter_ / NUM_STEPS;
|
||||
|
||||
if (load_counter_ < 192) {
|
||||
load_rect_.x = STEPS * (counter_ % NUM_STEPS);
|
||||
load_rect_.y = line_index_[load_counter_];
|
||||
mono_loading_screen_sprite_->setClip(load_rect_);
|
||||
mono_loading_screen_sprite_->setPosition(load_rect_);
|
||||
}
|
||||
// Una vez actualizadas las 192 lineas, pasa a la segunda fase de la carga
|
||||
else if (load_counter_ == 192) {
|
||||
loading_first_part_ = false;
|
||||
load_counter_ = 0;
|
||||
load_rect_ = {0, 0, 16, 8};
|
||||
color_loading_screen_sprite_->setClip(load_rect_);
|
||||
color_loading_screen_sprite_->setPosition(load_rect_);
|
||||
JA_PlayMusic(Resource::get()->getMusic("loading_sound3.ogg"));
|
||||
}
|
||||
}
|
||||
// Segunda parte de la carga, la parte de los bloques en color
|
||||
else {
|
||||
load_counter_ += 2;
|
||||
load_rect_.x = (load_counter_ * 8) % 256;
|
||||
load_rect_.y = (load_counter_ / 32) * 8;
|
||||
color_loading_screen_sprite_->setClip(load_rect_);
|
||||
color_loading_screen_sprite_->setPosition(load_rect_);
|
||||
|
||||
// Comprueba si ha terminado la intro
|
||||
if (load_counter_ >= 768) {
|
||||
options.section.section = Section::TITLE;
|
||||
options.section.subsection = Subsection::TITLE_WITH_LOADING_SCREEN;
|
||||
JA_StopMusic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gestiona el contador interno
|
||||
void LoadingScreen::updateCounter() {
|
||||
(pre_counter_ >= 50) ? counter_++ : pre_counter_++;
|
||||
|
||||
if (counter_ == 1) {
|
||||
JA_PlayMusic(Resource::get()->getMusic("loading_sound2.ogg"));
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja la pantalla de carga
|
||||
void LoadingScreen::renderLoad() {
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(screen_surface_);
|
||||
loading_first_part_ ? mono_loading_screen_sprite_->render(1, stringToColor("black")) : color_loading_screen_sprite_->render();
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
|
||||
// Dibuja el efecto de carga en el borde
|
||||
void LoadingScreen::renderBorder() {
|
||||
// Obtiene la Surface del borde
|
||||
auto border = Screen::get()->getBorderSurface();
|
||||
|
||||
// Pinta el borde de color azul
|
||||
border->clear(static_cast<Uint8>(PaletteColor::BLUE));
|
||||
|
||||
// Añade lineas amarillas
|
||||
const Uint8 COLOR = static_cast<Uint8>(PaletteColor::YELLOW);
|
||||
const int WIDTH = options.game.width + (options.video.border.width * 2);
|
||||
const int HEIGHT = options.game.height + (options.video.border.height * 2);
|
||||
bool draw_enabled = rand() % 2 == 0 ? true : false;
|
||||
|
||||
int row = 0;
|
||||
while (row < HEIGHT) {
|
||||
const int ROW_HEIGHT = (rand() % 4) + 3;
|
||||
if (draw_enabled) {
|
||||
for (int i = row; i < row + ROW_HEIGHT; ++i) {
|
||||
border->drawLine(0, i, WIDTH, i, COLOR);
|
||||
}
|
||||
}
|
||||
row += ROW_HEIGHT;
|
||||
draw_enabled = !draw_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables
|
||||
void LoadingScreen::update() {
|
||||
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
|
||||
if (SDL_GetTicks() - ticks_ > GAME_SPEED) {
|
||||
ticks_ = SDL_GetTicks();
|
||||
checkInput();
|
||||
updateCounter();
|
||||
updateLoad();
|
||||
renderLoad();
|
||||
Screen::get()->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja en pantalla
|
||||
void LoadingScreen::render() {
|
||||
if (options.video.border.enabled) {
|
||||
// Dibuja el efecto de carga en el borde
|
||||
renderBorder();
|
||||
}
|
||||
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
Screen::get()->start();
|
||||
Screen::get()->clearSurface(stringToColor("white"));
|
||||
|
||||
// Copia la surface a la surface de Screen
|
||||
screen_surface_->render(0, 0);
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Bucle para el logo del juego
|
||||
void LoadingScreen::run() {
|
||||
// Inicia el sonido de carga
|
||||
JA_SetVolume(64);
|
||||
JA_PlayMusic(Resource::get()->getMusic("loading_sound1.ogg"));
|
||||
|
||||
// Limpia la pantalla
|
||||
Screen::get()->start();
|
||||
Screen::get()->clearRenderer();
|
||||
Screen::get()->render();
|
||||
|
||||
while (options.section.section == Section::LOADING_SCREEN) {
|
||||
update();
|
||||
checkEvents();
|
||||
render();
|
||||
}
|
||||
|
||||
JA_SetVolume(128);
|
||||
}
|
||||
60
source2/Game/Scenes/loading_screen.h
Normal file
60
source2/Game/Scenes/loading_screen.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
class SSprite; // lines 7-7
|
||||
class Surface; // lines 8-8
|
||||
|
||||
class LoadingScreen {
|
||||
private:
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<Surface> mono_loading_screen_surface_; // Surface con la pantalla de carga en blanco y negro
|
||||
std::shared_ptr<Surface> color_loading_screen_surface_; // Surface con la pantalla de carga en color
|
||||
std::shared_ptr<SSprite> mono_loading_screen_sprite_; // SSprite para manejar la textura loadingScreenTexture1
|
||||
std::shared_ptr<SSprite> color_loading_screen_sprite_; // SSprite para manejar la textura loadingScreenTexture2
|
||||
std::shared_ptr<Surface> screen_surface_; // Surface para dibujar la pantalla de carga
|
||||
|
||||
// Variables
|
||||
int pre_counter_ = 0; // Contador previo para realizar una pausa inicial
|
||||
int counter_ = 0; // Contador
|
||||
Uint32 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
|
||||
int load_counter_ = 0; // Contador para controlar las cargas
|
||||
bool loading_first_part_ = true; // Para saber en que parte de la carga se encuentra
|
||||
int line_index_[192]; // El orden en el que se procesan las 192 lineas de la pantalla de carga
|
||||
SDL_FRect load_rect_ = {0, 0, 52, 1}; // Rectangulo para dibujar la pantalla de carga
|
||||
|
||||
// Actualiza las variables
|
||||
void update();
|
||||
|
||||
// Dibuja en pantalla
|
||||
void render();
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void checkEvents();
|
||||
|
||||
// Comprueba las entradas
|
||||
void checkInput();
|
||||
|
||||
// Gestiona el contador interno
|
||||
void updateCounter();
|
||||
|
||||
// Gestiona el contador de carga
|
||||
void updateLoad();
|
||||
|
||||
// Dibuja la pantalla de carga
|
||||
void renderLoad();
|
||||
|
||||
// Dibuja el efecto de carga en el borde
|
||||
void renderBorder();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
LoadingScreen();
|
||||
|
||||
// Destructor
|
||||
~LoadingScreen();
|
||||
|
||||
// Bucle principal
|
||||
void run();
|
||||
};
|
||||
225
source2/Game/Scenes/logo.cpp
Normal file
225
source2/Game/Scenes/logo.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "logo.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "defines.h" // Para GAME_SPEED
|
||||
#include "global_events.h" // Para check
|
||||
#include "global_inputs.h" // Para check
|
||||
#include "options.h" // Para Options, SectionState, options, Section
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "utils.h" // Para PaletteColor
|
||||
|
||||
// Constructor
|
||||
Logo::Logo()
|
||||
: jailgames_surface_(Resource::get()->getSurface("jailgames.gif")),
|
||||
since_1998_surface_(Resource::get()->getSurface("since_1998.gif")),
|
||||
since_1998_sprite_(std::make_shared<SSprite>(since_1998_surface_, (256 - since_1998_surface_->getWidth()) / 2, 83 + jailgames_surface_->getHeight() + 5, since_1998_surface_->getWidth(), since_1998_surface_->getHeight())) {
|
||||
since_1998_sprite_->setClip(0, 0, since_1998_surface_->getWidth(), since_1998_surface_->getHeight());
|
||||
since_1998_color_ = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
jailgames_color_ = static_cast<Uint8>(PaletteColor::BRIGHT_WHITE);
|
||||
|
||||
// Crea los sprites de cada linea
|
||||
for (int i = 0; i < jailgames_surface_->getHeight(); ++i) {
|
||||
jailgames_sprite_.push_back(std::make_shared<SSprite>(jailgames_surface_, 0, i, jailgames_surface_->getWidth(), 1));
|
||||
jailgames_sprite_.back()->setClip(0, i, jailgames_surface_->getWidth(), 1);
|
||||
jailgames_sprite_.at(i)->setX((i % 2 == 0) ? (256 + (i * 3)) : (-181 - (i * 3)));
|
||||
jailgames_sprite_.at(i)->setY(83 + i);
|
||||
}
|
||||
|
||||
// Inicializa variables
|
||||
options.section.section = Section::LOGO;
|
||||
|
||||
// Inicializa el vector de colores
|
||||
const std::vector<Uint8> COLORS = {
|
||||
static_cast<Uint8>(PaletteColor::BLACK),
|
||||
static_cast<Uint8>(PaletteColor::BLUE),
|
||||
static_cast<Uint8>(PaletteColor::RED),
|
||||
static_cast<Uint8>(PaletteColor::MAGENTA),
|
||||
static_cast<Uint8>(PaletteColor::GREEN),
|
||||
static_cast<Uint8>(PaletteColor::CYAN),
|
||||
static_cast<Uint8>(PaletteColor::YELLOW),
|
||||
static_cast<Uint8>(PaletteColor::BRIGHT_WHITE)};
|
||||
for (const auto& color : COLORS) {
|
||||
color_.push_back(color);
|
||||
}
|
||||
|
||||
// Cambia el color del borde
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
}
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void Logo::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
globalEvents::check(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las entradas
|
||||
void Logo::checkInput() {
|
||||
globalInputs::check();
|
||||
}
|
||||
|
||||
// Gestiona el logo de JAILGAME
|
||||
void Logo::updateJAILGAMES() {
|
||||
if (counter_ > 30) {
|
||||
for (int i = 1; i < (int)jailgames_sprite_.size(); ++i) {
|
||||
constexpr int SPEED = 8;
|
||||
constexpr int DEST = 37;
|
||||
if (jailgames_sprite_.at(i)->getX() != 37) {
|
||||
if (i % 2 == 0) {
|
||||
jailgames_sprite_.at(i)->incX(-SPEED);
|
||||
if (jailgames_sprite_.at(i)->getX() < DEST) {
|
||||
jailgames_sprite_.at(i)->setX(DEST);
|
||||
}
|
||||
} else {
|
||||
jailgames_sprite_.at(i)->incX(SPEED);
|
||||
if (jailgames_sprite_.at(i)->getX() > DEST) {
|
||||
jailgames_sprite_.at(i)->setX(DEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gestiona el color de las texturas
|
||||
void Logo::updateTextureColors() {
|
||||
constexpr int INI = 70;
|
||||
constexpr int INC = 4;
|
||||
|
||||
if (counter_ == INI + INC * 0) {
|
||||
since_1998_color_ = color_.at(0);
|
||||
}
|
||||
|
||||
else if (counter_ == INI + INC * 1) {
|
||||
since_1998_color_ = color_.at(1);
|
||||
}
|
||||
|
||||
else if (counter_ == INI + INC * 2) {
|
||||
since_1998_color_ = color_.at(2);
|
||||
}
|
||||
|
||||
else if (counter_ == INI + INC * 3) {
|
||||
since_1998_color_ = color_.at(3);
|
||||
}
|
||||
|
||||
else if (counter_ == INI + INC * 4) {
|
||||
since_1998_color_ = color_.at(4);
|
||||
}
|
||||
|
||||
else if (counter_ == INI + INC * 5) {
|
||||
since_1998_color_ = color_.at(5);
|
||||
}
|
||||
|
||||
else if (counter_ == INI + INC * 6) {
|
||||
since_1998_color_ = color_.at(6);
|
||||
}
|
||||
|
||||
else if (counter_ == INI + INC * 7) {
|
||||
since_1998_color_ = color_.at(7);
|
||||
}
|
||||
|
||||
else if (counter_ == INIT_FADE_ + INC * 0) {
|
||||
jailgames_color_ = color_.at(6);
|
||||
since_1998_color_ = color_.at(6);
|
||||
}
|
||||
|
||||
else if (counter_ == INIT_FADE_ + INC * 1) {
|
||||
jailgames_color_ = color_.at(5);
|
||||
since_1998_color_ = color_.at(5);
|
||||
}
|
||||
|
||||
else if (counter_ == INIT_FADE_ + INC * 2) {
|
||||
jailgames_color_ = color_.at(4);
|
||||
since_1998_color_ = color_.at(4);
|
||||
}
|
||||
|
||||
else if (counter_ == INIT_FADE_ + INC * 3) {
|
||||
jailgames_color_ = color_.at(3);
|
||||
since_1998_color_ = color_.at(3);
|
||||
}
|
||||
|
||||
else if (counter_ == INIT_FADE_ + INC * 4) {
|
||||
jailgames_color_ = color_.at(2);
|
||||
since_1998_color_ = color_.at(2);
|
||||
}
|
||||
|
||||
else if (counter_ == INIT_FADE_ + INC * 5) {
|
||||
jailgames_color_ = color_.at(1);
|
||||
since_1998_color_ = color_.at(1);
|
||||
}
|
||||
|
||||
else if (counter_ == INIT_FADE_ + INC * 6) {
|
||||
jailgames_color_ = color_.at(0);
|
||||
since_1998_color_ = color_.at(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables
|
||||
void Logo::update() {
|
||||
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
|
||||
if (SDL_GetTicks() - ticks_ > GAME_SPEED) {
|
||||
// Actualiza el contador de ticks
|
||||
ticks_ = SDL_GetTicks();
|
||||
|
||||
// Comprueba las entradas
|
||||
checkInput();
|
||||
|
||||
// Incrementa el contador
|
||||
counter_++;
|
||||
|
||||
// Gestiona el logo de JAILGAME
|
||||
updateJAILGAMES();
|
||||
|
||||
// Gestiona el color de las texturas
|
||||
updateTextureColors();
|
||||
|
||||
// Actualiza el objeto Screen
|
||||
Screen::get()->update();
|
||||
|
||||
// Comprueba si ha terminado el logo
|
||||
if (counter_ == END_LOGO_ + POST_LOGO_) {
|
||||
endSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja en pantalla
|
||||
void Logo::render() {
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
Screen::get()->start();
|
||||
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Dibuja los objetos
|
||||
for (const auto& s : jailgames_sprite_) {
|
||||
s->render(1, jailgames_color_);
|
||||
}
|
||||
since_1998_sprite_->render(1, since_1998_color_);
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Bucle para el logo del juego
|
||||
void Logo::run() {
|
||||
while (options.section.section == Section::LOGO) {
|
||||
update();
|
||||
checkEvents();
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
// Termina la sección
|
||||
void Logo::endSection() {
|
||||
if (options.section.subsection == Subsection::LOGO_TO_TITLE) {
|
||||
options.section.section = Section::TITLE;
|
||||
}
|
||||
|
||||
else if (options.section.subsection == Subsection::LOGO_TO_INTRO) {
|
||||
options.section.section = Section::LOADING_SCREEN;
|
||||
}
|
||||
}
|
||||
60
source2/Game/Scenes/logo.h
Normal file
60
source2/Game/Scenes/logo.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <vector> // Para vector
|
||||
class SSprite; // lines 7-7
|
||||
class Surface; // lines 8-8
|
||||
|
||||
class Logo {
|
||||
private:
|
||||
// Constantes
|
||||
static constexpr int INIT_FADE_ = 300; // Tiempo del contador cuando inicia el fade a negro
|
||||
static constexpr int END_LOGO_ = 400; // Tiempo del contador para terminar el logo
|
||||
static constexpr int POST_LOGO_ = 20; // Tiempo que dura el logo con el fade al maximo
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<Surface> jailgames_surface_; // Textura con los graficos "JAILGAMES"
|
||||
std::shared_ptr<Surface> since_1998_surface_; // Textura con los graficos "Since 1998"
|
||||
std::vector<std::shared_ptr<SSprite>> jailgames_sprite_; // Vector con los sprites de cada linea que forman el bitmap JAILGAMES
|
||||
std::shared_ptr<SSprite> since_1998_sprite_; // SSprite para manejar la textura2
|
||||
Uint8 jailgames_color_ = 0; // Color para el sprite de "JAILGAMES"
|
||||
Uint8 since_1998_color_ = 0; // Color para el sprite de "Since 1998"
|
||||
|
||||
// Variables
|
||||
std::vector<Uint8> color_; // Vector con los colores para el fade
|
||||
int counter_ = 0; // Contador
|
||||
Uint32 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
|
||||
|
||||
// Actualiza las variables
|
||||
void update();
|
||||
|
||||
// Dibuja en pantalla
|
||||
void render();
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void checkEvents();
|
||||
|
||||
// Comprueba las entradas
|
||||
void checkInput();
|
||||
|
||||
// Gestiona el logo de JAILGAME
|
||||
void updateJAILGAMES();
|
||||
|
||||
// Gestiona el color de las texturas
|
||||
void updateTextureColors();
|
||||
|
||||
// Termina la sección
|
||||
void endSection();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Logo();
|
||||
|
||||
// Destructor
|
||||
~Logo() = default;
|
||||
|
||||
// Bucle principal
|
||||
void run();
|
||||
};
|
||||
332
source2/Game/Scenes/title.cpp
Normal file
332
source2/Game/Scenes/title.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
#include "title.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para clamp
|
||||
|
||||
#include "cheevos.h" // Para Cheevos, Achievement
|
||||
#include "defines.h" // Para PLAY_AREA_CENTER_X, GAMECANVAS_WIDTH
|
||||
#include "global_events.h" // Para check
|
||||
#include "global_inputs.h" // Para check
|
||||
#include "input.h" // Para Input, InputAction, INPUT_DO_NOT_ALLOW_REPEAT, REP...
|
||||
#include "options.h" // Para Options, options, SectionState, Section
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "text.h" // Para Text, TEXT_CENTER, TEXT_COLOR
|
||||
#include "utils.h" // Para stringToColor, PaletteColor, playMusic
|
||||
|
||||
// Constructor
|
||||
Title::Title()
|
||||
: title_logo_surface_(Resource::get()->getSurface("title_logo.gif")),
|
||||
title_logo_sprite_(std::make_shared<SSprite>(title_logo_surface_, 29, 9, title_logo_surface_->getWidth(), title_logo_surface_->getHeight())),
|
||||
loading_screen_surface_(Resource::get()->getSurface("loading_screen_color.gif")),
|
||||
loading_screen_sprite_(std::make_shared<SSprite>(loading_screen_surface_, 0, 0, loading_screen_surface_->getWidth(), loading_screen_surface_->getHeight())),
|
||||
bg_surface_(std::make_shared<Surface>(options.game.width, options.game.height)) {
|
||||
// Inicializa variables
|
||||
state_ = options.section.subsection == Subsection::TITLE_WITH_LOADING_SCREEN ? TitleState::SHOW_LOADING_SCREEN : TitleState::SHOW_MENU;
|
||||
options.section.section = Section::TITLE;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
initMarquee();
|
||||
|
||||
// Crea y rellena la textura para mostrar los logros
|
||||
createCheevosTexture();
|
||||
|
||||
// Cambia el color del borde
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Rellena la textura de fondo con todos los gráficos
|
||||
fillSurface();
|
||||
|
||||
// Inicia la musica
|
||||
playMusic("title.ogg");
|
||||
}
|
||||
|
||||
// Inicializa la marquesina
|
||||
void Title::initMarquee() {
|
||||
letters_.clear();
|
||||
long_text_ = "HEY JAILERS!! IT'S 2022 AND WE'RE STILL ROCKING LIKE IT'S 1998!!! HAVE YOU HEARD IT? JAILGAMES ARE BACK!! YEEESSS BACK!! MORE THAN 10 TITLES ON JAILDOC'S KITCHEN!! THATS A LOOOOOOT OF JAILGAMES, BUT WHICH ONE WILL STRIKE FIRST? THERE IS ALSO A NEW DEVICE TO COME THAT WILL BLOW YOUR MIND WITH JAILGAMES ON THE GO: P.A.C.O. BUT WAIT! WHAT'S THAT BEAUTY I'M SEEING RIGHT OVER THERE?? OOOH THAT TINY MINIASCII IS PURE LOVE!! I WANT TO LICK EVERY BYTE OF IT!! OH SHIT! AND DON'T FORGET TO BRING BACK THOSE OLD AND FAT MS-DOS JAILGAMES TO GITHUB TO KEEP THEM ALIVE!! WHAT WILL BE THE NEXT JAILDOC RELEASE? WHAT WILL BE THE NEXT PROJECT TO COME ALIVE?? OH BABY WE DON'T KNOW BUT HERE YOU CAN FIND THE ANSWER, YOU JUST HAVE TO COMPLETE JAILDOCTOR'S DILEMMA ... COULD YOU?";
|
||||
for (int i = 0; i < (int)long_text_.length(); ++i) {
|
||||
TitleLetter l;
|
||||
l.letter = long_text_.substr(i, 1);
|
||||
l.x = 256;
|
||||
l.enabled = false;
|
||||
letters_.push_back(l);
|
||||
}
|
||||
letters_[0].enabled = true;
|
||||
}
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void Title::checkEvents() {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
globalEvents::check(event);
|
||||
|
||||
// Solo se comprueban estas teclas si no está activo el menu de logros
|
||||
if (event.type == SDL_EVENT_KEY_DOWN) {
|
||||
if (!show_cheevos_) {
|
||||
switch (event.key.key) {
|
||||
case SDLK_1:
|
||||
options.section.section = Section::GAME;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
break;
|
||||
|
||||
case SDLK_2:
|
||||
show_cheevos_ = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba las entradas
|
||||
void Title::checkInput() {
|
||||
if (show_cheevos_) {
|
||||
if (Input::get()->checkInput(InputAction::DOWN, INPUT_ALLOW_REPEAT)) {
|
||||
moveCheevosList(1);
|
||||
} else if (Input::get()->checkInput(InputAction::UP, INPUT_ALLOW_REPEAT)) {
|
||||
moveCheevosList(0);
|
||||
} else if (Input::get()->checkInput(InputAction::ACCEPT, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
hideCheevosList();
|
||||
counter_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (Input::get()->checkInput(InputAction::ACCEPT, INPUT_DO_NOT_ALLOW_REPEAT)) {
|
||||
if (state_ == TitleState::SHOW_LOADING_SCREEN) {
|
||||
state_ = TitleState::FADE_LOADING_SCREEN;
|
||||
}
|
||||
}
|
||||
|
||||
globalInputs::check();
|
||||
}
|
||||
|
||||
// Actualiza la marquesina
|
||||
void Title::updateMarquee() {
|
||||
const auto TEXT = Resource::get()->getText("gauntlet");
|
||||
|
||||
for (int i = 0; i < (int)letters_.size(); ++i) {
|
||||
if (letters_[i].enabled) {
|
||||
letters_[i].x -= marquee_speed_;
|
||||
if (letters_[i].x < -10) {
|
||||
letters_[i].enabled = false;
|
||||
}
|
||||
} else {
|
||||
if (i > 0 && letters_[i - 1].x < 256 && letters_[i - 1].enabled) {
|
||||
letters_[i].enabled = true;
|
||||
letters_[i].x = letters_[i - 1].x + TEXT->lenght(letters_[i - 1].letter) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la marquesina y la reinicia
|
||||
if (letters_[letters_.size() - 1].x < -10) {
|
||||
// Inicializa la marquesina
|
||||
initMarquee();
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja la marquesina
|
||||
void Title::renderMarquee() {
|
||||
const auto TEXT = Resource::get()->getText("gauntlet");
|
||||
for (const auto& l : letters_) {
|
||||
if (l.enabled) {
|
||||
TEXT->writeColored(l.x, 184, l.letter, static_cast<Uint8>(PaletteColor::BRIGHT_RED));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza las variables
|
||||
void Title::update() {
|
||||
// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
|
||||
if (SDL_GetTicks() - ticks_ > GAME_SPEED) {
|
||||
// Actualiza el contador de ticks
|
||||
ticks_ = SDL_GetTicks();
|
||||
|
||||
// Comprueba las entradas
|
||||
checkInput();
|
||||
|
||||
Screen::get()->update();
|
||||
|
||||
// Incrementa el contador
|
||||
counter_++;
|
||||
|
||||
switch (state_) {
|
||||
case TitleState::SHOW_LOADING_SCREEN:
|
||||
if (counter_ == 500) {
|
||||
counter_ = 0;
|
||||
state_ = TitleState::FADE_LOADING_SCREEN;
|
||||
}
|
||||
break;
|
||||
|
||||
case TitleState::FADE_LOADING_SCREEN:
|
||||
if (counter_ % 4 == 0) {
|
||||
if (loading_screen_surface_->fadeSubPalette()) {
|
||||
counter_ = 0;
|
||||
state_ = TitleState::SHOW_MENU;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TitleState::SHOW_MENU:
|
||||
// Actualiza la marquesina
|
||||
updateMarquee();
|
||||
|
||||
// Si el contador alcanza cierto valor, termina la seccion
|
||||
if (counter_ == 2200) {
|
||||
if (!show_cheevos_) {
|
||||
options.section.section = Section::CREDITS;
|
||||
options.section.subsection = Subsection::NONE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja en pantalla
|
||||
void Title::render() {
|
||||
// Prepara para empezar a dibujar en la textura de juego
|
||||
Screen::get()->start();
|
||||
Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
switch (state_) {
|
||||
case TitleState::SHOW_MENU:
|
||||
// Dibuja la textura de fondo
|
||||
bg_surface_->render(0, 0);
|
||||
|
||||
// Dibuja la marquesina
|
||||
renderMarquee();
|
||||
|
||||
// Dibuja la información de logros
|
||||
if (show_cheevos_) {
|
||||
cheevos_sprite_->render();
|
||||
}
|
||||
break;
|
||||
|
||||
case TitleState::SHOW_LOADING_SCREEN:
|
||||
case TitleState::FADE_LOADING_SCREEN:
|
||||
loading_screen_sprite_->render();
|
||||
title_logo_sprite_->render();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Vuelca el contenido del renderizador en pantalla
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Bucle para el logo del juego
|
||||
void Title::run() {
|
||||
while (options.section.section == Section::TITLE) {
|
||||
update();
|
||||
checkEvents();
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
// Desplaza la lista de logros
|
||||
void Title::moveCheevosList(int direction) {
|
||||
// Modifica la posición de la ventana de vista
|
||||
constexpr int SPEED = 2;
|
||||
cheevos_surface_view_.y = direction == 0 ? cheevos_surface_view_.y - SPEED : cheevos_surface_view_.y + SPEED;
|
||||
|
||||
// Ajusta los limites
|
||||
const float BOTTOM = cheevos_surface_->getHeight() - cheevos_surface_view_.h;
|
||||
cheevos_surface_view_.y = std::clamp(cheevos_surface_view_.y, 0.0F, BOTTOM);
|
||||
|
||||
cheevos_sprite_->setClip(cheevos_surface_view_);
|
||||
}
|
||||
|
||||
// Rellena la textura de fondo con todos los gráficos
|
||||
void Title::fillSurface() {
|
||||
// Coloca el puntero del renderizador sobre la textura
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(bg_surface_);
|
||||
|
||||
// Rellena la textura de color
|
||||
bg_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
|
||||
// Pinta el gráfico del titulo a partir del sprite
|
||||
title_logo_sprite_->render();
|
||||
|
||||
// Escribe el texto en la textura
|
||||
auto text = Resource::get()->getText("smb2");
|
||||
const Uint8 COLOR = stringToColor("green");
|
||||
const int TEXT_SIZE = text->getCharacterSize();
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 11 * TEXT_SIZE, "1.PLAY", 1, COLOR);
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 13 * TEXT_SIZE, "2.ACHIEVEMENTS", 1, COLOR);
|
||||
text->writeDX(TEXT_CENTER | TEXT_COLOR, PLAY_AREA_CENTER_X, 15 * TEXT_SIZE, "3.REDEFINE KEYS", 1, COLOR);
|
||||
|
||||
// Devuelve el puntero del renderizador a su sitio
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
|
||||
// Crea y rellena la textura para mostrar los logros
|
||||
void Title::createCheevosTexture() {
|
||||
// Crea la textura con el listado de logros
|
||||
const auto CHEEVOS_LIST = Cheevos::get()->list();
|
||||
const auto TEXT = Resource::get()->getText("subatomic");
|
||||
constexpr int CHEEVOS_TEXTURE_WIDTH = 200;
|
||||
constexpr int CHEEVOS_TEXTURE_VIEW_HEIGHT = 110 - 8;
|
||||
constexpr int CHEEVOS_TEXTURE_POS_Y = 73;
|
||||
constexpr int CHEEVOS_PADDING = 10;
|
||||
const int CHEEVO_HEIGHT = CHEEVOS_PADDING + (TEXT->getCharacterSize() * 2) + 1;
|
||||
const int CHEEVOS_TEXTURE_HEIGHT = (CHEEVO_HEIGHT * CHEEVOS_LIST.size()) + 2 + TEXT->getCharacterSize() + 8;
|
||||
cheevos_surface_ = std::make_shared<Surface>(CHEEVOS_TEXTURE_WIDTH, CHEEVOS_TEXTURE_HEIGHT);
|
||||
|
||||
// Prepara para dibujar sobre la textura
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(cheevos_surface_);
|
||||
|
||||
// Rellena la textura con color sólido
|
||||
const Uint8 CHEEVOS_BG_COLOR = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
cheevos_surface_->clear(CHEEVOS_BG_COLOR);
|
||||
|
||||
// Escribe la lista de logros en la textura
|
||||
const std::string CHEEVOS_OWNER = "ACHIEVEMENTS";
|
||||
const std::string CHEEVOS_LIST_CAPTION = CHEEVOS_OWNER + " (" + std::to_string(Cheevos::get()->getTotalUnlockedAchievements()) + " / " + std::to_string(Cheevos::get()->size()) + ")";
|
||||
int pos = 2;
|
||||
TEXT->writeDX(TEXT_CENTER | TEXT_COLOR, cheevos_surface_->getWidth() / 2, pos, CHEEVOS_LIST_CAPTION, 1, stringToColor("bright_green"));
|
||||
pos += TEXT->getCharacterSize();
|
||||
const Uint8 CHEEVO_LOCKED_COLOR = stringToColor("white");
|
||||
const Uint8 CHEEVO_UNLOCKED_COLOR = stringToColor("bright_green");
|
||||
constexpr int LINE_X1 = (CHEEVOS_TEXTURE_WIDTH / 7) * 3;
|
||||
constexpr int LINE_X2 = LINE_X1 + ((CHEEVOS_TEXTURE_WIDTH / 7) * 1);
|
||||
|
||||
for (const auto& cheevo : CHEEVOS_LIST) {
|
||||
const Uint8 CHEEVO_COLOR = cheevo.completed ? CHEEVO_UNLOCKED_COLOR : CHEEVO_LOCKED_COLOR;
|
||||
pos += CHEEVOS_PADDING;
|
||||
constexpr int HALF = CHEEVOS_PADDING / 2;
|
||||
cheevos_surface_->drawLine(LINE_X1, pos - HALF - 1, LINE_X2, pos - HALF - 1, CHEEVO_COLOR);
|
||||
TEXT->writeDX(TEXT_CENTER | TEXT_COLOR, CHEEVOS_TEXTURE_WIDTH / 2, pos, cheevo.caption, 1, CHEEVO_COLOR);
|
||||
pos += TEXT->getCharacterSize() + 1;
|
||||
TEXT->writeDX(TEXT_CENTER | TEXT_COLOR, CHEEVOS_TEXTURE_WIDTH / 2, pos, cheevo.description, 1, CHEEVO_COLOR);
|
||||
pos += TEXT->getCharacterSize();
|
||||
}
|
||||
|
||||
// Restablece el RenderSurface
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
|
||||
// Crea el sprite para el listado de logros
|
||||
cheevos_sprite_ = std::make_shared<SSprite>(cheevos_surface_, (GAMECANVAS_WIDTH - cheevos_surface_->getWidth()) / 2, CHEEVOS_TEXTURE_POS_Y, cheevos_surface_->getWidth(), cheevos_surface_->getHeight());
|
||||
cheevos_surface_view_ = {0, 0, cheevos_surface_->getWidth(), CHEEVOS_TEXTURE_VIEW_HEIGHT};
|
||||
cheevos_sprite_->setClip(cheevos_surface_view_);
|
||||
}
|
||||
|
||||
// Oculta la lista de logros
|
||||
void Title::hideCheevosList() {
|
||||
show_cheevos_ = false;
|
||||
cheevos_surface_view_.y = 0;
|
||||
cheevos_sprite_->setClip(cheevos_surface_view_);
|
||||
}
|
||||
86
source2/Game/Scenes/title.h
Normal file
86
source2/Game/Scenes/title.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
class SSprite; // lines 9-9
|
||||
class Surface; // lines 10-10
|
||||
|
||||
class Title {
|
||||
private:
|
||||
struct TitleLetter {
|
||||
std::string letter; // Letra a escribir
|
||||
int x; // Posición en el eje x
|
||||
bool enabled; // Solo se escriben y mueven si estan habilitadas
|
||||
};
|
||||
|
||||
enum class TitleState {
|
||||
SHOW_LOADING_SCREEN,
|
||||
FADE_LOADING_SCREEN,
|
||||
SHOW_MENU
|
||||
};
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<Surface> title_logo_surface_; // Textura con los graficos
|
||||
std::shared_ptr<SSprite> title_logo_sprite_; // SSprite para manejar la surface
|
||||
std::shared_ptr<Surface> loading_screen_surface_; // Surface con los gráficos de la pantalla de carga
|
||||
std::shared_ptr<SSprite> loading_screen_sprite_; // SSprite con los gráficos de la pantalla de carga
|
||||
std::shared_ptr<Surface> bg_surface_; // Textura para dibujar el fondo de la pantalla
|
||||
std::shared_ptr<Surface> cheevos_surface_; // Textura con la lista de logros
|
||||
std::shared_ptr<SSprite> cheevos_sprite_; // SSprite para manejar la surface con la lista de logros
|
||||
|
||||
// Variables
|
||||
int counter_ = 0; // Contador
|
||||
std::string long_text_; // Texto que aparece en la parte inferior del titulo
|
||||
Uint32 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
|
||||
std::vector<TitleLetter> letters_; // Vector con las letras de la marquesina
|
||||
int marquee_speed_ = 2; // Velocidad de desplazamiento de la marquesina
|
||||
bool show_cheevos_ = false; // Indica si se muestra por pantalla el listado de logros
|
||||
SDL_FRect cheevos_surface_view_; // Zona visible de la surface con el listado de logros
|
||||
TitleState state_; // Estado en el que se encuentra el bucle principal
|
||||
|
||||
// Actualiza las variables
|
||||
void update();
|
||||
|
||||
// Dibuja en pantalla
|
||||
void render();
|
||||
|
||||
// Comprueba el manejador de eventos
|
||||
void checkEvents();
|
||||
|
||||
// Comprueba las entradas
|
||||
void checkInput();
|
||||
|
||||
// Inicializa la marquesina
|
||||
void initMarquee();
|
||||
|
||||
// Actualiza la marquesina
|
||||
void updateMarquee();
|
||||
|
||||
// Dibuja la marquesina
|
||||
void renderMarquee();
|
||||
|
||||
// Desplaza la lista de logros
|
||||
void moveCheevosList(int direction);
|
||||
|
||||
// Rellena la surface de fondo con todos los gráficos
|
||||
void fillSurface();
|
||||
|
||||
// Crea y rellena la surface para mostrar los logros
|
||||
void createCheevosTexture();
|
||||
|
||||
// Oculta la lista de logros
|
||||
void hideCheevosList();
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
Title();
|
||||
|
||||
// Destructor
|
||||
~Title() = default;
|
||||
|
||||
// Bucle principal
|
||||
void run();
|
||||
};
|
||||
278
source2/Game/UI/notifier.cpp
Normal file
278
source2/Game/UI/notifier.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "ui/notifier.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para remove_if
|
||||
#include <iterator> // Para prev
|
||||
#include <string> // Para string, basic_string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "external/jail_audio.h" // Para JA_PlaySound
|
||||
#include "options.h" // Para Options, options, NotificationPosition
|
||||
#include "resource.h" // Para Resource
|
||||
#include "screen.h" // Para Screen
|
||||
#include "sprite/surface_sprite.h" // Para SSprite
|
||||
#include "surface.h" // Para Surface
|
||||
#include "text.h" // Para Text, TEXT_CENTER, TEXT_COLOR
|
||||
#include "utils.h" // Para PaletteColor
|
||||
|
||||
// [SINGLETON]
|
||||
Notifier* Notifier::notifier_ = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
void Notifier::init(const std::string& icon_file, const std::string& text) {
|
||||
Notifier::notifier_ = new Notifier(icon_file, text);
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
void Notifier::destroy() {
|
||||
delete Notifier::notifier_;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
Notifier* Notifier::get() {
|
||||
return Notifier::notifier_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Notifier::Notifier(const std::string& icon_file, const std::string& text)
|
||||
: icon_surface_(!icon_file.empty() ? Resource::get()->getSurface(icon_file) : nullptr),
|
||||
text_(Resource::get()->getText(text)),
|
||||
bg_color_(options.notifications.color),
|
||||
stack_(false),
|
||||
has_icons_(!icon_file.empty()) {}
|
||||
|
||||
// Dibuja las notificaciones por pantalla
|
||||
void Notifier::render() {
|
||||
for (auto it = notifications_.rbegin(); it != notifications_.rend(); ++it) {
|
||||
it->sprite->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el estado de las notificaiones
|
||||
void Notifier::update() {
|
||||
for (auto& notification : notifications_) {
|
||||
// Si la notificación anterior está "saliendo", no hagas nada
|
||||
if (!notifications_.empty() && ¬ification != ¬ifications_.front()) {
|
||||
const auto& PREVIOUS_NOTIFICATION = *(std::prev(¬ification));
|
||||
if (PREVIOUS_NOTIFICATION.state == NotificationStatus::RISING) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (notification.state) {
|
||||
case NotificationStatus::RISING: {
|
||||
const int DIRECTION = (options.notifications.getVerticalPosition() == NotificationPosition::TOP) ? 1 : -1;
|
||||
notification.rect.y += DIRECTION;
|
||||
|
||||
if (notification.rect.y == notification.y) {
|
||||
notification.state = NotificationStatus::STAY;
|
||||
notification.start_time = SDL_GetTicks();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NotificationStatus::STAY: {
|
||||
notification.elapsed_time = SDL_GetTicks() - notification.start_time;
|
||||
if (notification.elapsed_time >= notification.display_duration) {
|
||||
notification.state = NotificationStatus::VANISHING;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NotificationStatus::VANISHING: {
|
||||
const int DIRECTION = (options.notifications.getVerticalPosition() == NotificationPosition::TOP) ? -1 : 1;
|
||||
notification.rect.y += DIRECTION;
|
||||
|
||||
if (notification.rect.y == notification.y - notification.travel_dist) {
|
||||
notification.state = NotificationStatus::FINISHED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NotificationStatus::FINISHED:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
notification.sprite->setPosition(notification.rect);
|
||||
}
|
||||
|
||||
clearFinishedNotifications();
|
||||
}
|
||||
|
||||
// Elimina las notificaciones finalizadas
|
||||
void Notifier::clearFinishedNotifications() {
|
||||
notifications_.erase(
|
||||
std::remove_if(notifications_.begin(), notifications_.end(), [](const Notification& notification) {
|
||||
return notification.state == NotificationStatus::FINISHED;
|
||||
}),
|
||||
notifications_.end());
|
||||
}
|
||||
|
||||
void Notifier::show(std::vector<std::string> texts, NotificationText text_is, Uint32 display_duration, int icon, bool can_be_removed, const std::string& code) {
|
||||
// Si no hay texto, acaba
|
||||
if (texts.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Si las notificaciones no se apilan, elimina las anteriores
|
||||
if (!stack_) {
|
||||
clearNotifications();
|
||||
}
|
||||
|
||||
// Elimina las cadenas vacías
|
||||
texts.erase(std::remove_if(texts.begin(), texts.end(), [](const std::string& s) { return s.empty(); }),
|
||||
texts.end());
|
||||
|
||||
// Encuentra la cadena más larga
|
||||
std::string longest;
|
||||
for (const auto& text : texts) {
|
||||
if (text.length() > longest.length()) {
|
||||
longest = text;
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa variables
|
||||
const int text_size = 6;
|
||||
const auto PADDING_IN_H = text_size;
|
||||
const auto PADDING_IN_V = text_size / 2;
|
||||
const int ICON_SPACE = icon >= 0 ? ICON_SIZE_ + PADDING_IN_H : 0;
|
||||
text_is = ICON_SPACE > 0 ? NotificationText::LEFT : text_is;
|
||||
const float WIDTH = options.game.width - (PADDING_OUT_ * 2);
|
||||
const float HEIGHT = (text_size * texts.size()) + (PADDING_IN_V * 2);
|
||||
const auto SHAPE = NotificationShape::SQUARED;
|
||||
|
||||
// Posición horizontal
|
||||
float desp_h = 0;
|
||||
switch (options.notifications.getHorizontalPosition()) {
|
||||
case NotificationPosition::LEFT:
|
||||
desp_h = PADDING_OUT_;
|
||||
break;
|
||||
|
||||
case NotificationPosition::CENTER:
|
||||
desp_h = ((options.game.width / 2) - (WIDTH / 2));
|
||||
break;
|
||||
|
||||
case NotificationPosition::RIGHT:
|
||||
desp_h = options.game.width - WIDTH - PADDING_OUT_;
|
||||
break;
|
||||
|
||||
default:
|
||||
desp_h = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Posición vertical
|
||||
const int DESP_V = (options.notifications.getVerticalPosition() == NotificationPosition::TOP) ? PADDING_OUT_ : options.game.height - HEIGHT - PADDING_OUT_;
|
||||
|
||||
// Offset
|
||||
const auto TRAVEL_DIST = HEIGHT + PADDING_OUT_;
|
||||
const int TRAVEL_MOD = (options.notifications.getVerticalPosition() == NotificationPosition::TOP) ? 1 : -1;
|
||||
const int OFFSET = !notifications_.empty() ? notifications_.back().y + TRAVEL_MOD * notifications_.back().travel_dist : DESP_V;
|
||||
|
||||
// Crea la notificacion
|
||||
Notification n;
|
||||
|
||||
// Inicializa variables
|
||||
n.code = code;
|
||||
n.can_be_removed = can_be_removed;
|
||||
n.y = OFFSET;
|
||||
n.travel_dist = TRAVEL_DIST;
|
||||
n.texts = texts;
|
||||
n.shape = SHAPE;
|
||||
n.display_duration = display_duration;
|
||||
const float Y_POS = OFFSET + ((options.notifications.getVerticalPosition() == NotificationPosition::TOP) ? -TRAVEL_DIST : TRAVEL_DIST);
|
||||
n.rect = {desp_h, Y_POS, WIDTH, HEIGHT};
|
||||
|
||||
// Crea la textura
|
||||
n.surface = std::make_shared<Surface>(WIDTH, HEIGHT);
|
||||
|
||||
// Prepara para dibujar en la textura
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(n.surface);
|
||||
|
||||
// Dibuja el fondo de la notificación
|
||||
SDL_FRect rect;
|
||||
if (SHAPE == NotificationShape::ROUNDED) {
|
||||
rect = {4, 0, WIDTH - (4 * 2), HEIGHT};
|
||||
n.surface->fillRect(&rect, bg_color_);
|
||||
|
||||
rect = {4 / 2, 1, WIDTH - 4, HEIGHT - 2};
|
||||
n.surface->fillRect(&rect, bg_color_);
|
||||
|
||||
rect = {1, 4 / 2, WIDTH - 2, HEIGHT - 4};
|
||||
n.surface->fillRect(&rect, bg_color_);
|
||||
|
||||
rect = {0, 4, WIDTH, HEIGHT - (4 * 2)};
|
||||
n.surface->fillRect(&rect, bg_color_);
|
||||
}
|
||||
|
||||
else if (SHAPE == NotificationShape::SQUARED) {
|
||||
n.surface->clear(bg_color_);
|
||||
SDL_FRect squared_rect = {0, 0, n.surface->getWidth(), n.surface->getHeight()};
|
||||
n.surface->drawRectBorder(&squared_rect, static_cast<Uint8>(PaletteColor::CYAN));
|
||||
}
|
||||
|
||||
// Dibuja el icono de la notificación
|
||||
if (has_icons_ && icon >= 0 && texts.size() >= 2) {
|
||||
auto sp = std::make_unique<SSprite>(icon_surface_, (SDL_FRect){0, 0, ICON_SIZE_, ICON_SIZE_});
|
||||
sp->setPosition({PADDING_IN_H, PADDING_IN_V, ICON_SIZE_, ICON_SIZE_});
|
||||
sp->setClip((SDL_FRect){ICON_SIZE_ * (icon % 10), ICON_SIZE_ * (icon / 10), ICON_SIZE_, ICON_SIZE_});
|
||||
sp->render();
|
||||
}
|
||||
|
||||
// Escribe el texto de la notificación
|
||||
const Uint8 COLOR = static_cast<Uint8>(PaletteColor::WHITE);
|
||||
int iterator = 0;
|
||||
for (const auto& text : texts) {
|
||||
switch (text_is) {
|
||||
case NotificationText::LEFT:
|
||||
text_->writeColored(PADDING_IN_H + ICON_SPACE, PADDING_IN_V + iterator * (text_size + 1), text, COLOR);
|
||||
break;
|
||||
case NotificationText::CENTER:
|
||||
text_->writeDX(TEXT_CENTER | TEXT_COLOR, WIDTH / 2, PADDING_IN_V + iterator * (text_size + 1), text, 1, COLOR);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
++iterator;
|
||||
}
|
||||
|
||||
// Deja de dibujar en la textura
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
|
||||
// Crea el sprite de la notificación
|
||||
n.sprite = std::make_shared<SSprite>(n.surface, n.rect);
|
||||
|
||||
// Añade la notificación a la lista
|
||||
notifications_.emplace_back(n);
|
||||
|
||||
// Reproduce el sonido de la notificación
|
||||
JA_PlaySound(Resource::get()->getSound("notify.wav"));
|
||||
}
|
||||
|
||||
// Indica si hay notificaciones activas
|
||||
bool Notifier::isActive() { return !notifications_.empty(); }
|
||||
|
||||
// Finaliza y elimnina todas las notificaciones activas
|
||||
void Notifier::clearNotifications() {
|
||||
for (auto& notification : notifications_) {
|
||||
if (notification.can_be_removed) {
|
||||
notification.state = NotificationStatus::FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
clearFinishedNotifications();
|
||||
}
|
||||
|
||||
// Obtiene los códigos de las notificaciones
|
||||
std::vector<std::string> Notifier::getCodes() {
|
||||
std::vector<std::string> codes;
|
||||
for (const auto& notification : notifications_) {
|
||||
codes.emplace_back(notification.code);
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
126
source2/Game/UI/notifier.h
Normal file
126
source2/Game/UI/notifier.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string, basic_string
|
||||
#include <vector> // Para vector
|
||||
class SSprite; // lines 8-8
|
||||
class Surface; // lines 10-10
|
||||
class Text; // lines 9-9
|
||||
|
||||
// Constantes
|
||||
constexpr Uint32 DEFAULT_NOTIFICATION_DURATION = 2000;
|
||||
constexpr Uint32 CHEEVO_NOTIFICATION_DURATION = 4000;
|
||||
|
||||
// Justificado para las notificaciones
|
||||
enum class NotificationText {
|
||||
LEFT,
|
||||
CENTER,
|
||||
};
|
||||
|
||||
class Notifier {
|
||||
private:
|
||||
// Constantes
|
||||
static constexpr float ICON_SIZE_ = 16.0F;
|
||||
static constexpr float PADDING_OUT_ = 0.0F;
|
||||
|
||||
// [SINGLETON] Objeto notifier
|
||||
static Notifier* notifier_;
|
||||
|
||||
enum class NotificationStatus {
|
||||
RISING,
|
||||
STAY,
|
||||
VANISHING,
|
||||
FINISHED,
|
||||
};
|
||||
|
||||
enum class NotificationShape {
|
||||
ROUNDED,
|
||||
SQUARED,
|
||||
};
|
||||
|
||||
struct Notification {
|
||||
std::shared_ptr<Surface> surface; // Superficie asociada a la notificación
|
||||
std::shared_ptr<SSprite> sprite; // Sprite asociado para gráficos o animaciones
|
||||
std::vector<std::string> texts; // Lista de textos incluidos en la notificación
|
||||
NotificationStatus state; // Estado actual de la notificación (RISING, SHOWING, etc.)
|
||||
NotificationShape shape; // Forma de la notificación (ej. SQUARED o ROUNDED)
|
||||
SDL_FRect rect; // Dimensiones y posición de la notificación en pantalla
|
||||
int y; // Posición actual en el eje Y
|
||||
int travel_dist; // Distancia a recorrer (por ejemplo, en animaciones)
|
||||
std::string code; // Código identificador único para esta notificación
|
||||
bool can_be_removed; // Indica si la notificación puede ser eliminada
|
||||
int height; // Altura de la notificación
|
||||
Uint32 start_time; // Momento en que se creó la notificación
|
||||
Uint32 elapsed_time; // Tiempo transcurrido desde la creación
|
||||
Uint32 display_duration; // Duración total para mostrar la notificación
|
||||
|
||||
// Constructor
|
||||
explicit Notification()
|
||||
: surface(nullptr), // Inicializar superficie como nula
|
||||
sprite(nullptr), // Inicializar sprite como nulo
|
||||
texts(), // Inicializar lista de textos vacía
|
||||
state(NotificationStatus::RISING), // Estado inicial como "RISING"
|
||||
shape(NotificationShape::SQUARED), // Forma inicial como "SQUARED"
|
||||
rect{0, 0, 0, 0}, // Rectángulo inicial vacío
|
||||
y(0), // Posición Y inicializada a 0
|
||||
travel_dist(0), // Distancia inicializada a 0
|
||||
code(""), // Código identificador vacío
|
||||
can_be_removed(true), // Inicialmente se puede eliminar
|
||||
height(0), // Altura inicializada a 0
|
||||
start_time(0), // Tiempo de creación inicializado a 0
|
||||
elapsed_time(0), // Tiempo transcurrido inicializado a 0
|
||||
display_duration(0) // Duración inicializada a 0
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<Surface> icon_surface_; // Textura para los iconos de las notificaciones
|
||||
std::shared_ptr<Text> text_; // Objeto para dibujar texto
|
||||
|
||||
// Variables
|
||||
Uint8 bg_color_; // Color de fondo de las notificaciones
|
||||
std::vector<Notification> notifications_; // La lista de notificaciones activas
|
||||
bool stack_; // Indica si las notificaciones se apilan
|
||||
bool has_icons_; // Indica si el notificador tiene textura para iconos
|
||||
|
||||
// Elimina las notificaciones finalizadas
|
||||
void clearFinishedNotifications();
|
||||
|
||||
// Finaliza y elimnina todas las notificaciones activas
|
||||
void clearNotifications();
|
||||
|
||||
// [SINGLETON] Ahora el constructor y el destructor son privados, para no poder crear objetos notifier desde fuera
|
||||
|
||||
// Constructor
|
||||
Notifier(const std::string& icon_file, const std::string& text);
|
||||
|
||||
// Destructor
|
||||
~Notifier() = default;
|
||||
|
||||
public:
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
static void init(const std::string& icon_file, const std::string& text);
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
static void destroy();
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
static Notifier* get();
|
||||
|
||||
// Dibuja las notificaciones por pantalla
|
||||
void render();
|
||||
|
||||
// Actualiza el estado de las notificaiones
|
||||
void update();
|
||||
|
||||
// Muestra una notificación de texto por pantalla
|
||||
void show(std::vector<std::string> texts, NotificationText text_is = NotificationText::LEFT, Uint32 display_duration = DEFAULT_NOTIFICATION_DURATION, int icon = -1, bool can_be_removed = true, const std::string& code = std::string());
|
||||
|
||||
// Indica si hay notificaciones activas
|
||||
bool isActive();
|
||||
|
||||
// Obtiene los códigos de las notificaciones
|
||||
std::vector<std::string> getCodes();
|
||||
};
|
||||
45
source2/Utils/defines.h
Normal file
45
source2/Utils/defines.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
// Textos
|
||||
constexpr const char* WINDOW_CAPTION = "JailDoctor's Dilemma";
|
||||
constexpr const char* TEXT_COPYRIGHT = "@2022 JailDesigner";
|
||||
constexpr const char* VERSION = "1.10";
|
||||
|
||||
// Velocidad del juego
|
||||
constexpr Uint32 GAME_SPEED = 15;
|
||||
|
||||
// Tamaño de bloque
|
||||
constexpr int BLOCK = 8;
|
||||
constexpr int HALF_BLOCK = 4;
|
||||
|
||||
// Tamaño de la pantalla virtual
|
||||
constexpr int GAMECANVAS_WIDTH = 256;
|
||||
constexpr int GAMECANVAS_HEIGHT = 192;
|
||||
|
||||
// Zona de juego
|
||||
constexpr int PLAY_AREA_TOP = (0 * BLOCK);
|
||||
constexpr int PLAY_AREA_BOTTOM = (16 * BLOCK);
|
||||
constexpr int PLAY_AREA_LEFT = (0 * BLOCK);
|
||||
constexpr int PLAY_AREA_RIGHT = (32 * BLOCK);
|
||||
constexpr int PLAY_AREA_WIDTH = PLAY_AREA_RIGHT - PLAY_AREA_LEFT;
|
||||
constexpr int PLAY_AREA_HEIGHT = PLAY_AREA_BOTTOM - PLAY_AREA_TOP;
|
||||
constexpr int PLAY_AREA_CENTER_X = PLAY_AREA_LEFT + (PLAY_AREA_WIDTH / 2);
|
||||
constexpr int PLAY_AREA_CENTER_FIRST_QUARTER_X = (PLAY_AREA_WIDTH / 4);
|
||||
constexpr int PLAY_AREA_CENTER_THIRD_QUARTER_X = (PLAY_AREA_WIDTH / 4) * 3;
|
||||
constexpr int PLAY_AREA_CENTER_Y = PLAY_AREA_TOP + (PLAY_AREA_HEIGHT / 2);
|
||||
constexpr int PLAY_AREA_FIRST_QUARTER_Y = PLAY_AREA_HEIGHT / 4;
|
||||
constexpr int PLAY_AREA_THIRD_QUARTER_Y = (PLAY_AREA_HEIGHT / 4) * 3;
|
||||
|
||||
// Anclajes de pantalla
|
||||
constexpr int GAMECANVAS_CENTER_X = GAMECANVAS_WIDTH / 2;
|
||||
constexpr int GAMECANVAS_FIRST_QUARTER_X = GAMECANVAS_WIDTH / 4;
|
||||
constexpr int GAMECANVAS_THIRD_QUARTER_X = (GAMECANVAS_WIDTH / 4) * 3;
|
||||
constexpr int GAMECANVAS_CENTER_Y = GAMECANVAS_HEIGHT / 2;
|
||||
constexpr int GAMECANVAS_FIRST_QUARTER_Y = GAMECANVAS_HEIGHT / 4;
|
||||
constexpr int GAMECANVAS_THIRD_QUARTER_Y = (GAMECANVAS_HEIGHT / 4) * 3;
|
||||
24
source2/Utils/global_events.cpp
Normal file
24
source2/Utils/global_events.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "global_events.h"
|
||||
#include "options.h" // Para Options, options, OptionsGame, OptionsAudio
|
||||
#include "mouse.h"
|
||||
|
||||
namespace globalEvents
|
||||
{
|
||||
// Comprueba los eventos que se pueden producir en cualquier sección del juego
|
||||
void check(const SDL_Event &event)
|
||||
{
|
||||
// Evento de salida de la aplicación
|
||||
if (event.type == SDL_EVENT_QUIT)
|
||||
{
|
||||
options.section.section = Section::QUIT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_RENDER_DEVICE_RESET || event.type == SDL_EVENT_RENDER_TARGETS_RESET)
|
||||
{
|
||||
// reLoadTextures();
|
||||
}
|
||||
|
||||
Mouse::handleEvent(event);
|
||||
}
|
||||
}
|
||||
8
source2/Utils/global_events.h
Normal file
8
source2/Utils/global_events.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace globalEvents {
|
||||
// Comprueba los eventos que se pueden producir en cualquier sección del juego
|
||||
void check(const SDL_Event& event);
|
||||
} // namespace globalEvents
|
||||
460
source2/Utils/utils.cpp
Normal file
460
source2/Utils/utils.cpp
Normal file
@@ -0,0 +1,460 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdlib.h> // Para abs
|
||||
|
||||
#include <algorithm> // Para find, transform
|
||||
#include <cctype> // Para tolower
|
||||
#include <cmath> // Para round, abs
|
||||
#include <exception> // Para exception
|
||||
#include <filesystem> // Para path
|
||||
#include <iostream> // Para basic_ostream, cout, basic_ios, ios, endl
|
||||
#include <string> // Para basic_string, string, char_traits, allocator
|
||||
#include <unordered_map> // Para unordered_map, operator==, _Node_const_iter...
|
||||
#include <utility> // Para pair
|
||||
|
||||
#include "external/jail_audio.h" // Para JA_GetMusicState, JA_Music_state, JA_PlayMusic
|
||||
#include "resource.h" // Para Resource
|
||||
|
||||
// Calcula el cuadrado de la distancia entre dos puntos
|
||||
double distanceSquared(int x1, int y1, int x2, int y2) {
|
||||
const int deltaX = x2 - x1;
|
||||
const int deltaY = y2 - y1;
|
||||
return deltaX * deltaX + deltaY * deltaY;
|
||||
}
|
||||
|
||||
// Detector de colisiones entre dos circulos
|
||||
bool checkCollision(const Circle& a, const Circle& b) {
|
||||
// Calcula el radio total al cuadrado
|
||||
int totalRadiusSquared = a.r + b.r;
|
||||
totalRadiusSquared = totalRadiusSquared * totalRadiusSquared;
|
||||
|
||||
// Si la distancia entre el centro de los circulos es inferior a la suma de sus radios
|
||||
if (distanceSquared(a.x, a.y, b.x, b.y) < (totalRadiusSquared)) {
|
||||
// Los circulos han colisionado
|
||||
return true;
|
||||
}
|
||||
|
||||
// En caso contrario
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detector de colisiones entre un circulo y un rectangulo
|
||||
bool checkCollision(const Circle& a, const SDL_FRect& rect) {
|
||||
SDL_Rect b = toSDLRect(rect);
|
||||
// Closest point on collision box
|
||||
int cX, cY;
|
||||
|
||||
// Find closest x offset
|
||||
if (a.x < b.x) {
|
||||
cX = b.x;
|
||||
} else if (a.x > b.x + b.w) {
|
||||
cX = b.x + b.w;
|
||||
} else {
|
||||
cX = a.x;
|
||||
}
|
||||
|
||||
// Find closest y offset
|
||||
if (a.y < b.y) {
|
||||
cY = b.y;
|
||||
} else if (a.y > b.y + b.h) {
|
||||
cY = b.y + b.h;
|
||||
} else {
|
||||
cY = a.y;
|
||||
}
|
||||
|
||||
// If the closest point is inside the circle_t
|
||||
if (distanceSquared(a.x, a.y, cX, cY) < a.r * a.r) {
|
||||
// This box and the circle_t have collided
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the shapes have not collided
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detector de colisiones entre dos rectangulos
|
||||
bool checkCollision(const SDL_FRect& rect_a, const SDL_FRect& rect_b) {
|
||||
SDL_Rect a = toSDLRect(rect_a);
|
||||
SDL_Rect b = toSDLRect(rect_b);
|
||||
// Calcula las caras del rectangulo a
|
||||
const int leftA = a.x;
|
||||
const int rightA = a.x + a.w;
|
||||
const int topA = a.y;
|
||||
const int bottomA = a.y + a.h;
|
||||
|
||||
// Calcula las caras del rectangulo b
|
||||
const int leftB = b.x;
|
||||
const int rightB = b.x + b.w;
|
||||
const int topB = b.y;
|
||||
const int bottomB = b.y + b.h;
|
||||
|
||||
// Si cualquiera de las caras de a está fuera de b
|
||||
if (bottomA <= topB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (topA >= bottomB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rightA <= leftB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (leftA >= rightB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si ninguna de las caras está fuera de b
|
||||
return true;
|
||||
}
|
||||
|
||||
// Detector de colisiones entre un punto y un rectangulo
|
||||
bool checkCollision(const SDL_FPoint& point, const SDL_FRect& rect) {
|
||||
SDL_Rect r = toSDLRect(rect);
|
||||
SDL_Point p = toSDLPoint(point);
|
||||
|
||||
// Comprueba si el punto está a la izquierda del rectangulo
|
||||
if (p.x < r.x) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el punto está a la derecha del rectangulo
|
||||
if (p.x > r.x + r.w) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el punto está por encima del rectangulo
|
||||
if (p.y < r.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el punto está por debajo del rectangulo
|
||||
if (p.y > r.y + r.h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si no está fuera, es que está dentro
|
||||
return true;
|
||||
}
|
||||
|
||||
// Detector de colisiones entre una linea horizontal y un rectangulo
|
||||
bool checkCollision(const LineHorizontal& l, const SDL_FRect& rect) {
|
||||
SDL_Rect r = toSDLRect(rect);
|
||||
// Comprueba si la linea esta por encima del rectangulo
|
||||
if (l.y < r.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si la linea esta por debajo del rectangulo
|
||||
if (l.y >= r.y + r.h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el inicio de la linea esta a la derecha del rectangulo
|
||||
if (l.x1 >= r.x + r.w) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el final de la linea esta a la izquierda del rectangulo
|
||||
if (l.x2 < r.x) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si ha llegado hasta aquí, hay colisión
|
||||
return true;
|
||||
}
|
||||
|
||||
// Detector de colisiones entre una linea vertical y un rectangulo
|
||||
bool checkCollision(const LineVertical& l, const SDL_FRect& rect) {
|
||||
SDL_Rect r = toSDLRect(rect);
|
||||
// Comprueba si la linea esta por la izquierda del rectangulo
|
||||
if (l.x < r.x) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si la linea esta por la derecha del rectangulo
|
||||
if (l.x >= r.x + r.w) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el inicio de la linea esta debajo del rectangulo
|
||||
if (l.y1 >= r.y + r.h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el final de la linea esta encima del rectangulo
|
||||
if (l.y2 < r.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si ha llegado hasta aquí, hay colisión
|
||||
return true;
|
||||
}
|
||||
|
||||
// Detector de colisiones entre una linea horizontal y un punto
|
||||
bool checkCollision(const LineHorizontal& l, const SDL_FPoint& point) {
|
||||
SDL_Point p = toSDLPoint(point);
|
||||
|
||||
// Comprueba si el punto esta sobre la linea
|
||||
if (p.y > l.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el punto esta bajo la linea
|
||||
if (p.y < l.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el punto esta a la izquierda de la linea
|
||||
if (p.x < l.x1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si el punto esta a la derecha de la linea
|
||||
if (p.x > l.x2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si ha llegado aquí, hay colisión
|
||||
return true;
|
||||
}
|
||||
|
||||
// Detector de colisiones entre dos lineas
|
||||
SDL_Point checkCollision(const Line& l1, const Line& l2) {
|
||||
const float x1 = l1.x1;
|
||||
const float y1 = l1.y1;
|
||||
const float x2 = l1.x2;
|
||||
const float y2 = l1.y2;
|
||||
|
||||
const float x3 = l2.x1;
|
||||
const float y3 = l2.y1;
|
||||
const float x4 = l2.x2;
|
||||
const float y4 = l2.y2;
|
||||
|
||||
// calculate the direction of the lines
|
||||
float uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
|
||||
float uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
|
||||
|
||||
// if uA and uB are between 0-1, lines are colliding
|
||||
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
|
||||
// Calcula la intersección
|
||||
const float x = x1 + (uA * (x2 - x1));
|
||||
const float y = y1 + (uA * (y2 - y1));
|
||||
|
||||
return {static_cast<int>(round(x)), static_cast<int>(round(y))};
|
||||
}
|
||||
return {-1, -1};
|
||||
}
|
||||
|
||||
// Detector de colisiones entre dos lineas
|
||||
SDL_Point checkCollision(const LineDiagonal& l1, const LineVertical& l2) {
|
||||
const float x1 = l1.x1;
|
||||
const float y1 = l1.y1;
|
||||
const float x2 = l1.x2;
|
||||
const float y2 = l1.y2;
|
||||
|
||||
const float x3 = l2.x;
|
||||
const float y3 = l2.y1;
|
||||
const float x4 = l2.x;
|
||||
const float y4 = l2.y2;
|
||||
|
||||
// calculate the direction of the lines
|
||||
float uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
|
||||
float uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
|
||||
|
||||
// if uA and uB are between 0-1, lines are colliding
|
||||
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
|
||||
// Calcula la intersección
|
||||
const float x = x1 + (uA * (x2 - x1));
|
||||
const float y = y1 + (uA * (y2 - y1));
|
||||
|
||||
return {static_cast<int>(x), static_cast<int>(y)};
|
||||
}
|
||||
return {-1, -1};
|
||||
}
|
||||
|
||||
// Normaliza una linea diagonal
|
||||
void normalizeLine(LineDiagonal& l) {
|
||||
// Las lineas diagonales van de izquierda a derecha
|
||||
// x2 mayor que x1
|
||||
if (l.x2 < l.x1) {
|
||||
const int x = l.x1;
|
||||
const int y = l.y1;
|
||||
l.x1 = l.x2;
|
||||
l.y1 = l.y2;
|
||||
l.x2 = x;
|
||||
l.y2 = y;
|
||||
}
|
||||
}
|
||||
|
||||
// Detector de colisiones entre un punto y una linea diagonal
|
||||
bool checkCollision(const SDL_FPoint& point, const LineDiagonal& l) {
|
||||
SDL_Point p = toSDLPoint(point);
|
||||
|
||||
// Comprueba si el punto está en alineado con la linea
|
||||
if (abs(p.x - l.x1) != abs(p.y - l.y1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si está a la derecha de la linea
|
||||
if (p.x > l.x1 && p.x > l.x2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si está a la izquierda de la linea
|
||||
if (p.x < l.x1 && p.x < l.x2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si está por encima de la linea
|
||||
if (p.y > l.y1 && p.y > l.y2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba si está por debajo de la linea
|
||||
if (p.y < l.y1 && p.y < l.y2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// En caso contrario, el punto está en la linea
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convierte una cadena a un indice de la paleta
|
||||
Uint8 stringToColor(const std::string& str) {
|
||||
// Mapas de colores para cada paleta
|
||||
static const std::unordered_map<std::string, Uint8> paletteMap = {
|
||||
{"black", 0},
|
||||
{"bright_black", 1},
|
||||
|
||||
{"blue", 2},
|
||||
{"bright_blue", 3},
|
||||
|
||||
{"red", 4},
|
||||
{"bright_red", 5},
|
||||
|
||||
{"magenta", 6},
|
||||
{"bright_magenta", 7},
|
||||
|
||||
{"green", 8},
|
||||
{"bright_green", 9},
|
||||
|
||||
{"cyan", 10},
|
||||
{"bright_cyan", 11},
|
||||
|
||||
{"yellow", 12},
|
||||
{"bright_yellow", 13},
|
||||
|
||||
{"white", 14},
|
||||
{"bright_white", 15},
|
||||
|
||||
{"transparent", 255}};
|
||||
|
||||
// Busca el color en el mapa
|
||||
auto it = paletteMap.find(str);
|
||||
if (it != paletteMap.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
// Si no se encuentra el color, devolvemos negro por defecto
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Convierte una cadena a un entero de forma segura
|
||||
int safeStoi(const std::string& value, int defaultValue) {
|
||||
try {
|
||||
return std::stoi(value);
|
||||
} catch (const std::exception&) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Convierte una cadena a un booleano
|
||||
bool stringToBool(const std::string& str) {
|
||||
std::string lowerStr = str;
|
||||
std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::tolower);
|
||||
return (lowerStr == "true" || lowerStr == "1" || lowerStr == "yes" || lowerStr == "on");
|
||||
}
|
||||
|
||||
// Convierte un booleano a una cadena
|
||||
std::string boolToString(bool value) {
|
||||
return value ? "1" : "0";
|
||||
}
|
||||
|
||||
// Compara dos colores
|
||||
bool colorAreEqual(Color color1, Color color2) {
|
||||
const bool r = color1.r == color2.r;
|
||||
const bool g = color1.g == color2.g;
|
||||
const bool b = color1.b == color2.b;
|
||||
|
||||
return (r && g && b);
|
||||
}
|
||||
|
||||
// Función para convertir un string a minúsculas
|
||||
std::string toLower(const std::string& str) {
|
||||
std::string lower_str = str;
|
||||
std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), ::tolower);
|
||||
return lower_str;
|
||||
}
|
||||
|
||||
// Función para convertir un string a mayúsculas
|
||||
std::string toUpper(const std::string& str) {
|
||||
std::string upper_str = str;
|
||||
std::transform(upper_str.begin(), upper_str.end(), upper_str.begin(), ::toupper);
|
||||
return upper_str;
|
||||
}
|
||||
|
||||
// Obtiene el nombre de un fichero a partir de una ruta completa
|
||||
std::string getFileName(const std::string& path) {
|
||||
return std::filesystem::path(path).filename().string();
|
||||
}
|
||||
|
||||
// Obtiene la ruta eliminando el nombre del fichero
|
||||
std::string getPath(const std::string& full_path) {
|
||||
std::filesystem::path path(full_path);
|
||||
return path.parent_path().string();
|
||||
}
|
||||
|
||||
// Imprime por pantalla una linea de texto de tamaño fijo rellena con puntos
|
||||
void printWithDots(const std::string& text1, const std::string& text2, const std::string& text3) {
|
||||
std::cout.setf(std::ios::left, std::ios::adjustfield);
|
||||
std::cout << text1;
|
||||
|
||||
std::cout.width(50 - text1.length() - text3.length());
|
||||
std::cout.fill('.');
|
||||
std::cout << text2;
|
||||
|
||||
std::cout << text3 << std::endl;
|
||||
}
|
||||
|
||||
// Comprueba si una vector contiene una cadena
|
||||
bool stringInVector(const std::vector<std::string>& vec, const std::string& str) {
|
||||
return std::find(vec.begin(), vec.end(), str) != vec.end();
|
||||
}
|
||||
|
||||
// Hace sonar la música
|
||||
void playMusic(const std::string& music_path) {
|
||||
// Si la música no está sonando
|
||||
if (JA_GetMusicState() == JA_MUSIC_INVALID || JA_GetMusicState() == JA_MUSIC_STOPPED) {
|
||||
JA_PlayMusic(Resource::get()->getMusic(music_path));
|
||||
}
|
||||
}
|
||||
|
||||
// Rellena una textura de un color
|
||||
void fillTextureWithColor(SDL_Renderer* renderer, SDL_Texture* texture, Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||
// Guardar el render target actual
|
||||
SDL_Texture* previous_target = SDL_GetRenderTarget(renderer);
|
||||
|
||||
// Establecer la textura como el render target
|
||||
SDL_SetRenderTarget(renderer, texture);
|
||||
|
||||
// Establecer el color deseado
|
||||
SDL_SetRenderDrawColor(renderer, r, g, b, a);
|
||||
|
||||
// Pintar toda el área
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// Restaurar el render target previo
|
||||
SDL_SetRenderTarget(renderer, previous_target);
|
||||
}
|
||||
171
source2/Utils/utils.h
Normal file
171
source2/Utils/utils.h
Normal file
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
enum class PaletteColor : Uint8 {
|
||||
BLACK = 0,
|
||||
BRIGHT_BLACK = 1,
|
||||
|
||||
BLUE = 2,
|
||||
BRIGHT_BLUE = 3,
|
||||
|
||||
RED = 4,
|
||||
BRIGHT_RED = 5,
|
||||
|
||||
MAGENTA = 6,
|
||||
BRIGHT_MAGENTA = 7,
|
||||
|
||||
GREEN = 8,
|
||||
BRIGHT_GREEN = 9,
|
||||
|
||||
CYAN = 10,
|
||||
BRIGHT_CYAN = 11,
|
||||
|
||||
YELLOW = 12,
|
||||
BRIGHT_YELLOW = 13,
|
||||
|
||||
WHITE = 14,
|
||||
BRIGHT_WHITE = 15,
|
||||
|
||||
TRANSPARENT = 255,
|
||||
};
|
||||
|
||||
// Estructura para definir un circulo
|
||||
struct Circle {
|
||||
int x;
|
||||
int y;
|
||||
int r;
|
||||
};
|
||||
|
||||
// Estructura para definir una linea horizontal
|
||||
struct LineHorizontal {
|
||||
int x1, x2, y;
|
||||
};
|
||||
|
||||
// Estructura para definir una linea vertical
|
||||
struct LineVertical {
|
||||
int x, y1, y2;
|
||||
};
|
||||
|
||||
// Estructura para definir una linea diagonal
|
||||
struct LineDiagonal {
|
||||
int x1, y1, x2, y2;
|
||||
};
|
||||
|
||||
// Estructura para definir una linea
|
||||
struct Line {
|
||||
int x1, y1, x2, y2;
|
||||
};
|
||||
|
||||
// Estructura para definir un color
|
||||
struct Color {
|
||||
Uint8 r;
|
||||
Uint8 g;
|
||||
Uint8 b;
|
||||
|
||||
// Constructor por defecto
|
||||
Color()
|
||||
: r(0),
|
||||
g(0),
|
||||
b(0) {}
|
||||
|
||||
// Constructor
|
||||
Color(Uint8 red, Uint8 green, Uint8 blue)
|
||||
: r(red),
|
||||
g(green),
|
||||
b(blue) {}
|
||||
};
|
||||
|
||||
// Calcula el cuadrado de la distancia entre dos puntos
|
||||
double distanceSquared(int x1, int y1, int x2, int y2);
|
||||
|
||||
// Detector de colisiones entre dos circulos
|
||||
bool checkCollision(const Circle& a, const Circle& b);
|
||||
|
||||
// Detector de colisiones entre un circulo y un rectangulo
|
||||
bool checkCollision(const Circle& a, const SDL_FRect& b);
|
||||
|
||||
// Detector de colisiones entre un dos rectangulos
|
||||
bool checkCollision(const SDL_FRect& a, const SDL_FRect& b);
|
||||
|
||||
// Detector de colisiones entre un punto y un rectangulo
|
||||
bool checkCollision(const SDL_FPoint& p, const SDL_FRect& r);
|
||||
|
||||
// Detector de colisiones entre una linea horizontal y un rectangulo
|
||||
bool checkCollision(const LineHorizontal& l, const SDL_FRect& r);
|
||||
|
||||
// Detector de colisiones entre una linea vertical y un rectangulo
|
||||
bool checkCollision(const LineVertical& l, const SDL_FRect& r);
|
||||
|
||||
// Detector de colisiones entre una linea horizontal y un punto
|
||||
bool checkCollision(const LineHorizontal& l, const SDL_FPoint& p);
|
||||
|
||||
// Detector de colisiones entre dos lineas
|
||||
SDL_Point checkCollision(const Line& l1, const Line& l2);
|
||||
|
||||
// Detector de colisiones entre dos lineas
|
||||
SDL_Point checkCollision(const LineDiagonal& l1, const LineVertical& l2);
|
||||
|
||||
// Detector de colisiones entre un punto y una linea diagonal
|
||||
bool checkCollision(const SDL_FPoint& p, const LineDiagonal& l);
|
||||
|
||||
// Normaliza una linea diagonal
|
||||
void normalizeLine(LineDiagonal& l);
|
||||
|
||||
// Devuelve un Color a partir de un string
|
||||
Uint8 stringToColor(const std::string& str);
|
||||
|
||||
// Convierte una cadena a un entero de forma segura
|
||||
int safeStoi(const std::string& value, int defaultValue = 0);
|
||||
|
||||
// Convierte una cadena a un booleano
|
||||
bool stringToBool(const std::string& str);
|
||||
|
||||
// Convierte un booleano a una cadena
|
||||
std::string boolToString(bool value);
|
||||
|
||||
// Compara dos colores
|
||||
bool colorAreEqual(Color color1, Color color2);
|
||||
|
||||
// Convierte una cadena a minusculas
|
||||
std::string toLower(const std::string& str);
|
||||
|
||||
// Convierte una cadena a mayúsculas
|
||||
std::string toUpper(const std::string& str);
|
||||
|
||||
// Obtiene el nombre de un fichero a partir de una ruta
|
||||
std::string getFileName(const std::string& path);
|
||||
|
||||
// Obtiene la ruta eliminando el nombre del fichero
|
||||
std::string getPath(const std::string& full_path);
|
||||
|
||||
// Imprime por pantalla una linea de texto de tamaño fijo rellena con puntos
|
||||
void printWithDots(const std::string& text1, const std::string& text2, const std::string& text3);
|
||||
|
||||
// Comprueba si una vector contiene una cadena
|
||||
bool stringInVector(const std::vector<std::string>& vec, const std::string& str);
|
||||
|
||||
// Hace sonar la música
|
||||
void playMusic(const std::string& music_path);
|
||||
|
||||
// Rellena una textura de un color
|
||||
void fillTextureWithColor(SDL_Renderer* renderer, SDL_Texture* texture, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
|
||||
|
||||
inline SDL_Rect toSDLRect(const SDL_FRect& frect) {
|
||||
SDL_Rect rect;
|
||||
rect.x = static_cast<int>(frect.x);
|
||||
rect.y = static_cast<int>(frect.y);
|
||||
rect.w = static_cast<int>(frect.w);
|
||||
rect.h = static_cast<int>(frect.h);
|
||||
return rect;
|
||||
}
|
||||
|
||||
inline SDL_Point toSDLPoint(const SDL_FPoint& fpoint) {
|
||||
SDL_Point point;
|
||||
point.x = static_cast<int>(fpoint.x);
|
||||
point.y = static_cast<int>(fpoint.y);
|
||||
return point;
|
||||
}
|
||||
2
source2/external/.clang-format
vendored
Normal file
2
source2/external/.clang-format
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
DisableFormat: true
|
||||
SortIncludes: Never
|
||||
4
source2/external/.clang-tidy
vendored
Normal file
4
source2/external/.clang-tidy
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# source/external/.clang-tidy
|
||||
Checks: '-*'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
482
source2/external/jail_audio.cpp
vendored
Normal file
482
source2/external/jail_audio.cpp
vendored
Normal file
@@ -0,0 +1,482 @@
|
||||
#ifndef JA_USESDLMIXER
|
||||
#include "jail_audio.h"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_AudioFormat, SDL_BindAudioStream, SDL_SetAudioStreamGain, SDL_PutAudioStreamData, SDL_DestroyAudioStream, SDL_GetAudioStreamAvailable, Uint8, SDL_CreateAudioStream, SDL_UnbindAudioStream, Uint32, SDL_CloseAudioDevice, SDL_GetTicks, SDL_Log, SDL_free, SDL_AudioSpec, SDL_AudioStream, SDL_IOFromMem, SDL_LoadWAV, SDL_LoadWAV_IO, SDL_OpenAudioDevice, SDL_clamp, SDL_malloc, SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, SDL_AudioDeviceID, SDL_memcpy
|
||||
#include <stdint.h> // Para uint32_t, uint8_t
|
||||
#include <stdio.h> // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
|
||||
#include <stdlib.h> // Para free, malloc
|
||||
#include <string.h> // Para strcpy, strlen
|
||||
|
||||
#include "stb_vorbis.h" // Para stb_vorbis_decode_memory
|
||||
|
||||
#define JA_MAX_SIMULTANEOUS_CHANNELS 20
|
||||
#define JA_MAX_GROUPS 2
|
||||
|
||||
struct JA_Sound_t
|
||||
{
|
||||
SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 };
|
||||
Uint32 length { 0 };
|
||||
Uint8 *buffer { NULL };
|
||||
};
|
||||
|
||||
struct JA_Channel_t
|
||||
{
|
||||
JA_Sound_t *sound { nullptr };
|
||||
int pos { 0 };
|
||||
int times { 0 };
|
||||
int group { 0 };
|
||||
SDL_AudioStream *stream { nullptr };
|
||||
JA_Channel_state state { JA_CHANNEL_FREE };
|
||||
};
|
||||
|
||||
struct JA_Music_t
|
||||
{
|
||||
SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 };
|
||||
Uint32 length { 0 };
|
||||
Uint8 *buffer { nullptr };
|
||||
char *filename { nullptr };
|
||||
|
||||
int pos { 0 };
|
||||
int times { 0 };
|
||||
SDL_AudioStream *stream { nullptr };
|
||||
JA_Music_state state { JA_MUSIC_INVALID };
|
||||
};
|
||||
|
||||
JA_Music_t *current_music { nullptr };
|
||||
JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
|
||||
|
||||
SDL_AudioSpec JA_audioSpec { SDL_AUDIO_S16, 2, 48000 };
|
||||
float JA_musicVolume { 1.0f };
|
||||
float JA_soundVolume[JA_MAX_GROUPS];
|
||||
bool JA_musicEnabled { true };
|
||||
bool JA_soundEnabled { true };
|
||||
SDL_AudioDeviceID sdlAudioDevice { 0 };
|
||||
//SDL_TimerID JA_timerID { 0 };
|
||||
|
||||
bool fading = false;
|
||||
int fade_start_time;
|
||||
int fade_duration;
|
||||
int fade_initial_volume;
|
||||
|
||||
|
||||
void JA_Update()
|
||||
{
|
||||
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING)
|
||||
{
|
||||
if (fading) {
|
||||
int time = SDL_GetTicks();
|
||||
if (time > (fade_start_time+fade_duration)) {
|
||||
fading = false;
|
||||
JA_StopMusic();
|
||||
return;
|
||||
} else {
|
||||
const int time_passed = time - fade_start_time;
|
||||
const float percent = (float)time_passed / (float)fade_duration;
|
||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume*(1.0 - percent));
|
||||
}
|
||||
}
|
||||
|
||||
if (current_music->times != 0)
|
||||
{
|
||||
if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length/2)) {
|
||||
SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
|
||||
}
|
||||
if (current_music->times>0) current_music->times--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) JA_StopMusic();
|
||||
}
|
||||
}
|
||||
|
||||
if (JA_soundEnabled)
|
||||
{
|
||||
for (int i=0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
|
||||
if (channels[i].state == JA_CHANNEL_PLAYING)
|
||||
{
|
||||
if (channels[i].times != 0)
|
||||
{
|
||||
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length/2)) {
|
||||
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
|
||||
if (channels[i].times>0) channels[i].times--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
|
||||
#endif
|
||||
|
||||
SDL_Log("Iniciant JailAudio...");
|
||||
JA_audioSpec = {format, num_channels, freq };
|
||||
if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
|
||||
SDL_Log( (sdlAudioDevice==0) ? "Failed to initialize SDL audio!\n" : "OK!\n");
|
||||
for (int i=0; i<JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE;
|
||||
for (int i=0; i<JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f;
|
||||
//SDL_PauseAudioDevice(sdlAudioDevice);
|
||||
//JA_timerID = SDL_AddTimer(30, JA_UpdateCallback, nullptr);
|
||||
}
|
||||
|
||||
void JA_Quit()
|
||||
{
|
||||
//if (JA_timerID) SDL_RemoveTimer(JA_timerID);
|
||||
|
||||
if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
|
||||
sdlAudioDevice = 0;
|
||||
}
|
||||
|
||||
JA_Music_t *JA_LoadMusic(Uint8* buffer, Uint32 length)
|
||||
{
|
||||
JA_Music_t *music = new JA_Music_t();
|
||||
|
||||
int chan, samplerate;
|
||||
short *output;
|
||||
music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
|
||||
|
||||
music->spec.channels = chan;
|
||||
music->spec.freq = samplerate;
|
||||
music->spec.format = SDL_AUDIO_S16;
|
||||
music->buffer = (Uint8*)SDL_malloc(music->length);
|
||||
SDL_memcpy(music->buffer, output, music->length);
|
||||
free(output);
|
||||
music->pos = 0;
|
||||
music->state = JA_MUSIC_STOPPED;
|
||||
|
||||
return music;
|
||||
}
|
||||
|
||||
JA_Music_t *JA_LoadMusic(const char* filename)
|
||||
{
|
||||
// [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid.
|
||||
FILE *f = fopen(filename, "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fsize = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
Uint8 *buffer = (Uint8*)malloc(fsize + 1);
|
||||
if (fread(buffer, fsize, 1, f)!=1) return NULL;
|
||||
fclose(f);
|
||||
|
||||
JA_Music_t *music = JA_LoadMusic(buffer, fsize);
|
||||
music->filename = (char*)malloc(strlen(filename)+1);
|
||||
strcpy(music->filename, filename);
|
||||
|
||||
free(buffer);
|
||||
|
||||
return music;
|
||||
}
|
||||
|
||||
void JA_PlayMusic(JA_Music_t *music, const int loop)
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
|
||||
JA_StopMusic();
|
||||
|
||||
current_music = music;
|
||||
current_music->pos = 0;
|
||||
current_music->state = JA_MUSIC_PLAYING;
|
||||
current_music->times = loop;
|
||||
|
||||
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||
if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) printf("[ERROR] SDL_PutAudioStreamData failed!\n");
|
||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n");
|
||||
//SDL_ResumeAudioStreamDevice(current_music->stream);
|
||||
}
|
||||
|
||||
char *JA_GetMusicFilename(JA_Music_t *music)
|
||||
{
|
||||
if (!music) music = current_music;
|
||||
return music->filename;
|
||||
}
|
||||
|
||||
void JA_PauseMusic()
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
current_music->state = JA_MUSIC_PAUSED;
|
||||
//SDL_PauseAudioStreamDevice(current_music->stream);
|
||||
SDL_UnbindAudioStream(current_music->stream);
|
||||
}
|
||||
|
||||
void JA_ResumeMusic()
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
current_music->state = JA_MUSIC_PLAYING;
|
||||
//SDL_ResumeAudioStreamDevice(current_music->stream);
|
||||
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
|
||||
}
|
||||
|
||||
void JA_StopMusic()
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
current_music->pos = 0;
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
//SDL_PauseAudioStreamDevice(current_music->stream);
|
||||
SDL_DestroyAudioStream(current_music->stream);
|
||||
current_music->stream = nullptr;
|
||||
free(current_music->filename);
|
||||
current_music->filename = nullptr;
|
||||
}
|
||||
|
||||
void JA_FadeOutMusic(const int milliseconds)
|
||||
{
|
||||
if (!JA_musicEnabled) return;
|
||||
if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return;
|
||||
|
||||
fading = true;
|
||||
fade_start_time = SDL_GetTicks();
|
||||
fade_duration = milliseconds;
|
||||
fade_initial_volume = JA_musicVolume;
|
||||
}
|
||||
|
||||
JA_Music_state JA_GetMusicState()
|
||||
{
|
||||
if (!JA_musicEnabled) return JA_MUSIC_DISABLED;
|
||||
if (!current_music) return JA_MUSIC_INVALID;
|
||||
|
||||
return current_music->state;
|
||||
}
|
||||
|
||||
void JA_DeleteMusic(JA_Music_t *music)
|
||||
{
|
||||
if (current_music == music) current_music = nullptr;
|
||||
SDL_free(music->buffer);
|
||||
if (music->stream) SDL_DestroyAudioStream(music->stream);
|
||||
delete music;
|
||||
}
|
||||
|
||||
float JA_SetMusicVolume(float volume)
|
||||
{
|
||||
JA_musicVolume = SDL_clamp( volume, 0.0f, 1.0f );
|
||||
if (current_music) SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||
return JA_musicVolume;
|
||||
}
|
||||
|
||||
void JA_SetMusicPosition(float value)
|
||||
{
|
||||
if (!current_music) return;
|
||||
current_music->pos = value * current_music->spec.freq;
|
||||
}
|
||||
|
||||
float JA_GetMusicPosition()
|
||||
{
|
||||
if (!current_music) return 0;
|
||||
return float(current_music->pos)/float(current_music->spec.freq);
|
||||
}
|
||||
|
||||
void JA_EnableMusic(const bool value)
|
||||
{
|
||||
if ( !value && current_music && (current_music->state==JA_MUSIC_PLAYING) ) JA_StopMusic();
|
||||
|
||||
JA_musicEnabled = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
JA_Sound_t *JA_NewSound(Uint8* buffer, Uint32 length)
|
||||
{
|
||||
JA_Sound_t *sound = new JA_Sound_t();
|
||||
sound->buffer = buffer;
|
||||
sound->length = length;
|
||||
return sound;
|
||||
}
|
||||
|
||||
JA_Sound_t *JA_LoadSound(uint8_t* buffer, uint32_t size)
|
||||
{
|
||||
JA_Sound_t *sound = new JA_Sound_t();
|
||||
SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size),1, &sound->spec, &sound->buffer, &sound->length);
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
JA_Sound_t *JA_LoadSound(const char* filename)
|
||||
{
|
||||
JA_Sound_t *sound = new JA_Sound_t();
|
||||
SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length);
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
int JA_PlaySound(JA_Sound_t *sound, const int loop, const int group)
|
||||
{
|
||||
if (!JA_soundEnabled) return -1;
|
||||
|
||||
int channel = 0;
|
||||
while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; }
|
||||
if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) channel = 0;
|
||||
JA_StopChannel(channel);
|
||||
|
||||
channels[channel].sound = sound;
|
||||
channels[channel].times = loop;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
|
||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
||||
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop, const int group)
|
||||
{
|
||||
if (!JA_soundEnabled) return -1;
|
||||
|
||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1;
|
||||
JA_StopChannel(channel);
|
||||
|
||||
channels[channel].sound = sound;
|
||||
channels[channel].times = loop;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
|
||||
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
|
||||
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
void JA_DeleteSound(JA_Sound_t *sound)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||
if (channels[i].sound == sound) JA_StopChannel(i);
|
||||
}
|
||||
SDL_free(sound->buffer);
|
||||
delete sound;
|
||||
}
|
||||
|
||||
void JA_PauseChannel(const int channel)
|
||||
{
|
||||
if (!JA_soundEnabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
if (channels[i].state == JA_CHANNEL_PLAYING)
|
||||
{
|
||||
channels[i].state = JA_CHANNEL_PAUSED;
|
||||
//SDL_PauseAudioStreamDevice(channels[i].stream);
|
||||
SDL_UnbindAudioStream(channels[i].stream);
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state == JA_CHANNEL_PLAYING)
|
||||
{
|
||||
channels[channel].state = JA_CHANNEL_PAUSED;
|
||||
//SDL_PauseAudioStreamDevice(channels[channel].stream);
|
||||
SDL_UnbindAudioStream(channels[channel].stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JA_ResumeChannel(const int channel)
|
||||
{
|
||||
if (!JA_soundEnabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
if (channels[i].state == JA_CHANNEL_PAUSED)
|
||||
{
|
||||
channels[i].state = JA_CHANNEL_PLAYING;
|
||||
//SDL_ResumeAudioStreamDevice(channels[i].stream);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state == JA_CHANNEL_PAUSED)
|
||||
{
|
||||
channels[channel].state = JA_CHANNEL_PLAYING;
|
||||
//SDL_ResumeAudioStreamDevice(channels[channel].stream);
|
||||
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JA_StopChannel(const int channel)
|
||||
{
|
||||
if (!JA_soundEnabled) return;
|
||||
|
||||
if (channel == -1)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
|
||||
if (channels[i].state != JA_CHANNEL_FREE) SDL_DestroyAudioStream(channels[i].stream);
|
||||
channels[i].stream = nullptr;
|
||||
channels[i].state = JA_CHANNEL_FREE;
|
||||
channels[i].pos = 0;
|
||||
channels[i].sound = NULL;
|
||||
}
|
||||
}
|
||||
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
|
||||
{
|
||||
if (channels[channel].state != JA_CHANNEL_FREE) SDL_DestroyAudioStream(channels[channel].stream);
|
||||
channels[channel].stream = nullptr;
|
||||
channels[channel].state = JA_CHANNEL_FREE;
|
||||
channels[channel].pos = 0;
|
||||
channels[channel].sound = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
JA_Channel_state JA_GetChannelState(const int channel)
|
||||
{
|
||||
if (!JA_soundEnabled) return JA_SOUND_DISABLED;
|
||||
|
||||
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID;
|
||||
|
||||
return channels[channel].state;
|
||||
}
|
||||
|
||||
float JA_SetSoundVolume(float volume, const int group)
|
||||
{
|
||||
const float v = SDL_clamp( volume, 0.0f, 1.0f );
|
||||
for (int i = 0; i < JA_MAX_GROUPS; ++i) {
|
||||
if (group==-1 || group==i) JA_soundVolume[i]=v;
|
||||
}
|
||||
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
if ( ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) &&
|
||||
((group==-1) || (channels[i].group==group)) )
|
||||
SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[i]);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
void JA_EnableSound(const bool value)
|
||||
{
|
||||
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
|
||||
{
|
||||
if (channels[i].state == JA_CHANNEL_PLAYING) JA_StopChannel(i);
|
||||
}
|
||||
JA_soundEnabled = value;
|
||||
}
|
||||
|
||||
float JA_SetVolume(float volume)
|
||||
{
|
||||
JA_SetSoundVolume(JA_SetMusicVolume(volume) / 2.0f);
|
||||
|
||||
return JA_musicVolume;
|
||||
}
|
||||
|
||||
#endif
|
||||
43
source2/external/jail_audio.h
vendored
Normal file
43
source2/external/jail_audio.h
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
enum JA_Channel_state { JA_CHANNEL_INVALID, JA_CHANNEL_FREE, JA_CHANNEL_PLAYING, JA_CHANNEL_PAUSED, JA_SOUND_DISABLED };
|
||||
enum JA_Music_state { JA_MUSIC_INVALID, JA_MUSIC_PLAYING, JA_MUSIC_PAUSED, JA_MUSIC_STOPPED, JA_MUSIC_DISABLED };
|
||||
|
||||
struct JA_Sound_t;
|
||||
struct JA_Music_t;
|
||||
|
||||
void JA_Update();
|
||||
|
||||
void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels);
|
||||
void JA_Quit();
|
||||
|
||||
JA_Music_t *JA_LoadMusic(const char* filename);
|
||||
JA_Music_t *JA_LoadMusic(Uint8* buffer, Uint32 length);
|
||||
void JA_PlayMusic(JA_Music_t *music, const int loop = -1);
|
||||
char *JA_GetMusicFilename(JA_Music_t *music = nullptr);
|
||||
void JA_PauseMusic();
|
||||
void JA_ResumeMusic();
|
||||
void JA_StopMusic();
|
||||
void JA_FadeOutMusic(const int milliseconds);
|
||||
JA_Music_state JA_GetMusicState();
|
||||
void JA_DeleteMusic(JA_Music_t *music);
|
||||
float JA_SetMusicVolume(float volume);
|
||||
void JA_SetMusicPosition(float value);
|
||||
float JA_GetMusicPosition();
|
||||
void JA_EnableMusic(const bool value);
|
||||
|
||||
JA_Sound_t *JA_NewSound(Uint8* buffer, Uint32 length);
|
||||
JA_Sound_t *JA_LoadSound(Uint8* buffer, Uint32 length);
|
||||
JA_Sound_t *JA_LoadSound(const char* filename);
|
||||
int JA_PlaySound(JA_Sound_t *sound, const int loop = 0, const int group=0);
|
||||
int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop = 0, const int group=0);
|
||||
void JA_PauseChannel(const int channel);
|
||||
void JA_ResumeChannel(const int channel);
|
||||
void JA_StopChannel(const int channel);
|
||||
JA_Channel_state JA_GetChannelState(const int channel);
|
||||
void JA_DeleteSound(JA_Sound_t *sound);
|
||||
float JA_SetSoundVolume(float volume, const int group=0);
|
||||
void JA_EnableSound(const bool value);
|
||||
|
||||
float JA_SetVolume(float volume);
|
||||
9251
source2/external/stb_image.h
vendored
Normal file
9251
source2/external/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5565
source2/external/stb_vorbis.h
vendored
Normal file
5565
source2/external/stb_vorbis.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18
source2/main.cpp
Normal file
18
source2/main.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
|
||||
Código fuente creado por JailDesigner
|
||||
Empezado en Castalla el 01/07/2022.
|
||||
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include "director.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Crea el objeto Director
|
||||
auto director = std::make_unique<Director>(argc, const_cast<const char **>(argv));
|
||||
|
||||
// Bucle principal
|
||||
return director->run();
|
||||
}
|
||||
6
source2/version.h.in
Normal file
6
source2/version.h.in
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace Version {
|
||||
constexpr const char* GIT_HASH = "@GIT_HASH@";
|
||||
constexpr const char* APP_NAME = "Coffee Crisis Arcade Edition";
|
||||
}
|
||||
Reference in New Issue
Block a user