using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; namespace Barotrauma { class TalentTree : Prefab { public enum TalentTreeStageState { Invalid, Locked, Unlocked, Available, Highlighted } public static readonly PrefabCollection JobTalentTrees = new PrefabCollection(); public readonly ImmutableArray TalentSubTrees; /// /// Talent identifiers of all the talents in this tree /// public readonly ImmutableHashSet AllTalentIdentifiers; public ContentXElement ConfigElement { get; private set; } public TalentTree(ContentXElement element, TalentTreesFile file) : base(file, element.GetAttributeIdentifier("jobIdentifier", "")) { ConfigElement = element; if (Identifier.IsEmpty) { DebugConsole.ThrowError($"No job defined for talent tree in \"{file.Path}\"!"); return; } List subTrees = new List(); foreach (var subTreeElement in element.GetChildElements("subtree")) { subTrees.Add(new TalentSubTree(subTreeElement)); } TalentSubTrees = subTrees.ToImmutableArray(); AllTalentIdentifiers = TalentSubTrees.SelectMany(t => t.AllTalentIdentifiers).ToImmutableHashSet(); } public bool TalentIsInTree(Identifier talentIdentifier) { return AllTalentIdentifiers.Contains(talentIdentifier); } public static bool IsViableTalentForCharacter(Character character, Identifier talentIdentifier) { return IsViableTalentForCharacter(character, talentIdentifier, character?.Info?.UnlockedTalents ?? (ICollection)Array.Empty()); } // i hate this function - markus // me too - joonas public static TalentTreeStageState GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, List selectedTalents) { if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; } if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentTreeStageState.Invalid; } TalentSubTree subTree = talentTree.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier); if (subTree == null) { return TalentTreeStageState.Invalid; } TalentOption targetTalentOption = subTree.TalentOptionStages[index]; if (targetTalentOption.TalentIdentifiers.Any(t => character.HasTalent(t))) { return TalentTreeStageState.Unlocked; } if (targetTalentOption.TalentIdentifiers.Any(t => selectedTalents.Contains(t))) { return TalentTreeStageState.Highlighted; } bool hasTalentInLastTier = true; bool isLastTalentPurchased = true; int lastindex = index - 1; if (lastindex >= 0) { TalentOption lastLatentOption = subTree.TalentOptionStages[lastindex]; hasTalentInLastTier = lastLatentOption.TalentIdentifiers.Any(HasTalent); isLastTalentPurchased = lastLatentOption.TalentIdentifiers.Any(t => character.HasTalent(t)); } if (!hasTalentInLastTier) { return TalentTreeStageState.Locked; } bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0; if (hasPointsForNewTalent) { return isLastTalentPurchased ? TalentTreeStageState.Highlighted : TalentTreeStageState.Available; } return TalentTreeStageState.Locked; bool HasTalent(Identifier talentId) { return selectedTalents.Contains(talentId); } } public static bool IsViableTalentForCharacter(Character character, Identifier talentIdentifier, ICollection selectedTalents) { if (character?.Info?.Job.Prefab == null) { return false; } if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; } if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; } foreach (var subTree in talentTree.TalentSubTrees) { if (subTree.ForceUnlock && subTree.TalentOptionStages.Any(option => option.TalentIdentifiers.Contains(talentIdentifier))) { return true; } foreach (var talentOptionStage in subTree.TalentOptionStages) { bool hasTalentInThisTier = talentOptionStage.TalentIdentifiers.Any(t => selectedTalents.Contains(t)); if (!hasTalentInThisTier) { if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier)) { return true; } else { break; } } } } return false; } public static List CheckTalentSelection(Character controlledCharacter, IEnumerable selectedTalents) { List viableTalents = new List(); bool canStillUnlock = true; // keep trying to unlock talents until none of the talents are unlockable while (canStillUnlock && selectedTalents.Any()) { canStillUnlock = false; foreach (Identifier talent in selectedTalents) { if (!viableTalents.Contains(talent) && IsViableTalentForCharacter(controlledCharacter, talent, viableTalents)) { viableTalents.Add(talent); canStillUnlock = true; } } } return viableTalents; } public override void Dispose() { } } class TalentSubTree { public Identifier Identifier { get; } public LocalizedString DisplayName { get; } public bool ForceUnlock; public readonly ImmutableArray TalentOptionStages; public readonly ImmutableHashSet AllTalentIdentifiers; public TalentSubTree(ContentXElement subTreeElement) { Identifier = subTreeElement.GetAttributeIdentifier("identifier", ""); DisplayName = TextManager.Get("talenttree." + Identifier).Fallback(Identifier.Value); List talentOptionStages = new List(); foreach (var talentOptionsElement in subTreeElement.GetChildElements("talentoptions")) { talentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier)); } TalentOptionStages = talentOptionStages.ToImmutableArray(); AllTalentIdentifiers = TalentOptionStages.SelectMany(t => t.TalentIdentifiers).ToImmutableHashSet(); } } class TalentOption { private readonly ImmutableHashSet talentIdentifiers; public IEnumerable TalentIdentifiers => talentIdentifiers; public bool HasTalent(Identifier talentIdentifier) { return talentIdentifiers.Contains(talentIdentifier); } public TalentOption(ContentXElement talentOptionsElement, Identifier debugIdentifier) { var talentIdentifiers = new HashSet(); foreach (var talentOptionElement in talentOptionsElement.GetChildElements("talentoption")) { Identifier identifier = talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty); talentIdentifiers.Add(identifier); } this.talentIdentifiers = talentIdentifiers.ToImmutableHashSet(); } } }