From 99d58fb4e44e3648504f8edfee0ec187ef260775 Mon Sep 17 00:00:00 2001 From: Asterit Date: Mon, 26 Aug 2024 17:39:18 +0100 Subject: [PATCH 001/116] Implemented quest switching via quest groups. Only works with unique quest ids for now which may not be correct for variant quests. Alt quests do not have quest information such as title or description --- .../Properties/launchSettings.json | 7 + .../Characters/QuestManager.cs | 79 +++++++++ .../Handler/PartyPartyCreateHandler.cs | 4 +- .../Handler/QuestCancelHandler.cs | 2 +- .../Handler/QuestQuestProgressHandler.cs | 5 +- .../Party/PartyQuestState.cs | 91 +++++++++-- .../Quests/GenericQuest.cs | 6 +- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 3 + Arrowgene.Ddon.Shared/Asset/QuestAsset.cs | 1 + .../AssetReader/QuestAssetDeserializer.cs | 13 +- .../Assets/quests/q20055001 Ape One.json | 72 --------- .../Assets/quests/q20055001 Sphinx one.json | 72 --------- .../Files/Assets/quests/q20055001 Sphinx.json | 73 +++++++++ .../Files/Assets/quests/q200550011 Ape.json | 74 +++++++++ .../Assets/quests/q20055004 Redcaps.json | 151 +++++++++--------- .../Files/Assets/quests/q20055004 Troll.json | 70 -------- .../Files/Assets/quests/q200550041 Troll.json | 72 +++++++++ 17 files changed, 480 insertions(+), 315 deletions(-) create mode 100644 Arrowgene.Ddon.Database/Properties/launchSettings.json delete mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape One.json delete mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q200550011 Ape.json delete mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q200550041 Troll.json diff --git a/Arrowgene.Ddon.Database/Properties/launchSettings.json b/Arrowgene.Ddon.Database/Properties/launchSettings.json new file mode 100644 index 000000000..79ad96102 --- /dev/null +++ b/Arrowgene.Ddon.Database/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "Arrowgene.Ddon.Database": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index f5cd2f108..85e9b7337 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -28,6 +28,19 @@ private QuestManager() } private static Dictionary gQuests = new Dictionary(); + private static Dictionary> altQuestsGroups = new(); + private static Dictionary altQuestLookup = new(); + public static readonly HashSet questGroupIds = new(); + + public static void CopyQuestGroupIds(HashSet InactiveAlternateQuestsGroups) + { + + for (int i = 0; i < questGroupIds.Count; i++) + { + InactiveAlternateQuestsGroups.Add(questGroupIds.ElementAt(i)); + } + + } public static void LoadQuests(AssetRepository assetRepository) { @@ -43,8 +56,55 @@ public static void LoadQuests(AssetRepository assetRepository) // Load Quests defined in files foreach (var questAsset in assetRepository.QuestAssets.Quests) { + + // Check if this quest has a group quest id - if so then handle it separately + + if (questAsset.QuestGroupId is not null) + { + Quest alternateQuest = GenericQuest.FromAsset(questAsset); + alternateQuest.IsAlternateQuest = true; + alternateQuest.QuestGroupId = questAsset.QuestGroupId; + + // Add an entry to the dictionary if it doesn't exist then add the quest id and quest + if (!altQuestsGroups.ContainsKey((uint)questAsset.QuestGroupId)) + { + altQuestsGroups[(uint)alternateQuest.QuestGroupId] = new Dictionary(); + altQuestsGroups[(uint)questAsset.QuestGroupId].Add(alternateQuest.QuestId, alternateQuest); + + // Add to the quest lookup for easier finding of quests for explicit search. + altQuestLookup[alternateQuest.QuestId] = (uint)alternateQuest.QuestGroupId; + + continue; + } + + // Add quest id and quest + altQuestsGroups[(uint)questAsset.QuestGroupId].Add(alternateQuest.QuestId, alternateQuest); + + // Add to the quest lookup for explicit searches. + altQuestLookup[alternateQuest.QuestId] = (uint)alternateQuest.QuestGroupId; + + continue; + } + gQuests[questAsset.QuestId] = GenericQuest.FromAsset(questAsset); } + + + var allAltQuestsInDictionary = altQuestsGroups.Keys.ToArray(); + + for (int i = 0; i < allAltQuestsInDictionary.Length; i++) + { + // Create a reliable source of all quest IDs + questGroupIds.Add(allAltQuestsInDictionary[i]); + + Logger.Info($"Alt Group Id Listed: {allAltQuestsInDictionary[i]}"); + var questKeys = altQuestsGroups[allAltQuestsInDictionary[i]].Keys.ToArray(); + + for(int j = 0; j < questKeys.Length; j++) + { + Logger.Info($"Quest entry: {questKeys[j]}"); + } + } } /** @@ -71,8 +131,27 @@ public static List> GetQuestsByType(QuestType type) return results; } + public static QuestId GetRandomAltQuest(uint questGroupId) + { + // Get random index value to choose a version. + int randomIndex = new Random().Next(altQuestsGroups[questGroupId].Count); + + QuestId questKey = altQuestsGroups[questGroupId].ElementAt(randomIndex).Key; + + return questKey; + + } + public static Quest GetQuest(QuestId questId) { + + if (altQuestLookup.ContainsKey(questId)) + { + uint groupIdKey = altQuestLookup[questId]; + + return altQuestsGroups[groupIdKey][questId]; + } + if (!gQuests.ContainsKey(questId)) { return null; diff --git a/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs index ef776927f..073c10c98 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs @@ -53,7 +53,7 @@ public override void Handle(GameClient client, StructurePacket x.QuestId).ToList(); @@ -65,7 +65,7 @@ public override void Handle(GameClient client, StructurePacket ProcessState { get; set; } - public Dictionary>> QuestEnemies { get; set; } + public Dictionary ProcessState { get; set; } + public Dictionary>> QuestEnemies { get; set; } public Dictionary CurrentSubgroup { get; set; } - public Dictionary DeliveryRecords { get; set; } + public Dictionary DeliveryRecords { get; set; } public QuestState() { @@ -106,24 +107,36 @@ public class PartyQuestState private Dictionary ActiveQuests { get; set; } private Dictionary> QuestLookupTable { get; set; } - private List CompletedWorldQuests { get; set; } + private List CompletedWorldQuests { get; set; } + private HashSet InactiveAlternateQuestsGroups { get; set; } + private HashSet ActiveAlternateQuests { get; set; } public PartyQuestState() { ActiveQuests = new Dictionary(); QuestLookupTable = new Dictionary>(); CompletedWorldQuests = new List(); + InactiveAlternateQuestsGroups = new HashSet(); + ActiveAlternateQuests = new(); + + QuestManager.CopyQuestGroupIds(InactiveAlternateQuestsGroups); } - public void AddNewQuest(Quest quest, uint step = 0) + public void SetHasStarted(QuestId questId, bool hasStarted) { - lock (ActiveQuests) + ActiveQuests[questId].HasStarted = hasStarted; + } + + public void AddNewQuest(Quest quest, uint step = 0, bool questStarted = false) + { + lock (ActiveQuests) { ActiveQuests[quest.QuestId] = new QuestState() { QuestId = quest.QuestId, QuestType = quest.QuestType, - Step = step + Step = step, + HasStarted = questStarted }; foreach (var location in quest.Locations) @@ -236,10 +249,28 @@ public ushort GetInstanceSubgroupId(Quest quest, StageId stageId) } } - public void AddNewQuest(QuestId questId, uint step) + public void AddNewAltQuest(uint questGroupId) + { + var questId = QuestManager.GetRandomAltQuest(questGroupId); + + AddNewQuest(questId, 0, false); + } + + public void AddNewQuest(QuestId questId, uint step, bool questStarted) { + var quest = QuestManager.GetQuest(questId); - AddNewQuest(quest, step); + + // If we are adding a new alt quest, remove the quest group id from the list. + if (quest.IsAlternateQuest && InactiveAlternateQuestsGroups.Contains((uint)quest.QuestGroupId)) + { + + InactiveAlternateQuestsGroups.Remove((uint)quest.QuestGroupId); + ActiveAlternateQuests.Add(quest.QuestId); + + } + + AddNewQuest(quest, step, questStarted); } public void RemoveQuest(QuestId questId) @@ -249,6 +280,13 @@ public void RemoveQuest(QuestId questId) { ActiveQuests.Remove(questId); + // Ensure adding of selected quest's group id to be eligible for reroll. + if(quest.IsAlternateQuest) + { + InactiveAlternateQuestsGroups.Add((uint)quest.QuestGroupId); + ActiveAlternateQuests.Remove(quest.QuestId); + } + foreach (var location in quest.Locations) { if (QuestLookupTable.ContainsKey(location.StageId)) @@ -268,7 +306,8 @@ public void CancelQuest(QuestId questId) // Save the quest if it was a world quest // so we can add it back on instance reset - if (quest.QuestType == QuestType.World) + // Don't add alt quests to completed world quests. Rerolls are handled independently + if (quest.QuestType == QuestType.World && !quest.IsAlternateQuest) { CompletedWorldQuests.Add(questId); } @@ -291,7 +330,7 @@ public void CompleteQuest(QuestId questId) if (quest.NextQuestId != 0) { - AddNewQuest(quest.NextQuestId, 0); + AddNewQuest(quest.NextQuestId, 0, false); } } } @@ -328,7 +367,7 @@ public bool HasQuest(QuestId questId) { lock(ActiveQuests) { - return ActiveQuests.ContainsKey(questId); + return ActiveQuests.ContainsKey(questId); } } @@ -399,12 +438,36 @@ public QuestProcessState GetProcessState(QuestId questId, ushort processNo) } } + private void RerollUnfoundAltQuests() + { + foreach (var quest in ActiveAlternateQuests) + { + switch (ActiveQuests[quest].HasStarted) + { + case true: + continue; + case false: + RemoveQuest(quest); + continue; + } + } + + // See available group ids to choose from, then loop over and add a random quest from each. + + foreach (var groupId in InactiveAlternateQuestsGroups) + { + AddNewAltQuest(groupId); + } + } + public void ResetInstanceQuestState() { + RerollUnfoundAltQuests(); + // Add all world quests foreach (var questId in CompletedWorldQuests) { - AddNewQuest(questId, 0); + AddNewQuest(questId, 0, false); } CompletedWorldQuests.Clear(); diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index ff1a06871..32c96208d 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -142,7 +142,7 @@ public override List StateMachineExecute(DdonGameServer return new List(); } - var process = Processes[processState.ProcessNo]; + var process = Processes[processState.ProcessNo]; if ((processState.BlockNo) >= process.Blocks.Count) { questProgressState = QuestProgressState.Unknown; @@ -155,11 +155,11 @@ public override List StateMachineExecute(DdonGameServer questProgressState = QuestProgressState.Complete; } else if (questBlock.IsCheckpoint) - { + { questProgressState = QuestProgressState.Checkpoint; } else if (questBlock.AnnounceType == QuestAnnounceType.Accept) - { + { questProgressState = QuestProgressState.Accepted; } else diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 8d96b75a6..58fd2ae23 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -67,6 +67,8 @@ public abstract class Quest public List QuestLayoutFlagSetInfo; public List QuestLayoutFlags; public Dictionary EnemyGroups { get; set; } + public bool IsAlternateQuest { get; set; } + public uint ? QuestGroupId { get; set; } public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool isDiscoverable = false) { @@ -87,6 +89,7 @@ public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool QuestLayoutFlagSetInfo = new List(); QuestLayoutFlags = new List(); EnemyGroups = new Dictionary(); + IsAlternateQuest = false; Processes = new List(); } diff --git a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs index 1355a50f1..af1a862ef 100644 --- a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs +++ b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs @@ -34,6 +34,7 @@ public class QuestAssetData public List QuestLayoutFlags { get; set; } public List QuestLayoutSetInfoFlags { get; set; } public Dictionary EnemyGroups { get; set; } + public uint ? QuestGroupId { get; set; } public QuestAssetData() { diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 559e9af8e..b87d78cbe 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -8,11 +8,7 @@ using Arrowgene.Ddon.Shared.Entity.Structure; using System; using Arrowgene.Ddon.Shared.Model.Quest; -using YamlDotNet.Core.Tokens; -using System.Linq.Expressions; -using System.Reflection.Metadata.Ecma335; -using System.Text.RegularExpressions; -using System.Text.Json.Nodes; + namespace Arrowgene.Ddon.Shared.AssetReader { @@ -84,6 +80,13 @@ private bool ParseQuest(QuestAssetData assetData, JsonElement jQuest) assetData.MinimumItemRank = jQuest.GetProperty("minimum_item_rank").GetByte(); assetData.Discoverable = jQuest.GetProperty("discoverable").GetBoolean(); + // For the purpose of setting up alternate quests. + assetData.QuestGroupId = null; + if (jQuest.TryGetProperty("quest_group_id", out JsonElement AltQuestId)) + { + assetData.QuestGroupId = AltQuestId.GetUInt32(); + } + assetData.NextQuestId = 0; if (jQuest.TryGetProperty("next_quest", out JsonElement jNextQuest)) { diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape One.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape One.json deleted file mode 100644 index dfd401924..000000000 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape One.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "state_machine": "GenericStateMachine", - "type": "World", - "comment": "A Strange Creature Dances In the Forest", - "quest_id": 2005501, - "base_level": 15, - "minimum_item_rank": 0, - "discoverable": false, - "rewards": [ - { - "type": "wallet", - "wallet_type": "Gold", - "amount": 490 - }, - { - "type": "wallet", - "wallet_type": "RiftPoints", - "amount": 70 - }, - { - "type": "exp", - "amount": 690 - }, - { - "type": "select", - "loot_pool": [ - { - "item_id": 448, - "num": 1 - }, - { - "item_id": 61, - "num": 2 - }, - { - "item_id": 9409, - "num": 1 - } - ] - } - ], - "enemy_groups" : [ - { - "stage_id": { - "id": 1, - "group_id": 57 - }, - "enemies": [ - { - "comment" : "Young Dread Ape", - "enemy_id": "0x015502", - "named_enemy_params_id": 459, - "level": 15, - "exp": 2780, - "is_boss": true - } - ] - } - ], - "blocks": [ - { - "type": "DiscoverEnemy", - "groups": [0] - }, - { - "type": "KillGroup", - "announce_type": "Accept", - "reset_group": false, - "groups": [0] - } - ] -} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json deleted file mode 100644 index d19f6da0d..000000000 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "state_machine": "GenericStateMachine", - "type": "World", - "comment": "A Strange Bird Dances In the Forest", - "quest_id": 2005501, - "base_level": 18, - "minimum_item_rank": 0, - "discoverable": false, - "rewards": [ - { - "type": "wallet", - "wallet_type": "Gold", - "amount": 590 - }, - { - "type": "wallet", - "wallet_type": "RiftPoints", - "amount": 90 - }, - { - "type": "exp", - "amount": 830 - }, - { - "type": "select", - "loot_pool": [ - { - "item_id": 448, - "num": 1 - }, - { - "item_id": 61, - "num": 2 - }, - { - "item_id": 9409, - "num": 1 - } - ] - } - ], - "enemy_groups" : [ - { - "stage_id": { - "id": 1, - "group_id": 57 - }, - "enemies": [ - { - "comment" : "Young Dread Ape", - "enemy_id": "0x015302", - "named_enemy_params_id": 459, - "level": 18, - "exp": 2950, - "is_boss": true - } - ] - } - ], - "blocks": [ - { - "type": "DiscoverEnemy", - "groups": [0] - }, - { - "type": "KillGroup", - "announce_type": "Accept", - "reset_group": false, - "groups": [0] - } - ] -} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json new file mode 100644 index 000000000..cea379cba --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json @@ -0,0 +1,73 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "A Strange Bird Dances In the Forest", + "quest_id": 20055001, + "quest_group_id": 1000, + "base_level": 18, + "minimum_item_rank": 0, + "discoverable": false, + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 590 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 90 + }, + { + "type": "exp", + "amount": 830 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 448, + "num": 1 + }, + { + "item_id": 61, + "num": 2 + }, + { + "item_id": 9409, + "num": 1 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 1, + "group_id": 57 + }, + "enemies": [ + { + "comment": "Sphinx", + "enemy_id": "0x015302", + "named_enemy_params_id": 459, + "level": 18, + "exp": 2950, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [ 0 ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550011 Ape.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550011 Ape.json new file mode 100644 index 000000000..42cb9f378 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550011 Ape.json @@ -0,0 +1,74 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "A Strange Creature Dances In the Forest", + "quest_id": 21155112, + "quest_group_id": 1000, + "base_level": 15, + "minimum_item_rank": 0, + "discoverable": false, + "parent_quest_id": 20055001, + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 490 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 70 + }, + { + "type": "exp", + "amount": 690 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 448, + "num": 1 + }, + { + "item_id": 61, + "num": 2 + }, + { + "item_id": 9409, + "num": 1 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 1, + "group_id": 57 + }, + "enemies": [ + { + "comment": "Young Dread Ape", + "enemy_id": "0x015502", + "named_enemy_params_id": 459, + "level": 15, + "exp": 2780, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [ 0 ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json index a5a1fec7a..8da7278b6 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json @@ -1,88 +1,89 @@ { - "state_machine": "GenericStateMachine", - "type": "World", - "comment": "The Abductors’ True Nature", - "quest_id": 20055004, - "base_level": 19, - "minimum_item_rank": 0, - "discoverable": false, - "rewards": [ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Abductors’ True Nature", + "quest_id": 20055004, + "quest_group_id": 1001, + "base_level": 19, + "minimum_item_rank": 0, + "discoverable": false, + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 520 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 100 + }, + { + "type": "exp", + "amount": 750 + }, + { + "type": "select", + "loot_pool": [ { - "type": "wallet", - "wallet_type": "Gold", - "amount": 520 + "item_id": 8173, + "num": 1 }, { - "type": "wallet", - "wallet_type": "RiftPoints", - "amount": 100 + "item_id": 9376, + "num": 2 }, { - "type": "exp", - "amount": 750 - }, - { - "type": "select", - "loot_pool": [ - { - "item_id": 8173, - "num": 1 - }, - { - "item_id": 9376, - "num": 2 - }, - { - "item_id": 9368, - "num": 3 - } - ] + "item_id": 9368, + "num": 3 } - ], - "enemy_groups" : [ + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 67, + "group_id": 4 + }, + "enemies": [ { - "stage_id": { - "id": 67, - "group_id": 4 - }, - "enemies": [ - { - "enemy_id": "0x011110", - "level": 19, - "exp": 200, - "is_boss": false - }, - { - "enemy_id": "0x011110", - "level": 19, - "exp": 200, - "is_boss": false - }, - { - "enemy_id": "0x011110", - "level": 19, - "exp": 200, - "is_boss": false - }, - { - "enemy_id": "0x011110", - "level": 19, - "exp": 200, - "is_boss": false - } - ] - } - ], - "blocks": [ + "enemy_id": "0x011110", + "level": 19, + "exp": 200, + "is_boss": false + }, + { + "enemy_id": "0x011110", + "level": 19, + "exp": 200, + "is_boss": false + }, { - "type": "DiscoverEnemy", - "groups": [0] + "enemy_id": "0x011110", + "level": 19, + "exp": 200, + "is_boss": false }, { - "type": "KillGroup", - "announce_type": "Accept", - "reset_group": false, - "groups": [0] + "enemy_id": "0x011110", + "level": 19, + "exp": 200, + "is_boss": false } - ] + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [ 0 ] + } + ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json deleted file mode 100644 index 86645fc7f..000000000 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "state_machine": "GenericStateMachine", - "type": "World", - "comment": "The Abductors’ True Nature", - "quest_id": 20055004, - "base_level": 31, - "minimum_item_rank": 0, - "discoverable": false, - "rewards": [ - { - "type": "wallet", - "wallet_type": "Gold", - "amount": 1020 - }, - { - "type": "wallet", - "wallet_type": "RiftPoints", - "amount": 160 - }, - { - "type": "exp", - "amount": 1430 - }, - { - "type": "select", - "loot_pool": [ - { - "item_id": 605, - "num": 1 - }, - { - "item_id": 9411, - "num": 2 - }, - { - "item_id": 52, - "num": 3 - } - ] - } - ], - "enemy_groups" : [ - { - "stage_id": { - "id": 67, - "group_id": 4 - }, - "enemies": [ - { - "enemy_id": "0x015040", - "level": 31, - "exp": 5000, - "is_boss": true - } - ] - } - ], - "blocks": [ - { - "type": "DiscoverEnemy", - "groups": [0] - }, - { - "type": "KillGroup", - "announce_type": "Accept", - "reset_group": false, - "groups": [0] - } - ] -} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550041 Troll.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550041 Troll.json new file mode 100644 index 000000000..2bbad2148 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550041 Troll.json @@ -0,0 +1,72 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Abductors’ True Nature", + "quest_id": 21155114, + "quest_group_id": 1001, + "base_level": 31, + "minimum_item_rank": 0, + "discoverable": false, + "parent_quest_id": 20055004, + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1020 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 160 + }, + { + "type": "exp", + "amount": 1430 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 605, + "num": 1 + }, + { + "item_id": 9411, + "num": 2 + }, + { + "item_id": 52, + "num": 3 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 67, + "group_id": 4 + }, + "enemies": [ + { + "enemy_id": "0x015040", + "level": 31, + "exp": 5000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [ 0 ] + } + ] +} From 46ec2211fcc17796580ed8a8f2f6e30a781d85f4 Mon Sep 17 00:00:00 2001 From: Asterit Date: Mon, 26 Aug 2024 18:07:59 +0100 Subject: [PATCH 002/116] restored json files to original titles, and cleaned up unused fields. This implementation uses a 'quest_group_id' to group quests together. If they do use the same questId as each other then this will have to be implemented in another way. --- .../Assets/quests/{q200550011 Ape.json => q20055001 Ape.json} | 1 - .../quests/{q200550041 Troll.json => q20055004 Troll.json} | 1 - 2 files changed, 2 deletions(-) rename Arrowgene.Ddon.Shared/Files/Assets/quests/{q200550011 Ape.json => q20055001 Ape.json} (97%) rename Arrowgene.Ddon.Shared/Files/Assets/quests/{q200550041 Troll.json => q20055004 Troll.json} (97%) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550011 Ape.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json similarity index 97% rename from Arrowgene.Ddon.Shared/Files/Assets/quests/q200550011 Ape.json rename to Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json index 42cb9f378..52b6e1551 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550011 Ape.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json @@ -7,7 +7,6 @@ "base_level": 15, "minimum_item_rank": 0, "discoverable": false, - "parent_quest_id": 20055001, "rewards": [ { "type": "wallet", diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550041 Troll.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json similarity index 97% rename from Arrowgene.Ddon.Shared/Files/Assets/quests/q200550041 Troll.json rename to Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json index 2bbad2148..35ff48373 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q200550041 Troll.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json @@ -7,7 +7,6 @@ "base_level": 31, "minimum_item_rank": 0, "discoverable": false, - "parent_quest_id": 20055004, "rewards": [ { "type": "wallet", From 763c9bfb0fe1a45d7be33eac41c9992cd4a38777 Mon Sep 17 00:00:00 2001 From: Asterit Date: Thu, 29 Aug 2024 18:27:23 +0100 Subject: [PATCH 003/116] Added a way for variant quests to be added to the project. This version stored a variant id within quest progress if a quest has started. This is used to find the specific quest version the client needs to show the correct enemies. Rewards may still not work as expected. Needs further looking at. --- .../Arrowgene.Ddon.Database.csproj | 4 + .../DdonDatabaseBuilder.cs | 2 +- .../Script/migration_quest_variant.sql | 2 + .../Files/Database/Script/schema_sqlite.sql | 1 + Arrowgene.Ddon.Database/IDatabase.cs | 2 +- .../Sql/Core/DdonSqlQuestProgress.cs | 52 +++--- .../00000015_QuestVariantMigration.cs | 24 +++ .../Characters/QuestManager.cs | 141 ++++++++------- .../Handler/InstanceEnemyKillHandler.cs | 2 +- .../Handler/InstanceGetEnemySetListHandler.cs | 2 +- .../Handler/PartyPartyCreateHandler.cs | 8 + .../Handler/QuestCancelHandler.cs | 2 +- .../QuestGetCycleContentsStateListHandler.cs | 2 +- .../Handler/QuestGetMainQuestListHandler.cs | 2 +- .../QuestGetPartyQuestProgressInfoHandler.cs | 2 +- .../Handler/QuestGetPriorityQuestHandler.cs | 2 +- .../Handler/QuestGetSetQuestListHandler.cs | 2 +- .../Handler/QuestQuestOrderHandler.cs | 2 +- .../Handler/QuestQuestProgressHandler.cs | 15 +- .../Handler/QuestSetPriorityQuestHandler.cs | 2 +- .../Party/PartyQuestState.cs | 162 +++++++++++++----- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 8 +- Arrowgene.Ddon.Shared/Asset/QuestAsset.cs | 3 +- .../AssetReader/QuestAssetDeserializer.cs | 6 +- .../Files/Assets/quests/q20055001 Ape.json | 4 +- .../Files/Assets/quests/q20055001 Sphinx.json | 4 +- .../Assets/quests/q20055004 Redcaps.json | 2 +- .../Files/Assets/quests/q20055004 Troll.json | 4 +- .../Model/Quest/QuestProgress.cs | 1 + .../Database/DatabaseMigratorTest.cs | 2 +- 30 files changed, 308 insertions(+), 159 deletions(-) create mode 100644 Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql create mode 100644 Arrowgene.Ddon.Database/Sql/Core/Migration/00000015_QuestVariantMigration.cs diff --git a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj index 165eba20a..230959bf0 100644 --- a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj +++ b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj @@ -39,6 +39,7 @@ + @@ -98,6 +99,9 @@ PreserveNewest + + PreserveNewest + diff --git a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs index fd8cd897d..e984bb508 100644 --- a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs +++ b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs @@ -14,7 +14,7 @@ public static class DdonDatabaseBuilder private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonDatabaseBuilder)); private const string DefaultSchemaFile = "Script/schema_sqlite.sql"; - public const uint Version = 14; + public const uint Version = 15; public static IDatabase Build(DatabaseSetting settings) { diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql new file mode 100644 index 000000000..18b50549e --- /dev/null +++ b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql @@ -0,0 +1,2 @@ +ALTER TABLE "ddon_quest_progress" + ADD COLUMN "variant_id" INTEGER NOT NULL DEFAULT 0; diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql index 9c1569014..0d9fa7685 100644 --- a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql +++ b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql @@ -551,6 +551,7 @@ CREATE TABLE IF NOT EXISTS "ddon_quest_progress" "quest_type" INTEGER NOT NULL, "quest_id" INTEGER NOT NULL, "step" INTEGER NOT NULL, + "variant_id" INTEGER NOT NULL, CONSTRAINT "fk_ddon_quest_progress_character_common_id" FOREIGN KEY ("character_common_id") REFERENCES "ddon_character_common" ("character_common_id") ON DELETE CASCADE ); diff --git a/Arrowgene.Ddon.Database/IDatabase.cs b/Arrowgene.Ddon.Database/IDatabase.cs index a49cfa5c6..f2e9fa4e3 100644 --- a/Arrowgene.Ddon.Database/IDatabase.cs +++ b/Arrowgene.Ddon.Database/IDatabase.cs @@ -264,7 +264,7 @@ int UpdateContact(uint requestingCharacterId, uint requestedCharacterId, Contact bool InsertIfNotExistCompletedQuest(uint characterCommonId, QuestId questId, QuestType questType); // Quest Progress - bool InsertQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step); + bool InsertQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step, uint variantId=0); bool UpdateQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step); bool RemoveQuestProgress(uint characterCommonId, QuestId questId, QuestType questType); List GetQuestProgressByType(uint characterCommonId, QuestType questType); diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlQuestProgress.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlQuestProgress.cs index e55019fb3..c7428938d 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlQuestProgress.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlQuestProgress.cs @@ -1,8 +1,6 @@ +using Arrowgene.Ddon.Shared.Model.Quest; using System.Collections.Generic; -using System.ComponentModel.Design; using System.Data.Common; -using System.Xml; -using Arrowgene.Ddon.Shared.Model.Quest; namespace Arrowgene.Ddon.Database.Sql.Core { @@ -13,6 +11,11 @@ public abstract partial class DdonSqlDb : SqlDb : SqlDb GetQuestProgressByType(TCon connection, uint characte ExecuteInTransaction(conn => { ExecuteReader(conn, SqlSelectQuestProgressByType, - command => { + command => + { AddParameter(command, "@character_common_id", characterCommonId); AddParameter(command, "@quest_type", (uint)questType); - }, reader => { + }, reader => + { while (reader.Read()) { var result = ReadQuestProgress(reader); @@ -71,9 +76,11 @@ private List GetAllQuestProgress(TCon connection, uint characterC ExecuteInTransaction(conn => { ExecuteReader(conn, SqlSelectAllQuestProgress, - command => { + command => + { AddParameter(command, "@character_common_id", characterCommonId); - }, reader => { + }, reader => + { while (reader.Read()) { var result = ReadQuestProgress(reader); @@ -125,26 +132,28 @@ public bool RemoveQuestProgress(TCon connection, uint characterCommonId, QuestId return ExecuteNonQuery(connection, SqlDeleteQuestProgress, command => { AddParameter(command, "character_common_id", characterCommonId); - AddParameter(command, "quest_type", (uint) questType); - AddParameter(command, "quest_id", (uint) questId); + AddParameter(command, "quest_type", (uint)questType); + AddParameter(command, "quest_id", (uint)questId); }) == 1; } - public bool InsertQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step) + public bool InsertQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step, uint variantId=0) { using TCon connection = OpenNewConnection(); - return InsertQuestProgress(connection, characterCommonId, questId, questType, step); + return InsertQuestProgress(connection, characterCommonId, questId, questType, step, variantId); } - public bool InsertQuestProgress(TCon connection, uint characterCommonId, QuestId questId, QuestType questType, uint step) + public bool InsertQuestProgress(TCon connection, uint characterCommonId, QuestId questId, QuestType questType, uint step, uint variantId=0) { - return ExecuteNonQuery(connection, SqlInsertQuestProgress, command => - { - AddParameter(command, "character_common_id", characterCommonId); - AddParameter(command, "quest_id", (uint)questId); - AddParameter(command, "quest_type", (uint)questType); - AddParameter(command, "step", (uint) step); - }) == 1; + return ExecuteNonQuery(connection, SqlInsertQuestProgress, command => + { + AddParameter(command, "character_common_id", characterCommonId); + AddParameter(command, "quest_id", (uint)questId); + AddParameter(command, "quest_type", (uint)questType); + AddParameter(command, "step", (uint)step); + AddParameter(command, "variant_id", variantId); + + }) == 1; } public bool UpdateQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step) @@ -155,7 +164,7 @@ public bool UpdateQuestProgress(uint characterCommonId, QuestId questId, QuestTy public bool UpdateQuestProgress(TCon connection, uint characterCommonId, QuestId questId, QuestType questType, uint step) { - return ExecuteNonQuery(connection, SqlUpdateQuestProgress, command => + return ExecuteNonQuery(connection, SqlUpdateQuestProgress, command => { AddParameter(command, "character_common_id", characterCommonId); AddParameter(command, "quest_id", (uint)questId); @@ -171,6 +180,7 @@ private QuestProgress ReadQuestProgress(TReader reader) obj.QuestId = (QuestId)GetUInt32(reader, "quest_id"); obj.QuestType = (QuestType)GetUInt32(reader, "quest_type"); obj.Step = GetUInt32(reader, "step"); + obj.VariantId = GetUInt32(reader, "variant_id"); return obj; } } diff --git a/Arrowgene.Ddon.Database/Sql/Core/Migration/00000015_QuestVariantMigration.cs b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000015_QuestVariantMigration.cs new file mode 100644 index 000000000..0e5259f6f --- /dev/null +++ b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000015_QuestVariantMigration.cs @@ -0,0 +1,24 @@ +using System.Data.Common; + +namespace Arrowgene.Ddon.Database.Sql.Core.Migration +{ + public class QuestVariationMigration : IMigrationStrategy + { + public uint From => 14; + public uint To => 15; + + private readonly DatabaseSetting DatabaseSetting; + + public QuestVariationMigration(DatabaseSetting databaseSetting) + { + DatabaseSetting = databaseSetting; + } + + public bool Migrate(IDatabase db, DbConnection conn) + { + string adaptedSchema = DdonDatabaseBuilder.GetAdaptedSchema(DatabaseSetting, "Script/migration_quest_variant.sql"); + db.Execute(conn, adaptedSchema); + return true; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 85e9b7337..d1f68dfe9 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -1,21 +1,13 @@ -using Arrowgene.Ddon.Database; using Arrowgene.Ddon.GameServer.Quests; -using Arrowgene.Ddon.GameServer.Quests.MainQuests; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared; -using Arrowgene.Ddon.Shared.Asset; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Logging; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Text.Json; -using YamlDotNet.Core.Events; -using YamlDotNet.Core.Tokens; -using static Arrowgene.Ddon.GameServer.Characters.QuestManager; namespace Arrowgene.Ddon.GameServer.Characters { @@ -28,18 +20,15 @@ private QuestManager() } private static Dictionary gQuests = new Dictionary(); - private static Dictionary> altQuestsGroups = new(); - private static Dictionary altQuestLookup = new(); - public static readonly HashSet questGroupIds = new(); + //private static Dictionary VariantQuests = new Dictionary(); + private static readonly Dictionary> variantQuests = new(); + private static readonly HashSet AvailableVariantQuests = new(); + //private static Dictionary altQuestLookup = new(); + //public static readonly HashSet questGroupIds = new(); - public static void CopyQuestGroupIds(HashSet InactiveAlternateQuestsGroups) + public static HashSet GetAllVariantQuestIds() { - - for (int i = 0; i < questGroupIds.Count; i++) - { - InactiveAlternateQuestsGroups.Add(questGroupIds.ElementAt(i)); - } - + return AvailableVariantQuests; } public static void LoadQuests(AssetRepository assetRepository) @@ -53,35 +42,48 @@ public static void LoadQuests(AssetRepository assetRepository) // gQuests[QuestId.TheGreatAlchemist] = new Mq000025_TheGreatAlchemist(); // gQuests[QuestId.HopesBitterEnd] = new Mq030260_HopesBitterEnd(); + HashSet allVariantQuestIds = new(); + // Load Quests defined in files foreach (var questAsset in assetRepository.QuestAssets.Quests) { - // Check if this quest has a group quest id - if so then handle it separately + // Separate all variant quests to its own dictionary for separate handling. - if (questAsset.QuestGroupId is not null) + if (questAsset.VariantId is not null) { + if (questAsset.VariantId == 0) + { + Logger.Error($"Variant Id being 0 is a reserved value, please reassign to another number."); + continue; + } + Quest alternateQuest = GenericQuest.FromAsset(questAsset); - alternateQuest.IsAlternateQuest = true; - alternateQuest.QuestGroupId = questAsset.QuestGroupId; + alternateQuest.IsVariantQuest = true; + alternateQuest.VariantId = questAsset.VariantId; - // Add an entry to the dictionary if it doesn't exist then add the quest id and quest - if (!altQuestsGroups.ContainsKey((uint)questAsset.QuestGroupId)) + // Ensure variant ids are unique. Throw early to not clog up the logs. + try + { + allVariantQuestIds.Add((uint)alternateQuest.VariantId); + } + catch (Exception) { - altQuestsGroups[(uint)alternateQuest.QuestGroupId] = new Dictionary(); - altQuestsGroups[(uint)questAsset.QuestGroupId].Add(alternateQuest.QuestId, alternateQuest); + Logger.Error($"Multiple quests are using variant id {alternateQuest.VariantId}. Please ensure all are unique."); + throw; + } - // Add to the quest lookup for easier finding of quests for explicit search. - altQuestLookup[alternateQuest.QuestId] = (uint)alternateQuest.QuestGroupId; + // Add an entry to the dictionary if it doesn't exist then add the variant id and quest + if (!variantQuests.ContainsKey(questAsset.QuestId)) + { + variantQuests[alternateQuest.QuestId] = new Dictionary(); + variantQuests[questAsset.QuestId].Add((uint)alternateQuest.VariantId, alternateQuest); continue; } // Add quest id and quest - altQuestsGroups[(uint)questAsset.QuestGroupId].Add(alternateQuest.QuestId, alternateQuest); - - // Add to the quest lookup for explicit searches. - altQuestLookup[alternateQuest.QuestId] = (uint)alternateQuest.QuestGroupId; + variantQuests[questAsset.QuestId].Add((uint)alternateQuest.VariantId, alternateQuest); continue; } @@ -90,19 +92,20 @@ public static void LoadQuests(AssetRepository assetRepository) } - var allAltQuestsInDictionary = altQuestsGroups.Keys.ToArray(); + var variantQuestKeys = variantQuests.Keys.ToArray(); - for (int i = 0; i < allAltQuestsInDictionary.Length; i++) + for (int i = 0; i < variantQuestKeys.Length; i++) { - // Create a reliable source of all quest IDs - questGroupIds.Add(allAltQuestsInDictionary[i]); + // Create a reliable source of all variant quests - Logger.Info($"Alt Group Id Listed: {allAltQuestsInDictionary[i]}"); - var questKeys = altQuestsGroups[allAltQuestsInDictionary[i]].Keys.ToArray(); + AvailableVariantQuests.Add(variantQuestKeys[i]); - for(int j = 0; j < questKeys.Length; j++) + Logger.Info($"Quest Group Listed: {variantQuestKeys[i]}"); + var variantIds = variantQuests[variantQuestKeys[i]].Keys.ToArray(); + + for (int j = 0; j < variantIds.Length; j++) { - Logger.Info($"Quest entry: {questKeys[j]}"); + Logger.Info($"Variant entry: {variantIds[j]}"); } } } @@ -128,34 +131,44 @@ public static List> GetQuestsByType(QuestType type) } } - return results; - } + // Go over the variant quest collection, get a single quest per questId regardless of the variant id - public static QuestId GetRandomAltQuest(uint questGroupId) - { - // Get random index value to choose a version. - int randomIndex = new Random().Next(altQuestsGroups[questGroupId].Count); - - QuestId questKey = altQuestsGroups[questGroupId].ElementAt(randomIndex).Key; + foreach (var quests in variantQuests) + { + QuestId questId = quests.Key; + Quest quest = variantQuests[questId].First().Value; - return questKey; + if (quest.QuestType == type) + { + results.Add(new KeyValuePair(questId, quest)); + } + } + return results; } - public static Quest GetQuest(QuestId questId) + public static uint GetRandomVariantQuest(QuestId baseQuest) { + // Get random index value to choose a quest version. + int randomIndex = new Random().Next(variantQuests[baseQuest].Count); + uint variantId = variantQuests[baseQuest].ElementAt(randomIndex).Key; + return variantId; + } - if (altQuestLookup.ContainsKey(questId)) + public static Quest GetQuest(QuestId questId, uint? variantId = null) + { + // If a variant is specified, return the variant quest. + if (variantId is not null) { - uint groupIdKey = altQuestLookup[questId]; - - return altQuestsGroups[groupIdKey][questId]; + //Logger.Debug("Variant Quest found. Returning quest variant."); + return variantQuests[questId][(uint)variantId]; } if (!gQuests.ContainsKey(questId)) { return null; } + return gQuests[questId]; } @@ -191,17 +204,17 @@ public static CDataQuestOrderConditionParam NoRestriction() } public static CDataQuestOrderConditionParam MinimumLevelRestriction(uint level) { - return new CDataQuestOrderConditionParam() { Type = 0x1, Param01 = (int) level }; + return new CDataQuestOrderConditionParam() { Type = 0x1, Param01 = (int)level }; } public static CDataQuestOrderConditionParam MinimumVocationRestriction(JobId jobId, uint level) { - return new CDataQuestOrderConditionParam() { Type = 0x2, Param01 = (int)jobId, Param02 = (int) level}; + return new CDataQuestOrderConditionParam() { Type = 0x2, Param01 = (int)jobId, Param02 = (int)level }; } public static CDataQuestOrderConditionParam Solo() { - return new CDataQuestOrderConditionParam() { Type = 0x3}; + return new CDataQuestOrderConditionParam() { Type = 0x3 }; } public static CDataQuestOrderConditionParam MainQuestCompletionRestriction(QuestId questId) @@ -216,7 +229,7 @@ public static CDataQuestOrderConditionParam ClearPersonalQuestRestriction(int pa public static CDataQuestOrderConditionParam ClearPersonalQuestRestriction(QuestId questId, int param02 = 0) { - return new CDataQuestOrderConditionParam() { Type = 0x7, Param01 = (int) questId, Param02 = param02 }; + return new CDataQuestOrderConditionParam() { Type = 0x7, Param01 = (int)questId, Param02 = param02 }; } } @@ -224,7 +237,9 @@ public static CDataQuestProcessState CreateQuestProcessState(ushort processNo, u { return new CDataQuestProcessState() { - ProcessNo = processNo, SequenceNo = sequenceNo, BlockNo = blockNo, + ProcessNo = processNo, + SequenceNo = sequenceNo, + BlockNo = blockNo, ResultCommandList = resultCommands, CheckCommandList = QuestManager.CheckCommand.AddCheckCommands(checkCommands) }; @@ -2284,7 +2299,7 @@ public static CDataQuestCommand SetAnnounce(QuestAnnounceType announceType, int */ public static CDataQuestCommand UpdateAnnounce(QuestAnnounceType announceType = QuestAnnounceType.Accept, int param02 = 0, int param03 = 0, int param04 = 0) { - return new CDataQuestCommand() { Command = (ushort)QuestResultCommand.UpdateAnnounce, Param01 = (int) announceType, Param02 = param02, Param03 = param03, Param04 = param04 }; + return new CDataQuestCommand() { Command = (ushort)QuestResultCommand.UpdateAnnounce, Param01 = (int)announceType, Param02 = param02, Param03 = param03, Param04 = param04 }; } /** @@ -2940,7 +2955,7 @@ public static CDataQuestCommand BgmRequestFix(int type, int bgmId, int param03 = */ public static CDataQuestCommand EventExecCont(StageNo stageNo, int eventNo, StageNo jumpStageNo, int jumpStartPosNo) { - return new CDataQuestCommand() { Command = (ushort)QuestResultCommand.EventExecCont, Param01 = (int)stageNo, Param02 = eventNo, Param03 = (int) jumpStageNo, Param04 = jumpStartPosNo }; + return new CDataQuestCommand() { Command = (ushort)QuestResultCommand.EventExecCont, Param01 = (int)stageNo, Param02 = eventNo, Param03 = (int)jumpStageNo, Param04 = jumpStartPosNo }; } /** @@ -3180,7 +3195,7 @@ public static CDataQuestProgressWork Unknown(uint commandNo, int work01 = 0, int */ public static CDataQuestProgressWork KilledTargetEnemySetGroup(int flagNo, StageNo stageNo, int groupNo, int work04 = 0) { - return new CDataQuestProgressWork() { CommandNo = (uint) QuestNotifyCommand.KilledTargetEnemySetGroup, Work01 = flagNo, Work02 = (int)stageNo, Work03 = groupNo, Work04 = work04 }; + return new CDataQuestProgressWork() { CommandNo = (uint)QuestNotifyCommand.KilledTargetEnemySetGroup, Work01 = flagNo, Work02 = (int)stageNo, Work03 = groupNo, Work04 = work04 }; } /** @@ -3200,7 +3215,7 @@ public static CDataQuestProgressWork KilledTargetEmSetGrpNoMarker(int flagNo, St */ public static CDataQuestProgressWork KilledTargetEnemySetGroup1(NpcId npcId, int work02 = 0, int work03 = 0, int work04 = 0) { - return new CDataQuestProgressWork() { CommandNo = (uint)QuestNotifyCommand.FulfillDeliverItem, Work01 = (int) npcId, Work02 = work02, Work03 = work03, Work04 = work04 }; + return new CDataQuestProgressWork() { CommandNo = (uint)QuestNotifyCommand.FulfillDeliverItem, Work01 = (int)npcId, Work02 = work02, Work03 = work03, Work04 = work04 }; } } diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index bb9b39a73..ecb142ffa 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -36,7 +36,7 @@ public override void Handle(GameClient client, StructurePacket ActiveQuests { get; set; } private Dictionary> QuestLookupTable { get; set; } private List CompletedWorldQuests { get; set; } - private HashSet InactiveAlternateQuestsGroups { get; set; } - private HashSet ActiveAlternateQuests { get; set; } + private Dictionary ActiveVariantQuests { get; set; } + // For the purposes of each party quest state knowing the possible variant quests + private HashSet VariantQuests { get; set; } public PartyQuestState() { ActiveQuests = new Dictionary(); QuestLookupTable = new Dictionary>(); CompletedWorldQuests = new List(); - InactiveAlternateQuestsGroups = new HashSet(); - ActiveAlternateQuests = new(); + ActiveVariantQuests = new Dictionary(); + VariantQuests = QuestManager.GetAllVariantQuestIds(); + } - QuestManager.CopyQuestGroupIds(InactiveAlternateQuestsGroups); + public void SetHasStarted(QuestId questId, bool activeState) + { + ActiveQuests[questId].HasStarted = activeState; } - public void SetHasStarted(QuestId questId, bool hasStarted) + public Quest GetQuest(QuestId questId) { - ActiveQuests[questId].HasStarted = hasStarted; + if (ActiveVariantQuests.ContainsKey(questId)) + { + // Look inside the ActiveVariantQuests and get the quest variant id to be used to get back the specific quest. + return QuestManager.GetQuest(questId, ActiveVariantQuests[questId]); + } + + return QuestManager.GetQuest(questId); } public void AddNewQuest(Quest quest, uint step = 0, bool questStarted = false) @@ -200,7 +210,7 @@ public List GetInstancedEnemies(Quest quest, StageId stageId, us public List GetInstancedEnemies(QuestId questId, StageId stageId, ushort subGroupId) { - var quest = QuestManager.GetQuest(questId); + var quest = GetQuest(questId); return GetInstancedEnemies(quest, stageId, subGroupId); } @@ -223,7 +233,7 @@ public InstancedEnemy GetInstancedEnemy(Quest quest, StageId stageId, ushort sub public InstancedEnemy GetInstancedEnemy(QuestId questId, StageId stageId, ushort subGroupId, uint index) { - var quest = QuestManager.GetQuest(questId); + var quest = GetQuest(questId); return GetInstancedEnemy(quest, stageId, subGroupId, index); } @@ -249,42 +259,92 @@ public ushort GetInstanceSubgroupId(Quest quest, StageId stageId) } } - public void AddNewAltQuest(uint questGroupId) + //public uint GetNewVariantQuest(QuestId questId) + //{ + // return QuestManager.GetRandomVariantQuest(questId); + + //} + + public void AddNewQuest(QuestId questId, uint step, bool questStarted, uint variantId) { - var questId = QuestManager.GetRandomAltQuest(questGroupId); + //Logger.Info($"Specifically adding new quest for {questId} on variant {variantId}"); - AddNewQuest(questId, 0, false); + Quest quest = QuestManager.GetQuest(questId, variantId); + + if (VariantQuests.Contains(questId)) + { + ActiveVariantQuests[questId] = (uint)quest.VariantId; + AddNewQuest(quest, step, questStarted); + } } public void AddNewQuest(QuestId questId, uint step, bool questStarted) { + //Logger.Debug($"AddNewQuest - called with {questId}"); - var quest = QuestManager.GetQuest(questId); + //Logger.Debug($"Is {questId} contained within ActiveVariantQuests? ---->>>>>>>> {ActiveVariantQuests.ContainsKey(questId)}"); - // If we are adding a new alt quest, remove the quest group id from the list. - if (quest.IsAlternateQuest && InactiveAlternateQuestsGroups.Contains((uint)quest.QuestGroupId)) + // Trying to add a new variant quest before properly removing it will cause an exception. + + if (ActiveVariantQuests.ContainsKey(questId)) { + return; + } - InactiveAlternateQuestsGroups.Remove((uint)quest.QuestGroupId); - ActiveAlternateQuests.Add(quest.QuestId); + Quest quest; + // If the quest we are trying to add is a variant quest, then roll and get a random version. + if (VariantQuests.Contains(questId)) // && !ActiveVariantQuests.ContainsKey(questId)) + { + //Logger.Debug($"VariantQuests contains {questId}!!!"); + quest = QuestManager.GetQuest(questId, QuestManager.GetRandomVariantQuest(questId)); + //Logger.Debug($"Got back {quest.QuestId} with variantId {quest.VariantId}"); + } + else + { + quest = GetQuest(questId); + //Logger.Debug($"Quest Returned: {quest.QuestId}"); } + // If we are adding a new variant quest, then log the variant id for further reference + if (quest.IsVariantQuest) + { + Logger.Debug($"Adding to ActiveVariantQuest -> quest: {quest.QuestId} -> variantId: {quest.VariantId}"); + ActiveVariantQuests.Add(quest.QuestId, (uint)quest.VariantId); + } + + // If we are adding a new alt quest, remove the quest group id from the list. + //if (quest.IsVariantQuest && InactiveAlternateQuestsGroups.Contains((uint)quest.QuestGroupId)) + //{ + + // InactiveAlternateQuestsGroups.Remove((uint)quest.QuestGroupId); + // ActiveVariantQuests.Add(quest.QuestId); + + //} + AddNewQuest(quest, step, questStarted); } public void RemoveQuest(QuestId questId) { - var quest = QuestManager.GetQuest(questId); + var quest = GetQuest(questId); lock (ActiveQuests) { ActiveQuests.Remove(questId); // Ensure adding of selected quest's group id to be eligible for reroll. - if(quest.IsAlternateQuest) - { - InactiveAlternateQuestsGroups.Add((uint)quest.QuestGroupId); - ActiveAlternateQuests.Remove(quest.QuestId); + // if(quest.IsVariantQuest) + //{ + // InactiveAlternateQuestsGroups.Add((uint)quest.QuestGroupId); + // ActiveVariantQuests.Remove(quest.QuestId); + //} + + if (ActiveVariantQuests.ContainsKey(questId)) + { + Logger.Debug($"Number of activeVariantQuests BEFORE --->>> {ActiveVariantQuests.Count}"); + ActiveVariantQuests.Remove(questId); + Logger.Debug($"Removed variant quest {questId}"); + Logger.Debug($"Number of activeVariantQuests AFTER --->>> {ActiveVariantQuests.Count}"); } foreach (var location in quest.Locations) @@ -301,13 +361,13 @@ public void CancelQuest(QuestId questId) { lock (CompletedWorldQuests) { - var quest = QuestManager.GetQuest(questId); + var quest = GetQuest(questId); RemoveQuest(questId); // Save the quest if it was a world quest // so we can add it back on instance reset // Don't add alt quests to completed world quests. Rerolls are handled independently - if (quest.QuestType == QuestType.World && !quest.IsAlternateQuest) + if (quest.QuestType == QuestType.World && !quest.IsVariantQuest) { CompletedWorldQuests.Add(questId); } @@ -318,12 +378,13 @@ public void CompleteQuest(QuestId questId) { lock (CompletedWorldQuests) { - var quest = QuestManager.GetQuest(questId); + var quest = GetQuest(questId); RemoveQuest(questId); // Save the quest if it was a world quest // so we can add it back on instance reset - if (quest.QuestType == QuestType.World) + // Don't add alt quests to completed world quests. Rerolls are handled independently + if (quest.QuestType == QuestType.World && !quest.IsVariantQuest) { CompletedWorldQuests.Add(questId); } @@ -365,7 +426,7 @@ public List StageQuests(StageId stageId) public bool HasQuest(QuestId questId) { - lock(ActiveQuests) + lock (ActiveQuests) { return ActiveQuests.ContainsKey(questId); } @@ -387,7 +448,7 @@ public QuestState GetQuestState(Quest quest) public QuestState GetQuestState(QuestId questId) { - var quest = QuestManager.GetQuest(questId); + var quest = GetQuest(questId); return GetQuestState(quest); } @@ -440,23 +501,30 @@ public QuestProcessState GetProcessState(QuestId questId, ushort processNo) private void RerollUnfoundAltQuests() { - foreach (var quest in ActiveAlternateQuests) + // 1. Check all Active variant quests and see if they have started. + + foreach (var variantQuest in VariantQuests) { - switch (ActiveQuests[quest].HasStarted) + // 1. Check if the variant quest is not in either active quest lists + if (!ActiveVariantQuests.ContainsKey(variantQuest) && !ActiveQuests.ContainsKey(variantQuest)) { - case true: - continue; - case false: - RemoveQuest(quest); - continue; + // 2. Add a new variant quest if none were found. + AddNewQuest(variantQuest, 0, false); + continue; } - } - // See available group ids to choose from, then loop over and add a random quest from each. - - foreach (var groupId in InactiveAlternateQuestsGroups) - { - AddNewAltQuest(groupId); + // 3. Check if the quest exists within the larger active quest list + if (ActiveQuests.ContainsKey(variantQuest)) + switch (ActiveQuests[variantQuest].HasStarted) + { + // 4. If the quest is started, leave it alone, if not remove and add a new random quest. + case true: + continue; + case false: + RemoveQuest(variantQuest); + AddNewQuest(variantQuest, 0, false); + continue; + } } } @@ -475,7 +543,7 @@ public void ResetInstanceQuestState() public bool UpdatePartyQuestProgress(DdonGameServer server, PartyGroup party, QuestId questId) { - Quest quest = QuestManager.GetQuest(questId); + Quest quest = GetQuest(questId); var questState = party.QuestState.GetQuestState(quest); foreach (var memberClient in party.Clients) @@ -501,7 +569,7 @@ public bool UpdatePartyQuestProgress(DdonGameServer server, PartyGroup party, Qu public bool CompletePartyQuestProgress(DdonGameServer server, PartyGroup party, QuestId questId) { - Quest quest = QuestManager.GetQuest(questId); + Quest quest = GetQuest(questId); var questState = party.QuestState.GetQuestState(quest); foreach (var memberClient in party.Clients) @@ -521,7 +589,7 @@ public bool CompletePartyQuestProgress(DdonGameServer server, PartyGroup party, server.Database.RemoveQuestProgress(memberClient.Character.CommonId, quest.QuestId, quest.QuestType); if (quest.NextQuestId != QuestId.None) { - var nextQuest = QuestManager.GetQuest(quest.NextQuestId); + var nextQuest = GetQuest(quest.NextQuestId); server.Database.InsertQuestProgress(memberClient.Character.CommonId, nextQuest.QuestId, nextQuest.QuestType, 0); } @@ -536,7 +604,7 @@ public bool CompletePartyQuestProgress(DdonGameServer server, PartyGroup party, public bool DistributePartyQuestRewards(DdonGameServer server, PartyGroup party, QuestId questId) { - Quest quest = QuestManager.GetQuest(questId); + Quest quest = GetQuest(questId); var questState = party.QuestState.GetQuestState(quest); foreach (var memberClient in party.Clients) @@ -618,7 +686,7 @@ public void UpdatePriorityQuestList(DdonGameServer server, PartyGroup party) var priorityQuests = server.Database.GetPriorityQuests(leaderClient.Character.CommonId); foreach (var priorityQuestId in priorityQuests) { - var priorityQuest = QuestManager.GetQuest(priorityQuestId); + var priorityQuest = GetQuest(priorityQuestId); var questState = party.QuestState.GetQuestState(priorityQuest.QuestId); prioNtc.PriorityQuestList.Add(priorityQuest.ToCDataPriorityQuest(questState.Step)); } diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 58fd2ae23..0ce55117a 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -67,8 +67,8 @@ public abstract class Quest public List QuestLayoutFlagSetInfo; public List QuestLayoutFlags; public Dictionary EnemyGroups { get; set; } - public bool IsAlternateQuest { get; set; } - public uint ? QuestGroupId { get; set; } + public bool IsVariantQuest { get; set; } + public uint ? VariantId { get; set; } public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool isDiscoverable = false) { @@ -89,7 +89,7 @@ public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool QuestLayoutFlagSetInfo = new List(); QuestLayoutFlags = new List(); EnemyGroups = new Dictionary(); - IsAlternateQuest = false; + IsVariantQuest = false; Processes = new List(); } @@ -448,6 +448,8 @@ public static List AsCDataRewardBoxItems(QuestBoxRewards rew { List results = new List(); + // TODO: Add variant quest support to have rewards match + var quest = QuestManager.GetQuest(rewards.QuestId); foreach (var reward in quest.SelectableRewards) diff --git a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs index af1a862ef..c31ce32e4 100644 --- a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs +++ b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs @@ -34,7 +34,8 @@ public class QuestAssetData public List QuestLayoutFlags { get; set; } public List QuestLayoutSetInfoFlags { get; set; } public Dictionary EnemyGroups { get; set; } - public uint ? QuestGroupId { get; set; } + //public uint ? QuestGroupId { get; set; } + public uint ? VariantId { get; set; } public QuestAssetData() { diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index b87d78cbe..65c9ce79a 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -81,10 +81,10 @@ private bool ParseQuest(QuestAssetData assetData, JsonElement jQuest) assetData.Discoverable = jQuest.GetProperty("discoverable").GetBoolean(); // For the purpose of setting up alternate quests. - assetData.QuestGroupId = null; - if (jQuest.TryGetProperty("quest_group_id", out JsonElement AltQuestId)) + assetData.VariantId = null; + if (jQuest.TryGetProperty("variantId", out JsonElement AltQuestId)) { - assetData.QuestGroupId = AltQuestId.GetUInt32(); + assetData.VariantId = AltQuestId.GetUInt32(); } assetData.NextQuestId = 0; diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json index 52b6e1551..79d2e3800 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json @@ -2,8 +2,8 @@ "state_machine": "GenericStateMachine", "type": "World", "comment": "A Strange Creature Dances In the Forest", - "quest_id": 21155112, - "quest_group_id": 1000, + "quest_id": 20055001, + "variantId": 5876542, "base_level": 15, "minimum_item_rank": 0, "discoverable": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json index cea379cba..3280147b9 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json @@ -1,9 +1,9 @@ { "state_machine": "GenericStateMachine", "type": "World", - "comment": "A Strange Bird Dances In the Forest", + "comment": "A Strange Bird Dances In the Forest (alt quests carry the same title and questid. AKA they got lazy.)", "quest_id": 20055001, - "quest_group_id": 1000, + "variantId": 1020304, "base_level": 18, "minimum_item_rank": 0, "discoverable": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json index 8da7278b6..f2fa0cb72 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json @@ -3,7 +3,7 @@ "type": "World", "comment": "The Abductors’ True Nature", "quest_id": 20055004, - "quest_group_id": 1001, + "variantId": 7456284, "base_level": 19, "minimum_item_rank": 0, "discoverable": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json index 35ff48373..c573458bb 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json @@ -2,8 +2,8 @@ "state_machine": "GenericStateMachine", "type": "World", "comment": "The Abductors’ True Nature", - "quest_id": 21155114, - "quest_group_id": 1001, + "quest_id": 20055004, + "variantId": 9642584, "base_level": 31, "minimum_item_rank": 0, "discoverable": false, diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestProgress.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestProgress.cs index 515b095a0..96e69476b 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestProgress.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestProgress.cs @@ -13,6 +13,7 @@ public class QuestProgress public QuestId QuestId { get; set; } public QuestType QuestType { get; set; } public uint Step { get; set; } + public uint ? VariantId { get; set; } } public enum QuestProgressStatus : uint diff --git a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs index ca90aeb55..d73714ca2 100644 --- a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs +++ b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs @@ -247,7 +247,7 @@ public void Execute(DbConnection conn, string sql) {} public bool InsertNormalSkillParam(uint commonId, CDataNormalSkillParam normalSkillParam) { return true; } public bool InsertPawnTrainingStatus(uint pawnId, JobId job, byte[] pawnTrainingStatus) { return true; } public bool InsertPriorityQuest(uint characterCommonId, QuestId questId) { return true; } - public bool InsertQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step) { return true; } + public bool InsertQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step, uint? variantId = null) { return true; } public bool InsertReleasedWarpPoint(uint characterId, ReleasedWarpPoint ReleasedWarpPoint) { return true; } public bool InsertSecretAbilityUnlock(uint commonId, SecretAbility secretAbility) { return true; } public bool InsertShortcut(uint characterId, CDataShortCut shortcut) { return true; } From 40ea646239cac832099bd045626eed5cb4dc2162 Mon Sep 17 00:00:00 2001 From: Asterit Date: Thu, 29 Aug 2024 19:18:24 +0100 Subject: [PATCH 004/116] merged with latest --- .../Arrowgene - Backup.Ddon.GameServer.csproj | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj diff --git a/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj b/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj new file mode 100644 index 000000000..c1272072c --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj @@ -0,0 +1,22 @@ + + + net6.0 + Arrowgene.Ddon.GameServer + Dragons Dogma Online - Game Server + DDON Team + Ddon.GameServer + $(Version) + Copyright © 2019-2022 DDON Team + 10 + + + True + + + True + + + + + + From bfcda9379484bfee4a95fa1030bbdbd9e9d8108f Mon Sep 17 00:00:00 2001 From: Asterit Date: Thu, 29 Aug 2024 20:45:37 +0100 Subject: [PATCH 005/116] Fixed quest formatting and corrected variant id formats. --- Arrowgene.Ddon.Shared/Asset/QuestAsset.cs | 1 - .../AssetReader/QuestAssetDeserializer.cs | 2 +- .../Assets/quests/q20055001 Ape One.json | 3 +- .../Files/Assets/quests/q20055001 Ape.json | 73 ------------------- .../Assets/quests/q20055001 Sphinx one.json | 3 +- .../Files/Assets/quests/q20055001 Sphinx.json | 73 ------------------- .../Assets/quests/q20055004 Redcaps.json | 6 +- .../Files/Assets/quests/q20055004 Troll.json | 2 +- 8 files changed, 7 insertions(+), 156 deletions(-) delete mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json delete mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json diff --git a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs index 0199055d2..18b835d50 100644 --- a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs +++ b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs @@ -35,7 +35,6 @@ public class QuestAssetData public List QuestLayoutFlags { get; set; } public List QuestLayoutSetInfoFlags { get; set; } public Dictionary EnemyGroups { get; set; } - //public uint ? QuestGroupId { get; set; } public uint ? VariantId { get; set; } public QuestAssetData() diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 77404f0ac..7d1b7f0c2 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -95,7 +95,7 @@ private bool ParseQuest(QuestAssetData assetData, JsonElement jQuest) // For the purpose of setting up alternate quests. assetData.VariantId = null; - if (jQuest.TryGetProperty("variantId", out JsonElement AltQuestId)) + if (jQuest.TryGetProperty("variant_id", out JsonElement AltQuestId)) { assetData.VariantId = AltQuestId.GetUInt32(); } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape One.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape One.json index 3f98184b9..2e3977e82 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape One.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape One.json @@ -2,7 +2,8 @@ "state_machine": "GenericStateMachine", "type": "World", "comment": "A Strange Creature Dances In the Forest", - "quest_id": 2005501, + "quest_id": 20055001, + "variant_id": 587654, "base_level": 15, "minimum_item_rank": 0, "discoverable": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json deleted file mode 100644 index 79d2e3800..000000000 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Ape.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "state_machine": "GenericStateMachine", - "type": "World", - "comment": "A Strange Creature Dances In the Forest", - "quest_id": 20055001, - "variantId": 5876542, - "base_level": 15, - "minimum_item_rank": 0, - "discoverable": false, - "rewards": [ - { - "type": "wallet", - "wallet_type": "Gold", - "amount": 490 - }, - { - "type": "wallet", - "wallet_type": "RiftPoints", - "amount": 70 - }, - { - "type": "exp", - "amount": 690 - }, - { - "type": "select", - "loot_pool": [ - { - "item_id": 448, - "num": 1 - }, - { - "item_id": 61, - "num": 2 - }, - { - "item_id": 9409, - "num": 1 - } - ] - } - ], - "enemy_groups": [ - { - "stage_id": { - "id": 1, - "group_id": 57 - }, - "enemies": [ - { - "comment": "Young Dread Ape", - "enemy_id": "0x015502", - "named_enemy_params_id": 459, - "level": 15, - "exp": 2780, - "is_boss": true - } - ] - } - ], - "blocks": [ - { - "type": "DiscoverEnemy", - "groups": [ 0 ] - }, - { - "type": "KillGroup", - "announce_type": "Accept", - "reset_group": false, - "groups": [ 0 ] - } - ] -} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json index 32a9584a8..fd015b05f 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json @@ -2,7 +2,8 @@ "state_machine": "GenericStateMachine", "type": "World", "comment": "A Strange Bird Dances In the Forest", - "quest_id": 2005501, + "quest_id": 20055001, + "variant_id": 102030, "base_level": 18, "minimum_item_rank": 0, "discoverable": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json deleted file mode 100644 index 3280147b9..000000000 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "state_machine": "GenericStateMachine", - "type": "World", - "comment": "A Strange Bird Dances In the Forest (alt quests carry the same title and questid. AKA they got lazy.)", - "quest_id": 20055001, - "variantId": 1020304, - "base_level": 18, - "minimum_item_rank": 0, - "discoverable": false, - "rewards": [ - { - "type": "wallet", - "wallet_type": "Gold", - "amount": 590 - }, - { - "type": "wallet", - "wallet_type": "RiftPoints", - "amount": 90 - }, - { - "type": "exp", - "amount": 830 - }, - { - "type": "select", - "loot_pool": [ - { - "item_id": 448, - "num": 1 - }, - { - "item_id": 61, - "num": 2 - }, - { - "item_id": 9409, - "num": 1 - } - ] - } - ], - "enemy_groups": [ - { - "stage_id": { - "id": 1, - "group_id": 57 - }, - "enemies": [ - { - "comment": "Sphinx", - "enemy_id": "0x015302", - "named_enemy_params_id": 459, - "level": 18, - "exp": 2950, - "is_boss": true - } - ] - } - ], - "blocks": [ - { - "type": "DiscoverEnemy", - "groups": [ 0 ] - }, - { - "type": "KillGroup", - "announce_type": "Accept", - "reset_group": false, - "groups": [ 0 ] - } - ] -} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json index 99bdb4e13..ee9abfc82 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Redcaps.json @@ -3,6 +3,7 @@ "type": "World", "comment": "The Abductors’ True Nature", "quest_id": 20055004, + "variant_id": 121231, "base_level": 19, "minimum_item_rank": 0, "discoverable": false, @@ -86,10 +87,5 @@ "reset_group": false, "groups": [ 0 ] } - ], - ], - ], - "groups": [ 0 ] -} ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json index 827adf5a3..942fd91a2 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055004 Troll.json @@ -3,7 +3,7 @@ "type": "World", "comment": "The Abductors’ True Nature", "quest_id": 20055004, - "variantId": 9642584, + "variant_id": 364258, "base_level": 31, "minimum_item_rank": 0, "discoverable": false, From aae8e8b02e62dc50251c2287642976ef9320d4bf Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 30 Aug 2024 18:20:15 +0100 Subject: [PATCH 006/116] Added support for variant quests to drop their independent rewards as defined in their JSON. Renamed variant id to be more explicit within the schema. --- .../Script/migration_quest_variant.sql | 5 ++- .../Files/Database/Script/schema_sqlite.sql | 3 +- .../Sql/Core/DdonSqlDbQuestReward.cs | 5 ++- .../Sql/Core/DdonSqlQuestProgress.cs | 6 +-- .../Characters/QuestManager.cs | 40 +++++++++---------- .../Handler/QuestGetRewardBoxListHandler.cs | 3 +- .../Handler/QuestQuestProgressHandler.cs | 2 +- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 12 +++--- .../Entity/Structure/CDataRewardBoxRecord.cs | 1 + .../Model/Quest/QuestRewards.cs | 1 + .../Database/DatabaseMigratorTest.cs | 2 +- 11 files changed, 43 insertions(+), 37 deletions(-) diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql index 18b50549e..21ee7879d 100644 --- a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql +++ b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql @@ -1,2 +1,5 @@ ALTER TABLE "ddon_quest_progress" - ADD COLUMN "variant_id" INTEGER NOT NULL DEFAULT 0; + ADD COLUMN "variant_quest_id" INTEGER NOT NULL DEFAULT 0; + +ALTER TABLE "ddon_reward_box" + ADD COLUMN "variant_quest_id" INTEGER NOT NULL DEFAULT 0; diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql index 0d9fa7685..a46d6e7af 100644 --- a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql +++ b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql @@ -542,6 +542,7 @@ CREATE TABLE IF NOT EXISTS "ddon_reward_box" "random_reward1_index" INTEGER NOT NULL, "random_reward2_index" INTEGER NOT NULL, "random_reward3_index" INTEGER NOT NULL, + "variant_quest_id" INTEGER NOT NULL DEFAULT 0, CONSTRAINT "fk_ddon_reward_box_character_common_id" FOREIGN KEY ("character_common_id") REFERENCES "ddon_character_common" ("character_common_id") ON DELETE CASCADE ); @@ -551,7 +552,7 @@ CREATE TABLE IF NOT EXISTS "ddon_quest_progress" "quest_type" INTEGER NOT NULL, "quest_id" INTEGER NOT NULL, "step" INTEGER NOT NULL, - "variant_id" INTEGER NOT NULL, + "variant_quest_id" INTEGER NOT NULL, CONSTRAINT "fk_ddon_quest_progress_character_common_id" FOREIGN KEY ("character_common_id") REFERENCES "ddon_character_common" ("character_common_id") ON DELETE CASCADE ); diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs index ab055dbcd..563ae150e 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs @@ -19,7 +19,7 @@ public abstract partial class DdonSqlDb : SqlDb SelectBoxRewardItems(TCon conn, uint commonId) while (reader.Read()) { var result = ReadDatabaseQuestBoxReward(reader); + Console.WriteLine($"returning variant id in database is: {result.VariantId}"); results.Add(result); } }); @@ -102,6 +104,7 @@ private QuestBoxRewards ReadDatabaseQuestBoxReward(TReader reader) obj.CharacterCommonId = GetUInt32(reader, "character_common_id"); obj.QuestId = (QuestId) GetUInt32(reader, "quest_id"); obj.NumRandomRewards = GetInt32(reader, "num_random_rewards"); + obj.VariantId = GetUInt32(reader, "variant_quest_id"); for (int i = 0; i < obj.NumRandomRewards; i++) { diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlQuestProgress.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlQuestProgress.cs index c7428938d..ba5a9157f 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlQuestProgress.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlQuestProgress.cs @@ -12,7 +12,7 @@ public abstract partial class DdonSqlDb : SqlDb gQuests = new Dictionary(); - //private static Dictionary VariantQuests = new Dictionary(); private static readonly Dictionary> variantQuests = new(); private static readonly HashSet AvailableVariantQuests = new(); - //private static Dictionary altQuestLookup = new(); - //public static readonly HashSet questGroupIds = new(); public static HashSet GetAllVariantQuestIds() { @@ -42,8 +39,6 @@ public static void LoadQuests(AssetRepository assetRepository) // gQuests[QuestId.TheGreatAlchemist] = new Mq000025_TheGreatAlchemist(); // gQuests[QuestId.HopesBitterEnd] = new Mq030260_HopesBitterEnd(); - HashSet allVariantQuestIds = new(); - // Load Quests defined in files foreach (var questAsset in assetRepository.QuestAssets.Quests) { @@ -60,30 +55,19 @@ public static void LoadQuests(AssetRepository assetRepository) Quest alternateQuest = GenericQuest.FromAsset(questAsset); alternateQuest.IsVariantQuest = true; - alternateQuest.VariantId = questAsset.VariantId; - - // Ensure variant ids are unique. Throw early to not clog up the logs. - try - { - allVariantQuestIds.Add((uint)alternateQuest.VariantId); - } - catch (Exception) - { - Logger.Error($"Multiple quests are using variant id {alternateQuest.VariantId}. Please ensure all are unique."); - throw; - } + alternateQuest.VariantId = (uint)questAsset.VariantId; // Add an entry to the dictionary if it doesn't exist then add the variant id and quest if (!variantQuests.ContainsKey(questAsset.QuestId)) { variantQuests[alternateQuest.QuestId] = new Dictionary(); - variantQuests[questAsset.QuestId].Add((uint)alternateQuest.VariantId, alternateQuest); + variantQuests[questAsset.QuestId].Add(alternateQuest.VariantId, alternateQuest); continue; } // Add quest id and quest - variantQuests[questAsset.QuestId].Add((uint)alternateQuest.VariantId, alternateQuest); + variantQuests[questAsset.QuestId].Add(alternateQuest.VariantId, alternateQuest); continue; } @@ -91,13 +75,15 @@ public static void LoadQuests(AssetRepository assetRepository) gQuests[questAsset.QuestId] = GenericQuest.FromAsset(questAsset); } - var variantQuestKeys = variantQuests.Keys.ToArray(); for (int i = 0; i < variantQuestKeys.Length; i++) { - // Create a reliable source of all variant quests + // Store of all variant ids under the generic quest id + HashSet allVariantQuestIds = new(); + + // Create a reliable source of all variant quests, also checks if they are unique AvailableVariantQuests.Add(variantQuestKeys[i]); Logger.Info($"Quest Group Listed: {variantQuestKeys[i]}"); @@ -106,6 +92,17 @@ public static void LoadQuests(AssetRepository assetRepository) for (int j = 0; j < variantIds.Length; j++) { Logger.Info($"Variant entry: {variantIds[j]}"); + + // Ensure variant ids are unique. Throw early to not clog up the logs. + try + { + allVariantQuestIds.Add(variantIds[j]); + } + catch (Exception) + { + Logger.Error($"Multiple quests are using variant id {variantIds[j]}. Please ensure all are unique."); + throw; + } } } } @@ -160,7 +157,6 @@ public static Quest GetQuest(QuestId questId, uint? variantId = null) // If a variant is specified, return the variant quest. if (variantId is not null) { - //Logger.Debug("Variant Quest found. Returning quest variant."); return variantQuests[questId][(uint)variantId]; } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxListHandler.cs index 15bd3145c..1fa3c90cd 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxListHandler.cs @@ -37,7 +37,8 @@ public override S2CQuestGetRewardBoxListRes Handle(GameClient client, C2SQuestGe { ListNo = listNo, QuestId = (uint)boxReward.QuestId, - RewardItemList = Quest.AsCDataRewardBoxItems(boxReward) + RewardItemList = Quest.AsCDataRewardBoxItems(boxReward), + VariantId = boxReward.VariantId }); listNo += 1; diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs index c409cd174..d73e425c1 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs @@ -75,7 +75,7 @@ public override void Handle(GameClient client, StructurePacket QuestLayoutFlags; public Dictionary EnemyGroups { get; set; } public bool IsVariantQuest { get; set; } - public uint ? VariantId { get; set; } + public uint VariantId { get; set; } public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool isDiscoverable = false) { @@ -92,7 +92,7 @@ public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool QuestLayoutFlags = new List(); EnemyGroups = new Dictionary(); IsVariantQuest = false; - + VariantId = 0; Processes = new List(); } @@ -485,9 +485,8 @@ public static List AsCDataRewardBoxItems(QuestBoxRewards rew { List results = new List(); - // TODO: Add variant quest support to have rewards match - - var quest = QuestManager.GetQuest(rewards.QuestId); + var quest = rewards.VariantId == 0 ? QuestManager.GetQuest(rewards.QuestId) + : QuestManager.GetQuest(rewards.QuestId, rewards.VariantId); foreach (var reward in quest.SelectableRewards) { @@ -519,7 +518,8 @@ public QuestBoxRewards GenerateBoxRewards() { QuestBoxRewards obj = new QuestBoxRewards() { - QuestId = QuestId + QuestId = QuestId, + VariantId = VariantId }; foreach (var reward in ItemRewards) diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardBoxRecord.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardBoxRecord.cs index 55cd8d116..53955403f 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardBoxRecord.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardBoxRecord.cs @@ -13,6 +13,7 @@ public CDataRewardBoxRecord() public UInt32 ListNo { get; set; } public UInt32 QuestId { get; set; } + public uint VariantId { get; set; } public List RewardItemList { get; set; } public class Serializer : EntitySerializer diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs index 41ecd88f3..928637ae2 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs @@ -173,6 +173,7 @@ public class QuestBoxRewards public uint UniqRewardId { get; set; } public uint CharacterCommonId { get; set; } public QuestId QuestId { get; set; } + public uint VariantId { get; set; } public int NumRandomRewards { get; set; } public List RandomRewardIndices { get; set; } diff --git a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs index e5d235209..4efa3744d 100644 --- a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs +++ b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs @@ -247,7 +247,7 @@ public void Execute(DbConnection conn, string sql) {} public bool InsertNormalSkillParam(uint commonId, CDataNormalSkillParam normalSkillParam) { return true; } public bool InsertPawnTrainingStatus(uint pawnId, JobId job, byte[] pawnTrainingStatus) { return true; } public bool InsertPriorityQuest(uint characterCommonId, QuestId questId) { return true; } - public bool InsertQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step, uint? variantId = null) { return true; } + public bool InsertQuestProgress(uint characterCommonId, QuestId questId, QuestType questType, uint step, uint variantId=0) { return true; } public bool InsertReleasedWarpPoint(uint characterId, ReleasedWarpPoint ReleasedWarpPoint) { return true; } public bool InsertSecretAbilityUnlock(uint commonId, SecretAbility secretAbility) { return true; } public bool InsertShortcut(uint characterId, CDataShortCut shortcut) { return true; } From bc274f2069b561b6cb70a4fe5da38f436458b05b Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 30 Aug 2024 18:20:39 +0100 Subject: [PATCH 007/116] Added support for variant quests to drop their independent rewards as defined in their JSON. Renamed variant id to be more explicit within the schema. --- Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs index 563ae150e..1d706e639 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbQuestReward.cs @@ -73,7 +73,6 @@ public List SelectBoxRewardItems(TCon conn, uint commonId) while (reader.Read()) { var result = ReadDatabaseQuestBoxReward(reader); - Console.WriteLine($"returning variant id in database is: {result.VariantId}"); results.Add(result); } }); From 9830c7d5c697a064f34a4dabb4b2c58947d82f3e Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 30 Aug 2024 18:29:56 +0100 Subject: [PATCH 008/116] Clean up of comments and scratchpad code. --- .../Party/PartyQuestState.cs | 35 ++----------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index a6e37b488..bdb1c43f7 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -257,18 +257,10 @@ public ushort GetInstanceSubgroupId(Quest quest, StageId stageId) } return questState.CurrentSubgroup[stageId]; } - } - - //public uint GetNewVariantQuest(QuestId questId) - //{ - // return QuestManager.GetRandomVariantQuest(questId); - - //} + } public void AddNewQuest(QuestId questId, uint step, bool questStarted, uint variantId) { - //Logger.Info($"Specifically adding new quest for {questId} on variant {variantId}"); - Quest quest = QuestManager.GetQuest(questId, variantId); if (VariantQuests.Contains(questId)) @@ -280,10 +272,6 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted, uint vari public void AddNewQuest(QuestId questId, uint step, bool questStarted) { - //Logger.Debug($"AddNewQuest - called with {questId}"); - - //Logger.Debug($"Is {questId} contained within ActiveVariantQuests? ---->>>>>>>> {ActiveVariantQuests.ContainsKey(questId)}"); - // Trying to add a new variant quest before properly removing it will cause an exception. if (ActiveVariantQuests.ContainsKey(questId)) @@ -294,16 +282,13 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted) Quest quest; // If the quest we are trying to add is a variant quest, then roll and get a random version. - if (VariantQuests.Contains(questId)) // && !ActiveVariantQuests.ContainsKey(questId)) + if (VariantQuests.Contains(questId)) { - //Logger.Debug($"VariantQuests contains {questId}!!!"); quest = QuestManager.GetQuest(questId, QuestManager.GetRandomVariantQuest(questId)); - //Logger.Debug($"Got back {quest.QuestId} with variantId {quest.VariantId}"); } else { quest = GetQuest(questId); - //Logger.Debug($"Quest Returned: {quest.QuestId}"); } // If we are adding a new variant quest, then log the variant id for further reference @@ -313,15 +298,6 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted) ActiveVariantQuests.Add(quest.QuestId, (uint)quest.VariantId); } - // If we are adding a new alt quest, remove the quest group id from the list. - //if (quest.IsVariantQuest && InactiveAlternateQuestsGroups.Contains((uint)quest.QuestGroupId)) - //{ - - // InactiveAlternateQuestsGroups.Remove((uint)quest.QuestGroupId); - // ActiveVariantQuests.Add(quest.QuestId); - - //} - AddNewQuest(quest, step, questStarted); } @@ -332,13 +308,6 @@ public void RemoveQuest(QuestId questId) { ActiveQuests.Remove(questId); - // Ensure adding of selected quest's group id to be eligible for reroll. - // if(quest.IsVariantQuest) - //{ - // InactiveAlternateQuestsGroups.Add((uint)quest.QuestGroupId); - // ActiveVariantQuests.Remove(quest.QuestId); - //} - if (ActiveVariantQuests.ContainsKey(questId)) { Logger.Debug($"Number of activeVariantQuests BEFORE --->>> {ActiveVariantQuests.Count}"); From 93868056d097a75891f7b0cb5fec0cffe7480270 Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 30 Aug 2024 19:55:27 +0100 Subject: [PATCH 009/116] Modified migration_quest_variant.sql to delete a non existent quest id which would throw errors when processing. Added extra check for adding quest variations to ensure standard behaviour --- .../Files/Database/Script/migration_quest_variant.sql | 10 ++++++++++ Arrowgene.Ddon.GameServer/Characters/QuestManager.cs | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql index 21ee7879d..6d547424a 100644 --- a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql +++ b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql @@ -1,3 +1,13 @@ +DELETE FROM "ddon_quest_progress" WHERE "quest_id" = 2005501; + +DELETE FROM "ddon_priority_quests" WHERE "quest_id" = 2005501; + +DELETE FROM "ddon_reward_box" WHERE "quest_id" = 2005501; + +DELETE FROM "ddon_reward_box" WHERE "quest_id" = 2005501; + +DELETE FROM "ddon_completed_quests" WHERE "quest_id" = 2005501; + ALTER TABLE "ddon_quest_progress" ADD COLUMN "variant_quest_id" INTEGER NOT NULL DEFAULT 0; diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 993a4993f..30d679084 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -44,8 +44,8 @@ public static void LoadQuests(AssetRepository assetRepository) { // Separate all variant quests to its own dictionary for separate handling. - - if (questAsset.VariantId is not null) + // This also ensures these quests are not in gQuests before processing. + if (questAsset.VariantId is not null && !gQuests.ContainsKey(questAsset.QuestId)) { if (questAsset.VariantId == 0) { @@ -72,6 +72,8 @@ public static void LoadQuests(AssetRepository assetRepository) continue; } + Logger.Debug($"Adding quest {questAsset.QuestId} to gQuests"); + gQuests[questAsset.QuestId] = GenericQuest.FromAsset(questAsset); } @@ -79,7 +81,6 @@ public static void LoadQuests(AssetRepository assetRepository) for (int i = 0; i < variantQuestKeys.Length; i++) { - // Store of all variant ids under the generic quest id HashSet allVariantQuestIds = new(); @@ -93,7 +94,7 @@ public static void LoadQuests(AssetRepository assetRepository) { Logger.Info($"Variant entry: {variantIds[j]}"); - // Ensure variant ids are unique. Throw early to not clog up the logs. + // Ensure variant ids are unique. try { allVariantQuestIds.Add(variantIds[j]); From 531144b504e08154253be33babb6bbfa8ba65711 Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 30 Aug 2024 20:37:42 +0100 Subject: [PATCH 010/116] Removed duplicate SQL command --- .../Files/Database/Script/migration_quest_variant.sql | 2 -- 1 file changed, 2 deletions(-) diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql index 6d547424a..635954858 100644 --- a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql +++ b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql @@ -4,8 +4,6 @@ DELETE FROM "ddon_priority_quests" WHERE "quest_id" = 2005501; DELETE FROM "ddon_reward_box" WHERE "quest_id" = 2005501; -DELETE FROM "ddon_reward_box" WHERE "quest_id" = 2005501; - DELETE FROM "ddon_completed_quests" WHERE "quest_id" = 2005501; ALTER TABLE "ddon_quest_progress" From b9f48502de9a6f729de7720616aefcbe0ca4e6eb Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 30 Aug 2024 20:57:58 +0100 Subject: [PATCH 011/116] Removed leftover debug within QuestManager --- Arrowgene.Ddon.GameServer/Characters/QuestManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 30d679084..7508a5303 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -72,8 +72,6 @@ public static void LoadQuests(AssetRepository assetRepository) continue; } - Logger.Debug($"Adding quest {questAsset.QuestId} to gQuests"); - gQuests[questAsset.QuestId] = GenericQuest.FromAsset(questAsset); } From 0fbdb53e4373cb9a677832d79c53b12cefe6423a Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 30 Aug 2024 21:57:54 +0100 Subject: [PATCH 012/116] messy logging --- .../GatheringItems/InstanceDropItemManager.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs index cbad75107..71536a0fc 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs @@ -1,5 +1,7 @@ +using System; using System.Collections.Generic; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Logging; namespace Arrowgene.Ddon.GameServer.GatheringItems { @@ -15,11 +17,27 @@ public InstanceDropItemManager(GameClient client) protected override List FetchAssetsFromRepository(StageId stage, uint setId) { List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage, 0); + + //Logger.Debug($"enemiesInSet is not null? {enemiesInSet != null}"); + Console.WriteLine("======================================= InstanceDropItemManager =========================================="); + Console.WriteLine($"enemiesInSet is not null? {enemiesInSet != null}"); + Console.WriteLine($"setId ({setId}) < enemiesInSet.Count ({enemiesInSet.Count}) ? {setId < enemiesInSet.Count}"); + if(enemiesInSet != null && setId < enemiesInSet.Count) { Enemy enemy = enemiesInSet[(int) setId]; - if(enemy.DropsTable != null) + Console.WriteLine($"enemy IS {enemy.EnemyId}"); + Console.WriteLine($"enemy.DropsTable is null? {enemy.DropsTable}"); + + if (enemy.DropsTable != null) { + Console.WriteLine($"enemy.DropsTable.Items:"); + + foreach (var item in enemy.DropsTable.Items) + { + Console.WriteLine($"Item: {item}"); + } + return enemy.DropsTable.Items; } } From 88c75719539ebff03aa6f465b1a84df2db9e1a2f Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 30 Aug 2024 22:00:19 +0100 Subject: [PATCH 013/116] Removed Debug loggers from testing functionality. --- Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index bdb1c43f7..0339cb82d 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -294,7 +294,6 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted) // If we are adding a new variant quest, then log the variant id for further reference if (quest.IsVariantQuest) { - Logger.Debug($"Adding to ActiveVariantQuest -> quest: {quest.QuestId} -> variantId: {quest.VariantId}"); ActiveVariantQuests.Add(quest.QuestId, (uint)quest.VariantId); } @@ -310,10 +309,7 @@ public void RemoveQuest(QuestId questId) if (ActiveVariantQuests.ContainsKey(questId)) { - Logger.Debug($"Number of activeVariantQuests BEFORE --->>> {ActiveVariantQuests.Count}"); ActiveVariantQuests.Remove(questId); - Logger.Debug($"Removed variant quest {questId}"); - Logger.Debug($"Number of activeVariantQuests AFTER --->>> {ActiveVariantQuests.Count}"); } foreach (var location in quest.Locations) From f5158b27f2c4e2d40a04b3f03892347a8b701496 Mon Sep 17 00:00:00 2001 From: Asterit <32305591+Asterit@users.noreply.github.com> Date: Sat, 31 Aug 2024 22:43:16 +0100 Subject: [PATCH 014/116] Delete Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj --- .../Arrowgene - Backup.Ddon.GameServer.csproj | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj diff --git a/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj b/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj deleted file mode 100644 index c1272072c..000000000 --- a/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - net6.0 - Arrowgene.Ddon.GameServer - Dragons Dogma Online - Game Server - DDON Team - Ddon.GameServer - $(Version) - Copyright © 2019-2022 DDON Team - 10 - - - True - - - True - - - - - - From 25a8cfc4e962e05c2846a1d9df70b2d2871728ca Mon Sep 17 00:00:00 2001 From: Asterit <32305591+Asterit@users.noreply.github.com> Date: Sat, 31 Aug 2024 22:44:03 +0100 Subject: [PATCH 015/116] Delete Arrowgene.Ddon.Database/Properties/launchSettings.json --- Arrowgene.Ddon.Database/Properties/launchSettings.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 Arrowgene.Ddon.Database/Properties/launchSettings.json diff --git a/Arrowgene.Ddon.Database/Properties/launchSettings.json b/Arrowgene.Ddon.Database/Properties/launchSettings.json deleted file mode 100644 index 79ad96102..000000000 --- a/Arrowgene.Ddon.Database/Properties/launchSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "profiles": { - "Arrowgene.Ddon.Database": { - "commandName": "Project" - } - } -} \ No newline at end of file From 7c01c4f8acf09c57059f7bd4854b7e2cb5a0d5cd Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 2 Sep 2024 11:27:57 -0400 Subject: [PATCH 016/116] fix: Fix exception related to subgroups Fixed an exception which occurs the client requests enemies for a subgroup even when there are monsters are already populated in that node. --- .../Enemies/InstanceEnemyManager.cs | 72 ++++++++++++++++--- .../Handler/InstanceEnemyKillHandler.cs | 33 ++------- .../Handler/InstanceGetEnemySetListHandler.cs | 29 +++++--- .../InstanceTraningRoomSetEnemyHandler.cs | 1 + .../Party/PartyQuestState.cs | 9 +++ Arrowgene.Ddon.GameServer/Quests/Quest.cs | 1 + 6 files changed, 98 insertions(+), 47 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs index 35b02ef59..38a7dc522 100644 --- a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs +++ b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs @@ -4,16 +4,20 @@ using Arrowgene.Ddon.Shared.Model; using System; using System.Collections.Generic; +using System.Linq; public class InstanceEnemyManager : InstanceAssetManager { private readonly DdonGameServer _Server; private Dictionary _CurrentSubgroup { get; set; } + private Dictionary> _EnemyData; + public InstanceEnemyManager(DdonGameServer server) : base() { _Server = server; _CurrentSubgroup = new Dictionary(); + _EnemyData = new Dictionary>(); } protected override List FetchAssetsFromRepository(StageId stage, byte subGroupId) @@ -47,32 +51,80 @@ protected override List InstanceAssets(List originals) return filteredEnemyList; } - public ushort GetInstanceSubgroupId(StageId stageId) + public void SetInstanceEnemy(StageId stageId, byte index, InstancedEnemy enemy) + { + lock (_EnemyData) + { + if (!_EnemyData.ContainsKey(stageId)) + { + _EnemyData[stageId] = new Dictionary(); + } + + if (!_EnemyData[stageId].ContainsKey(index)) + { + _EnemyData[stageId][index] = enemy; + } + } + } + + public InstancedEnemy GetInstanceEnemy(StageId stageId, byte index) + { + lock (_EnemyData) + { + if (!_EnemyData.ContainsKey(stageId)) + { + return null; + } + + if (!_EnemyData[stageId].ContainsKey(index)) + { + return null; + } + return _EnemyData[stageId][index]; + } + } + + public List GetInstancedEnemies(StageId stageId) { - lock (_CurrentSubgroup) + lock (_EnemyData) { - if (!_CurrentSubgroup.ContainsKey(stageId)) + if (!_EnemyData.ContainsKey(stageId)) { - return 0; + return new List(); } - return _CurrentSubgroup[stageId]; + return _EnemyData[stageId].Select(x => x.Value).ToList(); } } - public void SetInstanceSubgroupId(StageId stageId, ushort subgroupId) + public bool HasInstanceEnemy(StageId stageId, byte index) { - lock (_CurrentSubgroup) + lock (_EnemyData) { - _CurrentSubgroup[stageId] = subgroupId; + if (!_EnemyData.ContainsKey(stageId)) + { + return false; + } + return _EnemyData[stageId].ContainsKey(index); + } + } + + public void ResetEnemyNode(StageId stageId) + { + lock (_EnemyData) + { + if (_EnemyData.ContainsKey(stageId)) + { + _EnemyData[stageId].Clear(); + } } } public override void Clear() { base.Clear(); - lock (_CurrentSubgroup) + lock (_EnemyData) { - _CurrentSubgroup.Clear(); + _EnemyData.Clear(); } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 5e3f19636..d2037ae44 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -35,7 +35,6 @@ public override void Handle(GameClient client, StructurePacket instancedGatheringItems = partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, packet.Structure.SetId); if (instancedGatheringItems.Count > 0) { @@ -84,25 +79,11 @@ public override void Handle(GameClient client, StructurePacket group; - if (IsQuestControlled && quest != null) - { - group = client.Party.QuestState.GetInstancedEnemies(quest.QuestId, stageId, subGroupId); - } - else - { - group = client.Party.InstanceEnemyManager.GetAssets(StageId.FromStageLayoutId(layoutId), (byte) subGroupId); - } + List group = client.Party.InstanceEnemyManager.GetInstancedEnemies(stageId); bool groupDestroyed = group.Where(x => x.IsRequired).All(x => x.IsKilled); if (groupDestroyed) { - if (IsQuestControlled && quest != null) - { - quest.SendProgressWorkNotices(client, stageId, subGroupId); - } bool IsAreaBoss = false; foreach (var enemy in group) diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs index 833a0ab2a..3cf2041dc 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs @@ -46,27 +46,34 @@ public override void Handle(GameClient client, StructurePacket new CDataLayoutEnemyData() + + foreach (var enemy in client.Party.QuestState.GetInstancedEnemies(quest, stageId, subGroupId)) { - PositionIndex = (byte)(enemy.Index), - EnemyInfo = enemy.asCDataStageLayoutEnemyPresetEnemyInfoClient() - }).ToList(); + response.EnemyList.Add(new CDataLayoutEnemyData() + { + PositionIndex = enemy.Index, + EnemyInfo = enemy.asCDataStageLayoutEnemyPresetEnemyInfoClient() + }); + client.Party.InstanceEnemyManager.SetInstanceEnemy(stageId, enemy.Index, enemy); + } } else { - response.EnemyList = client.Party.InstanceEnemyManager.GetAssets(stageId, subGroupId).Select((enemy, index) => new CDataLayoutEnemyData() + foreach (var asset in client.Party.InstanceEnemyManager.GetAssets(stageId, subGroupId).Select((Enemy, Index) => new {Index, Enemy})) { - PositionIndex = (byte)index, - EnemyInfo = enemy.asCDataStageLayoutEnemyPresetEnemyInfoClient() - }) - .ToList(); + response.EnemyList.Add(new CDataLayoutEnemyData() + { + PositionIndex = (byte) asset.Index, + EnemyInfo = asset.Enemy.asCDataStageLayoutEnemyPresetEnemyInfoClient() + }); + client.Party.InstanceEnemyManager.SetInstanceEnemy(stageId, (byte) asset.Index, asset.Enemy); + } } - if (subGroupId > 0) + if (subGroupId > 0 && response.EnemyList.Count > 0) { S2CInstanceEnemySubGroupAppearNtc subgroupNtc = new S2CInstanceEnemySubGroupAppearNtc() { diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceTraningRoomSetEnemyHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceTraningRoomSetEnemyHandler.cs index d7c38a848..a2ec8205a 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceTraningRoomSetEnemyHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceTraningRoomSetEnemyHandler.cs @@ -23,6 +23,7 @@ public override void Handle(GameClient client, StructurePacket Date: Mon, 2 Sep 2024 13:54:17 -0700 Subject: [PATCH 017/116] Improvements to Lestania News --- .../QuestGetSetQuestInfoListHandler.cs | 62 +++++++++++++++++-- .../Handler/WarpAreaWarpHandler.cs | 2 + .../Party/PartyQuestState.cs | 5 ++ Arrowgene.Ddon.GameServer/Quests/Quest.cs | 4 +- .../Entity/Structure/CDataSetQuestInfoList.cs | 5 -- 5 files changed, 67 insertions(+), 11 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs index 314973667..e8a148b8d 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs @@ -1,9 +1,11 @@ using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using System.Collections.Generic; using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler @@ -12,7 +14,33 @@ public class QuestGetSetQuestInfoListHandler : GameRequestPacketHandler(typeof(QuestGetSetQuestInfoListHandler)); - private static readonly byte[] PcapData = new byte[] { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0F,0x00,0x05,0xF7,0x8A,0x01,0x31,0x40,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x1E,0x82,0x00,0x00,0x22,0xF4,0x00,0x00,0x24,0xBF,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x59,0x00,0x00,0x00,0x00,0x00,0x0E,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x08,0xC1,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xF7,0x88,0x01,0x31,0x2D,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x00,0x94,0x00,0x00,0x00,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x23,0xEC,0x00,0x00,0x1D,0x81,0x00,0x00,0x24,0xCD,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x4C,0x00,0x00,0x00,0x00,0x00,0x28,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x42,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x01,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x06,0x00,0x05,0xF7,0x8C,0x01,0x31,0x40,0x8C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x02,0x21,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x02,0x26,0x00,0x00,0x02,0xF9,0x00,0x00,0x1E,0x59,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x64,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0xF7,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xF7,0x8D,0x01,0x31,0x40,0x93,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x02,0x5D,0x00,0x00,0x03,0x32,0x00,0x00,0x24,0xD5,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x62,0x00,0x00,0x00,0x00,0x00,0x1E,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x04,0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xF7,0xFF,0x01,0x31,0x40,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x03,0x86,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x23,0x00,0x00,0x24,0xBF,0x00,0x00,0x24,0xC1,0x01,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x46,0x00,0x00,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x08,0xC2,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xF6,0xFE,0x01,0x31,0x40,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x1F,0xE3,0x00,0x00,0x24,0x96,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x85,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x01,0x5A,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x08,0x00,0x00,0x08,0xA5,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x1C,0x00,0x00,0x03,0x76,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xF7,0x85,0x01,0x31,0x2D,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x02,0x27,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x1E,0x33,0x00,0x00,0x1E,0x99,0x00,0x00,0x1E,0xB7,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x5A,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x09,0xB3,0x00,0x00,0x00,0x01,0x00,0x00,0x02,0x3D,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x05,0xF7,0x89,0x01,0x31,0x2D,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x00,0x2A,0x00,0x00,0x00,0x37,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x24,0xE3,0x00,0x00,0x1E,0xE6,0x00,0x00,0x1F,0x54,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x00,0x00,0x37,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x37,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x05,0xF6,0xFD,0x01,0x31,0x2D,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x02,0x22,0x00,0x00,0x00,0x3A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x1E,0xE5,0x00,0x00,0x1F,0x55,0x00,0x00,0x1F,0x0B,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x00,0x00,0x3A,0x00,0x00,0x00,0x00,0x9A,0x00,0x00,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x32,0x00,0x00,0x00,0x01,0x00,0x00,0x02,0xDB,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x09,0x00,0x05,0xF7,0x86,0x01,0x31,0x2D,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x20,0x7E,0x00,0x00,0x20,0x97,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xF7,0x87,0x01,0x31,0x2D,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x5F,0x00,0x00,0x27,0x3F,0x00,0x00,0x24,0xB1,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x43,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x88,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x3C,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xF7,0x8B,0x01,0x31,0x40,0x8A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x01,0xF9,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x1E,0xAD,0x00,0x00,0x00,0x37,0x01,0x02,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x9B,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x9C,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x5E,0x00,0x00,0x00,0x01,0x00,0x00,0x02,0x25,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xF8,0x21,0x01,0x40,0x6F,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x02,0x27,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x2D,0xEF,0x00,0x00,0x1E,0xAD,0x00,0x00,0x24,0x94,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xAE,0x00,0x00,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x00,0x65,0x00,0x00,0x00,0x00,0x00,0x37,0x00,0x00,0x00,0x00,0x5A,0x00,0x00,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x00,0xC4,0x00,0x00,0x00,0x00,0x00,0x37,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x0B,0xAB,0x00,0x00,0x00,0x01,0x00,0x00,0x02,0x41,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x1F,0x16,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0D,0x00,0x05,0xF7,0x8E,0x01,0x40,0x6F,0x4C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x1F,0x17,0x00,0x00,0x1E,0xDD,0x00,0x00,0x00,0x29,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x4D,0x00,0x00,0x00,0x00,0x00,0x39,0x01,0x00,0x00,0x00,0x37,0x00,0x00,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x00,0xA0,0x00,0x00,0x00,0x00,0x00,0x39,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x0B,0xAF,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0xF6,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0B,0x00,0x05,0xF6,0xFF,0x01,0x40,0x6F,0x5A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xC3,0x6C,0x90,0x00,0x00,0x02,0x29,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x2A,0xF7,0x00,0x00,0x1F,0x61,0x00,0x00,0x24,0x94,0x02,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x52,0x00,0x00,0x00,0x00,0x00,0x3C,0x01,0x00,0x00,0x00,0x52,0x00,0x00,0x00,0x00,0x00,0x39,0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x5A,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x0B,0xB7,0x00,0x00,0x00,0x01,0x00,0x00,0x02,0x41,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0D,0x00,0x01,0x00,0x12,0x00,0x00,0x00,0x00 }; + /// + /// Mapping of the DistributeId sent by the client and the warp point ID used to warp from the Lestania News menu. + /// + private static readonly Dictionary WarpPointNewsMapping = new Dictionary() + { + { QuestAreaId.HidellPlains, 2 }, + { QuestAreaId.BreyaCoast, 3 }, + { QuestAreaId.MysreeForest, 5 }, + { QuestAreaId.VoldenMines, 7 }, + { QuestAreaId.DoweValley, 6 }, + { QuestAreaId.MysreeGrove, 9 }, + { QuestAreaId.DeenanWoods, 14 }, + { QuestAreaId.BetlandPlains, 15 }, + { QuestAreaId.NorthernBetlandPlains, 10 }, + { QuestAreaId.ZandoraWastelands, 11 }, + { QuestAreaId.EasternZandora, 12 }, + { QuestAreaId.MergodaRuins, 13 }, + { QuestAreaId.BloodbaneIsle, 16 }, + { QuestAreaId.ElanWaterGrove, 17 }, + { QuestAreaId.FaranaPlains, 17 }, + { QuestAreaId.MorrowForest, 19 }, + { QuestAreaId.KingalCanyon, 20 }, + { QuestAreaId.FeryanaWilderness, 71 }, + { QuestAreaId.MegadosysPlateau, 82 }, + { QuestAreaId.UrtecaMountains, 96 }, + { QuestAreaId.RathniteFoothills, 68 }, + }; public QuestGetSetQuestInfoListHandler(DdonGameServer server) : base(server) { @@ -25,11 +53,37 @@ public override S2CQuestGetSetQuestInfoListRes Handle(GameClient client, C2SQues DistributeId = request.DistributeId }; + // The client will let you warp to points you haven't unlocked through Lestania News, + // and there doesn't seem to be a flag we can return to prevent this. + // In the mean time, if you don't have the "main" warp point for a region, no quests are presented. + // TODO: Investigate how this information was actually presented. + if (!client.Character.ReleasedWarpPoints + .Where(x => x.WarpPointId == WarpPointNewsMapping[request.DistributeId]) + .Any()) + { + return res; + } + + // TODO: Mirror this serverside so it doesn't require a database query for each tab. + var completedQuests = Server.Database.GetCompletedQuestsByType(client.Character.CommonId, QuestType.World); + res.SetQuestList = QuestManager.GetQuestsByType(QuestType.World) .Where(x => QuestManager.IsWorldQuest(x.Key) - && x.Value.QuestAreaId == request.DistributeId - && client.Party.QuestState.HasActiveQuest(x.Key)) - .Select(x => x.Value.ToCDataSetQuestInfoList()) + && x.Value.QuestAreaId == request.DistributeId) + .Select(x => + { + var ret = x.Value.ToCDataSetQuestInfoList(); + ret.CompleteNum = (ushort)(client.Party.QuestState.IsComplete(x.Key) ? 1 : 0); // Completed in the current instance, hides rewards. + ret.IsDiscovery = x.Value.IsDiscoverable || (completedQuests.Where(y => y.QuestId == x.Key).FirstOrDefault()?.ClearCount ?? 0) > 0; + + if (!ret.IsDiscovery) + { + ret.DiscoverRewardItemId = ret.SelectRewardItemIdList.FirstOrDefault()?.Value ?? 0 ; + ret.SelectRewardItemIdList = new List(); + } + + return ret; + }) .ToList(); return res; diff --git a/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs b/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs index cecb249a2..e0725f9ce 100644 --- a/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs @@ -20,6 +20,8 @@ public WarpAreaWarpHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { + Logger.Info($"Warping to {packet.Structure.WarpPointId}"); + uint price = packet.Structure.Price; // TODO: Don't trust packet.Structure.Price and check its price server side CDataWalletPoint walletPoint = client.Character.WalletPointList.Where(wp => wp.Type == WalletType.RiftPoints).Single(); diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 56786bee7..82924af81 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -420,6 +420,11 @@ public bool UpdatePartyQuestProgress(DdonGameServer server, PartyGroup party, Qu return true; } + public bool IsComplete(QuestId questId) + { + return CompletedWorldQuests.Contains(questId); + } + public bool CompletePartyQuestProgress(DdonGameServer server, PartyGroup party, QuestId questId) { Quest quest = QuestManager.GetQuest(questId); diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index fe108f623..92268ad44 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -347,8 +347,8 @@ public virtual CDataSetQuestInfoList ToCDataSetQuestInfoList() ContentJoinItemRank = (ushort)(OrderConditions.Find(x => x.Type == QuestOrderConditionType.ItemRank)?.Param01 ?? 0), RandomRewardNum = RandomRewardNum(), SelectRewardItemIdList = GetQuestSelectableRewards().Select(x => new CDataCommonU32(x.ItemId)).ToList(), - DiscoverRewardWalletPoint = WalletRewards, - DiscoverRewardExp = ExpRewards, + //DiscoverRewardWalletPoint = WalletRewards, // These are not the same as the regular rewards? + //DiscoverRewardExp = ExpRewards, // These are not the same as the regular rewards? QuestLayoutFlagSetInfoList = QuestLayoutFlagSetInfo.Select(x => x.AsCDataQuestLayoutFlagSetInfo()).ToList(), QuestEnemyInfoList = EnemyGroups.Values.SelectMany(group => group.Enemies.Select(enemy => new CDataQuestEnemyInfo() { diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataSetQuestInfoList.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataSetQuestInfoList.cs index c06c664ce..30c8e407b 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataSetQuestInfoList.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataSetQuestInfoList.cs @@ -1,10 +1,5 @@ using Arrowgene.Buffers; -using Arrowgene.Ddon.Shared.Model.Quest; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Arrowgene.Ddon.Shared.Entity.Structure { From 8b386d2df1dec342dadfb6fe77ddebce604ead80 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Mon, 2 Sep 2024 14:29:36 -0700 Subject: [PATCH 018/116] Mirror quest completion statistics serverside so that they only need to be DB-queried on login, rather than any time something might want them. --- .../Sql/Core/DdonSqlDbCharacter.cs | 24 +++++++++++++++++++ .../QuestGetCycleContentsStateListHandler.cs | 3 ++- .../QuestGetQuestCompletedListHandler.cs | 3 ++- .../QuestGetSetQuestInfoListHandler.cs | 3 +-- .../Handler/QuestGetSetQuestListHandler.cs | 3 +-- .../Party/PartyQuestState.cs | 9 +++++++ Arrowgene.Ddon.Shared/Model/Character.cs | 3 +++ 7 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs index c636b3d2a..cf2b5abec 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs @@ -1,9 +1,11 @@ using Arrowgene.Ddon.Shared.Csv; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Model.Quest; using System; using System.Collections.Generic; using System.Data.Common; +using System.Linq; namespace Arrowgene.Ddon.Database.Sql.Core { @@ -372,6 +374,28 @@ private void QueryCharacterData(TCon conn, Character character) character.AbilityPresets.Add(ReadAbilityPreset(reader)); } }); + + // Quest Completion + foreach (var questType in Enum.GetValues(typeof(QuestType)).Cast()) + { + ExecuteReader(conn, SqlSelectCompletedQuestByType, + command => { + AddParameter(command, "@character_common_id", character.CommonId); + AddParameter(command, "@quest_type", (uint)questType); + }, reader => { + while (reader.Read()) + { + var quest = new CompletedQuest() + { + QuestId = (QuestId)GetUInt32(reader, "quest_id"), + QuestType = questType, + ClearCount = GetUInt32(reader, "clear_count") + }; + + character.CompletedQuests.TryAdd(quest.QuestId, quest); + } + }); + } } public bool UpdateMyPawnSlot(uint characterId, uint num) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs index b4feb049d..d44412953 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs @@ -13,6 +13,7 @@ using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using System.Collections.Generic; +using System.Linq; using System.Text.Json; namespace Arrowgene.Ddon.GameServer.Handler @@ -47,7 +48,7 @@ public override void Handle(GameClient client, IPacket packet) ntc.QuestDefine = pcap.QuestDefine; // Recover quest log data to be able to accept quests // pcap.MainQuestIdList; (this will add back all missing functionality which depends on complete MSQ) - var completedMsq = Server.Database.GetCompletedQuestsByType(client.Character.CommonId, QuestType.Main); + var completedMsq = client.Character.CompletedQuests.Values.Where(x => x.QuestType == QuestType.Main); foreach (var msq in completedMsq) { ntc.MainQuestIdList.Add(new CDataQuestId() { QuestId = (uint) msq.QuestId }); diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestCompletedListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestCompletedListHandler.cs index 062f44851..5e387ea32 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestCompletedListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestCompletedListHandler.cs @@ -7,6 +7,7 @@ using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { @@ -27,7 +28,7 @@ public override S2CQuestGetQuestCompleteListRes Handle(GameClient client, C2SQue QuestType = packet.QuestType }; - var completedQuests = Server.Database.GetCompletedQuestsByType(client.Character.CommonId, (QuestType) packet.QuestType); + var completedQuests = client.Character.CompletedQuests.Values.Where(x => x.QuestType == (QuestType)packet.QuestType); foreach (var completedQuest in completedQuests) { result.QuestIdList.Add(new CDataQuestId() diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs index e8a148b8d..397f22c05 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs @@ -64,8 +64,7 @@ public override S2CQuestGetSetQuestInfoListRes Handle(GameClient client, C2SQues return res; } - // TODO: Mirror this serverside so it doesn't require a database query for each tab. - var completedQuests = Server.Database.GetCompletedQuestsByType(client.Character.CommonId, QuestType.World); + var completedQuests = client.Character.CompletedQuests.Values.Where(x => x.QuestType == QuestType.World); res.SetQuestList = QuestManager.GetQuestsByType(QuestType.World) .Where(x => QuestManager.IsWorldQuest(x.Key) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs index a594e5f76..27e6c9cf3 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs @@ -34,8 +34,7 @@ public override void Handle(GameClient client, StructurePacket(); BbmProgress = new BitterblackMazeProgress(); + CompletedQuests = new Dictionary(); } public int AccountId { get; set; } @@ -113,6 +115,7 @@ public uint ContentCharacterId public uint NextBBMStageId { get; set; } public uint MaxBazaarExhibits { get; set; } + public Dictionary CompletedQuests { get; set; } // --- From e1abcf9bca9c50543d859625c3c3200755248d3a Mon Sep 17 00:00:00 2001 From: Asterit Date: Tue, 3 Sep 2024 08:23:08 +0100 Subject: [PATCH 019/116] Feature complete - implemented custom quest drops and default quest drops depending on monster and level. --- Arrowgene.Ddon.GameServer/GameClient.cs | 2 + .../GatheringItems/InstanceDropItemManager.cs | 17 +- .../InstanceQuestDropManager.cs | 86 + .../Handler/InstanceEnemyKillHandler.cs | 20 +- .../Handler/InstanceGetDropItemHandler.cs | 14 +- .../Handler/InstanceGetDropItemListHandler.cs | 22 +- Arrowgene.Ddon.GameServer/Party/PartyGroup.cs | 1 + .../Party/PartyQuestState.cs | 3 - .../Asset/QuestDropItemAsset.cs | 81 + .../EnemySpawnAssetDeserializer.cs | 2 +- .../AssetReader/QuestAssetDeserializer.cs | 53 +- .../AssetReader/QuestDropAssetDeserializer.cs | 115 + Arrowgene.Ddon.Shared/AssetRepository.cs | 5 +- .../Files/Assets/QuestEnemyDrops.json | 13896 ++++++++++++++++ .../Assets/quests/q20055001 Sphinx one.json | 2 +- .../Model/EnemySpawnAsset.cs | 3 +- 16 files changed, 14286 insertions(+), 36 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs create mode 100644 Arrowgene.Ddon.Shared/Asset/QuestDropItemAsset.cs create mode 100644 Arrowgene.Ddon.Shared/AssetReader/QuestDropAssetDeserializer.cs create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/QuestEnemyDrops.json diff --git a/Arrowgene.Ddon.GameServer/GameClient.cs b/Arrowgene.Ddon.GameServer/GameClient.cs index 6601ef185..440d469c2 100644 --- a/Arrowgene.Ddon.GameServer/GameClient.cs +++ b/Arrowgene.Ddon.GameServer/GameClient.cs @@ -19,6 +19,7 @@ public GameClient(ITcpSocket socket, PacketFactory packetFactory, ShopManager sh InstanceDropItemManager = new InstanceDropItemManager(this); InstanceShopManager = new InstanceShopManager(shopManager); InstanceBbmItemManager = new InstanceBitterblackGatheringItemManager(); + InstanceQuestDropManager = new InstanceQuestDropManager(); GameMode = GameMode.Normal; } @@ -47,6 +48,7 @@ public void UpdateIdentity() public InstanceGatheringItemManager InstanceGatheringItemManager { get; } public InstanceDropItemManager InstanceDropItemManager { get; } public InstanceBitterblackGatheringItemManager InstanceBbmItemManager { get; } + public InstanceQuestDropManager InstanceQuestDropManager { get; } public GameMode GameMode { get; set; } diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs index 71536a0fc..3bb5e9a29 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Logging; namespace Arrowgene.Ddon.GameServer.GatheringItems { @@ -16,28 +15,14 @@ public InstanceDropItemManager(GameClient client) protected override List FetchAssetsFromRepository(StageId stage, uint setId) { - List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage, 0); - - //Logger.Debug($"enemiesInSet is not null? {enemiesInSet != null}"); - Console.WriteLine("======================================= InstanceDropItemManager =========================================="); - Console.WriteLine($"enemiesInSet is not null? {enemiesInSet != null}"); - Console.WriteLine($"setId ({setId}) < enemiesInSet.Count ({enemiesInSet.Count}) ? {setId < enemiesInSet.Count}"); + List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage, 0); if(enemiesInSet != null && setId < enemiesInSet.Count) { Enemy enemy = enemiesInSet[(int) setId]; - Console.WriteLine($"enemy IS {enemy.EnemyId}"); - Console.WriteLine($"enemy.DropsTable is null? {enemy.DropsTable}"); if (enemy.DropsTable != null) { - Console.WriteLine($"enemy.DropsTable.Items:"); - - foreach (var item in enemy.DropsTable.Items) - { - Console.WriteLine($"Item: {item}"); - } - return enemy.DropsTable.Items; } } diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs new file mode 100644 index 000000000..caf4dd5a6 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs @@ -0,0 +1,86 @@ +using Arrowgene.Ddon.Server; +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.GatheringItems +{ + public class InstanceQuestDropManager + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(InstanceQuestDropManager)); + // + private Dictionary> QuestEnemyDropsTable; + public InstanceQuestDropManager() + { + QuestEnemyDropsTable = new(); + LastDropIdQuery = 0; + } + + private uint LastDropIdQuery { get; set; } + + private List InstanceAssets(List originals) + { + return originals.Select(item => new InstancedGatheringItem(item)) + .Where(instancedAsset => instancedAsset.ItemNum > 0) + .ToList(); + } + + private uint GetDropId(CDataStageLayoutId layoutId, uint setId) + { + return layoutId.GroupId + (setId * 2) + + (layoutId.StageId + layoutId.GroupId + layoutId.LayerNo); + } + + public bool IsQuestDrop(CDataStageLayoutId layoutId, uint setId) + { + uint dropEntryId = GetDropId(layoutId, setId); + + if (QuestEnemyDropsTable.ContainsKey(dropEntryId)) + { + // Stores the drop Id if we return true. This saves on an unnecessary call within FetchEnemyLoot + LastDropIdQuery = dropEntryId; + return true; + } + return false; + } + public List FetchEnemyLoot(CDataStageLayoutId layoutId, uint setId) + { + if (QuestEnemyDropsTable.ContainsKey(LastDropIdQuery)) + { + return QuestEnemyDropsTable[LastDropIdQuery]; + + } + return new List(); + } + + public List RollEnemyLoot(InstancedEnemy enemy, CDataStageLayoutId layoutId, uint setId) + { + uint dropEntryId = GetDropId(layoutId, setId); + + if (!QuestEnemyDropsTable.ContainsKey(dropEntryId)) + { + // Check to see if a drop table exists + if (enemy.DropsTable != null) + { + List items = InstanceAssets(enemy.DropsTable.Items); + + QuestEnemyDropsTable[dropEntryId] = items; + + // Return the list of drop items + return items; + } + } + // Return an empty list of gathering items for no loot. + return new List(); + } + + public void Clear() + { + LastDropIdQuery = 0; + QuestEnemyDropsTable.Clear(); + } + } + +} diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index cf4e2567b..402d2fe9a 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -49,8 +49,26 @@ public override void Handle(GameClient client, StructurePacket instancedGatheringItems = partyMemberClient.InstanceQuestDropManager.RollEnemyLoot(enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId); + + if (instancedGatheringItems.Count > 0) + { + partyMemberClient.Send(new S2CInstancePopDropItemNtc() + { + LayoutId = packet.Structure.LayoutId, + SetId = packet.Structure.SetId, + MdlType = enemyKilled.DropsTable.MdlType, + PosX = packet.Structure.DropPosX, + PosY = packet.Structure.DropPosY, + PosZ = packet.Structure.DropPosZ + }); + } + } } else { diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs index 000c29289..b94867818 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs @@ -1,3 +1,4 @@ +using Arrowgene.Ddon.GameServer.Quests; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; @@ -18,8 +19,17 @@ public InstanceGetDropItemHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { - List items = client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); - + List items; + if (client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId)) + { + items = client.InstanceQuestDropManager.FetchEnemyLoot(packet.Structure.LayoutId, packet.Structure.SetId); + } + else + { + items = client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); + } + + S2CInstanceGetDropItemRes res = new S2CInstanceGetDropItemRes(); res.LayoutId = packet.Structure.LayoutId; res.SetId = packet.Structure.SetId; diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs index c4359b6b1..971fc4ee3 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs @@ -1,25 +1,37 @@ -using System.Collections.Generic; -using System.Linq; +using Arrowgene.Ddon.GameServer.Party; +using Arrowgene.Ddon.GameServer.Quests; 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; +using System.Collections.Generic; +using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { public class InstanceGetDropItemListHandler : GameStructurePacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(InstanceGetDropItemListHandler)); - + public InstanceGetDropItemListHandler(DdonGameServer server) : base(server) { } public override void Handle(GameClient client, StructurePacket packet) { - List items = client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); + List items; + + if (client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId)) + { + items = client.InstanceQuestDropManager.FetchEnemyLoot(packet.Structure.LayoutId, packet.Structure.SetId); + } else + { + items = client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); + } + client.Send(new S2CInstanceGetDropItemListRes() { LayoutId = packet.Structure.LayoutId, @@ -34,4 +46,4 @@ public override void Handle(GameClient client, StructurePacket>> {ActiveVariantQuests.Count}"); ActiveVariantQuests.Remove(questId); - Logger.Debug($"Removed variant quest {questId}"); - Logger.Debug($"Number of activeVariantQuests AFTER --->>> {ActiveVariantQuests.Count}"); } foreach (var location in quest.Locations) diff --git a/Arrowgene.Ddon.Shared/Asset/QuestDropItemAsset.cs b/Arrowgene.Ddon.Shared/Asset/QuestDropItemAsset.cs new file mode 100644 index 000000000..a3fc8fe1a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Asset/QuestDropItemAsset.cs @@ -0,0 +1,81 @@ +using Arrowgene.Ddon.Shared.AssetReader; +using Arrowgene.Logging; +using System.Collections.Generic; +using System.Linq; + +namespace Arrowgene.Ddon.Shared.Asset +{ + public class QuestDropItemAsset + { + private static readonly ILogger Logger = LogProvider.Logger(typeof(QuestDropItemAsset)); + + public QuestDropItemAsset() + { + + QuestDropsTables = new(); + EnemyLevelMap = new(); + } + // > + private Dictionary QuestDropsTables { get; set; } + + // > + private Dictionary> EnemyLevelMap { get; set; } + + // This is to be used within the quest drop table serializer for adding the default drop tables + public void AddDropTable(uint enemyId, ushort levelMin, ushort levelMax, DropsTable dropTable) + { + if (!EnemyLevelMap.ContainsKey(enemyId)) + { + EnemyLevelMap[enemyId] = new SortedList(); + } + + // Create a mapping of the enemy id level values to the correct drop table id. + + for (ushort i = levelMin; i <= levelMax; i++) + { + EnemyLevelMap[enemyId].Add(i, dropTable.Id); + } + + // Add the drop table with the drop table id to the main DropsTable + QuestDropsTables[dropTable.Id] = dropTable; + } + + public void AddDropTable(uint enemyId, ushort levelMin, DropsTable dropTable) + { + if (!EnemyLevelMap.ContainsKey(enemyId)) + { + EnemyLevelMap[enemyId] = new SortedList(); + } + + // Create a mapping of the enemy id level values to the correct drop table id. + + EnemyLevelMap[enemyId].Add(levelMin, dropTable.Id); + + // Add the drop table with the drop table id to the main DropsTable + QuestDropsTables[dropTable.Id] = dropTable; + } + + public DropsTable GetDropTable(uint enemyId, ushort enemyLevel) + { + if (EnemyLevelMap.ContainsKey(enemyId)) + { + if (EnemyLevelMap[enemyId].ContainsKey(enemyLevel)) + { + return QuestDropsTables[EnemyLevelMap[enemyId][enemyLevel]]; + } + + // if we are here then the LevelMap doesn't contain an entry for this level. + // Check the enemy level against the largest level + ushort largestLevel = EnemyLevelMap[enemyId].Last().Key; + + if (largestLevel <= enemyLevel) + { + return QuestDropsTables[EnemyLevelMap[enemyId][largestLevel]]; + } + } + + // Nothing found if we are down here + return null; + } + } +} diff --git a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs index 0ee3cff9c..a86b538bd 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs @@ -108,7 +108,7 @@ public EnemySpawnAsset ReadPath(string path) }; //checking if the file has spawntime, if yes we convert the time and pass it along to enemy.cs - if(enemySchemaIndexes.ContainsKey("SpawnTime")) + if (enemySchemaIndexes.ContainsKey("SpawnTime")) { string SpawnTimeGet = row[enemySchemaIndexes["SpawnTime"]].GetString(); ConvertSpawnTimeToMilliseconds(SpawnTimeGet, out long start, out long end); diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 7d1b7f0c2..1d990ca12 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -23,7 +23,8 @@ public QuestAssetDeserializer(Dictionary namedParams) this.namedParams = namedParams; } - public bool LoadQuestsFromDirectory(string path, QuestAsset questAssets) + //public bool LoadQuestsFromDirectory(string path, QuestAsset questAssets, DropItemsAsset dropItemsAsset) + public bool LoadQuestsFromDirectory(string path, QuestAsset questAssets)//, QuestDropItemAsset questLootDrops) { DirectoryInfo info = new DirectoryInfo(path); if (!info.Exists) @@ -135,7 +136,7 @@ private bool ParseQuest(QuestAssetData assetData, JsonElement jQuest) } ParseRewards(assetData, jQuest); - + if (!ParseOrderCondition(assetData, jQuest)) { return false; @@ -553,7 +554,7 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) } questBlock.QuestOrderDetails.QuestType = questType; - questBlock.QuestOrderDetails.QuestId = (QuestId) jblock.GetProperty("quest_id").GetUInt32(); + questBlock.QuestOrderDetails.QuestId = (QuestId)jblock.GetProperty("quest_id").GetUInt32(); } break; case QuestBlockType.MyQstFlags: @@ -606,7 +607,7 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) if (jblock.TryGetProperty("quest_id", out JsonElement jQuestId)) { - questBlock.OmInteractEvent.QuestId = (QuestId) jQuestId.GetUInt32(); + questBlock.OmInteractEvent.QuestId = (QuestId)jQuestId.GetUInt32(); } questBlock.OmInteractEvent.QuestType = questType; @@ -836,6 +837,39 @@ private bool ParseEnemyGroups(QuestAssetData assetData, JsonElement quest) isRequired = jIsRequired.GetBoolean(); } + + // Look for custom drops here + bool customDropItems = false; + + // Setting default values in case a custom table is defined. + DropsTable customTable = new() + { + Id = 0, + MdlType = 0, + }; + + if (enemy.TryGetProperty("drop_items", out JsonElement itemsList)) + { + customDropItems = true; + var list = itemsList.EnumerateArray(); + + foreach (var items in list) + { + GatheringItem dropItems = new() + { + ItemId = items.GetProperty("item_id").GetUInt32(), + ItemNum = items.GetProperty("item_min").GetUInt32(), + MaxItemNum = items.GetProperty("item_max").GetUInt32(), + Quality = items.GetProperty("quality").GetUInt32(), + IsHidden = false, + DropChance = items.GetProperty("drop_chance").GetDouble() + }; + + customTable.Items.Add(dropItems); + } + + } + var questEnemy = new InstancedEnemy() { EnemyId = Convert.ToUInt32(enemy.GetProperty("enemy_id").GetString(), 16), @@ -850,7 +884,18 @@ private bool ParseEnemyGroups(QuestAssetData assetData, JsonElement quest) }; ApplyOptionalEnemyKeys(enemy, questEnemy); + // ApplyEnemyDropTable + if (customDropItems) + { + questEnemy.DropsTable = customTable; + } + else + { + // Get default drops for this enemy id and level. + DropsTable lootTable = AssetRepository.QuestDropItemAsset.GetDropTable(questEnemy.EnemyId, questEnemy.Lv); + questEnemy.DropsTable = lootTable; + } enemyGroup.Enemies.Add(questEnemy); } diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestDropAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestDropAssetDeserializer.cs new file mode 100644 index 000000000..9efd15b1d --- /dev/null +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestDropAssetDeserializer.cs @@ -0,0 +1,115 @@ +using Arrowgene.Ddon.Shared.Asset; +using Arrowgene.Logging; +using System.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.AssetReader +{ + public class QuestDropAssetDeserializer : IAssetDeserializer + { + private static readonly ILogger Logger = LogProvider.Logger(typeof(QuestDropItemAsset)); + + public QuestDropItemAsset ReadPath(string path) + { + Logger.Info($"Reading {path}"); + + QuestDropItemAsset asset = new QuestDropItemAsset(); + + string json = File.ReadAllText(path); + JsonDocument document = JsonDocument.Parse(json); + + JsonElement dropsTablesElement = document.RootElement.GetProperty("dropsTables"); + + foreach (JsonElement dropsTableElement in dropsTablesElement.EnumerateArray()) + { + // ignore flag is optional for non implemented default / monsters with WIP tables + if (dropsTableElement.TryGetProperty("ignore", out JsonElement ignore)) + { + if (ignore.GetBoolean()) + { + continue; + } + } + + DropsTable table = new(); + + uint dropTableId = dropsTableElement.GetProperty("id").GetUInt32(); + + table.Id = dropTableId; + table.Name = dropsTableElement.GetProperty("name").GetString(); + table.MdlType = dropsTableElement.GetProperty("mdlType").GetByte(); + + // Set default for max level + ushort maxLevel = 0; + + ushort minLevel = dropsTableElement.GetProperty("min_level").GetUInt16(); + + if(dropsTableElement.TryGetProperty("max_level", out JsonElement level)) + { + maxLevel = level.GetUInt16(); + } + + // Check now if the levels are correctly set + if(maxLevel != 0 && maxLevel < minLevel) + { + throw new Exception($"min_level and max_level are both defined, but min_level value is higher at id {dropTableId}"); + } + + foreach (JsonElement dropsTableItemsRow in dropsTableElement.GetProperty("items").EnumerateArray()) + { + List row = dropsTableItemsRow.EnumerateArray().ToList(); + + GatheringItem gatheringItem = new() + { + ItemId = row[(int)QuestEnemyDropHeaders.ItemId].GetUInt32(), + ItemNum = row[(int)QuestEnemyDropHeaders.ItemNum].GetUInt32(), + MaxItemNum = row[(int)QuestEnemyDropHeaders.MaxItemNum].GetUInt32(), + Quality = row[(int)QuestEnemyDropHeaders.Quality].GetUInt32(), + IsHidden = row[(int)QuestEnemyDropHeaders.IsHidden].GetBoolean(), + DropChance = row[(int)QuestEnemyDropHeaders.DropChance].GetDouble() + }; + + + table.Items.Add(gatheringItem); + } + + + List enemyIds = dropsTableElement.GetProperty("enemy_id").EnumerateArray().ToList(); + + foreach (var enemy in enemyIds) + { + + var enemyId = Convert.ToUInt32(enemy.GetString(), 16); + + + if (maxLevel > 0) + { + // Add the drop table with max level defined. + asset.AddDropTable(enemyId, minLevel, maxLevel, table); + } + else + { + // Add drop table only with min level. + asset.AddDropTable(enemyId, minLevel, table); + } + } + } + + return asset; + } + + public enum QuestEnemyDropHeaders + { + ItemId = 0, + ItemNum = 1, + MaxItemNum= 2, + Quality = 3, + IsHidden = 4, + DropChance = 5 + } + } +} diff --git a/Arrowgene.Ddon.Shared/AssetRepository.cs b/Arrowgene.Ddon.Shared/AssetRepository.cs index 3dbe76cca..0d4bcde8b 100644 --- a/Arrowgene.Ddon.Shared/AssetRepository.cs +++ b/Arrowgene.Ddon.Shared/AssetRepository.cs @@ -45,6 +45,7 @@ public class AssetRepository public const string SpecialShopKey = "SpecialShops.json"; public const string PawnCostReductionKey = "PawnCostReduction.json"; public const string BitterblackMazeKey = "BitterblackMaze.json"; + public const string QuestDropItemsKey = "QuestEnemyDrops.json"; private static readonly ILogger Logger = LogProvider.Logger(typeof(AssetRepository)); @@ -89,6 +90,7 @@ public AssetRepository(string folder) SpecialShopAsset = new SpecialShopAsset(); PawnCostReductionAsset = new PawnCostReductionAsset(); BitterblackMazeAsset = new BitterblackMazeAsset(); + QuestDropItemAsset = new QuestDropItemAsset(); } public List ClientErrorCodes { get; private set; } @@ -117,6 +119,7 @@ public AssetRepository(string folder) public SpecialShopAsset SpecialShopAsset { get; private set; } public PawnCostReductionAsset PawnCostReductionAsset { get; private set; } public BitterblackMazeAsset BitterblackMazeAsset { get; private set; } + static public QuestDropItemAsset QuestDropItemAsset { get; private set; } public void Initialize() { @@ -145,7 +148,7 @@ public void Initialize() RegisterAsset(value => SpecialShopAsset = value, SpecialShopKey, new SpecialShopDeserializer()); RegisterAsset(value => PawnCostReductionAsset = value, PawnCostReductionKey, new PawnCostReductionAssetDeserializer()); RegisterAsset(value => BitterblackMazeAsset = value, BitterblackMazeKey, new BitterblackMazeAssetDeserializer()); - + RegisterAsset(value => QuestDropItemAsset = value, QuestDropItemsKey, new QuestDropAssetDeserializer()); var questAssetDeserializer = new QuestAssetDeserializer(this.NamedParamAsset); questAssetDeserializer.LoadQuestsFromDirectory(Path.Combine(_directory.FullName, QuestAssestKey), QuestAssets); } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/QuestEnemyDrops.json b/Arrowgene.Ddon.Shared/Files/Assets/QuestEnemyDrops.json new file mode 100644 index 000000000..05461ffa8 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/QuestEnemyDrops.json @@ -0,0 +1,13896 @@ +{ + "dropsTables": [ + { + "id": 1, + "name": "Goblin (Lv1-9)", + "enemy_id": [ "0x010100" ], + "min_level": 1, + "max_level": 9, + "mdlType": 0, + "items": [ + [ + 7750, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 2, + "name": "Goblin (Lv10+)", + "enemy_id": ["0x010100"], + "min_level": 10, + "mdlType": 0, + "items": [ + [ + 7750, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7752, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 3, + "name": "Sling Goblin (Rock/Torch) (Lv1-9)", + "enemy_id": [ "0x010103", "0x010105" ], + "min_level": 1, + "max_level": 9, + "mdlType": 0, + "items": [ + [ + 7750, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 9393, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 21, + "name": "Sling Goblin (Rock/Torch) (Lv10+)", + "enemy_id": ["0x010103", "0x010105"], + "min_level": 10, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 9393, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7750, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 4, + "name": "Hobgoblin (Lv1-4)", + "enemy_id": ["0x010110"], + "min_level": 1, + "max_level": 4, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 5, + "name": "Hobgoblin (Lv5-29)", + "enemy_id": ["0x010110"], + "min_level": 5, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7839, + 1, + 1, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 6, + "name": "KillerBee (Lv1+)", + "enemy_id": ["0x011200"], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7812, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7836, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 8, + "name": "Sling Hobgoblin (Torch) (Lv1-14)", + "enemy_id": ["0x010114"], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 9, + "name": "Cyclops (Lv1-19)", + "enemy_id": [ "0x015000", "0x015001", "0x085110", "0x085111", "0x085112", "0x085113", "0x085114" ], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 7771, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7762, + 1, + 2, + 0, + false, + 0.95 + ] + ] + }, + { + "id": 10, + "name": "Giant Rat (Lv1+)", + "enemy_id": ["0x018401"], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 11, + "name": "Ox (Lv1-34)", + "enemy_id": ["0x018300"], + "min_level": 1, + "max_level": 34, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 12, + "name": "Ox (Lv35+)", + "enemy_id": ["0x018300"], + "min_level": 35, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 2, + 0, + false, + 1 + ], + [ + 7726, + 1, + 1, + 1, + false, + 0.8 + ] + ] + }, + { + "id": 13, + "name": "Rabbit (Lv1+)", + "enemy_id": ["0x018000"], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7773, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 14, + "name": "Orc Soldier (Lv1-9)", + "enemy_id": [ "0x015800", "0x085000", "0x085001", "0x085002", "0x085003" ], + "min_level": 1, + "max_level": 9, + "mdlType": 0, + "items": [ + [ + 7760, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 15, + "name": "Saurian (Lv1-14)", + "enemy_id": ["0x010400"], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7736, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7919, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 16, + "name": "Saurian (Lv15-79)", + "enemy_id": ["0x010400"], + "min_level": 15, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7919, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7736, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7916, + 1, + 1, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 17, + "name": "Redcap/Fighter/Slinger (Lv1-19)", + "enemy_id": ["0x011110", "0x011111", "0x011112"], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7853, + 1, + 1, + 1, + false, + 0.5 + ] + ] + }, + { + "id": 18, + "name": "Redcap Redcap/Fighter/Slinger (Lv20+)", + "enemy_id": [ "0x011110", "0x011111", "0x011112" ], + "min_level": 20, + "mdlType": 0, + "items": [ + [ + 7853, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7752, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7946, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 19, + "name": "Giant Saurian (Lv1-14)", + "enemy_id": ["0x010401"], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7919, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7736, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 22, + "name": "Doe (Lv1-19)", + "enemy_id": ["0x018201"], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 7917, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7768, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 23, + "name": "Doe (Lv20+)", + "enemy_id": ["0x018201"], + "min_level": 20, + "mdlType": 0, + "items": [ + [ + 7768, + 1, + 1, + 0, + false, + 1 + ], + [ + 7917, + 1, + 1, + 0, + false, + 1 + ], + [ + 7551, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 24, + "name": "Skeleton Sorcerer (Lv1-29)", + "enemy_id": ["0x010309", "0x075130", "0x075131", "0x075300"], + "min_level": 1, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 8002, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7804, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 25, + "name": "Skeleton Knight (Lv1-24)", + "enemy_id": ["0x010301"], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 7853, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 26, + "name": "Skeleton (Lv1-9)", + "enemy_id": [ "0x010300" ], + "min_level": 1, + "max_level": 9, + "mdlType": 0, + "items": [ + [ + 7767, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7854, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 27, + "name": "Skeleton (Lv10-89)", + "enemy_id": [ "0x010300" ], + "min_level": 10, + "max_level": 89, + "mdlType": 0, + "items": [ + [ + 7854, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7767, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7803, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 28, + "name": "Slime (Lv1-14)", + "enemy_id": [ "0x010900" ], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7834, + 1, + 2, + 0, + false, + 0.95 + ] + ] + }, + { + "id": 29, + "name": "Slime (Lv15-79)", + "enemy_id": [ "0x010900" ], + "min_level": 15, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7834, + 1, + 3, + 0, + false, + 0.95 + ], + [ + 7840, + 1, + 5, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 30, + "name": "Slime (Lv80+)", + "enemy_id": [ "0x010900" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7840, + 1, + 3, + 0, + false, + 0.95 + ], + [ + 7834, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 2, + 0, + false, + 1 + ] + ] + }, + { + "id": 31, + "name": "Wolf (Lv1-9)", + "enemy_id": [ "0x010200" ], + "min_level": 1, + "max_level": 9, + "mdlType": 0, + "items": [ + [ + 7798, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7773, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 32, + "name": "Wolf (Lv10-79)", + "enemy_id": [ "0x010200" ], + "min_level": 10, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7773, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7798, + 1, + 8, + 0, + false, + 0.9 + ], + [ + 7774, + 1, + 3, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 33, + "name": "Wolf (Lv80+)", + "enemy_id": [ "0x010200" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7774, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7798, + 1, + 10, + 0, + false, + 1 + ], + [ + 18655, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 34, + "name": "Brute Ape (Lv1-29)", + "enemy_id": [ "0x015504" ], + "min_level": 1, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7775, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7732, + 1, + 1, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 35, + "name": "Brute Ape (Lv30+)", + "enemy_id": [ "0x015504" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 7732, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7775, + 1, + 5, + 0, + false, + 0.8 + ], + [ + 7759, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 36, + "name": "Rogue Seeker (Lv1+)", + "enemy_id": [ "0x011001" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 36, + 1, + 2, + 0, + false, + 0.4 + ], + [ + 7791, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7790, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 37, + "name": "Rogue Fighter (Lv1+)", + "enemy_id": [ "0x011000" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 9365, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7790, + 1, + 10, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 38, + "name": "Rogue Mage (Lv1+)", + "enemy_id": [ "0x011005" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7790, + 1, + 20, + 0, + false, + 0.9 + ], + [ + 45, + 1, + 3, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 39, + "name": "Rogue Healer (Lv1+)", + "enemy_id": [ "0x011003" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7790, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7789, + 1, + 10, + 0, + false, + 0.8 + ], + [ + 34, + 1, + 3, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 40, + "name": "Rogue Defender (Lv1+)", + "enemy_id": [ "0x011004" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 36, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7790, + 1, + 5, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 41, + "name": "Rogue Warrior (Lv1+)", + "enemy_id": [ "0x011006" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7790, + 1, + 20, + 0, + false, + 0.3 + ], + [ + 7828, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 42, + "name": "Rogue Hunter (Lv1+)", + "enemy_id": [ "0x011002" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7790, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 59, + 1, + 9, + 0, + false, + 0.2 + ] + ] + }, + { + "id": 43, + "name": "Chicken (Lv1+)", + "enemy_id": [ "0x019000", "0x019001" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 44, + "name": "Pig (Lv1-19)", + "enemy_id": [ "0x018601", "0x018602", "0x018603", "0x018604" ], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7768, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 45, + "name": "Pig (Lv20+)", + "enemy_id": [ "0x018601", "0x018602", "0x018603", "0x018604" ], + "min_level": 20, + "mdlType": 0, + "items": [ + [ + 7768, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 1074, + 1, + 2, + 0, + false, + 1 + ], + [ + 7551, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 46, + "name": "Deep Slime (Lv1-14)", + "enemy_id": [ "0x010901" ], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7834, + 1, + 2, + 0, + false, + 0.99 + ] + ] + }, + { + "id": 47, + "name": "Deep Slime (Lv15-79)", + "enemy_id": [ "0x010901" ], + "min_level": 15, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7840, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7834, + 1, + 3, + 0, + false, + 1 + ] + ] + }, + { + "id": 48, + "name": "Deep Slime (Lv80+)", + "enemy_id": [ "0x010901" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7834, + 1, + 8, + 0, + false, + 1 + ], + [ + 17880, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7840, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 49, + "name": "Sulfur Saurian (Lv1-14)", + "enemy_id": [ "0x010410" ], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7920, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7736, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 50, + "name": "Sulfur Saurian (Lv15-79)", + "enemy_id": [ "0x010410" ], + "min_level": 15, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7920, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7916, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7736, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 51, + "name": "Sulfur Saurian (Lv80+)", + "enemy_id": [ "0x010410" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7916, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7920, + 1, + 3, + 0, + false, + 0.95 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 17872, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7736, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 52, + "name": "Orc Soldier (Lv10-29)", + "enemy_id": [ "0x015800", "0x085000", "0x085001", "0x085002", "0x085003" ], + "min_level": 10, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7839, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7760, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 53, + "name": "Orc Soldier (Lv30+)", + "enemy_id": [ "0x015800", "0x085000", "0x085001", "0x085002", "0x085003" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 7760, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7839, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7945, + 1, + 2, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 54, + "name": "Sphinx (Lv1-24)", + "enemy_id": [ "0x015302", "0x070700"], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7815, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7810, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 55, + "name": "Sphinx (Lv25-79)", + "enemy_id": [ "0x015302", "0x070700" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7815, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7810, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7747, + 1, + 4, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 56, + "name": "Sphinx (Lv80+)", + "enemy_id": [ "0x015302", "0x070700" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7747, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7810, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7815, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 57, + "name": "Harpy (Lv1-24)", + "enemy_id": [ "0x010600" ], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7811, + 1, + 2, + 0, + false, + 0.5 + ], + [ + 7813, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 58, + "name": "Harpy (Lv25-79)", + "enemy_id": [ "0x010600" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7813, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7811, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7783, + 1, + 3, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 59, + "name": "Harpy (Lv80+)", + "enemy_id": [ "0x010600" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7783, + 2, + 2, + 0, + false, + 0.9 + ], + [ + 7813, + 1, + 3, + 0, + false, + 0.95 + ], + [ + 17928, + 1, + 2, + 0, + false, + 0.2 + ] + ] + }, + { + "id": 60, + "name": "Armored Cyclops (Lv1-19)", + "enemy_id": [ "0x015002", "0x015003" ], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 7771, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8028, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7762, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 61, + "name": "Armored Cyclops (Lv20-33)", + "enemy_id": [ "0x015002", "0x015003" ], + "min_level": 20, + "max_level": 33, + "mdlType": 0, + "items": [ + [ + 7771, + 1, + 2, + 0, + false, + 1 + ], + [ + 7924, + 1, + 3, + 0, + false, + 0.4 + ], + [ + 8028, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7762, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 62, + "name": "Armored Cyclops (Lv34-54)", + "enemy_id": [ "0x015002", "0x015003" ], + "min_level": 34, + "max_level": 54, + "mdlType": 0, + "items": [ + [ + 7924, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7771, + 1, + 5, + 0, + false, + 1 + ], + [ + 7740, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 8028, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7762, + 1, + 2, + 0, + false, + 0.95 + ] + ] + }, + { + "id": 63, + "name": "Armored Cyclops (Lv55-79)", + "enemy_id": [ "0x015002", "0x015003" ], + "min_level": 55, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 9438, + 1, + 5, + 0, + false, + 0.1 + ], + [ + 7924, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7740, + 1, + 1, + 0, + false, + 1 + ], + [ + 7771, + 1, + 4, + 0, + false, + 1 + ], + [ + 8028, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7762, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 64, + "name": "Armored Cyclops (Lv80+)", + "enemy_id": [ "0x015002", "0x015003" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7762, + 1, + 2, + 0, + false, + 1 + ], + [ + 7740, + 1, + 1, + 0, + false, + 1 + ], + [ + 7771, + 1, + 3, + 0, + false, + 1 + ], + [ + 7924, + 1, + 3, + 0, + false, + 0.09 + ], + [ + 9438, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 8028, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17858, + 1, + 4, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 65, + "name": "Golem (Lv1-29)", + "enemy_id": [ "0x015100" ], + "min_level": 1, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7867, + 1, + 5, + 0, + false, + 0.3 + ], + [ + 7791, + 1, + 9, + 0, + false, + 1 + ] + ] + }, + { + "id": 66, + "name": "Golem (Lv30-45)", + "enemy_id": [ "0x015100" ], + "min_level": 30, + "max_level": 45, + "mdlType": 0, + "items": [ + [ + 7867, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7867, + 1, + 10, + 0, + false, + 0.01 + ], + [ + 7866, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 67, + "name": "Ogre (Lv1-29)", + "enemy_id": [ "0x015500" ], + "min_level": 1, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7776, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7766, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 68, + "name": "Ogre (Lv30+)", + "enemy_id": [ "0x015500" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 7766, + 1, + 4, + 0, + false, + 1 + ], + [ + 7776, + 1, + 6, + 0, + false, + 0.9 + ], + [ + 7777, + 1, + 5, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 69, + "name": "Undead (Lv1-3)", + "enemy_id": [ "0x010500", "0x010501" ], + "min_level": 1, + "max_level": 3, + "mdlType": 0, + "items": [ + [ + 7735, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 70, + "name": "Undead (Lv4-24)", + "enemy_id": [ "0x010500", "0x010501" ], + "min_level": 4, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7735, + 1, + 5, + 0, + false, + 1 + ], + [ + 7944, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 71, + "name": "Undead (Lv25-79)", + "enemy_id": [ "0x010500", "0x010501" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7944, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7735, + 1, + 4, + 0, + false, + 1 + ], + [ + 7838, + 1, + 3, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 72, + "name": "Undead (Lv80+)", + "enemy_id": [ "0x010500", "0x010501" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 17873, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7735, + 3, + 5, + 0, + false, + 1 + ], + [ + 7838, + 2, + 4, + 0, + false, + 0.9 + ], + [ + 7944, + 0, + 2, + 0, + false, + 1 + ] + ] + }, + { + "id": 73, + "name": "Sword Undead (Lv1-24)", + "enemy_id": [ "0x010503" ], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7944, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7735, + 1, + 3, + 0, + false, + 1 + ] + ] + }, + { + "id": 74, + "name": "Sword Undead (Lv25-79)", + "enemy_id": [ "0x010503" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7735, + 1, + 4, + 0, + false, + 1 + ], + [ + 7944, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 8024, + 1, + 3, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 75, + "name": "Sword Undead (Lv80+)", + "enemy_id": [ "0x010503" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8024, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7735, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7944, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 76, + "name": "Undead Stout (Lv1-24)", + "enemy_id": [ "0x010502" ], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7728, + 1, + 2, + 0, + false, + 0.3 + ], + [ + 7735, + 2, + 6, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 77, + "name": "Undead Stout (Lv25-79)", + "enemy_id": [ "0x010502" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7838, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7735, + 2, + 6, + 0, + false, + 0.9 + ], + [ + 7728, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 78, + "name": "Undead Stout (Lv80+)", + "enemy_id": [ "0x010502" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7728, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7735, + 3, + 5, + 0, + false, + 1 + ], + [ + 7838, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 79, + "name": "Wyrm (Lv1-79)", + "enemy_id": [ "0x015701", "0x070910" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7928, + 1, + 4, + 0, + false, + 1 + ], + [ + 7806, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7931, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 80, + "name": "Wyrm (Lv80+)", + "enemy_id": [ "0x015701", "0x070910" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7931, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7806, + 1, + 5, + 0, + false, + 1 + ], + [ + 7928, + 1, + 5, + 0, + false, + 1 + ] + ] + }, + { + "id": 81, + "name": "Chimera (Lv1-34)", + "enemy_id": [ "0x015200", "0x070800" ], + "min_level": 1, + "max_level": 34, + "mdlType": 0, + "items": [ + [ + 7779, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7807, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7920, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 82, + "name": "Chimera (Lv35-55)", + "enemy_id": [ "0x015200", "0x070800" ], + "min_level": 35, + "max_level": 54, + "mdlType": 0, + "items": [ + [ + 7807, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7779, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7785, + 1, + 2, + 0, + false, + 0.5 + ], + [ + 7920, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 83, + "name": "Chimera (Lv55-79)", + "enemy_id": [ "0x015200", "0x070800" ], + "min_level": 55, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7807, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7785, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 9437, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7920, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 84, + "name": "Chimera (Lv80+)", + "enemy_id": [ "0x015200", "0x070800" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7807, + 1, + 5, + 0, + false, + 1 + ], + [ + 7785, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 9437, + 1, + 1, + 0, + false, + 1 + ], + [ + 17857, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7920, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 85, + "name": "Giant Sulfur Saurian (Lv1-14)", + "enemy_id": [ "0x010411" ], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7736, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.2 + ], + [ + 7920, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 86, + "name": "Giant Sulfur Saurian (Lv15-79)", + "enemy_id": [ "0x010411" ], + "min_level": 15, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7736, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7920, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7916, + 1, + 1, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 87, + "name": "Giant Sulfur Saurian (Lv80+)", + "enemy_id": [ "0x010411" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7736, + 1, + 1, + 0, + false, + 0.1 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7920, + 1, + 5, + 0, + false, + 1 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7916, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 17872, + 1, + 1, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 88, + "name": "Giant Saurian (Lv15-79)", + "enemy_id": [ "0x010401" ], + "min_level": 15, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7736, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7916, + 1, + 2, + 0, + false, + 0.3 + ], + [ + 7919, + 1, + 3, + 0, + false, + 1 + ] + ] + }, + { + "id": 89, + "name": "Giant Saurian (Lv80+)", + "enemy_id": [ "0x010401" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7736, + 1, + 1, + 0, + false, + 1 + ], + [ + 7916, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 17872, + 1, + 2, + 0, + false, + 0.4 + ], + [ + 7919, + 1, + 5, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 90, + "name": "Cyclops (Lv20-34)", + "enemy_id": [ "0x015000", "0x015001", "0x085110", "0x085111", "0x085112", "0x085113", "0x085114" ], + "min_level": 20, + "max_level": 34, + "mdlType": 0, + "items": [ + [ + 7771, + 1, + 3, + 0, + false, + 1 + ], + [ + 7924, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7762, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 91, + "name": "Cyclops (Lv35-55)", + "enemy_id": [ "0x015000", "0x015001", "0x085110", "0x085111", "0x085112", "0x085113", "0x085114" ], + "min_level": 35, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7924, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 7771, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7740, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7762, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 92, + "name": "Cyclops (Lv56-79)", + "enemy_id": [ "0x015000", "0x015001", "0x085110", "0x085111", "0x085112", "0x085113", "0x085114" ], + "min_level": 56, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7924, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7740, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7771, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 9438, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7762, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 93, + "name": "Cyclops (Lv80+)", + "enemy_id": [ "0x015000", "0x015001", "0x085110", "0x085111", "0x085112", "0x085113", "0x085114" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7924, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 9438, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7771, + 1, + 5, + 0, + false, + 0.8 + ], + [ + 7740, + 1, + 1, + 0, + false, + 1 + ], + [ + 17858, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 7762, + 1, + 2, + 0, + false, + 1 + ] + ] + }, + { + "id": 94, + "name": "Behemoth (Lv1-40)", + "enemy_id": [ "0x015709", "0x070930" ], + "min_level": 1, + "max_level": 40, + "mdlType": 0, + "items": [ + [ + 7928, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7929, + 1, + 1, + 0, + false, + 0.05 + ], + [ + 7551, + 1, + 10, + 0, + false, + 1 + ] + ] + }, + { + "id": 95, + "name": "Behemoth (Lv41-55)", + "enemy_id": [ "0x015709", "0x070930" ], + "min_level": 41, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7928, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7929, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7799, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 20, + "name": "Behemoth (Lv56-79)", + "enemy_id": [ "0x015709", "0x070930" ], + "min_level": 56, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7928, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7929, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7799, + 1, + 6, + 0, + false, + 0.7 + ], + [ + 7763, + 1, + 3, + 0, + false, + 0.5 + ], + [ + 9258, + 1, + 3, + 0, + false, + 0.05 + ] + ] + }, + { + "id": 97, + "name": "Behemoth (Lv80+)", + "enemy_id": [ "0x015709", "0x070930" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7928, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7929, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7799, + 1, + 6, + 0, + false, + 0.9 + ], + [ + 7763, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 9258, + 1, + 5, + 0, + false, + 0.2 + ], + [ + 17879, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 98, + "name": "Snow Harpy (Lv1-24)", + "enemy_id": [ "0x010601" ], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7813, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 7783, + 1, + 1, + 0, + false, + 0.05 + ] + ] + }, + { + "id": 99, + "name": "Snow Harpy (Lv25-79)", + "enemy_id": [ "0x010601" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7813, + 1, + 6, + 0, + false, + 0.9 + ], + [ + 7783, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 17928, + 1, + 2, + 0, + false, + 0.01 + ] + ] + }, + { + "id": 100, + "name": "Snow Harpy (Lv80+)", + "enemy_id": [ "0x010601" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7783, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7813, + 1, + 5, + 0, + false, + 0.95 + ], + [ + 17928, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 101, + "name": "Hobgoblin (Lv30+)", + "enemy_id": [ "0x010110" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7839, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 8019, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 102, + "name": "Griffin (Lv1-34)", + "enemy_id": [ "0x015300", "0x070600" ], + "min_level": 1, + "max_level": 34, + "mdlType": 0, + "items": [ + [ + 7814, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7814, + 1, + 1, + 0, + false, + 1 + ], + [ + 7758, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 103, + "name": "Griffin (Lv35-55)", + "enemy_id": [ "0x015300", "0x070600" ], + "min_level": 35, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7758, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7814, + 1, + 4, + 0, + false, + 0.95 + ], + [ + 7727, + 1, + 2, + 0, + false, + 1 + ] + ] + }, + { + "id": 104, + "name": "Griffin (Lv56-79)", + "enemy_id": [ "0x015300", "0x070600" ], + "min_level": 56, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7727, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7758, + 1, + 1, + 0, + false, + 1 + ], + [ + 7814, + 1, + 4, + 0, + false, + 1 + ], + [ + 9439, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 105, + "name": "Griffin (Lv80+)", + "enemy_id": [ "0x015300", "0x070600" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 9439, + 1, + 4, + 0, + false, + 1 + ], + [ + 7814, + 1, + 7, + 0, + false, + 0.9 + ], + [ + 7758, + 1, + 1, + 0, + false, + 1 + ], + [ + 7727, + 1, + 3, + 0, + false, + 1 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 106, + "name": "Black Griffin (Lv1-55)", + "enemy_id": [ "0x070610" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7758, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7818, + 1, + 4, + 0, + false, + 0.7 + ], + [ + 7742, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 107, + "name": "Black Griffin (Lv56-79)", + "enemy_id": [ "0x070610" ], + "min_level": 56, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 9439, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7758, + 1, + 1, + 0, + false, + 1 + ], + [ + 7742, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 7818, + 1, + 5, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 108, + "name": "Black Griffin (Lv80+)", + "enemy_id": [ "0x070610" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7818, + 1, + 4, + 0, + false, + 1 + ], + [ + 9439, + 1, + 5, + 0, + false, + 1 + ], + [ + 7758, + 1, + 1, + 0, + false, + 1 + ], + [ + 7742, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 109, + "name": "Colossus (Lv1-19)", + "enemy_id": [ "0x015020", "0x070041", "0x070042" ], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 7771, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 8028, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 110, + "name": "Colossus (Lv20-34)", + "enemy_id": [ "0x015020", "0x070041", "0x070042" ], + "min_level": 20, + "max_level": 34, + "mdlType": 0, + "items": [ + [ + 7771, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7749, + 1, + 1, + 0, + false, + 1 + ], + [ + 8028, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 111, + "name": "Colossus (Lv35-79)", + "enemy_id": [ "0x015020", "0x070041", "0x070042" ], + "min_level": 35, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8028, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7771, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7749, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7925, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 112, + "name": "Colossus (Lv80+)", + "enemy_id": [ "0x015020", "0x070041", "0x070042" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7925, + 1, + 5, + 0, + false, + 0.8 + ], + [ + 7771, + 1, + 4, + 0, + false, + 1 + ], + [ + 8028, + 1, + 2, + 0, + false, + 0.95 + ], + [ + 7749, + 1, + 1, + 0, + false, + 1 + ], + [ + 17858, + 1, + 5, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 113, + "name": "Lindwurm (Lv1-40)", + "enemy_id": [ "0x015707", "0x070940" ], + "min_level": 1, + "max_level": 40, + "mdlType": 0, + "items": [ + [ + 7928, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7765, + 1, + 4, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 114, + "name": "Lindwurm (Lv41-79)", + "enemy_id": [ "0x015707", "0x070940" ], + "min_level": 41, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7765, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7928, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7932, + 1, + 3, + 0, + false, + 0.4 + ], + [ + 7928, + 1, + 6, + 0, + false, + 0.4 + ], + [ + 7820, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 115, + "name": "Dread Ape (Lv1-29)", + "enemy_id": [ "0x015502" ], + "min_level": 1, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7775, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7788, + 1, + 3, + 0, + false, + 0.6 + ], + [ + 7733, + 1, + 3, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 116, + "name": "Dread Ape (Lv30+)", + "enemy_id": [ "0x015502" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 7733, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7788, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7775, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7777, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 117, + "name": "Skeleton Sorcerer (Lv30-79)", + "enemy_id": [ "0x010309", "0x075130", "0x075131", "0x075300" ], + "min_level": 30, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7804, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8002, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7764, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 118, + "name": "Skeleton Sorcerer (Lv80+)", + "enemy_id": [ "0x010309", "0x075130", "0x075131", "0x075300" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7764, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7804, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 8002, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 119, + "name": "Spider (Lv1+)", + "enemy_id": [ "0x018800" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7960, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7836, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 120, + "name": "Boar (Lv1-19)", + "enemy_id": [ "0x018600" ], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7768, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 121, + "name": "Boar (Lv30+)", + "enemy_id": [ "0x018600" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 7768, + 1, + 3, + 0, + false, + 1 + ], + [ + 1074, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7551, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 122, + "name": "Grimwarg (Lv1-32)", + "enemy_id": [ "0x010205" ], + "min_level": 1, + "max_level": 32, + "mdlType": 0, + "items": [ + [ + 7778, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 123, + "name": "Grimwarg (Lv33-40)", + "enemy_id": [ "0x010205" ], + "min_level": 33, + "max_level": 40, + "mdlType": 0, + "items": [ + [ + 7778, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7801, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 124, + "name": "Grimwarg (Lv41-79)", + "enemy_id": [ "0x010205" ], + "min_level": 41, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7778, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7801, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7923, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 125, + "name": "Grimwarg (Lv80+)", + "enemy_id": [ "0x010205" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7778, + 1, + 8, + 0, + false, + 0.9 + ], + [ + 7801, + 1, + 7, + 0, + false, + 0.95 + ], + [ + 7923, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 126, + "name": "Ent (Lv1-29)", + "enemy_id": [ "0x015031" ], + "min_level": 1, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7987, + 1, + 5, + 0, + false, + 1 + ], + [ + 7971, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 127, + "name": "Ent (Lv30-54)", + "enemy_id": [ "0x015031" ], + "min_level": 30, + "max_level": 54, + "mdlType": 0, + "items": [ + [ + 7971, + 1, + 5, + 0, + false, + 1 + ], + [ + 7987, + 1, + 9, + 0, + false, + 1 + ], + [ + 7992, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 128, + "name": "Ent (Lv55+)", + "enemy_id": [ "0x015031" ], + "min_level": 55, + "mdlType": 0, + "items": [ + [ + 7992, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7987, + 1, + 6, + 0, + false, + 1 + ], + [ + 7971, + 1, + 5, + 0, + false, + 1 + ], + [ + 9442, + 1, + 4, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 129, + "name": "Goat (Lv1+)", + "enemy_id": [ "0x019100" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 130, + "name": "Direwolf (Lv1-14)", + "enemy_id": [ "0x010201" ], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7787, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 131, + "name": "Direwolf (15-40)", + "enemy_id": [ "0x010201" ], + "min_level": 15, + "max_level": 40, + "mdlType": 0, + "items": [ + [ + 7787, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7918, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 132, + "name": "Direwolf (Lv41-79)", + "enemy_id": [ "0x010201" ], + "min_level": 41, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7787, + 1, + 2, + 0, + false, + 0.99 + ], + [ + 7918, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7923, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 133, + "name": "Direwolf (Lv80+)", + "enemy_id": [ "0x010201" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7787, + 1, + 2, + 0, + false, + 1 + ], + [ + 7918, + 1, + 4, + 0, + false, + 1 + ], + [ + 7923, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 134, + "name": "Sling Hobgoblin (Torch) (Lv15-29)", + "enemy_id": [ "0x010114" ], + "min_level": 15, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 9396, + 1, + 3, + 0, + false, + 1 + ], + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 135, + "name": "Sling Hobgoblin (Torch) (Lv30+)", + "enemy_id": [ "0x010114" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 9396, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 8019, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 136, + "name": "Sling Hobgoblin (Oil) (Lv1-14)", + "enemy_id": [ "0x010112" ], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 137, + "name": "Sling Hobgoblin (Oil) (Lv15-29)", + "enemy_id": [ "0x010112" ], + "min_level": 15, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 9396, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 138, + "name": "Sling Hobgoblin (Oil) (Lv30+)", + "enemy_id": [ "0x010112" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 9396, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8019, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 139, + "name": "Shadow Chimera (Lv1-79)", + "enemy_id": [ "0x015203" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7911, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7800, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7748, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 140, + "name": "Shadow Chimera (Lv80+)", + "enemy_id": [ "0x015203" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7748, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7911, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7800, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 17857, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 141, + "name": "Goblin Bomber (Lv1-24)", + "enemy_id": [ "0x011150" ], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7839, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 142, + "name": "Goblin Bomber (Lv25+)", + "enemy_id": [ "0x011150" ], + "min_level": 25, + "mdlType": 0, + "items": [ + [ + 7839, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8034, + 1, + 4, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 143, + "name": "Zuhl Lv(Lv1+)", + "enemy_id": [ "0x020402", "0x071300", "0x080300" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7940, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 10986, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 144, + "name": "Orc Trooper (Lv1-55)", + "enemy_id": [ "0x015812" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7761, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8020, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 145, + "name": "Orc Trooper (Lv56+)", + "enemy_id": [ "0x015812" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 8020, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7761, + 1, + 2, + 0, + false, + 1 + ], + [ + 9443, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 146, + "name": "Captain Orc (Lv1-39)", + "enemy_id": [ "0x015820" ], + "min_level": 1, + "max_level": 39, + "mdlType": 0, + "items": [ + [ + 7761, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 147, + "name": "Captain Orc (Lv40-55)", + "enemy_id": [ "0x015820" ], + "min_level": 40, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7761, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7910, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 148, + "name": "Captain Orc (Lv56+)", + "enemy_id": [ "0x015820" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 7761, + 1, + 2, + 0, + false, + 1 + ], + [ + 7910, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 9443, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 149, + "name": "Worm (Lv1+)", + "enemy_id": [ "0x010800" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7836, + 1, + 3, + 0, + false, + 1 + ] + ] + }, + { + "id": 150, + "name": "Gargoyle (Lv1+)", + "enemy_id": [ "0x010603" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7849, + 1, + 5, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 151, + "name": "Sludgeman (Lv1-79)", + "enemy_id": [ "0x010510" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7840, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7841, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 152, + "name": "Sludgeman (Lv80+)", + "enemy_id": [ "0x010510" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7841, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7840, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 153, + "name": "Damned Goblin/Fighter (Lv1-44)", + "enemy_id": [ "0x011123", "0x011125" ], + "min_level": 1, + "max_level": 44, + "mdlType": 0, + "items": [ + [ + 8025, + 1, + 5, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 154, + "name": "Damned Goblin/Fighter (Lv45+)", + "enemy_id": [ "0x011123", "0x011125" ], + "min_level": 45, + "mdlType": 0, + "items": [ + [ + 8025, + 1, + 6, + 0, + false, + 0.9 + ], + [ + 7753, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 155, + "name": "Damned Wolf (Lv1-79)", + "enemy_id": [ "0x010208" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7781, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 8025, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 156, + "name": "Damned Wolf (Lv80+)", + "enemy_id": [ "0x010208" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7781, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 8025, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 157, + "name": "Blob (Lv1-79)", + "enemy_id": [ "0x010910" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7840, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7734, + 1, + 3, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 158, + "name": "Blob (Lv80+)", + "enemy_id": [ "0x010910" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7734, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7840, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 159, + "name": "Damned Golem (Lv1-55)", + "enemy_id": [ "0x015103" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 8025, + 1, + 6, + 0, + false, + 1 + ], + [ + 8029, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 160, + "name": "Damned Golem (Lv56+)", + "enemy_id": [ "0x015103" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 8029, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8025, + 1, + 7, + 0, + false, + 0.9 + ], + [ + 7871, + 1, + 3, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 161, + "name": "Geo Golem (Lv1-55)", + "enemy_id": [ "0x015104" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7869, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7870, + 1, + 6, + 0, + false, + 0.9 + ], + [ + 7867, + 1, + 8, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 162, + "name": "Geo Golem (Lv56+)", + "enemy_id": [ "0x015104" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 7867, + 1, + 8, + 0, + false, + 0.9 + ], + [ + 7870, + 1, + 8, + 0, + false, + 0.9 + ], + [ + 7869, + 1, + 5, + 0, + false, + 1 + ], + [ + 7913, + 1, + 3, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 163, + "name": "Skeleton Mage (Lv1-19)", + "enemy_id": [ "0x010308", "0x075120" ], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 7767, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8002, + 1, + 3, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 164, + "name": "Skeleton Mage (Lv20-79)", + "enemy_id": [ "0x010308", "0x075120" ], + "min_level": 20, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8002, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7767, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7804, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 165, + "name": "Skeleton Mage (Lv80+)", + "enemy_id": [ "0x010308", "0x075120" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7804, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 7767, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 8002, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 166, + "name": "Warrior Undead (Lv1-79)", + "enemy_id": [ "0x010504" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7741, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7728, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 167, + "name": "Warrior Undead (Lv80+)", + "enemy_id": [ "0x010504" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7728, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7741, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 168, + "name": "Eliminator (Lv1+)", + "enemy_id": [ "0x010508" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7726, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 169, + "name": "Living Armor (Lv1-79)", + "enemy_id": [ "0x010306" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7868, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8022, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 170, + "name": "Living Armor (Lv80+)", + "enemy_id": [ "0x010306" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8022, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7868, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 171, + "name": "Alchemized Griffin (Lv1-79)", + "enemy_id": [ "0x015304" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7758, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 8031, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7823, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7823, + 1, + 2, + 0, + false, + 0.1 + ], + [ + 8031, + 1, + 2, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 172, + "name": "Alchemized Griffin (Lv80+)", + "enemy_id": [ "0x015304" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8031, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 8031, + 1, + 3, + 0, + false, + 0.1 + ], + [ + 7758, + 1, + 1, + 0, + false, + 1 + ], + [ + 7823, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7823, + 1, + 5, + 0, + false, + 0.1 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 173, + "name": "Alchemized Skeleton (Lv1-45)", + "enemy_id": [ "0x010312" ], + "min_level": 1, + "max_level": 45, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8026, + 1, + 3, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 174, + "name": "Alchemized Skeleton (Lv46-79)", + "enemy_id": [ "0x010312" ], + "min_level": 46, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8026, + 1, + 4, + 0, + false, + 0.7 + ], + [ + 7803, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7850, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 175, + "name": "Alchemized Skeleton (Lv80+)", + "enemy_id": [ "0x010312" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7850, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8026, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7803, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 176, + "name": "Alchemized Goblin (Lv1-49)", + "enemy_id": [ "0x011120", "0x011121" ], + "min_level": 1, + "max_level": 49, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8026, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 177, + "name": "Alchemized Goblin/Fighter (Lv50+)", + "enemy_id": [ "0x011120", "0x011121" ], + "min_level": 50, + "mdlType": 0, + "items": [ + [ + 8026, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8027, + 1, + 2, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 178, + "name": "Skeleton Knight (Lv25-79)", + "enemy_id": [ "0x010301" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7853, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8024, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 179, + "name": "Skeleton Knight (Lv80+)", + "enemy_id": [ "0x010301" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8024, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7803, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7853, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 180, + "name": "Alchemized Wolf (Lv1-49)", + "enemy_id": [ "0x010207" ], + "min_level": 1, + "max_level": 49, + "mdlType": 0, + "items": [ + [ + 7917, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8026, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 181, + "name": "Alchemized Wolf (Lv50-79)", + "enemy_id": [ "0x010207" ], + "min_level": 50, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8026, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7917, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 8027, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 182, + "name": "Alchemized Wolf (Lv80+)", + "enemy_id": [ "0x010207" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8027, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7917, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8026, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 183, + "name": "Blue Newt (Lv1-14)", + "enemy_id": [ "0x010460" ], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7848, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7921, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 184, + "name": "Blue Newt (Lv15-79)", + "enemy_id": [ "0x010460" ], + "min_level": 15, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7738, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7848, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7921, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7916, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 185, + "name": "Blue Newt (Lv80+)", + "enemy_id": [ "0x010460" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7916, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7921, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7848, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7738, + 1, + 1, + 0, + false, + 1 + ], + [ + 17872, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 186, + "name": "Large Newt (Lv1-79)", + "enemy_id": [ "0x010461" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7916, + 1, + 3, + 0, + false, + 0.6 + ], + [ + 7921, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7738, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 187, + "name": "Large Newt (Lv80+)", + "enemy_id": [ "0x010461" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7738, + 1, + 1, + 0, + false, + 1 + ], + [ + 7916, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17872, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7921, + 1, + 5, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 188, + "name": "Forest Goblin/Fighter/Slinger (Lv1-39)", + "enemy_id": [ "0x011100", "0x011101", "0x011102" ], + "min_level": 1, + "max_level": 39, + "mdlType": 0, + "items": [ + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7985, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 189, + "name": "Forest Goblin/Fighter/Slinger (Lv40+)", + "enemy_id": [ "0x011100", "0x011101", "0x011102" ], + "min_level": 40, + "mdlType": 0, + "items": [ + [ + 7985, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7752, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7986, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 190, + "name": "Mist Fighter (Lv1-17)", + "enemy_id": [ "0x011030" ], + "min_level": 1, + "max_level": 17, + "mdlType": 0, + "items": [ + [ + 7893, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 191, + "name": "Mist Fighter (Lv18+)", + "enemy_id": [ "0x011030" ], + "min_level": 18, + "mdlType": 0, + "items": [ + [ + 7893, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7912, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 192, + "name": "Mist Warrior (Lv1+)", + "enemy_id": [ "0x011034" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7893, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 193, + "name": "Shadow Harpy (Lv1-44)", + "enemy_id": [ "0x010607" ], + "min_level": 1, + "max_level": 44, + "mdlType": 0, + "items": [ + [ + 7783, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 194, + "name": "Shadow Harpy (Lv45-55)", + "enemy_id": [ "0x010607" ], + "min_level": 45, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7783, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7911, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 195, + "name": "Shadow Harpy (Lv56+)", + "enemy_id": [ "0x010607" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 7911, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7783, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 9441, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 196, + "name": "Shadow Goblin/Fighter/Leader (Lv1-54)", + "enemy_id": [ "0x011130", "0x011131", "0x011160" ], + "min_level": 1, + "max_level": 54, + "mdlType": 0, + "items": [ + [ + 7753, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7911, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 197, + "name": "Shadow Goblin/Fighter/Leader (Lv55+)", + "enemy_id": [ "0x011130", "0x011131", "0x011160" ], + "min_level": 55, + "mdlType": 0, + "items": [ + [ + 7911, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7753, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 9441, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 198, + "name": "Mist Sorcerer (Lv1-17)", + "enemy_id": [ "0x011033" ], + "min_level": 1, + "max_level": 17, + "mdlType": 0, + "items": [ + [ + 7893, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 199, + "name": "Mist Sorcerer (Lv18+)", + "enemy_id": [ "0x011033" ], + "min_level": 18, + "mdlType": 0, + "items": [ + [ + 7893, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 200, + "name": "Banded Fighter (Lv1+)", + "enemy_id": [ "0x011010" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7828, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 9382, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 201, + "name": "Banded Hunter (Lv1+)", + "enemy_id": [ "0x011012" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7828, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 9403, + 1, + 3, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 202, + "name": "Saurian Sage (Lv1-79)", + "enemy_id": [ "0x010430" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7920, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7916, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 203, + "name": "Saurian Sage (Lv80+)", + "enemy_id": [ "0x010430" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7738, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7920, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7916, + 1, + 3, + 0, + false, + 0.5 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 17872, + 1, + 2, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 204, + "name": "Infected Snow Harpy (Lv1-79)", + "enemy_id": [ "0x010612" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.4 + ], + [ + 7783, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 205, + "name": "Infected Snow Harpy (Lv80+)", + "enemy_id": [ "0x010612" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 17928, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 11408, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7783, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 206, + "name": "Infected Griffin (Lv1-79)", + "enemy_id": [ "0x015306" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 13223, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 207, + "name": "Infected Griffin (Lv80+)", + "enemy_id": [ "0x015306" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 13223, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 11408, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 208, + "name": "Infected Gorecyclops (Lv1-79)", + "enemy_id": [ "0x015012" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 11774, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 209, + "name": "Infected Gorecyclops (Lv80+)", + "enemy_id": [ "0x015012" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 11774, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 11408, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 17858, + 1, + 4, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 210, + "name": "Nightmare (Lv1-79)", + "enemy_id": [ "0x015305" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7819, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7746, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 211, + "name": "Nightmare (Lv80+)", + "enemy_id": [ "0x015305" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7746, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7819, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 212, + "name": "Infected Hobgoblins/Fighter (Lv1+)", + "enemy_id": [ "0x010160", "0x010161" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 8019, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 213, + "name": "Infected Direwolf (Lv1-79)", + "enemy_id": [ "0x010209" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7787, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7918, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 214, + "name": "Infected Direwolf (Lv80+)", + "enemy_id": [ "0x010209" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7918, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 11408, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7787, + 1, + 4, + 0, + false, + 0.7 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 215, + "name": "Bolt Grimwarg (Lv1-79)", + "enemy_id": [ "0x010211" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7778, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 7801, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7923, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 216, + "name": "Bolt Grimwarg (Lv80+)", + "enemy_id": [ "0x010211" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7923, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7801, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7778, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 217, + "name": "Eliminator Slay (Lv1+)", + "enemy_id": [ "0x010530" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7726, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 219, + "name": "Golem (Lv46+)", + "enemy_id": [ "0x015100" ], + "min_level": 46, + "mdlType": 0, + "items": [ + [ + 7867, + 1, + 5, + 0, + false, + 1 + ], + [ + 7866, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7860, + 1, + 3, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 220, + "name": "Gorecyclops (Lv1-79)", + "enemy_id": [ "0x015010", "0x070050", "0x070051" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 15963, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 221, + "name": "Gorecyclops (Lv80+)", + "enemy_id": [ "0x015010", "0x070050", "0x070051" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15963, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17858, + 1, + 3, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 222, + "name": "Gorechimera (Lv1-79)", + "enemy_id": [ "0x015201", "0x070820" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 15965, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 223, + "name": "Gorechimera (Lv80+)", + "enemy_id": [ "0x015201", "0x070820" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15965, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17857, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 224, + "name": "Strix (Lv1+)", + "enemy_id": [ "0x010610" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7778, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 225, + "name": "Flame Skeleton (Lv1-79)", + "enemy_id": [ "0x010314" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8011, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 226, + "name": "Flame Skeleton (Lv80+)", + "enemy_id": [ "0x010314" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8011, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 227, + "name": "Flame Skeleton Brute (Lv1-79)", + "enemy_id": [ "0x010320" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8011, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7850, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 228, + "name": "Flame Skeleton Brute (Lv80+)", + "enemy_id": [ "0x010320" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7850, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 8011, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.2 + ] + ] + }, + { + "id": 229, + "name": "Moth (Lv1+)", + "enemy_id": [ "0x011210", "0x011211" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7836, + 1, + 3, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 230, + "name": "Pixies (Lv1+)", + "enemy_id": [ "0x010150", "0x010151", "0x010152", "0x010153", "0x010155" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 15959, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 231, + "name": "Buck (Lv1-19)", + "enemy_id": [ "0x018200" ], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 7917, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7750, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 232, + "name": "Buck (Lv20+)", + "enemy_id": [ "0x018200" ], + "min_level": 20, + "mdlType": 0, + "items": [ + [ + 7750, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7917, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7551, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 233, + "name": "Siren (Lv1-79)", + "enemy_id": [ "0x010605" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 16011, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 234, + "name": "Siren (Lv80+)", + "enemy_id": [ "0x010605" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 16011, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17928, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 235, + "name": "Ghost (Lv1-79)", + "enemy_id": [ "0x015620" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7868, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 236, + "name": "Ghost (Lv80+)", + "enemy_id": [ "0x015620" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7868, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17878, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 237, + "name": "Ghost Mail (Lv1-79)", + "enemy_id": [ "0x010311" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7868, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7889, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 238, + "name": "Ghost Mail (Lv80+)", + "enemy_id": [ "0x010311" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7889, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7868, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17878, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 239, + "name": "Warg (Lv1-79)", + "enemy_id": [ "0x010203" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 15960, + 1, + 2, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 240, + "name": "Warg (Lv80+)", + "enemy_id": [ "0x010203" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15960, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 241, + "name": "Tarasque (Lv1-79)", + "enemy_id": [ "0x015720" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 15934, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 242, + "name": "Tarasque (Lv80+)", + "enemy_id": [ "0x015720" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15934, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 17879, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 243, + "name": "Wisened Tarasque (Lv1-79)", + "enemy_id": [ "0x015721" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 17154, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 244, + "name": "Wisened Tarasque (Lv80+)", + "enemy_id": [ "0x015721" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 17154, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 17879, + 1, + 3, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 246, + "name": "Little Spine (Lv1+)", + "enemy_id": [ "0x015507" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7918, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 247, + "name": "Foot-Biter (Lv1+)", + "enemy_id": [ "0x011500" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 15961, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 248, + "name": "Phindymian Ent (Lv1+)", + "enemy_id": [ "0x015032", "0x070520" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 16012, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 15990, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 249, + "name": "White Chimera (Lv1-55)", + "enemy_id": [ "0x015202", "0x070810" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7807, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7780, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7739, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7921, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 250, + "name": "White Chimera (Lv56-79)", + "enemy_id": [ "0x015202", "0x070810" ], + "min_level": 56, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7739, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7780, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7807, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 9437, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7921, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 251, + "name": "White Chimera (Lv80+)", + "enemy_id": [ "0x015202", "0x070810" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 9437, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7739, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7780, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7807, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17857, + 1, + 3, + 0, + false, + 0.6 + ], + [ + 7921, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 252, + "name": "Green Guardian (Lv1-79)", + "enemy_id": [ "0x010210" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 16009, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 253, + "name": "Green Guardian (Lv80+)", + "enemy_id": [ "0x010210" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 16009, + 1, + 5, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 254, + "name": "Spineback (Lv1+)", + "enemy_id": [ "0x015506", "0x070210", "0x070211" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 15969, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 255, + "name": "Mole Troll (Lv1+)", + "enemy_id": [ "0x015041", "0x070410" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7809, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7770, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 8001, + 1, + 3, + 0, + false, + 0.6 + ], + [ + 47, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 256, + "name": "Wild Boar (Lv1+)", + "enemy_id": [ "0x019300", "0x019301" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 257, + "name": "Severely Infected Warg (Lv1-79)", + "enemy_id": [ "0x010220" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 15960, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 258, + "name": "Severely Infected Warg (Lv80+)", + "enemy_id": [ "0x010220" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15960, + 1, + 3, + 0, + false, + 0.6 + ], + [ + 11408, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 259, + "name": "Stymphalides (Lv1+)", + "enemy_id": [ "0x010611" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 15962, + 1, + 2, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 260, + "name": "Severely Infected Stymphalides (Lv1+)", + "enemy_id": [ "0x010614" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 15962, + 1, + 3, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 261, + "name": "Bolt Skeleton (Lv1-79)", + "enemy_id": [ "0x010316" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 8013, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 262, + "name": "Bolt Skeleton (Lv80+)", + "enemy_id": [ "0x010316" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8013, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7803, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 263, + "name": "Bolt Skeleton Brute (Lv1-79)", + "enemy_id": [ "0x010322" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7850, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 8013, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 264, + "name": "Bolt Skeleton Brute (Lv80+)", + "enemy_id": [ "0x010322" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8013, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 7850, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.2 + ] + ] + }, + { + "id": 265, + "name": "Bifrest (Lv80+)", + "enemy_id": [ "0x015321", "0x070640", "0x070641" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15928, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 266, + "name": "Bifrest (Lv1-79)", + "enemy_id": [ "0x015321", "0x070640", "0x070641" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 15928, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 267, + "name": "Frog (Lv1+)", + "enemy_id": [ "0x019200", "0x019201" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 1074, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 268, + "name": "Grigori (Lv1+)", + "enemy_id": [ "0x015900" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 15973, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 269, + "name": "Bearded Grigori (Lv1+)", + "enemy_id": [ "0x015920" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 15973, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 15974, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 270, + "name": "Troll (Lv1-19)", + "enemy_id": [ "0x015040" ], + "min_level": 1, + "max_level": 19, + "mdlType": 0, + "items": [ + [ + 7809, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 271, + "name": "Troll (Lv20-29)", + "enemy_id": [ "0x015040" ], + "min_level": 20, + "max_level": 29, + "mdlType": 0, + "items": [ + [ + 7809, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8000, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 272, + "name": "Troll (Lv30+)", + "enemy_id": [ "0x015040" ], + "min_level": 30, + "mdlType": 0, + "items": [ + [ + 8000, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7809, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7784, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 273, + "name": "Infected Orcs (Lv1+)", + "enemy_id": [ "0x015813", "0x015814", "0x015815" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 274, + "name": "Shadow Wolf (Lv1-44)", + "enemy_id": [ "0x010206" ], + "min_level": 1, + "max_level": 44, + "mdlType": 0, + "items": [ + [ + 7781, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 275, + "name": "Shadow Wolf (Lv45-55)", + "enemy_id": [ "0x010206" ], + "min_level": 45, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7781, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7911, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 276, + "name": "Shadow Wolf (Lv56+)", + "enemy_id": [ "0x010206" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 7911, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7781, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 9441, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 277, + "name": "Giant Saurian Sage (Lv1-79)", + "enemy_id": [ "0x010431" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7920, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7916, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 278, + "name": "Giant Saurian Sage (Lv80+)", + "enemy_id": [ "0x010431" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7738, + 1, + 1, + 0, + false, + 1 + ], + [ + 7920, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 7916, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 17872, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7737, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 279, + "name": "Ruby Eye (Lv1-44)", + "enemy_id": [ "0x015411" ], + "min_level": 1, + "max_level": 44, + "mdlType": 0, + "items": [ + [ + 7896, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7900, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 280, + "name": "Ruby Eye (Lv45+)", + "enemy_id": [ "0x015411" ], + "min_level": 45, + "mdlType": 0, + "items": [ + [ + 7900, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7896, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 7730, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 281, + "name": "Rock Saurian (Lv1-79)", + "enemy_id": [ "0x010450" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7855, + 1, + 2, + 0, + false, + 0.4 + ], + [ + 7864, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 282, + "name": "Rock Saurian (Lv80+)", + "enemy_id": [ "0x010450" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7738, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7855, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7864, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 17872, + 1, + 2, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 283, + "name": "Rock Saurian Spinel (Lv1-14)", + "enemy_id": [ "0x010451" ], + "min_level": 1, + "max_level": 14, + "mdlType": 0, + "items": [ + [ + 7855, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7864, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 284, + "name": "Rock Saurian Spinel (Lv15-79)", + "enemy_id": [ "0x010451" ], + "min_level": 15, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7864, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7855, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 7907, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 285, + "name": "Rock Saurian Spinel (Lv80+)", + "enemy_id": [ "0x010451" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7907, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7855, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7864, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7738, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 17872, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 286, + "name": "High Pixies (Lv1+)", + "enemy_id": [ "0x010190", "0x010191", "0x010192" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 15959, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 16008, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 287, + "name": "Lotus Fin (Lv90+)", + "enemy_id": [ "0x015718" ], + "min_level": 90, + "mdlType": 0, + "items": [ + [ + 7765, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17879, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 288, + "name": "Pyre Saurian (Lv95+)", + "enemy_id": [ "0x010440" ], + "min_level": 95, + "mdlType": 0, + "items": [ + [ + 7869, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 21262, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 289, + "name": "Severely Infected Pixie (Lv1+)", + "enemy_id": [ "0x010170", "0x010171", "0x0010172" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 15959, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 290, + "name": "Severely Infected Demon (Lv1+)", + "enemy_id": [ "0x015910" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 0.5 + ], + [ + 16010, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 291, + "name": "Severely Infected Gorecyclops (Lv1-79)", + "enemy_id": [ "0x015017" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 2, + 0, + false, + 1 + ], + [ + 15963, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 15927, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 292, + "name": "Severely Infected Gorecyclops (Lv80+)", + "enemy_id": [ "0x015017" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15927, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 11408, + 1, + 1, + 0, + false, + 1 + ], + [ + 15963, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17858, + 1, + 4, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 293, + "name": "Severely Infected Griffin (Lv1-79)", + "enemy_id": [ "0x015310" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 16014, + 1, + 3, + 0, + false, + 0.8 + ], + [ + 15995, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 294, + "name": "Severely Infected Griffin (Lv80+)", + "enemy_id": [ "0x015310" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15995, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 16014, + 1, + 3, + 0, + false, + 1 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 295, + "name": "Severely Infected Behemoth (Lv1-79)", + "enemy_id": [ "0x015717" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 15971, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 296, + "name": "Severely Infected Behemoth (Lv80+)", + "enemy_id": [ "0x015717" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15971, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 11408, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 17879, + 1, + 3, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 297, + "name": "Pixie King (Lv1+)", + "enemy_id": [ "0x010195" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 16008, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 15989, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 298, + "name": "Witch (Lv1-40)", + "enemy_id": [ "0x015604" ], + "min_level": 1, + "max_level": 40, + "mdlType": 0, + "items": [ + [ + 7894, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7900, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7832, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 299, + "name": "Witch (Lv41-79)", + "enemy_id": [ "0x015604" ], + "min_level": 41, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7832, + 1, + 1, + 0, + false, + 0.2 + ], + [ + 7894, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7900, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7947, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 300, + "name": "Witch (Lv80+)", + "enemy_id": [ "0x015604" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7947, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7894, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7900, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7832, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 17878, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 301, + "name": "Cragger (Lv1+)", + "enemy_id": [ "0x015508" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17870, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17871, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 302, + "name": "Little Crag (Lv1+)", + "enemy_id": [ "0x015509" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17870, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 303, + "name": "Wight (Lv1-24)", + "enemy_id": [ "0x015600", "0x0075700" ], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7804, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7729, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 304, + "name": "Wight (Lv25-79)", + "enemy_id": [ "0x015600", "0x0075700" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7729, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 7804, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7769, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 305, + "name": "Wight (Lv80+)", + "enemy_id": [ "0x015600", "0x0075700" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7769, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7729, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7804, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 17878, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 306, + "name": "Leech (Lv1+)", + "enemy_id": [ "0x010810" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7836, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 307, + "name": "War-Ready Gorecyclops (Lv1-79)", + "enemy_id": [ "0x015060" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 17876, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 308, + "name": "War-Ready Gorecyclops (Lv80+)", + "enemy_id": [ "0x015060" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 17876, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17858, + 1, + 3, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 309, + "name": "Solider Dwarf Orcs (Lv1-80+)", + "enemy_id": [ "0x015821", "0x015822", "0x015823", "0x015824" ], + "min_level": 1, + "max_level": 80, + "mdlType": 0, + "items": [ + [ + 17859, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 310, + "name": "War-Ready Grimwarg (Lv1-80+)", + "enemy_id": [ "0x010230" ], + "min_level": 1, + "max_level": 80, + "mdlType": 0, + "items": [ + [ + 17876, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 18655, + 1, + 1, + 0, + false, + 0.2 + ] + ] + }, + { + "id": 311, + "name": "Greater Goblins (Sword/Torch) (Lv1-85+)", + "enemy_id": [ "0x010130", "0x010131" ], + "min_level": 1, + "max_level": 85, + "mdlType": 0, + "items": [ + [ + 17868, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 312, + "name": "Grim Goblins (Lv1+)", + "enemy_id": [ "0x010120", "0x010121", "0x010123", "0x010124" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17861, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 313, + "name": "War-Ready Saurian (Lv1-85+)", + "enemy_id": [ "0x010470" ], + "min_level": 1, + "max_level": 85, + "mdlType": 0, + "items": [ + [ + 17859, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 17876, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 18726, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 314, + "name": "War-Ready Ogre (Lv88+)", + "enemy_id": [ "0x015510" ], + "min_level": 88, + "mdlType": 0, + "items": [ + [ + 17859, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 17876, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 19655, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 315, + "name": "General Orc (Lv1-39)", + "enemy_id": [ "0x015830" ], + "min_level": 1, + "max_level": 39, + "mdlType": 0, + "items": [ + [ + 7761, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 8020, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 316, + "name": "General Orc (Lv40-55)", + "enemy_id": [ "0x015830" ], + "min_level": 40, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 8020, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7761, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7910, + 1, + 2, + 0, + false, + 0.5 + ] + ] + }, + { + "id": 317, + "name": "General Orc (Lv56+)", + "enemy_id": [ "0x015830" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 7910, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7761, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 8020, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 9443, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 321, + "name": "Squad Leader Dwarf Orc (Lv1-85+)", + "enemy_id": [ "0x015826" ], + "min_level": 1, + "max_level": 85, + "mdlType": 0, + "items": [ + [ + 17859, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 322, + "name": "Skeleton Cyclops (Lv1-??)", + "enemy_id": [ "0x015050" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17858, + 1, + 4, + 0, + false, + 0.95 + ], + [ + 17864, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 323, + "name": "Skeleton Warg (Lv85+)", + "enemy_id": [ "0x010221" ], + "min_level": 85, + "mdlType": 0, + "items": [ + [ + 17869, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 324, + "name": "Saurian (Lv80+)", + "enemy_id": [ "0x010400" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7919, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7736, + 1, + 1, + 0, + false, + 1 + ], + [ + 7916, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 17872, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 325, + "name": "Goblin Shamans (Lv1+)", + "enemy_id": [ "0x010140" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17861, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 17867, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 326, + "name": "Goremanticore (Lv85+)", + "enemy_id": [ "0x015211" ], + "min_level": 85, + "mdlType": 0, + "items": [ + [ + 17866, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17927, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 19647, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 327, + "name": "War-Ready Giant Saurian (Lv1-85+)", + "enemy_id": [ "0x010471" ], + "min_level": 1, + "max_level": 85, + "mdlType": 0, + "items": [ + [ + 19654, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 18726, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 328, + "name": "War-Ready Goremanticore (Lv1+)", + "enemy_id": [ "0x015220" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17876, + 1, + 3, + 0, + false, + 0.4 + ], + [ + 17866, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 329, + "name": "Catoblepas (Lv1+)", + "enemy_id": [ "0x015730" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17876, + 1, + 3, + 0, + false, + 0.3 + ], + [ + 17856, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 18736, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 330, + "name": "Geo Saurian (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 331, + "name": "Giant Geo Saurian (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 332, + "name": "Banded Healer (Lv1+)", + "enemy_id": [ "0x011013" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 8002, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7552, + 1, + 3, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 333, + "name": "Banded Seeker (Lv1+)", + "enemy_id": [ "0x011011" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 9398, + 1, + 3, + 0, + false, + 0.3 + ], + [ + 7828, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 334, + "name": "Banded Mage (Lv1+)", + "enemy_id": [ "0x011015" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7791, + 1, + 2, + 0, + false, + 0.3 + ], + [ + 45, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 335, + "name": "Cockatrice (Lv1-42)", + "enemy_id": [ "0x015301" ], + "min_level": 1, + "max_level": 42, + "mdlType": 0, + "items": [ + [ + 7842, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7816, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 336, + "name": "Cockatrice (Lv43-55)", + "enemy_id": [ "0x015301" ], + "min_level": 43, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7842, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7745, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7816, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 337, + "name": "Cockatrice (Lv56-79)", + "enemy_id": [ "0x015301" ], + "min_level": 56, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 9440, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7745, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7842, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7816, + 1, + 4, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 338, + "name": "Cockatrice (Lv80+)", + "enemy_id": [ "0x015301" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7842, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7745, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 9440, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7816, + 1, + 5, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 339, + "name": "Manticore (Lv1-79)", + "enemy_id": [ "0x015210", "0x0070830" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7774, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7925, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 11805, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 340, + "name": "Manticore (Lv80+)", + "enemy_id": [ "0x015210", "0x0070830" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 11805, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7925, + 1, + 4, + 0, + false, + 0.8 + ], + [ + 7774, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 17866, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 341, + "name": "Death (Lv1+)", + "enemy_id": [ "0x015603" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 21630, + 1, + 1, + 0, + false, + 1 + ], + [ + 23364, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 342, + "name": "Blaze Harpy (Lv95+)", + "enemy_id": [ "0x010608" ], + "min_level": 95, + "mdlType": 0, + "items": [ + [ + 7783, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 21263, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 343, + "name": "War-Ready Nightmare (Lv93+)", + "enemy_id": [ "0x015330" ], + "min_level": 93, + "mdlType": 0, + "items": [ + [ + 17876, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 21224, + 1, + 3, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 344, + "name": "Dagon (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 345, + "name": "Drake (Lv1-79)", + "enemy_id": [ "0x015700", "0x070900" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7928, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7806, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7930, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 346, + "name": "Drake (Lv80+)", + "enemy_id": [ "0x015700", "0x070900" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7930, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7928, + 1, + 5, + 0, + false, + 0.9 + ], + [ + 7806, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17879, + 1, + 3, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 347, + "name": "Electric Slime (Lv1-79)", + "enemy_id": [ "0x010907" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 348, + "name": "Electric Slime (Lv80+)", + "enemy_id": [ "0x010907" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 349, + "name": "Mist Drake (Lv1-79)", + "enemy_id": [ "0x015710" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7802, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7922, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 350, + "name": "Mist Drake (Lv80+)", + "enemy_id": [ "0x015710" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7802, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7922, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17879, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 351, + "name": "Dark Skeleton (Lv1-79)", + "enemy_id": [ "0x010318" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8015, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7803, + 1, + 2, + 0, + false, + 1 + ] + ] + }, + { + "id": 352, + "name": "Dark Skeleton (Lv80+)", + "enemy_id": [ "0x010318" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 2, + 0, + false, + 1 + ], + [ + 8015, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 353, + "name": "Dark Corpses (Lv1+)", + "enemy_id": [ "0x010516", "0x010520" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17873, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 354, + "name": "Bolt Corpses (Lv1+)", + "enemy_id": [ "0x010514", "0x010518" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17873, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 355, + "name": "Ancestor Orc (Lv1-80+)", + "enemy_id": [ "0x015931", "0x015832", "0x015833" ], + "min_level": 1, + "max_level": 80, + "mdlType": 0, + "items": [ + [ + 21219, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 356, + "name": "Ancestor Origin (Lv1-??)", + "enemy_id": [ "0x015870" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 21225, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 21219, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 357, + "name": "Captain Ancestor Orc (Lv1-??) INCONCLUSIVE", + "enemy_id": [ "0x015839" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 21219, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 21225, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 358, + "name": "Hellhound (Lv95+)", + "enemy_id": [ "0x010202" ], + "min_level": 95, + "mdlType": 0, + "items": [ + [ + 21261, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 359, + "name": "Blaze Wolf (Lv1-??) INCONCLUSIVE", + "enemy_id": [ "0x010212" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 21263, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 360, + "name": "White Griffin (Lv1-79)", + "enemy_id": [ "0x015320", "0x070620" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 15935, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 15967, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 361, + "name": "White Griffin (Lv80+)", + "enemy_id": [ "0x015320", "0x070620" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 15967, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 15935, + 1, + 3, + 0, + false, + 0.7 + ], + [ + 17930, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 362, + "name": "Legion (Lv1-??) INCONCLUSIVE", + "mdlType": 0, + "ignore": true, + "items": [ + + ] + }, + { + "id": 363, + "name": "Legion Leader (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 364, + "name": "Gigant Machina Trinity (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 365, + "name": "Blaze Goblins (Lv1-??)", + "enemy_id": [ "0x011170", "0x011171", "0x011172", "0x011173" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 21263, + 1, + 2, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 366, + "name": "Blaze Grigori (Lv1-??)", + "enemy_id": [ "0x015930" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 21263, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 367, + "name": "Blaze Chimera (Lv1-??) INCONCLUSIVE", + "enemy_id": [ "0x015204" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 21264, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 368, + "name": "Grand Ent (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 369, + "name": "Banded Defender (Lv1+)", + "enemy_id": [ "0x011014" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7828, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 9361, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 370, + "name": "Mist Wyrm (Lv1-79)", + "mdlType": 0, + "enemy_id": [ "0x015711" ], + "min_level": 1, + "max_level": 79, + "items": [ + [ + 7922, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7802, + 1, + 4, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 371, + "name": "Mist Wyrm (Lv80+)", + "enemy_id": [ "0x015711" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7802, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7922, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17879, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 372, + "name": "Silver Roar (Lv1-45)", + "enemy_id": [ "0x015505" ], + "min_level": 1, + "max_level": 45, + "mdlType": 0, + "items": [ + [ + 7759, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7782, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 373, + "name": "Silver Roar (Lv46+)", + "enemy_id": [ "0x015505" ], + "min_level": 46, + "mdlType": 0, + "items": [ + [ + 7782, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7759, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7744, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 374, + "name": "Empress Ghost (Lv1-55)", + "enemy_id": [ "0x015605" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7904, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7908, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7808, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 375, + "name": "Empress Ghost (Lv56+)", + "enemy_id": [ "0x015605" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 7808, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7904, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7908, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7949, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 376, + "name": "Frost Skeleton Brute (Lv1-79)", + "enemy_id": [ "0x010321" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8012, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7850, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 377, + "name": "Frost Skeleton Brute (Lv80+)", + "enemy_id": [ "0x010321" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7850, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8012, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 378, + "name": "Lux Skeleton Brute (Lv1-79)", + "enemy_id": [ "0x010323" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8014, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7850, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 379, + "name": "Lux Skeleton Brute (Lv80+)", + "enemy_id": [ "0x010323" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7850, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8014, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 380, + "name": "Death Knight (Lv1-55)", + "enemy_id": [ "0x010310" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7947, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 8021, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 381, + "name": "Death Knight (Lv56-79)", + "enemy_id": [ "0x010310" ], + "min_level": 56, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8021, + 1, + 1, + 0, + false, + 0.5 + ], + [ + 7947, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7909, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 382, + "name": "Death Knight (Lv80+)", + "enemy_id": [ "0x010310" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7909, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7947, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8021, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 17878, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 383, + "name": "Mogok (Lv1+)", + "enemy_id": [ "0x015840" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7761, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8020, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 384, + "name": "Cursed Dragon (Lv.69)", + "enemy_id": [ "0x015706" ], + "min_level": 69, + "mdlType": 0, + "items": [ + [ + 11781, + 1, + 10, + 0, + false, + 100 + ], + [ + 11782, + 1, + 6, + 0, + false, + 100 + ], + [ + 11783, + 1, + 3, + 0, + false, + 100 + ] + ] + }, + { + "id": 385, + "name": "Golgorran (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 386, + "name": "Alchemized Harpy (Lv1-49)", + "enemy_id": [ "0x010606" ], + "min_level": 1, + "max_level": 49, + "mdlType": 0, + "items": [ + [ + 8027, + 1, + 2, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 387, + "name": "Alchemized Harpy (Lv50-79)", + "enemy_id": [ "0x010606" ], + "min_level": 50, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8027, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7822, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 388, + "name": "Alchemized Harpy (Lv80+)", + "enemy_id": [ "0x010606" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7822, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 8027, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17928, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 390, + "name": "Goliath (Lv1-55)", + "enemy_id": [ "0x015102" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 8031, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 8030, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 8031, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 391, + "name": "Goliath (Lv56+)", + "enemy_id": [ "0x015102" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 8031, + 1, + 1, + 0, + false, + 0.1 + ], + [ + 8031, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8030, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7871, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 392, + "name": "Mergan Element Archer (Lv1+)", + "mdlType": 0, + "enemy_id": [ "0x011027" ], + "min_level": 1, + "items": [ + [ + 8033, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 8023, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 393, + "name": "Mergan Fighter (Lv1+)", + "enemy_id": [ "0x011020" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 8023, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 8033, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 394, + "name": "Mergan Seeker (Lv1+)", + "enemy_id": [ "0x011021" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 8033, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 8023, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 395, + "name": "Mergan Hunter (Lv1+)", + "enemy_id": [ "0x011022" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 8023, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 8033, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 396, + "name": "Mergan Mage (Lv1+)", + "enemy_id": [ "0x011025" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 8033, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 8023, + 1, + 1, + 0, + false, + 0.2 + ] + ] + }, + { + "id": 397, + "name": "Mergan Warrior (Lv1+)", + "enemy_id": [ "0x011026" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 8023, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 8033, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 398, + "name": "Alchemy Eye (Lv1-55)", + "enemy_id": [ "0x015420" ], + "min_level": 1, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7863, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7871, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 399, + "name": "Alchemy Eye (Lv56+)", + "enemy_id": [ "0x015420" ], + "min_level": 56, + "mdlType": 0, + "items": [ + [ + 7871, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7863, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7731, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 400, + "name": "Skeleton Lord (Lv1-??) INCONCLUSIVE", + "ignore" : true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 401, + "name": "Iris (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 402, + "name": "Skull Lord (Lv1-44)", + "enemy_id": [ "0x010313" ], + "min_level": 1, + "max_level": 44, + "mdlType": 0, + "items": [ + [ + 7850, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 403, + "name": "Skull Lord (Lv45-79)", + "enemy_id": [ "0x010313" ], + "min_level": 45, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7850, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7772, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 404, + "name": "Skull Lord (Lv80+)", + "enemy_id": [ "0x010313" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7772, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7850, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17878, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 405, + "name": "Mergan Defender (Lv1+)", + "enemy_id": [ "0x011024" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 8023, + 1, + 1, + 0, + false, + 0.2 + ], + [ + 8033, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 406, + "name": "Mergan Healer (Lv1+)", + "enemy_id": [ "0x011023" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 8033, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 8023, + 1, + 1, + 0, + false, + 0.1 + ], + [ + 34, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 407, + "name": "Diamantes (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 408, + "name": "White Dragon (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 409, + "name": "Spirit Dragon Willmia (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 410, + "name": "Black Knight (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 411, + "name": "Baphomet (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 412, + "name": "Mudman (Lv1-24)", + "enemy_id": [ "0x010509" ], + "min_level": 1, + "max_level": 24, + "mdlType": 0, + "items": [ + [ + 7843, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7845, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 413, + "name": "Mudman (Lv25-79)", + "enemy_id": [ "0x010509" ], + "min_level": 25, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7845, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7843, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7849, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 414, + "name": "Mudman (Lv80+)", + "enemy_id": [ "0x010509" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7849, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7845, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7843, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 415, + "name": "Skeleton Brute (Lv1-79)", + "enemy_id": [ "0x010307" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 416, + "name": "Skeleton Brute (Lv80+)", + "enemy_id": [ "0x010307" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 417, + "name": "Frost Corpse (Lv1-79)", + "enemy_id": [ "0x010511", "0x010512" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7741, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 8024, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 418, + "name": "Frost Corpse (Lv80+)", + "enemy_id": [ "0x010511", "0x010512" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8024, + 1, + 2, + 0, + false, + 0.6 + ], + [ + 7741, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 419, + "name": "Flame Corpse (Lv1+)", + "enemy_id": [ "0x010513", "0x010517" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 17873, + 1, + 2, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 420, + "name": "Angules (Lv1-40)", + "enemy_id": [ "0x015708", "0x070920" ], + "min_level": 1, + "max_level": 40, + "mdlType": 0, + "items": [ + [ + 7928, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7756, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7927, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 421, + "name": "Angules (Lv41-55)", + "enemy_id": [ "0x015708", "0x070920" ], + "min_level": 41, + "max_level": 55, + "mdlType": 0, + "items": [ + [ + 7927, + 1, + 1, + 0, + false, + 0.2 + ], + [ + 7927, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7928, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7756, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 422, + "name": "Angules (Lv56-79)", + "enemy_id": [ "0x015708", "0x070920" ], + "min_level": 56, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7756, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7928, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7927, + 1, + 2, + 0, + false, + 0.3 + ], + [ + 7927, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7754, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 423, + "name": "Angules (Lv80+)", + "enemy_id": [ "0x015708", "0x070920" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7754, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7756, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7928, + 1, + 4, + 0, + false, + 0.9 + ], + [ + 7927, + 1, + 3, + 0, + false, + 0.4 + ], + [ + 7927, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17879, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 424, + "name": "Aqua Jelly (Lv1-79)", + "enemy_id": [ "0x010904" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 425, + "name": "Aqua Jelly (Lv80+)", + "enemy_id": [ "0x010904" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 426, + "name": "Acid Blob (Lv1-79)", + "enemy_id": [ "0x010911" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7734, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 427, + "name": "Acid Blob (Lv80+)", + "enemy_id": [ "0x010911" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7734, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7837, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 428, + "name": "Stone Blob (Lv95+)", + "enemy_id": [ "0x010915" ], + "min_level": 95, + "mdlType": 0, + "items": [ + [ + 17880, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 429, + "name": "Giant Unspeakable Meat (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 430, + "name": "Ooze (Lv1-79)", + "enemy_id": [ "0x010902" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7840, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 431, + "name": "Ooze (Lv80+)", + "enemy_id": [ "0x010902" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7840, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 17880, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 432, + "name": "Lapis Eye (Lv1-44)", + "enemy_id": [ "0x015412" ], + "min_level": 1, + "max_level": 44, + "mdlType": 0, + "items": [ + [ + 7893, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7902, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 433, + "name": "Lapis Eye (Lv45+)", + "enemy_id": [ "0x015412" ], + "min_level": 45, + "mdlType": 0, + "items": [ + [ + 7730, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 7893, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7902, + 1, + 2, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 434, + "name": "Emerald Eye (Lv1-44)", + "enemy_id": [ "0x015413" ], + "min_level": 1, + "max_level": 44, + "mdlType": 0, + "items": [ + [ + 7895, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7898, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 435, + "name": "Emerald Eye (Lv45+)", + "enemy_id": [ "0x015413" ], + "min_level": 45, + "mdlType": 0, + "items": [ + [ + 7898, + 1, + 1, + 0, + false, + 0.7 + ], + [ + 7895, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7730, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 436, + "name": "Megado Guard (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 437, + "name": "Fodden (Lv90+)", + "enemy_id": [ "0x015042" ], + "min_level": 90, + "mdlType": 0, + "items": [ + [ + 21226, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 438, + "name": "Armored Insect (Lv1-??) INCONCLUSIVE", + "enemy_id": [ "0x010820", "0x0010821" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7836, + 1, + 3, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 439, + "name": "Frost Skeleton (Lv1-79)", + "enemy_id": [ "0x010315" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 8012, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 440, + "name": "Frost Skeleton (Lv80+)", + "enemy_id": [ "0x010315" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 8012, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7803, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 441, + "name": "Ghoul (Lv1-45)", + "enemy_id": [ "0x015503", "0x070200" ], + "min_level": 1, + "max_level": 45, + "mdlType": 0, + "items": [ + [ + 7759, + 1, + 2, + 0, + false, + 0.8 + ], + [ + 7751, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 442, + "name": "Ghoul (Lv46+)", + "enemy_id": [ "0x015503", "0x070200" ], + "min_level": 46, + "mdlType": 0, + "items": [ + [ + 7751, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7759, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7744, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 443, + "name": "Frost Machina (Lv1+)", + "enemy_id": [ "0x015851" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11778, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 444, + "name": "Merman (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 445, + "name": "Poison Merman (Lv1-??) INCONCLUSIVE", + "mdlType": 0, + "ignore": true, + "items": [ + + ] + }, + { + "id": 446, + "name": "Freezing Slime (Lv1-79)", + "enemy_id": [ "0x010906" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 447, + "name": "Freezing Slime (Lv80+)", + "enemy_id": [ "0x010906" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 448, + "name": "Oil Jelly (Lv1-79)", + "enemy_id": [ "0x010920" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 449, + "name": "Oil Jelly (Lv80+)", + "enemy_id": [ "0x010920" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 450, + "name": "Photon Slime (Lv1-79)", + "enemy_id": [ "0x010908" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 451, + "name": "Photon Slime (Lv80+)", + "enemy_id": [ "0x010908" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 452, + "name": "Medusa (Lv1-79)", + "enemy_id": [ "0x015610" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7832, + 1, + 1, + 0, + false, + 0.3 + ], + [ + 13224, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 453, + "name": "Medusa (Lv80-84)", + "enemy_id": [ "0x015610" ], + "min_level": 80, + "max_level": 84, + "mdlType": 0, + "items": [ + [ + 13224, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 7832, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 17878, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 454, + "name": "Medusa (Lv85+)", + "enemy_id": [ "0x015610" ], + "min_level": 85, + "mdlType": 0, + "items": [ + [ + 17878, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 13224, + 1, + 1, + 0, + false, + 1 + ], + [ + 7832, + 1, + 1, + 0, + false, + 0.9 + ], + [ + 17929, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 455, + "name": "Lux Corpses (Lv80+)", + "enemy_id": [ "0x010515", "0x010519" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 17873, + 1, + 2, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 456, + "name": "Valefar (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 457, + "name": "Burned Ent (Lv95+)", + "enemy_id": [ "0x015033" ], + "min_level": 95, + "mdlType": 0, + "items": [ + [ + 21265, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 458, + "name": "Pyro Slime (Lv1-79)", + "enemy_id": [ "0x010905" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 459, + "name": "Pyro Slime (Lv80+)", + "enemy_id": [ "0x010905" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 460, + "name": "Maneater (Lv1+)", + "enemy_id": [ "0x011400", "0x011401" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 10133, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 461, + "name": "Evil Eye (Lv90+)", + "enemy_id": [ "0x011410", "0x011411" ], + "min_level": 90, + "mdlType": 0, + "items": [ + [ + 17863, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 462, + "name": "Tentacle Evil Eye (Lv1-??) INCONCLUSIVE", + "mdlType": 0, + "ignore": true, + "items": [ + + ] + }, + { + "id": 463, + "name": "Flame Skeleton Cyclops (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 464, + "name": "Ushumgal (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 465, + "name": "Ifrit (Lv95+)", + "enemy_id": [ "0x020803", "0x020804" ], + "min_level": 95, + "mdlType": 0, + "items": [ + [ + 21231, + 1, + 1, + 0, + false, + 1 + ] + ] + }, + { + "id": 466, + "name": "War Master (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 467, + "name": "Beast Master (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 468, + "name": "The Evil Dragon (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 469, + "name": "Arisen of the Black Dragon (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 470, + "name": "Black Dragon (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 471, + "name": "Former Commander Leo (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 472, + "name": "Ukobach (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 473, + "name": "Frost Skeleton Cyclops (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 474, + "name": "Frost Machina Trinity (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 475, + "name": "Bolt Skeleton Cyclops (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 476, + "name": "Bolt Machina (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 477, + "name": "Bolt Machina Trinity (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 478, + "name": "Dark Skeleton Brute (Lv1-79)", + "enemy_id": [ "0x010324" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8015, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7850, + 1, + 1, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 479, + "name": "Dark Skeleton Brute (Lv80+)", + "enemy_id": [ "0x010324" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7850, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8015, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 480, + "name": "Lux Skeleton (Lv1-79)", + "enemy_id": [ "0x010317" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 8014, + 1, + 1, + 0, + false, + 0.4 + ], + [ + 7803, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 481, + "name": "Lux Skeleton (Lv80+)", + "enemy_id": [ "0x010317" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7803, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 8014, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 17873, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 482, + "name": "Dim Slime (Lv1-79)", + "enemy_id": [ "0x010909" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 3, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 483, + "name": "Dim Slime (Lv80+)", + "enemy_id": [ "0x010909" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 17880, + 1, + 2, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 484, + "name": "Phindymian Fighter (Lv1+)", + "enemy_id": [ "0x011060" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 485, + "name": "Phindymian Seeker (Lv1+)", + "enemy_id": [ "0x011061" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 486, + "name": "Phindymian Hunter (Lv1+)", + "enemy_id": [ "0x011062" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 487, + "name": "Phindymian Preist (Lv1+)", + "enemy_id": [ "0x011063" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.2 + ] + ] + }, + { + "id": 488, + "name": "Phindymian Defender (Lv1+)", + "enemy_id": [ "0x011064" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 489, + "name": "Phindymian Sorcerer (Lv1+)", + "enemy_id": [ "0x011065" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 4, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 490, + "name": "Phindymian Warrior (Lv1+)", + "enemy_id": [ "0x011066" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 491, + "name": "Phindymian Element Archer (Lv1+)", + "enemy_id": [ "0x011067" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 11408, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 492, + "name": "Scourge (Lv1+)", + "enemy_id": [ "0x020601", "0x071310" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 15996, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 16013, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 493, + "name": "Severely Infected Scourge (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 494, + "name": "Red Zuhl (Lv1-??) INCONCLUSIVE", + "enemy_id": [ "0x020404" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 10986, + 1, + 1, + 0, + false, + 0.1 + ] + ] + }, + { + "id": 495, + "name": "Shadow Grigori (Lv1-??) INCONCLUSIVE", + "mdlType": 0, + "ignore": true, + "items": [ + + ] + }, + { + "id": 496, + "name": "Shadow Master (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 497, + "name": "Gorgon (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 498, + "name": "Rage Ghost (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 499, + "name": "Grudge Ghost (Lv1-??)", + "enemy_id": [ "0x015622" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 21222, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 500, + "name": "Misery Ghost (Lv85+)", + "enemy_id": [ "0x015621" ], + "min_level": 85, + "mdlType": 0, + "items": [ + [ + 17929, + 1, + 2, + 0, + false, + 0.9 + ] + ] + }, + { + "id": 501, + "name": "White Tarasque (Lv90+)", + "enemy_id": [ "0x015723" ], + "min_level": 90, + "mdlType": 0, + "items": [ + [ + 17877, + 1, + 2, + 0, + false, + 1 + ] + ] + }, + { + "id": 502, + "name": "Vile Eye (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + }, + { + "id": 503, + "name": "Volt Eye (Lv1+)", + "enemy_id": [ "0x015406" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 21267, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 504, + "name": "Crystal Eye (Lv1-44)", + "enemy_id": [ "0x015410" ], + "min_level": 1, + "max_level": 44, + "mdlType": 0, + "items": [ + [ + 7901, + 1, + 2, + 0, + false, + 0.7 + ], + [ + 7894, + 1, + 1, + 0, + false, + 0.3 + ] + ] + }, + { + "id": 505, + "name": "Crystal Eye (Lv45+)", + "enemy_id": [ "0x015410" ], + "min_level": 45, + "mdlType": 0, + "items": [ + [ + 7894, + 1, + 1, + 0, + false, + 0.6 + ], + [ + 7901, + 1, + 3, + 0, + false, + 0.9 + ], + [ + 7730, + 1, + 1, + 0, + false, + 0.4 + ] + ] + }, + { + "id": 506, + "name": "Glutton Ooze (Lv1-79)", + "enemy_id": [ "0x010903" ], + "min_level": 1, + "max_level": 79, + "mdlType": 0, + "items": [ + [ + 7840, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 7837, + 1, + 1, + 0, + false, + 0.7 + ] + ] + }, + { + "id": 507, + "name": "Glutton Ooze (Lv80+)", + "enemy_id": [ "0x010903" ], + "min_level": 80, + "mdlType": 0, + "items": [ + [ + 7837, + 1, + 2, + 0, + false, + 0.9 + ], + [ + 7840, + 1, + 1, + 0, + false, + 0.8 + ], + [ + 17880, + 1, + 1, + 0, + false, + 0.6 + ] + ] + }, + { + "id": 508, + "name": "Scarlet Fighter (Lv1-??)", + "enemy_id": [ "0x011070" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7827, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 509, + "name": "Scarlet Seeker", + "enemy_id": [ "0x011071" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7827, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 510, + "name": "Scarlet Hunter", + "enemy_id": [ "0x011072" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7827, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 511, + "name": "Scarlet Healer", + "enemy_id": [ "0x011073" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7827, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 512, + "name": "Scarlet Defender", + "enemy_id": [ "0x011074" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7827, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 513, + "name": "Scarlet Mage", + "enemy_id": [ "0x011075" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7827, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 514, + "name": "Scarlet Warrior", + "enemy_id": [ "0x011076" ], + "min_level": 1, + "mdlType": 0, + "items": [ + [ + 7827, + 1, + 1, + 0, + false, + 0.8 + ] + ] + }, + { + "id": 515, + "name": "Black Knight Phantom (Lv1-??) INCONCLUSIVE", + "ignore": true, + "mdlType": 0, + "items": [ + + ] + } + ] +} \ No newline at end of file diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json index fd015b05f..f64168a23 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20055001 Sphinx one.json @@ -50,7 +50,7 @@ }, "enemies": [ { - "comment": "Young Dread Ape", + "comment": "Sphinx", "enemy_id": "0x015302", "named_enemy_params_id": 459, "level": 18, diff --git a/Arrowgene.Ddon.Shared/Model/EnemySpawnAsset.cs b/Arrowgene.Ddon.Shared/Model/EnemySpawnAsset.cs index 6e776982e..a0ac471cb 100644 --- a/Arrowgene.Ddon.Shared/Model/EnemySpawnAsset.cs +++ b/Arrowgene.Ddon.Shared/Model/EnemySpawnAsset.cs @@ -11,5 +11,4 @@ public EnemySpawnAsset() public Dictionary<(StageId, byte), List> Enemies { get; set; } public Dictionary DropsTables { get; set; } - -} \ No newline at end of file +} From 72c83c85b5d5192f3ca7701f1512668c0f9a45c2 Mon Sep 17 00:00:00 2001 From: Asterit Date: Tue, 3 Sep 2024 10:03:15 +0100 Subject: [PATCH 020/116] Implemented review changes and updated sql to not delete but update entries --- .../Script/migration_quest_variant.sql | 16 +++++---- .../Characters/QuestManager.cs | 36 ++++++++++++------- .../Handler/QuestGetRewardBoxListHandler.cs | 3 +- .../Party/PartyQuestState.cs | 2 +- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 3 +- Arrowgene.Ddon.Shared/Asset/QuestAsset.cs | 2 +- .../AssetReader/QuestAssetDeserializer.cs | 5 ++- .../Entity/Structure/CDataRewardBoxRecord.cs | 1 - 8 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql index 635954858..33d9a9d5d 100644 --- a/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql +++ b/Arrowgene.Ddon.Database/Files/Database/Script/migration_quest_variant.sql @@ -1,13 +1,15 @@ -DELETE FROM "ddon_quest_progress" WHERE "quest_id" = 2005501; - -DELETE FROM "ddon_priority_quests" WHERE "quest_id" = 2005501; - -DELETE FROM "ddon_reward_box" WHERE "quest_id" = 2005501; - -DELETE FROM "ddon_completed_quests" WHERE "quest_id" = 2005501; ALTER TABLE "ddon_quest_progress" ADD COLUMN "variant_quest_id" INTEGER NOT NULL DEFAULT 0; ALTER TABLE "ddon_reward_box" ADD COLUMN "variant_quest_id" INTEGER NOT NULL DEFAULT 0; + +UPDATE "ddon_quest_progress" SET "quest_id" = 20055001 WHERE "quest_id" = 2005501; +UPDATE "ddon_quest_progress" SET "variant_quest_id" = 102030 WHERE "quest_id" = 20055001; + +UPDATE "ddon_priority_quests" SET "quest_id" = 20055001 WHERE "quest_id" = 2005501; + +UPDATE "ddon_reward_box" SET "quest_id" = 20055001 WHERE "quest_id" = 2005501; + +UPDATE "ddon_completed_quests" SET "quest_id" = 20055001 WHERE "quest_id" = 2005501; diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 7508a5303..d7b349f0a 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -45,14 +45,9 @@ public static void LoadQuests(AssetRepository assetRepository) // Separate all variant quests to its own dictionary for separate handling. // This also ensures these quests are not in gQuests before processing. - if (questAsset.VariantId is not null && !gQuests.ContainsKey(questAsset.QuestId)) + if (questAsset.VariantId != 0 && !gQuests.ContainsKey(questAsset.QuestId)) { - if (questAsset.VariantId == 0) - { - Logger.Error($"Variant Id being 0 is a reserved value, please reassign to another number."); - continue; - } - + Quest alternateQuest = GenericQuest.FromAsset(questAsset); alternateQuest.IsVariantQuest = true; alternateQuest.VariantId = (uint)questAsset.VariantId; @@ -143,20 +138,37 @@ public static List> GetQuestsByType(QuestType type) return results; } - public static uint GetRandomVariantQuest(QuestId baseQuest) + public static uint GetRandomVariantId(QuestId baseQuest) { // Get random index value to choose a quest version. - int randomIndex = new Random().Next(variantQuests[baseQuest].Count); + int randomIndex = Random.Shared.Next(variantQuests[baseQuest].Count); + uint variantId = variantQuests[baseQuest].ElementAt(randomIndex).Key; + return variantId; } - public static Quest GetQuest(QuestId questId, uint? variantId = null) + public static Quest GetRewardQuest(QuestId questId, uint variantId) + { + // Mostly for reward calls. If somehow the variantId is not valid, + // return the first quest found with the questId within VariantQuests + Quest quest = GetQuest(questId, variantId); + + if(quest is null) + { + // Check for variant quest + return variantQuests[questId].First().Value; + } + + return quest; + } + + public static Quest GetQuest(QuestId questId, uint variantId = 0) { // If a variant is specified, return the variant quest. - if (variantId is not null) + if (variantId != 0) { - return variantQuests[questId][(uint)variantId]; + return variantQuests[questId][variantId]; } if (!gQuests.ContainsKey(questId)) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxListHandler.cs index 1fa3c90cd..15bd3145c 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxListHandler.cs @@ -37,8 +37,7 @@ public override S2CQuestGetRewardBoxListRes Handle(GameClient client, C2SQuestGe { ListNo = listNo, QuestId = (uint)boxReward.QuestId, - RewardItemList = Quest.AsCDataRewardBoxItems(boxReward), - VariantId = boxReward.VariantId + RewardItemList = Quest.AsCDataRewardBoxItems(boxReward) }); listNo += 1; diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 0339cb82d..63613f31d 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -284,7 +284,7 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted) // If the quest we are trying to add is a variant quest, then roll and get a random version. if (VariantQuests.Contains(questId)) { - quest = QuestManager.GetQuest(questId, QuestManager.GetRandomVariantQuest(questId)); + quest = QuestManager.GetQuest(questId, QuestManager.GetRandomVariantId(questId)); } else { diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 7850ce392..3554cf73f 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -485,8 +485,7 @@ public static List AsCDataRewardBoxItems(QuestBoxRewards rew { List results = new List(); - var quest = rewards.VariantId == 0 ? QuestManager.GetQuest(rewards.QuestId) - : QuestManager.GetQuest(rewards.QuestId, rewards.VariantId); + Quest quest = QuestManager.GetRewardQuest(rewards.QuestId, rewards.VariantId); foreach (var reward in quest.SelectableRewards) { diff --git a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs index 18b835d50..28c7acbf1 100644 --- a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs +++ b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs @@ -35,7 +35,7 @@ public class QuestAssetData public List QuestLayoutFlags { get; set; } public List QuestLayoutSetInfoFlags { get; set; } public Dictionary EnemyGroups { get; set; } - public uint ? VariantId { get; set; } + public uint VariantId { get; set; } public QuestAssetData() { diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 7d1b7f0c2..53c517602 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -94,10 +94,13 @@ private bool ParseQuest(QuestAssetData assetData, JsonElement jQuest) } // For the purpose of setting up alternate quests. - assetData.VariantId = null; + if (jQuest.TryGetProperty("variant_id", out JsonElement AltQuestId)) { assetData.VariantId = AltQuestId.GetUInt32(); + } else + { + assetData.VariantId = 0; } assetData.NextQuestId = 0; diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardBoxRecord.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardBoxRecord.cs index 53955403f..55cd8d116 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardBoxRecord.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardBoxRecord.cs @@ -13,7 +13,6 @@ public CDataRewardBoxRecord() public UInt32 ListNo { get; set; } public UInt32 QuestId { get; set; } - public uint VariantId { get; set; } public List RewardItemList { get; set; } public class Serializer : EntitySerializer From eb1b6bf8a25c4cfd7af869d1e6a8f18518b676c4 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Tue, 3 Sep 2024 18:33:49 -0700 Subject: [PATCH 021/116] Add quest ClearCount tracking. --- Arrowgene.Ddon.Database/IDatabase.cs | 7 +++++ .../Sql/Core/DdonSqlCompletedQuests.cs | 27 +++++++++++++++++++ .../Party/PartyQuestState.cs | 7 ++++- .../Database/DatabaseMigratorTest.cs | 1 + 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.Database/IDatabase.cs b/Arrowgene.Ddon.Database/IDatabase.cs index 98dbe0742..9c54501a1 100644 --- a/Arrowgene.Ddon.Database/IDatabase.cs +++ b/Arrowgene.Ddon.Database/IDatabase.cs @@ -424,6 +424,13 @@ bool InsertIfNotExistCompletedQuest( QuestType questType ); + bool ReplaceCompletedQuest( + uint characterCommonId, + QuestId questId, + QuestType questType, + uint count = 1 + ); + // Quest Progress bool InsertQuestProgress( uint characterCommonId, diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlCompletedQuests.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlCompletedQuests.cs index de8ab34d6..5a9802955 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlCompletedQuests.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlCompletedQuests.cs @@ -21,6 +21,7 @@ public abstract partial class DdonSqlDb : SqlDb GetCompletedQuestsByType(uint characterCommonId, QuestType questType) { @@ -103,6 +104,32 @@ public bool InsertIfNotExistCompletedQuest(TCon connection, uint characterCommon AddParameter(command, "clear_count", 1); }) == 1; } + + public bool ReplaceCompletedQuest(uint characterCommonId, QuestId questId, QuestType questType, uint count = 1) + { + using TCon connection = OpenNewConnection(); + return ReplaceCompletedQuest(connection, characterCommonId, questId, questType, count); + } + + public bool ReplaceCompletedQuest(TCon connection, uint characterCommonId, QuestId questId, QuestType questType, uint count = 1) + { + if (!InsertIfNotExistCompletedQuest(connection, characterCommonId, questId, questType)) + { + return UpdateCompletedQuest(connection, characterCommonId, questId, questType, count); + } + return true; + } + + private bool UpdateCompletedQuest(TCon connection, uint characterCommonId, QuestId questId, QuestType questType, uint count = 1) + { + return ExecuteNonQuery(connection, SqlUpdateCompletedQuestId, command => + { + AddParameter(command, "character_common_id", characterCommonId); + AddParameter(command, "quest_id", (uint)questId); + AddParameter(command, "quest_type", (uint)questType); + AddParameter(command, "clear_count", count); + }) == 1; + } } } diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 2217a67e0..ec07be789 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -459,8 +459,13 @@ public bool CompletePartyQuestProgress(DdonGameServer server, PartyGroup party, QuestType = quest.QuestType, ClearCount = 1, }); + server.Database.InsertIfNotExistCompletedQuest(memberClient.Character.CommonId, quest.QuestId, quest.QuestType); + } + else + { + uint clearCount = ++memberClient.Character.CompletedQuests[quest.QuestId].ClearCount; + server.Database.ReplaceCompletedQuest(memberClient.Character.CommonId, quest.QuestId, quest.QuestType, clearCount); } - server.Database.InsertIfNotExistCompletedQuest(memberClient.Character.CommonId, quest.QuestId, quest.QuestType); } // Remove the quest data from the party object diff --git a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs index cd73b4637..0889ed87b 100644 --- a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs +++ b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs @@ -270,6 +270,7 @@ public void Execute(DbConnection conn, string sql) {} public bool ReplaceShortcut(uint characterId, CDataShortCut shortcut, DbConnection? connectionIn = null) { return true; } public bool ReplaceStorageItem(uint characterId, StorageType storageType, ushort slotNo, uint itemNum, Item item, DbConnection? connectionIn = null) { return true; } public bool ReplaceWalletPoint(uint characterId, CDataWalletPoint walletPoint) { return true; } + public bool ReplaceCompletedQuest(uint characterCommonId, QuestId questId, QuestType questType, uint count) { return true; } public Account SelectAccountById(int accountId) { return new Account(); } public Account SelectAccountByLoginToken(string loginToken) { return new Account(); } public Account SelectAccountByName(string accountName) { return new Account(); } From bfeba3efac9a487912874d1c3ffc2bc04d23ca9d Mon Sep 17 00:00:00 2001 From: Asterit Date: Wed, 4 Sep 2024 11:54:48 +0100 Subject: [PATCH 022/116] Slight refactor and addition of comments --- .../InstanceQuestDropManager.cs | 21 ++++++++++++++---- .../Handler/InstanceEnemyKillHandler.cs | 3 +++ .../Handler/InstanceGetDropItemHandler.cs | 22 ++++++++----------- .../Handler/InstanceGetDropItemListHandler.cs | 12 +++------- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs index caf4dd5a6..bfa28aed1 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs @@ -29,8 +29,7 @@ private List InstanceAssets(List original private uint GetDropId(CDataStageLayoutId layoutId, uint setId) { - return layoutId.GroupId + (setId * 2) + - (layoutId.StageId + layoutId.GroupId + layoutId.LayerNo); + return layoutId.StageId + layoutId.GroupId + setId; } public bool IsQuestDrop(CDataStageLayoutId layoutId, uint setId) @@ -45,13 +44,27 @@ public bool IsQuestDrop(CDataStageLayoutId layoutId, uint setId) } return false; } - public List FetchEnemyLoot(CDataStageLayoutId layoutId, uint setId) + + //public List FetchEnemyLoot(CDataStageLayoutId layoutId, uint setId) + //{ + // // Do a last backup check before fetching loot in case IsQuestDrop somehow wasn't called befoer this. + // if (LastDropIdQuery == 0) + // { + // bool check = IsQuestDrop(layoutId, setId); + + // if (!check) return null; + // } + + // return FetchEnemyLoot(); + //} + + public List FetchEnemyLoot() { if (QuestEnemyDropsTable.ContainsKey(LastDropIdQuery)) { return QuestEnemyDropsTable[LastDropIdQuery]; - } + return new List(); } diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 402d2fe9a..f53c793ed 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -54,8 +54,11 @@ public override void Handle(GameClient client, StructurePacket instancedGatheringItems = partyMemberClient.InstanceQuestDropManager.RollEnemyLoot(enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId); + // If the roll was unlucky, there is a chance that no bag will show. if (instancedGatheringItems.Count > 0) { partyMemberClient.Send(new S2CInstancePopDropItemNtc() diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs index b94867818..ee0df1f59 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs @@ -19,21 +19,17 @@ public InstanceGetDropItemHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { - List items; - if (client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId)) - { - items = client.InstanceQuestDropManager.FetchEnemyLoot(packet.Structure.LayoutId, packet.Structure.SetId); - } - else - { - items = client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); - } + // This call is for when an item is claimed from a bag. This also needs to drops stored from the enemy. + List items = client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId) ? + client.InstanceQuestDropManager.FetchEnemyLoot() : client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); + S2CInstanceGetDropItemRes res = new() + { + LayoutId = packet.Structure.LayoutId, + SetId = packet.Structure.SetId, + GatheringItemGetRequestList = packet.Structure.GatheringItemGetRequestList + }; - S2CInstanceGetDropItemRes res = new S2CInstanceGetDropItemRes(); - res.LayoutId = packet.Structure.LayoutId; - res.SetId = packet.Structure.SetId; - res.GatheringItemGetRequestList = packet.Structure.GatheringItemGetRequestList; client.Send(res); S2CItemUpdateCharacterItemNtc ntc = new S2CItemUpdateCharacterItemNtc() diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs index 971fc4ee3..4c3619842 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs @@ -22,15 +22,9 @@ public InstanceGetDropItemListHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { - List items; - - if (client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId)) - { - items = client.InstanceQuestDropManager.FetchEnemyLoot(packet.Structure.LayoutId, packet.Structure.SetId); - } else - { - items = client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); - } + // This call is for when a bag is opened. Get the correct drops stored from the kill handler. + List items = client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId) ? + client.InstanceQuestDropManager.FetchEnemyLoot() : client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); client.Send(new S2CInstanceGetDropItemListRes() { From 217c33015044ce0706bd5f6deb1e1b292aa0b1f4 Mon Sep 17 00:00:00 2001 From: Asterit <32305591+Asterit@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:31:56 +0100 Subject: [PATCH 023/116] Delete Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj removed junk file --- .../Arrowgene - Backup.Ddon.GameServer.csproj | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj diff --git a/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj b/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj deleted file mode 100644 index c1272072c..000000000 --- a/Arrowgene.Ddon.GameServer/Arrowgene - Backup.Ddon.GameServer.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - net6.0 - Arrowgene.Ddon.GameServer - Dragons Dogma Online - Game Server - DDON Team - Ddon.GameServer - $(Version) - Copyright © 2019-2022 DDON Team - 10 - - - True - - - True - - - - - - From 95e6d73d9a10d2434fe3ed0252e46b1ce5e28c4f Mon Sep 17 00:00:00 2001 From: Asterit <32305591+Asterit@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:32:21 +0100 Subject: [PATCH 024/116] Delete Arrowgene.Ddon.Database/Properties/launchSettings.json deleted junk file --- Arrowgene.Ddon.Database/Properties/launchSettings.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 Arrowgene.Ddon.Database/Properties/launchSettings.json diff --git a/Arrowgene.Ddon.Database/Properties/launchSettings.json b/Arrowgene.Ddon.Database/Properties/launchSettings.json deleted file mode 100644 index 79ad96102..000000000 --- a/Arrowgene.Ddon.Database/Properties/launchSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "profiles": { - "Arrowgene.Ddon.Database": { - "commandName": "Project" - } - } -} \ No newline at end of file From 66c6340a49011846e1fbd77ef0368a8e5b5a5b32 Mon Sep 17 00:00:00 2001 From: Asterit Date: Wed, 4 Sep 2024 15:32:19 +0100 Subject: [PATCH 025/116] fixed enemy kill drop condition --- Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 5a5fc815e..2b0747d76 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -59,10 +59,9 @@ public override void Handle(GameClient client, StructurePacket instancedGatheringItems = partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, packet.Structure.SetId); From ff32c7679f672a5c0c572a2a61ea6d33d070af0f Mon Sep 17 00:00:00 2001 From: Asterit Date: Wed, 4 Sep 2024 15:51:25 +0100 Subject: [PATCH 026/116] Simplified control flow within EnemyKillHandler. --- .../InstanceQuestDropManager.cs | 2 +- .../Handler/InstanceEnemyKillHandler.cs | 76 +++++++------------ 2 files changed, 28 insertions(+), 50 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs index bfa28aed1..a9eb8dca5 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs @@ -68,7 +68,7 @@ public List FetchEnemyLoot() return new List(); } - public List RollEnemyLoot(InstancedEnemy enemy, CDataStageLayoutId layoutId, uint setId) + public List GenerateEnemyLoot(InstancedEnemy enemy, CDataStageLayoutId layoutId, uint setId) { uint dropEntryId = GetDropId(layoutId, setId); diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 2b0747d76..6e1763c06 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -56,51 +56,28 @@ public override void Handle(GameClient client, StructurePacket instancedGatheringItems = IsQuestControlled ? + partyMemberClient.InstanceQuestDropManager.GenerateEnemyLoot(enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId) : + partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, packet.Structure.SetId); - foreach (var partyMemberClient in client.Party.Clients) + // If the roll was unlucky, there is a chance that no bag will show. + if (instancedGatheringItems.Count > 0) { - // Get the possible loot. This also stores the items entry within the QuestEnemyDropsTable for further reference. - // All references are cleared when in a safe zone. - List instancedGatheringItems = partyMemberClient.InstanceQuestDropManager.RollEnemyLoot(enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId); - - // If the roll was unlucky, there is a chance that no bag will show. - if (instancedGatheringItems.Count > 0) + partyMemberClient.Send(new S2CInstancePopDropItemNtc() { - partyMemberClient.Send(new S2CInstancePopDropItemNtc() - { - LayoutId = packet.Structure.LayoutId, - SetId = packet.Structure.SetId, - MdlType = enemyKilled.DropsTable.MdlType, - PosX = packet.Structure.DropPosX, - PosY = packet.Structure.DropPosY, - PosZ = packet.Structure.DropPosZ - }); - } - } - } - else - { - foreach (var partyMemberClient in client.Party.Clients) - { - List instancedGatheringItems = partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, packet.Structure.SetId); - if (instancedGatheringItems.Count > 0) - { - partyMemberClient.Send(new S2CInstancePopDropItemNtc() - { - LayoutId = packet.Structure.LayoutId, - SetId = packet.Structure.SetId, - MdlType = enemyKilled.DropsTable.MdlType, - PosX = packet.Structure.DropPosX, - PosY = packet.Structure.DropPosY, - PosZ = packet.Structure.DropPosZ - }); - } + LayoutId = packet.Structure.LayoutId, + SetId = packet.Structure.SetId, + MdlType = enemyKilled.DropsTable.MdlType, + PosX = packet.Structure.DropPosX, + PosY = packet.Structure.DropPosY, + PosZ = packet.Structure.DropPosZ + }); } } @@ -138,12 +115,13 @@ public override void Handle(GameClient client, StructurePacket 0) + if (enemyKilled.BloodOrbs > 0) { // Drop BO CDataWalletPoint boWallet = memberClient.Character.WalletPointList.Where(wp => wp.Type == WalletType.BloodOrbs).Single(); @@ -192,7 +170,7 @@ public override void Handle(GameClient client, StructurePacket 0) + if (enemyKilled.HighOrbs > 0) { // Drop HO CDataWalletPoint hoWallet = memberClient.Character.WalletPointList.Where(wp => wp.Type == WalletType.HighOrbs).Single(); @@ -200,7 +178,7 @@ public override void Handle(GameClient client, StructurePacket Date: Thu, 5 Sep 2024 21:55:45 +0100 Subject: [PATCH 027/116] Cleaned up unneeded logger call displaying variant quest rolls. --- Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 581b2792d..62e794c9c 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -278,8 +278,7 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted) // If we are adding a new variant quest, then log the variant id for further reference if (quest.IsVariantQuest) { - Logger.Debug($"Adding to ActiveVariantQuest -> quest: {quest.QuestId} -> variantId: {quest.VariantId}"); - ActiveVariantQuests.Add(quest.QuestId, (uint)quest.VariantId); + ActiveVariantQuests.Add(quest.QuestId, quest.VariantId); } AddNewQuest(quest, step, questStarted); From 2fa791464ca6a09486f33ddd667bf67e84e0a187 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 5 Sep 2024 21:09:04 -0400 Subject: [PATCH 028/116] Add World Quests for Zandora Wastelands Added new quests and fixed a couple of existing quests. Authored-by: Dixdros --- .../Files/Assets/quests/q20080001.json | 2 +- .../Files/Assets/quests/q20085005.json | 2 +- .../Files/Assets/quests/q20090000.json | 119 +++++++++++++++++ .../Files/Assets/quests/q20090001.json | 95 ++++++++++++++ .../Files/Assets/quests/q20095000.json | 105 +++++++++++++++ .../Files/Assets/quests/q20095001.json | 95 ++++++++++++++ .../Files/Assets/quests/q20095002.json | 101 ++++++++++++++ .../Files/Assets/quests/q20095003.json | 90 +++++++++++++ .../Files/Assets/quests/q20095004.json | 105 +++++++++++++++ .../Files/Assets/quests/q20095007.json | 104 +++++++++++++++ .../Files/Assets/quests/q20095008.json | 95 ++++++++++++++ .../Files/Assets/quests/q20095010.json | 123 ++++++++++++++++++ 12 files changed, 1034 insertions(+), 2 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20090000.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20090001.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20095000.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20095001.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20095002.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20095003.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20095004.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20095007.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20095008.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20095010.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20080001.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20080001.json index b7acd31f5..bffdf1ffd 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20080001.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20080001.json @@ -48,7 +48,7 @@ }, "enemies": [ { - "enemy_id": "0x0115605", + "enemy_id": "0x015605", "level": 48, "exp": 14000, "is_boss": true diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20085005.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20085005.json index a627cac7b..b1160c1c0 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20085005.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20085005.json @@ -88,7 +88,7 @@ "groups": [0] }, { - "type": "NewTalkToNpc", + "type": "TalkToNpc", "stage_id": { "id": 61, "group_id": 1, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20090000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20090000.json new file mode 100644 index 000000000..2fd9548ea --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20090000.json @@ -0,0 +1,119 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Dead City's Chronicles", + "quest_id": 20090000, + "base_level": 49, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "exp", + "amount": 1880 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1340 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 210 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7911, + "num": 2 + }, + { + "item_id": 7554, + "num": 3 + }, + { + "item_id": 9363, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 164, + "group_id": 4 + }, + "enemies": [ + { + "enemy_id": "0x015203", + "level": 50, + "exp": 20000, + "is_boss": true + }, + { + "enemy_id": "0x010607", + "level": 48, + "exp": 1200, + "is_boss": false + }, + { + "enemy_id": "0x011160", + "level": 49, + "exp": 1300, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1075, "comment": "Spawns Lise0 NPC"} + ], + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Lise0", + "message_id": 11372 + }, + { + "type": "CollectItem", + "announce_type": "Update", + "stage_id": { + "id": 77, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1754, "comment": "Spawns Glowing Item"} + ] + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Lise0", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20090001.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20090001.json new file mode 100644 index 000000000..bad9a82d5 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20090001.json @@ -0,0 +1,95 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The One Called ‘Knight-Devourer", + "quest_id": 20090001, + "base_level": 53, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "exp", + "amount": 1920 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1450 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 250 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7744, + "num": 1 + }, + { + "item_id": 9403, + "num": 3 + }, + { + "item_id": 41, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 157, + "group_id": 7 + }, + "enemies": [ + { + "enemy_id": "0x015500", + "level": 53, + "exp": 23000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1076, "comment": "Spawns WhiteKnight1 NPC"} + ], + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "WhiteKnight1", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "WhiteKnight1", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095000.json new file mode 100644 index 000000000..93826085a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095000.json @@ -0,0 +1,105 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Fiery Titan", + "quest_id": 20095000, + "base_level": 49, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1610 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 250 + }, + { + "type": "exp", + "amount": 2260 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 9315, + "num": 1 + }, + { + "item_id": 9318, + "num": 1 + }, + { + "item_id": 9321, + "num": 1 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 34, + "num": 9, + "chance": 1.0 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 77, + "group_id": 24 + }, + "enemies": [ + { + "enemy_id": "0x015102", + "level": 49, + "exp": 14000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1655, "comment": "Spawns Christoph NPC"} + ], + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Christoph", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Christoph", + "message_id": 11835 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095001.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095001.json new file mode 100644 index 000000000..f16ea966d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095001.json @@ -0,0 +1,95 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "A Thirst for Knowledge", + "quest_id": 20095001, + "base_level": 49, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "exp", + "amount": 2450 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1610 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 280 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 9159, + "num": 1 + }, + { + "item_id": 9401, + "num": 3 + }, + { + "item_id": 7554, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 77, + "group_id": 17 + }, + "enemies": [ + { + "enemy_id": "0x015202", + "level": 50, + "exp": 20000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1656, "comment": "Spawns Marquis NPC"} + ], + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Marquis", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Marquis", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095002.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095002.json new file mode 100644 index 000000000..2b8ee3f0f --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095002.json @@ -0,0 +1,101 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Dark Shadow Covering the Sky", + "quest_id": 20095002, + "base_level": 49, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "exp", + "amount": 2640 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1610 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 300 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7742, + "num": 1 + }, + { + "item_id": 9402, + "num": 3 + }, + { + "item_id": 9363, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 1, + "group_id": 434 + }, + "enemies": [ + { + "enemy_id": "0x015303", + "level": 50, + "exp": 20000, + "is_boss": true + }, + { + "enemy_id": "0x010600", + "level": 48, + "exp": 1200, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1657, "comment": "Spawns 禁域守の神官 NPC"} + ], + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "4701", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "4701", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095003.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095003.json new file mode 100644 index 000000000..4252f642a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095003.json @@ -0,0 +1,90 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Sinister Beasts", + "quest_id": 20095003, + "base_level": 49, + "minimum_item_rank": 0, + "discoverable": false, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1050 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 210 + }, + { + "type": "exp", + "amount": 1320 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7781, + "num": 2 + }, + { + "item_id": 9363, + "num": 3 + }, + { + "item_id": 41, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 164, + "group_id": 0 + }, + "starting_index": 6, + "enemies": [ + { + "enemy_id": "0x015200", + "level": 50, + "exp": 20000, + "is_boss": true + }, + { + "enemy_id": "0x011130", + "level": 48, + "exp": 1300, + "is_boss": false + }, + { + "enemy_id": "0x011130", + "level": 48, + "exp": 1300, + "is_boss": false + }, + { + "enemy_id": "0x011130", + "level": 48, + "exp": 1300, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [0] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095004.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095004.json new file mode 100644 index 000000000..482cce5c5 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095004.json @@ -0,0 +1,105 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Forgotten Guardian", + "quest_id": 20095004, + "base_level": 47, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1550 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 240 + }, + { + "type": "exp", + "amount": 2170 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 9059, + "num": 2 + }, + { + "item_id": 7925, + "num": 1 + }, + { + "item_id": 7554, + "num": 3 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 34, + "num": 9, + "chance": 1.0 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 164, + "group_id": 4 + }, + "enemies": [ + { + "enemy_id": "0x015102", + "level": 48, + "exp": 13000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1617, "comment": "Spawns Sorcerer0 NPC"} + ], + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Sorcerer0", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Sorcerer0", + "message_id": 11835 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095007.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095007.json new file mode 100644 index 000000000..123e70208 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095007.json @@ -0,0 +1,104 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Accursed Wanderer", + "quest_id": 20095007, + "base_level": 56, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "exp", + "amount": 5590 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 3750 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 330 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7959, + "num": 1 + }, + { + "item_id": 7745, + "num": 1 + }, + { + "item_id": 7744, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 1, + "group_id": 391 + }, + "enemies": [ + { + "enemy_id": "0x015040", + "level": 54, + "exp": 25000, + "is_boss": true + }, + { + "enemy_id": "0x011132", + "level": 50, + "exp": 1400, + "is_boss": false + }, + { + "enemy_id": "0x011132", + "level": 50, + "exp": 1400, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 55, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Bayard", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 55, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Bayard", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095008.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095008.json new file mode 100644 index 000000000..b0644268f --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095008.json @@ -0,0 +1,95 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Accursed Deserted Home", + "quest_id": 20095008, + "base_level": 48, + "minimum_item_rank": 0, + "discoverable": false, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1050 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 210 + }, + { + "type": "exp", + "amount": 1320 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7753, + "num": 2 + }, + { + "item_id": 41, + "num": 1 + }, + { + "item_id": 7554, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 41, + "group_id": 0 + }, + "enemies": [ + { + "enemy_id": "0x010206", + "level": 47, + "exp": 1250, + "is_boss": false + }, + { + "enemy_id": "0x010206", + "level": 47, + "exp": 1250, + "is_boss": false + }, + { + "enemy_id": "0x010206", + "level": 47, + "exp": 1350, + "is_boss": false + }, + { + "enemy_id": "0x011160", + "level": 47, + "exp": 1350, + "is_boss": false + }, + { + "enemy_id": "0x011160", + "level": 47, + "exp": 1350, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [0] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095010.json new file mode 100644 index 000000000..c36969abe --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095010.json @@ -0,0 +1,123 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "A Clash With the Orc Elite Corps", + "quest_id": 20095010, + "base_level": 48, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "ZandoraWastelands", + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1320 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 210 + }, + { + "type": "exp", + "amount": 1320 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 9324, + "num": 1 + }, + { + "item_id": 9327, + "num": 1 + }, + { + "item_id": 41, + "num": 1 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 34, + "num": 9, + "chance": 1.0 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 1, + "group_id": 407 + }, + "enemies": [ + { + "enemy_id": "0x015802", + "level": 48, + "exp": 1400, + "is_boss": false + }, + { + "enemy_id": "0x015802", + "level": 48, + "exp": 1400, + "is_boss": false + }, + { + "enemy_id": "0x015810", + "level": 48, + "exp": 1400, + "is_boss": false + }, + { + "enemy_id": "0x015820", + "level": 48, + "exp": 1500, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1618, "comment": "Spawns ArisenCorpsRegimentalSoldier1 NPC"} + ], + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "ArisenCorpsRegimentalSoldier1", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "ArisenCorpsRegimentalSoldier1", + "message_id": 11835 + } + ] +} From 26321f67771add5b88be35729559014fb13fd78b Mon Sep 17 00:00:00 2001 From: Ryan Yappert <122490202+RyanYappert@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:19:59 -0700 Subject: [PATCH 029/116] Update generic_quest_state_machine.md --- docs/quests/generic_quest_state_machine.md | 56 +++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/quests/generic_quest_state_machine.md b/docs/quests/generic_quest_state_machine.md index 3068bbdc2..823128183 100644 --- a/docs/quests/generic_quest_state_machine.md +++ b/docs/quests/generic_quest_state_machine.md @@ -53,6 +53,8 @@ The JSON for the state machine is split into 4 major parts. "base_level": int, "minimum_item_rank": int, "discoverable": bool, + "area_id": string, + "news_image": int, "rewards": [], "enemy_groups": [], "blocks": [] @@ -144,6 +146,8 @@ Each file should start with the following generic format. "base_level": int, "minimum_item_rank": int, "discoverable": bool, + "area_id": string, + "news_image": int, "rewards": [], "enemy_groups": [], "blocks": [] @@ -163,12 +167,60 @@ First fill in the common items using the values we collected from the wiki. Ques "base_level": 12, "minimum_item_rank": 0, "discoverable": false, + "area_id": "HidellPlains", + "news_image": 5, "rewards": [], "enemy_groups": [], "blocks": [] } ``` +The `area_id` field controls what category the quest appears under for Lestania News. In this case, the quest appears in Hidell Plains. You can also use "None" to hide the quest from the news. Similarly, if the quest does appear in Lestania News, you should provide a news image index. If none is provided, a default is used by the client, but the default only includes Hidell Plains images, so you should manually choose one. + +| Min ID | Max ID | Image Description | +|:---------:|:-----:|:-----------------------------------------------------------| +| 0 | | Locked, used for quests with hidden info. +| 1 | 16 | Hidell Plains + White Dragon Temple +| 21 | 29 | Breya Coast +| 41 | 57 | Betland Plains + Temple of Purification +| 61 | 75 | Dowe Valley + Dreed Castle +| 81 | 96 | Volden Mines + Tunnels +| 101 | 114 | Mysree Forest + Glowworm Cave +| 121 | 128 | Mysree Grove +| 141 | 154 | Northern Betland Plains + Gardnox Fort +| 161 | 171 | Zandora Wasteland +| 181 | 193 | Eastern Zandora + Mergoda Security District +| 201 | 211 | Deenan Woods + Erte Deenan +| 221 | 235 | Mergoda Ruins +| 241 | 249 | Bloodbane Isle +| 261 | 266 | Elan Water Grove +| 281 | 285 | Farana Plains +| 301 | 305 | Kingall Canyon +| 321 | 326 | Morrow Forest +| 330 | 345 | Acre Selund +| 347 | | Acre Selund +| 501 | 505 | Generic Wells +| 511 | 516 | Generic Cellars +| 521 | 527 | Generic Waterways +| 531 | 538 | Generic Caves +| 541 | 548 | Generic Catacombs +| 551 | 555 | Generic Arks +| 561 | 567 | Generic Terraces +| 571 | 573 | Generic Chapels +| 581 | 583 | Generic Caves (S2) +| 591 | 593 | Generic Waterways (S2) +| 603 | | Tower of Ivanos +| 611 | 615 | Generic Ruins +| 621 | 623 | Generic Wells (S2) +| 641 | 643 | Generic Catacombs (S2) +| 650 | | Hollow of Beginnings +| 660 | 662 | Generic Infected Forests +| 670 | 679 | Acre Selund Interiors +| 681 | | Firefall Lava Caves +| 683 | | Generic Cave (S3) +| 900 | 902 | Text Banners: Disaster/Rare Species/Bounty + + #### Adding Rewards Next we can define the rewards. The rewards have a variable format depending on the `type` field. @@ -309,6 +361,8 @@ Finally, your file should look like below. Save the file, reload the server and "base_level": 12, "minimum_item_rank": 0, "discoverable": false, + "area_id": "HidellPlains", + "news_image": 5, "rewards": [ { "type": "wallet", @@ -787,4 +841,4 @@ See the [quest command reference document](quest_command_reference.md) for more - [Lestania (stage0100)](events/st0100.md) - [White Dragon Temple (stage0200)](events/st0200.md) -- [The Audience Chamber (stage0201)](events/st0201.md) \ No newline at end of file +- [The Audience Chamber (stage0201)](events/st0201.md) From 2ec9a34fb7f31d82d43e8b0d3bb502d7fca25e54 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Mon, 2 Sep 2024 15:16:59 -0700 Subject: [PATCH 030/116] Cleanup stamp timing and logic with help from Ryohei. --- .../Handler/StampBonusReceiveDailyHandler.cs | 5 +- Arrowgene.Ddon.GameServer/StampManager.cs | 76 ++++++++++++++----- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveDailyHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveDailyHandler.cs index 7399a5991..7be7846bf 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveDailyHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveDailyHandler.cs @@ -23,11 +23,8 @@ public StampBonusReceiveDailyHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, IPacket packet) { //Update stamp bonus data. - client.Character.StampBonus.LastStamp = DateTime.Now; - client.Character.StampBonus.ConsecutiveStamp += 1; - client.Character.StampBonus.TotalStamp += 1; - _gameServer.Database.UpdateCharacterStampData(client.Character.CharacterId, client.Character.StampBonus); + _gameServer.StampManager.UpdateStamp(client.Character); var dailyStamps = _gameServer.StampManager.GetDailyStampAssets().Where(x => x.StampNum == client.Character.StampBonus.ConsecutiveStamp); diff --git a/Arrowgene.Ddon.GameServer/StampManager.cs b/Arrowgene.Ddon.GameServer/StampManager.cs index 00837d280..8a6b9db0d 100644 --- a/Arrowgene.Ddon.GameServer/StampManager.cs +++ b/Arrowgene.Ddon.GameServer/StampManager.cs @@ -1,3 +1,4 @@ +using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; @@ -14,10 +15,18 @@ public StampManager(DdonGameServer server) Server = server; } - public static int MAX_DAILY_STAMP = 8; - public static int TOTAL_STAMP_SLOT = 10; - public static int DAILY_STAMP_GRACE_PERIOD = 1; //Login every X days or lose your consecutive stamp. - public static int STAMP_RESET_HOUR = 20; //5AM JST = 8PM UTC = 1 PM PST = 4 PM EST + public static readonly int MAX_DAILY_STAMP = 8; + private static readonly int TOTAL_STAMP_SLOT = 10; + + /// + /// Grace period *after* their their regular daily reset. + /// + private static readonly TimeSpan DAILY_STAMP_GRACE_PERIOD = new TimeSpan(1, 0, 0, 0); + + /// + /// JST hour of the reset. + /// + private static readonly int STAMP_RESET_HOUR = 5; private DdonGameServer Server; @@ -28,17 +37,17 @@ public List GetDailyStampAssets() public List GetTotalStampAssets() { - return Server.AssetRepository.StampBonusAsset.Where(x => x.StampNum > 8).OrderBy(x => x.StampNum).ToList(); ; + return Server.AssetRepository.StampBonusAsset.Where(x => x.StampNum > MAX_DAILY_STAMP).OrderBy(x => x.StampNum).ToList(); ; } public List GetTotalStampAssetsWindow(ushort totalStamps) { var totalList = GetTotalStampAssets(); - //Return only the ten stamps surrounding the current position. + // Return only the ten stamps surrounding the current position. if (totalList.Count > TOTAL_STAMP_SLOT) { - //Find position of the last stamp checkpoint we've passed. + // Find position of the last stamp checkpoint we've passed. int position = totalList.FindLastIndex(x => x.StampNum <= totalStamps); if (position == -1) return totalList.Take(TOTAL_STAMP_SLOT).ToList(); @@ -67,8 +76,8 @@ public void HandleStampBonuses(GameClient client, IEnumerable= (DAILY_STAMP_GRACE_PERIOD + 1); + return SpanSinceLastStampReset(stampData.LastStamp) > DAILY_STAMP_GRACE_PERIOD; } - static private DateTime GetLastStampReset() + static public bool CanStamp(DateTime lastStamp) { - DateTime lastReset = DateTime.Today.AddHours(STAMP_RESET_HOUR); - if (lastReset > DateTime.Now) - { - lastReset = lastReset.AddDays(-1); - } - return lastReset; + return SpanSinceLastStampReset(lastStamp).TotalSeconds < 0; } - static public bool CanStamp(DateTime lastStamp) + public void UpdateStamp(Character character) { - return lastStamp < GetLastStampReset(); + DateTime utcNow = DateTime.UtcNow; + TimeZoneInfo jstZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time"); + DateTime jstNow = TimeZoneInfo.ConvertTimeFromUtc(utcNow, jstZone); + + character.StampBonus.LastStamp = jstNow; + character.StampBonus.ConsecutiveStamp += 1; + character.StampBonus.TotalStamp += 1; + + Server.Database.UpdateCharacterStampData(character.CharacterId, character.StampBonus); + } + + /// + /// Calculate the timespan from/until the reset. + /// If negative, their stamp reset hasn't happened yet. + /// If positive, their stamp reset may have happened. + /// + static private TimeSpan SpanSinceLastStampReset(DateTime lastStamp) + { + DateTime utcNow = DateTime.UtcNow; + TimeZoneInfo jstZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time"); + DateTime jstNow = TimeZoneInfo.ConvertTimeFromUtc(utcNow, jstZone); + + DateTime lastStampReset; // Always in JST + if (lastStamp.Hour < STAMP_RESET_HOUR) + { + lastStampReset = lastStamp.Date.AddHours(STAMP_RESET_HOUR); + } + else + { + lastStampReset = lastStamp.Date.AddDays(1).AddHours(STAMP_RESET_HOUR); + } + + return jstNow - lastStampReset; } } } From ac68937b429dd55791d85e2fafb9bf321f91289f Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Mon, 2 Sep 2024 23:56:47 -0700 Subject: [PATCH 031/116] Cleaned up stamp packets and logic now that I understand Ghidra. The check logic is still terrible. --- .../Handler/StampBonusCheckHandler.cs | 52 ++++++++++++------- .../Handler/StampBonusGetListHandler.cs | 38 +++++++++----- Arrowgene.Ddon.GameServer/StampManager.cs | 13 +++-- Arrowgene.Ddon.Shared/Csv/StampBonusCsv.cs | 1 + .../Entity/EntitySerializer.cs | 10 ++++ .../PacketStructure/C2SStampBonusCheckReq.cs | 23 ++++++++ .../C2SStampBonusGetListReq.cs | 24 +++++++++ .../C2SStampBonusRecieveDailyReq.cs | 30 +++++++++++ .../C2SStampBonusRecieveTotalReq.cs | 23 ++++++++ .../PacketStructure/S2CStampBonusCheckRes.cs | 36 +++++-------- .../S2CStampBonusGetListRes.cs | 46 +++------------- .../S2CStampBonusRecieveDailyRes.cs | 27 ++++++++++ .../S2CStampBonusRecieveTotalRes.cs | 27 ++++++++++ .../Entity/Structure/CDataStampBonus.cs | 18 ++++--- .../Entity/Structure/CDataStampBonusAsset.cs | 7 --- .../Entity/Structure/CDataStampBonusDaily.cs | 37 +++++++++++++ .../Entity/Structure/CDataStampBonusTotal.cs | 33 ++++++++++++ .../Entity/Structure/CDataStampCheck.cs | 27 ++++++++++ 18 files changed, 360 insertions(+), 112 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusCheckReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusGetListReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveDailyReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveTotalReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveDailyRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveTotalRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusDaily.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusTotal.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataStampCheck.cs diff --git a/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs index 00a922c40..028b7a924 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs @@ -1,8 +1,10 @@ using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { @@ -21,36 +23,48 @@ public StampBonusCheckHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, IPacket packet) { - bool canTotal = _gameServer.StampManager.CanTotalStamp(client.Character.StampBonus); bool canDaily = _gameServer.StampManager.CanDailyStamp(client.Character.StampBonus); + bool canTotal = _gameServer.StampManager.CanTotalStamp(client.Character.StampBonus); - if (canDaily) + var stampBonusList = _gameServer.StampManager.GetDailyStampAssets().Select(x => x.StampBonus.First()).ToList(); + + if (canTotal) { - client.Send(new S2CStampBonusCheckRes() + var res = new S2CStampBonusCheckRes() + { + IsRecieveBonusDaily = 0, + IsRecieveBonusTotal = 0, + }; + res.StampCheck.Add(new CDataStampCheck() + { + Unk0 = 1, + Unk1 = 0, + }); + client.Send(res); + } + else if (canDaily) + { + var res = new S2CStampBonusCheckRes() + { + IsRecieveBonusDaily = 1, + IsRecieveBonusTotal = 0, + }; + res.StampCheck.Add(new CDataStampCheck() { - SuppressTotal = !canTotal, - SuppressDaily = !canDaily, Unk0 = 1, Unk1 = 0, - Unk2 = 1, - Unk3 = 77, - Unk4 = 257 }); + client.Send(res); } - else + else { - //For whatever reason, suppresses the icon over Ophelia's head. - client.Send(new S2CStampBonusCheckRes() + client.Send(new S2CStampBonusCheckRes() { - Unk0 = 0, - Unk1 = ushort.MaxValue, - SuppressDaily = true, - SuppressTotal = true, - Unk2 = 1, - Unk3 = 77, - Unk4 = 257 + IsRecieveBonusDaily = byte.MaxValue, + IsRecieveBonusTotal = byte.MaxValue, }); - } + } + } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/StampBonusGetListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StampBonusGetListHandler.cs index 88c069431..542e0d1d9 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StampBonusGetListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StampBonusGetListHandler.cs @@ -1,10 +1,12 @@ using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; 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.Diagnostics; +using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { @@ -28,37 +30,47 @@ public override void Handle(GameClient client, IPacket packet) //Character.StampBonus is incremented in RECIEVE_DAILY, so the daily list portions need an offset and the total list portions don't. ushort totalStampNum = (ushort)(client.Character.StampBonus.TotalStamp); - var res = new S2CStampBonusGetListRes() + + //var res = new S2CStampBonusGetListRes() + //{ + // StampBonusDaily = _gameServer.StampManager.GetDailyStampAssets(), + // StampBonusTotal = _gameServer.StampManager.GetTotalStampAssetsWindow(totalStampNum), + // TotalStampNum = totalStampNum, + // Unk1 = 1, + // Unk2 = 1, + // Unk3 = 77, + // Unk5 = 50, + // Unk6 = 0 + //}; + + var res = new S2CStampBonusGetListRes(); + res.TotalStamp.TotalStampNum = totalStampNum; + res.TotalStamp.AssetList = _gameServer.StampManager.GetTotalStampAssetsWindow(totalStampNum); + res.DailyStamp.Add(new CDataStampBonusDaily() { - StampBonusDaily = _gameServer.StampManager.GetDailyStampAssets(), - StampBonusTotal = _gameServer.StampManager.GetTotalStampAssetsWindow(totalStampNum), - TotalStampNum = totalStampNum, - Unk1 = 1, - Unk2 = 1, - Unk3 = 77, - Unk5 = 50, - Unk6 = 0 - }; + Unk0 = 1, + Unk1 = 77, + AssetList = _gameServer.StampManager.GetDailyStampAssets() + }); //If you missed a day, reset stamp to 0 (to be incremented up in RECIEVE_DAILY) if (StampManager.CanResetConsecutiveStamp(client.Character.StampBonus)) client.Character.StampBonus.ConsecutiveStamp = 0; //If you've finished the 8-stamp sequence and you're going to roll over to 9, reset stamp to 0. if (client.Character.StampBonus.ConsecutiveStamp >= StampManager.MAX_DAILY_STAMP) client.Character.StampBonus.ConsecutiveStamp = 0; - foreach (var item in res.StampBonusDaily) + foreach (var item in res.DailyStamp.First().AssetList) { if (item.StampNum < client.Character.StampBonus.ConsecutiveStamp+1) item.RecieveState = (byte)StampRecieveState.Claimed; else if (item.StampNum == client.Character.StampBonus.ConsecutiveStamp+1) item.RecieveState = (byte)StampRecieveState.ToBeClaimed; else item.RecieveState = (byte)StampRecieveState.Unearned; } - foreach (var item in res.StampBonusTotal) + foreach (var item in res.TotalStamp.AssetList) { if (item.StampNum < totalStampNum) item.RecieveState = (byte)StampRecieveState.Claimed; else if (item.StampNum == totalStampNum) item.RecieveState = (byte)StampRecieveState.ToBeClaimed; else item.RecieveState = (byte)StampRecieveState.Unearned; } - res.TotalStampNum = totalStampNum; client.Send(res); } diff --git a/Arrowgene.Ddon.GameServer/StampManager.cs b/Arrowgene.Ddon.GameServer/StampManager.cs index 8a6b9db0d..0ab3b3066 100644 --- a/Arrowgene.Ddon.GameServer/StampManager.cs +++ b/Arrowgene.Ddon.GameServer/StampManager.cs @@ -1,7 +1,8 @@ -using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -10,6 +11,8 @@ namespace Arrowgene.Ddon.GameServer { public class StampManager { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(StampManager)); + public StampManager(DdonGameServer server) { Server = server; @@ -102,12 +105,12 @@ public void HandleStampBonuses(GameClient client, IEnumerable DAILY_STAMP_GRACE_PERIOD; + return RelativeSpanToReset(stampData.LastStamp) > DAILY_STAMP_GRACE_PERIOD; } static public bool CanStamp(DateTime lastStamp) { - return SpanSinceLastStampReset(lastStamp).TotalSeconds < 0; + return RelativeSpanToReset(lastStamp).TotalSeconds > 0; } public void UpdateStamp(Character character) @@ -126,9 +129,9 @@ public void UpdateStamp(Character character) /// /// Calculate the timespan from/until the reset. /// If negative, their stamp reset hasn't happened yet. - /// If positive, their stamp reset may have happened. + /// If positive, their stamp reset has happened. /// - static private TimeSpan SpanSinceLastStampReset(DateTime lastStamp) + static private TimeSpan RelativeSpanToReset(DateTime lastStamp) { DateTime utcNow = DateTime.UtcNow; TimeZoneInfo jstZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time"); diff --git a/Arrowgene.Ddon.Shared/Csv/StampBonusCsv.cs b/Arrowgene.Ddon.Shared/Csv/StampBonusCsv.cs index 47fe08e44..6a017250d 100644 --- a/Arrowgene.Ddon.Shared/Csv/StampBonusCsv.cs +++ b/Arrowgene.Ddon.Shared/Csv/StampBonusCsv.cs @@ -21,6 +21,7 @@ protected override CDataStampBonusAsset CreateInstance(string[] properties) obj.StampBonus.Add(new CDataStampBonus() { + Unk0 = 1, BonusType = type, BonusValue = value }); diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index 4a728fe04..d1f0a091f 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -303,6 +303,9 @@ static EntitySerializer() Create(new CDataStampBonus.Serializer()); Create(new CDataStampBonusAsset.Serializer()); + Create(new CDataStampBonusDaily.Serializer()); + Create(new CDataStampBonusTotal.Serializer()); + Create(new CDataStampCheck.Serializer()); Create(new CDataTimeLimitedQuestOrderList.Serializer()); Create(new CDataTraningRoomEnemyHeader.Serializer()); @@ -649,6 +652,11 @@ static EntitySerializer() Create(new C2SSetCommunicationShortcutReq.Serializer()); Create(new C2SStageAreaChangeReq.Serializer()); Create(new C2SStageGetStageListReq.Serializer()); + Create(new C2SStampBonusCheckReq.Serializer()); + Create(new C2SStampBonusGetListReq.Serializer()); + Create(new C2SStampBonusRecieveDailyReq.Serializer()); + Create(new C2SStampBonusRecieveTotalReq.Serializer()); + Create(new C2SInstanceTraningRoomGetEnemyListReq.Serializer()); Create(new C2SInstanceTraningRoomSetEnemyReq.Serializer()); Create(new C2SWarpAreaWarpReq.Serializer()); @@ -1147,6 +1155,8 @@ static EntitySerializer() Create(new S2CStampBonusGetListRes.Serializer()); Create(new S2CStampBonusCheckRes.Serializer()); + Create(new S2CStampBonusRecieveDailyRes.Serializer()); + Create(new S2CStampBonusRecieveTotalRes.Serializer()); Create(new S2CBinarySaveSetCharacterBinSaveDataRes.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusCheckReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusCheckReq.cs new file mode 100644 index 000000000..9d86f3df4 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusCheckReq.cs @@ -0,0 +1,23 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SStampBonusCheckReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_STAMP_BONUS_CHECK_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SStampBonusCheckReq obj) + { + } + + public override C2SStampBonusCheckReq Read(IBuffer buffer) + { + C2SStampBonusCheckReq obj = new C2SStampBonusCheckReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusGetListReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusGetListReq.cs new file mode 100644 index 000000000..11d136ee1 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusGetListReq.cs @@ -0,0 +1,24 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SStampBonusGetListReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_STAMP_BONUS_GET_LIST_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SStampBonusGetListReq obj) + { + } + + public override C2SStampBonusGetListReq Read(IBuffer buffer) + { + C2SStampBonusGetListReq obj = new C2SStampBonusGetListReq(); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveDailyReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveDailyReq.cs new file mode 100644 index 000000000..60c1a91eb --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveDailyReq.cs @@ -0,0 +1,30 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + internal class C2SStampBonusRecieveDailyReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_STAMP_BONUS_RECIEVE_DAILY_REQ; + + public uint Unk0 { get; set; } + public uint Unk1 { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SStampBonusRecieveDailyReq obj) + { + WriteUInt32(buffer, obj.Unk0); + WriteUInt32(buffer, obj.Unk1); + } + + public override C2SStampBonusRecieveDailyReq Read(IBuffer buffer) + { + C2SStampBonusRecieveDailyReq obj = new C2SStampBonusRecieveDailyReq(); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveTotalReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveTotalReq.cs new file mode 100644 index 000000000..0b1507cc0 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveTotalReq.cs @@ -0,0 +1,23 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SStampBonusRecieveTotalReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_STAMP_BONUS_RECIEVE_TOTAL_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SStampBonusRecieveTotalReq obj) + { + } + + public override C2SStampBonusRecieveTotalReq Read(IBuffer buffer) + { + C2SStampBonusRecieveTotalReq obj = new C2SStampBonusRecieveTotalReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusCheckRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusCheckRes.cs index 016319ae0..5fab6e48e 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusCheckRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusCheckRes.cs @@ -1,5 +1,7 @@ using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; namespace Arrowgene.Ddon.Shared.Entity.PacketStructure { @@ -7,45 +9,33 @@ public class S2CStampBonusCheckRes : ServerResponse { public S2CStampBonusCheckRes() { + StampCheck = new List(); } public override PacketId Id => PacketId.S2C_STAMP_BONUS_CHECK_RES; - //The structure of this is probably mostly wrong and totally differs from what's in the debug symbols. - //Take these data types with a grain of salt. - public uint Unk0 { get; set; } - public ushort Unk1 { get; set; } - public bool SuppressDaily { get; set; } - public byte Unk2 { get; set; } - public uint Unk3 { get; set; } - public bool SuppressTotal { get; set; } - public ushort Unk4 { get; set; } + public List StampCheck { get; set; } + public byte IsRecieveBonusDaily { get; set; } // May be flip-flopped with the other byte. + public byte IsRecieveBonusTotal { get; set; } // May be flip-flopped with the other byte. + public class Serializer : PacketEntitySerializer { public override void Write(IBuffer buffer, S2CStampBonusCheckRes obj) { WriteServerResponse(buffer, obj); - WriteUInt32(buffer, obj.Unk0); - WriteUInt16(buffer, obj.Unk1); - WriteBool(buffer, obj.SuppressDaily); - WriteByte(buffer, obj.Unk2); - WriteUInt32(buffer, obj.Unk3); - WriteBool(buffer, obj.SuppressTotal); - WriteUInt16(buffer, obj.Unk4); + WriteEntityList(buffer, obj.StampCheck); + WriteByte(buffer, obj.IsRecieveBonusDaily); + WriteByte(buffer, obj.IsRecieveBonusTotal); } public override S2CStampBonusCheckRes Read(IBuffer buffer) { S2CStampBonusCheckRes obj = new S2CStampBonusCheckRes(); ReadServerResponse(buffer, obj); - obj.Unk0 = ReadUInt32(buffer); - obj.Unk1 = ReadUInt16(buffer); - obj.SuppressDaily = ReadBool(buffer); - obj.Unk2 = ReadByte(buffer); - obj.Unk3 = ReadUInt16(buffer); - obj.SuppressTotal = ReadBool(buffer); - obj.Unk4 = ReadUInt16(buffer); + obj.StampCheck = ReadEntityList(buffer); + obj.IsRecieveBonusDaily = ReadByte(buffer); + obj.IsRecieveBonusTotal = ReadByte(buffer); return obj; } diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusGetListRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusGetListRes.cs index 08ec3e4ef..b737c6e1f 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusGetListRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusGetListRes.cs @@ -11,58 +11,28 @@ public class S2CStampBonusGetListRes : ServerResponse public S2CStampBonusGetListRes() { - Unk1 = 1; - Unk2 = 1; - - Unk3 = 77; - - StampBonusDaily = new List { }; - - TotalStampNum = 252; - - StampBonusTotal = new List { }; - - Unk5 = 50; - - Unk6 = 0; + DailyStamp = new List(); + TotalStamp = new CDataStampBonusTotal(); } - public uint Unk1 { get; set; } // 1 - public uint Unk2 { get; set; } // 1 - public uint Unk3 { get; set; } // 77 - public List StampBonusDaily { get; set; } - public ushort TotalStampNum { get; set; } // 252 - public List StampBonusTotal { get; set; } - public ushort Unk5 { get; set; } // 50; - public byte Unk6 { get; set; } // 0; + public List DailyStamp { get; set; } + public CDataStampBonusTotal TotalStamp { get; set; } public class Serializer : PacketEntitySerializer { public override void Write(IBuffer buffer, S2CStampBonusGetListRes obj) { WriteServerResponse(buffer, obj); - WriteUInt32(buffer, obj.Unk1); - WriteUInt32(buffer, obj.Unk2); - WriteUInt32(buffer, obj.Unk3); - WriteEntityList(buffer, obj.StampBonusDaily); - WriteUInt16(buffer, obj.TotalStampNum); - WriteEntityList(buffer, obj.StampBonusTotal); - WriteUInt16(buffer, obj.Unk5); - WriteByte(buffer, obj.Unk6); + WriteEntityList(buffer, obj.DailyStamp); + WriteEntity(buffer, obj.TotalStamp); } public override S2CStampBonusGetListRes Read(IBuffer buffer) { S2CStampBonusGetListRes obj = new S2CStampBonusGetListRes(); ReadServerResponse(buffer, obj); - obj.Unk1 = ReadUInt32(buffer); - obj.Unk2 = ReadUInt32(buffer); - obj.Unk3 = ReadUInt32(buffer); - obj.StampBonusDaily = ReadEntityList(buffer); - obj.TotalStampNum = ReadUInt16(buffer); - obj.StampBonusTotal = ReadEntityList(buffer); - obj.Unk5 = ReadUInt16(buffer); - obj.Unk6 = ReadByte(buffer); + obj.DailyStamp = ReadEntityList(buffer); + obj.TotalStamp = ReadEntity(buffer); return obj; } } diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveDailyRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveDailyRes.cs new file mode 100644 index 000000000..eec9edbea --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveDailyRes.cs @@ -0,0 +1,27 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CStampBonusRecieveDailyRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_STAMP_BONUS_RECIEVE_DAILY_RES; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CStampBonusRecieveDailyRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CStampBonusRecieveDailyRes Read(IBuffer buffer) + { + S2CStampBonusRecieveDailyRes obj = new S2CStampBonusRecieveDailyRes(); + + ReadServerResponse(buffer, obj); + + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveTotalRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveTotalRes.cs new file mode 100644 index 000000000..6a5ecbda8 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveTotalRes.cs @@ -0,0 +1,27 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CStampBonusRecieveTotalRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_STAMP_BONUS_RECIEVE_TOTAL_RES; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CStampBonusRecieveTotalRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CStampBonusRecieveTotalRes Read(IBuffer buffer) + { + S2CStampBonusRecieveTotalRes obj = new S2CStampBonusRecieveTotalRes(); + + ReadServerResponse(buffer, obj); + + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonus.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonus.cs index f9da8a3ba..1497ea169 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonus.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonus.cs @@ -1,16 +1,18 @@ using Arrowgene.Buffers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Arrowgene.Ddon.Shared.Entity.Structure { public class CDataStampBonus { - //BonusType 1-5 map to WalletType 1-5, but the others are not included. - //Alternatively, you can use an ItemID here. + /// + /// The client uses this as some sort of offset. + /// Unk0 = 1 gets the intuitive behavior. + /// + public uint Unk0 { get; set; } = 1; + /// + /// BonusType 1-5 map to WalletType 1-5, but the others are not included. + /// Alternatively, you can use an ItemID here. + /// public uint BonusType { get; set; } public uint BonusValue { get; set; } @@ -18,6 +20,7 @@ public class Serializer : EntitySerializer { public override void Write(IBuffer buffer, CDataStampBonus obj) { + WriteUInt32(buffer, obj.Unk0); WriteUInt32(buffer, obj.BonusType); WriteUInt32(buffer, obj.BonusValue); } @@ -25,6 +28,7 @@ public override void Write(IBuffer buffer, CDataStampBonus obj) public override CDataStampBonus Read(IBuffer buffer) { CDataStampBonus obj = new CDataStampBonus(); + obj.Unk0 = ReadUInt32(buffer); obj.BonusType = ReadUInt32(buffer); obj.BonusValue = ReadUInt32(buffer); return obj; diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusAsset.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusAsset.cs index d4818a617..5143bcf31 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusAsset.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusAsset.cs @@ -1,9 +1,5 @@ using Arrowgene.Buffers; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Arrowgene.Ddon.Shared.Entity.Structure { @@ -14,7 +10,6 @@ public CDataStampBonusAsset() StampBonus = new List(); } - public uint Unk0 = 1; //Nested list index? Weird MtArray stuff. public List StampBonus { get; set; } public ushort StampNum { get; set; } public byte RecieveState { get; set; } @@ -23,7 +18,6 @@ public class Serializer : EntitySerializer { public override void Write(IBuffer buffer, CDataStampBonusAsset obj) { - WriteUInt32(buffer, obj.Unk0); WriteEntityList(buffer, obj.StampBonus); WriteUInt16(buffer, obj.StampNum); WriteByte(buffer, obj.RecieveState); @@ -32,7 +26,6 @@ public override void Write(IBuffer buffer, CDataStampBonusAsset obj) public override CDataStampBonusAsset Read(IBuffer buffer) { CDataStampBonusAsset obj = new CDataStampBonusAsset(); - obj.Unk0 = ReadUInt32(buffer); obj.StampBonus = ReadEntityList(buffer); obj.StampNum = ReadUInt16(buffer); obj.RecieveState = ReadByte(buffer); diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusDaily.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusDaily.cs new file mode 100644 index 000000000..e6009bd95 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusDaily.cs @@ -0,0 +1,37 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataStampBonusDaily + { + public CDataStampBonusDaily() + { + AssetList = new List(); + } + + public uint Unk0 { get; set; } + public uint Unk1 { get; set; } + public List AssetList { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataStampBonusDaily obj) + { + WriteUInt32(buffer, obj.Unk0); + WriteUInt32(buffer, obj.Unk1); + WriteEntityList(buffer, obj.AssetList); + } + + public override CDataStampBonusDaily Read(IBuffer buffer) + { + CDataStampBonusDaily obj = new CDataStampBonusDaily(); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadUInt32(buffer); + obj.AssetList = ReadEntityList(buffer); + + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusTotal.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusTotal.cs new file mode 100644 index 000000000..acec29b6b --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampBonusTotal.cs @@ -0,0 +1,33 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataStampBonusTotal + { + public CDataStampBonusTotal() + { + AssetList = new List(); + } + + public ushort TotalStampNum { get; set; } + public List AssetList { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataStampBonusTotal obj) + { + WriteUInt16(buffer, obj.TotalStampNum); + WriteEntityList(buffer, obj.AssetList); + } + + public override CDataStampBonusTotal Read(IBuffer buffer) + { + CDataStampBonusTotal obj = new CDataStampBonusTotal(); + obj.TotalStampNum = ReadUInt16(buffer); + obj.AssetList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampCheck.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampCheck.cs new file mode 100644 index 000000000..ec74da9e1 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataStampCheck.cs @@ -0,0 +1,27 @@ +using Arrowgene.Buffers; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataStampCheck + { + public uint Unk0 { get; set; } + public uint Unk1 { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataStampCheck obj) + { + WriteUInt32(buffer, obj.Unk0); + WriteUInt32(buffer, obj.Unk1); + } + + public override CDataStampCheck Read(IBuffer buffer) + { + CDataStampCheck obj = new CDataStampCheck(); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadUInt32(buffer); + return obj; + } + } + } +} From c44df445d1b0b1df848b34267fe791253b6cc5d5 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Tue, 3 Sep 2024 00:12:15 -0700 Subject: [PATCH 032/116] Make Item Post not combine stacks, as the text response to sending there implies. Update /giveitem to send directly to item post. --- .../Characters/ItemManager.cs | 60 +++++++++++++++++++ .../Chat/Command/Commands/GiveItemCommand.cs | 31 ++-------- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs b/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs index 1154730bd..ac87f4efe 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs @@ -297,6 +297,10 @@ public List AddItem(DdonServer server, Charac // Equipment is a special case. It can't be stacked, even on the storage box. So we limit in there too return DoAddItem(server.Database, character, destinationStorage, itemId, num, clientItemInfo.StackLimit, plusvalue, connectionIn); } + if (destinationStorage == StorageType.ItemPost) // Item Post doesn't combine stacks. + { + return DoAddItemNoStack(server.Database, character, destinationStorage, itemId, num, plusvalue, connectionIn); + } else { // Move to storage box without stack limit if it's not equipment @@ -377,6 +381,62 @@ private List DoAddItem(IDatabase database, Character char return results; } + // TODO: Maybe make this more smoothly a part of the existing DoAddItem. + private List DoAddItemNoStack(IDatabase database, Character character, StorageType destinationStorageType, uint itemId, uint num, byte plusvalue = 0, DbConnection? connectionIn = null) + { + // Add to existing stacks or make new stacks until there are no more items to add + // The stack limit is specified by the stackLimit arg + List results = new List(); + uint itemsToAdd = num; + while (itemsToAdd > 0) + { + uint oldItemNum = 0; + uint newItemNum = num; + uint addedItems = newItemNum - oldItemNum; + itemsToAdd -= addedItems; + + Storage destinationStorage = character.Storage.GetStorage(destinationStorageType); + Item? item = new Item() + { + ItemId = itemId, + Unk3 = 0, + Color = 0, + PlusValue = plusvalue, + EquipPoints = 0, + EquipElementParamList = new List(), + AddStatusParamList = new List(), + Unk2List = new List() + }; + ushort slot = destinationStorage.AddItem(item, newItemNum); + + database.ReplaceStorageItem(character.ContentCharacterId, destinationStorageType, slot, newItemNum, item, connectionIn); + if (BitterblackMazeManager.IsMazeReward(item.ItemId)) + { + item = BitterblackMazeManager.ApplyCrest(database, character, item, connectionIn); + } + + CDataItemUpdateResult result = new CDataItemUpdateResult(); + result.ItemList.ItemUId = item.UId; + result.ItemList.ItemId = item.ItemId; + result.ItemList.ItemNum = newItemNum; + result.ItemList.Unk3 = item.Unk3; + result.ItemList.StorageType = destinationStorageType; + result.ItemList.SlotNo = slot; + result.ItemList.Color = item.Color; + result.ItemList.PlusValue = item.PlusValue; + result.ItemList.Bind = false; + result.ItemList.EquipPoint = item.EquipPoints; + result.ItemList.EquipCharacterID = 0; + result.ItemList.EquipPawnID = 0; + result.ItemList.EquipElementParamList = item.EquipElementParamList; + result.ItemList.AddStatusParamList = item.AddStatusParamList; + result.ItemList.Unk2List = item.Unk2List; + result.UpdateItemNum = (int)addedItems; + results.Add(result); + } + return results; + } + public List MoveItem(DdonServer server, Character character, Storage fromStorage, string itemUId, uint num, Storage toStorage, ushort toSlotNo, DbConnection? connectionIn = null) { var foundItem = fromStorage.FindItemByUId(itemUId); diff --git a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/GiveItemCommand.cs b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/GiveItemCommand.cs index 86ce5378b..e9a260244 100644 --- a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/GiveItemCommand.cs +++ b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/GiveItemCommand.cs @@ -1,6 +1,7 @@ using Arrowgene.Ddon.Database; using Arrowgene.Ddon.Database.Model; using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; using System; using System.Collections.Generic; @@ -36,7 +37,7 @@ public override void Execute(string[] command, GameClient client, ChatMessage me if (command.Length >= 1) { - if (UInt32.TryParse(command[0], out uint parsedId)) + if (uint.TryParse(command[0], out uint parsedId)) { itemId = parsedId; } @@ -50,7 +51,7 @@ public override void Execute(string[] command, GameClient client, ChatMessage me uint amount = DefaultAmount; if (command.Length >= 2) { - if (UInt32.TryParse(command[1], out uint parsedAmount)) + if (uint.TryParse(command[1], out uint parsedAmount)) { amount = parsedAmount; } @@ -67,31 +68,11 @@ public override void Execute(string[] command, GameClient client, ChatMessage me return; } - ClientItemInfo itemInfo = ClientItemInfo.GetInfoForItemId(_server.AssetRepository.ClientItemInfos, itemId); - - SystemMailMessage mail = new SystemMailMessage() - { - Title = $"GiveItem: {itemInfo.Name} x{amount}", - Body = $"", - CharacterId = client.Character.CharacterId, - SenderName = "/giveitem", - MessageState = MailState.Unopened - }; - mail.Attachments.Add(new SystemMailAttachment() + client.Send(new S2CItemUpdateCharacterItemNtc() { - AttachmentType = SystemMailAttachmentType.Item, - Param1 = itemId, - Param2 = amount, - MessageId = (ulong)(mail.Attachments.Count + 1), - IsReceived = false, + UpdateType = ItemNoticeType.StampBonus, + UpdateItemList = _server.ItemManager.AddItem(_server, client.Character, StorageType.ItemPost, itemId, amount), }); - SystemMailService.DeliverSystemMailMessage(_server.Database, mail); - - S2CMailSystemMailSendNtc notice = new S2CMailSystemMailSendNtc() - { - MailInfo = mail.ToCDataMailInfo((byte)(MailItemState.Exist | MailItemState.Item)) - }; - client.Send(notice); } } } From 85043ff6d414b4a720f759d226219913e365308d Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Tue, 3 Sep 2024 00:27:43 -0700 Subject: [PATCH 033/116] Cleanup stamp handlers. --- .../Handler/StampBonusCheckHandler.cs | 17 +++++------- .../Handler/StampBonusGetListHandler.cs | 27 ++++--------------- .../Handler/StampBonusReceiveDailyHandler.cs | 15 +++-------- .../Handler/StampBonusReceiveTotalHandler.cs | 13 +++------ .../C2SStampBonusRecieveDailyReq.cs | 2 +- .../S2CStampBonusRecieveTotalRes.cs | 2 -- 6 files changed, 20 insertions(+), 56 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs index 028b7a924..91a0242ee 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs @@ -1,14 +1,12 @@ using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; -using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { - public class StampBonusCheckHandler : PacketHandler + public class StampBonusCheckHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(StampBonusCheckHandler)); @@ -19,9 +17,7 @@ public StampBonusCheckHandler(DdonGameServer server) : base(server) _gameServer = server; } - public override PacketId Id => PacketId.C2S_STAMP_BONUS_CHECK_REQ; - - public override void Handle(GameClient client, IPacket packet) + public override S2CStampBonusCheckRes Handle(GameClient client, C2SStampBonusCheckReq request) { bool canDaily = _gameServer.StampManager.CanDailyStamp(client.Character.StampBonus); bool canTotal = _gameServer.StampManager.CanTotalStamp(client.Character.StampBonus); @@ -40,7 +36,7 @@ public override void Handle(GameClient client, IPacket packet) Unk0 = 1, Unk1 = 0, }); - client.Send(res); + return res; } else if (canDaily) { @@ -54,17 +50,16 @@ public override void Handle(GameClient client, IPacket packet) Unk0 = 1, Unk1 = 0, }); - client.Send(res); + return res; } else { - client.Send(new S2CStampBonusCheckRes() + return new S2CStampBonusCheckRes() { IsRecieveBonusDaily = byte.MaxValue, IsRecieveBonusTotal = byte.MaxValue, - }); + }; } - } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/StampBonusGetListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StampBonusGetListHandler.cs index 542e0d1d9..237d06ec5 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StampBonusGetListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StampBonusGetListHandler.cs @@ -1,16 +1,13 @@ using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; 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.Diagnostics; using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { - public class StampBonusGetListHandler : PacketHandler + public class StampBonusGetListHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(StampBonusGetListHandler)); @@ -21,9 +18,7 @@ public StampBonusGetListHandler(DdonGameServer server) : base(server) _gameServer = server; } - public override PacketId Id => PacketId.C2S_STAMP_BONUS_GET_LIST_REQ; - - public override void Handle(GameClient client, IPacket packet) + public override S2CStampBonusGetListRes Handle(GameClient client, C2SStampBonusGetListReq request) { //This handler gets called twice in a row; once for the daily bonus list and then once for the total bonus list. //The sequence normally goes CHECK -> GET_LIST -> RECIEVE_DAILY -> GET_LIST -> RECIEVE_TOTAL @@ -31,18 +26,6 @@ public override void Handle(GameClient client, IPacket packet) ushort totalStampNum = (ushort)(client.Character.StampBonus.TotalStamp); - //var res = new S2CStampBonusGetListRes() - //{ - // StampBonusDaily = _gameServer.StampManager.GetDailyStampAssets(), - // StampBonusTotal = _gameServer.StampManager.GetTotalStampAssetsWindow(totalStampNum), - // TotalStampNum = totalStampNum, - // Unk1 = 1, - // Unk2 = 1, - // Unk3 = 77, - // Unk5 = 50, - // Unk6 = 0 - //}; - var res = new S2CStampBonusGetListRes(); res.TotalStamp.TotalStampNum = totalStampNum; res.TotalStamp.AssetList = _gameServer.StampManager.GetTotalStampAssetsWindow(totalStampNum); @@ -60,8 +43,8 @@ public override void Handle(GameClient client, IPacket packet) foreach (var item in res.DailyStamp.First().AssetList) { - if (item.StampNum < client.Character.StampBonus.ConsecutiveStamp+1) item.RecieveState = (byte)StampRecieveState.Claimed; - else if (item.StampNum == client.Character.StampBonus.ConsecutiveStamp+1) item.RecieveState = (byte)StampRecieveState.ToBeClaimed; + if (item.StampNum < client.Character.StampBonus.ConsecutiveStamp + 1) item.RecieveState = (byte)StampRecieveState.Claimed; + else if (item.StampNum == client.Character.StampBonus.ConsecutiveStamp + 1) item.RecieveState = (byte)StampRecieveState.ToBeClaimed; else item.RecieveState = (byte)StampRecieveState.Unearned; } @@ -72,7 +55,7 @@ public override void Handle(GameClient client, IPacket packet) else item.RecieveState = (byte)StampRecieveState.Unearned; } - client.Send(res); + return res; } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveDailyHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveDailyHandler.cs index 7be7846bf..983fb700d 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveDailyHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveDailyHandler.cs @@ -1,14 +1,11 @@ -using Arrowgene.Ddon.GameServer.Dump; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; -using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Logging; -using System; using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { - public class StampBonusReceiveDailyHandler : PacketHandler + public class StampBonusReceiveDailyHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(StampBonusReceiveDailyHandler)); @@ -18,19 +15,15 @@ public StampBonusReceiveDailyHandler(DdonGameServer server) : base(server) _gameServer = server; } - public override PacketId Id => PacketId.C2S_STAMP_BONUS_RECIEVE_DAILY_REQ; - - public override void Handle(GameClient client, IPacket packet) + public override S2CStampBonusRecieveDailyRes Handle(GameClient client, C2SStampBonusRecieveDailyReq request) { - //Update stamp bonus data. - _gameServer.StampManager.UpdateStamp(client.Character); var dailyStamps = _gameServer.StampManager.GetDailyStampAssets().Where(x => x.StampNum == client.Character.StampBonus.ConsecutiveStamp); _gameServer.StampManager.HandleStampBonuses(client, dailyStamps); - client.Send(GameFull.Dump_701); + return new S2CStampBonusRecieveDailyRes(); } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveTotalHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveTotalHandler.cs index 57ee823c1..5880ac475 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveTotalHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StampBonusReceiveTotalHandler.cs @@ -1,13 +1,11 @@ -using Arrowgene.Ddon.GameServer.Dump; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; -using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Logging; using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { - internal class StampBonusReceiveTotalHandler : PacketHandler + internal class StampBonusReceiveTotalHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(StampBonusReceiveTotalHandler)); @@ -18,16 +16,13 @@ public StampBonusReceiveTotalHandler(DdonGameServer server) : base(server) _gameServer = server; } - public override PacketId Id => PacketId.C2S_STAMP_BONUS_RECIEVE_TOTAL_REQ; - - public override void Handle(GameClient client, IPacket packet) + public override S2CStampBonusRecieveTotalRes Handle(GameClient client, C2SStampBonusRecieveTotalReq request) { var totalStamps = _gameServer.StampManager.GetTotalStampAssets().Where(x => x.StampNum == client.Character.StampBonus.TotalStamp); _gameServer.StampManager.HandleStampBonuses(client, totalStamps); - //This is misusing the dumped data but the client accepts it. - client.Send(new Packet(PacketId.S2C_STAMP_BONUS_RECIEVE_TOTAL_RES, GameFull.data_Dump_701)); + return new S2CStampBonusRecieveTotalRes(); } } } diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveDailyReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveDailyReq.cs index 60c1a91eb..00482a2ba 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveDailyReq.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SStampBonusRecieveDailyReq.cs @@ -3,7 +3,7 @@ namespace Arrowgene.Ddon.Shared.Entity.PacketStructure { - internal class C2SStampBonusRecieveDailyReq : IPacketStructure + public class C2SStampBonusRecieveDailyReq : IPacketStructure { public PacketId Id => PacketId.C2S_STAMP_BONUS_RECIEVE_DAILY_REQ; diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveTotalRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveTotalRes.cs index 6a5ecbda8..95adb2578 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveTotalRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CStampBonusRecieveTotalRes.cs @@ -17,9 +17,7 @@ public override void Write(IBuffer buffer, S2CStampBonusRecieveTotalRes obj) public override S2CStampBonusRecieveTotalRes Read(IBuffer buffer) { S2CStampBonusRecieveTotalRes obj = new S2CStampBonusRecieveTotalRes(); - ReadServerResponse(buffer, obj); - return obj; } } From c46ffbfb8b998402e8bb14297a2209947c876f46 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Thu, 5 Sep 2024 21:35:07 -0700 Subject: [PATCH 034/116] Review feedback. --- .../Handler/QuestGetSetQuestInfoListHandler.cs | 1 - Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs index 397f22c05..ea674351e 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs @@ -77,7 +77,6 @@ public override S2CQuestGetSetQuestInfoListRes Handle(GameClient client, C2SQues if (!ret.IsDiscovery) { - ret.DiscoverRewardItemId = ret.SelectRewardItemIdList.FirstOrDefault()?.Value ?? 0 ; ret.SelectRewardItemIdList = new List(); } diff --git a/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs index 91a0242ee..acec85dba 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StampBonusCheckHandler.cs @@ -24,6 +24,9 @@ public override S2CStampBonusCheckRes Handle(GameClient client, C2SStampBonusChe var stampBonusList = _gameServer.StampManager.GetDailyStampAssets().Select(x => x.StampBonus.First()).ToList(); + // TODO: Investigate the proper expectations of the return packet. + // These values produce the desired behavior (notifications when necessary, silence otherwise), + // but are otherwise totally arbitrary. if (canTotal) { var res = new S2CStampBonusCheckRes() From 0dd253d6ee9a04776fac27317d7a9fdcaf89f3cf Mon Sep 17 00:00:00 2001 From: ryohei Date: Wed, 4 Sep 2024 00:33:29 +0100 Subject: [PATCH 035/116] Client is changing banner size based on the Enlarge UI setting. Resolved with introduction of reset.css and 100% width. --- .../www/sp_ingame/campaign/bnr/banner@2x.png | Bin 0 -> 81485 bytes .../www/sp_ingame/campaign/bnr/reset.css | 82 ++++++++++++++++++ .../www/sp_ingame/campaign/bnr/slide.css | 6 +- .../www/sp_ingame/campaign/bnr/slide.html | 1 + 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 Arrowgene.Ddon.WebServer/Files/www/sp_ingame/campaign/bnr/banner@2x.png create mode 100644 Arrowgene.Ddon.WebServer/Files/www/sp_ingame/campaign/bnr/reset.css diff --git a/Arrowgene.Ddon.WebServer/Files/www/sp_ingame/campaign/bnr/banner@2x.png b/Arrowgene.Ddon.WebServer/Files/www/sp_ingame/campaign/bnr/banner@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3b9435d1d60e0d04996bb542f5b3f8f893a3a1 GIT binary patch literal 81485 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%0DfQq~E>og`DkMl^v8`a*dRp=f606 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 DDON + From 2891a584464c09f2add1a4ed02a7b57dfd7328a0 Mon Sep 17 00:00:00 2001 From: ryohei Date: Wed, 4 Sep 2024 17:56:31 +0100 Subject: [PATCH 036/116] Remove commissioned banner, included by mistake --- .../www/sp_ingame/campaign/bnr/banner@2x.png | Bin 81485 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Arrowgene.Ddon.WebServer/Files/www/sp_ingame/campaign/bnr/banner@2x.png diff --git a/Arrowgene.Ddon.WebServer/Files/www/sp_ingame/campaign/bnr/banner@2x.png b/Arrowgene.Ddon.WebServer/Files/www/sp_ingame/campaign/bnr/banner@2x.png deleted file mode 100644 index 9a3b9435d1d60e0d04996bb542f5b3f8f893a3a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81485 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%0DfQq~E>og`DkMl^v8`a*dRp=f606 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 Date: Fri, 6 Sep 2024 09:50:00 +0100 Subject: [PATCH 037/116] Actioned review notes --- .../GatheringItems/InstanceQuestDropManager.cs | 13 ------------- .../Handler/InstanceGetDropItemHandler.cs | 14 +++++++++++--- .../Handler/InstanceGetDropItemListHandler.cs | 11 +++++++++-- .../AssetReader/QuestAssetDeserializer.cs | 10 ++++++---- Arrowgene.Ddon.Shared/AssetRepository.cs | 4 ++-- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs index a9eb8dca5..6cc2f5c9d 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs @@ -45,19 +45,6 @@ public bool IsQuestDrop(CDataStageLayoutId layoutId, uint setId) return false; } - //public List FetchEnemyLoot(CDataStageLayoutId layoutId, uint setId) - //{ - // // Do a last backup check before fetching loot in case IsQuestDrop somehow wasn't called befoer this. - // if (LastDropIdQuery == 0) - // { - // bool check = IsQuestDrop(layoutId, setId); - - // if (!check) return null; - // } - - // return FetchEnemyLoot(); - //} - public List FetchEnemyLoot() { if (QuestEnemyDropsTable.ContainsKey(LastDropIdQuery)) diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs index ee0df1f59..0b18b0712 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs @@ -19,9 +19,17 @@ public InstanceGetDropItemHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { - // This call is for when an item is claimed from a bag. This also needs to drops stored from the enemy. - List items = client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId) ? - client.InstanceQuestDropManager.FetchEnemyLoot() : client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); + // 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; + + if (client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId)) + { + items = client.InstanceQuestDropManager.FetchEnemyLoot(); + } else + { + items = client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); + } S2CInstanceGetDropItemRes res = new() { diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs index 4c3619842..f0213e796 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemListHandler.cs @@ -23,8 +23,15 @@ public InstanceGetDropItemListHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { // This call is for when a bag is opened. Get the correct drops stored from the kill handler. - List items = client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId) ? - client.InstanceQuestDropManager.FetchEnemyLoot() : client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); + List items; + + if(client.InstanceQuestDropManager.IsQuestDrop(packet.Structure.LayoutId, packet.Structure.SetId)) + { + items = client.InstanceQuestDropManager.FetchEnemyLoot(); + } else + { + items = client.InstanceDropItemManager.GetAssets(packet.Structure.LayoutId, packet.Structure.SetId); + } client.Send(new S2CInstanceGetDropItemListRes() { diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 1d990ca12..b75fce018 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -17,14 +17,15 @@ public class QuestAssetDeserializer private static readonly ILogger Logger = LogProvider.Logger(typeof(QuestAssetDeserializer)); private Dictionary namedParams; + private QuestDropItemAsset questDrops; - public QuestAssetDeserializer(Dictionary namedParams) + public QuestAssetDeserializer(Dictionary namedParams, QuestDropItemAsset questDrops) { this.namedParams = namedParams; + this.questDrops = questDrops; } - //public bool LoadQuestsFromDirectory(string path, QuestAsset questAssets, DropItemsAsset dropItemsAsset) - public bool LoadQuestsFromDirectory(string path, QuestAsset questAssets)//, QuestDropItemAsset questLootDrops) + public bool LoadQuestsFromDirectory(string path, QuestAsset questAssets) { DirectoryInfo info = new DirectoryInfo(path); if (!info.Exists) @@ -893,7 +894,8 @@ private bool ParseEnemyGroups(QuestAssetData assetData, JsonElement quest) else { // Get default drops for this enemy id and level. - DropsTable lootTable = AssetRepository.QuestDropItemAsset.GetDropTable(questEnemy.EnemyId, questEnemy.Lv); + DropsTable lootTable = questDrops.GetDropTable(questEnemy.EnemyId, questEnemy.Lv); + questEnemy.DropsTable = lootTable; } diff --git a/Arrowgene.Ddon.Shared/AssetRepository.cs b/Arrowgene.Ddon.Shared/AssetRepository.cs index 0d4bcde8b..cbde0d3ff 100644 --- a/Arrowgene.Ddon.Shared/AssetRepository.cs +++ b/Arrowgene.Ddon.Shared/AssetRepository.cs @@ -119,7 +119,7 @@ public AssetRepository(string folder) public SpecialShopAsset SpecialShopAsset { get; private set; } public PawnCostReductionAsset PawnCostReductionAsset { get; private set; } public BitterblackMazeAsset BitterblackMazeAsset { get; private set; } - static public QuestDropItemAsset QuestDropItemAsset { get; private set; } + public QuestDropItemAsset QuestDropItemAsset { get; private set; } public void Initialize() { @@ -149,7 +149,7 @@ public void Initialize() RegisterAsset(value => PawnCostReductionAsset = value, PawnCostReductionKey, new PawnCostReductionAssetDeserializer()); RegisterAsset(value => BitterblackMazeAsset = value, BitterblackMazeKey, new BitterblackMazeAssetDeserializer()); RegisterAsset(value => QuestDropItemAsset = value, QuestDropItemsKey, new QuestDropAssetDeserializer()); - var questAssetDeserializer = new QuestAssetDeserializer(this.NamedParamAsset); + var questAssetDeserializer = new QuestAssetDeserializer(this.NamedParamAsset, QuestDropItemAsset); questAssetDeserializer.LoadQuestsFromDirectory(Path.Combine(_directory.FullName, QuestAssestKey), QuestAssets); } From d3cd6c1cad4897b0a879afd4bbce6493fecb7bd4 Mon Sep 17 00:00:00 2001 From: Asterit Date: Fri, 6 Sep 2024 10:30:39 +0100 Subject: [PATCH 038/116] Merged with latest --- Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index cd2295fd0..29f14aa9b 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -279,7 +279,6 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted) if (quest.IsVariantQuest) { ActiveVariantQuests.Add(quest.QuestId, quest.VariantId); - ActiveVariantQuests.Add(quest.QuestId, (uint)quest.VariantId); } AddNewQuest(quest, step, questStarted); From 84fabd80adaa89873a4c9f7c41971895e4b725c3 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 29 Aug 2024 00:16:58 -0400 Subject: [PATCH 039/116] feat: Enable personal quests Added support for personal quests. --- .../Characters/ExpManager.cs | 2 +- .../Characters/QuestManager.cs | 84 +++++-- .../Characters/StageManager.cs | 2 +- .../QuestGetCycleContentsStateListHandler.cs | 8 + .../Handler/QuestGetSetQuestListHandler.cs | 38 ++- .../QuestGetTutorialQuestListHandler.cs | 58 +++-- .../Handler/QuestQuestProgressHandler.cs | 10 +- .../Quests/GenericQuest.cs | 1 + Arrowgene.Ddon.GameServer/Quests/Quest.cs | 20 +- Arrowgene.Ddon.Shared/Asset/QuestAsset.cs | 1 + .../AssetReader/QuestAssetDeserializer.cs | 6 + .../Entity/EntitySerializer.cs | 2 + .../S2CQuestGetTutorialQuestListRes.cs | 39 ++++ .../Structure/CDataTutorialQuestList.cs | 32 +++ .../Structure/CDataTutorialQuestOrderList.cs | 8 +- .../Files/Assets/quests/q60000050.json | 64 ++++++ .../Files/Assets/quests/q60200100.json | 217 ++++++++++++++++++ .../Files/Assets/quests/q60300401.json | 64 ++++++ .../Model/Quest/QuestType.cs | 8 +- 19 files changed, 604 insertions(+), 60 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetTutorialQuestListRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataTutorialQuestList.cs create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q60000050.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q60200100.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q60300401.json diff --git a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs index ca0e3104d..cefb67e97 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs @@ -458,7 +458,7 @@ public static CDataCharacterJobData CalculateBaseStats(CharacterCommon Character return JobData; } - public void AddExp(GameClient client, CharacterCommon characterToAddExpTo, uint gainedExp, RewardSource rewardType, QuestType questType = QuestType.Unknown) + public void AddExp(GameClient client, CharacterCommon characterToAddExpTo, uint gainedExp, RewardSource rewardType, QuestType questType = QuestType.All) { var lvCap = (client.GameMode == GameMode.Normal) ? ExpManager.LV_CAP : BitterblackMazeManager.LevelCap(client.Character.BbmProgress); diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index d7b349f0a..3b9960576 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -22,6 +22,8 @@ private QuestManager() private static Dictionary gQuests = new Dictionary(); private static readonly Dictionary> variantQuests = new(); private static readonly HashSet AvailableVariantQuests = new(); + private static Dictionary> gPersonalQuests = new Dictionary>(); + private static Dictionary> gWorldQuests = new Dictionary>(); public static HashSet GetAllVariantQuestIds() { @@ -30,24 +32,13 @@ public static HashSet GetAllVariantQuestIds() public static void LoadQuests(AssetRepository assetRepository) { - // TODO: Load additional quests from file? - // TODO: Once everything is comfortably understood, we can probably serialize - // TODO: the quest data and load it from file when the server starts instead - - // Main Quests - // gQuests[QuestId.TheSlumberingGod] = new Mq000002_TheSlumberingGod(); - // gQuests[QuestId.TheGreatAlchemist] = new Mq000025_TheGreatAlchemist(); - // gQuests[QuestId.HopesBitterEnd] = new Mq030260_HopesBitterEnd(); - // Load Quests defined in files foreach (var questAsset in assetRepository.QuestAssets.Quests) { - // Separate all variant quests to its own dictionary for separate handling. // This also ensures these quests are not in gQuests before processing. if (questAsset.VariantId != 0 && !gQuests.ContainsKey(questAsset.QuestId)) { - Quest alternateQuest = GenericQuest.FromAsset(questAsset); alternateQuest.IsVariantQuest = true; alternateQuest.VariantId = (uint)questAsset.VariantId; @@ -57,17 +48,34 @@ public static void LoadQuests(AssetRepository assetRepository) { variantQuests[alternateQuest.QuestId] = new Dictionary(); variantQuests[questAsset.QuestId].Add(alternateQuest.VariantId, alternateQuest); - continue; } // Add quest id and quest variantQuests[questAsset.QuestId].Add(alternateQuest.VariantId, alternateQuest); - continue; } gQuests[questAsset.QuestId] = GenericQuest.FromAsset(questAsset); + + var quest = gQuests[questAsset.QuestId]; + if (quest.QuestType == QuestType.Personal) + { + uint stageNo = (uint)StageManager.ConvertIdToStageNo(quest.StageId); + if (!gPersonalQuests.ContainsKey(stageNo)) + { + gPersonalQuests[stageNo] = new List(); + } + gPersonalQuests[stageNo].Add(quest); + } + else if (quest.QuestType == QuestType.World) + { + if (!gWorldQuests.ContainsKey(quest.QuestAreaId)) + { + gWorldQuests[quest.QuestAreaId] = new List(); + } + gWorldQuests[quest.QuestAreaId].Add(quest); + } } var variantQuestKeys = variantQuests.Keys.ToArray(); @@ -138,6 +146,36 @@ public static List> GetQuestsByType(QuestType type) return results; } + public static List GetWorldQuestsByAreaId(QuestAreaId areaId) + { + if (!gWorldQuests.ContainsKey(areaId)) + { + return new List(); + } + + return gWorldQuests[areaId]; + } + + public static List GetWorldQuestIdsByAreaId(QuestAreaId areaId) + { + if (!gWorldQuests.ContainsKey(areaId)) + { + return new List(); + } + + return gWorldQuests[areaId].Select(x => x.QuestId).ToList(); + } + + public static List GetPersonalQuestsByStageNo(uint stageNo) + { + if (!gPersonalQuests.ContainsKey(stageNo)) + { + return new List(); + } + + return gPersonalQuests[stageNo]; + } + public static uint GetRandomVariantId(QuestId baseQuest) { // Get random index value to choose a quest version. @@ -3231,9 +3269,29 @@ public static bool IsBoardQuest(QuestId questId) return (((uint)questId) >= 40000000) && (((uint)questId) < 50000000); } + public static bool IsBoardQuest(Quest quest) + { + return IsBoardQuest(quest.QuestId); + } + + public static bool IsPersonalQuest(QuestId questId) + { + return (((uint)questId) >= 60000000) && (((uint)questId) < 70000000); + } + + public static bool IsPersonalQuest(Quest quest) + { + return IsPersonalQuest(quest.QuestId); + } + public static bool IsWorldQuest(QuestId questId) { return (((uint)questId) >= 20000000) && (((uint)questId) < 30000000); } + + public static bool IsWorldQuest(Quest quest) + { + return IsWorldQuest(quest.QuestId); + } } } diff --git a/Arrowgene.Ddon.GameServer/Characters/StageManager.cs b/Arrowgene.Ddon.GameServer/Characters/StageManager.cs index 9e095ac97..24f500900 100644 --- a/Arrowgene.Ddon.GameServer/Characters/StageManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/StageManager.cs @@ -24,7 +24,7 @@ public static StageNo ConvertIdToStageNo(uint stageId) return (StageNo)stageInfo.StageNo; } - return 0; // TODO: Maybe throw an exception? + return 0; } public static StageNo ConvertIdToStageNo(StageId stageId) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs index 7d6bc7def..7070a4bca 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs @@ -54,6 +54,14 @@ public override void Handle(GameClient client, IPacket packet) ntc.MainQuestIdList.Add(new CDataQuestId() { QuestId = (uint) msq.QuestId }); } + var personalQuestInProgress = Server.Database.GetQuestProgressByType(client.Character.CommonId, QuestType.Personal); + foreach (var questProgress in personalQuestInProgress) + { + var quest = QuestManager.GetQuest(questProgress.QuestId); + var tutorialQuest = quest.ToCDataTutorialQuestOrderList(questProgress.Step); + ntc.TutorialQuestOrderList.Add(tutorialQuest); + } + if (client.Party != null) { var priorityQuests = Server.Database.GetPriorityQuests(client.Party.Leader.Client.Character.CommonId); diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs index e43b6c284..e062a333c 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs @@ -1,6 +1,7 @@ using Arrowgene.Buffers; using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Dump; +using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.GameServer.Quests; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; @@ -29,29 +30,26 @@ public override void Handle(GameClient client, StructurePacket + public class QuestGetTutorialQuestListHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetTutorialQuestListHandler)); - public QuestGetTutorialQuestListHandler(DdonGameServer server) : base(server) { } - public override PacketId Id => PacketId.C2S_QUEST_GET_TUTORIAL_QUEST_LIST_REQ; - - public override void Handle(GameClient client, IPacket packet) + public override S2CQuestGetTutorialQuestListRes Handle(GameClient client, C2SQuestGetTutorialQuestListReq request) { - IBuffer buffer = new StreamBuffer(); - buffer.WriteUInt32(0); - buffer.WriteUInt32(0); - buffer.WriteUInt32(0); - Packet p = new Packet(PacketId.S2C_QUEST_GET_TUTORIAL_QUEST_LIST_RES, buffer.GetAllBytes()); - client.Send(p); - //client.Send(GameFull.Dump_157); + var result = new S2CQuestGetTutorialQuestListRes() + { + StageNo = request.StageNo, + }; + + var completedQuests = Server.Database.GetCompletedQuestsByType(client.Character.CommonId, QuestType.Personal); + + // This handler should return personal quests which have not been started + // yet when the player enters the StageNo + foreach (var quest in QuestManager.GetPersonalQuestsByStageNo(request.StageNo)) + { + uint stageNo = (uint) StageManager.ConvertIdToStageNo(quest.StageId); + if (stageNo != request.StageNo) + { + continue; + } + + if (completedQuests.Exists(x => x.QuestId == quest.QuestId)) + { + continue; + } + + var questState = client.Party.QuestState.GetQuestState(quest); + if (questState == null || questState.Step == 0) + { + var tutorialQuest = quest.ToCDataTutorialQuestList(0); + result.TutorialQuestList.Add(tutorialQuest); + } + + if (questState == null) + { + client.Party.QuestState.AddNewQuest(quest, 0); + } + } + + return result; } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs index d73e425c1..81eaaac80 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs @@ -37,7 +37,7 @@ public override void Handle(GameClient client, StructurePacket PacketId.S2C_QUEST_GET_TUTORIAL_QUEST_LIST_RES; + + public S2CQuestGetTutorialQuestListRes() + { + TutorialQuestList = new List(); + } + + public uint StageNo { get; set; } + public List TutorialQuestList { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestGetTutorialQuestListRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt32(buffer, obj.StageNo); + WriteEntityList(buffer, obj.TutorialQuestList); + } + + public override S2CQuestGetTutorialQuestListRes Read(IBuffer buffer) + { + S2CQuestGetTutorialQuestListRes obj = new S2CQuestGetTutorialQuestListRes(); + ReadServerResponse(buffer, obj); + obj.StageNo = ReadUInt32(buffer); + obj.TutorialQuestList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTutorialQuestList.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTutorialQuestList.cs new file mode 100644 index 000000000..35530ef2e --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTutorialQuestList.cs @@ -0,0 +1,32 @@ +using Arrowgene.Buffers; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataTutorialQuestList + { + public CDataTutorialQuestList() + { + Param = new CDataQuestList(); + } + + public CDataQuestList Param { get; set; } + public bool EnableCancel { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataTutorialQuestList obj) + { + WriteEntity(buffer, obj.Param); + WriteBool(buffer, obj.EnableCancel); + } + + public override CDataTutorialQuestList Read(IBuffer buffer) + { + CDataTutorialQuestList obj = new CDataTutorialQuestList(); + obj.Param = ReadEntity(buffer); + obj.EnableCancel = ReadBool(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTutorialQuestOrderList.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTutorialQuestOrderList.cs index 8c176dc14..8c18c1c02 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTutorialQuestOrderList.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTutorialQuestOrderList.cs @@ -9,23 +9,23 @@ public CDataTutorialQuestOrderList() { } public CDataQuestOrderList Param { get; set; } - public byte EnableCancel { get; set; } + public bool EnableCancel { get; set; } public class Serializer : EntitySerializer { public override void Write(IBuffer buffer, CDataTutorialQuestOrderList obj) { WriteEntity(buffer, obj.Param); - WriteByte(buffer, obj.EnableCancel); + WriteBool(buffer, obj.EnableCancel); } public override CDataTutorialQuestOrderList Read(IBuffer buffer) { CDataTutorialQuestOrderList obj = new CDataTutorialQuestOrderList(); obj.Param = ReadEntity(buffer); - obj.EnableCancel = ReadByte(buffer); + obj.EnableCancel = ReadBool(buffer); return obj; } } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q60000050.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q60000050.json new file mode 100644 index 000000000..1e5e08a98 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q60000050.json @@ -0,0 +1,64 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Personal", + "comment": "Seeking the Master: Fighter", + "quest_id": 60000050, + "next_quest": 0, + "base_level": 18, + "minimum_item_rank": 0, + "discoverable": false, + "stage_id": {"id": 2}, + "order_conditions": [ + { + "type": "MinimumVocationLevel", + "Param1": 1, + "Param2": 18 + } + ], + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 300 + }, + { + "type": "exp", + "amount": 2500 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 9381, + "num": 1 + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 2, + "group_id": 1 + }, + "npc_id": "Renton0", + "message_id": 14011 + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 66, + "group_id": 1 + }, + "announce_type": "Accept", + "npc_id": "Vanessa0", + "message_id": 14014 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q60200100.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q60200100.json new file mode 100644 index 000000000..16416c17d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q60200100.json @@ -0,0 +1,217 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Personal", + "comment": "Skill Augmentation: Fighter Trial I", + "quest_id": 60200100, + "next_quest": 0, + "base_level": 40, + "minimum_item_rank": 0, + "discoverable": false, + "stage_id": {"id": 66}, + "order_conditions": [ + { + "type": "MinimumVocationLevel", + "Param1": 1, + "Param2": 40 + } + ], + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 600 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 60 + }, + { + "type": "exp", + "amount": 2500 + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 85, + "group_id": 3 + }, + "enemies": [ + { + "comment": "undead male", + "enemy_id": "0x010500", + "level": 40, + "exp": 110 + }, + { + "comment": "undead female", + "enemy_id": "0x010501", + "level": 40, + "exp": 110 + }, + { + "comment": "undead male", + "enemy_id": "0x010500", + "level": 40, + "exp": 110 + }, + { + "comment": "undead female", + "enemy_id": "0x010501", + "level": 40, + "exp": 110 + }, + { + "comment": "skeleton warrior", + "enemy_id": "0x010302", + "level": 40, + "exp": 125, + "blood_orbs": 8 + }, + { + "comment": "skeleton warrior", + "enemy_id": "0x010302", + "level": 40, + "exp": 125, + "blood_orbs": 8 + }, + { + "comment": "skeleton warrior", + "enemy_id": "0x010302", + "level": 40, + "exp": 125, + "blood_orbs": 8 + } + ] + }, + { + "stage_id": { + "id": 85, + "group_id": 7 + }, + "enemies": [ + { + "comment": "mudman", + "enemy_id": "0x010509", + "level": 40, + "exp": 185 + }, + { + "comment": "skeleton warrior", + "enemy_id": "0x010302", + "level": 40, + "exp": 162, + "blood_orbs": 8 + }, + { + "comment": "skeleton warrior", + "enemy_id": "0x010302", + "level": 40, + "exp": 162 + }, + { + "comment": "mudman", + "enemy_id": "0x010509", + "level": 40, + "exp": 185 + }, + { + "comment": "skeleton warrior", + "enemy_id": "0x010302", + "level": 40, + "exp": 162, + "blood_orbs": 8 + } + ] + }, + { + "stage_id": { + "id": 85, + "group_id": 10 + }, + "enemies": [ + { + "comment": "Skull Lord", + "enemy_id": "0x010313", + "level": 40, + "exp": 185 + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 66, + "group_id": 1 + }, + "npc_id": "Vanessa0", + "message_id": 17589 + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 85, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 3583, "comment": "Spawns Vanessa"} + ], + "announce_type": "Accept", + "npc_id": "Vanessa0", + "message_id": 17591 + }, + { + "type": "DiscoverEnemy", + "announce_type": "Update", + "flags": [ + {"type": "MyQst", "action": "Set", "value": 1346, "comment": "Makes Vanessa follow the player"} + ], + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 0 ] + }, + { + "type": "DiscoverEnemy", + "checkpoint": true, + "announce_type": "Update", + "groups": [ 1 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 1 ] + }, + { + "type": "DiscoverEnemy", + "checkpoint": true, + "announce_type": "Update", + "groups": [ 2 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 2 ] + }, + { + "type": "TalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 66, + "group_id": 1 + }, + "npc_id": "Vanessa0", + "message_id": 17593 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q60300401.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q60300401.json new file mode 100644 index 000000000..f2891cc07 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q60300401.json @@ -0,0 +1,64 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Personal", + "comment": "Seeking the Master: High Scepter", + "quest_id": 60300401, + "next_quest": 0, + "base_level": 18, + "minimum_item_rank": 0, + "discoverable": false, + "stage_id": {"id": 584}, + "order_conditions": [ + { + "type": "MinimumVocationLevel", + "Param1": 11, + "Param2": 18 + } + ], + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 300 + }, + { + "type": "exp", + "amount": 2500 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 20047, + "num": 1 + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 584, + "group_id": 1 + }, + "npc_id": "Yumit", + "message_id": 28177 + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 464, + "group_id": 1 + }, + "announce_type": "Accept", + "npc_id": "Kirsty0", + "message_id": 28179 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestType.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestType.cs index 9e9c54d67..91af1faaf 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestType.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestType.cs @@ -2,7 +2,7 @@ namespace Arrowgene.Ddon.Shared.Model.Quest { public enum QuestType : uint { - Unknown = 0, + All = 0, Light = 1, Set = 2, Main = 3, @@ -16,8 +16,10 @@ public enum QuestType : uint Unk1 = 11, // Queried when logging in // Pseudo Categories - World = 1, - All + Board = 1, + World = 1, // World should be Set Quest (2) not light + Personal = 4, + #if false // Seems game has 2 different sets of quest IDs From aa732b244437b3e9206bba74eed014f492946fc2 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 6 Sep 2024 13:27:14 -0400 Subject: [PATCH 040/116] Small changes - Added a todo about distribution id - Reorganized loop --- .../Characters/QuestManager.cs | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 3b9960576..181a79445 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -32,6 +32,9 @@ public static HashSet GetAllVariantQuestIds() public static void LoadQuests(AssetRepository assetRepository) { + // TODO: Quests should probably operate on the distribuition ID instead of quest id so the global list can still contain all quests + // TODO: Then quests can be distributed to different lists for faster lookup (like world by area id or personal by stageno) + // Load Quests defined in files foreach (var questAsset in assetRepository.QuestAssets.Quests) { @@ -53,33 +56,33 @@ public static void LoadQuests(AssetRepository assetRepository) // Add quest id and quest variantQuests[questAsset.QuestId].Add(alternateQuest.VariantId, alternateQuest); - continue; } - - gQuests[questAsset.QuestId] = GenericQuest.FromAsset(questAsset); - - var quest = gQuests[questAsset.QuestId]; - if (quest.QuestType == QuestType.Personal) + else { - uint stageNo = (uint)StageManager.ConvertIdToStageNo(quest.StageId); - if (!gPersonalQuests.ContainsKey(stageNo)) + gQuests[questAsset.QuestId] = GenericQuest.FromAsset(questAsset); + + var quest = gQuests[questAsset.QuestId]; + if (quest.QuestType == QuestType.Personal) { - gPersonalQuests[stageNo] = new List(); + uint stageNo = (uint)StageManager.ConvertIdToStageNo(quest.StageId); + if (!gPersonalQuests.ContainsKey(stageNo)) + { + gPersonalQuests[stageNo] = new List(); + } + gPersonalQuests[stageNo].Add(quest); } - gPersonalQuests[stageNo].Add(quest); - } - else if (quest.QuestType == QuestType.World) - { - if (!gWorldQuests.ContainsKey(quest.QuestAreaId)) + else if (quest.QuestType == QuestType.World) { - gWorldQuests[quest.QuestAreaId] = new List(); + if (!gWorldQuests.ContainsKey(quest.QuestAreaId)) + { + gWorldQuests[quest.QuestAreaId] = new List(); + } + gWorldQuests[quest.QuestAreaId].Add(quest); } - gWorldQuests[quest.QuestAreaId].Add(quest); } } var variantQuestKeys = variantQuests.Keys.ToArray(); - for (int i = 0; i < variantQuestKeys.Length; i++) { // Store of all variant ids under the generic quest id From 828b3741e70ee162b99fc4b6f6eb89972bb8b5bf Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 6 Sep 2024 13:17:57 -0400 Subject: [PATCH 041/116] Add Mergoda Ruins World Quests Authored-by: Dixdros --- .../Assets/quests/!BROKEN!q20990002.json | 191 ++++++++++++++++++ .../Files/Assets/quests/q20990001.json | 85 ++++++++ .../Files/Assets/quests/q20990003.json | 114 +++++++++++ .../Files/Assets/quests/q20990004.json | 92 +++++++++ .../Files/Assets/quests/q20995000.json | 100 +++++++++ .../Files/Assets/quests/q20995002.json | 92 +++++++++ .../Files/Assets/quests/q20995004.json | 95 +++++++++ .../Files/Assets/quests/q20995005.json | 113 +++++++++++ .../Files/Assets/quests/q20995006.json | 74 +++++++ .../Files/Assets/quests/q20995007.json | 71 +++++++ .../Files/Assets/quests/q20995009.json | 88 ++++++++ .../Files/Assets/quests/q20995010.json | 88 ++++++++ .../Files/Assets/quests/q20995011.json | 98 +++++++++ .../Files/Assets/quests/q21000090.json | 70 +++++++ 14 files changed, 1371 insertions(+) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/!BROKEN!q20990002.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20990001.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20990003.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20990004.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995000.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995002.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995004.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995005.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995006.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995007.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995009.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995010.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q20995011.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q21000090.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/!BROKEN!q20990002.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/!BROKEN!q20990002.json new file mode 100644 index 000000000..34f938030 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/!BROKEN!q20990002.json @@ -0,0 +1,191 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "A Deathly Wisdom", + "quest_id": 20990002, + "base_level": 53, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "exp", + "amount": 1920 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1740 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 250 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7959, + "num": 1 + }, + { + "item_id": 9363, + "num": 3 + }, + { + "item_id": 41, + "num": 1 + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Actaeon", + "message_id": 11372 + }, + { + "type": "CollectItem", + "announce_type": "Accept", + "stage_id": { + "id": 84, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1299, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1300, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1301, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1717, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1718, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1719, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1720, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1721, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1722, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1723, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1724, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1725, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1726, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1727, "comment": "Spawns Glowing Item"} + ] + }, + { + "type": "CollectItem", + "announce_type": "Update", + "stage_id": { + "id": 84, + "group_id": 2, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1299, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1300, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1301, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1717, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1718, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1719, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1720, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1721, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1722, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1723, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1724, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1725, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1726, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1727, "comment": "Spawns Glowing Item"} + ] + }, + { + "type": "CollectItem", + "announce_type": "Update", + "stage_id": { + "id": 84, + "group_id": 3, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1299, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1300, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1301, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1717, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1718, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1719, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1720, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1721, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1722, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1723, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1724, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1725, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1726, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1727, "comment": "Spawns Glowing Item"} + ] + }, + { + "type": "CollectItem", + "announce_type": "Update", + "stage_id": { + "id": 84, + "group_id": 4, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1299, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1300, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1301, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1717, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1718, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1719, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1720, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1721, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1722, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1723, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1724, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1725, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1726, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1727, "comment": "Spawns Glowing Item"} + ] + }, + { + "type": "CollectItem", + "announce_type": "Update", + "stage_id": { + "id": 84, + "group_id": 5, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1299, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1300, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1301, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1717, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1718, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1719, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1720, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1721, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1722, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1723, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1724, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1725, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1726, "comment": "Spawns Glowing Item"}, + {"type": "QstLayout", "action": "Set", "value": 1727, "comment": "Spawns Glowing Item"} + ] + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Actaeon", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990001.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990001.json new file mode 100644 index 000000000..e27a61bdd --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990001.json @@ -0,0 +1,85 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "Separation and Completion", + "quest_id": 20990001, + "base_level": 51, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 800 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 180 + }, + { + "type": "exp", + "amount": 560 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 8027, + "num": 1 + }, + { + "item_id": 7554, + "num": 3 + }, + { + "item_id": 9363, + "num": 3 + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 63 + }, + "npc_id": "Theodor", + "message_id": 10800 + }, + { + "type": "DeliverItems", + "stage_id": { + "id": 63, + "group_id": 1 + }, + "npc_id": "Theodor", + "announce_type": "Accept", + "items": [ + { + "id": 7734, + "amount": 8 + } + ], + "message_id": 10737 + }, + { + "type": "DeliverItems", + "stage_id": { + "id": 63, + "group_id": 1 + }, + "npc_id": "Theodor", + "announce_type": "Update", + "items": [ + { + "id": 8026, + "amount": 6 + } + ], + "message_id": 10737 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990003.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990003.json new file mode 100644 index 000000000..58dd7e47d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990003.json @@ -0,0 +1,114 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "A Wish For Inheritance", + "quest_id": 20990003, + "base_level": 50, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "exp", + "amount": 1370 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1100 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 220 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 8033, + "num": 2 + }, + { + "item_id": 8023, + "num": 1 + }, + { + "item_id": 41, + "num": 2 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 58, + "group_id": 0 + }, + "enemies": [ + { + "enemy_id": "0x011020", + "level": 50, + "exp": 1500, + "is_boss": false, + "hm_present_no": 59 + }, + { + "enemy_id": "0x011023", + "level": 50, + "exp": 1500, + "is_boss": false, + "hm_present_no": 62 + }, + { + "enemy_id": "0x011024", + "level": 50, + "exp": 1500, + "is_boss": false, + "hm_present_no": 63 + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 56, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Ariadne", + "message_id": 11372 + }, + { + "type": "CollectItem", + "announce_type": "Accept", + "stage_id": { + "id": 58, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1302, "comment": "Spawns Glowing Item"} + ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 56, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Ariadne", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990004.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990004.json new file mode 100644 index 000000000..1430bcaa6 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990004.json @@ -0,0 +1,92 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Lost Golden Child", + "quest_id": 20990004, + "base_level": 53, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "exp", + "amount": 2440 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1740 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 270 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 8031, + "num": 1 + }, + { + "item_id": 9402, + "num": 3 + }, + { + "item_id": 9363, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 84, + "group_id": 10 + }, + "enemies": [ + { + "enemy_id": "0x015102", + "level": 54, + "exp": 18500, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Metrophanes", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Metrophanes", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995000.json new file mode 100644 index 000000000..2e48d8b06 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995000.json @@ -0,0 +1,100 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "Seeking Alchemical Extract", + "quest_id": 20995000, + "base_level": 60, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "exp", + "amount": 6600 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 4620 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 1180 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 9452, + "num": 3 + }, + { + "item_id": 9453, + "num": 1 + }, + { + "item_id": 7802, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 74, + "group_id": 8 + }, + "enemies": [ + { + "enemy_id": "0x015711", + "level": 60, + "exp": 45000, + "named_enemy_params_id": 634, + "is_boss": true + }, + { + "enemy_id": "0x015711", + "level": 60, + "exp": 45000, + "named_enemy_params_id": 633, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Actaeon", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Actaeon", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995002.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995002.json new file mode 100644 index 000000000..ce22c4001 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995002.json @@ -0,0 +1,92 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Blue Dragon's Shadow", + "quest_id": 20995002, + "base_level": 51, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "exp", + "amount": 2940 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1680 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 330 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7914, + "num": 1 + }, + { + "item_id": 9403, + "num": 3 + }, + { + "item_id": 41, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 76, + "group_id": 1 + }, + "enemies": [ + { + "enemy_id": "0x015707", + "level": 51, + "exp": 17000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Metrophanes", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Metrophanes", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995004.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995004.json new file mode 100644 index 000000000..e5ec4625a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995004.json @@ -0,0 +1,95 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Abandoned Giant Warrior", + "quest_id": 20995004, + "base_level": 53, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "exp", + "amount": 2440 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1740 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 270 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 8029, + "num": 1 + }, + { + "item_id": 9401, + "num": 3 + }, + { + "item_id": 7554, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 76, + "group_id": 5 + }, + "enemies": [ + { + "enemy_id": "0x015103", + "level": 53, + "exp": 16000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1619, "comment": "Spawns 亡都の錬金術師 NPC"} + ], + "stage_id": { + "id": 76, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "4740", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 76, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "4740", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995005.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995005.json new file mode 100644 index 000000000..51e095bb0 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995005.json @@ -0,0 +1,113 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "An Altered Beast Attacks!", + "quest_id": 20995005, + "base_level": 55, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "exp", + "amount": 2960 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1810 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 330 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7833, + "num": 2 + }, + { + "item_id": 8031, + "num": 2 + }, + { + "item_id": 7555, + "num": 2 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 76, + "group_id": 10 + }, + "enemies": [ + { + "enemy_id": "0x015304", + "level": 55, + "exp": 21000, + "is_boss": true + }, + { + "enemy_id": "0x010606", + "level": 55, + "exp": 1500, + "is_boss": false + }, + { + "enemy_id": "0x010606", + "level": 55, + "exp": 1500, + "is_boss": false + }, + { + "enemy_id": "0x010606", + "level": 55, + "exp": 1500, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1620, "comment": "Spawns Eunike NPC"} + ], + "stage_id": { + "id": 76, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Eunike", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 76, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Eunike", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995006.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995006.json new file mode 100644 index 000000000..e5af3b497 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995006.json @@ -0,0 +1,74 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "A Training Requirement", + "quest_id": 20995006, + "base_level": 50, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 900 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 200 + }, + { + "type": "exp", + "amount": 650 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7833, + "num": 2 + }, + { + "item_id": 7554, + "num": 3 + }, + { + "item_id": 41, + "num": 1 + } + ] + } + ], + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1627, "comment": "Spawns 禁域守の神官 NPC"} + ], + "stage_id": { + "id": 76, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "4743", + "message_id": 11372 + }, + { + "type": "DeliverItems", + "stage_id": { + "id": 76, + "group_id": 1 + }, + "npc_id": "4743", + "announce_type": "Accept", + "items": [ + { + "id": 7850, + "amount": 9 + } + ], + "message_id": 10737 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995007.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995007.json new file mode 100644 index 000000000..8bdc69a1a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995007.json @@ -0,0 +1,71 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "Mergoda's Resource Issue", + "quest_id": 20995007, + "base_level": 50, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 800 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 180 + }, + { + "type": "exp", + "amount": 560 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 8027, + "num": 1 + }, + { + "item_id": 7554, + "num": 3 + }, + { + "item_id": 9363, + "num": 3 + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 237, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Iosef", + "message_id": 11372 + }, + { + "type": "DeliverItems", + "stage_id": { + "id": 237, + "group_id": 1 + }, + "npc_id": "Iosef", + "announce_type": "Accept", + "items": [ + { + "id": 8025, + "amount": 9 + } + ], + "message_id": 10737 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995009.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995009.json new file mode 100644 index 000000000..31abcc02c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995009.json @@ -0,0 +1,88 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Experiments Strike Back", + "quest_id": 20995009, + "base_level": 52, + "minimum_item_rank": 0, + "discoverable": false, + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1430 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 250 + }, + { + "type": "exp", + "amount": 1880 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 9159, + "num": 1 + }, + { + "item_id": 9402, + "num": 3 + }, + { + "item_id": 9363, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 84, + "group_id": 6 + }, + "enemies": [ + { + "enemy_id": "0x015505", + "level": 52, + "exp": 14000, + "is_boss": true + }, + { + "enemy_id": "0x010103", + "level": 52, + "exp": 1500, + "is_boss": false + }, + { + "enemy_id": "0x010103", + "level": 52, + "exp": 1500, + "is_boss": false + }, + { + "enemy_id": "0x010103", + "level": 52, + "exp": 1500, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [0] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995010.json new file mode 100644 index 000000000..a91e1bb6d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995010.json @@ -0,0 +1,88 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Misguided Soldiers", + "quest_id": 20995010, + "base_level": 52, + "minimum_item_rank": 0, + "discoverable": false, + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1430 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 250 + }, + { + "type": "exp", + "amount": 1880 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 8033, + "num": 1 + }, + { + "item_id": 9363, + "num": 2 + }, + { + "item_id": 41, + "num": 2 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 84, + "group_id": 0 + }, + "enemies": [ + { + "enemy_id": "0x010311", + "level": 52, + "exp": 2500, + "is_boss": false + }, + { + "enemy_id": "0x010311", + "level": 52, + "exp": 2500, + "is_boss": false + }, + { + "enemy_id": "0x010311", + "level": 52, + "exp": 2500, + "is_boss": false + }, + { + "enemy_id": "0x010311", + "level": 52, + "exp": 2500, + "is_boss": false + } + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [0] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995011.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995011.json new file mode 100644 index 000000000..cb58167e8 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995011.json @@ -0,0 +1,98 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Shadow That Roams the Dead City", + "quest_id": 20995011, + "base_level": 55, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MergodaRuins", + "rewards": [ + { + "type": "exp", + "amount": 2540 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1810 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 290 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 7886, + "num": 1 + }, + { + "item_id": 7822, + "num": 2 + }, + { + "item_id": 7555, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 84, + "group_id": 9 + }, + "enemies": [ + { + "enemy_id": "0x015102", + "level": 55, + "exp": 21000, + "is_boss": true + }, + { + "enemy_id": "0x015102", + "level": 55, + "exp": 21000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 63, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Theodor", + "message_id": 11372 + }, + { + "type": "SeekOutEnemiesAtMarkedLocation", + "announce_type": "Accept", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 63, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "Theodor", + "message_id": 11842 + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q21000090.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q21000090.json new file mode 100644 index 000000000..f4e2dcb20 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q21000090.json @@ -0,0 +1,70 @@ +{ + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "A Place of Regrets", + "quest_id": 21000090, + "base_level": 65, + "minimum_item_rank": 0, + "discoverable": false, + "rewards": [ + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 2243 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 290 + }, + { + "type": "exp", + "amount": 10665 + }, + { + "type": "select", + "loot_pool": [ + { + "item_id": 11803, + "num": 3 + }, + { + "item_id": 7555, + "num": 3 + }, + { + "item_id": 9364, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 424, + "group_id": 2 + }, + "enemies": [ + { + "enemy_id": "0x015012", + "level": 65, + "exp": 45000, + "is_boss": true + } + ] + } + ], + "blocks": [ + { + "type": "DiscoverEnemy", + "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Accept", + "reset_group": false, + "groups": [0] + } + ] +} From 50c3ad80faa859fd9002c3d73a76e244dd56edd1 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 7 Sep 2024 00:35:39 -0700 Subject: [PATCH 042/116] Fix enemy drops checking the position index rather than the subgroup for their loot tables. --- Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs | 5 +++++ .../GatheringItems/InstanceDropItemManager.cs | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs index 38a7dc522..fdeb9ad40 100644 --- a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs +++ b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs @@ -127,4 +127,9 @@ public override void Clear() _EnemyData.Clear(); } } + + public ushort GetSubgroup(StageId stageId) + { + return _CurrentSubgroup.GetValueOrDefault(stageId); + } } diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs index 8f14779fa..517babda9 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs @@ -15,7 +15,8 @@ public InstanceDropItemManager(GameClient client) protected override List FetchAssetsFromRepository(StageId stage, uint setId) { - List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage, (byte) setId); + ushort currentSubGroup = _client.Party.InstanceEnemyManager.GetSubgroup(stage); + List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage, (byte)currentSubGroup); if(enemiesInSet != null && setId < enemiesInSet.Count) { Enemy enemy = enemiesInSet[(int) setId]; From 6c8bce63cfa331a9e34bd0e5bfa3c3b1e0d0df20 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 31 Aug 2024 13:43:33 -0400 Subject: [PATCH 043/116] feat: Add support for EXM Added support for EXM either solo or with pawns. --- .../Characters/CharacterManager.cs | 19 + .../Characters/ExmManager.cs | 244 +++++++++ .../Chat/Command/Commands/WarpCommand.cs | 8 +- Arrowgene.Ddon.GameServer/DdonGameServer.cs | 24 +- Arrowgene.Ddon.GameServer/GameStructure.cs | 2 +- .../InstanceQuestDropManager.cs | 16 +- .../CharacterDecideCharacterIdHandler.cs | 2 +- .../Handler/EntryBoardEntryBoardItemCreate.cs | 26 - .../EntryBoardEntryBoardItemCreateHandler.cs | 71 +++ .../EntryBoardEntryBoardItemForceStart.cs | 26 - ...tryBoardEntryBoardItemForceStartHandler.cs | 36 ++ .../EntryBoardEntryBoardItemInfoMyself.cs | 26 - ...tryBoardEntryBoardItemInfoMyselfHandler.cs | 27 + .../EntryBoardEntryBoardItemLeaveHandler.cs | 52 ++ .../EntryBoardEntryBoardItemReadyHandler.cs | 48 ++ .../Handler/EntryBoardEntryBoardList.cs | 26 - .../EntryBoardEntryBoardListHandler.cs | 46 ++ .../EntryBoardEntryItemInfoChangeHandler.cs | 43 ++ .../Handler/InstanceEnemyKillHandler.cs | 25 +- .../Handler/InstanceGetEnemySetListHandler.cs | 17 + .../Handler/PartyPartyCreateHandler.cs | 3 + .../Handler/PartyPartyJoinHandler.cs | 10 +- .../Handler/PartyPartyLeaveHandler.cs | 10 + ...uestGetAdventureGuideQuestNoticeHandler.cs | 20 +- ...> QuestGetCycleContentsNewsListHandler.cs} | 10 +- .../Handler/QuestGetEndContentsGroup.cs | 26 - .../QuestGetEndContentsGroupHandler.cs | 46 ++ .../QuestGetEndContentsRecruitListHandler.cs | 35 ++ .../Handler/QuestGetSetQuestListHandler.cs | 13 + .../Handler/QuestPlayEndHandler.cs | 54 ++ .../Handler/QuestPlayEntryHandler.cs | 28 + .../Handler/QuestPlayStartHandler.cs | 65 +++ .../Handler/QuestPlayStartTimerHandler.cs | 29 ++ .../Party/PartyQuestState.cs | 23 + .../Quests/GenericQuest.cs | 33 +- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 65 ++- .../Utils/RandomExtensions.cs | 21 + Arrowgene.Ddon.GameServer/Utils/UUIDv7.cs | 31 ++ Arrowgene.Ddon.Shared/Asset/QuestAsset.cs | 2 + .../AssetReader/QuestAssetDeserializer.cs | 116 ++++- .../Entity/EntitySerializer.cs | 68 ++- .../C2SEntryBoardEntryBoardItemCreateReq.cs | 42 ++ ...2SEntryBoardEntryBoardItemForceStartReq.cs | 29 ++ ...2SEntryBoardEntryBoardItemInfoChangeReq.cs | 37 ++ ...2SEntryBoardEntryBoardItemInfoMyselfReq.cs | 33 ++ .../C2SEntryBoardEntryBoardItemLeaveReq.cs | 29 ++ .../C2SEntryBoardEntryBoardItemReadyReq.cs | 29 ++ .../C2SEntryBoardEntryBoardListReq.cs | 34 ++ .../C2SQuestGetAdventureGuideQuestNtcReq.cs | 23 + .../C2SQuestGetEndContentsGroupReq.cs | 26 + .../C2SQuestGetEndContentsRecruitListReq.cs | 23 + .../PacketStructure/C2SQuestPlayEndReq.cs | 24 + .../PacketStructure/C2SQuestPlayEntryReq.cs | 24 + .../C2SQuestPlayStartTimerReq.cs | 26 + .../PacketStructure/C2SQuestPlayerStartReq.cs | 29 ++ .../S2CDispelGetDispelItemSettingsRes.cs | 3 - ...EntryBoardEntryBoardItemChangeMemberNtc.cs | 38 ++ .../S2CEntryBoardEntryBoardItemCreateRes.cs | 40 ++ ...2CEntryBoardEntryBoardItemForceStartRes.cs | 31 ++ ...2CEntryBoardEntryBoardItemInfoChangeNtc.cs | 38 ++ ...2CEntryBoardEntryBoardItemInfoChangeRes.cs | 35 ++ ...2CEntryBoardEntryBoardItemInfoMyselfRes.cs | 39 ++ .../S2CEntryBoardEntryBoardItemLeaveNtc.cs | 35 ++ .../S2CEntryBoardEntryBoardItemLeaveRes.cs | 31 ++ .../S2CEntryBoardEntryBoardItemReadyNtc.cs | 37 ++ .../S2CEntryBoardEntryBoardItemReadyRes.cs | 31 ++ .../S2CEntryBoardEntryBoardItemReserveNtc.cs | 37 ++ .../S2CEntryBoardEntryBoardListRes.cs | 36 ++ .../S2CQuestGetAdventureGuideQuestNtcRes.cs | 36 ++ .../S2CQuestGetEndContentsGroupRes.cs | 43 ++ .../S2CQuestGetEndContentsRecruitListRes.cs | 41 ++ .../PacketStructure/S2CQuestPlayEndNtc.cs | 37 ++ .../PacketStructure/S2CQuestPlayEndRes.cs | 31 ++ .../PacketStructure/S2CQuestPlayEntryNtc.cs | 33 ++ .../PacketStructure/S2CQuestPlayEntryRes.cs | 31 ++ .../S2CQuestPlayStartTimerNtc.cs | 33 ++ .../S2CQuestPlayStartTimerRes.cs | 32 ++ .../PacketStructure/S2CQuestPlayerStartRes.cs | 31 ++ .../S2CQuestTimeGainQuestPlayStartNtc.cs | 42 ++ .../PacketStructure/S2CSeason62_26_16Ntc.cs | 39 ++ .../PacketStructure/S2CSituationDataEndNtc.cs | 34 ++ .../S2CSituationDataStartNtc.cs | 28 + .../S2CSituationDataUpdateObjectivesNtc.cs | 52 ++ .../PacketStructure/S2C_63_10_16_NTC.cs | 41 ++ .../Entity/PacketStructure/S2C_63_7_16_NTC.cs | 63 +++ .../Structure/CDataCharacterListElement.cs | 2 +- .../Entity/Structure/CDataCommonU64.cs | 35 ++ .../Entity/Structure/CDataContentsPlayEnd.cs | 51 ++ .../Structure/CDataContentsPlayStartData.cs | 70 +++ .../Structure/CDataEntryBoardListParam.cs | 46 ++ .../Entity/Structure/CDataEntryItem.cs | 51 ++ .../Entity/Structure/CDataEntryItemParam.cs | 64 +++ .../Entity/Structure/CDataEntryMemberData.cs | 39 ++ .../Entity/Structure/CDataEntryRecruitData.cs | 36 ++ .../Entity/Structure/CDataEntryRecruitJob.cs | 29 ++ .../Entity/Structure/CDataQuestList.cs | 2 - .../Structure/CDataQuestRecruitListItem.cs | 36 ++ .../Entity/Structure/CDataRewardItemDetail.cs | 29 ++ .../Structure/CDataSituationObjective.cs | 37 ++ .../Structure/CDataTimeGainQuestList.cs | 65 +++ .../CDataTimeGainQuestRestrictions.cs | 49 ++ .../Structure/CDataTimeGainQuestUnk1Unk2.cs | 34 ++ .../Structure/CDataTimeGainQuestUnk2.cs | 37 ++ .../Files/Assets/EnemySpawn.json | 224 -------- .../Files/Assets/quests/q00000030.json | 4 +- .../Files/Assets/quests/q50101020.json | 492 ++++++++++++++++++ .../Files/Assets/quests/q50102020.json | 307 +++++++++++ .../Files/Assets/quests/q50103020.json | 301 +++++++++++ .../Files/Assets/quests/q50104000.json | 253 +++++++++ .../Files/Assets/quests/q50201000.json | 69 +++ .../Files/Assets/quests/q50202000.json | 297 +++++++++++ .../Files/Assets/quests/q50203000.json | 69 +++ .../Files/Assets/quests/q50204002.json | 77 +++ Arrowgene.Ddon.Shared/Model/ContentsType.cs | 20 + Arrowgene.Ddon.Shared/Model/Enemy.cs | 2 + Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs | 3 + .../Model/Quest/QuestBlock.cs | 10 + .../Model/Quest/QuestMissionParams.cs | 29 ++ .../Model/Quest/QuestType.cs | 3 +- Arrowgene.Ddon.Shared/Network/PacketId.cs | 44 +- docs/quests/images/end_contents_update.png | Bin 0 -> 292273 bytes .../quests/images/general_announce_type_0.png | Bin 0 -> 25560 bytes docs/quests/images/stage_announce_clear.png | Bin 0 -> 214498 bytes docs/quests/quest_command_reference.md | 19 +- 124 files changed, 5472 insertions(+), 441 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Characters/ExmManager.cs delete mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreate.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs delete mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStart.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs delete mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyself.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyselfHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs delete mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardList.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryItemInfoChangeHandler.cs rename Arrowgene.Ddon.GameServer/Handler/{QuestGetCycleContentsNewsList.cs => QuestGetCycleContentsNewsListHandler.cs} (66%) delete mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsGroup.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsGroupHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsRecruitListHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestPlayEntryHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Utils/RandomExtensions.cs create mode 100644 Arrowgene.Ddon.GameServer/Utils/UUIDv7.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemCreateReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemForceStartReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInfoChangeReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInfoMyselfReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemLeaveReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemReadyReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardListReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetAdventureGuideQuestNtcReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetEndContentsGroupReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetEndContentsRecruitListReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEndReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEntryReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayStartTimerReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayerStartReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemChangeMemberNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemCreateRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemForceStartRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoChangeNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoChangeRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoMyselfRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReadyNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReadyRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReserveNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardListRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetAdventureGuideQuestNtcRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetEndContentsGroupRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetEndContentsRecruitListRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEndNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEndRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayStartTimerNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayStartTimerRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayerStartRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestTimeGainQuestPlayStartNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSeason62_26_16Ntc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataEndNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataStartNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataUpdateObjectivesNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_10_16_NTC.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_7_16_NTC.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataCommonU64.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataContentsPlayEnd.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataContentsPlayStartData.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardListParam.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryItem.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryItemParam.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryMemberData.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryRecruitData.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryRecruitJob.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataQuestRecruitListItem.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardItemDetail.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataSituationObjective.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestList.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestRestrictions.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestUnk1Unk2.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestUnk2.cs create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json create mode 100644 Arrowgene.Ddon.Shared/Model/ContentsType.cs create mode 100644 Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs create mode 100644 docs/quests/images/end_contents_update.png create mode 100644 docs/quests/images/general_announce_type_0.png create mode 100644 docs/quests/images/stage_announce_clear.png diff --git a/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs b/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs index e3dc22473..63277512b 100644 --- a/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs @@ -1,6 +1,7 @@ using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; using Arrowgene.Logging; using System.Linq; @@ -83,6 +84,24 @@ private void SelectPawns(Character character) } } + public void UpdateOnlineStatus(GameClient client, Character character, OnlineStatus onlineStatus) + { + client.Character.OnlineStatus = onlineStatus; + var charUpdateNtc = new S2CCharacterCommunityCharacterStatusUpdateNtc(); + charUpdateNtc.UpdateCharacterList.Add(ContactListManager.CharacterToListEml(client.Character)); + charUpdateNtc.UpdateMatchingProfileList.Add(new CDataUpdateMatchingProfileInfo() + { + CharacterId = client.Character.CharacterId, + Comment = client.Character.MatchingProfile.Comment, + }); + + // TODO: Is there a reduced set of clients we can send this to? + foreach (var memberClient in _Server.ClientLookup.GetAll()) + { + memberClient.Send(charUpdateNtc); + } + } + public uint GetMaxAugmentAllocation(CharacterCommon character) { return CharacterManager.BASE_ABILITY_COST_AMOUNT + character.ExtendedParams.AbilityCost; diff --git a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs new file mode 100644 index 000000000..ae5d3697d --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs @@ -0,0 +1,244 @@ +using Arrowgene.Ddon.GameServer.Quests; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Model.Quest; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.GameServer.Characters +{ + public class ExmManager + { + private DdonGameServer _Server; + + private Dictionary _ContentData; + private Dictionary _CharacterIdToContentId; + private Dictionary> _ContentIdToCharacterIds; + private Dictionary _ContentIdToQuest; + + public ExmManager(DdonGameServer server) + { + _Server = server; + _ContentData = new Dictionary(); + _CharacterIdToContentId = new Dictionary(); + _ContentIdToCharacterIds = new Dictionary>(); + _ContentIdToQuest = new Dictionary(); + } + + public bool HasContentId(ulong contentId) + { + lock (_ContentData) + { + return _ContentData.ContainsKey(contentId); + } + } + + public List GetListOfRecruitingContent() + { + lock (_ContentData) + { + return _ContentData.Values.ToList(); + } + } + + public uint NumGroupsRecruitingForContent(uint questId) + { + // TODO: Implement mechanism to search for this + return 0; + } + + public uint NumGroupsRecruitingForContent(QuestId questId) + { + return NumGroupsRecruitingForContent((uint)questId); + } + + public bool CreateGroupForContent(ulong id, CDataEntryItem entryItem) + { + lock (_ContentData) + { + if (_ContentData.ContainsKey(id)) + { + // Group is already registered + return false; + } + + _ContentData[id] = entryItem; + + return true; + } + } + + public CDataEntryItem GetEntryItemDataForContent(ulong id) + { + lock (_ContentData) + { + if (!_ContentData.ContainsKey(id)) + { + return null; + } + return _ContentData[id]; + } + } + + public CDataEntryItem GetEntryItemDataForCharacter(uint characterId) + { + lock (_ContentData) + { + ulong id = _CharacterIdToContentId[characterId]; + return GetEntryItemDataForContent(id); + } + } + + public CDataEntryItem GetEntryItemDataForCharacter(Character character) + { + return GetEntryItemDataForCharacter(character.CharacterId); + } + + public bool RemoveGroupForContent(ulong id) + { + lock (_ContentData) + { + if (!_ContentData.ContainsKey(id)) + { + return false; + } + + if (_ContentIdToCharacterIds.ContainsKey(id)) + { + foreach (var characterId in _ContentIdToCharacterIds[id]) + { + _CharacterIdToContentId.Remove(characterId); + } + _ContentIdToCharacterIds.Remove(id); + } + _ContentIdToQuest.Remove(id); + + return _ContentData.Remove(id); + } + } + + public ulong GetContentIdForCharacter(uint characterId) + { + lock (_ContentData) + { + if (!_CharacterIdToContentId.ContainsKey(characterId)) + { + return 0; + } + return _CharacterIdToContentId[characterId]; + } + } + + public ulong GetContentIdForCharacter(Character character) + { + return GetContentIdForCharacter(character.CharacterId); + } + + public List GetCharacterIdsForContent(ulong id) + { + lock (_ContentData) + { + if (!_ContentIdToCharacterIds.ContainsKey(id)) + { + return new List(); + } + return _ContentIdToCharacterIds[id].ToList(); + } + } + + public bool AddCharacterToContentGroup(ulong id, uint characterId) + { + lock (_ContentData) + { + if (!_ContentData.ContainsKey(id)) + { + return false; + } + + _CharacterIdToContentId[characterId] = id; + if (!_ContentIdToCharacterIds.ContainsKey(id)) + { + _ContentIdToCharacterIds[id] = new HashSet(); + } + + return _ContentIdToCharacterIds[id].Add(characterId); + } + } + + public bool AddCharacterToContentGroup(ulong id, Character character) + { + return AddCharacterToContentGroup(id, character.CharacterId); + } + + public bool RemoveCharacterFromContentGroup(uint characterId) + { + lock (_ContentData) + { + if (!_ContentIdToCharacterIds.ContainsKey(characterId)) + { + return false; + } + + ulong id = _CharacterIdToContentId[characterId]; + _CharacterIdToContentId.Remove(characterId); + + if (!_ContentIdToCharacterIds.ContainsKey(id)) + { + return true; + } + + _ContentIdToCharacterIds[id].Remove(characterId); + + if (_ContentIdToCharacterIds[id].Count == 0) + { + RemoveGroupForContent(id); + } + } + + return true; + } + + public bool RemoveCharacterFromContentGroup(Character character) + { + return RemoveCharacterFromContentGroup(character.CharacterId); + } + + public bool AddQuestToContent(ulong id, Quest quest) + { + lock (_ContentData) + { + _ContentIdToQuest[id] = quest; + return true; + } + } + + public bool RemoveQuestFromContent(ulong id) + { + lock (_ContentData) + { + if (!_ContentIdToQuest.ContainsKey(id)) + { + return false; + } + return _ContentIdToQuest.Remove(id); + } + } + + public Quest GetQuestForContent(ulong id) + { + lock (_ContentData) + { + if (!_ContentIdToQuest.ContainsKey(id)) + { + return null; + } + return _ContentIdToQuest[id]; + } + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/WarpCommand.cs b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/WarpCommand.cs index c9f413fc3..34b0cbc68 100644 --- a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/WarpCommand.cs +++ b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/WarpCommand.cs @@ -3,6 +3,7 @@ using Arrowgene.Ddon.Shared.Asset; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; using Arrowgene.Ddon.Shared.Model.Quest; using System; using System.Collections.Generic; @@ -73,12 +74,7 @@ public override void Execute(string[] command, GameClient client, ChatMessage me } var questResultCommands = new List() { - new CDataQuestCommand() - { - Command = (ushort)QuestResultCommand.StageJump, - Param01 = stageNo, - Param02 = startingLocation - } + QuestManager.ResultCommand.StageJump((StageNo) stageNo, startingLocation) }; //Send fake progress update to trigger the warp command. diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index 445425800..dbd3198b6 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -76,6 +76,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi HubManager = new HubManager(this); GpCourseManager = new GpCourseManager(this); WeatherManager = new WeatherManager(this); + ExmManager = new ExmManager(this); // Orb Management is slightly complex and requires updating fields across multiple systems OrbUnlockManager = new OrbUnlockManager(database, WalletManager, JobManager, CharacterManager); @@ -106,6 +107,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public GameRouter Router { get; } public GpCourseManager GpCourseManager { get; } public WeatherManager WeatherManager { get; } + public ExmManager ExmManager { get; } public ChatLogHandler ChatLogHandler { get; } @@ -492,8 +494,8 @@ private void LoadPacketHandler() AddHandler(new QuestGetTutorialQuestListHandler(this)); AddHandler(new QuestGetWorldManageQuestListHandler(this)); AddHandler(new QuestLeaderQuestProgressRequestHandler(this)); - AddHandler(new QuestGetEndContentsGroup(this)); - AddHandler(new QuestGetCycleContentsNewsList(this)); + AddHandler(new QuestGetEndContentsGroupHandler(this)); + AddHandler(new QuestGetCycleContentsNewsListHandler(this)); AddHandler(new QuestQuestOrderHandler(this)); AddHandler(new QuestQuestProgressHandler(this)); AddHandler(new QuestSendLeaderQuestOrderConditionInfoHandler(this)); @@ -507,11 +509,19 @@ private void LoadPacketHandler() AddHandler(new QuestCancelHandler(this)); AddHandler(new QuestGetPartyBonusListHandler(this)); AddHandler(new QuestGetMobHuntQuestListHandler(this)); - - AddHandler(new EntryBoardEntryBoardList(this)); - AddHandler(new EntryBoardEntryBoardItemCreate(this)); - AddHandler(new EntryBoardEntryBoardItemForceStart(this)); - AddHandler(new EntryBoardEntryBoardItemInfoMyself(this)); + AddHandler(new QuestPlayEntryHandler(this)); + AddHandler(new QuestPlayStartHandler(this)); + AddHandler(new QuestPlayStartTimerHandler(this)); + AddHandler(new QuestPlayEndHandler(this)); + AddHandler(new QuestGetEndContentsRecruitListHandler(this)); + + AddHandler(new EntryBoardEntryBoardListHandler(this)); + AddHandler(new EntryBoardEntryBoardItemCreateHandler(this)); + AddHandler(new EntryBoardEntryBoardItemForceStartHandler(this)); + AddHandler(new EntryBoardEntryBoardItemInfoMyselfHandler(this)); + AddHandler(new EntryBoardEntryBoardItemReadyHandler(this)); + AddHandler(new EntryBoardEntryBoardItemLeaveHandler(this)); + AddHandler(new EntryBoardEntryItemInfoChangeHandler(this)); AddHandler(new ServerGameTimeGetBaseinfoHandler(this)); AddHandler(new ServerGetGameSettingHandler(this)); diff --git a/Arrowgene.Ddon.GameServer/GameStructure.cs b/Arrowgene.Ddon.GameServer/GameStructure.cs index cb47d78ac..5ce7db074 100644 --- a/Arrowgene.Ddon.GameServer/GameStructure.cs +++ b/Arrowgene.Ddon.GameServer/GameStructure.cs @@ -38,7 +38,7 @@ private static void CDataCharacterListElement_common( CDataJobBaseInfo(cDataCharacterListElement.EntryJobBaseInfo, character.Job, (byte)character.ActiveCharacterJobData.Lv); cDataCharacterListElement.MatchingProfile = ""; - cDataCharacterListElement.unk2 = 0; + cDataCharacterListElement.unk2 = 1; } public static void CDataCommunityCharacterBaseInfo( diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs index 6cc2f5c9d..6e4d2e848 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceQuestDropManager.cs @@ -1,6 +1,8 @@ +using Arrowgene.Ddon.GameServer.Quests; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Logging; using System.Collections.Generic; using System.Linq; @@ -55,10 +57,14 @@ public List FetchEnemyLoot() return new List(); } - public List GenerateEnemyLoot(InstancedEnemy enemy, CDataStageLayoutId layoutId, uint setId) - { - uint dropEntryId = GetDropId(layoutId, setId); + public List GenerateEnemyLoot(Quest quest, InstancedEnemy enemy, CDataStageLayoutId layoutId, uint setId) + { + if (quest.QuestType == QuestType.ExtremeMission) + { + return new List(); + } + uint dropEntryId = GetDropId(layoutId, setId); if (!QuestEnemyDropsTable.ContainsKey(dropEntryId)) { // Check to see if a drop table exists @@ -72,8 +78,8 @@ public List GenerateEnemyLoot(InstancedEnemy enemy, CDat return items; } } - // Return an empty list of gathering items for no loot. - return new List(); + // Return an empty list of gathering items for no loot. + return new List(); } public void Clear() diff --git a/Arrowgene.Ddon.GameServer/Handler/CharacterDecideCharacterIdHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CharacterDecideCharacterIdHandler.cs index 3e1b37e01..312381073 100644 --- a/Arrowgene.Ddon.GameServer/Handler/CharacterDecideCharacterIdHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/CharacterDecideCharacterIdHandler.cs @@ -87,7 +87,7 @@ public override void Handle(GameClient client, StructurePacket - { - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemCreate)); - - - public EntryBoardEntryBoardItemCreate(DdonGameServer server) : base(server) - { - } - - public override PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_CREATE_REQ; - - public override void Handle(GameClient client, IPacket packet) - { - client.Send(GameFull.Dump_710); - } - } -} \ No newline at end of file diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs new file mode 100644 index 000000000..aae2ffa88 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs @@ -0,0 +1,71 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.GameServer.Dump; +using Arrowgene.Ddon.GameServer.Utils; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; +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; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemCreateHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemCreateHandler)); + + public EntryBoardEntryBoardItemCreateHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemCreateRes Handle(GameClient client, C2SEntryBoardEntryBoardItemCreateReq request) + { + // var pcap = new S2CEntryBoardEntryBoardItemCreateRes.Serializer().Read(GameFull.Dump_710.AsBuffer()); + var result = new S2CEntryBoardEntryBoardItemCreateRes() + { + BoardId = request.BoardId, + }; + + result.EntryItem.Param = request.CreateParam; + result.EntryItem.PartyLeaderCharacterId = client.Character.CharacterId; + result.EntryItem.TimeOut = 3600; + result.EntryItem.Id = Random.Shared.NextU32(); + result.EntryItem.Param.MinEntryNum = 1; + + var member = new CDataEntryMemberData() + { + EntryFlag = false, + Id = 1, + }; + GameStructure.CDataCharacterListElement(member.CharacterListElement, client.Character); + result.EntryItem.EntryMemberList.Add(member); + + for (int i = 2; i < request.CreateParam.MaxEntryNum + 1; i++) + { + result.EntryItem.EntryMemberList.Add(new CDataEntryMemberData() + { + EntryFlag = false, + Id = (ushort) i + }); + } + + // Everything went well, so store the state in the ExmManager + Server.ExmManager.CreateGroupForContent(request.BoardId, result.EntryItem); + Server.ExmManager.AddCharacterToContentGroup(request.BoardId, client.Character); + + S2CEntryBoardEntryBoardItemChangeMemberNtc ntc = new S2CEntryBoardEntryBoardItemChangeMemberNtc() + { + EntryFlag = true, + MemberData = member, + }; + client.Send(ntc); + + Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.EntryBoard); + + return result; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStart.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStart.cs deleted file mode 100644 index 26e555315..000000000 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStart.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Arrowgene.Buffers; -using Arrowgene.Ddon.GameServer.Dump; -using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; -using Arrowgene.Ddon.Shared.Network; -using Arrowgene.Logging; - -namespace Arrowgene.Ddon.GameServer.Handler -{ - public class EntryBoardEntryBoardItemForceStart : PacketHandler - { - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemForceStart)); - - - public EntryBoardEntryBoardItemForceStart(DdonGameServer server) : base(server) - { - } - - public override PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_REQ; - - public override void Handle(GameClient client, IPacket packet) - { - client.Send(GameFull.Dump_711); - } - } -} \ No newline at end of file diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs new file mode 100644 index 000000000..0f72929f5 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs @@ -0,0 +1,36 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.GameServer.Dump; +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.Networking.Tcp.Consumer.BlockingQueueConsumption; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemForceStartHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemForceStartHandler)); + + public EntryBoardEntryBoardItemForceStartHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemForceStartRes Handle(GameClient client, C2SEntryBoardEntryBoardItemForceStartReq request) + { + // var pcap = new S2CEntryBoardEntryBoardItemForceStartRes.Serializer().Read(GameFull.Dump_711.AsBuffer()); + var data = Server.ExmManager.GetEntryItemDataForCharacter(client.Character); + + // ALlows the menu to transition + var ntc = new S2CEntryBoardEntryBoardItemReadyNtc() + { + MaxMember = data.Param.MaxEntryNum, + TimeOut = data.TimeOut + }; + client.Send(ntc); + + return new S2CEntryBoardEntryBoardItemForceStartRes(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyself.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyself.cs deleted file mode 100644 index e7c87369e..000000000 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyself.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Arrowgene.Buffers; -using Arrowgene.Ddon.GameServer.Dump; -using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; -using Arrowgene.Ddon.Shared.Network; -using Arrowgene.Logging; - -namespace Arrowgene.Ddon.GameServer.Handler -{ - public class EntryBoardEntryBoardItemInfoMyself : PacketHandler - { - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemInfoMyself)); - - - public EntryBoardEntryBoardItemInfoMyself(DdonGameServer server) : base(server) - { - } - - public override PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_MYSELF_REQ; - - public override void Handle(GameClient client, IPacket packet) - { - client.Send(GameFull.Dump_712); - } - } -} \ No newline at end of file diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyselfHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyselfHandler.cs new file mode 100644 index 000000000..e1555239d --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyselfHandler.cs @@ -0,0 +1,27 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Logging; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemInfoMyselfHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemInfoMyselfHandler)); + + public EntryBoardEntryBoardItemInfoMyselfHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemInfoMyselfRes Handle(GameClient client, C2SEntryBoardEntryBoardItemInfoMyselfReq request) + { + // var pcap = new S2CEntryBoardEntryBoardItemInfoMyselfRes.Serializer().Read(GameFull.Dump_712.AsBuffer()); + var result = new S2CEntryBoardEntryBoardItemInfoMyselfRes() + { + ContentId = Server.ExmManager.GetContentIdForCharacter(client.Character), + EntryItem = Server.ExmManager.GetEntryItemDataForCharacter(client.Character) + }; + + return result; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs new file mode 100644 index 000000000..e2cf1e687 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs @@ -0,0 +1,52 @@ +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.GameServer.Dump; +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.Model.Quest; +using Arrowgene.Logging; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemLeaveHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemLeaveHandler)); + + public EntryBoardEntryBoardItemLeaveHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemLeaveRes Handle(GameClient client, C2SEntryBoardEntryBoardItemLeaveReq request) + { + var contentId = Server.ExmManager.GetContentIdForCharacter(client.Character); + var data = Server.ExmManager.GetEntryItemDataForContent(contentId); + var characterIds = Server.ExmManager.GetCharacterIdsForContent(contentId); + + if (data.PartyLeaderCharacterId == client.Character.CharacterId) + { + Server.ExmManager.RemoveGroupForContent(contentId); + foreach (var characterId in characterIds) + { + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + if (memberClient != null) + { + memberClient.Send(new S2CEntryBoardEntryBoardItemLeaveNtc()); + } + } + } + else + { + // TODO: This might be wrong + client.Send(new S2CEntryBoardEntryBoardItemLeaveNtc()); + var leaderClient = Server.ClientLookup.GetClientByCharacterId(data.PartyLeaderCharacterId); + leaderClient.Send(new S2CEntryBoardEntryBoardItemLeaveNtc()); + } + + return new S2CEntryBoardEntryBoardItemLeaveRes(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs new file mode 100644 index 000000000..d540b9740 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs @@ -0,0 +1,48 @@ +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.GameServer.Party; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Logging; +using System.IO; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemReadyHandler : GameStructurePacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemReadyHandler)); + + public EntryBoardEntryBoardItemReadyHandler(DdonGameServer server) : base(server) + { + } + + public override void Handle(GameClient client, StructurePacket request) + { + var data = Server.ExmManager.GetEntryItemDataForCharacter(client.Character); + var contentId = Server.ExmManager.GetContentIdForCharacter(client.Character); + var members = Server.ExmManager.GetCharacterIdsForContent(contentId); + + var ntc = new S2CEntryBoardEntryBoardItemReserveNtc() + { + NowMember = (uint) members.Count, + MaxMember = data.Param.MaxEntryNum, + }; + client.Send(ntc); + + client.Send(new S2CEntryBoardEntryBoardItemReadyRes()); + + PartyGroup party = Server.PartyManager.NewParty(); + ErrorRes host = party.AddHost(client); + + S2CPartyPartyInviteAcceptNtc inviteAcceptNtc = new S2CPartyPartyInviteAcceptNtc(); + inviteAcceptNtc.ServerId = (ushort)Server.Id; + inviteAcceptNtc.PartyId = party.Id; + inviteAcceptNtc.StageId = client.Character.Stage.Id; + inviteAcceptNtc.PositionId = 0; + inviteAcceptNtc.MemberIndex = 0; + client.Send(inviteAcceptNtc); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardList.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardList.cs deleted file mode 100644 index ffe095bdd..000000000 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardList.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Arrowgene.Buffers; -using Arrowgene.Ddon.GameServer.Dump; -using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; -using Arrowgene.Ddon.Shared.Network; -using Arrowgene.Logging; - -namespace Arrowgene.Ddon.GameServer.Handler -{ - public class EntryBoardEntryBoardList : PacketHandler - { - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardList)); - - - public EntryBoardEntryBoardList(DdonGameServer server) : base(server) - { - } - - public override PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_LIST_REQ; - - public override void Handle(GameClient client, IPacket packet) - { - client.Send(GameFull.Dump_709); - } - } -} \ No newline at end of file diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs new file mode 100644 index 000000000..2ac30c35e --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs @@ -0,0 +1,46 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.GameServer.Dump; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Logging; +using System.Linq; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardListHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardListHandler)); + + + public EntryBoardEntryBoardListHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardListRes Handle(GameClient client, C2SEntryBoardEntryBoardListReq request) + { + // var result = new S2CEntryBoardEntryBoardListRes.Serializer().Read(GameFull.Dump_709.AsBuffer()); + + var result = new S2CEntryBoardEntryBoardListRes(); + foreach (var contentId in request.Unk0List.Select(x => x.Value).ToList()) + { + if (Server.ExmManager.HasContentId(contentId)) + { + var data = Server.ExmManager.GetEntryItemDataForContent(contentId); + var contentParams = new CDataEntryBoardListParam() + { + EntryId = contentId, + SortieMin = 1, + NoPartyMembers = (ushort) data.EntryMemberList.Count, + TimeOut = 3600, // TODO: Figure this out from some config? + }; + result.EntryList.Add(contentParams); + } + } + + return result; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryItemInfoChangeHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryItemInfoChangeHandler.cs new file mode 100644 index 000000000..28ae0d4f6 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryItemInfoChangeHandler.cs @@ -0,0 +1,43 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.GameServer.Dump; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Logging; +using System.Linq; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryItemInfoChangeHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryItemInfoChangeHandler)); + + + public EntryBoardEntryItemInfoChangeHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemInfoChangeRes Handle(GameClient client, C2SEntryBoardEntryBoardItemInfoChangeReq request) + { + var data = Server.ExmManager.GetEntryItemDataForCharacter(client.Character); + data.Param = request.Param; + // TODO: How to save password? + // request.Password + + var ntc = new S2CEntryBoardEntryBoardItemInfoChangeNtc() + { + BoardId = Server.ExmManager.GetContentIdForCharacter(client.Character), + EntryItemData = data + }; + // TODO: Does this need to be sent to everyone in the server? + client.Party.SendToAllExcept(ntc, client); + + return new S2CEntryBoardEntryBoardItemInfoChangeRes() + { + EntryItemData = data + }; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 6e1763c06..065a70e5b 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -6,6 +6,7 @@ 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.Network; using Arrowgene.Logging; using System; @@ -57,13 +58,33 @@ public override void Handle(GameClient client, StructurePacket 0 && enemyKilled.RepopNum < enemyKilled.RepopCount) + { + enemyKilled.RepopNum += 1; + + S2CInstanceEnemyRepopNtc repopNtc = new S2CInstanceEnemyRepopNtc() + { + LayoutId = layoutId, + WaitSecond = enemyKilled.RepopWaitSecond, + EnemyData = new CDataLayoutEnemyData() + { + PositionIndex = (byte) packet.Structure.SetId, + EnemyInfo = enemyKilled.asCDataStageLayoutEnemyPresetEnemyInfoClient() + } + }; + client.Send(repopNtc); + } + else + { + enemyKilled.IsKilled = true; + } + foreach (var partyMemberClient in client.Party.Clients) { // If the enemy is quest controlled, then either get from the quest loot drop, or the general one. List instancedGatheringItems = IsQuestControlled ? - partyMemberClient.InstanceQuestDropManager.GenerateEnemyLoot(enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId) : + partyMemberClient.InstanceQuestDropManager.GenerateEnemyLoot(quest, enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId) : partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, packet.Structure.SetId); // If the roll was unlucky, there is a chance that no bag will show. diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs index 480919ec7..d5ee2830f 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs @@ -5,6 +5,7 @@ using Arrowgene.Ddon.Shared.Crypto; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using System.Linq; @@ -46,6 +47,7 @@ public override void Handle(GameClient client, StructurePacket 0 && response.EnemyList.Count > 0) { S2CInstanceEnemySubGroupAppearNtc subgroupNtc = new S2CInstanceEnemySubGroupAppearNtc() diff --git a/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs index 6d1509f33..6b8955077 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs @@ -79,6 +79,9 @@ public override void Handle(GameClient client, StructurePacket packet) { - S2CPartyPartyJoinRes res = new S2CPartyPartyJoinRes(); + S2CPartyPartyJoinRes res = new S2CPartyPartyJoinRes() + { + ContentNumber = Server.ExmManager.GetContentIdForCharacter(client.Character) + }; + + if (res.ContentNumber != 0) + { + Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.Contents); + } PartyGroup party = Server.PartyManager.GetParty(packet.Structure.PartyId); if (party == null) diff --git a/Arrowgene.Ddon.GameServer/Handler/PartyPartyLeaveHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PartyPartyLeaveHandler.cs index 7765b2413..886be951e 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PartyPartyLeaveHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PartyPartyLeaveHandler.cs @@ -1,6 +1,9 @@ +using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Party; 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; @@ -25,6 +28,13 @@ public override void Handle(GameClient client, StructurePacket + public class QuestGetAdventureGuideQuestNoticeHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetAdventureGuideQuestNoticeHandler)); - public QuestGetAdventureGuideQuestNoticeHandler(DdonGameServer server) : base(server) { } - public override PacketId Id => PacketId.C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_NOTICE_REQ; - - public override void Handle(GameClient client, IPacket packet) + public override S2CQuestGetAdventureGuideQuestNtcRes Handle(GameClient client, C2SQuestGetAdventureGuideQuestNtcReq request) { - //client.Send(GameFull.Dump_162); - - IBuffer buffer = new StreamBuffer(); - buffer.WriteInt32(0); // error - buffer.WriteInt32(0); // result - buffer.WriteBool(false); // notice - client.Send(new Packet(PacketId.S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NOTICE_RES, buffer.GetAllBytes())); + return new S2CQuestGetAdventureGuideQuestNtcRes(); } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsNewsList.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsNewsListHandler.cs similarity index 66% rename from Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsNewsList.cs rename to Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsNewsListHandler.cs index f32690224..f05cc81bf 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsNewsList.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsNewsListHandler.cs @@ -1,4 +1,4 @@ -using Arrowgene.Buffers; +using Arrowgene.Buffers; using Arrowgene.Ddon.GameServer.Dump; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; @@ -7,12 +7,12 @@ namespace Arrowgene.Ddon.GameServer.Handler { - public class QuestGetCycleContentsNewsList : PacketHandler + public class QuestGetCycleContentsNewsListHandler : PacketHandler { - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetCycleContentsNewsList)); + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetCycleContentsNewsListHandler)); - public QuestGetCycleContentsNewsList(DdonGameServer server) : base(server) + public QuestGetCycleContentsNewsListHandler(DdonGameServer server) : base(server) { } @@ -23,4 +23,4 @@ public override void Handle(GameClient client, IPacket packet) client.Send(GameFull.Dump_708); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsGroup.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsGroup.cs deleted file mode 100644 index be44c5adb..000000000 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsGroup.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Arrowgene.Buffers; -using Arrowgene.Ddon.GameServer.Dump; -using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; -using Arrowgene.Ddon.Shared.Network; -using Arrowgene.Logging; - -namespace Arrowgene.Ddon.GameServer.Handler -{ - public class QuestGetEndContentsGroup : PacketHandler - { - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetEndContentsGroup)); - - - public QuestGetEndContentsGroup(DdonGameServer server) : base(server) - { - } - - public override PacketId Id => PacketId.C2S_QUEST_GET_END_CONTENTS_GROUP_REQ; - - public override void Handle(GameClient client, IPacket packet) - { - client.Send(GameFull.Dump_707); - } - } -} \ No newline at end of file diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsGroupHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsGroupHandler.cs new file mode 100644 index 000000000..45b7b127e --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsGroupHandler.cs @@ -0,0 +1,46 @@ +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.GameServer.Dump; +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.Model.Quest; +using Arrowgene.Logging; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class QuestGetEndContentsGroupHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetEndContentsGroupHandler)); + + public QuestGetEndContentsGroupHandler(DdonGameServer server) : base(server) + { + } + + public override S2CQuestGetEndContentsGroupRes Handle(GameClient client, C2SQuestGetEndContentsGroupReq request) + { + // var pcap0 = new S2CQuestGetEndContentsGroupRes.Serializer().Read(pcap0_data); + var results = new S2CQuestGetEndContentsGroupRes() + { + ContentsType = ContentsType.End, + GroupId = request.GroupId + }; + + var quests = QuestManager.GetQuestsByType(QuestType.ExtremeMission).Where(x => x.Value.MissionParams.Group == request.GroupId); + foreach (var (questId, quest) in quests) + { + var entry = quest.ToCDataTimeGainQuestList(0); + results.TimeGainQuestList.Add(entry); + } + + return results; + } + + private static readonly byte[] pcap0_data = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD1, 0x04, 0x03, 0x01, 0x0B, 0x08, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xCE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xE7, 0x98, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x21, 0xE0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE2, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x60, 0x98, 0x00, 0x02, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x71, 0xFA, 0xD1, 0xBC, 0x52, 0x28, 0x68, 0xAD, 0xB2 }; + private static readonly byte[] pcap1_data = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xD1, 0x04, 0x03, 0x01, 0x0B, 0x08, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xCE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xE7, 0x98, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x21, 0xE0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE2, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x60, 0x98, 0x00, 0x02, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xAA, 0x00, 0x00, 0x00 }; + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsRecruitListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsRecruitListHandler.cs new file mode 100644 index 000000000..00328721e --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsRecruitListHandler.cs @@ -0,0 +1,35 @@ +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model.Quest; +using Arrowgene.Logging; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class QuestGetEndContentsRecruitListHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetEndContentsRecruitListHandler)); + + public QuestGetEndContentsRecruitListHandler(DdonGameServer server) : base(server) + { + } + + public override S2CQuestGetEndContentsRecruitListRes Handle(GameClient client, C2SQuestGetEndContentsRecruitListReq request) + { + var results = new S2CQuestGetEndContentsRecruitListRes(); + + foreach (var (questId, quest) in QuestManager.GetQuestsByType(QuestType.ExtremeMission)) + { + results.Unk1List.Add(new CDataQuestRecruitListItem() + { + QuestScheduleId = (uint) quest.QuestScheduleId, + QuestId = (uint) quest.QuestId, + GroupsRecruiting = Server.ExmManager.NumGroupsRecruitingForContent(quest.QuestId), + }); + } + + return results; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs index e062a333c..4b067753d 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs @@ -8,6 +8,7 @@ using Arrowgene.Ddon.Shared.Asset; 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.Network; using Arrowgene.Logging; @@ -52,6 +53,18 @@ public override void Handle(GameClient client, StructurePacket + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestPlayEndHandler)); + + public QuestPlayEndHandler(DdonGameServer server) : base(server) + { + } + + // C2S_QUEST_PLAY_END_REQ (C2SQuestPlayEndReq) + // S2C_QUEST_11_117_16_NTC + // S2C_QUEST_PLAY_END_RES (S2CQuestPlayEndRes) + // S2C_QUEST_11_45_16_NTC (S2CQuestPlayEndNtc) + // + // .. + // C2S_STAGE_AREA_CHANGE + // C2S_QUEST_11_60_16_NTC + // + // + // Party "ContentNumber" has changed: 17180184836 -> 4294967297 + + public override S2CQuestPlayEndRes Handle(GameClient client, C2SQuestPlayEndReq request) + { + var groupId = Server.ExmManager.GetContentIdForCharacter(client.Character); + var quest = Server.ExmManager.GetQuestForContent(groupId); + + var ntc = new S2CQuestPlayEndNtc(); + ntc.ContentsPlayEnd.RewardItemDetailList = quest.ToCDataTimeGainQuestList(0).RewardItemDetailList; + client.Send(ntc); + + // PacketId.S2C_QUEST_11_117_16_NTC + // ushort + // ushort + // byte + + // PacketId.C2S_QUEST_11_60_16_NTC + + // TODO: Clean up state from ExmManager? + + return new S2CQuestPlayEndRes(); + + // Send S2CQuestPlayEndNtc (S2C_QUEST_11_45_16_NTC) + // Send 0x7f4e10 + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayEntryHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayEntryHandler.cs new file mode 100644 index 000000000..04ae0eb43 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayEntryHandler.cs @@ -0,0 +1,28 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Logging; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class QuestPlayEntryHandler : StructurePacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestPlayEntryHandler)); + + public QuestPlayEntryHandler(DdonGameServer server) : base(server) + { + } + + public override void Handle(GameClient client, StructurePacket request) + { + client.Send(new S2CQuestPlayEntryRes()); + + var ntc = new S2CQuestPlayEntryNtc() + { + CharacterId = client.Character.CharacterId + }; + client.Send(ntc); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs new file mode 100644 index 000000000..323dc8c78 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs @@ -0,0 +1,65 @@ +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Logging; +using System.Linq; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class QuestPlayStartHandler : StructurePacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestPlayStartHandler)); + + private DdonGameServer _Server; + + public QuestPlayStartHandler(DdonGameServer server) : base(server) + { + _Server = server; + } + + public override void Handle(GameClient client, StructurePacket request) + { + // var pcap1 = new S2CQuestTimeGainQuestPlayStartNtc.Serializer().Read(pcap1_data); + var quest = QuestManager.GetQuest(request.Structure.QuestScheduleId); + if (quest != null) + { + client.Party.QuestState.AddNewQuest(quest); + + var groupId = _Server.ExmManager.GetContentIdForCharacter(client.Character); + _Server.ExmManager.AddQuestToContent(groupId, quest); + + var ntc = new S2CQuestTimeGainQuestPlayStartNtc() + { + TimeGainQuestPlayStartData = quest.ToCDataContentsPlayStartData() + }; + // These next values come from rEndContentsSortieInfo.esi.json + ntc.TimeGainQuestPlayStartData.QuestPhaseGroupIdList = quest.MissionParams.QuestPhaseGroupIdList; + // ntc.TimeGainQuestPlayStartData.Unk3List.Add(new CDataCommonU8() { Value = 1 }); + client.Party.SendToAll(ntc); + // TODO: Handle swapping/restricting items? + } + + client.Send(new S2CQuestPlayerStartRes()); + + // TODO: Figure out what these do + client.Party.SendToAll(new S2CSituationDataStartNtc() { Unk0 = 1 }); + + var pcap2 = new S2CSituationDataUpdateObjectivesNtc.Serializer().Read(pcap2_data); + client.Party.SendToAll(pcap2); + +#if false + var leaveNtc = new S2CUserListLeaveNtc() + { + CharacterList = client.Party.Clients.Select(x => new CDataCommonU32() { Value = x.Character.CharacterId }).ToList() + }; + client.Send(leaveNtc); +#endif + } + + private static readonly byte[] pcap1_data = new byte[] { 0x00, 0x00, 0x01, 0xEB, 0x00, 0x04, 0xD1, 0x04, 0x03, 0x01, 0x0B, 0x08, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xB3, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x7C, 0x00, 0x00, 0x21, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x14, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x00, 0x00, 0x14, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x14, 0xC1, 0x04, 0x2C, 0xA2, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x00, 0x00, 0x14, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x14, 0xC1, 0x04, 0x2C, 0xA2, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x21, 0xE0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE2, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + private static readonly byte[] pcap2_data = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x44, 0x00, 0x0D, 0x00, 0x06, 0x00, 0x1B, 0xE3, 0x81, 0x95, 0xE3, 0x81, 0x95, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0xAA, 0xE7, 0x95, 0xB0, 0xE5, 0xA4, 0x89, 0xE3, 0x81, 0x8C, 0xE7, 0x99, 0xBA, 0xE7, 0x94, 0x9F, 0x44, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x1B, 0xE3, 0x81, 0x95, 0xE3, 0x81, 0x95, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0xAA, 0xE7, 0x95, 0xB0, 0xE5, 0xA4, 0x89, 0xE3, 0x81, 0x8C, 0xE7, 0x99, 0xBA, 0xE7, 0x94, 0x9F, 0x3F, 0x00, 0x03, 0x00, 0x02, 0x00, 0x1B, 0xE3, 0x81, 0x95, 0xE3, 0x81, 0x95, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0xAA, 0xE7, 0x95, 0xB0, 0xE5, 0xA4, 0x89, 0xE3, 0x81, 0x8C, 0xE7, 0x99, 0xBA, 0xE7, 0x94, 0x9F, 0x44, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x1B, 0xE3, 0x81, 0x95, 0xE3, 0x81, 0x95, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0xAA, 0xE7, 0x95, 0xB0, 0xE5, 0xA4, 0x89, 0xE3, 0x81, 0x8C, 0xE7, 0x99, 0xBA, 0xE7, 0x94, 0x9F, 0x38, 0x16, 0x20, 0x00, 0x00, 0x00, 0x08, 0xE7, 0x00 }; + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs new file mode 100644 index 000000000..e822d54d6 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs @@ -0,0 +1,29 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Logging; +using System; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class QuestPlayStartTimerHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestPlayStartTimerHandler)); + + public QuestPlayStartTimerHandler(DdonGameServer server) : base(server) + { + } + + public override S2CQuestPlayStartTimerRes Handle(GameClient client, C2SQuestPlayStartTimerReq request) + { + var ntc = new S2CQuestPlayStartTimerNtc() + { + PlayEndDateTime = (ulong)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + 120000) + }; + client.Party.SendToAll(ntc); + + + + return new S2CQuestPlayStartTimerRes(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 29f14aa9b..67db19a27 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -528,6 +528,29 @@ public bool CompletePartyQuestProgress(DdonGameServer server, PartyGroup party, var questState = party.QuestState.GetQuestState(quest); foreach (var memberClient in party.Clients) { + // Special case for Exteme Missions where there is no state saved + // Tracking completion matters for progress and weekly reward limits + if (quest.QuestType == QuestType.ExtremeMission) + { + var completedQuests = memberClient.Character.CompletedQuests; + if (!completedQuests.ContainsKey(quest.QuestId)) + { + completedQuests.Add(quest.QuestId, new CompletedQuest() + { + QuestId = quest.QuestId, + QuestType = quest.QuestType, + ClearCount = 1, + }); + } + else + { + completedQuests[quest.QuestId].ClearCount += 1; + } + + server.Database.ReplaceCompletedQuest(memberClient.Character.CommonId, quest.QuestId, quest.QuestType, completedQuests[quest.QuestId].ClearCount); + continue; + } + var result = server.Database.GetQuestProgressById(memberClient.Character.CommonId, quest.QuestId); if (result == null) { diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index bb29c7ff7..285e77ee8 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -32,6 +32,7 @@ public static GenericQuest FromAsset(QuestAssetData questAsset) quest.ResetPlayerAfterQuest = questAsset.ResetPlayerAfterQuest; quest.OrderConditions = questAsset.OrderConditions; quest.StageId = questAsset.StageId; + quest.MissionParams = questAsset.MissionParams; quest.ExpRewards.Add(new CDataQuestExp() { @@ -162,8 +163,8 @@ public override List StateMachineExecute(DdonGameServer { questProgressState = QuestProgressState.Checkpoint; } - else if (questBlock.AnnounceType == QuestAnnounceType.Accept) - { + else if (questBlock.AnnounceType == QuestAnnounceType.Accept || questBlock.AnnounceType == QuestAnnounceType.Start) + { questProgressState = QuestProgressState.Accepted; } else @@ -266,9 +267,37 @@ private static CDataQuestProcessState BlockAsCDataQuestProcessState(GenericQuest case QuestAnnounceType.Update: resultCommands.Add(QuestManager.ResultCommand.UpdateAnnounce()); break; + case QuestAnnounceType.Start: + // resultCommands.Add(QuestManager.ResultCommand.SetAnnounce(QuestAnnounceType.Start)); + resultCommands.Add(QuestManager.ResultCommand.StartMissionAnnounce()); + resultCommands.Add(QuestManager.ResultCommand.Unknown(127)); + resultCommands.Add(QuestManager.ResultCommand.StartContentsTimer()); + break; + default: + resultCommands.Add(QuestManager.ResultCommand.SetAnnounce(questBlock.AnnounceType)); + break; } } + if (questBlock.Announcements.GeneralAnnounceId != 0) + { + resultCommands.Add(QuestManager.ResultCommand.CallGeneralAnnounce(0, questBlock.Announcements.GeneralAnnounceId)); + } + + if (questBlock.Announcements.StageStart != 0) + { + resultCommands.Add(QuestManager.ResultCommand.StageAnnounce(0, questBlock.Announcements.StageStart)); + } + else if (questBlock.Announcements.StageClear != 0) + { + resultCommands.Add(QuestManager.ResultCommand.StageAnnounce(1, questBlock.Announcements.StageClear)); + } + + if (questBlock.Announcements.EndContentsPurpose != 0) + { + resultCommands.Add(QuestManager.ResultCommand.AddEndContentsPurpose(questBlock.Announcements.EndContentsPurpose, 0)); + } + foreach (var item in questBlock.HandPlayerItems) { resultCommands.Add(QuestManager.ResultCommand.PushImteToPlBag((int)item.ItemId, (int)item.Amount)); diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 54224eef7..52a0a0f30 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -69,6 +69,7 @@ public abstract class Quest public List DeliveryItems { get; protected set; } public List QuestLayoutFlagSetInfo; public List QuestLayoutFlags; + public QuestMissionParams MissionParams { get; protected set; } public Dictionary EnemyGroups { get; set; } public bool IsVariantQuest { get; set; } public uint VariantId { get; set; } @@ -94,6 +95,7 @@ public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool EnemyGroups = new Dictionary(); IsVariantQuest = false; VariantId = 0; + MissionParams = new QuestMissionParams(); Processes = new List(); } @@ -213,7 +215,6 @@ public virtual CDataQuestList ToCDataQuestList(uint step) BaseLevel = BaseLevel, ContentJoinItemRank = MinimumItemRank, IsClientOrder = step > 0, - IsEnable = true, BaseExp = ExpRewards, BaseWalletPoints = WalletRewards, FixedRewardItemList = GetQuestFixedRewards(), @@ -353,6 +354,44 @@ public virtual CDataLightQuestList ToCDataLightQuestList(uint step) return result; } + public virtual CDataTimeGainQuestList ToCDataTimeGainQuestList(uint step) + { + var result = new CDataTimeGainQuestList() + { + Param = ToCDataQuestList(step), + PlayTimeInSec = MissionParams.PlaytimeInSeconds, + IsNoTimeup = (MissionParams.PlaytimeInSeconds == 0), + Unk0 = false, // Could also be IsNoTimeUp? + IsJoinCharacter = !MissionParams.IsSolo, + IsJoinPawn = MissionParams.MaxPawns > 0, + Unk1 = false, + JoinPawnNum = (byte) MissionParams.MaxPawns, + }; + result.Restrictions.RestrictArmor = !MissionParams.ArmorAllowed; + result.Restrictions.RestrictJewlery = !MissionParams.JewelryAllowed; + +#if false + // Figure out what these fields do + result.Restrictions.Unk0 = 2; + result.Restrictions.Unk1List.Add(new CDataCommonU32() { Value = 1 }); + result.Restrictions.Unk2List.Add(new CDataTimeGainQuestUnk1Unk2() { Unk0 = 3, Unk1 = true }); + result.Restrictions.Unk5List.Add(new CDataCommonU8() { Value = 2 }); +#endif + + + foreach (var reward in result.Param.FixedRewardItemList) + { + result.RewardItemDetailList.Add(new CDataRewardItemDetail() + { + ItemId = reward.ItemId, + Num = reward.Num, + Type = 12 // What does this type mean? + }); + } + + return result; + } + public virtual CDataSetQuestInfoList ToCDataSetQuestInfoList() { var result = new CDataSetQuestInfoList() @@ -388,6 +427,30 @@ public virtual CDataSetQuestInfoList ToCDataSetQuestInfoList() return result; } + + public virtual CDataContentsPlayStartData ToCDataContentsPlayStartData(uint step = 0) + { + return new CDataContentsPlayStartData() + { + QuestId = (uint) QuestId, + QuestScheudleId = (uint) QuestScheduleId, + BaseLevel = BaseLevel, + QuestEnemyInfoList = EnemyGroups.Values.SelectMany(group => group.Enemies.Select(enemy => new CDataQuestEnemyInfo() + { + GroupId = enemy.UINameId, + Unk0 = 0, // Seemingly always 0 in the pcaps + Lv = enemy.Lv, + IsPartyRecommend = enemy.IsBossGauge + })) + .ToList(), + QuestLayoutFlagSetInfoList = QuestLayoutFlagSetInfo.Select(x => x.AsCDataQuestLayoutFlagSetInfo()).ToList(), + QuestProcessStateList = GetProcessState(step, out uint announceNoCount), + Unk0 = true, + Unk1 = false, + Unk2 = true + }; + } + public abstract List StateMachineExecute(DdonGameServer server, GameClient client, QuestProcessState processState, out QuestProgressState questProgressState); public virtual void SendProgressWorkNotices(GameClient client, StageId stageId, uint subGroupId) diff --git a/Arrowgene.Ddon.GameServer/Utils/RandomExtensions.cs b/Arrowgene.Ddon.GameServer/Utils/RandomExtensions.cs new file mode 100644 index 000000000..6691e45e2 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Utils/RandomExtensions.cs @@ -0,0 +1,21 @@ +using System; + +namespace Arrowgene.Ddon.GameServer.Utils +{ + public static class RandomExtensions + { + public static UInt64 NextU64(this Random rnd) + { + var buffer = new byte[sizeof(Int64)]; + rnd.NextBytes(buffer); + return BitConverter.ToUInt64(buffer, 0); + } + + public static UInt32 NextU32(this Random rnd) + { + var buffer = new byte[sizeof(Int64)]; + rnd.NextBytes(buffer); + return BitConverter.ToUInt32(buffer, 0); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Utils/UUIDv7.cs b/Arrowgene.Ddon.GameServer/Utils/UUIDv7.cs new file mode 100644 index 000000000..02552f246 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Utils/UUIDv7.cs @@ -0,0 +1,31 @@ +using System; +using System.Security.Cryptography; + +public class UUIDv7 +{ + private static readonly RandomNumberGenerator random = RandomNumberGenerator.Create(); + + public static byte[] Generate() + { + // random bytes + byte[] value = new byte[16]; + random.GetBytes(value); + + // current timestamp in ms + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + // timestamp + value[0] = (byte)((timestamp >> 40) & 0xFF); + value[1] = (byte)((timestamp >> 32) & 0xFF); + value[2] = (byte)((timestamp >> 24) & 0xFF); + value[3] = (byte)((timestamp >> 16) & 0xFF); + value[4] = (byte)((timestamp >> 8) & 0xFF); + value[5] = (byte)(timestamp & 0xFF); + + // version and variant + value[6] = (byte)((value[6] & 0x0F) | 0x70); + value[8] = (byte)((value[8] & 0x3F) | 0x80); + + return value; + } +} diff --git a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs index 354c38d79..b9e8b27b0 100644 --- a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs +++ b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs @@ -37,6 +37,7 @@ public class QuestAssetData public List QuestLayoutSetInfoFlags { get; set; } public Dictionary EnemyGroups { get; set; } public uint VariantId { get; set; } + public QuestMissionParams MissionParams { get; set; } public QuestAssetData() { @@ -47,6 +48,7 @@ public QuestAssetData() QuestLayoutSetInfoFlags = new List(); EnemyGroups = new Dictionary(); OrderConditions = new List(); + MissionParams = new QuestMissionParams(); } } } diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 35739f4ad..caebc6612 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Text.Json; +using static Arrowgene.Ddon.Shared.Csv.GmdCsv; namespace Arrowgene.Ddon.Shared.AssetReader @@ -145,6 +146,17 @@ private bool ParseQuest(QuestAssetData assetData, JsonElement jQuest) assetData.ResetPlayerAfterQuest = true; } + if (questType == QuestType.ExtremeMission) + { + if (!jQuest.TryGetProperty("mission_params", out JsonElement jMissionParams)) + { + Logger.Error($"Unable to create the quest '{assetData.QuestId}'. Missing 'mission_params'. Skipping."); + return false; + } + + ParseMissionParams(assetData, jMissionParams); + } + ParseRewards(assetData, jQuest); if (!ParseOrderCondition(assetData, jQuest)) @@ -368,6 +380,8 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) questBlock.AnnounceType = announceType; } + ParseAnnoucementSubtypes(questBlock, jblock); + questBlock.IsCheckpoint = false; if (jblock.TryGetProperty("checkpoint", out JsonElement jCheckpoint)) { @@ -750,6 +764,35 @@ private StageId ParseStageId(JsonElement jStageId) return new StageId(id, layerNo, groupId); } + private void ParseAnnoucementSubtypes(QuestBlock questBlock, JsonElement jBlock) + { + var announcements = questBlock.Announcements; + + announcements.GeneralAnnounceId = 0; + if (jBlock.TryGetProperty("general_announce", out JsonElement jGeneralAnnounce)) + { + announcements.GeneralAnnounceId = jGeneralAnnounce.GetInt32(); + } + + announcements.StageStart = 0; + if (jBlock.TryGetProperty("stage_start", out JsonElement jStageStart)) + { + announcements.StageStart = jStageStart.GetInt32(); + } + + announcements.StageClear = 0; + if (jBlock.TryGetProperty("stage_clear", out JsonElement jStageClear)) + { + announcements.StageClear = jStageClear.GetInt32(); + } + + announcements.EndContentsPurpose = 0; + if (jBlock.TryGetProperty("end_contents_announce", out JsonElement jEndContentsPurpose)) + { + announcements.EndContentsPurpose = jEndContentsPurpose.GetInt32(); + } + } + private QuestFlag ParseQuestFlag(JsonElement jFlag) { QuestFlag questFlag = new QuestFlag(); @@ -778,6 +821,69 @@ private QuestFlag ParseQuestFlag(JsonElement jFlag) return questFlag; } + private bool ParseMissionParams(QuestAssetData assetData, JsonElement jMissionParams) + { + + if (!jMissionParams.TryGetProperty("group", out JsonElement jGroup)) + { + Logger.Error($"Missing required member 'group' from ExtremeMission config."); + return false; + } + assetData.MissionParams.Group = jGroup.GetUInt32(); + + if (!jMissionParams.TryGetProperty("phase_groups", out JsonElement jPhaseGroups)) + { + Logger.Error($"Missing required member 'phase_groups' from ExtremeMission config."); + return false; + } + + foreach (var element in jPhaseGroups.EnumerateArray()) + { + assetData.MissionParams.QuestPhaseGroupIdList.Add(new CDataCommonU32() { Value = element.GetUInt32() }); + } + + assetData.MissionParams.SortieMinimum = 4; + if (jMissionParams.TryGetProperty("minimum_members", out JsonElement jMinimumMembers)) + { + assetData.MissionParams.SortieMinimum = jMinimumMembers.GetUInt32(); + } + + assetData.MissionParams.PlaytimeInSeconds = 1200; + if (jMissionParams.TryGetProperty("playtime", out JsonElement jPlaytime)) + { + assetData.MissionParams.PlaytimeInSeconds = jPlaytime.GetUInt32(); + } + + assetData.MissionParams.IsSolo = false; + if (jMissionParams.TryGetProperty("solo_only", out JsonElement jIsSoloOnly)) + { + assetData.MissionParams.IsSolo = jIsSoloOnly.GetBoolean(); + } + + assetData.MissionParams.MaxPawns = 3; + if (jMissionParams.TryGetProperty("max_pawns", out JsonElement jMaxPawns)) + { + assetData.MissionParams.MaxPawns = jMaxPawns.GetUInt32(); + } + + assetData.MissionParams.ArmorAllowed = true; + assetData.MissionParams.JewelryAllowed = true; + if (jMissionParams.TryGetProperty("restrictions", out JsonElement jRestrictions)) + { + if (jMissionParams.TryGetProperty("armor", out JsonElement jArmorAllowed)) + { + assetData.MissionParams.ArmorAllowed = jArmorAllowed.GetBoolean(); + } + + if (jMissionParams.TryGetProperty("jewelry", out JsonElement jJewelryAllowed)) + { + assetData.MissionParams.JewelryAllowed = jJewelryAllowed.GetBoolean(); + } + } + + return true; + } + private bool ParseEnemyGroups(QuestAssetData assetData, JsonElement quest) { if (!quest.TryGetProperty("enemy_groups", out JsonElement jGroups)) @@ -847,7 +953,12 @@ private bool ParseEnemyGroups(QuestAssetData assetData, JsonElement quest) isRequired = jIsRequired.GetBoolean(); } - + uint repopWaitSecond = 0; + if (enemy.TryGetProperty("repop_wait_second", out JsonElement jRepopWaitSecond)) + { + repopWaitSecond = jRepopWaitSecond.GetUInt32(); + } + // Look for custom drops here bool customDropItems = false; @@ -890,7 +1001,8 @@ private bool ParseEnemyGroups(QuestAssetData assetData, JsonElement quest) Scale = 100, EnemyTargetTypesId = (byte)(isRequired ? 4 : 1), Index = index, - IsRequired = isRequired + IsRequired = isRequired, + RepopWaitSecond = repopWaitSecond, }; ApplyOptionalEnemyKeys(enemy, questEnemy); diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index 20bc19655..756bc3192 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -77,10 +77,13 @@ static EntitySerializer() Create(new CDataClanParam.Serializer()); Create(new CDataClanServerParam.Serializer()); Create(new CDataClanUserParam.Serializer()); - Create(new CDataCommonU32.Serializer()); Create(new CDataCommonU8.Serializer()); + Create(new CDataCommonU32.Serializer()); + Create(new CDataCommonU64.Serializer()); Create(new CDataCommunicationShortCut.Serializer()); Create(new CDataCommunityCharacterBaseInfo.Serializer()); + Create(new CDataContentsPlayStartData.Serializer()); + Create(new CDataContentsPlayEnd.Serializer()); Create(new CDataContextAcquirementData.Serializer()); Create(new CDataContextBase.Serializer()); Create(new CDataContextBaseUnk0.Serializer()); @@ -135,6 +138,14 @@ static EntitySerializer() Create(new CDataChangeEquipJobItem.Serializer()); Create(new CDataCharacterEditPrice.Serializer()); Create(new CDataDragonAbility.Serializer()); + + Create(new CDataEntryBoardListParam.Serializer()); + Create(new CDataEntryRecruitData.Serializer()); + Create(new CDataEntryRecruitJob.Serializer()); + Create(new CDataEntryItem.Serializer()); + Create(new CDataEntryItemParam.Serializer()); + Create(new CDataEntryMemberData.Serializer()); + Create(new CDataEquipItemInfo.Serializer()); Create(new CDataEquipJobItem.Serializer()); Create(new CDataErrorMessage.Serializer()); @@ -265,11 +276,13 @@ static EntitySerializer() Create(new CDataQuestSetInfo.Serializer()); Create(new CDataQuestTalkInfo.Serializer()); Create(new CDataQuestMobHuntQuestInfo.Serializer()); + Create(new CDataQuestRecruitListItem.Serializer()); Create(new CDataSetQuestInfoList.Serializer()); Create(new CDataSetQuestBonusList.Serializer()); Create(new CDataRentedPawnList.Serializer()); Create(new CDataRewardItem.Serializer()); + Create(new CDataRewardItemDetail.Serializer()); Create(new CDataRewardBoxRecord.Serializer()); Create(new CDataRewardBoxItem.Serializer()); Create(new CDataRegisteredLegendPawnInfo.Serializer()); @@ -284,6 +297,7 @@ static EntitySerializer() Create(new CDataS2CQuestJoinLobbyQuestInfoNtcUnk0.Serializer()); Create(new CDataS2CQuestJoinLobbyQuestInfoNtcUnk0Unk1.Serializer()); Create(new CDataWildHuntQuestOrderList.Serializer()); + Create(new CDataSituationObjective.Serializer()); Create(new CDataScreenShotCategory.Serializer()); Create(new CDataSetAcquirementParam.Serializer()); Create(new CDataSetQuestDetail.Serializer()); @@ -308,6 +322,11 @@ static EntitySerializer() Create(new CDataStampCheck.Serializer()); Create(new CDataTimeLimitedQuestOrderList.Serializer()); + Create(new CDataTimeGainQuestList.Serializer()); + Create(new CDataTimeGainQuestRestrictions.Serializer()); + Create(new CDataTimeGainQuestUnk1Unk2.Serializer()); + Create(new CDataTimeGainQuestUnk2.Serializer()); + Create(new CDataTraningRoomEnemyHeader.Serializer()); Create(new CDataTutorialQuestOrderList.Serializer()); Create(new CDataTutorialQuestList.Serializer()); @@ -449,6 +468,14 @@ static EntitySerializer() Create(new C2SEquipUpdateHidePawnHeadArmorReq.Serializer()); Create(new C2SEquipUpdateHidePawnLanternReq.Serializer()); + Create(new C2SEntryBoardEntryBoardListReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemCreateReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemForceStartReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemReadyReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemLeaveReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemInfoMyselfReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemInfoChangeReq.Serializer()); + Create(new C2SAchievementReceivableRewardNtc.Serializer()); Create(new C2SAchievementCompleteNtc.Serializer()); @@ -607,6 +634,13 @@ static EntitySerializer() Create(new C2SQuestGetSetQuestInfoListReq.Serializer()); Create(new C2SQuestGetPartyBonusListReq.Serializer()); Create(new C2SQuestGetMobHuntQuestListReq.Serializer()); + Create(new C2SQuestGetEndContentsGroupReq.Serializer()); + Create(new C2SQuestPlayEntryReq.Serializer()); + Create(new C2SQuestPlayerStartReq.Serializer()); + Create(new C2SQuestPlayStartTimerReq.Serializer()); + Create(new C2SQuestPlayEndReq.Serializer()); + Create(new C2SQuestGetAdventureGuideQuestNtcReq.Serializer()); + Create(new C2SQuestGetEndContentsRecruitListReq.Serializer()); Create(new C2SServerGameTimeGetBaseInfoReq.Serializer()); Create(new C2SServerGetRealTimeReq.Serializer()); @@ -824,6 +858,19 @@ static EntitySerializer() Create(new S2CDispelGetDispelItemListRes.Serializer()); Create(new S2CDispelExchangeDispelItemRes.Serializer()); + Create(new S2CEntryBoardEntryBoardListRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemCreateRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemForceStartRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemReadyNtc.Serializer()); + Create(new S2CEntryBoardEntryBoardItemReadyRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemReserveNtc.Serializer()); + Create(new S2CEntryBoardEntryBoardItemChangeMemberNtc.Serializer()); + Create(new S2CEntryBoardEntryBoardItemLeaveRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemLeaveNtc.Serializer()); + Create(new S2CEntryBoardEntryBoardItemInfoMyselfRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemInfoChangeRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemInfoChangeNtc.Serializer()); + Create(new S2CEquipChangeCharacterEquipJobItemNtc.Serializer()); Create(new S2CEquipChangeCharacterEquipJobItemRes.Serializer()); Create(new S2CEquipChangeCharacterEquipNtc.Serializer()); @@ -1058,6 +1105,16 @@ static EntitySerializer() Create(new S2CQuestDecideDeliveryItemNtc.Serializer()); Create(new S2CQuestGetSetQuestInfoListRes.Serializer()); Create(new S2CQuestGetPartyBonusListRes.Serializer()); + Create(new S2CQuestPlayEntryRes.Serializer()); + Create(new S2CQuestPlayEntryNtc.Serializer()); + Create(new S2CQuestPlayerStartRes.Serializer()); + Create(new S2CQuestTimeGainQuestPlayStartNtc.Serializer()); + Create(new S2CQuestPlayStartTimerRes.Serializer()); + Create(new S2CQuestPlayStartTimerNtc.Serializer()); + Create(new S2CQuestPlayEndRes.Serializer()); + Create(new S2CQuestPlayEndNtc.Serializer()); + Create(new S2CQuestGetAdventureGuideQuestNtcRes.Serializer()); + Create(new S2CQuestGetEndContentsRecruitListRes.Serializer()); Create(new S2CQuestSendLeaderQuestOrderConditionInfoNtc.Serializer()); Create(new S2CQuestSendLeaderQuestOrderConditionInfoRes.Serializer()); @@ -1065,6 +1122,9 @@ static EntitySerializer() Create(new S2CQuestSendLeaderWaitOrderQuestListRes.Serializer()); Create(new S2CQuestSetPriorityQuestRes.Serializer()); Create(new S2CQuestGetMobHuntQuestListRes.Serializer()); + Create(new S2CQuestGetEndContentsGroupRes.Serializer()); + + Create(new S2CSeason62_26_16Ntc.Serializer()); Create(new S2CServerGameTimeGetBaseInfoRes.Serializer()); Create(new S2CServerGetRealTimeRes.Serializer()); @@ -1073,6 +1133,12 @@ static EntitySerializer() Create(new S2CServerWeatherForecastGetRes.Serializer()); Create(new S2CServerTimeUpdateNtc.Serializer()); + Create(new S2CSituationDataStartNtc.Serializer()); + Create(new S2CSituationDataUpdateObjectivesNtc.Serializer()); + Create(new S2CSituationDataEndNtc.Serializer()); + Create(new S2C_63_7_16_NTC.Serializer()); + Create(new S2C_63_10_16_NTC.Serializer()); + Create(new S2CSkillAbilitySetNtc.Serializer()); Create(new S2CSkillChangeExSkillRes.Serializer()); Create(new S2CSkillCustomSkillSetNtc.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemCreateReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemCreateReq.cs new file mode 100644 index 000000000..b390789ef --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemCreateReq.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemCreateReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_CREATE_REQ; + + public C2SEntryBoardEntryBoardItemCreateReq() + { + Password = string.Empty; + CreateParam = new CDataEntryItemParam(); + } + + public ulong BoardId { get; set; } + public string Password { get; set; } + public CDataEntryItemParam CreateParam { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemCreateReq obj) + { + WriteUInt64(buffer, obj.BoardId); + WriteMtString(buffer, obj.Password); + WriteEntity(buffer, obj.CreateParam); + } + + public override C2SEntryBoardEntryBoardItemCreateReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemCreateReq obj = new C2SEntryBoardEntryBoardItemCreateReq(); + obj.BoardId = ReadUInt64(buffer); + obj.Password = ReadMtString(buffer); + obj.CreateParam = ReadEntity(buffer); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemForceStartReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemForceStartReq.cs new file mode 100644 index 000000000..0b82de34c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemForceStartReq.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemForceStartReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_REQ; + + public C2SEntryBoardEntryBoardItemForceStartReq() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemForceStartReq obj) + { + } + + public override C2SEntryBoardEntryBoardItemForceStartReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemForceStartReq obj = new C2SEntryBoardEntryBoardItemForceStartReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInfoChangeReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInfoChangeReq.cs new file mode 100644 index 000000000..01bcd0bf5 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInfoChangeReq.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemInfoChangeReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_REQ; + + public C2SEntryBoardEntryBoardItemInfoChangeReq() + { + Param = new CDataEntryItemParam(); + } + + public string Password { get; set; } + public CDataEntryItemParam Param { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemInfoChangeReq obj) + { + WriteMtString(buffer, obj.Password); + WriteEntity(buffer, obj.Param); + } + + public override C2SEntryBoardEntryBoardItemInfoChangeReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemInfoChangeReq obj = new C2SEntryBoardEntryBoardItemInfoChangeReq(); + obj.Password = ReadMtString(buffer); + obj.Param = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInfoMyselfReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInfoMyselfReq.cs new file mode 100644 index 000000000..05a8474c8 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInfoMyselfReq.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemInfoMyselfReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_MYSELF_REQ; + + public C2SEntryBoardEntryBoardItemInfoMyselfReq() + { + } + + public bool Unk0 { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemInfoMyselfReq obj) + { + WriteBool(buffer, obj.Unk0); + } + + public override C2SEntryBoardEntryBoardItemInfoMyselfReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemInfoMyselfReq obj = new C2SEntryBoardEntryBoardItemInfoMyselfReq(); + obj.Unk0 = ReadBool(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemLeaveReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemLeaveReq.cs new file mode 100644 index 000000000..b4338a22a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemLeaveReq.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemLeaveReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_REQ; + + public C2SEntryBoardEntryBoardItemLeaveReq() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemLeaveReq obj) + { + } + + public override C2SEntryBoardEntryBoardItemLeaveReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemLeaveReq obj = new C2SEntryBoardEntryBoardItemLeaveReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemReadyReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemReadyReq.cs new file mode 100644 index 000000000..8ac8abaaa --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemReadyReq.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemReadyReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_REQ; + + public C2SEntryBoardEntryBoardItemReadyReq() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemReadyReq obj) + { + } + + public override C2SEntryBoardEntryBoardItemReadyReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemReadyReq obj = new C2SEntryBoardEntryBoardItemReadyReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardListReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardListReq.cs new file mode 100644 index 000000000..c120333a6 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardListReq.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardListReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_LIST_REQ; + + public C2SEntryBoardEntryBoardListReq() + { + Unk0List = new List(); // List of Entry ID's? + } + + public List Unk0List { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardListReq obj) + { + WriteEntityList(buffer, obj.Unk0List); + } + + public override C2SEntryBoardEntryBoardListReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardListReq obj = new C2SEntryBoardEntryBoardListReq(); + obj.Unk0List = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetAdventureGuideQuestNtcReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetAdventureGuideQuestNtcReq.cs new file mode 100644 index 000000000..9a2083718 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetAdventureGuideQuestNtcReq.cs @@ -0,0 +1,23 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestGetAdventureGuideQuestNtcReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestGetAdventureGuideQuestNtcReq obj) + { + } + + public override C2SQuestGetAdventureGuideQuestNtcReq Read(IBuffer buffer) + { + C2SQuestGetAdventureGuideQuestNtcReq obj = new C2SQuestGetAdventureGuideQuestNtcReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetEndContentsGroupReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetEndContentsGroupReq.cs new file mode 100644 index 000000000..f1c7035a8 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetEndContentsGroupReq.cs @@ -0,0 +1,26 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestGetEndContentsGroupReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_GET_END_CONTENTS_GROUP_REQ; + public uint GroupId { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestGetEndContentsGroupReq obj) + { + WriteUInt32(buffer, obj.GroupId); + } + + public override C2SQuestGetEndContentsGroupReq Read(IBuffer buffer) + { + C2SQuestGetEndContentsGroupReq obj = new C2SQuestGetEndContentsGroupReq(); + obj.GroupId = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetEndContentsRecruitListReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetEndContentsRecruitListReq.cs new file mode 100644 index 000000000..7f7d1c296 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetEndContentsRecruitListReq.cs @@ -0,0 +1,23 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestGetEndContentsRecruitListReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_GET_END_CONTENTS_RECRUIT_LIST_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestGetEndContentsRecruitListReq obj) + { + } + + public override C2SQuestGetEndContentsRecruitListReq Read(IBuffer buffer) + { + C2SQuestGetEndContentsRecruitListReq obj = new C2SQuestGetEndContentsRecruitListReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEndReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEndReq.cs new file mode 100644 index 000000000..e9a1f89fe --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEndReq.cs @@ -0,0 +1,24 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestPlayEndReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_PLAY_END_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestPlayEndReq obj) + { + } + + public override C2SQuestPlayEndReq Read(IBuffer buffer) + { + C2SQuestPlayEndReq obj = new C2SQuestPlayEndReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEntryReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEntryReq.cs new file mode 100644 index 000000000..b9d32015f --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEntryReq.cs @@ -0,0 +1,24 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestPlayEntryReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_PLAY_ENTRY_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestPlayEntryReq obj) + { + } + + public override C2SQuestPlayEntryReq Read(IBuffer buffer) + { + C2SQuestPlayEntryReq obj = new C2SQuestPlayEntryReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayStartTimerReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayStartTimerReq.cs new file mode 100644 index 000000000..913196fed --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayStartTimerReq.cs @@ -0,0 +1,26 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; +using System; +using System.Security.Cryptography.X509Certificates; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestPlayStartTimerReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_PLAY_START_TIMER_REQ; + + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestPlayStartTimerReq obj) + { + } + + public override C2SQuestPlayStartTimerReq Read(IBuffer buffer) + { + C2SQuestPlayStartTimerReq obj = new C2SQuestPlayStartTimerReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayerStartReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayerStartReq.cs new file mode 100644 index 000000000..930f5b40f --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayerStartReq.cs @@ -0,0 +1,29 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; +using System; +using System.Security.Cryptography.X509Certificates; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestPlayerStartReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_PLAY_START_REQ; + + public uint QuestScheduleId { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestPlayerStartReq obj) + { + WriteUInt32(buffer, obj.QuestScheduleId); + } + + public override C2SQuestPlayerStartReq Read(IBuffer buffer) + { + C2SQuestPlayerStartReq obj = new C2SQuestPlayerStartReq(); + obj.QuestScheduleId = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CDispelGetDispelItemSettingsRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CDispelGetDispelItemSettingsRes.cs index 491fad038..911917781 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CDispelGetDispelItemSettingsRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CDispelGetDispelItemSettingsRes.cs @@ -37,6 +37,3 @@ public override S2CDispelGetDispelItemSettingsRes Read(IBuffer buffer) } } } - - - diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemChangeMemberNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemChangeMemberNtc.cs new file mode 100644 index 000000000..3d9225713 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemChangeMemberNtc.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemChangeMemberNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC; + + public S2CEntryBoardEntryBoardItemChangeMemberNtc() + { + MemberData = new CDataEntryMemberData(); + } + + public bool EntryFlag { get; set; } + public CDataEntryMemberData MemberData { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemChangeMemberNtc obj) + { + WriteBool(buffer, obj.EntryFlag); + WriteEntity(buffer, obj.MemberData); + } + + public override S2CEntryBoardEntryBoardItemChangeMemberNtc Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemChangeMemberNtc obj = new S2CEntryBoardEntryBoardItemChangeMemberNtc(); + obj.EntryFlag = ReadBool(buffer); + obj.MemberData = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemCreateRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemCreateRes.cs new file mode 100644 index 000000000..d163a0441 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemCreateRes.cs @@ -0,0 +1,40 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemCreateRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CREATE_RES; + + public S2CEntryBoardEntryBoardItemCreateRes() + { + EntryItem = new CDataEntryItem(); + } + + public ulong BoardId { get; set; } + public CDataEntryItem EntryItem { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemCreateRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt64(buffer, obj.BoardId); + WriteEntity(buffer, obj.EntryItem); + + } + + public override S2CEntryBoardEntryBoardItemCreateRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemCreateRes obj = new S2CEntryBoardEntryBoardItemCreateRes(); + ReadServerResponse(buffer, obj); + obj.BoardId = ReadUInt64(buffer); + obj.EntryItem = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemForceStartRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemForceStartRes.cs new file mode 100644 index 000000000..6cebc70ea --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemForceStartRes.cs @@ -0,0 +1,31 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemForceStartRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_RES; + + public S2CEntryBoardEntryBoardItemForceStartRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemForceStartRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CEntryBoardEntryBoardItemForceStartRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemForceStartRes obj = new S2CEntryBoardEntryBoardItemForceStartRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoChangeNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoChangeNtc.cs new file mode 100644 index 000000000..8035aaed0 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoChangeNtc.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemInfoChangeNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_NTC; + + public S2CEntryBoardEntryBoardItemInfoChangeNtc() + { + EntryItemData = new CDataEntryItem(); + } + + public ulong BoardId { get; set; } + public CDataEntryItem EntryItemData { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemInfoChangeNtc obj) + { + WriteUInt64(buffer, obj.BoardId); + WriteEntity(buffer, obj.EntryItemData); + } + + public override S2CEntryBoardEntryBoardItemInfoChangeNtc Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemInfoChangeNtc obj = new S2CEntryBoardEntryBoardItemInfoChangeNtc(); + obj.BoardId = ReadUInt64(buffer); + obj.EntryItemData = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoChangeRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoChangeRes.cs new file mode 100644 index 000000000..218306770 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoChangeRes.cs @@ -0,0 +1,35 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemInfoChangeRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_RES; + + public S2CEntryBoardEntryBoardItemInfoChangeRes() + { + } + + public CDataEntryItem EntryItemData; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemInfoChangeRes obj) + { + WriteServerResponse(buffer, obj); + WriteEntity(buffer, obj.EntryItemData); + } + + public override S2CEntryBoardEntryBoardItemInfoChangeRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemInfoChangeRes obj = new S2CEntryBoardEntryBoardItemInfoChangeRes(); + ReadServerResponse(buffer, obj); + obj.EntryItemData = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoMyselfRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoMyselfRes.cs new file mode 100644 index 000000000..81df2d1e5 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoMyselfRes.cs @@ -0,0 +1,39 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemInfoMyselfRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_MYSELF_RES; + + public S2CEntryBoardEntryBoardItemInfoMyselfRes() + { + EntryItem = new CDataEntryItem(); + } + + public ulong ContentId { get; set; } + public CDataEntryItem EntryItem { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemInfoMyselfRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt64(buffer, obj.ContentId); + WriteEntity(buffer, obj.EntryItem); + } + + public override S2CEntryBoardEntryBoardItemInfoMyselfRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemInfoMyselfRes obj = new S2CEntryBoardEntryBoardItemInfoMyselfRes(); + ReadServerResponse(buffer, obj); + obj.ContentId = ReadUInt64(buffer); + obj.EntryItem = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveNtc.cs new file mode 100644 index 000000000..db7b2c244 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveNtc.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemLeaveNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_NTC; + + public S2CEntryBoardEntryBoardItemLeaveNtc() + { + } + + public uint LeaveType { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemLeaveNtc obj) + { + WriteUInt32(buffer, obj.LeaveType); + } + + public override S2CEntryBoardEntryBoardItemLeaveNtc Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemLeaveNtc obj = new S2CEntryBoardEntryBoardItemLeaveNtc(); + obj.LeaveType = ReadUInt32(buffer); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveRes.cs new file mode 100644 index 000000000..ad6c94d24 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveRes.cs @@ -0,0 +1,31 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemLeaveRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_RES; + + public S2CEntryBoardEntryBoardItemLeaveRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemLeaveRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CEntryBoardEntryBoardItemLeaveRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemLeaveRes obj = new S2CEntryBoardEntryBoardItemLeaveRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReadyNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReadyNtc.cs new file mode 100644 index 000000000..8cdb9158d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReadyNtc.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemReadyNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_NTC; + + public S2CEntryBoardEntryBoardItemReadyNtc() + { + } + + public uint MaxMember { get; set; } + public ushort TimeOut { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemReadyNtc obj) + { + WriteUInt32(buffer, obj.MaxMember); + WriteUInt16(buffer, obj.TimeOut); + } + + public override S2CEntryBoardEntryBoardItemReadyNtc Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemReadyNtc obj = new S2CEntryBoardEntryBoardItemReadyNtc(); + obj.MaxMember = ReadUInt32(buffer); + obj.TimeOut = ReadUInt16(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReadyRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReadyRes.cs new file mode 100644 index 000000000..e4129f573 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReadyRes.cs @@ -0,0 +1,31 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemReadyRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_RES; + + public S2CEntryBoardEntryBoardItemReadyRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemReadyRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CEntryBoardEntryBoardItemReadyRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemReadyRes obj = new S2CEntryBoardEntryBoardItemReadyRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReserveNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReserveNtc.cs new file mode 100644 index 000000000..ec77b4a4c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemReserveNtc.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemReserveNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC; + + public S2CEntryBoardEntryBoardItemReserveNtc() + { + } + + public uint NowMember { get; set; } + public uint MaxMember { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemReserveNtc obj) + { + WriteUInt32(buffer, obj.NowMember); + WriteUInt32(buffer, obj.MaxMember); + } + + public override S2CEntryBoardEntryBoardItemReserveNtc Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemReserveNtc obj = new S2CEntryBoardEntryBoardItemReserveNtc(); + obj.NowMember = ReadUInt32(buffer); + obj.MaxMember = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardListRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardListRes.cs new file mode 100644 index 000000000..e438eb852 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardListRes.cs @@ -0,0 +1,36 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardListRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_LIST_RES; + + public S2CEntryBoardEntryBoardListRes() + { + EntryList = new List(); + } + + public List EntryList { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardListRes obj) + { + WriteServerResponse(buffer, obj); + WriteEntityList(buffer, obj.EntryList); + } + + public override S2CEntryBoardEntryBoardListRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardListRes obj = new S2CEntryBoardEntryBoardListRes(); + ReadServerResponse(buffer, obj); + obj.EntryList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetAdventureGuideQuestNtcRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetAdventureGuideQuestNtcRes.cs new file mode 100644 index 000000000..3dd310389 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetAdventureGuideQuestNtcRes.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestGetAdventureGuideQuestNtcRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_RES; + + public S2CQuestGetAdventureGuideQuestNtcRes() + { + } + + public bool Unk0 { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestGetAdventureGuideQuestNtcRes obj) + { + WriteServerResponse(buffer, obj); + WriteBool(buffer, obj.Unk0); + } + + public override S2CQuestGetAdventureGuideQuestNtcRes Read(IBuffer buffer) + { + S2CQuestGetAdventureGuideQuestNtcRes obj = new S2CQuestGetAdventureGuideQuestNtcRes(); + ReadServerResponse(buffer, obj); + obj.Unk0 = ReadBool(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetEndContentsGroupRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetEndContentsGroupRes.cs new file mode 100644 index 000000000..dba582877 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetEndContentsGroupRes.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestGetEndContentsGroupRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_GET_END_CONTENTS_GROUP_RES; + + public S2CQuestGetEndContentsGroupRes() + { + TimeGainQuestList = new List(); + } + + public uint GroupId { get; set; } + public ContentsType ContentsType { get; set; } + public List TimeGainQuestList { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestGetEndContentsGroupRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt32(buffer, obj.GroupId); + WriteUInt32(buffer, (uint) obj.ContentsType); + WriteEntityList(buffer, obj.TimeGainQuestList); + } + + public override S2CQuestGetEndContentsGroupRes Read(IBuffer buffer) + { + S2CQuestGetEndContentsGroupRes obj = new S2CQuestGetEndContentsGroupRes(); + ReadServerResponse(buffer, obj); + obj.GroupId = ReadUInt32(buffer); + obj.ContentsType = (ContentsType) ReadUInt32(buffer); + obj.TimeGainQuestList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetEndContentsRecruitListRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetEndContentsRecruitListRes.cs new file mode 100644 index 000000000..6a4df19c8 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetEndContentsRecruitListRes.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestGetEndContentsRecruitListRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_GET_END_CONTENTS_RECRUIT_LIST_RES; + + public S2CQuestGetEndContentsRecruitListRes() + { + Unk1List = new List(); + } + + public uint Unk0 { get; set; } + public List Unk1List { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestGetEndContentsRecruitListRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt32(buffer, obj.Unk0); + WriteEntityList(buffer, obj.Unk1List); + } + + public override S2CQuestGetEndContentsRecruitListRes Read(IBuffer buffer) + { + S2CQuestGetEndContentsRecruitListRes obj = new S2CQuestGetEndContentsRecruitListRes(); + ReadServerResponse(buffer, obj); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1List = ReadEntityList(buffer); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEndNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEndNtc.cs new file mode 100644 index 000000000..9bf1469d6 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEndNtc.cs @@ -0,0 +1,37 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayEndNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_PLAY_END_NTC; + + public S2CQuestPlayEndNtc() + { + ContentsPlayEnd = new CDataContentsPlayEnd(); + } + + public int Unk0 { get; set; } + public CDataContentsPlayEnd ContentsPlayEnd { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayEndNtc obj) + { + WriteInt32(buffer, obj.Unk0); + WriteEntity(buffer, obj.ContentsPlayEnd); + } + + public override S2CQuestPlayEndNtc Read(IBuffer buffer) + { + S2CQuestPlayEndNtc obj = new S2CQuestPlayEndNtc(); + obj.Unk0 = ReadInt32(buffer); + obj.ContentsPlayEnd = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEndRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEndRes.cs new file mode 100644 index 000000000..a0e1ae399 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEndRes.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayEndRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_PLAY_END_RES; + + public S2CQuestPlayEndRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayEndRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CQuestPlayEndRes Read(IBuffer buffer) + { + S2CQuestPlayEndRes obj = new S2CQuestPlayEndRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryNtc.cs new file mode 100644 index 000000000..4c12a147b --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryNtc.cs @@ -0,0 +1,33 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayEntryNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_PLAY_ENTRY_NTC; + + public S2CQuestPlayEntryNtc() + { + } + + public uint CharacterId { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayEntryNtc obj) + { + WriteUInt32(buffer, obj.CharacterId); + } + + public override S2CQuestPlayEntryNtc Read(IBuffer buffer) + { + S2CQuestPlayEntryNtc obj = new S2CQuestPlayEntryNtc(); + obj.CharacterId = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryRes.cs new file mode 100644 index 000000000..ab65953ee --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryRes.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayEntryRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_PLAY_ENTRY_RES; + + public S2CQuestPlayEntryRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayEntryRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CQuestPlayEntryRes Read(IBuffer buffer) + { + S2CQuestPlayEntryRes obj = new S2CQuestPlayEntryRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayStartTimerNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayStartTimerNtc.cs new file mode 100644 index 000000000..415a2856b --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayStartTimerNtc.cs @@ -0,0 +1,33 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayStartTimerNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_PLAY_START_TIMER_NTC; + + public S2CQuestPlayStartTimerNtc() + { + } + + public ulong PlayEndDateTime { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayStartTimerNtc obj) + { + WriteUInt64(buffer, obj.PlayEndDateTime); + } + + public override S2CQuestPlayStartTimerNtc Read(IBuffer buffer) + { + S2CQuestPlayStartTimerNtc obj = new S2CQuestPlayStartTimerNtc(); + obj.PlayEndDateTime = ReadUInt64(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayStartTimerRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayStartTimerRes.cs new file mode 100644 index 000000000..94d4ec1e9 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayStartTimerRes.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayStartTimerRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_PLAY_START_TIMER_RES; + + public S2CQuestPlayStartTimerRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayStartTimerRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CQuestPlayStartTimerRes Read(IBuffer buffer) + { + S2CQuestPlayStartTimerRes obj = new S2CQuestPlayStartTimerRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayerStartRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayerStartRes.cs new file mode 100644 index 000000000..cdb9d23c3 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayerStartRes.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayerStartRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_PLAY_START_RES; + + public S2CQuestPlayerStartRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayerStartRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CQuestPlayerStartRes Read(IBuffer buffer) + { + S2CQuestPlayerStartRes obj = new S2CQuestPlayerStartRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestTimeGainQuestPlayStartNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestTimeGainQuestPlayStartNtc.cs new file mode 100644 index 000000000..3fdd622c7 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestTimeGainQuestPlayStartNtc.cs @@ -0,0 +1,42 @@ + + +// CDataTimeGainQuestPlayStartData + +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestTimeGainQuestPlayStartNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC; + + public S2CQuestTimeGainQuestPlayStartNtc() + { + TimeGainQuestPlayStartData = new CDataContentsPlayStartData(); + } + + public CDataContentsPlayStartData TimeGainQuestPlayStartData {get; set;} + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestTimeGainQuestPlayStartNtc obj) + { + WriteEntity(buffer, obj.TimeGainQuestPlayStartData); + } + + public override S2CQuestTimeGainQuestPlayStartNtc Read(IBuffer buffer) + { + S2CQuestTimeGainQuestPlayStartNtc obj = new S2CQuestTimeGainQuestPlayStartNtc(); + obj.TimeGainQuestPlayStartData = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSeason62_26_16Ntc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSeason62_26_16Ntc.cs new file mode 100644 index 000000000..31755397c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSeason62_26_16Ntc.cs @@ -0,0 +1,39 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CSeason62_26_16Ntc : IPacketStructure + { + public PacketId Id => PacketId.S2C_SEASON_62_26_16_NTC; + + public S2CSeason62_26_16Ntc() + { + StageLayoutId = new CDataStageLayoutId(); + } + + public CDataStageLayoutId StageLayoutId { get; set; } + public uint Unk0 { get; set; } + public byte Unk1 { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CSeason62_26_16Ntc obj) + { + WriteEntity(buffer, obj.StageLayoutId); + WriteUInt32(buffer, obj.Unk0); + WriteByte(buffer, obj.Unk1); + } + + public override S2CSeason62_26_16Ntc Read(IBuffer buffer) + { + S2CSeason62_26_16Ntc obj = new S2CSeason62_26_16Ntc(); + obj.StageLayoutId = ReadEntity(buffer); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadByte(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataEndNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataEndNtc.cs new file mode 100644 index 000000000..0bc8c3f94 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataEndNtc.cs @@ -0,0 +1,34 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CSituationDataEndNtc : ServerResponse + { + public override PacketId Id => PacketId.S2C_63_1_16_NTC; + + public S2CSituationDataEndNtc() + { + } + + public uint Unk0 { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CSituationDataEndNtc obj) + { + WriteUInt32(buffer, obj.Unk0); + } + + public override S2CSituationDataEndNtc Read(IBuffer buffer) + { + S2CSituationDataEndNtc obj = new S2CSituationDataEndNtc(); + obj.Unk0 = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataStartNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataStartNtc.cs new file mode 100644 index 000000000..fd1e06210 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataStartNtc.cs @@ -0,0 +1,28 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CSituationDataStartNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_63_0_16_NTC; + + public uint Unk0 { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CSituationDataStartNtc obj) + { + WriteUInt32(buffer, obj.Unk0); + } + + public override S2CSituationDataStartNtc Read(IBuffer buffer) + { + S2CSituationDataStartNtc obj = new S2CSituationDataStartNtc(); + obj.Unk0 = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataUpdateObjectivesNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataUpdateObjectivesNtc.cs new file mode 100644 index 000000000..a47a05ca2 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CSituationDataUpdateObjectivesNtc.cs @@ -0,0 +1,52 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CSituationDataUpdateObjectivesNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_63_2_16_NTC; + + public S2CSituationDataUpdateObjectivesNtc() + { + ObjectiveList = new List(); + } + + public bool Unk0 { get; set; } + public byte Unk1 { get; set; } + public uint Unk2 { get; set; } + public uint Unk3 { get; set; } + public uint Unk4 { get; set; } + public uint Unk5 { get; set; } + public List ObjectiveList { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CSituationDataUpdateObjectivesNtc obj) + { + WriteBool(buffer, obj.Unk0); + WriteByte(buffer, obj.Unk1); + WriteUInt32(buffer, obj.Unk2); + WriteUInt32(buffer, obj.Unk3); + WriteUInt32(buffer, obj.Unk4); + WriteUInt32(buffer, obj.Unk5); + WriteEntityList(buffer, obj.ObjectiveList); + } + + public override S2CSituationDataUpdateObjectivesNtc Read(IBuffer buffer) + { + S2CSituationDataUpdateObjectivesNtc obj = new S2CSituationDataUpdateObjectivesNtc(); + obj.Unk0 = ReadBool(buffer); + obj.Unk1 = ReadByte(buffer); + obj.Unk2 = ReadUInt32(buffer); + obj.Unk3 = ReadUInt32(buffer); + obj.Unk4 = ReadUInt32(buffer); + obj.Unk5 = ReadUInt32(buffer); + obj.ObjectiveList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_10_16_NTC.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_10_16_NTC.cs new file mode 100644 index 000000000..f7df012bf --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_10_16_NTC.cs @@ -0,0 +1,41 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2C_63_10_16_NTC : ServerResponse + { + public override PacketId Id => PacketId.S2C_63_10_16_NTC; + + public S2C_63_10_16_NTC() + { + Unk1String = string.Empty; + } + + public uint Unk0 { get; set; } + public string Unk1String { get; set; } + public byte Unk2 { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2C_63_10_16_NTC obj) + { + WriteUInt32(buffer, obj.Unk0); + WriteMtString(buffer, obj.Unk1String); + WriteByte(buffer, obj.Unk2); + } + + public override S2C_63_10_16_NTC Read(IBuffer buffer) + { + S2C_63_10_16_NTC obj = new S2C_63_10_16_NTC(); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1String = ReadMtString(buffer); + obj.Unk2 = ReadByte(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_7_16_NTC.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_7_16_NTC.cs new file mode 100644 index 000000000..2e1aae8a5 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_7_16_NTC.cs @@ -0,0 +1,63 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2C_63_7_16_NTC : ServerResponse + { + public override PacketId Id => PacketId.S2C_63_7_16_NTC; + + public S2C_63_7_16_NTC() + { + Unk0List = new List(); + } + + public List Unk0List { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2C_63_7_16_NTC obj) + { + WriteEntityList(buffer, obj.Unk0List); + } + + public override S2C_63_7_16_NTC Read(IBuffer buffer) + { + S2C_63_7_16_NTC obj = new S2C_63_7_16_NTC(); + obj.Unk0List = ReadEntityList(buffer); + return obj; + } + } + } + + public class CDataS2C_63_7_16 + { + public CDataS2C_63_7_16() + { + LayoutId = new CDataStageLayoutId(); + } + + public CDataStageLayoutId LayoutId { get; set; } + public uint Unk0 { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataS2C_63_7_16 obj) + { + WriteEntity(buffer, obj.LayoutId); + WriteUInt32(buffer, obj.Unk0); + } + + public override CDataS2C_63_7_16 Read(IBuffer buffer) + { + CDataS2C_63_7_16 obj = new CDataS2C_63_7_16(); + obj.LayoutId = ReadEntity(buffer); + obj.Unk0 = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataCharacterListElement.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataCharacterListElement.cs index ef1acf876..99eb3205f 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataCharacterListElement.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataCharacterListElement.cs @@ -1,4 +1,4 @@ -using Arrowgene.Buffers; +using Arrowgene.Buffers; using Arrowgene.Ddon.Shared.Model; namespace Arrowgene.Ddon.Shared.Entity.Structure diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataCommonU64.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataCommonU64.cs new file mode 100644 index 000000000..f9fb523e1 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataCommonU64.cs @@ -0,0 +1,35 @@ +using Arrowgene.Buffers; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataCommonU64 + { + public CDataCommonU64(ulong value) + { + Value = value; + } + + public CDataCommonU64() + { + Value = 0; + } + + public ulong Value { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataCommonU64 obj) + { + WriteUInt64(buffer, obj.Value); + } + + public override CDataCommonU64 Read(IBuffer buffer) + { + CDataCommonU64 obj = new CDataCommonU64(); + obj.Value = ReadUInt64(buffer); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataContentsPlayEnd.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataContentsPlayEnd.cs new file mode 100644 index 000000000..316c8236d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataContentsPlayEnd.cs @@ -0,0 +1,51 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataContentsPlayEnd + { + public CDataContentsPlayEnd() + { + RewardItemDetailList = new List(); + } + + public uint Gold { get; set; } + public uint Exp { get; set; } + public uint Rim { get; set; } + public uint PlayTimeMillSec { get; set; } + public uint Unk0 { get; set; } + public uint Unk1 { get; set; } + public uint Unk2 { get; set; } + public List RewardItemDetailList { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataContentsPlayEnd obj) + { + WriteUInt32(buffer, obj.Gold); + WriteUInt32(buffer, obj.Exp); + WriteUInt32(buffer, obj.Rim); + WriteUInt32(buffer, obj.PlayTimeMillSec); + WriteUInt32(buffer, obj.Unk0); + WriteUInt32(buffer, obj.Unk1); + WriteUInt32(buffer, obj.Unk2); + WriteEntityList(buffer, obj.RewardItemDetailList); + } + + public override CDataContentsPlayEnd Read(IBuffer buffer) + { + CDataContentsPlayEnd obj = new CDataContentsPlayEnd(); + obj.Gold = ReadUInt32(buffer); + obj.Exp = ReadUInt32(buffer); + obj.Rim = ReadUInt32(buffer); + obj.PlayTimeMillSec = ReadUInt32(buffer); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadUInt32(buffer); + obj.Unk2 = ReadUInt32(buffer); + obj.RewardItemDetailList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataContentsPlayStartData.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataContentsPlayStartData.cs new file mode 100644 index 000000000..3af5091f3 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataContentsPlayStartData.cs @@ -0,0 +1,70 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataContentsPlayStartData + { + public CDataContentsPlayStartData() + { + QuestProcessStateList = new List(); + QuestEnemyInfoList = new List(); + QuestLayoutFlagSetInfoList = new List(); + QuestPhaseGroupIdList = new List(); + Unk3List = new List(); + } + + public uint KeyId { get; set; } + public uint QuestScheudleId { get; set; } + public uint QuestId { get; set; } + public uint BaseLevel { get; set; } + public byte StartPos { get; set; } + public bool Unk0 { get; set; } + public bool Unk1 { get; set; } + public bool Unk2 { get; set; } + public List QuestProcessStateList { get; set; } + public List QuestEnemyInfoList { get; set; } + public List QuestLayoutFlagSetInfoList { get; set; } + public List QuestPhaseGroupIdList { get; set; } + public List Unk3List { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataContentsPlayStartData obj) + { + WriteUInt32(buffer, obj.KeyId); + WriteUInt32(buffer, obj.QuestScheudleId); + WriteUInt32(buffer, obj.QuestId); + WriteUInt32(buffer, obj.BaseLevel); + WriteByte(buffer, obj.StartPos); + WriteBool(buffer, obj.Unk0); + WriteBool(buffer, obj.Unk1); + WriteBool(buffer, obj.Unk2); + WriteEntityList(buffer, obj.QuestProcessStateList); + WriteEntityList(buffer, obj.QuestEnemyInfoList); + WriteEntityList(buffer, obj.QuestLayoutFlagSetInfoList); + WriteEntityList(buffer, obj.QuestPhaseGroupIdList); + WriteEntityList(buffer, obj.Unk3List); + } + + public override CDataContentsPlayStartData Read(IBuffer buffer) + { + CDataContentsPlayStartData obj = new CDataContentsPlayStartData(); + obj.KeyId = ReadUInt32(buffer); + obj.QuestScheudleId = ReadUInt32(buffer); + obj.QuestId = ReadUInt32(buffer); + obj.BaseLevel = ReadUInt32(buffer); + obj.StartPos = ReadByte(buffer); + obj.Unk0 = ReadBool(buffer); + obj.Unk1 = ReadBool(buffer); + obj.Unk2 = ReadBool(buffer); + obj.QuestProcessStateList = ReadEntityList(buffer); + obj.QuestEnemyInfoList = ReadEntityList(buffer); + obj.QuestLayoutFlagSetInfoList = ReadEntityList(buffer); + obj.QuestPhaseGroupIdList = ReadEntityList(buffer); + obj.Unk3List = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardListParam.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardListParam.cs new file mode 100644 index 000000000..3e70549f6 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardListParam.cs @@ -0,0 +1,46 @@ +using Arrowgene.Buffers; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataEntryBoardListParam + { + public ulong EntryId { get; set; } // Board Entry ID? + public ushort SortieMin { get; set; } + public ushort NoPartyMembers { get; set; } + public ushort Unk3 { get; set; } + public ushort TimeOut { get; set; } // Looks like maximum time in seconds + public ushort Unk5 { get; set; } // Level or item level? + public bool Unk6 { get; set; } + public uint Unk7 { get; set; } + + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataEntryBoardListParam obj) + { + WriteUInt64(buffer, obj.EntryId); + WriteUInt16(buffer, obj.SortieMin); + WriteUInt16(buffer, obj.NoPartyMembers); + WriteUInt16(buffer, obj.Unk3); + WriteUInt16(buffer, obj.TimeOut); + WriteUInt16(buffer, obj.Unk5); + WriteBool(buffer, obj.Unk6); + WriteUInt32(buffer, obj.Unk7); + } + + public override CDataEntryBoardListParam Read(IBuffer buffer) + { + CDataEntryBoardListParam obj = new CDataEntryBoardListParam(); + obj.EntryId = ReadUInt64(buffer); + obj.SortieMin = ReadUInt16(buffer); + obj.NoPartyMembers = ReadUInt16(buffer); + obj.Unk3 = ReadUInt16(buffer); + obj.TimeOut = ReadUInt16(buffer); + obj.Unk5 = ReadUInt16(buffer); + obj.Unk6 = ReadBool(buffer); + obj.Unk7 = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryItem.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryItem.cs new file mode 100644 index 000000000..4bc12fc69 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryItem.cs @@ -0,0 +1,51 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataEntryItem + { + public CDataEntryItem() + { + Param = new CDataEntryItemParam(); + EntryMemberList = new List(); + } + + public uint Id { get; set; } + public CDataEntryItemParam Param { get; set; } + public List EntryMemberList { get; set; } + public ushort BoardRequiredAvgItemRank { get; set; } + public ushort TimeOut { get; set; } + public uint PartyLeaderCharacterId { get; set; } + public bool Unk0 { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataEntryItem obj) + { + WriteUInt32(buffer, obj.Id); + WriteEntity(buffer, obj.Param); + WriteEntityList(buffer, obj.EntryMemberList); + WriteUInt16(buffer, obj.BoardRequiredAvgItemRank); + WriteUInt16(buffer, obj.TimeOut); + WriteUInt32(buffer, obj.PartyLeaderCharacterId); + WriteBool(buffer, obj.Unk0); + } + + public override CDataEntryItem Read(IBuffer buffer) + { + CDataEntryItem obj = new CDataEntryItem(); + obj.Id = ReadUInt32(buffer); + obj.Param = ReadEntity(buffer); + obj.EntryMemberList = ReadEntityList(buffer); + obj.BoardRequiredAvgItemRank = ReadUInt16(buffer); + obj.TimeOut = ReadUInt16(buffer); + obj.PartyLeaderCharacterId = ReadUInt32(buffer); + obj.Unk0 = ReadBool(buffer); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryItemParam.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryItemParam.cs new file mode 100644 index 000000000..896e41c23 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryItemParam.cs @@ -0,0 +1,64 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataEntryItemParam + { + public CDataEntryItemParam() + { + Comment = string.Empty; + EntryRecruitList = new List(); + } + + public bool PasswordOn { get; set; } + public bool PawnOn { get; set; } + + public int BottomEntryJobLevel { get; set; } // Minimum Level? + public int TopEntryJobLevel { get; set; } // Maximum Level? + public ushort RequiredItemRank { get; set; } + + public byte ItemRankType { get; set; } + + public uint ItemRankCheckRoleType { get; set; } + public ushort MinEntryNum { get; set; } + public ushort MaxEntryNum { get; set; } + public string Comment { get; set; } + public List EntryRecruitList { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataEntryItemParam obj) + { + WriteBool(buffer, obj.PasswordOn); + WriteBool(buffer, obj.PawnOn); + WriteInt32(buffer, obj.BottomEntryJobLevel); + WriteInt32(buffer, obj.TopEntryJobLevel); + WriteUInt16(buffer, obj.RequiredItemRank); + WriteByte(buffer, obj.ItemRankType); + WriteUInt32(buffer, obj.ItemRankCheckRoleType); + WriteUInt16(buffer, obj.MinEntryNum); + WriteUInt16(buffer, obj.MaxEntryNum); + WriteMtString(buffer, obj.Comment); + WriteEntityList(buffer, obj.EntryRecruitList); + } + + public override CDataEntryItemParam Read(IBuffer buffer) + { + CDataEntryItemParam obj = new CDataEntryItemParam(); + obj.PasswordOn = ReadBool(buffer); + obj.PawnOn = ReadBool(buffer); + obj.BottomEntryJobLevel = ReadInt32(buffer); + obj.TopEntryJobLevel = ReadInt32(buffer); + obj.RequiredItemRank = ReadUInt16(buffer); + obj.ItemRankType = ReadByte(buffer); + obj.ItemRankCheckRoleType = ReadUInt32(buffer); + obj.MinEntryNum = ReadUInt16(buffer); + obj.MaxEntryNum = ReadUInt16(buffer); + obj.Comment = ReadMtString(buffer); + obj.EntryRecruitList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryMemberData.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryMemberData.cs new file mode 100644 index 000000000..bc310fd25 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryMemberData.cs @@ -0,0 +1,39 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataEntryMemberData + { + public CDataEntryMemberData() + { + CharacterListElement = new CDataCharacterListElement(); + } + + public ushort Id { get; set; } + public bool EntryFlag { get; set; } + public uint Unk0 { get; set; } + public CDataCharacterListElement CharacterListElement { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataEntryMemberData obj) + { + WriteUInt16(buffer, obj.Id); + WriteBool(buffer, obj.EntryFlag); + WriteUInt32(buffer, obj.Unk0); + WriteEntity(buffer, obj.CharacterListElement); + } + + public override CDataEntryMemberData Read(IBuffer buffer) + { + CDataEntryMemberData obj = new CDataEntryMemberData(); + obj.Id = ReadUInt16(buffer); + obj.EntryFlag = ReadBool(buffer); + obj.Unk0 = ReadUInt32(buffer); + obj.CharacterListElement = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryRecruitData.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryRecruitData.cs new file mode 100644 index 000000000..1548f9c28 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryRecruitData.cs @@ -0,0 +1,36 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataEntryRecruitData + { + public CDataEntryRecruitData() + { + EnableJobList = new List(); + } + + public ushort Id { get; set; } + public uint Unk1 { get; set; } + public List EnableJobList { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataEntryRecruitData obj) + { + WriteUInt16(buffer, obj.Id); + WriteUInt32(buffer, obj.Unk1); + WriteEntityList(buffer, obj.EnableJobList); + } + + public override CDataEntryRecruitData Read(IBuffer buffer) + { + CDataEntryRecruitData obj = new CDataEntryRecruitData(); + obj.Id = ReadUInt16(buffer); + obj.Unk1 = ReadUInt32(buffer); + obj.EnableJobList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryRecruitJob.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryRecruitJob.cs new file mode 100644 index 000000000..07c32e3e6 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryRecruitJob.cs @@ -0,0 +1,29 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataEntryRecruitJob + { + public CDataEntryRecruitJob() + { + } + + public JobId Job { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataEntryRecruitJob obj) + { + WriteByte(buffer, (byte) obj.Job); + } + + public override CDataEntryRecruitJob Read(IBuffer buffer) + { + CDataEntryRecruitJob obj = new CDataEntryRecruitJob(); + obj.Job = (JobId) ReadByte(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataQuestList.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataQuestList.cs index fcd99e61a..87f5ef925 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataQuestList.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataQuestList.cs @@ -50,8 +50,6 @@ public CDataQuestList() public List QuestLayoutFlagSetInfoList { get; set; } public List DeliveryItemList { get; set; } public bool IsClientOrder { get; set; } - public bool IsEnable { get; set; } - // public bool CanProgress { get; set; } public class Serializer : EntitySerializer { diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataQuestRecruitListItem.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataQuestRecruitListItem.cs new file mode 100644 index 000000000..7f0d4ffd3 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataQuestRecruitListItem.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using Arrowgene.Buffers; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataQuestRecruitListItem + { + public CDataQuestRecruitListItem() + { + } + + public uint QuestScheduleId { get; set; } // ID? + public uint QuestId { get; set; } // NoMembers? + public uint GroupsRecruiting { get; set; } // MaxMembers? + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataQuestRecruitListItem obj) + { + WriteUInt32(buffer, obj.QuestScheduleId); + WriteUInt32(buffer, obj.QuestId); + WriteUInt32(buffer, obj.GroupsRecruiting); + } + + public override CDataQuestRecruitListItem Read(IBuffer buffer) + { + CDataQuestRecruitListItem obj = new CDataQuestRecruitListItem(); + obj.QuestScheduleId = ReadUInt32(buffer); + obj.QuestId = ReadUInt32(buffer); + obj.GroupsRecruiting = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardItemDetail.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardItemDetail.cs new file mode 100644 index 000000000..90a2d0ea5 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRewardItemDetail.cs @@ -0,0 +1,29 @@ +using Arrowgene.Buffers; + +namespace Arrowgene.Ddon.Shared.Entity.Structure; + +public class CDataRewardItemDetail +{ + public uint ItemId { get; set; } + public ushort Num { get; set; } + public byte Type { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataRewardItemDetail obj) + { + WriteUInt32(buffer, obj.ItemId); + WriteUInt16(buffer, obj.Num); + WriteByte(buffer, obj.Type); + } + + public override CDataRewardItemDetail Read(IBuffer buffer) + { + CDataRewardItemDetail obj = new CDataRewardItemDetail(); + obj.ItemId = ReadUInt32(buffer); + obj.Num = ReadUInt16(buffer); + obj.Type = ReadByte(buffer); + return obj; + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataSituationObjective.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataSituationObjective.cs new file mode 100644 index 000000000..72a1cad97 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataSituationObjective.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataSituationObjective + { + public CDataSituationObjective() + { + Message = string.Empty; + } + + public byte Unk0 { get; set; } + public uint Unk1 { get; set; } + public string Message { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataSituationObjective obj) + { + WriteByte(buffer, obj.Unk0); + WriteUInt32(buffer, obj.Unk1); + WriteMtString(buffer, obj.Message); + } + + public override CDataSituationObjective Read(IBuffer buffer) + { + CDataSituationObjective obj = new CDataSituationObjective(); + obj.Unk0 = ReadByte(buffer); + obj.Unk1 = ReadUInt32(buffer); + obj.Message = ReadMtString(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestList.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestList.cs new file mode 100644 index 000000000..9bdf5ff13 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestList.cs @@ -0,0 +1,65 @@ +using Arrowgene.Buffers; +using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataTimeGainQuestList + { + public CDataTimeGainQuestList() + { + Param = new CDataQuestList(); + RewardItemDetailList = new List(); + Restrictions = new CDataTimeGainQuestRestrictions(); + Unk2List = new List(); + } + + public CDataQuestList Param { get; set; } + public uint PlayTimeInSec { get; set; } + public bool IsNoTimeup { get; set; } + public bool Unk0 { get; set; } + public bool IsJoinCharacter { get; set; } + public bool IsJoinPawn { get; set; } + public bool Unk1 { get; set; } + public byte JoinPawnNum { get; set; } + public List RewardItemDetailList { get; set; } + public CDataTimeGainQuestRestrictions Restrictions { get; set; } + public List Unk2List { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataTimeGainQuestList obj) + { + WriteEntity(buffer, obj.Param); + WriteUInt32(buffer, obj.PlayTimeInSec); + WriteBool(buffer, obj.IsNoTimeup); + WriteBool(buffer, obj.Unk0); + WriteBool(buffer, obj.IsJoinCharacter); + WriteBool(buffer, obj.IsJoinPawn); + WriteBool(buffer, obj.Unk1); + WriteByte(buffer, obj.JoinPawnNum); + WriteEntityList(buffer, obj.RewardItemDetailList); + WriteEntity(buffer, obj.Restrictions); + WriteEntityList(buffer, obj.Unk2List); + } + + public override CDataTimeGainQuestList Read(IBuffer buffer) + { + CDataTimeGainQuestList obj = new CDataTimeGainQuestList(); + obj.Param = ReadEntity(buffer); + obj.PlayTimeInSec = ReadUInt32(buffer); + obj.IsNoTimeup = ReadBool(buffer); + obj.Unk0 = ReadBool(buffer); + obj.IsJoinCharacter = ReadBool(buffer); + obj.IsJoinPawn = ReadBool(buffer); + obj.Unk1 = ReadBool(buffer); + obj.JoinPawnNum = ReadByte(buffer); + obj.RewardItemDetailList = ReadEntityList(buffer); + obj.Restrictions = ReadEntity(buffer); + obj.Unk2List = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestRestrictions.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestRestrictions.cs new file mode 100644 index 000000000..ac83dd8ce --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestRestrictions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataTimeGainQuestRestrictions + { + public CDataTimeGainQuestRestrictions() + { + Unk1List = new List(); + Unk2List = new List(); + Unk5List = new List(); + } + + public uint Unk0 { get; set; } + public List Unk1List { get; set; } + public List Unk2List { get; set; } + public bool RestrictArmor { get; set; } + public bool RestrictJewlery { get; set; } + public List Unk5List { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataTimeGainQuestRestrictions obj) + { + WriteUInt32(buffer, obj.Unk0); + WriteEntityList(buffer, obj.Unk1List); + WriteEntityList(buffer, obj.Unk2List); + WriteBool(buffer, obj.RestrictArmor); + WriteBool(buffer, obj.RestrictJewlery); + WriteEntityList(buffer, obj.Unk5List); + } + + public override CDataTimeGainQuestRestrictions Read(IBuffer buffer) + { + CDataTimeGainQuestRestrictions obj = new CDataTimeGainQuestRestrictions(); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1List = ReadEntityList(buffer); + obj.Unk2List = ReadEntityList(buffer); + obj.RestrictArmor = ReadBool(buffer); + obj.RestrictJewlery = ReadBool(buffer); + obj.Unk5List = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestUnk1Unk2.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestUnk1Unk2.cs new file mode 100644 index 000000000..f65680721 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestUnk1Unk2.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataTimeGainQuestUnk1Unk2 + { + public CDataTimeGainQuestUnk1Unk2() + { + } + + public uint Unk0 { get; set; } + public bool Unk1 { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataTimeGainQuestUnk1Unk2 obj) + { + WriteUInt32(buffer, obj.Unk0); + WriteBool(buffer, obj.Unk1); + } + + public override CDataTimeGainQuestUnk1Unk2 Read(IBuffer buffer) + { + CDataTimeGainQuestUnk1Unk2 obj = new CDataTimeGainQuestUnk1Unk2(); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadBool(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestUnk2.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestUnk2.cs new file mode 100644 index 000000000..add7a67bf --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestUnk2.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataTimeGainQuestUnk2 + { + public CDataTimeGainQuestUnk2() + { + WalletPointList = new List(); + Unk0List = new List(); + } + + public List WalletPointList { get; set; } + + public List Unk0List { get; set;} + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataTimeGainQuestUnk2 obj) + { + WriteEntityList(buffer, obj.WalletPointList); + WriteEntityList(buffer, obj.Unk0List); + } + + public override CDataTimeGainQuestUnk2 Read(IBuffer buffer) + { + CDataTimeGainQuestUnk2 obj = new CDataTimeGainQuestUnk2(); + obj.WalletPointList = ReadEntityList(buffer); + obj.Unk0List = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json index 95565f0bf..2547ff771 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json @@ -72194,118 +72194,6 @@ 397, "00:00,23:59" ], - [ - 288, - 0, - 8, - 0, - "0x011020", - 2298, - 0, - 100, - 103, - 59, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - 0, - 100000, - 393, - "00:00,23:59" - ], - [ - 288, - 0, - 8, - 0, - "0x011021", - 2298, - 0, - 100, - 103, - 60, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - 0, - 100000, - 394, - "00:00,23:59" - ], - [ - 288, - 0, - 8, - 0, - "0x011021", - 2298, - 0, - 100, - 103, - 60, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - 0, - 100000, - 394, - "00:00,23:59" - ], - [ - 288, - 0, - 8, - 0, - "0x011027", - 2298, - 0, - 100, - 103, - 66, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - 0, - 100000, - 392, - "00:00,23:59" - ], [ 335, 0, @@ -184726,34 +184614,6 @@ 421, "00:00,23:59" ], - [ - 362, - 0, - 0, - 0, - "0x021003", - 2298, - 0, - 100, - 100, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - true, - true, - false, - false, - 0, - 0, - 1000000, - 408, - "00:00,23:59" - ], [ 385, 0, @@ -229582,62 +229442,6 @@ 7, "00:00,23:59" ], - [ - 286, - 0, - 1, - 0, - "0x010100", - 2298, - 0, - 100, - 10, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - 0, - 136, - 7, - "00:00,23:59" - ], - [ - 289, - 0, - 0, - 0, - "0x010100", - 2298, - 0, - 100, - 10, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - 0, - 136, - 7, - "00:00,23:59" - ], [ 308, 0, @@ -230170,34 +229974,6 @@ 386, "00:00,23:59" ], - [ - 290, - 0, - 5, - 0, - "0x010606", - 2298, - 0, - 100, - 80, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 80, - 0, - 84000, - 388, - "00:00,23:59" - ], [ 491, 0, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000030.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000030.json index 636ce68e4..f67bf6c8d 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000030.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000030.json @@ -43,10 +43,10 @@ "id": 3 }, "event_id": 100 - }, + }, { "type": "PlayEvent", - "announce_type": "Accept", + "announce_type": "Accept", "stage_id": { "id": 76 }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json new file mode 100644 index 000000000..9089d2f69 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json @@ -0,0 +1,492 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "The Call of the Catacombs (EM1)", + "quest_id": 50101020, + "base_level": 58, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 1, + "minimum_members": 1, + "playtime": 3600, + "solo_only": true, + "max_pawns": 3, + "phase_groups": [733, 0, 733] + }, + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 9456, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "stage_id": { + "id": 293, + "group_id": 17 + }, + "enemies": [ + { + "comment": "Orc Soldier", + "enemy_id": "0x015800", + "level": 58, + "exp": 0 + }, + { + "comment": "Orc Soldier", + "enemy_id": "0x015800", + "level": 58, + "exp": 0 + }, + { + "comment": "Orc Soldier", + "enemy_id": "0x015800", + "level": 58, + "exp": 0 + }, + { + "comment": "Orc Soldier", + "enemy_id": "0x015800", + "level": 58, + "exp": 0 + } + ] + }, + { + "comment": "Key Monsters", + "stage_id": { + "id": 293, + "group_id": 20 + }, + "enemies": [ + { + "comment": "Captain Orc", + "enemy_id": "0x015820", + "level": 58, + "exp": 0 + }, + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 58, + "exp": 0 + }, + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 58, + "exp": 0 + }, + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 58, + "exp": 0 + } + ] + }, + { + "stage_id": { + "id": 293, + "group_id": 3 + }, + "enemies": [ + { + "comment": "Frost Corpse Punisher", + "enemy_id": "0x010511", + "level": 58, + "exp": 0 + }, + { + "comment": "Frost Corpse Punisher", + "enemy_id": "0x010511", + "level": 58, + "exp": 0 + }, + { + "comment": "Wight", + "enemy_id": "0x015600", + "level": 58, + "exp": 0, + "is_boss": true, + "is_boss_bgm": false + }, + { + "comment": "Frost Corpse Punisher", + "enemy_id": "0x010511", + "level": 58, + "exp": 0 + }, + { + "comment": "Frost Corpse Punisher", + "enemy_id": "0x010511", + "level": 58, + "exp": 0 + }, + { + "comment": "Wight", + "enemy_id": "0x015600", + "level": 58, + "exp": 0, + "is_boss": true, + "is_boss_bgm": false + } + ] + }, + { + "comment": "Key Monsters", + "stage_id": { + "id": 293, + "group_id": 21 + }, + "enemies": [ + { + "comment": "Living Armor", + "enemy_id": "0x010306", + "level": 58, + "exp": 0, + "is_boss": true + }, + { + "comment": "Living Armor", + "enemy_id": "0x010306", + "level": 58, + "exp": 0, + "is_boss": true + }, + { + "comment": "Skull Lord", + "enemy_id": "0x010313", + "level": 58, + "exp": 0 + }, + { + "comment": "Skull Lord", + "enemy_id": "0x010313", + "level": 58, + "exp": 0 + }, + { + "comment": "Skeleton Sorcerer", + "enemy_id": "0x010309", + "level": 58, + "exp": 0 + }, + { + "comment": "Skeleton Sorcerer", + "enemy_id": "0x010309", + "level": 58, + "exp": 0 + } + ] + }, + { + "stage_id": { + "id": 293, + "group_id": 7 + }, + "starting_index": 1, + "enemies": [ + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + } + ] + }, + { + "comment": "Key Monsters", + "stage_id": { + "id": 293, + "group_id": 22 + }, + "starting_index": 1, + "enemies": [ + { + "comment": "Shadow Chimera", + "enemy_id": "0x015203", + "level": 58, + "exp": 0, + "is_boss": true + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + }, + { + "comment": "Shadow Goblin", + "enemy_id": "0x011130", + "level": 58, + "exp": 0 + } + ] + }, + { + "stage_id": { + "id": 293, + "group_id": 10 + }, + "comment": "Boss Node (6)", + "enemies": [ + { + "comment": "Death Knight", + "enemy_id": "0x010310", + "named_enemy_params_id": 631, + "level": 58, + "exp": 0, + "is_boss": true + }, + { + "comment": "Witch", + "enemy_id": "0x015604", + "level": 58, + "exp": 0, + "is_boss": true, + "is_boss_bgm": false + }, + { + "comment": "Witch", + "enemy_id": "0x015604", + "level": 58, + "exp": 0, + "is_boss": true, + "is_boss_bgm": false + }, + { + "comment": "Witch", + "enemy_id": "0x015604", + "level": 58, + "exp": 0, + "is_boss": true, + "is_boss_bgm": false + } + ] + }, + { + "stage_id": { + "id": 293, + "group_id": 11 + }, + "enemies": [ + { + "comment": "Sludgeman", + "enemy_id": "0x010510", + "level": 58, + "exp": 0 + }, + { + "comment": "Sludgeman", + "enemy_id": "0x010510", + "level": 58, + "exp": 0 + }, + { + "comment": "Sludgeman", + "enemy_id": "0x010510", + "level": 58, + "exp": 0 + }, + { + "comment": "Sludgeman", + "enemy_id": "0x010510", + "level": 58, + "exp": 0 + } + ] + }, + { + "stage_id": { + "id": 293, + "group_id": 16 + }, + "enemies": [ + { + "comment": "Ghost Mail", + "enemy_id": "0x010311", + "level": 58, + "exp": 0, + "named_enemy_params_id": 705, + "is_boss": true, + "is_boss_bgm": false + }, + { + "comment": "Ghost Mail", + "enemy_id": "0x010311", + "level": 58, + "exp": 0, + "named_enemy_params_id": 705, + "is_boss": true, + "is_boss_bgm": false + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsStageNo", + "stage_id": { + "id": 293 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EventEnd", "Param1": 733, "Param2": 0} + ] + }, + { + "type": "DiscoverEnemy", + "groups": [0], + "announce_type": "Start" + }, + { + "type": "KillGroup", + "reset_group": false, + "announce_type": "Update", + "groups": [0] + }, + { + "type": "KillGroup", + "groups": [1] + }, + { + "type": "DiscoverEnemy", + "groups": [2] + }, + { + "type": "KillGroup", + "reset_group": false, + "groups": [2] + }, + { + "type": "KillGroup", + "groups": [3] + }, + { + "type": "DiscoverEnemy", + "groups": [4] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [4] + }, + { + "type": "KillGroup", + "groups": [5] + }, + { + "type": "DiscoverEnemy", + "flags": [ + {"type": "MyQst", "action": "Set", "value": 1, "comment": "Spawns other monster subgroups"} + ], + "groups": [6] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [6] + }, + { + "type": "MyQstFlags", + "check_flags": [2, 3] + } + ] + }, + { + "comment": "Boss trash (1)", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [1] + }, + { + "type": "KillGroup", + "groups": [7] + }, + { + "type": "MyQstFlags", + "set_flags": [2] + } + ] + }, + { + "comment": "Boss trash (2)", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [1] + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 733, "Param2": 10, "Param3": 0, "Param4": 50} + ] + }, + { + "type": "KillGroup", + "groups": [8] + }, + { + "type": "MyQstFlags", + "set_flags": [3] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json new file mode 100644 index 000000000..dd0b10274 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json @@ -0,0 +1,307 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "Drawn to Ancient Power (EM2)", + "quest_id": 50102020, + "base_level": 60, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 1, + "minimum_members": 1, + "playtime": 3600, + "solo_only": true, + "max_pawns": 3, + "phase_groups": [419, 0, 419] + }, + "order_conditions": [ + {"type": "ClearExtremeMission", "Param1": 50101020} + ], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 9457, + "num": 3 + } + ] + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 9378, + "num": 3 + } + ] + } + + ], + "enemy_groups" : [ + { + "comment": "Phase 1", + "stage_id": { + "id": 286, + "group_id": 1 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Shadow Chimera", + "enemy_id": "0x015203", + "index": 1, + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "White Chimera", + "enemy_id": "0x015202", + "index": 0, + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Chimera", + "enemy_id": "0x015200", + "index": 2, + "level": 60, + "exp": 0, + "is_boss": true + } + ] + }, + { + "comment": "Phase 2", + "stage_id": { + "id": 286, + "group_id": 1 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Cyclops", + "enemy_id": "0x015001", + "index": 3, + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Cyclops", + "enemy_id": "0x015001", + "index": 4, + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Captain Orc (Lord of a Hundred Battles)", + "enemy_id": "0x015820", + "index": 5, + "level": 60, + "exp": 0, + "named_enemy_params_id": 698, + "is_boss": true + } + ] + }, + { + "comment": "Phase 2 Adds", + "stage_id": { + "id": 286, + "group_id": 0 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Orc Tropper", + "enemy_id": "0x015812", + "index": 13, + "level": 60, + "exp": 0 + }, + { + "comment": "Orc Tropper", + "enemy_id": "0x015812", + "index": 9, + "level": 60, + "exp": 0 + }, + { + "comment": "Orc Tropper", + "enemy_id": "0x015812", + "index": 2, + "level": 60, + "exp": 0 + }, + { + "comment": "Orc Tropper", + "enemy_id": "0x015812", + "index": 15, + "level": 60, + "exp": 0 + } + ] + }, + { + "comment": "Phase 3", + "stage_id": { + "id": 286, + "group_id": 0 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Rock Saurian", + "enemy_id": "0x010450", + "index": 3, + "level": 60, + "exp": 0 + }, + { + "comment": "Rock Saurian", + "enemy_id": "0x010450", + "index": 11, + "level": 60, + "exp": 0 + }, + { + "comment": "Geo Golem", + "enemy_id": "0x015104", + "index": 5, + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Rock Saurian", + "enemy_id": "0x010450", + "index": 18, + "level": 60, + "exp": 0 + }, + { + "comment": "Rock Saurian", + "enemy_id": "0x010450", + "index": 15, + "level": 60, + "exp": 0 + } + ] + }, + { + "comment": "Phase 4", + "stage_id": { + "id": 286, + "group_id": 0 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Mist Drake", + "enemy_id": "0x015710", + "index": 0, + "level": 60, + "exp": 0, + "named_enemy_params_id": 682, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsStageNo", + "stage_id": { + "id": 286 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EventEnd", "Param1": 419, "Param2": 0} + ] + }, + { + "type": "KillGroup", + "stage_start": 1, + "groups": [0] + }, + { + "type": "IsStageNo", + "stage_clear": 1, + "stage_id": { + "id": 286 + } + }, + { + "type": "KillGroup", + "stage_start": 2, + "flags": [ + {"type": "MyQst", "action": "Set", "value": 2, "comment": "Starts adds process"} + ], + "groups": [1] + }, + { + "type": "KillGroup", + "reset_group": false, + "groups": [2] + }, + { + "type": "IsStageNo", + "stage_clear": 2, + "stage_id": { + "id": 286 + } + }, + { + "type": "KillGroup", + "stage_start": 3, + "groups": [3] + }, + { + "type": "IsStageNo", + "stage_clear": 3, + "stage_id": { + "id": 286 + } + }, + { + "type": "KillGroup", + "stage_start": 4, + "groups": [4] + }, + { + "type": "IsStageNo", + "stage_clear": 4, + "stage_id": { + "id": 286 + } + } + ] + }, + { + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [2] + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 419, "Param2": 1, "Param3": 5, "Param4": 50} + ] + }, + { + "type": "SpawnGroup", + "groups": [2] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json new file mode 100644 index 000000000..254d5ee85 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json @@ -0,0 +1,301 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "The Ancient City's Legacy (EM3)", + "quest_id": 50103020, + "base_level": 60, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 1, + "minimum_members": 1, + "playtime": 3600, + "solo_only": true, + "max_pawns": 3, + "phase_groups": [702, 0, 702] + }, + "order_conditions": [ + {"type": "ClearExtremeMission", "Param1": 50102020} + ], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 9788, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Group 1", + "stage_id": { + "id": 290, + "group_id": 5 + }, + "enemies": [ + { + "comment": "Silver Roar", + "enemy_id": "0x015505", + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Ghoul", + "enemy_id": "0x015503", + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Ghoul", + "enemy_id": "0x015503", + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Orc Bringer", + "enemy_id": "0x015811", + "level": 60, + "exp": 0 + }, + { + "comment": "Orc Solider", + "enemy_id": "0x015800", + "level": 60, + "exp": 0 + }, + { + "comment": "Orc Solider", + "enemy_id": "0x015800", + "level": 60, + "exp": 0 + } + ] + }, + { + "comment": "Group 2", + "stage_id": { + "id": 290, + "group_id": 8 + }, + "enemies": [ + { + "comment": "Nightmare", + "enemy_id": "0x015305", + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Ent", + "enemy_id": "0x015031", + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Ent", + "enemy_id": "0x015031", + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Orc Bringer", + "enemy_id": "0x015811", + "level": 60, + "exp": 0 + }, + { + "comment": "Orc Bringer", + "enemy_id": "0x015811", + "level": 60, + "exp": 0 + } + ] + }, + { + "comment": "Group 3", + "stage_id": { + "id": 290, + "group_id": 10 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Mole Troll", + "enemy_id": "0x015041", + "level": 60, + "exp": 0, + "index": 0, + "is_boss": true + }, + { + "comment": "Mudman", + "enemy_id": "0x010509", + "level": 60, + "exp": 0, + "index": 4 + }, + { + "comment": "Mudman", + "enemy_id": "0x010509", + "level": 60, + "exp": 0, + "index": 9 + }, + { + "comment": "Mudman", + "enemy_id": "0x010509", + "level": 60, + "exp": 0, + "index": 5 + }, + { + "comment": "Mudman", + "enemy_id": "0x010509", + "level": 60, + "exp": 0, + "index": 7 + } + ] + }, + { + "comment": "Group 4", + "stage_id": { + "id": 290, + "group_id": 12 + }, + "enemies": [ + { + "comment": "Mist Wyrm", + "enemy_id": "0x015711", + "level": 60, + "exp": 0, + "is_boss": true + }, + { + "comment": "Mogok", + "enemy_id": "0x015840", + "level": 60, + "exp": 0, + "is_boss": true + } + ] + }, + { + "comment": "Group 4 Adds", + "stage_id": { + "id": 290, + "group_id": 23 + }, + "enemies": [ + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 60, + "exp": 0, + "repop_count": 50, + "repop_wait_second": 60 + }, + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 60, + "exp": 0, + "repop_count": 50, + "repop_wait_second": 60 + }, + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 60, + "exp": 0, + "repop_count": 50, + "repop_wait_second": 60 + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsStageNo", + "stage_id": { + "id": 290 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EventEnd", "Param1": 702, "Param2": 0} + ] + }, + { + "type": "DiscoverEnemy", + "announce_type": "Start", + "groups": [0] + }, + { + "type": "KillGroup", + "reset_group": false, + "end_contents_announce": 1, + "groups": [0] + }, + { + "type": "KillGroup", + "end_contents_announce": 2, + "groups": [1] + }, + { + "type": "KillGroup", + "end_contents_announce": 3, + "groups": [2] + }, + { + "type": "KillGroup", + "end_contents_announce": 4, + "flags": [ + {"type": "MyQst", "action": "Set", "value": 1, "comment": "Start Adds Spawner"} + ], + "groups": [3] + }, + { + "type": "IsStageNo", + "flags": [ + {"type": "MyQst", "action": "Set", "value": 2, "comment": "Destroy Adds still alive"} + ], + "stage_id": { + "id": 290 + } + } + ] + }, + { + "blocks": [ + { + "comment": "Wait for fight to start", + "type": "MyQstFlags", + "check_flags": [1] + }, + { + "type": "SpawnGroup", + "groups": [4], + "check_commands": [ + {"type": "MyQstFlagOn", "Param1": 2} + ] + }, + { + "type": "DestroyGroup", + "groups": [4] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json new file mode 100644 index 000000000..13da3b54d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json @@ -0,0 +1,253 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "The Shining Gate (EM4)", + "quest_id": 50104000, + "base_level": 60, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 1, + "minimum_members": 1, + "playtime": 3600, + "solo_only": true, + "max_pawns": 3, + "phase_groups": [421, 0, 421] + }, + "order_conditions": [ + {"type": "ClearExtremeMission", "Param1": 50103020} + ], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 9789, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 289, + "group_id": 0 + }, + "enemies": [ + { + "comment": "Golgorran", + "enemy_id": "0x021002", + "level": 60, + "exp": 0, + "named_enemy_params_id": 440, + "is_boss": true + } + ] + }, + { + "comment": "First Group", + "stage_id": { + "id": 288, + "group_id": 5 + }, + "enemies": [ + { + "comment": "Alchemized Skeleton", + "enemy_id": "0x010312", + "level": 60, + "exp": 0 + }, + { + "comment": "Alchemized Skeleton", + "enemy_id": "0x010312", + "level": 60, + "exp": 0 + }, + { + "comment": "Mergan Defender", + "enemy_id": "0x011024", + "level": 60, + "exp": 0, + "hm_present_no": 63 + }, + { + "comment": "Mergan Defender", + "enemy_id": "0x011024", + "level": 60, + "exp": 0, + "hm_present_no": 63 + }, + { + "comment": "Mergan Hunter", + "enemy_id": "0x011022", + "level": 60, + "exp": 0, + "hm_present_no": 61 + }, + { + "comment": "Mergan Mage", + "enemy_id": "0x011025", + "level": 60, + "exp": 0, + "hm_present_no": 64 + }, + { + "comment": "Mergan Element Archer", + "enemy_id": "0x011027", + "level": 60, + "exp": 0, + "hm_present_no": 66 + }, + { + "comment": "Mergan Healer", + "enemy_id": "0x011023", + "level": 60, + "exp": 0, + "hm_present_no": 62 + } + ] + }, + { + "comment": "Second Group", + "stage_id": { + "id": 288, + "group_id": 7 + }, + "enemies": [ + { + "comment": "Gigant Machina", + "enemy_id": "0x015850", + "level": 60, + "exp": 0, + "is_boss": true + } + ] + }, + { + "comment": "Third Group", + "stage_id": { + "id": 288, + "group_id": 10 + }, + "enemies": [ + { + "comment": "Drake", + "enemy_id": "0x015700", + "level": 60, + "exp": 0, + "is_boss": true, + "named_enemy_params_id": 708 + }, + { + "comment": "Wyrm", + "enemy_id": "0x015701", + "level": 60, + "exp": 0, + "is_boss": true, + "named_enemy_params_id": 709 + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsStageNo", + "stage_id": { + "id": 288 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EventEnd", "Param1": 421, "Param2": 0} + ] + }, + { + "type": "DiscoverEnemy", + "groups": [1], + "announce_type": "Start" + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [1] + }, + { + "type": "DiscoverEnemy", + "groups": [2], + "announce_type": "Update" + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [2] + }, + { + "type": "DiscoverEnemy", + "groups": [3], + "announce_type": "Update" + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [3] + }, + { + "type": "IsStageNo", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 1245, "comment": "Enables teleporter"}, + {"type": "MyQst", "action": "Set", "value": 1245, "comment": "Enables teleporter"} + ], + "announce_type": "Update", + "stage_id": { + "id": 289 + } + }, + { + "type": "PartyGather", + "announce_type": "Update", + "stage_id": { + "id": 289 + }, + "location": { + "x": -3230, + "y": 1250, + "z": 14 + } + }, + { + "type": "PlayEvent", + "flags": [], + "stage_id": { + "id": 289 + }, + "event_id": 0, + "jump_stage_id": { + "id": 289 + }, + "start_pos_no": 1 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "PlayEvent", + "flags": [], + "stage_id": { + "id": 289 + }, + "event_id": 5 + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json new file mode 100644 index 000000000..544c89398 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json @@ -0,0 +1,69 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "Agent of Corruption (EM5)", + "quest_id": 50201000, + "base_level": 65, + "minimum_item_rank": 22, + "discoverable": false, + "mission_params": { + "group": 2, + "minimum_members": 1, + "playtime": 3600, + "solo_only": true, + "max_pawns": 3, + "phase_groups": [113, 5, 113] + }, + "order_conditions": [ + {"type": "ClearExtremeMission", "Param1": 50104000} + ], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 11780, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 433, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Scourge", + "enemy_id": "0x071310", + "level": 65, + "exp": 0, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + } + ] + }, + { + "blocks": [ + { + "comment": "Wait for fight to start", + "type": "MyQstFlags", + "check_flags": [1] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json new file mode 100644 index 000000000..a2164d59c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json @@ -0,0 +1,297 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "Phantasmic Great Dragon (EM6)", + "quest_id": 50202000, + "base_level": 70, + "minimum_item_rank": 37, + "discoverable": false, + "mission_params": { + "group": 2, + "minimum_members": 1, + "playtime": 3600, + "solo_only": true, + "max_pawns": 3, + "phase_groups": [431, 0, 431] + }, + "order_conditions": [ + {"type": "ClearExtremeMission", "Param1": 50201000} + ], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 11810, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 362, + "group_id": 0 + }, + "enemies": [ + { + "comment": "Phantasmic Great Dragon", + "enemy_id": "0x021003", + "level": 70, + "exp": 0, + "named_enemy_params_id": 942, + "is_boss": true + } + ] + }, + { + "comment": "Add Group (1)", + "stage_id": { + "id": 362, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Mist Fighter", + "enemy_id": "0x011030", + "level": 45, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 68, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Fighter", + "enemy_id": "0x011030", + "level": 45, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 68, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Fighter", + "enemy_id": "0x011030", + "level": 45, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 68, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Fighter", + "enemy_id": "0x011030", + "level": 45, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 68, + "named_enemy_params_id": 781 + } + ] + }, + { + "comment": "Add Group (2)", + "stage_id": { + "id": 362, + "group_id": 2 + }, + "enemies": [ + { + "comment": "Mist Fighter", + "enemy_id": "0x011030", + "level": 70, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 68, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Hunter", + "enemy_id": "0x011031", + "level": 70, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 69, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Hunter", + "enemy_id": "0x011031", + "level": 70, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 69, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Sorcerer", + "enemy_id": "0x011033", + "level": 70, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 71, + "named_enemy_params_id": 781 + } + ] + }, + { + "comment": "Add Group (3)", + "stage_id": { + "id": 362, + "group_id": 2 + }, + "enemies": [ + { + "comment": "Mist Fighter", + "enemy_id": "0x011030", + "level": 70, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 68, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Hunter", + "enemy_id": "0x011031", + "level": 70, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 69, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Hunter", + "enemy_id": "0x011031", + "level": 70, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 69, + "named_enemy_params_id": 781 + }, + { + "comment": "Mist Sorcerer", + "enemy_id": "0x011033", + "level": 70, + "exp": 0, + "enemy_target_types_id": 1, + "hm_present_no": 71, + "named_enemy_params_id": 781 + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsStageNo", + "stage_id": { + "id": 362 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EventEnd", "Param1": 431, "Param2": 0} + ] + }, + { + "type": "KillGroup", + "groups": [0] + }, + { + "type": "PlayEvent", + "flags": [ + {"type": "MyQst", "action": "Set", "value": 2, "comment": "Destroys left over adds"} + ], + "stage_id": { + "id": 362 + }, + "event_id": 5 + } + ] + }, + { + "blocks": [ + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 431, "Param2": 0, "Param3": 0, "Param4": 75} + ] + }, + { + "type": "SpawnGroup", + "announce_type": "ExUpdate", + "groups": [1], + "check_commands": [ + {"type": "MyQstFlagOn", "Param1": 2} + ] + }, + { + "type": "DestroyGroup", + "groups": [1] + } + ] + }, + { + "blocks": [ + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 431, "Param2": 0, "Param3": 0, "Param4": 50} + ] + }, + { + "type": "IsStageNo", + "announce_type": "ExUpdate", + "stage_id": { + "id": 362 + } + }, + { + "type": "SpawnGroup", + "announce_type": "Caution", + "groups": [2], + "check_commands": [ + {"type": "MyQstFlagOn", "Param1": 2} + ] + }, + { + "type": "DestroyGroup", + "groups": [2] + } + ] + }, + { + "blocks": [ + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 431, "Param2": 0, "Param3": 0, "Param4": 25} + ] + }, + { + "type": "IsStageNo", + "announce_type": "ExUpdate", + "stage_id": { + "id": 362 + } + }, + { + "type": "SpawnGroup", + "announce_type": "Caution", + "groups": [3], + "check_commands": [ + {"type": "MyQstFlagOn", "Param1": 2} + ] + }, + { + "type": "DestroyGroup", + "groups": [3] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json new file mode 100644 index 000000000..3c210096a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json @@ -0,0 +1,69 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "Earth's Fury (EM7)", + "quest_id": 50203000, + "base_level": 75, + "minimum_item_rank": 52, + "discoverable": false, + "mission_params": { + "group": 2, + "minimum_members": 1, + "playtime": 3600, + "solo_only": true, + "max_pawns": 3, + "phase_groups": [124, 5, 124] + }, + "order_conditions": [ + {"type": "ClearExtremeMission", "Param1": 50202000} + ], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 15940, + "num": 3 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 459, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Wisened Tarasque", + "enemy_id": "0x080600", + "level": 75, + "exp": 0, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + } + ] + }, + { + "blocks": [ + { + "comment": "Wait for fight to start", + "type": "MyQstFlags", + "check_flags": [1] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json new file mode 100644 index 000000000..bf5af877a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json @@ -0,0 +1,77 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "Onset of Darkness (EM8)", + "quest_id": 50204002, + "base_level": 80, + "minimum_item_rank": 67, + "discoverable": false, + "mission_params": { + "group": 2, + "minimum_members": 1, + "playtime": 3600, + "solo_only": true, + "max_pawns": 3, + "phase_groups": [889, 0, 889] + }, + "order_conditions": [ + {"type": "ClearExtremeMission", "Param1": 50203000} + ], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 15997, + "num": 3 + } + ] + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 7555, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 458, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Black Knight", + "enemy_id": "0x080501", + "level": 80, + "exp": 0, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + }, + { + "type": "PlayEvent", + "flags": [], + "stage_id": { + "id": 458 + }, + "event_id": 5 + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Model/ContentsType.cs b/Arrowgene.Ddon.Shared/Model/ContentsType.cs new file mode 100644 index 000000000..49041d8a8 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/ContentsType.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Model +{ + public enum ContentsType : uint + { + Unknown = 0, + Begin = 1, + WorldQuest = 2, + Cycle = 3, + End = 4, + QuickPartyMainQuest = 6, + QuickPartyArea = 7, + Large = 8 + } +} diff --git a/Arrowgene.Ddon.Shared/Model/Enemy.cs b/Arrowgene.Ddon.Shared/Model/Enemy.cs index 62dd4332c..5334183b8 100644 --- a/Arrowgene.Ddon.Shared/Model/Enemy.cs +++ b/Arrowgene.Ddon.Shared/Model/Enemy.cs @@ -39,6 +39,7 @@ public Enemy(Enemy enemy) SpawnTimeEnd = enemy.SpawnTimeEnd; Experience = enemy.Experience; DropsTable = enemy.DropsTable; + NotifyStrongEnemy = enemy.NotifyStrongEnemy; } public uint Id { get; set; } @@ -66,6 +67,7 @@ public Enemy(Enemy enemy) public long SpawnTimeEnd { get; set; } public uint Experience { get; set; } public DropsTable DropsTable { get; set; } + public bool NotifyStrongEnemy { get; set; } public uint UINameId { get { return NameMap.GetValueOrDefault(EnemyId); diff --git a/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs b/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs index 500a1d312..2e135e3a4 100644 --- a/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs +++ b/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs @@ -11,6 +11,7 @@ public InstancedEnemy(Enemy enemy) : base(enemy) { IsKilled = false; IsRequired = true; + RepopWaitSecond = 60; } public InstancedEnemy(InstancedEnemy enemy) : base (enemy) @@ -18,10 +19,12 @@ public InstancedEnemy(InstancedEnemy enemy) : base (enemy) IsKilled = false; Index = enemy.Index; IsRequired = enemy.IsRequired; + RepopWaitSecond = enemy.RepopWaitSecond; } public bool IsRequired { get; set; } public bool IsKilled { get; set; } public byte Index { get; set; } + public uint RepopWaitSecond { get; set; } } } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestBlock.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestBlock.cs index c12f6fd83..c0b91ab83 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestBlock.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestBlock.cs @@ -7,6 +7,14 @@ namespace Arrowgene.Ddon.Shared.Model.Quest { + public class Announcements + { + public int GeneralAnnounceId { get; set; } + public int StageStart { get; set; } + public int StageClear { get; set; } + public int EndContentsPurpose { get; set; } + } + public class QuestBlock { public QuestBlockType BlockType { get; set; } @@ -14,6 +22,7 @@ public class QuestBlock public ushort SequenceNo { get; set; } public ushort BlockNo { get; set; } public QuestAnnounceType AnnounceType { get; set; } + public Announcements Announcements { get; set; } public StageId StageId { get; set; } public ushort SubGroupId { get; set; } public uint SetNo { get; set; } @@ -75,6 +84,7 @@ public QuestBlock() OmInteractEvent = new QuestOmInteractEvent(); TargetEnemy = new QuestTargetEnemy(); + Announcements = new Announcements(); } } } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs new file mode 100644 index 000000000..f82d4c264 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs @@ -0,0 +1,29 @@ +using Arrowgene.Ddon.Shared.Entity.Structure; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Model.Quest +{ + public class QuestMissionParams + { + public QuestMissionParams() + { + QuestPhaseGroupIdList = new List(); + } + + public uint SortieMinimum { get; set; } + public uint PlaytimeInSeconds { get; set; } + public bool IsSolo { get; set; } + public uint MaxPawns { get; set; } + // public bool SupportPawnAllowed { get; set; } Is in symbol but doesn't seem to work? + public bool ArmorAllowed { get; set; } + public bool JewelryAllowed { get; set; } + + public uint Group { get; set; } + public List QuestPhaseGroupIdList { get; set; } + } +} diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestType.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestType.cs index 91af1faaf..ad944190b 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestType.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestType.cs @@ -19,8 +19,7 @@ public enum QuestType : uint Board = 1, World = 1, // World should be Set Quest (2) not light Personal = 4, - - + ExtremeMission = TimeGain, // Unsure if this is the proper category #if false // Seems game has 2 different sets of quest IDs // which one is the right one to use??? diff --git a/Arrowgene.Ddon.Shared/Network/PacketId.cs b/Arrowgene.Ddon.Shared/Network/PacketId.cs index 019f24453..d330fcab9 100644 --- a/Arrowgene.Ddon.Shared/Network/PacketId.cs +++ b/Arrowgene.Ddon.Shared/Network/PacketId.cs @@ -661,7 +661,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_GET_CYCLE_CONTENTS_SITUATION_INFO_LIST_RES = new PacketId(11, 38, 2, "S2C_QUEST_GET_CYCLE_CONTENTS_SITUATION_INFO_LIST_RES", ServerType.Game, PacketSource.Server); // 循環コンテンツ本編情報リストの取得に public static readonly PacketId C2S_QUEST_PLAY_ENTRY_REQ = new PacketId(11, 39, 1, "C2S_QUEST_PLAY_ENTRY_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_ENTRY_RES = new PacketId(11, 39, 2, "S2C_QUEST_PLAY_ENTRY_RES", ServerType.Game, PacketSource.Server); // プレイエントリーに - public static readonly PacketId S2C_QUEST_11_39_16_NTC = new PacketId(11, 39, 16, "S2C_QUEST_11_39_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_QUEST_PLAY_ENTRY_NTC = new PacketId(11, 39, 16, "S2C_QUEST_PLAY_ENTRY_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_39_16_NTC"); public static readonly PacketId C2S_QUEST_11_40_1_REQ = new PacketId(11, 40, 1, "C2S_QUEST_11_40_1_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_11_40_2_RES = new PacketId(11, 40, 2, "S2C_QUEST_11_40_2_RES", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_40_16_NTC = new PacketId(11, 40, 16, "S2C_QUEST_11_40_16_NTC", ServerType.Game, PacketSource.Server); @@ -669,7 +669,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_PLAY_START_RES = new PacketId(11, 41, 2, "S2C_QUEST_PLAY_START_RES", ServerType.Game, PacketSource.Server); // プレイスタートに public static readonly PacketId C2S_QUEST_PLAY_START_TIMER_REQ = new PacketId(11, 42, 1, "C2S_QUEST_PLAY_START_TIMER_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_START_TIMER_RES = new PacketId(11, 42, 2, "S2C_QUEST_PLAY_START_TIMER_RES", ServerType.Game, PacketSource.Server); // クエスト時間計測開始に - public static readonly PacketId S2C_QUEST_11_42_16_NTC = new PacketId(11, 42, 16, "S2C_QUEST_11_42_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_QUEST_PLAY_START_TIMER_NTC = new PacketId(11, 42, 16, "S2C_QUEST_PLAY_START_TIMER_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_42_16_NTC"); public static readonly PacketId C2S_QUEST_PLAY_INTERRUPT_REQ = new PacketId(11, 43, 1, "C2S_QUEST_PLAY_INTERRUPT_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_INTERRUPT_RES = new PacketId(11, 43, 2, "S2C_QUEST_PLAY_INTERRUPT_RES", ServerType.Game, PacketSource.Server); // コンテンツプレイ中断要求に public static readonly PacketId S2C_QUEST_11_43_16_NTC = new PacketId(11, 43, 16, "S2C_QUEST_11_43_16_NTC", ServerType.Game, PacketSource.Server); @@ -677,7 +677,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_PLAY_INTERRUPT_ANSWER_RES = new PacketId(11, 44, 2, "S2C_QUEST_PLAY_INTERRUPT_ANSWER_RES", ServerType.Game, PacketSource.Server); // コンテンツプレイ中断応答に public static readonly PacketId C2S_QUEST_PLAY_END_REQ = new PacketId(11, 45, 1, "C2S_QUEST_PLAY_END_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_END_RES = new PacketId(11, 45, 2, "S2C_QUEST_PLAY_END_RES", ServerType.Game, PacketSource.Server); // エンドコンテンツ終了に - public static readonly PacketId S2C_QUEST_11_45_16_NTC = new PacketId(11, 45, 16, "S2C_QUEST_11_45_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_QUEST_PLAY_END_NTC = new PacketId(11, 45, 16, "S2C_QUEST_PLAY_END_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_45_16_NTC"); public static readonly PacketId C2S_QUEST_CYCLE_CONTENTS_PLAY_START_REQ = new PacketId(11, 46, 1, "C2S_QUEST_CYCLE_CONTENTS_PLAY_START_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_CYCLE_CONTENTS_PLAY_START_RES = new PacketId(11, 46, 2, "S2C_QUEST_CYCLE_CONTENTS_PLAY_START_RES", ServerType.Game, PacketSource.Server); // 循環コンテンツプレイスタートに public static readonly PacketId C2S_QUEST_CYCLE_CONTENTS_PLAY_END_REQ = new PacketId(11, 47, 1, "C2S_QUEST_CYCLE_CONTENTS_PLAY_END_REQ", ServerType.Game, PacketSource.Client); @@ -770,7 +770,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_11_98_16_NTC = new PacketId(11, 98, 16, "S2C_QUEST_11_98_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_99_16_NTC = new PacketId(11, 99, 16, "S2C_QUEST_11_99_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_100_16_NTC = new PacketId(11, 100, 16, "S2C_QUEST_11_100_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_QUEST_11_101_16_NTC = new PacketId(11, 101, 16, "S2C_QUEST_11_101_16_NTC", ServerType.Game, PacketSource.Server); // + public static readonly PacketId S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC = new PacketId(11, 101, 16, "S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_101_16_NTC"); // public static readonly PacketId S2C_QUEST_11_102_16_NTC = new PacketId(11, 102, 16, "S2C_QUEST_11_102_16_NTC", ServerType.Game, PacketSource.Server); // Remaining time extended by x seconds public static readonly PacketId S2C_QUEST_11_103_16_NTC = new PacketId(11, 103, 16, "S2C_QUEST_11_103_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_104_16_NTC = new PacketId(11, 104, 16, "S2C_QUEST_11_104_16_NTC", ServerType.Game, PacketSource.Server); @@ -794,8 +794,8 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_GET_LEVEL_BONUS_LIST_RES = new PacketId(11, 121, 2, "S2C_QUEST_GET_LEVEL_BONUS_LIST_RES", ServerType.Game, PacketSource.Server); // レベルボーナス情報リストの取得に public static readonly PacketId C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_LIST_REQ = new PacketId(11, 122, 1, "C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_LIST_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_LIST_RES = new PacketId(11, 122, 2, "S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_LIST_RES", ServerType.Game, PacketSource.Server); // 冒険ガイドクエストの取得に - public static readonly PacketId C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_NOTICE_REQ = new PacketId(11, 123, 1, "C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_NOTICE_REQ", ServerType.Game, PacketSource.Client); - public static readonly PacketId S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NOTICE_RES = new PacketId(11, 123, 2, "S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NOTICE_RES", ServerType.Game, PacketSource.Server); // 冒険ガイドクエスト通知の取得に + public static readonly PacketId C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_REQ = new PacketId(11, 123, 1, "C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_REQ", ServerType.Game, PacketSource.Client); + public static readonly PacketId S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_RES = new PacketId(11, 123, 2, "S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_RES", ServerType.Game, PacketSource.Server); // 冒険ガイドクエスト通知の取得に public static readonly PacketId C2S_QUEST_SET_NAVIGATION_QUEST_REQ = new PacketId(11, 124, 1, "C2S_QUEST_SET_NAVIGATION_QUEST_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_SET_NAVIGATION_QUEST_RES = new PacketId(11, 124, 2, "S2C_QUEST_SET_NAVIGATION_QUEST_RES", ServerType.Game, PacketSource.Server); // ナビゲーションクエストセットに public static readonly PacketId S2C_QUEST_11_124_16_NTC = new PacketId(11, 124, 16, "S2C_QUEST_11_124_16_NTC", ServerType.Game, PacketSource.Server); // @@ -1561,10 +1561,10 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_RES = new PacketId(34, 4, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_RES", ServerType.Game, PacketSource.Server); // エントリーボードアイテム参加に public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_REQ = new PacketId(34, 5, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_RES = new PacketId(34, 5, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_RES", ServerType.Game, PacketSource.Server); // エントリーボードアイテムから抜けるに - public static readonly PacketId S2C_ENTRY_34_5_16_NTC = new PacketId(34, 5, 16, "S2C_ENTRY_34_5_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_NTC = new PacketId(34, 5, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_5_16_NTC"); public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_REQ = new PacketId(34, 6, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_RES = new PacketId(34, 6, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_RES", ServerType.Game, PacketSource.Server); // エントリーボード準備完了に - public static readonly PacketId S2C_ENTRY_34_6_16_NTC = new PacketId(34, 6, 16, "S2C_ENTRY_34_6_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_NTC = new PacketId(34, 6, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_6_16_NTC"); public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_REQ = new PacketId(34, 7, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_RES = new PacketId(34, 7, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_RES", ServerType.Game, PacketSource.Server); // エントリー強制開始に public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_REQ = new PacketId(34, 8, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_REQ", ServerType.Game, PacketSource.Client); @@ -1577,13 +1577,13 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_LOCK_RES = new PacketId(34, 11, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_LOCK_RES", ServerType.Game, PacketSource.Server); // エントリーボードアイテムロックに public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_REQ = new PacketId(34, 12, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_RES = new PacketId(34, 12, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_RES", ServerType.Game, PacketSource.Server); // エントリーボードアイテム情報変更に - public static readonly PacketId S2C_ENTRY_34_12_16_NTC = new PacketId(34, 12, 16, "S2C_ENTRY_34_12_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_NTC = new PacketId(34, 12, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_12_16_NTC"); public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_REQ = new PacketId(34, 13, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES = new PacketId(34, 13, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES", ServerType.Game, PacketSource.Server); // エントリーボード招待に public static readonly PacketId S2C_ENTRY_34_13_16_NTC = new PacketId(34, 13, 16, "S2C_ENTRY_34_13_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_ENTRY_34_14_16_NTC = new PacketId(34, 14, 16, "S2C_ENTRY_34_14_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC = new PacketId(34, 14, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_14_16_NTC"); public static readonly PacketId S2C_ENTRY_34_15_16_NTC = new PacketId(34, 15, 16, "S2C_ENTRY_34_15_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_ENTRY_34_16_16_NTC = new PacketId(34, 16, 16, "S2C_ENTRY_34_16_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC = new PacketId(34, 16, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_16_16_NTC"); public static readonly PacketId S2C_ENTRY_34_17_16_NTC = new PacketId(34, 17, 16, "S2C_ENTRY_34_17_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_ENTRY_34_18_16_NTC = new PacketId(34, 18, 16, "S2C_ENTRY_34_18_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId C2S_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_REQ = new PacketId(34, 19, 1, "C2S_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_REQ", ServerType.Game, PacketSource.Client); @@ -2586,7 +2586,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_GET_CYCLE_CONTENTS_SITUATION_INFO_LIST_RES); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_ENTRY_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ENTRY_RES); - AddPacketIdEntry(packetIds, S2C_QUEST_11_39_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ENTRY_NTC); AddPacketIdEntry(packetIds, C2S_QUEST_11_40_1_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_11_40_2_RES); AddPacketIdEntry(packetIds, S2C_QUEST_11_40_16_NTC); @@ -2594,7 +2594,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_START_RES); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_START_TIMER_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_START_TIMER_RES); - AddPacketIdEntry(packetIds, S2C_QUEST_11_42_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_START_TIMER_NTC); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_INTERRUPT_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_INTERRUPT_RES); AddPacketIdEntry(packetIds, S2C_QUEST_11_43_16_NTC); @@ -2602,7 +2602,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_INTERRUPT_ANSWER_RES); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_END_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_END_RES); - AddPacketIdEntry(packetIds, S2C_QUEST_11_45_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_END_NTC); AddPacketIdEntry(packetIds, C2S_QUEST_CYCLE_CONTENTS_PLAY_START_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_CYCLE_CONTENTS_PLAY_START_RES); AddPacketIdEntry(packetIds, C2S_QUEST_CYCLE_CONTENTS_PLAY_END_REQ); @@ -2695,7 +2695,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_11_98_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_99_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_100_16_NTC); - AddPacketIdEntry(packetIds, S2C_QUEST_11_101_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_102_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_103_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_104_16_NTC); @@ -2719,8 +2719,8 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_GET_LEVEL_BONUS_LIST_RES); AddPacketIdEntry(packetIds, C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_LIST_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_LIST_RES); - AddPacketIdEntry(packetIds, C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_NOTICE_REQ); - AddPacketIdEntry(packetIds, S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NOTICE_RES); + AddPacketIdEntry(packetIds, C2S_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_REQ); + AddPacketIdEntry(packetIds, S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_RES); AddPacketIdEntry(packetIds, C2S_QUEST_SET_NAVIGATION_QUEST_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_SET_NAVIGATION_QUEST_RES); AddPacketIdEntry(packetIds, S2C_QUEST_11_124_16_NTC); @@ -3486,10 +3486,10 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_RES); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_RES); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_5_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_NTC); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_RES); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_6_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_READY_NTC); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_FORCE_START_RES); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_REQ); @@ -3502,13 +3502,13 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_LOCK_RES); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_RES); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_12_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_NTC); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES); AddPacketIdEntry(packetIds, S2C_ENTRY_34_13_16_NTC); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_14_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC); AddPacketIdEntry(packetIds, S2C_ENTRY_34_15_16_NTC); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_16_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC); AddPacketIdEntry(packetIds, S2C_ENTRY_34_17_16_NTC); AddPacketIdEntry(packetIds, S2C_ENTRY_34_18_16_NTC); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_REQ); diff --git a/docs/quests/images/end_contents_update.png b/docs/quests/images/end_contents_update.png new file mode 100644 index 0000000000000000000000000000000000000000..9d2e31ea7334dc05ab32b6d1ab15475bd14b789e GIT binary patch literal 292273 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 + /// Interface for managing various objects that appear in instances: + /// Enemies, via InstanceEnemyManager, + /// Gatherable items, via InstanceGatheringItemManager, , List> + /// Dropped items, via InstanceDropItemManager, , List> + /// + /// Notably does not cover enemies associated with quests. + /// + /// Each object is associated uniquely with a stageLayoutId and an index, + /// which may also be referred to as setId, posId, or PositionIndex, among other terms. + /// + /// Enemies and dropped items are paired at the same stageLayoutId and index. + /// An enemy spawned at (foo, bar) will have its drops instanced at (foo, bar) in the other manager. + /// + /// For enemies, the index is NOT the spawn subgroup, but the position index. + /// public abstract class InstanceAssetManager { public InstanceAssetManager() From 5f3188d1ec27270b56f6409d4aab31c22b28ed07 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Mon, 9 Sep 2024 21:19:44 -0700 Subject: [PATCH 066/116] Fix NPE when killing things in a leader-less party. --- Arrowgene.Ddon.GameServer/Characters/ExpManager.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs index 4e36e55a6..270b76aa1 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs @@ -736,8 +736,9 @@ public uint CalculateExpBonus(CharacterCommon characterCommon, uint baseExpAmoun private uint PartyLevelRange(PartyGroup party) { - uint maxLevel = party.Leader.Client.Character.ActiveCharacterJobData.Lv; - uint minLevel = party.Leader.Client.Character.ActiveCharacterJobData.Lv; + var firstMember = party.Clients.First(); + uint maxLevel = firstMember.Character.ActiveCharacterJobData.Lv; + uint minLevel = firstMember.Character.ActiveCharacterJobData.Lv; foreach (var member in party.Members) { @@ -766,7 +767,7 @@ private uint PartyLevelRange(PartyGroup party) private uint PartyMemberMaxLevel(PartyGroup party) { - uint maxLevel = party.Leader.Client.Character.ActiveCharacterJobData.Lv; + uint maxLevel = party.Clients.First().Character.ActiveCharacterJobData.Lv; foreach (var member in party.Members) { CharacterCommon characterCommon = null; From 6b9d9cbb80c6bf37d93248f838cfbc39ee0749c7 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Mon, 9 Sep 2024 21:29:40 -0700 Subject: [PATCH 067/116] Guard against weird edge case in PawnGetPartyPawnDataHandler. --- .../Handler/PawnGetPartyPawnDataHandler.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/PawnGetPartyPawnDataHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PawnGetPartyPawnDataHandler.cs index d75fd1585..6b4c239dd 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PawnGetPartyPawnDataHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PawnGetPartyPawnDataHandler.cs @@ -1,17 +1,14 @@ -using System.Collections.Generic; -using System.Linq; using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; 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; +using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { - public class PawnGetPartyPawnDataHandler : GameStructurePacketHandler + public class PawnGetPartyPawnDataHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(PawnGetPartyPawnDataHandler)); @@ -24,13 +21,19 @@ public PawnGetPartyPawnDataHandler(DdonGameServer server) : base(server) _OrbUnlockManager = server.OrbUnlockManager; } - public override void Handle(GameClient client, StructurePacket packet) + public override S2CPawnGetPartyPawnDataRes Handle(GameClient client, C2SPawnGetPartyPawnDataReq packet) { // var owner = Server.CharacterManager.SelectCharacter(packet.Structure.CharacterId); - GameClient owner = this.Server.ClientLookup.GetClientByCharacterId(packet.Structure.CharacterId); + GameClient owner = this.Server.ClientLookup.GetClientByCharacterId(packet.CharacterId); // TODO: Move this to a function or lookup class List pawns = owner.Character.Pawns.Concat(client.Character.RentedPawns).ToList(); - Pawn pawn = pawns.Where(pawn => pawn.PawnId == packet.Structure.PawnId).First(); + + if (!pawns.Any()) + { + throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_NOT_FOUNDED); + } + + Pawn pawn = pawns.Where(pawn => pawn.PawnId == packet.PawnId).First(); var res = new S2CPawnGetPartyPawnDataRes(); res.CharacterId = pawn.CharacterId; @@ -44,13 +47,13 @@ public override void Handle(GameClient client, StructurePacket Date: Mon, 9 Sep 2024 21:46:57 -0700 Subject: [PATCH 068/116] Guard against wallet point strangeness in CraftStartCraftHandler. Guard against NPE for leader-less parties and priority quests. --- .../Handler/CraftStartCraftHandler.cs | 6 +++++- .../Handler/ProfileGetCharacterProfileHandler.cs | 5 +++++ Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs index d55ec8855..f9c5040af 100644 --- a/Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs @@ -169,9 +169,13 @@ public override S2CCraftStartCraftRes Handle(GameClient client, C2SCraftStartCra Server.Database.InsertPawnCraftProgress(craftProgress); // Subtract craft price + // TODO: This can apparently return null, but we can't throw an error here because we've already committed a bunch of stuff to the DB. CDataUpdateWalletPoint updateWalletPoint = Server.WalletManager.RemoveFromWallet(client.Character, WalletType.Gold, Server.CraftManager.CalculateRecipeCost(recipe.Cost, costPerformanceLevels) * request.CreateCount); - updateCharacterItemNtc.UpdateWalletList.Add(updateWalletPoint); + if (updateWalletPoint != null) + { + updateCharacterItemNtc.UpdateWalletList.Add(updateWalletPoint); + } client.Send(updateCharacterItemNtc); return new S2CCraftStartCraftRes(); diff --git a/Arrowgene.Ddon.GameServer/Handler/ProfileGetCharacterProfileHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ProfileGetCharacterProfileHandler.cs index cef1a065b..3ee00c545 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ProfileGetCharacterProfileHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ProfileGetCharacterProfileHandler.cs @@ -19,6 +19,11 @@ public override S2CProfileGetCharacterProfileRes Handle(GameClient client, C2SPr { GameClient targetClient = Server.ClientLookup.GetClientByCharacterId(request.CharacterId); + if (targetClient is null) + { + throw new ResponseErrorException(ErrorCode.ERROR_CODE_CHARACTER_DATA_INVALID_CHARACTER_ID); + } + S2CCharacterGetCharacterStatusNtc ntc = new S2CCharacterGetCharacterStatusNtc(); ntc.CharacterId = targetClient.Character.CharacterId; ntc.StatusInfo = targetClient.Character.StatusInfo; diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index dd64ad85f..5a11b42c5 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -668,6 +668,14 @@ private void SendWalletRewards(DdonGameServer server, GameClient client, Quest q public void UpdatePriorityQuestList(DdonGameServer server, PartyGroup party) { + if (party.Leader is null) + { + party.SendToAll( new S2CQuestSetPriorityQuestNtc() + { + CharacterId = party.Clients.First().Character.CharacterId + }); + } + var leaderClient = party.Leader.Client; S2CQuestSetPriorityQuestNtc prioNtc = new S2CQuestSetPriorityQuestNtc() From 787d23cde7b356d2f9d8560e8e0525fc739cb7dc Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Mon, 9 Sep 2024 22:41:28 -0700 Subject: [PATCH 069/116] Add packet entries/bare handlers for the mystery C2S Instance Ntcs (46 and 47). --- Arrowgene.Ddon.GameServer/DdonGameServer.cs | 2 + .../Handler/Instance_13_46_16_Handler.cs | 22 +++++++++ .../Handler/Instance_13_47_16_Handler.cs | 22 +++++++++ .../Entity/EntitySerializer.cs | 2 + .../C2SInstance_13_46_16_Ntc.cs | 48 +++++++++++++++++++ .../C2SInstance_13_47_16_Ntc.cs | 48 +++++++++++++++++++ Arrowgene.Ddon.Shared/Network/PacketId.cs | 4 +- 7 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 Arrowgene.Ddon.GameServer/Handler/Instance_13_46_16_Handler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/Instance_13_47_16_Handler.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_46_16_Ntc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_47_16_Ntc.cs diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index dbd3198b6..2cacff5a6 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -362,6 +362,8 @@ private void LoadPacketHandler() AddHandler(new InstanceGetOmInstantKeyValueAllHandler(this)); AddHandler(new InstanceTraningRoomGetEnemyListHandler(this)); AddHandler(new InstanceTraningRoomSetEnemyHandler(this)); + AddHandler(new Instance_13_46_16_Handler(this)); + AddHandler(new Instance_13_47_16_Handler(this)); AddHandler(new ItemConsumeStorageItemHandler(this)); AddHandler(new ItemGetStorageItemListHandler(this)); diff --git a/Arrowgene.Ddon.GameServer/Handler/Instance_13_46_16_Handler.cs b/Arrowgene.Ddon.GameServer/Handler/Instance_13_46_16_Handler.cs new file mode 100644 index 000000000..d6587872a --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/Instance_13_46_16_Handler.cs @@ -0,0 +1,22 @@ +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 Instance_13_46_16_Handler : GameStructurePacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(Instance_13_46_16_Handler)); + + public Instance_13_46_16_Handler(DdonGameServer server) : base(server) + { + } + + public override void Handle(GameClient client, StructurePacket packet) + { + Logger.Debug($"{packet.Structure.Unk0}.{packet.Structure.Unk1}.{packet.Structure.Unk2}.{packet.Structure.Unk3}.{packet.Structure.Unk4}.{packet.Structure.Unk5}"); + // TODO: Identify this packet. + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/Instance_13_47_16_Handler.cs b/Arrowgene.Ddon.GameServer/Handler/Instance_13_47_16_Handler.cs new file mode 100644 index 000000000..d627f390d --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/Instance_13_47_16_Handler.cs @@ -0,0 +1,22 @@ +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 Instance_13_47_16_Handler : GameStructurePacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(Instance_13_47_16_Handler)); + + public Instance_13_47_16_Handler(DdonGameServer server) : base(server) + { + } + + public override void Handle(GameClient client, StructurePacket packet) + { + Logger.Debug($"{packet.Structure.Unk0}.{packet.Structure.Unk1}.{packet.Structure.Unk2}.{packet.Structure.Unk3}.{packet.Structure.Unk4}.{packet.Structure.Unk5}"); + // TODO: Identify this packet. + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index 7d1f23d9b..b3a26714e 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -509,6 +509,8 @@ static EntitySerializer() Create(new C2SInstancePlTouchOmNtc.Serializer()); Create(new C2SInstanceCharacterEndBadStatusNtc.Serializer()); Create(new C2SInstanceCharacterStartBadStatusNtc.Serializer()); + Create(new C2SInstance_13_46_16_Ntc.Serializer()); + Create(new C2SInstance_13_47_16_Ntc.Serializer()); Create(new C2SItemConsumeStorageItemReq.Serializer()); Create(new C2SItemGetStorageItemListReq.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_46_16_Ntc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_46_16_Ntc.cs new file mode 100644 index 000000000..0c0657a50 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_46_16_Ntc.cs @@ -0,0 +1,48 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SInstance_13_46_16_Ntc : IPacketStructure + { + public PacketId Id => PacketId.C2S_INSTANCE_13_46_16_NTC; + + public C2SInstance_13_46_16_Ntc() + { + } + + public uint Unk0 { get; set; } + public byte Unk1 { get; set; } + public uint Unk2 { get; set; } + public uint Unk3 { get; set; } + public uint Unk4 { get; set; } + public uint Unk5 { get; set; } + + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SInstance_13_46_16_Ntc obj) + { + WriteUInt32(buffer, obj.Unk0); + WriteByte(buffer, obj.Unk1); + WriteUInt32(buffer, obj.Unk2); + WriteUInt32(buffer, obj.Unk3); + WriteUInt32(buffer, obj.Unk4); + WriteUInt32(buffer, obj.Unk5); + + } + + public override C2SInstance_13_46_16_Ntc Read(IBuffer buffer) + { + C2SInstance_13_46_16_Ntc obj = new C2SInstance_13_46_16_Ntc(); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadByte(buffer); + obj.Unk2 = ReadUInt32(buffer); + obj.Unk3 = ReadUInt32(buffer); + obj.Unk4 = ReadUInt32(buffer); + obj.Unk5 = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_47_16_Ntc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_47_16_Ntc.cs new file mode 100644 index 000000000..af4a9194c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_47_16_Ntc.cs @@ -0,0 +1,48 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SInstance_13_47_16_Ntc : IPacketStructure + { + public PacketId Id => PacketId.C2S_INSTANCE_13_47_16_NTC; + + public C2SInstance_13_47_16_Ntc() + { + } + + public uint Unk0 { get; set; } + public byte Unk1 { get; set; } + public uint Unk2 { get; set; } + public uint Unk3 { get; set; } + public uint Unk4 { get; set; } + public uint Unk5 { get; set; } + + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SInstance_13_47_16_Ntc obj) + { + WriteUInt32(buffer, obj.Unk0); + WriteByte(buffer, obj.Unk1); + WriteUInt32(buffer, obj.Unk2); + WriteUInt32(buffer, obj.Unk3); + WriteUInt32(buffer, obj.Unk4); + WriteUInt32(buffer, obj.Unk5); + + } + + public override C2SInstance_13_47_16_Ntc Read(IBuffer buffer) + { + C2SInstance_13_47_16_Ntc obj = new C2SInstance_13_47_16_Ntc(); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadByte(buffer); + obj.Unk2 = ReadUInt32(buffer); + obj.Unk3 = ReadUInt32(buffer); + obj.Unk4 = ReadUInt32(buffer); + obj.Unk5 = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Network/PacketId.cs b/Arrowgene.Ddon.Shared/Network/PacketId.cs index d330fcab9..79d2ec711 100644 --- a/Arrowgene.Ddon.Shared/Network/PacketId.cs +++ b/Arrowgene.Ddon.Shared/Network/PacketId.cs @@ -902,8 +902,10 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_INSTANCE_GET_EX_OM_INFO_RES = new PacketId(13, 43, 2, "S2C_INSTANCE_GET_EX_OM_INFO_RES", ServerType.Game, PacketSource.Server); // 拡張OM情報取得に public static readonly PacketId C2S_INSTANCE_CHARACTER_START_BAD_STATUS_NTC = new PacketId(13, 44, 16, "C2S_INSTANCE_CHARACTER_START_BAD_STATUS_NTC", ServerType.Game, PacketSource.Client, "C2S_INSTANCE_13_44_16"); //When you gain a status effect? public static readonly PacketId C2S_INSTANCE_CHARACTER_END_BAD_STATUS_NTC = new PacketId(13, 45, 16, "C2S_INSTANCE_CHARACTER_END_BAD_STATUS_NTC", ServerType.Game, PacketSource.Client, "C2S_INSTANCE_13_45_16"); //When you lose a status effect? + public static readonly PacketId C2S_INSTANCE_13_46_16_NTC = new PacketId(13, 46, 16, "C2S_INSTANCE_13_46_16_NTC", ServerType.Game, PacketSource.Client); + public static readonly PacketId C2S_INSTANCE_13_47_16_NTC = new PacketId(13, 47, 16, "C2S_INSTANCE_13_47_16_NTC", ServerType.Game, PacketSource.Client); -// Group: 14 - (WARP) + // Group: 14 - (WARP) public static readonly PacketId C2S_WARP_RELEASE_WARP_POINT_REQ = new PacketId(14, 0, 1, "C2S_WARP_RELEASE_WARP_POINT_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_WARP_RELEASE_WARP_POINT_RES = new PacketId(14, 0, 2, "S2C_WARP_RELEASE_WARP_POINT_RES", ServerType.Game, PacketSource.Server); // ワープポイント解放要求に public static readonly PacketId S2C_WARP_14_0_16_NTC = new PacketId(14, 0, 16, "S2C_WARP_14_0_16_NTC", ServerType.Game, PacketSource.Server); From b31778102f21ea79b08d13428f0509e6b536af02 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Mon, 9 Sep 2024 23:17:44 -0700 Subject: [PATCH 070/116] Guard against index error when party is full? --- Arrowgene.Ddon.GameServer/Party/PartyGroup.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs index bafc2ab45..02425430a 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs @@ -714,6 +714,11 @@ private int TakeSlot(PartyMember partyMember) } } + if (slotIndex == InvalidSlotIndex) + { + return slotIndex; + } + partyMember.MemberIndex = slotIndex; _slots[slotIndex] = partyMember; } From 43bf25a2df2d6350afaf4d3a4b981b41a0d22e6a Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Tue, 10 Sep 2024 01:07:06 -0700 Subject: [PATCH 071/116] Review feedback, guard against shenanigans related to priority quests in a leaderless state. --- .../Handler/Instance_13_46_16_Handler.cs | 2 +- .../Handler/Instance_13_47_16_Handler.cs | 2 +- .../Handler/QuestCancelPriorityQuestHandler.cs | 7 ++++++- .../Handler/QuestSetPriorityQuestHandler.cs | 7 ++----- .../Party/PartyQuestState.cs | 5 +---- .../PacketStructure/C2SInstance_13_46_16_Ntc.cs | 15 +++++---------- .../PacketStructure/C2SInstance_13_47_16_Ntc.cs | 15 ++++----------- 7 files changed, 20 insertions(+), 33 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/Instance_13_46_16_Handler.cs b/Arrowgene.Ddon.GameServer/Handler/Instance_13_46_16_Handler.cs index d6587872a..2ebfe0eb6 100644 --- a/Arrowgene.Ddon.GameServer/Handler/Instance_13_46_16_Handler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/Instance_13_46_16_Handler.cs @@ -15,7 +15,7 @@ public Instance_13_46_16_Handler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { - Logger.Debug($"{packet.Structure.Unk0}.{packet.Structure.Unk1}.{packet.Structure.Unk2}.{packet.Structure.Unk3}.{packet.Structure.Unk4}.{packet.Structure.Unk5}"); + Logger.Debug($"{packet.Structure.Unk0}\t{packet.Structure.Unk3}.{packet.Structure.Unk4}.{packet.Structure.Unk5}"); // TODO: Identify this packet. } } diff --git a/Arrowgene.Ddon.GameServer/Handler/Instance_13_47_16_Handler.cs b/Arrowgene.Ddon.GameServer/Handler/Instance_13_47_16_Handler.cs index d627f390d..dc41562f8 100644 --- a/Arrowgene.Ddon.GameServer/Handler/Instance_13_47_16_Handler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/Instance_13_47_16_Handler.cs @@ -15,7 +15,7 @@ public Instance_13_47_16_Handler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { - Logger.Debug($"{packet.Structure.Unk0}.{packet.Structure.Unk1}.{packet.Structure.Unk2}.{packet.Structure.Unk3}.{packet.Structure.Unk4}.{packet.Structure.Unk5}"); + Logger.Debug($"{packet.Structure.Unk0}\t{packet.Structure.Unk3}.{packet.Structure.Unk4}.{packet.Structure.Unk5}"); // TODO: Identify this packet. } } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestCancelPriorityQuestHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestCancelPriorityQuestHandler.cs index 944518891..d8048a813 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestCancelPriorityQuestHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestCancelPriorityQuestHandler.cs @@ -30,7 +30,12 @@ public override S2CQuestCancelPriorityQuestRes Handle(GameClient client, C2SQues Server.Database.DeletePriorityQuest(client.Character.CommonId, questId); - client.Party.QuestState.UpdatePriorityQuestList(Server, client.Party); + // The UI responds and sends this request even if you're not the leader, + // but we probably shouldn't let non-leaders mess with the PartyQuestState. + if (client.Party.Leader is not null && client.Party.Leader.Client == client) + { + client.Party.QuestState.UpdatePriorityQuestList(Server, client.Party); + } return new S2CQuestCancelPriorityQuestRes() { diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestSetPriorityQuestHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestSetPriorityQuestHandler.cs index 46b651f78..588b9232a 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestSetPriorityQuestHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestSetPriorityQuestHandler.cs @@ -1,11 +1,8 @@ -using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; -using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; -using System.Collections.Generic; namespace Arrowgene.Ddon.GameServer.Handler { @@ -28,8 +25,8 @@ public override void Handle(GameClient client, StructurePacket { public override void Write(IBuffer buffer, C2SInstance_13_46_16_Ntc obj) { - WriteUInt32(buffer, obj.Unk0); - WriteByte(buffer, obj.Unk1); - WriteUInt32(buffer, obj.Unk2); + WriteEntity(buffer, obj.Unk0); WriteUInt32(buffer, obj.Unk3); WriteUInt32(buffer, obj.Unk4); WriteUInt32(buffer, obj.Unk5); - } public override C2SInstance_13_46_16_Ntc Read(IBuffer buffer) { C2SInstance_13_46_16_Ntc obj = new C2SInstance_13_46_16_Ntc(); - obj.Unk0 = ReadUInt32(buffer); - obj.Unk1 = ReadByte(buffer); - obj.Unk2 = ReadUInt32(buffer); + obj.Unk0 = ReadEntity(buffer); obj.Unk3 = ReadUInt32(buffer); obj.Unk4 = ReadUInt32(buffer); obj.Unk5 = ReadUInt32(buffer); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_47_16_Ntc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_47_16_Ntc.cs index af4a9194c..cf4c408b0 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_47_16_Ntc.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SInstance_13_47_16_Ntc.cs @@ -1,4 +1,5 @@ using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Network; namespace Arrowgene.Ddon.Shared.Entity.PacketStructure @@ -11,33 +12,25 @@ public C2SInstance_13_47_16_Ntc() { } - public uint Unk0 { get; set; } - public byte Unk1 { get; set; } - public uint Unk2 { get; set; } + public CDataStageLayoutId Unk0 { get; set; } public uint Unk3 { get; set; } public uint Unk4 { get; set; } public uint Unk5 { get; set; } - public class Serializer : PacketEntitySerializer { public override void Write(IBuffer buffer, C2SInstance_13_47_16_Ntc obj) { - WriteUInt32(buffer, obj.Unk0); - WriteByte(buffer, obj.Unk1); - WriteUInt32(buffer, obj.Unk2); + WriteEntity(buffer, obj.Unk0); WriteUInt32(buffer, obj.Unk3); WriteUInt32(buffer, obj.Unk4); WriteUInt32(buffer, obj.Unk5); - } public override C2SInstance_13_47_16_Ntc Read(IBuffer buffer) { C2SInstance_13_47_16_Ntc obj = new C2SInstance_13_47_16_Ntc(); - obj.Unk0 = ReadUInt32(buffer); - obj.Unk1 = ReadByte(buffer); - obj.Unk2 = ReadUInt32(buffer); + obj.Unk0 = ReadEntity(buffer); obj.Unk3 = ReadUInt32(buffer); obj.Unk4 = ReadUInt32(buffer); obj.Unk5 = ReadUInt32(buffer); From 51f8001ede2aaaa61fb63bc3dcfd2999f7773817 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Tue, 10 Sep 2024 21:37:58 -0700 Subject: [PATCH 072/116] Review feedback; adjust PartyQuestState::UpdatePriorityQuestList. --- .../Chat/Command/Commands/FinishQuestCommand.cs | 2 +- .../Handler/PawnGetPartyPawnDataHandler.cs | 9 +++------ Arrowgene.Ddon.GameServer/Handler/QuestCancelHandler.cs | 2 +- .../Handler/QuestCancelPriorityQuestHandler.cs | 7 +------ .../Handler/QuestQuestProgressHandler.cs | 2 +- Arrowgene.Ddon.GameServer/Party/PartyGroup.cs | 3 ++- Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs | 4 ++-- 7 files changed, 11 insertions(+), 18 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/FinishQuestCommand.cs b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/FinishQuestCommand.cs index 201199bbe..1cd107ef1 100644 --- a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/FinishQuestCommand.cs +++ b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/FinishQuestCommand.cs @@ -64,7 +64,7 @@ public override void Execute(string[] command, GameClient client, ChatMessage me }; client.Party.SendToAll(completeNtc); - client.Party.QuestState.UpdatePriorityQuestList(_server, client.Party); + client.Party.QuestState.UpdatePriorityQuestList(_server, client, client.Party); if (quest.ResetPlayerAfterQuest) { diff --git a/Arrowgene.Ddon.GameServer/Handler/PawnGetPartyPawnDataHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PawnGetPartyPawnDataHandler.cs index 6b4c239dd..e0cc5a9dc 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PawnGetPartyPawnDataHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PawnGetPartyPawnDataHandler.cs @@ -28,12 +28,9 @@ public override S2CPawnGetPartyPawnDataRes Handle(GameClient client, C2SPawnGetP // TODO: Move this to a function or lookup class List pawns = owner.Character.Pawns.Concat(client.Character.RentedPawns).ToList(); - if (!pawns.Any()) - { - throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_NOT_FOUNDED); - } - - Pawn pawn = pawns.Where(pawn => pawn.PawnId == packet.PawnId).First(); + Pawn pawn = pawns + .Where(pawn => pawn.PawnId == packet.PawnId) + .FirstOrDefault() ?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_NOT_FOUNDED); var res = new S2CPawnGetPartyPawnDataRes(); res.CharacterId = pawn.CharacterId; diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestCancelHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestCancelHandler.cs index 3052cffad..22f817e17 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestCancelHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestCancelHandler.cs @@ -36,7 +36,7 @@ public override S2CQuestQuestCancelRes Handle(GameClient client, C2SQuestQuestCa if (isPriority) { - client.Party.QuestState.UpdatePriorityQuestList(Server, client.Party); + client.Party.QuestState.UpdatePriorityQuestList(Server, client, client.Party); } } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestCancelPriorityQuestHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestCancelPriorityQuestHandler.cs index d8048a813..ad7612f0f 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestCancelPriorityQuestHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestCancelPriorityQuestHandler.cs @@ -30,12 +30,7 @@ public override S2CQuestCancelPriorityQuestRes Handle(GameClient client, C2SQues Server.Database.DeletePriorityQuest(client.Character.CommonId, questId); - // The UI responds and sends this request even if you're not the leader, - // but we probably shouldn't let non-leaders mess with the PartyQuestState. - if (client.Party.Leader is not null && client.Party.Leader.Client == client) - { - client.Party.QuestState.UpdatePriorityQuestList(Server, client.Party); - } + client.Party.QuestState.UpdatePriorityQuestList(Server, client, client.Party); return new S2CQuestCancelPriorityQuestRes() { diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs index 3dd4d82e8..4b1a7caf5 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs @@ -159,7 +159,7 @@ private void CompleteQuest(Quest quest, GameClient client, PartyGroup party, Par client.Party.SendToAll(completeNtc); // Update the priority quest list - client.Party.QuestState.UpdatePriorityQuestList(Server, client.Party); + client.Party.QuestState.UpdatePriorityQuestList(Server, client.Party.Leader.Client, client.Party); if (quest.ResetPlayerAfterQuest) { diff --git a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs index 02425430a..06f43846a 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs @@ -716,7 +716,8 @@ private int TakeSlot(PartyMember partyMember) if (slotIndex == InvalidSlotIndex) { - return slotIndex; + Logger.Error($"[PartyId:{Id}][TakeSlot] (no empty slot)"); + return InvalidSlotIndex; } partyMember.MemberIndex = slotIndex; diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 55f0af9e8..84cb52549 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -666,9 +666,9 @@ private void SendWalletRewards(DdonGameServer server, GameClient client, Quest q } } - public void UpdatePriorityQuestList(DdonGameServer server, PartyGroup party) + public void UpdatePriorityQuestList(DdonGameServer server, GameClient requestingClient, PartyGroup party) { - if (party.Leader is null) + if (party.Leader is null || requestingClient != party.Leader.Client) { return; } From 28e562f127bd99318733b04330d91f261e98166c Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Thu, 12 Sep 2024 16:21:11 -0700 Subject: [PATCH 073/116] Move CraftStartCraftHandler into a transaction so there's some measure of rollback protection for unusual errors. --- Arrowgene.Ddon.Database/IDatabase.cs | 2 +- .../Sql/Core/DdonSqlDbPawnCraftProgress.cs | 21 +- .../Handler/CraftStartCraftHandler.cs | 203 +++++++++--------- .../Database/DatabaseMigratorTest.cs | 2 +- 4 files changed, 114 insertions(+), 114 deletions(-) diff --git a/Arrowgene.Ddon.Database/IDatabase.cs b/Arrowgene.Ddon.Database/IDatabase.cs index 4784cc5a5..26f6bf95d 100644 --- a/Arrowgene.Ddon.Database/IDatabase.cs +++ b/Arrowgene.Ddon.Database/IDatabase.cs @@ -128,7 +128,7 @@ CDataPawnSearchParameter searchParams #region Pawn craft progress bool ReplacePawnCraftProgress(CraftProgress craftProgress); - bool InsertPawnCraftProgress(CraftProgress craftProgress); + bool InsertPawnCraftProgress(CraftProgress craftProgress, DbConnection? connectionIn = null); bool InsertIfNotExistsPawnCraftProgress(CraftProgress craftProgress); bool UpdatePawnCraftProgress(CraftProgress craftProgress); bool DeletePawnCraftProgress(uint craftCharacterId, uint craftLeadPawnId); diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawnCraftProgress.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawnCraftProgress.cs index ef5203194..2c97fa95f 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawnCraftProgress.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawnCraftProgress.cs @@ -1,4 +1,6 @@ +using System.ComponentModel.Design; using System.Data.Common; +using Arrowgene.Ddon.Database.Model; using Arrowgene.Ddon.Shared.Model; namespace Arrowgene.Ddon.Database.Sql.Core @@ -47,15 +49,18 @@ public bool ReplacePawnCraftProgress(TCon connection, CraftProgress craftProgres return true; } - public bool InsertPawnCraftProgress(CraftProgress craftProgress) + public bool InsertPawnCraftProgress(CraftProgress craftProgress, DbConnection? connectionIn = null) { - using TCon connection = OpenNewConnection(); - return InsertPawnCraftProgress(connection, craftProgress); - } - - public bool InsertPawnCraftProgress(TCon connection, CraftProgress craftProgress) - { - return ExecuteNonQuery(connection, SqlInsertPawnCraftProgress, command => { AddAllParameters(command, craftProgress); }) == 1; + bool isTransaction = connectionIn is not null; + TCon connection = (TCon)(connectionIn ?? OpenNewConnection()); + try + { + return ExecuteNonQuery(connection, SqlInsertPawnCraftProgress, command => { AddAllParameters(command, craftProgress); }) == 1; + } + finally + { + if (!isTransaction) connection.Dispose(); + } } public bool InsertIfNotExistsPawnCraftProgress(CraftProgress craftProgress) diff --git a/Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs index f9c5040af..7db706cf6 100644 --- a/Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs @@ -54,128 +54,123 @@ public override S2CCraftStartCraftRes Handle(GameClient client, C2SCraftStartCra S2CItemUpdateCharacterItemNtc updateCharacterItemNtc = new S2CItemUpdateCharacterItemNtc(); updateCharacterItemNtc.UpdateType = ItemNoticeType.StartCraft; - - // Remove crafting materials - foreach (var craftMaterial in request.CraftMaterialList) + Server.Database.ExecuteInTransaction(connection => { - try + // Remove crafting materials + foreach (var craftMaterial in request.CraftMaterialList) { - List updateResults = Server.ItemManager.ConsumeItemByUIdFromMultipleStorages(Server, client.Character, ItemManager.BothStorageTypes, - craftMaterial.ItemUId, craftMaterial.ItemNum); - updateCharacterItemNtc.UpdateItemList.AddRange(updateResults); + try + { + List updateResults = Server.ItemManager.ConsumeItemByUIdFromMultipleStorages(Server, client.Character, ItemManager.BothStorageTypes, + craftMaterial.ItemUId, craftMaterial.ItemNum, connection); + updateCharacterItemNtc.UpdateItemList.AddRange(updateResults); + } + catch (NotEnoughItemsException) + { + throw new ResponseErrorException(ErrorCode.ERROR_CODE_ITEM_INVALID_ITEM_NUM, "Client Item Desync has Occurred."); + } } - catch (NotEnoughItemsException e) + + Pawn leadPawn = Server.CraftManager.FindPawn(client, request.CraftMainPawnID); + List pawns = new List { leadPawn }; + pawns.AddRange(request.CraftSupportPawnIDList.Select(p => Server.CraftManager.FindPawn(client, p.PawnId))); + List productionSpeedLevels = new List(); + List consumableQuantityLevels = new List(); + List costPerformanceLevels = new List(); + List qualityLevels = new List(); + foreach (Pawn pawn in pawns) { - Logger.Exception(e); - return new S2CCraftStartCraftRes() + if (pawn != null) { - Result = 1 - }; + productionSpeedLevels.Add(CraftManager.GetPawnProductionSpeedLevel(pawn)); + consumableQuantityLevels.Add(CraftManager.GetPawnConsumableQuantityLevel(pawn)); + costPerformanceLevels.Add(CraftManager.GetPawnCostPerformanceLevel(pawn)); + qualityLevels.Add(CraftManager.GetPawnEquipmentQualityLevel(pawn)); + } + else + { + throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_INVALID, "Couldn't find the Pawn ID."); + } } - } - - Pawn leadPawn = Server.CraftManager.FindPawn(client, request.CraftMainPawnID); - List pawns = new List { leadPawn }; - pawns.AddRange(request.CraftSupportPawnIDList.Select(p => Server.CraftManager.FindPawn(client, p.PawnId))); - List productionSpeedLevels = new List(); - List consumableQuantityLevels = new List(); - List costPerformanceLevels = new List(); - List qualityLevels = new List(); - foreach (Pawn pawn in pawns) - { - if (pawn != null) + + double calculatedOdds = CraftManager.CalculateEquipmentQualityIncreaseRate(qualityLevels); + uint plusValue = 0; + bool isGreatSuccessEquipmentQuality = false; + bool canPlusValue = !itemInfo.SubCategory.HasValue || !BannedSubCategories.Contains(itemInfo.SubCategory.Value); + if (canPlusValue && !string.IsNullOrEmpty(request.RefineMaterialUID)) { - productionSpeedLevels.Add(CraftManager.GetPawnProductionSpeedLevel(pawn)); - consumableQuantityLevels.Add(CraftManager.GetPawnConsumableQuantityLevel(pawn)); - costPerformanceLevels.Add(CraftManager.GetPawnCostPerformanceLevel(pawn)); - qualityLevels.Add(CraftManager.GetPawnEquipmentQualityLevel(pawn)); + Item refineMaterialItem = Server.Database.SelectStorageItemByUId(request.RefineMaterialUID); + CraftCalculationResult craftCalculationResult = CraftManager.CalculateEquipmentQuality(refineMaterialItem, (uint)calculatedOdds); + plusValue = craftCalculationResult.CalculatedValue; + isGreatSuccessEquipmentQuality = craftCalculationResult.IsGreatSuccess; + + try + { + List updateResults = + Server.ItemManager.ConsumeItemByUIdFromMultipleStorages(Server, client.Character, ItemManager.BothStorageTypes, request.RefineMaterialUID, 1, connection); + updateCharacterItemNtc.UpdateItemList.AddRange(updateResults); + } + catch (NotEnoughItemsException) + { + throw new ResponseErrorException(ErrorCode.ERROR_CODE_ITEM_INVALID_ITEM_NUM, "Client Item Desync has Occurred."); + } } - else + + uint consumableAdditionalQuantity = 0; + bool isGreatSuccessConsumableQuantity = false; + if (itemInfo.StorageType == StorageType.ItemBagConsumable) { - throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_INVALID, "Couldn't find the Pawn ID."); + CraftCalculationResult craftCalculationResult = CraftManager.CalculateConsumableQuantity(consumableQuantityLevels, (uint)calculatedOdds); + consumableAdditionalQuantity = request.CreateCount * craftCalculationResult.CalculatedValue; + isGreatSuccessConsumableQuantity = craftCalculationResult.IsGreatSuccess; } - } - - double calculatedOdds = CraftManager.CalculateEquipmentQualityIncreaseRate(qualityLevels); - uint plusValue = 0; - bool isGreatSuccessEquipmentQuality = false; - bool canPlusValue = !itemInfo.SubCategory.HasValue || !BannedSubCategories.Contains(itemInfo.SubCategory.Value); - if (canPlusValue && !string.IsNullOrEmpty(request.RefineMaterialUID)) - { - Item refineMaterialItem = Server.Database.SelectStorageItemByUId(request.RefineMaterialUID); - CraftCalculationResult craftCalculationResult = CraftManager.CalculateEquipmentQuality(refineMaterialItem, (uint)calculatedOdds); - plusValue = craftCalculationResult.CalculatedValue; - isGreatSuccessEquipmentQuality = craftCalculationResult.IsGreatSuccess; - try + CraftProgress craftProgress = new CraftProgress + { + CraftCharacterId = client.Character.CharacterId, + CraftLeadPawnId = request.CraftMainPawnID, + CraftSupportPawnId1 = request.CraftSupportPawnIDList.ElementAtOrDefault(0)?.PawnId ?? 0, + CraftSupportPawnId2 = request.CraftSupportPawnIDList.ElementAtOrDefault(1)?.PawnId ?? 0, + CraftSupportPawnId3 = request.CraftSupportPawnIDList.ElementAtOrDefault(2)?.PawnId ?? 0, + RecipeId = request.RecipeID, + NpcActionId = NpcActionType.NpcActionSmithy, + ItemId = recipe.ItemID, + AdditionalStatusId = request.AdditionalStatusId, + // TODO: implement mechanism to deduct time periodically + RemainTime = Server.CraftManager.CalculateRecipeProductionSpeed(recipe.Time, productionSpeedLevels), + CreateCount = recipe.Num * request.CreateCount, + PlusValue = plusValue, + GreatSuccess = isGreatSuccessEquipmentQuality || isGreatSuccessConsumableQuantity, + AdditionalQuantity = consumableAdditionalQuantity + }; + + // TODO: check if course bonus provides exp bonus for crafting & calculate bonus EXP + // TODO: Decide whether bonus exp should be calculated when craft is started vs. received + bool expBonus = false; + if (CraftManager.CanPawnExpUp(leadPawn)) { - List updateResults = - Server.ItemManager.ConsumeItemByUIdFromMultipleStorages(Server, client.Character, ItemManager.BothStorageTypes, request.RefineMaterialUID, 1); - updateCharacterItemNtc.UpdateItemList.AddRange(updateResults); + craftProgress.Exp = recipe.Exp * request.CreateCount; + craftProgress.ExpBonus = expBonus; + if (expBonus) + { + craftProgress.BonusExp = craftProgress.Exp * 2; + } } - catch (NotEnoughItemsException) + else { - throw new ResponseErrorException(ErrorCode.ERROR_CODE_ITEM_INVALID_ITEM_NUM, "Client Item Desync has Occurred."); + craftProgress.Exp = 0; + craftProgress.ExpBonus = false; + craftProgress.BonusExp = 0; } - } - uint consumableAdditionalQuantity = 0; - bool isGreatSuccessConsumableQuantity = false; - if (itemInfo.StorageType == StorageType.ItemBagConsumable) - { - CraftCalculationResult craftCalculationResult = CraftManager.CalculateConsumableQuantity(consumableQuantityLevels, (uint)calculatedOdds); - consumableAdditionalQuantity = request.CreateCount * craftCalculationResult.CalculatedValue; - isGreatSuccessConsumableQuantity = craftCalculationResult.IsGreatSuccess; - } + Server.Database.InsertPawnCraftProgress(craftProgress, connection); - CraftProgress craftProgress = new CraftProgress - { - CraftCharacterId = client.Character.CharacterId, - CraftLeadPawnId = request.CraftMainPawnID, - CraftSupportPawnId1 = request.CraftSupportPawnIDList.ElementAtOrDefault(0)?.PawnId ?? 0, - CraftSupportPawnId2 = request.CraftSupportPawnIDList.ElementAtOrDefault(1)?.PawnId ?? 0, - CraftSupportPawnId3 = request.CraftSupportPawnIDList.ElementAtOrDefault(2)?.PawnId ?? 0, - RecipeId = request.RecipeID, - NpcActionId = NpcActionType.NpcActionSmithy, - ItemId = recipe.ItemID, - AdditionalStatusId = request.AdditionalStatusId, - // TODO: implement mechanism to deduct time periodically - RemainTime = Server.CraftManager.CalculateRecipeProductionSpeed(recipe.Time, productionSpeedLevels), - CreateCount = recipe.Num * request.CreateCount, - PlusValue = plusValue, - GreatSuccess = isGreatSuccessEquipmentQuality || isGreatSuccessConsumableQuantity, - AdditionalQuantity = consumableAdditionalQuantity - }; - - // TODO: check if course bonus provides exp bonus for crafting & calculate bonus EXP - // TODO: Decide whether bonus exp should be calculated when craft is started vs. received - bool expBonus = false; - if (CraftManager.CanPawnExpUp(leadPawn)) - { - craftProgress.Exp = recipe.Exp * request.CreateCount; - craftProgress.ExpBonus = expBonus; - if (expBonus) - { - craftProgress.BonusExp = craftProgress.Exp * 2; - } - } - else - { - craftProgress.Exp = 0; - craftProgress.ExpBonus = false; - craftProgress.BonusExp = 0; - } - - Server.Database.InsertPawnCraftProgress(craftProgress); - - // Subtract craft price - // TODO: This can apparently return null, but we can't throw an error here because we've already committed a bunch of stuff to the DB. - CDataUpdateWalletPoint updateWalletPoint = Server.WalletManager.RemoveFromWallet(client.Character, WalletType.Gold, - Server.CraftManager.CalculateRecipeCost(recipe.Cost, costPerformanceLevels) * request.CreateCount); - if (updateWalletPoint != null) - { + // Subtract craft price + CDataUpdateWalletPoint updateWalletPoint = Server.WalletManager.RemoveFromWallet(client.Character, WalletType.Gold, + Server.CraftManager.CalculateRecipeCost(recipe.Cost, costPerformanceLevels) * request.CreateCount, connection) + ?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_SHOP_LACK_MONEY); updateCharacterItemNtc.UpdateWalletList.Add(updateWalletPoint); - } + }); client.Send(updateCharacterItemNtc); return new S2CCraftStartCraftRes(); diff --git a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs index 38154e9f2..92a0f1dea 100644 --- a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs +++ b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs @@ -322,7 +322,7 @@ public void Execute(DbConnection conn, string sql) {} public bool UpdatePawnBaseInfo(Pawn pawn) { return true; } public bool UpdatePawnTrainingStatus(uint pawnId, JobId job, byte[] pawnTrainingStatus) { return true; } public bool ReplacePawnCraftProgress(CraftProgress craftProgress) { return true; } - public bool InsertPawnCraftProgress(CraftProgress craftProgress) { return true; } + public bool InsertPawnCraftProgress(CraftProgress craftProgress, DbConnection? connectionIn = null) { return true; } public bool InsertIfNotExistsPawnCraftProgress(CraftProgress craftProgress) { return true; } public bool UpdatePawnCraftProgress(CraftProgress craftProgress) { return true; } public bool DeletePawnCraftProgress(uint craftCharacterId, uint craftLeadPawnId) { return true; } From 83ea1221a121822360c5fb1f771a50ad607f6412 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 8 Sep 2024 20:18:58 -0400 Subject: [PATCH 074/116] feat: Add support for quest timer - Added support for quest timer. - Added support for extreme mission banner - Adjust quest enemy spawn detection. - Adjusted EM7 to have time extended mechanism. --- .../Characters/ExmManager.cs | 84 ++++++++- Arrowgene.Ddon.GameServer/DdonGameServer.cs | 1 + .../CharacterDecideCharacterIdHandler.cs | 2 +- .../Handler/InstanceGetEnemySetListHandler.cs | 5 +- .../QuestGetEndContentsGroupHandler.cs | 17 +- .../Handler/QuestPlayEndHandler.cs | 8 +- .../Handler/QuestPlayInterruptHandler.cs | 31 ++++ .../Handler/QuestPlayStartHandler.cs | 78 +++++++-- .../Handler/QuestPlayStartTimerHandler.cs | 21 ++- .../Handler/StageAreaChangeHandler.cs | 9 + Arrowgene.Ddon.GameServer/Party/PartyGroup.cs | 1 + .../Party/PartyQuestState.cs | 24 ++- .../Quests/GenericQuest.cs | 16 ++ Arrowgene.Ddon.GameServer/Quests/Quest.cs | 69 +++++++- .../AssetReader/QuestAssetDeserializer.cs | 7 + .../Entity/EntitySerializer.cs | 12 ++ .../C2SQuestPlayInterruptReq.cs | 26 +++ .../S2CInstanceEnemyGroupResetNtc.cs | 1 - .../S2CItemSwitchStorageNtc.cs | 41 +++++ .../S2CQuestPlayAddTimerNtc.cs | 31 ++++ .../S2CQuestPlayInterruptNtc.cs | 36 ++++ .../S2CQuestPlayInterruptRes.cs | 34 ++++ .../PacketStructure/S2CQuestPlayTimeupNtc.cs | 30 ++++ .../S2CQuestRaidBossPlayStartNtc.cs | 40 +++++ .../S2CQuestTimeGainQuestPlayStartNtc.cs | 6 +- .../PacketStructure/S2C_63_11_16_NTC.cs | 33 ++++ .../Structure/CDataClearTimePointBonus.cs | 33 ++++ .../Structure/CDataHasRegionBreakReward.cs | 30 ++++ .../Structure/CDataRaidBossEnemyParam.cs | 75 ++++++++ .../Structure/CDataRaidBossPlayStartData.cs | 42 +++++ .../Entity/Structure/CDataSwitchStorage.cs | 21 ++- .../Files/Assets/quests/q50101020.json | 6 +- .../Files/Assets/quests/q50102020.json | 17 +- .../Files/Assets/quests/q50103020.json | 6 +- .../Files/Assets/quests/q50104000.json | 6 +- .../Files/Assets/quests/q50201000.json | 28 ++- .../Files/Assets/quests/q50202000.json | 7 +- .../Files/Assets/quests/q50202003.json | 7 +- .../Files/Assets/quests/q50203000.json | 161 +++++++++++++++++- .../Files/Assets/quests/q50204002.json | 19 +-- Arrowgene.Ddon.Shared/Model/ContentsType.cs | 15 ++ .../Model/Quest/QuestBlock.cs | 1 + .../Model/Quest/QuestBlockType.cs | 2 + Arrowgene.Ddon.Shared/Network/PacketId.cs | 32 ++-- 44 files changed, 1044 insertions(+), 127 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayInterruptReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CItemSwitchStorageNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayAddTimerNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayTimeupNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestRaidBossPlayStartNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_11_16_NTC.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataClearTimePointBonus.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataHasRegionBreakReward.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataRaidBossEnemyParam.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataRaidBossPlayStartData.cs diff --git a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs index d3adf2281..3505062b1 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs @@ -1,9 +1,13 @@ +using Arrowgene.Ddon.GameServer.Handler; using Arrowgene.Ddon.GameServer.Quests; +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.Model.Quest; +using Arrowgene.Logging; using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Linq; @@ -11,6 +15,8 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using YamlDotNet.Core.Tokens; +using static Arrowgene.Ddon.GameServer.Characters.ExmManager; namespace Arrowgene.Ddon.GameServer.Characters { @@ -22,9 +28,19 @@ public class ExmManager private Dictionary _CharacterIdToContentId; private Dictionary> _ContentIdToCharacterIds; private Dictionary _ContentIdToQuest; + private Dictionary _ContentTimers; private uint EntryItemIdCounter; private Stack _FreeEntryItemIds; + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ExmManager)); + + internal class TimerState + { + public DateTime TimeStart { get; set; } + public TimeSpan Duration { get; set; } + public Timer Timer { get; set; } + } + public ExmManager(DdonGameServer server) { _Server = server; @@ -32,6 +48,7 @@ public ExmManager(DdonGameServer server) _CharacterIdToContentId = new Dictionary(); _ContentIdToCharacterIds = new Dictionary>(); _ContentIdToQuest = new Dictionary(); + _ContentTimers = new Dictionary(); // ID tracking EntryItemIdCounter = 1; @@ -108,29 +125,34 @@ public CDataEntryItem GetEntryItemDataForCharacter(Character character) return GetEntryItemDataForCharacter(character.CharacterId); } - public bool RemoveGroupForContent(ulong id) + public bool RemoveGroupForContent(ulong contentId) { lock (_ContentData) { - if (!_ContentData.ContainsKey(id)) + if (!_ContentData.ContainsKey(contentId)) { return false; } - if (_ContentIdToCharacterIds.ContainsKey(id)) + if (_ContentTimers.ContainsKey(contentId)) + { + CancelTimer(contentId); + } + + if (_ContentIdToCharacterIds.ContainsKey(contentId)) { - foreach (var characterId in _ContentIdToCharacterIds[id]) + foreach (var characterId in _ContentIdToCharacterIds[contentId]) { _CharacterIdToContentId.Remove(characterId); } - _ContentIdToCharacterIds.Remove(id); + _ContentIdToCharacterIds.Remove(contentId); } - _ContentIdToQuest.Remove(id); + _ContentIdToQuest.Remove(contentId); - var data = _ContentData[id]; + var data = _ContentData[contentId]; ReclaimEntryItemId(data.Id); - return _ContentData.Remove(id); + return _ContentData.Remove(contentId); } } @@ -268,5 +290,51 @@ private void ReclaimEntryItemId(uint id) _FreeEntryItemIds.Push(id); } } + + public void StartTimer(ulong contentId, GameClient client, uint playtimeInSeconds) + { + lock (_ContentData) + { + _ContentTimers[contentId] = new TimerState(); + + var timerState = _ContentTimers[contentId]; + timerState.Duration = TimeSpan.FromSeconds(playtimeInSeconds); + timerState.TimeStart = DateTime.Now; + timerState.Timer = new Timer(task => + { + TimeSpan alreadyElapsed = DateTime.Now.Subtract(timerState.TimeStart); + if (alreadyElapsed > timerState.Duration) + { + Logger.Info($"Timer expired for ContentId={contentId}"); + client.Party.SendToAll(new S2CQuestPlayTimeupNtc()); + CancelTimer(contentId); + } + }, null, 0, 1000); + Logger.Info($"Starting {playtimeInSeconds} second timer for ContentId={contentId}"); + } + } + + public ulong ExtendTimer(ulong contentId, uint amountInSeconds) + { + lock (_ContentTimers) + { + Logger.Info($"Extending time by {amountInSeconds} seconds for ContentId={contentId}"); + _ContentTimers[contentId].Duration += TimeSpan.FromSeconds(amountInSeconds); + return (ulong) ((DateTimeOffset)(_ContentTimers[contentId].TimeStart + _ContentTimers[contentId].Duration)).ToUnixTimeSeconds(); + } + } + + public void CancelTimer(ulong contentId) + { + lock (_ContentData) + { + if (_ContentTimers.ContainsKey(contentId)) + { + Logger.Info($"Canceling timer for ContentId={contentId}"); + _ContentTimers[contentId].Timer.Dispose(); + _ContentTimers.Remove(contentId); + } + } + } } } diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index 2cacff5a6..b478f32ea 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -515,6 +515,7 @@ private void LoadPacketHandler() AddHandler(new QuestPlayStartHandler(this)); AddHandler(new QuestPlayStartTimerHandler(this)); AddHandler(new QuestPlayEndHandler(this)); + AddHandler(new QuestPlayInterruptHandler(this)); AddHandler(new QuestGetEndContentsRecruitListHandler(this)); AddHandler(new EntryBoardEntryBoardListHandler(this)); diff --git a/Arrowgene.Ddon.GameServer/Handler/CharacterDecideCharacterIdHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CharacterDecideCharacterIdHandler.cs index 312381073..b8855e26f 100644 --- a/Arrowgene.Ddon.GameServer/Handler/CharacterDecideCharacterIdHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/CharacterDecideCharacterIdHandler.cs @@ -87,7 +87,7 @@ public override void Handle(GameClient client, StructurePacket gContentMapping = new Dictionary() + { + {1, ContentsType.ExtremeMission}, // Seneka (WDT) + {2, ContentsType.ExtremeMission}, // Issac (WDT) + {3, ContentsType.ExtremeMission}, + {4, ContentsType.ClanDungeon}, // Clan Dungeon + {5, ContentsType.ExtremeMission}, + {6, ContentsType.ExtremeMission}, // Nayajiku (Lookout Castle) + {7, ContentsType.ExtremeMission}, // Doris (Mergado) + {8, ContentsType.ExtremeMission}, + {9, ContentsType.ExtremeMission}, // Alan (WDT) + {10, ContentsType.ChainDungeon}, // Travers (WDT) + // May be more... + }; + public override S2CQuestGetEndContentsGroupRes Handle(GameClient client, C2SQuestGetEndContentsGroupReq request) { // var pcap0 = new S2CQuestGetEndContentsGroupRes.Serializer().Read(pcap0_data); var results = new S2CQuestGetEndContentsGroupRes() { - ContentsType = ContentsType.End, + ContentsType = gContentMapping.ContainsKey(request.GroupId) ? gContentMapping[request.GroupId] : ContentsType.ExtremeMission, GroupId = request.GroupId }; diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs index 657997f29..e2c568879 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs @@ -29,13 +29,17 @@ public QuestPlayEndHandler(DdonGameServer server) : base(server) public override S2CQuestPlayEndRes Handle(GameClient client, C2SQuestPlayEndReq request) { - var groupId = Server.ExmManager.GetContentIdForCharacter(client.Character); - var quest = Server.ExmManager.GetQuestForContent(groupId); + var contentId = client.Party.ContentId; + Server.ExmManager.CancelTimer(contentId); + var quest = Server.ExmManager.GetQuestForContent(contentId); + var ntc = new S2CQuestPlayEndNtc(); ntc.ContentsPlayEnd.RewardItemDetailList = quest.ToCDataTimeGainQuestList(0).RewardItemDetailList; client.Party.SendToAll(ntc); + client.Party.ContentInProgress = false; + return new S2CQuestPlayEndRes(); } } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs new file mode 100644 index 000000000..b75eb2c1e --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs @@ -0,0 +1,31 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Logging; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class QuestPlayInterruptHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestPlayEntryHandler)); + + public QuestPlayInterruptHandler(DdonGameServer server) : base(server) + { + } + + public override S2CQuestPlayInterruptRes Handle(GameClient client, C2SQuestPlayInterruptReq request) + { + client.Party.SendToAll(new S2CQuestPlayInterruptNtc() + { + CharacterId = client.Character.CharacterId, + DeadlineSec = 60 + }); + + return new S2CQuestPlayInterruptRes() + { + DeadlineSec = 60 + }; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs index 323dc8c78..6922f87cf 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs @@ -3,8 +3,11 @@ using Arrowgene.Ddon.Server.Network; 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; +using System.Collections.Generic; using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler @@ -22,7 +25,9 @@ public QuestPlayStartHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket request) { - // var pcap1 = new S2CQuestTimeGainQuestPlayStartNtc.Serializer().Read(pcap1_data); + client.Send(new S2CQuestPlayerStartRes()); + + var pcap1 = new S2CQuestTimeGainQuestPlayStartNtc.Serializer().Read(pcap1_data); var quest = QuestManager.GetQuest(request.Structure.QuestScheduleId); if (quest != null) { @@ -42,21 +47,68 @@ public override void Handle(GameClient client, StructurePacket() + { + new CDataSwitchStorage() + { + StorageType = StorageType.ItemBagConsumable, + Num = 10 + } + } + }; + client.Party.SendToAll(missionItemsNtcs); - var pcap2 = new S2CSituationDataUpdateObjectivesNtc.Serializer().Read(pcap2_data); - client.Party.SendToAll(pcap2); -#if false - var leaveNtc = new S2CUserListLeaveNtc() + foreach (var memberClient in client.Party.Clients) { - CharacterList = client.Party.Clients.Select(x => new CDataCommonU32() { Value = x.Character.CharacterId }).ToList() - }; - client.Send(leaveNtc); -#endif + S2CItemUpdateCharacterItemNtc itemNtc = new S2CItemUpdateCharacterItemNtc() + { + UpdateType = ItemNoticeType.SwitchingStorage + }; + + ushort i = 0; + foreach (var storageItem in memberClient.Character.Storage.GetStorage(StorageType.ItemBagConsumable).Items) + { + ushort slot = (ushort)(i + 1); + + i++; + if (storageItem == null) + { + continue; + } + + var (item, amount) = storageItem; + + itemNtc.UpdateItemList.Add(new CDataItemUpdateResult() + { + UpdateItemNum = -(int)amount, + ItemList = new CDataItemList() + { + ItemUId = item.UId, + ItemId = item.ItemId, + StorageType = StorageType.ItemBagConsumable, + SlotNo = slot, + ItemNum = amount + } + }); + itemNtc.UpdateItemList.Add(new CDataItemUpdateResult() + { + UpdateItemNum = 0, + ItemList = new CDataItemList() + { + StorageType = StorageType.ItemBagConsumable, + SlotNo = slot, + ItemNum = 0 + } + }); + } + client.Send(itemNtc); + } + + client.Party.ContentInProgress = true; } private static readonly byte[] pcap1_data = new byte[] { 0x00, 0x00, 0x01, 0xEB, 0x00, 0x04, 0xD1, 0x04, 0x03, 0x01, 0x0B, 0x08, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xB3, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x7C, 0x00, 0x00, 0x21, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x14, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x00, 0x00, 0x14, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x14, 0xC1, 0x04, 0x2C, 0xA2, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x00, 0x00, 0x14, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x14, 0xC1, 0x04, 0x2C, 0xA2, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x21, 0xE0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE2, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs index e822d54d6..fa25eee10 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs @@ -1,7 +1,16 @@ +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.GameServer.Quests; using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Logging; using System; +using System.Buffers.Text; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Arrowgene.Ddon.GameServer.Handler { @@ -13,15 +22,23 @@ public QuestPlayStartTimerHandler(DdonGameServer server) : base(server) { } + private async void QuestTimeoutNotification(CancellationToken token) + { + + } + public override S2CQuestPlayStartTimerRes Handle(GameClient client, C2SQuestPlayStartTimerReq request) { + var contentId = Server.ExmManager.GetContentIdForCharacter(client.Character); + var quest = Server.ExmManager.GetQuestForContent(contentId); + var ntc = new S2CQuestPlayStartTimerNtc() { - PlayEndDateTime = (ulong)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + 120000) + PlayEndDateTime = (ulong)(DateTimeOffset.UtcNow.ToUnixTimeSeconds() + quest.MissionParams.PlaytimeInSeconds) }; client.Party.SendToAll(ntc); - + Server.ExmManager.StartTimer(contentId, client, quest.MissionParams.PlaytimeInSeconds); return new S2CQuestPlayStartTimerRes(); } diff --git a/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs index 8972658fd..aae975d10 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs @@ -74,6 +74,15 @@ public override S2CStageAreaChangeRes Handle(GameClient client, C2SStageAreaChan } } + if (client.Party.ContentInProgress) + { + var quest = Server.ExmManager.GetQuestForContent(client.Party.ContentId); + if (quest != null) + { + quest.HandleAreaChange(client, client.Character.Stage); + } + } + return res; } } diff --git a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs index 06f43846a..39e41f4c0 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs @@ -29,6 +29,7 @@ public class PartyGroup private bool _isBreakup; public readonly ulong ContentId; + public bool ContentInProgress; public InstanceEnemyManager InstanceEnemyManager { get; } diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 84cb52549..5230ecd46 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -184,20 +184,6 @@ public bool HasEnemiesInCurrentStageGroup(Quest quest, StageId stageId) } } - public bool HasEnemiesInCurrentStageGroup(Quest quest, StageId stageId, uint subGroupId) - { - lock (ActiveQuests) - { - var questState = ActiveQuests[quest.QuestId]; - if (!questState.QuestEnemies.ContainsKey(stageId)) - { - return false; - } - - return questState.QuestEnemies[stageId].ContainsKey(subGroupId); - } - } - public void SetInstanceEnemies(Quest quest, StageId stageId, ushort subGroupId, List enemies) { lock (ActiveQuests) @@ -210,6 +196,16 @@ public List GetInstancedEnemies(Quest quest, StageId stageId, us { lock (ActiveQuests) { + if (!ActiveQuests[quest.QuestId].QuestEnemies.ContainsKey(stageId)) + { + return new List(); + } + + if (!ActiveQuests[quest.QuestId].QuestEnemies[stageId].ContainsKey(subGroupId)) + { + return new List(); + } + return ActiveQuests[quest.QuestId].QuestEnemies[stageId][subGroupId]; } } diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index b9532cb2c..0f1327354 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -7,6 +7,7 @@ using Arrowgene.Ddon.Shared.Model; using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Logging; +using System.CodeDom.Compiler; using System.Collections.Generic; namespace Arrowgene.Ddon.GameServer.Quests @@ -80,6 +81,7 @@ public static GenericQuest FromAsset(QuestAssetData questAsset) { var enemyGroup = quest.EnemyGroups[groupId]; quest.Locations.Add(new QuestLocation() { StageId = enemyGroup.StageId, SubGroupId = (ushort) enemyGroup.SubGroupId }); + quest.UniqueEnemyGroups.Add(enemyGroup.StageId); } } break; @@ -193,6 +195,12 @@ public override List StateMachineExecute(DdonGameServer } } + if (questBlock.BlockType == QuestBlockType.ExtendTime && client.Party.ContentId != 0) + { + var newEndTime = server.ExmManager.ExtendTimer(client.Party.ContentId, questBlock.TimeAmount); + client.Party.SendToAll(new S2CQuestPlayAddTimerNtc() { PlayEndDateTime = newEndTime }); + } + foreach (var item in questBlock.HandPlayerItems) { var result = server.ItemManager.AddItem(server, client.Character, true, item.ItemId, item.Amount); @@ -331,6 +339,11 @@ private static CDataQuestProcessState BlockAsCDataQuestProcessState(GenericQuest resultCommands.Add(QuestManager.ResultCommand.Prt(StageManager.ConvertIdToStageNo(questBlock.StageId), questBlock.PartyGatherPoint.x, questBlock.PartyGatherPoint.y, questBlock.PartyGatherPoint.z)); } break; + case QuestBlockType.IsGatherPartyInStage: + { + checkCommands.Add(QuestManager.CheckCommand.IsGatherPartyInStage(StageManager.ConvertIdToStageNo(questBlock.StageId))); + } + break; case QuestBlockType.DiscoverEnemy: case QuestBlockType.SeekOutEnemiesAtMarkedLocation: { @@ -562,6 +575,9 @@ private static CDataQuestProcessState BlockAsCDataQuestProcessState(GenericQuest case QuestBlockType.DestroyGroup: /* This is a pseudo block handeled at the state machine level */ break; + case QuestBlockType.ExtendTime: + /* This is a pseudo block handeled at the state machine level */ + break; case QuestBlockType.DummyBlock: /* Filler block which might do some meta things like announce or set flags */ break; diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 855329761..4b7e0b68c 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -7,6 +7,7 @@ using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Logging; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -71,6 +72,7 @@ public abstract class Quest public List QuestLayoutFlags; public QuestMissionParams MissionParams { get; protected set; } public Dictionary EnemyGroups { get; set; } + public HashSet UniqueEnemyGroups { get; protected set; } public bool IsVariantQuest { get; set; } public uint VariantId { get; set; } @@ -93,6 +95,7 @@ public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool QuestLayoutFlagSetInfo = new List(); QuestLayoutFlags = new List(); EnemyGroups = new Dictionary(); + UniqueEnemyGroups = new HashSet(); IsVariantQuest = false; VariantId = 0; MissionParams = new QuestMissionParams(); @@ -378,15 +381,18 @@ public virtual CDataTimeGainQuestList ToCDataTimeGainQuestList(uint step) result.Restrictions.Unk5List.Add(new CDataCommonU8() { Value = 2 }); #endif - + // Rewards for EXM seem to show up independently foreach (var reward in result.Param.FixedRewardItemList) { - result.RewardItemDetailList.Add(new CDataRewardItemDetail() + for (var i = 0; i < reward.Num; i++) { - ItemId = reward.ItemId, - Num = reward.Num, - Type = 12 - }); + result.RewardItemDetailList.Add(new CDataRewardItemDetail() + { + ItemId = reward.ItemId, + Num = 1, + Type = 12 + }); + } } return result; @@ -451,6 +457,22 @@ public virtual CDataContentsPlayStartData ToCDataContentsPlayStartData(uint step }; } + public virtual CDataRaidBossPlayStartData ToCDataRaidBossPlayStartData(uint step = 0) + { + return new CDataRaidBossPlayStartData() + { + CommonData = ToCDataContentsPlayStartData(step), + ClearTimePointBonusList = new List() + { + new CDataClearTimePointBonus() {Ratio = 1, Seconds = 100} + }, + RaidBossEnemyParam = new CDataRaidBossEnemyParam() + { + RaidBossId = 1 + } + }; + } + public abstract List StateMachineExecute(DdonGameServer server, GameClient client, QuestProcessState processState, out QuestProgressState questProgressState); public virtual void SendProgressWorkNotices(GameClient client, StageId stageId, uint subGroupId) @@ -474,6 +496,36 @@ public virtual void ResetEnemiesForBlock(GameClient client, QuestBlock questBloc } } + public virtual void ResetEnemiesForStage(GameClient client, StageId stageId) + { + foreach (var (groupId, group) in EnemyGroups) + { + if (group.StageId.Id == stageId.Id) + { + S2CInstanceEnemyGroupResetNtc resetNtc = new S2CInstanceEnemyGroupResetNtc() + { + LayoutId = group.StageId.ToStageLayoutId() + }; + + client.Party.InstanceEnemyManager.ResetEnemyNode(group.StageId); + client.Party.SendToAll(resetNtc); + } + } + } + + public virtual void HandleAreaChange(GameClient client, StageId stageId) + { + ResetEnemiesForStage(client, stageId); + + // client.Party.SendToAll(new S2C_63_11_16_NTC() { StageNo = res.StageNo }); + // TODO: Figure out what these do + // client.Party.SendToAll(new S2CSituationDataStartNtc() { Unk0 = 1 }); +#if false + var pcap2 = new S2CSituationDataUpdateObjectivesNtc.Serializer().Read(pcap2_data); + client.Party.SendToAll(pcap2); +#endif + } + public virtual void DestroyEnemiesForBlock(GameClient client, QuestBlock questBlock) { foreach (var groupId in questBlock.EnemyGroupIds) @@ -489,6 +541,11 @@ public virtual void DestroyEnemiesForBlock(GameClient client, QuestBlock questBl } } + public bool HasEnemiesInInCurrentStage(StageId stageId) + { + return UniqueEnemyGroups.Contains(stageId); + } + public virtual void PopulateStartingEnemyData(PartyQuestState partyQuestState) { var questState = partyQuestState.GetQuestState(this.QuestId); diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 159f73a22..54c9854bd 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -514,6 +514,8 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) questBlock.PartyGatherPoint.z = jLocation.GetProperty("z").GetInt32(); } break; + case QuestBlockType.IsGatherPartyInStage: + break; case QuestBlockType.DiscoverEnemy: case QuestBlockType.SeekOutEnemiesAtMarkedLocation: case QuestBlockType.KillGroup: @@ -676,6 +678,11 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) } } break; + case QuestBlockType.ExtendTime: + { + questBlock.TimeAmount = jblock.GetProperty("amount").GetUInt32(); + } + break; case QuestBlockType.PlayEvent: { questBlock.QuestEvent.EventId = jblock.GetProperty("event_id").GetInt32(); diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index b3a26714e..8fc1438ce 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -77,6 +77,7 @@ static EntitySerializer() Create(new CDataClanParam.Serializer()); Create(new CDataClanServerParam.Serializer()); Create(new CDataClanUserParam.Serializer()); + Create(new CDataClearTimePointBonus.Serializer()); Create(new CDataCommonU8.Serializer()); Create(new CDataCommonU32.Serializer()); Create(new CDataCommonU64.Serializer()); @@ -145,6 +146,8 @@ static EntitySerializer() Create(new CDataEntryItem.Serializer()); Create(new CDataEntryItemParam.Serializer()); Create(new CDataEntryMemberData.Serializer()); + Create(new CDataRaidBossPlayStartData.Serializer()); + Create(new CDataRaidBossEnemyParam.Serializer()); Create(new CDataEquipItemInfo.Serializer()); Create(new CDataEquipJobItem.Serializer()); @@ -165,6 +168,7 @@ static EntitySerializer() Create(new CDataGPCourseInfoSerializer()); Create(new CDataGPCourseEffectParamSerializer()); Create(new CDataGPCourseAvailableSerializer()); + Create(new CDataHasRegionBreakReward.Serializer()); Create(new CDataHistoryElement.Serializer()); Create(new CDataItemEquipElement.Serializer()); Create(new CDataItemEquipElementParam.Serializer()); @@ -643,6 +647,7 @@ static EntitySerializer() Create(new C2SQuestPlayEndReq.Serializer()); Create(new C2SQuestGetAdventureGuideQuestNtcReq.Serializer()); Create(new C2SQuestGetEndContentsRecruitListReq.Serializer()); + Create(new C2SQuestPlayInterruptReq.Serializer()); Create(new C2SServerGameTimeGetBaseInfoReq.Serializer()); Create(new C2SServerGetRealTimeReq.Serializer()); @@ -944,6 +949,7 @@ static EntitySerializer() Create(new S2CItemGetEmbodyPayCostRes.Serializer()); Create(new S2CItemGetSpecifiedHavingItemListRes.Serializer()); Create(new S2CItemEmbodyItemsRes.Serializer()); + Create(new S2CItemSwitchStorageNtc.Serializer()); Create(new S2CJob_33_3_16_Ntc.Serializer()); Create(new S2CJobChangeJobNtc.Serializer()); @@ -1113,12 +1119,17 @@ static EntitySerializer() Create(new S2CQuestPlayEntryNtc.Serializer()); Create(new S2CQuestPlayerStartRes.Serializer()); Create(new S2CQuestTimeGainQuestPlayStartNtc.Serializer()); + Create(new S2CQuestRaidBossPlayStartNtc.Serializer()); Create(new S2CQuestPlayStartTimerRes.Serializer()); Create(new S2CQuestPlayStartTimerNtc.Serializer()); Create(new S2CQuestPlayEndRes.Serializer()); Create(new S2CQuestPlayEndNtc.Serializer()); Create(new S2CQuestGetAdventureGuideQuestNtcRes.Serializer()); Create(new S2CQuestGetEndContentsRecruitListRes.Serializer()); + Create(new S2CQuestPlayInterruptRes.Serializer()); + Create(new S2CQuestPlayInterruptNtc.Serializer()); + Create(new S2CQuestPlayTimeupNtc.Serializer()); + Create(new S2CQuestPlayAddTimerNtc.Serializer()); Create(new S2CQuestSendLeaderQuestOrderConditionInfoNtc.Serializer()); Create(new S2CQuestSendLeaderQuestOrderConditionInfoRes.Serializer()); @@ -1142,6 +1153,7 @@ static EntitySerializer() Create(new S2CSituationDataEndNtc.Serializer()); Create(new S2C_63_7_16_NTC.Serializer()); Create(new S2C_63_10_16_NTC.Serializer()); + Create(new S2C_63_11_16_NTC.Serializer()); Create(new S2CSkillAbilitySetNtc.Serializer()); Create(new S2CSkillChangeExSkillRes.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayInterruptReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayInterruptReq.cs new file mode 100644 index 000000000..f32c00a30 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayInterruptReq.cs @@ -0,0 +1,26 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; +using System; +using System.Security.Cryptography.X509Certificates; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestPlayInterruptReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_PLAY_INTERRUPT_REQ; + + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestPlayInterruptReq obj) + { + } + + public override C2SQuestPlayInterruptReq Read(IBuffer buffer) + { + C2SQuestPlayInterruptReq obj = new C2SQuestPlayInterruptReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CInstanceEnemyGroupResetNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CInstanceEnemyGroupResetNtc.cs index 3a71644cc..352d5ddbe 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CInstanceEnemyGroupResetNtc.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CInstanceEnemyGroupResetNtc.cs @@ -31,4 +31,3 @@ public override S2CInstanceEnemyGroupResetNtc Read(IBuffer buffer) } } } - diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CItemSwitchStorageNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CItemSwitchStorageNtc.cs new file mode 100644 index 000000000..d6f019649 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CItemSwitchStorageNtc.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CItemSwitchStorageNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_SWITCH_STORAGE_NTC; + + public S2CItemSwitchStorageNtc() + { + ChangeList = new List(); + } + + public int Unk0 { get; set; } + public bool IsStart { get; set; } + public List ChangeList { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CItemSwitchStorageNtc obj) + { + WriteInt32(buffer, obj.Unk0); + WriteBool(buffer, obj.IsStart); + WriteEntityList(buffer, obj.ChangeList); + } + + public override S2CItemSwitchStorageNtc Read(IBuffer buffer) + { + S2CItemSwitchStorageNtc obj = new S2CItemSwitchStorageNtc(); + obj.Unk0 = ReadInt32(buffer); + obj.IsStart = ReadBool(buffer); + obj.ChangeList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayAddTimerNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayAddTimerNtc.cs new file mode 100644 index 000000000..1d89430db --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayAddTimerNtc.cs @@ -0,0 +1,31 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayAddTimerNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_PLAY_ADD_TIMER_NTC; + + public S2CQuestPlayAddTimerNtc() + { + } + + public ulong PlayEndDateTime { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayAddTimerNtc obj) + { + WriteUInt64(buffer, obj.PlayEndDateTime); + } + + public override S2CQuestPlayAddTimerNtc Read(IBuffer buffer) + { + S2CQuestPlayAddTimerNtc obj = new S2CQuestPlayAddTimerNtc(); + obj.PlayEndDateTime = ReadUInt64(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptNtc.cs new file mode 100644 index 000000000..a5bc87d83 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptNtc.cs @@ -0,0 +1,36 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayInterruptNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_PLAY_INTERRUPT_NTC; + + public S2CQuestPlayInterruptNtc() + { + } + + public uint CharacterId { get; set; } + public byte DeadlineSec { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayInterruptNtc obj) + { + WriteUInt32(buffer, obj.CharacterId); + WriteByte(buffer, obj.DeadlineSec); + } + + public override S2CQuestPlayInterruptNtc Read(IBuffer buffer) + { + S2CQuestPlayInterruptNtc obj = new S2CQuestPlayInterruptNtc(); + obj.CharacterId = ReadUInt32(buffer); + obj.DeadlineSec = ReadByte(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptRes.cs new file mode 100644 index 000000000..4660712f9 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptRes.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayInterruptRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_PLAY_INTERRUPT_RES; + + public S2CQuestPlayInterruptRes() + { + } + public byte DeadlineSec { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayInterruptRes obj) + { + WriteServerResponse(buffer, obj); + WriteByte(buffer, obj.DeadlineSec); + } + + public override S2CQuestPlayInterruptRes Read(IBuffer buffer) + { + S2CQuestPlayInterruptRes obj = new S2CQuestPlayInterruptRes(); + ReadServerResponse(buffer, obj); + obj.DeadlineSec = ReadByte(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayTimeupNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayTimeupNtc.cs new file mode 100644 index 000000000..2270cc1be --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayTimeupNtc.cs @@ -0,0 +1,30 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayTimeupNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_PLAY_TIMEUP_NTC; + + + public S2CQuestPlayTimeupNtc() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayTimeupNtc obj) + { + } + + public override S2CQuestPlayTimeupNtc Read(IBuffer buffer) + { + S2CQuestPlayTimeupNtc obj = new S2CQuestPlayTimeupNtc(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestRaidBossPlayStartNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestRaidBossPlayStartNtc.cs new file mode 100644 index 000000000..7fb332d37 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestRaidBossPlayStartNtc.cs @@ -0,0 +1,40 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestRaidBossPlayStartNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_RAID_BOSS_PLAY_START_NTC; + + public S2CQuestRaidBossPlayStartNtc() + { + RaidBossPlayStartData = new CDataRaidBossPlayStartData(); + } + + public CDataRaidBossPlayStartData RaidBossPlayStartData { get; set; } + + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestRaidBossPlayStartNtc obj) + { + WriteEntity(buffer, obj.RaidBossPlayStartData); + } + + public override S2CQuestRaidBossPlayStartNtc Read(IBuffer buffer) + { + S2CQuestRaidBossPlayStartNtc obj = new S2CQuestRaidBossPlayStartNtc(); + obj.RaidBossPlayStartData = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestTimeGainQuestPlayStartNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestTimeGainQuestPlayStartNtc.cs index 3fdd622c7..94f90d686 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestTimeGainQuestPlayStartNtc.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestTimeGainQuestPlayStartNtc.cs @@ -1,7 +1,3 @@ - - -// CDataTimeGainQuestPlayStartData - using Arrowgene.Buffers; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Network; @@ -15,7 +11,7 @@ namespace Arrowgene.Ddon.Shared.Entity.PacketStructure { public class S2CQuestTimeGainQuestPlayStartNtc : IPacketStructure { - public PacketId Id => PacketId.S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC; + public PacketId Id => PacketId.S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC; // Might be chain quest??? public S2CQuestTimeGainQuestPlayStartNtc() { diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_11_16_NTC.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_11_16_NTC.cs new file mode 100644 index 000000000..d2e843400 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2C_63_11_16_NTC.cs @@ -0,0 +1,33 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2C_63_11_16_NTC : IPacketStructure + { + public PacketId Id => PacketId.S2C_63_11_16_NTC; + + public S2C_63_11_16_NTC() + { + } + + public uint StageNo { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2C_63_11_16_NTC obj) + { + WriteUInt32(buffer, obj.StageNo); + } + + public override S2C_63_11_16_NTC Read(IBuffer buffer) + { + S2C_63_11_16_NTC obj = new S2C_63_11_16_NTC(); + obj.StageNo = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataClearTimePointBonus.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataClearTimePointBonus.cs new file mode 100644 index 000000000..af70015e3 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataClearTimePointBonus.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataClearTimePointBonus + { + public CDataClearTimePointBonus() + { + } + + public uint Seconds { get; set; } + public uint Ratio { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataClearTimePointBonus obj) + { + WriteUInt32(buffer, obj.Seconds); + WriteUInt32(buffer, obj.Ratio); + } + + public override CDataClearTimePointBonus Read(IBuffer buffer) + { + CDataClearTimePointBonus obj = new CDataClearTimePointBonus(); + obj.Seconds = ReadUInt32(buffer); + obj.Ratio = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataHasRegionBreakReward.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataHasRegionBreakReward.cs new file mode 100644 index 000000000..ca4356a86 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataHasRegionBreakReward.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataHasRegionBreakReward + { + public CDataHasRegionBreakReward() + { + } + + public byte RegionNo { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataHasRegionBreakReward obj) + { + WriteByte(buffer, obj.RegionNo); + } + + public override CDataHasRegionBreakReward Read(IBuffer buffer) + { + CDataHasRegionBreakReward obj = new CDataHasRegionBreakReward(); + obj.RegionNo = ReadByte(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataRaidBossEnemyParam.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRaidBossEnemyParam.cs new file mode 100644 index 000000000..e8e7359b0 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRaidBossEnemyParam.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataRaidBossEnemyParam + { + public CDataRaidBossEnemyParam() + { + } + + public uint RaidBossId { get; set; } + public uint RateHp { get; set; } + public ushort RateHpPart { get; set; } + public ushort RateShrinkDef { get; set; } + public ushort RateShrinkDefPart { get; set; } + public ushort RateBlowDef { get; set; } + public ushort RateBlowDefPart { get; set; } + public ushort RateOcdDef { get; set; } + public ushort RateOcdAtk { get; set; } + public ushort RateShakeDef { get; set; } + public ushort RateDownDef { get; set; } + public ushort RateStr { get; set; } + public ushort RatePhyAtkBase { get; set; } + public ushort RateMagAtkBase { get; set; } + public ushort RatePhyDefBase { get; set; } + public ushort RateMagDefBase { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataRaidBossEnemyParam obj) + { + WriteUInt32(buffer, obj.RaidBossId); + WriteUInt32(buffer, obj.RateHp); + WriteUInt16(buffer, obj.RateHpPart); + WriteUInt16(buffer, obj.RateShrinkDef); + WriteUInt16(buffer, obj.RateShrinkDefPart); + WriteUInt16(buffer, obj.RateBlowDef); + WriteUInt16(buffer, obj.RateBlowDefPart); + WriteUInt16(buffer, obj.RateOcdDef); + WriteUInt16(buffer, obj.RateOcdAtk); + WriteUInt16(buffer, obj.RateShakeDef); + WriteUInt16(buffer, obj.RateDownDef); + WriteUInt16(buffer, obj.RateStr); + WriteUInt16(buffer, obj.RatePhyAtkBase); + WriteUInt16(buffer, obj.RateMagAtkBase); + WriteUInt16(buffer, obj.RatePhyDefBase); + WriteUInt16(buffer, obj.RateMagDefBase); + } + + public override CDataRaidBossEnemyParam Read(IBuffer buffer) + { + CDataRaidBossEnemyParam obj = new CDataRaidBossEnemyParam(); + obj.RaidBossId = ReadUInt32(buffer); + obj.RateHp = ReadUInt32(buffer); + obj.RateHpPart = ReadUInt16(buffer); + obj.RateShrinkDef = ReadUInt16(buffer); + obj.RateShrinkDefPart = ReadUInt16(buffer); + obj.RateBlowDef = ReadUInt16(buffer); + obj.RateBlowDefPart = ReadUInt16(buffer); + obj.RateOcdDef = ReadUInt16(buffer); + obj.RateOcdAtk = ReadUInt16(buffer); + obj.RateShakeDef = ReadUInt16(buffer); + obj.RateDownDef = ReadUInt16(buffer); + obj.RateStr = ReadUInt16(buffer); + obj.RatePhyAtkBase = ReadUInt16(buffer); + obj.RateMagAtkBase = ReadUInt16(buffer); + obj.RatePhyDefBase = ReadUInt16(buffer); + obj.RateMagDefBase = ReadUInt16(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataRaidBossPlayStartData.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRaidBossPlayStartData.cs new file mode 100644 index 000000000..2bd86a609 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataRaidBossPlayStartData.cs @@ -0,0 +1,42 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Model; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.Structure; + +public class CDataRaidBossPlayStartData +{ + public CDataRaidBossPlayStartData() + { + CommonData = new CDataContentsPlayStartData(); + ClearTimePointBonusList = new List(); + RaidBossEnemyParam = new CDataRaidBossEnemyParam(); + RegionBreakRewardList = new List(); + } + + public CDataContentsPlayStartData CommonData { get; set; } + public List ClearTimePointBonusList { get; set; } + public CDataRaidBossEnemyParam RaidBossEnemyParam { get; set; } + public List RegionBreakRewardList { get; set; } + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataRaidBossPlayStartData obj) + { + WriteEntity(buffer, obj.CommonData); + WriteEntityList(buffer, obj.ClearTimePointBonusList); + WriteEntity(buffer, obj.RaidBossEnemyParam); + WriteEntityList(buffer, obj.RegionBreakRewardList); + } + + public override CDataRaidBossPlayStartData Read(IBuffer buffer) + { + CDataRaidBossPlayStartData obj = new CDataRaidBossPlayStartData(); + obj.CommonData = ReadEntity(buffer); + obj.ClearTimePointBonusList = ReadEntityList(buffer); + obj.RaidBossEnemyParam = ReadEntity(buffer); + obj.RegionBreakRewardList = ReadEntityList(buffer); + return obj; + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataSwitchStorage.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataSwitchStorage.cs index 5625c0bca..1b5eb5a37 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataSwitchStorage.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataSwitchStorage.cs @@ -1,31 +1,30 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Arrowgene.Buffers; using Arrowgene.Ddon.Shared.Model; namespace Arrowgene.Ddon.Shared.Entity.Structure { - public class CDataSwitchStorage + public class CDataSwitchStorage // CDataSwitchStorage? { - public CDataSwitchStorage() - { - } - - public StorageType TargetStorageType { get; set; } - public StorageType ChangeStorageType { get; set; } + public StorageType StorageType { get; set; } + public ushort Num { get; set; } public class Serializer : EntitySerializer { public override void Write(IBuffer buffer, CDataSwitchStorage obj) { - WriteByte(buffer, (byte) obj.TargetStorageType); - WriteByte(buffer, (byte) obj.ChangeStorageType); + WriteByte(buffer, (byte) obj.StorageType); + WriteUInt16(buffer, obj.Num); } public override CDataSwitchStorage Read(IBuffer buffer) { CDataSwitchStorage obj = new CDataSwitchStorage(); - obj.TargetStorageType = (StorageType)ReadByte(buffer); - obj.ChangeStorageType = (StorageType)ReadByte(buffer); + obj.StorageType = (StorageType)ReadByte(buffer); + obj.Num = ReadUInt16(buffer); return obj; } } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json index a8060421f..1b586fe1e 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json @@ -9,10 +9,10 @@ "mission_params": { "group": 1, "minimum_members": 1, - "playtime": 3600, + "playtime": 1200, "solo_only": true, "max_pawns": 3, - "phase_groups": [733, 0, 733] + "phase_groups": [] }, "order_conditions": [ {"type": "MainQuestCompleted", "Param1": 30} @@ -372,7 +372,7 @@ { "blocks": [ { - "type": "IsStageNo", + "type": "IsGatherPartyInStage", "stage_id": { "id": 293 } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json index dd0b10274..cb0422133 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json @@ -9,10 +9,10 @@ "mission_params": { "group": 1, "minimum_members": 1, - "playtime": 3600, + "playtime": 1500, "solo_only": true, "max_pawns": 3, - "phase_groups": [419, 0, 419] + "phase_groups": [] }, "order_conditions": [ {"type": "ClearExtremeMission", "Param1": 50101020} @@ -26,17 +26,7 @@ "num": 3 } ] - }, - { - "type": "fixed", - "loot_pool": [ - { - "item_id": 9378, - "num": 3 - } - ] } - ], "enemy_groups" : [ { @@ -216,7 +206,7 @@ { "blocks": [ { - "type": "IsStageNo", + "type": "IsGatherPartyInStage", "stage_id": { "id": 286 } @@ -229,6 +219,7 @@ }, { "type": "KillGroup", + "announce_type": "Start", "stage_start": 1, "groups": [0] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json index 254d5ee85..d96b12299 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json @@ -9,10 +9,10 @@ "mission_params": { "group": 1, "minimum_members": 1, - "playtime": 3600, + "playtime": 1200, "solo_only": true, "max_pawns": 3, - "phase_groups": [702, 0, 702] + "phase_groups": [] }, "order_conditions": [ {"type": "ClearExtremeMission", "Param1": 50102020} @@ -226,7 +226,7 @@ { "blocks": [ { - "type": "IsStageNo", + "type": "IsGatherPartyInStage", "stage_id": { "id": 290 } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json index 13da3b54d..576fcf56d 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json @@ -9,10 +9,10 @@ "mission_params": { "group": 1, "minimum_members": 1, - "playtime": 3600, + "playtime": 1200, "solo_only": true, "max_pawns": 3, - "phase_groups": [421, 0, 421] + "phase_groups": [] }, "order_conditions": [ {"type": "ClearExtremeMission", "Param1": 50103020} @@ -155,7 +155,7 @@ { "blocks": [ { - "type": "IsStageNo", + "type": "IsGatherPartyInStage", "stage_id": { "id": 288 } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json index a153b0f52..160890e8b 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json @@ -9,10 +9,10 @@ "mission_params": { "group": 2, "minimum_members": 1, - "playtime": 3600, + "playtime": 1200, "solo_only": true, "max_pawns": 3, - "phase_groups": [113, 5, 113] + "phase_groups": [] }, "order_conditions": [ {"type": "ClearExtremeMission", "Param1": 50104000} @@ -51,6 +51,12 @@ "processes": [ { "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 433 + } + }, { "type": "KillGroup", "announce_type": "Start", @@ -61,9 +67,21 @@ { "blocks": [ { - "comment": "Wait for fight to start", - "type": "MyQstFlags", - "check_flags": [1] + "type": "Raw", + "check_commands": [ + {"type": "NoticeInterruptContents"} + ], + "result_commands": [ + {"type": "SetCheckPoint"} + ] + }, + { + "type": "Raw", + "result_commands": [ + {"type": "ReturnCheckPointEx", "Param": 0}, + {"type": "ReturnCheckPointEx", "Param": 1}, + {"type": "EndEndQuest"} + ] } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json index a2164d59c..4b01104f0 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json @@ -9,10 +9,10 @@ "mission_params": { "group": 2, "minimum_members": 1, - "playtime": 3600, + "playtime": 900, "solo_only": true, "max_pawns": 3, - "phase_groups": [431, 0, 431] + "phase_groups": [] }, "order_conditions": [ {"type": "ClearExtremeMission", "Param1": 50201000} @@ -186,7 +186,7 @@ { "blocks": [ { - "type": "IsStageNo", + "type": "IsGatherPartyInStage", "stage_id": { "id": 362 } @@ -199,6 +199,7 @@ }, { "type": "KillGroup", + "announce_type": "Start", "groups": [0] }, { diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json index 5f59fea74..59ee9271a 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json @@ -9,10 +9,10 @@ "mission_params": { "group": 1, "minimum_members": 1, - "playtime": 3600, + "playtime": 600, "solo_only": true, "max_pawns": 3, - "phase_groups": [438, 0, 438] + "phase_groups": [] }, "order_conditions": [ {"type": "SoloWithPawns"}, @@ -302,7 +302,7 @@ { "blocks": [ { - "type": "IsStageNo", + "type": "IsGatherPartyInStage", "stage_id": { "id": 452 } @@ -315,6 +315,7 @@ }, { "type": "KillGroup", + "announce_type": "Start", "stage_start": 1, "groups": [0] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json index 2a6e66117..0b590f9a9 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json @@ -9,10 +9,10 @@ "mission_params": { "group": 2, "minimum_members": 1, - "playtime": 3600, + "playtime": 600, "solo_only": true, "max_pawns": 3, - "phase_groups": [124, 5, 124] + "phase_groups": [] }, "order_conditions": [ {"type": "ClearExtremeMission", "Param1": 50202000} @@ -44,15 +44,170 @@ "is_boss": true } ] + }, + { + "comment": "Timegroup (1)", + "stage_id": { + "id": 459, + "group_id": 5 + }, + "enemies": [ + { + "comment": "Gorechimera", + "enemy_id": "0x015201", + "level": 75, + "exp": 0, + "is_boss": true, + "named_enemy_params_id": 1570 + }, + { + "comment": "Severly Infected Stymphalides", + "enemy_id": "0x010614", + "level": 75, + "exp": 0, + "infection_type": 2, + "named_enemy_params_id": 1459 + } + ] + }, + { + "comment": "Timegroup (2)", + "stage_id": { + "id": 459, + "group_id": 6 + }, + "enemies": [ + { + "comment": "Severely Infected Gorecyclops", + "enemy_id": "0x015017", + "level": 75, + "exp": 0, + "is_boss": true, + "infection_type": 2, + "named_enemy_params_id": 1570 + }, + { + "comment": "Severly Infected Warg", + "enemy_id": "0x010220", + "level": 75, + "exp": 0, + "infection_type": 2, + "named_enemy_params_id": 1459 + } + ] + }, + { + "comment": "Timegroup (3)", + "stage_id": { + "id": 459, + "group_id": 7 + }, + "enemies": [ + { + "comment": "Medusa", + "enemy_id": "0x015610", + "level": 75, + "exp": 0, + "is_boss": true, + "named_enemy_params_id": 1570 + }, + { + "comment": "Eliminator", + "enemy_id": "0x010508", + "level": 75, + "exp": 0, + "named_enemy_params_id": 1460 + }, + { + "comment": "Eliminator", + "enemy_id": "0x010508", + "level": 75, + "exp": 0, + "named_enemy_params_id": 1460 + }, + { + "comment": "Eliminator", + "enemy_id": "0x010508", + "level": 75, + "exp": 0, + "named_enemy_params_id": 1460 + } + ] } ], "processes": [ { "blocks": [ { - "type": "KillGroup", + "type": "IsGatherPartyInStage", "announce_type": "Start", + "stage_id": { + "id": 459 + } + }, + { + "type": "DiscoverEnemy", "groups": [0] + }, + { + "type": "KillGroup", + "announce_type": "Caution", + "reset_group": false, + "groups": [0] + } + ] + }, + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 459 + } + }, + { + "type": "KillGroup", + "groups": [1] + }, + { + "type": "ExtendTime", + "amount": 240 + } + ] + }, + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 459 + } + }, + { + "type": "KillGroup", + "groups": [2] + }, + { + "type": "ExtendTime", + "amount": 240 + } + ] + }, + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 459 + } + }, + { + "type": "KillGroup", + "groups": [3] + }, + { + "type": "ExtendTime", + "amount": 240 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json index bf5af877a..9cfc69b60 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json @@ -9,10 +9,10 @@ "mission_params": { "group": 2, "minimum_members": 1, - "playtime": 3600, + "playtime": 900, "solo_only": true, "max_pawns": 3, - "phase_groups": [889, 0, 889] + "phase_groups": [] }, "order_conditions": [ {"type": "ClearExtremeMission", "Param1": 50203000} @@ -26,15 +26,6 @@ "num": 3 } ] - }, - { - "type": "fixed", - "loot_pool": [ - { - "item_id": 7555, - "num": 1 - } - ] } ], "enemy_groups" : [ @@ -58,6 +49,12 @@ "processes": [ { "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 458 + } + }, { "type": "KillGroup", "announce_type": "Start", diff --git a/Arrowgene.Ddon.Shared/Model/ContentsType.cs b/Arrowgene.Ddon.Shared/Model/ContentsType.cs index 49041d8a8..86d7bc261 100644 --- a/Arrowgene.Ddon.Shared/Model/ContentsType.cs +++ b/Arrowgene.Ddon.Shared/Model/ContentsType.cs @@ -8,6 +8,20 @@ namespace Arrowgene.Ddon.Shared.Model { public enum ContentsType : uint { + ExtremeMission = 1, + ClanDungeon = 2, + Unkown3 = 3, + ChainDungeon = 4, + // May be more types + +#if false + None = 0xffffffff, + Cycle = 0, + End = 1, + Unk2 = 2, + Unk3 = 3, + Chain = 4 + Unknown = 0, Begin = 1, WorldQuest = 2, @@ -16,5 +30,6 @@ public enum ContentsType : uint QuickPartyMainQuest = 6, QuickPartyArea = 7, Large = 8 +#endif } } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestBlock.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestBlock.cs index c0b91ab83..d9fa37944 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestBlock.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestBlock.cs @@ -34,6 +34,7 @@ public class QuestBlock public bool ShouldStageJump { get; set; } public bool IsCheckpoint { get; set; } + public uint TimeAmount { get; set; } public QuestEvent QuestEvent { get; set; } public QuestCameraEvent QuestCameraEvent { get; set; } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestBlockType.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestBlockType.cs index 90ea721b6..9c5330233 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestBlockType.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestBlockType.cs @@ -13,6 +13,7 @@ public enum QuestBlockType : uint NpcTouchAndOrder, QuestNpcTalkAndOrder, PartyGather, + IsGatherPartyInStage, DiscoverEnemy, KillGroup, SpawnGroup, @@ -30,6 +31,7 @@ public enum QuestBlockType : uint IsQuestOrdered, PlayEvent, KillTargetEnemies, + ExtendTime, Raw, DummyBlock, DummyBlockNoProgress, diff --git a/Arrowgene.Ddon.Shared/Network/PacketId.cs b/Arrowgene.Ddon.Shared/Network/PacketId.cs index 79d2ec711..d71b21444 100644 --- a/Arrowgene.Ddon.Shared/Network/PacketId.cs +++ b/Arrowgene.Ddon.Shared/Network/PacketId.cs @@ -550,7 +550,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_ITEM_EXTEND_ITEM_SLOT_NTC = new PacketId(10, 10, 16, "S2C_ITEM_EXTEND_ITEM_SLOT_NTC", ServerType.Game, PacketSource.Server, "S2C_ITEM_10_10_16_NTC"); public static readonly PacketId S2C_ITEM_EXTEND_EQUIP_SLOT_NTC = new PacketId(10, 11, 16, "S2C_ITEM_EXTEND_EQUIP_SLOT_NTC", ServerType.Game, PacketSource.Server, "S2C_ITEM_10_11_16_NTC"); public static readonly PacketId S2C_ITEM_UPDATE_CHARACTER_ITEM_NTC = new PacketId(10, 12, 16, "S2C_ITEM_UPDATE_CHARACTER_ITEM_NTC", ServerType.Game, PacketSource.Server, "S2C_ITEM_10_12_16_NTC"); - public static readonly PacketId S2C_ITEM_10_13_16_NTC = new PacketId(10, 13, 16, "S2C_ITEM_10_13_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_SWITCH_STORAGE_NTC = new PacketId(10, 13, 16, "S2C_SWITCH_STORAGE_NTC", ServerType.Game, PacketSource.Server, "S2C_ITEM_10_13_16_NTC"); public static readonly PacketId S2C_ITEM_ACHIEVEMENT_REWARD_RECEIVE_NTC = new PacketId(10, 14, 16, "S2C_ITEM_CRAFT_RECIPE_UNLOCK_NTC", ServerType.Game, PacketSource.Server, "S2C_ITEM_10_14_16_NTC"); public static readonly PacketId C2S_ITEM_GET_PAY_COST_REQ = new PacketId(10, 15, 1, "C2S_ITEM_GET_PAY_COST_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ITEM_GET_PAY_COST_RES = new PacketId(10, 15, 2, "S2C_ITEM_GET_PAY_COST_RES", ServerType.Game, PacketSource.Server); // 代価 @@ -672,7 +672,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_PLAY_START_TIMER_NTC = new PacketId(11, 42, 16, "S2C_QUEST_PLAY_START_TIMER_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_42_16_NTC"); public static readonly PacketId C2S_QUEST_PLAY_INTERRUPT_REQ = new PacketId(11, 43, 1, "C2S_QUEST_PLAY_INTERRUPT_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_INTERRUPT_RES = new PacketId(11, 43, 2, "S2C_QUEST_PLAY_INTERRUPT_RES", ServerType.Game, PacketSource.Server); // コンテンツプレイ中断要求に - public static readonly PacketId S2C_QUEST_11_43_16_NTC = new PacketId(11, 43, 16, "S2C_QUEST_11_43_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_QUEST_PLAY_INTERRUPT_NTC = new PacketId(11, 43, 16, "S2C_QUEST_PLAY_INTERRUPT_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_43_16_NTC"); public static readonly PacketId C2S_QUEST_PLAY_INTERRUPT_ANSWER_REQ = new PacketId(11, 44, 1, "C2S_QUEST_PLAY_INTERRUPT_ANSWER_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_INTERRUPT_ANSWER_RES = new PacketId(11, 44, 2, "S2C_QUEST_PLAY_INTERRUPT_ANSWER_RES", ServerType.Game, PacketSource.Server); // コンテンツプレイ中断応答に public static readonly PacketId C2S_QUEST_PLAY_END_REQ = new PacketId(11, 45, 1, "C2S_QUEST_PLAY_END_REQ", ServerType.Game, PacketSource.Client); @@ -763,7 +763,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_QUEST_COMPLETE_NTC = new PacketId(11, 91, 16, "S2C_QUEST_QUEST_COMPLETE_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_91_16_NTC"); // Mission Completed public static readonly PacketId S2C_QUEST_11_92_16_NTC = new PacketId(11, 92, 16, "S2C_QUEST_11_92_16_NTC", ServerType.Game, PacketSource.Server); // Mission Started public static readonly PacketId S2C_QUEST_11_93_16_NTC = new PacketId(11, 93, 16, "S2C_QUEST_11_93_16_NTC", ServerType.Game, PacketSource.Server); // Mission completed - public static readonly PacketId S2C_QUEST_11_94_16_NTC = new PacketId(11, 94, 16, "S2C_QUEST_11_94_16_NTC", ServerType.Game, PacketSource.Server); // Mission All completed + public static readonly PacketId S2C_QUEST_11_94_16_NTC = new PacketId(11, 94, 16, "S2C_QUEST_11_94_16_NTC", ServerType.Game, PacketSource.Server); // Mission All completed public static readonly PacketId S2C_QUEST_11_95_16_NTC = new PacketId(11, 95, 16, "S2C_QUEST_11_95_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_96_16_NTC = new PacketId(11, 96, 16, "S2C_QUEST_11_96_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_97_16_NTC = new PacketId(11, 97, 16, "S2C_QUEST_11_97_16_NTC", ServerType.Game, PacketSource.Server); @@ -771,19 +771,19 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_11_99_16_NTC = new PacketId(11, 99, 16, "S2C_QUEST_11_99_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_100_16_NTC = new PacketId(11, 100, 16, "S2C_QUEST_11_100_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC = new PacketId(11, 101, 16, "S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_101_16_NTC"); // - public static readonly PacketId S2C_QUEST_11_102_16_NTC = new PacketId(11, 102, 16, "S2C_QUEST_11_102_16_NTC", ServerType.Game, PacketSource.Server); // Remaining time extended by x seconds + public static readonly PacketId S2C_QUEST_PLAY_ADD_TIMER_NTC = new PacketId(11, 102, 16, "S2C_QUEST_PLAY_ADD_TIMER_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_102_16_NTC"); // Remaining time extended by x seconds (S2C_PLAY_ADD_TIMER_NOTICE?) public static readonly PacketId S2C_QUEST_11_103_16_NTC = new PacketId(11, 103, 16, "S2C_QUEST_11_103_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_104_16_NTC = new PacketId(11, 104, 16, "S2C_QUEST_11_104_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_QUEST_11_105_16_NTC = new PacketId(11, 105, 16, "S2C_QUEST_11_105_16_NTC", ServerType.Game, PacketSource.Server); // Time is up + public static readonly PacketId S2C_QUEST_PLAY_TIMEUP_NTC = new PacketId(11, 105, 16, "S2C_QUEST_PLAY_TIMEUP_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_105_16_NTC"); // Time is up (S2C_PLAY_TIMEUP_NOTICE) public static readonly PacketId S2C_QUEST_11_106_16_NTC = new PacketId(11, 106, 16, "S2C_QUEST_11_106_16_NTC", ServerType.Game, PacketSource.Server); // public static readonly PacketId S2C_QUEST_11_107_16_NTC = new PacketId(11, 107, 16, "S2C_QUEST_11_107_16_NTC", ServerType.Game, PacketSource.Server); // The request to end the mission was successful public static readonly PacketId S2C_QUEST_11_108_16_NTC = new PacketId(11, 108, 16, "S2C_QUEST_11_108_16_NTC", ServerType.Game, PacketSource.Server); // The request to end the mission was successful public static readonly PacketId S2C_QUEST_11_109_16_NTC = new PacketId(11, 109, 16, "S2C_QUEST_11_109_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_QUEST_11_110_16_NTC = new PacketId(11, 110, 16, "S2C_QUEST_11_110_16_NTC", ServerType.Game, PacketSource.Server); // + public static readonly PacketId S2C_QUEST_11_110_16_NTC = new PacketId(11, 110, 16, "S2C_QUEST_11_110_16_NTC", ServerType.Game, PacketSource.Server); // (S2C_FORT_DEFENSE_PLAY_START_NOTICE) public static readonly PacketId S2C_QUEST_11_111_16_NTC = new PacketId(11, 111, 16, "S2C_QUEST_11_111_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_112_16_NTC = new PacketId(11, 112, 16, "S2C_QUEST_11_112_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_113_16_NTC = new PacketId(11, 113, 16, "S2C_QUEST_11_113_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_QUEST_11_114_16_NTC = new PacketId(11, 114, 16, "S2C_QUEST_11_114_16_NTC", ServerType.Game, PacketSource.Server); // + public static readonly PacketId S2C_QUEST_RAID_BOSS_PLAY_START_NTC = new PacketId(11, 114, 16, "S2C_QUEST_RAID_BOSS_PLAY_START_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_114_16_NTC"); // (CPacket_S2C_RAID_BOSS_PLAY_START_NOTICE) public static readonly PacketId S2C_QUEST_11_115_16_NTC = new PacketId(11, 115, 16, "S2C_QUEST_11_115_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_116_16_NTC = new PacketId(11, 116, 16, "S2C_QUEST_11_116_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_117_16_NTC = new PacketId(11, 117, 16, "S2C_QUEST_11_117_16_NTC", ServerType.Game, PacketSource.Server); @@ -798,7 +798,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_RES = new PacketId(11, 123, 2, "S2C_QUEST_GET_ADVENTURE_GUIDE_QUEST_NTC_RES", ServerType.Game, PacketSource.Server); // 冒険ガイドクエスト通知の取得に public static readonly PacketId C2S_QUEST_SET_NAVIGATION_QUEST_REQ = new PacketId(11, 124, 1, "C2S_QUEST_SET_NAVIGATION_QUEST_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_SET_NAVIGATION_QUEST_RES = new PacketId(11, 124, 2, "S2C_QUEST_SET_NAVIGATION_QUEST_RES", ServerType.Game, PacketSource.Server); // ナビゲーションクエストセットに - public static readonly PacketId S2C_QUEST_11_124_16_NTC = new PacketId(11, 124, 16, "S2C_QUEST_11_124_16_NTC", ServerType.Game, PacketSource.Server); // + public static readonly PacketId S2C_QUEST_11_124_16_NTC = new PacketId(11, 124, 16, "S2C_QUEST_11_124_16_NTC", ServerType.Game, PacketSource.Server); // (Looks like S2CQuestJoinLobbyQuestInfoNtc) public static readonly PacketId C2S_QUEST_CANCEL_NAVIGATION_QUEST_REQ = new PacketId(11, 125, 1, "C2S_QUEST_CANCEL_NAVIGATION_QUEST_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_CANCEL_NAVIGATION_QUEST_RES = new PacketId(11, 125, 2, "S2C_QUEST_CANCEL_NAVIGATION_QUEST_RES", ServerType.Game, PacketSource.Server); // ナビゲーションクエストのキャンセルに public static readonly PacketId S2C_QUEST_11_125_16_NTC = new PacketId(11, 125, 16, "S2C_QUEST_11_125_16_NTC", ServerType.Game, PacketSource.Server); @@ -2001,11 +2001,13 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_63_1_16_NTC = new PacketId(63, 1, 16, "S2C_63_1_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_63_2_16_NTC = new PacketId(63, 2, 16, "S2C_63_2_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_63_3_16_NTC = new PacketId(63, 3, 16, "S2C_63_3_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_63_5_16_NTC = new PacketId(63, 5, 16, "S2C_63_5_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_63_6_16_NTC = new PacketId(63, 6, 16, "S2C_63_6_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_63_7_16_NTC = new PacketId(63, 7, 16, "S2C_63_7_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_63_10_16_NTC = new PacketId(63, 10, 16, "S2C_63_10_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_63_11_16_NTC = new PacketId(63, 11, 16, "S2C_63_11_16_NTC", ServerType.Game, PacketSource.Server); -// Group: 64 - (MANDRAGORA) + // Group: 64 - (MANDRAGORA) public static readonly PacketId C2S_MANDRAGORA_GET_MY_MANDRAGORA_REQ = new PacketId(64, 0, 1, "C2S_MANDRAGORA_GET_MY_MANDRAGORA_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_MANDRAGORA_GET_MY_MANDRAGORA_RES = new PacketId(64, 0, 2, "S2C_MANDRAGORA_GET_MY_MANDRAGORA_RES", ServerType.Game, PacketSource.Server); // 所持マンドラゴラ取得 public static readonly PacketId C2S_MANDRAGORA_CREATE_MY_MANDRAGORA_REQ = new PacketId(64, 1, 1, "C2S_MANDRAGORA_CREATE_MY_MANDRAGORA_REQ", ServerType.Game, PacketSource.Client); @@ -2477,7 +2479,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_ITEM_EXTEND_ITEM_SLOT_NTC); AddPacketIdEntry(packetIds, S2C_ITEM_EXTEND_EQUIP_SLOT_NTC); AddPacketIdEntry(packetIds, S2C_ITEM_UPDATE_CHARACTER_ITEM_NTC); - AddPacketIdEntry(packetIds, S2C_ITEM_10_13_16_NTC); + AddPacketIdEntry(packetIds, S2C_SWITCH_STORAGE_NTC); AddPacketIdEntry(packetIds, S2C_ITEM_ACHIEVEMENT_REWARD_RECEIVE_NTC); AddPacketIdEntry(packetIds, C2S_ITEM_GET_PAY_COST_REQ); AddPacketIdEntry(packetIds, S2C_ITEM_GET_PAY_COST_RES); @@ -2599,7 +2601,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_START_TIMER_NTC); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_INTERRUPT_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_INTERRUPT_RES); - AddPacketIdEntry(packetIds, S2C_QUEST_11_43_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_INTERRUPT_NTC); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_INTERRUPT_ANSWER_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_INTERRUPT_ANSWER_RES); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_END_REQ); @@ -2698,10 +2700,10 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_11_99_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_100_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC); - AddPacketIdEntry(packetIds, S2C_QUEST_11_102_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ADD_TIMER_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_103_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_104_16_NTC); - AddPacketIdEntry(packetIds, S2C_QUEST_11_105_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_TIMEUP_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_106_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_107_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_108_16_NTC); @@ -2710,7 +2712,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_11_111_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_112_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_113_16_NTC); - AddPacketIdEntry(packetIds, S2C_QUEST_11_114_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_RAID_BOSS_PLAY_START_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_115_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_116_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_117_16_NTC); @@ -3926,9 +3928,11 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_63_1_16_NTC); AddPacketIdEntry(packetIds, S2C_63_2_16_NTC); AddPacketIdEntry(packetIds, S2C_63_3_16_NTC); + AddPacketIdEntry(packetIds, S2C_63_5_16_NTC); AddPacketIdEntry(packetIds, S2C_63_6_16_NTC); AddPacketIdEntry(packetIds, S2C_63_7_16_NTC); AddPacketIdEntry(packetIds, S2C_63_10_16_NTC); + AddPacketIdEntry(packetIds, S2C_63_11_16_NTC); // Group: 64 - (MANDRAGORA) AddPacketIdEntry(packetIds, C2S_MANDRAGORA_GET_MY_MANDRAGORA_REQ); From b10825d22231cd42e9cc4c4c97b4afe193bb856e Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 13 Sep 2024 19:03:31 -0400 Subject: [PATCH 075/116] fix: Fix issue with spontaneous world quests - Fixed an issue where world quests completion was not being tracked properly. - Fixed an issue where the area quest fetch was not cleaning up state properly. - Added some defensive checks to prevent exceptions from being thrown. --- .../Handler/QuestGetSetQuestListHandler.cs | 4 +-- Arrowgene.Ddon.GameServer/Party/PartyGroup.cs | 2 +- .../Party/PartyQuestState.cs | 32 ++++++++++++++----- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs index 443517483..6e18781ba 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs @@ -65,9 +65,9 @@ public override void Handle(GameClient client, StructurePacket ActiveVariantQuests { get; set; } // For the purposes of each party quest state knowing the possible variant quests private HashSet VariantQuests { get; set; } - private List CompletedWorldQuests { get; set; } + public HashSet CompletedWorldQuests { get; set; } public PartyQuestState() { ActiveQuests = new Dictionary(); QuestLookupTable = new Dictionary>(); - CompletedWorldQuests = new List(); + CompletedWorldQuests = new HashSet(); ActiveVariantQuests = new Dictionary(); VariantQuests = QuestManager.GetAllVariantQuestIds(); } @@ -196,6 +196,12 @@ public List GetInstancedEnemies(Quest quest, StageId stageId, us { lock (ActiveQuests) { + if (!ActiveQuests.ContainsKey(quest.QuestId)) + { + Logger.Error($"No state for '{quest.QuestId}' present. Returning empty enemy list."); + return new List(); + } + if (!ActiveQuests[quest.QuestId].QuestEnemies.ContainsKey(stageId)) { return new List(); @@ -324,7 +330,7 @@ public void RemoveInactiveWorldQuests() foreach (var questId in questsToRemove) { - ActiveQuests.Remove(questId); + RemoveQuest(questId); } } } @@ -340,6 +346,11 @@ public void CompleteQuest(QuestId questId) var quest = GetQuest(questId); RemoveQuest(questId); + if (QuestManager.IsWorldQuest(questId)) + { + CompletedWorldQuests.Add(questId); + } + if (quest.NextQuestId != 0) { AddNewQuest(quest.NextQuestId, 0, false); @@ -483,11 +494,6 @@ private void RerollUnfoundAltQuests() } } - public void ResetInstanceQuestState() - { - RerollUnfoundAltQuests(); - } - public bool UpdatePartyQuestProgress(DdonGameServer server, PartyGroup party, QuestId questId) { Quest quest = GetQuest(questId); @@ -685,5 +691,15 @@ public void UpdatePriorityQuestList(DdonGameServer server, GameClient requesting } party.SendToAll(prioNtc); } + + public void ResetInstance() + { + lock (ActiveQuests) + { + CompletedWorldQuests.Clear(); + + RerollUnfoundAltQuests(); + } + } } } From bf47d37024022a2475168548b378f9994b239b6c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 14 Sep 2024 01:11:31 -0400 Subject: [PATCH 076/116] Add new EXM - Implemented the quest "The Dragon Awakened". - Fixed issues related to random loot rewards. --- .../Characters/ExmManager.cs | 9 +- .../Handler/InstanceEnemyKillHandler.cs | 51 +- .../Handler/QuestGetRewardBoxItemHandler.cs | 3 +- .../Handler/QuestPlayEndHandler.cs | 7 +- .../Quests/GenericQuest.cs | 1 - Arrowgene.Ddon.GameServer/Quests/Quest.cs | 30 +- .../AssetReader/QuestAssetDeserializer.cs | 33 +- .../Files/Assets/EnemySpawn.json | 84 --- .../Files/Assets/quests/q00000006.json | 40 +- .../Files/Assets/quests/q50300010.json | 484 ++++++++++++++++++ .../Model/Quest/QuestLootDistribution.cs | 14 + .../Model/Quest/QuestMissionParams.cs | 3 +- .../Model/Quest/QuestRewardType.cs | 2 +- .../Model/Quest/QuestRewards.cs | 2 + 14 files changed, 613 insertions(+), 150 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json create mode 100644 Arrowgene.Ddon.Shared/Model/Quest/QuestLootDistribution.cs diff --git a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs index 3505062b1..00ee11d82 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs @@ -324,7 +324,7 @@ public ulong ExtendTimer(ulong contentId, uint amountInSeconds) } } - public void CancelTimer(ulong contentId) + public (TimeSpan Elapsed, TimeSpan MaximumDuration) CancelTimer(ulong contentId) { lock (_ContentData) { @@ -332,8 +332,15 @@ public void CancelTimer(ulong contentId) { Logger.Info($"Canceling timer for ContentId={contentId}"); _ContentTimers[contentId].Timer.Dispose(); + + var timerState = _ContentTimers[contentId]; + + TimeSpan elapsed = DateTime.Now.Subtract(timerState.TimeStart); + var results = (elapsed, timerState.Duration); _ContentTimers.Remove(contentId); + return results; } + return (TimeSpan.Zero, TimeSpan.Zero); } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 1dd2fec5b..1904cd9c5 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -38,7 +38,7 @@ public override void Handle(GameClient client, StructurePacket instancedGatheringItems = IsQuestControlled ? - partyMemberClient.InstanceQuestDropManager.GenerateEnemyLoot(quest, enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId) : - partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, (int)packet.Structure.SetId); - - // If the roll was unlucky, there is a chance that no bag will show. - if (instancedGatheringItems.Count > 0) - { - partyMemberClient.Send(new S2CInstancePopDropItemNtc() - { - LayoutId = packet.Structure.LayoutId, - SetId = packet.Structure.SetId, - MdlType = enemyKilled.DropsTable.MdlType, - PosX = packet.Structure.DropPosX, - PosY = packet.Structure.DropPosY, - PosZ = packet.Structure.DropPosZ - }); - } - } - List group = client.Party.InstanceEnemyManager.GetInstancedEnemies(stageId); bool groupDestroyed = group.Where(x => x.IsRequired).All(x => x.IsKilled); if (groupDestroyed) @@ -139,6 +117,33 @@ public override void Handle(GameClient client, StructurePacket instancedGatheringItems = IsQuestControlled ? + partyMemberClient.InstanceQuestDropManager.GenerateEnemyLoot(quest, enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId) : + partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, (int)packet.Structure.SetId); + + // If the roll was unlucky, there is a chance that no bag will show. + if (instancedGatheringItems.Count > 0) + { + partyMemberClient.Send(new S2CInstancePopDropItemNtc() + { + LayoutId = packet.Structure.LayoutId, + SetId = packet.Structure.SetId, + MdlType = enemyKilled.DropsTable.MdlType, + PosX = packet.Structure.DropPosX, + PosY = packet.Structure.DropPosY, + PosZ = packet.Structure.DropPosZ + }); + } + } + foreach (PartyMember member in client.Party.Members) { if (member.JoinState != JoinState.On) continue; // Only fully joined members get rewards. diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxItemHandler.cs index f5c167fa1..4bb4d15d3 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxItemHandler.cs @@ -47,7 +47,6 @@ public override S2CQuestGetRewardBoxItemRes Handle(GameClient client, C2SQuestGe UpdateType = 0 }; - foreach (var boxReward in packet.GetRewardBoxItemList) { var reward = rewards.Single(x => x.UID == boxReward.UID); @@ -57,7 +56,7 @@ public override S2CQuestGetRewardBoxItemRes Handle(GameClient client, C2SQuestGe var result = Server.WalletManager.AddToWallet(client.Character, walletType, amount); updateCharacterItemNtc.UpdateWalletList.Add(result); } - else + else if (reward.Num > 0) { var result = Server.ItemManager.AddItem(Server, client.Character, false, reward.ItemId, reward.Num); updateCharacterItemNtc.UpdateItemList.AddRange(result); diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs index e2c568879..d31fada78 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs @@ -2,8 +2,10 @@ using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using System.Collections.Generic; namespace Arrowgene.Ddon.GameServer.Handler { @@ -30,12 +32,13 @@ public QuestPlayEndHandler(DdonGameServer server) : base(server) public override S2CQuestPlayEndRes Handle(GameClient client, C2SQuestPlayEndReq request) { var contentId = client.Party.ContentId; - Server.ExmManager.CancelTimer(contentId); + var timeData = Server.ExmManager.CancelTimer(contentId); var quest = Server.ExmManager.GetQuestForContent(contentId); - + var ntc = new S2CQuestPlayEndNtc(); ntc.ContentsPlayEnd.RewardItemDetailList = quest.ToCDataTimeGainQuestList(0).RewardItemDetailList; + ntc.ContentsPlayEnd.PlayTimeMillSec = (uint) timeData.Elapsed.Milliseconds; client.Party.SendToAll(ntc); client.Party.ContentInProgress = false; diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index 0f1327354..a1d4edebb 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -35,7 +35,6 @@ public static GenericQuest FromAsset(QuestAssetData questAsset) quest.StageId = questAsset.StageId; quest.MissionParams = questAsset.MissionParams; - foreach (var pointReward in questAsset.PointRewards) { quest.ExpRewards.Add(new CDataQuestExp() diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 4b7e0b68c..cfd92238f 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -381,17 +381,35 @@ public virtual CDataTimeGainQuestList ToCDataTimeGainQuestList(uint step) result.Restrictions.Unk5List.Add(new CDataCommonU8() { Value = 2 }); #endif + HashSet items = new HashSet(); // Rewards for EXM seem to show up independently foreach (var reward in result.Param.FixedRewardItemList) { - for (var i = 0; i < reward.Num; i++) + if (MissionParams.LootDistribution == QuestLootDistribution.TimeBased) { - result.RewardItemDetailList.Add(new CDataRewardItemDetail() + if (!items.Contains(reward.ItemId)) { - ItemId = reward.ItemId, - Num = 1, - Type = 12 - }); + // Show 1 of each item as exact value is unknown + result.RewardItemDetailList.Add(new CDataRewardItemDetail() + { + ItemId = reward.ItemId, + Num = 1, + Type = 12 + }); + items.Add(reward.ItemId); + } + } + else + { + for (var i = 0; i < reward.Num; i++) + { + result.RewardItemDetailList.Add(new CDataRewardItemDetail() + { + ItemId = reward.ItemId, + Num = 1, + Type = 12 + }); + } } } diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 54c9854bd..cb69806ea 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -255,6 +255,7 @@ private void ParseRewards(QuestAssetData assetData, JsonElement quest) case "fixed": case "random": case "select": + case "TimeBased": if (!Enum.TryParse(reward.GetProperty("type").GetString(), true, out QuestRewardType questRewardType)) { continue; @@ -297,17 +298,14 @@ private void ParseRewards(QuestAssetData assetData, JsonElement quest) } else if (questRewardType == QuestRewardType.Fixed) { - var item = reward.GetProperty("loot_pool").EnumerateArray().ToList()[0]; - rewardItem = new QuestFixedRewardItem() + rewardItem = new QuestFixedRewardItem(); + foreach (var item in reward.GetProperty("loot_pool").EnumerateArray()) { - LootPool = new List() + rewardItem.LootPool.Add(new FixedLootPoolItem() { - new FixedLootPoolItem() - { - ItemId = item.GetProperty("item_id").GetUInt32(), - Num = item.GetProperty("num").GetUInt16(), - } - } + ItemId = item.GetProperty("item_id").GetUInt32(), + Num = item.GetProperty("num").GetUInt16(), + }); }; } else @@ -868,6 +866,23 @@ private bool ParseMissionParams(QuestAssetData assetData, JsonElement jMissionPa assetData.MissionParams.SortieMinimum = jMinimumMembers.GetUInt32(); } + assetData.MissionParams.SortieMaximum = 4; + if (jMissionParams.TryGetProperty("minimum_members", out JsonElement jMaximumMembers)) + { + assetData.MissionParams.SortieMaximum = jMaximumMembers.GetUInt32(); + } + + assetData.MissionParams.LootDistribution = QuestLootDistribution.Normal; + if (jMissionParams.TryGetProperty("loot_distribution", out JsonElement jLootDistribution)) + { + if (!Enum.TryParse(jLootDistribution.GetString(), true, out QuestLootDistribution lootDistribution)) + { + Logger.Error("Invalid 'loot_distribution' from ExtremeMission config."); + return false; + } + assetData.MissionParams.LootDistribution = lootDistribution; + } + assetData.MissionParams.PlaytimeInSeconds = 1200; if (jMissionParams.TryGetProperty("playtime", out JsonElement jPlaytime)) { diff --git a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json index eb38c8b3e..3737869ff 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json @@ -52902,34 +52902,6 @@ 41, "00:00,23:59" ], - [ - 380, - 0, - 3, - 0, - "0x080400", - 2298, - 0, - 100, - 75, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - true, - true, - false, - false, - 0, - 0, - 607000, - 409, - "00:00,23:59" - ], [ 455, 0, @@ -229554,34 +229526,6 @@ 7, "00:00,23:59" ], - [ - 381, - 0, - 2, - 0, - "0x010100", - 2298, - 0, - 100, - 10, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - 0, - 136, - 7, - "00:00,23:59" - ], [ 442, 0, @@ -240586,34 +240530,6 @@ -1, "00:00,23:59" ], - [ - 620, - 0, - 9, - 0, - "0x015603", - 2659, - 0, - 100, - 48, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - true, - true, - false, - false, - 0, - 0, - 10550, - 341, - "00:00,23:59" - ], [ 616, 0, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000006.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000006.json index b89c2f2b4..bc3b83c2a 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000006.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000006.json @@ -50,7 +50,7 @@ "id": 67, "group_id": 2 }, - "placement_type": "Manual", + "placement_type": "Manual", "enemies": [ { "enemy_id": "0x015040", @@ -58,39 +58,39 @@ "exp": 4660, "named_enemy_params_id": 141, "is_boss": true, - "index": 6 + "index": 6 }, { "enemy_id": "0x010200", "level": 18, - "named_enemy_params_id": 142, - "index": 3, + "named_enemy_params_id": 142, + "index": 3, "exp": 64, - "index": 4 + "index": 4 }, - { + { "enemy_id": "0x010200", "level": 18, - "named_enemy_params_id": 142, - "index": 3, + "named_enemy_params_id": 142, + "index": 3, "exp": 64, - "index": 10 + "index": 10 }, - { + { "enemy_id": "0x010200", "level": 18, - "named_enemy_params_id": 142, - "index": 3, + "named_enemy_params_id": 142, + "index": 3, "exp": 64, - "index": 11 + "index": 11 }, - { + { "enemy_id": "0x010200", "level": 18, - "named_enemy_params_id": 142, - "index": 3, + "named_enemy_params_id": 142, + "index": 3, "exp": 64, - "index": 12 + "index": 12 } ] } @@ -132,7 +132,7 @@ "stage_id": { "id": 1 }, - "flags": [ + "flags": [ {"type": "QstLayout", "action": "Clear", "value": 977, "comment": "Spawns Gerd and the White Knights outside Glowworm Cave"} ], "event_id": 20 @@ -155,7 +155,7 @@ "stage_id": { "id": 67 }, - "jump_stage_id": { + "jump_stage_id": { "id": 67 }, "start_pos_no": 2, @@ -176,7 +176,7 @@ }, "event_id": 10 }, - { + { "type": "PlayEvent", "stage_id": { "id": 67 diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json new file mode 100644 index 000000000..2cffb05c7 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json @@ -0,0 +1,484 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "The Dragon Awakened (Grand Mission)", + "quest_id": 50300010, + "base_level": 80, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 9, + "minimum_members": 1, + "maximum_members": 8, + "playtime": 900, + "solo_only": true, + "max_pawns": 3, + "loot_distribution": "TimeBased", + "phase_groups": [] + }, + "order_conditions": [ + ], + "rewards": [ + { + "type": "random", + "loot_pool": [ + { + "item_id": 15998, + "num": 1, + "chance": 0.35 + }, + { + "item_id": 15998, + "num": 2, + "chance": 0.25 + }, + { + "item_id": 15998, + "num": 4, + "chance": 0.20 + }, + { + "item_id": 15998, + "num": 12, + "chance": 0.15 + }, + { + "item_id": 15998, + "num": 16, + "chance": 0.10 + }, + { + "item_id": 15998, + "num": 20, + "chance": 0.05 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 15999, + "num": 0, + "chance": 0.35 + }, + { + "item_id": 15999, + "num": 1, + "chance": 0.20 + }, + { + "item_id": 15999, + "num": 2, + "chance": 0.25 + }, + { + "item_id": 15999, + "num": 4, + "chance": 0.20 + }, + { + "item_id": 15999, + "num": 8, + "chance": 0.15 + }, + { + "item_id": 15999, + "num": 10, + "chance": 0.05 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 16000, + "num": 0, + "chance": 0.60 + }, + { + "item_id": 16000, + "num": 1, + "chance": 0.20 + }, + { + "item_id": 16000, + "num": 2, + "chance": 0.15 + }, + { + "item_id": 16000, + "num": 3, + "chance": 0.05 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 381, + "group_id": 0 + }, + "enemies": [ + { + "comment": "Spirit Dragon Willmia", + "enemy_id": "0x080400", + "level": 80, + "exp": 0, + "is_boss": true, + "named_enemy_params_id": 1707 + } + ] + }, + { + "comment": "Adds (Towers)", + "stage_id": { + "id": 381, + "group_id": 2 + }, + "starting_index": 4, + "enemies": [ + { + "comment": "Dragon Crystal of Evil Sealing", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1710, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Evil Sealing", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1710, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + } + ] + }, + { + "comment": "Adds (Towers)", + "stage_id": { + "id": 381, + "group_id": 2 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Dragon Crystal of Evil Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 11, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Evil Sealing", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 12, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1710, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 15, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sealing", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 16, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1710, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 17, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 18, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 19, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sealing", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 20, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1710, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "index": 21, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + } + ] + }, + { + "comment": "Adds (Gimick)", + "stage_id": { + "id": 381, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Memory of Sighs", + "enemy_id": "0x030105", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1701, + "repop_count": 50, + "repop_wait_second": 45 + }, + { + "comment": "Memory of Sighs", + "enemy_id": "0x030105", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1701, + "repop_count": 50, + "repop_wait_second": 45 + } + ] + }, + { + "comment": "Adds (Gimick)", + "stage_id": { + "id": 381, + "group_id": 1 + }, + "starting_index": 2, + "enemies": [ + { + "comment": "Memory of Sighs", + "enemy_id": "0x030105", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1701, + "repop_count": 50, + "repop_wait_second": 45 + }, + { + "comment": "Memory of Sighs", + "enemy_id": "0x030105", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1701, + "repop_count": 50, + "repop_wait_second": 45 + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 381 + } + }, + { + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 381 + }, + "event_id": 5 + } + ] + }, + { + "blocks": [ + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 80} + ] + }, + { + "comment": "Spawn Crystals", + "type": "SpawnGroup", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 70} + ], + "groups": [3] + }, + { + "type": "DestroyGroup", + "groups": [3] + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 40} + ] + }, + { + "comment": "Spawn Crystals", + "type": "SpawnGroup", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 30} + ], + "groups": [4] + }, + { + "type": "DestroyGroup", + "groups": [4] + } + ] + }, + { + "blocks": [ + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 55} + ] + }, + { + "comment": "Spawn Towers", + "type": "KillGroup", + "groups": [1] + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 20} + ] + }, + { + "comment": "Spawn Towers", + "type": "KillGroup", + "groups": [2] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestLootDistribution.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestLootDistribution.cs new file mode 100644 index 000000000..d66d08c19 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestLootDistribution.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Model.Quest +{ + public enum QuestLootDistribution + { + Normal, + TimeBased + } +} diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs index f82d4c264..17ceb21b4 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs @@ -16,14 +16,15 @@ public QuestMissionParams() } public uint SortieMinimum { get; set; } + public uint SortieMaximum { get; set; } public uint PlaytimeInSeconds { get; set; } public bool IsSolo { get; set; } public uint MaxPawns { get; set; } // public bool SupportPawnAllowed { get; set; } Is in symbol but doesn't seem to work? public bool ArmorAllowed { get; set; } public bool JewelryAllowed { get; set; } - public uint Group { get; set; } public List QuestPhaseGroupIdList { get; set; } + public QuestLootDistribution LootDistribution { get; set; } } } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestRewardType.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestRewardType.cs index 1f56a4a8b..9c21a4195 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestRewardType.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestRewardType.cs @@ -23,6 +23,6 @@ public enum QuestRewardType : uint FixedSecond = 12, FixedMemberFirst = 13, ProgressBonus = 14, - Num = 15 + Num = 15, } } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs index 928637ae2..e54db6719 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs @@ -118,6 +118,7 @@ public CDataRewardBoxItem AsCDataRewardBoxItem() var item = LootPool[ItemIndex]; return new CDataRewardBoxItem() { + UID = item.GetUID(), ItemId = item.ItemId, Num = item.Num, Type = (byte)RewardType @@ -129,6 +130,7 @@ public CDataRewardBoxItem AsCDataRewardBoxItem(int index) var item = LootPool[index]; return new CDataRewardBoxItem() { + UID = item.GetUID(), ItemId = item.ItemId, Num = item.Num, Type = (byte)RewardType From 2131389fb35521c33179d570f944f34619d3c237 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 13 Sep 2024 16:47:25 -0700 Subject: [PATCH 077/116] Suppress particularly spammy packets in the logs, relating to chat and binary message exchanges. --- Arrowgene.Ddon.Cli/Program.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Arrowgene.Ddon.Cli/Program.cs b/Arrowgene.Ddon.Cli/Program.cs index c87a2a4c6..eab02e89c 100644 --- a/Arrowgene.Ddon.Cli/Program.cs +++ b/Arrowgene.Ddon.Cli/Program.cs @@ -1,4 +1,4 @@ -/* +/* * This file is part of Arrowgene.Ddon.Cli * * Arrowgene.Ddon.Cli is a server implementation for the game "Dragons Dogma Online". @@ -47,13 +47,15 @@ internal class Program // A list of packet Ids to always ignore, regardless of setting private static HashSet IgnorePacketIds = new HashSet() { - new PacketId(3, 3, 16, ""), - new PacketId(6, 25, 16, ""), PacketId.C2S_CONNECTION_PING_REQ, PacketId.S2C_CONNECTION_PING_RES, PacketId.C2L_PING_REQ, PacketId.L2C_PING_RES, PacketId.S2C_LOBBY_LOBBY_DATA_MSG_NTC, + PacketId.C2S_LOBBY_LOBBY_DATA_MSG_REQ, + PacketId.S2C_LOBBY_LOBBY_CHAT_MSG_NTC, + PacketId.C2S_PARTY_SEND_BINARY_MSG_NTC, + PacketId.S2C_PARTY_RECV_BINARY_MSG_NTC, }; private static void Main(string[] args) From 186930fdd70862301fdb3a56a92ef62b1746a0c2 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 13 Sep 2024 16:48:35 -0700 Subject: [PATCH 078/116] Fix NPEs in LobbyLobbyJoinHandler, HubManager. --- Arrowgene.Ddon.Cli/Program.cs | 10 ++++++++++ Arrowgene.Ddon.GameServer/Characters/HubManager.cs | 4 ++-- .../Handler/LobbyLobbyJoinHandler.cs | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Arrowgene.Ddon.Cli/Program.cs b/Arrowgene.Ddon.Cli/Program.cs index eab02e89c..f21829086 100644 --- a/Arrowgene.Ddon.Cli/Program.cs +++ b/Arrowgene.Ddon.Cli/Program.cs @@ -49,13 +49,23 @@ internal class Program { PacketId.C2S_CONNECTION_PING_REQ, PacketId.S2C_CONNECTION_PING_RES, + PacketId.C2L_PING_REQ, PacketId.L2C_PING_RES, + PacketId.S2C_LOBBY_LOBBY_DATA_MSG_NTC, PacketId.C2S_LOBBY_LOBBY_DATA_MSG_REQ, PacketId.S2C_LOBBY_LOBBY_CHAT_MSG_NTC, + PacketId.C2S_PARTY_SEND_BINARY_MSG_NTC, PacketId.S2C_PARTY_RECV_BINARY_MSG_NTC, + + PacketId.S2C_CONTEXT_MASTER_CHANGE_NTC, + PacketId.C2S_CONTEXT_GET_SET_CONTEXT_REQ, + PacketId.S2C_CONTEXT_SET_CONTEXT_NTC, + PacketId.S2C_CONTEXT_SET_CONTEXT_BASE_NTC, + + PacketId.S2C_USER_LIST_JOIN_NTC }; private static void Main(string[] args) diff --git a/Arrowgene.Ddon.GameServer/Characters/HubManager.cs b/Arrowgene.Ddon.GameServer/Characters/HubManager.cs index 5135f763b..f05db05c9 100644 --- a/Arrowgene.Ddon.GameServer/Characters/HubManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/HubManager.cs @@ -75,7 +75,7 @@ public void UpdateLobbyContextOnStageChange(GameClient client, uint previousStag lock (HubMembers[previousStageId]) { HubMembers[previousStageId].Remove(client); - foreach (GameClient otherClient in Server.ClientLookup.GetAll()) + foreach (GameClient otherClient in Server.ClientLookup.GetAll().Where(x => x.Character != null)) { if (HubMembers[previousStageId].Contains(otherClient)) { @@ -97,7 +97,7 @@ public void UpdateLobbyContextOnStageChange(GameClient client, uint previousStag { lock (HubMembers[targetStageId]) { - foreach (GameClient otherClient in HubMembers[targetStageId]) + foreach (GameClient otherClient in HubMembers[targetStageId].Where(x => x.Character != null)) { uint otherId = otherClient.Character.CharacterId; if (!otherClient.Character.LastSeenLobby.TryGetValue(id, out var lastStage) || lastStage != targetStageId) diff --git a/Arrowgene.Ddon.GameServer/Handler/LobbyLobbyJoinHandler.cs b/Arrowgene.Ddon.GameServer/Handler/LobbyLobbyJoinHandler.cs index 88fa5b8a8..589c3897f 100644 --- a/Arrowgene.Ddon.GameServer/Handler/LobbyLobbyJoinHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/LobbyLobbyJoinHandler.cs @@ -28,7 +28,7 @@ public override S2CLobbyJoinRes Handle(GameClient client, C2SLobbyJoinReq reques new List(); foreach (GameClient otherClient in Server.ClientLookup.GetAll()) { - if (otherClient != client) + if (otherClient != client && otherClient.Character != null) { alreadyPresentUsersNtc.UserList.Add ( From d97fd4ee2b506a21d63fd84aef659d7ed6bc3ca1 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 13 Sep 2024 20:27:51 -0700 Subject: [PATCH 079/116] Clean up some unnecessary logger statements. --- .../Sql/Core/DdonSqlDbCommunicationShortcut.cs | 2 -- Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs | 2 -- .../Handler/ContextGetSetContextHandler.cs | 5 +---- .../Handler/ContextSetContextHandler.cs | 2 +- Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs | 2 -- Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs | 2 -- 6 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs index ad15d34c8..ee951321d 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs @@ -54,10 +54,8 @@ public bool ReplaceCommunicationShortcut(uint characterId, CDataCommunicationSho TCon connection = (TCon)(connectionIn ?? OpenNewConnection()); try { - Logger.Debug("Inserting communication shortcut."); if (!InsertIfNotExistsCommunicationShortcut((TCon)connection, characterId, communicationShortcut)) { - Logger.Debug("Communication shortcut already exists, replacing."); return UpdateCommunicationShortcut((TCon)connection, characterId, communicationShortcut.PageNo, communicationShortcut.ButtonNo, communicationShortcut); } return true; diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs index 15ca92d2d..7e69b2c00 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs @@ -54,10 +54,8 @@ public bool ReplaceShortcut(uint characterId, CDataShortCut shortcut, DbConnecti TCon connection = (TCon)(connectionIn ?? OpenNewConnection()); try { - Logger.Debug("Inserting shortcut."); if (!InsertIfNotExistsShortcut((TCon)connection, characterId, shortcut)) { - Logger.Debug("Shortcut already exists, replacing."); return UpdateShortcut((TCon)connection, characterId, shortcut.PageNo, shortcut.ButtonNo, shortcut); } return true; diff --git a/Arrowgene.Ddon.GameServer/Handler/ContextGetSetContextHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ContextGetSetContextHandler.cs index cad82bc62..dc452c91b 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ContextGetSetContextHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ContextGetSetContextHandler.cs @@ -38,9 +38,6 @@ public override void Handle(GameClient client, StructurePacket context = ContextManager.GetContext(client.Party, baseContext.UniqueId); - int originalBaseIndex = baseContext.MasterIndex; - int originalAdditionalContext = (context != null) ? context.Item2.MasterIndex : -99; - int clientIndex = Math.Max(client.Party.ClientIndex(client), 0); baseContext.MasterIndex = clientIndex; //Likely hacky. @@ -64,7 +61,7 @@ public override void Handle(GameClient client, StructurePacket packet) { - Logger.Info($"Warping to {packet.Structure.WarpPointId}"); - uint price = packet.Structure.Price; // TODO: Don't trust packet.Structure.Price and check its price server side CDataWalletPoint walletPoint = client.Character.WalletPointList.Where(wp => wp.Type == WalletType.RiftPoints).Single(); From b9730e6072ba8d0f89fce93ac74ffe64a4d1743d Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 14 Sep 2024 16:08:18 -0700 Subject: [PATCH 080/116] Fix missing packet entry for C2S_INSTANCE_13_46_16_NTC and C2S_INSTANCE_13_47_16_NTC --- Arrowgene.Ddon.Shared/Network/PacketId.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Arrowgene.Ddon.Shared/Network/PacketId.cs b/Arrowgene.Ddon.Shared/Network/PacketId.cs index d71b21444..1ab4ec614 100644 --- a/Arrowgene.Ddon.Shared/Network/PacketId.cs +++ b/Arrowgene.Ddon.Shared/Network/PacketId.cs @@ -2831,6 +2831,8 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_INSTANCE_GET_EX_OM_INFO_RES); AddPacketIdEntry(packetIds, C2S_INSTANCE_CHARACTER_START_BAD_STATUS_NTC); AddPacketIdEntry(packetIds, C2S_INSTANCE_CHARACTER_END_BAD_STATUS_NTC); + AddPacketIdEntry(packetIds, C2S_INSTANCE_13_46_16_NTC); + AddPacketIdEntry(packetIds, C2S_INSTANCE_13_47_16_NTC); // Group: 14 - (WARP) AddPacketIdEntry(packetIds, C2S_WARP_RELEASE_WARP_POINT_REQ); From e72ac1fbf9caffb94b98c32f92bf9c55e2b07667 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 14 Sep 2024 20:35:45 -0700 Subject: [PATCH 081/116] Maybe fix phantom loot bags. --- Arrowgene.Ddon.Cli/Program.cs | 1 + Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.Cli/Program.cs b/Arrowgene.Ddon.Cli/Program.cs index f21829086..640ea6372 100644 --- a/Arrowgene.Ddon.Cli/Program.cs +++ b/Arrowgene.Ddon.Cli/Program.cs @@ -62,6 +62,7 @@ internal class Program PacketId.S2C_CONTEXT_MASTER_CHANGE_NTC, PacketId.C2S_CONTEXT_GET_SET_CONTEXT_REQ, + PacketId.C2S_CONTEXT_SET_CONTEXT_NTC, PacketId.S2C_CONTEXT_SET_CONTEXT_NTC, PacketId.S2C_CONTEXT_SET_CONTEXT_BASE_NTC, diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 1904cd9c5..7426a09aa 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -130,7 +130,7 @@ public override void Handle(GameClient client, StructurePacket 0) + if (instancedGatheringItems.Where(x => x.ItemNum > 0).Any()) { partyMemberClient.Send(new S2CInstancePopDropItemNtc() { From 6431b5cacbb0e229a6f5d58d32720f1a5e7bb896 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 14 Sep 2024 22:20:00 -0400 Subject: [PATCH 082/116] feat: Enable Party Board System for EXM - Added support for using the recruitment board to search for EXM parties. - Added support for inviting players in the same party into the recruitment board. - Added support to cancel, recreate and disaband the recruitment board. --- .../Characters/BoardManager.cs | 374 ++++++++++++++++++ .../Characters/ContentManager.cs | 84 ++++ .../Characters/ExmManager.cs | 347 ---------------- .../Characters/QuestManager.cs | 16 + Arrowgene.Ddon.GameServer/DdonGameServer.cs | 12 +- .../EntryBoardEntryBoardItemCreateHandler.cs | 38 +- .../EntryBoardEntryBoardItemEntryHandler.cs | 75 ++++ ...tryBoardEntryBoardItemForceStartHandler.cs | 21 +- .../EntryBoardEntryBoardItemInfoHandler.cs | 25 ++ ...tryBoardEntryBoardItemInfoMyselfHandler.cs | 6 +- .../EntryBoardEntryBoardItemInviteHandler.cs | 40 ++ .../EntryBoardEntryBoardItemLeaveHandler.cs | 42 +- .../EntryBoardEntryBoardItemListHandler.cs | 105 +++++ .../EntryBoardEntryBoardItemReadyHandler.cs | 53 ++- .../EntryBoardEntryBoardListHandler.cs | 15 +- .../EntryBoardEntryItemInfoChangeHandler.cs | 21 +- .../Handler/EntryBoardEntryRecreateHandler.cs | 39 ++ .../Handler/PartyPartyJoinHandler.cs | 6 - .../Handler/PartyPartyLeaveHandler.cs | 23 +- .../QuestGetEndContentsRecruitListHandler.cs | 3 +- .../QuestGetQuestScheduleInfoHandler.cs | 37 ++ .../Handler/QuestPlayEndHandler.cs | 8 +- .../Handler/QuestPlayStartHandler.cs | 68 ---- .../Handler/QuestPlayStartTimerHandler.cs | 12 +- .../Handler/StageAreaChangeHandler.cs | 2 +- Arrowgene.Ddon.GameServer/Party/PartyGroup.cs | 7 +- .../Party/PartyManager.cs | 4 +- .../Quests/GenericQuest.cs | 2 +- .../AssetReader/QuestAssetDeserializer.cs | 19 +- .../Entity/EntitySerializer.cs | 16 + .../C2SEntryBoardEntryBoardItemEntryReq.cs | 40 ++ .../C2SEntryBoardEntryBoardItemInviteReq.cs | 35 ++ .../C2SEntryBoardEntryBoardItemListReq.cs | 43 ++ .../C2SEntryBoardEntryBoardItemRecreateReq.cs | 41 ++ .../C2SEntryBoardEntryBoardItemReq.cs | 36 ++ .../C2SEntryBoardEntryBoardListReq.cs | 8 +- .../C2SQuestGetQuestScheduleInfoReq.cs | 34 ++ .../S2CEntryBoardEntryBoardItemEntryRes.cs | 36 ++ ...2CEntryBoardEntryBoardItemInfoMyselfRes.cs | 6 +- .../S2CEntryBoardEntryBoardItemInviteNtc.cs | 45 +++ .../S2CEntryBoardEntryBoardItemInviteRes.cs | 31 ++ .../S2CEntryBoardEntryBoardItemListRes.cs | 39 ++ .../S2CEntryBoardEntryBoardItemRecreateNtc.cs | 38 ++ .../S2CEntryBoardEntryBoardItemRecreateRes.cs | 39 ++ .../S2CEntryBoardEntryBoardItemRes.cs | 39 ++ .../S2CQuestGetQuestScheduleInfoRes.cs | 31 ++ .../S2CQuestPlayEntryCancelNtc.cs | 33 ++ .../CDataEntryBoardItemSearchParameter.cs | 65 +++ .../Structure/CDataEntryBoardListParam.cs | 6 +- .../Files/Assets/quests/q20095010.json | 18 +- .../Files/Assets/quests/q50101020.json | 3 +- .../Files/Assets/quests/q50102020.json | 3 +- .../Files/Assets/quests/q50103020.json | 3 +- .../Files/Assets/quests/q50104000.json | 3 +- .../Files/Assets/quests/q50201000.json | 3 +- .../Files/Assets/quests/q50202000.json | 3 +- .../Files/Assets/quests/q50202003.json | 1 + .../Files/Assets/quests/q50203000.json | 3 +- .../Files/Assets/quests/q50204002.json | 3 +- .../Files/Assets/quests/q50300010.json | 195 ++++----- .../Model/Quest/QuestMissionParams.cs | 5 +- Arrowgene.Ddon.Shared/Network/PacketId.cs | 14 +- 62 files changed, 1760 insertions(+), 662 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Characters/BoardManager.cs create mode 100644 Arrowgene.Ddon.GameServer/Characters/ContentManager.cs delete mode 100644 Arrowgene.Ddon.GameServer/Characters/ExmManager.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemEntryHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInviteHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemListHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryRecreateHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestGetQuestScheduleInfoHandler.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemEntryReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInviteReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemListReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemRecreateReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetQuestScheduleInfoReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemEntryRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInviteNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInviteRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemListRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRecreateNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRecreateRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetQuestScheduleInfoRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryCancelNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardItemSearchParameter.cs diff --git a/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs b/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs new file mode 100644 index 000000000..d86c9a0b7 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs @@ -0,0 +1,374 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Arrowgene.Ddon.GameServer.Characters +{ + public class BoardManager + { + private DdonGameServer _Server; + + private Dictionary> _Boards; + private Dictionary _Groups; + private Dictionary _CharacterToEntryIdMap; + private uint EntryItemIdCounter; + private Stack _FreeEntryItemIds; + + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(BoardManager)); + + public class GroupData + { + public ulong BoardId { get; set; } + public CDataEntryItem EntryItem { get; set; } + public string Password { get; set; } + public uint PartyLeaderCharacterId { get; set; } + public HashSet Members { get; set; } + public Dictionary MemberReadyState { get; set; } + public bool IsInRecreate { get; set; } + public bool ContentInProgress { get; set; } + + public GroupData() + { + EntryItem = new CDataEntryItem(); + Password = string.Empty; + Members = new HashSet(); + MemberReadyState = new Dictionary(); + } + + public void ResetReadyState() + { + foreach (var characterId in Members) + { + MemberReadyState[characterId] = false; + } + } + } + + public BoardManager(DdonGameServer server) + { + _Server = server; + _Boards = new Dictionary>(); + _Groups = new Dictionary(); + _CharacterToEntryIdMap = new Dictionary(); + + // Entry ID Tracking + EntryItemIdCounter = 1; + _FreeEntryItemIds = new Stack(); + _FreeEntryItemIds.Push(EntryItemIdCounter); + } + + public GroupData CreateNewGroup(ulong boardId, CDataEntryItemParam createParam, string password, uint leaderCharacterId) + { + var data = new GroupData() + { + BoardId = boardId, + Password = password, + PartyLeaderCharacterId = leaderCharacterId, + }; + data.EntryItem.Param = createParam; + data.EntryItem.Id = GenerateEntryItemId(); + + // TODO: Quest Manager look up min/max + + lock (_Boards) + { + if (!_Boards.ContainsKey(boardId)) + { + _Boards[boardId] = new Dictionary(); + } + + if (_Boards[boardId].ContainsKey(data.EntryItem.Id)) + { + Logger.Error($"EntryItemId={data.EntryItem.Id} is already in use (unable to assign)"); + return null; + } + + if (_Groups.ContainsKey(data.EntryItem.Id)) + { + Logger.Error($"EntryItemId={data.EntryItem.Id} is already in use (cleaned up improperly?)"); + return null; + } + + _Boards[boardId][data.EntryItem.Id] = data; + _Groups[data.EntryItem.Id] = data; + + AddCharacterToGroup(data.EntryItem.Id, leaderCharacterId); + } + + return data; + } + + public GroupData CreateNewGroup(ulong boardId, CDataEntryItemParam createParam, string password, Character leaderCharacter) + { + return CreateNewGroup(boardId, createParam, password, leaderCharacter.CharacterId); + } + + public bool RemoveGroup(uint entryItemId) + { + lock (_Boards) + { + if (!_Groups.ContainsKey(entryItemId)) + { + return false; + } + + var data = _Groups[entryItemId]; + _Groups.Remove(entryItemId); + + if (_Boards.ContainsKey(data.BoardId) && _Boards[data.BoardId].ContainsKey(entryItemId)) + { + _Boards[data.BoardId].Remove(entryItemId); + } + + foreach (var characterId in data.Members) + { + if (_CharacterToEntryIdMap.ContainsKey(characterId)) + { + _CharacterToEntryIdMap.Remove(characterId); + } + } + + ReclaimEntryItemId(data.EntryItem.Id); + } + + return true; + } + + public GroupData RecreateGroup(uint characterId) + { + lock (_Boards) + { + uint entryItemId = GetEntryItemIdForCharacter(characterId); + if (entryItemId == 0) + { + return null; + } + + var data = GetGroupData(entryItemId); + data.ResetReadyState(); + data.IsInRecreate = true; + data.ContentInProgress = false; + + return data; + } + } + + public GroupData RecreateGroup(Character character) + { + return RecreateGroup(character.CharacterId); + } + + public List GetGroupsForBoardId(ulong boardId) + { + lock (_Boards) + { + if (!_Boards.ContainsKey(boardId)) + { + return new List(); + } + return _Boards[boardId].Values.ToList(); + } + } + + public bool AddCharacterToGroup(uint entryItemId, uint characterId) + { + lock (_Boards) + { + if (!_Groups.ContainsKey(entryItemId)) + { + return false; + } + + var data = _Groups[entryItemId]; + if (data.Members.Count == data.EntryItem.Param.MaxEntryNum) + { + return false; + } + + data.Members.Add(characterId); + + _CharacterToEntryIdMap[characterId] = entryItemId; + + data.MemberReadyState[characterId] = false; + + return true; + } + } + + public bool AddCharacterToGroup(uint entryItemId, Character character) + { + return AddCharacterToGroup(entryItemId, character.CharacterId); + } + + public bool RemoveCharacterFromGroup(uint characterId) + { + lock (_Boards) + { + uint entryItemId = GetEntryItemIdForCharacter(characterId); + if (entryItemId == 0) + { + // Character was not part of a board group + return false; + } + + var data = GetGroupData(entryItemId); + if (data == null) + { + return false; + } + + if (data.Members.Contains(characterId)) + { + data.Members.Remove(characterId); + } + + if (data.MemberReadyState.ContainsKey(characterId)) + { + data.MemberReadyState.Remove(characterId); + } + + if (_CharacterToEntryIdMap.ContainsKey(characterId)) + { + _CharacterToEntryIdMap.Remove(characterId); + } + + if (data.Members.Count == 0) + { + RemoveGroup(data.EntryItem.Id); + } + + return true; + } + } + + public bool RemoveCharacterFromGroup(Character character) + { + return RemoveCharacterFromGroup(character.CharacterId); + } + + public void SetGroupMemberReadyState(uint characterId, bool isReady) + { + lock (_Boards) + { + if (!_CharacterToEntryIdMap.ContainsKey(characterId)) + { + return; + } + + uint entryItemId = _CharacterToEntryIdMap[characterId]; + if (!_Groups.ContainsKey(entryItemId)) + { + return; + } + + var data = _Groups[entryItemId]; + data.MemberReadyState[characterId] = isReady; + } + } + + public void SetGroupMemberReadyState(Character character, bool isReady) + { + SetGroupMemberReadyState(character.CharacterId, isReady); + } + + public uint NumGroupMembersReady(uint entryItemId) + { + lock (_Boards) + { + var data = GetGroupData(entryItemId); + uint count = 0; + foreach (var (characterId, isReady) in data.MemberReadyState) + { + if (isReady) + { + count += 1; + } + } + return count; + } + } + + public bool AllGroupMembersReady(uint entryItemId) + { + lock (_Boards) + { + var data = GetGroupData(entryItemId); + uint numReady = NumGroupMembersReady(entryItemId); + return numReady == data.Members.Count; + } + } + + public GroupData GetGroupData(uint entryItemId) + { + lock (_Boards) + { + if (!_Groups.ContainsKey(entryItemId)) + { + return null; + } + return _Groups[entryItemId]; + } + } + + public GroupData GetGroupDataForCharacter(uint characterId) + { + lock (_Boards) + { + uint entryItemId = GetEntryItemIdForCharacter(characterId); + return GetGroupData(entryItemId); + } + } + + public GroupData GetGroupDataForCharacter(Character character) + { + return GetGroupDataForCharacter(character.CharacterId); + } + + public uint GetEntryItemIdForCharacter(uint characterId) + { + lock (_Boards) + { + if (!_CharacterToEntryIdMap.ContainsKey(characterId)) + { + return 0; + } + return _CharacterToEntryIdMap[characterId]; + } + } + + public uint GetEntryItemIdForCharacter(Character character) + { + return GetEntryItemIdForCharacter(character.CharacterId); + } + + private uint GenerateEntryItemId() + { + lock (_FreeEntryItemIds) + { + if (_FreeEntryItemIds.Count == 0) + { + EntryItemIdCounter = EntryItemIdCounter + 1; + _FreeEntryItemIds.Push(EntryItemIdCounter); + } + + var entryItemId = _FreeEntryItemIds.Pop(); + Logger.Info($"Allocating EntryId={entryItemId}"); + return entryItemId; + } + } + + private void ReclaimEntryItemId(uint entryItemId) + { + lock (_FreeEntryItemIds) + { + Logger.Info($"Reclaiming EntryId={entryItemId}"); + _FreeEntryItemIds.Push(entryItemId); + } + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs b/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs new file mode 100644 index 000000000..6a7802c34 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs @@ -0,0 +1,84 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +// using static Arrowgene.Ddon.GameServer.Characters.ExmManager; + +namespace Arrowgene.Ddon.GameServer.Characters +{ + public class ContentManager + { + private DdonGameServer _Server; + private Dictionary _ContentTimers; + + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ContentManager)); + + internal class TimerState + { + public DateTime TimeStart { get; set; } + public TimeSpan Duration { get; set; } + public Timer Timer { get; set; } + } + + public ContentManager(DdonGameServer server) + { + _Server = server; + _ContentTimers = new Dictionary(); + } + + public void StartTimer(uint partyId, GameClient client, uint playtimeInSeconds) + { + lock (_ContentTimers) + { + _ContentTimers[partyId] = new TimerState(); + + var timerState = _ContentTimers[partyId]; + timerState.Duration = TimeSpan.FromSeconds(playtimeInSeconds); + timerState.TimeStart = DateTime.Now; + timerState.Timer = new Timer(task => + { + TimeSpan alreadyElapsed = DateTime.Now.Subtract(timerState.TimeStart); + if (alreadyElapsed > timerState.Duration) + { + Logger.Info($"Timer expired for Id={partyId}"); + client.Party.SendToAll(new S2CQuestPlayTimeupNtc()); + CancelTimer(partyId); + } + }, null, 0, 1000); + Logger.Info($"Starting {playtimeInSeconds} second timer for PartyId={partyId}"); + } + } + + public ulong ExtendTimer(uint partyId, uint amountInSeconds) + { + lock (_ContentTimers) + { + Logger.Info($"Extending time by {amountInSeconds} seconds for PartyId={partyId}"); + _ContentTimers[partyId].Duration += TimeSpan.FromSeconds(amountInSeconds); + return (ulong) ((DateTimeOffset)(_ContentTimers[partyId].TimeStart + _ContentTimers[partyId].Duration)).ToUnixTimeSeconds(); + } + } + + public (TimeSpan Elapsed, TimeSpan MaximumDuration) CancelTimer(uint partyId) + { + lock (_ContentTimers) + { + if (_ContentTimers.ContainsKey(partyId)) + { + Logger.Info($"Canceling timer for PartyId={partyId}"); + _ContentTimers[partyId].Timer.Dispose(); + + var timerState = _ContentTimers[partyId]; + + TimeSpan elapsed = DateTime.Now.Subtract(timerState.TimeStart); + var results = (elapsed, timerState.Duration); + _ContentTimers.Remove(partyId); + return results; + } + } + return (TimeSpan.Zero, TimeSpan.Zero); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs deleted file mode 100644 index 00ee11d82..000000000 --- a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs +++ /dev/null @@ -1,347 +0,0 @@ -using Arrowgene.Ddon.GameServer.Handler; -using Arrowgene.Ddon.GameServer.Quests; -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.Model.Quest; -using Arrowgene.Logging; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.Metrics; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using YamlDotNet.Core.Tokens; -using static Arrowgene.Ddon.GameServer.Characters.ExmManager; - -namespace Arrowgene.Ddon.GameServer.Characters -{ - public class ExmManager - { - private DdonGameServer _Server; - - private Dictionary _ContentData; - private Dictionary _CharacterIdToContentId; - private Dictionary> _ContentIdToCharacterIds; - private Dictionary _ContentIdToQuest; - private Dictionary _ContentTimers; - private uint EntryItemIdCounter; - private Stack _FreeEntryItemIds; - - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ExmManager)); - - internal class TimerState - { - public DateTime TimeStart { get; set; } - public TimeSpan Duration { get; set; } - public Timer Timer { get; set; } - } - - public ExmManager(DdonGameServer server) - { - _Server = server; - _ContentData = new Dictionary(); - _CharacterIdToContentId = new Dictionary(); - _ContentIdToCharacterIds = new Dictionary>(); - _ContentIdToQuest = new Dictionary(); - _ContentTimers = new Dictionary(); - - // ID tracking - EntryItemIdCounter = 1; - _FreeEntryItemIds = new Stack(); - _FreeEntryItemIds.Push(EntryItemIdCounter); - } - - public bool HasContentId(ulong contentId) - { - lock (_ContentData) - { - return _ContentData.ContainsKey(contentId); - } - } - - public List GetListOfRecruitingContent() - { - lock (_ContentData) - { - return _ContentData.Values.ToList(); - } - } - - public uint NumGroupsRecruitingForContent(uint questId) - { - // TODO: Implement mechanism to search for this - return 0; - } - - public uint NumGroupsRecruitingForContent(QuestId questId) - { - return NumGroupsRecruitingForContent((uint)questId); - } - - public bool CreateGroupForContent(ulong id, CDataEntryItem entryItem) - { - lock (_ContentData) - { - if (_ContentData.ContainsKey(id)) - { - // Group is already registered - return false; - } - - _ContentData[id] = entryItem; - - return true; - } - } - - public CDataEntryItem GetEntryItemDataForContent(ulong id) - { - lock (_ContentData) - { - if (!_ContentData.ContainsKey(id)) - { - return null; - } - return _ContentData[id]; - } - } - - public CDataEntryItem GetEntryItemDataForCharacter(uint characterId) - { - lock (_ContentData) - { - ulong id = _CharacterIdToContentId[characterId]; - return GetEntryItemDataForContent(id); - } - } - - public CDataEntryItem GetEntryItemDataForCharacter(Character character) - { - return GetEntryItemDataForCharacter(character.CharacterId); - } - - public bool RemoveGroupForContent(ulong contentId) - { - lock (_ContentData) - { - if (!_ContentData.ContainsKey(contentId)) - { - return false; - } - - if (_ContentTimers.ContainsKey(contentId)) - { - CancelTimer(contentId); - } - - if (_ContentIdToCharacterIds.ContainsKey(contentId)) - { - foreach (var characterId in _ContentIdToCharacterIds[contentId]) - { - _CharacterIdToContentId.Remove(characterId); - } - _ContentIdToCharacterIds.Remove(contentId); - } - _ContentIdToQuest.Remove(contentId); - - var data = _ContentData[contentId]; - ReclaimEntryItemId(data.Id); - - return _ContentData.Remove(contentId); - } - } - - public ulong GetContentIdForCharacter(uint characterId) - { - lock (_ContentData) - { - if (!_CharacterIdToContentId.ContainsKey(characterId)) - { - return 0; - } - return _CharacterIdToContentId[characterId]; - } - } - - public ulong GetContentIdForCharacter(Character character) - { - return GetContentIdForCharacter(character.CharacterId); - } - - public List GetCharacterIdsForContent(ulong id) - { - lock (_ContentData) - { - if (!_ContentIdToCharacterIds.ContainsKey(id)) - { - return new List(); - } - return _ContentIdToCharacterIds[id].ToList(); - } - } - - public bool AddCharacterToContentGroup(ulong id, uint characterId) - { - lock (_ContentData) - { - if (!_ContentData.ContainsKey(id)) - { - return false; - } - - _CharacterIdToContentId[characterId] = id; - if (!_ContentIdToCharacterIds.ContainsKey(id)) - { - _ContentIdToCharacterIds[id] = new HashSet(); - } - - return _ContentIdToCharacterIds[id].Add(characterId); - } - } - - public bool AddCharacterToContentGroup(ulong id, Character character) - { - return AddCharacterToContentGroup(id, character.CharacterId); - } - - public bool RemoveCharacterFromContentGroup(uint characterId) - { - lock (_ContentData) - { - if (!_ContentIdToCharacterIds.ContainsKey(characterId)) - { - return false; - } - - ulong id = _CharacterIdToContentId[characterId]; - _CharacterIdToContentId.Remove(characterId); - - if (!_ContentIdToCharacterIds.ContainsKey(id)) - { - return true; - } - - _ContentIdToCharacterIds[id].Remove(characterId); - } - - return true; - } - - public bool RemoveCharacterFromContentGroup(Character character) - { - return RemoveCharacterFromContentGroup(character.CharacterId); - } - - public bool AddQuestToContent(ulong id, Quest quest) - { - lock (_ContentData) - { - _ContentIdToQuest[id] = quest; - return true; - } - } - - public bool RemoveQuestFromContent(ulong id) - { - lock (_ContentData) - { - if (!_ContentIdToQuest.ContainsKey(id)) - { - return false; - } - return _ContentIdToQuest.Remove(id); - } - } - - public Quest GetQuestForContent(ulong id) - { - lock (_ContentData) - { - if (!_ContentIdToQuest.ContainsKey(id)) - { - return null; - } - return _ContentIdToQuest[id]; - } - } - - public uint GenerateEntryItemId() - { - lock (_FreeEntryItemIds) - { - if (_FreeEntryItemIds.Count == 0) - { - EntryItemIdCounter = EntryItemIdCounter + 1; - _FreeEntryItemIds.Push(EntryItemIdCounter); - } - return _FreeEntryItemIds.Pop(); - } - } - - private void ReclaimEntryItemId(uint id) - { - lock (_FreeEntryItemIds) - { - _FreeEntryItemIds.Push(id); - } - } - - public void StartTimer(ulong contentId, GameClient client, uint playtimeInSeconds) - { - lock (_ContentData) - { - _ContentTimers[contentId] = new TimerState(); - - var timerState = _ContentTimers[contentId]; - timerState.Duration = TimeSpan.FromSeconds(playtimeInSeconds); - timerState.TimeStart = DateTime.Now; - timerState.Timer = new Timer(task => - { - TimeSpan alreadyElapsed = DateTime.Now.Subtract(timerState.TimeStart); - if (alreadyElapsed > timerState.Duration) - { - Logger.Info($"Timer expired for ContentId={contentId}"); - client.Party.SendToAll(new S2CQuestPlayTimeupNtc()); - CancelTimer(contentId); - } - }, null, 0, 1000); - Logger.Info($"Starting {playtimeInSeconds} second timer for ContentId={contentId}"); - } - } - - public ulong ExtendTimer(ulong contentId, uint amountInSeconds) - { - lock (_ContentTimers) - { - Logger.Info($"Extending time by {amountInSeconds} seconds for ContentId={contentId}"); - _ContentTimers[contentId].Duration += TimeSpan.FromSeconds(amountInSeconds); - return (ulong) ((DateTimeOffset)(_ContentTimers[contentId].TimeStart + _ContentTimers[contentId].Duration)).ToUnixTimeSeconds(); - } - } - - public (TimeSpan Elapsed, TimeSpan MaximumDuration) CancelTimer(ulong contentId) - { - lock (_ContentData) - { - if (_ContentTimers.ContainsKey(contentId)) - { - Logger.Info($"Canceling timer for ContentId={contentId}"); - _ContentTimers[contentId].Timer.Dispose(); - - var timerState = _ContentTimers[contentId]; - - TimeSpan elapsed = DateTime.Now.Subtract(timerState.TimeStart); - var results = (elapsed, timerState.Duration); - _ContentTimers.Remove(contentId); - return results; - } - return (TimeSpan.Zero, TimeSpan.Zero); - } - } - } -} diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 353d1bcaa..4fba110a8 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -7,6 +7,8 @@ using Arrowgene.Logging; using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; namespace Arrowgene.Ddon.GameServer.Characters @@ -24,6 +26,7 @@ private QuestManager() private static readonly HashSet AvailableVariantQuests = new(); private static Dictionary> gTutorialQuests = new Dictionary>(); private static Dictionary> gWorldQuests = new Dictionary>(); + private static readonly Dictionary gExtremeQuests = new Dictionary(); public static HashSet GetAllVariantQuestIds() { @@ -79,6 +82,10 @@ public static void LoadQuests(AssetRepository assetRepository) } gWorldQuests[quest.QuestAreaId].Add(quest); } + else if (quest.QuestType == QuestType.ExtremeMission) + { + gExtremeQuests[quest.MissionParams.BoardId] = quest; + } } } @@ -169,6 +176,15 @@ public static List GetWorldQuestIdsByAreaId(QuestAreaId areaId) return gWorldQuests[areaId].Select(x => x.QuestId).ToList(); } + public static Quest GetQuestByBoardId(ulong boardId) + { + if (!gExtremeQuests.ContainsKey(boardId)) + { + return null; + } + return gExtremeQuests[boardId]; + } + public static List GetTutorialQuestsByStageNo(uint stageNo) { if (!gTutorialQuests.ContainsKey(stageNo)) diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index b478f32ea..ab36e1dc2 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -76,7 +76,8 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi HubManager = new HubManager(this); GpCourseManager = new GpCourseManager(this); WeatherManager = new WeatherManager(this); - ExmManager = new ExmManager(this); + ContentManager = new ContentManager(this); + BoardManager = new BoardManager(this); // Orb Management is slightly complex and requires updating fields across multiple systems OrbUnlockManager = new OrbUnlockManager(database, WalletManager, JobManager, CharacterManager); @@ -107,7 +108,8 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public GameRouter Router { get; } public GpCourseManager GpCourseManager { get; } public WeatherManager WeatherManager { get; } - public ExmManager ExmManager { get; } + public ContentManager ContentManager { get; } + public BoardManager BoardManager { get; } public ChatLogHandler ChatLogHandler { get; } @@ -517,6 +519,7 @@ private void LoadPacketHandler() AddHandler(new QuestPlayEndHandler(this)); AddHandler(new QuestPlayInterruptHandler(this)); AddHandler(new QuestGetEndContentsRecruitListHandler(this)); + AddHandler(new QuestGetQuestScheduleInfoHandler(this)); AddHandler(new EntryBoardEntryBoardListHandler(this)); AddHandler(new EntryBoardEntryBoardItemCreateHandler(this)); @@ -525,6 +528,11 @@ private void LoadPacketHandler() AddHandler(new EntryBoardEntryBoardItemReadyHandler(this)); AddHandler(new EntryBoardEntryBoardItemLeaveHandler(this)); AddHandler(new EntryBoardEntryItemInfoChangeHandler(this)); + AddHandler(new EntryBoardEntryBoardItemInviteHandler(this)); + AddHandler(new EntryBoardEntryBoardItemInfoHandler(this)); + AddHandler(new EntryBoardEntryBoardItemEntryHandler(this)); + AddHandler(new EntryBoardEntryBoardItemListHandler(this)); + AddHandler(new EntryBoardEntryRecreateHandler(this)); AddHandler(new ServerGameTimeGetBaseinfoHandler(this)); AddHandler(new ServerGetGameSettingHandler(this)); diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs index 4f528c0c3..dcc785b3a 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs @@ -23,39 +23,37 @@ public EntryBoardEntryBoardItemCreateHandler(DdonGameServer server) : base(serve public override S2CEntryBoardEntryBoardItemCreateRes Handle(GameClient client, C2SEntryBoardEntryBoardItemCreateReq request) { - // var pcap = new S2CEntryBoardEntryBoardItemCreateRes.Serializer().Read(GameFull.Dump_710.AsBuffer()); - var result = new S2CEntryBoardEntryBoardItemCreateRes() + // var pcap = new S2CEntryBoardEntryBoardItemCreateRes.Serializer().Read(GameFull.Dump_710.AsBuffer()) + + var data = Server.BoardManager.CreateNewGroup(request.BoardId, request.CreateParam, request.Password, client.Character); + // Override some defaults using JSON config + var quest = QuestManager.GetQuestByBoardId(request.BoardId); + if (quest != null) { - BoardId = request.BoardId, - }; + data.EntryItem.Param.MinEntryNum = (ushort)quest.MissionParams.MinimumMembers; + data.EntryItem.Param.MaxEntryNum = (ushort)quest.MissionParams.MaximumMembers; + } - result.EntryItem.Param = request.CreateParam; - result.EntryItem.PartyLeaderCharacterId = client.Character.CharacterId; - result.EntryItem.TimeOut = 3600; - result.EntryItem.Id = Server.ExmManager.GenerateEntryItemId(); - result.EntryItem.Param.MinEntryNum = 1; + data.EntryItem.PartyLeaderCharacterId = data.PartyLeaderCharacterId; + data.EntryItem.TimeOut = 3600; // TODO: Start a timer for 3600 var member = new CDataEntryMemberData() { - EntryFlag = false, + EntryFlag = true, Id = 1, }; GameStructure.CDataCharacterListElement(member.CharacterListElement, client.Character); - result.EntryItem.EntryMemberList.Add(member); + data.EntryItem.EntryMemberList.Add(member); - for (int i = 2; i < request.CreateParam.MaxEntryNum + 1; i++) + for (int i = 2; i < data.EntryItem.Param.MaxEntryNum + 1; i++) { - result.EntryItem.EntryMemberList.Add(new CDataEntryMemberData() + data.EntryItem.EntryMemberList.Add(new CDataEntryMemberData() { EntryFlag = false, Id = (ushort) i }); } - // Everything went well, so store the state in the ExmManager - Server.ExmManager.CreateGroupForContent(request.BoardId, result.EntryItem); - Server.ExmManager.AddCharacterToContentGroup(request.BoardId, client.Character); - S2CEntryBoardEntryBoardItemChangeMemberNtc ntc = new S2CEntryBoardEntryBoardItemChangeMemberNtc() { EntryFlag = true, @@ -65,7 +63,11 @@ public override S2CEntryBoardEntryBoardItemCreateRes Handle(GameClient client, C Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.EntryBoard); - return result; + return new S2CEntryBoardEntryBoardItemCreateRes() + { + BoardId = request.BoardId, + EntryItem = data.EntryItem + }; } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemEntryHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemEntryHandler.cs new file mode 100644 index 000000000..2217ddc18 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemEntryHandler.cs @@ -0,0 +1,75 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +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 EntryBoardEntryBoardItemEntryHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemEntryHandler)); + + public EntryBoardEntryBoardItemEntryHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemEntryRes Handle(GameClient client, C2SEntryBoardEntryBoardItemEntryReq request) + { + var data = Server.BoardManager.GetGroupData(request.EntryId); + if (data == null) + { + return new S2CEntryBoardEntryBoardItemEntryRes() + { + Error = (uint)ErrorCode.ERROR_CODE_ENTRY_BOARD_NO_ITEM + }; + } + + if (data.ContentInProgress) + { + return new S2CEntryBoardEntryBoardItemEntryRes() + { + Error = (uint)ErrorCode.ERROR_CODE_ENTRY_BOARD_ITEM_CREATE_OVER + }; + } + + if (!Server.BoardManager.AddCharacterToGroup(data.EntryItem.Id, client.Character)) + { + return new S2CEntryBoardEntryBoardItemEntryRes() + { + Error = (uint)ErrorCode.ERROR_CODE_ENTRY_BOARD_NO_SPACE + }; + } + + CDataEntryMemberData memberData; + lock (data) + { + memberData = data.EntryItem.EntryMemberList.First(x => x.EntryFlag == false); + memberData.EntryFlag = true; + GameStructure.CDataCharacterListElement(memberData.CharacterListElement, client.Character); + } + + S2CEntryBoardEntryBoardItemChangeMemberNtc ntc = new S2CEntryBoardEntryBoardItemChangeMemberNtc() + { + EntryFlag = true, + MemberData = memberData, + }; + + foreach (var characterId in data.Members) + { + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + memberClient.Send(ntc); + } + + Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.EntryBoard); + + return new S2CEntryBoardEntryBoardItemEntryRes() + { + EntryItem = data.EntryItem + }; + } + } +} + diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs index 0f72929f5..07af9686a 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs @@ -20,15 +20,22 @@ public EntryBoardEntryBoardItemForceStartHandler(DdonGameServer server) : base(s public override S2CEntryBoardEntryBoardItemForceStartRes Handle(GameClient client, C2SEntryBoardEntryBoardItemForceStartReq request) { // var pcap = new S2CEntryBoardEntryBoardItemForceStartRes.Serializer().Read(GameFull.Dump_711.AsBuffer()); - var data = Server.ExmManager.GetEntryItemDataForCharacter(client.Character); - // ALlows the menu to transition - var ntc = new S2CEntryBoardEntryBoardItemReadyNtc() + var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); + + foreach (var characterId in data.Members) { - MaxMember = data.Param.MaxEntryNum, - TimeOut = data.TimeOut - }; - client.Send(ntc); + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + // Allows the menu to transition + var ntc = new S2CEntryBoardEntryBoardItemReadyNtc() + { + MaxMember = data.EntryItem.Param.MaxEntryNum, + TimeOut = 120 + }; + memberClient.Send(ntc); + } + + // TODO: Start a timer for 120 seconds return new S2CEntryBoardEntryBoardItemForceStartRes(); } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoHandler.cs new file mode 100644 index 000000000..d4d8fb8a5 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoHandler.cs @@ -0,0 +1,25 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Logging; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemInfoHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemInfoHandler)); + + public EntryBoardEntryBoardItemInfoHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemRes Handle(GameClient client, C2SEntryBoardEntryBoardItemReq request) + { + var data = Server.BoardManager.GetGroupData(request.EntryId); + return new S2CEntryBoardEntryBoardItemRes() + { + BoardId = request.BoardId, + EntryItemData = data.EntryItem + }; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyselfHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyselfHandler.cs index e1555239d..00f786a43 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyselfHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInfoMyselfHandler.cs @@ -15,10 +15,12 @@ public EntryBoardEntryBoardItemInfoMyselfHandler(DdonGameServer server) : base(s public override S2CEntryBoardEntryBoardItemInfoMyselfRes Handle(GameClient client, C2SEntryBoardEntryBoardItemInfoMyselfReq request) { // var pcap = new S2CEntryBoardEntryBoardItemInfoMyselfRes.Serializer().Read(GameFull.Dump_712.AsBuffer()); + + var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); var result = new S2CEntryBoardEntryBoardItemInfoMyselfRes() { - ContentId = Server.ExmManager.GetContentIdForCharacter(client.Character), - EntryItem = Server.ExmManager.GetEntryItemDataForCharacter(client.Character) + BoardId = data.BoardId, + EntryItem = data.EntryItem }; return result; diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInviteHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInviteHandler.cs new file mode 100644 index 000000000..d883e8352 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemInviteHandler.cs @@ -0,0 +1,40 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Logging; +using System.Diagnostics.Metrics; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemInviteHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemLeaveHandler)); + + public EntryBoardEntryBoardItemInviteHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemInviteRes Handle(GameClient client, C2SEntryBoardEntryBoardItemInviteReq request) + { + var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); + foreach (var characterId in request.CharacterIds) + { + if (!data.Members.Contains(characterId.Value)) + { + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId.Value); + + var ntc = new S2CEntryBoardEntryBoardItemInviteNtc() + { + BoardId = data.BoardId, + ItemId = data.EntryItem.Id, + Comment = data.EntryItem.Param.Comment + }; + GameStructure.CDataCharacterListElement(ntc.Character, client.Character); + + memberClient.Send(ntc); + } + } + return new S2CEntryBoardEntryBoardItemInviteRes(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs index c589d2a52..9546d262c 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs @@ -8,6 +8,7 @@ using Arrowgene.Logging; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; namespace Arrowgene.Ddon.GameServer.Handler @@ -22,31 +23,50 @@ public EntryBoardEntryBoardItemLeaveHandler(DdonGameServer server) : base(server public override S2CEntryBoardEntryBoardItemLeaveRes Handle(GameClient client, C2SEntryBoardEntryBoardItemLeaveReq request) { - var contentId = Server.ExmManager.GetContentIdForCharacter(client.Character); - var data = Server.ExmManager.GetEntryItemDataForContent(contentId); - var characterIds = Server.ExmManager.GetCharacterIdsForContent(contentId); - + var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); if (data.PartyLeaderCharacterId == client.Character.CharacterId) { - Server.ExmManager.RemoveGroupForContent(contentId); - foreach (var characterId in characterIds) + Server.BoardManager.RemoveGroup(data.EntryItem.Id); + foreach (var characterId in data.Members) { var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); if (memberClient != null) { memberClient.Send(new S2CEntryBoardEntryBoardItemLeaveNtc()); + Server.CharacterManager.UpdateOnlineStatus(client, memberClient.Character, OnlineStatus.Online); } } } else { - // TODO: This might be wrong + CDataEntryMemberData memberData; + lock (data) + { + memberData = data.EntryItem.EntryMemberList.Where(x => x.CharacterListElement.CommunityCharacterBaseInfo.CharacterId == client.Character.CharacterId).First(); + memberData.EntryFlag = false; + memberData.CharacterListElement = new CDataCharacterListElement(); + } + + S2CEntryBoardEntryBoardItemChangeMemberNtc ntc = new S2CEntryBoardEntryBoardItemChangeMemberNtc() + { + EntryFlag = false, + MemberData = memberData, + }; + + foreach (var characterId in data.Members) + { + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + if (memberClient != null) + { + memberClient.Send(ntc); + } + } + client.Send(new S2CEntryBoardEntryBoardItemLeaveNtc()); - var leaderClient = Server.ClientLookup.GetClientByCharacterId(data.PartyLeaderCharacterId); - leaderClient.Send(new S2CEntryBoardEntryBoardItemLeaveNtc()); - } - Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.Online); + Server.BoardManager.RemoveCharacterFromGroup(client.Character); + Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.Online); + } return new S2CEntryBoardEntryBoardItemLeaveRes(); } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemListHandler.cs new file mode 100644 index 000000000..253bedfd1 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemListHandler.cs @@ -0,0 +1,105 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Logging; +using System.Diagnostics.Metrics; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemListHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemListHandler)); + + public EntryBoardEntryBoardItemListHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemListRes Handle(GameClient client, C2SEntryBoardEntryBoardItemListReq request) + { + var result = new S2CEntryBoardEntryBoardItemListRes() + { + BoardId = request.BoardId, + }; + + // TODO: Implement friend, clan, party and group search + // TODO: Implement more complex item rank searches + // TODO: Implement complex job searches + + var groups = Server.BoardManager.GetGroupsForBoardId(request.BoardId); + foreach (var group in groups) + { + if (group.ContentInProgress) + { + // Skip groups that are currently playing + continue; + } + + if (request.SearchParameter.IsNoPassword && group.Password != "") + { + continue; + } + + if (request.SearchParameter.RequiredItemRankMin > 0 && + (group.EntryItem.Param.RequiredItemRank < request.SearchParameter.RequiredItemRankMin || + group.EntryItem.Param.RequiredItemRank > request.SearchParameter.RequiredItemRankMax)) + { + continue; + } + + if (request.SearchParameter.RankMin > 0 && + (group.EntryItem.Param.TopEntryJobLevel < request.SearchParameter.RankMin || + group.EntryItem.Param.BottomEntryJobLevel> request.SearchParameter.RankMax)) + { + continue; + } + + if (request.SearchParameter.FirstName != "" || request.SearchParameter.LastName != "") + { + bool foundFirst = false; + bool foundSecond = false; + foreach (var info in group.EntryItem.EntryMemberList) + { + foundFirst = false; + foundSecond = false; + + if (!info.EntryFlag) + { + // Slot was not filled + continue; + } + + var characterName = info.CharacterListElement.CommunityCharacterBaseInfo.CharacterName; + if (request.SearchParameter.FirstName != "" && characterName.FirstName == request.SearchParameter.FirstName) + { + foundFirst = true; + } + + if (request.SearchParameter.FirstName != "" && characterName.FirstName == request.SearchParameter.FirstName) + { + foundSecond = true; + } + + if (foundFirst || foundSecond) + { + break; + } + } + + if (!foundFirst && !foundSecond) + { + continue; + } + } + + result.EntryItemList.Add(group.EntryItem); + + if (result.EntryItemList.Count >= request.Num) + { + break; + } + } + + return result; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs index fb2d7626f..4ce76bcd2 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs @@ -7,6 +7,8 @@ using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using System.IO; +using System.Linq; +using System.Linq.Expressions; namespace Arrowgene.Ddon.GameServer.Handler { @@ -20,28 +22,53 @@ public EntryBoardEntryBoardItemReadyHandler(DdonGameServer server) : base(server public override void Handle(GameClient client, StructurePacket request) { - var data = Server.ExmManager.GetEntryItemDataForCharacter(client.Character); - var contentId = Server.ExmManager.GetContentIdForCharacter(client.Character); - var members = Server.ExmManager.GetCharacterIdsForContent(contentId); + var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); + + Server.BoardManager.SetGroupMemberReadyState(client.Character, true); var ntc = new S2CEntryBoardEntryBoardItemReserveNtc() { - NowMember = (uint) members.Count, - MaxMember = data.Param.MaxEntryNum, + NowMember = Server.BoardManager.NumGroupMembersReady(data.EntryItem.Id), + MaxMember = (uint) data.Members.Count, }; client.Send(ntc); client.Send(new S2CEntryBoardEntryBoardItemReadyRes()); - PartyGroup party = Server.PartyManager.NewParty(contentId); + if (Server.BoardManager.AllGroupMembersReady(data.EntryItem.Id)) + { + // If some sort of party recreation was taking place, it is now over + data.IsInRecreate = false; + + // Label content is in progress so other can't join after group starts + data.ContentInProgress = true; + + PartyGroup party = Server.PartyManager.NewParty(data.BoardId); + + var hostClient = Server.ClientLookup.GetClientByCharacterId(data.PartyLeaderCharacterId); + party.AddHost(hostClient); + + S2CPartyPartyInviteAcceptNtc inviteAcceptNtc = new S2CPartyPartyInviteAcceptNtc(); + inviteAcceptNtc.ServerId = (ushort)Server.Id; + inviteAcceptNtc.PartyId = party.Id; + inviteAcceptNtc.StageId = hostClient.Character.Stage.Id; + inviteAcceptNtc.PositionId = 0; + inviteAcceptNtc.MemberIndex = 0; + + hostClient.Send(inviteAcceptNtc); + + var members = data.Members.ToList(); + for (byte i = 1; i < data.Members.Count; i++) + { + var characterId = members[i]; + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + + party.Invite(memberClient, hostClient); - S2CPartyPartyInviteAcceptNtc inviteAcceptNtc = new S2CPartyPartyInviteAcceptNtc(); - inviteAcceptNtc.ServerId = (ushort)Server.Id; - inviteAcceptNtc.PartyId = party.Id; - inviteAcceptNtc.StageId = client.Character.Stage.Id; - inviteAcceptNtc.PositionId = 0; - inviteAcceptNtc.MemberIndex = 0; - client.Send(inviteAcceptNtc); + inviteAcceptNtc.MemberIndex = i; + memberClient.Send(inviteAcceptNtc); + } + } } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs index 2ac30c35e..b2bfe36ae 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs @@ -1,4 +1,5 @@ using Arrowgene.Buffers; +using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Dump; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; @@ -24,17 +25,17 @@ public override S2CEntryBoardEntryBoardListRes Handle(GameClient client, C2SEntr // var result = new S2CEntryBoardEntryBoardListRes.Serializer().Read(GameFull.Dump_709.AsBuffer()); var result = new S2CEntryBoardEntryBoardListRes(); - foreach (var contentId in request.Unk0List.Select(x => x.Value).ToList()) + foreach (var boardId in request.BoardIdList.Select(x => x.Value).ToList()) { - if (Server.ExmManager.HasContentId(contentId)) + var quest = QuestManager.GetQuestByBoardId(boardId); + foreach (var group in Server.BoardManager.GetGroupsForBoardId(boardId)) { - var data = Server.ExmManager.GetEntryItemDataForContent(contentId); var contentParams = new CDataEntryBoardListParam() { - EntryId = contentId, - SortieMin = 1, - NoPartyMembers = (ushort) data.EntryMemberList.Count, - TimeOut = 3600, // TODO: Figure this out from some config? + BoardId = group.BoardId, + SortieMin = 1, // TODO: Can client populate this from somewhere? + NoPartyMembers = (ushort) group.Members.Count(), + TimeOut = 3600, // TODO: Get time ellapsed until recruitment ends and populate this to match? }; result.EntryList.Add(contentParams); } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryItemInfoChangeHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryItemInfoChangeHandler.cs index 28ae0d4f6..b155a771d 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryItemInfoChangeHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryItemInfoChangeHandler.cs @@ -21,22 +21,25 @@ public EntryBoardEntryItemInfoChangeHandler(DdonGameServer server) : base(server public override S2CEntryBoardEntryBoardItemInfoChangeRes Handle(GameClient client, C2SEntryBoardEntryBoardItemInfoChangeReq request) { - var data = Server.ExmManager.GetEntryItemDataForCharacter(client.Character); - data.Param = request.Param; - // TODO: How to save password? - // request.Password + var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); + data.Password = request.Password; + data.EntryItem.Param = request.Param; var ntc = new S2CEntryBoardEntryBoardItemInfoChangeNtc() { - BoardId = Server.ExmManager.GetContentIdForCharacter(client.Character), - EntryItemData = data + BoardId = data.BoardId, + EntryItemData = data.EntryItem }; - // TODO: Does this need to be sent to everyone in the server? - client.Party.SendToAllExcept(ntc, client); + foreach (var characterId in data.Members) + { + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + memberClient.Send(ntc); + } + return new S2CEntryBoardEntryBoardItemInfoChangeRes() { - EntryItemData = data + EntryItemData = data.EntryItem }; } } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryRecreateHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryRecreateHandler.cs new file mode 100644 index 000000000..cf529b22b --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryRecreateHandler.cs @@ -0,0 +1,39 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Logging; +using System.Diagnostics.Metrics; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryRecreateHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryRecreateHandler)); + + public EntryBoardEntryRecreateHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardItemRecreateRes Handle(GameClient client, C2SEntryBoardEntryBoardItemRecreateReq request) + { + var data = Server.BoardManager.RecreateGroup(client.Character); + foreach (var characterId in data.Members) + { + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + memberClient.Send(new S2CEntryBoardEntryBoardItemRecreateNtc() + { + BoardId = data.BoardId, + EntryItem = data.EntryItem + }); + + memberClient.Send(new S2CPartyPartyBreakupNtc()); + } + + return new S2CEntryBoardEntryBoardItemRecreateRes() + { + BoardId = data.BoardId, + EntryItem = data.EntryItem + }; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/PartyPartyJoinHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PartyPartyJoinHandler.cs index 9db853b73..2394efa90 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PartyPartyJoinHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PartyPartyJoinHandler.cs @@ -46,12 +46,6 @@ public override void Handle(GameClient client, StructurePacket !x.ContentInProgress).ToList().Count }); } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestScheduleInfoHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestScheduleInfoHandler.cs new file mode 100644 index 000000000..3ec8889c1 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestScheduleInfoHandler.cs @@ -0,0 +1,37 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.GameServer.Dump; +using Arrowgene.Ddon.GameServer.Quests; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Shared.Asset; +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.Network; +using Arrowgene.Logging; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class QuestGetQuestScheduleInfoHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestGetQuestScheduleInfoHandler)); + + public QuestGetQuestScheduleInfoHandler(DdonGameServer server) : base(server) + { + } + + public override S2CQuestGetQuestScheduleInfoRes Handle(GameClient client, C2SQuestGetQuestScheduleInfoReq packet) + { + // TODO: Convert QuestScheduleId to QuestId + return new S2CQuestGetQuestScheduleInfoRes() + { + QuestId = packet.QuestScheduleId + }; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs index d31fada78..09693a9a0 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayEndHandler.cs @@ -31,18 +31,14 @@ public QuestPlayEndHandler(DdonGameServer server) : base(server) public override S2CQuestPlayEndRes Handle(GameClient client, C2SQuestPlayEndReq request) { - var contentId = client.Party.ContentId; - var timeData = Server.ExmManager.CancelTimer(contentId); - - var quest = Server.ExmManager.GetQuestForContent(contentId); + var timeData = Server.ContentManager.CancelTimer(client.Party.Id); + var quest = QuestManager.GetQuestByBoardId(client.Party.ContentId); var ntc = new S2CQuestPlayEndNtc(); ntc.ContentsPlayEnd.RewardItemDetailList = quest.ToCDataTimeGainQuestList(0).RewardItemDetailList; ntc.ContentsPlayEnd.PlayTimeMillSec = (uint) timeData.Elapsed.Milliseconds; client.Party.SendToAll(ntc); - client.Party.ContentInProgress = false; - return new S2CQuestPlayEndRes(); } } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs index 6922f87cf..c7b0b8522 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs @@ -27,15 +27,10 @@ public override void Handle(GameClient client, StructurePacket() - { - new CDataSwitchStorage() - { - StorageType = StorageType.ItemBagConsumable, - Num = 10 - } - } - }; - client.Party.SendToAll(missionItemsNtcs); - - - foreach (var memberClient in client.Party.Clients) - { - S2CItemUpdateCharacterItemNtc itemNtc = new S2CItemUpdateCharacterItemNtc() - { - UpdateType = ItemNoticeType.SwitchingStorage - }; - - ushort i = 0; - foreach (var storageItem in memberClient.Character.Storage.GetStorage(StorageType.ItemBagConsumable).Items) - { - ushort slot = (ushort)(i + 1); - - i++; - if (storageItem == null) - { - continue; - } - - var (item, amount) = storageItem; - - itemNtc.UpdateItemList.Add(new CDataItemUpdateResult() - { - UpdateItemNum = -(int)amount, - ItemList = new CDataItemList() - { - ItemUId = item.UId, - ItemId = item.ItemId, - StorageType = StorageType.ItemBagConsumable, - SlotNo = slot, - ItemNum = amount - } - }); - itemNtc.UpdateItemList.Add(new CDataItemUpdateResult() - { - UpdateItemNum = 0, - ItemList = new CDataItemList() - { - StorageType = StorageType.ItemBagConsumable, - SlotNo = slot, - ItemNum = 0 - } - }); - } - client.Send(itemNtc); - } - - client.Party.ContentInProgress = true; } private static readonly byte[] pcap1_data = new byte[] { 0x00, 0x00, 0x01, 0xEB, 0x00, 0x04, 0xD1, 0x04, 0x03, 0x01, 0x0B, 0x08, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xB3, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x7C, 0x00, 0x00, 0x21, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x14, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x00, 0x00, 0x14, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x14, 0xC1, 0x04, 0x2C, 0xA2, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x09, 0x00, 0x00, 0x14, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x14, 0xC1, 0x04, 0x2C, 0xA2, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x01, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x21, 0xE0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x21, 0xE2, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0C, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs index fa25eee10..75e1e6320 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs @@ -4,6 +4,7 @@ using Arrowgene.Ddon.Server.Network; 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.Logging; using System; @@ -22,23 +23,16 @@ public QuestPlayStartTimerHandler(DdonGameServer server) : base(server) { } - private async void QuestTimeoutNotification(CancellationToken token) - { - - } - public override S2CQuestPlayStartTimerRes Handle(GameClient client, C2SQuestPlayStartTimerReq request) { - var contentId = Server.ExmManager.GetContentIdForCharacter(client.Character); - var quest = Server.ExmManager.GetQuestForContent(contentId); - + var quest = QuestManager.GetQuestByBoardId(client.Party.ContentId); var ntc = new S2CQuestPlayStartTimerNtc() { PlayEndDateTime = (ulong)(DateTimeOffset.UtcNow.ToUnixTimeSeconds() + quest.MissionParams.PlaytimeInSeconds) }; client.Party.SendToAll(ntc); - Server.ExmManager.StartTimer(contentId, client, quest.MissionParams.PlaytimeInSeconds); + Server.ContentManager.StartTimer(client.Party.Id, client, quest.MissionParams.PlaytimeInSeconds); return new S2CQuestPlayStartTimerRes(); } diff --git a/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs index a6c8ff100..f3c390787 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs @@ -74,7 +74,7 @@ public override S2CStageAreaChangeRes Handle(GameClient client, C2SStageAreaChan if (client.Party.ContentInProgress) { - var quest = Server.ExmManager.GetQuestForContent(client.Party.ContentId); + var quest = QuestManager.GetQuestByBoardId(client.Party.ContentId); if (quest != null) { quest.HandleAreaChange(client, client.Character.Stage); diff --git a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs index a31cb66b7..10ca3612c 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs @@ -274,12 +274,7 @@ public ErrorRes Join(GameClient client) Logger.Error(client, $"[PartyId:{Id}][Join(GameClient)] has no slot"); return ErrorRes.Fail; } - else if (partyMember == null) - { - AddHost(client); - partyMember = GetPlayerPartyMember(client); - } - + if (_leader == null && _host == null) { // first to join the party diff --git a/Arrowgene.Ddon.GameServer/Party/PartyManager.cs b/Arrowgene.Ddon.GameServer/Party/PartyManager.cs index a9f2f530e..fe3dc7fbb 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyManager.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyManager.cs @@ -129,7 +129,7 @@ public bool DisbandParty(uint partyId) return true; } - public PartyGroup NewParty(ulong contentId = 0) + public PartyGroup NewParty(ulong boardId = 0) { if (!_idPool.TryPop(out uint partyId)) { @@ -143,7 +143,7 @@ public PartyGroup NewParty(ulong contentId = 0) } } - PartyGroup party = new PartyGroup(partyId, this, contentId); + PartyGroup party = new PartyGroup(partyId, this, boardId); if (!_parties.TryAdd(partyId, party)) { Logger.Error("Could not create party, failed to add new party (!_parties.TryAdd(partyId, party))"); diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index a1d4edebb..0eaa04270 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -196,7 +196,7 @@ public override List StateMachineExecute(DdonGameServer if (questBlock.BlockType == QuestBlockType.ExtendTime && client.Party.ContentId != 0) { - var newEndTime = server.ExmManager.ExtendTimer(client.Party.ContentId, questBlock.TimeAmount); + var newEndTime = server.ContentManager.ExtendTimer(client.Party.Id, questBlock.TimeAmount); client.Party.SendToAll(new S2CQuestPlayAddTimerNtc() { PlayEndDateTime = newEndTime }); } diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index cb69806ea..93d9523ef 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -854,22 +854,29 @@ private bool ParseMissionParams(QuestAssetData assetData, JsonElement jMissionPa Logger.Error($"Missing required member 'phase_groups' from ExtremeMission config."); return false; } - + foreach (var element in jPhaseGroups.EnumerateArray()) { assetData.MissionParams.QuestPhaseGroupIdList.Add(new CDataCommonU32() { Value = element.GetUInt32() }); } - assetData.MissionParams.SortieMinimum = 4; + if (!jMissionParams.TryGetProperty("board_id", out JsonElement jBoardId)) + { + Logger.Error($"Missing required member 'board_id' from ExtremeMission config."); + return false; + } + assetData.MissionParams.BoardId = jBoardId.GetUInt64(); + + assetData.MissionParams.MinimumMembers = 4; if (jMissionParams.TryGetProperty("minimum_members", out JsonElement jMinimumMembers)) { - assetData.MissionParams.SortieMinimum = jMinimumMembers.GetUInt32(); + assetData.MissionParams.MinimumMembers = jMinimumMembers.GetUInt32(); } - assetData.MissionParams.SortieMaximum = 4; - if (jMissionParams.TryGetProperty("minimum_members", out JsonElement jMaximumMembers)) + assetData.MissionParams.MaximumMembers = 4; + if (jMissionParams.TryGetProperty("maximum_members", out JsonElement jMaximumMembers)) { - assetData.MissionParams.SortieMaximum = jMaximumMembers.GetUInt32(); + assetData.MissionParams.MaximumMembers = jMaximumMembers.GetUInt32(); } assetData.MissionParams.LootDistribution = QuestLootDistribution.Normal; diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index 8fc1438ce..31c905450 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -146,6 +146,7 @@ static EntitySerializer() Create(new CDataEntryItem.Serializer()); Create(new CDataEntryItemParam.Serializer()); Create(new CDataEntryMemberData.Serializer()); + Create(new CDataEntryBoardItemSearchParameter.Serializer()); Create(new CDataRaidBossPlayStartData.Serializer()); Create(new CDataRaidBossEnemyParam.Serializer()); @@ -479,6 +480,11 @@ static EntitySerializer() Create(new C2SEntryBoardEntryBoardItemLeaveReq.Serializer()); Create(new C2SEntryBoardEntryBoardItemInfoMyselfReq.Serializer()); Create(new C2SEntryBoardEntryBoardItemInfoChangeReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemInviteReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemEntryReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemListReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemRecreateReq.Serializer()); Create(new C2SAchievementReceivableRewardNtc.Serializer()); Create(new C2SAchievementCompleteNtc.Serializer()); @@ -648,6 +654,7 @@ static EntitySerializer() Create(new C2SQuestGetAdventureGuideQuestNtcReq.Serializer()); Create(new C2SQuestGetEndContentsRecruitListReq.Serializer()); Create(new C2SQuestPlayInterruptReq.Serializer()); + Create(new C2SQuestGetQuestScheduleInfoReq.Serializer()); Create(new C2SServerGameTimeGetBaseInfoReq.Serializer()); Create(new C2SServerGetRealTimeReq.Serializer()); @@ -877,6 +884,13 @@ static EntitySerializer() Create(new S2CEntryBoardEntryBoardItemInfoMyselfRes.Serializer()); Create(new S2CEntryBoardEntryBoardItemInfoChangeRes.Serializer()); Create(new S2CEntryBoardEntryBoardItemInfoChangeNtc.Serializer()); + Create(new S2CEntryBoardEntryBoardItemInviteRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemInviteNtc.Serializer()); + Create(new S2CEntryBoardEntryBoardItemRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemEntryRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemListRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemRecreateRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemRecreateNtc.Serializer()); Create(new S2CEquipChangeCharacterEquipJobItemNtc.Serializer()); Create(new S2CEquipChangeCharacterEquipJobItemRes.Serializer()); @@ -1130,6 +1144,7 @@ static EntitySerializer() Create(new S2CQuestPlayInterruptNtc.Serializer()); Create(new S2CQuestPlayTimeupNtc.Serializer()); Create(new S2CQuestPlayAddTimerNtc.Serializer()); + Create(new S2CQuestPlayEntryCancelNtc.Serializer()); Create(new S2CQuestSendLeaderQuestOrderConditionInfoNtc.Serializer()); Create(new S2CQuestSendLeaderQuestOrderConditionInfoRes.Serializer()); @@ -1138,6 +1153,7 @@ static EntitySerializer() Create(new S2CQuestSetPriorityQuestRes.Serializer()); Create(new S2CQuestGetMobHuntQuestListRes.Serializer()); Create(new S2CQuestGetEndContentsGroupRes.Serializer()); + Create(new S2CQuestGetQuestScheduleInfoRes.Serializer()); Create(new S2CSeason62_26_16Ntc.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemEntryReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemEntryReq.cs new file mode 100644 index 000000000..8e31f0a67 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemEntryReq.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemEntryReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_REQ; + + public C2SEntryBoardEntryBoardItemEntryReq() + { + Password = string.Empty; + } + + public ulong BoardId { get; set; } + public uint EntryId { get; set; } + public string Password { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemEntryReq obj) + { + WriteUInt64(buffer, obj.BoardId); + WriteUInt32(buffer, obj.EntryId); + WriteMtString(buffer, obj.Password); + } + + public override C2SEntryBoardEntryBoardItemEntryReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemEntryReq obj = new C2SEntryBoardEntryBoardItemEntryReq(); + obj.BoardId = ReadUInt64(buffer); + obj.EntryId = ReadUInt32(buffer); + obj.Password = ReadMtString(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInviteReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInviteReq.cs new file mode 100644 index 000000000..d8442deb3 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemInviteReq.cs @@ -0,0 +1,35 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemInviteReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_REQ; + + public C2SEntryBoardEntryBoardItemInviteReq() + { + CharacterIds = new List(); + } + + public List CharacterIds { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemInviteReq obj) + { + WriteEntityList(buffer, obj.CharacterIds); + } + + public override C2SEntryBoardEntryBoardItemInviteReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemInviteReq obj = new C2SEntryBoardEntryBoardItemInviteReq(); + obj.CharacterIds = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemListReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemListReq.cs new file mode 100644 index 000000000..1e7ed824c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemListReq.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemListReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_LIST_REQ; + + public C2SEntryBoardEntryBoardItemListReq() + { + SearchParameter = new CDataEntryBoardItemSearchParameter(); + } + + public ulong BoardId { get; set; } + public uint Offset { get; set; } + public uint Num { get; set; } + public CDataEntryBoardItemSearchParameter SearchParameter { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemListReq obj) + { + WriteUInt64(buffer, obj.BoardId); + WriteUInt32(buffer, obj.Offset); + WriteUInt32(buffer, obj.Num); + WriteEntity(buffer, obj.SearchParameter); + } + + public override C2SEntryBoardEntryBoardItemListReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemListReq obj = new C2SEntryBoardEntryBoardItemListReq(); + obj.BoardId = ReadUInt64(buffer); + obj.Offset = ReadUInt32(buffer); + obj.Num = ReadUInt32(buffer); + obj.SearchParameter = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemRecreateReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemRecreateReq.cs new file mode 100644 index 000000000..2b6c0808e --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemRecreateReq.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemRecreateReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_REQ; + + public C2SEntryBoardEntryBoardItemRecreateReq() + { + Password = string.Empty; + Param = new CDataEntryItemParam(); + } + + public ulong BoardId { get; set; } + public string Password { get; set; } + public CDataEntryItemParam Param { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemRecreateReq obj) + { + WriteUInt64(buffer, obj.BoardId); + WriteMtString(buffer, obj.Password); + WriteEntity(buffer, obj.Param); + } + + public override C2SEntryBoardEntryBoardItemRecreateReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemRecreateReq obj = new C2SEntryBoardEntryBoardItemRecreateReq(); + obj.BoardId = ReadUInt64(buffer); + obj.Password = ReadMtString(buffer); + obj.Param = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemReq.cs new file mode 100644 index 000000000..b578f5302 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemReq.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardItemReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_REQ; + + public C2SEntryBoardEntryBoardItemReq() + { + } + + public ulong BoardId { get; set; } + public uint EntryId { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemReq obj) + { + WriteUInt64(buffer, obj.BoardId); + WriteUInt32(buffer, obj.EntryId); + } + + public override C2SEntryBoardEntryBoardItemReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardItemReq obj = new C2SEntryBoardEntryBoardItemReq(); + obj.BoardId = ReadUInt64(buffer); + obj.EntryId = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardListReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardListReq.cs index c120333a6..b35897ae5 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardListReq.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardListReq.cs @@ -11,22 +11,22 @@ public class C2SEntryBoardEntryBoardListReq : IPacketStructure public C2SEntryBoardEntryBoardListReq() { - Unk0List = new List(); // List of Entry ID's? + BoardIdList = new List(); // List of Board ID's? } - public List Unk0List { get; set; } + public List BoardIdList { get; set; } public class Serializer : PacketEntitySerializer { public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardListReq obj) { - WriteEntityList(buffer, obj.Unk0List); + WriteEntityList(buffer, obj.BoardIdList); } public override C2SEntryBoardEntryBoardListReq Read(IBuffer buffer) { C2SEntryBoardEntryBoardListReq obj = new C2SEntryBoardEntryBoardListReq(); - obj.Unk0List = ReadEntityList(buffer); + obj.BoardIdList = ReadEntityList(buffer); return obj; } } diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetQuestScheduleInfoReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetQuestScheduleInfoReq.cs new file mode 100644 index 000000000..90917a68c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestGetQuestScheduleInfoReq.cs @@ -0,0 +1,34 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model.Quest; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestGetQuestScheduleInfoReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_GET_QUEST_SCHEDULE_INFO_REQ; + + public C2SQuestGetQuestScheduleInfoReq() + { + } + + public uint QuestScheduleId { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestGetQuestScheduleInfoReq obj) + { + WriteUInt32(buffer, obj.QuestScheduleId); + } + + public override C2SQuestGetQuestScheduleInfoReq Read(IBuffer buffer) + { + C2SQuestGetQuestScheduleInfoReq obj = new C2SQuestGetQuestScheduleInfoReq(); + obj.QuestScheduleId = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemEntryRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemEntryRes.cs new file mode 100644 index 000000000..e05501638 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemEntryRes.cs @@ -0,0 +1,36 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemEntryRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_RES; + + public S2CEntryBoardEntryBoardItemEntryRes() + { + EntryItem = new CDataEntryItem(); + } + + public CDataEntryItem EntryItem { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemEntryRes obj) + { + WriteServerResponse(buffer, obj); + WriteEntity(buffer, obj.EntryItem); + } + + public override S2CEntryBoardEntryBoardItemEntryRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemEntryRes obj = new S2CEntryBoardEntryBoardItemEntryRes(); + ReadServerResponse(buffer, obj); + obj.EntryItem = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoMyselfRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoMyselfRes.cs index 81df2d1e5..52f81c7e4 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoMyselfRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInfoMyselfRes.cs @@ -14,7 +14,7 @@ public S2CEntryBoardEntryBoardItemInfoMyselfRes() EntryItem = new CDataEntryItem(); } - public ulong ContentId { get; set; } + public ulong BoardId { get; set; } public CDataEntryItem EntryItem { get; set; } public class Serializer : PacketEntitySerializer @@ -22,7 +22,7 @@ public class Serializer : PacketEntitySerializer(buffer); return obj; } diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInviteNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInviteNtc.cs new file mode 100644 index 000000000..349c456b3 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInviteNtc.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemInviteNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_NTC; + + public S2CEntryBoardEntryBoardItemInviteNtc() + { + Character = new CDataCharacterListElement(); + Comment = string.Empty; + } + + public CDataCharacterListElement Character { get; set; } + public ulong BoardId { get; set; } + public uint ItemId { get; set; } + public string Comment { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemInviteNtc obj) + { + WriteEntity(buffer, obj.Character); + WriteUInt64(buffer, obj.BoardId); + WriteUInt32(buffer, obj.ItemId); + WriteMtString(buffer, obj.Comment); + } + + public override S2CEntryBoardEntryBoardItemInviteNtc Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemInviteNtc obj = new S2CEntryBoardEntryBoardItemInviteNtc(); + obj.Character = ReadEntity(buffer); + obj.BoardId = ReadUInt64(buffer); + obj.ItemId = ReadUInt32(buffer); + obj.Comment = ReadMtString(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInviteRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInviteRes.cs new file mode 100644 index 000000000..3539d1b65 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemInviteRes.cs @@ -0,0 +1,31 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemInviteRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES; + + public S2CEntryBoardEntryBoardItemInviteRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemInviteRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CEntryBoardEntryBoardItemInviteRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemInviteRes obj = new S2CEntryBoardEntryBoardItemInviteRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemListRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemListRes.cs new file mode 100644 index 000000000..dbc7f3785 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemListRes.cs @@ -0,0 +1,39 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemListRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_LIST_RES; + + public S2CEntryBoardEntryBoardItemListRes() + { + EntryItemList = new List(); + } + + public ulong BoardId { get; set; } + public List EntryItemList { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemListRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt64(buffer, obj.BoardId); + WriteEntityList(buffer, obj.EntryItemList); + } + + public override S2CEntryBoardEntryBoardItemListRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemListRes obj = new S2CEntryBoardEntryBoardItemListRes(); + ReadServerResponse(buffer, obj); + obj.BoardId = ReadUInt64(buffer); + obj.EntryItemList = ReadEntityList(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRecreateNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRecreateNtc.cs new file mode 100644 index 000000000..7f4493d33 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRecreateNtc.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemRecreateNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_NTC; + + public S2CEntryBoardEntryBoardItemRecreateNtc() + { + EntryItem = new CDataEntryItem(); + } + + public ulong BoardId { get; set; } + public CDataEntryItem EntryItem { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemRecreateNtc obj) + { + WriteUInt64(buffer, obj.BoardId); + WriteEntity(buffer, obj.EntryItem); + } + + public override S2CEntryBoardEntryBoardItemRecreateNtc Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemRecreateNtc obj = new S2CEntryBoardEntryBoardItemRecreateNtc(); + obj.BoardId = ReadUInt64(buffer); + obj.EntryItem = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRecreateRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRecreateRes.cs new file mode 100644 index 000000000..8e6114d80 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRecreateRes.cs @@ -0,0 +1,39 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemRecreateRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_RES; + + public S2CEntryBoardEntryBoardItemRecreateRes() + { + EntryItem = new CDataEntryItem(); + } + + public ulong BoardId { get; set; } + public CDataEntryItem EntryItem { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemRecreateRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt64(buffer, obj.BoardId); + WriteEntity(buffer, obj.EntryItem); + } + + public override S2CEntryBoardEntryBoardItemRecreateRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemRecreateRes obj = new S2CEntryBoardEntryBoardItemRecreateRes(); + ReadServerResponse(buffer, obj); + obj.BoardId = ReadUInt64(buffer); + obj.EntryItem = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRes.cs new file mode 100644 index 000000000..a69921f49 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemRes.cs @@ -0,0 +1,39 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardItemRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_RES; + + public S2CEntryBoardEntryBoardItemRes() + { + EntryItemData = new CDataEntryItem(); + } + + public ulong BoardId { get; set; } + public CDataEntryItem EntryItemData { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt64(buffer, obj.BoardId); + WriteEntity(buffer, obj.EntryItemData); + } + + public override S2CEntryBoardEntryBoardItemRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardItemRes obj = new S2CEntryBoardEntryBoardItemRes(); + ReadServerResponse(buffer, obj); + obj.BoardId = ReadUInt64(buffer); + obj.EntryItemData = ReadEntity(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetQuestScheduleInfoRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetQuestScheduleInfoRes.cs new file mode 100644 index 000000000..083280a23 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestGetQuestScheduleInfoRes.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestGetQuestScheduleInfoRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_GET_QUEST_SCHEDULE_INFO_RES; + + public uint QuestId { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestGetQuestScheduleInfoRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt32(buffer, obj.QuestId); + } + + public override S2CQuestGetQuestScheduleInfoRes Read(IBuffer buffer) + { + S2CQuestGetQuestScheduleInfoRes obj = new S2CQuestGetQuestScheduleInfoRes(); + ReadServerResponse(buffer, obj); + obj.QuestId = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryCancelNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryCancelNtc.cs new file mode 100644 index 000000000..34faa72be --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryCancelNtc.cs @@ -0,0 +1,33 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayEntryCancelNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_PLAY_ENTRY_CANCEL_NTC; + + public S2CQuestPlayEntryCancelNtc() + { + } + + public uint CharacterId { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayEntryCancelNtc obj) + { + WriteUInt32(buffer, obj.CharacterId); + } + + public override S2CQuestPlayEntryCancelNtc Read(IBuffer buffer) + { + S2CQuestPlayEntryCancelNtc obj = new S2CQuestPlayEntryCancelNtc(); + obj.CharacterId = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardItemSearchParameter.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardItemSearchParameter.cs new file mode 100644 index 000000000..088bb45d1 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardItemSearchParameter.cs @@ -0,0 +1,65 @@ +using Arrowgene.Buffers; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; + +namespace Arrowgene.Ddon.Shared.Entity.Structure +{ + public class CDataEntryBoardItemSearchParameter + { + public CDataEntryBoardItemSearchParameter() + { + SearchGroups = new List(); + } + + public List SearchGroups { get; set; } // 1 = Friends, 2 = Clan Members, 3 = Party Members, 4 = Group Chat + public string FirstName { get; set; } + public string LastName { get; set; } + public bool RankSetting { get; set; } + public uint RankMin { get; set; } // Min Level + public uint RankMax { get; set; } // Max Level + public uint Job { get; set; } // Job Mask + public bool IsNoPassword { get; set; } + public uint RequiredItemRankMin { get; set; } // IR MIN + public uint RequiredItemRankMax { get; set; } // IR MAX + public uint Unk0 { get; set; } + public uint Unk1 { get; set; } + + + public class Serializer : EntitySerializer + { + public override void Write(IBuffer buffer, CDataEntryBoardItemSearchParameter obj) + { + WriteEntityList(buffer, obj.SearchGroups); + WriteMtString(buffer, obj.FirstName); + WriteMtString(buffer, obj.LastName); + WriteBool(buffer, obj.RankSetting); + WriteUInt32(buffer, obj.RankMin); + WriteUInt32(buffer, obj.RankMax); + WriteUInt32(buffer, obj.Job); + WriteBool(buffer, obj.IsNoPassword); + WriteUInt32(buffer, obj.RequiredItemRankMin); + WriteUInt32(buffer, obj.RequiredItemRankMax); + WriteUInt32(buffer, obj.Unk0); + WriteUInt32(buffer, obj.Unk1); + } + + public override CDataEntryBoardItemSearchParameter Read(IBuffer buffer) + { + CDataEntryBoardItemSearchParameter obj = new CDataEntryBoardItemSearchParameter(); + obj.SearchGroups = ReadEntityList(buffer); + obj.FirstName = ReadMtString(buffer); + obj.LastName = ReadMtString(buffer); + obj.RankSetting = ReadBool(buffer); + obj.RankMin = ReadUInt32(buffer); + obj.RankMax = ReadUInt32(buffer); + obj.Job = ReadUInt32(buffer); + obj.IsNoPassword = ReadBool(buffer); + obj.RequiredItemRankMin = ReadUInt32(buffer); + obj.RequiredItemRankMax = ReadUInt32(buffer); + obj.Unk0 = ReadUInt32(buffer); + obj.Unk1 = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardListParam.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardListParam.cs index 3e70549f6..673cc913f 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardListParam.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataEntryBoardListParam.cs @@ -4,7 +4,7 @@ namespace Arrowgene.Ddon.Shared.Entity.Structure { public class CDataEntryBoardListParam { - public ulong EntryId { get; set; } // Board Entry ID? + public ulong BoardId { get; set; } // Board Entry ID? public ushort SortieMin { get; set; } public ushort NoPartyMembers { get; set; } public ushort Unk3 { get; set; } @@ -18,7 +18,7 @@ public class Serializer : EntitySerializer { public override void Write(IBuffer buffer, CDataEntryBoardListParam obj) { - WriteUInt64(buffer, obj.EntryId); + WriteUInt64(buffer, obj.BoardId); WriteUInt16(buffer, obj.SortieMin); WriteUInt16(buffer, obj.NoPartyMembers); WriteUInt16(buffer, obj.Unk3); @@ -31,7 +31,7 @@ public override void Write(IBuffer buffer, CDataEntryBoardListParam obj) public override CDataEntryBoardListParam Read(IBuffer buffer) { CDataEntryBoardListParam obj = new CDataEntryBoardListParam(); - obj.EntryId = ReadUInt64(buffer); + obj.BoardId = ReadUInt64(buffer); obj.SortieMin = ReadUInt16(buffer); obj.NoPartyMembers = ReadUInt16(buffer); obj.Unk3 = ReadUInt16(buffer); diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095010.json index c36969abe..0a4f9d13a 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095010.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20095010.json @@ -6,7 +6,7 @@ "base_level": 48, "minimum_item_rank": 0, "discoverable": true, - "area_id": "ZandoraWastelands", + "area_id": "ZandoraWastelands", "rewards": [ { "type": "wallet", @@ -62,24 +62,24 @@ "level": 48, "exp": 1400, "is_boss": false - }, - { + }, + { "enemy_id": "0x015802", "level": 48, "exp": 1400, "is_boss": false - }, - { + }, + { "enemy_id": "0x015810", "level": 48, "exp": 1400, "is_boss": false - }, - { + }, + { "enemy_id": "0x015820", "level": 48, "exp": 1500, - "is_boss": false + "is_boss": false } ] } @@ -113,7 +113,7 @@ "stage_id": { "id": 1, "group_id": 1, - "layer_no": 1 + "layer_no": 1 }, "announce_type": "Update", "npc_id": "ArisenCorpsRegimentalSoldier1", diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json index 1b586fe1e..52d19dfdf 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json @@ -8,9 +8,10 @@ "discoverable": false, "mission_params": { "group": 1, + "board_id": 17229970204, "minimum_members": 1, "playtime": 1200, - "solo_only": true, + "solo_only": false, "max_pawns": 3, "phase_groups": [] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json index cb0422133..dd2c317bc 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json @@ -8,9 +8,10 @@ "discoverable": false, "mission_params": { "group": 1, + "board_id": 17229971204, "minimum_members": 1, "playtime": 1500, - "solo_only": true, + "solo_only": false, "max_pawns": 3, "phase_groups": [] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json index d96b12299..071cd513b 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json @@ -8,9 +8,10 @@ "discoverable": false, "mission_params": { "group": 1, + "board_id": 17229972204, "minimum_members": 1, "playtime": 1200, - "solo_only": true, + "solo_only": false, "max_pawns": 3, "phase_groups": [] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json index 576fcf56d..313e21a0f 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json @@ -8,9 +8,10 @@ "discoverable": false, "mission_params": { "group": 1, + "board_id": 17229973184, "minimum_members": 1, "playtime": 1200, - "solo_only": true, + "solo_only": false, "max_pawns": 3, "phase_groups": [] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json index 160890e8b..b9c6a5eed 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json @@ -8,9 +8,10 @@ "discoverable": false, "mission_params": { "group": 2, + "board_id": 17230070184, "minimum_members": 1, "playtime": 1200, - "solo_only": true, + "solo_only": false, "max_pawns": 3, "phase_groups": [] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json index 4b01104f0..cf658fca7 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json @@ -8,9 +8,10 @@ "discoverable": false, "mission_params": { "group": 2, + "board_id": 17230071184, "minimum_members": 1, "playtime": 900, - "solo_only": true, + "solo_only": false, "max_pawns": 3, "phase_groups": [] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json index 59ee9271a..984ee2264 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json @@ -8,6 +8,7 @@ "discoverable": false, "mission_params": { "group": 1, + "board_id": 17230071187, "minimum_members": 1, "playtime": 600, "solo_only": true, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json index 0b590f9a9..8ab95a6f7 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json @@ -8,9 +8,10 @@ "discoverable": false, "mission_params": { "group": 2, + "board_id": 17230072184, "minimum_members": 1, "playtime": 600, - "solo_only": true, + "solo_only": false, "max_pawns": 3, "phase_groups": [] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json index 9cfc69b60..594775360 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json @@ -8,9 +8,10 @@ "discoverable": false, "mission_params": { "group": 2, + "board_id": 17230073186, "minimum_members": 1, "playtime": 900, - "solo_only": true, + "solo_only": false, "max_pawns": 3, "phase_groups": [] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json index 2cffb05c7..8ca8e4aaf 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json @@ -8,112 +8,113 @@ "discoverable": false, "mission_params": { "group": 9, + "board_id": 17230169194, "minimum_members": 1, "maximum_members": 8, "playtime": 900, - "solo_only": true, - "max_pawns": 3, + "solo_only": false, + "max_pawns": 7, "loot_distribution": "TimeBased", "phase_groups": [] }, "order_conditions": [ ], "rewards": [ - { - "type": "random", - "loot_pool": [ - { - "item_id": 15998, - "num": 1, - "chance": 0.35 - }, - { - "item_id": 15998, - "num": 2, - "chance": 0.25 - }, - { - "item_id": 15998, - "num": 4, - "chance": 0.20 - }, - { - "item_id": 15998, - "num": 12, - "chance": 0.15 - }, - { - "item_id": 15998, - "num": 16, - "chance": 0.10 - }, - { - "item_id": 15998, - "num": 20, - "chance": 0.05 - } - ] - }, - { - "type": "random", - "loot_pool": [ - { - "item_id": 15999, - "num": 0, - "chance": 0.35 - }, - { - "item_id": 15999, - "num": 1, - "chance": 0.20 - }, - { - "item_id": 15999, - "num": 2, - "chance": 0.25 - }, - { - "item_id": 15999, - "num": 4, - "chance": 0.20 - }, - { - "item_id": 15999, - "num": 8, - "chance": 0.15 - }, - { - "item_id": 15999, - "num": 10, - "chance": 0.05 - } - ] - }, - { - "type": "random", - "loot_pool": [ - { - "item_id": 16000, - "num": 0, - "chance": 0.60 - }, - { - "item_id": 16000, - "num": 1, - "chance": 0.20 - }, - { - "item_id": 16000, - "num": 2, - "chance": 0.15 - }, - { - "item_id": 16000, - "num": 3, - "chance": 0.05 - } - ] - } + { + "type": "random", + "loot_pool": [ + { + "item_id": 15998, + "num": 1, + "chance": 0.35 + }, + { + "item_id": 15998, + "num": 2, + "chance": 0.25 + }, + { + "item_id": 15998, + "num": 4, + "chance": 0.20 + }, + { + "item_id": 15998, + "num": 12, + "chance": 0.15 + }, + { + "item_id": 15998, + "num": 16, + "chance": 0.10 + }, + { + "item_id": 15998, + "num": 20, + "chance": 0.05 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 15999, + "num": 0, + "chance": 0.35 + }, + { + "item_id": 15999, + "num": 1, + "chance": 0.20 + }, + { + "item_id": 15999, + "num": 2, + "chance": 0.25 + }, + { + "item_id": 15999, + "num": 4, + "chance": 0.20 + }, + { + "item_id": 15999, + "num": 8, + "chance": 0.15 + }, + { + "item_id": 15999, + "num": 10, + "chance": 0.05 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 16000, + "num": 0, + "chance": 0.60 + }, + { + "item_id": 16000, + "num": 1, + "chance": 0.20 + }, + { + "item_id": 16000, + "num": 2, + "chance": 0.15 + }, + { + "item_id": 16000, + "num": 3, + "chance": 0.05 + } + ] + } ], "enemy_groups" : [ { diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs index 17ceb21b4..a1adadb14 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs @@ -15,8 +15,9 @@ public QuestMissionParams() QuestPhaseGroupIdList = new List(); } - public uint SortieMinimum { get; set; } - public uint SortieMaximum { get; set; } + public ulong BoardId { get; set; } + public uint MinimumMembers { get; set; } + public uint MaximumMembers { get; set; } public uint PlaytimeInSeconds { get; set; } public bool IsSolo { get; set; } public uint MaxPawns { get; set; } diff --git a/Arrowgene.Ddon.Shared/Network/PacketId.cs b/Arrowgene.Ddon.Shared/Network/PacketId.cs index 1ab4ec614..b00861008 100644 --- a/Arrowgene.Ddon.Shared/Network/PacketId.cs +++ b/Arrowgene.Ddon.Shared/Network/PacketId.cs @@ -662,9 +662,9 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId C2S_QUEST_PLAY_ENTRY_REQ = new PacketId(11, 39, 1, "C2S_QUEST_PLAY_ENTRY_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_ENTRY_RES = new PacketId(11, 39, 2, "S2C_QUEST_PLAY_ENTRY_RES", ServerType.Game, PacketSource.Server); // プレイエントリーに public static readonly PacketId S2C_QUEST_PLAY_ENTRY_NTC = new PacketId(11, 39, 16, "S2C_QUEST_PLAY_ENTRY_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_39_16_NTC"); - public static readonly PacketId C2S_QUEST_11_40_1_REQ = new PacketId(11, 40, 1, "C2S_QUEST_11_40_1_REQ", ServerType.Game, PacketSource.Client); + public static readonly PacketId C2S_QUEST_11_40_1_REQ = new PacketId(11, 40, 1, "C2S_QUEST_11_40_1_REQ", ServerType.Game, PacketSource.Client); // PLAY_ENTRY_CANCEL? public static readonly PacketId S2C_QUEST_11_40_2_RES = new PacketId(11, 40, 2, "S2C_QUEST_11_40_2_RES", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_QUEST_11_40_16_NTC = new PacketId(11, 40, 16, "S2C_QUEST_11_40_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_QUEST_PLAY_ENTRY_CANCEL_NTC = new PacketId(11, 40, 16, "S2C_QUEST_PLAY_ENTRY_CANCEL_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_40_16_NTC"); public static readonly PacketId C2S_QUEST_PLAY_START_REQ = new PacketId(11, 41, 1, "C2S_QUEST_PLAY_START_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_START_RES = new PacketId(11, 41, 2, "S2C_QUEST_PLAY_START_RES", ServerType.Game, PacketSource.Server); // プレイスタートに public static readonly PacketId C2S_QUEST_PLAY_START_TIMER_REQ = new PacketId(11, 42, 1, "C2S_QUEST_PLAY_START_TIMER_REQ", ServerType.Game, PacketSource.Client); @@ -1558,7 +1558,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CREATE_RES = new PacketId(34, 2, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CREATE_RES", ServerType.Game, PacketSource.Server); // エントリーボードアイテム作成に public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_REQ = new PacketId(34, 3, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_RES = new PacketId(34, 3, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_RES", ServerType.Game, PacketSource.Server); // エントリーボードアイテム再作成に - public static readonly PacketId S2C_ENTRY_34_3_16_NTC = new PacketId(34, 3, 16, "S2C_ENTRY_34_3_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_NTC = new PacketId(34, 3, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_3_16_NTC"); public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_REQ = new PacketId(34, 4, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_RES = new PacketId(34, 4, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_RES", ServerType.Game, PacketSource.Server); // エントリーボードアイテム参加に public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_REQ = new PacketId(34, 5, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_REQ", ServerType.Game, PacketSource.Client); @@ -1582,7 +1582,7 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_NTC = new PacketId(34, 12, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_12_16_NTC"); public static readonly PacketId C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_REQ = new PacketId(34, 13, 1, "C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES = new PacketId(34, 13, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES", ServerType.Game, PacketSource.Server); // エントリーボード招待に - public static readonly PacketId S2C_ENTRY_34_13_16_NTC = new PacketId(34, 13, 16, "S2C_ENTRY_34_13_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_NTC = new PacketId(34, 13, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_13_16_NTC"); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC = new PacketId(34, 14, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_14_16_NTC"); public static readonly PacketId S2C_ENTRY_34_15_16_NTC = new PacketId(34, 15, 16, "S2C_ENTRY_34_15_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC = new PacketId(34, 16, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_16_16_NTC"); @@ -2593,7 +2593,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ENTRY_NTC); AddPacketIdEntry(packetIds, C2S_QUEST_11_40_1_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_11_40_2_RES); - AddPacketIdEntry(packetIds, S2C_QUEST_11_40_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ENTRY_CANCEL_NTC); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_START_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_START_RES); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_START_TIMER_REQ); @@ -3487,7 +3487,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CREATE_RES); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_RES); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_3_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RECREATE_NTC); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_ENTRY_RES); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_LEAVE_REQ); @@ -3511,7 +3511,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INFO_CHANGE_NTC); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_13_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_NTC); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC); AddPacketIdEntry(packetIds, S2C_ENTRY_34_15_16_NTC); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC); From 5425b2a0ca9163b263433c705c1635e8faab1aa5 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 15 Sep 2024 22:47:53 -0400 Subject: [PATCH 083/116] Add support for quitting mod content Added some partial support for quitting an EXM mid content. --- .../Characters/ContentManager.cs | 101 +++++++++++++++++- Arrowgene.Ddon.GameServer/DdonGameServer.cs | 1 + .../QuestPlayInterruptAnswerHandler.cs | 46 ++++++++ .../Handler/QuestPlayInterruptHandler.cs | 7 +- .../Entity/EntitySerializer.cs | 3 + .../C2SQuestPlayInterruptAnswerReq.cs | 29 +++++ .../S2CQuestPlayInterruptAnswerNtc.cs | 33 ++++++ .../S2CQuestPlayInterruptAnswerRes.cs | 31 ++++++ 8 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptAnswerHandler.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayInterruptAnswerReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerRes.cs diff --git a/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs b/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs index 6a7802c34..d02f18691 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs @@ -1,9 +1,14 @@ +using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Model; using Arrowgene.Logging; using System; using System.Collections.Generic; +using System.IO; using System.Threading; +using static Arrowgene.Ddon.GameServer.Characters.ContentManager; // using static Arrowgene.Ddon.GameServer.Characters.ExmManager; namespace Arrowgene.Ddon.GameServer.Characters @@ -13,6 +18,9 @@ public class ContentManager private DdonGameServer _Server; private Dictionary _ContentTimers; + private Dictionary _VoteTimers; + private Dictionary> _VoteStatus; + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ContentManager)); internal class TimerState @@ -26,6 +34,93 @@ public ContentManager(DdonGameServer server) { _Server = server; _ContentTimers = new Dictionary(); + _VoteTimers = new Dictionary(); + _VoteStatus = new Dictionary>(); + } + + public void InitatePartyAbandonVote(PartyGroup party, uint timeoutInSeconds) + { + lock (_VoteStatus) + { + if (_VoteStatus.ContainsKey(party.Id)) + { + Logger.Error($"(VoteToAbandon) Vote is already in progress for PartyId={party.Id}."); + return; + } + + _VoteStatus[party.Id] = new Dictionary(); + foreach (var client in party.Clients) + { + _VoteStatus[party.Id][client.Character.CharacterId] = false; + } + _VoteTimers[party.Id] = new TimerState(); + + var timerState = _VoteTimers[party.Id]; + timerState.Duration = TimeSpan.FromSeconds(timeoutInSeconds); + timerState.TimeStart = DateTime.Now; + timerState.Timer = new Timer(task => + { + TimeSpan alreadyElapsed = DateTime.Now.Subtract(timerState.TimeStart); + if (alreadyElapsed > timerState.Duration) + { + Logger.Info($"(VoteToAbandon) Timer expired for PartyId={party.Id}"); + CancelVoteToAbandonTimer(party.Id); + } + }, null, 0, 1000); + } + Logger.Info($"(VoteToAbandon) Started {timeoutInSeconds} seconds timer for PartyId={party.Id}"); + } + + public void CancelVoteToAbandonTimer(uint partyId) + { + lock (_VoteStatus) + { + _VoteTimers[partyId].Timer.Dispose(); + _VoteStatus.Remove(partyId); + _VoteTimers.Remove(partyId); + Logger.Info($"(VoteToAbandon) Canceling timer for PartyId={partyId}"); + } + } + + public void VoteToAbandon(uint characterId, uint partyId, bool shouldAbandon) + { + lock (_VoteStatus) + { + if (!_VoteStatus.ContainsKey(partyId)) + { + return; + } + + if (!_VoteStatus[partyId].ContainsKey(characterId)) + { + return; + } + + _VoteStatus[partyId][characterId] = shouldAbandon; + } + } + + public void VoteToAbandon(Character character, uint partyId, bool shouldAbandon) + { + VoteToAbandon(character.CharacterId, partyId, shouldAbandon); + } + + public bool VoteToAbandonPassed(uint partyId) + { + lock (_VoteStatus) + { + if (!_VoteStatus.ContainsKey(partyId)) + { + return false; + } + + bool shouldAbandon = true; + foreach (var (characterId, result) in _VoteStatus[partyId]) + { + shouldAbandon &= result; + } + return shouldAbandon; + } } public void StartTimer(uint partyId, GameClient client, uint playtimeInSeconds) @@ -42,7 +137,7 @@ public void StartTimer(uint partyId, GameClient client, uint playtimeInSeconds) TimeSpan alreadyElapsed = DateTime.Now.Subtract(timerState.TimeStart); if (alreadyElapsed > timerState.Duration) { - Logger.Info($"Timer expired for Id={partyId}"); + Logger.Info($"(Content) Timer expired for Id={partyId}"); client.Party.SendToAll(new S2CQuestPlayTimeupNtc()); CancelTimer(partyId); } @@ -55,7 +150,7 @@ public ulong ExtendTimer(uint partyId, uint amountInSeconds) { lock (_ContentTimers) { - Logger.Info($"Extending time by {amountInSeconds} seconds for PartyId={partyId}"); + Logger.Info($"(Content) Extending time by {amountInSeconds} seconds for PartyId={partyId}"); _ContentTimers[partyId].Duration += TimeSpan.FromSeconds(amountInSeconds); return (ulong) ((DateTimeOffset)(_ContentTimers[partyId].TimeStart + _ContentTimers[partyId].Duration)).ToUnixTimeSeconds(); } @@ -67,7 +162,7 @@ public ulong ExtendTimer(uint partyId, uint amountInSeconds) { if (_ContentTimers.ContainsKey(partyId)) { - Logger.Info($"Canceling timer for PartyId={partyId}"); + Logger.Info($"(Content) Canceling timer for PartyId={partyId}"); _ContentTimers[partyId].Timer.Dispose(); var timerState = _ContentTimers[partyId]; diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index ab36e1dc2..812b66202 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -518,6 +518,7 @@ private void LoadPacketHandler() AddHandler(new QuestPlayStartTimerHandler(this)); AddHandler(new QuestPlayEndHandler(this)); AddHandler(new QuestPlayInterruptHandler(this)); + AddHandler(new QuestPlayInterruptAnswerHandler(this)); AddHandler(new QuestGetEndContentsRecruitListHandler(this)); AddHandler(new QuestGetQuestScheduleInfoHandler(this)); diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptAnswerHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptAnswerHandler.cs new file mode 100644 index 000000000..33397ea5f --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptAnswerHandler.cs @@ -0,0 +1,46 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.GameServer.Dump; +using Arrowgene.Ddon.GameServer.Quests; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Shared.Asset; +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.Network; +using Arrowgene.Logging; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class QuestPlayInterruptAnswerHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestPlayInterruptAnswerHandler)); + + public QuestPlayInterruptAnswerHandler(DdonGameServer server) : base(server) + { + } + + public override S2CQuestPlayInterruptAnswerRes Handle(GameClient client, C2SQuestPlayInterruptAnswerReq packet) + { + Server.ContentManager.VoteToAbandon(client.Character, client.Party.Id, packet.IsAnswer); + if (Server.ContentManager.VoteToAbandonPassed(client.Party.Id)) + { + Server.ContentManager.CancelVoteToAbandonTimer(client.Party.Id); + + // TODO: There is supposed to be an NTC which + // reports the response of the cancel operation + // but I can't find it. This packet will give similar results + // which basically allows the party to quit. + Server.ContentManager.CancelTimer(client.Party.Id); + client.Party.SendToAll(new S2CQuestPlayTimeupNtc()); + } + + return new S2CQuestPlayInterruptAnswerRes(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs index b75eb2c1e..1c6d740f3 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs @@ -16,9 +16,14 @@ public QuestPlayInterruptHandler(DdonGameServer server) : base(server) public override S2CQuestPlayInterruptRes Handle(GameClient client, C2SQuestPlayInterruptReq request) { + if (client.Party.Clients.Count > 1) + { + Server.ContentManager.InitatePartyAbandonVote(client.Party, 60); + Server.ContentManager.VoteToAbandon(client.Character, client.Party.Id, true); + } + client.Party.SendToAll(new S2CQuestPlayInterruptNtc() { - CharacterId = client.Character.CharacterId, DeadlineSec = 60 }); diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index 31c905450..a17c8dc0b 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -655,6 +655,7 @@ static EntitySerializer() Create(new C2SQuestGetEndContentsRecruitListReq.Serializer()); Create(new C2SQuestPlayInterruptReq.Serializer()); Create(new C2SQuestGetQuestScheduleInfoReq.Serializer()); + Create(new C2SQuestPlayInterruptAnswerReq.Serializer()); Create(new C2SServerGameTimeGetBaseInfoReq.Serializer()); Create(new C2SServerGetRealTimeReq.Serializer()); @@ -1145,6 +1146,8 @@ static EntitySerializer() Create(new S2CQuestPlayTimeupNtc.Serializer()); Create(new S2CQuestPlayAddTimerNtc.Serializer()); Create(new S2CQuestPlayEntryCancelNtc.Serializer()); + Create(new S2CQuestPlayInterruptAnswerRes.Serializer()); + Create(new S2CQuestPlayInterruptAnswerNtc.Serializer()); Create(new S2CQuestSendLeaderQuestOrderConditionInfoNtc.Serializer()); Create(new S2CQuestSendLeaderQuestOrderConditionInfoRes.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayInterruptAnswerReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayInterruptAnswerReq.cs new file mode 100644 index 000000000..309ff774e --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayInterruptAnswerReq.cs @@ -0,0 +1,29 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; +using System; +using System.Security.Cryptography.X509Certificates; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestPlayInterruptAnswerReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_PLAY_INTERRUPT_ANSWER_REQ; + + public bool IsAnswer { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestPlayInterruptAnswerReq obj) + { + WriteBool(buffer, obj.IsAnswer); + } + + public override C2SQuestPlayInterruptAnswerReq Read(IBuffer buffer) + { + C2SQuestPlayInterruptAnswerReq obj = new C2SQuestPlayInterruptAnswerReq(); + obj.IsAnswer = ReadBool(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerNtc.cs new file mode 100644 index 000000000..e0f38fa3a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerNtc.cs @@ -0,0 +1,33 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayInterruptAnswerNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_QUEST_11_86_16_NTC; // EMPTY ID + + public S2CQuestPlayInterruptAnswerNtc() + { + } + + public bool IsInterrupt { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayInterruptAnswerNtc obj) + { + WriteBool(buffer, obj.IsInterrupt); + } + + public override S2CQuestPlayInterruptAnswerNtc Read(IBuffer buffer) + { + S2CQuestPlayInterruptAnswerNtc obj = new S2CQuestPlayInterruptAnswerNtc(); + obj.IsInterrupt = ReadBool(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerRes.cs new file mode 100644 index 000000000..b57b8edd9 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerRes.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayInterruptAnswerRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_PLAY_INTERRUPT_ANSWER_RES; + + public S2CQuestPlayInterruptAnswerRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayInterruptAnswerRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CQuestPlayInterruptAnswerRes Read(IBuffer buffer) + { + S2CQuestPlayInterruptAnswerRes obj = new S2CQuestPlayInterruptAnswerRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} From f92a5e1463632fb3e0883055aada7edcf2f6bcd1 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Sep 2024 16:54:16 -0400 Subject: [PATCH 084/116] Code Review feedback - Renamed ContentManager to PartyQuestContentManager - Removed dead code --- .../{ContentManager.cs => PartyQuestContentManager.cs} | 8 +++----- Arrowgene.Ddon.GameServer/DdonGameServer.cs | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) rename Arrowgene.Ddon.GameServer/Characters/{ContentManager.cs => PartyQuestContentManager.cs} (96%) diff --git a/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs b/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs similarity index 96% rename from Arrowgene.Ddon.GameServer/Characters/ContentManager.cs rename to Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs index d02f18691..f63c9282c 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ContentManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs @@ -8,12 +8,10 @@ using System.Collections.Generic; using System.IO; using System.Threading; -using static Arrowgene.Ddon.GameServer.Characters.ContentManager; -// using static Arrowgene.Ddon.GameServer.Characters.ExmManager; namespace Arrowgene.Ddon.GameServer.Characters { - public class ContentManager + public class PartyQuestContentManager { private DdonGameServer _Server; private Dictionary _ContentTimers; @@ -21,7 +19,7 @@ public class ContentManager private Dictionary _VoteTimers; private Dictionary> _VoteStatus; - private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ContentManager)); + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(PartyQuestContentManager)); internal class TimerState { @@ -30,7 +28,7 @@ internal class TimerState public Timer Timer { get; set; } } - public ContentManager(DdonGameServer server) + public PartyQuestContentManager(DdonGameServer server) { _Server = server; _ContentTimers = new Dictionary(); diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index 812b66202..2b96472a4 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -76,7 +76,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi HubManager = new HubManager(this); GpCourseManager = new GpCourseManager(this); WeatherManager = new WeatherManager(this); - ContentManager = new ContentManager(this); + ContentManager = new PartyQuestContentManager(this); BoardManager = new BoardManager(this); // Orb Management is slightly complex and requires updating fields across multiple systems @@ -108,7 +108,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public GameRouter Router { get; } public GpCourseManager GpCourseManager { get; } public WeatherManager WeatherManager { get; } - public ContentManager ContentManager { get; } + public PartyQuestContentManager ContentManager { get; } public BoardManager BoardManager { get; } public ChatLogHandler ChatLogHandler { get; } From 7e8fe4ed3e95d7bc9454ad44f525ee92e678e9f2 Mon Sep 17 00:00:00 2001 From: nbfields Date: Mon, 9 Sep 2024 10:20:53 -0400 Subject: [PATCH 085/116] Added support for a new PPDrop field in enemy information --- .../EnemySpawnAssetDeserializer.cs | 53 +++++++++++++------ .../Files/Assets/EnemySpawn.json | 2 +- Arrowgene.Ddon.Shared/Model/Enemy.cs | 6 +-- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs index 5a158d144..78887f608 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs @@ -10,9 +10,17 @@ namespace Arrowgene.Ddon.Shared.AssetReader { public class EnemySpawnAssetDeserializer : IAssetDeserializer { + + // Consider this a macro that may be changed for balance + // reasons. Each enemy drops PP equal to its experience points + // divided by this number, unless an explicit value is + // provided (called "PPDrop") in which case that value is used + // instead. + private static uint EXP_PER_PP = 7500; + private static readonly ILogger Logger = LogProvider.Logger(typeof(EnemySpawnAssetDeserializer)); - private static readonly string[] ENEMY_HEADERS = new string[]{"StageId", "LayerNo", "GroupId", "SubGroupId", "EnemyId", "NamedEnemyParamsId", "RaidBossId", "Scale", "Lv", "HmPresetNo", "StartThinkTblNo", "RepopNum", "RepopCount", "EnemyTargetTypesId", "MontageFixNo", "SetType", "InfectionType", "IsBossGauge", "IsBossBGM", "IsManualSet", "IsAreaBoss", "BloodOrbs", "HighOrbs", "Experience", "DropsTableId", "SpawnTime"}; + private static readonly string[] ENEMY_HEADERS = new string[]{"StageId", "LayerNo", "GroupId", "SubGroupId", "EnemyId", "NamedEnemyParamsId", "RaidBossId", "Scale", "Lv", "HmPresetNo", "StartThinkTblNo", "RepopNum", "RepopCount", "EnemyTargetTypesId", "MontageFixNo", "SetType", "InfectionType", "IsBossGauge", "IsBossBGM", "IsManualSet", "IsAreaBoss", "BloodOrbs", "HighOrbs", "Experience", "DropsTableId", "SpawnTime", "PPDrop"}; private static readonly string[] DROPS_TABLE_HEADERS = new string[]{"ItemId", "ItemNum", "MaxItemNum", "Quality", "IsHidden", "DropChance"}; private Dictionary namedParams; @@ -109,22 +117,33 @@ public EnemySpawnAsset ReadPath(string path) Subgroup = subGroupId, }; - //checking if the file has spawntime, if yes we convert the time and pass it along to enemy.cs - if (enemySchemaIndexes.ContainsKey("SpawnTime")) - { - string SpawnTimeGet = row[enemySchemaIndexes["SpawnTime"]].GetString(); - ConvertSpawnTimeToMilliseconds(SpawnTimeGet, out long start, out long end); - enemy.SpawnTimeStart = start; - enemy.SpawnTimeEnd = end; - } - else - { - // if no, we define it to the "allday" spawn time range and pass this along to the enemy.cs instead. - ConvertSpawnTimeToMilliseconds("00:00,23:59", out long start, out long end); - enemy.SpawnTimeStart = start; - enemy.SpawnTimeEnd = end; - } - + // checking if the file has spawntime, if yes we convert the time and pass it along to enemy.cs + if(enemySchemaIndexes.ContainsKey("SpawnTime")) + { + string SpawnTimeGet = row[enemySchemaIndexes["SpawnTime"]].GetString(); + ConvertSpawnTimeToMilliseconds(SpawnTimeGet, out long start, out long end); + enemy.SpawnTimeStart = start; + enemy.SpawnTimeEnd = end; + } + else + { + // if no, we define it to the "allday" spawn time range and pass this along to the enemy.cs instead. + ConvertSpawnTimeToMilliseconds("00:00,23:59", out long start, out long end); + enemy.SpawnTimeStart = start; + enemy.SpawnTimeEnd = end; + } + + // check if the file has PPDrop. + if(enemySchemaIndexes.ContainsKey("PPDrop")) + { + // If yes, get it as a uint32. + enemy.PPDrop = row[enemySchemaIndexes["PPDrop"]].GetUInt32(); + } + else + { + // If no, set the value to its experience / EXP_PER_PP. + enemy.PPDrop = enemy.Experience / EXP_PER_PP; + } int dropsTableId = row[enemySchemaIndexes["DropsTableId"]].GetInt32(); if(dropsTableId >= 0) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json index 3737869ff..536447221 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json @@ -249547,4 +249547,4 @@ "00:00,23:59" ] ] -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Shared/Model/Enemy.cs b/Arrowgene.Ddon.Shared/Model/Enemy.cs index 283cf4c5b..e1192ff48 100644 --- a/Arrowgene.Ddon.Shared/Model/Enemy.cs +++ b/Arrowgene.Ddon.Shared/Model/Enemy.cs @@ -5,8 +5,6 @@ namespace Arrowgene.Ddon.Shared.Model { public class Enemy { - private static uint EXP_PER_PP = 7500; - public Enemy() { NamedEnemyParams = NamedParam.DEFAULT_NAMED_PARAM; @@ -41,6 +39,7 @@ public Enemy(Enemy enemy) DropsTable = enemy.DropsTable; NotifyStrongEnemy = enemy.NotifyStrongEnemy; Subgroup = enemy.Subgroup; + PPDrop = enemy.PPDrop; } public uint Id { get; set; } @@ -69,6 +68,7 @@ public Enemy(Enemy enemy) public uint Experience { get; set; } public DropsTable DropsTable { get; set; } public bool NotifyStrongEnemy { get; set; } + public uint PPDrop { get; set; } public uint UINameId { get { return NameMap.GetValueOrDefault(EnemyId); @@ -83,7 +83,7 @@ public uint GetDroppedExperience() public uint GetDroppedPlayPoints() { - return GetDroppedExperience()/EXP_PER_PP; //TODO: Totally arbitrary. Figure out how to do this properly. + return PPDrop; } public CDataStageLayoutEnemyPresetEnemyInfoClient asCDataStageLayoutEnemyPresetEnemyInfoClient() From 4ebb97a4c073d8fad36cd1a3b4ba99c1a5193379 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Sep 2024 21:24:58 -0400 Subject: [PATCH 086/116] enhancement: Handle multiple group edgecases - Created a new class UniqueIdPool to help with creating a pool of reusable 32bit IDs. - Created a new class TimerManager to help with generically using timers across the code base. - Added a timeout to board recruitment. - Added a timeout to board ready up. - Added the ability to kick a member from the entry board. - Added the ability to extend the ready up time. - Enhanced the vote to abandon content mechanism such that it will approve or reject the vote as soon as all members have voted. - Found the proper NTC to end the group content. - Synced timers shown in various UI elements so that they closer match the actual timer on the server. - Refactored existing classes to use new UniqueIdPool and TimerManager classes. --- .../Characters/BoardManager.cs | 188 +++++++++++++++--- .../Characters/PartyQuestContentManager.cs | 134 +++++++------ .../Characters/TimerManager.cs | 136 +++++++++++++ Arrowgene.Ddon.GameServer/DdonGameServer.cs | 9 +- .../EntryBoardEntryBoardItemCreateHandler.cs | 5 +- .../EntryBoardEntryBoardItemEntryHandler.cs | 2 + ...BoardEntryBoardItemExtendTimeoutHandler.cs | 47 +++++ ...tryBoardEntryBoardItemForceStartHandler.cs | 8 +- .../EntryBoardEntryBoardItemReadyHandler.cs | 2 + .../Handler/EntryBoardEntryRecreateHandler.cs | 3 + .../Handler/EntryBoardItemKickHandler.cs | 56 ++++++ .../Handler/PartyPartyLeaveHandler.cs | 1 + .../Handler/QuestPlayEndHandler.cs | 2 +- .../Handler/QuestPlayEntryCancelHandler.cs | 26 +++ .../QuestPlayInterruptAnswerHandler.cs | 35 ++-- .../Handler/QuestPlayInterruptHandler.cs | 19 +- .../Handler/QuestPlayStartTimerHandler.cs | 2 +- .../Quests/GenericQuest.cs | 2 +- .../Utils/UniqueIdPool.cs | 44 ++++ .../Entity/EntitySerializer.cs | 9 + ...C2SEntryBoardEntryBoardExtendTimeoutReq.cs | 30 +++ .../C2SEntryBoardItemKickReq.cs | 34 ++++ .../C2SQuestPlayEntryCancelReq.cs | 24 +++ ...S2CEntryBoardEntryBoardExtendTimeoutRes.cs | 36 ++++ .../S2CEntryBoardEntryBoardItemLeaveNtc.cs | 6 +- .../S2CEntryBoardItemKickRes.cs | 31 +++ .../S2CEntryBoardItemPartyNtc.cs | 27 +++ .../S2CEntryBoardItemTimeoutTimerNtc.cs | 31 +++ .../S2CEntryBoardItemUnreadyNtc.cs | 27 +++ .../S2CQuestPlayEntryCancelRes.cs | 31 +++ .../S2CQuestPlayInterruptAnswerNtc.cs | 2 +- .../Model/EntryBoardLeaveType.cs | 18 ++ Arrowgene.Ddon.Shared/Model/VoteAnswer.cs | 15 ++ Arrowgene.Ddon.Shared/Network/PacketId.cs | 33 ++- 34 files changed, 931 insertions(+), 144 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Characters/TimerManager.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemExtendTimeoutHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/EntryBoardItemKickHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Handler/QuestPlayEntryCancelHandler.cs create mode 100644 Arrowgene.Ddon.GameServer/Utils/UniqueIdPool.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardExtendTimeoutReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardItemKickReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEntryCancelReq.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardExtendTimeoutRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemKickRes.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemPartyNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemTimeoutTimerNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemUnreadyNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryCancelRes.cs create mode 100644 Arrowgene.Ddon.Shared/Model/EntryBoardLeaveType.cs create mode 100644 Arrowgene.Ddon.Shared/Model/VoteAnswer.cs diff --git a/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs b/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs index d86c9a0b7..23d92514c 100644 --- a/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs @@ -1,11 +1,11 @@ +using Arrowgene.Ddon.GameServer.Utils; using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; using Arrowgene.Logging; -using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; namespace Arrowgene.Ddon.GameServer.Characters { @@ -16,11 +16,13 @@ public class BoardManager private Dictionary> _Boards; private Dictionary _Groups; private Dictionary _CharacterToEntryIdMap; - private uint EntryItemIdCounter; - private Stack _FreeEntryItemIds; + private UniqueIdPool _EntryItemIdPool; private static readonly ServerLogger Logger = LogProvider.Logger(typeof(BoardManager)); + public static readonly ushort PARTY_BOARD_TIMEOUT = 3600; + public static readonly ushort ENTRY_BOARD_READY_TIMEOUT = 120; + public class GroupData { public ulong BoardId { get; set; } @@ -31,6 +33,8 @@ public class GroupData public Dictionary MemberReadyState { get; set; } public bool IsInRecreate { get; set; } public bool ContentInProgress { get; set; } + public uint RecruitmentTimerId { get; set; } + public uint ReadyUpTimerId { get; set; } public GroupData() { @@ -55,11 +59,7 @@ public BoardManager(DdonGameServer server) _Boards = new Dictionary>(); _Groups = new Dictionary(); _CharacterToEntryIdMap = new Dictionary(); - - // Entry ID Tracking - EntryItemIdCounter = 1; - _FreeEntryItemIds = new Stack(); - _FreeEntryItemIds.Push(EntryItemIdCounter); + _EntryItemIdPool = new UniqueIdPool(1); } public GroupData CreateNewGroup(ulong boardId, CDataEntryItemParam createParam, string password, uint leaderCharacterId) @@ -71,7 +71,7 @@ public GroupData CreateNewGroup(ulong boardId, CDataEntryItemParam createParam, PartyLeaderCharacterId = leaderCharacterId, }; data.EntryItem.Param = createParam; - data.EntryItem.Id = GenerateEntryItemId(); + data.EntryItem.Id = _EntryItemIdPool.GenerateId(); // TODO: Quest Manager look up min/max @@ -133,7 +133,17 @@ public bool RemoveGroup(uint entryItemId) } } - ReclaimEntryItemId(data.EntryItem.Id); + if (data.RecruitmentTimerId != 0) + { + _Server.TimerManager.CancelTimer(data.RecruitmentTimerId); + } + + if (data.ReadyUpTimerId != 0) + { + _Server.TimerManager.CancelTimer(data.ReadyUpTimerId); + } + + _EntryItemIdPool.ReclaimId(data.EntryItem.Id); } return true; @@ -346,28 +356,160 @@ public uint GetEntryItemIdForCharacter(Character character) return GetEntryItemIdForCharacter(character.CharacterId); } - private uint GenerateEntryItemId() + public bool StartRecruitmentTimer(uint entryItemId, uint timeoutInSeconds) + { + lock (_Boards) + { + var data = GetGroupData(entryItemId); + if (data == null) + { + return false; + } + + data.RecruitmentTimerId = _Server.TimerManager.CreateTimer(timeoutInSeconds, () => + { + lock (_Boards) + { + foreach (var characterId in data.Members) + { + var memberClient = _Server.ClientLookup.GetClientByCharacterId(characterId); + if (memberClient != null) + { + memberClient.Send(new S2CEntryBoardEntryBoardItemLeaveNtc() { LeaveType = EntryBoardLeaveType.EntryBoardTimeUp }); + } + } + } + }); + + if (!_Server.TimerManager.StartTimer(data.RecruitmentTimerId)) + { + _Server.TimerManager.CancelTimer(data.RecruitmentTimerId); + return false; + } + + return true; + } + } + + public ulong GetRecruitmentTimeLeft(uint entryItemId) + { + lock (_Boards) + { + var data = GetGroupData(entryItemId); + if (data == null) + { + return 0; + } + return _Server.TimerManager.GetTimeLeftInSeconds(data.RecruitmentTimerId); + } + } + + public bool CancelRecruitmentTimer(uint entryItemId) + { + lock (_Boards) + { + var data = GetGroupData(entryItemId); + if (data == null) + { + return false; + } + + _Server.TimerManager.CancelTimer(data.RecruitmentTimerId); + data.RecruitmentTimerId = 0; + return true; + } + } + + public bool StartReadyUpTimer(uint entryItemId, uint timeoutInSeconds) { - lock (_FreeEntryItemIds) + lock (_Boards) { - if (_FreeEntryItemIds.Count == 0) + var data = GetGroupData(entryItemId); + if (data == null) { - EntryItemIdCounter = EntryItemIdCounter + 1; - _FreeEntryItemIds.Push(EntryItemIdCounter); + return false; } - var entryItemId = _FreeEntryItemIds.Pop(); - Logger.Info($"Allocating EntryId={entryItemId}"); - return entryItemId; + data.ReadyUpTimerId = _Server.TimerManager.CreateTimer(timeoutInSeconds, () => + { + lock (_Boards) + { + foreach (var characterId in data.Members) + { + var memberClient = _Server.ClientLookup.GetClientByCharacterId(characterId); + if (memberClient != null) + { + memberClient.Send(new S2CEntryBoardItemUnreadyNtc()); + } + } + + // Restart the recruitment timer + data.EntryItem.TimeOut = 3600; + StartRecruitmentTimer(data.EntryItem.Id, 3600); + foreach (var characterId in data.Members) + { + var memberClient = _Server.ClientLookup.GetClientByCharacterId(characterId); + if (memberClient != null) + { + memberClient.Send(new S2CEntryBoardItemTimeoutTimerNtc() { TimeOut = 3600}); + } + } + } + }); + + if (!_Server.TimerManager.StartTimer(data.ReadyUpTimerId)) + { + _Server.TimerManager.CancelTimer(data.ReadyUpTimerId); + return false; + } + + return true; } } - private void ReclaimEntryItemId(uint entryItemId) + public bool CancelReadyUpTimer(uint entryItemId) { - lock (_FreeEntryItemIds) + lock (_Boards) { - Logger.Info($"Reclaiming EntryId={entryItemId}"); - _FreeEntryItemIds.Push(entryItemId); + var data = GetGroupData(entryItemId); + if (data == null) + { + return false; + } + + _Server.TimerManager.CancelTimer(data.ReadyUpTimerId); + data.ReadyUpTimerId = 0; + return true; + } + } + + public bool ExtendReadyUpTimer(uint entryItemId) + { + lock (_Boards) + { + var data = GetGroupData(entryItemId); + if (data == null) + { + return false; + } + + // UI only allows the count down to start from 120 + var ellapsedTime = BoardManager.ENTRY_BOARD_READY_TIMEOUT - _Server.TimerManager.GetTimeLeftInSeconds(data.ReadyUpTimerId); + _Server.TimerManager.ExtendTimer(data.ReadyUpTimerId, (uint)ellapsedTime); + return true; + } + } + + public ushort GetTimeLeftToReadyUp(uint entryItemId) + { + lock (_Boards) + { + var data = GetGroupData(entryItemId); + if (data == null) + { + return 0; + } + return (ushort) _Server.TimerManager.GetTimeLeftInSeconds(data.ReadyUpTimerId); } } } diff --git a/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs b/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs index f63c9282c..056856648 100644 --- a/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs @@ -14,29 +14,22 @@ namespace Arrowgene.Ddon.GameServer.Characters public class PartyQuestContentManager { private DdonGameServer _Server; - private Dictionary _ContentTimers; + private Dictionary _ContentTimers; - private Dictionary _VoteTimers; - private Dictionary> _VoteStatus; + private Dictionary _VoteTimers; + private Dictionary> _VoteStatus; private static readonly ServerLogger Logger = LogProvider.Logger(typeof(PartyQuestContentManager)); - internal class TimerState - { - public DateTime TimeStart { get; set; } - public TimeSpan Duration { get; set; } - public Timer Timer { get; set; } - } - public PartyQuestContentManager(DdonGameServer server) { _Server = server; - _ContentTimers = new Dictionary(); - _VoteTimers = new Dictionary(); - _VoteStatus = new Dictionary>(); + _VoteStatus = new Dictionary>(); + _VoteTimers = new Dictionary(); + _ContentTimers = new Dictionary(); } - public void InitatePartyAbandonVote(PartyGroup party, uint timeoutInSeconds) + public void InitatePartyAbandonVote(GameClient client, PartyGroup party, uint timeoutInSeconds) { lock (_VoteStatus) { @@ -46,41 +39,56 @@ public void InitatePartyAbandonVote(PartyGroup party, uint timeoutInSeconds) return; } - _VoteStatus[party.Id] = new Dictionary(); - foreach (var client in party.Clients) + _VoteStatus[party.Id] = new Dictionary(); + foreach (var memberClient in party.Clients) { - _VoteStatus[party.Id][client.Character.CharacterId] = false; + _VoteStatus[party.Id][memberClient.Character.CharacterId] = VoteAnswer.Undecided; } - _VoteTimers[party.Id] = new TimerState(); - var timerState = _VoteTimers[party.Id]; - timerState.Duration = TimeSpan.FromSeconds(timeoutInSeconds); - timerState.TimeStart = DateTime.Now; - timerState.Timer = new Timer(task => + _VoteTimers[party.Id] = _Server.TimerManager.CreateTimer(timeoutInSeconds, () => { - TimeSpan alreadyElapsed = DateTime.Now.Subtract(timerState.TimeStart); - if (alreadyElapsed > timerState.Duration) - { - Logger.Info($"(VoteToAbandon) Timer expired for PartyId={party.Id}"); - CancelVoteToAbandonTimer(party.Id); - } - }, null, 0, 1000); + Logger.Info($"(VoteToAbandon) Timer expired for PartyId={party.Id}"); + client.Party.SendToAll(new S2CQuestPlayInterruptAnswerNtc() { IsInterrupt = false }); + CancelVoteToAbandonTimer(party.Id); + }); + _Server.TimerManager.StartTimer(_VoteTimers[party.Id]); } Logger.Info($"(VoteToAbandon) Started {timeoutInSeconds} seconds timer for PartyId={party.Id}"); } + public void RemovePartyMember(uint partyId, uint characterId) + { + lock (_VoteStatus) + { + if (!_VoteStatus.ContainsKey(partyId)) + { + return; + } + + if (_VoteStatus[partyId].ContainsKey(characterId)) + { + _VoteStatus[partyId].Remove(characterId); + } + } + } + + public void RemovePartyMember(uint partyId, Character character) + { + RemovePartyMember(partyId, character.CharacterId); + } + public void CancelVoteToAbandonTimer(uint partyId) { lock (_VoteStatus) { - _VoteTimers[partyId].Timer.Dispose(); + _Server.TimerManager.CancelTimer(_VoteTimers[partyId]); _VoteStatus.Remove(partyId); _VoteTimers.Remove(partyId); Logger.Info($"(VoteToAbandon) Canceling timer for PartyId={partyId}"); } } - public void VoteToAbandon(uint characterId, uint partyId, bool shouldAbandon) + public void VoteToAbandon(uint characterId, uint partyId, VoteAnswer answer) { lock (_VoteStatus) { @@ -94,16 +102,16 @@ public void VoteToAbandon(uint characterId, uint partyId, bool shouldAbandon) return; } - _VoteStatus[partyId][characterId] = shouldAbandon; + _VoteStatus[partyId][characterId] = answer; } } - public void VoteToAbandon(Character character, uint partyId, bool shouldAbandon) + public void VoteToAbandon(Character character, uint partyId, VoteAnswer answer) { - VoteToAbandon(character.CharacterId, partyId, shouldAbandon); + VoteToAbandon(character.CharacterId, partyId, answer); } - public bool VoteToAbandonPassed(uint partyId) + public bool AllMembersVoted(uint partyId) { lock (_VoteStatus) { @@ -112,12 +120,30 @@ public bool VoteToAbandonPassed(uint partyId) return false; } - bool shouldAbandon = true; - foreach (var (characterId, result) in _VoteStatus[partyId]) + bool allMembersVoted = true; + foreach (var (characterId, answer) in _VoteStatus[partyId]) { - shouldAbandon &= result; + allMembersVoted &= answer != VoteAnswer.Undecided; } - return shouldAbandon; + return allMembersVoted; + } + } + + public bool VotedPassed(uint partyId) + { + lock (_VoteStatus) + { + if (!_VoteStatus.ContainsKey(partyId)) + { + return false; + } + + bool allMembersVoted = true; + foreach (var (characterId, answer) in _VoteStatus[partyId]) + { + allMembersVoted &= (answer == VoteAnswer.Agree); + } + return allMembersVoted; } } @@ -125,21 +151,13 @@ public void StartTimer(uint partyId, GameClient client, uint playtimeInSeconds) { lock (_ContentTimers) { - _ContentTimers[partyId] = new TimerState(); - - var timerState = _ContentTimers[partyId]; - timerState.Duration = TimeSpan.FromSeconds(playtimeInSeconds); - timerState.TimeStart = DateTime.Now; - timerState.Timer = new Timer(task => + _ContentTimers[partyId] = _Server.TimerManager.CreateTimer(playtimeInSeconds, () => { - TimeSpan alreadyElapsed = DateTime.Now.Subtract(timerState.TimeStart); - if (alreadyElapsed > timerState.Duration) - { - Logger.Info($"(Content) Timer expired for Id={partyId}"); - client.Party.SendToAll(new S2CQuestPlayTimeupNtc()); - CancelTimer(partyId); - } - }, null, 0, 1000); + Logger.Info($"(Content) Timer expired for Id={partyId}"); + client.Party.SendToAll(new S2CQuestPlayTimeupNtc()); + CancelTimer(partyId); + }); + _Server.TimerManager.StartTimer(_ContentTimers[partyId]); Logger.Info($"Starting {playtimeInSeconds} second timer for PartyId={partyId}"); } } @@ -149,8 +167,7 @@ public ulong ExtendTimer(uint partyId, uint amountInSeconds) lock (_ContentTimers) { Logger.Info($"(Content) Extending time by {amountInSeconds} seconds for PartyId={partyId}"); - _ContentTimers[partyId].Duration += TimeSpan.FromSeconds(amountInSeconds); - return (ulong) ((DateTimeOffset)(_ContentTimers[partyId].TimeStart + _ContentTimers[partyId].Duration)).ToUnixTimeSeconds(); + return _Server.TimerManager.ExtendTimer(_ContentTimers[partyId], amountInSeconds); } } @@ -161,12 +178,7 @@ public ulong ExtendTimer(uint partyId, uint amountInSeconds) if (_ContentTimers.ContainsKey(partyId)) { Logger.Info($"(Content) Canceling timer for PartyId={partyId}"); - _ContentTimers[partyId].Timer.Dispose(); - - var timerState = _ContentTimers[partyId]; - - TimeSpan elapsed = DateTime.Now.Subtract(timerState.TimeStart); - var results = (elapsed, timerState.Duration); + var results = _Server.TimerManager.CancelTimer(_ContentTimers[partyId]); _ContentTimers.Remove(partyId); return results; } diff --git a/Arrowgene.Ddon.GameServer/Characters/TimerManager.cs b/Arrowgene.Ddon.GameServer/Characters/TimerManager.cs new file mode 100644 index 000000000..5bd41dcd5 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Characters/TimerManager.cs @@ -0,0 +1,136 @@ +using Arrowgene.Ddon.GameServer.Utils; +using Arrowgene.Ddon.Server; +using Arrowgene.Logging; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading; + +namespace Arrowgene.Ddon.GameServer.Characters +{ + public class TimerManager + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(TimerManager)); + + private DdonGameServer _Server; + private Dictionary _Timers; + private UniqueIdPool _IdPool; + + public TimerManager(DdonGameServer server) + { + _Server = server; + _Timers = new Dictionary(); + _IdPool = new UniqueIdPool(1); + } + + internal class TimerState + { + public DateTime TimeStart { get; set; } + public TimeSpan Duration { get; set; } + public Timer Timer { get; set; } + public Action Action { get; set; } + public bool TimerStarted { get; set; } + } + + public uint CreateTimer(uint timeoutInSeconds, Action action) + { + uint timerId = _IdPool.GenerateId(); + lock (_Timers) + { + if (_Timers.ContainsKey(timerId)) + { + throw new Exception($"TimerId={timerId} already has state associated with it. Unable to allocate additional state."); + } + + _Timers[timerId] = new TimerState() + { + Action = action, + Duration = TimeSpan.FromSeconds(timeoutInSeconds) + }; + } + + return timerId; + } + + public bool StartTimer(uint timerId) + { + lock (_Timers) + { + if (!_Timers.ContainsKey(timerId)) + { + return false; + } + + var timerState = _Timers[timerId]; + if (timerState.TimerStarted) + { + return false; + } + + timerState.TimeStart = DateTime.Now; + timerState.Timer = new Timer(task => + { + TimeSpan alreadyElapsed = DateTime.Now.Subtract(timerState.TimeStart); + if (alreadyElapsed > timerState.Duration) + { + Logger.Info($"TimerId={timerId} expired."); + if (timerState.Action != null) + { + timerState.Action.Invoke(); + } + CancelTimer(timerId); + } + }, null, 0, 1000); + Logger.Info($"Starting {timerState.Duration.TotalSeconds} second timer for TimerId={timerId}"); + } + + return true; + } + + public ulong ExtendTimer(uint timerId, uint amountInSeconds) + { + lock (_Timers) + { + Logger.Info($"Extending timer by {amountInSeconds} seconds for TimerId={timerId}"); + _Timers[timerId].Duration += TimeSpan.FromSeconds(amountInSeconds); + return (ulong)((DateTimeOffset)(_Timers[timerId].TimeStart + _Timers[timerId].Duration)).ToUnixTimeSeconds(); + } + } + + public ulong GetTimeLeftInSeconds(uint timerId) + { + lock (_Timers) + { + if (!_Timers.ContainsKey(timerId)) + { + return 0; + } + + var timeLeft = (_Timers[timerId].Duration - (DateTime.Now.Subtract(_Timers[timerId].TimeStart))).TotalSeconds; + + return (ulong) ((timeLeft < 0) ? 0 : timeLeft); + } + } + + public (TimeSpan Elapsed, TimeSpan MaximumDuration) CancelTimer(uint timerId) + { + lock (_Timers) + { + if (_Timers.ContainsKey(timerId)) + { + Logger.Info($"Canceling timer for TimerId={timerId}"); + _Timers[timerId].Timer.Dispose(); + + var timerState = _Timers[timerId]; + + TimeSpan elapsed = DateTime.Now.Subtract(timerState.TimeStart); + var results = (elapsed, timerState.Duration); + _Timers.Remove(timerId); + _IdPool.ReclaimId(timerId); + return results; + } + } + return (TimeSpan.Zero, TimeSpan.Zero); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index 2b96472a4..8b75504ca 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -76,8 +76,9 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi HubManager = new HubManager(this); GpCourseManager = new GpCourseManager(this); WeatherManager = new WeatherManager(this); - ContentManager = new PartyQuestContentManager(this); + PartyQuestContentManager = new PartyQuestContentManager(this); BoardManager = new BoardManager(this); + TimerManager = new TimerManager(this); // Orb Management is slightly complex and requires updating fields across multiple systems OrbUnlockManager = new OrbUnlockManager(database, WalletManager, JobManager, CharacterManager); @@ -108,8 +109,9 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public GameRouter Router { get; } public GpCourseManager GpCourseManager { get; } public WeatherManager WeatherManager { get; } - public PartyQuestContentManager ContentManager { get; } + public PartyQuestContentManager PartyQuestContentManager { get; } public BoardManager BoardManager { get; } + public TimerManager TimerManager { get; } public ChatLogHandler ChatLogHandler { get; } @@ -514,6 +516,7 @@ private void LoadPacketHandler() AddHandler(new QuestGetPartyBonusListHandler(this)); AddHandler(new QuestGetMobHuntQuestListHandler(this)); AddHandler(new QuestPlayEntryHandler(this)); + AddHandler(new QuestPlayEntryCancelHandler(this)); AddHandler(new QuestPlayStartHandler(this)); AddHandler(new QuestPlayStartTimerHandler(this)); AddHandler(new QuestPlayEndHandler(this)); @@ -534,6 +537,8 @@ private void LoadPacketHandler() AddHandler(new EntryBoardEntryBoardItemEntryHandler(this)); AddHandler(new EntryBoardEntryBoardItemListHandler(this)); AddHandler(new EntryBoardEntryRecreateHandler(this)); + AddHandler(new EntryBoardItemKickHandler(this)); + AddHandler(new EntryBoardEntryBoardItemExtendTimeoutHandler(this)); AddHandler(new ServerGameTimeGetBaseinfoHandler(this)); AddHandler(new ServerGetGameSettingHandler(this)); diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs index dcc785b3a..8212d60e0 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs @@ -35,7 +35,7 @@ public override S2CEntryBoardEntryBoardItemCreateRes Handle(GameClient client, C } data.EntryItem.PartyLeaderCharacterId = data.PartyLeaderCharacterId; - data.EntryItem.TimeOut = 3600; // TODO: Start a timer for 3600 + data.EntryItem.TimeOut = BoardManager.PARTY_BOARD_TIMEOUT; var member = new CDataEntryMemberData() { @@ -61,6 +61,7 @@ public override S2CEntryBoardEntryBoardItemCreateRes Handle(GameClient client, C }; client.Send(ntc); + Server.BoardManager.StartRecruitmentTimer(data.EntryItem.Id, BoardManager.PARTY_BOARD_TIMEOUT); Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.EntryBoard); return new S2CEntryBoardEntryBoardItemCreateRes() @@ -69,5 +70,5 @@ public override S2CEntryBoardEntryBoardItemCreateRes Handle(GameClient client, C EntryItem = data.EntryItem }; } - } + } } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemEntryHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemEntryHandler.cs index 2217ddc18..5e503bb85 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemEntryHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemEntryHandler.cs @@ -65,6 +65,8 @@ public override S2CEntryBoardEntryBoardItemEntryRes Handle(GameClient client, C2 Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.EntryBoard); + data.EntryItem.TimeOut = (ushort)Server.BoardManager.GetRecruitmentTimeLeft(data.EntryItem.Id); + return new S2CEntryBoardEntryBoardItemEntryRes() { EntryItem = data.EntryItem diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemExtendTimeoutHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemExtendTimeoutHandler.cs new file mode 100644 index 000000000..6455734a9 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemExtendTimeoutHandler.cs @@ -0,0 +1,47 @@ +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Entity.PacketStructure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Logging; + +namespace Arrowgene.Ddon.GameServer.Handler +{ + public class EntryBoardEntryBoardItemExtendTimeoutHandler : GameRequestPacketHandler + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemExtendTimeoutHandler)); + + public EntryBoardEntryBoardItemExtendTimeoutHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardEntryBoardExtendTimeoutRes Handle(GameClient client, C2SEntryBoardEntryBoardExtendTimeoutReq request) + { + // var pcap = new S2CEntryBoardEntryBoardItemForceStartRes.Serializer().Read(GameFull.Dump_711.AsBuffer()); + var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); + + if (!Server.BoardManager.ExtendReadyUpTimer(data.EntryItem.Id)) + { + return new S2CEntryBoardEntryBoardExtendTimeoutRes() + { + Error = (uint) ErrorCode.ERROR_CODE_TIMER_INTERNAL_ERROR + }; + } + + foreach (var characterId in data.Members) + { + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + memberClient.Send(new S2CEntryBoardEntryBoardItemReadyNtc() + { + MaxMember = data.EntryItem.Param.MaxEntryNum, + TimeOut = Server.BoardManager.GetTimeLeftToReadyUp(data.EntryItem.Id), + }); + memberClient.Send(new S2CEntryBoardItemTimeoutTimerNtc() { TimeOut = Server.BoardManager.GetTimeLeftToReadyUp(data.EntryItem.Id) }); + } + + return new S2CEntryBoardEntryBoardExtendTimeoutRes() + { + TimeOut = Server.BoardManager.GetTimeLeftToReadyUp(data.EntryItem.Id) + }; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs index 07af9686a..7426dc4c3 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs @@ -1,4 +1,5 @@ using Arrowgene.Buffers; +using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Dump; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; @@ -6,6 +7,7 @@ using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; using Arrowgene.Networking.Tcp.Consumer.BlockingQueueConsumption; +using System.Threading; namespace Arrowgene.Ddon.GameServer.Handler { @@ -23,6 +25,7 @@ public override S2CEntryBoardEntryBoardItemForceStartRes Handle(GameClient clien var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); + Server.BoardManager.CancelRecruitmentTimer(data.EntryItem.Id); foreach (var characterId in data.Members) { var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); @@ -30,12 +33,13 @@ public override S2CEntryBoardEntryBoardItemForceStartRes Handle(GameClient clien var ntc = new S2CEntryBoardEntryBoardItemReadyNtc() { MaxMember = data.EntryItem.Param.MaxEntryNum, - TimeOut = 120 + TimeOut = BoardManager.ENTRY_BOARD_READY_TIMEOUT, }; memberClient.Send(ntc); + memberClient.Send(new S2CEntryBoardItemTimeoutTimerNtc() {TimeOut = BoardManager.ENTRY_BOARD_READY_TIMEOUT }); } - // TODO: Start a timer for 120 seconds + Server.BoardManager.StartReadyUpTimer(data.EntryItem.Id, BoardManager.ENTRY_BOARD_READY_TIMEOUT); return new S2CEntryBoardEntryBoardItemForceStartRes(); } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs index 4ce76bcd2..288360248 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs @@ -37,6 +37,8 @@ public override void Handle(GameClient client, StructurePacket + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardItemKickHandler)); + + public EntryBoardItemKickHandler(DdonGameServer server) : base(server) + { + } + + public override S2CEntryBoardItemKickRes Handle(GameClient client, C2SEntryBoardItemKickReq request) + { + var data = Server.BoardManager.RecreateGroup(client.Character); + + var kickedMemberClient = Server.ClientLookup.GetClientByCharacterId(request.CharacterId); + + CDataEntryMemberData memberData; + lock (data) + { + memberData = data.EntryItem.EntryMemberList.Where(x => x.CharacterListElement.CommunityCharacterBaseInfo.CharacterId == kickedMemberClient.Character.CharacterId).First(); + memberData.EntryFlag = false; + memberData.CharacterListElement = new CDataCharacterListElement(); + } + + S2CEntryBoardEntryBoardItemChangeMemberNtc ntc = new S2CEntryBoardEntryBoardItemChangeMemberNtc() + { + EntryFlag = false, + MemberData = memberData, + }; + + foreach (var characterId in data.Members) + { + var memberClient = Server.ClientLookup.GetClientByCharacterId(characterId); + if (memberClient != null) + { + memberClient.Send(ntc); + } + } + + kickedMemberClient.Send(new S2CEntryBoardEntryBoardItemLeaveNtc()); + + Server.BoardManager.RemoveCharacterFromGroup(kickedMemberClient.Character); + Server.CharacterManager.UpdateOnlineStatus(kickedMemberClient, kickedMemberClient.Character, OnlineStatus.Online); + + return new S2CEntryBoardItemKickRes(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/PartyPartyLeaveHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PartyPartyLeaveHandler.cs index e5501a6c5..7388993bf 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PartyPartyLeaveHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PartyPartyLeaveHandler.cs @@ -35,6 +35,7 @@ public override void Handle(GameClient client, StructurePacket + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestPlayEntryCancelHandler)); + + public QuestPlayEntryCancelHandler(DdonGameServer server) : base(server) + { + } + + public override S2CQuestPlayEntryCancelRes Handle(GameClient client, C2SQuestPlayEntryCancelReq request) + { + var ntc = new S2CQuestPlayEntryCancelNtc() + { + CharacterId = client.Character.CharacterId + }; + client.Party.SendToAll(ntc); + + return new S2CQuestPlayEntryCancelRes(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptAnswerHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptAnswerHandler.cs index 33397ea5f..2e0c1af53 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptAnswerHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptAnswerHandler.cs @@ -1,19 +1,7 @@ -using Arrowgene.Buffers; -using Arrowgene.Ddon.GameServer.Characters; -using Arrowgene.Ddon.GameServer.Dump; -using Arrowgene.Ddon.GameServer.Quests; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; -using Arrowgene.Ddon.Shared.Asset; -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.Network; using Arrowgene.Logging; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; namespace Arrowgene.Ddon.GameServer.Handler { @@ -27,17 +15,20 @@ public QuestPlayInterruptAnswerHandler(DdonGameServer server) : base(server) public override S2CQuestPlayInterruptAnswerRes Handle(GameClient client, C2SQuestPlayInterruptAnswerReq packet) { - Server.ContentManager.VoteToAbandon(client.Character, client.Party.Id, packet.IsAnswer); - if (Server.ContentManager.VoteToAbandonPassed(client.Party.Id)) + Server.PartyQuestContentManager.VoteToAbandon(client.Character, client.Party.Id, packet.IsAnswer ? VoteAnswer.Agree : VoteAnswer.Disagree); + if (Server.PartyQuestContentManager.AllMembersVoted(client.Party.Id)) { - Server.ContentManager.CancelVoteToAbandonTimer(client.Party.Id); - - // TODO: There is supposed to be an NTC which - // reports the response of the cancel operation - // but I can't find it. This packet will give similar results - // which basically allows the party to quit. - Server.ContentManager.CancelTimer(client.Party.Id); - client.Party.SendToAll(new S2CQuestPlayTimeupNtc()); + if (Server.PartyQuestContentManager.VotedPassed(client.Party.Id)) + { + Server.PartyQuestContentManager.CancelVoteToAbandonTimer(client.Party.Id); + Server.PartyQuestContentManager.CancelTimer(client.Party.Id); + client.Party.SendToAll(new S2CQuestPlayInterruptAnswerNtc() { IsInterrupt = true }); + } + else + { + Server.PartyQuestContentManager.CancelVoteToAbandonTimer(client.Party.Id); + client.Party.SendToAll(new S2CQuestPlayInterruptAnswerNtc() { IsInterrupt = false }); + } } return new S2CQuestPlayInterruptAnswerRes(); diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs index 1c6d740f3..d9b14969f 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayInterruptHandler.cs @@ -1,7 +1,6 @@ using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; -using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Ddon.Shared.Model; using Arrowgene.Logging; namespace Arrowgene.Ddon.GameServer.Handler @@ -18,14 +17,18 @@ public override S2CQuestPlayInterruptRes Handle(GameClient client, C2SQuestPlayI { if (client.Party.Clients.Count > 1) { - Server.ContentManager.InitatePartyAbandonVote(client.Party, 60); - Server.ContentManager.VoteToAbandon(client.Character, client.Party.Id, true); + Server.PartyQuestContentManager.InitatePartyAbandonVote(client, client.Party, 60); + Server.PartyQuestContentManager.VoteToAbandon(client.Character, client.Party.Id, VoteAnswer.Agree); + client.Party.SendToAllExcept(new S2CQuestPlayInterruptNtc() + { + CharacterId = client.Character.CharacterId, + DeadlineSec = 60 + }, client); } - - client.Party.SendToAll(new S2CQuestPlayInterruptNtc() + else { - DeadlineSec = 60 - }); + client.Party.SendToAll(new S2CQuestPlayInterruptAnswerNtc() { IsInterrupt = true }); + } return new S2CQuestPlayInterruptRes() { diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs index 75e1e6320..e20ba9a70 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartTimerHandler.cs @@ -32,7 +32,7 @@ public override S2CQuestPlayStartTimerRes Handle(GameClient client, C2SQuestPlay }; client.Party.SendToAll(ntc); - Server.ContentManager.StartTimer(client.Party.Id, client, quest.MissionParams.PlaytimeInSeconds); + Server.PartyQuestContentManager.StartTimer(client.Party.Id, client, quest.MissionParams.PlaytimeInSeconds); return new S2CQuestPlayStartTimerRes(); } diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index 0eaa04270..bcb8cf81d 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -196,7 +196,7 @@ public override List StateMachineExecute(DdonGameServer if (questBlock.BlockType == QuestBlockType.ExtendTime && client.Party.ContentId != 0) { - var newEndTime = server.ContentManager.ExtendTimer(client.Party.Id, questBlock.TimeAmount); + var newEndTime = server.PartyQuestContentManager.ExtendTimer(client.Party.Id, questBlock.TimeAmount); client.Party.SendToAll(new S2CQuestPlayAddTimerNtc() { PlayEndDateTime = newEndTime }); } diff --git a/Arrowgene.Ddon.GameServer/Utils/UniqueIdPool.cs b/Arrowgene.Ddon.GameServer/Utils/UniqueIdPool.cs new file mode 100644 index 000000000..0c28bb676 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Utils/UniqueIdPool.cs @@ -0,0 +1,44 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Logging; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.GameServer.Utils +{ + // If using dotnet 7+ we can use instead: + // where T : IBinaryInteger so the pool can be all numeric types + public class UniqueIdPool + { + private uint Counter; + private Stack FreeIdStack; + + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(UniqueIdPool)); + + public UniqueIdPool(uint defaultValue = 1) + { + Counter = defaultValue; + FreeIdStack = new Stack(); + FreeIdStack.Push(Counter); + } + + public uint GenerateId() + { + lock (FreeIdStack) + { + if (FreeIdStack.Count == 0) + { + Counter = Counter + 1; + FreeIdStack.Push(Counter); + } + return FreeIdStack.Pop(); + } + } + + public void ReclaimId(uint id) + { + lock (FreeIdStack) + { + FreeIdStack.Push(id); + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index a17c8dc0b..cb18d4761 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -485,6 +485,8 @@ static EntitySerializer() Create(new C2SEntryBoardEntryBoardItemEntryReq.Serializer()); Create(new C2SEntryBoardEntryBoardItemListReq.Serializer()); Create(new C2SEntryBoardEntryBoardItemRecreateReq.Serializer()); + Create(new C2SEntryBoardItemKickReq.Serializer()); + Create(new C2SEntryBoardEntryBoardExtendTimeoutReq.Serializer()); Create(new C2SAchievementReceivableRewardNtc.Serializer()); Create(new C2SAchievementCompleteNtc.Serializer()); @@ -656,6 +658,7 @@ static EntitySerializer() Create(new C2SQuestPlayInterruptReq.Serializer()); Create(new C2SQuestGetQuestScheduleInfoReq.Serializer()); Create(new C2SQuestPlayInterruptAnswerReq.Serializer()); + Create(new C2SQuestPlayEntryCancelReq.Serializer()); Create(new C2SServerGameTimeGetBaseInfoReq.Serializer()); Create(new C2SServerGetRealTimeReq.Serializer()); @@ -892,6 +895,11 @@ static EntitySerializer() Create(new S2CEntryBoardEntryBoardItemListRes.Serializer()); Create(new S2CEntryBoardEntryBoardItemRecreateRes.Serializer()); Create(new S2CEntryBoardEntryBoardItemRecreateNtc.Serializer()); + Create(new S2CEntryBoardItemUnreadyNtc.Serializer()); + Create(new S2CEntryBoardItemTimeoutTimerNtc.Serializer()); + Create(new S2CEntryBoardItemPartyNtc.Serializer()); + Create(new S2CEntryBoardItemKickRes.Serializer()); + Create(new S2CEntryBoardEntryBoardExtendTimeoutRes.Serializer()); Create(new S2CEquipChangeCharacterEquipJobItemNtc.Serializer()); Create(new S2CEquipChangeCharacterEquipJobItemRes.Serializer()); @@ -1145,6 +1153,7 @@ static EntitySerializer() Create(new S2CQuestPlayInterruptNtc.Serializer()); Create(new S2CQuestPlayTimeupNtc.Serializer()); Create(new S2CQuestPlayAddTimerNtc.Serializer()); + Create(new S2CQuestPlayEntryCancelRes.Serializer()); Create(new S2CQuestPlayEntryCancelNtc.Serializer()); Create(new S2CQuestPlayInterruptAnswerRes.Serializer()); Create(new S2CQuestPlayInterruptAnswerNtc.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardExtendTimeoutReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardExtendTimeoutReq.cs new file mode 100644 index 000000000..32f51b1fa --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardExtendTimeoutReq.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardEntryBoardExtendTimeoutReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_EXTEND_TIMEOUT_REQ; + + public C2SEntryBoardEntryBoardExtendTimeoutReq() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardExtendTimeoutReq obj) + { + } + + public override C2SEntryBoardEntryBoardExtendTimeoutReq Read(IBuffer buffer) + { + C2SEntryBoardEntryBoardExtendTimeoutReq obj = new C2SEntryBoardEntryBoardExtendTimeoutReq(); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardItemKickReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardItemKickReq.cs new file mode 100644 index 000000000..65bb27b95 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardItemKickReq.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SEntryBoardItemKickReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_ENTRY_BOARD_ITEM_KICK_REQ; + + public C2SEntryBoardItemKickReq() + { + } + + public uint CharacterId { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SEntryBoardItemKickReq obj) + { + WriteUInt32(buffer, obj.CharacterId); + } + + public override C2SEntryBoardItemKickReq Read(IBuffer buffer) + { + C2SEntryBoardItemKickReq obj = new C2SEntryBoardItemKickReq(); + obj.CharacterId = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEntryCancelReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEntryCancelReq.cs new file mode 100644 index 000000000..8b257d6ab --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SQuestPlayEntryCancelReq.cs @@ -0,0 +1,24 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; +using System; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class C2SQuestPlayEntryCancelReq : IPacketStructure + { + public PacketId Id => PacketId.C2S_QUEST_PLAY_ENTRY_CANCEL_REQ; + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, C2SQuestPlayEntryCancelReq obj) + { + } + + public override C2SQuestPlayEntryCancelReq Read(IBuffer buffer) + { + C2SQuestPlayEntryCancelReq obj = new C2SQuestPlayEntryCancelReq(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardExtendTimeoutRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardExtendTimeoutRes.cs new file mode 100644 index 000000000..d79803822 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardExtendTimeoutRes.cs @@ -0,0 +1,36 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardEntryBoardExtendTimeoutRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_EXTEND_TIMEOUT_RES; + + public S2CEntryBoardEntryBoardExtendTimeoutRes() + { + } + + public ushort TimeOut { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardExtendTimeoutRes obj) + { + WriteServerResponse(buffer, obj); + WriteUInt16(buffer, obj.TimeOut); + } + + public override S2CEntryBoardEntryBoardExtendTimeoutRes Read(IBuffer buffer) + { + S2CEntryBoardEntryBoardExtendTimeoutRes obj = new S2CEntryBoardEntryBoardExtendTimeoutRes(); + ReadServerResponse(buffer, obj); + obj.TimeOut = ReadUInt16(buffer); + return obj; + } + } + } +} + diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveNtc.cs index db7b2c244..d4ef3415f 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveNtc.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemLeaveNtc.cs @@ -14,19 +14,19 @@ public S2CEntryBoardEntryBoardItemLeaveNtc() { } - public uint LeaveType { get; set; } + public EntryBoardLeaveType LeaveType { get; set; } public class Serializer : PacketEntitySerializer { public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemLeaveNtc obj) { - WriteUInt32(buffer, obj.LeaveType); + WriteUInt32(buffer, (uint) obj.LeaveType); } public override S2CEntryBoardEntryBoardItemLeaveNtc Read(IBuffer buffer) { S2CEntryBoardEntryBoardItemLeaveNtc obj = new S2CEntryBoardEntryBoardItemLeaveNtc(); - obj.LeaveType = ReadUInt32(buffer); + obj.LeaveType = (EntryBoardLeaveType) ReadUInt32(buffer); return obj; } } diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemKickRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemKickRes.cs new file mode 100644 index 000000000..75480e515 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemKickRes.cs @@ -0,0 +1,31 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Network; +using System.Collections.Generic; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardItemKickRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ITEM_KICK_RES; + + public S2CEntryBoardItemKickRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardItemKickRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CEntryBoardItemKickRes Read(IBuffer buffer) + { + S2CEntryBoardItemKickRes obj = new S2CEntryBoardItemKickRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemPartyNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemPartyNtc.cs new file mode 100644 index 000000000..0801fd068 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemPartyNtc.cs @@ -0,0 +1,27 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardItemPartyNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ITEM_PARTY_NTC; + + public S2CEntryBoardItemPartyNtc() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardItemPartyNtc obj) + { + } + + public override S2CEntryBoardItemPartyNtc Read(IBuffer buffer) + { + S2CEntryBoardItemPartyNtc obj = new S2CEntryBoardItemPartyNtc(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemTimeoutTimerNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemTimeoutTimerNtc.cs new file mode 100644 index 000000000..a67119a9d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemTimeoutTimerNtc.cs @@ -0,0 +1,31 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardItemTimeoutTimerNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ITEM_TIMEOUT_TIMER_NTC; + + public S2CEntryBoardItemTimeoutTimerNtc() + { + } + + public ushort TimeOut { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardItemTimeoutTimerNtc obj) + { + WriteUInt16(buffer, obj.TimeOut); + } + + public override S2CEntryBoardItemTimeoutTimerNtc Read(IBuffer buffer) + { + S2CEntryBoardItemTimeoutTimerNtc obj = new S2CEntryBoardItemTimeoutTimerNtc(); + obj.TimeOut = ReadUInt16(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemUnreadyNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemUnreadyNtc.cs new file mode 100644 index 000000000..22369a31f --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardItemUnreadyNtc.cs @@ -0,0 +1,27 @@ +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CEntryBoardItemUnreadyNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_ENTRY_BOARD_ITEM_UNREADY_NTC; + + public S2CEntryBoardItemUnreadyNtc() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CEntryBoardItemUnreadyNtc obj) + { + } + + public override S2CEntryBoardItemUnreadyNtc Read(IBuffer buffer) + { + S2CEntryBoardItemUnreadyNtc obj = new S2CEntryBoardItemUnreadyNtc(); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryCancelRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryCancelRes.cs new file mode 100644 index 000000000..0b0a87043 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayEntryCancelRes.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CQuestPlayEntryCancelRes : ServerResponse + { + public override PacketId Id => PacketId.S2C_QUEST_PLAY_ENTRY_CANCEL_RES; + + public S2CQuestPlayEntryCancelRes() + { + } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CQuestPlayEntryCancelRes obj) + { + WriteServerResponse(buffer, obj); + } + + public override S2CQuestPlayEntryCancelRes Read(IBuffer buffer) + { + S2CQuestPlayEntryCancelRes obj = new S2CQuestPlayEntryCancelRes(); + ReadServerResponse(buffer, obj); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerNtc.cs index e0f38fa3a..d0b89204f 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerNtc.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CQuestPlayInterruptAnswerNtc.cs @@ -7,7 +7,7 @@ namespace Arrowgene.Ddon.Shared.Entity.PacketStructure { public class S2CQuestPlayInterruptAnswerNtc : IPacketStructure { - public PacketId Id => PacketId.S2C_QUEST_11_86_16_NTC; // EMPTY ID + public PacketId Id => PacketId.S2C_QUEST_PLAY_INTERRUPT_RESULT_NTC; public S2CQuestPlayInterruptAnswerNtc() { diff --git a/Arrowgene.Ddon.Shared/Model/EntryBoardLeaveType.cs b/Arrowgene.Ddon.Shared/Model/EntryBoardLeaveType.cs new file mode 100644 index 000000000..0d1ade397 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/EntryBoardLeaveType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Model +{ + public enum EntryBoardLeaveType : uint + { + EntryBoardRemoved = 0, + EntryBoardDisolved = 1, + EntryBoardTimeUp = 2, + EntryBoardReadyTimeUp = 3, + MemberReadyTimeup = 4, + EntryBoardAlarm = 5, + } +} diff --git a/Arrowgene.Ddon.Shared/Model/VoteAnswer.cs b/Arrowgene.Ddon.Shared/Model/VoteAnswer.cs new file mode 100644 index 000000000..5bb2d5564 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/VoteAnswer.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Model +{ + public enum VoteAnswer + { + Undecided, + Agree, + Disagree + } +} diff --git a/Arrowgene.Ddon.Shared/Network/PacketId.cs b/Arrowgene.Ddon.Shared/Network/PacketId.cs index b00861008..65bce0847 100644 --- a/Arrowgene.Ddon.Shared/Network/PacketId.cs +++ b/Arrowgene.Ddon.Shared/Network/PacketId.cs @@ -1,4 +1,3 @@ -using Arrowgene.Ddon.Shared.Entity.PacketStructure; using System; using System.Collections.Generic; @@ -662,8 +661,8 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId C2S_QUEST_PLAY_ENTRY_REQ = new PacketId(11, 39, 1, "C2S_QUEST_PLAY_ENTRY_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_ENTRY_RES = new PacketId(11, 39, 2, "S2C_QUEST_PLAY_ENTRY_RES", ServerType.Game, PacketSource.Server); // プレイエントリーに public static readonly PacketId S2C_QUEST_PLAY_ENTRY_NTC = new PacketId(11, 39, 16, "S2C_QUEST_PLAY_ENTRY_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_39_16_NTC"); - public static readonly PacketId C2S_QUEST_11_40_1_REQ = new PacketId(11, 40, 1, "C2S_QUEST_11_40_1_REQ", ServerType.Game, PacketSource.Client); // PLAY_ENTRY_CANCEL? - public static readonly PacketId S2C_QUEST_11_40_2_RES = new PacketId(11, 40, 2, "S2C_QUEST_11_40_2_RES", ServerType.Game, PacketSource.Server); + public static readonly PacketId C2S_QUEST_PLAY_ENTRY_CANCEL_REQ = new PacketId(11, 40, 1, "C2S_QUEST_PLAY_ENTRY_CANCEL_REQ", ServerType.Game, PacketSource.Client, "C2S_QUEST_11_40_1_REQ"); + public static readonly PacketId S2C_QUEST_PLAY_ENTRY_CANCEL_RES = new PacketId(11, 40, 2, "S2C_QUEST_PLAY_ENTRY_CANCEL_RES", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_40_2_RES"); public static readonly PacketId S2C_QUEST_PLAY_ENTRY_CANCEL_NTC = new PacketId(11, 40, 16, "S2C_QUEST_PLAY_ENTRY_CANCEL_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_40_16_NTC"); public static readonly PacketId C2S_QUEST_PLAY_START_REQ = new PacketId(11, 41, 1, "C2S_QUEST_PLAY_START_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_PLAY_START_RES = new PacketId(11, 41, 2, "S2C_QUEST_PLAY_START_RES", ServerType.Game, PacketSource.Server); // プレイスタートに @@ -750,12 +749,12 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_11_79_2_RES = new PacketId(11, 79, 2, "S2C_QUEST_11_79_2_RES", ServerType.Game, PacketSource.Server); public static readonly PacketId C2S_QUEST_DEBUG_ENEMY_SET_PRESET_FIX_REQ = new PacketId(11, 80, 1, "C2S_QUEST_DEBUG_ENEMY_SET_PRESET_FIX_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_QUEST_DEBUG_ENEMY_SET_PRESET_FIX_RES = new PacketId(11, 80, 2, "S2C_QUEST_DEBUG_ENEMY_SET_PRESET_FIX_RES", ServerType.Game, PacketSource.Server); // (デバッグ用)敵セットプリセット固定に - public static readonly PacketId S2C_QUEST_11_81_16_NTC = new PacketId(11, 81, 16, "S2C_QUEST_11_81_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_QUEST_11_81_16_NTC = new PacketId(11, 81, 16, "S2C_QUEST_11_81_16_NTC", ServerType.Game, PacketSource.Server); // Doesn't seem to have a handler? public static readonly PacketId S2C_QUEST_11_82_16_NTC = new PacketId(11, 82, 16, "S2C_QUEST_11_82_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_83_16_NTC = new PacketId(11, 83, 16, "S2C_QUEST_11_83_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_84_16_NTC = new PacketId(11, 84, 16, "S2C_QUEST_11_84_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_QUEST_11_85_16_NTC = new PacketId(11, 85, 16, "S2C_QUEST_11_85_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_QUEST_11_86_16_NTC = new PacketId(11, 86, 16, "S2C_QUEST_11_86_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_QUEST_11_85_16_NTC = new PacketId(11, 85, 16, "S2C_QUEST_11_85_16_NTC", ServerType.Game, PacketSource.Server); // S2C_QUEST_ENABLE_NOTICE? + public static readonly PacketId S2C_QUEST_11_86_16_NTC = new PacketId(11, 86, 16, "S2C_QUEST_11_86_16_NTC", ServerType.Game, PacketSource.Server); // S2C_CYCLE_CONTENTS_ENABLE_NOTICE? public static readonly PacketId S2C_QUEST_11_87_16_NTC = new PacketId(11, 87, 16, "S2C_QUEST_11_87_16_NTC", ServerType.Game, PacketSource.Server); // S2C_QUEST_MASTER_DATA_RELOAD_NOTICE public static readonly PacketId S2C_QUEST_11_88_16_NTC = new PacketId(11, 88, 16, "S2C_QUEST_11_88_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_JOIN_LOBBY_QUEST_INFO_NTC = new PacketId(11, 89, 16, "S2C_QUEST_JOIN_LOBBY_QUEST_INFO_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_89_16_NTC"); @@ -776,8 +775,8 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_QUEST_11_104_16_NTC = new PacketId(11, 104, 16, "S2C_QUEST_11_104_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_PLAY_TIMEUP_NTC = new PacketId(11, 105, 16, "S2C_QUEST_PLAY_TIMEUP_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_105_16_NTC"); // Time is up (S2C_PLAY_TIMEUP_NOTICE) public static readonly PacketId S2C_QUEST_11_106_16_NTC = new PacketId(11, 106, 16, "S2C_QUEST_11_106_16_NTC", ServerType.Game, PacketSource.Server); // - public static readonly PacketId S2C_QUEST_11_107_16_NTC = new PacketId(11, 107, 16, "S2C_QUEST_11_107_16_NTC", ServerType.Game, PacketSource.Server); // The request to end the mission was successful - public static readonly PacketId S2C_QUEST_11_108_16_NTC = new PacketId(11, 108, 16, "S2C_QUEST_11_108_16_NTC", ServerType.Game, PacketSource.Server); // The request to end the mission was successful + public static readonly PacketId S2C_QUEST_PLAY_INTERRUPT_RESULT_NTC = new PacketId(11, 107, 16, "S2C_QUEST_PLAY_INTERRUPT_RESULT_NTC", ServerType.Game, PacketSource.Server, "S2C_QUEST_11_107_16_NTC"); + public static readonly PacketId S2C_QUEST_11_108_16_NTC = new PacketId(11, 108, 16, "S2C_QUEST_11_108_16_NTC", ServerType.Game, PacketSource.Server); // The request to end the mission was successful (S2C_PLAY_FORCE_INTERRUPT_NOTICE?) public static readonly PacketId S2C_QUEST_11_109_16_NTC = new PacketId(11, 109, 16, "S2C_QUEST_11_109_16_NTC", ServerType.Game, PacketSource.Server); public static readonly PacketId S2C_QUEST_11_110_16_NTC = new PacketId(11, 110, 16, "S2C_QUEST_11_110_16_NTC", ServerType.Game, PacketSource.Server); // (S2C_FORT_DEFENSE_PLAY_START_NOTICE) public static readonly PacketId S2C_QUEST_11_111_16_NTC = new PacketId(11, 111, 16, "S2C_QUEST_11_111_16_NTC", ServerType.Game, PacketSource.Server); @@ -1584,10 +1583,10 @@ private static Dictionary InitializeLoginPacketIds() public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES = new PacketId(34, 13, 2, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES", ServerType.Game, PacketSource.Server); // エントリーボード招待に public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_NTC = new PacketId(34, 13, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_13_16_NTC"); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC = new PacketId(34, 14, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_14_16_NTC"); - public static readonly PacketId S2C_ENTRY_34_15_16_NTC = new PacketId(34, 15, 16, "S2C_ENTRY_34_15_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ITEM_UNREADY_NTC = new PacketId(34, 15, 16, "S2C_ENTRY_BOARD_ITEM_UNREADY_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_15_16_NTC"); public static readonly PacketId S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC = new PacketId(34, 16, 16, "S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_16_16_NTC"); - public static readonly PacketId S2C_ENTRY_34_17_16_NTC = new PacketId(34, 17, 16, "S2C_ENTRY_34_17_16_NTC", ServerType.Game, PacketSource.Server); - public static readonly PacketId S2C_ENTRY_34_18_16_NTC = new PacketId(34, 18, 16, "S2C_ENTRY_34_18_16_NTC", ServerType.Game, PacketSource.Server); + public static readonly PacketId S2C_ENTRY_BOARD_ITEM_PARTY_NTC = new PacketId(34, 17, 16, "S2C_ENTRY_BOARD_ITEM_PARTY_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_17_16_NTC"); + public static readonly PacketId S2C_ENTRY_BOARD_ITEM_TIMEOUT_TIMER_NTC = new PacketId(34, 18, 16, "S2C_ENTRY_BOARD_ITEM_TIMEOUT_TIMER_NTC", ServerType.Game, PacketSource.Server, "S2C_ENTRY_34_18_16_NTC"); public static readonly PacketId C2S_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_REQ = new PacketId(34, 19, 1, "C2S_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_REQ", ServerType.Game, PacketSource.Client); public static readonly PacketId S2C_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_RES = new PacketId(34, 19, 2, "S2C_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_RES", ServerType.Game, PacketSource.Server); // パーティ募集掲示板リスト取得に public static readonly PacketId C2S_ENTRY_BOARD_ITEM_KICK_REQ = new PacketId(34, 20, 1, "C2S_ENTRY_BOARD_ITEM_KICK_REQ", ServerType.Game, PacketSource.Client); @@ -2591,8 +2590,8 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_ENTRY_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ENTRY_RES); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ENTRY_NTC); - AddPacketIdEntry(packetIds, C2S_QUEST_11_40_1_REQ); - AddPacketIdEntry(packetIds, S2C_QUEST_11_40_2_RES); + AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_ENTRY_CANCEL_REQ); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ENTRY_CANCEL_RES); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_ENTRY_CANCEL_NTC); AddPacketIdEntry(packetIds, C2S_QUEST_PLAY_START_REQ); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_START_RES); @@ -2705,7 +2704,7 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_QUEST_11_104_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_TIMEUP_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_106_16_NTC); - AddPacketIdEntry(packetIds, S2C_QUEST_11_107_16_NTC); + AddPacketIdEntry(packetIds, S2C_QUEST_PLAY_INTERRUPT_RESULT_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_108_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_109_16_NTC); AddPacketIdEntry(packetIds, S2C_QUEST_11_110_16_NTC); @@ -3513,10 +3512,10 @@ private static Dictionary InitializeGamePacketIds() AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_RES); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_INVITE_NTC); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_CHANGE_MEMBER_NTC); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_15_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ITEM_UNREADY_NTC); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_RESERVE_NTC); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_17_16_NTC); - AddPacketIdEntry(packetIds, S2C_ENTRY_34_18_16_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ITEM_PARTY_NTC); + AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_ITEM_TIMEOUT_TIMER_NTC); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_REQ); AddPacketIdEntry(packetIds, S2C_ENTRY_BOARD_PARTY_RECRUIT_CATEGORY_LIST_RES); AddPacketIdEntry(packetIds, C2S_ENTRY_BOARD_ITEM_KICK_REQ); From 9fa0493177c18f44329c2e018d78b270e7f1e717 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 17 Sep 2024 17:42:49 -0400 Subject: [PATCH 087/116] Code Review Feedback - Renamed packets missing "Item". - Replaced literals with constant. --- Arrowgene.Ddon.GameServer/Characters/BoardManager.cs | 6 +++--- .../EntryBoardEntryBoardItemExtendTimeoutHandler.cs | 8 ++++---- Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs | 4 ++-- ...> C2SEntryBoardEntryBoardItemExtendTimeoutReq.cs} | 12 ++++++------ ...> S2CEntryBoardEntryBoardItemExtendTimeoutRes.cs} | 12 ++++++------ 5 files changed, 21 insertions(+), 21 deletions(-) rename Arrowgene.Ddon.Shared/Entity/PacketStructure/{C2SEntryBoardEntryBoardExtendTimeoutReq.cs => C2SEntryBoardEntryBoardItemExtendTimeoutReq.cs} (56%) rename Arrowgene.Ddon.Shared/Entity/PacketStructure/{S2CEntryBoardEntryBoardExtendTimeoutRes.cs => S2CEntryBoardEntryBoardItemExtendTimeoutRes.cs} (65%) diff --git a/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs b/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs index 23d92514c..b86c5607f 100644 --- a/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/BoardManager.cs @@ -444,14 +444,14 @@ public bool StartReadyUpTimer(uint entryItemId, uint timeoutInSeconds) } // Restart the recruitment timer - data.EntryItem.TimeOut = 3600; - StartRecruitmentTimer(data.EntryItem.Id, 3600); + data.EntryItem.TimeOut = BoardManager.PARTY_BOARD_TIMEOUT; + StartRecruitmentTimer(data.EntryItem.Id, BoardManager.PARTY_BOARD_TIMEOUT); foreach (var characterId in data.Members) { var memberClient = _Server.ClientLookup.GetClientByCharacterId(characterId); if (memberClient != null) { - memberClient.Send(new S2CEntryBoardItemTimeoutTimerNtc() { TimeOut = 3600}); + memberClient.Send(new S2CEntryBoardItemTimeoutTimerNtc() { TimeOut = BoardManager.PARTY_BOARD_TIMEOUT }); } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemExtendTimeoutHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemExtendTimeoutHandler.cs index 6455734a9..3d0211442 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemExtendTimeoutHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemExtendTimeoutHandler.cs @@ -6,7 +6,7 @@ namespace Arrowgene.Ddon.GameServer.Handler { - public class EntryBoardEntryBoardItemExtendTimeoutHandler : GameRequestPacketHandler + public class EntryBoardEntryBoardItemExtendTimeoutHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EntryBoardEntryBoardItemExtendTimeoutHandler)); @@ -14,14 +14,14 @@ public EntryBoardEntryBoardItemExtendTimeoutHandler(DdonGameServer server) : bas { } - public override S2CEntryBoardEntryBoardExtendTimeoutRes Handle(GameClient client, C2SEntryBoardEntryBoardExtendTimeoutReq request) + public override S2CEntryBoardEntryBoardItemExtendTimeoutRes Handle(GameClient client, C2SEntryBoardEntryBoardItemExtendTimeoutReq request) { // var pcap = new S2CEntryBoardEntryBoardItemForceStartRes.Serializer().Read(GameFull.Dump_711.AsBuffer()); var data = Server.BoardManager.GetGroupDataForCharacter(client.Character); if (!Server.BoardManager.ExtendReadyUpTimer(data.EntryItem.Id)) { - return new S2CEntryBoardEntryBoardExtendTimeoutRes() + return new S2CEntryBoardEntryBoardItemExtendTimeoutRes() { Error = (uint) ErrorCode.ERROR_CODE_TIMER_INTERNAL_ERROR }; @@ -38,7 +38,7 @@ public override S2CEntryBoardEntryBoardExtendTimeoutRes Handle(GameClient client memberClient.Send(new S2CEntryBoardItemTimeoutTimerNtc() { TimeOut = Server.BoardManager.GetTimeLeftToReadyUp(data.EntryItem.Id) }); } - return new S2CEntryBoardEntryBoardExtendTimeoutRes() + return new S2CEntryBoardEntryBoardItemExtendTimeoutRes() { TimeOut = Server.BoardManager.GetTimeLeftToReadyUp(data.EntryItem.Id) }; diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index cb18d4761..a9834d7cd 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -486,7 +486,7 @@ static EntitySerializer() Create(new C2SEntryBoardEntryBoardItemListReq.Serializer()); Create(new C2SEntryBoardEntryBoardItemRecreateReq.Serializer()); Create(new C2SEntryBoardItemKickReq.Serializer()); - Create(new C2SEntryBoardEntryBoardExtendTimeoutReq.Serializer()); + Create(new C2SEntryBoardEntryBoardItemExtendTimeoutReq.Serializer()); Create(new C2SAchievementReceivableRewardNtc.Serializer()); Create(new C2SAchievementCompleteNtc.Serializer()); @@ -899,7 +899,7 @@ static EntitySerializer() Create(new S2CEntryBoardItemTimeoutTimerNtc.Serializer()); Create(new S2CEntryBoardItemPartyNtc.Serializer()); Create(new S2CEntryBoardItemKickRes.Serializer()); - Create(new S2CEntryBoardEntryBoardExtendTimeoutRes.Serializer()); + Create(new S2CEntryBoardEntryBoardItemExtendTimeoutRes.Serializer()); Create(new S2CEquipChangeCharacterEquipJobItemNtc.Serializer()); Create(new S2CEquipChangeCharacterEquipJobItemRes.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardExtendTimeoutReq.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemExtendTimeoutReq.cs similarity index 56% rename from Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardExtendTimeoutReq.cs rename to Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemExtendTimeoutReq.cs index 32f51b1fa..79fed2320 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardExtendTimeoutReq.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/C2SEntryBoardEntryBoardItemExtendTimeoutReq.cs @@ -5,23 +5,23 @@ namespace Arrowgene.Ddon.Shared.Entity.PacketStructure { - public class C2SEntryBoardEntryBoardExtendTimeoutReq : IPacketStructure + public class C2SEntryBoardEntryBoardItemExtendTimeoutReq : IPacketStructure { public PacketId Id => PacketId.C2S_ENTRY_BOARD_ENTRY_BOARD_ITEM_EXTEND_TIMEOUT_REQ; - public C2SEntryBoardEntryBoardExtendTimeoutReq() + public C2SEntryBoardEntryBoardItemExtendTimeoutReq() { } - public class Serializer : PacketEntitySerializer + public class Serializer : PacketEntitySerializer { - public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardExtendTimeoutReq obj) + public override void Write(IBuffer buffer, C2SEntryBoardEntryBoardItemExtendTimeoutReq obj) { } - public override C2SEntryBoardEntryBoardExtendTimeoutReq Read(IBuffer buffer) + public override C2SEntryBoardEntryBoardItemExtendTimeoutReq Read(IBuffer buffer) { - C2SEntryBoardEntryBoardExtendTimeoutReq obj = new C2SEntryBoardEntryBoardExtendTimeoutReq(); + C2SEntryBoardEntryBoardItemExtendTimeoutReq obj = new C2SEntryBoardEntryBoardItemExtendTimeoutReq(); return obj; } } diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardExtendTimeoutRes.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemExtendTimeoutRes.cs similarity index 65% rename from Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardExtendTimeoutRes.cs rename to Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemExtendTimeoutRes.cs index d79803822..ba642007b 100644 --- a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardExtendTimeoutRes.cs +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CEntryBoardEntryBoardItemExtendTimeoutRes.cs @@ -5,27 +5,27 @@ namespace Arrowgene.Ddon.Shared.Entity.PacketStructure { - public class S2CEntryBoardEntryBoardExtendTimeoutRes : ServerResponse + public class S2CEntryBoardEntryBoardItemExtendTimeoutRes : ServerResponse { public override PacketId Id => PacketId.S2C_ENTRY_BOARD_ENTRY_BOARD_ITEM_EXTEND_TIMEOUT_RES; - public S2CEntryBoardEntryBoardExtendTimeoutRes() + public S2CEntryBoardEntryBoardItemExtendTimeoutRes() { } public ushort TimeOut { get; set; } - public class Serializer : PacketEntitySerializer + public class Serializer : PacketEntitySerializer { - public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardExtendTimeoutRes obj) + public override void Write(IBuffer buffer, S2CEntryBoardEntryBoardItemExtendTimeoutRes obj) { WriteServerResponse(buffer, obj); WriteUInt16(buffer, obj.TimeOut); } - public override S2CEntryBoardEntryBoardExtendTimeoutRes Read(IBuffer buffer) + public override S2CEntryBoardEntryBoardItemExtendTimeoutRes Read(IBuffer buffer) { - S2CEntryBoardEntryBoardExtendTimeoutRes obj = new S2CEntryBoardEntryBoardExtendTimeoutRes(); + S2CEntryBoardEntryBoardItemExtendTimeoutRes obj = new S2CEntryBoardEntryBoardItemExtendTimeoutRes(); ReadServerResponse(buffer, obj); obj.TimeOut = ReadUInt16(buffer); return obj; From 1e0aeaf6b68b6385029d8e61e902a0fdf34f0b3c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 17 Sep 2024 22:28:26 -0400 Subject: [PATCH 088/116] feat: Enable 8 person parties and add new quest - Added the ability to recruit up to 8 members depending on the information provided in the quest JSON. - Added a new EXM "Phindym War Chronicles" - Added a new shop category to Gregory called "Crimson Exchange" - Attempted to balance the quest "The Dragon Awakened" for 8 players as originally intended. - Figured out how to decode the board ID back into a proper quest ID. - Removed board id field from EXM quest JSON. - Fixed more issues related to random rewards. --- .../Characters/PartyQuestContentManager.cs | 3 - .../Characters/QuestManager.cs | 22 +- ...tryBoardEntryBoardItemForceStartHandler.cs | 6 - .../EntryBoardEntryBoardListHandler.cs | 10 +- .../QuestGetEndContentsRecruitListHandler.cs | 2 +- .../Handler/QuestGetRewardBoxItemHandler.cs | 21 +- .../Handler/QuestPlayStartHandler.cs | 15 +- .../AssetReader/QuestAssetDeserializer.cs | 7 - .../S2CQuestTimeGainQuestPlayStartNtc.cs | 2 +- .../Files/Assets/SpecialShops.json | 192 +++++ .../Files/Assets/quests/q50101020.json | 1 - .../Files/Assets/quests/q50102020.json | 1 - .../Files/Assets/quests/q50103020.json | 1 - .../Files/Assets/quests/q50104000.json | 1 - .../Files/Assets/quests/q50201000.json | 1 - .../Files/Assets/quests/q50202000.json | 1 - .../Files/Assets/quests/q50202003.json | 1 - .../Files/Assets/quests/q50203000.json | 1 - .../Files/Assets/quests/q50204002.json | 1 - .../Files/Assets/quests/q50206000.json | 669 ++++++++++++++++++ .../Files/Assets/quests/q50300010.json | 37 +- .../Model/Quest/QuestMissionParams.cs | 7 - .../Model/Quest/QuestRewards.cs | 2 + 23 files changed, 924 insertions(+), 80 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json diff --git a/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs b/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs index 056856648..d348d6de9 100644 --- a/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/PartyQuestContentManager.cs @@ -1,13 +1,10 @@ using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Model; using Arrowgene.Logging; using System; using System.Collections.Generic; -using System.IO; -using System.Threading; namespace Arrowgene.Ddon.GameServer.Characters { diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 4fba110a8..de1590f6d 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -26,7 +26,6 @@ private QuestManager() private static readonly HashSet AvailableVariantQuests = new(); private static Dictionary> gTutorialQuests = new Dictionary>(); private static Dictionary> gWorldQuests = new Dictionary>(); - private static readonly Dictionary gExtremeQuests = new Dictionary(); public static HashSet GetAllVariantQuestIds() { @@ -82,10 +81,6 @@ public static void LoadQuests(AssetRepository assetRepository) } gWorldQuests[quest.QuestAreaId].Add(quest); } - else if (quest.QuestType == QuestType.ExtremeMission) - { - gExtremeQuests[quest.MissionParams.BoardId] = quest; - } } } @@ -178,11 +173,18 @@ public static List GetWorldQuestIdsByAreaId(QuestAreaId areaId) public static Quest GetQuestByBoardId(ulong boardId) { - if (!gExtremeQuests.ContainsKey(boardId)) - { - return null; - } - return gExtremeQuests[boardId]; + uint questId = (uint)(boardId - 17179869184UL); + return GetQuest(questId); + } + + public static ulong QuestIdToBoardId(uint questId) + { + return questId + 17179869184UL; + } + + public static ulong QuestIdToBoardId(QuestId questId) + { + return QuestIdToBoardId((uint)questId); } public static List GetTutorialQuestsByStageNo(uint stageNo) diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs index 7426dc4c3..ea3e91d90 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemForceStartHandler.cs @@ -1,13 +1,7 @@ -using Arrowgene.Buffers; using Arrowgene.Ddon.GameServer.Characters; -using Arrowgene.Ddon.GameServer.Dump; 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.Networking.Tcp.Consumer.BlockingQueueConsumption; -using System.Threading; namespace Arrowgene.Ddon.GameServer.Handler { diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs index b2bfe36ae..b32fb01c1 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardListHandler.cs @@ -28,14 +28,14 @@ public override S2CEntryBoardEntryBoardListRes Handle(GameClient client, C2SEntr foreach (var boardId in request.BoardIdList.Select(x => x.Value).ToList()) { var quest = QuestManager.GetQuestByBoardId(boardId); - foreach (var group in Server.BoardManager.GetGroupsForBoardId(boardId)) + if (quest != null) { var contentParams = new CDataEntryBoardListParam() { - BoardId = group.BoardId, - SortieMin = 1, // TODO: Can client populate this from somewhere? - NoPartyMembers = (ushort) group.Members.Count(), - TimeOut = 3600, // TODO: Get time ellapsed until recruitment ends and populate this to match? + BoardId = boardId, + SortieMin = (ushort) quest.MissionParams.MinimumMembers, + NoPartyMembers = (ushort) quest.MissionParams.MaximumMembers, + TimeOut = BoardManager.PARTY_BOARD_TIMEOUT, }; result.EntryList.Add(contentParams); } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsRecruitListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsRecruitListHandler.cs index 359275b1e..33a6c33ac 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsRecruitListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetEndContentsRecruitListHandler.cs @@ -26,7 +26,7 @@ public override S2CQuestGetEndContentsRecruitListRes Handle(GameClient client, C { QuestScheduleId = (uint) quest.QuestScheduleId, QuestId = (uint) quest.QuestId, - GroupsRecruiting = (uint) Server.BoardManager.GetGroupsForBoardId(quest.MissionParams.BoardId).Where(x => !x.ContentInProgress).ToList().Count + GroupsRecruiting = (uint) Server.BoardManager.GetGroupsForBoardId(QuestManager.QuestIdToBoardId(quest.QuestId)).Where(x => !x.ContentInProgress).ToList().Count }); } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxItemHandler.cs index 4bb4d15d3..1ac74a457 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetRewardBoxItemHandler.cs @@ -47,13 +47,28 @@ public override S2CQuestGetRewardBoxItemRes Handle(GameClient client, C2SQuestGe UpdateType = 0 }; - foreach (var boxReward in packet.GetRewardBoxItemList) + // If a quest has multiple slots with the same reward, coalesce them into a single slot. + // This can happen when a quest has multiple random rewards and they random to the same item and amount + Dictionary coalescedRewards = new Dictionary(); + foreach (var reward in rewards) { - var reward = rewards.Single(x => x.UID == boxReward.UID); + if (!coalescedRewards.ContainsKey(reward.UID)) + { + coalescedRewards[reward.UID] = reward; + } + else + { + coalescedRewards[reward.UID].Num += reward.Num; + } + } + + foreach (var rewardUID in packet.GetRewardBoxItemList.Select(x => x.UID).Distinct().ToList()) + { + var reward = coalescedRewards[rewardUID]; if (Server.ItemManager.IsItemWalletPoint(reward.ItemId)) { (WalletType walletType, uint amount) = Server.ItemManager.ItemToWalletPoint(reward.ItemId); - var result = Server.WalletManager.AddToWallet(client.Character, walletType, amount); + var result = Server.WalletManager.AddToWallet(client.Character, walletType, amount * reward.Num); updateCharacterItemNtc.UpdateWalletList.Add(result); } else if (reward.Num > 0) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs index c7b0b8522..583b82075 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestPlayStartHandler.cs @@ -6,28 +6,24 @@ using Arrowgene.Ddon.Shared.Model; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using Arrowgene.Networking.Tcp.Consumer.BlockingQueueConsumption; using System; using System.Collections.Generic; using System.Linq; namespace Arrowgene.Ddon.GameServer.Handler { - public class QuestPlayStartHandler : StructurePacketHandler + public class QuestPlayStartHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(QuestPlayStartHandler)); - private DdonGameServer _Server; - public QuestPlayStartHandler(DdonGameServer server) : base(server) { - _Server = server; } - public override void Handle(GameClient client, StructurePacket request) + public override S2CQuestPlayerStartRes Handle(GameClient client, C2SQuestPlayerStartReq request) { - client.Send(new S2CQuestPlayerStartRes()); - - var quest = QuestManager.GetQuest(request.Structure.QuestScheduleId); + var quest = QuestManager.GetQuest(request.QuestScheduleId); if (quest != null) { client.Party.QuestState.AddNewQuest(quest); @@ -37,10 +33,11 @@ public override void Handle(GameClient client, StructurePacket PacketId.S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC; // Might be chain quest??? + public PacketId Id => PacketId.S2C_QUEST_TIME_GAIN_QUEST_PLAY_START_NTC; public S2CQuestTimeGainQuestPlayStartNtc() { diff --git a/Arrowgene.Ddon.Shared/Files/Assets/SpecialShops.json b/Arrowgene.Ddon.Shared/Files/Assets/SpecialShops.json index b16bbae51..f961450d7 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/SpecialShops.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/SpecialShops.json @@ -29622,6 +29622,198 @@ ] } ] + }, + { + "label": "Crimson Exchange", + "appraisals": [ + { + "label": "<Exch> Crimson Sword", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19149, + "name": "Crimson Sword", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Blade", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19154, + "name": "Crimson Blade", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Daggers", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19164, + "name": "Crimson Daggers", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Bow", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19169, + "name": "Crimson Bow", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Wand", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19189, + "name": "Crimson Wand", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Wall", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19159, + "name": "Crimson Wall", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Staff", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19184, + "name": "Crimson Staff", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Spell", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19179, + "name": "Crimson Spell", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Gauntlet", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19174, + "name": "Crimson Gauntlet", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Spear", + "base_items": [ + { + "item_id": 19147, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 19194, + "name": "Crimson Spear", + "amount": 1 + } + ] + }, + { + "label": "<Exch> Crimson Firangi", + "base_items": [ + { + "item_id": 19179, + "name": "Central Tree Drop", + "amount": 140 + } + ], + "pool": [ + { + "item_id": 21427, + "name": "Crimson Firangi", + "amount": 1 + } + ] + } + ] } ] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json index 52d19dfdf..3af4ce124 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 1, - "board_id": 17229970204, "minimum_members": 1, "playtime": 1200, "solo_only": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json index dd2c317bc..07928be29 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50102020.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 1, - "board_id": 17229971204, "minimum_members": 1, "playtime": 1500, "solo_only": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json index 071cd513b..da15b47b1 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50103020.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 1, - "board_id": 17229972204, "minimum_members": 1, "playtime": 1200, "solo_only": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json index 313e21a0f..d35e79303 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50104000.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 1, - "board_id": 17229973184, "minimum_members": 1, "playtime": 1200, "solo_only": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json index b9c6a5eed..e8ddea178 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 2, - "board_id": 17230070184, "minimum_members": 1, "playtime": 1200, "solo_only": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json index cf658fca7..586547617 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202000.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 2, - "board_id": 17230071184, "minimum_members": 1, "playtime": 900, "solo_only": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json index 984ee2264..59ee9271a 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 1, - "board_id": 17230071187, "minimum_members": 1, "playtime": 600, "solo_only": true, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json index 8ab95a6f7..79fe33bfa 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50203000.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 2, - "board_id": 17230072184, "minimum_members": 1, "playtime": 600, "solo_only": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json index 594775360..cfabd83cf 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50204002.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 2, - "board_id": 17230073186, "minimum_members": 1, "playtime": 900, "solo_only": false, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json new file mode 100644 index 000000000..a4a65304b --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json @@ -0,0 +1,669 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "Phindym War Chronicles", + "quest_id": 50206000, + "base_level": 80, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 2, + "minimum_members": 1, + "maximum_members": 4, + "playtime": 900, + "solo_only": false, + "loot_distribution": "TimeBased", + "phase_groups": [] + }, + "order_conditions": [ + ], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 19147, + "num": 10 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 19147, + "num": 0, + "chance": 0.30 + }, + { + "item_id": 19147, + "num": 5, + "chance": 0.20 + }, + { + "item_id": 19147, + "num": 10, + "chance": 0.20 + }, + { + "item_id": 19147, + "num": 20, + "chance": 0.15 + }, + { + "item_id": 19147, + "num": 30, + "chance": 0.10 + }, + { + "item_id": 19147, + "num": 60, + "chance": 0.05 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Phase 1 (0)", + "stage_id": { + "id": 433, + "group_id": 3 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Altered Zhul", + "enemy_id": "0x020403", + "level": 80, + "exp": 0, + "index": 0, + "is_boss": true, + "named_enemy_params_id": 1699 + } + ] + }, + { + "comment": "Phase 1 Adds (1)", + "stage_id": { + "id": 433, + "group_id": 4 + }, + "placement_type": "Manual", + "enemies": [ + { + "comment": "Strix", + "enemy_id": "0x010610", + "level": 80, + "exp": 0, + "index": 0, + "repop_count": 50, + "repop_wait_second": 45, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1698 + }, + { + "comment": "Strix", + "enemy_id": "0x010610", + "level": 80, + "exp": 0, + "index": 1, + "repop_count": 50, + "repop_wait_second": 45, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1698 + }, + { + "comment": "Strix", + "enemy_id": "0x010610", + "level": 80, + "exp": 0, + "index": 2, + "repop_count": 50, + "repop_wait_second": 45, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1698 + }, + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 80, + "exp": 0, + "index": 3, + "repop_count": 50, + "repop_wait_second": 45, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1698 + }, + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 80, + "exp": 0, + "index": 4, + "repop_count": 50, + "repop_wait_second": 45, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1698 + }, + { + "comment": "Grimwarg", + "enemy_id": "0x010205", + "level": 80, + "exp": 0, + "index": 5, + "repop_count": 50, + "repop_wait_second": 45, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1698 + } + ] + }, + { + "comment": "Phase 2 (2)", + "stage_id": { + "id": 375, + "group_id": 16 + }, + "enemies": [ + { + "comment": "Bearded Grigori", + "enemy_id": "0x015920", + "level": 80, + "exp": 0, + "named_enemy_params_id": 1700 + }, + { + "comment": "Bearded Grigori", + "enemy_id": "0x015920", + "level": 80, + "exp": 0, + "named_enemy_params_id": 1700 + }, + { + "comment": "Pixie Jabber", + "enemy_id": "0x010155", + "level": 80, + "exp": 0, + "named_enemy_params_id": 1700 + }, + { + "comment": "Pixie Pow", + "enemy_id": "0x010153", + "level": 80, + "exp": 0, + "named_enemy_params_id": 1700 + }, + { + "comment": "Pixie Din", + "enemy_id": "0x010152", + "level": 80, + "exp": 0, + "named_enemy_params_id": 1700 + }, + { + "comment": "Pixie Biff", + "enemy_id": "0x010151", + "level": 80, + "exp": 0, + "named_enemy_params_id": 1700 + }, + { + "comment": "Bearded Grigori", + "enemy_id": "0x015920", + "level": 80, + "exp": 0, + "named_enemy_params_id": 1700 + } + ] + }, + { + "comment": "Phase 2 Boss (3)", + "stage_id": { + "id": 375, + "group_id": 18 + }, + "enemies": [ + { + "comment": "Cursed Dragon", + "enemy_id": "0x015706", + "level": 80, + "exp": 0, + "is_boss": true, + "named_enemy_params_id": 1701 + } + ] + }, + { + "comment": "Phase 2 Boss Adds (4)", + "stage_id": { + "id": 375, + "group_id": 13 + }, + "enemies": [ + { + "comment": "Witch", + "enemy_id": "0x075700", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1700 + }, + { + "comment": "Shadow Wolf", + "enemy_id": "0x010206", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1700 + }, + { + "comment": "Shadow Wolf", + "enemy_id": "0x010206", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1700 + }, + { + "comment": "Shadow Wolf", + "enemy_id": "0x010206", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1700 + }, + { + "comment": "Shadow Wolf", + "enemy_id": "0x010206", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1700 + }, + { + "comment": "Shadow Wolf", + "enemy_id": "0x010206", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1700 + }, + { + "comment": "Shadow Wolf", + "enemy_id": "0x010206", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1700 + } + ] + }, + { + "comment": "Phase 3 Boss (5)", + "stage_id": { + "id": 459, + "group_id": 2 + }, + "enemies": [ + { + "comment": "Wisened Tarasque", + "enemy_id": "0x080600", + "level": 80, + "exp": 0, + "is_boss": true, + "named_enemy_params_id": 1706 + } + ] + }, + { + "comment": "Phase 4 Boss (6)", + "stage_id": { + "id": 381, + "group_id": 0 + }, + "enemies": [ + { + "comment": "Spirit Dragon", + "enemy_id": "0x022000", + "level": 80, + "exp": 0, + "is_boss": true, + "named_enemy_params_id": 1707 + } + ] + }, + { + "comment": "Adds (Towers) (7)", + "stage_id": { + "id": 381, + "group_id": 2 + }, + "starting_index": 4, + "enemies": [ + { + "comment": "Dragon Crystal of Evil Sealing", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1710, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Evil Sealing", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1710, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + }, + { + "comment": "Dragon Crystal of Power Sapping", + "enemy_id": "0x030104", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1711, + "start_think_tbl_no": 2, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + } + ] + }, + { + "comment": "Adds (Gimick) (8)", + "stage_id": { + "id": 381, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Memory of Sighs", + "enemy_id": "0x030105", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1701, + "repop_count": 50, + "repop_wait_second": 45 + }, + { + "comment": "Memory of Sighs", + "enemy_id": "0x030105", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1701, + "repop_count": 50, + "repop_wait_second": 45 + } + ] + }, + { + "comment": "Adds (Gimick) (9)", + "stage_id": { + "id": 381, + "group_id": 1 + }, + "starting_index": 2, + "enemies": [ + { + "comment": "Memory of Sighs", + "enemy_id": "0x030105", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1701, + "repop_count": 50, + "repop_wait_second": 45 + }, + { + "comment": "Memory of Sighs", + "enemy_id": "0x030105", + "level": 80, + "exp": 0, + "enemy_target_types_id": 1, + "named_enemy_params_id": 1701, + "repop_count": 50, + "repop_wait_second": 45 + } + ] + } + + ], + "processes": [ + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 433 + } + }, + { + "comment": "Kill Altered Zuhl", + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + }, + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 375 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 5371, "comment": "Enables Teleport"}, + {"type": "MyQst", "action": "Set", "value": 5371, "comment": "Enables Teleport"} + ] + }, + { + "comment": "Kill Dungeon Trash", + "type": "KillGroup", + "groups": [2] + }, + { + "type": "DiscoverEnemy", + "groups": [3], + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2802, "comment": "Enables Teleport"}, + {"type": "MyQst", "action": "Set", "value": 2802, "comment": "Enables Teleport"} + ] + }, + { + "comment": "Kill Dragon", + "type": "KillGroup", + "reset_group": false, + "groups": [3], + "flags": [ + {"type": "MyQst", "action": "Set", "value": 1, "comment": "Spawn Adds"} + ] + }, + { + "type": "PartyGather", + "stage_id": { + "id": 375 + }, + "location": { + "x": 2, + "y": 979, + "z": -1633 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "StageNo", "Param1": 124} + ], + "result_commands": [ + {"type": "StageJump", "Param1": 124, "Param2": 0} + ] + }, + { + "comment": "Kill Wisened Tarasque", + "type": "KillGroup", + "groups": [5] + }, + { + "type": "PartyGather", + "stage_id": { + "id": 459 + }, + "location": { + "x": -12505, + "y": -598, + "z": 4393 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "StageNo", "Param1": 436} + ], + "result_commands": [ + {"type": "StageJump", "Param1": 436, "Param2": 0} + ] + }, + { + "comment": "Kill Spirit Dragon", + "type": "KillGroup", + "groups": [6] + } + ] + }, + { + "comment": "Spawns Zuhl Adds", + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 433 + } + }, + { + "type": "SpawnGroup", + "groups": [1], + "check_commands": [ + {"type": "DieEnemy", "Param1": 113, "Param2": 3, "Param3": 0} + ] + }, + { + "type": "DestroyGroup", + "groups": [1] + } + ] + }, + { + "comment": "Spawn Zombie Dragon Adds", + "blocks": [ + { + "comment": "Wait for fight to start", + "type": "MyQstFlags", + "check_flags": [1] + }, + { + "type": "SpawnGroup", + "groups": [4], + "check_commands": [ + {"type": "MyQstFlagOn", "Param1": 2} + ] + }, + { + "type": "DestroyGroup", + "groups": [4] + } + ] + }, + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 381 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 85} + ] + }, + { + "comment": "Spawn Crystals", + "type": "SpawnGroup", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 65} + ], + "groups": [8] + }, + { + "type": "DestroyGroup", + "groups": [8] + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 45} + ] + }, + { + "comment": "Spawn Crystals", + "type": "SpawnGroup", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 25} + ], + "groups": [9] + }, + { + "type": "DestroyGroup", + "groups": [9] + } + ] + }, + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 381 + } + }, + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 15} + ] + }, + { + "comment": "Spawn Towers", + "type": "KillGroup", + "groups": [7] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json index 8ca8e4aaf..0ce207e9e 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json @@ -8,7 +8,6 @@ "discoverable": false, "mission_params": { "group": 9, - "board_id": 17230169194, "minimum_members": 1, "maximum_members": 8, "playtime": 900, @@ -130,7 +129,7 @@ "level": 80, "exp": 0, "is_boss": true, - "named_enemy_params_id": 1707 + "named_enemy_params_id": 1564 } ] }, @@ -148,7 +147,7 @@ "level": 80, "exp": 0, "enemy_target_types_id": 1, - "named_enemy_params_id": 1710, + "named_enemy_params_id": 1567, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -161,7 +160,7 @@ "level": 80, "exp": 0, "enemy_target_types_id": 1, - "named_enemy_params_id": 1710, + "named_enemy_params_id": 1567, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -174,7 +173,7 @@ "level": 80, "exp": 0, "enemy_target_types_id": 1, - "named_enemy_params_id": 1711, + "named_enemy_params_id": 1568, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -187,7 +186,7 @@ "level": 80, "exp": 0, "enemy_target_types_id": 1, - "named_enemy_params_id": 1711, + "named_enemy_params_id": 1568, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -211,7 +210,7 @@ "exp": 0, "index": 11, "enemy_target_types_id": 1, - "named_enemy_params_id": 1711, + "named_enemy_params_id": 1568, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -225,7 +224,7 @@ "exp": 0, "index": 12, "enemy_target_types_id": 1, - "named_enemy_params_id": 1710, + "named_enemy_params_id": 1567, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -239,7 +238,7 @@ "exp": 0, "index": 15, "enemy_target_types_id": 1, - "named_enemy_params_id": 1711, + "named_enemy_params_id": 1568, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -253,7 +252,7 @@ "exp": 0, "index": 16, "enemy_target_types_id": 1, - "named_enemy_params_id": 1710, + "named_enemy_params_id": 1567, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -267,7 +266,7 @@ "exp": 0, "index": 17, "enemy_target_types_id": 1, - "named_enemy_params_id": 1711, + "named_enemy_params_id": 1568, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -281,7 +280,7 @@ "exp": 0, "index": 18, "enemy_target_types_id": 1, - "named_enemy_params_id": 1711, + "named_enemy_params_id": 1568, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -295,7 +294,7 @@ "exp": 0, "index": 19, "enemy_target_types_id": 1, - "named_enemy_params_id": 1711, + "named_enemy_params_id": 1568, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -309,7 +308,7 @@ "exp": 0, "index": 20, "enemy_target_types_id": 1, - "named_enemy_params_id": 1710, + "named_enemy_params_id": 1567, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -323,7 +322,7 @@ "exp": 0, "index": 21, "enemy_target_types_id": 1, - "named_enemy_params_id": 1711, + "named_enemy_params_id": 1568, "start_think_tbl_no": 2, "is_manual_set": true, "is_required": false, @@ -345,7 +344,7 @@ "level": 80, "exp": 0, "enemy_target_types_id": 1, - "named_enemy_params_id": 1701, + "named_enemy_params_id": 1565, "repop_count": 50, "repop_wait_second": 45 }, @@ -355,7 +354,7 @@ "level": 80, "exp": 0, "enemy_target_types_id": 1, - "named_enemy_params_id": 1701, + "named_enemy_params_id": 1565, "repop_count": 50, "repop_wait_second": 45 } @@ -375,7 +374,7 @@ "level": 80, "exp": 0, "enemy_target_types_id": 1, - "named_enemy_params_id": 1701, + "named_enemy_params_id": 1565, "repop_count": 50, "repop_wait_second": 45 }, @@ -385,7 +384,7 @@ "level": 80, "exp": 0, "enemy_target_types_id": 1, - "named_enemy_params_id": 1701, + "named_enemy_params_id": 1565, "repop_count": 50, "repop_wait_second": 45 } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs index a1adadb14..222c0a570 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestMissionParams.cs @@ -1,10 +1,5 @@ using Arrowgene.Ddon.Shared.Entity.Structure; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace Arrowgene.Ddon.Shared.Model.Quest { @@ -14,8 +9,6 @@ public QuestMissionParams() { QuestPhaseGroupIdList = new List(); } - - public ulong BoardId { get; set; } public uint MinimumMembers { get; set; } public uint MaximumMembers { get; set; } public uint PlaytimeInSeconds { get; set; } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs index e54db6719..0ed9bcf35 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestRewards.cs @@ -14,6 +14,7 @@ public virtual string GetUID() { IncrementalHash hash = IncrementalHash.CreateHash(HashAlgorithmName.MD5); hash.AppendData(BitConverter.GetBytes(ItemId)); + hash.AppendData(BitConverter.GetBytes(Num)); return BitConverter.ToString(hash.GetHashAndReset()).Replace("-", string.Empty).Substring(0, 8); } } @@ -34,6 +35,7 @@ public override string GetUID() { IncrementalHash hash = IncrementalHash.CreateHash(HashAlgorithmName.MD5); hash.AppendData(BitConverter.GetBytes(ItemId)); + hash.AppendData(BitConverter.GetBytes(Num)); return BitConverter.ToString(hash.GetHashAndReset()).Replace("-", string.Empty).Substring(0, 8); } } From 3cf835f350036ee116e197c6891dd29b6a7ff38f Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 18 Sep 2024 17:37:57 -0400 Subject: [PATCH 089/116] Code Review feedback - Converted magic number into constant and added a brief explanation about it. - Added a check to the /invite command to prevent players from being invited to the party after content has started. --- Arrowgene.Ddon.GameServer/Characters/QuestManager.cs | 11 +++++++++-- .../Chat/Command/Commands/PartyInviteCommand.cs | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index de1590f6d..ace45caff 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -171,15 +171,22 @@ public static List GetWorldQuestIdsByAreaId(QuestAreaId areaId) return gWorldQuests[areaId].Select(x => x.QuestId).ToList(); } + /** + * @brief Magic number derived by taking a known BoardId associated with a QuestId and subtracting the two. + * A pattern was noticed that the BoardId values had the same distribution as the QuestId and via some + * experimentation this pattern was found and confirmed to work. + */ + private static readonly ulong BOARD_ID_MAGIC_VALUE_CONSTANT = 17179869184UL; + public static Quest GetQuestByBoardId(ulong boardId) { - uint questId = (uint)(boardId - 17179869184UL); + uint questId = (uint)(boardId - QuestManager.BOARD_ID_MAGIC_VALUE_CONSTANT); return GetQuest(questId); } public static ulong QuestIdToBoardId(uint questId) { - return questId + 17179869184UL; + return questId + QuestManager.BOARD_ID_MAGIC_VALUE_CONSTANT; } public static ulong QuestIdToBoardId(QuestId questId) diff --git a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/PartyInviteCommand.cs b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/PartyInviteCommand.cs index 0005fe30c..c2df8379b 100644 --- a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/PartyInviteCommand.cs +++ b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/PartyInviteCommand.cs @@ -35,6 +35,12 @@ public override void Execute(string[] command, GameClient client, ChatMessage me return; } + if (client.Party.ContentId != 0) + { + responses.Add(ChatResponse.CommandError(client, "Use the recruitment board to invite players to the party.")); + return; + } + if (!client.Party.GetPlayerPartyMember(client).IsLeader) { responses.Add(ChatResponse.CommandError(client, "Only the party leader can invite.")); From 98ad778372d47b6503a1021c3e0988c8c7d498f7 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 18 Sep 2024 19:59:14 -0400 Subject: [PATCH 090/116] feat: Implement new EXM - Implemented the EXM "Ancient Place of Rituals" - Implemented the EXM "The Lost Order" - Implemented the EXM "The Dazzling Gold" - Implemented the EXM "Arisen of the Black Dragon" Adjusted the EXM "The Dragon Awakened" Fixed a few bugs related to quest asset parsing. Co-authored-by: Abiorugu --- .../Party/PartyQuestState.cs | 12 +- .../Quests/GenericQuest.cs | 6 +- .../AssetReader/QuestAssetDeserializer.cs | 2 +- .../Files/Assets/EnemySpawn.json | 2 +- .../Files/Assets/quests/q50206000.json | 23 ++- .../Files/Assets/quests/q50300001.json | 192 ++++++++++++++++++ .../Files/Assets/quests/q50300003.json | 183 +++++++++++++++++ .../Files/Assets/quests/q50300006.json | 183 +++++++++++++++++ .../Files/Assets/quests/q50300010.json | 3 +- .../Files/Assets/quests/q50400004.json | 121 +++++++++++ 10 files changed, 708 insertions(+), 19 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50300001.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50300003.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50300006.json create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50400004.json diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 423786fa1..af27a869f 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -146,15 +146,17 @@ public void AddNewQuest(Quest quest, uint step = 0, bool questStarted = false) HasStarted = questStarted }; - foreach (var location in quest.Locations) + foreach (var stageId in quest.UniqueEnemyGroups) { - if (!QuestLookupTable.ContainsKey(location.StageId)) + if (!QuestLookupTable.ContainsKey(stageId)) { - QuestLookupTable[location.StageId] = new List(); + QuestLookupTable[stageId] = new List(); } + QuestLookupTable[stageId].Add(quest.QuestId); + } - QuestLookupTable[location.StageId].Add(quest.QuestId); - + foreach (var location in quest.Locations) + { // Populate data structures for Instance Enemy Data if (!ActiveQuests[quest.QuestId].QuestEnemies.ContainsKey(location.StageId)) { diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index bcb8cf81d..da953de5e 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -67,6 +67,11 @@ public static GenericQuest FromAsset(QuestAssetData questAsset) } } + foreach (var (_, enemyGroup) in questAsset.EnemyGroups) + { + quest.UniqueEnemyGroups.Add(enemyGroup.StageId); + } + foreach (var process in quest.Processes) { foreach (var block in process.Blocks) @@ -80,7 +85,6 @@ public static GenericQuest FromAsset(QuestAssetData questAsset) { var enemyGroup = quest.EnemyGroups[groupId]; quest.Locations.Add(new QuestLocation() { StageId = enemyGroup.StageId, SubGroupId = (ushort) enemyGroup.SubGroupId }); - quest.UniqueEnemyGroups.Add(enemyGroup.StageId); } } break; diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 641dd4427..87197b384 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -1106,7 +1106,7 @@ private void ApplyOptionalEnemyKeys(JsonElement enemy, Enemy questEnemey) questEnemey.EnemyTargetTypesId = jEnemyTargetTypesId.GetByte(); } - if (enemy.TryGetProperty("mondatge_fix_no", out JsonElement jMontageFixNo)) + if (enemy.TryGetProperty("montage_fix_no", out JsonElement jMontageFixNo)) { questEnemey.MontageFixNo = jMontageFixNo.GetByte(); } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json index 536447221..3737869ff 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json @@ -249547,4 +249547,4 @@ "00:00,23:59" ] ] -} +} \ No newline at end of file diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json index a4a65304b..ea4ecfbfd 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json @@ -4,7 +4,7 @@ "comment": "Phindym War Chronicles", "quest_id": 50206000, "base_level": 80, - "minimum_item_rank": 0, + "minimum_item_rank": 70, "discoverable": false, "mission_params": { "group": 2, @@ -15,8 +15,7 @@ "loot_distribution": "TimeBased", "phase_groups": [] }, - "order_conditions": [ - ], + "order_conditions": [], "rewards": [ { "type": "fixed", @@ -98,7 +97,7 @@ "exp": 0, "index": 0, "repop_count": 50, - "repop_wait_second": 45, + "repop_wait_second": 60, "enemy_target_types_id": 1, "named_enemy_params_id": 1698 }, @@ -109,7 +108,7 @@ "exp": 0, "index": 1, "repop_count": 50, - "repop_wait_second": 45, + "repop_wait_second": 60, "enemy_target_types_id": 1, "named_enemy_params_id": 1698 }, @@ -120,7 +119,7 @@ "exp": 0, "index": 2, "repop_count": 50, - "repop_wait_second": 45, + "repop_wait_second": 60, "enemy_target_types_id": 1, "named_enemy_params_id": 1698 }, @@ -131,7 +130,7 @@ "exp": 0, "index": 3, "repop_count": 50, - "repop_wait_second": 45, + "repop_wait_second": 60, "enemy_target_types_id": 1, "named_enemy_params_id": 1698 }, @@ -142,7 +141,7 @@ "exp": 0, "index": 4, "repop_count": 50, - "repop_wait_second": 45, + "repop_wait_second": 60, "enemy_target_types_id": 1, "named_enemy_params_id": 1698 }, @@ -153,7 +152,7 @@ "exp": 0, "index": 5, "repop_count": 50, - "repop_wait_second": 45, + "repop_wait_second": 60, "enemy_target_types_id": 1, "named_enemy_params_id": 1698 } @@ -564,6 +563,12 @@ "id": 433 } }, + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 113, "Param2": 3, "Param3": 0, "Param4": 80} + ] + }, { "type": "SpawnGroup", "groups": [1], diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300001.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300001.json new file mode 100644 index 000000000..95697d7ea --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300001.json @@ -0,0 +1,192 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "Ancient Place of Rituals (Grand Mission)", + "quest_id": 50300001, + "base_level": 45, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 9, + "minimum_members": 1, + "maximum_members": 8, + "playtime": 900, + "solo_only": false, + "max_pawns": 7, + "loot_distribution": "TimeBased", + "phase_groups": [] + }, + "order_conditions": [], + "rewards": [ + { + "type": "fixed", + "comment": "Green Dye", + "loot_pool": [ + { + "item_id": 8136, + "num": 1 + } + ] + }, + { + "type": "random", + "comment": "Lestania Glass", + "loot_pool": [ + { + "item_id": 9458, + "num": 1, + "chance": 0.37 + }, + { + "item_id": 9458, + "num": 5, + "chance": 0.25 + }, + { + "item_id": 9458, + "num": 24, + "chance": 0.20 + }, + { + "item_id": 9458, + "num": 36, + "chance": 0.10 + }, + { + "item_id": 9458, + "num": 48, + "chance": 0.05 + }, + { + "item_id": 9458, + "num": 60, + "chance": 0.03 + } + ] + }, + { + "type": "random", + "comment": "Lestania Amber", + "loot_pool": [ + { + "item_id": 9459, + "num": 0, + "chance": 0.50 + }, + { + "item_id": 9459, + "num": 18, + "chance": 0.20 + }, + { + "item_id": 9459, + "num": 27, + "chance": 0.15 + }, + { + "item_id": 9459, + "num": 36, + "chance": 0.10 + }, + { + "item_id": 9459, + "num": 45, + "chance": 0.05 + } + ] + }, + + { + "type": "random", + "comment": "Unappraised Moon Trinket (Soldier)", + "loot_pool": [ + { + "item_id": 11759, + "num": 1, + "chance": 0.40 + }, + { + "item_id": 11759, + "num": 2, + "chance": 0.30 + }, + { + "item_id": 11759, + "num": 3, + "chance": 0.20 + }, + { + "item_id": 11759, + "num": 4, + "chance": 0.10 + } + ] + }, + { + "type": "random", + "comment": "Unappraised Moon Trinket (General)", + "loot_pool": [ + { + "item_id": 13476, + "num": 0, + "chance": 0.90 + }, + { + "item_id": 13476, + "num": 1, + "chance": 0.10 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 82, + "group_id": 0 + }, + "enemies": [ + { + "comment": "Grand Ent", + "enemy_id": "0x080200", + "level": 45, + "exp": 0, + "is_boss": true + } + ] + }, + { + "comment": "None", + "stage_id": { + "id": 82, + "group_id": 1 + }, + "enemies": [] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 82 + } + }, + { + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 82 + }, + "event_id": 5 + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300003.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300003.json new file mode 100644 index 000000000..29be5515c --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300003.json @@ -0,0 +1,183 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "The Lost Order (Grand Mission)", + "quest_id": 50300003, + "base_level": 55, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 9, + "minimum_members": 1, + "maximum_members": 8, + "playtime": 900, + "solo_only": false, + "max_pawns": 7, + "loot_distribution": "TimeBased", + "phase_groups": [] + }, + "order_conditions": [], + "rewards": [ + { + "type": "fixed", + "comment": "Pink Dye", + "loot_pool": [ + { + "item_id": 8139, + "num": 1 + } + ] + }, + { + "type": "random", + "comment": "Disordering Drop", + "loot_pool": [ + { + "item_id": 9461, + "num": 2, + "chance": 0.37 + }, + { + "item_id": 9461, + "num": 5, + "chance": 0.25 + }, + { + "item_id": 9461, + "num": 8, + "chance": 0.20 + }, + { + "item_id": 9461, + "num": 15, + "chance": 0.10 + }, + { + "item_id": 9461, + "num": 30, + "chance": 0.05 + }, + { + "item_id": 9461, + "num": 45, + "chance": 0.03 + } + ] + }, + { + "type": "random", + "comment": "Water of Chaos", + "loot_pool": [ + { + "item_id": 9462, + "num": 1, + "chance": 0.30 + }, + { + "item_id": 9462, + "num": 3, + "chance": 0.25 + }, + { + "item_id": 9462, + "num": 8, + "chance": 0.20 + }, + { + "item_id": 9462, + "num": 10, + "chance": 0.15 + }, + { + "item_id": 9462, + "num": 20, + "chance": 0.05 + } + ] + }, + { + "type": "random", + "comment": "Unappraised Moon Trinket (Soldier)", + "loot_pool": [ + { + "item_id": 11759, + "num": 1, + "chance": 0.40 + }, + { + "item_id": 11759, + "num": 2, + "chance": 0.30 + }, + { + "item_id": 11759, + "num": 3, + "chance": 0.20 + }, + { + "item_id": 11759, + "num": 4, + "chance": 0.10 + } + ] + }, + { + "type": "random", + "comment": "Unappraised Moon Trinket (General)", + "loot_pool": [ + { + "item_id": 13476, + "num": 0, + "chance": 0.90 + }, + { + "item_id": 13476, + "num": 1, + "chance": 0.10 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 308, + "group_id": 10 + }, + "enemies": [ + { + "comment": "Zuhl", + "enemy_id": "0x080300", + "level": 55, + "exp": 0, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 308 + } + }, + { + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 308 + }, + "event_id": 5 + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300006.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300006.json new file mode 100644 index 000000000..24ff5f9b7 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300006.json @@ -0,0 +1,183 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "The Dazzling Gold (Grand Mission)", + "quest_id": 50300006, + "base_level": 60, + "minimum_item_rank": 0, + "discoverable": false, + "mission_params": { + "group": 9, + "minimum_members": 1, + "maximum_members": 8, + "playtime": 900, + "solo_only": false, + "max_pawns": 7, + "loot_distribution": "TimeBased", + "phase_groups": [] + }, + "order_conditions": [], + "rewards": [ + { + "type": "fixed", + "comment": "Yellow Dye", + "loot_pool": [ + { + "item_id": 8138, + "num": 1 + } + ] + }, + { + "type": "random", + "comment": "Golden Wedge", + "loot_pool": [ + { + "item_id": 10996, + "num": 1, + "chance": 0.37 + }, + { + "item_id": 10996, + "num": 3, + "chance": 0.25 + }, + { + "item_id": 10996, + "num": 7, + "chance": 0.20 + }, + { + "item_id": 10996, + "num": 22, + "chance": 0.10 + }, + { + "item_id": 10996, + "num": 37, + "chance": 0.05 + }, + { + "item_id": 10996, + "num": 45, + "chance": 0.03 + } + ] + }, + { + "type": "random", + "comment": "Golden Ritual Instrument", + "loot_pool": [ + { + "item_id": 10997, + "num": 1, + "chance": 0.30 + }, + { + "item_id": 10997, + "num": 4, + "chance": 0.25 + }, + { + "item_id": 10997, + "num": 12, + "chance": 0.20 + }, + { + "item_id": 10997, + "num": 20, + "chance": 0.15 + }, + { + "item_id": 10997, + "num": 25, + "chance": 0.05 + } + ] + }, + { + "type": "random", + "comment": "Unappraised Moon Trinket (Soldier)", + "loot_pool": [ + { + "item_id": 11759, + "num": 1, + "chance": 0.40 + }, + { + "item_id": 11759, + "num": 2, + "chance": 0.30 + }, + { + "item_id": 11759, + "num": 3, + "chance": 0.20 + }, + { + "item_id": 11759, + "num": 4, + "chance": 0.10 + } + ] + }, + { + "type": "random", + "comment": "Unappraised Moon Trinket (General)", + "loot_pool": [ + { + "item_id": 13476, + "num": 0, + "chance": 0.90 + }, + { + "item_id": 13476, + "num": 1, + "chance": 0.10 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 75, + "group_id": 0 + }, + "enemies": [ + { + "comment": "Golgorran", + "enemy_id": "0x080100", + "level": 60, + "exp": 0, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 75 + } + }, + { + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 75 + }, + "event_id": 5 + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json index 0ce207e9e..c6904a5fa 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json @@ -16,8 +16,7 @@ "loot_distribution": "TimeBased", "phase_groups": [] }, - "order_conditions": [ - ], + "order_conditions": [], "rewards": [ { "type": "random", diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50400004.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50400004.json new file mode 100644 index 000000000..58805538d --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50400004.json @@ -0,0 +1,121 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "Arisen of the Black Dragon", + "quest_id": 50400004, + "base_level": 100, + "minimum_item_rank": 0, + "discoverable": false, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 100} + ], + "mission_params": { + "group": 10, + "minimum_members": 1, + "playtime": 600, + "solo_only": false, + "max_pawns": 3, + "phase_groups": [] + }, + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 24819, + "num": 1 + }, + { + "item_id": 24737, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 652, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Arisen of the Black Dragon", + "enemy_id": "0x080504", + "level": 100, + "exp": 0, + "is_boss": true + } + ] + }, + { + "comment": "Adds", + "stage_id": { + "id": 652, + "group_id": 4 + }, + "enemies": [ + { + "comment": "Black Sword", + "enemy_id": "0x080505", + "level": 100, + "exp": 0, + "enemy_target_types_id": 1, + "start_think_tbl_no": 1, + "is_manual_set": true, + "is_required": false, + "repop_count": 50, + "repop_wait_second": 0 + } + ] + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 652 + } + }, + { + "type": "KillGroup", + "announce_type": "Start", + "groups": [0] + }, + { + "type": "PlayEvent", + "flags": [], + "stage_id": { + "id": 652 + }, + "event_id": 15 + } + ] + }, + { + "blocks": [ + { + "comment": "Spawn", + "type": "SpawnGroup", + "groups": [1] + } + ] + }, + { + "blocks": [ + { + "type": "KillGroup", + "groups": [1] + }, + { + "comment": "Spawn", + "type": "SpawnGroup", + "groups": [1] + } + ] + } + ] +} From de8eb02f54c8aefd4100782c3dae9ba652c0fc49 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 19 Sep 2024 16:30:21 -0400 Subject: [PATCH 091/116] Fix altered Zuhl nameplate --- .../Files/Assets/quests/q50206000.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json index ea4ecfbfd..429bbd21f 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json @@ -78,7 +78,7 @@ "exp": 0, "index": 0, "is_boss": true, - "named_enemy_params_id": 1699 + "named_enemy_params_id": 1698 } ] }, @@ -99,7 +99,7 @@ "repop_count": 50, "repop_wait_second": 60, "enemy_target_types_id": 1, - "named_enemy_params_id": 1698 + "named_enemy_params_id": 1699 }, { "comment": "Strix", @@ -110,7 +110,7 @@ "repop_count": 50, "repop_wait_second": 60, "enemy_target_types_id": 1, - "named_enemy_params_id": 1698 + "named_enemy_params_id": 1699 }, { "comment": "Strix", @@ -121,7 +121,7 @@ "repop_count": 50, "repop_wait_second": 60, "enemy_target_types_id": 1, - "named_enemy_params_id": 1698 + "named_enemy_params_id": 1699 }, { "comment": "Grimwarg", @@ -132,7 +132,7 @@ "repop_count": 50, "repop_wait_second": 60, "enemy_target_types_id": 1, - "named_enemy_params_id": 1698 + "named_enemy_params_id": 1699 }, { "comment": "Grimwarg", @@ -143,7 +143,7 @@ "repop_count": 50, "repop_wait_second": 60, "enemy_target_types_id": 1, - "named_enemy_params_id": 1698 + "named_enemy_params_id": 1699 }, { "comment": "Grimwarg", @@ -154,7 +154,7 @@ "repop_count": 50, "repop_wait_second": 60, "enemy_target_types_id": 1, - "named_enemy_params_id": 1698 + "named_enemy_params_id": 1699 } ] }, From 259ee5d52e446c161d35b83b708aace4156660d4 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 19 Sep 2024 18:38:54 -0400 Subject: [PATCH 092/116] Fix issues found - Fixed an issue with the crimson HS weapon having the wrong price. - Adjusted the Zuhl nameplate in EXM. - Adjusted the check for the spirit dragon adds. --- .../Files/Assets/SpecialShops.json | 2 +- .../Files/Assets/quests/q50206000.json | 13 +++++-------- .../Files/Assets/quests/q50300010.json | 9 +++------ 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/SpecialShops.json b/Arrowgene.Ddon.Shared/Files/Assets/SpecialShops.json index f961450d7..f52d4523d 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/SpecialShops.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/SpecialShops.json @@ -29800,7 +29800,7 @@ "label": "<Exch> Crimson Firangi", "base_items": [ { - "item_id": 19179, + "item_id": 19147, "name": "Central Tree Drop", "amount": 140 } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json index 429bbd21f..5f7075089 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50206000.json @@ -614,32 +614,29 @@ { "type": "Raw", "check_commands": [ - {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 85} + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 81} ] }, { "comment": "Spawn Crystals", "type": "SpawnGroup", "check_commands": [ - {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 65} + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 70} ], "groups": [8] }, { "type": "DestroyGroup", - "groups": [8] - }, - { - "type": "Raw", + "groups": [8], "check_commands": [ - {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 45} + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 41} ] }, { "comment": "Spawn Crystals", "type": "SpawnGroup", "check_commands": [ - {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 25} + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 30} ], "groups": [9] }, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json index c6904a5fa..586d03817 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300010.json @@ -418,7 +418,7 @@ { "type": "Raw", "check_commands": [ - {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 80} + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 81} ] }, { @@ -431,12 +431,9 @@ }, { "type": "DestroyGroup", - "groups": [3] - }, - { - "type": "Raw", + "groups": [3], "check_commands": [ - {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 40} + {"type": "EmHpLess", "Param1": 436, "Param2": 0, "Param3": 0, "Param4": 41} ] }, { From 73d1136fe701c9656d7b7f4cb9208b17957cddcc Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Thu, 19 Sep 2024 13:24:14 -0700 Subject: [PATCH 093/116] Hotfix: Guard against null quests. --- Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index af27a869f..ae628a910 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -138,6 +138,13 @@ public void AddNewQuest(Quest quest, uint step = 0, bool questStarted = false) { lock (ActiveQuests) { + if (quest == null) + { + // Might be a removed quest (or one in development). + Logger.Error($"Unable to locate quest data."); + return; + } + ActiveQuests[quest.QuestId] = new QuestState() { QuestId = quest.QuestId, @@ -251,6 +258,13 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted, uint vari { Quest quest = QuestManager.GetQuest(questId, variantId); + if (quest == null) + { + // Might be progress from removed quest (or one in development). + Logger.Error($"Unable to locate quest data for {questId}"); + return; + } + if (VariantQuests.Contains(questId)) { ActiveVariantQuests[questId] = (uint)quest.VariantId; From 38109c530b17866bcac6b0351b277c14b6b5adc9 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 19 Sep 2024 23:39:38 -0400 Subject: [PATCH 094/116] feat: Add new EXM "Flames of Darkness: Restricted Area" - Added new json "q50303000.json". - Added a new hook system which can be used to react to SET OM instant events. - Added new JSON parsing for the Instant OM hook. - Fixed an issue with the final CS in "q50300001.json". --- .../Context/ContextManager.cs | 11 ++ ...nstanceExchangeOmInstantKeyValueHandler.cs | 1 + .../InstanceSetOmInstantKeyValueHandler.cs | 17 +- .../Quests/GenericQuest.cs | 2 + Arrowgene.Ddon.GameServer/Quests/Quest.cs | 30 +++- Arrowgene.Ddon.Shared/Asset/QuestAsset.cs | 2 + .../AssetReader/QuestAssetDeserializer.cs | 57 +++++- .../Files/Assets/quests/q50300001.json | 2 +- .../Files/Assets/quests/q50303000.json | 162 ++++++++++++++++++ .../Model/Quest/QuestOmInteractEvent.cs | 16 +- .../Model/Quest/QuestServerAction.cs | 24 +++ 11 files changed, 314 insertions(+), 10 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50303000.json create mode 100644 Arrowgene.Ddon.Shared/Model/Quest/QuestServerAction.cs diff --git a/Arrowgene.Ddon.GameServer/Context/ContextManager.cs b/Arrowgene.Ddon.GameServer/Context/ContextManager.cs index ef2f1e4fa..9e70abca2 100644 --- a/Arrowgene.Ddon.GameServer/Context/ContextManager.cs +++ b/Arrowgene.Ddon.GameServer/Context/ContextManager.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Permissions; namespace Arrowgene.Ddon.GameServer.Context { @@ -67,6 +68,16 @@ public enum UIDKind : byte private static readonly Bitfield LayoutId = new Bitfield(29, 25, "LayoutId"); private static readonly Bitfield InnerId = new Bitfield(34, 30, "InnerId"); + public static bool IsOmUID(ulong value) + { + return ((byte) Kind.Get(value)) == (byte) UIDKind.OM; + } + + public static uint GetStageId(ulong value) + { + return (uint) StageId.Get(value); + } + public static ulong CreateEnemyUID(ulong setId, CDataStageLayoutId stageLayoutId) { return (Kind.Value((ulong) UIDKind.Enemy) | diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceExchangeOmInstantKeyValueHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceExchangeOmInstantKeyValueHandler.cs index 55053d9a5..60a789f4d 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceExchangeOmInstantKeyValueHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceExchangeOmInstantKeyValueHandler.cs @@ -5,6 +5,7 @@ using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Instance; +using Arrowgene.Ddon.Shared.Entity.Structure; namespace Arrowgene.Ddon.GameServer.Handler { diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceSetOmInstantKeyValueHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceSetOmInstantKeyValueHandler.cs index cb5490311..791b202ab 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceSetOmInstantKeyValueHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceSetOmInstantKeyValueHandler.cs @@ -1,11 +1,10 @@ +using Arrowgene.Ddon.GameServer.Characters; +using Arrowgene.Ddon.GameServer.Instance; 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.PacketStructure; -using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.GameServer.Characters; -using Arrowgene.Ddon.GameServer.Instance; namespace Arrowgene.Ddon.GameServer.Handler { @@ -32,6 +31,16 @@ public override void Handle(GameClient client, StructurePacket EnemyGroups { get; set; } public HashSet UniqueEnemyGroups { get; protected set; } + public List ServerActions { get; protected set; } public bool IsVariantQuest { get; set; } public uint VariantId { get; set; } @@ -99,6 +100,7 @@ public Quest(QuestId questId, QuestId questScheduleId, QuestType questType, bool IsVariantQuest = false; VariantId = 0; MissionParams = new QuestMissionParams(); + ServerActions = new List(); Processes = new List(); } @@ -581,7 +583,7 @@ public virtual void PopulateStartingEnemyData(PartyQuestState partyQuestState) continue; } - foreach (var groupId in process.Blocks[(int)processState.BlockNo - 1].EnemyGroupIds) + foreach (var groupId in process.Blocks[processState.BlockNo - 1].EnemyGroupIds) { var enemyGroup = EnemyGroups[groupId]; partyQuestState.SetInstanceEnemies(this, enemyGroup.StageId, (ushort)enemyGroup.SubGroupId, enemyGroup.CreateNewInstance()); @@ -589,6 +591,32 @@ public virtual void PopulateStartingEnemyData(PartyQuestState partyQuestState) } } + public virtual void HandleOmInstantValue(GameClient client, ulong key, uint value) + { + // Remove the valid bit (that way json doesn't need to provide it) + key = key & 0x7fffffffffffffff; + foreach (var action in ServerActions) + { + if (action.Key == key && action.Value == value && action.ActionType == QuestSeverActionType.OmSetInstantValue) + { + switch (action.OmInstantValueAction) + { + case OmInstantValueAction.ResetGroup: + client.Party.SendToAll(new S2CInstanceEnemyGroupResetNtc() + { + LayoutId = new CDataStageLayoutId() + { + StageId = action.StageId.Id, + GroupId = action.StageId.GroupId, + LayerNo = action.StageId.LayerNo + } + }); + break; + } + } + } + } + public List GetQuestRewards() { List rewards = new List(); diff --git a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs index a97ddd325..65c47a6d4 100644 --- a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs +++ b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs @@ -45,6 +45,7 @@ public class QuestAssetData public Dictionary EnemyGroups { get; set; } public uint VariantId { get; set; } public QuestMissionParams MissionParams { get; set; } + public List ServerActions { get; set; } public QuestAssetData() { @@ -57,6 +58,7 @@ public QuestAssetData() EnemyGroups = new Dictionary(); OrderConditions = new List(); MissionParams = new QuestMissionParams(); + ServerActions = new List(); } } } diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index 87197b384..b612f6a17 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -159,6 +159,11 @@ private bool ParseQuest(QuestAssetData assetData, JsonElement jQuest) ParseRewards(assetData, jQuest); + if (!ParseServerActions(assetData, jQuest)) + { + return false; + } + if (!ParseOrderCondition(assetData, jQuest)) { return false; @@ -632,7 +637,7 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) if (!Enum.TryParse(jblock.GetProperty("interact_type").GetString(), true, out OmInteractType interactType)) { - Logger.Error($"Unable to parse the quest typ in block @ index {blockIndex - 1}."); + Logger.Error($"Unable to parse the quest type in block @ index {blockIndex - 1}."); return false; } @@ -648,7 +653,6 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) } questBlock.OmInteractEvent.QuestType = questType; - questBlock.OmInteractEvent.InteractType = interactType; } break; case QuestBlockType.DeliverItems: @@ -919,6 +923,55 @@ private bool ParseMissionParams(QuestAssetData assetData, JsonElement jMissionPa return true; } + private bool ParseServerActions(QuestAssetData assetData, JsonElement quest) + { + if (!quest.TryGetProperty("server_actions", out JsonElement jServerActions)) + { + // It is optional to provide this list + return true; + } + + foreach (var jServerAction in jServerActions.EnumerateArray()) + { + var action = new QuestServerAction(); + + if (!jServerAction.TryGetProperty("action_type", out JsonElement jActionType)) + { + Logger.Error("Unable to find the server action type. Exiting."); + return false; + } + + if (!Enum.TryParse(jActionType.GetString(), true, out QuestSeverActionType actionType)) + { + Logger.Error("Unable to decode the server action type. Exiting."); + return false; + } + + action.ActionType = actionType; + if (actionType == QuestSeverActionType.OmSetInstantValue) + { + if (!jServerAction.TryGetProperty("instant_value_action", out JsonElement jInstantValueAction)) + { + Logger.Error("Failed to locate the instant_value_action field. Exiting."); + return false; + } + + if (!Enum.TryParse(jInstantValueAction.ToString(), true, out OmInstantValueAction instantValueAction)) + { + Logger.Error("Failed to decode the instant_value_action field. Exiting."); + } + action.OmInstantValueAction = instantValueAction; + action.Key = jServerAction.GetProperty("key").GetUInt64(); + action.Value = jServerAction.GetProperty("value").GetUInt32(); + action.StageId = ParseStageId(jServerAction.GetProperty("stage_id")); + } + + assetData.ServerActions.Add(action); + } + + return true; + } + private bool ParseEnemyGroups(QuestAssetData assetData, JsonElement quest) { if (!quest.TryGetProperty("enemy_groups", out JsonElement jGroups)) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300001.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300001.json index 95697d7ea..aa883fc1d 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300001.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50300001.json @@ -184,7 +184,7 @@ "stage_id": { "id": 82 }, - "event_id": 5 + "event_id": 10 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50303000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50303000.json new file mode 100644 index 000000000..58d0aa028 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50303000.json @@ -0,0 +1,162 @@ +{ + "state_machine": "GenericStateMachine", + "type": "ExtremeMission", + "comment": "The Flames of Darkness: Restricted Area", + "quest_id": 50303000, + "base_level": 95, + "minimum_item_rank": 110, + "discoverable": false, + "mission_params": { + "group": 7, + "minimum_members": 1, + "playtime": 900, + "solo_only": false, + "max_pawns": 3, + "phase_groups": [] + }, + "order_conditions": [], + "rewards": [ + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 18741, + "num": 1 + } + ] + } + ], + "enemy_groups" : [ + { + "comment": "Boss", + "stage_id": { + "id": 586, + "group_id": 1 + }, + "subgroup_id": 1, + "enemies": [ + { + "comment": "Ifrit (1st form)", + "enemy_id": "0x020803", + "level": 95, + "exp": 0, + "is_boss": true, + "start_think_tbl_no": 2 + } + ] + }, + { + "comment": "Boss", + "stage_id": { + "id": 586, + "group_id": 1 + }, + "subgroup_id": 0, + "enemies": [ + { + "comment": "Ifrit (2nd form)", + "enemy_id": "0x020804", + "level": 95, + "exp": 0, + "is_boss": true, + "start_think_tbl_no": 1, + "montage_fix_no": 1 + } + ] + }, + { + "comment": "Blaze Harpy", + "stage_id": { + "id": 586, + "group_id": 2 + }, + "starting_index": 1, + "enemies": [ + { + "enemy_id": "0x010608", + "level": 95, + "exp": 0, + "repop_count": 50, + "repop_wait_second": 60, + "enemy_target_types_id": 1 + }, + { + "enemy_id": "0x010608", + "level": 95, + "exp": 0, + "repop_count": 50, + "repop_wait_second": 60, + "enemy_target_types_id": 1 + }, + { + "enemy_id": "0x010608", + "level": 95, + "exp": 0, + "repop_count": 50, + "repop_wait_second": 60, + "enemy_target_types_id": 1 + }, + { + "enemy_id": "0x010608", + "level": 95, + "exp": 0, + "repop_count": 50, + "repop_wait_second": 60, + "enemy_target_types_id": 1 + } + ] + } + ], + "server_actions": [ + { + "action_type": "OmSetInstantValue", + "instant_value_action": "ResetGroup", + "stage_id": { + "id": 586, + "group_id": 1, + "layer_no": 0 + }, + "key": 9379, + "value": 8 + } + ], + "processes": [ + { + "blocks": [ + { + "type": "IsGatherPartyInStage", + "stage_id": { + "id": 586 + } + }, + { + "type": "SpawnGroup", + "announce_type": "Start", + "groups": [0], + "check_commands": [ + {"type": "EmHpLess", "Param1": 138, "Param2": 1, "Param3": 0, "Param4": 85} + ] + }, + { + "type": "KillGroup", + "reset_group": false, + "groups": [1] + } + ] + }, + { + "blocks": [ + { + "type": "Raw", + "check_commands": [ + {"type": "EmHpLess", "Param1": 138, "Param2": 1, "Param3": 0, "Param4": 40} + ] + }, + { + "type": "SpawnGroup", + "groups": [2] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestOmInteractEvent.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestOmInteractEvent.cs index fc9936b27..6fa121ca9 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestOmInteractEvent.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestOmInteractEvent.cs @@ -9,11 +9,13 @@ namespace Arrowgene.Ddon.Shared.Model.Quest public enum OmQuestType { MyQuest, - WorldManageQuest + WorldManageQuest, + Global } public enum OmInteractType { + None, Touch, Release, EndText, @@ -23,7 +25,14 @@ public enum OmInteractType IsBrokenQuest, TouchNpcUnitMarker, TouchActNpc, - UsedKey + UsedKey, + SetInstantValue + } + + public enum OmInstantValueAction + { + None, + ResetGroup } public class QuestOmInteractEvent @@ -31,5 +40,8 @@ public class QuestOmInteractEvent public QuestId QuestId { get; set; } // Some OM interactions require a quest ID to source marker information from. public OmQuestType QuestType { get; set; } public OmInteractType InteractType { get; set; } + public OmInstantValueAction InstantAction { get; set; } + public ulong Key { get; set; } + public uint Value { get; set; } } } diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestServerAction.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestServerAction.cs new file mode 100644 index 000000000..20f2b5971 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestServerAction.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Model.Quest +{ + public enum QuestSeverActionType + { + None, + OmSetInstantValue + } + + public class QuestServerAction + { + public QuestSeverActionType ActionType { get; set; } + public StageId StageId { get; set; } + public ulong Key { get; set; } + public uint Value { get; set; } + public OmInstantValueAction OmInstantValueAction { get; set; } + } +} From 27b61ce384baf571aa2bdffce0196ed45bde8be5 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 20 Sep 2024 18:01:00 -0400 Subject: [PATCH 095/116] Code Review - Added comment about OM value - Adjusted rewards --- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 18 ++--- .../Files/Assets/quests/q50303000.json | 66 +++++++++++++++++++ 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index a2a21ab32..930f6f1a8 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -385,26 +385,22 @@ public virtual CDataTimeGainQuestList ToCDataTimeGainQuestList(uint step) HashSet items = new HashSet(); // Rewards for EXM seem to show up independently - foreach (var reward in result.Param.FixedRewardItemList) + foreach (var rewardData in this.ItemRewards) { - if (MissionParams.LootDistribution == QuestLootDistribution.TimeBased) + foreach (var reward in rewardData.LootPool) { - if (!items.Contains(reward.ItemId)) + if (rewardData.RewardType == QuestRewardType.Fixed) { - // Show 1 of each item as exact value is unknown result.RewardItemDetailList.Add(new CDataRewardItemDetail() { ItemId = reward.ItemId, - Num = 1, - Type = 12 + Num = reward.Num, + Type = 11 }); - items.Add(reward.ItemId); } - } - else - { - for (var i = 0; i < reward.Num; i++) + else if (rewardData.RewardType == QuestRewardType.Random && !items.Contains(reward.ItemId)) { + items.Add(reward.ItemId); result.RewardItemDetailList.Add(new CDataRewardItemDetail() { ItemId = reward.ItemId, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50303000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50303000.json index 58d0aa028..06709942d 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50303000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50303000.json @@ -24,6 +24,71 @@ "num": 1 } ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 21231, + "num": 1, + "chance": 0.50 + }, + { + "item_id": 21231, + "num": 2, + "chance": 0.40 + }, + { + "item_id": 21231, + "num": 3, + "chance": 0.10 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 16030, + "num": 1, + "chance": 0.75 + }, + { + "item_id": 16035, + "num": 1, + "chance": 0.25 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 25656, + "num": 0, + "chance": 0.75 + }, + { + "item_id": 25656, + "num": 1, + "chance": 0.25 + } + ] + }, + { + "type": "random", + "loot_pool": [ + { + "item_id": 25672, + "num": 0, + "chance": 0.75 + }, + { + "item_id": 25672, + "num": 1, + "chance": 0.25 + } + ] } ], "enemy_groups" : [ @@ -109,6 +174,7 @@ ], "server_actions": [ { + "comment": "Detects when Ifirt destroys the stage, so the second form can come out.", "action_type": "OmSetInstantValue", "instant_value_action": "ResetGroup", "stage_id": { From 671178f6c09e4eb1fd891837c1708c37c5c355e7 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Thu, 19 Sep 2024 22:34:13 -0700 Subject: [PATCH 096/116] Track last safe stage and return it on death. --- .../Handler/StageAreaChangeHandler.cs | 1 + .../Handler/WarpGetReturnLocationHandler.cs | 97 +++++++++++++++++-- Arrowgene.Ddon.Shared/Model/Character.cs | 2 +- 3 files changed, 91 insertions(+), 9 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs b/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs index f3c390787..4daff1ab9 100644 --- a/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/StageAreaChangeHandler.cs @@ -45,6 +45,7 @@ public override S2CStageAreaChangeRes Handle(GameClient client, C2SStageAreaChan if (StageManager.IsSafeArea(client.Character.Stage)) { res.IsBase = true; + client.Character.LastSafeStageId = packet.StageId; bool shouldReset = true; // Check to see if all player members are in a safe area. diff --git a/Arrowgene.Ddon.GameServer/Handler/WarpGetReturnLocationHandler.cs b/Arrowgene.Ddon.GameServer/Handler/WarpGetReturnLocationHandler.cs index 2292d78d5..348e5233f 100644 --- a/Arrowgene.Ddon.GameServer/Handler/WarpGetReturnLocationHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/WarpGetReturnLocationHandler.cs @@ -1,28 +1,109 @@ using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Model; -using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using System.Collections.Generic; namespace Arrowgene.Ddon.GameServer.Handler { - public class WarpGetReturnLocationHandler : StructurePacketHandler + public class WarpGetReturnLocationHandler : GameRequestPacketHandler { private static readonly ServerLogger Logger = LogProvider.Logger(typeof(WarpGetReturnLocationHandler)); + /// + /// What startpos to use for each map. This is usually in front of a riftstone. + /// + private static readonly Dictionary RespawnStartPosMap = new() + { + { 2, 0}, // White Dragon Temple + { 341, 2}, // Dana Centrum + { 487, 2}, // Fortress City Megado: Residential Level + { 24, 1}, // White Deer Inn + { 25, 2}, // Black Grape Inn + { 26, 1}, // Sea Dragon Inn + { 48, 1}, // Singing Winds Inn + { 52, 1}, // Red Crystal Inn + { 53, 1}, // Sleeping Wolf Inn + { 61, 1}, // Golden Tankard Inn + { 66, 2}, // Gritten Fort + { 78, 4}, // Pawn Cathedral + { 95, 2}, // Hobolic Cave + { 137, 2}, // Mysree Grove Shrine + { 139, 2}, // Zandora Wastelands Shrine + { 237, 1}, // Mergoda Residential Area + { 317, 2}, // Expedition Garrison + { 339, 2}, // Protector's Retreat + { 340, 4}, // Morfaul Centrum + { 377, 1}, // Glyndwr Centrum + { 384, 1}, // Hollow of Beginnings + { 400, 2}, // Tower of Ivanos + { 411, 3}, // Manun Village + { 467, 6}, // Fort Thines + { 478, 2}, // Lookout Castle + { 480, 6}, // Bertha's Bandit Group + { 511, 1}, // Piremoth Traveler's Inn + { 512, 1}, // Rothgill Traveler's Inn + { 520, 1}, // Mephite Traveler's Inn + { 549, 1}, // Heroic Spirit Sleeping Path: Rathnite Foothills + { 557, 2}, // Heroic Spirit Sleeping Path: Feryana Wilderness + { 558, 5}, // Old Heroic Spirit Shrine + { 584, 2}, // Eli Guard Tower + { 594, 1}, // Northern Bandit Hideout + { 602, 0}, // Bitterblack Maze Cove + }; + + /// + /// Some safe areas are sub-areas of other safe areas, so redirect to the main one when needed. + /// + private static readonly Dictionary SafeStageRedirect = new() + { + {4, 2 }, // Craft Room -> WDT + {5, 2 }, // Cave Harbor -> WDT + {141, 2 }, // Summer Beach Area -> WDT + {347, 2 }, // Clan Hall -> WDT + {348, 2 }, // Arisen's Room -> WDT + {401, 411 }, // Spirit Arts Hut -> Manun Village + {576, 467 }, // Fort Thines: Great Hall -> Fort Thines + {580, 487 }, // Megado Craft Room -> Megado Residential Level + {602 , 2 }, // Bitterblack Maze Cove -> WDT, but only for GameMode.Normal + }; + public WarpGetReturnLocationHandler(DdonGameServer server) : base(server) { } - public override void Handle(GameClient client, StructurePacket packet) + public override S2CWarpGetReturnLocationRes Handle(GameClient client, C2SWarpGetReturnLocationReq request) { S2CWarpGetReturnLocationRes response = new S2CWarpGetReturnLocationRes(); - // WDT or BBM Cove - response.JumpLocation.stageId = (uint)((client.GameMode == GameMode.Normal) ? 3 : 602); - response.JumpLocation.startPos = 0; - client.Send(response); + Logger.Info($"LastSafeStageId: {client.Character.LastSafeStageId}"); + + if (client.GameMode == GameMode.BitterblackMaze) + { + response.JumpLocation.stageId = 602; + } + else + { + if (SafeStageRedirect.ContainsKey(client.Character.LastSafeStageId)) + { + response.JumpLocation.stageId = SafeStageRedirect[client.Character.LastSafeStageId]; + } + else + { + response.JumpLocation.stageId = client.Character.LastSafeStageId; + } + } + + if (RespawnStartPosMap.ContainsKey(response.JumpLocation.stageId)) + { + response.JumpLocation.startPos = RespawnStartPosMap[response.JumpLocation.stageId]; + } + else + { + response.JumpLocation.startPos = 0; + } + + return response; } } } diff --git a/Arrowgene.Ddon.Shared/Model/Character.cs b/Arrowgene.Ddon.Shared/Model/Character.cs index 4acc5b888..c5b8d5c6c 100644 --- a/Arrowgene.Ddon.Shared/Model/Character.cs +++ b/Arrowgene.Ddon.Shared/Model/Character.cs @@ -117,7 +117,7 @@ public uint ContentCharacterId public uint MaxBazaarExhibits { get; set; } public Dictionary CompletedQuests { get; set; } - // --- + public uint LastSafeStageId { get; set; } // TODO: Move to a more sensible place public uint LastEnteredShopId { get; set; } From d197a7f1f3af670ca3979b5da4e5f73a8cbd9c81 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Fri, 20 Sep 2024 22:23:34 -0700 Subject: [PATCH 097/116] More rigorous matching when applying a translation patch. --- Arrowgene.Ddon.Cli/Command/ClientCommand.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Arrowgene.Ddon.Cli/Command/ClientCommand.cs b/Arrowgene.Ddon.Cli/Command/ClientCommand.cs index f875be6c4..a93a8219a 100644 --- a/Arrowgene.Ddon.Cli/Command/ClientCommand.cs +++ b/Arrowgene.Ddon.Cli/Command/ClientCommand.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -221,15 +221,9 @@ public CommandResultType Run(CommandParameter parameter) GmdCsv.Entry matchCsvEntry = null; foreach (GmdCsv.Entry csvEntry in gmdCsvEntries) { - if (!string.IsNullOrEmpty(entry.Key) && csvEntry.Key == entry.Key) - { - // matched based on key - matchCsvEntry = csvEntry; - break; - } - - if (entry.ReadIndex == csvEntry.ReadIndex) + if (!string.IsNullOrEmpty(entry.Key) && csvEntry.Key == entry.Key && entry.ReadIndex == csvEntry.ReadIndex) { + // Both key AND index have to match, because there are gui entries with duplicate keys. matchCsvEntry = csvEntry; break; } From 3c06e4b8169f8724444e9b671091b25a3fbde067 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 20 Sep 2024 18:41:34 -0400 Subject: [PATCH 098/116] Remove Meat Monsters from Quest Files Removed meat monsters controlled by quest files in the season 1 story quests. --- .../Files/Assets/EnemySpawn.json | 1036 ++++++++++++++++- .../Files/Assets/quests/q00000018.json | 151 +-- .../Files/Assets/quests/q00000020.json | 50 +- .../Files/Assets/quests/q00000021.json | 148 --- .../Files/Assets/quests/q00000029.json | 152 --- 5 files changed, 1010 insertions(+), 527 deletions(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json index 3737869ff..0c2a1361f 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json @@ -179406,34 +179406,6 @@ 7, "00:00,23:59" ], - [ - 77, - 0, - 9, - 0, - "0x010110", - 2298, - 0, - 100, - 49, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - false, - false, - false, - false, - 0, - 0, - 5834, - 101, - "00:00,23:59" - ], [ 77, 0, @@ -249545,6 +249517,1014 @@ 310, -1, "00:00,23:59" + ], + [ + 144, + 0, + 1, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5446, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 1, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5446, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 7, + 2, + "0x010312", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 7, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 7, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 15, + 2, + "0x010312", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 15, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 15, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 15, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 17, + 2, + "0x010312", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 17, + 2, + "0x010312", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 17, + 2, + "0x010308", + 167, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 17, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 144, + 0, + 17, + 2, + "0x010900", + 2298, + 0, + 100, + 48, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5734, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 2, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 2, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 2, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 3, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 3, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 3, + 2, + "0x010312", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 21, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 21, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 21, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 21, + 2, + "0x010312", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 25, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 25, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 25, + 2, + "0x010910", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 145, + 0, + 25, + 2, + "0x010312", + 2298, + 0, + 100, + 50, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 5936, + -1, + "00:00,23:59" + ], + [ + 77, + 0, + 9, + 2, + "0x010110", + 2298, + 0, + 100, + 51, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6040, + -1, + "00:00,23:59" + ], + [ + 77, + 0, + 9, + 2, + "0x010111", + 2298, + 0, + 100, + 51, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6040, + -1, + "00:00,23:59" + ], + [ + 77, + 0, + 9, + 2, + "0x010113", + 2298, + 0, + 100, + 51, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6040, + -1, + "00:00,23:59" + ], + [ + 77, + 0, + 9, + 2, + "0x010114", + 2298, + 0, + 100, + 51, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6040, + -1, + "00:00,23:59" + ], + [ + 77, + 0, + 14, + 2, + "0x010302", + 2298, + 0, + 100, + 51, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6040, + -1, + "00:00,23:59" + ], + [ + 77, + 0, + 14, + 2, + "0x010302", + 2298, + 0, + 100, + 51, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6040, + -1, + "00:00,23:59" + ], + [ + 77, + 0, + 14, + 2, + "0x010302", + 2298, + 0, + 100, + 51, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6040, + -1, + "00:00,23:59" + ], + [ + 77, + 0, + 14, + 2, + "0x010302", + 2298, + 0, + 100, + 51, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6040, + -1, + "00:00,23:59" ] ] } \ No newline at end of file diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000018.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000018.json index 5471429f3..f82a917ea 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000018.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000018.json @@ -59,145 +59,6 @@ } ] }, - { - "comment": "Meat Monsters (1, 2)", - "stage_id": { - "id": 144, - "group_id": 1 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (7, 2)", - "stage_id": { - "id": 144, - "group_id": 7 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (17, 2)", - "stage_id": { - "id": 144, - "group_id": 17 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Alchemized Skeleton Mage", - "enemy_id": "0x010308", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1, - "named_enemy_params_id": 167 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (15, 2)", - "stage_id": { - "id": 144, - "group_id": 15 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 45, - "exp": 5446, - "enemy_target_types_id": 1 - } - ] - }, { "comment": "After Boss Monster Rush", "stage_id": { @@ -372,16 +233,6 @@ } ] }, - { - "comment": "Process 1 (Subgroup Process)", - "blocks": [ - { - "comment": "Spawns Enemies for meat monsters", - "type": "SpawnGroup", - "groups": [1, 2, 3, 4] - } - ] - }, { "comment": "Process 2 (After Boss Monsters)", "blocks": [ @@ -392,7 +243,7 @@ { "comment": "Spawns Enemies for meat monsters", "type": "SpawnGroup", - "groups": [5] + "groups": [1] } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000020.json index 2173a729c..a0fb267b2 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000020.json @@ -52,44 +52,6 @@ } ], "enemy_groups": [ - { - "comment": "Meat Monsters (14, 2)", - "stage_id": { - "id": 77, - "group_id": 14 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Skeleton Warrior", - "enemy_id": "0x010302", - "level": 51, - "exp": 6040, - "enemy_target_types_id": 1 - }, - { - "comment" : "Skeleton Warrior", - "enemy_id": "0x010302", - "level": 51, - "exp": 6040, - "enemy_target_types_id": 1 - }, - { - "comment" : "Skeleton Warrior", - "enemy_id": "0x010302", - "level": 51, - "exp": 6040, - "enemy_target_types_id": 1 - }, - { - "comment" : "Skeleton Warrior", - "enemy_id": "0x010302", - "level": 51, - "exp": 6040, - "enemy_target_types_id": 1 - } - ] - }, { "comment": "Boss Group 1", "stage_id": { @@ -315,7 +277,7 @@ {"type": "MyQst", "action": "Set", "value": 120, "comment": "Starts Leo's FSM"} ], "announce_type": "Update", - "groups": [1] + "groups": [0] }, { "type": "PlayEvent", @@ -424,16 +386,6 @@ "result_commands": [] } ] - }, - { - "comment": "Meat Spawner", - "blocks": [ - { - "comment": "Spawns Enemies for meat monsters", - "type": "SpawnGroup", - "groups": [0] - } - ] } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000021.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000021.json index a469bcaa4..13a36087d 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000021.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000021.json @@ -68,144 +68,6 @@ "is_boss": true } ] - }, - { - "comment": "Meat Monsters (2, 2)", - "stage_id": { - "id": 145, - "group_id": 2 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (3, 2)", - "stage_id": { - "id": 145, - "group_id": 3 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (21, 2)", - "stage_id": { - "id": 145, - "group_id": 21 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (25, 2)", - "stage_id": { - "id": 145, - "group_id": 25 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Blob", - "enemy_id": "0x010910", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - }, - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 50, - "exp": 5936, - "enemy_target_types_id": 1 - } - ] } ], "processes": [ @@ -358,16 +220,6 @@ "message_id": 11743 } ] - }, - { - "comment": "Meat Spawner", - "blocks": [ - { - "comment": "Spawns Enemies for meat monsters", - "type": "SpawnGroup", - "groups": [1, 2, 3] - } - ] } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000029.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000029.json index f9e0255d4..aa7753542 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000029.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000029.json @@ -108,145 +108,6 @@ } ] }, - { - "comment": "Meat Monsters (1, 2)", - "stage_id": { - "id": 144, - "group_id": 1 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5446, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5446, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (7, 2)", - "stage_id": { - "id": 144, - "group_id": 7 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (17, 2)", - "stage_id": { - "id": 144, - "group_id": 17 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - }, - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - }, - { - "comment" : "Alchemized Skeleton Mage", - "enemy_id": "0x010308", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1, - "named_enemy_params_id": 167 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - } - ] - }, - { - "comment": "Meat Monsters (15, 2)", - "stage_id": { - "id": 144, - "group_id": 15 - }, - "subgroup_id": 2, - "enemies": [ - { - "comment" : "Alchemized Skeleton", - "enemy_id": "0x010312", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - }, - { - "comment" : "Slime", - "enemy_id": "0x010900", - "level": 48, - "exp": 5734, - "enemy_target_types_id": 1 - } - ] - }, { "comment": "After Boss Monster Rush", "stage_id": { @@ -311,9 +172,6 @@ "id": 144 } }, - - - { "type": "PartyGather", "announce_type": "Update", @@ -423,16 +281,6 @@ "event_id": 75 } ] - }, - { - "comment": "Process 1 (Subgroup Process)", - "blocks": [ - { - "comment": "Spawns Enemies for meat monsters", - "type": "SpawnGroup", - "groups": [2, 3, 4, 5] - } - ] } ] } From 633a216e1d6223c20146116bc870febb4a4d1c72 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 11 Sep 2024 16:44:19 -0400 Subject: [PATCH 099/116] feat: Season 2.0 MSQ "The Storm That Brought A Tragedy" --- .../Files/Assets/quests/q00000030.json | 2 +- .../Files/Assets/quests/q00020010.json | 241 ++++++++++++++++++ Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs | 2 +- docs/quests/events/st0100.md | 2 +- docs/quests/events/st0201.md | 6 +- 5 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020010.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000030.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000030.json index f67bf6c8d..308319b25 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000030.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000030.json @@ -3,7 +3,7 @@ "type": "Main", "comment": "Be Forevermore, White Dragon", "quest_id": 30, - "next_quest": 0, + "next_quest": 20010, "base_level": 55, "minimum_item_rank": 0, "discoverable": true, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020010.json new file mode 100644 index 000000000..c58db5595 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020010.json @@ -0,0 +1,241 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "The Storm That Brought A Tragedy", + "quest_id": 20010, + "next_quest": 20020, + "base_level": 55, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "SoloWithPawns"} + ], + "rewards": [ + { + "type": "exp", + "amount": 98000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 12000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 1000 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 11784, + "num": 2 + }, + { + "item_id": 11408, + "num": 3 + }, + { + "item_id": 11407, + "num": 3 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 335, + "group_id": 3 + }, + "enemies": [ + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "level": 55, + "exp": 6476, + "named_enemy_params_id": 920 + }, + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "level": 55, + "exp": 6476, + "named_enemy_params_id": 920 + }, + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "level": 55, + "exp": 6476, + "named_enemy_params_id": 920 + }, + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "level": 55, + "exp": 6476, + "named_enemy_params_id": 920 + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 3, + "group_id": 1 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"} + ], + "npc_id": "Joseph", + "message_id": 0 + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 100 + }, + { + "type": "TalkToNpc", + "announce_type": "Accept", + "stage_id": { + "id": 3, + "group_id": 1 + }, + "flags": [], + "npc_id": "Joseph", + "message_id": 14936 + }, + { + "type": "NewTalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2762, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2640, "comment": "Spawns Gurdolin, Lise and Elliot"} + ], + "npc_id": "Gurdolin3", + "message_id": 14938 + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 1 + }, + "location": { + "x": -222507, + "y": 35, + "z": -83434 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2641, "comment": "Spawns Fabio, Gerd and Gurdolin in Breya Coast"}, + {"type": "QstLayout", "action": "Set", "value": 2977, "comment": "Spawns dead infected monster"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 105, + "jump_stage_id": { + "id": 335 + }, + "start_pos_no": 0 + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 335 + }, + "event_id": 0 + }, + { + "type": "NewTalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 335, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2642, "comment": "Gurdolin"} + ], + "npc_id": "Gurdolin3", + "message_id": 14960 + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 335 + }, + "location": { + "x": -6405, + "y": 2795, + "z": -11369 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 335 + }, + "event_id": 10 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 335 + }, + "event_id": 15, + "jump_stage_id": { + "id": 3 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 110 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3, + "group_id": 1 + }, + "flags": [], + "npc_id": "Joseph", + "message_id": 15007 + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs index c805b55b7..2dc4063c7 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs @@ -47,7 +47,7 @@ public enum QuestId : uint BeForevermoreWhiteDragon = 30, // Seems like quest names and IDs got out of sync when this list was made - Unknown20010 = 20010, + TheStormThatBroughtATragedy = 20010, TheStormThatBoughtATragedy = 20020, TheGirlWhoLostHerMemories = 20030, TheCorruptionAndTheKnights = 20040, diff --git a/docs/quests/events/st0100.md b/docs/quests/events/st0100.md index 7a56a658a..b5d450bf2 100644 --- a/docs/quests/events/st0100.md +++ b/docs/quests/events/st0100.md @@ -15,7 +15,7 @@ | 60 | The Ark, Once More | 65 | The Quandary of Soldiers | 71 | Thinking of A Friend -| 100 +| 100 | The Storm That Brought A Tragedy | Start of Season 2 | 105 | 110 | 115 diff --git a/docs/quests/events/st0201.md b/docs/quests/events/st0201.md index 9a0d93695..385467bd0 100644 --- a/docs/quests/events/st0201.md +++ b/docs/quests/events/st0201.md @@ -28,11 +28,11 @@ | 80 | The future entrusted to us | 90 | The Great Alchemist | 95 | Be Forevermore, White Dragon -| 100 | +| 100 | Credits | 103 | | 105 | -| 110 | -| 115 | +| 110 | The Storm That Brought A Tragedy | teleport player and gerd +| 115 | | Cici | 120 | | 125 | | 130 | From 656fcad862fccf2dc5a9484d37dfe2b79382dc78 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 20 Sep 2024 21:57:12 -0400 Subject: [PATCH 100/116] feat: Season 2.0 MSQ "The Girl Who Lost Her Memories" --- .../Files/Assets/quests/q00020020.json | 216 ++++++++++++++++++ docs/quests/events/st0100.md | 8 +- docs/quests/events/st0201.md | 2 +- 3 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020020.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020020.json new file mode 100644 index 000000000..e7d179df7 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020020.json @@ -0,0 +1,216 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "The Girl Who Lost Her Memories", + "quest_id": 20020, + "next_quest": 20030, + "base_level": 55, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 55} + ], + "rewards": [ + { + "type": "exp", + "amount": 128000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 18000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 1200 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13043, + "num": 1 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 1, + "group_id": 83 + }, + "enemies": [ + { + "comment": "Zuhl", + "enemy_id": "0x020402", + "level": 53, + "exp": 62540, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2692, "comment": "Spawns Gurdolin, Lise and Elliot"} + ], + "npc_id": "Lise0", + "message_id": 15008 + }, + { + "type": "PartyGather", + "announce_type": "Accept", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 3026, "comment": "Elliot and Cecily"} + ], + "stage_id": { + "id": 350 + }, + "location": { + "x": -2253, + "y": 18, + "z": -37 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 350 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3026, "comment": "Elliot and Cecily"}, + {"type": "QstLayout", "action": "Set", "value": 3440, "comment": "Elliot and Cecily (in the house)"}, + {"type": "QstLayout", "action": "Set", "value": 2764, "comment": "Elliot and Cecily (in breya coast)"} + ], + "event_id": 0 + }, + { + "type": "NewTalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 2 + }, + "npc_id": "Elliot0", + "message_id": 15022 + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3440, "comment": "Elliot and Cecily (in the house)"} + ], + "stage_id": { + "id": 1 + }, + "location": { + "x": -222265, + "y": 409, + "z": -78109 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 110 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [0] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 115 + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 1 + }, + "location": { + "x": -204091, + "y": 3345, + "z": -74870 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 120 + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "location": { + "x": -349, + "y": 9164, + "z": -29 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2692, "comment": "Spawns Gurdolin, Lise and Elliot"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 115 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 3027, "comment": "Spawns Cici, Gurdolin, Lise and Elliot"} + ], + "npc_id": "Joseph", + "message_id": 15078 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "npc_id": "Klaus0", + "message_id": 15079 + } + ] + } + ] +} diff --git a/docs/quests/events/st0100.md b/docs/quests/events/st0100.md index b5d450bf2..4d3de20e9 100644 --- a/docs/quests/events/st0100.md +++ b/docs/quests/events/st0100.md @@ -16,10 +16,10 @@ | 65 | The Quandary of Soldiers | 71 | Thinking of A Friend | 100 | The Storm That Brought A Tragedy | Start of Season 2 -| 105 -| 110 -| 115 -| 120 +| 105 | The Storm That Brought A Tragedy +| 110 | The Girl Who Lost Her Memories +| 115 | The Girl Who Lost Her Memories +| 120 | The Girl Who Lost Her Memories | 125 | 130 | 135 diff --git a/docs/quests/events/st0201.md b/docs/quests/events/st0201.md index 385467bd0..4b4f8c9eb 100644 --- a/docs/quests/events/st0201.md +++ b/docs/quests/events/st0201.md @@ -32,7 +32,7 @@ | 103 | | 105 | | 110 | The Storm That Brought A Tragedy | teleport player and gerd -| 115 | | Cici +| 115 | The Girl Who Lost Her Memories | Cici | 120 | | 125 | | 130 | From 26e6f19f01548043ea705d5186d6fc79f9f33dfd Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 20 Sep 2024 23:42:23 -0400 Subject: [PATCH 101/116] feat: Season 2.0 MSQ "The Corruption and the Knights" --- .../Files/Assets/quests/q00020030.json | 301 ++++++++++++++++++ docs/quests/events/st0100.md | 4 +- 2 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020030.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020030.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020030.json new file mode 100644 index 000000000..4836e24f1 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020030.json @@ -0,0 +1,301 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "The Corruption and the Knights", + "quest_id": 20030, + "next_quest": 20040, + "base_level": 56, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 55} + ], + "rewards": [ + { + "type": "exp", + "amount": 128000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 20000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 1500 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13053, + "num": 1 + }, + { + "item_id": 11408, + "num": 5 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 1, + "group_id": 48 + }, + "enemies": [ + { + "comment": "Infected Direwolf", + "enemy_id": "0x010209", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Direwolf", + "enemy_id": "0x010209", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Direwolf", + "enemy_id": "0x010209", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Direwolf", + "enemy_id": "0x010209", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Direwolf", + "enemy_id": "0x010209", + "level": 55, + "exp": 6476 + } + ] + }, + { + "stage_id": { + "id": 1, + "group_id": 58 + }, + "starting_index": 1, + "enemies": [ + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 55, + "exp": 6476 + } + ] + }, + { + "stage_id": { + "id": 1, + "group_id": 56 + }, + "enemies": [ + { + "comment": "Infected Gorecyclops", + "enemy_id": "0x015012", + "infection_type": 1, + "level": 55, + "exp": 64760, + "is_boss": true + }, + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "level": 55, + "exp": 6476 + }, + { + "comment": "Infected Hobgoblin Fighter", + "enemy_id": "0x010161", + "level": 55, + "exp": 6476 + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2738, "comment": "Spawns Gurdolin, Lise and Elliot"} + ], + "npc_id": "Elliot0", + "message_id": 15080 + }, + { + "type": "TalkToNpc", + "announce_type": "Accept", + "stage_id": { + "id": 2 + }, + "npc_id": "Heinz2", + "message_id": 15081 + }, + { + "type": "NewTalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2739, "comment": "Spawns Gerd"} + ], + "npc_id": "Gerd1", + "message_id": 15082 + }, + { + "type": "DiscoverEnemy", + "checkpoint": true, + "announce_type": "Update", + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 0 ] + }, + { + "type": "DiscoverEnemy", + "checkpoint": true, + "announce_type": "Update", + "groups": [ 1 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 1 ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2739, "comment": "Spawns Gerd"}, + {"type": "QstLayout", "action": "Set", "value": 3050, "comment": "Spawns Travers and WhiteKnights"} + ], + "location": { + "x": -110961, + "y": 1483, + "z": 7477 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 125 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 3051, "comment": "Spawns Gerd"}, + {"type": "MyQst", "action": "Set", "value": 1339, "comment": "Starts NPC Battle State Machine"} + ], + "groups": [ 2 ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 130 + }, + { + "type": "NewTalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 1, + "group_id": 4, + "layer_no": 1 + }, + "flags": [ + {"type": "MyQst", "action": "Clear", "value": 1199, "comment": "Starts NPC Battle State Machine"}, + {"type": "QstLayout", "action": "Clear", "value": 3050, "comment": "Spawns Travers and WhiteKnights"}, + {"type": "QstLayout", "action": "Clear", "value": 3051, "comment": "Spawns Gerd"}, + {"type": "QstLayout", "action": "Set", "value": 3124, "comment": "Spawns Gerd after battle"} + ], + "npc_id": "Gerd1", + "message_id": 15126 + }, + { + "type": "NewTalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 3, + "group_id": 2, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2769, "comment": "Spawns Gerd in Audience Chamber"} + ], + "npc_id": "Gerd1", + "message_id": 15127 + }, + { + "type": "TalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15129 + } + ] + } + ] +} diff --git a/docs/quests/events/st0100.md b/docs/quests/events/st0100.md index 4d3de20e9..c81af5070 100644 --- a/docs/quests/events/st0100.md +++ b/docs/quests/events/st0100.md @@ -20,8 +20,8 @@ | 110 | The Girl Who Lost Her Memories | 115 | The Girl Who Lost Her Memories | 120 | The Girl Who Lost Her Memories -| 125 -| 130 +| 125 | The Corruption and the Knights +| 130 | The Corruption and the Knights | 135 | 140 | 145 From eaf03cd39dc3172247b98aab4c043a84537ff0c9 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 10:53:08 -0400 Subject: [PATCH 102/116] feat: Season 2.0 MSQ "Exploring the Den of Monsters" --- .../WarpRegisterFavoriteWarpHandler.cs | 1 + .../Files/Assets/EnemySpawn.json | 840 ++++++++++++++++++ .../Files/Assets/quests/q00020040.json | 196 ++++ docs/quests/events/st0201.md | 2 +- 4 files changed, 1038 insertions(+), 1 deletion(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020040.json diff --git a/Arrowgene.Ddon.GameServer/Handler/WarpRegisterFavoriteWarpHandler.cs b/Arrowgene.Ddon.GameServer/Handler/WarpRegisterFavoriteWarpHandler.cs index 7e59e38fa..72a5ece1e 100644 --- a/Arrowgene.Ddon.GameServer/Handler/WarpRegisterFavoriteWarpHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/WarpRegisterFavoriteWarpHandler.cs @@ -1,5 +1,6 @@ #nullable enable using System.Linq; +using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; diff --git a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json index 0c2a1361f..cd5152ef8 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json @@ -250525,6 +250525,846 @@ 6040, -1, "00:00,23:59" + ], + [ + 316, + 0, + 4, + 0, + "0x010161", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 4, + 0, + "0x010161", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 5, + 0, + "0x018401", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 5, + 0, + "0x018401", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 5, + 0, + "0x010209", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 5, + 0, + "0x010209", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 5, + 0, + "0x010209", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 6, + 0, + "0x010209", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 6, + 0, + "0x010209", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 6, + 0, + "0x010161", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 7, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 7, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 7, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 7, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 7, + 0, + "0x018401", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 7, + 0, + "0x018401", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 8, + 0, + "0x010160", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 8, + 0, + "0x010160", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 8, + 0, + "0x010160", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 8, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 8, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 9, + 0, + "0x015012", + 925, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 1, + true, + true, + false, + false, + 0, + 0, + 65900, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 10, + 0, + "0x010160", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 10, + 0, + "0x010161", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 10, + 0, + "0x010160", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 10, + 0, + "0x010209", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 10, + 0, + "0x010209", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 12, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 12, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" + ], + [ + 316, + 0, + 12, + 0, + "0x010612", + 2298, + 0, + 100, + 56, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + 0, + 6590, + -1, + "00:00,23:59" ] ] } \ No newline at end of file diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020040.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020040.json new file mode 100644 index 000000000..13f28bd80 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020040.json @@ -0,0 +1,196 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "Exploring the Den of Monsters", + "quest_id": 20040, + "next_quest": 20050, + "base_level": 56, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 56} + ], + "rewards": [ + { + "type": "exp", + "amount": 138000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 25000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 1800 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13033, + "num": 1 + }, + { + "item_id": 11408, + "num": 5 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 316, + "group_id": 11 + }, + "enemies": [] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2475, "comment": "Spawns Cecily, Gurdolin, Lise and Elliot"} + ], + "npc_id": "Cecily0", + "message_id": 0 + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 120 + }, + { + "type": "TalkToNpc", + "announce_type": "Accept", + "stage_id": { + "id": 3 + }, + "npc_id": "Klaus0", + "message_id": 15148 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "stage_id": { + "id": 5 + }, + "npc_id": "Zerkin", + "message_id": 15149 + }, + { + "type": "IsStageNo", + "announce_type": "Update", + "stage_id": { + "id": 335 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2475, "comment": "Spawns Cecily, Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2476, "comment": "Spawns Cecily, Gurdolin, Lise and Elliot"} + ] + }, + { + "type": "NewTalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 335, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Gurdolin3", + "message_id": 15150 + }, + { + "type": "Raw", + "checkpoint": true, + "announce_type": "Update", + "check_commands": [ + {"type": "IsReleaseWarpPointAnyone", "Param1": 16} + ] + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 317 + }, + "npc_id": "Bertrand", + "message_id": 16902, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2476, "comment": "Spawns Cecily, Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2855, "comment": "Spawns Cecily, Gurdolin, Lise and Elliot"} + ] + }, + { + "type": "NewTalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 317, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Gurdolin3", + "message_id": 16903 + }, + { + "type": "IsStageNo", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 316 + } + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 316 + }, + "location": { + "x": 2210, + "y": -682, + "z": -66836 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 316 + }, + "event_id": 0, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2855, "comment": "Spawns Cecily, Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 3441, "comment": "Spawns Cecily, Gurdolin, Lise and Elliot"} + ] + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15175 + } + ] + } + ] +} diff --git a/docs/quests/events/st0201.md b/docs/quests/events/st0201.md index 4b4f8c9eb..d761062dc 100644 --- a/docs/quests/events/st0201.md +++ b/docs/quests/events/st0201.md @@ -33,7 +33,7 @@ | 105 | | 110 | The Storm That Brought A Tragedy | teleport player and gerd | 115 | The Girl Who Lost Her Memories | Cici -| 120 | +| 120 | Exploring the Den of Monsters | 125 | | 130 | | 135 | From bede9d11be1a4084865c1bfce63c89a57cfd08a5 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 11:54:00 -0400 Subject: [PATCH 103/116] feat: Season 2.0 MSQ "Eliminate the Corrosion Infestation" --- .../Files/Assets/quests/q00020050.json | 257 ++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020050.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020050.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020050.json new file mode 100644 index 000000000..7c23cc3a8 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020050.json @@ -0,0 +1,257 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "Eliminate the Corrosion Infestation", + "quest_id": 20050, + "next_quest": 20060, + "base_level": 56, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 56} + ], + "rewards": [ + { + "type": "exp", + "amount": 138000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 27000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 2100 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13063, + "num": 1 + }, + { + "item_id": 11408, + "num": 5 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 69, + "group_id": 26 + }, + "enemies": [ + { + "comment": "Hobgoblin Leader", + "enemy_id": "0x010113", + "level": 57, + "exp": 6706, + "infection_type": 1, + "named_enemy_params_id": 1480 + }, + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Hobgoblin FIgter", + "enemy_id": "0x010161", + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Sling Hobgoblin", + "enemy_id": "0x010162", + "level": 57, + "exp": 6706 + } + ] + }, + { + "stage_id": { + "id": 69, + "group_id": 5 + }, + "enemies": [ + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 57, + "exp": 6706, + "named_enemy_params_id": 1480 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 57, + "exp": 6706, + "named_enemy_params_id": 1480 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 57, + "exp": 6706, + "named_enemy_params_id": 1480 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 57, + "exp": 6706, + "named_enemy_params_id": 1480 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "level": 57, + "exp": 6706, + "named_enemy_params_id": 1480 + } + ] + }, + { + "stage_id": { + "id": 69, + "group_id": 2 + }, + "enemies": [ + { + "comment": "Infected Behemoth", + "enemy_id": "0x015712", + "level": 57, + "exp": 67060, + "infection_type": 2, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2740, "comment": "Spawns Elliot"} + ], + "npc_id": "Elliot0", + "message_id": 15176 + }, + { + "type": "TalkToNpc", + "announce_type": "Accept", + "stage_id": { + "id": 2 + }, + "npc_id": "Heinz2", + "message_id": 15177 + }, + { + "type": "NewTalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 69, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2741, "comment": "Spawns Gurdolin"} + ], + "npc_id": "Gurdolin3", + "message_id": 15178 + }, + { + "type": "DiscoverEnemy", + "checkpoint": true, + "announce_type": "Update", + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 0 ] + }, + { + "type": "DiscoverEnemy", + "checkpoint": true, + "announce_type": "Update", + "groups": [ 1 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 1 ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 69 + }, + "location": { + "x": 7, + "y": -198, + "z": 7729 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 69 + }, + "event_id": 20 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [ 2 ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 69 + }, + "event_id": 25 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2741, "comment": "Spawns Gurdolin"} + ], + "npc_id": "Joseph", + "message_id": 15208 + } + ] + } + ] +} From 402d9eb8c98cd37082ca820b11f675c84b32386f Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 12:25:34 -0400 Subject: [PATCH 104/116] feat: Season 2.0 MSQ "The Man From Another Land" --- .../Files/Assets/quests/q00020060.json | 129 ++++++++++++++++++ docs/quests/events/st0100.md | 4 +- 2 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020060.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020060.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020060.json new file mode 100644 index 000000000..928a10298 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020060.json @@ -0,0 +1,129 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "The Man From Another Land", + "quest_id": 20060, + "next_quest": 20070, + "base_level": 57, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 57} + ], + "rewards": [ + { + "type": "exp", + "amount": 148000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 28000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 2200 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 11407, + "num": 3 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 1, + "group_id": 313 + }, + "enemies": [ + { + "comment": "Zuhl", + "enemy_id": "0x020402", + "level": 57, + "exp": 67060, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 3907, "comment": "Spawns Elliot and Gurdolin"} + ], + "npc_id": "Gurdolin3", + "message_id": 15209 + }, + { + "type": "TalkToNpc", + "announce_type": "Accept", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15210 + }, + + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 1 + }, + "location": { + "x": -146041, + "y": 13242, + "z": -302402 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 135 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [ 0 ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 140 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15254 + } + ] + } + ] +} diff --git a/docs/quests/events/st0100.md b/docs/quests/events/st0100.md index c81af5070..915d567fd 100644 --- a/docs/quests/events/st0100.md +++ b/docs/quests/events/st0100.md @@ -22,8 +22,8 @@ | 120 | The Girl Who Lost Her Memories | 125 | The Corruption and the Knights | 130 | The Corruption and the Knights -| 135 -| 140 +| 135 | The Man From Another Land +| 140 | The Man From Another Land | 145 | 150 | 155 From 81cb64313d6561fbecbbcc3204d4e0ca6e960542 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 14:00:27 -0400 Subject: [PATCH 105/116] feat: Season 2.0 MSQ "The Fate of Lestania" --- .../Files/Assets/quests/q00020070.json | 345 ++++++++++++++++++ docs/quests/events/st0201.md | 6 +- 2 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json new file mode 100644 index 000000000..f5606fdc5 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json @@ -0,0 +1,345 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "The Fate of Lestania", + "quest_id": 20070, + "next_quest": 0, + "base_level": 57, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 57} + ], + "rewards": [ + { + "type": "exp", + "amount": 148000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 30000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 2400 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 11759, + "num": 1 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 1, + "group_id": 296 + }, + "enemies": [ + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Direwolf", + "enemy_id": "0x010209", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Direwolf", + "enemy_id": "0x010209", + "infection_type": 1, + "level": 57, + "exp": 6706 + } + ] + }, + { + "stage_id": { + "id": 1, + "group_id": 422 + }, + "enemies": [ + { + "comment": "Bolt Grimward", + "enemy_id": "0x010211", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Bolt Grimward", + "enemy_id": "0x010211", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Hobgoblin Fighter", + "enemy_id": "0x010161", + "infection_type": 1, + "level": 57, + "exp": 6706 + }, + { + "comment": "Infected Hobgoblin", + "enemy_id": "0x010160", + "infection_type": 1, + "level": 57, + "exp": 6706 + } + ] + }, + { + "stage_id": { + "id": 76, + "group_id": 10 + }, + "enemies": [ + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "start_think_tbl_no": 1, + "infection_type": 1, + "level": 60, + "exp": 15254 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "start_think_tbl_no": 1, + "infection_type": 1, + "level": 60, + "exp": 15254 + }, + { + "comment": "Infected Snow Harpy", + "enemy_id": "0x010612", + "start_think_tbl_no": 1, + "infection_type": 1, + "level": 60, + "exp": 15254 + }, + { + "comment": "Frost Skeleton Brute", + "enemy_id": "0x010321", + "start_think_tbl_no": 1, + "level": 60, + "exp": 15254 + }, + { + "comment": "Frost Skeleton Brute", + "enemy_id": "0x010321", + "start_think_tbl_no": 1, + "level": 60, + "exp": 15254 + } + ] + }, + { + "stage_id": { + "id": 76, + "group_id": 4 + }, + "enemies": [ + { + "comment": "Infected Griffin", + "enemy_id": "0x015306", + "infection_type": 2, + "level": 60, + "exp": 152540, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 3 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2758, "comment": "Spawns Gurdolin"} + ], + "npc_id": "Joseph", + "message_id": 15210 + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 125 + }, + { + "type": "DiscoverEnemy", + "announce_type": "Accept", + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "reset_group": false, + "announce_type": "Update", + "groups": [ 0 ] + }, + { + "type": "DiscoverEnemy", + "checkpoint": true, + "announce_type": "Update", + "groups": [ 1 ] + }, + { + "type": "KillGroup", + "reset_group": false, + "announce_type": "Update", + "groups": [ 1 ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "location": { + "x": -113, + "y": 9164, + "z": -369 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2758, "comment": "Spawns Gurdolin"}, + {"type": "QstLayout", "action": "Set", "value": 3101, "comment": "Spawns Elliot and Gerd"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 130 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 63 + }, + "npc_id": "Theodor", + "message_id": 15272 + }, + { + "type": "DiscoverEnemy", + "checkpoint": true, + "announce_type": "Update", + "groups": [ 2 ] + }, + { + "type": "KillGroup", + "reset_group": false, + "announce_type": "Update", + "groups": [ 2 ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 76 + }, + "location": { + "x": 3773, + "y": -180, + "z": -22550 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 76 + }, + "event_id": 20 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [ 3 ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 76 + }, + "event_id": 25 + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "location": { + "x": -817, + "y": 9163, + "z": 0 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3101, "comment": "Spawns Elliot and Gerd"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 135 + } + ] + } + ] +} diff --git a/docs/quests/events/st0201.md b/docs/quests/events/st0201.md index d761062dc..4d5301cd8 100644 --- a/docs/quests/events/st0201.md +++ b/docs/quests/events/st0201.md @@ -34,9 +34,9 @@ | 110 | The Storm That Brought A Tragedy | teleport player and gerd | 115 | The Girl Who Lost Her Memories | Cici | 120 | Exploring the Den of Monsters -| 125 | -| 130 | -| 135 | +| 125 | The Fate of Lestania +| 130 | The Fate of Lestania +| 135 | The Fate of Lestania | 140 | | 145 | | 150 | From 56e43a446627e251e9fb4ce249b3d9951cf960b1 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 14:41:57 -0400 Subject: [PATCH 106/116] Fix the white dragon in cutscenes Seems the 2.0 cutscenes include the white dragon in the animation and doesn't remove the one sitting in the chamber by itself. Since this happens, you end up with 2 white dragons at different points in their animation loop. In cutscenes which include the white dragon in the audience chamber, we need to despawn the dragon until the cutscene is over and then respawn it after. --- .../Files/Assets/quests/q00020010.json | 18 +++-- .../Files/Assets/quests/q00020020.json | 10 ++- .../Files/Assets/quests/q00020030.json | 2 + .../Files/Assets/quests/q00020070.json | 32 +++++++-- docs/quests/generic_quest_state_machine.md | 65 ------------------- docs/quests/quests.md | 9 +++ 6 files changed, 61 insertions(+), 75 deletions(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020010.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020010.json index c58db5595..38f5c7007 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020010.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020010.json @@ -102,6 +102,9 @@ "stage_id": { "id": 1 }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ], "event_id": 100 }, { @@ -111,7 +114,9 @@ "id": 3, "group_id": 1 }, - "flags": [], + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ], "npc_id": "Joseph", "message_id": 14936 }, @@ -221,7 +226,10 @@ "stage_id": { "id": 3 }, - "event_id": 110 + "event_id": 110, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] }, { "type": "TalkToNpc", @@ -231,9 +239,11 @@ "id": 3, "group_id": 1 }, - "flags": [], "npc_id": "Joseph", - "message_id": 15007 + "message_id": 15007, + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020020.json index e7d179df7..325328a9b 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020020.json @@ -185,7 +185,10 @@ "stage_id": { "id": 3 }, - "event_id": 115 + "event_id": 115, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] }, { "type": "TalkToNpc", @@ -198,7 +201,10 @@ {"type": "QstLayout", "action": "Set", "value": 3027, "comment": "Spawns Cici, Gurdolin, Lise and Elliot"} ], "npc_id": "Joseph", - "message_id": 15078 + "message_id": 15078, + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] }, { "type": "TalkToNpc", diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020030.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020030.json index 4836e24f1..963ff8709 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020030.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020030.json @@ -134,12 +134,14 @@ { "comment": "Infected Hobgoblin", "enemy_id": "0x010160", + "start_think_tbl_no": 1, "level": 55, "exp": 6476 }, { "comment": "Infected Hobgoblin Fighter", "enemy_id": "0x010161", + "start_think_tbl_no": 1, "level": 55, "exp": 6476 } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json index f5606fdc5..655f8411b 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json @@ -213,12 +213,18 @@ "stage_id": { "id": 3 }, - "event_id": 125 + "event_id": 125, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] }, { "type": "DiscoverEnemy", "announce_type": "Accept", - "groups": [ 0 ] + "groups": [ 0 ], + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] }, { "type": "KillGroup", @@ -260,7 +266,10 @@ "stage_id": { "id": 3 }, - "event_id": 130 + "event_id": 130, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] }, { "type": "TalkToNpc", @@ -270,7 +279,10 @@ "id": 63 }, "npc_id": "Theodor", - "message_id": 15272 + "message_id": 15272, + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] }, { "type": "DiscoverEnemy", @@ -337,7 +349,19 @@ "stage_id": { "id": 3 }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ], "event_id": 135 + }, + { + "type": "IsStageNo", + "stage_id": { + "id": 3 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] } ] } diff --git a/docs/quests/generic_quest_state_machine.md b/docs/quests/generic_quest_state_machine.md index 823128183..01f016c4f 100644 --- a/docs/quests/generic_quest_state_machine.md +++ b/docs/quests/generic_quest_state_machine.md @@ -772,71 +772,6 @@ See the [quest command reference document](quest_command_reference.md) for more | QstLayout | st0418 | 1101 | Spawns Klaus | MyQst | | 137 | Start Leo battl FSM -#### World Manage Quest - -##### q70000001 - -| Type | StageNo | Value | Comment -|:-----------------:|:-------:|:------:|:-----------------------------------------------------------| -| WorldManageLayout | st0100 | 977 | Spawns Gerd and the White Knights outside -| WorldManageLayout | st0100 | 1263 | The 2nd Ark (random) (st0574) -| WorldManageLayout | st0100 | 2201 | The 1st Ark (random) (st0573) -| WorldManageLayout | st0100 | 2204 | The 2nd Ark (quest) (st0571) -| WorldManageLayout | st0100 | 2204 | The 1st Ark (quest) (st0576) -| -| WorldManageLayout | st0201 | 1218 | Spawns Leo in the audience chamber -| WorldManageLayout | st0201 | 1219 | Spawns Iris in the audience chamber -| WorldManageLayout | st0201 | 1293 | Spawns The White Dragon in the audience chamber in the most injured state -| -| WorldManageLayout | st0403 | 1109 | Locks the double doors to the Chapel at (x:51,y:89) -| WorldManageLayout | st0403 | 1110 | Unlocks the double doors to the Chapel at (x:51,y:89) -| -| WorldManageLayout | st0408 | 1111 | Closed Water Flow Control Room Door -| WorldManageLayout | st0408 | 1112 | Open Water Flow Control Room Door -| WorldManageLayout | st0408 | 1317 | Water Falls Gimick -| WorldManageLayout | st0408 | 1671 | Closed Lever Door (Stone Door, middle) -| WorldManageLayout | st0408 | 1672 | Open Lever Door (Stone Door, middle) - - -##### q70002001 -| Type | StageNo | Value | Comment -|:-----------------:|:-------:|:------:|:-----------------------------------------------------------| -| WorldManageLayout | st0402 | 0 | Solider Corpse message -| WorldManageLayout | st0402 | 3859 | Floor Lever -| WorldManageLayout | st0402 | 3859 | Large Door Inside Gardnock Fort -| WorldManageLayout | st0402 | 3860 | Large Door Inside Gardnock Fort -| -| WorldManageLayout | st0403 | 1113 | Large Door Closed in Erte Deenan -| WorldManageLayout | st0403 | 1114 | Large Door Open in Erte Deenan - -##### q70003001 -| Type | StageNo | Value | Comment -|:-----------------:|:-------:|:------:|:-----------------------------------------------------------| -| WorldManageLayout | st0411 | 1106 | Front Door Lever (floor mounted) (ver1.2_正面扉用_レバー) (床置きレバー(遺跡用) -| WorldManageLayout | st0411 | 1104 | Front Large Door Closed (ver1.2閉じ_正面扉) (メルゴダ扉・大) -| WorldManageLayout | st0411 | 1105 | Front Large Door Open (ver1.2開き_正面扉) (メルゴダ扉・大) -| WorldManageLayout | st0411 | 1119 | Closed Alchemy Research Building Door (ver1.2閉じ_錬金研究棟扉) (メルゴダ扉・中片扉) -| WorldManageLayout | st0411 | 1120 | Open Alchemy Research Building Door (ver1.2開き_錬金研究棟扉) (メルゴダ扉・中片扉) -| WorldManageLayout | st0411 | 1121 | Closed Military Instructors Door (small door) (ver1.2閉じ_軍事指導官扉) (メルゴダ扉・小片扉) -| WorldManageLayout | st0411 | 1122 | Open Military Instructors Door (ver1.2開き_軍事指導官扉) (メルゴダ扉・小片扉) -| WorldManageLayout | st0411 | 1123 | Closed Special Research Door (ver1.2閉じ_特別研究区扉) (メルゴダ扉・小片扉) -| WorldManageLayout | st0411 | 1124 | Open Special Research Door (ver1.2開き_特別研究区扉) (メルゴダ扉・小片扉) -| WorldManageLayout | st0411 | 1202 | Mergoda Warp OFF? (Quest Specified Message) (ver1.2OFF_メルゴダワープ系) (クエスト指定メッセージOM) -| WorldManageLayout | st0411 | 1203 | Mergoda TO ON? Lost City Dungeon (ver1.2ON_メルゴダ行き) (レーゼ行きワープ(亡都ダンジョン)) -| WorldManageLayout | st0411 | 2458 | Quest Specified Message (ver1.2メルゴダ大扉用メッセージOM) (クエスト指定メッセージOM) -| -| WorldManageLayout | st0203 | 1103 | ver1.2 3rd Arc OM General purpose Glitter (ver1.2第三アーク行OM) (汎用キラキラポイント(調べる用)) -| -| WorldManageLayout | st0418 | 1098 | ver1.2 Closed_Diamantes Door (ver1.2閉じ_ディアマンテス扉) -| WorldManageLayout | st0418 | 1713 | ver1.2 Opening_Diamantes Door (ver1.2開き_ディアマンテス扉) -| WorldManageLayout | st0418 | 2456 | ver1.2 Royal Palace and Residential Area Door Warp OM (ver1.2王宮と住居区の扉ワープOM, フロア移動用透明ワープOM) - -##### q70032001 - -| Type | Value | Comment -|:-----------------:|:------:|:-----------------------------------------------------------| -| WorldManageLayout | 7390 | Spawns The White Dragon in the audience chamber in the fully revived state - ### Events - [Lestania (stage0100)](events/st0100.md) diff --git a/docs/quests/quests.md b/docs/quests/quests.md index 929c02bb1..3b59b06d4 100644 --- a/docs/quests/quests.md +++ b/docs/quests/quests.md @@ -76,6 +76,15 @@ There exists an implementation of the following main story quests. | [The Great Alchemist](http://ddon.wikidot.com/mq:thegreatalchemist) | Some quirks with the lighting. Also, having hard time to get proper spawn point for second boss. | [Be Forevermore, White Dragon](http://ddon.wikidot.com/mq:beforevermorewhitedragon) | Some weird flashing with the dragon. Seems cutscene uses it's own model instead of the one from the world manage quest. +### Season 2.0 +- [The Storm That Brought A Tragedy](http://ddon.wikidot.com/mq:thestormthatbroughtatragedy) +- [The Girl Who Lost Her Memories](http://ddon.wikidot.com/mq:thegirlwholosthermemories) +- [The Corruption and the Knights](http://ddon.wikidot.com/mq:thecorruptionandtheknights) +- [Exploring the Den of Monsters](http://ddon.wikidot.com/mq:exploringthedenofmonsters) +- [Eliminate the Corrosion Infestation](http://ddon.wikidot.com/mq:eliminatethecorrosioninfestation) +- [The Man From Another Land](http://ddon.wikidot.com/mq:themanfromanotherland) +- [The Fate of Lestania](http://ddon.wikidot.com/mq:thefateoflestania) + ### Season 3.3 | Quest Name | Comment | From 0b5f74fa13808175c9120c6e00e86361d62ec37f Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 15:20:57 -0400 Subject: [PATCH 107/116] Add migration strategy Adds new quest progress for players who have completed the final MSQ in the season 1.2 questline. --- .../Arrowgene.Ddon.Database.csproj | 4 ++++ .../DdonDatabaseBuilder.cs | 2 +- .../Database/Script/migration_msq_2.0.sql | 2 ++ .../Core/Migration/00000016_Msq20Migration.cs | 24 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Arrowgene.Ddon.Database/Files/Database/Script/migration_msq_2.0.sql create mode 100644 Arrowgene.Ddon.Database/Sql/Core/Migration/00000016_Msq20Migration.cs diff --git a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj index 230959bf0..2b7162386 100644 --- a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj +++ b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj @@ -39,6 +39,7 @@ + @@ -72,6 +73,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs index e984bb508..d6755acf9 100644 --- a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs +++ b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs @@ -14,7 +14,7 @@ public static class DdonDatabaseBuilder private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonDatabaseBuilder)); private const string DefaultSchemaFile = "Script/schema_sqlite.sql"; - public const uint Version = 15; + public const uint Version = 16; public static IDatabase Build(DatabaseSetting settings) { diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/migration_msq_2.0.sql b/Arrowgene.Ddon.Database/Files/Database/Script/migration_msq_2.0.sql new file mode 100644 index 000000000..0db8daef0 --- /dev/null +++ b/Arrowgene.Ddon.Database/Files/Database/Script/migration_msq_2.0.sql @@ -0,0 +1,2 @@ +INSERT INTO ddon_quest_progress(character_common_id, quest_type, quest_id, step, variant_quest_id) +VALUES ((SELECT character_common_id FROM ddon_completed_quests WHERE quest_id = 30), 3, 20010, 0, 0); diff --git a/Arrowgene.Ddon.Database/Sql/Core/Migration/00000016_Msq20Migration.cs b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000016_Msq20Migration.cs new file mode 100644 index 000000000..0849acda5 --- /dev/null +++ b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000016_Msq20Migration.cs @@ -0,0 +1,24 @@ +using System.Data.Common; + +namespace Arrowgene.Ddon.Database.Sql.Core.Migration +{ + public class Msq20Migration : IMigrationStrategy + { + public uint From => 15; + public uint To => 16; + + private readonly DatabaseSetting DatabaseSetting; + + public Msq20Migration(DatabaseSetting databaseSetting) + { + DatabaseSetting = databaseSetting; + } + + public bool Migrate(IDatabase db, DbConnection conn) + { + string adaptedSchema = DdonDatabaseBuilder.GetAdaptedSchema(DatabaseSetting, "Script/migration_msq_2.0.sql"); + db.Execute(conn, adaptedSchema); + return true; + } + } +} From fb620d695c1d4f546d6977e4dd8794f7ec73d39e Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 17:28:19 -0400 Subject: [PATCH 108/116] feat: Season 2.1 MSQ "A Fresh Incident" --- .../Files/Assets/quests/q00020070.json | 2 +- .../Files/Assets/quests/q00020080.json | 155 ++++++++++++++++++ docs/quests/events/st0201.md | 2 +- 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020080.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json index 655f8411b..dc754a862 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020070.json @@ -3,7 +3,7 @@ "type": "Main", "comment": "The Fate of Lestania", "quest_id": 20070, - "next_quest": 0, + "next_quest": 20080, "base_level": 57, "minimum_item_rank": 0, "discoverable": true, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020080.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020080.json new file mode 100644 index 000000000..57f4144c9 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020080.json @@ -0,0 +1,155 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "A Fresh Incident", + "quest_id": 20080, + "next_quest": 20090, + "base_level": 64, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 64} + ], + "rewards": [ + { + "type": "exp", + "amount": 98000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 25000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 2000 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 14191, + "num": 1 + }, + { + "item_id": 11510, + "num": 5 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 335, + "group_id": 4 + }, + "enemies": [ + { + "comment": "Medusa", + "enemy_id": "0x015610", + "level": 64, + "exp": 156320, + "named_enemy_params_id": 934, + "is_boss": true + }, + { + "comment": "Eliminator", + "enemy_id": "0x010530", + "level": 64, + "exp": 15632, + "named_enemy_params_id": 934 + }, + { + "comment": "Eliminator", + "enemy_id": "0x010530", + "level": 64, + "exp": 15632, + "named_enemy_params_id": 934 + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 3 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"} + ], + "npc_id": "Joseph", + "message_id": 0 + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 140, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] + }, + { + "type": "TalkToNpc", + "announce_type": "Accept", + "stage_id": { + "id": 3 + }, + "npc_id": "Klaus0", + "message_id": 15308, + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 335 + }, + "location": { + "x": -2984, + "y": 1539, + "z": 2350 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 335 + }, + "event_id": 20 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [ 0 ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 335 + }, + "event_id": 25 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15325 + } + ] + } + ] +} diff --git a/docs/quests/events/st0201.md b/docs/quests/events/st0201.md index 4d5301cd8..dfb49333d 100644 --- a/docs/quests/events/st0201.md +++ b/docs/quests/events/st0201.md @@ -37,7 +37,7 @@ | 125 | The Fate of Lestania | 130 | The Fate of Lestania | 135 | The Fate of Lestania -| 140 | +| 140 | A Fresh Incident | 145 | | 150 | | 155 | From e04edccd9e804ac0b0cce0ab2ce7d4e3e26c7c9a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 18:13:17 -0400 Subject: [PATCH 109/116] feat: Season 2.1 MSQ "Negotiations" --- .../Files/Assets/EnemySpawn.json | 38 +---- .../Files/Assets/quests/q00020090.json | 140 ++++++++++++++++++ 2 files changed, 145 insertions(+), 33 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020090.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json index cd5152ef8..a4ef6552b 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/EnemySpawn.json @@ -145895,11 +145895,11 @@ 0, 4, 0, - "0x015104", + "0x015003", 2298, 0, 100, - 65, + 62, 0, 0, 0, @@ -145912,10 +145912,10 @@ true, false, false, + 62, 0, - 0, - 197000, - 162, + 155040, + -1, "00:00,23:59" ], [ @@ -218606,34 +218606,6 @@ 331, "00:00,23:59" ], - [ - 329, - 0, - 8, - 0, - "0x070920", - 2298, - 0, - 100, - 65, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - true, - true, - false, - false, - 0, - 0, - 197000, - 422, - "00:00,23:59" - ], [ 329, 0, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020090.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020090.json new file mode 100644 index 000000000..126b76698 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020090.json @@ -0,0 +1,140 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "Negotiations", + "quest_id": 20090, + "next_quest": 20100, + "base_level": 64, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 64} + ], + "rewards": [ + { + "type": "exp", + "amount": 103000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 28000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 2300 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13078, + "num": 1 + }, + { + "item_id": 11510, + "num": 5 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 342, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Manticore", + "enemy_id": "0x015210", + "level": 59, + "exp": 151320, + "named_enemy_params_id": 934, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15326, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"} + ] + }, + { + "type": "NewTalkToNpc", + "announce_type": "Accept", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Gurdolin3", + "message_id": 15327, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2770, "comment": "Spawns Gurdolin, Lise and Elliot"} + ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 342 + }, + "location": { + "x": 114, + "y": 0, + "z": -1275 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 342 + }, + "event_id": 0 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [ 0 ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 342 + }, + "event_id": 5, + "jump_stage_id": { + "id": 342 + }, + "start_pos_no": 2 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15353, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2772, "comment": "Leo"} + ] + } + ] + } + ] +} From fb193aec5d52c99528b2472150c27ff8df4bd424 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 19:28:34 -0400 Subject: [PATCH 110/116] feat: Season 2.1 MSQ "Loeg's Illness" --- .../Files/Assets/quests/q00020100.json | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020100.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020100.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020100.json new file mode 100644 index 000000000..9b7cab184 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020100.json @@ -0,0 +1,263 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "Loeg's Illness", + "quest_id": 20100, + "next_quest": 20110, + "base_level": 64, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 64} + ], + "rewards": [ + { + "type": "exp", + "amount": 108000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 25000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 1500 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13118, + "num": 1 + }, + { + "item_id": 11510, + "num": 5 + } + ] + } + ], + "enemy_groups": [], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Mayleaf0", + "message_id": 15354, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2773, "comment": "Spawns Mayleaf"} + ] + }, + { + "type": "PartyGather", + "announce_type": "Accept", + "stage_id": { + "id": 350 + }, + "location": { + "x": -2143, + "y": 18, + "z": 9 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2774, "comment": "Spawns Lise, Cecily, Loeg"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 350 + }, + "event_id": 5 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 78 + }, + "npc_id": "Camus", + "message_id": 15365 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 78 + }, + "npc_id": "Alvar", + "message_id": 16737 + }, + { + "type": "MyQstFlags", + "announce_type": "Update", + "checkpoint": true, + "set_flags": [ 1 ], + "check_flags": [ 2, 3, 4, 5 ] + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 78 + }, + "npc_id": "Camus", + "message_id": 15366 + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 350 + }, + "location": { + "x": -2143, + "y": 18, + "z": 9 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 350 + }, + "event_id": 10 + }, + { + "type": "NewTalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Mayleaf0", + "message_id": 15381 + } + ] + }, + { + "comment": "Process 1", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [ 1 ] + }, + { + "type": "CollectItem", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2775} + ] + }, + { + "type": "MyQstFlags", + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2775} + ], + "set_flags": [ 2 ] + } + ] + }, + { + "comment": "Process 2", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [ 1 ] + }, + { + "type": "CollectItem", + "stage_id": { + "id": 1, + "group_id": 2, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2776} + ] + }, + { + "type": "MyQstFlags", + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2776} + ], + "set_flags": [ 3 ] + } + ] + }, + { + "comment": "Process 3", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [ 1 ] + }, + { + "type": "CollectItem", + "stage_id": { + "id": 1, + "group_id": 3, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2777} + ] + }, + { + "type": "MyQstFlags", + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2777} + ], + "set_flags": [ 4 ] + } + ] + }, + { + "comment": "Process 4", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [ 1 ] + }, + { + "type": "CollectItem", + "stage_id": { + "id": 1, + "group_id": 4, + "layer_no": 1 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2778} + ] + }, + { + "type": "MyQstFlags", + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2778} + ], + "set_flags": [ 5 ] + } + ] + } + ] +} From 14f8ed3cc3be5c25a5a8b71ffbc90fa69e5ef739 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 20:32:17 -0400 Subject: [PATCH 111/116] feat: Season 2.1 MSQ "A Strange Land's Light" --- .../Files/Assets/quests/q00020110.json | 213 ++++++++++++++++++ docs/quests/events/st0201.md | 2 +- 2 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020110.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020110.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020110.json new file mode 100644 index 000000000..8f884b8e7 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020110.json @@ -0,0 +1,213 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "A Strange Land's Light", + "quest_id": 20110, + "next_quest": 20120, + "base_level": 65, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 65} + ], + "rewards": [ + { + "type": "exp", + "amount": 113000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 35000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 2600 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 14190, + "num": 1 + }, + { + "item_id": 11407, + "num": 3 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 409, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Frost Machina", + "enemy_id": "0x015851", + "level": 64, + "exp": 157620, + "named_enemy_params_id": 939, + "is_boss": true + } + ] + }, + { + "stage_id": { + "id": 337, + "group_id": 1 + }, + "enemies": [] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Cecily0", + "message_id": 0, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2849, "comment": "Spawns Cecily"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 145, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] + }, + { + "type": "NewTalkToNpc", + "announce_type": "Accept", + "stage_id": { + "id": 3, + "group_id": 2, + "layer_no": 1 + }, + "npc_id": "Gurdolin3", + "message_id": 15395, + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"}, + {"type": "QstLayout", "action": "Set", "value": 3317, "comment": "Spawns Gurdolin, Elliot, Loeg and Lise"} + ] + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 5 + }, + "npc_id": "Zerkin", + "message_id": 15396 + }, + { + "type": "IsStageNo", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 336 + } + }, + { + "type": "NewTalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 336, + "group_id": 1, + "layer_no": 3 + }, + "npc_id": "Cecily0", + "message_id": 15398, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3317, "comment": "Spawns Gurdolin, Elliot, Loeg and Lise"}, + {"type": "QstLayout", "action": "Set", "value": 3318, "comment": "Spawns Gurdolin, Loeg, Cecily, Lise and Elliot"} + ] + }, + { + "type": "IsStageNo", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 409 + } + }, + { + "type": "DiscoverEnemy", + "announce_type": "Update", + "checkpoint": true, + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 0 ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 337 + }, + "location": { + "x": 30855, + "y": 19538, + "z": -31490 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 337 + }, + "event_id": 0 + }, + { + "type": "NewTalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 337, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Elliot0", + "message_id": 15420, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3318, "comment": "Spawns Gurdolin, Loeg, Cecily, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2851, "comment": "Spawns Elliot"} + ] + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15421 + } + ] + } + ] +} diff --git a/docs/quests/events/st0201.md b/docs/quests/events/st0201.md index dfb49333d..72a0fe923 100644 --- a/docs/quests/events/st0201.md +++ b/docs/quests/events/st0201.md @@ -38,7 +38,7 @@ | 130 | The Fate of Lestania | 135 | The Fate of Lestania | 140 | A Fresh Incident -| 145 | +| 145 | A Strange Land's Light | 150 | | 155 | | 157 | From 47bbe5a52969b9b6303c2b7f42fcf5ea4af9fa35 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 21 Sep 2024 23:03:31 -0400 Subject: [PATCH 112/116] feat: Season 2.1 MSQ "The Lone Arisen" - Implemented MSQ "The Lone Arisen". - Fixed bug in OM action parsing. --- .../AssetReader/QuestAssetDeserializer.cs | 4 +- .../Files/Assets/quests/q00020120.json | 299 ++++++++++++++++++ docs/quests/events/st0100.md | 2 +- 3 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index b612f6a17..cb8e675c1 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -634,12 +634,14 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) Logger.Error($"Unable to parse the quest type in block @ index {blockIndex - 1}."); return false; } + questBlock.OmInteractEvent.QuestType = questType; if (!Enum.TryParse(jblock.GetProperty("interact_type").GetString(), true, out OmInteractType interactType)) { Logger.Error($"Unable to parse the quest type in block @ index {blockIndex - 1}."); return false; } + questBlock.OmInteractEvent.InteractType = interactType; questBlock.ShowMarker = true; if (jblock.TryGetProperty("show_marker", out JsonElement jShowMarker)) @@ -651,8 +653,6 @@ private bool ParseBlocks(QuestProcess questProcess, JsonElement jBlocks) { questBlock.OmInteractEvent.QuestId = (QuestId)jQuestId.GetUInt32(); } - - questBlock.OmInteractEvent.QuestType = questType; } break; case QuestBlockType.DeliverItems: diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json new file mode 100644 index 000000000..2587d5a2f --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json @@ -0,0 +1,299 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "The Lone Arisen", + "quest_id": 20120, + "next_quest": 20130, + "base_level": 65, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 65} + ], + "rewards": [ + { + "type": "exp", + "amount": 118000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 38000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 2800 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13480, + "num": 1 + }, + { + "item_id": 11407, + "num": 3 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 405, + "group_id": 13 + }, + "enemies": [ + { + "comment": "Ghost Mail", + "enemy_id": "0x010311", + "level": 65, + "exp": 157620, + "is_boss": true + } + ] + }, + { + "stage_id": { + "id": 405, + "group_id": 15 + }, + "enemies": [ + { + "comment": "Ghost", + "enemy_id": "0x015620", + "level": 64, + "exp": 15762 + }, + { + "comment": "Ghost", + "enemy_id": "0x015620", + "level": 64, + "exp": 15762 + } + ] + }, + { + "stage_id": { + "id": 405, + "group_id": 16 + }, + "enemies": [ + { + "comment": "Gigant Machina", + "enemy_id": "0x015850", + "level": 65, + "exp": 157620, + "is_boss": true + }, + { + "comment": "Ghost", + "enemy_id": "0x015620", + "level": 64, + "exp": 15762 + }, + { + "comment": "Ghost", + "enemy_id": "0x015620", + "level": 64, + "exp": 15762 + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15422, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 3851, "comment": "Spawns Gurdolin and Elliot"} + ] + }, + { + "type": "NewTalkToNpc", + "announce_type": "Accept", + "stage_id": { + "id": 350, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Loeg", + "message_id": 15423, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2457, "comment": "Spawns Loeg, Cecily and Lise"} + ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 1 + }, + "location": { + "x": -34712, + "y": 15182, + "z": -176358 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2466, "comment": "Spawns Leo"}, + {"type": "QstLayout", "action": "Set", "value": 3378, "comment": "Spawns Loeg"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 1 + }, + "event_id": 145 + }, + { + "type": "NewTalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Leo0", + "message_id": 15443 + }, + { + "type": "IsStageNo", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 405 + } + }, + { + "type": "OmInteractEvent", + "announce_type": "Update", + "checkpoint": true, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2467, "comment": "Spawns Collection Point"} + ], + "quest_type": "MyQuest", + "interact_type": "Release", + "stage_id": { + "id": 405, + "group_id": 1, + "layer_no": 5 + } + }, + { + "type": "OmInteractEvent", + "announce_type": "Update", + "checkpoint": true, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2469, "comment": "Spawns Collection Point"} + ], + "quest_type": "MyQuest", + "interact_type": "Release", + "stage_id": { + "id": 405, + "group_id": 2, + "layer_no": 1 + } + }, + { + "type": "OmInteractEvent", + "announce_type": "Update", + "checkpoint": true, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2472, "comment": "Spawns Collection Point"} + ], + "quest_type": "MyQuest", + "interact_type": "Release", + "stage_id": { + "id": 405, + "group_id": 5, + "layer_no": 1 + } + }, + { + "type": "DiscoverEnemy", + "announce_type": "Update", + "groups": [ 0, 1 ] + }, + { + "type": "KillGroup", + "reset_group": false, + "groups": [ 0, 1 ] + }, + { + "type": "OmInteractEvent", + "announce_type": "Update", + "checkpoint": true, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2471, "comment": "Spawns Collection Point"} + ], + "quest_type": "MyQuest", + "interact_type": "Release", + "stage_id": { + "id": 405, + "group_id": 4, + "layer_no": 1 + } + }, + { + "type": "DiscoverEnemy", + "announce_type": "Update", + "groups": [ 2 ] + }, + { + "type": "KillGroup", + "reset_group": false, + "groups": [ 2 ] + }, + { + "type": "OmInteractEvent", + "announce_type": "Update", + "checkpoint": true, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2470, "comment": "Spawns Collection Point"} + ], + "quest_type": "MyQuest", + "interact_type": "Release", + "stage_id": { + "id": 405, + "group_id": 3, + "layer_no": 1 + } + }, + { + "type": "NewTalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "Leo0", + "message_id": 15444 + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 3 + }, + "announce_type": "Update", + "npc_id": "Joseph", + "message_id": 15446 + } + ] + } + ] +} diff --git a/docs/quests/events/st0100.md b/docs/quests/events/st0100.md index 915d567fd..d72e65932 100644 --- a/docs/quests/events/st0100.md +++ b/docs/quests/events/st0100.md @@ -24,7 +24,7 @@ | 130 | The Corruption and the Knights | 135 | The Man From Another Land | 140 | The Man From Another Land -| 145 +| 145 | The Lone Arisen | 150 | 155 | 160 \ No newline at end of file From 171b2ae2833f6d2b4f2c851513a53c2d124aac1a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 22 Sep 2024 00:12:47 -0400 Subject: [PATCH 113/116] feat: Season 2.1 MSQ "Straying Power" --- .../Files/Assets/quests/q00020130.json | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020130.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020130.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020130.json new file mode 100644 index 000000000..a2495d8eb --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020130.json @@ -0,0 +1,211 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "Straying Power", + "quest_id": 20130, + "next_quest": 20140, + "base_level": 65, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 65} + ], + "rewards": [ + { + "type": "exp", + "amount": 123000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 40000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 3000 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13481, + "num": 1 + }, + { + "item_id": 14189, + "num": 1 + } + ] + } + ], + "enemy_groups": [ + { + "comment": "Block monsters in room while quest is active", + "stage_id": { + "id": 74, + "group_id": 8 + }, + "enemies": [] + }, + { + "stage_id": { + "id": 361, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Phantasmic Great Dragon", + "enemy_id": "0x021003", + "level": 65, + "exp": 197000, + "named_enemy_params_id": 942, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15447, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 2475, "comment": "Spawns Cecily, Elliot and Lise"} + ] + }, + { + "type": "IsStageNo", + "announce_type": "Accept", + "stage_id": { + "id": 74 + }, + "flags": [ + {"type": "QstLayout", "action": "Set", "value": 2473, "comment": "Spawns Leo"} + ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 74 + }, + "location": { + "x": -230, + "y": -1649, + "z": -29680 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 74 + }, + "event_id": 15, + "jump_stage_id": { + "id": 361 + } + }, + { + "type": "IsStageNo", + "announce_type": "Update", + "stage_id": { + "id": 361 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2473, "comment": "Spawns Leo"}, + {"type": "QstLayout", "action": "Set", "value": 2529, "comment": "Spawns Leo"} + ] + }, + { + "type": "PartyGather", + "stage_id": { + "id": 361 + }, + "location": { + "x": 59, + "y": 519, + "z": -7292 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 361 + }, + "event_id": 0, + "jump_stage_id": { + "id": 361 + }, + "start_pos_no": 1 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [ 1 ], + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2529, "comment": "Spawns Leo"}, + {"type": "QstLayout", "action": "Set", "value": 3654, "comment": "Spawns Leo for combat"}, + {"type": "QstLayout", "action": "Set", "value": 3446, "comment": "Spawns Gurdolin for combat"}, + {"type": "MyQst", "action": "Set", "value": 1081, "comment": "Starts Gurdolin combat FSM"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 361 + }, + "event_id": 5 + }, + { + "type": "PartyGather", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 350 + }, + "location": { + "x": -2143, + "y": 18, + "z": 52 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3654, "comment": "Spawns Leo for combat"}, + {"type": "QstLayout", "action": "Clear", "value": 3446, "comment": "Spawns Gurdolin for combat"}, + {"type": "MyQst", "action": "Clear", "value": 1081, "comment": "Starts Gurdolin combat FSM"}, + {"type": "QstLayout", "action": "Set", "value": 3448, "comment": "Spawns Loeg, Cecily, Lise and Elliot"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 350 + }, + "event_id": 15 + }, + { + "type": "TalkToNpc", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "npc_id": "Joseph", + "message_id": 15486, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3448, "comment": "Spawns Loeg, Cecily, Lise and Elliot"}, + {"type": "QstLayout", "action": "Set", "value": 3449, "comment": "Spawns Loeg, Cecily, Lise and Elliot"} + ] + } + ] + } + ] +} From a24334a771c90a2073d3149d09c617e290cf94ea Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 22 Sep 2024 10:00:13 -0400 Subject: [PATCH 114/116] feat: Season 2.1 MSQ "The Entrusted One" --- .../Files/Assets/quests/q00020140.json | 198 ++++++++++++++++++ Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs | 95 +++++---- docs/quests/events/st0100.md | 6 +- docs/quests/events/st0201.md | 8 +- 4 files changed, 256 insertions(+), 51 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q00020140.json diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020140.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020140.json new file mode 100644 index 000000000..c2c2ee1bc --- /dev/null +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020140.json @@ -0,0 +1,198 @@ +{ + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "The Entrusted One", + "quest_id": 20140, + "next_quest": 0, + "base_level": 66, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "MinimumLevel", "Param1": 66} + ], + "rewards": [ + { + "type": "exp", + "amount": 128000 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 42000 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 3200 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 13173, + "num": 1 + } + ] + } + ], + "enemy_groups": [ + { + "stage_id": { + "id": 337, + "group_id": 1 + }, + "enemies": [ + { + "comment": "Altered Zuhl", + "enemy_id": "0x020403", + "level": 66, + "exp": 217000, + "is_boss": true + } + ] + } + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 3 + }, + "npc_id": "TheWhiteDragon", + "message_id": 0, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 8630, "quest_id": 70034001, "comment": "Spawns Gurdolin, Lise and Elliot"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 150, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 3, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Accept", + "npc_id": "Cecily0", + "message_id": 15508, + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"}, + {"type": "QstLayout", "action": "Set", "value": 2852, "comment": "Spawns Cecily, Elliot, Gurdolin, Lise, and Loeg"} + ] + }, + { + "type": "TalkToNpc", + "stage_id": { + "id": 3 + }, + "announce_type": "Update", + "checkpoint": true, + "npc_id": "Joseph", + "message_id": 15509 + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 337 + }, + "location": { + "x": 30707, + "y": 19540, + "z": -31453 + } + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 337 + }, + "event_id": 5 + }, + { + "type": "KillGroup", + "announce_type": "Update", + "groups": [ 0 ], + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 2852, "comment": "Spawns Cecily, Elliot, Gurdolin, Lise, and Loeg"}, + {"type": "QstLayout", "action": "Set", "value": 2853, "comment": "Spawns Cecily, Elliot, Gurdolin and Lise"}, + {"type": "QstLayout", "action": "Set", "value": 3452, "comment": "Spawns Walls that blocks exit"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 337 + }, + "event_id": 10, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3452, "comment": "Spawns Walls that blocks exit"} + ] + }, + { + "type": "PartyGather", + "announce_type": "Update", + "checkpoint": true, + "stage_id": { + "id": 3 + }, + "location": { + "x":-730, + "y": 9163, + "z": -3 + }, + "flags": [ + {"type": "QstLayout", "action": "Clear", "value": 3451, "comment": "Spawns Loeg and Gerd"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 3 + }, + "event_id": 155, + "jump_stage_id": { + "id": 337 + }, + "start_pos_no": 2, + "flags": [ + {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] + }, + { + "type": "PlayEvent", + "stage_id": { + "id": 337 + }, + "event_id": 15, + "jump_stage_id": { + "id": 3 + }, + "start_pos_no": 2 + }, + { + "type": "IsStageNo", + "stage_id": { + "id": 3 + }, + "flags": [ + {"type": "WorldManageLayout", "action": "Set", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"} + ] + } + ] + } + ] +} diff --git a/Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs b/Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs index 2dc4063c7..e3fd03aa4 100644 --- a/Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs +++ b/Arrowgene.Ddon.Shared/Model/Quest/QuestId.cs @@ -16,77 +16,84 @@ public enum QuestId : uint // the respective directories for different quests. Seems it may be possible to use // this to create additional main story quests. + // Season 1.0 ResolutionsAndOmens = 1, TheSlumberingGod = 2, EnvoyOfReconcilliation = 3, SolidersOfTheRift = 4, + AServantsPledge = 26, + TheCrimsonCrystal = 25, TheDullGreyArk = 5, TheGirlInTheForest = 6, + TheGoblinKing = 27, TheHouseOfSteam = 7, TheAssailedFort = 9, TheCastleOfDusk = 10, TheGodsAwakening = 11, + // Season 1.1 TheGirlCladInDarkness = 12, TheStolenHeart = 13, TheRoarsOfAThousand = 14, ReturnToYore = 15, AFriendlyVisit = 16, TheCourseOfLife = 17, + // Season 1.2 + ABriefRespite = 28, TheArkOnceMore = 18, ThinkingOfAFriend = 19, + TheBeastsFinalMoments = 29, TheFutureEntrustedToUs = 20, TheQuandaryOfSoliders = 21, TheDwellersOfTheGoldenLand = 22, TheGoldenKey = 23, TheGreatAlchemist = 24, - TheCrimsonCrystal = 25, - AServantsPledge = 26, - TheGoblinKing = 27, - ABriefRespite = 28, - TheBeastsFinalMoments = 29, BeForevermoreWhiteDragon = 30, - - // Seems like quest names and IDs got out of sync when this list was made + // Season 2.0 TheStormThatBroughtATragedy = 20010, - TheStormThatBoughtATragedy = 20020, - TheGirlWhoLostHerMemories = 20030, - TheCorruptionAndTheKnights = 20040, - ExploringTheDenOfMonsters = 20050, - EliminateTheCorrosionInfestation = 20060, - TheManFromAnotherLand = 20070, - TheFateOfLestania = 20080, - AFreshIncident = 20090, - Negotiations = 20100, - LeogsIllness = 20110, - AStrangeLandsLight = 20120, - TheLoneArisen = 20130, - StrayingPower = 20140, - TheEntrustedOne = 20150, - ANewContinent = 20160, - TheLostHometown = 20170, - TheVillageOfSoliders = 20180, - Homecoming = 20190, - GallantFootsteps = 20200, - TheDarknessOfTheHeart = 20210, - TheSpiritLand = 20220, - WithinTheTree = 20230, - RestorationRequirements = 20240, - ReasonAndBonds = 20250, - - TheNewGeneration = 30010, - TheLandOfDespair = 30020, - InSearchOfHope = 30030, - MeirovaTheVeteranGeneral = 30040, - ThePrincesWhereabouts = 30050, - SurvivorsVillage = 30060, - TheOpposition = 30070, - PrinceNedo = 30080, - TheRoyalFamilySacrament = 30090, - TheApproachingDemonArmy = 30100, - PortentOfDespair = 30110, + TheGirlWhoLostHerMemories = 20020, + TheCorruptionAndTheKnights = 20030, + ExploringTheDenOfMonsters = 20040, + EliminateTheCorrosionInfestation = 20050, + TheManFromAnotherLand = 20060, + TheFateOfLestania = 20070, + // Season 2.1 + AFreshIncident = 20080, + Negotiations = 20090, + LeogsIllness = 20100, + AStrangeLandsLight = 20110, + TheLoneArisen = 20120, + StrayingPower = 20130, + TheEntrustedOne = 20140, + // Season 2.2 + ANewContinent = 20150, + TheLostHometown = 20160, + TheVillageOfSoliders = 20170, + Homecoming = 20180, + GallantFootsteps = 20190, + TheDarknessOfTheHeart = 20200, + // Season 2.3 + TheSpiritLand = 20210, + WithinTheTree = 20220, + RestorationRequirements = 20230, + ReasonAndBonds = 20240, + TheNewGeneration = 20250, + // Season 3.0 + TheLandOfDespair = 30010, + InSearchOfHope = 30020, + MeirovaTheVeteranGeneral = 30030, + ThePrincesWhereabouts = 30040, + SurvivorsVillage = 30050, + TheOpposition = 30060, + PrinceNedo = 30070, + // Season 3.1 + TheRoyalFamilySacrament = 30080, + TheApproachingDemonArmy = 30090, + PortentOfDespair = 30100, + DiversionaryTactics = 30110, TheSecretEntrance = 30120, ADesperateInfiltration = 30125, TheBattleOfLookoutCastle = 30130, + // Season 3.2 TheRoadToTheRoyalCapital = 30140, RallyTheTroops = 30150, AttackOnTheRoyalCapital = 30160, @@ -94,12 +101,14 @@ public enum QuestId : uint ABriefDragonForce = 30180, ThePlightOfLookoutCastle = 30190, TheFinalBattleOfTheRoyalCapital = 30200, + // Season 3.3 TheMissingPrince = 30210, NedosTrail = 30220, TheRoyalFamilyMausoleum = 30230, TheDreadfulPassage = 30240, TheRelicsOfTheFirstKing = 30250, HopesBitterEnd = 30260, + // Season 3.4? ThoseWhoFollowTheDragon = 30270, Unknown30410 = 30410, // Name is in Japanese Unknown30420 = 30420, // Name is in Japanese diff --git a/docs/quests/events/st0100.md b/docs/quests/events/st0100.md index d72e65932..510300a3d 100644 --- a/docs/quests/events/st0100.md +++ b/docs/quests/events/st0100.md @@ -25,6 +25,6 @@ | 135 | The Man From Another Land | 140 | The Man From Another Land | 145 | The Lone Arisen -| 150 -| 155 -| 160 \ No newline at end of file +| 150 | +| 155 | +| 160 | \ No newline at end of file diff --git a/docs/quests/events/st0201.md b/docs/quests/events/st0201.md index 72a0fe923..53666c004 100644 --- a/docs/quests/events/st0201.md +++ b/docs/quests/events/st0201.md @@ -39,8 +39,8 @@ | 135 | The Fate of Lestania | 140 | A Fresh Incident | 145 | A Strange Land's Light -| 150 | -| 155 | +| 150 | The Entrusted One +| 155 | The Entrusted One | 157 | | 160 | | 165 | @@ -61,6 +61,4 @@ | 235 | | 240 | | 245 | -| 250 | - - +| 250 | \ No newline at end of file From db31bb72b7d1f05e069a660fe8abaf22b90ce3a9 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 22 Sep 2024 10:11:40 -0400 Subject: [PATCH 115/116] Add migration strategy DB version increments from 16 to 17. --- .../Arrowgene.Ddon.Database.csproj | 4 ++++ .../DdonDatabaseBuilder.cs | 2 +- .../Database/Script/migration_msq_2.1.sql | 2 ++ .../Core/Migration/00000017_Msq21Migration.cs | 24 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Arrowgene.Ddon.Database/Files/Database/Script/migration_msq_2.1.sql create mode 100644 Arrowgene.Ddon.Database/Sql/Core/Migration/00000017_Msq21Migration.cs diff --git a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj index 2b7162386..bd4409fcd 100644 --- a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj +++ b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj @@ -40,6 +40,7 @@ + @@ -76,6 +77,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs index d6755acf9..078396e66 100644 --- a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs +++ b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs @@ -14,7 +14,7 @@ public static class DdonDatabaseBuilder private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonDatabaseBuilder)); private const string DefaultSchemaFile = "Script/schema_sqlite.sql"; - public const uint Version = 16; + public const uint Version = 17; public static IDatabase Build(DatabaseSetting settings) { diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/migration_msq_2.1.sql b/Arrowgene.Ddon.Database/Files/Database/Script/migration_msq_2.1.sql new file mode 100644 index 000000000..fd52035c9 --- /dev/null +++ b/Arrowgene.Ddon.Database/Files/Database/Script/migration_msq_2.1.sql @@ -0,0 +1,2 @@ +INSERT INTO ddon_quest_progress(character_common_id, quest_type, quest_id, step, variant_quest_id) +VALUES ((SELECT character_common_id FROM ddon_completed_quests WHERE quest_id = 20070), 3, 20080, 0, 0); diff --git a/Arrowgene.Ddon.Database/Sql/Core/Migration/00000017_Msq21Migration.cs b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000017_Msq21Migration.cs new file mode 100644 index 000000000..5df021b8c --- /dev/null +++ b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000017_Msq21Migration.cs @@ -0,0 +1,24 @@ +using System.Data.Common; + +namespace Arrowgene.Ddon.Database.Sql.Core.Migration +{ + public class Msq21Migration : IMigrationStrategy + { + public uint From => 16; + public uint To => 17; + + private readonly DatabaseSetting DatabaseSetting; + + public Msq21Migration(DatabaseSetting databaseSetting) + { + DatabaseSetting = databaseSetting; + } + + public bool Migrate(IDatabase db, DbConnection conn) + { + string adaptedSchema = DdonDatabaseBuilder.GetAdaptedSchema(DatabaseSetting, "Script/migration_msq_2.1.sql"); + db.Execute(conn, adaptedSchema); + return true; + } + } +} From 987934f0f83c4f4698c049c6962b6dfbd999deb5 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 22 Sep 2024 11:02:02 -0400 Subject: [PATCH 116/116] Small fixes while testing --- Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json index 2587d5a2f..ead70a24b 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00020120.json @@ -229,12 +229,12 @@ }, { "type": "KillGroup", + "announce_type": "Update", "reset_group": false, "groups": [ 0, 1 ] }, { "type": "OmInteractEvent", - "announce_type": "Update", "checkpoint": true, "flags": [ {"type": "QstLayout", "action": "Set", "value": 2471, "comment": "Spawns Collection Point"} @@ -254,12 +254,12 @@ }, { "type": "KillGroup", + "announce_type": "Update", "reset_group": false, "groups": [ 2 ] }, { "type": "OmInteractEvent", - "announce_type": "Update", "checkpoint": true, "flags": [ {"type": "QstLayout", "action": "Set", "value": 2470, "comment": "Spawns Collection Point"}

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∋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 literal 0 HcmV?d00001 diff --git a/docs/quests/images/general_announce_type_0.png b/docs/quests/images/general_announce_type_0.png new file mode 100644 index 0000000000000000000000000000000000000000..21ed117f6df51bf970f5409d43e505ac80f854cb GIT binary patch literal 25560 zcmV)3K+C_0P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DV{J)9K~#8NmAwa? zW!YWd`F}Z8>gpV(yC?Saec?fzw=&eG}GG8$EW)CyYJo;{yCg;?tPhC z-}6nIg?v6DHe;wiJ~J8pKg`7DX81}*7dAI_rti8D*(?=wKGc7?Y-IF3m(9jzwh)V`IbPX*BhbQ=eH?Ro#Kw zs4abE)F<~NGmOl}&@fRilP^R*S5h5d$+^C}8g6K0;iJAQo1ClfyzZ;7;b0INGz#jj z*WZZ##z1gw2rogL0Td5!&uF}<$;r6$^>;+AR*qsdqFBjCqpoo~5q;qY-v=784}JQgrFgr( zx~B1aBI!VN8`0@?Ouu%kAN6`C>TB(|bg3cQsy%cN204wlbI10W5#G}i)u>jA2`|wq zBi#DkuEx+I6!YRWJbZ)04H>GCLL3ayFKhSEJMFm_|Lp*%h5i#av8O ztI=pQEh{KRlhoSJH}E$z^n|C%1zbf7^)xo6ffMrC}RbeglZi6Q$4k(Ur|SOi@Gmk1Hm*l zb$zIHUcz6}7zK<`sTiArZ&P&&QX)xx^kl#~s?!@ObDvKDqyIu#1RIJ`7>A^+9qQ-+ zHWbD55=>ZMrzio!U_)ow3@K4D83WN2Wh@md67F)$Zr>{QLgS$XO8E2yx9sJH z6-UAi?+X?8Jy1s-85oYf=+}{gx1_j@wv=9q-_>`ty@!zkd9{IVxq=ix%3H2fq`Yz> z!o_C6#yIhQL$Wf!Q0p?X1kdxrf%Z6noX(4CTP%yeQVjPY83C@23_ZrXuj>QxGbf`_ zothHuijo7FX!Q>aMbRLO+?6Wk--hTr;J!YG!iRB1yy!`==@T9f#W%QWn(M5qde8=* zp!(pjAs874bAZBQfT;_e85aZAm*E7zp5&#!(Gyx38{0xjawda=(J4>VB)+1LLss&Ng2|&?qCcyL@+Co?r(_jFc1cyP_~sCn`;=8jPI#G zU!#t$iD`U-Tvq2phQa_aCKedU(l{`A6AxD|M@59&RDTHiP~&tZ#TE#@>#&j1^_B#* zD@GwCz>ZOH8}$vY7$S^FS>6GK$?H5XMVa0*6B80BgabuJ&@nLdPk&iQA9I+}p+khXcm(`dkLNcD4_Aq@B1y8vl zx({@8bsa-ll&3*Sau`DMOi`QK(g)sdAR!t;XWhfNZrQmb@=~bz#U)?st8XD-oH`sN|I=!I`f$Hf32NBHx#-YTbL$x-cD5)AH;aY$`!o`k3 zG6MarYAB(2fQ)2|il_wh09i8*W#gBT0*npyhIlp*%@`Ylj)5d<%u0cpii+BTSGs1o zKo9cjk1;V4W4aO1;RwJfD>@;IZAF84W8}t>stvv9u5*+TSwem=LKu&p7y%AMQ}l=N zEr@@}4sxA$^a8&z#?k?d4t*iRf-UhzMn}H$wrUp55liU?xE-;?n`q3GWc0%Pc$gKJ3$WC-|9Jt4|?S$qB@py;|`RcI)K z={^vMtdt+YfLQ?wRS@y2B7Q|4F)snK zmlP}*f=ab!CBpJI+X~CozXMyrO*#aF%7O=h%?h7^`bRktAg4R3+f$pa4!lMd<;L3y zHaut>Ws{IKrDW?mcJA431q8NiK?XqmTj+#SU(D{paDDGe@G&OPTd|?(2`m|m1zrSr zbUx-S0zalT9)EgvM*ImyByH0km@-PYpk-eICViIBLNnTm#)1*t`vVyq3G0ByYS-#0 z?3L0t+EO@;L4C`Q#s}ZZ#DqiNvJ3$IL0gQF2D7mnJ0|eJ5McB-kpUSBlm-O>udH%9 zXh|dqJ@KSn5gJ%A+KzVAH>ATT^rRd_p~x!+G%p@wL@bM<8-0NjdVxHEx0PRgBkKdn zw&4Rd2^r^(Fm!*Q_9&nGQa{KQJi$Yk6y-o?3{=5#K?GFRHOZT5qx|$|;~;obq>G^} z+1OxQ(bGJ#DPCbhfISIB`Hr5z@+f$6F^g) zk<#l(*EOJkf_ZrXoF&}go@yq=(U}>Sl-*&z#~BlpYcNP{MhZ<|Fco4X@2~MjV-VWf zxcHim5g|M$pzk^5?&=|dDrIg;(6aI%V?pnWh$v-O1c4zC#;^htW8zKJ5BQ@*c?pzq z2E&CA5|(kmFgz0iM9O2M$2qhSJS{~NZH3r`c=QGF$l^J5XwZWB+TH$Ikq6u+&LmX3myWc%*zVk{{@@+LXa2*JejlFATupvV?(XsV$k>xr)F ze?v-%@x|a}!5126EZRT|V8EEwW+p|?j^MyxLNoOR4w8WlXl6R3C<5O11s|Rg4~W4+ z@G(YBr7XY?FMz@6B5ku8Tb~tqegqWngpwv@Rvp3a=nH`rZwIs!ornOC6?j1Ij}e28 zDQZoyGOqwEC^rTM7}crZ1O_55@!s-~9EO!&v>=Q|&Pmzm631cXS3f=Sd*S7F8d2o6 z3=&1sAM(Xv`PO&(rH=8CQ2XN1|%sK6Lb&{WG*YcK4AdBBTSAotmtX1@wkbZX&eXy(-|8$3B#DQ zEQnA$ar+x*enV>#kmYITJ&^KdXV8Bb`6s6<{09KZxAjT9W zV5LgU_pK}%n@KuwF@}DqM_&&4rIZf;)rX@2 z^@*Z0R{`y=J9Kr_WK5`wpb=rA{M^LzlT%ZlaZwzPr@m1Z=w}+}9*PM4@jOL5irOb( zO|-+{Rh5g^8%^`Ap_GGuhMS_hXatS}dDX0Pe&$UCij@GE48JNc*~jEmQz3#4lgCU- zVOdgt%okN^%4enc7)sAEh|b7Ff=BgHYF*>8g6jKVdvd2Ogde5^a;=hL#vz;SY~=!|=$! zKyOkCWDet;Ca4)hG?w8aa>OZQRSzY{K@Q0O)j#K|?6HSLP=c?=+>ORyJ_)C5L&YC7 zk(V!&D)L(x8X_?9L9&E=IbslCxgEFOeIJ7Ejs?6GT#Pd^8puM4;^`m2TbTtcK)^8P z*CGl7g81dhLAR$*5C`C~bxh1gfW}53^_lv0E+hi8dSIrE#!Ch!-M2wN7}U1i?kK!h zD8mGXh9ea2^a;h1m3KI;df=CoR-Q@c76gP;=j7lW84DPn7$K!sNRPlX6U^9n0gj?Q z5*8I)6CHpTi_4gV<#-uZ4+Ny1LCKwURLn|0`;(L-eis-HbMHnkCd~s@|I#z7a zo3R)phYXwMx~;xFL8oqUR6vIJ<${$}7dCZ{yufGY_%uWmY{WG-lXb|Kh-dywH|Up1 zREPVL3v>h+5L)gbPibNjhnnyKZ~FJduK4c$)fOX;zIYyid|ua>%c4HU203cVI1`m3 z^L<4vHb#so7_!2dv2#0byXU^K@+GAj5n0Gv`8gRQ0KwJBM22`U-h>p;v_*L!7wsSt z{kSjKpwCfTlv0gSU#dSAbc}?F;_s;~Cy5Zr8Y8G~D|qs%&oT)D%km9gg!@=|EMixP zhzIIF8&gwLG7uH_kMejz1|}F^!a7+;gOLQy9$k!}E#FZb@Z^)R`Q$oELVE-mYwqie zq45!ZV=fr+o)|Pws=_$xt2+I|G&4@;cyMx3=giPd_4&gfj6G=zE1^%K7c;=n+mV!w zny#YI8dIkpiyR;2b~vi`%ncxH#vdt%uEHI~!?5HfO!N)?2>J7>pD$#it5haUR$Q$bI@7 zj{*FA_chVeGT}POp@5qc4&#NQkOm1FWL4up68LE2uKq|l$4&*>!BY;m7p~BRC0O9a z8?yw>L@p5+3e6bg<4pQFx|Qcm6Oiy$W7xpJuM`p4;TmO)Cq6-I_@)1i6VB8rC%7SU z&`8(q1c;bG0GW^hK(EHpjP+G=(2|iRC4-h-87fjF>n%n`4ugJk+i$?1ED%pa(xg>3hB^E!ezoC;QI5V5{O~RR6vftV2;lX44_v3|LBUFe0QT@Pphnc_RdY z@d~u)qIgAT*Fds0C$Wh+mzLy*E{1WQ1d;l->Hf>B#9m3Tzr+XN`WVlgqBC9lU2mctK zek@O-qvc3rkz!$FoSq0K_{epXObw)fx~~Jn!Wh&;KcKSZNd4NQ>OS-!ssk?+4?eT{ zMYPD=g(GCiMUoW^kM1cFCDKT;l9r&+8Te?qQB<3BMnz-58yk1kw>*jN^hwnIJ}K?NC2b7sOv(ZHtolG`EWhoI2J!~t-fCU4F4#28x*4NhG%ijMI70u)6s zU<^Q8*HK#tCO{w+1mnK@re7E!1~Llr3IYiZ4xKZ;&QSo7MHP}cu%*;^6zre!5cS0x)sdghvjc_19BbezI_$@=gAl$~D z7@QMyQZ&Iym`f@^$YrJ1b%ADK^q3)4}weT(C&0x2X70VTWy8?f&(K2e9(tYHW&bh;30^_>BI$^{ z9?M{KfKd_>Vruz8AdT+Y$V{A>!}Z97vn~>k|KkI zt39~d!IM0SDj0}hNTWQa+HV+sjf*VPm+F8ghLnj<%ck)_@dXb`pze_aRt6Zy6LFA0 z?U^u7^DW|!@XY0iC^6`xlx|ET;XstgIr0qN@XUJY^GtLk3*Ky`YdxjH;D#{*4ki|d zG7|FqufLCuHM$YC_0&0|rLb1#3~B*LhbO}zu!xpI8knMcDwhF7>3vQc{-u5yOSIE< z2tgZN(|1ttOzKCSVMpADd?IIG&jWevLz0 z+JG11BCsep{c%~flA+K!0;TT6ESe`8fVcUhd-O}H<1|7C_n^TT-&2@;-%r zx=v1x5*bG0uDYD7zGvLUPv`zp7?chf1z&p+U3XfdaqWc);pOIq}0@Q&;iW z@Tn=!Pi~K*3rz_k%X=y0XIvXR2_%@0Wy}smeV6d~;sH3Kyx@yKC*w#-!VVo#QsAY% zy{hgp9`}$-!hiFKF;Qp{F!2nsiQkMP`T$G93)*v2@&|vUq>P1;Kw*iT+z*3TrjpLU z1ENnHB5)KcHNlSo5TA6op88I>0*^k0FTC)iDn?GY5+%TU!>(g6z*wpsd@`+cO?_aj zG3>yGZdvw$OV_*`Rn83O1233^k=C0>>cF_5Pr0pleh`6pLdHbXp9#uf5M0-Ma1N6M zltX^3rp{f|1&A@28KX}Zn8u&4sgCQ>6<5bDE9eAog zNWy(4`xw*9HM;L*OkFojjL*6IVd955l0-}B3%#H>c%T^6<6f!*@~MvL!F8Q8wo@w? zKA8zmG~|0*)}C;jeForwU@>V#|7k2D3t)p^QZB)4;pY$>X>*IjO+03@FDVjzj|?3W z2~5B)csDjTlEIU*ajdS_qbWlKDh~a1&hjTX!XNT|`my2|x5RS_Yy8j>&yNu>y;XpX zKV2{_Hks_w;kg^bP9GRS)m5JuJPZo`rU^=o%y)g_wUbf{FB>nZl)i%>bV|lX-_#dI z*z|!nqx&Yg{)1Q092lJTsUDx+pCH;XHuOzVE=sT3@Xjz}tl%Xylfz)fU&hmLm?o+( z8o^uop*{y`Acg>$$7riNz;@lC_+lC7Ty(OW2v_8ePl}Zv+_&9+uRK40jF*TEBLI{h zZH)m0fD8nJh$U8CK0y}3j|gLm>EMGtl3;-h6eMpI!s=h9OAL}#R>ic$rvqZCzCTBV zqf3y0xqP=*kdUPbu#qrp91esm30EQn6K4n}#6XaJ9T3$w-)%Ig&ppEo-qZqDKp88k z&IRk(140WERbxYBj1YLTN8WvSOff-yTQJlg#={{dLZoXJcF@xo1SsL4|A9kwS)wT* zm>$n$0HRS_^#xtk#xvE>6ddSB_p;fd_Xw~lh-ID*`}&*hB$(upYc@XjO+B zjA=RwH$Ow7?=1P!hL9T`xj(f_e9#Rw_H!7>j&LyD)qwFC&ouTtLD6Gaw)6=uDd$gP z^N(|3tb0T`I8%74Yl4X$p^GzZK-Dig&%+Cl;rzAR<`%2G4(4+X} z1q{e`w~2S86gYdxVS080yzID7-<6xFcxIIYhscatBQ^yC=9B95SP=l zSKSr+c5I7bWg^{I8P)vq$_RxS>c1p~JkA0f1Y8s4OwG{gJU zuemK|cJB3ZB>lBp&FG$gIf@rgM@=#^yLHMrJ4;9i7lYYA>&)J51Q*J{awQKVRpgCw z3i%5qa%koK*kz5#gEsce!V$01a+r@nMA=gQES;0_>Dw?1h4f%alLu|m?0bsTEGjCN zwwS1vo!XVlC9ghI1TWE&X(^tb-l#;gG$SP|%fKlOR3N<8XvTrwg{TfX9t$NYDeQ$O zC~1c|2@PT;)EFG-vL9?J$$$|V5J_zG)rRZvKSd|hCGQvabS5NZ_@G@{LPG|0N6x_$ z#e$Ouy2_$Iw>DGo}0)kRo zX!YF|SLX;BgL`9wh~x)(=)?bySg3j&uBYSZjb9m`{DwPXYe|yxKUp9zc>mlp3-LG4 zo`_$4*PZc(?KN4zh~IqiV*JJb_(YTzUb4cOJ|3D7KUr}U66JxAcq#$NKQkBH5CE(% zijPoIER-Wfpf-5Ie5Vu8gFoQ~j`V|qbDecp9%e}fN1-qV(p`-D0+>-DHLR z5(r(>kfy)?be&u2ZWznd6r90jhAA0MJao5(WBa%fUlG9CTFY}RN%8e(P!heN$3(TFD2fOG1xESH`Y!K? zkrG_bZ%)Uz+;&5J)4?rKL{8Q2dp~nBau<%p#GpOGO~y*RrY%cdO{GBKM??YIC^s^m z9YrsG8c{sUW@PBqwj@6D^e77MXbHqs92rRYf!8ZULf?+T`F=`K;73IVbRDNpR$|@T zz(Ju?>XW_-M8Nvv&!$WGUym%7c~w~pF(Fp z>8U1`De-~vG>pI?Jrz#gb%%hd0+!sAQAyWy!hOE;MFha>V?9!6A}I9Tg-@=*AQM#` zsR;%nr5rm;^5n&8+%TQ4Uq4lfDG{rkFFUlfchOk(TFfd7 z2W*)`C&8?*HV6cdNn4m^I_PlNroO%2FNSi?Sd58BgK1Uex-}gYc_M^>Hskv!Cu740`t&lV+R#1Y zxrnJeS8$+K6s=39&z@>AvWm9Yy@x3)UII2xpHyem%@5(!W?Br82d zg`T~aGSgJas1+Q7vmS6y%?Xm z&@^*iU2xV|Xq4Kyz7KFf(2}qL-}pZM1{UG8z!g+67KEfW8X}AuyMiB%(LwsqZg-tO z+e=6gtaNG*NSBSA3Xuegk0me3V-mjdkVTpA9C#!dfRdEAtOJE&!;&P&3Qaxtp?Y{K z<{q2_>tJ4ia~F-515CVtzB(Nn!SKqG6`btZYb)Hwn*%44OI}hFoUjf0;1N8o#&_N= zn22650`!j(BN6Q;X$^GDMs<2ZhC_XbwpLcrm%e!K0HH5N1>@5JGS2%ggmWo?{_F^+W@aVbt!b>m5ljkqG?!%|%+UhNY&e$laQv}1r`ves7iRcVrGOA*tFXfBI z2*brEO2f`QCXSe(<9z|HIck$40QGJ-5G-{ncm`W`L^D^8`{(QN+)77BD;_#>B64dN zBP$_5xQINw`AF>;7vZBHHnVUr1|%Z|;=0oT;W#l_gQP+j`f5z?`Xg0Jp4kM-sb9&4 z22ou+l|t&#@*WS~kRFV8HxiMJ>8XKC8^$9dqhEgtcgC_26pkp@xy@?4W#6`V-L|TY zCSClN980WTjPiy&oi5NTG%+vLr;EN5+~N>`*6%%lhx&uZ;LUhRel(_xvQFKX6^McI=kvf9S5)F*{&PzLpc%TF64G)+R85nPPC0*PU{Ah!b0*>(~2f%YFF(e)Q zXQId&@|8j@fU!h>N#RaXM66u((wS(Ud@g#=KCI*O(Rty~7{2gGT)HqHYpgkq%8CT# z!pds&o_{z7&p#3a)hVBRGOF`OmDisTY(VwJl0sngJQ*PvfxlR}}VD zrMQe=lCaSaWAQ?dw!9J#0r{m5B0)~V1KbSbZLrGQ5T?mmH6|yjQPF35HbI@}@M;2M z<0&vK3ddV#PsU$<`t$MopZ!Ao+^3$1KfeFrD6cL=URIxe+4Z%)vhKIo@LU9R$cuLv zDr9Fo`;C`{$?z8A$>XBl_b+4QiPMr4F{kk@Y(x?mjZ0ekden9<(c$DcccX--K8_f&+!moMm_>d{vJ1Q&FbpbI{}<1LW^BdYP3 z(8i!ZcV@x;NUUTBTpR^u$hT8En65LeMj0u$;Z4`U3uA>ifD`;=OlarfG@fNu^?-vi z#>s`cI*>gps17>w+pfSvUYrS7A_1u2319v5%V21T9B?497*|EB;OCVtwXs7HPV~=) zHX@zOb^q*p6-h(21Ov!{KnhD>0=YZ{P2mr93562k`W)SpPNas|R59HXgLG&coBGoK zw!D-LB%|J~EE@0pJaSa~Dmql76~!FIbB=kI=R zJn)UT#hZ83qAQGk`ss!ErTd=<$zn{-Y>7c-DryR|K(^IvM}Of|#Qe#~Hr5;lB_#oN z&`~2Xd{f>syZ5@7nBEc_waKVS*x0~>F&Na>qqlfI%8O?r+ieLCF;Vyql`9Xn?v8wI znuY1eDM#yw2wCOa)yb*o6)G_`?0SV_cHwB0+jZ{+;Mw~6#MT(@Iv7=rwZSW+gl7-> zkmofTk-0P%Q>*i#5iE$ze3QE1J^SR0QYgZA6c@5XVTjF}iZ*s#qx@UrD|F9b{p)Ly zU0;kst1h1Ey96Ltszh_kp2+XsABBnOs0iPV`i9PIne-lUX5(B(UO%IJTuM~ozQDv&^9=l8btyJP z*IZ*YGBQl$(N;v=pQx365Gs$O5fs$?Qh6-9J3jdOL-9Q~Zi_O!66|;X&5MyeeAon|YV{mnQZ)#ztS(NpA9+#Ezx2n~5vo#dq% zh1i}S#;VfLdkdFh>#$|xU~jL((SuR|&#j1VPQ}!gJt&=)bOOiHZbjzM`@dg$niLi0 zprj}=mX6{aM|YS>#5$%hI^DC-sV>(y1YjYLmzOfB16$bfeG!vRF$lD^_r*JksTcR$ z6#wR(cV9vI|A&>-JJYao>aQiD&)@U)QvTa7EB`M&e^G5#<1ITU<6V0vqaBu$M<~dXqOP=aQN;R4<)MJ{6lU{9fg>%7dO@Va-zH{W(m%#w$z{=tfGzE#A3%D!xWEWBd!9fybvjw$zG0KeZZjYmL~QU5~5FL?A;M z*sKipFzW3!MH?~-;vYhfhn3%yuvcTgTaDwbvU2{(c$0Vpj?m$CTdPto8Dt3}soE#! z>hb7OGmf8~i`=PaBj2f8@tKs|vZWTYTPD5Pr-EETuM%+~GkuB*1hyTucD%PI(SOefa6aa;$yePV7Yu4&K3Otv3iDC~)E zz4e-y89m4F_vhB5xjYvKH&>!0_*v!X$_cRb`gmb?+;M$cSwSW)w)^on&mD`q^6RlI zMV~KjjeKP~zH-lWeBD)ypN)GZTPre_FRpgthwxHzOWLv<(6VcC=ydNxIDab15gIs5b_Z72v^9tc(v$ieXaqS)}9Oal8^5@4dMD6@>hpF^U z*oCk&=KAg%QqDiYCW?rvl;@idZi#O>IOA(qRnyFBNd#g=00G=NS&irNTcdvATtr)* zRrfR1$;jXQ`gq@6H^qg{o$M9gLmwWZ#Xa$ho(#M_T7_$Wk&s!+?I_NPh4up@p|Qib0u_u+auOylKZooEhZf3!Qva`aDTdOdp)=#(ZoHNKR;m#fAj>r?U0tG34nZ`&E~ zzkX|6H&seBRb@m91z^?ZU3;gD=gD3!PPD}@MLIZ|$*Drjh~`r>rI?tINh!*(4G5ii zA6+KO)nc6LiAIV-UY+v~S1&|Ku(jne7bg$ISKqMTwEka2i>oCYdq%vijCgyy#(AT7 zyQKafSd}bo_M@IH$9LVbH@-v8=*@zUx}RR^#bm!8d8Ktw8P8oG7GrH{#k36N@!Yn!XW#DlvDA0mGA$ty9aZv3;ZN4L*eT)-i|rq=GmF z^btNOl~k&^wzh7kU11mHj=SD;pTl4kK2cJbnZ74QXBQdQ?eSrv!vh_v&pAqNQ$RoF zGC4_Fjv$Ii!irT&dFP<3kOUzXVD0Ih@s8{Eeu?t`*|CMF%^fE;bM6fyY8E`LwYKy6 z_~(aq#sM*toN1?ocUp>bqTY+YKD`!?$ZI_-?};+u{V)!9%QBzT{U=vCk-c!np?+`w zP4U({ZjGP0dvDylMIoky=y_S=KaM@5T$MZd=~#KfSmVfB4c$d{`dtDG3uowM#+@40nib zpE$P`ZAC4FsF7jc0^H{HlWbg(5DEsK<;QUh6!zE3+v4Y6x6g(YozlQU>6jZMB$??CcUxKYYF+Z@C&(@nNV47uql7CgPf_4#q#ZdiJsb{r1nCv5~l_ zbYTDX9r3f0ty^cyjslOYb>hzzW&Dn0i%D&~HwG!mRyDdB|8M8Jv3;{8>dMJZ?~3>A zpD~Rv%*fVQMp+?yTJo~k>cz^+a!mKuly+skG>DUg?tSUa_?-&Nt4giZ|H{g$4Fc&% zwW?Hg=iP7iY`-UY%u?}KjKs_1aUBil-dOqV$ra{v&61d!s4mMHd4E+E83g^|6;uVe z)s~m(AeUNgy6t8N}R*a=?vV?7jM0DN<7YarqBQLw@S1 zh4|RXg?RGi^Ks$aS*It54jqagymgnAg7VW(Ux=T5dOjXJdMb{*bSl0e;r!&%K>4OT zqsH5&K5v$1Kp@xG*P`5MBqEyL1a0~s|9iumtyA) zx5Rhcyjvb9-TS{pv)_7tE{+^I635P-ji*<;@$rjFbsBwp8ie#WURa1NU8Oqxo|h5J z6-kK{j1>(84#i|_`kGY;geR*|o{)Ok#GVKHBmt#)KGuLd!$LE@HwBENd-6G=* zU7wLapE!3urt-4#Ek@$cE5Wcv4xFKhgT zc<}7SI6Xff$CexMvBjQK#j$LWI(&Mu8|Nik6VP{hhaJmUekuR)$YR{5e(pbeDUMuR zicWnwCN|qq+vME|o8CRh2Bh?=c^>I)L{m{J24TJ4lp~d)R=Ud`5_WFN`4Xa&tJ6I> zIZDok0u&q$Se-}rMq#HXVtC>zJ&Ww8uRPn!?5NtqG#zSfG39UEiw5(NW?(O0{nDaK zqz+!1K(a?wSUyYee6irM?voHd`RM1O|JlEbjVJGq&SyUyA9?;|?;{wOuiQQ1omX_W zIlVOwZ<~yF?3(mFtmtnZxfrJ&elW66d?Ko+o{8d-N8;k+55&KJ{8W5SQ2=F&tn{}Z z+8!N6D_1H%_soU(ch6pk^%tIqZ7+U4s>h#-+ zW{*D>JCvEt zc}8Y!6Mr@&NW^uCM^m|eTx>PsC!aVUzbZxe6QyrkyXWK1(o)<}Zp7|PE7n?VM*~fg z(NV$sF;QzohUyJqWyuujN54Jrd-r?2wuGC>w(hG%;BtXJtz0c`+FG;mq7cpe=8KE5 zasFuRzjQpV>s^W)H2pfUTCO)M7{2woZA$NyiZ)i2W2ftse{y3NJtFNgXCtu*~F|2o3l1JN7wunjnwWz2A^$ICTLu{)ceEuTh+_G^mNo# z7i@)@!Ilk={LHK)6}$*L^Zw5#<|4B+uP`|u>szmi#l1Jj{QldcvHf81yftYD%u#iF zrbe;q-ECa+!%HGQDOsghO#9$VGQJoB8Rjd|lfn|_a|^36-%|87y7q@(aUkAzK z{HAE#@s3zNcxNms2j97NE*^W~xj1^{>Dbm=G0j*OYY-m;l&W%-h1P~mT7TO2R#h$nM9 z<8*#Dc=0Id*`lHf3iCUSOfhB#>k6;iHjb34Waqu|@aJ;VmS=X9-n4zj=_KWXWP=IP zot>pv%#^*max7c$a(-=1iZ+OIo0IW^#{PVMUz{lIR_a@fkDgzTbFCB&)YNL-}w#ij{p6gZ-@`Q?Qs0X*B*|?-g_wi^P8?x?w7(ZCi5sL;+4vj zyr>Yg;4Tm zN=I-g7z!qdL|h+f*8vQaArKAy4mX_>aua=#79|W{RdlJi(T(!jf@hac$shtgx#S;x z#X;lvr(bn_{Pf#ziF@w8D{}h}$A$8CFJD#%ZRus&ew<`Oc{fH(cBW7g-*ZD59tnDp z{SBFz(Dyw`4c4O^=QG=)S=tj{clA~A`)@xKAAaYx@#}Bc=WWNYDI1k+ee`_IXLn)IqbyoOY08z$L0OkZB;I>bVm93YJmyfa$G%1;XXC5&{|%0yNpJ! zP`e!N&q=A_;niDbqp8U)#y8%4UHrhEY5Ygub0~i2&HEFNRj!q6ff*a2u2fjb zus%r#J(;IZ_V7cGv~#Z~ol=2~jk$?R5o$=AHxh_8a53Y}& zQHb}`ckYhwKfK+`iG<#i^J3&lDGez?cC=i0r2-=`l~`RHN`Wy^Dk`r_{V)Ob3-|7g z`!&|b-*w&P@&3hivmTGsfGC>=g=>V1lu- zb|L=s$rt0NpPGwL%+-w(g}jm#jqkW&Yy8W;nL<)XfT5#9?`<@hMP4o3mPUDv?TFTcEka)2ZA5WFPwDjr`{K{nZZlaez2Q_HO=XncPE;7SE&>t_{kydnsD zJ{tA4SX)_)Zo8@b7zBP9NrnJJEm`DE$*lKJ&dx-2dQvHS8ndqyD!aMamryZ7D1z!A z#OK5h9*jJvqXJGOa2NuR4#a4(l zVnUHGW5iQVEB8{A0l@RX-f<-InxgTOQSq0N5OIE`l3-?Sn6WvS03&yMajhHAiQ&w= z|HbLm_>H4Wao-mf;)4p)f9SFE@k5_K9Y6lS3sGb%@t5hJr55&Lcf}ZlVZ7%d&0nPP zSoQ&J#^PXwf|+RMD1PFpd5`ylkDiYoR{sB=pF9~8?NzlS)F%{F%kkJ;(4nqXxPE1z zM%ALM{C(#9i}BG%pNt>=%!&AcN6y848t*SOHZZU^0#8Lq{vV{U|Ms5!QQ37so^JB; z_-4QOmUaS4>4`eTtE_V6l};v}m*RidpqcD`ts7hV_4xa!)5Z{*P+>pM3bG*g336)luea zam3NGcHe5X4?;Qy~%D>=!JJz8X)>*Ndt7wGY%8eRVjVX27(=) zQhq8+$!X#7#)ArS3SWhh6;GGO#=&J=#n&W+Vh+FSmY%_`*a+|-3(Vsf1H^+PY~AF6 zkSi7XqsL?bcnF3iw6yK@N`?AF1>=`r&=_NIr2?94q&TZ^pJ;-E2af*X{V&J2KXfX- z^MRM*yFYy_KKS5^@iQNPB7XVrKNG+9x1Wr^d*Gq?G-Y(PsaB@cp`rCp`-DO4?Z2gd;g>H z2M;_NPdxohJp1$)V)^*w6P&&L;zAB&?Wj>gLQld-S2Xc?XCH)8(MrFiIKBRXu(c&l)pZ+7R{BVsBE+?<6?N6dBynT{Qta-BL~F-5Bn$@ z6ko{op&)6J#3b4>7;7u*LW%@ zPu$-XG+4qEP-n(BHojupbP_U2NzPhkw8C=IJ85`YpEH1BJo;;#FB5!g3b0=cq!H>kHhd&X` z$3GQ?=RY44FFh5r=Z|EOW965Bkwvc&xp65JG~Hb>d7e2 zzo6s!*u8u_uBxAj>$?k^J3Ac+1&v?5oX@K8rrz*7eN`mS+3c!NV&lL;nw+NMlLXA0i98_OlQ~XoM>fFx) zs1BcBu3s+q7S6m-kM^!>|tK0a4ZuRvE@Rw($T7hdDdxcmo> zgy8L#qDEqDsM|n)){<)R4 zs0~_HSRRm?=&pE+_IUh%uKIgtx5W90y;0zK2&EJSp4V5JbZqjPI9b~tKXmss@!M}d z6!-4m8*`B*Rswa!XF zrRipzl~|uxNuEn4Kc@MW%GmO||B-NGwZ2?^Rd79W8WiKp)>Fx>F2+*}E6)4C=FY8^ z`2DvYhkr%7Gvm+OV=`jm*g(FzKp&zd z3{Y8ysG?A8vbz?4b8;#E_UxMLuyywDzWUm@>V_MmbML#Oe)HR6_3)dbcgx%2J-1vN zr~mOS&Q&?SYVVZ119=)aiH7h4UZ`N4p$kE>$ED5{3WgD*`0Y3CaX!zs-j#oLdwkoi z2V(M#?~cJ+|6w$5`>Kf7zc+5)war^^$#EfJTguILRW|q#UP4%LHM%X0C-$-IKVTI` zcoG%QJ`K%?J0yufK6$9G))OXi=E>s_nfxF66g+vY3}& zVCY#s#K0j3FpB=Yg5*}OEj+QfHA7zAi2rizlHq4L?vK9WKZ#Xy;CCNDblNV>&^~iVEJtxAZC1m*eDV8V`o}W_$cjm%; zYXPme9i#~#9*0Hv`H3Wt_o{->ds0Ifr&)0@9^Je` z$%s+wFFqN6eqt$jz=WLc?roKL@E=?sfB24T;z!?nI6nAQcgHWi^;$2*+&Nl0VlVao zb8N{p84K>K3p8M88%%wc6g|}XtynvCBK|=69}`YYHll!!zV}f4)?2TM|KkmZ;s@S* zQ~bxbUhQQ?!b{5MlyCm+%aV5EO-9srUd;@nS@2|n`C zsZ2LMJzsZQ%0InkR#DF0_|}^a#P{8{KmPRX*TjE&`#~EZX3@tp>Hq5R_W0pjcE+jV zf#7|5yn2htUU0+!c-yL~I;E56zEXt4mBv~pmd-E5_Kk)3vzM2>fd-|$N72aVzV3$j z{kI*A@4fv%eD__~$1l9$VEh;HmWP@s@a2zATuOY^LGZ{^cH4U^y1pdupPrx`^XAxk zC!Sa8xK*CM&`&SX@Z;cub!z2w{P$Cr;(wf9kG243=@GjA(N|m@-*?y5@jdt47(f4p ztK)aya=@@rm6o%7?eukK$aL7osAU?fw2ZPTcs0Y*|wP#O~p$u#eQii%SBIZw&L+) zFU5UNoQpp`x#HPp3bXM%@%Do^@304Arx}H%Mqv1pqvvBwbKdETCx;o+QUbnXA>HsE zjH`01@i%9b^Nx1S?UP6Uw(Do(M}!~8U5c=nOk_3XcSO&JFMcsM{+>hI)GMn9cz($v z%CB?gIxwT~ep3dBz5S(3dbZ$|B_|koZB-wSwlp87&!38)dwSk$@E8CJGxTFx^WYPT z%%&$MeBtf6wvN7E+6smX>33Q*=~aoG1sp*wR~#6{VIx z|Ao2u=s6hxQX>@83_wCW!_0Iv}6m!a|x4Z#sgTDY(M6D<@or;o;TP)OY-G~ zHe>b8TV}ui>#@c7=*h)+a;@iwi-?3+4f$PpzUwm!Qe=5FIV5;aL9y zi?vKGPBjOn`$IY&yRaG)4Ta!xCgixKelzCetskvtMTb^21Pcdy8<>rMRLYEDV9D(d zj+~G3^6A(!5dWONBSk4v<;^{vTq%zAN~RYyC8GGdb8E4@a5}b0S%K5fyddLh%Sw8A zqZRYbZhXF;v23l%3xg}P28Kr@Tkz($j*1>HoR8t$vDnjHipr)UGwJ<>{7kG4bKaf| zj-Opvi~Rgaufp(51*>0vK@BvLel!{l?2aOB-abqOj_#z1bLwE1aP&A3WW`Tt9)AD# zZu+N@id6xhexuBr2-E-_aabvglA^2dT?OJ0qF`ah4*+2Rc<}>oE}fVtJ5)V;X5Jfq z3L*?j$KV)ZUMqxctCy;g*|s~fGuxc!<(uo#TfP{@)eBx5=k?B>IA7 zR$^n^50!;jimNEzE;#$gm8T7oxm&A&QEC zU=+&j7m-;4Jq9T5EDgY>GC+(u6oOjB>XT)s8$=p9h09h73Hb z2^R`8(W&jw_%*>(l~ThWw(%;8sxL=eIvcg-Vw8t6-tsoQh;nw@mYAxF$I8i>(BvI} zkkPZvf{B-T^rV<`ol?y8D>2MfbWKVuNLW^4ulb<9;#Z1oG@H?^Oh-w)pO~DAk|LG` z@#v23NgKM_1oD^_JcRwcd4{dTgD6xsqnVkCR!rz~BKnz9Y{@GMl3^{@FUs?`G>&K_ zPUYonF{gF<14Zhcm z&4!F}Ye|t=dK)r4Xm&Q@!a(v{lZ^9LB<6fNt(X>mJYrlf=c87sN@pdz!ee=PCDxT@ z_quJruY!4!jEo3Qo0S-r{Va#XR+3Ft(3n@r9RAkt+2m)hic)^w*1{|xN=HLb5T%4i zQvA_}@4LWx9lT|QSD0DJ^SlUEx1w0>sj07nu&V0id+mCs`xDUw(vF zUE}q6sU`&lLb&;1LX^*nrT*<*RSypV#0H7ch}-mOBc(Gdmrn2?fRCY|GWYPNZU_ej z7e;}?UDH9gFme<^sTAoXPwTwfcL~YTgeHgkPYwLxx>AI+#ZvXv4o3L*>Vz!SQC4)gl#z+L(n72;wUSy{RiFO& zu}StLNIujyc?s%4L)zJBNm#&hYHBj}>{O%%ndA_7G_=5q1_`F%5$j}3*}%kTS-*y? z-&1Zc9`@zgaVq{Lp?cfG&yRaL+7yk7%H^-xuUub()bA*C-dc-Fm3RIr8f&!7U%Wok zQk(LK>7_BExeOO?I%=p6DP~(98sS3)*Q>6z#{PzWvM z+~}9%`0=@DthY>CE57i9e(C4yL>tk8Lf?>_ix?$f;PvH6PB7F&VxpvQl8x~a+w{zo zCq~mxnh~+ND;4pS)Nv$!&<*{ouZceTWLyc1^b%es8KG~K7f0#$^NV+@JgO_=6RD)F zze+`hq;)uiv|*~(7p;^Ycr`_JczyfmS9-CAVgitj3LYQJ=Rbs1f0Qwh>3c%jY9}Nl zqq@pnDW#Z+ATbfrR7f&bK8Go)>qEkTNApC)MtXxPfZD@RUv)7I2)c6>9T)|(L-**5 za}yrm1V6flCv;u3T=&M~L1+jMZrq}P4|Ujr%)aULiRF|vA64QyeZq5aGoBpas(WmC zrXRyd8?LEOE93}H#vV_!j2~_0lXYIgw1T9=zqq37otB~q(Sqwd_bq;_8;q9v)8X7+ z*LabKy|SV$|Hy~>;FGxoRvE~ld6KV)FzO!b;EodDj}H3C>v{?MIgsbk{hYq@6an6e zaWmp0^ujPO4!h3^xx{05WZJ<4^8!4DKYY+A=8LY2srFFNVjx4nbLO-iuOMk~!!a`@ zqa~r{p&`k;91JjrvY%H5lK3$NO0+(mM6`8R- z(O7H7mgz#|ODO`_R6k`IudRy0ya$EoOGe3L!YJzxe)83y!cW~L1HpG4$Qi}~#Ux6@ zF>zlY3D)%TRm+v|rC-+*j?kFmcQ*q56(s^obV(2ZWIT-9K8vr|WPI zeyKiiR-`l)SqtFwa~s0Jua1&ZqkP_bu21+ZAT7A0GtdM6usLM_Ug85!3W7KIGWNtI z&xR23kUmN&HBO~WzOTNOzXJ~i8g}sTgN*P~D6RhdRH4QoT4Hlq$$LvkVcFQCbVKy^ ztJ#%%_mme;R8ctbO!Va4iHtKdRgJp5KdUPkJopFP@E!gdR(%&kbtD>DeiYs#hj@K8 zQ+>W;w3sK65yB%2M&<=PKch-CO?s#PL>CM(kr9uKI)Hti3o8Vya=Z& zMei!T>9wU-x{jk5^T++l$dU7xWTc7mkQvhKvS2~(fel(o`R~3DU=6TMNmTnie)JXlWL``Fmb}3X}w;z@VXCL30d0H#vU3aPyz5{y(Bcg zp9mI))5F#4hn{Utb2Xg(nIb^<7F}?{ z5p8bb5!Hv_7#lHu28)orD4df47K}0yyo0hPj9MufP#}Fsp!UJSp##1$f=-!%x$~n4k^EUIh8;JhRZbY-5ybmAPz(j zx)ufqnTV!K2%f5t8aO``!c1J0<#kxEMG%)4FC{@xMTFl=7Br&JF$$QzxE_Lmbw^mp z`A7{w6hiq8r5Y2YyzxcICXWkafsp=y7Z^tec<7Vuss>thxo)A+0lf@^`x$*7osSO` zj4|oK)>J2qhc~l8=o;`j%(b#IE=r!p$3wD{l24}qs^_*E%u2vGz@Q^3oV>XQz>_A$ zM_KWHcoCEiLxAFJlH++SMFV0i0$0HZNJ%SFXt6`x@`4bh66zZx;J(zJaVip$+Nd6i zKETk3k_Fv!3ZPHI@)WAdGfP1+G6fk3+5oeifbRJ*Y8`lWgq>@N){6S@ygKX%;IR!J z#)inP7p$vk?nVzXet3kAy_-uZ`Lsdg@fY${%P`_3r9XP$Q zT#$6}iL!%l(lIvRfS+VY_|skhJ1U{Qjfrq-$*VI22876=*GUEp2VlC%*zzgBLO%@ZHvKFHaaZ8V`|l^7HO7gni%#z8O`1Hv(e zt|f(m@CacdI)5;lc5W*npw8G+SiuZ)VqTW^^rL>MgW_@D(TMAT2jeCDpa%!-p%ZJc zsekA~|6`h^@#u4`+^LUL&)x%OtG)Yo-#P~m=)hqZJ(xc6%o-1%O=k(SPQeu;f-<70 zgv*5eC^L#Q==074h1ClA9JcEq3^WY7mhw%6k`w_Wtg%M8JKxvg$)k~Ba374TPhQ>u zeem!Y7t#-m0R@Ex7r+HR3$pqg&)@hx@){@Y(v-kz4Ab6!iMHFerB`^-uZ5Pb)hEi& z1hVu(#zVAK7dd9_1WH-nKfPaH9@R9!IA8#PMIN;zIx1CRPSW?m6P#%bJQ#qQ42d*` z$cA|Z=}Ama=cC;uoq*pM)-fG~t4=UBrlnW85}$zCPR%^0Zi?b?Xeru-*M#DNQMeY0O~TPJ-@~U|FZBPiekFg zt9gz(SVV>YO1T#=Tc4pCp?E(9tfZj0uj>ZFb~G7KB)tt zen|uQmsU%KSY2K?~N@Tfg-NWftvIF9EyfH5g| z8poc-u%Lg$`M@MWFH|eJ=(KQ5;)5Y%Y~)FY&iy-tVn8yITu=BjM+JYKc1aeHQDh>~Nr&(zOh+z# z5Blg6`DW{FudRH((-E$CdXzoElypPI4K7Z@pQ6o490-qH67;D&O;$Qz$+Y~m9OhN{czoM9l@t!FDewdM`%sBC~}IfYA1mmdwZ0g zet3)(;G0|Kwst>#wk%n=DzTzjjKDjngUYh^F5J5-bslKKs|vplz)&@v|`eaSI?Ja%u3RivMiBve%0@@EUPx~$A$zQ5l)N& z9dQP%956mniMp`tFkir{yBq84UK!Ys;mB<}bm%^2D?!FPnykd?RDYKRoCD?50M@%# zC?<7LdNW-_&}|Kvi7CQ*4ce2HSzoJr8ISkC`*l!i+adpBPqdiOW!7@kKL{~9PRQf$ zob!p2AqBvRpd(kK`ydTrfd_c~Xrq{t6cHsFDV$-1k;2FDqomwZ8+~UCzU!v%(H94} zF$N(n?ImBnpK@09K^@&sFn;l1>@+6zsjKUZXF5S&l#p?B-v%Hlm6cB4QF4q@ZXCAj z+~K~?iy}Z$PTf;ol-N;$j0H-`b%%Z!4Anz63N!ZnF~;m4f;k88_)dzAzy<&ih58<{vnLhvXCIW+k$0V2C9XD5?5{ zzdS0F_H;pWB`79F938DY|xR2OZLEyIV}t8-r;w@N4k%~^Bd^t*=d$%FyC}Z zF2sfm1RX+|X`%rIMfF6VI}_97$sq_K*!~?`{M+FR%WKisy`@Vl{$*PB6M(EYw@4xU z3%IgKy!-->j@7au(K)|-$__6)fcUO!viX9| zF$K73Wd;C)3b678kDeztryq6VO(V2dWfThqV`d(3fG3Ag>%Qyh9-D|fDWz+iLmN_n zT2<+mc*)MNYHcynf(We zWWxB{vyVwB$%yb|atb+szX(6+r*l^wM2S5=W}A~|o;00$GGOgiD_ZiJd{6Hc6s=_p zfD?K--4JZ>ZXj4u6Ko3W>#Sg4*w`R7HCc&+SMP|e+qOa(DQ-lg(Tn;@FD}k6#yV^3 zGGK`XqJ%m`aVa=893A5{>vbt}Pqg8enM*Mt1*Y(rGIENMU`H(*;pP{X9RVk;lMcyI zc9qXFA#Hv1y);>yQ)t)kv((p*wqUn&)E{z?ldGSYs07&#x|o(0MPnib2&=k%(Zo^O z;dgzll%K_aUc;T9y(e5jktysp<0X+jTQtIRS<;c@TRB8%E0_jCK>3kM1Y%~Y7TtP7 zMBXrf9T@^9doUp0yu+si&z2WUEGrF@*-3^FW2wB=cr=!9GZXdCoCMuu0904^P;Quk zk@BP-#DL?>5>ecSKBVLV+%v2i-%4ibPz%nbsVxYqx-bF5NZ+(~R3Lo7b-X-Dp*dxo z`p_B5n0ATrJt;aow6U~00XdC?hYnR|2)DEK*3A^Ui?%2|-_=1yN``k#OL&McbifEO zECx>oM17<^{}^JnTGwLB%uGyAZ;x%WyJBkVj>vD{sXDBMWAymjq7G{6@}lTd(J`rG zUV_E@1L41TS6_+dg}FFCcP#4bOOgd%nz7;EL7gYcA&YGNOCHQdHrAdDX)!pPX=vjx)z5I-5`&< zO$J4>FF0CjLm9yJSXj8=8F@l>Z^=~~lo>i9vrcUUUwvi8j>fxG#UMC_Q`@(9mv=8B zPUrw*uCF!X;>8t52}Ii7g{ii1hPe!)EYdl21HPm1JVVly93x_z(jj!)yE|n#d8rJO rq00000NkvXXu0mjf3xHfm literal 0 HcmV?d00001 diff --git a/docs/quests/images/stage_announce_clear.png b/docs/quests/images/stage_announce_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..0141faa7acfcf311986d7fc754a9798cec05fe13 GIT binary patch literal 214498 zcmV(}K+wO5P)41^@s6_eN+`00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D|D{PpK~#8NjQv@x zZp(Jxht=Av`PR4E-JgB>=JuD}z3fSolqtzHMN^Vt8+KyFN(00XPNKj-@{$INa%iElL&OP1kR^P9c-|s)FzV+>W4kguE z_04KVjT$v-)TmK4YtFfF|H1vM7PnBB;gWjQF0U>Vyt>SUtV`IJDymh-REibpr z%gc6ge%?;c&f7U{Jv%+4)D`eI8sKetaj|W$Ew}BBN!yw(waxWOTc6Z*V|Ar1uiT*g zCAb$-<7H?hPQwkYE-khNGEUAbZ)s@(oJ;M)$NTMD-+kJC^>_cc9aHC-^^jJ=B zL&Q1njL)(3PjO2U8kupXfrneSp8cwvskoTwJx|^NY5Bd`3Og3k?_4FTWS;U+7wb z-X+2-6x^P!w4eE&7upu(cMne6yN?dr2Tu;$cb*)zQ}D?1h&XNm9P-DO_oVKsZ<8u* z)F(|V@MC%5s{O#ttM=9gyusOyy``1*#_RXm8g*YNQxr*EgvGSr*s_B31y|B9@h8x7 zaB|W9=sQo^I}i8U8FF=@OaLN#hb&}F#gQV-#7Ca=g-&>|ARFyF>IwZIkw2EtxDFA5 z41^V@M6SlgUHxI+@YfLK!0QEk!7#u>fZ?PDMipT!G6*d?Ipqoj6K53OE)5y z+Z$``dq4G{-P+!0i_^7s@0B;(kNnxc*p|TmVTh`ox8 zn3??U2klj=hb;^*E2getj2P^=b%s)&R@X=`Ug$zY=aqdG8sec*LnrU`Z{BGe=&9xKk-mmbaf)>@^nir_B`BCKBfpDR?eOrVefQmmZF+v%Cgh3T zCLh70Q{eU{Ywerw?X_(FwTaf9XNx&hn1&1?7dZ#dh_Oya4m4ECIaf{HUsy(DkcJ=wRe& zl>z&I{@?zY_WOV1@3*~!eg%j0N~>*?S7RKTcFCMDrCrvnK0AlUi}2LGIXpUMzCBM} z=}e($lk;?Fs^$cTX_>=;yJ6>AAK}H7rN};=j&p804?TmIwnuM0jw#2wx=&U)M=Vo* zd3i#5&1tsBwrLZbU7UrUOXOpMoLcVm^t7EEACn#i(r-)jkrD#|Rt(~dXXXX_$7ZR= zoIj&yBfx!30)BRUN}ryZ6nwUIvbqXB>Rg#5{RZW2&pC6cd{ow*i$!&M0*$1qC#CQF z>a|rXS}dMDv08jou+YIO5Vwh7Llwn zyF_th(xKdndaRQre{XNEwOcng+B(M85^Yd0H6CnY_--Kw!0_-V8#)G<14EVsAbe4%Y_t&*b&MDUST6yoftVtG8y2wVxrS!@(>BR zQQ!t~V;o((AnZ7F)JY#!s?Jcs$IzkfjF%b)G5AAp6(&C>#w;QFhRV|t!$maZ7<c%F9 z5sGlZ1u=%%C5xvIciItn4lvkGu+&ZnpE*&+QNCQN+}h6j013#ZiY5_ zXINPNj##a$i((*PtY+Nk88eQ9=o#eloZprW}8gOX|n6K+wg@HVtRnGXLUZSVNO^I))VO$|5ji^O^A~%vJQv1llK# zvB0W-J4l@Pyqd?qeCtFwzH}51KMM|*`WBD}WTJLHV#L~yi_vpSQu%6e?G<%Izt(g_V= zA^0Nu7PL9y5DxUu=$zDqo-gG?JwiOIZ7aFw@ zX5ID&Wo%3Et52+jwxE+@WYR{{U5H3e(E-4%Grm~ywq0L zfa6d8XADzjX@Y@BnNy4|Vd)*&5*mYpaa4=k8n2eWQdf7p#!rrpqI(?kIz=(_G4LxZ zt4V_ub&ZSw0B*;(d;wwFmU){p@^NLdn)W+q4{y2#_7!zqYDm(K7{cQ1UKhIjiL(vh zPZy3Jti{KYFtJ6>v#2;eK59qDC&feif$T-bgz{PVEYCG#P5#w2Y0o&}s(f6JUK7}_ zp-2BLhS75~*Xtf_Ql?g<$w+7H*(q~HLWQ&Xv%5ZjT0UdQD2109d*TxRjT!*uOEK5v zro0t=b^bh|A9H4^5sQW(4zsKT(W06NeteIY(BOqho`ak+3SA%giFaH>aBGz;ylI6q ziei>F>h5Ly>u{u2?J#fq=FPO%9bi`&t}u@fh9iXflmP@%%+kh)-^)|i$(%yCStHE2~<>q}SdGq2yz1ZTgd z4V~5$m@zhy$WQqmFujK>jW&CQ-r>{7(jE-u6pW4a4HS{9_F9F-(2GK|=Yy*E0n~5} zWvPR<;1ln&p-NCgdu?rvfxApyOzKCh+6SDDKxB1p+kwJRpJe#i@De~otdbYMV zieKw%?bDxn(6%;FBGh>!3#^(H^x2V<7TOX68ox}qb{sUai)Py_x=c(^G-#S#k)bp? zj;k45Mjxkyk&I=-j&XB>H8N~pWMWRr$DQSr;0l+U!F46!#j#*tR-U6I)wN3&Ia1#) zOfv+pXunlDpD2-*bYgSCslh59hDMB+vvZW1V~2675xjSF*7lE1+D8xf+SmTz58HRY z{Z+>5!!vJ|5k+48Ph7V3tT!O#XKWX@`o!_Dp0_Bso|<0Xv3 z)LWD01i!v;o}Jxn2H?`9xEkL@$@8voeB50pny+60vE#xfnVY5;NZjC192wF-O&K_0 zTSy)x&C1Oqz*%~7=Vs@jeU)yEvux4~I1b&ZpI|`3FHJ}9e4S`0*x!+ia@MVJa)u!x zKJ&EO5M*RU-e{zijOl7qL)j@AON%=F*~C)bxMoh9wPE9^nTYu%fk&CG`3Y3{XIANWda{&WU*E z!H{7AuETIuE-O&{GCwUg4qlDq>Dn~Auqzm+8t$ z#Ut|7kOnfbe&fJg-wL>t}<}XXOx>|ilD!9D8;J_qI`VxVxdWqMhQt1On1cqy1^bT(06bO8eM*#5dY$iSV z*Ra7Sa>D~xhrI!zq*fTXlp`EsfLA__4wzgI_Opu8;B&PWVaz}(Wv5`MgWNUDPAFw( zyNSVD3PzoBN;!A3-0gDmccrEXxqGWbDt|WsWc5NvT#(0=Nqm*T44U@Q&H;3sW}sII zz|yhvPDm5E8r18?b>7XTB}m)psVGK`Wp|k$yzoL>MS005SKZcEEAsk_QLR!_>2^N5 zkPw%6!dpcHz~WvAVo*DH>dRF>ZHB!(%rVP$-qp9hda;orMRSt|Ab>~ z8>jwMgFl0&fAaPdWU7oy4b7@n82Ka^fd2O~R~ek;u1Ks{&-7w_^KJ1`h$_G}2BDsa zJANk46xV1#oME8qa*ZoUyyCbP)j^!N@D((@hvWGXM*x(uWW%?DH12hEb*1edp0-=- zlXkR!+&+B!+wJ%M;cvH{?>=Nmx%k~{Z-4a<+wT5hd*_|+w1?k$yS@FL58KnnyKU#- z7z6vP?HwF4M*A@?#;g-&3;_A8EX>uZlNU89nO|vHsFGeIReH%Kcy$TiR0{l?meFCm zf~(-IZ+1I1h0H8G{>LEhm1oQ$M?)q9X?|cWYXY@sOwzA04uJpkOE+_5Ba2QZ>1;Mo zM%u3Rq~(xB^ZPha*RG(CHa1o<46z2`i*1czK~)~Pc#pL)Hf7ouGL+k2;_Lny9RUci`0J(h7`%PaeVv=}qM7_YHH7&@fW zEI=eS{d+~4?NwLV42>iWl_htzmud%ES9O(9Yd@r&{N;KBof#bcE+cwMS=C6nM2~4~ zYB*UeyJg}dqRy6O8V=V+j{PW{cx{is)Fj(%Jg!s-Khmq7m;hOfP>WK~ zc}pRYc6m~ZME=Erw{58t$FvvObG&7^Xq=F_f|2f3I&713sIqID2b}Zn*qJuHcGD3S z>A*$CQ`kPSM{$Iu%l2ERZK-24eLkW6>e;|bXD*3_e_*XP!$)KC9*q$i(?3Zh9tliSvd|)sH1WoZN4`-mGN+v)0iro~-!`cb=TvwC`>H0czg(u$*iNOk-K0CHlxPqwTuS#5p^A$o@ICK=olKnhcvcr481SB`GC%aZacsZ!SxI7 zIuvHlMRke#Gw+{q*TUqWv5#z9K*LjakP{)zTc%8?2#)9Wim&!(4RDoN(xg)i3}D&d zZMqx@a7!s7XOo7KssFL@`_A({_+Z5t=+rXCTlEAs6bf@ukr3QLo$3T z1Hc7Y+9M1Li`+3{4KpprwTz3Rsft@Bp|}cFr(0*>1R>RVS3RCPHOP_Y zRoyktu?xo+Li62+`?;0xSaTQfXm`IUxu=JFZa=rfy{GN?(CaSI{r1gozuVq_=Yts5 z?(Dd*vne@pMa7O^ccDFg;^ITzWLF#?ImVYx&?z~Bt%r6}~PvnZ6K2l9d@SmW@|f`>+gbyn0#rN*^kRH`+Re^!EK*?TB_cojBAjGrZK2h_L_{na~I>9vbJ|NpLmy3`HP%s zU_lG<7%istThHh2C=xfB#>8JK+mwkkg0yrIotFlvV;C`%2@6t|uyP_Se>qm7Vzm!Q z&yf*v$3Z{iZ`|1^`7c@5WpO}zolBFFI*N}&ZUi+&<5gZLyATdflwYKctBd&5%{(rQ z$1p;Bv~Gt9ZYj%cPX>NH=voMWwXeoixNUtG6GJjLtcQ6KB# zVcx%y&BKro9xDj8B0J+WREox(>pDVYeig7>Y}@=K3A)axzm8xGr))}JbWZ3hn$jZj zvhe5XSn&vmJI&(=M`#s?Ui!lqZ5%u{N7@E$CF}GvJoUV9`8p=#Gdykrs$ruR1Lu7iVlbD6_gDCOqz1lK2T&YP5<3QN}r zmn%6LPF}fN?wyZegVy*4xy5?Fq;F=f#>9NP;^QnwKfU$2&tA)IO-T zFaeE;aIee3<5 z_M89o-FA3zgpps9wU7V==e!f|%t>T-3b(+c56Y1*D;+N@tL^ zcmoBGqV?f`8Wa|{&q}cJOIijZZFF^J^%*hj z5!xP1Z?rGoTWv3I)K1hdee*I8__!kt#!TG3;lULiLd#1xSKDpM$QO+&cNKH1-^I%@ zbyFyV!ij*ZGO~gUt*$J#FTQfC{n+R4(C?S+(cWo$@8LoF?vumz?T3fCdk~{2Ot8Yb zeI;y3hE4XbTQN>felW8H+gUN+ojDxc>>^ zfbBTacw8o_j-xV0KE4=NrTLMk%G`>(yC{`E_rtHZ?R5+^aQPs#lSbqXo)nj;TO27@ z`8mT6e9*%Kk1-rP*>4}c`(YkFblg2TzG%PwWTCx(ie4ptCbOm2iKa-whfCd6YRE-j z=p4(|<wPUk;k z2`1Sm&KJt7^v}ZNqX96Sp@;T6xhRuk9u9*IdLbW7NmpjoW4hY4Wxn*-U5ue5g9=@? zQX5a{yWpiQmg8C*5Va6gH&&~_MOuvTc^6)BrCiz@TpG(~Wuz5I_$}=om2jTP8EM)Y z2@A3-ju64<(u41p*Y9c;RaC|u&IbjYk?x&&Lz;ExtSK&P(7KM z^W_lq)ckLpJko4kHVfn}bU1IBFW&{rW?U$uVG_vh4oZAAXSz@p z2%X0XGzt{pJYdLR$}Q8OAV_c!x#Avp51E`EK0RuG^!DTIRNt6P+YMKRt}^LB&+ja= zf_lc2mh(=l(}Kw@6%1g!gJYSpo;zDvUBlPev&post6VoWH`@KXw=ptVg+qtsrLmM* z?UF%h6ik!_#Ae`T-~mHY4D!Jw^JtT2s1z*wTz)&roSYn|-F3X9wG9-){b}tW{mOeM z?WxZ_WLFy{420t*fZ+6^l8^+AXeUB>76k#}_FwKQpiI+!?iBDRT`8w=0iCbiU28Aj z-3a^JfXWd?(>~iBEc^v$r-|?YG8hLQ zTa{pa;QbAPr zEH9v`Olqmm1Zjs)tCN*>b6RK5HJ&wOqR6i zg{zvFE5FLym5#HN3k~>GWxS&KDWU>07RfFwzVO=3yj93^vKk-Rbq1`)g>p-shI>2@ z9SK9+qjl2myy2U4p6ngBiAFFoe{qQsecamqIpZUIv+m+SQ6?$HtBU(O@*>?K+qQX> z;}&Cji~ii0F1Pj7<@RL%w0*d9oI4PXOZz1yz4o>`wlu`ofOnysMJrBGIMKjLEbQuS zv`od7kC$$tUmris&e~IC;Wq@%`W0`xD?)jKF^HTuVyFp$ezF`v`D49SDX=@Y zg~^%%(Odh0t=f5^vKVfENH8d`m;D=4f(RHw0lg!cDh5Gwui3j z|MYQgn=?;<2DKmB^zz#=Z=U6(Gkk>?_Lp?#4|&UOu=>_KyzrhG_GpB=i!%6}v2OL| z%kM#p7m2xGg#=|u*Qm8#Y13uLR{p{t%S&sDCpS%|ug@`jJx5!{FEbA1s0e;pK71RE z>vhgBaIxN+7l;6O3GF#H1f2KC^Bg|uhcP)L+G5>YY~Pc%umLh ze@UAA4UrY--t>{Cn+qq{VP@s#+kavzzpDXbk(V)z@isL$5@WzGp_JoTB>2~o@0uG#jPIgcU{g7V=`{~?NZ;&1KN`U-sa7N7ddfv)m%0xV^KPEYyJ zh}Rf)uuqum-nhTf9^9U0M_8k3@33~WcQK~NVycE;r=jNDf^To;m0D(2T$Cw`RaF`My7HGu#0M`xe7~z zxDzL#9*7}LpXAExkmJ9mPZ8)9O2cqLs6Mj>uPG0i(mF-Qcnri14PPy)PV;$! zW4DxN)Z;>zeH!AnX~8!oq&k1-5h_(Libp-F z-tzW+<~(@fyx6NsKPa~`U}#@ty0^jp4)48YBgby;%;qXwcHbjQz3t|MHw#89DB$3j zPSBgt4QNi}+ucF&21?SoS~&9@iSDx2u9}=E{Dw*oL9ehbm;hgLjFD!&?n(+TOy1rB z9rCdZ3jX`tAUhVYc^U)-_)UwBgr3}hPQc+H|uZ= zOr{&byJTCqmO?4jOrmP#s!cj>vCb_#!Zgq3|;`E*B0>jhDN@3 zRAQWqgTlhg$5h6@P11Fm0_ouBKxxoc!P4er9!`8Ql* zzzx|*S-_2Hdl^l83o<0+n2QX;6)%LZcs3fT<3 z&uy&;hQ21T7~)YjhUbj*+LgR!qZ+#qt+gp(yIq;!FcWc}Q%3A=a zQ=t@xgVWv9>WnLlxzqMX-~OmQ-q}k><}D)d#oe=WuHixAE;{}NUKraN-q~K-*B2Bj zdwJyfITeAgtlQe!N=qFerBvOSV8GMnAxd2wi$~!{@uH&kd+h|+AP3em{@~EN62~bS zW0H2Nq~kiQh?!^F2?HHe@t^Ffq!W+|*>O=cb^46=G@WUpPnfI06==E16Nd8H>zC7%8BZ6#GudIxP)5A#xt-F{VZ zYg`DHp&%ULj5`C+<;Afu$`5Cm%5$+p81Gug>kN+ahK(kPF;kO(lX|6vCS)_uCgAfR z8krvL(U5lc^29|P0_Aaz$4BSw5QBLSqwMK{?<97TWL%QwxodYb$5Ea#TC4*+85=d8 z9kY3hfjW&m$45v6g(?}BGuip`Sd~ZUSbRF!A%J5+8F|C-ys=y|8sj>?8vu?EoJLy4 zE%HdAHrZHhTQA&cN33o*BabI(Peb#kTZf>q!sTy9Xf%j{7p^Od*Z zM$oJ9l4{-dWflvJ?;KGHfSkN`;Uhfp-Pc^&w?5cupSriv4w*lsGsf(k17pfag)Ro0 zaPZt~@b$2+R~cvj=PC{GRhqI;CT+mjww$Hy_4;bI^DFeWqXPo9`$~o?UNTxV_@JVA za!i206n=SR!ZWh=;pjJcGb7M;gKNPzSwy}xZqYj$VBpE33RuUH`blC(|M=_tX=hZK zy5m_pbOj-u&Rcn(zkIX2eG?kNgmIUJLv&=v8-`SvB8+V}glc}y-3ZdIF6Euaw;V&@ z)+m%87NV$cp$H1w#a=x@$12OTpS$@Y`G#@;q*2IvqdG zJ>!|Lv|CW?0-+^EJZ5^JkJBLlu^9KCmlCs&9135HLDbXY^S& zV22w746l%CIrOWYDx*R;VSOf?e_2nDl&=n~+l6zUdh|6J&&Nxc${?TfnfRVdi!b=R zx(14Erp%(p8EE}UP8^i<*63uPafOKi&OoTi9AlS>*$G<1*@p@}(?Ab$x8nWbT zV7o)+!-~hJ8oL_Vtp3P*=Y!q$bk7H&DBBI;OO$F>-L~JFxrXu|;`0GRk zN@3`kq*=#g)muXva;_{(!DkSVC!OjL+f{vKe?w2gP6T~2a6!dnAa7j^cx1=E6klc# zq%8al(+zl8Xm_TpXvq(m!#xw};QVM1l<`qi{!7X(R-Hau!e`P7)~L45;f8_OoqtN7TXQMnnu7@^hwj1p~kx zHFp%C(}_2euf`6rD#qe_da(LV*?`wWHZlw8g~SH%S^TP8oZ|IQOnfpRoqocE)jwms<^O zpnvc5vMqWHfCbw-`$z4aorCt(_nx+2{rdau_uhWkzV_kM_KgpB+q;hr+RnbOwLE9R z*4hE%(054t`a5@Oe0AIv6UZf#Tga4-KKa>~)r*GBs|kg{5;}_)x@u5w7R4-A?G=Z{ z&Gp12BI=FF#Gm7aq`W?hbm7!C{#%>6w64aBo^pgJ60*eC%iCd+mAfLjb7otB!speB zOn54O7rV-rX-;q&MUa}a`!z}^qoKk7g?xY~E5429<-1wn-JCG70xxcdxHNDn99-jO z0bJN2;59(_p(8%{<3u%G@eW_fev0{H8R;-?1981sr15742sAXTV#wQY7EzWp#GN(~YU5r=tWhd-Xorlw)8hoQm zp+nkSY|)Q^nx3BEzuJeyhaPyKTmz#{k6aOV?J6DF36=Dfk_VDT)8oq#u#)8ol22KL zI`cz!@+I=G?{l`gK;tua9j*z_Sr>@7Ys{*c$CD=yi zEZZQ@@<1a9ve~A7Z~;MF+=aHwTATsK&x#*QVPQ^P!%35@&w>LU`@E20=T2qK@lrfe zmlS_pAi0oK7ShkaO5Yjtsr02SA}8IEkolQBUypZsbdbBg`lG$wcCf$KPL2*ETbA)| z^CE_IcCPaR0Ut=DE^?i>)psYr)*Mp2wY|c`Ag6qEo+ad$v|y{m{&FLYa99jW^vc69 zs^zJ896M~j_Jpaoh9by%3!hjGwo=qpSfm+?jNCx^aw z`v^E-HLU5!?Dm1j0aUj{J6A(X?ckQzxp=}$#cyCWL_YHf?y%l_4%Dx<2L*=6EzRkr$sYN}&x z$XikY9_M!LdSzH1x!6e3fW;3Zf)Ia}FTA$ju-O#PLd<++iejNhPjYv_!wYFN$$Da} z(w4+YmwLmhalb_O(Ubl50sfosJd9C%cy!u!Nq2$j5srNpus%fQ@g2`fd-b(OfEyf`faaQO9JavS}-ouSJs`?m{;*zCqdDW;7{&kk zRqfXLnmdocl;KqE$-_%{v1r?o9F)P@+v1y>d`;BC+FCnc5?;fKe(nB7>5l;0zBCU6 zrj-kMI?@ZR0HzzOT+3yA@TZJF^6Xv2 z_xI1DhpJ|2DcmzhQr&?y2)|W_mggEiV_fJQ0bzvyjN8aa#xLz4eN4*U30S0UIkWB< z2ZuYDtNq-qa#eI#5A$GepFf6en)B8hTA7S4Dlea`@Y*W~?q#8~>HzMKkP59`p zUU%XaHi#=3@>=p(py(ZqGu!9gR2ROW%Dh5*vxu;b_|%YbL;xH<*sJ`o1_~Znm3W-M zIO(-aD#PWa2E?=DOpC}hR@&;?I&ECzx>nC(A**?Q zigw63`_p+MCkt!?{bV?&13(tpcdS5r8P)aLN1e~yo=XWYGLedTCS58A;dSby4WjtFoIcw^v?zsZCcWU0Axo#t$vc_>zF^2zHvK zOI-4&lrFUdrdIOuM>cx<>gmodi;BbW#N!S=fOyDC*#Xwgg|>#0dSS!enT7VpPZ@Yj z&gKjsAYAobwaatJcQ#j>Ut@EZ-vKPoaz`M_z!}a$)J!@1IA3m~ukLNHwl`kbXd6si z8e0ebY_tzH?O|x8Z)}$h0ZI5sRF@>uysCE#!ZUB+S4p7!{lxL-UfE9AKF^LbUY@78 za+kbFRb;fvqp9p26m*nUY_;E{WSDVr;C@r7*!I=|CAVFgUV zKqp-shRqLjXksjaqwtcBObI27I!SpK^$&jLURz_LSLVh7&GDd&rcRvYN*79$wf8>| z408GpFz(nbwlyYtA3l8V;EF|y6XqB@1sk1CJTz5G$DlK0+iH9&=bMbl?fy2T4d~UF ze*dZc|-YqbRM((7(pb{uia zygQm!1c{MF=^6_v)67{v_r$X+O&F}jHvpwo9$aDcJCEfRtH9c(L6Od=B+0^o!!-L-3iCQYGvx*HQUz!N zVR!bE25r*sd$ui=F^iYZ7@G1exa~h2MdWM7HI=)-QZ}n17a((re$u;hF5TW$$q^!%4*eP1s0y3@@E@f5*j{;Hfrpp$0xBoB@C9Iv65a+Ox_)|P&>96zh{ z@7jtAz5u1c{?tGX->esHSX6q8SnB@8P7(3Q$XoX}Gd@OrURAErBu@3R3OD?aFDMe_ zU8ARV3M)@hCs6&R%!UZD=&zj$l*SN}7b;om=^)8UXUb6%J0T_ozM@fC5E&T%<-u^Y zYe0^_;0dGf0N0xWOz+OD&@^xXZxQ>)KeFUY$RRxQB*lOsW7aPz-X|PnE@RIyS9iI6 z+k(fd9DhDI=us+ntMYDl()Htg@5*}4Jd2kK>v)(rtpwT2I0tul(P;o~7z=_8Psb#G zl+k4ph515el23V?S<*DaN5Dzdw>8u*jx=;n+=*~=YdjX%}LcX z>U83VH0Tr8d0WRQ^>vWy%jg3aZSG1ht2fK%3^nqWH^y*P_qy|JJ@P@?b;{M)S(TpY z`lQ`?a3{Pi+`x3;x`b1680Oqa0;g@NpTQqmOx#!6<;^^#XOm+N)2ujf=|8VV2uL4u zTV|u{0+xT)1-h5>!Ewi{ybGl1UUeNXhJjad>=X4+-f+djET#Bgf!{0WGNClMJYtad zAKOplTbFWIyLB3n)!&ZOQ)JiI490LLzhFF4U?b`*f95@K3GXhRdZW%ad&_HzV>kg$Pk?zqMn>ulyn*-kJ_ zH1)Eru1zbi*C%f*6k71g?YZrRAMRGlv&g%hK{@reW2KIK6rUBe#RWRKs>2abW*s}| zh0^bio7$wPbK`)}&vK$@krH7kVz%%c?5p&x$GH_*LVlJd}vX2AXLuUPudBV&Dm3CVeT2&mtkrEVuBf&wbYP zPJoP(6$NbNB_|HF%2%_xTC(yO*XBZm(nnOL^Uwf@hr&9(o{Tsoc zwJ^WJ_c_Yj`Vftp}i;$&rN;s!Nb%gHkG3T_WcjuYZu2yBEqP= zXxs4a$6i`%-*acR{a=3baa-QNxCfq|Y?t#*xguu_e&6W1*o%KAazjMe$Mn4^J z_5ItsN2fVzleW+fd2E>#`pR3LUwY$q`^hifH@7|7J#OzmIm-Fjw?EozkM_akElvj% z{UQ$E#NtkOIol`RE}KcZsJ_NH_Z4G5`@Ju;SMRTdhZ-in*2{5_#bFv^JKz;@S*fgB zM__C&NUcQ~@XLEH`1TJ@+yDJ<{a)K;f#zhpZ-jH z?S)N5)#o6VkxM2;ce81SM;gTIa~?J~x;6U85r`aGBNMW8HyHVUdQ)yRo*}jt&n>e$Zuhh5hDhU*(f}X*ltFi@ywA``AqcWuV6i z`tcwQUE8i)nXQg=XRh8Zr4gWfm3|0*ej-oAJ@4+k=20bOP-470aN&k{r%lJqmurss zaCOs1@yepj3}CHG`IA9jbT`1|Jf*Fr961J@4m7fTTg7z`mP}+$PNj}*er+dxk~RUW zp{>E4yT{P0+!q|Qz`raiHKI^djdqVE2JJY? zI{JbGI>P`>K(fDMjeUu6AV@2)lPUZFUyR%WPVso3msn0rl zz2bP&LG2lPIskA$TmM=VoX=GpH#f``eL6w>T69~D=NKaH1E0oHF%C~ptC^;7+M81P9 zVbj5r{>Np|P`Yx>ued;&=m9)KaeLONZY>m~%E21Mtzk4pvE162v<)UJcTV!!CU+lz z855D6q@kzKsx(ygIzI+6R;E7mvWJnhbK)+w=al;Z=q-!VNNCO_a@|*UP00+fbHWoZ z({K(-6}HDNe2vwEJ9le9GhigeVk-X|tR@`*LpUoO6wILv3eswD6IAf59H(Bes`K;H zw!3$bvrj91wd$9m_VzGVpFD0CetC{T=UWz5DfCmXthG0{R@-0u>TWxmY@#L{Y6xYg zTW6nHovV63#_|e=v`^{ppvatH_u=0j3lfcK4e7{=WoWZy{7{1*&-sy0-)%qg*}LKW z!`-9y-A5YI`|aBw9$=K8?DYm<0iGc^DF{GJ%G+h3|kG!1YBQQC) zocYh-R4I<;2wyT7_T_;p(aFDnP>u7V3oI4A$4~zAzxDfVXW#d;pVb=&BBuiPGU<#; zm>JU`x8JU%1Xo!#h#<(Qav6oezs8vT5+`E8fb(z2m5o>F%y8jC1ENt$J_lwsQ% z3097kB_QO*3|(5Ug9;`@-8Bg#Kv{!ii&|+AXX?$*tjf3zH4(~G-QcD}V*~3zN*bDB8-P~Ahn;3H%U1Rs! zC*KtW$EY8Qu~U7=b-n+(u6_cx+Qww!LH?O%ujLd!uQ)ulq}%+Hb7f zQ66`0-D>OWYf;*Zi{27c;JTzDTn>OJ3|E;-{TLj|5kixl1P7*0!BRj8s?Z!*)rRw| zXln(CVR(4h{Gg8yEf!7`9PqDft+Z_>z~B7{frP)B!H$%Auq$9(N}21om?(YUtaFgN zvw4F9vz|voT!gr573|RDDluP9V=%bG^{E#(+bj3h;mZ|9_j!!(odX|2JIU9~DE3QAg&PfID2 z*R9c4?rOVp{@;J~gT!rXo+3}}X=3OZif1Sz2exh~sPMyZG3GiSAH!Q5p-JUZxJwC9 zwou~>f6{@ab%j6=UAn;HN-~I8A!SssGsTH3Xk6g=v0&s0I{fPfK(frs6LrGMap~(c z>iN-2Lc*XKX+x>%BEP1IAD){NK2lF6FzSh6l0K_G316+6?3(Q4rELL`n8J_BPYMxh zOLOvvGaNL;oUr7xb=W-8ZE>Y}l?V80I{DENM?>;%ohm)dnz!BbyP+n7k-dDio z9ogXVoyz9tVN>N!=2w;7SzhY6qCeFS-+ynlJw=CnXBV9beiv36LksF7=7{W;-KY<6 zIQK}mn<+CR24|Wgo+u3Vnz3a%_#~qY3b{yG)65Jw_$g3Ep}WrA5XBF}MTdqWr12s> zGSWILH}&E`>u-!3%_SE$b!@$oJ@}!fhW4 z>%2Jn&i+>KNo)9!HbNiS@+q`JyD-_wmyjZlOAv?W%5{!en&Hq-e{iKvZG^GOXZg}BUX)DF zT~tr*)UNzqyi@^Z)=ChE;my+3%fR+K_Z1sY9&Bl0U+>jjM}^)4axz zdO}x91&_{l4Lf$&nwE_`(g}W&U*1=3vWGm{lRLZR9q1!$k@0t(rjmKq+W_)r*Jpjf zl!A&v#MdpZkHhK*d|=bTc(2d+Yh&Mzbf7)sNw2U*G94e^(|r<_jw)lZIz~k(BR97; z>&t%_$`e03i_yF?UCkLJZy)7}xfmYJ@!>DmNib=tNxqHnIX zTl!mT?ZLgx_U22s+UH-p(?0jw-S)-{H`{A3Y|%mTLxF{d@wR^mt8IYRIbRDCJe~Zk z$8fgevzVAStP8%BM=4YVNIu(i>sFphi0y5g#{gxJ@{_pA#B*DDU6(NOjdT^YN=1|^ zwCvb`H3J6k(HXxosX)#j6f`M9ErJ zECBtXCyA;_G42(dmP`MWfD4>7NzV)pf7N#a$x1%;8@K4V##tq_nwCk1I!XrsW1ddy zPrfnfUv|9DcWLj4zPM=#y8Q^0iTQE>@ctzEHQrF4KtmxwQ~eM>!$3tKHSM$f+7ph9?`QOq&?w8DFY4*G7}KR zrSj0FyqaaF+V?kLf(t-*WPOg{)!WlTC8G5FaHf(zm()cy&SazVGz&##5kDqA7o~1C zs2hSBr#xHA--03rg2adH0~?)WFeIgx?s20a*VF}!m*n<(%9Tv1|AwBoO}@9-9ndlsDzwdBQsbRTr?*-s#RnjQ5?to_qBehhyIjiF(Tci)`;6 zfXs3lqS7^Qptw+X7qd1&h=aqrY=<=aRX!W8@szR%phL?sm!lOGiM;4)te1XD+aNUa zhx0B-)%OapuTpcK1tMi^Pc*B=bn-DS8Np$%#Q>#FkGvI4^qu1c7o=wcj&G*@66z zy7Ia?{^XgI*|^f3dVfu&cRL*?EG&;tk10x?IB?eQ*G~tJ^N^i7h*;(XH#^mV&iVbF z({4V_HZk2W%U7m7*kv|@zk|7=_uu0W3P_>)2Lq|H1(!{efW5UO$Y%<#tgdR_^a!|5>A2RT-j zF`N|$1YV`4qVsmWJ126eCh;2VCkz;Gar#9%cSN$TML?D&%NXY#Sf8}*&GmNY)>gZF zbECbAA^pnT?e@9XAAl3w;LSrMOoTaGi$E<#VZ%e1m(^RYAvr#sqelm3OH<^aAPQVTKGY@>JXos3%XKMz|fY z4q!Vv1Ce}paB9-952`<;V@i89riXD}wNxL?J5$0F+eX;72VYk*x=|`})~|YR0sAo) zZzF3^rYg$$(*L?0J?FqFJ7+GL{>(#Fc*wRpXz82!??1)MYLBpkF4Dc9Fa1~)&{sWKqN*yZSEKCJ7 zB9012jvlX|Q(^H8W%4T4%+0GAH)!NdOM4${LR zI>hlCqvHIuUF0~3wP_@3>}nJehhTZ9Y-j*$bSPuWk()MS1IMEd9+{9d@y4(~@9H$T zhaR_&owrujrpz_yBt^H==-pL&%JynJD7?{Gx#I(^$Fza+8rNZrMdf2Lj{ghvlE#+1 zyf8ZHv`AyG^Hm?C1_Q)C&YS_9==1}_{nhIah=6E~z zEKqM-7Eba8msign9fk)Pd~x)-eNWuuCxfaOek3n1YFI1FwYafwP0yV@0_PfugAdiZ zw0+37_!b$@&Xpd&^6f^Y`)!0EB|lj>6E{aY$GVii)wR{OiY=u4X{bv}8PCIjEyLD% zUgI@)%rQ_;yHS1W>u_oFQC_#_7a-*+dQ1BwHdWo}_wIF!Xws!6x|2SMm*IBmpb`3K z^P_FeUw85+K$am&kR1AJ4Ol^>~+|7PX8O#}{!CtEWqrgdAQDq#9^dIg^H7 zDD7d)Z-yMUGuqDB0(kgdP<-(s&?|Ugvu(zm>=Wik0?Oz!PZ(jzk0;FBzWPDj}KVn7A-!z9^r5CxQ2f5QW~Fk<_H(M6;{im z1a95D$zXB!gwmu%VDTzC1wMQt$z5$<0`CJd?pkJobMVVR2a?A~Sm9=(0M@?A2Oh#Z z2XR+Uls$3<$STgVk#PYoJHf;JNnbrDX9O~@hip_BZAfa5W!7=cNKjEtAdNR`$Pt9^Y?cZ#&1^@6NIt*1A0k2$&NAd=V zxEeutK*wmR20K$bPVg49dc5Pc95TRfc9v6@UPHW$aTRIdz-Mc=Xsilwfgir&D&E8c zTC}$-cWwxRBUgdNm)q7$(GRMK;x@(%qBeG)1FlOQ;1Yi(OTFopZJ&n!(ndh`ndxhx zO2JbHipPl{w_+*m7)w|jkTTx3k)1f3ZaN0=)e_L!Nh{_9N4<_&C;EJ5SyJ$(Vb8ND z1-={3R~o$xcKbJMSq6vc_#MWbU-V2m`Hbd*ht@@CJ)_gqLFrw>N zDF(N=q&;^|4Fl-#N!<#k7QN7RhQ}YbM3^UPaC<~ z&P>c)p9B3EB@{vAD^>mSF$V*3w1aErISdXLC5t%+tRaM6&1Q>7W(dQRyiXQgEB^-W z@Yjh#2?WLsiWqIcA%UYEr_2QcrQf@^ zOkH~bJe7O}kCwtCjj~W`n=`QH9JqyvCtSOv;sVmSB8Fu3yJN9yrEEgE3n{N`ub**` zo=HP=N;!^lelbzr*E~Rbna9UGRIe5U2kDmg`JyERUpYP(~sVyIsq+11F# zw((eLH?B1<=Xb4-3Fn>Qz{{#S{^`+Ky$?7tj^K5Th3-Q7ppimmm?mHN%VJG$9AER` zwG+I2^}A*2cyJww%~8~hBXy8+HD5kGpqkC%Lc3NL>h17c^21oCP3Q;*GF}GuuxEjC z3(J)Q0+E$Nd(ZBO@4+YUzb)4h|nM_5DKGr7Kl_3=K!#lVmmx3|~Y_kH$t z>aSH`9vEVR@_erY#A|kAQ4-Rh)iE@>TP+u_{{}xbU>v|bjjQLPeeh2fE>G6NfBVKy zQ!BVR-_-j=<|dzdC`vuGh%nEciR=vYiNIqi!f7xc9{N@eSD&RgTs1qOBjlF#uLAw5 z0xRM4R#B8f6vJ~)fuVMlyh<#}9%71Ce#K|8J_t-7-Uh7EZeLMnJ<>1~k$q^9Dph?h z2Yi=ps(juBuwy3Qg^ep-m3QSRFEwD3(uqvGKyV^1zK*MMXtGIJxNxlhyo(;GQiLv2&1&p-9#lLk?X(=x2 z)k6f?wJ;$)iio5%&azru9Z&Ga0_(mTy*3~#U&2NM-@O*)%u}AL?UtV42S*m!!DD`T zqp0|_uXcwzjKMfnBREH;y5zwpuJOcDO%(?%V+o#IP$YDRZv?s_Tl|8)6rzB*85{TiSdOcO8SvG1R%qPnDi`F@Pv1Eso!8K3JyE_ZF9{(t{RTQ0*0F z9C)PGQm?uD{N-*9G-ymxYw0@8Y<)3+>LY6SjuA?-;J5E6__h zUyG*9m}lv83==1P$D1D*vVADm^h4+XtrNJrgZ7nh!^u~5c#d;$6_?yC2n;XEdxvi9 z-m9x)aQ3<>lyqgmIevL*6}hTiZRwSid3^u@q2aoia0$ixFtO#d1&n96SQy(=Ah=Pl z{a19TW6jEZ1J`Yn{T-bcXS*>x!Mn1`{Fd@bO?~q6^!OB%bO8O9%`n>S;z!==f{(t* z6(t$H*3rlA!N99sOy&_^gT~eItUF_|I_qP~xM0$FhjP+~t>mC5QyQvR7GQ4Nm9w%z zi#z{isO#iSc+tzL|I8^nN?XCZ1U?jZ5g9o;(NLAQmbEPjP!77hQjd4P(?oGoc2sX% z>|o9{mBN$Q8|Wl=lQk$Oew;-fP&PW2_GbJ#j+I6F%J}Svh?|GVaXtT4#;d#4?%1E5 z9szTj8hvO#uh5g%^3KH3aGeD5A3Mc`2JMgB7vXUR=eg=Q^)JS=1~cP8yxDyYUx@%H zef3&jk{J`@sEG;nW;4MnUd~!Nc4^=bS<|@G_^F-C@?`)kY#K@$T{13kR35`Qgp{!A z1D7yq;2O7P3?~ivtTrSbSBkkBeC;|LfR8W|Ovm;C-+LQJ8n~;^3!kpw_56~Y-3$Ur z<7d3eN1FVil&@#=NP^BYXbz?rYXEB)93C9Dy}h0G^vR>P3*5r#KDzFry}WVNUf5{u zCVJ|@=8g8oouzhvW2x-}|Di_n@p%m8JO!?i?JXa|XJ=P+{Nu!rN?gX!_FdAx67KbT z+j*VZ%}tD66$1k_yB=Qk;X7Qx7av1*+E$KG*ax4D0bu*>%k8br+#(d#!Cs$R?FO-< z%7VGVYU;AT7#bZ|JzKhx2GxykM4o_+Ap z1xoL*%I{8Dft1MMB=T3i>D8L_PNrk^T|6O=YZN!=+>zFhmMe9(*fLH+AwTdsgToiP zWR@dXF%%1h!WVv&>9tk+q9(T7DNPgTE9np}-gb03G`pj&v0$z{5f04CQlTHB7{}xo zMJNk&ZzIMcsWa> zPpa)b?)Hskg)^R?YbUzkwqJzl^;G#Djt42;F3AFxu&|<2uJQ+15AkPCuaj$WB!pF3 z&=yYPs!t_do|~52w*^*t$_$a5dDgoNiyn^g+x3}n?&Rh?vqv$UJEaks^=xyDT+2DO z@EK!_%bKY1?$Wt{^EGOIW*OK;jA@N)cdNx|ocSZW&hBt)JlFYX;SJYS-o(&?){Hx< zmv`=TC7rtMr=0zS<``(j8A!GS{K{k^oV*(x#ZlgXZ=$e$e$jK4`9kASHLS$fYqGja zp~8Lb3P&6QB-^v7wQq?#N*u4Alb(oIoO4CuBAcZ3M9I>Kb`!$QhtSc}Kgmg<7?N+b zBm{I0KD@}dQ#Y9)&(uNmmxgbQJ0zF-Y`^`Od6PaBc4f`yCQ6ocenvS)9D3O2Xp{pq zmpS~wV~DV<6S*l@`C^_s(-^(oaB-34+1AyG&+PcHIK~`?iaUfcDxxH+KKrloPtmpL z5*t|qHXpk2Lr*?WQ$wTkA1B9kwllAa1yAM@5%<0wxHRIOSFFF%X?SFcvZHeZ#XH|f zUoD#4N!MUnjB)04GF*BN+$l|!&M(QfY(mgmp3nr%wlNNYh*1p+S*d_I=h4#gj3JMd!yrRpGuh*00T5L#%eG4h}2fSUGV92_tA=6h++K+*wO!R%)FX=^7>Gslq z*@RLqMqd}(08`$CtGLec9?7&`V&zFwu3`&9zecYM82Raun+Du(_ zB*h(YZ2{Y^uJ1VT+A$e>^4dh&>gLjbcOj~twgYU3P7(^kvu@#E`BOiQ&V|h|-G-_V z1kBC|>7FHtC2x9fh^vq6l=w%Q51HA?dDx-ni$n&vTPf3hm!rFu?|<-7RwMRh^_@*C zK~7^}cy->h1e>RSHf^wl@-i`KBG$#UE*u1N4*FA{ely1w;9^>$c zfq4ff7wsD#?lXzpU|`lQDPdF}3b|iod;h~nF{U0p-3z_$XpGySF&-5dnyly7ADl`I zaDE)H4$SG=dVAy5SEIOe4yg<);DpTp$`ziFY&=GVynq(;diYom5;!PIY1(dA=q|i= z_x9;ilmqoCH{#ga-D?jYKaMPTd+6$Dk5ymcQ#OC-g|+tD)=K+ZU*B!}EHqX&Z(K}BrLs>9l+%ZB|*0<>FY_J2-2<{F~oyzy0-x zc{@UVXvBpciphZkQCX~lx5^ip8ieCBZ zV@$co*etp`Sh?Ai^m%!tt>K|z^F{&E$JJoTtM!rJ;d`mzj^S%l%qB@ieROn~F*@q9 zsr6I%Y9U}5b%~WE8dr51s&@1W1s*Bos@j>e{wi1e_vnT|VI@Y7JmiPmAdA7RB2F26 z@th!ta-0HseTQ%G;&b#vkSad&>2iVrB9i{Ie$#9-Y>;<$vwU>Xd1rg0{p=6C++MrC zNj~t*bL&gWsW{Y)lnpM&Ipy&AfDK+ldprQB4p5}zvUlL5#9gTU(~I`bhkNaJzVWzy zH8EB+>!7(?w%_i_vR$*V^m%HruQBHuCB535$u}`r4!R zYya#aW8L?tUj~14U)Rf)Tc7&cy9qiOzqK0O`<+Jz?YobT+c9*XQ^t0dxXEwI68H6Y zQ~KsV_}*LX?cLM%tM4DR6WV%0yG|`I-E7B4`x#%})|ck`zCiK08mAD67xq%dyL!V= z^7G8*x${=K)INuQ?(9w6(UclN_=~)h#Hj0>Z=z>mUY?gg7g=X^8mY%~o#N3*@Ez3Z z*D`F-d&q>yHS*PG(PCX^XxLaL8(iS^VX~@=cF<>`9bF!y1sr1|td8+i84XEkbKHs3 z&8L-BH>cZR8S3X(B#fQA~c_9tH+UTkI^!x;43?LV# zI{7Ic=_*4I`5|2cFjF99_#h3|W`#=5jAB+W?*`LrMJUrsX#AIvma~M4%|V!3I{rzZ znfiVaZd`3poN)7X&c$cHBbz*%vAH&FJNpMQ&a&`haBKKwmE<3!=Bdo|xHWRY%WiLd zsgvvgjUt;P&a6^kzI@8LX_XBn#;&o07EXn9K_?Hq4Tuszaeegg zqjqq3NSUiPSqJWLpK6&Ls6yWU(1U4vV{3vTy@Mgm1hKi*E}==@NT7PhI~)r8O=i8< znPgvP!nKbc?i{tp7}6e}@hHtxe3Y9sz`HV}eU;Wy`>8Ly(0=su_u3jt^xcR1?b{#h zwzog>V=Vjaeeih4U>MTY>#uc$r>2lVko4k@^57)y15uxO>1O*gKky)Zsw4>Iu9!*# z+dsyOtn^JUbhX*w?6Tq{1AuWSoTFnVM#hJ8!h6v6|N7hi1pd{>Sn_&5#}PgX9sHR* z`?$+^44w9u`AIHkBkJcFH$R5$&+E86D15`&Z}OnrHLieAZXTC_(lLV@mmy!cN^%5P zkL8mPH^Z42E|~>4Q`g`xB@W@mf}~y7-DxsxIsB+S;0pdI82p_gX;fK&*Hn3}8$vj}w zzTOza&c{Km;NbE>AmrbDt)KkjOYM8#xD#5PT-K*d#um4kv@-@W6KmL{agAY=2V4R7 zL2+BoaH1s0OTvD* z$g%XwZ5Gvx?>8`_{fLa`qyOOBPuf5H!w=hsyT|Pax_qe1omBOYW!<#dKwoaIt+adF z({=|#+OhZE<0Fh|Kd_<^COwXss`-X<8Fj~yW^DeYFWzq7-aTo*_TFJ0#PqorszDKJ zNFT%isfN(zw8QRlAQQF^Ly319Pov}QkDBn6&DM?%j+?JdwVeH&H%0L8n-I)*<3R&U z<0>HVBRk!};dloe8MJ?FcOH^rf_F#UGty@et8pw(<;OU~977z0&fV6h9Q*8x%0*es zLt^%`b>gWwH0G8!F2)PASfBW;KYbYbst-h5#!KlW@s`0teH3uM&BEL9M4~&w&i(F4 z=GX&#R0=eTeC@5YYYrN za|hN2#-QZtPO7hIjgbeKdN;NUIK4Y$Gi;|v4b<&o%G{?Bs#h;Sn?>v^=UP%}2XX7R z5y=cY5*M$090p}@;9Pu^-bu-}x-tRIG3G04Yx7IvdEN~JIW<^Mp-LOAWY}TyQx`cS zt6qUu&JD0cpg&IHyg*ARv;awYPCyv5!5^6dqU6*5C9k(>=4_ADQO07n$L0>h+T31i z@x$W%daWtcHF~qShED56N4bEE@&c)J&ahoO%as#{k`_da9~UI*r^2sHx-%}#<~asj zP|hzre6Z5?dfr=Sm2NP*lOBDBTU?#4enn&&xmo4nb6DKL*01g+Yk(P7SPhT&9cwhz z_BtyC;Jj^-yk4fh+ysnQ225Thl5{+{18W&?K|OP=Gh*Vc{1JcF`%dM+bh^fSln;Hm zdh#8J2oM5LTm7}FjzZw-YEQw_3lrm4p2Hl6kR75fu){Tf6%BVgqckj|V1UbTb*BT) z+ZV;pJmJbf*7!a2;5~GnK(918Kn{w5Rxe(tUsQF=aGl*O!s=;F-;lvvfI!1b&xA3NxMN%IjRlGA0X|1!@GeX~^U_v( z_3nD~#U2ZjC;KPa$$oNh2H)*>S=9+k&DSh2QYkw5<_-e5sbg(*rTy?{?zG#RzUC;B z0w0}>Dj%?N6gMpsQtCj7k)QLk7}Ax(WDKveiv%rCFdlyQ>mOw&S{}O7C!!&vX(zDh zNpbRD29a>~mpH{-Eg5~7{;bTT4MRL&vqqc4FR&{6dQeLD3{a=LT;awu7H0Sjt1^3= zbKBRkGH3``T}K(inS?8uqN8kDnyck!IBXP@LvZ&p2A#1;+^-?hYE0!{K%t}?MF)C8D&zFHy@NsQd*3x+vO6w*2P{t6_3n|ncw=?Bt)m!y_}33J?NY{PR!UyH zrbz0wXRpvjipUr5uC_gN$Onh$XWIjyy6BQ|S#O1qZ^cvT)u0n+`iwkrWEUBon;k9s z(RNy=JeEJ+q0`9Ia8ci8uBAPuWihB6h#zF*7>V(;lfKaK%q9qc9t#oA(9`J?VBOS_ zPTLoH9V30*&d%*pcaHZdM?hvn3E=b%iRK%QVc)~HZRoD(;w%>2h{7Qt&U@m>*;evq zyRN{;&?z!QbNYhzI(L|U14G0H+BRXz$kibCE?Y6hzLH*@I6dYv47b7~&)}u!&0So8 zC=bBN*Yv-2L%X=`GZ9fnskc&7)UU!tHXMWj@vaLC7wINmMl|@uYbgdoUhRf)EWR?j zq^tN`b@3-++LAd5UJ7eFgZj~N4eQP|V-!i7k;C6gzbETR5-B4icFulf|J_Z?nwgO8b zEpxq!pp?NZPK6qZZnr0Hn5(xc27H79!@!l6@EL^oNdGe)V(|_Lkj$kUe%1>kzMOS| zZt+D?k*-l4Biee&lZLE@7~pjfzOI=>(h_AHz8-dW7y97n1cUDwgZijF!JvM! zx8HX6_c5M#+oLB>+rhy;?Q-I(*U#i#it+N6{=<7qNTKtS;2OSJEYL{hP#5urkSGXf zP~qpcX&P^SSWv?3%xXdYGZ^@@O*#6b@jyCFkMPxw##PQo=hhVXyglh)Fuk1N`ZeDo z)<-D66^p7%(3peNI$(JgFc}C|4FYitC(Q$!ZS*)rzCH-8bVt>Rw@E5SQ!;oh(90Q~ zp&7Wt3^7)+G|RKxv5*i``IiSaS6V|$Z<1`%RgISAS1YUIufFJE0uOim>Jqf^r<6#0 zl&{VT2%D=xnX#M2qGO8w3Zm-&p*V%FveKyxo20A)$&V9H#spr%GorS7U%1eInR#W{?eieG!Jn z`4$SUxmC{OzO;&Nz<~7497`Bri!3T{03+NIY3d&h*W?>#&eo7vTES0{?KR+5d)dHP zf5F`#-}BN3Zm&|mE${(Hq@-Q4AdfsjpMR2n4h-f`WFrqCl3#LcKwT(gIoVJXl#6=b zic^z+c+j2EPjbE@bl|Dl;S&kz*WO_%%g-vj$rocm-omgtL+C|%NCM=zqM zkR=VO%ug8A%iySUtt_t8jg-lq6lIpu+IA6L`6%)7*Tfjn#dH6}kzHHx*O3Tmw=HMD zTv2DqW8L|^2u|lyT|QS2jIw3jwQ%h9c^(-Yjz#(8{3{;YRdq-!xTS--^I$ICIayWDB=bVsZC(hx@A9>d8mml3E3%#8bW$#=&2HG=sfXFn%DO+HXgR(=! znx+HMSwJ(oi&}?>mo%9*CZ!&9n512>=f2 z3tZ(3B7E-NYe&Ery$>|TSf#RzA;*BSWE`2Lp|S5`%fW|Z#^~u$?GQ&E!UV25>wp*1 zq0O~C@!KF%l-XoS0ihBHwlHe@zox8pnqZjyNp-C19o)%BDAOJn3NS_zR2pz1x7FxWy~@yYW>E~tfCEn@dxAnb#2;H0;oRDE z_hiy;alN&*(r&ITwVNBuZIj7xYoqdR-&}1sx9cm1+v}6Ixwf3I9B!|ZhF@P>$vI;c ze~w|KQ%fP}lnux3$uY+Dp|93EZjUjp-+OWtBemL63Q7;d(|9}CCc8s$b9ytOr4&WRjH+CZPcGUY zedqDaxRBn!*&qsvGh;+!P&r`2QMuG^mRNda2~(Z|_C=zUw*}P-WvuG8AL0cQzL(L@ zXrM?Zp(b!(?7QqtW}KT=zLY%qfRC|Sf8@{Vb62z*^>bdA2~M6U+lDONtbA(F zV%TO^2>r8&VVnmCEf4NIKq$U=##Dd9hK5AW&M$M-SYE~F0Yjuse0mLJ^72|dbi8oG z&@LSY9u2x)<`CIWX>c!Dzx^0`%l<9}s3wrLTciUcp#!CXwbs z(&s`n_U~>^vRHLd9o7cQtD%ae|G18B2%m$laAvMa9~PXnXhi$|cNePBSvd92yt7=$ zBtIZT)Cs83)GvJ1ISO{5{_8pLAuH)XoWIJA8!cXy1r4d_S^$TkmGQwfiLC*;&aB6vIg$F^&?H740(Ao z4gV%);Y3iwCVHJTWig}^RWC$`i8Fj9z4LeM$_p5Sny~#lj*^_7cqhH)N2}JD&bejE zA0O;z4rNeLPTeULv_UbDSny0eO68nRV)eT`@wkTl6Y#+1j5PSqurbna>hcI7dR0$j zqHiofxomKTZurHYBQ}eQqzL1kQ+M0l%#wEbSh}{dZGYzpKk+m$@epQT!&hBM&Lf^qu(8kjI@f7Oc9_J& z0tlrJorKJJ2~#Mdu+oVYp7%+AE?xs$4}oS`eR$DNdHAaNto#_^#_?s)%H=R0%?>MzXY5KN+ZaK=GdY zjFPn_UIP-Ul3$gSi&}M{@+V&VO2vEKV!W~`eFTx>Ad$42EOxGq>0s`t#l|m@&|sMQ zBX903S4v>yjdcYc&+y|{2-9kkU8Ib1YFMtR804$Y_lPSWk`>j-e^G6|v~%fc_Fcv- zvHYge&hze63S;+Jmwr{LMVZN`qP;MZ7ruz2@CcuUb)P_Dyn6ODaobdU;J@0QW;ppd z218KlQZcDOOHRaGb)Z?|YnqTlvzA5`DPM(St_`M>rms@`r9aa9iqbl$@zSgMg&W3NOpqPwK zxhnffPV~C!WdqPeN2@C;UIiz#Hy^&DZn0La-37GZLwBz`&nFP~ef)FK-3e5e9_#cD zH=`(hoXMJOWnY@5Pw?EWg=P&I2Q)jd3~g%js##>BPY_I z9aSc5b&1*UCh5+fp zE8pru&&HEPV)b7zkZ)a<(_3!jjU8;mG3=Ndq$zh=G*q(72))ROD$)f}^aYjV4k7S2 zFs7AxQ92HMC8P#Z`kXTAtyz0Xu)o4_;mH>!ua}byiN_nw4rNdCTzcJo^elVrimNxz zF~~eNp-vk1JS6A?z&SST1|j|IW~5(Uc4vEU=V^}B z?(gli{oS2*$Rg>4xL-0ncOgYXeG8IDI+iiYvlx;{t5*$BfA1NboX~sW+WSBX<2<*Q zYxgM$^6L+(;AHB&4v5UflV_MEdU(^g%nWKGDPl*&6SfE*dBWG^ z=eZ-_o=~@;^o+P%RZbuPj|PSlVvHP>7BDwZdQM_D82r9C+|~RojO8hY@YJWqF?#RZ zUT=3u+uoYybKqOs)3&*}-X_yYn=;9GMtO>nwuZu6)rh8?ehp)Jo%D_Mm0TxIl+^1R zA=ZK0!U(^)>8p~Kk?nf8P)0_X5f6JuW5X!<;NV?ow{PCc>P+RJK$cRr52}D;!Q0uM zzdb?dJV@^?pqxD)y9)}g9Y+yMSze`CMnfryOcLGsWhc)^d%IjY+deBmL{R3;lXXo=x09 zS`MzJZBdNGP3tU8L2;Vs)w+H4*R75E*nwH%u1N-0EGtJqQbQNf%(p>oP4*?=v|YT? zsPm=zxr;-e4P&*4k_TQ&<~4v+GzcX)eXpiq@P~w&=zs}s z(xp*3#5-=Q-Ign#vU-@W%-jAPXHgacpJfevtnnU(3NMU^65-!^EUQ;fF#f$Or7Su@ zYTR1f=c97=*)T?dcQoym^gD1Gx4v^Y$216Aeil|0VJt0SIM*>U4P;<3#sddTjBtDn z5X)<*lMc-C6u1fD;Dv5?mbrR-;}BW$fPlNua4$x&$2&BlhLN-2?qFskc?sXCB5%(z zY(IcbHeC6omo7`jov>H?Zenz=e`XfPd%D00#>v`Ql!);htq8a@=906|^>xEwEk24W zf57S@Jjs+f}rc5UPdXeqR?#A@D{G>zIGZzo|lx>-r~*tfOB$}7e4b78JF%Z zvsaE6a5|s(etn6xEy`^K7^pfQFTd2keQZNa&iJCIs)77dk9FntGHI5OrWostE00BJ z)Vi3lt)u-G_l~OVb7Q7%ZxbQG1(*-K8I5kas3TztJ4qdfR6;BCXIvPkE?eTC<&wsq zS81=i*R{dog3;ac+*yGRje50!a%P$p*^kO;jAW7EyfdRAu7Tz}5EvJul!+`x29aSV zK0sPcvu0Chl$zvfM+P6uV} zgi5`#B7dy0^sjs?yQccnj#FOT!PS8Fozl6Z0v#?0uE$J`W$79zy71IGXsmguY48ST zOP_UBI)<&XF2Edo8l1|j&u31!kHtxlKIKhbAwLShOg6moFI~CA;ZAjYy>mM7Agaw? z`3H|^`x+2O&RF33VWHDg^6~j9CI0AWAESB~1ADg} z?(dRz7<|%PbDTAM?w>KAh84}-*}CDdDL73&MEeEmIxSEkOq;!)Wp zDs%DRCL{yZWGV~NlCGC=&K4b)e+v9Hf+|`aP`)~#uawgX6(8qdR5A{H4eaa;A)hKl z1xtV3Ip;CXREADKo@w^s!Gj~;9&z0E4o~usjBnf6A%2WOdUSHpP7yMn3io!}B$MTG zDODyRg~#1+Kgcqj`Wa1MD~EB7k+!8rq2JnE#ZaH7IUvE;1fVY`|7ZjJU`VCCH*ejH zA)P0R9UvKqRl=w1BWMViXURQ^;e(F~Ts^p1ub8P7AZ5^IIe!KHeo4#sJ8CQ+&_-`p zRqr);af%^*LOCTu))$@S3P!~gDrw9b{oqF({@nLGh*1jq>gzzOD8(!+-Of*Tnh1~b zUi-g^#UJzhCAIW>;oehpKNP;NARL9b-l(Y01~vAbr^#Yh=?M!WiS~#MB9H zjUc?bb{ysKct|z?G?-VxqhX!1(3J7&oN66hV^H^^G!X_C0ooTd$66$$DDBGu0wUSqvX&D)3P}ujjl;A zi_=8hJWj)6v35nmF$O8{!F~-$@^mv;aq*-6C}W|*0mxNeGAE!rhtIAUlS0X;yMFb0 zwVYvAHe=vb4c-pcpvl-|A)Mm?vx@0YWq`i*Y_a`aXGQ5#X~Me@@m>&?T-M?;t6%tEX=7S}0@1vg#AH zg5%G6ts~Qqx#=f01bNWu`_7fqS|mD*>(5(kJ5;iydKIAi*#+U!apuYm;4x-H|_ z%AA}87_SggzkN0{uKY8LpCKPbyP3%)UJre0X!}sSuTd-*Tsnp*^`xJ`9XV2-8Bq4+ zCpyrJx>Qwd7!@s^orPoIksNoxT_koQ)7AwBtjF^5sc^}&DYPeLh}-WbO0yeXgH|sd zYj9IAi?aaYD67mUbIReU%XE!+LOm(mkjnlCJ~MF6;W7C6OJ0W`Za$p)y6*}7S}?RS z*H9cp1fKyN5!_sGIBBqsQIAd;$fjJBsA;y1RN@(R`i|p>4dr5J+CeFaBWyY2b;dBrD}lPwZ$uN+OerEF*k$>=`X! zDW?+njX(Yfqun=FxNGdHnL?=(`XE+?zl!1g#%r&&t*y-nkzXS6eWn`9;?`)%ge5&a zUW9@Ng6f@?@FILI-pT{JqRy<{$jY<{x4pfC2%r;m-6Hi}qmy=Z zd_ecQ^2R7-(0}>ukF0QGhp?1WqB&NnkozX+QVnm)oaa+@d|M zPOC4n8$_8E4JQJM(S$y6H_0~)cx-4q0k6z(X@~TS^p`=`+IQZ2+J5PmzuKPevm!oG z0nq>OneM87(usHw$#obG%8dO~0Qia!Dg`;hy7YgPM550)A*{-_c$E*Pg$4p4gEq(b zBsg=yMM|%CmOBrmaN`E3u6SJar4#BWSLIOPaXUJB)@@MvbZ}-CHAUFcUm=mcz1B6j zRI2W%a_SD{)?!Zn%CdcFzE=$#F)8=9Ndq;;wgv}YLqwSi;y&@mn9xuGM+{~9UE?8o z$6(3=K6oU2CvkOc+HT!^p}qFX>-prf1W)o>3#`k=k{;oEKRV29=)U)IAQyak>g{_OoWU0l&(jf}2=cM<(|b%ER?bPLF? z?|AnWc-hT54Wm?4Of2;K`9j9Y3WU~bZ;{o7Qgw`58B`Qjd$B_UITfa z@#iFGqaAnNxqJ1_dVAsadb_u^nrBl!e0tQr{@z}D|0y~Tnl)^)TL~%JX=pVV@oKL$UMIA)q>UrJ6@dMyptks zW!r+GB5ligAm+_R-;r-86hMtLuF`ROqS2y0qTR|NOqFJNuTJyx-MLeePEl7oPYqle zN`+H&%S#iH=GA1jYxIxvr-oEI)}O%&c@4kBop(Y*Bc+m+^SCKcnoK+@K`9+BISRH; z98{R22)0$Q&{-ha2%sX{<^yhJs1={m2gZ84oOjE9A}_U@uW;4|pvPlOcQI$5$OkvM z>a~8tIZlI+ALpI250ZvUABvw#*(wBPJmo^zsB^fu;$|?{VP&JDoloW`T)6xTtnvBs z|2(H$TP`wT`JVoqn_&u0+fdQxojBxEK{^(V_SKfGwDg-5*vI^h z@hu*iy12Bm^p&9~>Xmf$wu@|scR#Fjt@^yut$T^fQ2^n+`k>kH?rbIvSj!DgC=Aq&-jIuVK+i?tlECf^raIba_R8R;L@!W28CmU`2xmRwt?d?@yR1yHl zY>N^1Hl4!d1nMqE)w9&rm@HOTS1>AB!C>UAPM0$gOu0@y?vZoRD|y%J8aT|`TwkT^ zG7|u+O&aq4b>>$+1GAm@;*E|YdZ(0?eXqOd&K0wbBJs*35d^u5nW+FQnj@kly(P< z{16{aPFr0FeCf^GIq&YE6V@SbIFkjqyjot}9HUx4{>(_3dUtJJA4r-!{rbB*?W^B@ z$k@?1cLG##Qthjc16Bd$xX_(Px!G5(_^4G==BQrSwr#=BR4on6x$1%h! zh-FOg#R{tP11|;gH+=$~#-)=Kt~Dv!C*hApY-kJ!;Yi?W^W3S70j^wQ*GQ6vhG;Z` zawdA>xD!UbLb>75C%F}`UXg|s3`oZM)~)UK!V539S6_Xrec?0T*M8u8ez5((XMebT z;k7TcPrdvZ49icqFW&mz_R8Al+DrHEx6j@Be0zBfPx$p0-)L{X_^I~#{WsbRFTdDs zZQiMIo;FE8@H4y}OH% z%NVDaMqhPN8Pc}Ng@W&r_Sq75u#cE;9`7+9V4P(~FaNZLI+oqZ<@#^^;+NX-hfmv! zQ*`R#d3!$#cy|C@I{*#L*ddR#m$qtbQ7?0bV{+nMMH;8b3X+{%Tg89$U-}u-2SZn* z2^68(4!K$^-5L3@NQ#yY$j+>NqtPTDWlC7&O+G1W>h>%Ss55WINMDwYUS?cefJ+*z zTYX>S7TPH%Zs(2Mp>=*!PQ{n<(gmz%_;JxZ83#q9y23g#7PyMnBS_I_FhRapk8_@5 zCvz)2*WiYGgbR=T;jiVSlhUChbtGmT7%0}^ZXgv|MT$jM(J=Id{EChXx%8hpv)dvj zDj)WbCa1<`c3rI>_^OZbOj?D(Mc=p+9Yv&!+fK)7+RZitx8WbKz&5G}wQ!{jkZr&Po-LhUPrKj1%Q(;H-rLj(8R%$_rW1mne?K z$?ED?#)K<#W`3l_)3o6onDc%vEpIM}r&6xj`TQ6@;Z#z+EIzAFd?`x9RaWKY7Jc%2 znYyaf%qyWNIF)!Q4)a~9gCcy%>s1^WFeiNBngK#YIgQ}-ybK;?7he-Ef)*p%hyLpN z#)FNvf$~;DgHDCSWEsOYgMzXssti8xLK(CG8^uby0H8?dP)T^}Eoa!MIJZfq4*<`( ztzjJdz@l{g(K}DGIPo}1EnsSqLW8N3&V-n|ckkvSEDnBeb>wyrxoLM-rYdInQGx}H zgoPofo=(^TDs%fIdBEzREQ8p-QVA`)Sirl8@$6Bagr_gA@zP8d679Ucsc#)Cz`wM) z-0rO{x1ayoVojUAB^d1JXyYuqg zL7#^`pjBh{EqA#vq0dTwfCz)hF+Bt5!#K%+nDox)!;AAR(os>rb@n?8drA%MB zBj;;{zUS5LEPNcK0!o1sWoWjMl$Gc1K;?WOyb@F7OE{T9JhzCuA$@p!*8cH7`ECpd zm1G&xQqYlAA&d1e0q~1AJ6Sznwx=0^GoQHOIO^_^^iTa)ekG?3J&C5fr@`HAB>3-Y7 zFE6bl)5~pR={DhwDDw5CTWxFce%oHW*KSW9v{yDTxL^Eod-*dDa;wvGyeH>}PNb+-*PT|Mi=!;v3kL%p~Joz79_%LJjzxK(bvs!yG) z;WIu9Nc4+o!o*NbKX{eSbXZ_XNjdqS@g(hBhS6L&OGmovE$TsM<|^xh2FrR)*|f_} zQ;%DYWJ$6dH1*RF#Hk%Q51r!5baBbtW?I23GQ!3CN7-wmhF|HSymkbv?#^}+zbIX4 zIR(c%r-8G-y33hi^3xvLXgIn7Aa!?+KT)aYYn`*gYw@V)aUO4qu?_v|W9QF$cef27 zADs-A^jGG^QMXs{UducDhkx?cK9?7H0Mab3wXmwi3xVDSGCRWXiad{&*vHxh6J*$nPK_&$GOC?*DU&`^_Nu;61Mbr0#KyI!TbCpV znABN9Qzr#2zmw_j8vF4qU|K* zAIKb4z#I<`>R}e>rS970f?jyx0LVj*J}5(3%Z>g}Av+ldCuJ1FF#%XEt1R=ly6`X_ zPg*IvI=knc1szR@qOfikFej(|i;MJw@BxT3P~Pq;VNXW8i(!2B-4EJ>*I!855ZZFU z*~|_vTk{qZ;baQPvJkRJybx91p#1a$4NkTijx)JZRAfwt)mhSA@9Z=Xo}ueu8JR0T zrr%<+-(mvqso}F68uWw`uL1`VlN^n)-)Fe`RnhIq@%D4k+7+zcP9Ndnhf8Dd%2obq zG3gFhh_Eo1QixE=Ra(LayvC=C*3_XdRN<9Z{!=C{gcF7~<)@Om3bCxN)8oY9g4Wk* z<{jnMqa~7hyr$w5(WR|8N|Ed+=x%iWtW7}>T52&p!juS{^~wi=k_heY>=eGvBdkRy zs*ENwGMNY)Vd`}l&SY%9Wq=m<74pxxW5|FS*>s$jpwi7X&PA$@(6CtWO&pF<%1n^4 z2QNQpfA(j8vHgdC`9E&|!C(BJx1am|Ki59H_POve1(}5w7e_l)v7F% zVV@GXz)+`n@OD=`Sus+j#wAJM7fHL zff!YojS2tc!i^gkN$}4C&I=2~&zXE(#AiaMd~{Wu^Gm-5o?@9VfK#3bv!V#^#Tlbd z_(kgA8bg=#9NF=1G6w1uvXytmg|~^`-yeonL;{=&!lNKDtU9YJ-ManM+;7xuFArI2%)R+dFPRs7G9+sT#sJ z@@oDA7FlyHJG(dqzWD2F*W3)r@jzdhHqqc-%+6%qx?!WqbC-BgV>n@XUUwX>g^hIS zk&mA4RY)>}Qq?-^(x zHq?!_S!s?9(XArz{JS5^JC5Ds-S+Mb{LA=1(b+ovoP*Dl8Ktfxr@l#`4x!wN_uNxaG}IT8=XnP|ij70VupQ ztCVAa&KwG`hlJCZv?}YowK3_FB^f3PF)sE^;L9h?N(dPBS`T-H?9^G4Eww?@?hyk% zeP&#{=) zHmP6j6NB)mcwOV7-eg1J)M?!SQPwd!bcW+SJ5t2e2U%dJ4i=w`XAOev6gq~X#qk=Q zLp92#6ZiVYdVBqix7rVW=_lGx|Ja{xf96X+*M9K!&$Lf({y@7sd4q|2TQZ@+zu_c! zz8&~9LVBSugT-snsSEjwYi)DorS`_g544~9{Li*O^%MVE`}}8r92)LLFR9?}0Gnve~LMO7R6D~%lixVFTv;p>w{6G&+ralB!2Ci<&XGLh2{K>es9okFIEy|a~ zP`-AjOQw{!7?fSs+@u+OqWneXNb>>IynmcDjb)Dz=1nig^%%0!A&H;RtsO1i6cF} ze(GXjW!Xab>{6o>y_zql)tyPh3sZ*woEuSTF#KG$@#xs(^~}V(4~c_LuCz@kQ!JM@ zn$>^fp5a}=iOX_&oiWpuiC`gPetx$dE|4PU#M71V_8h%XIOyLvAC6r?wkOz2CzYhF z278r(*g#%Q&rh&X!E=73f6PDcM&CW}u4{M(WCof}PoQ78KJRU$hfDkgI=~sz4HgPS&;=?3Q)n%EU%hDF;Eeqs;X3*@=LLtm0QU(=0 z#h4To_}ZM$smsLO!_g`+$v&_s9YTQ3YOH?dzPUZTFo=v z9Uz^`K>n6N@53UVM=xcRm(+*%GVmI{h}9Qs^;4AY-RX^XcEXAmkTD<K{9pR6SckwA;bB-S7SK2Mo^DN6HhOX(h!9+pR3S>gzk1JZLkA_R> z3gRR`yWsH6RPjZE_0>(flbih{DChg@{-e~{czxkKjfAp{a@7gbZ;V-q9 z*FFcVyHa5G4Cv)C3VfHb`M8~&KT7!8@3F`|WJ2)aGUY?PGRP0rGuN9mOTM@KsrI8U z{Mq)i-~aEow_f|cwmR7go9rWZp)VF`FYO5TZL{9Cbe$A=O=o}`_!&%;rkuyyX87SV zAa%4!5Psw_`5{Rh-AP)E?w%AdT;0I{9%8~5(#GpRr6b*d>ld65l1rW>S}#9YD+K-; zGUU`HM_lZ_tG&ZEalu2Hx-Q^)oTOD-(7CkFz7l74V`+;EE7Q+4h}70$XyMSn%n^#n zNbxnh%^2IxEgl1k4znH%Q+JGgNK9i)wysY7q7QoE!dL9|-RSN$jF%_w{=1m7&5S_G z*ye25IB(_M%!_Shx`a^(UUz$`uXY$qr}#D(k0@rbL^);Ha;Z!H(Le4I`}#$n=UJp4 z^1!*5kSC=yKtqqwcVlT%{w> z9bT+oNmX28ID^+CgC$`L?TooMa}O{nnm$8EKyT)6I|n#7jk17N;p)L#SQ!s)FgZV- zI6q!w5xjx{l_Mie!s?;)yJK6v$t(HTh1k9)Goq=yf(>3ek4H`liCrC&|S zA8i;$w8v@u7?MY7%&$8PK0Itc3@muU#D@3+1pwCW^!@6?hNw4v)+oJR48EeAu z<$7NCvpoJwSjx}bHRyF<#OeH+tWA5)f09dH1+&wjB2$T}MZo2R7K05Ln(ZJ7XOfD^ z@}$K;Hs7cdg##dAI^V&~ATDFIR=z40@2q$`sM3e05G(Ct4N?Z~F@w^bT;C+&E9E@m zaE3Pz^E4Bc2AU79oN?9Iju8z#ihcb}#oO^;VObQ&Ps zgq}g%E4T)~Gx-b`IIrDXfnH}keBvrjopcvja}J)#w^=&97J`Y6w1Hu0!v310@1Q5L)*-5;+boxUq0AxqmRV3Ws$ z6yDlK%llJRr~Sf2=15XE`7w%&Rlyi$uf6qV`}v>y^X-517yifXi#NXqMPBFXYP`Z5 z#>_kW|E&H0zWHCb|Kjid-`fB2Z+xZwr@!frM+<;euhicsIO_AOgh*)Tt$7sEz0c~$E&ZAlwFE`(v-d8Bcwv6@|7%7|B5>cLhF_B9P_;`FWa zSYE|w{nTxJ8sE$A9#cm=d6_T0iXZrz8==WpE`ryW!6^3{_;!T()h2hq84u8!yRq_z zJQ?hK8v~s-InXUJ2R|M<|OQWFaL z#1Xw5IWfz!ZUO*I`^so5m)&;h7m)wahSI0yJU3l$`@nU4!ywO`EYI|#jP>;@3W`V+ zR3a8C_=LS$-iXj;yNt2oksxW;PO)zWO%#mY%;rIg6-`CA=Hh47w znOa+$ekGOQYf|XpT>Js9=f@H#EzD1>NU>fRx9T^!>8}4l0S>%D_;er;(`G1+O9g2RdBUh_T8y| zP{oHkGEB3(qtJ|9YEu;+jl`N9^mHt68YLO@;^gAHJC&(%%UC>lFv1?@Q9XUf0& z_S1Sj75VirPw`g+^WL4?ZEJHqJke&(O4e$u3s<-6NaN~2kS6=Zv(+^KX<(d_e1`>* z0_gzB4)(FfH_D*&{Isv(Q81tG>?Geomose{oP~=5x!eF}jA61ow|sAHvAwdf(l!Zy z@Vr=dH)@r-Ey_$2$z5>g)cKB+z0~QKCaOnHItt9QBfM=su zQN~|->vntfh0T=N%Nsk6+oQdc_QBJWb_73dS52?A2%+0#`Lm?EV?tC~yoGspYudi} z%FTRGCq_F?JXz=&rHnL}Tv8^+ycoKZ2gsza8q1yMA~s#h*5ltzqXOI6_%2`+h*dV9vGD>$sB zlB~;i`IpGsqA%Fni+`rJ>q z*Ehe2%sVzE=c;{c_Yd0deDLe-x4-p|+pquAf874?Yro&#e)o^t!zb^x!^8b{aIn{o zkGQgMK0MlMJNpmG`>1{J(fjS4r|-0nPQTMGmrvXJg0Dr}paDARwJ}`M$coyGEI@rI z@zz3nwEvhz;~s=`gF@h9N>0#u^eS)Gkgp_PVrhqN!iXyRg7$Y2q4mh#J>Tx)OQ%!*4%nK0GK*jx19Emel79d=UA`-dTHez~Wwc>~f$D=gzs;zxb1{w)SKfy>;Bay?dJ1 zZu$lv;iXxlPnx}@J@mIbuKAiFEz2fNj9q2J2FYWOb1dggERK!rCR3LSlG@~f2Kpho z-`&hEYuOCqsvKpPSfTDbGG9a3`ZS<@`;haQZHmr8x7d%=mNLReH*5HIygrb(GOdU0 zZHvZGMya%!uWcl~lpkOB)fi6Q#Dmv9a(<%C!lC-G%2~!o z77t79{>v}4>Ba;(-?wYRn;h++(cy{hx8go-!(#}Hq3O&>n>3cR2h}VA0wRU% zxqt?z03>Q3WYLZb8eDeoq0jc2A6m^IUKiy?rb>!^UuAR0B=hldjugqK@&4*z$Xmtm z<2P3k#Pctot12V#gR`pz=gCBDOrWaDrpgq6h=>?q;V30atRc_so z#+4+(u+xDD>L_^OI;^)NvMP2^TeTCP)z|=k2s?3_=IY!BCW6bj*`z@N=_tHBFa)!E zCp#X_?GeiOkaS^lB!swU ztKG>y9s0L8yFC!NIwK5vOAFyYDKVTer8{y?b}FW1&JEGLhSp3HhtQ z6XHjuI=%unx<-CDP;)zk`Wz@{{Y@J;h&z!CR|YY-xgH!HWw$!YP2XV*RNXr1u=0ut zGZczEe|3GKJv=JK61jwq0*CJ*deo^r7_NAXRkKMnE~s&dcGC1g4QX^oz9wP#%7j@@ z{}&y)7!=)EJdLg&e(OEAPCCl?Z6*MB->l5m7QKVdg>&GI6OBLuq)fT2^ogWA&V|7@ z-`feDwm%CU;AvU zRHyTBBTkazo(Bk;%5<`y{~In73i08SX8)(9K*ERcDY^b}b%%sB;i6bO0m79rPhhlB zG~G!atV}=3Tf>nZfOA|bFeWaDMMa1V@Ov=*e?6ru>(?f6XVI>+}dtG{=+}h z{ttioKWqQS7ybwBg|$zM3ED6)&kk7#KW=~Tcm8hs8-Me!wZHY>{`K}-zx^xiJMVm* z1>sJ1$1cyRZMMaFWQlJJ*gJgO-u>Wf?Kgk_@3+7Ccm7)YJHP(7+gJC#3LW)O%)GP7 zmQTHSU!`d3w0*h#!Vms$+Jk$a(W*ng&_3HDDDBTjS!@qsnNfV?7Z^ncwHlg4Eoa=Z zc0u9pr@P0iEF>al@+p5EM|XzoS{JcN1$M}si$)i`r30#8H9YfC7Uh|GYcxnil}Uea z<|0r?Cp+JVqFeV!2yYt!KNxxo3kX5S}ZG>?4zipqm zlZ)}K;psVGb#jD>Ic3rBHpOsuSDW6zm=t#6t5Crm12glL<lV)o`mY*;K->+2i%bylp?wz08+Cl7dE>9)2;{-#%8w#-}Ox|uxFx3)KfTZ41m z>$=}YXTZVa^s$}Iz}F~_63L*@NG8ug78n?%5+R_nsg!5EN0;nPzB^ zKB5&m+By!k_1 z`Zu7_*7$z!ANPwx)cuT%?fihIgg@ws+&oZyBNLM3}?|DyO}ID^Ul{nmTC zc~`i{eUyX23;8T9nK+6h>JXPZ7ET7xLWVRAx&%mE!eqJyl3tg(mfR`Z;uX{k@s7N5 zbqpZ&5=+sJUuRj_6<=VM#v9TRN<9THt{Bk0QjW!6}53roSux4xkf zNt0P#z33g|29lhfD`lj)>`|4Fa2=) z(|_t;Yd`fpKhw5X?^PYv%Kslf{{8lM{_wwPfA#PDKik*8@rNux_rep~U2PK&JSuUh zUNwXIZG*YPsn4G5e$?K6@0;ztGuv$eWi^CHuUzb{kF6B zw4F#p>am>qz%i`6@e^I7Ty^^7bLVMv3-J~7i~V@w`+J$lAMKtscaL-4x%W%)nK2+D z<9dQcYPgeeKEYtR$2h#rSl>b~tYeh<#s%N2{@o`>(V_VwYbgiDP-9#!=N;0%DlhNj zXW{JK9A9zyKncKD6=+AjL_TTf2fcYzfxMMHPa^r9m* zlI`c{XWIkeF($#7vCTME9^BM$$9S3g)op&mz_Y@r%#8i?1@OwJ$3dbKh%eE`GRmNn zj*l^p5h_mW6E3et1+PaTWSrwY<53;bujwJKInDtsS=43Yr-Uh`z?bwF*myg>Y{K4-~7c-&qLSJP8Ey=`FH;hw{R*y>yA!M*bhcNjbw{VU$ z-#)9&7Uu!^)p;W*_%y^dVoT?m@qHMUGS;9fRj$c z@WwGx>ClkAHQ!xukb+0rhgvmByx~1CJt6o6RR4-6hBq#|sn7&a{abfp{1MkxI|@&X zR&b1S;R0ua5nZs1{bqO^$)wi&Q3>Vo&@G-Czl1@VETMY6hO=o(JPS-+rz5TkKu5=u zn7_tNPeSr^HCD?FLpwcdKL6^P;p)|gg~#OZ2E5)OlXhFAJlQ|mHf6_EJ@YOo?GXE{ z!xR~25HL@@NBXP<9nIjI@Qah_+E;Q8)&tnxkFeKx3_@2s!o~a14dcVRtID-rGg9Y? zD%^d?;qCt!PJAAM%hd82;cOQCvtxVw*K85)&S8c1K9q^G1C!pIX@0T~1+ZL0Dw}pT2 zvAgsdjw*7Te5Zhu>HY21b`S3>>g0*LxB5ejXuk^R@s6|@SaCZ6-CUcrANus&w#lUI z`P`YQ{rK{)C1M@8u5tGwJ=pR_mcuVb9AB`$=0 zX#Zu0UQe0YO;cW&P~44>aaKjcEauLMeVudJ0L6`JhjC%Vn~8{i5?99-lFg57$~Yp`)+8E8FwP21bHExNNCqt((EcG5&bxF#^nZ+T3jG%eM*^7v9Dft2BD5l?!cqY1&Sg4%!DFeT#8vUkjh!1$WWp zLQlO>wVUSi0i?~aAlvklI!9UdD~IkVf5`Z9v1{FtA==QpGd~8cM}wy%s&;GL_GRrK%6FTc@#_y>QY{mhU4tL^nW-@~F{+P?AlpSIuq{ojgSg1pd` zuWNEOuHH+4j*tuyCaM6=!pbgn5ph>JfEK@71V4aiA4?bKO^o3e4E^%bzL4tanE)L|E?Hb0f?~m55aidh@I!3=^mjba9 ztXG`GvxD5DAtz2-iF#7ZEkgRydU3(h+vM*I-*aw0ZN?)n!PUcci5tPf*9~ZJq(8hS z4|-F+Cwzhvr}ME7s-_=_mzIMLQ2Hrbw7p;5_gm%S^{a%wr|;yLIBCc6<{#rLFJCk4UoK-d4B7= zPiXI9+W+M7!}jRWlXi4$+t1ReQ6coQ;_e^AKEj>D_tobabefTMX4G&En5?2Iq>}pY`L@&DFFlGK9=dEv9nvIm@;7PzmMd85*D}$8 zCSKYthWy8f1TehU+ZOwC223*P*X_3-Eg(+gLWt15FCJuf^{SM09>g6Wy-13U>!p$+ z{u(c}(iMLa@#aED3LtkH(B5VxIXXi_1f%0iU;d%?Z~el*+kWKbf0ae;{i+6>r)LN4 z_dfW&_Fw+ZUuysO*ZyAn@cnmVFy~#wJ`ce7&s`{VNG5-cSC|{NP`Bx`yOxr|PnzNn zAyiTeWu7A#?$!3Wd*9z~ZNJj4Y!6U*m8bm02S3fe zL|^8(2j1PyuvR)NySj|DOyc}a=sS;$IUb#GE7d;hbPPvfKcZ6oEK=A`BbBXu=~EAn zJfg6HaSXbStFAhKqtmHd*;V%py2<^emLG)V6`I#!-KWE4+#n!`Kt%nzTDCv{^C zevKp2Jc^Np7JXhDp5$wEYLxmADQYps>`nulhYO)q+OoszoMk;+$Ifux&XM{l!`1n$j(fQ43cPTu-c?@8 z+~!76-faww^>{qPhX?D`bW|2TipRRJmr0YZGN$p;L%Mn}WeB$$7vQBWz^$wZ&(Pc1iAUyVs-IC3o z$ca0wj!$_4?Fy!Fz-SzfT~@EzSo016<%Z!H*`$buwF^==B=_nfkZ8jYgctzpV!*tn|C8VD^mr?kY*E}E6|q*R+|*N;?VB=Q*LlVr+S3YK!T8cTCaP!P~^mZLzIm4Bx)9-8Q$^+B$x1YuZ*Zl2@nrb&r}%+8S{UWgt+9QHzi1y}WIsH{ z*gn2!AMT$e{1Ha<2m2@OgWZ$%1S5J6IL{FtQN|CqM93NGYZ`G1K6Q8|$w|#49jo5P z~sESJW{ILb~wr9fy=pb>1RR$|4S@d~ODEN7QEQH#};xgSqYMv9F zlb;i?3xbFZGU9HU6LcBUgd~skxk9SM#19^c_b&68{XYcF;_R4Z98^XbX12WfVaY36uCYi362E5hUhvrAw z@h>V<)E&BhNs-(Y=~op{)j~pd!WO)^q`O)!#DJ=B<=M_SXmT8y6!zpDYUS*^;IVF9 zM-=s%a!Ue@Ej>_pz?!QeN>E3oJB~)%1vS+j#0r6)iYysc$~(O9 z#tRo=gE(nkzqtTMK)Aore*C9?vi<3w{<-$~?JuT&@w%ve^5k**FMj#|+WzX__Pa@Qn(@$8d#)+__v|%yor%VuD{bjBc9dZ5x!wh}Dp^OzsNA$im3yWQJT)e4UyeG0*E8F`p~QJx+Ba`*wjceGpK1U8zw;lq|HZ%kSK80K_48R^ z#=&fP9V3s$S#W^2_>OZ7L{U`VI{zv#83)RV2%PIvUI6=}j9%xzY!HyGk$irt0c~Bv zQ_M0n)Ui!U#-Mk`%c+;oyUfc+Mg5DZrFLNt;T6a@N zHZ{g(7S4wQb&kv4+#WYfdw{ncaG3=0pqnTjoK{r--C2mZ^qQx$frg6&oZ%WHW{JD{ zLF)YzoL}oC&AH5W_=VKLqp}G+8iEY3AWYqfM|^RGD6oTVM z&oF@TiQ|DY%@vv*A7zP=y}rHPrWoXtwKBXFPW7#C^4MNqYFq0IZF`MLC4RBp+Gfhy zoU~h8OfsAJjpaP7=G!`MprG8L_DF+<~#5?b6h9@(=+h-BUlrK zC`hwx?Z7R!V-%7`Xa-uL@5%xNpRVG^a%Skp6*r7aUG*e1;AtvtqHgf9ZYXDFzZ42- zr*DarQq!QeEqJcNc>c3o2&fQOKi};+zlaa`r9ZD4)IhNY)A8arsGCt8k~I$C@x~+z zj0uzJijzks^=oAm14TvM#GyL# zm&RKm{bksouC8-*_v^qtOSfX$5PH)FwV;_HWaeT#yAc3^GlM!A<&%%kL`zw(>66|M zL1x*}f8=GN5|`-w>#)I}6dd6tjFFdKe!2a*pZ)Xg`|kW8#>UO$?;O0-e&_vPY5(uv z`fKgCf9KcR#90VPCvW&c@sr+xA6mp$8uLDk!7U)glqZYA_V_#mm_ z5i$%vvs~|RPLhMKvhKxOt9iT4Y(h_mSvE!bFLZ;a9EEV!@AD+mnN1R!;P_jE&yKx9 zryk&!^!!<8*9GoG4?U5%v{w;auZuP~CY+#Mz!N!5k1nQN4=yBPsdfpH~dpHRggmOvrp?US4fSc{hc! zYpv0Y9&o&`pocbYu4n$)z^`qtx9!{8ZDVVj@Dw8#9N-c@-6oC|y)<&gW{`K%*cQ%b z_7_5V2Wzh~KRREzc#*f|jr-Z_++~EY?4*M;hB=5m(s=GTQ1-jdLul|_7sLTb z|7FM6RmRM@=#ZDvSVwJKpoIZC`cOkSn|9(X@ucqhs;ozql!-jt=jNgVfOu&bjDHdIq|Vr z$PZGkPXdgwBWM;CE)GPj z6H&sn5IE&%WX)F;9FwZf&*)FTB)VefgDk|Neva0Dt?=ebR5X z>FPQogM#3T1qD5NKN;?>Yi{8?K|o7xi<5Q%>>7sni!VHA%P6(U(q&siQEi||J$t=L z{WnppKIAq9_o_+}z8*B)6MrJ_@g5z%IV6e#wO?LVP zp`kT^6b9NZPz0I+4h&Lq%g!BL!XDvpFw1L&(_QJqqoelt$&)Ced>INM&E(l>n7J{~ z;I+xJ$4T!dZF_}<$W0~`b=$`g@}sPR{$R956>|` zPujsr4FnakA8?Q-pMGhxeeWB$Q}@B)NqY+1hfh!1druDAp||5~H}H2^z{?pU_Ja=wwU3MU496hDQE$jdSJk-4k1oADnd-A8Vsb{pCka1 ztNcuw#sm}eodp;u zzN1b$Htd8ovfsCV$iVDO!LJ2YmbEw|oxCV}uAy2&d)uWclaS2E`BeG`kKSo-zV-R` z!s@FrE_8wATI1O?k?2-Pzu&(3_#d_XgU4+#bqEQ5=+r0(?Q&7NrIVlrj)y@si{zJS z8Xms-&bul*ho|l7!D%}oZ53V$ud4?d6N*oaAA8BLs5}|uA(=kduQ*oJz~8341zq8hw2<`leUS$`9rU6 zw5#2NwtIBezUAvM573{sL#+1q|DUq|46-cA^8B!q8REqYEi!aOYF$}bSz2{fS9eeM z^vw2%6~N3cv5PeTBte>iMl<3Qzp#zQ?<5UoG;j$nu(ntbv$KQQot@e4ncmh@-CbQ; zrk0VJkr`UYd!ds5-~Zej8P#G4`U?-g*ZuhM3T87=Vp2YnLOhlc$B6;Rwj4Bh*HIz9$|@``mn&GyoOHJhG}W)rl%@gs-~yRs3=y>uVYt+L%=4(bXr#X84{Zn*1SYvf z1F*ya=4O=W*F3a@)&$%+jI89JN2e-t@vHI+K7A5;h1J`9p3c$*j_wWEu z+0^|PoFejA;=F}y_K2IOj1BU%1?WDP*YxN3Q@VY<2M!2~LJQC`vCQct8cOjbl}sRX z0|J_P0L5jq?sW*5U|hkyZtuPDm8kI;%KRj>Amo}$3mcEQ09OY`z^yo>Ai-zFKxe1( zOK5#O5BJ~`YpzdHmp_X7O3f=$`&oGsIXsfEd`P*#UU#T%aT;S70o$PqBG@Q`G*dUW8)Lo>A2gr< zuRk}o3f*ERxw&Cw<5N2Xl>&*7$vHwZaXP=?{c?sfLs$;o`-h zzRs3-a=a^!jdsLvU%L!=P6t?*(OHZ8v#W9K!IDqopj@6xaO542gTHoRIDY$;qfwI) zJvF-=w;!&=rF#qU;mw7ZTdjGZL?n61f>Q@56S43=Sf+ohKw}KED)!uwo;W++9i1Ay z+4IO=&bkcvnk-*d$T@X@>ca!}XLDVxG#kGsexQZjisupbD*OFaF8rrXKl}=ebU5mfIw-ldFug9wNZXQ1>N=3`I_}0+C$p+Gu4bpw$J*&Gc#CVQ^ z?8s>Kx#FsT#}T$lD6y%6Y~d}OHaszK(eHeU-P_>gY;ehool))Rh~BP&7#%(s9lGx6 z>53-~pO5PP7W1sFvDN*+hNtrE=ZH0Qzj(VX#GA2U*}6J^Ij-Hh;<9=Av9hunH?Cca z>G=nm=&HSSM6%Uyd*fm zU5?`f@1)&$?vRnZee48a5(`<7qfK2*F}cD~l=UvUSe=rI3eGFp^n)TV`Xu{?Z|xdd z+grbxhM~aBP_AKN{))WWIrtUnFx4GWX;#+mhyZiF*9!tPx;BnIp3I-aiAQMV!B2;! z!5BChK)6R1xMy(lDIB^-+4+SLV3?6J;9y8I_(=KjD9VDN`+U>|K9wntGT_*~gX2fz z%;~dn?%b1c_Uzdh8W>bNZO6>~R9wA(DSm$W&G_i%d-49wcjJRw@5G(C+i`dPe%xKW z6FW^RSA}m$osAD4i6et2W3>NJbPw_ogu&?Q?T>1EXLR)R#?a8wIC1L9c=G&najNS= zymR%(F*SKNwq-mafPU*&KedD(PjX^}s)Nn$Sa`&$C7slJEY;}JI~rj2#kvUL0}2dW z2${st?LkLED3McElZi_OfbhuBbw@*p*i&4rPN>{w&AOLYR;1Y2J0_|WhtbFhEX&KQ z-uG#mX)9<{Xxw%wrc9uSk-88L;T8xAp;PTJ+TI+U8r|3j{?RX8#KI~Hoqm3)7Wbx?|rJgM$@DR<;G4q(8e1X|t-wJcC@^f|^o zWlB8YrEUpj=j{k%g^aWh$El>GuVSbNoiO(!47B}rCfm&N82bc-*myXv)j;- zM<{`j_h3Q!>H~&FAe@$zl+|G3-qzk8!-KtXb?%e+>8EeTU;p&4#fs3Y{?%%3A61Oy5oKnchd1eWzF|1cm1LqY( zJJDWL0evDUthNFc_o(GzEX>bDt+uT3S~*mVi`t#QbDcS$QLJ@aBky?n5&D6rD)(pB zJeKkCBu>uYNJn7v!z{u952=&laOBUGD(HLU$tXwQ9gycVjyCl>R>m$(EW|AMRJi*E zGzhWJbQfHQ;COklrIHH>%hC(8np`hyg31#ea8B`0i$Fr4sqzL|{&QQN;9 zRROd#@i}_^3OUBnw>$~f-;KG?;-k;rb2>6cyZZeA`i0_{z`38>*r(m-7vi<#5}Bi+ zFnFxb3hkqe$!7}C?e6i7)_VvI^^?pMMl%L@d4@HoGi+%x2?F;m;X(6eHuA|U!fl`iX1eR+sANTXe;>Y#SW5m?Wdc_oX2X)pajy2z~hK6$Wx{z z5s%|YM~HO|zZs`+rH%*AdQfrJB?jd#oprlH+R5qM7x)=_QhO_{%wiDkvp%JrkN{vx z$E|V8rpk$fBEc&d;@LiBV;cp7qLJhMm81<{IEp8m>zJ1&&)59+INrx0KLR5i@A}~7 zpf_P3=Wr8wOi)(QSX4iGh*t#d>?_|7|j9&-<7)Da7TkhiSppZ8SKQ8Ad+kSGl+8vPg) zaPS5J#OPd;A}~wu%i+ujtk{V{uK&TSjP28B>mOaBr_R$1_I{#Nm;ySk(Z$ zxLVV|T8q20YsK+gD81yfT}dP4a{(>|d@YT0Y5QfwkM*{CuRC8VWYEEO2M_cR$9~C_ zSJe*&3MYoNxM{xG;4_Q%$A%_NHl#%aZMw3)VI%tPv?eW@*mytzUJf2Zk{d-xJ+O&K zc?sp-nHt;9ZxV!0l3GN@Gr z=e6vrt=YCfKS%m77V~R?=tVIdS%3iHvk}dTvtTQHg4<({k{Z-k%WK+Bvdt&B*)G`K z*Q>GML_GE6Gx5svUyQGx`kgpC@=T1jk49@l)ikeetV*}tkB=X`A0J$PKd#=s8n-8J z#ErYxu52%CX?C4CQ@Ojd-#lj3&`)DjZ;zdfzw!x(Ji-Uj zT3d{VGm~-a?zNa+n2DYCM={zqrZ#P|EP3oUyyQpp33XhV`Y1lT@;9P4-xLqFAb1Pxs}&=vQ4-|gVkH9P5!oCJvef~Io(qhyzko>GLbOAjjAT-U}= z!<4kiSS%so({3KdAKk-#R-tKImqW5s1Y0u6k@ApE)YH#DE8U^O+{&a&c?g(BL+DH9 zxVXGjU1)4BxK`o1#E1djj}Y{iw5w_&4#9vKEQ!-$N{lcL0f#?4wy^?}5sQ$MP6u?S z^_}ukQH((0@ytzeJNwx0x)m#|oXRLH_p`w`RtK0to6aKM0;~wa(@#7ZFF*HcJbUhw;}x{XxtwOh#>02BZw02A*jO1&Uy|EvPRBJg8MiIwlGh{_r+y|AEnX^_8#0 z`TnP)w|daL+2==Z>To-GF5$-ZTwGlJIR5yL{#i^<+*7=2sad$?2fl0Y(uB-H0x!K^ z3>cIP1Dmx9f8v(}V{@}HHuty0mb8XNpFHZ|<)Q1+imtZ}Akpn3xCG?4Qt2F|x}K~= zYhY?`_W=2D>S5MbdD-v8D)hqQQmo6sE(Hxn*pP;xy^eM&vn)6J@`dZ_O@t!z3(h;M&=Hn{!OENs?*W%vHYJ7BS z)&nMugY1BNpr;!D;EiMP?3tl>@L(zK&Ma%tS&R>E&c%&~%l^bT;!7DA%9c+zvI<5F z6*68{B|EG>KXs@(4h^)%KzD1jws7Rl{#dUw*lqbybDJh)74aDZx|zuvsPwP3rA4^* z+2CL$9sbgHFznaY^SC>B_^Ign_Rnv`y6RC^d-!XYZXdJWeaeP74%{XbM_hJ%T(*In z_zt*d{={Jc(Z&S|S8k6>rAgaK)&Mg?fE8z)n4gdHB$_Oge&dP}OF|(_nVR`>520`g zZ$av=bDvV#);XXl>R@UB_HD-DxGJMdTxF^$&T1rr{3k;wAyBJ30?5@N^#ipL?Zb%c z{jUrl^44L>l3%1#Tr+tT>bh*h#_E0NGfAE|CC=OPRisn}FIENT? z6d$vC%rV?@++6yr_|eb)BpTFA4Uc3QvXU%av>^i%<9CNg>`L2_oyIv3O(FNY>nWlSzA9i7C_i}OYOZ6N51CvWa=%?;}7UGe0h z9#4Wt4zx$R49`FQ;r00OXAk^QC7vcR=|`8K8;%Ti#z=2l^mp@V@n#<}$~LY~Z!g4+ zsTJv2bncF{B-^hzA?Q(j^DBp9ZEij$7Hjd7$>q2_R}Xb(=^yna^<&KBi5TY}Vxd$+OSIxpPm)nc*{0 zZETI|{rhgz<;umFxOY32mghXK@KSt!(;@W-tMbS={RQ2$B^{K8 zyl9i5*VWb&gT141;^fIVGj=Y<+s;Z~@lsqG54>Do9t*qqNP1{zRlepig0|dH+Vuxl z;zw_NFK%7CYMF9Iye|FXmg;61Rh=>Zd^DbDar-K5=)j@)##jG?c&g)qba=1(jhzT5 z-_rX1__O!^r}+1O{bxS0XsfO^WlM)VTR0d8H`&^ux?vFU1aebpx%4X>3ZYfaWIk+5 zCOiS!!3JUBDRnyS04#K>H@S|iw(qd5K>YHr&^H2gURJ9cHU61qlMNJ45r}K_LWgl9 zE0m?SAxvQUw0I(UaDP`+!a7SLXCA*@!t`!M#kRuEZ(`G9SP4UL7<7(3)oB3^w$b$DXaa$sS zR>8nW$`>3-+*4lZJPMFZ6rr5y76I^m_vJq_Q(WO_E4P>M)i)%=DueriB87=8vt)TL z0^0{%WqIOTrgDo=l@8frdsc4iNcMtxuPg3_hgEdw0ke>Zn ziu>L8HO1QlQ!DP3CZlu3@aqb7y&`3oQ-{zt!Qs(m2&QMBe8NRX%X$~@#*FFV{IiLtNJtw zw>LlGmD0d5I8lxVGc_P9C)9asd%qN)sBCDHZ;|5Xli^KCr+!#;bX4d=sr#i7DLf>^ zD?9SMg^SQL%VQOqL7}m!5-rUYwarr0>lzKgCLBE0f-juk&E8%1JfWa1+%SeR_c%Hy zbkp}RkKrNpW2L!G#=%%ee%TP*Z%s+TlSgz=UE<)=f%cXd=&JaOXa2IGlw@6smXEOT z0^$7yO?aeK8QoZIqK&)SD)Icu{y5N8jm70!%rDntc5yv!O-ZI$r4m7u!PDo8IsWT@C$%^pZDDmS+GmB-iF47n#1T zncuo(5F>|yog|N`^5n_>3ZUAA5sh{wP`u&XujOe6h4k&rU&P@^*B}3~>d1u3mtLbk znN9!*1;8Q$FRYD>AB^FaLDeZIbJU9v`BeqTRJhGp+`JjL?tSKQ-ts~@q5+y&9ZdZr zP=$W{EC9H|#Fb{jK-pA3qCZ@_w;)5QCOpzn!ol_q`T#5WpeRKJgy^?Yz6BxuNZ)OW zKJ_>HIw$W`WgOqpWcY@R6obY%eL{kqP>B zMe6Shhq_`@W6P3^vkAe>t}~pfjbNkNo6yFy#}0~O9LOW*Sd7VSKcYeWbidaGfGvX1 z#6*lXLhg9{R+awZG>Z%8pO2THeJ!3m@Kp4-9+45<9c%T)xV-pTeCMOTh@XD^*YVk< zk7MHQ%~)8N^T$Byo6@tq2#tR}j23n<`c)HdWtxdhrT^W!b?pHV%J@Sh&9XIY=kNNp& z)roql@2i=-SfqAR_0lWOlL;ky@+k)$o7>VldMmJZ90)QwGUmzS7<0-= z0ezY$D##w~gbrnbN0Zq&bDyW2>^GdF3@+}y&sRj3iy$zgCm5@CTQ4F;OHQ(FCgOhIcJf-bS_&_ zrJVzi>rwDNDZ?qG7{eQ=h$dbM+$trF=ZJn4r2S@ z8a3wA-~9AQyeXgFmF4As++!U6%Bq=@>25FhSMbo=TZP~C5*)`|Qg@EZ%M(B_!u3N& zM7wut$jOdw{-!&pW@$d#7NJx~++OdrJsyK9uO;vpReRESA%im(;{bgVsEjrN}Q=xy#5EN4N}cw&A%;)Cly z(_p+P#l!yI#MP|EZqj|T8p|)qDC7ev7-pn5HMgLnWuSM&{^nLG0hU4K5fagboFOX+ z83SuMle35cfCS-`R;DwH_SvW^gRKe-xC;x5ZcFM+*%;1muPeMF>{BRu5s@C&B~ed5Qw4QaM^)MIi^6SW#E2<)@l@!o3sGK51L^@MY@ z^??pL5PPUa=YjmHB6~LB3kukVg9>KVo;IOQV!z}dWrUZL*so1zqkB^T@szjb7gD7- zgqu@crd5Z#)5|u_^JD@6(KaFgURa4DGbkVZVGx)TqBA?q?f3&$3D@nA!e&l22;ql{pDI>`Q#h8)4NPJwS@Xu z$n@JoW|mDpwS5}UbUohDP>N(Td8cdIk$$Lfho?)5qgffW{d9>m!52-Papn_zRH?em z_JYsKZyvB>;{;tNomns76%SoP*3sG%#}1v2;p&KFkgsq8tZedZXA{NFkCW*uW9vh2 z?Vu4XF`|tuJSFs?1C6drxc~7IB5lP7SfGS`jh+}PsZBbTJ^vWhNC1Dmn%L6UEE;OC zlmY6(QK9P7^acGe#@cu%LdG$Z;ZF8I_jbfR8Pb>UE!vPa|6rIL6WHP2*66BMeEcYT zq+ucNPOz10218mAek?6`3`wbn`0|lX8KCPiFMM~GH)0;cP4u9Lm{56=!qZD#dli}! zFe;*3NF1+pPrM{Yw7nR~0we_W#pPP=`6Srbw@>WdGY8=|X7Ok3mwC>}DamEVev0Q->oGM!5>*c9W|Yq| z;PsdMcnnNu6;J3tw5Jn=guT<~6WESzlKYp16gjr^t31J351T+T60I^6@B-^H<(!Q^SLBy# z=vjK#SG~)5Va=HTt+LOk2-8lgw<15~e z#iNp4FPN5=7Vkl-C?u}ReVcqs#SCedvLvjwDo#cX$H%p{anA=-$dg}=Vr77@aY^o? zp(4Xd9zo=BO@Gfo{MOfgC%$~)(nM#;seH#jbt^< zt`RMU={7x#Bn>a+S?0kIwZSW8%jqKZ`eIB?-ti-|0|PzLTN!b_M?0%=qxM-`y8M1@ zudmy1<`E~ysPTefzGdDZ#fw3vczFgcjLRnF_sbztgxgZgto+c%-pH>(i7rg1Mj@qe zO5l`^aO`#*(Mv9jXSXeb0UcJbO41}?A!@vQAwfm3kOTHgV?^_%C|1S2nj~77<^8e~ zp7KhRO!EoPEIy(p8%%yi�t7z@!G#g+L6sZh^`G$K_ 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 DKoccpF*nFve*w@9PR;oaJpzYq$Sl;4zP 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 Date: Fri, 6 Sep 2024 22:32:24 -0400 Subject: [PATCH 044/116] Code review feedback Add a mechanism to generate entry ids and be able to reclaim them. --- .../Characters/ExmManager.cs | 33 +++++++++++++++++++ .../EntryBoardEntryBoardItemCreateHandler.cs | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs index ae5d3697d..6dbce4223 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs @@ -5,9 +5,11 @@ using Arrowgene.Ddon.Shared.Model.Quest; using System; using System.Collections.Generic; +using System.Diagnostics.Metrics; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; namespace Arrowgene.Ddon.GameServer.Characters @@ -20,6 +22,8 @@ public class ExmManager private Dictionary _CharacterIdToContentId; private Dictionary> _ContentIdToCharacterIds; private Dictionary _ContentIdToQuest; + private uint EntryItemIdCounter; + private Stack _FreeEntryItemIds; public ExmManager(DdonGameServer server) { @@ -28,6 +32,11 @@ public ExmManager(DdonGameServer server) _CharacterIdToContentId = new Dictionary(); _ContentIdToCharacterIds = new Dictionary>(); _ContentIdToQuest = new Dictionary(); + + // ID tracking + EntryItemIdCounter = 1; + _FreeEntryItemIds = new Stack(); + _FreeEntryItemIds.Push(EntryItemIdCounter); } public bool HasContentId(ulong contentId) @@ -118,6 +127,9 @@ public bool RemoveGroupForContent(ulong id) } _ContentIdToQuest.Remove(id); + var data = _ContentData[id]; + ReclaimEntryItemId(data.Id); + return _ContentData.Remove(id); } } @@ -240,5 +252,26 @@ public Quest GetQuestForContent(ulong id) return _ContentIdToQuest[id]; } } + + public uint GenerateEntryItemId() + { + lock (_FreeEntryItemIds) + { + if (_FreeEntryItemIds.Count == 0) + { + EntryItemIdCounter = EntryItemIdCounter + 1; + _FreeEntryItemIds.Push(EntryItemIdCounter); + } + return _FreeEntryItemIds.Pop(); + } + } + + private void ReclaimEntryItemId(uint id) + { + lock (_FreeEntryItemIds) + { + _FreeEntryItemIds.Push(id); + } + } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs index aae2ffa88..4f528c0c3 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemCreateHandler.cs @@ -32,7 +32,7 @@ public override S2CEntryBoardEntryBoardItemCreateRes Handle(GameClient client, C result.EntryItem.Param = request.CreateParam; result.EntryItem.PartyLeaderCharacterId = client.Character.CharacterId; result.EntryItem.TimeOut = 3600; - result.EntryItem.Id = Random.Shared.NextU32(); + result.EntryItem.Id = Server.ExmManager.GenerateEntryItemId(); result.EntryItem.Param.MinEntryNum = 1; var member = new CDataEntryMemberData() From a7a3d2e9d471ec9cfc050205fc73a932bf34f33d Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 7 Sep 2024 00:44:24 -0400 Subject: [PATCH 045/116] Fix some issues - Fixed an issue where if a player tries to join before earning a pawn, the client crashes. - Fixed an issue where if the player attempted multiplayer with invite, the quest will progress correctly. --- .../Characters/ExmManager.cs | 5 ---- .../EntryBoardEntryBoardItemLeaveHandler.cs | 2 ++ .../EntryBoardEntryBoardItemReadyHandler.cs | 3 +-- .../Handler/PartyPartyJoinHandler.cs | 23 +++++++++++------- .../Handler/PartyPartyLeaveHandler.cs | 10 +++++--- .../Handler/QuestPlayEndHandler.cs | 14 +---------- .../Handler/QuestPlayEntryHandler.cs | 2 +- Arrowgene.Ddon.GameServer/Party/PartyGroup.cs | 16 +++++++++---- .../Party/PartyManager.cs | 4 ++-- .../Files/Assets/quests/q50101020.json | 24 ++++++++----------- 10 files changed, 50 insertions(+), 53 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs index 6dbce4223..d3adf2281 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExmManager.cs @@ -205,11 +205,6 @@ public bool RemoveCharacterFromContentGroup(uint characterId) } _ContentIdToCharacterIds[id].Remove(characterId); - - if (_ContentIdToCharacterIds[id].Count == 0) - { - RemoveGroupForContent(id); - } } return true; diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs index e2cf1e687..c589d2a52 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemLeaveHandler.cs @@ -46,6 +46,8 @@ public override S2CEntryBoardEntryBoardItemLeaveRes Handle(GameClient client, C2 leaderClient.Send(new S2CEntryBoardEntryBoardItemLeaveNtc()); } + Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.Online); + return new S2CEntryBoardEntryBoardItemLeaveRes(); } } diff --git a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs index d540b9740..fb2d7626f 100644 --- a/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/EntryBoardEntryBoardItemReadyHandler.cs @@ -33,8 +33,7 @@ public override void Handle(GameClient client, StructurePacket host = party.AddHost(client); + PartyGroup party = Server.PartyManager.NewParty(contentId); S2CPartyPartyInviteAcceptNtc inviteAcceptNtc = new S2CPartyPartyInviteAcceptNtc(); inviteAcceptNtc.ServerId = (ushort)Server.Id; diff --git a/Arrowgene.Ddon.GameServer/Handler/PartyPartyJoinHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PartyPartyJoinHandler.cs index 0e420f884..9db853b73 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PartyPartyJoinHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PartyPartyJoinHandler.cs @@ -1,5 +1,6 @@ using Arrowgene.Ddon.GameServer.Dump; using Arrowgene.Ddon.GameServer.Party; +using Arrowgene.Ddon.GameServer.Quests; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.Shared.Entity.PacketStructure; @@ -19,15 +20,7 @@ public PartyPartyJoinHandler(DdonGameServer server) : base(server) public override void Handle(GameClient client, StructurePacket packet) { - S2CPartyPartyJoinRes res = new S2CPartyPartyJoinRes() - { - ContentNumber = Server.ExmManager.GetContentIdForCharacter(client.Character) - }; - - if (res.ContentNumber != 0) - { - Server.CharacterManager.UpdateOnlineStatus(client, client.Character, OnlineStatus.Contents); - } + S2CPartyPartyJoinRes res = new S2CPartyPartyJoinRes(); PartyGroup party = Server.PartyManager.GetParty(packet.Structure.PartyId); if (party == null) @@ -47,6 +40,18 @@ public override void Handle(GameClient client, StructurePacket> InstanceOmData { get; } - public PartyGroup(uint id, PartyManager partyManager) + public PartyGroup(uint id, PartyManager partyManager, ulong contentId) { MaxSlots = MaxPartyMember; _lock = new object(); _slots = new PartyMember[MaxSlots]; _partyManager = partyManager; _isBreakup = false; + ContentId = contentId; Id = id; @@ -265,13 +268,17 @@ public ErrorRes Join(GameClient client) lock (_lock) { PlayerPartyMember partyMember = GetPlayerPartyMember(client); - if (partyMember == null) + if (partyMember == null && MemberCount() > 0) { Logger.Error(client, $"[PartyId:{Id}][Join(GameClient)] has no slot"); return ErrorRes.Fail; } - - client.Party = this; + else if (partyMember == null) + { + AddHost(client); + partyMember = GetPlayerPartyMember(client); + } + if (_leader == null && _host == null) { // first to join the party @@ -279,6 +286,7 @@ public ErrorRes Join(GameClient client) _leader = partyMember; _host = partyMember; } + client.Party = this; partyMember.JoinState = JoinState.On; Logger.Info(client, $"[PartyId:{Id}][Join(GameClient)] joined"); diff --git a/Arrowgene.Ddon.GameServer/Party/PartyManager.cs b/Arrowgene.Ddon.GameServer/Party/PartyManager.cs index 215763516..a9f2f530e 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyManager.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyManager.cs @@ -129,7 +129,7 @@ public bool DisbandParty(uint partyId) return true; } - public PartyGroup NewParty() + public PartyGroup NewParty(ulong contentId = 0) { if (!_idPool.TryPop(out uint partyId)) { @@ -143,7 +143,7 @@ public PartyGroup NewParty() } } - PartyGroup party = new PartyGroup(partyId, this); + PartyGroup party = new PartyGroup(partyId, this, contentId); if (!_parties.TryAdd(partyId, party)) { Logger.Error("Could not create party, failed to add new party (!_parties.TryAdd(partyId, party))"); diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json index 9089d2f69..a8060421f 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50101020.json @@ -14,6 +14,9 @@ "max_pawns": 3, "phase_groups": [733, 0, 733] }, + "order_conditions": [ + {"type": "MainQuestCompleted", "Param1": 30} + ], "rewards": [ { "type": "fixed", @@ -114,8 +117,7 @@ "enemy_id": "0x015600", "level": 58, "exp": 0, - "is_boss": true, - "is_boss_bgm": false + "is_boss": true }, { "comment": "Frost Corpse Punisher", @@ -134,8 +136,7 @@ "enemy_id": "0x015600", "level": 58, "exp": 0, - "is_boss": true, - "is_boss_bgm": false + "is_boss": true } ] }, @@ -292,24 +293,21 @@ "enemy_id": "0x015604", "level": 58, "exp": 0, - "is_boss": true, - "is_boss_bgm": false + "is_boss": true }, { "comment": "Witch", "enemy_id": "0x015604", "level": 58, "exp": 0, - "is_boss": true, - "is_boss_bgm": false + "is_boss": true }, { "comment": "Witch", "enemy_id": "0x015604", "level": 58, "exp": 0, - "is_boss": true, - "is_boss_bgm": false + "is_boss": true } ] }, @@ -357,8 +355,7 @@ "level": 58, "exp": 0, "named_enemy_params_id": 705, - "is_boss": true, - "is_boss_bgm": false + "is_boss": true }, { "comment": "Ghost Mail", @@ -366,8 +363,7 @@ "level": 58, "exp": 0, "named_enemy_params_id": 705, - "is_boss": true, - "is_boss_bgm": false + "is_boss": true } ] } From 3d417aae06f9d330f89c1827f8e5398640b60277 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 7 Sep 2024 08:57:21 -0400 Subject: [PATCH 046/116] Fix quest bugs - Fixed issue with world quests not populating the quest log correctly. - Fixed issue with quest rewards where invalid quest ids crash the client --- .../Characters/QuestManager.cs | 12 +--- .../Handler/PartyPartyCreateHandler.cs | 17 +---- .../QuestGetSetQuestInfoListHandler.cs | 2 +- .../Handler/QuestGetSetQuestListHandler.cs | 50 +++++++++++--- .../Party/PartyQuestState.cs | 68 ++++++++----------- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 4 ++ 6 files changed, 77 insertions(+), 76 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 181a79445..876d341ca 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -191,17 +191,7 @@ public static uint GetRandomVariantId(QuestId baseQuest) public static Quest GetRewardQuest(QuestId questId, uint variantId) { - // Mostly for reward calls. If somehow the variantId is not valid, - // return the first quest found with the questId within VariantQuests - Quest quest = GetQuest(questId, variantId); - - if(quest is null) - { - // Check for variant quest - return variantQuests[questId].First().Value; - } - - return quest; + return GetQuest(questId, variantId); } public static Quest GetQuest(QuestId questId, uint variantId = 0) diff --git a/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs index 6b8955077..d404d9ec8 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PartyPartyCreateHandler.cs @@ -54,29 +54,14 @@ public override void Handle(GameClient client, StructurePacket x.QuestId).ToList(); - foreach (var quest in QuestManager.GetQuestsByType(QuestType.World)) - { - if (QuestManager.IsBoardQuest(quest.Key)) - { - continue; - } - - if (!worldQuests.Contains(quest.Key)) - { - party.QuestState.AddNewQuest(quest.Key, 0, false); - } + party.QuestState.AddNewQuest(quest.QuestId, quest.Step, true); } // Add quest for debug command diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs index ea674351e..08fa1d268 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestInfoListHandler.cs @@ -72,7 +72,7 @@ public override S2CQuestGetSetQuestInfoListRes Handle(GameClient client, C2SQues .Select(x => { var ret = x.Value.ToCDataSetQuestInfoList(); - ret.CompleteNum = (ushort)(client.Party.QuestState.IsComplete(x.Key) ? 1 : 0); // Completed in the current instance, hides rewards. + ret.CompleteNum = (ushort)(client.Party.QuestState.IsCompletedWorldQuest(x.Key) ? 1 : 0); // Completed in the current instance, hides rewards. ret.IsDiscovery = x.Value.IsDiscoverable || (completedQuests.Where(y => y.QuestId == x.Key).FirstOrDefault()?.ClearCount ?? 0) > 0; if (!ret.IsDiscovery) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs index 4b067753d..8650742e6 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs @@ -14,6 +14,8 @@ using Arrowgene.Logging; using System.Collections; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text.RegularExpressions; namespace Arrowgene.Ddon.GameServer.Handler @@ -31,17 +33,47 @@ public override void Handle(GameClient client, StructurePacket QuestManager.IsWorldQuest(x)).ToHashSet(); + foreach (var activeQuestId in activeQuestIds) + { + var quest = client.Party.QuestState.GetQuest(activeQuestId); + var questStats = client.Party.Leader.Client.Character.CompletedQuests.GetValueOrDefault(quest.QuestId); + var questState = client.Party.QuestState.GetQuestState(quest); + + res.SetQuestList.Add(new CDataSetQuestList() + { + Detail = new CDataSetQuestDetail() + { + IsDiscovery = (questStats == null) ? quest.IsDiscoverable : true, + ClearCount = (questStats == null) ? 0 : questStats.ClearCount + }, + Param = quest.ToCDataQuestList(questState.Step), + }); + } + + // Populate rest of quests for the area foreach (var questId in QuestManager.GetWorldQuestIdsByAreaId(packet.Structure.DistributeId)) { + if (activeQuestIds.Contains(questId)) + { + // Skip quests already populated + continue; + } + var quest = client.Party.QuestState.GetQuest(questId); var questStats = client.Party.Leader.Client.Character.CompletedQuests.GetValueOrDefault(quest.QuestId); - var questState = client.Party.QuestState.GetQuestState(questId); - /** - * World quests get added here instead of QuestGetWorldManageQuestListHandler because - * "World Manage Quests" are different from "World Quests". World manage quests appear - * to control the state of the game world (doors, paths, gates, etc.). World quests - * are random fetch, deliver and kill type quests. - */ + res.SetQuestList.Add(new CDataSetQuestList() { Detail = new CDataSetQuestDetail() @@ -49,8 +81,9 @@ public override void Handle(GameClient client, StructurePacket ActiveQuests { get; set; } private Dictionary> QuestLookupTable { get; set; } - private List CompletedWorldQuests { get; set; } private Dictionary ActiveVariantQuests { get; set; } // For the purposes of each party quest state knowing the possible variant quests private HashSet VariantQuests { get; set; } + private List CompletedWorldQuests { get; set; } public PartyQuestState() { @@ -306,45 +306,48 @@ public void RemoveQuest(QuestId questId) } } - public void CancelQuest(QuestId questId) + public void RemoveInactiveWorldQuests() { - lock (CompletedWorldQuests) + lock (ActiveQuests) { - var quest = GetQuest(questId); - RemoveQuest(questId); + var questsToRemove = new List(); + foreach (var (questId, quest) in ActiveQuests) + { + if (quest.QuestType == QuestType.World && quest.Step == 0) + { + questsToRemove.Add(questId); + } + } - // Save the quest if it was a world quest - // so we can add it back on instance reset - // Don't add alt quests to completed world quests. Rerolls are handled independently - if (quest.QuestType == QuestType.World && !quest.IsVariantQuest) + foreach (var questId in questsToRemove) { - CompletedWorldQuests.Add(questId); + ActiveQuests.Remove(questId); } } } - public void CompleteQuest(QuestId questId) + public void CancelQuest(QuestId questId) { - lock (CompletedWorldQuests) - { - var quest = GetQuest(questId); - RemoveQuest(questId); + var quest = GetQuest(questId); + RemoveQuest(questId); + } - // Save the quest if it was a world quest - // so we can add it back on instance reset - // Don't add alt quests to completed world quests. Rerolls are handled independently - if (quest.QuestType == QuestType.World && !quest.IsVariantQuest) - { - CompletedWorldQuests.Add(questId); - } + public void CompleteQuest(QuestId questId) + { + var quest = GetQuest(questId); + RemoveQuest(questId); - if (quest.NextQuestId != 0) - { - AddNewQuest(quest.NextQuestId, 0, false); - } + if (quest.NextQuestId != 0) + { + AddNewQuest(quest.NextQuestId, 0, false); } } + public bool IsCompletedWorldQuest(QuestId questId) + { + return CompletedWorldQuests.Contains(questId); + } + public List GetActiveQuestIds() { lock (ActiveQuests) @@ -480,14 +483,6 @@ private void RerollUnfoundAltQuests() public void ResetInstanceQuestState() { RerollUnfoundAltQuests(); - - // Add all world quests - foreach (var questId in CompletedWorldQuests) - { - AddNewQuest(questId, 0, false); - } - - CompletedWorldQuests.Clear(); } public bool UpdatePartyQuestProgress(DdonGameServer server, PartyGroup party, QuestId questId) @@ -516,11 +511,6 @@ public bool UpdatePartyQuestProgress(DdonGameServer server, PartyGroup party, Qu return true; } - public bool IsComplete(QuestId questId) - { - return CompletedWorldQuests.Contains(questId); - } - public bool CompletePartyQuestProgress(DdonGameServer server, PartyGroup party, QuestId questId) { Quest quest = GetQuest(questId); diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 52a0a0f30..4094ca3fe 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -566,6 +566,10 @@ public static List AsCDataRewardBoxItems(QuestBoxRewards rew List results = new List(); Quest quest = QuestManager.GetRewardQuest(rewards.QuestId, rewards.VariantId); + if (quest == null) + { + return new List(); + } foreach (var reward in quest.SelectableRewards) { From 2e46f161065aee024a42b03e2a03b84308c485f7 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 7 Sep 2024 10:08:45 -0400 Subject: [PATCH 047/116] Fix issue with some mergoda quests --- .../Files/Assets/quests/q20050009.json | 462 +++++++++--------- .../Files/Assets/quests/q20990001.json | 84 ++-- .../Files/Assets/quests/q20990003.json | 24 +- .../Files/Assets/quests/q20990004.json | 10 +- .../Files/Assets/quests/q20995000.json | 20 +- .../Files/Assets/quests/q20995002.json | 12 +- .../Files/Assets/quests/q20995004.json | 14 +- .../Files/Assets/quests/q20995005.json | 26 +- .../Files/Assets/quests/q20995006.json | 10 +- .../Files/Assets/quests/q20995007.json | 8 +- .../Files/Assets/quests/q20995011.json | 16 +- .../Files/Assets/quests/q21000090.json | 5 +- 12 files changed, 347 insertions(+), 344 deletions(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20050009.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20050009.json index 01dd326e8..4cddf4518 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20050009.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20050009.json @@ -1,236 +1,236 @@ { - "state_machine": "GenericStateMachine", - "type": "World", - "comment": "The Bridges of Our Livelihood", - "quest_id": 20050009, - "base_level": 14, - "minimum_item_rank": 0, - "discoverable": true, - "area_id": "MysreeForest", - "news_image": 103, - "rewards": [ - { - "type": "exp", - "amount": 460 - }, - { - "type": "wallet", - "wallet_type": "Gold", - "amount": 460 - }, - { - "type": "wallet", - "wallet_type": "RiftPoints", - "amount": 60 - }, - { - "type": "select", - "loot_pool": [ - { - "comment": "Leather Armor", - "item_id": 444, - "num": 1 - }, - { - "comment": "Superior Gala Extract", - "item_id": 61, - "num": 3 + "state_machine": "GenericStateMachine", + "type": "World", + "comment": "The Bridges of Our Livelihood", + "quest_id": 20050009, + "base_level": 14, + "minimum_item_rank": 0, + "discoverable": true, + "area_id": "MysreeForest", + "news_image": 103, + "rewards": [ + { + "type": "exp", + "amount": 460 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 460 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 60 + }, + { + "type": "select", + "loot_pool": [ + { + "comment": "Leather Armor", + "item_id": 444, + "num": 1 + }, + { + "comment": "Superior Gala Extract", + "item_id": 61, + "num": 3 + } + ] } - ] - } - ], - "enemy_groups": [ - { - "stage_id": { - "id": 1, - "group_id": 51 - }, - "starting_index": 1, - "enemies": [ - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 - }, - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 - }, - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 + ], + "enemy_groups": [ + { + "stage_id": { + "id": 1, + "group_id": 51 + }, + "starting_index": 1, + "enemies": [ + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + }, + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + }, + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + } + ] + }, + { + "stage_id": { + "id": 1, + "group_id": 55 + }, + "starting_index": 1, + "enemies": [ + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + }, + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + }, + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + } + ] + }, + { + "stage_id": { + "id": 1, + "group_id": 46 + }, + "starting_index": 1, + "enemies": [ + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + }, + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + }, + { + "comment": "Redcap", + "enemy_id": "0x011110", + "level": 14, + "exp": 92 + } + ] } - ] - }, - { - "stage_id": { - "id": 1, - "group_id": 55 - }, - "starting_index": 1, - "enemies": [ - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 - }, - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 - }, - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 - } - ] - }, - { - "stage_id": { - "id": 1, - "group_id": 46 - }, - "starting_index": 1, - "enemies": [ - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 - }, - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 - }, - { - "comment": "Redcap", - "enemy_id": "0x011110", - "level": 14, - "exp": 92 - } - ] - } - ], - "processes": [ - { - "comment": "Process 0", - "blocks": [ - { - "type": "NewNpcTalkAndOrder", - "flags": [ - { - "type": "QstLayout", - "action": "Set", - "value": 1060, - "comment": "Spawns NPC" - } - ], - "stage_id": { - "id": 1, - "group_id": 1, - "layer_no": 1 - }, - "npc_id": "LightlyEquippedFemaleSoldier", - "message_id": 11372 - }, - { - "type": "MyQstFlags", - "announce_type": "Accept", - "set_flags": [ 1 ], - "check_flags": [ 2, 3, 4 ] - }, - { - "type": "NewTalkToNpc", - "stage_id": { - "id": 1, - "group_id": 1, - "layer_no": 1 - }, - "announce_type": "Update", - "npc_id": "LightlyEquippedFemaleSoldier", - "message_id": 11842 - } - ] - }, - { - "comment": "Process1 (Redcap Group 1)", - "blocks": [ - { - "type": "MyQstFlags", - "check_flags": [ 1 ] - }, - { - "type": "DiscoverEnemy", - "groups": [ 0 ] - }, - { - "type": "KillGroup", - "announce_type": "Update", - "reset_group": false, - "groups": [ 0 ] - }, - { - "type": "MyQstFlags", - "set_flags": [ 2 ] - } - ] - }, - { - "comment": "Process2 (Redcap Group 2)", - "blocks": [ - { - "type": "MyQstFlags", - "check_flags": [ 1 ] - }, - { - "type": "DiscoverEnemy", - "groups": [ 1 ] - }, - { - "type": "KillGroup", - "announce_type": "Update", - "reset_group": false, - "groups": [ 1 ] - }, - { - "type": "MyQstFlags", - "set_flags": [ 3 ] - } - ] - }, - { - "comment": "Process3 (Redcap Group 3)", - "blocks": [ - { - "type": "MyQstFlags", - "check_flags": [ 1 ] - }, - { - "type": "DiscoverEnemy", - "groups": [ 2 ] - }, - { - "type": "KillGroup", - "announce_type": "Update", - "reset_group": false, - "groups": [ 2 ] - }, - { - "type": "MyQstFlags", - "set_flags": [ 4 ] + ], + "processes": [ + { + "comment": "Process 0", + "blocks": [ + { + "type": "NewNpcTalkAndOrder", + "flags": [ + { + "type": "QstLayout", + "action": "Set", + "value": 1060, + "comment": "Spawns NPC" + } + ], + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "npc_id": "LightlyEquippedFemaleSoldier", + "message_id": 11372 + }, + { + "type": "MyQstFlags", + "announce_type": "Accept", + "set_flags": [ 1 ], + "check_flags": [ 2, 3, 4 ] + }, + { + "type": "NewTalkToNpc", + "stage_id": { + "id": 1, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Update", + "npc_id": "LightlyEquippedFemaleSoldier", + "message_id": 11842 + } + ] + }, + { + "comment": "Process1 (Redcap Group 1)", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [ 1 ] + }, + { + "type": "DiscoverEnemy", + "groups": [ 0 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 0 ] + }, + { + "type": "MyQstFlags", + "set_flags": [ 2 ] + } + ] + }, + { + "comment": "Process2 (Redcap Group 2)", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [ 1 ] + }, + { + "type": "DiscoverEnemy", + "groups": [ 1 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 1 ] + }, + { + "type": "MyQstFlags", + "set_flags": [ 3 ] + } + ] + }, + { + "comment": "Process3 (Redcap Group 3)", + "blocks": [ + { + "type": "MyQstFlags", + "check_flags": [ 1 ] + }, + { + "type": "DiscoverEnemy", + "groups": [ 2 ] + }, + { + "type": "KillGroup", + "announce_type": "Update", + "reset_group": false, + "groups": [ 2 ] + }, + { + "type": "MyQstFlags", + "set_flags": [ 4 ] + } + ] } - ] - } - ] + ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990001.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990001.json index e27a61bdd..3a70399d6 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990001.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990001.json @@ -6,7 +6,7 @@ "base_level": 51, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "wallet", @@ -29,57 +29,57 @@ "item_id": 8027, "num": 1 }, - { + { "item_id": 7554, "num": 3 }, - { + { "item_id": 9363, - "num": 3 + "num": 3 } ] } ], "blocks": [ - { - "type": "NpcTalkAndOrder", - "stage_id": { - "id": 63 - }, - "npc_id": "Theodor", - "message_id": 10800 - }, - { - "type": "DeliverItems", - "stage_id": { - "id": 63, - "group_id": 1 - }, - "npc_id": "Theodor", - "announce_type": "Accept", - "items": [ { - "id": 7734, - "amount": 8 - } - ], - "message_id": 10737 - }, - { - "type": "DeliverItems", - "stage_id": { - "id": 63, - "group_id": 1 - }, - "npc_id": "Theodor", - "announce_type": "Update", - "items": [ + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 63 + }, + "npc_id": "Theodor", + "message_id": 10800 + }, { - "id": 8026, - "amount": 6 + "type": "DeliverItems", + "stage_id": { + "id": 63, + "group_id": 1 + }, + "npc_id": "Theodor", + "announce_type": "Accept", + "items": [ + { + "id": 7734, + "amount": 8 + } + ], + "message_id": 10737 + }, + { + "type": "DeliverItems", + "stage_id": { + "id": 63, + "group_id": 1 + }, + "npc_id": "Theodor", + "announce_type": "Update", + "items": [ + { + "id": 8026, + "amount": 6 + } + ], + "message_id": 10737 } - ], - "message_id": 10737 - } - ] + ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990003.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990003.json index 58dd7e47d..3d6ba383b 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990003.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990003.json @@ -6,7 +6,7 @@ "base_level": 50, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "exp", @@ -35,7 +35,7 @@ }, { "item_id": 41, - "num": 2 + "num": 2 } ] } @@ -52,25 +52,25 @@ "level": 50, "exp": 1500, "is_boss": false, - "hm_present_no": 59 - }, - { + "hm_present_no": 59 + }, + { "enemy_id": "0x011023", "level": 50, "exp": 1500, "is_boss": false, - "hm_present_no": 62 - }, - { + "hm_present_no": 62 + }, + { "enemy_id": "0x011024", "level": 50, "exp": 1500, "is_boss": false, - "hm_present_no": 63 + "hm_present_no": 63 } ] } - ], + ], "blocks": [ { "type": "NpcTalkAndOrder", @@ -91,7 +91,7 @@ "layer_no": 1 }, "flags": [ - {"type": "QstLayout", "action": "Set", "value": 1302, "comment": "Spawns Glowing Item"} + {"type": "QstLayout", "action": "Set", "value": 1302, "comment": "Spawns Glowing Item"} ] }, { @@ -108,7 +108,7 @@ }, "announce_type": "Update", "npc_id": "Ariadne", - "message_id": 11842 + "message_id": 11842 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990004.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990004.json index 1430bcaa6..6e201669c 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990004.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20990004.json @@ -6,7 +6,7 @@ "base_level": 53, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "exp", @@ -51,11 +51,11 @@ "enemy_id": "0x015102", "level": 54, "exp": 18500, - "is_boss": true + "is_boss": true } ] } - ], + ], "blocks": [ { "type": "NpcTalkAndOrder", @@ -72,7 +72,7 @@ "announce_type": "Accept", "groups": [0] }, - { + { "type": "KillGroup", "announce_type": "Update", "groups": [0] @@ -86,7 +86,7 @@ }, "announce_type": "Update", "npc_id": "Metrophanes", - "message_id": 11842 + "message_id": 11842 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995000.json index 2e48d8b06..9b99b97c8 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995000.json @@ -6,7 +6,7 @@ "base_level": 60, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "exp", @@ -35,7 +35,7 @@ }, { "item_id": 7802, - "num": 1 + "num": 1 } ] } @@ -51,19 +51,19 @@ "enemy_id": "0x015711", "level": 60, "exp": 45000, - "named_enemy_params_id": 634, + "named_enemy_params_id": 634, "is_boss": true - }, - { + }, + { "enemy_id": "0x015711", "level": 60, "exp": 45000, - "named_enemy_params_id": 633, - "is_boss": true + "named_enemy_params_id": 633, + "is_boss": true } ] } - ], + ], "blocks": [ { "type": "NpcTalkAndOrder", @@ -80,7 +80,7 @@ "announce_type": "Accept", "groups": [0] }, - { + { "type": "KillGroup", "announce_type": "Update", "groups": [0] @@ -94,7 +94,7 @@ }, "announce_type": "Update", "npc_id": "Actaeon", - "message_id": 11842 + "message_id": 11842 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995002.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995002.json index ce22c4001..e10c43c3e 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995002.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995002.json @@ -6,7 +6,7 @@ "base_level": 51, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "exp", @@ -35,7 +35,7 @@ }, { "item_id": 41, - "num": 1 + "num": 1 } ] } @@ -51,11 +51,11 @@ "enemy_id": "0x015707", "level": 51, "exp": 17000, - "is_boss": true + "is_boss": true } ] } - ], + ], "blocks": [ { "type": "NpcTalkAndOrder", @@ -72,7 +72,7 @@ "announce_type": "Accept", "groups": [0] }, - { + { "type": "KillGroup", "announce_type": "Update", "groups": [0] @@ -86,7 +86,7 @@ }, "announce_type": "Update", "npc_id": "Metrophanes", - "message_id": 11842 + "message_id": 11842 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995004.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995004.json index e5ec4625a..f835e6bc9 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995004.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995004.json @@ -6,7 +6,7 @@ "base_level": 53, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "exp", @@ -35,7 +35,7 @@ }, { "item_id": 7554, - "num": 3 + "num": 3 } ] } @@ -51,16 +51,16 @@ "enemy_id": "0x015103", "level": 53, "exp": 16000, - "is_boss": true + "is_boss": true } ] } - ], + ], "blocks": [ { "type": "NewNpcTalkAndOrder", "flags": [ - {"type": "QstLayout", "action": "Set", "value": 1619, "comment": "Spawns 亡都の錬金術師 NPC"} + {"type": "QstLayout", "action": "Set", "value": 1619, "comment": "Spawns 亡都の錬金術師 NPC"} ], "stage_id": { "id": 76, @@ -75,7 +75,7 @@ "announce_type": "Accept", "groups": [0] }, - { + { "type": "KillGroup", "announce_type": "Update", "groups": [0] @@ -89,7 +89,7 @@ }, "announce_type": "Update", "npc_id": "4740", - "message_id": 11842 + "message_id": 11842 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995005.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995005.json index 51e095bb0..e85b7f7e6 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995005.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995005.json @@ -6,7 +6,7 @@ "base_level": 55, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "exp", @@ -35,7 +35,7 @@ }, { "item_id": 7555, - "num": 2 + "num": 2 } ] } @@ -52,33 +52,33 @@ "level": 55, "exp": 21000, "is_boss": true - }, - { + }, + { "enemy_id": "0x010606", "level": 55, "exp": 1500, "is_boss": false - }, - { + }, + { "enemy_id": "0x010606", "level": 55, "exp": 1500, "is_boss": false - }, - { + }, + { "enemy_id": "0x010606", "level": 55, "exp": 1500, - "is_boss": false + "is_boss": false } ] } - ], + ], "blocks": [ { "type": "NewNpcTalkAndOrder", "flags": [ - {"type": "QstLayout", "action": "Set", "value": 1620, "comment": "Spawns Eunike NPC"} + {"type": "QstLayout", "action": "Set", "value": 1620, "comment": "Spawns Eunike NPC"} ], "stage_id": { "id": 76, @@ -93,7 +93,7 @@ "announce_type": "Accept", "groups": [0] }, - { + { "type": "KillGroup", "announce_type": "Update", "groups": [0] @@ -107,7 +107,7 @@ }, "announce_type": "Update", "npc_id": "Eunike", - "message_id": 11842 + "message_id": 11842 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995006.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995006.json index e5af3b497..19eb86d04 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995006.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995006.json @@ -6,7 +6,7 @@ "base_level": 50, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "wallet", @@ -29,13 +29,13 @@ "item_id": 7833, "num": 2 }, - { + { "item_id": 7554, "num": 3 }, - { + { "item_id": 41, - "num": 1 + "num": 1 } ] } @@ -44,7 +44,7 @@ { "type": "NewNpcTalkAndOrder", "flags": [ - {"type": "QstLayout", "action": "Set", "value": 1627, "comment": "Spawns 禁域守の神官 NPC"} + {"type": "QstLayout", "action": "Set", "value": 1627, "comment": "Spawns 禁域守の神官 NPC"} ], "stage_id": { "id": 76, diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995007.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995007.json index 8bdc69a1a..ba43a1e44 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995007.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995007.json @@ -6,7 +6,7 @@ "base_level": 50, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "wallet", @@ -29,13 +29,13 @@ "item_id": 8027, "num": 1 }, - { + { "item_id": 7554, "num": 3 }, - { + { "item_id": 9363, - "num": 3 + "num": 3 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995011.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995011.json index cb58167e8..933c36ab7 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995011.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q20995011.json @@ -6,7 +6,7 @@ "base_level": 55, "minimum_item_rank": 0, "discoverable": true, - "area_id": "MergodaRuins", + "area_id": "MergodaRuins", "rewards": [ { "type": "exp", @@ -35,7 +35,7 @@ }, { "item_id": 7555, - "num": 3 + "num": 3 } ] } @@ -52,16 +52,16 @@ "level": 55, "exp": 21000, "is_boss": true - }, - { + }, + { "enemy_id": "0x015102", "level": 55, "exp": 21000, - "is_boss": true + "is_boss": true } ] } - ], + ], "blocks": [ { "type": "NpcTalkAndOrder", @@ -78,7 +78,7 @@ "announce_type": "Accept", "groups": [0] }, - { + { "type": "KillGroup", "announce_type": "Update", "groups": [0] @@ -92,7 +92,7 @@ }, "announce_type": "Update", "npc_id": "Theodor", - "message_id": 11842 + "message_id": 11842 } ] } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q21000090.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q21000090.json index f4e2dcb20..7eddee251 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q21000090.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q21000090.json @@ -6,6 +6,7 @@ "base_level": 65, "minimum_item_rank": 0, "discoverable": false, + "area_id": "MergodaRuins", "rewards": [ { "type": "wallet", @@ -47,10 +48,12 @@ }, "enemies": [ { + "comment": "Infected Gorecyclops", "enemy_id": "0x015012", "level": 65, "exp": 45000, - "is_boss": true + "is_boss": true, + "infection_type": 2 } ] } From fabf2bcdf530ffff4ef2ec4406ec304c4a4386a5 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 7 Sep 2024 12:20:08 -0400 Subject: [PATCH 048/116] fix: Fix issue with world quest fetch Fixed an issue where the wrong list was queried for quests. --- .../Handler/QuestGetSetQuestListHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs index 8650742e6..443517483 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetSetQuestListHandler.cs @@ -71,7 +71,7 @@ public override void Handle(GameClient client, StructurePacket Date: Sat, 7 Sep 2024 12:50:28 -0700 Subject: [PATCH 049/116] Fix ghost slot bug when pawns are lost to the rift. --- .../Handler/PawnPawnLostHandler.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/PawnPawnLostHandler.cs b/Arrowgene.Ddon.GameServer/Handler/PawnPawnLostHandler.cs index 7fa729f57..81122f20c 100644 --- a/Arrowgene.Ddon.GameServer/Handler/PawnPawnLostHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/PawnPawnLostHandler.cs @@ -1,4 +1,5 @@ using System.Linq; +using Arrowgene.Ddon.GameServer.Party; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Model; @@ -25,6 +26,19 @@ public override void Handle(GameClient client, StructurePacket x is PawnPartyMember xpawn && xpawn.PawnId == packet.Structure.PawnId); + if (pawnIndex >= 0) + { + // Handle serverside tracking. C2SPawnPawnLostReq is only sent to the owner, and only they can kick their own pawn, so it works out. + client.Party.Kick(client, (byte)pawnIndex); + + // Free up the party slot so that the client allows new invites, if there are less than 4 people remaining. + client.Party.SendToAll(new S2CPartyPartyMemberKickNtc() + { + MemberIndex = (byte)pawnIndex + }); + } } } -} \ No newline at end of file +} From 72460339aed8467797094e1a650772cd381ad65a Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 7 Sep 2024 13:05:15 -0700 Subject: [PATCH 050/116] Limit /invite to safe areas, for both users and target clients. --- .../Chat/Command/Commands/PartyInviteCommand.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/PartyInviteCommand.cs b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/PartyInviteCommand.cs index aa9822f66..0005fe30c 100644 --- a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/PartyInviteCommand.cs +++ b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/PartyInviteCommand.cs @@ -1,4 +1,5 @@ using Arrowgene.Ddon.Database.Model; +using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.GameServer.Handler; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Network; @@ -36,7 +37,13 @@ public override void Execute(string[] command, GameClient client, ChatMessage me if (!client.Party.GetPlayerPartyMember(client).IsLeader) { - responses.Add(ChatResponse.CommandError(client, "Only the party leader can invite players.")); + responses.Add(ChatResponse.CommandError(client, "Only the party leader can invite.")); + return; + } + + if (!StageManager.IsSafeArea(client.Character.Stage)) + { + responses.Add(ChatResponse.CommandError(client, "You must be in a safe area to invite others.")); return; } @@ -75,6 +82,12 @@ public override void Execute(string[] command, GameClient client, ChatMessage me return; } + if (!StageManager.IsSafeArea(targetClient.Character.Stage)) + { + responses.Add(ChatResponse.CommandError(client, "The invited player is not in a safe area.")); + return; + } + //TODO: Revisit how to check if a player can actually receive an invite or not. _inviteCharacterHandler.Handle(client, new StructurePacket(new C2SPartyPartyInviteCharacterReq() From 36e86ec5e09e2d479b534a752318310562b8de31 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 8 Sep 2024 08:52:44 -0400 Subject: [PATCH 051/116] Update "Agent of Corruption (EM5)" - Added infection type - Added named param --- Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json index 544c89398..a153b0f52 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q50201000.json @@ -41,7 +41,9 @@ "enemy_id": "0x071310", "level": 65, "exp": 0, - "is_boss": true + "is_boss": true, + "infection_type": 1, + "named_enemy_params_id": 1674 } ] } From cf61bd88a2a2cddc8041cee008683a0ad2bb759a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 8 Sep 2024 10:40:27 -0400 Subject: [PATCH 052/116] feat: Add tutorial quest "To the Bitterblack Maze" - Added tutorial quest "To the Bitterblack Maze" - Refactored naming of personal to tutorial quests to more closly match nomenclature of game packets. --- .../Characters/QuestManager.cs | 28 ++++---- .../QuestGetCycleContentsStateListHandler.cs | 11 +++- .../QuestGetQuestCompletedListHandler.cs | 6 +- .../QuestGetTutorialQuestListHandler.cs | 4 +- .../Handler/QuestQuestProgressHandler.cs | 2 +- .../AssetReader/QuestAssetDeserializer.cs | 2 +- .../Files/Assets/quests/q60000050.json | 2 +- .../Files/Assets/quests/q60200100.json | 2 +- .../Files/Assets/quests/q60300047.json | 64 +++++++++++++++++++ .../Files/Assets/quests/q60300401.json | 2 +- .../Model/Quest/QuestType.cs | 2 +- 11 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q60300047.json diff --git a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs index 876d341ca..353d1bcaa 100644 --- a/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/QuestManager.cs @@ -22,7 +22,7 @@ private QuestManager() private static Dictionary gQuests = new Dictionary(); private static readonly Dictionary> variantQuests = new(); private static readonly HashSet AvailableVariantQuests = new(); - private static Dictionary> gPersonalQuests = new Dictionary>(); + private static Dictionary> gTutorialQuests = new Dictionary>(); private static Dictionary> gWorldQuests = new Dictionary>(); public static HashSet GetAllVariantQuestIds() @@ -33,7 +33,7 @@ public static HashSet GetAllVariantQuestIds() public static void LoadQuests(AssetRepository assetRepository) { // TODO: Quests should probably operate on the distribuition ID instead of quest id so the global list can still contain all quests - // TODO: Then quests can be distributed to different lists for faster lookup (like world by area id or personal by stageno) + // TODO: Then quests can be distributed to different lists for faster lookup (like world by area id or tutorial by stageno) // Load Quests defined in files foreach (var questAsset in assetRepository.QuestAssets.Quests) @@ -62,14 +62,14 @@ public static void LoadQuests(AssetRepository assetRepository) gQuests[questAsset.QuestId] = GenericQuest.FromAsset(questAsset); var quest = gQuests[questAsset.QuestId]; - if (quest.QuestType == QuestType.Personal) + if (quest.QuestType == QuestType.Tutorial) { uint stageNo = (uint)StageManager.ConvertIdToStageNo(quest.StageId); - if (!gPersonalQuests.ContainsKey(stageNo)) + if (!gTutorialQuests.ContainsKey(stageNo)) { - gPersonalQuests[stageNo] = new List(); + gTutorialQuests[stageNo] = new List(); } - gPersonalQuests[stageNo].Add(quest); + gTutorialQuests[stageNo].Add(quest); } else if (quest.QuestType == QuestType.World) { @@ -169,14 +169,14 @@ public static List GetWorldQuestIdsByAreaId(QuestAreaId areaId) return gWorldQuests[areaId].Select(x => x.QuestId).ToList(); } - public static List GetPersonalQuestsByStageNo(uint stageNo) + public static List GetTutorialQuestsByStageNo(uint stageNo) { - if (!gPersonalQuests.ContainsKey(stageNo)) + if (!gTutorialQuests.ContainsKey(stageNo)) { return new List(); } - return gPersonalQuests[stageNo]; + return gTutorialQuests[stageNo]; } public static uint GetRandomVariantId(QuestId baseQuest) @@ -260,12 +260,12 @@ public static CDataQuestOrderConditionParam MainQuestCompletionRestriction(Quest return new CDataQuestOrderConditionParam() { Type = 0x6, Param01 = (int)questId }; } - public static CDataQuestOrderConditionParam ClearPersonalQuestRestriction(int param01, int param02 = 0) + public static CDataQuestOrderConditionParam ClearTutorialQuestRestriction(int param01, int param02 = 0) { return new CDataQuestOrderConditionParam() { Type = 0x7, Param01 = param01, Param02 = param02 }; } - public static CDataQuestOrderConditionParam ClearPersonalQuestRestriction(QuestId questId, int param02 = 0) + public static CDataQuestOrderConditionParam ClearTutorialQuestRestriction(QuestId questId, int param02 = 0) { return new CDataQuestOrderConditionParam() { Type = 0x7, Param01 = (int)questId, Param02 = param02 }; } @@ -3267,14 +3267,14 @@ public static bool IsBoardQuest(Quest quest) return IsBoardQuest(quest.QuestId); } - public static bool IsPersonalQuest(QuestId questId) + public static bool IsTutorialQuest(QuestId questId) { return (((uint)questId) >= 60000000) && (((uint)questId) < 70000000); } - public static bool IsPersonalQuest(Quest quest) + public static bool IsTutorialQuest(Quest quest) { - return IsPersonalQuest(quest.QuestId); + return IsTutorialQuest(quest.QuestId); } public static bool IsWorldQuest(QuestId questId) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs index 7070a4bca..d8fe938ea 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetCycleContentsStateListHandler.cs @@ -12,6 +12,7 @@ using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Ddon.Shared.Network; using Arrowgene.Logging; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.Json; @@ -54,8 +55,14 @@ public override void Handle(GameClient client, IPacket packet) ntc.MainQuestIdList.Add(new CDataQuestId() { QuestId = (uint) msq.QuestId }); } - var personalQuestInProgress = Server.Database.GetQuestProgressByType(client.Character.CommonId, QuestType.Personal); - foreach (var questProgress in personalQuestInProgress) + var completedTutorials = client.Character.CompletedQuests.Values.Where(x => x.QuestType == QuestType.Tutorial); + foreach (var tut in completedTutorials) + { + ntc.TutorialQuestIdList.Add(new CDataQuestId() { QuestId = (uint) tut.QuestId}); + } + + var tutorialQuestInProgress = Server.Database.GetQuestProgressByType(client.Character.CommonId, QuestType.Tutorial); + foreach (var questProgress in tutorialQuestInProgress) { var quest = QuestManager.GetQuest(questProgress.QuestId); var tutorialQuest = quest.ToCDataTutorialQuestOrderList(questProgress.Step); diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestCompletedListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestCompletedListHandler.cs index 5e387ea32..77e53db45 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestCompletedListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetQuestCompletedListHandler.cs @@ -1,11 +1,7 @@ -using Arrowgene.Buffers; -using Arrowgene.Ddon.GameServer.Dump; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; using Arrowgene.Ddon.Shared.Entity.PacketStructure; -using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Ddon.Shared.Entity.Structure; -using Arrowgene.Ddon.Shared.Network; +using Arrowgene.Ddon.Shared.Model.Quest; using Arrowgene.Logging; using System.Linq; diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetTutorialQuestListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetTutorialQuestListHandler.cs index 5c359f018..c780696c9 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetTutorialQuestListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetTutorialQuestListHandler.cs @@ -24,11 +24,11 @@ public override S2CQuestGetTutorialQuestListRes Handle(GameClient client, C2SQue StageNo = request.StageNo, }; - var completedQuests = Server.Database.GetCompletedQuestsByType(client.Character.CommonId, QuestType.Personal); + var completedQuests = Server.Database.GetCompletedQuestsByType(client.Character.CommonId, QuestType.Tutorial); // This handler should return personal quests which have not been started // yet when the player enters the StageNo - foreach (var quest in QuestManager.GetPersonalQuestsByStageNo(request.StageNo)) + foreach (var quest in QuestManager.GetTutorialQuestsByStageNo(request.StageNo)) { uint stageNo = (uint) StageManager.ConvertIdToStageNo(quest.StageId); if (stageNo != request.StageNo) diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs index 81eaaac80..3dd4d82e8 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestQuestProgressHandler.cs @@ -90,7 +90,7 @@ public override void Handle(GameClient client, StructurePacket Date: Sun, 8 Sep 2024 17:25:45 -0400 Subject: [PATCH 053/116] feat: Learn abilities from items Added the ability to learn new secret augments by using items. Implemented the EXM "With the Myrmidons" which rewards the item "Book of Acquisition (Companion Healing)". --- .../Characters/ItemManager.cs | 104 +++- .../Characters/JobManager.cs | 16 +- .../Characters/OrbUnlockManager.cs | 2 +- .../Handler/ItemUseBagItemHandler.cs | 8 + .../Files/Assets/EnemySpawn.json | 28 -- .../Files/Assets/quests/q50202003.json | 459 ++++++++++++++++++ .../Files/Assets/quests/q50203000.json | 9 - 7 files changed, 568 insertions(+), 58 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Files/Assets/quests/q50202003.json diff --git a/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs b/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs index ac87f4efe..72392ebe1 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ItemManager.cs @@ -7,9 +7,14 @@ using Arrowgene.Ddon.Shared.Model; using Arrowgene.Logging; using System; +using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using YamlDotNet.Core.Tokens; +using YamlDotNet.Core; namespace Arrowgene.Ddon.GameServer.Characters { @@ -26,31 +31,31 @@ public ItemManager(DdonGameServer server) private static readonly uint STACK_BOX_MAX = 999; public static readonly List AllItemStorages = Enum.GetValues(typeof(StorageType)).Cast().ToList(); - public static readonly List ItemBagStorageTypes = new List { - StorageType.ItemBagConsumable, StorageType.ItemBagMaterial, StorageType.ItemBagEquipment, StorageType.ItemBagJob, - StorageType.KeyItems + public static readonly List ItemBagStorageTypes = new List { + StorageType.ItemBagConsumable, StorageType.ItemBagMaterial, StorageType.ItemBagEquipment, StorageType.ItemBagJob, + StorageType.KeyItems }; - public static readonly List BoxStorageTypes = new List { - StorageType.StorageBoxNormal, StorageType.StorageBoxExpansion, - StorageType.StorageChestDrawer1, StorageType.StorageChestDrawer2, StorageType.StorageChestDrawer3 + public static readonly List BoxStorageTypes = new List { + StorageType.StorageBoxNormal, StorageType.StorageBoxExpansion, + StorageType.StorageChestDrawer1, StorageType.StorageChestDrawer2, StorageType.StorageChestDrawer3 }; public static readonly List BothStorageTypes = ItemBagStorageTypes.Concat(BoxStorageTypes).ToList(); - public static readonly List EquipmentStorages = new List { - StorageType.CharacterEquipment, StorageType.PawnEquipment, - StorageType.ItemBagEquipment, - StorageType.StorageBoxNormal, StorageType.StorageBoxExpansion, - StorageType.StorageChestDrawer1, StorageType.StorageChestDrawer2, StorageType.StorageChestDrawer3 + public static readonly List EquipmentStorages = new List { + StorageType.CharacterEquipment, StorageType.PawnEquipment, + StorageType.ItemBagEquipment, + StorageType.StorageBoxNormal, StorageType.StorageBoxExpansion, + StorageType.StorageChestDrawer1, StorageType.StorageChestDrawer2, StorageType.StorageChestDrawer3 }; - public static readonly List BbmEmbodyStorages = new List { StorageType.StorageBoxNormal, StorageType.ItemBagConsumable, StorageType.ItemBagMaterial, StorageType.ItemBagEquipment, StorageType.ItemBagJob}; + public static readonly List BbmEmbodyStorages = new List { StorageType.StorageBoxNormal, StorageType.ItemBagConsumable, StorageType.ItemBagMaterial, StorageType.ItemBagEquipment, StorageType.ItemBagJob }; - private static readonly Dictionary ItemIdWalletTypeAndQuantity = new Dictionary() { + private static readonly Dictionary ItemIdWalletTypeAndQuantity = new Dictionary() { {7789, (WalletType.Gold, 1)}, {7790, (WalletType.Gold, 10)}, {7791, (WalletType.Gold, 100)}, {7792, (WalletType.RiftPoints,1)}, {7793, (WalletType.RiftPoints,10)}, {7794, (WalletType.RiftPoints,100)}, - {7795, (WalletType.BloodOrbs,1)}, // Doesn't show up + {7795, (WalletType.BloodOrbs,1)}, // Doesn't show up {7796, (WalletType.BloodOrbs,10)}, // Doesn't show up {7797, (WalletType.BloodOrbs,100)}, // Doesn't show up {18742, (WalletType.HighOrbs,1)}, @@ -68,6 +73,69 @@ public ItemManager(DdonGameServer server) // TODO: Find all items that add wallet points }; + private static readonly Dictionary AbilityItems = new Dictionary() + { + {16100, 448}, // 習得の書【友癒】,Book of Acquisition (Companion Healing), + {16101, 449}, // 習得の書【重歩 軽】,Book of Acquisition (Heavy Steps: Light), + {16102, 450},// 習得の書【穿歩 軽】,Book of Acquisition (Deft Footing: Light), + {16103, 451}, // 習得の書【延泉 軽】,Book of Acquisition (Extended Springs: Light), + {16104, 452}, // 習得の書【探採 軽】,Book of Acquisition (Gathering: Light), + {16105, 453}, // 習得の書【薬効 軽】,Book of Acquisition (Efficacy: Light), + {16106, 454}, // 習得の書【効延 軽】,Book of Acquisition (Effect Extension: Light), + {16107, 455}, // 習得の書【巧掘 軽】,Book of Acquisition (Expert Excavator: Light), + {16108, 456}, // 習得の書【流断 軽】,Book of Acquisition (Flow: Light), + {16109, 457}, // 習得の書【宝眼 軽】,Book of Acquisition (Treasure Eye: Light), + {16110, 458}, // 習得の書【根性 軽】,Book of Acquisition (Willpower: Light), + {16111, 459}, // 習得の書【安着 軽】,Book of Acquisition (Safe Landing: Light), + {19199, 248}, // 習得の書【抗毒】,Book of Acquisition (Resist Poison), + {19200, 249}, // 習得の書【抗遅】,Book of Acquisition (Anti-slow), + {19201, 250}, // 習得の書【抗眠】,Book of Acquisition (Anti-sleep), + {19202, 251}, // 習得の書【抗絶】,Book of Acquisition (Anti-stun), + {19203, 252}, // 習得の書【抗水】,Book of Acquisition (Anti-drench), + {19204, 253}, // 習得の書【抗油】,Book of Acquisition (Anti-oil), + {19205, 254}, // 習得の書【抗封】,Book of Acquisition (Anti-seal), + {19206, 255}, // 習得の書【抗軟】,Book of Acquisition (Anti-subdue), + {19207, 256}, // 習得の書【抗石】,Book of Acquisition (Anti-petrify), + {19208, 257}, // 習得の書【抗金】,Book of Acquisition (Anti-goldify), + {19209, 258}, // 習得の書【親炎】,Book of Acquisition (Close to Fire), + {19210, 259}, // 習得の書【親氷】,Book of Acquisition (Close to Ice), + {19211, 260}, // 習得の書【親雷】,Book of Acquisition (Close to Thunder), + {19212, 261}, // 習得の書【親聖】,Book of Acquisition (Close to Holy), + {19213, 262}, // 習得の書【親闇】,Book of Acquisition (Close to Dark), + {19214, 263}, // 習得の書【制毒】,Book of Acquisition (Control Poison), + {19215, 264}, // 習得の書【制遅】,Book of Acquisition (Control Slow), + {19216, 265}, // 習得の書【制眠】,Book of Acquisition (Control Sleep), + {19217, 266}, // 習得の書【制絶】,Book of Acquisition (Control Stun), + {19218, 267}, // 習得の書【速乾】,Book of Acquisition (Quick Drying), + {19219, 268}, // 習得の書【速清】,Book of Acquisition (Quick Clean), + {19220, 269}, // 習得の書【縮封】,Book of Acquisition (Quick Seal), + {19221, 270}, // 習得の書【縮軟】,Book of Acquisition (Reduce Subdue), + {19222, 273}, // 習得の書【縮焼】,Book of Acquisition (Reduce Tar), + {19223, 274}, // 習得の書【縮凍】,Book of Acquisition (Reduce Freeze), + {19224, 275}, // 習得の書【縮霧】,Book of Acquisition (Reduce Blind), + {19225, 276}, // 習得の書【縮炎】,Book of Acquisition (Reduce Fire), + {19226, 277}, // 習得の書【縮氷】,Book of Acquisition (Reduce Ice), + {19227, 278}, // 習得の書【縮雷】,Book of Acquisition (Reduce Thunder), + {19228, 279}, // 習得の書【縮聖】,Book of Acquisition (Reduce Holy), + {19229, 280}, // 習得の書【縮闇】,Book of Acquisition (Reduce Dark), + {19230, 281}, // 習得の書【縮攻】,Book of Acquisition (Reduce Physical Attack Down), + {19231, 282}, // 習得の書【縮防】,Book of Acquisition (Reduce Defense Down), + {19232, 283}, // 習得の書【縮念】,Book of Acquisition (Reduce Magick Attack Down), + {19233, 284}, // 習得の書【縮衰】,Book of Acquisition (Reduce Magick Defense Down), + {19234, 271}, // 習得の書【縮石】,Book of Acquisition (Reduce Petrify), + {19235, 272}, // 習得の書【縮金】,Book of Acquisition (Reduce Goldify), + }; + + public bool IsSecretAbilityItem(uint itemId) + { + return AbilityItems.ContainsKey(itemId); + } + + public uint GetAbilityId(uint itemId) + { + return AbilityItems[itemId]; + } + public bool IsItemWalletPoint(uint itemId) { return ItemIdWalletTypeAndQuantity.ContainsKey(itemId); @@ -117,7 +185,7 @@ public void GatherItem(DdonServer server, Character character, S2CIt if(ItemIdWalletTypeAndQuantity.ContainsKey(gatheringItem.ItemId)) { var walletTypeAndQuantity = ItemIdWalletTypeAndQuantity[gatheringItem.ItemId]; uint totalQuantityToAdd = walletTypeAndQuantity.Quantity * gatheringItem.ItemNum; - + CDataWalletPoint characterWalletPoint = character.WalletPointList.Where(wp => wp.Type == walletTypeAndQuantity.Type).First(); characterWalletPoint.Value += totalQuantityToAdd; // TODO: Cap to maximum for that wallet @@ -128,7 +196,7 @@ public void GatherItem(DdonServer server, Character character, S2CIt walletUpdate.AddPoint = (int) totalQuantityToAdd; walletUpdate.Value = characterWalletPoint.Value; ntc.UpdateWalletList.Add(walletUpdate); - + gatheringItem.ItemNum -= pickedGatherItems; } else { List results = AddItem(server, character, true, gatheringItem.ItemId, pickedGatherItems, connectionIn:connectionIn); @@ -332,7 +400,7 @@ private List DoAddItem(IDatabase database, Character char uint newItemNum = Math.Min(stackLimit, oldItemNum + itemsToAdd); uint addedItems = newItemNum - oldItemNum; itemsToAdd -= addedItems; - + Storage destinationStorage = character.Storage.GetStorage(destinationStorageType); if (item == null) { @@ -466,7 +534,7 @@ private void InsertItem(DdonServer server, Character character, Item storage.SetItem(item, num, slotNo); server.Database.InsertStorageItem(character.ContentCharacterId, storage.Type, slotNo, num, item, connectionIn); - + foreach (var crest in item.EquipElementParamList) { server.Database.InsertCrest(character.CommonId, item.UId, crest.SlotNo, crest.CrestId, crest.Add, connectionIn); diff --git a/Arrowgene.Ddon.GameServer/Characters/JobManager.cs b/Arrowgene.Ddon.GameServer/Characters/JobManager.cs index 31f80c793..c698d3127 100644 --- a/Arrowgene.Ddon.GameServer/Characters/JobManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/JobManager.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Data.Common; +using System.Data.Entity; using System.Linq; namespace Arrowgene.Ddon.GameServer.Characters @@ -694,9 +695,20 @@ public void RemoveAbility(IDatabase database, CharacterCommon character, byte sl database.ReplaceEquippedAbilities(character.CommonId, character.Job, equippedAbilities); } - public bool UnlockSecretAbility(CharacterCommon Character, SecretAbility secretAbilityType) + public void UnlockSecretAbility(GameClient client, CharacterCommon character, SecretAbility secretAbilityType) { - return _Server.Database.InsertSecretAbilityUnlock(Character.CommonId, secretAbilityType); + // MSG_GROUP_TYPE_GET_SECRET_ABILITY = 0x30, + if (_Server.Database.InsertSecretAbilityUnlock(character.CommonId, secretAbilityType)) + { + var newAbility = new Ability() + { + Job = 0, + AbilityId = (uint)secretAbilityType, + AbilityLv = 1 + }; + character.LearnedAbilities.Add(newAbility); + _Server.Database.InsertLearnedAbility(character.CommonId, newAbility); + } } public static CDataPresetAbilityParam MakePresetAbilityParam(CharacterCommon character, List abilities, byte presetNo, string presetName = "") diff --git a/Arrowgene.Ddon.GameServer/Characters/OrbUnlockManager.cs b/Arrowgene.Ddon.GameServer/Characters/OrbUnlockManager.cs index 90621c5de..09efa8d8b 100644 --- a/Arrowgene.Ddon.GameServer/Characters/OrbUnlockManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/OrbUnlockManager.cs @@ -150,7 +150,7 @@ private CDataOrbGainExtendParam UpdateExtendedParamData(GameClient client, Chara case OrbGainParamType.MainPawnLostRate: break; case OrbGainParamType.SecretAbility: - _JobManager.UnlockSecretAbility(character, upgrade.SecretAbility); + _JobManager.UnlockSecretAbility(client, character, upgrade.SecretAbility); break; case OrbGainParamType.Rim: _WalletManager.AddToWalletNtc(client, client.Character, WalletType.RiftPoints, upgrade.Amount); diff --git a/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs index d74105c5f..2c736cc3c 100644 --- a/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/ItemUseBagItemHandler.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.GameServer.Characters; namespace Arrowgene.Ddon.GameServer.Handler { @@ -16,9 +17,11 @@ public class ItemUseBagItemHandler : StructurePacketHandler(typeof(ItemUseBagItemHandler)); private static readonly StorageType DestinationStorageType = StorageType.ItemBagConsumable; + private DdonGameServer _Server; public ItemUseBagItemHandler(DdonGameServer server) : base(server) { + _Server = server; } public override void Handle(GameClient client, StructurePacket req) @@ -43,6 +46,11 @@ public override void Handle(GameClient client, StructurePacket Date: Mon, 9 Sep 2024 19:57:02 +0200 Subject: [PATCH 054/116] corrects stage id for hobolic cave --- Arrowgene.Ddon.GameServer/Characters/StageManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/StageManager.cs b/Arrowgene.Ddon.GameServer/Characters/StageManager.cs index 24f500900..d6681b275 100644 --- a/Arrowgene.Ddon.GameServer/Characters/StageManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/StageManager.cs @@ -50,7 +50,7 @@ public static StageNo ConvertIdToStageNo(StageId stageId) 61, // Golden Tankard Inn 66, // Gritten Fort 78, // Pawn Cathedral - 98, // Hobolic Cave + 95, // Hobolic Cave 137, // Mysree Grove Shrine 139, // Zandora Wastelands Shrine 141, // Breya Coast (Summer Event Hub Area) From d840b1b43732ada35af5c227b9ca451aa8e70e33 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 9 Sep 2024 19:24:24 -0400 Subject: [PATCH 055/116] Add innerwear to BBM loot Added shirts and pants to BBM loot pool. --- .../Files/Assets/BitterblackMaze.json | 631 ++++++++++++------ 1 file changed, 444 insertions(+), 187 deletions(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/BitterblackMaze.json b/Arrowgene.Ddon.Shared/Files/Assets/BitterblackMaze.json index 3c0198ecc..5f90b0203 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/BitterblackMaze.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/BitterblackMaze.json @@ -1060,85 +1060,202 @@ "chest_loot": { "other": { "low_quality": [ - 492, - 2104, - 953, - 2565, - 955, - 2567, - 956, - 2568, - 957, - 2569, - 958, - 2570, - 959, - 2571, - 960, - 2572, - 8524, - 9604, - 9605, - 9609, - 9610, - 9614, - 9615, - 9619, - 9620, - 9624, - 9625, - 9629, - 9630, - 9634, - 9635, - 9639, - 9640, - 9644, - 9645, - 10442, - 13596, - 17956, - 17957, - 17958, - 961, - 2573, - 962, - 2574, - 965, - 2577, - 2578, - 968, - 2580, - 8528, - 8529, - 9679, - 9680, - 9684, - 9685, - 9689, - 9690, - 9694, - 9695, - 9699, - 9700, - 9704, - 9705, - 9709, - 9710, - 9715, - 9719, - 9720, - 9724, - 9725, - 9729, - 9730, - 9734, - 9735, - 10477, - 13611, - 21339, - 21341, - 21343, + 496, + 2108, + 501, + 2113, + 502, + 2114, + 503, + 2115, + 518, + 2130, + 974, + 2586, + 975, + 2587, + 976, + 2588, + 977, + 2589, + 979, + 2591, + 980, + 2592, + 981, + 2593, + 982, + 2594, + 983, + 2595, + 984, + 2596, + 985, + 2597, + 987, + 2599, + 988, + 2600, + 989, + 2601, + 991, + 2603, + 992, + 2604, + 1025, + 2637, + 8548, + 8549, + 8553, + 8554, + 8558, + 8559, + 8563, + 8564, + 8573, + 8574, + 8583, + 8584, + 8588, + 8589, + 8593, + 8594, + 8598, + 8599, + 8603, + 8604, + 8608, + 8609, + 8618, + 8619, + 8623, + 8624, + 8628, + 8629, + 8633, + 8634, + 8638, + 8639, + 8648, + 8649, + 9900, + 10518, + 10519, + 13600, + 13601, + 14151, + 14153, + 14155, + 14157, + 14159, + 14161, + 17954, + 19135, + 19136, + 19137, + 19138, + 19139, + 19140, + 19605, + 19606, + 19609, + 19610, + 507, + 2119, + 516, + 2128, + 519, + 2131, + 677, + 2289, + 941, + 2553, + 993, + 2605, + 994, + 2606, + 996, + 2608, + 997, + 2609, + 998, + 2610, + 1000, + 2612, + 1001, + 2613, + 1002, + 2614, + 1003, + 2615, + 1004, + 2616, + 1005, + 2617, + 1006, + 2618, + 8673, + 8674, + 8678, + 8679, + 8683, + 8684, + 8688, + 8689, + 8693, + 8694, + 8698, + 8699, + 8703, + 8704, + 8708, + 8709, + 8713, + 8714, + 8718, + 8719, + 8728, + 8729, + 8733, + 8734, + 8738, + 8739, + 8743, + 8744, + 8748, + 8749, + 8753, + 8754, + 8758, + 8759, + 8773, + 8774, + 8778, + 8779, + 9674, + 9675, + 9901, + 10496, + 10497, + 13615, + 13616, + 14152, + 14154, + 14156, + 14158, + 14160, + 14162, + 17955, + 19141, + 19142, + 19143, + 19144, + 19145, + 19146, + 19607, + 19608, + 19611, + 19612, 486, 2098, 515, @@ -1249,114 +1366,254 @@ 13836 ], "high_quality": [ - 3127, - 4150, - 5173, - 3588, - 4611, - 5634, - 3590, - 4613, - 5636, - 3591, - 4614, - 5637, - 3592, - 4615, - 5638, - 3593, - 4616, - 5639, - 3594, - 4617, - 5640, - 3595, - 4618, - 5641, - 8526, - 9606, - 9607, - 9608, - 9611, - 9612, - 9613, - 9616, - 9617, - 9618, - 9621, - 9622, - 9623, - 9626, - 9627, - 9628, - 9631, - 9632, - 9633, - 9636, - 9637, - 9638, - 9641, - 9642, - 9643, - 9646, - 9647, - 9648, - 10444, - 13597, - 13599, - 3596, - 4619, - 5642, - 3597, - 4620, - 5643, - 3600, - 4623, - 5646, - 4624, - 3603, - 4626, - 5649, - 8530, - 8531, - 8532, - 9681, - 9682, - 9683, - 9686, - 9687, - 9688, - 9691, - 9692, - 9693, - 9696, - 9697, - 9698, - 9701, - 9702, - 9703, - 9706, - 9707, - 9708, - 9711, - 9712, - 9713, - 9717, - 9721, - 9722, - 9723, - 9726, - 9727, - 9728, - 9731, - 9732, - 9733, - 9736, - 9737, - 9738, - 10479, - 13612, - 13614, + 3131, + 4154, + 5177, + 3136, + 4159, + 5182, + 3137, + 4160, + 5183, + 3138, + 4161, + 5184, + 3153, + 4176, + 5199, + 3609, + 4632, + 5655, + 3610, + 4633, + 5656, + 3611, + 4634, + 5657, + 3612, + 4635, + 5658, + 3614, + 4637, + 5660, + 3615, + 4638, + 5661, + 3616, + 4639, + 5662, + 3617, + 4640, + 5663, + 3618, + 4641, + 5664, + 3619, + 4642, + 5665, + 3620, + 4643, + 5666, + 3622, + 4645, + 5668, + 3623, + 4646, + 5669, + 3624, + 4647, + 5670, + 3626, + 4649, + 5672, + 3627, + 4650, + 5673, + 3660, + 4683, + 5706, + 8550, + 8551, + 8552, + 8555, + 8556, + 8557, + 8560, + 8561, + 8562, + 8565, + 8566, + 8567, + 8575, + 8576, + 8577, + 8585, + 8586, + 8587, + 8590, + 8591, + 8592, + 8595, + 8596, + 8597, + 8600, + 8601, + 8602, + 8605, + 8606, + 8607, + 8610, + 8611, + 8612, + 8620, + 8621, + 8622, + 8625, + 8626, + 8627, + 8630, + 8631, + 8632, + 8635, + 8636, + 8637, + 8640, + 8641, + 8642, + 8650, + 8651, + 8652, + 10520, + 10521, + 10522, + 13602, + 13603, + 13604, + 19501, + 19503, + 20436, + 20438, + 3142, + 4165, + 5188, + 3151, + 4174, + 5197, + 3154, + 4177, + 5200, + 3312, + 4335, + 5358, + 3576, + 4599, + 5622, + 3628, + 4651, + 5674, + 3629, + 4652, + 5675, + 3631, + 4654, + 5677, + 3632, + 4655, + 5678, + 3633, + 4656, + 5679, + 3635, + 4658, + 5681, + 3636, + 4659, + 5682, + 3637, + 4660, + 5683, + 3638, + 4661, + 5684, + 3639, + 4662, + 5685, + 3640, + 4663, + 5686, + 3641, + 4664, + 5687, + 8675, + 8676, + 8677, + 8680, + 8681, + 8682, + 8685, + 8686, + 8687, + 8690, + 8691, + 8692, + 8695, + 8696, + 8697, + 8700, + 8701, + 8702, + 8705, + 8706, + 8707, + 8710, + 8711, + 8712, + 8715, + 8716, + 8717, + 8720, + 8721, + 8722, + 8730, + 8731, + 8732, + 8735, + 8736, + 8737, + 8740, + 8741, + 8742, + 8745, + 8746, + 8747, + 8750, + 8751, + 8752, + 8755, + 8756, + 8757, + 8760, + 8761, + 8762, + 8775, + 8776, + 8777, + 8780, + 8781, + 8782, + 9676, + 9677, + 9678, + 10498, + 10499, + 10500, + 13617, + 13618, + 13619, + 19502, + 19504, + 20437, + 20439, 3121, 4144, 5167, From d826a9c197f3147fc7ff7bcb8e9b0efd80d1d7a3 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 7 Sep 2024 15:43:25 -0700 Subject: [PATCH 056/116] Fix DC and DB entry corruption for legacy pawns changing jobs to ones they don't have an entry for. --- Arrowgene.Ddon.Database/IDatabase.cs | 2 +- .../Sql/Core/DdonSqlDbCharacterCommon.cs | 2 +- .../Sql/Core/DdonSqlDbCharacterJobData.cs | 28 +++++++++++-------- .../Characters/JobManager.cs | 2 +- .../Database/DatabaseMigratorTest.cs | 2 +- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Arrowgene.Ddon.Database/IDatabase.cs b/Arrowgene.Ddon.Database/IDatabase.cs index 980c9d60d..4784cc5a5 100644 --- a/Arrowgene.Ddon.Database/IDatabase.cs +++ b/Arrowgene.Ddon.Database/IDatabase.cs @@ -140,7 +140,7 @@ CDataPawnSearchParameter searchParams bool DeleteSpSkill(uint pawnId, JobId job, byte spSkillId); // CharacterJobData - bool ReplaceCharacterJobData(uint commonId, CDataCharacterJobData replacedCharacterJobData); + bool ReplaceCharacterJobData(uint commonId, CDataCharacterJobData replacedCharacterJobData, DbConnection? connectionIn = null); bool UpdateCharacterJobData(uint commonId, CDataCharacterJobData updatedCharacterJobData); // Wallet Points diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs index 10bf2a9d1..4075ab162 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs @@ -259,7 +259,7 @@ private void StoreCharacterCommonData(TCon conn, CharacterCommon common) { foreach(CDataCharacterJobData characterJobData in common.CharacterJobDataList) { - ReplaceCharacterJobData(conn, common.CommonId, characterJobData); + ReplaceCharacterJobData(common.CommonId, characterJobData, conn); } foreach(CDataNormalSkillParam normalSkillParam in common.LearnedNormalSkills) diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs index ff8a25efd..3f8d24c48 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using Arrowgene.Ddon.Database.Model; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; @@ -36,21 +37,24 @@ public abstract partial class DdonSqlDb : SqlDb Date: Mon, 9 Sep 2024 17:08:23 -0700 Subject: [PATCH 057/116] Make A Servant's Pledge "SoloWithPawns" to prevent people from sneaking into the pawn creation screen earlier than they should. --- .../Files/Assets/quests/q00000026.json | 355 +++++++++++------- 1 file changed, 228 insertions(+), 127 deletions(-) diff --git a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000026.json b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000026.json index 273070d2e..b95f05832 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000026.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/quests/q00000026.json @@ -1,150 +1,251 @@ { - "state_machine": "GenericStateMachine", - "type": "Main", - "comment": "A Servant's Pledge", - "quest_id": 26, - "next_quest": 25, - "base_level": 6, - "minimum_item_rank": 0, - "discoverable": true, - "order_conditions": [], - "rewards": [ - { - "type": "exp", - "amount": 2350 - }, + "state_machine": "GenericStateMachine", + "type": "Main", + "comment": "A Servant's Pledge", + "quest_id": 26, + "next_quest": 25, + "base_level": 6, + "minimum_item_rank": 0, + "discoverable": true, + "order_conditions": [ + {"type": "SoloWithPawns"} + ], + "rewards": [ + { + "type": "exp", + "amount": 2350 + }, + { + "type": "wallet", + "wallet_type": "Gold", + "amount": 1500 + }, + { + "type": "wallet", + "wallet_type": "RiftPoints", + "amount": 200 + }, + { + "type": "fixed", + "loot_pool": [ + { + "item_id": 514, + "num": 1 + } + ] + }, + { + "type": "select", + "loot_pool": [ { - "type": "wallet", - "wallet_type": "Gold", - "amount": 1500 + "item_id": 427, + "num": 1 }, { - "type": "wallet", - "wallet_type": "RiftPoints", - "amount": 200 + "item_id": 459, + "num": 1 }, { - "type": "fixed", - "loot_pool": [ - { - "item_id": 514, - "num": 1 - } - ] + "item_id": 475, + "num": 1 }, { - "type": "select", - "loot_pool": [ - { - "item_id": 427, - "num": 1 - }, - { - "item_id": 459, - "num": 1 - }, - { - "item_id": 475, - "num": 1 - }, - { - "item_id": 443, - "num": 1 - } - ] + "item_id": 443, + "num": 1 } - ], - "enemy_groups": [], - "blocks": [ - { - "type": "NpcTalkAndOrder", - "stage_id": { - "id": 78, - "group_id": 1 - }, - "flags": [ - {"type": "QstLayout", "action": "Set", "value": 273}, - {"type": "QstLayout", "action": "Set", "value": 973, "comment": "Pawn Dungeon Entrance (wall)"}, - {"type": "WorldManageLayout", "action": "Set", "value": 1215, "quest_id": 70000001, "comment": "Mysial"}, - {"type": "WorldManageLayout", "action": "Set", "value": 1218, "quest_id": 70000001, "comment": "Leo"}, - {"type": "WorldManageLayout", "action": "Set", "value": 1219, "quest_id": 70000001, "comment": "Iris"}, - {"type": "WorldManageLayout", "action": "Clear", "value": 7390, "quest_id": 70032001, "comment": "The White Dragon (Full)"}, - {"type": "WorldManageLayout", "action": "Set", "value": 1293, "quest_id": 70000001, "comment": "The White Dragon (Dead)"}, - {"type": "WorldManageLayout", "action": "Clear", "value": 1262, "quest_id": 70000001, "comment": "Pawn Dungeon Entrance (back)"} - ], - "npc_id": "Alvar", - "message_id": 10904 + ] + } + ], + "enemy_groups": [], + "blocks": [ + { + "type": "NpcTalkAndOrder", + "stage_id": { + "id": 78, + "group_id": 1 + }, + "flags": [ + { + "type": "QstLayout", + "action": "Set", + "value": 273 + }, + { + "type": "QstLayout", + "action": "Set", + "value": 973, + "comment": "Pawn Dungeon Entrance (wall)" + }, + { + "type": "WorldManageLayout", + "action": "Set", + "value": 1215, + "quest_id": 70000001, + "comment": "Mysial" }, { - "type": "CollectItem", - "stage_id": { - "id": 142, - "group_id": 1, - "layer_no": 1 - }, - "announce_type": "Accept", - "flags": [ - {"type": "QstLayout", "action": "Clear", "value": 973, "comment": "Pawn Dungeon Entrance (Wall)"}, - {"type": "QstLayout", "action": "Set", "value": 974, "comment": "Pawn Dungeon Entrance (Quest)"}, - {"type": "QstLayout", "action": "Set", "value": 898, "comment": "Glowing Point for Quest"} - ] + "type": "WorldManageLayout", + "action": "Set", + "value": 1218, + "quest_id": 70000001, + "comment": "Leo" }, { - "type": "TalkToNpc", - "checkpoint": true, - "announce_type": "Update", - "stage_id": { - "id": 78, - "group_id": 1 - }, - "hand_items": [ - {"id": 1026, "amount": 1} - ], - "flags": [ - {"type": "QstLayout", "action": "Clear", "value": 898}, - {"type": "QstLayout", "action": "Set", "value": 973, "comment": "Pawn Dungeon Entrance (wall)"}, - {"type": "QstLayout", "action": "Set", "value": 974, "comment": "Pawn Dungeon Entrance (Quest)"} - ], - "npc_id": "Alvar", - "message_id": 10909 + "type": "WorldManageLayout", + "action": "Set", + "value": 1219, + "quest_id": 70000001, + "comment": "Iris" + }, + { + "type": "WorldManageLayout", + "action": "Clear", + "value": 7390, + "quest_id": 70032001, + "comment": "The White Dragon (Full)" + }, + { + "type": "WorldManageLayout", + "action": "Set", + "value": 1293, + "quest_id": 70000001, + "comment": "The White Dragon (Dead)" + }, + { + "type": "WorldManageLayout", + "action": "Clear", + "value": 1262, + "quest_id": 70000001, + "comment": "Pawn Dungeon Entrance (back)" + } + ], + "npc_id": "Alvar", + "message_id": 10904 + }, + { + "type": "CollectItem", + "stage_id": { + "id": 142, + "group_id": 1, + "layer_no": 1 + }, + "announce_type": "Accept", + "flags": [ + { + "type": "QstLayout", + "action": "Clear", + "value": 973, + "comment": "Pawn Dungeon Entrance (Wall)" }, { - "type": "TalkToNpc", - "checkpoint": true, - "announce_type": "Update", - "stage_id": { - "id": 3, - "group_id": 1 - }, - "npc_id": "Mysial0", - "message_id": 10915 + "type": "QstLayout", + "action": "Set", + "value": 974, + "comment": "Pawn Dungeon Entrance (Quest)" }, { - "type": "Raw", - "check_commands": [ - {"type": "StageNo", "Param1": 160} - ], - "result_commands": [ - {"type": "StageJump", "Param1": 160} - ] + "type": "QstLayout", + "action": "Set", + "value": 898, + "comment": "Glowing Point for Quest" + } + ] + }, + { + "type": "TalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 78, + "group_id": 1 + }, + "hand_items": [ + { + "id": 1026, + "amount": 1 + } + ], + "flags": [ + { + "type": "QstLayout", + "action": "Clear", + "value": 898 }, { - "type": "Raw", - "check_commands": [ - {"type": "StageNo", "Param1": 201} - ] + "type": "QstLayout", + "action": "Set", + "value": 973, + "comment": "Pawn Dungeon Entrance (wall)" }, { - "type": "Raw", - "consume_items": [ - {"id": 1026, "amount": 1} - ], - "check_commands": [ - {"type": "EventEnd", "Param1": 201, "Param2": 10} - ], - "result_commands": [ - {"type": "EventExec", "Param1": 201, "Param2": 10, "Param3": 0, "Param4": 0} - ] + "type": "QstLayout", + "action": "Set", + "value": 974, + "comment": "Pawn Dungeon Entrance (Quest)" + } + ], + "npc_id": "Alvar", + "message_id": 10909 + }, + { + "type": "TalkToNpc", + "checkpoint": true, + "announce_type": "Update", + "stage_id": { + "id": 3, + "group_id": 1 + }, + "npc_id": "Mysial0", + "message_id": 10915 + }, + { + "type": "Raw", + "check_commands": [ + { + "type": "StageNo", + "Param1": 160 + } + ], + "result_commands": [ + { + "type": "StageJump", + "Param1": 160 + } + ] + }, + { + "type": "Raw", + "check_commands": [ + { + "type": "StageNo", + "Param1": 201 + } + ] + }, + { + "type": "Raw", + "consume_items": [ + { + "id": 1026, + "amount": 1 + } + ], + "check_commands": [ + { + "type": "EventEnd", + "Param1": 201, + "Param2": 10 + } + ], + "result_commands": [ + { + "type": "EventExec", + "Param1": 201, + "Param2": 10, + "Param3": 0, + "Param4": 0 } - ] + ] + } + ] } From fe6d390d22cbbceb974a403d57d81f7379b1aa40 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 9 Sep 2024 23:45:55 -0400 Subject: [PATCH 058/116] feat: Enable "Unlimited revive refills" course effect - Added support for unlimited revives in the course manager. - Renamed "Arrowgene QOL Course" to "Arrowgene Blessing". - Added unlimited revive effect to "Arrowgene Blessing". - Fixed issue where ended course still shows up. - Added support for upcomming course effect to report "activating". --- .../Characters/GpCourseManager.cs | 16 +++++++++++++++ ...CharacterGetReviveChargeableTimeHandler.cs | 8 ++++++-- .../Handler/GetCharacterListHandler.cs | 20 +++++++++++++++++-- .../Files/Assets/GpCourseInfo.json | 6 +++--- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/GpCourseManager.cs b/Arrowgene.Ddon.GameServer/Characters/GpCourseManager.cs index b616e63be..b430dbd0d 100644 --- a/Arrowgene.Ddon.GameServer/Characters/GpCourseManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/GpCourseManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Threading; @@ -33,6 +34,7 @@ internal class CourseBonus public double PawnCraftBonus = 0.0; public bool DisablePartyExpAdjustment = false; public double EnemyBloodOrbMultiplier = 0.0; + public bool InfiniteRevive = false; }; private void ApplyCourseEffects(uint courseId) @@ -68,6 +70,9 @@ private void ApplyCourseEffects(uint courseId) case GPCourseId.BloodOrbUp: _CourseBonus.EnemyBloodOrbMultiplier += (effect.Param0 / 100.0); break; + case GPCourseId.InfiniteRevive: + _CourseBonus.InfiniteRevive = true; + break; } } } @@ -106,6 +111,9 @@ private void RemoveCourseEffects(uint courseId) case GPCourseId.BloodOrbUp: _CourseBonus.EnemyBloodOrbMultiplier -= (effect.Param0 / 100.0); break; + case GPCourseId.InfiniteRevive: + _CourseBonus.InfiniteRevive = false; + break; } } } @@ -234,5 +242,13 @@ public double EnemyBloodOrbBonus() return _CourseBonus.EnemyBloodOrbMultiplier; } } + + public bool InfiniteReviveRefresh() + { + lock (_CourseBonus) + { + return _CourseBonus.InfiniteRevive; + } + } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/CharacterGetReviveChargeableTimeHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CharacterGetReviveChargeableTimeHandler.cs index 34307caf3..9668a80ef 100644 --- a/Arrowgene.Ddon.GameServer/Handler/CharacterGetReviveChargeableTimeHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/CharacterGetReviveChargeableTimeHandler.cs @@ -22,7 +22,11 @@ public override void Handle(GameClient client, StructurePacket ValidCourses = new List(); foreach (var ValidCourse in _AssetRepo.GPCourseInfoAsset.ValidCourses) { + if (now > ValidCourse.Value.EndTime) + { + continue; + } + CDataGPCourseValid cDataGPCourseValid = new CDataGPCourseValid() { Id = c.CharacterId, CourseId = ValidCourse.Value.Id, NameA = _AssetRepo.GPCourseInfoAsset.Courses[ValidCourse.Value.Id].Name, // Course Name NameB = _AssetRepo.GPCourseInfoAsset.Courses[ValidCourse.Value.Id].IconPath, // Link to a icon - StartTime = ValidCourse.Value.StartTime, - EndTime = ValidCourse.Value.EndTime, }; + if ((now >= ValidCourse.Value.StartTime) && (now <= ValidCourse.Value.EndTime)) + { + cDataGPCourseValid.StartTime = ValidCourse.Value.StartTime; + cDataGPCourseValid.EndTime = ValidCourse.Value.EndTime; + } + else + { + cDataGPCourseValid.StartTime = ValidCourse.Value.StartTime; + } + ValidCourses.Add(cDataGPCourseValid); } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/GpCourseInfo.json b/Arrowgene.Ddon.Shared/Files/Assets/GpCourseInfo.json index 0ef8f7a95..3f639efe3 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/GpCourseInfo.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/GpCourseInfo.json @@ -226,16 +226,16 @@ }, { "id": 17, - "name": "Arrowgene QOL Course", + "name": "Arrowgene Blessing", "comment": "Example of custom made course effect.", "icon_path": "", - "description": "Allows item boxes to be used outside the main city, enable expanded storage tabs and allows craft items to be consumed from the storage box.", + "description": "Allows players to use infinite revive refresh, item boxes to be used outside the main city, enable expanded storage tabs and allows craft items to be consumed from the storage box.", "url": "", "target": 0, "priority_grp": 100, "priority_same_time": 70, "announce_type": 2, - "effects": [ 2, 3, 69, 216, 217, 128 ] + "effects": [ 1, 2, 3, 69, 216, 217, 128 ] }, { "id": 18, From c0dfd321c42f9b6d396718980a65158dd709eab4 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 11 Sep 2024 18:22:31 -0400 Subject: [PATCH 059/116] enhancement: Make BBM Loot more configurable - Moved chest trash loot into JSON - Moved rare chest loot into JSON - Added the ability to configure the chance of getting jewlery from the boss chest. - Added the ability to configure the chance of getting rare armor from the boss chest. - Added the ability to configure the gold, silver and red marks rewarded on each stage. --- .../Characters/BitterblackMazeManager.cs | 94 ++++++++++--------- .../Handler/QuestGetPriorityQuestHandler.cs | 5 + .../Party/PartyQuestState.cs | 7 ++ .../Asset/BitterblackMazeAsset.cs | 11 ++- .../BitterblackMazeAssetDeserializer.cs | 21 ++++- .../Files/Assets/BitterblackMaze.json | 63 +++++++++++-- 6 files changed, 147 insertions(+), 54 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/BitterblackMazeManager.cs b/Arrowgene.Ddon.GameServer/Characters/BitterblackMazeManager.cs index 5367ed3fa..fc01151e7 100644 --- a/Arrowgene.Ddon.GameServer/Characters/BitterblackMazeManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/BitterblackMazeManager.cs @@ -1,5 +1,6 @@ using Arrowgene.Ddon.Database; using Arrowgene.Ddon.Server.Network; +using Arrowgene.Ddon.Shared.Asset; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; @@ -141,9 +142,10 @@ public static void HandleTierClear(DdonGameServer server, GameClient client, Cha var rewards = server.Database.SelectBBMRewards(character.CharacterId); // TODO: handle BattleContentRewardBonus.Up (some sort of reward bonus) // TODO: Is there a reason we wouldn't get a reward here? - rewards.GoldMarks += 1; - rewards.SilverMarks += 5; - rewards.RedMarks += 15; + var marks = GetMarksForStage(server.AssetRepository.BitterblackMazeAsset, stageId); + rewards.GoldMarks += marks.Gold; + rewards.SilverMarks += marks.Silver; + rewards.RedMarks += marks.Red; server.Database.UpdateBBMRewards(character.CharacterId, rewards); // Update the situation information @@ -301,13 +303,14 @@ public static List RollChestLoot(DdonGameServer server, if (chestType == ChestType.Purple || chestType == ChestType.Bracelet || chestType == ChestType.Earring) { - uint rareItem = RollRareArmor(stageId); + uint rareItem = RollRareArmor(assets, stageId); if (rareItem > 0) { results.Add(new InstancedGatheringItem() { ItemId = rareItem, ItemNum = 1, + Quality = 1, }); } } @@ -319,12 +322,24 @@ public static List RollChestLoot(DdonGameServer server, var treasure = server.Database.SelectBBMContentTreasure(character.CharacterId).Where(x => x.ContentId == character.BbmProgress.ContentId).ToList(); if (treasure.Count == 0) { - uint itemId = (chestType == ChestType.Bracelet) ? BitterblackMazeManager.BitterblackBraceletItemId : BitterblackMazeManager.BitterblackEarringItemId; + uint itemId; + uint quality; + if (RollRewardChance(DetermineJewelryChance(assets, stageId))) + { + itemId = (chestType == ChestType.Bracelet) ? BitterblackMazeManager.BitterblackBraceletItemId : BitterblackMazeManager.BitterblackEarringItemId; + quality = 1; + } + else + { + var items = BitterblackMazeManager.SelectGearType(assets.HighQualityWeapons[jobId], DetermineEquipClass(assets.HighQualityArmors, jobId), assets.HighQualityOther); + itemId = BitterblackMazeManager.SelectGear(server, items, chestType, stageId); + quality = 0; + } results.Add(new InstancedGatheringItem() { ItemId = itemId, ItemNum = 1, - Quality = 1 + Quality = quality }); } } @@ -369,7 +384,7 @@ public static List RollChestLoot(DdonGameServer server, if (chestType != ChestType.Earring && chestType != ChestType.Bracelet) { - foreach (var item in gChestTrash) + foreach (var item in assets.ChestTrash) { if (Random.Shared.Next(5) < 4) { @@ -377,7 +392,7 @@ public static List RollChestLoot(DdonGameServer server, continue; } - uint numItems = (uint)Random.Shared.Next((int)(item.Item2 + 1)); + uint numItems = (uint)Random.Shared.Next((int)(item.Amount + 1)); if (numItems > 0) { // Stick consumable in the front of the list @@ -393,11 +408,11 @@ public static List RollChestLoot(DdonGameServer server, if (results.Count == 0) { // Stick something in the chest so it is not empty - var item = gChestTrash[Random.Shared.Next(gChestTrash.Count)]; - uint numItems = (uint)Random.Shared.Next((int)(item.Item2 + 1)); + var item = assets.ChestTrash[Random.Shared.Next(assets.ChestTrash.Count)]; + uint numItems = (uint)Random.Shared.Next((int)(item.Amount + 1)); results.Add(new InstancedGatheringItem() { - ItemId = item.Item1, + ItemId = item.ItemId, ItemNum = numItems > 0 ? numItems : 1 }); } @@ -451,12 +466,29 @@ private static (uint Min, uint Max) DetermineItemTier(DdonGameServer server, Che { case ChestType.Orange: case ChestType.Purple: + case ChestType.Bracelet: + case ChestType.Earring: return lootRange.SealedRange; default: return lootRange.NormalRange; } } + private static double DetermineRareChance(BitterblackMazeAsset assets, StageId stageId) + { + return assets.LootRanges[stageId.Id].RareChance; + } + + private static double DetermineJewelryChance(BitterblackMazeAsset assets, StageId stageId) + { + return assets.LootRanges[stageId.Id].JewelryChance; + } + + private static (uint Gold, uint Silver, uint Red) GetMarksForStage(BitterblackMazeAsset assets, StageId stageId) + { + return assets.LootRanges[stageId.Id].Marks; + } + private static uint SelectGear(DdonGameServer server, List items, ChestType chestType, StageId stageId) { if (items.Count == 0) @@ -487,48 +519,20 @@ private static uint SelectGear(DdonGameServer server, List items, ChestTyp return itemId; } - private static uint RollRareArmor(StageId stageId) + private static uint RollRareArmor(BitterblackMazeAsset assets, StageId stageId) { uint itemId = 0; - var dropTable = StageManager.IsBitterBlackMazeBossStageId(stageId) ? gRareRotundaDrops : gRareAbyssNormalDrops; - if (Random.Shared.Next(100) > 98) + var dropTable = StageManager.IsBitterBlackMazeBossStageId(stageId) ? assets.RotundaRare : assets.AbyssRare; + if (RollRewardChance(DetermineRareChance(assets, stageId))) { itemId = dropTable[Random.Shared.Next(dropTable.Count)]; } return itemId; } - private static readonly List<(uint, uint)> gChestTrash = new List<(uint, uint)>() - { - // ItemId, Max - (55, 5), // Lantern Kindling - (9361, 2), // Quality Gala Extract - (41, 1), // Panacea - (7552, 2), // Healing Elixer - }; - - private static readonly List gRareRotundaDrops = new List() + private static bool RollRewardChance(double chance) { - 21396, // カースドヘルム,Cursed Helm, - 21397, // カースドアーマー,Cursed Armor, - 21398, // カースドアーム,Cursed Arms, - 21399, // カースドキュイス,Cursed Cuisses, - 21400, // ケイオスマスク,Chaos Mask, - 21401, // ケイオスローブ,Chaos Robe, - 21402, // ケイオスグローブ,Chaos Gloves, - 21403, // ケイオスレッグス,Chaos Legs, - }; - - private static readonly List gRareAbyssNormalDrops = new List() - { - 24601, // プレアードヘルム,Pleiades Helm, - 24605, // ヴァルキリアヘルム,Valkyrian Helm, - 24609, // プレアードローブ,Pleiades Robe, - 24614, // ヴァルキリアアーマー,Valkyrian Armor, - 24615, // プレアードウェア,Pleiades Clothing, - 24616, // ヴァルキリアアーム,Valkyrian Arms, - 24617, // プレアードパンツ,Pleiades Pants, - 24618, // ヴァルキリアレッグス,Valkyrian Legs, - }; + return chance >= Random.Shared.NextDouble(); + } } } diff --git a/Arrowgene.Ddon.GameServer/Handler/QuestGetPriorityQuestHandler.cs b/Arrowgene.Ddon.GameServer/Handler/QuestGetPriorityQuestHandler.cs index d88f79a44..470da9619 100644 --- a/Arrowgene.Ddon.GameServer/Handler/QuestGetPriorityQuestHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/QuestGetPriorityQuestHandler.cs @@ -36,6 +36,11 @@ public override void Handle(GameClient client, IPacket packet) foreach (var questId in priorityQuests) { var quest = client.Party.QuestState.GetQuest(questId); + if (quest == null) + { + continue; + } + var questState = client.Party.QuestState.GetQuestState(quest); if (questState == null) { diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 60d2a7aef..527e6e29f 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -275,6 +275,13 @@ public void AddNewQuest(QuestId questId, uint step, bool questStarted) quest = GetQuest(questId); } + if (quest == null) + { + // Might be progress from removed quest (or one in development). + Logger.Error($"Unable to locate quest data for {questId}"); + return; + } + // If we are adding a new variant quest, then log the variant id for further reference if (quest.IsVariantQuest) { diff --git a/Arrowgene.Ddon.Shared/Asset/BitterblackMazeAsset.cs b/Arrowgene.Ddon.Shared/Asset/BitterblackMazeAsset.cs index 91fb05790..bdc4bbf95 100644 --- a/Arrowgene.Ddon.Shared/Asset/BitterblackMazeAsset.cs +++ b/Arrowgene.Ddon.Shared/Asset/BitterblackMazeAsset.cs @@ -24,15 +24,20 @@ public BitterblackMazeAsset() HighQualityArmors = new Dictionary>(); LowQualityOther = new List(); HighQualityOther = new List(); + RotundaRare = new List(); + AbyssRare = new List(); + ChestTrash = new List<(uint ItemId, uint Amount)>(); } public class LootRange { public (uint Min, uint Max) NormalRange; public (uint Min, uint Max) SealedRange; + public double RareChance { get; set; } + public double JewelryChance { get; set; } + public (uint Gold, uint Silver, uint Red) Marks { get; set; } } - public Dictionary Stages { get; set; } public Dictionary LootRanges { get; set; } public Dictionary>> StarterEquipment { get; set; } @@ -48,6 +53,10 @@ public class LootRange public List LowQualityOther { get; set; } public List HighQualityOther { get; set; } + public List RotundaRare { get; set; } + public List AbyssRare { get; set; } + public List<(uint ItemId, uint Amount)> ChestTrash { get; set; } + public Dictionary>> GenerateStarterEquipment() { var result = new Dictionary>>(); diff --git a/Arrowgene.Ddon.Shared/AssetReader/BitterblackMazeAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/BitterblackMazeAssetDeserializer.cs index 04bbd4a8e..4f21363db 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/BitterblackMazeAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/BitterblackMazeAssetDeserializer.cs @@ -73,7 +73,10 @@ public BitterblackMazeAsset ReadPath(string path) var record = new LootRange() { NormalRange = (lootRange.GetProperty("normal").GetProperty("min").GetUInt32(), lootRange.GetProperty("normal").GetProperty("max").GetUInt32()), - SealedRange = (lootRange.GetProperty("sealed").GetProperty("min").GetUInt32(), lootRange.GetProperty("sealed").GetProperty("max").GetUInt32()) + SealedRange = (lootRange.GetProperty("sealed").GetProperty("min").GetUInt32(), lootRange.GetProperty("sealed").GetProperty("max").GetUInt32()), + JewelryChance = lootRange.GetProperty("jewelry_chance").GetDouble(), + RareChance = lootRange.GetProperty("rare_chance").GetDouble(), + Marks = (lootRange.GetProperty("marks").GetProperty("gold").GetUInt32(), lootRange.GetProperty("marks").GetProperty("silver").GetUInt32(), lootRange.GetProperty("marks").GetProperty("red").GetUInt32()) }; foreach (var id in lootRange.GetProperty("stage_ids").EnumerateArray()) @@ -189,6 +192,22 @@ public BitterblackMazeAsset ReadPath(string path) } } + var jRareLoot = document.RootElement.GetProperty("chest_loot").GetProperty("rare"); + foreach (var quality in jRareLoot.EnumerateObject()) + { + List items = quality.NameEquals("rotunda") ? asset.RotundaRare : asset.AbyssRare; + foreach (var itemId in quality.Value.EnumerateArray()) + { + items.Add(itemId.GetUInt32()); + } + } + + var jTrashLoot = document.RootElement.GetProperty("chest_loot").GetProperty("trash"); + foreach (var drop in jTrashLoot.EnumerateArray()) + { + asset.ChestTrash.Add((drop.GetProperty("id").GetUInt32(), drop.GetProperty("max_amount").GetUInt32())); + } + return asset; } diff --git a/Arrowgene.Ddon.Shared/Files/Assets/BitterblackMaze.json b/Arrowgene.Ddon.Shared/Files/Assets/BitterblackMaze.json index 5f90b0203..0d46f0a27 100644 --- a/Arrowgene.Ddon.Shared/Files/Assets/BitterblackMaze.json +++ b/Arrowgene.Ddon.Shared/Files/Assets/BitterblackMaze.json @@ -1016,48 +1016,97 @@ "loot_ranges": [ { "comment": "Rotunda 1", + "jewelry_chance": 1.0, + "rare_chance": 0.1, "stage_ids": [603, 610, 611, 612], "normal": {"min": 3, "max": 4}, - "sealed": {"min": 4, "max": 5} + "sealed": {"min": 4, "max": 5}, + "marks": {"gold": 1, "silver": 5, "red": 15} }, { "comment": "Rotunda 2", + "jewelry_chance": 1.0, + "rare_chance": 0.01, "stage_ids": [604, 614, 615, 616], "normal": {"min": 4, "max": 5}, - "sealed": {"min": 5, "max": 6} + "sealed": {"min": 5, "max": 6}, + "marks": {"gold": 1, "silver": 5, "red": 15} }, { "comment": "Rotunda 3", + "jewelry_chance": 1.0, + "rare_chance": 0.01, "stage_ids": [605, 617, 618, 619, 620, 621, 622], "normal": {"min": 6, "max": 10}, - "sealed": {"min": 7, "max": 10} + "sealed": {"min": 7, "max": 10}, + "marks": {"gold": 1, "silver": 5, "red": 15} }, { "comment": "Abyss 1", + "jewelry_chance": 1.0, + "rare_chance": 0.01, "stage_ids": [682, 686, 687, 688], "normal": {"min": 3, "max": 4}, - "sealed": {"min": 5, "max": 6} + "sealed": {"min": 5, "max": 6}, + "marks": {"gold": 1, "silver": 5, "red": 15} }, { "comment": "Abyss 2", + "jewelry_chance": 1.0, + "rare_chance": 0.01, "stage_ids": [683, 689, 690, 691], "normal": {"min": 5, "max": 6}, - "sealed": {"min": 6, "max": 7} + "sealed": {"min": 6, "max": 7}, + "marks": {"gold": 1, "silver": 5, "red": 15} }, { "comment": "Abyss 3", + "jewelry_chance": 1.0, + "rare_chance": 0.01, "stage_ids": [684, 692, 693, 694], "normal": {"min": 7, "max": 11}, - "sealed": {"min": 8, "max": 11} + "sealed": {"min": 8, "max": 11}, + "marks": {"gold": 1, "silver": 5, "red": 15} }, { "comment": "Abyss 4", + "jewelry_chance": 1.0, + "rare_chance": 0.01, "stage_ids": [685, 695, 696, 697, 715, 716, 717], "normal": {"min": 10, "max": 11}, - "sealed": {"min": 11, "max": 11} + "sealed": {"min": 11, "max": 11}, + "marks": {"gold": 1, "silver": 5, "red": 15} } ], "chest_loot": { + "rare": { + "rotunda": [ + 21396, + 21397, + 21398, + 21399, + 21400, + 21401, + 21402, + 21403 + ], + "abyss": [ + 24601, + 24605, + 24609, + 24614, + 24615, + 24616, + 24617, + 24618 + ] + }, + "trash": [ + {"id": 55, "max_amount": 5, "comment": "Lantern Kindling"}, + {"id": 9361, "max_amount": 2, "comment": "Quality Gala Extract"}, + {"id": 41, "max_amount": 1, "comment": "Panacea"}, + {"id": 7552, "max_amount": 2, "comment": "Healing Elixer"} + ], "other": { "low_quality": [ 496, From 5b200f1157dad9cf64af18783d769e0fbc19bf61 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 11 Sep 2024 19:11:15 -0400 Subject: [PATCH 060/116] feat: Allow quests to reward Job Points Added the ability for a quest to reward job points. Note that the job point reward will only show in the quest UI for EXM. --- .../Characters/ExpManager.cs | 29 ++++++++++++ .../Party/PartyQuestState.cs | 4 ++ .../Quests/GenericQuest.cs | 12 +++-- Arrowgene.Ddon.GameServer/Quests/Quest.cs | 2 +- Arrowgene.Ddon.Shared/Asset/QuestAsset.cs | 12 ++++- .../AssetReader/QuestAssetDeserializer.cs | 21 +++++++-- .../Entity/EntitySerializer.cs | 2 + .../PacketStructure/S2CJobPawnJobPointNtc.cs | 46 +++++++++++++++++++ .../S2CUpdateCharacterJobPointNtc.cs | 43 +++++++++++++++++ ...DataS2CEquipEnhancedGetPacksResUnk0Unk6.cs | 14 +++--- .../Structure/CDataTimeGainQuestList.cs | 8 ++-- Arrowgene.Ddon.Shared/Model/ExpType.cs | 2 +- 12 files changed, 172 insertions(+), 23 deletions(-) create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CJobPawnJobPointNtc.cs create mode 100644 Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CUpdateCharacterJobPointNtc.cs diff --git a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs index cefb67e97..4e36e55a6 100644 --- a/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/ExpManager.cs @@ -566,6 +566,35 @@ public void AddExp(GameClient client, CharacterCommon characterToAddExpTo, uint } } + public void AddJp(GameClient client, CharacterCommon characterToJpExpTo, uint gainedJp, RewardSource rewardType, QuestType questType = QuestType.All) + { + CDataCharacterJobData? activeCharacterJobData = characterToJpExpTo.ActiveCharacterJobData; + activeCharacterJobData.JobPoint += gainedJp; + + if (characterToJpExpTo is Character) + { + S2CUpdateCharacterJobPointNtc jpNtc = new S2CUpdateCharacterJobPointNtc(); + jpNtc.Job = characterToJpExpTo.Job; + jpNtc.AddJobPoint = gainedJp; + jpNtc.ExtraBonusJobPoint = 0; + jpNtc.TotalJobPoint = activeCharacterJobData.JobPoint; + client.Send(jpNtc); + } + else + { + S2CJobPawnJobPointNtc jpNtc = new S2CJobPawnJobPointNtc(); + jpNtc.PawnId = ((Pawn)characterToJpExpTo).PawnId; + jpNtc.Job = characterToJpExpTo.Job; + jpNtc.AddJobPoint = gainedJp; + jpNtc.ExtraBonusJobPoint = 0; + jpNtc.TotalJobPoint = activeCharacterJobData.JobPoint; + client.Send(jpNtc); + } + + // PERSIST CHANGES IN DB + _Server.Database.UpdateCharacterJobData(characterToJpExpTo.CommonId, activeCharacterJobData); + } + public void ResetExpData(GameClient client, CharacterCommon characterCommon) { foreach (var jobData in client.Character.CharacterJobDataList) diff --git a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs index 527e6e29f..dd64ad85f 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyQuestState.cs @@ -655,6 +655,10 @@ private void SendWalletRewards(DdonGameServer server, GameClient client, Quest q { server.ExpManager.AddExp(client, client.Character, expPoint.Reward, RewardSource.Quest, quest.QuestType); } + else if (expPoint.Type == ExpType.JobPoints) + { + server.ExpManager.AddJp(client, client.Character, expPoint.Reward, RewardSource.Quest, quest.QuestType); + } else if (expPoint.Type == ExpType.PlayPoints) { server.PPManager.AddPlayPoint(client, expPoint.Reward, type: 2); diff --git a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs index 285e77ee8..b9532cb2c 100644 --- a/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/GenericQuest.cs @@ -34,11 +34,15 @@ public static GenericQuest FromAsset(QuestAssetData questAsset) quest.StageId = questAsset.StageId; quest.MissionParams = questAsset.MissionParams; - quest.ExpRewards.Add(new CDataQuestExp() + + foreach (var pointReward in questAsset.PointRewards) { - Type = questAsset.ExpType, - Reward = questAsset.ExpReward - }); + quest.ExpRewards.Add(new CDataQuestExp() + { + Type = pointReward.ExpType, + Reward = pointReward.ExpReward + }); + } foreach (var walletReward in questAsset.RewardCurrency) { diff --git a/Arrowgene.Ddon.GameServer/Quests/Quest.cs b/Arrowgene.Ddon.GameServer/Quests/Quest.cs index 4094ca3fe..855329761 100644 --- a/Arrowgene.Ddon.GameServer/Quests/Quest.cs +++ b/Arrowgene.Ddon.GameServer/Quests/Quest.cs @@ -385,7 +385,7 @@ public virtual CDataTimeGainQuestList ToCDataTimeGainQuestList(uint step) { ItemId = reward.ItemId, Num = reward.Num, - Type = 12 // What does this type mean? + Type = 12 }); } diff --git a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs index b9e8b27b0..a97ddd325 100644 --- a/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs +++ b/Arrowgene.Ddon.Shared/Asset/QuestAsset.cs @@ -14,6 +14,12 @@ public QuestAsset() public List Quests { get; set; } } + public class PointReward + { + public ExpType ExpType { get; set; } + public uint ExpReward { get; set; } + } + public class QuestAssetData { public List Processes { get; set; } @@ -26,8 +32,9 @@ public class QuestAssetData public uint NewsImageId { get; set; } public ushort BaseLevel { get; set; } public byte MinimumItemRank { get; set; } - public ExpType ExpType { get; set; } - public uint ExpReward { get; set; } + + public List PointRewards { get; set; } + public bool Discoverable { get; set; } public List RewardItems; public List RewardCurrency; @@ -42,6 +49,7 @@ public class QuestAssetData public QuestAssetData() { Processes = new List(); + PointRewards = new List(); RewardItems = new List(); RewardCurrency = new List(); QuestLayoutFlags = new List(); diff --git a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs index a9a71c0f7..159f73a22 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/QuestAssetDeserializer.cs @@ -321,12 +321,25 @@ private void ParseRewards(QuestAssetData assetData, JsonElement quest) } break; case "exp": - assetData.ExpType = ExpType.ExperiencePoints; - assetData.ExpReward = reward.GetProperty("amount").GetUInt32(); + assetData.PointRewards.Add(new PointReward() + { + ExpType = ExpType.ExperiencePoints, + ExpReward = reward.GetProperty("amount").GetUInt32() + }); break; case "pp": - assetData.ExpType = ExpType.PlayPoints; - assetData.ExpReward = reward.GetProperty("amount").GetUInt32(); + assetData.PointRewards.Add(new PointReward() + { + ExpType = ExpType.PlayPoints, + ExpReward = reward.GetProperty("amount").GetUInt32() + }); + break; + case "jp": + assetData.PointRewards.Add(new PointReward() + { + ExpType = ExpType.JobPoints, + ExpReward = reward.GetProperty("amount").GetUInt32() + }); break; case "wallet": if (!Enum.TryParse(reward.GetProperty("wallet_type").GetString(), true, out WalletType walletType)) diff --git a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs index 756bc3192..7d1f23d9b 100644 --- a/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs +++ b/Arrowgene.Ddon.Shared/Entity/EntitySerializer.cs @@ -957,6 +957,8 @@ static EntitySerializer() Create(new S2CJobGetPlayPointListRes.Serializer()); Create(new S2CJobJobValueShopGetLineupRes.Serializer()); Create(new S2CJobJobValueShopBuyItemRes.Serializer()); + Create(new S2CUpdateCharacterJobPointNtc.Serializer()); + Create(new S2CJobPawnJobPointNtc.Serializer()); Create(new S2COrbDevoteGetReleaseOrbElementListRes.Serializer()); Create(new S2CJobOrbTreeGetJobOrbTreeStatusListRes.Serializer()); diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CJobPawnJobPointNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CJobPawnJobPointNtc.cs new file mode 100644 index 000000000..4bff5a514 --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CJobPawnJobPointNtc.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CJobPawnJobPointNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_JOB_PAWN_JOB_POINT_NTC; + + public S2CJobPawnJobPointNtc() + { + } + + public uint PawnId { get; set; } + public JobId Job { get; set; } + public uint AddJobPoint { get; set; } + public uint ExtraBonusJobPoint { get; set; } + public uint TotalJobPoint { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CJobPawnJobPointNtc obj) + { + WriteUInt32(buffer, obj.PawnId); + WriteByte(buffer, (byte)obj.Job); + WriteUInt32(buffer, obj.AddJobPoint); + WriteUInt32(buffer, obj.ExtraBonusJobPoint); + WriteUInt32(buffer, obj.TotalJobPoint); + } + + public override S2CJobPawnJobPointNtc Read(IBuffer buffer) + { + S2CJobPawnJobPointNtc obj = new S2CJobPawnJobPointNtc(); + obj.PawnId = ReadUInt32(buffer); + obj.Job = (JobId)ReadByte(buffer); + obj.AddJobPoint = ReadUInt32(buffer); + obj.ExtraBonusJobPoint = ReadUInt32(buffer); + obj.TotalJobPoint = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CUpdateCharacterJobPointNtc.cs b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CUpdateCharacterJobPointNtc.cs new file mode 100644 index 000000000..fc34d3a9a --- /dev/null +++ b/Arrowgene.Ddon.Shared/Entity/PacketStructure/S2CUpdateCharacterJobPointNtc.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Arrowgene.Buffers; +using Arrowgene.Ddon.Shared.Entity.Structure; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Network; + +namespace Arrowgene.Ddon.Shared.Entity.PacketStructure +{ + public class S2CUpdateCharacterJobPointNtc : IPacketStructure + { + public PacketId Id => PacketId.S2C_JOB_CHARACTER_JOB_POINT_NTC; + + public S2CUpdateCharacterJobPointNtc() + { + } + + public JobId Job { get; set; } + public uint AddJobPoint { get; set; } + public uint ExtraBonusJobPoint { get; set; } + public uint TotalJobPoint { get; set; } + + public class Serializer : PacketEntitySerializer + { + public override void Write(IBuffer buffer, S2CUpdateCharacterJobPointNtc obj) + { + WriteByte(buffer, (byte) obj.Job); + WriteUInt32(buffer, obj.AddJobPoint); + WriteUInt32(buffer, obj.ExtraBonusJobPoint); + WriteUInt32(buffer, obj.TotalJobPoint); + } + + public override S2CUpdateCharacterJobPointNtc Read(IBuffer buffer) + { + S2CUpdateCharacterJobPointNtc obj = new S2CUpdateCharacterJobPointNtc(); + obj.Job = (JobId) ReadByte(buffer); + obj.AddJobPoint = ReadUInt32(buffer); + obj.ExtraBonusJobPoint = ReadUInt32(buffer); + obj.TotalJobPoint = ReadUInt32(buffer); + return obj; + } + } + } +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataS2CEquipEnhancedGetPacksResUnk0Unk6.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataS2CEquipEnhancedGetPacksResUnk0Unk6.cs index 4d6b833eb..645b28850 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataS2CEquipEnhancedGetPacksResUnk0Unk6.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataS2CEquipEnhancedGetPacksResUnk0Unk6.cs @@ -4,24 +4,24 @@ namespace Arrowgene.Ddon.Shared.Entity.Structure { public class CDataS2CEquipEnhancedGetPacksResUnk0Unk6 { - public uint Unk0 { get; set; } - public ushort Unk1 { get; set; } + public uint ItemId { get; set; } + public ushort Num { get; set; } public class Serializer : EntitySerializer { public override void Write(IBuffer buffer, CDataS2CEquipEnhancedGetPacksResUnk0Unk6 obj) { - WriteUInt32(buffer, obj.Unk0); - WriteUInt16(buffer, obj.Unk1); + WriteUInt32(buffer, obj.ItemId); + WriteUInt16(buffer, obj.Num); } public override CDataS2CEquipEnhancedGetPacksResUnk0Unk6 Read(IBuffer buffer) { CDataS2CEquipEnhancedGetPacksResUnk0Unk6 obj = new CDataS2CEquipEnhancedGetPacksResUnk0Unk6(); - obj.Unk0 = ReadUInt32(buffer); - obj.Unk1 = ReadUInt16(buffer); + obj.ItemId = ReadUInt32(buffer); + obj.Num = ReadUInt16(buffer); return obj; } } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestList.cs b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestList.cs index 9bdf5ff13..2d86589a6 100644 --- a/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestList.cs +++ b/Arrowgene.Ddon.Shared/Entity/Structure/CDataTimeGainQuestList.cs @@ -12,7 +12,7 @@ public CDataTimeGainQuestList() Param = new CDataQuestList(); RewardItemDetailList = new List(); Restrictions = new CDataTimeGainQuestRestrictions(); - Unk2List = new List(); + RequiredItemsList = new List(); } public CDataQuestList Param { get; set; } @@ -25,7 +25,7 @@ public CDataTimeGainQuestList() public byte JoinPawnNum { get; set; } public List RewardItemDetailList { get; set; } public CDataTimeGainQuestRestrictions Restrictions { get; set; } - public List Unk2List { get; set; } + public List RequiredItemsList { get; set; } public class Serializer : EntitySerializer { @@ -41,7 +41,7 @@ public override void Write(IBuffer buffer, CDataTimeGainQuestList obj) WriteByte(buffer, obj.JoinPawnNum); WriteEntityList(buffer, obj.RewardItemDetailList); WriteEntity(buffer, obj.Restrictions); - WriteEntityList(buffer, obj.Unk2List); + WriteEntityList(buffer, obj.RequiredItemsList); } public override CDataTimeGainQuestList Read(IBuffer buffer) @@ -57,7 +57,7 @@ public override CDataTimeGainQuestList Read(IBuffer buffer) obj.JoinPawnNum = ReadByte(buffer); obj.RewardItemDetailList = ReadEntityList(buffer); obj.Restrictions = ReadEntity(buffer); - obj.Unk2List = ReadEntityList(buffer); + obj.RequiredItemsList = ReadEntityList(buffer); return obj; } } diff --git a/Arrowgene.Ddon.Shared/Model/ExpType.cs b/Arrowgene.Ddon.Shared/Model/ExpType.cs index 9c0680c0b..4e3161da3 100644 --- a/Arrowgene.Ddon.Shared/Model/ExpType.cs +++ b/Arrowgene.Ddon.Shared/Model/ExpType.cs @@ -3,7 +3,7 @@ namespace Arrowgene.Ddon.Shared.Model public enum ExpType : byte { ExperiencePoints = 1, - UnknownPoints1 = 2, + JobPoints = 2, PlayPoints = 3 // Reward for wild hunt quests } } From 59d180c398d7733ddc89ea57fa257fe69faed1c3 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 12 Sep 2024 19:59:28 -0400 Subject: [PATCH 061/116] fix: Fix issue where death rewards items when running away Add a check for the "IsNoBattleReward" in the enemy kill handler. --- .../Handler/InstanceEnemyKillHandler.cs | 3 +-- .../Entity/PacketStructure/C2SInstanceEnemyKillReq.cs | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index 065a70e5b..fe2eacc3a 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -39,7 +39,7 @@ public override void Handle(GameClient client, StructurePacket @@ -42,7 +42,7 @@ public override void Write(IBuffer buffer, C2SInstanceEnemyKillReq obj) WriteDouble(buffer, obj.DropPosX); WriteFloat(buffer, obj.DropPosY); WriteDouble(buffer, obj.DropPosZ); - WriteBool(buffer, obj.IsNoBattleReword); + WriteBool(buffer, obj.IsNoBattleReward); WriteBool(buffer, obj.Unk0); WriteUInt32(buffer, obj.RegionFlag); } @@ -56,7 +56,7 @@ public override C2SInstanceEnemyKillReq Read(IBuffer buffer) obj.DropPosX = ReadDouble(buffer); obj.DropPosY = ReadFloat(buffer); obj.DropPosZ = ReadDouble(buffer); - obj.IsNoBattleReword = ReadBool(buffer); + obj.IsNoBattleReward = ReadBool(buffer); obj.Unk0 = ReadBool(buffer); obj.RegionFlag = ReadUInt32(buffer); return obj; From 4902240226f2a0a0b085b1711c817afd5ed0f9db Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Sat, 7 Sep 2024 20:47:30 -0700 Subject: [PATCH 062/116] More tweaks to spawns/loot with regards to subgroups. --- .../Enemies/InstanceEnemyManager.cs | 11 +++++---- .../GatheringItems/InstanceDropItemManager.cs | 3 +-- .../Handler/InstanceEnemyKillHandler.cs | 1 - .../Handler/InstanceGetEnemySetListHandler.cs | 11 +++++---- .../InstanceAssetManager.cs | 24 +++++++++---------- .../EnemySpawnAssetDeserializer.cs | 7 ++++-- Arrowgene.Ddon.Shared/Model/Enemy.cs | 5 ++++ Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs | 2 -- 8 files changed, 35 insertions(+), 29 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs index fdeb9ad40..88e099d01 100644 --- a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs +++ b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs @@ -11,18 +11,19 @@ public class InstanceEnemyManager : InstanceAssetManager _CurrentSubgroup { get; set; } - private Dictionary> _EnemyData; + private Dictionary> _EnemyData; public InstanceEnemyManager(DdonGameServer server) : base() { _Server = server; _CurrentSubgroup = new Dictionary(); - _EnemyData = new Dictionary>(); + _EnemyData = new Dictionary>(); } - protected override List FetchAssetsFromRepository(StageId stage, byte subGroupId) + protected override List FetchAssetsFromRepository(StageId stage, byte setId) { - return _Server.AssetRepository.EnemySpawnAsset.Enemies.GetValueOrDefault((stage, subGroupId)) ?? new List(); + // SetId is not used here, because the enemy data structure is flat, but the interface demands we have it. + return _Server.AssetRepository.EnemySpawnAsset.Enemies.GetValueOrDefault((stage, (byte)0)) ?? new List(); } protected override List InstanceAssets(List originals) @@ -57,7 +58,7 @@ public void SetInstanceEnemy(StageId stageId, byte index, InstancedEnemy enemy) { if (!_EnemyData.ContainsKey(stageId)) { - _EnemyData[stageId] = new Dictionary(); + _EnemyData[stageId] = new Dictionary(); } if (!_EnemyData[stageId].ContainsKey(index)) diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs index 517babda9..d72c23e64 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs @@ -15,8 +15,7 @@ public InstanceDropItemManager(GameClient client) protected override List FetchAssetsFromRepository(StageId stage, uint setId) { - ushort currentSubGroup = _client.Party.InstanceEnemyManager.GetSubgroup(stage); - List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage, (byte)currentSubGroup); + List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage, 0); if(enemiesInSet != null && setId < enemiesInSet.Count) { Enemy enemy = enemiesInSet[(int) setId]; diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index fe2eacc3a..c3a784381 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -102,7 +102,6 @@ public override void Handle(GameClient client, StructurePacket group = client.Party.InstanceEnemyManager.GetInstancedEnemies(stageId); - bool groupDestroyed = group.Where(x => x.IsRequired).All(x => x.IsKilled); if (groupDestroyed) { diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs index d5ee2830f..0e03e464f 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetEnemySetListHandler.cs @@ -69,16 +69,17 @@ public override void Handle(GameClient client, StructurePacket new {Index, Enemy})) + foreach (var asset in client.Party.InstanceEnemyManager.GetAssets(stageId, 0) + .Where(x => x.Subgroup == subGroupId)) { response.EnemyList.Add(new CDataLayoutEnemyData() { - PositionIndex = (byte) asset.Index, - EnemyInfo = asset.Enemy.asCDataStageLayoutEnemyPresetEnemyInfoClient() + PositionIndex = asset.Index, + EnemyInfo = asset.asCDataStageLayoutEnemyPresetEnemyInfoClient() }); - client.Party.InstanceEnemyManager.SetInstanceEnemy(stageId, (byte) asset.Index, asset.Enemy); + client.Party.InstanceEnemyManager.SetInstanceEnemy(stageId, asset.Index, asset); - if (asset.Enemy.NotifyStrongEnemy) + if (asset.NotifyStrongEnemy) { notifyStrongEnemy = true; } diff --git a/Arrowgene.Ddon.GameServer/InstanceAssetManager.cs b/Arrowgene.Ddon.GameServer/InstanceAssetManager.cs index 16fcf98fa..0d0b0fe28 100644 --- a/Arrowgene.Ddon.GameServer/InstanceAssetManager.cs +++ b/Arrowgene.Ddon.GameServer/InstanceAssetManager.cs @@ -14,31 +14,31 @@ public InstanceAssetManager() private readonly Dictionary<(StageId, T1), List> _instancedAssetsDictionary; - public bool HasAssetsInstanced(CDataStageLayoutId stageLayoutId, T1 subGroupId) + public bool HasAssetsInstanced(CDataStageLayoutId stageLayoutId, T1 setId) { - return _instancedAssetsDictionary.ContainsKey((StageId.FromStageLayoutId(stageLayoutId), subGroupId)); + return _instancedAssetsDictionary.ContainsKey((StageId.FromStageLayoutId(stageLayoutId), setId)); } - public bool HasAssetsInstanced(StageId stageId, T1 subGroupId) + public bool HasAssetsInstanced(StageId stageId, T1 setId) { - return _instancedAssetsDictionary.ContainsKey((stageId, subGroupId)); + return _instancedAssetsDictionary.ContainsKey((stageId, setId)); } - public List GetAssets(CDataStageLayoutId stageLayoutId, T1 subGroupId) + public List GetAssets(CDataStageLayoutId stageLayoutId, T1 setId) { - return GetAssets(StageId.FromStageLayoutId(stageLayoutId), subGroupId); + return GetAssets(StageId.FromStageLayoutId(stageLayoutId), setId); } - public List GetAssets(StageId stageId, T1 subGroupId) + public List GetAssets(StageId stageId, T1 setId) { - if(!HasAssetsInstanced(stageId, subGroupId)) + if(!HasAssetsInstanced(stageId, setId)) { - List items = FetchAssetsFromRepository(stageId, subGroupId); + List items = FetchAssetsFromRepository(stageId, setId); List instancedAssets = InstanceAssets(items); - _instancedAssetsDictionary.Add((stageId, subGroupId), instancedAssets); + _instancedAssetsDictionary.Add((stageId, setId), instancedAssets); return instancedAssets; } - return _instancedAssetsDictionary[(stageId, subGroupId)]; + return _instancedAssetsDictionary[(stageId, setId)]; } public virtual void Clear() @@ -46,7 +46,7 @@ public virtual void Clear() _instancedAssetsDictionary.Clear(); } - protected abstract List FetchAssetsFromRepository(StageId stage, T1 subGroupId); + protected abstract List FetchAssetsFromRepository(StageId stage, T1 setId); protected abstract List InstanceAssets(List originals); } diff --git a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs index a86b538bd..2bdfff6a9 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs @@ -82,7 +82,7 @@ public EnemySpawnAsset ReadPath(string path) row[enemySchemaIndexes["GroupId"]].GetUInt32() ); byte subGroupId = row[enemySchemaIndexes["SubGroupId"]].GetByte(); - List enemies = asset.Enemies.GetValueOrDefault((layoutId, subGroupId)) ?? new List(); + List enemies = asset.Enemies.GetValueOrDefault((layoutId, (byte)0)) ?? new List(); Enemy enemy = new Enemy() { EnemyId = ParseHexUInt(row[enemySchemaIndexes["EnemyId"]].GetString()), @@ -105,6 +105,9 @@ public EnemySpawnAsset ReadPath(string path) BloodOrbs = row[enemySchemaIndexes["BloodOrbs"]].GetUInt32(), HighOrbs = row[enemySchemaIndexes["HighOrbs"]].GetUInt32(), Experience = row[enemySchemaIndexes["Experience"]].GetUInt32(), + + Index = (byte)enemies.Count, + Subgroup = subGroupId, }; //checking if the file has spawntime, if yes we convert the time and pass it along to enemy.cs @@ -130,7 +133,7 @@ public EnemySpawnAsset ReadPath(string path) enemy.DropsTable = asset.DropsTables[(uint) dropsTableId]; } enemies.Add(enemy); - asset.Enemies[(layoutId, subGroupId)] = enemies; + asset.Enemies[(layoutId, 0)] = enemies; } return asset; diff --git a/Arrowgene.Ddon.Shared/Model/Enemy.cs b/Arrowgene.Ddon.Shared/Model/Enemy.cs index 5334183b8..d9b15827b 100644 --- a/Arrowgene.Ddon.Shared/Model/Enemy.cs +++ b/Arrowgene.Ddon.Shared/Model/Enemy.cs @@ -40,6 +40,8 @@ public Enemy(Enemy enemy) Experience = enemy.Experience; DropsTable = enemy.DropsTable; NotifyStrongEnemy = enemy.NotifyStrongEnemy; + Index = enemy.Index; + Subgroup = enemy.Subgroup; } public uint Id { get; set; } @@ -74,6 +76,9 @@ public uint UINameId { get } } + public byte Index { get; set; } + public byte Subgroup { get; set; } + public uint GetDroppedExperience() { return Experience * (NamedEnemyParams.Experience/100); diff --git a/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs b/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs index 2e135e3a4..52dc66b28 100644 --- a/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs +++ b/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs @@ -17,14 +17,12 @@ public InstancedEnemy(Enemy enemy) : base(enemy) public InstancedEnemy(InstancedEnemy enemy) : base (enemy) { IsKilled = false; - Index = enemy.Index; IsRequired = enemy.IsRequired; RepopWaitSecond = enemy.RepopWaitSecond; } public bool IsRequired { get; set; } public bool IsKilled { get; set; } - public byte Index { get; set; } public uint RepopWaitSecond { get; set; } } } From d1705653b2e4301850b79b8f060575e107e667ad Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Mon, 9 Sep 2024 15:23:45 -0700 Subject: [PATCH 063/116] Rework such that I hate the data structure a little less. --- .../Chat/Command/Commands/RepopCommand.cs | 9 ++- .../Context/ContextManager.cs | 2 +- .../Enemies/InstanceEnemyManager.cs | 48 +++++++++--- .../GatheringItems/InstanceDropItemManager.cs | 14 +++- .../InstanceGatheringItemManager.cs | 13 +++- .../GatheringItems/InstanceItemManager.cs | 9 ++- .../Handler/InstanceEnemyKillHandler.cs | 3 +- .../Handler/InstanceGetDropItemHandler.cs | 3 +- .../Handler/InstanceGetDropItemListHandler.cs | 6 +- .../Handler/InstanceGetEnemySetListHandler.cs | 7 +- .../InstanceGetGatheringItemHandler.cs | 5 +- .../InstanceGetGatheringItemListHandler.cs | 11 ++- .../InstanceAssetManager.cs | 75 ++++++++++++++----- .../EnemySpawnAssetDeserializer.cs | 4 +- .../Model/EnemySpawnAsset.cs | 4 +- 15 files changed, 144 insertions(+), 69 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/RepopCommand.cs b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/RepopCommand.cs index f02070716..6985e9c91 100644 --- a/Arrowgene.Ddon.GameServer/Chat/Command/Commands/RepopCommand.cs +++ b/Arrowgene.Ddon.GameServer/Chat/Command/Commands/RepopCommand.cs @@ -1,9 +1,10 @@ -using System; -using System.Collections.Generic; using Arrowgene.Ddon.Database.Model; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; +using System; +using System.Collections.Generic; +using System.Linq; namespace Arrowgene.Ddon.GameServer.Chat.Command.Commands { @@ -48,7 +49,9 @@ public override void Execute(string[] command, GameClient client, ChatMessage me LayerNo = layerNo, GroupId = groupId }; - List enemySpawns = client.Party.InstanceEnemyManager.GetAssets(enemyGroup, subGroupId); + List enemySpawns = client.Party.InstanceEnemyManager.GetAssets(enemyGroup) + .Where(x => x.Subgroup == subGroupId) + .ToList(); if (enemySpawns.Count <= positionIndex) { diff --git a/Arrowgene.Ddon.GameServer/Context/ContextManager.cs b/Arrowgene.Ddon.GameServer/Context/ContextManager.cs index ec2c95362..ef2f1e4fa 100644 --- a/Arrowgene.Ddon.GameServer/Context/ContextManager.cs +++ b/Arrowgene.Ddon.GameServer/Context/ContextManager.cs @@ -79,7 +79,7 @@ public static ulong CreateEnemyUID(ulong setId, CDataStageLayoutId stageLayoutId public static List CreateEnemyUIDs(InstanceEnemyManager enemyManager, CDataStageLayoutId stageLayoutId) { - List enemies = enemyManager.GetAssets(stageLayoutId, 0); + List enemies = enemyManager.GetAssets(stageLayoutId); List results = new List(); for (int i = 0; i < enemies.Count(); i++) diff --git a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs index 88e099d01..fdacf026d 100644 --- a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs +++ b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs @@ -1,32 +1,62 @@ using Arrowgene.Ddon.GameServer; using Arrowgene.Ddon.GameServer.GatheringItems; -using Arrowgene.Ddon.GameServer.Quests; using Arrowgene.Ddon.Shared.Model; using System; using System.Collections.Generic; using System.Linq; -public class InstanceEnemyManager : InstanceAssetManager +public class InstanceEnemyManager : InstanceAssetManager { private readonly DdonGameServer _Server; private Dictionary _CurrentSubgroup { get; set; } - private Dictionary> _EnemyData; + private Dictionary> _EnemyData; public InstanceEnemyManager(DdonGameServer server) : base() { _Server = server; _CurrentSubgroup = new Dictionary(); - _EnemyData = new Dictionary>(); + _EnemyData = new Dictionary>(); } - protected override List FetchAssetsFromRepository(StageId stage, byte setId) + protected override InstancedEnemy InstanceAssets(Enemy original) { - // SetId is not used here, because the enemy data structure is flat, but the interface demands we have it. - return _Server.AssetRepository.EnemySpawnAsset.Enemies.GetValueOrDefault((stage, (byte)0)) ?? new List(); + long gameTimeMSec = _Server.WeatherManager.RealTimeToGameTimeMS(DateTimeOffset.UtcNow); + + if (original.SpawnTimeEnd < original.SpawnTimeStart) + { + // Morning range is 0 (midnight) to end time, Evening range is start time and onwards + if (gameTimeMSec <= original.SpawnTimeEnd || gameTimeMSec >= original.SpawnTimeStart) + { + return new InstancedEnemy(original); + } + } + else if (gameTimeMSec >= original.SpawnTimeStart && gameTimeMSec <= original.SpawnTimeEnd) + { + return new InstancedEnemy(original); + } + return null; + } + + protected override Enemy FetchAssetsFromRepository(StageId stage, int setId) + { + var enemiesInStage = _Server.AssetRepository.EnemySpawnAsset.Enemies.GetValueOrDefault(stage) ?? new List(); + if (enemiesInStage.Count > setId) + { + return enemiesInStage[setId]; + } + else + { + return null; + } + } + + protected override IEnumerable FetchAssetsFromRepository(StageId stage) + { + return _Server.AssetRepository.EnemySpawnAsset.Enemies.GetValueOrDefault(stage) ?? new List(); } - protected override List InstanceAssets(List originals) + protected override List InstanceAssets(IEnumerable originals) { List filteredEnemyList = new List(); @@ -58,7 +88,7 @@ public void SetInstanceEnemy(StageId stageId, byte index, InstancedEnemy enemy) { if (!_EnemyData.ContainsKey(stageId)) { - _EnemyData[stageId] = new Dictionary(); + _EnemyData[stageId] = new Dictionary(); } if (!_EnemyData[stageId].ContainsKey(index)) diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs index d72c23e64..a44e9f12b 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceDropItemManager.cs @@ -1,6 +1,6 @@ -using System; -using System.Collections.Generic; using Arrowgene.Ddon.Shared.Model; +using System.Collections.Generic; +using System.Linq; namespace Arrowgene.Ddon.GameServer.GatheringItems { @@ -13,9 +13,9 @@ public InstanceDropItemManager(GameClient client) this._client = client; } - protected override List FetchAssetsFromRepository(StageId stage, uint setId) + protected override List FetchAssetsFromRepository(StageId stage, int setId) { - List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage, 0); + List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage); if(enemiesInSet != null && setId < enemiesInSet.Count) { Enemy enemy = enemiesInSet[(int) setId]; @@ -27,5 +27,11 @@ protected override List FetchAssetsFromRepository(StageId stage, } return new List(); } + + protected override IEnumerable> FetchAssetsFromRepository(StageId stage) + { + List enemiesInSet = _client.Party.InstanceEnemyManager.GetAssets(stage); + return enemiesInSet.Select(x => x.DropsTable.Items); + } } } diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceGatheringItemManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceGatheringItemManager.cs index d210aa2ba..e8911a15d 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceGatheringItemManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceGatheringItemManager.cs @@ -1,7 +1,7 @@ -using System; -using System.Collections.Generic; using Arrowgene.Ddon.Shared; using Arrowgene.Ddon.Shared.Model; +using System.Collections.Generic; +using System.Linq; namespace Arrowgene.Ddon.GameServer.GatheringItems { @@ -17,9 +17,14 @@ public InstanceGatheringItemManager(AssetRepository assetRepository) BitterBlackLootTables = new Dictionary<(StageId, uint), List>(); } - protected override List FetchAssetsFromRepository(StageId stage, uint subGroupId) + protected override List FetchAssetsFromRepository(StageId stage, int subGroupId) + { + return _assetRepository.GatheringItems.GetValueOrDefault((stage, (uint)subGroupId)) ?? new List(); + } + + protected override IEnumerable> FetchAssetsFromRepository(StageId stage) { - return _assetRepository.GatheringItems.GetValueOrDefault((stage, subGroupId)) ?? new List(); + return _assetRepository.GatheringItems.Where(x => x.Key.Item1.Equals(stage)).Select(x => x.Value); } } } diff --git a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceItemManager.cs b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceItemManager.cs index 644f60fb3..2a5d9c30a 100644 --- a/Arrowgene.Ddon.GameServer/GatheringItems/InstanceItemManager.cs +++ b/Arrowgene.Ddon.GameServer/GatheringItems/InstanceItemManager.cs @@ -3,7 +3,7 @@ using Arrowgene.Ddon.GameServer.GatheringItems; using Arrowgene.Ddon.Shared.Model; -public abstract class InstanceItemManager : InstanceAssetManager +public abstract class InstanceItemManager : InstanceAssetManager, List> { protected override List InstanceAssets(List originals) { @@ -11,4 +11,9 @@ protected override List InstanceAssets(List instancedAsset.ItemNum > 0) .ToList(); } -} \ No newline at end of file + + protected override List> InstanceAssets(IEnumerable> originals) + { + return originals.Select(x => InstanceAssets(x)).ToList(); + } +} diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs index c3a784381..1dd2fec5b 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceEnemyKillHandler.cs @@ -6,7 +6,6 @@ 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.Network; using Arrowgene.Logging; using System; @@ -84,7 +83,7 @@ public override void Handle(GameClient client, StructurePacket instancedGatheringItems = IsQuestControlled ? partyMemberClient.InstanceQuestDropManager.GenerateEnemyLoot(quest, enemyKilled, packet.Structure.LayoutId, packet.Structure.SetId) : - partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, packet.Structure.SetId); + partyMemberClient.InstanceDropItemManager.GetAssets(layoutId, (int)packet.Structure.SetId); // If the roll was unlucky, there is a chance that no bag will show. if (instancedGatheringItems.Count > 0) diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs index 0b18b0712..1e5d00bd1 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetDropItemHandler.cs @@ -1,4 +1,3 @@ -using Arrowgene.Ddon.GameServer.Quests; using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Entity.PacketStructure; using Arrowgene.Ddon.Shared.Entity.Structure; @@ -28,7 +27,7 @@ public override void Handle(GameClient client, StructurePacket x.Subgroup == subGroupId)) + var instancedEnemyList = client.Party.InstanceEnemyManager.GetAssets(stageId) + .Where(x => x.Subgroup == subGroupId); + foreach (var asset in instancedEnemyList) { response.EnemyList.Add(new CDataLayoutEnemyData() { diff --git a/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs b/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs index 4c79309c6..b5a1a1ff3 100644 --- a/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/InstanceGetGatheringItemHandler.cs @@ -1,14 +1,11 @@ #nullable enable using Arrowgene.Ddon.GameServer.Characters; using Arrowgene.Ddon.Server; -using Arrowgene.Ddon.Server.Network; 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 Microsoft.VisualBasic; -using System; using System.Collections.Generic; namespace Arrowgene.Ddon.GameServer.Handler @@ -42,7 +39,7 @@ public override void Handle(GameClient client, StructurePacket req) { bool isGatheringItemBreak = false; - if(!client.InstanceGatheringItemManager.HasAssetsInstanced(req.Structure.LayoutId, req.Structure.PosId) && req.Structure.GatheringItemUId.Length > 0 && Random.Shared.NextDouble() < BREAK_CHANCE) + if(!client.InstanceGatheringItemManager.HasAssetsInstanced(req.Structure.LayoutId, (int)req.Structure.PosId) && req.Structure.GatheringItemUId.Length > 0 && Random.Shared.NextDouble() < BREAK_CHANCE) { isGatheringItemBreak = true; @@ -44,7 +43,7 @@ public override void Handle(GameClient client, StructurePacket + public abstract class InstanceAssetManager { public InstanceAssetManager() { - this._instancedAssetsDictionary = new Dictionary<(StageId, T1), List>(); + this._instancedAssetsDictionary = new Dictionary>(); + } + + private readonly Dictionary> _instancedAssetsDictionary; + + public bool HasAssetsInstanced(CDataStageLayoutId stageLayoutId, int subId) + { + var stageId = StageId.FromStageLayoutId(stageLayoutId); + return HasAssetsInstanced(stageId, subId); } - private readonly Dictionary<(StageId, T1), List> _instancedAssetsDictionary; + public bool HasAssetsInstanced(StageId stageId, int subId) + { + return _instancedAssetsDictionary.ContainsKey(stageId) && _instancedAssetsDictionary[stageId].ContainsKey(subId); + } - public bool HasAssetsInstanced(CDataStageLayoutId stageLayoutId, T1 setId) + public List GetAssets(CDataStageLayoutId stageLayoutId) { - return _instancedAssetsDictionary.ContainsKey((StageId.FromStageLayoutId(stageLayoutId), setId)); + return GetAssets(StageId.FromStageLayoutId(stageLayoutId)); } - public bool HasAssetsInstanced(StageId stageId, T1 setId) + public List GetAssets(StageId stageId) { - return _instancedAssetsDictionary.ContainsKey((stageId, setId)); + if (!_instancedAssetsDictionary.ContainsKey(stageId)) + { + IEnumerable items = FetchAssetsFromRepository(stageId); + List instancedAssets = InstanceAssets(items); + if (!_instancedAssetsDictionary.ContainsKey(stageId)) + { + _instancedAssetsDictionary[stageId] = new Dictionary(); + } + for (int i = 0; i < instancedAssets.Count; i++) + { + _instancedAssetsDictionary[stageId].Add(i, instancedAssets[i]); + } + return instancedAssets; + } + else + { + return _instancedAssetsDictionary[stageId].Values.ToList(); + } } - public List GetAssets(CDataStageLayoutId stageLayoutId, T1 setId) + public TAssetItem GetAssets(CDataStageLayoutId stageLayoutId, int subId) { - return GetAssets(StageId.FromStageLayoutId(stageLayoutId), setId); + return GetAssets(StageId.FromStageLayoutId(stageLayoutId), subId); } - public List GetAssets(StageId stageId, T1 setId) + public TAssetItem GetAssets(StageId stageId, int subId) { - if(!HasAssetsInstanced(stageId, setId)) + if(!HasAssetsInstanced(stageId, subId)) { - List items = FetchAssetsFromRepository(stageId, setId); - List instancedAssets = InstanceAssets(items); - _instancedAssetsDictionary.Add((stageId, setId), instancedAssets); + TItem items = FetchAssetsFromRepository(stageId, subId); + TAssetItem instancedAssets = InstanceAssets(items); + + if (!_instancedAssetsDictionary.ContainsKey(stageId)) + { + _instancedAssetsDictionary[stageId] = new Dictionary(); + } + _instancedAssetsDictionary[stageId].Add(subId, instancedAssets); return instancedAssets; } - return _instancedAssetsDictionary[(stageId, setId)]; + return _instancedAssetsDictionary[stageId][subId]; } public virtual void Clear() @@ -46,8 +79,12 @@ public virtual void Clear() _instancedAssetsDictionary.Clear(); } - protected abstract List FetchAssetsFromRepository(StageId stage, T1 setId); + protected abstract TItem FetchAssetsFromRepository(StageId stage, int subId); + + protected abstract IEnumerable FetchAssetsFromRepository(StageId stage); + + protected abstract TAssetItem InstanceAssets(TItem original); - protected abstract List InstanceAssets(List originals); + protected abstract List InstanceAssets(IEnumerable originals); } } diff --git a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs index 2bdfff6a9..7c9aaac39 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs @@ -82,7 +82,7 @@ public EnemySpawnAsset ReadPath(string path) row[enemySchemaIndexes["GroupId"]].GetUInt32() ); byte subGroupId = row[enemySchemaIndexes["SubGroupId"]].GetByte(); - List enemies = asset.Enemies.GetValueOrDefault((layoutId, (byte)0)) ?? new List(); + List enemies = asset.Enemies.GetValueOrDefault(layoutId) ?? new List(); Enemy enemy = new Enemy() { EnemyId = ParseHexUInt(row[enemySchemaIndexes["EnemyId"]].GetString()), @@ -133,7 +133,7 @@ public EnemySpawnAsset ReadPath(string path) enemy.DropsTable = asset.DropsTables[(uint) dropsTableId]; } enemies.Add(enemy); - asset.Enemies[(layoutId, 0)] = enemies; + asset.Enemies[layoutId] = enemies; } return asset; diff --git a/Arrowgene.Ddon.Shared/Model/EnemySpawnAsset.cs b/Arrowgene.Ddon.Shared/Model/EnemySpawnAsset.cs index a0ac471cb..c1d5f40e7 100644 --- a/Arrowgene.Ddon.Shared/Model/EnemySpawnAsset.cs +++ b/Arrowgene.Ddon.Shared/Model/EnemySpawnAsset.cs @@ -5,10 +5,10 @@ public class EnemySpawnAsset { public EnemySpawnAsset() { - Enemies = new Dictionary<(StageId, byte), List>(); + Enemies = new Dictionary>(); DropsTables = new Dictionary(); } - public Dictionary<(StageId, byte), List> Enemies { get; set; } + public Dictionary> Enemies { get; set; } public Dictionary DropsTables { get; set; } } From 3cf32b5abc907f8996ed8afb6f4ea9263386fcf9 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Wed, 11 Sep 2024 12:25:18 -0700 Subject: [PATCH 064/116] Enemies once again get their Index when they're instantiated, not when they're read from the JSON. --- Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs | 8 ++++++-- .../AssetReader/EnemySpawnAssetDeserializer.cs | 1 - Arrowgene.Ddon.Shared/Model/Enemy.cs | 3 --- Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs | 2 ++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs index fdacf026d..f27689e56 100644 --- a/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs +++ b/Arrowgene.Ddon.GameServer/Enemies/InstanceEnemyManager.cs @@ -71,12 +71,16 @@ protected override List InstanceAssets(IEnumerable origin // Morning range is 0 (midnight) to end time, Evening range is start time and onwards if(gameTimeMSec <= original.SpawnTimeEnd || gameTimeMSec >= original.SpawnTimeStart) { - filteredEnemyList.Add(new InstancedEnemy(original)); + var enemy = new InstancedEnemy(original); + enemy.Index = (byte)filteredEnemyList.Count; + filteredEnemyList.Add(enemy); } } else if(gameTimeMSec >= original.SpawnTimeStart && gameTimeMSec <= original.SpawnTimeEnd) { - filteredEnemyList.Add(new InstancedEnemy(original)); + var enemy = new InstancedEnemy(original); + enemy.Index = (byte)filteredEnemyList.Count; + filteredEnemyList.Add(enemy); } } return filteredEnemyList; diff --git a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs index 7c9aaac39..5a158d144 100644 --- a/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs +++ b/Arrowgene.Ddon.Shared/AssetReader/EnemySpawnAssetDeserializer.cs @@ -106,7 +106,6 @@ public EnemySpawnAsset ReadPath(string path) HighOrbs = row[enemySchemaIndexes["HighOrbs"]].GetUInt32(), Experience = row[enemySchemaIndexes["Experience"]].GetUInt32(), - Index = (byte)enemies.Count, Subgroup = subGroupId, }; diff --git a/Arrowgene.Ddon.Shared/Model/Enemy.cs b/Arrowgene.Ddon.Shared/Model/Enemy.cs index d9b15827b..283cf4c5b 100644 --- a/Arrowgene.Ddon.Shared/Model/Enemy.cs +++ b/Arrowgene.Ddon.Shared/Model/Enemy.cs @@ -40,7 +40,6 @@ public Enemy(Enemy enemy) Experience = enemy.Experience; DropsTable = enemy.DropsTable; NotifyStrongEnemy = enemy.NotifyStrongEnemy; - Index = enemy.Index; Subgroup = enemy.Subgroup; } @@ -75,8 +74,6 @@ public uint UINameId { get return NameMap.GetValueOrDefault(EnemyId); } } - - public byte Index { get; set; } public byte Subgroup { get; set; } public uint GetDroppedExperience() diff --git a/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs b/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs index 52dc66b28..db538c196 100644 --- a/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs +++ b/Arrowgene.Ddon.Shared/Model/InstancedEnemy.cs @@ -17,10 +17,12 @@ public InstancedEnemy(Enemy enemy) : base(enemy) public InstancedEnemy(InstancedEnemy enemy) : base (enemy) { IsKilled = false; + Index = enemy.Index; IsRequired = enemy.IsRequired; RepopWaitSecond = enemy.RepopWaitSecond; } + public byte Index { get; set; } public bool IsRequired { get; set; } public bool IsKilled { get; set; } public uint RepopWaitSecond { get; set; } From be636e6b7a290d562c66ff7c40292870698136a6 Mon Sep 17 00:00:00 2001 From: Ryan Yappert Date: Thu, 12 Sep 2024 17:11:26 -0700 Subject: [PATCH 065/116] Additional comment in InstanceAssetManager. --- .../InstanceAssetManager.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Arrowgene.Ddon.GameServer/InstanceAssetManager.cs b/Arrowgene.Ddon.GameServer/InstanceAssetManager.cs index 9c8441617..cc4a2e80a 100644 --- a/Arrowgene.Ddon.GameServer/InstanceAssetManager.cs +++ b/Arrowgene.Ddon.GameServer/InstanceAssetManager.cs @@ -5,6 +5,22 @@ namespace Arrowgene.Ddon.GameServer.GatheringItems { + ///