58a14fd054
commit bd9a92df11a8d83d0d0087ce64422f2fba3f6f99 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 20:07:36 2019 +0200 v0.8.9.2 commit 4e1d5fa3744fb8cab80cb006a433d2efe7685562 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 19:20:03 2019 +0200 Moved humpback's cargo spawn position a bit to prevent the cargo from falling down the hatch under it. commit f0ec5530b0c7ecbdaaf8bd266d9a57fac2c92fe9 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 19:18:41 2019 +0200 Fixed structure BodyOffset not being taken into account when generating the collider for the sub. Caused the button outside Orca's button to be impossible to interact with. commit 1360f5511522280d03d608c8eb6f56fb6608aab5 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 19:10:37 2019 +0200 Halved the status effect for damage to submerged items. This might be a bit too generous, let's see. commit 06afb668bc816a345dbd12899b819843549e908a Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 18:22:00 2019 +0200 Changed mantis loading tip to mud raptor, fixed a glaring error in one of the npc conversations commit d2dfa960dd5fa43426eb4dfd02de6c497ae9d838 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 18:17:17 2019 +0200 Fixed water flow forces occasionally being way too high due to bugged force calculation logic when water is flowing from a hull to the one below it. + Added a hard cap to flow forces to prevent this from happening again. Closes #572 commit 21754b39ded247e8c9837d58c6de9658f56a9772 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 17:24:48 2019 +0200 Entities cannot be placed in the sub editor when the cursor is on a UI element. Fixes an entity being placed when selecting an item from them menu while something else is already selected. Closes #1023 commit 97d0cf92f738a058fa0140ec841549a5d6c746a3 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:59:56 2019 +0200 Fixed a typo in one of the affliction descriptions commit ddccbdc9a438199d3cb796789dd9915cc305ec5b Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:59:18 2019 +0200 Fixed sliders buttons becoming invisible when pressed commit bc28473a1c61698c9447825b1c5a13edd6e7bf6c Merge: 2d212802e f3dfe5990 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 16:45:33 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 2d212802ea945c6cbd1c889d304595b68692e05c Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 16:44:30 2019 +0200 corrected the condition at which railgun and coilgun loaders can be repaired commit f3dfe5990a03cb3c577635dffb25883085d919f1 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:41:34 2019 +0200 Fixed second submarine overlapping with the outpost at the end of the level in combat missions. commit 1f224e9b7cae3f94e9b6937d66c171edbd8ef523 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:35:22 2019 +0200 Fixed sub/mode selection setting tickboxes commit 0eb99486454c1bb47b51b20e4d23f42f4147dd8d Merge: ff90fdb35 80785d627 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:08:18 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit ff90fdb352af6800d0dcf224816cf9e831184f1e Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:08:12 2019 +0200 Don't do chat message similarity tests on order messages (caused spam kicks way too easily when giving orders or reporting things) commit af8a350c7998ae2c729b4354c3c02cbcf75cabde Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:06:42 2019 +0200 Fixed Huskappendage.xml not being copied to the output directory commit df3af76d909099e9c9e59f74b1f5b3b381303a99 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 14:06:19 2019 +0200 Revert "Ignore anything but the content path when checking the filepath cases." Doesn't work correctly if there's a folder named "Content" in some subfolder (e.g. "Mods/SomeMod/Content") or if checking file paths somewhere else than the content folder (Submarines, Data, NewWorkshopItem, Mods...) This reverts commit 123e84ea1ac6f4392c293d0d5c64b857bfc4eb30. commit 80785d627c82948ce1784654070a0da8f732ca33 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:43:36 2019 +0200 added better visual feedback when reactor is in poor condition commit e450872721d8e0b47b3ae5792292ea2071a8eefe Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:40:42 2019 +0200 added a small stun effect to railgun shells commit 05792285eab781990bdc3b658a9217ff38704d1e Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:40:03 2019 +0200 added a little extra fuel, moved the position of guns to hull to prevent players getting caught under them commit 8c85c33023b6cec1c18b8743d1f05fd4fc8c2ea7 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 13:58:55 2019 +0200 Fixed crashing when attempting to use active sonar in the sub editor. Closes #1024 commit d71225d8c58fb285cbf207549b77cabdc7e1df11 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:27:06 2019 +0200 Fixed crashing when selecting an item in a fabricator that's linked to containers, but not set to display the contents of the containers commit a6fa5d82c7b1505932b8283ccfef75798b839d92 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:07:40 2019 +0200 Fixed "can't create and entity event for Hull" error messages when attempting to remove a hull with fire sources in it in MP commit 72737c6d3eb7ca7daf5e5d84fdcbc1f060b79b10 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:05:04 2019 +0200 Filename case fix part 913, fixed rewards mentioned in mission descriptions not matching the actual reward of the mission. commit a148c7843f2805b1757d231bcf028bafdbc22187 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:56:18 2019 +0200 Attempt to forward steam query port when UPnP is enabled commit 3e73d705c221a417f60baf76c8c7d100391f7802 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:44:34 2019 +0200 Removed the "training" button from the main menu (not needed, tutorial is now implemented directly in the SP campaign) commit 1f07c3f1b4de0bb09fe11e9834faeea30e4afade Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:33:01 2019 +0200 Fixed crewmanager UI elements hidden outside the extents of the crew list overlapping with other UI elements commit aa0c5f4e90b57a8ee3f152b9aeba1e73c7c539c0 Merge: 123e84ea1 73f51a405 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Fri Feb 1 16:21:27 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 123e84ea1ac6f4392c293d0d5c64b857bfc4eb30 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Fri Feb 1 16:21:19 2019 +0200 Ignore anything but the content path when checking the filepath cases. commit 73f51a4055ca7fd1cd65570a51d88db1f1272d23 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 15:20:15 2019 +0200 Don't show the "incorrect case" errors in non-linux release builds. It seems that Path.GetFullPath may return paths with an incorrect case on Windows (try changing the case of any of the game's parent folders to repro). commit 5a598129a9d63c5185529107d469404c35be59c5 Author: ezjamsen <ezjames.fi@gmail.com> Date: Fri Feb 1 11:29:41 2019 +0200 Renamed hull areas... again.
809 lines
34 KiB
C#
809 lines
34 KiB
C#
using FarseerPhysics;
|
|
using FarseerPhysics.Collision;
|
|
using FarseerPhysics.Common;
|
|
using FarseerPhysics.Dynamics;
|
|
using FarseerPhysics.Dynamics.Contacts;
|
|
using FarseerPhysics.Dynamics.Joints;
|
|
using FarseerPhysics.Factories;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Voronoi2;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class SubmarineBody
|
|
{
|
|
public const float NeutralBallastPercentage = 0.07f;
|
|
|
|
const float HorizontalDrag = 0.01f;
|
|
const float VerticalDrag = 0.05f;
|
|
const float MaxDrag = 0.1f;
|
|
|
|
public const float DamageDepth = -30000.0f;
|
|
private const float ImpactDamageMultiplier = 10.0f;
|
|
|
|
private const float Friction = 0.2f, Restitution = 0.0f;
|
|
|
|
public List<Vector2> HullVertices
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
private float depthDamageTimer;
|
|
|
|
private readonly Submarine submarine;
|
|
|
|
public readonly PhysicsBody Body;
|
|
|
|
private List<PosInfo> memPos = new List<PosInfo>();
|
|
|
|
public Rectangle Borders
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public Vector2 Velocity
|
|
{
|
|
get { return Body.LinearVelocity; }
|
|
set
|
|
{
|
|
if (!MathUtils.IsValid(value)) return;
|
|
Body.LinearVelocity = value;
|
|
}
|
|
}
|
|
|
|
public Vector2 Position
|
|
{
|
|
get { return ConvertUnits.ToDisplayUnits(Body.SimPosition); }
|
|
}
|
|
|
|
public List<PosInfo> MemPos
|
|
{
|
|
get { return memPos; }
|
|
}
|
|
|
|
public bool AtDamageDepth
|
|
{
|
|
get { return Position.Y < DamageDepth; }
|
|
}
|
|
|
|
public Submarine Submarine
|
|
{
|
|
get { return submarine; }
|
|
}
|
|
|
|
public SubmarineBody(Submarine sub, bool showWarningMessages = true)
|
|
{
|
|
this.submarine = sub;
|
|
|
|
Body farseerBody = null;
|
|
if (!Hull.hullList.Any())
|
|
{
|
|
farseerBody = BodyFactory.CreateRectangle(GameMain.World, 1.0f, 1.0f, 1.0f);
|
|
if (showWarningMessages)
|
|
{
|
|
DebugConsole.ThrowError("WARNING: no hulls found, generating a physics body for the submarine failed.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
List<Vector2> convexHull = GenerateConvexHull();
|
|
HullVertices = convexHull;
|
|
for (int i = 0; i < convexHull.Count; i++)
|
|
{
|
|
convexHull[i] = ConvertUnits.ToSimUnits(convexHull[i]);
|
|
}
|
|
|
|
Vector2 minExtents = Vector2.Zero, maxExtents = Vector2.Zero;
|
|
|
|
farseerBody = BodyFactory.CreateBody(GameMain.World, this);
|
|
foreach (Structure wall in Structure.WallList)
|
|
{
|
|
if (wall.Submarine != submarine) continue;
|
|
|
|
Rectangle rect = wall.Rect;
|
|
|
|
FixtureFactory.AttachRectangle(
|
|
ConvertUnits.ToSimUnits(wall.BodyWidth),
|
|
ConvertUnits.ToSimUnits(wall.BodyHeight),
|
|
50.0f,
|
|
-wall.BodyRotation,
|
|
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + wall.BodyOffset),
|
|
farseerBody, this);
|
|
|
|
minExtents.X = Math.Min(rect.X, minExtents.X);
|
|
minExtents.Y = Math.Min(rect.Y - rect.Height, minExtents.Y);
|
|
maxExtents.X = Math.Max(rect.Right, maxExtents.X);
|
|
maxExtents.Y = Math.Max(rect.Y, maxExtents.Y);
|
|
}
|
|
|
|
foreach (Hull hull in Hull.hullList)
|
|
{
|
|
if (hull.Submarine != submarine) continue;
|
|
|
|
Rectangle rect = hull.Rect;
|
|
FixtureFactory.AttachRectangle(
|
|
ConvertUnits.ToSimUnits(rect.Width),
|
|
ConvertUnits.ToSimUnits(rect.Height),
|
|
5.0f,
|
|
ConvertUnits.ToSimUnits(new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2)),
|
|
farseerBody, this);
|
|
|
|
minExtents.X = Math.Min(rect.X, minExtents.X);
|
|
minExtents.Y = Math.Min(rect.Y - rect.Height, minExtents.Y);
|
|
maxExtents.X = Math.Max(rect.Right, maxExtents.X);
|
|
maxExtents.Y = Math.Max(rect.Y, maxExtents.Y);
|
|
}
|
|
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
if (item.StaticBodyConfig == null || item.Submarine != submarine) continue;
|
|
|
|
float radius = item.StaticBodyConfig.GetAttributeFloat("radius", 0.0f) * item.Scale;
|
|
float width = item.StaticBodyConfig.GetAttributeFloat("width", 0.0f) * item.Scale;
|
|
float height = item.StaticBodyConfig.GetAttributeFloat("height", 0.0f) * item.Scale;
|
|
|
|
Vector2 simPos = ConvertUnits.ToSimUnits(item.Position);
|
|
float simRadius = ConvertUnits.ToSimUnits(radius);
|
|
float simWidth = ConvertUnits.ToSimUnits(width);
|
|
float simHeight = ConvertUnits.ToSimUnits(height);
|
|
|
|
if (width > 0.0f && height > 0.0f)
|
|
{
|
|
FixtureFactory.AttachRectangle(simWidth, simHeight, 5.0f, simPos, farseerBody, this).UserData = item;
|
|
|
|
minExtents.X = Math.Min(item.Position.X - width / 2, minExtents.X);
|
|
minExtents.Y = Math.Min(item.Position.Y - height / 2, minExtents.Y);
|
|
maxExtents.X = Math.Max(item.Position.X + width / 2, maxExtents.X);
|
|
maxExtents.Y = Math.Max(item.Position.Y + height / 2, maxExtents.Y);
|
|
}
|
|
else if (radius > 0.0f && width > 0.0f)
|
|
{
|
|
FixtureFactory.AttachRectangle(simWidth, simRadius * 2, 5.0f, simPos, farseerBody, this).UserData = item;
|
|
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos - Vector2.UnitX * simWidth / 2, this).UserData = item;
|
|
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos + Vector2.UnitX * simWidth / 2, this).UserData = item;
|
|
minExtents.X = Math.Min(item.Position.X - width / 2 - radius, minExtents.X);
|
|
minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y);
|
|
maxExtents.X = Math.Max(item.Position.X + width / 2 + radius, maxExtents.X);
|
|
maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y);
|
|
}
|
|
else if (radius > 0.0f && height > 0.0f)
|
|
{
|
|
FixtureFactory.AttachRectangle(simRadius * 2, height, 5.0f, simPos, farseerBody, this).UserData = item;
|
|
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos - Vector2.UnitY * simHeight / 2, this).UserData = item;
|
|
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos + Vector2.UnitX * simHeight / 2, this).UserData = item;
|
|
minExtents.X = Math.Min(item.Position.X - radius, minExtents.X);
|
|
minExtents.Y = Math.Min(item.Position.Y - height / 2 - radius, minExtents.Y);
|
|
maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X);
|
|
maxExtents.Y = Math.Max(item.Position.Y + height / 2 + radius, maxExtents.Y);
|
|
}
|
|
else if (radius > 0.0f)
|
|
{
|
|
FixtureFactory.AttachCircle(simRadius, 5.0f, farseerBody, simPos, this).UserData = item;
|
|
minExtents.X = Math.Min(item.Position.X - radius, minExtents.X);
|
|
minExtents.Y = Math.Min(item.Position.Y - radius, minExtents.Y);
|
|
maxExtents.X = Math.Max(item.Position.X + radius, maxExtents.X);
|
|
maxExtents.Y = Math.Max(item.Position.Y + radius, maxExtents.Y);
|
|
}
|
|
}
|
|
|
|
Borders = new Rectangle((int)minExtents.X, (int)maxExtents.Y, (int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y));
|
|
}
|
|
|
|
farseerBody.BodyType = BodyType.Dynamic;
|
|
farseerBody.CollisionCategories = Physics.CollisionWall;
|
|
farseerBody.CollidesWith =
|
|
Physics.CollisionItem |
|
|
Physics.CollisionLevel |
|
|
Physics.CollisionCharacter |
|
|
Physics.CollisionProjectile |
|
|
Physics.CollisionWall;
|
|
|
|
farseerBody.Restitution = Restitution;
|
|
farseerBody.Friction = Friction;
|
|
farseerBody.FixedRotation = true;
|
|
//mass = Body.Mass;
|
|
farseerBody.Awake = true;
|
|
farseerBody.SleepingAllowed = false;
|
|
farseerBody.IgnoreGravity = true;
|
|
farseerBody.OnCollision += OnCollision;
|
|
farseerBody.UserData = submarine;
|
|
|
|
Body = new PhysicsBody(farseerBody);
|
|
}
|
|
|
|
|
|
private List<Vector2> GenerateConvexHull()
|
|
{
|
|
List<Structure> subWalls = Structure.WallList.FindAll(wall => wall.Submarine == submarine);
|
|
|
|
if (subWalls.Count == 0)
|
|
{
|
|
return new List<Vector2> { new Vector2(-1.0f, 1.0f), new Vector2(1.0f, 1.0f), new Vector2(0.0f, -1.0f) };
|
|
}
|
|
|
|
List<Vector2> points = new List<Vector2>();
|
|
|
|
foreach (Structure wall in subWalls)
|
|
{
|
|
points.Add(new Vector2(wall.Rect.X, wall.Rect.Y));
|
|
points.Add(new Vector2(wall.Rect.X + wall.Rect.Width, wall.Rect.Y));
|
|
points.Add(new Vector2(wall.Rect.X, wall.Rect.Y - wall.Rect.Height));
|
|
points.Add(new Vector2(wall.Rect.X + wall.Rect.Width, wall.Rect.Y - wall.Rect.Height));
|
|
}
|
|
|
|
List<Vector2> hullPoints = MathUtils.GiftWrap(points);
|
|
|
|
return hullPoints;
|
|
}
|
|
|
|
public void Update(float deltaTime)
|
|
{
|
|
if (Body.FarseerBody.IsStatic) { return; }
|
|
|
|
if (GameMain.Client != null)
|
|
{
|
|
if (memPos.Count == 0) return;
|
|
|
|
Vector2 newVelocity = Body.LinearVelocity;
|
|
Vector2 newPosition = Body.SimPosition;
|
|
|
|
Body.CorrectPosition(memPos, deltaTime, out newVelocity, out newPosition);
|
|
Vector2 moveAmount = ConvertUnits.ToDisplayUnits(newPosition - Body.SimPosition);
|
|
newVelocity = newVelocity.ClampLength(100.0f);
|
|
if (!MathUtils.IsValid(newVelocity))
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<Submarine> subsToMove = submarine.GetConnectedSubs();
|
|
foreach (Submarine dockedSub in subsToMove)
|
|
{
|
|
if (dockedSub == submarine) continue;
|
|
//clear the position buffer of the docked subs to prevent unnecessary position corrections
|
|
dockedSub.SubBody.memPos.Clear();
|
|
}
|
|
|
|
Submarine closestSub = null;
|
|
if (Character.Controlled == null)
|
|
{
|
|
closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
|
}
|
|
else
|
|
{
|
|
closestSub = Character.Controlled.Submarine;
|
|
}
|
|
|
|
bool displace = moveAmount.LengthSquared() > 100.0f * 100.0f;
|
|
foreach (Submarine sub in subsToMove)
|
|
{
|
|
sub.PhysicsBody.SetTransform(sub.PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(moveAmount), 0.0f);
|
|
sub.PhysicsBody.LinearVelocity = newVelocity;
|
|
|
|
if (displace) sub.SubBody.DisplaceCharacters(moveAmount);
|
|
}
|
|
|
|
if (closestSub != null && subsToMove.Contains(closestSub))
|
|
{
|
|
GameMain.GameScreen.Cam.Position += moveAmount;
|
|
if (GameMain.GameScreen.Cam.TargetPos != Vector2.Zero) GameMain.GameScreen.Cam.TargetPos += moveAmount;
|
|
|
|
if (Character.Controlled != null) Character.Controlled.CursorPosition += moveAmount;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//if outside left or right edge of the level
|
|
if (Position.X < 0 || Position.X > Level.Loaded.Size.X)
|
|
{
|
|
Rectangle worldBorders = Borders;
|
|
worldBorders.Location += MathUtils.ToPoint(Position);
|
|
|
|
//push the sub back below the upper "barrier" of the level
|
|
if (worldBorders.Y > Level.Loaded.Size.Y)
|
|
{
|
|
Body.LinearVelocity = new Vector2(
|
|
Body.LinearVelocity.X,
|
|
Math.Min(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.Size.Y - worldBorders.Y)));
|
|
}
|
|
else if (worldBorders.Y - worldBorders.Height < Level.Loaded.BottomPos)
|
|
{
|
|
Body.LinearVelocity = new Vector2(
|
|
Body.LinearVelocity.X,
|
|
Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height))));
|
|
}
|
|
}
|
|
|
|
//-------------------------
|
|
|
|
Vector2 totalForce = CalculateBuoyancy();
|
|
if (Body.LinearVelocity.LengthSquared() > 0.0001f)
|
|
{
|
|
//TODO: sync current drag with clients?
|
|
float attachedMass = 0.0f;
|
|
JointEdge jointEdge = Body.FarseerBody.JointList;
|
|
while (jointEdge != null)
|
|
{
|
|
Body otherBody = jointEdge.Joint.BodyA == Body.FarseerBody ? jointEdge.Joint.BodyB : jointEdge.Joint.BodyA;
|
|
Character character = (otherBody.UserData as Limb)?.character;
|
|
if (character != null) attachedMass += character.Mass;
|
|
|
|
jointEdge = jointEdge.Next;
|
|
}
|
|
|
|
float horizontalDragCoefficient = MathHelper.Clamp(HorizontalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag);
|
|
totalForce.X -= Math.Sign(Body.LinearVelocity.X) * Body.LinearVelocity.X * Body.LinearVelocity.X * horizontalDragCoefficient * Body.Mass;
|
|
|
|
float verticalDragCoefficient = MathHelper.Clamp(VerticalDrag + attachedMass / 5000.0f, 0.0f, MaxDrag);
|
|
totalForce.Y -= Math.Sign(Body.LinearVelocity.Y) * Body.LinearVelocity.Y * Body.LinearVelocity.Y * verticalDragCoefficient * Body.Mass;
|
|
}
|
|
|
|
ApplyForce(totalForce);
|
|
|
|
UpdateDepthDamage(deltaTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves away any character that is inside the bounding box of the sub (but not inside the sub)
|
|
/// </summary>
|
|
/// <param name="subTranslation">The translation that was applied to the sub before doing the displacement
|
|
/// (used for determining where to push the characters)</param>
|
|
private void DisplaceCharacters(Vector2 subTranslation)
|
|
{
|
|
Rectangle worldBorders = Borders;
|
|
worldBorders.Location += MathUtils.ToPoint(ConvertUnits.ToDisplayUnits(Body.SimPosition));
|
|
|
|
Vector2 translateDir = Vector2.Normalize(subTranslation);
|
|
if (!MathUtils.IsValid(translateDir)) translateDir = Vector2.UnitY;
|
|
|
|
foreach (Character c in Character.CharacterList)
|
|
{
|
|
if (c.AnimController.CurrentHull != null && c.AnimController.CanEnterSubmarine) continue;
|
|
|
|
foreach (Limb limb in c.AnimController.Limbs)
|
|
{
|
|
//if the character isn't inside the bounding box, continue
|
|
if (!Submarine.RectContains(worldBorders, limb.WorldPosition)) continue;
|
|
|
|
//cast a line from the position of the character to the same direction as the translation of the sub
|
|
//and see where it intersects with the bounding box
|
|
if (!MathUtils.GetLineRectangleIntersection(limb.WorldPosition,
|
|
limb.WorldPosition + translateDir * 100000.0f, worldBorders, out Vector2 intersection))
|
|
{
|
|
//should never happen when casting a line out from inside the bounding box
|
|
Debug.Assert(false);
|
|
continue;
|
|
}
|
|
|
|
|
|
//"+ translatedir" in order to move the character slightly away from the wall
|
|
c.AnimController.SetPosition(ConvertUnits.ToSimUnits(c.WorldPosition + (intersection - limb.WorldPosition)) + translateDir);
|
|
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private Vector2 CalculateBuoyancy()
|
|
{
|
|
float waterVolume = 0.0f;
|
|
float volume = 0.0f;
|
|
foreach (Hull hull in Hull.hullList)
|
|
{
|
|
if (hull.Submarine != submarine) continue;
|
|
|
|
waterVolume += hull.WaterVolume;
|
|
volume += hull.Volume;
|
|
}
|
|
|
|
float waterPercentage = volume <= 0.0f ? 0.0f : waterVolume / volume;
|
|
|
|
float buoyancy = NeutralBallastPercentage - waterPercentage;
|
|
|
|
if (buoyancy > 0.0f)
|
|
buoyancy *= 2.0f;
|
|
else
|
|
buoyancy = Math.Max(buoyancy, -0.5f);
|
|
|
|
return new Vector2(0.0f, buoyancy * Body.Mass * 10.0f);
|
|
}
|
|
|
|
public void ApplyForce(Vector2 force)
|
|
{
|
|
Body.ApplyForce(force);
|
|
}
|
|
|
|
public void SetPosition(Vector2 position)
|
|
{
|
|
Body.SetTransform(ConvertUnits.ToSimUnits(position), 0.0f);
|
|
}
|
|
|
|
private void UpdateDepthDamage(float deltaTime)
|
|
{
|
|
if (Position.Y > DamageDepth) return;
|
|
|
|
float depth = DamageDepth - Position.Y;
|
|
|
|
depthDamageTimer -= deltaTime;
|
|
|
|
if (depthDamageTimer > 0.0f) return;
|
|
|
|
foreach (Structure wall in Structure.WallList)
|
|
{
|
|
if (wall.Submarine != submarine) continue;
|
|
|
|
if (wall.Health < depth * 0.01f)
|
|
{
|
|
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, depth * 0.01f);
|
|
|
|
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
|
{
|
|
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(depth * 0.001f, 50.0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
depthDamageTimer = 10.0f;
|
|
}
|
|
|
|
public bool OnCollision(Fixture f1, Fixture f2, Contact contact)
|
|
{
|
|
if (f2.Body.UserData is Limb limb)
|
|
{
|
|
bool collision = CheckCharacterCollision(contact, limb.character);
|
|
if (collision) HandleLimbCollision(contact, limb);
|
|
return collision;
|
|
}
|
|
if (f2.Body.UserData is Character character)
|
|
{
|
|
return CheckCharacterCollision(contact, character);
|
|
}
|
|
|
|
contact.GetWorldManifold(out Vector2 normal, out FixedArray2<Vector2> points);
|
|
if (contact.FixtureA.Body == f1.Body)
|
|
{
|
|
normal = -normal;
|
|
}
|
|
|
|
if (f2.UserData is VoronoiCell cell)
|
|
{
|
|
HandleLevelCollision(contact, normal);
|
|
return true;
|
|
}
|
|
|
|
if (f2.Body.UserData is Structure structure)
|
|
{
|
|
|
|
HandleLevelCollision(contact, normal);
|
|
return true;
|
|
}
|
|
|
|
if (f2.Body.UserData is Submarine otherSub)
|
|
{
|
|
HandleSubCollision(contact, otherSub);
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool CheckCharacterCollision(Contact contact, Character character)
|
|
{
|
|
//characters that can't enter the sub always collide regardless of gaps
|
|
if (!character.AnimController.CanEnterSubmarine) return true;
|
|
if (character.Submarine != null) return false;
|
|
|
|
contact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> points);
|
|
|
|
Vector2 normalizedVel = character.AnimController.Collider.LinearVelocity == Vector2.Zero ?
|
|
Vector2.Zero : Vector2.Normalize(character.AnimController.Collider.LinearVelocity);
|
|
|
|
Vector2 targetPos = ConvertUnits.ToDisplayUnits(points[0] - contactNormal);
|
|
Hull newHull = Hull.FindHull(targetPos, null);
|
|
|
|
if (newHull == null)
|
|
{
|
|
targetPos = ConvertUnits.ToDisplayUnits(points[0] + normalizedVel);
|
|
newHull = Hull.FindHull(targetPos, null);
|
|
}
|
|
|
|
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
|
|
targetPos = character.WorldPosition;
|
|
Gap adjacentGap = Gap.FindAdjacent(gaps, targetPos, 200.0f);
|
|
if (adjacentGap == null) return true;
|
|
|
|
if (newHull != null)
|
|
{
|
|
character.AnimController.FindHull(newHull.WorldPosition, true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void HandleLimbCollision(Contact contact, Limb limb)
|
|
{
|
|
if (limb.Mass > 100.0f)
|
|
{
|
|
Vector2 normal = Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ?
|
|
Vector2.UnitY :
|
|
Vector2.Normalize(Body.SimPosition - limb.SimPosition);
|
|
|
|
float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f * Math.Min(limb.Mass / 200.0f, 1);
|
|
|
|
ApplyImpact(impact, -normal, contact);
|
|
foreach (Submarine dockedSub in submarine.DockedTo)
|
|
{
|
|
dockedSub.SubBody.ApplyImpact(impact, -normal, contact);
|
|
}
|
|
}
|
|
|
|
//find all contacts between the limb and level walls
|
|
List<Contact> levelContacts = new List<Contact>();
|
|
ContactEdge contactEdge = limb.body.FarseerBody.ContactList;
|
|
while (contactEdge.Next != null)
|
|
{
|
|
if (contactEdge.Contact.Enabled &&
|
|
contactEdge.Other.UserData is VoronoiCell &&
|
|
contactEdge.Contact.IsTouching)
|
|
{
|
|
levelContacts.Add(contactEdge.Contact);
|
|
}
|
|
contactEdge = contactEdge.Next;
|
|
}
|
|
|
|
if (levelContacts.Count == 0) return;
|
|
|
|
//if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it
|
|
//not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete),
|
|
//but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage
|
|
Vector2 avgContactNormal = Vector2.Zero;
|
|
foreach (Contact levelContact in levelContacts)
|
|
{
|
|
levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> temp);
|
|
|
|
//if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal
|
|
VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ?
|
|
((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData);
|
|
|
|
var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center;
|
|
if (Vector2.Dot(contactNormal, cellDiff) < 0)
|
|
{
|
|
contactNormal = -contactNormal;
|
|
}
|
|
|
|
avgContactNormal += contactNormal;
|
|
|
|
//apply impacts at the positions where this sub is touching the limb
|
|
ApplyImpact((Vector2.Dot(-Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, levelContact);
|
|
}
|
|
avgContactNormal /= levelContacts.Count;
|
|
|
|
float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal);
|
|
if (contactDot > 0.001f)
|
|
{
|
|
Vector2 velChange = Vector2.Normalize(Body.LinearVelocity) * contactDot;
|
|
if (!MathUtils.IsValid(velChange))
|
|
{
|
|
GameAnalyticsManager.AddErrorEventOnce(
|
|
"SubmarineBody.HandleLimbCollision:" + submarine.ID,
|
|
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
|
"Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " + Body.LinearVelocity
|
|
+ ", avgContactNormal: " + avgContactNormal
|
|
+ ", contactDot: " + contactDot
|
|
+ ", velChange: " + velChange + ")");
|
|
return;
|
|
}
|
|
|
|
|
|
Body.LinearVelocity -= velChange;
|
|
|
|
float damageAmount = contactDot * Body.Mass / limb.character.Mass;
|
|
|
|
Vector2 n;
|
|
FixedArray2<Vector2> contactPos;
|
|
contact.GetWorldManifold(out n, out contactPos);
|
|
limb.character.LastDamageSource = submarine;
|
|
limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(contactPos[0]), limb,
|
|
new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageAmount) }, 0.0f, true, 0.0f);
|
|
|
|
if (limb.character.IsDead)
|
|
{
|
|
foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints)
|
|
{
|
|
if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) continue;
|
|
limb.character.AnimController.SeverLimbJoint(limbJoint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandleLevelCollision(Contact contact, Vector2 collisionNormal)
|
|
{
|
|
float wallImpact = Vector2.Dot(Velocity, -collisionNormal);
|
|
|
|
ApplyImpact(wallImpact, -collisionNormal, contact);
|
|
foreach (Submarine dockedSub in submarine.DockedTo)
|
|
{
|
|
dockedSub.SubBody.ApplyImpact(wallImpact, -collisionNormal, contact);
|
|
}
|
|
|
|
#if CLIENT
|
|
Vector2 n;
|
|
FixedArray2<Vector2> particlePos;
|
|
contact.GetWorldManifold(out n, out particlePos);
|
|
|
|
int particleAmount = (int)Math.Min(wallImpact * 10.0f, 50);
|
|
for (int i = 0; i < particleAmount; i++)
|
|
{
|
|
GameMain.ParticleManager.CreateParticle("iceshards",
|
|
ConvertUnits.ToDisplayUnits(particlePos[0]) + Rand.Vector(Rand.Range(1.0f, 50.0f)),
|
|
Rand.Vector(Rand.Range(50.0f, 500.0f)) + Velocity);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private void HandleSubCollision(Contact contact, Submarine otherSub)
|
|
{
|
|
Debug.Assert(otherSub != submarine);
|
|
|
|
Vector2 normal;
|
|
FixedArray2<Vector2> points;
|
|
contact.GetWorldManifold(out normal, out points);
|
|
if (contact.FixtureA.Body == otherSub.SubBody.Body.FarseerBody)
|
|
{
|
|
normal = -normal;
|
|
}
|
|
|
|
float thisMass = Body.Mass + submarine.DockedTo.Sum(s => s.PhysicsBody.Mass);
|
|
float otherMass = otherSub.PhysicsBody.Mass + otherSub.DockedTo.Sum(s => s.PhysicsBody.Mass);
|
|
float massRatio = otherMass / (thisMass + otherMass);
|
|
|
|
float impact = (Vector2.Dot(Velocity - otherSub.Velocity, normal) / 2.0f) * massRatio;
|
|
|
|
//apply impact to this sub (the other sub takes care of this in its own collision callback)
|
|
ApplyImpact(impact, normal, contact);
|
|
foreach (Submarine dockedSub in submarine.DockedTo)
|
|
{
|
|
dockedSub.SubBody.ApplyImpact(impact, normal, contact);
|
|
}
|
|
|
|
//find all contacts between this sub and level walls
|
|
List<Contact> levelContacts = new List<Contact>();
|
|
ContactEdge contactEdge = Body.FarseerBody.ContactList;
|
|
while (contactEdge.Next != null)
|
|
{
|
|
if (contactEdge.Contact.Enabled &&
|
|
contactEdge.Other.UserData is VoronoiCell &&
|
|
contactEdge.Contact.IsTouching)
|
|
{
|
|
levelContacts.Add(contactEdge.Contact);
|
|
}
|
|
|
|
contactEdge = contactEdge.Next;
|
|
}
|
|
|
|
if (levelContacts.Count == 0) return;
|
|
|
|
//if this sub is in contact with the level, apply artifical impacts
|
|
//to both subs to prevent the other sub from bouncing on top of this one
|
|
//and to fake the other sub "crushing" this one against a wall
|
|
Vector2 avgContactNormal = Vector2.Zero;
|
|
foreach (Contact levelContact in levelContacts)
|
|
{
|
|
Vector2 contactNormal;
|
|
FixedArray2<Vector2> temp;
|
|
levelContact.GetWorldManifold(out contactNormal, out temp);
|
|
|
|
//if the contact normal is pointing from the sub towards the level cell we collided with, flip the normal
|
|
VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ?
|
|
((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData);
|
|
|
|
var cellDiff = ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center;
|
|
if (Vector2.Dot(contactNormal, cellDiff) < 0)
|
|
{
|
|
contactNormal = -contactNormal;
|
|
}
|
|
|
|
avgContactNormal += contactNormal;
|
|
|
|
//apply impacts at the positions where this sub is touching the level
|
|
ApplyImpact((Vector2.Dot(Velocity - otherSub.Velocity, contactNormal) / 2.0f) * massRatio / levelContacts.Count, contactNormal, levelContact);
|
|
}
|
|
avgContactNormal /= levelContacts.Count;
|
|
|
|
//apply an impact to the other sub
|
|
float contactDot = Vector2.Dot(otherSub.PhysicsBody.LinearVelocity, -avgContactNormal);
|
|
if (contactDot > 0.0f)
|
|
{
|
|
if (otherSub.PhysicsBody.LinearVelocity.LengthSquared() > 0.0001f)
|
|
{
|
|
otherSub.PhysicsBody.LinearVelocity -= Vector2.Normalize(otherSub.PhysicsBody.LinearVelocity) * contactDot;
|
|
}
|
|
|
|
impact = Vector2.Dot(otherSub.Velocity, normal);
|
|
otherSub.SubBody.ApplyImpact(impact, normal, contact);
|
|
foreach (Submarine dockedSub in otherSub.DockedTo)
|
|
{
|
|
dockedSub.SubBody.ApplyImpact(impact, normal, contact);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyImpact(float impact, Vector2 direction, Contact contact)
|
|
{
|
|
if (impact < 3.0f) return;
|
|
|
|
contact.GetWorldManifold(out Vector2 tempNormal, out FixedArray2<Vector2> worldPoints);
|
|
Vector2 lastContactPoint = worldPoints[0];
|
|
|
|
Vector2 impulse = direction * impact * 0.5f;
|
|
impulse = impulse.ClampLength(5.0f);
|
|
|
|
#if CLIENT
|
|
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
|
{
|
|
GameMain.GameScreen.Cam.Shake = impact * 2.0f;
|
|
float angularVelocity =
|
|
(lastContactPoint.X - Body.SimPosition.X) / ConvertUnits.ToSimUnits(submarine.Borders.Width / 2) * impulse.Y
|
|
- (lastContactPoint.Y - Body.SimPosition.Y) / ConvertUnits.ToSimUnits(submarine.Borders.Height / 2) * impulse.X;
|
|
GameMain.GameScreen.Cam.AngularVelocity = MathHelper.Clamp(angularVelocity * 0.1f, -1.0f, 1.0f);
|
|
}
|
|
#endif
|
|
|
|
foreach (Character c in Character.CharacterList)
|
|
{
|
|
if (c.Submarine != submarine) continue;
|
|
if (impact > 2.0f) c.SetStun((impact - 2.0f) * 0.1f);
|
|
|
|
foreach (Limb limb in c.AnimController.Limbs)
|
|
{
|
|
limb.body.ApplyLinearImpulse(limb.Mass * impulse, 20.0f);
|
|
}
|
|
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 20.0f);
|
|
}
|
|
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
if (item.Submarine != submarine || item.CurrentHull == null ||
|
|
item.body == null || !item.body.Enabled) continue;
|
|
|
|
item.body.ApplyLinearImpulse(item.body.Mass * impulse, 20.0f);
|
|
}
|
|
|
|
var damagedStructures = Explosion.RangedStructureDamage(ConvertUnits.ToDisplayUnits(lastContactPoint), impact * 50.0f, impact * ImpactDamageMultiplier);
|
|
|
|
#if CLIENT
|
|
//play a damage sound for the structure that took the most damage
|
|
float maxDamage = 0.0f;
|
|
Structure maxDamageStructure = null;
|
|
foreach (KeyValuePair<Structure,float> structureDamage in damagedStructures)
|
|
{
|
|
if (maxDamageStructure == null || structureDamage.Value > maxDamage)
|
|
{
|
|
maxDamage = structureDamage.Value;
|
|
maxDamageStructure = structureDamage.Key;
|
|
}
|
|
}
|
|
|
|
if (maxDamageStructure != null)
|
|
{
|
|
SoundPlayer.PlayDamageSound(
|
|
"StructureBlunt",
|
|
impact * 10.0f,
|
|
ConvertUnits.ToDisplayUnits(lastContactPoint),
|
|
MathHelper.Clamp(maxDamage * 4.0f, 2000.0f, 10000.0f),
|
|
maxDamageStructure.Tags);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}
|
|
}
|