Merge remote-tracking branch 'upstream/dev' into develop
This commit is contained in:
@@ -1026,7 +1026,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
branch.DamageVisualizationTimer = 1.0f;
|
||||
}
|
||||
|
||||
if (branch.IsRootGrowth && root != null && root.Health > 0.0f) { return; }
|
||||
if (branch.IsRootGrowth && root is { Health: > 0.0f }) { return; }
|
||||
|
||||
if (type != AttackType.Other && type != AttackType.CutFromRoot)
|
||||
{
|
||||
@@ -1035,7 +1035,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
{
|
||||
// damage is handled server side
|
||||
if (GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -1059,6 +1059,11 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
if (type == AttackType.Fire)
|
||||
{
|
||||
if (attacker is not null)
|
||||
{
|
||||
damage *= 1f + attacker.GetStatValue(StatTypes.BallastFloraDamageMultiplier);
|
||||
}
|
||||
|
||||
if (IsInWater(branch))
|
||||
{
|
||||
damage *= 1f - SubmergedWaterResistance;
|
||||
@@ -1066,7 +1071,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
if (defenseCooldown <= 0)
|
||||
{
|
||||
if (!(StateMachine.State is DefendWithPumpState))
|
||||
if (StateMachine.State is not DefendWithPumpState)
|
||||
{
|
||||
StateMachine.EnterState(new DefendWithPumpState(branch, ClaimedTargets, attacker));
|
||||
defenseCooldown = 180f;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.MapCreatures.Behavior;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -28,13 +27,14 @@ namespace Barotrauma
|
||||
private readonly bool applyFireEffects;
|
||||
private readonly string[] ignoreFireEffectsForTags;
|
||||
private readonly bool ignoreCover;
|
||||
private readonly bool onlyInside,onlyOutside;
|
||||
private readonly float flashDuration;
|
||||
private readonly float? flashRange;
|
||||
private readonly string decal;
|
||||
private readonly float decalSize;
|
||||
private readonly bool applyToSelf;
|
||||
|
||||
public bool OnlyInside, OnlyOutside;
|
||||
|
||||
private readonly float itemRepairStrength;
|
||||
|
||||
public readonly HashSet<Submarine> IgnoredSubmarines = new HashSet<Submarine>();
|
||||
@@ -82,8 +82,8 @@ namespace Barotrauma
|
||||
ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
|
||||
ignoreCover = element.GetAttributeBool("ignorecover", false);
|
||||
onlyInside = element.GetAttributeBool("onlyinside", false);
|
||||
onlyOutside = element.GetAttributeBool("onlyoutside", false);
|
||||
OnlyInside = element.GetAttributeBool("onlyinside", false);
|
||||
OnlyOutside = element.GetAttributeBool("onlyoutside", false);
|
||||
|
||||
flash = element.GetAttributeBool("flash", showEffects);
|
||||
flashDuration = element.GetAttributeFloat("flashduration", 0.05f);
|
||||
@@ -131,17 +131,23 @@ namespace Barotrauma
|
||||
if (damageSource is Item sourceItem)
|
||||
{
|
||||
var launcher = sourceItem.GetComponent<Projectile>()?.Launcher;
|
||||
displayRange *=
|
||||
1.0f
|
||||
+ sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius)
|
||||
displayRange *=
|
||||
1.0f
|
||||
+ sourceItem.GetQualityModifier(Quality.StatType.ExplosionRadius)
|
||||
+ (launcher?.GetQualityModifier(Quality.StatType.ExplosionRadius) ?? 0);
|
||||
Attack.DamageMultiplier *=
|
||||
1.0f
|
||||
Attack.DamageMultiplier *=
|
||||
1.0f
|
||||
+ sourceItem.GetQualityModifier(Quality.StatType.ExplosionDamage)
|
||||
+ (launcher?.GetQualityModifier(Quality.StatType.ExplosionDamage) ?? 0);
|
||||
Attack.SourceItem ??= sourceItem;
|
||||
}
|
||||
|
||||
if (attacker is not null)
|
||||
{
|
||||
displayRange *= 1f + attacker.GetStatValue(StatTypes.ExplosionRadiusMultiplier);
|
||||
Attack.DamageMultiplier *= 1f + attacker.GetStatValue(StatTypes.ExplosionDamageMultiplier);
|
||||
}
|
||||
|
||||
Vector2 cameraPos = GameMain.GameScreen.Cam.Position;
|
||||
float cameraDist = Vector2.Distance(cameraPos, worldPosition) / 2.0f;
|
||||
GameMain.GameScreen.Cam.Shake = cameraShake * Math.Max((cameraShakeRange - cameraDist) / cameraShakeRange, 0.0f);
|
||||
@@ -171,13 +177,12 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
float distSqr = Vector2.DistanceSquared(item.WorldPosition, worldPosition);
|
||||
if (distSqr > displayRangeSqr) continue;
|
||||
|
||||
float distFactor = 1.0f - (float)Math.Sqrt(distSqr) / displayRange;
|
||||
if (distSqr > displayRangeSqr) { continue; }
|
||||
float distFactor = CalculateDistanceFactor(distSqr, displayRange);
|
||||
|
||||
//damage repairable power-consuming items
|
||||
var powered = item.GetComponent<Powered>();
|
||||
if (powered == null || !powered.VulnerableToEMP) continue;
|
||||
if (powered == null || !powered.VulnerableToEMP) { continue; }
|
||||
if (item.Repairables.Any())
|
||||
{
|
||||
item.Condition -= item.MaxCondition * EmpStrength * distFactor;
|
||||
@@ -187,9 +192,10 @@ namespace Barotrauma
|
||||
var powerContainer = item.GetComponent<PowerContainer>();
|
||||
if (powerContainer != null)
|
||||
{
|
||||
powerContainer.Charge -= powerContainer.Capacity * EmpStrength * distFactor;
|
||||
powerContainer.Charge -= powerContainer.GetCapacity() * EmpStrength * distFactor;
|
||||
}
|
||||
}
|
||||
static float CalculateDistanceFactor(float distSqr, float displayRange) => 1.0f - (float)Math.Sqrt(distSqr) / displayRange;
|
||||
}
|
||||
|
||||
if (itemRepairStrength > 0.0f)
|
||||
@@ -283,10 +289,16 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (c == attacker && !applyToSelf) { continue; }
|
||||
//if (c == attacker && !applyToSelf) { continue; }
|
||||
|
||||
if (onlyInside && c.Submarine == null) { continue; }
|
||||
else if (onlyOutside && c.Submarine != null) { 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; }
|
||||
@@ -332,15 +344,19 @@ namespace Barotrauma
|
||||
modifiedAfflictions.Clear();
|
||||
foreach (Affliction affliction in attack.Afflictions.Keys)
|
||||
{
|
||||
// Shouldn't go above 15, or the damage can be unexpectedly low -> doesn't break armor
|
||||
// Effectively this makes large explosions more effective against large creatures (because more limbs are affected), but I don't think that's necessarily a bad thing.
|
||||
float limbCountFactor = Math.Min(distFactors.Count, 15);
|
||||
float dmgMultiplier = distFactor;
|
||||
if (affliction.DivideByLimbCount)
|
||||
{
|
||||
float limbCountFactor = distFactors.Count;
|
||||
if (affliction.Prefab.LimbSpecific && affliction.Prefab.AfflictionType == "damage")
|
||||
{
|
||||
// Shouldn't go above 15, or the damage can be unexpectedly low -> doesn't break armor
|
||||
// Effectively this makes large explosions more effective against large creatures (because more limbs are affected), but I don't think that's necessarily a bad thing.
|
||||
limbCountFactor = Math.Min(distFactors.Count, 15);
|
||||
}
|
||||
dmgMultiplier /= limbCountFactor;
|
||||
}
|
||||
modifiedAfflictions.Add(affliction.CreateMultiplied(dmgMultiplier, affliction.Probability));
|
||||
modifiedAfflictions.Add(affliction.CreateMultiplied(dmgMultiplier, affliction));
|
||||
}
|
||||
c.LastDamageSource = damageSource;
|
||||
if (attacker == null)
|
||||
@@ -348,26 +364,29 @@ namespace Barotrauma
|
||||
if (damageSource is Item item)
|
||||
{
|
||||
attacker = item.GetComponent<Projectile>()?.User;
|
||||
if (attacker == null)
|
||||
{
|
||||
attacker = item.GetComponent<MeleeWeapon>()?.User;
|
||||
}
|
||||
attacker ??= item.GetComponent<MeleeWeapon>()?.User;
|
||||
}
|
||||
}
|
||||
|
||||
AbilityAttackData attackData = new AbilityAttackData(Attack, c, attacker);
|
||||
if (attackData.Afflictions != null)
|
||||
if (attack.Afflictions.Any() || attack.Stun > 0.0f)
|
||||
{
|
||||
modifiedAfflictions.AddRange(attackData.Afflictions);
|
||||
if (!attack.OnlyHumans || c.IsHuman)
|
||||
{
|
||||
AbilityAttackData attackData = new AbilityAttackData(Attack, c, attacker);
|
||||
if (attackData.Afflictions != null)
|
||||
{
|
||||
modifiedAfflictions.AddRange(attackData.Afflictions);
|
||||
}
|
||||
|
||||
//use a position slightly from the limb's position towards the explosion
|
||||
//ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
|
||||
Vector2 dir = worldPosition - limb.WorldPosition;
|
||||
Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
|
||||
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier * attackData.DamageMultiplier);
|
||||
damages.Add(limb, attackResult.Damage);
|
||||
}
|
||||
}
|
||||
|
||||
//use a position slightly from the limb's position towards the explosion
|
||||
//ensures that the attack hits the correct limb and that the direction of the hit can be determined correctly in the AddDamage methods
|
||||
Vector2 dir = worldPosition - limb.WorldPosition;
|
||||
Vector2 hitPos = limb.WorldPosition + (dir.LengthSquared() <= 0.001f ? Rand.Vector(1.0f) : Vector2.Normalize(dir)) * 0.01f;
|
||||
AttackResult attackResult = c.AddDamage(hitPos, modifiedAfflictions, attack.Stun * distFactor, false, attacker: attacker, damageMultiplier: attack.DamageMultiplier * attackData.DamageMultiplier);
|
||||
damages.Add(limb, attackResult.Damage);
|
||||
|
||||
if (attack.StatusEffects != null && attack.StatusEffects.Any())
|
||||
{
|
||||
attack.SetUser(attacker);
|
||||
@@ -430,7 +449,7 @@ namespace Barotrauma
|
||||
damagedStructureList.Clear();
|
||||
foreach (MapEntity entity in MapEntity.mapEntityList)
|
||||
{
|
||||
if (!(entity is Structure structure)) { continue; }
|
||||
if (entity is not Structure structure) { continue; }
|
||||
if (ignoredSubmarines != null && entity.Submarine != null && ignoredSubmarines.Contains(entity.Submarine)) { continue; }
|
||||
|
||||
if (structure.HasBody &&
|
||||
@@ -479,7 +498,7 @@ namespace Barotrauma
|
||||
|
||||
for (int i = Level.Loaded.ExtraWalls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!(Level.Loaded.ExtraWalls[i] is DestructibleLevelWall destructibleWall)) { continue; }
|
||||
if (Level.Loaded.ExtraWalls[i] is not DestructibleLevelWall destructibleWall) { continue; }
|
||||
foreach (var cell in destructibleWall.Cells)
|
||||
{
|
||||
if (cell.IsPointInside(worldPosition))
|
||||
@@ -502,7 +521,7 @@ namespace Barotrauma
|
||||
return damagedStructures;
|
||||
}
|
||||
|
||||
public void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
|
||||
public static void RangedBallastFloraDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
|
||||
{
|
||||
List<BallastFloraBehavior> ballastFlorae = new List<BallastFloraBehavior>();
|
||||
|
||||
|
||||
@@ -550,21 +550,24 @@ namespace Barotrauma
|
||||
if (hull1.WaterVolume < hull1.Volume / Hull.MaxCompress &&
|
||||
hull1.Surface < rect.Y)
|
||||
{
|
||||
//create a wave from the side of the hull the water is leaking from
|
||||
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
|
||||
{
|
||||
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1])) * 6.0f;
|
||||
vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
|
||||
|
||||
hull1.WaveVel[hull1.WaveY.Length - 1] += vel * deltaTime;
|
||||
hull1.WaveVel[hull1.WaveY.Length - 2] += vel * deltaTime;
|
||||
CreateWave(rect, hull1, hull1.WaveY.Length - 1, hull1.WaveY.Length - 2, flowForce, deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
float vel = ((rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[0])) * 6.0f;
|
||||
CreateWave(rect, hull1, 0, 1, flowForce, deltaTime);
|
||||
}
|
||||
static void CreateWave(Rectangle rect, Hull hull1, int index1, int index2, Vector2 flowForce, float deltaTime)
|
||||
{
|
||||
float vel = (rect.Y - rect.Height / 2) - (hull1.Surface + hull1.WaveY[index1]);
|
||||
vel *= Math.Min(Math.Abs(flowForce.X) / 200.0f, 1.0f);
|
||||
|
||||
hull1.WaveVel[0] += vel * deltaTime;
|
||||
hull1.WaveVel[1] += vel * deltaTime;
|
||||
if (vel > 0.0f)
|
||||
{
|
||||
hull1.WaveVel[index1] += vel * deltaTime;
|
||||
hull1.WaveVel[index2] += vel * deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -18,6 +20,14 @@ namespace Barotrauma
|
||||
|
||||
public readonly ImmutableHashSet<int> AllowedZones;
|
||||
|
||||
private readonly SubmarineAvailability? submarineAvailability;
|
||||
private readonly ImmutableHashSet<SubmarineAvailability> submarineAvailabilityOverrides;
|
||||
|
||||
public readonly record struct SubmarineAvailability(
|
||||
Identifier LocationType,
|
||||
SubmarineClass Class = SubmarineClass.Undefined,
|
||||
int MaxTier = 0);
|
||||
|
||||
public Biome(ContentXElement element, LevelGenerationParametersFile file) : base(file, ParseIdentifier(element))
|
||||
{
|
||||
OldIdentifier = element.GetAttributeIdentifier("oldidentifier", Identifier.Empty);
|
||||
@@ -34,6 +44,26 @@ namespace Barotrauma
|
||||
AllowedZones = element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }).ToImmutableHashSet();
|
||||
MinDifficulty = element.GetAttributeFloat("MinDifficulty", 0);
|
||||
maxDifficulty = element.GetAttributeFloat("MaxDifficulty", 100);
|
||||
|
||||
var submarineAvailabilityOverrides = new HashSet<SubmarineAvailability>();
|
||||
if (element.GetChildElement("submarines") is ContentXElement availabilityElement)
|
||||
{
|
||||
submarineAvailability = GetAvailability(availabilityElement);
|
||||
foreach (var overrideElement in availabilityElement.GetChildElements("override"))
|
||||
{
|
||||
var availabilityOverride = GetAvailability(overrideElement);
|
||||
submarineAvailabilityOverrides.Add(availabilityOverride);
|
||||
}
|
||||
}
|
||||
this.submarineAvailabilityOverrides = submarineAvailabilityOverrides.ToImmutableHashSet();
|
||||
|
||||
static SubmarineAvailability GetAvailability(ContentXElement element)
|
||||
{
|
||||
return new SubmarineAvailability(
|
||||
LocationType: element.GetAttributeIdentifier("locationtype", Identifier.Empty),
|
||||
Class: element.GetAttributeEnum("class", SubmarineClass.Undefined),
|
||||
MaxTier: element.GetAttributeInt("maxtier", 0));
|
||||
}
|
||||
}
|
||||
|
||||
public static Identifier ParseIdentifier(ContentXElement element)
|
||||
@@ -47,6 +77,31 @@ namespace Barotrauma
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass subClass, Identifier locationType)
|
||||
{
|
||||
if (!submarineAvailability.HasValue)
|
||||
{
|
||||
// If the availability is not explicitly defined, make all subs available
|
||||
return SubmarineInfo.HighestTier;
|
||||
}
|
||||
int maxTier = submarineAvailability.Value.MaxTier;
|
||||
if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == locationType && a.Class == subClass) is SubmarineAvailability locationAndClassOverride)
|
||||
{
|
||||
maxTier = locationAndClassOverride.MaxTier;
|
||||
}
|
||||
else if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == locationType && a.Class == SubmarineClass.Undefined) is SubmarineAvailability locationOverride)
|
||||
{
|
||||
maxTier = locationOverride.MaxTier;
|
||||
}
|
||||
else if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == Identifier.Empty && a.Class == subClass) is SubmarineAvailability classOverride)
|
||||
{
|
||||
maxTier = classOverride.MaxTier;
|
||||
}
|
||||
return maxTier;
|
||||
}
|
||||
|
||||
public bool IsSubmarineAvailable(SubmarineInfo info, Identifier locationType) => info.Tier <= HighestSubmarineTierAvailable(info.SubmarineClass, locationType);
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -228,6 +228,9 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 edgeDiff = edge.Point2 - edge.Point1;
|
||||
Vector2 edgeDir = Vector2.Normalize(edgeDiff);
|
||||
|
||||
//If the edge is next to an empty cell and there's another solid cell at the other side of the empty one,
|
||||
//don't touch this edge. Otherwise we may end up closing off small passages between cells.
|
||||
var adjacentEmptyCell = edge.AdjacentCell(cell);
|
||||
@@ -238,8 +241,10 @@ namespace Barotrauma
|
||||
//find the edge at the opposite side of the adjacent cell
|
||||
foreach (GraphEdge otherEdge in adjacentEmptyCell.Edges)
|
||||
{
|
||||
if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) > 0 &&
|
||||
otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType != CellType.Solid)
|
||||
if (otherEdge == edge || otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType != CellType.Solid) { continue; }
|
||||
Vector2 otherEdgeDir = Vector2.Normalize(otherEdge.Point2 - otherEdge.Point1);
|
||||
//dot product is > 0.7 if the edges are roughly parallel
|
||||
if (Math.Abs(Vector2.Dot(otherEdgeDir, edgeDir)) > 0.7f)
|
||||
{
|
||||
adjacentEdge = otherEdge;
|
||||
break;
|
||||
@@ -251,13 +256,11 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
List<Vector2> edgePoints = new List<Vector2>();
|
||||
Vector2 edgeNormal = edge.GetNormal(cell);
|
||||
|
||||
float edgeLength = Vector2.Distance(edge.Point1, edge.Point2);
|
||||
int pointCount = (int)Math.Max(Math.Ceiling(edgeLength / minEdgeLength), 1);
|
||||
Vector2 edgeDir = edge.Point2 - edge.Point1;
|
||||
for (int i = 0; i <= pointCount; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
@@ -275,7 +278,7 @@ namespace Barotrauma
|
||||
float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.ServerAndClient);
|
||||
Vector2 extrudedPoint =
|
||||
edge.Point1 +
|
||||
edgeDir * (i / (float)pointCount) +
|
||||
edgeDiff * (i / (float)pointCount) +
|
||||
edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF;
|
||||
|
||||
var nearbyCells = Level.Loaded.GetCells(extrudedPoint, searchDepth: 2);
|
||||
|
||||
@@ -447,7 +447,7 @@ namespace Barotrauma
|
||||
|
||||
private Level(LevelData levelData) : base(null, 0)
|
||||
{
|
||||
this.LevelData = levelData;
|
||||
LevelData = levelData;
|
||||
borders = new Rectangle(Point.Zero, levelData.Size);
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ namespace Barotrauma
|
||||
if (Rand.Range(0, 10, Rand.RandSync.ServerAndClient) != 0) { continue; }
|
||||
}
|
||||
|
||||
if (!TooClose(siteX, siteY))
|
||||
if (!TooCloseToOtherSites(siteX, siteY))
|
||||
{
|
||||
siteCoordsX.Add(siteX);
|
||||
siteCoordsY.Add(siteY);
|
||||
@@ -717,14 +717,14 @@ namespace Barotrauma
|
||||
|
||||
if (closeToCave)
|
||||
{
|
||||
for (int x2 = x; x2 < x + siteInterval.X; x2 += caveSiteInterval)
|
||||
for (int x2 = x - siteInterval.X; x2 < x + siteInterval.X; x2 += caveSiteInterval)
|
||||
{
|
||||
for (int y2 = y; y2 < y + siteInterval.Y; y2 += caveSiteInterval)
|
||||
for (int y2 = y - siteInterval.Y; y2 < y + siteInterval.Y; y2 += caveSiteInterval)
|
||||
{
|
||||
int caveSiteX = x2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.ServerAndClient);
|
||||
int caveSiteY = y2 + Rand.Int(caveSiteInterval / 2, Rand.RandSync.ServerAndClient);
|
||||
|
||||
if (!TooClose(caveSiteX, caveSiteY))
|
||||
if (!TooCloseToOtherSites(caveSiteX, caveSiteY, caveSiteInterval))
|
||||
{
|
||||
siteCoordsX.Add(caveSiteX);
|
||||
siteCoordsY.Add(caveSiteY);
|
||||
@@ -735,11 +735,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool TooClose(double siteX, double siteY)
|
||||
bool TooCloseToOtherSites(double siteX, double siteY, float minDistance = 10.0f)
|
||||
{
|
||||
float minDistanceSqr = minDistance * minDistance;
|
||||
for (int i = 0; i < siteCoordsX.Count; i++)
|
||||
{
|
||||
if (MathUtils.DistanceSquared(siteCoordsX[i], siteCoordsY[i], siteX, siteY) < 10.0f * 10.0f)
|
||||
if (MathUtils.DistanceSquared(siteCoordsX[i], siteCoordsY[i], siteX, siteY) < minDistanceSqr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2539,7 +2540,8 @@ namespace Barotrauma
|
||||
levelResources.Add((itemPrefab, commonnessInfo));
|
||||
}
|
||||
else if (itemPrefab.LevelQuantity.TryGetValue(GenerationParams.Identifier, out var fixedQuantityResourceInfo) ||
|
||||
itemPrefab.LevelQuantity.TryGetValue(Identifier.Empty, out fixedQuantityResourceInfo))
|
||||
itemPrefab.LevelQuantity.TryGetValue(LevelData.Biome.Identifier, out fixedQuantityResourceInfo) ||
|
||||
itemPrefab.LevelQuantity.TryGetValue(Identifier.Empty, out fixedQuantityResourceInfo))
|
||||
{
|
||||
fixedResources.Add((itemPrefab, fixedQuantityResourceInfo));
|
||||
}
|
||||
@@ -3939,34 +3941,14 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SubmarineInfo outpostInfo;
|
||||
Submarine outpost;
|
||||
Submarine outpost = null;
|
||||
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
|
||||
{
|
||||
if (OutpostGenerationParams.OutpostParams.Any() || LevelData.ForceOutpostGenerationParams != null)
|
||||
if (LevelData.OutpostGenerationParamsExist)
|
||||
{
|
||||
Location location = i == 0 ? StartLocation : EndLocation;
|
||||
|
||||
OutpostGenerationParams outpostGenerationParams = null;
|
||||
if (LevelData.ForceOutpostGenerationParams != null)
|
||||
{
|
||||
outpostGenerationParams = LevelData.ForceOutpostGenerationParams;
|
||||
}
|
||||
else
|
||||
{
|
||||
var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any());
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters.");
|
||||
suitableParams = OutpostGenerationParams.OutpostParams;
|
||||
}
|
||||
}
|
||||
|
||||
outpostGenerationParams = suitableParams.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
OutpostGenerationParams outpostGenerationParams = LevelData.ForceOutpostGenerationParams ??
|
||||
LevelData.GetSuitableOutpostGenerationParams(location).GetRandom(Rand.RandSync.ServerAndClient);
|
||||
LocationType locationType = location?.Type;
|
||||
if (locationType == null)
|
||||
{
|
||||
@@ -4324,6 +4306,10 @@ namespace Barotrauma
|
||||
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
|
||||
// Deduce the job from the selected prefab
|
||||
selectedPrefab = GetCorpsePrefab(usedJobs);
|
||||
if (selectedPrefab != null)
|
||||
{
|
||||
job = selectedPrefab.GetJobPrefab();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedPrefab == null) { continue; }
|
||||
|
||||
@@ -57,9 +57,19 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public int? MinMainPathWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Events that have previously triggered in this level. Used for making events the player hasn't seen yet more likely to trigger when re-entering the level. Has a maximum size of <see cref="EventManager.MaxEventHistory"/>.
|
||||
/// </summary>
|
||||
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
|
||||
|
||||
/// <summary>
|
||||
/// Events that have already triggered in this level and can never trigger again. <see cref="EventSet.OncePerLevel"/>.
|
||||
/// </summary>
|
||||
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
|
||||
|
||||
/// <summary>
|
||||
/// 'Exhaustible' sets won't appear in the same level until after one world step (~10 min, see Map.ProgressWorld) has passed. <see cref="EventSet.Exhaustible"/>.
|
||||
/// </summary>
|
||||
public bool EventsExhausted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -146,7 +156,6 @@ namespace Barotrauma
|
||||
EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates level data using the properties of the connection (seed, size, difficulty)
|
||||
/// </summary>
|
||||
@@ -243,6 +252,23 @@ namespace Barotrauma
|
||||
return levelData;
|
||||
}
|
||||
|
||||
public bool OutpostGenerationParamsExist => ForceOutpostGenerationParams != null || OutpostGenerationParams.OutpostParams.Any();
|
||||
|
||||
public static IEnumerable<OutpostGenerationParams> GetSuitableOutpostGenerationParams(Location location)
|
||||
{
|
||||
var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any());
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters.");
|
||||
suitableParams = OutpostGenerationParams.OutpostParams;
|
||||
}
|
||||
}
|
||||
return suitableParams;
|
||||
}
|
||||
|
||||
public void Save(XElement parentElement)
|
||||
{
|
||||
var newElement = new XElement("Level",
|
||||
@@ -284,6 +310,7 @@ namespace Barotrauma
|
||||
newElement.Add(new XAttribute("nonrepeatableevents", string.Join(',', NonRepeatableEvents.Select(p => p.Identifier))));
|
||||
}
|
||||
}
|
||||
|
||||
parentElement.Add(newElement);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-25
@@ -542,38 +542,41 @@ namespace Barotrauma
|
||||
GlobalForceDecreaseTimer = 0.0f;
|
||||
}
|
||||
|
||||
foreach (LevelObject obj in updateableObjects)
|
||||
if (updateableObjects is not null)
|
||||
{
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
foreach (LevelObject obj in updateableObjects)
|
||||
{
|
||||
obj.NetworkUpdateTimer -= deltaTime;
|
||||
if (obj.NeedsNetworkSyncing && obj.NetworkUpdateTimer <= 0.0f)
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new EventData(obj));
|
||||
obj.NeedsNetworkSyncing = false;
|
||||
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
|
||||
}
|
||||
}
|
||||
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
|
||||
|
||||
if (obj.Triggers != null)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; i++)
|
||||
{
|
||||
obj.Triggers[i].Update(deltaTime);
|
||||
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
|
||||
obj.NetworkUpdateTimer -= deltaTime;
|
||||
if (obj.NeedsNetworkSyncing && obj.NetworkUpdateTimer <= 0.0f)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new EventData(obj));
|
||||
obj.NeedsNetworkSyncing = false;
|
||||
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
|
||||
|
||||
if (obj.PhysicsBody != null)
|
||||
{
|
||||
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) { obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered; }
|
||||
/*obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
|
||||
obj.Rotation = -obj.PhysicsBody.Rotation;*/
|
||||
if (obj.Triggers != null)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab;
|
||||
for (int i = 0; i < obj.Triggers.Count; i++)
|
||||
{
|
||||
obj.Triggers[i].Update(deltaTime);
|
||||
if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
|
||||
{
|
||||
obj.ActivePrefab = obj.Prefab.OverrideProperties[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.PhysicsBody != null)
|
||||
{
|
||||
if (obj.Prefab.PhysicsBodyTriggerIndex > -1) { obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered; }
|
||||
/*obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
|
||||
obj.Rotation = -obj.PhysicsBody.Rotation;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -287,6 +287,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable]
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Name => Identifier.Value;
|
||||
|
||||
public List<ChildObject> ChildObjects
|
||||
|
||||
@@ -661,7 +661,7 @@ namespace Barotrauma
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
|
||||
effect.AddNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effect.type, deltaTime, triggerer, targets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ namespace Barotrauma
|
||||
if (wall.Submarine != sub) { continue; }
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.SetDamage(i, 0, createNetworkEvent: false);
|
||||
wall.SetDamage(i, 0, createNetworkEvent: false, createExplosionEffect: false);
|
||||
}
|
||||
}
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma
|
||||
|
||||
private LocationType addInitialMissionsForType;
|
||||
|
||||
public bool Discovered { get; private set; }
|
||||
public bool Discovered => GameMain.GameSession?.Map?.IsDiscovered(this) ?? false;
|
||||
|
||||
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
|
||||
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
|
||||
@@ -135,7 +135,7 @@ namespace Barotrauma
|
||||
foreach (var stockElement in storeElement.GetChildElements("stock"))
|
||||
{
|
||||
var identifier = stockElement.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
if (identifier.IsEmpty || !(ItemPrefab.FindByIdentifier(identifier) is ItemPrefab prefab)) { continue; }
|
||||
if (identifier.IsEmpty || ItemPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab) { continue; }
|
||||
int qty = stockElement.GetAttributeInt("qty", 0);
|
||||
if (qty < 1) { continue; }
|
||||
Stock.Add(new PurchasedItem(prefab, qty, buyer: null));
|
||||
@@ -157,7 +157,7 @@ namespace Barotrauma
|
||||
foreach (var childElement in element.GetChildElements("item"))
|
||||
{
|
||||
var id = childElement.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
if (id.IsEmpty || !(ItemPrefab.FindByIdentifier(id) is ItemPrefab prefab)) { continue; }
|
||||
if (id.IsEmpty || ItemPrefab.FindByIdentifier(id) is not ItemPrefab prefab) { continue; }
|
||||
specials.Add(prefab);
|
||||
}
|
||||
return specials;
|
||||
@@ -240,7 +240,7 @@ namespace Barotrauma
|
||||
availableStock.Add(stockItem.ItemPrefab, weight);
|
||||
}
|
||||
DailySpecials.Clear();
|
||||
int extraSpecialSalesCount = Location.GetExtraSpecialSalesCount();
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
for (int i = 0; i < Location.DailySpecialsCount + extraSpecialSalesCount; i++)
|
||||
{
|
||||
if (availableStock.None()) { break; }
|
||||
@@ -283,6 +283,17 @@ namespace Barotrauma
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
price *= Location.GetStoreReputationModifier(true);
|
||||
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
{
|
||||
if (Location.Reputation?.Faction is { } faction && faction.GetPlayerAffiliationStatus() is FactionAffiliation.Affiliated)
|
||||
{
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated));
|
||||
}
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
|
||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
|
||||
}
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
@@ -303,6 +314,14 @@ namespace Barotrauma
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
price *= Location.GetStoreReputationModifier(false);
|
||||
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
{
|
||||
price *= 1f + characters.Max(static c => c.GetStatValue(StatTypes.StoreSellMultiplier, includeSaved: false));
|
||||
price *= 1f + characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreSellMultiplier, tag)));
|
||||
}
|
||||
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
@@ -478,7 +497,6 @@ namespace Barotrauma
|
||||
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);
|
||||
@@ -641,7 +659,7 @@ namespace Barotrauma
|
||||
return new Location(position, zone, rand, requireOutpost, forceLocationType, existingLocations);
|
||||
}
|
||||
|
||||
public void ChangeType(LocationType newType)
|
||||
public void ChangeType(LocationType newType, bool createStores = true)
|
||||
{
|
||||
if (newType == Type) { return; }
|
||||
|
||||
@@ -665,7 +683,10 @@ namespace Barotrauma
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandomUnsynced());
|
||||
}
|
||||
|
||||
CreateStores(force: true);
|
||||
if (createStores)
|
||||
{
|
||||
CreateStores(force: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnlockInitialMissions()
|
||||
@@ -1125,7 +1146,7 @@ namespace Barotrauma
|
||||
public void UpdateStores()
|
||||
{
|
||||
// In multiplayer, stores should be updated by the server and loaded from save data by clients
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
if (Stores == null)
|
||||
{
|
||||
CreateStores();
|
||||
@@ -1167,13 +1188,10 @@ namespace Barotrauma
|
||||
stockToRemove.ForEach(i => stock.Remove(i));
|
||||
store.Stock.Clear();
|
||||
store.Stock.AddRange(stock);
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval || store.DailySpecials.Count() != DailySpecialsCount + extraSpecialSalesCount)
|
||||
{
|
||||
store.GenerateSpecials();
|
||||
}
|
||||
store.GeneratePriceModifier();
|
||||
}
|
||||
|
||||
StepsSinceSpecialsUpdated++;
|
||||
foreach (var identifier in storesToRemove)
|
||||
{
|
||||
Stores.Remove(identifier);
|
||||
@@ -1184,6 +1202,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSpecials()
|
||||
{
|
||||
if (GameMain.NetworkMember is { IsClient: true } || Stores is null) { return; }
|
||||
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
|
||||
foreach (StoreInfo store in Stores.Values)
|
||||
{
|
||||
if (StepsSinceSpecialsUpdated < SpecialsUpdateInterval && store.DailySpecials.Count == DailySpecialsCount + extraSpecialSalesCount) { continue; }
|
||||
|
||||
store.GenerateSpecials();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStoreIdentifiers()
|
||||
{
|
||||
StoreIdentifiers.Clear();
|
||||
@@ -1255,21 +1287,37 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public int GetExtraSpecialSalesCount()
|
||||
public static int GetExtraSpecialSalesCount()
|
||||
{
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (!characters.Any()) { return 0; }
|
||||
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
return characters.Max(static c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
}
|
||||
|
||||
public void Discover(bool checkTalents = true)
|
||||
public bool CanHaveSubsForSale()
|
||||
{
|
||||
if (Discovered) { return; }
|
||||
Discovered = true;
|
||||
if (checkTalents)
|
||||
return HasOutpost() && CanHaveCampaignInteraction(CampaignMode.InteractionType.PurchaseSub);
|
||||
}
|
||||
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass submarineClass = SubmarineClass.Undefined)
|
||||
{
|
||||
if (CanHaveSubsForSale())
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
|
||||
return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool IsSubmarineAvailable(SubmarineInfo info)
|
||||
{
|
||||
return Biome?.IsSubmarineAvailable(info, Type.Identifier) ?? true;
|
||||
}
|
||||
|
||||
private bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType)
|
||||
{
|
||||
return LevelData != null &&
|
||||
LevelData.OutpostGenerationParamsExist &&
|
||||
LevelData.GetSuitableOutpostGenerationParams(this).Any(p => p.CanHaveCampaignInteraction(interactionType));
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
@@ -1283,7 +1331,6 @@ namespace Barotrauma
|
||||
ClearMissions();
|
||||
LevelData?.EventHistory?.Clear();
|
||||
UnlockInitialMissions();
|
||||
Discovered = false;
|
||||
}
|
||||
|
||||
public XElement Save(Map map, XElement parentElement)
|
||||
@@ -1294,7 +1341,6 @@ namespace Barotrauma
|
||||
new XAttribute("basename", BaseName),
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("biome", Biome?.Identifier.Value ?? string.Empty),
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("isgatebetweenbiomes", IsGateBetweenBiomes),
|
||||
@@ -1423,7 +1469,7 @@ namespace Barotrauma
|
||||
HireManager?.Remove();
|
||||
}
|
||||
|
||||
class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
public class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
{
|
||||
public AbilityLocation(Location location)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Barotrauma
|
||||
public readonly Dictionary<int, int> MinCountPerZone = new Dictionary<int, int>();
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
|
||||
public readonly float BeaconStationChance;
|
||||
|
||||
@@ -70,6 +71,13 @@ namespace Barotrauma
|
||||
public Sprite Sprite { get; private set; }
|
||||
public Sprite RadiationSprite { get; }
|
||||
|
||||
private readonly Identifier forceOutpostGenerationParamsIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, only event sets that explicitly define this location type in <see cref="EventSet.LocationTypeIdentifiers"/> can be selected at this location. Defaults to false.
|
||||
/// </summary>
|
||||
public bool IgnoreGenericEvents { get; }
|
||||
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
@@ -77,9 +85,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public float StoreMaxReputationModifier { get; } = 0.1f;
|
||||
public float StoreSellPriceModifier { get; } = 0.8f;
|
||||
public float StoreSellPriceModifier { get; } = 0.3f;
|
||||
public float DailySpecialPriceModifier { get; } = 0.5f;
|
||||
public float RequestGoodPriceModifier { get; } = 1.5f;
|
||||
public float RequestGoodPriceModifier { get; } = 2f;
|
||||
public int StoreInitialBalance { get; } = 5000;
|
||||
/// <summary>
|
||||
/// In percentages
|
||||
@@ -96,6 +104,7 @@ namespace Barotrauma
|
||||
public LocationType(ContentXElement element, LocationTypesFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
|
||||
{
|
||||
Name = TextManager.Get("LocationName." + Identifier, "unknown");
|
||||
Description = TextManager.Get("LocationDescription." + Identifier, "");
|
||||
|
||||
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
|
||||
|
||||
@@ -110,6 +119,10 @@ namespace Barotrauma
|
||||
|
||||
ReplaceInRadiation = element.GetAttributeIdentifier(nameof(ReplaceInRadiation), Identifier.Empty);
|
||||
|
||||
forceOutpostGenerationParamsIdentifier = element.GetAttributeIdentifier("forceoutpostgenerationparams", Identifier.Empty);
|
||||
|
||||
IgnoreGenericEvents = element.GetAttributeBool(nameof(IgnoreGenericEvents), false);
|
||||
|
||||
string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
|
||||
Enum.TryParse(teamStr, out OutpostTeam);
|
||||
|
||||
@@ -261,6 +274,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public OutpostGenerationParams GetForcedOutpostGenerationParams()
|
||||
{
|
||||
if (OutpostGenerationParams.OutpostParams.TryGet(forceOutpostGenerationParamsIdentifier, out var parameters))
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +68,15 @@ namespace Barotrauma
|
||||
|
||||
public List<Location> Locations { get; private set; }
|
||||
|
||||
private readonly List<Location> locationsDiscovered = new List<Location>();
|
||||
private readonly List<Location> outpostsVisited = new List<Location>();
|
||||
|
||||
public List<LocationConnection> Connections { get; private set; }
|
||||
|
||||
public Radiation Radiation;
|
||||
|
||||
private bool wasLocationDiscoveryOrderTracked = true;
|
||||
|
||||
public Map(CampaignSettings settings)
|
||||
{
|
||||
generationParams = MapGenerationParams.Instance;
|
||||
@@ -282,7 +287,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLocation.Discover(true);
|
||||
if (campaign.IsSinglePlayer && campaign.Settings.TutorialEnabled && LocationType.Prefabs.TryGet("tutorialoutpost", out var tutorialOutpost))
|
||||
{
|
||||
CurrentLocation.ChangeType(tutorialOutpost);
|
||||
}
|
||||
Discover(CurrentLocation);
|
||||
Visit(CurrentLocation);
|
||||
CurrentLocation.CreateStores();
|
||||
|
||||
foreach (var location in Locations)
|
||||
@@ -542,7 +552,8 @@ namespace Barotrauma
|
||||
Connections[i].Locations[1];
|
||||
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier == "abandoned")
|
||||
{
|
||||
leftMostLocation.ChangeType(LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
|
||||
leftMostLocation.ChangeType(LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"),
|
||||
createStores: false);
|
||||
}
|
||||
leftMostLocation.IsGateBetweenBiomes = true;
|
||||
Connections[i].Locked = true;
|
||||
@@ -618,6 +629,7 @@ namespace Barotrauma
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location, CalculateDifficulty(location.MapPosition.X, location.Biome));
|
||||
location.CreateStores(force: true);
|
||||
}
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
@@ -724,7 +736,7 @@ namespace Barotrauma
|
||||
|
||||
if (LocationType.Prefabs.TryGet("none", out LocationType locationType))
|
||||
{
|
||||
previousToEndLocation.ChangeType(locationType);
|
||||
previousToEndLocation.ChangeType(locationType, createStores: false);
|
||||
}
|
||||
|
||||
//remove all locations from the end biome except the end location
|
||||
@@ -820,7 +832,8 @@ namespace Barotrauma
|
||||
SelectedConnection.Passed = true;
|
||||
|
||||
CurrentLocation = SelectedLocation;
|
||||
CurrentLocation.Discover();
|
||||
Discover(CurrentLocation);
|
||||
Visit(CurrentLocation);
|
||||
SelectedLocation = null;
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
@@ -851,7 +864,7 @@ namespace Barotrauma
|
||||
|
||||
Location prevLocation = CurrentLocation;
|
||||
CurrentLocation = Locations[index];
|
||||
CurrentLocation.Discover();
|
||||
Discover(CurrentLocation);
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
if (prevLocation != CurrentLocation)
|
||||
@@ -982,6 +995,16 @@ namespace Barotrauma
|
||||
ProgressWorld();
|
||||
}
|
||||
|
||||
// always update specials every step
|
||||
for (int i = 0; i < Math.Max(1, steps); i++)
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Discovered) { continue; }
|
||||
location.UpdateSpecials();
|
||||
}
|
||||
}
|
||||
|
||||
Radiation?.OnStep(steps);
|
||||
}
|
||||
|
||||
@@ -1174,6 +1197,51 @@ namespace Barotrauma
|
||||
|
||||
partial void ClearAnimQueue();
|
||||
|
||||
public void Discover(Location location, bool checkTalents = true)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (locationsDiscovered.Contains(location)) { return; }
|
||||
locationsDiscovered.Add(location);
|
||||
if (checkTalents)
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new Location.AbilityLocation(location)));
|
||||
}
|
||||
}
|
||||
|
||||
public void Visit(Location location)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (!location.HasOutpost()) { return; }
|
||||
if (outpostsVisited.Contains(location)) { return; }
|
||||
outpostsVisited.Add(location);
|
||||
}
|
||||
|
||||
public void ClearLocationHistory()
|
||||
{
|
||||
locationsDiscovered.Clear();
|
||||
outpostsVisited.Clear();
|
||||
}
|
||||
|
||||
public int? GetDiscoveryIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return locationsDiscovered.IndexOf(location);
|
||||
}
|
||||
|
||||
public int? GetVisitIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return outpostsVisited.IndexOf(location);
|
||||
}
|
||||
|
||||
public bool IsDiscovered(Location location)
|
||||
{
|
||||
if (location is null) { return false; }
|
||||
return locationsDiscovered.Contains(location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
@@ -1201,6 +1269,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
ClearLocationHistory();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -1216,19 +1285,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
location.LoadLocationTypeChange(subElement);
|
||||
|
||||
// Backwards compatibility
|
||||
if (subElement.GetAttributeBool("discovered", false))
|
||||
{
|
||||
location.Discover(checkTalents: false);
|
||||
}
|
||||
if (location.Discovered)
|
||||
{
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(location);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
Discover(location);
|
||||
wasLocationDiscoveryOrderTracked = false;
|
||||
}
|
||||
|
||||
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
@@ -1258,6 +1320,36 @@ namespace Barotrauma
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
break;
|
||||
case "discovered":
|
||||
foreach (var childElement in subElement.GetChildElements("location"))
|
||||
{
|
||||
int index = childElement.GetAttributeInt("i", -1);
|
||||
if (index < 0) { continue; }
|
||||
if (Locations[index] is not Location l) { continue; }
|
||||
Discover(l);
|
||||
}
|
||||
break;
|
||||
case "visited":
|
||||
foreach (var childElement in subElement.GetChildElements("location"))
|
||||
{
|
||||
int index = childElement.GetAttributeInt("i", -1);
|
||||
if (index < 0) { continue; }
|
||||
if (Locations[index] is not Location l) { continue; }
|
||||
Visit(l);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Discover(Location location)
|
||||
{
|
||||
this.Discover(location, checkTalents: false);
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(location);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1333,6 +1425,30 @@ namespace Barotrauma
|
||||
mapElement.Add(Radiation.Save());
|
||||
}
|
||||
|
||||
if (locationsDiscovered.Any())
|
||||
{
|
||||
var discoveryElement = new XElement("discovered");
|
||||
foreach (Location location in locationsDiscovered)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
discoveryElement.Add(locationElement);
|
||||
}
|
||||
mapElement.Add(discoveryElement);
|
||||
}
|
||||
|
||||
if (outpostsVisited.Any())
|
||||
{
|
||||
var visitElement = new XElement("visited");
|
||||
foreach (Location location in outpostsVisited)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
visitElement.Add(locationElement);
|
||||
}
|
||||
mapElement.Add(visitElement);
|
||||
}
|
||||
|
||||
element.Add(mapElement);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Barotrauma
|
||||
[Flags]
|
||||
enum MapEntityCategory
|
||||
{
|
||||
None = 0,
|
||||
Structure = 1,
|
||||
Decorative = 2,
|
||||
Machine = 4,
|
||||
|
||||
@@ -96,6 +96,8 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
public ContentPath OutpostFilePath { get; set; }
|
||||
|
||||
public class ModuleCount
|
||||
{
|
||||
public Identifier Identifier;
|
||||
@@ -182,6 +184,7 @@ namespace Barotrauma
|
||||
Name = element.GetAttributeString("name", Identifier.Value);
|
||||
allowedLocationTypes = element.GetAttributeIdentifierArray("allowedlocationtypes", Array.Empty<Identifier>()).ToHashSet();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
OutpostFilePath = element.GetAttributeContentPath(nameof(OutpostFilePath));
|
||||
|
||||
var humanPrefabCollections = new List<IReadOnlyList<HumanPrefab>>();
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -257,6 +260,21 @@ namespace Barotrauma
|
||||
return humanPrefabCollections.GetRandom(randSync);
|
||||
}
|
||||
|
||||
public bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType)
|
||||
{
|
||||
foreach (var collection in humanPrefabCollections)
|
||||
{
|
||||
foreach (var prefab in collection)
|
||||
{
|
||||
if (prefab.CampaignInteractionType == interactionType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ImmutableHashSet<Identifier> GetStoreIdentifiers()
|
||||
{
|
||||
if (StoreIdentifiers == null)
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Barotrauma
|
||||
//select which module types the outpost should consist of
|
||||
List<Identifier> pendingModuleFlags =
|
||||
onlyEntrance ?
|
||||
generationParams.ModuleCounts.First().Identifier.ToEnumerable().ToList() :
|
||||
(generationParams.ModuleCounts.FirstOrDefault()?.Identifier.ToEnumerable() ?? Enumerable.Empty<Identifier>()).ToList() :
|
||||
SelectModules(outpostModules, generationParams);
|
||||
|
||||
foreach (Identifier flag in pendingModuleFlags)
|
||||
@@ -246,6 +246,7 @@ namespace Barotrauma
|
||||
|
||||
var outpostFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<OutpostFile>())
|
||||
.Where(f => !TutorialPrefab.Prefabs.Any(tp => tp.OutpostPath == f.Path))
|
||||
.OrderBy(f => f.UintIdentifier).ToArray();
|
||||
if (!outpostFiles.Any())
|
||||
{
|
||||
@@ -696,6 +697,14 @@ namespace Barotrauma
|
||||
rect.Location += (module.Offset + module.MoveOffset).ToPoint();
|
||||
rect.Y += module.Bounds.Height;
|
||||
|
||||
Vector2? selfGapPos1 = null;
|
||||
Vector2? selfGapPos2 = null;
|
||||
if (module.PreviousModule != null)
|
||||
{
|
||||
selfGapPos1 = module.Offset + module.ThisGap.Position + module.MoveOffset;
|
||||
selfGapPos2 = module.PreviousModule.Offset + module.PreviousGap.Position + module.PreviousModule.MoveOffset;
|
||||
}
|
||||
|
||||
foreach (PlacedModule otherModule in modules)
|
||||
{
|
||||
if (otherModule == module || otherModule.PreviousModule == null || otherModule.PreviousModule == module) { continue; }
|
||||
@@ -710,7 +719,17 @@ namespace Barotrauma
|
||||
|
||||
Vector2 gapPos1 = otherModule.Offset + otherModule.ThisGap.Position + gapEdgeOffset + otherModule.MoveOffset;
|
||||
Vector2 gapPos2 = otherModule.PreviousModule.Offset + otherModule.PreviousGap.Position + gapEdgeOffset + otherModule.PreviousModule.MoveOffset;
|
||||
if (Submarine.RectContains(rect, gapPos1) || Submarine.RectContains(rect, gapPos2) || MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
|
||||
if (Submarine.RectContains(rect, gapPos1) ||
|
||||
Submarine.RectContains(rect, gapPos2) ||
|
||||
MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//check if the connection overlaps with this module's connection
|
||||
if (selfGapPos1.HasValue && selfGapPos2.HasValue &&
|
||||
!gapPos1.NearlyEquals(gapPos2) && !selfGapPos1.Value.NearlyEquals(selfGapPos2.Value) &&
|
||||
MathUtils.LinesIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace Barotrauma
|
||||
public bool DisplayNonEmpty { get; } = false;
|
||||
public Identifier StoreIdentifier { get; }
|
||||
|
||||
public bool RequiresUnlock { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used when both <see cref="MinAvailableAmount"/> and <see cref="MaxAvailableAmount"/> are set to 0.
|
||||
/// </summary>
|
||||
@@ -48,11 +50,12 @@ namespace Barotrauma
|
||||
int maxAmount = GetMaxAmount(element);
|
||||
maxAmount = Math.Min(maxAmount, CargoManager.MaxQuantity);
|
||||
MaxAvailableAmount = Math.Max(maxAmount, MinAvailableAmount);
|
||||
RequiresUnlock = element.GetAttributeBool("requiresunlock", false);
|
||||
}
|
||||
|
||||
public PriceInfo(int price, bool canBeBought,
|
||||
int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f,
|
||||
bool displayNonEmpty = false, string storeIdentifier = null)
|
||||
bool displayNonEmpty = false, bool requiresUnlock = false, string storeIdentifier = null)
|
||||
{
|
||||
Price = price;
|
||||
CanBeBought = canBeBought;
|
||||
@@ -64,6 +67,7 @@ namespace Barotrauma
|
||||
CanBeSpecial = canBeSpecial;
|
||||
DisplayNonEmpty = displayNonEmpty;
|
||||
StoreIdentifier = new Identifier(storeIdentifier);
|
||||
RequiresUnlock = requiresUnlock;
|
||||
}
|
||||
|
||||
public static List<PriceInfo> CreatePriceInfos(XElement element, out PriceInfo defaultPrice)
|
||||
@@ -78,6 +82,7 @@ namespace Barotrauma
|
||||
float buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
bool displayNonEmpty = element.GetAttributeBool("displaynonempty", false);
|
||||
bool soldByDefault = element.GetAttributeBool("sold", element.GetAttributeBool("soldbydefault", true));
|
||||
bool requiresUnlock = element.GetAttributeBool("requiresunlock", false);
|
||||
foreach (XElement childElement in element.GetChildElements("price"))
|
||||
{
|
||||
float priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
@@ -91,26 +96,28 @@ namespace Barotrauma
|
||||
}
|
||||
string storeIdentifier = childElement.GetAttributeString("storeidentifier", backwardsCompatibleIdentifier);
|
||||
// TODO: Add some error messages if we have defined the min or max amount while the item is not sold
|
||||
var priceInfo = new PriceInfo((int)(priceMultiplier * basePrice),
|
||||
sold,
|
||||
sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial,
|
||||
storeMinLevelDifficulty,
|
||||
storeBuyingMultiplier,
|
||||
displayNonEmpty,
|
||||
storeIdentifier);
|
||||
var priceInfo = new PriceInfo(price: (int)(priceMultiplier * basePrice),
|
||||
canBeBought: sold,
|
||||
minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial: canBeSpecial,
|
||||
minLevelDifficulty: storeMinLevelDifficulty,
|
||||
buyingPriceMultiplier: storeBuyingMultiplier,
|
||||
displayNonEmpty: displayNonEmpty,
|
||||
requiresUnlock: requiresUnlock,
|
||||
storeIdentifier: storeIdentifier);
|
||||
priceInfos.Add(priceInfo);
|
||||
}
|
||||
bool soldElsewhere = soldByDefault && element.GetAttributeBool("soldelsewhere", element.GetAttributeBool("soldeverywhere", false));
|
||||
defaultPrice = new PriceInfo(basePrice,
|
||||
soldElsewhere,
|
||||
soldElsewhere ? minAmount : 0,
|
||||
soldElsewhere ? maxAmount : 0,
|
||||
canBeSpecial,
|
||||
minLevelDifficulty,
|
||||
buyingPriceMultiplier,
|
||||
displayNonEmpty);
|
||||
defaultPrice = new PriceInfo(price: basePrice,
|
||||
canBeBought: soldElsewhere,
|
||||
minAmount: soldElsewhere ? minAmount : 0,
|
||||
maxAmount: soldElsewhere ? maxAmount : 0,
|
||||
canBeSpecial: canBeSpecial,
|
||||
minLevelDifficulty: minLevelDifficulty,
|
||||
buyingPriceMultiplier: buyingPriceMultiplier,
|
||||
displayNonEmpty: displayNonEmpty,
|
||||
requiresUnlock: requiresUnlock);
|
||||
return priceInfos;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace Barotrauma
|
||||
//dimensions of the wall sections' physics bodies (only used for debug rendering)
|
||||
private readonly List<Vector2> bodyDebugDimensions = new List<Vector2>();
|
||||
|
||||
private static Explosion explosionOnBroken;
|
||||
|
||||
#if DEBUG
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
#else
|
||||
@@ -1083,7 +1085,7 @@ namespace Barotrauma
|
||||
return new AttackResult(damageAmount, null);
|
||||
}
|
||||
|
||||
public void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true)
|
||||
public void SetDamage(int sectionIndex, float damage, Character attacker = null, bool createNetworkEvent = true, bool createExplosionEffect = true)
|
||||
{
|
||||
if (Submarine != null && Submarine.GodMode || Indestructible) { return; }
|
||||
if (!Prefab.Body) { return; }
|
||||
@@ -1128,6 +1130,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float prevGapOpenState = Sections[sectionIndex].gap?.Open ?? 0.0f;
|
||||
if (Sections[sectionIndex].gap == null)
|
||||
{
|
||||
Rectangle gapRect = Sections[sectionIndex].rect;
|
||||
@@ -1204,8 +1207,15 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
var gap = Sections[sectionIndex].gap;
|
||||
float gapOpen = MaxHealth <= 0.0f ? 0.0f : (damage / MaxHealth - LeakThreshold) * (1.0f / (1.0f - LeakThreshold));
|
||||
Sections[sectionIndex].gap.Open = gapOpen;
|
||||
gap.Open = gapOpen;
|
||||
|
||||
//gap appeared or became much larger -> explosion effect
|
||||
if (gapOpen - prevGapOpenState > 0.25f && createExplosionEffect && !gap.IsRoomToRoom)
|
||||
{
|
||||
CreateWallDamageExplosion(gap, attacker);
|
||||
}
|
||||
}
|
||||
|
||||
float damageDiff = damage - Sections[sectionIndex].damage;
|
||||
@@ -1234,6 +1244,59 @@ namespace Barotrauma
|
||||
UpdateSections();
|
||||
}
|
||||
|
||||
private void CreateWallDamageExplosion(Gap gap, Character attacker)
|
||||
{
|
||||
const float explosionRange = 750.0f;
|
||||
float explosionStrength = gap.Open;
|
||||
|
||||
var linkedHull = gap.linkedTo.FirstOrDefault() as Hull;
|
||||
if (linkedHull != null)
|
||||
{
|
||||
//existing, nearby gaps leading to the same hull reduce the strength of the explosion
|
||||
// -> the first breached section does most (or all) of the damage, making it more consistent
|
||||
// (otherwise the damage would depend on how many structures and sections happen to be breached)
|
||||
foreach (var otherGap in linkedHull.ConnectedGaps)
|
||||
{
|
||||
if (otherGap == gap || otherGap.IsRoomToRoom || otherGap.Open < 0.25f) { continue; }
|
||||
explosionStrength -= Math.Max(0, explosionRange - Vector2.Distance(otherGap.WorldPosition, gap.WorldPosition)) / explosionRange;
|
||||
if (explosionStrength <= 0.0f) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
if (explosionOnBroken == null)
|
||||
{
|
||||
explosionOnBroken = new Explosion(explosionRange * gap.Open, force: 10.0f, damage: 0.0f, structureDamage: 0.0f, itemDamage: 0.0f);
|
||||
if (AfflictionPrefab.Prefabs.TryGet("lacerations".ToIdentifier(), out AfflictionPrefab lacerations))
|
||||
{
|
||||
explosionOnBroken.Attack.Afflictions.Add(lacerations.Instantiate(50.0f), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
explosionOnBroken.Attack.Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(5.0f), null);
|
||||
}
|
||||
explosionOnBroken.OnlyInside = true;
|
||||
explosionOnBroken.DisableParticles();
|
||||
}
|
||||
|
||||
explosionOnBroken.Attack.DamageMultiplier = explosionStrength;
|
||||
explosionOnBroken?.Explode(gap.WorldPosition, damageSource: null, attacker: attacker);
|
||||
#if CLIENT
|
||||
if (linkedHull != null)
|
||||
{
|
||||
for (int i = 0; i <= 50; i++)
|
||||
{
|
||||
Vector2 particlePos = new Vector2(Rand.Range(gap.WorldRect.X, gap.WorldRect.Right), Rand.Range(gap.WorldRect.Y - gap.WorldRect.Height, gap.WorldRect.Y));
|
||||
var velocity = gap.IsHorizontal ?
|
||||
gap.linkedTo[0].WorldPosition.X < gap.WorldPosition.X ? -Vector2.UnitX : Vector2.UnitX :
|
||||
gap.linkedTo[0].WorldPosition.Y < gap.WorldPosition.Y ? -Vector2.UnitY : Vector2.UnitY;
|
||||
velocity = new Vector2(velocity.X + Rand.Range(-0.2f, 0.2f), velocity.Y + Rand.Range(-0.2f, 0.2f));
|
||||
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, velocity * Rand.Range(100.0f, 3000.0f), collisionIgnoreTimer: 0.1f);
|
||||
if (particle == null) { break; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
partial void OnHealthChangedProjSpecific(Character attacker, float damageAmount);
|
||||
|
||||
public void SetCollisionCategory(Category collisionCategory)
|
||||
@@ -1570,7 +1633,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < Sections.Length; i++)
|
||||
{
|
||||
SetDamage(i, Sections[i].damage, createNetworkEvent: false);
|
||||
SetDamage(i, Sections[i].damage, createNetworkEvent: false, createExplosionEffect: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1114,7 +1114,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.Submarine != this) { continue; }
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || !item.HasTag("ballast") || item.CurrentHull == null) { continue; }
|
||||
if (pump == null || item.CurrentHull == null) { continue; }
|
||||
if (!item.HasTag("ballast") && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
pump.FlowPercentage = 0.0f;
|
||||
ballastHulls.Add(item.CurrentHull);
|
||||
}
|
||||
|
||||
@@ -720,7 +720,7 @@ namespace Barotrauma
|
||||
|
||||
private void HandleLevelCollision(Impact impact, VoronoiCell cell = null)
|
||||
{
|
||||
if (GameMain.GameSession != null && Timing.TotalTime < GameMain.GameSession.RoundStartTime + 10)
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.RoundDuration < 10)
|
||||
{
|
||||
//ignore level collisions for the first 10 seconds of the round in case the sub spawns in a way that causes it to hit a wall
|
||||
//(e.g. level without outposts to dock to and an incorrectly configured ballast that makes the sub go up)
|
||||
|
||||
@@ -314,6 +314,7 @@ namespace Barotrauma
|
||||
Tier = original.Tier;
|
||||
IsManuallyOutfitted = original.IsManuallyOutfitted;
|
||||
Tags = original.Tags;
|
||||
OutpostGenerationParams = original.OutpostGenerationParams;
|
||||
if (original.OutpostModuleInfo != null)
|
||||
{
|
||||
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
|
||||
@@ -747,6 +748,8 @@ namespace Barotrauma
|
||||
return doc;
|
||||
}
|
||||
|
||||
public static int GetDefaultTier(int price) => price > 20000 ? 3 : price > 10000 ? 2 : 1;
|
||||
public static int GetDefaultTier(int price) => price > 20000 ? HighestTier : price > 10000 ? 2 : 1;
|
||||
|
||||
public const int HighestTier = 3;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user