(5a377a8ee) Unstable v0.9.1000.0

This commit is contained in:
Juan Pablo Arce
2020-05-13 12:55:42 -03:00
parent b143329701
commit a1ca41aa5d
426 changed files with 14384 additions and 5708 deletions
@@ -155,16 +155,16 @@ namespace Barotrauma
Prefabs.RemoveByFile(filePath);
}
public void GiveItems(Character character)
public void GiveItems(Character character, Submarine submarine)
{
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), Rand.RandSync.Unsynced);
foreach (XElement itemElement in spawnItems.GetChildElements("item"))
{
InitializeItems(character, itemElement);
InitializeItems(character, itemElement, submarine);
}
}
private void InitializeItems(Character character, XElement itemElement, Item parentItem = null)
private void InitializeItems(Character character, XElement itemElement, Submarine submarine, Item parentItem = null)
{
ItemPrefab itemPrefab;
string itemIdentifier = itemElement.GetAttributeString("identifier", "");
@@ -201,9 +201,10 @@ namespace Barotrauma
{
character.Inventory.TryPutItem(item, null, item.AllowedSlots);
}
if (item.Prefab.Identifier == "idcard")
if (item.Prefab.Identifier == "idcard" || item.Prefab.Identifier == "idcardwreck")
{
item.AddTag("name:" + character.Name);
item.ReplaceTag("wreck_id", Level.Loaded.GetWreckIDTag("wreck_id", submarine));
var job = character.Info?.Job;
if (job != null)
{
@@ -220,7 +221,7 @@ namespace Barotrauma
}
foreach (XElement childItemElement in itemElement.Elements())
{
InitializeItems(character, childItemElement, item);
InitializeItems(character, childItemElement, submarine, item);
}
}
}
@@ -1,7 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Text;
@@ -146,7 +146,7 @@ namespace Barotrauma
{
id += 1;
IDfound = dictionary.ContainsKey(id);
} while (IDfound);
} while (IDfound || id == NullEntityID || id == EntitySpawnerID);
return id;
}
@@ -30,9 +30,9 @@ namespace Barotrauma
public float EmpStrength { get; set; }
public Explosion(float range, float force, float damage, float structureDamage, float empStrength = 0.0f)
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f)
{
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, range)
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range)
{
SeverLimbsProbability = 1.0f
};
@@ -157,42 +157,47 @@ namespace Barotrauma
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
if (flames)
foreach (Item item in Item.ItemList)
{
foreach (Item item in Item.ItemList)
if (item.Condition <= 0.0f) { continue; }
if (Vector2.Distance(item.WorldPosition, worldPosition) > attack.Range * 0.5f) { continue; }
if (flames && !item.FireProof)
{
if (item.CurrentHull != hull || item.FireProof || item.Condition <= 0.0f) { continue; }
//don't apply OnFire effects if the item is inside a fireproof container
//(or if it's inside a container that's inside a fireproof container, etc)
Item container = item.Container;
bool fireProof = false;
while (container != null)
{
if (container.FireProof) { fireProof = true; break; }
if (container.FireProof)
{
fireProof = true;
break;
}
container = container.Container;
}
if (fireProof || Vector2.Distance(item.WorldPosition, worldPosition) > attack.Range * 0.5f) { continue; }
item.ApplyStatusEffects(ActionType.OnFire, 1.0f);
if (item.Condition <= 0.0f && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
if (!fireProof)
{
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
item.ApplyStatusEffects(ActionType.OnFire, 1.0f);
if (item.Condition <= 0.0f && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFire });
}
}
}
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
{
float limbRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
float dist = Vector2.Distance(item.WorldPosition, worldPosition);
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
if (dist > attack.Range)
{
float limbRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
float dist = Vector2.Distance(item.WorldPosition, worldPosition);
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
if (dist > attack.Range) { continue; }
float distFactor = 1.0f - dist / attack.Range;
float damageAmount = attack.GetItemDamage(1.0f);
item.Condition -= damageAmount * distFactor;
continue;
}
float distFactor = 1.0f - dist / attack.Range;
float damageAmount = attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
item.Condition -= damageAmount * distFactor;
}
}
}
@@ -200,10 +205,9 @@ namespace Barotrauma
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
{
if (attack.Range <= 0.0f) return;
if (attack.Range <= 0.0f) { return; }
//long range for the broad distance check, because large characters may still be in range even if their collider isn't
float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f);
@@ -220,19 +224,20 @@ namespace Barotrauma
Vector2 explosionPos = worldPosition;
if (c.Submarine != null) { explosionPos -= c.Submarine.Position; }
Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false);
Hull hull = Hull.FindHull(explosionPos, null, false);
bool underWater = hull == null || explosionPos.Y < hull.Surface;
explosionPos = ConvertUnits.ToSimUnits(explosionPos);
Dictionary<Limb, float> distFactors = new Dictionary<Limb, float>();
Dictionary<Limb, float> damages = new Dictionary<Limb, float>();
foreach (Limb limb in c.AnimController.Limbs)
{
float dist = Vector2.Distance(limb.WorldPosition, worldPosition);
//calculate distance from the "outer surface" of the physics body
//doesn't take the rotation of the limb into account, but should be accurate enough for this purpose
float limbRadius = Math.Max(Math.Max(limb.body.width * 0.5f, limb.body.height * 0.5f), limb.body.radius);
float limbRadius = limb.body.GetMaxExtent();
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
if (dist > attack.Range) { continue; }
@@ -240,14 +245,18 @@ namespace Barotrauma
float distFactor = 1.0f - dist / attack.Range;
//solid obstacles between the explosion and the limb reduce the effect of the explosion by 90%
if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null) distFactor *= 0.1f;
if (Submarine.CheckVisibility(limb.SimPosition, explosionPos) != null)
{
distFactor *= 0.1f;
}
distFactors.Add(limb, distFactor);
List<Affliction> modifiedAfflictions = new List<Affliction>();
int limbCount = c.AnimController.Limbs.Count(l => !l.IsSevered && !l.ignoreCollisions);
foreach (Affliction affliction in attack.Afflictions.Keys)
{
modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / c.AnimController.Limbs.Length));
modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / limbCount));
}
c.LastDamageSource = damageSource;
if (attacker == null)
@@ -255,14 +264,18 @@ namespace Barotrauma
if (damageSource is Item item)
{
attacker = item.GetComponent<Projectile>()?.User;
if (attacker == null) attacker = item.GetComponent<MeleeWeapon>()?.User;
if (attacker == null)
{
attacker = item.GetComponent<MeleeWeapon>()?.User;
}
}
}
//use a position slightly from the limb's position towards the explosion
//ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
Vector2 hitPos = limb.WorldPosition + (worldPosition - limb.WorldPosition) / dist * 0.01f;
c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker);
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker);
damages.Add(limb, attackResult.Damage);
if (attack.StatusEffects != null && attack.StatusEffects.Any())
{
@@ -287,14 +300,19 @@ namespace Barotrauma
}
//sever joints
if (c.IsDead && attack.SeverLimbsProbability > 0.0f)
if (attack.SeverLimbsProbability > 0.0f)
{
foreach (Limb limb in c.AnimController.Limbs)
{
if (!distFactors.ContainsKey(limb)) { continue; }
if (Rand.Range(0.0f, 1.0f) < attack.SeverLimbsProbability * distFactors[limb])
if (limb.character.Removed || limb.Removed) { continue; }
if (limb.IsSevered) { continue; }
if (!c.IsDead && !limb.CanBeSeveredAlive) { continue; }
if (distFactors.TryGetValue(limb, out float distFactor))
{
c.TrySeverLimbJoints(limb, 1.0f);
if (damages.TryGetValue(limb, out float damage))
{
c.TrySeverLimbJoints(limb, attack.SeverLimbsProbability * distFactor, damage);
}
}
}
}
@@ -2,12 +2,14 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Barotrauma.Extensions;
#if CLIENT
using Barotrauma.Sounds;
using Barotrauma.Lights;
using Barotrauma.Particles;
#endif
using FarseerPhysics;
using System.Linq;
namespace Barotrauma
{
@@ -15,6 +17,7 @@ namespace Barotrauma
{
const float OxygenConsumption = 50.0f;
const float GrowSpeed = 20.0f;
const float MaxDamageRange = 250.0f;
protected Hull hull;
@@ -65,7 +68,7 @@ namespace Barotrauma
public virtual float DamageRange
{
get { return (float)Math.Sqrt(size.X) * 20.0f; }
get { return Math.Min((float)Math.Sqrt(size.X) * 10.0f, MaxDamageRange); }
}
public Hull Hull
@@ -123,8 +126,8 @@ namespace Barotrauma
{
i = Math.Min(i, fireSources.Count - 1);
j = Math.Min(j, i - 1);
if (!fireSources[i].CheckOverLap(fireSources[j])) continue;
if (!fireSources[i].CheckOverLap(fireSources[j])) { continue; }
float leftEdge = Math.Min(fireSources[i].position.X, fireSources[j].position.X);
@@ -133,12 +136,10 @@ namespace Barotrauma
- leftEdge;
fireSources[j].position.X = leftEdge;
#if CLIENT
fireSources[j].burnDecals.AddRange(fireSources[i].burnDecals);
fireSources[j].burnDecals.Sort((d1, d2) => { return Math.Sign(d1.WorldPosition.X - d2.WorldPosition.X); });
#endif
fireSources[i].Remove();
}
}
@@ -210,21 +211,32 @@ namespace Barotrauma
private void DamageCharacters(float deltaTime)
{
if (size.X <= 0.0f) return;
if (size.X <= 0.0f) { return; }
for (int i = 0; i < Character.CharacterList.Count; i++)
{
Character c = Character.CharacterList[i];
if (c.AnimController.CurrentHull == null || c.IsDead) continue;
if (c.CurrentHull == null || c.IsDead) { continue; }
if (!IsInDamageRange(c, DamageRange)) continue;
if (!IsInDamageRange(c, DamageRange)) { continue; }
float dmg = (float)Math.Sqrt(size.X) * deltaTime / c.AnimController.Limbs.Length;
//GetApproximateDistance returns float.MaxValue if there's no path through open gaps between the hulls (e.g. if there's a door/wall in between)
if (hull.GetApproximateDistance(Position, c.Position, c.CurrentHull, 10000.0f) > size.X + DamageRange)
{
return;
}
float dmg = (float)Math.Sqrt(Math.Min(500, size.X)) * deltaTime / c.AnimController.Limbs.Count(l => !l.IsSevered);
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
c.LastDamageSource = null;
c.DamageLimb(WorldPosition, limb, new List<Affliction>() { AfflictionPrefab.Burn.Instantiate(dmg) }, 0.0f, false, 0.0f);
c.DamageLimb(WorldPosition, limb, AfflictionPrefab.Burn.Instantiate(dmg).ToEnumerable(), 0.0f, false, 0.0f);
}
#if CLIENT
//let clients display the client-side damage immediately, otherwise they may not be able to react to the damage fast enough
c.CharacterHealth.DisplayedVitality = c.Vitality;
#endif
c.ApplyStatusEffects(ActionType.OnFire, deltaTime);
}
}
@@ -33,6 +33,8 @@ namespace Barotrauma
//the force of the water flow which is exerted on physics bodies
private Vector2 flowForce;
private Hull flowTargetHull;
private float openedTimer = 1.0f;
private float higherSurface;
private float lowerSurface;
@@ -54,7 +56,11 @@ namespace Barotrauma
public float Open
{
get { return open; }
set { open = MathHelper.Clamp(value, 0.0f, 1.0f); }
set
{
if (value > open) { openedTimer = 1.0f; }
open = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
public float Size => IsHorizontal ? Rect.Height : Rect.Width;
@@ -124,6 +130,7 @@ namespace Barotrauma
InsertToList();
outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * 2.0f, Vector2.UnitX * 2.0f);
outsideCollisionBlocker.UserData = $"CollisionBlocker (Gap {ID})";
outsideCollisionBlocker.BodyType = BodyType.Static;
outsideCollisionBlocker.CollisionCategories = Physics.CollisionWall;
outsideCollisionBlocker.CollidesWith = Physics.CollisionCharacter;
@@ -277,57 +284,18 @@ namespace Barotrauma
flowForce.X = MathHelper.Clamp(flowForce.X, -MaxFlowForce, MaxFlowForce);
flowForce.Y = MathHelper.Clamp(flowForce.Y, -MaxFlowForce, MaxFlowForce);
lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime * 5.0f);
if (openedTimer > 0.0f && flowForce.Length() > lerpedFlowForce.Length())
{
//if the gap has just been opened/created, allow it to exert a large force instantly without any smoothing
lerpedFlowForce = flowForce;
}
else
{
lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime * 5.0f);
}
openedTimer -= deltaTime;
EmitParticles(deltaTime);
if (flowTargetHull != null && lerpedFlowForce.LengthSquared() > 0.0001f)
{
foreach (Character character in Character.CharacterList)
{
if (character.CurrentHull == null) continue;
if (character.CurrentHull != linkedTo[0] as Hull &&
(linkedTo.Count < 2 || character.CurrentHull != linkedTo[1] as Hull))
{
continue;
}
foreach (Limb limb in character.AnimController.Limbs)
{
if (!limb.inWater) continue;
float dist = Vector2.Distance(limb.WorldPosition, WorldPosition);
if (dist > lerpedFlowForce.Length()) continue;
Vector2 force = lerpedFlowForce / (float)Math.Max(Math.Sqrt(dist), 20.0f) * 0.025f;
//vertical gaps only apply forces if the character is roughly above/below the gap
if (!IsHorizontal)
{
float xDist = Math.Abs(limb.WorldPosition.X - WorldPosition.X);
if (xDist > rect.Width || rect.Width == 0) break;
force *= 1.0f - xDist / rect.Width;
}
if (!MathUtils.IsValid(force))
{
string errorMsg = "Attempted to apply invalid flow force to the character \"" + character.Name +
"\", gap pos: " + WorldPosition +
", limb pos: " + limb.WorldPosition +
", flowforce: " + flowForce + ", lerpedFlowForce:" + lerpedFlowForce +
", dist: " + dist;
DebugConsole.Log(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Gap.Update:InvalidFlowForce:" + character.Name,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg);
continue;
}
character.AnimController.Collider.ApplyForce(force * limb.body.Mass, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
}
}
partial void EmitParticles(float deltaTime);
@@ -510,7 +478,7 @@ namespace Barotrauma
//the larger the gap is, the faster the water flows
float sizeModifier = size * open * open;
float delta = hull1.Volume * Hull.MaxCompress * sizeModifier * deltaTime;
float delta = 500.0f * sizeModifier * deltaTime;
//make sure not to place more water to the target room than it can hold
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
@@ -595,7 +563,7 @@ namespace Barotrauma
private void UpdateOutsideColliderPos(Hull hull)
{
if (Submarine == null || IsRoomToRoom) { return; }
if (Submarine == null || IsRoomToRoom || Level.Loaded == null) { return; }
Vector2 rayDir;
if (IsHorizontal)
@@ -22,9 +22,9 @@ namespace Barotrauma
public const float OxygenConsumptionSpeed = 700.0f;
public const int WaveWidth = 32;
public static float WaveStiffness = 0.02f;
public static float WaveSpread = 0.05f;
public static float WaveDampening = 0.05f;
public static float WaveStiffness = 0.01f;
public static float WaveSpread = 0.02f;
public static float WaveDampening = 0.02f;
//how much excess water the room can contain, relative to the volume of the room.
//needed to make it possible for pressure to "push" water up through U-shaped hull configurations
@@ -83,6 +83,21 @@ namespace Barotrauma
}
}
private Color ambientLight;
[Editable, Serialize("0,0,0,0", true)]
public Color AmbientLight
{
get { return ambientLight; }
set
{
ambientLight = value;
#if CLIENT
lastAmbientLightEditTime = Timing.TotalTime;
#endif
}
}
public override Rectangle Rect
{
get
@@ -501,54 +516,34 @@ namespace Barotrauma
rightDelta[i] = WaveSpread * (waveY[i] - waveY[i + 1]);
waveVel[i + 1] += rightDelta[i];
}
for (int i = 1; i < waveY.Length - 1; i++)
{
waveY[i - 1] += leftDelta[i];
waveY[i + 1] += rightDelta[i];
}
}
//make waves propagate through horizontal gaps
foreach (Gap gap in ConnectedGaps)
{
if (!gap.IsRoomToRoom || !gap.IsHorizontal || gap.Open <= 0.0f) continue;
if (surface > gap.Rect.Y || surface < gap.Rect.Y - gap.Rect.Height) continue;
Hull hull2 = this == gap.linkedTo[0] as Hull ? (Hull)gap.linkedTo[1] : (Hull)gap.linkedTo[0];
float otherSurfaceY = hull2.surface;
if (otherSurfaceY > gap.Rect.Y || otherSurfaceY < gap.Rect.Y - gap.Rect.Height) continue;
float surfaceDiff = (surface - otherSurfaceY) * gap.Open;
if (this != gap.linkedTo[0] as Hull)
{
//the first hull linked to the gap handles the wave propagation,
//the second just updates the surfaces to the same level
if (surfaceDiff < 32.0f)
{
hull2.waveY[hull2.waveY.Length - 1] = surfaceDiff * 0.5f;
waveY[0] = -surfaceDiff * 0.5f;
}
//let the first linked hull handle the water propagation
continue;
}
if (!gap.IsRoomToRoom || !gap.IsHorizontal || gap.Open <= 0.0f) { continue; }
if (surface > gap.Rect.Y || surface < gap.Rect.Y - gap.Rect.Height) { continue; }
Hull hull2 = this == gap.linkedTo[0] as Hull ? (Hull)gap.linkedTo[1] : (Hull)gap.linkedTo[0];
float otherSurfaceY = hull2.surface;
if (otherSurfaceY > gap.Rect.Y || otherSurfaceY < gap.Rect.Y - gap.Rect.Height) { continue; }
float surfaceDiff = (surface - otherSurfaceY) * gap.Open;
for (int j = 0; j < 2; j++)
{
int i = waveY.Length - 1;
rightDelta[waveY.Length - 1] = WaveSpread * (hull2.waveY[0] - waveY[waveY.Length - 1] - surfaceDiff) * 0.5f;
waveVel[waveY.Length - 1] += rightDelta[waveY.Length - 1];
waveY[waveY.Length - 1] += rightDelta[waveY.Length - 1];
leftDelta[i] = WaveSpread * (waveY[i] - waveY[i - 1]);
waveVel[i - 1] += leftDelta[i];
rightDelta[i] = WaveSpread * (waveY[i] - hull2.waveY[0] + surfaceDiff);
hull2.waveVel[0] += rightDelta[i];
i = 0;
hull2.leftDelta[i] = WaveSpread * (hull2.waveY[i] - waveY[waveY.Length - 1] - surfaceDiff);
waveVel[waveVel.Length - 1] += hull2.leftDelta[i];
hull2.rightDelta[i] = WaveSpread * (hull2.waveY[i] - hull2.waveY[i + 1]);
hull2.waveVel[i + 1] += hull2.rightDelta[i];
hull2.leftDelta[0] = WaveSpread * (waveY[waveY.Length - 1] - hull2.waveY[0] + surfaceDiff) * 0.5f;
hull2.waveVel[0] += hull2.leftDelta[0];
hull2.waveY[0] += hull2.leftDelta[0];
}
if (surfaceDiff < 32.0f)
@@ -557,13 +552,19 @@ namespace Barotrauma
hull2.waveY[0] = surfaceDiff * 0.5f;
waveY[waveY.Length - 1] = -surfaceDiff * 0.5f;
}
else
}
//apply spread (two iterations)
for (int j = 0; j < 2; j++)
{
for (int i = 1; i < waveY.Length - 1; i++)
{
hull2.waveY[0] += rightDelta[waveY.Length - 1];
waveY[waveY.Length - 1] += hull2.leftDelta[0];
waveY[i - 1] += leftDelta[i];
waveY[i + 1] += rightDelta[i];
}
}
if (waterVolume < Volume)
{
LethalPressure -= 10.0f * deltaTime;
@@ -609,37 +610,33 @@ namespace Barotrauma
FireSources.Remove(fire);
}
private HashSet<Hull> adjacentHulls = new HashSet<Hull>();
public IEnumerable<Hull> GetConnectedHulls(bool includingThis, int? searchDepth = null)
private readonly HashSet<Hull> adjacentHulls = new HashSet<Hull>();
public IEnumerable<Hull> GetConnectedHulls(bool includingThis, int? searchDepth = null, bool ignoreClosedGaps = false)
{
adjacentHulls.Clear();
int startStep = 0;
searchDepth = searchDepth ?? 100;
return GetAdjacentHulls(includingThis, adjacentHulls, ref startStep, searchDepth.Value);
searchDepth ??= 100;
GetAdjacentHulls(adjacentHulls, ref startStep, searchDepth.Value, ignoreClosedGaps);
if (!includingThis) { adjacentHulls.Remove(this); }
return adjacentHulls;
}
private HashSet<Hull> GetAdjacentHulls(bool includingThis, HashSet<Hull> connectedHulls, ref int step, int searchDepth)
private void GetAdjacentHulls(HashSet<Hull> connectedHulls, ref int step, int searchDepth, bool ignoreClosedGaps = false)
{
if (includingThis)
{
connectedHulls.Add(this);
}
if (step > searchDepth)
{
return connectedHulls;
}
connectedHulls.Add(this);
if (step > searchDepth) { return; }
foreach (Gap g in ConnectedGaps)
{
if (ignoreClosedGaps && g.Open <= 0.0f) { continue; }
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
{
if (g.linkedTo[i] is Hull hull && !connectedHulls.Contains(hull))
{
step++;
hull.GetAdjacentHulls(true, connectedHulls, ref step, searchDepth);
hull.GetAdjacentHulls(connectedHulls, ref step, searchDepth, ignoreClosedGaps);
}
}
}
return connectedHulls;
}
/// <summary>
@@ -666,7 +663,7 @@ namespace Barotrauma
if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken)
{
//gap blocked if the door is not open or the predicted state is not open
if (!g.ConnectedDoor.IsOpen || (g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
if ((!g.ConnectedDoor.IsOpen && !g.ConnectedDoor.IsBroken) || (g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
{
if (g.ConnectedDoor.OpenState < 0.1f) continue;
}
@@ -2,7 +2,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
@@ -55,7 +55,7 @@ namespace Barotrauma
DisplayEntities = new List<Pair<MapEntityPrefab, Rectangle>>();
foreach (XElement entityElement in doc.Root.Elements())
{
string identifier = entityElement.GetAttributeString("identifier", "");
string identifier = entityElement.GetAttributeString("identifier", entityElement.Name.ToString().ToLowerInvariant());
MapEntityPrefab mapEntity = List.FirstOrDefault(p => p.Identifier == identifier);
if (mapEntity == null)
{
@@ -64,9 +64,9 @@ namespace Barotrauma
}
Rectangle rect = entityElement.GetAttributeRect("rect", Rectangle.Empty);
if (mapEntity != null && !entityElement.GetAttributeBool("hideinassemblypreview", false))
if (mapEntity != null && !entityElement.Elements().Any(e => e.Name.LocalName.Equals("wire", StringComparison.OrdinalIgnoreCase)))
{
DisplayEntities.Add(new Pair<MapEntityPrefab, Rectangle>(mapEntity, rect));
if (!entityElement.GetAttributeBool("hideinassemblypreview", false)) { DisplayEntities.Add(new Pair<MapEntityPrefab, Rectangle>(mapEntity, rect)); }
minX = Math.Min(minX, rect.X);
minY = Math.Min(minY, rect.Y - rect.Height);
maxX = Math.Max(maxX, rect.Right);
@@ -74,7 +74,9 @@ namespace Barotrauma
}
}
Bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY);
Bounds = minX == int.MaxValue ?
new Rectangle(0, 0, 1, 1) :
new Rectangle(minX, minY, maxX - minX, maxY - minY);
Prefabs.Add(this, false);
}
@@ -89,7 +91,7 @@ namespace Barotrauma
CreateInstance(rect.Location.ToVector2(), Submarine.MainSub);
}
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub)
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub, bool selectPrefabs = false)
{
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, FilePath);
if (entities.Count == 0) return entities;
@@ -107,10 +109,10 @@ namespace Barotrauma
MapEntity.MapLoaded(entities, true);
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen)
if (Screen.Selected == GameMain.SubEditorScreen && selectPrefabs)
{
MapEntity.SelectedList.Clear();
entities.ForEach(e => MapEntity.AddSelection(e));
entities.ForEach(MapEntity.AddSelection);
}
#endif
return entities;
@@ -27,19 +27,19 @@ namespace Barotrauma
foreach (GraphEdge ge in graphEdges)
{
if (Vector2.DistanceSquared(ge.Point1, ge.Point2) < 0.001f) continue;
if (Vector2.DistanceSquared(ge.Point1, ge.Point2) < 0.001f) { continue; }
for (int i = 0; i < 2; i++)
{
Site site = (i == 0) ? ge.Site1 : ge.Site2;
int x = (int)(Math.Floor((site.Coord.X-borders.X) / gridCellSize));
int y = (int)(Math.Floor((site.Coord.Y-borders.Y) / gridCellSize));
int x = (int)(Math.Floor((site.Coord.X - borders.X) / gridCellSize));
int y = (int)(Math.Floor((site.Coord.Y - borders.Y) / gridCellSize));
x = MathHelper.Clamp(x, 0, cellGrid.GetLength(0)-1);
y = MathHelper.Clamp(y, 0, cellGrid.GetLength(1)-1);
VoronoiCell cell = cellGrid[x,y].Find(c => c.Site == site);
x = MathHelper.Clamp(x, 0, cellGrid.GetLength(0) - 1);
y = MathHelper.Clamp(y, 0, cellGrid.GetLength(1) - 1);
VoronoiCell cell = cellGrid[x, y].Find(c => c.Site == site);
if (cell == null)
{
@@ -60,14 +60,62 @@ namespace Barotrauma
}
}
//add edges to the borders of the graph
foreach (var cell in cells)
{
Vector2? point1 = null, point2 = null;
foreach (GraphEdge ge in cell.Edges)
{
if (MathUtils.NearlyEqual(ge.Point1.X, borders.X) || MathUtils.NearlyEqual(ge.Point1.X, borders.Right) ||
MathUtils.NearlyEqual(ge.Point1.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point1.Y, borders.Bottom))
{
if (point1 == null)
{
point1 = ge.Point1;
}
else if (point2 == null)
{
if (MathUtils.NearlyEqual(point1.Value, ge.Point1)) { continue; }
point2 = ge.Point1;
}
}
if (MathUtils.NearlyEqual(ge.Point2.X, borders.X) || MathUtils.NearlyEqual(ge.Point2.X, borders.Right) ||
MathUtils.NearlyEqual(ge.Point2.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point2.Y, borders.Bottom))
{
if (point1 == null)
{
point1 = ge.Point2;
}
else
{
if (MathUtils.NearlyEqual(point1.Value, ge.Point2)) { continue; }
point2 = ge.Point2;
}
}
if (point1.HasValue && point2.HasValue)
{
Debug.Assert(point1 != point2);
var newEdge = new GraphEdge(point1.Value, point2.Value)
{
Cell1 = cell,
IsSolid = true,
Site1 = cell.Site,
OutsideLevel = true
};
cell.Edges.Add(newEdge);
break;
}
}
}
return cells;
}
private static Vector2 GetEdgeNormal(GraphEdge edge, VoronoiCell cell = null)
{
if (cell == null) cell = edge.AdjacentCell(null);
if (cell == null) return Vector2.UnitX;
if (cell == null) { cell = edge.AdjacentCell(null); }
if (cell == null) { return Vector2.UnitX; }
CompareCCW compare = new CompareCCW(cell.Center);
if (compare.Compare(edge.Point1, edge.Point2) == -1)
@@ -77,9 +125,7 @@ namespace Barotrauma
edge.Point2 = temp;
}
Vector2 normal = Vector2.Zero;
normal = Vector2.Normalize(edge.Point2 - edge.Point1);
Vector2 normal = Vector2.Normalize(edge.Point2 - edge.Point1);
Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.Point2);
normal = new Vector2(-normal.Y, normal.X);
@@ -148,7 +194,7 @@ namespace Barotrauma
foreach (GraphEdge edge in currentCell.Edges)
{
var adjacentCell = edge.AdjacentCell(currentCell);
if (limits.Contains(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y))
if (adjacentCell != null && limits.Contains(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y))
{
allowedEdges.Add(edge);
}
@@ -161,6 +207,7 @@ namespace Barotrauma
for (int i = 0; i < currentCell.Edges.Count; i++)
{
var adjacentCell = currentCell.Edges[i].AdjacentCell(currentCell);
if (adjacentCell == null) { continue; }
double dist = MathUtils.Distance(
adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y,
targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y);
@@ -18,6 +18,10 @@ namespace Barotrauma
//all entities are disabled after they reach this depth
public const int MaxEntityDepth = -300000;
public const float ShaftHeight = 1000.0f;
/// <summary>
/// The level generator won't try to adjust the width of the main path above this limit.
/// </summary>
public const int MaxSubmarineWidth = 16000;
public static Level Loaded
{
@@ -141,8 +145,6 @@ namespace Barotrauma
get { return positionsOfInterest; }
}
public readonly List<InterestingPosition> UsedPositions = new List<InterestingPosition>();
public Submarine StartOutpost { get; private set; }
public Submarine EndOutpost { get; private set; }
@@ -166,6 +168,11 @@ namespace Barotrauma
private set;
}
public List<Entity> EntitiesBeforeGenerate { get; private set; } = new List<Entity>();
public int EntityCountBeforeGenerate { get; private set; }
public int EntityCountAfterGenerate { get; private set; }
public float Difficulty
{
get;
@@ -281,8 +288,11 @@ namespace Barotrauma
public void Generate(bool mirror)
{
if (loaded != null) loaded.Remove();
if (loaded != null) { loaded.Remove(); }
loaded = this;
EntitiesBeforeGenerate = GetEntityList();
EntityCountBeforeGenerate = EntitiesBeforeGenerate.Count();
levelObjectManager = new LevelObjectManager();
@@ -316,26 +326,25 @@ namespace Barotrauma
SeaFloorTopPos = generationParams.SeaFloorDepth + generationParams.MountainHeightMax + generationParams.SeaFloorVariance;
int minWidth = 6500;
int maxWidth = 50000;
if (Submarine.MainSub != null)
{
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.05f);
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.15f);
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
minWidth = Math.Min(minWidth, maxWidth);
minWidth = Math.Min(minWidth, MaxSubmarineWidth);
}
Rectangle pathBorders = borders;
pathBorders.Inflate(-minWidth * 2, -minWidth);
pathBorders.Inflate(-Math.Min(minWidth * 2, MaxSubmarineWidth), -minWidth);
Debug.Assert(pathBorders.Width > 0 && pathBorders.Height > 0, "The size of the level's path area was negative.");
startPosition = new Point(
Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server),
minWidth,
Rand.Range(borders.Height / 2, borders.Height - minWidth * 2, Rand.RandSync.Server));
endPosition = new Point(
borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server),
borders.Width - minWidth,
Rand.Range(borders.Height / 2, borders.Height - minWidth * 2, Rand.RandSync.Server));
//----------------------------------------------------------------------------------
@@ -348,19 +357,25 @@ namespace Barotrauma
Point nodeInterval = generationParams.MainPathNodeIntervalRange;
for (int x = startPosition.X + nodeInterval.X;
x < endPosition.X - nodeInterval.X;
x < endPosition.X - nodeInterval.X;
x += Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server))
{
pathNodes.Add(new Point(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, Rand.RandSync.Server)));
}
pathNodes.Add(new Point(endPosition.X, borders.Height));
if (pathNodes.Count <= 2)
if (pathNodes.Count == 1)
{
pathNodes.Insert(1, borders.Center);
pathNodes.Add(new Point(pathBorders.Center.X, pathBorders.Y));
}
//if all nodes ended up high up in the level, move one down to make sure we utilize the full height of the level
else if (pathNodes.GetRange(1, pathNodes.Count - 1).All(p => p.Y > pathBorders.Y + pathBorders.Height * 0.25f))
{
int nodeIndex = Rand.Range(1, pathNodes.Count, Rand.RandSync.Server);
pathNodes[nodeIndex] = new Point(pathNodes[nodeIndex].X, pathBorders.Y);
}
pathNodes.Add(new Point(endPosition.X, borders.Height));
GenerateTunnels(pathNodes, minWidth);
//----------------------------------------------------------------------------------
@@ -539,21 +554,21 @@ namespace Barotrauma
{
foreach (GraphEdge edge in cell.Edges)
{
if (mirroredEdges.Contains(edge)) continue;
if (mirroredEdges.Contains(edge)) { continue; }
edge.Point1.X = borders.Width - edge.Point1.X;
edge.Point2.X = borders.Width - edge.Point2.X;
if (!mirroredSites.Contains(edge.Site1))
if (edge.Site1 != null && !mirroredSites.Contains(edge.Site1))
{
//make sure that sites right at the edge of a grid cell end up in the same cell as in the non-mirrored level
if (edge.Site1.Coord.X % GridCellSize < 1.0f &&
edge.Site1.Coord.X % GridCellSize >= 0.0f) edge.Site1.Coord.X += 1.0f;
edge.Site1.Coord.X % GridCellSize >= 0.0f) { edge.Site1.Coord.X += 1.0f; }
edge.Site1.Coord.X = borders.Width - edge.Site1.Coord.X;
mirroredSites.Add(edge.Site1);
}
if (!mirroredSites.Contains(edge.Site2))
if (edge.Site2 != null && !mirroredSites.Contains(edge.Site2))
{
if (edge.Site2.Coord.X % GridCellSize < 1.0f &&
edge.Site2.Coord.X % GridCellSize >= 0.0f) edge.Site2.Coord.X += 1.0f;
edge.Site2.Coord.X % GridCellSize >= 0.0f) { edge.Site2.Coord.X += 1.0f; }
edge.Site2.Coord.X = borders.Width - edge.Site2.Coord.X;
mirroredSites.Add(edge.Site2);
}
@@ -759,6 +774,8 @@ namespace Barotrauma
DebugConsole.NewMessage("Generated level with the seed " + seed + " (type: " + generationParams.Name + ")", Color.White);
}
EntityCountAfterGenerate = Entity.GetEntityList().Count();
//assign an ID to make entity events work
ID = FindFreeID();
}
@@ -1531,6 +1548,13 @@ namespace Barotrauma
return tempCells;
}
public string GetWreckIDTag(string originalTag, Submarine wreck)
{
string shortSeed = ToolBox.StringToInt(seed + wreck.Info.Name).ToString();
if (shortSeed.Length > 6) { shortSeed = shortSeed.Substring(0, 6); }
return originalTag + "_" + shortSeed;
}
// For debugging
private readonly Dictionary<Submarine, List<Vector2>> wreckPositions = new Dictionary<Submarine, List<Vector2>>();
private readonly Dictionary<Submarine, List<Rectangle>> blockedRects = new Dictionary<Submarine, List<Rectangle>>();
@@ -1546,6 +1570,7 @@ namespace Barotrauma
return;
}
wreckFiles.Shuffle(Rand.RandSync.Server);
int wreckCount = Math.Min(Loaded.GenerationParams.WreckCount, wreckFiles.Count);
// Min distance between a wreck and the start/end/other wreck.
float minDistance = Sonar.DefaultSonarRange;
@@ -1610,7 +1635,6 @@ namespace Barotrauma
Type = SubmarineInfo.SubmarineType.Wreck
};
Submarine wreck = new Submarine(info);
//wreck.Load(unloadPrevious: false);
wreck.MakeWreck();
tempSW.Stop();
Debug.WriteLine($"Wreck {wreck.Info.Name} loaded in { tempSW.ElapsedMilliseconds.ToString()} (ms)");
@@ -1626,17 +1650,18 @@ namespace Barotrauma
hull.WaterVolume = hull.Volume * Rand.Range(Loaded.GenerationParams.WreckFloodingHullMinWaterPercentage, Loaded.GenerationParams.WreckFloodingHullMaxWaterPercentage, Rand.RandSync.Server);
}
}
if (Rand.Value(Rand.RandSync.Server) <= Loaded.GenerationParams.ThalamusProbability)
// Only spawn thalamus when the wreck has some thalamus items defined.
if (Rand.Value(Rand.RandSync.Server) <= Loaded.GenerationParams.ThalamusProbability && wreck.GetItems(false).Any(i => i.Prefab.Category == MapEntityCategory.Thalamus))
{
if (!wreck.CreateThalamus())
if (!wreck.CreateWreckAI())
{
DebugConsole.NewMessage($"Failed to create thalamus inside {wreckName}.", Color.Red);
wreck.DisableThalamus();
DebugConsole.NewMessage($"Failed to create wreck AI inside {wreckName}.", Color.Red);
wreck.DisableWreckAI();
}
}
else
{
wreck.DisableThalamus();
wreck.DisableWreckAI();
}
}
else
@@ -1959,13 +1984,13 @@ namespace Barotrauma
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount);
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path);
pathPoints.Shuffle(Rand.RandSync.Unsynced);
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
var spawnPoints = corpsePoints.Union(pathPoints).ToList();
spawnPoints.Shuffle(Rand.RandSync.Unsynced);
corpsePoints.Shuffle(Rand.RandSync.Unsynced);
int spawnCounter = 0;
for (int j = 0; j < corpseCount; j++)
{
WayPoint sp = spawnPoints.FirstOrDefault();
WayPoint sp = corpsePoints.FirstOrDefault() ?? pathPoints.FirstOrDefault();
JobPrefab job = sp?.AssignedJob;
CorpsePrefab selectedPrefab;
if (job == null)
@@ -1979,18 +2004,19 @@ namespace Barotrauma
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck && (p.Job == "any" || p.Job == job.Identifier));
if (selectedPrefab == null)
{
spawnPoints.Remove(sp);
sp = spawnPoints.FirstOrDefault(sp => sp.AssignedJob == null);
corpsePoints.Remove(sp);
pathPoints.Remove(sp);
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
// Deduce the job from the selected prefab
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
job = GetJobPrefab();
}
}
if (selectedPrefab == null) { continue; }
Vector2 pos;
Vector2 worldPos;
if (sp == null)
{
if (!TryGetExtraSpawnPoint(out pos))
if (!TryGetExtraSpawnPoint(out worldPos))
{
break;
}
@@ -1998,15 +2024,17 @@ namespace Barotrauma
}
else
{
pos = sp.WorldPosition;
spawnPoints.Remove(sp);
worldPos = sp.WorldPosition;
corpsePoints.Remove(sp);
pathPoints.Remove(sp);
}
if (job == null) { continue; }
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job);
var corpse = Character.Create(CharacterPrefab.HumanConfigFile, pos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
var corpse = Character.Create(CharacterPrefab.HumanConfigFile, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
corpse.AnimController.FindHull(worldPos, true);
corpse.TeamID = Character.TeamType.None;
corpse.EnableDespawn = false;
selectedPrefab.GiveItems(corpse);
selectedPrefab.GiveItems(corpse, wreck);
corpse.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false);
spawnCounter++;
@@ -2018,9 +2046,9 @@ namespace Barotrauma
JobPrefab GetJobPrefab() => selectedPrefab.Job != null && selectedPrefab.Job != "any" ? JobPrefab.Get(selectedPrefab.Job) : JobPrefab.Random();
}
#if DEBUG
DebugConsole.NewMessage($"{spawnCounter}/{corpseCount} corpses spawned in {wreck.Info.Name}.", spawnCounter == corpseCount ? Color.Green : Color.Yellow);
#endif
bool TryGetExtraSpawnPoint(out Vector2 point)
{
point = Vector2.Zero;
@@ -238,7 +238,8 @@ namespace Barotrauma
}
[Editable, Serialize("5000, 10000", true, description: "The distance between the nodes that are used to generate the main path through the level (min, max). Larger values produce a straighter path.")]
[Editable(VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" }),
Serialize("5000, 10000", true, description: "The distance between the nodes that are used to generate the main path through the level (min, max). Larger values produce a straighter path.")]
public Point MainPathNodeIntervalRange
{
get { return mainPathNodeIntervalRange; }
@@ -256,7 +257,8 @@ namespace Barotrauma
set { smallTunnelCount = MathHelper.Clamp(value, 0, 100); }
}
[Editable, Serialize("5000, 10000", true, description: "The minimum and maximum length of small tunnels placed along the main path.")]
[Editable(VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" }),
Serialize("5000, 10000", true, description: "The minimum and maximum length of small tunnels placed along the main path.")]
public Point SmallTunnelLengthRange
{
get { return smallTunnelLengthRange; }
@@ -349,8 +351,7 @@ namespace Barotrauma
[Serialize(5, true, description: "The maximum number of corpses per wreck."), Editable(MinValueInt = 0, MaxValueInt = 20)]
public int MaxCorpseCount { get; set; }
// TODO: default to 0
[Serialize(1f, true, description: "How likely is it that a Thalamus inhabits a wreck. Percentage from 0 to 1 per wreck."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
[Serialize(0.0f, true, description: "How likely is it that a Thalamus inhabits a wreck. Percentage from 0 to 1 per wreck."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
public float ThalamusProbability { get; set; }
[Serialize(0.5f, true, description: "How likely the water level of a hull inside a wreck is randomly set."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
@@ -358,19 +358,23 @@ namespace Barotrauma
return;
}
//check if there are any other contacts with the entity
//check if there are contacts with any other fixture of the trigger
//(the OnSeparation callback happens when two fixtures separate,
//e.g. if a body stops touching the circular fixture at the end of a capsule-shaped body)
ContactEdge contactEdge = fixtureA.Body.ContactList;
while (contactEdge != null)
{
if (contactEdge.Contact != null &&
contactEdge.Contact.Enabled &&
contactEdge.Contact.IsTouching)
{
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ?
contactEdge.Contact.FixtureB :
contactEdge.Contact.FixtureA);
if (otherEntity == entity) return;
if (contactEdge.Contact.FixtureA != fixtureA && contactEdge.Contact.FixtureB != fixtureA)
{
var otherEntity = GetEntity(contactEdge.Contact.FixtureB == fixtureB ?
contactEdge.Contact.FixtureB :
contactEdge.Contact.FixtureA);
if (otherEntity == entity) { return; }
}
}
contactEdge = contactEdge.Next;
}
@@ -418,10 +422,20 @@ namespace Barotrauma
public void Update(float deltaTime)
{
if (ParentTrigger != null && !ParentTrigger.IsTriggered) return;
if (ParentTrigger != null && !ParentTrigger.IsTriggered) { return; }
triggerers.RemoveWhere(t => t.Removed);
if (physicsBody != null)
{
//failsafe to ensure triggerers get removed when they're far from the trigger
float maxExtent = Math.Max(ConvertUnits.ToDisplayUnits(physicsBody.GetMaxExtent() * 5), 5000.0f);
triggerers.RemoveWhere(t =>
{
return Vector2.Distance(t.WorldPosition, WorldPosition) > maxExtent;
});
}
bool isNotClient = true;
#if CLIENT
isNotClient = GameMain.Client == null;
@@ -511,6 +525,7 @@ namespace Barotrauma
ApplyForce(character.AnimController.Collider, deltaTime);
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
ApplyForce(limb.body, deltaTime);
}
}
@@ -1,8 +1,8 @@
using Microsoft.Xna.Framework;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace Barotrauma.RuinGeneration
@@ -174,7 +174,7 @@ namespace Barotrauma.RuinGeneration
public static void SaveAll()
{
XmlWriterSettings settings = new XmlWriterSettings
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = true
@@ -654,13 +654,14 @@ namespace Barotrauma.RuinGeneration
{
connectionPanel.Locked = true;
connectionPanel.CanBeSelected = false;
connectionPanel.Item.ShouldBeSaved = false;
}
// Hide wires for now
// Hide wires
if (ic is Wire wire)
{
wire.Hidden = true;
wire.CanBeSelected = false;
wire.Item.ShouldBeSaved = false;
}
}
}
@@ -19,18 +19,17 @@ namespace Barotrauma
//Prefabs.Remove(this);
}
public readonly Submarine mainSub;
public readonly SubmarineInfo subInfo;
public LinkedSubmarinePrefab(Submarine submarine)
public LinkedSubmarinePrefab(SubmarineInfo subInfo)
{
this.mainSub = submarine;
this.subInfo = subInfo;
}
protected override void CreateInstance(Rectangle rect)
{
System.Diagnostics.Debug.Assert(Submarine.MainSub != null);
LinkedSubmarine.CreateDummy(Submarine.MainSub, mainSub.Info.FilePath, rect.Location.ToVector2());
LinkedSubmarine.CreateDummy(Submarine.MainSub, subInfo.FilePath, rect.Location.ToVector2());
}
}
@@ -113,7 +112,9 @@ namespace Barotrauma
(int)sl.wallVertices.Max(v => v.X + position.X),
(int)sl.wallVertices.Min(v => v.Y + position.Y));
sl.Rect = new Rectangle(sl.rect.X, sl.rect.Y, sl.rect.Width - sl.rect.X, sl.rect.Y - sl.rect.Height);
int width = sl.rect.Width - sl.rect.X;
int height = sl.rect.Y - sl.rect.Height;
sl.Rect = new Rectangle((int)(position.X - width / 2), (int)(position.Y + height / 2), width, height);
}
else
{
@@ -162,8 +163,7 @@ namespace Barotrauma
public static LinkedSubmarine Load(XElement element, Submarine submarine)
{
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
LinkedSubmarine linkedSub = null;
LinkedSubmarine linkedSub;
if (Screen.Selected == GameMain.SubEditorScreen)
{
linkedSub = CreateDummy(submarine, element, pos);
@@ -198,21 +198,26 @@ namespace Barotrauma
for (int i = 0; i < linkedToIds.Length; i++)
{
linkedSub.linkedToID.Add((ushort)linkedToIds[i]);
if (Screen.Selected == GameMain.SubEditorScreen)
{
if (FindEntityByID((ushort)linkedToIds[i]) is MapEntity linked)
{
linkedSub.linkedTo.Add(linked);
}
}
}
linkedSub.originalLinkedToID = (ushort)element.GetAttributeInt("originallinkedto", 0);
linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0);
return linkedSub.loadSub ? linkedSub : null;
}
public void LinkDummyToMainSubmarine()
{
if (Screen.Selected != GameMain.SubEditorScreen) { return; }
for (int i = 0; i < linkedToID.Count; i++)
{
if (FindEntityByID(linkedToID[i]) is MapEntity linked)
{
linkedTo.Add(linked);
}
}
}
public override void OnMapLoaded()
{
if (!loadSub) { return; }
@@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using Barotrauma.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
@@ -56,6 +56,15 @@ namespace Barotrauma
Level.Loaded.TopBarrier.Enabled = false;
foreach (Character character in Character.CharacterList)
{
character.AnimController.Frozen = true;
foreach (Limb limb in character.AnimController.Limbs)
{
limb.body.PhysEnabled = false;
}
}
cam.TargetPos = Vector2.Zero;
float timer = 0.0f;
float initialZoom = cam.Zoom;
@@ -283,6 +283,13 @@ namespace Barotrauma
}
}
[Serialize(false, true), Editable]
public bool NoAITarget
{
get;
private set;
}
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
@@ -354,6 +361,7 @@ namespace Barotrauma
}
StairDirection = Prefab.StairDirection;
NoAITarget = Prefab.NoAITarget;
SerializableProperties = SerializableProperty.GetProperties(this);
InitProjSpecific();
@@ -381,7 +389,7 @@ namespace Barotrauma
}
// Only add ai targets automatically to submarine/outpost walls
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !Prefab.NoAITarget)
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !submarine.Info.IsWreck && !NoAITarget)
{
aiTarget = new AITarget(this)
{
@@ -818,7 +826,10 @@ namespace Barotrauma
Vector2 sectionPos = new Vector2(
Sections[sectionIndex].rect.X + Sections[sectionIndex].rect.Width / 2.0f,
Sections[sectionIndex].rect.Y - Sections[sectionIndex].rect.Height / 2.0f);
if (world && Submarine != null) sectionPos += Submarine.Position;
if (world && Submarine != null)
{
sectionPos += Submarine.Position;
}
return sectionPos;
}
else
@@ -839,7 +850,10 @@ namespace Barotrauma
(float)Math.Cos(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation),
(float)Math.Sin(IsHorizontal ? -BodyRotation : MathHelper.PiOver2 - BodyRotation)) * diffFromCenter;
if (world && Submarine != null) sectionPos += Submarine.Position;
if (world && Submarine != null)
{
sectionPos += Submarine.Position;
}
return sectionPos;
}
@@ -913,7 +927,7 @@ namespace Barotrauma
//the structure doesn't have any other gap, log the structure being fixed
if (noGaps && attacker != null)
{
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + attacker.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall repaired by " + GameServer.CharacterLogName(attacker), ServerLog.MessageType.ItemInteraction);
}
#endif
DebugConsole.Log("Removing gap (ID " + Sections[sectionIndex].gap.ID + ", section: " + sectionIndex + ") from wall " + ID);
@@ -987,11 +1001,11 @@ namespace Barotrauma
//the structure didn't have any other gaps yet, log the breach
if (noGaps && attacker != null)
{
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall breached by " + attacker.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log((Sections[sectionIndex].gap.IsRoomToRoom ? "Inner" : "Outer") + " wall breached by " + GameServer.CharacterLogName(attacker), ServerLog.MessageType.ItemInteraction);
}
#endif
}
float gapOpen = (damage / Prefab.Health - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
Sections[sectionIndex].gap.Open = gapOpen;
}
@@ -1255,6 +1269,11 @@ namespace Barotrauma
s.UseDropShadow = prefab.Body;
}
if (element.Attribute("noaitarget") == null)
{
s.NoAITarget = prefab.NoAITarget;
}
return s;
}
@@ -1327,6 +1346,7 @@ namespace Barotrauma
SerializableProperties = SerializableProperty.DeserializeProperties(this, Prefab.ConfigElement);
Sprite.ReloadXML();
SpriteDepth = Sprite.Depth;
NoAITarget = Prefab.NoAITarget;
}
public override void Update(float deltaTime, Camera cam)
@@ -4,7 +4,7 @@ using System;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
using System.IO;
using Barotrauma.IO;
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
#endif
@@ -7,7 +7,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -128,7 +128,7 @@ namespace Barotrauma
public PhysicsBody PhysicsBody
{
get { return subBody.Body; }
get { return subBody?.Body; }
}
public Rectangle Borders
@@ -178,23 +178,6 @@ namespace Barotrauma
}
}
private bool? subsLeftBehind;
public bool SubsLeftBehind
{
get
{
if (subsLeftBehind.HasValue) { return subsLeftBehind.Value; }
CheckSubsLeftBehind(Info.SubmarineElement);
return subsLeftBehind.Value;
}
//set { subsLeftBehind = value; }
}
public bool LeftBehindSubDockingPortOccupied
{
get; private set;
}
public new Vector2 DrawPosition
{
@@ -222,7 +205,7 @@ namespace Barotrauma
public List<Vector2> HullVertices
{
get { return subBody.HullVertices; }
get { return subBody?.HullVertices; }
}
public bool AtDamageDepth
@@ -232,7 +215,7 @@ namespace Barotrauma
public override string ToString()
{
return "Barotrauma.Submarine (" + Info?.Name ?? "[NULL INFO]" + ")";
return "Barotrauma.Submarine (" + (Info?.Name ?? "[NULL INFO]") + ", " + IdOffset + ")";
}
public override bool Removed
@@ -249,87 +232,69 @@ namespace Barotrauma
ShowSonarMarker = false;
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
TeamID = Character.TeamType.None;
string defaultTag = Level.Loaded.GetWreckIDTag("wreck_id", this);
ReplaceIDCardTagRequirements("wreck_id", defaultTag);
foreach (Item item in Item.ItemList)
{
if (item.Submarine != this) { continue; }
if (item.prefab.Identifier == "idcardwreck" || item.prefab.Identifier == "idcard")
{
foreach (string tag in item.GetTags().ToList())
{
if (tag == "smallitem") { continue; }
string newTag = Level.Loaded.GetWreckIDTag(tag, this);
item.ReplaceTag(tag, newTag);
ReplaceIDCardTagRequirements(tag, newTag);
}
}
}
void ReplaceIDCardTagRequirements(string oldTag, string newTag)
{
foreach (Item item in Item.ItemList)
{
if (item.Submarine != this) { continue; }
foreach (ItemComponent ic in item.Components)
{
ReplaceIDCardTagRequirement(ic, RelatedItem.RelationType.Picked, oldTag, newTag);
ReplaceIDCardTagRequirement(ic, RelatedItem.RelationType.Equipped, oldTag, newTag);
}
}
}
static void ReplaceIDCardTagRequirement(ItemComponent ic, RelatedItem.RelationType relationType, string oldTag, string newTag)
{
if (!ic.requiredItems.ContainsKey(relationType)) { return; }
foreach (RelatedItem requiredItem in ic.requiredItems[relationType])
{
int index = Array.IndexOf(requiredItem.Identifiers, oldTag);
if (index == -1) { continue; }
requiredItem.Identifiers[index] = newTag;
}
}
}
public WreckAI ThalamusAI { get; private set; }
public bool CreateThalamus()
public WreckAI WreckAI { get; private set; }
public bool CreateWreckAI()
{
MakeWreck();
var thalamusPrefabs = ItemPrefab.Prefabs.Where(p => p.Category == MapEntityCategory.Thalamus || p.Tags.Contains("thalamus"));
var brainPrefab = thalamusPrefabs.GetRandom(i => i.Tags.Contains("thalamusbrain"), Rand.RandSync.Server);
if (brainPrefab == null) { return false; }
var allItems = GetItems(false);
var thalamusItems = allItems.FindAll(i => i.Prefab.Category == MapEntityCategory.Thalamus || i.HasTag("thalamus"));
var hulls = GetHulls(false);
Item brain = new Item(brainPrefab, Vector2.Zero, this);
Vector2 negativeMargin = new Vector2(40, 20);
Vector2 minSize = brain.Rect.Size.ToVector2() - negativeMargin;
Vector2 maxSize = new Vector2(brain.Rect.Width * 3, brain.Rect.Height * 3);
// First try to get a room that is not too big and not in the edges of the sub.
// Also try not to create the brain in a room that already have carrier items inside.
// Ignore hulls that have any linked hulls to keep the calculations simple.
// Shrink the horizontal axis so that the brain is not placed in the left or right side, where we often have curved walls.
// Also ignore hulls that have open gaps, because we'll want the room to be full of water. The room will be filled with water when the brain is inserted in the room.
Rectangle shrinkedBounds = ToolBox.GetWorldBounds(WorldPosition.ToPoint(), new Point(Borders.Width - 500, Borders.Height));
bool BaseCondition(Hull h) => h.RectWidth > minSize.X && h.RectHeight > minSize.Y && h.GetLinkedEntities<Hull>().None() && h.ConnectedGaps.None(g => g.Open > 0);
bool IsNotTooBig(Hull h) => h.RectWidth < maxSize.X && h.RectHeight < maxSize.Y;
bool IsNotInFringes(Hull h) => shrinkedBounds.ContainsWorld(h.WorldRect);
bool DoesNotContainOtherItems(Hull h) => thalamusItems.None(i => i.CurrentHull == h);
Hull brainHull = hulls.GetRandom(h => BaseCondition(h) && IsNotTooBig(h) && IsNotInFringes(h) && DoesNotContainOtherItems(h), Rand.RandSync.Server);
if (brainHull == null)
{
brainHull = hulls.GetRandom(h => BaseCondition(h) && IsNotInFringes(h) && DoesNotContainOtherItems(h), Rand.RandSync.Server);
}
if (brainHull == null)
{
brainHull = hulls.GetRandom(h => BaseCondition(h) && (IsNotInFringes(h) || DoesNotContainOtherItems(h)), Rand.RandSync.Server);
}
if (brainHull == null)
{
brainHull = hulls.GetRandom(BaseCondition, Rand.RandSync.Server);
}
var thalamusStructs = StructurePrefab.Prefabs.Where(p => p.Category == MapEntityCategory.Thalamus);
if (brainHull == null) { return false; }
brainHull.WaterVolume = brainHull.Volume;
brain.SetTransform(brainHull.SimPosition, rotation: 0, findNewHull: false);
brain.CurrentHull = brainHull;
var backgroundPrefab = thalamusStructs.GetRandom(i => i.Tags.Contains("brainroombackground"), Rand.RandSync.Server);
if (backgroundPrefab != null)
{
new Structure(brainHull.Rect, backgroundPrefab, this);
}
var horizontalWallPrefab = thalamusStructs.GetRandom(p => p.Tags.Contains("thalamuswall_horizontal_decorative"), Rand.RandSync.Server);
if (horizontalWallPrefab != null)
{
int height = (int)horizontalWallPrefab.Size.Y;
int halfHeight = height / 2;
int quarterHeight = halfHeight / 2;
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, this);
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top - brainHull.Rect.Height + halfHeight + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, this);
}
var verticalWallPrefab = thalamusStructs.GetRandom(p => p.Tags.Contains("thalamuswall_vertical_decorative"), Rand.RandSync.Server);
if (verticalWallPrefab != null)
{
int width = (int)verticalWallPrefab.Size.X;
int halfWidth = width / 2;
int quarterWidth = halfWidth / 2;
new Structure(new Rectangle(brainHull.Rect.Left - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, this);
new Structure(new Rectangle(brainHull.Rect.Right - halfWidth - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, this);
}
ThalamusAI = new WreckAI(this, brain, allItems);
return true;
WreckAI = new WreckAI(this);
return WreckAI != null;
}
public void DisableThalamus()
public void DisableWreckAI()
{
var thalamusEntities = GetEntities(false, MapEntity.mapEntityList).FindAll(e => e.prefab.Category == MapEntityCategory.Thalamus || e.prefab.Tags.Contains("thalamus")).ToList();
foreach (var entity in thalamusEntities)
if (WreckAI == null)
{
entity.Remove();
WreckAI.RemoveThalamusItems(this);
}
else
{
WreckAI?.Remove();
WreckAI = null;
}
ThalamusAI?.Kill();
ThalamusAI = null;
}
/// <summary>
@@ -775,7 +740,7 @@ namespace Barotrauma
public void FlipX(List<Submarine> parents = null)
{
if (parents == null) parents = new List<Submarine>();
if (parents == null) { parents = new List<Submarine>(); }
parents.Add(this);
flippedX = !flippedX;
@@ -783,20 +748,25 @@ namespace Barotrauma
Item.UpdateHulls();
List<Item> bodyItems = Item.ItemList.FindAll(it => it.Submarine == this && it.body != null);
List<MapEntity> subEntities = MapEntity.mapEntityList.FindAll(me => me.Submarine == this);
foreach (MapEntity e in subEntities)
{
if (e is Item) continue;
if (e is LinkedSubmarine)
if (e is LinkedSubmarine linkedSub)
{
Submarine sub = ((LinkedSubmarine)e).Sub;
if (!parents.Contains(sub))
Submarine sub = linkedSub.Sub;
if (sub == null)
{
Vector2 relative1 = linkedSub.Position - SubBody.Position;
relative1.X = -relative1.X;
linkedSub.Rect = new Rectangle((relative1 + SubBody.Position).ToPoint(), linkedSub.Rect.Size);
}
else if (!parents.Contains(sub))
{
Vector2 relative1 = sub.SubBody.Position - SubBody.Position;
relative1.X = -relative1.X;
sub.SetPosition(relative1 + SubBody.Position);
sub.SetPosition(relative1 + SubBody.Position, new List<Submarine>(parents));
sub.FlipX(parents);
}
}
@@ -814,7 +784,7 @@ namespace Barotrauma
Vector2 pos = new Vector2(subBody.Position.X, subBody.Position.Y);
subBody.Body.Remove();
subBody = new SubmarineBody(this);
SetPosition(pos);
SetPosition(pos, new List<Submarine>(parents.Where(p => p != this)));
if (entityGrid != null)
{
@@ -853,14 +823,15 @@ namespace Barotrauma
{
//if (PlayerInput.KeyHit(InputType.Crouch) && (this == MainSub)) FlipX();
if (Level.Loaded == null || subBody == null) { return; }
if (Info.Type == SubmarineInfo.SubmarineType.Wreck)
if (Info.IsWreck)
{
ThalamusAI?.Update(deltaTime);
WreckAI?.Update(deltaTime);
}
if (WorldPosition.Y < Level.MaxEntityDepth &&
if (subBody?.Body == null) { return; }
if (Level.Loaded != null &&
WorldPosition.Y < Level.MaxEntityDepth &&
subBody.Body.Enabled &&
(GameMain.NetworkMember?.RespawnManager == null || this != GameMain.NetworkMember.RespawnManager.RespawnShuttle))
{
@@ -887,17 +858,17 @@ namespace Barotrauma
return;
}
subBody.Body.LinearVelocity = new Vector2(
LockX ? 0.0f : subBody.Body.LinearVelocity.X,
LockY ? 0.0f : subBody.Body.LinearVelocity.Y);
subBody.Update(deltaTime);
for (int i = 0; i < 2; i++)
{
if (MainSubs[i] == null) continue;
if (this != MainSubs[i] && MainSubs[i].DockedTo.Contains(this)) return;
if (MainSubs[i] == null) { continue; }
if (this != MainSubs[i] && MainSubs[i].DockedTo.Contains(this)) { return; }
}
//send updates more frequently if moving fast
@@ -919,9 +890,9 @@ namespace Barotrauma
prevPosition = position;
}
public void SetPosition(Vector2 position, List<Submarine> checkd=null)
public void SetPosition(Vector2 position, List<Submarine> checkd = null)
{
if (!MathUtils.IsValid(position)) return;
if (!MathUtils.IsValid(position)) { return; }
if (checkd == null) { checkd = new List<Submarine>(); }
if (checkd.Contains(this)) { return; }
@@ -933,9 +904,16 @@ namespace Barotrauma
foreach (Submarine dockedSub in DockedTo)
{
if (dockedSub.PhysicsBody.BodyType == BodyType.Static)
{
if (ConnectedDockingPorts.TryGetValue(dockedSub, out DockingPort port))
{
port.Undock();
continue;
}
}
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
if (expectedLocation == null) { continue; }
dockedSub.SetPosition(position + expectedLocation.Value, checkd);
dockedSub.UpdateTransform(interpolate: false);
}
@@ -992,6 +970,11 @@ namespace Barotrauma
return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
}
public IEnumerable<T> GetEntities<T>(bool includingConnectedSubs, IEnumerable<T> list) where T : MapEntity
{
return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
}
public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs)
{
if (entity == null) { return false; }
@@ -999,7 +982,7 @@ namespace Barotrauma
if (entity.Submarine == null) { return false; }
if (includingConnectedSubs)
{
return GetConnectedSubs().Any(s => s == entity.Submarine && entity.Submarine.TeamID == TeamID);
return GetConnectedSubs().Any(s => s == entity.Submarine && entity.Submarine.TeamID == TeamID && entity.Submarine.Info.Type == Info.Type);
}
return false;
}
@@ -1182,6 +1165,13 @@ namespace Barotrauma
Loading = false;
MapEntity.MapLoaded(newEntities, true);
foreach (MapEntity me in MapEntity.mapEntityList)
{
if (me is LinkedSubmarine linkedSub && linkedSub.Submarine == this)
{
linkedSub.LinkDummyToMainSubmarine();
}
}
foreach (Hull hull in matchingHulls)
{
@@ -1221,28 +1211,6 @@ namespace Barotrauma
return sub;
}
public void CheckSubsLeftBehind(XElement element = null)
{
if (element == null) { element = Info.SubmarineElement; }
subsLeftBehind = false;
LeftBehindSubDockingPortOccupied = false;
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("linkedsubmarine")) { continue; }
if (subElement.Attribute("location") == null) { continue; }
subsLeftBehind = true;
ushort targetDockingPortID = (ushort)subElement.GetAttributeInt("originallinkedto", 0);
XElement targetPortElement = targetDockingPortID == 0 ? null :
element.Elements().FirstOrDefault(e => e.GetAttributeInt("ID", 0) == targetDockingPortID);
if (targetPortElement != null && targetPortElement.GetAttributeIntArray("linked", new int[0]).Length > 0)
{
LeftBehindSubDockingPortOccupied = true;
}
}
}
public void SaveToXElement(XElement element)
{
element.Add(new XAttribute("name", Info.Name));
@@ -1264,14 +1232,17 @@ namespace Barotrauma
e.Save(element);
}
CheckSubsLeftBehind(element);
Info.CheckSubsLeftBehind(element);
}
public bool SaveAs(string filePath, MemoryStream previewImage = null)
public bool SaveAs(string filePath, System.IO.MemoryStream previewImage = null)
{
var newInfo = new SubmarineInfo(this);
newInfo.FilePath = filePath;
newInfo.Name = Path.GetFileNameWithoutExtension(filePath);
var newInfo = new SubmarineInfo(this)
{
GameVersion = GameMain.Version,
FilePath = filePath,
Name = Path.GetFileNameWithoutExtension(filePath)
};
Info.Dispose(); Info = newInfo;
return newInfo.SaveAs(filePath, previewImage);
@@ -131,7 +131,7 @@ namespace Barotrauma
farseerBody.UserData = this;
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != submarine) continue;
if (wall.Submarine != submarine || wall.IsPlatform) { continue; }
Rectangle rect = wall.Rect;
@@ -150,7 +150,7 @@ namespace Barotrauma
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != submarine) continue;
if (hull.Submarine != submarine) { continue; }
Rectangle rect = hull.Rect;
farseerBody.CreateRectangle(
@@ -167,7 +167,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList)
{
if (item.StaticBodyConfig == null || item.Submarine != submarine) continue;
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;
@@ -180,7 +180,7 @@ namespace Barotrauma
if (width > 0.0f && height > 0.0f)
{
farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos).UserData = item;
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos));
minExtents.X = Math.Min(item.Position.X - width / 2, minExtents.X);
minExtents.Y = Math.Min(item.Position.Y - height / 2, minExtents.Y);
@@ -189,9 +189,9 @@ namespace Barotrauma
}
else if (radius > 0.0f && width > 0.0f)
{
farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos).UserData = item;
farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2).UserData = item;
farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2).UserData = item;
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simRadius * 2, 5.0f, simPos));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitX * simWidth / 2));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simWidth / 2));
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);
@@ -199,9 +199,9 @@ namespace Barotrauma
}
else if (radius > 0.0f && height > 0.0f)
{
farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos).UserData = item;
farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2).UserData = item;
farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simHeight / 2).UserData = item;
item.StaticFixtures.Add(farseerBody.CreateRectangle(simRadius * 2, height, 5.0f, simPos));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos - Vector2.UnitY * simHeight / 2));
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos + Vector2.UnitX * simHeight / 2));
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);
@@ -209,12 +209,13 @@ namespace Barotrauma
}
else if (radius > 0.0f)
{
farseerBody.CreateCircle(simRadius, 5.0f, simPos).UserData = item;
item.StaticFixtures.Add(farseerBody.CreateCircle(simRadius, 5.0f, simPos));
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);
}
item.StaticFixtures.ForEach(f => f.UserData = item);
}
Borders = new Rectangle((int)minExtents.X, (int)maxExtents.Y, (int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y));
@@ -301,7 +302,7 @@ namespace Barotrauma
//-------------------------
//if outside left or right edge of the level
if (Position.X < 0 || Position.X > Level.Loaded.Size.X)
if (Level.Loaded != null && (Position.X < 0 || Position.X > Level.Loaded.Size.X))
{
Rectangle worldBorders = Borders;
worldBorders.Location += MathUtils.ToPoint(Position);
@@ -385,12 +386,13 @@ namespace Barotrauma
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null && c.AnimController.CanEnterSubmarine) continue;
if (c.AnimController.CurrentHull != null && c.AnimController.CanEnterSubmarine) { continue; }
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
//if the character isn't inside the bounding box, continue
if (!Submarine.RectContains(worldBorders, limb.WorldPosition)) 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
@@ -454,11 +456,11 @@ namespace Barotrauma
depthDamageTimer -= deltaTime;
if (depthDamageTimer > 0.0f) return;
if (depthDamageTimer > 0.0f) { return; }
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != submarine) continue;
if (wall.Submarine != submarine) { continue; }
if (wall.Health < depth * 0.01f)
{
@@ -498,10 +500,14 @@ namespace Barotrauma
}
return collision;
}
if (f2.Body.UserData is Character character)
else if (f2.Body.UserData is Character character)
{
return CheckCharacterCollision(contact, character);
}
else if (f2.UserData is Items.Components.DockingPort)
{
return false;
}
lock (impactQueue)
{
@@ -692,8 +698,8 @@ namespace Barotrauma
//find all contacts between this sub and level walls
List<Contact> levelContacts = new List<Contact>();
ContactEdge contactEdge = Body.FarseerBody.ContactList;
while (contactEdge.Next != null)
ContactEdge contactEdge = Body?.FarseerBody?.ContactList;
while (contactEdge?.Next != null)
{
if (contactEdge.Contact.Enabled &&
contactEdge.Other.UserData is VoronoiCell &&
@@ -705,7 +711,7 @@ namespace Barotrauma
contactEdge = contactEdge.Next;
}
if (levelContacts.Count == 0) return;
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
@@ -794,6 +800,7 @@ namespace Barotrauma
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f);
}
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
@@ -2,7 +2,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -68,6 +68,8 @@ namespace Barotrauma
public bool IsOutpost => Type == SubmarineType.Outpost;
public bool IsWreck => Type == SubmarineType.Wreck;
public bool IsPlayer => Type == SubmarineType.Player;
public enum SubmarineType { Player, Outpost, Wreck }
public SubmarineType Type { get; set; }
@@ -99,7 +101,11 @@ namespace Barotrauma
set;
}
public readonly XElement SubmarineElement;
public XElement SubmarineElement
{
get;
private set;
}
public override string ToString()
{
@@ -126,6 +132,22 @@ namespace Barotrauma
}
}
private bool? subsLeftBehind;
public bool SubsLeftBehind
{
get
{
if (subsLeftBehind.HasValue) { return subsLeftBehind.Value; }
CheckSubsLeftBehind(SubmarineElement);
return subsLeftBehind.Value;
}
}
public bool LeftBehindSubDockingPortOccupied
{
get; private set;
}
//constructors & generation ----------------------------------------------------
public SubmarineInfo()
{
@@ -135,7 +157,7 @@ namespace Barotrauma
RequiredContentPackages = new HashSet<string>();
}
public SubmarineInfo(string filePath, string hash = "", XElement element = null)
public SubmarineInfo(string filePath, string hash = "", XElement element = null, bool tryLoad = true)
{
FilePath = filePath;
if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
@@ -160,41 +182,23 @@ namespace Barotrauma
RequiredContentPackages = new HashSet<string>();
if (element == null)
if (element == null && tryLoad)
{
XDocument doc = null;
int maxLoadRetries = 4;
for (int i = 0; i <= maxLoadRetries; i++)
{
doc = OpenFile(filePath, out Exception e);
if (e != null && !(e is IOException)) { break; }
if (doc != null || i == maxLoadRetries || !File.Exists(filePath)) { break; }
DebugConsole.NewMessage("Opening submarine file \"" + filePath + "\" failed, retrying in 250 ms...");
Thread.Sleep(250);
}
if (doc == null || doc.Root == null)
{
IsFileCorrupted = true;
return;
}
if (string.IsNullOrWhiteSpace(hash))
{
StartHashDocTask(doc);
}
SubmarineElement = doc.Root;
Reload();
}
else
{
SubmarineElement = element;
}
Name = SubmarineElement.GetAttributeString("name", null) ?? Name;
Init();
}
public SubmarineInfo(Submarine sub) : this(sub.Info)
{
GameVersion = GameMain.Version;
SubmarineElement = new XElement("Submarine");
sub.SaveToXElement(SubmarineElement);
Init();
@@ -222,6 +226,30 @@ namespace Barotrauma
#endif
}
public void Reload()
{
XDocument doc = null;
int maxLoadRetries = 4;
for (int i = 0; i <= maxLoadRetries; i++)
{
doc = OpenFile(FilePath, out Exception e);
if (e != null && !(e is System.IO.IOException)) { break; }
if (doc != null || i == maxLoadRetries || !File.Exists(FilePath)) { break; }
DebugConsole.NewMessage("Opening submarine file \"" + FilePath + "\" failed, retrying in 250 ms...");
Thread.Sleep(250);
}
if (doc == null || doc.Root == null)
{
IsFileCorrupted = true;
return;
}
if (hash == null)
{
StartHashDocTask(doc);
}
SubmarineElement = doc.Root;
}
private void Init()
{
DisplayName = TextManager.Get("Submarine.Name." + Name, true);
@@ -317,8 +345,31 @@ namespace Barotrauma
Tags &= ~tag;
}
public void CheckSubsLeftBehind(XElement element = null)
{
if (element == null) { element = SubmarineElement; }
subsLeftBehind = false;
LeftBehindSubDockingPortOccupied = false;
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("linkedsubmarine", StringComparison.OrdinalIgnoreCase)) { continue; }
if (subElement.Attribute("location") == null) { continue; }
subsLeftBehind = true;
ushort targetDockingPortID = (ushort)subElement.GetAttributeInt("originallinkedto", 0);
XElement targetPortElement = targetDockingPortID == 0 ? null :
element.Elements().FirstOrDefault(e => e.GetAttributeInt("ID", 0) == targetDockingPortID);
if (targetPortElement != null && targetPortElement.GetAttributeIntArray("linked", new int[0]).Length > 0)
{
LeftBehindSubDockingPortOccupied = true;
}
}
}
//saving/loading ----------------------------------------------------
public bool SaveAs(string filePath, MemoryStream previewImage=null)
public bool SaveAs(string filePath, System.IO.MemoryStream previewImage=null)
{
var newElement = new XElement(SubmarineElement.Name,
SubmarineElement.Attributes().Where(a => !string.Equals(a.Name.LocalName, "previewimage", StringComparison.InvariantCultureIgnoreCase)),
@@ -405,7 +456,11 @@ namespace Barotrauma
try
{
filePaths = Directory.GetFiles(SavePath).ToList();
subDirectories = Directory.GetDirectories(SavePath);
subDirectories = Directory.GetDirectories(SavePath).Where(s =>
{
DirectoryInfo dir = new DirectoryInfo(s);
return (dir.Attributes & System.IO.FileAttributes.Hidden) == 0;
}).ToArray();
}
catch (Exception e)
{
@@ -504,12 +559,12 @@ namespace Barotrauma
if (extension == ".sub")
{
Stream stream = null;
System.IO.Stream stream = null;
try
{
stream = SaveUtil.DecompressFiletoStream(file);
}
catch (FileNotFoundException e)
catch (System.IO.FileNotFoundException e)
{
exception = e;
DebugConsole.ThrowError("Loading submarine \"" + file + "\" failed! (File not found) " + Environment.StackTrace, e);
@@ -124,13 +124,15 @@ namespace Barotrauma
#if CLIENT
if (iconSprites == null)
{
iconSprites = new Dictionary<SpawnType, Sprite>()
iconSprites = new Dictionary<string, Sprite>()
{
{ SpawnType.Path, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(0,0,128,128)) },
{ SpawnType.Human, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,0,128,128)) },
{ SpawnType.Enemy, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256,0,128,128)) },
{ SpawnType.Cargo, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384,0,128,128)) },
{ SpawnType.Corpse, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512,0,128,128)) }
{ "Path", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(0,0,128,128)) },
{ "Human", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,0,128,128)) },
{ "Enemy", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256,0,128,128)) },
{ "Cargo", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384,0,128,128)) },
{ "Corpse", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512,0,128,128)) },
{ "Ladder", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(0,128,128,128)) },
{ "Door", new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(128,128,128,128)) }
};
}
#endif