Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into unstable

This commit is contained in:
EvilFactory
2022-09-29 12:13:55 -03:00
602 changed files with 19759 additions and 16312 deletions
@@ -84,7 +84,6 @@ namespace Barotrauma
}
}
private bool disposed = false;
public override Sprite Sprite => null;
@@ -102,9 +101,8 @@ namespace Barotrauma
public override void Dispose()
{
if (disposed) { return; }
disposed = true;
Prefabs.Remove(this);
throw new InvalidOperationException(
$"{nameof(CoreEntityPrefab)}.{nameof(Dispose)} should never be called");
}
}
}
@@ -33,8 +33,6 @@ namespace Barotrauma
private readonly float? flashRange;
private readonly string decal;
private readonly float decalSize;
// used to apply friendly afflictions in an area without effects displaying
private readonly bool abilityExplosion;
private readonly bool applyToSelf;
private readonly float itemRepairStrength;
@@ -70,6 +68,7 @@ namespace Barotrauma
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);
shockwave = element.GetAttributeBool("shockwave", showEffects);
@@ -131,8 +130,15 @@ namespace Barotrauma
float displayRange = Attack.Range;
if (damageSource is Item sourceItem)
{
displayRange *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius);
Attack.DamageMultiplier *= 1.0f + sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage);
var launcher = sourceItem.GetComponent<Projectile>()?.Launcher;
displayRange *=
1.0f
+ sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius)
+ (launcher?.GetQualityModifier(Quality.StatType.ExplosionRadius) ?? 0);
Attack.DamageMultiplier *=
1.0f
+ sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage)
+ (launcher?.GetQualityModifier(Quality.StatType.ExplosionDamage) ?? 0);
Attack.SourceItem ??= sourceItem;
}
@@ -203,7 +209,7 @@ namespace Barotrauma
}
}
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f) && !abilityExplosion)
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && Attack.Afflictions.None())
{
return;
}
@@ -293,12 +299,13 @@ namespace Barotrauma
Dictionary<Limb, float> distFactors = new Dictionary<Limb, float>();
Dictionary<Limb, float> damages = new Dictionary<Limb, float>();
List<Affliction> modifiedAfflictions = new List<Affliction>();
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.IsSevered || limb.IgnoreCollisions || !limb.body.Enabled) { continue; }
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 = limb.body.GetMaxExtent();
@@ -313,17 +320,27 @@ namespace Barotrauma
{
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
}
distFactors.Add(limb, distFactor);
if (distFactor > 0)
{
distFactors.Add(limb, distFactor);
}
}
foreach (Limb limb in distFactors.Keys)
{
if (!distFactors.TryGetValue(limb, out float distFactor)) { continue; }
modifiedAfflictions.Clear();
foreach (Affliction affliction in attack.Afflictions.Keys)
{
//previously the damage would be divided by the number of limbs (the intention was to prevent characters with more limbs taking more damage from explosions)
//that didn't work well on large characters like molochs and endworms: the explosions tend to only damage one or two of their limbs, and since the characters
//have lots of limbs, they tended to only take a fraction of the damage they should
//now we just divide by 10, which keeps the damage to normal-sized characters roughly the same as before and fixes the large characters
modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / 10));
// Shouldn't go above 15, or the damage can be unexpectedly low -> doesn't break armor
// Effectively this makes large explosions more effective against large creatures (because more limbs are affected), but I don't think that's necessarily a bad thing.
float limbCountFactor = Math.Min(distFactors.Count, 15);
float dmgMultiplier = distFactor;
if (affliction.DivideByLimbCount)
{
dmgMultiplier /= limbCountFactor;
}
modifiedAfflictions.Add(affliction.CreateMultiplied(dmgMultiplier, affliction.Probability));
}
c.LastDamageSource = damageSource;
if (attacker == null)
@@ -368,7 +385,7 @@ namespace Barotrauma
Vector2 limbDiff = Vector2.Normalize(limb.WorldPosition - worldPosition);
if (!MathUtils.IsValid(limbDiff)) { limbDiff = Rand.Vector(1.0f); }
Vector2 impulse = limbDiff * distFactor * force;
Vector2 impulsePoint = limb.SimPosition - limbDiff * limbRadius;
Vector2 impulsePoint = limb.SimPosition - limbDiff * limb.body.GetMaxExtent();
limb.body.ApplyLinearImpulse(impulse, impulsePoint, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.2f);
}
}
@@ -26,6 +26,12 @@ namespace Barotrauma
private set;
}
/// <summary>
/// "Diagonal" gaps are used on sloped walls to allow characters to pass through them either horizontally or vertically.
/// Water still flows through them only horizontally or vertically
/// </summary>
public bool IsDiagonal { get; }
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
private float open;
@@ -136,12 +142,13 @@ namespace Barotrauma
: this(rect, rect.Width < rect.Height, submarine)
{ }
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, ushort id = Entity.NullEntityID)
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, bool isDiagonal = false, ushort id = Entity.NullEntityID)
: base(CoreEntityPrefab.GapPrefab, submarine, id)
{
this.rect = rect;
flowForce = Vector2.Zero;
IsHorizontal = isHorizontal;
IsDiagonal = isDiagonal;
open = 1.0f;
FindHulls();
@@ -671,15 +678,15 @@ namespace Barotrauma
{
foreach (Gap gap in gaps)
{
if (gap.Open == 0.0f || gap.IsRoomToRoom) continue;
if (gap.Open == 0.0f || gap.IsRoomToRoom) { continue; }
if (gap.ConnectedWall != null)
{
int sectionIndex = gap.ConnectedWall.FindSectionIndex(gap.Position);
if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) continue;
if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) { continue; }
}
if (gap.IsHorizontal)
if (gap.IsHorizontal || gap.IsDiagonal)
{
if (worldPos.Y < gap.WorldRect.Y && worldPos.Y > gap.WorldRect.Y - gap.WorldRect.Height &&
Math.Abs(gap.WorldRect.Center.X - worldPos.X) < allowedOrthogonalDist)
@@ -687,7 +694,7 @@ namespace Barotrauma
return gap;
}
}
else
if (!gap.IsHorizontal || gap.IsDiagonal)
{
if (worldPos.X > gap.WorldRect.X && worldPos.X < gap.WorldRect.Right &&
Math.Abs(gap.WorldRect.Y - gap.WorldRect.Height / 2 - worldPos.Y) < allowedOrthogonalDist)
@@ -759,7 +766,7 @@ namespace Barotrauma
isHorizontal = horizontalAttribute.Value.ToString() == "true";
}
Gap g = new Gap(rect, isHorizontal, submarine, idRemap.GetOffsetId(element))
Gap g = new Gap(rect, isHorizontal, submarine, id: idRemap.GetOffsetId(element))
{
linkedToID = new List<ushort>(),
};
@@ -759,7 +759,7 @@ namespace Barotrauma
for (int i = start; i < end; i++)
{
msg.WriteRangedSingle(BackgroundSections[i].ColorStrength, 0.0f, 1.0f, 8);
msg.Write(BackgroundSections[i].Color.PackedValue);
msg.WriteUInt32(BackgroundSections[i].Color.PackedValue);
}
}
#endregion
@@ -994,7 +994,11 @@ namespace Barotrauma
foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0))
{
var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position) / 1000, 1f);
item.body.ApplyForce((gap.LerpedFlowForce / distance) * deltaTime);
Vector2 force = (gap.LerpedFlowForce / distance) * deltaTime;
if (force.LengthSquared() > 0.01f)
{
item.body.ApplyForce(force);
}
}
}
@@ -1545,7 +1549,7 @@ namespace Barotrauma
var hull = new Hull(rect, submarine, idRemap.GetOffsetId(element))
{
WaterVolume = element.GetAttributeFloat("pressure", 0.0f)
WaterVolume = element.GetAttributeFloat("water", 0.0f)
};
hull.linkedToID = new List<ushort>();
@@ -157,7 +157,7 @@ namespace Barotrauma
public void Delete()
{
Dispose();
Prefabs.Remove(this);
try
{
if (ContentPackage is { Files: { Length: 1 } }
@@ -12,7 +12,9 @@ namespace Barotrauma
public readonly bool IsEndBiome;
public readonly float MinDifficulty;
public readonly float MaxDifficulty;
private readonly float maxDifficulty;
public float ActualMaxDifficulty => maxDifficulty;
public float AdjustedMaxDifficulty => maxDifficulty - 0.1f;
public readonly ImmutableHashSet<int> AllowedZones;
@@ -31,7 +33,7 @@ namespace Barotrauma
IsEndBiome = element.GetAttributeBool("endbiome", false);
AllowedZones = element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }).ToImmutableHashSet();
MinDifficulty = element.GetAttributeFloat("MinDifficulty", 0);
MaxDifficulty = element.GetAttributeFloat("MaxDifficulty", 100);
maxDifficulty = element.GetAttributeFloat("MaxDifficulty", 100);
}
public static Identifier ParseIdentifier(ContentXElement element)
@@ -79,7 +79,7 @@ namespace Barotrauma
set { maxBranchCount = Math.Max(value, minBranchCount); }
}
[Serialize(50, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000)]
[Serialize(50, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 10000)]
public int LevelObjectAmount
{
get;
@@ -238,8 +238,8 @@ namespace Barotrauma
//find the edge at the opposite side of the adjacent cell
foreach (GraphEdge otherEdge in adjacentEmptyCell.Edges)
{
if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) < 0 &&
otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType == CellType.Solid)
if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) > 0 &&
otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType != CellType.Solid)
{
adjacentEdge = otherEdge;
break;
@@ -8,6 +8,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Voronoi2;
@@ -24,6 +25,22 @@ namespace Barotrauma
//all entities are disabled after they reach this depth
public const int MaxEntityDepth = -1000000;
public const float ShaftHeight = 1000.0f;
/// <summary>
/// How far outside the boundaries of the level the water current that pushes subs towards the level starts
/// </summary>
public const float OutsideBoundsCurrentMargin = 30000.0f;
/// <summary>
/// How far outside the boundaries of the level the strength of the current starts to increase exponentially
/// </summary>
public const float OutsideBoundsCurrentMarginExponential = 150000.0f;
/// <summary>
/// How far outside the boundaries of the level the current stops submarines entirely
/// </summary>
public const float OutsideBoundsCurrentHardLimit = 200000.0f;
/// <summary>
/// The level generator won't try to adjust the width of the main path above this limit.
/// </summary>
@@ -1955,7 +1972,7 @@ namespace Barotrauma
List<Tunnel> caveBranches = new List<Tunnel>();
var tunnel = new Tunnel(TunnelType.Cave, SegmentsToNodes(caveSegments), 100, parentTunnel);
var tunnel = new Tunnel(TunnelType.Cave, SegmentsToNodes(caveSegments), 150, parentTunnel);
Tunnels.Add(tunnel);
caveBranches.Add(tunnel);
@@ -1972,7 +1989,7 @@ namespace Barotrauma
bounds: caveArea);
if (!branchSegments.Any()) { continue; }
var branch = new Tunnel(TunnelType.Cave, SegmentsToNodes(branchSegments), 0, parentBranch);
var branch = new Tunnel(TunnelType.Cave, SegmentsToNodes(branchSegments), 150, parentBranch);
Tunnels.Add(branch);
caveBranches.Add(branch);
}
@@ -2437,16 +2454,27 @@ namespace Barotrauma
public List<ClusterLocation> ClusterLocations { get; }
public TunnelType TunnelType { get; }
public PathPoint(string id, Vector2 position, bool shouldContainResources, TunnelType tunnelType)
private PathPoint(string id, Vector2 position, bool shouldContainResources, TunnelType tunnelType, List<Identifier> resourceTags, List<Identifier> resourceIds, List<ClusterLocation> clusterLocations)
{
Id = id;
Id = id;
Position = position;
ShouldContainResources = shouldContainResources;
ResourceTags = new List<Identifier>();
ResourceIds = new List<Identifier>();
ClusterLocations = new List<ClusterLocation>();
ResourceTags = resourceTags;
ResourceIds = resourceIds;
ClusterLocations = clusterLocations;
TunnelType = tunnelType;
}
public PathPoint(string id, Vector2 position, bool shouldContainResources, TunnelType tunnelType)
: this(id, position, shouldContainResources, tunnelType, new List<Identifier>(), new List<Identifier>(), new List<ClusterLocation>())
{
}
public PathPoint WithResources(bool containsResources)
{
return new PathPoint(Id, Position, containsResources, TunnelType, ResourceTags, ResourceIds, ClusterLocations);
}
}
public List<ClusterLocation> AbyssResources { get; } = new List<ClusterLocation>();
@@ -2485,22 +2513,26 @@ namespace Barotrauma
// Such as the exploding crystals in The Great Sea
private void GenerateItems()
{
Identifier levelName = GenerationParams.Identifier;
float minCommonness = float.MaxValue, maxCommonness = float.MinValue;
List<(ItemPrefab itemPrefab, float commonness)> levelResources = new List<(ItemPrefab itemPrefab, float commonness)>();
var levelResources = new List<(ItemPrefab itemPrefab, ItemPrefab.CommonnessInfo commonnessInfo)>();
var fixedResources = new List<(ItemPrefab itemPrefab, ItemPrefab.FixedQuantityResourceInfo resourceInfo)>();
Vector2 commonnessRange = new Vector2(float.MaxValue, float.MinValue), caveCommonnessRange = new Vector2(float.MaxValue, float.MinValue);
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier))
{
if (itemPrefab.LevelCommonness.TryGetValue(levelName, out float commonness) ||
itemPrefab.LevelCommonness.TryGetValue(LevelData.Biome.Identifier, out commonness) ||
itemPrefab.LevelCommonness.TryGetValue(Identifier.Empty, out commonness))
if (itemPrefab.GetCommonnessInfo(this) is { CanAppear: true } commonnessInfo)
{
if (commonness <= 0.0f) { continue; }
if (commonness < minCommonness) { minCommonness = commonness; }
if (commonness > maxCommonness) { maxCommonness = commonness; }
levelResources.Add((itemPrefab, commonness));
if (commonnessInfo.Commonness > 0.0)
{
if (commonnessInfo.Commonness < commonnessRange.X) { commonnessRange.X = commonnessInfo.Commonness; }
if (commonnessInfo.Commonness > commonnessRange.Y) { commonnessRange.Y = commonnessInfo.Commonness; }
}
if (commonnessInfo.CaveCommonness > 0.0)
{
if (commonnessInfo.CaveCommonness < caveCommonnessRange.X) { caveCommonnessRange.X = commonnessInfo.CaveCommonness; }
if (commonnessInfo.CaveCommonness > caveCommonnessRange.Y) { caveCommonnessRange.Y = commonnessInfo.CaveCommonness; }
}
levelResources.Add((itemPrefab, commonnessInfo));
}
else if (itemPrefab.LevelQuantity.TryGetValue(levelName, out var fixedQuantityResourceInfo) ||
else if (itemPrefab.LevelQuantity.TryGetValue(GenerationParams.Identifier, out var fixedQuantityResourceInfo) ||
itemPrefab.LevelQuantity.TryGetValue(Identifier.Empty, out fixedQuantityResourceInfo))
{
fixedResources.Add((itemPrefab, fixedQuantityResourceInfo));
@@ -2533,35 +2565,41 @@ namespace Barotrauma
}
}
//place some of the least common resources in the abyss
// Abyss Resources
AbyssResources.Clear();
int abyssClusterCount = (int)MathHelper.Lerp(GenerationParams.AbyssResourceClustersMin, GenerationParams.AbyssResourceClustersMax, Difficulty / 100.0f);
for (int i = 0; i < abyssClusterCount; i++)
var abyssResourcePrefabs = levelResources.Where(r => r.commonnessInfo.AbyssCommonness > 0.0f);
if (abyssResourcePrefabs.Any())
{
//use inverse commonness to select the abyss resources (the rarest ones are the most common in the abyss)
var selectedPrefab = ToolBox.SelectWeightedRandom(
levelResources.Select(it => it.itemPrefab).ToList(),
levelResources.Select(it => it.commonness <= 0.0f ? 0.0f : 1.0f / it.commonness).ToList(),
Rand.RandSync.ServerAndClient);
var location = allValidLocations.GetRandom(l =>
int abyssClusterCount = (int)MathHelper.Lerp(GenerationParams.AbyssResourceClustersMin, GenerationParams.AbyssResourceClustersMax, MathUtils.InverseLerp(LevelData.Biome.MinDifficulty, LevelData.Biome.AdjustedMaxDifficulty, Difficulty));
for (int i = 0; i < abyssClusterCount; i++)
{
if (l.Cell == null || l.Edge == null) { return false; }
if (l.EdgeCenter.Y > AbyssArea.Bottom) { return false; }
l.InitializeResources();
return l.Resources.Count <= GetMaxResourcesOnEdge(selectedPrefab, l, out _);
}, randSync: Rand.RandSync.ServerAndClient);
var selectedPrefab = ToolBox.SelectWeightedRandom(
abyssResourcePrefabs.Select(r => r.itemPrefab).ToList(),
abyssResourcePrefabs.Select(r => r.commonnessInfo.AbyssCommonness).ToList(),
Rand.RandSync.ServerAndClient);
var location = allValidLocations.GetRandom(l =>
{
if (l.Cell == null || l.Edge == null) { return false; }
if (l.EdgeCenter.Y > AbyssArea.Bottom) { return false; }
l.InitializeResources();
return l.Resources.Count <= GetMaxResourcesOnEdge(selectedPrefab, l, out _);
}, randSync: Rand.RandSync.ServerAndClient);
if (location.Cell == null || location.Edge == null) { break; }
int clusterSize = Rand.Range(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y + 1, Rand.RandSync.ServerAndClient);
PlaceResources(selectedPrefab, clusterSize, location, out var placedResources, maxResourceOverlap: 0);
var abyssClusterLocation = new ClusterLocation(location.Cell, location.Edge, initializeResourceList: true);
abyssClusterLocation.Resources.AddRange(placedResources);
AbyssResources.Add(abyssClusterLocation);
var locationIndex = allValidLocations.FindIndex(l => l.Equals(location));
allValidLocations.RemoveAt(locationIndex);
}
}
if (location.Cell == null || location.Edge == null) { break; }
int clusterSize = Rand.Range(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y + 1, Rand.RandSync.ServerAndClient);
PlaceResources(selectedPrefab, clusterSize, location, out var abyssResources);
var abyssClusterLocation = new ClusterLocation(location.Cell, location.Edge, initializeResourceList: true);
abyssClusterLocation.Resources.AddRange(abyssResources);
AbyssResources.Add(abyssClusterLocation);
var locationIndex = allValidLocations.FindIndex(l => l.Equals(location));
allValidLocations.RemoveAt(locationIndex);
}
PathPoints.Clear();
nextPathPointId = 0;
@@ -2618,17 +2656,25 @@ namespace Barotrauma
int itemCount = 0;
Identifier[] exclusiveResourceTags = new Identifier[2] { "ore".ToIdentifier(), "plant".ToIdentifier() };
var disabledPathPoints = new List<string>();
// Create first cluster for each spawn point
foreach (var pathPoint in PathPoints.Where(p => p.ShouldContainResources))
foreach (var pathPoint in PathPoints)
{
if (itemCount >= GenerationParams.ItemCount) { break; }
if (!pathPoint.ShouldContainResources) { continue; }
GenerateFirstCluster(pathPoint);
if (pathPoint.ClusterLocations.Count > 0) { continue; }
disabledPathPoints.Add(pathPoint.Id);
}
// Don't try to spawn more resource clusters for points for which the initial cluster could not be spawned
foreach (string pathPointId in disabledPathPoints)
{
if (PathPoints.FirstOrNull(p => p.Id == pathPointId) is PathPoint pathPoint)
{
PathPoints.RemoveAll(p => p.Id == pathPointId);
PathPoints.Add(pathPoint.WithResources(false));
}
}
// Don't try to spawn more resource clusters for points
// for which the initial cluster could not be spawned
PathPoints.Where(p => p.ShouldContainResources && p.ClusterLocations.Count == 0)
.ForEach(p => p.ShouldContainResources = false);
var excludedPathPointIds = new List<string>();
while (itemCount < GenerationParams.ItemCount)
@@ -2647,35 +2693,16 @@ namespace Barotrauma
GenerateAdditionalCluster(pathPoint);
}
// If none of the point set to contain resources can take more resources,
// but we still haven't reached the item count set in the generation parameters...
while (itemCount < GenerationParams.ItemCount)
{
// We need to start filling some of the path points previously set to not contain resources
Func<PathPoint, bool> availablePathPoints = p => !excludedPathPointIds.Contains(p.Id) && p.ClusterLocations.None();
if (PathPoints.None(availablePathPoints)) { break; }
var pathPoint = PathPoints.GetRandom(availablePathPoints, randSync: Rand.RandSync.ServerAndClient);
if (!GenerateFirstCluster(pathPoint))
{
excludedPathPointIds.Add(pathPoint.Id);
continue;
}
while (pathPoint.NextClusterProbability > 0)
{
if (!GenerateAdditionalCluster(pathPoint)) { break; }
}
pathPoint.ShouldContainResources = pathPoint.ClusterLocations.Any();
}
#if DEBUG
DebugConsole.NewMessage("Level resources spawned: " + itemCount + "\n" +
" Spawn points containing resources: " + PathPoints.Where(p => p.ClusterLocations.Any()).Count() + "/" + PathPoints.Count + "\n" +
" Total value: " + PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0))) + " mk");
int spawnPointsContainingResources = PathPoints.Where(p => p.ClusterLocations.Any()).Count();
string percentage = string.Format(CultureInfo.InvariantCulture, "{0:P2}", (float)spawnPointsContainingResources / PathPoints.Count);
DebugConsole.NewMessage($"Level resources spawned: {itemCount}\n" +
$" Spawn points containing resources: {spawnPointsContainingResources} ({percentage})\n" +
$" Total value: {PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0)))} mk");
if (AbyssResources.Count > 0)
{
DebugConsole.NewMessage("Abyss resources spawned: " + AbyssResources.Sum(a => a.Resources.Count) + "\n" +
" Total value: " + AbyssResources.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0)) + " mk");
DebugConsole.NewMessage($"Abyss resources spawned: {AbyssResources.Sum(a => a.Resources.Count)}\n" +
$" Total value: {AbyssResources.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0))} mk");
}
#endif
@@ -2842,7 +2869,7 @@ namespace Barotrauma
{
selectedPrefab = ToolBox.SelectWeightedRandom(
levelResources.Select(it => it.itemPrefab).ToList(),
levelResources.Select(it => it.commonness).ToList(),
levelResources.Select(it => it.commonnessInfo.GetCommonness(pathPoint.TunnelType)).ToList(),
Rand.RandSync.ServerAndClient);
selectedPrefab.Tags.ForEach(t =>
{
@@ -2854,20 +2881,21 @@ namespace Barotrauma
}
else
{
var filteredResources = levelResources.Where(it =>
!pathPoint.ResourceIds.Contains(it.itemPrefab.Identifier) &&
pathPoint.ResourceTags.Any() && it.itemPrefab.Tags.Any(t => pathPoint.ResourceTags.Contains(t)));
selectedPrefab = ToolBox.SelectWeightedRandom(
var filteredResources = pathPoint.ResourceTags.None() ? levelResources :
levelResources.Where(it => it.itemPrefab.Tags.Any(t => pathPoint.ResourceTags.Contains(t)));
selectedPrefab = ToolBox.SelectWeightedRandom(
filteredResources.Select(it => it.itemPrefab).ToList(),
filteredResources.Select(it => it.commonness).ToList(),
filteredResources.Select(it => it.commonnessInfo.GetCommonness(pathPoint.TunnelType)).ToList(),
Rand.RandSync.ServerAndClient);
}
if (selectedPrefab == null) { return false; }
// Create resources for the cluster
var commonness = levelResources.First(r => r.itemPrefab == selectedPrefab).commonness;
var lerpAmount = MathUtils.InverseLerp(minCommonness, maxCommonness, commonness);
float commonness = levelResources.First(r => r.itemPrefab == selectedPrefab).commonnessInfo.GetCommonness(pathPoint.TunnelType);
float lerpAmount = pathPoint.TunnelType != TunnelType.Cave ?
MathUtils.InverseLerp(commonnessRange.X, commonnessRange.Y, commonness) :
MathUtils.InverseLerp(caveCommonnessRange.X, caveCommonnessRange.Y, commonness);
var maxClusterSize = (int)MathHelper.Lerp(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y, lerpAmount);
var maxFitOnEdge = GetMaxResourcesOnEdge(selectedPrefab, location, out var edgeLength);
maxClusterSize = Math.Min(maxClusterSize, maxFitOnEdge);
@@ -2898,58 +2926,92 @@ namespace Barotrauma
edgeLength = 0.0f;
if (location.Cell == null || location.Edge == null) { return 0; }
edgeLength = Vector2.Distance(location.Edge.Point1, location.Edge.Point2);
if (resourcePrefab == null) { return 0; }
return (int)Math.Floor(edgeLength / ((1.0f - maxResourceOverlap) * resourcePrefab.Size.X));
}
}
/// <param name="rotation">Used by clients to set the rotation for the resources</param>
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, out float rotation)
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, out float rotation, IEnumerable<Cave> targetCaves = null)
{
var allValidLocations = GetAllValidClusterLocations();
var placedResources = new List<Item>();
rotation = 0.0f;
if (allValidLocations.None()) { return placedResources; } // TODO: WHAT?!
// Make sure not to pick a spot that already has other level resources
for (int i = allValidLocations.Count - 1; i >= 0; i--)
{
var location = allValidLocations[i];
var locationHasResources = PathPoints.Any(p =>
p.ClusterLocations.Any(c =>
c.Equals(location) &&
c.Resources.Any(r => r != null && !r.Removed &&
(!(r.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))));
if (locationHasResources)
if (HasResources(allValidLocations[i]))
{
allValidLocations.RemoveAt(i);
}
bool HasResources(ClusterLocation clusterLocation)
{
foreach (var p in PathPoints)
{
foreach (var c in p.ClusterLocations)
{
if (!c.Equals(clusterLocation)) { continue; }
foreach (var r in c.Resources)
{
if (r == null) { continue; }
if (r.Removed) { continue; }
if (!(r.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)) { return true; }
}
}
}
return false;
}
}
var positionType = PositionType.MainPath;
if (PositionsOfInterest.Any(p => p.PositionType == PositionType.Cave))
if (PositionsOfInterest.None(p => p.PositionType == positionType))
{
positionType = PositionType.Cave;
if (allValidLocations.Any(l => l.Edge.NextToCave))
foreach (var validType in MineralMission.ValidPositionTypes)
{
allValidLocations.RemoveAll(l => !l.Edge.NextToCave);
if (validType != positionType && PositionsOfInterest.Any(p => p.PositionType == validType))
{
positionType = validType;
break;
}
}
}
else if (PositionsOfInterest.Any(p => p.PositionType == PositionType.SidePath))
try
{
positionType = PositionType.SidePath;
if (allValidLocations.Any(l => l.Edge.NextToSidePath))
RemoveInvalidLocations(positionType switch
{
allValidLocations.RemoveAll(l => !l.Edge.NextToSidePath);
}
PositionType.MainPath => IsOnMainPath,
PositionType.SidePath => IsOnSidePath,
PositionType.Cave => IsInCave,
PositionType.AbyssCave => IsInAbyssCave,
_ => throw new NotImplementedException(),
});
}
catch (NotImplementedException)
{
DebugConsole.ThrowError($"Unexpected PositionType (\"{positionType}\") for mineral mission resources: mineral spawning might not work as expected.");
}
if (targetCaves != null && targetCaves.Any())
{
// If resources are placed inside a cave, make sure all of them are placed inside the same one
allValidLocations.RemoveAll(l => targetCaves.None(c => c.Area.Contains(l.EdgeCenter)));
}
var poi = PositionsOfInterest.GetRandom(p => p.PositionType == positionType, randSync: Rand.RandSync.ServerAndClient);
var poiPos = poi.Position.ToVector2();
Vector2 poiPos = poi.Position.ToVector2();
allValidLocations.Sort((x, y) => Vector2.DistanceSquared(poiPos, x.EdgeCenter)
.CompareTo(Vector2.DistanceSquared(poiPos, y.EdgeCenter)));
var maxResourceOverlap = 0.4f;
float maxResourceOverlap = 0.4f;
var selectedLocation = allValidLocations.FirstOrDefault(l =>
Vector2.Distance(l.Edge.Point1, l.Edge.Point2) is float edgeLength &&
!l.Edge.OutsideLevel &&
requiredAmount <= (int)Math.Floor(edgeLength / ((1.0f - maxResourceOverlap) * prefab.Size.X)));
if (selectedLocation.Edge == null)
{
//couldn't find a long enough edge, find the largest one
@@ -2968,9 +3030,18 @@ namespace Barotrauma
throw new Exception("Failed to find a suitable level wall edge to place level resources on.");
}
PlaceResources(prefab, requiredAmount, selectedLocation, out placedResources);
var edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell);
Vector2 edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell);
rotation = MathHelper.ToDegrees(-MathUtils.VectorToAngle(edgeNormal) + MathHelper.PiOver2);
return placedResources;
static bool IsOnMainPath(ClusterLocation location) => location.Edge.NextToMainPath;
static bool IsOnSidePath(ClusterLocation location) => location.Edge.NextToSidePath;
static bool IsInCave(ClusterLocation location) => location.Edge.NextToCave;
bool IsInAbyssCave(ClusterLocation location) => location.EdgeCenter.Y < AbyssStart;
void RemoveInvalidLocations(Predicate<ClusterLocation> match)
{
allValidLocations.RemoveAll(l => !match(l));
}
}
private List<ClusterLocation> GetAllValidClusterLocations()
@@ -3041,15 +3112,19 @@ namespace Barotrauma
{
edgeLength ??= Vector2.Distance(location.Edge.Point1, location.Edge.Point2);
Vector2 edgeDir = (location.Edge.Point2 - location.Edge.Point1) / edgeLength.Value;
if (!MathUtils.IsValid(edgeDir))
{
edgeDir = Vector2.Zero;
}
var minResourceOverlap = -((edgeLength.Value - (resourceCount * resourcePrefab.Size.X)) / (resourceCount * resourcePrefab.Size.X));
minResourceOverlap = Math.Max(minResourceOverlap, 0.0f);
minResourceOverlap = Math.Clamp(minResourceOverlap, 0, maxResourceOverlap);
var lerpAmounts = new float[resourceCount];
lerpAmounts[0] = 0.0f;
var lerpAmount = 0.0f;
for (int i = 1; i < resourceCount; i++)
{
var overlap = Rand.Range(minResourceOverlap, maxResourceOverlap, sync: Rand.RandSync.ServerAndClient);
lerpAmount += ((1.0f - overlap) * resourcePrefab.Size.X) / edgeLength.Value;
lerpAmount += (1.0f - overlap) * resourcePrefab.Size.X / edgeLength.Value;
lerpAmounts[i] = Math.Clamp(lerpAmount, 0.0f, 1.0f);
}
var startOffset = Rand.Range(0.0f, 1.0f - lerpAmount, sync: Rand.RandSync.ServerAndClient);
@@ -3128,14 +3203,14 @@ namespace Barotrauma
return success;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null)
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
{
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter);
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning);
position = pos.ToVector2();
return success;
}
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null)
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func<InterestingPosition, bool> filter = null, bool suppressWarning = false)
{
if (!PositionsOfInterest.Any())
{
@@ -3155,11 +3230,14 @@ namespace Barotrauma
}
if (!suitablePositions.Any())
{
string errorMsg = "Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
if (!suppressWarning)
{
string errorMsg = "Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
DebugConsole.ThrowError(errorMsg);
#endif
}
position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))].Position;
return false;
}
@@ -3983,17 +4061,21 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
}
float outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
//don't try to compensate if the port is very far from the outpost's center of mass
if (Math.Abs(outpostDockingPortOffset) > 5000.0f)
float? outpostDockingPortOffset = null;
if (outpostPort != null)
{
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
//don't try to compensate if the port is very far from the outpost's center of mass
if (Math.Abs(outpostDockingPortOffset.Value) > 5000.0f)
{
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset.Value, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
}
}
Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset, verticalMoveDir: 1);
Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, outpostDockingPortOffset != null ? subDockingPortOffset - outpostDockingPortOffset.Value : 0.0f, verticalMoveDir: 1);
if (Type == LevelData.LevelType.Outpost)
{
spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2);
@@ -57,6 +57,8 @@ namespace Barotrauma
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
public bool EventsExhausted { get; set; }
public float CrushDepth
{
get
@@ -130,6 +132,8 @@ namespace Barotrauma
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { });
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)));
EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
}
@@ -238,7 +242,8 @@ namespace Barotrauma
new XAttribute("difficulty", Difficulty.ToString("G", CultureInfo.InvariantCulture)),
new XAttribute("size", XMLExtensions.PointToString(Size)),
new XAttribute("generationparams", GenerationParams.Identifier),
new XAttribute("initialdepth", InitialDepth));
new XAttribute("initialdepth", InitialDepth),
new XAttribute(nameof(EventsExhausted).ToLower(), EventsExhausted));
if (HasBeaconStation)
{
@@ -323,7 +323,7 @@ namespace Barotrauma
set { caveCount = MathHelper.Clamp(value, 0, 100); }
}
[Serialize(100, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 10000)]
[Serialize(100, IsPropertySaveable.Yes, description: "The maximum number of level resources in the level."), Editable(MinValueInt = 0, MaxValueInt = 10000)]
public int ItemCount
{
get;
@@ -344,7 +344,7 @@ namespace Barotrauma
set;
}
[Serialize("2,8", IsPropertySaveable.Yes, description: "The minimum and maximum amount of resources in a single cluster. " +
[Serialize("3,6", IsPropertySaveable.Yes, description: "The minimum and maximum amount of resources in a single cluster. " +
"In addition to this, resource commonness affects the cluster size. Less common resources spawn in smaller clusters."), Editable(1, 20)]
public Point ResourceClusterSizeRange
{
@@ -11,15 +11,7 @@ namespace Barotrauma
{
partial class LinkedSubmarinePrefab : MapEntityPrefab
{
//public static readonly PrefabCollection<LinkedSubmarinePrefab> Prefabs = new PrefabCollection<LinkedSubmarinePrefab>();
private bool disposed = false;
public override void Dispose()
{
if (disposed) { return; }
disposed = true;
//Prefabs.Remove(this);
}
public override void Dispose() { }
public readonly SubmarineInfo subInfo;
@@ -289,6 +281,10 @@ namespace Barotrauma
IdRemap parentRemap = new IdRemap(Submarine.Info.SubmarineElement, Submarine.IdOffset);
sub = Submarine.Load(info, false, parentRemap);
sub.Info.SubmarineClass = Submarine.Info.SubmarineClass;
if (Submarine.Info.IsOutpost && Submarine.TeamID == CharacterTeamType.FriendlyNPC)
{
sub.TeamID = CharacterTeamType.FriendlyNPC;
}
IdRemap childRemap = new IdRemap(saveElement, sub.IdOffset);
@@ -357,7 +353,9 @@ namespace Barotrauma
float closestDistance = 0.0f;
foreach (DockingPort port in DockingPort.List)
{
if (port.Item.Submarine != sub || port.IsHorizontal != linkedPort.IsHorizontal) { continue; }
if (port.Item.Submarine != sub) { continue; }
if (port.IsHorizontal != linkedPort.IsHorizontal) { continue; }
if (port.ForceDockingDirection != DockingPort.DirectionType.None && port.ForceDockingDirection == linkedPort.ForceDockingDirection) { continue; }
float dist = Vector2.Distance(port.Item.WorldPosition, linkedPort.Item.WorldPosition);
if (myPort == null || dist < closestDistance)
{
@@ -453,22 +451,22 @@ namespace Barotrauma
saveElement.SetAttributeValue("pos", XMLExtensions.Vector2ToString(Position - Submarine.HiddenSubPosition));
if (linkedTo.Any() || linkedToID.Any())
{
var linkedPort =
linkedTo.FirstOrDefault(lt => (lt is Item item) && item.GetComponent<DockingPort>() != null) ??
FindEntityByID(linkedToID.First()) as MapEntity;
if (linkedPort != null)
{
saveElement.SetAttributeValue("linkedto", linkedPort.ID);
}
}
}
else
{
saveElement = new XElement("LinkedSubmarine");
sub.SaveToXElement(saveElement);
}
if (linkedTo.Any() || linkedToID.Any())
{
var linkedPort =
linkedTo.FirstOrDefault(lt => (lt is Item item) && item.GetComponent<DockingPort>() != null) ??
FindEntityByID(linkedToID.First()) as MapEntity;
if (linkedPort != null)
{
saveElement.SetAttributeValue("linkedto", linkedPort.ID);
}
}
saveElement.SetAttributeValue("originallinkedto", originalLinkedPort != null ? originalLinkedPort.Item.ID : originalLinkedToID);
saveElement.SetAttributeValue("originalmyport", originalMyPortID);
@@ -700,7 +700,7 @@ namespace Barotrauma
#endif
}
public MissionPrefab UnlockMissionByIdentifier(Identifier identifier)
public Mission UnlockMissionByIdentifier(Identifier identifier)
{
if (AvailableMissions.Any(m => m.Prefab.Identifier == identifier)) { return null; }
@@ -721,17 +721,17 @@ namespace Barotrauma
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
#endif
return missionPrefab;
return mission;
}
return null;
}
public MissionPrefab UnlockMissionByTag(Identifier tag)
public Mission UnlockMissionByTag(Identifier tag)
{
var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Any(t => t == tag));
if (!matchingMissions.Any())
{
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions not found.");
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.");
}
else
{
@@ -754,7 +754,7 @@ namespace Barotrauma
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
#endif
return missionPrefab;
return mission;
}
else
{
@@ -1253,7 +1253,7 @@ namespace Barotrauma
{
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
if (!characters.Any()) { return 0; }
return characters.Max(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
}
public void Discover(bool checkTalents = true)
@@ -149,7 +149,7 @@ namespace Barotrauma
Biome.Prefabs.FirstOrDefault(b => b.Identifier == biomeId) ??
Biome.Prefabs.FirstOrDefault(b => !b.OldIdentifier.IsEmpty && b.OldIdentifier == biomeId) ??
Biome.Prefabs.First();
connection.Difficulty = MathHelper.Clamp(connection.Difficulty, connection.Biome.MinDifficulty, connection.Biome.MaxDifficulty);
connection.Difficulty = MathHelper.Clamp(connection.Difficulty, connection.Biome.MinDifficulty, connection.Biome.AdjustedMaxDifficulty);
connection.LevelData = new LevelData(subElement.Element("Level"), connection.Difficulty);
Connections.Add(connection);
connectionElements.Add(subElement);
@@ -462,6 +462,13 @@ namespace Barotrauma
}
}
//make sure the connections are in the same order on the locations and the Connections list
//otherwise their order will change when loading the game (as they're added to the locations in the same order they're loaded)
foreach (var location in Locations)
{
location.Connections.Sort((c1, c2) => Connections.IndexOf(c1).CompareTo(Connections.IndexOf(c2)));
}
for (int i = Connections.Count - 1; i >= 0; i--)
{
i = Math.Min(i, Connections.Count - 1);
@@ -562,7 +569,7 @@ namespace Barotrauma
{
if (connection.Locations.Any(l => l.IsGateBetweenBiomes))
{
connection.Difficulty = connection.Locations.Min(l => l.Biome.MaxDifficulty);
connection.Difficulty = Math.Min(connection.Locations.Min(l => l.Biome.ActualMaxDifficulty), connection.Biome.AdjustedMaxDifficulty);
}
else
{
@@ -591,7 +598,7 @@ namespace Barotrauma
if (biome != null)
{
minDifficulty = biome.MinDifficulty;
maxDifficulty = biome.MaxDifficulty;
maxDifficulty = biome.AdjustedMaxDifficulty;
float diff = 1 - settingsFactor;
difficulty *= 1 - (1f / biome.AllowedZones.Max() * diff);
}
@@ -944,15 +951,20 @@ namespace Barotrauma
{
foreach (Location location in Locations)
{
location.LevelData.EventsExhausted = false;
if (location.Discovered)
{
if (furthestDiscoveredLocation == null ||
if (furthestDiscoveredLocation == null ||
location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{
furthestDiscoveredLocation = location;
}
}
}
foreach (LocationConnection connection in Connections)
{
connection.LevelData.EventsExhausted = false;
}
foreach (Location location in Locations)
{
@@ -340,7 +340,7 @@ namespace Barotrauma
/// </summary>
public virtual bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false)
{
if (this is Item item && !upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(item, upgrade.Prefab)))
if (!upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(this, upgrade.Prefab)))
{
return false;
}
@@ -361,16 +361,6 @@ namespace Barotrauma
Upgrades.Add(upgrade);
}
// not used anymore
#if SERVER
// if (createNetworkEvent)
// {
// if (this is IServerSerializable serializable)
// {
// GameMain.Server.CreateEntityEvent(serializable, new object[] { NetEntityEvent.Type.Upgrade, upgrade });
// }
// }
#endif
return true;
}
@@ -280,5 +280,29 @@ namespace Barotrauma
return AllowedLinks.Contains(target.Identifier) || target.AllowedLinks.Contains(Identifier)
|| target.Tags.Any(t => AllowedLinks.Contains(t)) || Tags.Any(t => target.AllowedLinks.Contains(t));
}
protected void LoadDescription(ContentXElement element)
{
Identifier descriptionIdentifier = element.GetAttributeIdentifier("descriptionidentifier", "");
Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", "");
string originalDescription = Description.Value;
if (descriptionIdentifier != Identifier.Empty)
{
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}");
}
else if (nameIdentifier == Identifier.Empty)
{
Description = TextManager.Get($"EntityDescription.{Identifier}");
}
else
{
Description = TextManager.Get($"EntityDescription.{nameIdentifier}");
}
if (!originalDescription.IsNullOrEmpty())
{
Description = Description.Fallback(originalDescription);
}
}
}
}
@@ -19,7 +19,7 @@ namespace Barotrauma
public NPCSet(ContentXElement element, NPCSetsFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
Humans = element.Elements().Select(npcElement => new HumanPrefab(npcElement, file)).ToImmutableArray();
Humans = element.Elements().Select(npcElement => new HumanPrefab(npcElement, file, Identifier)).ToImmutableArray();
}
public static HumanPrefab? Get(Identifier setIdentifier, Identifier npcidentifier)
@@ -203,7 +203,7 @@ namespace Barotrauma
}
else
{
newCollection.Add(new HumanPrefab(npcElement, file));
newCollection.Add(new HumanPrefab(npcElement, file, npcSetIdentifier: from));
}
}
humanPrefabCollections.Add(newCollection);
@@ -1426,7 +1426,7 @@ namespace Barotrauma
static bool ShouldRemoveLinkedEntity(MapEntity e, bool doorInUse, PlacedModule module)
{
if (e is Item it && it.GetComponent<Ladder>() != null)
if (e is Item it && it.IsLadder)
{
if (module.UsedGapPositions.HasFlag(OutpostModuleInfo.GapPosition.Top) || module.UsedGapPositions.HasFlag(OutpostModuleInfo.GapPosition.Bottom))
{
@@ -1568,7 +1568,7 @@ namespace Barotrauma
foreach (HumanPrefab humanPrefab in humanPrefabs)
{
if (humanPrefab is null) { continue; }
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.ServerAndClient), randSync: Rand.RandSync.ServerAndClient);
var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
if (location != null && location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
{
killedCharacters.Add(humanPrefab);
@@ -1582,7 +1582,7 @@ namespace Barotrauma
{
for (int tries = 0; tries < 100; tries++)
{
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: killedCharacter.GetJobPrefab(Rand.RandSync.ServerAndClient), randSync: Rand.RandSync.ServerAndClient);
var characterInfo = killedCharacter.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
if (!location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
{
selectedCharacters.Add((killedCharacter, characterInfo));
@@ -1,126 +0,0 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class RoundEndCinematic
{
public bool Running
{
get;
private set;
}
public Camera AssignedCamera;
private float duration;
private CoroutineHandle updateCoroutine;
public RoundEndCinematic(Submarine submarine, Camera cam, float duration = 10.0f)
: this(new List<Submarine>() { submarine }, cam, duration)
{
}
public RoundEndCinematic(List<Submarine> submarines, Camera cam, float duration)
{
if (!submarines.Any(s => s != null)) return;
this.duration = duration;
AssignedCamera = cam;
Running = true;
updateCoroutine = CoroutineManager.StartCoroutine(Update(submarines, cam));
}
public void Stop()
{
CoroutineManager.StopCoroutines(updateCoroutine);
Running = false;
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
}
private IEnumerable<CoroutineStatus> Update(List<Submarine> subs, Camera cam)
{
if (!subs.Any()) yield return CoroutineStatus.Success;
#if CLIENT
Character.Controlled = null;
GameMain.LightManager.LosEnabled = false;
#endif
cam.TargetPos = Vector2.Zero;
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;
Vector2 initialCameraPos = cam.Position;
while (timer < duration)
{
if (Screen.Selected != GameMain.GameScreen)
{
yield return new WaitForSeconds(0.1f);
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
Running = false;
yield return CoroutineStatus.Success;
}
Vector2 minPos = new Vector2(
subs.Min(s => s.WorldPosition.X - s.Borders.Width / 2),
subs.Min(s => s.WorldPosition.Y - s.Borders.Height / 2));
Vector2 maxPos = new Vector2(
subs.Min(s => s.WorldPosition.X + s.Borders.Width / 2),
subs.Min(s => s.WorldPosition.Y + s.Borders.Height / 2));
Vector2 cameraPos = new Vector2(
MathHelper.SmoothStep(minPos.X, maxPos.X, timer / duration),
(minPos.Y + maxPos.Y) / 2.0f);
cam.Translate(cameraPos - cam.Position);
foreach (Submarine sub in subs)
{
sub.PhysicsBody?.ResetDynamics();
}
#if CLIENT
cam.Zoom = MathHelper.SmoothStep(initialZoom, 0.5f, timer / duration);
if (timer / duration > 0.9f)
{
GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((timer / duration) - 0.9f) * 10.0f);
}
#endif
timer += CoroutineManager.UnscaledDeltaTime;
yield return CoroutineStatus.Running;
}
Running = false;
yield return new WaitForSeconds(0.1f);
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
yield return CoroutineStatus.Success;
}
}
}
@@ -168,6 +168,17 @@ namespace Barotrauma
public ImmutableHashSet<Identifier> Tags => Prefab.Tags;
#if DEBUG
[Editable, Serialize("", IsPropertySaveable.Yes)]
#else
[Serialize("", IsPropertySaveable.Yes)]
#endif
public string SpecialTag
{
get;
set;
}
protected Color spriteColor;
[Editable, Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes)]
public Color SpriteColor
@@ -574,6 +585,11 @@ namespace Barotrauma
int xsections = 1, ysections = 1;
int width = rect.Width, height = rect.Height;
WallSection[] prevSections = null;
if (Sections != null)
{
prevSections = Sections.ToArray();
}
if (!HasBody)
{
if (FlippedX && IsHorizontal)
@@ -657,6 +673,14 @@ namespace Barotrauma
}
}
}
if (prevSections != null && Sections.Length == prevSections.Length)
{
for (int i = 0; i < Sections.Length; i++)
{
Sections[i].damage = prevSections[i].damage;
}
}
}
private Rectangle GenerateMergedRect(List<WallSection> mergedSections)
@@ -829,27 +853,33 @@ namespace Barotrauma
public WallSection GetSection(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return null;
if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return null; }
return Sections[sectionIndex];
}
public bool SectionBodyDisabled(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false;
if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return false; }
return (Sections[sectionIndex].damage >= MaxHealth);
}
public bool AllSectionBodiesDisabled()
{
for (int i = 0; i < Sections.Length; i++)
{
if (Sections[i].damage < MaxHealth) { return false; }
}
return true;
}
/// <summary>
/// Sections that are leaking have a gap placed on them
/// </summary>
public bool SectionIsLeaking(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false;
return (Sections[sectionIndex].damage >= MaxHealth * LeakThreshold);
if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return false; }
return Sections[sectionIndex].damage >= MaxHealth * LeakThreshold;
}
public int SectionLength(int sectionIndex)
@@ -1139,21 +1169,22 @@ namespace Barotrauma
gapRect.Height += 20;
bool horizontalGap = !IsHorizontal;
bool diagonalGap = false;
if (Prefab.BodyRotation != 0.0f)
{
//rotation within a 90 deg sector (e.g. 100 -> 10, 190 -> 10, -10 -> 80)
float sectorizedRotation = MathUtils.WrapAngleTwoPi(BodyRotation) % MathHelper.PiOver2;
//diagonal if 30 < angle < 60
bool diagonal = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3;
diagonalGap = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3;
//gaps on the lower half of a diagonal wall are horizontal, ones on the upper half are vertical
if (diagonal)
if (diagonalGap)
{
horizontalGap = gapRect.Y - gapRect.Height / 2 < Position.Y;
if (FlippedY) { horizontalGap = !horizontalGap; }
}
}
Sections[sectionIndex].gap = new Gap(gapRect, horizontalGap, Submarine);
Sections[sectionIndex].gap = new Gap(gapRect, horizontalGap, Submarine, isDiagonal: diagonalGap);
//free the ID, because if we give gaps IDs we have to make sure they always match between the clients and the server and
//that clients create them in the correct order along with every other entity created/removed during the round
@@ -142,8 +142,6 @@ namespace Barotrauma
//only used if the item doesn't have a name/description defined in the currently selected language
Identifier fallbackNameIdentifier = element.GetAttributeIdentifier("fallbacknameidentifier", "");
Identifier descriptionIdentifier = element.GetAttributeIdentifier("descriptionidentifier", "");
Name = TextManager.Get(nameIdentifier.IsEmpty
? $"EntityName.{Identifier}"
: $"EntityName.{nameIdentifier}",
@@ -271,21 +269,7 @@ namespace Barotrauma
tags.Add("wall".ToIdentifier());
}
if (Description.IsNullOrEmpty())
{
if (!descriptionIdentifier.IsEmpty)
{
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}");
}
else if (nameIdentifier.IsEmpty)
{
Description = TextManager.Get($"EntityDescription.{Identifier}");
}
else
{
Description = TextManager.Get($"EntityDescription.{nameIdentifier}");
}
}
LoadDescription(element);
//backwards compatibility
if (element.GetAttribute("size") == null)
@@ -334,12 +318,6 @@ namespace Barotrauma
throw new NotImplementedException();
}
private bool disposed = false;
public override void Dispose()
{
if (disposed) { return; }
disposed = true;
Prefabs.Remove(this);
}
public override void Dispose() { }
}
}
@@ -187,7 +187,6 @@ namespace Barotrauma
if (structure.Submarine != this || !structure.HasBody || structure.Indestructible) { continue; }
realWorldCrushDepth = Math.Min(structure.CrushDepth, realWorldCrushDepth.Value);
}
realWorldCrushDepth *= Info.GetRealWorldCrushDepthMultiplier();
}
return realWorldCrushDepth.Value;
}
@@ -452,10 +451,27 @@ namespace Barotrauma
verticalMoveDir = Math.Sign(verticalMoveDir);
//do a raycast towards the top/bottom of the level depending on direction
Vector2 potentialPos = new Vector2(spawnPos.X, verticalMoveDir > 0 ? Level.Loaded.Size.Y : 0);
if (PickBody(ConvertUnits.ToSimUnits(spawnPos), ConvertUnits.ToSimUnits(potentialPos), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
//3 raycasts (left, middle and right side of the sub, so we don't accidentally raycast up a passage too narrow for the sub)
for (int x = -1; x <= 1; x++)
{
//if the raycast hit a wall, attempt to place the spawnpos there
potentialPos.Y = ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) - 10;
Vector2 xOffset = Vector2.UnitX * minWidth / 2 * x;
if (PickBody(
ConvertUnits.ToSimUnits(spawnPos + xOffset),
ConvertUnits.ToSimUnits(potentialPos + xOffset),
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
{
int offsetFromWall = 10 * -verticalMoveDir;
//if the raycast hit a wall, attempt to place the spawnpos there
if (verticalMoveDir > 0)
{
potentialPos.Y = Math.Min(potentialPos.Y, ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall);
}
else
{
potentialPos.Y = Math.Max(potentialPos.Y, ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall);
}
}
}
//step away from the top/bottom of the level, or from whatever wall the raycast hit,
@@ -986,14 +1002,6 @@ namespace Barotrauma
subBody.Body.ResetDynamics();
subBody.Body.Enabled = false;
foreach (MapEntity e in MapEntity.mapEntityList)
{
if (e.Submarine == this)
{
Spawner.AddEntityToRemoveQueue(e);
}
}
foreach (Character c in Character.CharacterList)
{
if (c.Submarine == this)
@@ -1226,7 +1234,7 @@ namespace Barotrauma
public List<(ItemContainer container, int freeSlots)> GetCargoContainers()
{
List<(ItemContainer container, int freeSlots)> containers = new List<(ItemContainer container, int freeSlots)>();
var connectedSubs = GetConnectedSubs();
var connectedSubs = GetConnectedSubs().Where(sub => sub.Info?.Type == Info.Type);
foreach (Item item in Item.ItemList.ToList())
{
if (!connectedSubs.Contains(item.Submarine)) { continue; }
@@ -1540,6 +1548,7 @@ namespace Barotrauma
element.Add(new XAttribute("description", Info.Description ?? ""));
element.Add(new XAttribute("checkval", Rand.Int(int.MaxValue)));
element.Add(new XAttribute("price", Info.Price));
element.Add(new XAttribute("tier", Info.Tier));
element.Add(new XAttribute("initialsuppliesspawned", Info.InitialSuppliesSpawned));
element.Add(new XAttribute("noitems", Info.NoItems));
element.Add(new XAttribute("lowfuel", !CheckFuel()));
@@ -1,8 +1,6 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Collision;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
@@ -345,14 +343,12 @@ namespace Barotrauma
Math.Max(Body.LinearVelocity.Y, ConvertUnits.ToSimUnits(Level.Loaded.BottomPos - (worldBorders.Y - worldBorders.Height))));
}
//hard limit for how far outside the level the sub can go
float maxDist = 200000.0f;
//the force of the current starts to increase exponentially after this point
float exponentialForceIncreaseDist = 150000.0f;
float distance = Position.X < 0 ? Math.Abs(Position.X) : Position.X - Level.Loaded.Size.X;
float distance = Position.X < -Level.OutsideBoundsCurrentMargin ?
Math.Abs(Position.X + Level.OutsideBoundsCurrentMargin) :
Position.X - (Level.Loaded.Size.X + Level.OutsideBoundsCurrentMargin);
if (distance > 0)
{
if (distance > maxDist)
if (distance > Level.OutsideBoundsCurrentHardLimit)
{
if (Position.X < 0)
{
@@ -363,9 +359,9 @@ namespace Barotrauma
Body.LinearVelocity = new Vector2(Math.Min(0, Body.LinearVelocity.X), Body.LinearVelocity.Y);
}
}
if (distance > exponentialForceIncreaseDist)
if (distance > Level.OutsideBoundsCurrentMarginExponential)
{
distance += (float)Math.Pow((distance - exponentialForceIncreaseDist) * 0.01f, 2.0f);
distance += (float)Math.Pow((distance - Level.OutsideBoundsCurrentMarginExponential) * 0.01f, 2.0f);
}
float force = distance * 0.5f;
totalForce += (Position.X < 0 ? Vector2.UnitX : -Vector2.UnitX) * force;
@@ -451,11 +447,13 @@ namespace Barotrauma
private Vector2 CalculateBuoyancy()
{
if (Submarine.LockY) { return Vector2.Zero; }
float waterVolume = 0.0f;
float volume = 0.0f;
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine != submarine) continue;
if (hull.Submarine != submarine) { continue; }
waterVolume += hull.WaterVolume;
volume += hull.Volume;
@@ -509,7 +507,6 @@ namespace Barotrauma
if (wall.Submarine != submarine) { continue; }
float wallCrushDepth = wall.CrushDepth;
if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; }
float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
if (pastCrushDepth > 0)
{
@@ -591,9 +588,13 @@ namespace Barotrauma
newHull = Hull.FindHull(targetPos, null);
}
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
if (adjacentGap == null) { return true; }
//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())
{
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
if (adjacentGap == null) { return true; }
}
if (newHull != null)
{
@@ -898,13 +899,15 @@ namespace Barotrauma
}
bool holdingOntoSomething = false;
if (c.SelectedConstruction != null)
if (c.SelectedSecondaryItem != null)
{
holdingOntoSomething =
c.SelectedConstruction.GetComponent<Ladder>() != null ||
(c.SelectedConstruction.GetComponent<Controller>()?.LimbPositions.Any() ?? false);
holdingOntoSomething = c.SelectedSecondaryItem.IsLadder ||
(c.SelectedSecondaryItem.GetComponent<Controller>()?.LimbPositions.Any() ?? false);
}
if (!holdingOntoSomething && c.SelectedItem != null)
{
holdingOntoSomething = c.SelectedItem.GetComponent<Controller>()?.LimbPositions.Any() ?? false;
}
if (!holdingOntoSomething)
{
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
@@ -24,7 +24,7 @@ namespace Barotrauma
}
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine, Ruin }
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
public enum SubmarineClass { Undefined, Scout, Attack, Transport }
partial class SubmarineInfo : IDisposable
{
@@ -49,6 +49,12 @@ namespace Barotrauma
}
public CrewExperienceLevel RecommendedCrewExperience;
public int Tier
{
get;
set;
}
/// <summary>
/// A random int that gets assigned when saving the sub. Used in mp campaign to verify that sub files match
/// </summary>
@@ -305,6 +311,7 @@ namespace Barotrauma
RecommendedCrewExperience = original.RecommendedCrewExperience;
RecommendedCrewSizeMin = original.RecommendedCrewSizeMin;
RecommendedCrewSizeMax = original.RecommendedCrewSizeMax;
Tier = original.Tier;
IsManuallyOutfitted = original.IsManuallyOutfitted;
Tags = original.Tags;
if (original.OutpostModuleInfo != null)
@@ -386,6 +393,7 @@ namespace Barotrauma
{
Enum.TryParse(recommendedCrewExperience.Value, ignoreCase: true, out RecommendedCrewExperience);
}
Tier = SubmarineElement.GetAttributeInt("tier", GetDefaultTier(Price));
if (SubmarineElement?.Attribute("type") != null)
{
@@ -407,7 +415,13 @@ namespace Barotrauma
{
if (SubmarineElement?.Attribute("class") != null)
{
if (Enum.TryParse(SubmarineElement.GetAttributeString("class", "Undefined"), out SubmarineClass submarineClass))
string classStr = SubmarineElement.GetAttributeString("class", "Undefined");
if (classStr == "DeepDiver")
{
//backwards compatibility
SubmarineClass = SubmarineClass.Scout;
}
else if (Enum.TryParse(classStr, out SubmarineClass submarineClass))
{
SubmarineClass = submarineClass;
}
@@ -538,25 +552,9 @@ namespace Barotrauma
{
realWorldCrushDepth = Level.DefaultRealWorldCrushDepth;
}
realWorldCrushDepth *= GetRealWorldCrushDepthMultiplier();
return realWorldCrushDepth;
}
/// <summary>
/// Based on <see cref="SubmarineClass"/>
/// </summary>
public float GetRealWorldCrushDepthMultiplier()
{
if (SubmarineClass == SubmarineClass.DeepDiver)
{
return 1.2f;
}
else
{
return 1.0f;
}
}
//saving/loading ----------------------------------------------------
public void SaveAs(string filePath, System.IO.MemoryStream previewImage = null)
{
@@ -691,7 +689,7 @@ namespace Barotrauma
System.IO.Stream stream;
try
{
stream = SaveUtil.DecompressFiletoStream(file);
stream = SaveUtil.DecompressFileToStream(file);
}
catch (System.IO.FileNotFoundException e)
{
@@ -748,5 +746,7 @@ namespace Barotrauma
return doc;
}
public static int GetDefaultTier(int price) => price > 20000 ? 3 : price > 10000 ? 2 : 1;
}
}
}
@@ -19,7 +19,7 @@ namespace Barotrauma
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
public const float LadderWaypointInterval = 70.0f;
public const float LadderWaypointInterval = 55.0f;
protected SpawnType spawnType;
private string[] idCardTags;
@@ -560,21 +560,22 @@ namespace Barotrauma
stairPoints.ForEach(wp => wp.FindStairs());
}
// Ladders
foreach (Item item in Item.ItemList)
{
var ladders = item.GetComponent<Ladder>();
if (ladders == null) { continue; }
Vector2 bottomPoint = new Vector2(item.Rect.Center.X, item.Rect.Top - item.Rect.Height + 10);
List<WayPoint> ladderPoints = new List<WayPoint>
List<(WayPoint wp, bool connectHullPoints)> ladderPoints = new List<(WayPoint, bool)>
{
new WayPoint(bottomPoint, SpawnType.Path, submarine),
(new WayPoint(bottomPoint, SpawnType.Path, submarine), true)
};
List<Body> ignoredBodies = new List<Body>();
// Lowest point is only meaningful for hanging ladders inside the sub, but it shouldn't matter in other cases either.
// Start point is where the bots normally grasp the ladder when they stand on ground.
WayPoint lowestPoint = ladderPoints[0];
WayPoint lowestPoint = ladderPoints[0].wp;
WayPoint prevPoint = lowestPoint;
Vector2 prevPos = prevPoint.SimPosition;
Body ground = Submarine.PickBody(lowestPoint.SimPosition, lowestPoint.SimPosition - Vector2.UnitY, ignoredBodies,
@@ -589,7 +590,7 @@ namespace Barotrauma
if (lowestPoint == null || Math.Abs(startPoint.Position.Y - startHeight) > 40 && Hull.FindHull(nextPos) != null)
{
startPoint = new WayPoint(nextPos, SpawnType.Path, submarine);
ladderPoints.Add(startPoint);
ladderPoints.Add((startPoint, true));
if (lowestPoint != null)
{
startPoint.ConnectTo(lowestPoint);
@@ -613,18 +614,13 @@ namespace Barotrauma
}
else
{
//no door, check for walls
//no door, check for platforms/walls
pickedBody = Submarine.PickBody(
ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)), prevPos, ignoredBodies, null, false,
(Fixture f) => f.Body.UserData is Structure);
}
if (pickedBody == null)
{
prevPos = Submarine.LastPickedPosition;
continue;
}
else
if (pickedBody != null)
{
ignoredBodies.Add(pickedBody);
}
@@ -632,19 +628,29 @@ namespace Barotrauma
if (pickedDoor != null)
{
WayPoint newPoint = new WayPoint(pickedDoor.Item.Position, SpawnType.Path, submarine);
ladderPoints.Add(newPoint);
ladderPoints.Add((newPoint, true));
newPoint.ConnectedGap = pickedDoor.LinkedGap;
// TODO: Prevent the waypoint below being too close to the door
newPoint.ConnectTo(prevPoint);
prevPoint = newPoint;
prevPos = new Vector2(prevPos.X, ConvertUnits.ToSimUnits(pickedDoor.Item.Position.Y - pickedDoor.Item.Rect.Height));
// Adjust y to prevent waypoints clamping up together
y = Math.Max(pickedDoor.Item.Position.Y, y);
}
else
{
WayPoint newPoint = new WayPoint(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor, SpawnType.Path, submarine);
ladderPoints.Add(newPoint);
Vector2 pos = pickedBody == null ? new Vector2(startPoint.Position.X, y) :
ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor;
WayPoint newPoint = new WayPoint(pos, SpawnType.Path, submarine);
ladderPoints.Add((newPoint, pickedBody != null));
newPoint.ConnectTo(prevPoint);
prevPoint = newPoint;
prevPos = ConvertUnits.ToSimUnits(newPoint.Position);
if (pickedBody != null)
{
// Adjust y to prevent waypoints clamping up together
y = Math.Max(newPoint.Position.Y, y);
}
}
}
@@ -652,33 +658,30 @@ namespace Barotrauma
if (prevPoint.rect.Y < item.Rect.Y - 40)
{
WayPoint wayPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine);
ladderPoints.Add(wayPoint);
ladderPoints.Add((wayPoint, true));
wayPoint.ConnectTo(prevPoint);
}
// Connect ladder waypoints to hull points at the right and left side
foreach (WayPoint ladderPoint in ladderPoints)
var ladderWaypoints = ladderPoints.Select(lp => lp.wp);
foreach (var ladderPoint in ladderPoints)
{
ladderPoint.Ladders = ladders;
bool isHatch = ladderPoint.ConnectedGap != null && !ladderPoint.ConnectedGap.IsRoomToRoom;
var wp = ladderPoint.wp;
wp.Ladders = ladders;
if (!ladderPoint.connectHullPoints) { continue; }
bool isHatch = wp.ConnectedGap != null && !wp.ConnectedGap.IsRoomToRoom;
for (int dir = -1; dir <= 1; dir += 2)
{
WayPoint closest = null;
if (isHatch)
{
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderPoints);
}
else
{
closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints);
}
WayPoint closest = isHatch ?
wp.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), wp.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderWaypoints) :
wp.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), wp.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderWaypoints);
if (closest == null) { continue; }
ladderPoint.ConnectTo(closest);
wp.ConnectTo(closest);
}
}
}
// Another pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders)
// Another ladder pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders)
foreach (Item item in Item.ItemList)
{
var ladders = item.GetComponent<Ladder>();
@@ -1035,12 +1038,10 @@ namespace Barotrauma
w.tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToHashSet();
string jobIdentifier = element.GetAttributeString("job", "").ToLowerInvariant();
if (!string.IsNullOrWhiteSpace(jobIdentifier))
Identifier jobIdentifier = element.GetAttributeIdentifier("job", Identifier.Empty);
if (!jobIdentifier.IsEmpty)
{
w.AssignedJob =
JobPrefab.Get(jobIdentifier) ??
JobPrefab.Prefabs.Find(jp => jp.Name.Equals(jobIdentifier, StringComparison.OrdinalIgnoreCase));
w.AssignedJob = JobPrefab.Get(jobIdentifier);
}
w.linkedToID = new List<ushort>();