mirror of
https://github.com/Phantop/LADXHD.git
synced 2024-11-01 04:14:22 +00:00
383 lines
14 KiB
C#
383 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.Things;
|
|
using ProjectZ.InGame.Map;
|
|
using ProjectZ.InGame.SaveLoad;
|
|
using ProjectZ.InGame.Things;
|
|
|
|
namespace ProjectZ.InGame.GameObjects.NPCs
|
|
{
|
|
public class ObjCock : GameObjectFollower
|
|
{
|
|
private ObjCockParticle _objParticle;
|
|
|
|
private readonly BodyDrawComponent _drawComponent;
|
|
private readonly BodyDrawShadowComponent _shadowCompnent;
|
|
private readonly CarriableComponent _carriableCompnent;
|
|
private readonly BodyComponent _body;
|
|
private readonly AiComponent _aiComponent;
|
|
private readonly Animator _animator;
|
|
private readonly CSprite _sprite;
|
|
|
|
private readonly string _saveKey;
|
|
|
|
private const int CarryHeight = 14;
|
|
|
|
private int _blinkTime;
|
|
private int _direction;
|
|
|
|
private bool _isThrown;
|
|
private bool _slowReturn;
|
|
private bool _freezePlayer;
|
|
private bool _isActive = true;
|
|
|
|
private const int FollowDistance = 18;
|
|
|
|
public ObjCock() : base("cock") { }
|
|
|
|
public ObjCock(Map.Map map, int posX, int posY, string saveKey) : base(map)
|
|
{
|
|
EntityPosition = new CPosition(posX + 8, posY + 16, 0);
|
|
EntitySize = new Rectangle(-8, -16, 16, 16);
|
|
|
|
_saveKey = saveKey;
|
|
// skeleton was already awakend?
|
|
if (_saveKey != null && Game1.GameManager.SaveManager.GetString(_saveKey) == "1")
|
|
{
|
|
IsDead = true;
|
|
return;
|
|
}
|
|
|
|
// TODO_CHECK: must align with the player body
|
|
_body = new BodyComponent(EntityPosition, -4, -10, 8, 10, 8)
|
|
{
|
|
Bounciness = 0f,
|
|
Gravity = -0.075f,
|
|
Drag = 0.85f,
|
|
IsSlider = true,
|
|
CollisionTypes = Values.CollisionTypes.None,
|
|
};
|
|
|
|
_animator = AnimatorSaveLoad.LoadAnimator("NPCs/cock");
|
|
_animator.Play("stand_3");
|
|
|
|
_sprite = new CSprite(EntityPosition);
|
|
var animationComponent = new AnimationComponent(_animator, _sprite, Vector2.Zero);
|
|
|
|
// blink for ~1000ms
|
|
_blinkTime = (1000 / AiDamageState.BlinkTime) * AiDamageState.BlinkTime;
|
|
|
|
var stateSkeleton = new AiState();
|
|
var stateParticle = new AiState(UpdateParticle) { Init = InitParticle };
|
|
var stateBlinking = new AiState();
|
|
stateBlinking.Trigger.Add(new AiTriggerCountdown(_blinkTime, TickBlink, EndBlink));
|
|
var statePreSpawn = new AiState();
|
|
statePreSpawn.Trigger.Add(new AiTriggerCountdown(1100, null, ToSpawn));
|
|
var stateSpawn = new AiState();
|
|
stateSpawn.Trigger.Add(new AiTriggerCountdown(750, null, StartFollowing));
|
|
// buffer state to not be one frame into a jump while showing the textbox
|
|
var statePreFollowing = new AiState();
|
|
statePreFollowing.Trigger.Add(new AiTriggerCountdown(100, null, EndPreFollowing));
|
|
var stateFollowing = new AiState(UpdateWalking) { Init = InitWalk };
|
|
var stateThrown = new AiState(UpdateThrown);
|
|
var statePickedUp = new AiState(UpdatePickedUp);
|
|
|
|
_aiComponent = new AiComponent();
|
|
_aiComponent.States.Add("skeleton", stateSkeleton);
|
|
_aiComponent.States.Add("particle", stateParticle);
|
|
_aiComponent.States.Add("blinking", stateBlinking);
|
|
_aiComponent.States.Add("preSpawn", statePreSpawn);
|
|
_aiComponent.States.Add("spawn", stateSpawn);
|
|
_aiComponent.States.Add("preFollowing", statePreFollowing);
|
|
_aiComponent.States.Add("following", stateFollowing);
|
|
_aiComponent.States.Add("thrown", stateThrown);
|
|
_aiComponent.States.Add("pickedUp", statePickedUp);
|
|
|
|
AddComponent(CarriableComponent.Index, _carriableCompnent = new CarriableComponent(
|
|
new CRectangle(EntityPosition, new Rectangle(-6, -14, 12, 14)), CarryInit, CarryUpdate, CarryThrow)
|
|
{ CarryHeight = CarryHeight });
|
|
AddComponent(BodyComponent.Index, _body);
|
|
AddComponent(AiComponent.Index, _aiComponent);
|
|
AddComponent(BaseAnimationComponent.Index, animationComponent);
|
|
AddComponent(OcarinaListenerComponent.Index, new OcarinaListenerComponent(OnSongPlayed));
|
|
AddComponent(CollisionComponent.Index, new BoxCollisionComponent(new CBox(EntityPosition, -8, -16, 16, 16, 8), Values.CollisionTypes.Normal));
|
|
AddComponent(UpdateComponent.Index, new UpdateComponent(Update));
|
|
AddComponent(DrawComponent.Index, _drawComponent = new BodyDrawComponent(_body, _sprite, Values.LayerBottom));
|
|
AddComponent(DrawShadowComponent.Index, _shadowCompnent = new BodyDrawShadowComponent(_body, _sprite) { IsActive = false });
|
|
|
|
// no saveKey => spawned by the player in the following state
|
|
if (_saveKey == null)
|
|
{
|
|
ToActiveState();
|
|
_aiComponent.ChangeState("following");
|
|
}
|
|
else
|
|
{
|
|
_animator.Play("skeleton");
|
|
_aiComponent.ChangeState("skeleton");
|
|
}
|
|
}
|
|
|
|
public override void SetPosition(Vector2 position)
|
|
{
|
|
EntityPosition.Set(position);
|
|
}
|
|
|
|
private void SetActive(bool isActive)
|
|
{
|
|
_isActive = isActive;
|
|
_drawComponent.IsActive = isActive;
|
|
_shadowCompnent.IsActive = isActive;
|
|
_carriableCompnent.IsActive = isActive;
|
|
}
|
|
|
|
private void OnSongPlayed(int songIndex)
|
|
{
|
|
if (songIndex == 2 && _aiComponent.CurrentStateId == "skeleton")
|
|
_aiComponent.ChangeState("particle");
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
// do not follow the player into dungeons
|
|
if (Map.DungeonMode && _isActive)
|
|
SetActive(false);
|
|
if (!Map.DungeonMode && !_isActive)
|
|
SetActive(true);
|
|
|
|
if (_freezePlayer)
|
|
MapManager.ObjLink.FreezePlayer();
|
|
}
|
|
|
|
private void ToActiveState()
|
|
{
|
|
((DrawComponent)Components[DrawComponent.Index]).Layer = Values.LayerPlayer;
|
|
((BodyDrawShadowComponent)Components[DrawShadowComponent.Index]).IsActive = true;
|
|
RemoveComponent(CollisionComponent.Index);
|
|
}
|
|
|
|
private void InitParticle()
|
|
{
|
|
_freezePlayer = true;
|
|
|
|
Game1.GameManager.SetMusic(84, 2);
|
|
|
|
// spawn the particle
|
|
_objParticle = new ObjCockParticle(Map, new Vector2(EntityPosition.X, EntityPosition.Y - 8));
|
|
Map.Objects.SpawnObject(_objParticle);
|
|
}
|
|
|
|
private void UpdateParticle()
|
|
{
|
|
// start blinking when the particle hits the skeleton
|
|
if (!_objParticle.IsRunning())
|
|
_aiComponent.ChangeState("blinking");
|
|
}
|
|
|
|
private void TickBlink(double time)
|
|
{
|
|
_sprite.SpriteShader = ((_blinkTime - time) % (AiDamageState.BlinkTime * 2) < AiDamageState.BlinkTime) ? Resources.DamageSpriteShader0 : null;
|
|
}
|
|
|
|
private void EndBlink()
|
|
{
|
|
_sprite.SpriteShader = null;
|
|
_aiComponent.ChangeState("preSpawn");
|
|
}
|
|
|
|
private void ToSpawn()
|
|
{
|
|
// explosion
|
|
_animator.Play("spawn");
|
|
ToActiveState();
|
|
|
|
Game1.GameManager.PlaySoundEffect("D378-12-0C");
|
|
Game1.GameManager.SetMusic(-1, 2);
|
|
|
|
// spawn explosion effect
|
|
var objAnimation = new ObjAnimator(Map, (int)EntityPosition.X, (int)EntityPosition.Y - 8, Values.LayerTop, "Particles/explosionBomb", "run", true);
|
|
Map.Objects.SpawnObject(objAnimation);
|
|
|
|
_aiComponent.ChangeState("spawn");
|
|
}
|
|
|
|
private void StartFollowing()
|
|
{
|
|
Game1.GameManager.PlaySoundEffect("D368-16-10");
|
|
|
|
// add the rooster as a follower
|
|
var itemRooster = new GameItemCollected("rooster") { Count = 1 };
|
|
MapManager.ObjLink.PickUpItem(itemRooster, false);
|
|
|
|
Game1.GameManager.SaveManager.SetString(_saveKey, "1");
|
|
|
|
_aiComponent.ChangeState("preFollowing");
|
|
}
|
|
|
|
private void EndPreFollowing()
|
|
{
|
|
_freezePlayer = false;
|
|
_animator.Play("stand_3");
|
|
_aiComponent.ChangeState("following");
|
|
}
|
|
|
|
private void InitWalk()
|
|
{
|
|
SetThrowState(false);
|
|
}
|
|
|
|
private void UpdateWalking()
|
|
{
|
|
var playerDirection = MapManager.ObjLink.EntityPosition.Position - EntityPosition.Position;
|
|
var distance = playerDirection.Length();
|
|
var playerSpeed = MapManager.ObjLink.LastMoveVector.Length();
|
|
|
|
// slowly transition to the full speed
|
|
var movementSpeed = MathHelper.Clamp((distance - FollowDistance) / 4, -2, 2);
|
|
if (Math.Abs(distance - FollowDistance) > FollowDistance + 4)
|
|
movementSpeed = MathHelper.Clamp(distance / (FollowDistance + 4), -2, 2);
|
|
// slowly walk back to the player after been thrown
|
|
if (_slowReturn)
|
|
movementSpeed = MathHelper.Clamp(movementSpeed, playerSpeed, 1);
|
|
|
|
if (movementSpeed > 0 && !_isThrown)
|
|
{
|
|
if (playerDirection != Vector2.Zero)
|
|
playerDirection.Normalize();
|
|
|
|
_body.Velocity.X = playerDirection.X * movementSpeed;
|
|
_body.Velocity.Y = playerDirection.Y * movementSpeed;
|
|
|
|
_direction = AnimationHelper.GetDirection(playerDirection);
|
|
_animator.Play("stand_" + _direction);
|
|
}
|
|
|
|
// stop slow return when we reached the player or the player is moving faster away than we are moving
|
|
if (!_isThrown && (distance <= FollowDistance || playerSpeed > 1))
|
|
_slowReturn = false;
|
|
|
|
// fly over deep water
|
|
if ((_body.CurrentFieldState & MapStates.FieldStates.DeepWater) != 0)
|
|
{
|
|
_body.IsGrounded = false;
|
|
_body.IgnoresZ = true;
|
|
var targetPosZ = 7.5f + MathF.Sin(((float)Game1.TotalGameTime / 1000) * MathF.PI * 2) * 1.5f;
|
|
EntityPosition.Z = AnimationHelper.MoveToTarget(EntityPosition.Z, targetPosZ, 1 * Game1.TimeMultiplier);
|
|
}
|
|
else
|
|
{
|
|
_body.IgnoresZ = false;
|
|
}
|
|
|
|
// jump
|
|
if (_body.IsGrounded)
|
|
{
|
|
var jumpHeight = MathHelper.Clamp(distance / 18, 1, 2);
|
|
// while returning from a throw do not jump high
|
|
if (_slowReturn)
|
|
jumpHeight = 1;
|
|
|
|
_body.Velocity.Z = jumpHeight;
|
|
}
|
|
}
|
|
|
|
public void TargetVelocity(Vector2 targetVelocity, float maxSpeed, int direction)
|
|
{
|
|
// move towards the target velocity
|
|
var target = _body.VelocityTarget + targetVelocity * 0.05f * Game1.TimeMultiplier;
|
|
if (target.Length() > maxSpeed)
|
|
{
|
|
target.Normalize();
|
|
target *= maxSpeed;
|
|
}
|
|
|
|
_body.VelocityTarget = target;
|
|
|
|
_direction = direction;
|
|
_animator.Play("stand_" + _direction);
|
|
}
|
|
|
|
private void UpdatePickedUp()
|
|
{
|
|
if (!MapManager.ObjLink.IsFlying())
|
|
MapManager.ObjLink.StartFlying(this);
|
|
|
|
Game1.GameManager.PlaySoundEffect("D378-45-2D", false);
|
|
|
|
// move up
|
|
var targetPosZ = 36 + MathF.Sin(((float)Game1.TotalGameTime / 450) * MathF.PI * 2) * 1.5f;
|
|
EntityPosition.Z = AnimationHelper.MoveToTarget(EntityPosition.Z, targetPosZ, 0.5f * Game1.TimeMultiplier);
|
|
|
|
// lift the player up
|
|
if (EntityPosition.Z > CarryHeight)
|
|
MapManager.ObjLink.EntityPosition.Z = EntityPosition.Z - CarryHeight;
|
|
}
|
|
|
|
private void UpdateThrown()
|
|
{
|
|
if (_body.IsGrounded)
|
|
{
|
|
_aiComponent.ChangeState("following");
|
|
_body.Velocity.X = 0;
|
|
_body.Velocity.Y = 0;
|
|
}
|
|
}
|
|
|
|
private void SetThrowState(bool thrown)
|
|
{
|
|
_isThrown = thrown;
|
|
_body.DragAir = thrown ? 0.975f : 0.85f;
|
|
}
|
|
|
|
private Vector3 CarryInit()
|
|
{
|
|
_body.IgnoresZ = true;
|
|
_body.Velocity = Vector3.Zero;
|
|
_body.VelocityTarget = Vector2.Zero;
|
|
_body.CollisionTypes = MapManager.ObjLink._body.CollisionTypes;
|
|
|
|
_animator.SpeedMultiplier = 2.0f;
|
|
_aiComponent.ChangeState("pickedUp");
|
|
EntityPosition.AddPositionListener(typeof(ObjCock), OnPositionChange);
|
|
|
|
return new Vector3(EntityPosition.X, EntityPosition.Y, EntityPosition.Z);
|
|
}
|
|
|
|
private bool CarryUpdate(Vector3 position)
|
|
{
|
|
EntityPosition.Set(new Vector3(position.X, position.Y, position.Z));
|
|
return true;
|
|
}
|
|
|
|
private void CarryThrow(Vector2 direction)
|
|
{
|
|
_body.Velocity = new Vector3(direction.X, direction.Y, 0);
|
|
|
|
MapManager.ObjLink.StopFlying();
|
|
}
|
|
|
|
public void StopFlying()
|
|
{
|
|
_body.IgnoresZ = false;
|
|
_body.IsGrounded = false;
|
|
_body.VelocityTarget = Vector2.Zero;
|
|
_body.CollisionTypes = Values.CollisionTypes.None;
|
|
|
|
_slowReturn = true;
|
|
SetThrowState(true);
|
|
_animator.SpeedMultiplier = 1.0f;
|
|
_aiComponent.ChangeState("thrown");
|
|
EntityPosition.RemovePositionListener(typeof(ObjCock));
|
|
}
|
|
|
|
private void OnPositionChange(CPosition newPosition)
|
|
{
|
|
if (MapManager.ObjLink.IsFlying())
|
|
MapManager.ObjLink.SetPosition(new Vector2(newPosition.X, newPosition.Y));
|
|
}
|
|
}
|
|
} |