From 3d97567ee913d37ce595b3533547c66379ae6055 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 10 Jan 2025 17:25:27 -0800 Subject: [PATCH 01/11] Reduce duplicated data in JobGetJobChangeListHandler so it doesn't hit the packet size limit. --- .../Handler/JobGetJobChangeListHandler.cs | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/JobGetJobChangeListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/JobGetJobChangeListHandler.cs index e9ce8bf1e..e80675af0 100644 --- a/Arrowgene.Ddon.GameServer/Handler/JobGetJobChangeListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/JobGetJobChangeListHandler.cs @@ -1,16 +1,15 @@ -using System.Linq; +using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; -using Arrowgene.Ddon.Shared.Network; -using Arrowgene.Logging; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Logging; using System.Collections.Generic; +using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { - public class JobGetJobChangeListHandler : StructurePacketHandler + public class JobGetJobChangeListHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(JobGetJobChangeListHandler)); @@ -19,35 +18,49 @@ public JobGetJobChangeListHandler(DdonGameServer server) : base(server) { } - public override void Handle(GameClient client, StructurePacket packet) + public override S2CJobGetJobChangeListRes Handle(GameClient client, C2SJobGetJobChangeListReq request) { - S2CJobGetJobChangeListRes jobChangeList = new S2CJobGetJobChangeListRes(); - jobChangeList.JobChangeInfo = this.buildJobChangeInfoList(client.Character); - jobChangeList.JobReleaseInfo = this.buildJobReleaseInfoList(client.Character); - jobChangeList.PawnJobChangeInfoList = client.Character.Pawns - .Select((pawn, index) => new CDataPawnJobChangeInfo() - { - SlotNo = (byte) (index+1), - PawnId = pawn.PawnId, - JobChangeInfoList = this.buildJobChangeInfoList(pawn), - JobReleaseInfoList = this.buildJobReleaseInfoList(pawn) - }) - .ToList(); - jobChangeList.PlayPointList = client.Character.PlayPointList; - client.Send(jobChangeList); + S2CJobGetJobChangeListRes res = new S2CJobGetJobChangeListRes(); + res.JobChangeInfo = buildJobChangeInfoList(client.Character); + res.JobReleaseInfo = buildJobReleaseInfoList(client.Character); + + if (client.Party is not null) + { + var partyPawns = client.Party.Members.Where(x => + x is PawnPartyMember pawnMember + && pawnMember.Pawn.PawnType == PawnType.Main + && pawnMember.Pawn.CharacterId == client.Character.CharacterId) + .Select(x => ((PawnPartyMember)x).Pawn) + .ToList(); + + res.PawnJobChangeInfoList = client.Character.Pawns + .Where(x => partyPawns.Contains(x)) + .Select((pawn, index) => new CDataPawnJobChangeInfo() + { + SlotNo = (byte)(index + 1), + PawnId = pawn.PawnId, + JobChangeInfoList = buildJobChangeInfoList(pawn), + JobReleaseInfoList = buildJobReleaseInfoList(pawn) + }) + .ToList(); + } + res.PlayPointList = client.Character.PlayPointList; + + return res; } private List buildJobChangeInfoList(CharacterCommon common) { return common.CharacterJobDataList - .Select(jobData => this.getJobChangeInfo(common, jobData.Job)) + .Select(jobData => getJobChangeInfo(common, jobData.Job)) .ToList(); } private List buildJobReleaseInfoList(CharacterCommon common) { return ((JobId[]) JobId.GetValues(typeof(JobId))) - .Select(jobId => this.getJobChangeInfo(common, jobId)) + .Where(x => !common.CharacterJobDataList.Any(job => job.Job == x)) + .Select(jobId => getJobChangeInfo(common, jobId)) .ToList(); } @@ -58,6 +71,7 @@ private CDataJobChangeInfo getJobChangeInfo(CharacterCommon common, JobId jobId) JobId = jobId, EquipItemList = common.EquipmentTemplate.EquipmentAsCDataEquipItemInfo(jobId, EquipType.Performance) .Union(common.EquipmentTemplate.EquipmentAsCDataEquipItemInfo(jobId, EquipType.Visual)) + .Where(x => x.ItemId > 0) // Strip empty slots. .ToList() }; } From 5edc07a4f0044dfde0e17c1b6348faa87da4dee6 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 10 Jan 2025 19:18:13 -0800 Subject: [PATCH 02/11] Update ItemConsumeStorageItemHandler and stop it from squelching exceptions; Revert InstanceGetGatheringItemHandler so it stops breaking other things. --- .../InstanceGetGatheringItemHandler.cs | 36 ++++++------ .../Handler/ItemConsumeStorageItemHandler.cs | 55 +++++++++---------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs index 8d83b1da4..667849906 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs @@ -5,6 +5,7 @@ using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using System.Collections.Generic; @@ -52,9 +53,16 @@ public override PacketQueue Handle(GameClient client, C2SInstanceGetGatheringIte } }); + client.Enqueue(ntc, packetQueue); + + S2CInstanceGetGatheringItemRes res = new S2CInstanceGetGatheringItemRes(); + res.LayoutId = request.LayoutId; + res.PosId = request.PosId; + res.GatheringItemGetRequestList = request.GatheringItemGetRequestList; + client.Enqueue(res, packetQueue); + if (request.EquipToCharacter == 1) { - var itemInfo = ClientItemInfo.GetInfoForItemId(Server.AssetRepository.ClientItemInfos, ntc.UpdateItemList[0].ItemList.ItemId); var equipInfo = new CDataCharacterEquipInfo() { @@ -63,23 +71,19 @@ public override PacketQueue Handle(GameClient client, C2SInstanceGetGatheringIte EquipType = EquipType.Performance, }; - packetQueue.AddRange(Server.EquipManager.HandleChangeEquipList( - Server, - client, - client.Character, - new List() { equipInfo }, - ItemNoticeType.GatherEquipItem, - new List() { StorageType.ItemBagEquipment })); + Server.Database.ExecuteInTransaction(connection => + { + packetQueue.AddRange(Server.EquipManager.HandleChangeEquipList( + Server, + client, + client.Character, + new List() { equipInfo }, + ItemNoticeType.GatherEquipItem, + new List() { StorageType.ItemBagEquipment }, + connection)); + }); } - client.Enqueue(ntc, packetQueue); - - S2CInstanceGetGatheringItemRes res = new S2CInstanceGetGatheringItemRes(); - res.LayoutId = request.LayoutId; - res.PosId = request.PosId; - res.GatheringItemGetRequestList = request.GatheringItemGetRequestList; - client.Enqueue(res, packetQueue); - return packetQueue; } } diff --git a/Arrowgene.Ddon.GameServer/Handler/ItemConsumeStorageItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ItemConsumeStorageItemHandler.cs index e65175082..c788d9c6b 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ItemConsumeStorageItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ItemConsumeStorageItemHandler.cs @@ -1,14 +1,12 @@ -using System; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; namespace Arrowgene.Ddon.GameServer.Handler { - public class ItemConsumeStorageItemHandler : GameStructurePacketHandler + public class ItemConsumeStorageItemHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ItemConsumeStorageItemHandler)); @@ -16,40 +14,39 @@ public ItemConsumeStorageItemHandler(DdonGameServer server) : base(server) { } - public override void Handle(GameClient client, StructurePacket req) + public override S2CItemConsumeStorageItemRes Handle(GameClient client, C2SItemConsumeStorageItemReq request) { S2CItemConsumeStorageItemRes res = new S2CItemConsumeStorageItemRes(); - try + + S2CItemUpdateCharacterItemNtc ntc = new S2CItemUpdateCharacterItemNtc() { - S2CItemUpdateCharacterItemNtc ntc = new S2CItemUpdateCharacterItemNtc() - { - UpdateType = ItemNoticeType.ConsumeBag - }; + UpdateType = ItemNoticeType.ConsumeBag + }; - Server.Database.ExecuteInTransaction(connection => + Server.Database.ExecuteInTransaction(connection => + { + foreach (CDataStorageItemUIDList consumeItem in request.ConsumeItemList) { - foreach (CDataStorageItemUIDList consumeItem in req.Structure.ConsumeItemList) + CDataItemUpdateResult itemUpdate; + if (consumeItem.SlotNo == 0) { - CDataItemUpdateResult itemUpdate; - if (consumeItem.SlotNo == 0) - { - itemUpdate = Server.ItemManager.ConsumeItemByUId(Server, client.Character, consumeItem.StorageType, consumeItem.ItemUId, consumeItem.Num, connection); - } - else - { - itemUpdate = Server.ItemManager.ConsumeItemInSlot(Server, client.Character, consumeItem.StorageType, consumeItem.SlotNo, consumeItem.Num, connection); - } - ntc.UpdateItemList.Add(itemUpdate); + itemUpdate = Server.ItemManager.ConsumeItemByUId(Server, client.Character, consumeItem.StorageType, consumeItem.ItemUId, consumeItem.Num, connection) + ?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_ITEM_NOT_FOUND, + $"Cannot find item in {consumeItem.StorageType}, UID {consumeItem.ItemUId}"); } - }); + else + { + itemUpdate = Server.ItemManager.ConsumeItemInSlot(Server, client.Character, consumeItem.StorageType, consumeItem.SlotNo, consumeItem.Num, connection) + ?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_ITEM_NOT_FOUND, + $"Cannot find item in {consumeItem.StorageType}, slot {consumeItem.SlotNo}"); + } + + ntc.UpdateItemList.Add(itemUpdate); + } + }); - client.Send(ntc); - } - catch(Exception _) - { - res.Error = (uint)ErrorCode.ERROR_CODE_INSTANCE_AREA_GATHERING_ITEM_ERROR; - } - client.Send(res); + client.Send(ntc); + return res; } } } From 5d6dac65ad76d6cbff6c12ebfa1d0d6438ffbfd0 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 10 Jan 2025 19:56:30 -0800 Subject: [PATCH 03/11] Yet another attempt to fix the server list tracking under load. --- Arrowgene.Ddon.GameServer/RpcManager.cs | 49 ++++++++++++------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/RpcManager.cs b/Arrowgene.Ddon.GameServer/RpcManager.cs index dd5e8269f..2779869b5 100644 --- a/Arrowgene.Ddon.GameServer/RpcManager.cs +++ b/Arrowgene.Ddon.GameServer/RpcManager.cs @@ -8,6 +8,7 @@ using Arrowgene.Ddon.Shared.Model.Rpc; using Arrowgene.Logging; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -21,30 +22,25 @@ public class RpcManager { private class RpcTrackingMap : Dictionary { - public DateTime TimeStamp { get; set; } + public readonly DateTime TimeStamp; public RpcTrackingMap() : base() { TimeStamp = DateTime.Now; } - public bool Update(DateTime newTimestamp, List characterData) + public RpcTrackingMap(List characterData) + : base(characterData.ToDictionary(key => key.CharacterId, val => val)) { - if (newTimestamp <= TimeStamp) return false; - lock (this) - { - TimeStamp = newTimestamp; - - this.Clear(); - foreach (var character in characterData) - { - this[character.CharacterId] = character; - } - } - return true; + TimeStamp = DateTime.Now; } - } + public RpcTrackingMap(List characterData, DateTime timeStamp) + : base(characterData.ToDictionary(key => key.CharacterId, val => val)) + { + TimeStamp = timeStamp; + } + } private static readonly ServerLogger Logger = LogProvider.Logger(typeof(RpcManager)); @@ -58,7 +54,7 @@ public bool Update(DateTime newTimestamp, List characterData) private readonly DdonGameServer Server; private readonly Dictionary ChannelInfo; - private readonly Dictionary CharacterTrackingMap; + private readonly ConcurrentDictionary CharacterTrackingMap; public RpcManager(DdonGameServer server) { @@ -113,10 +109,7 @@ public CDataGameServerListInfo ServerListInfo(ushort channelId) } else { - lock (CharacterTrackingMap[channelId]) - { - info.LoginNum = (uint)CharacterTrackingMap[channelId].Count; - } + info.LoginNum = (uint)CharacterTrackingMap[channelId].Count; } info.TrafficName = GetTrafficName(info.LoginNum); @@ -210,9 +203,9 @@ public void AnnounceClan(uint clanId, string route, RpcInternalCommand command, #region Player Tracking public ushort FindPlayerByName(string firstName, string lastName) { - foreach ((ushort channelId, var channelMembers) in CharacterTrackingMap) + lock (CharacterTrackingMap) { - lock(channelMembers) + foreach ((ushort channelId, var channelMembers) in CharacterTrackingMap) { foreach (var player in channelMembers.Values) { @@ -228,9 +221,9 @@ public ushort FindPlayerByName(string firstName, string lastName) public ushort FindPlayerById(uint characterId) { - foreach ((ushort channelId, var channelMembers) in CharacterTrackingMap) + lock (CharacterTrackingMap) { - lock (channelMembers) + foreach ((ushort channelId, var channelMembers) in CharacterTrackingMap) { if (channelMembers.ContainsKey(characterId)) { @@ -251,7 +244,7 @@ public void AnnouncePlayerList(Character exception = null) } Logger.Info($"Announcing player list for channel {Server.Id} with {rpcCharacterDatas.Count} players over RPC."); AnnounceOthers("internal/command", RpcInternalCommand.NotifyPlayerList, rpcCharacterDatas); - CharacterTrackingMap[(ushort) Server.Id].Update(DateTime.Now, rpcCharacterDatas); + CharacterTrackingMap[(ushort) Server.Id] = new RpcTrackingMap(rpcCharacterDatas); } public void ReceivePlayerList(ushort channelId, DateTime timestamp, List characterDatas) @@ -259,7 +252,11 @@ public void ReceivePlayerList(ushort channelId, DateTime timestamp, List CharacterTrackingMap[channelId].TimeStamp) + { + CharacterTrackingMap[channelId] = new RpcTrackingMap(characterDatas, timestamp); + } + else { Logger.Error($"Out of date character list discarded for channel ID {channelId}"); } From 7c13d5a2152a1401ea51eaa84290e7301760c6cc Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 10 Jan 2025 20:22:51 -0800 Subject: [PATCH 04/11] Prevent null reference in naive lobby handling. --- Arrowgene.Ddon.GameServer/Characters/HubManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Arrowgene.Ddon.GameServer/Characters/HubManager.cs b/Arrowgene.Ddon.GameServer/Characters/HubManager.cs index e0504607c..bc1edc834 100644 --- a/Arrowgene.Ddon.GameServer/Characters/HubManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/HubManager.cs @@ -196,6 +196,10 @@ private void NaiveLobbyHandling(GameClient client, uint sourceStageId) foreach (GameClient otherClient in Server.ClientLookup.GetAll()) { + if (otherClient.Character is null) + { + continue; + } targetClients.Add(otherClient); gatherClients.Add(otherClient); } From 81856432c8d15cda483cfba7391350e21dda09b4 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 10 Jan 2025 20:25:34 -0800 Subject: [PATCH 05/11] Item exceptions extend ResponseErrorException so they can return proper codes to the player. --- Arrowgene.Ddon.GameServer/Characters/ItemManager.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs b/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs index f9d6d7940..8cc90494e 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs @@ -952,24 +952,26 @@ public void SetSafetySetting(GameClient client, Character character, List Date: Sat, 11 Jan 2025 01:22:07 -0800 Subject: [PATCH 06/11] Explicit overflow guards in ItemManager::GatherItem --- .../Characters/ItemManager.cs | 32 +++++++++++++++---- .../GatheringItems/InstanceDropItemManager.cs | 7 ++-- .../Handler/InstanceGetDropItemHandler.cs | 26 +++++++-------- .../InstanceGetGatheringItemHandler.cs | 2 +- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs b/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs index 8cc90494e..683629cfe 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs @@ -182,21 +182,41 @@ public bool IsItemWalletPoint(uint itemId) // old = 'プレイポイント' // new = 'Play Point' - public void GatherItem(DdonGameServer server, Character character, S2CItemUpdateCharacterItemNtc ntc, InstancedGatheringItem gatheringItem, uint pickedGatherItems, DbConnection? connectionIn = null) + public void GatherItem(Character character, S2CItemUpdateCharacterItemNtc ntc, InstancedGatheringItem gatheringItem, uint pickedGatherItems, DbConnection? connectionIn = null) { - if(ItemIdWalletTypeAndQuantity.ContainsKey(gatheringItem.ItemId)) { + if (ItemIdWalletTypeAndQuantity.ContainsKey(gatheringItem.ItemId)) + { var walletTypeAndQuantity = ItemIdWalletTypeAndQuantity[gatheringItem.ItemId]; uint totalQuantityToAdd = walletTypeAndQuantity.Quantity * gatheringItem.ItemNum; ntc.UpdateWalletList.Add( - server.WalletManager.AddToWallet(character, walletTypeAndQuantity.Type, totalQuantityToAdd, 0, connectionIn + _Server.WalletManager.AddToWallet(character, walletTypeAndQuantity.Type, totalQuantityToAdd, 0, connectionIn )); + if (pickedGatherItems > gatheringItem.ItemNum) + { + throw new ResponseErrorException(ErrorCode.ERROR_CODE_ITEM_INVALID_ITEM_NUM, + $"Overflow error, trying to remove {pickedGatherItems} from stack of ID {gatheringItem.ItemId} x{gatheringItem.ItemNum}", + critical:true); + } + gatheringItem.ItemNum -= pickedGatherItems; - } else { - List results = AddItem(server, character, true, gatheringItem.ItemId, pickedGatherItems, connectionIn:connectionIn); + } + else + { + List results = AddItem(_Server, character, true, gatheringItem.ItemId, pickedGatherItems, connectionIn:connectionIn); ntc.UpdateItemList.AddRange(results); - gatheringItem.ItemNum -= (uint) results.Select(result => result.UpdateItemNum).Sum(); + + uint totalRemoved = (uint)results.Select(result => result.UpdateItemNum).Sum(); + + if (totalRemoved > gatheringItem.ItemNum) + { + throw new ResponseErrorException(ErrorCode.ERROR_CODE_ITEM_INVALID_ITEM_NUM, + $"Overflow error, trying to remove {totalRemoved} from stack of ID {gatheringItem.ItemId} x{gatheringItem.ItemNum}", + critical: true); + } + + gatheringItem.ItemNum -= totalRemoved; } } diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs index a44e9f12b..56f71d336 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs @@ -18,12 +18,9 @@ protected override List FetchAssetsFromRepository(StageId stage, List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage); if(enemiesInSet != null && setId < enemiesInSet.Count) { - Enemy enemy = enemiesInSet[(int) setId]; + Enemy enemy = enemiesInSet[setId]; - if (enemy.DropsTable != null) - { - return enemy.DropsTable.Items; - } + return enemy?.DropsTable?.Items ?? new(); } return new List(); } diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs index 8dc37f634..d61c4f651 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs @@ -2,13 +2,12 @@ using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using System.Collections.Generic; namespace Arrowgene.Ddon.GameServer.Handler { - public class InstanceGetDropItemHandler : GameStructurePacketHandler + public class InstanceGetDropItemHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(InstanceGetDropItemHandler)); @@ -16,35 +15,33 @@ public InstanceGetDropItemHandler(DdonGameServer server) : base(server) { } - public override void Handle(GameClient client, StructurePacket packet) + public override S2CInstanceGetDropItemRes Handle(GameClient client, C2SInstanceGetDropItemReq request) { // This call is for when an item is claimed from a bag. It needs the drops rolled from the enemy to keep track of the items left. List items = new List(); - if (client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId)) + if (client.InstanceQuestDropManager.IsQuestDrop(request.LayoutId, request.SetId)) { items.AddRange(client.InstanceQuestDropManager.FetchEnemyLoot()); } else { - items.AddRange(client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, (int)packet.Structure.SetId)); + items.AddRange(client.InstanceDropItemManager.GetAssets(request.LayoutId, (int)request.SetId)); } // Special Event Items - items.AddRange(client.InstanceEventDropItemManager.FetchEventItems(client, packet.Structure.LayoutId, packet.Structure.SetId)); + items.AddRange(client.InstanceEventDropItemManager.FetchEventItems(client, request.LayoutId, request.SetId)); // Add Epitaph Items - items.AddRange(client.InstanceEpiDropItemManager.FetchItems(client, packet.Structure.LayoutId, packet.Structure.SetId)); + items.AddRange(client.InstanceEpiDropItemManager.FetchItems(client, request.LayoutId, request.SetId)); S2CInstanceGetDropItemRes res = new() { - LayoutId = packet.Structure.LayoutId, - SetId = packet.Structure.SetId, - GatheringItemGetRequestList = packet.Structure.GatheringItemGetRequestList + LayoutId = request.LayoutId, + SetId = request.SetId, + GatheringItemGetRequestList = request.GatheringItemGetRequestList }; - client.Send(res); - S2CItemUpdateCharacterItemNtc ntc = new S2CItemUpdateCharacterItemNtc() { UpdateType = ItemNoticeType.Drop @@ -52,14 +49,15 @@ public override void Handle(GameClient client, StructurePacket { - foreach (CDataGatheringItemGetRequest gatheringItemRequest in packet.Structure.GatheringItemGetRequestList) + foreach (CDataGatheringItemGetRequest gatheringItemRequest in request.GatheringItemGetRequestList) { InstancedGatheringItem dropItem = items[(int)gatheringItemRequest.SlotNo]; - Server.ItemManager.GatherItem(Server, client.Character, ntc, dropItem, gatheringItemRequest.Num, connection); + Server.ItemManager.GatherItem(client.Character, ntc, dropItem, gatheringItemRequest.Num, connection); } }); client.Send(ntc); + return res; } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs index 667849906..fd498abdc 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs @@ -49,7 +49,7 @@ public override PacketQueue Handle(GameClient client, C2SInstanceGetGatheringIte gatheredItem = client.InstanceGatheringItemManager.GetAssets(request.LayoutId, (int)request.PosId)[(int)gatheringItemRequest.SlotNo]; } - Server.ItemManager.GatherItem(Server, client.Character, ntc, gatheredItem, gatheringItemRequest.Num, connection); + Server.ItemManager.GatherItem(client.Character, ntc, gatheredItem, gatheringItemRequest.Num, connection); } }); From 32e3d6897edbbe358abfbf0366d5160e6310e15e Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 11 Jan 2025 01:22:19 -0800 Subject: [PATCH 07/11] Tweaks to logging to support critical ResponseErrorExceptions. --- Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs b/Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs index 31a56fc7e..22c1e9274 100644 --- a/Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs +++ b/Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs @@ -51,7 +51,7 @@ public sealed override void Handle(TClient client, StructurePacket r response.Error = (uint) ex.ErrorCode; var stringBuilder = new StringBuilder(); - stringBuilder.AppendLine($"{ex.ErrorCode} thrown when handling {typeof(TReqStruct).Name}"); + stringBuilder.AppendLine($"{(ex.Critical ? "!!CRITICAL!! " : "")}{ex.ErrorCode} thrown when handling {typeof(TReqStruct).Name}"); if (ex.Message.Length > 0) { stringBuilder.AppendLine($"\tMessage: {ex.Message}"); From ae3d34ac9d81c8fafdbfd97ed5afdac9c5642d9a Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 11 Jan 2025 13:33:02 -0800 Subject: [PATCH 08/11] Timezone issue with player list tracking, as usual. Logging tweaks. --- Arrowgene.Ddon.GameServer/RpcManager.cs | 4 ++-- Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/RpcManager.cs b/Arrowgene.Ddon.GameServer/RpcManager.cs index 2779869b5..65127a056 100644 --- a/Arrowgene.Ddon.GameServer/RpcManager.cs +++ b/Arrowgene.Ddon.GameServer/RpcManager.cs @@ -26,13 +26,13 @@ private class RpcTrackingMap : Dictionary public RpcTrackingMap() : base() { - TimeStamp = DateTime.Now; + TimeStamp = DateTime.UtcNow; } public RpcTrackingMap(List characterData) : base(characterData.ToDictionary(key => key.CharacterId, val => val)) { - TimeStamp = DateTime.Now; + TimeStamp = DateTime.UtcNow; } public RpcTrackingMap(List characterData, DateTime timeStamp) diff --git a/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs b/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs index c99650419..87aa33b25 100644 --- a/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs +++ b/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs @@ -196,7 +196,7 @@ private void RequestKick(Connection connection) if (connection.Type == ConnectionType.LoginServer) { - // Can't talk to the login server, but there's usually not a stuck connection here. + Logger.Error($"Can't kick account {connection.AccountId}; stuck at login server."); return; } @@ -210,6 +210,8 @@ private void RequestKick(Connection connection) Data = connection.AccountId }; + Logger.Info($"Attempting to auto kick account {connection.AccountId} from server {connection.ServerId}"); + var json = JsonSerializer.Serialize(wrappedObject); _ = _httpClient.PostAsync(route, new StringContent(json)); } From 5220383f099a76598ffb7b228ae7cff129de6711 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 11 Jan 2025 19:11:49 -0800 Subject: [PATCH 09/11] Fix bad constructor for C2SCraftGetCraftProductRes that was breaking error handling. --- .../Entity/PacketStructure/S2CCraftProductRes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CCraftProductRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CCraftProductRes.cs index 37a8a8866..541452d8e 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CCraftProductRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CCraftProductRes.cs @@ -14,6 +14,7 @@ public class C2SCraftGetCraftProductRes : ServerResponse public C2SCraftGetCraftProductRes() { + CraftProduct = new(); UpdateItemList = new List(); } From 6cbdd33475281ac596204a13292237ae2ad799b2 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Tue, 14 Jan 2025 00:41:06 -0800 Subject: [PATCH 10/11] Packet research: CDataGoodsParam. --- .../Handler/ShopGetShopGoodsListHandler.cs | 13 ++-- .../Entity/EntitySerializer.cs | 2 +- .../Entity/Structure/CDataGoodsParam.cs | 36 ++++----- .../Structure/CDataGoodsParamRequirement.cs | 78 +++++++++++++++++++ .../Entity/Structure/CDataGoodsParamUnk7.cs | 77 ------------------ .../Model/ShopItemUnlockCondition.cs | 56 +++++++++++++ 6 files changed, 159 insertions(+), 103 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParamRequirement.cs delete mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParamUnk7.cs create mode 100644 Arrowgene.Ddon.Shared/Model/ShopItemUnlockCondition.cs diff --git a/Arrowgene.Ddon.GameServer/Handler/ShopGetShopGoodsListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ShopGetShopGoodsListHandler.cs index 39b4c05dd..00264d949 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ShopGetShopGoodsListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ShopGetShopGoodsListHandler.cs @@ -1,11 +1,10 @@ using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; -using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; namespace Arrowgene.Ddon.GameServer.Handler { - public class ShopGetShopGoodsListHandler : GameStructurePacketHandler + public class ShopGetShopGoodsListHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ShopGetShopGoodsListHandler)); @@ -13,12 +12,12 @@ public ShopGetShopGoodsListHandler(DdonGameServer server) : base(server) { } - public override void Handle(GameClient client, StructurePacket packet) + public override S2CShopGetShopGoodsListRes Handle(GameClient client, C2SShopGetShopGoodsListReq request) { - client.Character.LastEnteredShopId = packet.Structure.ShopId; + client.Character.LastEnteredShopId = request.ShopId; - S2CShopGetShopGoodsListRes res = client.InstanceShopManager.GetAssets(packet.Structure.ShopId); - client.Send(res); + return client.InstanceShopManager.GetAssets(request.ShopId); } + } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index 6660f1611..be5ed0f02 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -204,7 +204,7 @@ static EntitySerializer() Create(new CDataGetDispelItem.Serializer()); Create(new CDataGetRewardBoxItem.Serializer()); Create(new CDataGoodsParam.Serializer()); - Create(new CDataGoodsParamUnk7.Serializer()); + Create(new CDataGoodsParamRequirement.Serializer()); Create(new CDataHasRegionBreakReward.Serializer()); Create(new CDataHistoryElement.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParam.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParam.cs index 10add56a2..8b4fdb490 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParam.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParam.cs @@ -9,7 +9,7 @@ namespace Arrowgene.Ddon.Shared.Entity.Structure public class CDataGoodsParam : ICloneable { public CDataGoodsParam() { - Unk7 = new List(); + Requirements = new(); } // PS4 fields: Index, Price, Stock, MaxStock, RequireFavorite, ItemId (all uint) @@ -17,10 +17,10 @@ public CDataGoodsParam() { public uint ItemId { get; set; } public uint Price { get; set; } public byte Stock { get; set; } // 255 for unlimited - public bool Unk4{ get; set; } - public ulong Unk5 { get; set; } - public ulong Unk6 { get; set; } - public List Unk7 { get; set; } // Requirements? + public bool HideIfReqsUnmet { get; set; } + public DateTimeOffset SalesPeriodStart { get; set; } + public DateTimeOffset SalesPeriodEnd { get; set; } + public List Requirements { get; set; } // Requirements? public object Clone() { @@ -30,10 +30,10 @@ public object Clone() ItemId = this.ItemId, Price = this.Price, Stock = this.Stock, - Unk4 = this.Unk4, - Unk5 = this.Unk5, - Unk6 = this.Unk6, - Unk7 = this.Unk7.Select(gpu7 => (CDataGoodsParamUnk7) gpu7.Clone()).ToList() + HideIfReqsUnmet = this.HideIfReqsUnmet, + SalesPeriodStart = this.SalesPeriodStart, + SalesPeriodEnd = this.SalesPeriodEnd, + Requirements = this.Requirements.Select(gpu7 => (CDataGoodsParamRequirement) gpu7.Clone()).ToList() }; } @@ -45,10 +45,10 @@ public override void Write(IBuffer buffer, CDataGoodsParam obj) WriteUInt32(buffer, obj.ItemId); WriteUInt32(buffer, obj.Price); WriteByte(buffer, obj.Stock); - WriteBool(buffer, obj.Unk4); - WriteUInt64(buffer, obj.Unk5); - WriteUInt64(buffer, obj.Unk6); - WriteEntityList(buffer, obj.Unk7); + WriteBool(buffer, obj.HideIfReqsUnmet); + WriteInt64(buffer, obj.SalesPeriodStart.ToUnixTimeSeconds()); + WriteInt64(buffer, obj.SalesPeriodEnd.ToUnixTimeSeconds()); + WriteEntityList(buffer, obj.Requirements); } public override CDataGoodsParam Read(IBuffer buffer) @@ -58,12 +58,12 @@ public override CDataGoodsParam Read(IBuffer buffer) obj.ItemId = ReadUInt32(buffer); obj.Price = ReadUInt32(buffer); obj.Stock = ReadByte(buffer); - obj.Unk4 = ReadBool(buffer); - obj.Unk5 = ReadUInt64(buffer); - obj.Unk6 = ReadUInt64(buffer); - obj.Unk7 = ReadEntityList(buffer); + obj.HideIfReqsUnmet = ReadBool(buffer); + obj.SalesPeriodStart = DateTimeOffset.FromUnixTimeSeconds(ReadInt64(buffer)); + obj.SalesPeriodEnd = DateTimeOffset.FromUnixTimeSeconds(ReadInt64(buffer)); + obj.Requirements = ReadEntityList(buffer); return obj; } } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParamRequirement.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParamRequirement.cs new file mode 100644 index 000000000..a9db97b28 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParamRequirement.cs @@ -0,0 +1,78 @@ +using System; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataGoodsParamRequirement : ICloneable + { + public uint Index { get; set; } // Maybe? + public ShopItemUnlockCondition Condition { get; set; } + public bool IgnoreRequirements { get; set; } + public uint Progress { get; set; } + public bool HideRequirementDetails { get; set; } + public uint Param1 { get; set; } + public uint Param2 { get; set; } + public uint Param3 { get; set; } + public uint Param4 { get; set; } + public uint Param5 { get; set; } + public DateTimeOffset SalesPeriodStart { get; set; } + public DateTimeOffset SalesPeriodEnd { get; set; } + + public object Clone() + { + return new CDataGoodsParamRequirement() + { + Index = this.Index, + Condition = this.Condition, + IgnoreRequirements = this.IgnoreRequirements, + Progress = this.Progress, + HideRequirementDetails = this.HideRequirementDetails, + Param1 = this.Param1, + Param2 = this.Param2, + Param3 = this.Param3, + Param4 = this.Param4, + Param5 = this.Param5, + SalesPeriodStart = this.SalesPeriodStart, + SalesPeriodEnd = this.SalesPeriodEnd + }; + } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataGoodsParamRequirement obj) + { + WriteUInt32(buffer, obj.Index); + WriteUInt32(buffer, (uint)obj.Condition); + WriteBool(buffer, obj.IgnoreRequirements); + WriteUInt32(buffer, obj.Progress); + WriteBool(buffer, obj.HideRequirementDetails); + WriteUInt32(buffer, obj.Param1); + WriteUInt32(buffer, obj.Param2); + WriteUInt32(buffer, obj.Param3); + WriteUInt32(buffer, obj.Param4); + WriteUInt32(buffer, obj.Param5); + WriteInt64(buffer, obj.SalesPeriodStart.ToUnixTimeSeconds()); + WriteInt64(buffer, obj.SalesPeriodEnd.ToUnixTimeSeconds()); + } + + public override CDataGoodsParamRequirement Read(IBuffer buffer) + { + CDataGoodsParamRequirement obj = new CDataGoodsParamRequirement(); + obj.Index = ReadUInt32(buffer); + obj.Condition = (ShopItemUnlockCondition)ReadUInt32(buffer); + obj.IgnoreRequirements = ReadBool(buffer); + obj.Progress = ReadUInt32(buffer); + obj.HideRequirementDetails = ReadBool(buffer); + obj.Param1 = ReadUInt32(buffer); + obj.Param2 = ReadUInt32(buffer); + obj.Param3 = ReadUInt32(buffer); + obj.Param4 = ReadUInt32(buffer); + obj.Param5 = ReadUInt32(buffer); + obj.SalesPeriodStart = DateTimeOffset.FromUnixTimeSeconds(ReadInt64(buffer)); + obj.SalesPeriodEnd = DateTimeOffset.FromUnixTimeSeconds(ReadInt64(buffer)); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParamUnk7.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParamUnk7.cs deleted file mode 100644 index 5797bcf98..000000000 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataGoodsParamUnk7.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using Arrowgene.Buffers; - -namespace Arrowgene.Ddon.Shared.Entity.Structure -{ - public class CDataGoodsParamUnk7 : ICloneable - { - public uint Unk0 { get; set; } - public uint Unk1 { get; set; } - public bool Unk2 { get; set; } - public uint Unk3 { get; set; } - public bool Unk4 { get; set; } - public uint Unk5 { get; set; } - public uint Unk6 { get; set; } - public uint Unk7 { get; set; } - public uint Unk8 { get; set; } - public uint Unk9 { get; set; } - public ulong Unk10 { get; set; } - public ulong Unk11 { get; set; } - - public object Clone() - { - return new CDataGoodsParamUnk7() - { - Unk0 = this.Unk0, - Unk1 = this.Unk1, - Unk2 = this.Unk2, - Unk3 = this.Unk3, - Unk4 = this.Unk4, - Unk5 = this.Unk5, - Unk6 = this.Unk6, - Unk7 = this.Unk7, - Unk8 = this.Unk8, - Unk9 = this.Unk9, - Unk10 = this.Unk10, - Unk11 = this.Unk11 - }; - } - - public class Serializer : EntitySerializer - { - public override void Write(IBuffer buffer, CDataGoodsParamUnk7 obj) - { - WriteUInt32(buffer, obj.Unk0); - WriteUInt32(buffer, obj.Unk1); - WriteBool(buffer, obj.Unk2); - WriteUInt32(buffer, obj.Unk3); - WriteBool(buffer, obj.Unk4); - WriteUInt32(buffer, obj.Unk5); - WriteUInt32(buffer, obj.Unk6); - WriteUInt32(buffer, obj.Unk7); - WriteUInt32(buffer, obj.Unk8); - WriteUInt32(buffer, obj.Unk9); - WriteUInt64(buffer, obj.Unk10); - WriteUInt64(buffer, obj.Unk11); - } - - public override CDataGoodsParamUnk7 Read(IBuffer buffer) - { - CDataGoodsParamUnk7 obj = new CDataGoodsParamUnk7(); - obj.Unk0 = ReadUInt32(buffer); - obj.Unk1 = ReadUInt32(buffer); - obj.Unk2 = ReadBool(buffer); - obj.Unk3 = ReadUInt32(buffer); - obj.Unk4 = ReadBool(buffer); - obj.Unk5 = ReadUInt32(buffer); - obj.Unk6 = ReadUInt32(buffer); - obj.Unk7 = ReadUInt32(buffer); - obj.Unk8 = ReadUInt32(buffer); - obj.Unk9 = ReadUInt32(buffer); - obj.Unk10 = ReadUInt64(buffer); - obj.Unk11 = ReadUInt64(buffer); - return obj; - } - } - } -} \ No newline at end of file diff --git a/Arrowgene.Ddon.Shared/Model/ShopItemUnlockCondition.cs b/Arrowgene.Ddon.Shared/Model/ShopItemUnlockCondition.cs new file mode 100644 index 000000000..21910ebad --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/ShopItemUnlockCondition.cs @@ -0,0 +1,56 @@ +namespace Arrowgene.Ddon.Shared.Model +{ + public enum ShopItemUnlockCondition : uint + { + /// + /// [a blank string] + /// + None = 1, + + /// + /// {Param2 -> ???} cleared with rank {Param1} or better. (Final result {Progress} rank) + /// Not sure how this is parsed, probably needs a questScheduleId for a quest in the right category. + /// + ClearWithRank = 2, + + /// + /// Defeat {Param3 -> EnemyName} x{Param1} ({Progress}/{Param1}) + /// + DefeatEnemies = 3, + + /// + /// Acquire War Mission Accumulated Points, {Param1} pt. ({Progress}/{Param1}) + /// + WarMissionPoints = 4, + + /// + /// "Currently selected job with Play point content already released." + /// + UnlockPlayPoints = 5, + + /// + /// Defeat LV.{Param2} or more {Param3 -> EnemyName} x{Param1} ({Progress}/{Param1}) + /// + DefeatEnemiesLevel = 6, + } +} + +//0,SHOP_GOODS_UNLOCK_CONDITION_1,,, ui\00_message\common\shop_goods_unlock_condition.gmd, \ui\uGUIPopDetail01.arc, uGUIPopDetail01.arc,0, +//1, SHOP_GOODS_UNLOCK_CONDITION_2,"%sの +//ランキング%d位以内に入賞","%s +//cleared with rank %d or better.",ui\00_message\common\shop_goods_unlock_condition.gmd,\ui\uGUIPopDetail01.arc,uGUIPopDetail01.arc,1, +//2, SHOP_GOODS_UNLOCK_CONDITION_3,"%sの +//%sを%d体討伐(%d/%d)","Defeat %s +//%s x%d. (%d/%d)",ui\00_message\common\shop_goods_unlock_condition.gmd,\ui\uGUIPopDetail01.arc,uGUIPopDetail01.arc,2, +//3, SHOP_GOODS_UNLOCK_CONDITION_4,"%sにて +//ウォーミッション累積ポイントを +//%s 獲得(%s/%s)","Acquire +//%s +//War Mission Accumulated Points, +//%s . (%s/%s)",ui\00_message\common\shop_goods_unlock_condition.gmd,\ui\uGUIPopDetail01.arc,uGUIPopDetail01.arc,3, +//4, SHOP_GOODS_UNLOCK_CONDITION_5,"現在選択中のジョブで +//プレイポイントのコンテンツを解放済み","Currently selected job with Play point content +//already released",ui\00_message\common\shop_goods_unlock_condition.gmd,\ui\uGUIPopDetail01.arc,uGUIPopDetail01.arc,4, +//5, SHOP_GOODS_UNLOCK_CONDITION_6,"LV.%d以上の +//%sを%d体討伐(%d/%d)","Defeat LV.%d or more +//%s x%d. (%d/%d)",ui\00_message\common\shop_goods_unlock_condition.gmd,\ui\uGUIPopDetail01.arc,uGUIPopDetail01.arc,5, From 672bae66643ccf9b462dbff5e338e2f7d01f8653 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Tue, 14 Jan 2025 01:22:13 -0800 Subject: [PATCH 11/11] Packet research: S2CBazaarGetItemListRes. Upgrade BazaarGetItemListHandler to use a transaction. --- Arrowgene.Ddon.Database/IDatabase.cs | 6 ++-- .../Sql/Core/DdonSqlDbBazaarExhibition.cs | 35 ++++++++----------- Arrowgene.Ddon.GameServer/BazaarManager.cs | 5 +-- .../Handler/BazaarGetItemListHandler.cs | 30 ++++++++-------- .../S2CBazaarGetItemListRes.cs | 13 ++++--- .../Database/DatabaseMigratorTest.cs | 4 +-- 6 files changed, 46 insertions(+), 47 deletions(-) diff --git a/Arrowgene.Ddon.Database/IDatabase.cs b/Arrowgene.Ddon.Database/IDatabase.cs index 481f80669..467bf4bdc 100644 --- a/Arrowgene.Ddon.Database/IDatabase.cs +++ b/Arrowgene.Ddon.Database/IDatabase.cs @@ -410,11 +410,13 @@ uint commonId List FetchCharacterBazaarExhibitions(uint characterId); List SelectActiveBazaarExhibitionsByItemIdExcludingOwn( uint itemId, - uint excludedCharacterId + uint excludedCharacterId, + DbConnection? connectionIn = null ); List SelectActiveBazaarExhibitionsByItemIdsExcludingOwn( List itemIds, - uint excludedCharacterId + uint excludedCharacterId, + DbConnection? connectionIn = null ); // Rewards diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbBazaarExhibition.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbBazaarExhibition.cs index 9ae4ee90f..e21bad767 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbBazaarExhibition.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbBazaarExhibition.cs @@ -155,18 +155,13 @@ public List SelectBazaarExhibitionsByCharacterId(TCon conn, ui return entities; } - - - public List SelectActiveBazaarExhibitionsByItemIdExcludingOwn(uint itemId, uint excludedCharacterId) - { - using TCon conn = OpenNewConnection(); - return SelectActiveBazaarExhibitionsByItemIdExcludingOwn(conn, itemId, excludedCharacterId); - } - - public List SelectActiveBazaarExhibitionsByItemIdExcludingOwn(TCon conn, uint itemId, uint excludedCharacterId) + + public List SelectActiveBazaarExhibitionsByItemIdExcludingOwn(uint itemId, uint excludedCharacterId, DbConnection? connectionIn = null) { List entities = new List(); - ExecuteReader(conn, SqlSelectActiveBazaarExhibitionsByItemIdExcludingOwn, + ExecuteQuerySafe(connectionIn, conn => + { + ExecuteReader(conn, SqlSelectActiveBazaarExhibitionsByItemIdExcludingOwn, command => { AddParameter(command, "@item_id", itemId); @@ -180,24 +175,22 @@ public List SelectActiveBazaarExhibitionsByItemIdExcludingOwn( entities.Add(e); } }); + }); return entities; } - public List SelectActiveBazaarExhibitionsByItemIdsExcludingOwn(List itemIds, uint excludedCharacterId) - { - using TCon conn = OpenNewConnection(); - return SelectActiveBazaarExhibitionsByItemIdsExcludingOwn(conn, itemIds, excludedCharacterId); - } - - public List SelectActiveBazaarExhibitionsByItemIdsExcludingOwn(TCon conn, List itemIds, uint excludedCharacterId) + public List SelectActiveBazaarExhibitionsByItemIdsExcludingOwn(List itemIds, uint excludedCharacterId, DbConnection? connectionIn = null) { List entities = new List(); - foreach (uint itemId in itemIds) + ExecuteQuerySafe(connectionIn, conn => { - List exhibitionsForItemId = SelectActiveBazaarExhibitionsByItemIdExcludingOwn(conn, itemId, excludedCharacterId); - entities.AddRange(exhibitionsForItemId); - } + foreach (uint itemId in itemIds) + { + List exhibitionsForItemId = SelectActiveBazaarExhibitionsByItemIdExcludingOwn(itemId, excludedCharacterId, conn); + entities.AddRange(exhibitionsForItemId); + } + }); return entities; } diff --git a/Arrowgene.Ddon.GameServer/BazaarManager.cs b/Arrowgene.Ddon.GameServer/BazaarManager.cs index 061a5a53e..afb8d0bb7 100644 --- a/Arrowgene.Ddon.GameServer/BazaarManager.cs +++ b/Arrowgene.Ddon.GameServer/BazaarManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data.Common; using System.Linq; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; @@ -194,9 +195,9 @@ public List GetExhibitionsByCharacter(Character character) return Server.Database.FetchCharacterBazaarExhibitions(character.CharacterId); } - public List GetActiveExhibitionsForItemId(uint itemId, Character filterOutCharacter) + public List GetActiveExhibitionsForItemId(uint itemId, Character filterOutCharacter, DbConnection? connectionIn = null) { - return Server.Database.SelectActiveBazaarExhibitionsByItemIdExcludingOwn(itemId, filterOutCharacter.CharacterId); + return Server.Database.SelectActiveBazaarExhibitionsByItemIdExcludingOwn(itemId, filterOutCharacter.CharacterId, connectionIn); } public List GetActiveExhibitionsForItemIds(List itemIds, Character filterOutCharacter) diff --git a/Arrowgene.Ddon.GameServer/Handler/BazaarGetItemListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/BazaarGetItemListHandler.cs index e62f91ecc..12f478f3c 100644 --- a/Arrowgene.Ddon.GameServer/Handler/BazaarGetItemListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/BazaarGetItemListHandler.cs @@ -1,10 +1,9 @@ -using System.Collections.Generic; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using System.Collections.Generic; namespace Arrowgene.Ddon.GameServer.Handler { @@ -18,24 +17,25 @@ public BazaarGetItemListHandler(DdonGameServer server) : base(server) public override S2CBazaarGetItemListRes Handle(GameClient client, C2SBazaarGetItemListReq request) { - // TODO: Optimize to run in one DB connection S2CBazaarGetItemListRes response = new S2CBazaarGetItemListRes(); - foreach (CDataCommonU32 itemId in request.ItemIdList) - { - List exhibitionsForItemId = Server.BazaarManager.GetActiveExhibitionsForItemId(itemId.Value, client.Character); - if(exhibitionsForItemId.Count > 0) + Server.Database.ExecuteInTransaction(connection => { + foreach (CDataCommonU32 itemId in request.ItemIdList) { - CDataBazaarItemNumOfExhibitionInfo exhibitionInfo = new CDataBazaarItemNumOfExhibitionInfo(); - exhibitionInfo.ItemId = itemId.Value; - foreach (BazaarExhibition exhibition in exhibitionsForItemId) + List exhibitionsForItemId = Server.BazaarManager.GetActiveExhibitionsForItemId(itemId.Value, client.Character, connection); + if (exhibitionsForItemId.Count > 0) { - exhibitionInfo.Num += exhibition.Info.ItemInfo.ItemBaseInfo.Num; + CDataBazaarItemNumOfExhibitionInfo exhibitionInfo = new CDataBazaarItemNumOfExhibitionInfo(); + exhibitionInfo.ItemId = itemId.Value; + foreach (BazaarExhibition exhibition in exhibitionsForItemId) + { + exhibitionInfo.Num += exhibition.Info.ItemInfo.ItemBaseInfo.Num; + } + response.ItemList.Add(exhibitionInfo); } - response.ItemList.Add(exhibitionInfo); } - } - // TODO: response.Unk0 + }); + return response; } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CBazaarGetItemListRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CBazaarGetItemListRes.cs index 9eabffa5e..ffcc09dd3 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CBazaarGetItemListRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CBazaarGetItemListRes.cs @@ -12,11 +12,14 @@ public class S2CBazaarGetItemListRes : ServerResponse public S2CBazaarGetItemListRes() { ItemList = new List(); - Unk0 = new List(); + IgnoredItemIdList = new List(); } public List ItemList { get; set; } - public List Unk0 { get; set; } + /// + /// List of ItemIDs that are removed from the search result. + /// + public List IgnoredItemIdList { get; set; } public class Serializer : PacketEntitySerializer { @@ -24,7 +27,7 @@ public override void Write(IBuffer buffer, S2CBazaarGetItemListRes obj) { WriteServerResponse(buffer, obj); WriteEntityList(buffer, obj.ItemList); - WriteEntityList(buffer, obj.Unk0); + WriteEntityList(buffer, obj.IgnoredItemIdList); } public override S2CBazaarGetItemListRes Read(IBuffer buffer) @@ -32,9 +35,9 @@ public override S2CBazaarGetItemListRes Read(IBuffer buffer) S2CBazaarGetItemListRes obj = new S2CBazaarGetItemListRes(); ReadServerResponse(buffer, obj); obj.ItemList = ReadEntityList(buffer); - obj.Unk0 = ReadEntityList(buffer); + obj.IgnoredItemIdList = ReadEntityList(buffer); return obj; } } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs index 121ba706b..89967d5b3 100644 --- a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs +++ b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs @@ -279,8 +279,8 @@ public void Execute(DbConnection conn, string sql) {} public Account SelectAccountById(int accountId) { return new Account(); } public Account SelectAccountByLoginToken(string loginToken) { return new Account(); } public Account SelectAccountByName(string accountName) { return new Account(); } - public List SelectActiveBazaarExhibitionsByItemIdExcludingOwn(uint itemId, uint excludedCharacterId) { return new List(); } - public List SelectActiveBazaarExhibitionsByItemIdsExcludingOwn(List itemIds, uint excludedCharacterId) { return new List(); } + public List SelectActiveBazaarExhibitionsByItemIdExcludingOwn(uint itemId, uint excludedCharacterId, DbConnection? connectionIn = null) { return new List(); } + public List SelectActiveBazaarExhibitionsByItemIdsExcludingOwn(List itemIds, uint excludedCharacterId, DbConnection? connectionIn = null) { return new List(); } public List SelectAllUnlockedSecretAbilities(uint commonId) { return new List(); } public BazaarExhibition SelectBazaarExhibitionByBazaarId(ulong bazaarId) { return new BazaarExhibition(); } public List SelectBoxRewardItems(uint commonId, DbConnection? connectionIn = null) { return new List(); }