v1.0.13.1 (first post-1.0 patch)
This commit is contained in:
@@ -290,6 +290,7 @@ namespace Barotrauma
|
||||
dictionary.Clear();
|
||||
Hull.EntityGrids.Clear();
|
||||
Spawner?.Reset();
|
||||
Items.Components.Projectile.ResetSpreadCounter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,37 +10,169 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Explosions are area of effect attacks that can damage characters, items and structures.
|
||||
/// </summary>
|
||||
/// <doc>
|
||||
/// <Field Identifier="showEffects" Type="bool" DefaultValue="true">
|
||||
/// Used to enable all particle effects without having to specify them one by one.
|
||||
/// </Field>
|
||||
/// </doc>
|
||||
partial class Explosion
|
||||
{
|
||||
public readonly Attack Attack;
|
||||
|
||||
/// <summary>
|
||||
/// How much force the explosion applies to the characters.
|
||||
/// </summary>
|
||||
private readonly float force;
|
||||
|
||||
private readonly float cameraShake, cameraShakeRange;
|
||||
/// <summary>
|
||||
/// Intensity of the screen shake effect.
|
||||
/// </summary>
|
||||
/// <doc>
|
||||
/// <override type="DefaultValue">
|
||||
/// 10% of the range if showEffects is true, 0 otherwise.
|
||||
/// </override>
|
||||
/// </doc>
|
||||
private readonly float cameraShake;
|
||||
|
||||
/// <summary>
|
||||
/// How far away does the camera shake effect reach.
|
||||
/// </summary>
|
||||
/// <doc>
|
||||
/// <override type="DefaultValue">
|
||||
/// Same as attack range if showEffects is true, 0 otherwise.
|
||||
/// </override>
|
||||
/// </doc>
|
||||
private readonly float cameraShakeRange;
|
||||
|
||||
/// <summary>
|
||||
/// Color tint to apply to the player's screen when in range of the explosion.
|
||||
/// </summary>
|
||||
private readonly Color screenColor;
|
||||
private readonly float screenColorRange, screenColorDuration;
|
||||
|
||||
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
|
||||
/// <summary>
|
||||
/// How far away can the screen color effect be seen.
|
||||
/// </summary>
|
||||
/// <doc>
|
||||
/// <override type="DefaultValue">
|
||||
/// 10% of the range if showEffects is true, 0 otherwise.
|
||||
/// </override>
|
||||
/// </doc>
|
||||
private readonly float screenColorRange;
|
||||
|
||||
/// <summary>
|
||||
/// How long the screen color effect lasts.
|
||||
/// </summary>
|
||||
private readonly float screenColorDuration;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a spark particle effect is created when the explosion happens.
|
||||
/// </summary>
|
||||
private bool sparks;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a shockwave particle effect is created when the explosion happens.
|
||||
/// </summary>
|
||||
private bool shockwave;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a flame particle effect is created when the explosion happens.
|
||||
/// </summary>
|
||||
private bool flames;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a smoke particle effect is created when the explosion happens.
|
||||
/// </summary>
|
||||
private bool smoke;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a flash effect is created when the explosion happens.
|
||||
/// </summary>
|
||||
private bool flash;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a underwater bubble particle effect is created when the explosion happens.
|
||||
/// </summary>
|
||||
private bool underwaterBubble;
|
||||
|
||||
/// <summary>
|
||||
/// Color of the light source created by the explosion.
|
||||
/// </summary>
|
||||
private readonly Color flashColor;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the explosion plays a tinnitus sound to players who get hit by it.
|
||||
/// </summary>
|
||||
private readonly bool playTinnitus;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the explosion executes 'OnFire' status effects on the items it hits.
|
||||
/// </summary>
|
||||
/// <doc>
|
||||
/// <override type="DefaultValue">
|
||||
/// true if showEffects is true and flames haven't been explicitly set to false, false otherwise.
|
||||
/// </override>
|
||||
/// </doc>
|
||||
private readonly bool applyFireEffects;
|
||||
|
||||
/// <summary>
|
||||
/// List of item tags that the explosion ignores when applying fire effects.
|
||||
/// </summary>
|
||||
private readonly string[] ignoreFireEffectsForTags;
|
||||
|
||||
/// <summary>
|
||||
/// When set to true, the explosion don't deal less damage when the target is behind a solid object.
|
||||
/// </summary>
|
||||
private readonly bool ignoreCover;
|
||||
|
||||
/// <summary>
|
||||
/// How long the light source created by the explosion lasts.
|
||||
/// </summary>
|
||||
private readonly float flashDuration;
|
||||
|
||||
/// <summary>
|
||||
/// How large the light source created by the explosion is.
|
||||
/// </summary>
|
||||
private readonly float? flashRange;
|
||||
|
||||
/// <summary>
|
||||
/// Identifier of the decal the explosion creates on the background structure it explodes over.
|
||||
/// Set to empty string to disable.
|
||||
/// </summary>
|
||||
private readonly string decal;
|
||||
|
||||
/// <summary>
|
||||
/// Relative size of the decal created by the explosion.
|
||||
/// </summary>
|
||||
private readonly float decalSize;
|
||||
private readonly bool applyToSelf;
|
||||
|
||||
public bool OnlyInside, OnlyOutside;
|
||||
/// <summary>
|
||||
/// Whether the explosion only affects characters inside a submarine.
|
||||
/// </summary>
|
||||
public bool OnlyInside;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the explosion only affects characters outside a submarine.
|
||||
/// </summary>
|
||||
public bool OnlyOutside;
|
||||
|
||||
/// <summary>
|
||||
/// How much the explosion repairs items.
|
||||
/// </summary>
|
||||
private readonly float itemRepairStrength;
|
||||
|
||||
public readonly HashSet<Submarine> IgnoredSubmarines = new HashSet<Submarine>();
|
||||
|
||||
/// <summary>
|
||||
/// Strength of the EMP effect created by the explosion.
|
||||
/// </summary>
|
||||
public float EmpStrength { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// How much damage the explosion does to ballast flora.
|
||||
/// </summary>
|
||||
public float BallastFloraDamage { get; set; }
|
||||
|
||||
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f, float ballastFloraStrength = 0.0f)
|
||||
@@ -66,8 +198,6 @@ namespace Barotrauma
|
||||
|
||||
force = element.GetAttributeFloat("force", 0.0f);
|
||||
|
||||
applyToSelf = element.GetAttributeBool("applytoself", true);
|
||||
|
||||
//the "abilityexplosion" field is kept for backwards compatibility (basically the opposite of "showeffects")
|
||||
bool showEffects = !element.GetAttributeBool("abilityexplosion", false) && element.GetAttributeBool("showeffects", true);
|
||||
sparks = element.GetAttributeBool("sparks", showEffects);
|
||||
@@ -231,7 +361,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker, applyToSelf);
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -284,8 +414,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
|
||||
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker, bool applyToSelf)
|
||||
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
|
||||
{
|
||||
if (attack.Range <= 0.0f) { return; }
|
||||
|
||||
@@ -300,7 +430,6 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//if (c == attacker && !applyToSelf) { continue; }
|
||||
|
||||
if (OnlyInside && c.Submarine == null)
|
||||
{
|
||||
@@ -513,21 +642,28 @@ namespace Barotrauma
|
||||
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Level.Loaded.ExtraWalls[i] is not DestructibleLevelWall destructibleWall) { continue; }
|
||||
|
||||
bool inRange = false;
|
||||
foreach (var cell in destructibleWall.Cells)
|
||||
{
|
||||
if (cell.IsPointInside(worldPosition))
|
||||
{
|
||||
destructibleWall.AddDamage(levelWallDamage, worldPosition);
|
||||
continue;
|
||||
inRange = true;
|
||||
break;
|
||||
}
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
if (MathUtils.LineSegmentToPointDistanceSquared((edge.Point1 + cell.Translation).ToPoint(), (edge.Point2 + cell.Translation).ToPoint(), worldPosition.ToPoint()) < worldRange * worldRange)
|
||||
{
|
||||
destructibleWall.AddDamage(levelWallDamage, worldPosition);
|
||||
inRange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inRange) { break; }
|
||||
}
|
||||
if (inRange)
|
||||
{
|
||||
destructibleWall.AddDamage(levelWallDamage, worldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Gap : MapEntity
|
||||
partial class Gap : MapEntity, ISerializableEntity
|
||||
{
|
||||
public static List<Gap> GapList = new List<Gap>();
|
||||
|
||||
@@ -57,6 +57,8 @@ namespace Barotrauma
|
||||
private Body outsideCollisionBlocker;
|
||||
private float outsideColliderRaycastTimer;
|
||||
|
||||
private bool wasRoomToRoom;
|
||||
|
||||
public float Open
|
||||
{
|
||||
get { return open; }
|
||||
@@ -153,12 +155,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override string Name
|
||||
public override string Name => "Gap";
|
||||
|
||||
public readonly Dictionary<Identifier, SerializableProperty> properties;
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Gap";
|
||||
}
|
||||
get { return properties; }
|
||||
}
|
||||
|
||||
public Gap(Rectangle rectangle)
|
||||
@@ -185,6 +187,8 @@ namespace Barotrauma
|
||||
IsDiagonal = isDiagonal;
|
||||
open = 1.0f;
|
||||
|
||||
properties = SerializableProperty.GetProperties(this);
|
||||
|
||||
FindHulls();
|
||||
GapList.Add(this);
|
||||
InsertToList();
|
||||
@@ -199,7 +203,10 @@ namespace Barotrauma
|
||||
outsideCollisionBlocker.Enabled = false;
|
||||
#if CLIENT
|
||||
Resized += newRect => IsHorizontal = newRect.Width < newRect.Height;
|
||||
#endif
|
||||
# endif
|
||||
|
||||
wasRoomToRoom = IsRoomToRoom;
|
||||
RefreshOutsideCollider();
|
||||
DebugConsole.Log("Created gap (" + ID + ")");
|
||||
}
|
||||
|
||||
@@ -332,9 +339,14 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
flowForce = Vector2.Zero;
|
||||
|
||||
outsideColliderRaycastTimer -= deltaTime;
|
||||
|
||||
if (IsRoomToRoom != wasRoomToRoom)
|
||||
{
|
||||
RefreshOutsideCollider();
|
||||
wasRoomToRoom = IsRoomToRoom;
|
||||
}
|
||||
|
||||
if (open == 0.0f || linkedTo.Count == 0)
|
||||
{
|
||||
lerpedFlowForce = Vector2.Zero;
|
||||
@@ -628,11 +640,16 @@ namespace Barotrauma
|
||||
|
||||
public bool RefreshOutsideCollider()
|
||||
{
|
||||
if (IsRoomToRoom || Submarine == null || open <= 0.0f || linkedTo.Count == 0 || !(linkedTo[0] is Hull)) return false;
|
||||
if (outsideCollisionBlocker == null) { return false; }
|
||||
if (IsRoomToRoom || Submarine == null || open <= 0.0f || linkedTo.Count == 0 || linkedTo[0] is not Hull)
|
||||
{
|
||||
outsideCollisionBlocker.Enabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outsideColliderRaycastTimer <= 0.0f)
|
||||
{
|
||||
UpdateOutsideColliderPos((Hull)linkedTo[0]);
|
||||
UpdateOutsideColliderState((Hull)linkedTo[0]);
|
||||
outsideColliderRaycastTimer = outsideCollisionBlocker.Enabled ?
|
||||
OutsideColliderRaycastIntervalHighPrio :
|
||||
OutsideColliderRaycastIntervalLowPrio;
|
||||
@@ -641,7 +658,7 @@ namespace Barotrauma
|
||||
return outsideCollisionBlocker.Enabled;
|
||||
}
|
||||
|
||||
private void UpdateOutsideColliderPos(Hull hull)
|
||||
private void UpdateOutsideColliderState(Hull hull)
|
||||
{
|
||||
if (Submarine == null || IsRoomToRoom || Level.Loaded == null) { return; }
|
||||
|
||||
@@ -678,7 +695,7 @@ namespace Barotrauma
|
||||
if (blockingBody.UserData == Submarine) { return; }
|
||||
outsideCollisionBlocker.Enabled = true;
|
||||
Vector2 colliderPos = Submarine.LastPickedPosition - Submarine.SimPosition;
|
||||
float colliderRotation = MathUtils.VectorToAngle(rayDir) - MathHelper.PiOver2;
|
||||
float colliderRotation = MathUtils.VectorToAngle(Submarine.LastPickedNormal) - MathHelper.PiOver2;
|
||||
outsideCollisionBlocker.SetTransformIgnoreContacts(ref colliderPos, colliderRotation);
|
||||
}
|
||||
else
|
||||
@@ -775,8 +792,7 @@ namespace Barotrauma
|
||||
|
||||
public static Gap Load(ContentXElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Rectangle rect = Rectangle.Empty;
|
||||
|
||||
Rectangle rect;
|
||||
if (element.GetAttribute("rect") != null)
|
||||
{
|
||||
rect = element.GetAttributeRect("rect", Rectangle.Empty);
|
||||
|
||||
@@ -65,28 +65,28 @@ namespace Barotrauma
|
||||
set { maxHeight = Math.Max(value, minHeight); }
|
||||
}
|
||||
|
||||
[Serialize(2, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
[Serialize(2, IsPropertySaveable.Yes, description: "Minimum number of tunnel branches in the cave."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MinBranchCount
|
||||
{
|
||||
get { return minBranchCount; }
|
||||
set { minBranchCount = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
[Serialize(4, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
[Serialize(4, IsPropertySaveable.Yes, description: "Maximum number of tunnel branches in the cave."), Editable(MinValueInt = 0, MaxValueInt = 10)]
|
||||
public int MaxBranchCount
|
||||
{
|
||||
get { return maxBranchCount; }
|
||||
set { maxBranchCount = Math.Max(value, minBranchCount); }
|
||||
}
|
||||
|
||||
[Serialize(50, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 10000)]
|
||||
[Serialize(50, IsPropertySaveable.Yes, description: "Total amount of level objects in the cave."), Editable(MinValueInt = 0, MaxValueInt = 10000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.1f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 1.0f, DecimalCount = 2 )]
|
||||
[Serialize(0.1f, IsPropertySaveable.Yes, description: "What portion of the empty cells in the cave should be turned into destructible walls? For example, 0.1 = 10%."), Editable(MinValueFloat = 0, MaxValueFloat = 1.0f, DecimalCount = 2 )]
|
||||
public float DestructibleWallRatio
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -429,7 +429,7 @@ namespace Barotrauma
|
||||
public static bool IsLoadedOutpost => Loaded?.Type == LevelData.LevelType.Outpost;
|
||||
|
||||
/// <summary>
|
||||
/// Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1)
|
||||
/// Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1). Does not take reputation into account.
|
||||
/// </summary>
|
||||
public static bool IsLoadedFriendlyOutpost =>
|
||||
loaded?.Type == LevelData.LevelType.Outpost &&
|
||||
@@ -468,8 +468,7 @@ namespace Barotrauma
|
||||
(StartOutpost.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false) &&
|
||||
StartOutpost.GetConnectedSubs().Any(s => s.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
var reputation = GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation;
|
||||
return reputation == null || reputation.NormalizedValue >= Reputation.HostileThreshold;
|
||||
return GameMain.GameSession.Campaign?.CurrentLocation is not { IsFactionHostile: true };
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -508,6 +507,8 @@ namespace Barotrauma
|
||||
StartLocation = startLocation;
|
||||
EndLocation = endLocation;
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenStart);
|
||||
SetEqualityCheckValue(LevelGenStage.LevelGenParams, unchecked((int)GenerationParams.UintIdentifier));
|
||||
SetEqualityCheckValue(LevelGenStage.Size, borders.Width ^ borders.Height << 16);
|
||||
@@ -535,7 +536,6 @@ namespace Barotrauma
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
|
||||
Voronoi voronoi = new Voronoi(1.0);
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
|
||||
#if CLIENT
|
||||
renderer = new LevelRenderer(this);
|
||||
@@ -838,14 +838,6 @@ namespace Barotrauma
|
||||
foreach (var pathCell in tunnel.Cells)
|
||||
{
|
||||
MarkEdges(pathCell, tunnel.Type);
|
||||
foreach (GraphEdge edge in pathCell.Edges)
|
||||
{
|
||||
var adjacent = edge.AdjacentCell(pathCell);
|
||||
if (adjacent != null)
|
||||
{
|
||||
MarkEdges(adjacent, tunnel.Type);
|
||||
}
|
||||
}
|
||||
if (!pathCells.Contains(pathCell))
|
||||
{
|
||||
pathCells.Add(pathCell);
|
||||
@@ -1813,6 +1805,7 @@ namespace Barotrauma
|
||||
|
||||
if (AbyssArea.Height < islandSize.Y) { return; }
|
||||
|
||||
int createdCaves = 0;
|
||||
int islandCount = GenerationParams.AbyssIslandCount;
|
||||
for (int i = 0; i < islandCount; i++)
|
||||
{
|
||||
@@ -1842,8 +1835,13 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) > GenerationParams.AbyssIslandCaveProbability)
|
||||
bool createCave =
|
||||
//force at least one abyss cave
|
||||
(i == islandCount - 1 && createdCaves == 0) ||
|
||||
Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) < GenerationParams.AbyssIslandCaveProbability;
|
||||
if (!createCave)
|
||||
{
|
||||
//just create a chunk with no cave
|
||||
float radiusVariance = Math.Min(islandArea.Width, islandArea.Height) * 0.1f;
|
||||
var vertices = CaveGenerator.CreateRandomChunk(islandArea.Width - (int)(radiusVariance * 2), islandArea.Height - (int)(radiusVariance * 2), 16, radiusVariance: radiusVariance);
|
||||
Vector2 position = islandArea.Center.ToVector2();
|
||||
@@ -1901,6 +1899,7 @@ namespace Barotrauma
|
||||
new Point(islandArea.Center.X, islandArea.Center.Y + (int)(islandArea.Size.Y * (1.0f - caveScaleRelativeToIsland)) / 2),
|
||||
new Point((int)(islandArea.Size.X * caveScaleRelativeToIsland), (int)(islandArea.Size.Y * caveScaleRelativeToIsland)));
|
||||
AbyssIslands.Add(new AbyssIsland(islandArea, islandCells));
|
||||
createdCaves++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3013,10 +3012,12 @@ namespace Barotrauma
|
||||
|
||||
if (PositionsOfInterest.None(p => p.PositionType == positionType))
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to find a position of the type \"{positionType}\" for mission resources.");
|
||||
foreach (var validType in MineralMission.ValidPositionTypes)
|
||||
{
|
||||
if (validType != positionType && PositionsOfInterest.Any(p => p.PositionType == validType))
|
||||
{
|
||||
DebugConsole.AddWarning($"Placing in \"{validType}\" instead.");
|
||||
positionType = validType;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly List<Identifier> NonRepeatableEvents = new List<Identifier>();
|
||||
|
||||
public readonly Dictionary<EventSet, int> FinishedEvents = new Dictionary<EventSet, int>();
|
||||
|
||||
/// <summary>
|
||||
/// 'Exhaustible' sets won't appear in the same level until after one world step (~10 min, see Map.ProgressWorld) has passed. <see cref="EventSet.Exhaustible"/>.
|
||||
/// </summary>
|
||||
@@ -155,6 +157,47 @@ namespace Barotrauma
|
||||
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", Array.Empty<string>());
|
||||
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)).Select(p => p.Identifier));
|
||||
|
||||
string finishedEventsName = nameof(FinishedEvents);
|
||||
if (element.GetChildElement(finishedEventsName) is { } finishedEventsElement)
|
||||
{
|
||||
foreach (var childElement in finishedEventsElement.GetChildElements(finishedEventsName))
|
||||
{
|
||||
Identifier eventSetIdentifier = childElement.GetAttributeIdentifier("set", Identifier.Empty);
|
||||
if (eventSetIdentifier.IsEmpty) { continue; }
|
||||
if (!EventSet.Prefabs.TryGet(eventSetIdentifier, out EventSet eventSet))
|
||||
{
|
||||
foreach (var prefab in EventSet.Prefabs)
|
||||
{
|
||||
if (FindSetRecursive(prefab, eventSetIdentifier) is { } foundSet)
|
||||
{
|
||||
eventSet = foundSet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (eventSet is null) { continue; }
|
||||
int count = childElement.GetAttributeInt("count", 0);
|
||||
if (count < 1) { continue; }
|
||||
FinishedEvents.Add(eventSet, count);
|
||||
}
|
||||
|
||||
static EventSet FindSetRecursive(EventSet parentSet, Identifier setIdentifier)
|
||||
{
|
||||
foreach (var childSet in parentSet.ChildSets)
|
||||
{
|
||||
if (childSet.Identifier == setIdentifier)
|
||||
{
|
||||
return childSet;
|
||||
}
|
||||
if (FindSetRecursive(childSet, setIdentifier) is { } foundSet)
|
||||
{
|
||||
return foundSet;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
|
||||
}
|
||||
|
||||
@@ -319,6 +362,18 @@ namespace Barotrauma
|
||||
{
|
||||
newElement.Add(new XAttribute("nonrepeatableevents", string.Join(',', NonRepeatableEvents)));
|
||||
}
|
||||
if (FinishedEvents.Any())
|
||||
{
|
||||
var finishedEventsElement = new XElement(nameof(FinishedEvents));
|
||||
foreach (var (set, count) in FinishedEvents)
|
||||
{
|
||||
var element = new XElement(nameof(FinishedEvents),
|
||||
new XAttribute("set", set.Identifier),
|
||||
new XAttribute("count", count));
|
||||
finishedEventsElement.Add(element);
|
||||
}
|
||||
newElement.Add(finishedEventsElement);
|
||||
}
|
||||
}
|
||||
|
||||
parentElement.Add(newElement);
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("20,40,50", IsPropertySaveable.Yes), Editable()]
|
||||
[Serialize("20,40,50", IsPropertySaveable.Yes), Editable]
|
||||
public Color BackgroundTextureColor
|
||||
{
|
||||
get;
|
||||
@@ -176,7 +176,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, ""), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If enabled, no walls generate in the level. Can be useful for e.g. levels that are just supposed to consist of a pre-built outpost."), Editable]
|
||||
public bool NoLevelGeometry
|
||||
{
|
||||
get;
|
||||
@@ -218,21 +218,21 @@ namespace Barotrauma
|
||||
set { height = MathHelper.Clamp(value, 2000, 1000000); }
|
||||
}
|
||||
|
||||
[Serialize(80000, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
[Serialize(80000, IsPropertySaveable.Yes, description: "Minimum depth at the top of the level (100 corresponds to 1 meter)."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int InitialDepthMin
|
||||
{
|
||||
get { return initialDepthMin; }
|
||||
set { initialDepthMin = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
[Serialize(80000, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
[Serialize(80000, IsPropertySaveable.Yes, description: "Maximum depth at the top of the level (100 corresponds to 1 meter)."), Editable(MinValueInt = 0, MaxValueInt = 1000000)]
|
||||
public int InitialDepthMax
|
||||
{
|
||||
get { return initialDepthMax; }
|
||||
set { initialDepthMax = Math.Max(value, initialDepthMin); }
|
||||
}
|
||||
|
||||
[Serialize(6500, IsPropertySaveable.Yes), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
|
||||
[Serialize(6500, IsPropertySaveable.Yes, description: "Minimum width of the main tunnel going through the level, in pixels. Can be automatically increased by the level editor if the submarine is larger than this."), Editable(MinValueInt = 5000, MaxValueInt = 1000000)]
|
||||
public int MinTunnelRadius
|
||||
{
|
||||
get;
|
||||
@@ -240,7 +240,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
[Serialize("0,1", IsPropertySaveable.Yes), Editable]
|
||||
[Serialize("0,1", IsPropertySaveable.Yes, description: "Amount of side tunnels in the level (min,max)."), Editable]
|
||||
public Point SideTunnelCount
|
||||
{
|
||||
get;
|
||||
@@ -248,14 +248,14 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, description: "How much the side tunnels can \"zigzag\". 0 = completely straight tunnel, 1 = can go all the way from the top of the level to the bottom."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float SideTunnelVariance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("2000,6000", IsPropertySaveable.Yes), Editable]
|
||||
[Serialize("2000,6000", IsPropertySaveable.Yes, description: "Minimum width of the side tunnels, in pixels. Unlike the main tunnel, does not get adjusted based on the size of the submarine."), Editable]
|
||||
public Point MinSideTunnelRadius
|
||||
{
|
||||
get;
|
||||
@@ -336,7 +336,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, description: "How much the side tunnels can \"zigzag\". 0 = completely straight tunnel, 1 = can go all the way from the top of the level to the bottom."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
|
||||
public float MainPathVariance
|
||||
{
|
||||
get;
|
||||
@@ -385,28 +385,28 @@ namespace Barotrauma
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "How likely a resource spawn point on a cave path is to contain resources."), Editable(MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float CaveResourceSpawnChance { get; set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
[Serialize(0, IsPropertySaveable.Yes, description: "Number of floating, destructible ice chunks in the level."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int FloatingIceChunkCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 100)]
|
||||
[Serialize(0, IsPropertySaveable.Yes, description: "Number of islands (static wall chunks along the main path) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100)]
|
||||
public int IslandCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
[Serialize(0, IsPropertySaveable.Yes, description: "Number of ice spires in the level."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int IceSpireCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(5, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
[Serialize(5, IsPropertySaveable.Yes, description: "Number of abyss islands in the level."), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int AbyssIslandCount
|
||||
{
|
||||
get;
|
||||
@@ -427,7 +427,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes), Editable()]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, description: "The probability of an abyss island having a cave. There is always a cave in at least one of the islands regardless of this setting."), Editable()]
|
||||
public float AbyssIslandCaveProbability
|
||||
{
|
||||
get;
|
||||
@@ -532,7 +532,7 @@ namespace Barotrauma
|
||||
public float WreckFloodingHullMaxWaterPercentage { get; set; }
|
||||
#endregion
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Should a beacon station always spawn in this type of level?")]
|
||||
public string ForceBeaconStation { get; set; }
|
||||
|
||||
[Serialize(0.4f, IsPropertySaveable.Yes, description: "The probability for wall cells to be removed from the bottom of the map. A value of 0 will produce a completely enclosed tunnel and 1 will make the entire bottom of the level completely open."), Editable()]
|
||||
@@ -571,21 +571,21 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0,0", IsPropertySaveable.Yes), Editable]
|
||||
[Serialize("0,0", IsPropertySaveable.Yes, description: "Interval of lightning-like flashes of light in the level."), Editable]
|
||||
public Vector2 FlashInterval
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("0,0,0,0", IsPropertySaveable.Yes), Editable]
|
||||
[Serialize("0,0,0,0", IsPropertySaveable.Yes, description: "Color of lightning-like flashes of light in the level."), Editable]
|
||||
public Color FlashColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should the \"ambient noise\" of the biome play in this level if it's an outpost level."), Editable]
|
||||
public bool PlayNoiseLoopInOutpostLevel
|
||||
{
|
||||
get;
|
||||
|
||||
+3
-3
@@ -129,7 +129,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0.0,1.0", IsPropertySaveable.Yes), Editable]
|
||||
[Serialize("0.0,1.0", IsPropertySaveable.Yes, description: "The sprite depth of the object (min, max). Values of 0 or less make the object render in front of walls, values larger than 0 make it render behind walls with a parallax effect."), Editable]
|
||||
public Vector2 DepthRange
|
||||
{
|
||||
get;
|
||||
@@ -273,14 +273,14 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should the object disappear if the object is destroyed? Only relevant if TakeLevelWallDamage is true."), Editable]
|
||||
public bool HideWhenBroken
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes, description: "Amount of health the object has. Only relevant if TakeLevelWallDamage is true."), Editable]
|
||||
public float Health
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
@@ -658,6 +659,10 @@ namespace Barotrauma
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, triggerer, item.AllPropertyObjects, position);
|
||||
}
|
||||
else if (triggerer is Submarine sub)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, sub, Array.Empty<ISerializableEntity>(), position);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
|
||||
@@ -95,6 +95,8 @@ namespace Barotrauma
|
||||
|
||||
public Reputation Reputation => Faction?.Reputation;
|
||||
|
||||
public bool IsFactionHostile => Faction?.Reputation.NormalizedValue < Reputation.HostileThreshold;
|
||||
|
||||
public int TurnsInRadiation { get; set; }
|
||||
|
||||
#region Store
|
||||
@@ -177,29 +179,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static PurchasedItem CreateInitialStockItem(ItemPrefab itemPrefab, PriceInfo priceInfo)
|
||||
{
|
||||
int quantity = PriceInfo.DefaultAmount;
|
||||
if (priceInfo.MaxAvailableAmount > 0)
|
||||
{
|
||||
quantity =
|
||||
priceInfo.MaxAvailableAmount > priceInfo.MinAvailableAmount ?
|
||||
Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount + 1) :
|
||||
priceInfo.MaxAvailableAmount;
|
||||
}
|
||||
else if (priceInfo.MinAvailableAmount > 0)
|
||||
{
|
||||
quantity = priceInfo.MinAvailableAmount;
|
||||
}
|
||||
return new PurchasedItem(itemPrefab, quantity, buyer: null);
|
||||
}
|
||||
|
||||
public List<PurchasedItem> CreateStock()
|
||||
{
|
||||
var stock = new List<PurchasedItem>();
|
||||
foreach (var prefab in ItemPrefab.Prefabs)
|
||||
{
|
||||
if (!prefab.CanBeBoughtFrom(this, out var priceInfo)) { continue; }
|
||||
int quantity = PriceInfo.DefaultAmount;
|
||||
if (priceInfo.MaxAvailableAmount > 0)
|
||||
{
|
||||
if (priceInfo.MaxAvailableAmount > priceInfo.MinAvailableAmount)
|
||||
{
|
||||
quantity = Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
quantity = priceInfo.MaxAvailableAmount;
|
||||
}
|
||||
}
|
||||
else if (priceInfo.MinAvailableAmount > 0)
|
||||
{
|
||||
quantity = priceInfo.MinAvailableAmount;
|
||||
}
|
||||
stock.Add(new PurchasedItem(prefab, quantity, buyer: null));
|
||||
stock.Add(CreateInitialStockItem(prefab, priceInfo));
|
||||
}
|
||||
return stock;
|
||||
}
|
||||
@@ -304,6 +307,7 @@ namespace Barotrauma
|
||||
if (!faction.IsEmpty && GameMain.GameSession.Campaign.GetFactionAffiliation(faction) is FactionAffiliation.Positive)
|
||||
{
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated, includeSaved: false));
|
||||
price *= 1f - characters.Max(static c => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, new Identifier("all")));
|
||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, tag)));
|
||||
}
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
|
||||
@@ -811,30 +815,36 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
AddMission(mission);
|
||||
DebugConsole.NewMessage($"Unlocked a mission by \"{identifier}\".", debugOnly: true);
|
||||
return mission;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mission UnlockMissionByTag(Identifier tag)
|
||||
public Mission UnlockMissionByTag(Identifier tag, Random random = null)
|
||||
{
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
|
||||
var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Contains(tag));
|
||||
if (!matchingMissions.Any())
|
||||
if (matchingMissions.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var unusedMissions = matchingMissions.Where(m => !availableMissions.Any(mission => mission.Prefab == m));
|
||||
var unusedMissions = matchingMissions.Where(m => availableMissions.None(mission => mission.Prefab == m));
|
||||
if (unusedMissions.Any())
|
||||
{
|
||||
var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this)) || m.IsAllowed(this, this)));
|
||||
if (!suitableMissions.Any())
|
||||
if (suitableMissions.None())
|
||||
{
|
||||
suitableMissions = unusedMissions;
|
||||
}
|
||||
MissionPrefab missionPrefab = ToolBox.SelectWeightedRandom(suitableMissions.ToList(), suitableMissions.Select(m => (float)m.Commonness).ToList(), Rand.RandSync.Unsynced);
|
||||
|
||||
MissionPrefab missionPrefab =
|
||||
random != null ?
|
||||
ToolBox.SelectWeightedRandom(suitableMissions.OrderBy(m => m.Identifier), m => m.Commonness, random) :
|
||||
ToolBox.SelectWeightedRandom(suitableMissions.OrderBy(m => m.Identifier), m => m.Commonness, Rand.RandSync.Unsynced);
|
||||
|
||||
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
|
||||
//don't allow duplicate missions in the same connection
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
|
||||
@@ -842,6 +852,7 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
AddMission(mission);
|
||||
DebugConsole.NewMessage($"Unlocked a random mission by \"{tag}\".", debugOnly: true);
|
||||
return mission;
|
||||
}
|
||||
else
|
||||
@@ -876,7 +887,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var suitableConnections = Connections.Where(c => prefab.IsAllowed(this, c.OtherLocation(this)));
|
||||
if (!suitableConnections.Any())
|
||||
if (suitableConnections.None())
|
||||
{
|
||||
suitableConnections = Connections.ToList();
|
||||
}
|
||||
@@ -1270,25 +1281,31 @@ namespace Barotrauma
|
||||
}
|
||||
var stock = new List<PurchasedItem>(store.Stock);
|
||||
var stockToRemove = new List<PurchasedItem>();
|
||||
foreach (var item in stock)
|
||||
|
||||
foreach (var itemPrefab in ItemPrefab.Prefabs)
|
||||
{
|
||||
if (item.ItemPrefab.CanBeBoughtFrom(store, out PriceInfo priceInfo))
|
||||
var existingStock = stock.FirstOrDefault(s => s.ItemPrefab == itemPrefab);
|
||||
if (itemPrefab.CanBeBoughtFrom(store, out PriceInfo priceInfo))
|
||||
{
|
||||
item.Quantity += 1;
|
||||
if (priceInfo.MaxAvailableAmount > 0)
|
||||
if (existingStock == null)
|
||||
{
|
||||
item.Quantity = Math.Min(item.Quantity, priceInfo.MaxAvailableAmount);
|
||||
//can be bought from the location, but not in stock - some new item added by an update or mod?
|
||||
stock.Add(StoreInfo.CreateInitialStockItem(itemPrefab, priceInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Quantity = Math.Min(item.Quantity, CargoManager.MaxQuantity);
|
||||
existingStock.Quantity =
|
||||
Math.Min(
|
||||
existingStock.Quantity + 1,
|
||||
priceInfo.MaxAvailableAmount > 0 ? priceInfo.MaxAvailableAmount : CargoManager.MaxQuantity);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (existingStock != null)
|
||||
{
|
||||
stockToRemove.Add(item);
|
||||
stockToRemove.Add(existingStock);
|
||||
}
|
||||
}
|
||||
|
||||
stockToRemove.ForEach(i => stock.Remove(i));
|
||||
store.Stock.Clear();
|
||||
store.Stock.AddRange(stock);
|
||||
|
||||
@@ -1456,7 +1456,13 @@ namespace Barotrauma
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "location":
|
||||
Location location = Locations[subElement.GetAttributeInt("i", 0)];
|
||||
int locationIndex = subElement.GetAttributeInt("i", -1);
|
||||
if (locationIndex < 0 || locationIndex >= Locations.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the campaign map: location index out of bounds ({locationIndex})");
|
||||
continue;
|
||||
}
|
||||
Location location = Locations[locationIndex];
|
||||
location.ProximityTimer.Clear();
|
||||
for (int i = 0; i < location.Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
@@ -1502,9 +1508,17 @@ namespace Barotrauma
|
||||
|
||||
break;
|
||||
case "connection":
|
||||
int connectionIndex = subElement.GetAttributeInt("i", 0);
|
||||
//the index wasn't saved previously, skip if that's the case
|
||||
if (subElement.Attribute("i") == null) { continue; }
|
||||
|
||||
int connectionIndex = subElement.GetAttributeInt("i", -1);
|
||||
if (connectionIndex < 0 || connectionIndex >= Connections.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the campaign map: connection index out of bounds ({connectionIndex})");
|
||||
continue;
|
||||
}
|
||||
Connections[connectionIndex].Passed = subElement.GetAttributeBool("passed", false);
|
||||
Connections[connectionIndex].Locked = subElement.GetAttributeBool("locked", false);
|
||||
Connections[connectionIndex].Locked = subElement.GetAttributeBool("locked", false);
|
||||
break;
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
@@ -1635,6 +1649,7 @@ namespace Barotrauma
|
||||
new XAttribute("locked", connection.Locked),
|
||||
new XAttribute("difficulty", connection.Difficulty),
|
||||
new XAttribute("biome", connection.Biome.Identifier),
|
||||
new XAttribute("i", i),
|
||||
new XAttribute("locations", Locations.IndexOf(connection.Locations[0]) + "," + Locations.IndexOf(connection.Locations[1])));
|
||||
connection.LevelData.Save(connectionElement);
|
||||
mapElement.Add(connectionElement);
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes), Editable(MinValueInt = -1, MaxValueInt = 10)]
|
||||
[Serialize(-1, IsPropertySaveable.Yes, description: "Should this type of outpost be forced to the locations at the end of the campaign map? 0 = first end level, 1 = second end level, and so on."), Editable(MinValueInt = -1, MaxValueInt = 10)]
|
||||
public int ForceToEndLocationIndex
|
||||
{
|
||||
get;
|
||||
@@ -32,7 +32,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
[Serialize(10, IsPropertySaveable.Yes), Editable(MinValueInt = 1, MaxValueInt = 50)]
|
||||
[Serialize(10, IsPropertySaveable.Yes, description: "Total number of modules in the outpost."), Editable(MinValueInt = 1, MaxValueInt = 50)]
|
||||
public int TotalModuleCount
|
||||
{
|
||||
get;
|
||||
@@ -46,70 +46,70 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(200.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
[Serialize(200.0f, IsPropertySaveable.Yes, description: "Minimum length of the hallways between modules. If 0, the generator will place the modules directly against each other assuming it can be done without making any modules overlap."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
||||
public float MinHallwayLength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should this outpost always be destructible, regardless if damaging outposts is allowed by the server?"), Editable]
|
||||
public bool AlwaysDestructible
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should this outpost always be rewireable, regardless if rewiring is allowed by the server?"), Editable]
|
||||
public bool AlwaysRewireable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should stealing from this outpost be always allowed?"), Editable]
|
||||
public bool AllowStealing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Should the crew spawn inside the outpost (if not, they'll spawn in the submarine)."), Editable]
|
||||
public bool SpawnCrewInsideOutpost
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Should doors at the edges of an outpost module that didn't get connected to another module be locked?"), Editable]
|
||||
public bool LockUnusedDoors
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "Should gaps at the edges of an outpost module that didn't get connected to another module be removed?"), Editable]
|
||||
public bool RemoveUnusedGaps
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should the whole outpost render behind submarines? Only set this to true if the submarine is intended to go inside the outpost."), Editable]
|
||||
public bool DrawBehindSubs
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Minimum amount of water in the hulls of the outpost."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float MinWaterPercentage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Maximum amount of water in the hulls of the outpost."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
|
||||
public float MaxWaterPercentage
|
||||
{
|
||||
get;
|
||||
@@ -122,7 +122,7 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes), Editable]
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the outpost generation parameters that should be used if this outpost has become critically irradiated."), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
public ContentPath OutpostFilePath { get; set; }
|
||||
|
||||
@@ -306,7 +306,7 @@ namespace Barotrauma
|
||||
}
|
||||
idOffset = moduleEntities.Max(e => e.ID) + 1;
|
||||
|
||||
var wallEntities = moduleEntities.Where(e => e is Structure).Cast<Structure>();
|
||||
var wallEntities = moduleEntities.Where(e => e is Structure s && s.HasBody).Cast<Structure>();
|
||||
var hullEntities = moduleEntities.Where(e => e is Hull).Cast<Hull>();
|
||||
|
||||
// Tell the hulls what tags the module has, used to spawn NPCs on specific rooms
|
||||
@@ -1440,27 +1440,7 @@ namespace Barotrauma
|
||||
|
||||
private static void EnableFactionSpecificEntities(Submarine sub, Location location)
|
||||
{
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (string.IsNullOrEmpty(me.Layer) || me.Submarine != sub) { continue; }
|
||||
|
||||
var layerAsIdentifier = me.Layer.ToIdentifier();
|
||||
if (FactionPrefab.Prefabs.ContainsKey(layerAsIdentifier))
|
||||
{
|
||||
me.HiddenInGame =
|
||||
location?.Faction?.Prefab != FactionPrefab.Prefabs[layerAsIdentifier];
|
||||
#if CLIENT
|
||||
//normally this is handled in LightComponent.OnMapLoaded, but this method is called after that
|
||||
if (me.HiddenInGame && me is Item item)
|
||||
{
|
||||
foreach (var lightComponent in item.GetComponents<LightComponent>())
|
||||
{
|
||||
lightComponent.Light.Enabled = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
sub.EnableFactionSpecificEntities(location?.Faction?.Prefab.Identifier ?? Identifier.Empty);
|
||||
}
|
||||
|
||||
private static void LockUnusedDoors(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities, bool removeUnusedGaps)
|
||||
|
||||
@@ -93,13 +93,11 @@ namespace Barotrauma
|
||||
{
|
||||
moduleFlags.Add("hallwayhorizontal".ToIdentifier());
|
||||
if (newFlags.Contains("ruin".ToIdentifier())) { moduleFlags.Add("ruin".ToIdentifier()); }
|
||||
return;
|
||||
}
|
||||
if (newFlags.Contains("hallwayvertical".ToIdentifier()))
|
||||
{
|
||||
moduleFlags.Add("hallwayvertical".ToIdentifier());
|
||||
if (newFlags.Contains("ruin".ToIdentifier())) { moduleFlags.Add("ruin".ToIdentifier()); }
|
||||
return;
|
||||
}
|
||||
if (!newFlags.Any())
|
||||
{
|
||||
|
||||
@@ -939,7 +939,7 @@ namespace Barotrauma
|
||||
Rand.Range(worldRect.X, worldRect.Right + 1),
|
||||
Rand.Range(worldRect.Y - worldRect.Height, worldRect.Y + 1));
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
|
||||
var particle = GameMain.ParticleManager.CreateParticle(Prefab.DamageParticle, particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
|
||||
if (particle == null) break;
|
||||
}
|
||||
}
|
||||
@@ -1085,9 +1085,9 @@ namespace Barotrauma
|
||||
return new AttackResult(damageAmount, null);
|
||||
}
|
||||
|
||||
public void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true, bool createExplosionEffect = true)
|
||||
public void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true, bool isNetworkEvent = true, bool createExplosionEffect = true)
|
||||
{
|
||||
if (Submarine != null && Submarine.GodMode || Indestructible) { return; }
|
||||
if (Submarine != null && Submarine.GodMode || (Indestructible && !isNetworkEvent)) { return; }
|
||||
if (!Prefab.Body) { return; }
|
||||
if (!MathUtils.IsValid(damage)) { return; }
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
#endif
|
||||
@@ -44,30 +45,26 @@ namespace Barotrauma
|
||||
|
||||
public override ImmutableHashSet<string> Aliases { get; }
|
||||
|
||||
//does the structure have a physics body
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Does the structure have a physics body?")]
|
||||
public bool Body { get; private set; }
|
||||
|
||||
//rotation of the physics body in degrees
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "Rotation of the physics body in degrees.")]
|
||||
public float BodyRotation { get; private set; }
|
||||
|
||||
//in display units
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "Width of the physics body in pixels.")]
|
||||
public float BodyWidth { get; private set; }
|
||||
|
||||
//in display units
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "Height of the physics body in pixels.")]
|
||||
public float BodyHeight { get; private set; }
|
||||
|
||||
//in display units
|
||||
[Serialize("0.0,0.0", IsPropertySaveable.No)]
|
||||
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "Offset of the physics body from the center of the structure in pixels.")]
|
||||
public Vector2 BodyOffset { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Is the structure a platform (i.e. a \"floor\" the players can pass through)? Only relevant if the structure has a physics body.")]
|
||||
public bool Platform { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can items like signal components be attached on this structure? Should be enabled on structures like decorative background walls.")]
|
||||
public bool AllowAttachItems { get; private set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
@@ -81,27 +78,30 @@ namespace Barotrauma
|
||||
private set { health = Math.Max(value, MinHealth); }
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the structure be indestructible when used in an outpost?")]
|
||||
public bool IndestructibleInOutposts { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should the structure cast shadows and obstruct visibility when LOS is enabled?")]
|
||||
public bool CastShadow { get; private set; }
|
||||
|
||||
[Serialize(Direction.None, IsPropertySaveable.No)]
|
||||
[Serialize(Direction.None, IsPropertySaveable.No, description: "Makes the structure function as a staircase.")]
|
||||
public Direction StairDirection { get; private set; }
|
||||
|
||||
[Serialize(45.0f, IsPropertySaveable.No)]
|
||||
[Serialize(45.0f, IsPropertySaveable.No, description: "Angle of the stairs in degrees. Only relevant if StairDirection is something else than None.")]
|
||||
public float StairAngle { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "If enabled, monsters will not be able to target this structure.")]
|
||||
public bool NoAITarget { get; private set; }
|
||||
|
||||
[Serialize("0,0", IsPropertySaveable.Yes)]
|
||||
[Serialize("0,0", IsPropertySaveable.Yes, description: "Size of the structure in pixels. If not set, the size is determined, based on the attributes width and height, and if those aren't defined either, based on the size of the structure's sprite.")]
|
||||
public Vector2 Size { get; private set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the sound that plays when something damages the wall.")]
|
||||
public string DamageSound { get; private set; }
|
||||
|
||||
[Serialize("shrapnel", IsPropertySaveable.Yes, description: "Identifier of the particles emitted when something damages the wall.")]
|
||||
public string DamageParticle { get; private set; }
|
||||
|
||||
protected Vector2 textureScale = Vector2.One;
|
||||
[Editable(DecimalCount = 3), Serialize("1.0, 1.0", IsPropertySaveable.Yes)]
|
||||
public Vector2 TextureScale
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
@@ -1044,6 +1041,30 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
public void EnableFactionSpecificEntities(Identifier factionIdentifier)
|
||||
{
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (string.IsNullOrEmpty(me.Layer) || me.Submarine != this) { continue; }
|
||||
|
||||
var layerAsIdentifier = me.Layer.ToIdentifier();
|
||||
if (FactionPrefab.Prefabs.ContainsKey(layerAsIdentifier))
|
||||
{
|
||||
me.HiddenInGame = factionIdentifier != layerAsIdentifier;
|
||||
#if CLIENT
|
||||
//normally this is handled in LightComponent.OnMapLoaded, but this method is called after that
|
||||
if (me.HiddenInGame && me is Item item)
|
||||
{
|
||||
foreach (var lightComponent in item.GetComponents<LightComponent>())
|
||||
{
|
||||
lightComponent.Light.Enabled = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (Info.IsWreck)
|
||||
@@ -1098,7 +1119,7 @@ namespace Barotrauma
|
||||
|
||||
public void ApplyForce(Vector2 force)
|
||||
{
|
||||
if (subBody != null) subBody.ApplyForce(force);
|
||||
if (subBody != null) { subBody.ApplyForce(force); }
|
||||
}
|
||||
|
||||
public void EnableMaintainPosition()
|
||||
@@ -1690,9 +1711,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<int, MapEntity> savedEntities = new Dictionary<int, MapEntity>();
|
||||
foreach (MapEntity e in MapEntity.mapEntityList.OrderBy(e => e.ID))
|
||||
{
|
||||
if (!e.ShouldBeSaved) { continue; }
|
||||
|
||||
if (e.Removed)
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Submarine.SaveToXElement:Removed" + e.Name,
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
$"Attempted to save a removed entity (\"{e.Name}\"). Duplicate ID: {savedEntities.ContainsKey(e.ID)}");
|
||||
DebugConsole.ThrowError($"Error while saving the submarine. Attempted to save a removed entity (\"{e.Name} ({e.ID})\"). The entity will not be saved to avoid corrupting the submarine file.");
|
||||
continue;
|
||||
}
|
||||
if (savedEntities.TryGetValue(e.ID, out MapEntity duplicateEntity))
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Submarine.SaveToXElement:DuplicateId" + e.Name,
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
$"Attempted to save an entity with a duplicate ID ({e.Name}, {duplicateEntity.Name}).");
|
||||
DebugConsole.ThrowError($"Error while saving the submarine. The entity \"{e.Name}\" has the same ID as \"{duplicateEntity.Name}\" ({e.ID}). The entity will not be saved to avoid corrupting the submarine file.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e is Item item)
|
||||
{
|
||||
if (item.FindParentInventory(inv => inv is CharacterInventory) != null) { continue; }
|
||||
@@ -1712,6 +1754,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
e.Save(element);
|
||||
savedEntities.Add(e.ID, e);
|
||||
}
|
||||
Info.CheckSubsLeftBehind(element);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace Barotrauma
|
||||
private const float MaxCollisionImpact = 5.0f;
|
||||
private const float Friction = 0.2f, Restitution = 0.0f;
|
||||
|
||||
private readonly List<Contact> levelContacts = new List<Contact>();
|
||||
|
||||
public List<Vector2> HullVertices
|
||||
{
|
||||
get;
|
||||
@@ -56,6 +58,9 @@ namespace Barotrauma
|
||||
|
||||
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||
|
||||
private float forceUpwardsTimer;
|
||||
private const float ForceUpwardsDelay = 30.0f;
|
||||
|
||||
struct Impact
|
||||
{
|
||||
public Fixture Target;
|
||||
@@ -199,6 +204,7 @@ namespace Barotrauma
|
||||
if (item.Submarine != submarine) { continue; }
|
||||
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(item.Position);
|
||||
if (sub.FlippedX) { simPos.X = -simPos.X; }
|
||||
if (item.GetComponent<Door>() is Door door)
|
||||
{
|
||||
door.OutsideSubmarineFixture = farseerBody.CreateRectangle(door.Body.Width, door.Body.Height, 5.0f, simPos, collisionCategory, collidesWith);
|
||||
@@ -215,11 +221,6 @@ namespace Barotrauma
|
||||
float simWidth = ConvertUnits.ToSimUnits(width);
|
||||
float simHeight = ConvertUnits.ToSimUnits(height);
|
||||
|
||||
if (sub.FlippedX)
|
||||
{
|
||||
simPos.X = -simPos.X;
|
||||
}
|
||||
|
||||
if (width > 0.0f && height > 0.0f)
|
||||
{
|
||||
item.StaticFixtures.Add(farseerBody.CreateRectangle(simWidth, simHeight, 5.0f, simPos, collisionCategory, collidesWith));
|
||||
@@ -393,6 +394,33 @@ namespace Barotrauma
|
||||
|
||||
//-------------------------
|
||||
|
||||
//if heading up and there's another sub on top of us, gradually force it upwards
|
||||
//(i.e. apply "artificial buoyancy" to it) to prevent us from getting pinned under it
|
||||
//only applies to enemy subs with no enemies inside them (like destroyed pirate subs)
|
||||
if (totalForce.Y > 0)
|
||||
{
|
||||
ContactEdge contactEdge = Body?.FarseerBody?.ContactList;
|
||||
while (contactEdge?.Contact != null)
|
||||
{
|
||||
if (contactEdge.Contact.Enabled &&
|
||||
contactEdge.Other.UserData is Submarine otherSubmarine &&
|
||||
otherSubmarine.TeamID != Submarine.TeamID &&
|
||||
contactEdge.Contact.IsTouching)
|
||||
{
|
||||
contactEdge.Contact.GetWorldManifold(out Vector2 _, out FixedArray2<Vector2> points);
|
||||
if (points[0].Y > Body.SimPosition.Y &&
|
||||
!Character.CharacterList.Any(c => c.Submarine == otherSubmarine && !c.IsIncapacitated && c.TeamID == otherSubmarine.TeamID))
|
||||
{
|
||||
otherSubmarine.GetConnectedSubs().ForEach(s => s.SubBody.forceUpwardsTimer += deltaTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------
|
||||
|
||||
if (Body.LinearVelocity.LengthSquared() > 0.0001f)
|
||||
{
|
||||
//TODO: sync current drag with clients?
|
||||
@@ -416,7 +444,32 @@ namespace Barotrauma
|
||||
|
||||
ApplyForce(totalForce);
|
||||
|
||||
if (Velocity.LengthSquared() < 0.01f)
|
||||
{
|
||||
levelContacts.Clear();
|
||||
levelContacts.AddRange(GetLevelContacts(Body));
|
||||
for (int i = 0; i < levelContacts.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < levelContacts.Count; j++)
|
||||
{
|
||||
levelContacts[i].GetWorldManifold(out Vector2 normal1, out _);
|
||||
levelContacts[j].GetWorldManifold(out Vector2 normal2, out _);
|
||||
|
||||
//normals pointing in different directions = sub lodged between two walls
|
||||
if (Vector2.Dot(normal1, normal2) < 0)
|
||||
{
|
||||
//apply an extra force to hopefully dislodge the sub
|
||||
ApplyForce(totalForce * 100.0f);
|
||||
i = levelContacts.Count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDepthDamage(deltaTime);
|
||||
|
||||
forceUpwardsTimer = MathHelper.Clamp(forceUpwardsTimer - deltaTime * 0.1f, 0.0f, ForceUpwardsDelay);
|
||||
}
|
||||
|
||||
partial void ClientUpdatePosition(float deltaTime);
|
||||
@@ -483,9 +536,18 @@ namespace Barotrauma
|
||||
float buoyancy = NeutralBallastPercentage - waterPercentage;
|
||||
|
||||
if (buoyancy > 0.0f)
|
||||
{
|
||||
buoyancy *= 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
buoyancy = Math.Max(buoyancy, -0.5f);
|
||||
}
|
||||
|
||||
if (forceUpwardsTimer > 0.0f)
|
||||
{
|
||||
buoyancy = MathHelper.Lerp(buoyancy, 0.1f, forceUpwardsTimer / ForceUpwardsDelay);
|
||||
}
|
||||
|
||||
return new Vector2(0.0f, buoyancy * Body.Mass * 10.0f);
|
||||
}
|
||||
@@ -630,7 +692,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//if all the bodies of a wall have been disabled, we don't need to care about gaps (can always pass through)
|
||||
if (!(contact.FixtureA.UserData is Structure wall) || !wall.AllSectionBodiesDisabled())
|
||||
if (contact.FixtureA.UserData is not Structure wall || !wall.AllSectionBodiesDisabled())
|
||||
{
|
||||
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
|
||||
Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
|
||||
@@ -682,20 +744,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//find all contacts between the limb and level walls
|
||||
List<Contact> levelContacts = new List<Contact>();
|
||||
ContactEdge contactEdge = limb.body.FarseerBody.ContactList;
|
||||
while (contactEdge?.Contact != null)
|
||||
{
|
||||
if (contactEdge.Contact.Enabled &&
|
||||
contactEdge.Contact.IsTouching &&
|
||||
contactEdge.Other?.UserData is VoronoiCell)
|
||||
{
|
||||
levelContacts.Add(contactEdge.Contact);
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
IEnumerable<Contact> levelContacts = GetLevelContacts(limb.body);
|
||||
int levelContactCount = levelContacts.Count();
|
||||
|
||||
if (levelContacts.Count == 0) { return; }
|
||||
if (levelContactCount == 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),
|
||||
@@ -718,9 +770,9 @@ namespace Barotrauma
|
||||
avgContactNormal += contactNormal;
|
||||
|
||||
//apply impacts at the positions where this sub is touching the limb
|
||||
ApplyImpact((Vector2.Dot(-collision.Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, collision.ImpactPos, applyDamage: false);
|
||||
ApplyImpact((Vector2.Dot(-collision.Velocity, contactNormal) / 2.0f) / levelContactCount, contactNormal, collision.ImpactPos, applyDamage: false);
|
||||
}
|
||||
avgContactNormal /= levelContacts.Count;
|
||||
avgContactNormal /= levelContactCount;
|
||||
|
||||
float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal);
|
||||
if (contactDot > 0.001f)
|
||||
@@ -759,6 +811,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Contact> GetLevelContacts(PhysicsBody body)
|
||||
{
|
||||
ContactEdge contactEdge = body.FarseerBody.ContactList;
|
||||
while (contactEdge?.Contact != null)
|
||||
{
|
||||
if (contactEdge.Contact.Enabled &&
|
||||
contactEdge.Contact.IsTouching &&
|
||||
contactEdge.Other?.UserData is VoronoiCell)
|
||||
{
|
||||
yield return contactEdge.Contact;
|
||||
}
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLevelCollision(Impact impact, VoronoiCell cell = null)
|
||||
{
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.RoundDuration < 10)
|
||||
@@ -827,21 +894,9 @@ 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)
|
||||
{
|
||||
if (contactEdge.Contact.Enabled &&
|
||||
contactEdge.Other.UserData is VoronoiCell &&
|
||||
contactEdge.Contact.IsTouching)
|
||||
{
|
||||
levelContacts.Add(contactEdge.Contact);
|
||||
}
|
||||
|
||||
contactEdge = contactEdge.Next;
|
||||
}
|
||||
|
||||
if (levelContacts.Count == 0) { return; }
|
||||
IEnumerable<Contact> levelContacts = GetLevelContacts(Body);
|
||||
int levelContactCount = levelContacts.Count();
|
||||
if (levelContactCount == 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
|
||||
@@ -852,8 +907,7 @@ namespace Barotrauma
|
||||
levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2<Vector2> 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);
|
||||
VoronoiCell cell = levelContact.FixtureB.UserData as VoronoiCell ?? levelContact.FixtureA.UserData as VoronoiCell;
|
||||
|
||||
var cellDiff = ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center;
|
||||
if (Vector2.Dot(contactNormal, cellDiff) < 0)
|
||||
@@ -864,9 +918,9 @@ namespace Barotrauma
|
||||
avgContactNormal += contactNormal;
|
||||
|
||||
//apply impacts at the positions where this sub is touching the level
|
||||
ApplyImpact((Vector2.Dot(impact.Velocity, contactNormal) / 2.0f) * massRatio / levelContacts.Count, contactNormal, impact.ImpactPos);
|
||||
ApplyImpact((Vector2.Dot(impact.Velocity, contactNormal) / 2.0f) * massRatio / levelContactCount, contactNormal, impact.ImpactPos);
|
||||
}
|
||||
avgContactNormal /= levelContacts.Count;
|
||||
avgContactNormal /= levelContactCount;
|
||||
|
||||
//apply an impact to the other sub
|
||||
float contactDot = Vector2.Dot(otherSub.PhysicsBody.LinearVelocity, -avgContactNormal);
|
||||
|
||||
Reference in New Issue
Block a user