Unstable v0.19.3.0

This commit is contained in:
Juan Pablo Arce
2022-09-02 15:10:56 -03:00
parent 28789616bd
commit 3f2c843247
336 changed files with 7152 additions and 7739 deletions
@@ -3847,7 +3847,7 @@ namespace Barotrauma
public override void ServerWrite(IWriteMessage msg)
{
msg.Write((byte)State);
msg.WriteByte((byte)State);
PetBehavior?.ServerWrite(msg);
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using System.Collections.Immutable;
namespace Barotrauma
{
@@ -529,6 +530,15 @@ namespace Barotrauma
}
return canEquip;
}
protected bool CheckItemIdentifiersOrTags(Item item, ImmutableHashSet<Identifier> identifiersOrTags)
{
if (identifiersOrTags.Contains(item.Prefab.Identifier)) { return true; }
foreach (var identifier in identifiersOrTags)
{
if (item.HasTag(identifier)) { return true; }
}
return false;
}
protected bool CanEquip(Item item) => CanEquip(character, item);
}
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using FarseerPhysics.Dynamics;
using static Barotrauma.AIObjectiveFindSafety;
using System.Collections.Immutable;
namespace Barotrauma
{
@@ -967,7 +968,7 @@ namespace Barotrauma
/// <summary>
/// Seeks for more ammunition. Creates a new subobjective.
/// </summary>
private void SeekAmmunition(Identifier[] ammunitionIdentifiers)
private void SeekAmmunition(ImmutableHashSet<Identifier> ammunitionIdentifiers)
{
retreatTarget = null;
RemoveSubObjective(ref retreatObjective);
@@ -1002,7 +1003,7 @@ namespace Barotrauma
HumanAIController.UnequipEmptyItems(Weapon);
RelatedItem item = null;
Item ammunition = null;
Identifier[] ammunitionIdentifiers = null;
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
{
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
@@ -1028,8 +1029,8 @@ namespace Barotrauma
if (ammunitionIdentifiers != null)
{
// Try reload ammunition from inventory
bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
ammunition = character.Inventory.FindItem(i => ammunitionIdentifiers.Any(id => id == i.Prefab.Identifier || i.HasTag(id)) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
ammunition = character.Inventory.FindItem(i => CheckItemIdentifiersOrTags(i, ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
if (ammunition != null)
{
var container = Weapon.GetComponent<ItemContainer>();
@@ -1,6 +1,8 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
@@ -11,14 +13,14 @@ namespace Barotrauma
public Func<Item, float> GetItemPriority;
public Identifier[] ignoredContainerIdentifiers;
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
public bool checkInventory = true;
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs and in some cases also enemy NPCs, like pirates)
private readonly bool spawnItemIfNotFound;
//can either be a tag or an identifier
public readonly Identifier[] itemIdentifiers;
public readonly ImmutableHashSet<Identifier> itemIdentifiers;
public readonly ItemContainer container;
private readonly Item item;
public Item ItemToContain { get; private set; }
@@ -61,9 +63,9 @@ namespace Barotrauma
}
public AIObjectiveContainItem(Character character, Identifier itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: this(character, new Identifier[] { itemIdentifier }, container, objectiveManager, priorityModifier, spawnItemIfNotFound) { }
: this(character, itemIdentifier.ToEnumerable().ToImmutableHashSet(), container, objectiveManager, priorityModifier, spawnItemIfNotFound) { }
public AIObjectiveContainItem(Character character, Identifier[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
public AIObjectiveContainItem(Character character, ImmutableHashSet<Identifier> itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool spawnItemIfNotFound = false)
: base(character, objectiveManager, priorityModifier)
{
this.itemIdentifiers = itemIdentifiers;
@@ -102,7 +104,10 @@ namespace Barotrauma
return containedItemCount >= ItemCount;
}
private bool CheckItem(Item i) => itemIdentifiers.Any(id => i.Prefab.Identifier == id || i.HasTag(id)) && i.ConditionPercentage >= ConditionLevel && i.HasAccess(character);
private bool CheckItem(Item item)
{
return CheckItemIdentifiersOrTags(item, itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
}
protected override void Act(float deltaTime)
{
@@ -1,5 +1,7 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
@@ -13,7 +15,7 @@ namespace Barotrauma
//can either be a tag or an identifier
private readonly string[] itemIdentifiers;
private readonly ItemContainer sourceContainer;
private ItemContainer targetContainer;
private readonly ItemContainer targetContainer;
private readonly Item targetItem;
private AIObjectiveGetItem getItemObjective;
@@ -127,7 +129,7 @@ namespace Barotrauma
RemoveExistingPredicate = RemoveExistingPredicate,
RemoveMax = RemoveExistingMax,
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = sourceContainer != null ? new Identifier[] { sourceContainer.Item.Prefab.Identifier } : null
ignoredContainerIdentifiers = sourceContainer?.Item.Prefab.Identifier.ToEnumerable().ToImmutableHashSet()
},
onCompleted: () => IsCompleted = true,
onAbandon: () => Abandon = true);
@@ -177,7 +177,7 @@ namespace Barotrauma
TargetName = Leak.FlowTargetHull?.DisplayName,
requiredCondition = () =>
Leak.Submarine == character.Submarine &&
Leak.linkedTo.Any(e => e is Hull h && character.CurrentHull == h),
Leak.linkedTo.Any(e => e is Hull h && (character.CurrentHull == h || h.linkedTo.Contains(character.CurrentHull))),
endNodeFilter = n => n.Waypoint.CurrentHull != null && Leak.linkedTo.Any(e => e is Hull h && h == n.Waypoint.CurrentHull),
// The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
SpeakCannotReachCondition = () => !CheckObjectiveSpecific()
@@ -21,10 +21,10 @@ namespace Barotrauma
public float TargetCondition { get; set; } = 1;
public bool AllowDangerousPressure { get; set; }
public readonly ImmutableArray<Identifier> IdentifiersOrTags;
public readonly ImmutableHashSet<Identifier> IdentifiersOrTags;
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
private bool spawnItemIfNotFound = false;
private readonly bool spawnItemIfNotFound = false;
private Item targetItem;
private readonly Item originalTarget;
@@ -32,8 +32,8 @@ namespace Barotrauma
private bool isDoneSeeking;
public Item TargetItem => targetItem;
private int currSearchIndex;
public Identifier[] ignoredContainerIdentifiers;
public Identifier[] ignoredIdentifiersOrTags;
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
public ImmutableHashSet<Identifier> ignoredIdentifiersOrTags;
private AIObjectiveGoTo goToObjective;
private float currItemPriority;
private readonly bool checkInventory;
@@ -93,8 +93,8 @@ namespace Barotrauma
Equip = equip;
this.spawnItemIfNotFound = spawnItemIfNotFound;
this.checkInventory = checkInventory;
IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray();
ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToArray();
IdentifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableHashSet();
ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToImmutableHashSet();
}
public static IEnumerable<Identifier> ParseGearTags(IEnumerable<Identifier> identifiersOrTags)
@@ -558,11 +558,11 @@ namespace Barotrauma
{
if (!item.HasAccess(character)) { return false; }
if (ignoredItems.Contains(item)) { return false; };
if (ignoredIdentifiersOrTags != null && ignoredIdentifiersOrTags.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { return false; }
if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; }
if (item.Condition < TargetCondition) { return false; }
if (ItemFilter != null && !ItemFilter(item)) { return false; }
if (RequireLoaded && item.Components.Any(i => !i.IsLoaded(character))) { return false; }
return IdentifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && item.Prefab.VariantOf == id));
return CheckItemIdentifiersOrTags(item, IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
}
public override void Reset()
@@ -25,7 +25,7 @@ namespace Barotrauma
public bool RequireAllItems { get; set; }
private readonly ImmutableArray<Identifier> gearTags;
private readonly Identifier[] ignoredTags;
private readonly ImmutableHashSet<Identifier> ignoredTags;
private bool subObjectivesCreated;
public readonly HashSet<Item> achievedItems = new HashSet<Item>();
@@ -33,7 +33,7 @@ namespace Barotrauma
public AIObjectiveGetItems(Character character, AIObjectiveManager objectiveManager, IEnumerable<Identifier> identifiersOrTags, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
{
gearTags = AIObjectiveGetItem.ParseGearTags(identifiersOrTags).ToImmutableArray();
ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToArray();
ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToImmutableHashSet();
}
protected override bool CheckObjectiveSpecific() => subObjectivesCreated && subObjectives.None();
@@ -259,7 +259,8 @@ namespace Barotrauma
// Check that there is no unsafe hulls on the way to the target
if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; }
return true;
}, endNodeFilter: node => !isCurrentHullAllowed | !IsForbidden(node.Waypoint.CurrentHull));
//don't stop at ladders when idling
}, endNodeFilter: node => node.Waypoint.Ladders == null && (!isCurrentHullAllowed || !IsForbidden(node.Waypoint.CurrentHull)));
if (path.Unreachable)
{
//can't go to this room, remove it from the list and try another room
@@ -290,7 +291,9 @@ namespace Barotrauma
}
else if (currentTarget != null)
{
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1,
nodeFilter: node => node.Waypoint.CurrentHull != null,
endNodeFilter: node => node.Waypoint.Ladders == null);
}
else
{
@@ -444,7 +444,7 @@ namespace Barotrauma
#if SERVER
public void ServerEventWrite(IWriteMessage msg, Client client, NetEntityEvent.IData extraData = null)
{
msg.Write(IsAlive);
msg.WriteBoolean(IsAlive);
}
#endif
}
@@ -331,39 +331,41 @@ namespace Barotrauma
Collider.SetTransform(Collider.SimPosition, Collider.Rotation + angleDiff);
}
}
if (character.LockHands)
{
var leftHand = GetLimb(LimbType.LeftHand);
var rightHand = GetLimb(LimbType.RightHand);
var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
rightHand.Disabled = true;
leftHand.Disabled = true;
Vector2 midPos = waist.SimPosition;
Matrix torsoTransform = Matrix.CreateRotationZ(waist.Rotation);
midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform);
if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f;
HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
}
else if (character.AnimController.AnimationTestPose)
if (character.AnimController.AnimationTestPose)
{
ApplyTestPose();
}
else if (Anim != Animation.UsingItem)
else
{
if (Anim != Animation.UsingItemWhileClimbing)
if (character.LockHands)
{
ResetPullJoints();
var leftHand = GetLimb(LimbType.LeftHand);
var rightHand = GetLimb(LimbType.RightHand);
var waist = GetLimb(LimbType.Waist) ?? GetLimb(LimbType.Torso);
rightHand.Disabled = true;
leftHand.Disabled = true;
Vector2 midPos = waist.SimPosition;
Matrix torsoTransform = Matrix.CreateRotationZ(waist.Rotation);
midPos += Vector2.Transform(new Vector2(-0.3f * Dir, -0.2f), torsoTransform);
if (rightHand.PullJointEnabled) midPos = (midPos + rightHand.PullJointWorldAnchorB) / 2.0f;
HandIK(rightHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
HandIK(leftHand, midPos, CurrentAnimationParams.ArmIKStrength, CurrentAnimationParams.HandIKStrength);
}
else
if (Anim != Animation.UsingItem && character.SelectedBy == null)
{
ResetPullJoints(l => l.IsLowerBody);
if (Anim != Animation.UsingItemWhileClimbing)
{
ResetPullJoints();
}
else
{
ResetPullJoints(l => l.IsLowerBody);
}
}
}
@@ -1489,17 +1491,17 @@ namespace Barotrauma
public override void DragCharacter(Character target, float deltaTime)
{
if (target == null) return;
if (target == null) { return; }
Limb torso = GetLimb(LimbType.Torso);
Limb leftHand = GetLimb(LimbType.LeftHand);
Limb rightHand = GetLimb(LimbType.RightHand);
Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftHand);
Limb targetLeftHand = target.AnimController.GetLimb(LimbType.LeftForearm);
if (targetLeftHand == null) targetLeftHand = target.AnimController.GetLimb(LimbType.Torso);
if (targetLeftHand == null) targetLeftHand = target.AnimController.MainLimb;
Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightHand);
Limb targetRightHand = target.AnimController.GetLimb(LimbType.RightForearm);
if (targetRightHand == null) targetRightHand = target.AnimController.GetLimb(LimbType.Torso);
if (targetRightHand == null) targetRightHand = target.AnimController.MainLimb;
@@ -1628,7 +1630,7 @@ namespace Barotrauma
}
//only pull with one hand when swimming
if (i > 0 && inWater) continue;
if (i > 0 && inWater) { continue; }
Vector2 diff = ConvertUnits.ToSimUnits(targetLimb.WorldPosition - pullLimb.WorldPosition);
@@ -1680,14 +1682,15 @@ namespace Barotrauma
targetForce = 5000.0f;
}
if (!target.AllowInput)
{
targetLimb.PullJointEnabled = true;
targetLimb.PullJointMaxForce = targetForce;
targetLimb.PullJointWorldAnchorB = targetAnchor;
}
targetLimb.PullJointEnabled = true;
targetLimb.PullJointMaxForce = targetForce;
targetLimb.PullJointWorldAnchorB = targetAnchor;
targetLimb.Disabled = true;
target.AnimController.movement = -diff;
if (diff.LengthSquared() > 0.1f)
{
target.AnimController.movement = -diff;
}
}
float dist = ConvertUnits.ToSimUnits(Vector2.Distance(target.WorldPosition, WorldPosition));
@@ -1714,7 +1717,8 @@ namespace Barotrauma
else if (target is AICharacter && target != Character.Controlled)
{
target.AnimController.TargetDir = WorldPosition.X > target.WorldPosition.X ? Direction.Right : Direction.Left;
target.AnimController.TargetMovement = (character.SimPosition + Vector2.UnitX * Dir) - target.SimPosition;
Vector2 movement = (character.SimPosition + Vector2.UnitX * 0.5f * Dir) - target.SimPosition;
target.AnimController.TargetMovement = movement.LengthSquared() > 0.01f ? movement : Vector2.Zero;
}
}
}
@@ -2969,7 +2969,7 @@ namespace Barotrauma
}
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) &&
PressureProtection < (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f) &&
WorldPosition.Y < CharacterHealth.CrushDepth)
WorldPosition.Y < CharacterHealth.CrushDepth && !HasAbilityFlag(AbilityFlags.ImmuneToPressure))
{
//implode if below crush depth, and either outside or in a high-pressure hull
if (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)
@@ -4776,7 +4776,7 @@ namespace Barotrauma
{
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
{
if (talentOption.Talents.None(t => HasTalent(t.Identifier)))
if (talentOption.TalentIdentifiers.None(t => HasTalent(t)))
{
return false;
}
@@ -809,8 +809,8 @@ namespace Barotrauma
}
HumanPrefabIds = (
element.GetAttributeIdentifier("npcsetid", Identifier.Empty),
element.GetAttributeIdentifier("npcid", Identifier.Empty));
infoElement.GetAttributeIdentifier("npcsetid", Identifier.Empty),
infoElement.GetAttributeIdentifier("npcid", Identifier.Empty));
MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0);
@@ -1146,14 +1146,14 @@ namespace Barotrauma
activeAfflictions.Add(affliction);
}
}
msg.Write((byte)activeAfflictions.Count);
msg.WriteByte((byte)activeAfflictions.Count);
foreach (Affliction affliction in activeAfflictions)
{
msg.Write(affliction.Prefab.UintIdentifier);
msg.WriteUInt32(affliction.Prefab.UintIdentifier);
msg.WriteRangedSingle(
MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength),
0.0f, affliction.Prefab.MaxStrength, 8);
msg.Write((byte)affliction.Prefab.PeriodicEffects.Count());
msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count());
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects)
{
msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8);
@@ -1170,15 +1170,15 @@ namespace Barotrauma
limbAfflictions.Add((limbHealth, limbAffliction));
}
msg.Write((byte)limbAfflictions.Count);
msg.WriteByte((byte)limbAfflictions.Count);
foreach (var (limbHealth, affliction) in limbAfflictions)
{
msg.WriteRangedInteger(limbHealths.IndexOf(limbHealth), 0, limbHealths.Count - 1);
msg.Write(affliction.Prefab.UintIdentifier);
msg.WriteUInt32(affliction.Prefab.UintIdentifier);
msg.WriteRangedSingle(
MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength),
0.0f, affliction.Prefab.MaxStrength, 8);
msg.Write((byte)affliction.Prefab.PeriodicEffects.Count());
msg.WriteByte((byte)affliction.Prefab.PeriodicEffects.Count());
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in affliction.Prefab.PeriodicEffects)
{
msg.WriteRangedSingle(affliction.PeriodicEffectTimers[periodicEffect], periodicEffect.MinInterval, periodicEffect.MaxInterval, 8);
@@ -85,7 +85,7 @@ namespace Barotrauma
public readonly List<(XElement element, float commonness)> ItemSets = new List<(XElement element, float commonness)>();
public readonly List<(XElement element, float commonness)> CustomCharacterInfos = new List<(XElement element, float commonness)>();
private readonly Identifier npcSetIdentifier;
public readonly Identifier NpcSetIdentifier;
public HumanPrefab(ContentXElement element, ContentFile file, Identifier npcSetIdentifier) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
@@ -94,7 +94,7 @@ namespace Barotrauma
element.GetChildElements("itemset").ForEach(e => ItemSets.Add((e, e.GetAttributeFloat("commonness", 1))));
element.GetChildElements("character").ForEach(e => CustomCharacterInfos.Add((e, e.GetAttributeFloat("commonness", 1))));
PreferredOutpostModuleTypes = element.GetAttributeIdentifierArray("preferredoutpostmoduletypes", Array.Empty<Identifier>());
this.npcSetIdentifier = npcSetIdentifier;
this.NpcSetIdentifier = npcSetIdentifier;
}
public IEnumerable<Identifier> GetModuleFlags()
@@ -183,7 +183,7 @@ namespace Barotrauma
{
characterInfo = new CharacterInfo(characterElement, Identifier);
}
characterInfo.HumanPrefabIds = (npcSetIdentifier, Identifier);
characterInfo.HumanPrefabIds = (NpcSetIdentifier, Identifier);
return characterInfo;
}
@@ -1,6 +1,4 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class AbilityConditionAboveVitality : AbilityConditionDataless
{
@@ -13,7 +11,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific()
{
return character.HealthPercentage / 100f > vitalityPercentage;
return character.Vitality / character.MaxVitality > vitalityPercentage;
}
}
}
@@ -1,11 +1,10 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAlliesAboveVitality : AbilityConditionDataless
{
float vitalityPercentage;
readonly float vitalityPercentage;
public AbilityConditionAlliesAboveVitality(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
{
@@ -1,7 +1,4 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class AbilityConditionCoauthor : AbilityConditionDataless
{
@@ -1,7 +1,4 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class AbilityConditionCrouched : AbilityConditionDataless
{
@@ -1,28 +1,22 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class AbilityConditionHasAffliction : AbilityConditionDataless
{
private string afflictionIdentifier;
private float minimumPercentage;
private readonly Identifier afflictionIdentifier;
private readonly float minimumPercentage;
public AbilityConditionHasAffliction(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
{
afflictionIdentifier = conditionElement.GetAttributeString("afflictionidentifier", "");
afflictionIdentifier = conditionElement.GetAttributeIdentifier("afflictionidentifier", Identifier.Empty);
minimumPercentage = conditionElement.GetAttributeFloat("minimumpercentage", 0f);
}
protected override bool MatchesConditionSpecific()
{
if (!string.IsNullOrEmpty(afflictionIdentifier))
if (!afflictionIdentifier.IsEmpty)
{
var affliction = character.CharacterHealth.GetAffliction(afflictionIdentifier);
if (affliction == null) { return false; }
return minimumPercentage <= affliction.Strength / affliction.Prefab.MaxStrength;
}
return false;
@@ -3,55 +3,43 @@ using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Abilities
{
class AbilityConditionHasItem : AbilityConditionDataless
{
// not used for anything atm, will be used for clown subclass
private readonly string[] tags;
private InvSlotType? invSlotType;
bool requireAll;
private List<Item> items = new List<Item>();
readonly bool requireAll;
public AbilityConditionHasItem(CharacterTalent characterTalent, ContentXElement conditionElement) : base(characterTalent, conditionElement)
{
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
tags = conditionElement.GetAttributeStringArray("tags", Array.Empty<string>());
requireAll = conditionElement.GetAttributeBool("requireall", false);
//this.invSlotType = invSlotType;
}
protected override bool MatchesConditionSpecific()
{
items.Clear();
if (tags.Any())
if (tags.None())
{
foreach (string tag in tags)
{
// there is a better method, should use that instead
if (character.GetEquippedItem(tag, invSlotType) is Item foundItem)
{
items.Add(foundItem);
}
}
}
else
{
if (character.GetEquippedItem(null, invSlotType) is Item foundItem)
{
items.Add(foundItem);
}
return character.GetEquippedItem(null) is Item;
}
if (requireAll)
{
return (items.Count >= tags.Count());
foreach (string tag in tags)
{
if (character.GetEquippedItem(tag) == null) { return false; }
}
return true;
}
else
{
return items.Any();
foreach (string tag in tags)
{
if (character.GetEquippedItem(tag) != null) { return true; }
}
return false;
}
}
}
@@ -1,7 +1,4 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class AbilityConditionHasVelocity : AbilityConditionDataless
{
@@ -1,7 +1,4 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class AbilityConditionShipFlooded : AbilityConditionDataless
{
@@ -14,8 +11,14 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific()
{
if (!character.IsInFriendlySub) { return false; }
float currentFloodPercentage = character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
return currentFloodPercentage / 100 > floodPercentage;
float waterVolume = 0.0f, totalVolume = 0.0f;
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine != character.Submarine) { continue; }
waterVolume += hull.WaterVolume;
totalVolume += hull.Volume;
}
return (waterVolume / totalVolume) > floodPercentage;
}
}
}
@@ -1,7 +1,4 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStatToFlooding : CharacterAbility
{
@@ -22,8 +19,14 @@ namespace Barotrauma.Abilities
if (conditionsMatched && Character.IsInFriendlySub)
{
float currentFloodPercentage = Character.Submarine.GetHulls(false).Average(h => h.WaterPercentage);
lastValue = currentFloodPercentage / 100f * maxValue;
float waterVolume = 0.0f, totalVolume = 0.0f;
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine != Character.Submarine) { continue; }
waterVolume += hull.WaterVolume;
totalVolume += hull.Volume;
}
lastValue = (totalVolume == 0.0f ? 1.0f : waterVolume / totalVolume) * maxValue;
Character.ChangeStat(statType, lastValue);
}
else
@@ -1,8 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityUnlockTree : CharacterAbility
{
@@ -14,22 +10,19 @@ namespace Barotrauma.Abilities
{
if (!TalentTree.JobTalentTrees.TryGet(Character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
var subTree = talentTree.TalentSubTrees.Find(t => t.TalentOptionStages.Any(ts => ts.Talents.Contains(CharacterTalent.Prefab)));
var subTree = talentTree.TalentSubTrees.Find(t => t.AllTalentIdentifiers.Contains(CharacterTalent.Prefab.Identifier));
if (subTree == null) { return; }
subTree.ForceUnlock = true;
if (!addingFirstTime) { return; }
foreach (var talentOption in subTree.TalentOptionStages)
foreach (var talentId in subTree.AllTalentIdentifiers)
{
foreach (var talent in talentOption.Talents)
if (talentId == CharacterTalent.Prefab.Identifier) { continue; }
if (Character.GiveTalent(talentId))
{
if (talent == CharacterTalent.Prefab) { continue; }
if (Character.GiveTalent(talent))
{
Character.Info.AdditionalTalentPoints++;
}
}
Character.Info.AdditionalTalentPoints++;
}
}
}
}
@@ -1,10 +1,4 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
namespace Barotrauma.Abilities
{
class CharacterAbilityInsurancePolicy : CharacterAbility
{
@@ -19,10 +13,10 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (Character?.Info is CharacterInfo info)
if (Character?.Info is CharacterInfo info && GameMain.GameSession?.GameMode is CampaignMode campaign)
{
int totalAmount = moneyPerMission * info.MissionsCompletedSinceDeath;
Character.GiveMoney(totalAmount);
campaign.Bank.Give(totalAmount);
GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier.Value);
}
}
@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
public readonly AbilityEffectType AbilityEffectType;
protected int maxTriggerCount { get; }
protected readonly int maxTriggerCount;
protected int timesTriggered = 0;
@@ -88,8 +88,6 @@ namespace Barotrauma.Abilities
// XML
private AbilityCondition ConstructCondition(CharacterTalent characterTalent, ContentXElement conditionElement, bool errorMessages = true)
{
AbilityCondition newCondition = null;
Type conditionType;
string type = conditionElement.Name.ToString().ToLowerInvariant();
try
@@ -109,6 +107,7 @@ namespace Barotrauma.Abilities
object[] args = { characterTalent, conditionElement };
AbilityCondition newCondition;
try
{
newCondition = (AbilityCondition)Activator.CreateInstance(conditionType, args);
@@ -210,8 +209,7 @@ namespace Barotrauma.Abilities
public static AbilityFlags ParseFlagType(string flagTypeString, string debugIdentifier)
{
AbilityFlags flagType = AbilityFlags.None;
if (!Enum.TryParse(flagTypeString, true, out flagType))
if (!Enum.TryParse(flagTypeString, true, out AbilityFlags flagType))
{
DebugConsole.ThrowError("Invalid flag type type \"" + flagTypeString + "\" in CharacterTalent (" + debugIdentifier + ")");
}
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -19,7 +18,12 @@ namespace Barotrauma
public static readonly PrefabCollection<TalentTree> JobTalentTrees = new PrefabCollection<TalentTree>();
public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>();
public readonly ImmutableArray<TalentSubTree> TalentSubTrees;
/// <summary>
/// Talent identifiers of all the talents in this tree
/// </summary>
public readonly ImmutableHashSet<Identifier> AllTalentIdentifiers;
public ContentXElement ConfigElement
{
@@ -36,16 +40,19 @@ namespace Barotrauma
DebugConsole.ThrowError($"No job defined for talent tree in \"{file.Path}\"!");
return;
}
List<TalentSubTree> subTrees = new List<TalentSubTree>();
foreach (var subTreeElement in element.GetChildElements("subtree"))
{
TalentSubTrees.Add(new TalentSubTree(subTreeElement));
subTrees.Add(new TalentSubTree(subTreeElement));
}
TalentSubTrees = subTrees.ToImmutableArray();
AllTalentIdentifiers = TalentSubTrees.SelectMany(t => t.AllTalentIdentifiers).ToImmutableHashSet();
}
public bool TalentIsInTree(Identifier talentIdentifier)
{
return TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))).Any(c => c == talentIdentifier);
return AllTalentIdentifiers.Contains(talentIdentifier);
}
public static bool IsViableTalentForCharacter(Character character, Identifier talentIdentifier)
@@ -54,6 +61,7 @@ namespace Barotrauma
}
// i hate this function - markus
// me too - joonas
public static TalentTreeStageState GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, List<Identifier> selectedTalents)
{
if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; }
@@ -66,12 +74,12 @@ namespace Barotrauma
TalentOption targetTalentOption = subTree.TalentOptionStages[index];
if (targetTalentOption.Talents.Any(t => character.HasTalent(t.Identifier)))
if (targetTalentOption.TalentIdentifiers.Any(t => character.HasTalent(t)))
{
return TalentTreeStageState.Unlocked;
}
if (targetTalentOption.Talents.Any(t => selectedTalents.Contains(t.Identifier)))
if (targetTalentOption.TalentIdentifiers.Any(t => selectedTalents.Contains(t)))
{
return TalentTreeStageState.Highlighted;
}
@@ -83,8 +91,8 @@ namespace Barotrauma
if (lastindex >= 0)
{
TalentOption lastLatentOption = subTree.TalentOptionStages[lastindex];
hasTalentInLastTier = lastLatentOption.Talents.Any(HasTalent);
isLastTalentPurchased = lastLatentOption.Talents.Any(t => character.HasTalent(t.Identifier));
hasTalentInLastTier = lastLatentOption.TalentIdentifiers.Any(HasTalent);
isLastTalentPurchased = lastLatentOption.TalentIdentifiers.Any(t => character.HasTalent(t));
}
if (!hasTalentInLastTier)
@@ -101,9 +109,9 @@ namespace Barotrauma
return TalentTreeStageState.Locked;
bool HasTalent(TalentPrefab t)
bool HasTalent(Identifier talentId)
{
return selectedTalents.Contains(t.Identifier);
return selectedTalents.Contains(talentId);
}
}
@@ -117,14 +125,14 @@ namespace Barotrauma
foreach (var subTree in talentTree.TalentSubTrees)
{
if (subTree.ForceUnlock && subTree.TalentOptionStages.Any(option => option.Talents.Any(t => t.Identifier == talentIdentifier))) { return true; }
if (subTree.ForceUnlock && subTree.TalentOptionStages.Any(option => option.TalentIdentifiers.Contains(talentIdentifier))) { return true; }
foreach (var talentOptionStage in subTree.TalentOptionStages)
{
bool hasTalentInThisTier = talentOptionStage.Talents.Any(t => selectedTalents.Contains(t.Identifier));
bool hasTalentInThisTier = talentOptionStage.TalentIdentifiers.Any(t => selectedTalents.Contains(t));
if (!hasTalentInThisTier)
{
if (talentOptionStage.Talents.Any(t => t.Identifier == talentIdentifier))
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
{
return true;
}
@@ -170,18 +178,21 @@ namespace Barotrauma
public bool ForceUnlock;
public readonly List<TalentOption> TalentOptionStages = new List<TalentOption>();
public readonly ImmutableArray<TalentOption> TalentOptionStages;
public readonly ImmutableHashSet<Identifier> AllTalentIdentifiers;
public TalentSubTree(ContentXElement subTreeElement)
{
Identifier = subTreeElement.GetAttributeIdentifier("identifier", "");
DisplayName = TextManager.Get("talenttree." + Identifier).Fallback(Identifier.Value);
List<TalentOption> talentOptionStages = new List<TalentOption>();
foreach (var talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
{
TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
talentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
}
TalentOptionStages = talentOptionStages.ToImmutableArray();
AllTalentIdentifiers = TalentOptionStages.SelectMany(t => t.TalentIdentifiers).ToImmutableHashSet();
}
}
@@ -190,8 +201,12 @@ namespace Barotrauma
{
private readonly ImmutableHashSet<Identifier> talentIdentifiers;
public IEnumerable<TalentPrefab> Talents
=> talentIdentifiers.Select(id => TalentPrefab.TalentPrefabs[id]);
public IEnumerable<Identifier> TalentIdentifiers => talentIdentifiers;
public bool HasTalent(Identifier talentIdentifier)
{
return talentIdentifiers.Contains(talentIdentifier);
}
public TalentOption(ContentXElement talentOptionsElement, Identifier debugIdentifier)
{
@@ -0,0 +1,16 @@
namespace Barotrauma
{
[RequiredByCorePackage]
sealed class TutorialsFile : GenericPrefabFile<TutorialPrefab>
{
protected override PrefabCollection<TutorialPrefab> Prefabs => TutorialPrefab.Prefabs;
public TutorialsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
protected override bool MatchesSingular(Identifier identifier) => identifier == "Tutorial";
protected override bool MatchesPlural(Identifier identifier) => identifier == "Tutorials";
protected override TutorialPrefab CreatePrefab(ContentXElement element) => new TutorialPrefab(this, element);
}
}
@@ -965,16 +965,10 @@ namespace Barotrauma
foreach (var talentTree in talentTrees)
{
foreach (var subTree in talentTree.TalentSubTrees)
foreach (var talentId in talentTree.AllTalentIdentifiers)
{
foreach (var option in subTree.TalentOptionStages)
{
foreach (var talent in option.Talents)
{
character.GiveTalent(talent);
NewMessage($"Unlocked talent \"{talent.DisplayName}\".");
}
}
character.GiveTalent(talentId);
NewMessage($"Unlocked talent \"{talentId}\".");
}
}
},
@@ -1304,102 +1298,7 @@ namespace Barotrauma
}
}
}, null, true));
commands.Add(new Command("upgradeitem", "upgradeitem [upgrade] [level] [items]: Adds an upgrade to the current targeted item.", args =>
{
if (args.Length > 0)
{
int level;
if (args.Length > 1)
{
if (int.TryParse(args[1], out int result))
{
level = result;
}
else
{
ThrowError($"\"{args[1]}\" is not a valid level.");
return;
}
}
else
{
ThrowError("Parameter \"level\" is required.");
return;
}
var upgradePrefab = UpgradePrefab.Find(args[0].ToIdentifier());
if (upgradePrefab == null)
{
ThrowError($"Unknown upgrade: {args[0]}.");
return;
}
List<MapEntity> targetItems = new List<MapEntity>();
if (upgradePrefab.IsWallUpgrade)
{
targetItems.AddRange(Submarine.MainSub.GetWalls(true).Cast<MapEntity>());
}
else
{
if (args.Length > 2)
{
targetItems.AddRange(Item.ItemList.Where(item => item.Submarine == Submarine.MainSub).Where(item => item.HasTag(args[2])).Cast<MapEntity>());
}
else
{
ThrowError("Argument \"tag\" is required.");
return;
}
}
if (!targetItems.Any())
{
ThrowError("No valid items found.");
return;
}
foreach (MapEntity targetItem in targetItems)
{
Upgrade existingUpgrade = targetItem.GetUpgrade(args[0].ToIdentifier());
if (!(targetItem is ISerializableEntity sEntity)) { continue; }
var upgrade = new Upgrade(sEntity, upgradePrefab, level);
if (targetItem.AddUpgrade(upgrade, true))
{
if (existingUpgrade == null)
{
NewMessage($"Added {upgradePrefab.Identifier}:{level} to {sEntity.Name}.", Color.Green);
upgrade.ApplyUpgrade();
}
else
{
NewMessage($"Set {sEntity.Name}'s {upgradePrefab.Identifier} upgrade to level {existingUpgrade.Level}.", Color.Cyan);
existingUpgrade.ApplyUpgrade();
}
}
else
{
ThrowError($"{upgrade.Prefab.Identifier} cannot be applied to {sEntity.Name}");
}
}
}
else
{
ThrowError("Parameter \"upgrade\" is required.");
}
}, () =>
{
return new[]
{
UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().Select(i => i.Value).ToArray()
};
}, true));
commands.Add(new Command("maxupgrades", "maxupgrades [category] [prefab]: Maxes out all upgrades or only specific one if given arguments.", args =>
{
UpgradeManager upgradeManager = GameMain.GameSession?.Campaign?.UpgradeManager;
@@ -2362,7 +2261,7 @@ namespace Barotrauma
{
NewMessage(msg, color.Value, isCommand: false, isError: false);
}
#if DEBUG
#if DEBUG && CLIENT
Console.WriteLine(msg);
#endif
}
@@ -2547,7 +2446,7 @@ namespace Barotrauma
#endif
fileName += DateTime.Now.ToShortDateString() + "_" + DateTime.Now.ToShortTimeString();
var invalidChars = Path.GetInvalidFileNameChars();
var invalidChars = Path.GetInvalidFileNameCharsCrossPlatform();
foreach (char invalidChar in invalidChars)
{
fileName = fileName.Replace(invalidChar.ToString(), "");
@@ -1,8 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -20,6 +18,9 @@ namespace Barotrauma
[Serialize(true, IsPropertySaveable.Yes, "When set to false when TargetLimb is not specified prevent checking limb-specific afflictions")]
public bool AllowLimbAfflictions { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes, "Minimum strength of the affliction")]
public float MinStrength { get; set; }
public CheckAfflictionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
protected override bool? DetermineSuccess()
@@ -32,14 +33,15 @@ namespace Barotrauma
if (target.CharacterHealth == null) { continue; }
if (TargetLimb == LimbType.None)
{
if (target.CharacterHealth.GetAffliction(Identifier, AllowLimbAfflictions) != null) { return true; }
var affliction = target.CharacterHealth.GetAffliction(Identifier, AllowLimbAfflictions);
if (affliction != null && affliction.Strength >= MinStrength) { return true; }
}
IEnumerable<Affliction> afflictions = target.CharacterHealth.GetAllAfflictions().Where(affliction =>
{
LimbType? limbType = target.CharacterHealth.GetAfflictionLimb(affliction)?.type;
if (limbType == null) { return false; }
return limbType == TargetLimb || true;
return limbType == TargetLimb && affliction.Strength >= MinStrength;
});
if (afflictions.Any(a => a.Identifier == Identifier)) { return true; }
@@ -0,0 +1,71 @@
using System.Xml.Linq;
namespace Barotrauma
{
class CheckConditionalAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TargetTag { get; set; }
private PropertyConditional Conditional { get; }
public CheckConditionalAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
if (TargetTag.IsEmpty)
{
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no target tag! This will cause the check to automatically succeed.");
}
foreach (var attribute in element.Attributes())
{
if (PropertyConditional.IsValid(attribute) && !IsTargetTagAttribute(attribute))
{
Conditional = new PropertyConditional(attribute);
break;
}
}
if (Conditional == null)
{
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed.");
}
static bool IsTargetTagAttribute(XAttribute attribute)
{
return attribute.Name.ToString().Equals("targettag", System.StringComparison.OrdinalIgnoreCase);
}
}
private string GetEventName()
{
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
}
protected override bool? DetermineSuccess()
{
ISerializableEntity target = null;
if (!TargetTag.IsEmpty)
{
foreach (var t in ParentEvent.GetTargets(TargetTag))
{
if (t is ISerializableEntity e)
{
target = e;
break;
}
}
}
if (target == null)
{
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.");
}
if (target == null || Conditional == null)
{
return true;
}
if (target is Item item)
{
return item.ConditionalMatches(Conditional);
}
return Conditional.Matches(target);
}
}
}
@@ -0,0 +1,59 @@
namespace Barotrauma
{
class CheckOrderAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier OrderIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier OrderOption { get; set; }
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
protected override bool? DetermineSuccess()
{
ISerializableEntity target = null;
if (!TargetTag.IsEmpty)
{
foreach (var t in ParentEvent.GetTargets(TargetTag))
{
if (t is ISerializableEntity e)
{
target = e;
break;
}
}
}
if (target == null)
{
DebugConsole.ShowError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.");
return true;
}
if (target is Character character)
{
var currentOrderInfo = character.GetCurrentOrderWithTopPriority();
if (currentOrderInfo?.Identifier == OrderIdentifier)
{
if (OrderOption.IsEmpty)
{
return true;
}
else
{
return currentOrderInfo?.Option == OrderOption;
}
}
return false;
}
return true;
}
private string GetEventName()
{
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
}
}
}
@@ -0,0 +1,116 @@
using System;
using System.Linq;
namespace Barotrauma
{
class MessageBoxAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
public Identifier Header { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier Text { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public string IconStyle { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
public bool HideCloseButton { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public string CloseOnInput { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier CloseOnInteractTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier CloseOnPickUpTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier CloseOnEquipTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier CloseOnExitRoomName { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
public bool IsTutorialObjective { get; set; }
private bool isFinished = false;
public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
public override void Update(float deltaTime)
{
if (isFinished) { return; }
#if CLIENT
CreateMessageBox();
if (IsTutorialObjective && GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
tutorialMode.Tutorial?.TriggerTutorialSegment(new Tutorials.Tutorial.Segment(Text, CreateMessageBox));
}
#endif
isFinished = true;
}
#if CLIENT
public void CreateMessageBox()
{
new GUIMessageBox(
headerText: TextManager.Get(Header),
text: RichString.Rich(TextManager.ParseInputTypes(TextManager.Get(Text).Fallback(Text.ToString()), useColorHighlight: true)),
buttons: Array.Empty<LocalizedString>(),
type: GUIMessageBox.Type.Tutorial,
iconStyle: IconStyle,
autoCloseCondition: GetAutoCloseCondition(),
hideCloseButton: HideCloseButton);
}
#endif
private Func<bool> GetAutoCloseCondition()
{
var character = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Character;
Func<bool> autoCloseCondition = null;
if (!string.IsNullOrEmpty(CloseOnInput) && Enum.TryParse(CloseOnInput, true, out InputType closeOnInput))
{
#if CLIENT
autoCloseCondition = () => PlayerInput.KeyDown(closeOnInput);
#endif
}
else if (!CloseOnInteractTag.IsEmpty)
{
autoCloseCondition = () => character?.SelectedItem != null && character.SelectedItem.HasTag(CloseOnInteractTag);
}
else if (!CloseOnPickUpTag.IsEmpty)
{
autoCloseCondition = () => character?.Inventory != null && character.Inventory.FindItemByTag(CloseOnPickUpTag, recursive: true) != null;
}
else if (!CloseOnEquipTag.IsEmpty)
{
autoCloseCondition = () => character != null && character.HasEquippedItem(CloseOnEquipTag);
}
else if (!CloseOnExitRoomName.IsEmpty)
{
autoCloseCondition = () => character?.CurrentHull != null && character.CurrentHull.RoomName.ToIdentifier() != CloseOnExitRoomName;
}
return autoCloseCondition;
}
public override bool IsFinished(ref string goToLabel)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MessageBoxAction)}";
}
}
}
@@ -145,9 +145,9 @@ namespace Barotrauma
foreach (Client client in GameMain.Server.ConnectedClients)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
outmsg.Write((byte)EventManager.NetworkEventType.MISSION);
outmsg.Write(prefab.Identifier);
outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION);
outmsg.WriteByte((byte)EventManager.NetworkEventType.MISSION);
outmsg.WriteIdentifier(prefab.Identifier);
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
}
}
@@ -57,6 +57,9 @@ namespace Barotrauma
private readonly HashSet<Identifier> targetModuleTags = new HashSet<Identifier>();
[Serialize(true, IsPropertySaveable.Yes, description: "If false, we won't spawn another character if one with the same identifier has already been spawned.")]
public bool AllowDuplicates { get; set; }
[Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the entity prefer to spawn in.")]
public string TargetModuleTags
{
@@ -115,6 +118,12 @@ namespace Barotrauma
HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier);
if (humanPrefab != null)
{
if (!AllowDuplicates &&
Character.CharacterList.Any(c => c.Info?.HumanPrefabIds.NpcIdentifier == NPCIdentifier && c.Info?.HumanPrefabIds.NpcSetIdentifier == NPCSetIdentifier))
{
spawned = true;
return;
}
ISpatialEntity spawnPos = GetSpawnPos();
if (spawnPos != null)
{
@@ -145,6 +154,11 @@ namespace Barotrauma
}
else if (!SpeciesName.IsEmpty)
{
if (!AllowDuplicates && Character.CharacterList.Any(c => c.SpeciesName == SpeciesName))
{
spawned = true;
return;
}
ISpatialEntity spawnPos = GetSpawnPos();
if (spawnPos != null)
{
@@ -1,10 +1,7 @@
using Barotrauma.Extensions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -36,6 +33,7 @@ namespace Barotrauma
("crew", v => TagCrew()),
("humanprefabidentifier", TagHumansByIdentifier),
("structureidentifier", TagStructuresByIdentifier),
("structurespecialtag", TagStructuresBySpecialTag),
("itemidentifier", TagItemsByIdentifier),
("itemtag", TagItemsByTag),
("hullname", TagHullsByName)
@@ -100,6 +98,11 @@ namespace Barotrauma
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier);
}
private void TagStructuresBySpecialTag(Identifier tag)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.SpecialTag.ToIdentifier() == tag);
}
private void TagItemsByIdentifier(Identifier identifier)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.Prefab.Identifier == identifier);
@@ -0,0 +1,32 @@
namespace Barotrauma
{
class TutorialCompleteAction : EventAction
{
private bool isFinished;
public TutorialCompleteAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
public override void Update(float deltaTime)
{
if (isFinished) { return; }
#if CLIENT
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
tutorialMode.Tutorial?.Complete();
}
#endif
isFinished = true;
}
public override bool IsFinished(ref string goToLabel)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
}
}
@@ -0,0 +1,95 @@
#if CLIENT
using Barotrauma.Tutorials;
#endif
namespace Barotrauma
{
class TutorialSegmentAction : EventAction
{
public enum SegmentActionType { Trigger, Complete, Remove };
[Serialize(SegmentActionType.Trigger, IsPropertySaveable.Yes)]
public SegmentActionType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier Id { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier ObjectiveTextTag { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
public bool AutoPlayVideo { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public Identifier TextTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
public string VideoFile { get; set; }
[Serialize(450, IsPropertySaveable.Yes)]
public int Width { get; set; }
[Serialize(80, IsPropertySaveable.Yes)]
public int Height { get; set; }
#if CLIENT
private readonly Tutorial.Segment segment;
#endif
private bool isFinished;
public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
#if CLIENT
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
if (Type == SegmentActionType.Trigger)
{
segment = new Tutorial.Segment(Id, ObjectiveTextTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center),
new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height));
}
#endif
}
public override void Update(float deltaTime)
{
if (isFinished) { return; }
#if CLIENT
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
if (tutorialMode.Tutorial is Tutorial tutorial)
{
switch (Type)
{
case SegmentActionType.Trigger:
tutorial.TriggerTutorialSegment(segment);
break;
case SegmentActionType.Complete:
tutorial.CompleteTutorialSegment(Id);
break;
case SegmentActionType.Remove:
tutorial.RemoveTutorialSegment(Id);
break;
}
}
}
else
{
DebugConsole.ShowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!");
}
#endif
isFinished = true;
}
public override bool IsFinished(ref string goToLabel)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
}
}
@@ -55,9 +55,9 @@ namespace Barotrauma
foreach (Client client in GameMain.Server.ConnectedClients)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
outmsg.Write((byte)EventManager.NetworkEventType.UNLOCKPATH);
outmsg.Write((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection));
outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION);
outmsg.WriteByte((byte)EventManager.NetworkEventType.UNLOCKPATH);
outmsg.WriteUInt16((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection));
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
}
}
@@ -109,6 +109,7 @@ namespace Barotrauma
subs[1].FlipX();
#if SERVER
crews = new List<Character>[] { new List<Character>(), new List<Character>() };
roundEndTimer = RoundEndDuration;
#endif
}
@@ -3,7 +3,9 @@ using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using PositionType = Barotrauma.Level.PositionType;
namespace Barotrauma
{
@@ -29,6 +31,25 @@ namespace Barotrauma
private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();
private readonly PositionType positionType = PositionType.Cave;
/// <remarks>
/// The list order is important.
/// It defines the order in which we "override" <see cref="positionType"/> in case no valid position types are found
/// in the level when generating them in <see cref="Level.GenerateMissionResources(ItemPrefab, int, PositionType, out float)"/>.
/// </remarks>
public static readonly ImmutableArray<PositionType> ValidPositionTypes = new PositionType[]
{
PositionType.Cave,
PositionType.SidePath,
PositionType.MainPath,
PositionType.AbyssCave,
}.ToImmutableArray();
/// <summary>
/// Percentage. Value between 0 and 1.
/// </summary>
private readonly float resourceHandoverAmount;
public override IEnumerable<Vector2> SonarPositions
{
get
@@ -39,8 +60,23 @@ namespace Barotrauma
}
}
public override LocalizedString SuccessMessage => ModifyMessage(base.SuccessMessage);
public override LocalizedString FailureMessage => ModifyMessage(base.FailureMessage);
public override LocalizedString Description => ModifyMessage(description);
public override LocalizedString Name => ModifyMessage(base.Name, false);
public override LocalizedString SonarLabel => ModifyMessage(base.SonarLabel, false);
public MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
{
var positionType = prefab.ConfigElement.GetAttributeEnum("PositionType", in this.positionType);
if (ValidPositionTypes.Contains(positionType))
{
this.positionType = positionType;
}
float handoverAmount = prefab.ConfigElement.GetAttributeFloat("ResourceHandoverAmount", 0.0f);
resourceHandoverAmount = Math.Clamp(handoverAmount, 0.0f, 1.0f);
var configElement = prefab.ConfigElement.GetChildElement("Items");
foreach (var c in configElement.GetChildElements("Item"))
{
@@ -92,27 +128,28 @@ namespace Barotrauma
caves.Clear();
if (IsClient) { return; }
foreach (var kvp in resourceClusters)
foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters)
{
var prefab = ItemPrefab.Find(null, kvp.Key);
if (prefab == null)
if (!(MapEntityPrefab.FindByIdentifier(identifier) is ItemPrefab prefab))
{
DebugConsole.ThrowError("Error in MineralMission - " +
"couldn't find an item prefab with the identifier " + kvp.Key);
DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")");
continue;
}
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.Amount, out float rotation);
if (spawnedResources.Count < kvp.Value.Amount)
{
DebugConsole.ThrowError("Error in MineralMission - " +
"spawned " + spawnedResources.Count + "/" + kvp.Value.Amount + " of " + prefab.Name);
}
if (spawnedResources.None()) { continue; }
this.spawnedResources.Add(kvp.Key, spawnedResources);
foreach (Level.Cave cave in Level.Loaded.Caves)
var spawnedResources = level.GenerateMissionResources(prefab, cluster.Amount, positionType, out float rotation);
if (spawnedResources.Count < cluster.Amount)
{
foreach (Item spawnedResource in spawnedResources)
DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{cluster.Amount} of {prefab.Name}");
}
if (spawnedResources.None()) { continue; }
this.spawnedResources.Add(identifier, spawnedResources);
foreach (var cave in Level.Loaded.Caves)
{
foreach (var spawnedResource in spawnedResources)
{
if (cave.Area.Contains(spawnedResource.WorldPosition))
{
@@ -123,6 +160,7 @@ namespace Barotrauma
}
}
}
CalculateMissionClusterPositions();
FindRelevantLevelResources();
}
@@ -151,6 +189,28 @@ namespace Barotrauma
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
if (!IsClient)
{
// When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission)
var handoverResources = new List<Item>();
foreach (Identifier identifier in resourceClusters.Keys)
{
if (relevantLevelResources.TryGetValue(identifier, out var availableResources))
{
var collectedResources = availableResources.Where(HasBeenCollected);
if (collectedResources.Count() < 1) { continue; }
int handoverCount = (int)MathF.Round(resourceHandoverAmount * collectedResources.Count());
for (int i = 0; i < handoverCount; i++)
{
handoverResources.Add(collectedResources.ElementAt(i));
}
}
}
foreach (var resource in handoverResources)
{
resource.Remove();
}
}
GiveReward();
completed = true;
}
@@ -237,5 +297,27 @@ namespace Barotrauma
missionClusterPositions.Add((kvp.Key, pos));
}
}
protected override LocalizedString ModifyMessage(LocalizedString message, bool color = true)
{
int i = 1;
foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters)
{
Replace($"[resourcename{i}]", ItemPrefab.FindByIdentifier(identifier)?.Name.Value ?? "");
Replace($"[resourcequantity{i}]", cluster.Amount.ToString());
i++;
}
Replace("[handoverpercentage]", ToolBox.GetFormattedPercentage(resourceHandoverAmount));
return message;
void Replace(string find, string replace)
{
if (color)
{
replace = $"‖color:gui.orange‖{replace}‖end‖";
}
message = message.Replace(find, replace);
}
}
}
}
@@ -41,7 +41,7 @@ namespace Barotrauma
public readonly ImmutableArray<LocalizedString> Headers;
public readonly ImmutableArray<LocalizedString> Messages;
public LocalizedString Name => Prefab.Name;
public virtual LocalizedString Name => Prefab.Name;
private readonly LocalizedString successMessage;
public virtual LocalizedString SuccessMessage
@@ -276,6 +276,10 @@ namespace Barotrauma
partial void ShowMessageProjSpecific(int missionState);
protected virtual LocalizedString ModifyMessage(LocalizedString message, bool color = true)
{
return message;
}
private void TryTriggerEvents(int state)
{
@@ -1,4 +1,7 @@
using Barotrauma.Extensions;
#if CLIENT
using Barotrauma.Tutorials;
#endif
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -348,6 +351,9 @@ namespace Barotrauma
private void UpdateConversations(float deltaTime)
{
if (GameMain.GameSession?.GameMode?.Preset == GameModePreset.TestMode) { return; }
#if CLIENT
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial is Tutorial tutorial && tutorial.TutorialPrefab.DisableBotConversations) { return; }
#endif
if (GameMain.NetworkMember != null && GameMain.NetworkMember.ServerSettings.DisableBotConversations) { return; }
conversationTimer -= deltaTime;
@@ -10,8 +10,6 @@ namespace Barotrauma
{
partial class MultiPlayerCampaign : CampaignMode
{
public const int MinimumInitialMoney = 500;
[Flags]
public enum NetFlags : UInt16
{
@@ -294,14 +292,14 @@ namespace Barotrauma
private static void WriteItems(IWriteMessage msg, Dictionary<Identifier, List<PurchasedItem>> purchasedItems)
{
msg.Write((byte)purchasedItems.Count);
msg.WriteByte((byte)purchasedItems.Count);
foreach (var storeItems in purchasedItems)
{
msg.Write(storeItems.Key);
msg.Write((UInt16)storeItems.Value.Count);
msg.WriteIdentifier(storeItems.Key);
msg.WriteUInt16((UInt16)storeItems.Value.Count);
foreach (var item in storeItems.Value)
{
msg.Write(item.ItemPrefabIdentifier);
msg.WriteIdentifier(item.ItemPrefabIdentifier);
msg.WriteRangedInteger(item.Quantity, 0, CargoManager.MaxQuantity);
}
}
@@ -328,18 +326,18 @@ namespace Barotrauma
private static void WriteItems(IWriteMessage msg, Dictionary<Identifier, List<SoldItem>> soldItems)
{
msg.Write((byte)soldItems.Count);
msg.WriteByte((byte)soldItems.Count);
foreach (var storeItems in soldItems)
{
msg.Write(storeItems.Key);
msg.Write((UInt16)storeItems.Value.Count);
msg.WriteIdentifier(storeItems.Key);
msg.WriteUInt16((UInt16)storeItems.Value.Count);
foreach (var item in storeItems.Value)
{
msg.Write(item.ItemPrefab.Identifier);
msg.Write((UInt16)item.ID);
msg.Write(item.Removed);
msg.Write(item.SellerID);
msg.Write((byte)item.Origin);
msg.WriteIdentifier(item.ItemPrefab.Identifier);
msg.WriteUInt16((UInt16)item.ID);
msg.WriteBoolean(item.Removed);
msg.WriteByte(item.SellerID);
msg.WriteByte((byte)item.Origin);
}
}
}
@@ -1,5 +1,7 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -11,37 +13,37 @@ namespace Barotrauma
public void AssignTeamIDs(IEnumerable<Client> clients)
{
int teamWeight = 0;
List<Client> randList = new List<Client>(clients);
for (int i = 0; i < randList.Count; i++)
int team1Count = 0, team2Count = 0;
//if a client has a preference, assign them to that team
List<Client> unassignedClients = new List<Client>(clients);
for (int i = 0; i < unassignedClients.Count; i++)
{
if (randList[i].PreferredTeam == CharacterTeamType.Team1 ||
randList[i].PreferredTeam == CharacterTeamType.Team2)
if (unassignedClients[i].PreferredTeam == CharacterTeamType.Team1 ||
unassignedClients[i].PreferredTeam == CharacterTeamType.Team2)
{
randList[i].TeamID = randList[i].PreferredTeam;
teamWeight += randList[i].PreferredTeam == CharacterTeamType.Team1 ? -1 : 1;
randList.RemoveAt(i);
assignTeam(unassignedClients[i], unassignedClients[i].PreferredTeam);
i--;
}
}
for (int i = 0; i<randList.Count; i++)
{
Client a = randList[i];
int oi = Rand.Range(0, randList.Count - 1);
Client b = randList[oi];
randList[i] = b;
randList[oi] = a;
}
int halfPlayers = (randList.Count / 2) + teamWeight;
for (int i = 0; i < randList.Count; i++)
//assign the rest of the clients to the team that has the least players
while (unassignedClients.Any())
{
if (i < halfPlayers)
var randomClient = unassignedClients.GetRandom(Rand.RandSync.Unsynced);
assignTeam(randomClient, team1Count < team2Count ? CharacterTeamType.Team1 : CharacterTeamType.Team2);
}
void assignTeam(Client client, CharacterTeamType team)
{
client.TeamID = team;
unassignedClients.Remove(client);
if (team == CharacterTeamType.Team1)
{
randList[i].TeamID = CharacterTeamType.Team1;
team1Count++;
}
else
{
randList[i].TeamID = CharacterTeamType.Team2;
team2Count++;
}
}
}
@@ -0,0 +1,73 @@
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
{
class TutorialPrefab : Prefab
{
public static readonly PrefabCollection<TutorialPrefab> Prefabs = new PrefabCollection<TutorialPrefab>();
public readonly int Order;
public readonly bool DisableBotConversations;
public readonly bool AllowCharacterSwitch;
public readonly ContentPath SubmarinePath = ContentPath.FromRaw("Content/Tutorials/Dugong_Tutorial.sub");
public readonly ContentPath OutpostPath = ContentPath.FromRaw("Content/Tutorials/TutorialOutpost.sub");
public readonly string LevelSeed;
public readonly string LevelParams;
private readonly ContentXElement tutorialCharacterElement;
public readonly ImmutableArray<Identifier> StartingItemTags;
public readonly Identifier EventIdentifier;
public TutorialPrefab(ContentFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
Order = element.GetAttributeInt("order", int.MaxValue);
DisableBotConversations = element.GetAttributeBool("disablebotconversations", true);
AllowCharacterSwitch = element.GetAttributeBool("allowcharacterswitch", false);
SubmarinePath = element.GetAttributeContentPath("submarinepath") ?? SubmarinePath;
OutpostPath = element.GetAttributeContentPath("outpostpath") ?? OutpostPath;
LevelSeed = element.GetAttributeString("levelseed", "nLoZLLtza");
LevelParams = element.GetAttributeString("levelparams", "ColdCavernsTutorial");
tutorialCharacterElement = element.GetChildElement("characterinfo");
if (tutorialCharacterElement != null)
{
StartingItemTags = tutorialCharacterElement
.GetAttributeIdentifierArray("startingitemtags", new Identifier[0])
.ToImmutableArray();
}
else
{
StartingItemTags = ImmutableArray<Identifier>.Empty;
}
EventIdentifier = element.GetChildElement("scriptedevent")?.GetAttributeIdentifier("identifier", "") ?? Identifier.Empty;
}
public CharacterInfo GetTutorialCharacterInfo()
{
if (tutorialCharacterElement == null)
{
return null;
}
Identifier speciesName = tutorialCharacterElement.GetAttributeIdentifier("speciesname", CharacterPrefab.HumanSpeciesName);
string jobPrefabIdentifier = tutorialCharacterElement.GetAttributeString("jobidentifier", "assistant");
var jobPrefab = JobPrefab.Prefabs.FirstOrDefault(p => p.Identifier == jobPrefabIdentifier) ?? JobPrefab.Prefabs.First();
int jobVariant = tutorialCharacterElement.GetAttributeInt("variant", 0);
var characterInfo = new CharacterInfo(speciesName, jobOrJobPrefab: jobPrefab, variant: jobVariant);
foreach (var skillElement in tutorialCharacterElement.GetChildElements("skill"))
{
Identifier skillIdentifier = skillElement.GetAttributeIdentifier("identifier", "");
if (skillIdentifier.IsEmpty) { continue; }
float level = skillElement.GetAttributeFloat("level", 0.0f);
characterInfo.SetSkillLevel(skillIdentifier, level);
}
return characterInfo;
}
public override void Dispose() { }
}
}
@@ -211,7 +211,7 @@ namespace Barotrauma
if (selectedSub != null)
{
campaign.Bank.Deduct(selectedSub.Price);
campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, MultiPlayerCampaign.MinimumInitialMoney);
campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, 0);
}
return campaign;
}
@@ -222,7 +222,7 @@ namespace Barotrauma
if (selectedSub != null)
{
campaign.Bank.TryDeduct(selectedSub.Price);
campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, MultiPlayerCampaign.MinimumInitialMoney);
campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, 0);
}
return campaign;
}
@@ -602,7 +602,7 @@ namespace Barotrauma
if (GameMode is MultiPlayerCampaign mpCampaign)
{
mpCampaign.UpgradeManager.ApplyUpgrades();
mpCampaign.UpgradeManager.SanityCheckUpgrades(Submarine);
mpCampaign.UpgradeManager.SanityCheckUpgrades();
}
}
@@ -512,8 +512,11 @@ namespace Barotrauma
/// Should be called after every round start right after <see cref="ApplyUpgrades"/>
/// </summary>
/// <param name="submarine"></param>
public void SanityCheckUpgrades(Submarine submarine)
public void SanityCheckUpgrades()
{
Submarine submarine = GameMain.GameSession?.Submarine ?? Submarine.MainSub;
if (submarine is null) { return; }
// check walls
foreach (Structure wall in submarine.GetWalls(UpgradeAlsoConnectedSubs))
{
@@ -521,23 +524,8 @@ namespace Barotrauma
{
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs)
{
int level = GetRealUpgradeLevel(prefab, category);
if (level == 0 || !prefab.IsWallUpgrade) { continue; }
Upgrade? upgrade = wall.GetUpgrade(prefab.Identifier);
bool isOverMax = IsOverMaxLevel(level, prefab);
if (isOverMax)
{
SetUpgradeLevel(prefab, category, prefab.MaxLevel);
level = prefab.MaxLevel;
}
if (upgrade == null || upgrade.Level != level || isOverMax)
{
DebugLog($"{((MapEntity)wall).Prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing...");
FixUpgradeOnItem(wall, prefab, level);
}
if (!prefab.IsWallUpgrade) { continue; }
TryFixUpgrade(wall, category, prefab);
}
}
}
@@ -549,35 +537,38 @@ namespace Barotrauma
{
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs)
{
if (!category.CanBeApplied(item, prefab)) { continue; }
int level = GetRealUpgradeLevel(prefab, category);
if (level == 0) { continue; }
Upgrade? upgrade = item.GetUpgrade(prefab.Identifier);
bool isOverMax = IsOverMaxLevel(level, prefab);
if (isOverMax)
{
SetUpgradeLevel(prefab, category, prefab.MaxLevel);
level = prefab.MaxLevel;
}
if (upgrade == null || upgrade.Level != level || isOverMax)
{
DebugLog($"{((MapEntity)item).Prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}{(isOverMax ? " (Over max level!)" : string.Empty)}. Fixing...");
FixUpgradeOnItem(item, prefab, level);
}
TryFixUpgrade(item, category, prefab);
}
}
}
static bool IsOverMaxLevel(int level, UpgradePrefab prefab) => level > prefab.MaxLevel;
void TryFixUpgrade(MapEntity entity, UpgradeCategory category, UpgradePrefab prefab)
{
if (!category.CanBeApplied(entity, prefab)) { return; }
int level = GetRealUpgradeLevel(prefab, category);
int maxLevel = submarine.Info is { } info ? prefab.GetMaxLevel(info) : prefab.MaxLevel;
if (maxLevel < level) { level = maxLevel; }
if (level == 0) { return; }
Upgrade? upgrade = entity.GetUpgrade(prefab.Identifier);
if (upgrade == null || upgrade.Level != level)
{
DebugLog($"{entity.Prefab.Name} has incorrect \"{prefab.Name}\" level! Expected {level} but got {upgrade?.Level ?? 0}. Fixing...");
FixUpgradeOnItem((ISerializableEntity)entity, prefab, level);
}
}
}
private static void FixUpgradeOnItem(ISerializableEntity target, UpgradePrefab prefab, int level)
{
if (target is MapEntity mapEntity)
{
// do not fix what's not broken
if (level == 0) { return; }
mapEntity.SetUpgrade(new Upgrade(target, prefab, level), false);
}
}
@@ -23,5 +23,6 @@ namespace Barotrauma
PreviousFireMode,
ActiveChat,
ToggleChatMode,
ChatBox
}
}
@@ -1191,7 +1191,7 @@ namespace Barotrauma.Items.Components
//trying to dock/undock from an outpost and the signal was sent by some automated system instead of a character
// -> ask if the player really wants to dock/undock to prevent a softlock if someone's wired the docking port
// in a way that makes always makes it dock/undock immediately at the start of the roun
if (tryingToToggleOutpostDocking && signal.sender == null)
if (GameMain.NetworkMember != null && tryingToToggleOutpostDocking && signal.sender == null)
{
if (allowOutpostAutoDocking == AllowOutpostAutoDocking.Ask)
{
@@ -132,8 +132,8 @@ namespace Barotrauma.Items.Components
public static FoliageConfig CreateRandomConfig(int maxVariants, float minScale, float maxScale, Random? random = null)
{
int flowerVariant = Growable.RandomInt(0, maxVariants, random);
float flowerScale = (float) Growable.RandomDouble(minScale, maxScale, random);
float flowerRotation = (float) Growable.RandomDouble(0, MathHelper.TwoPi, random);
float flowerScale = (float)Growable.RandomDouble(minScale, maxScale, random);
float flowerRotation = (float)Growable.RandomDouble(0, MathHelper.TwoPi, random);
return new FoliageConfig { Variant = flowerVariant, Scale = flowerScale, Rotation = flowerRotation };
}
}
@@ -169,10 +169,10 @@ namespace Barotrauma.Items.Components
{
const float limit = 1.0f;
growthStep = value;
VineStep = Math.Min((float) Math.Pow(value, 2), limit);
VineStep = Math.Min((float)Math.Pow(value, 2), limit);
if (value > limit)
{
FlowerStep = Math.Min((float) Math.Pow(value - limit, 2), limit);
FlowerStep = Math.Min((float)Math.Pow(value - limit, 2), limit);
}
}
}
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
{
if (Type == VineTileType.Stem) { return; }
Type = (VineTileType) Sides;
Type = (VineTileType)Sides;
}
/// <summary>
@@ -310,7 +310,7 @@ namespace Barotrauma.Items.Components
public bool IsSideBlocked(TileSide side) => BlockedSides.HasFlag(side) || Sides.HasFlag(side);
public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int) pos.X - Size / 2, (int) pos.Y + Size / 2, Size, Size);
public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int)pos.X - Size / 2, (int)pos.Y + Size / 2, Size, Size);
}
internal static class GrowthSideExtension
@@ -318,7 +318,7 @@ namespace Barotrauma.Items.Components
// K&R algorithm for counting how many bits are set in a bit field
public static int Count(this TileSide side)
{
int n = (int) side;
int n = (int)side;
int count = 0;
while (n != 0)
{
@@ -607,7 +607,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
foreach (VineTile vine in Vines)
{
vine.DecayDelay = (float) RandomDouble(0f, 30f);
vine.DecayDelay = (float)RandomDouble(0f, 30f);
}
#endif
#if SERVER
@@ -742,7 +742,7 @@ namespace Barotrauma.Items.Components
{
var (x, y, z, w) = GrowthWeights;
float[] weights = { x, y, z, w };
int index = (int) Math.Log2((int) side);
int index = (int)Math.Log2((int)side);
if (MathUtils.NearlyEqual(weights[index], 0f))
{
oldVines.FailedGrowthAttempts++;
@@ -778,7 +778,7 @@ namespace Barotrauma.Items.Components
foreach (VineTile otherVine in Vines)
{
var (distX, distY) = pos - otherVine.Position;
int absDistX = (int) Math.Abs(distX), absDistY = (int) Math.Abs(distY);
int absDistX = (int)Math.Abs(distX), absDistY = (int)Math.Abs(distY);
// check if the tile is within the with or height distance from us but ignore diagonals
if (absDistX > newVine.Rect.Width || absDistY > newVine.Rect.Height || absDistX > 0 && absDistY > 0) { continue; }
@@ -872,10 +872,10 @@ namespace Barotrauma.Items.Components
foreach (VineTile vine in Vines)
{
XElement vineElement = new XElement("Vine");
vineElement.Add(new XAttribute("sides", (int) vine.Sides));
vineElement.Add(new XAttribute("blockedsides", (int) vine.BlockedSides));
vineElement.Add(new XAttribute("sides", (int)vine.Sides));
vineElement.Add(new XAttribute("blockedsides", (int)vine.BlockedSides));
vineElement.Add(new XAttribute("pos", XMLExtensions.Vector2ToString(vine.Position)));
vineElement.Add(new XAttribute("tile", (int) vine.Type));
vineElement.Add(new XAttribute("tile", (int)vine.Type));
vineElement.Add(new XAttribute("failedattempts", vine.FailedGrowthAttempts));
#if SERVER
vineElement.Add(new XAttribute("growthscale", Decayed ? 1.0f : 2.0f));
@@ -902,10 +902,10 @@ namespace Barotrauma.Items.Components
{
if (element.Name.ToString().Equals("vine", StringComparison.OrdinalIgnoreCase))
{
VineTileType type = (VineTileType) element.GetAttributeInt("tile", 0);
VineTileType type = (VineTileType)element.GetAttributeInt("tile", 0);
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
TileSide sides = (TileSide) element.GetAttributeInt("sides", 0);
TileSide blockedSides = (TileSide) element.GetAttributeInt("blockedsides", 0);
TileSide sides = (TileSide)element.GetAttributeInt("sides", 0);
TileSide blockedSides = (TileSide)element.GetAttributeInt("blockedsides", 0);
int failedAttempts = element.GetAttributeInt("failedattempts", 0);
float growthscale = element.GetAttributeFloat("growthscale", 0f);
int flowerConfig = element.GetAttributeInt("flowerconfig", FoliageConfig.EmptyConfigValue);
@@ -4,6 +4,7 @@ using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma.Items.Components
@@ -62,7 +63,7 @@ namespace Barotrauma.Items.Components
/// <summary>
/// Defines items that boost the weapon functionality, like battery cell for stun batons.
/// </summary>
public readonly Identifier[] PreferredContainedItems;
public readonly ImmutableHashSet<Identifier> PreferredContainedItems;
public MeleeWeapon(Item item, ContentXElement element)
: base(item, element)
@@ -77,7 +78,7 @@ namespace Barotrauma.Items.Components
}
item.IsShootable = true;
item.RequireAimToUse = element.Parent.GetAttributeBool("requireaimtouse", true);
PreferredContainedItems = element.GetAttributeIdentifierArray("preferredcontaineditems", Array.Empty<Identifier>());
PreferredContainedItems = element.GetAttributeIdentifierArray("preferredcontaineditems", Array.Empty<Identifier>()).ToImmutableHashSet();
}
public override void Equip(Character character)
@@ -387,14 +388,16 @@ namespace Barotrauma.Items.Components
User = null;
return;
}
float damageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
damageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
Limb targetLimb = target.UserData as Limb;
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
if (Attack != null)
{
Attack.SetUser(User);
Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
Attack.DamageMultiplier = damageMultiplier;
if (targetLimb != null)
{
@@ -467,7 +470,7 @@ namespace Barotrauma.Items.Components
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
{
ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User);
ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User, afflictionMultiplier: damageMultiplier);
}
if (DeleteOnUse)
@@ -287,7 +287,7 @@ namespace Barotrauma.Items.Components
public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(activePicker?.ID ?? (ushort)0);
msg.WriteUInt16(activePicker?.ID ?? (ushort)0);
}
public virtual void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -229,8 +229,6 @@ namespace Barotrauma.Items.Components
set;
}
public virtual bool RecreateGUIOnResolutionChange => false;
/// <summary>
/// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).
/// </summary>
@@ -397,7 +395,7 @@ namespace Barotrauma.Items.Components
RelatedItem ri = RelatedItem.Load(element, returnEmpty, item.Name);
if (ri != null)
{
if (ri.Identifiers.Length == 0)
if (ri.Identifiers.Count == 0)
{
DisabledRequiredItems.Add(ri);
}
@@ -816,7 +814,7 @@ namespace Barotrauma.Items.Components
}
}
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float applyOnUserFraction = 0.0f)
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float afflictionMultiplier = 1.0f, float applyOnUserFraction = 0.0f)
{
if (statusEffectLists == null) { return; }
@@ -828,13 +826,14 @@ namespace Barotrauma.Items.Components
{
if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; }
if (user != null) { effect.SetUser(user); }
effect.AfflictionMultiplier = afflictionMultiplier;
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
if (user != null && applyOnUserFraction > 0.0f && effect.HasTargetType(StatusEffect.TargetType.Character))
{
effect.AfflictionMultiplier = applyOnUserFraction;
item.ApplyStatusEffect(effect, type, deltaTime, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), useTarget, false, false, worldPosition);
effect.AfflictionMultiplier = 1.0f;
}
effect.AfflictionMultiplier = 1.0f;
reducesCondition |= effect.ReducesItemCondition();
}
//if any of the effects reduce the item's condition, set the user for OnBroken effects as well
@@ -1070,7 +1069,7 @@ namespace Barotrauma.Items.Components
AIObjectiveContainItem containObjective = null;
if (character.AIController is HumanAIController aiController)
{
containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers, container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
{
ItemCount = itemCount,
Equip = equip,
@@ -216,9 +216,7 @@ namespace Barotrauma.Items.Components
}
private ImmutableHashSet<Identifier> containableItemIdentifiers;
public IEnumerable<Identifier> ContainableItemIdentifiers => containableItemIdentifiers;
public override bool RecreateGUIOnResolutionChange => true;
public ImmutableHashSet<Identifier> ContainableItemIdentifiers => containableItemIdentifiers;
public List<RelatedItem> ContainableItems { get; }
@@ -40,8 +40,6 @@ namespace Barotrauma.Items.Components
[Editable, Serialize(1.0f, IsPropertySaveable.Yes)]
public float DeconstructionSpeed { get; set; }
public override bool RecreateGUIOnResolutionChange => true;
public Deconstructor(Item item, ContentXElement element)
: base(item, element)
{
@@ -122,7 +120,7 @@ namespace Barotrauma.Items.Components
{
if ((Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false) || !inputContainer.Inventory.AllItems.Contains(targetItem)) { continue; }
var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it =>
(it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier == r)) &&
it.IsValidDeconstructor(item) &&
(it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it != targetItem && (it.HasTag(r) || it.Prefab.Identifier == r))))).ToList();
ProcessItem(targetItem, items, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any());
@@ -140,9 +138,7 @@ namespace Barotrauma.Items.Components
var targetItem = inputContainer.Inventory.LastOrDefault();
if (targetItem == null) { return; }
var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it =>
it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier == r)).ToList();
var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => it.IsValidDeconstructor(item)).ToList();
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
@@ -76,8 +76,6 @@ namespace Barotrauma.Items.Components
get { return outputContainer; }
}
public override bool RecreateGUIOnResolutionChange => true;
private float progressState;
private readonly Dictionary<uint, int> fabricationLimits = new Dictionary<uint, int>();
@@ -647,10 +645,13 @@ namespace Barotrauma.Items.Components
if (skills.Length == 0) { return 1.0f; }
if (character == null) { return 0.0f; }
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
float average = skillSum / skills.Length;
return (average + 100.0f) / 2.0f / 100.0f;
float minDegreeOfSuccess = 1.0f;
foreach (var skill in skills)
{
float characterLevel = character.GetSkillLevel(skill.Identifier);
minDegreeOfSuccess = Math.Min(minDegreeOfSuccess, (characterLevel - (skill.Level * SkillRequirementMultiplier) + 100.0f) / 2.0f / 100.0f);
}
return minDegreeOfSuccess;
}
public override float GetSkillMultiplier()
@@ -658,13 +659,16 @@ namespace Barotrauma.Items.Components
return SkillRequirementMultiplier;
}
private readonly HashSet<Inventory> linkedInventories = new HashSet<Inventory>();
private void RefreshAvailableIngredients()
{
Character user = this.user;
#if CLIENT
user ??= Character.Controlled;
#endif
linkedInventories.Clear();
List<Item> itemList = new List<Item>();
itemList.AddRange(inputContainer.Inventory.AllItems);
foreach (MapEntity linkedTo in item.linkedTo)
@@ -684,6 +688,7 @@ namespace Barotrauma.Items.Components
itemContainer = deconstructor.OutputContainer;
}
linkedInventories.Add(itemContainer.Inventory);
itemList.AddRange(itemContainer.Inventory.AllItems);
}
}
@@ -698,6 +703,7 @@ namespace Barotrauma.Items.Components
if (user?.Inventory != null)
{
itemList.AddRange(user.Inventory.AllItems);
linkedInventories.Add(user.Inventory);
}
availableIngredients.Clear();
foreach (Item item in itemList)
@@ -48,6 +48,9 @@ namespace Barotrauma.Items.Components
private Vector2 optimalFissionRate, allowedFissionRate;
private Vector2 optimalTurbineOutput, allowedTurbineOutput;
private float? signalControlledTargetFissionRate, signalControlledTargetTurbineOutput;
private double lastReceivedFissionRateSignalTime, lastReceivedTurbineOutputSignalTime;
private float temperatureBoost;
private bool _powerOn;
@@ -245,7 +248,7 @@ namespace Barotrauma.Items.Components
}
#endif
if (signalControlledTargetFissionRate.HasValue && Math.Abs(signalControlledTargetFissionRate.Value - TargetFissionRate) > 1.0f)
if (signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1)
{
TargetFissionRate = adjustValueWithoutOverShooting(TargetFissionRate, signalControlledTargetFissionRate.Value, deltaTime * 5.0f);
#if CLIENT
@@ -256,7 +259,7 @@ namespace Barotrauma.Items.Components
{
signalControlledTargetFissionRate = null;
}
if (signalControlledTargetTurbineOutput.HasValue && Math.Abs(signalControlledTargetTurbineOutput.Value - TargetTurbineOutput) > 1.0f)
if (signalControlledTargetTurbineOutput.HasValue && lastReceivedTurbineOutputSignalTime > Timing.TotalTime - 1)
{
TargetTurbineOutput = adjustValueWithoutOverShooting(TargetTurbineOutput, signalControlledTargetTurbineOutput.Value, deltaTime * 5.0f);
#if CLIENT
@@ -841,6 +844,7 @@ namespace Barotrauma.Items.Components
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
{
signalControlledTargetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
lastReceivedFissionRateSignalTime = Timing.TotalTime;
registerUnsentChanges();
}
break;
@@ -848,6 +852,7 @@ namespace Barotrauma.Items.Components
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
{
signalControlledTargetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
lastReceivedTurbineOutputSignalTime = Timing.TotalTime;
registerUnsentChanges();
}
break;
@@ -858,7 +863,5 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember is { IsServer: true }) { unsentChanges = true; }
}
}
private float? signalControlledTargetFissionRate, signalControlledTargetTurbineOutput;
}
}
@@ -113,9 +113,23 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(false, IsPropertySaveable.No, description: "Does the sonar have mineral scanning mode. " +
"Only available in-game when the Item has no Steering component.")]
public bool HasMineralScanner { get; set; }
private bool hasMineralScanner;
[Editable, Serialize(false, IsPropertySaveable.No, description: "Does the sonar have mineral scanning mode. ")]
public bool HasMineralScanner
{
get => hasMineralScanner;
set
{
#if CLIENT
if (controlContainer != null && !hasMineralScanner && value)
{
AddMineralScannerSwitchToGUI();
}
#endif
hasMineralScanner = value;
}
}
public float Zoom
{
@@ -144,8 +158,6 @@ namespace Barotrauma.Items.Components
}
}
public override bool RecreateGUIOnResolutionChange => true;
public Sonar(Item item, ContentXElement element)
: base(item, element)
{
@@ -396,17 +408,17 @@ namespace Barotrauma.Items.Components
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(currentMode == Mode.Active);
msg.WriteBoolean(currentMode == Mode.Active);
if (currentMode == Mode.Active)
{
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
msg.Write(useDirectionalPing);
msg.WriteBoolean(useDirectionalPing);
if (useDirectionalPing)
{
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
}
msg.Write(useMineralScanner);
msg.WriteBoolean(useMineralScanner);
}
}
}
@@ -166,8 +166,6 @@ namespace Barotrauma.Items.Components
set { posToMaintain = value; }
}
public override bool RecreateGUIOnResolutionChange => true;
struct ObstacleDebugInfo
{
public Vector2 Point1;
@@ -401,14 +401,14 @@ namespace Barotrauma.Items.Components
msg.WriteVariableUInt32((uint)connection.Wires.Count);
foreach (Wire wire in connection.Wires)
{
msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID);
msg.WriteUInt16(wire?.Item == null ? (ushort)0 : wire.Item.ID);
}
}
msg.Write((ushort)DisconnectedWires.Count);
msg.WriteUInt16((ushort)DisconnectedWires.Count);
foreach (Wire disconnectedWire in DisconnectedWires)
{
msg.Write(disconnectedWire.Item.ID);
msg.WriteUInt16(disconnectedWire.Item.ID);
}
}
}
@@ -155,7 +155,7 @@ namespace Barotrauma.Items.Components
{
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
//kind of hacky, we should probably add support for (string) arrays to SerializableEntityEditor so this wouldn't be needed
get { return signals == null ? "" : string.Join(";", signals); }
get { return signals == null ? string.Empty : string.Join(";", signals); }
set
{
if (value == null) { return; }
@@ -167,7 +167,31 @@ namespace Barotrauma.Items.Components
}
}
public override bool RecreateGUIOnResolutionChange => true;
private bool[] elementStates;
[Serialize("", IsPropertySaveable.Yes, description: "", alwaysUseInstanceValues: true)]
public string ElementStates
{
get { return elementStates == null ? string.Empty : string.Join(",", elementStates); }
set
{
if (value == null) { return; }
if (customInterfaceElementList.Count > 0)
{
string[] splitValues = value == "" ? Array.Empty<string>() : value.Split(',');
for (int i = 0; i < customInterfaceElementList.Count && i < splitValues.Length; i++)
{
if (!bool.TryParse(splitValues[i], out bool val)) { continue; }
customInterfaceElementList[i].State = val;
#if CLIENT
if (uiElements != null && i < uiElements.Count && uiElements[i] is GUITickBox tickBox)
{
tickBox.Selected = val;
}
#endif
}
}
}
}
private readonly List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
@@ -207,8 +231,10 @@ namespace Barotrauma.Items.Components
}
IsActive = true;
InitProjSpecific();
//load these here to ensure the UI elements (created in InitProjSpecific) are up-to-date
Labels = element.GetAttributeString("labels", "");
Signals = element.GetAttributeString("signals", "");
ElementStates = element.GetAttributeString("elementstates", "");
}
private void UpdateLabels(string[] newLabels)
@@ -386,6 +412,7 @@ namespace Barotrauma.Items.Components
{
labels = customInterfaceElementList.Select(ci => ci.Label).ToArray();
signals = customInterfaceElementList.Select(ci => ci.Signal).ToArray();
elementStates = customInterfaceElementList.Select(ci => ci.State).ToArray();
return base.Save(parentElement);
}
@@ -253,7 +253,13 @@ namespace Barotrauma.Items.Components
public void CheckIfNeedsUpdate()
{
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null && IsOn &&
if (!IsOn)
{
base.IsActive = false;
return;
}
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null &&
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
{
@@ -268,7 +274,7 @@ namespace Barotrauma.Items.Components
}
else
{
IsActive = true;
base.IsActive = true;
}
}
@@ -374,7 +374,7 @@ namespace Barotrauma.Items.Components
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(isOn);
msg.WriteBoolean(isOn);
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -7,6 +7,7 @@ using System.Globalization;
using System.Linq;
using Barotrauma.Extensions;
using FarseerPhysics.Dynamics;
using System.Collections.Immutable;
namespace Barotrauma.Items.Components
{
@@ -1151,7 +1152,7 @@ namespace Barotrauma.Items.Components
if (objective.SubObjectives.None())
{
var loadItemsObjective = AIContainItems<Turret>(container, character, objective, usableProjectileCount + 1, equip: true, removeEmpty: true, dropItemOnDeselected: true);
loadItemsObjective.ignoredContainerIdentifiers = new Identifier[] { ((MapEntity)containerItem).Prefab.Identifier };
loadItemsObjective.ignoredContainerIdentifiers = ((MapEntity)containerItem).Prefab.Identifier.ToEnumerable().ToImmutableHashSet();
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: FormatCapitals.Yes).Value,
@@ -1340,7 +1341,7 @@ namespace Barotrauma.Items.Components
{
if (character.AIController.SelectedAiTarget == null && !hadCurrentTarget)
{
if (CreatureMetrics.Instance.RecentlyEncountered.Contains(closestEnemy.SpeciesName))
if (CreatureMetrics.Instance.RecentlyEncountered.Contains(closestEnemy.SpeciesName) || closestEnemy.IsHuman)
{
character.Speak(TextManager.Get("DialogNewTargetSpotted").Value,
identifier: "newtargetspotted".ToIdentifier(),
@@ -1611,6 +1612,7 @@ namespace Barotrauma.Items.Components
targetRotation = rotation = (minRotation + maxRotation) / 2;
UpdateTransformedBarrelPos();
UpdateLightComponents();
}
public override void FlipY(bool relativeToSub)
@@ -1632,6 +1634,7 @@ namespace Barotrauma.Items.Components
targetRotation = rotation = (minRotation + maxRotation) / 2;
UpdateTransformedBarrelPos();
UpdateLightComponents();
}
public override void ReceiveSignal(Signal signal, Connection connection)
@@ -1714,12 +1717,12 @@ namespace Barotrauma.Items.Components
{
if (TryExtractEventData(extraData, out EventData eventData))
{
msg.Write(eventData.Projectile.ID);
msg.WriteUInt16(eventData.Projectile.ID);
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
}
else
{
msg.Write((ushort)0);
msg.WriteUInt16((ushort)0);
float wrappedTargetRotation = targetRotation;
while (wrappedTargetRotation < minRotation && MathUtils.IsValid(wrappedTargetRotation))
{
@@ -560,7 +560,7 @@ namespace Barotrauma.Items.Components
}
public override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write((byte)Variant);
msg.WriteByte((byte)Variant);
base.ServerEventWrite(msg, c, extraData);
}
@@ -959,14 +959,14 @@ namespace Barotrauma
public void SharedWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.Write((byte)capacity);
msg.WriteByte((byte)capacity);
for (int i = 0; i < capacity; i++)
{
msg.WriteRangedInteger(slots[i].Items.Count, 0, MaxStackSize);
for (int j = 0; j < Math.Min(slots[i].Items.Count, MaxStackSize); j++)
{
var item = slots[i].Items[j];
msg.Write(item?.ID ?? (ushort)0);
msg.WriteUInt16(item?.ID ?? (ushort)0);
}
}
}
@@ -1536,7 +1536,7 @@ namespace Barotrauma
return false;
}
private bool ConditionalMatches(PropertyConditional conditional)
public bool ConditionalMatches(PropertyConditional conditional)
{
if (string.IsNullOrEmpty(conditional.TargetItemComponentName))
{
@@ -2861,77 +2861,77 @@ namespace Barotrauma
var propertyOwner = allProperties.Find(p => p.property == property);
if (allProperties.Count > 1)
{
msg.Write((byte)allProperties.FindIndex(p => p.property == property));
msg.WriteByte((byte)allProperties.FindIndex(p => p.property == property));
}
object value = property.GetValue(propertyOwner.obj);
if (value is string stringVal)
{
msg.Write(stringVal);
msg.WriteString(stringVal);
}
else if (value is Identifier idValue)
{
msg.Write(idValue);
msg.WriteIdentifier(idValue);
}
else if (value is float floatVal)
{
msg.Write(floatVal);
msg.WriteSingle(floatVal);
}
else if (value is int intVal)
{
msg.Write(intVal);
msg.WriteInt32(intVal);
}
else if (value is bool boolVal)
{
msg.Write(boolVal);
msg.WriteBoolean(boolVal);
}
else if (value is Color color)
{
msg.Write(color.R);
msg.Write(color.G);
msg.Write(color.B);
msg.Write(color.A);
msg.WriteByte(color.R);
msg.WriteByte(color.G);
msg.WriteByte(color.B);
msg.WriteByte(color.A);
}
else if (value is Vector2 vector2)
{
msg.Write(vector2.X);
msg.Write(vector2.Y);
msg.WriteSingle(vector2.X);
msg.WriteSingle(vector2.Y);
}
else if (value is Vector3 vector3)
{
msg.Write(vector3.X);
msg.Write(vector3.Y);
msg.Write(vector3.Z);
msg.WriteSingle(vector3.X);
msg.WriteSingle(vector3.Y);
msg.WriteSingle(vector3.Z);
}
else if (value is Vector4 vector4)
{
msg.Write(vector4.X);
msg.Write(vector4.Y);
msg.Write(vector4.Z);
msg.Write(vector4.W);
msg.WriteSingle(vector4.X);
msg.WriteSingle(vector4.Y);
msg.WriteSingle(vector4.Z);
msg.WriteSingle(vector4.W);
}
else if (value is Point point)
{
msg.Write(point.X);
msg.Write(point.Y);
msg.WriteInt32(point.X);
msg.WriteInt32(point.Y);
}
else if (value is Rectangle rect)
{
msg.Write(rect.X);
msg.Write(rect.Y);
msg.Write(rect.Width);
msg.Write(rect.Height);
msg.WriteInt32(rect.X);
msg.WriteInt32(rect.Y);
msg.WriteInt32(rect.Width);
msg.WriteInt32(rect.Height);
}
else if (value is Enum)
{
msg.Write((int)value);
msg.WriteInt32((int)value);
}
else if (value is string[] a)
{
msg.Write(a.Length);
msg.WriteInt32(a.Length);
for (int i = 0; i < a.Length; i++)
{
msg.Write(a[i] ?? "");
msg.WriteString(a[i] ?? "");
}
}
else
@@ -53,6 +53,11 @@ namespace Barotrauma
InfoText = element.GetAttributeString("infotext", string.Empty);
InfoTextOnOtherItemMissing = element.GetAttributeString("infotextonotheritemmissing", string.Empty);
}
public bool IsValidDeconstructor(Item deconstructor)
{
return RequiredDeconstructor.Length == 0 || RequiredDeconstructor.Any(r => deconstructor.HasTag(r) || deconstructor.Prefab.Identifier == r);
}
}
class FabricationRecipe
@@ -62,6 +67,10 @@ namespace Barotrauma
public abstract IEnumerable<ItemPrefab> ItemPrefabs { get; }
public abstract UInt32 UintIdentifier { get; }
public abstract bool MatchesItem(Item item);
public abstract ItemPrefab FirstMatchingPrefab { get; }
public RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition)
{
Amount = amount;
@@ -92,12 +101,21 @@ namespace Barotrauma
public class RequiredItemByIdentifier : RequiredItem
{
public readonly Identifier ItemPrefabIdentifier;
public ItemPrefab ItemPrefab => ItemPrefab.Prefabs.TryGet(ItemPrefabIdentifier, out var prefab) ? prefab
: MapEntityPrefab.FindByName(ItemPrefabIdentifier.Value) as ItemPrefab ?? throw new Exception($"No ItemPrefab with identifier or name \"{ItemPrefabIdentifier}\"");
public override UInt32 UintIdentifier { get; }
public override IEnumerable<ItemPrefab> ItemPrefabs => ItemPrefab.ToEnumerable();
public override ItemPrefab FirstMatchingPrefab => ItemPrefab;
public override bool MatchesItem(Item item)
{
return item?.Prefab.Identifier == ItemPrefabIdentifier;
}
public RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition) : base(amount, minCondition, maxCondition, useCondition)
{
ItemPrefabIdentifier = itemPrefab;
@@ -109,10 +127,19 @@ namespace Barotrauma
public class RequiredItemByTag : RequiredItem
{
public readonly Identifier Tag;
public override UInt32 UintIdentifier { get; }
public override IEnumerable<ItemPrefab> ItemPrefabs => ItemPrefab.Prefabs.Where(p => p.Tags.Contains(Tag));
public override ItemPrefab FirstMatchingPrefab => ItemPrefab.Prefabs.FirstOrDefault(p => p.Tags.Contains(Tag));
public override bool MatchesItem(Item item)
{
if (item == null) { return false; }
return item.HasTag(Tag);
}
public RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition) : base(amount, minCondition, maxCondition, useCondition)
{
Tag = tag;
@@ -208,10 +235,10 @@ namespace Barotrauma
if (requiredItemIdentifier != Identifier.Empty)
{
var existing = requiredItems.FindIndex(r =>
r is RequiredItemByIdentifier ri &&
ri.ItemPrefabIdentifier == requiredItemIdentifier &&
MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
r is RequiredItemByIdentifier ri &&
ri.ItemPrefabIdentifier == requiredItemIdentifier &&
MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
if (existing >= 0)
{
amount += requiredItems[existing].Amount;
@@ -222,10 +249,10 @@ namespace Barotrauma
else
{
var existing = requiredItems.FindIndex(r =>
r is RequiredItemByTag rt &&
rt.Tag == requiredItemTag &&
MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
r is RequiredItemByTag rt &&
rt.Tag == requiredItemTag &&
MathUtils.NearlyEqual(r.MinCondition, minCondition) &&
MathUtils.NearlyEqual(r.MaxCondition, maxCondition));
if (existing >= 0)
{
amount += requiredItems[existing].Amount;
@@ -783,19 +810,12 @@ namespace Barotrauma
//works the same as nameIdentifier, but just replaces the description
Identifier descriptionIdentifier = ConfigElement.GetAttributeIdentifier("descriptionidentifier", "");
if (string.IsNullOrEmpty(OriginalName))
{
name = TextManager.Get(nameIdentifier.IsEmpty
? $"EntityName.{Identifier}"
: $"EntityName.{nameIdentifier}",
$"EntityName.{fallbackNameIdentifier}");
}
else if (Category.HasFlag(MapEntityCategory.Legacy))
{
// Legacy items use names as identifiers, so we have to define them in the xml. But we also want to support the translations. Therefore
name = TextManager.Get(nameIdentifier.IsEmpty
name = TextManager.Get(nameIdentifier.IsEmpty
? $"EntityName.{Identifier}"
: $"EntityName.{nameIdentifier}");
: $"EntityName.{nameIdentifier}",
$"EntityName.{fallbackNameIdentifier}");
if (!string.IsNullOrEmpty(OriginalName))
{
name = name.Fallback(OriginalName);
}
@@ -838,20 +858,17 @@ namespace Barotrauma
SerializableProperty.DeserializeProperties(this, ConfigElement);
if (Description.IsNullOrEmpty())
if (descriptionIdentifier != Identifier.Empty)
{
if (descriptionIdentifier != Identifier.Empty)
{
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}");
}
else if (nameIdentifier == Identifier.Empty)
{
Description = TextManager.Get($"EntityDescription.{Identifier}");
}
else
{
Description = TextManager.Get($"EntityDescription.{nameIdentifier}");
}
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}").Fallback(Description);
}
else if (nameIdentifier == Identifier.Empty)
{
Description = TextManager.Get($"EntityDescription.{Identifier}").Fallback(Description);
}
else
{
Description = TextManager.Get($"EntityDescription.{nameIdentifier}").Fallback(Description);
}
var allowDroppingOnSwapWith = ConfigElement.GetAttributeIdentifierArray("allowdroppingonswapwith", Array.Empty<Identifier>());
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
@@ -22,7 +23,7 @@ namespace Barotrauma
public bool IgnoreInEditor { get; set; }
private Identifier[] excludedIdentifiers;
private ImmutableHashSet<Identifier> excludedIdentifiers;
private RelationType type;
@@ -60,11 +61,11 @@ namespace Barotrauma
{
if (value == null) return;
Identifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToArray();
Identifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToImmutableHashSet();
}
}
public Identifier[] Identifiers { get; private set; }
public ImmutableHashSet<Identifier> Identifiers { get; private set; }
public string JoinedExcludedIdentifiers
{
@@ -73,27 +74,53 @@ namespace Barotrauma
{
if (value == null) return;
excludedIdentifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToArray();
excludedIdentifiers = value.Split(',').Select(s => s.Trim()).ToIdentifiers().ToImmutableHashSet();
}
}
public bool MatchesItem(Item item)
{
if (item == null) { return false; }
if (excludedIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { return false; }
return Identifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && item.Prefab.VariantOf == id));
if (excludedIdentifiers.Contains(item.Prefab.Identifier)) { return false; }
foreach (var excludedIdentifier in excludedIdentifiers)
{
if (item.HasTag(excludedIdentifier)) { return false; }
}
if (Identifiers.Contains(item.Prefab.Identifier)) { return true; }
foreach (var identifier in Identifiers)
{
if (item.HasTag(identifier)) { return true; }
}
if (AllowVariants && !item.Prefab.VariantOf.IsEmpty)
{
if (Identifiers.Contains(item.Prefab.VariantOf)) { return true; }
}
return false;
}
public bool MatchesItem(ItemPrefab itemPrefab)
{
if (itemPrefab == null) { return false; }
if (excludedIdentifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id))) { return false; }
return Identifiers.Any(id => itemPrefab.Identifier == id || itemPrefab.Tags.Contains(id) || (AllowVariants && !itemPrefab.VariantOf.IsEmpty && itemPrefab.VariantOf == id));
if (excludedIdentifiers.Contains(itemPrefab.Identifier)) { return false; }
foreach (var excludedIdentifier in excludedIdentifiers)
{
if (itemPrefab.Tags.Contains(excludedIdentifier)) { return false; }
}
if (Identifiers.Contains(itemPrefab.Identifier)) { return true; }
foreach (var identifier in Identifiers)
{
if (itemPrefab.Tags.Contains(identifier)) { return true; }
}
if (AllowVariants && !itemPrefab.VariantOf.IsEmpty)
{
if (Identifiers.Contains(itemPrefab.VariantOf)) { return true; }
}
return false;
}
public RelatedItem(Identifier[] identifiers, Identifier[] excludedIdentifiers)
{
this.Identifiers = identifiers.Select(id => id.Value.Trim().ToIdentifier()).ToArray();
this.excludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToArray();
this.Identifiers = identifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
this.excludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
statusEffects = new List<StatusEffect>();
}
@@ -161,7 +188,7 @@ namespace Barotrauma
new XAttribute("targetslot", TargetSlot),
new XAttribute("allowvariants", AllowVariants));
if (excludedIdentifiers.Length > 0)
if (excludedIdentifiers.Count > 0)
{
element.Add(new XAttribute("excludedidentifiers", JoinedExcludedIdentifiers));
}
@@ -25,6 +25,12 @@ namespace Barotrauma
private set;
}
/// <summary>
/// "Diagonal" gaps are used on sloped walls to allow characters to pass through them either horizontally or vertically.
/// Water still flows through them only horizontally or vertically
/// </summary>
public bool IsDiagonal { get; }
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
private float open;
@@ -135,12 +141,13 @@ namespace Barotrauma
: this(rect, rect.Width < rect.Height, submarine)
{ }
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, ushort id = Entity.NullEntityID)
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, bool isDiagonal = false, ushort id = Entity.NullEntityID)
: base(CoreEntityPrefab.GapPrefab, submarine, id)
{
this.rect = rect;
flowForce = Vector2.Zero;
IsHorizontal = isHorizontal;
IsDiagonal = isDiagonal;
open = 1.0f;
FindHulls();
@@ -666,15 +673,15 @@ namespace Barotrauma
{
foreach (Gap gap in gaps)
{
if (gap.Open == 0.0f || gap.IsRoomToRoom) continue;
if (gap.Open == 0.0f || gap.IsRoomToRoom) { continue; }
if (gap.ConnectedWall != null)
{
int sectionIndex = gap.ConnectedWall.FindSectionIndex(gap.Position);
if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) continue;
if (sectionIndex > -1 && !gap.ConnectedWall.SectionBodyDisabled(sectionIndex)) { continue; }
}
if (gap.IsHorizontal)
if (gap.IsHorizontal || gap.IsDiagonal)
{
if (worldPos.Y < gap.WorldRect.Y && worldPos.Y > gap.WorldRect.Y - gap.WorldRect.Height &&
Math.Abs(gap.WorldRect.Center.X - worldPos.X) < allowedOrthogonalDist)
@@ -682,7 +689,7 @@ namespace Barotrauma
return gap;
}
}
else
if (!gap.IsHorizontal || gap.IsDiagonal)
{
if (worldPos.X > gap.WorldRect.X && worldPos.X < gap.WorldRect.Right &&
Math.Abs(gap.WorldRect.Y - gap.WorldRect.Height / 2 - worldPos.Y) < allowedOrthogonalDist)
@@ -754,7 +761,7 @@ namespace Barotrauma
isHorizontal = horizontalAttribute.Value.ToString() == "true";
}
Gap g = new Gap(rect, isHorizontal, submarine, idRemap.GetOffsetId(element))
Gap g = new Gap(rect, isHorizontal, submarine, id: idRemap.GetOffsetId(element))
{
linkedToID = new List<ushort>(),
};
@@ -759,7 +759,7 @@ namespace Barotrauma
for (int i = start; i < end; i++)
{
msg.WriteRangedSingle(BackgroundSections[i].ColorStrength, 0.0f, 1.0f, 8);
msg.Write(BackgroundSections[i].Color.PackedValue);
msg.WriteUInt32(BackgroundSections[i].Color.PackedValue);
}
}
#endregion
@@ -994,7 +994,11 @@ namespace Barotrauma
foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0))
{
var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position) / 1000, 1f);
item.body.ApplyForce((gap.LerpedFlowForce / distance) * deltaTime);
Vector2 force = (gap.LerpedFlowForce / distance) * deltaTime;
if (force.LengthSquared() > 0.01f)
{
item.body.ApplyForce(force);
}
}
}
@@ -1545,7 +1549,7 @@ namespace Barotrauma
var hull = new Hull(rect, submarine, idRemap.GetOffsetId(element))
{
WaterVolume = element.GetAttributeFloat("pressure", 0.0f)
WaterVolume = element.GetAttributeFloat("water", 0.0f)
};
hull.linkedToID = new List<ushort>();
@@ -1972,7 +1972,7 @@ namespace Barotrauma
List<Tunnel> caveBranches = new List<Tunnel>();
var tunnel = new Tunnel(TunnelType.Cave, SegmentsToNodes(caveSegments), 100, parentTunnel);
var tunnel = new Tunnel(TunnelType.Cave, SegmentsToNodes(caveSegments), 150, parentTunnel);
Tunnels.Add(tunnel);
caveBranches.Add(tunnel);
@@ -1989,7 +1989,7 @@ namespace Barotrauma
bounds: caveArea);
if (!branchSegments.Any()) { continue; }
var branch = new Tunnel(TunnelType.Cave, SegmentsToNodes(branchSegments), 0, parentBranch);
var branch = new Tunnel(TunnelType.Cave, SegmentsToNodes(branchSegments), 150, parentBranch);
Tunnels.Add(branch);
caveBranches.Add(branch);
}
@@ -2569,34 +2569,38 @@ namespace Barotrauma
AbyssResources.Clear();
var abyssResourcePrefabs = levelResources.Where(r => r.commonnessInfo.AbyssCommonness > 0.0f);
int abyssClusterCount = (int)MathHelper.Lerp(GenerationParams.AbyssResourceClustersMin, GenerationParams.AbyssResourceClustersMax, MathUtils.InverseLerp(LevelData.Biome.MinDifficulty, LevelData.Biome.AdjustedMaxDifficulty, Difficulty));
for (int i = 0; i < abyssClusterCount; i++)
if (abyssResourcePrefabs.Any())
{
var selectedPrefab = ToolBox.SelectWeightedRandom(
abyssResourcePrefabs.Select(r => r.itemPrefab).ToList(),
abyssResourcePrefabs.Select(r => r.commonnessInfo.AbyssCommonness).ToList(),
Rand.RandSync.ServerAndClient);
var location = allValidLocations.GetRandom(l =>
int abyssClusterCount = (int)MathHelper.Lerp(GenerationParams.AbyssResourceClustersMin, GenerationParams.AbyssResourceClustersMax, MathUtils.InverseLerp(LevelData.Biome.MinDifficulty, LevelData.Biome.AdjustedMaxDifficulty, Difficulty));
for (int i = 0; i < abyssClusterCount; i++)
{
if (l.Cell == null || l.Edge == null) { return false; }
if (l.EdgeCenter.Y > AbyssArea.Bottom) { return false; }
l.InitializeResources();
return l.Resources.Count <= GetMaxResourcesOnEdge(selectedPrefab, l, out _);
}, randSync: Rand.RandSync.ServerAndClient);
var selectedPrefab = ToolBox.SelectWeightedRandom(
abyssResourcePrefabs.Select(r => r.itemPrefab).ToList(),
abyssResourcePrefabs.Select(r => r.commonnessInfo.AbyssCommonness).ToList(),
Rand.RandSync.ServerAndClient);
if (location.Cell == null || location.Edge == null) { break; }
var location = allValidLocations.GetRandom(l =>
{
if (l.Cell == null || l.Edge == null) { return false; }
if (l.EdgeCenter.Y > AbyssArea.Bottom) { return false; }
l.InitializeResources();
return l.Resources.Count <= GetMaxResourcesOnEdge(selectedPrefab, l, out _);
}, randSync: Rand.RandSync.ServerAndClient);
int clusterSize = Rand.Range(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y + 1, Rand.RandSync.ServerAndClient);
PlaceResources(selectedPrefab, clusterSize, location, out var placedResources, maxResourceOverlap: 0);
var abyssClusterLocation = new ClusterLocation(location.Cell, location.Edge, initializeResourceList: true);
abyssClusterLocation.Resources.AddRange(placedResources);
AbyssResources.Add(abyssClusterLocation);
if (location.Cell == null || location.Edge == null) { break; }
var locationIndex = allValidLocations.FindIndex(l => l.Equals(location));
allValidLocations.RemoveAt(locationIndex);
int clusterSize = Rand.Range(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y + 1, Rand.RandSync.ServerAndClient);
PlaceResources(selectedPrefab, clusterSize, location, out var placedResources, maxResourceOverlap: 0);
var abyssClusterLocation = new ClusterLocation(location.Cell, location.Edge, initializeResourceList: true);
abyssClusterLocation.Resources.AddRange(placedResources);
AbyssResources.Add(abyssClusterLocation);
var locationIndex = allValidLocations.FindIndex(l => l.Equals(location));
allValidLocations.RemoveAt(locationIndex);
}
}
PathPoints.Clear();
nextPathPointId = 0;
@@ -2928,50 +2932,74 @@ namespace Barotrauma
}
/// <param name="rotation">Used by clients to set the rotation for the resources</param>
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, out float rotation)
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, out float rotation)
{
var allValidLocations = GetAllValidClusterLocations();
var placedResources = new List<Item>();
rotation = 0.0f;
if (allValidLocations.None()) { return placedResources; } // TODO: WHAT?!
// Make sure not to pick a spot that already has other level resources
for (int i = allValidLocations.Count - 1; i >= 0; i--)
{
var location = allValidLocations[i];
var locationHasResources = PathPoints.Any(p =>
p.ClusterLocations.Any(c =>
c.Equals(location) &&
c.Resources.Any(r => r != null && !r.Removed &&
(!(r.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))));
if (locationHasResources)
if (HasResources(allValidLocations[i]))
{
allValidLocations.RemoveAt(i);
}
bool HasResources(ClusterLocation clusterLocation)
{
foreach (var p in PathPoints)
{
foreach (var c in p.ClusterLocations)
{
if (!c.Equals(clusterLocation)) { continue; }
foreach (var r in c.Resources)
{
if (r == null) { continue; }
if (r.Removed) { continue; }
if (!(r.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)) { return true; }
}
}
}
return false;
}
}
var positionType = PositionType.MainPath;
if (PositionsOfInterest.Any(p => p.PositionType == PositionType.Cave))
if (PositionsOfInterest.None(p => p.PositionType == positionType))
{
positionType = PositionType.Cave;
if (allValidLocations.Any(l => l.Edge.NextToCave))
foreach (var validType in MineralMission.ValidPositionTypes)
{
allValidLocations.RemoveAll(l => !l.Edge.NextToCave);
if (validType != positionType && PositionsOfInterest.Any(p => p.PositionType == validType))
{
positionType = validType;
break;
}
}
}
else if (PositionsOfInterest.Any(p => p.PositionType == PositionType.SidePath))
try
{
positionType = PositionType.SidePath;
if (allValidLocations.Any(l => l.Edge.NextToSidePath))
RemoveInvalidLocations(positionType switch
{
allValidLocations.RemoveAll(l => !l.Edge.NextToSidePath);
}
PositionType.MainPath => IsOnMainPath,
PositionType.SidePath => IsOnSidePath,
PositionType.Cave => IsInCave,
PositionType.AbyssCave => IsInAbyssCave,
_ => throw new NotImplementedException(),
});
}
catch (NotImplementedException)
{
DebugConsole.ThrowError($"Unexpected PositionType (\"{positionType}\") for mineral mission resources: mineral spawning might not work as expected.");
}
var poi = PositionsOfInterest.GetRandom(p => p.PositionType == positionType, randSync: Rand.RandSync.ServerAndClient);
var poiPos = poi.Position.ToVector2();
Vector2 poiPos = poi.Position.ToVector2();
allValidLocations.Sort((x, y) => Vector2.DistanceSquared(poiPos, x.EdgeCenter)
.CompareTo(Vector2.DistanceSquared(poiPos, y.EdgeCenter)));
var maxResourceOverlap = 0.4f;
float maxResourceOverlap = 0.4f;
var selectedLocation = allValidLocations.FirstOrDefault(l =>
Vector2.Distance(l.Edge.Point1, l.Edge.Point2) is float edgeLength &&
requiredAmount <= (int)Math.Floor(edgeLength / ((1.0f - maxResourceOverlap) * prefab.Size.X)));
@@ -2993,9 +3021,18 @@ namespace Barotrauma
throw new Exception("Failed to find a suitable level wall edge to place level resources on.");
}
PlaceResources(prefab, requiredAmount, selectedLocation, out placedResources);
var edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell);
Vector2 edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell);
rotation = MathHelper.ToDegrees(-MathUtils.VectorToAngle(edgeNormal) + MathHelper.PiOver2);
return placedResources;
static bool IsOnMainPath(ClusterLocation location) => location.Edge.NextToMainPath;
static bool IsOnSidePath(ClusterLocation location) => location.Edge.NextToSidePath;
static bool IsInCave(ClusterLocation location) => location.Edge.NextToCave;
bool IsInAbyssCave(ClusterLocation location) => location.EdgeCenter.Y > AbyssArea.Bottom;
void RemoveInvalidLocations(Predicate<ClusterLocation> match)
{
allValidLocations.RemoveAll(match);
}
}
private List<ClusterLocation> GetAllValidClusterLocations()
@@ -4015,17 +4052,21 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
}
float outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
//don't try to compensate if the port is very far from the outpost's center of mass
if (Math.Abs(outpostDockingPortOffset) > 5000.0f)
float? outpostDockingPortOffset = null;
if (outpostPort != null)
{
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
//don't try to compensate if the port is very far from the outpost's center of mass
if (Math.Abs(outpostDockingPortOffset.Value) > 5000.0f)
{
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset.Value, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
}
}
Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset, verticalMoveDir: 1);
Vector2 spawnPos = outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, outpostDockingPortOffset != null ? subDockingPortOffset - outpostDockingPortOffset.Value : 0.0f, verticalMoveDir: 1);
if (Type == LevelData.LevelType.Outpost)
{
spawnPos.Y = Math.Min(Size.Y - outpost.Borders.Height * 0.6f, spawnPos.Y + outpost.Borders.Height / 2);
@@ -357,7 +357,9 @@ namespace Barotrauma
float closestDistance = 0.0f;
foreach (DockingPort port in DockingPort.List)
{
if (port.Item.Submarine != sub || port.IsHorizontal != linkedPort.IsHorizontal) { continue; }
if (port.Item.Submarine != sub) { continue; }
if (port.IsHorizontal != linkedPort.IsHorizontal) { continue; }
if (port.ForceDockingDirection != DockingPort.DirectionType.None && port.ForceDockingDirection == linkedPort.ForceDockingDirection) { continue; }
float dist = Vector2.Distance(port.Item.WorldPosition, linkedPort.Item.WorldPosition);
if (myPort == null || dist < closestDistance)
{
@@ -453,22 +455,22 @@ namespace Barotrauma
saveElement.SetAttributeValue("pos", XMLExtensions.Vector2ToString(Position - Submarine.HiddenSubPosition));
if (linkedTo.Any() || linkedToID.Any())
{
var linkedPort =
linkedTo.FirstOrDefault(lt => (lt is Item item) && item.GetComponent<DockingPort>() != null) ??
FindEntityByID(linkedToID.First()) as MapEntity;
if (linkedPort != null)
{
saveElement.SetAttributeValue("linkedto", linkedPort.ID);
}
}
}
else
{
saveElement = new XElement("LinkedSubmarine");
sub.SaveToXElement(saveElement);
}
if (linkedTo.Any() || linkedToID.Any())
{
var linkedPort =
linkedTo.FirstOrDefault(lt => (lt is Item item) && item.GetComponent<DockingPort>() != null) ??
FindEntityByID(linkedToID.First()) as MapEntity;
if (linkedPort != null)
{
saveElement.SetAttributeValue("linkedto", linkedPort.ID);
}
}
saveElement.SetAttributeValue("originallinkedto", originalLinkedPort != null ? originalLinkedPort.Item.ID : originalLinkedToID);
saveElement.SetAttributeValue("originalmyport", originalMyPortID);
@@ -338,7 +338,7 @@ namespace Barotrauma
/// </summary>
public virtual bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false)
{
if (this is Item item && !upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(item, upgrade.Prefab)))
if (!upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(this, upgrade.Prefab)))
{
return false;
}
@@ -359,16 +359,6 @@ namespace Barotrauma
Upgrades.Add(upgrade);
}
// not used anymore
#if SERVER
// if (createNetworkEvent)
// {
// if (this is IServerSerializable serializable)
// {
// GameMain.Server.CreateEntityEvent(serializable, new object[] { NetEntityEvent.Type.Upgrade, upgrade });
// }
// }
#endif
return true;
}
@@ -168,6 +168,17 @@ namespace Barotrauma
public ImmutableHashSet<Identifier> Tags => Prefab.Tags;
#if DEBUG
[Editable, Serialize("", IsPropertySaveable.Yes)]
#else
[Serialize("", IsPropertySaveable.Yes)]
#endif
public string SpecialTag
{
get;
set;
}
protected Color spriteColor;
[Editable, Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes)]
public Color SpriteColor
@@ -574,6 +585,11 @@ namespace Barotrauma
int xsections = 1, ysections = 1;
int width = rect.Width, height = rect.Height;
WallSection[] prevSections = null;
if (Sections != null)
{
prevSections = Sections.ToArray();
}
if (!HasBody)
{
if (FlippedX && IsHorizontal)
@@ -657,6 +673,14 @@ namespace Barotrauma
}
}
}
if (prevSections != null && Sections.Length == prevSections.Length)
{
for (int i = 0; i < Sections.Length; i++)
{
Sections[i].damage = prevSections[i].damage;
}
}
}
private Rectangle GenerateMergedRect(List<WallSection> mergedSections)
@@ -829,27 +853,33 @@ namespace Barotrauma
public WallSection GetSection(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return null;
if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return null; }
return Sections[sectionIndex];
}
public bool SectionBodyDisabled(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false;
if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return false; }
return (Sections[sectionIndex].damage >= MaxHealth);
}
public bool AllSectionBodiesDisabled()
{
for (int i = 0; i < Sections.Length; i++)
{
if (Sections[i].damage < MaxHealth) { return false; }
}
return true;
}
/// <summary>
/// Sections that are leaking have a gap placed on them
/// </summary>
public bool SectionIsLeaking(int sectionIndex)
{
if (sectionIndex < 0 || sectionIndex >= Sections.Length) return false;
return (Sections[sectionIndex].damage >= MaxHealth * LeakThreshold);
if (sectionIndex < 0 || sectionIndex >= Sections.Length) { return false; }
return Sections[sectionIndex].damage >= MaxHealth * LeakThreshold;
}
public int SectionLength(int sectionIndex)
@@ -1139,21 +1169,22 @@ namespace Barotrauma
gapRect.Height += 20;
bool horizontalGap = !IsHorizontal;
bool diagonalGap = false;
if (Prefab.BodyRotation != 0.0f)
{
//rotation within a 90 deg sector (e.g. 100 -> 10, 190 -> 10, -10 -> 80)
float sectorizedRotation = MathUtils.WrapAngleTwoPi(BodyRotation) % MathHelper.PiOver2;
//diagonal if 30 < angle < 60
bool diagonal = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3;
diagonalGap = sectorizedRotation > MathHelper.Pi / 6 && sectorizedRotation < MathHelper.Pi / 3;
//gaps on the lower half of a diagonal wall are horizontal, ones on the upper half are vertical
if (diagonal)
if (diagonalGap)
{
horizontalGap = gapRect.Y - gapRect.Height / 2 < Position.Y;
if (FlippedY) { horizontalGap = !horizontalGap; }
}
}
Sections[sectionIndex].gap = new Gap(gapRect, horizontalGap, Submarine);
Sections[sectionIndex].gap = new Gap(gapRect, horizontalGap, Submarine, isDiagonal: diagonalGap);
//free the ID, because if we give gaps IDs we have to make sure they always match between the clients and the server and
//that clients create them in the correct order along with every other entity created/removed during the round
@@ -271,20 +271,17 @@ namespace Barotrauma
tags.Add("wall".ToIdentifier());
}
if (Description.IsNullOrEmpty())
if (!descriptionIdentifier.IsEmpty)
{
if (!descriptionIdentifier.IsEmpty)
{
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}");
}
else if (nameIdentifier.IsEmpty)
{
Description = TextManager.Get($"EntityDescription.{Identifier}");
}
else
{
Description = TextManager.Get($"EntityDescription.{nameIdentifier}");
}
Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}").Fallback(Description);
}
else if (nameIdentifier.IsEmpty)
{
Description = TextManager.Get($"EntityDescription.{Identifier}").Fallback(Description);
}
else
{
Description = TextManager.Get($"EntityDescription.{nameIdentifier}").Fallback(Description);
}
//backwards compatibility
@@ -187,7 +187,6 @@ namespace Barotrauma
if (structure.Submarine != this || !structure.HasBody || structure.Indestructible) { continue; }
realWorldCrushDepth = Math.Min(structure.CrushDepth, realWorldCrushDepth.Value);
}
realWorldCrushDepth *= Info.GetRealWorldCrushDepthMultiplier();
}
return realWorldCrushDepth.Value;
}
@@ -452,10 +451,27 @@ namespace Barotrauma
verticalMoveDir = Math.Sign(verticalMoveDir);
//do a raycast towards the top/bottom of the level depending on direction
Vector2 potentialPos = new Vector2(spawnPos.X, verticalMoveDir > 0 ? Level.Loaded.Size.Y : 0);
if (PickBody(ConvertUnits.ToSimUnits(spawnPos), ConvertUnits.ToSimUnits(potentialPos), collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
//3 raycasts (left, middle and right side of the sub, so we don't accidentally raycast up a passage too narrow for the sub)
for (int x = -1; x <= 1; x++)
{
//if the raycast hit a wall, attempt to place the spawnpos there
potentialPos.Y = ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) - 10;
Vector2 xOffset = Vector2.UnitX * minWidth / 2 * x;
if (PickBody(
ConvertUnits.ToSimUnits(spawnPos + xOffset),
ConvertUnits.ToSimUnits(potentialPos + xOffset),
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
{
int offsetFromWall = 10 * -verticalMoveDir;
//if the raycast hit a wall, attempt to place the spawnpos there
if (verticalMoveDir > 0)
{
potentialPos.Y = Math.Min(potentialPos.Y, ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall);
}
else
{
potentialPos.Y = Math.Max(potentialPos.Y, ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall);
}
}
}
//step away from the top/bottom of the level, or from whatever wall the raycast hit,
@@ -505,7 +505,6 @@ namespace Barotrauma
if (wall.Submarine != submarine) { continue; }
float wallCrushDepth = wall.CrushDepth;
if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; }
float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth;
if (pastCrushDepth > 0)
{
@@ -587,9 +586,13 @@ namespace Barotrauma
newHull = Hull.FindHull(targetPos, null);
}
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
if (adjacentGap == null) { return true; }
//if all the bodies of a wall have been disabled, we don't need to care about gaps (can always pass through)
if (!(contact.FixtureA.UserData is Structure wall) || !wall.AllSectionBodiesDisabled())
{
var gaps = newHull?.ConnectedGaps ?? Gap.GapList.Where(g => g.Submarine == submarine);
Gap adjacentGap = Gap.FindAdjacent(gaps, ConvertUnits.ToDisplayUnits(points[0]), 200.0f);
if (adjacentGap == null) { return true; }
}
if (newHull != null)
{
@@ -24,7 +24,7 @@ namespace Barotrauma
}
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine, Ruin }
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
public enum SubmarineClass { Undefined, Scout, Attack, Transport }
partial class SubmarineInfo : IDisposable
{
@@ -49,6 +49,12 @@ namespace Barotrauma
}
public CrewExperienceLevel RecommendedCrewExperience;
public int Tier
{
get;
set;
}
/// <summary>
/// A random int that gets assigned when saving the sub. Used in mp campaign to verify that sub files match
/// </summary>
@@ -305,6 +311,7 @@ namespace Barotrauma
RecommendedCrewExperience = original.RecommendedCrewExperience;
RecommendedCrewSizeMin = original.RecommendedCrewSizeMin;
RecommendedCrewSizeMax = original.RecommendedCrewSizeMax;
Tier = original.Tier;
IsManuallyOutfitted = original.IsManuallyOutfitted;
Tags = original.Tags;
if (original.OutpostModuleInfo != null)
@@ -386,6 +393,7 @@ namespace Barotrauma
{
Enum.TryParse(recommendedCrewExperience.Value, ignoreCase: true, out RecommendedCrewExperience);
}
Tier = SubmarineElement.GetAttributeInt("tier", GetDefaultTier(Price));
if (SubmarineElement?.Attribute("type") != null)
{
@@ -407,7 +415,13 @@ namespace Barotrauma
{
if (SubmarineElement?.Attribute("class") != null)
{
if (Enum.TryParse(SubmarineElement.GetAttributeString("class", "Undefined"), out SubmarineClass submarineClass))
string classStr = SubmarineElement.GetAttributeString("class", "Undefined");
if (classStr == "DeepDiver")
{
//backwards compatibility
SubmarineClass = SubmarineClass.Scout;
}
else if (Enum.TryParse(classStr, out SubmarineClass submarineClass))
{
SubmarineClass = submarineClass;
}
@@ -538,25 +552,9 @@ namespace Barotrauma
{
realWorldCrushDepth = Level.DefaultRealWorldCrushDepth;
}
realWorldCrushDepth *= GetRealWorldCrushDepthMultiplier();
return realWorldCrushDepth;
}
/// <summary>
/// Based on <see cref="SubmarineClass"/>
/// </summary>
public float GetRealWorldCrushDepthMultiplier()
{
if (SubmarineClass == SubmarineClass.DeepDiver)
{
return 1.2f;
}
else
{
return 1.0f;
}
}
//saving/loading ----------------------------------------------------
public void SaveAs(string filePath, System.IO.MemoryStream previewImage = null)
{
@@ -691,7 +689,7 @@ namespace Barotrauma
System.IO.Stream stream;
try
{
stream = SaveUtil.DecompressFiletoStream(file);
stream = SaveUtil.DecompressFileToStream(file);
}
catch (System.IO.FileNotFoundException e)
{
@@ -748,5 +746,7 @@ namespace Barotrauma
return doc;
}
public static int GetDefaultTier(int price) => price > 20000 ? 3 : price > 10000 ? 2 : 1;
}
}
}
@@ -83,7 +83,7 @@ namespace Barotrauma
foreach (byte b in Buffer)
{
msg.Write(b);
msg.WriteByte(b);
}
Dispose();
@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
@@ -98,7 +98,7 @@ namespace Barotrauma.Networking
Task<int> readTask = readStream?.ReadAsync(readTempBytes, 0, readTempBytes.Length, readCancellationToken.Token);
if (readTask is null) { return -1; }
TimeSpan timeOut = TimeSpan.FromMilliseconds(100);
int timeOutMilliseconds = 100;
for (int i = 0; i < 150; i++)
{
if (shutDown)
@@ -106,12 +106,9 @@ namespace Barotrauma.Networking
readCancellationToken?.Cancel();
return -1;
}
// BUG workaround for crash when closing the server under .NET 6.0, not sure if this is the proper way to fix it but it prevents it from crashing the client. - Markus
#if NET6_0
try
{
if (readTask.IsCompleted || readTask.Wait(100, readCancellationToken.Token))
if (readTask.IsCompleted || readTask.Wait(timeOutMilliseconds, readCancellationToken.Token))
{
break;
}
@@ -120,12 +117,6 @@ namespace Barotrauma.Networking
{
return -1;
}
#else
if (readTask.IsCompleted || readTask.Wait(timeOut))
{
break;
}
#endif
}
if (readTask.Status != TaskStatus.RanToCompletion)
@@ -177,8 +177,6 @@ namespace Barotrauma.Networking
}
public bool HasSpawned; //has the client spawned as a character during the current round
private readonly List<Client> kickVoters;
public HashSet<Identifier> GivenAchievements = new HashSet<Identifier>();
public ClientPermissions Permissions = ClientPermissions.None;
@@ -186,11 +184,6 @@ namespace Barotrauma.Networking
private readonly object[] votes;
public int KickVoteCount
{
get { return kickVoters.Count; }
}
partial void InitProjSpecific();
partial void DisposeProjSpecific();
public Client(string name, byte sessionId)
@@ -198,8 +191,6 @@ namespace Barotrauma.Networking
this.Name = name;
this.SessionId = sessionId;
kickVoters = new List<Client>();
votes = new object[Enum.GetNames(typeof(VoteType)).Length];
InitProjSpecific();
@@ -221,53 +212,22 @@ namespace Barotrauma.Networking
{
votes[i] = null;
}
kickVoters.Clear();
}
public void AddKickVote(Client voter)
{
if (voter != null && !kickVoters.Contains(voter)) { kickVoters.Add(voter); }
}
public void RemoveKickVote(Client voter)
{
kickVoters.Remove(voter);
}
public bool HasKickVoteFrom(Client voter)
{
return kickVoters.Contains(voter);
}
public bool HasKickVoteFromSessionId(int id)
{
return kickVoters.Any(k => k.SessionId == id);
}
public bool SessionOrAccountIdMatches(string userId)
=> (AccountId.IsSome() && Networking.AccountId.Parse(userId) == AccountId)
|| (byte.TryParse(userId, out byte sessionId) && SessionId == sessionId);
public static void UpdateKickVotes(IReadOnlyList<Client> connectedClients)
{
foreach (Client client in connectedClients)
{
client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter));
}
}
public void WritePermissions(IWriteMessage msg)
{
msg.Write(SessionId);
msg.WriteByte(SessionId);
msg.WriteRangedInteger((int)Permissions, 0, (int)ClientPermissions.All);
if (HasPermission(ClientPermissions.ConsoleCommands))
{
msg.Write((UInt16)PermittedConsoleCommands.Count);
msg.WriteUInt16((UInt16)PermittedConsoleCommands.Count);
foreach (DebugConsole.Command command in PermittedConsoleCommands)
{
msg.Write(command.names[0]);
msg.WriteString(command.names[0]);
}
}
}
@@ -325,9 +325,14 @@ namespace Barotrauma
Range<int> range = GetEnumRange(type);
int enumIndex = bitField.ReadInteger(range.Start, range.End);
if (typeof(T).GetCustomAttribute<FlagsAttribute>() != null)
{
return (T)(object)enumIndex;
}
foreach (T e in (T[])Enum.GetValues(type))
{
if ((int)Convert.ChangeType(e, e.GetTypeCode()) == enumIndex) { return e; }
if (((int)(object)e) == enumIndex) { return e; }
}
throw new InvalidOperationException($"An enum {type} with value {enumIndex} could not be found in {nameof(ReadEnum)}");
@@ -388,18 +393,18 @@ namespace Barotrauma
private static bool ReadBoolean(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => bitField.ReadBoolean();
private static void WriteBoolean(bool b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { bitField.WriteBoolean(b); }
private static byte ReadByte(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadByte();
private static void WriteByte(byte b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteByte(byte b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteByte(b); }
private static ushort ReadUInt16(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt16();
private static void WriteUInt16(ushort b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteUInt16(ushort b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt16(b); }
private static short ReadInt16(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadInt16();
private static void WriteInt16(short b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteInt16(short b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteInt16(b); }
private static uint ReadUInt32(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt32();
private static void WriteUInt32(uint b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteUInt32(uint b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt32(b); }
private static int ReadInt32(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
{
@@ -421,14 +426,14 @@ namespace Barotrauma
return;
}
msg.Write(i);
msg.WriteInt32(i);
}
private static ulong ReadUInt64(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt64();
private static void WriteUInt64(ulong b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteUInt64(ulong b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteUInt64(b); }
private static long ReadInt64(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadInt64();
private static void WriteInt64(long b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteInt64(long b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteInt64(b); }
private static float ReadSingle(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
{
@@ -450,17 +455,17 @@ namespace Barotrauma
return;
}
msg.Write(f);
msg.WriteSingle(f);
}
private static double ReadDouble(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadDouble();
private static void WriteDouble(double b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteDouble(double b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteDouble(b); }
private static string ReadString(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadString();
private static void WriteString(string b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteString(string b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteString(b); }
private static Identifier ReadIdentifier(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadIdentifier();
private static void WriteIdentifier(Identifier b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static void WriteIdentifier(Identifier b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.WriteIdentifier(b); }
private static AccountId ReadAccountId(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
{
@@ -472,7 +477,7 @@ namespace Barotrauma
private static void WriteAccountId(AccountId accountId, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
{
msg.Write(accountId.StringRepresentation);
msg.WriteString(accountId.StringRepresentation);
}
private static Color ReadColor(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => attribute.IncludeColorAlpha ? inc.ReadColorR8G8B8A8() : inc.ReadColorR8G8B8();
@@ -519,7 +524,7 @@ namespace Barotrauma
private static bool TryFindBehavior<T>(out ReadWriteBehavior<T> behavior) where T : notnull
{
bool found = TryFindBehavior(typeof(T), out var bhvr);
behavior = (ReadWriteBehavior<T>)bhvr;
behavior = found ? (ReadWriteBehavior<T>)bhvr : default;
return found;
}
@@ -589,8 +594,8 @@ namespace Barotrauma
return array;
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? type.GetCustomAttribute<NetworkSerialize>()) != null;
bool NotStatic(MemberInfo info)
static bool NotStatic(MemberInfo info)
=> info switch
{
PropertyInfo property => property.GetGetMethod() is { IsStatic: false },
@@ -656,7 +661,7 @@ namespace Barotrauma
/// Using <see cref="Nullable{T}"/> or <see cref="Option{T}"/> will make the field or property optional.
/// </remarks>
/// <seealso cref="NetworkSerialize"/>
interface INetSerializableStruct
internal interface INetSerializableStruct
{
/// <summary>
/// Deserializes a network message into a struct.
@@ -743,7 +748,7 @@ namespace Barotrauma
IWriteMessage structWriteMsg = new WriteOnlyMessage();
WriteInternal(structWriteMsg, bitField);
bitField.WriteToMessage(msg);
msg.Write(structWriteMsg.Buffer, 0, structWriteMsg.LengthBytes);
msg.WriteBytes(structWriteMsg.Buffer, 0, structWriteMsg.LengthBytes);
}
public void WriteInternal(IWriteMessage msg, IWritableBitField bitField)
@@ -757,25 +762,4 @@ namespace Barotrauma
}
}
}
static class WriteOnlyMessageExtensions
{
#if CLIENT
public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header)
{
msg.Write((byte)header);
return msg;
}
#elif SERVER
public static IWriteMessage WithHeader(this IWriteMessage msg, ServerPacketHeader header)
{
msg.Write((byte)header);
return msg;
}
#endif
public static void Write(this IWriteMessage msg, INetSerializableStruct serializableStruct)
{
serializableStruct.Write(msg);
}
}
}
@@ -1,15 +0,0 @@
namespace Barotrauma.Networking
{
static class NetBufferExtensions
{
//public static void WriteEnum(this NetBuffer buffer, Enum value)
//{
// buffer.WriteRangedInteger(0, Enum.GetValues(value.GetType()).Length - 1, Convert.ToInt32(value));
//}
//public static TEnum ReadEnum<TEnum>(this NetBuffer buffer)
//{
// return (TEnum)(object)buffer.ReadRangedInteger(0, Enum.GetValues(typeof(TEnum)).Length - 1);
//}
}
}
@@ -37,7 +37,7 @@ namespace Barotrauma.Networking
//write an empty event to avoid messing up IDs
//(otherwise the clients might read the next event in the message and think its ID
//is consecutive to the previous one, even though we skipped over this broken event)
tempBuffer.Write(Entity.NullEntityID);
tempBuffer.WriteUInt16(Entity.NullEntityID);
eventCount++;
continue;
}
@@ -49,9 +49,9 @@ namespace Barotrauma.Networking
break;
}
tempBuffer.Write(e.EntityID);
tempBuffer.WriteUInt16(e.EntityID);
tempBuffer.WriteVariableUInt32((uint)tempEventBuffer.LengthBytes);
tempBuffer.Write(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes);
tempBuffer.WriteBytes(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes);
sentEvents.Add(e);
eventCount++;
@@ -60,9 +60,9 @@ namespace Barotrauma.Networking
if (eventCount > 0)
{
msg.WritePadBits();
msg.Write(eventsToSync[0].ID);
msg.Write((byte)eventCount);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
msg.WriteUInt16(eventsToSync[0].ID);
msg.WriteByte((byte)eventCount);
msg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
}
}
@@ -28,6 +28,19 @@ namespace Barotrauma.Networking
public static bool IdMoreRecentOrMatches(ushort newId, ushort oldId)
=> !IdMoreRecent(oldId, newId);
/// <summary>
/// Returns some ID that is older than the input ID. There are no guarantees
/// regarding its relation to values other than the input.
/// </summary>
public static ushort GetIdOlderThan(ushort id)
#if DEBUG
// Debug implementation has some RNG to discourage bad assumptions about the return value
=> unchecked((ushort)(id - 1 - Rand.Int(500, sync: Rand.RandSync.Unsynced)));
#else
// Release implementation favors performance
=> unchecked((ushort)(id - 1));
#endif
public static ushort Difference(ushort id1, ushort id2)
{
int diff = id2 > id1 ? id2 - id1 : id1 - id2;
@@ -47,32 +47,32 @@ namespace Barotrauma.Networking
public static void WriteOrder(IWriteMessage msg, Order order, Character targetCharacter, bool isNewOrder)
{
msg.Write(order.Prefab.Identifier);
msg.Write(targetCharacter == null ? (UInt16)0 : targetCharacter.ID);
msg.Write(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0);
msg.WriteIdentifier(order.Prefab.Identifier);
msg.WriteUInt16(targetCharacter == null ? (UInt16)0 : targetCharacter.ID);
msg.WriteUInt16(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0);
// The option of a Dismiss order is written differently so we know what order we target
// now that the game supports multiple current orders simultaneously
if (!order.IsDismissal)
{
msg.Write((byte)order.Options.IndexOf(order.Option));
msg.WriteByte((byte)order.Options.IndexOf(order.Option));
}
else
{
if (order.Option != Identifier.Empty)
{
msg.Write(true);
msg.WriteBoolean(true);
string[] dismissedOrder = order.Option.Value.Split('.');
msg.Write((byte)dismissedOrder.Length);
msg.WriteByte((byte)dismissedOrder.Length);
if (dismissedOrder.Length > 0)
{
Identifier dismissedOrderIdentifier = dismissedOrder[0].ToIdentifier();
var orderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier];
msg.Write(dismissedOrderIdentifier);
msg.WriteIdentifier(dismissedOrderIdentifier);
if (dismissedOrder.Length > 1)
{
Identifier dismissedOrderOption = dismissedOrder[1].ToIdentifier();
msg.Write((byte)orderPrefab.Options.IndexOf(dismissedOrderOption));
msg.WriteByte((byte)orderPrefab.Options.IndexOf(dismissedOrderOption));
}
}
}
@@ -80,29 +80,29 @@ namespace Barotrauma.Networking
{
// If the order option is not specified for a Dismiss order,
// we dismiss all current orders for the character
msg.Write(false);
msg.WriteBoolean(false);
}
}
msg.Write((byte)order.ManualPriority);
msg.Write((byte)order.TargetType);
msg.WriteByte((byte)order.ManualPriority);
msg.WriteByte((byte)order.TargetType);
if (order.TargetType == Order.OrderTargetType.Position && order.TargetSpatialEntity is OrderTarget orderTarget)
{
msg.Write(true);
msg.Write(orderTarget.Position.X);
msg.Write(orderTarget.Position.Y);
msg.Write(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID);
msg.WriteBoolean(true);
msg.WriteSingle(orderTarget.Position.X);
msg.WriteSingle(orderTarget.Position.Y);
msg.WriteUInt16(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID);
}
else
{
msg.Write(false);
msg.WriteBoolean(false);
if (order.TargetType == Order.OrderTargetType.WallSection)
{
msg.Write((byte)(order.WallSectionIndex ?? 0));
msg.WriteByte((byte)(order.WallSectionIndex ?? 0));
}
}
msg.Write(isNewOrder);
msg.WriteBoolean(isNewOrder);
}
private void WriteOrder(IWriteMessage msg)
@@ -4,27 +4,27 @@ namespace Barotrauma.Networking
{
interface IWriteMessage
{
void Write(bool val);
void WriteBoolean(bool val);
void WritePadBits();
void Write(byte val);
void Write(Int16 val);
void Write(UInt16 val);
void Write(Int32 val);
void Write(UInt32 val);
void Write(Int64 val);
void Write(UInt64 val);
void Write(Single val);
void Write(Double val);
void WriteByte(byte val);
void WriteInt16(Int16 val);
void WriteUInt16(UInt16 val);
void WriteInt32(Int32 val);
void WriteUInt32(UInt32 val);
void WriteInt64(Int64 val);
void WriteUInt64(UInt64 val);
void WriteSingle(Single val);
void WriteDouble(Double val);
void WriteColorR8G8B8(Microsoft.Xna.Framework.Color val);
void WriteColorR8G8B8A8(Microsoft.Xna.Framework.Color val);
void WriteVariableUInt32(UInt32 val);
void Write(string val);
void Write(Identifier val);
void WriteString(string val);
void WriteIdentifier(Identifier val);
void WriteRangedInteger(int val, int min, int max);
void WriteRangedSingle(Single val, Single min, Single max, int bitCount);
void Write(byte[] val, int startIndex, int length);
void WriteBytes(byte[] val, int startIndex, int length);
void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int outLength);
byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength);
int BitPosition { get; set; }
int BytePosition { get; }
@@ -1,7 +1,6 @@
using Lidgren.Network;
using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
@@ -58,7 +57,7 @@ namespace Barotrauma.Networking
bool testVal = MsgReader.ReadBoolean(buf, ref resetPos);
if (testVal != val || resetPos != bitPos)
{
DebugConsole.ThrowError("Boolean written incorrectly! " + testVal + ", " + val + "; " + resetPos + ", " + bitPos);
DebugConsole.ThrowError($"Boolean written incorrectly! {testVal}, {val}; {resetPos}, {bitPos}");
}
#endif
}
@@ -125,7 +124,7 @@ namespace Barotrauma.Networking
SingleUIntUnion su;
su.UIntValue = 0; // must initialize every member of the union to avoid warning
su.SingleValue = val;
EnsureBufferSize(ref buf, bitPos + 32);
NetBitWriter.WriteUInt32(su.UIntValue, 32, buf, bitPos);
@@ -140,50 +139,48 @@ namespace Barotrauma.Networking
WriteBytes(ref buf, ref bitPos, bytes, 0, 8);
}
internal static void WriteColorR8G8B8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val)
internal static void WriteColorR8G8B8(ref byte[] buf, ref int bitPos, Color val)
{
EnsureBufferSize(ref buf, bitPos + 24);
Write(ref buf, ref bitPos, val.R);
Write(ref buf, ref bitPos, val.G);
Write(ref buf, ref bitPos, val.B);
}
internal static void WriteColorR8G8B8A8(ref byte[] buf, ref int bitPos, Microsoft.Xna.Framework.Color val)
internal static void WriteColorR8G8B8A8(ref byte[] buf, ref int bitPos, Color val)
{
EnsureBufferSize(ref buf, bitPos + 32);
Write(ref buf, ref bitPos, val.R);
Write(ref buf, ref bitPos, val.G);
Write(ref buf, ref bitPos, val.B);
Write(ref buf, ref bitPos, val.A);
}
internal static void Write(ref byte[] buf, ref int bitPos, string val)
{
if (string.IsNullOrEmpty(val))
{
WriteVariableUInt32(ref buf, ref bitPos, (uint)0);
WriteVariableUInt32(ref buf, ref bitPos, 0u);
return;
}
byte[] bytes = Encoding.UTF8.GetBytes(val);
WriteVariableUInt32(ref buf, ref bitPos, (uint)bytes.Length);
WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length);
}
internal static int WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value)
internal static void WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value)
{
int retval = 1;
uint remainingValue = (uint)value;
uint remainingValue = value;
while (remainingValue >= 0x80)
{
Write(ref buf, ref bitPos, (byte)(remainingValue | 0x80));
remainingValue = remainingValue >> 7;
retval++;
remainingValue >>= 7;
}
Write(ref buf, ref bitPos, (byte)remainingValue);
return retval;
}
internal static void WriteRangedInteger(ref byte[] buf, ref int bitPos, int val, int min, int max)
@@ -206,7 +203,7 @@ namespace Barotrauma.Networking
EnsureBufferSize(ref buf, bitPos + numberOfBits);
NetBitWriter.WriteUInt32((UInt32)((float)maxVal * unit), numberOfBits, buf, bitPos);
NetBitWriter.WriteUInt32((UInt32)(maxVal * unit), numberOfBits, buf, bitPos);
bitPos += numberOfBits;
}
@@ -225,9 +222,10 @@ namespace Barotrauma.Networking
buf = new byte[byteLen + MsgConstants.BufferOverAllocateAmount];
return;
}
if (buf.Length < byteLen)
{
Array.Resize<byte>(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount);
Array.Resize(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount);
}
}
}
@@ -241,7 +239,7 @@ namespace Barotrauma.Networking
return retval > 0;
}
internal static void ReadPadBits(byte[] buf, ref int bitPos)
internal static void ReadPadBits(ref int bitPos)
{
int bitOffset = bitPos % 8;
bitPos += (8 - bitOffset) % 8;
@@ -326,15 +324,15 @@ namespace Barotrauma.Networking
return BitConverter.ToDouble(bytes, 0);
}
internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8(byte[] buf, ref int bitPos)
internal static Color ReadColorR8G8B8(byte[] buf, ref int bitPos)
{
byte r = ReadByte(buf, ref bitPos);
byte g = ReadByte(buf, ref bitPos);
byte b = ReadByte(buf, ref bitPos);
return new Color(r, g, b, (byte)255);
}
internal static Microsoft.Xna.Framework.Color ReadColorR8G8B8A8(byte[] buf, ref int bitPos)
internal static Color ReadColorR8G8B8A8(byte[] buf, ref int bitPos)
{
byte r = ReadByte(buf, ref bitPos);
byte g = ReadByte(buf, ref bitPos);
@@ -354,8 +352,7 @@ namespace Barotrauma.Networking
byte chunk = ReadByte(buf, ref bitPos);
result |= (chunk & 0x7f) << shift;
shift += 7;
if ((chunk & 0x80) == 0)
return (uint)result;
if ((chunk & 0x80) == 0) { return (uint)result; }
}
// ouch; failed to find enough bytes; malformed variable length number?
@@ -378,23 +375,23 @@ namespace Barotrauma.Networking
if ((bitPos & 7) == 0)
{
// read directly
string retval = System.Text.Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen);
string retval = Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen);
bitPos += (8 * byteLen);
return retval;
}
byte[] bytes = ReadBytes(buf, ref bitPos, byteLen);
return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
internal static int ReadRangedInteger(byte[] buf, ref int bitPos, int min, int max)
{
uint range = (uint)(max - min);
int numBits = NetUtility.BitsToHoldUInt(range);
uint range = (uint)(max - min);
int numBits = NetUtility.BitsToHoldUInt(range);
uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos);
uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos);
bitPos += numBits;
return (int)(min + rvalue);
}
@@ -403,51 +400,33 @@ namespace Barotrauma.Networking
int maxInt = (1 << bitCount) - 1;
int intVal = ReadRangedInteger(buf, ref bitPos, 0, maxInt);
Single range = max - min;
return min + (range * ((Single)intVal) / ((Single)maxInt));
return min + range * intVal / maxInt;
}
internal static byte[] ReadBytes(byte[] buf, ref int bitPos, int numberOfBytes)
{
byte[] retval = new byte[numberOfBytes];
NetBitWriter.ReadBytes(buf, numberOfBytes, bitPos, retval, 0);
bitPos += (8 * numberOfBytes);
bitPos += 8 * numberOfBytes;
return retval;
}
}
class WriteOnlyMessage : IWriteMessage
internal sealed class WriteOnlyMessage : IWriteMessage
{
private byte[] buf = new byte[MsgConstants.InitialBufferSize];
private int seekPos = 0;
private int lengthBits = 0;
private int seekPos;
private int lengthBits;
public int BitPosition
{
get
{
return seekPos;
}
set
{
seekPos = value;
}
get => seekPos;
set => seekPos = value;
}
public int BytePosition
{
get
{
return seekPos / 8;
}
}
public int BytePosition => seekPos / 8;
public byte[] Buffer
{
get
{
return buf;
}
}
public byte[] Buffer => buf;
public int LengthBits
{
@@ -464,15 +443,9 @@ namespace Barotrauma.Networking
}
}
public int LengthBytes
{
get
{
return (LengthBits + 7) / 8;
}
}
public int LengthBytes => (LengthBits + 7) / 8;
public void Write(bool val)
public void WriteBoolean(bool val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
@@ -482,47 +455,47 @@ namespace Barotrauma.Networking
MsgWriter.WritePadBits(ref buf, ref seekPos);
}
public void Write(byte val)
public void WriteByte(byte val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt16 val)
public void WriteUInt16(UInt16 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int16 val)
public void WriteInt16(Int16 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt32 val)
public void WriteUInt32(UInt32 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int32 val)
public void WriteInt32(Int32 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt64 val)
public void WriteUInt64(UInt64 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int64 val)
public void WriteInt64(Int64 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Single val)
public void WriteSingle(Single val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Double val)
public void WriteDouble(Double val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
@@ -531,7 +504,7 @@ namespace Barotrauma.Networking
{
MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val);
}
public void WriteColorR8G8B8A8(Color val)
{
MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val);
@@ -542,14 +515,14 @@ namespace Barotrauma.Networking
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
}
public void Write(String val)
public void WriteString(String val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Identifier val)
public void WriteIdentifier(Identifier val)
{
Write(val.Value);
WriteString(val.Value);
}
public void WriteRangedInteger(int val, int min, int max)
@@ -562,85 +535,67 @@ namespace Barotrauma.Networking
MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
}
public void Write(byte[] val, int startPos, int length)
public void WriteBytes(byte[] val, int startPos, int length)
{
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
}
public void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int length)
public byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int length)
{
byte[] outBuf;
if (LengthBytes <= MsgConstants.CompressionThreshold || !compressPastThreshold)
{
isCompressed = false;
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
outBuf = new byte[LengthBytes];
Array.Copy(buf, outBuf, LengthBytes);
length = LengthBytes;
}
else
{
using (System.IO.MemoryStream output = new System.IO.MemoryStream())
using MemoryStream output = new MemoryStream();
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest))
{
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest))
{
dstream.Write(buf, 0, LengthBytes);
}
byte[] compressedBuf = output.ToArray();
//don't send the data as compressed if the data takes up more space after compression
//(which may happen when sending a sub/save file that's already been compressed with a better compression ratio)
if (compressedBuf.Length >= outBuf.Length)
{
isCompressed = false;
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
Array.Copy(buf, outBuf, LengthBytes);
length = LengthBytes;
}
else
{
isCompressed = true;
if (compressedBuf.Length > outBuf.Length) { Array.Resize(ref outBuf, compressedBuf.Length); }
Array.Copy(compressedBuf, outBuf, compressedBuf.Length);
length = compressedBuf.Length;
DebugConsole.Log("Compressed message: " + LengthBytes + " to " + length);
}
dstream.Write(buf, 0, LengthBytes);
}
byte[] compressedBuf = output.ToArray();
//don't send the data as compressed if the data takes up more space after compression
//(which may happen when sending a sub/save file that's already been compressed with a better compression ratio)
if (compressedBuf.Length >= LengthBytes)
{
isCompressed = false;
outBuf = new byte[LengthBytes];
Array.Copy(buf, outBuf, LengthBytes);
length = LengthBytes;
}
else
{
isCompressed = true;
outBuf = compressedBuf;
length = outBuf.Length;
DebugConsole.Log($"Compressed message: {LengthBytes} to {length}");
}
}
return outBuf;
}
}
class ReadOnlyMessage : IReadMessage
internal sealed class ReadOnlyMessage : IReadMessage
{
private readonly byte[] buf;
private int seekPos = 0;
private int lengthBits = 0;
private int seekPos;
private int lengthBits;
public int BitPosition
{
get
{
return seekPos;
}
set
{
seekPos = value;
}
get => seekPos;
set => seekPos = value;
}
public int BytePosition
{
get
{
return seekPos / 8;
}
}
public int BytePosition => seekPos / 8;
public byte[] Buffer
{
get
{
return buf;
}
}
public byte[] Buffer { get; }
public int LengthBits
{
@@ -656,129 +611,126 @@ namespace Barotrauma.Networking
}
}
public int LengthBytes
{
get
{
return (LengthBits + 7) / 8;
}
}
public int LengthBytes => (LengthBits + 7) / 8;
public NetworkConnection Sender { get; private set; }
public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int inLength, NetworkConnection sender)
public NetworkConnection Sender { get; }
public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int byteLength, NetworkConnection sender)
{
Sender = sender;
if (isCompressed)
{
byte[] decompressedData;
using (System.IO.MemoryStream input = new System.IO.MemoryStream(inBuf, startPos, inLength))
using (MemoryStream input = new MemoryStream(inBuf, startPos, byteLength))
{
using (System.IO.MemoryStream output = new System.IO.MemoryStream())
using (MemoryStream output = new MemoryStream())
{
using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress))
{
dstream.CopyTo(output);
}
decompressedData = output.ToArray();
}
}
buf = new byte[decompressedData.Length];
Buffer = new byte[decompressedData.Length];
try
{
Array.Copy(decompressedData, 0, buf, 0, decompressedData.Length);
Array.Copy(decompressedData, 0, Buffer, 0, decompressedData.Length);
}
catch (ArgumentException e)
{
throw new ArgumentException($"Failed to copy the incoming compressed buffer. Source buffer length: {decompressedData.Length}, start position: {0}, length: {decompressedData.Length}, destination buffer length: {buf.Length}.", e);
throw new ArgumentException(
$"Failed to copy the incoming compressed buffer. Source buffer length: {decompressedData.Length}, start position: {0}, length: {decompressedData.Length}, destination buffer length: {Buffer.Length}.", e);
}
lengthBits = decompressedData.Length * 8;
DebugConsole.Log("Decompressing message: " + inLength + " to " + LengthBytes);
DebugConsole.Log("Decompressing message: " + byteLength + " to " + LengthBytes);
}
else
{
buf = new byte[inBuf.Length];
Buffer = new byte[inBuf.Length];
try
{
Array.Copy(inBuf, startPos, buf, 0, inLength);
Array.Copy(inBuf, startPos, Buffer, 0, byteLength);
}
catch (ArgumentException e)
{
throw new ArgumentException($"Failed to copy the incoming uncompressed buffer. Source buffer length: {inBuf.Length}, start position: {startPos}, length: {inLength}, destination buffer length: {buf.Length}.", e);
throw new ArgumentException($"Failed to copy the incoming uncompressed buffer. Source buffer length: {inBuf.Length}, start position: {startPos}, length: {byteLength}, destination buffer length: {Buffer.Length}.", e);
}
lengthBits = inLength * 8;
lengthBits = byteLength * 8;
}
seekPos = 0;
}
public bool ReadBoolean()
{
return MsgReader.ReadBoolean(buf, ref seekPos);
return MsgReader.ReadBoolean(Buffer, ref seekPos);
}
public void ReadPadBits()
{
MsgReader.ReadPadBits(buf, ref seekPos);
}
public void ReadPadBits() { MsgReader.ReadPadBits(ref seekPos); }
public byte ReadByte()
{
return MsgReader.ReadByte(buf, ref seekPos);
return MsgReader.ReadByte(Buffer, ref seekPos);
}
public byte PeekByte()
{
return MsgReader.PeekByte(buf, ref seekPos);
return MsgReader.PeekByte(Buffer, ref seekPos);
}
public UInt16 ReadUInt16()
{
return MsgReader.ReadUInt16(buf, ref seekPos);
return MsgReader.ReadUInt16(Buffer, ref seekPos);
}
public Int16 ReadInt16()
{
return MsgReader.ReadInt16(buf, ref seekPos);
return MsgReader.ReadInt16(Buffer, ref seekPos);
}
public UInt32 ReadUInt32()
{
return MsgReader.ReadUInt32(buf, ref seekPos);
return MsgReader.ReadUInt32(Buffer, ref seekPos);
}
public Int32 ReadInt32()
{
return MsgReader.ReadInt32(buf, ref seekPos);
return MsgReader.ReadInt32(Buffer, ref seekPos);
}
public UInt64 ReadUInt64()
{
return MsgReader.ReadUInt64(buf, ref seekPos);
return MsgReader.ReadUInt64(Buffer, ref seekPos);
}
public Int64 ReadInt64()
{
return MsgReader.ReadInt64(buf, ref seekPos);
return MsgReader.ReadInt64(Buffer, ref seekPos);
}
public Single ReadSingle()
{
return MsgReader.ReadSingle(buf, ref seekPos);
return MsgReader.ReadSingle(Buffer, ref seekPos);
}
public Double ReadDouble()
{
return MsgReader.ReadDouble(buf, ref seekPos);
return MsgReader.ReadDouble(Buffer, ref seekPos);
}
public UInt32 ReadVariableUInt32()
{
return MsgReader.ReadVariableUInt32(buf, ref seekPos);
return MsgReader.ReadVariableUInt32(Buffer, ref seekPos);
}
public String ReadString()
{
return MsgReader.ReadString(buf, ref seekPos);
return MsgReader.ReadString(Buffer, ref seekPos);
}
public Identifier ReadIdentifier()
@@ -788,35 +740,35 @@ namespace Barotrauma.Networking
public Color ReadColorR8G8B8()
{
return MsgReader.ReadColorR8G8B8(buf, ref seekPos);
return MsgReader.ReadColorR8G8B8(Buffer, ref seekPos);
}
public Color ReadColorR8G8B8A8()
{
return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos);
return MsgReader.ReadColorR8G8B8A8(Buffer, ref seekPos);
}
public int ReadRangedInteger(int min, int max)
{
return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
return MsgReader.ReadRangedInteger(Buffer, ref seekPos, min, max);
}
public Single ReadRangedSingle(Single min, Single max, int bitCount)
{
return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount);
return MsgReader.ReadRangedSingle(Buffer, ref seekPos, min, max, bitCount);
}
public byte[] ReadBytes(int numberOfBytes)
{
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
return MsgReader.ReadBytes(Buffer, ref seekPos, numberOfBytes);
}
}
class ReadWriteMessage : IWriteMessage, IReadMessage
internal sealed class ReadWriteMessage : IWriteMessage, IReadMessage
{
private byte[] buf;
private int seekPos = 0;
private int lengthBits = 0;
private int seekPos;
private int lengthBits;
public ReadWriteMessage()
{
@@ -825,40 +777,22 @@ namespace Barotrauma.Networking
lengthBits = 0;
}
public ReadWriteMessage(byte[] b, int sPos, int lBits, bool copyBuf)
public ReadWriteMessage(byte[] b, int bitPos, int lBits, bool copyBuf)
{
buf = copyBuf ? (byte[])b.Clone() : b;
seekPos = sPos;
seekPos = bitPos;
lengthBits = lBits;
}
public int BitPosition
{
get
{
return seekPos;
}
set
{
seekPos = value;
}
get => seekPos;
set => seekPos = value;
}
public int BytePosition
{
get
{
return seekPos / 8;
}
}
public int BytePosition => seekPos / 8;
public byte[] Buffer
{
get
{
return buf;
}
}
public byte[] Buffer => buf;
public int LengthBits
{
@@ -874,17 +808,11 @@ namespace Barotrauma.Networking
}
}
public int LengthBytes
{
get
{
return (LengthBits + 7) / 8;
}
}
public int LengthBytes => (LengthBits + 7) / 8;
public NetworkConnection Sender { get { return null; } }
public NetworkConnection Sender => null;
public void Write(bool val)
public void WriteBoolean(bool val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
@@ -894,47 +822,47 @@ namespace Barotrauma.Networking
MsgWriter.WritePadBits(ref buf, ref seekPos);
}
public void Write(byte val)
public void WriteByte(byte val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt16 val)
public void WriteUInt16(UInt16 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int16 val)
public void WriteInt16(Int16 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt32 val)
public void WriteUInt32(UInt32 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int32 val)
public void WriteInt32(Int32 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt64 val)
public void WriteUInt64(UInt64 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int64 val)
public void WriteInt64(Int64 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Single val)
public void WriteSingle(Single val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Double val)
public void WriteDouble(Double val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
@@ -943,7 +871,7 @@ namespace Barotrauma.Networking
{
MsgWriter.WriteColorR8G8B8(ref buf, ref seekPos, val);
}
public void WriteColorR8G8B8A8(Color val)
{
MsgWriter.WriteColorR8G8B8A8(ref buf, ref seekPos, val);
@@ -954,14 +882,14 @@ namespace Barotrauma.Networking
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
}
public void Write(String val)
public void WriteString(String val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Identifier val)
public void WriteIdentifier(Identifier val)
{
Write(val.Value);
WriteString(val.Value);
}
public void WriteRangedInteger(int val, int min, int max)
@@ -974,7 +902,7 @@ namespace Barotrauma.Networking
MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
}
public void Write(byte[] val, int startPos, int length)
public void WriteBytes(byte[] val, int startPos, int length)
{
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
}
@@ -984,10 +912,7 @@ namespace Barotrauma.Networking
return MsgReader.ReadBoolean(buf, ref seekPos);
}
public void ReadPadBits()
{
MsgReader.ReadPadBits(buf, ref seekPos);
}
public void ReadPadBits() { MsgReader.ReadPadBits(ref seekPos); }
public byte ReadByte()
{
@@ -1058,7 +983,7 @@ namespace Barotrauma.Networking
{
return MsgReader.ReadColorR8G8B8(buf, ref seekPos);
}
public Color ReadColorR8G8B8A8()
{
return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos);
@@ -1079,10 +1004,10 @@ namespace Barotrauma.Networking
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
}
public void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int outLength)
public byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength)
{
throw new InvalidOperationException("ReadWriteMessages are not to be sent");
}
}
}
}
@@ -40,6 +40,7 @@ namespace Barotrauma.Networking
public static bool IsCompressed(this PacketHeader h)
=> h.HasFlag(PacketHeader.IsCompressed);
#warning TODO: remove?
public static bool IsConnectionInitializationStep(this PacketHeader h)
=> h.HasFlag(PacketHeader.IsConnectionInitializationStep);
@@ -0,0 +1,68 @@
using System;
using System.Runtime.CompilerServices;
using Lidgren.Network;
namespace Barotrauma.Networking
{
internal static class WriteOnlyMessageExtensions
{
#if CLIENT
public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header)
{
msg.WriteByte((byte)header);
return msg;
}
#elif SERVER
public static IWriteMessage WithHeader(this IWriteMessage msg, ServerPacketHeader header)
{
msg.WriteByte((byte)header);
return msg;
}
#endif
public static void WriteNetSerializableStruct(this IWriteMessage msg, INetSerializableStruct serializableStruct)
{
serializableStruct.Write(msg);
}
public static NetOutgoingMessage ToLidgren(this IWriteMessage msg, NetPeer peer)
{
NetOutgoingMessage outMsg = peer.CreateMessage();
outMsg.Write(msg.Buffer, 0, msg.LengthBytes);
return outMsg;
}
}
internal static class NetIncomingMessageExtensions
{
public static T ReadHeader<T>(this NetIncomingMessage msg) where T : Enum
{
byte header = msg.ReadByte();
return Unsafe.As<byte, T>(ref header);
}
public static IReadMessage ToReadMessage(this NetIncomingMessage msg)
{
return new ReadWriteMessage(msg.Data, 0, msg.LengthBits, copyBuf: false);
}
}
internal static class DeliveryMethodExtensions
{
public static NetDeliveryMethod ToLidgren(this DeliveryMethod deliveryMethod) =>
deliveryMethod switch
{
DeliveryMethod.Unreliable => NetDeliveryMethod.Unreliable,
DeliveryMethod.Reliable => NetDeliveryMethod.ReliableUnordered,
DeliveryMethod.ReliableOrdered => NetDeliveryMethod.ReliableOrdered,
_ => NetDeliveryMethod.Unreliable
};
public static Steamworks.P2PSend ToSteam(this DeliveryMethod deliveryMethod) =>
deliveryMethod switch
{
DeliveryMethod.Reliable => Steamworks.P2PSend.Reliable,
DeliveryMethod.ReliableOrdered => Steamworks.P2PSend.Unreliable,
_ => Steamworks.P2PSend.Unreliable
};
}
}
@@ -0,0 +1,142 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma.Networking
{
[NetworkSerialize]
internal struct PeerPacketHeaders : INetSerializableStruct
{
public DeliveryMethod DeliveryMethod;
public PacketHeader PacketHeader;
public ConnectionInitialization? Initialization;
public readonly void Deconstruct(
out DeliveryMethod deliveryMethod,
out PacketHeader packetHeader,
out ConnectionInitialization? initialization)
{
deliveryMethod = DeliveryMethod;
packetHeader = PacketHeader;
initialization = Initialization;
}
}
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
internal struct ClientSteamTicketAndVersionPacket : INetSerializableStruct
{
public string Name;
public Option<int> OwnerKey;
#warning TODO: do something about the type of this
// It probably should be Option<SteamId> but we shouldn't build support for
// writing SteamIDs to INetSerializableStruct; we should consider adding
// attributes to give custom behaviors to specific members of a struct
public Option<AccountId> SteamId;
public Option<byte[]> SteamAuthTicket;
public string GameVersion;
public Identifier Language;
}
[NetworkSerialize]
internal struct SteamP2PInitializationRelayPacket : INetSerializableStruct
{
public ulong LobbyID;
public PeerPacketMessage Message;
}
[NetworkSerialize]
internal struct SteamP2PInitializationOwnerPacket : INetSerializableStruct
{
public string OwnerName;
}
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
internal struct ServerPeerContentPackageOrderPacket : INetSerializableStruct
{
public string ServerName;
public ImmutableArray<ServerContentPackage> ContentPackages;
}
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
internal struct PeerPacketMessage : INetSerializableStruct
{
public byte[] Buffer;
public readonly int Length => Buffer.Length;
public readonly IReadMessage GetReadMessage() => new ReadWriteMessage(Buffer, 0, Length, copyBuf: false);
public readonly IReadMessage GetReadMessage(bool isCompressed, NetworkConnection conn) => new ReadOnlyMessage(Buffer, isCompressed, 0, Length, conn);
}
[NetworkSerialize(ArrayMaxSize = byte.MaxValue)]
internal struct ClientPeerPasswordPacket : INetSerializableStruct
{
public byte[] Password;
}
[NetworkSerialize]
internal struct ServerPeerPasswordPacket : INetSerializableStruct
{
public Option<int> Salt;
public Option<int> RetriesLeft;
}
[NetworkSerialize]
internal struct PeerDisconnectPacket : INetSerializableStruct
{
public string Message;
}
// ReSharper disable MemberCanBePrivate.Global, FieldCanBeMadeReadOnly.Global, UnassignedField.Global
public sealed class ServerContentPackage : INetSerializableStruct
{
[NetworkSerialize]
public string Name = "";
[NetworkSerialize(ArrayMaxSize = ushort.MaxValue)]
public byte[] HashBytes = Array.Empty<byte>();
[NetworkSerialize]
public ulong WorkshopId;
[NetworkSerialize]
public uint InstallTimeDiffInSeconds;
private Md5Hash? cachedHash;
private DateTime? cachedDateTime;
public Md5Hash Hash
{
get => cachedHash ??= Md5Hash.BytesAsHash(HashBytes);
set
{
cachedHash = value;
HashBytes = value.ByteRepresentation;
}
}
public DateTime InstallTime => cachedDateTime ??= DateTime.UtcNow + TimeSpan.FromSeconds(InstallTimeDiffInSeconds);
public RegularPackage? RegularPackage => ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash));
public CorePackage? CorePackage => ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash));
public ContentPackage? ContentPackage => (ContentPackage?)RegularPackage ?? CorePackage;
public ServerContentPackage() { }
public ServerContentPackage(ContentPackage contentPackage, DateTime referenceTime)
{
Name = contentPackage.Name;
Hash = contentPackage.Hash;
WorkshopId = contentPackage.SteamWorkshopId;
InstallTimeDiffInSeconds =
contentPackage.InstallTime is { } installTime
? (uint)(installTime - referenceTime).TotalSeconds
: 0;
}
public string GetPackageStr() => $"\"{Name}\" (hash {Hash.ShortRepresentation})";
}
}
@@ -11,6 +11,11 @@ namespace Barotrauma.Networking
{
partial class RespawnManager : Entity, IServerSerializable
{
/// <summary>
/// How much skills drop towards the job's default skill levels when dying
/// </summary>
const float SkillReductionOnDeath = 0.75f;
public enum State
{
Waiting,
@@ -209,48 +209,48 @@ namespace Barotrauma.Networking
{
case "float":
msg.WriteVariableUInt32(4);
msg.Write((float)overrideValue);
msg.WriteSingle((float)overrideValue);
break;
case "int":
msg.WriteVariableUInt32(4);
msg.Write((int)overrideValue);
msg.WriteInt32((int)overrideValue);
break;
case "vector2":
msg.WriteVariableUInt32(8);
msg.Write(((Vector2)overrideValue).X);
msg.Write(((Vector2)overrideValue).Y);
msg.WriteSingle(((Vector2)overrideValue).X);
msg.WriteSingle(((Vector2)overrideValue).Y);
break;
case "vector3":
msg.WriteVariableUInt32(12);
msg.Write(((Vector3)overrideValue).X);
msg.Write(((Vector3)overrideValue).Y);
msg.Write(((Vector3)overrideValue).Z);
msg.WriteSingle(((Vector3)overrideValue).X);
msg.WriteSingle(((Vector3)overrideValue).Y);
msg.WriteSingle(((Vector3)overrideValue).Z);
break;
case "vector4":
msg.WriteVariableUInt32(16);
msg.Write(((Vector4)overrideValue).X);
msg.Write(((Vector4)overrideValue).Y);
msg.Write(((Vector4)overrideValue).Z);
msg.Write(((Vector4)overrideValue).W);
msg.WriteSingle(((Vector4)overrideValue).X);
msg.WriteSingle(((Vector4)overrideValue).Y);
msg.WriteSingle(((Vector4)overrideValue).Z);
msg.WriteSingle(((Vector4)overrideValue).W);
break;
case "color":
msg.WriteVariableUInt32(4);
msg.Write(((Color)overrideValue).R);
msg.Write(((Color)overrideValue).G);
msg.Write(((Color)overrideValue).B);
msg.Write(((Color)overrideValue).A);
msg.WriteByte(((Color)overrideValue).R);
msg.WriteByte(((Color)overrideValue).G);
msg.WriteByte(((Color)overrideValue).B);
msg.WriteByte(((Color)overrideValue).A);
break;
case "rectangle":
msg.WriteVariableUInt32(16);
msg.Write(((Rectangle)overrideValue).X);
msg.Write(((Rectangle)overrideValue).Y);
msg.Write(((Rectangle)overrideValue).Width);
msg.Write(((Rectangle)overrideValue).Height);
msg.WriteInt32(((Rectangle)overrideValue).X);
msg.WriteInt32(((Rectangle)overrideValue).Y);
msg.WriteInt32(((Rectangle)overrideValue).Width);
msg.WriteInt32(((Rectangle)overrideValue).Height);
break;
default:
string strVal = overrideValue.ToString();
msg.Write(strVal);
msg.WriteString(strVal);
break;
}
}
@@ -550,7 +550,7 @@ namespace Barotrauma.Networking
public bool HasPassword
{
get { return password != null; }
get { return !string.IsNullOrEmpty(password); }
#if CLIENT
set
{
@@ -953,14 +953,7 @@ namespace Barotrauma.Networking
public void SetPassword(string password)
{
if (string.IsNullOrEmpty(password))
{
this.password = null;
}
else
{
this.password = password;
}
this.password = string.IsNullOrEmpty(password) ? null : password;
}
public static byte[] SaltPassword(byte[] password, int salt)
@@ -977,14 +970,9 @@ namespace Barotrauma.Networking
public bool IsPasswordCorrect(byte[] input, int salt)
{
if (!HasPassword) return true;
if (!HasPassword) { return true; }
byte[] saltedPw = SaltPassword(Encoding.UTF8.GetBytes(password), salt);
if (input.Length != saltedPw.Length) return false;
for (int i = 0; i < input.Length; i++)
{
if (input[i] != saltedPw[i]) return false;
}
return true;
return saltedPw.SequenceEqual(input);
}
/// <summary>
@@ -1039,7 +1027,7 @@ namespace Barotrauma.Networking
msg.WriteVariableUInt32((uint)monsterNames.Count);
foreach (Identifier s in monsterNames)
{
msg.Write(monsterEnabled[s]);
msg.WriteBoolean(monsterEnabled[s]);
}
msg.WritePadBits();
}
@@ -1071,15 +1059,15 @@ namespace Barotrauma.Networking
{
if (ExtraCargo == null)
{
msg.Write((UInt32)0);
msg.WriteUInt32((UInt32)0);
return;
}
msg.Write((UInt32)ExtraCargo.Count);
msg.WriteUInt32((UInt32)ExtraCargo.Count);
foreach (KeyValuePair<ItemPrefab, int> kvp in ExtraCargo)
{
msg.Write(kvp.Key.Identifier);
msg.Write((byte)kvp.Value);
msg.WriteIdentifier(kvp.Key.Identifier);
msg.WriteByte((byte)kvp.Value);
}
}
@@ -1109,7 +1097,7 @@ namespace Barotrauma.Networking
msg.WriteVariableUInt32((uint)HiddenSubs.Count);
foreach (string submarineName in HiddenSubs)
{
msg.Write((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase)));
msg.WriteUInt16((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase)));
}
}
}
@@ -119,16 +119,16 @@ namespace Barotrauma.Networking
{
if (!CanSend) { throw new Exception("Called Write on a VoipQueue not set up for sending"); }
msg.Write((UInt16)LatestBufferID);
msg.Write(ForceLocal); msg.WritePadBits();
msg.WriteUInt16((UInt16)LatestBufferID);
msg.WriteBoolean(ForceLocal); msg.WritePadBits();
lock (buffers)
{
for (int i = 0; i < BUFFER_COUNT; i++)
{
int index = (newestBufferInd + i + 1) % BUFFER_COUNT;
msg.Write((byte)bufferLengths[index]);
msg.Write(buffers[index], 0, bufferLengths[index]);
msg.WriteByte((byte)bufferLengths[index]);
msg.WriteBytes(buffers[index], 0, bufferLengths[index]);
}
}
}
@@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
namespace Barotrauma.Networking
{
partial class WhiteListedPlayer
{
public string Name;
public string IP;
public UInt16 UniqueIdentifier;
}
partial class WhiteList
{
const string SavePath = "Data/whitelist.txt";
private List<WhiteListedPlayer> whitelistedPlayers;
public List<WhiteListedPlayer> WhiteListedPlayers
{
get { return whitelistedPlayers; }
}
public bool Enabled;
partial void InitProjSpecific();
public WhiteList()
{
Enabled = false;
whitelistedPlayers = new List<WhiteListedPlayer>();
InitProjSpecific();
}
}
}
@@ -508,7 +508,14 @@ namespace Barotrauma
try
{
return (float)PropertyInfo.GetValue(parentObject, null);
if (PropertyType == typeof(int))
{
return (int)PropertyInfo.GetValue(parentObject, null);
}
else
{
return (float)PropertyInfo.GetValue(parentObject, null);
}
}
catch (TargetInvocationException e)
{

Some files were not shown because too many files have changed in this diff Show More