#include "demo/player.h" #include "base/interpolation.h" #include "base/log.h" #include "base/vecmath.h" #include "engine/asset/font.h" #include "engine/engine.h" #include "engine/input_event.h" #include "demo/demo.h" using namespace base; using namespace eng; namespace { constexpr int wepon_warmup_frame[] = {1, 9}; constexpr int wepon_warmup_frame_count = 4; constexpr int wepon_cooldown_frame[] = {5, 13}; constexpr int wepon_cooldown_frame_count = 3; constexpr int wepon_anim_speed = 48; const Vector4f kNukeColor[2] = {{0.16f, 0.46f, 0.93f, 0}, {0.93f, 0.35f, 0.15f, 1}}; } // namespace Player::Player() = default; Player::~Player() = default; bool Player::PreInitialize() { Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true); Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true); Engine::Get().SetImageSource("nuke_symbol_tex", "nuke_frames.png", true); Engine::Get().SetImageSource("health_bead", "bead.png", true); Engine::Get().AsyncLoadSound("laser", "laser.mp3"); Engine::Get().AsyncLoadSound("nuke", "nuke.mp3"); Engine::Get().AsyncLoadSound("no_nuke", "no_nuke.mp3"); return true; } bool Player::Initialize() { SetupWeapons(); Vector2f hb_pos = Engine::Get().GetScreenSize() / Vector2f(2, -2) + Vector2f(0, weapon_[0].GetSize().y * 0.3f); for (int i = 0; i < 3; ++i) { health_bead_[i].Create("health_bead", {1, 2}); health_bead_[i].SetZOrder(25); health_bead_[i].Translate(hb_pos * Vector2f(0, 1)); health_bead_[i].SetVisible(true); } health_bead_[0].PlaceToLeftOf(health_bead_[1]); health_bead_[0].Translate(health_bead_[1].GetSize() * Vector2f(-0.1f, 0)); health_bead_[2].PlaceToRightOf(health_bead_[1]); health_bead_[2].Translate(health_bead_[1].GetSize() * Vector2f(0.1f, 0)); nuke_symbol_.Create("nuke_symbol_tex", {5, 1}); nuke_symbol_.SetZOrder(29); nuke_symbol_.SetPosition({0, weapon_[0].GetPosition().y}); nuke_symbol_.SetFrame(4); nuke_symbol_.SetVisible(true); nuke_.SetZOrder(20); nuke_.SetSize(Engine::Get().GetScreenSize()); nuke_.SetColor(kNukeColor[0]); nuke_animator_.Attach(&nuke_); nuke_symbol_animator_.Attach(&nuke_symbol_); nuke_explosion_.SetSound("nuke"); nuke_explosion_.SetVariate(false); nuke_explosion_.SetSimulateStereo(false); nuke_explosion_.SetMaxAmplitude(0.8f); no_nuke_.SetSound("no_nuke"); return true; } void Player::Update(float delta_time) { for (int i = 0; i < 2; ++i) { if (drag_weapon_[i] != kDamageType_Invalid) UpdateTarget(drag_weapon_[i]); } #if defined(LOAD_TEST) Enemy& enemy = static_cast(Engine::Get().GetGame())->GetEnemy(); if (enemy.num_enemies_killed_in_current_wave() == 40) Nuke(); DamageType type = (DamageType)(Engine::Get().GetRandomGenerator().Roll(2) - 1); if (!IsFiring(type)) { DragStart(type, GetWeaponPos(type)); Drag(type, {0, 0}); DragEnd(type); } #endif } void Player::Pause(bool pause) { for (int i = 0; i < 2; ++i) { warmup_animator_[i].PauseOrResumeAll(pause); cooldown_animator_[i].PauseOrResumeAll(pause); beam_animator_[i].PauseOrResumeAll(pause); spark_animator_[i].PauseOrResumeAll(pause); } nuke_animator_.PauseOrResumeAll(pause); nuke_symbol_animator_.PauseOrResumeAll(pause); } void Player::OnInputEvent(std::unique_ptr event) { if (event->GetType() == InputEvent::kNavigateBack) NavigateBack(); else if (event->GetType() == InputEvent::kDragStart) DragStart(event->GetPointerId(), event->GetVector()); else if (event->GetType() == InputEvent::kDrag) Drag(event->GetPointerId(), event->GetVector()); else if (event->GetType() == InputEvent::kDragEnd) DragEnd(event->GetPointerId()); else if (event->GetType() == InputEvent::kDragCancel) DragCancel(event->GetPointerId()); } void Player::TakeDamage(int damage) { if (damage > 0) Engine::Get().Vibrate(250); hit_points_ = std::min(total_health_, std::max(0, hit_points_ - damage)); for (int i = 0; i < 3; ++i) health_bead_[i].SetFrame(hit_points_ > i ? 0 : 1); if (hit_points_ == 0) static_cast(Engine::Get().GetGame())->EnterGameOverState(); } void Player::AddNuke(int n) { int new_nuke_count = std::max(std::min(nuke_count_ + n, 3), 0); if (new_nuke_count == nuke_count_) return; nuke_count_ = new_nuke_count; nuke_symbol_.SetFrame(4 - nuke_count_); if (!nuke_symbol_animator_.IsPlaying(Animator::kRotation)) { nuke_symbol_animator_.SetRotation( PIf * 5, 2, std::bind(SmootherStep, std::placeholders::_1)); nuke_symbol_animator_.Play(Animator::kRotation, false); } } void Player::Reset() { DragCancel(0); DragCancel(1); TakeDamage(-total_health_); nuke_count_ = 1; nuke_symbol_.SetFrame(3); } Vector2f Player::GetWeaponPos(DamageType type) const { return Engine::Get().GetScreenSize() / Vector2f(type == kDamageType_Green ? 3.5f : -3.5f, -2) + Vector2f(0, weapon_[type].GetSize().y * 0.95f); } Vector2f Player::GetWeaponScale() const { return weapon_[0].GetSize(); } DamageType Player::GetWeaponType(const Vector2f& pos) { DamageType closest_weapon = kDamageType_Invalid; float closest_dist = std::numeric_limits::max(); for (int i = 0; i < 2; ++i) { float dist = (pos - weapon_[i].GetPosition()).Length(); if (dist < closest_dist) { closest_dist = dist; closest_weapon = (DamageType)i; } } DCHECK(closest_weapon != kDamageType_Invalid); if (closest_dist < weapon_[closest_weapon].GetSize().x * 0.5f) return closest_weapon; return kDamageType_Invalid; } void Player::WarmupWeapon(DamageType type) { cooldown_animator_[type].Stop(Animator::kFrames); warmup_animator_[type].Play(Animator::kFrames, false); } void Player::CooldownWeapon(DamageType type) { warmup_animator_[type].Stop(Animator::kFrames); cooldown_animator_[type].Play(Animator::kFrames, false); } void Player::Fire(DamageType type, Vector2f dir) { Engine& engine = Engine::Get(); Enemy& enemy = static_cast(engine.GetGame())->GetEnemy(); float max_beam_length = engine.GetScreenSize().y * 1.3f * 0.85f; constexpr float max_beam_duration = 0.259198f; if (enemy.HasTarget(type)) dir = weapon_[type].GetPosition() - enemy.GetTargetPos(type); else dir *= engine.GetScreenSize().y * 1.3f; float len = dir.Length(); beam_[type].SetSize({len, beam_[type].GetSize().y}); beam_[type].SetPosition(weapon_[type].GetPosition()); dir.Normalize(); float cos_theta = dir.DotProduct(Vector2f(1, 0)); float theta = acos(cos_theta) + PIf; beam_[type].SetTheta(theta); auto offset = beam_[type].GetRotation() * (len / 2); beam_[type].Translate({offset.y, -offset.x}); beam_[type].SetColor({1, 1, 1, 1}); beam_[type].SetVisible(true); beam_spark_[type].SetVisible(true); spark_animator_[type].Stop(Animator::kMovement); float length = beam_[type].GetSize().x * 0.9f; Vector2f movement = dir * -length; float duration = (length * max_beam_duration) / max_beam_length; spark_animator_[type].SetMovement(movement, duration); spark_animator_[type].Play(Animator::kMovement, false); laser_shot_[type].Play(false); } bool Player::IsFiring(DamageType type) { return beam_animator_[type].IsPlaying(Animator::kBlending) || spark_animator_[type].IsPlaying(Animator::kMovement); } void Player::SetupWeapons() { for (int i = 0; i < 2; ++i) { // Setup draw sign. drag_sign_[i].Create("weapon_tex", {8, 2}); drag_sign_[i].SetZOrder(21); drag_sign_[i].SetFrame(i * 8); // Setup weapon. weapon_[i].Create("weapon_tex", {8, 2}); weapon_[i].SetZOrder(24); weapon_[i].SetVisible(true); weapon_[i].SetFrame(wepon_warmup_frame[i]); Vector2f pos = GetWeaponPos((DamageType)i); weapon_[i].SetPosition(pos); // Setup beam. beam_[i].Create("beam_tex", {1, 2}); beam_[i].SetZOrder(22); beam_[i].SetFrame(i); beam_[i].SetPosition(pos); beam_[i].Translate(beam_[i].GetSize() * Vector2f(-0.5f, -0.5f)); // Setup beam spark. beam_spark_[i].Create("weapon_tex", {8, 2}); beam_spark_[i].SetZOrder(23); beam_spark_[i].SetFrame(i * 8 + 1); beam_spark_[i].SetPosition(pos); // Setup animators. weapon_[i].SetFrame(wepon_cooldown_frame[i]); cooldown_animator_[i].SetFrames(wepon_cooldown_frame_count, wepon_anim_speed); cooldown_animator_[i].SetEndCallback(Animator::kFrames, [&, i]() -> void { weapon_[i].SetFrame(wepon_warmup_frame[i]); }); cooldown_animator_[i].Attach(&weapon_[i]); weapon_[i].SetFrame(wepon_warmup_frame[i]); warmup_animator_[i].SetFrames(wepon_warmup_frame_count, wepon_anim_speed); warmup_animator_[i].SetRotation(PIf * 2, 20.0f); warmup_animator_[i].Attach(&weapon_[i]); warmup_animator_[i].Play(Animator::kRotation, true); spark_animator_[i].SetEndCallback(Animator::kMovement, [&, i]() -> void { beam_spark_[i].SetVisible(false); beam_animator_[i].Play(Animator::kBlending, false); static_cast(Engine::Get().GetGame()) ->GetEnemy() .HitTarget((DamageType)i); }); spark_animator_[i].Attach(&beam_spark_[i]); beam_animator_[i].SetEndCallback( Animator::kBlending, [&, i]() -> void { beam_[i].SetVisible(false); }); beam_animator_[i].SetBlending({1, 1, 1, 0}, 0.16f); beam_animator_[i].Attach(&beam_[i]); laser_shot_[i].SetSound("laser"); laser_shot_[i].SetVariate(true); laser_shot_[i].SetSimulateStereo(false); laser_shot_[i].SetMaxAmplitude(0.4f); } } void Player::UpdateTarget(DamageType weapon) { if (IsFiring(weapon)) return; Engine& engine = Engine::Get(); Demo* game = static_cast(engine.GetGame()); int i = weapon_drag_ind[weapon]; if (drag_valid_[i]) { Vector2f origin = weapon_[weapon].GetPosition(); Vector2f dir = (drag_end_[i] - drag_start_[i]).Normalize(); game->GetEnemy().SelectTarget(weapon, origin, dir); } else { game->GetEnemy().DeselectTarget(weapon); } } void Player::Nuke() { if (nuke_animator_.IsPlaying(Animator::kBlending)) return; if (nuke_count_ <= 0) { no_nuke_.Play(false); return; } Engine& engine = Engine::Get(); Demo* game = static_cast(engine.GetGame()); AddNuke(-1); nuke_animator_.SetEndCallback(Animator::kBlending, [&, game]() -> void { nuke_animator_.SetEndCallback(Animator::kBlending, [&]() -> void { nuke_.SetVisible(false); }); nuke_animator_.SetBlending( kNukeColor[0], 2, std::bind(Acceleration, std::placeholders::_1, -1)); nuke_animator_.SetEndCallback(Animator::kTimer, [&, game]() -> void { game->GetEnemy().KillAllEnemyUnits(false); game->GetEnemy().ResumeProgress(); }); nuke_animator_.SetTimer(0.5f); nuke_animator_.Play(Animator::kBlending | Animator::kTimer, false); }); nuke_animator_.SetBlending(kNukeColor[1], 0.1f, std::bind(Acceleration, std::placeholders::_1, 1)); nuke_animator_.Play(Animator::kBlending, false); nuke_.SetVisible(true); game->GetEnemy().PauseProgress(); game->GetEnemy().StopAllEnemyUnits(true); nuke_explosion_.Play(false); } void Player::DragStart(int i, const Vector2f& pos) { drag_weapon_[i] = GetWeaponType(pos); if (drag_weapon_[i] == kDamageType_Invalid) { float dist = (pos - nuke_symbol_.GetPosition()).Length(); drag_nuke_[i] = dist <= nuke_symbol_.GetSize().x * 0.7f; return; } weapon_drag_ind[drag_weapon_[i]] = i; drag_start_[i] = pos; drag_end_[i] = pos; drag_sign_[drag_weapon_[i]].SetPosition(pos); drag_sign_[drag_weapon_[i]].SetVisible(true); } void Player::Drag(int i, const Vector2f& pos) { if (drag_weapon_[i] == kDamageType_Invalid) return; drag_end_[i] = pos; drag_sign_[drag_weapon_[i]].SetPosition(pos); if (ValidateDrag(i)) { if (!drag_valid_[i] && !IsFiring(drag_weapon_[i])) WarmupWeapon(drag_weapon_[i]); drag_valid_[i] = true; } else { if (drag_valid_[i] && !IsFiring(drag_weapon_[i])) CooldownWeapon(drag_weapon_[i]); drag_valid_[i] = false; } } void Player::DragEnd(int i) { if (drag_weapon_[i] == kDamageType_Invalid) { if (drag_nuke_[i]) { drag_nuke_[i] = false; Nuke(); } return; } UpdateTarget(drag_weapon_[i]); DamageType type = drag_weapon_[i]; drag_weapon_[i] = kDamageType_Invalid; drag_sign_[type].SetVisible(false); Vector2f fire_dir = (drag_start_[i] - drag_end_[i]).Normalize(); if (drag_valid_[i] && !IsFiring(type)) { if (warmup_animator_[type].IsPlaying(Animator::kFrames)) { warmup_animator_[type].SetEndCallback( Animator::kFrames, [&, type, fire_dir]() -> void { warmup_animator_[type].SetEndCallback(Animator::kFrames, nullptr); CooldownWeapon(type); Fire(type, fire_dir); }); } else { CooldownWeapon(type); Fire(type, fire_dir); } } drag_valid_[i] = false; drag_start_[i] = {0, 0}; drag_end_[i] = {0, 0}; } void Player::DragCancel(int i) { if (drag_weapon_[i] == kDamageType_Invalid) return; Engine& engine = Engine::Get(); Demo* game = static_cast(engine.GetGame()); game->GetEnemy().DeselectTarget(drag_weapon_[i]); DamageType type = drag_weapon_[i]; drag_weapon_[i] = kDamageType_Invalid; drag_sign_[type].SetVisible(false); if (drag_valid_[i] && !IsFiring(type)) { if (warmup_animator_[type].IsPlaying(Animator::kFrames)) { warmup_animator_[type].SetEndCallback( Animator::kFrames, [&, type]() -> void { warmup_animator_[type].SetEndCallback(Animator::kFrames, nullptr); CooldownWeapon(type); }); } else { CooldownWeapon(type); } } drag_valid_[i] = false; drag_start_[i] = {0, 0}; drag_end_[i] = {0, 0}; } bool Player::ValidateDrag(int i) { Vector2f dir = drag_end_[i] - drag_start_[i]; float len = dir.Length(); dir.Normalize(); if (len < weapon_[0].GetSize().y / 3) return false; if (dir.DotProduct(Vector2f(0, 1)) < 0) return false; return true; } void Player::NavigateBack() { DragCancel(0); DragCancel(1); Engine& engine = Engine::Get(); static_cast(engine.GetGame())->EnterMenuState(); }