diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs index 2b6aa0c1a..ed73286b6 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs @@ -175,7 +175,10 @@ public Character SelectCharacter(uint characterId, DbConnection? connectionIn = } }); - QueryCharacterData(conn, character); + if (character != null) + { + QueryCharacterData(conn, character); + } }); return character; } diff --git a/Arrowgene.Ddon.Database/Sql/SqlDb.cs b/Arrowgene.Ddon.Database/Sql/SqlDb.cs index 801958344..195811bce 100644 --- a/Arrowgene.Ddon.Database/Sql/SqlDb.cs +++ b/Arrowgene.Ddon.Database/Sql/SqlDb.cs @@ -60,12 +60,10 @@ public bool ExecuteInTransaction(Action action) transaction.Commit(); return true; } - catch (Exception ex) + catch (Exception) { transaction.Rollback(); - connection.Close(); - Exception(ex); - return false; + throw; } finally { diff --git a/Arrowgene.Ddon.GameServer/Characters/ClanManager.cs b/Arrowgene.Ddon.GameServer/Characters/ClanManager.cs index b4554a438..106dcfb76 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ClanManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ClanManager.cs @@ -6,10 +6,10 @@ using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; using Arrowgene.Ddon.Shared.Model.Clan; +using Arrowgene.Ddon.Shared.Model.Rpc; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Data.Common; @@ -520,8 +520,11 @@ public PacketQueue CompleteClanQuest(Quest quest, GameClient client) { ClanQuestClearCount[characterId][quest.QuestScheduleId] = ClanQuestClearCount[characterId].GetValueOrDefault(quest.QuestScheduleId) + 1; - // TODO: Revise this; this is a hacky solution so I don't need a dedicated route just for this one mechanic. - Server.RpcManager.AnnounceClanPacket(character.ClanId, ntc, characterId); + Server.RpcManager.AnnounceOthers("internal/tracking", RpcInternalCommand.NotifyClanQuestCompletion, new RpcQuestCompletionData() + { + CharacterId = characterId, + QuestStatus = ClanQuestClearCount[characterId] + }); } if (ClanQuestClearCount[characterId].GetValueOrDefault(quest.QuestScheduleId) == quest.LightQuestDetail.OrderLimit) @@ -534,19 +537,9 @@ public PacketQueue CompleteClanQuest(Quest quest, GameClient client) } // For syncing across channels. - public void CompleteClanQuestForeign(Quest quest, uint characterId) + public void UpdateClanQuestCompletion(uint characterId, Dictionary questStatus) { - if (!ClanQuestClearCount.ContainsKey(characterId)) - { - ClanQuestClearCount[characterId] = new(); - } - lock (ClanQuestClearCount[characterId]) - { - if (ClanQuestClearCount[characterId].GetValueOrDefault(quest.QuestScheduleId) < quest.LightQuestDetail.OrderLimit) - { - ClanQuestClearCount[characterId][quest.QuestScheduleId] = ClanQuestClearCount[characterId].GetValueOrDefault(quest.QuestScheduleId) + 1; - } - } + ClanQuestClearCount[characterId] = questStatus; } public uint ClanQuestCompletionStatistics(uint characterId, uint questScheduleId) diff --git a/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs b/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs index a4dfb6db3..ed558705b 100644 --- a/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs @@ -16,7 +16,7 @@ public class EquipManager { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EquipManager)); - private static readonly List EnsembleSlots = new List() + public static readonly List EnsembleSlots = new List() { EquipSlot.ArmorLeg, EquipSlot.ArmorHelm, @@ -157,7 +157,7 @@ public void EquipJobItem(DdonGameServer server, GameClient client, CharacterComm // Set in equipment template //TODO: Move this lookup to memory instead of the DB if possible. - characterToEquipTo.EquipmentTemplate.SetEquipItem(server.Database.SelectStorageItemByUId(itemUId), characterToEquipTo.Job, equipType, equipSlot); + characterToEquipTo.EquipmentTemplate.SetEquipItem(server.Database.SelectStorageItemByUId(itemUId, connectionIn), characterToEquipTo.Job, equipType, equipSlot); server.Database.ReplaceEquipItem(characterToEquipTo.CommonId, characterToEquipTo.Job, equipType, equipSlot, itemUId, connectionIn); @@ -407,7 +407,5 @@ public uint CalculateItemRank(DdonGameServer server, CharacterCommon characterCo return itemRank > 0 ? itemRank : 1; } - - } } diff --git a/Arrowgene.Ddon.GameServer/Characters/JobManager.cs b/Arrowgene.Ddon.GameServer/Characters/JobManager.cs index 20d948604..c3b760e8a 100644 --- a/Arrowgene.Ddon.GameServer/Characters/JobManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/JobManager.cs @@ -191,7 +191,7 @@ private List SwapEquipmentAndStorage(GameClient client, C } else { - throw ex; + throw; } } } @@ -222,7 +222,7 @@ private List SwapEquipmentAndStorage(GameClient client, C } else { - throw ex; + throw; } } break; @@ -249,7 +249,7 @@ private List SwapEquipmentAndStorage(GameClient client, C } else { - throw ex; + throw; } } } @@ -275,7 +275,7 @@ private List SwapEquipmentAndStorage(GameClient client, C } else { - throw ex; + throw; } } } diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 9dd34bbc3..10e7dac48 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -123,6 +123,7 @@ public static Quest GetQuestByScheduleId(uint questScheduleId) { if (!gQuests.ContainsKey(questScheduleId)) { + Logger.Error($"GetQuestByScheduleId: Invalid questScheduleId {questScheduleId}"); return null; } diff --git a/Arrowgene.Ddon.GameServer/GameStructure.cs b/Arrowgene.Ddon.GameServer/GameStructure.cs index 449b2d583..c1dcc0e1e 100644 --- a/Arrowgene.Ddon.GameServer/GameStructure.cs +++ b/Arrowgene.Ddon.GameServer/GameStructure.cs @@ -1,11 +1,10 @@ -using System.Linq; -using System.Collections.Generic; +using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Party; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.Shared.Entity.PacketStructure; -using Arrowgene.Ddon.GameServer.Characters; -using Arrowgene.Ddon.Server.Network; +using System.Collections.Generic; +using System.Linq; namespace Arrowgene.Ddon.GameServer; @@ -163,19 +162,71 @@ public static void CDataPawnInfo(CDataPawnInfo cDataPawnInfo, Pawn pawn) cDataPawnInfo.SpSkillList = pawn.SpSkills.GetValueOrDefault(pawn.Job, new List()); } - public static void CDataNoraPawnInfo(CDataNoraPawnInfo cDataNoraPawnInfo, Pawn pawn) + public static void CDataNoraPawnInfo(CDataNoraPawnInfo cDataNoraPawnInfo, Pawn pawn, DdonGameServer server) { cDataNoraPawnInfo.Name = pawn.Name; cDataNoraPawnInfo.EditInfo = pawn.EditInfo; cDataNoraPawnInfo.Job = (byte)pawn.Job; - cDataNoraPawnInfo.CharacterEquipData = new() // TODO: ??? + + // Merge visual and performance equips + var performanceEquips = pawn.Equipment.GetItems(EquipType.Performance); + var visualEquips = pawn.Equipment.GetItems(EquipType.Visual); + + List perfInfo = performanceEquips.Select(x => x is not null ? server.ItemManager.LookupInfoByItem(server, x) : new()).ToList(); + List visualInfo = visualEquips.Select(x => x is not null ? server.ItemManager.LookupInfoByItem(server, x) : new()).ToList(); + + List mergedEquips = new(); + List mergedInfo = new(); + + for (int i = 0; i < visualEquips.Count; i++) + { + mergedEquips.Add(visualEquips[i] ?? performanceEquips[i]); + mergedInfo.Add(mergedEquips[i] is not null ? server.ItemManager.LookupInfoByItem(server, mergedEquips[i]) : new ClientItemInfo()); + } + + // Check for ensembles + bool overwriteBody = perfInfo.Any(x => x.SubCategory == ItemSubCategory.EquipEnsemble) + && visualInfo.Any(x => x.EquipSlot is not null && EquipManager.EnsembleSlots.Contains((EquipSlot)x.EquipSlot)); + bool overwriteOthers = visualInfo.Any(x => x.SubCategory == ItemSubCategory.EquipEnsemble); + + if (overwriteBody && !overwriteOthers) + { + mergedEquips[(byte)EquipSlot.ArmorBody - 1] = null; + } + else if (overwriteOthers && !overwriteBody) + { + for (int i = 0; i < mergedInfo.Count; i++) + { + if (mergedInfo[i].EquipSlot is null) continue; + if (EquipManager.EnsembleSlots.Contains((EquipSlot)mergedInfo[i].EquipSlot)) + { + mergedEquips[i] = null; + } + } + } + + // Process to CDataEquipItemInfo + var mergedCData = mergedEquips.Select((item, index) => new CDataEquipItemInfo() + { + ItemId = item?.ItemId ?? 0, + Unk0 = item?.SafetySetting ?? 0, + EquipType = EquipType.Performance, + EquipSlot = (byte)(index + 1), + Color = item?.Color ?? 0, + PlusValue = item?.PlusValue ?? 0, + EquipElementParamList = item?.EquipElementParamList ?? new List(), + AddStatusParamList = item?.AddStatusParamList ?? new List(), + Unk2List = item?.Unk2List ?? new List() + }) + .ToList(); + + + cDataNoraPawnInfo.CharacterEquipData = new() { new CDataCharacterEquipData() { - Equips = pawn.Equipment.AsCDataEquipItemInfo(EquipType.Performance) + Equips = mergedCData }, - new CDataCharacterEquipData() { - Equips = pawn.Equipment.AsCDataEquipItemInfo(EquipType.Visual) - } + new CDataCharacterEquipData() // The client expects a second element to this list, even if its not used. }; } diff --git a/Arrowgene.Ddon.GameServer/Handler/ClanClanPartnerPawnDataGetHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ClanClanPartnerPawnDataGetHandler.cs index eaf68930b..bb3d9c89a 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ClanClanPartnerPawnDataGetHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ClanClanPartnerPawnDataGetHandler.cs @@ -19,6 +19,7 @@ public override S2CClanClanPartnerPawnDataGetRes Handle(GameClient client, C2SCl res.PawnId = request.PawnId; + Pawn pawn = null; Server.Database.ExecuteInTransaction(connection => { uint ownerCharacterId = Server.Database.GetPawnOwnerCharacterId((uint)request.PawnId); @@ -28,11 +29,11 @@ public override S2CClanClanPartnerPawnDataGetRes Handle(GameClient client, C2SCl } var ownerCharacter = Server.CharacterManager.SelectCharacter(ownerCharacterId); - Pawn pawn = ownerCharacter.Pawns.Find(x => x.PawnId == request.PawnId) + pawn = ownerCharacter.Pawns.Find(x => x.PawnId == request.PawnId) ?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_INVALID); - GameStructure.CDataNoraPawnInfo(res.PawnInfo, pawn); }); - + GameStructure.CDataNoraPawnInfo(res.PawnInfo, pawn, Server); + return res; } diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 95e283009..60ff51506 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -82,7 +82,6 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne } }; client.Party.EnqueueToAll(repopNtc, queuedPackets); - //client.Send(repopNtc); } else { @@ -121,7 +120,6 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne IsAreaBoss = IsAreaBoss && (client.GameMode == GameMode.Normal) }; client.Party.EnqueueToAll(groupDestroyedNtc, queuedPackets); - //client.Party.SendToAll(groupDestroyedNtc); if (IsAreaBoss && client.GameMode == GameMode.BitterblackMaze) { @@ -142,7 +140,7 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne { LayoutId = packet.LayoutId, SetId = packet.SetId, - MdlType = enemyKilled.DropsTable.MdlType, + MdlType = enemyKilled.DropsTable?.MdlType ?? 0, PosX = packet.DropPosX, PosY = packet.DropPosY, PosZ = packet.DropPosZ @@ -169,14 +167,12 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne } // If the roll was unlucky, there is a chance that no bag will show. - if (instancedGatheringItems.Where(x => x.ItemNum > 0).Any()) + if (instancedGatheringItems.Any(x => x.ItemNum > 0)) { partyMemberClient.Enqueue(dropItemNtc, queuedPackets); - //partyMemberClient.Send(dropItemNtc); } } - uint calcExp = _gameServer.ExpManager.GetAdjustedExp(client.GameMode, RewardSource.Enemy, client.Party, enemyKilled.GetDroppedExperience(), enemyKilled.Lv); uint calcPP = (uint)(enemyKilled.GetDroppedPlayPoints() * _gameServer.Setting.GameLogicSetting.PpModifier); @@ -223,13 +219,13 @@ public override S2CInstanceEnemyKillRes Handle(GameClient client, C2SInstanceEne { // Drop HO uint gainedHo = (uint)(enemyKilled.HighOrbs * _gameServer.Setting.GameLogicSetting.HoModifier); - CDataUpdateWalletPoint hoUpdateWalletPoint = _gameServer.WalletManager.AddToWallet(memberClient.Character, WalletType.HighOrbs, gainedHo, connectionIn: connectionIn); + CDataUpdateWalletPoint hoUpdateWalletPoint = _gameServer.WalletManager.AddToWallet(memberClient.Character, WalletType.HighOrbs, gainedHo, connectionIn: connectionIn); + updateCharacterItemNtc.UpdateWalletList.Add(hoUpdateWalletPoint); } if (updateCharacterItemNtc.UpdateItemList.Count != 0 || updateCharacterItemNtc.UpdateWalletList.Count != 0) { memberClient.Enqueue(updateCharacterItemNtc, queuedPackets); - //memberClient.Send(updateCharacterItemNtc); } if (gainedPP > 0) diff --git a/Arrowgene.Ddon.GameServer/Handler/LobbyLobbyLeaveHandler.cs b/Arrowgene.Ddon.GameServer/Handler/LobbyLobbyLeaveHandler.cs index ca15c58ae..cde5119bf 100644 --- a/Arrowgene.Ddon.GameServer/Handler/LobbyLobbyLeaveHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/LobbyLobbyLeaveHandler.cs @@ -34,7 +34,7 @@ private void OnClientConnectionChangeEvent(object sender, ClientConnectionChange private void NotifyDisconnect(GameClient client) { - if(client.Character != null) { + if(client.Character != null && client.Character.Stage.Id != 0) { // Notice all other users S2CUserListLeaveNtc ntc = new S2CUserListLeaveNtc(); ntc.CharacterList.Add(new CDataCommonU32(client.Character.CharacterId)); diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetMainQuestListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetMainQuestListHandler.cs index 5c322c3b4..f3a7561bf 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetMainQuestListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetMainQuestListHandler.cs @@ -1,16 +1,11 @@ -using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; -using Arrowgene.Ddon.Shared.Asset; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Model.Quest; -using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; -using System.Dynamic; namespace Arrowgene.Ddon.GameServer.Handler { - public class QuestGetMainQuestListHandler : PacketHandler + public class QuestGetMainQuestListHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetMainQuestListHandler)); @@ -18,9 +13,7 @@ public QuestGetMainQuestListHandler(DdonGameServer server) : base(server) { } - public override PacketId Id => PacketId.C2S_QUEST_GET_MAIN_QUEST_LIST_REQ; - - public override void Handle(GameClient client, IPacket packet) + public override S2CQuestGetMainQuestListRes Handle(GameClient client, C2SQuestGetMainQuestListReq request) { // client.Send(GameFull.Dump_123); @@ -29,7 +22,7 @@ public override void Handle(GameClient client, IPacket packet) foreach (var questId in client.Party.QuestState.GetActiveQuestScheduleIds()) { var quest = client.Party.QuestState.GetQuest(questId); - if (quest.QuestType == QuestType.Main) + if (quest != null && quest.QuestType == QuestType.Main) { var questState = client.Party.QuestState.GetQuestState(questId); res.MainQuestList.Add(quest.ToCDataQuestList(questState.Step)); @@ -44,7 +37,7 @@ public override void Handle(GameClient client, IPacket packet) // res.MainQuestList.Add(Quest30270); // Those Who Follow the Dragon (White Dragon) // res.MainQuestList.Add(Quest30410); // Japanese Name (Joseph Historian) - client.Send(res); + return res; } } } diff --git a/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs b/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs index e095bf48a..66174ce64 100644 --- a/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs +++ b/Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs @@ -915,27 +915,27 @@ public override PacketQueue UpdatePriorityQuestList(GameClient requestingClient, public PacketQueue HandleEnemyHuntRequests(Enemy enemy, DbConnection? connectionIn = null) { + PacketQueue packets = new(); + if (Member.Client.Character.GameMode != GameMode.Normal) { - return new(); + return packets; } - PacketQueue packets = new(); - lock (ActiveQuests) { - foreach (var quest in ActiveQuests) + foreach ((uint questScheduleId, QuestState questState) in ActiveQuests) { - Quest questObject = QuestManager.GetQuestByScheduleId(quest.Key); - QuestState questState = quest.Value; - QuestEnemyHuntRecord huntRecord = questState.UpdateHuntRequest(enemy); + Quest questObject = QuestManager.GetQuestByScheduleId(questScheduleId); - if (Member.Client.Party.ExmInProgress && questObject.QuestType == QuestType.Light) + if (questObject is null || Member.Client.Party.ExmInProgress && questObject?.QuestType == QuestType.Light) { // The UI indicates that light quests cannot progress during EXMs. continue; } + QuestEnemyHuntRecord huntRecord = questState.UpdateHuntRequest(enemy); + if (huntRecord != null) { if (questObject.SaveWorkAsStep) @@ -950,7 +950,7 @@ public PacketQueue HandleEnemyHuntRequests(Enemy enemy, DbConnection? connection S2CQuestQuestProgressWorkSaveNtc ntc = new() { - QuestScheduleId = quest.Key, + QuestScheduleId = questScheduleId, ProcessNo = huntRecord.ProcessNo, SequenceNo = huntRecord.SequenceNo, BlockNo = huntRecord.BlockNo diff --git a/Arrowgene.Ddon.GameServer/RpcManager.cs b/Arrowgene.Ddon.GameServer/RpcManager.cs index fe14d82b7..1a7925e80 100644 --- a/Arrowgene.Ddon.GameServer/RpcManager.cs +++ b/Arrowgene.Ddon.GameServer/RpcManager.cs @@ -1,14 +1,11 @@ using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Chat; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Ddon.Shared.Model.Rpc; -using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using System; using System.Collections.Generic; @@ -23,6 +20,30 @@ namespace Arrowgene.Ddon.GameServer { public class RpcManager { + private class RpcTrackingMap : Dictionary + { + public DateTime TimeStamp { get; set; } + + public RpcTrackingMap() : base() + { + TimeStamp = DateTime.Now; + } + + public bool Update(DateTime newTimestamp, List characterData) + { + if (newTimestamp <= TimeStamp) return false; + + TimeStamp = newTimestamp; + this.Clear(); + foreach (var character in characterData) + { + this[character.CharacterId] = character; + } + return true; + } + } + + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(RpcManager)); private static readonly string[] TRAFFIC_LABELS = new string[] { @@ -35,7 +56,7 @@ public class RpcManager private readonly DdonGameServer Server; private readonly Dictionary ChannelInfo; - private readonly Dictionary CharacterData)> CharacterTrackingMap; + private readonly Dictionary CharacterTrackingMap; public class RpcWrappedObject { @@ -103,7 +124,7 @@ public RpcManager(DdonGameServer server) CharacterTrackingMap = new(); foreach (var info in ChannelInfo.Values) { - CharacterTrackingMap[info.Id] = (DateTime.MinValue, new()); + CharacterTrackingMap[info.Id] = new(); } string authToken = string.Empty; @@ -124,10 +145,19 @@ public List ServerListInfo() public CDataGameServerListInfo ServerListInfo(ushort channelId) { var info = ChannelInfo[channelId].ToCDataGameServerListInfo(); - lock (CharacterTrackingMap[channelId].CharacterData) + if (channelId == Server.Id) { - info.LoginNum = (uint)CharacterTrackingMap[channelId].CharacterData.Count; + // Check against StageId to not count clients that are in the character select. + info.LoginNum = (uint)Server.ClientLookup.GetAll().Where(x => x.Character != null && x.Character.Stage.Id != 0).Count(); + } + else + { + lock (CharacterTrackingMap[channelId]) + { + info.LoginNum = (uint)CharacterTrackingMap[channelId].Count; + } } + info.TrafficName = GetTrafficName(info.LoginNum); return info; } @@ -208,7 +238,7 @@ public void AnnounceClan(uint clanId, string route, RpcInternalCommand command, continue; } - if (channel.Value.CharacterData.Any(x => x.Value.ClanId == clanId)) + if (channel.Value.Any(x => x.Value.ClanId == clanId)) { Announce(channel.Key, route, command, data); } @@ -219,7 +249,7 @@ 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) + foreach ((ushort channelId, var channelMembers) in CharacterTrackingMap) { lock(channelMembers) { @@ -237,7 +267,7 @@ public ushort FindPlayerByName(string firstName, string lastName) public ushort FindPlayerById(uint characterId) { - foreach ((ushort channelId, (_, var channelMembers)) in CharacterTrackingMap) + foreach ((ushort channelId, var channelMembers) in CharacterTrackingMap) { lock (channelMembers) { @@ -250,44 +280,23 @@ public ushort FindPlayerById(uint characterId) return 0; } - public void RemovePlayerSummary(ushort channelId, uint characterId) - { - lock (CharacterTrackingMap[channelId].CharacterData) - { - CharacterTrackingMap[channelId].CharacterData.Remove(characterId); - } - } - - public void AddPlayerSummary(ushort channelId, RpcCharacterData characterSummary) - { - lock (CharacterTrackingMap[channelId].CharacterData) - { - CharacterTrackingMap[channelId].CharacterData[characterSummary.CharacterId] = characterSummary; - } - } - public void AnnouncePlayerList() { List rpcCharacterDatas = new List(); - foreach (var client in Server.ClientLookup.GetAll()) - { - if (client.Character != null) + foreach (var character in Server.ClientLookup.GetAllCharacter().Where(x => x.Stage.Id != 0)) { - rpcCharacterDatas.Add(new(client.Character)); + rpcCharacterDatas.Add(new(character)); } - } + AnnounceOthers("internal/tracking", RpcInternalCommand.NotifyPlayerList, rpcCharacterDatas); + CharacterTrackingMap[(ushort) Server.Id].Update(DateTime.Now, rpcCharacterDatas); } public void ReceivePlayerList(ushort channelId, DateTime timestamp, List characterDatas) { if (CharacterTrackingMap.ContainsKey(channelId)) { - if (CharacterTrackingMap[channelId].TimeStamp <= timestamp) - { - CharacterTrackingMap[channelId] = (timestamp, characterDatas.ToDictionary(key => key.CharacterId, val => val)); - } - else + if (!CharacterTrackingMap[channelId].Update(timestamp, characterDatas)) { Logger.Info($"Out of date character list discarded for channel ID {channelId}"); } @@ -297,7 +306,7 @@ public void ReceivePlayerList(ushort channelId, DateTime timestamp, List false; - } else if (packet.Id == PacketId.S2C_CLAN_CLAN_SHOP_BUY_ITEM_NTC) { var parsedPacket = new S2CClanClanShopBuyItemNtc.Serializer().Read(data.Data); diff --git a/Arrowgene.Ddon.Rpc.Web/Route/Internal/TrackingRoute.cs b/Arrowgene.Ddon.Rpc.Web/Route/Internal/TrackingRoute.cs index 455f88873..55adc062d 100644 --- a/Arrowgene.Ddon.Rpc.Web/Route/Internal/TrackingRoute.cs +++ b/Arrowgene.Ddon.Rpc.Web/Route/Internal/TrackingRoute.cs @@ -25,31 +25,22 @@ public override RpcCommandResult Execute(DdonGameServer gameServer) { switch (_entry.Command) { - case RpcInternalCommand.NotifyPlayerLeave: - { - RpcCharacterData data = _entry.GetData(); - gameServer.RpcManager.RemovePlayerSummary(_entry.Origin, data.CharacterId); - return new RpcCommandResult(this, true) - { - Message = $"NotifyPlayerLeave Channel {_entry.Origin} ID {data.CharacterId}" - }; - } - case RpcInternalCommand.NotifyPlayerJoin: + case RpcInternalCommand.NotifyPlayerList: { - RpcCharacterData data = _entry.GetData(); - gameServer.RpcManager.AddPlayerSummary(_entry.Origin, data); + List data = _entry.GetData>(); + gameServer.RpcManager.ReceivePlayerList(_entry.Origin, _entry.Timestamp, data); return new RpcCommandResult(this, true) { - Message = $"NotifyPlayerJoin Channel {_entry.Origin} ID {data.CharacterId}" + Message = $"NotifyPlayerList Channel {_entry.Origin}" }; } - case RpcInternalCommand.NotifyPlayerList: + case RpcInternalCommand.NotifyClanQuestCompletion: { - List data = _entry.GetData>(); - gameServer.RpcManager.ReceivePlayerList(_entry.Origin, _entry.Timestamp, data); + RpcQuestCompletionData data = _entry.GetData(); + gameServer.ClanManager.UpdateClanQuestCompletion(data.CharacterId, data.QuestStatus); return new RpcCommandResult(this, true) { - Message = $"NotifyPlayerList Channel {_entry.Origin}" + Message = $"NotifyClanQuestCompletion for CharacterId {data.CharacterId}" }; } default: diff --git a/Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs b/Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs index e4c330246..7225f58f3 100644 --- a/Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs +++ b/Arrowgene.Ddon.Server/Network/RequestPacketHandler.cs @@ -1,9 +1,10 @@ -using System; -using Arrowgene.Ddon.Database; using Arrowgene.Ddon.Shared.Entity; using Arrowgene.Ddon.Shared.Model; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using System; +using System.Linq; +using System.Text; namespace Arrowgene.Ddon.Server.Network { @@ -31,8 +32,16 @@ public sealed override void Handle(TClient client, StructurePacket r { response = new TResStruct(); response.Error = (uint) ex.ErrorCode; - var message = ex.Message.Length > 0 ? ("\n\tMessage: " + ex.Message) : ""; - Logger.Error(client, $"{ex.ErrorCode} thrown when handling {typeof(TReqStruct)}{message}."); + + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine($"{ex.ErrorCode} thrown when handling {typeof(TReqStruct).Name}"); + if (ex.Message.Length > 0) + { + stringBuilder.AppendLine($"\tMessage: {ex.Message}"); + } + stringBuilder.AppendLine(ex.StackTrace?.Split(Environment.NewLine).FirstOrDefault()); + Logger.Error(client, stringBuilder.ToString()); + client.Send(response); } catch (Exception) diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index 9fceffcdb..3c90e0431 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -753,6 +753,7 @@ static EntitySerializer() Create(new C2SQuestPlayEntryCancelReq.Serializer()); Create(new C2SQuestGetCycleContentsStateListReq.Serializer()); Create(new C2SQuestQuestLogInfoReq.Serializer()); + Create(new C2SQuestGetMainQuestListReq.Serializer()); Create(new C2SServerGameTimeGetBaseInfoReq.Serializer()); Create(new C2SServerGetRealTimeReq.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetMainQuestListReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetMainQuestListReq.cs new file mode 100644 index 000000000..9090ab3ac --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetMainQuestListReq.cs @@ -0,0 +1,23 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestGetMainQuestListReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_GET_MAIN_QUEST_LIST_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestGetMainQuestListReq obj) + { + } + + public override C2SQuestGetMainQuestListReq Read(IBuffer buffer) + { + C2SQuestGetMainQuestListReq obj = new C2SQuestGetMainQuestListReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CFriendApplyFriendRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CFriendApplyFriendRes.cs index e30ed9968..93b6478ca 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CFriendApplyFriendRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CFriendApplyFriendRes.cs @@ -8,8 +8,12 @@ public class S2CFriendApplyFriendRes : ServerResponse { public override PacketId Id => PacketId.S2C_FRIEND_APPLY_FRIEND_RES; - public CDataFriendInfo FriendInfo { get; set; } + public S2CFriendApplyFriendRes() + { + FriendInfo = new(); + } + public CDataFriendInfo FriendInfo { get; set; } public class Serializer : PacketEntitySerializer { diff --git a/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs b/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs index 64edc0cec..4589e334c 100644 --- a/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs +++ b/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs @@ -2,9 +2,8 @@ namespace Arrowgene.Ddon.Shared.Model.Rpc { public enum RpcInternalCommand { - NotifyPlayerJoin, // RpcCharacterData - NotifyPlayerLeave, // RpcCharacterData NotifyPlayerList, // List + NotifyClanQuestCompletion, //RpcQuestCompletionData SendTellMessage, // RpcChatData SendClanMessage, // RpcChatData diff --git a/Arrowgene.Ddon.Shared/Model/Rpc/RpcQuestCompletionData.cs b/Arrowgene.Ddon.Shared/Model/Rpc/RpcQuestCompletionData.cs new file mode 100644 index 000000000..1cfd715d5 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/Rpc/RpcQuestCompletionData.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Model.Rpc +{ + public class RpcQuestCompletionData + { + public RpcQuestCompletionData() { } + + public uint CharacterId { get; set; } + public Dictionary QuestStatus { get; set; } + } +} diff --git a/Arrowgene.Ddon.Server/Network/ResponseErrorException.cs b/Arrowgene.Ddon.Shared/Network/ResponseErrorException.cs similarity index 100% rename from Arrowgene.Ddon.Server/Network/ResponseErrorException.cs rename to Arrowgene.Ddon.Shared/Network/ResponseErrorException.cs