diff --git a/Source/Framework/Constants/AchievementConst.cs b/Source/Framework/Constants/AchievementConst.cs index 560a43f4c..46720b948 100644 --- a/Source/Framework/Constants/AchievementConst.cs +++ b/Source/Framework/Constants/AchievementConst.cs @@ -450,8 +450,8 @@ namespace Framework.Constants PlayerWeaponHighWatermarkAboveOrEqual = 375, /*NYI*/ PlayerHeadHighWatermarkAboveOrEqual = 376, /*NYI*/ PlayerHasDisplayedCurrencyLessThan = 377, /*NYI*/ // Player has {CurrencyTypes} less than {#Amount} (value visible in ui is taken into account, not raw value) - PlayerDataFlagAccountIsSet = 378, /*NYI*/ // Player {PlayerDataFlagAccount} is set - PlayerDataFlagCharacterIsSet = 379, /*NYI*/ // Player {PlayerDataFlagCharacter} is set + PlayerDataFlagAccountIsSet = 378, // Player {PlayerDataFlagAccount} is set + PlayerDataFlagCharacterIsSet = 379, // Player {PlayerDataFlagCharacter} is set PlayerIsOnMapWithExpansion = 380, // Player is on map that has {ExpansionID} PlayerHasCompletedQuestOnAccount = 382, /*NYI*/ // Player has previously completed quest "{QuestV2}" on account @@ -462,12 +462,16 @@ namespace Framework.Constants PlayerIsInSoloRBG = 387, /*NYI*/ // Player is in solo RBG (BG Blitz) PlayerHasCompletedCampaign = 388, /*NYI*/ // Player has completed campaign "{Campaign}" TargetCreatureClassificationEqual = 389, // Creature classification is {CreatureClassification} - PlayerDataElementCharacterEqual = 390, /*NYI*/ // Player {PlayerDataElementCharacter} is greater than {#Amount} - PlayerDataElementAccountEqual = 391, /*NYI*/ // Player {PlayerDataElementAccount} is greater than {#Amount} + PlayerDataElementCharacterBetween = 390, // Player {PlayerDataElementCharacter} is between {#Amount} and {#Amount2} + PlayerDataElementAccountBetween = 391, // Player {PlayerDataElementAccount} is between {#Amount} and {#Amount2} PlayerHasCompletedQuestOrIsReadyToTurnIn = 392, // Player has previously completed quest "{QuestV2}" or is ready to turn it in PlayerTitle = 393, // Player is currently using "{ChrTitles}" title + PlayerWeeklyCurrencyIsRelOpFromMax = 397, /*NYI*/ // Player weekly {CurrencyTypes} is {RelOp} {#Amount} from currency weekly limit + PlayerIsInGuild = 404, // Player is in a guild + + PlayerAvgItemLevelRelOp = 415, /*NYI*/ // Player average item level {AvgItemLevelCategory} is {RelOp} {#Amount} } public enum CriteriaFailEvent : byte diff --git a/Source/Framework/Constants/CliDBConst.cs b/Source/Framework/Constants/CliDBConst.cs index 935cc3549..9588b9a30 100644 --- a/Source/Framework/Constants/CliDBConst.cs +++ b/Source/Framework/Constants/CliDBConst.cs @@ -1969,6 +1969,12 @@ namespace Framework.Constants GearDiff = 8 } + public enum PlayerDataElementType + { + Int64 = 0, + Float = 1 + } + public enum PlayerInteractionType { None = 0, diff --git a/Source/Framework/Constants/PlayerConst.cs b/Source/Framework/Constants/PlayerConst.cs index 016337893..77e0657d5 100644 --- a/Source/Framework/Constants/PlayerConst.cs +++ b/Source/Framework/Constants/PlayerConst.cs @@ -22,6 +22,7 @@ namespace Framework.Constants public const int ExploredZonesSize = 240; public const int ExploredZonesBits = sizeof(ulong) * 8; + public const int DataFlagValueBits = sizeof(ulong) * 8; public const ulong MaxMoneyAmount = 99999999999UL; public const int MaxActionButtons = 180; diff --git a/Source/Framework/Constants/Spells/SpellConst.cs b/Source/Framework/Constants/Spells/SpellConst.cs index c1637d228..364419ad0 100644 --- a/Source/Framework/Constants/Spells/SpellConst.cs +++ b/Source/Framework/Constants/Spells/SpellConst.cs @@ -2620,7 +2620,7 @@ namespace Framework.Constants CraftItem = 288, // Miscvalue[0] = Craftingdataid ModifyAuraStacks = 289, // Miscvalue[0] = 0 Means Add, = 1 Means Set ModifyCooldown = 290, - ModifyCooldowns = 291, // Miscvalue[0] = Spellfamily, Miscvalue[1] = Maybe Bit Index For Family Flags? Off By 1 For The Only Spell Using This Effect + ModifyCooldowns = 291, // Miscvalue[0] = Spellfamily, Miscvalue[1] = bit index for family flags ModifyCooldownsByCategory = 292, // Miscvalue[0] = Category ModifyCharges = 293, // Miscvalue[0] = Charge Category CraftLoot = 294, // Miscvalue[0] = Craftingdataid @@ -2664,10 +2664,10 @@ namespace Framework.Constants Unk332 = 332, Unk333 = 333, Unk334 = 334, - Unk335 = 335, - Unk336 = 336, - Unk337 = 337, - Unk338 = 338, + SetPlayerDataElementAccount = 335, // MiscValue[0] = PlayerDataElementAccount + SetPlayerDataElementCharacter = 336, // MiscValue[0] = PlayerDataElementCharacter + SetPlayerDataFlagAccount = 337, // MiscValue[0] = PlayerDataFlagAccount + SetPlayerDataFlagCharacter = 338, // MiscValue[0] = PlayerDataFlagCharacter UiAction = 339, Unk340 = 340, LearnWarbanScene = 341, diff --git a/Source/Framework/Database/Databases/CharacterDatabase.cs b/Source/Framework/Database/Databases/CharacterDatabase.cs index fb30ec1a8..059bacf5c 100644 --- a/Source/Framework/Database/Databases/CharacterDatabase.cs +++ b/Source/Framework/Database/Databases/CharacterDatabase.cs @@ -767,6 +767,13 @@ namespace Framework.Database PrepareStatement(CharStatements.UPD_CHARACTER_INSTANCE_LOCK_FORCE_EXPIRE, "UPDATE character_instance_lock SET expiryTime = ?, extended = 0 WHERE guid = ? AND mapId = ? AND lockId = ?"); PrepareStatement(CharStatements.DEL_INSTANCE, "DELETE FROM instance WHERE instanceId = ?"); PrepareStatement(CharStatements.INS_INSTANCE, "INSERT INTO instance (instanceId, data, completedEncountersMask, entranceWorldSafeLocId) VALUES (?, ?, ?, ?)"); + + PrepareStatement(CharStatements.SEL_PLAYER_DATA_ELEMENTS_CHARACTER, "SELECT playerDataElementCharacterId, floatValue, int64Value FROM character_player_data_element WHERE characterGuid = ?"); + PrepareStatement(CharStatements.DEL_PLAYER_DATA_ELEMENTS_CHARACTER, "DELETE FROM character_player_data_element WHERE characterGuid = ? AND playerDataElementCharacterId = ?"); + PrepareStatement(CharStatements.INS_PLAYER_DATA_ELEMENTS_CHARACTER, "INSERT INTO character_player_data_element (characterGuid, playerDataElementCharacterId, floatValue, int64Value) VALUES (?, ?, ?, ?)"); + PrepareStatement(CharStatements.SEL_PLAYER_DATA_FLAGS_CHARACTER, "SELECT storageIndex, mask FROM character_player_data_flag WHERE characterGuid = ?"); + PrepareStatement(CharStatements.DEL_PLAYER_DATA_FLAGS_CHARACTER, "DELETE FROM character_player_data_flag WHERE characterGuid = ? AND storageIndex = ?"); + PrepareStatement(CharStatements.INS_PLAYER_DATA_FLAGS_CHARACTER, "INSERT INTO character_player_data_flag (characterGuid, storageIndex, mask) VALUES (?, ?, ?)"); } } @@ -1395,6 +1402,13 @@ namespace Framework.Database DEL_INSTANCE, INS_INSTANCE, + SEL_PLAYER_DATA_ELEMENTS_CHARACTER, + DEL_PLAYER_DATA_ELEMENTS_CHARACTER, + INS_PLAYER_DATA_ELEMENTS_CHARACTER, + SEL_PLAYER_DATA_FLAGS_CHARACTER, + DEL_PLAYER_DATA_FLAGS_CHARACTER, + INS_PLAYER_DATA_FLAGS_CHARACTER, + MAX_CHARACTERDATABASE_STATEMENTS } } diff --git a/Source/Framework/Database/Databases/HotfixDatabase.cs b/Source/Framework/Database/Databases/HotfixDatabase.cs index c6d0d1793..0961a0fa7 100644 --- a/Source/Framework/Database/Databases/HotfixDatabase.cs +++ b/Source/Framework/Database/Databases/HotfixDatabase.cs @@ -1052,6 +1052,22 @@ namespace Framework.Database PrepareStatement(HotfixStatements.SEL_PLAYER_CONDITION_LOCALE, "SELECT ID, FailureDescription_lang FROM player_condition_locale WHERE (`VerifiedBuild` > 0) = ?" + " AND locale = ?"); + // PlayerDataElementAccount.db2 + PrepareStatement(HotfixStatements.SEL_PLAYER_DATA_ELEMENT_ACCOUNT, "SELECT ID, StorageIndex, Type FROM player_data_element_account" + + " WHERE (`VerifiedBuild` > 0) = ?"); + + // PlayerDataElementCharacter.db2 + PrepareStatement(HotfixStatements.SEL_PLAYER_DATA_ELEMENT_CHARACTER, "SELECT ID, StorageIndex, Type FROM player_data_element_character" + + " WHERE (`VerifiedBuild` > 0) = ?"); + + // PlayerDataFlagAccount.db2 + PrepareStatement(HotfixStatements.SEL_PLAYER_DATA_FLAG_ACCOUNT, "SELECT ID, StorageIndex, Unknown1107 FROM player_data_flag_account" + + " WHERE (`VerifiedBuild` > 0) = ?"); + + // PlayerDataFlagCharacter.db2 + PrepareStatement(HotfixStatements.SEL_PLAYER_DATA_FLAG_CHARACTER, "SELECT ID, StorageIndex, Unknown1107 FROM player_data_flag_character" + + " WHERE (`VerifiedBuild` > 0) = ?"); + // PowerDisplay.db2 PrepareStatement(HotfixStatements.SEL_POWER_DISPLAY, "SELECT ID, GlobalStringBaseTag, ActualType, Red, Green, Blue FROM power_display" + " WHERE (`VerifiedBuild` > 0) = ?"); @@ -2157,6 +2173,14 @@ namespace Framework.Database SEL_PLAYER_CONDITION, SEL_PLAYER_CONDITION_LOCALE, + SEL_PLAYER_DATA_ELEMENT_ACCOUNT, + + SEL_PLAYER_DATA_ELEMENT_CHARACTER, + + SEL_PLAYER_DATA_FLAG_ACCOUNT, + + SEL_PLAYER_DATA_FLAG_CHARACTER, + SEL_POWER_DISPLAY, SEL_POWER_TYPE, diff --git a/Source/Framework/Database/Databases/LoginDatabase.cs b/Source/Framework/Database/Databases/LoginDatabase.cs index 99a2e8983..917c11fd1 100644 --- a/Source/Framework/Database/Databases/LoginDatabase.cs +++ b/Source/Framework/Database/Databases/LoginDatabase.cs @@ -28,7 +28,7 @@ namespace Framework.Database PrepareStatement(LoginStatements.SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?"); PrepareStatement(LoginStatements.SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?"); PrepareStatement(LoginStatements.SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id AS aId, a.session_key_bnet, ba.last_ip, ba.locked, ba.lock_country, a.expansion, a.mutetime, a.client_build, a.locale, a.recruiter, a.os, a.timezone_offset, ba.id AS baId, aa.SecurityLevel, " + - "bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate AS is_bnet_banned, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate AS is_banned, r.id " + + "bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate AS is_bnet_banned, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate AS is_banned, r.id " + "FROM account a LEFT JOIN account r ON a.id = r.recruiter LEFT JOIN battlenet_accounts ba ON a.battlenet_account = ba.id " + "LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?) LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 " + "WHERE a.username = ? AND LENGTH(a.session_key_bnet) = 64 ORDER BY aa.RealmID DESC LIMIT 1"); @@ -108,8 +108,8 @@ namespace Framework.Database PrepareStatement(LoginStatements.SEL_BNET_EXISTING_AUTHENTICATION, "SELECT LoginTicketExpiry FROM battlenet_accounts WHERE LoginTicket = ?"); PrepareStatement(LoginStatements.SEL_BNET_EXISTING_AUTHENTICATION_BY_ID, "SELECT LoginTicket FROM battlenet_accounts WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_EXISTING_AUTHENTICATION, "UPDATE battlenet_accounts SET LoginTicketExpiry = ? WHERE LoginTicket = ?"); - PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_INFO, $"SELECT {BnetAccountInfo}, {BnetGameAccountInfo}" + - " FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account a ON ba.id = a.battlenet_account" + + PrepareStatement(LoginStatements.SEL_BNET_ACCOUNT_INFO, $"SELECT {BnetAccountInfo}, {BnetGameAccountInfo}" + + " FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab ON ba.id = bab.id LEFT JOIN account a ON ba.id = a.battlenet_account" + " LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID = -1 WHERE ba.LoginTicket = ? ORDER BY a.id"); PrepareStatement(LoginStatements.UPD_BNET_LAST_LOGIN_INFO, "UPDATE battlenet_accounts SET last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE id = ?"); PrepareStatement(LoginStatements.UPD_BNET_GAME_ACCOUNT_LOGIN_INFO, "UPDATE account SET session_key_bnet = ?, last_ip = ?, last_login = NOW(), client_build = ?, locale = ?, failed_logins = 0, os = ?, timezone_offset = ? WHERE username = ?"); @@ -166,19 +166,26 @@ namespace Framework.Database // Transmog collection PrepareStatement(LoginStatements.SEL_BNET_ITEM_APPEARANCES, "SELECT blobIndex, appearanceMask FROM battlenet_item_appearances WHERE battlenetAccountId = ? ORDER BY blobIndex DESC"); - PrepareStatement(LoginStatements.INS_BNET_ITEM_APPEARANCES, "INSERT INTO battlenet_item_appearances (battlenetAccountId, blobIndex, appearanceMask) VALUES (?, ?, ?) " + + PrepareStatement(LoginStatements.INS_BNET_ITEM_APPEARANCES, "INSERT INTO battlenet_item_appearances (battlenetAccountId, blobIndex, appearanceMask) VALUES (?, ?, ?) " + "ON DUPLICATE KEY UPDATE appearanceMask = appearanceMask | VALUES(appearanceMask)"); PrepareStatement(LoginStatements.SEL_BNET_ITEM_FAVORITE_APPEARANCES, "SELECT itemModifiedAppearanceId FROM battlenet_item_favorite_appearances WHERE battlenetAccountId = ?"); PrepareStatement(LoginStatements.INS_BNET_ITEM_FAVORITE_APPEARANCE, "INSERT INTO battlenet_item_favorite_appearances (battlenetAccountId, itemModifiedAppearanceId) VALUES (?, ?)"); PrepareStatement(LoginStatements.DEL_BNET_ITEM_FAVORITE_APPEARANCE, "DELETE FROM battlenet_item_favorite_appearances WHERE battlenetAccountId = ? AND itemModifiedAppearanceId = ?"); PrepareStatement(LoginStatements.SEL_BNET_TRANSMOG_ILLUSIONS, "SELECT blobIndex, illusionMask FROM battlenet_account_transmog_illusions WHERE battlenetAccountId = ? ORDER BY blobIndex DESC"); - PrepareStatement(LoginStatements.INS_BNET_TRANSMOG_ILLUSIONS, "INSERT INTO battlenet_account_transmog_illusions (battlenetAccountId, blobIndex, illusionMask) VALUES (?, ?, ?) " + + PrepareStatement(LoginStatements.INS_BNET_TRANSMOG_ILLUSIONS, "INSERT INTO battlenet_account_transmog_illusions (battlenetAccountId, blobIndex, illusionMask) VALUES (?, ?, ?) " + "ON DUPLICATE KEY UPDATE illusionMask = illusionMask | VALUES(illusionMask)"); PrepareStatement(LoginStatements.SEL_BNET_WARBAND_SCENES, "SELECT warbandSceneId, isFavorite, hasFanfare FROM battlenet_account_warband_scenes WHERE battlenetAccountId = ?"); PrepareStatement(LoginStatements.INS_BNET_WARBAND_SCENE, "INSERT INTO battlenet_account_warband_scenes (battlenetAccountId, warbandSceneId, isFavorite, hasFanfare) VALUES (?, ?, ?, ?) " + "ON DUPLICATE KEY UPDATE isFavorite = VALUES(isFavorite)"); PrepareStatement(LoginStatements.UPD_BNET_WARBAND_SCENE, "UPDATE battlenet_account_warband_scenes SET isFavorite = ?, hasFanfare = ? WHERE battlenetAccountId = ? AND warbandSceneId = ?"); PrepareStatement(LoginStatements.DEL_BNET_WARBAND_SCENE, "DELETE FROM battlenet_account_warband_scenes WHERE battlenetAccountId = ? AND warbandSceneId = ?"); + + PrepareStatement(LoginStatements.SEL_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT, "SELECT playerDataElementAccountId, floatValue, int64Value FROM battlenet_account_player_data_element WHERE battlenetAccountId = ?"); + PrepareStatement(LoginStatements.DEL_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT, "DELETE FROM battlenet_account_player_data_element WHERE battlenetAccountId = ? AND playerDataElementAccountId = ?"); + PrepareStatement(LoginStatements.INS_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT, "INSERT INTO battlenet_account_player_data_element (battlenetAccountId, playerDataElementAccountId, floatValue, int64Value) VALUES (?, ?, ?, ?)"); + PrepareStatement(LoginStatements.SEL_BNET_PLAYER_DATA_FLAGS_ACCOUNT, "SELECT storageIndex, mask FROM battlenet_account_player_data_flag WHERE battlenetAccountId = ?"); + PrepareStatement(LoginStatements.DEL_BNET_PLAYER_DATA_FLAGS_ACCOUNT, "DELETE FROM battlenet_account_player_data_flag WHERE battlenetAccountId = ? AND storageIndex = ?"); + PrepareStatement(LoginStatements.INS_BNET_PLAYER_DATA_FLAGS_ACCOUNT, "INSERT INTO battlenet_account_player_data_flag (battlenetAccountId, storageIndex, mask) VALUES (?, ?, ?)"); } } @@ -337,6 +344,13 @@ namespace Framework.Database UPD_BNET_WARBAND_SCENE, DEL_BNET_WARBAND_SCENE, + SEL_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT, + DEL_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT, + INS_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT, + SEL_BNET_PLAYER_DATA_FLAGS_ACCOUNT, + DEL_BNET_PLAYER_DATA_FLAGS_ACCOUNT, + INS_BNET_PLAYER_DATA_FLAGS_ACCOUNT, + MAX_LOGINDATABASE_STATEMENTS } } diff --git a/Source/Game/Achievements/CriteriaHandler.cs b/Source/Game/Achievements/CriteriaHandler.cs index 2e39c2c62..b55df56d5 100644 --- a/Source/Game/Achievements/CriteriaHandler.cs +++ b/Source/Game/Achievements/CriteriaHandler.cs @@ -3713,6 +3713,14 @@ namespace Game.Achievements if (referencePlayer.GetPositionZ() >= reqValue) return false; break; + case ModifierTreeType.PlayerDataFlagAccountIsSet: // 378 + if (!referencePlayer.HasDataFlagAccount(reqValue)) + return false; + break; + case ModifierTreeType.PlayerDataFlagCharacterIsSet: // 379 + if (!referencePlayer.HasDataFlagCharacter(reqValue)) + return false; + break; case ModifierTreeType.PlayerIsOnMapWithExpansion: // 380 { var mapEntry = referencePlayer.GetMap().GetEntry(); @@ -3747,6 +3755,16 @@ namespace Game.Achievements return false; break; } + case ModifierTreeType.PlayerDataElementCharacterBetween: // 390 + { + var value = referencePlayer.GetDataElementCharacter(reqValue); + return value >= secondaryAsset && value <= tertiaryAsset; + } + case ModifierTreeType.PlayerDataElementAccountBetween: // 391 + { + var value = referencePlayer.GetDataElementAccount(reqValue); + return value >= secondaryAsset && value <= tertiaryAsset; + } case ModifierTreeType.PlayerHasCompletedQuestOrIsReadyToTurnIn: // 392 { QuestStatus status = referencePlayer.GetQuestStatus(reqValue); diff --git a/Source/Game/DataStorage/CliDB.cs b/Source/Game/DataStorage/CliDB.cs index 5d5897937..7d07b12af 100644 --- a/Source/Game/DataStorage/CliDB.cs +++ b/Source/Game/DataStorage/CliDB.cs @@ -264,6 +264,10 @@ namespace Game.DataStorage PhaseStorage = ReadDB2("Phase.db2", HotfixStatements.SEL_PHASE); PhaseXPhaseGroupStorage = ReadDB2("PhaseXPhaseGroup.db2", HotfixStatements.SEL_PHASE_X_PHASE_GROUP); PlayerConditionStorage = ReadDB2("PlayerCondition.db2", HotfixStatements.SEL_PLAYER_CONDITION, HotfixStatements.SEL_PLAYER_CONDITION_LOCALE); + PlayerDataElementAccountStorage = ReadDB2("PlayerDataElementAccount.db2", HotfixStatements.SEL_PLAYER_DATA_ELEMENT_ACCOUNT); + PlayerDataElementCharacterStorage = ReadDB2("PlayerDataElementCharacter.db2", HotfixStatements.SEL_PLAYER_DATA_ELEMENT_CHARACTER); + PlayerDataFlagAccountStorage = ReadDB2("PlayerDataFlagAccount.db2", HotfixStatements.SEL_PLAYER_DATA_FLAG_ACCOUNT); + PlayerDataFlagCharacterStorage = ReadDB2("PlayerDataFlagCharacter.db2", HotfixStatements.SEL_PLAYER_DATA_FLAG_CHARACTER); PowerDisplayStorage = ReadDB2("PowerDisplay.db2", HotfixStatements.SEL_POWER_DISPLAY); PowerTypeStorage = ReadDB2("PowerType.db2", HotfixStatements.SEL_POWER_TYPE); PrestigeLevelInfoStorage = ReadDB2("PrestigeLevelInfo.db2", HotfixStatements.SEL_PRESTIGE_LEVEL_INFO, HotfixStatements.SEL_PRESTIGE_LEVEL_INFO_LOCALE); @@ -396,7 +400,7 @@ namespace Game.DataStorage WMOAreaTableStorage = ReadDB2("WMOAreaTable.db2", HotfixStatements.SEL_WMO_AREA_TABLE, HotfixStatements.SEL_WMO_AREA_TABLE_LOCALE); WorldEffectStorage = ReadDB2("WorldEffect.db2", HotfixStatements.SEL_WORLD_EFFECT); WorldMapOverlayStorage = ReadDB2("WorldMapOverlay.db2", HotfixStatements.SEL_WORLD_MAP_OVERLAY); - WorldStateExpressionStorage = ReadDB2("WorldStateExpression.db2", HotfixStatements.SEL_WORLD_STATE_EXPRESSION); + WorldStateExpressionStorage = ReadDB2("WorldStateExpression.db2", HotfixStatements.SEL_WORLD_STATE_EXPRESSION); // Check loaded DB2 files proper version if (!AreaTableStorage.ContainsKey(16108) || // last area added in 11.0.7 (58162) @@ -664,6 +668,10 @@ namespace Game.DataStorage public static DB6Storage PhaseStorage; public static DB6Storage PhaseXPhaseGroupStorage; public static DB6Storage PlayerConditionStorage; + public static DB6Storage PlayerDataElementAccountStorage; + public static DB6Storage PlayerDataElementCharacterStorage; + public static DB6Storage PlayerDataFlagAccountStorage; + public static DB6Storage PlayerDataFlagCharacterStorage; public static DB6Storage PowerDisplayStorage; public static DB6Storage PowerTypeStorage; public static DB6Storage PrestigeLevelInfoStorage; diff --git a/Source/Game/DataStorage/Structs/P_Records.cs b/Source/Game/DataStorage/Structs/P_Records.cs index e4525a398..f1be33296 100644 --- a/Source/Game/DataStorage/Structs/P_Records.cs +++ b/Source/Game/DataStorage/Structs/P_Records.cs @@ -149,6 +149,38 @@ namespace Game.DataStorage public ushort[] TraitNodeEntryMaxRank = new ushort[4]; } + public sealed class PlayerDataElementAccountRecord + { + public uint Id; + public int StorageIndex; + public int Type; + + public PlayerDataElementType GetElementType() { return (PlayerDataElementType)Type; } + } + + public sealed class PlayerDataElementCharacterRecord + { + public uint Id; + public int StorageIndex; + public int Type; + + public PlayerDataElementType GetElementType() { return (PlayerDataElementType)Type; } + } + + public sealed class PlayerDataFlagAccountRecord + { + public uint Id; + public int StorageIndex; + public int Unknown1107; + } + + public sealed class PlayerDataFlagCharacterRecord + { + public uint Id; + public int StorageIndex; + public int Unknown1107; + } + public sealed class PowerDisplayRecord { public uint Id; diff --git a/Source/Game/Entities/Object/Update/UpdateFields.cs b/Source/Game/Entities/Object/Update/UpdateFields.cs index 5f1b6655e..0775d5af1 100644 --- a/Source/Game/Entities/Object/Update/UpdateFields.cs +++ b/Source/Game/Entities/Object/Update/UpdateFields.cs @@ -3804,6 +3804,7 @@ namespace Game.Entities data.WriteInt64(Int64Value); } } + public void WriteUpdate(WorldPacket data, bool ignoreChangesMask, Player owner, Player receiver) { data.WriteUInt32(Type); diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index 7d280a457..6b23fd789 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -575,6 +575,7 @@ namespace Game.Entities // TODO: finish dragonriding - this forces old flight mode AddAura(404468, this); } + bool _LoadHomeBind(SQLResult result) { PlayerInfo info = Global.ObjectMgr.GetPlayerInfo(GetRace(), GetClass()); @@ -650,6 +651,80 @@ namespace Game.Entities return true; } + + void _LoadPlayerData(SQLResult elementsResult, SQLResult flagsResult) + { + if (!elementsResult.IsEmpty()) + { + do + { + var entry = CliDB.PlayerDataElementCharacterStorage.LookupByKey(elementsResult.Read(0)); + if (entry == null) + continue; + + PlayerDataElement dataElement = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.CharacterDataElements, entry.StorageIndex); + + SetUpdateFieldValue(ref dataElement.Type, (uint)entry.Type); + + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + SetUpdateFieldValue(ref dataElement.Int64Value, elementsResult.Read(1)); + break; + case PlayerDataElementType.Float: + SetUpdateFieldValue(ref dataElement.FloatValue, elementsResult.Read(2)); + break; + default: + break; + } + } while (elementsResult.NextRow()); + } + + if (!flagsResult.IsEmpty()) + { + BitVectors bitVectors = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.BitVectors); + BitVector bitVector = bitVectors.ModifyValue(bitVectors.Values, (int)PlayerDataFlag.CharacterDataIndex); + + do + { + SetUpdateFieldValue(bitVector.ModifyValue(bitVector.Values, flagsResult.Read(0)), flagsResult.Read(1)); + } while (flagsResult.NextRow()); + } + + PlayerDataAccount accountData = GetSession().GetPlayerDataAccount(); + foreach (var element in accountData.Elements) + { + var entry = CliDB.PlayerDataElementAccountStorage.LookupByKey(element.Id); + if (entry == null) + continue; + + PlayerDataElement dataElement = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.AccountDataElements, entry.StorageIndex); + + SetUpdateFieldValue(ref dataElement.Type, (uint)entry.Type); + + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + SetUpdateFieldValue(ref dataElement.Int64Value, element.Int64Value); + break; + case PlayerDataElementType.Float: + SetUpdateFieldValue(ref dataElement.FloatValue, element.FloatValue); + break; + default: + break; + } + } + + if (!accountData.Flags.Empty()) + { + BitVectors bitVectors = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.BitVectors); + BitVector bitVector = bitVectors.ModifyValue(bitVectors.Values, (int)PlayerDataFlag.AccountDataIndex); + + for (var i = 0; i < accountData.Flags.Count; ++i) + SetUpdateFieldValue(bitVector.ModifyValue(bitVector.Values, i), accountData.Flags[i].Value); + } + } + void _LoadCurrency(SQLResult result) { if (result.IsEmpty()) @@ -2194,7 +2269,7 @@ namespace Game.Entities } // Save spawn trackings - stmt = CharacterDatabase.GetPreparedStatement( CharStatements.DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING_BY_QUEST); + stmt = CharacterDatabase.GetPreparedStatement(CharStatements.DEL_CHAR_QUESTSTATUS_OBJECTIVES_SPAWN_TRACKING_BY_QUEST); stmt.AddValue(0, GetGUID().GetCounter()); stmt.AddValue(1, save.Key); trans.Append(stmt); @@ -2586,6 +2661,7 @@ namespace Game.Entities } } } + void _SaveStats(SQLTransaction trans) { // check if stat saving is enabled and if char level is high enough @@ -2625,6 +2701,71 @@ namespace Game.Entities trans.Append(stmt); } + + void _SavePlayerData(SQLTransaction trans) + { + ulong guid = GetGUID().GetCounter(); + PreparedStatement stmt; + foreach (uint playerDataElementCharacterId in _playerDataElementsNeedSave) + { + stmt = CharacterDatabase.GetPreparedStatement(CharStatements.DEL_PLAYER_DATA_ELEMENTS_CHARACTER); + stmt.AddValue(0, guid); + stmt.AddValue(1, playerDataElementCharacterId); + trans.Append(stmt); + + var entry = CliDB.PlayerDataElementCharacterStorage.LookupByKey(playerDataElementCharacterId); + if (entry == null) + continue; + + PlayerDataElement element = m_activePlayerData.CharacterDataElements[entry.StorageIndex]; + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + if (element.Int64Value == 0) + continue; + stmt = CharacterDatabase.GetPreparedStatement(CharStatements.INS_PLAYER_DATA_ELEMENTS_CHARACTER); + stmt.AddValue(0, guid); + stmt.AddValue(1, playerDataElementCharacterId); + stmt.AddNull(2); + stmt.AddValue(3, element.Int64Value); + trans.Append(stmt); + break; + case PlayerDataElementType.Float: + if (element.FloatValue == 0) + continue; + stmt = CharacterDatabase.GetPreparedStatement(CharStatements.INS_PLAYER_DATA_ELEMENTS_CHARACTER); + stmt.AddValue(0, guid); + stmt.AddValue(1, playerDataElementCharacterId); + stmt.AddValue(2, element.FloatValue); + stmt.AddNull(3); + trans.Append(stmt); + break; + } + + } + + foreach (int storageIndex in _playerDataFlagsNeedSave) + { + stmt = CharacterDatabase.GetPreparedStatement(CharStatements.DEL_PLAYER_DATA_FLAGS_CHARACTER); + stmt.AddValue(0, guid); + stmt.AddValue(1, storageIndex); + trans.Append(stmt); + + ulong value = m_activePlayerData.BitVectors.GetValue().Values[(int)PlayerDataFlag.CharacterDataIndex].Values[storageIndex]; + if (value == 0) + continue; + + stmt = CharacterDatabase.GetPreparedStatement(CharStatements.INS_PLAYER_DATA_FLAGS_CHARACTER); + stmt.AddValue(0, guid); + stmt.AddValue(1, storageIndex); + stmt.AddValue(2, value); + trans.Append(stmt); + } + + _playerDataElementsNeedSave.Clear(); + _playerDataFlagsNeedSave.Clear(); + } + public void SaveInventoryAndGoldToDB(SQLTransaction trans) { _SaveInventory(trans); @@ -2635,6 +2776,7 @@ namespace Game.Entities stmt.AddValue(1, GetGUID().GetCounter()); trans.Append(stmt); } + void _SaveEquipmentSets(SQLTransaction trans) { foreach (var pair in _equipmentSets) @@ -3628,6 +3770,8 @@ namespace Game.Entities _LoadCUFProfiles(holder.GetResult(PlayerLoginQueryLoad.CufProfiles)); + _LoadPlayerData(holder.GetResult(PlayerLoginQueryLoad.DataElements), holder.GetResult(PlayerLoginQueryLoad.DataFlags)); + var garrison = new Garrison(this); if (garrison.LoadFromDB(holder.GetResult(PlayerLoginQueryLoad.Garrison), holder.GetResult(PlayerLoginQueryLoad.GarrisonBlueprints), @@ -4062,6 +4206,7 @@ namespace Game.Entities _SaveInstanceTimeRestrictions(characterTransaction); _SaveCurrency(characterTransaction); _SaveCUFProfiles(characterTransaction); + _SavePlayerData(characterTransaction); if (_garrison != null) _garrison.SaveToDB(characterTransaction); @@ -4078,6 +4223,7 @@ namespace Game.Entities GetSession().GetCollectionMgr().SaveAccountItemAppearances(loginTransaction); GetSession().GetCollectionMgr().SaveAccountTransmogIllusions(loginTransaction); GetSession().GetCollectionMgr().SaveAccountWarbandScenes(loginTransaction); + GetSession().SavePlayerDataAccount(loginTransaction); var currentRealmId = Global.RealmMgr.GetCurrentRealmId(); diff --git a/Source/Game/Entities/Player/Player.Fields.cs b/Source/Game/Entities/Player/Player.Fields.cs index b1793d710..57e964266 100644 --- a/Source/Game/Entities/Player/Player.Fields.cs +++ b/Source/Game/Entities/Player/Player.Fields.cs @@ -253,6 +253,9 @@ namespace Game.Entities PlayerCommandStates _activeCheats; + List _playerDataElementsNeedSave = new(); + List _playerDataFlagsNeedSave = new(); + class ValuesUpdateForPlayerWithMaskSender : IDoWork { Player Owner; diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 32481735c..a51d83a99 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -5027,7 +5027,7 @@ namespace Game.Entities reward.Money = playerChoiceResponseTemplate.Reward.Money; reward.Xp = playerChoiceResponseTemplate.Reward.Xp; - void fillRewardItems(List src, List dest) + void fillRewardItems(List src, List dest) { for (var j = 0; j < src.Count; ++j) { @@ -5077,6 +5077,176 @@ namespace Game.Entities return ConditionManager.IsPlayerMeetingCondition(this, conditionId); } + public dynamic GetDataElementAccount(uint dataElementId) + { + var entry = CliDB.PlayerDataElementAccountStorage.LookupByKey(dataElementId); + if (entry == null) + return 0L; + + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + if (entry.StorageIndex < m_activePlayerData.AccountDataElements.Size()) + return m_activePlayerData.AccountDataElements[entry.StorageIndex].Int64Value; + return 0L; + case PlayerDataElementType.Float: + if (entry.StorageIndex < m_activePlayerData.AccountDataElements.Size()) + return (0, m_activePlayerData.AccountDataElements[entry.StorageIndex].FloatValue); + return 0.0f; + default: + break; + } + + return 0L; + } + + public void SetDataElementAccount(uint dataElementId, dynamic value) + { + var entry = CliDB.PlayerDataElementAccountStorage.LookupByKey(dataElementId); + if (entry == null) + return; + + PlayerDataElement elementSetter = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.AccountDataElements, entry.StorageIndex); + SetUpdateFieldValue(ref elementSetter.Type, (uint)entry.Type); + + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + { + long int64Value = value; + SetUpdateFieldValue(ref elementSetter.Int64Value, int64Value); + GetSession().SetPlayerDataElementAccount(dataElementId, int64Value); + break; + } + case PlayerDataElementType.Float: + { + float floatValue = value; + SetUpdateFieldValue(ref elementSetter.FloatValue, floatValue); + GetSession().SetPlayerDataElementAccount(dataElementId, floatValue); + break; + } + default: + break; + } + } + + public dynamic GetDataElementCharacter(uint dataElementId) + { + var entry = CliDB.PlayerDataElementCharacterStorage.LookupByKey(dataElementId); + if (entry == null) + return 0L; + + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + if (entry.StorageIndex < m_activePlayerData.CharacterDataElements.Size()) + return m_activePlayerData.CharacterDataElements[entry.StorageIndex].Int64Value; + return 0L; + case PlayerDataElementType.Float: + if (entry.StorageIndex < m_activePlayerData.CharacterDataElements.Size()) + return m_activePlayerData.CharacterDataElements[entry.StorageIndex].FloatValue; + return 0.0f; + default: + break; + } + + return 0L; + } + + public void SetDataElementCharacter(uint dataElementId, dynamic value) + { + var entry = CliDB.PlayerDataElementCharacterStorage.LookupByKey(dataElementId); + if (entry == null) + return; + + PlayerDataElement elementSetter = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.CharacterDataElements, entry.StorageIndex); + SetUpdateFieldValue(ref elementSetter.Type, (uint)entry.Type); + + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + SetUpdateFieldValue(ref elementSetter.Int64Value, value); + break; + case PlayerDataElementType.Float: + SetUpdateFieldValue(ref elementSetter.FloatValue, value); + break; + default: + break; + } + + _playerDataElementsNeedSave.Add(dataElementId); + } + + public bool HasDataFlagAccount(uint dataFlagId) + { + var entry = CliDB.PlayerDataFlagAccountStorage.LookupByKey(dataFlagId); + if (entry == null) + return false; + + int fieldOffset = entry.StorageIndex / PlayerConst.DataFlagValueBits; + ulong flag = 1UL << (entry.StorageIndex % PlayerConst.DataFlagValueBits); + + if (fieldOffset >= m_activePlayerData.BitVectors.GetValue().Values[(int)PlayerDataFlag.AccountDataIndex].Values.Size()) + return false; + + return (m_activePlayerData.BitVectors.GetValue().Values[(int)PlayerDataFlag.AccountDataIndex].Values[fieldOffset] & flag) != 0; + } + + public void SetDataFlagAccount(uint dataFlagId, bool on) + { + var entry = CliDB.PlayerDataFlagAccountStorage.LookupByKey(dataFlagId); + if (entry == null) + return; + + int fieldOffset = entry.StorageIndex / PlayerConst.DataFlagValueBits; + ulong flag = 1UL << (entry.StorageIndex % PlayerConst.DataFlagValueBits); + + BitVectors bitVectors = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.BitVectors); + BitVector bitVector = bitVectors.ModifyValue(bitVectors.Values, (int)PlayerDataFlag.AccountDataIndex); + + if (on) + SetUpdateFieldFlagValue(bitVector.ModifyValue(bitVector.Values, fieldOffset), flag); + else + RemoveUpdateFieldFlagValue(bitVector.ModifyValue(bitVector.Values, fieldOffset), flag); + + GetSession().SetPlayerDataFlagAccount(dataFlagId, on); + } + + public bool HasDataFlagCharacter(uint dataFlagId) + { + var entry = CliDB.PlayerDataFlagCharacterStorage.LookupByKey(dataFlagId); + if (entry == null) + return false; + + int fieldOffset = entry.StorageIndex / PlayerConst.DataFlagValueBits; + ulong flag = 1UL << (entry.StorageIndex % PlayerConst.DataFlagValueBits); + + if (fieldOffset >= m_activePlayerData.BitVectors.GetValue().Values[(int)PlayerDataFlag.CharacterDataIndex].Values.Size()) + return false; + + return (m_activePlayerData.BitVectors.GetValue().Values[(int)PlayerDataFlag.CharacterDataIndex].Values[fieldOffset] & flag) != 0; + } + + public void SetDataFlagCharacter(uint dataFlagId, bool on) + { + var entry = CliDB.PlayerDataFlagCharacterStorage.LookupByKey(dataFlagId); + if (entry == null) + return; + + int fieldOffset = entry.StorageIndex / PlayerConst.DataFlagValueBits; + ulong flag = 1UL << (entry.StorageIndex % PlayerConst.DataFlagValueBits); + + BitVectors bitVectors = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.BitVectors); + BitVector bitVector = bitVectors.ModifyValue(bitVectors.Values, (int)PlayerDataFlag.CharacterDataIndex); + + if (on) + SetUpdateFieldFlagValue(bitVector.ModifyValue(bitVector.Values, fieldOffset), flag); + else + RemoveUpdateFieldFlagValue(bitVector.ModifyValue(bitVector.Values, fieldOffset), flag); + + _playerDataFlagsNeedSave.Add((uint)fieldOffset); + } + bool IsInFriendlyArea() { var areaEntry = CliDB.AreaTableStorage.LookupByKey(GetAreaId()); diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs index 1dd620dc3..2963ce042 100644 --- a/Source/Game/Handlers/CharacterHandler.cs +++ b/Source/Game/Handlers/CharacterHandler.cs @@ -2958,6 +2958,14 @@ namespace Game stmt = CharacterDatabase.GetPreparedStatement(CharStatements.SEL_CHAR_TRAIT_CONFIGS); stmt.AddValue(0, lowGuid); SetQuery(PlayerLoginQueryLoad.TraitConfigs, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CharStatements.SEL_PLAYER_DATA_ELEMENTS_CHARACTER); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.DataElements, stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CharStatements.SEL_PLAYER_DATA_FLAGS_CHARACTER); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.DataFlags, stmt); } public ObjectGuid GetGuid() { return m_guid; } @@ -3064,6 +3072,8 @@ namespace Game GarrisonFollowerAbilities, TraitEntries, TraitConfigs, + DataElements, + DataFlags, Max } diff --git a/Source/Game/Server/WorldSession.cs b/Source/Game/Server/WorldSession.cs index d90a0df60..723ee2911 100644 --- a/Source/Game/Server/WorldSession.cs +++ b/Source/Game/Server/WorldSession.cs @@ -9,6 +9,7 @@ using Game.Accounts; using Game.BattleGrounds; using Game.BattlePets; using Game.Chat; +using Game.DataStorage; using Game.Entities; using Game.Guilds; using Game.Maps; @@ -18,6 +19,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Numerics; using System.Text; @@ -26,7 +28,7 @@ namespace Game public partial class WorldSession : IDisposable { public static uint SPECIAL_INIT_ACTIVE_MOVER_TIME_SYNC_COUNTER = 0xFFFFFFFF; - public static uint SPECIAL_RESUME_COMMS_TIME_SYNC_COUNTER = 0xFFFFFFFE; + public static uint SPECIAL_RESUME_COMMS_TIME_SYNC_COUNTER = 0xFFFFFFFE; public WorldSession(uint id, string name, uint battlenetAccountId, WorldSocket sock, AccountTypes sec, Expansion expansion, long mute_time, string os, TimeSpan timezoneOffset, uint build, Framework.ClientBuild.ClientBuildVariantId clientBuildVariant, Locale locale, uint recruiter, bool isARecruiter) { @@ -496,6 +498,163 @@ namespace Game tutorialsChanged &= ~TutorialsFlag.Changed; } + void LoadPlayerDataAccount(SQLResult elementsResult, SQLResult flagsResult) + { + if (!elementsResult.IsEmpty()) + { + do + { + var entry = CliDB.PlayerDataElementAccountStorage.LookupByKey(elementsResult.Read(0)); + if (entry == null) + continue; + + PlayerDataAccount.Element element = new(); + element.Id = entry.Id; + element.NeedSave = false; + + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + element.Int64Value = elementsResult.Read(2); + break; + case PlayerDataElementType.Float: + element.FloatValue = elementsResult.Read(1); + break; + default: + break; + } + + _playerDataAccount.Elements.Add(element); + } while (elementsResult.NextRow()); + } + + if (!flagsResult.IsEmpty()) + { + do + { + _playerDataAccount.Flags.EnsureWritableListIndex(flagsResult.Read(0), new PlayerDataAccount.Flag() { Value = flagsResult.Read(1), NeedSave = false }); + } while (flagsResult.NextRow()); + } + } + + public void SavePlayerDataAccount(SQLTransaction transaction) + { + PreparedStatement stmt; + for (int i = 0; i < _playerDataAccount.Elements.Count; i++) + { + PlayerDataAccount.Element element = _playerDataAccount.Elements[i]; + if (!element.NeedSave) + continue; + + stmt = LoginDatabase.GetPreparedStatement(LoginStatements.DEL_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT); + stmt.AddValue(0, GetBattlenetAccountId()); + stmt.AddValue(1, element.Id); + transaction.Append(stmt); + + element.NeedSave = false; + + var entry = CliDB.PlayerDataElementAccountStorage.LookupByKey(element.Id); + if (entry == null) + continue; + + switch (entry.GetElementType()) + { + case PlayerDataElementType.Int64: + if (element.Int64Value == 0) + continue; + stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT); + stmt.AddValue(0, GetBattlenetAccountId()); + stmt.AddValue(1, element.Id); + stmt.AddNull(2); + stmt.AddValue(3, element.Int64Value); + transaction.Append(stmt); + break; + case PlayerDataElementType.Float: + if (element.FloatValue == 0) + continue; + stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_BNET_PLAYER_DATA_ELEMENTS_ACCOUNT); + stmt.AddValue(0, GetBattlenetAccountId()); + stmt.AddValue(1, element.Id); + stmt.AddValue(2, element.FloatValue); + stmt.AddNull(3); + transaction.Append(stmt); + break; + } + } + + for (var i = 0; i < _playerDataAccount.Flags.Count; ++i) + { + var flag = _playerDataAccount.Flags[i]; + if (!flag.NeedSave) + continue; + + stmt = LoginDatabase.GetPreparedStatement(LoginStatements.DEL_BNET_PLAYER_DATA_FLAGS_ACCOUNT); + stmt.AddValue(0, GetBattlenetAccountId()); + stmt.AddValue(1, i); + transaction.Append(stmt); + + flag.NeedSave = false; + + if (flag.Value == 0) + continue; + + stmt = LoginDatabase.GetPreparedStatement(LoginStatements.INS_BNET_PLAYER_DATA_FLAGS_ACCOUNT); + stmt.AddValue(0, GetBattlenetAccountId()); + stmt.AddValue(1, i); + stmt.AddValue(2, flag.Value); + transaction.Append(stmt); + } + } + + public void SetPlayerDataElementAccount(uint dataElementId, float value) + { + var element = _playerDataAccount.Elements.Find(p => p.Id == dataElementId); + if (element == null) + { + element = new(); + element.Id = dataElementId; + _playerDataAccount.Elements.Add(element); + } + + element.NeedSave = true; + element.FloatValue = value; + } + + public void SetPlayerDataElementAccount(uint dataElementId, long value) + { + var element = _playerDataAccount.Elements.Find(p => p.Id == dataElementId); + if (element == null) + { + element = new(); + element.Id = dataElementId; + _playerDataAccount.Elements.Add(element); + } + + element.NeedSave = true; + element.Int64Value = value; + } + + public void SetPlayerDataFlagAccount(uint dataFlagId, bool on) + { + var entry = CliDB.PlayerDataFlagAccountStorage.LookupByKey(dataFlagId); + if (entry == null) + return; + + int fieldOffset = entry.StorageIndex / PlayerConst.DataFlagValueBits; + ulong flagValue = 1UL << (entry.StorageIndex % PlayerConst.DataFlagValueBits); + + _playerDataAccount.Flags.EnsureWritableListIndex((uint)fieldOffset, new PlayerDataAccount.Flag()); + var flag = _playerDataAccount.Flags[fieldOffset]; + if (on) + flag.Value |= flagValue; + else + flag.Value &= ~flagValue; + + flag.NeedSave = true; + } + + public PlayerDataAccount GetPlayerDataAccount() { return _playerDataAccount; } + public void SendConnectToInstance(ConnectToSerial serial) { System.Net.IPAddress instanceAddress = null; @@ -824,6 +983,7 @@ namespace Game _collectionMgr.LoadAccountItemAppearances(holder.GetResult(AccountInfoQueryLoad.ItemAppearances), holder.GetResult(AccountInfoQueryLoad.ItemFavoriteAppearances)); _collectionMgr.LoadAccountTransmogIllusions(holder.GetResult(AccountInfoQueryLoad.TransmogIllusions)); _collectionMgr.LoadAccountWarbandScenes(holder.GetResult(AccountInfoQueryLoad.WarbandScenes)); + LoadPlayerDataAccount(holder.GetResult(AccountInfoQueryLoad.PlayerDataElementsAccount), holder.GetResult(AccountInfoQueryLoad.PlayerDataFlagsAccount)); if (!m_inQueue) SendAuthResponse(BattlenetRpcErrorCode.Ok, false); @@ -1007,6 +1167,7 @@ namespace Game Dictionary> _battlenetResponseCallbacks = new(); uint _battlenetRequestToken; + PlayerDataAccount _playerDataAccount = new(); List _registeredAddonPrefixes = new(); bool _filterAddonMessages; uint recruiterId; @@ -1217,6 +1378,28 @@ namespace Game GlobalAccountDataIndexPerRealm, TutorialsIndexPerRealm, TransmogIllusions, - WarbandScenes + WarbandScenes, + PlayerDataElementsAccount, + PlayerDataFlagsAccount, + } + + public class PlayerDataAccount + { + public List Elements = new(); + public List Flags = new(); + + public class Element + { + public uint Id; + public bool NeedSave; + public float FloatValue; + public long Int64Value; + } + + public class Flag + { + public ulong Value; + public bool NeedSave; + } } } diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 7bda16f9c..1f9b0f279 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -5899,9 +5899,7 @@ namespace Game.Spells if (bitIndex < 0 || bitIndex >= sizeof(uint) * 8) return false; - FlagArray128 reqFlag = new(); - reqFlag[bitIndex / 32] = 1u << (bitIndex % 32); - return (spellOnCooldown.SpellFamilyFlags & reqFlag); + return (spellOnCooldown.SpellFamilyFlags[bitIndex / 32] & 1u << (bitIndex % 32)) != 0; }, TimeSpan.FromMilliseconds(damage)); } @@ -6003,6 +6001,58 @@ namespace Game.Spells target.GetSession().GetCollectionMgr().AddWarbandScene((uint)effectInfo.MiscValue); } + + [SpellEffectHandler(SpellEffectName.SetPlayerDataElementAccount)] + void EffectSetPlayerDataElementAccount() + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player target = unitTarget?.ToPlayer(); + if (target == null) + return; + + target.SetDataElementAccount((uint)effectInfo.MiscValue, (long)damage); + } + + [SpellEffectHandler(SpellEffectName.SetPlayerDataElementCharacter)] + void EffectSetPlayerDataElementCharacter() + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player target = unitTarget?.ToPlayer(); + if (target == null) + return; + + target.SetDataElementCharacter((uint)effectInfo.MiscValue, (long)damage); + } + + [SpellEffectHandler(SpellEffectName.SetPlayerDataFlagAccount)] + void EffectSetPlayerDataFlagAccount() + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player target = unitTarget?.ToPlayer(); + if (target == null) + return; + + target.SetDataFlagAccount((uint)effectInfo.MiscValue, damage != 0); + } + + [SpellEffectHandler(SpellEffectName.SetPlayerDataFlagCharacter)] + void EffectSetPlayerDataFlagCharacter() + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player target = unitTarget?.ToPlayer(); + if (target == null) + return; + + target.SetDataFlagCharacter((uint)effectInfo.MiscValue, damage != 0); + } } public class DispelableAura diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index ed794dc8c..7ad68ec66 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -4891,10 +4891,10 @@ namespace Game.Spells new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 332 SPELL_EFFECT_332 new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 333 SPELL_EFFECT_333 new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 334 SPELL_EFFECT_334 - new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 335 SPELL_EFFECT_335 - new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 336 SPELL_EFFECT_336 - new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 337 SPELL_EFFECT_337 - new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 338 SPELL_EFFECT_338 + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 335 SPELL_EFFECT_SET_PLAYER_DATA_ELEMENT_ACCOUNT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 336 SPELL_EFFECT_SET_PLAYER_DATA_ELEMENT_CHARACTER + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 337 SPELL_EFFECT_SET_PLAYER_DATA_FLAG_ACCOUNT + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 338 SPELL_EFFECT_SET_PLAYER_DATA_FLAG_CHARACTER new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 339 SPELL_EFFECT_UI_ACTION new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 340 SPELL_EFFECT_340 new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 341 SPELL_EFFECT_LEARN_WARBAND_SCENE