3 using FarseerPhysics.Dynamics;
4 using FarseerPhysics.Dynamics.Contacts;
5 using FarseerPhysics.Dynamics.Joints;
6 using Microsoft.Xna.Framework;
8 using System.Collections.Generic;
9 using System.Collections.Immutable;
17 private static readonly ImmutableArray<float> spreadPool;
21 spreadPool = Enumerable.Range(0,
byte.MaxValue + 1).Select(f => (
float)random.
NextDouble() - 0.5f).ToImmutableArray();
33 public Fixture Fixture;
36 public float Fraction;
37 public HitscanResult(Fixture fixture, Vector2 point, Vector2 normal,
float fraction)
47 public Fixture Fixture;
49 public Vector2 LinearVelocity;
51 public Impact(Fixture fixture, Vector2 normal, Vector2 velocity)
55 LinearVelocity = velocity;
71 private readonly Queue<Impact> impactQueue =
new Queue<Impact>();
73 private bool removePending;
75 private byte spreadIndex;
78 const float ContinuousCollisionThreshold = 5.0f;
80 private Joint stickJoint;
81 private Vector2 jointAxis;
85 private Vector2 launchPos;
88 private readonly HashSet<Body> hits =
new HashSet<Body>();
102 get {
return _user; }
117 [
Serialize(10.0f,
IsPropertySaveable.No, description:
"The impulse applied to the physics body of the item when it's launched. Higher values make the projectile faster.")]
120 [
Serialize(0.0f,
IsPropertySaveable.No, description:
"The random percentage modifier used to add variance to the launch impulse.")]
123 [
Serialize(0.0f,
IsPropertySaveable.No, description:
"The rotation of the item relative to the rotation of the weapon when launched (in degrees).")]
187 [
Serialize(
false,
IsPropertySaveable.No, description:
"Hitscan projectiles cast a ray forwards and immediately hit whatever the ray hits. "+
188 "It is recommended to use hitscans for very fast-moving projectiles such as bullets, because using extremely fast launch velocities may cause physics glitches.")]
196 +
"Multiple hitscans can be used to simulate weapons that fire multiple projectiles at the same time" +
197 " without having to actually use multiple projectile items, for example shotguns.")]
225 [
Serialize(
false,
IsPropertySaveable.No, description:
"Override random spread with static spread; projectiles are launched with an equal amount of angle between them. Only applies when firing multiple projectiles.")]
239 private float deactivationTimer;
248 private float stickTimer;
262 private float maxJointTranslationInSimUnits = -1;
271 [
Serialize(
false,
IsPropertySaveable.No, description:
"Enable only if you want to make the projectile ignore collisions with other projectiles when it's shot. Doesn't have any effect, if the item is not set to be damaged by projectiles.")]
291 [
Serialize(
false,
IsPropertySaveable.No, description:
"Can the projectile hit the user? Should generally be disabled, unless the projectile is for example something like shrapnel launched by a projectile impact.")]
300 private Category originalCollisionCategories;
301 private Category originalCollisionTargets;
304 : base (
item, element)
308 foreach (var subElement
in element.Elements())
310 if (!subElement.Name.ToString().Equals(
"attack", StringComparison.OrdinalIgnoreCase)) {
continue; }
316 DebugConsole.ThrowError($
"Error in projectile definition ({item.Name}): No body defined!",
317 contentPackage: element.ContentPackage);
324 InitProjSpecific(element);
353 spreadIndex = (byte)MathUtils.PositiveModulo(spreadIndex, spreadPool.Length);
354 return spreadPool[spreadIndex];
357 private void Launch(
Character user, Vector2 simPosition,
float rotation,
float damageMultiplier = 1f,
float launchImpulseModifier = 0f)
369 Use(character:
null, launchImpulseModifier);
373 launchPos = simPosition;
383 public void Shoot(
Character user, Vector2 weaponPos, Vector2 spawnPos,
float rotation, List<Body> ignoredBodies,
bool createNetworkEvent,
float damageMultiplier = 1f,
float launchImpulseModifier = 0f)
388 Vector2 projectilePos = weaponPos;
391 customPredicate: (Fixture f) => { return IgnoredBodies == null || !IgnoredBodies.Contains(f.Body); }) ==
null)
394 projectilePos = spawnPos;
396 else if ((weaponPos - spawnPos).LengthSquared() > 0.0001f)
399 Vector2 newPos = weaponPos - Vector2.Normalize(spawnPos - projectilePos) * Math.Max(
Item.
body.
GetMaxExtent(), 0.1f);
400 if (MathUtils.IsValid(newPos))
402 projectilePos = newPos;
405 Launch(user, projectilePos, rotation, damageMultiplier, launchImpulseModifier);
409 launchRot = rotation;
410 Item.CreateServerEvent(
this,
new EventData(launch:
true, spreadCounter: (
byte)(spreadIndex - 1)));
415 public bool Use(
Character character =
null,
float launchImpulseModifier = 0f)
418 if (
item.
body ==
null) {
return false; }
427 initialRotation -= MathHelper.Pi;
434 launchAngle = initialRotation + MathHelper.ToRadians(i - ((
float)(
HitScanCount - 1) / 2)) *
Spread;
442 Vector2 launchDir =
new Vector2((
float)Math.Cos(launchAngle), (
float)Math.Sin(launchAngle));
447 DoHitscan(launchDir);
457 DoLaunch(launchDir * modifiedLaunchImpulse);
465 public override bool Use(
float deltaTime,
Character character =
null) =>
Use(character);
467 private void DoLaunch(Vector2 impulse)
486 CoroutineManager.Invoke(() =>
489 prevInventory.CreateNetworkEvent();
492 item.
Drop(
null, createNetworkEvent:
false);
504 else if (impulse.LengthSquared() > 0.001f)
519 EnableProjectileCollisions();
523 if (stickJoint ==
null) {
return; }
526 GameMain.World.Remove(stickJoint);
530 private void DoHitscan(Vector2 dir)
535 item.
Drop(
null, createNetworkEvent:
false);
544 Vector2 rayStart = simPositon;
545 Vector2 rayEnd = rayStart + dir * 500.0f;
547 float worldDist = 1000.0f;
549 worldDist = Screen.Selected?.Cam?.WorldView.Width ?? GameMain.GraphicsWidth;
551 Vector2 rayEndWorld = rayStartWorld + dir * worldDist;
553 List<HitscanResult> hits =
new List<HitscanResult>();
554 hits.AddRange(DoRayCast(rayStart, rayEnd, submarine:
item.
Submarine));
565 RayCastInOtherSubs(rayStart, rayEnd);
568 void RayCastInOtherSubs(Vector2 rayStart, Vector2 rayEnd)
571 foreach (Submarine submarine
in Submarine.Loaded)
574 var inSubHits = DoRayCast(rayStart - submarine.SimPosition, rayEnd - submarine.SimPosition, submarine);
576 for (
int i = 0; i < inSubHits.Count; i++)
578 inSubHits[i] =
new HitscanResult(
579 inSubHits[i].Fixture,
580 inSubHits[i].Point + submarine.SimPosition,
582 inSubHits[i].Fraction);
584 hits.AddRange(inSubHits);
590 hits = hits.OrderBy(h => h.Fraction).ToList();
591 for (
int i = 0; i < hits.Count; i++)
596 if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
610 LaunchProjSpecific(rayStartWorld, rayEndWorld);
611 if (Entity.Spawner ==
null)
617 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsClient)
624 Entity.Spawner.AddItemToRemoveQueue(
item);
630 private List<HitscanResult> DoRayCast(Vector2 rayStart, Vector2 rayEnd, Submarine submarine)
632 List<HitscanResult> hits =
new List<HitscanResult>();
634 Vector2 dir = rayEnd - rayStart;
635 dir = dir.LengthSquared() < 0.00001f ? Vector2.UnitY : Vector2.Normalize(dir);
638 var aabb =
new FarseerPhysics.Collision.AABB(rayStart - Vector2.One * 0.001f, rayStart + Vector2.One * 0.001f);
639 GameMain.World.QueryAABB((fixture) =>
641 if (fixture?.Body.UserData is LevelObject levelObj)
643 if (!levelObj.Prefab.TakeLevelWallDamage) { return true; }
645 else if (fixture?.Body ==
null || fixture.IsSensor)
650 if (fixture.Body.UserData is VineTile) { return true; }
651 if (fixture.CollidesWith == Category.None) { return true; }
653 if (fixture.CollidesWith == Physics.CollisionCharacter) { return true; }
655 if (fixture.Body.UserData as
string ==
"ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; }
658 if (submarine !=
null)
660 if (fixture.Body.UserData is
VoronoiCell) { return true; }
661 if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return true; }
664 if (fixture.Body.UserData is
VoronoiCell && (
this.item.Submarine !=
null || submarine !=
null)) { return true; }
666 if (fixture.Body.UserData is
Item item)
668 if (item == Item) { return true; }
672 else if (fixture.Body.UserData is Holdable { CanPush: false })
681 if (!fixture.CollisionCategories.HasFlag(Physics.CollisionCharacter) &&
682 !fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
683 !fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return true; }
686 fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform);
687 if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
689 hits.Add(
new HitscanResult(fixture, rayStart, -dir, 0.0f));
693 GameMain.World.RayCast((fixture, point, normal, fraction) =>
696 if (fixture?.Body.UserData is LevelObject levelObj)
698 if (!levelObj.Prefab.TakeLevelWallDamage) { return -1; }
700 else if (fixture?.Body ==
null || fixture.IsSensor)
705 if (fixture.Body.UserData is VineTile) { return -1; }
706 if (fixture.CollidesWith == Category.None) { return -1; }
708 if (fixture.CollidesWith == Physics.CollisionCharacter) { return -1; }
709 if (fixture.Body.UserData is Item item)
711 if (item.Condition <= 0) { return -1; }
712 if (!item.Prefab.DamagedByProjectiles && item.GetComponent<Door>() ==
null) { return -1; }
714 if (fixture.Body.UserData as
string ==
"ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; }
717 if (submarine !=
null)
719 if (fixture.Body.UserData is
VoronoiCell) { return -1; }
720 if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return -1; }
721 if (fixture.Body.UserData is Limb limb && limb.character?.Submarine != submarine) { return -1; }
722 if (fixture.Body == Level.Loaded?.TopBarrier || fixture.Body == Level.Loaded?.BottomBarrier) { return -1; }
726 if (fixture.Body.UserData is Holdable { CanPush: false })
734 if (Hull.FindHull(ConvertUnits.ToDisplayUnits(point), this.item.CurrentHull) != null && this.item.Submarine != null)
742 float furthestHit = 0.0f;
743 int furthestHitIndex = -1;
744 for (int i = 0; i < hits.Count; i++)
746 if (hits[i].Fraction > furthestHit)
748 furthestHitIndex = i;
749 furthestHit = hits[i].Fraction;
752 if (furthestHitIndex > -1)
754 hits.RemoveAt(furthestHitIndex);
758 hits.Add(
new HitscanResult(fixture, point, normal, fraction));
761 }, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking | Physics.CollisionProjectile);
771 DisableProjectileCollisions();
774 base.Drop(dropper, setTransform);
779 if (DeactivationTime > 0)
781 deactivationTimer -= deltaTime;
782 if (deactivationTimer < 0)
784 DisableProjectileCollisions();
787 while (impactQueue.Count > 0)
789 var impact = impactQueue.Dequeue();
790 HandleProjectileCollision(impact.Fixture, impact.Normal, impact.LinearVelocity);
796 ApplyStatusEffects(
ActionType.OnActive, deltaTime, useTarget: useTarget, user: _user);
799 if (item.body !=
null && item.body.FarseerBody.IsBullet)
801 if (item.body.LinearVelocity.LengthSquared() < ContinuousCollisionThreshold * ContinuousCollisionThreshold)
803 item.body.FarseerBody.IsBullet =
false;
807 if (stickJoint ==
null && !item.body.FarseerBody.IsBullet)
810 if (DeactivationTime > 0 && deactivationTimer > 0)
812 DisableProjectileCollisions();
816 if (stickJoint ==
null) {
return; }
818 if (StickDuration > 0 && stickTimer > 0)
820 stickTimer -= deltaTime;
824 float absoluteMaxTranslation = 100;
826 if (StickTarget?.UserData is
Limb target && target.
Submarine != item.
Submarine || stickJoint is PrismaticJoint prismaticJoint && Math.Abs(prismaticJoint.JointTranslation) > absoluteMaxTranslation)
828 item.UpdateTransform();
833 if (StickTargetRemoved() || stickJoint is PrismaticJoint pJoint && Math.Abs(pJoint.JointTranslation) > maxJointTranslationInSimUnits)
837 item.CreateServerEvent(
this,
new EventData(launch:
false));
843 private bool StickTargetRemoved()
845 if (StickTarget ==
null) {
return true; }
846 if (StickTarget.UserData is
Limb limb) {
return limb.character.Removed; }
847 if (StickTarget.UserData is
Entity entity) {
return entity.Removed; }
851 private bool OnProjectileCollision(Fixture f1, Fixture target, Contact contact)
853 if (User !=
null && User.Removed) { User =
null;
return false; }
854 if (IgnoredBodies !=
null && IgnoredBodies.Contains(target.Body)) {
return false; }
855 if (originalCollisionCategories == Category.None && originalCollisionTargets == Category.None) {
return false; }
857 if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
861 if (target.IsSensor) {
return false; }
862 if (hits.Contains(target.Body)) {
return false; }
863 if (target.Body.UserData is Submarine)
865 if (ShouldIgnoreSubmarineCollision(ref target, contact)) {
return false; }
867 else if (target.Body.UserData is Limb limb)
872 limb.body?.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass * 0.1f, item.SimPosition);
875 if (!FriendlyFire && User !=
null && limb.character.IsFriendly(User))
880 else if (target.Body.UserData is Item item)
882 if (item.Condition <= 0.0f) {
return false; }
883 if (!item.Prefab.DamagedByProjectiles)
885 if (item.GetComponent<Door>() ==
null)
891 else if (target.Body.UserData is Holdable { CanPush: false })
898 if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
903 hits.Add(target.Body);
904 impactQueue.Enqueue(
new Impact(target, contact.Manifold.LocalNormal, item.body.LinearVelocity));
908 item.body.FarseerBody.ResetDynamics();
910 if (hits.Count >= MaxTargetsToHit || target.Body.UserData is
VoronoiCell)
912 DisableProjectileCollisions();
929 return ShouldIgnoreSubmarineCollision(ref target, contact);
932 private bool ShouldIgnoreSubmarineCollision(ref Fixture target, Contact contact)
935 if (item.body.CollisionCategories != Physics.CollisionProjectile)
939 if (target.Body.UserData is
Submarine sub)
948 Vector2 normalizedVel;
950 if (item.body.LinearVelocity.LengthSquared() < 0.001f)
952 normalizedVel = Vector2.Zero;
953 dir = contact.Manifold.LocalNormal;
957 normalizedVel = dir = Vector2.Normalize(item.body.LinearVelocity);
962 item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
963 item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
964 collisionCategory: Physics.CollisionWall);
966 Vector2 launchPosInCurrentCoordinateSpace = launchPos;
967 if (item.body.Submarine ==
null && LaunchSub !=
null)
969 launchPosInCurrentCoordinateSpace += ConvertUnits.ToSimUnits(LaunchSub.
Position);
971 if (wallBody?.FixtureList?.First() !=
null && (wallBody.UserData is Structure || wallBody.UserData is Item) &&
973 Vector2.Dot((item.body.SimPosition + normalizedVel) - launchPosInCurrentCoordinateSpace, dir) > 0)
975 target = wallBody.FixtureList.First();
976 if (hits.Contains(target.Body))
989 private readonly List<ISerializableEntity> targets =
new List<ISerializableEntity>();
990 private Fixture lastTarget;
992 private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal, Vector2 velocity)
994 if (User !=
null && User.Removed) { User =
null; }
995 if (IgnoredBodies !=
null && IgnoredBodies.Contains(target.Body)) {
return false; }
997 if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
1001 lastTarget = target;
1003 int remainingHits = Math.Max(MaxTargetsToHit - hits.Count, 0);
1004 float speedMultiplier = Math.Min(0.4f + remainingHits * 0.1f, 1.0f);
1005 float deflectedSpeedMultiplier = 0.1f;
1007 AttackResult attackResult =
new AttackResult();
1009 if (target.Body.UserData is Submarine submarine && target.UserData is not
Barotrauma.
Item)
1011 item.Move(-submarine.Position, ignoreContacts:
false);
1012 item.Submarine = submarine;
1013 item.body.Submarine = submarine;
1016 else if (target.Body.UserData is Limb limb)
1018 if (!FriendlyFire && User !=
null && limb.character.IsFriendly(User))
1023 if (MaxTargetsToHit > 1)
1025 speedMultiplier = 1f;
1026 deflectedSpeedMultiplier = 0.8f;
1028 if (limb.IsSevered || limb.character ==
null || limb.character.Removed) {
return false; }
1030 limb.character.LastDamageSource = item;
1031 if (Attack !=
null) { attackResult =
Attack.DoDamageToLimb(User ?? Attacker, limb, item.WorldPosition, 1.0f); }
1032 if (limb.character !=
null) { character = limb.character; }
1034 else if ((target.Body.UserData as Item ?? (target.Body.UserData as ItemComponent)?.Item ?? target.UserData as Item) is Item targetItem)
1036 if (targetItem.Removed) {
return false; }
1038 if (target.UserData is Item && targetItem.Submarine !=
null && targetItem.Submarine == Launcher?.Submarine) {
return false; }
1039 if (Attack !=
null && (targetItem.Prefab.DamagedByProjectiles || DamageDoors && targetItem.GetComponent<Door>() !=
null) && targetItem.Condition > 0)
1041 attackResult =
Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f);
1043 if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar &&
Character.Controlled !=
null &&
1046 Character.Controlled.UpdateHUDProgressBar(targetItem,
1047 targetItem.WorldPosition,
1048 targetItem.Condition / targetItem.MaxCondition,
1049 emptyColor: GUIStyle.HealthBarColorLow,
1050 fullColor: GUIStyle.HealthBarColorHigh,
1051 textTag: targetItem.Prefab.ShowNameInHealthBar ? targetItem.Name :
string.Empty);
1056 else if (target.Body.UserData is IDamageable damageable)
1060 Vector2 pos = item.WorldPosition;
1061 if (item.Submarine ==
null && damageable is Structure structure && structure.Submarine !=
null && Vector2.DistanceSquared(item.WorldPosition, structure.WorldPosition) > 10000.0f * 10000.0f)
1063 item.Submarine = structure.Submarine;
1065 attackResult =
Attack.DoDamage(User ?? Attacker, damageable, pos, 1.0f);
1070 if (Level.Loaded?.ExtraWalls.Find(w => w.Body == target.Body) is DestructibleLevelWall destructibleWall)
1072 attackResult =
Attack.DoDamage(User ?? Attacker, destructibleWall, item.WorldPosition, 1.0f);
1076 if (character !=
null) { character.LastDamageSource = item; }
1079 if (User !=
null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(User))
1081 conditionalActionType =
ActionType.OnFailure;
1084 PlaySound(conditionalActionType, user: User);
1088 if (GameMain.NetworkMember ==
null || GameMain.NetworkMember.IsServer)
1090 if (target.Body.UserData is Limb targetLimb)
1092 ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, useTarget: character, user: User);
1093 ApplyStatusEffects(
ActionType.OnImpact, 1.0f, character, targetLimb, useTarget: character, user: User);
1094 var attack = targetLimb.attack;
1098 foreach (var effect
in attack.StatusEffects)
1102 if (effect.HasTargetType(StatusEffect.TargetType.This))
1104 effect.Apply(effect.type, 1.0f, User, User);
1106 if (effect.HasTargetType(StatusEffect.TargetType.Character) || effect.HasTargetType(StatusEffect.TargetType.UseTarget))
1108 effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character);
1110 if (effect.HasTargetType(StatusEffect.TargetType.Limb))
1112 effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb);
1114 if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
1115 effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
1118 effect.AddNearbyTargets(targetLimb.WorldPosition, targets);
1119 effect.Apply(effect.type, 1.0f, targetLimb.character, targets);
1124 if (GameMain.NetworkMember is { IsServer: true } server)
1126 server.CreateEntityEvent(item,
new Item.ApplyStatusEffectEventData(conditionalActionType,
this, targetLimb.character, targetLimb, useTarget: targetLimb.character, item.WorldPosition));
1127 server.CreateEntityEvent(item,
new Item.ApplyStatusEffectEventData(
ActionType.OnImpact,
this, targetLimb.character, targetLimb, useTarget: targetLimb.character, item.WorldPosition));
1132 ApplyStatusEffects(conditionalActionType, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
1133 ApplyStatusEffects(
ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
1134 if (GameMain.NetworkMember is { IsServer: true } server)
1136 server.CreateEntityEvent(item,
new Item.ApplyStatusEffectEventData(conditionalActionType,
this, useTarget: target.Body.UserData as Entity, worldPosition: item.WorldPosition));
1137 server.CreateEntityEvent(item,
new Item.ApplyStatusEffectEventData(
ActionType.OnImpact,
this, useTarget: target.Body.UserData as Entity, worldPosition: item.WorldPosition));
1142 target.Body.ApplyLinearImpulse(velocity * item.body.Mass);
1143 target.Body.LinearVelocity = target.Body.LinearVelocity.ClampLength(NetConfig.MaxPhysicsBodyVelocity * 0.5f);
1145 if (hits.Count >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is
VoronoiCell)
1147 DisableProjectileCollisions();
1150 if (attackResult.AppliedDamageModifiers !=
null && attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles) && !StickToDeflective)
1152 item.body.LinearVelocity *= deflectedSpeedMultiplier;
1154 else if ( remainingHits <= 0 &&
1155 stickJoint ==
null && StickTarget ==
null &&
1156 StickToStructures && target.Body.UserData is Structure ||
1157 ((StickToLightTargets || target.Body.Mass > item.body.Mass * 0.5f) &&
1159 (StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) ||
1160 (target.Body.UserData is Item i && (i.GetComponent<Door>() !=
null ? StickToDoors : StickToItems)))))
1162 Vector2 dir =
new Vector2(
1163 (
float)Math.Cos(item.body.Rotation),
1164 (
float)Math.Sin(item.body.Rotation));
1166 if (GameMain.NetworkMember ==
null || GameMain.NetworkMember.IsServer)
1168 if (target.Body.UserData is Structure structure && structure.Submarine != item.Submarine && structure.Submarine !=
null)
1170 StickToTarget(structure.Submarine.PhysicsBody.FarseerBody, dir);
1174 StickToTarget(target.Body, dir);
1178 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsServer)
1180 item.CreateServerEvent(
this,
new EventData(launch:
false));
1183 item.body.LinearVelocity *= speedMultiplier;
1189 item.body.LinearVelocity *= speedMultiplier;
1192 var containedItems = item.OwnInventory?.AllItems;
1193 if (containedItems !=
null)
1195 foreach (Item contained
in containedItems)
1197 if (contained.body !=
null)
1199 contained.SetTransform(item.SimPosition, contained.body.Rotation);
1206 removePending =
true;
1207 item.HiddenInGame =
true;
1208 item.body.FarseerBody.Enabled =
false;
1211 CoroutineManager.Invoke(() =>
1213 if (item.Removed) { return; }
1214 Entity.Spawner?.AddItemToRemoveQueue(item);
1221 private void EnableProjectileCollisions()
1223 if (item.body.CollisionCategories != Category.None)
1225 item.body.CollisionCategories = Physics.CollisionProjectile;
1226 item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
1228 if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive)
1230 if (item.body.CollisionCategories == Category.None) { item.body.CollisionCategories = Physics.CollisionCharacter; }
1231 item.body.CollidesWith |= Physics.CollisionProjectile;
1235 private void DisableProjectileCollisions()
1237 if (item?.body?.FarseerBody ==
null) {
return; }
1238 item.body.FarseerBody.OnCollision -= OnProjectileCollision;
1239 if (originalCollisionCategories != Category.None && originalCollisionTargets != Category.None)
1241 item.body.CollisionCategories = originalCollisionCategories;
1242 item.body.CollidesWith = originalCollisionTargets;
1246 if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
1248 item.body.CollisionCategories = Physics.CollisionCharacter;
1249 item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
1253 item.body.CollisionCategories = Physics.CollisionItem;
1254 item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
1257 IgnoredBodies?.Clear();
1262 return stickJoint !=
null && (stickJoint.BodyA == body?.
FarseerBody || stickJoint.BodyB == body?.
FarseerBody);
1265 private void StickToTarget(Body targetBody, Vector2 axis)
1267 if (stickJoint !=
null) {
return; }
1269 item.body.ResetDynamics();
1272 stickJoint =
new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, useWorldCoordinates:
true)
1274 MotorEnabled =
true,
1275 MaxMotorForce = 30.0f,
1276 LimitEnabled =
true,
1277 Breakpoint = 1000.0f
1280 if (maxJointTranslationInSimUnits == -1)
1282 if (item.Sprite !=
null && MaxJointTranslation < 0)
1284 MaxJointTranslation = item.Sprite.size.X / 2 * item.Scale;
1286 MaxJointTranslation = Math.Min(MaxJointTranslation, 1000);
1287 maxJointTranslationInSimUnits = ConvertUnits.ToSimUnits(MaxJointTranslation);
1292 stickJoint =
new WeldJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, item.body.SimPosition, useWorldCoordinates:
true)
1294 FrequencyHz = 10.0f,
1298 stickTimer = StickDuration;
1299 StickTarget = targetBody;
1300 GameMain.World.Add(stickJoint);
1302 if (targetBody.UserData is Limb limb)
1304 stickTargetCharacter = limb.character;
1312 if (stickJoint !=
null)
1320 if (!item.body.FarseerBody.IsBullet)
1323 if (DeactivationTime > 0 && deactivationTimer > 0)
1325 DisableProjectileCollisions();
1328 item.GetComponent<
Rope>()?.Snap();
1329 if (stickTargetCharacter !=
null)
1332 stickTargetCharacter =
null;
1338 base.RemoveComponentSpecific();
1339 if (IsStuckToTarget || stickJoint !=
null || stickTargetCharacter !=
null)
1344 partial
void LaunchProjSpecific(Vector2 startLocation, Vector2 endLocation);
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
float DamageMultiplier
Used for multiplying all the damage.
void SetUser(Character user)
readonly HashSet< Projectile > AttachedProjectiles
static NetworkMember NetworkMember
override Vector2? SimPosition
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
Inventory ParentInventory
void ResetWaterDragCoefficient()
Removes the override value -> falls back to using the original value defined in the xml.
void SetTransform(Vector2 simPosition, float rotation, bool findNewHull=true, bool setPrevTransform=true)
float?? WaterDragCoefficient
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
bool DamagedByProjectiles
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)
const float CorrectionDelay
bool Use(Character character=null, float launchImpulseModifier=0f)
override void RemoveComponentSpecific()
bool ShouldIgnoreSubmarineCollision(Fixture target, Contact contact)
Should the collision with the target submarine be ignored (e.g. did the projectile collide with the w...
bool IgnoreProjectilesWhileActive
float LaunchRotationRadians
bool IsAttachedTo(PhysicsBody body)
static void ResetSpreadCounter()
void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List< Body > ignoredBodies, bool createNetworkEvent, float damageMultiplier=1f, float launchImpulseModifier=0f)
float GetSpreadFromPool()
List< Body > IgnoredBodies
override void Update(float deltaTime, Camera cam)
const float WaterDragCoefficient
override bool Use(float deltaTime, Character character=null)
override void Drop(Character dropper, bool setTransform=true)
a Character has dropped the item
float MaxJointTranslation
Projectile(Item item, ContentXElement element)
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
Item Launcher
The item that launched this projectile (if any)
static byte SpreadCounter
readonly Character character
Mersenne Twister based random
override double NextDouble()
Returns random value larger or equal to 0.0 and less than 1.0
void ApplyLinearImpulse(Vector2 impulse)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
Category CollisionCategories
bool SetTransformIgnoreContacts(Vector2 simPosition, float rotation, bool setPrevTransform=true)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
override Vector2 SimPosition
override Vector2? Position
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.