creada carpeta source2

This commit is contained in:
2025-10-26 12:31:49 +01:00
parent 545d471654
commit 9676e5bc2f
85 changed files with 30192 additions and 0 deletions

View 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_;
}

View 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();
};

View 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);
}

View 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);
};

View 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

View 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; }
};

View 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;
}
}

View 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(); }
};

View 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;
}

View 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);
};

View 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;
}

View 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);

File diff suppressed because it is too large Load Diff

View 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_; }
};

View 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;
}

View 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);
};

View 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);
}

View 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();
};

View 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);
}

View 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);
};

View 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();
}
}

View 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();
};

View 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);
}
}

View 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();
};

View 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);
}

View 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();
};

View 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
View 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();
};

View 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;
}
}

View 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();
};

View 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);
}

View 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();
};

View 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;
}
}

View 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();
};

View 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_);
}

View 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();
};

View 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() && &notification != &notifications_.front()) {
const auto& PREVIOUS_NOTIFICATION = *(std::prev(&notification));
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
View 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();
};