Refactored various collections (lists, dictionaries, queues, and caches) in AI, character, animation, and item component systems to use thread-safe patterns and concurrent data structures. This includes introducing copy-on-write wrappers, ConcurrentDictionary, ConcurrentQueue, and ThreadLocal where appropriate to ensure safe concurrent access and mutation, improving stability in multi-threaded scenarios.
167 lines
7.6 KiB
C#
167 lines
7.6 KiB
C#
using Barotrauma.Abilities;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class CharacterTalent
|
|
{
|
|
public Character Character { get; }
|
|
public string DebugIdentifier { get; }
|
|
|
|
public readonly TalentPrefab Prefab;
|
|
|
|
public bool AddedThisRound = true;
|
|
|
|
private readonly Dictionary<AbilityEffectType, List<CharacterAbilityGroupEffect>> characterAbilityGroupEffectDictionary = new Dictionary<AbilityEffectType, List<CharacterAbilityGroupEffect>>();
|
|
|
|
private readonly List<CharacterAbilityGroupInterval> characterAbilityGroupIntervals = new List<CharacterAbilityGroupInterval>();
|
|
|
|
// works functionally but a missing recipe is not represented on GUI side. this might be better placed in the character class itself, though it might be fine here as well
|
|
public List<Identifier> UnlockedRecipes { get; } = new List<Identifier>();
|
|
public List<Identifier> UnlockedStoreItems { get; } = new List<Identifier>();
|
|
|
|
public CharacterTalent(TalentPrefab talentPrefab, Character character)
|
|
{
|
|
Character = character ?? throw new ArgumentNullException(nameof(character));
|
|
Prefab = talentPrefab ?? throw new ArgumentNullException(nameof(talentPrefab));
|
|
var element = talentPrefab.ConfigElement;
|
|
DebugIdentifier = talentPrefab.OriginalName;
|
|
|
|
foreach (var subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "abilitygroupeffect":
|
|
LoadAbilityGroupEffect(subElement);
|
|
break;
|
|
case "abilitygroupinterval":
|
|
LoadAbilityGroupInterval(subElement);
|
|
break;
|
|
case "addedrecipe":
|
|
if (subElement.GetAttributeIdentifier("itemidentifier", Identifier.Empty) is { IsEmpty: false } recipeIdentifier)
|
|
{
|
|
UnlockedRecipes.Add(recipeIdentifier);
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.ThrowError($"No recipe identifier defined for talent {DebugIdentifier}",
|
|
contentPackage: element.ContentPackage);
|
|
}
|
|
break;
|
|
case "addedstoreitem":
|
|
if (subElement.GetAttributeIdentifier("itemtag", Identifier.Empty) is { IsEmpty: false } storeItemTag)
|
|
{
|
|
UnlockedStoreItems.Add(storeItemTag);
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.ThrowError($"No store item identifier defined for talent {DebugIdentifier}",
|
|
contentPackage: element.ContentPackage);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void UpdateTalent(float deltaTime)
|
|
{
|
|
foreach (var characterAbilityGroupInterval in characterAbilityGroupIntervals)
|
|
{
|
|
characterAbilityGroupInterval.UpdateAbilityGroup(deltaTime);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
/// <summary>
|
|
/// Checks talents for a given AbilityObject taking into account non-stackable talents.
|
|
/// </summary>
|
|
public static void CheckTalentsForCrew(IEnumerable<Character> crew, AbilityEffectType type, AbilityObject abilityObject)
|
|
{
|
|
checkedNonStackableTalents.Clear();
|
|
foreach (Character character in crew)
|
|
{
|
|
foreach (CharacterTalent characterTalent in character.CharacterTalents)
|
|
{
|
|
if (!characterTalent.Prefab.AbilityEffectsStackWithSameTalent)
|
|
{
|
|
if (checkedNonStackableTalents.Contains(characterTalent.Prefab.Identifier)) { continue; }
|
|
checkedNonStackableTalents.Add(characterTalent.Prefab.Identifier);
|
|
}
|
|
|
|
characterTalent.CheckTalent(type, abilityObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CheckTalent(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
|
|
{
|
|
if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityGroups))
|
|
{
|
|
foreach (var characterAbilityGroup in characterAbilityGroups)
|
|
{
|
|
characterAbilityGroup.CheckAbilityGroup(abilityObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ActivateTalent(bool addingFirstTime)
|
|
{
|
|
foreach (var characterAbilityGroups in characterAbilityGroupEffectDictionary.Values)
|
|
{
|
|
foreach (var characterAbilityGroup in characterAbilityGroups)
|
|
{
|
|
characterAbilityGroup.ActivateAbilityGroup(addingFirstTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
// XML logic
|
|
private void LoadAbilityGroupInterval(ContentXElement abilityGroup)
|
|
{
|
|
characterAbilityGroupIntervals.Add(new CharacterAbilityGroupInterval(AbilityEffectType.Undefined, this, abilityGroup));
|
|
}
|
|
|
|
private void LoadAbilityGroupEffect(ContentXElement abilityGroup)
|
|
{
|
|
AbilityEffectType abilityEffectType = ParseAbilityEffectType(this, abilityGroup.GetAttributeString("abilityeffecttype", "none"));
|
|
AddAbilityGroupEffect(new CharacterAbilityGroupEffect(abilityEffectType, this, abilityGroup), abilityEffectType);
|
|
}
|
|
|
|
public void AddAbilityGroupEffect(CharacterAbilityGroupEffect characterAbilityGroup, AbilityEffectType abilityEffectType = AbilityEffectType.None)
|
|
{
|
|
if (characterAbilityGroupEffectDictionary.TryGetValue(abilityEffectType, out var characterAbilityList))
|
|
{
|
|
characterAbilityList.Add(characterAbilityGroup);
|
|
}
|
|
else
|
|
{
|
|
List<CharacterAbilityGroupEffect> characterAbilityGroups = new List<CharacterAbilityGroupEffect>();
|
|
characterAbilityGroups.Add(characterAbilityGroup);
|
|
characterAbilityGroupEffectDictionary.Add(abilityEffectType, characterAbilityGroups);
|
|
}
|
|
}
|
|
|
|
public static AbilityEffectType ParseAbilityEffectType(CharacterTalent characterTalent, string abilityEffectTypeString)
|
|
{
|
|
if (!Enum.TryParse(abilityEffectTypeString, true, out AbilityEffectType abilityEffectType))
|
|
{
|
|
DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")",
|
|
contentPackage: characterTalent?.Prefab?.ContentPackage);
|
|
}
|
|
if (abilityEffectType == AbilityEffectType.Undefined)
|
|
{
|
|
DebugConsole.ThrowError("Ability effect type not defined in CharacterTalent (" + characterTalent.DebugIdentifier + ")",
|
|
contentPackage: characterTalent?.Prefab?.ContentPackage);
|
|
}
|
|
|
|
return abilityEffectType;
|
|
}
|
|
}
|
|
}
|