LADXHD/InGame/GameObjects/Bosses/BossEvilEagle.cs
2023-12-14 17:21:22 -05:00

571 lines
21 KiB
C#

using System;
using Microsoft.Xna.Framework;
using ProjectZ.InGame.GameObjects.Base;
using ProjectZ.InGame.GameObjects.Base.CObjects;
using ProjectZ.InGame.GameObjects.Base.Components;
using ProjectZ.InGame.GameObjects.Base.Components.AI;
using ProjectZ.InGame.GameObjects.Enemies;
using ProjectZ.InGame.GameObjects.MidBoss;
using ProjectZ.InGame.GameObjects.Things;
using ProjectZ.InGame.Map;
using ProjectZ.InGame.SaveLoad;
using ProjectZ.InGame.Things;
namespace ProjectZ.InGame.GameObjects.Bosses
{
class BossEvilEagle : GameObject
{
private MBossGrimCreeper _grimCreeper;
private MBossGrimCreeperFly _creeperFly0;
private MBossGrimCreeperFly _creeperFly1;
private readonly CSprite _sprite;
private readonly BodyComponent _body;
private readonly AiComponent _aiComponent;
private readonly Animator _animator;
private readonly AiDamageState _damageState;
private readonly DamageFieldComponent _damageComponent;
private readonly HittableComponent _hittableComponent;
private readonly Vector2 _startPosition;
private readonly string _saveKey;
private Vector2 _slowStart;
private float _slowCounter;
private float _transparency = 0;
private int _spawnIndex;
private int _direction;
private const float FlySpeed = 2;
private const int Lives = 12;
private Vector2 _wingStartPosition;
private Vector2 _wingEndPosition;
private float _wingCounter;
private float _featherCounter;
private const float WingTime = 700;
private bool _featherAttack;
private bool _playedIntro;
private bool _introSound;
public BossEvilEagle() : base("evil eagle") { }
public BossEvilEagle(Map.Map map, int posX, int posY, string saveKey) : base(map)
{
Tags = Values.GameObjectTag.Enemy;
EntityPosition = new CPosition(posX, posY, 0);
EntitySize = new Rectangle(-32, -32, 64, 64);
_startPosition = EntityPosition.Position;
_saveKey = saveKey;
if (!string.IsNullOrWhiteSpace(_saveKey) && Game1.GameManager.SaveManager.GetString(_saveKey) == "1")
{
// respawn the heart if the player died after he killed the boss without collecting the heart
SpawnHeart();
IsDead = true;
return;
}
_animator = AnimatorSaveLoad.LoadAnimator("Nightmares/evil eagle");
_animator.Play("glide_-1");
_sprite = new CSprite(EntityPosition) { Color = Color.Transparent };
var animationComponent = new AnimationComponent(_animator, _sprite, Vector2.Zero);
_body = new BodyComponent(EntityPosition, -8, -16, 16, 16, 8)
{
CollisionTypes = Values.CollisionTypes.None,
Bounciness = 0.25f,
Drag = 1.0f,
DragAir = 1.0f,
IgnoresZ = true,
IsGrounded = false
};
var hittableRectangle = new CBox(EntityPosition, -8, -16, 16, 16, 8);
var damageCollider = new CBox(EntityPosition, -8, -16, 16, 16, 8);
var stateIdle = new AiState(UpdateIdle) { Init = InitIdle };
var stateSpawnDelay = new AiState();
stateSpawnDelay.Trigger.Add(new AiTriggerCountdown(750, null, () => _aiComponent.ChangeState("spawning")));
var stateSpawning = new AiState(UpdateSpawning) { Init = InitSpawning };
var statePreJump = new AiState();
statePreJump.Trigger.Add(new AiTriggerCountdown(1000, null, () => _aiComponent.ChangeState("grimSaddle")));
var stateGrimSaddle = new AiState(UpdateGrimSaddle) { Init = InitGrimSaddle };
var stateSaddled = new AiState(UpdateSaddled);
var stateAttack = new AiState(UpdateAttack) { Init = InitAttack };
var stateDamaged = new AiState(UpdateDamaged);
stateDamaged.Trigger.Add(new AiTriggerCountdown(500, null, FlyUp));
var stateFlyUp = new AiState(UpdateFlyUp) { Init = InitFlyUp };
var stateAttackEnter = new AiState(UpdateWingAttackEnter) { Init = InitWingAttackEnter };
var stateFeatherAttack = new AiState(UpdateWingAttack) { Init = InitWingAttack };
var statePreAttack = new AiState() { Init = InitPreAttack };
statePreAttack.Trigger.Add(new AiTriggerCountdown(800, null, () => _aiComponent.ChangeState("grabAttack")));
var stateGrab = new AiState(UpdateAttackGrab) { Init = InitGrab };
var stateLeave = new AiState(UpdateLeave) { Init = InitLeave };
var stateGone = new AiState();
stateGone.Trigger.Add(new AiTriggerCountdown(100, null, ToAttack));
_aiComponent = new AiComponent();
_aiComponent.Trigger.Add(new AiTriggerUpdate(UpdateVisibility));
_aiComponent.States.Add("idle", stateIdle);
_aiComponent.States.Add("spawnDelay", stateSpawnDelay);
_aiComponent.States.Add("spawning", stateSpawning);
_aiComponent.States.Add("grimPreJump", statePreJump);
_aiComponent.States.Add("grimSaddle", stateGrimSaddle);
_aiComponent.States.Add("saddled", stateSaddled);
_aiComponent.States.Add("attack", stateAttack);
_aiComponent.States.Add("damaged", stateDamaged);
_aiComponent.States.Add("flyup", stateFlyUp);
_aiComponent.States.Add("attackEnter", stateAttackEnter);
_aiComponent.States.Add("featherAttack", stateFeatherAttack);
_aiComponent.States.Add("preAttack", statePreAttack);
_aiComponent.States.Add("grabAttack", stateGrab);
_aiComponent.States.Add("leave", stateLeave);
_aiComponent.States.Add("gone", stateGone);
_damageState = new AiDamageState(this, _body, _aiComponent, _sprite, Lives, false, false, AiDamageState.BlinkTime * 2 * 10) { ExplosionOffsetY = 8 };
_damageState.AddBossDamageState(OnDeath);
AddComponent(AiComponent.Index, _aiComponent);
AddComponent(DamageFieldComponent.Index, _damageComponent = new DamageFieldComponent(damageCollider, HitType.Enemy, 8));
AddComponent(HittableComponent.Index, _hittableComponent = new HittableComponent(hittableRectangle, OnHit));
AddComponent(BaseAnimationComponent.Index, animationComponent);
AddComponent(BodyComponent.Index, _body);
AddComponent(DrawComponent.Index, new DrawCSpriteComponent(_sprite, Values.LayerPlayer));
InitStartSequence();
_aiComponent.ChangeState("idle");
// spawn position
EntityPosition.Set(new Vector2(EntityPosition.X + 180, 24));
}
private void InitStartSequence()
{
_grimCreeper = new MBossGrimCreeper(Map, (int)EntityPosition.X - 32, (int)EntityPosition.Y + 32, null);
_grimCreeper.StartNightmareSequnece();
Map.Objects.SpawnObject(_grimCreeper);
_creeperFly0 = new MBossGrimCreeperFly(Map, new Vector2(EntityPosition.X - 41, EntityPosition.Y + 32), new Vector2(-24, 12));
_creeperFly0.StartSequenceMode();
Map.Objects.SpawnObject(_creeperFly0);
_creeperFly1 = new MBossGrimCreeperFly(Map, new Vector2(EntityPosition.X - 7, EntityPosition.Y + 32), new Vector2(-16, -8));
_creeperFly1.StartSequenceMode();
Map.Objects.SpawnObject(_creeperFly1);
}
private void UpdateVisibility()
{
var target = (_startPosition.X - 144 < EntityPosition.X && EntityPosition.X < _startPosition.X + 144 &&
EntityPosition.Y > -16 && EntityPosition.Y < 160) ? 1 : 0;
_transparency = AnimationHelper.MoveToTarget(_transparency, target, 0.175f * Game1.TimeMultiplier);
_sprite.Color = Color.White * _transparency;
}
private void InitIdle()
{
_body.Velocity = Vector3.Zero;
_body.VelocityTarget = Vector2.Zero;
_damageState.CurrentLives = Lives;
}
private void UpdateIdle()
{
if (MapManager.ObjLink.EntityPosition.Y > _startPosition.Y + 90 || !MapManager.ObjLink.IsClimbing())
return;
if (!_playedIntro)
{
Game1.GameManager.StartDialogPath("grim_creeper_3");
_aiComponent.ChangeState("spawnDelay");
}
else
ToAttack();
}
private void SpawnHeart()
{
Map.Objects.SpawnObject(new ObjItem(Map, (int)_startPosition.X - 8, (int)_startPosition.Y, null, _saveKey + "_heart", "heartMeterFull", null));
}
private void InitPreAttack()
{
_animator.SpeedMultiplier = 1.5f;
}
private void InitGrab()
{
_animator.Stop();
_animator.Play("cflap_" + _direction);
_animator.Stop();
// adjust the distance
var distance = MathF.Abs(MapManager.ObjLink.EntityPosition.X - EntityPosition.X);
var mult = Math.Clamp(distance / 40 * 1.45f, 0.35f, 1.5f);
var direction = new Vector2(_direction * mult, 1.45f);
direction.Normalize();
_body.VelocityTarget = direction * 4f;
}
private void UpdateAttackGrab()
{
if (EntityPosition.Y > _startPosition.Y + 180)
ToAttack();
}
private void InitLeave()
{
//_body.VelocityTarget = new Vector2(0, -1);
}
private void UpdateLeave()
{
if (_direction < 0 && _body.Velocity.X > -3)
_body.Velocity.X -= 0.15f * Game1.TimeMultiplier;
if (_direction > 0 && _body.Velocity.X < 3)
_body.Velocity.X += 0.15f * Game1.TimeMultiplier;
if (_body.Velocity.Y > -2)
_body.Velocity.Y -= 0.015f * Game1.TimeMultiplier;
// start playing the glide animation
if (_direction == -1 && EntityPosition.X < _startPosition.X ||
_direction == 1 && EntityPosition.X > _startPosition.X)
{
_animator.Play("cglide_" + _direction);
}
if (_direction == -1 && EntityPosition.X < _startPosition.X - 160 ||
_direction == 1 && EntityPosition.X > _startPosition.X + 160)
{
ToAttack();
}
}
private void InitWingAttackEnter()
{
_direction = MapManager.ObjLink.EntityPosition.X < _startPosition.X ? -1 : 1;
// only show the first frame
_animator.Play("cflap_" + _direction);
_animator.Stop();
_body.Velocity = Vector3.Zero;
_body.VelocityTarget = Vector2.Zero;
_wingCounter = 0;
_wingStartPosition = new Vector2(_startPosition.X - _direction * 100, _startPosition.Y - 24);
_wingEndPosition = new Vector2(_startPosition.X - _direction * 28, _startPosition.Y + 8);
EntityPosition.Set(_wingStartPosition);
}
private void UpdateWingAttackEnter()
{
_wingCounter += Game1.DeltaTime;
var percentage = _wingCounter / WingTime;
// start flapping the wings
if (percentage > 0.80)
_animator.Continue();
if (percentage >= 1)
{
EntityPosition.Set(_wingEndPosition);
// feather attack
if (_featherAttack)
_aiComponent.ChangeState("featherAttack");
else
_aiComponent.ChangeState("preAttack");
_featherAttack = false;
return;
}
var lerpState = MathF.Sin(percentage * MathF.PI / 2);
var newPosition = Vector2.Lerp(_wingStartPosition, _wingEndPosition, lerpState);
EntityPosition.Set(newPosition);
}
private void InitWingAttack()
{
_wingCounter = 0;
}
private void UpdateWingAttack()
{
_wingCounter += Game1.DeltaTime;
// end state
if (_wingCounter > 275 * 11)
{
_aiComponent.ChangeState("leave");
}
// move up and down
var offset = MathF.Sin(_wingCounter / 1100 * MathF.PI * 2 + MathF.PI / 2) * 4 - 4;
var newPosition = new Vector2(_wingEndPosition.X, _wingEndPosition.Y + offset);
EntityPosition.Set(newPosition);
// push the playyer
if (MapManager.ObjLink.EntityPosition.Y < _startPosition.Y + 90)
MapManager.ObjLink._body.AdditionalMovementVT.X = _direction * 1.1f;
else
MapManager.ObjLink._body.AdditionalMovementVT.X = 0;
// shoot a feather
_featherCounter += Game1.DeltaTime;
if (_featherCounter > 275)
{
_featherCounter = 0;
var startPosition = new Vector2(EntityPosition.X - _direction * 4, EntityPosition.Y + 10);
var direction = MapManager.ObjLink.EntityPosition.Position - startPosition;
if (direction != Vector2.Zero)
direction.Normalize();
var radiants = MathF.Atan2(direction.Y, direction.X);
// randomly offset the direction a little bit
radiants += (Game1.RandomNumber.Next(0, 11) - 5) / 25f;
var aimDirection = new Vector2(MathF.Cos(radiants), MathF.Sin(radiants));
var eagleFeather = new BossEvilEagleFeather(Map, startPosition, aimDirection * 4);
Map.Objects.SpawnObject(eagleFeather);
Game1.GameManager.PlaySoundEffect("D378-50-32");
}
}
private void UpdateDamaged()
{
if (!_damageState.IsInDamageState())
_aiComponent.ChangeState("flyup");
}
private void FlyUp()
{
Game1.GameManager.PlaySoundEffect("D378-49-31");
_animator.Play("cflap_" + _direction);
}
private void InitFlyUp()
{
}
private void UpdateFlyUp()
{
if (_body.Velocity.Y > -2)
_body.Velocity.Y -= 0.1f * Game1.TimeMultiplier;
if (EntityPosition.Y < -48)
ToAttack();
}
private void ToAttack()
{
// reset the boss
if (MapManager.ObjLink.EntityPosition.Y > _startPosition.Y + 90)
{
_aiComponent.ChangeState("idle");
return;
}
_body.VelocityTarget = Vector2.Zero;
_damageComponent.IsActive = true;
if (_damageState.CurrentLives == 4 || _damageState.CurrentLives == 5 || _featherAttack)
_aiComponent.ChangeState("attackEnter");
else
_aiComponent.ChangeState("attack");
}
private void InitAttack()
{
_direction = Game1.RandomNumber.Next(0, 2) * 2 - 1;
_body.Velocity = Vector3.Zero;
_body.VelocityTarget.X = _direction * FlySpeed;
var randomHeight = Game1.RandomNumber.Next(0, 28) * 2;
EntityPosition.Set(new Vector2(_startPosition.X - _direction * 160, (int)MapManager.ObjLink.EntityPosition.Y - randomHeight));
_animator.Play("cglide_" + _direction);
}
private void UpdateAttack()
{
if (EntityPosition.X < _startPosition.X - 180 || _startPosition.X + 180 < EntityPosition.X)
_aiComponent.ChangeState("gone");
}
private void UpdateSaddled()
{
if (_body.Velocity.X > -3)
_body.Velocity.X -= 0.15f * Game1.TimeMultiplier;
if (_body.Velocity.Y > -2)
_body.Velocity.Y -= 0.01f * Game1.TimeMultiplier;
if (EntityPosition.X < _startPosition.X - 23)
{
_animator.Play("cglide_-1");
}
if (EntityPosition.X < _startPosition.X - 180)
{
// start attacking
ToAttack();
}
}
private void InitGrimSaddle()
{
_grimCreeper.StartSaddleJump();
}
private void UpdateGrimSaddle()
{
if (_grimCreeper.Map == null)
{
_aiComponent.ChangeState("saddled");
_animator.Play("cflap");
_animator.SpeedMultiplier = 0.5f;
}
}
private void InitSpawning()
{
_playedIntro = true;
_body.VelocityTarget.X = -FlySpeed;
_spawnIndex = 0;
}
private void UpdateSpawning()
{
MapManager.ObjLink.FreezePlayer();
if (_spawnIndex == 0)
{
if (EntityPosition.X < _startPosition.X - 180)
{
_animator.Play("glide_1");
_body.VelocityTarget.X = FlySpeed;
EntityPosition.Y += 20;
_spawnIndex = 1;
}
}
else if (_spawnIndex == 1)
{
if (!_introSound && EntityPosition.X > _startPosition.X - 130)
{
_introSound = true;
Game1.GameManager.PlaySoundEffect("D378-34-22");
}
if (EntityPosition.X > _startPosition.X + 180)
{
_introSound = false;
_animator.Play("glide_-1");
_body.VelocityTarget.X = -FlySpeed;
_spawnIndex = 2;
EntityPosition.Y += 20;
}
}
else if (_spawnIndex == 2)
{
if (!_introSound && EntityPosition.X < _startPosition.X + 110)
{
_introSound = true;
Game1.GameManager.PlaySoundEffect("D378-48-30");
}
if (EntityPosition.X < _startPosition.X + 40)
{
Game1.GameManager.PlaySoundEffect("D360-48-30");
_animator.Play("flap");
_slowStart = EntityPosition.Position;
_body.VelocityTarget.X = 0;
_spawnIndex = 3;
}
}
else if (_spawnIndex == 3)
{
_slowCounter += Game1.DeltaTime;
if (_slowCounter > 500)
{
_aiComponent.ChangeState("grimSaddle");
_slowCounter = 500;
_creeperFly0.ToLeave();
_creeperFly1.ToLeave();
}
var percentage = MathF.Sin(_slowCounter / 500 * MathF.PI / 2);
var newPositionX = MathHelper.Lerp(_slowStart.X, _startPosition.X + 6, percentage);
EntityPosition.Set(new Vector2(newPositionX, EntityPosition.Y));
}
}
private Values.HitCollision OnHit(GameObject originObject, Vector2 direction, HitType damageType, int damage, bool pieceOfPower)
{
if (_damageState.IsInDamageState() || _aiComponent.CurrentStateId == "flyup")
return Values.HitCollision.None;
if (damageType == HitType.MagicRod || damageType == HitType.Boomerang)
damage = 4;
_damageComponent.IsActive = false;
_damageState.SetDamageState(true);
_damageState.CurrentLives -= damage;
Game1.GameManager.PlaySoundEffect("D370-07-07");
// dead?
if (_damageState.CurrentLives <= 0)
{
_body.IsActive = false;
_hittableComponent.IsActive = false;
Game1.GameManager.StartDialogPath("grim_creeper_4");
_damageState.OnDeathBoss(pieceOfPower);
return Values.HitCollision.Enemy;
}
// next move will be the wing attack; not sure how this works in the original
if (Game1.RandomNumber.Next(0, 2) == 0)
_featherAttack = true;
_aiComponent.ChangeState("damaged");
_body.VelocityTarget = Vector2.Zero;
return Values.HitCollision.Enemy;
}
private void OnDeath()
{
if (!string.IsNullOrEmpty(_saveKey))
Game1.GameManager.SaveManager.SetString(_saveKey, "1");
Game1.GameManager.PlaySoundEffect("D378-26-1A");
SpawnHeart();
Map.Objects.DeleteObjects.Add(this);
}
}
}