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
+77
View File
@@ -2213,6 +2213,23 @@ namespace Framework.Constants
SharedActionBars = 0x4
}
[Flags]
public enum TraitCondFlags
{
None = 0x0,
IsGate = 0x1,
IsAlwaysMet = 0x2,
IsSufficient = 0x4,
}
public enum TraitConditionType
{
Available = 0,
Visible = 1,
Granted = 2,
Increased = 3
}
public enum TraitConfigType
{
Invalid = 0,
@@ -2221,6 +2238,66 @@ namespace Framework.Constants
Generic = 3
}
public enum TraitCurrencyType
{
Gold = 0,
CurrencyTypesBased = 1,
TraitSourced = 2
}
public enum TraitEdgeType
{
VisualOnly = 0,
DeprecatedRankConnection = 1,
SufficientForAvailability = 2,
RequiredForAvailability = 3,
MutuallyExclusive = 4,
DeprecatedSelectionOption = 5
}
public enum TraitNodeEntryType
{
SpendHex = 0,
SpendSquare = 1,
SpendCircle = 2,
SpendSmallCircle = 3,
DeprecatedSelect = 4,
DragAndDrop = 5,
SpendDiamond = 6,
ProfPath = 7,
ProfPerk = 8,
ProfPathUnlock = 9
}
[Flags]
public enum TraitNodeGroupFlag
{
None = 0x0,
AvailableByDefault = 0x1
}
public enum TraitNodeType
{
Single = 0,
Tiered = 1,
Selection = 2
}
public enum TraitPointsOperationType
{
None = -1,
Set = 0,
Multiply = 1
}
[Flags]
public enum TraitTreeFlag
{
None = 0x0,
CannotRefund = 0x1,
HideSingleRankNumbers = 0x2
}
public enum UiMapFlag
{
None = 0x00,
@@ -116,7 +116,6 @@ namespace Framework.Database
PrepareStatement(CharStatements.SEL_CHARACTER_REPUTATION, "SELECT faction, standing, flags FROM character_reputation WHERE guid = ?");
PrepareStatement(CharStatements.SEL_CHARACTER_INVENTORY, "SELECT " + SelectItemInstanceContent + ", bag, slot FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid WHERE ci.guid = ? ORDER BY (ii.flags & 0x80000) ASC, bag ASC, slot ASC");
PrepareStatement(CharStatements.SEL_CHARACTER_ACTIONS, "SELECT a.button, a.action, a.type FROM character_action as a, characters as c WHERE a.guid = c.guid AND a.spec = c.activeTalentGroup AND a.guid = ? ORDER BY button");
PrepareStatement(CharStatements.SEL_MAIL_COUNT, "SELECT COUNT(*) FROM mail WHERE receiver = ?");
PrepareStatement(CharStatements.SEL_CHARACTER_SOCIALLIST, "SELECT cs.friend, c.account, cs.flags, cs.note FROM character_social cs JOIN characters c ON c.guid = cs.friend WHERE cs.guid = ? AND c.deleteinfos_name IS NULL LIMIT 255");
PrepareStatement(CharStatements.SEL_CHARACTER_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ, orientation FROM character_homebind WHERE guid = ?");
@@ -148,7 +147,7 @@ namespace Framework.Database
PrepareStatement(CharStatements.DEL_CHARACTER_FAVORITE_AUCTIONS_BY_CHAR, "DELETE FROM character_favorite_auctions WHERE guid = ?");
PrepareStatement(CharStatements.SEL_ACCOUNT_INSTANCELOCKTIMES, "SELECT instanceId, releaseTime FROM account_instance_times WHERE accountId = ?");
PrepareStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? ORDER BY button");
PrepareStatement(CharStatements.SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? AND traitConfigId = ? ORDER BY button");
PrepareStatement(CharStatements.SEL_MAILITEMS, "SELECT " + SelectItemInstanceContent + ", ii.owner_guid, m.id FROM mail_items mi INNER JOIN mail m ON mi.mail_id = m.id LEFT JOIN item_instance ii ON mi.item_guid = ii.guid LEFT JOIN item_instance_gems ig ON ii.guid = ig.itemGuid LEFT JOIN item_instance_transmog iit ON ii.guid = iit.itemGuid LEFT JOIN item_instance_modifiers im ON ii.guid = im.itemGuid WHERE m.receiver = ?");
PrepareStatement(CharStatements.SEL_MAILITEMS_ARTIFACT, "SELECT a.itemGuid, a.xp, a.artifactAppearanceId, a.artifactTierId, ap.artifactPowerId, ap.purchasedRank FROM item_instance_artifact_powers ap LEFT JOIN item_instance_artifact a ON ap.itemGuid = a.itemGuid INNER JOIN mail_items mi ON a.itemGuid = mi.item_guid INNER JOIN mail m ON mi.mail_id = m.id WHERE m.receiver = ?");
PrepareStatement(CharStatements.SEL_MAILITEMS_AZERITE, "SELECT iz.itemGuid, iz.xp, iz.level, iz.knowledgeLevel, " +
@@ -605,9 +604,10 @@ namespace Framework.Database
PrepareStatement(CharStatements.DEL_CHAR_PVP_TALENT, "DELETE FROM character_pvp_talent WHERE guid = ?");
PrepareStatement(CharStatements.DEL_CHAR_SKILLS, "DELETE FROM character_skills WHERE guid = ?");
PrepareStatement(CharStatements.UPD_CHAR_MONEY, "UPDATE characters SET money = ? WHERE guid = ?");
PrepareStatement(CharStatements.INS_CHAR_ACTION, "INSERT INTO character_action (guid, spec, button, action, type) VALUES (?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.UPD_CHAR_ACTION, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ?");
PrepareStatement(CharStatements.DEL_CHAR_ACTION_BY_BUTTON_SPEC, "DELETE FROM character_action WHERE guid = ? and button = ? and spec = ?");
PrepareStatement(CharStatements.INS_CHAR_ACTION, "INSERT INTO character_action (guid, spec, traitConfigId, button, action, type) VALUES (?, ?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.UPD_CHAR_ACTION, "UPDATE character_action SET action = ?, type = ? WHERE guid = ? AND button = ? AND spec = ? AND traitConfigId = ?");
PrepareStatement(CharStatements.DEL_CHAR_ACTION_BY_BUTTON_SPEC, "DELETE FROM character_action WHERE guid = ? and button = ? and spec = ? AND traitConfigId = ?");
PrepareStatement(CharStatements.DEL_CHAR_ACTION_BY_TRAIT_CONFIG, "DELETE FROM character_action WHERE guid = ? AND traitConfigId = ?");
PrepareStatement(CharStatements.DEL_CHAR_INVENTORY_BY_ITEM, "DELETE FROM character_inventory WHERE item = ?");
PrepareStatement(CharStatements.DEL_CHAR_INVENTORY_BY_BAG_SLOT, "DELETE FROM character_inventory WHERE bag = ? AND slot = ? AND guid = ?");
PrepareStatement(CharStatements.UPD_MAIL, "UPDATE mail SET has_items = ?, expire_time = ?, deliver_time = ?, money = ?, cod = ?, checked = ? WHERE id = ?");
@@ -639,6 +639,14 @@ namespace Framework.Database
PrepareStatement(CharStatements.UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?");
PrepareStatement(CharStatements.INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)");
PrepareStatement(CharStatements.DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?");
PrepareStatement(CharStatements.SEL_CHAR_TRAIT_ENTRIES, "SELECT traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks FROM character_trait_entry WHERE guid = ?");
PrepareStatement(CharStatements.INS_CHAR_TRAIT_ENTRIES, "INSERT INTO character_trait_entry (guid, traitConfigId, traitNodeId, traitNodeEntryId, `rank`, grantedRanks) VALUES (?, ?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES, "DELETE FROM character_trait_entry WHERE guid = ? AND traitConfigId = ?");
PrepareStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES_BY_CHAR, "DELETE FROM character_trait_entry WHERE guid = ?");
PrepareStatement(CharStatements.SEL_CHAR_TRAIT_CONFIGS, "SELECT traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name` FROM character_trait_config WHERE guid = ?");
PrepareStatement(CharStatements.INS_CHAR_TRAIT_CONFIGS, "INSERT INTO character_trait_config (guid, traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
PrepareStatement(CharStatements.DEL_CHAR_TRAIT_CONFIGS, "DELETE FROM character_trait_config WHERE guid = ? AND traitConfigId = ?");
PrepareStatement(CharStatements.DEL_CHAR_TRAIT_CONFIGS_BY_CHAR, "DELETE FROM character_trait_config WHERE guid = ?");
// Void Storage
PrepareStatement(CharStatements.SEL_CHAR_VOID_STORAGE, "SELECT itemId, itemEntry, slot, creatorGuid, randomBonusListId, fixedScalingLevel, artifactKnowledgeLevel, context, bonusListIDs FROM character_void_storage WHERE playerGuid = ?");
@@ -821,7 +829,6 @@ namespace Framework.Database
SEL_CHARACTER_REPUTATION,
SEL_CHARACTER_INVENTORY,
SEL_CHARACTER_ACTIONS,
SEL_CHARACTER_ACTIONS_SPEC,
SEL_MAIL_COUNT,
SEL_CHARACTER_SOCIALLIST,
@@ -1233,6 +1240,7 @@ namespace Framework.Database
INS_CHAR_ACTION,
UPD_CHAR_ACTION,
DEL_CHAR_ACTION_BY_BUTTON_SPEC,
DEL_CHAR_ACTION_BY_TRAIT_CONFIG,
DEL_CHAR_INVENTORY_BY_ITEM,
DEL_CHAR_INVENTORY_BY_BAG_SLOT,
UPD_MAIL,
@@ -1262,6 +1270,14 @@ namespace Framework.Database
UPD_CHAR_LIST_SLOT,
INS_CHAR_FISHINGSTEPS,
DEL_CHAR_FISHINGSTEPS,
SEL_CHAR_TRAIT_ENTRIES,
INS_CHAR_TRAIT_ENTRIES,
DEL_CHAR_TRAIT_ENTRIES,
DEL_CHAR_TRAIT_ENTRIES_BY_CHAR,
SEL_CHAR_TRAIT_CONFIGS,
INS_CHAR_TRAIT_CONFIGS,
DEL_CHAR_TRAIT_CONFIGS,
DEL_CHAR_TRAIT_CONFIGS_BY_CHAR,
SEL_CHAR_VOID_STORAGE,
REP_CHAR_VOID_STORAGE_ITEM,
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -2724,13 +2724,13 @@ namespace Game
case UnitConditionVariable.IsEnemy:
return (otherUnit && unit.GetReactionTo(otherUnit) <= ReputationRank.Hostile) ? 1 : 0;
case UnitConditionVariable.IsSpecMelee:
return (unit.IsPlayer() && CliDB.ChrSpecializationStorage.LookupByKey(unit.ToPlayer().GetPrimarySpecialization()).Flags.HasFlag(ChrSpecializationFlag.Melee)) ? 1 : 0;
return (unit.IsPlayer() && unit.ToPlayer().GetPrimarySpecialization() != 0 && CliDB.ChrSpecializationStorage.LookupByKey(unit.ToPlayer().GetPrimarySpecialization()).Flags.HasFlag(ChrSpecializationFlag.Melee)) ? 1 : 0;
case UnitConditionVariable.IsSpecTank:
return (unit.IsPlayer() && CliDB.ChrSpecializationStorage.LookupByKey(unit.ToPlayer().GetPrimarySpecialization()).Role == 0) ? 1 : 0;
return (unit.IsPlayer() && unit.ToPlayer().GetPrimarySpecialization() != 0 && CliDB.ChrSpecializationStorage.LookupByKey(unit.ToPlayer().GetPrimarySpecialization()).Role == 0) ? 1 : 0;
case UnitConditionVariable.IsSpecRanged:
return (unit.IsPlayer() && CliDB.ChrSpecializationStorage.LookupByKey(unit.ToPlayer().GetPrimarySpecialization()).Flags.HasFlag(ChrSpecializationFlag.Ranged)) ? 1 : 0;
return (unit.IsPlayer() && unit.ToPlayer().GetPrimarySpecialization() != 0 && CliDB.ChrSpecializationStorage.LookupByKey(unit.ToPlayer().GetPrimarySpecialization()).Flags.HasFlag(ChrSpecializationFlag.Ranged)) ? 1 : 0;
case UnitConditionVariable.IsSpecHealer:
return (unit.IsPlayer() && CliDB.ChrSpecializationStorage.LookupByKey(unit.ToPlayer().GetPrimarySpecialization()).Role == 1) ? 1 : 0;
return (unit.IsPlayer() && unit.ToPlayer().GetPrimarySpecialization() != 0 && CliDB.ChrSpecializationStorage.LookupByKey(unit.ToPlayer().GetPrimarySpecialization()).Role == 1) ? 1 : 0;
case UnitConditionVariable.IsPlayerControlledNPC:
return unit.IsCreature() && unit.HasUnitFlag(UnitFlags.PlayerControlled) ? 1 : 0;
case UnitConditionVariable.IsDying:
+48
View File
@@ -275,6 +275,7 @@ namespace Game.DataStorage
SceneScriptTextStorage = ReadDB2<SceneScriptTextRecord>("SceneScriptText.db2", HotfixStatements.SEL_SCENE_SCRIPT_TEXT);
SkillLineStorage = ReadDB2<SkillLineRecord>("SkillLine.db2", HotfixStatements.SEL_SKILL_LINE, HotfixStatements.SEL_SKILL_LINE_LOCALE);
SkillLineAbilityStorage = ReadDB2<SkillLineAbilityRecord>("SkillLineAbility.db2", HotfixStatements.SEL_SKILL_LINE_ABILITY);
SkillLineXTraitTreeStorage = ReadDB2<SkillLineXTraitTreeRecord>("SkillLineXTraitTree.db2", HotfixStatements.SEL_SKILL_LINE_X_TRAIT_TREE);
SkillRaceClassInfoStorage = ReadDB2<SkillRaceClassInfoRecord>("SkillRaceClassInfo.db2", HotfixStatements.SEL_SKILL_RACE_CLASS_INFO);
SoulbindConduitRankStorage = ReadDB2<SoulbindConduitRankRecord>("SoulbindConduitRank.db2", HotfixStatements.SEL_SOULBIND_CONDUIT_RANK);
SoundKitStorage = ReadDB2<SoundKitRecord>("SoundKit.db2", HotfixStatements.SEL_SOUND_KIT);
@@ -326,6 +327,29 @@ namespace Game.DataStorage
TaxiPathNodeStorage = ReadDB2<TaxiPathNodeRecord>("TaxiPathNode.db2", HotfixStatements.SEL_TAXI_PATH_NODE);
TotemCategoryStorage = ReadDB2<TotemCategoryRecord>("TotemCategory.db2", HotfixStatements.SEL_TOTEM_CATEGORY, HotfixStatements.SEL_TOTEM_CATEGORY_LOCALE);
ToyStorage = ReadDB2<ToyRecord>("Toy.db2", HotfixStatements.SEL_TOY, HotfixStatements.SEL_TOY_LOCALE);
TraitCondStorage = ReadDB2<TraitCondRecord>("TraitCond.db2", HotfixStatements.SEL_TRAIT_COND);
TraitCostStorage = ReadDB2<TraitCostRecord>("TraitCost.db2", HotfixStatements.SEL_TRAIT_COST);
TraitCurrencyStorage = ReadDB2<TraitCurrencyRecord>("TraitCurrency.db2", HotfixStatements.SEL_TRAIT_CURRENCY);
TraitCurrencySourceStorage = ReadDB2<TraitCurrencySourceRecord>("TraitCurrencySource.db2", HotfixStatements.SEL_TRAIT_CURRENCY_SOURCE, HotfixStatements.SEL_TRAIT_CURRENCY_SOURCE_LOCALE);
TraitDefinitionStorage = ReadDB2<TraitDefinitionRecord>("TraitDefinition.db2", HotfixStatements.SEL_TRAIT_DEFINITION, HotfixStatements.SEL_TRAIT_DEFINITION_LOCALE);
TraitDefinitionEffectPointsStorage = ReadDB2<TraitDefinitionEffectPointsRecord>("TraitDefinitionEffectPoints.db2", HotfixStatements.SEL_TRAIT_DEFINITION_EFFECT_POINTS);
TraitEdgeStorage = ReadDB2<TraitEdgeRecord>("TraitEdge.db2", HotfixStatements.SEL_TRAIT_EDGE);
TraitNodeStorage = ReadDB2<TraitNodeRecord>("TraitNode.db2", HotfixStatements.SEL_TRAIT_NODE);
TraitNodeEntryStorage = ReadDB2<TraitNodeEntryRecord>("TraitNodeEntry.db2", HotfixStatements.SEL_TRAIT_NODE_ENTRY);
TraitNodeEntryXTraitCondStorage = ReadDB2<TraitNodeEntryXTraitCondRecord>("TraitNodeEntryXTraitCond.db2", HotfixStatements.SEL_TRAIT_NODE_ENTRY_X_TRAIT_COND);
TraitNodeEntryXTraitCostStorage = ReadDB2<TraitNodeEntryXTraitCostRecord>("TraitNodeEntryXTraitCost.db2", HotfixStatements.SEL_TRAIT_NODE_ENTRY_X_TRAIT_COST);
TraitNodeGroupStorage = ReadDB2<TraitNodeGroupRecord>("TraitNodeGroup.db2", HotfixStatements.SEL_TRAIT_NODE_GROUP);
TraitNodeGroupXTraitCondStorage = ReadDB2<TraitNodeGroupXTraitCondRecord>("TraitNodeGroupXTraitCond.db2", HotfixStatements.SEL_TRAIT_NODE_GROUP_X_TRAIT_COND);
TraitNodeGroupXTraitCostStorage = ReadDB2<TraitNodeGroupXTraitCostRecord>("TraitNodeGroupXTraitCost.db2", HotfixStatements.SEL_TRAIT_NODE_GROUP_X_TRAIT_COST);
TraitNodeGroupXTraitNodeStorage = ReadDB2<TraitNodeGroupXTraitNodeRecord>("TraitNodeGroupXTraitNode.db2", HotfixStatements.SEL_TRAIT_NODE_GROUP_X_TRAIT_NODE);
TraitNodeXTraitCondStorage = ReadDB2<TraitNodeXTraitCondRecord>("TraitNodeXTraitCond.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_COND);
TraitNodeXTraitCostStorage = ReadDB2<TraitNodeXTraitCostRecord>("TraitNodeXTraitCost.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_COST);
TraitNodeXTraitNodeEntryStorage = ReadDB2<TraitNodeXTraitNodeEntryRecord>("TraitNodeXTraitNodeEntry.db2", HotfixStatements.SEL_TRAIT_NODE_X_TRAIT_NODE_ENTRY);
TraitTreeStorage = ReadDB2<TraitTreeRecord>("TraitTree.db2", HotfixStatements.SEL_TRAIT_TREE);
TraitTreeLoadoutStorage = ReadDB2<TraitTreeLoadoutRecord>("TraitTreeLoadout.db2", HotfixStatements.SEL_TRAIT_TREE_LOADOUT);
TraitTreeLoadoutEntryStorage = ReadDB2<TraitTreeLoadoutEntryRecord>("TraitTreeLoadoutEntry.db2", HotfixStatements.SEL_TRAIT_TREE_LOADOUT_ENTRY);
TraitTreeXTraitCostStorage = ReadDB2<TraitTreeXTraitCostRecord>("TraitTreeXTraitCost.db2", HotfixStatements.SEL_TRAIT_TREE_X_TRAIT_COST);
TraitTreeXTraitCurrencyStorage = ReadDB2<TraitTreeXTraitCurrencyRecord>("TraitTreeXTraitCurrency.db2", HotfixStatements.SEL_TRAIT_TREE_X_TRAIT_CURRENCY);
TransmogHolidayStorage = ReadDB2<TransmogHolidayRecord>("TransmogHoliday.db2", HotfixStatements.SEL_TRANSMOG_HOLIDAY);
TransmogIllusionStorage = ReadDB2<TransmogIllusionRecord>("TransmogIllusion.db2", HotfixStatements.SEL_TRANSMOG_ILLUSION);
TransmogSetStorage = ReadDB2<TransmogSetRecord>("TransmogSet.db2", HotfixStatements.SEL_TRANSMOG_SET, HotfixStatements.SEL_TRANSMOG_SET_LOCALE);
@@ -674,6 +698,7 @@ namespace Game.DataStorage
public static DB6Storage<SceneScriptTextRecord> SceneScriptTextStorage;
public static DB6Storage<SkillLineRecord> SkillLineStorage;
public static DB6Storage<SkillLineAbilityRecord> SkillLineAbilityStorage;
public static DB6Storage<SkillLineXTraitTreeRecord> SkillLineXTraitTreeStorage;
public static DB6Storage<SkillRaceClassInfoRecord> SkillRaceClassInfoStorage;
public static DB6Storage<SoulbindConduitRankRecord> SoulbindConduitRankStorage;
public static DB6Storage<SoundKitRecord> SoundKitStorage;
@@ -725,6 +750,29 @@ namespace Game.DataStorage
public static DB6Storage<TaxiPathNodeRecord> TaxiPathNodeStorage;
public static DB6Storage<TotemCategoryRecord> TotemCategoryStorage;
public static DB6Storage<ToyRecord> ToyStorage;
public static DB6Storage<TraitCondRecord> TraitCondStorage;
public static DB6Storage<TraitCostRecord> TraitCostStorage;
public static DB6Storage<TraitCurrencyRecord> TraitCurrencyStorage;
public static DB6Storage<TraitCurrencySourceRecord> TraitCurrencySourceStorage;
public static DB6Storage<TraitDefinitionRecord> TraitDefinitionStorage;
public static DB6Storage<TraitDefinitionEffectPointsRecord> TraitDefinitionEffectPointsStorage;
public static DB6Storage<TraitEdgeRecord> TraitEdgeStorage;
public static DB6Storage<TraitNodeRecord> TraitNodeStorage;
public static DB6Storage<TraitNodeEntryRecord> TraitNodeEntryStorage;
public static DB6Storage<TraitNodeEntryXTraitCondRecord> TraitNodeEntryXTraitCondStorage;
public static DB6Storage<TraitNodeEntryXTraitCostRecord> TraitNodeEntryXTraitCostStorage;
public static DB6Storage<TraitNodeGroupRecord> TraitNodeGroupStorage;
public static DB6Storage<TraitNodeGroupXTraitCondRecord> TraitNodeGroupXTraitCondStorage;
public static DB6Storage<TraitNodeGroupXTraitCostRecord> TraitNodeGroupXTraitCostStorage;
public static DB6Storage<TraitNodeGroupXTraitNodeRecord> TraitNodeGroupXTraitNodeStorage;
public static DB6Storage<TraitNodeXTraitCondRecord> TraitNodeXTraitCondStorage;
public static DB6Storage<TraitNodeXTraitCostRecord> TraitNodeXTraitCostStorage;
public static DB6Storage<TraitNodeXTraitNodeEntryRecord> TraitNodeXTraitNodeEntryStorage;
public static DB6Storage<TraitTreeRecord> TraitTreeStorage;
public static DB6Storage<TraitTreeLoadoutRecord> TraitTreeLoadoutStorage;
public static DB6Storage<TraitTreeLoadoutEntryRecord> TraitTreeLoadoutEntryStorage;
public static DB6Storage<TraitTreeXTraitCostRecord> TraitTreeXTraitCostStorage;
public static DB6Storage<TraitTreeXTraitCurrencyRecord> TraitTreeXTraitCurrencyStorage;
public static DB6Storage<TransmogHolidayRecord> TransmogHolidayStorage;
public static DB6Storage<TransmogIllusionRecord> TransmogIllusionStorage;
public static DB6Storage<TransmogSetRecord> TransmogSetStorage;
+9 -4
View File
@@ -1985,19 +1985,24 @@ namespace Game.DataStorage
public SkillRaceClassInfoRecord GetSkillRaceClassInfo(uint skill, Race race, Class class_)
{
var bounds = _skillRaceClassInfoBySkill.LookupByKey(skill);
foreach (var record in bounds)
foreach (var skllRaceClassInfo in bounds)
{
if (record.RaceMask != 0 && !Convert.ToBoolean(record.RaceMask & SharedConst.GetMaskForRace(race)))
if (skllRaceClassInfo.RaceMask != 0 && !Convert.ToBoolean(skllRaceClassInfo.RaceMask & SharedConst.GetMaskForRace(race)))
continue;
if (record.ClassMask != 0 && !Convert.ToBoolean(record.ClassMask & (1 << ((byte)class_ - 1))))
if (skllRaceClassInfo.ClassMask != 0 && !Convert.ToBoolean(skllRaceClassInfo.ClassMask & (1 << ((byte)class_ - 1))))
continue;
return record;
return skllRaceClassInfo;
}
return null;
}
public List<SkillRaceClassInfoRecord> GetSkillRaceClassInfo(uint skill)
{
return _skillRaceClassInfoBySkill.LookupByKey(skill);
}
public SoulbindConduitRankRecord GetSoulbindConduitRank(int soulbindConduitId, int rank)
{
return _soulbindConduitRanks.LookupByKey(Tuple.Create(soulbindConduitId, rank));
+1 -1
View File
@@ -292,7 +292,7 @@ namespace Game.DataStorage
public sealed class ChrSpecializationRecord
{
public string Name;
public LocalizedString Name;
public string FemaleName;
public string Description;
public uint Id;
@@ -124,6 +124,14 @@ namespace Game.DataStorage
public ushort SkillupSkillLineID;
}
public sealed class SkillLineXTraitTreeRecord
{
public uint Id;
public int SkillLineID;
public int TraitTreeID;
public int OrderIndex;
}
public sealed class SkillRaceClassInfoRecord
{
public uint Id;
+216 -1
View File
@@ -103,6 +103,222 @@ namespace Game.DataStorage
public int RequiredTransmogHoliday;
}
public sealed class TraitCondRecord
{
public uint Id;
public int CondType;
public int TraitTreeID;
public int GrantedRanks;
public uint QuestID;
public uint AchievementID;
public int SpecSetID;
public int TraitNodeGroupID;
public int TraitNodeID;
public int TraitCurrencyID;
public int SpentAmountRequired;
public int Flags;
public int RequiredLevel;
public int FreeSharedStringID;
public int SpendMoreSharedStringID;
public TraitConditionType GetCondType() { return (TraitConditionType)CondType; }
}
public sealed class TraitCostRecord
{
public string InternalName;
public uint Id;
public int Amount;
public int TraitCurrencyID;
}
public sealed class TraitCurrencyRecord
{
public uint Id;
public int Type;
public int CurrencyTypesID;
public int Flags;
public int Icon;
public TraitCurrencyType GetCurrencyType() { return (TraitCurrencyType)Type; }
}
public sealed class TraitCurrencySourceRecord
{
public LocalizedString Requirement;
public uint Id;
public int TraitCurrencyID;
public int Amount;
public uint QuestID;
public uint AchievementID;
public uint PlayerLevel;
public int TraitNodeEntryID;
public int OrderIndex;
}
public sealed class TraitDefinitionRecord
{
public LocalizedString OverrideName;
public LocalizedString OverrideSubtext;
public LocalizedString OverrideDescription;
public uint Id;
public uint SpellID;
public int OverrideIcon;
public uint OverridesSpellID;
public uint VisibleSpellID;
}
public sealed class TraitDefinitionEffectPointsRecord
{
public uint Id;
public int TraitDefinitionID;
public int EffectIndex;
public int OperationType;
public int CurveID;
public TraitPointsOperationType GetOperationType() { return (TraitPointsOperationType)OperationType; }
}
public sealed class TraitEdgeRecord
{
public uint Id;
public int VisualStyle;
public int LeftTraitNodeID;
public int RightTraitNodeID;
public int Type;
}
public sealed class TraitNodeRecord
{
public uint Id;
public int TraitTreeID;
public int PosX;
public int PosY;
public sbyte Type;
public int Flags;
public TraitNodeType GetNodeType() { return (TraitNodeType)Type; }
}
public sealed class TraitNodeEntryRecord
{
public uint Id;
public int TraitDefinitionID;
public int MaxRanks;
public byte NodeRecordType;
}
public sealed class TraitNodeEntryXTraitCondRecord
{
public uint Id;
public int TraitCondID;
public uint TraitNodeEntryID;
}
public sealed class TraitNodeEntryXTraitCostRecord
{
public uint Id;
public int TraitNodeEntryID;
public int TraitCostID;
}
public sealed class TraitNodeGroupRecord
{
public uint Id;
public int TraitTreeID;
public int Flags;
}
public sealed class TraitNodeGroupXTraitCondRecord
{
public uint Id;
public int TraitCondID;
public int TraitNodeGroupID;
}
public sealed class TraitNodeGroupXTraitCostRecord
{
public uint Id;
public int TraitNodeGroupID;
public int TraitCostID;
}
public sealed class TraitNodeGroupXTraitNodeRecord
{
public uint Id;
public int TraitNodeGroupID;
public int TraitNodeID;
public int Index;
}
public sealed class TraitNodeXTraitCondRecord
{
public uint Id;
public int TraitCondID;
public int TraitNodeID;
}
public sealed class TraitNodeXTraitCostRecord
{
public uint Id;
public uint TraitNodeID;
public int TraitCostID;
}
public sealed class TraitNodeXTraitNodeEntryRecord
{
public uint Id;
public int TraitNodeID;
public int TraitNodeEntryID;
public int Index;
}
public sealed class TraitTreeRecord
{
public uint Id;
public int TraitSystemID;
public int Unused1000_1;
public int FirstTraitNodeID;
public int PlayerConditionID;
public int Flags;
public float Unused1000_2;
public float Unused1000_3;
public TraitTreeFlag GetFlags() { return (TraitTreeFlag)Flags; }
}
public sealed class TraitTreeLoadoutRecord
{
public uint Id;
public int TraitTreeID;
public int ChrSpecializationID;
}
public sealed class TraitTreeLoadoutEntryRecord
{
public uint Id;
public int TraitTreeLoadoutID;
public int SelectedTraitNodeID;
public int SelectedTraitNodeEntryID;
public int NumPoints;
public int OrderIndex;
}
public sealed class TraitTreeXTraitCostRecord
{
public uint Id;
public uint TraitTreeID;
public int TraitCostID;
}
public sealed class TraitTreeXTraitCurrencyRecord
{
public uint Id;
public int Index;
public int TraitTreeID;
public int TraitCurrencyID;
}
public sealed class TransmogIllusionRecord
{
public uint Id;
@@ -161,5 +377,4 @@ namespace Game.DataStorage
public uint TimeIndex;
public uint GameObjectsID;
}
}
@@ -437,6 +437,12 @@ namespace Game.Entities
return updateField;
}
public UpdateFieldString ModifyValue(UpdateFieldString updateField)
{
MarkChanged(updateField);
return updateField;
}
public ref U ModifyValue<U>(UpdateFieldArray<U> updateField, int index) where U : new()
{
MarkChanged(updateField, index);
@@ -472,6 +478,12 @@ namespace Game.Entities
_changesMask.Set(updateField.Bit);
}
public void MarkChanged(UpdateFieldString updateField)
{
_changesMask.Set(updateField.BlockBit);
_changesMask.Set(updateField.Bit);
}
public void MarkChanged<U>(UpdateFieldArray<U> updateField, int index) where U : new()
{
_changesMask.Set(updateField.Bit);
+2 -2
View File
@@ -824,7 +824,7 @@ namespace Game.Entities
public abstract void BuildValuesCreate(WorldPacket data, Player target);
public abstract void BuildValuesUpdate(WorldPacket data, Player target);
public void SetUpdateFieldValue<T>(IUpdateField<T> updateField, T newValue) where T : new()
public void SetUpdateFieldValue<T>(IUpdateField<T> updateField, T newValue)
{
if (!newValue.Equals(updateField.GetValue()))
{
@@ -863,7 +863,7 @@ namespace Game.Entities
SetUpdateFieldValue(ref value, (T)(value | (dynamic)flag));
}
public void RemoveUpdateFieldFlagValue<T>(IUpdateField<T> updateField, T flag) where T : new()
public void RemoveUpdateFieldFlagValue<T>(IUpdateField<T> updateField, T flag)
{
//static_assert(std::is_integral < T >::value, "SetUpdateFieldFlagValue must be used with integral types");
SetUpdateFieldValue(updateField, (T)(updateField.GetValue() & ~(dynamic)flag));
+268 -4
View File
@@ -1010,6 +1010,146 @@ namespace Game.Entities
while (result.NextRow());
}
}
void _LoadTraits(SQLResult configsResult, SQLResult entriesResult)
{
MultiMap<int, TraitEntryPacket> traitEntriesByConfig = new();
if (!entriesResult.IsEmpty())
{
// 0 1, 2 3 4
// SELECT traitConfigId, traitNodeId, traitNodeEntryId, rank, grantedRanks FROM character_trait_entry WHERE guid = ?
do
{
TraitEntryPacket traitEntry = new();
traitEntry.TraitNodeID = entriesResult.Read<int>(1);
traitEntry.TraitNodeEntryID = entriesResult.Read<int>(2);
traitEntry.Rank = entriesResult.Read<int>(3);
traitEntry.GrantedRanks = entriesResult.Read<int>(4);
if (!TraitMgr.IsValidEntry(traitEntry))
continue;
traitEntriesByConfig.Add(entriesResult.Read<int>(0), traitEntry);
} while (entriesResult.NextRow());
}
if (!configsResult.IsEmpty())
{
// 0 1 2 3 4 5 6 7
// SELECT traitConfigId, type, chrSpecializationId, combatConfigFlags, localIdentifier, skillLineId, traitSystemId, `name` FROM character_trait_config WHERE guid = ?
do
{
TraitConfigPacket traitConfig = new();
traitConfig.ID = configsResult.Read<int>(0);
traitConfig.Type = (TraitConfigType)configsResult.Read<int>(1);
switch (traitConfig.Type)
{
case TraitConfigType.Combat:
traitConfig.ChrSpecializationID = configsResult.Read<int>(2);
traitConfig.CombatConfigFlags = (TraitCombatConfigFlags)configsResult.Read<int>(3);
traitConfig.LocalIdentifier = configsResult.Read<int>(4);
break;
case TraitConfigType.Profession:
traitConfig.SkillLineID = configsResult.Read<uint>(5);
break;
case TraitConfigType.Generic:
traitConfig.TraitSystemID = configsResult.Read<int>(6);
break;
default:
break;
}
traitConfig.Name = configsResult.Read<string>(7);
foreach (var traitEntry in traitEntriesByConfig.LookupByKey(traitConfig.ID))
traitConfig.Entries.Add(traitEntry);
if (TraitMgr.ValidateConfig(traitConfig, this) != TalentLearnResult.LearnOk)
{
traitConfig.Entries.Clear();
foreach (TraitEntry grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(traitConfig, this))
traitConfig.Entries.Add(new TraitEntryPacket(grantedEntry));
}
AddTraitConfig(traitConfig);
} while (configsResult.NextRow());
}
bool hasConfigForSpec(int specId)
{
return m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return traitConfig.Type == (int)TraitConfigType.Combat
&& traitConfig.ChrSpecializationID == specId
&& (traitConfig.CombatConfigFlags & (int)TraitCombatConfigFlags.ActiveForSpec) != 0;
}) >= 0;
}
int findFreeLocalIdentifier(int specId)
{
int index = 1;
while (m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return traitConfig.Type == (int)TraitConfigType.Combat
&& traitConfig.ChrSpecializationID == specId
&& traitConfig.LocalIdentifier == index;
}) >= 0)
++index;
return index;
}
for (uint i = 0; i < PlayerConst.MaxSpecializations - 1 /*initial spec doesnt get a config*/; ++i)
{
var spec = Global.DB2Mgr.GetChrSpecializationByIndex(GetClass(), i);
if (spec != null)
{
if (hasConfigForSpec((int)spec.Id))
continue;
TraitConfigPacket traitConfig = new();
traitConfig.Type = TraitConfigType.Combat;
traitConfig.ChrSpecializationID = (int)spec.Id;
traitConfig.CombatConfigFlags = TraitCombatConfigFlags.ActiveForSpec;
traitConfig.LocalIdentifier = findFreeLocalIdentifier((int)spec.Id);
traitConfig.Name = spec.Name[GetSession().GetSessionDbcLocale()];
CreateTraitConfig(traitConfig);
}
}
int activeConfig = m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return traitConfig.Type == (int)TraitConfigType.Combat
&& traitConfig.ChrSpecializationID == GetPrimarySpecialization()
&& (traitConfig.CombatConfigFlags & (int)TraitCombatConfigFlags.ActiveForSpec) != 0;
});
if (activeConfig >= 0)
SetActiveCombatTraitConfigID(m_activePlayerData.TraitConfigs[activeConfig].ID);
foreach (TraitConfig traitConfig in m_activePlayerData.TraitConfigs)
{
switch ((TraitConfigType)(int)traitConfig.Type)
{
case TraitConfigType.Combat:
if (traitConfig.ID != m_activePlayerData.ActiveCombatTraitConfigID)
continue;
break;
case TraitConfigType.Profession:
if (!HasSkill((uint)(int)traitConfig.SkillLineID))
continue;
break;
default:
break;
}
ApplyTraitConfig(traitConfig.ID, true);
}
}
void _LoadGlyphs(SQLResult result)
{
// SELECT talentGroup, glyphId from character_glyphs WHERE guid = ?
@@ -1863,6 +2003,23 @@ namespace Game.Entities
void _SaveActions(SQLTransaction trans)
{
int 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 = m_activePlayerData.TraitConfigs[usedSavedTraitConfigIndex].ID;
}
PreparedStatement stmt;
foreach (var pair in m_actionButtons.ToList())
@@ -1873,9 +2030,10 @@ namespace Game.Entities
stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_ACTION);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, GetActiveTalentGroup());
stmt.AddValue(2, pair.Key);
stmt.AddValue(3, pair.Value.GetAction());
stmt.AddValue(4, (byte)pair.Value.GetButtonType());
stmt.AddValue(2, traitConfigId);
stmt.AddValue(3, pair.Key);
stmt.AddValue(4, pair.Value.GetAction());
stmt.AddValue(5, (byte)pair.Value.GetButtonType());
trans.Append(stmt);
pair.Value.uState = ActionButtonUpdateState.UnChanged;
@@ -1887,6 +2045,7 @@ namespace Game.Entities
stmt.AddValue(2, GetGUID().GetCounter());
stmt.AddValue(3, pair.Key);
stmt.AddValue(4, GetActiveTalentGroup());
stmt.AddValue(5, traitConfigId);
trans.Append(stmt);
pair.Value.uState = ActionButtonUpdateState.UnChanged;
@@ -1896,6 +2055,7 @@ namespace Game.Entities
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, pair.Key);
stmt.AddValue(2, GetActiveTalentGroup());
stmt.AddValue(3, traitConfigId);
trans.Append(stmt);
m_actionButtons.Remove(pair.Key);
@@ -2135,6 +2295,99 @@ namespace Game.Entities
trans.Append(stmt);
}
}
void _SaveTraits(SQLTransaction trans)
{
PreparedStatement stmt = null;
foreach (var (traitConfigId, state) in m_traitConfigStates)
{
switch (state)
{
case PlayerSpellState.Changed:
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, traitConfigId);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TRAIT_CONFIGS);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, traitConfigId);
trans.Append(stmt);
TraitConfig traitConfig = GetTraitConfig(traitConfigId);
if (traitConfig != null)
{
stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_TRAIT_CONFIGS);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, traitConfig.ID);
stmt.AddValue(2, traitConfig.Type);
switch ((TraitConfigType)(int)traitConfig.Type)
{
case TraitConfigType.Combat:
stmt.AddValue(3, traitConfig.ChrSpecializationID);
stmt.AddValue(4, traitConfig.CombatConfigFlags);
stmt.AddValue(5, traitConfig.LocalIdentifier);
stmt.AddNull(6);
stmt.AddNull(7);
break;
case TraitConfigType.Profession:
stmt.AddNull(3);
stmt.AddNull(4);
stmt.AddNull(5);
stmt.AddValue(6, traitConfig.SkillLineID);
stmt.AddNull(7);
break;
case TraitConfigType.Generic:
stmt.AddNull(3);
stmt.AddNull(4);
stmt.AddNull(5);
stmt.AddNull(6);
stmt.AddValue(7, traitConfig.TraitSystemID);
break;
default:
break;
}
stmt.AddValue(8, traitConfig.Name);
trans.Append(stmt);
foreach (var traitEntry in traitConfig.Entries)
{
stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_TRAIT_ENTRIES);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, traitConfig.ID);
stmt.AddValue(2, traitEntry.TraitNodeID);
stmt.AddValue(3, traitEntry.TraitNodeEntryID);
stmt.AddValue(4, traitEntry.Rank);
stmt.AddValue(5, traitEntry.GrantedRanks);
trans.Append(stmt);
}
}
break;
case PlayerSpellState.Removed:
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, traitConfigId);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TRAIT_CONFIGS);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, traitConfigId);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_ACTION_BY_TRAIT_CONFIG);
stmt.AddValue(0, GetGUID().GetCounter());
stmt.AddValue(1, traitConfigId);
trans.Append(stmt);
break;
default:
break;
}
}
m_traitConfigStates.Clear();
}
public void _SaveMail(SQLTransaction trans)
{
PreparedStatement stmt;
@@ -3074,6 +3327,8 @@ namespace Game.Entities
LearnDefaultSkills();
LearnCustomSpells();
_LoadTraits(holder.GetResult(PlayerLoginQueryLoad.TraitConfigs), holder.GetResult(PlayerLoginQueryLoad.TraitEntries)); // must be after loading spells
// must be before inventory (some items required reputation check)
reputationMgr.LoadFromDB(holder.GetResult(PlayerLoginQueryLoad.Reputation));
@@ -3086,7 +3341,7 @@ namespace Game.Entities
// update items with duration and realtime
UpdateItemDuration(time_diff, true);
_LoadActions(holder.GetResult(PlayerLoginQueryLoad.Actions));
StartLoadingActionButtons();
// unread mails and next delivery time, actual mails not loaded
_LoadMail(holder.GetResult(PlayerLoginQueryLoad.Mails),
@@ -3633,6 +3888,7 @@ namespace Game.Entities
_SaveMonthlyQuestStatus(characterTransaction);
_SaveGlyphs(characterTransaction);
_SaveTalents(characterTransaction);
_SaveTraits(characterTransaction);
_SaveSpells(characterTransaction);
GetSpellHistory().SaveToDB<Player>(characterTransaction);
_SaveActions(characterTransaction);
@@ -4183,6 +4439,14 @@ namespace Game.Entities
Garrison.DeleteFromDB(guid, trans);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TRAIT_ENTRIES_BY_CHAR);
stmt.AddValue(0, guid);
trans.Append(stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_TRAIT_CONFIGS_BY_CHAR);
stmt.AddValue(0, guid);
trans.Append(stmt);
Global.CharacterCacheStorage.DeleteCharacterCacheEntry(playerGuid, name);
break;
}
@@ -257,6 +257,8 @@ namespace Game.Entities
uint m_PlayedTimeTotal;
uint m_PlayedTimeLevel;
Dictionary<int, PlayerSpellState> m_traitConfigStates = new();
Dictionary<byte, ActionButton> m_actionButtons = new();
ObjectGuid m_playerSharingQuest;
uint m_sharedQuestId;
+79 -12
View File
@@ -1024,13 +1024,17 @@ namespace Game.Entities
}
public bool HasSkill(SkillType skill)
{
return HasSkill((uint)skill);
}
public bool HasSkill(uint skill)
{
if (skill == 0)
return false;
SkillInfo skillInfoField = m_activePlayerData.Skill;
var skillStatusData = mSkillStatus.LookupByKey((uint)skill);
var skillStatusData = mSkillStatus.LookupByKey(skill);
return skillStatusData != null && skillStatusData.State != SkillState.Deleted && skillInfoField.SkillRank[skillStatusData.Pos] != 0;
}
public void SetSkill(SkillType skill, uint step, uint newVal, uint maxVal)
@@ -2046,14 +2050,14 @@ namespace Game.Entities
SendPacket(new SendUnlearnSpells());
}
public void LearnSpell(uint spellId, bool dependent, uint fromSkill = 0, bool suppressMessaging = false)
public void LearnSpell(uint spellId, bool dependent, uint fromSkill = 0, bool suppressMessaging = false, int? traitDefinitionId = null)
{
PlayerSpell spell = m_spells.LookupByKey(spellId);
bool disabled = (spell != null) && spell.Disabled;
bool active = !disabled || spell.Active;
bool learning = AddSpell(spellId, active, true, dependent, false, false, fromSkill);
bool learning = AddSpell(spellId, active, true, dependent, false, false, fromSkill, traitDefinitionId);
// prevent duplicated entires in spell book, also not send if not in world (loading)
if (learning && IsInWorld)
@@ -2061,6 +2065,7 @@ namespace Game.Entities
LearnedSpells learnedSpells = new();
LearnedSpellInfo learnedSpellInfo = new();
learnedSpellInfo.SpellID = spellId;
learnedSpellInfo.TraitDefinitionID = traitDefinitionId;
learnedSpells.SuppressMessaging = suppressMessaging;
learnedSpells.ClientLearnedSpellData.Add(learnedSpellInfo);
SendPacket(learnedSpells);
@@ -2315,7 +2320,7 @@ namespace Game.Entities
return null;
}
bool AddSpell(uint spellId, bool active, bool learning, bool dependent, bool disabled, bool loading = false, uint fromSkill = 0)
bool AddSpell(uint spellId, bool active, bool learning, bool dependent, bool disabled, bool loading = false, uint fromSkill = 0, int? traitDefinitionId = null)
{
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId, Difficulty.None);
if (spellInfo == null)
@@ -2394,6 +2399,18 @@ namespace Game.Entities
dependent_set = true;
}
if (spell.TraitDefinitionId != traitDefinitionId)
{
if (spell.TraitDefinitionId.HasValue)
{
TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(spell.TraitDefinitionId.Value);
if (traitDefinition != null)
RemoveOverrideSpell((uint)traitDefinition.OverridesSpellID, spellId);
}
spell.TraitDefinitionId = traitDefinitionId;
}
// update active state for known spell
if (spell.Active != active && spell.State != PlayerSpellState.Removed && !spell.Disabled)
{
@@ -2475,6 +2492,8 @@ namespace Game.Entities
newspell.Active = active;
newspell.Dependent = dependent;
newspell.Disabled = disabled;
if (traitDefinitionId.HasValue)
newspell.TraitDefinitionId = traitDefinitionId.Value;
// replace spells in action bars and spellbook to bigger rank if only one spell rank must be accessible
if (newspell.Active && !newspell.Disabled && spellInfo.IsRanked())
@@ -2524,26 +2543,73 @@ namespace Game.Entities
return false;
}
bool castSpell = false;
// cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned)
// note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive
if (!loading && spellInfo.HasAttribute(SpellCustomAttributes.IsTalent) && spellInfo.HasEffect(SpellEffectName.LearnSpell))
{
// ignore stance requirement for talent learn spell (stance set for spell only for client spell description show)
CastSpell(this, spellId, true);
castSpell = true;
}
// also cast passive spells (including all talents without SPELL_EFFECT_LEARN_SPELL) with additional checks
else if (spellInfo.IsPassive())
{
if (HandlePassiveSpellLearn(spellInfo))
CastSpell(this, spellId, true);
}
castSpell = HandlePassiveSpellLearn(spellInfo);
else if (spellInfo.HasEffect(SpellEffectName.SkillStep))
castSpell = true;
else if (spellInfo.HasAttribute(SpellAttr1.CastWhenLearned))
castSpell = true;
if (castSpell)
{
CastSpell(this, spellId, true);
CastSpellExtraArgs args = new(TriggerCastFlags.FullMask);
if (traitDefinitionId.HasValue)
{
TraitConfig traitConfig = GetTraitConfig((int)(uint)m_activePlayerData.ActiveCombatTraitConfigID);
if (traitConfig != null)
{
int traitEntryIndex = traitConfig.Entries.FindIndexIf(traitEntry =>
{
return CliDB.TraitNodeEntryStorage.LookupByKey(traitEntry.TraitNodeEntryID)?.TraitDefinitionID == traitDefinitionId;
});
int rank = 0;
if (traitEntryIndex >= 0)
rank = traitConfig.Entries[traitEntryIndex].Rank + traitConfig.Entries[traitEntryIndex].GrantedRanks;
if (rank > 0)
{
var traitDefinitionEffectPoints = TraitMgr.GetTraitDefinitionEffectPointModifiers(traitDefinitionId.Value);
if (traitDefinitionEffectPoints != null)
{
foreach (TraitDefinitionEffectPointsRecord traitDefinitionEffectPoint in traitDefinitionEffectPoints)
{
if (traitDefinitionEffectPoint.EffectIndex >= spellInfo.GetEffects().Count)
continue;
float basePoints = Global.DB2Mgr.GetCurveValueAt((uint)traitDefinitionEffectPoint.CurveID, rank);
if (traitDefinitionEffectPoint.GetOperationType() == TraitPointsOperationType.Multiply)
basePoints *= spellInfo.GetEffect((uint)traitDefinitionEffectPoint.EffectIndex).CalcBaseValue(this, null, 0, -1);
args.AddSpellMod(SpellValueMod.BasePoint0 + traitDefinitionEffectPoint.EffectIndex, (int)basePoints);
}
}
}
}
}
CastSpell(this, spellId, args);
if (spellInfo.HasEffect(SpellEffectName.SkillStep))
return false;
}
else if (spellInfo.HasAttribute(SpellAttr1.CastWhenLearned))
CastSpell(this, spellId, true);
if (traitDefinitionId.HasValue)
{
TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(traitDefinitionId.Value);
if (traitDefinition != null)
AddOverrideSpell(traitDefinition.OverridesSpellID, spellId);
}
// update free primary prof.points (if any, can be none in case GM .learn prof. learning)
uint freeProfs = GetFreePrimaryProfessionPoints();
@@ -3525,5 +3591,6 @@ namespace Game.Entities
public bool Active;
public bool Dependent;
public bool Disabled;
public int? TraitDefinitionId;
}
}
+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;
}
}
}
+3 -1
View File
@@ -6839,7 +6839,9 @@ namespace Game.Entities
return false;
}
//New shit
void SetActiveCombatTraitConfigID(int traitConfigId) { SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ActiveCombatTraitConfigID), (uint)traitConfigId); }
void InitPrimaryProfessions()
{
SetFreePrimaryProfessions(WorldConfig.GetUIntValue(WorldCfg.MaxPrimaryTradeSkill));
+10 -5
View File
@@ -2723,10 +2723,6 @@ namespace Game
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.VoidStorage, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_ACTIONS);
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.Actions, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_MAIL);
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.Mails, stmt);
@@ -2877,6 +2873,14 @@ namespace Game
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_GARRISON_FOLLOWER_ABILITIES);
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.GarrisonFollowerAbilities, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_TRAIT_ENTRIES);
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.TraitEntries, stmt);
stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHAR_TRAIT_CONFIGS);
stmt.AddValue(0, lowGuid);
SetQuery(PlayerLoginQueryLoad.TraitConfigs, stmt);
}
public ObjectGuid GetGuid() { return m_guid; }
@@ -2938,7 +2942,6 @@ namespace Game
AzeriteMilestonePowers,
AzeriteUnlockedEssences,
AzeriteEmpowered,
Actions,
Mails,
MailItems,
MailItemsArtifact,
@@ -2980,6 +2983,8 @@ namespace Game
GarrisonBuildings,
GarrisonFollowers,
GarrisonFollowerAbilities,
TraitEntries,
TraitConfigs,
Max
}
+258
View File
@@ -0,0 +1,258 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Game.DataStorage;
using Game.Entities;
using Game.Networking;
using Game.Networking.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Game
{
public partial class WorldSession
{
[WorldPacketHandler(ClientOpcodes.TraitsCommitConfig)]
void HandleTraitsCommitConfig(TraitsCommitConfig traitsCommitConfig)
{
int configId = (int)(uint)traitsCommitConfig.Config.ID;
TraitConfig existingConfig = _player.GetTraitConfig(configId);
if (existingConfig == null)
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedUnknown));
return;
}
if (_player.IsInCombat())
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedAffectingCombat));
return;
}
if (_player.GetBattleground() && _player.GetBattleground().GetStatus() == BattlegroundStatus.InProgress)
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.InPvpMatch));
return;
}
bool hasRemovedEntries = false;
TraitConfigPacket newConfigState = new(existingConfig);
foreach (TraitEntryPacket newEntry in traitsCommitConfig.Config.Entries)
{
TraitEntryPacket traitEntry = newConfigState.Entries.Find(traitEntry => traitEntry.TraitNodeID == newEntry.TraitNodeID && traitEntry.TraitNodeEntryID == newEntry.TraitNodeEntryID);
if (traitEntry != null && traitEntry.Rank > newEntry.Rank)
{
TraitNodeRecord traitNode = CliDB.TraitNodeStorage.LookupByKey(newEntry.TraitNodeID);
if (traitNode == null)
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedUnknown));
return;
}
TraitTreeRecord traitTree = CliDB.TraitTreeStorage.LookupByKey(traitNode.TraitTreeID);
if (traitTree == null)
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedUnknown));
return;
}
if (traitTree.GetFlags().HasFlag(TraitTreeFlag.CannotRefund))
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedCantRemoveTalent));
return;
}
TraitNodeEntryRecord traitNodeEntry = CliDB.TraitNodeEntryStorage.LookupByKey(newEntry.TraitNodeEntryID);
if (traitNodeEntry == null)
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedUnknown));
return;
}
TraitDefinitionRecord traitDefinition = CliDB.TraitDefinitionStorage.LookupByKey(traitNodeEntry.TraitDefinitionID);
if (traitDefinition == null)
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)TalentLearnResult.FailedUnknown));
return;
}
if (traitDefinition.SpellID != 0 && _player.GetSpellHistory().HasCooldown(traitDefinition.SpellID))
{
SendPacket(new TraitConfigCommitFailed(configId, traitDefinition.SpellID, (int)TalentLearnResult.FailedCantRemoveTalent));
return;
}
if (traitDefinition.VisibleSpellID != 0 && _player.GetSpellHistory().HasCooldown((uint)traitDefinition.VisibleSpellID))
{
SendPacket(new TraitConfigCommitFailed(configId, traitDefinition.VisibleSpellID, (int)TalentLearnResult.FailedCantRemoveTalent));
return;
}
hasRemovedEntries = true;
}
if (traitEntry != null)
{
if (newEntry.Rank != 0)
traitEntry.Rank = newEntry.Rank;
else
newConfigState.Entries.RemoveAll(TraitEntrytraitEntry => traitEntry.TraitNodeID == newEntry.TraitNodeID && traitEntry.TraitNodeEntryID == newEntry.TraitNodeEntryID);
}
else
newConfigState.Entries.Add(newEntry);
}
TalentLearnResult validationResult = TraitMgr.ValidateConfig(newConfigState, _player, true);
if (validationResult != TalentLearnResult.LearnOk)
{
SendPacket(new TraitConfigCommitFailed(configId, 0, (int)validationResult));
return;
}
bool needsCastTime = newConfigState.Type == TraitConfigType.Combat && hasRemovedEntries;
if (traitsCommitConfig.SavedLocalIdentifier != 0)
newConfigState.LocalIdentifier = traitsCommitConfig.SavedLocalIdentifier;
else
{
TraitConfig savedConfig = _player.GetTraitConfig(traitsCommitConfig.SavedLocalIdentifier);
if (savedConfig != null)
newConfigState.LocalIdentifier = savedConfig.LocalIdentifier;
}
_player.UpdateTraitConfig(newConfigState, traitsCommitConfig.SavedConfigID, needsCastTime);
}
[WorldPacketHandler(ClientOpcodes.ClassTalentsRequestNewConfig)]
void HandleClassTalentsRequestNewConfig(ClassTalentsRequestNewConfig classTalentsRequestNewConfig)
{
if (classTalentsRequestNewConfig.Config.Type != TraitConfigType.Combat)
return;
if ((classTalentsRequestNewConfig.Config.CombatConfigFlags & TraitCombatConfigFlags.ActiveForSpec) != (int)TraitCombatConfigFlags.None)
return;
long configCount = _player.m_activePlayerData.TraitConfigs._values.Count(traitConfig =>
{
return (TraitConfigType)(int)traitConfig.Type == TraitConfigType.Combat
&& ((TraitCombatConfigFlags)(int)traitConfig.CombatConfigFlags & TraitCombatConfigFlags.ActiveForSpec) == TraitCombatConfigFlags.None;
});
if (configCount >= TraitMgr.MAX_COMBAT_TRAIT_CONFIGS)
return;
int findFreeLocalIdentifier()
{
int index = 1;
while (_player.m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return (TraitConfigType)(int)traitConfig.Type == TraitConfigType.Combat
&& traitConfig.ChrSpecializationID == _player.GetPrimarySpecialization()
&& traitConfig.LocalIdentifier == index;
}) >= 0)
++index;
return index;
}
classTalentsRequestNewConfig.Config.ChrSpecializationID = (int)_player.GetPrimarySpecialization();
classTalentsRequestNewConfig.Config.LocalIdentifier = findFreeLocalIdentifier();
foreach (TraitEntry grantedEntry in TraitMgr.GetGrantedTraitEntriesForConfig(classTalentsRequestNewConfig.Config, _player))
{
var newEntry = classTalentsRequestNewConfig.Config.Entries.Find(entry => { return entry.TraitNodeID == grantedEntry.TraitNodeID && entry.TraitNodeEntryID == grantedEntry.TraitNodeEntryID; });
if (newEntry == null)
{
newEntry = new();
classTalentsRequestNewConfig.Config.Entries.Add(newEntry);
}
newEntry.TraitNodeID = grantedEntry.TraitNodeID;
newEntry.TraitNodeEntryID = grantedEntry.TraitNodeEntryID;
newEntry.Rank = grantedEntry.Rank;
newEntry.GrantedRanks = grantedEntry.GrantedRanks;
TraitNodeEntryRecord traitNodeEntry = CliDB.TraitNodeEntryStorage.LookupByKey(grantedEntry.TraitNodeEntryID);
if (traitNodeEntry != null)
if (newEntry.Rank + newEntry.GrantedRanks > traitNodeEntry.MaxRanks)
newEntry.Rank = Math.Max(0, traitNodeEntry.MaxRanks - newEntry.GrantedRanks);
}
TalentLearnResult validationResult = TraitMgr.ValidateConfig(classTalentsRequestNewConfig.Config, _player);
if (validationResult != TalentLearnResult.LearnOk)
return;
_player.CreateTraitConfig(classTalentsRequestNewConfig.Config);
}
[WorldPacketHandler(ClientOpcodes.ClassTalentsRenameConfig)]
void HandleClassTalentsRenameConfig(ClassTalentsRenameConfig classTalentsRenameConfig)
{
_player.RenameTraitConfig(classTalentsRenameConfig.ConfigID, classTalentsRenameConfig.Name);
}
[WorldPacketHandler(ClientOpcodes.ClassTalentsDeleteConfig)]
void HandleClassTalentsDeleteConfig(ClassTalentsDeleteConfig classTalentsDeleteConfig)
{
_player.DeleteTraitConfig(classTalentsDeleteConfig.ConfigID);
}
[WorldPacketHandler(ClientOpcodes.ClassTalentsSetStarterBuildActive)]
void HandleClassTalentsSetStarterBuildActive(ClassTalentsSetStarterBuildActive classTalentsSetStarterBuildActive)
{
TraitConfig traitConfig = _player.GetTraitConfig(classTalentsSetStarterBuildActive.ConfigID);
if (traitConfig == null)
return;
if ((TraitConfigType)(int)traitConfig.Type != TraitConfigType.Combat)
return;
if (!((TraitCombatConfigFlags)(int)traitConfig.CombatConfigFlags).HasFlag(TraitCombatConfigFlags.ActiveForSpec))
return;
if (classTalentsSetStarterBuildActive.Active)
{
TraitConfigPacket newConfigState = new(traitConfig);
int freeLocalIdentifier = 1;
while (_player.m_activePlayerData.TraitConfigs.FindIndexIf(traitConfig =>
{
return (TraitConfigType)(int)traitConfig.Type == TraitConfigType.Combat
&& traitConfig.ChrSpecializationID == _player.GetPrimarySpecialization()
&& traitConfig.LocalIdentifier == freeLocalIdentifier;
}) >= 0)
++freeLocalIdentifier;
TraitMgr.InitializeStarterBuildTraitConfig(newConfigState, _player);
newConfigState.CombatConfigFlags |= TraitCombatConfigFlags.StarterBuild;
newConfigState.LocalIdentifier = freeLocalIdentifier;
_player.UpdateTraitConfig(newConfigState, 0, true);
}
else
_player.SetTraitConfigUseStarterBuild(classTalentsSetStarterBuildActive.ConfigID, false);
}
[WorldPacketHandler(ClientOpcodes.ClassTalentsSetUsesSharedActionBars)]
void HandleClassTalentsSetUsesSharedActionBars(ClassTalentsSetUsesSharedActionBars classTalentsSetUsesSharedActionBars)
{
_player.SetTraitConfigUseSharedActionBars(classTalentsSetUsesSharedActionBars.ConfigID, classTalentsSetUsesSharedActionBars.UsesShared,
classTalentsSetUsesSharedActionBars.IsLastSelectedSavedConfig);
}
}
}
@@ -339,7 +339,7 @@ namespace Game.Networking.Packets
{
public int Level;
public int ChrSpecializationID;
public TraitConfig Config;
public TraitConfigPacket Config;
public void Write(WorldPacket data)
{
@@ -0,0 +1,256 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Game.Entities;
using System;
using System.Collections.Generic;
namespace Game.Networking.Packets
{
class TraitsCommitConfig : ClientPacket
{
public TraitConfigPacket Config = new();
public int SavedConfigID;
public int SavedLocalIdentifier;
public TraitsCommitConfig(WorldPacket packet) : base(packet) { }
public override void Read()
{
Config.Read(_worldPacket);
SavedConfigID = _worldPacket.ReadInt32();
SavedLocalIdentifier = _worldPacket.ReadInt32();
}
}
class TraitConfigCommitFailed : ServerPacket
{
public int ConfigID;
public uint SpellID;
public int Reason;
public TraitConfigCommitFailed(int configId = 0, uint spellId = 0, int reason = 0) : base(ServerOpcodes.TraitConfigCommitFailed)
{
ConfigID = configId;
SpellID = spellId;
Reason = reason;
}
public override void Write()
{
_worldPacket.WriteInt32(ConfigID);
_worldPacket.WriteUInt32(SpellID);
_worldPacket.WriteBits(Reason, 4);
_worldPacket.FlushBits();
}
}
class ClassTalentsRequestNewConfig : ClientPacket
{
public TraitConfigPacket Config = new();
public ClassTalentsRequestNewConfig(WorldPacket packet) : base(packet) { }
public override void Read()
{
Config.Read(_worldPacket);
}
}
class ClassTalentsRenameConfig : ClientPacket
{
public int ConfigID;
public string Name;
public ClassTalentsRenameConfig(WorldPacket packet) : base(packet) { }
public override void Read()
{
ConfigID = _worldPacket.ReadInt32();
uint nameLength = _worldPacket.ReadBits<uint>(9);
Name = _worldPacket.ReadString(nameLength);
}
}
class ClassTalentsDeleteConfig : ClientPacket
{
public int ConfigID;
public ClassTalentsDeleteConfig(WorldPacket packet) : base(packet) { }
public override void Read()
{
ConfigID = _worldPacket.ReadInt32();
}
}
class ClassTalentsSetStarterBuildActive : ClientPacket
{
public int ConfigID;
public bool Active;
public ClassTalentsSetStarterBuildActive(WorldPacket packet) : base(packet) { }
public override void Read()
{
ConfigID = _worldPacket.ReadInt32();
Active = _worldPacket.HasBit();
}
}
class ClassTalentsSetUsesSharedActionBars : ClientPacket
{
public int ConfigID;
public bool UsesShared;
public bool IsLastSelectedSavedConfig;
public ClassTalentsSetUsesSharedActionBars(WorldPacket packet) : base(packet) { }
public override void Read()
{
ConfigID = _worldPacket.ReadInt32();
UsesShared = _worldPacket.HasBit();
IsLastSelectedSavedConfig = _worldPacket.HasBit();
}
}
public class TraitEntryPacket
{
public int TraitNodeID;
public int TraitNodeEntryID;
public int Rank;
public int GrantedRanks;
public TraitEntryPacket() { }
public TraitEntryPacket(TraitEntry ufEntry)
{
TraitNodeID = ufEntry.TraitNodeID;
TraitNodeEntryID = ufEntry.TraitNodeEntryID;
Rank = ufEntry.Rank;
GrantedRanks = ufEntry.GrantedRanks;
}
public void Read(WorldPacket data)
{
TraitNodeID = data.ReadInt32();
TraitNodeEntryID = data.ReadInt32();
Rank = data.ReadInt32();
GrantedRanks = data.ReadInt32();
}
public void Write(WorldPacket data)
{
data.WriteInt32(TraitNodeID);
data.WriteInt32(TraitNodeEntryID);
data.WriteInt32(Rank);
data.WriteInt32(GrantedRanks);
}
}
public class TraitConfigPacket
{
public int ID;
public TraitConfigType Type;
public int ChrSpecializationID = 0;
public TraitCombatConfigFlags CombatConfigFlags;
public int LocalIdentifier; // Local to specialization
public uint SkillLineID;
public int TraitSystemID;
public List<TraitEntryPacket> Entries = new();
public string Name = "";
public TraitConfigPacket() { }
public TraitConfigPacket(TraitConfig ufConfig)
{
ID = ufConfig.ID;
Type = (TraitConfigType)(int)ufConfig.Type;
ChrSpecializationID = ufConfig.ChrSpecializationID;
CombatConfigFlags = (TraitCombatConfigFlags)(int)ufConfig.CombatConfigFlags;
LocalIdentifier = ufConfig.LocalIdentifier;
SkillLineID = (uint)(int)ufConfig.SkillLineID;
TraitSystemID = ufConfig.TraitSystemID;
foreach (TraitEntry ufEntry in ufConfig.Entries)
Entries.Add(new TraitEntryPacket(ufEntry));
Name = ufConfig.Name;
}
public void Read(WorldPacket data)
{
ID = data.ReadInt32();
Type = (TraitConfigType)data.ReadInt32();
var entriesCount = data.ReadInt32();
switch (Type)
{
case TraitConfigType.Combat:
ChrSpecializationID = data.ReadInt32();
CombatConfigFlags = (TraitCombatConfigFlags)data.ReadInt32();
LocalIdentifier = data.ReadInt32();
break;
case TraitConfigType.Profession:
SkillLineID = data.ReadUInt32();
break;
case TraitConfigType.Generic:
TraitSystemID = data.ReadInt32();
break;
default:
break;
}
for (var i = 0; i < entriesCount; ++i)
{
TraitEntryPacket traitEntry = new();
traitEntry.Read(data);
Entries.Add(traitEntry);
}
uint nameLength = data.ReadBits<uint>(9);
Name = data.ReadString(nameLength);
}
public void Write(WorldPacket data)
{
data.WriteInt32(ID);
data.WriteInt32((int)Type);
data.WriteInt32(Entries.Count);
switch (Type)
{
case TraitConfigType.Combat:
data.WriteInt32(ChrSpecializationID);
data.WriteInt32((int)CombatConfigFlags);
data.WriteInt32(LocalIdentifier);
break;
case TraitConfigType.Profession:
data.WriteUInt32(SkillLineID);
break;
case TraitConfigType.Generic:
data.WriteInt32(TraitSystemID);
break;
default:
break;
}
foreach (TraitEntryPacket traitEntry in Entries)
traitEntry.Write(data);
data.WriteBits(Name.GetByteCount(), 9);
data.FlushBits();
data.WriteString(Name);
}
}
}
@@ -1,84 +0,0 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Game.Entities;
using System.Collections.Generic;
using System;
namespace Game.Networking.Packets
{
public struct TraitEntry
{
public int TraitNodeID;
public int TraitNodeEntryID;
public int Rank;
public int GrantedRanks;
public void Write(WorldPacket data)
{
data.WriteInt32(TraitNodeID);
data.WriteInt32(TraitNodeEntryID);
data.WriteInt32(Rank);
data.WriteInt32(GrantedRanks);
}
}
public class TraitConfig
{
public int ID;
public TraitConfigType Type;
public int ChrSpecializationID = 0;
public TraitCombatConfigFlags CombatConfigFlags;
public int LocalIdentifier; // Local to specialization
public int SkillLineID;
public int TraitSystemID;
public List<TraitEntry> Entries = new();
public string Name = "";
public void Write(WorldPacket data)
{
data.WriteInt32(ID);
data.WriteInt32((int)Type);
data.WriteInt32(Entries.Count);
switch (Type)
{
case TraitConfigType.Combat:
data.WriteInt32(ChrSpecializationID);
data.WriteInt32((int)CombatConfigFlags);
data.WriteInt32(LocalIdentifier);
break;
case TraitConfigType.Profession:
data.WriteInt32(SkillLineID);
break;
case TraitConfigType.Generic:
data.WriteInt32(TraitSystemID);
break;
default:
break;
}
foreach (TraitEntry traitEntry in Entries)
traitEntry.Write(data);
data.WriteBits(Name.GetByteCount(), 9);
data.FlushBits();
data.WriteString(Name);
}
}
}
+7
View File
@@ -3394,7 +3394,14 @@ namespace Game.Spells
Unit.ProcSkillsAndAuras(unitCaster, null, new ProcFlagsInit(ProcFlags.CastEnded), new ProcFlagsInit(), ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, this, null, null);
if (!ok)
{
// on failure (or manual cancel) send TraitConfigCommitFailed to revert talent UI saved config selection
if (m_caster.IsPlayer() && m_spellInfo.HasEffect(SpellEffectName.ChangeActiveCombatTraitConfig))
if (m_customArg is TraitConfig)
m_caster.ToPlayer().SendPacket(new TraitConfigCommitFailed((m_customArg as TraitConfig).ID));
return;
}
if (unitCaster.IsTypeId(TypeId.Unit) && unitCaster.ToCreature().IsSummon())
{
+35
View File
@@ -5676,6 +5676,41 @@ namespace Game.Spells
for (int i = 0; i < damage; ++i)
unitTarget.GetSpellHistory().RestoreCharge((uint)effectInfo.MiscValue);
}
[SpellEffectHandler(SpellEffectName.CreateTraitTreeConfig)]
void EffectCreateTraitTreeConfig()
{
if (effectHandleMode != SpellEffectHandleMode.HitTarget)
return;
Player target = unitTarget?.ToPlayer();
if (target == null)
return;
TraitConfigPacket newConfig = new();
newConfig.Type = TraitMgr.GetConfigTypeForTree(effectInfo.MiscValue);
if (newConfig.Type != TraitConfigType.Generic)
return;
newConfig.TraitSystemID = CliDB.TraitTreeStorage.LookupByKey(effectInfo.MiscValue).TraitSystemID;
target.CreateTraitConfig(newConfig);
}
[SpellEffectHandler(SpellEffectName.ChangeActiveCombatTraitConfig)]
void EffectChangeActiveCombatTraitConfig()
{
if (effectHandleMode != SpellEffectHandleMode.HitTarget)
return;
Player target = unitTarget?.ToPlayer();
if (target == null)
return;
if (m_customArg is not TraitConfigPacket)
return;
target.UpdateTraitConfig(m_customArg as TraitConfigPacket, damage, false);
}
}
public class DispelableAura
+746
View File
@@ -0,0 +1,746 @@
using Framework.Constants;
using Game.DataStorage;
using Game.Entities;
using Game.Networking.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Game
{
class TraitMgr
{
public static uint COMMIT_COMBAT_TRAIT_CONFIG_CHANGES_SPELL_ID = 384255u;
public static uint MAX_COMBAT_TRAIT_CONFIGS = 10u;
static Dictionary<int, NodeGroup> _traitGroups = new();
static Dictionary<int, Node> _traitNodes = new();
static Dictionary<int, Tree> _traitTrees = new();
static int[] _skillLinesByClass = new int[(int)Class.Max];
static MultiMap<int, Tree> _traitTreesBySkillLine = new();
static MultiMap<int, Tree> _traitTreesByTraitSystem = new();
static int _configIdGenerator;
static MultiMap<int, TraitCurrencySourceRecord> _traitCurrencySourcesByCurrency = new();
static MultiMap<int, TraitDefinitionEffectPointsRecord> _traitDefinitionEffectPointModifiers = new();
static MultiMap<int, TraitTreeLoadoutEntryRecord> _traitTreeLoadoutsByChrSpecialization = new();
public static void Load()
{
_configIdGenerator = int.MaxValue;
MultiMap<int, TraitCondRecord> nodeEntryConditions = new();
foreach (TraitNodeEntryXTraitCondRecord traitNodeEntryXTraitCondEntry in CliDB.TraitNodeEntryXTraitCondStorage.Values)
{
TraitCondRecord traitCondEntry = CliDB.TraitCondStorage.LookupByKey(traitNodeEntryXTraitCondEntry.TraitCondID);
if (traitCondEntry != null)
nodeEntryConditions.Add((int)traitNodeEntryXTraitCondEntry.TraitNodeEntryID, traitCondEntry);
}
MultiMap<int, TraitCostRecord> nodeEntryCosts = new();
foreach (TraitNodeEntryXTraitCostRecord traitNodeEntryXTraitCostEntry in CliDB.TraitNodeEntryXTraitCostStorage.Values)
{
TraitCostRecord traitCostEntry = CliDB.TraitCostStorage.LookupByKey(traitNodeEntryXTraitCostEntry.TraitCostID);
if (traitCostEntry != null)
nodeEntryCosts.Add(traitNodeEntryXTraitCostEntry.TraitNodeEntryID, traitCostEntry);
}
MultiMap<int, TraitCondRecord> nodeGroupConditions = new();
foreach (TraitNodeGroupXTraitCondRecord traitNodeGroupXTraitCondEntry in CliDB.TraitNodeGroupXTraitCondStorage.Values)
{
TraitCondRecord traitCondEntry = CliDB.TraitCondStorage.LookupByKey(traitNodeGroupXTraitCondEntry.TraitCondID);
if (traitCondEntry != null)
nodeGroupConditions.Add(traitNodeGroupXTraitCondEntry.TraitNodeGroupID, traitCondEntry);
}
MultiMap<int, TraitCostRecord> nodeGroupCosts = new();
foreach (TraitNodeGroupXTraitCostRecord traitNodeGroupXTraitCostEntry in CliDB.TraitNodeGroupXTraitCostStorage.Values)
{
TraitCostRecord traitCondEntry = CliDB.TraitCostStorage.LookupByKey(traitNodeGroupXTraitCostEntry.TraitCostID);
if (traitCondEntry != null)
nodeGroupCosts.Add(traitNodeGroupXTraitCostEntry.TraitNodeGroupID, traitCondEntry);
}
MultiMap<int, int> nodeGroups = new();
foreach (TraitNodeGroupXTraitNodeRecord traitNodeGroupXTraitNodeEntry in CliDB.TraitNodeGroupXTraitNodeStorage.Values)
nodeGroups.Add(traitNodeGroupXTraitNodeEntry.TraitNodeID, traitNodeGroupXTraitNodeEntry.TraitNodeGroupID);
MultiMap<int, TraitCondRecord> nodeConditions = new();
foreach (TraitNodeXTraitCondRecord traitNodeXTraitCondEntry in CliDB.TraitNodeXTraitCondStorage.Values)
{
TraitCondRecord traitCondEntry = CliDB.TraitCondStorage.LookupByKey(traitNodeXTraitCondEntry.TraitCondID);
if (traitCondEntry != null)
nodeConditions.Add(traitNodeXTraitCondEntry.TraitNodeID, traitCondEntry);
}
MultiMap<uint, TraitCostRecord> nodeCosts = new();
foreach (TraitNodeXTraitCostRecord traitNodeXTraitCostEntry in CliDB.TraitNodeXTraitCostStorage.Values)
{
TraitCostRecord traitCostEntry = CliDB.TraitCostStorage.LookupByKey(traitNodeXTraitCostEntry.TraitCostID);
if (traitCostEntry != null)
nodeCosts.Add(traitNodeXTraitCostEntry.TraitNodeID, traitCostEntry);
}
MultiMap<int, TraitNodeEntryRecord> nodeEntries = new();
foreach (TraitNodeXTraitNodeEntryRecord traitNodeXTraitNodeEntryEntry in CliDB.TraitNodeXTraitNodeEntryStorage.Values)
{
TraitNodeEntryRecord traitNodeEntryEntry = CliDB.TraitNodeEntryStorage.LookupByKey(traitNodeXTraitNodeEntryEntry.TraitNodeEntryID);
if (traitNodeEntryEntry != null)
nodeEntries.Add(traitNodeXTraitNodeEntryEntry.TraitNodeID, traitNodeEntryEntry);
}
MultiMap<uint, TraitCostRecord> treeCosts = new();
foreach (TraitTreeXTraitCostRecord traitTreeXTraitCostEntry in CliDB.TraitTreeXTraitCostStorage.Values)
{
TraitCostRecord traitCostEntry = CliDB.TraitCostStorage.LookupByKey(traitTreeXTraitCostEntry.TraitCostID);
if (traitCostEntry != null)
treeCosts.Add(traitTreeXTraitCostEntry.TraitTreeID, traitCostEntry);
}
MultiMap<int, TraitCurrencyRecord> treeCurrencies = new();
foreach (TraitTreeXTraitCurrencyRecord traitTreeXTraitCurrencyEntry in CliDB.TraitTreeXTraitCurrencyStorage.Values)
{
TraitCurrencyRecord traitCurrencyEntry = CliDB.TraitCurrencyStorage.LookupByKey(traitTreeXTraitCurrencyEntry.TraitCurrencyID);
if (traitCurrencyEntry != null)
treeCurrencies.Add(traitTreeXTraitCurrencyEntry.TraitTreeID, traitCurrencyEntry);
}
MultiMap<int, int> traitTreesIdsByTraitSystem = new();
foreach (TraitTreeRecord traitTree in CliDB.TraitTreeStorage.Values)
{
Tree tree = _traitTrees[(int)traitTree.Id];
tree.Data = traitTree;
var costs = treeCosts.LookupByKey(traitTree.Id);
if (costs != null)
tree.Costs = costs;
var currencies = treeCurrencies.LookupByKey(traitTree.Id);
if (currencies != null)
tree.Currencies = currencies;
if (traitTree.TraitSystemID != 0)
{
traitTreesIdsByTraitSystem.Add(traitTree.TraitSystemID, (int)traitTree.Id);
tree.ConfigType = TraitConfigType.Generic;
}
}
foreach (TraitNodeGroupRecord traitNodeGroup in CliDB.TraitNodeGroupStorage.Values)
{
NodeGroup nodeGroup = _traitGroups[(int)traitNodeGroup.Id];
nodeGroup.Data = traitNodeGroup;
var conditions = nodeGroupConditions.LookupByKey(traitNodeGroup.Id);
if (conditions != null)
nodeGroup.Conditions = conditions;
var costs = nodeGroupCosts.LookupByKey(traitNodeGroup.Id);
if (costs != null)
nodeGroup.Costs = costs;
}
foreach (TraitNodeRecord traitNode in CliDB.TraitNodeStorage.Values)
{
Node node = _traitNodes[(int)traitNode.Id];
node.Data = traitNode;
Tree tree = _traitTrees.LookupByKey(traitNode.TraitTreeID);
if (tree != null)
tree.Nodes.Add(node);
foreach (var traitNodeEntry in nodeEntries.LookupByKey(traitNode.Id))
{
NodeEntry entry = new();
entry.Data = traitNodeEntry;
var conditions = nodeEntryConditions.LookupByKey(traitNodeEntry.Id);
if (conditions != null)
entry.Conditions = conditions;
var costs = nodeEntryCosts.LookupByKey(traitNodeEntry.Id);
if (costs != null)
entry.Costs = costs;
node.Entries.Add(entry);
}
foreach (var nodeGroupId in nodeGroups.LookupByKey(traitNode.Id))
{
NodeGroup nodeGroup = _traitGroups.LookupByKey(nodeGroupId);
if (nodeGroup == null)
continue;
nodeGroup.Nodes.Add(node);
node.Groups.Add(nodeGroup);
}
var conditions1 = nodeConditions.LookupByKey(traitNode.Id);
if (conditions1 != null)
node.Conditions = conditions1;
var costs1 = nodeCosts.LookupByKey(traitNode.Id);
if (costs1 != null)
node.Costs = costs1;
}
foreach (TraitEdgeRecord traitEdgeEntry in CliDB.TraitEdgeStorage.Values)
{
Node left = _traitNodes.LookupByKey(traitEdgeEntry.LeftTraitNodeID);
Node right = _traitNodes.LookupByKey(traitEdgeEntry.RightTraitNodeID);
if (left == null || right == null)
continue;
right.ParentNodes.Add(Tuple.Create(left, (TraitEdgeType)traitEdgeEntry.Type));
}
foreach (SkillLineXTraitTreeRecord skillLineXTraitTreeEntry in CliDB.SkillLineXTraitTreeStorage.Values)
{
Tree tree = _traitTrees.LookupByKey(skillLineXTraitTreeEntry.TraitTreeID);
if (tree == null)
continue;
SkillLineRecord skillLineEntry = CliDB.SkillLineStorage.LookupByKey(skillLineXTraitTreeEntry.SkillLineID);
if (skillLineEntry == null)
continue;
_traitTreesBySkillLine[skillLineXTraitTreeEntry.SkillLineID].Add(tree);
if (skillLineEntry.CategoryID == SkillCategory.Class)
{
foreach (SkillRaceClassInfoRecord skillRaceClassInfo in Global.DB2Mgr.GetSkillRaceClassInfo(skillLineEntry.Id))
for (int i = 1; i < (int)Class.Max; ++i)
if ((skillRaceClassInfo.ClassMask & (1 << (i - 1))) != 0)
_skillLinesByClass[i] = skillLineXTraitTreeEntry.SkillLineID;
tree.ConfigType = TraitConfigType.Combat;
}
else
tree.ConfigType = TraitConfigType.Profession;
}
foreach (var (traitSystemId, traitTreeId) in traitTreesIdsByTraitSystem)
{
Tree tree = _traitTrees.LookupByKey(traitTreeId);
if (tree != null)
_traitTreesByTraitSystem[traitSystemId].Add(tree);
}
foreach (TraitCurrencySourceRecord traitCurrencySource in CliDB.TraitCurrencySourceStorage.Values)
_traitCurrencySourcesByCurrency[traitCurrencySource.TraitCurrencyID].Add(traitCurrencySource);
foreach (TraitDefinitionEffectPointsRecord traitDefinitionEffectPoints in CliDB.TraitDefinitionEffectPointsStorage.Values)
_traitDefinitionEffectPointModifiers[traitDefinitionEffectPoints.TraitDefinitionID].Add(traitDefinitionEffectPoints);
MultiMap<int, TraitTreeLoadoutEntryRecord> traitTreeLoadoutEntries = new();
foreach (TraitTreeLoadoutEntryRecord traitTreeLoadoutEntry in CliDB.TraitTreeLoadoutEntryStorage.Values)
traitTreeLoadoutEntries[traitTreeLoadoutEntry.TraitTreeLoadoutID].Add(traitTreeLoadoutEntry);
foreach (TraitTreeLoadoutRecord traitTreeLoadout in CliDB.TraitTreeLoadoutStorage.Values)
{
var entries = traitTreeLoadoutEntries.LookupByKey(traitTreeLoadout.Id);
if (entries != null)
{
entries.Sort((left, right) => { return left.OrderIndex.CompareTo(right.OrderIndex); });
// there should be only one loadout per spec, we take last one encountered
_traitTreeLoadoutsByChrSpecialization[traitTreeLoadout.ChrSpecializationID] = entries;
}
}
}
/**
* Generates new TraitConfig identifier.
* Because this only needs to be unique for each character we let it overflow
*/
public static int GenerateNewTraitConfigId()
{
if (_configIdGenerator == int.MaxValue)
_configIdGenerator = 0;
return ++_configIdGenerator;
}
public static TraitConfigType GetConfigTypeForTree(int traitTreeId)
{
Tree tree = _traitTrees.LookupByKey(traitTreeId);
if (tree == null)
return TraitConfigType.Invalid;
return tree.ConfigType;
}
/**
* @brief Finds relevant TraitTree identifiers
* @param traitConfig config data
* @return Trait tree data
*/
public static List<Tree> GetTreesForConfig(TraitConfigPacket traitConfig)
{
switch (traitConfig.Type)
{
case TraitConfigType.Combat:
ChrSpecializationRecord chrSpecializationEntry = CliDB.ChrSpecializationStorage.LookupByKey(traitConfig.ChrSpecializationID);
if (chrSpecializationEntry != null)
return _traitTreesBySkillLine.LookupByKey(_skillLinesByClass[chrSpecializationEntry.ClassID]);
break;
case TraitConfigType.Profession:
return _traitTreesBySkillLine.LookupByKey(traitConfig.SkillLineID);
case TraitConfigType.Generic:
return _traitTreesByTraitSystem.LookupByKey(traitConfig.TraitSystemID);
default:
break;
}
return null;
}
public static bool HasEnoughCurrency(TraitEntryPacket entry, Dictionary<int, int> currencies)
{
int getCurrencyCount(int currencyId)
{
return currencies.LookupByKey(currencyId);
}
Node node = _traitNodes.LookupByKey(entry.TraitNodeID);
foreach (NodeGroup group in node.Groups)
foreach (TraitCostRecord cost in group.Costs)
if (getCurrencyCount(cost.TraitCurrencyID) < cost.Amount * entry.Rank)
return false;
var nodeEntryItr = node.Entries.Find(nodeEntry => nodeEntry.Data.Id == entry.TraitNodeEntryID);
if (nodeEntryItr != null)
foreach (TraitCostRecord cost in nodeEntryItr.Costs)
if (getCurrencyCount(cost.TraitCurrencyID) < cost.Amount * entry.Rank)
return false;
foreach (TraitCostRecord cost in node.Costs)
if (getCurrencyCount(cost.TraitCurrencyID) < cost.Amount * entry.Rank)
return false;
Tree tree = _traitTrees.LookupByKey(node.Data.TraitTreeID);
if (tree != null)
foreach (TraitCostRecord cost in tree.Costs)
if (getCurrencyCount(cost.TraitCurrencyID) < cost.Amount * entry.Rank)
return false;
return true;
}
public static void TakeCurrencyCost(TraitEntryPacket entry, Dictionary<int, int> currencies)
{
Node node = _traitNodes.LookupByKey(entry.TraitNodeID);
foreach (NodeGroup group in node.Groups)
foreach (TraitCostRecord cost in group.Costs)
currencies[cost.TraitCurrencyID] -= cost.Amount * entry.Rank;
var nodeEntryItr = node.Entries.Find(nodeEntry => nodeEntry.Data.Id == entry.TraitNodeEntryID);
if (nodeEntryItr != null)
foreach (TraitCostRecord cost in nodeEntryItr.Costs)
currencies[cost.TraitCurrencyID] -= cost.Amount * entry.Rank;
foreach (TraitCostRecord cost in node.Costs)
currencies[cost.TraitCurrencyID] -= cost.Amount * entry.Rank;
Tree tree = _traitTrees.LookupByKey(node.Data.TraitTreeID);
if (tree != null)
foreach (TraitCostRecord cost in tree.Costs)
currencies[cost.TraitCurrencyID] -= cost.Amount * entry.Rank;
}
public static void FillOwnedCurrenciesMap(TraitConfigPacket traitConfig, Player player, Dictionary<int, int> currencies)
{
List<Tree> trees = GetTreesForConfig(traitConfig);
if (trees == null)
return;
bool hasTraitNodeEntry(int traitNodeEntryId)
{
return traitConfig.Entries.Any(traitEntry => traitEntry.TraitNodeEntryID == traitNodeEntryId && (traitEntry.Rank > 0 || traitEntry.GrantedRanks > 0));
}
foreach (Tree tree in trees)
{
foreach (TraitCurrencyRecord currency in tree.Currencies)
{
switch (currency.GetCurrencyType())
{
case TraitCurrencyType.Gold:
{
int amount = currencies[(int)currency.Id];
if (player.GetMoney() > (ulong)(int.MaxValue - amount))
amount = int.MaxValue;
else
amount += (int)player.GetMoney();
break;
}
case TraitCurrencyType.CurrencyTypesBased:
currencies[(int)currency.Id] += (int)player.GetCurrency((uint)currency.CurrencyTypesID);
break;
case TraitCurrencyType.TraitSourced:
var currencySources = _traitCurrencySourcesByCurrency.LookupByKey(currency.Id);
if (currencySources != null)
{
foreach (TraitCurrencySourceRecord currencySource in currencySources)
{
if (currencySource.QuestID != 0 && !player.IsQuestRewarded(currencySource.QuestID))
continue;
if (currencySource.AchievementID != 0 && !player.HasAchieved(currencySource.AchievementID))
continue;
if (currencySource.PlayerLevel != 0 && player.GetLevel() < currencySource.PlayerLevel)
continue;
if (currencySource.TraitNodeEntryID != 0 && !hasTraitNodeEntry(currencySource.TraitNodeEntryID))
continue;
currencies[currencySource.TraitCurrencyID] += currencySource.Amount;
}
}
break;
default:
break;
}
}
}
}
public static void FillSpentCurrenciesMap(TraitEntryPacket entry, Dictionary<int, int> cachedCurrencies)
{
Node node = _traitNodes.LookupByKey(entry.TraitNodeID);
foreach (NodeGroup group in node.Groups)
foreach (TraitCostRecord cost in group.Costs)
cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank;
var nodeEntryItr = node.Entries.Find(nodeEntry => nodeEntry.Data.Id == entry.TraitNodeEntryID);
if (nodeEntryItr != null)
foreach (TraitCostRecord cost in nodeEntryItr.Costs)
cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank;
foreach (TraitCostRecord cost in node.Costs)
cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank;
Tree tree = _traitTrees.LookupByKey(node.Data.TraitTreeID);
if (tree != null)
foreach (TraitCostRecord cost in tree.Costs)
cachedCurrencies[cost.TraitCurrencyID] += cost.Amount * entry.Rank;
}
public static void FillSpentCurrenciesMap(TraitConfigPacket traitConfig, Dictionary<int, int> cachedCurrencies)
{
foreach (TraitEntryPacket entry in traitConfig.Entries)
FillSpentCurrenciesMap(entry, cachedCurrencies);
}
public static bool MeetsTraitCondition(TraitConfigPacket traitConfig, Player player, TraitCondRecord condition, ref Dictionary<int, int> cachedCurrencies)
{
if (condition.QuestID != 0 && !player.IsQuestRewarded(condition.QuestID))
return false;
if (condition.AchievementID != 0 && !player.HasAchieved(condition.AchievementID))
return false;
if (condition.SpecSetID != 0 && !Global.DB2Mgr.IsSpecSetMember(condition.SpecSetID, player.GetPrimarySpecialization()))
return false;
if (condition.TraitCurrencyID != 0 && condition.SpentAmountRequired != 0)
{
if (cachedCurrencies == null)
{
cachedCurrencies = new();
FillSpentCurrenciesMap(traitConfig, cachedCurrencies);
}
if (condition.TraitNodeGroupID != 0)
{
cachedCurrencies.TryAdd(condition.TraitCurrencyID, 0);
if (cachedCurrencies[condition.TraitCurrencyID] < condition.SpentAmountRequired)
return false;
}
else if (condition.TraitNodeID != 0)
{
cachedCurrencies.TryAdd(condition.TraitCurrencyID, 0);
if (cachedCurrencies[condition.TraitCurrencyID] < condition.SpentAmountRequired)
return false;
}
}
if (condition.RequiredLevel != 0 && player.GetLevel() < condition.RequiredLevel)
return false;
return true;
}
public static List<TraitEntry> GetGrantedTraitEntriesForConfig(TraitConfigPacket traitConfig, Player player)
{
List<TraitEntry> entries = new();
var trees = GetTreesForConfig(traitConfig);
if (trees == null)
return entries;
TraitEntry getOrCreateEntry(uint nodeId, uint entryId)
{
TraitEntry foundTraitEntry = entries.Find(traitEntry => traitEntry.TraitNodeID == nodeId && traitEntry.TraitNodeEntryID == entryId);
if (foundTraitEntry == null)
{
foundTraitEntry = new();
foundTraitEntry.TraitNodeID = (int)nodeId;
foundTraitEntry.TraitNodeEntryID = (int)entryId;
foundTraitEntry.Rank = 0;
foundTraitEntry.GrantedRanks = 0;
entries.Add(foundTraitEntry);
}
return foundTraitEntry;
}
Dictionary<int, int> cachedCurrencies = null;
foreach (Tree tree in trees)
{
foreach (Node node in tree.Nodes)
{
foreach (NodeEntry entry in node.Entries)
foreach (TraitCondRecord condition in entry.Conditions)
if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies))
getOrCreateEntry(node.Data.Id, entry.Data.Id).GrantedRanks += condition.GrantedRanks;
foreach (TraitCondRecord condition in node.Conditions)
if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies))
foreach (NodeEntry entry in node.Entries)
getOrCreateEntry(node.Data.Id, entry.Data.Id).GrantedRanks += condition.GrantedRanks;
foreach (NodeGroup group in node.Groups)
foreach (TraitCondRecord condition in group.Conditions)
if (condition.GetCondType() == TraitConditionType.Granted && MeetsTraitCondition(traitConfig, player, condition, ref cachedCurrencies))
foreach (NodeEntry entry in node.Entries)
getOrCreateEntry(node.Data.Id, entry.Data.Id).GrantedRanks += condition.GrantedRanks;
}
}
return entries;
}
public static bool IsValidEntry(TraitEntryPacket traitEntry)
{
Node node = _traitNodes.LookupByKey(traitEntry.TraitNodeID);
if (node == null)
return false;
var entryItr = node.Entries.Find(entry => entry.Data.Id == traitEntry.TraitNodeEntryID);
if (entryItr == null)
return false;
if (entryItr.Data.MaxRanks < traitEntry.Rank + traitEntry.GrantedRanks)
return false;
return true;
}
public static TalentLearnResult ValidateConfig(TraitConfigPacket traitConfig, Player player, bool requireSpendingAllCurrencies = false)
{
int getNodeEntryCount(int traitNodeId)
{
return traitConfig.Entries.Count(traitEntry => traitEntry.TraitNodeID == traitNodeId);
}
TraitEntryPacket getNodeEntry(uint traitNodeId, uint traitNodeEntryId)
{
return traitConfig.Entries.Find(traitEntry => traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId);
}
bool isNodeFullyFilled(Node node)
{
if (node.Data.GetNodeType() == TraitNodeType.Selection)
return node.Entries.Any(nodeEntry =>
{
TraitEntryPacket traitEntry = getNodeEntry(node.Data.Id, nodeEntry.Data.Id);
return traitEntry != null && (traitEntry.Rank + traitEntry.GrantedRanks) == nodeEntry.Data.MaxRanks;
});
return node.Entries.All(nodeEntry =>
{
TraitEntryPacket traitEntry = getNodeEntry(node.Data.Id, nodeEntry.Data.Id);
return traitEntry != null && (traitEntry.Rank + traitEntry.GrantedRanks) == nodeEntry.Data.MaxRanks;
});
};
Dictionary<int, int> spentCurrencies = new();
FillSpentCurrenciesMap(traitConfig, spentCurrencies);
foreach (TraitEntryPacket traitEntry in traitConfig.Entries)
{
if (!IsValidEntry(traitEntry))
return TalentLearnResult.FailedUnknown;
Node node = _traitNodes.LookupByKey(traitEntry.TraitNodeID);
if (node.Data.GetNodeType() == TraitNodeType.Selection)
if (getNodeEntryCount(traitEntry.TraitNodeID) != 1)
return TalentLearnResult.FailedUnknown;
foreach (NodeEntry entry in node.Entries)
foreach (TraitCondRecord condition in entry.Conditions)
if ((condition.GetCondType() == TraitConditionType.Available || condition.GetCondType() == TraitConditionType.Visible)
&& !MeetsTraitCondition(traitConfig, player, condition, ref spentCurrencies))
return TalentLearnResult.FailedUnknown;
foreach (TraitCondRecord condition in node.Conditions)
if ((condition.GetCondType() == TraitConditionType.Available || condition.GetCondType() == TraitConditionType.Visible)
&& !MeetsTraitCondition(traitConfig, player, condition, ref spentCurrencies))
return TalentLearnResult.FailedUnknown;
foreach (NodeGroup group in node.Groups)
foreach (TraitCondRecord condition in group.Conditions)
if ((condition.GetCondType() == TraitConditionType.Available || condition.GetCondType() == TraitConditionType.Visible)
&& !MeetsTraitCondition(traitConfig, player, condition, ref spentCurrencies))
return TalentLearnResult.FailedUnknown;
if (!node.ParentNodes.Empty())
{
bool hasAnyParentTrait = false;
foreach (var (parentNode, edgeType) in node.ParentNodes)
{
if (!isNodeFullyFilled(parentNode))
{
if (edgeType == TraitEdgeType.RequiredForAvailability)
return TalentLearnResult.FailedNotEnoughTalentsInPrimaryTree;
continue;
}
hasAnyParentTrait = true;
}
if (!hasAnyParentTrait)
return TalentLearnResult.FailedNotEnoughTalentsInPrimaryTree;
}
}
Dictionary<int, int> grantedCurrencies = new();
FillOwnedCurrenciesMap(traitConfig, player, grantedCurrencies);
foreach (var (traitCurrencyId, spentAmount) in spentCurrencies)
{
if (CliDB.TraitCurrencyStorage.LookupByKey(traitCurrencyId).GetCurrencyType() != TraitCurrencyType.TraitSourced)
continue;
if (spentAmount == 0)
continue;
int grantedCount = grantedCurrencies.LookupByKey(traitCurrencyId);
if (grantedCount == 0 || grantedCount < spentAmount)
return TalentLearnResult.FailedNotEnoughTalentsInPrimaryTree;
}
if (requireSpendingAllCurrencies && traitConfig.Type == TraitConfigType.Combat)
{
foreach (var (traitCurrencyId, grantedAmount) in grantedCurrencies)
{
if (grantedAmount == 0)
continue;
int spentAmount = spentCurrencies.LookupByKey(traitCurrencyId);
if (spentAmount == 0 || spentAmount != grantedAmount)
return TalentLearnResult.UnspentTalentPoints;
}
}
return TalentLearnResult.LearnOk;
}
public static List<TraitDefinitionEffectPointsRecord> GetTraitDefinitionEffectPointModifiers(int traitDefinitionId)
{
return _traitDefinitionEffectPointModifiers.LookupByKey(traitDefinitionId);
}
public static void InitializeStarterBuildTraitConfig(TraitConfigPacket traitConfig, Player player)
{
traitConfig.Entries.Clear();
var trees = GetTreesForConfig(traitConfig);
if (trees == null)
return;
foreach (TraitEntry grant in GetGrantedTraitEntriesForConfig(traitConfig, player))
{
TraitEntryPacket newEntry = new();
newEntry.TraitNodeID = grant.TraitNodeID;
newEntry.TraitNodeEntryID = grant.TraitNodeEntryID;
newEntry.GrantedRanks = grant.GrantedRanks;
traitConfig.Entries.Add(newEntry);
}
Dictionary<int, int> currencies = new();
FillOwnedCurrenciesMap(traitConfig, player, currencies);
var loadoutEntries = _traitTreeLoadoutsByChrSpecialization.LookupByKey(traitConfig.ChrSpecializationID);
if (loadoutEntries != null)
{
TraitEntryPacket findEntry(TraitConfigPacket config, int traitNodeId, int traitNodeEntryId)
{
return config.Entries.Find(traitEntry => traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId);
}
foreach (TraitTreeLoadoutEntryRecord loadoutEntry in loadoutEntries)
{
int addedRanks = loadoutEntry.NumPoints;
Node node = _traitNodes.LookupByKey(loadoutEntry.SelectedTraitNodeID);
TraitEntryPacket newEntry = new();
newEntry.TraitNodeID = loadoutEntry.SelectedTraitNodeID;
newEntry.TraitNodeEntryID = loadoutEntry.SelectedTraitNodeEntryID;
if (newEntry.TraitNodeEntryID == 0)
newEntry.TraitNodeEntryID = (int)node.Entries[0].Data.Id;
TraitEntryPacket entryInConfig = findEntry(traitConfig, newEntry.TraitNodeID, newEntry.TraitNodeEntryID);
if (entryInConfig != null)
addedRanks -= entryInConfig.Rank;
newEntry.Rank = addedRanks;
if (!HasEnoughCurrency(newEntry, currencies))
break;
if (entryInConfig != null)
entryInConfig.Rank += addedRanks;
else
traitConfig.Entries.Add(newEntry);
TakeCurrencyCost(newEntry, currencies);
}
}
}
}
class NodeEntry
{
public TraitNodeEntryRecord Data;
public List<TraitCondRecord> Conditions = new();
public List<TraitCostRecord> Costs = new();
}
class Node
{
public TraitNodeRecord Data;
public List<NodeEntry> Entries = new();
public List<NodeGroup> Groups = new();
public List<Tuple<Node, TraitEdgeType>> ParentNodes = new(); // TraitEdge::LeftTraitNodeID
public List<TraitCondRecord> Conditions = new();
public List<TraitCostRecord> Costs = new();
}
class NodeGroup
{
public TraitNodeGroupRecord Data;
public List<TraitCondRecord> Conditions = new();
public List<TraitCostRecord> Costs = new();
public List<Node> Nodes = new();
}
class Tree
{
public TraitTreeRecord Data;
public List<Node> Nodes = new();
public List<TraitCostRecord> Costs = new();
public List<TraitCurrencyRecord> Currencies = new();
public TraitConfigType ConfigType;
}
}