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

354 lines
14 KiB
C#

using System;
using Microsoft.Xna.Framework;
using ProjectZ.InGame.GameObjects.Base;
using ProjectZ.InGame.GameObjects.Base.Components;
using ProjectZ.InGame.GameObjects.Base.CObjects;
using ProjectZ.InGame.GameObjects.Base.Components.AI;
using ProjectZ.InGame.Map;
using ProjectZ.InGame.SaveLoad;
using ProjectZ.InGame.Things;
using ProjectZ.InGame.GameObjects.Things;
namespace ProjectZ.InGame.GameObjects.Enemies
{
internal class EnemyVire : GameObject
{
private readonly EnemyVireBat _batLeft;
private readonly EnemyVireBat _batRight;
private readonly BodyComponent _body;
private readonly AiComponent _aiComponent;
private readonly Animator _animator;
private readonly AiDamageState _damageState;
private readonly Rectangle _roomRectangle;
private readonly Vector2 _roomCenter;
private const float DashSpeed = 2.0f;
private const int CircleWidth = 80;
private const int CircleHeight = 60;
private const int FlyHeight = 41;
private Vector2 _targetPosition;
private int _circleDirection;
public EnemyVire() : base("vire") { }
public EnemyVire(Map.Map map, int posX, int posY) : base(map)
{
Tags = Values.GameObjectTag.Enemy;
EntityPosition = new CPosition(posX + 8, posY + 16, 0);
EntitySize = new Rectangle(-12, -64, 24, 64);
_animator = AnimatorSaveLoad.LoadAnimator("Enemies/vire");
_animator.Play("idle");
var sprite = new CSprite(EntityPosition);
var animatorComponent = new AnimationComponent(_animator, sprite, Vector2.Zero);
var fieldRectangle = map.GetField(posX, posY, 8);
_body = new BodyComponent(EntityPosition, -8, -15, 16, 15, 8)
{
IgnoresZ = true,
IgnoreHoles = true,
Gravity = -0.125f,
DragAir = 0.975f,
Bounciness = 0.25f,
CollisionTypes = Values.CollisionTypes.None
};
_roomRectangle = Map.GetField(posX, posY);
_roomCenter = new Vector2(fieldRectangle.Center.X, fieldRectangle.Center.Y);
_aiComponent = new AiComponent();
var stateDebug = new AiState();
var stateIdle = new AiState(UpdateIdle);
var stateFlying = new AiState(UpdateFlying) { Init = InitFlying };
var stateCircling = new AiState(UpdateCircling) { Init = InitCircling };
stateCircling.Trigger.Add(new AiTriggerCountdown(2000, null, RandomAttack));
var statePreAttack = new AiState() { Init = InitPreAttack };
statePreAttack.Trigger.Add(new AiTriggerCountdown(100, null, () => _aiComponent.ChangeState("attack")));
var stateAttack = new AiState(UpdateAttack) { Init = InitAttack };
var statePreDash = new AiState() { Init = InitPreDash };
statePreDash.Trigger.Add(new AiTriggerCountdown(500, null, () => _aiComponent.ChangeState("dash")));
var stateDash = new AiState(UpdateDash) { Init = InitDash };
stateDash.Trigger.Add(new AiTriggerCountdown(750, null, () => _aiComponent.ChangeState("repelled")));
var stateRepelled = new AiState(UpdateRepelled);
stateRepelled.Trigger.Add(new AiTriggerCountdown(750, null, () => _aiComponent.ChangeState("circling")));
_aiComponent.States.Add("debug", stateDebug);
_aiComponent.States.Add("idle", stateIdle);
_aiComponent.States.Add("flying", stateFlying);
_aiComponent.States.Add("circling", stateCircling);
_aiComponent.States.Add("preAttack", statePreAttack);
_aiComponent.States.Add("attack", stateAttack);
_aiComponent.States.Add("preDash", statePreDash);
_aiComponent.States.Add("dash", stateDash);
_aiComponent.States.Add("repelled", stateRepelled);
_damageState = new AiDamageState(this, _body, _aiComponent, sprite, 3) { OnBurn = OnBurn };
new AiFallState(_aiComponent, _body, null, null);
new AiDeepWaterState(_body);
_aiComponent.ChangeState("idle");
var damageBox = new CBox(EntityPosition, -8, -15, 0, 16, 15, 8, true);
var hittableBox = new CBox(EntityPosition, -10, -15, 0, 20, 15, 8, true);
AddComponent(DamageFieldComponent.Index, new DamageFieldComponent(damageBox, HitType.Enemy, 2));
AddComponent(HittableComponent.Index, new HittableComponent(hittableBox, OnHit));
AddComponent(PushableComponent.Index, new PushableComponent(damageBox, OnPush));
AddComponent(AiComponent.Index, _aiComponent);
AddComponent(BodyComponent.Index, _body);
AddComponent(BaseAnimationComponent.Index, animatorComponent);
AddComponent(DrawComponent.Index, new BodyDrawComponent(_body, sprite, Values.LayerPlayer) { WaterOutline = false });
AddComponent(DrawShadowComponent.Index, new BodyDrawShadowComponent(_body, sprite) { ShadowWidth = 10, ShadowHeight = 5 });
_batLeft = new EnemyVireBat(Map, EntityPosition.ToVector3(), new Vector2(-0.75f, 0)) { IsActive = false };
_batRight = new EnemyVireBat(Map, EntityPosition.ToVector3(), new Vector2(0.75f, 0)) { IsActive = false };
Map.Objects.SpawnObject(_batLeft);
Map.Objects.SpawnObject(_batRight);
}
private void UpdateIdle()
{
var distVec = EntityPosition.Position - new Vector2(MapManager.ObjLink.PosX, MapManager.ObjLink.PosY + 16);
// start flying if the player gets near
if (distVec.Length() < 60)
_aiComponent.ChangeState("flying");
}
private void RandomAttack()
{
if (!_roomRectangle.Contains(MapManager.ObjLink.EntityPosition.Position))
return;
if (Game1.RandomNumber.Next(0, 3) == 0)
_aiComponent.ChangeState("preDash");
else
_aiComponent.ChangeState("preAttack");
}
private Values.HitCollision OnHit(GameObject originObject, Vector2 direction, HitType type, int damage, bool pieceOfPower)
{
if (type == HitType.Bow)
damage = 1;
if (_aiComponent.CurrentStateId == "idle")
_aiComponent.ChangeState("flying");
var hitReturn = _damageState.OnHit(originObject, direction, type, damage, pieceOfPower);
if (_damageState.CurrentLives <= 0)
SpawnBats();
return hitReturn;
}
private void SpawnBats()
{
var spawnPosition = new Vector3(EntityPosition.X, EntityPosition.Y, EntityPosition.Z);
// spawn the explosion effect
var splashAnimator = new ObjAnimator(Map, (int)spawnPosition.X - 8,
(int)spawnPosition.Y - (int)spawnPosition.Z - 16, 0, 0, Values.LayerTop, "Particles/spawn", "run", true);
Map.Objects.SpawnObject(splashAnimator);
// spawn the bats
_batLeft.IsActive = true;
_batLeft.EntityPosition.Set(spawnPosition);
_batRight.IsActive = true;
_batRight.EntityPosition.Set(spawnPosition);
Map.Objects.DeleteObjects.Add(this);
}
private void OnBurn()
{
_body.IgnoresZ = false;
_animator.Pause();
}
private void UpdateRepelled()
{
// get repelled by the player
var playerDirection = new Vector2(EntityPosition.X, EntityPosition.Y - EntityPosition.Z) -
MapManager.ObjLink.EntityPosition.Position;
if (playerDirection != Vector2.Zero && playerDirection.Length() < 64)
{
playerDirection.Normalize();
// dodge to the side
var centerDirection = _roomCenter - new Vector2(EntityPosition.X, EntityPosition.Y - EntityPosition.Z);
if (centerDirection != Vector2.Zero)
centerDirection.Normalize();
var moveDirection = new Vector2(centerDirection.Y, -centerDirection.X) * _circleDirection;
_body.VelocityTarget = Vector2.Lerp(_body.VelocityTarget, moveDirection * 2f + playerDirection * 1.5f, 0.025f * Game1.TimeMultiplier);
}
else
{
_body.VelocityTarget = Vector2.Lerp(_body.VelocityTarget, Vector2.Zero, 0.05f * Game1.TimeMultiplier);
}
// move up
if (EntityPosition.Z + 1 * Game1.TimeMultiplier < FlyHeight)
EntityPosition.Z += 1 * Game1.TimeMultiplier;
else
EntityPosition.Z = FlyHeight;
}
private void InitPreDash()
{
_body.VelocityTarget = Vector2.Zero;
_animator.Play("attack");
}
private void InitDash()
{
_animator.Play("fly");
var playerDirection = MapManager.ObjLink.EntityPosition.Position - new Vector2(EntityPosition.X, EntityPosition.Y - 12);
if (playerDirection != Vector2.Zero)
{
playerDirection.Normalize();
_body.VelocityTarget = playerDirection * DashSpeed;
}
}
private void UpdateDash()
{
// move down
if (EntityPosition.Z - 1 * Game1.TimeMultiplier > 12)
EntityPosition.Z -= 1 * Game1.TimeMultiplier;
else
EntityPosition.Z = 12;
}
private void InitPreAttack()
{
_animator.Play("attack");
}
private void InitAttack()
{
var startPosition = new Vector2(EntityPosition.X, EntityPosition.Y - EntityPosition.Z - 8);
var direction = MapManager.ObjLink.EntityPosition.Position - startPosition;
var radiant = MathF.Atan2(direction.Y, direction.X);
var dist = 0.125f;
var vireball0 = new EnemyVireball(Map, startPosition, new Vector2(MathF.Cos(radiant - dist), MathF.Sin(radiant - dist) * 1.5f));
Map.Objects.SpawnObject(vireball0);
var vireball1 = new EnemyVireball(Map, startPosition, new Vector2(MathF.Cos(radiant + dist), MathF.Sin(radiant + dist) * 1.5f));
Map.Objects.SpawnObject(vireball1);
}
private void UpdateAttack()
{
if (!_animator.IsPlaying)
{
_animator.Play("fly");
_aiComponent.ChangeState("circling");
}
}
private void InitFlying()
{
var playerDirection = _roomCenter - MapManager.ObjLink.EntityPosition.Position;
if (playerDirection != Vector2.Zero)
playerDirection.Normalize();
_targetPosition = _roomCenter + new Vector2(playerDirection.X * CircleWidth, playerDirection.Y * CircleHeight + FlyHeight + 16);
}
private void UpdateFlying()
{
_animator.Play("fly");
var reachedTarget = true;
// move towards the target position
var distVec = _targetPosition - EntityPosition.Position;
if (distVec.Length() > 0.5f * Game1.TimeMultiplier)
{
distVec.Normalize();
_body.VelocityTarget = distVec * 0.5f;
reachedTarget = false;
}
else
{
_body.VelocityTarget = Vector2.Zero;
EntityPosition.Set(_targetPosition);
}
// fly up
if (EntityPosition.Z + 0.5f * Game1.TimeMultiplier < FlyHeight)
{
EntityPosition.Z += 0.5f * Game1.TimeMultiplier;
reachedTarget = false;
}
else
{
EntityPosition.Z = FlyHeight;
}
// finished flying up and moving towards the target?
if (reachedTarget)
{
_aiComponent.ChangeState("circling");
}
}
private void InitCircling()
{
_circleDirection = Game1.RandomNumber.Next(0, 1) * 2 - 1;
}
private void UpdateCircling()
{
var playerDirection = new Vector2(EntityPosition.X, EntityPosition.Y - EntityPosition.Z) -
MapManager.ObjLink.EntityPosition.Position;
if (playerDirection.Length() < 40)
{
_aiComponent.ChangeState("repelled");
}
var centerDirection = _roomCenter - new Vector2(EntityPosition.X, EntityPosition.Y - EntityPosition.Z);
if (centerDirection != Vector2.Zero)
centerDirection.Normalize();
var moveDirection = new Vector2(centerDirection.Y, -centerDirection.X) * _circleDirection;
var angle = MathF.Atan2(centerDirection.Y, centerDirection.X);
// distance to the ellipse
var e = MathF.Sqrt(1 - (float)(CircleHeight * CircleHeight) / (CircleWidth * CircleWidth));
var distance = CircleHeight / MathF.Sqrt(1 - MathF.Pow(e * MathF.Cos(angle), 2));
var circleVector = -centerDirection * distance;
var targetPosition = _roomCenter + circleVector;
var entityPosition = new Vector2(EntityPosition.X, EntityPosition.Y - EntityPosition.Z);
var circleDirection = targetPosition - entityPosition;
// slow down to not overshoot while moving towards the circle
if (circleDirection.Length() > 16)
circleDirection.Normalize();
else
circleDirection /= 16;
// move towards the circle/ around the circle
_body.VelocityTarget = Vector2.Lerp(_body.VelocityTarget, moveDirection * 0.5f + circleDirection, 0.1f * Game1.TimeMultiplier);
}
private bool OnPush(Vector2 direction, PushableComponent.PushType type)
{
if (type == PushableComponent.PushType.Impact)
_body.Velocity = new Vector3(direction.X * 1.75f, direction.Y * 1.75f, _body.Velocity.Z);
return true;
}
}
}