Core/Players: Implemented new talent system
Port From (https://github.com/TrinityCore/TrinityCore/commit/0cc5ab8372f19dad7412038d52dcd39db5e0e171)
This commit is contained in:
@@ -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
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user