From aae051d73f38668543c9363fc5883ad05058e21f Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Wed, 16 Feb 2022 15:30:32 -0500 Subject: [PATCH 1/9] Capitalized the DataObject class. --- Map Server/Actors/Chara/Ai/State/ItemState.cs | 2 +- Map Server/Actors/Chara/Character.cs | 2 +- Map Server/Actors/Chara/ItemPackage.cs | 2 +- Map Server/Actors/Chara/Player/Player.cs | 20 +++++++++---- Map Server/Actors/Chara/Player/PlayerWork.cs | 2 +- .../Actors/Chara/ReferencedItemPackage.cs | 2 +- .../Actors/Director/GuildleveDirector.cs | 2 +- Map Server/Actors/Group/ContentGroup.cs | 2 +- Map Server/Actors/Group/Group.cs | 2 +- Map Server/Actors/Group/MonsterParty.cs | 2 +- Map Server/Actors/Group/RelationGroup.cs | 2 +- .../Group/RetainerMeetingRelationGroup.cs | 2 +- Map Server/Actors/Group/TradeGroup.cs | 2 +- Map Server/CommandProcessor.cs | 2 +- Map Server/DataObjects/GuildleveData.cs | 2 +- Map Server/DataObjects/InventoryItem.cs | 2 +- Map Server/DataObjects/ItemData.cs | 2 +- Map Server/DataObjects/QuestData.cs | 28 +++++++++++++++++++ Map Server/DataObjects/RecruitmentDetails.cs | 2 +- Map Server/DataObjects/SeamlessBoundry.cs | 2 +- Map Server/DataObjects/SearchEntry.cs | 2 +- Map Server/DataObjects/Session.cs | 7 +++-- Map Server/DataObjects/TradeTransaction.cs | 2 +- Map Server/DataObjects/ZoneConnection.cs | 2 +- Map Server/Lua/LuaEngine.cs | 4 +-- .../Actor/Inventory/InventoryItemEndPacket.cs | 2 +- .../Actor/Inventory/InventoryItemPacket.cs | 2 +- .../Actor/Inventory/InventoryListX01Packet.cs | 2 +- .../Actor/Inventory/InventoryListX08Packet.cs | 2 +- .../Actor/Inventory/InventoryListX16Packet.cs | 2 +- .../Actor/Inventory/InventoryListX32Packet.cs | 2 +- .../Actor/Inventory/InventoryListX64Packet.cs | 2 +- .../Inventory/LinkedItemListX01Packet.cs | 2 +- .../Inventory/LinkedItemListX08Packet.cs | 2 +- .../Inventory/LinkedItemListX16Packet.cs | 2 +- .../Inventory/LinkedItemListX32Packet.cs | 2 +- .../Inventory/LinkedItemListX64Packet.cs | 2 +- .../Send/Player/SetCutsceneBookPacket.cs | 6 ++-- .../CurrentRecruitmentDetailsPacket.cs | 2 +- .../Send/Group/CreateLinkshellPacket.cs | 2 +- .../Send/Group/DeleteLinkshellPacket.cs | 2 +- .../Send/Group/GroupInviteResultPacket.cs | 2 +- .../Send/Group/LinkshellChangePacket.cs | 2 +- .../Send/Group/LinkshellInviteCancelPacket.cs | 2 +- .../Send/Group/LinkshellInvitePacket.cs | 2 +- .../Send/Group/LinkshellLeavePacket.cs | 2 +- .../Send/Group/LinkshellRankChangePacket.cs | 2 +- .../Send/Group/ModifyLinkshellPacket.cs | 2 +- .../Send/Group/PartyInvitePacket.cs | 2 +- .../Send/Group/PartyLeavePacket.cs | 2 +- .../Send/Group/PartyModifyPacket.cs | 2 +- .../Send/SessionBeginConfirmPacket.cs | 2 +- .../Send/SessionEndConfirmPacket.cs | 2 +- 53 files changed, 99 insertions(+), 62 deletions(-) create mode 100644 Map Server/DataObjects/QuestData.cs diff --git a/Map Server/Actors/Chara/Ai/State/ItemState.cs b/Map Server/Actors/Chara/Ai/State/ItemState.cs index 94ea645b..e9c34019 100644 --- a/Map Server/Actors/Chara/Ai/State/ItemState.cs +++ b/Map Server/Actors/Chara/Ai/State/ItemState.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Map.Actors; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; namespace Meteor.Map.actors.chara.ai.state { diff --git a/Map Server/Actors/Chara/Character.cs b/Map Server/Actors/Chara/Character.cs index 914a7895..b7fa3242 100644 --- a/Map Server/Actors/Chara/Character.cs +++ b/Map Server/Actors/Chara/Character.cs @@ -24,7 +24,7 @@ using Meteor.Common; using Meteor.Map.actors.chara.player; using Meteor.Map.actors.group; using Meteor.Map.Actors.Chara; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.actor; using Meteor.Map.packets.send.actor.inventory; using Meteor.Map.utils; diff --git a/Map Server/Actors/Chara/ItemPackage.cs b/Map Server/Actors/Chara/ItemPackage.cs index 39beea0a..e14e89d5 100644 --- a/Map Server/Actors/Chara/ItemPackage.cs +++ b/Map Server/Actors/Chara/ItemPackage.cs @@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see . using Meteor.Common; using Meteor.Map.actors.chara.npc; using Meteor.Map.Actors; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.actor.inventory; using System; using System.Collections.Generic; diff --git a/Map Server/Actors/Chara/Player/Player.cs b/Map Server/Actors/Chara/Player/Player.cs index 2f0e6abb..2a80d930 100644 --- a/Map Server/Actors/Chara/Player/Player.cs +++ b/Map Server/Actors/Chara/Player/Player.cs @@ -23,8 +23,8 @@ using Meteor.Common; using System; using System.Collections.Generic; using MoonSharp.Interpreter; -using Meteor.Map.dataobjects; -using Meteor.Map.dataobjects.chara; +using Meteor.Map.DataObjects; +using Meteor.Map.DataObjects.chara; using Meteor.Map.lua; using Meteor.Map.packets.WorldPackets.Send.Group; using Meteor.Map.utils; @@ -147,6 +147,7 @@ namespace Meteor.Map.Actors //Quest Actors (MUST MATCH playerWork.questScenario/questGuildleve) public Quest[] questScenario = new Quest[16]; public uint[] questGuildleve = new uint[8]; + public QuestStateManager questStateManager; //Aetheryte public uint homepoint = 0; @@ -274,6 +275,9 @@ namespace Meteor.Map.Actors this.aiContainer = new AIContainer(this, new PlayerController(this), null, new TargetFind(this)); allegiance = CharacterTargetingAllegiance.Player; CalculateBaseStats(); + + questStateManager = new QuestStateManager(this); + questStateManager.Init(); } public List Create0x132Packets() @@ -400,9 +404,10 @@ namespace Meteor.Map.Actors if (CurrentArea.isInn) { SetCutsceneBookPacket cutsceneBookPacket = new SetCutsceneBookPacket(); + bool[] testComplete = new bool[2048]; //TODO: Change to playerwork.scenarioComplete for (int i = 0; i < 2048; i++) - cutsceneBookPacket.cutsceneFlags[i] = true; - QueuePacket(cutsceneBookPacket.BuildPacket(Id, "", 11, 1, 1)); + testComplete[i] = true; + QueuePacket(cutsceneBookPacket.BuildPacket(Id, "", 11, 1, 1, testComplete)); QueuePacket(SetPlayerDreamPacket.BuildPacket(Id, 0x16, GetInnCode())); } @@ -1730,11 +1735,16 @@ namespace Meteor.Map.Actors return null; } - public Quest[] GetQuestsForNpc(Npc npc) + public Quest[] GetJournalQuestsForNpc(Npc npc) { return Array.FindAll(questScenario, e => e != null && e.IsQuestENPC(this, npc)); } + public Quest[] GetQuestsForNpc(Npc npc) + { + return questStateManager.GetQuestsForNpc(npc); + } + public void HandleNpcLS(uint id) { foreach (Quest quest in questScenario) diff --git a/Map Server/Actors/Chara/Player/PlayerWork.cs b/Map Server/Actors/Chara/Player/PlayerWork.cs index ebe46ebd..3851e106 100644 --- a/Map Server/Actors/Chara/Player/PlayerWork.cs +++ b/Map Server/Actors/Chara/Player/PlayerWork.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -namespace Meteor.Map.dataobjects.chara +namespace Meteor.Map.DataObjects.chara { class PlayerWork { diff --git a/Map Server/Actors/Chara/ReferencedItemPackage.cs b/Map Server/Actors/Chara/ReferencedItemPackage.cs index 4e37b0de..e21d4582 100644 --- a/Map Server/Actors/Chara/ReferencedItemPackage.cs +++ b/Map Server/Actors/Chara/ReferencedItemPackage.cs @@ -21,7 +21,7 @@ along with Project Meteor Server. If not, see . using Meteor.Map.actors.chara.player; using Meteor.Map.Actors; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.actor.inventory; using System.Collections.Generic; using System.Diagnostics; diff --git a/Map Server/Actors/Director/GuildleveDirector.cs b/Map Server/Actors/Director/GuildleveDirector.cs index 55b29063..1c59909a 100644 --- a/Map Server/Actors/Director/GuildleveDirector.cs +++ b/Map Server/Actors/Director/GuildleveDirector.cs @@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see . using Meteor.Common; using Meteor.Map.actors.director.Work; using Meteor.Map.Actors; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.utils; using System; using System.Collections.Generic; diff --git a/Map Server/Actors/Group/ContentGroup.cs b/Map Server/Actors/Group/ContentGroup.cs index d1588725..5a0844f2 100644 --- a/Map Server/Actors/Group/ContentGroup.cs +++ b/Map Server/Actors/Group/ContentGroup.cs @@ -23,7 +23,7 @@ using Meteor.Common; using Meteor.Map.actors.director; using Meteor.Map.actors.group.Work; using Meteor.Map.Actors; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.group; using Meteor.Map.packets.send.groups; using System.Collections.Generic; diff --git a/Map Server/Actors/Group/Group.cs b/Map Server/Actors/Group/Group.cs index 8c31a019..be81989e 100644 --- a/Map Server/Actors/Group/Group.cs +++ b/Map Server/Actors/Group/Group.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.group; using Meteor.Map.packets.send.groups; using System; diff --git a/Map Server/Actors/Group/MonsterParty.cs b/Map Server/Actors/Group/MonsterParty.cs index fc91b31a..a3d0dbc1 100644 --- a/Map Server/Actors/Group/MonsterParty.cs +++ b/Map Server/Actors/Group/MonsterParty.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.group; using Meteor.Map.packets.send.groups; using System.Collections.Generic; diff --git a/Map Server/Actors/Group/RelationGroup.cs b/Map Server/Actors/Group/RelationGroup.cs index c8e154a8..4c66dadf 100644 --- a/Map Server/Actors/Group/RelationGroup.cs +++ b/Map Server/Actors/Group/RelationGroup.cs @@ -21,7 +21,7 @@ along with Project Meteor Server. If not, see . using Meteor.Common; using Meteor.Map.actors.group.Work; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.group; using Meteor.Map.packets.send.groups; using System.Collections.Generic; diff --git a/Map Server/Actors/Group/RetainerMeetingRelationGroup.cs b/Map Server/Actors/Group/RetainerMeetingRelationGroup.cs index 047a3bc0..37058c70 100644 --- a/Map Server/Actors/Group/RetainerMeetingRelationGroup.cs +++ b/Map Server/Actors/Group/RetainerMeetingRelationGroup.cs @@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see . using Meteor.Common; using Meteor.Map.actors.chara.npc; using Meteor.Map.Actors; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.group; using Meteor.Map.packets.send.groups; using System.Collections.Generic; diff --git a/Map Server/Actors/Group/TradeGroup.cs b/Map Server/Actors/Group/TradeGroup.cs index 941b99ed..c9149038 100644 --- a/Map Server/Actors/Group/TradeGroup.cs +++ b/Map Server/Actors/Group/TradeGroup.cs @@ -21,7 +21,7 @@ along with Project Meteor Server. If not, see . using Meteor.Common; using Meteor.Map.actors.group.Work; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.send.group; using Meteor.Map.packets.send.groups; using System.Collections.Generic; diff --git a/Map Server/CommandProcessor.cs b/Map Server/CommandProcessor.cs index 9918d46a..66e28b44 100644 --- a/Map Server/CommandProcessor.cs +++ b/Map Server/CommandProcessor.cs @@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see . using System; using System.Collections.Generic; using System.Linq; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.IO; using Meteor.Map.packets.send; diff --git a/Map Server/DataObjects/GuildleveData.cs b/Map Server/DataObjects/GuildleveData.cs index 07a93c19..3845e96f 100644 --- a/Map Server/DataObjects/GuildleveData.cs +++ b/Map Server/DataObjects/GuildleveData.cs @@ -21,7 +21,7 @@ along with Project Meteor Server. If not, see . using MySql.Data.MySqlClient; -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { class GuildleveData { diff --git a/Map Server/DataObjects/InventoryItem.cs b/Map Server/DataObjects/InventoryItem.cs index 3b4f7a48..b331c4b2 100644 --- a/Map Server/DataObjects/InventoryItem.cs +++ b/Map Server/DataObjects/InventoryItem.cs @@ -25,7 +25,7 @@ using MySql.Data.MySqlClient; using System; using System.IO; -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { class InventoryItem { diff --git a/Map Server/DataObjects/ItemData.cs b/Map Server/DataObjects/ItemData.cs index 07ad2f86..ceb182eb 100644 --- a/Map Server/DataObjects/ItemData.cs +++ b/Map Server/DataObjects/ItemData.cs @@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see . using MySql.Data.MySqlClient; using System; -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { class ItemData { diff --git a/Map Server/DataObjects/QuestData.cs b/Map Server/DataObjects/QuestData.cs new file mode 100644 index 00000000..3054f64f --- /dev/null +++ b/Map Server/DataObjects/QuestData.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Meteor.Map.DataObjects +{ + class QuestData + { + public uint Id { get; } + public string ClassName { get; } + public string Name { get; } + public uint PrerequisiteQuest { get; } + public int MinLevel { get; } + public int MinGCRank { get; } + + public QuestData(uint id, string className, string name, uint prereq, int minLv, int minGcRank) + { + Id = id; + ClassName = className; + Name = Name; + PrerequisiteQuest = prereq; + MinLevel = minLv; + MinGCRank = minGcRank; + } + } +} diff --git a/Map Server/DataObjects/RecruitmentDetails.cs b/Map Server/DataObjects/RecruitmentDetails.cs index 76723e15..077e69fb 100644 --- a/Map Server/DataObjects/RecruitmentDetails.cs +++ b/Map Server/DataObjects/RecruitmentDetails.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { class RecruitmentDetails { diff --git a/Map Server/DataObjects/SeamlessBoundry.cs b/Map Server/DataObjects/SeamlessBoundry.cs index 9d9319a3..a543a01b 100644 --- a/Map Server/DataObjects/SeamlessBoundry.cs +++ b/Map Server/DataObjects/SeamlessBoundry.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { class SeamlessBoundry { diff --git a/Map Server/DataObjects/SearchEntry.cs b/Map Server/DataObjects/SearchEntry.cs index 6f092e11..ce145375 100644 --- a/Map Server/DataObjects/SearchEntry.cs +++ b/Map Server/DataObjects/SearchEntry.cs @@ -23,7 +23,7 @@ using System; using System.IO; using System.Text; -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { class SearchEntry { diff --git a/Map Server/DataObjects/Session.cs b/Map Server/DataObjects/Session.cs index 1c93f276..299fa72c 100644 --- a/Map Server/DataObjects/Session.cs +++ b/Map Server/DataObjects/Session.cs @@ -26,8 +26,9 @@ using Meteor.Map.packets.send.actor; using System.Collections.Generic; using Meteor.Map.actors.chara.npc; using static Meteor.Map.Actors.Quest; +using static Meteor.Map.Actors.QuestState; -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { class Session { @@ -167,7 +168,7 @@ namespace Meteor.Map.dataobjects Quest[] quests = playerActor.GetQuestsForNpc(npc); if (quests.Length != 0) { - ENpcQuestInstance questInstance = quests[0].GetENpcInstance(npc.GetActorClassId()); + QuestENpc questInstance = quests[0].GetQuestState().GetENpc(npc.GetActorClassId()); QueuePacket(npc.GetSetEventStatusPackets()); QueuePacket(SetActorQuestGraphicPacket.BuildPacket(npc.Id, questInstance.questFlagType)); } @@ -179,7 +180,7 @@ namespace Meteor.Map.dataobjects } } - public void UpdateQuestNpcInInstance(ENpcQuestInstance questInstance, bool clearInstance = false) + public void UpdateQuestNpcInInstance(QuestENpc questInstance, bool clearInstance = false) { LockUpdates(true); Actor actor = actorInstanceList.Find(x => x is Npc npc && npc.GetActorClassId().Equals(questInstance.actorClassId)); diff --git a/Map Server/DataObjects/TradeTransaction.cs b/Map Server/DataObjects/TradeTransaction.cs index 57181fa1..6b096b91 100644 --- a/Map Server/DataObjects/TradeTransaction.cs +++ b/Map Server/DataObjects/TradeTransaction.cs @@ -19,6 +19,6 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { } diff --git a/Map Server/DataObjects/ZoneConnection.cs b/Map Server/DataObjects/ZoneConnection.cs index c681b15a..793b58fc 100644 --- a/Map Server/DataObjects/ZoneConnection.cs +++ b/Map Server/DataObjects/ZoneConnection.cs @@ -27,7 +27,7 @@ using System.Collections.Concurrent; using System.Net; using Meteor.Map.packets.WorldPackets.Send; -namespace Meteor.Map.dataobjects +namespace Meteor.Map.DataObjects { class ZoneConnection { diff --git a/Map Server/Lua/LuaEngine.cs b/Map Server/Lua/LuaEngine.cs index b10f090e..2a05fbd7 100644 --- a/Map Server/Lua/LuaEngine.cs +++ b/Map Server/Lua/LuaEngine.cs @@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see . using Meteor.Map.actors.director; using Meteor.Map.Actors; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.receive.events; using Meteor.Map.packets.send; using Meteor.Map.packets.send.events; @@ -40,7 +40,7 @@ using Meteor.Map.actors.chara.ai.controllers; using Meteor.Map.DataObjects; using Meteor.Map.actors.chara.player; using Meteor.Map.Actors.Chara; -using Meteor.Map.dataobjects.chara; +using Meteor.Map.DataObjects.chara; using Meteor.Map.actors.chara; namespace Meteor.Map.lua diff --git a/Map Server/Packets/Send/Actor/Inventory/InventoryItemEndPacket.cs b/Map Server/Packets/Send/Actor/Inventory/InventoryItemEndPacket.cs index 29a64880..bddba142 100644 --- a/Map Server/Packets/Send/Actor/Inventory/InventoryItemEndPacket.cs +++ b/Map Server/Packets/Send/Actor/Inventory/InventoryItemEndPacket.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/InventoryItemPacket.cs b/Map Server/Packets/Send/Actor/Inventory/InventoryItemPacket.cs index 96e56d49..3cf40a0e 100644 --- a/Map Server/Packets/Send/Actor/Inventory/InventoryItemPacket.cs +++ b/Map Server/Packets/Send/Actor/Inventory/InventoryItemPacket.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/InventoryListX01Packet.cs b/Map Server/Packets/Send/Actor/Inventory/InventoryListX01Packet.cs index f7c2c16f..bec8da3b 100644 --- a/Map Server/Packets/Send/Actor/Inventory/InventoryListX01Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/InventoryListX01Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.IO; using Meteor.Common; diff --git a/Map Server/Packets/Send/Actor/Inventory/InventoryListX08Packet.cs b/Map Server/Packets/Send/Actor/Inventory/InventoryListX08Packet.cs index 88400896..22f54b29 100644 --- a/Map Server/Packets/Send/Actor/Inventory/InventoryListX08Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/InventoryListX08Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/InventoryListX16Packet.cs b/Map Server/Packets/Send/Actor/Inventory/InventoryListX16Packet.cs index 143658b1..96cfe0a0 100644 --- a/Map Server/Packets/Send/Actor/Inventory/InventoryListX16Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/InventoryListX16Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/InventoryListX32Packet.cs b/Map Server/Packets/Send/Actor/Inventory/InventoryListX32Packet.cs index dbcadaa4..0710b630 100644 --- a/Map Server/Packets/Send/Actor/Inventory/InventoryListX32Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/InventoryListX32Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/InventoryListX64Packet.cs b/Map Server/Packets/Send/Actor/Inventory/InventoryListX64Packet.cs index 0fe22ff3..8d6278b4 100644 --- a/Map Server/Packets/Send/Actor/Inventory/InventoryListX64Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/InventoryListX64Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX01Packet.cs b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX01Packet.cs index f90d749d..78697fc3 100644 --- a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX01Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX01Packet.cs @@ -23,7 +23,7 @@ using System; using System.IO; using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; namespace Meteor.Map.packets.send.actor.inventory { diff --git a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX08Packet.cs b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX08Packet.cs index e54442a3..2addbb7b 100644 --- a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX08Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX08Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX16Packet.cs b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX16Packet.cs index 3d1e00a5..d9e867c6 100644 --- a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX16Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX16Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX32Packet.cs b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX32Packet.cs index 2aa30ab3..dde58f04 100644 --- a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX32Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX32Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX64Packet.cs b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX64Packet.cs index b23a39b1..82f8b891 100644 --- a/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX64Packet.cs +++ b/Map Server/Packets/Send/Actor/Inventory/LinkedItemListX64Packet.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.Collections.Generic; using System.IO; diff --git a/Map Server/Packets/Send/Player/SetCutsceneBookPacket.cs b/Map Server/Packets/Send/Player/SetCutsceneBookPacket.cs index 2a08adbb..4050f87a 100644 --- a/Map Server/Packets/Send/Player/SetCutsceneBookPacket.cs +++ b/Map Server/Packets/Send/Player/SetCutsceneBookPacket.cs @@ -79,9 +79,7 @@ namespace Meteor.Map.packets.send.player public const ushort OPCODE = 0x01A3; public const uint PACKET_SIZE = 0x150; - public bool[] cutsceneFlags = new bool[2048]; - - public SubPacket BuildPacket(uint sourceActorId, string sNpcName, short sNpcActorIdOffset, byte sNpcSkin, byte sNpcPersonality) + public SubPacket BuildPacket(uint sourceActorId, string sNpcName, short sNpcActorIdOffset, byte sNpcSkin, byte sNpcPersonality, bool[] completedQuests) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -89,7 +87,7 @@ namespace Meteor.Map.packets.send.player { using (BinaryWriter binWriter = new BinaryWriter(mem)) { - byte[] binStream = Utils.ConvertBoolArrayToBinaryStream(cutsceneFlags); + byte[] binStream = Utils.ConvertBoolArrayToBinaryStream(completedQuests); //Temp Path Companion SNPC Stuff binWriter.Seek(0x01 ,SeekOrigin.Begin); diff --git a/Map Server/Packets/Send/Recruitment/CurrentRecruitmentDetailsPacket.cs b/Map Server/Packets/Send/Recruitment/CurrentRecruitmentDetailsPacket.cs index 38602168..7f3d36f6 100644 --- a/Map Server/Packets/Send/Recruitment/CurrentRecruitmentDetailsPacket.cs +++ b/Map Server/Packets/Send/Recruitment/CurrentRecruitmentDetailsPacket.cs @@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see . =========================================================================== */ -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/CreateLinkshellPacket.cs b/Map Server/Packets/WorldPackets/Send/Group/CreateLinkshellPacket.cs index e08a5f80..1eb7f045 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/CreateLinkshellPacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/CreateLinkshellPacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/DeleteLinkshellPacket.cs b/Map Server/Packets/WorldPackets/Send/Group/DeleteLinkshellPacket.cs index 1aec3b84..e88d70bb 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/DeleteLinkshellPacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/DeleteLinkshellPacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/GroupInviteResultPacket.cs b/Map Server/Packets/WorldPackets/Send/Group/GroupInviteResultPacket.cs index 21b4c89b..f6f5b06c 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/GroupInviteResultPacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/GroupInviteResultPacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; diff --git a/Map Server/Packets/WorldPackets/Send/Group/LinkshellChangePacket.cs b/Map Server/Packets/WorldPackets/Send/Group/LinkshellChangePacket.cs index 34cb1a97..acbb9e0a 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/LinkshellChangePacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/LinkshellChangePacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/LinkshellInviteCancelPacket.cs b/Map Server/Packets/WorldPackets/Send/Group/LinkshellInviteCancelPacket.cs index 52ed6900..ae8c9a26 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/LinkshellInviteCancelPacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/LinkshellInviteCancelPacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; namespace Meteor.Map.packets.WorldPackets.Send.Group { diff --git a/Map Server/Packets/WorldPackets/Send/Group/LinkshellInvitePacket.cs b/Map Server/Packets/WorldPackets/Send/Group/LinkshellInvitePacket.cs index 3c904196..f262110e 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/LinkshellInvitePacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/LinkshellInvitePacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/LinkshellLeavePacket.cs b/Map Server/Packets/WorldPackets/Send/Group/LinkshellLeavePacket.cs index 4d75fb55..8637d14a 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/LinkshellLeavePacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/LinkshellLeavePacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/LinkshellRankChangePacket.cs b/Map Server/Packets/WorldPackets/Send/Group/LinkshellRankChangePacket.cs index b246e57c..46804a8c 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/LinkshellRankChangePacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/LinkshellRankChangePacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/ModifyLinkshellPacket.cs b/Map Server/Packets/WorldPackets/Send/Group/ModifyLinkshellPacket.cs index 079ec4bb..0a70745a 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/ModifyLinkshellPacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/ModifyLinkshellPacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/PartyInvitePacket.cs b/Map Server/Packets/WorldPackets/Send/Group/PartyInvitePacket.cs index 81fd9c95..8cb6239f 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/PartyInvitePacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/PartyInvitePacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/Group/PartyLeavePacket.cs b/Map Server/Packets/WorldPackets/Send/Group/PartyLeavePacket.cs index 9c80ced8..dc9ae4ed 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/PartyLeavePacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/PartyLeavePacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; diff --git a/Map Server/Packets/WorldPackets/Send/Group/PartyModifyPacket.cs b/Map Server/Packets/WorldPackets/Send/Group/PartyModifyPacket.cs index 4b7ece29..a7745ce2 100644 --- a/Map Server/Packets/WorldPackets/Send/Group/PartyModifyPacket.cs +++ b/Map Server/Packets/WorldPackets/Send/Group/PartyModifyPacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; using System.Text; diff --git a/Map Server/Packets/WorldPackets/Send/SessionBeginConfirmPacket.cs b/Map Server/Packets/WorldPackets/Send/SessionBeginConfirmPacket.cs index 2ee8159c..11481336 100644 --- a/Map Server/Packets/WorldPackets/Send/SessionBeginConfirmPacket.cs +++ b/Map Server/Packets/WorldPackets/Send/SessionBeginConfirmPacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; diff --git a/Map Server/Packets/WorldPackets/Send/SessionEndConfirmPacket.cs b/Map Server/Packets/WorldPackets/Send/SessionEndConfirmPacket.cs index eb47c299..4f697e0c 100644 --- a/Map Server/Packets/WorldPackets/Send/SessionEndConfirmPacket.cs +++ b/Map Server/Packets/WorldPackets/Send/SessionEndConfirmPacket.cs @@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using System; using System.IO; From 1523ae200bc763400de2b186ee18defba834327c Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Wed, 16 Feb 2022 15:32:54 -0500 Subject: [PATCH 2/9] Finished quest state system idea --- Common Class Lib/Bitstream.cs | 150 +++++++++++++++ Common Class Lib/Common Class Lib.csproj | 1 + .../chara/npc/populace/PopulaceStandard.lua | 6 +- Data/scripts/quests/etc/etc3g0.lua | 34 ++-- Data/scripts/quests/etc/etc5g0.lua | 10 +- Data/scripts/quests/man/man0l0.lua | 54 +++--- Data/scripts/quests/man/man0l1.lua | 64 +++---- Data/scripts/quests/man/man0u0.lua | 50 ++--- Data/scripts/quests/man/man0u1.lua | 10 +- Data/scripts/quests/man/man2l0.lua | 2 +- Data/scripts/quests/trl/Trl0g1.lua | 29 --- Data/scripts/quests/trl/Trl0l1.lua | 29 --- Data/scripts/quests/trl/Trl0u1.lua | 29 --- Map Server/Actors/Quest/Quest.cs | 181 +++++++----------- Map Server/Actors/Quest/QuestState.cs | 112 +++++++++++ Map Server/Actors/Quest/QuestStateManager.cs | 108 +++++++++++ Map Server/Database.cs | 52 ++++- Map Server/Map Server.csproj | 3 + Map Server/PacketProcessor.cs | 2 +- Map Server/Server.cs | 165 ++++++++++------ Map Server/WorldManager.cs | 2 +- 21 files changed, 720 insertions(+), 373 deletions(-) create mode 100644 Common Class Lib/Bitstream.cs delete mode 100644 Data/scripts/quests/trl/Trl0g1.lua delete mode 100644 Data/scripts/quests/trl/Trl0l1.lua delete mode 100644 Data/scripts/quests/trl/Trl0u1.lua create mode 100644 Map Server/Actors/Quest/QuestState.cs create mode 100644 Map Server/Actors/Quest/QuestStateManager.cs diff --git a/Common Class Lib/Bitstream.cs b/Common Class Lib/Bitstream.cs new file mode 100644 index 00000000..a543a75c --- /dev/null +++ b/Common Class Lib/Bitstream.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Meteor.Common +{ + public class Bitstream + { + private readonly byte[] Data; + + public Bitstream(uint numBits, bool setAllTrue = false) + { + Debug.Assert(numBits % 8 == 0); + Debug.Assert(numBits % 4 == 0); + Data = new byte[numBits / 8]; + + if (setAllTrue) + SetAll(true); + } + + public Bitstream(bool[] boolArray) + { + Data = Utils.ConvertBoolArrayToBinaryStream(boolArray); + } + + private Bitstream(byte[] byteArray) + { + Data = byteArray; + } + + public void SetAll(bool to) + { + for (int i = 0; i < Data.Length; i += 4) + { + Data[i] = Data[i + 1] = Data[i + 2] = Data[i + 3] = (byte)(to ? 0xFF : 0x00); + } + } + + public void SetTo(Bitstream result) + { + Debug.Assert(Data.Length == result.Data.Length); + for (int i = 0; i < result.Data.Length; i += 4) + { + Data[i] = result.Data[i]; + Data[i + 1] = result.Data[i + 1]; + Data[i + 2] = result.Data[i + 2]; + Data[i + 3] = result.Data[i + 3]; + } + } + + public bool Get(uint at) + { + return Get((int)at); + } + + public bool Get(int at) + { + int bytePos = at / 8; + int bitPos = at % 8; + return (Data[bytePos] & (1 << bitPos)) != 0; + } + + public void Set(uint at) + { + Set((int)at); + } + + public void Set(int at) + { + int bytePos = at / 8; + int bitPos = at % 8; + Data[bytePos] |= (byte)(1 << bitPos); + } + + public void Clear(uint at) + { + Clear((int)at); + } + + public void Clear(int at) + { + int bytePos = at / 8; + int bitPos = at % 8; + Data[bytePos] &= (byte)~(1 << bitPos); + } + + public void NOT() + { + for (int i = 0; i < Data.Length; i += 4) + { + Data[i] = (byte)~Data[i]; + Data[i + 1] = (byte)~Data[i + 1]; + Data[i + 2] = (byte)~Data[i + 2]; + Data[i + 3] = (byte)~Data[i + 3]; + } + } + + public void OR(Bitstream other) + { + Debug.Assert(Data.Length == other.Data.Length); + for (int i = 0; i < Data.Length; i += 4) + { + Data[i] |= other.Data[i]; + Data[i + 1] |= other.Data[i + 1]; + Data[i + 2] |= other.Data[i + 2]; + Data[i + 3] |= other.Data[i + 3]; + } + } + + public void AND(Bitstream other) + { + Debug.Assert(Data.Length == other.Data.Length); + for (int i = 0; i < Data.Length; i += 4) + { + Data[i] &= other.Data[i]; + Data[i + 1] &= other.Data[i + 1]; + Data[i + 2] &= other.Data[i + 2]; + Data[i + 3] &= other.Data[i + 3]; + } + } + + public void XOR(Bitstream other) + { + Debug.Assert(Data.Length == other.Data.Length); + for (int i = 0; i < Data.Length; i += 4) + { + Data[i] ^= other.Data[i]; + Data[i + 1] ^= other.Data[i + 1]; + Data[i + 2] ^= other.Data[i + 2]; + Data[i + 3] ^= other.Data[i + 3]; + } + } + + public Bitstream Copy() + { + byte[] copy = new byte[Data.Length]; + Array.Copy(Data, copy, Data.Length); + return new Bitstream(copy); + } + + public byte[] GetBytes() + { + return Data; + } + + } +} diff --git a/Common Class Lib/Common Class Lib.csproj b/Common Class Lib/Common Class Lib.csproj index 1462cb50..5c60a839 100644 --- a/Common Class Lib/Common Class Lib.csproj +++ b/Common Class Lib/Common Class Lib.csproj @@ -87,6 +87,7 @@ + diff --git a/Data/scripts/base/chara/npc/populace/PopulaceStandard.lua b/Data/scripts/base/chara/npc/populace/PopulaceStandard.lua index f69ad19b..77635106 100644 --- a/Data/scripts/base/chara/npc/populace/PopulaceStandard.lua +++ b/Data/scripts/base/chara/npc/populace/PopulaceStandard.lua @@ -23,7 +23,8 @@ end function onEventStarted(player, npc, eventType, eventName) local defaultTalk = player:GetDefaultTalkQuest(npc); - local tutorialTalk = player:GetTutorialQuest(npc); + local tutorialTalk = player:GetTutorialQuest(npc); + local journalQuests = player:GetJournalQuestsForNpc(npc); local activeQuests = player:GetQuestsForNpc(npc); local possibleQuests = {}; @@ -34,6 +35,9 @@ function onEventStarted(player, npc, eventType, eventName) if (tutorialTalk ~= nil and eventType == ETYPE_TALK) then table.insert(possibleQuests, tutorialTalk); end + if (journalQuests ~= nil) then + table.insert(possibleQuests, unpack(journalQuests)); + end if (activeQuests ~= nil) then table.insert(possibleQuests, unpack(activeQuests)); end diff --git a/Data/scripts/quests/etc/etc3g0.lua b/Data/scripts/quests/etc/etc3g0.lua index a8f95077..d5f326b5 100644 --- a/Data/scripts/quests/etc/etc3g0.lua +++ b/Data/scripts/quests/etc/etc3g0.lua @@ -41,8 +41,6 @@ FLAG_TALKED_LEFWYNE = 4; -- Quest Counters COUNTER_TALKED = 0; ---offerQuestResult = callClientFunction(player, "delegateEvent", player, quest, "processEventOffersStart"); - function onStart(player, quest) quest:StartSequence(SEQ_000); end @@ -50,25 +48,39 @@ end function onFinish(player, quest) end -function onSequence(player, quest, sequence) +function onStateChange(player, quest, sequence) + if (sequence == 65536) then + quest:SetENpc(KINNISON, QFLAG_PLATE); + end + if (sequence == SEQ_000) then - quest:AddENpc(KINNISON); - quest:AddENpc(SYBELL, (not quest:GetFlag(FLAG_TALKED_SYBELL) and QFLAG_PLATE or QFLAG_NONE)); - quest:AddENpc(KHUMA_MOSHROCA, (not quest:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA) and QFLAG_PLATE or QFLAG_NONE)); - quest:AddENpc(NELLAURE, (not quest:GetFlag(FLAG_TALKED_NELLAURE) and QFLAG_PLATE or QFLAG_NONE)); - quest:AddENpc(MESTONNAUX, (not quest:GetFlag(FLAG_TALKED_MESTONNAUX) and QFLAG_PLATE or QFLAG_NONE)); - quest:AddENpc(LEFWYNE, (not quest:GetFlag(FLAG_TALKED_LEFWYNE) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(KINNISON); + quest:SetENpc(SYBELL, (not quest:GetFlag(FLAG_TALKED_SYBELL) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(KHUMA_MOSHROCA, (not quest:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(NELLAURE, (not quest:GetFlag(FLAG_TALKED_NELLAURE) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(MESTONNAUX, (not quest:GetFlag(FLAG_TALKED_MESTONNAUX) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(LEFWYNE, (not quest:GetFlag(FLAG_TALKED_LEFWYNE) and QFLAG_PLATE or QFLAG_NONE)); elseif (sequence == SEQ_001) then - quest:AddENpc(KINNISON, QFLAG_PLATE); + quest:SetENpc(KINNISON, QFLAG_PLATE); end end - function onTalk(player, quest, npc, eventName) local npcClassId = npc.GetActorClassId(); local seq = quest:GetSequence(); local incCounter = false; + -- Offer the quest + if (npcClassId == KINNISON and not player:HasQuest(quest)) then + local questAccepted = callClientFunction(player, "delegateEvent", player, quest, "processEventOffersStart"); + if (questAccepted) then + player:AddQuest(quest); + end + player:EndEvent(); + return; + end + + -- Quest Progress if (seq == SEQ_000) then if (npcClassId == KINNISON) then callClientFunction(player, "delegateEvent", player, quest, "processEventOffersAfter"); diff --git a/Data/scripts/quests/etc/etc5g0.lua b/Data/scripts/quests/etc/etc5g0.lua index 4c82338f..08893374 100644 --- a/Data/scripts/quests/etc/etc5g0.lua +++ b/Data/scripts/quests/etc/etc5g0.lua @@ -40,13 +40,13 @@ end -function onSequence(player, quest, sequence) +function onStateChange(player, quest, sequence) if (sequence == SEQ_000) then - quest:AddENpc(VKOROLON); - quest:AddENpc(PFARAHR, QFLAG_PLATE); + quest:SetENpc(VKOROLON); + quest:SetENpc(PFARAHR, QFLAG_PLATE); elseif (sequence == SEQ_001) then - quest:AddENpc(VKOROLON, QFLAG_PLATE); - quest:AddENpc(PFARAHR); + quest:SetENpc(VKOROLON, QFLAG_PLATE); + quest:SetENpc(PFARAHR); end end diff --git a/Data/scripts/quests/man/man0l0.lua b/Data/scripts/quests/man/man0l0.lua index 8e6e7d0a..47217e3b 100644 --- a/Data/scripts/quests/man/man0l0.lua +++ b/Data/scripts/quests/man/man0l0.lua @@ -62,7 +62,7 @@ end function onFinish(player, quest) end -function onSequence(player, quest, sequence) +function onStateChange(player, quest, sequence) if (sequence == SEQ_000) then -- Setup states incase we loaded in. local rostnsthalFlag = quest:GetFlag(FLAG_SEQ000_MINITUT1) and QFLAG_NONE or QFLAG_PLATE; @@ -72,34 +72,34 @@ function onSequence(player, quest, sequence) local exitCanPush = quest:GetFlags() == 0xF; local exitFlag = quest:GetFlags() == 0xF and QFLAG_MAP or QFLAG_NONE; - quest:AddENpc(WELLTRAVELED_MERCHANT); - quest:AddENpc(TIPSY_ADVENTURER); - quest:AddENpc(CULTIVATED_TENDER); - quest:AddENpc(ANXIOUS_ADVENTURER); - quest:AddENpc(BABYFACED_ADVENTURER, babyfaceFlag); - quest:AddENpc(AUSTERE_ADVENTURER); - quest:AddENpc(UNDIGNIFIED_ADVENTURER); - quest:AddENpc(SHADOWY_TRAVELER); - quest:AddENpc(ASTUTE_MERCHANT); - quest:AddENpc(VOLUPTUOUS_VIXEN, vixenFlag); - quest:AddENpc(INDIFFERENT_PASSERBY); - quest:AddENpc(PRATTLING_ADVENTURER); - quest:AddENpc(LANKY_TRAVELER); - quest:AddENpc(GRINNING_ADVENTURER); - quest:AddENpc(ROSTNSTHAL, rostnsthalFlag, true, rostnsthalCanPush); - quest:AddENpc(EXIT_TRIGGER, exitFlag, false, exitCanPush); + quest:SetENpc(WELLTRAVELED_MERCHANT); + quest:SetENpc(TIPSY_ADVENTURER); + quest:SetENpc(CULTIVATED_TENDER); + quest:SetENpc(ANXIOUS_ADVENTURER); + quest:SetENpc(BABYFACED_ADVENTURER, babyfaceFlag); + quest:SetENpc(AUSTERE_ADVENTURER); + quest:SetENpc(UNDIGNIFIED_ADVENTURER); + quest:SetENpc(SHADOWY_TRAVELER); + quest:SetENpc(ASTUTE_MERCHANT); + quest:SetENpc(VOLUPTUOUS_VIXEN, vixenFlag); + quest:SetENpc(INDIFFERENT_PASSERBY); + quest:SetENpc(PRATTLING_ADVENTURER); + quest:SetENpc(LANKY_TRAVELER); + quest:SetENpc(GRINNING_ADVENTURER); + quest:SetENpc(ROSTNSTHAL, rostnsthalFlag, true, rostnsthalCanPush); + quest:SetENpc(EXIT_TRIGGER, exitFlag, false, exitCanPush); elseif (sequence == SEQ_005) then elseif (sequence == SEQ_010) then - quest:AddENpc(HOB); - quest:AddENpc(GERT); - quest:AddENpc(LORHZANT); - quest:AddENpc(MUSCLEBOUND_DECKHAND); - quest:AddENpc(PEARLYTOOTHED_PORTER); - quest:AddENpc(UNDIGNIFIED_ADVENTURER); - quest:AddENpc(WELLTRAVELED_MERCHANT); - quest:AddENpc(VOLUPTUOUS_VIXEN); - quest:AddENpc(LANKY_TRAVELER); - quest:AddENpc(PRIVAREA_PAST_EXIT, QFLAG_NONE, false, true); + quest:SetENpc(HOB); + quest:SetENpc(GERT); + quest:SetENpc(LORHZANT); + quest:SetENpc(MUSCLEBOUND_DECKHAND); + quest:SetENpc(PEARLYTOOTHED_PORTER); + quest:SetENpc(UNDIGNIFIED_ADVENTURER); + quest:SetENpc(WELLTRAVELED_MERCHANT); + quest:SetENpc(VOLUPTUOUS_VIXEN); + quest:SetENpc(LANKY_TRAVELER); + quest:SetENpc(PRIVAREA_PAST_EXIT, QFLAG_NONE, false, true); end end diff --git a/Data/scripts/quests/man/man0l1.lua b/Data/scripts/quests/man/man0l1.lua index 9ced680f..e0a38b1d 100644 --- a/Data/scripts/quests/man/man0l1.lua +++ b/Data/scripts/quests/man/man0l1.lua @@ -85,56 +85,56 @@ end function onFinish(player, quest) end -function onSequence(player, quest, sequence) +function onStateChange(player, quest, sequence) if (sequence == SEQ_000) then - quest:AddENpc(YSHTOLA); - quest:AddENpc(CRAPULOUS_ADVENTURER); - quest:AddENpc(DUPLICITOUS_TRADER); - quest:AddENpc(DEBONAIR_PIRATE); - quest:AddENpc(ONYXHAIRED_ADVENTURER); - quest:AddENpc(SKITTISH_ADVENTURER); - quest:AddENpc(RELAXING_ADVENTURER); - quest:AddENpc(BADERON, QFLAG_PLATE); - quest:AddENpc(MYTESYN); - quest:AddENpc(COCKAHOOP_COCKSWAIN); - quest:AddENpc(SENTENIOUS_SELLSWORD); - quest:AddENpc(SOLICITOUS_SELLSWORD); + quest:SetENpc(YSHTOLA); + quest:SetENpc(CRAPULOUS_ADVENTURER); + quest:SetENpc(DUPLICITOUS_TRADER); + quest:SetENpc(DEBONAIR_PIRATE); + quest:SetENpc(ONYXHAIRED_ADVENTURER); + quest:SetENpc(SKITTISH_ADVENTURER); + quest:SetENpc(RELAXING_ADVENTURER); + quest:SetENpc(BADERON, QFLAG_PLATE); + quest:SetENpc(MYTESYN); + quest:SetENpc(COCKAHOOP_COCKSWAIN); + quest:SetENpc(SENTENIOUS_SELLSWORD); + quest:SetENpc(SOLICITOUS_SELLSWORD); elseif (sequence == SEQ_003) then - quest:AddENpc(BADERON); + quest:SetENpc(BADERON); elseif (sequence == SEQ_005) then - quest:AddENpc(BADERON, QFLAG_PLATE); + quest:SetENpc(BADERON, QFLAG_PLATE); elseif (sequence == SEQ_006) then - quest:AddENpc(BADERON, QFLAG_PLATE); + quest:SetENpc(BADERON, QFLAG_PLATE); elseif (sequence == SEQ_007) then local subseqCUL = quest:GetCounter(CNTR_SEQ7_CUL); local subseqMRD = quest:GetCounter(CNTR_SEQ7_MRD); -- Always active in this seqence - quest:AddENpc(BADERON); - quest:AddENpc(CHARLYS, subseqCUL == 0 and QFLAG_PLATE or QFLAG_NONE); + quest:SetENpc(BADERON); + quest:SetENpc(CHARLYS, subseqCUL == 0 and QFLAG_PLATE or QFLAG_NONE); -- Down and Up the MSK guild - quest:AddENpc(ISANDOREL, (subseqMRD == 0 or subseqMRD == 2) and QFLAG_PLATE or QFLAG_NONE); + quest:SetENpc(ISANDOREL, (subseqMRD == 0 or subseqMRD == 2) and QFLAG_PLATE or QFLAG_NONE); if (subseqMRD == 1) then - quest:AddENpc(MSK_TRIGGER, QFLAG_MAP, false, true); + quest:SetENpc(MSK_TRIGGER, QFLAG_MAP, false, true); elseif (subseqMRD == 2) then - quest:AddENpc(MERLZIRN); + quest:SetENpc(MERLZIRN); end -- In Echo - quest:AddENpc(NERVOUS_BARRACUDA); - quest:AddENpc(INTIMIDATING_BARRACUDA); - quest:AddENpc(OVEREAGER_BARRACUDA); - quest:AddENpc(SOPHISTICATED_BARRACUDA); - quest:AddENpc(SMIRKING_BARRACUDA); - quest:AddENpc(MANNSKOEN); - quest:AddENpc(TOTORUTO); - quest:AddENpc(ADVENTURER1); - quest:AddENpc(ADVENTURER2); - quest:AddENpc(ADVENTURER3); - quest:AddENpc(ECHO_EXIT_TRIGGER, subseqMRD == 3 and QFLAG_MAP or QFLAG_NONE, false, subseqMRD == 3); + quest:SetENpc(NERVOUS_BARRACUDA); + quest:SetENpc(INTIMIDATING_BARRACUDA); + quest:SetENpc(OVEREAGER_BARRACUDA); + quest:SetENpc(SOPHISTICATED_BARRACUDA); + quest:SetENpc(SMIRKING_BARRACUDA); + quest:SetENpc(MANNSKOEN); + quest:SetENpc(TOTORUTO); + quest:SetENpc(ADVENTURER1); + quest:SetENpc(ADVENTURER2); + quest:SetENpc(ADVENTURER3); + quest:SetENpc(ECHO_EXIT_TRIGGER, subseqMRD == 3 and QFLAG_MAP or QFLAG_NONE, false, subseqMRD == 3); if (subseqCUL == 1 and subseqMRD == 4) then player:SetNpcLS(1, 1); diff --git a/Data/scripts/quests/man/man0u0.lua b/Data/scripts/quests/man/man0u0.lua index a8f8e70c..4eab0227 100644 --- a/Data/scripts/quests/man/man0u0.lua +++ b/Data/scripts/quests/man/man0u0.lua @@ -103,7 +103,7 @@ end function onFinish(player, quest) end -function onSequence(player, quest, sequence) +function onStateChange(player, quest, sequence) if (sequence == SEQ_000) then -- Setup states incase we loaded in. @@ -119,34 +119,34 @@ function onSequence(player, quest, sequence) gildiggingmistressFlag = QFLAG_NONE; end - --AddENpc(classId, byte flagType=0,isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned) - quest:AddENpc(ASCILIA, asciliaFlag, true, asciliaCanPush); - quest:AddENpc(WARBURTON); - quest:AddENpc(RURURAJI); - quest:AddENpc(BIG_BELLIED_BARKER); - quest:AddENpc(FRETFUL_FARMHAND, fretfulfarmhandFlag); - quest:AddENpc(DEBAUCHED_DEMONESS); - quest:AddENpc(DAPPER_DAN); - quest:AddENpc(LOUTISH_LAD); - quest:AddENpc(GIL_DIGGING_MISTRESS, gildiggingmistressFlag); - quest:AddENpc(TWITTERING_TOMBOY); - quest:AddENpc(STOCKY_STRANGER); - quest:AddENpc(EXIT_TRIGGER, exitFlag, false, true); - quest:AddENpc(OPENING_STOPER_ULDAH, QFLAG_NONE, false, false, true); + --SetENpc(classId, byte flagType=0,isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned) + quest:SetENpc(ASCILIA, asciliaFlag, true, asciliaCanPush); + quest:SetENpc(WARBURTON); + quest:SetENpc(RURURAJI); + quest:SetENpc(BIG_BELLIED_BARKER); + quest:SetENpc(FRETFUL_FARMHAND, fretfulfarmhandFlag); + quest:SetENpc(DEBAUCHED_DEMONESS); + quest:SetENpc(DAPPER_DAN); + quest:SetENpc(LOUTISH_LAD); + quest:SetENpc(GIL_DIGGING_MISTRESS, gildiggingmistressFlag); + quest:SetENpc(TWITTERING_TOMBOY); + quest:SetENpc(STOCKY_STRANGER); + quest:SetENpc(EXIT_TRIGGER, exitFlag, false, true); + quest:SetENpc(OPENING_STOPER_ULDAH, QFLAG_NONE, false, false, true); elseif (sequence == SEQ_010) then local yayatokiFlag = quest:GetFlag(FLAG_SEQ010_TALK0) and QFLAG_NONE or QFLAG_PLATE; local uldahopeningexitFlag = QFLAG_MAP; - quest:AddENpc(KEEN_EYED_MERCHANT); - quest:AddENpc(HIGH_SPIRITED_FELLOW); - quest:AddENpc(DISREPUTABLE_MIDLANDER); - quest:AddENpc(LONG_LEGGED_LADY); - quest:AddENpc(LARGE_LUNGED_LABORER); - quest:AddENpc(TOOTH_GRINDING_TRAVELER); - quest:AddENpc(FULL_LIPPED_FILLE); - quest:AddENpc(YAYATOKI, yayatokiFlag); - quest:AddENpc(BLOCKER, QFLAG_NONE, false, true); - quest:AddENpc(ULDAH_OPENING_EXIT, uldahopeningexitFlag, false, true); + quest:SetENpc(KEEN_EYED_MERCHANT); + quest:SetENpc(HIGH_SPIRITED_FELLOW); + quest:SetENpc(DISREPUTABLE_MIDLANDER); + quest:SetENpc(LONG_LEGGED_LADY); + quest:SetENpc(LARGE_LUNGED_LABORER); + quest:SetENpc(TOOTH_GRINDING_TRAVELER); + quest:SetENpc(FULL_LIPPED_FILLE); + quest:SetENpc(YAYATOKI, yayatokiFlag); + quest:SetENpc(BLOCKER, QFLAG_NONE, false, true); + quest:SetENpc(ULDAH_OPENING_EXIT, uldahopeningexitFlag, false, true); end end diff --git a/Data/scripts/quests/man/man0u1.lua b/Data/scripts/quests/man/man0u1.lua index 1de8bf9d..6fc8a6ad 100644 --- a/Data/scripts/quests/man/man0u1.lua +++ b/Data/scripts/quests/man/man0u1.lua @@ -127,17 +127,17 @@ end function onFinish(player, quest) end -function onSequence(player, quest, sequence) +function onStateChange(player, quest, sequence) if (sequence == SEQ_000) then -- Setup states incase we loaded in. - --AddENpc(classId, byte flagType=0,isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned) - quest:AddENpc(MOMODI, QFLAG_PLATE); - quest:AddENpc(OTOPA_POTTOPA); + --SetENpc(classId, byte flagType=0,isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned) + quest:SetENpc(MOMODI, QFLAG_PLATE); + quest:SetENpc(OTOPA_POTTOPA); elseif (sequence == SEQ_005) then - quest:AddENpc(MOMODI); + quest:SetENpc(MOMODI); end end diff --git a/Data/scripts/quests/man/man2l0.lua b/Data/scripts/quests/man/man2l0.lua index cffda573..b5788ee7 100644 --- a/Data/scripts/quests/man/man2l0.lua +++ b/Data/scripts/quests/man/man2l0.lua @@ -7,7 +7,7 @@ end function onFinish(player, quest) end -function onSequence(player, quest, seqNum) +function onStateChange(player, quest, seqNum) quest:ClearENpcs(); end diff --git a/Data/scripts/quests/trl/Trl0g1.lua b/Data/scripts/quests/trl/Trl0g1.lua deleted file mode 100644 index e07ec868..00000000 --- a/Data/scripts/quests/trl/Trl0g1.lua +++ /dev/null @@ -1,29 +0,0 @@ -require ("global") - ---[[ - -Quest Script - -Name: Getting Started (Mother Miounne) -Code: Trl0g1 -Id: 110141 - -Enables the "Getting Started" option on Miounne. -* NOTE: This quest is active for all players at all times. -]] - -function onTalk(player, quest, npc, eventName) - local choice = callClientFunction(player, "delegateEvent", player, quest, "processEventMiounneStart"); - - if (choice == 1) then - callClientFunction(player, "delegateEvent", player, quest, "processEvent225"); - elseif (choice == 2) then - callClientFunction(player, "delegateEvent", player, quest, "processEvent230"); - end - - player:EndEvent(); -end - -function IsQuestENPC(player, quest, npc) - return npc:GetActorClassId()] == 1000230; -end \ No newline at end of file diff --git a/Data/scripts/quests/trl/Trl0l1.lua b/Data/scripts/quests/trl/Trl0l1.lua deleted file mode 100644 index 6e8ac0f5..00000000 --- a/Data/scripts/quests/trl/Trl0l1.lua +++ /dev/null @@ -1,29 +0,0 @@ -require ("global") - ---[[ - -Quest Script - -Name: Getting Started (Baderon) -Code: Trl0l1 -Id: 110140 - -Enables the "Getting Started" option on Baderon. -* NOTE: This quest is active for all players at all times. -]] - -function onTalk(player, quest, npc, eventName) - local choice = callClientFunction(player, "delegateEvent", player, quest, "processEventBaderonStart"); - - if (choice == 1) then - callClientFunction(player, "delegateEvent", player, quest, "processEvent640"); - elseif (choice == 2) then - callClientFunction(player, "delegateEvent", player, quest, "processEvent650"); - end - - player:EndEvent(); -end - -function IsQuestENPC(player, quest, npc) - return npc:GetActorClassId()] == 1000137; -end \ No newline at end of file diff --git a/Data/scripts/quests/trl/Trl0u1.lua b/Data/scripts/quests/trl/Trl0u1.lua deleted file mode 100644 index bf5734d0..00000000 --- a/Data/scripts/quests/trl/Trl0u1.lua +++ /dev/null @@ -1,29 +0,0 @@ -require ("global") - ---[[ - -Quest Script - -Name: Getting Started (Momodi) -Code: Trl0u1 -Id: 110142 - -Enables the "Getting Started" option on Momodi. -* NOTE: This quest is active for all players at all times. -]] - -function onTalk(player, quest, npc, eventName) - local choice = callClientFunction(player, "delegateEvent", player, quest, "processEventMomodiStart"); - - if (choice == 1) then - callClientFunction(player, "delegateEvent", player, quest, "processEvent235"); - elseif (choice == 2) then - callClientFunction(player, "delegateEvent", player, quest, "processEvent240"); - end - - player:EndEvent(); -end - -function IsQuestENPC(player, quest, npc) - return npc:GetActorClassId() == 1000841; -end \ No newline at end of file diff --git a/Map Server/Actors/Quest/Quest.cs b/Map Server/Actors/Quest/Quest.cs index 7cd6e3e5..67d246df 100644 --- a/Map Server/Actors/Quest/Quest.cs +++ b/Map Server/Actors/Quest/Quest.cs @@ -28,48 +28,8 @@ namespace Meteor.Map.Actors { class Quest : Actor { - public const ushort SEQ_NOT_STARTED = ushort.MaxValue; - - public enum QuestFlag { None = 0, Map = 1, Plate = 2 } - public enum ENpcProperty { QuestFlag = 0, CanTalk = 1, CanPush = 2, CanEmote = 3, CanNotice = 4} - - public class ENpcQuestInstance - { - public readonly uint actorClassId; - public byte questFlagType { set; get; } - public bool isSpawned { set; get; } - public bool isTalkEnabled { set; get; } - public bool isEmoteEnabled { set; get; } - public bool isPushEnabled { set; get; } - - public ENpcQuestInstance(uint actorClassId, byte questFlagType, bool isSpawned, bool isTalkEnabled, bool isEmoteEnabled, bool isPushEnabled) - { - this.actorClassId = actorClassId; - this.questFlagType = questFlagType; - this.isSpawned = isSpawned; - this.isTalkEnabled = isTalkEnabled; - this.isEmoteEnabled = isEmoteEnabled; - this.isPushEnabled = isPushEnabled; - } - - public bool IsChanged(byte flagType, bool isTalkEnabled, bool isPushEnabled, bool isEmoteEnabled, bool isSpawned) - { - return flagType != this.questFlagType - || isTalkEnabled != this.isTalkEnabled - || isPushEnabled != this.isPushEnabled - || isEmoteEnabled != this.isEmoteEnabled - || isSpawned != this.isSpawned; - } - - public void Update(byte flagType, bool isTalkEnabled, bool isPushEnabled, bool isEmoteEnabled, bool isSpawned) - { - this.questFlagType = flagType; - this.isSpawned = isSpawned; - this.isTalkEnabled = isTalkEnabled; - this.isEmoteEnabled = isEmoteEnabled; - this.isPushEnabled = isPushEnabled; - } - } + public const ushort SEQ_NOT_STARTED = 65535; + public const ushort SEQ_COMPLETED = 65534; private struct QuestData { @@ -88,43 +48,37 @@ namespace Meteor.Map.Actors } } + // This is only set on instance quests (non static) private Player Owner; private ushort currentSequence; - private QuestData data = new QuestData(); + private QuestState QuestState; + private QuestData Data; private bool dataDirty = false; - private Dictionary CurrentENPCs = new Dictionary(); - private Dictionary OldENPCs = new Dictionary(); - public void AddENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false) + public void SetENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false) { - ENpcQuestInstance instanceUpdated = null; - - if (OldENPCs.ContainsKey(classId)) - { - if (OldENPCs[classId].IsChanged(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned)) - { - OldENPCs[classId].Update(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned); - instanceUpdated = OldENPCs[classId]; - } - - CurrentENPCs.Add(classId, OldENPCs[classId]); - OldENPCs.Remove(classId); - } - else - { - instanceUpdated = new ENpcQuestInstance(classId, flagType, isSpawned, isTalkEnabled, isEmoteEnabled, isPushEnabled); - CurrentENPCs.Add(classId, instanceUpdated); - } - - if (instanceUpdated != null) - Owner.playerSession.UpdateQuestNpcInInstance(instanceUpdated); + if (QuestState != null) + QuestState.AddENpc(classId, flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned); } - - public ENpcQuestInstance GetENpcInstance(uint classId) + + public void UpdateENPCs() { - if (CurrentENPCs.ContainsKey(classId)) - return CurrentENPCs[classId]; - return null; + if (dataDirty) + { + if (QuestState != null) + QuestState.UpdateState(); + dataDirty = false; + } + } + + public QuestState GetQuestState() + { + return QuestState; + } + + public bool IsInstance() + { + return Owner != null; } public void OnTalk(Player caller, Npc npc) @@ -152,25 +106,11 @@ namespace Meteor.Map.Actors LuaEngine.GetInstance().CallLuaFunction(caller, this, "onNpcLS", true, npcLSId); } - public void UpdateENPCs() - { - if (dataDirty) - { - OldENPCs = CurrentENPCs; - CurrentENPCs = new Dictionary(); - LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "onSequence", false, currentSequence); - foreach (var enpc in OldENPCs) - Owner.playerSession.UpdateQuestNpcInInstance(enpc.Value); - OldENPCs = null; - dataDirty = false; - } - } - public bool IsQuestENPC(Player caller, Npc npc) { List returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(caller, this, "IsQuestENPC", true, npc, this); bool scriptReturned = returned != null && returned.Count != 0 && returned[0].typeID == 3; - return scriptReturned || CurrentENPCs.ContainsKey(npc.GetActorClassId()); + return scriptReturned || QuestState.HasENpc(npc.GetActorClassId()); } @@ -213,14 +153,14 @@ namespace Meteor.Map.Actors public void ClearData() { - data.flags = data.counter1 = data.counter2 = data.counter3 = data.counter4 = 0; + Data.flags = Data.counter1 = Data.counter2 = Data.counter3 = Data.counter4 = 0; } public void SetFlag(int index) { if (index >= 0 && index < 32) { - data.flags |= (uint)(1 << index); + Data.flags |= (uint)(1 << index); dataDirty = true; } } @@ -229,7 +169,7 @@ namespace Meteor.Map.Actors { if (index >= 0 && index < 32) { - data.flags &= (uint)~(1 << index); + Data.flags &= (uint)~(1 << index); dataDirty = true; } } @@ -241,16 +181,16 @@ namespace Meteor.Map.Actors switch (num) { case 0: - data.counter1++; + Data.counter1++; return; case 1: - data.counter2++; + Data.counter2++; return; case 2: - data.counter3++; + Data.counter3++; return; case 3: - data.counter4++; + Data.counter4++; return; } @@ -264,16 +204,16 @@ namespace Meteor.Map.Actors switch (num) { case 0: - data.counter1--; + Data.counter1--; return; case 1: - data.counter2--; + Data.counter2--; return; case 2: - data.counter3--; + Data.counter3--; return; case 3: - data.counter4--; + Data.counter4--; return; } @@ -287,16 +227,16 @@ namespace Meteor.Map.Actors switch (num) { case 0: - data.counter1 = value; + Data.counter1 = value; return; case 1: - data.counter2 = value; + Data.counter2 = value; return; case 2: - data.counter3 = value; + Data.counter3 = value; return; case 3: - data.counter4 = value; + Data.counter4 = value; return; } @@ -306,13 +246,13 @@ namespace Meteor.Map.Actors public bool GetFlag(int index) { if (index >= 0 && index < 32) - return (data.flags & (uint) (1 << index)) != 0; + return (Data.flags & (uint) (1 << index)) != 0; return false; } public uint GetFlags() { - return data.flags; + return Data.flags; } public ushort GetCounter(int num) @@ -320,13 +260,13 @@ namespace Meteor.Map.Actors switch (num) { case 0: - return data.counter1; + return Data.counter1; case 1: - return data.counter2; + return Data.counter2; case 2: - return data.counter3; + return Data.counter3; case 3: - return data.counter4; + return Data.counter4; } return 0; @@ -354,12 +294,8 @@ namespace Meteor.Map.Actors className = baseQuest.className; classPath = baseQuest.classPath; currentSequence = sequence; - data = new QuestData(flags, counter1, counter2, counter3); - - if (currentSequence == SEQ_NOT_STARTED) - LuaEngine.GetInstance().CallLuaFunction(Owner, this, "onStart", false); - else - StartSequence(currentSequence); + QuestState = new QuestState(owner, this); + Data = new QuestData(flags, counter1, counter2, counter3); } public uint GetQuestId() @@ -367,17 +303,34 @@ namespace Meteor.Map.Actors return Id & 0xFFFFF; } + public void DoAccept() + { + if (currentSequence == SEQ_NOT_STARTED) + LuaEngine.GetInstance().CallLuaFunction(Owner, this, "onStart", false); + else + StartSequence(currentSequence); + } + public void DoComplete() { LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "onFinish", true); Owner.SendDataPacket("attention", Server.GetWorldManager().GetActor(), "", 25225, (object)GetQuestId()); Owner.SendGameMessage(Server.GetWorldManager().GetActor(), 25225, 0x20, (object)GetQuestId()); + currentSequence = SEQ_COMPLETED; } public void DoAbandon() { LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "onFinish", false); Owner.SendGameMessage(Owner, Server.GetWorldManager().GetActor(), 25236, 0x20, (object)GetQuestId()); + currentSequence = SEQ_NOT_STARTED; + } + + public override bool Equals(object obj) + { + if (obj is Quest quest) + return quest.Id == this.Id; + return false; } } } diff --git a/Map Server/Actors/Quest/QuestState.cs b/Map Server/Actors/Quest/QuestState.cs new file mode 100644 index 00000000..068083cd --- /dev/null +++ b/Map Server/Actors/Quest/QuestState.cs @@ -0,0 +1,112 @@ +using Meteor.Map.lua; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Meteor.Map.Actors +{ + class QuestState + { + public enum QuestFlag { None = 0, Map = 1, Plate = 2 } + + public class QuestENpc + { + public readonly uint actorClassId; + public byte questFlagType { set; get; } + public bool isSpawned { set; get; } + public bool isTalkEnabled { set; get; } + public bool isEmoteEnabled { set; get; } + public bool isPushEnabled { set; get; } + + public QuestENpc(uint actorClassId, byte questFlagType, bool isSpawned, bool isTalkEnabled, bool isEmoteEnabled, bool isPushEnabled) + { + this.actorClassId = actorClassId; + this.questFlagType = questFlagType; + this.isSpawned = isSpawned; + this.isTalkEnabled = isTalkEnabled; + this.isEmoteEnabled = isEmoteEnabled; + this.isPushEnabled = isPushEnabled; + } + + public bool IsChanged(byte flagType, bool isTalkEnabled, bool isPushEnabled, bool isEmoteEnabled, bool isSpawned) + { + return flagType != this.questFlagType + || isTalkEnabled != this.isTalkEnabled + || isPushEnabled != this.isPushEnabled + || isEmoteEnabled != this.isEmoteEnabled + || isSpawned != this.isSpawned; + } + + public void Update(byte flagType, bool isTalkEnabled, bool isPushEnabled, bool isEmoteEnabled, bool isSpawned) + { + this.questFlagType = flagType; + this.isSpawned = isSpawned; + this.isTalkEnabled = isTalkEnabled; + this.isEmoteEnabled = isEmoteEnabled; + this.isPushEnabled = isPushEnabled; + } + } + + private Player Owner; + private Quest Parent; + private Dictionary CurrentENPCs = new Dictionary(); + private Dictionary OldENPCs = new Dictionary(); + + public QuestState(Player owner, Quest parent) + { + Owner = owner; + Parent = parent; + UpdateState(); + } + + public void AddENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false) + { + QuestENpc instanceUpdated = null; + + if (OldENPCs.ContainsKey(classId)) + { + if (OldENPCs[classId].IsChanged(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned)) + { + OldENPCs[classId].Update(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned); + instanceUpdated = OldENPCs[classId]; + } + + CurrentENPCs.Add(classId, OldENPCs[classId]); + OldENPCs.Remove(classId); + } + else + { + instanceUpdated = new QuestENpc(classId, flagType, isSpawned, isTalkEnabled, isEmoteEnabled, isPushEnabled); + CurrentENPCs.Add(classId, instanceUpdated); + } + + if (instanceUpdated != null) + Owner.playerSession.UpdateQuestNpcInInstance(instanceUpdated); + } + + public QuestENpc GetENpc(uint classId) + { + if (CurrentENPCs.ContainsKey(classId)) + return CurrentENPCs[classId]; + return null; + } + + public bool HasENpc(uint classId) + { + return CurrentENPCs.ContainsKey(classId); + } + + public void UpdateState() + { + ushort currentSeq = Parent.GetSequence(); + OldENPCs = CurrentENPCs; + CurrentENPCs = new Dictionary(); + LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, Parent, "onStateChange", false, currentSeq); + foreach (var enpc in OldENPCs) + Owner.playerSession.UpdateQuestNpcInInstance(enpc.Value); + OldENPCs = null; + } + } +} diff --git a/Map Server/Actors/Quest/QuestStateManager.cs b/Map Server/Actors/Quest/QuestStateManager.cs new file mode 100644 index 00000000..e2e2bf7d --- /dev/null +++ b/Map Server/Actors/Quest/QuestStateManager.cs @@ -0,0 +1,108 @@ +using Meteor.Common; +using Meteor.Map.DataObjects; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Meteor.Map.Actors +{ + class QuestStateManager + { + private const int SCENARIO_START = 110001; + private const int SCENARIO_MAX = 2048; + + private readonly Player player; + private readonly Bitstream AvailableQuestsBitfield = new Bitstream(SCENARIO_MAX); + private readonly Bitstream MinLevelBitfield = new Bitstream(SCENARIO_MAX); + private readonly Bitstream PrereqBitfield = new Bitstream(SCENARIO_MAX, true); + private readonly Bitstream GCRankBitfield = new Bitstream(SCENARIO_MAX, true); + + private List ActiveQuests = new List(); + + public QuestStateManager(Player player) + { + this.player = player; + } + + public void Init() + { + // Init MinLv + QuestData[] minLvl = Server.GetQuestGamedataByMaxLvl(player.GetHighestLevel(), true); + foreach (var questData in minLvl) + MinLevelBitfield.Set(questData.Id - SCENARIO_START); + + // Init Prereq + Bitstream completed = new Bitstream(player.playerWork.questScenarioComplete); + foreach (var questData in Server.GetQuestGamedataAllPrerequisite()) + { + if (completed.Get(((Quest)Server.GetStaticActors(0xA0F00000 | questData.PrerequisiteQuest)).GetQuestId() - SCENARIO_START)) + PrereqBitfield.Set(questData.Id - SCENARIO_START); + else + PrereqBitfield.Clear(questData.Id - SCENARIO_START); + } + ComputeAvailable(); + } + + public void UpdateLevel(int level) + { + QuestData[] updated = Server.GetQuestGamedataByMaxLvl(level); + foreach (var questData in updated) + MinLevelBitfield.Set(questData.Id - SCENARIO_START); + ComputeAvailable(); + } + + public void UpdateQuestComplete(Quest quest) + { + QuestData[] updated = Server.GetQuestGamedataByPrerequisite(quest.GetQuestId()); + foreach (var questData in updated) + PrereqBitfield.Set(questData.Id - SCENARIO_START); + ComputeAvailable(); + } + + public void QuestAdded(Quest quest) + { + ActiveQuests.Remove(quest); + } + + private void ComputeAvailable() + { + Bitstream result = new Bitstream(player.playerWork.questScenarioComplete); + result.NOT(); + result.AND(MinLevelBitfield); + result.AND(PrereqBitfield); + result.AND(GCRankBitfield); + + Bitstream difference = AvailableQuestsBitfield.Copy(); + difference.XOR(result); + byte[] diffBytes = difference.GetBytes(); + + for (int i = 0; i < diffBytes.Length; i++) + { + if (diffBytes[i] == 0) + continue; + for (int shift = 0; shift < 8; shift++) + { + if ((diffBytes[i] >> shift & 1) == 1) + { + int index = i * 8 + shift; + Quest quest = (Quest)Server.GetStaticActors(0xA0F00000 | (SCENARIO_START + (uint)index)); + if (!AvailableQuestsBitfield.Get(index)) + ActiveQuests.Add(new Quest(player, quest)); + else + ActiveQuests.Remove(quest); + } + } + } + + AvailableQuestsBitfield.SetTo(result); + } + + public Quest[] GetQuestsForNpc(Npc npc) + { + return ActiveQuests.FindAll(quest => quest.IsQuestENPC(player, npc)).ToArray(); + } + } +} diff --git a/Map Server/Database.cs b/Map Server/Database.cs index ecf7eec0..129062ba 100644 --- a/Map Server/Database.cs +++ b/Map Server/Database.cs @@ -26,7 +26,7 @@ using Meteor.Common; using Meteor.Map.utils; using Meteor.Map.packets.send.player; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.Actors; using Meteor.Map.actors.chara.player; using Meteor.Map.packets.receive.supportdesk; @@ -72,6 +72,56 @@ namespace Meteor.Map return id; } + public static Dictionary GetQuestGamedata() + { + using (var conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) + { + Dictionary gamedataQuests = new Dictionary(); + + try + { + conn.Open(); + + string query = @" + SELECT + id, + className, + questName, + prerequisite, + minLevel, + minGCRank + FROM gamedata_quests + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + uint questId = reader.GetUInt32("id"); + string code = reader.GetString("className"); + string name = reader.GetString("questName"); + uint prerequisite = reader.GetUInt32("prerequisite"); + ushort minLevel = reader.GetUInt16("minLevel"); + ushort minRank = reader.GetUInt16("minGCRank"); + gamedataQuests.Add(questId, new QuestData(questId, code, name, prerequisite, minLevel, minRank)); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + + return gamedataQuests; + } + } + public static Dictionary GetItemGamedata() { using (var conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) diff --git a/Map Server/Map Server.csproj b/Map Server/Map Server.csproj index 8c6639a5..8d421cd9 100644 --- a/Map Server/Map Server.csproj +++ b/Map Server/Map Server.csproj @@ -180,9 +180,12 @@ + + + diff --git a/Map Server/PacketProcessor.cs b/Map Server/PacketProcessor.cs index 6e6619d5..c36f761a 100644 --- a/Map Server/PacketProcessor.cs +++ b/Map Server/PacketProcessor.cs @@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see . using Meteor.Common; using System; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.packets.receive; using Meteor.Map.packets.send; using Meteor.Map.packets.send.login; diff --git a/Map Server/Server.cs b/Map Server/Server.cs index 8bb8f1c9..af4724bf 100644 --- a/Map Server/Server.cs +++ b/Map Server/Server.cs @@ -23,10 +23,11 @@ using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Common; using Meteor.Map.Actors; +using System.Linq; namespace Meteor.Map { @@ -38,52 +39,56 @@ namespace Meteor.Map public const string STATIC_ACTORS_PATH = "./staticactors.bin"; - private static Server mSelf; + private static Server _Self; - private Socket mServerSocket; + private Socket ServerSocket; - private Dictionary mSessionList = new Dictionary(); + private Dictionary SessionList = new Dictionary(); - private static CommandProcessor mCommandProcessor = new CommandProcessor(); - private static ZoneConnection mWorldConnection = new ZoneConnection(); - private static WorldManager mWorldManager; - private static Dictionary mGamedataItems; - private static Dictionary mGamedataGuildleves; - private static StaticActors mStaticActors; + private static CommandProcessor CommandProcessor = new CommandProcessor(); + private static ZoneConnection WorldConnection = new ZoneConnection(); + private static WorldManager WorldManager; + private static Dictionary GamedataItems; + private static Dictionary GamedataGuildleves; + private static Dictionary GamedataQuests; + private static StaticActors StaticActors; private PacketProcessor mProcessor; public Server() { - mSelf = this; + _Self = this; } public bool StartServer() { - mStaticActors = new StaticActors(STATIC_ACTORS_PATH); + StaticActors = new StaticActors(STATIC_ACTORS_PATH); - mGamedataItems = Database.GetItemGamedata(); - Program.Log.Info("Loaded {0} items.", mGamedataItems.Count); - mGamedataGuildleves = Database.GetGuildleveGamedata(); - Program.Log.Info("Loaded {0} guildleves.", mGamedataGuildleves.Count); + Program.Log.Info("Loading gamedata..."); + GamedataItems = Database.GetItemGamedata(); + Program.Log.Info("Loaded {0} items.", GamedataItems.Count); + GamedataGuildleves = Database.GetGuildleveGamedata(); + Program.Log.Info("Loaded {0} guildleves.", GamedataGuildleves.Count); + GamedataQuests = Database.GetQuestGamedata(); + Program.Log.Info("Loaded {0} quests.", GamedataQuests.Count); - mWorldManager = new WorldManager(this); - mWorldManager.LoadZoneList(); - mWorldManager.LoadSeamlessBoundryList(); - mWorldManager.LoadActorClasses(); - mWorldManager.LoadENPCs(); - mWorldManager.LoadBattleNpcs(); - mWorldManager.LoadStatusEffects(); - mWorldManager.LoadBattleCommands(); - mWorldManager.LoadBattleTraits(); - mWorldManager.SpawnAllActors(); - mWorldManager.StartZoneThread(); + WorldManager = new WorldManager(this); + WorldManager.LoadZoneList(); + WorldManager.LoadSeamlessBoundryList(); + WorldManager.LoadActorClasses(); + WorldManager.LoadENPCs(); + WorldManager.LoadBattleNpcs(); + WorldManager.LoadStatusEffects(); + WorldManager.LoadBattleCommands(); + WorldManager.LoadBattleTraits(); + WorldManager.SpawnAllActors(); + WorldManager.StartZoneThread(); IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ConfigConstants.OPTIONS_BINDIP), int.Parse(ConfigConstants.OPTIONS_PORT)); try { - mServerSocket = new Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + ServerSocket = new Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); } catch (Exception e) { @@ -91,8 +96,8 @@ namespace Meteor.Map } try { - mServerSocket.Bind(serverEndPoint); - mServerSocket.Listen(BACKLOG); + ServerSocket.Bind(serverEndPoint); + ServerSocket.Listen(BACKLOG); } catch (Exception e) { @@ -100,7 +105,7 @@ namespace Meteor.Map } try { - mServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), mServerSocket); + ServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), ServerSocket); } catch (Exception e) { @@ -108,7 +113,7 @@ namespace Meteor.Map } Console.ForegroundColor = ConsoleColor.White; - Program.Log.Info("Map Server has started @ {0}:{1}", (mServerSocket.LocalEndPoint as IPEndPoint).Address, (mServerSocket.LocalEndPoint as IPEndPoint).Port); + Program.Log.Info("Map Server has started @ {0}:{1}", (ServerSocket.LocalEndPoint as IPEndPoint).Address, (ServerSocket.LocalEndPoint as IPEndPoint).Port); Console.ForegroundColor = ConsoleColor.Gray; mProcessor = new PacketProcessor(this); @@ -122,36 +127,36 @@ namespace Meteor.Map public Session AddSession(uint id) { - if (mSessionList.ContainsKey(id)) + if (SessionList.ContainsKey(id)) { - mSessionList[id].ClearInstance(); - return mSessionList[id]; + SessionList[id].ClearInstance(); + return SessionList[id]; } Session session = new Session(id); - mSessionList.Add(id, session); + SessionList.Add(id, session); return session; } public void RemoveSession(uint id) { - if (mSessionList.ContainsKey(id)) + if (SessionList.ContainsKey(id)) { - mSessionList.Remove(id); + SessionList.Remove(id); } } public Session GetSession(uint id) { - if (mSessionList.ContainsKey(id)) - return mSessionList[id]; + if (SessionList.ContainsKey(id)) + return SessionList[id]; else return null; } public Session GetSession(string name) { - foreach (Session s in mSessionList.Values) + foreach (Session s in SessionList.Values) { if (s.GetActor().DisplayName.ToLower().Equals(name.ToLower())) return s; @@ -161,7 +166,7 @@ namespace Meteor.Map public Dictionary GetSessionList() { - return mSessionList; + return SessionList; } #endregion @@ -179,29 +184,29 @@ namespace Meteor.Map conn.socket = socket.EndAccept(result); conn.buffer = new byte[BUFFER_SIZE]; - mWorldConnection = conn; + WorldConnection = conn; Program.Log.Info("Connection {0}:{1} has connected.", (conn.socket.RemoteEndPoint as IPEndPoint).Address, (conn.socket.RemoteEndPoint as IPEndPoint).Port); //Queue recieving of data from the connection conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn); //Queue the accept of the next incomming connection - mServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), mServerSocket); + ServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), ServerSocket); } catch (SocketException) { if (conn != null) { - mWorldConnection = null; + WorldConnection = null; } - mServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), mServerSocket); + ServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), ServerSocket); } catch (Exception) { if (conn != null) { - mWorldConnection = null; + WorldConnection = null; } - mServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), mServerSocket); + ServerSocket.BeginAccept(new AsyncCallback(AcceptCallback), ServerSocket); } } @@ -216,7 +221,7 @@ namespace Meteor.Map //Check if disconnected if ((conn.socket.Poll(1, SelectMode.SelectRead) && conn.socket.Available == 0)) { - mWorldConnection = null; + WorldConnection = null; Program.Log.Info("Disconnected from world server!"); } @@ -260,7 +265,7 @@ namespace Meteor.Map } else { - mWorldConnection = null; + WorldConnection = null; Program.Log.Info("Disconnected from world server!"); } } @@ -268,7 +273,7 @@ namespace Meteor.Map { if (conn.socket != null) { - mWorldConnection = null; + WorldConnection = null; Program.Log.Info("Disconnected from world server!"); } } @@ -278,54 +283,90 @@ namespace Meteor.Map public static ZoneConnection GetWorldConnection() { - return mWorldConnection; + return WorldConnection; } public static Server GetServer() { - return mSelf; + return _Self; } public static CommandProcessor GetCommandProcessor() { - return mCommandProcessor; + return CommandProcessor; } public static WorldManager GetWorldManager() { - return mWorldManager; + return WorldManager; } public static Dictionary GetGamedataItems() { - return mGamedataItems; + return GamedataItems; } public static Actor GetStaticActors(uint id) { - return mStaticActors.GetActor(id); + return StaticActors.GetActor(id); } public static Actor GetStaticActors(string name) { - return mStaticActors.FindStaticActor(name); + return StaticActors.FindStaticActor(name); } public static ItemData GetItemGamedata(uint id) { - if (mGamedataItems.ContainsKey(id)) - return mGamedataItems[id]; + if (GamedataItems.ContainsKey(id)) + return GamedataItems[id]; else return null; } public static GuildleveData GetGuildleveGamedata(uint id) { - if (mGamedataGuildleves.ContainsKey(id)) - return mGamedataGuildleves[id]; + if (GamedataGuildleves.ContainsKey(id)) + return GamedataGuildleves[id]; else return null; } + public static QuestData GetQuestGamedata(uint id) + { + if (GamedataQuests.ContainsKey(id)) + return GamedataQuests[id]; + else + return null; + } + + + public static QuestData[] GetQuestGamedataByMaxLvl(int lvl, bool all = false) + { + if (all) + return GamedataQuests.Values.Where(quest => quest.MinLevel > 0 && quest.MinLevel <= lvl).ToArray(); + else + return GamedataQuests.Values.Where(quest => quest.MinLevel > 0 && quest.MinLevel == lvl).ToArray(); + } + + public static QuestData[] GetQuestGamedataByPrerequisite(uint questId) + { + return GamedataQuests.Values.Where(quest => quest.PrerequisiteQuest == questId).ToArray(); + } + + public static QuestData[] GetQuestGamedataAllPrerequisite() + { + return GamedataQuests.Values.Where(quest => quest.PrerequisiteQuest != 0).ToArray(); + } + + public static QuestData[] GetQuestGamedataAllGCRanked() + { + return GamedataQuests.Values.Where(quest => quest.MinGCRank != 0).ToArray(); + } + + //public static QuestData[] GetQuestGamedataByGCRank(int gc, int rank, bool all = false) + //{ + // return GamedataQuests.Values.Where(quest => all ? quest.MinLevel == lvl : quest.MinLevel <= lvl).ToArray(); + //} } } diff --git a/Map Server/WorldManager.cs b/Map Server/WorldManager.cs index 85ec220a..d1723e2e 100644 --- a/Map Server/WorldManager.cs +++ b/Map Server/WorldManager.cs @@ -23,7 +23,7 @@ using Meteor.Common; using Meteor.Map.actors.area; using Meteor.Map.actors.chara.npc; using Meteor.Map.Actors; -using Meteor.Map.dataobjects; +using Meteor.Map.DataObjects; using Meteor.Map.lua; using Meteor.Map.packets.send; using Meteor.Map.packets.send.actor; From 02cb0a3f43494365a6114cde5a5ed4dc4e50b0f7 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Thu, 17 Feb 2022 13:22:18 -0500 Subject: [PATCH 3/9] Refactored quest state system seems to work! --- .../chara/npc/populace/PopulaceStandard.lua | 4 - Data/scripts/quests/etc/etc3g0.lua | 68 +-- Map Server/Actors/Chara/Player/Player.cs | 471 ++++++++++-------- Map Server/Actors/Quest/Quest.cs | 329 +++++------- Map Server/Actors/Quest/QuestData.cs | 167 +++++++ Map Server/Actors/Quest/QuestState.cs | 16 +- Map Server/Actors/Quest/QuestStateManager.cs | 68 ++- Map Server/Actors/StaticActors.cs | 3 +- .../{QuestData.cs => QuestGameData.cs} | 4 +- Map Server/DataObjects/Session.cs | 4 +- Map Server/Database.cs | 32 +- Map Server/Lua/LuaEngine.cs | 4 +- Map Server/Map Server.csproj | 3 +- Map Server/Server.cs | 12 +- 14 files changed, 673 insertions(+), 512 deletions(-) create mode 100644 Map Server/Actors/Quest/QuestData.cs rename Map Server/DataObjects/{QuestData.cs => QuestGameData.cs} (82%) diff --git a/Data/scripts/base/chara/npc/populace/PopulaceStandard.lua b/Data/scripts/base/chara/npc/populace/PopulaceStandard.lua index 77635106..90d06334 100644 --- a/Data/scripts/base/chara/npc/populace/PopulaceStandard.lua +++ b/Data/scripts/base/chara/npc/populace/PopulaceStandard.lua @@ -24,7 +24,6 @@ end function onEventStarted(player, npc, eventType, eventName) local defaultTalk = player:GetDefaultTalkQuest(npc); local tutorialTalk = player:GetTutorialQuest(npc); - local journalQuests = player:GetJournalQuestsForNpc(npc); local activeQuests = player:GetQuestsForNpc(npc); local possibleQuests = {}; @@ -35,9 +34,6 @@ function onEventStarted(player, npc, eventType, eventName) if (tutorialTalk ~= nil and eventType == ETYPE_TALK) then table.insert(possibleQuests, tutorialTalk); end - if (journalQuests ~= nil) then - table.insert(possibleQuests, unpack(journalQuests)); - end if (activeQuests ~= nil) then table.insert(possibleQuests, unpack(activeQuests)); end diff --git a/Data/scripts/quests/etc/etc3g0.lua b/Data/scripts/quests/etc/etc3g0.lua index d5f326b5..cacb5111 100644 --- a/Data/scripts/quests/etc/etc3g0.lua +++ b/Data/scripts/quests/etc/etc3g0.lua @@ -48,18 +48,19 @@ end function onFinish(player, quest) end -function onStateChange(player, quest, sequence) - if (sequence == 65536) then +function onStateChange(player, quest, sequence) + if (sequence == 65535) then quest:SetENpc(KINNISON, QFLAG_PLATE); end + local data = quest:GetData(); if (sequence == SEQ_000) then quest:SetENpc(KINNISON); - quest:SetENpc(SYBELL, (not quest:GetFlag(FLAG_TALKED_SYBELL) and QFLAG_PLATE or QFLAG_NONE)); - quest:SetENpc(KHUMA_MOSHROCA, (not quest:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA) and QFLAG_PLATE or QFLAG_NONE)); - quest:SetENpc(NELLAURE, (not quest:GetFlag(FLAG_TALKED_NELLAURE) and QFLAG_PLATE or QFLAG_NONE)); - quest:SetENpc(MESTONNAUX, (not quest:GetFlag(FLAG_TALKED_MESTONNAUX) and QFLAG_PLATE or QFLAG_NONE)); - quest:SetENpc(LEFWYNE, (not quest:GetFlag(FLAG_TALKED_LEFWYNE) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(SYBELL, (not data:GetFlag(FLAG_TALKED_SYBELL) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(KHUMA_MOSHROCA, (not data:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(NELLAURE, (not data:GetFlag(FLAG_TALKED_NELLAURE) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(MESTONNAUX, (not data:GetFlag(FLAG_TALKED_MESTONNAUX) and QFLAG_PLATE or QFLAG_NONE)); + quest:SetENpc(LEFWYNE, (not data:GetFlag(FLAG_TALKED_LEFWYNE) and QFLAG_PLATE or QFLAG_NONE)); elseif (sequence == SEQ_001) then quest:SetENpc(KINNISON, QFLAG_PLATE); end @@ -74,67 +75,66 @@ function onTalk(player, quest, npc, eventName) if (npcClassId == KINNISON and not player:HasQuest(quest)) then local questAccepted = callClientFunction(player, "delegateEvent", player, quest, "processEventOffersStart"); if (questAccepted) then - player:AddQuest(quest); + player:AcceptQuest(quest); end player:EndEvent(); return; end -- Quest Progress + local data = quest:GetData(); if (seq == SEQ_000) then if (npcClassId == KINNISON) then callClientFunction(player, "delegateEvent", player, quest, "processEventOffersAfter"); elseif (npcClassId == SYBELL) then - if (not quest:GetFlag(FLAG_TALKED_SYBELL)) then + if (not data:GetFlag(FLAG_TALKED_SYBELL)) then callClientFunction(player, "delegateEvent", player, quest, "processEventSybellSpeak"); - quest:SetFlag(FLAG_TALKED_SYBELL); + data:SetFlag(FLAG_TALKED_SYBELL); incCounter = true; else callClientFunction(player, "delegateEvent", player, quest, "processEventSybellSpeakAfter"); end elseif (npcClassId == KHUMA_MOSHROCA) then - if (not quest:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA)) then + if (not data:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA)) then callClientFunction(player, "delegateEvent", player, quest, "processEventKhumaSpeak"); - quest:SetFlag(FLAG_TALKED_KHUMA_MOSHROCA); + data:SetFlag(FLAG_TALKED_KHUMA_MOSHROCA); incCounter = true; else callClientFunction(player, "delegateEvent", player, quest, "processEventKhumaSpeakAfter"); end elseif (npcClassId == NELLAURE) then - if (not quest:GetFlag(FLAG_TALKED_NELLAURE)) then + if (not data:GetFlag(FLAG_TALKED_NELLAURE)) then callClientFunction(player, "delegateEvent", player, quest, "processEventNellaureSpeak"); - quest:SetFlag(FLAG_TALKED_NELLAURE); + data:SetFlag(FLAG_TALKED_NELLAURE); incCounter = true; else callClientFunction(player, "delegateEvent", player, quest, "processEventNellaureSpeakAfter"); end elseif (npcClassId == MESTONNAUX) then - if (not quest:GetFlag(FLAG_TALKED_MESTONNAUX)) then + if (not data:GetFlag(FLAG_TALKED_MESTONNAUX)) then callClientFunction(player, "delegateEvent", player, quest, "processEventMestonnauxSpeak"); - quest:SetFlag(FLAG_TALKED_MESTONNAUX); + data:SetFlag(FLAG_TALKED_MESTONNAUX); incCounter = true; else callClientFunction(player, "delegateEvent", player, quest, "processEventMestonnauxSpeakAfter"); end elseif (npcClassId == LEFWYNE) then - if (not quest:GetFlag(FLAG_TALKED_LEFWYNE)) then + if (not data:GetFlag(FLAG_TALKED_LEFWYNE)) then callClientFunction(player, "delegateEvent", player, quest, "processEventLefwyneSpeak"); - quest:SetFlag(FLAG_TALKED_LEFWYNE); + data:SetFlag(FLAG_TALKED_LEFWYNE); incCounter = true; else callClientFunction(player, "delegateEvent", player, quest, "processEventLefwyneSpeakAfter"); end end - - + -- Increase objective counter & play relevant messages if (incCounter == true) then - quest:IncCounter(COUNTER_TALKED); - local counterAmount = quest:GetCounter(COUNTER_TALKED); + local counterAmount = data:IncCounter(COUNTER_TALKED); attentionMessage(player, 51061, 0, counterAmount, 5); -- You have heard word of the Seedseers. (... of 5) - if (seq000_checkCondition(quest)) then -- All Seers spoken to + if (seq000_checkCondition(data)) then -- All Seers spoken to attentionMessage(player, 25225, 110674); -- "Seeing the Seers" objectives complete! quest:UpdateENPCs(); -- Band-aid for a QFLAG_PLATE issue quest:StartSequence(SEQ_001); @@ -155,12 +155,12 @@ end -- Check if all seers are talked to -function seq000_checkCondition(quest) - return (quest:GetFlag(FLAG_TALKED_SYBELL) and - quest:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA) and - quest:GetFlag(FLAG_TALKED_NELLAURE) and - quest:GetFlag(FLAG_TALKED_MESTONNAUX) and - quest:GetFlag(FLAG_TALKED_LEFWYNE)); +function seq000_checkCondition(data) + return (data:GetFlag(FLAG_TALKED_SYBELL) and + data:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA) and + data:GetFlag(FLAG_TALKED_NELLAURE) and + data:GetFlag(FLAG_TALKED_MESTONNAUX) and + data:GetFlag(FLAG_TALKED_LEFWYNE)); end @@ -169,11 +169,11 @@ function getJournalMapMarkerList(player, quest) local possibleMarkers = {}; if (sequence == SEQ_000) then - if (not quest:GetFlag(FLAG_TALKED_SYBELL)) then table.insert(possibleMarkers, MRKR_SYBELL); end - if (not quest:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA)) then table.insert(possibleMarkers, MRKR_KHUMA_MOSHROCA); end - if (not quest:GetFlag(FLAG_TALKED_NELLAURE)) then table.insert(possibleMarkers, MRKR_NELLAURE); end - if (not quest:GetFlag(FLAG_TALKED_MESTONNAUX)) then table.insert(possibleMarkers, MRKR_MESTONNAUX); end - if (not quest:GetFlag(FLAG_TALKED_LEFWYNE)) then table.insert(possibleMarkers, MRKR_LEFWYNE); end + if (not data:GetFlag(FLAG_TALKED_SYBELL)) then table.insert(possibleMarkers, MRKR_SYBELL); end + if (not data:GetFlag(FLAG_TALKED_KHUMA_MOSHROCA)) then table.insert(possibleMarkers, MRKR_KHUMA_MOSHROCA); end + if (not data:GetFlag(FLAG_TALKED_NELLAURE)) then table.insert(possibleMarkers, MRKR_NELLAURE); end + if (not data:GetFlag(FLAG_TALKED_MESTONNAUX)) then table.insert(possibleMarkers, MRKR_MESTONNAUX); end + if (not data:GetFlag(FLAG_TALKED_LEFWYNE)) then table.insert(possibleMarkers, MRKR_LEFWYNE); end elseif (sequence == SEQ_001) then table.insert(possibleMarkers, MRKR_KINNISON); end diff --git a/Map Server/Actors/Chara/Player/Player.cs b/Map Server/Actors/Chara/Player/Player.cs index 2a80d930..910cfda2 100644 --- a/Map Server/Actors/Chara/Player/Player.cs +++ b/Map Server/Actors/Chara/Player/Player.cs @@ -37,6 +37,7 @@ using Meteor.Map.actors.chara.ai.controllers; using Meteor.Map.actors.chara.ai.utils; using Meteor.Map.actors.chara.ai.state; using Meteor.Map.actors.chara; +using Meteor.Map.Actors.QuestNS; using Meteor.Map.packets.send; using Meteor.Map.packets.send.actor; using Meteor.Map.packets.send.events; @@ -277,7 +278,7 @@ namespace Meteor.Map.Actors CalculateBaseStats(); questStateManager = new QuestStateManager(this); - questStateManager.Init(); + questStateManager.Init(questScenario); } public List Create0x132Packets() @@ -806,7 +807,7 @@ namespace Meteor.Map.Actors foreach (Quest quest in questScenario) { if (quest != null) - quest.SaveData(); + quest.GetData().Save(); } } @@ -1418,33 +1419,253 @@ namespace Meteor.Map.Actors return -1; } - //For Lua calls, cause MoonSharp goes retard with uint - public void AddQuest(int id, bool isSilent = false) + #region Quests - Script Related + // Add quest from an active quest in the player's quest state. Quest scripts will use this to add a quest. + public bool AcceptQuest(Quest instance, bool isSilent = false) { - AddQuest((uint)id, isSilent); - } - public void CompleteQuest(int id) - { - CompleteQuest((uint)id); - } - public bool HasQuest(int id) - { - return HasQuest((uint)id); - } - public Quest GetQuest(int id) - { - return GetQuest((uint)id); - } - public bool IsQuestCompleted(int id) - { - return IsQuestCompleted((uint)id); - } - public bool CanAcceptQuest(int id) - { - return CanAcceptQuest((uint)id); - } - //For Lua calls, cause MoonSharp goes retard with uint + if (instance == null) + return false; + int freeSlot = GetFreeQuestSlot(); + + if (freeSlot == -1) + { + SendGameMessage(Server.GetWorldManager().GetActor(), 25234, 0x20); // "You cannot accept any more quests at this time." + return false; + } + + playerWork.questScenario[freeSlot] = instance.Id; + questScenario[freeSlot] = instance; + Database.SaveQuest(this, questScenario[freeSlot]); + SendQuestClientUpdate(freeSlot); + + if (!isSilent) + { + SendGameMessage(Server.GetWorldManager().GetActor(), 25224, 0x20, (object)questScenario[freeSlot].GetQuestId()); // " accepted." + } + + instance.OnAccept(); + + return true; + } + + // Replace a quest with another quest in the player's quest state. + public void ReplaceQuest(Quest oldQuestInstance, Quest newQuestInstance) + { + for (int i = 0; i < questScenario.Length; i++) + { + if (questScenario[i] != null && questScenario[i].Equals(oldQuestInstance)) + { + questScenario[i] = newQuestInstance; + playerWork.questScenario[i] = questScenario[i].Id; + Database.SaveQuest(this, questScenario[i]); + SendQuestClientUpdate(i); + break; + } + } + } + + public void CompleteQuest(Quest completed) + { + int slot = GetQuestSlot(completed); + if (slot >= 0) + { + // Remove the quest from the DB and update client work values + playerWork.questScenarioComplete[completed.GetQuestId() - 110001] = true; + Database.CompleteQuest(playerSession.GetActor(), completed.Id); + Database.RemoveQuest(this, completed.Id); + questScenario[slot] = null; + playerWork.questScenario[slot] = 0; + SendQuestClientUpdate(slot); + + // Reset active quest and quest state + completed.OnComplete(); + questStateManager.UpdateQuestCompleted(completed); + + // Msg Player + SendGameMessage(Server.GetWorldManager().GetActor(), 25086, 0x20, (object)completed.GetQuestId()); // " complete!" + } + + } + + public bool AbandonQuest(uint questId) + { + // Check if in an instance + if (CurrentArea.IsPrivate()) + { + SendGameMessage(Server.GetWorldManager().GetActor(), 25235, 0x20); // "Quests cannot be abandoned while from within an instance." + return false; + } + + // Get the quest object + int slot = GetQuestSlot(questId); + Quest abandoned = questScenario[slot]; + + if (abandoned == null) + return false; + + // Check if Main Scenario + if (abandoned.IsMainScenario()) + { + SendGameMessage(Server.GetWorldManager().GetActor(), 25233, 0x20); // "Main scenario quests cannot be abandoned." + return false; + } + + // Remove the quest from the DB and update client work values + Database.RemoveQuest(this, abandoned.Id); + questScenario[slot] = null; + playerWork.questScenario[slot] = 0; + SendQuestClientUpdate(slot); + + // Reset active quest and quest state + abandoned.OnAbandon(); + questStateManager.UpdateQuestAbandoned(); + + // Msg Player + SendGameMessage(this, Server.GetWorldManager().GetActor(), 25236, 0x20, (object)abandoned.GetQuestId()); // " abandoned." + return true; + } + + public bool HasQuest(Quest questInstance) + { + return GetQuestSlot(questInstance) != -1; + } + #endregion + + #region Quests - Debug/Misc Related + // Force-Add a quest by Id. Called be debug scripts. + public void AddQuest(uint id, bool isSilent = false) + { + Actor actor = Server.GetStaticActors((0xA0F00000 | id)); + AddQuest(actor.Name, isSilent); + } + + // Force-Add a quest by Name. Called be debug scripts. Will try to use an active quest, otherwise adds a new instance. + public void AddQuest(string name, bool isSilent = false) + { + Quest baseQuest = (Quest)Server.GetStaticActors(name); + Quest activeQuest = questStateManager.GetActiveQuest(baseQuest.GetQuestId()); + + int freeSlot = GetFreeQuestSlot(); + + if (freeSlot == -1) + return; + + playerWork.questScenario[freeSlot] = baseQuest.Id; + questScenario[freeSlot] = activeQuest ?? new Quest(this, baseQuest); + + if (activeQuest == null) + questStateManager.ForceAddActiveQuest(questScenario[freeSlot]); + + Database.SaveQuest(this, questScenario[freeSlot]); + SendQuestClientUpdate(freeSlot); + + if (!isSilent) + { + SendGameMessage(Server.GetWorldManager().GetActor(), 25224, 0x20, (object)questScenario[freeSlot].GetQuestId()); + } + + questScenario[freeSlot].OnAccept(); + } + + public void RemoveQuest(uint id) + { + for (int i = 0; i < questScenario.Length; i++) + { + if (questScenario[i] != null && questScenario[i].Id == (0xA0F00000 | id)) + { + Database.RemoveQuest(this, questScenario[i].Id); + questScenario[i] = null; + playerWork.questScenario[i] = 0; + SendQuestClientUpdate(i); + break; + } + } + } + + public void RemoveQuest(string name) + { + for (int i = 0; i < questScenario.Length; i++) + { + if (questScenario[i] != null && questScenario[i].Name.ToLower().Equals(name.ToLower())) + { + Database.RemoveQuest(this, questScenario[i].Id); + questScenario[i] = null; + playerWork.questScenario[i] = 0; + SendQuestClientUpdate(i); + break; + } + } + } + + public bool HasQuest(string name) + { + for (int i = 0; i < questScenario.Length; i++) + { + if (questScenario[i] != null && questScenario[i].Name.ToLower().Equals(name.ToLower())) + return true; + } + + return false; + } + + public bool HasQuest(uint id) + { + for (int i = 0; i < questScenario.Length; i++) + { + if (questScenario[i] != null && questScenario[i].Id == (0xA0F00000 | id)) + return true; + } + + return false; + } + + public Quest GetQuest(uint id) + { + for (int i = 0; i < questScenario.Length; i++) + { + if (questScenario[i] != null && questScenario[i].Id == (0xA0F00000 | id)) + return questScenario[i]; + } + + return null; + } + + public Quest GetQuest(string name) + { + for (int i = 0; i < questScenario.Length; i++) + { + if (questScenario[i] != null && questScenario[i].Name.ToLower().Equals(name.ToLower())) + return questScenario[i]; + } + + return null; + } + + public int GetQuestSlot(Quest quest) + { + for (int slot = 0; slot < questScenario.Length; slot++) + { + if (questScenario[slot] != null && questScenario[slot].Id == quest.Id) + return slot; + } + + return -1; + } + + public int GetQuestSlot(uint id) + { + for (int slot = 0; slot < questScenario.Length; slot++) + { + if (questScenario[slot] != null && questScenario[slot].GetQuestId() == id) + return slot; + } + + return -1; + } + #endregion + + #region Guildleves public void AddGuildleve(uint id) { int freeSlot = GetFreeGuildleveSlot(); @@ -1489,179 +1710,7 @@ namespace Meteor.Map.Actors } } } - } - - public void AddQuest(uint id, bool isSilent = false) - { - Actor actor = Server.GetStaticActors((0xA0F00000 | id)); - AddQuest(actor.Name, isSilent); - } - - public void AddQuest(string name, bool isSilent = false) - { - Quest baseQuest = (Quest) Server.GetStaticActors(name); - - if (baseQuest == null) - return; - - int freeSlot = GetFreeQuestSlot(); - - if (freeSlot == -1) - return; - - playerWork.questScenario[freeSlot] = baseQuest.Id; - questScenario[freeSlot] = new Quest(this, baseQuest); - Database.SaveQuest(this, questScenario[freeSlot]); - SendQuestClientUpdate(freeSlot); - - if (!isSilent) - { - SendGameMessage(Server.GetWorldManager().GetActor(), 25224, 0x20, (object)questScenario[freeSlot].GetQuestId()); - } - } - - public void CompleteQuest(uint id) - { - Actor actor = Server.GetStaticActors((0xA0F00000 | id)); - CompleteQuest(actor.Name); - } - - public void CompleteQuest(string name) - { - Actor actor = Server.GetStaticActors(name); - - if (actor == null) - return; - - uint id = actor.Id; - if (HasQuest(id)) - { - Database.CompleteQuest(playerSession.GetActor(), id); - SendGameMessage(Server.GetWorldManager().GetActor(), 25086, 0x20, (object)GetQuest(id).GetQuestId()); - RemoveQuest(id); - } - } - - //TODO: Add checks for you being in an instance or main scenario - public void AbandonQuest(uint id) - { - Quest quest = GetQuest(id); - RemoveQuestByQuestId(id); - quest.DoAbandon(); - } - - public void RemoveQuestByQuestId(uint id) - { - RemoveQuest((0xA0F00000 | id)); - } - - public void RemoveQuest(uint id) - { - if (HasQuest(id)) - { - for (int i = 0; i < questScenario.Length; i++) - { - if (questScenario[i] != null && questScenario[i].Id == id) - { - Database.RemoveQuest(this, questScenario[i].Id); - questScenario[i] = null; - playerWork.questScenario[i] = 0; - SendQuestClientUpdate(i); - break; - } - } - } - } - - public void ReplaceQuest(Quest oldQuest, string questCode) - { - for (int i = 0; i < questScenario.Length; i++) - { - if (questScenario[i] != null && questScenario[i].Equals(oldQuest)) - { - Quest baseQuest = (Quest) Server.GetStaticActors(questCode); - questScenario[i] = new Quest(this, baseQuest); - playerWork.questScenario[i] = questScenario[i].Id; - Database.SaveQuest(this, questScenario[i]); - SendQuestClientUpdate(i); - break; - } - } - } - - public bool CanAcceptQuest(string name) - { - if (!IsQuestCompleted(name) && !HasQuest(name)) - return true; - else - return false; - } - - public bool CanAcceptQuest(uint id) - { - Actor actor = Server.GetStaticActors((0xA0F00000 | id)); - return CanAcceptQuest(actor.Name); - } - - public bool IsQuestCompleted(string questName) - { - Actor actor = Server.GetStaticActors(questName); - return IsQuestCompleted(actor.Id); - } - - public bool IsQuestCompleted(uint questId) - { - return Database.IsQuestCompleted(this, 0xFFFFF & questId); - } - - public Quest GetQuest(uint id) - { - for (int i = 0; i < questScenario.Length; i++) - { - if (questScenario[i] != null && questScenario[i].Id == (0xA0F00000 | id)) - return questScenario[i]; - } - - return null; - } - - public Quest GetQuest(string name) - { - for (int i = 0; i < questScenario.Length; i++) - { - if (questScenario[i] != null && questScenario[i].Name.ToLower().Equals(name.ToLower())) - return questScenario[i]; - } - - return null; - } - - public bool HasQuest(string name) - { - for (int i = 0; i < questScenario.Length; i++) - { - if (questScenario[i] != null && questScenario[i].Name.ToLower().Equals(name.ToLower())) - return true; - } - - return false; - } - - public bool HasQuest(uint id) - { - for (int i = 0; i < questScenario.Length; i++) - { - if (questScenario[i] != null && questScenario[i].Id == (0xA0F00000 | id)) - return true; - } - - return false; - } - - public bool HasQuest(Quest quest) - { - return HasQuest(quest.className); - } + } public bool HasGuildleve(uint id) { @@ -1673,17 +1722,7 @@ namespace Meteor.Map.Actors return false; } - - public int GetQuestSlot(uint id) - { - for (int i = 0; i < questScenario.Length; i++) - { - if (questScenario[i] != null && questScenario[i].Id == (0xA0F00000 | id)) - return i; - } - - return -1; - } + #endregion public Quest GetDefaultTalkQuest(Npc npc) { @@ -1735,14 +1774,11 @@ namespace Meteor.Map.Actors return null; } - public Quest[] GetJournalQuestsForNpc(Npc npc) - { - return Array.FindAll(questScenario, e => e != null && e.IsQuestENPC(this, npc)); - } - public Quest[] GetQuestsForNpc(Npc npc) { - return questStateManager.GetQuestsForNpc(npc); + Quest[] quests = questStateManager.GetQuestsForNpc(npc); + Array.Sort(quests, (q1, q2) => (q1.HasData() ? 1 : 0) - (q2.HasData() ? 1 : 0)); + return quests; } public void HandleNpcLS(uint id) @@ -2765,6 +2801,7 @@ namespace Meteor.Map.Actors actionList.Add(new CommandResult(Id, 33909, 0, (ushort)charaWork.battleSave.skillLevel[classId - 1])); EquipAbilitiesAtLevel(classId, GetLevel(), actionList); + questStateManager.UpdateLevel(GetHighestLevel()); } } diff --git a/Map Server/Actors/Quest/Quest.cs b/Map Server/Actors/Quest/Quest.cs index 67d246df..ee594f85 100644 --- a/Map Server/Actors/Quest/Quest.cs +++ b/Map Server/Actors/Quest/Quest.cs @@ -20,69 +20,127 @@ along with Project Meteor Server. If not, see . */ using Meteor.Map.lua; -using Newtonsoft.Json; using System; using System.Collections.Generic; -namespace Meteor.Map.Actors +namespace Meteor.Map.Actors.QuestNS { class Quest : Actor { public const ushort SEQ_NOT_STARTED = 65535; public const ushort SEQ_COMPLETED = 65534; - private struct QuestData - { - public UInt32 flags; - public UInt16 counter1; - public UInt16 counter2; - public UInt16 counter3; - public UInt16 counter4; - - public QuestData(uint flags, ushort counter1, ushort counter2, ushort counter3) : this() - { - this.flags = flags; - this.counter1 = counter1; - this.counter2 = counter2; - this.counter3 = counter3; - } - } - - // This is only set on instance quests (non static) - private Player Owner; + private Player owner; private ushort currentSequence; - private QuestState QuestState; - private QuestData Data; + private QuestState questState = null; + private QuestData data = null; private bool dataDirty = false; + // Creates a Static Quest for the StaticActors list. + public Quest(uint actorID, string className, string classPath) + : base(actorID) + { + Name = className; + this.className = className; + this.classPath = classPath; + } + + // Creates a Static Quest from another Static Quest + public Quest(Quest staticQuest) + : this(staticQuest.Id, staticQuest.Name, staticQuest.classPath) + { } + + // Creates a Instance Quest that has been started. + public Quest(Player owner, Quest staticQuest, ushort sequence) : this(staticQuest) + { + this.owner = owner; + currentSequence = sequence; + questState = new QuestState(owner, this); + questState.UpdateState(); + } + + // Creates a Instance Quest that has not been started. + public Quest(Player owner, Quest staticQuest) : this(owner, staticQuest, SEQ_NOT_STARTED) + { } + + #region Getters + public uint GetQuestId() + { + return Id & 0xFFFFF; + } + + public override bool Equals(object obj) + { + if (obj != null && obj is Quest quest) + return quest.Id == this.Id; + return false; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public bool IsInstance() + { + return questState != null; + } + + public bool IsMainScenario() + { + uint id = GetQuestId(); + return id >= 110001 && id <= 110021; + } + + public ushort GetSequence() + { + return currentSequence; + } + #endregion + + #region Quest Data + public void SetData(uint flags, ushort counter1, ushort counter2, ushort counter3, ushort counter4) + { + data = new QuestData(owner, this, flags, counter1, counter2, counter3, counter4); + } + + public QuestData GetData() + { + return data; + } + + public bool HasData() + { + return data != null; + } + #endregion + + #region Quest State public void SetENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false) { - if (QuestState != null) - QuestState.AddENpc(classId, flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned); + if (questState != null) + questState.AddENpc(classId, flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned); } public void UpdateENPCs() { if (dataDirty) { - if (QuestState != null) - QuestState.UpdateState(); + if (questState != null) + questState.UpdateState(); dataDirty = false; } } public QuestState GetQuestState() { - return QuestState; - } - - public bool IsInstance() - { - return Owner != null; + return questState; } + #endregion + #region Script Callbacks public void OnTalk(Player caller, Npc npc) - { + { LuaEngine.GetInstance().CallLuaFunction(caller, this, "onTalk", true, npc); } @@ -106,17 +164,9 @@ namespace Meteor.Map.Actors LuaEngine.GetInstance().CallLuaFunction(caller, this, "onNpcLS", true, npcLSId); } - public bool IsQuestENPC(Player caller, Npc npc) - { - List returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(caller, this, "IsQuestENPC", true, npc, this); - bool scriptReturned = returned != null && returned.Count != 0 && returned[0].typeID == 3; - return scriptReturned || QuestState.HasENpc(npc.GetActorClassId()); - } - - public object[] GetJournalInformation() { - List returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "getJournalInformation", true); + List returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(owner, this, "getJournalInformation", true); if (returned != null && returned.Count != 0) return LuaUtils.CreateLuaParamObjectList(returned); else @@ -125,212 +175,59 @@ namespace Meteor.Map.Actors public object[] GetJournalMapMarkerList() { - List returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "getJournalMapMarkerList", true); + List returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(owner, this, "getJournalMapMarkerList", true); if (returned != null && returned.Count != 0) return LuaUtils.CreateLuaParamObjectList(returned); else return new object[0]; } + #endregion - public ushort GetSequence() + public bool IsQuestENPC(Player caller, Npc npc) { - return currentSequence; + List returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(caller, this, "IsQuestENPC", true, npc, this); + bool scriptReturned = returned != null && returned.Count != 0 && returned[0].typeID == 3; + return scriptReturned || questState.HasENpc(npc.GetActorClassId()); } public void StartSequence(ushort sequence) - { + { if (sequence == SEQ_NOT_STARTED) return; // Send the message that the journal has been updated if (currentSequence != SEQ_NOT_STARTED) - Owner.SendGameMessage(Server.GetWorldManager().GetActor(), 25116, 0x20, (object)GetQuestId()); + owner.SendGameMessage(Server.GetWorldManager().GetActor(), 25116, 0x20, (object)GetQuestId()); currentSequence = sequence; dataDirty = true; - UpdateENPCs(); + questState.UpdateState(); } - public void ClearData() - { - Data.flags = Data.counter1 = Data.counter2 = Data.counter3 = Data.counter4 = 0; - } - - public void SetFlag(int index) - { - if (index >= 0 && index < 32) - { - Data.flags |= (uint)(1 << index); - dataDirty = true; - } - } - - public void ClearFlag(int index) - { - if (index >= 0 && index < 32) - { - Data.flags &= (uint)~(1 << index); - dataDirty = true; - } - } - - public void IncCounter(int num) - { - dataDirty = true; - - switch (num) - { - case 0: - Data.counter1++; - return; - case 1: - Data.counter2++; - return; - case 2: - Data.counter3++; - return; - case 3: - Data.counter4++; - return; - } - - dataDirty = false; - } - - public void DecCounter(int num) - { - dataDirty = true; - - switch (num) - { - case 0: - Data.counter1--; - return; - case 1: - Data.counter2--; - return; - case 2: - Data.counter3--; - return; - case 3: - Data.counter4--; - return; - } - - dataDirty = false; - } - - public void SetCounter(int num, ushort value) - { - dataDirty = true; - - switch (num) - { - case 0: - Data.counter1 = value; - return; - case 1: - Data.counter2 = value; - return; - case 2: - Data.counter3 = value; - return; - case 3: - Data.counter4 = value; - return; - } - - dataDirty = false; - } - - public bool GetFlag(int index) - { - if (index >= 0 && index < 32) - return (Data.flags & (uint) (1 << index)) != 0; - return false; - } - - public uint GetFlags() - { - return Data.flags; - } - - public ushort GetCounter(int num) - { - switch (num) - { - case 0: - return Data.counter1; - case 1: - return Data.counter2; - case 2: - return Data.counter3; - case 3: - return Data.counter4; - } - - return 0; - } - - public void SaveData() - { - Database.SaveQuest(Owner, this); - } - - public Quest(uint actorID, string name) - : base(actorID) - { - Name = name; - } - - public Quest(Player owner, Quest baseQuest): this(owner, baseQuest, SEQ_NOT_STARTED, 0, 0, 0, 0) - {} - - public Quest(Player owner, Quest baseQuest, ushort sequence, uint flags, ushort counter1, ushort counter2, ushort counter3) - : base(baseQuest.Id) - { - Owner = owner; - Name = baseQuest.Name; - className = baseQuest.className; - classPath = baseQuest.classPath; - currentSequence = sequence; - QuestState = new QuestState(owner, this); - Data = new QuestData(flags, counter1, counter2, counter3); - } - - public uint GetQuestId() - { - return Id & 0xFFFFF; - } - - public void DoAccept() + public void OnAccept() { + data = new QuestData(owner, this); if (currentSequence == SEQ_NOT_STARTED) - LuaEngine.GetInstance().CallLuaFunction(Owner, this, "onStart", false); + LuaEngine.GetInstance().CallLuaFunction(owner, this, "onStart", false); else StartSequence(currentSequence); } - public void DoComplete() + public void OnComplete() { - LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "onFinish", true); - Owner.SendDataPacket("attention", Server.GetWorldManager().GetActor(), "", 25225, (object)GetQuestId()); - Owner.SendGameMessage(Server.GetWorldManager().GetActor(), 25225, 0x20, (object)GetQuestId()); + LuaEngine.GetInstance().CallLuaFunctionForReturn(owner, this, "onFinish", true); currentSequence = SEQ_COMPLETED; + data = null; + questState.UpdateState(); } - public void DoAbandon() + public void OnAbandon() { - LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "onFinish", false); - Owner.SendGameMessage(Owner, Server.GetWorldManager().GetActor(), 25236, 0x20, (object)GetQuestId()); + LuaEngine.GetInstance().CallLuaFunctionForReturn(owner, this, "onFinish", false); currentSequence = SEQ_NOT_STARTED; + data = null; + questState.UpdateState(); } - public override bool Equals(object obj) - { - if (obj is Quest quest) - return quest.Id == this.Id; - return false; - } } } diff --git a/Map Server/Actors/Quest/QuestData.cs b/Map Server/Actors/Quest/QuestData.cs new file mode 100644 index 00000000..6b0818db --- /dev/null +++ b/Map Server/Actors/Quest/QuestData.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Meteor.Map.Actors.QuestNS +{ + class QuestData + { + private Player owner; + private Quest parent; + + private uint flags; + private ushort counter1; + private ushort counter2; + private ushort counter3; + private ushort counter4; + private bool dataDirty = false; + + public QuestData(Player owner, Quest parent, uint flags, ushort counter1, ushort counter2, ushort counter3, ushort counter4) + { + this.owner = owner; + this.parent = parent; + this.flags = flags; + this.counter1 = counter1; + this.counter2 = counter2; + this.counter3 = counter3; + this.counter4 = counter4; + } + + public QuestData(Player owner, Quest parent) + { + this.owner = owner; + this.parent = parent; + flags = counter1 = counter2 = counter3 = counter4 = 0; + } + + public void ClearData() + { + flags = counter1 = counter2 = counter3 = counter4 = 0; + } + + public void SetFlag(int index) + { + if (index >= 0 && index < 32) + { + flags |= (uint)(1 << index); + dataDirty = true; + } + } + + public void ClearFlag(int index) + { + if (index >= 0 && index < 32) + { + flags &= (uint)~(1 << index); + dataDirty = true; + } + } + + public ushort IncCounter(int num) + { + dataDirty = true; + + switch (num) + { + case 0: + counter1++; + return counter1; + case 1: + counter2++; + return counter2; + case 2: + counter3++; + return counter3; + case 3: + counter4++; + return counter4; + } + + dataDirty = false; + return 0; + } + + public ushort DecCounter(int num) + { + dataDirty = true; + + switch (num) + { + case 0: + counter1--; + return counter1; + case 1: + counter2--; + return counter2; + case 2: + counter3--; + return counter3; + case 3: + counter4--; + return counter4; + } + + dataDirty = false; + return 0; + } + + public void SetCounter(int num, ushort value) + { + dataDirty = true; + + switch (num) + { + case 0: + counter1 = value; + return; + case 1: + counter2 = value; + return; + case 2: + counter3 = value; + return; + case 3: + counter4 = value; + return; + } + + dataDirty = false; + } + + public bool GetFlag(int index) + { + if (index >= 0 && index < 32) + return (flags & (uint)(1 << index)) != 0; + return false; + } + + public uint GetFlags() + { + return flags; + } + + public ushort GetCounter(int num) + { + switch (num) + { + case 0: + return counter1; + case 1: + return counter2; + case 2: + return counter3; + case 3: + return counter4; + } + + return 0; + } + + public void Save() + { + Database.SaveQuest(owner, parent); + } + } +} diff --git a/Map Server/Actors/Quest/QuestState.cs b/Map Server/Actors/Quest/QuestState.cs index 068083cd..d950b2ca 100644 --- a/Map Server/Actors/Quest/QuestState.cs +++ b/Map Server/Actors/Quest/QuestState.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Meteor.Map.Actors +namespace Meteor.Map.Actors.QuestNS { class QuestState { @@ -49,8 +49,8 @@ namespace Meteor.Map.Actors } } - private Player Owner; - private Quest Parent; + private readonly Player Owner; + private readonly Quest Parent; private Dictionary CurrentENPCs = new Dictionary(); private Dictionary OldENPCs = new Dictionary(); @@ -58,7 +58,6 @@ namespace Meteor.Map.Actors { Owner = owner; Parent = parent; - UpdateState(); } public void AddENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false) @@ -105,8 +104,15 @@ namespace Meteor.Map.Actors CurrentENPCs = new Dictionary(); LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, Parent, "onStateChange", false, currentSeq); foreach (var enpc in OldENPCs) - Owner.playerSession.UpdateQuestNpcInInstance(enpc.Value); + Owner.playerSession.UpdateQuestNpcInInstance(enpc.Value, true); OldENPCs = null; } + + public void DeleteState() + { + foreach (var enpc in CurrentENPCs) + Owner.playerSession.UpdateQuestNpcInInstance(enpc.Value, true); + CurrentENPCs.Clear(); + } } } diff --git a/Map Server/Actors/Quest/QuestStateManager.cs b/Map Server/Actors/Quest/QuestStateManager.cs index e2e2bf7d..23452b88 100644 --- a/Map Server/Actors/Quest/QuestStateManager.cs +++ b/Map Server/Actors/Quest/QuestStateManager.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Meteor.Map.Actors +namespace Meteor.Map.Actors.QuestNS { class QuestStateManager { @@ -21,16 +21,30 @@ namespace Meteor.Map.Actors private readonly Bitstream GCRankBitfield = new Bitstream(SCENARIO_MAX, true); private List ActiveQuests = new List(); + private Dictionary QuestStateTable = new Dictionary(); public QuestStateManager(Player player) { this.player = player; } - public void Init() + public void Init(Quest[] questScenario) { + // Preload any quests that the player loaded + if (questScenario != null) + { + foreach (var quest in questScenario) + { + if (quest != null) + { + ActiveQuests.Add(quest); + AvailableQuestsBitfield.Set(quest.GetQuestId() - SCENARIO_START); + } + } + } + // Init MinLv - QuestData[] minLvl = Server.GetQuestGamedataByMaxLvl(player.GetHighestLevel(), true); + QuestGameData[] minLvl = Server.GetQuestGamedataByMaxLvl(player.GetHighestLevel(), true); foreach (var questData in minLvl) MinLevelBitfield.Set(questData.Id - SCENARIO_START); @@ -43,28 +57,29 @@ namespace Meteor.Map.Actors else PrereqBitfield.Clear(questData.Id - SCENARIO_START); } + ComputeAvailable(); } public void UpdateLevel(int level) { - QuestData[] updated = Server.GetQuestGamedataByMaxLvl(level); + QuestGameData[] updated = Server.GetQuestGamedataByMaxLvl(level); foreach (var questData in updated) MinLevelBitfield.Set(questData.Id - SCENARIO_START); ComputeAvailable(); } - public void UpdateQuestComplete(Quest quest) + public void UpdateQuestCompleted(Quest quest) { - QuestData[] updated = Server.GetQuestGamedataByPrerequisite(quest.GetQuestId()); + QuestGameData[] updated = Server.GetQuestGamedataByPrerequisite(quest.GetQuestId()); foreach (var questData in updated) PrereqBitfield.Set(questData.Id - SCENARIO_START); ComputeAvailable(); } - public void QuestAdded(Quest quest) + public void UpdateQuestAbandoned() { - ActiveQuests.Remove(quest); + ComputeAvailable(); } private void ComputeAvailable() @@ -90,9 +105,9 @@ namespace Meteor.Map.Actors int index = i * 8 + shift; Quest quest = (Quest)Server.GetStaticActors(0xA0F00000 | (SCENARIO_START + (uint)index)); if (!AvailableQuestsBitfield.Get(index)) - ActiveQuests.Add(new Quest(player, quest)); + AddActiveQuest(quest); else - ActiveQuests.Remove(quest); + RemoveActiveQuest(quest); } } } @@ -100,6 +115,39 @@ namespace Meteor.Map.Actors AvailableQuestsBitfield.SetTo(result); } + public void ForceAddActiveQuest(Quest questInstance) + { + ActiveQuests.Add(questInstance); + QuestStateTable.Add(questInstance.Id, questInstance.GetQuestState()); + } + + private void AddActiveQuest(Quest staticQuest) + { + Quest instance = new Quest(player, staticQuest); + ActiveQuests.Add(instance); + QuestStateTable.Add(staticQuest.Id, instance.GetQuestState()); + } + + private void RemoveActiveQuest(Quest staticQuest) + { + // Do not remove quests in the player's journal + if (player.HasQuest(staticQuest.GetQuestId())) + return; + + ActiveQuests.Remove(staticQuest); + + if (QuestStateTable.ContainsKey(staticQuest.Id)) + { + QuestStateTable[staticQuest.Id].DeleteState(); + QuestStateTable.Remove(staticQuest.Id); + } + } + + public Quest GetActiveQuest(uint id) + { + return ActiveQuests.Find(quest => quest.GetQuestId() == id); + } + public Quest[] GetQuestsForNpc(Npc npc) { return ActiveQuests.FindAll(quest => quest.IsQuestENPC(player, npc)).ToArray(); diff --git a/Map Server/Actors/StaticActors.cs b/Map Server/Actors/StaticActors.cs index 73082cf8..c5b002c6 100644 --- a/Map Server/Actors/StaticActors.cs +++ b/Map Server/Actors/StaticActors.cs @@ -20,6 +20,7 @@ along with Project Meteor Server. If not, see . */ using Meteor.Common; +using Meteor.Map.Actors.QuestNS; using System; using System.Collections.Generic; using System.IO; @@ -98,7 +99,7 @@ namespace Meteor.Map.Actors if (actorType.Equals("Command")) actor = new Command(id, actorName); else if (actorType.Equals("Quest")) - actor = new Quest(id, actorName); + actor = new Quest(id, actorName, output); //else if (actorType.Equals("Status")) //mStaticActors.Add(id, new Status(id, actorName)); else if (actorType.Equals("Judge")) diff --git a/Map Server/DataObjects/QuestData.cs b/Map Server/DataObjects/QuestGameData.cs similarity index 82% rename from Map Server/DataObjects/QuestData.cs rename to Map Server/DataObjects/QuestGameData.cs index 3054f64f..98723b7b 100644 --- a/Map Server/DataObjects/QuestData.cs +++ b/Map Server/DataObjects/QuestGameData.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Meteor.Map.DataObjects { - class QuestData + class QuestGameData { public uint Id { get; } public string ClassName { get; } @@ -15,7 +15,7 @@ namespace Meteor.Map.DataObjects public int MinLevel { get; } public int MinGCRank { get; } - public QuestData(uint id, string className, string name, uint prereq, int minLv, int minGcRank) + public QuestGameData(uint id, string className, string name, uint prereq, int minLv, int minGcRank) { Id = id; ClassName = className; diff --git a/Map Server/DataObjects/Session.cs b/Map Server/DataObjects/Session.cs index 299fa72c..28233d26 100644 --- a/Map Server/DataObjects/Session.cs +++ b/Map Server/DataObjects/Session.cs @@ -25,8 +25,8 @@ using Meteor.Map.Actors; using Meteor.Map.packets.send.actor; using System.Collections.Generic; using Meteor.Map.actors.chara.npc; -using static Meteor.Map.Actors.Quest; -using static Meteor.Map.Actors.QuestState; +using Meteor.Map.Actors.QuestNS; +using static Meteor.Map.Actors.QuestNS.QuestState; namespace Meteor.Map.DataObjects { diff --git a/Map Server/Database.cs b/Map Server/Database.cs index 129062ba..aab35607 100644 --- a/Map Server/Database.cs +++ b/Map Server/Database.cs @@ -28,12 +28,13 @@ using Meteor.Map.utils; using Meteor.Map.packets.send.player; using Meteor.Map.DataObjects; using Meteor.Map.Actors; +using Meteor.Map.Actors.QuestNS; using Meteor.Map.actors.chara.player; using Meteor.Map.packets.receive.supportdesk; using Meteor.Map.actors.chara.npc; using Meteor.Map.actors.chara.ai; using Meteor.Map.packets.send.actor.battle; -using Meteor.Map.DataObjects; + using System.Security.Cryptography; namespace Meteor.Map @@ -72,11 +73,11 @@ namespace Meteor.Map return id; } - public static Dictionary GetQuestGamedata() + public static Dictionary GetQuestGamedata() { using (var conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) { - Dictionary gamedataQuests = new Dictionary(); + Dictionary gamedataQuests = new Dictionary(); try { @@ -88,8 +89,7 @@ namespace Meteor.Map className, questName, prerequisite, - minLevel, - minGCRank + minLevel FROM gamedata_quests "; @@ -104,8 +104,8 @@ namespace Meteor.Map string name = reader.GetString("questName"); uint prerequisite = reader.GetUInt32("prerequisite"); ushort minLevel = reader.GetUInt16("minLevel"); - ushort minRank = reader.GetUInt16("minGCRank"); - gamedataQuests.Add(questId, new QuestData(questId, code, name, prerequisite, minLevel, minRank)); + //ushort minRank = reader.GetUInt16("minGCRank"); + gamedataQuests.Add(questId, new QuestGameData(questId, code, name, prerequisite, minLevel, 0)); } } } @@ -544,6 +544,8 @@ namespace Meteor.Map string query; MySqlCommand cmd; + QuestData qData = quest.GetData(); + using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) { try @@ -564,10 +566,14 @@ namespace Meteor.Map cmd.Parameters.AddWithValue("@slot", slot); cmd.Parameters.AddWithValue("@questId", 0xFFFFF & quest.Id); cmd.Parameters.AddWithValue("@sequence", quest.GetSequence()); - cmd.Parameters.AddWithValue("@flags", quest.GetFlags()); - cmd.Parameters.AddWithValue("@counter1", quest.GetCounter(1)); - cmd.Parameters.AddWithValue("@counter2", quest.GetCounter(2)); - cmd.Parameters.AddWithValue("@counter3", quest.GetCounter(3)); + + if (qData != null) + { + cmd.Parameters.AddWithValue("@flags", qData.GetFlags()); + cmd.Parameters.AddWithValue("@counter1", qData.GetCounter(1)); + cmd.Parameters.AddWithValue("@counter2", qData.GetCounter(2)); + cmd.Parameters.AddWithValue("@counter3", qData.GetCounter(3)); + } cmd.ExecuteNonQuery(); } @@ -1218,11 +1224,13 @@ namespace Meteor.Map ushort counter1 = reader.GetUInt16("counter1"); ushort counter2 = reader.GetUInt16("counter2"); ushort counter3 = reader.GetUInt16("counter3"); + //ushort counter4 = reader.GetUInt16("counter4"); Quest baseQuest = (Quest) Server.GetStaticActors(questId); player.playerWork.questScenario[index] = questId; - player.questScenario[index] = new Quest(player, baseQuest, sequence, flags, counter1, counter2, counter3); + player.questScenario[index] = new Quest(player, baseQuest, sequence); + player.questScenario[index].SetData(flags, counter1, counter2, counter3, 0); } } diff --git a/Map Server/Lua/LuaEngine.cs b/Map Server/Lua/LuaEngine.cs index 2a05fbd7..dc106826 100644 --- a/Map Server/Lua/LuaEngine.cs +++ b/Map Server/Lua/LuaEngine.cs @@ -27,7 +27,6 @@ using Meteor.Map.packets.receive.events; using Meteor.Map.packets.send; using Meteor.Map.packets.send.events; using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; using MoonSharp.Interpreter.Loaders; using System; using System.Collections.Generic; @@ -37,11 +36,11 @@ using Meteor.Map.actors.area; using System.Threading; using Meteor.Map.actors.chara.ai; using Meteor.Map.actors.chara.ai.controllers; -using Meteor.Map.DataObjects; using Meteor.Map.actors.chara.player; using Meteor.Map.Actors.Chara; using Meteor.Map.DataObjects.chara; using Meteor.Map.actors.chara; +using Meteor.Map.Actors.QuestNS; namespace Meteor.Map.lua { @@ -78,6 +77,7 @@ namespace Meteor.Map.lua UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); + UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); diff --git a/Map Server/Map Server.csproj b/Map Server/Map Server.csproj index 8d421cd9..3698fc77 100644 --- a/Map Server/Map Server.csproj +++ b/Map Server/Map Server.csproj @@ -180,12 +180,13 @@ + - + diff --git a/Map Server/Server.cs b/Map Server/Server.cs index af4724bf..c3407354 100644 --- a/Map Server/Server.cs +++ b/Map Server/Server.cs @@ -50,7 +50,7 @@ namespace Meteor.Map private static WorldManager WorldManager; private static Dictionary GamedataItems; private static Dictionary GamedataGuildleves; - private static Dictionary GamedataQuests; + private static Dictionary GamedataQuests; private static StaticActors StaticActors; private PacketProcessor mProcessor; @@ -332,7 +332,7 @@ namespace Meteor.Map return null; } - public static QuestData GetQuestGamedata(uint id) + public static QuestGameData GetQuestGamedata(uint id) { if (GamedataQuests.ContainsKey(id)) return GamedataQuests[id]; @@ -341,7 +341,7 @@ namespace Meteor.Map } - public static QuestData[] GetQuestGamedataByMaxLvl(int lvl, bool all = false) + public static QuestGameData[] GetQuestGamedataByMaxLvl(int lvl, bool all = false) { if (all) return GamedataQuests.Values.Where(quest => quest.MinLevel > 0 && quest.MinLevel <= lvl).ToArray(); @@ -349,17 +349,17 @@ namespace Meteor.Map return GamedataQuests.Values.Where(quest => quest.MinLevel > 0 && quest.MinLevel == lvl).ToArray(); } - public static QuestData[] GetQuestGamedataByPrerequisite(uint questId) + public static QuestGameData[] GetQuestGamedataByPrerequisite(uint questId) { return GamedataQuests.Values.Where(quest => quest.PrerequisiteQuest == questId).ToArray(); } - public static QuestData[] GetQuestGamedataAllPrerequisite() + public static QuestGameData[] GetQuestGamedataAllPrerequisite() { return GamedataQuests.Values.Where(quest => quest.PrerequisiteQuest != 0).ToArray(); } - public static QuestData[] GetQuestGamedataAllGCRanked() + public static QuestGameData[] GetQuestGamedataAllGCRanked() { return GamedataQuests.Values.Where(quest => quest.MinGCRank != 0).ToArray(); } From a2c4d077e9a9d5b3c1a45a97d10f617d9e22e95a Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Thu, 17 Feb 2022 21:30:06 -0500 Subject: [PATCH 4/9] Fixed quest loading --- Map Server/Actors/Chara/Player/Player.cs | 6 +-- Map Server/Actors/Quest/QuestData.cs | 2 +- Map Server/Database.cs | 57 +++++++++++++++++++----- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/Map Server/Actors/Chara/Player/Player.cs b/Map Server/Actors/Chara/Player/Player.cs index 910cfda2..b1ae6fc5 100644 --- a/Map Server/Actors/Chara/Player/Player.cs +++ b/Map Server/Actors/Chara/Player/Player.cs @@ -1436,7 +1436,7 @@ namespace Meteor.Map.Actors playerWork.questScenario[freeSlot] = instance.Id; questScenario[freeSlot] = instance; - Database.SaveQuest(this, questScenario[freeSlot]); + Database.SaveQuest(this, questScenario[freeSlot], freeSlot); SendQuestClientUpdate(freeSlot); if (!isSilent) @@ -1458,7 +1458,7 @@ namespace Meteor.Map.Actors { questScenario[i] = newQuestInstance; playerWork.questScenario[i] = questScenario[i].Id; - Database.SaveQuest(this, questScenario[i]); + Database.SaveQuest(this, questScenario[i], i); SendQuestClientUpdate(i); break; } @@ -1557,7 +1557,7 @@ namespace Meteor.Map.Actors if (activeQuest == null) questStateManager.ForceAddActiveQuest(questScenario[freeSlot]); - Database.SaveQuest(this, questScenario[freeSlot]); + Database.SaveQuest(this, questScenario[freeSlot], freeSlot); SendQuestClientUpdate(freeSlot); if (!isSilent) diff --git a/Map Server/Actors/Quest/QuestData.cs b/Map Server/Actors/Quest/QuestData.cs index 6b0818db..4707a03a 100644 --- a/Map Server/Actors/Quest/QuestData.cs +++ b/Map Server/Actors/Quest/QuestData.cs @@ -161,7 +161,7 @@ namespace Meteor.Map.Actors.QuestNS public void Save() { - Database.SaveQuest(owner, parent); + Database.UpdateQuest(owner, parent); } } } diff --git a/Map Server/Database.cs b/Map Server/Database.cs index aab35607..d7d47f1f 100644 --- a/Map Server/Database.cs +++ b/Map Server/Database.cs @@ -527,18 +527,6 @@ namespace Meteor.Map } } - public static void SaveQuest(Player player, Quest quest) - { - int slot = player.GetQuestSlot(quest.Id); - if (slot == -1) - { - Program.Log.Error("Tried saving quest player didn't have: Player: {0:x}, QuestId: {0:x}", player.Id, quest.Id); - return; - } - else - SaveQuest(player, quest, slot); - } - public static void SaveQuest(Player player, Quest quest, int slot) { string query; @@ -588,6 +576,51 @@ namespace Meteor.Map } } + public static void UpdateQuest(Player player, Quest quest) + { + string query; + MySqlCommand cmd; + + QuestData qData = quest.GetData(); + + using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) + { + try + { + conn.Open(); + + query = @" + UPDATE characters_quest_scenario + SET sequence = @sequence, flags = @flags, counter1 = @counter1, counter2 = @counter2, counter3 = @counter3 + WHERE characterId = @charaId and questId = @questId + "; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charaId", player.Id); + cmd.Parameters.AddWithValue("@questId", 0xFFFFF & quest.Id); + cmd.Parameters.AddWithValue("@sequence", quest.GetSequence()); + + if (qData != null) + { + cmd.Parameters.AddWithValue("@flags", qData.GetFlags()); + cmd.Parameters.AddWithValue("@counter1", qData.GetCounter(1)); + cmd.Parameters.AddWithValue("@counter2", qData.GetCounter(2)); + cmd.Parameters.AddWithValue("@counter3", qData.GetCounter(3)); + } + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + public static void MarkGuildleve(Player player, uint glId, bool isAbandoned, bool isCompleted) { string query; From 306f4ef346c57c7346161bda906af0392b5a35f6 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Sat, 19 Feb 2022 01:17:50 -0500 Subject: [PATCH 5/9] Got aetheryte map and completed quest work values... working. --- Common Class Lib/Bitstream.cs | 50 +- Common Class Lib/Utils.cs | 20 + Data/scripts/quests/etc/etc3g0.lua | 4 +- Map Server/Actors/Chara/Player/Player.cs | 58 +- Map Server/Actors/Quest/QuestStateManager.cs | 20 +- Map Server/Database.cs | 54 +- Map Server/Map Server.csproj | 2 +- Map Server/PacketProcessor.cs | 650 +++++++++--------- ...uestPacket.cs => WorkSyncRequestPacket.cs} | 27 +- .../Send/Actor/SetActorPropetyPacket.cs | 40 ++ Map Server/Program.cs | 3 +- 11 files changed, 546 insertions(+), 382 deletions(-) rename Map Server/Packets/Receive/{ParameterDataRequestPacket.cs => WorkSyncRequestPacket.cs} (65%) diff --git a/Common Class Lib/Bitstream.cs b/Common Class Lib/Bitstream.cs index a543a75c..1e3df17c 100644 --- a/Common Class Lib/Bitstream.cs +++ b/Common Class Lib/Bitstream.cs @@ -9,7 +9,7 @@ namespace Meteor.Common { public class Bitstream { - private readonly byte[] Data; + private byte[] Data; public Bitstream(uint numBits, bool setAllTrue = false) { @@ -51,6 +51,12 @@ namespace Meteor.Common } } + public void SetTo(bool[] result) + { + Debug.Assert(Data.Length == result.Length / 8); + Data = Utils.ConvertBoolArrayToBinaryStream(result); + } + public bool Get(uint at) { return Get((int)at); @@ -60,6 +66,7 @@ namespace Meteor.Common { int bytePos = at / 8; int bitPos = at % 8; + return (Data[bytePos] & (1 << bitPos)) != 0; } @@ -146,5 +153,46 @@ namespace Meteor.Common return Data; } + public byte[] GetSlice(ushort from, ushort to) + { + int remainder = ((to - from) % 8 != 0) ? 1 : 0; + byte[] toReturn = new byte[((to - from) / 8) + remainder + 1]; + toReturn[toReturn.Length - 1] = 0x3; + + + byte curByte = 0; + + int destByteIndx = 0; + int destShiftIndx = 0; + int srcByteIndx = from / 8; + int srcShiftIndx = from % 8; + + for (int i = from; i <= to; i++) + { + // Skip Zeros + if (Data[srcByteIndx] == 0) + { + srcByteIndx++; + srcShiftIndx = 0; + destByteIndx++; + i += 8; + } + + bool val = (Data[srcByteIndx] & (1 << srcShiftIndx)) != 0; + + curByte |= (byte)((val ? 1 : 0) << destShiftIndx++); + if (destShiftIndx == 8) + { + toReturn[destByteIndx++] = curByte; + destShiftIndx = 0; + curByte = 0; + } + } + + if (destByteIndx == toReturn.Length - 2) + toReturn[destByteIndx] = curByte; + + return toReturn; + } } } diff --git a/Common Class Lib/Utils.cs b/Common Class Lib/Utils.cs index 9b52009c..9556e8c5 100644 --- a/Common Class Lib/Utils.cs +++ b/Common Class Lib/Utils.cs @@ -255,6 +255,26 @@ namespace Meteor.Common return data; } + public static bool[] ConvertBinaryStreamToBoolArray(byte[] bytes) + { + bool[] data = new bool[bytes.Length * 8]; + + int boolCounter = 0; + for (int i = 0; i < bytes.Length; i ++) + { + if (bytes[i] == 0) + { + boolCounter += 8; + continue; + } + + for (int bitCount = 0; bitCount < 8; bitCount++) + data[boolCounter++] = (bytes[i] >> bitCount & 1) == 1; + } + + return data; + } + public static string ToStringBase63(int number) { var lookup = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; diff --git a/Data/scripts/quests/etc/etc3g0.lua b/Data/scripts/quests/etc/etc3g0.lua index cacb5111..e8d333ae 100644 --- a/Data/scripts/quests/etc/etc3g0.lua +++ b/Data/scripts/quests/etc/etc3g0.lua @@ -74,7 +74,7 @@ function onTalk(player, quest, npc, eventName) -- Offer the quest if (npcClassId == KINNISON and not player:HasQuest(quest)) then local questAccepted = callClientFunction(player, "delegateEvent", player, quest, "processEventOffersStart"); - if (questAccepted) then + if (questAccepted == 1) then player:AcceptQuest(quest); end player:EndEvent(); @@ -146,7 +146,7 @@ function onTalk(player, quest, npc, eventName) if (npcClassId == KINNISON) then callClientFunction(player, "delegateEvent", player, quest, "processEventClear"); callClientFunction(player, "delegateEvent", player, quest, "sqrwa", 200, 1, 1, 9); - player:CompleteQuest(quest:GetQuestId()); + player:CompleteQuest(quest); end end quest:UpdateENPCs(); diff --git a/Map Server/Actors/Chara/Player/Player.cs b/Map Server/Actors/Chara/Player/Player.cs index b1ae6fc5..e67e1379 100644 --- a/Map Server/Actors/Chara/Player/Player.cs +++ b/Map Server/Actors/Chara/Player/Player.cs @@ -47,6 +47,7 @@ using Meteor.Map.packets.send.actor.battle; using Meteor.Map.packets.receive.events; using static Meteor.Map.LuaUtils; using Meteor.Map.packets.send.actor.events; +using System.Text; namespace Meteor.Map.Actors { @@ -243,8 +244,7 @@ namespace Meteor.Map.Actors charaWork.command[15] = 0xA0F00000 | 22015; charaWork.commandAcquired[27150 - 26000] = true; - - playerWork.questScenarioComplete[110001 - 110001] = true; + playerWork.questGuildleveComplete[120050 - 120001] = true; for (int i = 0; i < charaWork.additionalCommandAcquired.Length; i++ ) @@ -278,7 +278,7 @@ namespace Meteor.Map.Actors CalculateBaseStats(); questStateManager = new QuestStateManager(this); - questStateManager.Init(questScenario); + questStateManager.Init(questScenario, playerWork.questScenarioComplete); } public List Create0x132Packets() @@ -1157,6 +1157,44 @@ namespace Meteor.Map.Actors } + private void SendAchievedAetheryte(ushort from, ushort to) + { + Bitstream fakeAetheryte = new Bitstream(512, true); + + SetActorPropetyPacket completedQuestWorkUpdate = new SetActorPropetyPacket(from, to, "work/achieveAetheryte"); + completedQuestWorkUpdate.AddBitfield(Utils.MurmurHash2("work.event_achieve_aetheryte", 0), fakeAetheryte.GetSlice(from, to)); + completedQuestWorkUpdate.AddTarget(); + QueuePacket(completedQuestWorkUpdate.BuildPacket(Id)); + } + + private void SendCompletedQuests(ushort from, ushort to) + { + Bitstream completed = questStateManager.GetCompletedBitstream(); + completed.SetAll(true); + byte[] data = completed.GetSlice(from, to); + + SetActorPropetyPacket completedQuestWorkUpdate = new SetActorPropetyPacket(from, to, "playerWork/journal"); + completedQuestWorkUpdate.AddBitfield(Utils.MurmurHash2("playerWork.questScenarioComplete", 0), data); + completedQuestWorkUpdate.AddTarget(); + QueuePacket(completedQuestWorkUpdate.BuildPacket(Id)); + } + + public void OnWorkSyncRequest(string propertyName, ushort from = 0, ushort to = 0) + { + switch (propertyName) + { + case "charaWork/exp": + SendCharaExpInfo(); + break; + case "work/achieveAetheryte": + SendAchievedAetheryte(from, to); + break; + case "playerWork/questCompleteS": + SendCompletedQuests(from, to); + break; + } + } + public int GetHighestLevel() { int max = 0; @@ -1436,7 +1474,6 @@ namespace Meteor.Map.Actors playerWork.questScenario[freeSlot] = instance.Id; questScenario[freeSlot] = instance; - Database.SaveQuest(this, questScenario[freeSlot], freeSlot); SendQuestClientUpdate(freeSlot); if (!isSilent) @@ -1446,6 +1483,8 @@ namespace Meteor.Map.Actors instance.OnAccept(); + Database.SaveQuest(this, questScenario[freeSlot], freeSlot); + return true; } @@ -1458,8 +1497,11 @@ namespace Meteor.Map.Actors { questScenario[i] = newQuestInstance; playerWork.questScenario[i] = questScenario[i].Id; - Database.SaveQuest(this, questScenario[i], i); SendQuestClientUpdate(i); + oldQuestInstance.OnComplete(); + questStateManager.UpdateQuestCompleted(oldQuestInstance); + newQuestInstance.OnAccept(); + Database.SaveQuest(this, questScenario[i], i); break; } } @@ -1472,14 +1514,14 @@ namespace Meteor.Map.Actors { // Remove the quest from the DB and update client work values playerWork.questScenarioComplete[completed.GetQuestId() - 110001] = true; - Database.CompleteQuest(playerSession.GetActor(), completed.Id); - Database.RemoveQuest(this, completed.Id); questScenario[slot] = null; playerWork.questScenario[slot] = 0; SendQuestClientUpdate(slot); // Reset active quest and quest state completed.OnComplete(); + Database.SaveCompletedQuests(playerSession.GetActor()); + Database.RemoveQuest(this, completed.Id); questStateManager.UpdateQuestCompleted(completed); // Msg Player @@ -1512,13 +1554,13 @@ namespace Meteor.Map.Actors } // Remove the quest from the DB and update client work values - Database.RemoveQuest(this, abandoned.Id); questScenario[slot] = null; playerWork.questScenario[slot] = 0; SendQuestClientUpdate(slot); // Reset active quest and quest state abandoned.OnAbandon(); + Database.RemoveQuest(this, abandoned.Id); questStateManager.UpdateQuestAbandoned(); // Msg Player diff --git a/Map Server/Actors/Quest/QuestStateManager.cs b/Map Server/Actors/Quest/QuestStateManager.cs index 23452b88..1bc0bebd 100644 --- a/Map Server/Actors/Quest/QuestStateManager.cs +++ b/Map Server/Actors/Quest/QuestStateManager.cs @@ -15,6 +15,7 @@ namespace Meteor.Map.Actors.QuestNS private const int SCENARIO_MAX = 2048; private readonly Player player; + private readonly Bitstream CompletedQuestsBitfield = new Bitstream(SCENARIO_MAX); private readonly Bitstream AvailableQuestsBitfield = new Bitstream(SCENARIO_MAX); private readonly Bitstream MinLevelBitfield = new Bitstream(SCENARIO_MAX); private readonly Bitstream PrereqBitfield = new Bitstream(SCENARIO_MAX, true); @@ -28,12 +29,12 @@ namespace Meteor.Map.Actors.QuestNS this.player = player; } - public void Init(Quest[] questScenario) + public void Init(Quest[] journalQuests, bool[] completedQuests) { // Preload any quests that the player loaded - if (questScenario != null) + if (journalQuests != null) { - foreach (var quest in questScenario) + foreach (var quest in journalQuests) { if (quest != null) { @@ -49,10 +50,10 @@ namespace Meteor.Map.Actors.QuestNS MinLevelBitfield.Set(questData.Id - SCENARIO_START); // Init Prereq - Bitstream completed = new Bitstream(player.playerWork.questScenarioComplete); + CompletedQuestsBitfield.SetTo(completedQuests); foreach (var questData in Server.GetQuestGamedataAllPrerequisite()) { - if (completed.Get(((Quest)Server.GetStaticActors(0xA0F00000 | questData.PrerequisiteQuest)).GetQuestId() - SCENARIO_START)) + if (CompletedQuestsBitfield.Get(((Quest)Server.GetStaticActors(0xA0F00000 | questData.PrerequisiteQuest)).GetQuestId() - SCENARIO_START)) PrereqBitfield.Set(questData.Id - SCENARIO_START); else PrereqBitfield.Clear(questData.Id - SCENARIO_START); @@ -71,6 +72,7 @@ namespace Meteor.Map.Actors.QuestNS public void UpdateQuestCompleted(Quest quest) { + CompletedQuestsBitfield.Set(quest.Id - SCENARIO_START); QuestGameData[] updated = Server.GetQuestGamedataByPrerequisite(quest.GetQuestId()); foreach (var questData in updated) PrereqBitfield.Set(questData.Id - SCENARIO_START); @@ -84,7 +86,8 @@ namespace Meteor.Map.Actors.QuestNS private void ComputeAvailable() { - Bitstream result = new Bitstream(player.playerWork.questScenarioComplete); + Bitstream result = new Bitstream(SCENARIO_MAX); + result.OR(CompletedQuestsBitfield); result.NOT(); result.AND(MinLevelBitfield); result.AND(PrereqBitfield); @@ -152,5 +155,10 @@ namespace Meteor.Map.Actors.QuestNS { return ActiveQuests.FindAll(quest => quest.IsQuestENPC(player, npc)).ToArray(); } + + public Bitstream GetCompletedBitstream() + { + return CompletedQuestsBitfield; + } } } diff --git a/Map Server/Database.cs b/Map Server/Database.cs index d7d47f1f..cd13735c 100644 --- a/Map Server/Database.cs +++ b/Map Server/Database.cs @@ -763,7 +763,7 @@ namespace Meteor.Map } } - public static void CompleteQuest(Player player, uint questId) + public static void SaveCompletedQuests(Player player) { string query; MySqlCommand cmd; @@ -776,15 +776,15 @@ namespace Meteor.Map query = @" INSERT INTO characters_quest_completed - (characterId, questId) + (characterId, completedQuests) VALUES - (@charaId, @questId) - ON DUPLICATE KEY UPDATE characterId=characterId + (@charaId, @completedQuests) + ON DUPLICATE KEY UPDATE completedQuests=completedQuests "; cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charaId", player.Id); - cmd.Parameters.AddWithValue("@questId", 0xFFFFF & questId); + cmd.Parameters.AddWithValue("@completedQuests", Utils.ConvertBoolArrayToBinaryStream(player.playerWork.questScenarioComplete)); cmd.ExecuteNonQuery(); } @@ -799,31 +799,6 @@ namespace Meteor.Map } } - public static bool IsQuestCompleted(Player player, uint questId) - { - bool isCompleted = false; - using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) - { - try - { - conn.Open(); - MySqlCommand cmd = new MySqlCommand("SELECT * FROM characters_quest_completed WHERE characterId = @charaId and questId = @questId", conn); - cmd.Parameters.AddWithValue("@charaId", player.Id); - cmd.Parameters.AddWithValue("@questId", questId); - isCompleted = cmd.ExecuteScalar() != null; - } - catch (MySqlException e) - { - Program.Log.Error(e.ToString()); - } - finally - { - conn.Dispose(); - } - } - return isCompleted; - } - public static void LoadPlayerCharacter(Player player) { string query; @@ -1267,6 +1242,25 @@ namespace Meteor.Map } } + //Load Completed Quests bitstream + query = @" + SELECT + completedQuests + FROM characters_quest_completed WHERE characterId = @charaId"; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charaId", player.Id); + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + // Replace the bool stream or use the default empty one. + if (reader.Read()) + { + byte[] bytes = new byte[256]; + reader.GetBytes(reader.GetOrdinal("completedQuests"), 0, bytes, 0, 256); + player.playerWork.questScenarioComplete = Utils.ConvertBinaryStreamToBoolArray(bytes); + } + } + //Load Local Guildleves query = @" SELECT diff --git a/Map Server/Map Server.csproj b/Map Server/Map Server.csproj index 3698fc77..4badd30b 100644 --- a/Map Server/Map Server.csproj +++ b/Map Server/Map Server.csproj @@ -227,7 +227,7 @@ - + diff --git a/Map Server/PacketProcessor.cs b/Map Server/PacketProcessor.cs index c36f761a..0c77c0af 100644 --- a/Map Server/PacketProcessor.cs +++ b/Map Server/PacketProcessor.cs @@ -39,6 +39,7 @@ using Meteor.Map.Actors; using Meteor.Map.packets.WorldPackets.Send; using Meteor.Map.packets.WorldPackets.Receive; using Meteor.Map.actors.director; +using System.Text; namespace Meteor.Map { @@ -49,345 +50,344 @@ namespace Meteor.Map public PacketProcessor(Server server) { mServer = server; - } + } public void ProcessPacket(ZoneConnection client, SubPacket subpacket) - { - Session session = mServer.GetSession(subpacket.header.sourceId); - - if (session == null && subpacket.gameMessage.opcode != 0x1000) - return; - - //Normal Game Opcode - switch (subpacket.gameMessage.opcode) - { - //World Server - Error - case 0x100A: - ErrorPacket worldError = new ErrorPacket(subpacket.data); - switch (worldError.errorCode) - { - case 0x01: - session.GetActor().SendGameMessage(Server.GetWorldManager().GetActor(), 60005, 0x20); - break; - } - break; - //World Server - Session Begin - case 0x1000: - subpacket.DebugPrintSubPacket(); - - SessionBeginPacket beginSessionPacket = new SessionBeginPacket(subpacket.data); - - session = mServer.AddSession(subpacket.header.sourceId); - - if (!beginSessionPacket.isLogin) - Server.GetWorldManager().DoZoneIn(session.GetActor(), false, session.GetActor().destinationSpawnType); - - Program.Log.Info("{0} has been added to the session list.", session.GetActor().DisplayName); - - client.FlushQueuedSendPackets(); - break; - //World Server - Session End - case 0x1001: - SessionEndPacket endSessionPacket = new SessionEndPacket(subpacket.data); - - if (endSessionPacket.destinationZoneId == 0) - session.GetActor().CleanupAndSave(); - else - session.GetActor().CleanupAndSave(endSessionPacket.destinationZoneId, endSessionPacket.destinationSpawnType, endSessionPacket.destinationX, endSessionPacket.destinationY, endSessionPacket.destinationZ, endSessionPacket.destinationRot); - - Server.GetServer().RemoveSession(session.id); - Program.Log.Info("{0} has been removed from the session list.", session.GetActor().DisplayName); - - session.QueuePacket(SessionEndConfirmPacket.BuildPacket(session, endSessionPacket.destinationZoneId)); - client.FlushQueuedSendPackets(); - break; - //World Server - Party Synch - case 0x1020: - PartySyncPacket partySyncPacket = new PartySyncPacket(subpacket.data); - Server.GetWorldManager().PartyMemberListRecieved(partySyncPacket); - break; - //World Server - Linkshell Creation Result - case 0x1025: - LinkshellResultPacket lsResult = new LinkshellResultPacket(subpacket.data); - LuaEngine.GetInstance().OnSignal("ls_result", lsResult.resultCode); - break; - //Ping - case 0x0001: - //subpacket.DebugPrintSubPacket(); - PingPacket pingPacket = new PingPacket(subpacket.data); - session.QueuePacket(PongPacket.BuildPacket(session.id, pingPacket.time)); - session.Ping(); - break; - //Unknown - case 0x0002: - - subpacket.DebugPrintSubPacket(); - session.QueuePacket(_0x2Packet.BuildPacket(session.id)); - client.FlushQueuedSendPackets(); - - break; - //Chat Received - case 0x0003: - ChatMessagePacket chatMessage = new ChatMessagePacket(subpacket.data); - //Program.Log.Info("Got type-{5} message: {0} @ {1}, {2}, {3}, Rot: {4}", chatMessage.message, chatMessage.posX, chatMessage.posY, chatMessage.posZ, chatMessage.posRot, chatMessage.logType); - - if (chatMessage.message.StartsWith("!")) - { - if (Server.GetCommandProcessor().DoCommand(chatMessage.message, session)) - return; ; - } - - if (chatMessage.logType == SendMessagePacket.MESSAGE_TYPE_SAY || chatMessage.logType == SendMessagePacket.MESSAGE_TYPE_SHOUT) - session.GetActor().BroadcastPacket(SendMessagePacket.BuildPacket(session.id, chatMessage.logType, session.GetActor().DisplayName, chatMessage.message), false); - - break; - //Langauge Code (Client safe to send packets to now) - case 0x0006: - LangaugeCodePacket langCode = new LangaugeCodePacket(subpacket.data); - LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onBeginLogin", true); - Server.GetWorldManager().DoZoneIn(session.GetActor(), true, 0x1); - LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onLogin", true); - session.languageCode = langCode.languageCode; - break; - //Unknown - Happens a lot at login, then once every time player zones - case 0x0007: - //subpacket.DebugPrintSubPacket(); - ZoneInCompletePacket zoneInCompletePacket = new ZoneInCompletePacket(subpacket.data); - break; - //Update Position - case 0x00CA: - //Update Position - UpdatePlayerPositionPacket posUpdate = new UpdatePlayerPositionPacket(subpacket.data); - session.UpdatePlayerActorPosition(posUpdate.x, posUpdate.y, posUpdate.z, posUpdate.rot, posUpdate.moveState); - session.GetActor().SendInstanceUpdate(); - - if (session.GetActor().IsInZoneChange()) - session.GetActor().SetZoneChanging(false); - - break; - //Set Target - case 0x00CD: - //subpacket.DebugPrintSubPacket(); - - SetTargetPacket setTarget = new SetTargetPacket(subpacket.data); - session.GetActor().currentTarget = setTarget.actorID; - session.GetActor().isAutoAttackEnabled = setTarget.attackTarget != 0xE0000000; - session.GetActor().BroadcastPacket(SetActorTargetAnimatedPacket.BuildPacket(session.id, setTarget.actorID), true); - break; - //Lock Target - case 0x00CC: - LockTargetPacket lockTarget = new LockTargetPacket(subpacket.data); - session.GetActor().currentLockedTarget = lockTarget.actorID; - break; - //Start Event - case 0x012D: - subpacket.DebugPrintSubPacket(); - EventStartPacket eventStart = new EventStartPacket(subpacket.data); - - /* - if (eventStart.error != null) - { - player.errorMessage += eventStart.error; - - if (eventStart.errorIndex == eventStart.errorNum - 1) - Program.Log.Error("\n"+player.errorMessage); + { + Session session = mServer.GetSession(subpacket.header.sourceId); + if (session == null && subpacket.gameMessage.opcode != 0x1000) + return; + //Normal Game Opcode + switch (subpacket.gameMessage.opcode) + { + //World Server - Error + case 0x100A: + ErrorPacket worldError = new ErrorPacket(subpacket.data); + switch (worldError.errorCode) + { + case 0x01: + session.GetActor().SendGameMessage(Server.GetWorldManager().GetActor(), 60005, 0x20); break; - } - */ + } + break; + //World Server - Session Begin + case 0x1000: + subpacket.DebugPrintSubPacket(); - Actor ownerActor = Server.GetStaticActors(eventStart.ownerActorID); - + SessionBeginPacket beginSessionPacket = new SessionBeginPacket(subpacket.data); + + session = mServer.AddSession(subpacket.header.sourceId); + + if (!beginSessionPacket.isLogin) + Server.GetWorldManager().DoZoneIn(session.GetActor(), false, session.GetActor().destinationSpawnType); + + Program.Log.Info("{0} has been added to the session list.", session.GetActor().DisplayName); + + client.FlushQueuedSendPackets(); + break; + //World Server - Session End + case 0x1001: + SessionEndPacket endSessionPacket = new SessionEndPacket(subpacket.data); + + if (endSessionPacket.destinationZoneId == 0) + session.GetActor().CleanupAndSave(); + else + session.GetActor().CleanupAndSave(endSessionPacket.destinationZoneId, endSessionPacket.destinationSpawnType, endSessionPacket.destinationX, endSessionPacket.destinationY, endSessionPacket.destinationZ, endSessionPacket.destinationRot); + + Server.GetServer().RemoveSession(session.id); + Program.Log.Info("{0} has been removed from the session list.", session.GetActor().DisplayName); + + session.QueuePacket(SessionEndConfirmPacket.BuildPacket(session, endSessionPacket.destinationZoneId)); + client.FlushQueuedSendPackets(); + break; + //World Server - Party Synch + case 0x1020: + PartySyncPacket partySyncPacket = new PartySyncPacket(subpacket.data); + Server.GetWorldManager().PartyMemberListRecieved(partySyncPacket); + break; + //World Server - Linkshell Creation Result + case 0x1025: + LinkshellResultPacket lsResult = new LinkshellResultPacket(subpacket.data); + LuaEngine.GetInstance().OnSignal("ls_result", lsResult.resultCode); + break; + //Ping + case 0x0001: + //subpacket.DebugPrintSubPacket(); + PingPacket pingPacket = new PingPacket(subpacket.data); + session.QueuePacket(PongPacket.BuildPacket(session.id, pingPacket.time)); + session.Ping(); + break; + //Unknown + case 0x0002: + + subpacket.DebugPrintSubPacket(); + session.QueuePacket(_0x2Packet.BuildPacket(session.id)); + client.FlushQueuedSendPackets(); + + break; + //Chat Received + case 0x0003: + ChatMessagePacket chatMessage = new ChatMessagePacket(subpacket.data); + //Program.Log.Info("Got type-{5} message: {0} @ {1}, {2}, {3}, Rot: {4}", chatMessage.message, chatMessage.posX, chatMessage.posY, chatMessage.posZ, chatMessage.posRot, chatMessage.logType); + + if (chatMessage.message.StartsWith("!")) + { + if (Server.GetCommandProcessor().DoCommand(chatMessage.message, session)) + return; ; + } + + if (chatMessage.logType == SendMessagePacket.MESSAGE_TYPE_SAY || chatMessage.logType == SendMessagePacket.MESSAGE_TYPE_SHOUT) + session.GetActor().BroadcastPacket(SendMessagePacket.BuildPacket(session.id, chatMessage.logType, session.GetActor().DisplayName, chatMessage.message), false); + + break; + //Langauge Code (Client safe to send packets to now) + case 0x0006: + LangaugeCodePacket langCode = new LangaugeCodePacket(subpacket.data); + LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onBeginLogin", true); + Server.GetWorldManager().DoZoneIn(session.GetActor(), true, 0x1); + LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onLogin", true); + session.languageCode = langCode.languageCode; + break; + //Unknown - Happens a lot at login, then once every time player zones + case 0x0007: + //subpacket.DebugPrintSubPacket(); + ZoneInCompletePacket zoneInCompletePacket = new ZoneInCompletePacket(subpacket.data); + break; + //Update Position + case 0x00CA: + //Update Position + UpdatePlayerPositionPacket posUpdate = new UpdatePlayerPositionPacket(subpacket.data); + session.UpdatePlayerActorPosition(posUpdate.x, posUpdate.y, posUpdate.z, posUpdate.rot, posUpdate.moveState); + session.GetActor().SendInstanceUpdate(); + + if (session.GetActor().IsInZoneChange()) + session.GetActor().SetZoneChanging(false); + + break; + //Set Target + case 0x00CD: + //subpacket.DebugPrintSubPacket(); + + SetTargetPacket setTarget = new SetTargetPacket(subpacket.data); + session.GetActor().currentTarget = setTarget.actorID; + session.GetActor().isAutoAttackEnabled = setTarget.attackTarget != 0xE0000000; + session.GetActor().BroadcastPacket(SetActorTargetAnimatedPacket.BuildPacket(session.id, setTarget.actorID), true); + break; + //Lock Target + case 0x00CC: + LockTargetPacket lockTarget = new LockTargetPacket(subpacket.data); + session.GetActor().currentLockedTarget = lockTarget.actorID; + break; + //Start Event + case 0x012D: + subpacket.DebugPrintSubPacket(); + EventStartPacket eventStart = new EventStartPacket(subpacket.data); + + /* + if (eventStart.error != null) + { + player.errorMessage += eventStart.error; + + if (eventStart.errorIndex == eventStart.errorNum - 1) + Program.Log.Error("\n"+player.errorMessage); + + + break; + } + */ + + Actor ownerActor = Server.GetStaticActors(eventStart.ownerActorID); + + if (ownerActor == null) + { + //Is it your retainer? + if (session.GetActor().currentSpawnedRetainer != null && session.GetActor().currentSpawnedRetainer.Id == eventStart.ownerActorID) + ownerActor = session.GetActor().currentSpawnedRetainer; + //Is it a instance actor? + if (ownerActor == null) + ownerActor = session.GetActor().CurrentArea.FindActorInArea(eventStart.ownerActorID); + //Is it a Director? if (ownerActor == null) { - //Is it your retainer? - if (session.GetActor().currentSpawnedRetainer != null && session.GetActor().currentSpawnedRetainer.Id == eventStart.ownerActorID) - ownerActor = session.GetActor().currentSpawnedRetainer; - //Is it a instance actor? - if (ownerActor == null) - ownerActor = session.GetActor().CurrentArea.FindActorInArea(eventStart.ownerActorID); - //Is it a Director? - if (ownerActor == null) + Director director = session.GetActor().GetDirector(eventStart.ownerActorID); + if (director != null) + ownerActor = director; + else { - Director director = session.GetActor().GetDirector(eventStart.ownerActorID); - if (director != null) - ownerActor = director; - else - { - Program.Log.Debug("\n===Event START===\nCould not find actor 0x{0:X} for event started by caller: 0x{1:X}\nEvent Starter: {2}\nParams: {3}", eventStart.triggerActorID, eventStart.ownerActorID, eventStart.eventName, LuaUtils.DumpParams(eventStart.luaParams)); - break; - } - } - } - - session.GetActor().StartEvent(ownerActor, eventStart); - - Program.Log.Debug("\n===Event START===\nSource Actor: 0x{0:X}\nCaller Actor: 0x{1:X}\nVal1: 0x{2:X}\nVal2: 0x{3:X}\nEvent Starter: {4}\nParams: {5}", eventStart.triggerActorID, eventStart.ownerActorID, eventStart.serverCodes, eventStart.unknown, eventStart.eventName, LuaUtils.DumpParams(eventStart.luaParams)); - break; - //Unknown, happens at npc spawn and cutscene play???? - case 0x00CE: - subpacket.DebugPrintSubPacket(); - break; - //Countdown requested - case 0x00CF: - CountdownRequestPacket countdownPacket = new CountdownRequestPacket(subpacket.data); - session.GetActor().BroadcastCountdown(countdownPacket.countdownLength, countdownPacket.syncTime); - break; - //Event Result - case 0x012E: - subpacket.DebugPrintSubPacket(); - EventUpdatePacket eventUpdate = new EventUpdatePacket(subpacket.data); - Program.Log.Debug("\n===Event UPDATE===\nSource Actor: 0x{0:X}\nCaller Actor: 0x{1:X}\nVal1: 0x{2:X}\nVal2: 0x{3:X}\nStep: 0x{4:X}\nParams: {5}", eventUpdate.triggerActorID, eventUpdate.serverCodes, eventUpdate.unknown1, eventUpdate.unknown2, eventUpdate.eventType, LuaUtils.DumpParams(eventUpdate.luaParams)); - /* - //Is it a static actor? If not look in the player's instance - Actor updateOwnerActor = Server.GetStaticActors(session.GetActor().currentEventOwner); - if (updateOwnerActor == null) - { - updateOwnerActor = Server.GetWorldManager().GetActorInWorld(session.GetActor().currentEventOwner); - - if (session.GetActor().currentDirector != null && session.GetActor().currentEventOwner == session.GetActor().currentDirector.actorId) - updateOwnerActor = session.GetActor().currentDirector; - - if (updateOwnerActor == null) + Program.Log.Debug("\n===Event START===\nCould not find actor 0x{0:X} for event started by caller: 0x{1:X}\nEvent Starter: {2}\nParams: {3}", eventStart.triggerActorID, eventStart.ownerActorID, eventStart.eventName, LuaUtils.DumpParams(eventStart.luaParams)); break; + } } - */ - session.GetActor().UpdateEvent(eventUpdate); + } - //LuaEngine.DoActorOnEventUpdated(session.GetActor(), updateOwnerActor, eventUpdate); - - break; - case 0x012F: - subpacket.DebugPrintSubPacket(); - ParameterDataRequestPacket paramRequest = new ParameterDataRequestPacket(subpacket.data); - if (paramRequest.paramName.Equals("charaWork/exp")) - session.GetActor().SendCharaExpInfo(); - break; - //Item Package Request - case 0x0131: - UpdateItemPackagePacket packageRequest = new UpdateItemPackagePacket(subpacket.data); - if (Server.GetWorldManager().GetActorInWorld(packageRequest.actorID) != null) - { - ((Character)Server.GetWorldManager().GetActorInWorld(packageRequest.actorID)).SendItemPackage(session.GetActor(), packageRequest.packageId); + session.GetActor().StartEvent(ownerActor, eventStart); + + Program.Log.Debug("\n===Event START===\nSource Actor: 0x{0:X}\nCaller Actor: 0x{1:X}\nVal1: 0x{2:X}\nVal2: 0x{3:X}\nEvent Starter: {4}\nParams: {5}", eventStart.triggerActorID, eventStart.ownerActorID, eventStart.serverCodes, eventStart.unknown, eventStart.eventName, LuaUtils.DumpParams(eventStart.luaParams)); + break; + //Unknown, happens at npc spawn and cutscene play???? + case 0x00CE: + subpacket.DebugPrintSubPacket(); + break; + //Countdown requested + case 0x00CF: + CountdownRequestPacket countdownPacket = new CountdownRequestPacket(subpacket.data); + session.GetActor().BroadcastCountdown(countdownPacket.countdownLength, countdownPacket.syncTime); + break; + //Event Result + case 0x012E: + subpacket.DebugPrintSubPacket(); + EventUpdatePacket eventUpdate = new EventUpdatePacket(subpacket.data); + Program.Log.Debug("\n===Event UPDATE===\nSource Actor: 0x{0:X}\nCaller Actor: 0x{1:X}\nVal1: 0x{2:X}\nVal2: 0x{3:X}\nStep: 0x{4:X}\nParams: {5}", eventUpdate.triggerActorID, eventUpdate.serverCodes, eventUpdate.unknown1, eventUpdate.unknown2, eventUpdate.eventType, LuaUtils.DumpParams(eventUpdate.luaParams)); + /* + //Is it a static actor? If not look in the player's instance + Actor updateOwnerActor = Server.GetStaticActors(session.GetActor().currentEventOwner); + if (updateOwnerActor == null) + { + updateOwnerActor = Server.GetWorldManager().GetActorInWorld(session.GetActor().currentEventOwner); + + if (session.GetActor().currentDirector != null && session.GetActor().currentEventOwner == session.GetActor().currentDirector.actorId) + updateOwnerActor = session.GetActor().currentDirector; + + if (updateOwnerActor == null) break; - } - if (session.GetActor().GetSpawnedRetainer() != null && session.GetActor().GetSpawnedRetainer().Id == packageRequest.actorID) - session.GetActor().GetSpawnedRetainer().SendItemPackage(session.GetActor(), packageRequest.packageId); + } + */ + session.GetActor().UpdateEvent(eventUpdate); + + //LuaEngine.DoActorOnEventUpdated(session.GetActor(), updateOwnerActor, eventUpdate); + + break; + case 0x012F: + subpacket.DebugPrintSubPacket(); + WorkSyncRequestPacket workSyncRequest = new WorkSyncRequestPacket(subpacket.data); + session.GetActor().OnWorkSyncRequest(workSyncRequest.propertyName, workSyncRequest.from, workSyncRequest.to); + break; + //Item Package Request + case 0x0131: + UpdateItemPackagePacket packageRequest = new UpdateItemPackagePacket(subpacket.data); + if (Server.GetWorldManager().GetActorInWorld(packageRequest.actorID) != null) + { + ((Character)Server.GetWorldManager().GetActorInWorld(packageRequest.actorID)).SendItemPackage(session.GetActor(), packageRequest.packageId); break; - //Group Created Confirm - case 0x0133: - GroupCreatedPacket groupCreated = new GroupCreatedPacket(subpacket.data); - Server.GetWorldManager().SendGroupInit(session, groupCreated.groupId); - break; - //Achievement Progress Request - case 0x0135: - AchievementProgressRequestPacket progressRequest = new AchievementProgressRequestPacket(subpacket.data); - session.QueuePacket(Database.GetAchievementProgress(session.GetActor(), progressRequest.achievementId)); - break; - /* RECRUITMENT */ - //Start Recruiting - case 0x01C3: - StartRecruitingRequestPacket recruitRequestPacket = new StartRecruitingRequestPacket(subpacket.data); - session.QueuePacket(StartRecruitingResponse.BuildPacket(session.id, true)); - break; - //End Recruiting - case 0x01C4: - session.QueuePacket(EndRecruitmentPacket.BuildPacket(session.id)); - break; - //Party Window Opened, Request State - case 0x01C5: - session.QueuePacket(RecruiterStatePacket.BuildPacket(session.id, false, false, 0)); - break; - //Search Recruiting - case 0x01C7: - RecruitmentSearchRequestPacket recruitSearchPacket = new RecruitmentSearchRequestPacket(subpacket.data); - break; - //Get Recruitment Details - case 0x01C8: - RecruitmentDetailsRequestPacket currentRecruitDetailsPacket = new RecruitmentDetailsRequestPacket(subpacket.data); - RecruitmentDetails details = new RecruitmentDetails(); - details.recruiterName = "Localhost Character"; - details.purposeId = 2; - details.locationId = 1; - details.subTaskId = 1; - details.comment = "This is a test details packet sent by the server. No implementation has been Created yet..."; - details.num[0] = 1; - session.QueuePacket(CurrentRecruitmentDetailsPacket.BuildPacket(session.id, details)); - break; - //Accepted Recruiting - case 0x01C6: - subpacket.DebugPrintSubPacket(); - break; - /* SOCIAL STUFF */ - case 0x01C9: - AddRemoveSocialPacket addBlackList = new AddRemoveSocialPacket(subpacket.data); - session.QueuePacket(BlacklistAddedPacket.BuildPacket(session.id, true, addBlackList.name)); - break; - case 0x01CA: - AddRemoveSocialPacket RemoveBlackList = new AddRemoveSocialPacket(subpacket.data); - session.QueuePacket(BlacklistRemovedPacket.BuildPacket(session.id, true, RemoveBlackList.name)); - break; - case 0x01CB: - int offset1 = 0; - session.QueuePacket(SendBlacklistPacket.BuildPacket(session.id, new String[] { "Test" }, ref offset1)); - break; - case 0x01CC: - AddRemoveSocialPacket addFriendList = new AddRemoveSocialPacket(subpacket.data); - session.QueuePacket(FriendlistAddedPacket.BuildPacket(session.id, true, (uint)addFriendList.name.GetHashCode(), true, addFriendList.name)); - break; - case 0x01CD: - AddRemoveSocialPacket RemoveFriendList = new AddRemoveSocialPacket(subpacket.data); - session.QueuePacket(FriendlistRemovedPacket.BuildPacket(session.id, true, RemoveFriendList.name)); - break; - case 0x01CE: - int offset2 = 0; - session.QueuePacket(SendFriendlistPacket.BuildPacket(session.id, new Tuple[] { new Tuple(01, "Test2") }, ref offset2)); - break; - case 0x01CF: - session.QueuePacket(FriendStatusPacket.BuildPacket(session.id, null)); - break; - /* SUPPORT DESK STUFF */ - //Request for FAQ/Info List - case 0x01D0: - FaqListRequestPacket faqRequest = new FaqListRequestPacket(subpacket.data); - session.QueuePacket(FaqListResponsePacket.BuildPacket(session.id, new string[] { "Testing FAQ1", "Coded style!" })); - break; - //Request for body of a faq/info selection - case 0x01D1: - FaqBodyRequestPacket faqBodyRequest = new FaqBodyRequestPacket(subpacket.data); - session.QueuePacket(FaqBodyResponsePacket.BuildPacket(session.id, "HERE IS A GIANT BODY. Nothing else to say!")); - break; - //Request issue list - case 0x01D2: - GMTicketIssuesRequestPacket issuesRequest = new GMTicketIssuesRequestPacket(subpacket.data); - session.QueuePacket(IssueListResponsePacket.BuildPacket(session.id, new string[] { "Test1", "Test2", "Test3", "Test4", "Test5" })); - break; - //Request if GM ticket exists - case 0x01D3: - session.QueuePacket(StartGMTicketPacket.BuildPacket(session.id, false)); - break; - //Request for GM response message - case 0x01D4: - session.QueuePacket(GMTicketPacket.BuildPacket(session.id, "This is a GM Ticket Title", "This is a GM Ticket Body.")); - break; - //GM Ticket Sent - case 0x01D5: - GMSupportTicketPacket gmTicket = new GMSupportTicketPacket(subpacket.data); - Program.Log.Info("Got GM Ticket: \n" + gmTicket.ticketTitle + "\n" + gmTicket.ticketBody); - session.QueuePacket(GMTicketSentResponsePacket.BuildPacket(session.id, true)); - break; - //Request to end ticket - case 0x01D6: - session.QueuePacket(EndGMTicketPacket.BuildPacket(session.id)); - break; - default: - Program.Log.Debug("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode); - subpacket.DebugPrintSubPacket(); - break; - } - - } + } + if (session.GetActor().GetSpawnedRetainer() != null && session.GetActor().GetSpawnedRetainer().Id == packageRequest.actorID) + session.GetActor().GetSpawnedRetainer().SendItemPackage(session.GetActor(), packageRequest.packageId); + break; + //Group Created Confirm + case 0x0133: + GroupCreatedPacket groupCreated = new GroupCreatedPacket(subpacket.data); + Server.GetWorldManager().SendGroupInit(session, groupCreated.groupId); + break; + //Achievement Progress Request + case 0x0135: + AchievementProgressRequestPacket progressRequest = new AchievementProgressRequestPacket(subpacket.data); + session.QueuePacket(Database.GetAchievementProgress(session.GetActor(), progressRequest.achievementId)); + break; + /* RECRUITMENT */ + //Start Recruiting + case 0x01C3: + StartRecruitingRequestPacket recruitRequestPacket = new StartRecruitingRequestPacket(subpacket.data); + session.QueuePacket(StartRecruitingResponse.BuildPacket(session.id, true)); + break; + //End Recruiting + case 0x01C4: + session.QueuePacket(EndRecruitmentPacket.BuildPacket(session.id)); + break; + //Party Window Opened, Request State + case 0x01C5: + session.QueuePacket(RecruiterStatePacket.BuildPacket(session.id, false, false, 0)); + break; + //Search Recruiting + case 0x01C7: + RecruitmentSearchRequestPacket recruitSearchPacket = new RecruitmentSearchRequestPacket(subpacket.data); + break; + //Get Recruitment Details + case 0x01C8: + RecruitmentDetailsRequestPacket currentRecruitDetailsPacket = new RecruitmentDetailsRequestPacket(subpacket.data); + RecruitmentDetails details = new RecruitmentDetails(); + details.recruiterName = "Localhost Character"; + details.purposeId = 2; + details.locationId = 1; + details.subTaskId = 1; + details.comment = "This is a test details packet sent by the server. No implementation has been Created yet..."; + details.num[0] = 1; + session.QueuePacket(CurrentRecruitmentDetailsPacket.BuildPacket(session.id, details)); + break; + //Accepted Recruiting + case 0x01C6: + subpacket.DebugPrintSubPacket(); + break; + /* SOCIAL STUFF */ + case 0x01C9: + AddRemoveSocialPacket addBlackList = new AddRemoveSocialPacket(subpacket.data); + session.QueuePacket(BlacklistAddedPacket.BuildPacket(session.id, true, addBlackList.name)); + break; + case 0x01CA: + AddRemoveSocialPacket RemoveBlackList = new AddRemoveSocialPacket(subpacket.data); + session.QueuePacket(BlacklistRemovedPacket.BuildPacket(session.id, true, RemoveBlackList.name)); + break; + case 0x01CB: + int offset1 = 0; + session.QueuePacket(SendBlacklistPacket.BuildPacket(session.id, new String[] { "Test" }, ref offset1)); + break; + case 0x01CC: + AddRemoveSocialPacket addFriendList = new AddRemoveSocialPacket(subpacket.data); + session.QueuePacket(FriendlistAddedPacket.BuildPacket(session.id, true, (uint)addFriendList.name.GetHashCode(), true, addFriendList.name)); + break; + case 0x01CD: + AddRemoveSocialPacket RemoveFriendList = new AddRemoveSocialPacket(subpacket.data); + session.QueuePacket(FriendlistRemovedPacket.BuildPacket(session.id, true, RemoveFriendList.name)); + break; + case 0x01CE: + int offset2 = 0; + session.QueuePacket(SendFriendlistPacket.BuildPacket(session.id, new Tuple[] { new Tuple(01, "Test2") }, ref offset2)); + break; + case 0x01CF: + session.QueuePacket(FriendStatusPacket.BuildPacket(session.id, null)); + break; + /* SUPPORT DESK STUFF */ + //Request for FAQ/Info List + case 0x01D0: + FaqListRequestPacket faqRequest = new FaqListRequestPacket(subpacket.data); + session.QueuePacket(FaqListResponsePacket.BuildPacket(session.id, new string[] { "Testing FAQ1", "Coded style!" })); + break; + //Request for body of a faq/info selection + case 0x01D1: + FaqBodyRequestPacket faqBodyRequest = new FaqBodyRequestPacket(subpacket.data); + session.QueuePacket(FaqBodyResponsePacket.BuildPacket(session.id, "HERE IS A GIANT BODY. Nothing else to say!")); + break; + //Request issue list + case 0x01D2: + GMTicketIssuesRequestPacket issuesRequest = new GMTicketIssuesRequestPacket(subpacket.data); + session.QueuePacket(IssueListResponsePacket.BuildPacket(session.id, new string[] { "Test1", "Test2", "Test3", "Test4", "Test5" })); + break; + //Request if GM ticket exists + case 0x01D3: + session.QueuePacket(StartGMTicketPacket.BuildPacket(session.id, false)); + break; + //Request for GM response message + case 0x01D4: + session.QueuePacket(GMTicketPacket.BuildPacket(session.id, "This is a GM Ticket Title", "This is a GM Ticket Body.")); + break; + //GM Ticket Sent + case 0x01D5: + GMSupportTicketPacket gmTicket = new GMSupportTicketPacket(subpacket.data); + Program.Log.Info("Got GM Ticket: \n" + gmTicket.ticketTitle + "\n" + gmTicket.ticketBody); + session.QueuePacket(GMTicketSentResponsePacket.BuildPacket(session.id, true)); + break; + //Request to end ticket + case 0x01D6: + session.QueuePacket(EndGMTicketPacket.BuildPacket(session.id)); + break; + default: + Program.Log.Debug("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode); + subpacket.DebugPrintSubPacket(); + break; + } + + } } } diff --git a/Map Server/Packets/Receive/ParameterDataRequestPacket.cs b/Map Server/Packets/Receive/WorkSyncRequestPacket.cs similarity index 65% rename from Map Server/Packets/Receive/ParameterDataRequestPacket.cs rename to Map Server/Packets/Receive/WorkSyncRequestPacket.cs index e67308ed..08d57532 100644 --- a/Map Server/Packets/Receive/ParameterDataRequestPacket.cs +++ b/Map Server/Packets/Receive/WorkSyncRequestPacket.cs @@ -26,7 +26,7 @@ using System.Text; namespace Meteor.Map.packets.receive { - class ParameterDataRequestPacket + class WorkSyncRequestPacket { public const ushort OPCODE = 0x012F; public const uint PACKET_SIZE = 0x48; @@ -34,9 +34,11 @@ namespace Meteor.Map.packets.receive public bool invalidPacket = false; public uint actorID; - public string paramName; + public string propertyName; + public ushort from, to; + public bool requestingBitfield = false; - public ParameterDataRequestPacket(byte[] data) + public WorkSyncRequestPacket(byte[] data) { using (MemoryStream mem = new MemoryStream(data)) { @@ -44,13 +46,22 @@ namespace Meteor.Map.packets.receive { try{ actorID = binReader.ReadUInt32(); - List strList = new List(); - byte curByte; - while ((curByte = binReader.ReadByte()) != 0 && strList.Count<=0x20) + if (binReader.PeekChar() == 9) { - strList.Add(curByte); + binReader.ReadByte(); + from = binReader.ReadUInt16(); + to = binReader.ReadUInt16(); } - paramName = Encoding.ASCII.GetString(strList.ToArray()); + + byte currentByte; + int size = 0; + long strPos = binReader.BaseStream.Position; + while ((currentByte = binReader.ReadByte()) != 0 && size <= 0x20) + size++; + + binReader.BaseStream.Seek(strPos, SeekOrigin.Begin); + byte[] str = binReader.ReadBytes(size); + propertyName = Encoding.ASCII.GetString(str); } catch (Exception){ invalidPacket = true; diff --git a/Map Server/Packets/Send/Actor/SetActorPropetyPacket.cs b/Map Server/Packets/Send/Actor/SetActorPropetyPacket.cs index 64fa5513..f5d1d969 100644 --- a/Map Server/Packets/Send/Actor/SetActorPropetyPacket.cs +++ b/Map Server/Packets/Send/Actor/SetActorPropetyPacket.cs @@ -44,6 +44,10 @@ namespace Meteor.Map.packets.send.actor string currentTarget; + bool isBitfield = false; + ushort from; + ushort to; + private MemoryStream mem; private BinaryWriter binWriter; @@ -54,6 +58,16 @@ namespace Meteor.Map.packets.send.actor binWriter.Seek(1, SeekOrigin.Begin); currentTarget = startingTarget; } + public SetActorPropetyPacket(ushort from, ushort to, string startingTarget) + { + mem = new MemoryStream(data); + binWriter = new BinaryWriter(mem); + binWriter.Seek(1, SeekOrigin.Begin); + currentTarget = startingTarget; + this.from = from; + this.to = to; + isBitfield = true; + } public void CloseStreams() { @@ -100,6 +114,19 @@ namespace Meteor.Map.packets.send.actor return true; } + public bool AddBitfield(uint id, byte[] data) + { + if (runningByteTotal + 5 + data.Length + 1 + (1 + 5 + Encoding.ASCII.GetByteCount(currentTarget)) > MAXBYTES) + return false; + + binWriter.Write((byte) (data.Length)); + binWriter.Write((UInt32)id); + binWriter.Write(data); + runningByteTotal += (ushort)(5 + data.Length); + + return true; + } + public bool AddBuffer(uint id, byte[] buffer) { if (runningByteTotal + 5 + buffer.Length + (1 + Encoding.ASCII.GetByteCount(currentTarget)) > MAXBYTES) @@ -208,10 +235,22 @@ namespace Meteor.Map.packets.send.actor public void SetTarget(string target) { currentTarget = target; + isBitfield = false; } public void AddTarget() { + if (isBitfield) + { + binWriter.Write((byte)(isMore ? 0x60 + currentTarget.Length + 5 : 0x82 + currentTarget.Length + 5)); + binWriter.Write((byte)9); + binWriter.Write(from); + binWriter.Write(to); + binWriter.Write(Encoding.ASCII.GetBytes(currentTarget)); + runningByteTotal += (ushort)(6 + Encoding.ASCII.GetByteCount(currentTarget)); + return; + } + if (isArrayMode) binWriter.Write((byte)(0xA4 + currentTarget.Length)); else @@ -236,6 +275,7 @@ namespace Meteor.Map.packets.send.actor CloseStreams(); SubPacket packet = new SubPacket(OPCODE, sourceActorId, data); + packet.DebugPrintSubPacket(); return packet; } diff --git a/Map Server/Program.cs b/Map Server/Program.cs index 23c524b9..5e7f28d7 100644 --- a/Map Server/Program.cs +++ b/Map Server/Program.cs @@ -21,6 +21,7 @@ along with Project Meteor Server. If not, see . using System; using System.Diagnostics; +using Meteor.Common; using MySql.Data.MySqlClient; using NLog; @@ -35,7 +36,7 @@ namespace Meteor.Map public static DateTime Tick = DateTime.Now; static void Main(string[] args) - { + { // set up logging Log = LogManager.GetCurrentClassLogger(); #if DEBUG From c677479a03e2562326dd1a77b56f8c60229bd73d Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Sun, 20 Feb 2022 12:06:48 -0500 Subject: [PATCH 6/9] Added completed quest sendback. --- Common Class Lib/Bitstream.cs | 8 +++++++- Common Class Lib/Utils.cs | 2 +- Data/scripts/commands/gm/quest.lua | 16 +++++++++------- Map Server/Actors/Chara/Player/Player.cs | 1 - Map Server/Actors/Quest/Quest.cs | 8 +++----- Map Server/Actors/Quest/QuestData.cs | 24 +++++++++++++++--------- 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/Common Class Lib/Bitstream.cs b/Common Class Lib/Bitstream.cs index 1e3df17c..7e225eef 100644 --- a/Common Class Lib/Bitstream.cs +++ b/Common Class Lib/Bitstream.cs @@ -176,11 +176,17 @@ namespace Meteor.Common srcShiftIndx = 0; destByteIndx++; i += 8; + continue; } - bool val = (Data[srcByteIndx] & (1 << srcShiftIndx)) != 0; + bool val = (Data[srcByteIndx] & (1 << srcShiftIndx++)) != 0; curByte |= (byte)((val ? 1 : 0) << destShiftIndx++); + if (srcShiftIndx == 8) + { + srcShiftIndx = 0; + srcByteIndx++; + } if (destShiftIndx == 8) { toReturn[destByteIndx++] = curByte; diff --git a/Common Class Lib/Utils.cs b/Common Class Lib/Utils.cs index 9556e8c5..b8d9a214 100644 --- a/Common Class Lib/Utils.cs +++ b/Common Class Lib/Utils.cs @@ -245,7 +245,7 @@ namespace Meteor.Common { for (var bitCount = 0; bitCount < 8; bitCount++) { - if (i + bitCount >= array.Length) + if (i + bitCount >= array.Length - 1) break; data[dataCounter] = (byte)(((array[i + bitCount] ? 1 : 0) << 7 - bitCount) | data[dataCounter]); } diff --git a/Data/scripts/commands/gm/quest.lua b/Data/scripts/commands/gm/quest.lua index 29946fdf..3f227706 100644 --- a/Data/scripts/commands/gm/quest.lua +++ b/Data/scripts/commands/gm/quest.lua @@ -66,7 +66,7 @@ function onTrigger(player, argc, command, var1, var2, var3) local flagStr = ""; for i=0,31,1 do - if (quest:GetFlag(i)) then + if (quest:GetData():GetFlag(i)) then flagStr = flagStr .. "O"; else flagStr = flagStr .. "X"; @@ -76,10 +76,12 @@ function onTrigger(player, argc, command, var1, var2, var3) end end + local data = quest:GetData(); + message = string.format("\nInfo for quest %s [%d]\n", quest.Name, quest:GetQuestId()); message = message .. string.format("Current Sequence: %d\n", quest:getSequence()); message = message .. string.format("Flags: \n%s\n", flagStr) - message = message .. string.format("Counters: %d,%d,%d,%d", quest:getCounter(0), quest:getCounter(1), quest:getCounter(2), quest:getCounter(3)); + message = message .. string.format("Counters: %d,%d,%d,%d", data:getCounter(0), data:getCounter(1), data:getCounter(2), data:getCounter(3)); else message = ("Quest not active: "..var1); end @@ -118,7 +120,7 @@ function onTrigger(player, argc, command, var1, var2, var3) boolvar = false; elseif var3 == "flip" or var3 == "toggle" then if player:HasQuest(questvar) == true then - boolvar = not player:GetQuest(questvar):GetFlag(flagvar); + boolvar = not player:GetQuest(questvar):GetData():GetFlag(flagvar); end else message = ("error: flag: boolean not recognized"); @@ -126,13 +128,13 @@ function onTrigger(player, argc, command, var1, var2, var3) return; end - var4 = player:GetQuest(questvar):GetFlag(flagvar); + var4 = player:GetQuest(questvar):GetData():GetFlag(flagvar); if var4 ~= boolvar then if (boolvar == true) then - player:GetQuest(questvar):SetFlag(flagvar); + player:GetQuest(questvar):GetData():SetFlag(flagvar); else - player:GetQuest(questvar):ClearFlag(flagvar); + player:GetQuest(questvar):GetData():ClearFlag(flagvar); end player:GetQuest(questvar):UpdateENPCs(); player:GetQuest(questvar):SaveData(); @@ -152,7 +154,7 @@ function onTrigger(player, argc, command, var1, var2, var3) questvar = tonumber(var1); index = (tonumber(var2)); - player:GetQuest(questvar):SetCounter(index, tonumber(var3)); + player:GetQuest(questvar):GetData():SetCounter(index, tonumber(var3)); player:GetQuest(questvar):UpdateENPCs(); player:GetQuest(questvar):SaveData(); message = ("changing counter "..tonumber(var2).." to "..var3); diff --git a/Map Server/Actors/Chara/Player/Player.cs b/Map Server/Actors/Chara/Player/Player.cs index e67e1379..814c3607 100644 --- a/Map Server/Actors/Chara/Player/Player.cs +++ b/Map Server/Actors/Chara/Player/Player.cs @@ -1170,7 +1170,6 @@ namespace Meteor.Map.Actors private void SendCompletedQuests(ushort from, ushort to) { Bitstream completed = questStateManager.GetCompletedBitstream(); - completed.SetAll(true); byte[] data = completed.GetSlice(from, to); SetActorPropetyPacket completedQuestWorkUpdate = new SetActorPropetyPacket(from, to, "playerWork/journal"); diff --git a/Map Server/Actors/Quest/Quest.cs b/Map Server/Actors/Quest/Quest.cs index ee594f85..387f7572 100644 --- a/Map Server/Actors/Quest/Quest.cs +++ b/Map Server/Actors/Quest/Quest.cs @@ -34,7 +34,6 @@ namespace Meteor.Map.Actors.QuestNS private ushort currentSequence; private QuestState questState = null; private QuestData data = null; - private bool dataDirty = false; // Creates a Static Quest for the StaticActors list. public Quest(uint actorID, string className, string classPath) @@ -124,11 +123,11 @@ namespace Meteor.Map.Actors.QuestNS public void UpdateENPCs() { - if (dataDirty) + if (data.Dirty) { if (questState != null) questState.UpdateState(); - dataDirty = false; + data.ClearDirty(); } } @@ -199,8 +198,7 @@ namespace Meteor.Map.Actors.QuestNS if (currentSequence != SEQ_NOT_STARTED) owner.SendGameMessage(Server.GetWorldManager().GetActor(), 25116, 0x20, (object)GetQuestId()); - currentSequence = sequence; - dataDirty = true; + currentSequence = sequence; questState.UpdateState(); } diff --git a/Map Server/Actors/Quest/QuestData.cs b/Map Server/Actors/Quest/QuestData.cs index 4707a03a..02d6800b 100644 --- a/Map Server/Actors/Quest/QuestData.cs +++ b/Map Server/Actors/Quest/QuestData.cs @@ -16,7 +16,8 @@ namespace Meteor.Map.Actors.QuestNS private ushort counter2; private ushort counter3; private ushort counter4; - private bool dataDirty = false; + + public bool Dirty { get; private set; } = false; public QuestData(Player owner, Quest parent, uint flags, ushort counter1, ushort counter2, ushort counter3, ushort counter4) { @@ -46,7 +47,7 @@ namespace Meteor.Map.Actors.QuestNS if (index >= 0 && index < 32) { flags |= (uint)(1 << index); - dataDirty = true; + Dirty = true; } } @@ -55,13 +56,13 @@ namespace Meteor.Map.Actors.QuestNS if (index >= 0 && index < 32) { flags &= (uint)~(1 << index); - dataDirty = true; + Dirty = true; } } public ushort IncCounter(int num) { - dataDirty = true; + Dirty = true; switch (num) { @@ -79,13 +80,13 @@ namespace Meteor.Map.Actors.QuestNS return counter4; } - dataDirty = false; + Dirty = false; return 0; } public ushort DecCounter(int num) { - dataDirty = true; + Dirty = true; switch (num) { @@ -103,13 +104,13 @@ namespace Meteor.Map.Actors.QuestNS return counter4; } - dataDirty = false; + Dirty = false; return 0; } public void SetCounter(int num, ushort value) { - dataDirty = true; + Dirty = true; switch (num) { @@ -127,7 +128,7 @@ namespace Meteor.Map.Actors.QuestNS return; } - dataDirty = false; + Dirty = false; } public bool GetFlag(int index) @@ -159,6 +160,11 @@ namespace Meteor.Map.Actors.QuestNS return 0; } + public void ClearDirty() + { + Dirty = false; + } + public void Save() { Database.UpdateQuest(owner, parent); From 25f1b0fd95b8343b3a687b837100a81156200640 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Sun, 20 Feb 2022 12:39:34 -0500 Subject: [PATCH 7/9] Added debug script for setting quest completion --- Data/scripts/commands/gm/completedQuest.lua | 58 ++++++++++++++++++++ Map Server/Actors/Chara/Player/Player.cs | 22 +++++++- Map Server/Actors/Quest/QuestStateManager.cs | 18 +++++- 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 Data/scripts/commands/gm/completedQuest.lua diff --git a/Data/scripts/commands/gm/completedQuest.lua b/Data/scripts/commands/gm/completedQuest.lua new file mode 100644 index 00000000..dd11b49b --- /dev/null +++ b/Data/scripts/commands/gm/completedQuest.lua @@ -0,0 +1,58 @@ +require("global"); + +properties = { + permissions = 0, + parameters = "dd", + description = +[[ +Sets if a quest is completed. +!completedQuest true/false +]], +} + +function onTrigger(player, argc, questId, flag) + + print("HEY"); + local messageID = MESSAGE_TYPE_SYSTEM_ERROR; + local sender = "[completedQuest] "; + local message = "Error"; + + if (argc < 1) then + return; + end + + local questId = tonumber(questId); + local flag = flag or nil; + + -- Fail if not valid questId + if (questId < 110001 or questId > 110001 + 2048) then + player:SendMessage(messageID, sender, "Invalid questId entered"); + return; + end + + -- Getting + if (arc == 1) then + player:SendMessage(messageID, sender, string.format("Quest %d completion is set to: %s", questId, tostring(player:IsQuestCompleted(questId)))); + return; + -- Setting + else + -- Fail if not valid flag + if (not flag == nil) then + player:SendMessage(messageID, sender, "Invalid flag entered"); + else + local boolFlag = false; + + if (flag == "true" or flag == "1" or flag == "on" or flag == "O") then + boolFlag = true; + elseif (flag == "false" or flag == "0" or flag == "off" or flag == "X") then + boolFlag = false; + elseif flag == "flip" or flag == "toggle" then + boolFlag = not player:IsQuestCompleted(questId); + end + + player:SetQuestComplete(questId, boolFlag); + player:SendMessage(messageID, sender, string.format("Quest %d completion set to: %s", questId, tostring(player:IsQuestCompleted(questId)))); + return; + end + end +end \ No newline at end of file diff --git a/Map Server/Actors/Chara/Player/Player.cs b/Map Server/Actors/Chara/Player/Player.cs index 814c3607..143cb108 100644 --- a/Map Server/Actors/Chara/Player/Player.cs +++ b/Map Server/Actors/Chara/Player/Player.cs @@ -1169,8 +1169,7 @@ namespace Meteor.Map.Actors private void SendCompletedQuests(ushort from, ushort to) { - Bitstream completed = questStateManager.GetCompletedBitstream(); - byte[] data = completed.GetSlice(from, to); + byte[] data = questStateManager.GetCompletionSliceBytes(from, to); SetActorPropetyPacket completedQuestWorkUpdate = new SetActorPropetyPacket(from, to, "playerWork/journal"); completedQuestWorkUpdate.AddBitfield(Utils.MurmurHash2("playerWork.questScenarioComplete", 0), data); @@ -1661,6 +1660,25 @@ namespace Meteor.Map.Actors return false; } + public bool IsQuestCompleted(uint id) + { + return questStateManager.IsQuestComplete(id); + } + + public void SetQuestComplete(uint id, bool flag) + { + if (flag) + { + Quest currentQuest = GetQuest(id); + if (currentQuest != null) + { + CompleteQuest(currentQuest); + return; + } + } + questStateManager.ForceQuestCompleteFlag(id, flag); + } + public Quest GetQuest(uint id) { for (int i = 0; i < questScenario.Length; i++) diff --git a/Map Server/Actors/Quest/QuestStateManager.cs b/Map Server/Actors/Quest/QuestStateManager.cs index 1bc0bebd..3bc6c0ec 100644 --- a/Map Server/Actors/Quest/QuestStateManager.cs +++ b/Map Server/Actors/Quest/QuestStateManager.cs @@ -156,9 +156,23 @@ namespace Meteor.Map.Actors.QuestNS return ActiveQuests.FindAll(quest => quest.IsQuestENPC(player, npc)).ToArray(); } - public Bitstream GetCompletedBitstream() + public byte[] GetCompletionSliceBytes(ushort from, ushort to) { - return CompletedQuestsBitfield; + return CompletedQuestsBitfield.GetSlice(from, to); + } + + public bool IsQuestComplete(uint questId) + { + return CompletedQuestsBitfield.Get(questId - SCENARIO_START); + } + + public void ForceQuestCompleteFlag(uint questId, bool flag) + { + if (flag) + CompletedQuestsBitfield.Set(questId - SCENARIO_START); + else + CompletedQuestsBitfield.Clear(questId - SCENARIO_START); + ComputeAvailable(); } } } From 08e7a9cfd40f423fc5ede9ac393a48e3fc4ffc56 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Sun, 20 Feb 2022 12:41:21 -0500 Subject: [PATCH 8/9] Added sql schema change to completed quests --- Data/sql/characters_quest_completed.sql | 58 ++++++++----------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/Data/sql/characters_quest_completed.sql b/Data/sql/characters_quest_completed.sql index daaaa7cd..d009f1ab 100644 --- a/Data/sql/characters_quest_completed.sql +++ b/Data/sql/characters_quest_completed.sql @@ -1,51 +1,29 @@ --- MySQL dump 10.13 Distrib 5.7.10, for Win64 (x86_64) --- --- Host: localhost Database: ffxiv_database --- ------------------------------------------------------ --- Server version 5.7.10-log +-- -------------------------------------------------------- +-- Host: 127.0.0.1 +-- Server version: 5.6.17 - MySQL Community Server (GPL) +-- Server OS: Win64 +-- HeidiSQL Version: 10.1.0.5464 +-- -------------------------------------------------------- /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!50503 SET NAMES utf8mb4 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; --- --- Table structure for table `characters_quest_completed` --- -DROP TABLE IF EXISTS `characters_quest_completed`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `characters_quest_completed` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, +-- Dumping database structure for ffxiv_server +CREATE DATABASE IF NOT EXISTS `ffxiv_server` /*!40100 DEFAULT CHARACTER SET latin1 */; +USE `ffxiv_server`; + +-- Dumping structure for table ffxiv_server.characters_quest_completed +CREATE TABLE IF NOT EXISTS `characters_quest_completed` ( `characterId` int(10) unsigned NOT NULL, - `questId` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`) + `completedQuests` varbinary(2048) DEFAULT NULL, + PRIMARY KEY (`characterId`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; --- --- Dumping data for table `characters_quest_completed` --- - -LOCK TABLES `characters_quest_completed` WRITE; -/*!40000 ALTER TABLE `characters_quest_completed` DISABLE KEYS */; -/*!40000 ALTER TABLE `characters_quest_completed` ENABLE KEYS */; -UNLOCK TABLES; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +-- Data exporting was unselected. +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2016-06-07 22:54:47 From c240096eb8fbfb75e29dd902b7138eec16c127f0 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Sun, 20 Feb 2022 12:42:29 -0500 Subject: [PATCH 9/9] Added gamedata quest table. --- Data/sql/gamedata_quests.sql | 560 +++++++++++++++++++++++++++++++++++ 1 file changed, 560 insertions(+) create mode 100644 Data/sql/gamedata_quests.sql diff --git a/Data/sql/gamedata_quests.sql b/Data/sql/gamedata_quests.sql new file mode 100644 index 00000000..db2cd21f --- /dev/null +++ b/Data/sql/gamedata_quests.sql @@ -0,0 +1,560 @@ +-- -------------------------------------------------------- +-- Host: 127.0.0.1 +-- Server version: 5.6.17 - MySQL Community Server (GPL) +-- Server OS: Win64 +-- HeidiSQL Version: 10.1.0.5464 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; + + +-- Dumping database structure for ffxiv_server +CREATE DATABASE IF NOT EXISTS `ffxiv_server` /*!40100 DEFAULT CHARACTER SET latin1 */; +USE `ffxiv_server`; + +-- Dumping structure for table ffxiv_server.gamedata_quests +DROP TABLE IF EXISTS `gamedata_quests`; +CREATE TABLE IF NOT EXISTS `gamedata_quests` ( + `id` int(11) unsigned NOT NULL, + `questName` varchar(50) NOT NULL, + `className` varchar(10) NOT NULL, + `prerequisite` int(11) unsigned NOT NULL, + `minLevel` smallint(5) unsigned NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- Dumping data for table ffxiv_server.gamedata_quests: ~524 rows (approximately) +/*!40000 ALTER TABLE `gamedata_quests` DISABLE KEYS */; +REPLACE INTO `gamedata_quests` (`id`, `questName`, `className`, `prerequisite`, `minLevel`) VALUES + (110001, 'Shapeless Melody', 'Man0l0', 0, 1), + (110002, 'Treasures of the Main', 'Man0l1', 0, 1), + (110003, 'Legends Adrift', 'Man1l0', 0, 8), + (110004, 'Never the Twain Shall Meet', 'Man2l0', 0, 13), + (110005, 'Sundered Skies', 'Man0g0', 0, 1), + (110006, 'Souls Gone Wild', 'Man0g1', 0, 1), + (110007, 'Whispers in the Wood', 'Man1g0', 0, 8), + (110008, 'Beckon of the Elementals', 'Man2g0', 0, 13), + (110009, 'Flowers for All', 'Man0u0', 0, 1), + (110010, 'Court in the Sands', 'Man0u1', 0, 1), + (110011, 'Golden Sacrifices', 'Man1u0', 0, 8), + (110012, 'Calamity Cometh', 'Man2u0', 0, 13), + (110013, 'Fade to White', 'Man200', 0, 18), + (110014, 'Together We Stand', 'Man206', 0, 22), + (110015, 'Toll of the Warden', 'Man300', 0, 30), + (110016, 'Forever Taken', 'Man304', 0, 34), + (110017, 'Lord Errant', 'Man308', 0, 38), + (110018, 'Of Men They Sing', 'Man402', 0, 42), + (110019, 'Futures Perfect', 'Man406', 0, 46), + (110020, '[en]', 'Man502', 0, 52), + (110021, '[en]', 'Man504', 0, 54), + (110060, 'The House Always Wins', 'Pgl200', 0, 20), + (110061, 'Here There Be Pirates', 'Pgl300', 0, 30), + (110062, 'Two Sides to Every Chip', 'Pgl306', 0, 36), + (110063, '[en]', 'Pgl400', 0, 40), + (110080, 'All Bark and No Bite', 'Gla200', 0, 20), + (110081, 'Unalienable Rights', 'Gla300', 0, 30), + (110082, 'Thrill of the Fight', 'Gla306', 0, 36), + (110083, '[en]', 'Gla400', 0, 40), + (110100, 'Bloody Baptism', 'Exc200', 0, 20), + (110101, 'Two-man Crew', 'Exc300', 0, 30), + (110102, 'Captain\'s Orders', 'Exc306', 0, 36), + (110103, '[en]', 'Exc400', 0, 40), + (110104, '[en]', 'Exc500', 0, 50), + (110105, '[en]', 'Exc506', 0, 56), + (110140, 'Getting Started', 'Trl0l1', 0, 20), + (110141, 'Getting Started', 'Trl0g1', 0, 30), + (110142, '[en]', 'Trl0u1', 0, 36), + (110143, '[en]', 'Trl0l2', 0, 20), + (110144, 'Selecting a Different Path Companion', 'Trl0l3', 0, 50), + (110145, '[en]', 'Trl0l4', 0, 56), + (110160, 'Filling the Quiver', 'Arc200', 0, 20), + (110161, 'The Foreboding Forest', 'Arc300', 110160, 30), + (110162, 'There Can Be Only One', 'Arc306', 110161, 36), + (110163, '[en]', 'Arc400', 0, 40), + (110164, '[en]', 'Arc500', 0, 50), + (110165, '[en]', 'Arc506', 0, 56), + (110180, 'A Wailing Welcome', 'Lnc200', 0, 20), + (110181, 'Culture Shock', 'Lnc300', 110180, 30), + (110182, 'Necessary Evils', 'Lnc306', 110181, 36), + (110183, '[en]', 'Lnc400', 0, 40), + (110184, '[en]', 'Lnc500', 0, 50), + (110185, '[en]', 'Lnc506', 0, 56), + (110240, 'The Big Payback', 'Thm200', 0, 20), + (110241, 'Revelry in Rivalry', 'Thm300', 0, 30), + (110242, 'Law and the Order', 'Thm306', 0, 36), + (110243, '[en]', 'Thm400', 0, 40), + (110260, 'Dendrological Duties', 'Cnj200', 0, 20), + (110261, 'Good Knight, Sweet Dreams', 'Cnj300', 110260, 30), + (110262, 'The Call of Nature', 'Cnj306', 110261, 36), + (110263, '[en]', 'Cnj400', 0, 40), + (110264, '[en]', 'Cnj500', 0, 50), + (110265, '[en]', 'Cnj506', 0, 56), + (110280, '[en]', 'Acn200', 0, 20), + (110281, '[en]', 'Acn300', 0, 30), + (110282, '[en]', 'Acn306', 0, 36), + (110283, '[en]', 'Acn400', 0, 40), + (110284, '[en]', 'Acn500', 0, 50), + (110285, '[en]', 'Acn506', 0, 56), + (110300, 'The Mouths of Babes', 'Wdk200', 0, 20), + (110301, 'Hide and Seek Shenanigans', 'Wdk300', 110300, 30), + (110302, 'Spanning the Spectrum', 'Wdk306', 110301, 36), + (110303, '[en]', 'Wdk400', 0, 40), + (110304, '[en]', 'Wdk500', 0, 50), + (110305, '[en]', 'Wdk506', 0, 56), + (110320, 'An Ear for Quality', 'Bsm200', 0, 20), + (110321, 'Song of the Sirens', 'Bsm300', 0, 30), + (110322, 'The Sound of Silence', 'Bsm306', 0, 36), + (110323, '[en]', 'Bsm400', 0, 40), + (110324, '[en]', 'Bsm500', 0, 50), + (110325, '[en]', 'Bsm506', 0, 56), + (110360, 'She Walks in Beauty', 'Gld200', 0, 20), + (110361, 'F\'lhaminn\'s Flower', 'Gld300', 0, 30), + (110362, 'Struck Through the Heart', 'Gld306', 0, 36), + (110363, '[en]', 'Gld400', 0, 40), + (110364, '[en]', 'Gld500', 0, 50), + (110365, '[en]', 'Gld506', 0, 56), + (110380, 'The Silent Partners', 'Tan200', 0, 20), + (110381, 'Design Imposters', 'Tan300', 110380, 30), + (110382, 'Head of the Class', 'Tan306', 110381, 36), + (110383, '[en]', 'Tan400', 0, 40), + (110384, '[en]', 'Tan500', 0, 50), + (110385, '[en]', 'Tan506', 0, 56), + (110400, 'Hoodwinked', 'Wvr200', 0, 20), + (110401, 'Dance the Night Away', 'Wvr300', 0, 30), + (110402, 'A Fruitful Murder', 'Wvr306', 0, 36), + (110403, '[en]', 'Wvr400', 0, 40), + (110404, '[en]', 'Wvr500', 0, 50), + (110405, '[en]', 'Wvr506', 0, 56), + (110420, 'Sleep, Cousin of Death', 'Alc200', 0, 20), + (110421, 'The Boy and the Dragon Gay', 'Alc300', 110420, 30), + (110422, 'Dream On, Dream Away', 'Alc306', 110421, 36), + (110423, '[en]', 'Alc400', 0, 40), + (110424, '[en]', 'Alc500', 0, 50), + (110425, '[en]', 'Alc506', 0, 56), + (110440, 'Showdown', 'Cul200', 0, 20), + (110441, 'Mystery of the Gastronome Gone Home', 'Cul300', 0, 30), + (110442, 'Something in the Soup', 'Cul306', 0, 36), + (110443, '[en]', 'Cul400', 0, 40), + (110444, '[en]', 'Cul500', 0, 50), + (110445, '[en]', 'Cul506', 0, 56), + (110460, 'A Piece of History', 'Min200', 0, 20), + (110461, 'Little Saboteurs', 'Min300', 0, 30), + (110462, 'Runaway Little Girl', 'Min306', 0, 36), + (110463, '[en]', 'Min400', 0, 40), + (110464, '[en]', 'Min500', 0, 50), + (110465, '[en]', 'Min506', 0, 56), + (110480, 'Gridanian Roots', 'Hrv200', 0, 20), + (110481, 'The Grass is Always Greener', 'Hrv300', 110480, 30), + (110482, 'A Moogle Bouquet', 'Hrv306', 110481, 36), + (110483, '[en]', 'Hrv400', 0, 40), + (110484, '[en]', 'Hrv500', 0, 50), + (110485, '[en]', 'Hrv506', 0, 56), + (110500, 'To Fight a Fishback', 'Fsh200', 0, 20), + (110501, 'The Beast of the Barrel', 'Fsh300', 0, 30), + (110502, 'Polishing the Mast', 'Fsh306', 0, 36), + (110503, '[en]', 'Fsh400', 0, 40), + (110504, '[en]', 'Fsh500', 0, 50), + (110505, '[en]', 'Fsh506', 0, 56), + (110540, 'Small Talk', 'Dftsea', 0, 0), + (110541, 'Small Talk', 'Dftfst', 0, 0), + (110542, 'Small Talk', 'Dftroc', 0, 0), + (110543, 'Small Talk', 'Dftwil', 0, 0), + (110544, 'Small Talk', 'Dftsrt', 0, 0), + (110545, 'Small Talk', 'Dftlak', 0, 0), + (110600, '[en]', 'Etc0l1', 0, 0), + (110601, '[en]', 'Etc0l2', 0, 0), + (110602, '[en]', 'Etc0l3', 0, 0), + (110603, '[en]', 'Etc0l4', 0, 0), + (110604, '[en]', 'Etc0l5', 0, 0), + (110605, '[en]', 'Etc0l6', 0, 0), + (110606, '[en]', 'Etc0l7', 0, 0), + (110607, '[en]', 'Etc0l8', 0, 0), + (110608, '[en]', 'Etc0l9', 0, 0), + (110609, '[en]', 'Etc0g1', 0, 0), + (110610, '[en]', 'Etc0g2', 0, 0), + (110611, '[en]', 'Etc0g3', 0, 0), + (110612, '[en]', 'Etc0g4', 0, 0), + (110613, '[en]', 'Etc0g5', 0, 0), + (110614, '[en]', 'Etc0g6', 0, 0), + (110615, '[en]', 'Etc0g7', 0, 0), + (110616, '[en]', 'Etc0g8', 0, 0), + (110617, '[en]', 'Etc0g9', 0, 0), + (110618, '[en]', 'Etc0u1', 0, 0), + (110619, '[en]', 'Etc0u2', 0, 0), + (110620, '[en]', 'Etc0u3', 0, 0), + (110621, '[en]', 'Etc0u4', 0, 0), + (110622, '[en]', 'Etc0u5', 0, 0), + (110623, '[en]', 'Etc0u6', 0, 0), + (110624, '[en]', 'Etc0u7', 0, 0), + (110625, '[en]', 'Etc0u8', 0, 0), + (110626, '[en]', 'Etc0u9', 0, 0), + (110627, 'Ifrit Bleeds, We Can Kill It', 'Sum6a0', 0, 45), + (110628, '[en]', 'Sum7l0', 0, 0), + (110629, '[en]', 'Sum7t0', 0, 0), + (110630, '[en]', 'Sum8a0', 0, 0), + (110631, '[en]', 'Sum8l0', 0, 0), + (110632, '[en]', 'Sum8t0', 0, 0), + (110633, 'Assessing the Damage', 'Etc1l0', 0, 20), + (110634, 'Bridging the Gap', 'Etc1l1', 0, 10), + (110635, '[en]', 'Etc1l2', 0, 0), + (110636, 'Revenge on the Reavers', 'Etc1l3', 0, 45), + (110637, '[en]', 'Etc1l4', 0, 0), + (110638, 'Till Death Do Us Part', 'Etc1l5', 0, 20), + (110639, 'Beryl Overboard', 'Etc1l6', 110638, 20), + (110640, 'Have You Seen My Son', 'Etc1l7', 0, 30), + (110641, 'Food for Thought', 'Etc1l8', 0, 20), + (110642, 'Seashells by the Seashore', 'Etc1l9', 0, 20), + (110643, 'Fishing for Answers', 'Etc2l0', 0, 25), + (110644, 'Moonstruck', 'Etc2l1', 0, 20), + (110645, '[en]', 'Etc2l2', 0, 0), + (110646, 'A Misty Past', 'Etc2l3', 0, 17), + (110647, '[en]', 'Etc2l4', 0, 0), + (110648, 'Carving a Name', 'Etc2l5', 0, 47), + (110649, '[en]', 'Etc2l6', 0, 0), + (110650, '[en]', 'Etc2l7', 0, 0), + (110651, '[en]', 'Etc2l8', 0, 0), + (110652, '[en]', 'Etc2l9', 0, 0), + (110653, 'The Tug of the Whorl', 'Etc3l0', 0, 5), + (110654, 'Proceed with Caution', 'Etc1g0', 0, 10), + (110655, 'Playing with Fire', 'Etc1g1', 0, 15), + (110656, 'A Well-Balanced Diet', 'Etc1g2', 0, 25), + (110657, '[en]', 'Etc1g3', 0, 0), + (110658, 'The Penultimate Prank', 'Etc1g4', 0, 30), + (110659, 'The Search for Sicksa', 'Etc1g5', 0, 10), + (110660, 'The Ultimate Prank', 'Etc1g6', 110658, 35), + (110661, '[en]', 'Etc1g7', 0, 0), + (110662, 'Say it with Wolf Tails', 'Etc1g8', 110640, 30), + (110663, 'Embarrassing Excerpts', 'Etc1g9', 0, 30), + (110664, 'A Forbidden Love', 'Etc2g0', 110663, 30), + (110665, 'Last Respects', 'Etc2g1', 0, 40), + (110666, 'Stone Deaf', 'Etc2g2', 0, 18), + (110667, 'Hunting the Hunters', 'Etc2g3', 0, 24), + (110668, 'To Deskunk A Beer', 'Etc2g4', 0, 31), + (110669, 'Losing One\'s Thread', 'Etc2g5', 0, 25), + (110670, '[en]', 'Etc2g6', 0, 0), + (110671, '[en]', 'Etc2g7', 0, 0), + (110672, '[en]', 'Etc2g8', 0, 0), + (110673, '[en]', 'Etc2g9', 0, 0), + (110674, 'Seeing the Seers', 'Etc3g0', 0, 5), + (110675, 'A Knock in the Night', 'Etc1u0', 0, 35), + (110676, 'Sleepless in Eorzea', 'Etc1u1', 0, 10), + (110677, 'Dressed to Be Killed', 'Etc1u2', 0, 45), + (110678, '[en]', 'Etc1u3', 0, 0), + (110679, 'The Customer Comes First', 'Etc1u4', 0, 30), + (110680, 'An Inconvenient Dodo', 'Etc1u5', 0, 15), + (110681, 'Besmitten and Besmirched', 'Etc1u6', 0, 15), + (110682, 'Clasping to Hope', 'Etc1u7', 0, 34), + (110683, 'Traumaturgy', 'Etc1u8', 110682, 36), + (110684, 'Best Flower Ever', 'Etc1u9', 0, 10), + (110685, 'The Unheard Horizon', 'Etc2u0', 0, 20), + (110686, 'Freedom Isn\'t Free', 'Etc2u1', 0, 32), + (110687, 'Ore for an Ore', 'Etc2u2', 0, 28), + (110688, '[en]', 'Etc2u3', 0, 0), + (110689, '[en]', 'Etc2u4', 0, 0), + (110690, 'No Other Dodo Will Do', 'Etc2u5', 0, 15), + (110691, '[en]', 'Etc2u6', 0, 0), + (110692, '[en]', 'Etc2u7', 0, 0), + (110693, '[en]', 'Etc2u8', 0, 0), + (110694, '[en]', 'Etc2u9', 0, 0), + (110695, 'A Call to Arms', 'Etc3u0', 0, 5), + (110696, '[en]', 'Etc1i0', 0, 0), + (110697, '[en]', 'Etc1i1', 0, 0), + (110698, '[en]', 'Etc1i2', 0, 0), + (110699, '[en]', 'Etc1i3', 0, 0), + (110700, '[en]', 'Etc1i4', 0, 0), + (110701, '[en]', 'Etc1i5', 0, 0), + (110702, '[en]', 'Etc1i6', 0, 0), + (110703, '[en]', 'Etc1i7', 0, 0), + (110704, '[en]', 'Etc1i8', 0, 0), + (110705, '[en]', 'Etc1i9', 0, 0), + (110706, 'Counting Sheep', 'Etc2i0', 0, 25), + (110707, 'A Hypocritical Oath', 'Etc2i1', 0, 25), + (110708, 'Blood Price', 'Etc2i2', 0, 45), + (110709, '[en]', 'Etc2i3', 0, 0), + (110710, '[en]', 'Etc2i4', 0, 0), + (110711, '[en]', 'Etc2i5', 0, 0), + (110712, '[en]', 'Etc2i6', 0, 0), + (110713, '[en]', 'Etc2i7', 0, 0), + (110714, '[en]', 'Etc2i8', 0, 0), + (110715, '[en]', 'Etc2i9', 0, 0), + (110716, '[en]', 'Etc3i0', 0, 0), + (110717, '[en]', 'Etc3i1', 0, 0), + (110718, '[en]', 'Etc3i2', 0, 0), + (110719, '[en]', 'Etc3i3', 0, 0), + (110720, '[en]', 'Etc3i4', 0, 0), + (110721, '[en]', 'Etc3i5', 0, 0), + (110722, '[en]', 'Etc3i6', 0, 0), + (110723, '[en]', 'Etc3i7', 0, 0), + (110724, '[en]', 'Etc3i8', 0, 0), + (110725, '[en]', 'Etc3i9', 0, 0), + (110726, 'Quid Pro Quo', 'Etc3u1', 0, 15), + (110727, 'There Might Be Blood', 'Etc3u2', 0, 21), + (110728, 'Cutthroat Prices', 'Etc3u3', 0, 15), + (110729, '[en]', 'Etc3u4', 0, 0), + (110730, '[en]', 'Etc3u5', 0, 0), + (110731, '[en]', 'Etc3u6', 0, 0), + (110732, '[en]', 'Etc3u7', 0, 0), + (110733, '[en]', 'Etc3u8', 0, 0), + (110734, 'Monster of Maw Most Massive', 'Etc3u9', 0, 45), + (110735, 'Scrubbing the Soul', 'Etc3g1', 0, 15), + (110736, 'Disorganized Crime', 'Etc3g2', 0, 21), + (110737, 'A Slippery Stone', 'Etc3g3', 0, 15), + (110738, '[en]', 'Etc3g4', 0, 0), + (110739, '[en]', 'Etc3g5', 0, 0), + (110740, '[en]', 'Etc3g6', 0, 0), + (110741, '[en]', 'Etc3g7', 0, 0), + (110742, '[en]', 'Etc3g8', 0, 0), + (110743, '[en]', 'Etc3g9', 0, 0), + (110744, 'Winds of Change', 'Etc3l1', 0, 15), + (110745, 'Shot Through the Heart', 'Etc3l2', 0, 21), + (110746, 'What a Pirate Wants', 'Etc3l3', 0, 15), + (110747, '[en]', 'Etc3l4', 0, 0), + (110748, '[en]', 'Etc3l5', 0, 0), + (110749, '[en]', 'Etc3l6', 0, 0), + (110750, '[en]', 'Etc3l7', 0, 0), + (110751, '[en]', 'Etc3l8', 0, 0), + (110752, '[en]', 'Etc3l9', 0, 0), + (110753, 'Of Archons and Muses', 'Wld0u1', 0, 10), + (110754, 'Sanguine Studies', 'Wld0u2', 0, 27), + (110755, 'Secrets Unearthed', 'Wld0u3', 0, 17), + (110756, 'Rustproof', 'Wld0u4', 0, 28), + (110757, '[en]', 'Wld0u5', 0, 0), + (110758, '[en]', 'Wld0u6', 0, 0), + (110759, '[en]', 'Wld0u7', 0, 0), + (110760, '[en]', 'Wld0u8', 0, 0), + (110761, '[en]', 'Wld0u9', 0, 0), + (110762, 'In the Name of Science', 'Wld0g1', 0, 10), + (110763, 'Hearing Confession', 'Wld0g2', 0, 10), + (110764, 'A Bitter Oil to Swallow', 'Wld0g3', 0, 17), + (110765, 'Spores on the Brain', 'Wld0g4', 110762, 11), + (110766, '[en]', 'Wld0g5', 0, 0), + (110767, '[en]', 'Wld0g6', 0, 0), + (110768, '[en]', 'Wld0g7', 0, 0), + (110769, '[en]', 'Wld0g8', 0, 0), + (110770, '[en]', 'Wld0g9', 0, 0), + (110771, 'Trading Tongueflaps', 'Wld0l1', 0, 5), + (110772, 'Letting Out Orion\'s Belt', 'Wld0l2', 0, 10), + (110773, 'Sour Grapes', 'Wld0l3', 0, 17), + (110774, 'Sniffing Out a Profit', 'Wld0l4', 0, 37), + (110775, '[en]', 'Wld0l5', 0, 0), + (110776, '[en]', 'Wld0l6', 0, 0), + (110777, '[en]', 'Wld0l7', 0, 0), + (110778, '[en]', 'Wld0l8', 0, 0), + (110779, '[en]', 'Wld0l9', 0, 0), + (110780, '[en]', 'Wld0i1', 0, 0), + (110781, '[en]', 'Wld0i2', 0, 0), + (110782, '[en]', 'Wld0i3', 0, 0), + (110783, '[en]', 'Wld0i4', 0, 0), + (110784, '[en]', 'Wld0i5', 0, 0), + (110785, '[en]', 'Wld0i6', 0, 0), + (110786, '[en]', 'Wld0i7', 0, 0), + (110787, '[en]', 'Wld0i8', 0, 0), + (110788, '[en]', 'Wld0i9', 0, 0), + (110789, 'The Dreamer\'s Gospel (Ul\'dah)', 'Spl0u1', 0, 5), + (110790, 'The Dreamer\'s Dilemma (Ul\'dah)', 'Spl0u2', 0, 5), + (110791, '[en]', 'Spl0u3', 0, 0), + (110792, '[en]', 'Spl0u4', 0, 0), + (110793, '[en]', 'Spl0u5', 0, 0), + (110794, 'The Dreamer\'s Gospel (Gridania)', 'Spl0g1', 0, 5), + (110795, 'The Dreamer\'s Dilemma (Gridania)', 'Spl0g2', 0, 5), + (110796, '[en]', 'Spl0g3', 0, 0), + (110797, '[en]', 'Spl0g4', 0, 0), + (110798, '[en]', 'Spl0g5', 0, 0), + (110799, 'The Heat Is On', 'Spl0i1', 0, 1), + (110800, 'Impish Impositions', 'Spl0i2', 0, 1), + (110801, 'Winter Is Not Coming', 'Spl0i3', 0, 1), + (110802, 'Gone with the Snow', 'Spl0i4', 0, 1), + (110803, '[en]', 'Spl0i5', 0, 0), + (110804, 'The Dreamer\'s Gospel (Limsa Lominsa)', 'Spl0l1', 0, 5), + (110805, 'The Dreamer\'s Dilemma (Limsa Lominsa)', 'Spl0l2', 0, 5), + (110806, '[en]', 'Spl0l3', 0, 0), + (110807, '[en]', 'Spl0l4', 0, 0), + (110808, '[en]', 'Spl0l5', 0, 0), + (110809, 'Guild Tasks', 'Noc000', 0, 0), + (110810, 'Call of Booty', 'Etc303', 110737, 15), + (110811, 'Risky Business', 'Etc101', 0, 18), + (110812, 'Forging the Spirit', 'Etc102', 0, 18), + (110813, 'Joining the Spirit', 'Etc103', 0, 18), + (110814, 'Waking the Spirit', 'Etc104', 0, 18), + (110815, '[en]', 'Etc105', 0, 0), + (110816, 'A Feast of Fools', 'Sum6m0', 0, 45), + (110817, 'Provisioning & Supply Missions', 'Noc001', 0, 0), + (110818, 'A Light in the Dark', 'Etc200', 0, 45), + (110819, 'What Glitters Always Isn\'t Gold', 'Etc201', 0, 45), + (110820, '[en]', 'Etc202', 0, 1), + (110821, 'the Thousand Maws of Toto’Rak', 'Etc202', 0, 1), + (110822, 'Dzemael Darkhold', 'Etc202', 0, 1), + (110823, 'Aurum Vale', 'Etc202', 0, 1), + (110824, 'Cutter\'s Cry', 'Etc202', 0, 1), + (110828, 'Waste Not Want Not', 'Etc5g0', 110006, 1), + (110829, 'In Plain Sight', 'Etc5g1', 0, 15), + (110838, 'The Ink Thief', 'Etc5l0', 110002, 1), + (110839, 'Private Eyes', 'Etc5l1', 0, 15), + (110840, 'Mysteries of the Red Moon', 'Etc5l2', 110839, 20), + (110841, 'Prophecy Inspection', 'Etc5l3', 110840, 20), + (110848, 'Ring of Deceit', 'Etc5u0', 110010, 1), + (110849, 'The Usual Suspect', 'Etc5u1', 0, 15), + (110850, '[en]', 'Etc5u2', 0, 1), + (110851, '[en]', 'Etc5u3', 0, 1), + (110858, 'Seasonal Event', 'Spl000', 0, 1), + (110859, 'Scrambled Eggs', 'Spl101', 0, 1), + (110860, 'Bombard Backlash', 'Spl102', 0, 1), + (110861, 'Seasonal Event (All City-states)', 'Spl103', 0, 1), + (110862, 'Hamlet Defense', 'Noc002', 0, 1), + (110863, 'Class is in Session', 'Noc003', 0, 1), + (110867, 'Taming the Tempest', 'Sum6g0', 0, 45), + (110868, 'A Relic Reborn', 'Etc106', 0, 50), + (110869, 'Living on a Prayer', 'Etc304', 0, 45), + (110870, 'The Raven, Nevermore', 'Sum6w0', 0, 45), + (111201, 'Pride and Duty (Will Take You from the Mountain)', 'War0j1', 0, 15), + (111202, 'Embracing the Beast', 'War0j2', 0, 15), + (111203, 'Curious Gorge Goes to the Bazaar', 'War0j3', 0, 15), + (111204, 'Looking the Part', 'War0j4', 0, 15), + (111205, 'Proof is in the Pudding', 'War0j5', 0, 15), + (111206, 'How to Quit You', 'War0j6', 0, 15), + (111221, 'Brother from Another Mother', 'Mnk0j1', 0, 15), + (111222, 'Insulted Intelligence', 'Mnk0j2', 0, 15), + (111223, 'The Pursuit of Power', 'Mnk0j3', 0, 15), + (111224, 'Good Vibrations', 'Mnk0j4', 0, 15), + (111225, 'Five Easy Pieces', 'Mnk0j5', 0, 15), + (111226, 'Return of the King...of Ruin', 'Mnk0j6', 0, 15), + (111241, 'Seeds of Initiative', 'Whm0j1', 0, 15), + (111242, 'When Sheep Attack', 'Whm0j2', 0, 15), + (111243, 'Lost in Rage', 'Whm0j3', 0, 15), + (111244, 'The Wheel of Disaster', 'Whm0j4', 0, 15), + (111245, 'In Search of Succor', 'Whm0j5', 0, 15), + (111246, 'The Chorus of Cataclysm', 'Whm0j6', 0, 15), + (111261, 'Hearing Voices', 'Blm0j1', 0, 15), + (111262, 'A Time to Kill', 'Blm0j2', 0, 15), + (111263, 'International Relations', 'Blm0j3', 0, 15), + (111264, 'The Voidgate Breathes Gloomy', 'Blm0j4', 0, 15), + (111265, 'Gearing Up', 'Blm0j5', 0, 15), + (111266, 'Always Bet on Black', 'Blm0j6', 0, 15), + (111281, 'Paladin\'s Pledge', 'Pld0j1', 0, 15), + (111282, 'Honor Lost', 'Pld0j2', 0, 15), + (111283, 'Power Struggles', 'Pld0j3', 0, 15), + (111284, 'Poisoned Hearts', 'Pld0j4', 0, 15), + (111285, 'Parley on High Ground', 'Pld0j5', 0, 15), + (111286, 'Keeping the Oath', 'Pld0j6', 0, 15), + (111301, 'A Song of Bards and Bowmen', 'Brd0j1', 0, 15), + (111302, 'The Archer\'s Anthem', 'Brd0j2', 0, 15), + (111303, 'Bard\'s-Eye View', 'Brd0j3', 0, 15), + (111304, 'Doing It the Bard Way', 'Brd0j4', 0, 15), + (111305, 'Pieces of the Past', 'Brd0j5', 0, 15), + (111306, 'Requiem for the Fallen', 'Brd0j6', 0, 15), + (111321, 'Eye of the Dragon', 'Drg0j1', 0, 15), + (111322, 'Lance of Fury', 'Drg0j2', 0, 15), + (111323, 'Unfading Scars', 'Drg0j3', 0, 15), + (111324, 'Double Dragoon', 'Drg0j4', 0, 15), + (111325, 'Fatal Seduction', 'Drg0j5', 0, 15), + (111326, 'Into the Dragon\'s Maw', 'Drg0j6', 0, 15), + (111401, 'The Price of Integrity', 'Com0l1', 0, 22), + (111402, 'Testing the Waters', 'Com0l2', 0, 22), + (111403, 'Seals for the Whorl', 'Com0l3', 0, 22), + (111404, 'Engineering Victory', 'Com0l4', 0, 22), + (111405, 'An Officer and a Wise Man', 'Com0l5', 0, 25), + (111406, 'Ceruleum Shock', 'Com0l6', 0, 25), + (111407, 'Till Sea Swallows All', 'Com0l7', 0, 25), + (111408, '[en]', 'Com0l8', 0, 0), + (111409, '[en]', 'Com0l9', 0, 0), + (111410, 'Imperial Devices (Limsa Lominsa)', 'Com5l0', 0, 25), + (111411, 'Into the Dark (Limsa Lominsa)', 'Com5l1', 0, 45), + (111412, '[en]', 'Com5l2', 0, 0), + (111413, '[en]', 'Com5l3', 0, 0), + (111414, '[en]', 'Com5l4', 0, 0), + (111415, '[en]', 'Com5l5', 0, 0), + (111416, 'It Kills with Fire (Limsa Lominsa)', 'Gcl101', 0, 30), + (111417, 'The Cove', 'Gcl301', 0, 25), + (111418, 'Saving the Stead Instead', 'Gcl302', 0, 25), + (111419, 'It\'s a Piece of Cake to Bake a Poison Cake', 'Gcl303', 0, 40), + (111420, 'Kobold and the Beautiful', 'Gcl304', 0, 45), + (111421, '[en]', 'Gcl501', 0, 0), + (111422, '[en]', 'Gcl502', 0, 0), + (111423, '[en]', 'Gcl601', 0, 0), + (111424, '[en]', 'Gcl602', 0, 0), + (111425, '[en]', 'Gcl603', 0, 0), + (111426, 'Oil Crisis', 'Gcl305', 0, 50), + (111427, 'Alive', 'Gcl102', 0, 40), + (111428, 'The Weakest Link', 'Gcl701', 0, 45), + (111429, 'Deus ex Machina', 'Gcl103', 0, 45), + (111430, 'In for Garuda Wakening (Limsa Lominsa)', 'Gcl104', 0, 45), + (111431, 'Don\'t Hate the Messenger (Limsa Lominsa)', 'Gcl105', 0, 45), + (111432, 'United We Stand (Limsa Lominsa)', 'Gcl106', 0, 45), + (111433, 'To Kill a Raven (Limsa Lominsa)', 'Gcl107', 0, 45), + (111434, 'Patrol, Interrupted', 'Gcl702', 0, 50), + (111601, 'Breaking the Seals', 'Com0g1', 0, 22), + (111602, 'Why Did It Have to Be Snakes', 'Com0g2', 0, 22), + (111603, 'Adder\'s Nest Egg', 'Com0g3', 0, 22), + (111604, 'The Mail Must Get Through', 'Com0g4', 0, 22), + (111605, 'Their Finest Hour', 'Com0g5', 0, 25), + (111606, 'Appetite for Destruction', 'Com0g6', 0, 25), + (111607, 'Serenity, Purity, Sanctity', 'Com0g7', 0, 25), + (111608, '[en]', 'Com0g8', 0, 0), + (111609, '[en]', 'Com0g9', 0, 0), + (111610, 'Imperial Devices (Gridania)', 'Com5g0', 0, 25), + (111611, 'Into the Dark (Gridania)', 'Com5g1', 0, 45), + (111612, '[en]', 'Com5g2', 0, 0), + (111613, '[en]', 'Com5g3', 0, 0), + (111614, '[en]', 'Com5g4', 0, 0), + (111615, '[en]', 'Com5g5', 0, 0), + (111616, 'It Kills with Fire (Gridania)', 'Gcg101', 0, 30), + (111617, 'Eternal Recurrence', 'Gcg301', 0, 25), + (111618, 'The Pen Is Mightier Than the Spear', 'Gcg302', 0, 25), + (111619, 'Woes of the Botanist', 'Gcg303', 0, 40), + (111620, 'Gone with the Wind', 'Gcg304', 0, 45), + (111621, '[en]', 'Gcg501', 0, 0), + (111622, '[en]', 'Gcg502', 0, 0), + (111623, '[en]', 'Gcg601', 0, 0), + (111624, '[en]', 'Gcg602', 0, 0), + (111625, '[en]', 'Gcg603', 0, 0), + (111626, 'A Taste for Death', 'Gcg305', 0, 50), + (111627, 'Two Vans are Better than One', 'Gcg102', 0, 40), + (111628, 'You Don\'t Have the Rite', 'Gcg701', 0, 45), + (111629, 'Shadow of the Raven', 'Gcg103', 0, 45), + (111630, 'In for Garuda Wakening (Gridania)', 'Gcg104', 0, 45), + (111631, 'Don\'t Hate the Messenger (Gridania)', 'Gcg105', 0, 45), + (111632, 'United We Stand (Gridania)', 'Gcg106', 0, 45), + (111633, 'To Kill a Raven (Gridania)', 'Gcg107', 0, 45), + (111634, 'Cure for the Common Pox', 'Gcg702', 0, 50), + (111801, 'Career Opportunities', 'Com0u1', 0, 22), + (111802, 'Kindling a Flame', 'Com0u2', 0, 22), + (111803, 'Burning a Hole in One\'s Pocket', 'Com0u3', 0, 22), + (111804, 'Arms Race', 'Com0u4', 0, 22), + (111805, 'Burning Man', 'Com0u5', 0, 25), + (111806, 'Know Your Enemy', 'Com0u6', 0, 25), + (111807, 'By Fire Reborn', 'Com0u7', 0, 25), + (111808, '[en]', 'Com0u8', 0, 0), + (111809, '[en]', 'Com0u9', 0, 0), + (111810, 'Imperial Devices (Ul\'dah)', 'Com5u0', 0, 25), + (111811, 'Into the Dark (Ul\'dah)', 'Com5u1', 0, 45), + (111812, '[en]', 'Com5u2', 0, 0), + (111813, '[en]', 'Com5u3', 0, 0), + (111814, '[en]', 'Com5u4', 0, 0), + (111815, '[en]', 'Com5u5', 0, 0), + (111816, 'It Kills with Fire (Ul\'dah)', 'Gcu101', 0, 30), + (111817, 'Prying Eyes', 'Gcu301', 0, 25), + (111818, 'Different Strokes', 'Gcu302', 0, 25), + (111819, 'A Weaver and a Mummer', 'Gcu303', 0, 40), + (111820, 'When Alchemists Cry', 'Gcu304', 0, 45), + (111821, '[en]', 'Gcu501', 0, 0), + (111822, '[en]', 'Gcu502', 0, 0), + (111823, '[en]', 'Gcu601', 0, 0), + (111824, '[en]', 'Gcu602', 0, 0), + (111825, '[en]', 'Gcu603', 0, 0), + (111826, 'Challenge Accepted', 'Gcu305', 0, 50), + (111827, 'Like Father, Like Son', 'Gcu102', 0, 40), + (111828, 'Gore a Lizard, Hurry', 'Gcu701', 0, 45), + (111829, 'Careless Whispers', 'Gcu103', 0, 45), + (111830, 'In for Garuda Wakening (Ul\'dah)', 'Gcu104', 0, 45), + (111831, 'Don\'t Hate the Messenger (Ul\'dah)', 'Gcu105', 0, 45), + (111832, 'United We Stand (Ul\'dah)', 'Gcu106', 0, 45), + (111833, 'To Kill a Raven (Ul\'dah)', 'Gcu107', 0, 45), + (111834, 'Mess with the Goat, Get the Horns', 'Gcu702', 0, 50); +/*!40000 ALTER TABLE `gamedata_quests` ENABLE KEYS */; + +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;