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

337 lines
13 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.Things;
using ProjectZ.InGame.Map;
using ProjectZ.InGame.SaveLoad;
using ProjectZ.InGame.Things;
namespace ProjectZ.InGame.GameObjects.MidBoss
{
class MBossArmosKnight : GameObject
{
private readonly Animator _animator;
private readonly BodyComponent _body;
private readonly AiComponent _aiComponent;
private readonly AnimationComponent _animationComponent;
private readonly AiDamageState _aiDamageState;
private readonly AiTriggerSwitch _knockbackSwitch;
private readonly DamageFieldComponent _damageField;
private string _saveKey;
private int _jumpCount;
private bool _hitRepelling = true;
private const int ShakeTime = 500;
private const float WalkSpeed = 0.6f;
private const int AttackUpTime = 400;
public MBossArmosKnight() : base("armos knight") { }
public MBossArmosKnight(Map.Map map, int posX, int posY, string saveKey) : base(map)
{
EntityPosition = new CPosition(posX + 16, posY + 32, 0);
EntitySize = new Rectangle(-16, -32, 32, 32);
// check if the boss was already killed
_saveKey = saveKey;
if (!string.IsNullOrEmpty(_saveKey) && Game1.GameManager.SaveManager.GetString(_saveKey) == "1")
{
// we need to make sure to spawn the key because the player could walk out after killing the boss without collecting the key
SpawnKey();
IsDead = true;
return;
}
_animator = AnimatorSaveLoad.LoadAnimator("MidBoss/armosKnight");
var sprite = new CSprite(EntityPosition);
_animationComponent = new AnimationComponent(_animator, sprite, new Vector2(0, -32));
_body = new BodyComponent(EntityPosition, -14, -20, 28, 20, 8)
{
IgnoreHoles = true,
Gravity = -0.15f,
DragAir = 0.875f,
MaxJumpHeight = 8
};
_aiComponent = new AiComponent();
_aiComponent.Trigger.Add(_knockbackSwitch = new AiTriggerSwitch(400));
var stateIdle = new AiState(UpdateIdle) { Init = InitIdle };
var stateAwake = new AiState(UpdateAwake) { Init = InitAwake };
var stateShake = new AiState();
stateShake.Trigger.Add(new AiTriggerCountdown(ShakeTime, ShakeTick, ShakeEnd));
var stateWalk = new AiState(UpdateWalk) { Init = InitWalk };
var stateJump = new AiState();
var stateAttackUp = new AiState { Init = InitAttackUp };
stateAttackUp.Trigger.Add(new AiTriggerCountdown(AttackUpTime, AttackUpTick, AttackUpEnd));
var stateAttackWait = new AiState();
stateAttackWait.Trigger.Add(new AiTriggerCountdown(200, null, () => _aiComponent.ChangeState("attack")));
var stateAttack = new AiState(UpdateAttack) { Init = InitAttack };
var stateAttackFinished = new AiState();
stateAttackFinished.Trigger.Add(new AiTriggerCountdown(850, null, () => _aiComponent.ChangeState("walk")));
_aiComponent.States.Add("idle", stateIdle);
_aiComponent.States.Add("awake", stateAwake);
_aiComponent.States.Add("shake", stateShake);
_aiComponent.States.Add("walk", stateWalk);
_aiComponent.States.Add("jump", stateJump);
_aiComponent.States.Add("attackUp", stateAttackUp);
_aiComponent.States.Add("attackWait", stateAttackWait);
_aiComponent.States.Add("attack", stateAttack);
_aiComponent.States.Add("attackFinished", stateAttackFinished);
_aiDamageState = new AiDamageState(this, _body, _aiComponent, sprite, 2 * 6, false) { BossHitSound = true };
_aiDamageState.AddBossDamageState(RemoveObject);
_aiComponent.ChangeState("idle");
var damageCollider = new CBox(EntityPosition, -14, -24, 0, 28, 24, 8, true);
var hittableBox = new CBox(EntityPosition, -13, -24, 0, 26, 22, 8);
AddComponent(DamageFieldComponent.Index, _damageField = new DamageFieldComponent(damageCollider, HitType.Enemy, 6));
AddComponent(PushableComponent.Index, new PushableComponent(_body.BodyBox, OnPush));
AddComponent(HittableComponent.Index, new HittableComponent(hittableBox, OnHit));
AddComponent(AiComponent.Index, _aiComponent);
AddComponent(BodyComponent.Index, _body);
AddComponent(BaseAnimationComponent.Index, _animationComponent);
AddComponent(DrawComponent.Index, new BodyDrawComponent(_body, sprite, Values.LayerPlayer));
AddComponent(DrawShadowComponent.Index, new BodyDrawShadowComponent(_body, sprite) { ShadowWidth = 24, ShadowHeight = 6 });
}
private void InitIdle()
{
_animator.Play("idle");
}
private void UpdateIdle()
{
// awake if the player is close enough
var distance = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
if (distance.Length() < 32)
_aiComponent.ChangeState("awake");
}
private void InitAwake()
{
_animator.Play("red");
Game1.GameManager.SetMusic(79, 2);
}
private void UpdateAwake()
{
if (!_animator.IsPlaying)
{
_aiComponent.ChangeState("shake");
_hitRepelling = false;
}
}
private void ShakeTick(double counter)
{
// 5 frames to go left/right
_animationComponent.SpriteOffset.X = MathF.Sin(MathF.PI * ((ShakeTime - (float)counter) / 1000 * (60 / 5f)));
_animationComponent.UpdateSprite();
}
private void ShakeEnd()
{
_animationComponent.SpriteOffset.X = 0;
_aiComponent.ChangeState("walk");
}
private void InitWalk()
{
_jumpCount = 0;
}
private void UpdateWalk()
{
if (_body.IsGrounded && _aiDamageState.CurrentLives > 0)
{
var distance = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
if (_jumpCount >= 4)
{
_aiComponent.ChangeState("attackUp");
}
else
{
if (distance != Vector2.Zero)
{
_jumpCount++;
distance.Normalize();
_body.Velocity.Z += 1.125f;
_body.VelocityTarget = distance * WalkSpeed;
Game1.GameManager.PlaySoundEffect("D360-32-20");
}
}
}
}
private void InitAttackUp()
{
_body.IsGrounded = false;
_body.IgnoresZ = true;
_body.VelocityTarget = Vector2.Zero;
Game1.GameManager.PlaySoundEffect("D360-36-24");
var distance = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
if (distance != Vector2.Zero)
{
distance.Normalize();
_body.Velocity = new Vector3(distance * 1f, 0);
}
}
private void AttackUpTick(double time)
{
EntityPosition.Z = MathF.Sin((float)((AttackUpTime - time) / AttackUpTime) * MathF.PI * 0.5f) * 38;
}
private void AttackUpEnd()
{
_aiComponent.ChangeState("attackWait");
}
private void InitAttack()
{
// start falling down
_body.IgnoresZ = false;
_body.JumpStartHeight = 0;
}
private void UpdateAttack()
{
if (_body.IsGrounded)
{
MapManager.ObjLink.GroundStun(1000);
Game1.GameManager.ShakeScreen(500, 1, 3, 2.5f, 5.5f);
Game1.GameManager.PlaySoundEffect("D360-11-0B");
_aiComponent.ChangeState("attackFinished");
}
}
private void InitAngry()
{
if (_animator.CurrentAnimation.Id == "angry")
return;
Game1.GameManager.PlaySoundEffect("D378-41-29");
SpawnStones();
_animator.Play("angry");
}
private void InitBroke()
{
if (_animator.CurrentAnimation.Id == "broken")
return;
Game1.GameManager.PlaySoundEffect("D378-41-29");
SpawnStones();
_animator.Play("broken");
}
private void SpawnStones()
{
var randomOffset0 = Game1.RandomNumber.Next(90, 110) / 100f;
var randomOffset1 = Game1.RandomNumber.Next(90, 110) / 100f;
var randomOffset2 = Game1.RandomNumber.Next(90, 110) / 100f;
var randomOffset3 = Game1.RandomNumber.Next(90, 110) / 100f;
var stone0 = new ObjSmallStone(Map, (int)EntityPosition.X - 3, (int)EntityPosition.Y, (int)EntityPosition.Z + 26, new Vector3(-0.25f, 0.25f * 1, 0.85f) * randomOffset0, true);
var stone1 = new ObjSmallStone(Map, (int)EntityPosition.X - 4, (int)EntityPosition.Y + 8, (int)EntityPosition.Z + 26, new Vector3(-0.35f, 0.25f * 1, 0.85f) * randomOffset1, true);
var stone2 = new ObjSmallStone(Map, (int)EntityPosition.X + 3, (int)EntityPosition.Y, (int)EntityPosition.Z + 26, new Vector3(0.25f, 0.25f * 1, 0.85f) * randomOffset2, true);
var stone3 = new ObjSmallStone(Map, (int)EntityPosition.X + 4, (int)EntityPosition.Y + 8, (int)EntityPosition.Z + 26, new Vector3(0.35f, 0.25f * 1, 0.85f) * randomOffset3, true);
Map.Objects.SpawnObject(stone0);
Map.Objects.SpawnObject(stone1);
Map.Objects.SpawnObject(stone2);
Map.Objects.SpawnObject(stone3);
}
private void RemoveObject()
{
if (!string.IsNullOrEmpty(_saveKey))
Game1.GameManager.SaveManager.SetString(_saveKey, "1");
SpawnKey();
// stop boss music
Game1.GameManager.SetMusic(-1, 2);
Map.Objects.DeleteObjects.Add(this);
}
private void SpawnKey()
{
var objItem = new ObjItem(Map, 0, 0, "j", "dkey4Collected", "dkey4", null);
if (!objItem.IsDead)
{
objItem.EntityPosition.Set(new Vector2(EntityPosition.X, EntityPosition.Y - 2));
Map.Objects.SpawnObject(objItem);
}
}
private bool OnPush(Vector2 direction, PushableComponent.PushType type)
{
if (!_hitRepelling && type == PushableComponent.PushType.Impact)
_body.Velocity = new Vector3(direction.X, direction.Y, _body.Velocity.Z);
return true;
}
public Values.HitCollision OnHit(GameObject gameObject, Vector2 direction, HitType damageType, int damage, bool pieceOfPower)
{
if (_aiDamageState.CurrentLives <= 0 ||
_aiDamageState.IsInDamageState())
return Values.HitCollision.None;
// knock the boss back
if (!_hitRepelling && _knockbackSwitch.State)
{
_knockbackSwitch.Reset();
_body.VelocityTarget = Vector2.Zero;
_body.Velocity.X = direction.X * 3.0f;
_body.Velocity.Y = direction.Y * 3.0f;
}
if (_hitRepelling)
return Values.HitCollision.RepellingParticle;
if ((damageType & HitType.PegasusBootsSword) != 0)
{
var hitCollision = _aiDamageState.OnHit(gameObject, direction, damageType, damage, pieceOfPower);
if (_aiDamageState.CurrentLives <= 0)
{
_damageField.IsActive = false;
}
// boss should start jumping directly after getting hit
_jumpCount = 0;
// change the animation to reflect the health of the boss
if (_aiDamageState.CurrentLives <= 2)
InitBroke();
else if (_aiDamageState.CurrentLives <= 6)
InitAngry();
return hitCollision | Values.HitCollision.Repelling;
}
return Values.HitCollision.None;
}
}
}