4 using FarseerPhysics.Common;
5 using FarseerPhysics.Dynamics;
6 using FarseerPhysics.Dynamics.Contacts;
7 using FarseerPhysics.Dynamics.Joints;
8 using Microsoft.Xna.Framework;
10 using System.Collections.Generic;
11 using System.Diagnostics;
22 Physics.CollisionItem |
23 Physics.CollisionLevel |
24 Physics.CollisionCharacter |
25 Physics.CollisionProjectile |
26 Physics.CollisionWall;
28 const float HorizontalDrag = 0.01f;
29 const float VerticalDrag = 0.05f;
30 const float MaxDrag = 0.1f;
32 private const float ImpactDamageMultiplier = 10.0f;
35 private const float MinImpactLimbMass = 10.0f;
37 private const float MinCollisionImpact = 3.0f;
39 private const float MaxCollisionImpact = 5.0f;
40 private const float Friction = 0.2f, Restitution = 0.0f;
42 private readonly List<Contact> levelContacts =
new List<Contact>();
50 private float depthDamageTimer = 10.0f;
51 private float damageSoundTimer = 10.0f;
57 private readonly List<PosInfo> positionBuffer =
new List<PosInfo>();
59 private readonly Queue<Impact> impactQueue =
new Queue<Impact>();
61 private float forceUpwardsTimer;
62 private const float ForceUpwardsDelay = 30.0f;
68 public Vector2 ImpactPos;
71 public Impact(Fixture f1, Fixture f2, Contact contact)
74 contact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> points);
75 if (contact.FixtureA.Body == f1.Body) { contactNormal = -contactNormal; }
76 ImpactPos = points[0];
78 Velocity = f1.Body.LinearVelocity - f2.Body.LinearVelocity;
105 if (!MathUtils.IsValid(value))
return;
117 get {
return positionBuffer; }
122 get {
return submarine; }
127 this.submarine = sub;
129 Vector2 minExtents = Vector2.Zero, maxExtents = Vector2.Zero;
130 Vector2 visibleMinExtents = Vector2.Zero, visibleMaxExtents = Vector2.Zero;
131 Body farseerBody =
null;
134 farseerBody =
GameMain.
World.CreateRectangle(1.0f, 1.0f, 1.0f);
135 if (showErrorMessages)
137 DebugConsole.ThrowError($
"No hulls found in the submarine \"{sub.Info.Name}\". Generating a physics body for the submarine failed.");
142 List<Vector2> convexHull = GenerateConvexHull();
143 for (
int i = 0; i < convexHull.Count; i++)
145 convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]);
150 farseerBody =
GameMain.
World.CreateBody(findNewContacts:
false, bodyType: BodyType.Dynamic);
151 var collisionCategory = Physics.CollisionWall;
153 Physics.CollisionItem |
154 Physics.CollisionLevel |
155 Physics.CollisionCharacter |
156 Physics.CollisionProjectile |
157 Physics.CollisionWall;
158 farseerBody.CollisionCategories = collisionCategory;
159 farseerBody.CollidesWith = collidesWith;
160 farseerBody.Enabled =
false;
161 farseerBody.UserData =
this;
164 farseerBody.BodyType = BodyType.Static;
168 if (mapEntity.Submarine != submarine || mapEntity is not
Structure wall) {
continue; }
170 bool hasCollider = wall.HasBody && !wall.IsPlatform && wall.StairDirection ==
Direction.None;
171 Rectangle rect = wall.Rect;
173 var transformedQuad = wall.GetTransformedQuad();
174 AddPointToExtents(transformedQuad.A, hasCollider: hasCollider);
175 AddPointToExtents(transformedQuad.B, hasCollider: hasCollider);
176 AddPointToExtents(transformedQuad.C, hasCollider: hasCollider);
177 AddPointToExtents(transformedQuad.D, hasCollider: hasCollider);
180 farseerBody.CreateRectangle(
181 ConvertUnits.ToSimUnits(wall.BodyWidth),
182 ConvertUnits.ToSimUnits(wall.BodyHeight),
185 ConvertUnits.ToSimUnits(
new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + wall.BodyOffset),
187 collidesWith).UserData = wall;
195 Rectangle rect = hull.
Rect;
196 AddPointToExtents(
new Vector2(rect.X, rect.Y - rect.Height), hasCollider:
true);
197 AddPointToExtents(
new Vector2(rect.Right, rect.Y), hasCollider:
true);
199 farseerBody.CreateRectangle(
200 ConvertUnits.ToSimUnits(rect.Width),
201 ConvertUnits.ToSimUnits(rect.Height),
203 ConvertUnits.ToSimUnits(
new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
205 collidesWith).UserData = hull;
210 if (item.
Submarine != submarine) {
continue; }
212 Vector2 simPos = ConvertUnits.ToSimUnits(item.
Position);
213 if (sub.
FlippedX) { simPos.X = -simPos.X; }
214 if (item.GetComponent<
Door>() is
Door door)
216 door.OutsideSubmarineFixture = farseerBody.CreateRectangle(door.Body.Width, door.Body.Height, 5.0f, simPos, collisionCategory, collidesWith);
217 door.OutsideSubmarineFixture.UserData = item;
226 float simRadius = ConvertUnits.ToSimUnits(radius);
227 float simWidth = ConvertUnits.ToSimUnits(width);
228 float simHeight = ConvertUnits.ToSimUnits(height);
230 if (radius > 0f || (width > 0f && height > 0f))
233 AddPointToExtents(transformedQuad.A, hasCollider:
true);
234 AddPointToExtents(transformedQuad.B, hasCollider:
true);
235 AddPointToExtents(transformedQuad.C, hasCollider:
true);
236 AddPointToExtents(transformedQuad.D, hasCollider:
true);
239 if (width > 0.0f && height > 0.0f)
241 item.
StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos, collisionCategory, collidesWith));
242 AddPointToExtents(item.
Position -
new Vector2(width, height) / 2, hasCollider:
true);
243 AddPointToExtents(item.
Position +
new Vector2(width, height) / 2, hasCollider:
true);
245 else if (radius > 0.0f && width > 0.0f)
247 item.
StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos, collisionCategory, collidesWith));
248 item.
StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2, collisionCategory, collidesWith));
249 item.
StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2, collisionCategory, collidesWith));
250 AddPointToExtents(item.
Position -
new Vector2(width / 2 + radius, height / 2), hasCollider:
true);
251 AddPointToExtents(item.
Position +
new Vector2(width / 2 + radius, height / 2), hasCollider:
true);
253 else if (radius > 0.0f && height > 0.0f)
255 item.
StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos, collisionCategory, collidesWith));
256 item.
StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2, collisionCategory, collidesWith));
257 item.
StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitY * simHeight / 2, collisionCategory, collidesWith));
258 AddPointToExtents(item.
Position -
new Vector2(width / 2, height / 2 + radius), hasCollider:
true);
259 AddPointToExtents(item.
Position +
new Vector2(width / 2, height / 2 + radius), hasCollider:
true);
261 else if (radius > 0.0f)
263 item.
StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos, collisionCategory, collidesWith));
264 AddPointToExtents(item.
Position -
new Vector2(radius, radius), hasCollider:
true);
265 AddPointToExtents(item.
Position +
new Vector2(radius, radius), hasCollider:
true);
270 Borders =
new Rectangle((
int)minExtents.X, (
int)maxExtents.Y, (
int)(maxExtents.X - minExtents.X), (
int)(maxExtents.Y - minExtents.Y));
271 VisibleBorders =
new Rectangle((
int)visibleMinExtents.X, (
int)visibleMaxExtents.Y, (
int)(visibleMaxExtents.X - visibleMinExtents.X), (
int)(visibleMaxExtents.Y - visibleMinExtents.Y));
274 farseerBody.Enabled =
true;
275 farseerBody.Restitution = Restitution;
276 farseerBody.Friction = Friction;
277 farseerBody.FixedRotation =
true;
278 farseerBody.Awake =
true;
279 farseerBody.SleepingAllowed =
false;
280 farseerBody.IgnoreGravity =
true;
282 farseerBody.UserData = submarine;
286 void AddPointToExtents(Vector2 point,
bool hasCollider)
288 visibleMinExtents.X = Math.Min(point.X, visibleMinExtents.X);
289 visibleMinExtents.Y = Math.Min(point.Y, visibleMinExtents.Y);
290 visibleMaxExtents.X = Math.Max(point.X, visibleMaxExtents.X);
291 visibleMaxExtents.Y = Math.Max(point.Y, visibleMaxExtents.Y);
294 minExtents.X = Math.Min(point.X, minExtents.X);
295 minExtents.Y = Math.Min(point.Y, minExtents.Y);
296 maxExtents.X = Math.Max(point.X, maxExtents.X);
297 maxExtents.Y = Math.Max(point.Y, maxExtents.Y);
302 private List<Vector2> GenerateConvexHull()
304 List<Structure> subWalls =
Structure.
WallList.FindAll(wall => wall.Submarine == submarine);
306 if (subWalls.Count == 0)
308 return new List<Vector2> {
new Vector2(-1.0f, 1.0f),
new Vector2(1.0f, 1.0f),
new Vector2(0.0f, -1.0f) };
311 List<Vector2> points =
new List<Vector2>();
313 foreach (Structure wall
in subWalls)
315 points.Add(
new Vector2(wall.Rect.X, wall.Rect.Y));
316 points.Add(
new Vector2(wall.Rect.X + wall.Rect.Width, wall.Rect.Y));
317 points.Add(
new Vector2(wall.Rect.X, wall.Rect.Y - wall.Rect.Height));
318 points.Add(
new Vector2(wall.Rect.X + wall.Rect.Width, wall.Rect.Y - wall.Rect.Height));
321 List<Vector2> hullPoints = MathUtils.GiftWrap(points);
328 while (impactQueue.Count > 0)
330 var impact = impactQueue.Dequeue();
334 HandleLevelCollision(impact, cell);
336 else if (impact.Target.Body.UserData is
Structure)
338 HandleLevelCollision(impact);
340 else if (impact.Target.Body.UserData is
Submarine otherSub)
342 HandleSubCollision(impact, otherSub);
344 else if (impact.Target.Body.UserData is
Limb limb)
346 HandleLimbCollision(impact, limb);
354 ClientUpdatePosition(deltaTime);
357 Vector2 totalForce = CalculateBuoyancy();
364 Rectangle worldBorders =
Borders;
365 worldBorders.Location += MathUtils.ToPoint(
Position);
401 float force = distance * 0.5f;
402 totalForce += (
Position.X < 0 ? Vector2.UnitX : -Vector2.UnitX) * force;
415 if (totalForce.Y > 0)
418 while (contactEdge?.
Contact !=
null)
420 if (contactEdge.Contact.Enabled &&
421 contactEdge.Other.UserData is
Submarine otherSubmarine &&
423 contactEdge.Contact.IsTouching)
425 contactEdge.Contact.GetWorldManifold(out Vector2 _, out FixedArray2<Vector2> points);
427 !
Character.
CharacterList.Any(c => c.Submarine == otherSubmarine && !c.IsIncapacitated && c.TeamID == otherSubmarine.TeamID))
429 otherSubmarine.GetConnectedSubs().ForEach(s => s.SubBody.forceUpwardsTimer += deltaTime);
433 contactEdge = contactEdge.Next;
442 float attachedMass = 0.0f;
444 while (jointEdge !=
null)
446 Body otherBody = jointEdge.Joint.BodyA ==
Body.
FarseerBody ? jointEdge.Joint.BodyB : jointEdge.Joint.BodyA;
447 Character character = (otherBody.UserData as
Limb)?.character;
448 if (character !=
null) attachedMass += character.
Mass;
450 jointEdge = jointEdge.Next;
453 float horizontalDragCoefficient = MathHelper.Clamp(HorizontalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag);
456 float verticalDragCoefficient = MathHelper.Clamp(VerticalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag);
462 if (
Velocity.LengthSquared() < 0.01f)
464 levelContacts.Clear();
465 levelContacts.AddRange(GetLevelContacts(
Body));
466 for (
int i = 0; i < levelContacts.Count; i++)
468 for (
int j = i + 1; j < levelContacts.Count; j++)
470 levelContacts[i].GetWorldManifold(out Vector2 normal1, out _);
471 levelContacts[j].GetWorldManifold(out Vector2 normal2, out _);
474 if (Vector2.Dot(normal1, normal2) < 0)
478 i = levelContacts.Count;
485 UpdateDepthDamage(deltaTime);
487 forceUpwardsTimer = MathHelper.Clamp(forceUpwardsTimer - deltaTime * 0.1f, 0.0f, ForceUpwardsDelay);
490 partial
void ClientUpdatePosition(
float deltaTime);
497 private void DisplaceCharacters(Vector2 subTranslation)
499 Rectangle worldBorders =
Borders;
500 worldBorders.Location += MathUtils.ToPoint(ConvertUnits.ToDisplayUnits(
Body.
SimPosition));
502 Vector2 translateDir = Vector2.Normalize(subTranslation);
503 if (!MathUtils.IsValid(translateDir)) translateDir = Vector2.UnitY;
514 if (limb.IsSevered) {
continue; }
520 if (!MathUtils.GetLineRectangleIntersection(limb.WorldPosition,
521 limb.WorldPosition + translateDir * 100000.0f, worldBorders, out Vector2 intersection))
538 private Vector2 CalculateBuoyancy()
540 if (
Submarine.LockY) {
return Vector2.Zero; }
546 var connectedSubs = submarine.GetConnectedSubs();
547 float waterVolume = 0.0f;
549 float totalMass = connectedSubs.Sum(s => s.SubBody.Body.Mass);
550 foreach (Hull hull
in Hull.HullList)
552 if (hull.Submarine ==
null || !connectedSubs.Contains(hull.Submarine)) {
continue; }
553 if (hull.Submarine.PhysicsBody is not { BodyType: BodyType.Dynamic }) {
continue; }
554 waterVolume += hull.WaterVolume;
555 volume += hull.Volume;
558 float waterPercentage = volume <= 0.0f ? 0.0f : waterVolume / volume;
560 float massRatio =
Body.
Mass / totalMass;
567 buoyancy = Math.Max(buoyancy, -0.5f);
569 if (forceUpwardsTimer > 0.0f)
571 buoyancy = MathHelper.Lerp(buoyancy, 0.1f, forceUpwardsTimer / ForceUpwardsDelay);
573 return new Vector2(0.0f, buoyancy * totalMass * 10.0f) * massRatio;
586 private void UpdateDepthDamage(
float deltaTime)
589 if (Level.Loaded ==
null) {
return; }
592 const float CosmeticEffectThreshold = -500.0f;
594 const float MaxEffectThreshold = 500.0f;
595 const float MinWallDamageProbability = 0.1f;
596 const float MaxWallDamageProbability = 1.0f;
597 const float MinWallDamage = 50f;
598 const float MaxWallDamage = 500.0f;
599 const float MinCameraShake = 10f;
600 const float MaxCameraShake = 50.0f;
603 const float MinRoundDuration = 60.0f;
610 damageSoundTimer -= deltaTime;
611 if (damageSoundTimer <= 0.0f)
613 const float PressureSoundRange = -CosmeticEffectThreshold;
617 SoundPlayer.PlayDamageSound(
"pressure", MathHelper.Lerp(0f, 100f, closenessToCrushDepthRatio), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f, gain: 1f + closenessToCrushDepthRatio * 2);
619 damageSoundTimer = Rand.Range(5.0f, 10.0f);
622 depthDamageTimer -= deltaTime;
623 if (depthDamageTimer <= 0.0f && (GameMain.GameSession ==
null || GameMain.GameSession.RoundDuration > MinRoundDuration))
625 foreach (Structure wall
in Structure.WallList)
627 if (wall.Submarine != submarine) {
continue; }
629 float wallCrushDepth = wall.CrushDepth;
630 float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
631 float pastCrushDepthRatio = Math.Clamp(pastCrushDepth / MaxEffectThreshold, 0.0f, 1.0f);
633 if (Rand.Range(0.0f, 1.0f) > MathHelper.Lerp(MinWallDamageProbability, MaxWallDamageProbability, pastCrushDepthRatio)) {
continue; }
635 float damage = MathHelper.Lerp(MinWallDamage, MaxWallDamage, pastCrushDepthRatio);
636 if (pastCrushDepth > 0)
638 Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, damage, levelWallDamage: 0.0f);
640 SoundPlayer.PlayDamageSound(
"StructureBlunt", Rand.Range(0.0f, 100.0f), wall.WorldPosition, 2000.0f);
645 GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, MathHelper.Lerp(MinCameraShake, MaxCameraShake, pastCrushDepthRatio));
648 depthDamageTimer = Rand.Range(5.0f, 10.0f);
654 List<Vector2> convexHull = GenerateConvexHull();
655 for (
int i = 0; i < convexHull.Count; i++)
657 convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]);
664 if (f2.Body.UserData is
Limb limb)
666 bool collision = CheckCharacterCollision(contact, limb.character);
671 impactQueue.Enqueue(
new Impact(f1, f2, contact));
676 else if (f2.Body.UserData is
Character character)
678 return CheckCharacterCollision(contact, character);
680 else if (f1.UserData is Items.Components.DockingPort || f2.UserData is Items.Components.DockingPort)
687 impactQueue.Enqueue(
new Impact(f1, f2, contact));
692 private bool CheckCharacterCollision(Contact contact,
Character character)
694 if (character.
Submarine !=
null) {
return false; }
702 if (contact.FixtureB.Body ==
707 if (contact.FixtureB.Body.UserData is Limb limb &&
708 !limb.Params.CanEnterSubmarine)
715 contact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> points);
721 Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal * 0.1f);
722 Hull newHull = Hull.FindHull(targetPos,
null);
726 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal);
727 newHull = Hull.FindHull(targetPos,
null);
732 targetPos = ConvertUnits.ToDisplayUnits(points[0] + normalizedVel);
733 newHull = Hull.FindHull(targetPos,
null);
737 if (contact.FixtureA.UserData is not Structure wall || !wall.AllSectionBodiesDisabled())
739 var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
740 Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
741 if (adjacentGap ==
null) {
return true; }
752 CoroutineManager.Invoke(() =>
754 if (character !=
null && !character.
Removed)
756 character.AnimController.FindHull(newHull.WorldPosition, setSubmarine: true);
764 private void HandleLimbCollision(Impact collision, Limb limb)
766 if (limb?.body?.FarseerBody ==
null || limb.character ==
null) {
return; }
768 float impactMass = limb.Mass;
769 var enemyAI = limb.character.AIController as EnemyAIController;
770 float attackMultiplier = 1.0f;
771 if (enemyAI?.ActiveAttack !=
null)
773 impactMass = Math.Max(Math.Max(limb.Mass, limb.character.AnimController.MainLimb.Mass), limb.character.AnimController.Collider.Mass);
774 attackMultiplier = enemyAI.ActiveAttack.SubmarineImpactMultiplier;
777 if (impactMass * attackMultiplier > MinImpactLimbMass &&
Body.
BodyType != BodyType.Static)
780 Vector2.DistanceSquared(
Body.
SimPosition, limb.SimPosition) < 0.0001f ?
784 float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(impactMass / 300.0f, 1);
785 impact *= attackMultiplier;
787 ApplyImpact(impact, normal, collision.ImpactPos, applyDamage:
false);
790 dockedSub.SubBody.ApplyImpact(impact, normal, collision.ImpactPos, applyDamage:
false);
795 IEnumerable<Contact> levelContacts = GetLevelContacts(limb.body);
796 int levelContactCount = levelContacts.Count();
798 if (levelContactCount == 0) {
return; }
803 Vector2 avgContactNormal = Vector2.Zero;
804 foreach (Contact levelContact
in levelContacts)
806 levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> temp);
812 var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.
Center;
813 if (Vector2.Dot(contactNormal, cellDiff) < 0)
815 contactNormal = -contactNormal;
818 avgContactNormal += contactNormal;
821 ApplyImpact((Vector2.Dot(-collision.Velocity, contactNormal) / 2.0f) / levelContactCount, contactNormal, collision.ImpactPos, applyDamage:
false);
823 avgContactNormal /= levelContactCount;
826 if (contactDot > 0.001f)
829 if (!MathUtils.IsValid(velChange))
831 GameAnalyticsManager.AddErrorEventOnce(
832 "SubmarineBody.HandleLimbCollision:" + submarine.ID,
833 GameAnalyticsManager.ErrorSeverity.Error,
834 "Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " +
Body.
LinearVelocity
835 +
", avgContactNormal: " + avgContactNormal
836 +
", contactDot: " + contactDot
837 +
", velChange: " + velChange +
")");
843 if (contactDot > 0.1f)
845 float damageAmount = contactDot *
Body.
Mass / limb.character.Mass;
846 limb.character.LastDamageSource = submarine;
847 limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb,
848 AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(),
851 attackImpulse: Vector2.Zero);
853 if (limb.character.IsDead)
855 foreach (LimbJoint limbJoint
in limb.character.AnimController.LimbJoints)
857 if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb))
continue;
858 limb.character.AnimController.SeverLimbJoint(limbJoint);
865 private static IEnumerable<Contact> GetLevelContacts(PhysicsBody body)
867 ContactEdge contactEdge = body.FarseerBody.ContactList;
868 while (contactEdge?.Contact !=
null)
870 if (contactEdge.Contact.Enabled &&
871 contactEdge.Contact.IsTouching &&
874 yield
return contactEdge.Contact;
876 contactEdge = contactEdge.Next;
880 private void HandleLevelCollision(Impact impact,
VoronoiCell cell =
null)
882 if (GameMain.GameSession !=
null && GameMain.GameSession.RoundDuration < 10)
889 float wallImpact = Vector2.Dot(impact.Velocity, -impact.Normal);
891 ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos);
894 dockedSub.SubBody.ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos);
899 var hitWall = Level.Loaded?.ExtraWalls.Find(w => w.Cells.Contains(cell));
900 if (hitWall !=
null && hitWall.WallDamageOnTouch > 0.0f)
902 var damagedStructures = Explosion.RangedStructureDamage(
903 ConvertUnits.ToDisplayUnits(impact.ImpactPos),
905 hitWall.WallDamageOnTouch,
906 levelWallDamage: 0.0f);
908 PlayDamageSounds(damagedStructures, impact.ImpactPos, wallImpact,
"StructureSlash");
913 HandleLevelCollisionProjSpecific(impact);
917 partial
void HandleLevelCollisionProjSpecific(Impact impact);
919 private void HandleSubCollision(Impact impact,
Submarine otherSub)
921 Debug.Assert(otherSub != submarine);
925 if (submarine.IsAboveLevel)
930 Vector2 normal = impact.Normal;
931 if (impact.Target.Body == otherSub.SubBody.Body.FarseerBody)
936 float thisMass =
Body.
Mass + submarine.DockedTo.Sum(s => s.PhysicsBody.Mass);
937 float otherMass = otherSub.PhysicsBody.Mass + otherSub.DockedTo.Sum(s => s.PhysicsBody.Mass);
938 float massRatio = otherMass / (thisMass + otherMass);
940 float impulse = (Vector2.Dot(impact.Velocity, normal) / 2.0f) * massRatio;
943 ApplyImpact(impulse, normal, impact.ImpactPos);
946 dockedSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos);
950 IEnumerable<Contact> levelContacts = GetLevelContacts(
Body);
951 int levelContactCount = levelContacts.Count();
952 if (levelContactCount == 0) {
return; }
957 Vector2 avgContactNormal = Vector2.Zero;
958 foreach (Contact levelContact
in levelContacts)
960 levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> temp);
966 if (Vector2.Dot(contactNormal, cellDiff) < 0)
968 contactNormal = -contactNormal;
971 avgContactNormal += contactNormal;
974 ApplyImpact((Vector2.Dot(impact.Velocity, contactNormal) / 2.0f) * massRatio / levelContactCount, contactNormal, impact.ImpactPos);
976 avgContactNormal /= levelContactCount;
979 float contactDot = Vector2.Dot(otherSub.PhysicsBody.LinearVelocity, -avgContactNormal);
980 if (contactDot > 0.0f)
982 if (otherSub.PhysicsBody.LinearVelocity.LengthSquared() > 0.0001f)
984 otherSub.PhysicsBody.LinearVelocity -= Vector2.Normalize(otherSub.PhysicsBody.LinearVelocity) * contactDot;
987 impulse = Vector2.Dot(otherSub.Velocity, normal);
988 otherSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos);
991 dockedSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos);
996 private void ApplyImpact(
float impact, Vector2 direction, Vector2 impactPos,
bool applyDamage =
true)
998 if (impact < MinCollisionImpact) {
return; }
1000 Vector2 impulse = direction * impact * 0.5f;
1001 impulse = impulse.ClampLength(MaxCollisionImpact);
1003 float impulseMagnitude = impulse.Length();
1005 if (!MathUtils.IsValid(impulse))
1008 "Invalid impulse in SubmarineBody.ApplyImpact: " + impulse +
1009 ". Direction: " + direction +
", body position: " +
Body.
SimPosition +
", impact: " + impact +
".";
1010 if (GameMain.NetworkMember !=
null)
1012 errorMsg += GameMain.NetworkMember.IsClient ?
" Playing as a client." :
" Hosting a server.";
1014 if (GameSettings.CurrentConfig.VerboseLogging) DebugConsole.ThrowError(errorMsg);
1015 GameAnalyticsManager.AddErrorEventOnce(
1016 "SubmarineBody.ApplyImpact:InvalidImpulse",
1017 GameAnalyticsManager.ErrorSeverity.Error,
1023 if (
Character.Controlled !=
null &&
Character.Controlled.Submarine == submarine &&
Character.Controlled.KnockbackCooldownTimer <= 0.0f)
1025 GameMain.GameScreen.Cam.Shake = Math.Max(impact * 10.0f, GameMain.GameScreen.Cam.Shake);
1028 float angularVelocity =
1029 (impactPos.X -
Body.
SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y
1030 - (impactPos.Y -
Body.
SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X;
1031 GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f);
1036 foreach (Character c
in Character.CharacterList)
1038 if (c.Submarine != submarine) {
continue; }
1039 if (c.KnockbackCooldownTimer > 0.0f) {
continue; }
1041 c.KnockbackCooldownTimer =
Character.KnockbackCooldown;
1043 foreach (Limb limb
in c.AnimController.Limbs)
1045 if (limb.IsSevered) {
continue; }
1046 limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f);
1049 bool holdingOntoSomething =
false;
1050 if (c.SelectedSecondaryItem !=
null)
1052 holdingOntoSomething = c.SelectedSecondaryItem.IsLadder ||
1053 (c.SelectedSecondaryItem.GetComponent<
Controller>()?.LimbPositions.Any() ??
false);
1055 if (!holdingOntoSomething && c.SelectedItem !=
null)
1057 holdingOntoSomething = c.SelectedItem.GetComponent<
Controller>()?.LimbPositions.Any() ??
false;
1059 if (!holdingOntoSomething)
1061 c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
1063 if (impact >= MaxCollisionImpact)
1065 float impactDamage = c.AnimController.GetImpactDamage(impact);
1066 c.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(impactDamage).ToEnumerable(), stun: Math.Min(impulse.Length() * 0.2f, 2.0f), playSound:
true);
1071 foreach (
Item item
in Item.ItemList)
1073 if (item.Submarine != submarine || item.CurrentHull ==
null || item.body ==
null || !item.body.Enabled) {
continue; }
1074 if (item.body.Mass > impulseMagnitude) {
continue; }
1076 item.body.ApplyLinearImpulse(impulse, 10.0f);
1077 item.PositionUpdateInterval = 0.0f;
1080 float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f;
1081 var damagedStructures = Explosion.RangedStructureDamage(
1082 ConvertUnits.ToDisplayUnits(impactPos),
1087 PlayDamageSounds(damagedStructures, impactPos, impact,
"StructureBlunt");
static readonly List< Character > CharacterList
static Character Controlled
readonly AnimController AnimController
virtual Vector2 WorldPosition
static GameScreen GameScreen
static NetworkMember NetworkMember
static GameSession GameSession
static readonly List< Hull > HullList
override Quad2D GetTransformedQuad()
override Vector2? Position
readonly XElement StaticBodyConfig
static readonly List< Item > ItemList
List< Fixture > StaticFixtures
const float OutsideBoundsCurrentMargin
How far outside the boundaries of the level the water current that pushes subs towards the level star...
const float OutsideBoundsCurrentHardLimit
How far outside the boundaries of the level the current stops submarines entirely
const float OutsideBoundsCurrentMarginExponential
How far outside the boundaries of the level the strength of the current starts to increase exponentia...
static readonly List< MapEntity > MapEntityList
void ApplyForce(Vector2 force, float maxVelocity=NetConfig.MaxPhysicsBodyVelocity)
bool SetTransform(Vector2 simPosition, float rotation, bool setPrevTransform=true)
CanEnterSubmarine CanEnterSubmarine
void SetPosition(Vector2 simPosition, bool lerp=false, bool ignorePlatforms=true, bool forceMainLimbToCollider=false, bool moveLatchers=true)
static List< Structure > WallList
Rectangle VisibleBorders
Extents of all the visible items/structures/hulls (including ones without a physics body)
List< PosInfo > PositionBuffer
Rectangle Borders
Extents of the solid items/structures (ones with a physics body) and hulls
bool OnCollision(Fixture f1, Fixture f2, Contact contact)
void ApplyForce(Vector2 force)
SubmarineBody(Submarine sub, bool showErrorMessages=true)
readonly PhysicsBody Body
void SetPosition(Vector2 position)
const Category CollidesWith
const float NeutralBallastPercentage
void Update(float deltaTime)
List< Vector2 > HullVertices
IEnumerable< Submarine > DockedTo
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
float RealWorldCrushDepth
float? RealWorldDepth
How deep down the sub is from the surface of Europa in meters (affected by level type,...
@ Character
Characters only
@ Structure
Structures and hulls, but also items (for backwards support)!