kaliber/src/demo/player.cc

486 lines
14 KiB
C++

#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<Demo*>(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<InputEvent> 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<Demo*>(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<float>::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<Demo*>(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<Demo*>(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<Demo*>(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<Demo*>(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<Demo*>(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<Demo*>(engine.GetGame())->EnterMenuState();
}