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;
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;
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();
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)
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)
591 if (Level.Loaded ==
null) {
return; }
594 const float CosmeticEffectThreshold = -500.0f;
596 const float MaxEffectThreshold = 500.0f;
597 const float MinWallDamageProbability = 0.1f;
598 const float MaxWallDamageProbability = 1.0f;
599 const float MinWallDamage = 50f;
600 const float MaxWallDamage = 500.0f;
601 const float MinCameraShake = 5f;
602 const float MaxCameraShake = 50.0f;
605 const float MinRoundDuration = 60.0f;
612 damageSoundTimer -= deltaTime;
613 if (damageSoundTimer <= 0.0f)
616 SoundPlayer.PlayDamageSound(
"pressure", Rand.Range(0.0f, 100.0f), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f);
618 damageSoundTimer = Rand.Range(5.0f, 10.0f);
621 depthDamageTimer -= deltaTime;
622 if (depthDamageTimer <= 0.0f && (GameMain.GameSession ==
null || GameMain.GameSession.RoundDuration > MinRoundDuration))
624 foreach (Structure wall
in Structure.WallList)
626 if (wall.Submarine != submarine) {
continue; }
628 float wallCrushDepth = wall.CrushDepth;
629 float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
630 float pastCrushDepthRatio = Math.Clamp(pastCrushDepth / MaxEffectThreshold, 0.0f, 1.0f);
632 if (Rand.Range(0.0f, 1.0f) > MathHelper.Lerp(MinWallDamageProbability, MaxWallDamageProbability, pastCrushDepthRatio)) {
continue; }
634 float damage = MathHelper.Lerp(MinWallDamage, MaxWallDamage, pastCrushDepthRatio);
635 if (pastCrushDepth > 0)
637 Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, damage, levelWallDamage: 0.0f);
639 SoundPlayer.PlayDamageSound(
"StructureBlunt", Rand.Range(0.0f, 100.0f), wall.WorldPosition, 2000.0f);
644 GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, MathHelper.Lerp(MinCameraShake, MaxCameraShake, pastCrushDepthRatio));
647 depthDamageTimer = Rand.Range(5.0f, 10.0f);
653 List<Vector2> convexHull = GenerateConvexHull();
654 for (
int i = 0; i < convexHull.Count; i++)
656 convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]);
663 if (f2.Body.UserData is
Limb limb)
665 bool collision = CheckCharacterCollision(contact, limb.character);
670 impactQueue.Enqueue(
new Impact(f1, f2, contact));
675 else if (f2.Body.UserData is
Character character)
677 return CheckCharacterCollision(contact, character);
679 else if (f1.UserData is Items.Components.DockingPort || f2.UserData is Items.Components.DockingPort)
686 impactQueue.Enqueue(
new Impact(f1, f2, contact));
691 private bool CheckCharacterCollision(Contact contact,
Character character)
693 if (character.
Submarine !=
null) {
return false; }
701 if (contact.FixtureB.Body ==
706 if (contact.FixtureB.Body.UserData is Limb limb &&
707 !limb.Params.CanEnterSubmarine)
714 contact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> points);
720 Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal * 0.1f);
721 Hull newHull = Hull.FindHull(targetPos,
null);
725 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal);
726 newHull = Hull.FindHull(targetPos,
null);
731 targetPos = ConvertUnits.ToDisplayUnits(points[0] + normalizedVel);
732 newHull = Hull.FindHull(targetPos,
null);
736 if (contact.FixtureA.UserData is not Structure wall || !wall.AllSectionBodiesDisabled())
738 var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
739 Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
740 if (adjacentGap ==
null) {
return true; }
751 CoroutineManager.Invoke(() =>
753 if (character !=
null && !character.
Removed)
755 character.AnimController.FindHull(newHull.WorldPosition, setSubmarine: true);
763 private void HandleLimbCollision(Impact collision, Limb limb)
765 if (limb?.body?.FarseerBody ==
null || limb.character ==
null) {
return; }
767 float impactMass = limb.Mass;
768 var enemyAI = limb.character.AIController as EnemyAIController;
769 float attackMultiplier = 1.0f;
770 if (enemyAI?.ActiveAttack !=
null)
772 impactMass = Math.Max(Math.Max(limb.Mass, limb.character.AnimController.MainLimb.Mass), limb.character.AnimController.Collider.Mass);
773 attackMultiplier = enemyAI.ActiveAttack.SubmarineImpactMultiplier;
776 if (impactMass * attackMultiplier > MinImpactLimbMass &&
Body.
BodyType != BodyType.Static)
779 Vector2.DistanceSquared(
Body.
SimPosition, limb.SimPosition) < 0.0001f ?
783 float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(impactMass / 300.0f, 1);
784 impact *= attackMultiplier;
786 ApplyImpact(impact, normal, collision.ImpactPos, applyDamage:
false);
789 dockedSub.SubBody.ApplyImpact(impact, normal, collision.ImpactPos, applyDamage:
false);
794 IEnumerable<Contact> levelContacts = GetLevelContacts(limb.body);
795 int levelContactCount = levelContacts.Count();
797 if (levelContactCount == 0) {
return; }
802 Vector2 avgContactNormal = Vector2.Zero;
803 foreach (Contact levelContact
in levelContacts)
805 levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> temp);
811 var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.
Center;
812 if (Vector2.Dot(contactNormal, cellDiff) < 0)
814 contactNormal = -contactNormal;
817 avgContactNormal += contactNormal;
820 ApplyImpact((Vector2.Dot(-collision.Velocity, contactNormal) / 2.0f) / levelContactCount, contactNormal, collision.ImpactPos, applyDamage:
false);
822 avgContactNormal /= levelContactCount;
825 if (contactDot > 0.001f)
828 if (!MathUtils.IsValid(velChange))
830 GameAnalyticsManager.AddErrorEventOnce(
831 "SubmarineBody.HandleLimbCollision:" + submarine.ID,
832 GameAnalyticsManager.ErrorSeverity.Error,
833 "Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " +
Body.
LinearVelocity
834 +
", avgContactNormal: " + avgContactNormal
835 +
", contactDot: " + contactDot
836 +
", velChange: " + velChange +
")");
842 if (contactDot > 0.1f)
844 float damageAmount = contactDot *
Body.
Mass / limb.character.Mass;
845 limb.character.LastDamageSource = submarine;
846 limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb,
847 AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(),
850 attackImpulse: Vector2.Zero);
852 if (limb.character.IsDead)
854 foreach (LimbJoint limbJoint
in limb.character.AnimController.LimbJoints)
856 if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb))
continue;
857 limb.character.AnimController.SeverLimbJoint(limbJoint);
864 private static IEnumerable<Contact> GetLevelContacts(PhysicsBody body)
866 ContactEdge contactEdge = body.FarseerBody.ContactList;
867 while (contactEdge?.Contact !=
null)
869 if (contactEdge.Contact.Enabled &&
870 contactEdge.Contact.IsTouching &&
873 yield
return contactEdge.Contact;
875 contactEdge = contactEdge.Next;
879 private void HandleLevelCollision(Impact impact,
VoronoiCell cell =
null)
881 if (GameMain.GameSession !=
null && GameMain.GameSession.RoundDuration < 10)
888 float wallImpact = Vector2.Dot(impact.Velocity, -impact.Normal);
890 ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos);
893 dockedSub.SubBody.ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos);
898 var hitWall = Level.Loaded?.ExtraWalls.Find(w => w.Cells.Contains(cell));
899 if (hitWall !=
null && hitWall.WallDamageOnTouch > 0.0f)
901 var damagedStructures = Explosion.RangedStructureDamage(
902 ConvertUnits.ToDisplayUnits(impact.ImpactPos),
904 hitWall.WallDamageOnTouch,
905 levelWallDamage: 0.0f);
907 PlayDamageSounds(damagedStructures, impact.ImpactPos, wallImpact,
"StructureSlash");
912 HandleLevelCollisionProjSpecific(impact);
916 partial
void HandleLevelCollisionProjSpecific(Impact impact);
918 private void HandleSubCollision(Impact impact,
Submarine otherSub)
920 Debug.Assert(otherSub != submarine);
922 Vector2 normal = impact.Normal;
923 if (impact.Target.Body == otherSub.SubBody.Body.FarseerBody)
928 float thisMass =
Body.
Mass + submarine.DockedTo.Sum(s => s.PhysicsBody.Mass);
929 float otherMass = otherSub.PhysicsBody.Mass + otherSub.DockedTo.Sum(s => s.PhysicsBody.Mass);
930 float massRatio = otherMass / (thisMass + otherMass);
932 float impulse = (Vector2.Dot(impact.Velocity, normal) / 2.0f) * massRatio;
935 ApplyImpact(impulse, normal, impact.ImpactPos);
938 dockedSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos);
942 IEnumerable<Contact> levelContacts = GetLevelContacts(
Body);
943 int levelContactCount = levelContacts.Count();
944 if (levelContactCount == 0) {
return; }
949 Vector2 avgContactNormal = Vector2.Zero;
950 foreach (Contact levelContact
in levelContacts)
952 levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> temp);
958 if (Vector2.Dot(contactNormal, cellDiff) < 0)
960 contactNormal = -contactNormal;
963 avgContactNormal += contactNormal;
966 ApplyImpact((Vector2.Dot(impact.Velocity, contactNormal) / 2.0f) * massRatio / levelContactCount, contactNormal, impact.ImpactPos);
968 avgContactNormal /= levelContactCount;
971 float contactDot = Vector2.Dot(otherSub.PhysicsBody.LinearVelocity, -avgContactNormal);
972 if (contactDot > 0.0f)
974 if (otherSub.PhysicsBody.LinearVelocity.LengthSquared() > 0.0001f)
976 otherSub.PhysicsBody.LinearVelocity -= Vector2.Normalize(otherSub.PhysicsBody.LinearVelocity) * contactDot;
979 impulse = Vector2.Dot(otherSub.Velocity, normal);
980 otherSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos);
983 dockedSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos);
988 private void ApplyImpact(
float impact, Vector2 direction, Vector2 impactPos,
bool applyDamage =
true)
990 if (impact < MinCollisionImpact) {
return; }
992 Vector2 impulse = direction * impact * 0.5f;
993 impulse = impulse.ClampLength(MaxCollisionImpact);
995 float impulseMagnitude = impulse.Length();
997 if (!MathUtils.IsValid(impulse))
1000 "Invalid impulse in SubmarineBody.ApplyImpact: " + impulse +
1001 ". Direction: " + direction +
", body position: " +
Body.
SimPosition +
", impact: " + impact +
".";
1002 if (GameMain.NetworkMember !=
null)
1004 errorMsg += GameMain.NetworkMember.IsClient ?
" Playing as a client." :
" Hosting a server.";
1006 if (GameSettings.CurrentConfig.VerboseLogging) DebugConsole.ThrowError(errorMsg);
1007 GameAnalyticsManager.AddErrorEventOnce(
1008 "SubmarineBody.ApplyImpact:InvalidImpulse",
1009 GameAnalyticsManager.ErrorSeverity.Error,
1015 if (
Character.Controlled !=
null &&
Character.Controlled.Submarine == submarine &&
Character.Controlled.KnockbackCooldownTimer <= 0.0f)
1017 GameMain.GameScreen.Cam.Shake = Math.Max(impact * 10.0f, GameMain.GameScreen.Cam.Shake);
1020 float angularVelocity =
1021 (impactPos.X -
Body.
SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y
1022 - (impactPos.Y -
Body.
SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X;
1023 GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f);
1028 foreach (Character c
in Character.CharacterList)
1030 if (c.Submarine != submarine) {
continue; }
1031 if (c.KnockbackCooldownTimer > 0.0f) {
continue; }
1033 c.KnockbackCooldownTimer =
Character.KnockbackCooldown;
1035 foreach (Limb limb
in c.AnimController.Limbs)
1037 if (limb.IsSevered) {
continue; }
1038 limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f);
1041 bool holdingOntoSomething =
false;
1042 if (c.SelectedSecondaryItem !=
null)
1044 holdingOntoSomething = c.SelectedSecondaryItem.IsLadder ||
1045 (c.SelectedSecondaryItem.GetComponent<
Controller>()?.LimbPositions.Any() ??
false);
1047 if (!holdingOntoSomething && c.SelectedItem !=
null)
1049 holdingOntoSomething = c.SelectedItem.GetComponent<
Controller>()?.LimbPositions.Any() ??
false;
1051 if (!holdingOntoSomething)
1053 c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
1055 if (impact >= MaxCollisionImpact)
1057 float impactDamage = c.AnimController.GetImpactDamage(impact);
1058 c.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(impactDamage).ToEnumerable(), stun: Math.Min(impulse.Length() * 0.2f, 2.0f), playSound:
true);
1063 foreach (Item item
in Item.ItemList)
1065 if (item.Submarine != submarine || item.CurrentHull ==
null || item.body ==
null || !item.body.Enabled) {
continue; }
1066 if (item.body.Mass > impulseMagnitude) {
continue; }
1068 item.body.ApplyLinearImpulse(impulse, 10.0f);
1069 item.PositionUpdateInterval = 0.0f;
1072 float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f;
1073 var damagedStructures = Explosion.RangedStructureDamage(
1074 ConvertUnits.ToDisplayUnits(impactPos),
1079 PlayDamageSounds(damagedStructures, impactPos, impact,
"StructureBlunt");
static readonly List< Character > CharacterList
static Character? Controlled
readonly AnimController AnimController
virtual Vector2 WorldPosition
static GameSession?? GameSession
static GameScreen GameScreen
static NetworkMember NetworkMember
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,...