4 using FarseerPhysics.Dynamics;
5 using Microsoft.Xna.Framework;
14 private Vector2? launchDir;
15 private float currentRopeLength;
20 if (source is
Limb sourceLimb)
22 sourceLimb.AttachedRope =
this;
23 float offset = sourceLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
24 launchDir = VectorExtensions.Forward(sourceLimb.body.TransformedRotation - offset * sourceLimb.character.AnimController.Dir);
28 private void ResetSource()
32 sourceLimb.AttachedRope =
null;
37 private float snapTimer;
46 private float raycastTimer;
47 private const float RayCastInterval = 0.2f;
70 [
Serialize(1000.0f,
IsPropertySaveable.No, description:
"How far the source item can be from the projectile until the rope breaks.")]
91 [
Serialize(
true,
IsPropertySaveable.No, description:
"Should the rope snap when it collides with a structure/submarine (if not, it will just go through it).")]
105 [
Serialize(30.0f,
IsPropertySaveable.No, description:
"How much mass is required for the target to pull the source towards it. Static and kinematic targets are always treated heavy enough.")]
119 private bool isReelingIn;
120 private bool snapped;
123 get {
return snapped; }
126 if (snapped == value) {
return; }
136 item.CreateServerEvent(
this);
145 else if (target !=
null && source !=
null && target != source)
159 InitProjSpecific(element);
168 System.Diagnostics.Debug.Assert(source !=
null);
169 System.Diagnostics.Debug.Assert(target !=
null);
170 this.target = target;
179 UpdateProjSpecific();
182 if (source ==
null || target ==
null || target.
Removed ||
183 source is
Entity { Removed:
true } ||
184 source is
Limb { Removed:
true } ||
185 user is { Removed:
true })
195 snapTimer += deltaTime;
203 Vector2 diff = target.
WorldPosition - GetSourcePos(useDrawPosition:
false);
204 float lengthSqr = diff.LengthSquared();
211 if (MaxAngle < 180 && lengthSqr > 2500)
214 float angle = MathHelper.ToDegrees(launchDir.Value.Angle(diff));
225 var projectile = target.GetComponent<Projectile>();
226 if (projectile ==
null) {
return; }
230 raycastTimer += deltaTime;
231 if (raycastTimer > RayCastInterval)
234 collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
235 customPredicate: (Fixture f) =>
237 foreach (Body body in projectile.Hits)
239 Submarine alreadyHitSub = null;
240 if (body.UserData is Structure hitStructure)
242 alreadyHitSub = hitStructure.Submarine;
244 else if (body.UserData is Submarine hitSub)
246 alreadyHitSub = hitSub;
248 if (alreadyHitSub != null)
250 if (f.Body?.UserData is MapEntity me && me.Submarine == alreadyHitSub) { return false; }
251 if (f.Body?.UserData as Submarine == alreadyHitSub) { return false; }
254 Submarine targetSub = projectile.StickTarget?.UserData as Submarine ?? target.
Submarine;
256 if (f.Body?.UserData is MapEntity mapEntity && mapEntity.Submarine !=
null)
258 if (mapEntity.Submarine == targetSub || mapEntity.Submarine == source.Submarine)
263 else if (f.Body?.UserData is Submarine sub)
265 if (sub == targetSub || sub == source.Submarine)
280 Vector2 forceDir = diff;
281 currentRopeLength = diff.Length();
282 if (currentRopeLength > 0.001f)
284 forceDir = Vector2.Normalize(forceDir);
287 if (Math.Abs(ProjectilePullForce) > 0.001f)
289 projectile.Item?.body?.ApplyForce(-forceDir * ProjectilePullForce);
292 if (projectile.StickTarget !=
null)
294 float targetMass =
float.MaxValue;
296 switch (projectile.StickTarget.UserData)
298 case Limb targetLimb:
299 targetCharacter = targetLimb.character;
300 targetMass = targetLimb.ragdoll.Mass;
303 targetCharacter = character;
304 targetMass = character.Mass;
307 targetMass = projectile.StickTarget.Mass;
310 if (projectile.StickTarget.BodyType != BodyType.Dynamic)
312 targetMass =
float.MaxValue;
319 user.AnimController.HoldToRope();
320 if (targetCharacter !=
null)
322 targetCharacter.AnimController.DragWithRope();
326 user.AnimController.HangWithRope();
329 if (Math.Abs(SourcePullForce) > 0.001f && targetMass > TargetMinMass)
332 var sourceBody = GetBodyToPull(source);
333 if (sourceBody !=
null)
335 isReelingIn = user.InWater && user.IsRagdolled || !user.InWater && targetCharacter is { IsIncapacitated:
false };
338 float pullForce = SourcePullForce;
344 float lengthFactor = MathUtils.InverseLerp(0, MaxLength / 2, currentRopeLength);
345 float force = LerpForces ? MathHelper.Lerp(0, pullForce, lengthFactor) : pullForce;
346 sourceBody.ApplyForce(forceDir * force);
348 PhysicsBody targetBody = GetBodyToPull(target);
349 if (targetBody !=
null)
351 if (targetCharacter !=
null)
353 if (targetBody.LinearVelocity != Vector2.Zero && sourceBody.LinearVelocity != Vector2.Zero)
355 Vector2 targetDir = Vector2.Normalize(targetBody.LinearVelocity);
356 float movementDot = Vector2.Dot(Vector2.Normalize(sourceBody.LinearVelocity), targetDir);
360 const float multiplier = 5;
361 float inverseLengthFactor = MathHelper.Lerp(1, 0, lengthFactor);
362 sourceBody.ApplyForce(targetBody.LinearVelocity * Math.Min(targetBody.Mass * multiplier, 250) * sourceBody.Mass * -movementDot * inverseLengthFactor);
364 float forceDot = Vector2.Dot(forceDir, targetDir);
368 float targetSpeed = targetBody.LinearVelocity.Length();
369 const float multiplier = 25;
370 sourceBody.ApplyForce(forceDir * targetSpeed * sourceBody.Mass * multiplier * forceDot * lengthFactor);
372 float colliderMainLimbDistance = Vector2.Distance(sourceBody.SimPosition, user.AnimController.MainLimb.SimPosition);
373 const float minDist = 1;
374 const float maxDist = 10;
375 if (colliderMainLimbDistance > minDist)
378 float correctionForce = MathHelper.Lerp(10.0f, NetConfig.MaxPhysicsBodyVelocity, MathUtils.InverseLerp(minDist, maxDist, colliderMainLimbDistance));
379 Vector2 targetPos = sourceBody.SimPosition +
new Vector2((
float)Math.Sin(-sourceBody.Rotation), (
float)Math.Cos(-sourceBody.Rotation)) * 0.4f;
380 user.AnimController.MainLimb.MoveToPos(targetPos, correctionForce);
386 sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
394 if (Math.Abs(TargetPullForce) > 0.001f && user is not { IsRagdolled: true})
396 PhysicsBody targetBody = GetBodyToPull(target);
397 if (targetBody ==
null) {
return; }
398 bool lerpForces = LerpForces;
399 float maxVelocity = NetConfig.MaxPhysicsBodyVelocity * 0.25f;
401 float maxPullDistance = MaxLength / 3;
402 float minPullDistance = MinPullDistance;
403 const float absoluteMinPullDistance = 50;
404 if (targetCharacter !=
null)
406 if (targetCharacter.IsRagdolled || targetCharacter.IsUnconscious)
408 if (!targetCharacter.InWater)
411 maxVelocity = NetConfig.MaxPhysicsBodyVelocity * 0.075f;
418 minPullDistance = absoluteMinPullDistance;
419 maxPullDistance = 200;
422 minPullDistance = MathHelper.Max(minPullDistance, absoluteMinPullDistance);
423 if (currentRopeLength < minPullDistance) {
return; }
424 maxPullDistance = MathHelper.Max(minPullDistance * 2, maxPullDistance);
425 float force = lerpForces
426 ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(minPullDistance, maxPullDistance, currentRopeLength))
428 targetBody.ApplyForce(-forceDir * force, maxVelocity);
429 AnimController targetRagdoll = targetCharacter?.AnimController;
430 if (targetRagdoll?.Collider !=
null)
433 if (targetRagdoll.InWater || targetRagdoll.OnGround)
435 float forceMultiplier = 1;
436 if (!targetCharacter.IsRagdolled && !targetCharacter.IsIncapacitated)
439 Vector2 targetMovement = targetCharacter.AnimController.TargetMovement;
440 float dot = Vector2.Dot(Vector2.Normalize(targetMovement), forceDir);
443 const float constMultiplier = 2.5f;
444 float targetVelocity = targetMovement.Length();
445 float massFactor = Math.Max((
float)Math.Log(targetCharacter.Mass / 10), 1);
446 forceMultiplier = Math.Max(targetVelocity * massFactor * constMultiplier * dot, 1);
449 targetRagdoll.Collider.ApplyForce(-forceDir * force * forceMultiplier, maxVelocity);
456 partial
void UpdateProjSpecific();
460 base.UpdateBroken(deltaTime, cam);
463 snapTimer += deltaTime;
464 if (snapTimer >= SnapAnimDuration)
475 private Vector2 GetSourcePos(
bool useDrawPosition =
false)
478 if (source is
Item sourceItem)
482 sourcePos = sourceItem.DrawPosition;
484 if (!sourceItem.Removed)
486 if (sourceItem.GetComponent<Turret>() is { } turret)
488 sourcePos =
new Vector2(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y);
490 else if (sourceItem.GetComponent<RangedWeapon>() is { } weapon)
492 sourcePos += ConvertUnits.ToDisplayUnits(weapon.TransformedBarrelPos);
496 else if (useDrawPosition && source is Limb sourceLimb && sourceLimb.body !=
null)
498 sourcePos = sourceLimb.body.DrawPosition;
504 private static PhysicsBody GetBodyToPull(ISpatialEntity target)
506 if (target is Item targetItem)
508 if (targetItem.ParentInventory is CharacterInventory { Owner: Character ownerCharacter })
510 if (ownerCharacter.Removed) {
return null; }
511 return ownerCharacter.AnimController.Collider;
513 var projectile = targetItem.GetComponent<Projectile>();
514 if (projectile is { StickTarget: not
null })
516 return projectile.StickTarget.UserData
switch
518 Structure structure => structure.Submarine?.PhysicsBody,
520 Item item => item.body,
521 Limb limb => limb.body,
525 if (targetItem.body !=
null) {
return targetItem.body; }
527 else if (target is Limb targetLimb)
529 return targetLimb.body;
static NetworkMember NetworkMember
void ResetCachedVisibleSize()
The base class for components holding the different functionalities of the item
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
override void Update(float deltaTime, Camera cam)
Rope(Item item, ContentXElement element)
float ProjectilePullForce
void Attach(ISpatialEntity source, Item target)
override void UpdateBroken(float deltaTime, Camera cam)
Items.Components.Rope AttachedRope
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.