Build 0.18.4.0
This commit is contained in:
@@ -34,14 +34,18 @@ namespace Barotrauma
|
||||
public float SoundRange
|
||||
{
|
||||
get { return soundRange; }
|
||||
set
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
DebugConsole.ThrowError("Attempted to set the SoundRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
soundRange = MathHelper.Clamp(value, MinSoundRange, MaxSoundRange);
|
||||
soundRange = MathHelper.Clamp(value, MinSoundRange, MaxSoundRange);
|
||||
if (soundRange > 0.0f && !Static && FadeOutTime > 0.0f)
|
||||
{
|
||||
NeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +59,11 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Attempted to set the SightRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange);
|
||||
sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange);
|
||||
if (sightRange > 0 && !Static && FadeOutTime > 0.0f)
|
||||
{
|
||||
NeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,13 +107,33 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool InDetectable
|
||||
{
|
||||
get => inDetectable || (SoundRange <= 0 && SightRange <= 0);
|
||||
set => inDetectable = value;
|
||||
get
|
||||
{
|
||||
return inDetectable || (SoundRange <= 0 && SightRange <= 0);
|
||||
}
|
||||
set
|
||||
{
|
||||
inDetectable = value;
|
||||
if (inDetectable)
|
||||
{
|
||||
NeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float MinSoundRange, MinSightRange;
|
||||
public float MaxSoundRange = 100000, MaxSightRange = 100000;
|
||||
|
||||
/// <summary>
|
||||
/// Does the AI target do something that requires Update() to be called (e.g. static targets don't need to be updated)
|
||||
/// </summary>
|
||||
public bool NeedsUpdate
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = true;
|
||||
|
||||
public TargetType Type { get; private set; }
|
||||
|
||||
public enum TargetType
|
||||
@@ -190,14 +218,22 @@ namespace Barotrauma
|
||||
if (!Static && FadeOutTime > 0)
|
||||
{
|
||||
// The aitarget goes silent/invisible if the components don't keep it active
|
||||
if (!StaticSight && SightRange > 0)
|
||||
if (!StaticSight && sightRange > 0)
|
||||
{
|
||||
DecreaseSightRange(deltaTime);
|
||||
}
|
||||
if (!StaticSound && SoundRange > 0)
|
||||
if (!StaticSound && soundRange > 0)
|
||||
{
|
||||
DecreaseSoundRange(deltaTime);
|
||||
}
|
||||
if (sightRange <= 0 && soundRange <= 0)
|
||||
{
|
||||
NeedsUpdate = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NeedsUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1440,14 +1440,33 @@ namespace Barotrauma
|
||||
}
|
||||
else if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && distance < AttackLimb.attack.Range * 5)
|
||||
{
|
||||
reachTimer += deltaTime;
|
||||
if (reachTimer > reachTimeOut)
|
||||
Vector2 targetVelocity = Vector2.Zero;
|
||||
Submarine targetSub = SelectedAiTarget.Entity.Submarine;
|
||||
if (targetSub != null)
|
||||
{
|
||||
reachTimer = 0;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
State = AIState.Idle;
|
||||
ResetAITarget();
|
||||
return;
|
||||
targetVelocity = targetSub.Velocity;
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
targetVelocity = targetCharacter.AnimController.Collider.LinearVelocity;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity is Item i && i.body != null)
|
||||
{
|
||||
targetVelocity = i.body.LinearVelocity;
|
||||
}
|
||||
float mySpeed = Character.AnimController.Collider.LinearVelocity.LengthSquared();
|
||||
float targetSpeed = targetVelocity.LengthSquared();
|
||||
if (mySpeed < 0.1f || mySpeed > targetSpeed)
|
||||
{
|
||||
reachTimer += deltaTime;
|
||||
if (reachTimer > reachTimeOut)
|
||||
{
|
||||
reachTimer = 0;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
State = AIState.Idle;
|
||||
ResetAITarget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -866,8 +866,8 @@ namespace Barotrauma
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (!container.Inventory.CanBePut(containableItem)) { return 0; }
|
||||
var rootContainer = container.Item.GetRootContainer();
|
||||
if (rootContainer?.GetComponent<Fabricator>() != null || rootContainer?.GetComponent<Deconstructor>() != null) { return 0; }
|
||||
var rootContainer = container.Item.GetRootContainer() ?? container.Item;
|
||||
if (rootContainer.GetComponent<Fabricator>() != null || rootContainer.GetComponent<Deconstructor>() != null) { return 0; }
|
||||
if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined))
|
||||
{
|
||||
if (isRestrictionsDefined)
|
||||
@@ -882,7 +882,12 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return isPreferencesDefined ? 0 : 1;
|
||||
if (isPreferencesDefined)
|
||||
{
|
||||
// Use any valid locker as a fall back container.
|
||||
return container.Item.HasTag("locker") ? 0.5f : 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1950,11 +1955,10 @@ namespace Barotrauma
|
||||
enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
|
||||
}
|
||||
float dangerousItemsFactor = 1f;
|
||||
foreach (Item item in Item.ItemList)
|
||||
foreach (Item item in Item.DangerousItems)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (item.Prefab != null && item.Prefab.IsDangerous)
|
||||
{
|
||||
if (item.CurrentHull == hull)
|
||||
{
|
||||
dangerousItemsFactor = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IgnoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
if (IgnoreAtOutpost && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
|
||||
+23
-10
@@ -48,16 +48,29 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
||||
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float reduction = isPriority ? 1 : 2;
|
||||
float max = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
if (operateObjective != null && objectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
|
||||
{
|
||||
// Prioritize leaks that we are already fixing
|
||||
Priority = maxPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
||||
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
|
||||
{
|
||||
// Double the distance when the leak can be accessed from the current hull.
|
||||
distanceFactor *= 2;
|
||||
}
|
||||
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -202,7 +215,7 @@ namespace Barotrauma
|
||||
// This is an approximation, because we don't know the exact reach until the pose is taken.
|
||||
// And even then the actual range depends on the direction we are aiming to.
|
||||
// Found out that without any multiplier the value (209) is often too short.
|
||||
return repairTool.Range + armLength * 1.3f;
|
||||
return repairTool.Range + armLength * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-1
@@ -185,6 +185,11 @@ namespace Barotrauma
|
||||
{
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
}
|
||||
else
|
||||
{
|
||||
PathSteering.ResetPath();
|
||||
PathSteering.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -290,12 +295,25 @@ namespace Barotrauma
|
||||
{
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
}
|
||||
else
|
||||
{
|
||||
PathSteering.ResetPath();
|
||||
PathSteering.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Wander(float deltaTime)
|
||||
{
|
||||
if (character.IsClimbing) { return; }
|
||||
if (character.IsClimbing)
|
||||
{
|
||||
if (character.AnimController.GetHeightFromFloor() < 0.1f)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
var currentHull = character.CurrentHull;
|
||||
if (!character.AnimController.InWater && currentHull != null)
|
||||
{
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@ namespace Barotrauma
|
||||
}
|
||||
var order = new Order(orderPrefab, autonomousObjective.Option, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
|
||||
+1
-1
@@ -238,7 +238,7 @@ namespace Barotrauma
|
||||
};
|
||||
if (repairTool != null)
|
||||
{
|
||||
objective.CloseEnough = repairTool.Range * 0.75f;
|
||||
objective.CloseEnough = AIObjectiveFixLeak.CalculateReach(repairTool, character);
|
||||
}
|
||||
return objective;
|
||||
},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -90,6 +88,10 @@ namespace Barotrauma
|
||||
{
|
||||
steering = Vector2.Normalize(steering) * Math.Abs(speed);
|
||||
}
|
||||
if (host is AIController aiController && aiController?.Character.CharacterHealth.GetAfflictionOfType("invertcontrols".ToIdentifier()) != null)
|
||||
{
|
||||
steering = -steering;
|
||||
}
|
||||
host.Steering = steering;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (_ragdollParams == null)
|
||||
{
|
||||
#warning TODO: this is kinda janky, this should probably be done better
|
||||
_ragdollParams = FishRagdollParams.GetDefaultRagdollParams(character.VariantOf.IfEmpty(character.SpeciesName));
|
||||
_ragdollParams = FishRagdollParams.GetDefaultRagdollParams(character.SpeciesName);
|
||||
if (!character.VariantOf.IsEmpty)
|
||||
{
|
||||
_ragdollParams.ApplyVariantScale(character.Params.VariantFile);
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasMultipleLimbsOfSameType => limbs == null ? false : Limbs.Length > limbDictionary.Count;
|
||||
public bool HasMultipleLimbsOfSameType => limbs != null && limbs.Length > limbDictionary.Count;
|
||||
|
||||
private bool frozen;
|
||||
public bool Frozen
|
||||
@@ -1850,36 +1850,30 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note that if there are multiple limbs of the same type, only the first of them is found in the dictionary.
|
||||
/// Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
|
||||
/// </summary>
|
||||
public Limb GetLimb(LimbType limbType, bool excludeSevered = true)
|
||||
{
|
||||
Limb limb = null;
|
||||
if (HasMultipleLimbsOfSameType)
|
||||
if (limbDictionary.TryGetValue(limbType, out Limb limb))
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
if (excludeSevered && limb.IsSevered)
|
||||
{
|
||||
limbDictionary.TryGetValue(limbType, out limb);
|
||||
if (limb == null)
|
||||
limb = null;
|
||||
}
|
||||
}
|
||||
if (limb == null && HasMultipleLimbsOfSameType)
|
||||
{
|
||||
// Didn't find a (valid) limb of the matching type. If there's multiple limbs of the same type, check the other limbs.
|
||||
foreach (var l in limbs)
|
||||
{
|
||||
if (l.type != limbType) { continue; }
|
||||
if (!excludeSevered || !l.IsSevered)
|
||||
{
|
||||
// No limbs found
|
||||
break;
|
||||
}
|
||||
if (!excludeSevered || !limb.IsSevered)
|
||||
{
|
||||
// Found a valid limb
|
||||
limb = l;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
limbDictionary.TryGetValue(limbType, out limb);
|
||||
}
|
||||
if (excludeSevered && limb != null && limb.IsSevered)
|
||||
{
|
||||
limb = null;
|
||||
}
|
||||
return limb;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,29 @@ namespace Barotrauma
|
||||
|
||||
protected Key[] keys;
|
||||
|
||||
public HumanPrefab HumanPrefab;
|
||||
private HumanPrefab humanPrefab;
|
||||
public HumanPrefab HumanPrefab
|
||||
{
|
||||
get { return humanPrefab; }
|
||||
set
|
||||
{
|
||||
if (humanPrefab == value) { return; }
|
||||
humanPrefab = value;
|
||||
|
||||
if (humanPrefab != null)
|
||||
{
|
||||
HumanPrefabHealthMultiplier = humanPrefab.HealthMultiplier;
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
HumanPrefabHealthMultiplier *= humanPrefab.HealthMultiplierInMultiplayer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HumanPrefabHealthMultiplier = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CharacterTeamType teamID;
|
||||
public CharacterTeamType TeamID
|
||||
@@ -1192,7 +1214,7 @@ namespace Barotrauma
|
||||
CharacterHealth = new CharacterHealth(selectedHealthElement, this, limbHealthElement);
|
||||
}
|
||||
|
||||
if (Params.Husk && speciesName != "husk")
|
||||
if (Params.Husk && speciesName != "husk" && Prefab.VariantOf != "husk")
|
||||
{
|
||||
// Get the non husked name and find the ragdoll with it
|
||||
var matchingAffliction = AfflictionPrefab.List
|
||||
@@ -1392,7 +1414,7 @@ namespace Barotrauma
|
||||
if (inputType == InputType.Up || inputType == InputType.Down ||
|
||||
inputType == InputType.Left || inputType == InputType.Right)
|
||||
{
|
||||
var invertControls = CharacterHealth.GetAffliction("invertcontrols");
|
||||
var invertControls = CharacterHealth.GetAfflictionOfType("invertcontrols".ToIdentifier());
|
||||
if (invertControls != null)
|
||||
{
|
||||
switch (inputType)
|
||||
@@ -1652,14 +1674,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to modify a character's health for runtime session. Change with AddHealthMultiplier
|
||||
/// Health multiplier of the human prefab this character is an instance of (if any)
|
||||
/// </summary>
|
||||
public float StaticHealthMultiplier { get; private set; } = 1;
|
||||
|
||||
public void AddStaticHealthMultiplier(float newMultiplier)
|
||||
{
|
||||
StaticHealthMultiplier *= newMultiplier;
|
||||
}
|
||||
public float HumanPrefabHealthMultiplier { get; private set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Speed reduction from the current limb specific damage. Min 0, max 1.
|
||||
@@ -4824,21 +4841,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<AbilityFlags> abilityFlags = new List<AbilityFlags>();
|
||||
private AbilityFlags abilityFlags;
|
||||
|
||||
public void AddAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
abilityFlags.Add(abilityFlag);
|
||||
abilityFlags |= abilityFlag;
|
||||
}
|
||||
|
||||
public void RemoveAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
abilityFlags.Remove(abilityFlag);
|
||||
abilityFlags &= ~abilityFlag;
|
||||
}
|
||||
|
||||
public bool HasAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
return abilityFlags.Contains(abilityFlag) || CharacterHealth.HasFlag(abilityFlag);
|
||||
return abilityFlags.HasFlag(abilityFlag) || CharacterHealth.HasFlag(abilityFlag);
|
||||
}
|
||||
|
||||
private readonly Dictionary<Identifier, float> abilityResistances = new Dictionary<Identifier, float>();
|
||||
|
||||
+6
-2
@@ -35,6 +35,7 @@ namespace Barotrauma
|
||||
if (newValue > _strength)
|
||||
{
|
||||
PendingAdditionStrength = Prefab.GrainBurst;
|
||||
Duration = Prefab.Duration;
|
||||
}
|
||||
_strength = newValue;
|
||||
}
|
||||
@@ -60,6 +61,8 @@ namespace Barotrauma
|
||||
|
||||
public double AppliedAsSuccessfulTreatmentTime, AppliedAsFailedTreatmentTime;
|
||||
|
||||
public float Duration;
|
||||
|
||||
/// <summary>
|
||||
/// Which character gave this affliction
|
||||
/// </summary>
|
||||
@@ -75,6 +78,8 @@ namespace Barotrauma
|
||||
_strength = strength;
|
||||
Identifier = prefab.Identifier;
|
||||
|
||||
Duration = prefab.Duration;
|
||||
|
||||
foreach (var periodicEffect in prefab.PeriodicEffects)
|
||||
{
|
||||
PeriodicEffectTimers[periodicEffect] = Rand.Range(periodicEffect.MinInterval, periodicEffect.MaxInterval);
|
||||
@@ -315,8 +320,7 @@ namespace Barotrauma
|
||||
public bool HasFlag(AbilityFlags flagType)
|
||||
{
|
||||
if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return false; }
|
||||
|
||||
return currentEffect.AfflictionAbilityFlags.Contains(flagType);
|
||||
return currentEffect.AfflictionAbilityFlags.HasFlag(flagType);
|
||||
}
|
||||
|
||||
private AfflictionPrefab.Effect GetViableEffect()
|
||||
|
||||
+11
-4
@@ -235,7 +235,7 @@ namespace Barotrauma
|
||||
public Identifier[] BlockTransformation { get; private set; }
|
||||
|
||||
public readonly Dictionary<StatTypes, (float minValue, float maxValue)> AfflictionStatValues = new Dictionary<StatTypes, (float minValue, float maxValue)>();
|
||||
public readonly HashSet<AbilityFlags> AfflictionAbilityFlags = new HashSet<AbilityFlags>();
|
||||
public AbilityFlags AfflictionAbilityFlags;
|
||||
|
||||
//statuseffects applied on the character when the affliction is active
|
||||
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
@@ -265,7 +265,7 @@ namespace Barotrauma
|
||||
break;
|
||||
case "abilityflag":
|
||||
var flagType = CharacterAbilityGroup.ParseFlagType(subElement.GetAttributeString("flagtype", ""), parentDebugName);
|
||||
AfflictionAbilityFlags.Add(flagType);
|
||||
AfflictionAbilityFlags |= flagType;
|
||||
break;
|
||||
case "affliction":
|
||||
DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects.");
|
||||
@@ -354,6 +354,11 @@ namespace Barotrauma
|
||||
//how strong the affliction needs to be before bots attempt to treat it
|
||||
public readonly float TreatmentThreshold = 5.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The affliction is automatically removed after this time. 0 = unlimited
|
||||
/// </summary>
|
||||
public readonly float Duration;
|
||||
|
||||
//how much karma changes when a player applies this affliction to someone (per strength of the affliction)
|
||||
public float KarmaChangeOnApplied;
|
||||
|
||||
@@ -407,8 +412,10 @@ namespace Barotrauma
|
||||
!IsBuff &&
|
||||
AfflictionType != "geneticmaterialbuff" &&
|
||||
AfflictionType != "geneticmaterialdebuff");
|
||||
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier).ToLowerInvariant(), 1f);
|
||||
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost).ToLowerInvariant(), 0);
|
||||
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier), 1f);
|
||||
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost), 0);
|
||||
|
||||
Duration = element.GetAttributeFloat(nameof(Duration), 0.0f);
|
||||
|
||||
if (element.GetAttribute("nameidentifier") != null)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Globalization;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -148,7 +147,7 @@ namespace Barotrauma
|
||||
{
|
||||
max += Character.Info.Job.Prefab.VitalityModifier;
|
||||
}
|
||||
max *= Character.StaticHealthMultiplier;
|
||||
max *= Character.HumanPrefabHealthMultiplier;
|
||||
max *= 1f + Character.GetStatValue(StatTypes.MaximumHealthMultiplier);
|
||||
return max * Character.HealthMultiplier;
|
||||
}
|
||||
@@ -700,6 +699,7 @@ namespace Barotrauma
|
||||
newStrength = Math.Min(existingAffliction.Prefab.MaxStrength, newStrength);
|
||||
if (existingAffliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
|
||||
existingAffliction.Strength = newStrength;
|
||||
existingAffliction.Duration = existingAffliction.Prefab.Duration;
|
||||
if (newAffliction.Source != null) { existingAffliction.Source = newAffliction.Source; }
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality)
|
||||
@@ -759,6 +759,15 @@ namespace Barotrauma
|
||||
if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
|
||||
continue;
|
||||
}
|
||||
if (affliction.Prefab.Duration > 0.0f)
|
||||
{
|
||||
affliction.Duration -= deltaTime;
|
||||
if (affliction.Duration <= 0.0f)
|
||||
{
|
||||
afflictionsToRemove.Add(affliction);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
afflictionsToUpdate.Add(kvp);
|
||||
}
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictionsToUpdate)
|
||||
|
||||
@@ -112,12 +112,6 @@ namespace Barotrauma
|
||||
|
||||
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
|
||||
{
|
||||
npc.AddStaticHealthMultiplier(HealthMultiplier);
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
npc.AddStaticHealthMultiplier(HealthMultiplierInMultiplayer);
|
||||
}
|
||||
|
||||
var humanAI = npc.AIController as HumanAIController;
|
||||
if (humanAI != null)
|
||||
{
|
||||
|
||||
+33
-41
@@ -138,61 +138,53 @@ namespace Barotrauma
|
||||
ragdolls = new Dictionary<string, RagdollParams>();
|
||||
allRagdolls.Add(speciesName, ragdolls);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(fileName) && ragdolls.TryGetValue(fileName, out RagdollParams ragdoll))
|
||||
{
|
||||
return (T)ragdoll;
|
||||
}
|
||||
|
||||
string selectedFile = null;
|
||||
|
||||
void tryFolderForSpecies(Identifier species, out string err)
|
||||
Identifier ragdollSpecies = speciesName;
|
||||
if (CharacterPrefab.Prefabs.TryGet(speciesName, out var prefab))
|
||||
{
|
||||
err = null;
|
||||
string folder = GetFolder(species);
|
||||
if (!prefab.VariantOf.IsEmpty)
|
||||
{
|
||||
ragdollSpecies = prefab.VariantOf;
|
||||
}
|
||||
string error = null;
|
||||
string folder = GetFolder(ragdollSpecies);
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
err = $"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(species);
|
||||
return;
|
||||
}
|
||||
|
||||
string[] files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
{
|
||||
err = $"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(species);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified
|
||||
selectedFile = GetDefaultFile(species);
|
||||
error = $"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(ragdollSpecies);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = files.FirstOrDefault(f => IO.Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (selectedFile == null)
|
||||
string[] files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
{
|
||||
err = $"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(species);
|
||||
error = $"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(ragdollSpecies);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified
|
||||
selectedFile = GetDefaultFile(ragdollSpecies);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = files.FirstOrDefault(f => IO.Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (selectedFile == null)
|
||||
{
|
||||
error = $"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(ragdollSpecies);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error != null)
|
||||
{
|
||||
DebugConsole.ThrowError(error);
|
||||
}
|
||||
}
|
||||
|
||||
tryFolderForSpecies(speciesName, out var error);
|
||||
Identifier parentSpeciesName = CharacterPrefab.Prefabs.TryGet(speciesName, out var prefab)
|
||||
? prefab.VariantOf
|
||||
: Identifier.Empty;
|
||||
if (!error.IsNullOrEmpty() && !parentSpeciesName.IsEmpty)
|
||||
{
|
||||
tryFolderForSpecies(parentSpeciesName, out error);
|
||||
}
|
||||
|
||||
if (!error.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.ThrowError(error);
|
||||
}
|
||||
|
||||
if (selectedFile == null)
|
||||
{
|
||||
throw new Exception("[RagdollParams] Selected file null!");
|
||||
@@ -200,7 +192,7 @@ namespace Barotrauma
|
||||
DebugConsole.Log($"[RagdollParams] Loading ragdoll from {selectedFile}.");
|
||||
var characterPrefab = CharacterPrefab.Prefabs[speciesName];
|
||||
T r = new T();
|
||||
if (r.Load(ContentPath.FromRaw(characterPrefab.ContentPackage, selectedFile), speciesName))
|
||||
if (r.Load(ContentPath.FromRaw(characterPrefab.ContentPackage, selectedFile), ragdollSpecies))
|
||||
{
|
||||
if (!ragdolls.ContainsKey(r.Name))
|
||||
{
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityConditionDataless : AbilityCondition
|
||||
{
|
||||
|
||||
-3
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+2
-6
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
@@ -19,10 +16,9 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
IEnumerable<Character> chosenCharacters = Character.GetFriendlyCrew(Character).Where(c => allowSelf || c != Character);
|
||||
|
||||
foreach (Character character in chosenCharacters)
|
||||
foreach (Character character in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
if (!allowSelf && character == Character) { continue; }
|
||||
if (maxDistance < float.MaxValue)
|
||||
{
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Character.WorldPosition) > maxDistance * maxDistance) { continue; }
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToAttacker : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToLastOrderedCharacter : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
-1
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+9
-8
@@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupEffect : CharacterAbilityGroup
|
||||
{
|
||||
@@ -30,7 +24,14 @@ namespace Barotrauma.Abilities
|
||||
private bool IsApplicable(AbilityObject abilityObject)
|
||||
{
|
||||
if (timesTriggered >= maxTriggerCount) { return false; }
|
||||
return abilityConditions.All(c => c.MatchesCondition(abilityObject));
|
||||
foreach (var abilityCondition in abilityConditions)
|
||||
{
|
||||
if (!abilityCondition.MatchesCondition(abilityObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupInterval : CharacterAbilityGroup
|
||||
{
|
||||
@@ -49,7 +43,14 @@ namespace Barotrauma.Abilities
|
||||
private bool IsApplicable()
|
||||
{
|
||||
if (timesTriggered >= maxTriggerCount) { return false; }
|
||||
return abilityConditions.All(c => c.MatchesCondition());
|
||||
foreach (var abilityCondition in abilityConditions)
|
||||
{
|
||||
if (!abilityCondition.MatchesCondition())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using Barotrauma.Abilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
+24
-8
@@ -2,12 +2,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -69,10 +67,10 @@ namespace Barotrauma
|
||||
.ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public static Result<ContentFile, string> CreateFromXElement(ContentPackage contentPackage, XElement element)
|
||||
public static Result<ContentFile, LoadError> CreateFromXElement(ContentPackage contentPackage, XElement element)
|
||||
{
|
||||
static Result<ContentFile, string> fail(string error, string? stackTrace = null)
|
||||
=> Result<ContentFile, string>.Failure(error, stackTrace);
|
||||
static Result<ContentFile, LoadError> fail(string error, Exception? exception = null)
|
||||
=> Result<ContentFile, LoadError>.Failure(new LoadError(error, exception));
|
||||
|
||||
Identifier elemName = element.NameAsIdentifier();
|
||||
var type = Types.FirstOrDefault(t => t.Names.Contains(elemName));
|
||||
@@ -95,11 +93,11 @@ namespace Barotrauma
|
||||
var file = type.CreateInstance(contentPackage, filePath);
|
||||
return file is null
|
||||
? throw new Exception($"Content type is not implemented correctly")
|
||||
: Result<ContentFile, string>.Success(file);
|
||||
: Result<ContentFile, LoadError>.Success(file);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": {e.Message}", e.StackTrace.CleanupStackTrace());
|
||||
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": {e.Message}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,5 +123,23 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public bool NotSyncedInMultiplayer => Types.Any(t => t.Type == GetType() && t.NotSyncedInMultiplayer);
|
||||
|
||||
public readonly struct LoadError
|
||||
{
|
||||
public readonly string Message;
|
||||
public readonly Exception? Exception;
|
||||
|
||||
public LoadError(string message, Exception? exception)
|
||||
{
|
||||
Message = message;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Message
|
||||
+ (Exception is { StackTrace: var stackTrace }
|
||||
? '\n' + stackTrace.CleanupStackTrace()
|
||||
: string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
-34
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
{
|
||||
public abstract class ContentPackage
|
||||
{
|
||||
public static readonly Version MinimumHashCompatibleVersion = new Version(0, 17, 16, 0);
|
||||
public static readonly Version MinimumHashCompatibleVersion = new Version(0, 18, 3, 0);
|
||||
|
||||
public const string LocalModsDir = "LocalMods";
|
||||
public static readonly string WorkshopModsDir = Barotrauma.IO.Path.Combine(
|
||||
@@ -33,11 +33,11 @@ namespace Barotrauma
|
||||
|
||||
public readonly Version GameVersion;
|
||||
public readonly string ModVersion;
|
||||
public readonly Md5Hash Hash;
|
||||
public Md5Hash Hash { get; private set; }
|
||||
public readonly DateTime? InstallTime;
|
||||
|
||||
public readonly ImmutableArray<ContentFile> Files;
|
||||
public readonly ImmutableArray<(string error, string? stackTrace)> Errors;
|
||||
public ImmutableArray<ContentFile> Files { get; private set; }
|
||||
public ImmutableArray<ContentFile.LoadError> Errors { get; private set; }
|
||||
|
||||
public async Task<bool> IsUpToDate()
|
||||
{
|
||||
@@ -55,7 +55,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Does the content package include some content that needs to match between all players in multiplayer.
|
||||
/// </summary>
|
||||
public readonly bool HasMultiplayerSyncedContent;
|
||||
public bool HasMultiplayerSyncedContent { get; private set; }
|
||||
|
||||
protected ContentPackage(XDocument doc, string path)
|
||||
{
|
||||
@@ -84,13 +84,13 @@ namespace Barotrauma
|
||||
.ToArray();
|
||||
|
||||
Files = fileResults
|
||||
.OfType<Success<ContentFile, string>>()
|
||||
.OfType<Success<ContentFile, ContentFile.LoadError>>()
|
||||
.Select(f => f.Value)
|
||||
.ToImmutableArray();
|
||||
|
||||
Errors = fileResults
|
||||
.OfType<Failure<ContentFile, string>>()
|
||||
.Select(f => (f.Error, f.StackTrace))
|
||||
.OfType<Failure<ContentFile, ContentFile.LoadError>>()
|
||||
.Select(f => f.Error)
|
||||
.ToImmutableArray();
|
||||
|
||||
HasMultiplayerSyncedContent = Files.Any(f => !f.NotSyncedInMultiplayer);
|
||||
@@ -127,18 +127,13 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
if (doc.Root.GetAttributeBool("corepackage", false))
|
||||
{
|
||||
return new CorePackage(doc, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RegularPackage(doc, path);
|
||||
}
|
||||
return doc.Root.GetAttributeBool("corepackage", false)
|
||||
? (ContentPackage)new CorePackage(doc, path)
|
||||
: new RegularPackage(doc, path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
while (e.InnerException != null) { e = e.InnerException; }
|
||||
e = e.GetInnermost();
|
||||
DebugConsole.ThrowError($"{e.Message}: {e.StackTrace}");
|
||||
return null;
|
||||
}
|
||||
@@ -278,12 +273,42 @@ namespace Barotrauma
|
||||
Files.ForEach(f => f.UnloadFile());
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
public void ReloadSubsAndItemAssemblies()
|
||||
{
|
||||
byte[] shortHash = Encoding.ASCII.GetBytes(Hash.StringRepresentation.Substring(0, 4));
|
||||
return (shortHash[0] << 24) | (shortHash[1] << 16) | (shortHash[2] << 8) | shortHash[3];
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
List<ContentFile> newFileList = new List<ContentFile>();
|
||||
XElement rootElement = doc.Root ?? throw new NullReferenceException("XML document is invalid: root element is null.");
|
||||
|
||||
var fileResults = rootElement.Elements()
|
||||
.Select(e => ContentFile.CreateFromXElement(this, e))
|
||||
.ToArray();
|
||||
|
||||
foreach (var result in fileResults)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case Success<ContentFile, ContentFile.LoadError> { Value: var file }:
|
||||
if (file is BaseSubFile || file is ItemAssemblyFile)
|
||||
{
|
||||
newFileList.Add(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingFile = Files.FirstOrDefault(f => f.Path == file.Path);
|
||||
newFileList.Add(existingFile ?? file);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UnloadFilesOfType<BaseSubFile>();
|
||||
UnloadFilesOfType<ItemAssemblyFile>();
|
||||
Files = newFileList.ToImmutableArray();
|
||||
Hash = CalculateHash();
|
||||
LoadFilesOfType<BaseSubFile>();
|
||||
LoadFilesOfType<ItemAssemblyFile>();
|
||||
}
|
||||
|
||||
|
||||
public static bool PathAllowedAsLocalModFile(string path)
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -305,21 +330,17 @@ namespace Barotrauma
|
||||
|
||||
public void LogErrors()
|
||||
{
|
||||
if (Errors.Any())
|
||||
if (!Errors.Any())
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"The following errors occurred while loading the content package\"{Name}\". The package might not work correctly.\n" +
|
||||
string.Join('\n', Errors.Select(e => errorToStr(e.error, e.stackTrace))));
|
||||
static string errorToStr(string error, string? stackTrace)
|
||||
{
|
||||
string str = error;
|
||||
if (stackTrace != null)
|
||||
{
|
||||
str += '\n' + stackTrace;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DebugConsole.AddWarning(
|
||||
$"The following errors occurred while loading the content package \"{Name}\". The package might not work correctly.\n" +
|
||||
string.Join('\n', Errors.Select(errorToStr)));
|
||||
|
||||
static string errorToStr(ContentFile.LoadError error)
|
||||
=> error.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,9 +430,9 @@ namespace Barotrauma
|
||||
public static void LoadVanillaFileList()
|
||||
{
|
||||
VanillaCorePackage = new CorePackage(XDocument.Load(VanillaFileList), VanillaFileList);
|
||||
foreach ((string error, string? stackTrace) in VanillaCorePackage.Errors)
|
||||
foreach (ContentFile.LoadError error in VanillaCorePackage.Errors)
|
||||
{
|
||||
DebugConsole.ThrowError(error + (stackTrace == null ? string.Empty : '\n' + stackTrace));
|
||||
DebugConsole.ThrowError(error.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
|
||||
public string BaseUri => Element.BaseUri;
|
||||
|
||||
public XDocument Document => Element.Document ?? throw new NullReferenceException("XML element is invalid: document is null.");
|
||||
public XDocument? Document => Element.Document;
|
||||
|
||||
public ContentXElement? FirstElement() => Elements().FirstOrDefault();
|
||||
|
||||
|
||||
@@ -1836,8 +1836,8 @@ namespace Barotrauma
|
||||
ThrowError($"No start item set identifier defined!");
|
||||
return;
|
||||
}
|
||||
AutoItemPlacer.StartItemSet = args[0].ToIdentifier();
|
||||
NewMessage($"Start item set changed to \"{AutoItemPlacer.StartItemSet}\"");
|
||||
AutoItemPlacer.DefaultStartItemSet = args[0].ToIdentifier();
|
||||
NewMessage($"Start item set changed to \"{AutoItemPlacer.DefaultStartItemSet}\"");
|
||||
}, isCheat: false));
|
||||
|
||||
//"dummy commands" that only exist so that the server can give clients permissions to use them
|
||||
|
||||
@@ -131,21 +131,22 @@ namespace Barotrauma
|
||||
MaxAttachableCount,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AbilityFlags
|
||||
{
|
||||
None,
|
||||
MustWalk,
|
||||
ImmuneToPressure,
|
||||
IgnoredByEnemyAI,
|
||||
MoveNormallyWhileDragging,
|
||||
CanTinker,
|
||||
CanTinkerFabricatorsAndDeconstructors,
|
||||
TinkeringPowersDevices,
|
||||
GainSkillPastMaximum,
|
||||
RetainExperienceForNewCharacter,
|
||||
AllowSecondOrderedTarget,
|
||||
PowerfulCPR,
|
||||
AlwaysStayConscious,
|
||||
None = 0,
|
||||
MustWalk = 0x1,
|
||||
ImmuneToPressure = 0x2,
|
||||
IgnoredByEnemyAI = 0x4,
|
||||
MoveNormallyWhileDragging = 0x8,
|
||||
CanTinker = 0x10,
|
||||
CanTinkerFabricatorsAndDeconstructors = 0x20,
|
||||
TinkeringPowersDevices = 0x40,
|
||||
GainSkillPastMaximum = 0x80,
|
||||
RetainExperienceForNewCharacter = 0x100,
|
||||
AllowSecondOrderedTarget = 0x200,
|
||||
PowerfulCPR = 0x400,
|
||||
AlwaysStayConscious = 0x800,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@@ -156,9 +157,24 @@ namespace Barotrauma
|
||||
Both = Bot | Player
|
||||
}
|
||||
|
||||
public enum StartingBalanceAmount
|
||||
{
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
public enum GameDifficulty
|
||||
{
|
||||
Easy,
|
||||
Medium,
|
||||
Hard,
|
||||
Hellish
|
||||
}
|
||||
|
||||
public enum NumberType
|
||||
{
|
||||
Int,
|
||||
Float
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
mpCampaign.LastUpdateID++;
|
||||
mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
|
||||
}
|
||||
if (prefab != null)
|
||||
{
|
||||
|
||||
@@ -64,8 +64,6 @@ namespace Barotrauma
|
||||
campaign.GetWallet(client).Give(Amount);
|
||||
}
|
||||
}
|
||||
|
||||
((MultiPlayerCampaign)campaign).LastUpdateID++;
|
||||
#else
|
||||
campaign.Wallet.Give(Amount);
|
||||
#endif
|
||||
|
||||
@@ -49,6 +49,9 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier SpawnPointTag { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should we spawn the entity even when no spawn points with matching tags were found?")]
|
||||
public bool RequireSpawnPointTag { get; set; }
|
||||
|
||||
private readonly HashSet<Identifier> targetModuleTags = new HashSet<Identifier>();
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the entity prefer to spawn in.")]
|
||||
@@ -79,7 +82,7 @@ namespace Barotrauma
|
||||
|
||||
public SpawnAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
ignoreSpawnPointType = !element.Attributes().Any(a => a.Name.ToString().Equals("spawnpointtype", StringComparison.OrdinalIgnoreCase));
|
||||
ignoreSpawnPointType = element.GetAttribute("spawnpointtype") == null;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
@@ -110,22 +113,40 @@ namespace Barotrauma
|
||||
if (humanPrefab != null)
|
||||
{
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
|
||||
if (spawnPos != null)
|
||||
{
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
newCharacter.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
|
||||
{
|
||||
foreach (Item item in newCharacter.Inventory.AllItems)
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
newCharacter.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
foreach (Item item in newCharacter.Inventory.AllItems)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
|
||||
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!SpeciesName.IsEmpty)
|
||||
{
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), onSpawn: newCharacter =>
|
||||
{
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
@@ -134,20 +155,9 @@ namespace Barotrauma
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (!SpeciesName.IsEmpty)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter =>
|
||||
{
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
else if (!ItemIdentifier.IsEmpty)
|
||||
{
|
||||
if (!(MapEntityPrefab.Find(null, identifier: ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
if (!(MapEntityPrefab.FindByIdentifier(ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)");
|
||||
}
|
||||
@@ -178,7 +188,11 @@ namespace Barotrauma
|
||||
|
||||
if (spawnInventory == null)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawned: onSpawned);
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), onSpawned: onSpawned);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -244,10 +258,10 @@ namespace Barotrauma
|
||||
SpawnType? spawnPointType = null;
|
||||
if (!ignoreSpawnPointType) { spawnPointType = SpawnPointType; }
|
||||
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable());
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable(), requireTaggedSpawnPoint: RequireSpawnPointTag);
|
||||
}
|
||||
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false)
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false)
|
||||
{
|
||||
List<WayPoint> potentialSpawnPoints = spawnLocation switch
|
||||
{
|
||||
@@ -274,18 +288,24 @@ namespace Barotrauma
|
||||
if (spawnpointTags != null && spawnpointTags.Any())
|
||||
{
|
||||
var spawnPoints = potentialSpawnPoints
|
||||
.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag)))
|
||||
.Where(wp => wp.ConnectedDoor == null && !wp.isObstructed);
|
||||
.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor == null && !wp.isObstructed));
|
||||
|
||||
if (spawnPoints.Any())
|
||||
if (requireTaggedSpawnPoint || spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialSpawnPoints.Count == 0)
|
||||
if (potentialSpawnPoints.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation})");
|
||||
if (requireTaggedSpawnPoint && spawnpointTags != null && spawnpointTags.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation} (tag: {string.Join(",", spawnpointTags)}), skipping.", color: Color.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation})");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -307,7 +327,7 @@ namespace Barotrauma
|
||||
validSpawnPoints = validSpawnPoints.Except(airlockSpawnPoints);
|
||||
}
|
||||
|
||||
if (!validSpawnPoints.Any())
|
||||
if (validSpawnPoints.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point of the correct type for a SpawnAction (spawn location: {spawnLocation}, type: {spawnPointType}, module flags: {((moduleFlags == null || !moduleFlags.Any()) ? "none" : string.Join(", ", moduleFlags))})");
|
||||
return potentialSpawnPoints.GetRandomUnsynced();
|
||||
@@ -320,7 +340,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//if not trying to spawn at a tagged spawnpoint, favor spawnpoints without tags
|
||||
if (spawnpointTags == null || !spawnpointTags.Any())
|
||||
if (spawnpointTags == null || spawnpointTags.None())
|
||||
{
|
||||
var spawnPoints = validSpawnPoints.Where(wp => !wp.Tags.Any());
|
||||
if (spawnPoints.Any())
|
||||
|
||||
@@ -260,9 +260,15 @@ namespace Barotrauma
|
||||
throw new InvalidOperationException("Could not select EventManager settings (level not set).");
|
||||
}
|
||||
|
||||
float extraDifficulty = 0;
|
||||
if (GameMain.GameSession.Campaign?.Settings != null)
|
||||
{
|
||||
extraDifficulty = GameMain.GameSession.Campaign.Settings.ExtraEventManagerDifficulty;
|
||||
}
|
||||
float modifiedDifficulty = Math.Clamp(level.Difficulty + extraDifficulty, 0, 100);
|
||||
var suitableSettings = EventManagerSettings.OrderedByDifficulty.Where(s =>
|
||||
level.Difficulty >= s.MinLevelDifficulty &&
|
||||
level.Difficulty <= s.MaxLevelDifficulty).ToArray();
|
||||
modifiedDifficulty >= s.MinLevelDifficulty &&
|
||||
modifiedDifficulty <= s.MaxLevelDifficulty).ToArray();
|
||||
|
||||
if (suitableSettings.Length == 0)
|
||||
{
|
||||
|
||||
@@ -437,7 +437,7 @@ namespace Barotrauma
|
||||
{
|
||||
minDistance = 5000;
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Wreck))
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Wreck) || SpawnPosType.HasFlag(Level.PositionType.BeaconStation))
|
||||
{
|
||||
minDistance = 3000;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static bool OutputDebugInfo = false;
|
||||
|
||||
public static void SpawnItems()
|
||||
public static void SpawnItems(Identifier? startItemSet = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma
|
||||
var sub = Submarine.MainSubs[i];
|
||||
if (sub == null || sub.Info.InitialSuppliesSpawned || !sub.Info.IsPlayer) { continue; }
|
||||
//1st pass: items defined in the start item set, only spawned in the main sub (not drones/shuttles or other linked subs)
|
||||
SpawnStartItems(sub);
|
||||
SpawnStartItems(sub, startItemSet);
|
||||
//2nd pass: items defined using preferred containers, spawned in the main sub and all the linked subs (drones, shuttles etc)
|
||||
var subs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID);
|
||||
CreateAndPlace(subs);
|
||||
@@ -62,17 +62,23 @@ namespace Barotrauma
|
||||
CreateAndPlace(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer);
|
||||
}
|
||||
|
||||
public static Identifier StartItemSet = new Identifier("normal");
|
||||
public static Identifier DefaultStartItemSet = new Identifier("normal");
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the items defined in the start item set in the specified sub.
|
||||
/// </summary>
|
||||
private static void SpawnStartItems(Submarine sub)
|
||||
private static void SpawnStartItems(Submarine sub, Identifier? startItemSet)
|
||||
{
|
||||
if (!Barotrauma.StartItemSet.Sets.TryGet(StartItemSet, out StartItemSet itemSet))
|
||||
Identifier setIdentifier = startItemSet ?? DefaultStartItemSet;
|
||||
if (!StartItemSet.Sets.TryGet(setIdentifier, out StartItemSet itemSet))
|
||||
{
|
||||
DebugConsole.AddWarning($"Couldn't find a start item set matching the identifier \"{StartItemSet}\"!");
|
||||
return;
|
||||
DebugConsole.AddWarning($"Couldn't find a start item set matching the identifier \"{setIdentifier}\"!");
|
||||
if (!StartItemSet.Sets.TryGet(DefaultStartItemSet, out StartItemSet defaultSet))
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't find the default start item set \"{DefaultStartItemSet}\"!");
|
||||
return;
|
||||
}
|
||||
itemSet = defaultSet;
|
||||
}
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub);
|
||||
ISpatialEntity initialSpawnPos;
|
||||
@@ -164,7 +170,7 @@ namespace Barotrauma
|
||||
var itemPrefabs = ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier);
|
||||
foreach (ItemPrefab ip in itemPrefabs)
|
||||
{
|
||||
if (!ip.PreferredContainers.Any()) { continue; }
|
||||
if (ip.PreferredContainers.None()) { continue; }
|
||||
if (ip.ConfigElement.Elements().Any(e => string.Equals(e.Name.ToString(), typeof(ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)) && itemPrefabs.Any(ip2 => CanSpawnIn(ip2, ip)))
|
||||
{
|
||||
prefabsItemsCanSpawnIn.Add(ip);
|
||||
|
||||
@@ -10,63 +10,6 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal struct CampaignSettings
|
||||
{
|
||||
public static CampaignSettings Empty => new CampaignSettings();
|
||||
|
||||
// Anything that uses this field I wasn't sure if actually needed the proper campaign settings to be passed down
|
||||
public static CampaignSettings Unsure => Empty;
|
||||
public bool RadiationEnabled { get; set; }
|
||||
|
||||
public int TotalMaxMissionCount => MaxMissionCount + GetAddedMissionCount();
|
||||
|
||||
private int maxMissionCount;
|
||||
public int MaxMissionCount
|
||||
{
|
||||
get { return maxMissionCount; }
|
||||
set { maxMissionCount = MathHelper.Clamp(value, MinMissionCountLimit, MaxMissionCountLimit); }
|
||||
}
|
||||
|
||||
public const int DefaultMaxMissionCount = 2;
|
||||
public const int MaxMissionCountLimit = 10;
|
||||
public const int MinMissionCountLimit = 1;
|
||||
|
||||
public CampaignSettings(IReadMessage inc)
|
||||
{
|
||||
maxMissionCount = DefaultMaxMissionCount;
|
||||
RadiationEnabled = inc.ReadBoolean();
|
||||
MaxMissionCount = inc.ReadRangedInteger(MinMissionCountLimit, MaxMissionCountLimit);
|
||||
}
|
||||
|
||||
public CampaignSettings(XElement element)
|
||||
{
|
||||
maxMissionCount = DefaultMaxMissionCount;
|
||||
RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true);
|
||||
MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount);
|
||||
}
|
||||
|
||||
public void Serialize(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(RadiationEnabled);
|
||||
msg.WriteRangedInteger(MaxMissionCount, MinMissionCountLimit, MaxMissionCountLimit);
|
||||
}
|
||||
|
||||
public int GetAddedMissionCount()
|
||||
{
|
||||
int count = 0;
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
count += (int)character.GetStatValue(StatTypes.ExtraMissionCount);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLowerInvariant(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLowerInvariant(), MaxMissionCount));
|
||||
}
|
||||
}
|
||||
|
||||
abstract partial class CampaignMode : GameMode
|
||||
{
|
||||
[NetworkSerialize]
|
||||
@@ -149,9 +92,8 @@ namespace Barotrauma
|
||||
//key = dialog flag, double = Timing.TotalTime when the line was last said
|
||||
private readonly Dictionary<string, double> dialogLastSpoken = new Dictionary<string, double>();
|
||||
|
||||
public bool PurchasedHullRepairs, PurchasedLostShuttles, PurchasedItemRepairs;
|
||||
|
||||
public SubmarineInfo PendingSubmarineSwitch;
|
||||
public bool TransferItemsOnSubSwitch { get; set; }
|
||||
|
||||
protected Map map;
|
||||
public Map Map
|
||||
@@ -189,12 +131,16 @@ namespace Barotrauma
|
||||
protected set;
|
||||
}
|
||||
|
||||
protected CampaignMode(GameModePreset preset)
|
||||
public virtual bool PurchasedHullRepairs { get; set; }
|
||||
public virtual bool PurchasedLostShuttles { get; set; }
|
||||
public virtual bool PurchasedItemRepairs { get; set; }
|
||||
|
||||
protected CampaignMode(GameModePreset preset, CampaignSettings settings)
|
||||
: base(preset)
|
||||
{
|
||||
Bank = new Wallet(Option<Character>.None())
|
||||
{
|
||||
Balance = InitialMoney
|
||||
Balance = settings.InitialMoney
|
||||
};
|
||||
|
||||
CargoManager = new CargoManager(this);
|
||||
@@ -596,6 +542,7 @@ namespace Barotrauma
|
||||
if (Level.Loaded.StartOutpost == null)
|
||||
{
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
|
||||
if (closestSub == null) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
else
|
||||
@@ -729,7 +676,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void EndCampaign()
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
@@ -741,7 +687,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (LocationConnection connection in Map.Connections)
|
||||
{
|
||||
connection.Difficulty = MathHelper.Lerp(connection.Difficulty, 100.0f, 0.25f);
|
||||
connection.Difficulty = connection.Biome.MaxDifficulty;
|
||||
connection.LevelData = new LevelData(connection)
|
||||
{
|
||||
IsBeaconActive = false
|
||||
@@ -750,6 +696,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (Location location in Map.Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location, location.Biome.MaxDifficulty);
|
||||
location.Reset();
|
||||
}
|
||||
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
|
||||
@@ -873,7 +820,7 @@ namespace Barotrauma
|
||||
const float MaxDist = 3000.0f;
|
||||
const float MinDist = 2500.0f;
|
||||
|
||||
if (!Level.IsLoadedOutpost) { return; }
|
||||
if (!Level.IsLoadedFriendlyOutpost) { return; }
|
||||
|
||||
Rectangle worldBorders = Submarine.MainSub.GetDockedBorders();
|
||||
worldBorders.Location += Submarine.MainSub.WorldPosition.ToPoint();
|
||||
@@ -1058,7 +1005,10 @@ namespace Barotrauma
|
||||
|
||||
public SubmarineInfo SwitchSubs()
|
||||
{
|
||||
TransferItemsBetweenSubs();
|
||||
if (TransferItemsOnSubSwitch)
|
||||
{
|
||||
TransferItemsBetweenSubs();
|
||||
}
|
||||
RefreshOwnedSubmarines();
|
||||
PendingSubmarineSwitch = null;
|
||||
return GameMain.GameSession.SubmarineInfo;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal static class CampaignModePresets
|
||||
{
|
||||
public static readonly ImmutableArray<CampaignSettings> List;
|
||||
public static readonly ImmutableDictionary<Identifier, CampaignSettingDefinitions> Definitions;
|
||||
|
||||
private static readonly string fileListPath = Path.Combine("Data", "campaignsettings.xml");
|
||||
|
||||
static CampaignModePresets()
|
||||
{
|
||||
if (!File.Exists(fileListPath) || !(XMLExtensions.TryLoadXml(fileListPath)?.Root is { } docRoot))
|
||||
{
|
||||
List = ImmutableArray<CampaignSettings>.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
List<CampaignSettings> list = new List<CampaignSettings>();
|
||||
Dictionary<Identifier, CampaignSettingDefinitions> definitions = new Dictionary<Identifier, CampaignSettingDefinitions>();
|
||||
|
||||
foreach (XElement element in docRoot.Elements())
|
||||
{
|
||||
Identifier name = element.NameAsIdentifier();
|
||||
|
||||
if (name == CampaignSettings.LowerCaseSaveElementName)
|
||||
{
|
||||
list.Add(new CampaignSettings(element));
|
||||
}
|
||||
else if (name == nameof(CampaignSettingDefinitions))
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
definitions.Add(subElement.NameAsIdentifier(), new CampaignSettingDefinitions(subElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List = list.ToImmutableArray();
|
||||
Definitions = definitions.ToImmutableDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct CampaignSettingDefinitions
|
||||
{
|
||||
// Definitely not the best way to do this
|
||||
private readonly ImmutableDictionary<Identifier, Either<int, float>> values;
|
||||
|
||||
public CampaignSettingDefinitions(XElement element)
|
||||
{
|
||||
var definitions = new Dictionary<Identifier, Either<int, float>>();
|
||||
foreach (XAttribute attribute in element.Attributes())
|
||||
{
|
||||
Identifier name = attribute.NameAsIdentifier();
|
||||
if (attribute.Value.Contains('.'))
|
||||
{
|
||||
definitions.Add(name, element.GetAttributeFloat(name.Value, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
definitions.Add(name, element.GetAttributeInt(name.Value, 0));
|
||||
}
|
||||
}
|
||||
|
||||
values = definitions.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public float GetFloat(Identifier identifier)
|
||||
{
|
||||
return values.TryGetValue(identifier, out Either<int, float> value) && value.TryGet(out float range) ? range : 0.0f;
|
||||
}
|
||||
|
||||
public int GetInt(Identifier identifier)
|
||||
{
|
||||
return values.TryGetValue(identifier, out Either<int, float> value) && value.TryGet(out int integer) ? integer : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class CampaignSettings : INetSerializableStruct, ISerializableEntity
|
||||
{
|
||||
public static CampaignSettings Empty => new CampaignSettings(element: null);
|
||||
|
||||
public string Name => "CampaignSettings";
|
||||
|
||||
public const string LowerCaseSaveElementName = "campaignsettings";
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string PresetName { get; set; } = string.Empty;
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public bool RadiationEnabled { get; set; }
|
||||
|
||||
private int maxMissionCount;
|
||||
|
||||
[Serialize(DefaultMaxMissionCount, IsPropertySaveable.Yes), NetworkSerialize(MinValueInt = MinMissionCountLimit, MaxValueInt = MaxMissionCountLimit)]
|
||||
public int MaxMissionCount
|
||||
{
|
||||
get => maxMissionCount;
|
||||
set => maxMissionCount = MathHelper.Clamp(value, MinMissionCountLimit, MaxMissionCountLimit);
|
||||
}
|
||||
|
||||
public int TotalMaxMissionCount => MaxMissionCount + GetAddedMissionCount();
|
||||
|
||||
[Serialize(StartingBalanceAmount.Medium, IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public StartingBalanceAmount StartingBalanceAmount { get; set; }
|
||||
|
||||
[Serialize(GameDifficulty.Medium, IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public GameDifficulty Difficulty { get; set; }
|
||||
|
||||
[Serialize("normal", IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public Identifier StartItemSet { get; set; }
|
||||
|
||||
public int InitialMoney
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CampaignModePresets.Definitions.TryGetValue(nameof(StartingBalanceAmount).ToIdentifier(), out var definition))
|
||||
{
|
||||
return definition.GetInt(StartingBalanceAmount.ToIdentifier());
|
||||
}
|
||||
return 8000;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public float ExtraEventManagerDifficulty
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CampaignModePresets.Definitions.TryGetValue(nameof(ExtraEventManagerDifficulty).ToIdentifier(), out var definition))
|
||||
{
|
||||
return definition.GetFloat(Difficulty.ToIdentifier());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public float LevelDifficultyMultiplier
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CampaignModePresets.Definitions.TryGetValue(nameof(LevelDifficultyMultiplier).ToIdentifier(), out var definition))
|
||||
{
|
||||
return definition.GetFloat(Difficulty.ToIdentifier());
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public const int DefaultMaxMissionCount = 2;
|
||||
public const int MaxMissionCountLimit = 10;
|
||||
public const int MinMissionCountLimit = 1;
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
// required for INetSerializableStruct
|
||||
public CampaignSettings()
|
||||
{
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
}
|
||||
|
||||
public CampaignSettings(XElement? element = null)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement saveElement = new XElement(LowerCaseSaveElementName);
|
||||
SerializableProperty.SerializeProperties(this, saveElement, saveIfDefault: true);
|
||||
return saveElement;
|
||||
}
|
||||
|
||||
private static int GetAddedMissionCount()
|
||||
{
|
||||
int count = 0;
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
count += (int)character.GetStatValue(StatTypes.ExtraMissionCount);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,11 @@ namespace Barotrauma
|
||||
: base(preset)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
missions.Add(Mission.LoadRandom(locations, seed, false, missionType));
|
||||
var mission = Mission.LoadRandom(locations, seed, false, missionType);
|
||||
if (mission != null)
|
||||
{
|
||||
missions.Add(mission);
|
||||
}
|
||||
}
|
||||
|
||||
protected static IEnumerable<MissionPrefab> ValidateMissionPrefabs(IEnumerable<MissionPrefab> missionPrefabs, Dictionary<MissionType, Type> missionClasses)
|
||||
|
||||
+71
-21
@@ -12,19 +12,60 @@ namespace Barotrauma
|
||||
{
|
||||
public const int MinimumInitialMoney = 500;
|
||||
|
||||
private UInt16 lastUpdateID;
|
||||
public UInt16 LastUpdateID
|
||||
[Flags]
|
||||
public enum NetFlags : UInt16
|
||||
{
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && lastUpdateID < 1) { lastUpdateID++; }
|
||||
#endif
|
||||
return lastUpdateID;
|
||||
}
|
||||
set { lastUpdateID = value; }
|
||||
Misc = 0x1,
|
||||
MapAndMissions = 0x2,
|
||||
UpgradeManager = 0x4,
|
||||
SubList = 0x8,
|
||||
ItemsInBuyCrate = 0x10,
|
||||
ItemsInSellFromSubCrate = 0x20,
|
||||
PurchasedItems = 0x80,
|
||||
SoldItems = 0x100,
|
||||
Reputation = 0x200,
|
||||
CharacterInfo = 0x800
|
||||
}
|
||||
|
||||
private readonly Dictionary<NetFlags, UInt16> lastUpdateID;
|
||||
|
||||
public UInt16 GetLastUpdateIdForFlag(NetFlags flag)
|
||||
{
|
||||
if (!ValidateFlag(flag)) { return 0; }
|
||||
return lastUpdateID[flag];
|
||||
}
|
||||
public void SetLastUpdateIdForFlag(NetFlags flag, UInt16 id)
|
||||
{
|
||||
if (!ValidateFlag(flag)) { return; }
|
||||
lastUpdateID[flag] = id;
|
||||
}
|
||||
|
||||
public void IncrementLastUpdateIdForFlag(NetFlags flag)
|
||||
{
|
||||
if (!ValidateFlag(flag)) { return; }
|
||||
if (!lastUpdateID.ContainsKey(flag)) { lastUpdateID[flag] = 0; }
|
||||
lastUpdateID[flag]++;
|
||||
}
|
||||
public void IncrementAllLastUpdateIds()
|
||||
{
|
||||
foreach (NetFlags flag in Enum.GetValues(typeof(NetFlags)))
|
||||
{
|
||||
if (!lastUpdateID.ContainsKey(flag)) { lastUpdateID[flag] = 0; }
|
||||
lastUpdateID[flag]++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateFlag(NetFlags flag)
|
||||
{
|
||||
if (MathHelper.IsPowerOfTwo((int)flag)) { return true; }
|
||||
#if DEBUG
|
||||
throw new InvalidOperationException($"\"{flag}\" is not a valid campaign update flag.");
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private UInt16 lastSaveID;
|
||||
public UInt16 LastSaveID
|
||||
{
|
||||
@@ -35,11 +76,11 @@ namespace Barotrauma
|
||||
#endif
|
||||
return lastSaveID;
|
||||
}
|
||||
set
|
||||
set
|
||||
{
|
||||
#if SERVER
|
||||
//trigger a campaign update to notify the clients of the changed save ID
|
||||
lastUpdateID++;
|
||||
IncrementLastUpdateIdForFlag(NetFlags.Misc);
|
||||
#endif
|
||||
lastSaveID = value;
|
||||
}
|
||||
@@ -52,23 +93,33 @@ namespace Barotrauma
|
||||
get; set;
|
||||
}
|
||||
|
||||
private MultiPlayerCampaign() : base(GameModePreset.MultiPlayerCampaign)
|
||||
private MultiPlayerCampaign(CampaignSettings settings) : base(GameModePreset.MultiPlayerCampaign, settings)
|
||||
{
|
||||
currentCampaignID++;
|
||||
lastUpdateID = new Dictionary<NetFlags, ushort>();
|
||||
foreach (NetFlags flag in Enum.GetValues(typeof(NetFlags)))
|
||||
{
|
||||
#if SERVER
|
||||
//server starts from a higher ID to ensure we send the initial state
|
||||
lastUpdateID[flag] = 1;
|
||||
#else
|
||||
lastUpdateID[flag] = 0;
|
||||
#endif
|
||||
}
|
||||
CampaignID = currentCampaignID;
|
||||
CampaignMetadata = new CampaignMetadata(this);
|
||||
UpgradeManager = new UpgradeManager(this);
|
||||
InitCampaignData();
|
||||
}
|
||||
|
||||
public static MultiPlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub, CampaignSettings settings)
|
||||
public static MultiPlayerCampaign StartNew(string mapSeed, CampaignSettings settings)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign();
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign(settings);
|
||||
//only the server generates the map, the clients load it from a save file
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
campaign.map = new Map(campaign, mapSeed, settings);
|
||||
campaign.Settings = settings;
|
||||
campaign.map = new Map(campaign, mapSeed);
|
||||
}
|
||||
campaign.InitProjSpecific();
|
||||
return campaign;
|
||||
@@ -76,7 +127,7 @@ namespace Barotrauma
|
||||
|
||||
public static MultiPlayerCampaign LoadNew(XElement element)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign();
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign(CampaignSettings.Empty);
|
||||
campaign.Load(element);
|
||||
campaign.InitProjSpecific();
|
||||
campaign.IsFirstRound = false;
|
||||
@@ -124,18 +175,17 @@ namespace Barotrauma
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "campaignsettings":
|
||||
case CampaignSettings.LowerCaseSaveElementName:
|
||||
Settings = new CampaignSettings(subElement);
|
||||
#if CLIENT
|
||||
GameMain.NetworkMember.ServerSettings.MaxMissionCount = Settings.MaxMissionCount;
|
||||
GameMain.NetworkMember.ServerSettings.RadiationEnabled = Settings.RadiationEnabled;
|
||||
GameMain.NetworkMember.ServerSettings.CampaignSettings = Settings;
|
||||
#endif
|
||||
break;
|
||||
case "map":
|
||||
if (map == null)
|
||||
{
|
||||
//map not created yet, loading this campaign for the first time
|
||||
map = Map.Load(this, subElement, Settings);
|
||||
map = Map.Load(this, subElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Map != null) { return Map.CurrentLocation; }
|
||||
if (dummyLocations == null) { CreateDummyLocations(); }
|
||||
if (dummyLocations == null) { dummyLocations = CreateDummyLocations(LevelData?.Seed ?? string.Empty); }
|
||||
if (dummyLocations == null) { throw new NullReferenceException("dummyLocations is null somehow!"); }
|
||||
return dummyLocations[0];
|
||||
}
|
||||
@@ -83,7 +83,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Map != null) { return Map.SelectedLocation; }
|
||||
if (dummyLocations == null) { CreateDummyLocations(); }
|
||||
if (dummyLocations == null) { dummyLocations = CreateDummyLocations(LevelData?.Seed ?? string.Empty); }
|
||||
if (dummyLocations == null) { throw new NullReferenceException("dummyLocations is null somehow!"); }
|
||||
return dummyLocations[1];
|
||||
}
|
||||
@@ -207,7 +207,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (gameModePreset.GameModeType == typeof(MultiPlayerCampaign))
|
||||
{
|
||||
var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub, settings);
|
||||
var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), settings);
|
||||
if (selectedSub != null)
|
||||
{
|
||||
campaign.Bank.Deduct(selectedSub.Price);
|
||||
@@ -218,7 +218,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
else if (gameModePreset.GameModeType == typeof(SinglePlayerCampaign))
|
||||
{
|
||||
var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub, settings);
|
||||
var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), settings);
|
||||
if (selectedSub != null)
|
||||
{
|
||||
campaign.Bank.TryDeduct(selectedSub.Price);
|
||||
@@ -245,25 +245,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDummyLocations(LocationType? forceLocationType = null)
|
||||
public static Location[] CreateDummyLocations(string seed, LocationType? forceLocationType = null)
|
||||
{
|
||||
dummyLocations = new Location[2];
|
||||
|
||||
string seed = "";
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.Level != null)
|
||||
{
|
||||
seed = GameMain.GameSession.Level.Seed;
|
||||
}
|
||||
else if (GameMain.NetLobbyScreen != null)
|
||||
{
|
||||
seed = GameMain.NetLobbyScreen.LevelSeed;
|
||||
}
|
||||
|
||||
var dummyLocations = new Location[2];
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(seed));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand, requireOutpost: true, forceLocationType: forceLocationType);
|
||||
}
|
||||
return dummyLocations;
|
||||
}
|
||||
|
||||
public void LoadPreviousSave()
|
||||
@@ -275,7 +265,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Switch to another submarine. The sub is loaded when the next round starts.
|
||||
/// </summary>
|
||||
public void SwitchSubmarine(SubmarineInfo newSubmarine, int cost, Client? client = null)
|
||||
public void SwitchSubmarine(SubmarineInfo newSubmarine, bool transferItems, int cost, Client? client = null)
|
||||
{
|
||||
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
|
||||
{
|
||||
@@ -299,6 +289,7 @@ namespace Barotrauma
|
||||
}
|
||||
GameAnalyticsManager.AddMoneySpentEvent(cost, GameAnalyticsManager.MoneySink.SubmarineSwitch, newSubmarine.Name);
|
||||
Campaign!.PendingSubmarineSwitch = newSubmarine;
|
||||
Campaign!.TransferItemsOnSubSwitch = transferItems;
|
||||
}
|
||||
|
||||
public void PurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null)
|
||||
@@ -309,6 +300,9 @@ namespace Barotrauma
|
||||
{
|
||||
GameAnalyticsManager.AddMoneySpentEvent(newSubmarine.Price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.Name);
|
||||
OwnedSubmarines.Add(newSubmarine);
|
||||
#if SERVER
|
||||
(Campaign as MultiPlayerCampaign)?.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.SubList);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +339,7 @@ namespace Barotrauma
|
||||
!missionPrefab.AllowedConnectionTypes.Any())
|
||||
{
|
||||
LocationType? locationType = LocationType.Prefabs.FirstOrDefault(lt => missionPrefab.AllowedLocationTypes.Any(m => m == lt.Identifier));
|
||||
CreateDummyLocations(locationType);
|
||||
dummyLocations = CreateDummyLocations(levelSeed, locationType);
|
||||
randomLevel = LevelData.CreateRandom(levelSeed, difficulty, levelGenerationParams, requireOutpost: true);
|
||||
break;
|
||||
}
|
||||
@@ -430,7 +424,7 @@ namespace Barotrauma
|
||||
Level? level = null;
|
||||
if (levelData != null)
|
||||
{
|
||||
level = Level.Generate(levelData, mirrorLevel, startOutpost, endOutpost);
|
||||
level = Level.Generate(levelData, mirrorLevel, StartLocation, EndLocation, startOutpost, endOutpost);
|
||||
}
|
||||
|
||||
InitializeLevel(level);
|
||||
@@ -603,7 +597,7 @@ namespace Barotrauma
|
||||
Level.SpawnCorpses();
|
||||
Level.PrepareBeaconStation();
|
||||
}
|
||||
AutoItemPlacer.SpawnItems();
|
||||
AutoItemPlacer.SpawnItems(Campaign?.Settings.StartItemSet);
|
||||
}
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
|
||||
@@ -360,9 +360,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && slots[i].Items.Any(it => it != item))
|
||||
{
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
if (!slots[i].First().AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(slots[i].FirstOrDefault(), character, new List<InvSlotType> { InvSlotType.Any }, true, ignoreCondition))
|
||||
{
|
||||
free = false;
|
||||
@@ -382,9 +379,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && item.GetComponents<Pickable>().Any(p => p.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i]))) && slots[i].Empty())
|
||||
{
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
bool removeFromOtherSlots = item.ParentInventory != this;
|
||||
if (placedInSlot == -1 && inWrongSlot)
|
||||
{
|
||||
@@ -454,9 +448,6 @@ namespace Barotrauma
|
||||
GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
return false;
|
||||
}
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(SlotTypes[index])) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
//there's already an item in the slot
|
||||
if (slots[index].Any())
|
||||
{
|
||||
@@ -480,9 +471,6 @@ namespace Barotrauma
|
||||
foreach (InvSlotType allowedSlot in pickable.AllowedSlots)
|
||||
{
|
||||
if (!allowedSlot.HasFlag(SlotTypes[index])) { continue; }
|
||||
#if CLIENT
|
||||
if (PersonalSlots.HasFlag(allowedSlot)) { hidePersonalSlots = false; }
|
||||
#endif
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Any() && !slots[i].Contains(item))
|
||||
|
||||
@@ -144,9 +144,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool Combine(GeneticMaterial otherGeneticMaterial, Character user)
|
||||
public enum CombineResult
|
||||
{
|
||||
if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; }
|
||||
None,
|
||||
Refined,
|
||||
Combined
|
||||
}
|
||||
|
||||
public CombineResult Combine(GeneticMaterial otherGeneticMaterial, Character user)
|
||||
{
|
||||
if (!CanBeCombinedWith(otherGeneticMaterial)) { return CombineResult.None; }
|
||||
|
||||
float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
|
||||
conditionIncrease += user?.GetStatValue(StatTypes.GeneticMaterialRefineBonus) ?? 0.0f;
|
||||
@@ -158,7 +165,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
MakeTainted();
|
||||
}
|
||||
return true;
|
||||
return CombineResult.Refined;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -171,7 +178,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
MakeTainted();
|
||||
}
|
||||
return false;
|
||||
return CombineResult.Combined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -697,14 +697,17 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 fromCharacterToLeak = leak.WorldPosition - character.AnimController.AimSourceWorldPos;
|
||||
float dist = fromCharacterToLeak.Length();
|
||||
float reach = AIObjectiveFixLeak.CalculateReach(this, character);
|
||||
|
||||
if (dist > reach * 3)
|
||||
if (dist > reach * 2)
|
||||
{
|
||||
// Too far away -> consider this done and hope the AI is smart enough to move closer
|
||||
Reset();
|
||||
return true;
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering)
|
||||
{
|
||||
pathSteering.ResetPath();
|
||||
}
|
||||
if (!character.AnimController.InWater)
|
||||
{
|
||||
// TODO: use the collider size?
|
||||
@@ -714,34 +717,25 @@ namespace Barotrauma.Items.Components
|
||||
humanAnim.Crouching = true;
|
||||
}
|
||||
}
|
||||
if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.InWater))
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
// Steer closer
|
||||
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
|
||||
if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.InWater))
|
||||
{
|
||||
// Swimming inside the sub
|
||||
if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && (indoorSteering.CurrentPath.Unreachable || indoorSteering.CurrentPath.Finished))
|
||||
// Steer closer
|
||||
Vector2 dir = Vector2.Normalize(fromCharacterToLeak);
|
||||
if (!character.InWater)
|
||||
{
|
||||
Vector2 dir = Vector2.Normalize(fromCharacterToLeak);
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
|
||||
dir.Y = 0;
|
||||
}
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, dir);
|
||||
}
|
||||
else
|
||||
else if (dist < reach * 0.25f && !character.IsClimbing)
|
||||
{
|
||||
// Swimming outside the sub
|
||||
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
|
||||
// Too close -> steer away
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
|
||||
}
|
||||
}
|
||||
else if (dist < reach * 0.25f)
|
||||
{
|
||||
// Too close -> steer away
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
|
||||
}
|
||||
if (dist <= reach)
|
||||
if (dist <= reach || character.IsClimbing)
|
||||
{
|
||||
// In range
|
||||
character.CursorPosition = leak.WorldPosition;
|
||||
@@ -815,7 +809,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
|
||||
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
|
||||
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Max(s => s.damage) < 0.1f);
|
||||
|
||||
if (leakFixed && leak.FlowTargetHull?.DisplayName != null && character.IsOnPlayerTeam)
|
||||
{
|
||||
|
||||
@@ -187,7 +187,12 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool RemoveContainedItemsOnDeconstruct { get; set; }
|
||||
|
||||
private SlotRestrictions[] slotRestrictions;
|
||||
private readonly ImmutableArray<SlotRestrictions> slotRestrictions;
|
||||
|
||||
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
|
||||
private Vector2 prevContainedItemPositions;
|
||||
|
||||
|
||||
public bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined)
|
||||
{
|
||||
@@ -237,10 +242,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
Inventory = new ItemInventory(item, this, totalCapacity, SlotsPerRow);
|
||||
slotRestrictions = new SlotRestrictions[totalCapacity];
|
||||
|
||||
List<SlotRestrictions> newSlotRestrictions = new List<SlotRestrictions>(totalCapacity);
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
slotRestrictions[i] = new SlotRestrictions(maxStackSize, ContainableItems);
|
||||
newSlotRestrictions.Add(new SlotRestrictions(maxStackSize, ContainableItems));
|
||||
}
|
||||
|
||||
int subContainerIndex = capacity;
|
||||
@@ -268,11 +274,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
|
||||
{
|
||||
slotRestrictions[i] = new SlotRestrictions(subMaxStackSize, subContainableItems);
|
||||
newSlotRestrictions.Add(new SlotRestrictions(subMaxStackSize, subContainableItems));
|
||||
}
|
||||
subContainerIndex += subCapacity;
|
||||
}
|
||||
capacity = totalCapacity;
|
||||
slotRestrictions = newSlotRestrictions.ToImmutableArray();
|
||||
System.Diagnostics.Debug.Assert(totalCapacity == slotRestrictions.Length);
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -365,18 +373,21 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SpawnWithId) && !alwaysContainedItemsSpawned)
|
||||
{
|
||||
SpawnAlwaysContainedItems();
|
||||
alwaysContainedItemsSpawned = true;
|
||||
}
|
||||
|
||||
if (item.ParentInventory is CharacterInventory ownerInventory)
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
if (Vector2.DistanceSquared(prevContainedItemPositions, item.Position) > 10.0f)
|
||||
{
|
||||
SetContainedItemPositions();
|
||||
prevContainedItemPositions = item.Position;
|
||||
}
|
||||
|
||||
if (AutoInject)
|
||||
{
|
||||
@@ -397,7 +408,7 @@ namespace Barotrauma.Items.Components
|
||||
item.body.Enabled &&
|
||||
item.body.FarseerBody.Awake)
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
SetContainedItemPositions();
|
||||
}
|
||||
else if (activeContainedItems.Count == 0)
|
||||
{
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace Barotrauma.Items.Components
|
||||
CancelUsing(user);
|
||||
user = null;
|
||||
}
|
||||
if (!IsToggle) { IsActive = false; }
|
||||
if (!IsToggle || item.Connections == null) { IsActive = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+22
-10
@@ -231,28 +231,40 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem == otherItem) { continue; }
|
||||
if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r == otherItem.Prefab.Identifier))
|
||||
{
|
||||
user?.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
|
||||
foreach (Character character in Character.GetFriendlyCrew(user))
|
||||
{
|
||||
character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
|
||||
}
|
||||
|
||||
var geneticMaterial1 = targetItem.GetComponent<GeneticMaterial>();
|
||||
var geneticMaterial2 = otherItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial1 != null && geneticMaterial2 != null)
|
||||
{
|
||||
if (geneticMaterial1.Combine(geneticMaterial2, user))
|
||||
var result = geneticMaterial1.Combine(geneticMaterial2, user);
|
||||
if (result == GeneticMaterial.CombineResult.Refined)
|
||||
{
|
||||
inputContainer.Inventory.RemoveItem(otherItem);
|
||||
OutputContainer.Inventory.RemoveItem(otherItem);
|
||||
Entity.Spawner.AddItemToRemoveQueue(otherItem);
|
||||
}
|
||||
if (result != GeneticMaterial.CombineResult.None)
|
||||
{
|
||||
OnCombinedOrRefined();
|
||||
}
|
||||
allowRemove = false;
|
||||
return;
|
||||
}
|
||||
inputContainer.Inventory.RemoveItem(otherItem);
|
||||
OutputContainer.Inventory.RemoveItem(otherItem);
|
||||
Entity.Spawner.AddItemToRemoveQueue(otherItem);
|
||||
else
|
||||
{
|
||||
inputContainer.Inventory.RemoveItem(otherItem);
|
||||
OutputContainer.Inventory.RemoveItem(otherItem);
|
||||
Entity.Spawner.AddItemToRemoveQueue(otherItem);
|
||||
OnCombinedOrRefined();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnCombinedOrRefined()
|
||||
{
|
||||
user?.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
|
||||
foreach (Character character in Character.GetFriendlyCrew(user))
|
||||
{
|
||||
character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,8 +400,8 @@ namespace Barotrauma.Items.Components
|
||||
private void IncreaseSkillLevel(Character user, float deltaTime)
|
||||
{
|
||||
if (user?.Info == null) { return; }
|
||||
// Do not increase the helm skill when "steering" the sub in an outpost level
|
||||
if (GameMain.GameSession?.Campaign != null && Level.IsLoadedOutpost) { return; }
|
||||
// Do not increase the helm skill when "steering" the sub while docked into something static (e.g. outpost or wreck)
|
||||
if (GameMain.GameSession?.Campaign != null && controlledSub != null && controlledSub.DockedTo.Any(d => d.PhysicsBody.BodyType == BodyType.Static)) { return; }
|
||||
|
||||
float userSkill = Math.Max(user.GetSkillLevel("helm"), 1.0f) / 100.0f;
|
||||
user.Info.IncreaseSkillLevel(
|
||||
|
||||
@@ -3,7 +3,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -81,6 +80,8 @@ namespace Barotrauma.Items.Components
|
||||
private ItemContainer? container;
|
||||
private float growthTickTimer;
|
||||
|
||||
private List<LightComponent>? lightComponents;
|
||||
|
||||
public Planter(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
canBePicked = true;
|
||||
@@ -107,10 +108,14 @@ namespace Barotrauma.Items.Components
|
||||
base.OnItemLoaded();
|
||||
IsActive = true;
|
||||
#if CLIENT
|
||||
lightComponent = item.GetComponent<LightComponent>();
|
||||
if (lightComponent != null)
|
||||
var lights = item.GetComponents<LightComponent>();
|
||||
if (lights.Any())
|
||||
{
|
||||
lightComponent.Light.Enabled = false;
|
||||
lightComponents = lights.ToList();
|
||||
foreach (var light in lightComponents)
|
||||
{
|
||||
light.Light.Enabled = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
container = item.GetComponent<ItemContainer>();
|
||||
@@ -227,12 +232,17 @@ namespace Barotrauma.Items.Components
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
#if CLIENT
|
||||
if (lightComponent != null)
|
||||
if (lightComponents != null && lightComponents.Count > 0)
|
||||
{
|
||||
bool hasSeed = false;
|
||||
foreach (Growable? seed in GrowableSeeds) { hasSeed |= seed != null; }
|
||||
|
||||
lightComponent.Light.Enabled = hasSeed;
|
||||
foreach (Growable? seed in GrowableSeeds)
|
||||
{
|
||||
hasSeed |= seed != null;
|
||||
}
|
||||
foreach (var light in lightComponents)
|
||||
{
|
||||
light.Light.Enabled = hasSeed;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -602,7 +602,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool ShouldDeteriorate()
|
||||
{
|
||||
if (Level.IsLoadedOutpost) { return false; }
|
||||
if (Level.IsLoadedFriendlyOutpost) { return false; }
|
||||
|
||||
if (LastActiveTime > Timing.TotalTime) { return true; }
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
|
||||
@@ -13,12 +13,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float updateTimer;
|
||||
|
||||
[Flags]
|
||||
public enum TargetType
|
||||
{
|
||||
Any,
|
||||
Human,
|
||||
Monster,
|
||||
Wall
|
||||
Human = 1,
|
||||
Monster = 2,
|
||||
Wall = 4,
|
||||
Any = Human | Monster | Wall,
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
@@ -179,6 +180,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(new Signal(signalOut, 1), "state_out"); }
|
||||
|
||||
if (MotionDetected)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, deltaTime);
|
||||
}
|
||||
|
||||
updateTimer -= deltaTime;
|
||||
if (updateTimer > 0.0f) { return; }
|
||||
|
||||
@@ -199,8 +205,7 @@ namespace Barotrauma.Items.Components
|
||||
float broadRangeX = Math.Max(rangeX * 2, 500);
|
||||
float broadRangeY = Math.Max(rangeY * 2, 500);
|
||||
|
||||
if (item.CurrentHull == null && item.Submarine != null &&
|
||||
(Target == TargetType.Wall || Target == TargetType.Any))
|
||||
if (item.CurrentHull == null && item.Submarine != null && Target.HasFlag(TargetType.Wall))
|
||||
{
|
||||
if (Level.Loaded != null && (Math.Abs(item.Submarine.Velocity.X) > MinimumVelocity || Math.Abs(item.Submarine.Velocity.Y) > MinimumVelocity))
|
||||
{
|
||||
@@ -248,7 +253,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (Target != TargetType.Wall)
|
||||
if (Target.HasFlag(TargetType.Human) || Target.HasFlag(TargetType.Monster))
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
@@ -258,14 +263,13 @@ namespace Barotrauma.Items.Components
|
||||
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
|
||||
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
|
||||
switch (Target)
|
||||
if (c.IsHuman)
|
||||
{
|
||||
case TargetType.Human:
|
||||
if (!c.IsHuman) { continue; }
|
||||
break;
|
||||
case TargetType.Monster:
|
||||
if (c.IsHuman || c.IsPet) { continue; }
|
||||
break;
|
||||
if (!Target.HasFlag(TargetType.Human)) { continue; }
|
||||
}
|
||||
else if (!c.IsPet)
|
||||
{
|
||||
if (!Target.HasFlag(TargetType.Monster)) { continue; }
|
||||
}
|
||||
|
||||
//do a rough check based on the position of the character's collider first
|
||||
|
||||
@@ -4,6 +4,7 @@ using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -12,6 +13,46 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)]
|
||||
public float Force { get; set; }
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force gets higher the closer the triggerer is to the center of the trigger.", alwaysUseInstanceValues: true)]
|
||||
public bool DistanceBasedForce { get; set; }
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force fluctuates over time or if it stays constant.", alwaysUseInstanceValues: true)]
|
||||
public bool ForceFluctuation { get; set; }
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "How much the fluctuation affects the force. 1 is the maximum fluctuation, 0 is no fluctuation.", alwaysUseInstanceValues: true)]
|
||||
private float ForceFluctuationStrength
|
||||
{
|
||||
get
|
||||
{
|
||||
return forceFluctuationStrength;
|
||||
}
|
||||
set
|
||||
{
|
||||
forceFluctuationStrength = Math.Clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "How fast (cycles per second) the force fluctuates.", alwaysUseInstanceValues: true)]
|
||||
private float ForceFluctuationFrequency
|
||||
{
|
||||
get
|
||||
{
|
||||
return forceFluctuationFrequency;
|
||||
}
|
||||
set
|
||||
{
|
||||
forceFluctuationFrequency = Math.Max(value, 0.01f);
|
||||
}
|
||||
}
|
||||
[Serialize(0.01f, IsPropertySaveable.Yes, description: "How often (in seconds) the force fluctuation is calculated.", alwaysUseInstanceValues: true)]
|
||||
private float ForceFluctuationInterval
|
||||
{
|
||||
get
|
||||
{
|
||||
return forceFluctuationInterval;
|
||||
}
|
||||
set
|
||||
{
|
||||
forceFluctuationInterval = Math.Max(value, 0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
public PhysicsBody PhysicsBody { get; private set; }
|
||||
private float Radius { get; set; }
|
||||
@@ -38,11 +79,6 @@ namespace Barotrauma.Items.Components
|
||||
private readonly LevelTrigger.TriggererType triggeredBy;
|
||||
private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
|
||||
private readonly bool triggerOnce;
|
||||
private readonly bool distanceBasedForce;
|
||||
private readonly bool forceFluctuation;
|
||||
private readonly float forceFluctuationStrength;
|
||||
private readonly float forceFluctuationFrequency;
|
||||
private readonly float forceFluctuationInterval;
|
||||
private readonly List<ISerializableEntity> statusEffectTargets = new List<ISerializableEntity>();
|
||||
/// <summary>
|
||||
/// Effects applied to entities inside the trigger
|
||||
@@ -53,6 +89,10 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
private readonly List<Attack> attacks = new List<Attack>();
|
||||
|
||||
private float forceFluctuationStrength;
|
||||
private float forceFluctuationFrequency;
|
||||
private float forceFluctuationInterval;
|
||||
|
||||
public TriggerComponent(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
string triggeredByAttribute = element.GetAttributeString("triggeredby", "Character");
|
||||
@@ -61,15 +101,6 @@ namespace Barotrauma.Items.Components
|
||||
DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type.");
|
||||
}
|
||||
triggerOnce = element.GetAttributeBool("triggeronce", false);
|
||||
distanceBasedForce = element.GetAttributeBool("distancebasedforce", false);
|
||||
forceFluctuation = element.GetAttributeBool("forcefluctuation", false);
|
||||
forceFluctuationStrength = element.GetAttributeFloat("forcefluctuationstrength", 1.0f);
|
||||
forceFluctuationStrength = Math.Clamp(forceFluctuationStrength, 0.0f, 1.0f);
|
||||
forceFluctuationFrequency = element.GetAttributeFloat("fluctuationfrequency", 1.0f);
|
||||
forceFluctuationFrequency = Math.Max(forceFluctuationFrequency, 0.01f);
|
||||
forceFluctuationInterval = element.GetAttributeFloat("fluctuationinterval", 0.01f);
|
||||
forceFluctuationInterval = Math.Max(forceFluctuationInterval, 0.01f);
|
||||
|
||||
string parentDebugName = $"TriggerComponent in {item.Name}";
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
@@ -153,14 +184,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
TriggerActive = triggerers.Any();
|
||||
|
||||
if (forceFluctuation && TriggerActive && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
if (ForceFluctuation && TriggerActive && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
{
|
||||
ForceFluctuationTimer += deltaTime;
|
||||
if (ForceFluctuationTimer >= forceFluctuationInterval)
|
||||
if (ForceFluctuationTimer >= ForceFluctuationInterval)
|
||||
{
|
||||
float v = MathF.Sin(2 * MathF.PI * forceFluctuationFrequency * TimeInLevel);
|
||||
float v = MathF.Sin(2 * MathF.PI * ForceFluctuationFrequency * TimeInLevel);
|
||||
float amount = MathUtils.InverseLerp(-1.0f, 1.0f, v);
|
||||
CurrentForceFluctuation = MathHelper.Lerp(1.0f - forceFluctuationStrength, 1.0f, amount);
|
||||
CurrentForceFluctuation = MathHelper.Lerp(1.0f - ForceFluctuationStrength, 1.0f, amount);
|
||||
ForceFluctuationTimer = 0.0f;
|
||||
GameMain.NetworkMember?.CreateEntityEvent(this);
|
||||
}
|
||||
@@ -179,7 +210,7 @@ namespace Barotrauma.Items.Components
|
||||
LevelTrigger.ApplyAttacks(attacks, item.WorldPosition, deltaTime);
|
||||
}
|
||||
|
||||
if (Force < 0.01f)
|
||||
if (Math.Abs(Force) < 0.01f)
|
||||
{
|
||||
// Just ignore very minimal forces
|
||||
continue;
|
||||
@@ -205,7 +236,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Vector2 diff = ConvertUnits.ToDisplayUnits(PhysicsBody.SimPosition - body.SimPosition);
|
||||
if (diff.LengthSquared() < 0.0001f) { return; }
|
||||
float distanceFactor = distanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
|
||||
float distanceFactor = DistanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
|
||||
if (distanceFactor <= 0.0f) { return; }
|
||||
Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff);
|
||||
if (force.LengthSquared() < 0.01f) { return; }
|
||||
@@ -227,5 +258,43 @@ namespace Barotrauma.Items.Components
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
base.ReceiveSignal(signal, connection);
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "set_force":
|
||||
if (!FloatTryParse(signal, out float force)) { break; }
|
||||
Force = force;
|
||||
break;
|
||||
case "set_distancebasedforce":
|
||||
if (!bool.TryParse(signal.value, out bool distanceBasedForce)) { break; }
|
||||
DistanceBasedForce = distanceBasedForce;
|
||||
break;
|
||||
case "set_forcefluctuation":
|
||||
if (!bool.TryParse(signal.value, out bool forceFluctuation)) { break; }
|
||||
ForceFluctuation = forceFluctuation;
|
||||
break;
|
||||
case "set_forcefluctuationstrength":
|
||||
if (!FloatTryParse(signal, out float forceFluctuationStrength)) { break; }
|
||||
ForceFluctuationStrength = forceFluctuationStrength;
|
||||
break;
|
||||
case "set_forcefluctuationfrequency":
|
||||
if (!FloatTryParse(signal, out float forceFluctuationFrequency)) { break; }
|
||||
ForceFluctuationFrequency = forceFluctuationFrequency;
|
||||
break;
|
||||
case "set_forcefluctuationinterval":
|
||||
if (!FloatTryParse(signal, out float forceFluctuationInterval)) { break; }
|
||||
ForceFluctuationInterval = forceFluctuationInterval;
|
||||
break;
|
||||
}
|
||||
|
||||
static bool FloatTryParse(Signal signal, out float value)
|
||||
{
|
||||
return float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics.Dynamics;
|
||||
|
||||
@@ -20,8 +18,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Vector2 barrelPos;
|
||||
private Vector2 transformedBarrelPos;
|
||||
|
||||
private LightComponent lightComponent;
|
||||
|
||||
private float rotation, targetRotation;
|
||||
|
||||
@@ -71,6 +67,8 @@ namespace Barotrauma.Items.Components
|
||||
public Character ActiveUser;
|
||||
private float resetActiveUserTimer;
|
||||
|
||||
private List<LightComponent> lightComponents;
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
@@ -168,10 +166,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
rotation = (minRotation + maxRotation) / 2;
|
||||
#if CLIENT
|
||||
if (lightComponent != null)
|
||||
if (lightComponents != null)
|
||||
{
|
||||
lightComponent.Rotation = rotation;
|
||||
lightComponent.Light.Rotation = -rotation;
|
||||
foreach (var light in lightComponents)
|
||||
{
|
||||
light.Rotation = rotation;
|
||||
light.Light.Rotation = -rotation;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -331,27 +332,39 @@ namespace Barotrauma.Items.Components
|
||||
if (loadedRotationLimits.HasValue) { RotationLimits = loadedRotationLimits.Value; }
|
||||
if (loadedBaseRotation.HasValue) { BaseRotation = loadedBaseRotation.Value; }
|
||||
targetRotation = rotation;
|
||||
FindLightComponent();
|
||||
UpdateTransformedBarrelPos();
|
||||
}
|
||||
|
||||
private void FindLightComponent()
|
||||
private void FindLightComponents()
|
||||
{
|
||||
if (lightComponents != null)
|
||||
{
|
||||
// Can't run again, because of reparenting.
|
||||
return;
|
||||
}
|
||||
foreach (LightComponent lc in item.GetComponents<LightComponent>())
|
||||
{
|
||||
// Only make the Turret control the LightComponents that are it's children. So it'd be possible to for example have some extra lights on the turret that don't rotate with it.
|
||||
if (lc?.Parent == this)
|
||||
{
|
||||
lightComponent = lc;
|
||||
break;
|
||||
if (lightComponents == null)
|
||||
{
|
||||
lightComponents = new List<LightComponent>();
|
||||
}
|
||||
lightComponents.Add(lc);
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (lightComponent != null)
|
||||
if (lightComponents != null)
|
||||
{
|
||||
lightComponent.Parent = null;
|
||||
lightComponent.Rotation = Rotation - item.RotationRad;
|
||||
lightComponent.Light.Rotation = -rotation;
|
||||
foreach (var light in lightComponents)
|
||||
{
|
||||
// We want the turret to control the state of the LightComponent, not tie it's state to the state of the Turret (the light can be inactive even if the turret is active)
|
||||
light.Parent = null;
|
||||
light.Rotation = Rotation - item.RotationRad;
|
||||
light.Light.Rotation = -rotation;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -428,7 +441,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (MathUtils.NearlyEqual(minRotation, maxRotation))
|
||||
{
|
||||
UpdateLightComponent();
|
||||
UpdateLightComponents();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -452,7 +465,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
// Do not increase the weapons skill when operating a turret in an outpost level
|
||||
if (user?.Info != null && (GameMain.GameSession?.Campaign == null || !Level.IsLoadedOutpost))
|
||||
if (user?.Info != null && (GameMain.GameSession?.Campaign == null || !Level.IsLoadedFriendlyOutpost))
|
||||
{
|
||||
user.Info.IncreaseSkillLevel("weapons".ToIdentifier(),
|
||||
SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f));
|
||||
@@ -509,14 +522,17 @@ namespace Barotrauma.Items.Components
|
||||
aiFindTargetTimer -= deltaTime;
|
||||
}
|
||||
|
||||
UpdateLightComponent();
|
||||
UpdateLightComponents();
|
||||
}
|
||||
|
||||
private void UpdateLightComponent()
|
||||
private void UpdateLightComponents()
|
||||
{
|
||||
if (lightComponent != null)
|
||||
if (lightComponents != null)
|
||||
{
|
||||
lightComponent.Rotation = Rotation - item.RotationRad;
|
||||
foreach (var light in lightComponents)
|
||||
{
|
||||
light.Rotation = Rotation - item.RotationRad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1601,21 +1617,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "toggle_light":
|
||||
if (lightComponent != null && signal.value != "0")
|
||||
if (lightComponents != null && signal.value != "0")
|
||||
{
|
||||
lightComponent.IsOn = !lightComponent.IsOn;
|
||||
UpdateLightComponent();
|
||||
foreach (var light in lightComponents)
|
||||
{
|
||||
light.IsOn = !light.IsOn;
|
||||
}
|
||||
UpdateLightComponents();
|
||||
}
|
||||
break;
|
||||
case "set_light":
|
||||
if (lightComponent != null)
|
||||
if (lightComponents != null)
|
||||
{
|
||||
bool shouldBeOn = signal.value != "0";
|
||||
if (shouldBeOn != lightComponent.IsOn)
|
||||
foreach (var light in lightComponents)
|
||||
{
|
||||
lightComponent.IsOn = shouldBeOn;
|
||||
UpdateLightComponent();
|
||||
light.IsOn = shouldBeOn;
|
||||
}
|
||||
UpdateLightComponents();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1633,7 +1652,7 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
FindLightComponent();
|
||||
FindLightComponents();
|
||||
targetRotation = rotation;
|
||||
if (!loadedBaseRotation.HasValue)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -82,7 +81,20 @@ namespace Barotrauma
|
||||
public string Sound { get; private set; }
|
||||
public Point? SheetIndex { get; private set; }
|
||||
|
||||
public LightComponent LightComponent { get; set; }
|
||||
public LightComponent LightComponent => LightComponents?.FirstOrDefault();
|
||||
|
||||
public List<LightComponent> LightComponents
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_lightComponents == null)
|
||||
{
|
||||
_lightComponents = new List<LightComponent>();
|
||||
}
|
||||
return _lightComponents;
|
||||
}
|
||||
}
|
||||
private List<LightComponent> _lightComponents;
|
||||
|
||||
public int Variant { get; set; }
|
||||
|
||||
@@ -338,11 +350,14 @@ namespace Barotrauma.Items.Components
|
||||
foreach (var lightElement in subElement.Elements())
|
||||
{
|
||||
if (!lightElement.Name.ToString().Equals("lightcomponent", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
wearableSprites[i].LightComponent = new LightComponent(item, lightElement)
|
||||
wearableSprites[i].LightComponents.Add(new LightComponent(item, lightElement)
|
||||
{
|
||||
Parent = this
|
||||
};
|
||||
item.AddComponent(wearableSprites[i].LightComponent);
|
||||
});
|
||||
foreach (var light in wearableSprites[i].LightComponents)
|
||||
{
|
||||
item.AddComponent(light);
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
@@ -413,7 +428,10 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
if (wearableSprite.LightComponent != null)
|
||||
{
|
||||
wearableSprite.LightComponent.ParentBody = equipLimb.body;
|
||||
foreach (var light in wearableSprite.LightComponents)
|
||||
{
|
||||
light.ParentBody = equipLimb.body;
|
||||
}
|
||||
}
|
||||
|
||||
limb[i] = equipLimb;
|
||||
@@ -467,7 +485,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (wearableSprites[i].LightComponent != null)
|
||||
{
|
||||
wearableSprites[i].LightComponent.ParentBody = null;
|
||||
foreach (var light in wearableSprites[i].LightComponents)
|
||||
{
|
||||
light.ParentBody = null;
|
||||
}
|
||||
}
|
||||
|
||||
equipLimb.WearingItems.RemoveAll(w => w != null && w == wearableSprites[i]);
|
||||
@@ -494,7 +515,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
item.SetTransform(picker.SimPosition, 0.0f);
|
||||
item.SetContainedItemPositions();
|
||||
|
||||
item.ApplyStatusEffects(ActionType.OnWearing, deltaTime, picker);
|
||||
|
||||
|
||||
@@ -24,13 +24,18 @@ namespace Barotrauma
|
||||
partial class Item : MapEntity, IDamageable, IIgnorable, ISerializableEntity, IServerPositionSync, IClientSerializable
|
||||
{
|
||||
public static List<Item> ItemList = new List<Item>();
|
||||
|
||||
private static readonly HashSet<Item> dangerousItems = new HashSet<Item>();
|
||||
|
||||
public static IReadOnlyCollection<Item> DangerousItems { get { return dangerousItems; } }
|
||||
|
||||
public new ItemPrefab Prefab => base.Prefab as ItemPrefab;
|
||||
|
||||
public static bool ShowLinks = true;
|
||||
|
||||
private readonly HashSet<Identifier> tags;
|
||||
|
||||
private bool isWire, isLogic;
|
||||
private readonly bool isWire, isLogic;
|
||||
|
||||
private Hull currentHull;
|
||||
public Hull CurrentHull
|
||||
@@ -279,7 +284,10 @@ namespace Barotrauma
|
||||
if (Screen.Selected == GameMain.SubEditorScreen)
|
||||
{
|
||||
SetContainedItemPositions();
|
||||
GetComponent<LightComponent>()?.SetLightSourceTransform();
|
||||
foreach (var light in GetComponents<LightComponent>())
|
||||
{
|
||||
light.SetLightSourceTransform();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1001,6 +1009,10 @@ namespace Barotrauma
|
||||
|
||||
InsertToList();
|
||||
ItemList.Add(this);
|
||||
if (Prefab.IsDangerous)
|
||||
{
|
||||
dangerousItems.Add(this);
|
||||
}
|
||||
|
||||
DebugConsole.Log("Created " + Name + " (" + ID + ")");
|
||||
|
||||
@@ -1092,17 +1104,16 @@ namespace Barotrauma
|
||||
|
||||
component.OnActiveStateChanged += (bool isActive) =>
|
||||
{
|
||||
bool hasSounds = false;
|
||||
bool needsSoundUpdate = false;
|
||||
#if CLIENT
|
||||
hasSounds = component.HasSounds;
|
||||
needsSoundUpdate = component.NeedsSoundUpdate();
|
||||
#endif
|
||||
//component doesn't need to be updated if it isn't active, doesn't have a parent that could activate it,
|
||||
//nor status effects, sounds or conditionals that would need to run
|
||||
//nor sounds or conditionals that would need to run
|
||||
if (!isActive && !component.UpdateWhenInactive &&
|
||||
!hasSounds &&
|
||||
!needsSoundUpdate &&
|
||||
component.Parent == null &&
|
||||
(component.IsActiveConditionals == null || !component.IsActiveConditionals.Any()) &&
|
||||
(component.statusEffectLists == null || !component.statusEffectLists.Any()))
|
||||
(component.IsActiveConditionals == null || !component.IsActiveConditionals.Any()))
|
||||
{
|
||||
if (updateableComponents.Contains(component)) { updateableComponents.Remove(component); }
|
||||
}
|
||||
@@ -1499,6 +1510,11 @@ namespace Barotrauma
|
||||
|
||||
public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null)
|
||||
{
|
||||
if (effect.intervalTimer > 0.0f)
|
||||
{
|
||||
effect.intervalTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (!isNetworkEvent && checkCondition)
|
||||
{
|
||||
if (condition == 0.0f && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { return; }
|
||||
@@ -1630,6 +1646,7 @@ namespace Barotrauma
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
ic.PlaySound(ActionType.OnBroken);
|
||||
ic.StopSounds(ActionType.OnActive);
|
||||
}
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return; }
|
||||
#endif
|
||||
@@ -1722,6 +1739,19 @@ namespace Barotrauma
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
#if SERVER
|
||||
if (!(Submarine is { Loading: true }))
|
||||
{
|
||||
sendConditionUpdateTimer -= deltaTime;
|
||||
if (conditionUpdatePending && sendConditionUpdateTimer <= 0.0f)
|
||||
{
|
||||
SendPendingNetworkUpdates();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (impactQueue != null)
|
||||
{
|
||||
while (impactQueue.TryDequeue(out float impact))
|
||||
@@ -1730,22 +1760,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && (!Submarine?.Loading ?? true))
|
||||
{
|
||||
sendConditionUpdateTimer -= deltaTime;
|
||||
if (conditionUpdatePending && sendConditionUpdateTimer <= 0.0f)
|
||||
{
|
||||
SendPendingNetworkUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
if (aiTarget != null)
|
||||
if (aiTarget != null && aiTarget.NeedsUpdate)
|
||||
{
|
||||
aiTarget.Update(deltaTime);
|
||||
}
|
||||
|
||||
if (!isActive) { return; }
|
||||
|
||||
ApplyStatusEffects(ActionType.Always, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
|
||||
ApplyStatusEffects(parentInventory == null ? ActionType.OnNotContained : ActionType.OnContained, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
|
||||
|
||||
@@ -1846,7 +1865,10 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (updateableComponents.Count == 0 && !hasStatusEffectsOfType[(int)ActionType.Always] && (body == null || !body.Enabled))
|
||||
if (updateableComponents.Count == 0 &&
|
||||
(aiTarget == null || !aiTarget.NeedsUpdate) &&
|
||||
!hasStatusEffectsOfType[(int)ActionType.Always] &&
|
||||
(body == null || !body.Enabled))
|
||||
{
|
||||
#if CLIENT
|
||||
positionBuffer.Clear();
|
||||
@@ -1983,6 +2005,7 @@ namespace Barotrauma
|
||||
|
||||
impactQueue ??= new ConcurrentQueue<float>();
|
||||
impactQueue.Enqueue(impact);
|
||||
isActive = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2501,11 +2524,9 @@ namespace Barotrauma
|
||||
if (ic.Use(deltaTime, character))
|
||||
{
|
||||
ic.WasUsed = true;
|
||||
|
||||
#if CLIENT
|
||||
ic.PlaySound(ActionType.OnUse, character);
|
||||
#endif
|
||||
|
||||
ic.PlaySound(ActionType.OnUse, character);
|
||||
#endif
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb);
|
||||
|
||||
if (ic.DeleteOnUse) { remove = true; }
|
||||
@@ -2534,11 +2555,9 @@ namespace Barotrauma
|
||||
if (ic.SecondaryUse(deltaTime, character))
|
||||
{
|
||||
ic.WasSecondaryUsed = true;
|
||||
|
||||
#if CLIENT
|
||||
ic.PlaySound(ActionType.OnSecondaryUse, character);
|
||||
#endif
|
||||
|
||||
ic.ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character);
|
||||
|
||||
if (ic.DeleteOnUse) { remove = true; }
|
||||
@@ -3219,9 +3238,17 @@ namespace Barotrauma
|
||||
item.RecalculateConditionValues();
|
||||
item.SetActiveSprite();
|
||||
|
||||
if (submarine?.Info.GameVersion != null)
|
||||
Version savedVersion = submarine?.Info.GameVersion;
|
||||
if (element.Document?.Root != null && element.Document.Root.Name.ToString().Equals("gamesession", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SerializableProperty.UpgradeGameVersion(item, item.Prefab.ConfigElement, submarine.Info.GameVersion);
|
||||
//character inventories are loaded from the game session file - use the version number of the saved game session instead of the sub
|
||||
//(the sub may have already been saved and up-to-date, even though the character inventories aren't)
|
||||
savedVersion = new Version(element.Document.Root.GetAttributeString("version", "0.0.0.0"));
|
||||
}
|
||||
|
||||
if (savedVersion != null)
|
||||
{
|
||||
SerializableProperty.UpgradeGameVersion(item, item.Prefab.ConfigElement, savedVersion);
|
||||
}
|
||||
|
||||
foreach (ItemComponent component in item.components)
|
||||
@@ -3342,6 +3369,7 @@ namespace Barotrauma
|
||||
ic.ShallowRemove();
|
||||
}
|
||||
ItemList.Remove(this);
|
||||
dangerousItems.Remove(this);
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
@@ -3400,6 +3428,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
ItemList.Remove(this);
|
||||
dangerousItems.Remove(this);
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
|
||||
@@ -26,9 +26,15 @@ namespace Barotrauma
|
||||
|
||||
public readonly ImmutableArray<StartItem> Items;
|
||||
|
||||
/// <summary>
|
||||
/// The order in which the sets are displayed in menus
|
||||
/// </summary>
|
||||
public readonly int Order;
|
||||
|
||||
public StartItemSet(ContentXElement element, StartItemsFile file) : base(file, element.GetAttributeIdentifier("identifier", Identifier.Empty))
|
||||
{
|
||||
Items = element.Elements().Select(e => new StartItem(e!)).ToImmutableArray();
|
||||
Order = element.GetAttributeInt("order", 0);
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
|
||||
@@ -10,12 +10,11 @@ using System.Net;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
#warning TODO: MapEntityPrefab should be constrained further to not include item assemblies, as assemblies are effectively not entities at all
|
||||
partial class ItemAssemblyPrefab : MapEntityPrefab
|
||||
{
|
||||
public static readonly PrefabCollection<ItemAssemblyPrefab> Prefabs = new PrefabCollection<ItemAssemblyPrefab>();
|
||||
|
||||
public static readonly string VanillaSaveFolder = Path.Combine("Content", "Items", "Assemblies");
|
||||
|
||||
private readonly XElement configElement;
|
||||
|
||||
public readonly ImmutableArray<(Identifier Identifier, Rectangle Rect)> DisplayEntities;
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Barotrauma
|
||||
Cave = 0x4,
|
||||
Ruin = 0x8,
|
||||
Wreck = 0x10,
|
||||
BeaconStation = 0x20, // Not used anywhere
|
||||
BeaconStation = 0x20,
|
||||
Abyss = 0x40,
|
||||
AbyssCave = 0x80
|
||||
}
|
||||
@@ -395,6 +395,13 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static bool IsLoadedOutpost => Loaded?.Type == LevelData.LevelType.Outpost;
|
||||
|
||||
/// <summary>
|
||||
/// Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1)
|
||||
/// </summary>
|
||||
public static bool IsLoadedFriendlyOutpost =>
|
||||
loaded?.Type == LevelData.LevelType.Outpost &&
|
||||
(loaded?.StartLocation?.Type?.OutpostTeam == CharacterTeamType.FriendlyNPC || loaded?.StartLocation?.Type?.OutpostTeam == CharacterTeamType.Team1);
|
||||
|
||||
public LevelGenerationParams GenerationParams
|
||||
{
|
||||
get { return LevelData.GenerationParams; }
|
||||
@@ -421,7 +428,7 @@ namespace Barotrauma
|
||||
borders = new Rectangle(Point.Zero, levelData.Size);
|
||||
}
|
||||
|
||||
public static Level Generate(LevelData levelData, bool mirror, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
|
||||
public static Level Generate(LevelData levelData, bool mirror, Location startLocation, Location endLocation, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
|
||||
{
|
||||
Debug.Assert(levelData.Biome != null);
|
||||
if (levelData.Biome == null) { throw new ArgumentException("Biome was null"); }
|
||||
@@ -433,11 +440,11 @@ namespace Barotrauma
|
||||
preSelectedStartOutpost = startOutpost,
|
||||
preSelectedEndOutpost = endOutpost
|
||||
};
|
||||
level.Generate(mirror);
|
||||
level.Generate(mirror, startLocation, endLocation);
|
||||
return level;
|
||||
}
|
||||
|
||||
private void Generate(bool mirror)
|
||||
private void Generate(bool mirror, Location startLocation, Location endLocation)
|
||||
{
|
||||
Loaded?.Remove();
|
||||
Loaded = this;
|
||||
@@ -454,8 +461,8 @@ namespace Barotrauma
|
||||
|
||||
if (LevelData.ForceOutpostGenerationParams == null)
|
||||
{
|
||||
StartLocation = GameMain.GameSession?.StartLocation;
|
||||
EndLocation = GameMain.GameSession?.EndLocation;
|
||||
StartLocation = startLocation;
|
||||
EndLocation = endLocation;
|
||||
}
|
||||
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenStart);
|
||||
@@ -509,7 +516,7 @@ namespace Barotrauma
|
||||
Rectangle pathBorders = borders;
|
||||
pathBorders.Inflate(
|
||||
-Math.Min(Math.Min(minMainPathWidth * 2, MaxSubmarineWidth), borders.Width / 5),
|
||||
-Math.Min(minMainPathWidth, borders.Height / 5));
|
||||
-Math.Min(minMainPathWidth * 2, borders.Height / 5));
|
||||
|
||||
if (pathBorders.Width <= 0) { throw new InvalidOperationException($"The width of the level's path area is invalid ({pathBorders.Width})"); }
|
||||
if (pathBorders.Height <= 0) { throw new InvalidOperationException($"The height of the level's path area is invalid ({pathBorders.Height})"); }
|
||||
@@ -1713,7 +1720,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (abyssHeight > 30000)
|
||||
{
|
||||
//if the bottom of the abyss area is below crush depth, try to move it up to keep (most) of the abyss content above crush depth
|
||||
//but only if start of the abyss is above crush depth (no point in doing this if all of it is below crush depth)
|
||||
@@ -3527,6 +3534,8 @@ namespace Barotrauma
|
||||
}
|
||||
else if (type == SubmarineType.BeaconStation)
|
||||
{
|
||||
PositionsOfInterest.Add(new InterestingPosition(spawnPoint.ToPoint(), PositionType.BeaconStation, submarine: sub));
|
||||
|
||||
sub.ShowSonarMarker = false;
|
||||
sub.DockedTo.ForEach(s => s.ShowSonarMarker = false);
|
||||
sub.PhysicsBody.FarseerBody.BodyType = BodyType.Static;
|
||||
@@ -3940,7 +3949,7 @@ namespace Barotrauma
|
||||
//the submarine port has to be at the top of the sub
|
||||
if (port.Item.WorldPosition.Y < Submarine.MainSub.WorldPosition.Y) { continue; }
|
||||
float dist = Math.Abs(port.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X);
|
||||
if (dist < closestDistance)
|
||||
if (dist < closestDistance || subPort.MainDockingPort)
|
||||
{
|
||||
subPort = port;
|
||||
closestDistance = dist;
|
||||
@@ -4023,6 +4032,26 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("No BeaconStation files found in the selected content packages!");
|
||||
return;
|
||||
}
|
||||
|
||||
var beaconInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsBeacon);
|
||||
for (int i = beaconStationFiles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var beaconStationFile = beaconStationFiles[i];
|
||||
var matchingInfo = beaconInfos.SingleOrDefault(info => info.FilePath == beaconStationFile.Path.Value);
|
||||
Debug.Assert(matchingInfo != null);
|
||||
if (matchingInfo?.BeaconStationInfo is BeaconStationInfo beaconInfo)
|
||||
{
|
||||
if (LevelData.Difficulty < beaconInfo.MinLevelDifficulty || LevelData.Difficulty > beaconInfo.MaxLevelDifficulty)
|
||||
{
|
||||
beaconStationFiles.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (beaconStationFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"No BeaconStation files found for the level difficulty {LevelData.Difficulty}!");
|
||||
return;
|
||||
}
|
||||
var contentFile = beaconStationFiles.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
string beaconStationName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path.Value);
|
||||
|
||||
@@ -4078,24 +4107,22 @@ namespace Barotrauma
|
||||
{
|
||||
if (!(GameMain.NetworkMember?.IsClient ?? false))
|
||||
{
|
||||
//empty the reactor
|
||||
if (reactorContainer != null)
|
||||
bool allowDisconnectedWires = true;
|
||||
bool allowDamagedWalls = true;
|
||||
if (BeaconStation.Info?.BeaconStationInfo is BeaconStationInfo info)
|
||||
{
|
||||
foreach (Item item in reactorContainer.Inventory.AllItems)
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
Spawner.AddItemToRemoveQueue(item);
|
||||
}
|
||||
allowDisconnectedWires = info.AllowDisconnectedWires;
|
||||
allowDamagedWalls = info.AllowDamagedWalls;
|
||||
}
|
||||
|
||||
//remove wires
|
||||
float removeWireMinDifficulty = 20.0f;
|
||||
float removeWireProbability = MathUtils.InverseLerp(removeWireMinDifficulty, 100.0f, LevelData.Difficulty) * 0.5f;
|
||||
if (removeWireProbability > 0.0f)
|
||||
if (removeWireProbability > 0.0f && allowDisconnectedWires)
|
||||
{
|
||||
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
if (wire.Locked) { continue; }
|
||||
if (wire.Connections[0] != null && (wire.Connections[0].Item.NonInteractable || wire.Connections[0].Item.GetComponent<ConnectionPanel>().Locked))
|
||||
@@ -4115,8 +4142,8 @@ namespace Barotrauma
|
||||
connection.ConnectionPanel.DisconnectedWires.Add(wire);
|
||||
wire.RemoveConnection(connection.Item);
|
||||
#if SERVER
|
||||
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
|
||||
wire.CreateNetworkEvent();
|
||||
connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel);
|
||||
wire.CreateNetworkEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -4124,23 +4151,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
//break powered items
|
||||
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
|
||||
if (allowDamagedWalls)
|
||||
{
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
|
||||
//break powered items
|
||||
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
|
||||
{
|
||||
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
|
||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f)
|
||||
{
|
||||
item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//poke holes in the walls
|
||||
foreach (Structure structure in Structure.WallList.Where(s => s.Submarine == BeaconStation))
|
||||
{
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f)
|
||||
//poke holes in the walls
|
||||
foreach (Structure structure in Structure.WallList.Where(s => s.Submarine == BeaconStation))
|
||||
{
|
||||
int sectionIndex = Rand.Range(0, structure.SectionCount - 1, Rand.RandSync.Unsynced);
|
||||
structure.AddDamage(sectionIndex, Rand.Range(structure.MaxHealth * 0.2f, structure.MaxHealth, Rand.RandSync.Unsynced));
|
||||
if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f)
|
||||
{
|
||||
int sectionIndex = Rand.Range(0, structure.SectionCount - 1, Rand.RandSync.Unsynced);
|
||||
structure.AddDamage(sectionIndex, Rand.Range(structure.MaxHealth * 0.2f, structure.MaxHealth, Rand.RandSync.Unsynced));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly string Seed;
|
||||
|
||||
public float Difficulty;
|
||||
public readonly float Difficulty;
|
||||
|
||||
public readonly Biome Biome;
|
||||
|
||||
|
||||
@@ -432,7 +432,7 @@ namespace Barotrauma
|
||||
if (sub != null)
|
||||
{
|
||||
bool leaveBehind = false;
|
||||
if (!sub.DockedTo.Contains(Submarine.MainSub))
|
||||
if (sub.Submarine != null && !sub.DockedTo.Contains(sub.Submarine))
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Submarine.MainSub.AtEndExit || Submarine.MainSub.AtStartExit);
|
||||
if (Submarine.MainSub.AtEndExit)
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Load a previously saved campaign map from XML
|
||||
/// </summary>
|
||||
private Map(CampaignMode campaign, XElement element, CampaignSettings settings) : this(settings)
|
||||
private Map(CampaignMode campaign, XElement element) : this(campaign.Settings)
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "a");
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
@@ -104,7 +104,7 @@ namespace Barotrauma
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement)
|
||||
{
|
||||
Enabled = settings.RadiationEnabled
|
||||
Enabled = campaign.Settings.RadiationEnabled
|
||||
};
|
||||
break;
|
||||
}
|
||||
@@ -208,12 +208,12 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Generate a new campaign map from the seed
|
||||
/// </summary>
|
||||
public Map(CampaignMode campaign, string seed, CampaignSettings settings) : this(settings)
|
||||
public Map(CampaignMode campaign, string seed) : this(campaign.Settings)
|
||||
{
|
||||
Seed = seed;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
|
||||
Generate();
|
||||
Generate(campaign.Settings);
|
||||
|
||||
if (Locations.Count == 0)
|
||||
{
|
||||
@@ -228,10 +228,7 @@ namespace Barotrauma
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.Type.Identifier != "outpost") { continue; }
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
}
|
||||
SetStartLocation(location);
|
||||
}
|
||||
//if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost
|
||||
if (CurrentLocation == null)
|
||||
@@ -239,25 +236,36 @@ namespace Barotrauma
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Type.HasOutpost) { continue; }
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
}
|
||||
SetStartLocation(location);
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
|
||||
if (StartLocation?.LevelData != null)
|
||||
|
||||
void SetStartLocation(Location location)
|
||||
{
|
||||
StartLocation.LevelData.Difficulty = 0;
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
//ensure all paths from the starting location have 0 difficulty to make the 1st campaign round very easy
|
||||
foreach (var locationConnection in StartLocation.Connections)
|
||||
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
|
||||
|
||||
int loops = campaign.CampaignMetadata.GetInt("campaign.endings".ToIdentifier(), 0);
|
||||
if (loops == 0 && (campaign.Settings.Difficulty == GameDifficulty.Easy || campaign.Settings.Difficulty == GameDifficulty.Medium))
|
||||
{
|
||||
if (locationConnection.Difficulty > 0.0f)
|
||||
if (StartLocation != null)
|
||||
{
|
||||
locationConnection.Difficulty = 0.0f;
|
||||
locationConnection.LevelData = new LevelData(locationConnection);
|
||||
StartLocation.LevelData = new LevelData(StartLocation, 0);
|
||||
}
|
||||
|
||||
//ensure all paths from the starting location have 0 difficulty to make the 1st campaign round very easy
|
||||
foreach (var locationConnection in StartLocation.Connections)
|
||||
{
|
||||
if (locationConnection.Difficulty > 0.0f)
|
||||
{
|
||||
locationConnection.Difficulty = 0.0f;
|
||||
locationConnection.LevelData = new LevelData(locationConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +284,7 @@ namespace Barotrauma
|
||||
|
||||
#region Generation
|
||||
|
||||
private void Generate()
|
||||
private void Generate(CampaignSettings settings)
|
||||
{
|
||||
Connections.Clear();
|
||||
Locations.Clear();
|
||||
@@ -294,7 +302,6 @@ namespace Barotrauma
|
||||
|
||||
Voronoi voronoi = new Voronoi(0.5f);
|
||||
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(voronoiSites, Width, Height);
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
|
||||
Vector2 margin = new Vector2(
|
||||
Math.Min(10, Width * 0.1f),
|
||||
@@ -310,6 +317,7 @@ namespace Barotrauma
|
||||
|
||||
voronoiSites.Clear();
|
||||
Dictionary<int, List<Location>> locationsPerZone = new Dictionary<int, List<Location>>();
|
||||
bool possibleStartOutpostCreated = false;
|
||||
foreach (GraphEdge edge in edges)
|
||||
{
|
||||
if (edge.Point1 == edge.Point2) { continue; }
|
||||
@@ -344,12 +352,26 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
LocationType forceLocationType = null;
|
||||
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
|
||||
if (!possibleStartOutpostCreated)
|
||||
{
|
||||
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
float threshold = zoneWidth * 0.1f;
|
||||
if (position.X < threshold)
|
||||
{
|
||||
forceLocationType = locationType;
|
||||
break;
|
||||
LocationType.Prefabs.TryGet("outpost", out forceLocationType);
|
||||
possibleStartOutpostCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (forceLocationType == null)
|
||||
{
|
||||
foreach (LocationType locationType in LocationType.Prefabs.OrderBy(lt => lt.Identifier))
|
||||
{
|
||||
if (locationType.MinCountPerZone.TryGetValue(zone, out int minCount) && locationsPerZone[zone].Count(l => l.Type == locationType) < minCount)
|
||||
{
|
||||
forceLocationType = locationType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,9 +477,7 @@ namespace Barotrauma
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 > zone2)
|
||||
{
|
||||
int temp = zone2;
|
||||
zone2 = zone1;
|
||||
zone1 = temp;
|
||||
(zone1, zone2) = (zone2, zone1);
|
||||
}
|
||||
|
||||
if (generationParams.GateCount[zone1] == 0) { continue; }
|
||||
@@ -527,32 +547,43 @@ namespace Barotrauma
|
||||
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
float difficulty = connection.CenterPos.X / Width * 100;
|
||||
float minDifficulty = 0;
|
||||
float maxDifficulty = 100;
|
||||
var biome = connection.Biome;
|
||||
if (biome != null)
|
||||
if (connection.Locations.Any(l => l.IsGateBetweenBiomes))
|
||||
{
|
||||
minDifficulty = connection.Biome.MinDifficulty;
|
||||
maxDifficulty = connection.Biome.MaxDifficulty;
|
||||
if (connection.Locked)
|
||||
{
|
||||
connection.Difficulty = maxDifficulty;
|
||||
}
|
||||
connection.Difficulty = connection.Locations.Min(l => l.Biome.MaxDifficulty);
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.Difficulty = CalculateDifficulty(connection.CenterPos.X, connection.Biome);
|
||||
}
|
||||
connection.Difficulty = MathHelper.Clamp(difficulty, minDifficulty, maxDifficulty);
|
||||
}
|
||||
|
||||
CreateEndLocation();
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location, MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f));
|
||||
location.LevelData = new LevelData(location, CalculateDifficulty(location.MapPosition.X, location.Biome));
|
||||
}
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.LevelData = new LevelData(connection);
|
||||
}
|
||||
|
||||
float CalculateDifficulty(float mapPosition, Biome biome)
|
||||
{
|
||||
float settingsFactor = settings.LevelDifficultyMultiplier;
|
||||
float minDifficulty = 0;
|
||||
float maxDifficulty = 100;
|
||||
float difficulty = mapPosition / Width * 100;
|
||||
System.Diagnostics.Debug.Assert(biome != null);
|
||||
if (biome != null)
|
||||
{
|
||||
minDifficulty = biome.MinDifficulty;
|
||||
maxDifficulty = biome.MaxDifficulty;
|
||||
float diff = 1 - settingsFactor;
|
||||
difficulty *= 1 - (1f / biome.AllowedZones.Max() * diff);
|
||||
}
|
||||
return MathHelper.Clamp(difficulty, minDifficulty, maxDifficulty);
|
||||
}
|
||||
}
|
||||
|
||||
partial void GenerateLocationConnectionVisuals();
|
||||
@@ -633,6 +664,11 @@ namespace Barotrauma
|
||||
|
||||
if (EndLocation == null || previousToEndLocation == null) { return; }
|
||||
|
||||
if (LocationType.Prefabs.TryGet("none", out LocationType locationType))
|
||||
{
|
||||
previousToEndLocation.ChangeType(locationType);
|
||||
}
|
||||
|
||||
//remove all locations from the end biome except the end location
|
||||
for (int i = Locations.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -652,7 +688,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//removed all connections from the second-to-last location, need to reconnect it
|
||||
if (!previousToEndLocation.Connections.Any())
|
||||
if (previousToEndLocation.Connections.None())
|
||||
{
|
||||
Location connectTo = Locations.First();
|
||||
foreach (Location location in Locations)
|
||||
@@ -759,6 +795,7 @@ namespace Barotrauma
|
||||
CurrentLocation = Locations[index];
|
||||
CurrentLocation.Discover();
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
if (prevLocation != CurrentLocation)
|
||||
{
|
||||
var connection = CurrentLocation.Connections.Find(c => c.Locations.Contains(prevLocation));
|
||||
@@ -766,10 +803,8 @@ namespace Barotrauma
|
||||
{
|
||||
connection.Passed = true;
|
||||
}
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
}
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
}
|
||||
|
||||
public void SelectLocation(int index)
|
||||
@@ -789,6 +824,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
Location prevSelected = SelectedLocation;
|
||||
SelectedLocation = Locations[index];
|
||||
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
|
||||
SelectedConnection =
|
||||
@@ -798,7 +834,10 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
if (prevSelected != SelectedLocation)
|
||||
{
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectLocation(Location location)
|
||||
@@ -811,13 +850,17 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
Location prevSelected = SelectedLocation;
|
||||
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);
|
||||
if (prevSelected != SelectedLocation)
|
||||
{
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectMission(IEnumerable<int> missionIndices)
|
||||
@@ -830,23 +873,24 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentLocation.SetSelectedMissionIndices(missionIndices);
|
||||
|
||||
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
|
||||
if (!missionIndices.SequenceEqual(GetSelectedMissionIndices()))
|
||||
{
|
||||
if (selectedMission.Locations[0] != CurrentLocation ||
|
||||
selectedMission.Locations[1] != CurrentLocation)
|
||||
CurrentLocation.SetSelectedMissionIndices(missionIndices);
|
||||
foreach (Mission selectedMission in CurrentLocation.SelectedMissions.ToList())
|
||||
{
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (selectedMission.Locations[1] != SelectedLocation)
|
||||
if (selectedMission.Locations[0] != CurrentLocation ||
|
||||
selectedMission.Locations[1] != CurrentLocation)
|
||||
{
|
||||
CurrentLocation.DeselectMission(selectedMission);
|
||||
if (SelectedConnection == null) { return; }
|
||||
//the destination must be the same as the destination of the mission
|
||||
if (selectedMission.Locations[1] != SelectedLocation)
|
||||
{
|
||||
CurrentLocation.DeselectMission(selectedMission);
|
||||
}
|
||||
}
|
||||
}
|
||||
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
|
||||
}
|
||||
|
||||
OnMissionsSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMissions);
|
||||
}
|
||||
|
||||
public void SelectRandomLocation(bool preferUndiscovered)
|
||||
@@ -1070,9 +1114,9 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
public static Map Load(CampaignMode campaign, XElement element, CampaignSettings settings)
|
||||
public static Map Load(CampaignMode campaign, XElement element)
|
||||
{
|
||||
Map map = new Map(campaign, element, settings);
|
||||
Map map = new Map(campaign, element);
|
||||
map.LoadState(element, false);
|
||||
#if CLIENT
|
||||
map.DrawOffset = -map.CurrentLocation.MapPosition;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BeaconStationInfo : ISerializableEntity
|
||||
{
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDamagedWalls { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes), Editable]
|
||||
public bool AllowDisconnectedWires { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float MinLevelDifficulty { get; set; }
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), Editable]
|
||||
public float MaxLevelDifficulty { get; set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
public BeaconStationInfo(SubmarineInfo submarineInfo, XElement element)
|
||||
{
|
||||
Name = $"BeaconStationInfo ({submarineInfo.Name})";
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public BeaconStationInfo(SubmarineInfo submarineInfo)
|
||||
{
|
||||
Name = $"BeaconStationInfo ({submarineInfo.Name})";
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this);
|
||||
}
|
||||
|
||||
public BeaconStationInfo(BeaconStationInfo original)
|
||||
{
|
||||
Name = original.Name;
|
||||
SerializableProperties = new Dictionary<Identifier, SerializableProperty>();
|
||||
foreach (KeyValuePair<Identifier, SerializableProperty> kvp in original.SerializableProperties)
|
||||
{
|
||||
SerializableProperties.Add(kvp.Key, kvp.Value);
|
||||
if (SerializableProperty.GetSupportedTypeName(kvp.Value.PropertyType) != null)
|
||||
{
|
||||
kvp.Value.TrySetValue(this, kvp.Value.GetValue(original));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(XElement element)
|
||||
{
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -830,43 +830,44 @@ namespace Barotrauma
|
||||
|
||||
private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType, bool allowDifferentLocationType)
|
||||
{
|
||||
IEnumerable<SubmarineInfo> availableModules = null;
|
||||
IEnumerable<SubmarineInfo> modulesWithCorrectFlags = null;
|
||||
if (moduleFlag.IsEmpty || moduleFlag.Equals("none"))
|
||||
{
|
||||
availableModules = modules
|
||||
modulesWithCorrectFlags = modules
|
||||
.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains("none".ToIdentifier())));
|
||||
}
|
||||
else
|
||||
{
|
||||
availableModules = modules
|
||||
modulesWithCorrectFlags = modules
|
||||
.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
|
||||
}
|
||||
modulesWithCorrectFlags = modulesWithCorrectFlags.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
|
||||
|
||||
availableModules = availableModules.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
|
||||
|
||||
if (prevModule != null)
|
||||
var suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: true, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: true);
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
availableModules = availableModules.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));// && CanAttachTo(prevModule, m.OutpostModuleInfo));
|
||||
//no suitable module found, see if we can find a "generic" module that's not meant for any specific type of outpost
|
||||
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: true, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: false);
|
||||
//still not found, see if we can find something that's otherwise suitable but not meant to attach to the previous module
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: false, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: true);
|
||||
}
|
||||
//still not found! Try if we can find a generic module that's not meant to attach to the previous module
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
suitableModules = GetSuitable(modulesWithCorrectFlags, requireAllowAttachToPrevious: false, requireCorrectLocationType: true, disallowNonLocationTypeSpecific: false);
|
||||
}
|
||||
}
|
||||
|
||||
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.Contains(locationType.Identifier));
|
||||
|
||||
//if not found, search for modules suitable for any location type
|
||||
if (allowDifferentLocationType && !modulesSuitableForLocationType.Any())
|
||||
{
|
||||
modulesSuitableForLocationType = availableModules.Where(m => !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
|
||||
if (!modulesSuitableForLocationType.Any())
|
||||
if (!suitableModules.Any())
|
||||
{
|
||||
if (allowDifferentLocationType)
|
||||
{
|
||||
if (modulesWithCorrectFlags.Any())
|
||||
|
||||
DebugConsole.NewMessage($"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
|
||||
return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
return ToolBox.SelectWeightedRandom(modulesWithCorrectFlags.ToList(), modulesWithCorrectFlags.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -875,7 +876,28 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
return ToolBox.SelectWeightedRandom(suitableModules.ToList(), suitableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
IEnumerable<SubmarineInfo> GetSuitable(IEnumerable<SubmarineInfo> modules, bool requireAllowAttachToPrevious, bool requireCorrectLocationType, bool disallowNonLocationTypeSpecific)
|
||||
{
|
||||
IEnumerable<SubmarineInfo> suitable = modules;
|
||||
if (requireCorrectLocationType)
|
||||
{
|
||||
if (disallowNonLocationTypeSpecific)
|
||||
{
|
||||
suitable = modules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
|
||||
}
|
||||
else
|
||||
{
|
||||
suitable = modules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier) || !m.OutpostModuleInfo.AllowedLocationTypes.Any());
|
||||
}
|
||||
}
|
||||
if (requireAllowAttachToPrevious && prevModule != null)
|
||||
{
|
||||
suitable = suitable.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));
|
||||
}
|
||||
return suitable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1590,10 +1612,6 @@ namespace Barotrauma
|
||||
{
|
||||
npc.CharacterHealth.Unkillable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
npc.AddStaticHealthMultiplier(humanPrefab.HealthMultiplier);
|
||||
}
|
||||
humanPrefab.GiveItems(npc, outpost, Rand.RandSync.ServerAndClient);
|
||||
foreach (Item item in npc.Inventory.FindAllItems(it => it != null, recursive: true))
|
||||
{
|
||||
|
||||
@@ -1483,8 +1483,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.Submarine != this) continue;
|
||||
if (item.ParentInventory != null || item.body != null) continue;
|
||||
var lightComponent = item.GetComponent<Items.Components.LightComponent>();
|
||||
if (lightComponent != null) lightComponent.LightColor = new Color(lightComponent.LightColor, lightComponent.LightColor.A / 255.0f * 0.5f);
|
||||
foreach (var light in item.GetComponents<LightComponent>())
|
||||
{
|
||||
light.LightColor = new Color(light.LightColor, light.LightColor.A / 255.0f * 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
GenerateOutdoorNodes();
|
||||
@@ -1555,7 +1557,7 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("cargocapacity", cargoCapacity));
|
||||
element.Add(new XAttribute("recommendedcrewsizemin", Info.RecommendedCrewSizeMin));
|
||||
element.Add(new XAttribute("recommendedcrewsizemax", Info.RecommendedCrewSizeMax));
|
||||
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience ?? ""));
|
||||
element.Add(new XAttribute("recommendedcrewexperience", Info.RecommendedCrewExperience.ToString()));
|
||||
element.Add(new XAttribute("requiredcontentpackages", string.Join(", ", Info.RequiredContentPackages)));
|
||||
|
||||
if (Info.Type == SubmarineType.OutpostModule)
|
||||
@@ -1632,6 +1634,7 @@ namespace Barotrauma
|
||||
Type = Info.Type,
|
||||
FilePath = filePath,
|
||||
OutpostModuleInfo = Info.OutpostModuleInfo != null ? new OutpostModuleInfo(Info.OutpostModuleInfo) : null,
|
||||
BeaconStationInfo = Info.BeaconStationInfo != null ? new BeaconStationInfo(Info.BeaconStationInfo) : null,
|
||||
Name = Path.GetFileNameWithoutExtension(filePath)
|
||||
};
|
||||
#if CLIENT
|
||||
|
||||
@@ -39,7 +39,15 @@ namespace Barotrauma
|
||||
public SubmarineTag Tags { get; private set; }
|
||||
|
||||
public int RecommendedCrewSizeMin = 1, RecommendedCrewSizeMax = 2;
|
||||
public string RecommendedCrewExperience;
|
||||
|
||||
public enum CrewExperienceLevel
|
||||
{
|
||||
Unknown,
|
||||
CrewExperienceLow,
|
||||
CrewExperienceMid,
|
||||
CrewExperienceHigh
|
||||
}
|
||||
public CrewExperienceLevel RecommendedCrewExperience;
|
||||
|
||||
/// <summary>
|
||||
/// A random int that gets assigned when saving the sub. Used in mp campaign to verify that sub files match
|
||||
@@ -89,6 +97,7 @@ namespace Barotrauma
|
||||
public SubmarineClass SubmarineClass;
|
||||
|
||||
public OutpostModuleInfo OutpostModuleInfo { get; set; }
|
||||
public BeaconStationInfo BeaconStationInfo { get; set; }
|
||||
|
||||
public bool IsOutpost => Type == SubmarineType.Outpost || Type == SubmarineType.OutpostModule;
|
||||
|
||||
@@ -280,6 +289,10 @@ namespace Barotrauma
|
||||
{
|
||||
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
|
||||
}
|
||||
if (original.BeaconStationInfo != null)
|
||||
{
|
||||
BeaconStationInfo = new BeaconStationInfo(original.BeaconStationInfo);
|
||||
}
|
||||
#if CLIENT
|
||||
PreviewImage = original.PreviewImage != null ? new Sprite(original.PreviewImage) : null;
|
||||
#endif
|
||||
@@ -330,7 +343,24 @@ namespace Barotrauma
|
||||
CargoCapacity = SubmarineElement.GetAttributeInt("cargocapacity", -1);
|
||||
RecommendedCrewSizeMin = SubmarineElement.GetAttributeInt("recommendedcrewsizemin", 0);
|
||||
RecommendedCrewSizeMax = SubmarineElement.GetAttributeInt("recommendedcrewsizemax", 0);
|
||||
RecommendedCrewExperience = SubmarineElement.GetAttributeString("recommendedcrewexperience", "Unknown");
|
||||
var recommendedCrewExperience = SubmarineElement.GetAttributeIdentifier("recommendedcrewexperience", CrewExperienceLevel.Unknown.ToIdentifier());
|
||||
// Backwards compatibility
|
||||
if (recommendedCrewExperience == "Beginner")
|
||||
{
|
||||
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceLow;
|
||||
}
|
||||
else if (recommendedCrewExperience == "Intermediate")
|
||||
{
|
||||
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceMid;
|
||||
}
|
||||
else if (recommendedCrewExperience == "Experienced")
|
||||
{
|
||||
RecommendedCrewExperience = CrewExperienceLevel.CrewExperienceHigh;
|
||||
}
|
||||
else
|
||||
{
|
||||
Enum.TryParse(recommendedCrewExperience.Value, ignoreCase: true, out RecommendedCrewExperience);
|
||||
}
|
||||
|
||||
if (SubmarineElement?.Attribute("type") != null)
|
||||
{
|
||||
@@ -341,6 +371,10 @@ namespace Barotrauma
|
||||
{
|
||||
OutpostModuleInfo = new OutpostModuleInfo(this, SubmarineElement);
|
||||
}
|
||||
else if (Type == SubmarineType.BeaconStation)
|
||||
{
|
||||
BeaconStationInfo = new BeaconStationInfo(this, SubmarineElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,20 +393,6 @@ namespace Barotrauma
|
||||
SubmarineClass = SubmarineClass.Undefined;
|
||||
}
|
||||
|
||||
//backwards compatibility (use text tags instead of the actual text)
|
||||
if (RecommendedCrewExperience == "Beginner")
|
||||
{
|
||||
RecommendedCrewExperience = "CrewExperienceLow";
|
||||
}
|
||||
else if (RecommendedCrewExperience == "Intermediate")
|
||||
{
|
||||
RecommendedCrewExperience = "CrewExperienceMid";
|
||||
}
|
||||
else if (RecommendedCrewExperience == "Experienced")
|
||||
{
|
||||
RecommendedCrewExperience = "CrewExperienceHigh";
|
||||
}
|
||||
|
||||
RequiredContentPackages.Clear();
|
||||
string[] contentPackageNames = SubmarineElement.GetAttributeStringArray("requiredcontentpackages", Array.Empty<string>());
|
||||
foreach (string contentPackageName in contentPackageNames)
|
||||
@@ -528,6 +548,11 @@ namespace Barotrauma
|
||||
OutpostModuleInfo.Save(newElement);
|
||||
OutpostModuleInfo = new OutpostModuleInfo(this, newElement);
|
||||
}
|
||||
else if (Type == SubmarineType.BeaconStation)
|
||||
{
|
||||
BeaconStationInfo.Save(newElement);
|
||||
BeaconStationInfo = new BeaconStationInfo(this, newElement);
|
||||
}
|
||||
XDocument doc = new XDocument(newElement);
|
||||
|
||||
doc.Root.Add(new XAttribute("name", Name));
|
||||
@@ -590,6 +615,7 @@ namespace Barotrauma
|
||||
List<string> filePaths = new List<string>();
|
||||
foreach (BaseSubFile subFile in contentPackageSubs)
|
||||
{
|
||||
if (!File.Exists(subFile.Path.Value)) { continue; }
|
||||
if (!filePaths.Any(fp => fp == subFile.Path))
|
||||
{
|
||||
filePaths.Add(subFile.Path.Value);
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
private static void UpdateRead()
|
||||
{
|
||||
Span<byte> msgLengthSpan = stackalloc byte[3];
|
||||
Span<byte> msgLengthSpan = stackalloc byte[4 + 1];
|
||||
while (!shutDown)
|
||||
{
|
||||
CheckPipeConnected(nameof(readStream), readStream);
|
||||
@@ -182,8 +182,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (!readBytes(msgLengthSpan)) { shutDown = true; break; }
|
||||
|
||||
int msgLength = msgLengthSpan[0] | (msgLengthSpan[1] << 8);
|
||||
WriteStatus writeStatus = (WriteStatus)msgLengthSpan[2];
|
||||
int msgLength = msgLengthSpan[0]
|
||||
| (msgLengthSpan[1] << 8)
|
||||
| (msgLengthSpan[2] << 16)
|
||||
| (msgLengthSpan[3] << 24);
|
||||
WriteStatus writeStatus = (WriteStatus)msgLengthSpan[4];
|
||||
|
||||
if (msgLength > 0)
|
||||
{
|
||||
@@ -225,12 +228,15 @@ namespace Barotrauma.Networking
|
||||
// when the function returns; placing it in the loop
|
||||
// this method is based around would lead to a stack
|
||||
// overflow real quick!
|
||||
Span<byte> bytesToWrite = stackalloc byte[3 + msg.Length];
|
||||
Span<byte> bytesToWrite = stackalloc byte[4 + 1 + msg.Length];
|
||||
|
||||
bytesToWrite[0] = (byte)(msg.Length & 0xFF);
|
||||
bytesToWrite[1] = (byte)((msg.Length >> 8) & 0xFF);
|
||||
bytesToWrite[2] = (byte)writeStatus;
|
||||
Span<byte> msgSlice = bytesToWrite.Slice(3, msg.Length);
|
||||
bytesToWrite[2] = (byte)((msg.Length >> 16) & 0xFF);
|
||||
bytesToWrite[3] = (byte)((msg.Length >> 24) & 0xFF);
|
||||
|
||||
bytesToWrite[4] = (byte)writeStatus;
|
||||
Span<byte> msgSlice = bytesToWrite.Slice(4 + 1, msg.Length);
|
||||
|
||||
msg.AsSpan().CopyTo(msgSlice);
|
||||
|
||||
@@ -284,6 +290,12 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (shutDown) { return; }
|
||||
|
||||
if (msg.Length > 0x1fff_ffff)
|
||||
{
|
||||
//This message is extremely long and is close to breaking
|
||||
//ChildServerRelay, so let's not allow this to go through!
|
||||
return;
|
||||
}
|
||||
msgsToWrite.Enqueue(msg);
|
||||
writeManualResetEvent.Set();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization
|
||||
|
||||
UPDATE_CHARACTERINFO,
|
||||
|
||||
ERROR, //tell the server that an error occurred
|
||||
CREW, //hiring UI
|
||||
MEDICAL, //medical clinic
|
||||
|
||||
@@ -900,27 +900,13 @@ namespace Barotrauma.Networking
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool RadiationEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(LootedMoneyDestination.Bank, IsPropertySaveable.Yes)]
|
||||
public LootedMoneyDestination LootedMoneyDestination { get; set; }
|
||||
|
||||
[Serialize(999999, IsPropertySaveable.Yes)]
|
||||
public int MaximumMoneyTransferRequest { get; set; }
|
||||
|
||||
private int maxMissionCount = CampaignSettings.DefaultMaxMissionCount;
|
||||
|
||||
[Serialize(CampaignSettings.DefaultMaxMissionCount, IsPropertySaveable.Yes)]
|
||||
public int MaxMissionCount
|
||||
{
|
||||
get { return maxMissionCount; }
|
||||
set { maxMissionCount = MathHelper.Clamp(value, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); }
|
||||
}
|
||||
public CampaignSettings CampaignSettings { get; set; } = CampaignSettings.Empty;
|
||||
|
||||
private bool allowSubVoting;
|
||||
//Don't serialize: the value is set based on SubSelectionMode
|
||||
|
||||
@@ -786,6 +786,9 @@ namespace Barotrauma
|
||||
case nameof(Character.HealthMultiplier):
|
||||
{ if (parentObject is Character character) { character.StackHealthMultiplier(value); return true; } }
|
||||
break;
|
||||
case nameof(Character.PropulsionSpeedMultiplier):
|
||||
{ if (parentObject is Character character) { character.PropulsionSpeedMultiplier = value; return true; } }
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -799,6 +802,12 @@ namespace Barotrauma
|
||||
case nameof(Character.ObstructVision):
|
||||
{ if (parentObject is Character character) { character.ObstructVision = value; return true; } }
|
||||
break;
|
||||
case nameof(Character.HideFace):
|
||||
{ if (parentObject is Character character) { character.HideFace = value; return true; } }
|
||||
break;
|
||||
case nameof(Character.UseHullOxygen):
|
||||
{ if (parentObject is Character character) { character.UseHullOxygen = value; return true; } }
|
||||
break;
|
||||
case nameof(LightComponent.IsOn):
|
||||
{ if (parentObject is LightComponent lightComponent) { lightComponent.IsOn = value; return true; } }
|
||||
break;
|
||||
|
||||
@@ -490,6 +490,10 @@ namespace Barotrauma
|
||||
{
|
||||
font.Prefabs.ForEach(p => p.LoadFont());
|
||||
}
|
||||
foreach (var componentStyle in GUIStyle.ComponentStyles)
|
||||
{
|
||||
componentStyle.RefreshSize();
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.SoundManager?.ApplySettings();
|
||||
|
||||
@@ -94,7 +94,13 @@ namespace Barotrauma
|
||||
|
||||
public override void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition = null)
|
||||
{
|
||||
if (this.type != type || !HasRequiredItems(entity)) { return; }
|
||||
if (this.type != type) { return; }
|
||||
if (intervalTimer > 0.0f)
|
||||
{
|
||||
intervalTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (!HasRequiredItems(entity)) { return; }
|
||||
if (delayType == DelayTypes.ReachCursor && Character.Controlled == null) { return; }
|
||||
if (!Stackable)
|
||||
{
|
||||
|
||||
@@ -132,7 +132,11 @@ namespace Barotrauma
|
||||
public enum SpawnPositionType
|
||||
{
|
||||
This,
|
||||
//the inventory of the StatusEffect's target entity
|
||||
ThisInventory,
|
||||
//the same inventory the StatusEffect's target entity is in (only valid if the target is an Item)
|
||||
SameInventory,
|
||||
//the inventory of an item in the inventory of the StatusEffect's target entity (e.g. a container in the character's inventory)
|
||||
ContainedInventory
|
||||
}
|
||||
|
||||
@@ -308,11 +312,26 @@ namespace Barotrauma
|
||||
private readonly float lifeTime;
|
||||
private float lifeTimer;
|
||||
|
||||
public float intervalTimer;
|
||||
|
||||
public static readonly List<DurationListElement> DurationList = new List<DurationListElement>();
|
||||
|
||||
public readonly bool CheckConditionalAlways; //Always do the conditional checks for the duration/delay. If false, only check conditional on apply.
|
||||
/// <summary>
|
||||
/// Always do the conditional checks for the duration/delay. If false, only check conditional on apply.
|
||||
/// </summary>
|
||||
public readonly bool CheckConditionalAlways;
|
||||
|
||||
public readonly bool Stackable = true; //Can the same status effect be applied several times to the same targets?
|
||||
/// <summary>
|
||||
/// Only valid if the effect has a duration or delay. Can the effect be applied on the same target(s)s if the effect is already being applied?
|
||||
/// </summary>
|
||||
public readonly bool Stackable = true;
|
||||
|
||||
/// <summary>
|
||||
/// The interval at which the effect is executed. The difference between delay and interval is that effects with a delay find the targets, check the conditions, etc
|
||||
/// immediately when Apply is called, but don't apply the effects until the delay has passed. Effects with an interval check if the interval has passed when Apply is
|
||||
/// called and apply the effects if it has, otherwise they do nothing.
|
||||
/// </summary>
|
||||
public readonly float Interval;
|
||||
|
||||
#if CLIENT
|
||||
private readonly bool playSoundOnRequiredItemFailure = false;
|
||||
@@ -450,6 +469,8 @@ namespace Barotrauma
|
||||
|
||||
TargetSlot = element.GetAttributeInt("targetslot", -1);
|
||||
|
||||
Interval = element.GetAttributeFloat("interval", 0.0f);
|
||||
|
||||
Range = element.GetAttributeFloat("range", 0.0f);
|
||||
Offset = element.GetAttributeVector2("offset", Vector2.Zero);
|
||||
string[] targetLimbNames = element.GetAttributeStringArray("targetlimb", null) ?? element.GetAttributeStringArray("targetlimbs", null);
|
||||
@@ -556,6 +577,7 @@ namespace Barotrauma
|
||||
" - sounds should be defined as child elements of the StatusEffect, not as attributes.");
|
||||
break;
|
||||
case "delay":
|
||||
case "interval":
|
||||
break;
|
||||
case "range":
|
||||
if (!HasTargetType(TargetType.NearbyCharacters) && !HasTargetType(TargetType.NearbyItems))
|
||||
@@ -1094,6 +1116,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (this.type != type) { return; }
|
||||
|
||||
if (intervalTimer > 0.0f)
|
||||
{
|
||||
intervalTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
currentTargets.Clear();
|
||||
foreach (ISerializableEntity target in targets)
|
||||
{
|
||||
@@ -1195,7 +1223,11 @@ namespace Barotrauma
|
||||
lifeTimer -= deltaTime;
|
||||
if (lifeTimer <= 0) { return; }
|
||||
}
|
||||
|
||||
if (intervalTimer > 0.0f)
|
||||
{
|
||||
intervalTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
Hull hull = GetHull(entity);
|
||||
Vector2 position = GetPosition(entity, targets, worldPosition);
|
||||
if (useItemCount > 0)
|
||||
@@ -1717,6 +1749,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnPositionType.SameInventory:
|
||||
{
|
||||
Inventory inventory = null;
|
||||
if (entity is Character character)
|
||||
{
|
||||
inventory = character.Inventory;
|
||||
}
|
||||
else if (entity is Item item)
|
||||
{
|
||||
inventory = item.ParentInventory;
|
||||
}
|
||||
if (inventory != null)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: (Item newItem) =>
|
||||
{
|
||||
newItem.Condition = newItem.MaxCondition * chosenItemSpawnInfo.Condition;
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ItemSpawnInfo.SpawnPositionType.ContainedInventory:
|
||||
{
|
||||
Inventory thisInventory = null;
|
||||
@@ -1756,6 +1808,8 @@ namespace Barotrauma
|
||||
|
||||
ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true);
|
||||
|
||||
intervalTimer = Interval;
|
||||
|
||||
static Character CharacterFromTarget(ISerializableEntity target)
|
||||
{
|
||||
Character targetCharacter = target as Character;
|
||||
|
||||
@@ -414,7 +414,7 @@ namespace Barotrauma.Steam
|
||||
{
|
||||
await Task.Yield();
|
||||
Identifier extension = Path.GetExtension(from).ToIdentifier();
|
||||
if (extension == ".xml" && shouldCorrectPaths == ShouldCorrectPaths.Yes)
|
||||
if (extension == ".xml")
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -427,10 +427,14 @@ namespace Barotrauma.Steam
|
||||
{
|
||||
throw new Exception($"Could not load \"{from}\": doc is null");
|
||||
}
|
||||
await CorrectPaths(
|
||||
fileListDir: fileListDir,
|
||||
modName: modName,
|
||||
element: doc.Root ?? throw new NullReferenceException());
|
||||
|
||||
if (shouldCorrectPaths == ShouldCorrectPaths.Yes)
|
||||
{
|
||||
await CorrectPaths(
|
||||
fileListDir: fileListDir,
|
||||
modName: modName,
|
||||
element: doc.Root ?? throw new NullReferenceException());
|
||||
}
|
||||
doc.SaveSafe(to);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,21 @@ namespace Barotrauma
|
||||
public bool IsNone() => this is None<T>;
|
||||
public bool IsSome() => this is Some<T>;
|
||||
|
||||
public bool TryUnwrap(out T outValue)
|
||||
{
|
||||
switch (this)
|
||||
{
|
||||
case Some<T> { Value: var value }:
|
||||
outValue = value;
|
||||
return true;
|
||||
case None<T> _:
|
||||
outValue = default;
|
||||
return false;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public Option<TType> Select<TType>(Func<T, TType> selector) =>
|
||||
this switch
|
||||
{
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Barotrauma
|
||||
public static Success<T, TError> Success(T value)
|
||||
=> new Success<T, TError>(value);
|
||||
|
||||
public static Failure<T, TError> Failure(TError error, string? stackTrace)
|
||||
=> new Failure<T, TError>(error, stackTrace);
|
||||
public static Failure<T, TError> Failure(TError error)
|
||||
=> new Failure<T, TError>(error);
|
||||
}
|
||||
|
||||
public sealed class Success<T, TError> : Result<T, TError>
|
||||
@@ -34,14 +34,11 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly TError Error;
|
||||
|
||||
public readonly string? StackTrace;
|
||||
|
||||
public override bool IsSuccess => false;
|
||||
|
||||
public Failure(TError error, string? stackTrace)
|
||||
public Failure(TError error)
|
||||
{
|
||||
Error = error;
|
||||
StackTrace = stackTrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,23 @@ namespace Barotrauma.IO
|
||||
".bat", ".sh", //shell scripts
|
||||
}.ToIdentifiers().ToImmutableArray();
|
||||
|
||||
public ref struct Skipper
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
SkipValidationInDebugBuilds = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips validation for as long as the returned object remains in scope (remember to use using)
|
||||
/// </summary>
|
||||
public static Skipper SkipInDebugBuilds()
|
||||
{
|
||||
SkipValidationInDebugBuilds = true;
|
||||
return new Skipper();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When set to true, the game is allowed to modify the vanilla content in debug builds. Has no effect in non-debug builds.
|
||||
/// </summary>
|
||||
|
||||
@@ -126,6 +126,10 @@ namespace Barotrauma
|
||||
|
||||
public static void LoadGame(string filePath)
|
||||
{
|
||||
//ensure there's no gamesession/sub loaded because it'd lead to issues when starting a new one (e.g. trying to determine which level to load based on the placement of the sub)
|
||||
//can happen if a gamesession is interrupted ungracefully (exception during loading)
|
||||
Submarine.Unload();
|
||||
GameMain.GameSession = null;
|
||||
DebugConsole.Log("Loading save file: " + filePath);
|
||||
DecompressToDirectory(filePath, TempPath, null);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user