diff --git a/Source/Framework/Constants/PlayerConst.cs b/Source/Framework/Constants/PlayerConst.cs index 00f207f6d..77ff5c229 100644 --- a/Source/Framework/Constants/PlayerConst.cs +++ b/Source/Framework/Constants/PlayerConst.cs @@ -24,6 +24,8 @@ namespace Framework.Constants public const int MaxTalentTiers = 7; public const int MaxTalentColumns = 3; public const int MaxTalentRank = 5; + public const int MaxPvpTalentTiers = 6; + public const int MaxPvpTalentColumns = 3; public const int MinSpecializationLevel = 10; public const int MaxSpecializations = 4; public const int MaxMasterySpells = 2; @@ -67,6 +69,7 @@ namespace Framework.Constants public const byte MaxHonorLevel = 50; public const byte LevelMinHonor = 110; + public const uint SpellPvpRulesEnabled = 134735; } public struct MoneyConstants diff --git a/Source/Framework/Constants/Spells/SpellAuraConst.cs b/Source/Framework/Constants/Spells/SpellAuraConst.cs index e9811d0fd..3b8d21798 100644 --- a/Source/Framework/Constants/Spells/SpellAuraConst.cs +++ b/Source/Framework/Constants/Spells/SpellAuraConst.cs @@ -140,7 +140,7 @@ namespace Framework.Constants ModRegenDuringCombat = 116, ModMechanicResistance = 117, ModHealingPct = 118, - Unk119 = 119, // Old SharePetTracking + PvpTalents = 119, Untrackable = 120, Empathy = 121, ModOffhandDamagePct = 122, diff --git a/Source/Framework/Database/Databases/CharacterDatabase.cs b/Source/Framework/Database/Databases/CharacterDatabase.cs index 434d7c45c..359be60f4 100644 --- a/Source/Framework/Database/Databases/CharacterDatabase.cs +++ b/Source/Framework/Database/Databases/CharacterDatabase.cs @@ -137,6 +137,7 @@ namespace Framework.Database PrepareStatement(CharStatements.SEL_CHARACTER_BGDATA, "SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell FROM character_battleground_data WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHARACTER_GLYPHS, "SELECT talentGroup, glyphId FROM character_glyphs WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHARACTER_TALENTS, "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?"); + PrepareStatement(CharStatements.SEL_CHARACTER_PVP_TALENTS, "SELECT talentId, talentGroup FROM character_pvp_talent WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHARACTER_SKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?"); PrepareStatement(CharStatements.SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1"); @@ -584,6 +585,7 @@ namespace Framework.Database PrepareStatement(CharStatements.DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, "DELETE FROM guild_bank_eventlog WHERE PlayerGuid = ?"); PrepareStatement(CharStatements.DEL_CHAR_GLYPHS, "DELETE FROM character_glyphs WHERE guid = ?"); PrepareStatement(CharStatements.DEL_CHAR_TALENT, "DELETE FROM character_talent WHERE guid = ?"); + 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 (?, ?, ?, ?, ?)"); @@ -616,6 +618,7 @@ namespace Framework.Database PrepareStatement(CharStatements.DEL_PETITION_SIGNATURE_BY_OWNER, "DELETE FROM petition_sign WHERE ownerguid = ?"); PrepareStatement(CharStatements.INS_CHAR_GLYPHS, "INSERT INTO character_glyphs VALUES(?, ?, ?)"); PrepareStatement(CharStatements.INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentGroup) VALUES (?, ?, ?)"); + PrepareStatement(CharStatements.INS_CHAR_PVP_TALENT, "INSERT INTO character_pvp_talent (guid, talentId, talentGroup) VALUES (?, ?, ?)"); 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 = ?"); @@ -827,6 +830,7 @@ namespace Framework.Database SEL_CHARACTER_BGDATA, SEL_CHARACTER_GLYPHS, SEL_CHARACTER_TALENTS, + SEL_CHARACTER_PVP_TALENTS, SEL_CHARACTER_TRANSMOG_OUTFITS, SEL_CHARACTER_SKILLS, SEL_CHARACTER_RANDOMBG, @@ -1207,6 +1211,7 @@ namespace Framework.Database DEL_GUILD_BANK_EVENTLOG_BY_PLAYER, DEL_CHAR_GLYPHS, DEL_CHAR_TALENT, + DEL_CHAR_PVP_TALENT, DEL_CHAR_SKILLS, UPD_CHAR_MONEY, INS_CHAR_ACTION, @@ -1237,6 +1242,7 @@ namespace Framework.Database DEL_PETITION_SIGNATURE_BY_OWNER, INS_CHAR_GLYPHS, INS_CHAR_TALENT, + INS_CHAR_PVP_TALENT, UPD_CHAR_LIST_SLOT, INS_CHAR_FISHINGSTEPS, DEL_CHAR_FISHINGSTEPS, diff --git a/Source/Framework/Database/Databases/HotfixDatabase.cs b/Source/Framework/Database/Databases/HotfixDatabase.cs index f41b5bfd1..476f93376 100644 --- a/Source/Framework/Database/Databases/HotfixDatabase.cs +++ b/Source/Framework/Database/Databases/HotfixDatabase.cs @@ -693,6 +693,14 @@ namespace Framework.Database // PvpReward.db2 PrepareStatement(HotfixStatements.SEL_PVP_REWARD, "SELECT ID, HonorLevel, Prestige, RewardPackID FROM pvp_reward ORDER BY ID DESC"); + // PvpTalent.db2 + PrepareStatement(HotfixStatements.SEL_PVP_TALENT, "SELECT ID, Description, SpellID, OverridesSpellID, ExtraSpellID, TierID, ColumnIndex, Flags, ClassID, " + + "SpecID, Role FROM pvp_talent ORDER BY ID DESC"); + PrepareStatement(HotfixStatements.SEL_PVP_TALENT_LOCALE, "SELECT ID, Description_lang FROM pvp_talent_locale WHERE locale = ?"); + + // PvpTalentUnlock.db2 + PrepareStatement(HotfixStatements.SEL_PVP_TALENT_UNLOCK, "SELECT ID, TierID, ColumnIndex, HonorLevel FROM pvp_talent_unlock ORDER BY ID DESC"); + // QuestFactionReward.db2 PrepareStatement(HotfixStatements.SEL_QUEST_FACTION_REWARD, "SELECT ID, QuestRewFactionValue1, QuestRewFactionValue2, QuestRewFactionValue3, " + "QuestRewFactionValue4, QuestRewFactionValue5, QuestRewFactionValue6, QuestRewFactionValue7, QuestRewFactionValue8, QuestRewFactionValue9, " + @@ -1385,6 +1393,11 @@ namespace Framework.Database SEL_PVP_REWARD, + SEL_PVP_TALENT, + SEL_PVP_TALENT_LOCALE, + + SEL_PVP_TALENT_UNLOCK, + SEL_QUEST_FACTION_REWARD, SEL_QUEST_MONEY_REWARD, diff --git a/Source/Game/DataStorage/CliDB.cs b/Source/Game/DataStorage/CliDB.cs index 744c6285e..716deb7c4 100644 --- a/Source/Game/DataStorage/CliDB.cs +++ b/Source/Game/DataStorage/CliDB.cs @@ -183,6 +183,8 @@ namespace Game.DataStorage PrestigeLevelInfoStorage = DBReader.Read("PrestigeLevelInfo.db2", HotfixStatements.SEL_PRESTIGE_LEVEL_INFO, HotfixStatements.SEL_PRESTIGE_LEVEL_INFO_LOCALE); PVPDifficultyStorage = DBReader.Read("PVPDifficulty.db2", HotfixStatements.SEL_PVP_DIFFICULTY); PvpRewardStorage = DBReader.Read("PvpReward.db2", HotfixStatements.SEL_PVP_REWARD); + PvpTalentStorage = DBReader.Read("PvpTalent.db2", HotfixStatements.SEL_PVP_TALENT, HotfixStatements.SEL_PVP_TALENT_LOCALE); + PvpTalentUnlockStorage = DBReader.Read("PvpTalentUnlock.db2", HotfixStatements.SEL_PVP_TALENT_UNLOCK); QuestFactionRewardStorage = DBReader.Read("QuestFactionReward.db2", HotfixStatements.SEL_QUEST_FACTION_REWARD); QuestMoneyRewardStorage = DBReader.Read("QuestMoneyReward.db2", HotfixStatements.SEL_QUEST_MONEY_REWARD); QuestPackageItemStorage = DBReader.Read("QuestPackageItem.db2", HotfixStatements.SEL_QUEST_PACKAGE_ITEM); @@ -512,6 +514,8 @@ namespace Game.DataStorage public static DB6Storage PrestigeLevelInfoStorage; public static DB6Storage PVPDifficultyStorage; public static DB6Storage PvpRewardStorage; + public static DB6Storage PvpTalentStorage; + public static DB6Storage PvpTalentUnlockStorage; public static DB6Storage QuestFactionRewardStorage; public static DB6Storage QuestMoneyRewardStorage; public static DB6Storage QuestPackageItemStorage; diff --git a/Source/Game/DataStorage/ClientReader/BitReader.cs b/Source/Game/DataStorage/ClientReader/BitReader.cs index 07708a4fd..69b6c2931 100644 --- a/Source/Game/DataStorage/ClientReader/BitReader.cs +++ b/Source/Game/DataStorage/ClientReader/BitReader.cs @@ -48,78 +48,30 @@ namespace Game.DataStorage return result; } - public ulong ReadUInt64(int numBits) + public ulong ReadUInt64(int bitWidth, int bitOffset) { - ulong result = FastStruct.ArrayToStructure(ref m_array[m_readOffset + (m_readPos >> 3)]) << (64 - numBits - (m_readPos & 7)) >> (64 - numBits); - m_readPos += numBits; + int bitsToRead = bitOffset & 7; + ulong result = FastStruct.ArrayToStructure(ref m_array[m_readOffset + (m_readPos >> 3)]) << (64 - bitsToRead - bitWidth) >> (64 - bitWidth); + m_readPos += bitWidth; return result; } - public Value32 ReadValue32(int numBits) + public Value64 ReadValue64(int bitWidth, int bitOffset = 0, bool isSigned = false) { unsafe { - ulong result = ReadUInt32(numBits); - return *(Value32*)&result; - } - } + ulong result = ReadUInt64(bitWidth, bitOffset); + if (isSigned) + { + ulong mask = 1ul << (bitWidth - 1); + result = (result ^ mask) - mask; + } - public Value64 ReadValue64(int numBits) - { - unsafe - { - ulong result = ReadUInt64(numBits); return *(Value64*)&result; } } } - public struct Value32 - { - unsafe fixed byte Value[4]; - - public T GetValue() where T : struct - { - unsafe - { - fixed (byte* ptr = Value) - return FastStruct.ArrayToStructure(ref ptr[0]); - } - } - - public byte[] GetBytes(int bitSize) - { - if (((bitSize + 7) / 8) != NextPow2((bitSize + 7) / 8)) - { - - } - - byte[] data = new byte[NextPow2((bitSize + 7) / 8)]; - unsafe - { - fixed (byte* ptr = Value) - { - for (var i = 0; i < data.Length; ++i) - data[i] = ptr[i]; - } - } - - return data; - } - - private int NextPow2(int v) - { - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v++; - return Math.Max(v, 1); - } - } - public struct Value64 { unsafe fixed byte Value[8]; @@ -135,9 +87,6 @@ namespace Game.DataStorage public byte[] GetBytes(int bitSize) { - var val = ((bitSize + 7) / 8); - var val1 = NextPow2((bitSize + 7) / 8); - byte[] data = new byte[NextPow2((bitSize + 7) / 8)]; unsafe { diff --git a/Source/Game/DataStorage/ClientReader/DBReader.cs b/Source/Game/DataStorage/ClientReader/DBReader.cs index 894736d09..1a12c2ac7 100644 --- a/Source/Game/DataStorage/ClientReader/DBReader.cs +++ b/Source/Game/DataStorage/ClientReader/DBReader.cs @@ -275,7 +275,7 @@ namespace Game.DataStorage if (!Copies.ContainsKey(idcopy)) Copies.Add(idcopy, new List()); - + Copies[idcopy].Add(id); } } @@ -429,7 +429,7 @@ namespace Game.DataStorage } else { - data.AddRange(bitReader.ReadValue64(bitWidth).GetBytes(bitWidth)); + data.AddRange(bitReader.ReadValue64(bitWidth, ColumnMeta[f].BitOffset, ColumnMeta[f].Cardinality == 1).GetBytes(bitWidth)); } break; diff --git a/Source/Game/DataStorage/DB2Manager.cs b/Source/Game/DataStorage/DB2Manager.cs index 78f35e8dc..cfa29ffa9 100644 --- a/Source/Game/DataStorage/DB2Manager.cs +++ b/Source/Game/DataStorage/DB2Manager.cs @@ -350,6 +350,41 @@ namespace Game.DataStorage CliDB.PvpRewardStorage.Clear(); + for (var x = 0; x < (int)Class.Max; ++x) + { + _pvpTalentsByPosition[x] = new List[PlayerConst.MaxPvpTalentTiers][]; + for (var y = 0; y < PlayerConst.MaxPvpTalentTiers; ++y) + { + _pvpTalentsByPosition[x][y] = new List[PlayerConst.MaxPvpTalentColumns]; + for (var z = 0; z < PlayerConst.MaxPvpTalentColumns; ++z) + _pvpTalentsByPosition[x][y][z] = new List(); + } + } + + for (var i = 0; i < PlayerConst.MaxPvpTalentTiers; ++i) + _pvpTalentUnlock[i] = new uint[PlayerConst.MaxPvpTalentColumns]; + + foreach (PvpTalentRecord talentInfo in CliDB.PvpTalentStorage.Values) + { + //ASSERT(talentInfo->ClassID < MAX_CLASSES); + //ASSERT(talentInfo->TierID < MAX_PVP_TALENT_TIERS, "MAX_PVP_TALENT_TIERS must be at least %u", talentInfo.TierID + 1); + //ASSERT(talentInfo->ColumnIndex < MAX_PVP_TALENT_COLUMNS, "MAX_PVP_TALENT_COLUMNS must be at least %u", talentInfo.ColumnIndex + 1); + if (talentInfo.ClassID == 0) + { + for (uint i = 1; i < (int)Class.Max; ++i) + _pvpTalentsByPosition[i][talentInfo.TierID][talentInfo.ColumnIndex].Add(talentInfo); + } + else + _pvpTalentsByPosition[talentInfo.ClassID][talentInfo.TierID][talentInfo.ColumnIndex].Add(talentInfo); + } + + foreach (PvpTalentUnlockRecord talentUnlock in CliDB.PvpTalentUnlockStorage.Values) + { + //ASSERT(talentUnlock->TierID < MAX_PVP_TALENT_TIERS, "MAX_PVP_TALENT_TIERS must be at least %u", talentUnlock->TierID + 1); + //ASSERT(talentUnlock->ColumnIndex < MAX_PVP_TALENT_COLUMNS, "MAX_PVP_TALENT_COLUMNS must be at least %u", talentUnlock->ColumnIndex + 1); + _pvpTalentUnlock[talentUnlock.TierID][talentUnlock.ColumnIndex] = (uint)talentUnlock.HonorLevel; + } + foreach (QuestPackageItemRecord questPackageItem in CliDB.QuestPackageItemStorage.Values) { if (!_questPackages.ContainsKey(questPackageItem.QuestPackageID)) @@ -1146,6 +1181,17 @@ namespace Game.DataStorage return value; } + public uint GetRequiredHonorLevelForPvpTalent(PvpTalentRecord talentInfo) + { + //ASSERT(talentInfo); + return _pvpTalentUnlock[talentInfo.TierID][talentInfo.ColumnIndex]; + } + + public List GetPvpTalentsByPosition(uint classId, uint tier, uint column) + { + return _pvpTalentsByPosition[classId][tier][column]; + } + public List GetQuestPackageItems(uint questPackageID) { if( _questPackages.ContainsKey(questPackageID)) @@ -1468,6 +1514,8 @@ namespace Game.DataStorage MultiMap _phasesByGroup = new MultiMap(); Dictionary _powerTypes = new Dictionary(); Dictionary, uint> _pvpRewardPack = new Dictionary, uint>(); + List[][][] _pvpTalentsByPosition = new List[(int)Class.Max][][]; + uint[][] _pvpTalentUnlock = new uint[PlayerConst.MaxPvpTalentTiers][]; Dictionary, List>> _questPackages = new Dictionary, List>>(); MultiMap _rewardPackItems = new MultiMap(); Dictionary _rulesetItemUpgrade = new Dictionary(); diff --git a/Source/Game/DataStorage/Structs/P_Records.cs b/Source/Game/DataStorage/Structs/P_Records.cs index 7be5c1f7c..516188272 100644 --- a/Source/Game/DataStorage/Structs/P_Records.cs +++ b/Source/Game/DataStorage/Structs/P_Records.cs @@ -175,4 +175,27 @@ namespace Game.DataStorage public byte Prestige; public ushort RewardPackID; } + + public sealed class PvpTalentRecord + { + public uint Id; + public LocalizedString Description; + public uint SpellID; + public uint OverridesSpellID; + public int ExtraSpellID; + public int TierID; + public byte ColumnIndex; + public byte Flags; + public byte ClassID; + public ushort SpecID; + public byte Role; + } + + public sealed class PvpTalentUnlockRecord + { + public uint Id; + public byte TierID; + public byte ColumnIndex; + public byte HonorLevel; + } } diff --git a/Source/Game/Entities/Player/Player.Combat.cs b/Source/Game/Entities/Player/Player.Combat.cs index 354559b69..e3710052f 100644 --- a/Source/Game/Entities/Player/Player.Combat.cs +++ b/Source/Game/Entities/Player/Player.Combat.cs @@ -393,6 +393,9 @@ namespace Game.Entities SendMessageToSet(duelWinner, true); } + duel.opponent.DisablePvpRules(); + DisablePvpRules(); + Global.ScriptMgr.OnPlayerDuelEnd(duel.opponent, this, type); switch (type) diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index 4f421147d..4a6d1cea5 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -948,6 +948,20 @@ namespace Game.Entities while (result.NextRow()); } } + void _LoadPvpTalents(SQLResult result) + { + // "SELECT TalentID, TalentGroup FROM character_pvp_talent WHERE guid = ?" + if (!result.IsEmpty()) + { + do + { + PvpTalentRecord talent = CliDB.PvpTalentStorage.LookupByKey(result.Read(0)); + if (talent != null) + AddPvpTalent(talent, result.Read(1), false); + } + while (result.NextRow()); + } + } void _LoadGlyphs(SQLResult result) { // SELECT talentGroup, glyphId from character_glyphs WHERE guid = ? @@ -1977,6 +1991,29 @@ namespace Game.Entities trans.Append(stmt); } } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_PVP_TALENT); + stmt.AddValue(0, GetGUID().GetCounter()); + trans.Append(stmt); + + for (byte group = 0; group < PlayerConst.MaxSpecializations; ++group) + { + talents = GetPvpTalentMap(group); + foreach (var pair in talents.ToList()) + { + if (pair.Value == PlayerSpellState.Removed) + { + talents.Remove(pair.Key); + continue; + } + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_PVP_TALENT); + stmt.AddValue(0, GetGUID().GetCounter()); + stmt.AddValue(1, pair.Key); + stmt.AddValue(2, group); + trans.Append(stmt); + } + } } public void _SaveMail(SQLTransaction trans) { @@ -2852,6 +2889,7 @@ namespace Game.Entities SetUInt32Value(PlayerFields.CurrentSpecId, spec.Id); _LoadTalents(holder.GetResult(PlayerLoginQueryLoad.Talents)); + _LoadPvpTalents(holder.GetResult(PlayerLoginQueryLoad.PvpTalents)); _LoadSpells(holder.GetResult(PlayerLoginQueryLoad.Spells)); GetSession().GetCollectionMgr().LoadToys(); GetSession().GetCollectionMgr().LoadHeirlooms(); @@ -2880,6 +2918,8 @@ namespace Game.Entities InitTalentForLevel(); LearnDefaultSkills(); LearnCustomSpells(); + if (getLevel() < PlayerConst.LevelMinHonor) + ResetPvpTalents(); // must be before inventory (some items required reputation check) reputationMgr.LoadFromDB(holder.GetResult(PlayerLoginQueryLoad.Reputation)); diff --git a/Source/Game/Entities/Player/Player.Fields.cs b/Source/Game/Entities/Player/Player.Fields.cs index e2ee85ca6..be8e972e7 100644 --- a/Source/Game/Entities/Player/Player.Fields.cs +++ b/Source/Game/Entities/Player/Player.Fields.cs @@ -317,11 +317,13 @@ namespace Game.Entities for (byte i = 0; i < PlayerConst.MaxSpecializations; ++i) { Talents[i] = new Dictionary(); + PvpTalents[i] = new Dictionary(); Glyphs[i] = new List(); } } public Dictionary[] Talents = new Dictionary[PlayerConst.MaxSpecializations]; + public Dictionary[] PvpTalents = new Dictionary[PlayerConst.MaxSpecializations]; public List[] Glyphs = new List[PlayerConst.MaxSpecializations]; public uint ResetTalentsCost; public long ResetTalentsTime; diff --git a/Source/Game/Entities/Player/Player.Map.cs b/Source/Game/Entities/Player/Player.Map.cs index 7224cf7d6..43766d164 100644 --- a/Source/Game/Entities/Player/Player.Map.cs +++ b/Source/Game/Entities/Player/Player.Map.cs @@ -122,6 +122,11 @@ namespace Game.Entities UpdateAreaDependentAuras(newArea); UpdateAreaAndZonePhase(); + if (IsAreaThatActivatesPvpTalents(newArea)) + EnablePvpRules(); + else + DisablePvpRules(); + // previously this was in UpdateZone (but after UpdateArea) so nothing will break pvpInfo.IsInNoPvPArea = false; if (area != null && area.IsSanctuary()) // in sanctuary diff --git a/Source/Game/Entities/Player/Player.PvP.cs b/Source/Game/Entities/Player/Player.PvP.cs index 8c36e545f..2c8c1c540 100644 --- a/Source/Game/Entities/Player/Player.PvP.cs +++ b/Source/Game/Entities/Player/Player.PvP.cs @@ -23,6 +23,7 @@ using Game.BattleGrounds; using Game.DataStorage; using Game.Network.Packets; using Game.PvP; +using Game.Spells; using System; using System.Collections.Generic; @@ -333,6 +334,217 @@ namespace Game.Entities public uint GetHonorLevel() { return GetUInt32Value(PlayerFields.HonorLevel); } public bool IsMaxHonorLevelAndPrestige() { return IsMaxPrestige() && GetHonorLevel() == PlayerConst.MaxHonorLevel; } + void ResetPvpTalents() + { + foreach (var talentInfo in CliDB.PvpTalentStorage.Values) + { + if (talentInfo.ClassID != 0 && talentInfo.ClassID != (int)GetClass()) + continue; + + RemovePvpTalent(talentInfo); + } + + SQLTransaction trans = new SQLTransaction(); + _SaveTalents(trans); + _SaveSpells(trans); + DB.Characters.CommitTransaction(trans); + } + + public TalentLearnResult LearnPvpTalent(uint talentID, ref uint spellOnCooldown) + { + if (IsInCombat()) + return TalentLearnResult.FailedAffectingCombat; + + if (getLevel() < PlayerConst.LevelMinHonor) + return TalentLearnResult.FailedUnknown; + + PvpTalentRecord talentInfo = CliDB.PvpTalentStorage.LookupByKey(talentID); + if (talentInfo == null) + return TalentLearnResult.FailedUnknown; + + if (talentInfo.SpecID != 0) + { + if (talentInfo.SpecID != GetInt32Value(PlayerFields.CurrentSpecId)) + return TalentLearnResult.FailedUnknown; + } + else if (talentInfo.Role < 7) + { + if (talentInfo.Role != CliDB.ChrSpecializationStorage.LookupByKey(GetUInt32Value(PlayerFields.CurrentSpecId)).Role) + return TalentLearnResult.FailedUnknown; + } + + // prevent learn talent for different class (cheating) + if (talentInfo.ClassID != 0 && talentInfo.ClassID != (int)GetClass()) + return TalentLearnResult.FailedUnknown; + + if (GetPrestigeLevel() == 0) + if (Global.DB2Mgr.GetRequiredHonorLevelForPvpTalent(talentInfo) > GetHonorLevel()) + return TalentLearnResult.FailedUnknown; + + // Check if player doesn't have any talent in current tier + for (uint c = 0; c < PlayerConst.MaxPvpTalentColumns; ++c) + { + foreach (PvpTalentRecord talent in Global.DB2Mgr.GetPvpTalentsByPosition((uint)GetClass(), (uint)talentInfo.TierID, c)) + { + if (HasPvpTalent(talent.Id, GetActiveTalentGroup()) && !HasFlag(PlayerFields.Flags, PlayerFlags.Resting) && HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc)) + return TalentLearnResult.FailedRestArea; + + if (GetSpellHistory().HasCooldown(talent.SpellID)) + { + spellOnCooldown = talent.SpellID; + return TalentLearnResult.FailedCantRemoveTalent; + } + + RemovePvpTalent(talent); + } + } + + if (!AddPvpTalent(talentInfo, GetActiveTalentGroup(), true)) + return TalentLearnResult.FailedUnknown; + + return TalentLearnResult.LearnOk; + } + + bool AddPvpTalent(PvpTalentRecord talent, byte activeTalentGroup, bool learning) + { + //ASSERT(talent); + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talent.SpellID); + if (spellInfo == null) + { + Log.outError(LogFilter.Spells, $"Player.AddPvpTalent: Spell (ID: {talent.SpellID}) does not exist."); + return false; + } + + if (!Global.SpellMgr.IsSpellValid(spellInfo, this, false)) + { + Log.outError(LogFilter.Spells, $"Player.AddPvpTalent: Spell (ID: {talent.SpellID}) is invalid"); + return false; + } + + if (HasPvpRulesEnabled()) + LearnSpell(talent.SpellID, false); + + // Move this to toggle ? + if (talent.OverridesSpellID != 0) + AddOverrideSpell(talent.OverridesSpellID, talent.SpellID); + + if (learning) + GetPvpTalentMap(activeTalentGroup)[talent.Id] = PlayerSpellState.New; + else + GetPvpTalentMap(activeTalentGroup)[talent.Id] = PlayerSpellState.Unchanged; + + return true; + } + + void RemovePvpTalent(PvpTalentRecord talent) + { + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talent.SpellID); + if (spellInfo == null) + return; + + RemoveSpell(talent.SpellID, true); + + // Move this to toggle ? + if (talent.OverridesSpellID != 0) + RemoveOverrideSpell(talent.OverridesSpellID, talent.SpellID); + + // if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted + if (!GetPvpTalentMap(GetActiveTalentGroup()).ContainsKey(talent.Id)) + GetPvpTalentMap(GetActiveTalentGroup())[talent.Id] = PlayerSpellState.Removed; + } + + public void TogglePvpTalents(bool enable) + { + var pvpTalents = GetPvpTalentMap(GetActiveTalentGroup()); + foreach (var pair in pvpTalents) + { + PvpTalentRecord pvpTalentInfo = CliDB.PvpTalentStorage.LookupByKey(pair.Key); + if (enable && pair.Value != PlayerSpellState.Removed) + LearnSpell(pvpTalentInfo.SpellID, false); + else + RemoveSpell(pvpTalentInfo.SpellID, true); + } + } + + bool HasPvpTalent(uint talentID, byte activeTalentGroup) + { + return GetPvpTalentMap(activeTalentGroup).ContainsKey(talentID) && GetPvpTalentMap(activeTalentGroup)[talentID] != PlayerSpellState.Removed; + } + + public void EnablePvpRules(bool dueToCombat = false) + { + if (HasPvpRulesEnabled()) + return; + + if (!HasSpell(195710)) // Honorable Medallion + CastSpell(this, 208682); // Learn Gladiator's Medallion + + CastSpell(this, PlayerConst.SpellPvpRulesEnabled); + if (!dueToCombat) + { + Aura aura = GetAura(PlayerConst.SpellPvpRulesEnabled); + if (aura != null) + { + aura.SetMaxDuration(-1); + aura.SetDuration(-1); + } + } + } + + void DisablePvpRules() + { + // Don't disable pvp rules when in pvp zone. + if (IsInAreaThatActivatesPvpTalents()) + return; + + if (GetCombatTimer() == 0) + RemoveAurasDueToSpell(PlayerConst.SpellPvpRulesEnabled); + else + { + Aura aura = GetAura(PlayerConst.SpellPvpRulesEnabled); + if (aura != null) + aura.SetDuration(aura.GetSpellInfo().GetMaxDuration()); + } + } + + bool HasPvpRulesEnabled() + { + return HasAura(PlayerConst.SpellPvpRulesEnabled); + } + + bool IsInAreaThatActivatesPvpTalents() + { + return IsAreaThatActivatesPvpTalents(GetAreaId()); + } + + bool IsAreaThatActivatesPvpTalents(uint areaID) + { + if (InBattleground()) + return true; + + AreaTableRecord area = CliDB.AreaTableStorage.LookupByKey(areaID); + if (area != null) + { + do + { + if (area.IsSanctuary()) + return false; + + if (area.Flags[0].HasAnyFlag(AreaFlags.Arena)) + return true; + + if (Global.BattleFieldMgr.GetBattlefieldToZoneId(area.Id) != null) + return true; + + area = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID); + + } while (area != null); + } + + return false; + } + + public Dictionary GetPvpTalentMap(uint spec) { return _specializationInfo.PvpTalents[spec]; } //BGs public Battleground GetBattleground() diff --git a/Source/Game/Entities/Player/Player.Talents.cs b/Source/Game/Entities/Player/Player.Talents.cs index a2fcd5b76..9db9b342c 100644 --- a/Source/Game/Entities/Player/Player.Talents.cs +++ b/Source/Game/Entities/Player/Player.Talents.cs @@ -306,6 +306,32 @@ namespace Game.Entities RemoveOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID); } + foreach (var talentInfo in CliDB.PvpTalentStorage.Values) + { + // unlearn only talents for character class + // some spell learned by one class as normal spells or know at creation but another class learn it as talent, + // to prevent unexpected lost normal learned spell skip another class talents + if (talentInfo.ClassID != 0 && talentInfo.ClassID != (int)GetClass()) + continue; + + if (talentInfo.SpellID == 0) + continue; + + SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID); + if (spellInfo == null) + continue; + + RemoveSpell(talentInfo.SpellID, true); + + // search for spells that the talent teaches and unlearn them + foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) + if (effect != null && effect.TriggerSpell > 0 && effect.Effect == SpellEffectName.LearnSpell) + RemoveSpell(effect.TriggerSpell, true); + + if (talentInfo.OverridesSpellID != 0) + RemoveOverrideSpell(talentInfo.OverridesSpellID, talentInfo.SpellID); + } + // Remove spec specific spells RemoveSpecializationSpells(); @@ -334,6 +360,19 @@ namespace Game.Entities } } + foreach (var talentInfo in CliDB.PvpTalentStorage.Values) + { + // learn only talents for character class (or x-class talents) + if (talentInfo.ClassID != 0 && talentInfo.ClassID != (int)GetClass()) + continue; + + if (talentInfo.SpellID == 0) + continue; + + if (HasPvpTalent(talentInfo.Id, GetActiveTalentGroup())) + AddPvpTalent(talentInfo, GetActiveTalentGroup(), true); + } + LearnSpecializationSpells(); if (CanUseMastery()) @@ -498,7 +537,7 @@ namespace Game.Entities continue; var talents = GetTalentMap(i); - + var pvpTalents = GetPvpTalentMap(i); UpdateTalentData.TalentGroupInfo groupInfoPkt = new UpdateTalentData.TalentGroupInfo(); groupInfoPkt.SpecID = spec.Id; @@ -528,6 +567,31 @@ namespace Game.Entities groupInfoPkt.TalentIDs.Add((ushort)pair.Key); } + foreach (var pair in pvpTalents) + { + if (pair.Value == PlayerSpellState.Removed) + continue; + + PvpTalentRecord talentInfo = CliDB.PvpTalentStorage.LookupByKey(pair.Key); + if (talentInfo == null) + { + Log.outError(LogFilter.Player, $"Player.SendTalentsInfoData: Player '{GetName()}' ({GetGUID().ToString()}) has unknown pvp talent id: {pair.Key}"); + continue; + } + + if (talentInfo.ClassID != 0 && talentInfo.ClassID != (int)GetClass()) + continue; + + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(talentInfo.SpellID); + if (spellEntry == null) + { + Log.outError(LogFilter.Player, $"Player.SendTalentsInfoData: Player '{GetName()}' ({GetGUID().ToString()}) has unknown pvp talent spell: {talentInfo.SpellID}"); + continue; + } + + groupInfoPkt.PvPTalentIDs.Add((ushort)pair.Key); + } + packet.Info.TalentGroups.Add(groupInfoPkt); } diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index c30da806f..e8b35a0ad 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -494,6 +494,14 @@ namespace Game.Entities UpdateAfkReport(now); + if (GetCombatTimer() != 0) // Only set when in pvp combat + { + Aura aura = GetAura(PlayerConst.SpellPvpRulesEnabled); + if (aura != null) + if (!aura.IsPermanent()) + aura.SetDuration(aura.GetSpellInfo().GetMaxDuration()); + } + if (IsAIEnabled && GetAI() != null) GetAI().UpdateAI(diff); else if (NeedChangeAI) @@ -5307,6 +5315,9 @@ namespace Game.Entities InitTalentForLevel(); InitTaxiNodesForLevel(); + if (level < PlayerConst.LevelMinHonor) + ResetPvpTalents(); + UpdateAllStats(); if (WorldConfig.GetBoolValue(WorldCfg.AlwaysMaxskill)) // Max weapon skill when leveling up diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index 01aa6d036..5f42d3994 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -1279,7 +1279,12 @@ namespace Game.Entities return; if (PvP) + { m_CombatTimer = 5000; + Player me = ToPlayer(); + if (me) + me.EnablePvpRules(true); + } if (IsInCombat() || HasUnitState(UnitState.Evade)) return; diff --git a/Source/Game/Handlers/CharacterHandler.cs b/Source/Game/Handlers/CharacterHandler.cs index 42d7ae405..046a36070 100644 --- a/Source/Game/Handlers/CharacterHandler.cs +++ b/Source/Game/Handlers/CharacterHandler.cs @@ -2520,6 +2520,10 @@ namespace Game stmt.AddValue(0, lowGuid); SetQuery(PlayerLoginQueryLoad.Talents, stmt); + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_CHARACTER_PVP_TALENTS); + stmt.AddValue(0, lowGuid); + SetQuery(PlayerLoginQueryLoad.PvpTalents, stmt); + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_PLAYER_ACCOUNT_DATA); stmt.AddValue(0, lowGuid); SetQuery(PlayerLoginQueryLoad.AccountData, stmt); @@ -2615,6 +2619,7 @@ namespace Game BgData, Glyphs, Talents, + PvpTalents, AccountData, Skills, WeeklyQuestStatus, diff --git a/Source/Game/Handlers/DuelHandler.cs b/Source/Game/Handlers/DuelHandler.cs index 18da8f133..97d6cf1d5 100644 --- a/Source/Game/Handlers/DuelHandler.cs +++ b/Source/Game/Handlers/DuelHandler.cs @@ -77,6 +77,9 @@ namespace Game player.SendPacket(packet); plTarget.SendPacket(packet); + + player.EnablePvpRules(); + plTarget.EnablePvpRules(); } void HandleDuelCancelled() diff --git a/Source/Game/Handlers/SkillHandler.cs b/Source/Game/Handlers/SkillHandler.cs index 2226f3632..bcd51f550 100644 --- a/Source/Game/Handlers/SkillHandler.cs +++ b/Source/Game/Handlers/SkillHandler.cs @@ -52,6 +52,32 @@ namespace Game GetPlayer().SendTalentsInfoData(); } + [WorldPacketHandler(ClientOpcodes.LearnPvpTalents)] + void HandleLearnPvpTalentsOpcode(LearnPvpTalents packet) + { + LearnPvpTalentsFailed learnPvpTalentsFailed = new LearnPvpTalentsFailed(); + bool anythingLearned = false; + foreach (ushort talentId in packet.Talents) + { + TalentLearnResult result = _player.LearnPvpTalent(talentId, ref learnPvpTalentsFailed.SpellID); + if (result != 0) + { + if (learnPvpTalentsFailed.Reason == 0) + learnPvpTalentsFailed.Reason = (uint)result; + + learnPvpTalentsFailed.Talents.Add(talentId); + } + else + anythingLearned = true; + } + + if (learnPvpTalentsFailed.Reason != 0) + SendPacket(learnPvpTalentsFailed); + + if (anythingLearned) + _player.SendTalentsInfoData(); + } + [WorldPacketHandler(ClientOpcodes.ConfirmRespecWipe)] void HandleConfirmRespecWipe(ConfirmRespecWipe confirmRespecWipe) { diff --git a/Source/Game/Network/Packets/TalentPackets.cs b/Source/Game/Network/Packets/TalentPackets.cs index d65c8e050..1ac29e60e 100644 --- a/Source/Game/Network/Packets/TalentPackets.cs +++ b/Source/Game/Network/Packets/TalentPackets.cs @@ -18,6 +18,8 @@ using Framework.Constants; using Game.Entities; using System.Collections.Generic; +using Framework.Collections; +using Framework.Dynamic; namespace Game.Network.Packets { @@ -144,6 +146,39 @@ namespace Game.Network.Packets public bool IsFullUpdate; } + class LearnPvpTalents : ClientPacket + { + public LearnPvpTalents(WorldPacket packet) : base(packet) { } + + public override void Read() + { + int size = _worldPacket.ReadBits(6); + for (int i = 0; i < size; ++i) + Talents[i] = _worldPacket.ReadUInt16(); + } + + public Array Talents = new Array(6); + } + + class LearnPvpTalentsFailed : ServerPacket + { + public LearnPvpTalentsFailed() : base(ServerOpcodes.LearnPvpTalentsFailed) { } + + public override void Write() + { + _worldPacket.WriteBits(Reason, 4); + _worldPacket.WriteUInt32(SpellID); + _worldPacket.WriteUInt32(Talents.Count); + + foreach (var id in Talents) + _worldPacket.WriteUInt16(id); + } + + public uint Reason; + public uint SpellID; + public List Talents = new List(); + } + //Structs struct GlyphBinding { diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index 72f85304d..a8b01ba3c 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -3431,7 +3431,7 @@ namespace Game.Spells // Save old powers for further calculation int oldPower = target.GetPower(powerType); int oldMaxPower = target.GetMaxPower(powerType); - + // Handle aura effect for max power target.HandleStatModifier(unitMod, UnitModifierType.TotalPCT, GetAmount(), apply); @@ -5953,6 +5953,22 @@ namespace Game.Spells caster.RemoveAreaTrigger(this); } } + + [AuraEffectHandler(AuraType.PvpTalents)] + void HandleAuraPvpTalents(AuraApplication auraApp, AuraEffectHandleModes mode, bool apply) + { + if (!mode.HasAnyFlag(AuraEffectHandleModes.Real)) + return; + + Player target = auraApp.GetTarget().ToPlayer(); + if (target) + { + if (apply) + target.TogglePvpTalents(true); + else if (!target.HasAuraType(AuraType.PvpTalents)) + target.TogglePvpTalents(false); + } + } #endregion } diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 55abfac06..9e08c4a4a 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -1086,6 +1086,30 @@ LOCK TABLES `character_pet_declinedname` WRITE; /*!40000 ALTER TABLE `character_pet_declinedname` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `character_pvp_talent` +-- + +DROP TABLE IF EXISTS `character_pvp_talent`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `character_pvp_talent` ( + `guid` bigint(20) unsigned NOT NULL, + `talentId` mediumint(8) unsigned NOT NULL, + `talentGroup` tinyint(3) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`,`talentId`,`talentGroup`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `character_pvp_talent` +-- + +LOCK TABLES `character_pvp_talent` WRITE; +/*!40000 ALTER TABLE `character_pvp_talent` DISABLE KEYS */; +/*!40000 ALTER TABLE `character_pvp_talent` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `character_queststatus` -- @@ -3537,7 +3561,8 @@ INSERT INTO `updates` VALUES ('2017_10_29_00_characters.sql','8CFC473E7E87E58C317A72016BF69E9050D3BC83','ARCHIVED','2017-04-19 00:07:40',25), ('2018_02_03_00_characters.sql','73E9BFD848D7A22F2A7DD89CF64E30E3A8689512','ARCHIVED','2018-02-03 23:52:42',0), ('2018_02_08_00_characters.sql','75FA162A9B85D678B26F972371265F1EC2C75187','ARCHIVED','2018-02-08 22:23:28',0), -('2018_02_19_00_characters.sql','75A0FFAFD0633921708DB0F72F9CC9796ACB960B','RELEASED','2018-02-19 22:33:32',117); +('2018_02_19_00_characters.sql','75A0FFAFD0633921708DB0F72F9CC9796ACB960B','RELEASED','2018-02-19 22:33:32',117), +('2018_03_04_00_characters.sql','2A4CD2EE2547E718490706FADC78BF36F0DED8D6','RELEASED','2018-03-04 18:15:24',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/characters/master/2018_03_04_00_characters.sql b/sql/updates/characters/master/2018_03_04_00_characters.sql new file mode 100644 index 000000000..66ce84bad --- /dev/null +++ b/sql/updates/characters/master/2018_03_04_00_characters.sql @@ -0,0 +1,10 @@ +-- +-- Table structure for table `character_pvp_talent` +-- +DROP TABLE IF EXISTS `character_pvp_talent`; +CREATE TABLE `character_pvp_talent` ( + `guid` bigint(20) unsigned NOT NULL, + `talentId` mediumint(8) unsigned NOT NULL, + `talentGroup` tinyint(3) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`guid`,`talentId`,`talentGroup`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql/updates/hotfixes/master/2018_03_04_00_hotfixes.sql b/sql/updates/hotfixes/master/2018_03_04_00_hotfixes.sql new file mode 100644 index 000000000..ef7a3adfc --- /dev/null +++ b/sql/updates/hotfixes/master/2018_03_04_00_hotfixes.sql @@ -0,0 +1,44 @@ +-- +-- Table structure for table `pvp_talent` +-- +DROP TABLE IF EXISTS `pvp_talent`; +CREATE TABLE `pvp_talent` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `Description` text, + `SpellID` int(10) NOT NULL DEFAULT '0', + `OverridesSpellID` int(10) NOT NULL DEFAULT '0', + `ExtraSpellID` int(10) NOT NULL DEFAULT '0', + `TierID` int(10) NOT NULL DEFAULT '0', + `ColumnIndex` int(10) NOT NULL DEFAULT '0', + `Flags` int(10) NOT NULL DEFAULT '0', + `ClassID` int(10) NOT NULL DEFAULT '0', + `SpecID` int(10) NOT NULL DEFAULT '0', + `Role` int(10) NOT NULL DEFAULT '0', + `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- Table structure for table `pvp_talent_locale` +-- +DROP TABLE IF EXISTS `pvp_talent_locale`; +CREATE TABLE `pvp_talent_locale` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `locale` varchar(4) NOT NULL, + `Description_lang` text, + `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`locale`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- Table structure for table `pvp_talent_unlock` +-- +DROP TABLE IF EXISTS `pvp_talent_unlock`; +CREATE TABLE `pvp_talent_unlock` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `TierID` int(10) NOT NULL DEFAULT '0', + `ColumnIndex` int(10) NOT NULL DEFAULT '0', + `HonorLevel` int(10) NOT NULL DEFAULT '0', + `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/sql/updates/hotfixes/master/2018_03_05_00_hotfixes.sql b/sql/updates/hotfixes/master/2018_03_05_00_hotfixes.sql new file mode 100644 index 000000000..ccac08f99 --- /dev/null +++ b/sql/updates/hotfixes/master/2018_03_05_00_hotfixes.sql @@ -0,0 +1,11 @@ +-- +-- Table structure for table `pvp_item` +-- +DROP TABLE IF EXISTS `pvp_item`; +CREATE TABLE `pvp_item` ( + `ID` int(10) unsigned NOT NULL DEFAULT '0', + `ItemID` int(10) unsigned NOT NULL DEFAULT '0', + `ItemLevelBonus` tinyint(3) unsigned NOT NULL DEFAULT '0', + `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8;