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