diff --git a/Source/Game/Entities/Creature/Gossip.cs b/Source/Game/Entities/Creature/Gossip.cs index 70f2a5763..07bd1ddcf 100644 --- a/Source/Game/Entities/Creature/Gossip.cs +++ b/Source/Game/Entities/Creature/Gossip.cs @@ -8,6 +8,7 @@ using Game.DataStorage; using Game.Entities; using Game.Networking.Packets; using Game.Spells; +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -205,16 +206,31 @@ namespace Game.Misc public class InteractionData { + public ObjectGuid SourceGuid; + public bool IsLaunchedByQuest; + + ushort _playerChoiceResponseIdentifierGenerator = 0; // not reset between interactions + uint trainerId; + PlayerChoiceData playerChoiceData; + public void Reset() { SourceGuid.Clear(); - TrainerId = 0; + IsLaunchedByQuest = false; + trainerId = 0; } - public ObjectGuid SourceGuid; - public uint TrainerId; - public uint PlayerChoiceId; - public bool IsLaunchedByQuest; + public uint? GetTrainerId() { return trainerId; } + public void SetTrainerId(uint _trainerId) { trainerId = _trainerId; } + + public PlayerChoiceData GetPlayerChoice() { return playerChoiceData; } + public void SetPlayerChoice(uint choiceId) { playerChoiceData = new PlayerChoiceData(choiceId); } + + public ushort AddPlayerChoiceResponse(uint responseId) + { + playerChoiceData.AddResponse(responseId, ++_playerChoiceResponseIdentifierGenerator); + return _playerChoiceResponseIdentifierGenerator; + } } public class PlayerMenu @@ -835,4 +851,40 @@ namespace Game.Misc public uint TextId; public ConditionsReference Conditions; } + + public class PlayerChoiceData + { + uint _choiceId; + List _responses = new(); + DateTime? _expireTime; + + public PlayerChoiceData(uint choiceId) + { + _choiceId = choiceId; + } + + public uint? FindIdByClientIdentifier(ushort clientIdentifier) + { + var itr = _responses.Find(p => p.ClientIdentifier == clientIdentifier); + return itr != null ? itr.Id : null; + } + + public void AddResponse(uint id, ushort clientIdentifier) + { + _responses.Add(new Response(id, clientIdentifier)); + } + + public uint GetChoiceId() { return _choiceId; } + public void SetChoiceId(uint choiceId) { _choiceId = choiceId; } + + public DateTime? GetExpireTime() { return _expireTime; } + public void SetExpireTime(DateTime expireTime) { _expireTime = expireTime; } + + + public class Response(uint id, ushort clientIdentifier) + { + public uint Id = id; + public ushort ClientIdentifier = clientIdentifier; + } + } } diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index f02bcf7a2..2b3485dd3 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -4954,22 +4954,28 @@ namespace Game.Entities PlayerTalkClass.GetInteractionData().Reset(); PlayerTalkClass.GetInteractionData().SourceGuid = sender; - PlayerTalkClass.GetInteractionData().PlayerChoiceId = (uint)choiceId; + PlayerTalkClass.GetInteractionData().SetPlayerChoice((uint)choiceId); DisplayPlayerChoice displayPlayerChoice = new(); displayPlayerChoice.SenderGUID = sender; displayPlayerChoice.ChoiceID = choiceId; displayPlayerChoice.UiTextureKitID = playerChoice.UiTextureKitId; displayPlayerChoice.SoundKitID = playerChoice.SoundKitId; + displayPlayerChoice.CloseUISoundKitID = playerChoice.CloseSoundKitId; + if (playerChoice.Duration > TimeSpan.Zero) + displayPlayerChoice.ExpireTime = (GameTime.GetSystemTime() + playerChoice.Duration).ToFileTime(); + displayPlayerChoice.Question = playerChoice.Question; if (playerChoiceLocale != null) ObjectManager.GetLocaleString(playerChoiceLocale.Question, locale, ref displayPlayerChoice.Question); - displayPlayerChoice.InfiniteRange = false; + displayPlayerChoice.InfiniteRange = playerChoice.InfiniteRange; displayPlayerChoice.HideWarboardHeader = playerChoice.HideWarboardHeader; displayPlayerChoice.KeepOpenAfterChoice = playerChoice.KeepOpenAfterChoice; + displayPlayerChoice.ShowChoicesAsList = playerChoice.ShowChoicesAsList; + displayPlayerChoice.ForceDontShowChoicesAsList = playerChoice.ForceDontShowChoicesAsList; - for (var i = 0; i < playerChoice.Responses.Count; ++i) + for (var i = 0; i < playerChoice.Responses.Count && (playerChoice.MaxResponses == 0 || displayPlayerChoice.Responses.Count < playerChoice.MaxResponses); ++i) { PlayerChoiceResponse playerChoiceResponseTemplate = playerChoice.Responses[i]; if (!Global.ConditionMgr.IsObjectMeetingPlayerChoiceResponseConditions((uint)choiceId, playerChoiceResponseTemplate.ResponseId, this)) @@ -4977,9 +4983,9 @@ namespace Game.Entities var playerChoiceResponse = new Networking.Packets.PlayerChoiceResponse(); playerChoiceResponse.ResponseID = playerChoiceResponseTemplate.ResponseId; - playerChoiceResponse.ResponseIdentifier = playerChoiceResponseTemplate.ResponseIdentifier; + playerChoiceResponse.ResponseIdentifier = PlayerTalkClass.GetInteractionData().AddPlayerChoiceResponse((uint)playerChoiceResponseTemplate.ResponseId); playerChoiceResponse.ChoiceArtFileID = playerChoiceResponseTemplate.ChoiceArtFileId; - playerChoiceResponse.Flags = playerChoiceResponseTemplate.Flags; + playerChoiceResponse.Flags = (int)playerChoiceResponseTemplate.Flags; playerChoiceResponse.WidgetSetID = playerChoiceResponseTemplate.WidgetSetID; playerChoiceResponse.UiTextureAtlasElementID = playerChoiceResponseTemplate.UiTextureAtlasElementID; playerChoiceResponse.SoundKitID = playerChoiceResponseTemplate.SoundKitID; @@ -5017,51 +5023,32 @@ namespace Game.Entities reward.Money = playerChoiceResponseTemplate.Reward.Money; reward.Xp = playerChoiceResponseTemplate.Reward.Xp; - foreach (var item in playerChoiceResponseTemplate.Reward.Items) + void fillRewardItems(List src, List dest) { - var rewardEntry = new Networking.Packets.PlayerChoiceResponseRewardEntry(); - rewardEntry.Item.ItemID = item.Id; - rewardEntry.Quantity = item.Quantity; - if (!item.BonusListIDs.Empty()) + for (var j = 0; j < src.Count; ++j) { - rewardEntry.Item.ItemBonus = new(); - rewardEntry.Item.ItemBonus.BonusListIDs = item.BonusListIDs; + dynamic rewardEntryTemplate = src[j]; + Networking.Packets.PlayerChoiceResponseRewardEntry rewardEntry = new(); + rewardEntry.Item.ItemID = rewardEntryTemplate.Id; + rewardEntry.Quantity = rewardEntryTemplate.Quantity; + if (rewardEntryTemplate is PlayerChoiceResponseRewardItem) + { + if (!rewardEntryTemplate.BonusListIDs.empty()) + { + ItemBonuses itemBonuses = new(); + itemBonuses.BonusListIDs = rewardEntryTemplate.BonusListIDs; + rewardEntry.Item.ItemBonus = itemBonuses; + } + } + + dest.Add(rewardEntry); } - reward.Items.Add(rewardEntry); } - foreach (var currency in playerChoiceResponseTemplate.Reward.Currency) - { - var rewardEntry = new Networking.Packets.PlayerChoiceResponseRewardEntry(); - rewardEntry.Item.ItemID = currency.Id; - rewardEntry.Quantity = currency.Quantity; - reward.Items.Add(rewardEntry); - } - - foreach (var faction in playerChoiceResponseTemplate.Reward.Faction) - { - var rewardEntry = new Networking.Packets.PlayerChoiceResponseRewardEntry(); - rewardEntry.Item.ItemID = faction.Id; - rewardEntry.Quantity = faction.Quantity; - reward.Items.Add(rewardEntry); - } - - foreach (PlayerChoiceResponseRewardItem item in playerChoiceResponseTemplate.Reward.ItemChoices) - { - var rewardEntry = new Networking.Packets.PlayerChoiceResponseRewardEntry(); - rewardEntry.Item.ItemID = item.Id; - rewardEntry.Quantity = item.Quantity; - if (!item.BonusListIDs.Empty()) - { - rewardEntry.Item.ItemBonus = new(); - rewardEntry.Item.ItemBonus.BonusListIDs = item.BonusListIDs; - } - - reward.ItemChoices.Add(rewardEntry); - } - - playerChoiceResponse.Reward = reward; - displayPlayerChoice.Responses[i] = playerChoiceResponse; + fillRewardItems(playerChoiceResponseTemplate.Reward.Items, playerChoiceResponse.Reward.Items); + fillRewardItems(playerChoiceResponseTemplate.Reward.Currency, playerChoiceResponse.Reward.Currencies); + fillRewardItems(playerChoiceResponseTemplate.Reward.Faction, playerChoiceResponse.Reward.Factions); + fillRewardItems(playerChoiceResponseTemplate.Reward.ItemChoices, playerChoiceResponse.Reward.ItemChoices); } playerChoiceResponse.RewardQuestID = playerChoiceResponseTemplate.RewardQuestID; diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 432bc7dc4..aa8ef905e 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -10152,7 +10152,10 @@ namespace Game uint oldMSTime = Time.GetMSTime(); _playerChoices.Clear(); - SQLResult choiceResult = DB.World.Query("SELECT ChoiceId, UiTextureKitId, SoundKitId, CloseSoundKitId, Duration, Question, PendingChoiceText, HideWarboardHeader, KeepOpenAfterChoice FROM playerchoice"); + // 0 1 2 3 4 5 6 + SQLResult choiceResult = DB.World.Query("SELECT ChoiceId, UiTextureKitId, SoundKitId, CloseSoundKitId, Duration, Question, PendingChoiceText, " + + //7 8 9 10 11 12 13 + "InfiniteRange, HideWarboardHeader, KeepOpenAfterChoice, ShowChoicesAsList, ForceDontShowChoicesAsList, MaxResponses, ScriptName FROM playerchoice"); if (choiceResult.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 player choices. DB table `playerchoice` is empty."); @@ -10174,18 +10177,26 @@ namespace Game choice.UiTextureKitId = choiceResult.Read(1); choice.SoundKitId = choiceResult.Read(2); choice.CloseSoundKitId = choiceResult.Read(3); - choice.Duration = choiceResult.Read(4); + choice.Duration = TimeSpan.FromSeconds(choiceResult.Read(4)); choice.Question = choiceResult.Read(5); choice.PendingChoiceText = choiceResult.Read(6); - choice.HideWarboardHeader = choiceResult.Read(7); - choice.KeepOpenAfterChoice = choiceResult.Read(8); + choice.InfiniteRange = choiceResult.Read(7); + choice.HideWarboardHeader = choiceResult.Read(8); + choice.KeepOpenAfterChoice = choiceResult.Read(9); + choice.ShowChoicesAsList = choiceResult.Read(10); + choice.ForceDontShowChoicesAsList = choiceResult.Read(11); + + if (!choiceResult.IsNull(12)) + choice.MaxResponses = choiceResult.Read(12); + + choice.ScriptId = GetScriptId(choiceResult.Read(13)); _playerChoices[choice.ChoiceId] = choice; } while (choiceResult.NextRow()); - // 0 1 2 3 4 5 - SQLResult responses = DB.World.Query("SELECT ChoiceId, ResponseId, ResponseIdentifier, ChoiceArtFileId, Flags, WidgetSetID, " + + // 0 1 2 3 4 5 + SQLResult responses = DB.World.Query("SELECT ChoiceId, ResponseId, NULL, ChoiceArtFileId, Flags, WidgetSetID, " + //6 7 8 9 10 11 12 13 14 15 16 "UiTextureAtlasElementID, SoundKitID, GroupID, UiTextureKitID, Answer, Header, SubHeader, ButtonTooltip, Description, Confirmation, RewardQuestID " + "FROM playerchoice_response ORDER BY `Index` ASC"); @@ -10206,9 +10217,8 @@ namespace Game PlayerChoiceResponse response = new(); response.ResponseId = responseId; - response.ResponseIdentifier = responses.Read(2); response.ChoiceArtFileId = responses.Read(3); - response.Flags = responses.Read(4); + response.Flags = (PlayerChoiceResponseFlags)responses.Read(4); response.WidgetSetID = responses.Read(5); response.UiTextureAtlasElementID = responses.Read(6); response.SoundKitID = responses.Read(7); @@ -12665,104 +12675,6 @@ namespace Game public StringArray Confirmation = new((int)Locale.Total); } - public class PlayerChoiceResponseRewardItem - { - public PlayerChoiceResponseRewardItem() { } - public PlayerChoiceResponseRewardItem(uint id, List bonusListIDs, int quantity) - { - Id = id; - BonusListIDs = bonusListIDs; - Quantity = quantity; - } - - public uint Id; - public List BonusListIDs = new(); - public int Quantity; - } - - public class PlayerChoiceResponseRewardEntry - { - public PlayerChoiceResponseRewardEntry(uint id, int quantity) - { - Id = id; - Quantity = quantity; - } - - public uint Id; - public int Quantity; - } - - public class PlayerChoiceResponseReward - { - public int TitleId; - public int PackageId; - public int SkillLineId; - public uint SkillPointCount; - public uint ArenaPointCount; - public uint HonorPointCount; - public ulong Money; - public uint Xp; - - public List Items = new(); - public List Currency = new(); - public List Faction = new(); - public List ItemChoices = new(); - } - - public struct PlayerChoiceResponseMawPower - { - public int TypeArtFileID; - public int? Rarity; - public int SpellID; - public int MaxStacks; - } - - public class PlayerChoiceResponse - { - public int ResponseId; - public ushort ResponseIdentifier; - public int ChoiceArtFileId; - public int Flags; - public uint WidgetSetID; - public uint UiTextureAtlasElementID; - public uint SoundKitID; - public byte GroupID; - public int UiTextureKitID; - public string Answer; - public string Header; - public string SubHeader; - public string ButtonTooltip; - public string Description; - public string Confirmation; - public PlayerChoiceResponseReward Reward; - public uint? RewardQuestID; - public PlayerChoiceResponseMawPower? MawPower; - } - - public class PlayerChoice - { - public int ChoiceId; - public int UiTextureKitId; - public uint SoundKitId; - public uint CloseSoundKitId; - public long Duration; - public string Question; - public string PendingChoiceText; - public List Responses = new(); - public bool HideWarboardHeader; - public bool KeepOpenAfterChoice; - - public PlayerChoiceResponse GetResponse(int responseId) - { - return Responses.Find(playerChoiceResponse => playerChoiceResponse.ResponseId == responseId); - } - - public PlayerChoiceResponse GetResponseByIdentifier(int responseIdentifier) - { - return Responses.Find(playerChoiceResponse => playerChoiceResponse.ResponseIdentifier == responseIdentifier); - } - } - public class ClassAvailability { public byte ClassID; diff --git a/Source/Game/Globals/PlayerChoice.cs b/Source/Game/Globals/PlayerChoice.cs new file mode 100644 index 000000000..add76486b --- /dev/null +++ b/Source/Game/Globals/PlayerChoice.cs @@ -0,0 +1,119 @@ +// Copyright (c) CypherCore All rights reserved. +// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +namespace Game +{ + public class PlayerChoiceResponseRewardItem + { + public uint Id; + public List BonusListIDs = new(); + public int Quantity; + + public PlayerChoiceResponseRewardItem() { } + public PlayerChoiceResponseRewardItem(uint id, List bonusListIDs, int quantity) + { + Id = id; + BonusListIDs = bonusListIDs; + Quantity = quantity; + } + } + + public class PlayerChoiceResponseRewardEntry + { + public uint Id; + public int Quantity; + + public PlayerChoiceResponseRewardEntry(uint id, int quantity) + { + Id = id; + Quantity = quantity; + } + } + + public class PlayerChoiceResponseReward + { + public int TitleId; + public int PackageId; + public int SkillLineId; + public uint SkillPointCount; + public uint ArenaPointCount; + public uint HonorPointCount; + public ulong Money; + public uint Xp; + + public List Items = new(); + public List Currency = new(); + public List Faction = new(); + public List ItemChoices = new(); + } + + public struct PlayerChoiceResponseMawPower + { + public int TypeArtFileID; + public int? Rarity; + public int SpellID; + public int MaxStacks; + } + + public enum PlayerChoiceResponseFlags + { + None = 0x000, + DisabledButton = 0x001, // Disables single button + DesaturateArt = 0x002, + DisabledOption = 0x004, // Disables the entire group of options + ConsolidateWidgets = 0x020, + ShowCheckmark = 0x040, + HideButtonShowText = 0x080, + Selected = 0x100, + } + + public class PlayerChoiceResponse + { + public int ResponseId; + public int ChoiceArtFileId; + public PlayerChoiceResponseFlags Flags; + public uint WidgetSetID; + public uint UiTextureAtlasElementID; + public uint SoundKitID; + public byte GroupID; + public int UiTextureKitID; + public string Answer; + public string Header; + public string SubHeader; + public string ButtonTooltip; + public string Description; + public string Confirmation; + public PlayerChoiceResponseReward Reward; + public uint? RewardQuestID; + public PlayerChoiceResponseMawPower? MawPower; + } + + public class PlayerChoice + { + public int ChoiceId; + public int UiTextureKitId; + public uint SoundKitId; + public uint CloseSoundKitId; + public TimeSpan Duration; + public string Question; + public string PendingChoiceText; + public List Responses = new(); + public bool InfiniteRange; + public bool HideWarboardHeader; + public bool KeepOpenAfterChoice; + public bool ShowChoicesAsList; + public bool ForceDontShowChoicesAsList; + + public uint MaxResponses; + + public uint ScriptId; + + public PlayerChoiceResponse GetResponse(int responseId) + { + return Responses.Find(playerChoiceResponse => playerChoiceResponse.ResponseId == responseId); + } + } +} diff --git a/Source/Game/Handlers/NPCHandler.cs b/Source/Game/Handlers/NPCHandler.cs index 1a75eca34..223a46b05 100644 --- a/Source/Game/Handlers/NPCHandler.cs +++ b/Source/Game/Handlers/NPCHandler.cs @@ -91,7 +91,7 @@ namespace Game _player.PlayerTalkClass.GetInteractionData().Reset(); _player.PlayerTalkClass.GetInteractionData().SourceGuid = npc.GetGUID(); - _player.PlayerTalkClass.GetInteractionData().TrainerId = trainerId; + _player.PlayerTalkClass.GetInteractionData().SetTrainerId(trainerId); trainer.SendSpells(npc, _player, GetSessionDbLocaleIndex()); } @@ -112,7 +112,7 @@ namespace Game if (_player.PlayerTalkClass.GetInteractionData().SourceGuid != packet.TrainerGUID) return; - if (_player.PlayerTalkClass.GetInteractionData().TrainerId != packet.TrainerID) + if (_player.PlayerTalkClass.GetInteractionData().GetTrainerId() != packet.TrainerID) return; // check present spell in trainer spell list diff --git a/Source/Game/Handlers/QuestHandler.cs b/Source/Game/Handlers/QuestHandler.cs index 9bd4d87a7..ff2246e88 100644 --- a/Source/Game/Handlers/QuestHandler.cs +++ b/Source/Game/Handlers/QuestHandler.cs @@ -10,6 +10,7 @@ using Game.Networking.Packets; using Game.DataStorage; using System.Collections.Generic; using System; +using Game.Misc; namespace Game { @@ -738,9 +739,29 @@ namespace Game [WorldPacketHandler(ClientOpcodes.ChoiceResponse)] void HandlePlayerChoiceResponse(ChoiceResponse choiceResponse) { - if (_player.PlayerTalkClass.GetInteractionData().PlayerChoiceId != choiceResponse.ChoiceID) + PlayerChoiceData playerChoiceData = _player.PlayerTalkClass.GetInteractionData().GetPlayerChoice(); + if (playerChoiceData == null) { - Log.outError(LogFilter.Player, $"Error in CMSG_CHOICE_RESPONSE: {GetPlayerInfo()} tried to respond to invalid player choice {choiceResponse.ChoiceID} (allowed {_player.PlayerTalkClass.GetInteractionData().PlayerChoiceId}) (possible packet-hacking detected)"); + Log.outError(LogFilter.Player, $"Error in CMSG_CHOICE_RESPONSE: {GetPlayerInfo()} tried to respond to invalid player choice {choiceResponse.ChoiceID} (none allowed)"); + return; + } + + if (playerChoiceData.GetChoiceId() != choiceResponse.ChoiceID) + { + Log.outError(LogFilter.Player, $"Error in CMSG_CHOICE_RESPONSE: {GetPlayerInfo()} tried to respond to invalid player choice {choiceResponse.ChoiceID} ({playerChoiceData.GetChoiceId()} allowed)"); + return; + } + + if (playerChoiceData.GetExpireTime().HasValue && playerChoiceData.GetExpireTime() < GameTime.GetSystemTime()) + { + Log.outError(LogFilter.Player, $"Error in CMSG_CHOICE_RESPONSE: {GetPlayerInfo()} tried to respond to expired player choice {choiceResponse.ChoiceID})"); + return; + } + + uint? responseId = playerChoiceData.FindIdByClientIdentifier((ushort)choiceResponse.ResponseIdentifier); + if (!responseId.HasValue) + { + Log.outError(LogFilter.Player, $"Error in CMSG_CHOICE_RESPONSE: {GetPlayerInfo()} tried to select invalid player choice response identifier {choiceResponse.ResponseIdentifier}"); return; } @@ -748,52 +769,21 @@ namespace Game if (playerChoice == null) return; - PlayerChoiceResponse playerChoiceResponse = playerChoice.GetResponseByIdentifier(choiceResponse.ResponseIdentifier); + PlayerChoiceResponse playerChoiceResponse = playerChoice.GetResponse((ushort)responseId); if (playerChoiceResponse == null) { - Log.outError(LogFilter.Player, $"Error in CMSG_CHOICE_RESPONSE: {GetPlayerInfo()} tried to select invalid player choice response {choiceResponse.ResponseIdentifier} (possible packet-hacking detected)"); + Log.outError(LogFilter.Player, $"Error in CMSG_CHOICE_RESPONSE: {GetPlayerInfo()} tried to select invalid player choice response {responseId}"); return; } - Global.ScriptMgr.OnPlayerChoiceResponse(GetPlayer(), (uint)choiceResponse.ChoiceID, (uint)choiceResponse.ResponseIdentifier); - - if (playerChoiceResponse.Reward != null) + if (playerChoiceResponse.Flags.HasFlag(PlayerChoiceResponseFlags.DisabledButton | PlayerChoiceResponseFlags.DisabledOption | PlayerChoiceResponseFlags.HideButtonShowText)) { - var reward = playerChoiceResponse.Reward; - if (reward.TitleId != 0) - _player.SetTitle(CliDB.CharTitlesStorage.LookupByKey(reward.TitleId), false); - - if (reward.PackageId != 0) - _player.RewardQuestPackage((uint)reward.PackageId, ItemContext.None); - - if (reward.SkillLineId != 0 && _player.HasSkill((SkillType)reward.SkillLineId)) - _player.UpdateSkillPro((uint)reward.SkillLineId, 1000, reward.SkillPointCount); - - if (reward.HonorPointCount != 0) - _player.AddHonorXP(reward.HonorPointCount); - - if (reward.Money != 0) - _player.ModifyMoney((long)reward.Money, false); - - if (reward.Xp != 0) - _player.GiveXP(reward.Xp, null, 0.0f); - - foreach (PlayerChoiceResponseRewardItem item in reward.Items) - { - List dest = new(); - if (_player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.Id, (uint)item.Quantity) == InventoryResult.Ok) - { - Item newItem = _player.StoreNewItem(dest, item.Id, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(item.Id), null, ItemContext.QuestReward, item.BonusListIDs); - _player.SendNewItem(newItem, (uint)item.Quantity, true, false); - } - } - - foreach (PlayerChoiceResponseRewardEntry currency in reward.Currency) - _player.ModifyCurrency(currency.Id, currency.Quantity); - - foreach (PlayerChoiceResponseRewardEntry faction in reward.Faction) - _player.GetReputationMgr().ModifyReputation(CliDB.FactionStorage.LookupByKey(faction.Id), faction.Quantity); + Log.outError(LogFilter.Player, $"Error in CMSG_CHOICE_RESPONSE: {GetPlayerInfo()} tried to select disabled player choice response {responseId}"); + return; } + + Global.ScriptMgr.OnPlayerChoiceResponse(Global.ObjAccessor.GetWorldObject(_player, _player.PlayerTalkClass.GetInteractionData().SourceGuid), _player, + playerChoice, playerChoiceResponse, (ushort)choiceResponse.ResponseIdentifier); } [WorldPacketHandler(ClientOpcodes.UiMapQuestLinesRequest)] diff --git a/Source/Game/Networking/Packets/QuestPackets.cs b/Source/Game/Networking/Packets/QuestPackets.cs index 848b4ff43..fad0d8fdc 100644 --- a/Source/Game/Networking/Packets/QuestPackets.cs +++ b/Source/Game/Networking/Packets/QuestPackets.cs @@ -912,14 +912,14 @@ namespace Game.Networking.Packets _worldPacket.WriteUInt32(SoundKitID); _worldPacket.WriteUInt32(CloseUISoundKitID); _worldPacket.WriteUInt8(NumRerolls); - _worldPacket.WriteInt64(Duration); + _worldPacket.WriteInt64(ExpireTime); _worldPacket.WriteBits(Question.GetByteCount(), 8); _worldPacket.WriteBits(PendingChoiceText.GetByteCount(), 8); _worldPacket.WriteBit(InfiniteRange); _worldPacket.WriteBit(HideWarboardHeader); _worldPacket.WriteBit(KeepOpenAfterChoice); - _worldPacket.WriteBit(Unknown_1115_1); - _worldPacket.WriteBit(Unknown_1115_2); + _worldPacket.WriteBit(ShowChoicesAsList); + _worldPacket.WriteBit(ForceDontShowChoicesAsList); _worldPacket.FlushBits(); foreach (PlayerChoiceResponse response in Responses) @@ -935,15 +935,15 @@ namespace Game.Networking.Packets public uint SoundKitID; public uint CloseUISoundKitID; public byte NumRerolls; - public long Duration; + public long ExpireTime; public string Question; public string PendingChoiceText; public List Responses = new(); public bool InfiniteRange; public bool HideWarboardHeader; public bool KeepOpenAfterChoice; - public bool Unknown_1115_1; - public bool Unknown_1115_2; + public bool ShowChoicesAsList; + public bool ForceDontShowChoicesAsList; } class ChoiceResponse : ClientPacket @@ -1502,7 +1502,7 @@ namespace Game.Networking.Packets public int Unused901_1; public int TypeArtFileID; public int? Rarity; - public int Unused901_2; + public int BorderUiTextureAtlasMemberID; public int SpellID; public int MaxStacks; @@ -1510,7 +1510,7 @@ namespace Game.Networking.Packets { data.WriteInt32(Unused901_1); data.WriteInt32(TypeArtFileID); - data.WriteInt32(Unused901_2); + data.WriteInt32(BorderUiTextureAtlasMemberID); data.WriteInt32(SpellID); data.WriteInt32(MaxStacks); data.WriteBit(Rarity.HasValue); diff --git a/Source/Game/Scripting/CoreScripts.cs b/Source/Game/Scripting/CoreScripts.cs index a3eda1baa..f06c38346 100644 --- a/Source/Game/Scripting/CoreScripts.cs +++ b/Source/Game/Scripting/CoreScripts.cs @@ -703,9 +703,6 @@ namespace Game.Scripting // Called when a player completes a movie public virtual void OnMovieComplete(Player player, uint movieId) { } - - // Called when a player choose a response from a PlayerChoice - public virtual void OnPlayerChoiceResponse(Player player, uint choiceId, uint responseId) { } } public class AccountScript : ScriptObject @@ -892,4 +889,16 @@ namespace Game.Scripting // Called when a game event is triggered public virtual void OnTrigger(WorldObject obj, WorldObject invoker, uint eventId) { } } + + public class PlayerChoiceScript : ScriptObject + { + public PlayerChoiceScript(string name) : base(name) + { + Global.ScriptMgr.AddScript(this); + } + + public override bool IsDatabaseBound() { return true; } + + public virtual void OnResponse(WorldObject obj, Player player, PlayerChoice choice, PlayerChoiceResponse response, ushort clientIdentifier) { } + } } diff --git a/Source/Game/Scripting/ScriptManager.cs b/Source/Game/Scripting/ScriptManager.cs index 15ad96ff5..b0527680c 100644 --- a/Source/Game/Scripting/ScriptManager.cs +++ b/Source/Game/Scripting/ScriptManager.cs @@ -915,9 +915,12 @@ namespace Game.Scripting { ForEach(p => p.OnMovieComplete(player, movieId)); } - public void OnPlayerChoiceResponse(Player player, uint choiceId, uint responseId) + public void OnPlayerChoiceResponse(WorldObject obj, Player player, PlayerChoice choice, PlayerChoiceResponse response, ushort clientIdentifier) { - ForEach(p => p.OnPlayerChoiceResponse(player, choiceId, responseId)); + Cypher.Assert(choice != null); + Cypher.Assert(response != null); + + ForEach(p => p.OnResponse(obj, player, choice, response, clientIdentifier)); } // Account