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

381 lines
14 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.Dungeon;
using ProjectZ.InGame.Map;
using ProjectZ.InGame.SaveLoad;
using ProjectZ.InGame.Things;
namespace ProjectZ.InGame.GameObjects.MidBoss
{
class MBossGiantBuzzBlob : GameObject
{
private readonly Animator _animator;
private readonly BodyComponent _body;
private readonly AiComponent _aiComponent;
private readonly AiDamageState _aiDamageState;
private readonly CSprite _sprite;
private ObjDungeonFairy _dungeonFairy;
private readonly string _saveKey;
private const float WalkSpeed = 0.25f;
private const float JumpSpeed = 0.5f;
// small delay before starting to walk
private float _idleDelayCounter = 250;
private bool _startAttack;
private bool _swordMessage;
private bool _toSlime;
private int _jumpCounter;
private bool _wasHit;
private bool _attackable;
public MBossGiantBuzzBlob() : base("giant buzz blob") { }
public MBossGiantBuzzBlob(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);
_saveKey = saveKey;
// was already killed?
if (!string.IsNullOrEmpty(_saveKey) &&
Game1.GameManager.SaveManager.GetString(_saveKey) == "1")
{
IsDead = true;
return;
}
_animator = AnimatorSaveLoad.LoadAnimator("MidBoss/giant buzz blob");
_animator.Play("floor");
_sprite = new CSprite(EntityPosition);
var animationComponent = new AnimationComponent(_animator, _sprite, Vector2.Zero);
_body = new BodyComponent(EntityPosition, -10, -16, 20, 16, 8)
{
IgnoreHoles = true,
Gravity = -0.095f,
FieldRectangle = Map.GetField(posX, posY, 16)
};
_aiComponent = new AiComponent();
var stateIdle = new AiState(UpdateIdle);
var stateIdleDelay = new AiState(UpdateIdleDelay);
var stateWalk = new AiState(UpdateWalk) { Init = InitWalk };
stateWalk.Trigger.Add(new AiTriggerRandomTime(EndWalk, 300, 1000));
var stateAttack = new AiState { Init = InitAttack };
stateAttack.Trigger.Add(new AiTriggerCountdown(50, null, EndAttack));
var statePreSlime = new AiState();
statePreSlime.Trigger.Add(new AiTriggerCountdown(600, null, () => _aiComponent.ChangeState("toSlime")));
var stateToSlime = new AiState(UpdateToSlime) { Init = InitToSlime };
var stateSlime = new AiState { Init = InitSlime };
stateSlime.Trigger.Add(new AiTriggerCountdown(133, null, EndSlime));
var statePreJump = new AiState(UpdatePreJump) { Init = InitPreJump };
var stateJump = new AiState(UpdateJump) { Init = InitJump };
var statePostJump = new AiState(UpdatePostJump) { Init = InitPostJump };
var stateEndSlime = new AiState(UpdateEndSlime) { Init = InitEndSlime };
var stateDeath = new AiState { Init = InitDeath };
_aiComponent.States.Add("idle", stateIdle);
_aiComponent.States.Add("idleDelay", stateIdleDelay);
_aiComponent.States.Add("walk", stateWalk);
_aiComponent.States.Add("attack", stateAttack);
_aiComponent.States.Add("preSlime", statePreSlime);
_aiComponent.States.Add("toSlime", stateToSlime);
_aiComponent.States.Add("slime", stateSlime);
_aiComponent.States.Add("preJump", statePreJump);
_aiComponent.States.Add("jump", stateJump);
_aiComponent.States.Add("postJump", statePostJump);
_aiComponent.States.Add("endSlime", stateEndSlime);
_aiComponent.States.Add("death", stateDeath);
_aiDamageState = new AiDamageState(this, _body, _aiComponent, _sprite, 6, false, false)
{
HitMultiplierX = 1,
HitMultiplierY = 1,
ExplosionOffsetY = 4,
BossHitSound = true
};
_aiDamageState.AddBossDamageState(OnDeathAnimationEnd);
_aiDamageState.DamageSpriteShader = Resources.DamageSpriteShader1;
_aiComponent.ChangeState("idle");
var damageBox = new CBox(EntityPosition, -8, -28, 0, 16, 28, 8, false);
var hittableBox = new CBox(EntityPosition, -8, -28, 0, 16, 28, 8, false);
AddComponent(DamageFieldComponent.Index, new DamageFieldComponent(damageBox, 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 BodyDrawShadowComponent(_body, _sprite) { ShadowWidth = 14, ShadowHeight = 5 });
}
private void UpdateIdle()
{
if (_body.FieldRectangle.Contains(MapManager.ObjLink.BodyRectangle))
{
Game1.GameManager.StartDialogPath("giant_buzz_blob_enter");
_aiComponent.ChangeState("idleDelay");
}
}
private void UpdateIdleDelay()
{
if (Game1.GameManager.DialogIsRunning())
return;
_idleDelayCounter -= Game1.DeltaTime;
if (0 < _idleDelayCounter)
return;
_aiComponent.ChangeState("endSlime");
}
private void InitWalk()
{
if (_toSlime)
return;
_animator.Play("walk");
var rotation = Game1.RandomNumber.Next(0, 628) / 100f;
var direction = new Vector2(MathF.Sin(rotation), MathF.Cos(rotation));
_body.VelocityTarget = direction * WalkSpeed;
}
private void UpdateWalk()
{
// spawn a fairy carrying powder for the player?
var powder = Game1.GameManager.GetItem("powder");
if ((powder == null || powder.Count <= 0) && (_dungeonFairy == null || !_dungeonFairy.IsActive))
{
_dungeonFairy = new ObjDungeonFairy(Map, (int)EntityPosition.X, (int)EntityPosition.Y, 32, "powder_10");
Map.Objects.SpawnObject(_dungeonFairy);
}
// make sure to be on the straight frame when attacking
if (!_toSlime && _startAttack && _animator.CurrentFrameIndex % 2 == 0 && _animator.FrameCounter >= 50)
{
_startAttack = false;
_aiComponent.ChangeState("attack");
}
if (_toSlime && _animator.CurrentFrameIndex % 2 == 0)
{
_toSlime = false;
_jumpCounter = 0;
_animator.Pause();
_aiComponent.ChangeState("preSlime");
}
}
private void EndWalk()
{
if (Game1.RandomNumber.Next(0, 3) == 0)
_startAttack = true;
else
_aiComponent.ChangeState("walk");
}
private void InitAttack()
{
_body.VelocityTarget = Vector2.Zero;
_sprite.SpriteShader = Resources.DamageSpriteShader1;
// spawn buzz
var direction = Game1.RandomNumber.Next(0, 2);
var spawnOrigin = new Vector2(EntityPosition.X, EntityPosition.Y - 14);
for (var i = 0; i < 4; i++)
{
var rotation = MathF.PI / 2 * i + direction * MathF.PI / 4;
var offset = new Vector2(-MathF.Cos(rotation), MathF.Sin(rotation));
var objBuzz = new MBossBuzz(Map, new Vector2(spawnOrigin.X + offset.X * 20, spawnOrigin.Y + offset.Y * 20), offset, "buzz_" + direction, MathF.PI / 2 * i);
Map.Objects.SpawnObject(objBuzz);
}
}
private void EndAttack()
{
_sprite.SpriteShader = null;
_aiComponent.ChangeState("walk");
}
private void InitEndSlime()
{
_attackable = false;
_wasHit = false;
_animator.Play("deslime");
}
private void UpdateEndSlime()
{
if (!_animator.IsPlaying)
_aiComponent.ChangeState("walk");
}
private void InitToSlime()
{
_attackable = true;
_animator.Play("slime");
}
private void UpdateToSlime()
{
if (!_animator.IsPlaying)
_aiComponent.ChangeState("slime");
}
private void InitSlime()
{
_animator.Play("floor");
}
private void EndSlime()
{
if (_jumpCounter < 3 && (!_wasHit || _jumpCounter == 0))
_aiComponent.ChangeState("preJump");
else
_aiComponent.ChangeState("endSlime");
_jumpCounter++;
}
private void InitPreJump()
{
_animator.Play("jump");
}
private void UpdatePreJump()
{
if (!_animator.IsPlaying)
_aiComponent.ChangeState("jump");
}
private void InitJump()
{
_animator.Play("fly");
_body.Velocity.Z = 2.5f;
// move towards the player
var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
if (playerDirection != Vector2.Zero)
playerDirection.Normalize();
_body.VelocityTarget = playerDirection * JumpSpeed;
}
private void UpdateJump()
{
if (_body.IsGrounded)
_aiComponent.ChangeState("postJump");
}
private void InitPostJump()
{
_body.VelocityTarget = Vector2.Zero;
_animator.Play("land");
}
private void UpdatePostJump()
{
if (!_animator.IsPlaying)
_aiComponent.ChangeState("slime");
}
private void InitDeath()
{
_body.VelocityTarget = Vector2.Zero;
_animator.Play("land");
}
private void OnDeathAnimationEnd()
{
if (!string.IsNullOrEmpty(_saveKey))
Game1.GameManager.SaveManager.SetString(_saveKey, "1");
// stop boss 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 (_aiDamageState.CurrentLives <= 0 || _aiDamageState.IsInDamageState())
return Values.HitCollision.None;
if (damageType == HitType.MagicPowder)
damage = 1;
if (damageType == HitType.Boomerang)
damage = 2;
if (_attackable)
{
_wasHit = true;
var hit = _aiDamageState.OnHit(gameObject, direction, damageType, damage, false);
if (_aiDamageState.CurrentLives <= 0)
_aiComponent.ChangeState("death");
return hit;
}
_body.Velocity.X = direction.X;
_body.Velocity.Y = direction.Y;
// show initial message telling the player that the sword is useless
if (!_swordMessage && (damageType & HitType.Sword) != 0)
{
_swordMessage = true;
Game1.GameManager.StartDialogPath("giant_buzz_blob_sword");
}
if (damageType == HitType.MagicPowder && _aiComponent.CurrentStateId == "walk")
{
// do not show the sword message after the player has already figured out that there can be something done with the powder
_swordMessage = true;
_toSlime = true;
_body.VelocityTarget = Vector2.Zero;
var hit = _aiDamageState.OnHit(gameObject, direction, damageType, damage, false);
if (_aiDamageState.CurrentLives <= 0)
_aiComponent.ChangeState("death");
return hit;
}
if (_aiComponent.CurrentStateId == "walk")
{
_aiDamageState.SetDamageState(false);
Game1.GameManager.PlaySoundEffect("D370-07-07");
return Values.HitCollision.Repelling | Values.HitCollision.Repelling0;
}
return Values.HitCollision.Enemy;
}
}
}