mirror of
https://github.com/Phantop/LADXHD.git
synced 2024-11-01 04:14:22 +00:00
337 lines
13 KiB
C#
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|