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

377 lines
15 KiB
C#

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.Dungeon;
using ProjectZ.InGame.GameObjects.Things;
using ProjectZ.InGame.Map;
using ProjectZ.InGame.SaveLoad;
using ProjectZ.InGame.Things;
namespace ProjectZ.InGame.GameObjects.MidBoss
{
class MBossHinox : GameObject
{
private readonly Color[] _colors = new Color[] { new Color(248, 120, 8), new Color(248, 8, 40), new Color(24, 128, 248) };
private readonly Animator _animator;
private readonly BodyComponent _body;
private readonly AiComponent _aiComponent;
private readonly AiDamageState _aiDamageState;
private readonly DamageFieldComponent _damageFieldComponent;
private readonly CBox _grabBox;
private readonly string _saveKey;
private const int GrabTime = 300;
private const int Lives = 8;
private float _runParticleCount;
private Vector3 _grabStartPosition;
private int _grabDirection;
public MBossHinox() : base("hinox") { }
public MBossHinox(Map.Map map, int posX, int posY, string saveKey, int color) : base(map)
{
if (!string.IsNullOrEmpty(saveKey) &&
Game1.GameManager.SaveManager.GetString(saveKey) == "1")
{
IsDead = true;
return;
}
EntityPosition = new CPosition(posX + 16, posY + 32, 0);
EntitySize = new Rectangle(-16, -32, 32, 32);
_saveKey = saveKey;
_animator = AnimatorSaveLoad.LoadAnimator("MidBoss/hinox");
_animator.Play("idle_0");
color = MathHelper.Clamp(color, 0, _colors.Length - 1);
var sprite = new CSprite(EntityPosition) { SpriteShader = Resources.ColorShader, Color = _colors[color] };
var animationComponent = new AnimationComponent(_animator, sprite, Vector2.Zero);
_body = new BodyComponent(EntityPosition, -14, -20, 28, 20, 8)
{
IgnoreHoles = true,
FieldRectangle = Map.GetField(posX, posY, 8)
};
_aiComponent = new AiComponent();
var stateIdle = new AiState(UpdateIdle);
var stateWait = new AiState(UpdateWait) { Init = InitWait };
stateWait.Trigger.Add(new AiTriggerRandomTime(WalkOrRun, 1000, 1500));
var stateWalk = new AiState { Init = InitWalking };
stateWalk.Trigger.Add(new AiTriggerRandomTime(EndWalking, 750, 1250));
var statePreRun = new AiState(UpdatePreRun) { Init = InitPreRun };
statePreRun.Trigger.Add(new AiTriggerCountdown(750, null, () => _aiComponent.ChangeState("run")));
var stateRun = new AiState(UpdateRunning) { Init = InitRun };
stateRun.Trigger.Add(new AiTriggerRandomTime(EndRun, 500, 750));
var stateThrowLink = new AiState();
var stateThrowBomb = new AiState() { Init = InitThrowBomb };
stateThrowBomb.Trigger.Add(new AiTriggerCountdown(250, null, ThrowBomb));
var stateThrownBomb = new AiState(UpdateThrownBomb);
var stateGrab = new AiState { Init = InitGrab };
stateGrab.Trigger.Add(new AiTriggerCountdown(GrabTime, GrabTick, () => _aiComponent.ChangeState("grabbed")));
var stateGrabbed = new AiState();
stateGrabbed.Trigger.Add(new AiTriggerCountdown(500, null, () => _aiComponent.ChangeState("throw")));
var stateThrow = new AiState { Init = InitThrow };
stateThrow.Trigger.Add(new AiTriggerCountdown(600, null, () => _aiComponent.ChangeState("walk")));
_aiComponent.States.Add("idle", stateIdle);
_aiComponent.States.Add("wait", stateWait);
_aiComponent.States.Add("walk", stateWalk);
_aiComponent.States.Add("preRun", statePreRun);
_aiComponent.States.Add("run", stateRun);
_aiComponent.States.Add("throwLink", stateThrowLink);
_aiComponent.States.Add("throwBomb", stateThrowBomb);
_aiComponent.States.Add("thrownBomb", stateThrownBomb);
_aiComponent.States.Add("grab", stateGrab);
_aiComponent.States.Add("grabbed", stateGrabbed);
_aiComponent.States.Add("throw", stateThrow);
_aiDamageState = new AiDamageState(this, _body, _aiComponent, sprite, Lives, true, false)
{
BossHitSound = true
};
_aiDamageState.AddBossDamageState(OnDeath);
_aiComponent.ChangeState("idle");
_grabBox = new CBox(EntityPosition, -20, -20, 0, 40, 24, 8);
var damageCollider = new CBox(EntityPosition, -14, -24, 0, 28, 24, 8);
var hittableBox = new CBox(EntityPosition, -14, -28, 0, 28, 28, 8);
AddComponent(DamageFieldComponent.Index, _damageFieldComponent = new DamageFieldComponent(damageCollider, HitType.Enemy, 4));
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 DrawShadowCSpriteComponent(sprite));
}
private void UpdateIdle()
{
// player entered the room?
if (_body.FieldRectangle.Contains(MapManager.ObjLink.BodyRectangle))
{
// start boss music
Game1.GameManager.SetMusic(79, 2);
_aiComponent.ChangeState("walk");
}
}
private void InitGrab()
{
MapManager.ObjLink.Stun(2000);
MapManager.ObjLink.StartGrab();
Game1.GameManager.PlaySoundEffect("D370-22-16");
_body.VelocityTarget = Vector2.Zero;
_damageFieldComponent.IsActive = false;
if (MapManager.ObjLink.PosX < EntityPosition.X)
_grabDirection = 1;
else
_grabDirection = -1;
_animator.Pause();
_animator.SetFrame(_grabDirection == 1 ? 0 : 1);
_grabStartPosition = MapManager.ObjLink.EntityPosition.ToVector3();
}
private void GrabTick(double counter)
{
var percentage = 1 - (float)(counter / GrabTime);
var grabEndPosition = new Vector3(EntityPosition.X + 16 * _grabDirection, EntityPosition.Y + 1, 26);
var newPosition = Vector3.Lerp(_grabStartPosition, grabEndPosition, percentage);
MapManager.ObjLink.EntityPosition.Set(newPosition);
}
private void InitThrow()
{
Game1.GameManager.PlaySoundEffect("D360-08-08");
// set the position to be inside of the hinox body to not start throwing the player into a collider
var grabEndPosition = new Vector3(EntityPosition.X + 16 * _grabDirection, EntityPosition.Y, 25);
MapManager.ObjLink.EntityPosition.Set(grabEndPosition);
MapManager.ObjLink.EndGrab();
MapManager.ObjLink.StartThrow(new Vector3(-4.5f * _grabDirection, 2.5f, 0));
Game1.GameManager.InflictDamage(4);
_damageFieldComponent.IsActive = true;
_animator.SetFrame(_grabDirection == 1 ? 1 : 0);
}
private void ContinueAnimation()
{
_animator.Continue();
_animator.SpeedMultiplier = 1.0f;
// set the animation to the next frame
_animator.ResetFrameCounter();
_animator.SetFrame((_animator.CurrentFrameIndex + 1) % _animator.CurrentAnimation.Frames.Length);
}
private void InitWait()
{
_animator.Pause();
_body.VelocityTarget = Vector2.Zero;
}
private void UpdateWait()
{
// player left the room?
if (!_body.FieldRectangle.Contains(MapManager.ObjLink.BodyRectangle))
{
// stop boss music
Game1.GameManager.SetMusic(-1, 2);
_aiComponent.ChangeState("idle");
}
}
private void WalkOrRun()
{
var random = Game1.RandomNumber.Next(0, 2);
_aiComponent.ChangeState(random == 0 ? "preRun" : "walk");
_animator.Play("idle_0");
}
private void InitWalking()
{
ContinueAnimation();
// walk into a random direction
var direction = Game1.RandomNumber.Next(0, 4);
_body.VelocityTarget = AnimationHelper.DirectionOffset[direction] * 0.5f;
}
private void EndWalking()
{
_aiComponent.ChangeState("wait");
}
private void InitPreRun()
{
_animator.Continue();
_animator.SpeedMultiplier = 2.0f;
}
private void UpdatePreRun()
{
Game1.GameManager.PlaySoundEffect("D360-32-20", false);
}
private void InitRun()
{
// run towards the player
var direction = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
if (direction != Vector2.Zero)
direction.Normalize();
_body.VelocityTarget = direction * 1.5f;
}
private void UpdateRunning()
{
// grab the player
if (Game1.GameManager.CurrentHealth > 0 &&
_grabBox.Box.Rectangle().Intersects(MapManager.ObjLink.BodyRectangle))
_aiComponent.ChangeState("grab");
// spawn run particle
_runParticleCount -= Game1.DeltaTime;
if (_runParticleCount <= 0)
{
_runParticleCount = 133;
var animator = new ObjAnimator(Map,
(int)EntityPosition.X, (int)(EntityPosition.Y + 1),
0, -1 - (int)EntityPosition.Z, Values.LayerPlayer, "Particles/run", "spawn", true);
Map.Objects.SpawnObject(animator);
}
}
private void EndRun()
{
_aiComponent.ChangeState("wait");
}
private void InitThrowBomb()
{
_animator.Pause();
_body.VelocityTarget = Vector2.Zero;
}
private void ThrowBomb()
{
var handOffset = _animator.CurrentFrameIndex == 0 ? 8 : -8;
var spawnPosition = new Vector2(EntityPosition.X + handOffset, EntityPosition.Y);
var throwDirection = MapManager.ObjLink.EntityPosition.Position - spawnPosition;
var maxRange = 48f;
var mult = 1.5f;
if (throwDirection.Length() > maxRange)
{
throwDirection.Normalize();
throwDirection *= mult;
}
else
throwDirection = (throwDirection / maxRange) * mult;
// spawn a bomb
var bomb = new ObjBomb(Map, 0, 0, false, true);
bomb.EntityPosition.Set(new Vector3(spawnPosition.X, spawnPosition.Y, 20));
bomb.Body.Velocity = new Vector3(throwDirection.X, throwDirection.Y, 1);
bomb.Body.Gravity = -0.2f;
bomb.Body.Bounciness = 0.25f;
bomb.Body.DragAir = 1.0f;
Map.Objects.SpawnObject(bomb);
Game1.GameManager.PlaySoundEffect("D360-08-08");
// play throw animation
_animator.Play("throw_" + _animator.CurrentFrameIndex);
_aiComponent.ChangeState("thrownBomb");
}
private void UpdateThrownBomb()
{
// finished throw animation?
if (!_animator.IsPlaying)
WalkOrRun();
}
private void OnDeath()
{
if (!string.IsNullOrEmpty(_saveKey))
Game1.GameManager.SaveManager.SetString(_saveKey, "1");
// stop the music
Game1.GameManager.SetMusic(-1, 2);
Game1.GameManager.PlaySoundEffect("D378-26-1A");
// spawns a fairy
Game1.GameManager.PlaySoundEffect("D360-27-1B");
Map.Objects.SpawnObject(new ObjDungeonFairy(Map, (int)EntityPosition.X, (int)EntityPosition.Y, 8));
Map.Objects.DeleteObjects.Add(this);
}
private bool OnPush(Vector2 direction, PushableComponent.PushType type)
{
if (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 (_aiComponent.CurrentStateId == "idle")
return Values.HitCollision.None;
if (_aiComponent.CurrentStateId == "throwBomb")
ThrowBomb();
// the boss will throw a bomb right after getting damaged
if (!_aiDamageState.IsInDamageState() &&
_aiComponent.CurrentStateId != "deathBoss" &&
_aiComponent.CurrentStateId != "preRun" &&
_aiComponent.CurrentStateId != "run")
_aiComponent.ChangeState("throwBomb");
if (damageType == HitType.Bow || damageType == HitType.Bomb || damageType == HitType.MagicRod)
damage *= 2;
if (damageType == HitType.Boomerang)
damage = 2;
var hitCollision = _aiDamageState.OnHit(gameObject, direction, damageType, damage, pieceOfPower);
// stop walking and stop the animation when dead
if (_aiDamageState.CurrentLives <= 0)
{
RemoveComponent(HittableComponent.Index);
_animator.Stop();
_body.VelocityTarget = Vector2.Zero;
// make sure to let the player go if he was grabbed
MapManager.ObjLink.EndGrab();
}
return hitCollision;
}
}
}