mirror of
https://github.com/Phantop/LADXHD.git
synced 2025-05-06 12:54:28 +00:00
464 lines
17 KiB
C#
464 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
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.Enemies;
|
|
using ProjectZ.InGame.GameObjects.Things;
|
|
using ProjectZ.InGame.Map;
|
|
using ProjectZ.InGame.SaveLoad;
|
|
using ProjectZ.InGame.Things;
|
|
|
|
namespace ProjectZ.InGame.GameObjects.NPCs
|
|
{
|
|
class ObjBowWow : GameObject
|
|
{
|
|
private readonly List<GameObject> _enemyList = new List<GameObject>();
|
|
private GameObject _enemyTarget;
|
|
|
|
private readonly ObjChain _chain;
|
|
|
|
private readonly BodyComponent _body;
|
|
private readonly AiComponent _aiComponent;
|
|
private readonly AiTriggerSwitch _changeDirectionSwitch;
|
|
|
|
private Animator _animator;
|
|
|
|
private Vector2 _chainPull;
|
|
private Vector2 _origin;
|
|
private Vector2 _currentDirectionOffset;
|
|
private Vector2 _treasurePosition;
|
|
|
|
private float _outsideCounter;
|
|
private int _direction;
|
|
private bool _followMode;
|
|
|
|
public ObjBowWow() : base("bowwow") { }
|
|
|
|
public ObjBowWow(Map.Map map, int posX, int posY, string mode) : base(map)
|
|
{
|
|
EntityPosition = new CPosition(posX, posY + 16, 0);
|
|
EntitySize = new Rectangle(-8, -16, 16, 16);
|
|
|
|
_origin = new Vector2(posX + 8, posY + 8);
|
|
|
|
var state = Game1.GameManager.SaveManager.GetString("bowWow");
|
|
|
|
// cave bowwow
|
|
if (mode == "cave")
|
|
{
|
|
// only spawn at the right state
|
|
if (state == "1")
|
|
{
|
|
EntityPosition.Set(new Vector2(EntityPosition.X - 8, EntityPosition.Y));
|
|
_followMode = false;
|
|
}
|
|
else
|
|
{
|
|
IsDead = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// bowwow is in the cave
|
|
if (state == "1")
|
|
IsDead = true;
|
|
// bowwow is following the player
|
|
if (state == "2" || state == "3")
|
|
_followMode = true;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(mode) && state != "2" && state != "3")
|
|
IsDead = true;
|
|
|
|
if (IsDead)
|
|
return;
|
|
|
|
_body = new BodyComponent(EntityPosition, -7, -10, 14, 10, 16)
|
|
{
|
|
MoveCollision = OnCollision,
|
|
Gravity = -0.175f,
|
|
CollisionTypes = Values.CollisionTypes.Normal | Values.CollisionTypes.NPCWall
|
|
};
|
|
|
|
_animator = AnimatorSaveLoad.LoadAnimator("NPCs/BowWow");
|
|
var sprite = new CSprite(EntityPosition);
|
|
var animationComponent = new AnimationComponent(_animator, sprite, new Vector2(-8, -16));
|
|
|
|
var stateIdle = new AiState(UpdateIdle);
|
|
stateIdle.Trigger.Add(new AiTriggerRandomTime(EndIdle, 500, 1500));
|
|
var stateWalking = new AiState(UpdateWalking) { Init = InitWalking };
|
|
stateWalking.Trigger.Add(new AiTriggerRandomTime(EndWalking, 500, 1000));
|
|
stateWalking.Trigger.Add(_changeDirectionSwitch = new AiTriggerSwitch(250));
|
|
var stateAttack = new AiState(UpdateAttack);
|
|
var stateTreasure = new AiState(UpdateTreasure);
|
|
|
|
_aiComponent = new AiComponent();
|
|
_aiComponent.States.Add("idle", stateIdle);
|
|
_aiComponent.States.Add("walking", stateWalking);
|
|
_aiComponent.States.Add("attack", stateAttack);
|
|
_aiComponent.States.Add("treasure", stateTreasure);
|
|
_aiComponent.ChangeState("walking");
|
|
|
|
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 = 12, ShadowHeight = 5 });
|
|
// add key change listener
|
|
AddComponent(KeyChangeListenerComponent.Index, new KeyChangeListenerComponent(KeyChanged));
|
|
|
|
if (!_followMode)
|
|
{
|
|
AddComponent(HittableComponent.Index, new HittableComponent(_body.BodyBox, OnHit));
|
|
}
|
|
|
|
// spawn the chain
|
|
Map.Objects.SpawnObject(_chain = new ObjChain(map, _origin));
|
|
_currentDirectionOffset = AnimationHelper.DirectionOffset[_direction];
|
|
|
|
SetFollowMode(_followMode);
|
|
}
|
|
|
|
private Values.HitCollision OnHit(GameObject originObject, Vector2 direction, HitType type, int damage, bool pieceOfPower)
|
|
{
|
|
return Values.HitCollision.RepellingParticle;
|
|
}
|
|
|
|
public override void Init()
|
|
{
|
|
// set the position to the one of the player
|
|
if (_followMode && MapManager.ObjLink.NextMapPositionEnd.HasValue)
|
|
{
|
|
EntityPosition.Set(MapManager.ObjLink.NextMapPositionEnd.Value);
|
|
_chain.SetChainPosition(MapManager.ObjLink.NextMapPositionEnd.Value);
|
|
}
|
|
}
|
|
|
|
private bool OnPush(Vector2 direction, PushableComponent.PushType type)
|
|
{
|
|
if (type == PushableComponent.PushType.Impact)
|
|
_body.Velocity = new Vector3(direction.X, direction.Y, 0.25f);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void KeyChanged()
|
|
{
|
|
var state = Game1.GameManager.SaveManager.GetString("bowWow");
|
|
if (state != null && state == "2")
|
|
SetFollowMode(true);
|
|
}
|
|
|
|
private void SetFollowMode(bool follow)
|
|
{
|
|
_followMode = follow;
|
|
_body.CollisionTypes = follow ? Values.CollisionTypes.None : (Values.CollisionTypes.Normal | Values.CollisionTypes.NPCWall);
|
|
}
|
|
|
|
private void ToIdle()
|
|
{
|
|
_aiComponent.ChangeState("idle");
|
|
|
|
// stop and wait
|
|
_body.VelocityTarget.X = 0;
|
|
_body.VelocityTarget.Y = 0;
|
|
}
|
|
|
|
private void UpdateIdle()
|
|
{
|
|
UpdatePosition();
|
|
}
|
|
|
|
private void EndIdle()
|
|
{
|
|
_treasurePosition = GetTreasurePosition();
|
|
if (_treasurePosition != Vector2.Zero)
|
|
{
|
|
var direction = _treasurePosition - EntityPosition.Position;
|
|
if (direction != Vector2.Zero)
|
|
direction.Normalize();
|
|
_body.VelocityTarget = direction * 1.5f;
|
|
|
|
// update the animation
|
|
_direction = AnimationHelper.GetDirection(_body.VelocityTarget);
|
|
_animator.Play("walk_" + _direction);
|
|
|
|
_aiComponent.ChangeState("treasure");
|
|
return;
|
|
}
|
|
|
|
if (!_followMode || Game1.RandomNumber.Next(0, 100) < 35)
|
|
_aiComponent.ChangeState("walking");
|
|
else
|
|
ToAttack();
|
|
}
|
|
|
|
private void InitWalking()
|
|
{
|
|
float rotation = 1;
|
|
if (_followMode)
|
|
{
|
|
// the farther away the enemy is from the origin the more likely it becomes that he will move towards the center position
|
|
var origin = MapManager.ObjLink.EntityPosition.Position - new Vector2(0, 4);
|
|
var directionToStart = origin - EntityPosition.Position;
|
|
var radiusToCenter = MathF.Atan2(directionToStart.Y, directionToStart.X);
|
|
|
|
var maxDistanceX = 64.0f;
|
|
var maxDistanceY = 64.0f;
|
|
var distanceMultiplier = MathHelper.Clamp(
|
|
MathF.Min(
|
|
(maxDistanceX - MathF.Abs(directionToStart.X)) / maxDistanceX,
|
|
(maxDistanceY - MathF.Abs(directionToStart.Y)) / maxDistanceY), 0, 1);
|
|
|
|
rotation = radiusToCenter + (MathF.PI - Game1.RandomNumber.Next(0, 628) / 100f) * distanceMultiplier;
|
|
}
|
|
else
|
|
{
|
|
rotation = Game1.RandomNumber.Next(0, 628) / 100f;
|
|
}
|
|
|
|
|
|
// change the direction
|
|
SetWalkDirection(new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)));
|
|
}
|
|
|
|
private void SetWalkDirection(Vector2 direction)
|
|
{
|
|
_body.VelocityTarget = direction * Game1.RandomNumber.Next(25, 40) / 25f;
|
|
|
|
_direction = AnimationHelper.GetDirection(_body.VelocityTarget);
|
|
_animator.Play("walk_" + _direction);
|
|
}
|
|
|
|
private void UpdateWalking()
|
|
{
|
|
if (_body.IsGrounded)
|
|
_body.Velocity.Z = 1.25f;
|
|
|
|
UpdatePosition();
|
|
}
|
|
|
|
private void EndWalking()
|
|
{
|
|
if (!_followMode || Game1.RandomNumber.Next(0, 100) < 35)
|
|
_aiComponent.ChangeState("walking");
|
|
else
|
|
ToAttack();
|
|
|
|
ToIdle();
|
|
}
|
|
|
|
private void ToAttack()
|
|
{
|
|
// search for an enemy to attack
|
|
Map.Objects.GetGameObjectsWithTag(_enemyList, Values.GameObjectTag.Enemy,
|
|
(int)MapManager.ObjLink.EntityPosition.Position.X - 50,
|
|
(int)MapManager.ObjLink.EntityPosition.Position.Y - 50, 100, 100);
|
|
|
|
// choose a random enemy to attack
|
|
if (_enemyList.Count > 0)
|
|
{
|
|
var randomIndex = Game1.RandomNumber.Next(0, _enemyList.Count);
|
|
_enemyTarget = _enemyList[randomIndex];
|
|
|
|
// try finding a goponga flower and attack them first
|
|
for (var i = 0; i < _enemyList.Count; i++)
|
|
{
|
|
if (!_enemyTarget.IsActive)
|
|
_enemyTarget = _enemyList[(randomIndex + i) % _enemyList.Count];
|
|
|
|
if (_enemyTarget is EnemyGopongaFlower || _enemyTarget is EnemyGopongaFlowerGiant)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_enemyTarget != null && _enemyTarget.IsActive)
|
|
{
|
|
// set the attack direction
|
|
var damageState = (HittableComponent)_enemyTarget.Components[HittableComponent.Index];
|
|
var direction = damageState.HittableBox.Box.Center - new Vector2(EntityPosition.X, EntityPosition.Y - 8);
|
|
if (direction != Vector2.Zero)
|
|
direction.Normalize();
|
|
_body.VelocityTarget = direction * 3;
|
|
|
|
// update the animation
|
|
_direction = AnimationHelper.GetDirection(_body.VelocityTarget);
|
|
_animator.Play("walk_" + _direction);
|
|
|
|
_aiComponent.ChangeState("attack");
|
|
|
|
return;
|
|
}
|
|
|
|
_enemyTarget = null;
|
|
|
|
ToIdle();
|
|
}
|
|
|
|
private Vector2 GetTreasurePosition()
|
|
{
|
|
var digPositionX = (int)EntityPosition.X / 16;
|
|
var digPositionY = (int)EntityPosition.Y / 16;
|
|
|
|
for (var y = digPositionY - 3; y < digPositionY + 3; y++)
|
|
{
|
|
if (y < 0 || Map.DigMap.GetLength(1) <= y)
|
|
continue;
|
|
|
|
for (int x = digPositionX - 3; x < digPositionX + 3; x++)
|
|
{
|
|
if (x < 0 || Map.DigMap.GetLength(0) <= x)
|
|
continue;
|
|
|
|
// do not look at the current position
|
|
if (x == digPositionX && y == digPositionY)
|
|
continue;
|
|
|
|
if (Map.HoleMap.ArrayTileMap[x, y, 0] < 0 && Map.DigMap[x, y].Contains(':'))
|
|
{
|
|
// check if the item has already been dug out
|
|
var split = Map.DigMap[x, y].Split(':');
|
|
if (Game1.GameManager.SaveManager.GetString(split[1], "0") != "1")
|
|
return new Vector2(x * 16 + 8, y * 16 + 8);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Vector2.Zero;
|
|
}
|
|
|
|
private void UpdateAttack()
|
|
{
|
|
var damageState = (HittableComponent)_enemyTarget.Components[HittableComponent.Index];
|
|
var direction = damageState.HittableBox.Box.Center - new Vector2(EntityPosition.X, EntityPosition.Y - 8);
|
|
|
|
// attack the enemy if we are close enough
|
|
if (direction.Length() < 5)
|
|
{
|
|
// already removed from the map?
|
|
if (_enemyTarget.Map != null)
|
|
damageState?.Hit(this, _body.VelocityTarget * 0.1f, HitType.BowWow, 8, false);
|
|
|
|
ToIdle();
|
|
}
|
|
|
|
if (_body.VelocityTarget == Vector2.Zero)
|
|
{
|
|
ToIdle();
|
|
}
|
|
|
|
UpdatePosition();
|
|
}
|
|
|
|
private void UpdateTreasure()
|
|
{
|
|
var direction = _treasurePosition - EntityPosition.Position;
|
|
|
|
// move to the treasure
|
|
if (direction.Length() < 5)
|
|
{
|
|
// already removed from the map?
|
|
Game1.GameManager.StartDialogPath("bowWow_dig");
|
|
ToIdle();
|
|
}
|
|
|
|
UpdatePosition();
|
|
}
|
|
|
|
private void UpdatePosition()
|
|
{
|
|
if (_followMode)
|
|
_origin = MapManager.ObjLink.EntityPosition.Position - new Vector2(0, 4);
|
|
|
|
// limit the position
|
|
var distance = (EntityPosition.Position +
|
|
new Vector2(_body.VelocityTarget.X + _body.Velocity.X, _body.VelocityTarget.Y + _body.Velocity.Y) * Game1.TimeMultiplier - new Vector2(0, 4)) - _origin;
|
|
var dist = distance.Length();
|
|
if (dist > 46)
|
|
{
|
|
// chain pull
|
|
var dir = distance;
|
|
dir.Normalize();
|
|
var newPosition = _origin + dir * 46 + new Vector2(0, 4);
|
|
|
|
var direction = newPosition - EntityPosition.Position;
|
|
if (direction.Length() > 0)
|
|
direction.Normalize();
|
|
|
|
var mult = 0.125f + Math.Clamp((dist - 46) / 4, 0, 4);
|
|
_chainPull = direction * mult;
|
|
|
|
if (_followMode)
|
|
{
|
|
// are we moving outside of the range?
|
|
if (dist < (distance + _body.VelocityTarget).Length())
|
|
_body.VelocityTarget = AnimationHelper.MoveToTarget(_body.VelocityTarget, Vector2.Zero, Game1.TimeMultiplier);
|
|
|
|
_outsideCounter -= Game1.DeltaTime;
|
|
if (_outsideCounter <= 0)
|
|
{
|
|
_outsideCounter += 350;
|
|
SetWalkDirection(direction);
|
|
_body.VelocityTarget *= 1.5f;
|
|
_aiComponent.ChangeState("walking", true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_outsideCounter = 350;
|
|
}
|
|
|
|
_body.AdditionalMovementVT = _chainPull;
|
|
_chainPull *= (float)Math.Pow(0.75f, Game1.TimeMultiplier);
|
|
|
|
UpdateChain();
|
|
}
|
|
|
|
private void UpdateChain()
|
|
{
|
|
// update the chain
|
|
var directionOffset = AnimationHelper.DirectionOffset[_direction] * new Vector2(6, 3);
|
|
_currentDirectionOffset = Vector2.Lerp(_currentDirectionOffset, directionOffset, Game1.TimeMultiplier * 0.25f);
|
|
|
|
var startPosition = new Vector3(_origin.X, _origin.Y, _followMode ? MapManager.ObjLink.EntityPosition.Z : 0);
|
|
var goalPosition = new Vector3(
|
|
EntityPosition.Position.X - _currentDirectionOffset.X,
|
|
EntityPosition.Position.Y - 4 - _currentDirectionOffset.Y,
|
|
EntityPosition.Z);
|
|
|
|
startPosition.Z = MathHelper.Clamp(startPosition.Z, 0, 12);
|
|
|
|
_chain.UpdateChain(startPosition, goalPosition);
|
|
}
|
|
|
|
private void OnCollision(Values.BodyCollision moveCollision)
|
|
{
|
|
// rotate after wall collision
|
|
// top collision
|
|
if ((moveCollision & Values.BodyCollision.Horizontal) != 0)
|
|
{
|
|
if (!_changeDirectionSwitch.State)
|
|
return;
|
|
_changeDirectionSwitch.Reset();
|
|
|
|
_body.VelocityTarget.X = -_body.VelocityTarget.X * 0.5f;
|
|
}
|
|
// vertical collision
|
|
else if ((moveCollision & Values.BodyCollision.Vertical) != 0)
|
|
{
|
|
_body.VelocityTarget.Y = -_body.VelocityTarget.Y * 0.5f;
|
|
}
|
|
|
|
if ((moveCollision & (Values.BodyCollision.Vertical | Values.BodyCollision.Horizontal)) != 0)
|
|
{
|
|
_direction = AnimationHelper.GetDirection(_body.VelocityTarget);
|
|
_animator.Play("walk_" + _direction);
|
|
}
|
|
}
|
|
}
|
|
} |