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

431 lines
16 KiB
C#

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
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.Map;
using ProjectZ.InGame.SaveLoad;
using ProjectZ.InGame.Things;
namespace ProjectZ.InGame.GameObjects.NPCs
{
internal class ObjMonkey : GameObject
{
private readonly Animator _animator;
private readonly BodyComponent _body;
private readonly AiComponent _aiComponent;
private readonly AiTriggerSwitch _hitCooldown;
private readonly CSprite _sprite;
private readonly AiTriggerSwitch _waitTimer;
private readonly BodyDrawComponent _drawComponent;
private ObjBowWow _bowWow;
private readonly Vector2 _resetPosition;
private readonly Vector2 _endPosition;
// lives are used to fight with the bowwow
private const int MaxLives = 5;
private int _currentLives = MaxLives;
private int _direction;
private bool _initBusiness;
private const int FadeTime = 150;
private int _directionChangeCounter = 0;
private float _damageCounter;
private const int DamageTime = 400;
public ObjMonkey() : base("monkey") { }
public ObjMonkey(Map.Map map, int posX, int posY) : base(map)
{
EntityPosition = new CPosition(posX + 8, posY + 16, 0);
EntitySize = new Rectangle(-8, -16, 16, 16);
// already build the bridge?
var value = Game1.GameManager.SaveManager.GetString("monkeyBusiness");
if (value == "3")
{
IsDead = true;
return;
}
_resetPosition = EntityPosition.Position;
_body = new BodyComponent(EntityPosition, -6, -10, 12, 10, 8)
{
MoveCollision = OnCollision,
CollisionTypes = Values.CollisionTypes.Normal |
Values.CollisionTypes.NPCWall,
FieldRectangle = map.GetField(posX, posY),
MaxJumpHeight = 4f,
DragAir = 0.99f,
Drag = 0.85f,
Gravity = -0.15f
};
var randomDir = (Game1.RandomNumber.Next(0, 50) / 50.0f) * MathF.PI * 2;
_endPosition = new Vector2(EntityPosition.X + 8, EntityPosition.Y - 24) +
new Vector2(MathF.Sin(randomDir), MathF.Cos(randomDir)) * 150;
_animator = AnimatorSaveLoad.LoadAnimator("NPCs/monkey");
_sprite = new CSprite(EntityPosition);
var animationComponent = new AnimationComponent(_animator, _sprite, new Vector2(-8, -16));
_hitCooldown = new AiTriggerSwitch(250);
_waitTimer = new AiTriggerSwitch(150);
var stateWaiting = new AiState(UpdateWaiting);
var stateSitInit = new AiState();
stateSitInit.Trigger.Add(new AiTriggerRandomTime(ToJump, 125, 1000));
stateSitInit.Trigger.Add(_hitCooldown);
var stateSit = new AiState(UpdateSit);
stateSit.Trigger.Add(_hitCooldown);
stateSit.Trigger.Add(new AiTriggerRandomTime(ToJump, 750, 1500));
var stateJump = new AiState(UpdateJump);
stateJump.Trigger.Add(_hitCooldown);
var stateFlee = new AiState(UpdateFlee) { Init = ToFlee };
var stateFleeSit = new AiState(UpdateFleeSit);
stateFleeSit.Trigger.Add(new AiTriggerRandomTime(() => _aiComponent.ChangeState("flee"), 500, 1000));
var stateReset = new AiState(UpdateReset);
var stateBanana = new AiState(UpdateBusiness);
stateBanana.Trigger.Add(new AiTriggerCountdown(1500, null, ToBusiness));
var stateBusiness = new AiState(UpdateBusiness);
stateBusiness.Trigger.Add(new AiTriggerCountdown(250, null, ChangeDirection));
var stateLeave = new AiState(UpdateLeave);
stateLeave.Trigger.Add(_waitTimer);
var stateFade = new AiState();
stateFade.Trigger.Add(new AiTriggerCountdown(FadeTime, TickFade, () => TickFade(0)));
_aiComponent = new AiComponent();
_aiComponent.States.Add("waiting", stateWaiting);
_aiComponent.States.Add("sitInit", stateSitInit);
_aiComponent.States.Add("sit", stateSit);
_aiComponent.States.Add("jump", stateJump);
_aiComponent.States.Add("flee", stateFlee);
_aiComponent.States.Add("fleeSit", stateFleeSit);
_aiComponent.States.Add("reset", stateReset);
_aiComponent.States.Add("banana", stateBanana);
_aiComponent.States.Add("business", stateBusiness);
_aiComponent.States.Add("leave", stateLeave);
_aiComponent.States.Add("fade", stateFade);
// start by locking into a random direction
_direction = Game1.RandomNumber.Next(0, 2);
_animator.Play("idle_" + _direction);
_aiComponent.ChangeState("waiting");
_drawComponent = new BodyDrawComponent(_body, _sprite, Values.LayerPlayer);
AddComponent(InteractComponent.Index, new InteractComponent(_body.BodyBox, OnInteract));
AddComponent(BodyComponent.Index, _body);
AddComponent(AiComponent.Index, _aiComponent);
AddComponent(BaseAnimationComponent.Index, animationComponent);
AddComponent(HittableComponent.Index, new HittableComponent(_body.BodyBox, OnHit));
AddComponent(CollisionComponent.Index, new BodyCollisionComponent(_body, Values.CollisionTypes.Normal | Values.CollisionTypes.PushIgnore));
AddComponent(DrawComponent.Index, new DrawComponent(Draw, Values.LayerPlayer, EntityPosition));
AddComponent(DrawShadowComponent.Index, new BodyDrawShadowComponent(_body, _sprite));
AddComponent(KeyChangeListenerComponent.Index, new KeyChangeListenerComponent(KeyChanged));
}
private void KeyChanged()
{
var value = Game1.GameManager.SaveManager.GetString("monkeyBusiness");
if (!_initBusiness && value == "1")
{
_initBusiness = true;
ToBanana();
}
else if (_aiComponent.CurrentStateId != "leave" && _aiComponent.CurrentStateId != "fade" && value == "3")
{
ToLeave();
}
}
private bool OnInteract()
{
Game1.GameManager.StartDialogPath("castle_monkey");
return true;
}
private void UpdateWaiting()
{
var distance = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
if (distance.Length() < 24)
{
// start fighting with the bowwow
var bowWowState = Game1.GameManager.SaveManager.GetString("bowWow");
if (bowWowState == "2" || bowWowState == "3")
{
_aiComponent.ChangeState("sit");
Game1.GameManager.StartDialogPath("castle_monkey");
Tags = Values.GameObjectTag.Enemy;
// search the bowwow
_bowWow = (ObjBowWow)Map.Objects.GetObjectOfType(
(int)EntityPosition.X - 80, (int)EntityPosition.Y - 40, 160, 160, typeof(ObjBowWow));
}
}
}
private void ToSit()
{
_aiComponent.ChangeState("sit");
// stop and wait
_body.Velocity = Vector3.Zero;
_animator.Play("idle_" + _direction);
}
private void UpdateSit()
{
DamageTick();
}
private void ToJump()
{
_aiComponent.ChangeState("jump");
Vector2 direction;
// _bowWow should actually never be null...
if (_bowWow != null)
{
if (!_body.FieldRectangle.Contains(_bowWow.EntityPosition.Position))
{
_aiComponent.ChangeState("sit");
return;
}
// jump towards the bowwow
direction = _bowWow.EntityPosition.Position - EntityPosition.Position;
if (direction != Vector2.Zero)
direction.Normalize();
}
else
{
// change the direction
var rotation = Game1.RandomNumber.Next(0, 628) / 100f;
direction = new Vector2(
(float)Math.Sin(rotation),
(float)Math.Cos(rotation)) * Game1.RandomNumber.Next(25, 40) / 50f;
}
_body.Velocity = new Vector3(direction.X * 1.5f, direction.Y * 1.5f, 1.75f);
_direction = direction.X < 0 ? 0 : 1;
_animator.Play("jump_" + _direction);
}
private void UpdateJump()
{
DamageTick();
// finished jumping
if (_body.IsGrounded)
ToSit();
}
private void ToFlee()
{
if (EntityPosition.Y < _resetPosition.Y - 90)
{
_aiComponent.ChangeState("reset");
_animator.Play("idle_" + _direction);
Tags = Values.GameObjectTag.None;
return;
}
_direction = EntityPosition.X < _resetPosition.X ? 1 : 0;
// jump up
_body.Velocity = new Vector3(_direction == 0 ? -0.5f : 0.5f, -1, 2.0f);
_animator.Play("jump_" + _direction);
_body.CollisionTypes = Values.CollisionTypes.None;
}
private void UpdateFlee()
{
DamageTick();
}
private void UpdateFleeSit()
{
_animator.Play("idle_u_" + _direction);
}
private void UpdateReset()
{
var distance = MapManager.ObjLink.EntityPosition.Position - _resetPosition;
// come back to the start position?
if (distance.Length() > 128)
{
_currentLives = MaxLives;
EntityPosition.Set(_resetPosition);
_aiComponent.ChangeState("waiting");
_damageCounter = 0;
_body.CollisionTypes = Values.CollisionTypes.Normal |
Values.CollisionTypes.NPCWall;
}
}
private void ToBanana()
{
_animator.Play("jump_1");
_aiComponent.ChangeState("banana");
Game1.GameManager.PlaySoundEffect("D360-01-01");
}
private void ToBusiness()
{
Game1.GameManager.StartDialogPath("castle_monkey_business");
_aiComponent.ChangeState("business");
_animator.Play("idle_1");
_direction = 1;
}
private void UpdateBusiness()
{
// freeze the player while the big business is happening
MapManager.ObjLink.FreezePlayer();
Game1.GameManager.InGameOverlay.DisableInventoryToggle = true;
}
private void ChangeDirection()
{
if (_directionChangeCounter < 3)
{
_directionChangeCounter++;
_direction = (_direction + 1) % 2;
_animator.Play("idle_" + _direction);
}
// done to reset the direction change trigger
_aiComponent.ChangeState("business");
}
private void ToLeave()
{
_body.CollisionTypes = Values.CollisionTypes.None;
_aiComponent.ChangeState("leave");
}
private void UpdateLeave()
{
if (!_body.IsGrounded || !_waitTimer.State)
return;
var direction = _endPosition - EntityPosition.Position;
var distance = direction.Length();
direction.Normalize();
var strength = Game1.RandomNumber.Next(150, 200) / 100.0f;
_body.Velocity = new Vector3(direction.X * strength, direction.Y * strength, 1.75f);
_direction = direction.X < 0 ? 0 : 1;
_animator.Play("jump_" + _direction);
// start fading away
if (distance < 48)
_aiComponent.ChangeState("fade");
}
private void TickFade(double time)
{
_sprite.Color = Color.White * (float)(time / FadeTime);
// delete the monkey after it is faded away
if (time <= 0)
Map.Objects.DeleteObjects.Add(this);
}
private void DamageTick()
{
if (_damageCounter > 0)
_damageCounter -= Game1.DeltaTime;
_sprite.SpriteShader = _damageCounter % 133 > 66 ? Resources.DamageSpriteShader0 : null;
}
private void Draw(SpriteBatch spriteBatch)
{
_drawComponent.Draw(spriteBatch);
if (_aiComponent.CurrentStateId != "banana")
return;
// draw the banana
var sourceRectangle = Game1.GameManager.ItemManager["trade3"].SourceRectangle;
spriteBatch.Draw(Resources.SprItem, new Vector2(EntityPosition.X - 8, EntityPosition.Y - 30), sourceRectangle, Color.White);
}
private Values.HitCollision OnHit(GameObject gameObject, Vector2 direction, HitType damageType, int damage, bool pieceOfPower)
{
if (!_hitCooldown.State || damageType != HitType.BowWow)
return Values.HitCollision.None;
Game1.GameManager.PlaySoundEffect("D360-03-03");
// fighting with the great bowwow?
_damageCounter = DamageTime;
_currentLives--;
if (_currentLives <= 0)
_aiComponent.ChangeState("flee");
_hitCooldown.Reset();
_body.Velocity.X += direction.X * 4.0f;
_body.Velocity.Y += direction.Y * 4.0f;
return Values.HitCollision.Blocking;
}
private void OnCollision(Values.BodyCollision moveCollision)
{
// finished jumping?
if (_aiComponent.CurrentStateId == "leave" && moveCollision.HasFlag(Values.BodyCollision.Floor))
{
_waitTimer.Reset();
if (_body.Velocity.Y > 0)
_animator.Play("idle_" + _direction);
else
_animator.Play("idle_u_" + _direction);
_body.Velocity = Vector3.Zero;
}
if (_aiComponent.CurrentStateId == "flee" && moveCollision.HasFlag(Values.BodyCollision.Floor))
_aiComponent.ChangeState("fleeSit");
if (_aiComponent.CurrentStateId != "jump")
return;
// repel after wall collision
if (moveCollision.HasFlag(Values.BodyCollision.Horizontal))
_body.Velocity.X *= -0.25f;
else if (moveCollision.HasFlag(Values.BodyCollision.Vertical))
_body.Velocity.Y *= -0.25f;
}
private bool OnPush(Vector2 direction, PushableComponent.PushType type)
{
if (type == PushableComponent.PushType.Impact)
_body.Velocity = new Vector3(direction * 1.25f, _body.Velocity.Z);
return true;
}
}
}