297 lines
13 KiB
C#
297 lines
13 KiB
C#
using FarseerPhysics;
|
|
using FarseerPhysics.Common;
|
|
using FarseerPhysics.Dynamics;
|
|
using FarseerPhysics.Dynamics.Joints;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class LatchOntoAI
|
|
{
|
|
const float RaycastInterval = 5.0f;
|
|
|
|
private float raycastTimer;
|
|
|
|
private Body attachTargetBody;
|
|
private Vector2 attachSurfaceNormal;
|
|
private Submarine attachTargetSubmarine;
|
|
|
|
private bool attachToSub;
|
|
private bool attachToWalls;
|
|
|
|
private float minDeattachSpeed = 3.0f, maxDeattachSpeed = 10.0f;
|
|
private float damageOnDetach = 0.0f, detachStun = 0.0f;
|
|
private float deattachTimer;
|
|
|
|
private Vector2 wallAttachPos;
|
|
|
|
private float attachCooldown;
|
|
|
|
private Limb attachLimb;
|
|
private Vector2 localAttachPos;
|
|
private float attachLimbRotation;
|
|
|
|
private float jointDir;
|
|
|
|
private List<WeldJoint> attachJoints = new List<WeldJoint>();
|
|
|
|
public List<WeldJoint> AttachJoints
|
|
{
|
|
get { return attachJoints; }
|
|
}
|
|
|
|
public Vector2? WallAttachPos
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public bool IsAttached
|
|
{
|
|
get { return attachJoints.Count > 0; }
|
|
}
|
|
|
|
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
|
{
|
|
attachToWalls = element.GetAttributeBool("attachtowalls", false);
|
|
attachToSub = element.GetAttributeBool("attachtosub", false);
|
|
minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 3.0f);
|
|
maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 10.0f));
|
|
damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f);
|
|
detachStun = element.GetAttributeFloat("detachstun", 0.0f);
|
|
localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero));
|
|
attachLimbRotation = MathHelper.ToRadians(element.GetAttributeFloat("attachlimbrotation", 0.0f));
|
|
|
|
if (Enum.TryParse(element.GetAttributeString("attachlimb", "Head"), out LimbType attachLimbType))
|
|
{
|
|
attachLimb = enemyAI.Character.AnimController.GetLimb(attachLimbType);
|
|
}
|
|
if (attachLimb == null) attachLimb = enemyAI.Character.AnimController.MainLimb;
|
|
|
|
enemyAI.Character.OnDeath += OnCharacterDeath;
|
|
}
|
|
|
|
public void SetAttachTarget(Body attachTarget, Submarine attachTargetSub, Vector2 attachPos, Vector2 attachSurfaceNormal)
|
|
{
|
|
attachTargetBody = attachTarget;
|
|
attachTargetSubmarine = attachTargetSub;
|
|
this.attachSurfaceNormal = attachSurfaceNormal;
|
|
wallAttachPos = attachPos;
|
|
}
|
|
|
|
public void Update(EnemyAIController enemyAI, float deltaTime)
|
|
{
|
|
Character character = enemyAI.Character;
|
|
|
|
if (character.Submarine != null)
|
|
{
|
|
DeattachFromBody();
|
|
WallAttachPos = null;
|
|
return;
|
|
}
|
|
if (attachJoints.Count > 0)
|
|
{
|
|
if (Math.Sign(attachLimb.Dir) != Math.Sign(jointDir))
|
|
{
|
|
attachJoints[0].LocalAnchorA =
|
|
new Vector2(-attachJoints[0].LocalAnchorA.X, attachJoints[0].LocalAnchorA.Y);
|
|
attachJoints[0].ReferenceAngle = -attachJoints[0].ReferenceAngle;
|
|
jointDir = attachLimb.Dir;
|
|
}
|
|
for (int i = 0; i < attachJoints.Count; i++)
|
|
{
|
|
//something went wrong, limb body is very far from the joint anchor -> deattach
|
|
if (Vector2.DistanceSquared(attachJoints[i].WorldAnchorB, attachJoints[i].BodyA.Position) > 10.0f * 10.0f)
|
|
{
|
|
DebugConsole.ThrowError("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach");
|
|
DeattachFromBody();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
attachCooldown -= deltaTime;
|
|
deattachTimer -= deltaTime;
|
|
|
|
Vector2 transformedAttachPos = wallAttachPos;
|
|
if (character.Submarine == null && attachTargetSubmarine != null)
|
|
{
|
|
transformedAttachPos += ConvertUnits.ToSimUnits(attachTargetSubmarine.Position);
|
|
}
|
|
if (transformedAttachPos != Vector2.Zero)
|
|
{
|
|
WallAttachPos = transformedAttachPos;
|
|
}
|
|
|
|
switch (enemyAI.State)
|
|
{
|
|
case AIController.AIState.Idle:
|
|
if (attachToWalls && character.Submarine == null && Level.Loaded != null)
|
|
{
|
|
raycastTimer -= deltaTime;
|
|
//check if there are any walls nearby the character could attach to
|
|
if (raycastTimer < 0.0f)
|
|
{
|
|
wallAttachPos = Vector2.Zero;
|
|
|
|
var cells = Level.Loaded.GetCells(character.WorldPosition, 1);
|
|
if (cells.Count > 0)
|
|
{
|
|
foreach (Voronoi2.VoronoiCell cell in cells)
|
|
{
|
|
foreach (Voronoi2.GraphEdge edge in cell.Edges)
|
|
{
|
|
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
|
{
|
|
attachSurfaceNormal = edge.GetNormal(cell);
|
|
attachTargetBody = cell.Body;
|
|
wallAttachPos = ConvertUnits.ToSimUnits(intersection);
|
|
break;
|
|
}
|
|
}
|
|
if (WallAttachPos != Vector2.Zero) break;
|
|
}
|
|
}
|
|
raycastTimer = RaycastInterval;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wallAttachPos = Vector2.Zero;
|
|
}
|
|
|
|
if (wallAttachPos == Vector2.Zero)
|
|
{
|
|
DeattachFromBody();
|
|
}
|
|
else
|
|
{
|
|
float dist = Vector2.Distance(character.SimPosition, wallAttachPos);
|
|
if (dist < Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f)
|
|
{
|
|
//close enough to a wall -> attach
|
|
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, wallAttachPos);
|
|
enemyAI.SteeringManager.Reset();
|
|
}
|
|
else
|
|
{
|
|
//move closer to the wall
|
|
DeattachFromBody();
|
|
enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, 0.1f);
|
|
enemyAI.SteeringManager.SteeringSeek(wallAttachPos);
|
|
}
|
|
}
|
|
break;
|
|
case AIController.AIState.Attack:
|
|
if (enemyAI.AttackingLimb != null)
|
|
{
|
|
if (attachToSub && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
|
{
|
|
// is not attached or is attached to something else
|
|
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
|
|
{
|
|
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
|
|
{
|
|
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, transformedAttachPos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
WallAttachPos = null;
|
|
DeattachFromBody();
|
|
break;
|
|
}
|
|
|
|
if (attachTargetBody != null && deattachTimer < 0.0f)
|
|
{
|
|
Entity entity = attachTargetBody.UserData as Entity;
|
|
Submarine attachedSub = entity is Submarine ? (Submarine)entity : entity?.Submarine;
|
|
if (attachedSub != null)
|
|
{
|
|
float velocity = attachedSub.Velocity == Vector2.Zero ? 0.0f : attachedSub.Velocity.Length();
|
|
float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
|
|
Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
|
|
(Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
|
|
|
|
if (Rand.Range(0.0f, 1.0f) < velocityFactor)
|
|
{
|
|
DeattachFromBody();
|
|
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
|
|
attachCooldown = 5.0f;
|
|
}
|
|
}
|
|
|
|
deattachTimer = 5.0f;
|
|
}
|
|
}
|
|
|
|
private void AttachToBody(PhysicsBody collider, Limb attachLimb, Body targetBody, Vector2 attachPos)
|
|
{
|
|
//already attached to something
|
|
if (attachJoints.Count > 0)
|
|
{
|
|
//already attached to the target body, no need to do anything
|
|
if (attachJoints[0].BodyB == targetBody) return;
|
|
DeattachFromBody();
|
|
}
|
|
|
|
jointDir = attachLimb.Dir;
|
|
|
|
Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.character.AnimController.RagdollParams.LimbScale;
|
|
if (jointDir < 0.0f) transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
|
|
|
|
//transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation));
|
|
|
|
float angle = MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2 + attachLimbRotation * attachLimb.Dir;
|
|
attachLimb.body.SetTransform(attachPos + attachSurfaceNormal * transformedLocalAttachPos.Length(), angle);
|
|
|
|
var limbJoint = new WeldJoint(attachLimb.body.FarseerBody, targetBody,
|
|
transformedLocalAttachPos, targetBody.GetLocalPoint(attachPos), false)
|
|
{
|
|
FrequencyHz = 10.0f,
|
|
DampingRatio = 0.5f,
|
|
KinematicBodyB = true,
|
|
CollideConnected = false,
|
|
};
|
|
GameMain.World.AddJoint(limbJoint);
|
|
attachJoints.Add(limbJoint);
|
|
|
|
// Limb scale is already taken into account when creating the collider.
|
|
Vector2 colliderFront = collider.GetLocalFront();
|
|
if (jointDir < 0.0f) colliderFront.X = -colliderFront.X;
|
|
collider.SetTransform(attachPos + attachSurfaceNormal * colliderFront.Length(), MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2);
|
|
|
|
var colliderJoint = new WeldJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false)
|
|
{
|
|
FrequencyHz = 10.0f,
|
|
DampingRatio = 0.5f,
|
|
KinematicBodyB = true,
|
|
CollideConnected = false,
|
|
//Length = 0.1f
|
|
};
|
|
GameMain.World.AddJoint(colliderJoint);
|
|
attachJoints.Add(colliderJoint);
|
|
}
|
|
|
|
public void DeattachFromBody()
|
|
{
|
|
foreach (Joint joint in attachJoints)
|
|
{
|
|
GameMain.World.RemoveJoint(joint);
|
|
}
|
|
attachJoints.Clear();
|
|
}
|
|
|
|
private void OnCharacterDeath(Character character, CauseOfDeath causeOfDeath)
|
|
{
|
|
DeattachFromBody();
|
|
character.OnDeath -= OnCharacterDeath;
|
|
}
|
|
}
|
|
}
|