v0.13.0.11
This commit is contained in:
@@ -87,7 +87,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
internal partial class BallastFloraBehavior : ISerializableEntity
|
||||
{
|
||||
#if DEBUG || UNSTABLE
|
||||
#if DEBUG
|
||||
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
protected virtual void Grow()
|
||||
{
|
||||
List<BallastFloraBranch> newTiles = GrowRandomly();
|
||||
#if DEBUG || UNSTABLE
|
||||
#if DEBUG
|
||||
Behavior.debugSearchLines.Clear();
|
||||
#endif
|
||||
if (newTiles.Any(TryScanTargets)) { return; }
|
||||
@@ -135,7 +135,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
Vector2 itemSimPos = ConvertUnits.ToSimUnits(item.Position);
|
||||
|
||||
#if DEBUG || UNSTABLE
|
||||
#if DEBUG
|
||||
Tuple<Vector2, Vector2> debugLine1 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(topLeft), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos - diameter));
|
||||
Tuple<Vector2, Vector2> debugLine2 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(bottomRight), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos + diameter));
|
||||
Behavior.debugSearchLines.Add(debugLine2);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
{
|
||||
private static readonly List<Triplet<Explosion, Vector2, float>> prevExplosions = new List<Triplet<Explosion, Vector2, float>>();
|
||||
|
||||
private readonly Attack attack;
|
||||
public readonly Attack Attack;
|
||||
|
||||
private readonly float force;
|
||||
|
||||
@@ -27,6 +27,10 @@ namespace Barotrauma
|
||||
private bool sparks, shockwave, flames, smoke, flash, underwaterBubble;
|
||||
private bool playTinnitus;
|
||||
private bool applyFireEffects;
|
||||
private string[] ignoreFireEffectsForTags;
|
||||
private bool ignoreCover;
|
||||
private bool onlyInside;
|
||||
private bool onlyOutside;
|
||||
private readonly float flashDuration;
|
||||
private readonly float? flashRange;
|
||||
private readonly string decal;
|
||||
@@ -38,7 +42,7 @@ namespace Barotrauma
|
||||
|
||||
public Explosion(float range, float force, float damage, float structureDamage, float itemDamage, float empStrength = 0.0f, float ballastFloraStrength = 0.0f)
|
||||
{
|
||||
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range)
|
||||
Attack = new Attack(damage, 0.0f, 0.0f, structureDamage, itemDamage, range)
|
||||
{
|
||||
SeverLimbsProbability = 1.0f
|
||||
};
|
||||
@@ -50,11 +54,12 @@ namespace Barotrauma
|
||||
smoke = true;
|
||||
flames = true;
|
||||
underwaterBubble = true;
|
||||
ignoreFireEffectsForTags = new string[0];
|
||||
}
|
||||
|
||||
public Explosion(XElement element, string parentDebugName)
|
||||
{
|
||||
attack = new Attack(element, parentDebugName + ", Explosion");
|
||||
Attack = new Attack(element, parentDebugName + ", Explosion");
|
||||
|
||||
force = element.GetAttributeFloat("force", 0.0f);
|
||||
|
||||
@@ -67,6 +72,11 @@ namespace Barotrauma
|
||||
playTinnitus = element.GetAttributeBool("playtinnitus", true);
|
||||
|
||||
applyFireEffects = element.GetAttributeBool("applyfireeffects", flames);
|
||||
ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", new string[0], convertToLowerInvariant: true);
|
||||
|
||||
ignoreCover = element.GetAttributeBool("ignorecover", false);
|
||||
onlyInside = element.GetAttributeBool("onlyinside", false);
|
||||
onlyOutside = element.GetAttributeBool("onlyoutside", false);
|
||||
|
||||
flash = element.GetAttributeBool("flash", true);
|
||||
flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
|
||||
@@ -78,10 +88,10 @@ namespace Barotrauma
|
||||
decal = element.GetAttributeString("decal", "");
|
||||
decalSize = element.GetAttributeFloat(1.0f, "decalSize", "decalsize");
|
||||
|
||||
cameraShake = element.GetAttributeFloat("camerashake", attack.Range * 0.1f);
|
||||
cameraShakeRange = element.GetAttributeFloat("camerashakerange", attack.Range);
|
||||
cameraShake = element.GetAttributeFloat("camerashake", Attack.Range * 0.1f);
|
||||
cameraShakeRange = element.GetAttributeFloat("camerashakerange", Attack.Range);
|
||||
|
||||
screenColorRange = element.GetAttributeFloat("screencolorrange", attack.Range * 0.1f);
|
||||
screenColorRange = element.GetAttributeFloat("screencolorrange", Attack.Range * 0.1f);
|
||||
screenColor = element.GetAttributeColor("screencolor", Color.Transparent);
|
||||
screenColorDuration = element.GetAttributeFloat("screencolorduration", 0.1f);
|
||||
}
|
||||
@@ -117,7 +127,7 @@ namespace Barotrauma
|
||||
hull.AddDecal(decal, worldPosition, decalSize, isNetworkEvent: false);
|
||||
}
|
||||
|
||||
float displayRange = attack.Range;
|
||||
float displayRange = Attack.Range;
|
||||
|
||||
Vector2 cameraPos = Character.Controlled != null ? Character.Controlled.WorldPosition : GameMain.GameScreen.Cam.Position;
|
||||
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
|
||||
@@ -132,9 +142,9 @@ namespace Barotrauma
|
||||
|
||||
if (displayRange < 0.1f) { return; }
|
||||
|
||||
if (attack.GetStructureDamage(1.0f) > 0.0f)
|
||||
if (Attack.GetStructureDamage(1.0f) > 0.0f || Attack.GetLevelWallDamage(1.0f) > 0.0f)
|
||||
{
|
||||
RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f), attack.GetLevelWallDamage(1.0f), attacker);
|
||||
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker);
|
||||
}
|
||||
|
||||
if (BallastFloraDamage > 0.0f)
|
||||
@@ -169,12 +179,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(attack.Stun, 0.0f) && MathUtils.NearlyEqual(attack.GetTotalDamage(false), 0.0f))
|
||||
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DamageCharacters(worldPosition, attack, force, damageSource, attacker);
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -184,9 +194,9 @@ namespace Barotrauma
|
||||
float dist = Vector2.Distance(item.WorldPosition, worldPosition);
|
||||
float itemRadius = item.body == null ? 0.0f : item.body.GetMaxExtent();
|
||||
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius));
|
||||
if (dist > attack.Range) { continue; }
|
||||
if (dist > Attack.Range) { continue; }
|
||||
|
||||
if (dist < attack.Range * 0.5f && applyFireEffects && !item.FireProof)
|
||||
if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t)))
|
||||
{
|
||||
//don't apply OnFire effects if the item is inside a fireproof container
|
||||
//(or if it's inside a container that's inside a fireproof container, etc)
|
||||
@@ -213,8 +223,8 @@ namespace Barotrauma
|
||||
|
||||
if (item.Prefab.DamagedByExplosions && !item.Indestructible)
|
||||
{
|
||||
float distFactor = 1.0f - dist / attack.Range;
|
||||
float damageAmount = attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
|
||||
float distFactor = 1.0f - dist / Attack.Range;
|
||||
float damageAmount = Attack.GetItemDamage(1.0f) * item.Prefab.ExplosionDamageMultiplier;
|
||||
|
||||
Vector2 explosionPos = worldPosition;
|
||||
if (item.Submarine != null) { explosionPos -= item.Submarine.Position; }
|
||||
@@ -243,6 +253,8 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (onlyInside && c.Submarine == null) { continue; }
|
||||
else if (onlyOutside && c.Submarine != null) { continue; }
|
||||
|
||||
Vector2 explosionPos = worldPosition;
|
||||
if (c.Submarine != null) { explosionPos -= c.Submarine.Position; }
|
||||
@@ -271,7 +283,10 @@ namespace Barotrauma
|
||||
float distFactor = 1.0f - dist / attack.Range;
|
||||
|
||||
//solid obstacles between the explosion and the limb reduce the effect of the explosion
|
||||
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
|
||||
if (!ignoreCover)
|
||||
{
|
||||
distFactor *= GetObstacleDamageMultiplier(explosionPos, worldPosition, limb.SimPosition);
|
||||
}
|
||||
distFactors.Add(limb, distFactor);
|
||||
|
||||
modifiedAfflictions.Clear();
|
||||
|
||||
@@ -203,9 +203,9 @@ namespace Barotrauma
|
||||
public void AutoOrient()
|
||||
{
|
||||
Vector2 searchPosLeft = new Vector2(rect.X, rect.Y - rect.Height / 2);
|
||||
Hull hullLeft = Hull.FindHullOld(searchPosLeft, null, false);
|
||||
Hull hullLeft = Hull.FindHullUnoptimized(searchPosLeft, null, false);
|
||||
Vector2 searchPosRight = new Vector2(rect.Right, rect.Y - rect.Height / 2);
|
||||
Hull hullRight = Hull.FindHullOld(searchPosRight, null, false);
|
||||
Hull hullRight = Hull.FindHullUnoptimized(searchPosRight, null, false);
|
||||
|
||||
if (hullLeft != null && hullRight != null && hullLeft != hullRight)
|
||||
{
|
||||
@@ -214,9 +214,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 searchPosTop = new Vector2(rect.Center.X, rect.Y);
|
||||
Hull hullTop = Hull.FindHullOld(searchPosTop, null, false);
|
||||
Hull hullTop = Hull.FindHullUnoptimized(searchPosTop, null, false);
|
||||
Vector2 searchPosBottom = new Vector2(rect.Center.X, rect.Y - rect.Height);
|
||||
Hull hullBottom = Hull.FindHullOld(searchPosBottom, null, false);
|
||||
Hull hullBottom = Hull.FindHullUnoptimized(searchPosBottom, null, false);
|
||||
|
||||
if (hullTop != null && hullBottom != null && hullTop != hullBottom)
|
||||
{
|
||||
@@ -261,8 +261,8 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hulls[i] = Hull.FindHullOld(searchPos[i], null, false);
|
||||
if (hulls[i] == null) hulls[i] = Hull.FindHullOld(searchPos[i], null, false, true);
|
||||
hulls[i] = Hull.FindHullUnoptimized(searchPos[i], null, false);
|
||||
if (hulls[i] == null) hulls[i] = Hull.FindHullUnoptimized(searchPos[i], null, false, true);
|
||||
}
|
||||
|
||||
if (hulls[0] == null && hulls[1] == null) { return; }
|
||||
|
||||
@@ -939,14 +939,14 @@ namespace Barotrauma
|
||||
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
||||
/// Uses a greedy algo and may not use the most optimal path. Returns float.MaxValue if no path is found.
|
||||
/// </summary>
|
||||
public float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance)
|
||||
public float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance, float distanceMultiplierPerClosedDoor = 0)
|
||||
{
|
||||
return GetApproximateHullDistance(startPos, endPos, new HashSet<Hull>(), targetHull, 0.0f, maxDistance);
|
||||
return GetApproximateHullDistance(startPos, endPos, new HashSet<Hull>(), targetHull, 0.0f, maxDistance, distanceMultiplierPerClosedDoor);
|
||||
}
|
||||
|
||||
private float GetApproximateHullDistance(Vector2 startPos, Vector2 endPos, HashSet<Hull> connectedHulls, Hull target, float distance, float maxDistance)
|
||||
private float GetApproximateHullDistance(Vector2 startPos, Vector2 endPos, HashSet<Hull> connectedHulls, Hull target, float distance, float maxDistance, float distanceMultiplierFromDoors = 0)
|
||||
{
|
||||
if (distance >= maxDistance) return float.MaxValue;
|
||||
if (distance >= maxDistance) { return float.MaxValue; }
|
||||
if (this == target)
|
||||
{
|
||||
return distance + Vector2.Distance(startPos, endPos);
|
||||
@@ -956,12 +956,17 @@ namespace Barotrauma
|
||||
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
float distanceMultiplier = 1;
|
||||
if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken)
|
||||
{
|
||||
//gap blocked if the door is not open or the predicted state is not open
|
||||
if ((!g.ConnectedDoor.IsOpen && !g.ConnectedDoor.IsBroken) || (g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
|
||||
{
|
||||
if (g.ConnectedDoor.OpenState < 0.1f) continue;
|
||||
if (g.ConnectedDoor.OpenState < 0.1f)
|
||||
{
|
||||
if (distanceMultiplierFromDoors <= 0) { continue; }
|
||||
distanceMultiplier *= distanceMultiplierFromDoors;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g.Open <= 0.0f)
|
||||
@@ -973,8 +978,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (g.linkedTo[i] is Hull hull && !connectedHulls.Contains(hull))
|
||||
{
|
||||
float dist = hull.GetApproximateHullDistance(g.Position, endPos, connectedHulls, target, distance + Vector2.Distance(startPos, g.Position), maxDistance);
|
||||
if (dist < float.MaxValue) { return dist; }
|
||||
float dist = hull.GetApproximateHullDistance(g.Position, endPos, connectedHulls, target, distance + Vector2.Distance(startPos, g.Position) * distanceMultiplier, maxDistance);
|
||||
if (dist < float.MaxValue)
|
||||
{
|
||||
return dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -982,7 +990,13 @@ namespace Barotrauma
|
||||
return float.MaxValue;
|
||||
}
|
||||
|
||||
//returns the water block which contains the point (or null if it isn't inside any)
|
||||
/// <summary>
|
||||
/// Returns the hull which contains the point (or null if it isn't inside any)
|
||||
/// </summary>
|
||||
/// <param name="position">The position to check</param>
|
||||
/// <param name="guess">This hull is checked first: if the current hull is known, this can be used as an optimization</param>
|
||||
/// <param name="useWorldCoordinates">Should world coordinates or the sub's local coordinates be used?</param>
|
||||
/// <param name="inclusive">Does being exactly at the edge of the hull count as being inside?</param>
|
||||
public static Hull FindHull(Vector2 position, Hull guess = null, bool useWorldCoordinates = true, bool inclusive = true)
|
||||
{
|
||||
if (EntityGrids == null) return null;
|
||||
@@ -1030,20 +1044,19 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
//returns the water block which contains the point (or null if it isn't inside any)
|
||||
public static Hull FindHullOld(Vector2 position, Hull guess = null, bool useWorldCoordinates = true, bool inclusive = true)
|
||||
/// <summary>
|
||||
/// Returns the hull which contains the point (or null if it isn't inside any). The difference to FindHull is that this method goes through all hulls without trying
|
||||
/// to first find the sub the point is inside and checking the hulls in that sub.
|
||||
/// = This is slower, use with caution in situations where the sub's extents or hulls may have changed after it was loaded.
|
||||
/// </summary>
|
||||
public static Hull FindHullUnoptimized(Vector2 position, Hull guess = null, bool useWorldCoordinates = true, bool inclusive = true)
|
||||
{
|
||||
return FindHullOld(position, hullList, guess, useWorldCoordinates, inclusive);
|
||||
}
|
||||
|
||||
public static Hull FindHullOld(Vector2 position, List<Hull> hulls, Hull guess = null, bool useWorldCoordinates = true, bool inclusive = true)
|
||||
{
|
||||
if (guess != null && hulls.Contains(guess))
|
||||
if (guess != null && hullList.Contains(guess))
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive)) return guess;
|
||||
}
|
||||
|
||||
foreach (Hull hull in hulls)
|
||||
foreach (Hull hull in hullList)
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? hull.WorldRect : hull.rect, position, inclusive)) return hull;
|
||||
}
|
||||
|
||||
@@ -135,13 +135,18 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public List<MapEntity> CreateInstance(Vector2 position, Submarine sub, bool selectInstance = false)
|
||||
{
|
||||
return PasteEntities(position, sub, configElement, FilePath, selectInstance);
|
||||
}
|
||||
|
||||
public static List<MapEntity> PasteEntities(Vector2 position, Submarine sub, XElement configElement, string filePath = null, bool selectInstance = false)
|
||||
{
|
||||
int idOffset = Entity.FindFreeID(1);
|
||||
if (MapEntity.mapEntityList.Any()) { idOffset = MapEntity.mapEntityList.Max(e => e.ID); }
|
||||
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, FilePath, idOffset);
|
||||
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, filePath, idOffset);
|
||||
if (entities.Count == 0) { return entities; }
|
||||
|
||||
Vector2 offset = sub == null ? Vector2.Zero : sub.HiddenSubPosition;
|
||||
Vector2 offset = sub?.HiddenSubPosition ?? Vector2.Zero;
|
||||
|
||||
foreach (MapEntity me in entities)
|
||||
{
|
||||
@@ -168,9 +173,8 @@ namespace Barotrauma
|
||||
MapEntity.SelectedList.Clear();
|
||||
entities.ForEach(MapEntity.AddSelection);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return entities;
|
||||
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
|
||||
@@ -101,19 +101,19 @@ namespace Barotrauma
|
||||
public Sprite WallSprite { get; private set; }
|
||||
public Sprite WallEdgeSprite { get; private set; }
|
||||
|
||||
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, Rand.RandSync rand)
|
||||
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, bool abyss, Rand.RandSync rand)
|
||||
{
|
||||
if (CaveParams.All(p => p.GetCommonness(generationParams) <= 0.0f))
|
||||
if (CaveParams.All(p => p.GetCommonness(generationParams, abyss) <= 0.0f))
|
||||
{
|
||||
return CaveParams.First();
|
||||
}
|
||||
return ToolBox.SelectWeightedRandom(CaveParams, CaveParams.Select(p => p.GetCommonness(generationParams)).ToList(), rand);
|
||||
return ToolBox.SelectWeightedRandom(CaveParams, CaveParams.Select(p => p.GetCommonness(generationParams, abyss)).ToList(), rand);
|
||||
}
|
||||
|
||||
public float GetCommonness(LevelGenerationParams generationParams)
|
||||
public float GetCommonness(LevelGenerationParams generationParams, bool abyss)
|
||||
{
|
||||
if (generationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
||||
OverrideCommonness.TryGetValue(abyss ? "abyss" : generationParams.Identifier, out float commonness))
|
||||
{
|
||||
return commonness;
|
||||
}
|
||||
|
||||
@@ -141,29 +141,21 @@ namespace Barotrauma
|
||||
return cells;
|
||||
}
|
||||
|
||||
public static void GeneratePath(Level.Tunnel tunnel, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, Rectangle limits)
|
||||
public static void GeneratePath(Level.Tunnel tunnel, Level level)
|
||||
{
|
||||
var targetCells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < tunnel.Nodes.Count; i++)
|
||||
{
|
||||
//a search depth of 2 is large enough to find a cell in almost all maps, but in case it fails, we increase the depth
|
||||
int searchDepth = 2;
|
||||
while (searchDepth < 5)
|
||||
var closestCell = level.GetClosestCell(tunnel.Nodes[i].ToVector2());
|
||||
if (closestCell != null && !targetCells.Contains(closestCell))
|
||||
{
|
||||
int cellIndex = FindCellIndex(tunnel.Nodes[i], cells, cellGrid, gridCellSize, searchDepth);
|
||||
if (cellIndex > -1)
|
||||
{
|
||||
targetCells.Add(cells[cellIndex]);
|
||||
break;
|
||||
}
|
||||
|
||||
searchDepth++;
|
||||
targetCells.Add(closestCell);
|
||||
}
|
||||
}
|
||||
tunnel.Cells.AddRange(GeneratePath(targetCells, cells, limits));
|
||||
tunnel.Cells.AddRange(GeneratePath(targetCells, level.GetAllCells()));
|
||||
}
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells, Rectangle limits)
|
||||
public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
@@ -460,10 +452,15 @@ namespace Barotrauma
|
||||
|
||||
return cellBody;
|
||||
}
|
||||
|
||||
|
||||
public static List<Vector2> CreateRandomChunk(float radius, int vertexCount, float radiusVariance)
|
||||
{
|
||||
Debug.Assert(radiusVariance < radius);
|
||||
return CreateRandomChunk(radius * 2, radius * 2, vertexCount, radiusVariance);
|
||||
}
|
||||
|
||||
public static List<Vector2> CreateRandomChunk(float width, float height, int vertexCount, float radiusVariance)
|
||||
{
|
||||
Debug.Assert(radiusVariance < Math.Min(width, height));
|
||||
Debug.Assert(vertexCount >= 3);
|
||||
|
||||
List<Vector2> verts = new List<Vector2>();
|
||||
@@ -471,72 +468,12 @@ namespace Barotrauma
|
||||
float angle = 0.0f;
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
verts.Add(new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) *
|
||||
(radius + Rand.Range(-radiusVariance, radiusVariance, Rand.RandSync.Server)));
|
||||
Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
|
||||
verts.Add(new Vector2(dir.X * width / 2, dir.Y * height / 2) + dir * Rand.Range(-radiusVariance, radiusVariance, Rand.RandSync.Server));
|
||||
angle += angleStep;
|
||||
}
|
||||
return verts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// find the index of the cell which the point is inside
|
||||
/// (actually finds the cell whose center is closest, but it's always the correct cell assuming the point is inside the borders of the diagram)
|
||||
/// </summary>
|
||||
public static int FindCellIndex(Vector2 position,List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, int searchDepth = 1, Vector2? offset = null)
|
||||
{
|
||||
float closestDist = float.PositiveInfinity;
|
||||
VoronoiCell closestCell = null;
|
||||
|
||||
Vector2 gridOffset = offset == null ? Vector2.Zero : (Vector2)offset;
|
||||
position -= gridOffset;
|
||||
|
||||
int gridPosX = (int)Math.Floor(position.X / gridCellSize);
|
||||
int gridPosY = (int)Math.Floor(position.Y / gridCellSize);
|
||||
|
||||
for (int x = Math.Max(gridPosX - searchDepth, 0); x <= Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1); x++)
|
||||
{
|
||||
for (int y = Math.Max(gridPosY - searchDepth, 0); y <= Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1); y++)
|
||||
{
|
||||
for (int i = 0; i < cellGrid[x, y].Count; i++)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(cellGrid[x, y][i].Center, position);
|
||||
if (dist > closestDist) continue;
|
||||
|
||||
closestDist = dist;
|
||||
closestCell = cellGrid[x, y][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells.IndexOf(closestCell);
|
||||
}
|
||||
|
||||
public static int FindCellIndex(Point position, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, int searchDepth = 1)
|
||||
{
|
||||
int closestDist = int.MaxValue;
|
||||
VoronoiCell closestCell = null;
|
||||
|
||||
int gridPosX = position.X / gridCellSize;
|
||||
int gridPosY = position.Y / gridCellSize;
|
||||
|
||||
for (int x = Math.Max(gridPosX - searchDepth, 0); x <= Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1); x++)
|
||||
{
|
||||
for (int y = Math.Max(gridPosY - searchDepth, 0); y <= Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1); y++)
|
||||
{
|
||||
for (int i = 0; i < cellGrid[x, y].Count; i++)
|
||||
{
|
||||
int dist = MathUtils.DistanceSquared(
|
||||
(int)cellGrid[x, y][i].Site.Coord.X, (int)cellGrid[x, y][i].Site.Coord.Y,
|
||||
position.X, position.Y);
|
||||
if (dist > closestDist) continue;
|
||||
|
||||
closestDist = dist;
|
||||
closestCell = cellGrid[x, y][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells.IndexOf(closestCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,8 @@ namespace Barotrauma
|
||||
public bool HasBeaconStation;
|
||||
public bool IsBeaconActive;
|
||||
|
||||
public bool HasHuntingGrounds, OriginallyHadHuntingGrounds;
|
||||
|
||||
public OutpostGenerationParams ForceOutpostGenerationParams;
|
||||
|
||||
public readonly Point Size;
|
||||
@@ -86,6 +88,9 @@ namespace Barotrauma
|
||||
HasBeaconStation = element.GetAttributeBool("hasbeaconstation", false);
|
||||
IsBeaconActive = element.GetAttributeBool("isbeaconactive", false);
|
||||
|
||||
HasHuntingGrounds = element.GetAttributeBool("hashuntinggrounds", false);
|
||||
OriginallyHadHuntingGrounds = element.GetAttributeBool("originallyhadhuntinggrounds", HasHuntingGrounds);
|
||||
|
||||
string generationParamsId = element.GetAttributeString("generationparams", "");
|
||||
GenerationParams = LevelGenerationParams.LevelParams.Find(l => l.Identifier == generationParamsId || l.OldIdentifier == generationParamsId);
|
||||
if (GenerationParams == null)
|
||||
@@ -112,8 +117,7 @@ namespace Barotrauma
|
||||
EventHistory.AddRange(EventSet.PrefabList.Where(p => prefabNames.Any(n => p.Identifier.Equals(n, StringComparison.InvariantCultureIgnoreCase))));
|
||||
|
||||
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { });
|
||||
NonRepeatableEvents.AddRange(EventSet.PrefabList.Where(p => prefabNames.Any(n => p.Identifier.Equals(n, StringComparison.InvariantCultureIgnoreCase))));
|
||||
|
||||
NonRepeatableEvents.AddRange(EventSet.PrefabList.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier.Equals(n, StringComparison.InvariantCultureIgnoreCase))));
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +144,13 @@ namespace Barotrauma
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
|
||||
|
||||
HasBeaconStation = rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
//minimum difficulty of the level before hunting grounds can appear
|
||||
float huntingGroundsDifficultyThreshold = 25;
|
||||
//probability of hunting grounds appearing in 100% difficulty levels
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability;
|
||||
|
||||
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
IsBeaconActive = false;
|
||||
}
|
||||
|
||||
@@ -163,7 +173,7 @@ namespace Barotrauma
|
||||
(int)MathUtils.Round(GenerationParams.Height, Level.GridCellSize));
|
||||
}
|
||||
|
||||
public static LevelData CreateRandom(string seed = "", float? difficulty = null, LevelGenerationParams generationParams = null)
|
||||
public static LevelData CreateRandom(string seed = "", float? difficulty = null, LevelGenerationParams generationParams = null, bool requireOutpost = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
{
|
||||
@@ -172,7 +182,9 @@ namespace Barotrauma
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
LevelType type = generationParams == null ? LevelData.LevelType.LocationConnection : generationParams.Type;
|
||||
LevelType type = generationParams == null ?
|
||||
(requireOutpost ? LevelType.Outpost : LevelType.LocationConnection) :
|
||||
generationParams.Type;
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); }
|
||||
var biome =
|
||||
@@ -191,7 +203,13 @@ namespace Barotrauma
|
||||
levelData.HasBeaconStation = beaconRng < 0.5f;
|
||||
levelData.IsBeaconActive = beaconRng > 0.25f;
|
||||
}
|
||||
GameMain.GameSession?.GameMode?.Mission?.AdjustLevelData(levelData);
|
||||
if (GameMain.GameSession?.GameMode != null)
|
||||
{
|
||||
foreach (Mission mission in GameMain.GameSession.GameMode.Missions)
|
||||
{
|
||||
mission.AdjustLevelData(levelData);
|
||||
}
|
||||
}
|
||||
return levelData;
|
||||
}
|
||||
|
||||
@@ -213,6 +231,17 @@ namespace Barotrauma
|
||||
new XAttribute("isbeaconactive", IsBeaconActive.ToString()));
|
||||
}
|
||||
|
||||
if (HasHuntingGrounds)
|
||||
{
|
||||
newElement.Add(
|
||||
new XAttribute("hashuntinggrounds", true));
|
||||
}
|
||||
if (HasHuntingGrounds || OriginallyHadHuntingGrounds)
|
||||
{
|
||||
newElement.Add(
|
||||
new XAttribute("originallyhadhuntinggrounds", true));
|
||||
}
|
||||
|
||||
if (Type == LevelType.Outpost)
|
||||
{
|
||||
if (EventHistory.Any())
|
||||
|
||||
@@ -181,6 +181,13 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true, "Should the generator force a hole to the bottom of the level to ensure there's a way to the abyss."), Editable]
|
||||
public bool CreateHoleToAbyss
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1000, true, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)]
|
||||
public int LevelObjectAmount
|
||||
{
|
||||
@@ -404,7 +411,35 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(300000, true, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
[Serialize(5, true), Editable(MinValueInt = 0, MaxValueInt = 20)]
|
||||
public int AbyssIslandCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("4000,7000", true), Editable]
|
||||
public Point AbyssIslandSizeMin
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("8000,10000", true), Editable]
|
||||
public Point AbyssIslandSizeMax
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.5f, true), Editable()]
|
||||
public float AbyssIslandCaveProbability
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-300000, true, description: "How far below the level the sea floor is placed."), Editable(MinValueFloat = Level.MaxEntityDepth, MaxValueFloat = 0.0f)]
|
||||
public int SeaFloorDepth
|
||||
{
|
||||
get { return seaFloorBaseDepth; }
|
||||
@@ -554,7 +589,7 @@ namespace Barotrauma
|
||||
var matchingLevelParams = LevelParams.FindAll(lp => lp.Type == type && lp.allowedBiomes.Any());
|
||||
if (biome == null)
|
||||
{
|
||||
matchingLevelParams = matchingLevelParams.FindAll(lp => !lp.allowedBiomes.Any(b => b.IsEndBiome));
|
||||
matchingLevelParams = matchingLevelParams.FindAll(lp => !lp.allowedBiomes.All(b => b.IsEndBiome));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -101,6 +101,7 @@ namespace Barotrauma
|
||||
sl.filePath = filePath;
|
||||
sl.saveElement = doc.Root;
|
||||
sl.saveElement.Name = "LinkedSubmarine";
|
||||
sl.saveElement.SetAttributeValue("filepath", filePath);
|
||||
|
||||
return sl;
|
||||
}
|
||||
@@ -183,10 +184,10 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
string levelSeed = element.GetAttributeString("location", "");
|
||||
LevelData levelData = GameMain.GameSession.Campaign?.NextLevel ?? GameMain.GameSession.LevelData;
|
||||
LevelData levelData = GameMain.GameSession?.Campaign?.NextLevel ?? GameMain.GameSession?.LevelData;
|
||||
linkedSub = new LinkedSubmarine(submarine, idRemap.AssignMaxId())
|
||||
{
|
||||
purchasedLostShuttles = GameMain.GameSession.GameMode is CampaignMode campaign && campaign.PurchasedLostShuttles,
|
||||
purchasedLostShuttles = GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.PurchasedLostShuttles,
|
||||
saveElement = element
|
||||
};
|
||||
|
||||
@@ -236,6 +237,11 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Failed to load a linked submarine (empty XML element). The save file may be corrupted.");
|
||||
return;
|
||||
}
|
||||
if (!info.SubmarineElement.Elements().Any(e => e.Name.ToString().Equals("hull", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to load a linked submarine (the submarine contains no hulls).");
|
||||
return;
|
||||
}
|
||||
|
||||
IdRemap parentRemap = new IdRemap(Submarine.Info.SubmarineElement, Submarine.IdOffset);
|
||||
sub = Submarine.Load(info, false, parentRemap);
|
||||
@@ -297,7 +303,7 @@ namespace Barotrauma
|
||||
{
|
||||
originalMyPortID = myPort.Item.ID;
|
||||
|
||||
myPort.Undock();
|
||||
myPort.Undock(applyEffects: false);
|
||||
myPort.DockingDir = 0;
|
||||
|
||||
//something else is already docked to the port this sub should be docked to
|
||||
@@ -321,8 +327,8 @@ namespace Barotrauma
|
||||
|
||||
sub.SetPosition((linkedPort.Item.WorldPosition - portDiff) - offset);
|
||||
|
||||
myPort.Dock(linkedPort);
|
||||
myPort.Lock(true);
|
||||
myPort.Dock(linkedPort);
|
||||
myPort.Lock(isNetworkMessage: true, applyEffects: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +372,11 @@ namespace Barotrauma
|
||||
}
|
||||
saveElement.Name = "LinkedSubmarine";
|
||||
|
||||
if (saveElement.Attribute("previewimage") != null)
|
||||
{
|
||||
saveElement.Attribute("previewimage").Remove();
|
||||
}
|
||||
|
||||
if (saveElement.Attribute("pos") != null) { saveElement.Attribute("pos").Remove(); }
|
||||
saveElement.Add(new XAttribute("pos", XMLExtensions.Vector2ToString(Position - Submarine.HiddenSubPosition)));
|
||||
|
||||
@@ -392,14 +403,14 @@ namespace Barotrauma
|
||||
bool leaveBehind = false;
|
||||
if (!sub.DockedTo.Contains(Submarine.MainSub))
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Submarine.MainSub.AtEndPosition || Submarine.MainSub.AtStartPosition);
|
||||
if (Submarine.MainSub.AtEndPosition)
|
||||
System.Diagnostics.Debug.Assert(Submarine.MainSub.AtEndExit || Submarine.MainSub.AtStartExit);
|
||||
if (Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
leaveBehind = sub.AtEndPosition != Submarine.MainSub.AtEndPosition;
|
||||
leaveBehind = sub.AtEndExit != Submarine.MainSub.AtEndExit;
|
||||
}
|
||||
else
|
||||
{
|
||||
leaveBehind = sub.AtStartPosition != Submarine.MainSub.AtStartPosition;
|
||||
leaveBehind = sub.AtStartExit != Submarine.MainSub.AtStartExit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@ namespace Barotrauma
|
||||
{
|
||||
OriginalContainerID = item.OriginalContainerID;
|
||||
}
|
||||
|
||||
OriginalID = item.ID;
|
||||
ModuleIndex = (ushort)item.OriginalModuleIndex;
|
||||
ModuleIndex = (ushort) item.OriginalModuleIndex;
|
||||
Identifier = item.prefab.Identifier;
|
||||
}
|
||||
|
||||
@@ -42,6 +43,7 @@ namespace Barotrauma
|
||||
{
|
||||
return obj.OriginalID == OriginalID && obj.OriginalContainerID == OriginalContainerID && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
|
||||
}
|
||||
|
||||
public bool Matches(Item item)
|
||||
{
|
||||
if (item.OriginalContainerID != Entity.NullEntityID)
|
||||
@@ -56,15 +58,17 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public readonly List<LocationConnection> Connections = new List<LocationConnection>();
|
||||
|
||||
|
||||
private string baseName;
|
||||
private int nameFormatIndex;
|
||||
|
||||
private LocationType addInitialMissionsForType;
|
||||
|
||||
public bool Discovered;
|
||||
|
||||
public readonly Dictionary<LocationTypeChange, int> ProximityTimer = new Dictionary<LocationTypeChange, int>();
|
||||
|
||||
public Pair<LocationTypeChange, int> PendingLocationTypeChange;
|
||||
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
|
||||
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
|
||||
public int LocationTypeChangeCooldown;
|
||||
|
||||
public string BaseName { get => baseName; }
|
||||
|
||||
@@ -76,12 +80,16 @@ namespace Barotrauma
|
||||
|
||||
public LocationType Type { get; private set; }
|
||||
|
||||
public LocationType OriginalType { get; private set; }
|
||||
|
||||
public LevelData LevelData { get; set; }
|
||||
|
||||
public int PortraitId { get; private set; }
|
||||
|
||||
public Reputation Reputation { get; set; }
|
||||
|
||||
public int TurnsInRadiation { get; set; }
|
||||
|
||||
#region Store
|
||||
|
||||
private const float StoreMaxReputationModifier = 0.1f;
|
||||
@@ -168,12 +176,11 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
availableMissions.RemoveAll(m => m.Completed || m.Failed);
|
||||
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry));
|
||||
return availableMissions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Mission SelectedMission
|
||||
{
|
||||
get;
|
||||
@@ -216,6 +223,8 @@ namespace Barotrauma
|
||||
|
||||
public int TimeSinceLastTypeChange;
|
||||
|
||||
public bool IsGateBetweenBiomes;
|
||||
|
||||
private struct LoadedMission
|
||||
{
|
||||
public MissionPrefab MissionPrefab { get; }
|
||||
@@ -239,38 +248,64 @@ namespace Barotrauma
|
||||
return $"Location ({Name ?? "null"})";
|
||||
}
|
||||
|
||||
public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, IEnumerable<Location> existingLocations = null)
|
||||
public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, LocationType? forceLocationType = null, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
Type = LocationType.Random(rand, zone, requireOutpost);
|
||||
Type = OriginalType = forceLocationType ?? LocationType.Random(rand, zone, requireOutpost);
|
||||
Name = RandomName(Type, rand, existingLocations);
|
||||
MapPosition = mapPosition;
|
||||
PortraitId = ToolBox.StringToInt(Name);
|
||||
Connections = new List<LocationConnection>();
|
||||
Connections = new List<LocationConnection>();
|
||||
}
|
||||
|
||||
public Location(XElement element)
|
||||
{
|
||||
string locationType = element.GetAttributeString("type", "");
|
||||
Type = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase));
|
||||
bool typeNotFound = false;
|
||||
if (Type == null)
|
||||
{
|
||||
//turn lairs into abandoned outposts
|
||||
if (locationType.Equals("lair", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Type ??= LocationType.List.Find(lt => lt.Identifier.Equals("Abandoned", StringComparison.OrdinalIgnoreCase));
|
||||
addInitialMissionsForType = Type;
|
||||
}
|
||||
if (Type == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find location type \"{locationType}\". Using location type \"None\" instead.");
|
||||
Type ??= LocationType.List.Find(lt => lt.Identifier.Equals("None", StringComparison.OrdinalIgnoreCase));
|
||||
Type ??= LocationType.List.First();
|
||||
}
|
||||
if (Type != null)
|
||||
{
|
||||
element.SetAttributeValue("type", Type.Identifier);
|
||||
}
|
||||
typeNotFound = true;
|
||||
}
|
||||
|
||||
string originalLocationType = element.GetAttributeString("originaltype", locationType);
|
||||
OriginalType = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
baseName = element.GetAttributeString("basename", "");
|
||||
Name = element.GetAttributeString("name", "");
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
Discovered = element.GetAttributeBool("discovered", false);
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
TimeSinceLastTypeChange = element.GetAttributeInt("timesincelasttypechange", 0);
|
||||
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
|
||||
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
if (!typeNotFound)
|
||||
{
|
||||
ProximityTimer.Add(Type.CanChangeTo[i], element.GetAttributeInt("proximitytimer" + i, 0));
|
||||
}
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < Type.CanChangeTo[i].Requirements.Count; j++)
|
||||
{
|
||||
ProximityTimer.Add(Type.CanChangeTo[i].Requirements[j], element.GetAttributeInt("proximitytimer" + i + "-" + j, 0));
|
||||
}
|
||||
}
|
||||
|
||||
int locationTypeChangeIndex = element.GetAttributeInt("pendinglocationtypechange", -1);
|
||||
if (locationTypeChangeIndex > 0 && locationTypeChangeIndex < Type.CanChangeTo.Count - 1)
|
||||
{
|
||||
PendingLocationTypeChange = new Pair<LocationTypeChange, int>(
|
||||
Type.CanChangeTo[locationTypeChangeIndex],
|
||||
element.GetAttributeInt("pendinglocationtypechangetimer", 0));
|
||||
LoadLocationTypeChange(element);
|
||||
}
|
||||
|
||||
string[] takenItemStr = element.GetAttributeStringArray("takenitems", new string[0]);
|
||||
@@ -316,6 +351,42 @@ namespace Barotrauma
|
||||
LoadMissions(element);
|
||||
}
|
||||
|
||||
public void LoadLocationTypeChange(XElement locationElement)
|
||||
{
|
||||
TimeSinceLastTypeChange = locationElement.GetAttributeInt("timesincelasttypechange", 0);
|
||||
LocationTypeChangeCooldown = locationElement.GetAttributeInt("locationtypechangecooldown", 0);
|
||||
foreach (XElement subElement in locationElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "pendinglocationtypechange":
|
||||
int timer = subElement.GetAttributeInt("timer", 0);
|
||||
if (subElement.Attribute("index") != null)
|
||||
{
|
||||
int locationTypeChangeIndex = subElement.GetAttributeInt("index", 0);
|
||||
if (locationTypeChangeIndex < 0 || locationTypeChangeIndex >= Type.CanChangeTo.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to activate a location type change in the location \"{Name}\". Location index out of bounds ({locationTypeChangeIndex}).");
|
||||
continue;
|
||||
}
|
||||
PendingLocationTypeChange = (Type.CanChangeTo[locationTypeChangeIndex], timer, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
string missionIdentifier = subElement.GetAttributeString("missionidentifier", "");
|
||||
var mission = MissionPrefab.List.Find(mp => mp.Identifier.Equals(missionIdentifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (mission == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to activate a location type change from the mission \"{missionIdentifier}\" in location \"{Name}\". Matching mission not found.");
|
||||
continue;
|
||||
}
|
||||
PendingLocationTypeChange = (mission.LocationTypeChangeOnCompleted, timer, mission);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadMissions(XElement locationElement)
|
||||
{
|
||||
if (locationElement.GetChildElement("missions") is XElement missionsElement)
|
||||
@@ -335,9 +406,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
public static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, IEnumerable<Location> existingLocations = null)
|
||||
public static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, LocationType? forceLocationType = null, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
return new Location(position, zone, rand, requireOutpost, existingLocations);
|
||||
return new Location(position, zone, rand, requireOutpost, forceLocationType, existingLocations);
|
||||
}
|
||||
|
||||
public void ChangeType(LocationType newType)
|
||||
@@ -348,6 +419,16 @@ namespace Barotrauma
|
||||
|
||||
Type = newType;
|
||||
Name = Type.NameFormats == null ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
|
||||
if (Type.MissionIdentifiers.Any())
|
||||
{
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom());
|
||||
}
|
||||
if (Type.MissionTags.Any())
|
||||
{
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandom());
|
||||
}
|
||||
|
||||
CreateStore(force: true);
|
||||
}
|
||||
|
||||
@@ -361,6 +442,16 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UnlockMission(MissionPrefab missionPrefab)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
|
||||
var mission = InstantiateMission(missionPrefab);
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
}
|
||||
|
||||
public MissionPrefab UnlockMissionByIdentifier(string identifier)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase))) { return null; }
|
||||
@@ -399,12 +490,12 @@ namespace Barotrauma
|
||||
var unusedMissions = matchingMissions.Where(m => !availableMissions.Any(mission => mission.Prefab == m));
|
||||
if (unusedMissions.Any())
|
||||
{
|
||||
var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this))));
|
||||
var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this)) || m.IsAllowed(this, this)));
|
||||
if (!suitableMissions.Any())
|
||||
{
|
||||
suitableMissions = unusedMissions;
|
||||
}
|
||||
MissionPrefab missionPrefab = suitableMissions.GetRandom();
|
||||
MissionPrefab missionPrefab = ToolBox.SelectWeightedRandom(suitableMissions.ToList(), suitableMissions.Select(m => (float)m.Commonness).ToList(), Rand.RandSync.Unsynced);
|
||||
var mission = InstantiateMission(missionPrefab, out LocationConnection connection);
|
||||
//don't allow duplicate missions in the same connection
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
|
||||
@@ -428,6 +519,12 @@ namespace Barotrauma
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab, out LocationConnection connection)
|
||||
{
|
||||
if (prefab.IsAllowed(this, this))
|
||||
{
|
||||
connection = null;
|
||||
return InstantiateMission(prefab);
|
||||
}
|
||||
|
||||
var suitableConnections = Connections.Where(c => prefab.IsAllowed(this, c.OtherLocation(this)));
|
||||
if (!suitableConnections.Any())
|
||||
{
|
||||
@@ -439,10 +536,7 @@ namespace Barotrauma
|
||||
suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
|
||||
Location destination = connection.OtherLocation(this);
|
||||
var mission = prefab.Instantiate(new Location[] { this, destination });
|
||||
mission.AdjustLevelData(connection.LevelData);
|
||||
return mission;
|
||||
return InstantiateMission(prefab, connection);
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection)
|
||||
@@ -453,26 +547,47 @@ namespace Barotrauma
|
||||
return mission;
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab)
|
||||
{
|
||||
var mission = prefab.Instantiate(new Location[] { this, this });
|
||||
mission.AdjustLevelData(LevelData);
|
||||
return mission;
|
||||
}
|
||||
|
||||
public void InstantiateLoadedMissions(Map map)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
if (loadedMissions == null || loadedMissions.None()) { return; }
|
||||
foreach (LoadedMission loadedMission in loadedMissions)
|
||||
{
|
||||
Location destination = null;
|
||||
if (loadedMission.DestinationIndex >= 0 && loadedMission.DestinationIndex < map.Locations.Count)
|
||||
if (loadedMissions != null && loadedMissions.Any())
|
||||
{
|
||||
foreach (LoadedMission loadedMission in loadedMissions)
|
||||
{
|
||||
destination = map.Locations[loadedMission.DestinationIndex];
|
||||
Location destination;
|
||||
if (loadedMission.DestinationIndex >= 0 && loadedMission.DestinationIndex < map.Locations.Count)
|
||||
{
|
||||
destination = map.Locations[loadedMission.DestinationIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
destination = Connections.First().OtherLocation(this);
|
||||
}
|
||||
var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination });
|
||||
availableMissions.Add(mission);
|
||||
if (loadedMission.SelectedMission) { SelectedMission = mission; }
|
||||
}
|
||||
else
|
||||
{
|
||||
destination = Connections.First().OtherLocation(this);
|
||||
}
|
||||
var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination });
|
||||
availableMissions.Add(mission);
|
||||
if (loadedMission.SelectedMission) { SelectedMission = mission; }
|
||||
loadedMissions = null;
|
||||
}
|
||||
if (addInitialMissionsForType != null)
|
||||
{
|
||||
if (addInitialMissionsForType.MissionIdentifiers.Any())
|
||||
{
|
||||
UnlockMissionByIdentifier(addInitialMissionsForType.MissionIdentifiers.GetRandom());
|
||||
}
|
||||
if (addInitialMissionsForType.MissionTags.Any())
|
||||
{
|
||||
UnlockMissionByTag(addInitialMissionsForType.MissionTags.GetRandom());
|
||||
}
|
||||
addInitialMissionsForType = null;
|
||||
}
|
||||
loadedMissions = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -484,6 +599,33 @@ namespace Barotrauma
|
||||
SelectedMissionIndex = -1;
|
||||
}
|
||||
|
||||
public bool HasOutpost()
|
||||
{
|
||||
if (!Type.HasOutpost) { return false; }
|
||||
|
||||
return !IsCriticallyRadiated();
|
||||
}
|
||||
|
||||
public bool IsCriticallyRadiated()
|
||||
{
|
||||
if (GameMain.GameSession?.Map?.Radiation != null)
|
||||
{
|
||||
return TurnsInRadiation > GameMain.GameSession.Map.Radiation.Params.CriticalRadiationThreshold;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public LocationType GetLocationType()
|
||||
{
|
||||
if (IsCriticallyRadiated() && LocationType.List.FirstOrDefault(lt => lt.Identifier.Equals(Type.ReplaceInRadiation, StringComparison.OrdinalIgnoreCase)) is { } newLocationType)
|
||||
{
|
||||
return newLocationType;
|
||||
}
|
||||
|
||||
return Type;
|
||||
}
|
||||
|
||||
public IEnumerable<Mission> GetMissionsInConnection(LocationConnection connection)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Connections.Contains(connection));
|
||||
@@ -583,6 +725,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRadiated() => GameMain.GameSession?.Map?.Radiation != null && GameMain.GameSession.Map.Radiation.Enabled && GameMain.GameSession.Map.Radiation.Contains(this);
|
||||
|
||||
private List<PurchasedItem> CreateStoreStock()
|
||||
{
|
||||
var stock = new List<PurchasedItem>();
|
||||
@@ -908,27 +1052,55 @@ namespace Barotrauma
|
||||
{
|
||||
var locationElement = new XElement("location",
|
||||
new XAttribute("type", Type.Identifier),
|
||||
new XAttribute("originaltype", (Type ?? OriginalType).Identifier),
|
||||
new XAttribute("basename", BaseName),
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("isgatebetweenbiomes", IsGateBetweenBiomes),
|
||||
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier),
|
||||
new XAttribute("timesincelasttypechange", TimeSinceLastTypeChange));
|
||||
new XAttribute("timesincelasttypechange", TimeSinceLastTypeChange),
|
||||
new XAttribute(nameof(TurnsInRadiation).ToLower(), TurnsInRadiation));
|
||||
LevelData.Save(locationElement);
|
||||
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
if (ProximityTimer.ContainsKey(Type.CanChangeTo[i]))
|
||||
for (int j = 0; j < Type.CanChangeTo[i].Requirements.Count; j++)
|
||||
{
|
||||
locationElement.Add(new XAttribute("proximitytimer" + i, ProximityTimer[Type.CanChangeTo[i]]));
|
||||
if (ProximityTimer.ContainsKey(Type.CanChangeTo[i].Requirements[j]))
|
||||
{
|
||||
locationElement.Add(new XAttribute("proximitytimer" + i + "-" + j, ProximityTimer[Type.CanChangeTo[i].Requirements[j]]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PendingLocationTypeChange != null)
|
||||
if (PendingLocationTypeChange.HasValue)
|
||||
{
|
||||
locationElement.Add(new XAttribute("pendinglocationtypechange", Type.CanChangeTo.IndexOf(PendingLocationTypeChange.First)));
|
||||
locationElement.Add(new XAttribute("pendinglocationtypechangetimer", PendingLocationTypeChange.Second));
|
||||
var changeElement = new XElement("pendinglocationtypechange", new XAttribute("timer", PendingLocationTypeChange.Value.delay));
|
||||
if (PendingLocationTypeChange.Value.parentMission != null)
|
||||
{
|
||||
changeElement.Add(new XAttribute("missionidentifier", PendingLocationTypeChange.Value.parentMission.Identifier));
|
||||
locationElement.Add(changeElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = Type.CanChangeTo.IndexOf(PendingLocationTypeChange.Value.typeChange);
|
||||
changeElement.Add(new XAttribute("index", index));
|
||||
if (index == -1)
|
||||
{
|
||||
DebugConsole.AddWarning($"Invalid location type change in the location \"{Name}\". Unknown type change ({PendingLocationTypeChange.Value.typeChange.ChangeToType}).");
|
||||
}
|
||||
else
|
||||
{
|
||||
locationElement.Add(changeElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LocationTypeChangeCooldown > 0)
|
||||
{
|
||||
locationElement.Add(new XAttribute("locationtypechangecooldown", LocationTypeChangeCooldown));
|
||||
}
|
||||
|
||||
if (takenItems.Any())
|
||||
@@ -987,7 +1159,7 @@ namespace Barotrauma
|
||||
var missionsElement = new XElement("missions");
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
var location = mission.Locations.FirstOrDefault(l => l != this);
|
||||
var location = mission.Locations.All(l => l == this) ? this : mission.Locations.FirstOrDefault(l => l != this);
|
||||
var i = map.Locations.IndexOf(location);
|
||||
missionsElement.Add(new XElement("mission",
|
||||
new XAttribute("prefabid", mission.Prefab.Identifier),
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace Barotrauma
|
||||
|
||||
public bool Passed;
|
||||
|
||||
public bool Locked;
|
||||
|
||||
public LevelData LevelData { get; set; }
|
||||
|
||||
public Vector2 CenterPos
|
||||
@@ -32,6 +34,16 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly List<Mission> availableMissions = new List<Mission>();
|
||||
public IEnumerable<Mission> AvailableMissions
|
||||
{
|
||||
get
|
||||
{
|
||||
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry));
|
||||
return availableMissions;
|
||||
}
|
||||
}
|
||||
|
||||
public LocationConnection(Location location1, Location location2)
|
||||
{
|
||||
if (location1 == null)
|
||||
|
||||
@@ -13,17 +13,12 @@ namespace Barotrauma
|
||||
class LocationType
|
||||
{
|
||||
public static readonly List<LocationType> List = new List<LocationType>();
|
||||
|
||||
private readonly List<string> nameFormats;
|
||||
private readonly List<string> names;
|
||||
|
||||
private readonly Sprite symbolSprite;
|
||||
|
||||
private readonly List<Sprite> portraits = new List<Sprite>();
|
||||
|
||||
//<name, commonness>
|
||||
private List<Tuple<JobPrefab, float>> hireableJobs;
|
||||
private float totalHireableWeight;
|
||||
private readonly List<Tuple<JobPrefab, float>> hireableJobs;
|
||||
private readonly float totalHireableWeight;
|
||||
|
||||
public Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
|
||||
|
||||
@@ -32,18 +27,24 @@ namespace Barotrauma
|
||||
|
||||
public readonly float BeaconStationChance;
|
||||
|
||||
public readonly CharacterTeamType OutpostTeam;
|
||||
|
||||
public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
|
||||
|
||||
public readonly List<string> MissionIdentifiers = new List<string>();
|
||||
public readonly List<string> MissionTags = new List<string>();
|
||||
|
||||
public readonly List<string> HideEntitySubcategories = new List<string>();
|
||||
|
||||
public bool IsEnterable { get; private set; }
|
||||
|
||||
public bool UseInMainMenu
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<string> NameFormats
|
||||
{
|
||||
get { return nameFormats; }
|
||||
}
|
||||
|
||||
public List<string> NameFormats { get; private set; }
|
||||
|
||||
public bool HasHireableCharacters
|
||||
{
|
||||
@@ -55,11 +56,11 @@ namespace Barotrauma
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string ReplaceInRadiation { get; }
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return symbolSprite; }
|
||||
}
|
||||
public Sprite Sprite { get; private set; }
|
||||
public Sprite RadiationSprite { get; }
|
||||
|
||||
public Color SpriteColor
|
||||
{
|
||||
@@ -79,9 +80,20 @@ namespace Barotrauma
|
||||
|
||||
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
|
||||
|
||||
nameFormats = TextManager.GetAll("LocationNameFormat." + Identifier);
|
||||
NameFormats = TextManager.GetAll("LocationNameFormat." + Identifier);
|
||||
UseInMainMenu = element.GetAttributeBool("useinmainmenu", false);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
|
||||
|
||||
MissionIdentifiers = element.GetAttributeStringArray("missionidentifiers", new string[0]).ToList();
|
||||
MissionTags = element.GetAttributeStringArray("missiontags", new string[0]).ToList();
|
||||
|
||||
HideEntitySubcategories = element.GetAttributeStringArray("hideentitysubcategories", new string[0]).ToList();
|
||||
|
||||
ReplaceInRadiation = element.GetAttributeString(nameof(ReplaceInRadiation).ToLower(), "");
|
||||
|
||||
string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
|
||||
Enum.TryParse(teamStr, out OutpostTeam);
|
||||
|
||||
string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt");
|
||||
try
|
||||
@@ -135,11 +147,14 @@ namespace Barotrauma
|
||||
hireableJobs.Add(hireableJob);
|
||||
break;
|
||||
case "symbol":
|
||||
symbolSprite = new Sprite(subElement, lazyLoad: true);
|
||||
Sprite = new Sprite(subElement, lazyLoad: true);
|
||||
SpriteColor = subElement.GetAttributeColor("color", Color.White);
|
||||
break;
|
||||
case "radiationsymbol":
|
||||
RadiationSprite = new Sprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
case "changeto":
|
||||
CanChangeTo.Add(new LocationTypeChange(Identifier, subElement));
|
||||
CanChangeTo.Add(new LocationTypeChange(Identifier, subElement, requireChangeMessages: true));
|
||||
break;
|
||||
case "portrait":
|
||||
var portrait = new Sprite(subElement, lazyLoad: true);
|
||||
|
||||
@@ -8,36 +8,136 @@ namespace Barotrauma
|
||||
{
|
||||
class LocationTypeChange
|
||||
{
|
||||
public class Requirement
|
||||
{
|
||||
public enum FunctionType
|
||||
{
|
||||
Add,
|
||||
Multiply
|
||||
}
|
||||
|
||||
public readonly FunctionType Function;
|
||||
|
||||
/// <summary>
|
||||
/// The change can only happen if there's at least one of the given types of locations near this one
|
||||
/// </summary>
|
||||
public readonly List<string> RequiredLocations;
|
||||
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the RequiredLocations for the change to occur
|
||||
/// </summary>
|
||||
public readonly int RequiredProximity;
|
||||
|
||||
/// <summary>
|
||||
/// Base probability per turn for the location to change if near one of the RequiredLocations
|
||||
/// </summary>
|
||||
public readonly float Probability;
|
||||
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the RequiredLocations for the probability to increase
|
||||
/// </summary>
|
||||
public readonly int RequiredProximityForProbabilityIncrease;
|
||||
|
||||
/// <summary>
|
||||
/// How much the probability increases per turn if within RequiredProximityForProbabilityIncrease steps of RequiredLocations
|
||||
/// </summary>
|
||||
public readonly float ProximityProbabilityIncrease;
|
||||
|
||||
/// <summary>
|
||||
/// Does there need to be a beacon station within RequiredProximity
|
||||
/// </summary>
|
||||
public readonly bool RequireBeaconStation;
|
||||
|
||||
/// <summary>
|
||||
/// Does there need to be hunting grounds within RequiredProximity
|
||||
/// </summary>
|
||||
public readonly bool RequireHuntingGrounds;
|
||||
|
||||
public Requirement(XElement element, LocationTypeChange change)
|
||||
{
|
||||
RequiredLocations = element.GetAttributeStringArray("requiredlocations", element.GetAttributeStringArray("requiredadjacentlocations", new string[0])).ToList();
|
||||
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 1);
|
||||
ProximityProbabilityIncrease = element.GetAttributeFloat("proximityprobabilityincrease", 0.0f);
|
||||
RequiredProximityForProbabilityIncrease = element.GetAttributeInt("requiredproximityforprobabilityincrease", -1);
|
||||
RequireBeaconStation = element.GetAttributeBool("requirebeaconstation", false);
|
||||
RequireHuntingGrounds = element.GetAttributeBool("requirehuntinggrounds", false);
|
||||
|
||||
string functionStr = element.GetAttributeString("function", "Add");
|
||||
if (!Enum.TryParse(functionStr, ignoreCase: true, out Function))
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Invalid location type change in location type \"{change.CurrentType}\". " +
|
||||
$"\"{functionStr}\" is not a valid function.");
|
||||
}
|
||||
|
||||
Probability = element.GetAttributeFloat("probability", 1.0f);
|
||||
|
||||
if (RequiredProximityForProbabilityIncrease > 0 || ProximityProbabilityIncrease > 0.0f)
|
||||
{
|
||||
if (!RequiredLocations.Any() && !RequireBeaconStation && !RequireHuntingGrounds)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{change.CurrentType}\". " +
|
||||
"Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set.");
|
||||
}
|
||||
if (Probability >= 1.0f)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{change.CurrentType}\". " +
|
||||
"Probability is configured to increase when near some other type of location, but the base probability is already 100%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesLocation(Location location)
|
||||
{
|
||||
return RequiredLocations.Contains(location.Type.Identifier) && !location.IsCriticallyRadiated();
|
||||
}
|
||||
|
||||
public bool AnyWithinDistance(Location location, int maxDistance, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > maxDistance) { return false; }
|
||||
if (currentDistance > 0 && MatchesLocation(location)) { return true; }
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
foreach (var connection in location.Connections)
|
||||
{
|
||||
if (RequireBeaconStation && connection.LevelData.HasBeaconStation && connection.LevelData.IsBeaconActive)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (RequireHuntingGrounds && connection.LevelData.HasHuntingGrounds)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation))
|
||||
{
|
||||
if (AnyWithinDistance(otherLocation, maxDistance, currentDistance + 1, checkedLocations)) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string CurrentType;
|
||||
|
||||
public readonly string ChangeToType;
|
||||
|
||||
public readonly bool RequireDiscovered;
|
||||
|
||||
public List<string> Messages = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The change can only happen if there's at least one of the given types of locations near this one
|
||||
/// </summary>
|
||||
public readonly List<string> RequiredLocations;
|
||||
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the RequiredLocations for the change to occur
|
||||
/// </summary>
|
||||
public readonly int RequiredProximity;
|
||||
|
||||
/// <summary>
|
||||
/// Base probability per turn for the location to change if near one of the RequiredLocations
|
||||
/// </summary>
|
||||
public readonly float Probability;
|
||||
|
||||
/// <summary>
|
||||
/// How close the location needs to be to one of the RequiredLocations for the probability to increase
|
||||
/// </summary>
|
||||
public readonly int RequiredProximityForProbabilityIncrease;
|
||||
public readonly bool RequireDiscovered;
|
||||
|
||||
/// <summary>
|
||||
/// How much the probability increases per turn if within RequiredProximityForProbabilityIncrease steps of RequiredLocations
|
||||
/// </summary>
|
||||
public readonly float ProximityProbabilityIncrease;
|
||||
public List<Requirement> Requirements = new List<Requirement>();
|
||||
|
||||
public List<string> Messages = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The change can't happen if there's one or more of the given types of locations near this one
|
||||
@@ -49,41 +149,35 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly int DisallowedProximity;
|
||||
|
||||
/// <summary>
|
||||
/// The location can't change it's type for this many turns after this location type changes occurs
|
||||
/// </summary>
|
||||
public readonly int CooldownAfterChange;
|
||||
|
||||
public readonly Point RequiredDurationRange;
|
||||
|
||||
public LocationTypeChange(string currentType, XElement element)
|
||||
public LocationTypeChange(string currentType, XElement element, bool requireChangeMessages, float defaultProbability = 0.0f)
|
||||
{
|
||||
ChangeToType = element.GetAttributeString("type", "");
|
||||
Probability = element.GetAttributeFloat("probability", 1.0f);
|
||||
CurrentType = currentType;
|
||||
ChangeToType = element.GetAttributeString("type", element.GetAttributeString("to", ""));
|
||||
|
||||
RequireDiscovered = element.GetAttributeBool("requirediscovered", false);
|
||||
|
||||
RequiredLocations = element.GetAttributeStringArray("requiredlocations", element.GetAttributeStringArray("requiredadjacentlocations", new string[0])).ToList();
|
||||
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 1);
|
||||
ProximityProbabilityIncrease = element.GetAttributeFloat("proximityprobabilityincrease", 0.0f);
|
||||
RequiredProximityForProbabilityIncrease = element.GetAttributeInt("requiredproximityforprobabilityincrease", -1);
|
||||
|
||||
|
||||
if (RequiredProximityForProbabilityIncrease > 0 || ProximityProbabilityIncrease > 0.0f)
|
||||
{
|
||||
if (!RequiredLocations.Any())
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{currentType}\". "+
|
||||
"Probability is configured to increase when near some other type of location, but the RequiredLocations attribute is not set.");
|
||||
}
|
||||
if (Probability >= 1.0f)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Invalid location type change in location type \"{currentType}\". " +
|
||||
"Probability is configured to increase when near some other type of location, but the base probability is already 100%");
|
||||
}
|
||||
}
|
||||
|
||||
DisallowedAdjacentLocations = element.GetAttributeStringArray("disallowedadjacentlocations", new string[0]).ToList();
|
||||
DisallowedProximity = Math.Max(element.GetAttributeInt("disallowedproximity", 1), 1);
|
||||
|
||||
RequiredDurationRange = element.GetAttributePoint("requireddurationrange", Point.Zero);
|
||||
|
||||
Probability = element.GetAttributeFloat("probability", defaultProbability);
|
||||
|
||||
CooldownAfterChange = Math.Max(element.GetAttributeInt("cooldownafterchange", 0), 0);
|
||||
|
||||
//backwards compatibility
|
||||
if (element.Attribute("requiredlocations") != null)
|
||||
{
|
||||
Requirements.Add(new Requirement(element, this));
|
||||
}
|
||||
|
||||
//backwards compatibility
|
||||
if (element.Attribute("requiredduration") != null)
|
||||
{
|
||||
@@ -95,34 +189,70 @@ namespace Barotrauma
|
||||
Messages = TextManager.GetAll(messageTag);
|
||||
if (Messages == null)
|
||||
{
|
||||
DebugConsole.ThrowError("No messages defined for the location type change " + currentType + " -> " + ChangeToType);
|
||||
if (requireChangeMessages)
|
||||
{
|
||||
DebugConsole.ThrowError("No messages defined for the location type change " + currentType + " -> " + ChangeToType);
|
||||
}
|
||||
Messages = new List<string>();
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("requirement", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Requirements.Add(new Requirement(subElement, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float DetermineProbability(Location location)
|
||||
{
|
||||
if (RequireDiscovered && !location.Discovered) { return 0.0f; }
|
||||
if (location.IsCriticallyRadiated()) { return 0.0f; }
|
||||
if (location.LocationTypeChangeCooldown > 0) { return 0.0f; }
|
||||
if (location.IsGateBetweenBiomes) { return 0.0f; }
|
||||
|
||||
if (RequiredLocations.Any() && !AnyWithinDistance(location, RequiredProximity, (otherLocation) => { return RequiredLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
if (DisallowedAdjacentLocations.Any() && AnyWithinDistance(location, DisallowedProximity, (otherLocation) => { return DisallowedAdjacentLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
if (DisallowedAdjacentLocations.Any() &&
|
||||
AnyWithinDistance(location, DisallowedProximity, (otherLocation) => { return DisallowedAdjacentLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float probability = Probability;
|
||||
if (location.ProximityTimer.ContainsKey(this))
|
||||
foreach (Requirement requirement in Requirements)
|
||||
{
|
||||
if (AnyWithinDistance(location, RequiredProximityForProbabilityIncrease, (otherLocation) => { return RequiredLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
if (requirement.AnyWithinDistance(location, requirement.RequiredProximity))
|
||||
{
|
||||
return probability += ProximityProbabilityIncrease * location.ProximityTimer[this];
|
||||
if (requirement.Function == Requirement.FunctionType.Add)
|
||||
{
|
||||
probability += requirement.Probability;
|
||||
}
|
||||
else
|
||||
{
|
||||
probability *= requirement.Probability;
|
||||
}
|
||||
}
|
||||
|
||||
if (location.ProximityTimer.ContainsKey(requirement))
|
||||
{
|
||||
if (requirement.AnyWithinDistance(location, requirement.RequiredProximityForProbabilityIncrease))
|
||||
{
|
||||
if (requirement.Function == Requirement.FunctionType.Add)
|
||||
{
|
||||
probability += requirement.ProximityProbabilityIncrease * location.ProximityTimer[requirement];
|
||||
}
|
||||
else
|
||||
{
|
||||
probability *= requirement.ProximityProbabilityIncrease * location.ProximityTimer[requirement];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return probability;
|
||||
}
|
||||
|
||||
public bool AnyWithinDistance(Location location, int maxDistance, Func<Location, bool> predicate, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
private bool AnyWithinDistance(Location location, int maxDistance, Func<Location, bool> predicate, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > maxDistance) { return false; }
|
||||
if (currentDistance > 0 && predicate(location)) { return true; }
|
||||
@@ -141,22 +271,5 @@ namespace Barotrauma
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int CountWithinRequiredProximity(Location location, int currentDistance = 0, HashSet<Location> checkedLocations = null)
|
||||
{
|
||||
if (currentDistance > RequiredProximityForProbabilityIncrease) { return 0; }
|
||||
int count = currentDistance > 0 && RequiredLocations.Contains(location.Type.Identifier) ? 1 : 0;
|
||||
|
||||
checkedLocations ??= new HashSet<Location>();
|
||||
checkedLocations.Add(location);
|
||||
|
||||
foreach (var connection in location.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (!checkedLocations.Contains(otherLocation)) { count += CountWithinRequiredProximity(otherLocation, currentDistance+1, checkedLocations); }
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -15,8 +16,8 @@ namespace Barotrauma
|
||||
|
||||
private Location furthestDiscoveredLocation;
|
||||
|
||||
private int Width => generationParams.Width;
|
||||
private int Height => generationParams.Height;
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
|
||||
public Action<Location, LocationConnection> OnLocationSelected;
|
||||
/// <summary>
|
||||
@@ -56,20 +57,37 @@ namespace Barotrauma
|
||||
|
||||
public List<LocationConnection> Connections { get; private set; }
|
||||
|
||||
public Map()
|
||||
public Radiation Radiation;
|
||||
|
||||
public Map(CampaignSettings settings)
|
||||
{
|
||||
generationParams = MapGenerationParams.Instance;
|
||||
Width = generationParams.Width;
|
||||
Height = generationParams.Height;
|
||||
Locations = new List<Location>();
|
||||
Connections = new List<LocationConnection>();
|
||||
if (generationParams.RadiationParams != null)
|
||||
{
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams)
|
||||
{
|
||||
Enabled = settings.RadiationEnabled
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a previously saved campaign map from XML
|
||||
/// </summary>
|
||||
private Map(CampaignMode campaign, XElement element) : this()
|
||||
private Map(CampaignMode campaign, XElement element, CampaignSettings settings) : this(settings)
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "a");
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
|
||||
Width = element.GetAttributeInt("width", Width);
|
||||
Height = element.GetAttributeInt("height", Height);
|
||||
|
||||
bool lairsFound = false;
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -80,8 +98,15 @@ namespace Barotrauma
|
||||
{
|
||||
Locations.Add(null);
|
||||
}
|
||||
lairsFound |= subElement.GetAttributeString("type", "").Equals("lair", StringComparison.OrdinalIgnoreCase);
|
||||
Locations[i] = new Location(subElement);
|
||||
break;
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement)
|
||||
{
|
||||
Enabled = settings.RadiationEnabled
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(!Locations.Contains(null));
|
||||
@@ -90,6 +115,7 @@ namespace Barotrauma
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
|
||||
}
|
||||
|
||||
List<XElement> connectionElements = new List<XElement>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -100,6 +126,7 @@ namespace Barotrauma
|
||||
var connection = new LocationConnection(Locations[locationIndices.X], Locations[locationIndices.Y])
|
||||
{
|
||||
Passed = subElement.GetAttributeBool("passed", false),
|
||||
Locked = subElement.GetAttributeBool("locked", false),
|
||||
Difficulty = subElement.GetAttributeFloat("difficulty", 0.0f)
|
||||
};
|
||||
Locations[locationIndices.X].Connections.Add(connection);
|
||||
@@ -111,6 +138,7 @@ namespace Barotrauma
|
||||
LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.OldIdentifier == biomeId) ??
|
||||
LevelGenerationParams.GetBiomes().First();
|
||||
Connections.Add(connection);
|
||||
connectionElements.Add(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -149,13 +177,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility: if the map contained the now-removed lairs and has no hunting grounds, create some hunting grounds
|
||||
if (lairsFound && !Connections.Any(c => c.LevelData.HasHuntingGrounds))
|
||||
{
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
Connections[i].LevelData.HasHuntingGrounds = Rand.Range(0.0f, 1.0f) < Connections[i].Difficulty / 100.0f * maxHuntingGroundsProbability;
|
||||
connectionElements[i].SetAttributeValue("hashuntinggrounds", true);
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility: if locations go out of bounds (map saved with different generation parameters before width/height were included in the xml)
|
||||
float maxX = Locations.Select(l => l.MapPosition.X).Max();
|
||||
if (maxX > Width) { Width = (int)(maxX + 10); }
|
||||
float maxY = Locations.Select(l => l.MapPosition.Y).Max();
|
||||
if (maxY > Height) { Height = (int)(maxY + 10); }
|
||||
|
||||
InitProjectSpecific();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new campaign map from the seed
|
||||
/// </summary>
|
||||
public Map(CampaignMode campaign, string seed) : this()
|
||||
public Map(CampaignMode campaign, string seed, CampaignSettings settings) : this(settings)
|
||||
{
|
||||
Seed = seed;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
@@ -255,14 +300,14 @@ namespace Barotrauma
|
||||
int positionIndex = Rand.Int(1, Rand.RandSync.Server);
|
||||
|
||||
Vector2 position = points[positionIndex];
|
||||
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex];
|
||||
int zone = MathHelper.Clamp((int)Math.Floor(position.X / zoneWidth) + 1, 1, generationParams.DifficultyZones);
|
||||
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, Locations);
|
||||
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; }
|
||||
int zone = GetZoneIndex(position.X);
|
||||
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, existingLocations: Locations);
|
||||
Locations.Add(newLocations[i]);
|
||||
}
|
||||
|
||||
var newConnection = new LocationConnection(newLocations[0], newLocations[1]);
|
||||
Connections.Add(newConnection);
|
||||
Connections.Add(newConnection);
|
||||
}
|
||||
|
||||
//remove connections that are too short
|
||||
@@ -285,20 +330,13 @@ namespace Barotrauma
|
||||
if (connection2.Locations[1] == connection.Locations[0]) { connection2.Locations[1] = connection.Locations[1]; }
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<Location> connectedLocations = new HashSet<Location>();
|
||||
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.Locations[0].Connections.Add(connection);
|
||||
connection.Locations[1].Connections.Add(connection);
|
||||
|
||||
connectedLocations.Add(connection.Locations[0]);
|
||||
connectedLocations.Add(connection.Locations[1]);
|
||||
}
|
||||
|
||||
//remove orphans
|
||||
Locations.RemoveAll(c => !connectedLocations.Contains(c));
|
||||
|
||||
//remove locations that are too close to each other
|
||||
float minLocationDistanceSqr = generationParams.MinLocationDistance * generationParams.MinLocationDistance;
|
||||
for (int i = Locations.Count - 1; i >= 0; i--)
|
||||
@@ -350,6 +388,58 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
LocationConnection[] connectionsBetweenZones = new LocationConnection[generationParams.DifficultyZones];
|
||||
foreach (var connection in Connections)
|
||||
{
|
||||
int zone1 = GetZoneIndex(connection.Locations[0].MapPosition.X);
|
||||
int zone2 = GetZoneIndex(connection.Locations[1].MapPosition.X);
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 > zone2)
|
||||
{
|
||||
int temp = zone2;
|
||||
zone2 = zone1;
|
||||
zone1 = temp;
|
||||
}
|
||||
|
||||
if (connectionsBetweenZones[zone1] == null)
|
||||
{
|
||||
connectionsBetweenZones[zone1] = connection;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Abs(connection.CenterPos.Y - Height / 2) < Math.Abs(connectionsBetweenZones[zone1].CenterPos.Y - Height / 2))
|
||||
{
|
||||
connectionsBetweenZones[zone1] = connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = Connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
int zone1 = GetZoneIndex(Connections[i].Locations[0].MapPosition.X);
|
||||
int zone2 = GetZoneIndex(Connections[i].Locations[1].MapPosition.X);
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 == generationParams.DifficultyZones || zone2 == generationParams.DifficultyZones) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones.Contains(Connections[i]))
|
||||
{
|
||||
Connections.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
var leftMostLocation =
|
||||
Connections[i].Locations[0].MapPosition.X < Connections[i].Locations[1].MapPosition.X ?
|
||||
Connections[i].Locations[0] :
|
||||
Connections[i].Locations[1];
|
||||
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
leftMostLocation.ChangeType(LocationType.List.First(lt => lt.HasOutpost && !lt.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
leftMostLocation.IsGateBetweenBiomes = true;
|
||||
Connections[i].Locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
for (int i = location.Connections.Count - 1; i >= 0; i--)
|
||||
@@ -361,6 +451,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//remove orphans
|
||||
Locations.RemoveAll(l => !Connections.Any(c => c.Locations.Contains(l)));
|
||||
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
|
||||
@@ -371,7 +464,18 @@ namespace Barotrauma
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location);
|
||||
location.LevelData = new LevelData(location)
|
||||
{
|
||||
Difficulty = MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f)
|
||||
};
|
||||
if (location.Type.MissionIdentifiers.Any())
|
||||
{
|
||||
location.UnlockMissionByIdentifier(location.Type.MissionIdentifiers.GetRandom());
|
||||
}
|
||||
if (location.Type.MissionTags.Any())
|
||||
{
|
||||
location.UnlockMissionByTag(location.Type.MissionTags.GetRandom());
|
||||
}
|
||||
}
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
@@ -381,6 +485,12 @@ namespace Barotrauma
|
||||
|
||||
partial void GenerateLocationConnectionVisuals();
|
||||
|
||||
private int GetZoneIndex(float xPos)
|
||||
{
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
return MathHelper.Clamp((int)Math.Floor(xPos / zoneWidth) + 1, 1, generationParams.DifficultyZones);
|
||||
}
|
||||
|
||||
public Biome GetBiome(Vector2 mapPos)
|
||||
{
|
||||
return GetBiome(mapPos.X);
|
||||
@@ -412,7 +522,7 @@ namespace Barotrauma
|
||||
{
|
||||
allowedBiomes.Clear();
|
||||
allowedBiomes.AddRange(biomes.Where(b => b.AllowedZones.Contains(generationParams.DifficultyZones - i)));
|
||||
float zoneX = Width - zoneWidth * i;
|
||||
float zoneX = zoneWidth * (generationParams.DifficultyZones - i);
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
@@ -425,7 +535,7 @@ namespace Barotrauma
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
if (connection.Biome != null) { continue; }
|
||||
connection.Biome = connection.Locations[0].Biome;
|
||||
connection.Biome = connection.Locations[0].MapPosition.X > connection.Locations[1].MapPosition.X ? connection.Locations[0].Biome : connection.Locations[1].Biome;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Assert(Locations.All(l => l.Biome != null));
|
||||
@@ -558,10 +668,12 @@ namespace Barotrauma
|
||||
CurrentLocation.CreateStore();
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.CampaignMetadata is { } metadata)
|
||||
if (GameMain.GameSession is { Campaign: { CampaignMetadata: { } metadata } })
|
||||
{
|
||||
metadata.SetValue("campaign.location.id", CurrentLocationIndex);
|
||||
metadata.SetValue("campaign.location.name", CurrentLocation.Name);
|
||||
metadata.SetValue("campaign.location.biome", CurrentLocation.Biome?.Identifier ?? "null");
|
||||
metadata.SetValue("campaign.location.type", CurrentLocation.Type?.Identifier ?? "null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,9 +726,14 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SelectedLocation = Locations[index];
|
||||
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
|
||||
SelectedConnection =
|
||||
Connections.Find(c => c.Locations.Contains(GameMain.GameSession?.Campaign?.CurrentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ??
|
||||
Connections.Find(c => c.Locations.Contains(currentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ??
|
||||
Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
if (SelectedConnection?.Locked ?? false)
|
||||
{
|
||||
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
|
||||
@@ -632,12 +749,15 @@ namespace Barotrauma
|
||||
|
||||
SelectedLocation = location;
|
||||
SelectedConnection = Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
if (SelectedConnection?.Locked ?? false)
|
||||
{
|
||||
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
|
||||
public void SelectMission(int missionIndex)
|
||||
{
|
||||
if (SelectedConnection == null) { return; }
|
||||
if (CurrentLocation == null)
|
||||
{
|
||||
string errorMsg = "Failed to select a mission (current location not set).";
|
||||
@@ -647,11 +767,18 @@ namespace Barotrauma
|
||||
}
|
||||
CurrentLocation.SelectedMissionIndex = missionIndex;
|
||||
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (CurrentLocation.SelectedMission != null &&
|
||||
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
|
||||
if (CurrentLocation.SelectedMission == null) { return; }
|
||||
|
||||
if (CurrentLocation.SelectedMission.Locations[0] != CurrentLocation ||
|
||||
CurrentLocation.SelectedMission.Locations[1] != CurrentLocation)
|
||||
{
|
||||
CurrentLocation.SelectedMissionIndex = -1;
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (CurrentLocation.SelectedMission != null &&
|
||||
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
|
||||
{
|
||||
CurrentLocation.SelectedMissionIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission);
|
||||
@@ -659,7 +786,7 @@ namespace Barotrauma
|
||||
|
||||
public void SelectRandomLocation(bool preferUndiscovered)
|
||||
{
|
||||
List<Location> nextLocations = CurrentLocation.Connections.Select(c => c.OtherLocation(CurrentLocation)).ToList();
|
||||
List<Location> nextLocations = CurrentLocation.Connections.Where(c => !c.Locked).Select(c => c.OtherLocation(CurrentLocation)).ToList();
|
||||
List<Location> undiscoveredLocations = nextLocations.FindAll(l => !l.Discovered);
|
||||
|
||||
if (undiscoveredLocations.Count > 0 && preferUndiscovered)
|
||||
@@ -687,6 +814,8 @@ namespace Barotrauma
|
||||
{
|
||||
ProgressWorld();
|
||||
}
|
||||
|
||||
Radiation?.OnStep(steps);
|
||||
}
|
||||
|
||||
private void ProgressWorld()
|
||||
@@ -710,7 +839,7 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
if (location == CurrentLocation || location == SelectedLocation) { continue; }
|
||||
if (location == CurrentLocation || location == SelectedLocation || location.IsGateBetweenBiomes) { continue; }
|
||||
|
||||
ProgressLocationTypeChanges(location);
|
||||
|
||||
@@ -724,20 +853,24 @@ namespace Barotrauma
|
||||
private void ProgressLocationTypeChanges(Location location)
|
||||
{
|
||||
location.TimeSinceLastTypeChange++;
|
||||
location.LocationTypeChangeCooldown--;
|
||||
|
||||
if (location.PendingLocationTypeChange != null)
|
||||
{
|
||||
if (location.PendingLocationTypeChange.First.DetermineProbability(location) <= 0.0f)
|
||||
if (location.PendingLocationTypeChange.Value.typeChange.DetermineProbability(location) <= 0.0f)
|
||||
{
|
||||
//remove pending type change if it's no longer allowed
|
||||
location.PendingLocationTypeChange = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
location.PendingLocationTypeChange.Second--;
|
||||
if (location.PendingLocationTypeChange.Second <= 0)
|
||||
location.PendingLocationTypeChange =
|
||||
(location.PendingLocationTypeChange.Value.typeChange,
|
||||
location.PendingLocationTypeChange.Value.delay - 1,
|
||||
location.PendingLocationTypeChange.Value.parentMission);
|
||||
if (location.PendingLocationTypeChange.Value.delay <= 0)
|
||||
{
|
||||
ChangeLocationType(location, location.PendingLocationTypeChange.First);
|
||||
ChangeLocationType(location, location.PendingLocationTypeChange.Value.typeChange);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -764,9 +897,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (selectedTypeChange.RequiredDurationRange.X > 0)
|
||||
{
|
||||
location.PendingLocationTypeChange = new Pair<LocationTypeChange, int>(
|
||||
selectedTypeChange,
|
||||
Rand.Range(selectedTypeChange.RequiredDurationRange.X, selectedTypeChange.RequiredDurationRange.Y));
|
||||
location.PendingLocationTypeChange =
|
||||
(selectedTypeChange,
|
||||
Rand.Range(selectedTypeChange.RequiredDurationRange.X, selectedTypeChange.RequiredDurationRange.Y),
|
||||
null);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -778,20 +912,19 @@ namespace Barotrauma
|
||||
|
||||
foreach (LocationTypeChange typeChange in location.Type.CanChangeTo)
|
||||
{
|
||||
if (typeChange.AnyWithinDistance(
|
||||
location,
|
||||
typeChange.RequiredProximityForProbabilityIncrease,
|
||||
(otherLocation) => { return typeChange.RequiredLocations.Contains(otherLocation.Type.Identifier); }))
|
||||
foreach (var requirement in typeChange.Requirements)
|
||||
{
|
||||
if (!location.ProximityTimer.ContainsKey(typeChange)) { location.ProximityTimer[typeChange] = 0; }
|
||||
location.ProximityTimer[typeChange] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
location.ProximityTimer.Remove(typeChange);
|
||||
if (requirement.AnyWithinDistance(location, requirement.RequiredProximityForProbabilityIncrease))
|
||||
{
|
||||
if (!location.ProximityTimer.ContainsKey(requirement)) { location.ProximityTimer[requirement] = 0; }
|
||||
location.ProximityTimer[requirement] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
location.ProximityTimer.Remove(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public int DistanceToClosestLocationWithOutpost(Location startingLocation, out Location endingLocation)
|
||||
@@ -844,8 +977,12 @@ namespace Barotrauma
|
||||
string prevName = location.Name;
|
||||
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase)));
|
||||
ChangeLocationTypeProjSpecific(location, prevName, change);
|
||||
location.ProximityTimer.Remove(change);
|
||||
foreach (var requirement in change.Requirements)
|
||||
{
|
||||
location.ProximityTimer.Remove(requirement);
|
||||
}
|
||||
location.TimeSinceLastTypeChange = 0;
|
||||
location.LocationTypeChangeCooldown = change.CooldownAfterChange;
|
||||
location.PendingLocationTypeChange = null;
|
||||
}
|
||||
|
||||
@@ -856,9 +993,9 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
public static Map Load(CampaignMode campaign, XElement element)
|
||||
public static Map Load(CampaignMode campaign, XElement element, CampaignSettings settings)
|
||||
{
|
||||
Map map = new Map(campaign, element);
|
||||
Map map = new Map(campaign, element, settings);
|
||||
map.LoadState(element, false);
|
||||
#if CLIENT
|
||||
map.DrawOffset = -map.CurrentLocation.MapPosition;
|
||||
@@ -889,16 +1026,12 @@ namespace Barotrauma
|
||||
location.ProximityTimer.Clear();
|
||||
for (int i = 0; i < location.Type.CanChangeTo.Count; i++)
|
||||
{
|
||||
location.ProximityTimer.Add(location.Type.CanChangeTo[i], subElement.GetAttributeInt("changetimer" + i, 0));
|
||||
for (int j = 0; j < location.Type.CanChangeTo[i].Requirements.Count; j++)
|
||||
{
|
||||
location.ProximityTimer.Add(location.Type.CanChangeTo[i].Requirements[j], subElement.GetAttributeInt("changetimer" + i + "-" + j, 0));
|
||||
}
|
||||
}
|
||||
int locationTypeChangeIndex = subElement.GetAttributeInt("pendinglocationtypechange", -1);
|
||||
if (locationTypeChangeIndex > 0 && locationTypeChangeIndex < location.Type.CanChangeTo.Count - 1)
|
||||
{
|
||||
location.PendingLocationTypeChange = new Pair<LocationTypeChange, int>(
|
||||
location.Type.CanChangeTo[locationTypeChangeIndex],
|
||||
subElement.GetAttributeInt("pendinglocationtypechangetimer", 0));
|
||||
}
|
||||
location.TimeSinceLastTypeChange = subElement.GetAttributeInt("timesincelasttypechange", 0);
|
||||
location.LoadLocationTypeChange(subElement);
|
||||
location.Discovered = subElement.GetAttributeBool("discovered", false);
|
||||
if (location.Discovered)
|
||||
{
|
||||
@@ -933,6 +1066,10 @@ namespace Barotrauma
|
||||
case "connection":
|
||||
int connectionIndex = subElement.GetAttributeInt("i", 0);
|
||||
Connections[connectionIndex].Passed = subElement.GetAttributeBool("passed", false);
|
||||
Connections[connectionIndex].Locked = subElement.GetAttributeBool("locked", false);
|
||||
break;
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -945,6 +1082,7 @@ namespace Barotrauma
|
||||
int currentLocationConnection = element.GetAttributeInt("currentlocationconnection", -1);
|
||||
if (currentLocationConnection >= 0)
|
||||
{
|
||||
Connections[currentLocationConnection].Locked = false;
|
||||
SelectLocation(Connections[currentLocationConnection].OtherLocation(CurrentLocation));
|
||||
}
|
||||
else
|
||||
@@ -975,6 +1113,8 @@ namespace Barotrauma
|
||||
mapElement.Add(new XAttribute("currentlocationconnection", Connections.IndexOf(Connections.Find(c => c.LevelData == Level.Loaded.LevelData))));
|
||||
}
|
||||
}
|
||||
mapElement.Add(new XAttribute("width", Width));
|
||||
mapElement.Add(new XAttribute("height", Height));
|
||||
mapElement.Add(new XAttribute("selectedlocation", SelectedLocationIndex));
|
||||
mapElement.Add(new XAttribute("startlocation", Locations.IndexOf(StartLocation)));
|
||||
mapElement.Add(new XAttribute("endlocation", Locations.IndexOf(EndLocation)));
|
||||
@@ -993,6 +1133,7 @@ namespace Barotrauma
|
||||
|
||||
var connectionElement = new XElement("connection",
|
||||
new XAttribute("passed", connection.Passed),
|
||||
new XAttribute("locked", connection.Locked),
|
||||
new XAttribute("difficulty", connection.Difficulty),
|
||||
new XAttribute("biome", connection.Biome.Identifier),
|
||||
new XAttribute("locations", Locations.IndexOf(connection.Locations[0]) + "," + Locations.IndexOf(connection.Locations[1])));
|
||||
@@ -1000,6 +1141,11 @@ namespace Barotrauma
|
||||
mapElement.Add(connectionElement);
|
||||
}
|
||||
|
||||
if (Radiation != null)
|
||||
{
|
||||
mapElement.Add(Radiation.Save());
|
||||
}
|
||||
|
||||
element.Add(mapElement);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ namespace Barotrauma
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public RadiationParams RadiationParams;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
|
||||
@@ -238,6 +240,9 @@ namespace Barotrauma
|
||||
TypeChangeIcon = new Sprite(subElement);
|
||||
break;
|
||||
#endif
|
||||
case "radiationparams":
|
||||
RadiationParams = new RadiationParams(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal partial class Radiation : ISerializableEntity
|
||||
{
|
||||
public string Name => nameof(Radiation);
|
||||
|
||||
[Serialize(defaultValue: 0f, isSaveable: true)]
|
||||
public float Amount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: true, isSaveable: true)]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
public readonly Map Map;
|
||||
public readonly RadiationParams Params;
|
||||
|
||||
private float radiationTimer;
|
||||
|
||||
private float increasedAmount;
|
||||
private float lastIncrease;
|
||||
|
||||
public Radiation(Map map, RadiationParams radiationParams, XElement? element = null)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
Map = map;
|
||||
Params = radiationParams;
|
||||
radiationTimer = Params.RadiationDamageDelay;
|
||||
if (element == null)
|
||||
{
|
||||
Amount = Params.StartingRadiation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the progress of the radiation.
|
||||
/// </summary>
|
||||
/// <param name="steps"></param>
|
||||
public void OnStep(float steps = 1)
|
||||
{
|
||||
if (!Enabled) { return; }
|
||||
if (steps <= 0) { return; }
|
||||
|
||||
float increaseAmount = Params.RadiationStep * steps;
|
||||
|
||||
if (Params.MaxRadiation > 0 && Params.MaxRadiation < Amount + increaseAmount)
|
||||
{
|
||||
increaseAmount = Params.MaxRadiation - Amount;
|
||||
}
|
||||
|
||||
IncreaseRadiation(increaseAmount);
|
||||
|
||||
int amountOfOutposts = Map.Locations.Count(location => location.Type.HasOutpost && !location.IsCriticallyRadiated());
|
||||
|
||||
foreach (Location location in Map.Locations.Where(Contains))
|
||||
{
|
||||
if (location.IsGateBetweenBiomes)
|
||||
{
|
||||
location.Connections.ForEach(c => c.Locked = false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (amountOfOutposts <= Params.MinimumOutpostAmount) { break; }
|
||||
|
||||
if (Map.CurrentLocation is { } currLocation)
|
||||
{
|
||||
// Don't advance on nearby locations to avoid buggy behavior
|
||||
if (currLocation == location || currLocation.Connections.Any(lc => lc.OtherLocation(currLocation) == location)) { continue; }
|
||||
}
|
||||
|
||||
bool wasCritical = location.IsCriticallyRadiated();
|
||||
|
||||
location.TurnsInRadiation++;
|
||||
|
||||
if (location.Type.HasOutpost && !wasCritical && location.IsCriticallyRadiated())
|
||||
{
|
||||
amountOfOutposts--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void IncreaseRadiation(float amount)
|
||||
{
|
||||
Amount += amount;
|
||||
increasedAmount = lastIncrease = amount;
|
||||
}
|
||||
|
||||
public void UpdateRadiation(float deltaTime)
|
||||
{
|
||||
if (!(GameMain.GameSession?.IsCurrentLocationRadiated() ?? false)) { return; }
|
||||
|
||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
|
||||
if (radiationTimer > 0)
|
||||
{
|
||||
radiationTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
radiationTimer = Params.RadiationDamageDelay;
|
||||
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsDead || character.Removed || !(character.CharacterHealth is { } health)) { continue; }
|
||||
|
||||
if (IsEntityRadiated(character))
|
||||
{
|
||||
health.ApplyAffliction(null, new Affliction(AfflictionPrefab.RadiationSickness, Params.RadiationDamageAmount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(Location location)
|
||||
{
|
||||
return Contains(location.MapPosition);
|
||||
}
|
||||
|
||||
public bool Contains(Vector2 pos)
|
||||
{
|
||||
return pos.X < Amount;
|
||||
}
|
||||
|
||||
public bool IsEntityRadiated(Entity entity)
|
||||
{
|
||||
if (!Enabled) { return false; }
|
||||
if (Level.Loaded is { Type: LevelData.LevelType.LocationConnection, StartLocation: { } startLocation, EndLocation: { } endLocation } level)
|
||||
{
|
||||
if (Contains(startLocation) && Contains(endLocation)) { return true; }
|
||||
|
||||
float distance = MathHelper.Clamp((entity.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X), 0.0f, 1.0f);
|
||||
var (startX, startY) = startLocation.MapPosition;
|
||||
var (endX, endY) = endLocation.MapPosition;
|
||||
Vector2 mapPos = new Vector2(startX + (endX - startX), startY + (endY - startY)) * distance;
|
||||
|
||||
return Contains(mapPos);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement element = new XElement(nameof(Radiation));
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class RadiationParams: ISerializableEntity
|
||||
{
|
||||
public string Name => nameof(RadiationParams);
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
[Serialize(defaultValue: -100f, isSaveable: false, "How much radiation the world starts with.")]
|
||||
public float StartingRadiation { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 100f, isSaveable: false, "How much radiation is added on each step.")]
|
||||
public float RadiationStep { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 10, isSaveable: false, "How many turns in radiation does it take for an outpost to be removed from the map.")]
|
||||
public int CriticalRadiationThreshold { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 3, isSaveable: false, "Minimum amount of outposts in the level that cannot be removed due to radiation.")]
|
||||
public int MinimumOutpostAmount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 3f, isSaveable: false, "How fast the radiation increase animation goes.")]
|
||||
public float AnimationSpeed { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 10f, isSaveable: false, "How long it takes to apply more radiation damage while in a radiated zone.")]
|
||||
public float RadiationDamageDelay { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 1f, isSaveable: false, "How much is the radiation affliction increased by while in a radiated zone.")]
|
||||
public float RadiationDamageAmount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: -1.0f, isSaveable: false, "Maximum amount of radiation.")]
|
||||
public float MaxRadiation { get; set; }
|
||||
|
||||
[Serialize(defaultValue: "139,0,0,85", isSaveable: false, "The color of the radiated area.")]
|
||||
public Color RadiationAreaColor { get; set; }
|
||||
|
||||
[Serialize(defaultValue: "255,0,0,255", isSaveable: false, "The tint of the radiation border sprites.")]
|
||||
public Color RadiationBorderTint { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 16.66f, isSaveable: false, "Speed of the border spritesheet animation.")]
|
||||
public float BorderAnimationSpeed { get; set; }
|
||||
|
||||
public RadiationParams(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,23 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
enum MapEntityCategory
|
||||
{
|
||||
Structure = 1, Decorative = 2, Machine = 4, Equipment = 8, Electrical = 16, Material = 32, Misc = 64, Alien = 128, Wrecked = 256, Thalamus = 512, ItemAssembly = 1024, Legacy = 2048
|
||||
Structure = 1,
|
||||
Decorative = 2,
|
||||
Machine = 4,
|
||||
Equipment = 8,
|
||||
Electrical = 16,
|
||||
Material = 32,
|
||||
Misc = 64,
|
||||
Alien = 128,
|
||||
Wrecked = 256,
|
||||
ItemAssembly = 512,
|
||||
Legacy = 1024
|
||||
}
|
||||
|
||||
abstract partial class MapEntityPrefab : IPrefab, IDisposable
|
||||
@@ -54,6 +63,7 @@ namespace Barotrauma
|
||||
//is it possible to stretch the entity horizontally/vertically
|
||||
[Serialize(false, false)]
|
||||
public bool ResizeHorizontal { get; protected set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool ResizeVertical { get; protected set; }
|
||||
|
||||
@@ -118,6 +128,9 @@ namespace Barotrauma
|
||||
[Serialize(false, false)]
|
||||
public bool HideInMenus { get; set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Subcategory { get; set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool Linkable
|
||||
{
|
||||
@@ -215,6 +228,11 @@ namespace Barotrauma
|
||||
return string.IsNullOrWhiteSpace(AllowedUpgrades) ? new string[0] : AllowedUpgrades.Split(",");
|
||||
}
|
||||
|
||||
public bool HasSubCategory(string subcategory)
|
||||
{
|
||||
return subcategory?.Equals(this.Subcategory, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
}
|
||||
|
||||
protected virtual void CreateInstance(Rectangle rect)
|
||||
{
|
||||
if (constructor == null) return;
|
||||
|
||||
@@ -39,6 +39,37 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
public bool AlwaysDestructible
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
public bool AlwaysRewireable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, isSaveable: true), Editable]
|
||||
public bool AllowStealing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, isSaveable: true), Editable]
|
||||
public bool SpawnCrewInsideOutpost
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", isSaveable: true), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
private readonly Dictionary<string, int> moduleCounts = new Dictionary<string, int>();
|
||||
|
||||
public IEnumerable<KeyValuePair<string, int>> ModuleCounts
|
||||
|
||||
@@ -68,6 +68,15 @@ namespace Barotrauma
|
||||
private static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, Location location, bool onlyEntrance = false)
|
||||
{
|
||||
var outpostModuleFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.OutpostModule);
|
||||
if (location != null)
|
||||
{
|
||||
if (location.IsCriticallyRadiated() && OutpostGenerationParams.Params.FirstOrDefault(p => p.Identifier.Equals(generationParams.ReplaceInRadiation, StringComparison.OrdinalIgnoreCase)) is { } newParams)
|
||||
{
|
||||
generationParams = newParams;
|
||||
}
|
||||
|
||||
locationType = location.GetLocationType();
|
||||
}
|
||||
|
||||
//load the infos of the outpost module files
|
||||
List<SubmarineInfo> outpostModules = new List<SubmarineInfo>();
|
||||
@@ -169,6 +178,7 @@ namespace Barotrauma
|
||||
Type = SubmarineType.Outpost
|
||||
};
|
||||
generationFailed = false;
|
||||
outpostInfo.OutpostGenerationParams = generationParams;
|
||||
sub = new Submarine(outpostInfo, loadEntities: loadEntities);
|
||||
sub.Info.OutpostGenerationParams = generationParams;
|
||||
if (!generationFailed)
|
||||
@@ -669,10 +679,15 @@ namespace Barotrauma
|
||||
|
||||
if (availableModules.Count() == 0) { return null; }
|
||||
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m =>
|
||||
!m.OutpostModuleInfo.AllowedLocationTypes.Any() ||
|
||||
m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
//try to search for modules made specifically for this location type first
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
@@ -705,10 +720,15 @@ namespace Barotrauma
|
||||
|
||||
if (availableModules.Count() == 0) { return null; }
|
||||
|
||||
//try to search for modules made specifically for this location type first
|
||||
var modulesSuitableForLocationType =
|
||||
availableModules.Where(m =>
|
||||
!m.OutpostModuleInfo.AllowedLocationTypes.Any() ||
|
||||
m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
availableModules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier.ToLowerInvariant()));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
{
|
||||
@@ -963,7 +983,7 @@ namespace Barotrauma
|
||||
var moduleEntities = MapEntity.LoadAll(sub, hallwayInfo.SubmarineElement, hallwayInfo.FilePath, -1);
|
||||
|
||||
//remove items that don't fit in the hallway
|
||||
moduleEntities.Where(e => e is Item item && item.GetComponent<Door>() == null && e.Rect.Width > hallwayLength).ForEach(e => e.Remove());
|
||||
moduleEntities.Where(e => e is Item item && item.GetComponent<Door>() == null && (isHorizontal ? e.Rect.Width : e.Rect.Height) > hallwayLength).ForEach(e => e.Remove());
|
||||
|
||||
//find the largest hull to use it as the center point of the hallway
|
||||
//and the bounds of all the hulls, used when resizing the hallway to fit between the modules
|
||||
@@ -1036,11 +1056,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (me is Structure structure)
|
||||
else if (me is Structure || (me is Item item && item.GetComponent<Door>() == null))
|
||||
{
|
||||
if (isHorizontal)
|
||||
{
|
||||
if (!structure.ResizeHorizontal)
|
||||
if (!me.ResizeHorizontal)
|
||||
{
|
||||
int xPos = (int)(leftHull.WorldRect.Right + (me.WorldPosition.X - hullBounds.X) * scaleFactor);
|
||||
me.Rect = new Rectangle(xPos - me.RectWidth / 2, me.Rect.Y, me.Rect.Width, me.Rect.Height);
|
||||
@@ -1054,9 +1074,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!structure.ResizeVertical)
|
||||
if (!me.ResizeVertical)
|
||||
{
|
||||
int yPos = (int)(topHull.WorldRect.Y - topHull.RectHeight + (me.WorldPosition.X - hullBounds.Bottom) * scaleFactor);
|
||||
int yPos = (int)(topHull.WorldRect.Y - topHull.RectHeight + (me.WorldPosition.Y - hullBounds.Bottom) * scaleFactor);
|
||||
me.Rect = new Rectangle(me.Rect.X, yPos + me.RectHeight / 2, me.Rect.Width, me.Rect.Height);
|
||||
}
|
||||
else
|
||||
@@ -1084,8 +1104,35 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.PreviousModule.Info.Name}\".");
|
||||
continue;
|
||||
}
|
||||
startWaypoint.linkedTo.Add(endWaypoint);
|
||||
endWaypoint.linkedTo.Add(startWaypoint);
|
||||
|
||||
if (startWaypoint.WorldPosition.X > endWaypoint.WorldPosition.X)
|
||||
{
|
||||
var temp = startWaypoint;
|
||||
startWaypoint = endWaypoint;
|
||||
endWaypoint = temp;
|
||||
}
|
||||
|
||||
if (hallwayLength > 100 && isHorizontal)
|
||||
{
|
||||
WayPoint prevWayPoint = startWaypoint;
|
||||
for (float x = leftHull.Rect.Right + 50; x < rightHull.Rect.X - 50; x += 100.0f)
|
||||
{
|
||||
var newWayPoint = new WayPoint(new Vector2(x, hullBounds.Y + 110.0f), SpawnType.Path, sub);
|
||||
prevWayPoint.linkedTo.Add(newWayPoint);
|
||||
newWayPoint.linkedTo.Add(prevWayPoint);
|
||||
prevWayPoint = newWayPoint;
|
||||
}
|
||||
if (prevWayPoint != null)
|
||||
{
|
||||
prevWayPoint.linkedTo.Add(endWaypoint);
|
||||
endWaypoint.linkedTo.Add(prevWayPoint);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
startWaypoint.linkedTo.Add(endWaypoint);
|
||||
endWaypoint.linkedTo.Add(startWaypoint);
|
||||
}
|
||||
|
||||
WayPoint closestWaypoint = null;
|
||||
float closestDistSqr = 30.0f * 30.0f;
|
||||
@@ -1381,10 +1428,9 @@ namespace Barotrauma
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(characterInfo.Name));
|
||||
|
||||
ISpatialEntity gotoTarget = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Human, humanPrefab.GetModuleFlags(), humanPrefab.GetSpawnPointTags());
|
||||
|
||||
if (gotoTarget == null)
|
||||
{
|
||||
gotoTarget = outpost.GetHulls(true).GetRandom();
|
||||
gotoTarget = outpost.GetHulls(true).GetRandom(Rand.RandSync.Server);
|
||||
}
|
||||
characterInfo.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
var npc = Character.Create(CharacterPrefab.HumanConfigFile, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
@@ -1406,27 +1452,11 @@ namespace Barotrauma
|
||||
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.Server);
|
||||
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
|
||||
{
|
||||
item.AllowStealing = outpost.Info.OutpostGenerationParams.AllowStealing;
|
||||
item.SpawnedInOutpost = true;
|
||||
}
|
||||
npc.GiveIdCardTags(gotoTarget as WayPoint);
|
||||
if (npc.AIController is HumanAIController humanAI)
|
||||
{
|
||||
var idleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
|
||||
if (humanPrefab.CampaignInteractionType != CampaignMode.InteractionType.None)
|
||||
{
|
||||
idleObjective.Behavior = AIObjectiveIdle.BehaviorType.StayInHull;
|
||||
idleObjective.TargetHull = AIObjectiveGoTo.GetTargetHull(gotoTarget);
|
||||
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(npc, humanPrefab.CampaignInteractionType);
|
||||
}
|
||||
else
|
||||
{
|
||||
idleObjective.Behavior = humanPrefab.Behavior;
|
||||
foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes)
|
||||
{
|
||||
idleObjective.PreferredOutpostModuleTypes.Add(moduleType);
|
||||
}
|
||||
}
|
||||
}
|
||||
humanPrefab.InitializeCharacter(npc, gotoTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,20 @@ namespace Barotrauma
|
||||
/// Can the item be a Daily Special or a Requested Good
|
||||
/// </summary>
|
||||
public readonly bool CanBeSpecial;
|
||||
/// <summary>
|
||||
/// The item isn't available in stores unless the level's difficulty is above this value
|
||||
/// </summary>
|
||||
public readonly int MinLevelDifficulty;
|
||||
|
||||
/// <summary>
|
||||
/// Support for the old style of determining item prices
|
||||
/// when there were individual Price elements for each location type
|
||||
/// where the item was for sale.
|
||||
/// </summary>
|
||||
public PriceInfo (XElement element)
|
||||
public PriceInfo(XElement element)
|
||||
{
|
||||
Price = element.GetAttributeInt("buyprice", 0);
|
||||
MinLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
CanBeBought = true;
|
||||
var minAmount = GetMinAmount(element);
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
@@ -37,13 +42,14 @@ namespace Barotrauma
|
||||
MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount);
|
||||
}
|
||||
|
||||
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true)
|
||||
public PriceInfo(int price, bool canBeBought, int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0)
|
||||
{
|
||||
Price = price;
|
||||
CanBeBought = canBeBought;
|
||||
MinAvailableAmount = Math.Min(minAmount, CargoManager.MaxQuantity);
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, minAmount);
|
||||
MinLevelDifficulty = minLevelDifficulty;
|
||||
CanBeSpecial = canBeSpecial;
|
||||
}
|
||||
|
||||
@@ -54,6 +60,7 @@ namespace Barotrauma
|
||||
var soldByDefault = element.GetAttributeBool("soldbydefault", true);
|
||||
var minAmount = GetMinAmount(element);
|
||||
var maxAmount = GetMaxAmount(element);
|
||||
var minLevelDifficulty = element.GetAttributeInt("minleveldifficulty", 0);
|
||||
var canBeSpecial = element.GetAttributeBool("canbespecial", true);
|
||||
var priceInfos = new List<Tuple<string, PriceInfo>>();
|
||||
|
||||
@@ -65,14 +72,16 @@ namespace Barotrauma
|
||||
new PriceInfo(price: (int)(priceMultiplier * basePrice), canBeBought: sold,
|
||||
minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial: canBeSpecial)));
|
||||
canBeSpecial,
|
||||
childElement.GetAttributeInt("minleveldifficulty", minLevelDifficulty))));
|
||||
}
|
||||
|
||||
var canBeBoughtAtOtherLocations = soldByDefault && element.GetAttributeBool("soldeverywhere", true);
|
||||
defaultPrice = new PriceInfo(basePrice, canBeBoughtAtOtherLocations,
|
||||
minAmount: canBeBoughtAtOtherLocations ? minAmount : 0,
|
||||
maxAmount: canBeBoughtAtOtherLocations ? maxAmount : 0,
|
||||
canBeSpecial: canBeSpecial);
|
||||
canBeSpecial,
|
||||
minLevelDifficulty);
|
||||
|
||||
return priceInfos;
|
||||
}
|
||||
|
||||
@@ -637,7 +637,7 @@ namespace Barotrauma
|
||||
|
||||
Vector2 bodyPos = WorldPosition + BodyOffset;
|
||||
|
||||
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos, MathHelper.ToDegrees(BodyRotation));
|
||||
Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos, BodyRotation);
|
||||
|
||||
return
|
||||
Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f &&
|
||||
@@ -819,16 +819,12 @@ namespace Barotrauma
|
||||
}
|
||||
for (int i = 1; i <= particleAmount; i++)
|
||||
{
|
||||
var worldRect = section.WorldRect;
|
||||
Vector2 particlePos = new Vector2(
|
||||
Rand.Range(section.rect.X, section.rect.Right),
|
||||
Rand.Range(section.rect.Y - section.rect.Height, section.rect.Y));
|
||||
Rand.Range(worldRect.X, worldRect.Right),
|
||||
Rand.Range(worldRect.Y - worldRect.Height, worldRect.Y));
|
||||
|
||||
if (Submarine != null)
|
||||
{
|
||||
particlePos += Submarine.DrawPosition;
|
||||
}
|
||||
|
||||
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)));
|
||||
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
|
||||
if (particle == null) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +360,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(element.GetAttributeString("category", "Structure"), true, out MapEntityCategory category))
|
||||
string categoryStr = element.GetAttributeString("category", "Structure");
|
||||
if (!Enum.TryParse(categoryStr, true, out MapEntityCategory category))
|
||||
{
|
||||
category = MapEntityCategory.Structure;
|
||||
}
|
||||
@@ -419,6 +420,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility
|
||||
if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sp.Category = MapEntityCategory.Wrecked;
|
||||
sp.Subcategory = "Thalamus";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(sp.identifier))
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Barotrauma
|
||||
|
||||
public Rectangle Borders
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
return subBody == null ? Rectangle.Empty : subBody.Borders;
|
||||
}
|
||||
@@ -155,7 +155,7 @@ namespace Barotrauma
|
||||
private float? realWorldCrushDepth;
|
||||
public float RealWorldCrushDepth
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (!realWorldCrushDepth.HasValue)
|
||||
{
|
||||
@@ -165,13 +165,11 @@ namespace Barotrauma
|
||||
if (structure.Submarine != this || !structure.HasBody || structure.Indestructible) { continue; }
|
||||
realWorldCrushDepth = Math.Min(structure.CrushDepth, realWorldCrushDepth.Value);
|
||||
}
|
||||
if (Info.SubmarineClass == SubmarineClass.DeepDiver)
|
||||
{
|
||||
realWorldCrushDepth *= 1.2f;
|
||||
}
|
||||
realWorldCrushDepth *= Info.GetRealWorldCrushDepthMultiplier();
|
||||
}
|
||||
return realWorldCrushDepth.Value;
|
||||
}
|
||||
set { realWorldCrushDepth = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -179,34 +177,30 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public float RealWorldDepth
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (Level.Loaded?.GenerationParams == null)
|
||||
{
|
||||
return -WorldPosition.Y * Physics.DisplayToRealWorldRatio;
|
||||
}
|
||||
else if (GameMain.GameSession?.Campaign == null)
|
||||
{
|
||||
return (-(WorldPosition.Y - Level.Loaded.GenerationParams.Height) + 80000.0f) * Physics.DisplayToRealWorldRatio;
|
||||
}
|
||||
return Level.Loaded.GetRealWorldDepth(WorldPosition.Y);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AtEndPosition
|
||||
public bool AtEndExit
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (Level.Loaded == null) { return false; }
|
||||
if (Level.Loaded.EndOutpost != null && DockedTo.Contains(Level.Loaded.EndOutpost))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.EndPosition) < Level.ExitDistance * Level.ExitDistance);
|
||||
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.EndExitPosition) < Level.ExitDistance * Level.ExitDistance);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AtStartPosition
|
||||
public bool AtStartExit
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -215,7 +209,7 @@ namespace Barotrauma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.StartPosition) < Level.ExitDistance * Level.ExitDistance);
|
||||
return (Vector2.DistanceSquared(Position + HiddenSubPosition, Level.Loaded.StartExitPosition) < Level.ExitDistance * Level.ExitDistance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,10 +245,10 @@ namespace Barotrauma
|
||||
|
||||
public bool AtDamageDepth
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (Level.Loaded == null || subBody == null) { return false; }
|
||||
return RealWorldDepth > Level.Loaded.RealWorldCrushDepth & RealWorldDepth > RealWorldCrushDepth;
|
||||
return RealWorldDepth > Level.Loaded.RealWorldCrushDepth && RealWorldDepth > RealWorldCrushDepth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +318,7 @@ namespace Barotrauma
|
||||
{
|
||||
Info.Type = SubmarineType.Wreck;
|
||||
ShowSonarMarker = false;
|
||||
DockedTo.ForEach(s => s.ShowSonarMarker = false);
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = CharacterTeamType.None;
|
||||
|
||||
@@ -333,7 +328,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != this) { continue; }
|
||||
if (item.prefab.Identifier == "idcardwreck" || item.prefab.Identifier == "idcard")
|
||||
if (item.prefab.Identifier == "idcardwreck" || item.prefab.Identifier == "idcard")
|
||||
{
|
||||
foreach (string tag in item.GetTags().ToList())
|
||||
{
|
||||
@@ -341,7 +336,7 @@ namespace Barotrauma
|
||||
string newTag = Level.Loaded.GetWreckIDTag(tag, this);
|
||||
item.ReplaceTag(tag, newTag);
|
||||
ReplaceIDCardTagRequirements(tag, newTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +450,7 @@ namespace Barotrauma
|
||||
public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null, float subDockingPortOffset = 0.0f, int verticalMoveDir = 0)
|
||||
{
|
||||
Rectangle dockedBorders = GetDockedBorders();
|
||||
Vector2 diffFromDockedBorders =
|
||||
Vector2 diffFromDockedBorders =
|
||||
new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
|
||||
- new Vector2(Borders.Center.X, Borders.Y - Borders.Height / 2);
|
||||
|
||||
@@ -506,7 +501,7 @@ namespace Barotrauma
|
||||
(e.Point1.Y > refPos.Y + minHeight * 0.5f && e.Point2.Y > refPos.Y + minHeight * 0.5f))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.Site.Coord.X < refPos.X)
|
||||
{
|
||||
@@ -553,7 +548,7 @@ namespace Barotrauma
|
||||
//walls found at both sides, use their midpoint
|
||||
spawnPos.X = (limits.X + limits.Y) / 2 + subDockingPortOffset;
|
||||
}
|
||||
|
||||
|
||||
spawnPos.Y = MathHelper.Clamp(spawnPos.Y, dockedBorders.Height / 2 + 10, Level.Loaded.Size.Y - dockedBorders.Height / 2 - padding * 2);
|
||||
return spawnPos - diffFromDockedBorders;
|
||||
}
|
||||
@@ -587,18 +582,31 @@ namespace Barotrauma
|
||||
{
|
||||
if (e is Item item)
|
||||
{
|
||||
if (item.GetComponent<Turret>() != null) { return false; }
|
||||
if (item.body != null && !item.body.Enabled) { return true; }
|
||||
}
|
||||
if (e.HiddenInGame) { return true; }
|
||||
return false;
|
||||
});
|
||||
|
||||
if (entities.Count == 0) return Rectangle.Empty;
|
||||
if (entities.Count == 0) { return Rectangle.Empty; }
|
||||
|
||||
float minX = entities[0].Rect.X, minY = entities[0].Rect.Y - entities[0].Rect.Height;
|
||||
float maxX = entities[0].Rect.Right, maxY = entities[0].Rect.Y;
|
||||
|
||||
for (int i = 1; i < entities.Count; i++)
|
||||
{
|
||||
if (entities[i] is Item item)
|
||||
{
|
||||
var turret = item.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
minX = Math.Min(minX, entities[i].Rect.X + turret.TransformedBarrelPos.X * 2f);
|
||||
minY = Math.Min(minY, entities[i].Rect.Y - entities[i].Rect.Height - turret.TransformedBarrelPos.Y * 2f);
|
||||
maxX = Math.Max(maxX, entities[i].Rect.Right + turret.TransformedBarrelPos.X * 2f);
|
||||
maxY = Math.Max(maxY, entities[i].Rect.Y - turret.TransformedBarrelPos.Y * 2f);
|
||||
}
|
||||
}
|
||||
minX = Math.Min(minX, entities[i].Rect.X);
|
||||
minY = Math.Min(minY, entities[i].Rect.Y - entities[i].Rect.Height);
|
||||
maxX = Math.Max(maxX, entities[i].Rect.Right);
|
||||
@@ -607,7 +615,7 @@ namespace Barotrauma
|
||||
|
||||
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
|
||||
}
|
||||
|
||||
|
||||
public static Rectangle AbsRect(Vector2 pos, Vector2 size)
|
||||
{
|
||||
if (size.X < 0.0f)
|
||||
@@ -620,7 +628,7 @@ namespace Barotrauma
|
||||
pos.Y -= size.Y;
|
||||
size.Y = -size.Y;
|
||||
}
|
||||
|
||||
|
||||
return new Rectangle((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y);
|
||||
}
|
||||
|
||||
@@ -674,7 +682,7 @@ namespace Barotrauma
|
||||
|
||||
closestFraction = 0.0f;
|
||||
closestNormal = Vector2.Normalize(rayEnd - rayStart);
|
||||
if (fixture.Body != null) closestBody = fixture.Body;
|
||||
if (fixture.Body != null) closestBody = fixture.Body;
|
||||
return false;
|
||||
}, ref aabb);
|
||||
if (closestFraction <= 0.0f)
|
||||
@@ -685,7 +693,7 @@ namespace Barotrauma
|
||||
return closestBody;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GameMain.World.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) { return -1; }
|
||||
@@ -702,7 +710,7 @@ namespace Barotrauma
|
||||
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||
lastPickedFraction = closestFraction;
|
||||
lastPickedNormal = closestNormal;
|
||||
|
||||
|
||||
return closestBody;
|
||||
}
|
||||
|
||||
@@ -827,13 +835,13 @@ namespace Barotrauma
|
||||
lastPickedPosition = rayEnd;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
GameMain.World.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
if (fixture == null) { return -1; }
|
||||
if (ignoreSensors && fixture.IsSensor) { return -1; }
|
||||
if (ignoreLevel && fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) { return -1; }
|
||||
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)
|
||||
if (!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)
|
||||
&& !fixture.CollisionCategories.HasFlag(Physics.CollisionWall)
|
||||
&& !fixture.CollisionCategories.HasFlag(Physics.CollisionRepair)) { return -1; }
|
||||
if (ignoreSubs && fixture.Body.UserData is Submarine) { return -1; }
|
||||
@@ -880,7 +888,7 @@ namespace Barotrauma
|
||||
parents.Add(this);
|
||||
|
||||
flippedX = !flippedX;
|
||||
|
||||
|
||||
Item.UpdateHulls();
|
||||
|
||||
List<Item> bodyItems = Item.ItemList.FindAll(it => it.Submarine == this && it.body != null);
|
||||
@@ -1049,6 +1057,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
steering.MaintainPos = true;
|
||||
steering.PosToMaintain = WorldPosition;
|
||||
steering.AutoPilot = true;
|
||||
#if SERVER
|
||||
steering.UnsentChanges = true;
|
||||
@@ -1137,7 +1146,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (ConnectedDockingPorts.TryGetValue(dockedSub, out DockingPort port))
|
||||
{
|
||||
port.Undock();
|
||||
port.Undock(applyEffects: false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1164,7 +1173,8 @@ namespace Barotrauma
|
||||
subBody.SetPosition(subBody.Position + amount);
|
||||
}
|
||||
|
||||
public static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts = false, bool ignoreOutsideLevel = true)
|
||||
/// <param name="teamType">If has value, the sub must match the team type.</param>
|
||||
public static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts = false, bool ignoreOutsideLevel = true, bool ignoreRespawnShuttle = false, CharacterTeamType? teamType = null)
|
||||
{
|
||||
Submarine closest = null;
|
||||
float closestDist = 0.0f;
|
||||
@@ -1172,6 +1182,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (ignoreOutposts && sub.Info.IsOutpost) { continue; }
|
||||
if (ignoreOutsideLevel && Level.Loaded != null && sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
|
||||
if (ignoreRespawnShuttle)
|
||||
{
|
||||
if (sub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle) { continue; }
|
||||
}
|
||||
if (teamType.HasValue && sub.TeamID != teamType) { continue; }
|
||||
float dist = Vector2.DistanceSquared(worldPosition, sub.WorldPosition);
|
||||
if (closest == null || dist < closestDist)
|
||||
{
|
||||
@@ -1334,14 +1349,19 @@ namespace Barotrauma
|
||||
PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
TeamID = CharacterTeamType.FriendlyNPC;
|
||||
|
||||
bool indestructible =
|
||||
GameMain.NetworkMember != null &&
|
||||
!GameMain.NetworkMember.ServerSettings.DestructibleOutposts &&
|
||||
!(info.OutpostGenerationParams?.AlwaysDestructible ?? false);
|
||||
|
||||
foreach (MapEntity me in MapEntity.mapEntityList)
|
||||
{
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
{
|
||||
item.SpawnedInOutpost = true;
|
||||
if (item.GetComponent<Repairable>() != null &&
|
||||
(GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.DestructibleOutposts))
|
||||
item.SpawnedInOutpost = info.OutpostGenerationParams != null;
|
||||
item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true;
|
||||
if (item.GetComponent<Repairable>() != null && indestructible)
|
||||
{
|
||||
item.Indestructible = true;
|
||||
}
|
||||
@@ -1350,7 +1370,10 @@ namespace Barotrauma
|
||||
if (ic is ConnectionPanel connectionPanel)
|
||||
{
|
||||
//prevent rewiring
|
||||
connectionPanel.Locked = true;
|
||||
if (info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AlwaysRewireable)
|
||||
{
|
||||
connectionPanel.Locked = true;
|
||||
}
|
||||
}
|
||||
else if (ic is Holdable holdable && holdable.Attached && item.GetComponent<LevelResource>() == null)
|
||||
{
|
||||
@@ -1363,9 +1386,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts)
|
||||
else if (me is Structure structure && structure.Prefab.IndestructibleInOutposts && indestructible)
|
||||
{
|
||||
structure.Indestructible = GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.DestructibleOutposts;
|
||||
structure.Indestructible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1415,7 +1438,7 @@ namespace Barotrauma
|
||||
//halve the brightness of the lights to make them look (almost) right on the new lighting formula
|
||||
if (showWarningMessages &&
|
||||
!string.IsNullOrEmpty(Info.FilePath) &&
|
||||
Screen.Selected != GameMain.SubEditorScreen &&
|
||||
Screen.Selected != GameMain.SubEditorScreen &&
|
||||
(Info.GameVersion == null || Info.GameVersion < new Version("0.8.9.0")))
|
||||
{
|
||||
DebugConsole.ThrowError("The submarine \"" + Info.Name + "\" was made using an older version of the Barotrauma that used a different formula to calculate the lighting. "
|
||||
@@ -1428,6 +1451,7 @@ namespace Barotrauma
|
||||
if (lightComponent != null) lightComponent.LightColor = new Color(lightComponent.LightColor, lightComponent.LightColor.A / 255.0f * 0.5f);
|
||||
}
|
||||
}
|
||||
GenerateOutdoorNodes();
|
||||
}
|
||||
|
||||
protected override ushort DetermineID(ushort id, Submarine submarine)
|
||||
@@ -1483,7 +1507,7 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("recommendedcrewsizemax", Info.RecommendedCrewSizeMax));
|
||||
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience ?? ""));
|
||||
element.Add(new XAttribute("requiredcontentpackages", string.Join(", ", Info.RequiredContentPackages)));
|
||||
|
||||
|
||||
if (Info.Type == SubmarineType.OutpostModule)
|
||||
{
|
||||
Info.OutpostModuleInfo?.Save(element);
|
||||
@@ -1589,7 +1613,7 @@ namespace Barotrauma
|
||||
|
||||
PhysicsBody.RemoveAll();
|
||||
|
||||
GameMain.World.Clear();
|
||||
GameMain.World.Clear();
|
||||
|
||||
Unloading = false;
|
||||
}
|
||||
@@ -1634,12 +1658,18 @@ namespace Barotrauma
|
||||
{
|
||||
if (outdoorNodes == null)
|
||||
{
|
||||
outdoorNodes = PathNode.GenerateNodes(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path && wp.Submarine == this && wp.CurrentHull == null));
|
||||
GenerateOutdoorNodes();
|
||||
}
|
||||
return outdoorNodes;
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateOutdoorNodes()
|
||||
{
|
||||
var waypoints = WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path && wp.Submarine == this && wp.CurrentHull == null);
|
||||
outdoorNodes = PathNode.GenerateNodes(waypoints, removeOrphans: false);
|
||||
}
|
||||
|
||||
private readonly Dictionary<Submarine, HashSet<PathNode>> obstructedNodes = new Dictionary<Submarine, HashSet<PathNode>>();
|
||||
|
||||
/// <summary>
|
||||
@@ -1707,7 +1737,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
node.Waypoint.FindHull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1722,7 +1751,8 @@ namespace Barotrauma
|
||||
nodes.Clear();
|
||||
obstructedNodes.Remove(otherSub);
|
||||
}
|
||||
OutdoorNodes.ForEach(n => n.Waypoint.FindHull());
|
||||
}
|
||||
|
||||
public void RefreshOutdoorNodes() => OutdoorNodes.ForEach(n => n?.Waypoint?.FindHull());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision;
|
||||
@@ -449,12 +450,21 @@ namespace Barotrauma
|
||||
if (GameMain.GameSession?.GameMode is TestGameMode) { return; }
|
||||
#endif
|
||||
if (Level.Loaded == null) { return; }
|
||||
float submarineDepth = submarine.RealWorldDepth;
|
||||
if (!Submarine.AtDamageDepth) { return; }
|
||||
|
||||
//camera shake and sounds start playing 500 meters before crush depth
|
||||
float depthEffectThreshold = 500.0f;
|
||||
if (Submarine.RealWorldDepth < Level.Loaded.RealWorldCrushDepth - depthEffectThreshold || Submarine.RealWorldDepth < Submarine.RealWorldCrushDepth - depthEffectThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
depthDamageTimer -= deltaTime;
|
||||
if (depthDamageTimer > 0.0f) { return; }
|
||||
|
||||
#if CLIENT
|
||||
SoundPlayer.PlayDamageSound("pressure", Rand.Range(0.0f, 100.0f), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f);
|
||||
#endif
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != submarine) { continue; }
|
||||
@@ -462,12 +472,14 @@ namespace Barotrauma
|
||||
float wallCrushDepth = wall.CrushDepth;
|
||||
if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; }
|
||||
float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
|
||||
if (pastCrushDepth < 0) { return; }
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f);
|
||||
if (pastCrushDepth > 0)
|
||||
{
|
||||
Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f);
|
||||
}
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(pastCrushDepth * 0.001f, 50.0f));
|
||||
}
|
||||
GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, MathHelper.Clamp(pastCrushDepth * 0.001f, 1.0f, 50.0f));
|
||||
}
|
||||
}
|
||||
|
||||
depthDamageTimer = 10.0f;
|
||||
@@ -558,19 +570,29 @@ namespace Barotrauma
|
||||
{
|
||||
if (limb?.body?.FarseerBody == null || limb.character == null) { return; }
|
||||
|
||||
if (limb.Mass > MinImpactLimbMass)
|
||||
float impactMass = limb.Mass;
|
||||
var enemyAI = limb.character.AIController as EnemyAIController;
|
||||
float attackMultiplier = 1.0f;
|
||||
if (enemyAI?.ActiveAttack != null)
|
||||
{
|
||||
impactMass = Math.Max(Math.Max(limb.Mass, limb.character.AnimController.MainLimb.Mass), limb.character.AnimController.Collider.Mass);
|
||||
attackMultiplier = enemyAI.ActiveAttack.SubmarineImpactMultiplier;
|
||||
}
|
||||
|
||||
if (impactMass * attackMultiplier > MinImpactLimbMass)
|
||||
{
|
||||
Vector2 normal =
|
||||
Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ?
|
||||
Vector2.UnitY :
|
||||
Vector2.Normalize(Body.SimPosition - limb.SimPosition);
|
||||
|
||||
float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(limb.Mass / 100.0f, 1);
|
||||
float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(impactMass / 300.0f, 1);
|
||||
impact *= attackMultiplier;
|
||||
|
||||
ApplyImpact(impact, -normal, collision.ImpactPos, applyDamage: false);
|
||||
ApplyImpact(impact, normal, collision.ImpactPos, applyDamage: false);
|
||||
foreach (Submarine dockedSub in submarine.DockedTo)
|
||||
{
|
||||
dockedSub.SubBody.ApplyImpact(impact, -normal, collision.ImpactPos, applyDamage: false);
|
||||
dockedSub.SubBody.ApplyImpact(impact, normal, collision.ImpactPos, applyDamage: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,7 +825,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = impact * 2.0f;
|
||||
GameMain.GameScreen.Cam.Shake = impact * 10.0f;
|
||||
if (submarine.Info.Type == SubmarineType.Player && !submarine.DockedTo.Any(s => s.Info.Type != SubmarineType.Player))
|
||||
{
|
||||
float angularVelocity =
|
||||
@@ -817,34 +839,41 @@ namespace Barotrauma
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != submarine) { continue; }
|
||||
|
||||
if (c.KnockbackCooldownTimer > 0.0f) { continue; }
|
||||
|
||||
c.KnockbackCooldownTimer = Character.KnockbackCooldown;
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
limb.body.ApplyLinearImpulse(limb.Mass * impulse, 10.0f);
|
||||
}
|
||||
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
|
||||
|
||||
bool holdingOntoSomething = false;
|
||||
if (c.SelectedConstruction != null)
|
||||
{
|
||||
var controller = c.SelectedConstruction.GetComponent<Items.Components.Controller>();
|
||||
holdingOntoSomething = controller != null && controller.LimbPositions.Any();
|
||||
holdingOntoSomething =
|
||||
c.SelectedConstruction.GetComponent<Ladder>() != null ||
|
||||
(c.SelectedConstruction.GetComponent<Controller>()?.LimbPositions.Any() ?? false);
|
||||
}
|
||||
|
||||
//stun for up to 1 second if the impact equal or higher to the maximum impact
|
||||
if (impact >= MaxCollisionImpact && !holdingOntoSomething)
|
||||
if (!holdingOntoSomething)
|
||||
{
|
||||
c.SetStun(Math.Min(impulse.Length() * 0.2f, 1.0f));
|
||||
c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f);
|
||||
//stun for up to 2 second if the impact equal or higher to the maximum impact
|
||||
if (impact >= MaxCollisionImpact)
|
||||
{
|
||||
c.AddDamage(impactPos, AfflictionPrefab.ImpactDamage.Instantiate(3.0f).ToEnumerable(), stun: Math.Min(impulse.Length() * 0.2f, 2.0f), playSound: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != submarine || item.CurrentHull == null ||
|
||||
item.body == null || !item.body.Enabled) continue;
|
||||
if (item.Submarine != submarine || item.CurrentHull == null || item.body == null || !item.body.Enabled) { continue; }
|
||||
|
||||
item.body.ApplyLinearImpulse(item.body.Mass * impulse, 10.0f);
|
||||
item.PositionUpdateInterval = 0.0f;
|
||||
}
|
||||
|
||||
float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f;
|
||||
|
||||
@@ -95,8 +95,9 @@ namespace Barotrauma
|
||||
|
||||
public OutpostModuleInfo OutpostModuleInfo { get; set; }
|
||||
|
||||
public bool IsOutpost => Type == SubmarineType.Outpost;
|
||||
public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
|
||||
public bool IsWreck => Type == SubmarineType.Wreck;
|
||||
public bool IsBeacon => Type == SubmarineType.BeaconStation;
|
||||
public bool IsPlayer => Type == SubmarineType.Player;
|
||||
|
||||
public bool IsCampaignCompatible => IsPlayer && !HasTag(SubmarineTag.Shuttle) && !HasTag(SubmarineTag.HideInMenus) && SubmarineClass != SubmarineClass.Undefined;
|
||||
@@ -274,7 +275,7 @@ namespace Barotrauma
|
||||
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
|
||||
}
|
||||
#if CLIENT
|
||||
PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage.Texture, null, null) : null;
|
||||
PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage) : null;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -463,6 +464,49 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculated from <see cref="SubmarineElement"/>. Can be used when the sub hasn't been loaded and we can't access <see cref="Submarine.RealWorldCrushDepth"/>.
|
||||
/// </summary>
|
||||
public float GetRealWorldCrushDepth()
|
||||
{
|
||||
if (SubmarineElement == null) { return Level.DefaultRealWorldCrushDepth; }
|
||||
bool structureCrushDepthsDefined = false;
|
||||
float realWorldCrushDepth = float.PositiveInfinity;
|
||||
foreach (var structureElement in SubmarineElement.GetChildElements("structure"))
|
||||
{
|
||||
string name = structureElement.Attribute("name")?.Value ?? "";
|
||||
string identifier = structureElement.GetAttributeString("identifier", "");
|
||||
var structurePrefab = Structure.FindPrefab(name, identifier);
|
||||
if (structurePrefab == null || !structurePrefab.Body) { continue; }
|
||||
if (!structureCrushDepthsDefined && structureElement.Attribute("crushdepth") != null)
|
||||
{
|
||||
structureCrushDepthsDefined = true;
|
||||
}
|
||||
float structureCrushDepth = structureElement.GetAttributeFloat("crushdepth", float.PositiveInfinity);
|
||||
realWorldCrushDepth = Math.Min(structureCrushDepth, realWorldCrushDepth);
|
||||
}
|
||||
if (!structureCrushDepthsDefined)
|
||||
{
|
||||
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 bool SaveAs(string filePath, System.IO.MemoryStream previewImage = null)
|
||||
|
||||
@@ -11,7 +11,9 @@ using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 3, Corpse = 4 };
|
||||
[Flags]
|
||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8 };
|
||||
|
||||
partial class WayPoint : MapEntity
|
||||
{
|
||||
public static List<WayPoint> WayPointList = new List<WayPoint>();
|
||||
@@ -142,7 +144,7 @@ namespace Barotrauma
|
||||
|
||||
DebugConsole.Log("Created waypoint (" + ID + ")");
|
||||
|
||||
CurrentHull = Hull.FindHull(WorldPosition);
|
||||
FindHull();
|
||||
}
|
||||
|
||||
public override MapEntity Clone()
|
||||
@@ -784,12 +786,19 @@ namespace Barotrauma
|
||||
public void FindHull()
|
||||
{
|
||||
CurrentHull = Hull.FindHull(WorldPosition, CurrentHull);
|
||||
#if CLIENT
|
||||
//we may not be able to find the hull with the optimized method in the sub editor if new hulls have been added, use the unoptimized method
|
||||
if (Screen.Selected == GameMain.SubEditorScreen)
|
||||
{
|
||||
CurrentHull ??= Hull.FindHullUnoptimized(WorldPosition);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
InitializeLinks();
|
||||
CurrentHull = Hull.FindHull(WorldPosition, CurrentHull);
|
||||
FindHull();
|
||||
FindStairs();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user