Files
coffee_crisis_arcade_edition/source/player.cpp
2025-03-25 20:26:45 +01:00

802 lines
18 KiB
C++

#include "player.h"
#include <SDL3/SDL_render.h> // Para SDL_FLIP_HORIZONTAL, SDL_FLIP_NONE, SDL...
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <stdlib.h> // Para rand
#include <algorithm> // Para clamp, max, min
#include "animated_sprite.h" // Para AnimatedSprite
#include "input.h" // Para InputAction
#include "param.h" // Para Param, ParamGame, param
#include "scoreboard.h" // Para Scoreboard, ScoreboardMode
#include "texture.h" // Para Texture
#include "resource.h"
#include "jail_audio.h"
#include "stage.h"
#include <array>
// Constructor
Player::Player(int id, float x, int y, bool demo, SDL_FRect &play_area, std::vector<std::shared_ptr<Texture>> texture, const std::vector<std::vector<std::string>> &animations)
: player_sprite_(std::make_unique<AnimatedSprite>(texture[0], animations[0])),
power_sprite_(std::make_unique<AnimatedSprite>(texture[1], animations[1])),
enter_name_(std::make_unique<EnterName>()),
id_(id),
play_area_(play_area),
default_pos_x_(x),
default_pos_y_(y),
demo_(demo)
{
// Configura objetos
player_sprite_->getTexture()->setPalette(coffees_);
power_sprite_->getTexture()->setAlpha(224);
power_up_desp_x_ = (power_sprite_->getWidth() - player_sprite_->getWidth()) / 2;
power_sprite_->setPosY(y - (power_sprite_->getHeight() - player_sprite_->getHeight()));
// Inicializa variables
pos_x_ = default_pos_x_;
init();
}
// Iniciador
void Player::init()
{
// Inicializa variables de estado
pos_y_ = default_pos_y_;
walking_state_ = PlayerState::WALKING_STOP;
firing_state_ = PlayerState::FIRING_NONE;
playing_state_ = PlayerState::WAITING;
invulnerable_ = true;
invulnerable_counter_ = INVULNERABLE_COUNTER_;
power_up_ = false;
power_up_counter_ = POWERUP_COUNTER_;
extra_hit_ = false;
coffees_ = 0;
continue_ticks_ = 0;
continue_counter_ = 10;
enter_name_ticks_ = 0;
enter_name_counter_ = param.game.enter_name_seconds;
shiftColliders();
vel_x_ = 0;
vel_y_ = 0;
score_ = 0;
score_multiplier_ = 1.0f;
cool_down_ = 10;
enter_name_->init(last_enter_name_);
// Establece la posición del sprite
player_sprite_->clear();
shiftSprite();
// Selecciona un frame para pintar
player_sprite_->setCurrentAnimation("stand");
}
// Actua en consecuencia de la entrada recibida
void Player::setInput(InputAction input)
{
switch (playing_state_)
{
case PlayerState::PLAYING:
{
setInputPlaying(input);
break;
}
case PlayerState::ENTERING_NAME:
case PlayerState::ENTERING_NAME_GAME_COMPLETED:
{
setInputEnteringName(input);
break;
}
default:
break;
}
}
// Procesa inputs para cuando está jugando
void Player::setInputPlaying(InputAction input)
{
switch (input)
{
case InputAction::LEFT:
{
vel_x_ = -BASE_SPEED_;
setWalkingState(PlayerState::WALKING_LEFT);
break;
}
case InputAction::RIGHT:
{
vel_x_ = BASE_SPEED_;
setWalkingState(PlayerState::WALKING_RIGHT);
break;
}
case InputAction::FIRE_CENTER:
{
setFiringState(PlayerState::FIRING_UP);
break;
}
case InputAction::FIRE_LEFT:
{
setFiringState(PlayerState::FIRING_LEFT);
break;
}
case InputAction::FIRE_RIGHT:
{
setFiringState(PlayerState::FIRING_RIGHT);
break;
}
default:
{
vel_x_ = 0;
setWalkingState(PlayerState::WALKING_STOP);
break;
}
}
}
// Procesa inputs para cuando está introduciendo el nombre
void Player::setInputEnteringName(InputAction input)
{
switch (input)
{
case InputAction::LEFT:
enter_name_->decPosition();
break;
case InputAction::RIGHT:
enter_name_->incPosition();
break;
case InputAction::UP:
enter_name_->incIndex();
break;
case InputAction::DOWN:
enter_name_->decIndex();
break;
case InputAction::START:
last_enter_name_ = getRecordName();
if (last_enter_name_.empty())
{
const std::array<std::string, 8> NAMES = {"BAL1", "TABE", "DOC", "MON", "SAM1", "JORDI", "JDES", "PEPE"};
last_enter_name_ = NAMES.at(rand() % NAMES.size());
}
break;
default:
break;
}
}
// Mueve el jugador a la posición y animación que le corresponde
void Player::move()
{
switch (playing_state_)
{
case PlayerState::PLAYING:
{
// Mueve el jugador a derecha o izquierda
pos_x_ += vel_x_;
// Si el jugador abandona el area de juego por los laterales, restaura su posición
const float MIN_X = play_area_.x - 5;
const float MAX_X = play_area_.w + 5 - WIDTH_;
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
shiftSprite();
break;
}
case PlayerState::DYING:
{
// Si el cadaver abandona el area de juego por los laterales lo hace rebotar
const int X = player_sprite_->getPosX();
const int MIN_X = play_area_.x;
const int MAX_X = play_area_.x + play_area_.w - WIDTH_;
if ((X < MIN_X) || (X > MAX_X))
{
player_sprite_->setPosX(std::clamp(X, MIN_X, MAX_X));
player_sprite_->setVelX(-player_sprite_->getVelX());
playRandomBubbleSound();
}
// Si el cadaver toca el suelo cambia el estado
if (player_sprite_->getPosY() > play_area_.h - HEIGHT_)
{
if (player_sprite_->getVelY() < 2.0f)
{
// Si la velocidad de rebote es baja, termina de rebotar y cambia de estado
setPlayingState(PlayerState::DIED);
pos_x_ = player_sprite_->getPosX();
pos_y_ = default_pos_y_;
player_sprite_->clear();
shiftSprite();
playRandomBubbleSound();
}
else
{
// Decrementa las velocidades de rebote
player_sprite_->setPosY(play_area_.h - HEIGHT_);
player_sprite_->setVelY(player_sprite_->getVelY() * -0.5f);
player_sprite_->setVelX(player_sprite_->getVelX() * 0.75f);
playRandomBubbleSound();
}
}
break;
}
case PlayerState::LEAVING_SCREEN:
{
++step_counter_;
if (step_counter_ % 10 == 0)
{
JA_PlaySound(Resource::get()->getSound("walk.wav"));
}
switch (id_)
{
case 1:
setInputPlaying(InputAction::LEFT);
break;
case 2:
setInputPlaying(InputAction::RIGHT);
break;
default:
break;
}
pos_x_ += vel_x_;
const float min_x = -WIDTH_;
const float max_x = play_area_.w;
pos_x_ = std::clamp(pos_x_, min_x, max_x);
shiftSprite();
if (pos_x_ == min_x || pos_x_ == max_x)
{
setPlayingState(PlayerState::GAME_OVER);
}
break;
}
case PlayerState::ENTERING_SCREEN:
{
++step_counter_;
if (step_counter_ % 10 == 0)
{
JA_PlaySound(Resource::get()->getSound("walk.wav"));
}
switch (id_)
{
case 1:
setInputPlaying(InputAction::RIGHT);
pos_x_ += vel_x_;
if (pos_x_ > default_pos_x_)
{
pos_x_ = default_pos_x_;
setPlayingState(PlayerState::PLAYING);
setInvulnerable(false);
}
break;
case 2:
setInputPlaying(InputAction::LEFT);
pos_x_ += vel_x_;
if (pos_x_ < default_pos_x_)
{
pos_x_ = default_pos_x_;
setPlayingState(PlayerState::PLAYING);
setInvulnerable(false);
}
break;
default:
break;
}
shiftSprite();
break;
}
case PlayerState::CREDITS:
{
pos_x_ += vel_x_ / 2.0f;
if (vel_x_ > 0)
{
// setInputPlaying(InputAction::RIGHT);
if (pos_x_ > param.game.game_area.rect.w - WIDTH_)
{
pos_x_ = param.game.game_area.rect.w - WIDTH_;
vel_x_ *= -1;
}
}
else
{
// setInputPlaying(InputAction::LEFT);
if (pos_x_ < param.game.game_area.rect.x)
{
pos_x_ = param.game.game_area.rect.x;
vel_x_ *= -1;
}
}
if (pos_x_ > param.game.game_area.center_x - WIDTH_ / 2)
{
setWalkingState(PlayerState::WALKING_LEFT);
}
else
{
setWalkingState(PlayerState::WALKING_RIGHT);
}
shiftSprite();
break;
}
default:
break;
}
}
// Pinta el jugador en pantalla
void Player::render()
{
if (power_up_ && isPlaying())
{
if (power_up_counter_ > (POWERUP_COUNTER_ / 4) || power_up_counter_ % 20 > 4)
{
power_sprite_->render();
}
}
if (isRenderable())
{
player_sprite_->render();
}
}
// Establece la animación correspondiente al estado
void Player::setAnimation()
{
switch (playing_state_)
{
case PlayerState::PLAYING:
case PlayerState::ENTERING_NAME_GAME_COMPLETED:
case PlayerState::ENTERING_SCREEN:
case PlayerState::LEAVING_SCREEN:
case PlayerState::CREDITS:
{
// Crea cadenas de texto para componer el nombre de la animación
const std::string a_walking = walking_state_ == PlayerState::WALKING_STOP ? "stand" : "walk";
const std::string a_firing = firing_state_ == PlayerState::FIRING_UP ? "centershoot" : "sideshoot";
const std::string a_cooling = firing_state_ == PlayerState::COOLING_UP ? "centershoot" : "sideshoot";
const SDL_RendererFlip flip_walk = walking_state_ == PlayerState::WALKING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
const SDL_RendererFlip flip_fire = firing_state_ == PlayerState::FIRING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
const SDL_RendererFlip flip_cooling = firing_state_ == PlayerState::COOLING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
// Establece la animación a partir de las cadenas
if (firing_state_ == PlayerState::FIRING_NONE)
{
// No esta disparando
player_sprite_->setCurrentAnimation(a_walking);
player_sprite_->setFlip(flip_walk);
}
else if (isCooling())
{
// Acaba de disparar
player_sprite_->setCurrentAnimation(a_walking + "-" + a_cooling + "-cooldown");
player_sprite_->setFlip(flip_cooling);
}
else
{
// Está disparando
player_sprite_->setCurrentAnimation(a_walking + "-" + a_firing);
// Si dispara de lado, invierte el sprite segun hacia donde dispara
// Si dispara recto, invierte el sprite segun hacia donde camina
player_sprite_->setFlip(a_firing == "centershoot" ? flip_walk : flip_fire);
}
break;
}
case PlayerState::DYING:
{
player_sprite_->setCurrentAnimation("dying");
break;
}
case PlayerState::DIED:
case PlayerState::ENTERING_NAME:
case PlayerState::CONTINUE:
{
player_sprite_->setCurrentAnimation("dead");
break;
}
case PlayerState::CELEBRATING:
{
player_sprite_->setCurrentAnimation("celebration");
break;
}
default:
break;
}
// Actualiza las animaciones de los sprites
player_sprite_->update(); // Hace avanzar las animaciones y mueve el cadaver del jugador
power_sprite_->update();
}
// Actualiza el valor de la variable
void Player::updateCooldown()
{
if (cool_down_ > 0)
{
--cool_down_;
cooling_state_counter_ = 50;
}
else
{
if (cooling_state_counter_ > 0)
{
if (cooling_state_counter_ == 40)
{
switch (firing_state_)
{
case PlayerState::FIRING_LEFT:
setFiringState(PlayerState::COOLING_LEFT);
break;
case PlayerState::FIRING_RIGHT:
setFiringState(PlayerState::COOLING_RIGHT);
break;
case PlayerState::FIRING_UP:
setFiringState(PlayerState::COOLING_UP);
break;
default:
break;
}
}
--cooling_state_counter_;
}
else
{
setFiringState(PlayerState::FIRING_NONE);
cooling_state_counter_ = 0;
}
}
}
// Actualiza al jugador a su posicion, animación y controla los contadores
void Player::update()
{
move();
setAnimation();
shiftColliders();
updateCooldown();
updatePowerUp();
updateInvulnerable();
updateContinueCounter();
updateEnterNameCounter();
updateShowingName();
updateScoreboard();
}
// Incrementa la puntuación del jugador
void Player::addScore(int score)
{
if (isPlaying())
{
score_ += score;
}
}
// Actualiza el panel del marcador
void Player::updateScoreboard()
{
switch (playing_state_)
{
case PlayerState::CONTINUE:
{
Scoreboard::get()->setContinue(getScoreBoardPanel(), getContinueCounter());
break;
}
case PlayerState::ENTERING_NAME:
case PlayerState::ENTERING_NAME_GAME_COMPLETED:
{
Scoreboard::get()->setRecordName(getScoreBoardPanel(), enter_name_->getCurrentName());
Scoreboard::get()->setSelectorPos(getScoreBoardPanel(), getRecordNamePos());
break;
}
default:
break;
}
}
// Cambia el modo del marcador
void Player::setScoreboardMode(ScoreboardMode mode)
{
if (!demo_)
{
Scoreboard::get()->setMode(getScoreBoardPanel(), mode);
}
}
// Establece el estado del jugador en el juego
void Player::setPlayingState(PlayerState state)
{
playing_state_ = state;
switch (playing_state_)
{
case PlayerState::PLAYING:
{
init();
playing_state_ = PlayerState::PLAYING;
setScoreboardMode(ScoreboardMode::SCORE);
Stage::power_can_be_added = true;
break;
}
case PlayerState::CONTINUE:
{
// Inicializa el contador de continuar
continue_ticks_ = SDL_GetTicks();
continue_counter_ = 9;
setScoreboardMode(ScoreboardMode::CONTINUE);
break;
}
case PlayerState::WAITING:
{
setScoreboardMode(ScoreboardMode::WAITING);
break;
}
case PlayerState::ENTERING_NAME:
{
setScoreboardMode(ScoreboardMode::ENTER_NAME);
break;
}
case PlayerState::SHOWING_NAME:
{
showing_name_ticks_ = SDL_GetTicks();
setScoreboardMode(ScoreboardMode::SHOW_NAME);
Scoreboard::get()->setRecordName(scoreboard_panel_, last_enter_name_);
break;
}
case PlayerState::DYING:
{
// Activa la animación de morir
player_sprite_->setAccelY(0.2f);
player_sprite_->setVelY(-6.6f);
(rand() % 2 == 0) ? player_sprite_->setVelX(3.3f) : player_sprite_->setVelX(-3.3f);
break;
}
case PlayerState::DIED:
{
const auto nextPlayerStatus = IsEligibleForHighScore() ? PlayerState::ENTERING_NAME : PlayerState::CONTINUE;
demo_ ? setPlayingState(PlayerState::WAITING) : setPlayingState(nextPlayerStatus);
break;
}
case PlayerState::GAME_OVER:
{
setScoreboardMode(ScoreboardMode::GAME_OVER);
break;
}
case PlayerState::CELEBRATING:
{
game_completed_ = true;
setScoreboardMode(ScoreboardMode::SCORE);
break;
}
case PlayerState::ENTERING_NAME_GAME_COMPLETED:
{
setWalkingState(PlayerState::WALKING_STOP);
setFiringState(PlayerState::FIRING_NONE);
setScoreboardMode(ScoreboardMode::ENTER_NAME);
break;
}
case PlayerState::LEAVING_SCREEN:
{
step_counter_ = 0;
setScoreboardMode(ScoreboardMode::GAME_COMPLETED);
break;
}
case PlayerState::ENTERING_SCREEN:
{
step_counter_ = 0;
setScoreboardMode(ScoreboardMode::SCORE);
switch (id_)
{
case 1:
pos_x_ = param.game.game_area.rect.x - WIDTH_;
break;
case 2:
pos_x_ = param.game.game_area.rect.x + param.game.game_area.rect.w;
break;
default:
break;
}
break;
}
case PlayerState::CREDITS:
{
vel_x_ = (walking_state_ == PlayerState::WALKING_RIGHT) ? BASE_SPEED_ : -BASE_SPEED_;
break;
}
default:
break;
}
}
// Aumenta el valor de la variable hasta un máximo
void Player::incScoreMultiplier()
{
score_multiplier_ += 0.1f;
score_multiplier_ = std::min(score_multiplier_, 5.0f);
}
// Decrementa el valor de la variable hasta un mínimo
void Player::decScoreMultiplier()
{
score_multiplier_ -= 0.1f;
score_multiplier_ = std::max(score_multiplier_, 1.0f);
}
// Establece el valor del estado
void Player::setInvulnerable(bool value)
{
invulnerable_ = value;
invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER_ : 0;
}
// Monitoriza el estado
void Player::updateInvulnerable()
{
if (invulnerable_)
{
if (invulnerable_counter_ > 0)
{
--invulnerable_counter_;
invulnerable_counter_ % 8 > 3 ? player_sprite_->getTexture()->setPalette(coffees_) : player_sprite_->getTexture()->setPalette(3);
}
else
{
setInvulnerable(false);
player_sprite_->getTexture()->setPalette(coffees_);
}
}
}
// Establece el valor de la variable
void Player::setPowerUp()
{
power_up_ = true;
power_up_counter_ = POWERUP_COUNTER_;
}
// Actualiza el valor de la variable
void Player::updatePowerUp()
{
if (power_up_)
{
--power_up_counter_;
power_up_ = power_up_counter_ > 0;
}
}
// Concede un toque extra al jugador
void Player::giveExtraHit()
{
extra_hit_ = true;
if (coffees_ < 2)
{
coffees_++;
player_sprite_->getTexture()->setPalette(coffees_);
}
}
// Quita el toque extra al jugador
void Player::removeExtraHit()
{
if (coffees_ > 0)
{
coffees_--;
setInvulnerable(true);
player_sprite_->getTexture()->setPalette(coffees_);
}
extra_hit_ = coffees_ == 0 ? false : true;
}
// Actualiza el circulo de colisión a la posición del jugador
void Player::shiftColliders()
{
collider_.x = static_cast<int>(pos_x_ + (WIDTH_ / 2));
collider_.y = static_cast<int>(pos_y_ + (HEIGHT_ / 2));
}
// Pone las texturas del jugador
void Player::setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &texture)
{
player_sprite_->setTexture(texture[0]);
power_sprite_->setTexture(texture[1]);
}
// Actualiza el contador de continue
void Player::updateContinueCounter()
{
if (playing_state_ == PlayerState::CONTINUE)
{
constexpr int TICKS_SPEED = 1000;
if (SDL_GetTicks() - continue_ticks_ > TICKS_SPEED)
{
decContinueCounter();
}
}
}
// Actualiza el contador de entrar nombre
void Player::updateEnterNameCounter()
{
if (playing_state_ == PlayerState::ENTERING_NAME || playing_state_ == PlayerState::ENTERING_NAME_GAME_COMPLETED)
{
constexpr int TICKS_SPEED = 1000;
if (SDL_GetTicks() - enter_name_ticks_ > TICKS_SPEED)
{
decEnterNameCounter();
}
}
}
// Actualiza el estado de SHOWING_NAME
void Player::updateShowingName()
{
if (playing_state_ == PlayerState::SHOWING_NAME)
{
constexpr int TICKS_SPEED = 5000;
if (SDL_GetTicks() - enter_name_ticks_ > TICKS_SPEED)
{
game_completed_ ? setPlayingState(PlayerState::LEAVING_SCREEN) : setPlayingState(PlayerState::CONTINUE);
}
}
}
// Decrementa el contador de continuar
void Player::decContinueCounter()
{
continue_ticks_ = SDL_GetTicks();
--continue_counter_;
if (continue_counter_ < 0)
{
setPlayingState(PlayerState::GAME_OVER);
}
else
{
JA_PlaySound(Resource::get()->getSound("continue_clock.wav"));
}
}
// Decrementa el contador de entrar nombre
void Player::decEnterNameCounter()
{
enter_name_ticks_ = SDL_GetTicks();
--enter_name_counter_;
if (enter_name_counter_ < 0)
{
enter_name_counter_ = param.game.enter_name_seconds;
if (playing_state_ == PlayerState::ENTERING_NAME)
{
setPlayingState(PlayerState::CONTINUE);
}
else
{
setPlayingState(PlayerState::LEAVING_SCREEN);
}
}
}
// Obtiene la posición que se está editando del nombre del jugador para la tabla de mejores puntuaciones
int Player::getRecordNamePos() const
{
if (enter_name_)
{
return enter_name_->getPosition();
}
return 0;
}
// Recoloca los sprites
void Player::shiftSprite()
{
player_sprite_->setPosX(pos_x_);
player_sprite_->setPosY(pos_y_);
power_sprite_->setPosX(getPosX() - power_up_desp_x_);
}
// Hace sonar un ruido al azar
void Player::playRandomBubbleSound()
{
const std::vector<std::string> sounds = {"bubble1.wav", "bubble2.wav", "bubble3.wav", "bubble4.wav"};
JA_PlaySound(Resource::get()->getSound(sounds.at(rand() % sounds.size())));
}