Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs
T
Joonas Rikkonen 81fafaf3f5 633e54b...7cc231b
commit 7cc231bc51890e7fde50bbac3413328dd7bc5189
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Wed Mar 20 14:23:06 2019 +0200

    Fixed server creating network events in Item.Load due to CreateServerEvent calls in some of the ItemComponent properties (e.g. LightComponent.IsOn). Caused syncing problems because the entity spawn events aren't created until the item has been loaded, leading to situations where clients fail to read events because the entity doesn't exist at their end yet (see #1293). TODO: get rid of console errors when attempting to create events during component initialization in the Item constructor.

commit ac1bf32edf125d95d56775e1e8ce54ac7671328c
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Wed Mar 20 13:49:55 2019 +0200

    Fix Hammerhead attack causing warping. Adjust the targeting priorities.

commit e3e032d7c3a753397269bb4236c1494ab0809004
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Wed Mar 20 13:48:47 2019 +0200

    Enemy AI fixes:
    - Fix enemies "fleeing" after they have been shot. There was a steering issue when they targeted characters that were inside the sub when they were outside of it
    - Fix the previous target resetting too often
    - Fix the wall target resetting too often
    - Use world positions instead of sim positions where possible, because the sub positions are then taken into account

commit 847cf5ffd9212a542000dbf12332b2c08756579a
Author: Daniel Asteljoki <daniel.asteljoki@gmail.com>
Date:   Wed Mar 20 12:00:15 2019 +0200

    Remora: added power connection between sub and drone, removed non-scaling hull parts

commit a9c2e8cc124713e90dd44a9adf2fcbb3204b2c4d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Wed Mar 20 11:24:37 2019 +0200

    Server sets clients' last received campaign save/update IDs to one before the current up-to-date IDs (instead of zero) when a campaign changes. Zero would get interpreted as a more up-to-date ID if the IDs of the current campaign are close to ushort.MaxValue where the IDs wrap around.

commit 07d82b64e6990eacaf8905aed1a5d7c61224e47d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Mar 19 18:24:01 2019 +0200

    Log level inequality error messages & client error reports handled by the server to GameAnalytics
2019-03-20 14:25:03 +02:00

714 lines
28 KiB
C#

using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
public enum LimbType
{
None, LeftHand, RightHand, LeftArm, RightArm, LeftForearm, RightForearm,
LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist
};
partial class LimbJoint : RevoluteJoint
{
public bool IsSevered;
public bool CanBeSevered => jointParams.CanBeSevered;
public readonly JointParams jointParams;
public readonly Ragdoll ragdoll;
public readonly Limb LimbA, LimbB;
public LimbJoint(Limb limbA, Limb limbB, JointParams jointParams, Ragdoll ragdoll) : this(limbA, limbB, Vector2.One, Vector2.One)
{
this.jointParams = jointParams;
this.ragdoll = ragdoll;
LoadParams();
}
public LimbJoint(Limb limbA, Limb limbB, Vector2 anchor1, Vector2 anchor2)
: base(limbA.body.FarseerBody, limbB.body.FarseerBody, anchor1, anchor2)
{
CollideConnected = false;
MotorEnabled = true;
MaxMotorTorque = 0.25f;
LimbA = limbA;
LimbB = limbB;
}
public void SaveParams()
{
// Saving to the params is handled only in the params level.
return;
jointParams.Stiffness = MaxMotorTorque;
if (ragdoll.IsFlipped)
{
jointParams.Limb1Anchor = ConvertUnits.ToDisplayUnits(new Vector2(-LocalAnchorA.X, LocalAnchorA.Y) / jointParams.Ragdoll.JointScale);
jointParams.Limb2Anchor = ConvertUnits.ToDisplayUnits(new Vector2(-LocalAnchorB.X, LocalAnchorB.Y) / jointParams.Ragdoll.JointScale);
jointParams.UpperLimit = MathHelper.ToDegrees(-LowerLimit);
jointParams.LowerLimit = MathHelper.ToDegrees(-UpperLimit);
}
else
{
jointParams.Limb1Anchor = ConvertUnits.ToDisplayUnits(LocalAnchorA / jointParams.Ragdoll.JointScale);
jointParams.Limb2Anchor = ConvertUnits.ToDisplayUnits(LocalAnchorB / jointParams.Ragdoll.JointScale);
jointParams.UpperLimit = MathHelper.ToDegrees(UpperLimit);
jointParams.LowerLimit = MathHelper.ToDegrees(LowerLimit);
}
}
public void LoadParams()
{
MaxMotorTorque = jointParams.Stiffness;
LimitEnabled = jointParams.LimitEnabled;
if (float.IsNaN(jointParams.LowerLimit))
{
jointParams.LowerLimit = 0;
}
if (float.IsNaN(jointParams.UpperLimit))
{
jointParams.UpperLimit = 0;
}
if (ragdoll.IsFlipped)
{
LocalAnchorA = ConvertUnits.ToSimUnits(new Vector2(-jointParams.Limb1Anchor.X, jointParams.Limb1Anchor.Y) * jointParams.Ragdoll.JointScale);
LocalAnchorB = ConvertUnits.ToSimUnits(new Vector2(-jointParams.Limb2Anchor.X, jointParams.Limb2Anchor.Y) * jointParams.Ragdoll.JointScale);
UpperLimit = MathHelper.ToRadians(-jointParams.LowerLimit);
LowerLimit = MathHelper.ToRadians(-jointParams.UpperLimit);
}
else
{
LocalAnchorA = ConvertUnits.ToSimUnits(jointParams.Limb1Anchor * jointParams.Ragdoll.JointScale);
LocalAnchorB = ConvertUnits.ToSimUnits(jointParams.Limb2Anchor * jointParams.Ragdoll.JointScale);
UpperLimit = MathHelper.ToRadians(jointParams.UpperLimit);
LowerLimit = MathHelper.ToRadians(jointParams.LowerLimit);
}
}
}
partial class Limb : ISerializableEntity
{
// Note: not used
private const float LimbDensity = 15;
private const float LimbAngularDamping = 7;
//how long it takes for severed limbs to fade out
private const float SeveredFadeOutTime = 10.0f;
public readonly Character character;
/// <summary>
/// Note that during the limb initialization, character.AnimController returns null, whereas this field is already assigned.
/// </summary>
public readonly Ragdoll ragdoll;
public readonly LimbParams limbParams;
//the physics body of the limb
public PhysicsBody body;
public Vector2 StepOffset => ConvertUnits.ToSimUnits(limbParams.StepOffset) * ragdoll.RagdollParams.JointScale;
public bool inWater;
private readonly FixedMouseJoint pullJoint;
public readonly LimbType type;
public readonly bool ignoreCollisions;
private bool isSevered;
private float severedFadeOutTimer;
public Vector2? MouthPos;
public readonly Attack attack;
private List<DamageModifier> damageModifiers;
private Direction dir;
public int HealthIndex => limbParams.HealthIndex;
public float Scale => limbParams.Ragdoll.LimbScale;
public float AttackPriority => limbParams.AttackPriority;
public bool DoesFlip => limbParams.Flip;
public float SteerForce => limbParams.SteerForce;
public Vector2 DebugTargetPos;
public Vector2 DebugRefPos;
public bool IsSevered
{
get { return isSevered; }
set
{
isSevered = value;
if (!isSevered) severedFadeOutTimer = 0.0f;
#if CLIENT
if (isSevered) damageOverlayStrength = 100.0f;
#endif
}
}
public Vector2 WorldPosition
{
get { return character.Submarine == null ? Position : Position + character.Submarine.Position; }
}
public Vector2 Position
{
get { return ConvertUnits.ToDisplayUnits(body.SimPosition); }
}
public Vector2 SimPosition
{
get { return body.SimPosition; }
}
public float Rotation
{
get { return body.Rotation; }
}
//where an animcontroller is trying to pull the limb, only used for debug visualization
public Vector2 AnimTargetPos { get; private set; }
public float Mass
{
get { return body.Mass; }
}
public bool Disabled { get; set; }
public Vector2 LinearVelocity
{
get { return body.LinearVelocity; }
}
public float Dir
{
get { return ((dir == Direction.Left) ? -1.0f : 1.0f); }
set { dir = (value == -1.0f) ? Direction.Left : Direction.Right; }
}
public int RefJointIndex => limbParams.RefJoint;
private List<WearableSprite> wearingItems;
public List<WearableSprite> WearingItems
{
get { return wearingItems; }
}
public List<WearableSprite> OtherWearables { get; private set; } = new List<WearableSprite>();
public bool PullJointEnabled
{
get { return pullJoint.Enabled; }
set { pullJoint.Enabled = value; }
}
public float PullJointMaxForce
{
get { return pullJoint.MaxForce; }
set { pullJoint.MaxForce = value; }
}
public Vector2 PullJointWorldAnchorA
{
get { return pullJoint.WorldAnchorA; }
set
{
if (!MathUtils.IsValid(value))
{
string errorMsg = "Attempted to set the anchor A of a limb's pull joint to an invalid value (" + value + ")\n" + Environment.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorA:InvalidValue", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
return;
}
if (Vector2.DistanceSquared(SimPosition, value) > 50.0f * 50.0f)
{
Vector2 diff = value - SimPosition;
string errorMsg = "Attempted to move the anchor A of a limb's pull joint extremely far from the limb (diff: " + diff +
", limb enabled: " + body.Enabled +
", simple physics enabled: " + character.AnimController.SimplePhysicsEnabled + ")\n"
+ Environment.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorA:ExcessiveValue", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
return;
}
pullJoint.WorldAnchorA = value;
}
}
public Vector2 PullJointWorldAnchorB
{
get { return pullJoint.WorldAnchorB; }
set
{
if (!MathUtils.IsValid(value))
{
string errorMsg = "Attempted to set the anchor B of a limb's pull joint to an invalid value (" + value + ")\n" + Environment.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorB:InvalidValue", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
return;
}
if (Vector2.DistanceSquared(pullJoint.WorldAnchorA, value) > 50.0f * 50.0f)
{
Vector2 diff = value - pullJoint.WorldAnchorA;
string errorMsg = "Attempted to move the anchor B of a limb's pull joint extremely far from the limb (diff: " + diff +
", limb enabled: " + body.Enabled +
", simple physics enabled: " + character.AnimController.SimplePhysicsEnabled + ")\n"
+ Environment.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("Limb.SetPullJointAnchorB:ExcessiveValue", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
return;
}
pullJoint.WorldAnchorB = value;
}
}
public Vector2 PullJointLocalAnchorA
{
get { return pullJoint.LocalAnchorA; }
}
public string Name => limbParams.Name;
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
}
public Limb(Ragdoll ragdoll, Character character, LimbParams limbParams)
{
this.ragdoll = ragdoll;
this.character = character;
this.limbParams = limbParams;
wearingItems = new List<WearableSprite>();
dir = Direction.Right;
body = new PhysicsBody(limbParams);
type = limbParams.Type;
if (limbParams.IgnoreCollisions)
{
body.CollisionCategories = Category.None;
body.CollidesWith = Category.None;
ignoreCollisions = true;
}
else
{
//limbs don't collide with each other
body.CollisionCategories = Physics.CollisionCharacter;
body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
}
body.UserData = this;
pullJoint = new FixedMouseJoint(body.FarseerBody, ConvertUnits.ToSimUnits(limbParams.PullPos * Scale))
{
Enabled = false,
MaxForce = ((type == LimbType.LeftHand || type == LimbType.RightHand) ? 400.0f : 150.0f) * body.Mass
};
GameMain.World.AddJoint(pullJoint);
var element = limbParams.Element;
if (element.Attribute("mouthpos") != null)
{
MouthPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("mouthpos", Vector2.Zero));
}
body.BodyType = BodyType.Dynamic;
body.FarseerBody.AngularDamping = LimbAngularDamping;
damageModifiers = new List<DamageModifier>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "attack":
attack = new Attack(subElement, (character == null ? "null" : character.Name) + ", limb " + type);
if (attack.DamageRange <= 0)
{
switch (body.BodyShape)
{
case PhysicsBody.Shape.Circle:
attack.DamageRange = body.radius;
break;
case PhysicsBody.Shape.Capsule:
attack.DamageRange = body.height / 2 + body.radius;
break;
case PhysicsBody.Shape.Rectangle:
attack.DamageRange = new Vector2(body.width / 2.0f, body.height / 2.0f).Length();
break;
}
attack.DamageRange = ConvertUnits.ToDisplayUnits(attack.DamageRange);
}
break;
case "damagemodifier":
damageModifiers.Add(new DamageModifier(subElement, character.Name));
break;
}
}
SerializableProperties = SerializableProperty.GetProperties(this);
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public void MoveToPos(Vector2 pos, float force, bool pullFromCenter = false)
{
Vector2 pullPos = body.SimPosition;
if (!pullFromCenter)
{
pullPos = pullJoint.WorldAnchorA;
}
AnimTargetPos = pos;
body.MoveToPos(pos, force, pullPos);
}
public void MirrorPullJoint()
{
pullJoint.LocalAnchorA = new Vector2(-pullJoint.LocalAnchorA.X, pullJoint.LocalAnchorA.Y);
}
public AttackResult AddDamage(Vector2 simPosition, float damage, float bleedingDamage, float burnDamage, bool playSound)
{
List<Affliction> afflictions = new List<Affliction>();
if (damage > 0.0f) afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damage));
if (bleedingDamage > 0.0f) afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamage));
if (burnDamage > 0.0f) afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamage));
return AddDamage(simPosition, afflictions, playSound);
}
public AttackResult AddDamage(Vector2 simPosition, List<Affliction> afflictions, bool playSound)
{
List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
//create a copy of the original affliction list to prevent modifying the afflictions of an Attack/StatusEffect etc
afflictions = new List<Affliction>(afflictions);
for (int i = 0; i < afflictions.Count; i++)
{
foreach (DamageModifier damageModifier in damageModifiers)
{
if (!damageModifier.MatchesAffliction(afflictions[i])) continue;
if (SectorHit(damageModifier.ArmorSector, simPosition))
{
afflictions[i] = afflictions[i].CreateMultiplied(damageModifier.DamageMultiplier);
appliedDamageModifiers.Add(damageModifier);
}
}
foreach (WearableSprite wearable in wearingItems)
{
foreach (DamageModifier damageModifier in wearable.WearableComponent.DamageModifiers)
{
if (!damageModifier.MatchesAffliction(afflictions[i])) continue;
if (SectorHit(damageModifier.ArmorSector, simPosition))
{
afflictions[i] = afflictions[i].CreateMultiplied(damageModifier.DamageMultiplier);
appliedDamageModifiers.Add(damageModifier);
}
}
}
}
AddDamageProjSpecific(simPosition, afflictions, playSound, appliedDamageModifiers);
return new AttackResult(afflictions, this, appliedDamageModifiers);
}
partial void AddDamageProjSpecific(Vector2 simPosition, List<Affliction> afflictions, bool playSound, List<DamageModifier> appliedDamageModifiers);
public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
{
if (armorSector == Vector2.Zero) { return false; }
float rotation = body.TransformedRotation;
float offset = (MathHelper.PiOver2 - GetArmorSectorRotationOffset(armorSector)) * Dir;
float hitAngle = VectorExtensions.Angle(VectorExtensions.Forward(rotation + offset), SimPosition - simPosition);
float sectorSize = GetArmorSectorSize(armorSector);
return hitAngle < sectorSize / 2;
}
protected float GetArmorSectorRotationOffset(Vector2 armorSector)
{
float midAngle = MathUtils.GetMidAngle(armorSector.X, armorSector.Y);
float spritesheetOrientation = MathHelper.ToRadians(limbParams.Ragdoll.SpritesheetOrientation);
return midAngle + spritesheetOrientation;
}
protected float GetArmorSectorSize(Vector2 armorSector)
{
float min = Math.Min(armorSector.X, armorSector.Y);
float max = Math.Max(armorSector.X, armorSector.Y);
return max - min;
}
public void Update(float deltaTime)
{
UpdateProjSpecific(deltaTime);
if (inWater)
{
body.ApplyWaterForces();
}
if (isSevered)
{
severedFadeOutTimer += deltaTime;
if (severedFadeOutTimer > SeveredFadeOutTime)
{
body.Enabled = false;
}
}
if (attack != null)
{
attack.UpdateCoolDown(deltaTime);
}
}
partial void UpdateProjSpecific(float deltaTime);
/// <summary>
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
/// </summary>
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1)
{
attackResult = default(AttackResult);
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos));
bool wasRunning = attack.IsRunning;
attack.UpdateAttackTimer(deltaTime);
bool wasHit = false;
Body structureBody = null;
if (damageTarget != null)
{
switch (attack.HitDetectionType)
{
case HitDetection.Distance:
if (dist < attack.DamageRange)
{
List<Body> ignoredBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
structureBody = Submarine.PickBody(
SimPosition, attackSimPos,
ignoredBodies, Physics.CollisionWall);
if (damageTarget is Item)
{
// If the attack is aimed to an item and hits an item, it's successful.
// Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example.
wasHit = true;
}
else if (damageTarget is Structure && structureBody?.UserData is Structure)
{
// If the attack is aimed to a structure and hits a structure, it's successful
wasHit = true;
}
else
{
// If the attack is aimed to a character but hits a structure, the hit is blocked.
wasHit = structureBody == null;
}
}
break;
case HitDetection.Contact:
var targetBodies = new List<Body>();
if (damageTarget is Character targetCharacter)
{
foreach (Limb limb in targetCharacter.AnimController.Limbs)
{
if (!limb.IsSevered && limb.body?.FarseerBody != null) targetBodies.Add(limb.body.FarseerBody);
}
}
else if (damageTarget is Structure targetStructure)
{
if (character.Submarine == null && targetStructure.Submarine != null)
{
targetBodies.Add(targetStructure.Submarine.PhysicsBody.FarseerBody);
}
else
{
targetBodies.AddRange(targetStructure.Bodies);
}
}
else if (damageTarget is Item)
{
Item targetItem = damageTarget as Item;
if (targetItem.body?.FarseerBody != null) targetBodies.Add(targetItem.body.FarseerBody);
}
if (targetBodies != null)
{
ContactEdge contactEdge = body.FarseerBody.ContactList;
while (contactEdge != null)
{
if (contactEdge.Contact != null &&
contactEdge.Contact.IsTouching &&
targetBodies.Any(b => b == contactEdge.Contact.FixtureA?.Body || b == contactEdge.Contact.FixtureB?.Body))
{
structureBody = targetBodies.LastOrDefault();
wasHit = true;
break;
}
contactEdge = contactEdge.Next;
}
}
break;
}
}
if (wasHit)
{
wasHit = damageTarget != null;
}
if (wasHit)
{
bool playSound = false;
#if CLIENT
playSound = LastAttackSoundTime < Timing.TotalTime - SoundInterval;
if (playSound)
{
LastAttackSoundTime = SoundInterval;
}
#endif
attackResult = attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, playSound);
if (structureBody != null && attack.StickChance > Rand.Range(0.0f, 1.0f, Rand.RandSync.Server))
{
// TODO: use the hit pos?
var localFront = body.GetLocalFront(MathHelper.ToRadians(ragdoll.RagdollParams.SpritesheetOrientation));
var from = body.FarseerBody.GetWorldPoint(localFront);
var to = from;
var drawPos = body.DrawPosition;
StickTo(structureBody, from, to);
}
attack.ResetAttackTimer();
attack.SetCoolDown();
}
Vector2 diff = attackSimPos - SimPosition;
bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f;
if (applyForces)
{
body.ApplyTorque(Mass * character.AnimController.Dir * attack.Torque);
if (attack.ForceOnLimbIndices != null && attack.ForceOnLimbIndices.Count > 0)
{
foreach (int limbIndex in attack.ForceOnLimbIndices)
{
if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) continue;
Limb limb = character.AnimController.Limbs[limbIndex];
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
}
}
else
{
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
}
}
return wasHit;
}
private WeldJoint attachJoint;
private WeldJoint colliderJoint;
public bool IsStuck => attachJoint != null;
/// <summary>
/// Attach the limb to a target with WeldJoints.
/// Uses sim units.
/// </summary>
private void StickTo(Body target, Vector2 from, Vector2 to)
{
if (attachJoint != null)
{
// Already attached to the target body, no need to do anything
if (attachJoint.BodyB == target) { return; }
Release();
}
if (!ragdoll.IsStuck)
{
PhysicsBody mainLimbBody = ragdoll.MainLimb.body;
Body colliderBody = ragdoll.Collider.FarseerBody;
Vector2 mainLimbLocalFront = mainLimbBody.GetLocalFront(MathHelper.ToRadians(ragdoll.RagdollParams.SpritesheetOrientation));
if (Dir < 0)
{
mainLimbLocalFront.X = -mainLimbLocalFront.X;
}
Vector2 mainLimbFront = mainLimbBody.FarseerBody.GetWorldPoint(mainLimbLocalFront);
colliderBody.SetTransform(mainLimbBody.SimPosition, mainLimbBody.Rotation);
// Attach the collider to the main body so that they don't go out of sync (TODO: why is the collider still rotated 90d off?)
colliderJoint = new WeldJoint(colliderBody, mainLimbBody.FarseerBody, mainLimbFront, mainLimbFront, true)
{
KinematicBodyB = true,
CollideConnected = false
};
GameMain.World.AddJoint(colliderJoint);
}
attachJoint = new WeldJoint(body.FarseerBody, target, from, to, true)
{
FrequencyHz = 1,
DampingRatio = 0.5f,
KinematicBodyB = true,
CollideConnected = false
};
GameMain.World.AddJoint(attachJoint);
}
public void Release()
{
if (!IsStuck) { return; }
GameMain.World.RemoveJoint(attachJoint);
attachJoint = null;
if (colliderJoint != null)
{
GameMain.World.RemoveJoint(colliderJoint);
colliderJoint = null;
}
}
public void Remove()
{
body?.Remove();
body = null;
Release();
RemoveProjSpecific();
}
partial void RemoveProjSpecific();
public void LoadParams()
{
attack?.Deserialize();
pullJoint.LocalAnchorA = ConvertUnits.ToSimUnits(limbParams.PullPos * Scale);
LoadParamsProjSpecific();
}
partial void LoadParamsProjSpecific();
}
}