#include "player.h" #include // Para SDL_FLIP_HORIZONTAL, SDL_FLIP_NONE, SDL... #include // Para SDL_GetTicks #include // Para rand #include // Para clamp, max, min #include "animated_sprite.h" // Para AnimatedSprite #include "input.h" // Para InputType #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" // Constructor Player::Player(int id, float x, int y, bool demo, SDL_Rect &play_area, std::vector> texture, const std::vector> &animations) : player_sprite_(std::make_unique(texture[0], animations[0])), power_sprite_(std::make_unique(texture[1], animations[1])), enter_name_(std::make_unique()), 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; setPlayingState(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; cooldown_ = 10; enter_name_->init(); // 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(InputType 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(InputType input) { switch (input) { case InputType::LEFT: { vel_x_ = -BASE_SPEED_; setWalkingState(PlayerState::WALKING_LEFT); break; } case InputType::RIGHT: { vel_x_ = BASE_SPEED_; setWalkingState(PlayerState::WALKING_RIGHT); break; } case InputType::FIRE_CENTER: { setFiringState(PlayerState::FIRING_UP); break; } case InputType::FIRE_LEFT: { setFiringState(PlayerState::FIRING_LEFT); break; } case InputType::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(InputType input) { switch (input) { case InputType::LEFT: enter_name_->decPosition(); break; case InputType::RIGHT: enter_name_->incPosition(); break; case InputType::UP: enter_name_->incIndex(); break; case InputType::DOWN: enter_name_->decIndex(); break; case InputType::START: 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(InputType::LEFT); break; case 2: setInputPlaying(InputType::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(InputType::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(InputType::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(InputType::RIGHT); if (pos_x_ > param.game.game_area.rect.w - WIDTH_) { pos_x_ = param.game.game_area.rect.w - WIDTH_; vel_x_ *= -1; // setInputPlaying(InputType::LEFT); } } else { // setInputPlaying(InputType::LEFT); if (pos_x_ < param.game.game_area.rect.x) { pos_x_ = param.game.game_area.rect.x; vel_x_ *= -1; // setInputPlaying(InputType::RIGHT); } } 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 (cooldown_ > 0) { cooldown_ -= power_up_ ? 2 : 1; } else { if (!isCooling()) { cooling_status_counter_ = 40; switch (firing_state_) { case PlayerState::FIRING_LEFT: firing_state_ = PlayerState::COOLING_LEFT; break; case PlayerState::FIRING_RIGHT: firing_state_ = PlayerState::COOLING_RIGHT; break; case PlayerState::FIRING_UP: firing_state_ = PlayerState::COOLING_UP; break; default: break; } } else if (cooling_status_counter_ > 0) { --cooling_status_counter_; } else { setFiringState(PlayerState::FIRING_NONE); } } } // Actualiza al jugador a su posicion, animación y controla los contadores void Player::update() { move(); setAnimation(); shiftColliders(); updateCooldown(); updatePowerUp(); updateInvulnerable(); updateContinueCounter(); updateEnterNameCounter(); 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_->getName()); 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); 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::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: { 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(pos_x_ + (WIDTH_ / 2)); collider_.y = static_cast(pos_y_ + (HEIGHT_ / 2)); } // Pone las texturas del jugador void Player::setPlayerTextures(const std::vector> &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(); } } } // 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 sounds = {"bubble1.wav", "bubble2.wav", "bubble3.wav", "bubble4.wav"}; JA_PlaySound(Resource::get()->getSound(sounds.at(rand() % sounds.size()))); }