mirror of
https://github.com/Phantop/LADXHD.git
synced 2024-11-22 14:32:46 +00:00
403 lines
15 KiB
C#
403 lines
15 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.GameObjects.Dungeon;
|
|||
|
using ProjectZ.InGame.Map;
|
|||
|
using ProjectZ.InGame.SaveLoad;
|
|||
|
using ProjectZ.InGame.Things;
|
|||
|
using ProjectZ.Base;
|
|||
|
|
|||
|
namespace ProjectZ.InGame.GameObjects.MidBoss
|
|||
|
{
|
|||
|
internal class MBossSmasher : GameObject
|
|||
|
{
|
|||
|
private MBossSmasherBall _ball;
|
|||
|
|
|||
|
private readonly BodyComponent _body;
|
|||
|
private readonly AiComponent _aiComponent;
|
|||
|
private readonly AiDamageState _damageState;
|
|||
|
private readonly Animator _animator;
|
|||
|
private readonly CubicBezier _pickupCurveX;
|
|||
|
private readonly CubicBezier _pickupCurveY;
|
|||
|
|
|||
|
private readonly RectangleF _triggerRectangle;
|
|||
|
private Vector2 _moveDirection;
|
|||
|
|
|||
|
private readonly string _saveKey;
|
|||
|
|
|||
|
private Vector2 _jumpDirection;
|
|||
|
private Vector2 _pickupStart;
|
|||
|
private const int PickupTime = 500;
|
|||
|
|
|||
|
private const float WalkSpeed = 0.75f;
|
|||
|
private const float CarrySpeed = 0.25f;
|
|||
|
|
|||
|
private int _direction;
|
|||
|
private int _jumpCount;
|
|||
|
|
|||
|
public MBossSmasher(Map.Map map, int posX, int posY, string saveKey) : base(map, "smasher")
|
|||
|
{
|
|||
|
Tags = Values.GameObjectTag.Enemy;
|
|||
|
|
|||
|
EntityPosition = new CPosition(posX + 8, posY + 16, 0);
|
|||
|
EntitySize = new Rectangle(-12, -24, 24, 24);
|
|||
|
|
|||
|
_saveKey = saveKey;
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(_saveKey) &&
|
|||
|
Game1.GameManager.SaveManager.GetString(_saveKey) == "1")
|
|||
|
{
|
|||
|
IsDead = true;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
EntityPosition.AddPositionListener(typeof(MBossSmasher), OnPositionChange);
|
|||
|
|
|||
|
_pickupCurveX = new CubicBezier(100, new Vector2(0.6f, 0.8f), new Vector2(0.7f, 1));
|
|||
|
_pickupCurveY = new CubicBezier(100, new Vector2(0.15f, 0.55f), new Vector2(0.15f, 1));
|
|||
|
|
|||
|
_triggerRectangle = Map.GetField(posX, posY, 16);
|
|||
|
|
|||
|
_animator = AnimatorSaveLoad.LoadAnimator("MidBoss/smasher");
|
|||
|
|
|||
|
var sprite = new CSprite(EntityPosition);
|
|||
|
var animationComponent = new AnimationComponent(_animator, sprite, new Vector2(0, 0));
|
|||
|
|
|||
|
_body = new BodyComponent(EntityPosition, -9, -12, 18, 12, 8)
|
|||
|
{
|
|||
|
MoveCollision = OnCollision,
|
|||
|
Drag = 0.65f,
|
|||
|
DragAir = 0.75f,
|
|||
|
Gravity = -0.125f,
|
|||
|
FieldRectangle = map.GetField(posX, posY)
|
|||
|
};
|
|||
|
|
|||
|
var stateWaiting = new AiState(UpdateWaiting) { Init = InitWaiting };
|
|||
|
var stateWalk = new AiState(UpdateWalk) { Init = InitWalk };
|
|||
|
var statePickup = new AiState { Init = InitPickup };
|
|||
|
statePickup.Trigger.Add(new AiTriggerCountdown(PickupTime, TickPickup, PickupEnd));
|
|||
|
var stateCarry = new AiState(UpdateCarry) { Init = InitCarrying };
|
|||
|
var statePostThrow = new AiState();
|
|||
|
statePostThrow.Trigger.Add(new AiTriggerCountdown(550, null, () => _aiComponent.ChangeState("walk")));
|
|||
|
|
|||
|
_aiComponent = new AiComponent();
|
|||
|
_aiComponent.Trigger.Add(new AiTriggerUpdate(Update));
|
|||
|
|
|||
|
_aiComponent.States.Add("waiting", stateWaiting);
|
|||
|
_aiComponent.States.Add("walk", stateWalk);
|
|||
|
_aiComponent.States.Add("pickup", statePickup);
|
|||
|
_aiComponent.States.Add("carry", stateCarry);
|
|||
|
_aiComponent.States.Add("postThrow", statePostThrow);
|
|||
|
_damageState = new AiDamageState(this, _body, _aiComponent, sprite, 8) { BossHitSound = true, ExplosionOffsetY = 6, OnLiveZeroed = OnLiveZeroed };
|
|||
|
_damageState.AddBossDamageState(RemoveObject);
|
|||
|
|
|||
|
_aiComponent.ChangeState("waiting");
|
|||
|
|
|||
|
var damageCollider = new CBox(EntityPosition, -7, -11, 0, 14, 11, 14, true);
|
|||
|
var hittableBox = new CBox(EntityPosition, -9, -14, 0, 18, 14, 16, true);
|
|||
|
|
|||
|
AddComponent(DamageFieldComponent.Index, new DamageFieldComponent(damageCollider, HitType.Enemy, 4));
|
|||
|
AddComponent(HittableComponent.Index, new HittableComponent(hittableBox, OnHit));
|
|||
|
AddComponent(BodyComponent.Index, _body);
|
|||
|
AddComponent(AiComponent.Index, _aiComponent);
|
|||
|
AddComponent(PushableComponent.Index, new PushableComponent(_body.BodyBox, OnPush));
|
|||
|
AddComponent(BaseAnimationComponent.Index, animationComponent);
|
|||
|
AddComponent(DrawComponent.Index, new BodyDrawComponent(_body, sprite, Values.LayerPlayer));
|
|||
|
AddComponent(DrawShadowComponent.Index, new BodyDrawShadowComponent(_body, sprite) { ShadowWidth = 18, ShadowHeight = 6 });
|
|||
|
|
|||
|
_moveDirection = new Vector2(-1.2f, 0);
|
|||
|
_animator.Play("idle_0");
|
|||
|
|
|||
|
_ball = new MBossSmasherBall(map, new Vector2(EntityPosition.X + 56, EntityPosition.Y + 16));
|
|||
|
map.Objects.SpawnObject(_ball);
|
|||
|
}
|
|||
|
|
|||
|
private void Update()
|
|||
|
{
|
|||
|
// player left?
|
|||
|
if (!_triggerRectangle.Contains(MapManager.ObjLink.BodyRectangle) &&
|
|||
|
_aiComponent.CurrentStateId == "walk" && _body.IsGrounded)
|
|||
|
{
|
|||
|
_aiComponent.ChangeState("waiting");
|
|||
|
|
|||
|
// stop boss music
|
|||
|
Game1.GameManager.SetMusic(-1, 2);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void InitWaiting()
|
|||
|
{
|
|||
|
_body.VelocityTarget = Vector2.Zero;
|
|||
|
}
|
|||
|
|
|||
|
private void UpdateWaiting()
|
|||
|
{
|
|||
|
// awake if the player enters the room
|
|||
|
if (_triggerRectangle.Contains(MapManager.ObjLink.BodyRectangle))
|
|||
|
StartMoving();
|
|||
|
}
|
|||
|
|
|||
|
private void StartMoving()
|
|||
|
{
|
|||
|
_aiComponent.ChangeState("walk");
|
|||
|
|
|||
|
// start boss music
|
|||
|
Game1.GameManager.SetMusic(79, 2);
|
|||
|
}
|
|||
|
|
|||
|
private void InitPickup()
|
|||
|
{
|
|||
|
if (!_ball.InitPickup())
|
|||
|
{
|
|||
|
_aiComponent.ChangeState("walk");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
Game1.GameManager.PlaySoundEffect("D370-28-1C");
|
|||
|
|
|||
|
_pickupStart = new Vector2(_ball.EntityPosition.Position.X, EntityPosition.Y - _ball.EntityPosition.Position.Y);
|
|||
|
_direction = _ball.EntityPosition.Position.X < EntityPosition.X ? 0 : 1;
|
|||
|
_animator.Play("up_" + _direction);
|
|||
|
}
|
|||
|
|
|||
|
private void TickPickup(double countdownState)
|
|||
|
{
|
|||
|
var ballTargetPosition = new Vector2(EntityPosition.X, 15);
|
|||
|
|
|||
|
// the ball gets picked up in a curved way
|
|||
|
var percentage = (float)((PickupTime - countdownState) / PickupTime);
|
|||
|
var percentageX = _pickupCurveX.EvaluateX(percentage);
|
|||
|
var percentageY = _pickupCurveY.EvaluateX(percentage);
|
|||
|
var newBallPosition = new Vector2(
|
|||
|
MathHelper.Lerp(_pickupStart.X, ballTargetPosition.X, percentageX),
|
|||
|
MathHelper.Lerp(_pickupStart.Y, ballTargetPosition.Y, percentageY));
|
|||
|
|
|||
|
_ball.EntityPosition.Set(new Vector3(newBallPosition.X, EntityPosition.Y + 1, newBallPosition.Y));
|
|||
|
}
|
|||
|
|
|||
|
private void PickupEnd()
|
|||
|
{
|
|||
|
_ball.EntityPosition.Set(new Vector3(EntityPosition.X, EntityPosition.Y + 1, 15));
|
|||
|
_aiComponent.ChangeState("carry");
|
|||
|
}
|
|||
|
|
|||
|
private void InitWalk()
|
|||
|
{
|
|||
|
_jumpCount = 0;
|
|||
|
}
|
|||
|
|
|||
|
private void UpdateWalk()
|
|||
|
{
|
|||
|
if (_body.Velocity.Z < 0)
|
|||
|
_animator.Play("idle_" + _direction);
|
|||
|
|
|||
|
if (_body.IsGrounded)
|
|||
|
{
|
|||
|
// jump towards the ball if the player is not already carrying it
|
|||
|
if (_ball.IsAvailable())
|
|||
|
JumpTowardsBall();
|
|||
|
else
|
|||
|
JumpRandom();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void JumpRandom()
|
|||
|
{
|
|||
|
// change direction?
|
|||
|
if (_jumpCount <= 0)
|
|||
|
{
|
|||
|
_jumpCount = Game1.RandomNumber.Next(2, 3);
|
|||
|
var dirX = Game1.RandomNumber.Next(0, 2) * 2 - 1;
|
|||
|
var dirY = Game1.RandomNumber.Next(0, 2) * 2 - 1;
|
|||
|
_jumpDirection = new Vector2(dirX, dirY * 0.5f);
|
|||
|
}
|
|||
|
|
|||
|
Jump(_jumpDirection, "up_");
|
|||
|
_jumpCount--;
|
|||
|
}
|
|||
|
|
|||
|
private void JumpTowardsBall()
|
|||
|
{
|
|||
|
// jump toward the ball or pick him up if we are close enough
|
|||
|
var targetPosition = new Vector2(_ball.EntityPosition.X, _ball.EntityPosition.Y);
|
|||
|
if (EntityPosition.Position.X < _ball.EntityPosition.X)
|
|||
|
{
|
|||
|
// need to make sure that the target position is not inside the wall where the boss can not reach it
|
|||
|
var offset = Math.Clamp(14 + _body.Width / 2 - (_ball.EntityPosition.X - (_body.FieldRectangle.X + 16)), 0, 32);
|
|||
|
targetPosition.X -= 14 - offset;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// need to make sure that the target position is not inside the wall where the boss can not reach it
|
|||
|
var offset = Math.Clamp(14 + _body.Width / 2 - ((_body.FieldRectangle.Right - 16) - _ball.EntityPosition.X), 0, 32);
|
|||
|
targetPosition.X += 14 - offset;
|
|||
|
}
|
|||
|
|
|||
|
var ballDirection = targetPosition - EntityPosition.Position;
|
|||
|
|
|||
|
if (ballDirection.Length() > 5)
|
|||
|
{
|
|||
|
ballDirection.Normalize();
|
|||
|
Jump(ballDirection, "idle_");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_aiComponent.ChangeState("pickup");
|
|||
|
_body.VelocityTarget = Vector2.Zero;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void Jump(Vector2 direction, string animationName)
|
|||
|
{
|
|||
|
_direction = direction.X < 0 ? 0 : 1;
|
|||
|
_animator.Play(animationName + _direction);
|
|||
|
|
|||
|
_body.VelocityTarget = direction * WalkSpeed;
|
|||
|
_body.Velocity = new Vector3(0, 0, 0.8f);
|
|||
|
}
|
|||
|
|
|||
|
private void OnPositionChange(CPosition newPosition)
|
|||
|
{
|
|||
|
if (_aiComponent.CurrentStateId != "carry")
|
|||
|
return;
|
|||
|
|
|||
|
// set the position of the ball if it is carried
|
|||
|
_ball.EntityPosition.Set(new Vector3(newPosition.X, newPosition.Y + 1, newPosition.Z + 15));
|
|||
|
}
|
|||
|
|
|||
|
private void InitCarrying()
|
|||
|
{
|
|||
|
_jumpCount = 0;
|
|||
|
}
|
|||
|
|
|||
|
private void UpdateCarry()
|
|||
|
{
|
|||
|
// start throwing
|
|||
|
if (_jumpCount > 2 && _body.Velocity.Z < 0)
|
|||
|
{
|
|||
|
ThrowBall();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (_body.IsGrounded)
|
|||
|
{
|
|||
|
_jumpCount++;
|
|||
|
|
|||
|
// jump toward the player
|
|||
|
var ballDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
|
|||
|
|
|||
|
if (ballDirection.Length() > 5)
|
|||
|
{
|
|||
|
ballDirection.Normalize();
|
|||
|
_direction = ballDirection.X < 0 ? 0 : 1;
|
|||
|
_animator.Play("up_" + _direction);
|
|||
|
|
|||
|
_body.VelocityTarget = ballDirection * CarrySpeed;
|
|||
|
_body.Velocity = new Vector3(0, 0, 0.8f);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ThrowBall()
|
|||
|
{
|
|||
|
_aiComponent.ChangeState("postThrow");
|
|||
|
|
|||
|
Game1.GameManager.PlaySoundEffect("D360-08-08");
|
|||
|
|
|||
|
_animator.Play("idle_" + _direction);
|
|||
|
_body.Velocity = new Vector3(0, 0, 1.75f);
|
|||
|
|
|||
|
// throw towards the player; scale the throw speed depending on the distance of the player
|
|||
|
var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
|
|||
|
var playerDistance = playerDirection.Length();
|
|||
|
if (playerDistance > 0)
|
|||
|
playerDirection.Normalize();
|
|||
|
playerDirection *= Math.Clamp(playerDistance / 24, 0, 2.5f);
|
|||
|
|
|||
|
var throwDirection = new Vector3(playerDirection, 1.5f);
|
|||
|
_ball.Throw(throwDirection);
|
|||
|
}
|
|||
|
|
|||
|
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 (_damageState.IsInDamageState())
|
|||
|
return Values.HitCollision.None;
|
|||
|
|
|||
|
if (_aiComponent.CurrentStateId == "carry")
|
|||
|
{
|
|||
|
_ball.EndPickup();
|
|||
|
_animator.Play("idle_" + _direction);
|
|||
|
_aiComponent.ChangeState("walk");
|
|||
|
}
|
|||
|
|
|||
|
// ball was thrown at the boss
|
|||
|
if ((damageType & HitType.ThrownObject) != 0)
|
|||
|
{
|
|||
|
_damageState.OnHit(gameObject, direction, damageType, damage, pieceOfPower);
|
|||
|
_body.VelocityTarget = Vector2.Zero;
|
|||
|
}
|
|||
|
// only knock the boss back
|
|||
|
else if (_aiComponent.CurrentStateId != "pickup")
|
|||
|
{
|
|||
|
_damageState.HitKnockBack(gameObject, direction, damageType, pieceOfPower, false);
|
|||
|
}
|
|||
|
|
|||
|
return Values.HitCollision.RepellingParticle;
|
|||
|
}
|
|||
|
|
|||
|
private void OnCollision(Values.BodyCollision direction)
|
|||
|
{
|
|||
|
if (_aiComponent.CurrentStateId == "jumping" && (direction & Values.BodyCollision.Horizontal) != 0 &&
|
|||
|
Math.Sign(_body.Velocity.X) == Math.Sign(_moveDirection.X))
|
|||
|
{
|
|||
|
_aiComponent.ChangeState("pushing");
|
|||
|
_moveDirection.X = -_moveDirection.X;
|
|||
|
_body.Velocity.X = -_body.Velocity.X * 0.125f;
|
|||
|
_animator.Play("idle_" + (_moveDirection.X < 0 ? 0 : 1));
|
|||
|
}
|
|||
|
|
|||
|
// landed after a jump?
|
|||
|
if ((direction & Values.BodyCollision.Floor) != 0)
|
|||
|
{
|
|||
|
if (_aiComponent.CurrentStateId == "jumping")
|
|||
|
_aiComponent.ChangeState("idle");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void OnLiveZeroed()
|
|||
|
{
|
|||
|
// destroy the ball
|
|||
|
if (_ball != null)
|
|||
|
{
|
|||
|
_ball.Destroy();
|
|||
|
_ball = null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void RemoveObject()
|
|||
|
{
|
|||
|
if (!string.IsNullOrEmpty(_saveKey))
|
|||
|
Game1.GameManager.SaveManager.SetString(_saveKey, "1");
|
|||
|
|
|||
|
// stop boss music
|
|||
|
Game1.GameManager.SetMusic(-1, 2);
|
|||
|
|
|||
|
// 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);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|