Core/Players: Implemented new talent system

Port From (https://github.com/TrinityCore/TrinityCore/commit/0cc5ab8372f19dad7412038d52dcd39db5e0e171)
This commit is contained in:
hondacrx
2023-01-04 02:50:40 -05:00
parent 0bfc448595
commit 55da7dc549
24 changed files with 3168 additions and 532 deletions
+409 -12
View File
@@ -349,6 +349,8 @@ namespace Game.Entities
RemoveOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID);
}
ApplyTraitConfig((int)(uint)m_activePlayerData.ActiveCombatTraitConfigID, false);
// Remove spec specific spells
RemoveSpecializationSpells();
@@ -357,6 +359,16 @@ namespace Game.Entities
SetActiveTalentGroup(spec.OrderIndex);
SetPrimarySpecialization(spec.Id);
int specTraitConfigIndex = m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return (TraitConfigType)(int)traitConfig.Type == TraitConfigType.Combat
&& traitConfig.ChrSpecializationID == spec.Id
&& ((TraitCombatConfigFlags)(int)traitConfig.CombatConfigFlags & TraitCombatConfigFlags.ActiveForSpec) != TraitCombatConfigFlags.None;
});
if (specTraitConfigIndex >= 0)
SetActiveCombatTraitConfigID(m_activePlayerData.TraitConfigs[specTraitConfigIndex].ID);
else
SetActiveCombatTraitConfigID(0);
foreach (var talentInfo in CliDB.TalentStorage.Values)
{
@@ -399,20 +411,11 @@ namespace Game.Entities
}
}
ApplyTraitConfig((int)(uint)m_activePlayerData.ActiveCombatTraitConfigID, true);
InitTalentForLevel();
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, GetActiveTalentGroup());
WorldSession mySess = GetSession();
mySess.GetQueryProcessor().AddCallback(DB.Characters.AsyncQuery(stmt).WithCallback(result =>
{
// in case player logs out before db response (player would be deleted in that case)
Player thisPlayer = mySess.GetPlayer();
if (thisPlayer != null)
thisPlayer.LoadActions(result);
}));
StartLoadingActionButtons();
UpdateDisplayPower();
PowerType pw = GetPowerType();
@@ -477,6 +480,48 @@ namespace Game.Entities
}
}
void StartLoadingActionButtons(Action callback = null)
{
uint traitConfigId = 0;
TraitConfig traitConfig = GetTraitConfig((int)(uint)m_activePlayerData.ActiveCombatTraitConfigID);
if (traitConfig != null)
{
int usedSavedTraitConfigIndex = m_activePlayerData.TraitConfigs.FindIndexIf(savedConfig =>
{
return (TraitConfigType)(int)savedConfig.Type == TraitConfigType.Combat
&& ((TraitCombatConfigFlags)(int)savedConfig.CombatConfigFlags & TraitCombatConfigFlags.ActiveForSpec) == TraitCombatConfigFlags.None
&& ((TraitCombatConfigFlags)(int)savedConfig.CombatConfigFlags & TraitCombatConfigFlags.SharedActionBars) == TraitCombatConfigFlags.None
&& savedConfig.LocalIdentifier == traitConfig.LocalIdentifier;
});
if (usedSavedTraitConfigIndex >= 0)
traitConfigId = (uint)(int)m_activePlayerData.TraitConfigs[usedSavedTraitConfigIndex].ID;
}
// load them asynchronously
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, GetActiveTalentGroup());
stmt.AddValue(2, traitConfigId);
var myGuid = GetGUID();
WorldSession mySess = GetSession();
mySess.GetQueryProcessor().AddCallback(DB.Characters.AsyncQuery(stmt)
.WithCallback(result =>
{
// safe callback, we can't pass this pointer directly
// in case player logs out before db response (player would be deleted in that case)
Player thisPlayer = mySess.GetPlayer();
if (thisPlayer != null && thisPlayer.GetGUID() == myGuid)
thisPlayer.LoadActions(result);
if (callback != null)
callback();
}));
}
public Dictionary<uint, PlayerSpellState> GetTalentMap(uint spec) { return _specializationInfo.Talents[spec]; }
public List<uint> GetGlyphs(byte spec) { return _specializationInfo.Glyphs[spec]; }
@@ -803,5 +848,357 @@ namespace Game.Entities
{
return GetPvpTalentMap(activeTalentGroup).Contains(talentID);
}
//Traits
public void CreateTraitConfig(TraitConfigPacket traitConfig)
{
int configId = TraitMgr.GenerateNewTraitConfigId();
bool hasConfigId(int id)
{
return m_activePlayerData.TraitConfigs.FindIndexIf(config => config.ID == id) >= 0;
}
while (hasConfigId(configId))
configId = TraitMgr.GenerateNewTraitConfigId();
traitConfig.ID = configId;
int traitConfigIndex = m_activePlayerData.TraitConfigs.Size();
AddTraitConfig(traitConfig);
foreach (TraitEntry grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(traitConfig, this))
{
var entryIndex = traitConfig.Entries.Find(entry => entry.TraitNodeID == grantedEntry.TraitNodeID && entry.TraitNodeEntryID == grantedEntry.TraitNodeEntryID);
if (entryIndex == null)
{
TraitConfig value = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, traitConfigIndex);
AddDynamicUpdateFieldValue(value.ModifyValue(value.Entries), grantedEntry);
}
}
m_traitConfigStates[(int)configId] = PlayerSpellState.Changed;
}
void AddTraitConfig(TraitConfigPacket traitConfig)
{
var setter = new TraitConfig();
setter.ModifyValue(setter.ID).SetValue(traitConfig.ID);
setter.ModifyValue(setter.Name).SetValue(traitConfig.Name);
setter.ModifyValue(setter.Type).SetValue((int)traitConfig.Type);
setter.ModifyValue(setter.SkillLineID).SetValue((int)traitConfig.SkillLineID);
setter.ModifyValue(setter.ChrSpecializationID).SetValue(traitConfig.ChrSpecializationID);
setter.ModifyValue(setter.CombatConfigFlags).SetValue((int)traitConfig.CombatConfigFlags);
setter.ModifyValue(setter.LocalIdentifier).SetValue(traitConfig.LocalIdentifier);
setter.ModifyValue(setter.TraitSystemID).SetValue(traitConfig.TraitSystemID);
AddDynamicUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs), setter);
foreach (TraitEntryPacket traitEntry in traitConfig.Entries)
{
TraitEntry newEntry = new();
newEntry.TraitNodeID = traitEntry.TraitNodeID;
newEntry.TraitNodeEntryID = traitEntry.TraitNodeEntryID;
newEntry.Rank = traitEntry.Rank;
newEntry.GrantedRanks = traitEntry.GrantedRanks;
AddDynamicUpdateFieldValue(setter.ModifyValue(setter.Entries), newEntry);
}
}
public TraitConfig GetTraitConfig(int configId)
{
int index = m_activePlayerData.TraitConfigs.FindIndexIf(config => config.ID == configId);
if (index < 0)
return null;
return m_activePlayerData.TraitConfigs[index];
}
public void UpdateTraitConfig(TraitConfigPacket newConfig, int savedConfigId, bool withCastTime)
{
int index = m_activePlayerData.TraitConfigs.FindIndexIf(config => config.ID == newConfig.ID);
if (index < 0)
return;
if (withCastTime)
{
CastSpell(this, TraitMgr.COMMIT_COMBAT_TRAIT_CONFIG_CHANGES_SPELL_ID, new CastSpellExtraArgs(SpellValueMod.BasePoint0, savedConfigId).SetCustomArg(newConfig));
return;
}
bool isActiveConfig = true;
bool loadActionButtons = false;
switch ((TraitConfigType)(int)m_activePlayerData.TraitConfigs[index].Type)
{
case TraitConfigType.Combat:
isActiveConfig = newConfig.ID == m_activePlayerData.ActiveCombatTraitConfigID;
loadActionButtons = m_activePlayerData.TraitConfigs[index].LocalIdentifier != newConfig.LocalIdentifier;
break;
case TraitConfigType.Profession:
isActiveConfig = HasSkill((uint)(int)m_activePlayerData.TraitConfigs[index].SkillLineID);
break;
default:
break;
}
Action finalizeTraitConfigUpdate = () =>
{
TraitConfig newTraitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, index);
SetUpdateFieldValue(newTraitConfig.ModifyValue(newTraitConfig.LocalIdentifier), newConfig.LocalIdentifier);
ApplyTraitEntryChanges(newConfig.ID, newConfig, isActiveConfig, true);
if (savedConfigId != 0)
ApplyTraitEntryChanges(savedConfigId, newConfig, false, false);
if (((TraitCombatConfigFlags)(int)newConfig.CombatConfigFlags).HasFlag(TraitCombatConfigFlags.StarterBuild))
SetTraitConfigUseStarterBuild(newConfig.ID, true);
};
if (loadActionButtons)
{
SQLTransaction trans = new SQLTransaction();
_SaveActions(trans);
DB.Characters.CommitTransaction(trans);
StartLoadingActionButtons(finalizeTraitConfigUpdate);
}
else
finalizeTraitConfigUpdate();
}
void ApplyTraitEntryChanges(int editedConfigId, TraitConfigPacket newConfig, bool applyTraits, bool consumeCurrencies)
{
int editedIndex = m_activePlayerData.TraitConfigs.FindIndexIf(config => config.ID == editedConfigId);
if (editedIndex < 0)
return;
TraitConfig editedConfig = m_activePlayerData.TraitConfigs[editedIndex];
// remove traits not found in new config
List<int> entryIndicesToRemove = new();
for (int i = 0; i < editedConfig.Entries.Size(); ++i)
{
TraitEntry oldEntry = editedConfig.Entries[i];
var entryItr = newConfig.Entries.Find(ufEntry => ufEntry.TraitNodeID == oldEntry.TraitNodeID && ufEntry.TraitNodeEntryID == oldEntry.TraitNodeEntryID);
if (entryItr != null)
continue;
if (applyTraits)
ApplyTraitEntry(oldEntry.TraitNodeEntryID, 0, 0, false);
entryIndicesToRemove.Add(i);
}
foreach (int indexToRemove in entryIndicesToRemove)
{
TraitConfig traitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, editedIndex);
RemoveDynamicUpdateFieldValue(traitConfig.ModifyValue(traitConfig.Entries), indexToRemove);
}
List<TraitEntryPacket> costEntries = new();
// apply new traits
for (var i = 0; i < newConfig.Entries.Count; ++i)
{
TraitEntryPacket newEntry = newConfig.Entries[i];
int oldEntryIndex = editedConfig.Entries.FindIndexIf(ufEntry => ufEntry.TraitNodeID == newEntry.TraitNodeID && ufEntry.TraitNodeEntryID == newEntry.TraitNodeEntryID);
if (oldEntryIndex < 0)
{
if (consumeCurrencies)
costEntries.Add(newEntry);
TraitConfig newTraitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, editedIndex);
TraitEntry newUfEntry = new();
newUfEntry.TraitNodeID = newEntry.TraitNodeID;
newUfEntry.TraitNodeEntryID = newEntry.TraitNodeEntryID;
newUfEntry.Rank = newEntry.Rank;
newUfEntry.GrantedRanks = newEntry.GrantedRanks;
AddDynamicUpdateFieldValue(newTraitConfig.ModifyValue(newTraitConfig.Entries), newUfEntry);
if (applyTraits)
ApplyTraitEntry(newUfEntry.TraitNodeEntryID, newUfEntry.Rank, 0, true);
}
else if (newEntry.Rank != editedConfig.Entries[oldEntryIndex].Rank || newEntry.GrantedRanks != editedConfig.Entries[oldEntryIndex].GrantedRanks)
{
if (consumeCurrencies && newEntry.Rank > editedConfig.Entries[oldEntryIndex].Rank)
{
TraitEntryPacket costEntry = new();
costEntry.Rank -= editedConfig.Entries[oldEntryIndex].Rank;
costEntries.Add(newEntry);
}
TraitConfig traitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, editedIndex);
TraitEntry traitEntry = traitConfig.ModifyValue(traitConfig.Entries, oldEntryIndex);
traitEntry.Rank = newEntry.Rank;
traitEntry.GrantedRanks = newEntry.GrantedRanks;
SetUpdateFieldValue(traitConfig.Entries, oldEntryIndex, traitEntry);
if (applyTraits)
ApplyTraitEntry(newEntry.TraitNodeEntryID, newEntry.Rank, newEntry.GrantedRanks, true);
}
}
if (consumeCurrencies)
{
Dictionary<int, int> currencies = new();
foreach (TraitEntryPacket costEntry in costEntries)
TraitMgr.FillSpentCurrenciesMap(costEntry, currencies);
foreach (var (traitCurrencyId, amount) in currencies)
{
TraitCurrencyRecord traitCurrency = CliDB.TraitCurrencyStorage.LookupByKey(traitCurrencyId);
if (traitCurrency == null)
continue;
switch (traitCurrency.GetCurrencyType())
{
case TraitCurrencyType.Gold:
ModifyMoney(-amount);
break;
case TraitCurrencyType.CurrencyTypesBased:
ModifyCurrency((CurrencyTypes)traitCurrency.CurrencyTypesID, -amount);
break;
default:
break;
}
}
}
m_traitConfigStates[(int)editedConfigId] = PlayerSpellState.Changed;
}
public void RenameTraitConfig(int editedConfigId, string newName)
{
int editedIndex = m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return traitConfig.ID == editedConfigId
&& (TraitConfigType)(int)traitConfig.Type == TraitConfigType.Combat
&& ((TraitCombatConfigFlags)(int)traitConfig.CombatConfigFlags & TraitCombatConfigFlags.ActiveForSpec) == TraitCombatConfigFlags.None;
});
if (editedIndex < 0)
return;
TraitConfig traitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, editedIndex);
SetUpdateFieldValue(traitConfig.ModifyValue(traitConfig.Name), newName);
m_traitConfigStates[editedConfigId] = PlayerSpellState.Changed;
}
public void DeleteTraitConfig(int deletedConfigId)
{
int deletedIndex = m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return traitConfig.ID == deletedConfigId
&& (TraitConfigType)(int)traitConfig.Type == TraitConfigType.Combat
&& ((TraitCombatConfigFlags)(int)traitConfig.CombatConfigFlags & TraitCombatConfigFlags.ActiveForSpec) == TraitCombatConfigFlags.None;
});
if (deletedIndex < 0)
return;
RemoveDynamicUpdateFieldValue(m_values.ModifyValue(m_activePlayerData)
.ModifyValue(m_activePlayerData.TraitConfigs), deletedIndex);
m_traitConfigStates[deletedConfigId] = PlayerSpellState.Removed;
}
void ApplyTraitConfig(int configId, bool apply)
{
TraitConfig traitConfig = GetTraitConfig(configId);
if (traitConfig == null)
return;
foreach (TraitEntry traitEntry in traitConfig.Entries)
ApplyTraitEntry(traitEntry.TraitNodeEntryID, traitEntry.Rank, traitEntry.GrantedRanks, apply);
}
void ApplyTraitEntry(int traitNodeEntryId, int rank, int grantedRanks, bool apply)
{
TraitNodeEntryRecord traitNodeEntry = CliDB.TraitNodeEntryStorage.LookupByKey(traitNodeEntryId);
if (traitNodeEntry == null)
return;
TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(traitNodeEntry.TraitDefinitionID);
if (traitDefinition == null)
return;
if (traitDefinition.SpellID != 0)
{
if (apply)
LearnSpell(traitDefinition.SpellID, true, 0, false, traitNodeEntry.TraitDefinitionID);
else
RemoveSpell(traitDefinition.SpellID);
}
}
public void SetTraitConfigUseStarterBuild(int traitConfigId, bool useStarterBuild)
{
int configIndex = m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return traitConfig.ID == traitConfigId
&& (TraitConfigType)(int)traitConfig.Type == TraitConfigType.Combat
&& ((TraitCombatConfigFlags)(int)traitConfig.CombatConfigFlags & TraitCombatConfigFlags.ActiveForSpec) != TraitCombatConfigFlags.None;
});
if (configIndex < 0)
return;
bool currentlyUsesStarterBuild = ((TraitCombatConfigFlags)(int)m_activePlayerData.TraitConfigs[configIndex].CombatConfigFlags).HasFlag(TraitCombatConfigFlags.StarterBuild);
if (currentlyUsesStarterBuild == useStarterBuild)
return;
if (useStarterBuild)
{
TraitConfig traitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, configIndex);
SetUpdateFieldFlagValue(traitConfig.ModifyValue(traitConfig.CombatConfigFlags), (int)TraitCombatConfigFlags.StarterBuild);
}
else
{
TraitConfig traitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, configIndex);
RemoveUpdateFieldFlagValue(traitConfig.ModifyValue(traitConfig.CombatConfigFlags), (int)TraitCombatConfigFlags.StarterBuild);
}
m_traitConfigStates[(int)traitConfigId] = PlayerSpellState.Changed;
}
public void SetTraitConfigUseSharedActionBars(int traitConfigId, bool usesSharedActionBars, bool isLastSelectedSavedConfig)
{
int configIndex = m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return traitConfig.ID == traitConfigId
&& (TraitConfigType)(int)traitConfig.Type == TraitConfigType.Combat
&& ((TraitCombatConfigFlags)(int)traitConfig.CombatConfigFlags & TraitCombatConfigFlags.ActiveForSpec) == TraitCombatConfigFlags.None;
});
if (configIndex < 0)
return;
bool currentlyUsesSharedActionBars = ((TraitCombatConfigFlags)(int)m_activePlayerData.TraitConfigs[configIndex].CombatConfigFlags).HasFlag(TraitCombatConfigFlags.SharedActionBars);
if (currentlyUsesSharedActionBars == usesSharedActionBars)
return;
TraitConfig traitConfig = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.TraitConfigs, configIndex);
if (usesSharedActionBars)
{
SetUpdateFieldFlagValue(traitConfig.ModifyValue(traitConfig.CombatConfigFlags), (int)TraitCombatConfigFlags.SharedActionBars);
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACTION_BY_TRAIT_CONFIG);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, traitConfigId);
DB.Characters.Execute(stmt);
if (isLastSelectedSavedConfig)
StartLoadingActionButtons(); // load action buttons that were saved in shared mode
}
else
{
RemoveUpdateFieldFlagValue(traitConfig.ModifyValue(traitConfig.CombatConfigFlags), (int)TraitCombatConfigFlags.SharedActionBars);
// trigger a save with traitConfigId
foreach (var (_, button) in m_actionButtons)
if (button.uState != ActionButtonUpdateState.Deleted)
button.uState = ActionButtonUpdateState.New;
}
m_traitConfigStates[traitConfigId] = PlayerSpellState.Changed;
}
}
}