Revert "OBT1.1.0 Merge branch 'dev_pte' into dev"
This reverts commit177cf89756, reversing changes made to42ba733cd4.
This commit is contained in:
@@ -1,67 +1,13 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread-safe wrapper for AITarget list operations.
|
||||
/// Uses copy-on-write pattern for lock-free reads.
|
||||
/// </summary>
|
||||
class ThreadSafeAITargetList : IEnumerable<AITarget>
|
||||
{
|
||||
private volatile List<AITarget> _list = new List<AITarget>();
|
||||
private readonly object _writeLock = new object();
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public void Add(AITarget target)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
var newList = new List<AITarget>(_list) { target };
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(AITarget target)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
var newList = new List<AITarget>(_list);
|
||||
bool removed = newList.Remove(target);
|
||||
if (removed)
|
||||
{
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Interlocked.Exchange(ref _list, new List<AITarget>());
|
||||
}
|
||||
|
||||
public bool Contains(AITarget target) => _list.Contains(target);
|
||||
|
||||
public AITarget this[int index] => _list[index];
|
||||
|
||||
public IEnumerator<AITarget> GetEnumerator() => _list.GetEnumerator();
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public List<AITarget> ToList() => new List<AITarget>(_list);
|
||||
public AITarget FirstOrDefault(Func<AITarget, bool> predicate) => _list.FirstOrDefault(predicate);
|
||||
public IEnumerable<AITarget> Where(Func<AITarget, bool> predicate) => _list.Where(predicate);
|
||||
public bool Any(Func<AITarget, bool> predicate) => _list.Any(predicate);
|
||||
}
|
||||
|
||||
partial class AITarget
|
||||
{
|
||||
public static ThreadSafeAITargetList List = new ThreadSafeAITargetList();
|
||||
public static List<AITarget> List = new List<AITarget>();
|
||||
|
||||
private Entity entity;
|
||||
public Entity Entity
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -1818,9 +1817,7 @@ namespace Barotrauma
|
||||
public static bool HasDivingMask(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
||||
=> HasItem(character, Tags.LightDivingGear, out _, requireOxygenTank ? Tags.OxygenSource : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
||||
|
||||
// ThreadLocal to ensure thread safety - each thread gets its own list instance
|
||||
private static readonly ThreadLocal<List<Item>> matchingItemsLocal = new ThreadLocal<List<Item>>(() => new List<Item>());
|
||||
private static List<Item> matchingItems => matchingItemsLocal.Value;
|
||||
private static List<Item> matchingItems = new List<Item>();
|
||||
|
||||
/// <summary>
|
||||
/// Note: uses a single list for matching items. The item is reused each time when the method is called. So if you use the method twice, and then refer to the first items, you'll actually get the second.
|
||||
@@ -1828,16 +1825,15 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static bool HasItem(Character character, Identifier tagOrIdentifier, out IEnumerable<Item> items, Identifier containedTag = default, float conditionPercentage = 0, bool requireEquipped = false, bool recursive = true, Func<Item, bool> predicate = null)
|
||||
{
|
||||
var localMatchingItems = matchingItems;
|
||||
localMatchingItems.Clear();
|
||||
items = localMatchingItems;
|
||||
matchingItems.Clear();
|
||||
items = matchingItems;
|
||||
if (character?.Inventory == null) { return false; }
|
||||
character.Inventory.FindAllItems(i => (i.Prefab.Identifier == tagOrIdentifier || i.HasTag(tagOrIdentifier)) &&
|
||||
matchingItems = character.Inventory.FindAllItems(i => (i.Prefab.Identifier == tagOrIdentifier || i.HasTag(tagOrIdentifier)) &&
|
||||
i.ConditionPercentage >= conditionPercentage &&
|
||||
(!requireEquipped || character.HasEquippedItem(i)) &&
|
||||
(predicate == null || predicate(i)), recursive, localMatchingItems);
|
||||
items = localMatchingItems;
|
||||
foreach (var item in localMatchingItems)
|
||||
(predicate == null || predicate(i)), recursive, matchingItems);
|
||||
items = matchingItems;
|
||||
foreach (var item in matchingItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
@@ -10,8 +9,7 @@ namespace Barotrauma
|
||||
{
|
||||
class NPCConversationCollection : Prefab
|
||||
{
|
||||
// Thread-safe dictionary for language-based collections
|
||||
public static readonly ConcurrentDictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>> Collections = new ConcurrentDictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>>();
|
||||
public static readonly Dictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>> Collections = new Dictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>>();
|
||||
|
||||
public readonly LanguageIdentifier Language;
|
||||
|
||||
@@ -162,24 +160,7 @@ namespace Barotrauma
|
||||
return currentFlags;
|
||||
}
|
||||
|
||||
// Thread-safe previous conversations tracking using copy-on-write pattern
|
||||
private static volatile List<NPCConversation> _previousConversations = new List<NPCConversation>();
|
||||
private static readonly object _previousConversationsLock = new object();
|
||||
private static List<NPCConversation> previousConversations => _previousConversations;
|
||||
|
||||
private static void AddToPreviousConversations(NPCConversation conversation)
|
||||
{
|
||||
lock (_previousConversationsLock)
|
||||
{
|
||||
var newList = new List<NPCConversation>(_previousConversations);
|
||||
newList.Insert(0, conversation);
|
||||
if (newList.Count > MaxPreviousConversations)
|
||||
{
|
||||
newList.RemoveAt(MaxPreviousConversations);
|
||||
}
|
||||
_previousConversations = newList;
|
||||
}
|
||||
}
|
||||
private static readonly List<NPCConversation> previousConversations = new List<NPCConversation>();
|
||||
|
||||
public static List<(Character speaker, string line)> CreateRandom(List<Character> availableSpeakers)
|
||||
{
|
||||
@@ -300,7 +281,8 @@ namespace Barotrauma
|
||||
|
||||
if (baseConversation == null)
|
||||
{
|
||||
AddToPreviousConversations(selectedConversation);
|
||||
previousConversations.Insert(0, selectedConversation);
|
||||
if (previousConversations.Count > MaxPreviousConversations) previousConversations.RemoveAt(MaxPreviousConversations);
|
||||
}
|
||||
lineList.Add((speaker, selectedConversation.Line));
|
||||
CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations);
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool CheckObjectiveState()
|
||||
{
|
||||
if (item.IgnoreByAI(character) || Item.IsMarkedForDeconstruction(item))
|
||||
if (item.IgnoreByAI(character) || Item.DeconstructItems.Contains(item))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ namespace Barotrauma
|
||||
if (!allowUnloading) { return false; }
|
||||
if (requireValidContainer && !IsValidContainer(item.Container, character)) { return false; }
|
||||
}
|
||||
if (ignoreItemsMarkedForDeconstruction && Item.IsMarkedForDeconstruction(item)) { return false; }
|
||||
if (ignoreItemsMarkedForDeconstruction && Item.DeconstructItems.Contains(item)) { return false; }
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.HasBallastFloraInHull) { return false; }
|
||||
|
||||
+1
-3
@@ -1,6 +1,5 @@
|
||||
#nullable enable
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
@@ -69,9 +68,8 @@ namespace Barotrauma
|
||||
|
||||
/// <summary>
|
||||
/// When did the character last inspect whether some other character has stolen items on them?
|
||||
/// Thread-safe dictionary for concurrent access.
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Character, double> lastInspectionTimes = new ConcurrentDictionary<Character, double>();
|
||||
private static readonly Dictionary<Character, double> lastInspectionTimes = new Dictionary<Character, double>();
|
||||
|
||||
private const float NormalInspectionInterval = 120.0f;
|
||||
private const float CriminalInspectionInterval = 30.0f;
|
||||
|
||||
@@ -440,7 +440,7 @@ namespace Barotrauma
|
||||
|
||||
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
|
||||
{
|
||||
if (item.AllowDeconstruct && !Item.IsMarkedForDeconstruction(item) &&
|
||||
if (item.AllowDeconstruct && !Item.DeconstructItems.Contains(item) &&
|
||||
//only allow deconstructing if there are no deconstruction recipes (= deconstructing yields nothing), or deconstruction recipes that
|
||||
(item.Prefab.DeconstructItems.None() ||
|
||||
item.Prefab.DeconstructItems.Any(deconstructItem =>
|
||||
@@ -454,7 +454,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (Identifier == Tags.DontDeconstructThis)
|
||||
{
|
||||
if (Item.IsMarkedForDeconstruction(item)) { return true; }
|
||||
if (Item.DeconstructItems.Contains(item)) { return true; }
|
||||
}
|
||||
|
||||
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
|
||||
|
||||
@@ -5,10 +5,8 @@ using FarseerPhysics.Dynamics.Contacts;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
||||
@@ -27,33 +25,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
const float MaxImpactDamage = 0.1f;
|
||||
|
||||
// Thread-safe list using copy-on-write pattern (ConcurrentBag doesn't support indexer/Remove)
|
||||
private static volatile List<Ragdoll> _list = new List<Ragdoll>();
|
||||
private static readonly object _listLock = new object();
|
||||
private static List<Ragdoll> list => _list;
|
||||
|
||||
private static void ListAdd(Ragdoll ragdoll)
|
||||
{
|
||||
lock (_listLock)
|
||||
{
|
||||
var newList = new List<Ragdoll>(_list) { ragdoll };
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ListRemove(Ragdoll ragdoll)
|
||||
{
|
||||
lock (_listLock)
|
||||
{
|
||||
var newList = new List<Ragdoll>(_list);
|
||||
bool removed = newList.Remove(ragdoll);
|
||||
if (removed)
|
||||
{
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
private static readonly List<Ragdoll> list = new List<Ragdoll>();
|
||||
|
||||
struct Impact
|
||||
{
|
||||
@@ -73,8 +45,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-safe queue for physics collision callbacks
|
||||
private readonly ConcurrentQueue<Impact> impactQueue = new ConcurrentQueue<Impact>();
|
||||
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||
|
||||
protected Hull currentHull;
|
||||
|
||||
@@ -496,7 +467,7 @@ namespace Barotrauma
|
||||
|
||||
public Ragdoll(Character character, string seed, RagdollParams ragdollParams = null)
|
||||
{
|
||||
ListAdd(this);
|
||||
list.Add(this);
|
||||
this.character = character;
|
||||
Recreate(ragdollParams ?? RagdollParams);
|
||||
}
|
||||
@@ -773,7 +744,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (!f2.IsSensor)
|
||||
{
|
||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||
lock (impactQueue)
|
||||
{
|
||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -845,7 +819,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||
lock (impactQueue)
|
||||
{
|
||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1297,8 +1274,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (!character.Enabled || character.Removed || Frozen || Invalid || Collider == null || Collider.Removed) { return; }
|
||||
|
||||
while (impactQueue.TryDequeue(out var impact))
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
var impact = impactQueue.Dequeue();
|
||||
ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity);
|
||||
}
|
||||
|
||||
@@ -2347,7 +2325,7 @@ namespace Barotrauma
|
||||
LimbJoints = null;
|
||||
}
|
||||
|
||||
ListRemove(this);
|
||||
list.Remove(this);
|
||||
}
|
||||
|
||||
public static void RemoveAll()
|
||||
|
||||
@@ -7,12 +7,10 @@ using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
#if SERVER
|
||||
using System.Text;
|
||||
@@ -30,70 +28,12 @@ namespace Barotrauma
|
||||
|
||||
public readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier);
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe wrapper for character list operations.
|
||||
/// Provides lock-free read operations and synchronized write operations.
|
||||
/// </summary>
|
||||
class ThreadSafeCharacterList : IEnumerable<Character>
|
||||
{
|
||||
private volatile List<Character> _list = new List<Character>();
|
||||
private readonly object _writeLock = new object();
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public void Add(Character character)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
var newList = new List<Character>(_list) { character };
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(Character character)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
var newList = new List<Character>(_list);
|
||||
bool removed = newList.Remove(character);
|
||||
if (removed)
|
||||
{
|
||||
Interlocked.Exchange(ref _list, newList);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Interlocked.Exchange(ref _list, new List<Character>());
|
||||
}
|
||||
|
||||
public bool Contains(Character character) => _list.Contains(character);
|
||||
|
||||
public Character this[int index] => _list[index];
|
||||
|
||||
public IEnumerator<Character> GetEnumerator() => _list.GetEnumerator();
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
// LINQ-friendly snapshot for complex queries
|
||||
public List<Character> ToList() => new List<Character>(_list);
|
||||
|
||||
public Character FirstOrDefault(Func<Character, bool> predicate) => _list.FirstOrDefault(predicate);
|
||||
public Character Find(Predicate<Character> predicate) => _list.Find(predicate);
|
||||
public List<Character> FindAll(Predicate<Character> predicate) => _list.FindAll(predicate);
|
||||
public IEnumerable<Character> Where(Func<Character, bool> predicate) => _list.Where(predicate);
|
||||
public bool Any(Func<Character, bool> predicate) => _list.Any(predicate);
|
||||
public bool None(Func<Character, bool> predicate) => !_list.Any(predicate);
|
||||
public int CountWhere(Func<Character, bool> predicate) => _list.Count(predicate);
|
||||
}
|
||||
|
||||
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
||||
{
|
||||
public static readonly ThreadSafeCharacterList CharacterList = new ThreadSafeCharacterList();
|
||||
public static readonly List<Character> CharacterList = new List<Character>();
|
||||
|
||||
public static int CharacterUpdateInterval = 1;
|
||||
private static volatile int characterUpdateTick = 1;
|
||||
private static int characterUpdateTick = 1;
|
||||
|
||||
public const float MaxHighlightDistance = 150.0f;
|
||||
public const float MaxDragDistance = 200.0f;
|
||||
@@ -2821,11 +2761,10 @@ namespace Barotrauma
|
||||
}
|
||||
int itemsPerFrame = IsOnPlayerTeam ? 100 : 10;
|
||||
int checkedItemCount = 0;
|
||||
var cachedItems = Item.GetCachedItemList();
|
||||
for (int i = 0; i < itemsPerFrame && itemIndex < cachedItems.Count; i++, itemIndex++)
|
||||
for (int i = 0; i < itemsPerFrame && itemIndex < Item.ItemList.Count; i++, itemIndex++)
|
||||
{
|
||||
checkedItemCount++;
|
||||
var item = cachedItems[itemIndex];
|
||||
var item = Item.ItemList[itemIndex];
|
||||
if (!item.IsInteractable(this)) { continue; }
|
||||
if (ignoredItems != null && ignoredItems.Contains(item)) { continue; }
|
||||
if (item.Submarine == null) { continue; }
|
||||
@@ -2861,10 +2800,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
targetItem = _foundItem;
|
||||
bool completed = itemIndex >= cachedItems.Count - 1;
|
||||
bool completed = itemIndex >= Item.ItemList.Count - 1;
|
||||
if (HumanAIController.DebugAI && checkedItemCount > 0 && targetItem != null && StopWatch.ElapsedMilliseconds > 1)
|
||||
{
|
||||
var msg = $"Went through {checkedItemCount} of total {cachedItems.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {completed}";
|
||||
var msg = $"Went through {checkedItemCount} of total {Item.ItemList.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {completed}";
|
||||
if (StopWatch.ElapsedMilliseconds > 5)
|
||||
{
|
||||
DebugConsole.ThrowError(msg);
|
||||
@@ -4880,11 +4819,7 @@ namespace Barotrauma
|
||||
HealthUpdateInterval = 0.0f;
|
||||
}
|
||||
|
||||
// Thread-static to avoid concurrent modification in parallel item updates
|
||||
[ThreadStatic]
|
||||
private static List<ISerializableEntity> t_statusEffectTargets;
|
||||
private static List<ISerializableEntity> StatusEffectTargets => t_statusEffectTargets ??= new List<ISerializableEntity>();
|
||||
|
||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||
{
|
||||
if (actionType == ActionType.OnEating)
|
||||
@@ -4913,7 +4848,6 @@ namespace Barotrauma
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = StatusEffectTargets;
|
||||
targets.Clear();
|
||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||
statusEffect.Apply(actionType, deltaTime, this, targets);
|
||||
|
||||
@@ -3,13 +3,15 @@ using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Globalization;
|
||||
using MoonSharp.Interpreter;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -130,9 +132,8 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<LimbHealth> limbHealths = new List<LimbHealth>();
|
||||
|
||||
// Thread-safe afflictions dictionary for concurrent access
|
||||
private readonly ConcurrentDictionary<Affliction, LimbHealth> afflictions = new ConcurrentDictionary<Affliction, LimbHealth>();
|
||||
private readonly ConcurrentDictionary<Affliction, byte> irremovableAfflictions = new ConcurrentDictionary<Affliction, byte>();
|
||||
private readonly Dictionary<Affliction, LimbHealth> afflictions = new Dictionary<Affliction, LimbHealth>();
|
||||
private readonly HashSet<Affliction> irremovableAfflictions = new HashSet<Affliction>();
|
||||
private Affliction bloodlossAffliction;
|
||||
private Affliction oxygenLowAffliction;
|
||||
private Affliction pressureAffliction;
|
||||
@@ -323,13 +324,13 @@ namespace Barotrauma
|
||||
|
||||
private void InitIrremovableAfflictions()
|
||||
{
|
||||
irremovableAfflictions.TryAdd(bloodlossAffliction = new Affliction(AfflictionPrefab.Bloodloss, 0.0f), 0);
|
||||
irremovableAfflictions.TryAdd(stunAffliction = new Affliction(AfflictionPrefab.Stun, 0.0f), 0);
|
||||
irremovableAfflictions.TryAdd(pressureAffliction = new Affliction(AfflictionPrefab.Pressure, 0.0f), 0);
|
||||
irremovableAfflictions.TryAdd(oxygenLowAffliction = new Affliction(AfflictionPrefab.OxygenLow, 0.0f), 0);
|
||||
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
||||
irremovableAfflictions.Add(bloodlossAffliction = new Affliction(AfflictionPrefab.Bloodloss, 0.0f));
|
||||
irremovableAfflictions.Add(stunAffliction = new Affliction(AfflictionPrefab.Stun, 0.0f));
|
||||
irremovableAfflictions.Add(pressureAffliction = new Affliction(AfflictionPrefab.Pressure, 0.0f));
|
||||
irremovableAfflictions.Add(oxygenLowAffliction = new Affliction(AfflictionPrefab.OxygenLow, 0.0f));
|
||||
foreach (Affliction affliction in irremovableAfflictions)
|
||||
{
|
||||
afflictions.TryAdd(affliction, null);
|
||||
afflictions.Add(affliction, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +338,7 @@ namespace Barotrauma
|
||||
|
||||
public IReadOnlyCollection<Affliction> GetAllAfflictions()
|
||||
{
|
||||
return afflictions.Keys.ToList();
|
||||
return afflictions.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<Affliction> GetAllAfflictions(Func<Affliction, bool> limbHealthFilter)
|
||||
@@ -502,18 +503,19 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public float GetResistance(AfflictionPrefab afflictionPrefab, LimbType limbType)
|
||||
{
|
||||
// ConcurrentDictionary is thread-safe, no lock needed
|
||||
// This is a % resistance (0 to 1.0)
|
||||
float resistance = 0.0f;
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
resistance += affliction.GetResistance(afflictionPrefab.Identifier, limbType);
|
||||
lock (afflictions) {
|
||||
// This is a % resistance (0 to 1.0)
|
||||
float resistance = 0.0f;
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
resistance += affliction.GetResistance(afflictionPrefab.Identifier, limbType);
|
||||
}
|
||||
// This is a multiplier, ie. 0.0 = 100% resistance and 1.0 = 0% resistance
|
||||
float abilityResistanceMultiplier = Character.GetAbilityResistance(afflictionPrefab);
|
||||
// The returned value is calculated to be a % resistance again
|
||||
return 1 - ((1 - resistance) * abilityResistanceMultiplier);
|
||||
}
|
||||
// This is a multiplier, ie. 0.0 = 100% resistance and 1.0 = 0% resistance
|
||||
float abilityResistanceMultiplier = Character.GetAbilityResistance(afflictionPrefab);
|
||||
// The returned value is calculated to be a % resistance again
|
||||
return 1 - ((1 - resistance) * abilityResistanceMultiplier);
|
||||
}
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
@@ -537,25 +539,20 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
// Thread-static to avoid concurrent modification in parallel item updates
|
||||
[ThreadStatic]
|
||||
private static List<Affliction> t_matchingAfflictions;
|
||||
private static List<Affliction> MatchingAfflictions => t_matchingAfflictions ??= new List<Affliction>();
|
||||
private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
|
||||
|
||||
public void ReduceAllAfflictionsOnAllLimbs(float amount, ActionType? treatmentAction = null)
|
||||
{
|
||||
var matchingAfflictions = MatchingAfflictions;
|
||||
matchingAfflictions.Clear();
|
||||
matchingAfflictions.AddRange(afflictions.Keys);
|
||||
|
||||
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction);
|
||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
||||
}
|
||||
|
||||
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
||||
{
|
||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||
|
||||
var matchingAfflictions = MatchingAfflictions;
|
||||
matchingAfflictions.Clear();
|
||||
foreach (var affliction in afflictions)
|
||||
{
|
||||
@@ -565,7 +562,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction, attacker);
|
||||
ReduceMatchingAfflictions(amount, treatmentAction, attacker);
|
||||
}
|
||||
|
||||
private IEnumerable<Affliction> GetAfflictionsForLimb(Limb targetLimb)
|
||||
@@ -575,11 +572,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||
|
||||
var matchingAfflictions = MatchingAfflictions;
|
||||
matchingAfflictions.Clear();
|
||||
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
|
||||
|
||||
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction);
|
||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
||||
}
|
||||
|
||||
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
||||
@@ -587,7 +583,6 @@ namespace Barotrauma
|
||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||
|
||||
var matchingAfflictions = MatchingAfflictions;
|
||||
matchingAfflictions.Clear();
|
||||
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
|
||||
foreach (var affliction in afflictions)
|
||||
@@ -598,10 +593,10 @@ namespace Barotrauma
|
||||
matchingAfflictions.Add(affliction.Key);
|
||||
}
|
||||
}
|
||||
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction, attacker);
|
||||
ReduceMatchingAfflictions(amount, treatmentAction, attacker);
|
||||
}
|
||||
|
||||
private void ReduceMatchingAfflictions(List<Affliction> matchingAfflictions, float amount, ActionType? treatmentAction, Character attacker = null)
|
||||
private void ReduceMatchingAfflictions(float amount, ActionType? treatmentAction, Character attacker = null)
|
||||
{
|
||||
if (matchingAfflictions.Count == 0) { return; }
|
||||
|
||||
@@ -688,19 +683,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-static to avoid concurrent modification when multiple characters are updated in parallel
|
||||
[ThreadStatic]
|
||||
private static List<Affliction> t_afflictionsToRemove;
|
||||
[ThreadStatic]
|
||||
private static List<KeyValuePair<Affliction, LimbHealth>> t_afflictionsToUpdate;
|
||||
private static List<Affliction> AfflictionsToRemove => t_afflictionsToRemove ??= new List<Affliction>();
|
||||
private static List<KeyValuePair<Affliction, LimbHealth>> AfflictionsToUpdate => t_afflictionsToUpdate ??= new List<KeyValuePair<Affliction, LimbHealth>>();
|
||||
|
||||
private readonly static List<Affliction> afflictionsToRemove = new List<Affliction>();
|
||||
private readonly static List<KeyValuePair<Affliction, LimbHealth>> afflictionsToUpdate = new List<KeyValuePair<Affliction, LimbHealth>>();
|
||||
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
|
||||
{
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
|
||||
var afflictionsToRemove = AfflictionsToRemove;
|
||||
afflictionsToRemove.Clear();
|
||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
||||
a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType ||
|
||||
@@ -708,14 +696,14 @@ namespace Barotrauma
|
||||
a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType));
|
||||
foreach (var affliction in afflictionsToRemove)
|
||||
{
|
||||
afflictions.TryRemove(affliction, out _);
|
||||
afflictions.Remove(affliction);
|
||||
}
|
||||
|
||||
foreach (LimbHealth limbHealth in limbHealths)
|
||||
{
|
||||
if (damageAmount > 0.0f) { afflictions.TryAdd(AfflictionPrefab.InternalDamage.Instantiate(damageAmount), limbHealth); }
|
||||
if (bleedingDamageAmount > 0.0f && DoesBleed) { afflictions.TryAdd(AfflictionPrefab.Bleeding.Instantiate(bleedingDamageAmount), limbHealth); }
|
||||
if (burnDamageAmount > 0.0f) { afflictions.TryAdd(AfflictionPrefab.Burn.Instantiate(burnDamageAmount), limbHealth); }
|
||||
if (damageAmount > 0.0f) { afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damageAmount), limbHealth); }
|
||||
if (bleedingDamageAmount > 0.0f && DoesBleed) { afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamageAmount), limbHealth); }
|
||||
if (burnDamageAmount > 0.0f) { afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamageAmount), limbHealth); }
|
||||
}
|
||||
|
||||
RecalculateVitality();
|
||||
@@ -751,28 +739,26 @@ namespace Barotrauma
|
||||
|
||||
public void RemoveAfflictions(Func<Affliction, bool> predicate)
|
||||
{
|
||||
var afflictionsToRemove = AfflictionsToRemove;
|
||||
afflictionsToRemove.Clear();
|
||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(affliction => predicate(affliction)));
|
||||
foreach (var affliction in afflictionsToRemove)
|
||||
{
|
||||
afflictions.TryRemove(affliction, out _);
|
||||
afflictions.Remove(affliction);
|
||||
}
|
||||
CalculateVitality();
|
||||
}
|
||||
|
||||
public void RemoveAllAfflictions()
|
||||
{
|
||||
var afflictionsToRemove = AfflictionsToRemove;
|
||||
afflictionsToRemove.Clear();
|
||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.ContainsKey(a)));
|
||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.Contains(a)));
|
||||
foreach (var affliction in afflictionsToRemove)
|
||||
{
|
||||
//set strength to 0 in case the affliction needs to react to becoming inactive
|
||||
affliction.Strength = 0.0f;
|
||||
afflictions.TryRemove(affliction, out _);
|
||||
afflictions.Remove(affliction);
|
||||
}
|
||||
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
||||
foreach (Affliction affliction in irremovableAfflictions)
|
||||
{
|
||||
affliction.Strength = 0.0f;
|
||||
}
|
||||
@@ -781,18 +767,17 @@ namespace Barotrauma
|
||||
|
||||
public void RemoveNegativeAfflictions()
|
||||
{
|
||||
var afflictionsToRemove = AfflictionsToRemove;
|
||||
afflictionsToRemove.Clear();
|
||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
||||
!irremovableAfflictions.ContainsKey(a) &&
|
||||
!irremovableAfflictions.Contains(a) &&
|
||||
!a.Prefab.IsBuff &&
|
||||
a.Prefab.AfflictionType != "geneticmaterialbuff" &&
|
||||
a.Prefab.AfflictionType != "geneticmaterialdebuff"));
|
||||
foreach (var affliction in afflictionsToRemove)
|
||||
{
|
||||
afflictions.TryRemove(affliction, out _);
|
||||
afflictions.Remove(affliction);
|
||||
}
|
||||
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
||||
foreach (Affliction affliction in irremovableAfflictions)
|
||||
{
|
||||
affliction.Strength = 0.0f;
|
||||
}
|
||||
@@ -884,7 +869,7 @@ namespace Barotrauma
|
||||
var copyAffliction = newAffliction.Prefab.Instantiate(
|
||||
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab, limbType))),
|
||||
newAffliction.Source);
|
||||
afflictions.TryAdd(copyAffliction, limbHealth);
|
||||
afflictions.Add(copyAffliction, limbHealth);
|
||||
AchievementManager.OnAfflictionReceived(copyAffliction, Character);
|
||||
MedicalClinic.OnAfflictionCountChanged(Character);
|
||||
|
||||
@@ -921,8 +906,6 @@ namespace Barotrauma
|
||||
|
||||
if (!Character.GodMode)
|
||||
{
|
||||
var afflictionsToRemove = AfflictionsToRemove;
|
||||
var afflictionsToUpdate = AfflictionsToUpdate;
|
||||
afflictionsToRemove.Clear();
|
||||
afflictionsToUpdate.Clear();
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
@@ -931,7 +914,7 @@ namespace Barotrauma
|
||||
if (affliction.Strength <= 0.0f)
|
||||
{
|
||||
AchievementManager.OnAfflictionRemoved(affliction, Character);
|
||||
if (!irremovableAfflictions.ContainsKey(affliction)) { afflictionsToRemove.Add(affliction); }
|
||||
if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
|
||||
continue;
|
||||
}
|
||||
if (affliction.Prefab.Duration > 0.0f)
|
||||
@@ -969,7 +952,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (var affliction in afflictionsToRemove)
|
||||
{
|
||||
afflictions.TryRemove(affliction, out _);
|
||||
afflictions.Remove(affliction);
|
||||
}
|
||||
|
||||
if (afflictionsToRemove.Count is not 0)
|
||||
@@ -1217,14 +1200,9 @@ namespace Barotrauma
|
||||
return (causeOfDeath, strongestAffliction);
|
||||
}
|
||||
|
||||
// Thread-static to avoid concurrent modification in parallel item updates
|
||||
[ThreadStatic]
|
||||
private static List<Affliction> t_allAfflictions;
|
||||
private static List<Affliction> AllAfflictionsList => t_allAfflictions ??= new List<Affliction>();
|
||||
|
||||
private readonly List<Affliction> allAfflictions = new List<Affliction>();
|
||||
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||
{
|
||||
var allAfflictions = AllAfflictionsList;
|
||||
allAfflictions.Clear();
|
||||
if (!mergeSameAfflictions)
|
||||
{
|
||||
@@ -1407,17 +1385,10 @@ namespace Barotrauma
|
||||
return MathHelper.Clamp(strength, 0.0f, affliction.Prefab.MaxStrength);
|
||||
}
|
||||
|
||||
// Thread-static to avoid concurrent modification in parallel updates
|
||||
[ThreadStatic]
|
||||
private static List<Affliction> t_activeAfflictions;
|
||||
[ThreadStatic]
|
||||
private static List<(LimbHealth limbHealth, Affliction affliction)> t_limbAfflictions;
|
||||
private static List<Affliction> ActiveAfflictionsList => t_activeAfflictions ??= new List<Affliction>();
|
||||
private static List<(LimbHealth limbHealth, Affliction affliction)> LimbAfflictionsList => t_limbAfflictions ??= new List<(LimbHealth limbHealth, Affliction affliction)>();
|
||||
|
||||
private readonly List<Affliction> activeAfflictions = new List<Affliction>();
|
||||
private readonly List<(LimbHealth limbHealth, Affliction affliction)> limbAfflictions = new List<(LimbHealth limbHealth, Affliction affliction)>();
|
||||
public void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
var activeAfflictions = ActiveAfflictionsList;
|
||||
activeAfflictions.Clear();
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
@@ -1443,7 +1414,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
var limbAfflictions = LimbAfflictionsList;
|
||||
limbAfflictions.Clear();
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
@@ -1473,9 +1443,8 @@ namespace Barotrauma
|
||||
public void Remove()
|
||||
{
|
||||
RemoveProjSpecific();
|
||||
// Clear thread-static lists to help with garbage collection
|
||||
AfflictionsToRemove.Clear();
|
||||
AfflictionsToUpdate.Clear();
|
||||
afflictionsToRemove.Clear();
|
||||
afflictionsToUpdate.Clear();
|
||||
}
|
||||
|
||||
partial void RemoveProjSpecific();
|
||||
@@ -1550,14 +1519,14 @@ namespace Barotrauma
|
||||
}
|
||||
if (afflictionPredicate != null && !afflictionPredicate.Invoke(afflictionPrefab)) { return; }
|
||||
float strength = afflictionElement.GetAttributeFloat("strength", 0.0f);
|
||||
var irremovableAffliction = irremovableAfflictions.Keys.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
||||
var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
||||
if (irremovableAffliction != null)
|
||||
{
|
||||
irremovableAffliction.Strength = strength;
|
||||
}
|
||||
else
|
||||
{
|
||||
afflictions.TryAdd(afflictionPrefab.Instantiate(strength), limbHealth);
|
||||
afflictions.Add(afflictionPrefab.Instantiate(strength), limbHealth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,14 +797,16 @@ namespace Barotrauma
|
||||
return AddDamage(simPosition, afflictions, playSound);
|
||||
}
|
||||
|
||||
// Thread-safe: using local variables instead of instance fields to avoid concurrent modification
|
||||
private readonly List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
|
||||
private readonly List<DamageModifier> tempModifiers = new List<DamageModifier>();
|
||||
private readonly List<Affliction> afflictionsCopy = new List<Affliction>();
|
||||
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null)
|
||||
{
|
||||
var appliedDamageModifiers = new List<DamageModifier>();
|
||||
var afflictionsCopy = new List<Affliction>();
|
||||
appliedDamageModifiers.Clear();
|
||||
afflictionsCopy.Clear();
|
||||
foreach (var affliction in afflictions)
|
||||
{
|
||||
var tempModifiers = new List<DamageModifier>();
|
||||
tempModifiers.Clear();
|
||||
var newAffliction = affliction;
|
||||
float random = Rand.Value(Rand.RandSync.Unsynced);
|
||||
bool foundMatchingModifier = false;
|
||||
@@ -1020,18 +1022,13 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
// Thread-static to avoid concurrent modification in parallel item updates
|
||||
[ThreadStatic]
|
||||
private static List<Body> t_contactBodies;
|
||||
private static List<Body> ContactBodies => t_contactBodies ??= new List<Body>();
|
||||
|
||||
private readonly List<Body> contactBodies = new List<Body>();
|
||||
/// <summary>
|
||||
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
||||
/// </summary>
|
||||
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
||||
{
|
||||
attackResult = default;
|
||||
var contactBodies = ContactBodies;
|
||||
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
|
||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
|
||||
bool wasRunning = attack.IsRunning;
|
||||
@@ -1290,11 +1287,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-static to avoid concurrent modification in parallel item updates
|
||||
[ThreadStatic]
|
||||
private static List<ISerializableEntity> t_statusEffectTargets;
|
||||
private static List<ISerializableEntity> StatusEffectTargets => t_statusEffectTargets ??= new List<ISerializableEntity>();
|
||||
|
||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||
{
|
||||
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
||||
@@ -1317,7 +1310,6 @@ namespace Barotrauma
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
var targets = StatusEffectTargets;
|
||||
targets.Clear();
|
||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||
statusEffect.Apply(actionType, deltaTime, character, targets);
|
||||
|
||||
+20
-14
@@ -1,12 +1,10 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
@@ -119,9 +117,8 @@ namespace Barotrauma
|
||||
public virtual AnimationType AnimationType { get; protected set; }
|
||||
/// <summary>
|
||||
/// The cached animations of all the characters that have been loaded.
|
||||
/// Thread-safe cache using ConcurrentDictionary.
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Identifier, ConcurrentDictionary<string, AnimationParams>> allAnimations = new ConcurrentDictionary<Identifier, ConcurrentDictionary<string, AnimationParams>>();
|
||||
private static readonly Dictionary<Identifier, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<Identifier, Dictionary<string, AnimationParams>>();
|
||||
|
||||
[Header("Movement")]
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED, ValueStep = 0.1f)]
|
||||
@@ -247,9 +244,7 @@ namespace Barotrauma
|
||||
return GetAnimParams<T>(speciesName, animSpecies, fallbackSpecies: character.Prefab.GetBaseCharacterSpeciesName(speciesName), animType, file, throwErrors);
|
||||
}
|
||||
|
||||
// ThreadLocal for thread-safe error message collection during animation loading
|
||||
private static readonly ThreadLocal<List<string>> errorMessagesLocal = new ThreadLocal<List<string>>(() => new List<string>());
|
||||
private static List<string> errorMessages => errorMessagesLocal.Value;
|
||||
private static readonly List<string> errorMessages = new List<string>();
|
||||
|
||||
private static T GetAnimParams<T>(Identifier speciesName, Identifier animSpecies, Identifier fallbackSpecies, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
|
||||
{
|
||||
@@ -267,7 +262,11 @@ namespace Barotrauma
|
||||
}
|
||||
ContentPackage contentPackage = contentPath?.ContentPackage ?? CharacterPrefab.FindBySpeciesName(speciesName)?.ContentPackage;
|
||||
Debug.Assert(contentPackage != null);
|
||||
var animations = allAnimations.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, AnimationParams>());
|
||||
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> animations))
|
||||
{
|
||||
animations = new Dictionary<string, AnimationParams>();
|
||||
allAnimations.Add(speciesName, animations);
|
||||
}
|
||||
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(animSpecies, animType);
|
||||
if (animations.TryGetValue(key, out AnimationParams anim) && anim.AnimationType == animType)
|
||||
{
|
||||
@@ -419,12 +418,16 @@ namespace Barotrauma
|
||||
{
|
||||
throw new Exception("Cannot create an animation file of type " + animationType);
|
||||
}
|
||||
var anims = allAnimations.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, AnimationParams>());
|
||||
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> anims))
|
||||
{
|
||||
anims = new Dictionary<string, AnimationParams>();
|
||||
allAnimations.Add(speciesName, anims);
|
||||
}
|
||||
string fileName = IO.Path.GetFileNameWithoutExtension(fullPath);
|
||||
if (anims.ContainsKey(fileName))
|
||||
{
|
||||
DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
|
||||
anims.TryRemove(fileName, out _);
|
||||
anims.Remove(fileName);
|
||||
}
|
||||
var instance = new T();
|
||||
XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
|
||||
@@ -436,7 +439,7 @@ namespace Barotrauma
|
||||
instance.IsLoaded = instance.Deserialize(animationElement);
|
||||
instance.Save();
|
||||
instance.Load(contentPath, speciesName);
|
||||
anims.TryAdd(fileName, instance);
|
||||
anims.Add(fileName, instance);
|
||||
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
|
||||
return instance;
|
||||
}
|
||||
@@ -464,14 +467,17 @@ namespace Barotrauma
|
||||
{
|
||||
// Update the key by removing and re-adding the animation.
|
||||
string fileName = FileNameWithoutExtension;
|
||||
if (allAnimations.TryGetValue(SpeciesName, out ConcurrentDictionary<string, AnimationParams> animations))
|
||||
if (allAnimations.TryGetValue(SpeciesName, out Dictionary<string, AnimationParams> animations))
|
||||
{
|
||||
animations.TryRemove(fileName, out _);
|
||||
animations.Remove(fileName);
|
||||
}
|
||||
base.UpdatePath(newPath);
|
||||
if (animations != null)
|
||||
{
|
||||
animations.TryAdd(fileName, this);
|
||||
if (!animations.ContainsKey(fileName))
|
||||
{
|
||||
animations.Add(fileName, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-12
@@ -1,6 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Xml.Linq;
|
||||
@@ -125,9 +124,8 @@ namespace Barotrauma
|
||||
/// key1: Species name
|
||||
/// key2: File path
|
||||
/// value: Ragdoll parameters
|
||||
/// Thread-safe cache using ConcurrentDictionary.
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Identifier, ConcurrentDictionary<string, RagdollParams>> allRagdolls = new ConcurrentDictionary<Identifier, ConcurrentDictionary<string, RagdollParams>>();
|
||||
private static readonly Dictionary<Identifier, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<Identifier, Dictionary<string, RagdollParams>>();
|
||||
|
||||
public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
|
||||
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
|
||||
@@ -224,7 +222,11 @@ namespace Barotrauma
|
||||
Debug.Assert(!fileName.IsNullOrWhiteSpace() || !contentPath.IsNullOrWhiteSpace());
|
||||
}
|
||||
Debug.Assert(contentPackage != null);
|
||||
var ragdolls = allRagdolls.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, RagdollParams>());
|
||||
if (!allRagdolls.TryGetValue(speciesName, out Dictionary<string, RagdollParams> ragdolls))
|
||||
{
|
||||
ragdolls = new Dictionary<string, RagdollParams>();
|
||||
allRagdolls.Add(speciesName, ragdolls);
|
||||
}
|
||||
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(ragdollSpecies);
|
||||
if (ragdolls.TryGetValue(key, out RagdollParams ragdoll))
|
||||
{
|
||||
@@ -329,10 +331,10 @@ namespace Barotrauma
|
||||
if (allRagdolls.ContainsKey(speciesName))
|
||||
{
|
||||
DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
|
||||
allRagdolls.TryRemove(speciesName, out _);
|
||||
allRagdolls.Remove(speciesName);
|
||||
}
|
||||
var ragdolls = new ConcurrentDictionary<string, RagdollParams>();
|
||||
allRagdolls.TryAdd(speciesName, ragdolls);
|
||||
var ragdolls = new Dictionary<string, RagdollParams>();
|
||||
allRagdolls.Add(speciesName, ragdolls);
|
||||
var instance = new T
|
||||
{
|
||||
doc = new XDocument(mainElement)
|
||||
@@ -343,7 +345,7 @@ namespace Barotrauma
|
||||
instance.IsLoaded = instance.Deserialize(mainElement);
|
||||
instance.Save();
|
||||
instance.Load(contentPath, speciesName);
|
||||
ragdolls.TryAdd(instance.FileNameWithoutExtension, instance);
|
||||
ragdolls.Add(instance.FileNameWithoutExtension, instance);
|
||||
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
|
||||
return instance;
|
||||
}
|
||||
@@ -360,14 +362,17 @@ namespace Barotrauma
|
||||
{
|
||||
// Update the key by removing and re-adding the ragdoll.
|
||||
string fileName = FileNameWithoutExtension;
|
||||
if (allRagdolls.TryGetValue(SpeciesName, out ConcurrentDictionary<string, RagdollParams> ragdolls))
|
||||
if (allRagdolls.TryGetValue(SpeciesName, out Dictionary<string, RagdollParams> ragdolls))
|
||||
{
|
||||
ragdolls.TryRemove(fileName, out _);
|
||||
ragdolls.Remove(fileName);
|
||||
}
|
||||
base.UpdatePath(fullPath);
|
||||
if (ragdolls != null)
|
||||
{
|
||||
ragdolls.TryAdd(fileName, this);
|
||||
if (!ragdolls.ContainsKey(fileName))
|
||||
{
|
||||
ragdolls.Add(fileName, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1479,4 +1484,4 @@ namespace Barotrauma
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using Barotrauma.Abilities;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -74,9 +72,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// ThreadLocal for thread-safe talent checking
|
||||
private static readonly ThreadLocal<HashSet<Identifier>> checkedNonStackableTalentsLocal = new ThreadLocal<HashSet<Identifier>>(() => new HashSet<Identifier>());
|
||||
private static HashSet<Identifier> checkedNonStackableTalents => checkedNonStackableTalentsLocal.Value;
|
||||
private static readonly HashSet<Identifier> checkedNonStackableTalents = new();
|
||||
|
||||
/// <summary>
|
||||
/// Checks talents for a given AbilityObject taking into account non-stackable talents.
|
||||
|
||||
Reference in New Issue
Block a user