2 using FarseerPhysics.Dynamics;
3 using FarseerPhysics.Dynamics.Joints;
4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
15 const float RaycastInterval = 5.0f;
16 private float raycastTimer;
17 private Body targetBody;
18 private Vector2 attachSurfaceNormal;
29 private readonly
float minDeattachSpeed, maxDeattachSpeed, maxAttachDuration, coolDown;
30 private readonly
float damageOnDetach, detachStun;
31 private readonly
bool weld;
32 private float deattachCheckTimer;
34 private Vector2 _attachPos;
39 private float attachCooldown;
41 private readonly
Limb attachLimb;
42 private Vector2 localAttachPos;
43 private readonly
float attachLimbRotation;
45 private float jointDir;
47 private float latchedDuration;
49 private readonly
bool freezeWhenLatched;
68 minDeattachSpeed = element.GetAttributeFloat(nameof(minDeattachSpeed), 5.0f);
69 maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat(nameof(maxDeattachSpeed), 8.0f));
70 maxAttachDuration = element.GetAttributeFloat(nameof(maxAttachDuration), -1.0f);
71 coolDown = element.GetAttributeFloat(nameof(coolDown), 2f);
72 damageOnDetach = element.GetAttributeFloat(nameof(damageOnDetach), 0.0f);
73 detachStun = element.GetAttributeFloat(nameof(detachStun), 0.0f);
74 localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2(nameof(localAttachPos), Vector2.Zero));
75 attachLimbRotation = MathHelper.ToRadians(element.GetAttributeFloat(nameof(attachLimbRotation), 0.0f));
76 weld = element.GetAttributeBool(nameof(weld),
true);
77 freezeWhenLatched = element.GetAttributeBool(nameof(freezeWhenLatched),
false);
79 string limbString = element.GetAttributeString(
"attachlimb",
null);
80 attachLimb = enemyAI.
Character.
AnimController.
Limbs.FirstOrDefault(l =>
string.Equals(l.Name, limbString, StringComparison.OrdinalIgnoreCase));
81 if (attachLimb ==
null)
83 if (Enum.TryParse(limbString, out
LimbType attachLimbType))
88 if (attachLimb ==
null)
100 if (wall ==
null) {
return; }
102 if (sub ==
null) {
return; }
107 this.attachSurfaceNormal = attachSurfaceNormal;
108 _attachPos = attachPos;
118 attachSurfaceNormal = Vector2.Normalize(character.WorldPosition - target.
WorldPosition);
127 if (MathUtils.GetLineSegmentIntersection(edge.Point1, edge.Point2, character.WorldPosition, levelWall.
Center, out Vector2 intersection))
129 attachSurfaceNormal = edge.GetNormal(levelWall);
130 targetBody = levelWall.
Body;
131 _attachPos = ConvertUnits.ToSimUnits(intersection);
147 latchedDuration += deltaTime;
148 if (freezeWhenLatched && targetBody is { BodyType: BodyType.Static } &&
150 latchedDuration > 5.0f)
152 foreach (var limb
in character.AnimController.Limbs)
154 limb.body.LinearVelocity = Vector2.Zero;
155 limb.body.AngularVelocity = 0.0f;
158 if (Math.Sign(attachLimb.Dir) != Math.Sign(jointDir))
161 if (attachJoint is WeldJoint weldJoint)
163 weldJoint.LocalAnchorA =
new Vector2(-weldJoint.LocalAnchorA.X, weldJoint.LocalAnchorA.Y);
164 weldJoint.ReferenceAngle = -weldJoint.ReferenceAngle;
166 else if (attachJoint is RevoluteJoint revoluteJoint)
168 revoluteJoint.LocalAnchorA =
new Vector2(-revoluteJoint.LocalAnchorA.X, revoluteJoint.LocalAnchorA.Y);
169 revoluteJoint.ReferenceAngle = -revoluteJoint.ReferenceAngle;
171 jointDir = attachLimb.Dir;
179 DebugConsole.Log(
"Limb body of the character \"" + character.Name +
"\" is very far from the attach joint anchor -> deattach");
206 if (attachCooldown > 0)
208 attachCooldown -= deltaTime;
210 if (deattachCheckTimer > 0)
212 deattachCheckTimer -= deltaTime;
218 _attachPos = character.SimPosition;
220 Vector2 transformedAttachPos = _attachPos;
225 if (transformedAttachPos != Vector2.Zero)
230 switch (enemyAI.
State)
237 raycastTimer -= deltaTime;
239 if (raycastTimer < 0.0f)
241 _attachPos = Vector2.Zero;
247 float closestDist = 200.0f * 200.0f;
252 if (MathUtils.GetLineSegmentIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
254 Vector2 potentialAttachPos = ConvertUnits.ToSimUnits(intersection);
255 float distSqr = Vector2.DistanceSquared(character.SimPosition, potentialAttachPos);
256 if (distSqr < closestDist)
258 attachSurfaceNormal = edge.GetNormal(cell);
259 targetBody = cell.Body;
260 _attachPos = potentialAttachPos;
261 closestDist = distSqr;
268 raycastTimer = RaycastInterval;
274 _attachPos = Vector2.Zero;
276 if (_attachPos == Vector2.Zero || targetBody ==
null)
280 else if (attachCooldown <= 0.0f)
282 float squaredDistance = Vector2.DistanceSquared(character.SimPosition, _attachPos);
283 float targetDistance = Math.Max(Math.Max(character.AnimController.Collider.Radius, character.AnimController.Collider.Width), character.AnimController.Collider.Height) * 1.2f;
284 if (squaredDistance < targetDistance * targetDistance)
306 if (_attachPos == Vector2.Zero) {
break; }
309 if (targetBody ==
null) {
break; }
322 if (
IsAttached && targetBody !=
null && deattachCheckTimer <= 0.0f)
324 attachCooldown = coolDown;
325 bool deattach =
false;
326 if (maxAttachDuration > 0)
342 deattach = velocity > maxDeattachSpeed;
345 if (velocity > minDeattachSpeed)
347 float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
348 Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
349 (Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
351 if (Rand.Range(0.0f, 1.0f) < velocityFactor)
355 attachCooldown = Math.Max(detachStun * 2, coolDown);
360 deattachCheckTimer = 5.0f;
369 public void AttachToBody(Vector2 attachPos, Vector2? forceAttachSurfaceNormal =
null, Vector2? forceColliderSimPosition =
null)
371 if (attachLimb ==
null) {
return; }
372 if (targetBody ==
null) {
return; }
373 if (attachCooldown > 0) {
return; }
374 var collider = character.AnimController.Collider;
383 jointDir = attachLimb.Dir;
385 if (forceAttachSurfaceNormal.HasValue) { attachSurfaceNormal = forceAttachSurfaceNormal.Value; }
386 if (forceColliderSimPosition.HasValue)
388 character.TeleportTo(ConvertUnits.ToDisplayUnits(forceColliderSimPosition.Value));
392 Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.Scale * attachLimb.Params.Ragdoll.LimbScale;
395 transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
398 float angle = MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2 + attachLimbRotation * attachLimb.Dir;
401 angle = attachLimb.body.WrapAngleToSameNumberOfRevolutions(angle);
402 attachLimb.body.SetTransform(attachPos + attachSurfaceNormal * transformedLocalAttachPos.Length(), angle);
404 var limbJoint =
new WeldJoint(attachLimb.body.FarseerBody, targetBody,
405 transformedLocalAttachPos, targetBody.GetLocalPoint(attachPos),
false)
409 KinematicBodyB =
true,
410 CollideConnected =
false,
416 Vector2 colliderFront = collider.GetLocalFront();
419 colliderFront.X = -colliderFront.X;
421 collider.SetTransform(attachPos + attachSurfaceNormal * colliderFront.Length(), MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2);
423 Joint colliderJoint = weld ?
424 new WeldJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos),
false)
428 KinematicBodyB =
true,
429 CollideConnected =
false,
431 new RevoluteJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos),
false)
434 MaxMotorTorque = 0.25f
440 if (maxAttachDuration > 0)
442 deattachCheckTimer = maxAttachDuration;
471 attachCooldown = cooldown;
496 private void OnCharacterDeath(Character character, CauseOfDeath causeOfDeath)
499 character.OnDeath -= OnCharacterDeath;
readonly Character Character
SteeringManager SteeringManager
bool IsSteeringThroughGap
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
Affliction Instantiate(float strength, Character source=null)
static AfflictionPrefab InternalDamage
readonly HashSet< LatchOntoAI > Latchers
readonly AnimController AnimController
bool CanPassThroughHole(Structure wall, int sectionIndex)
virtual Vector2 WorldPosition
void SetAttachTarget(Structure wall, Vector2 attachPos, Vector2 attachSurfaceNormal)
void DeattachFromBody(bool reset, float cooldown=0)
void AttachToBody(Vector2 attachPos, Vector2? forceAttachSurfaceNormal=null, Vector2? forceColliderSimPosition=null)
void SetAttachTarget(VoronoiCell levelWall)
List< Joint > AttachJoints
Submarine TargetSubmarine
void SetAttachTarget(Character target)
LatchOntoAI(XElement element, EnemyAIController enemyAI)
void Update(EnemyAIController enemyAI, float deltaTime)
Character TargetCharacter
List< VoronoiCell > GetCells(Vector2 worldPos, int searchDepth=2)
Limb GetLimb(LimbType limbType, bool excludeSevered=true, bool excludeLimbsWithSecondaryType=false, bool useSecondaryType=false)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
void SteeringSeek(Vector2 targetSimPos, float weight=1)
void SteeringAvoid(float deltaTime, float lookAheadDistance, float weight=1)
int FindSectionIndex(Vector2 displayPos, bool world=false, bool clamp=false)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
override Vector2? Position