#include "player.h" #include // Para SDL_GetTicks, SDL_FlipMode, SDL_FRect #include // Para rand #include // Para clamp, max, min #include "animated_sprite.h" // Para AnimatedSprite #include "asset.h" // Para Asset #include "audio.h" // Para Audio #include "input.h" // Para InputAction #include "manage_hiscore_table.h" // Para ManageHiScoreTable, HiScoreEntry #include "param.h" // Para Param, ParamGame, param #include "scoreboard.h" // Para ScoreboardMode, Scoreboard #include "stage.h" // Para power_can_be_added #include "texture.h" // Para Texture // Constructor Player::Player(int id, float x, int y, bool demo, SDL_FRect &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_; invulnerable_ = false; invulnerable_counter_ = 0; 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; power_up_ = false; power_up_counter_ = POWERUP_COUNTER_; extra_hit_ = false; coffees_ = 0; continue_ticks_ = 0; continue_counter_ = 10; name_entry_ticks_ = 0; name_entry_idle_counter_ = 0; name_entry_total_counter_ = 0; shiftColliders(); vel_x_ = 0; vel_y_ = 0; score_ = 0; score_multiplier_ = 1.0f; cant_fire_counter_ = 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(); break; default: break; } name_entry_idle_counter_ = 0; } // 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::ROLLING: { // Si el jugador 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()); playSound("jump.wav"); } // Si el jugador toca el suelo rebota y si tiene poca velocidad, se detiene y cambia de estado if (player_sprite_->getPosY() > play_area_.h - HEIGHT_) { if (player_sprite_->getVelY() < 2.0f) { // Si la velocidad de rebote es baja, lo detiene y cambia de estado const auto NEXT_PLAYER_STATUS = IsEligibleForHighScore() ? PlayerState::ENTERING_NAME : PlayerState::CONTINUE; demo_ ? setPlayingState(PlayerState::LYING_ON_THE_FLOOR_FOREVER) : setPlayingState(NEXT_PLAYER_STATUS); pos_x_ = player_sprite_->getPosX(); pos_y_ = default_pos_y_; player_sprite_->clear(); shiftSprite(); playSound("jump.wav"); } 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); player_sprite_->setAnimationSpeed(player_sprite_->getAnimationSpeed() * 2); playSound("jump.wav"); } } break; } case PlayerState::TITLE_ANIMATION: { // Si el jugador abandona el area de juego por los laterales lo detiene /*const int X = player_sprite_->getPosX(); const int MIN_X = play_area_.x - WIDTH_; const int MAX_X = play_area_.x + play_area_.w; if ((X < MIN_X) || (X > MAX_X)) { setPlayingState(PlayerState::TITLE_HIDDEN); } // Si el jugador toca el suelo rebota lo detiene if (player_sprite_->getPosY() > play_area_.h) { setPlayingState(PlayerState::TITLE_HIDDEN); }*/ switch (id_) { case 1: setInputPlaying(InputAction::LEFT); break; case 2: setInputPlaying(InputAction::RIGHT); break; default: break; } pos_x_ += vel_x_ * 2.0f; 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::TITLE_HIDDEN); } break; } case PlayerState::CONTINUE_TIME_OUT: { // Si el cadaver desaparece por el suelo, cambia de estado if (player_sprite_->getPosY() > play_area_.h) { setPlayingState(PlayerState::WAITING); } break; } case PlayerState::LEAVING_SCREEN: { ++step_counter_; if (step_counter_ % 10 == 0) { playSound("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) { playSound("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::TITLE_ANIMATION: case PlayerState::CREDITS: { // Crea cadenas de texto para componer el nombre de la animación const std::string WALK_ANIMATION = walking_state_ == PlayerState::WALKING_STOP ? "stand" : "walk"; const std::string FIRE_ANIMATION = firing_state_ == PlayerState::FIRING_UP ? "-fire-center" : "-fire-side"; const std::string RECOIL_ANIMATION = firing_state_ == PlayerState::RECOILING_UP ? "-recoil-center" : "-recoil-side"; const std::string COOL_ANIMATION = firing_state_ == PlayerState::COOLING_UP ? "-cool-center" : "-cool-side"; const SDL_FlipMode FLIP_WALK = walking_state_ == PlayerState::WALKING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; const SDL_FlipMode FLIP_FIRE = firing_state_ == PlayerState::FIRING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; const SDL_FlipMode FLIP_RECOIL = firing_state_ == PlayerState::RECOILING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; const SDL_FlipMode FLIP_COOL = 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(WALK_ANIMATION, false); player_sprite_->setFlip(FLIP_WALK); } else if (isRecoiling()) { // Retroceso player_sprite_->setCurrentAnimation(WALK_ANIMATION + RECOIL_ANIMATION, false); player_sprite_->setFlip(FLIP_RECOIL); } else if (isCooling()) { // Acaba de disparar player_sprite_->setCurrentAnimation(WALK_ANIMATION + COOL_ANIMATION, false); player_sprite_->setFlip(FLIP_COOL); } else { // Está disparando player_sprite_->setCurrentAnimation(WALK_ANIMATION + FIRE_ANIMATION, false); // Si dispara de lado, invierte el sprite segun hacia donde dispara // Si dispara recto, invierte el sprite segun hacia donde camina player_sprite_->setFlip(FIRE_ANIMATION == "-fire-center" ? FLIP_WALK : FLIP_FIRE); } break; } case PlayerState::ROLLING: case PlayerState::CONTINUE_TIME_OUT: { player_sprite_->setCurrentAnimation("rolling"); break; } case PlayerState::LYING_ON_THE_FLOOR_FOREVER: 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 (playing_state_ == PlayerState::PLAYING) { if (cant_fire_counter_ > 0) { cooling_state_counter_ = COOLING_DURATION_; // La mitad del tiempo que no puede disparar tiene el brazo arriba (PlayerState::FIRING) // y la otra mitad en retroceso (PlayerState::RECOILING) if (cant_fire_counter_ == recoiling_state_duration_ / 2) { switch (firing_state_) { case PlayerState::FIRING_LEFT: setFiringState(PlayerState::RECOILING_LEFT); break; case PlayerState::FIRING_RIGHT: setFiringState(PlayerState::RECOILING_RIGHT); break; case PlayerState::FIRING_UP: setFiringState(PlayerState::RECOILING_UP); break; default: break; } } --cant_fire_counter_; if (cant_fire_counter_ == 0) { recoiling_state_counter_ = recoiling_state_duration_; } } else { if (recoiling_state_counter_ > 0) { --recoiling_state_counter_; } else { if (cooling_state_counter_ > COOLING_COMPLETE_) { if (cooling_state_counter_ == COOLING_DURATION_) { switch (firing_state_) { case PlayerState::RECOILING_LEFT: setFiringState(PlayerState::COOLING_LEFT); break; case PlayerState::RECOILING_RIGHT: setFiringState(PlayerState::COOLING_RIGHT); break; case PlayerState::RECOILING_UP: setFiringState(PlayerState::COOLING_UP); break; default: break; } } --cooling_state_counter_; } if (cooling_state_counter_ == COOLING_COMPLETE_) { setFiringState(PlayerState::FIRING_NONE); cooling_state_counter_ = -1; } } } } } // Actualiza al jugador a su posicion, animación y controla los contadores void Player::update() { move(); setAnimation(); shiftColliders(); updateCooldown(); updatePowerUp(); updateInvulnerable(); updateScoreboard(); updateContinueCounter(); updateEnterNameCounter(); updateShowingName(); } // 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::RESPAWNING: { setInvulnerable(true); addCredit(); playSound("voice_thankyou.wav"); setPlayingState(PlayerState::PLAYING); } 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); playSound("continue_clock.wav"); break; } case PlayerState::WAITING: { pos_x_ = default_pos_x_; 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_); addScoreToScoreBoard(); break; } case PlayerState::ROLLING: { // Activa la animación de rodar player_sprite_->setCurrentAnimation("rolling"); player_sprite_->setAnimationSpeed(4); 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::TITLE_ANIMATION: { // Activa la animación de rodar player_sprite_->setCurrentAnimation("walk"); playSound("voice_thankyou.wav"); break; } case PlayerState::TITLE_HIDDEN: { player_sprite_->setVelX(0.0f); player_sprite_->setVelY(0.0f); break; } case PlayerState::CONTINUE_TIME_OUT: { // Activa la animación de morir player_sprite_->setAccelY(0.2f); player_sprite_->setVelY(-4.0f); player_sprite_->setVelX(0.0f); player_sprite_->setCurrentAnimation("rolling"); player_sprite_->setAnimationSpeed(5); setScoreboardMode(ScoreboardMode::GAME_OVER); playSound("voice_aw_aw_aw.wav"); playSound("jump.wav"); 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 (playing_state_ == PlayerState::PLAYING) 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 (playing_state_ == PlayerState::PLAYING) 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() - name_entry_ticks_ > TICKS_SPEED) { decNameEntryCounter(); } } } // Actualiza el estado de SHOWING_NAME void Player::updateShowingName() { if (playing_state_ == PlayerState::SHOWING_NAME) { constexpr int TICKS_SPEED = 5000; if (SDL_GetTicks() - name_entry_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::CONTINUE_TIME_OUT); } else { playSound("continue_clock.wav"); } } // Decrementa el contador de entrar nombre void Player::decNameEntryCounter() { name_entry_ticks_ = SDL_GetTicks(); // Actualiza contadores ++name_entry_idle_counter_; ++name_entry_total_counter_; // Comprueba los contadores if ((name_entry_total_counter_ >= param.game.name_entry_total_time) || (name_entry_idle_counter_ >= param.game.name_entry_idle_time)) { name_entry_total_counter_ = 0; name_entry_idle_counter_ = 0; if (playing_state_ == PlayerState::ENTERING_NAME) { last_enter_name_ = getRecordName(); setPlayingState(PlayerState::SHOWING_NAME); } 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_); } void Player::playSound(const std::string &name) { if (demo_) return; static auto audio_ = Audio::get(); audio_->playSound(name); } // Añade una puntuación a la tabla de records void Player::addScoreToScoreBoard() { const auto ENTRY = HiScoreEntry(trim(getLastEnterName()), getScore(), get1CC()); auto manager = std::make_unique(Options::settings.hi_score_table); Options::settings.last_hi_score_entry.at(getId() - 1) = manager->add(ENTRY); manager->saveToFile(Asset::get()->get("score.bin")); }