Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into unstable
This commit is contained in:
@@ -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>();
|
||||
|
||||
Reference in New Issue
Block a user