1
Fork 0
mirror of https://bitbucket.org/Ioncannon/project-meteor-server.git synced 2025-04-25 14:17:46 +00:00

Merge remote-tracking branch 'origin/ioncannon/quest_system' into Jorge/quest_system

This commit is contained in:
CuriousJorge 2022-02-20 15:10:49 -05:00
commit 594e08f990
84 changed files with 2660 additions and 1279 deletions

View file

@ -0,0 +1,204 @@
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 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 void SetTo(bool[] result)
{
Debug.Assert(Data.Length == result.Length / 8);
Data = Utils.ConvertBoolArrayToBinaryStream(result);
}
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;
}
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;
continue;
}
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;
destShiftIndx = 0;
curByte = 0;
}
}
if (destByteIndx == toReturn.Length - 2)
toReturn[destByteIndx] = curByte;
return toReturn;
}
}
}

View file

@ -87,6 +87,7 @@
<ItemGroup>
<Compile Include="BasePacket.cs" />
<Compile Include="Bitfield.cs" />
<Compile Include="Bitstream.cs" />
<Compile Include="Blowfish.cs" />
<Compile Include="EfficientHashTables.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -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]);
}
@ -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";

View file

@ -23,7 +23,7 @@ end
function onEventStarted(player, npc, eventType, eventName)
local defaultTalk = player:GetDefaultTalkQuest(npc);
local tutorialTalk = player:GetTutorialQuest(npc);
local tutorialTalk = player:GetTutorialQuest(npc);
local activeQuests = player:GetQuestsForNpc(npc);
local possibleQuests = {};

View file

@ -0,0 +1,58 @@
require("global");
properties = {
permissions = 0,
parameters = "dd",
description =
[[
Sets if a quest is completed.
!completedQuest <questId> 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

View file

@ -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);

View file

@ -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,79 +48,93 @@ end
function onFinish(player, quest)
end
function onSequence(player, quest, sequence)
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: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 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: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 == 1) then
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);
@ -134,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();
@ -143,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
@ -157,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

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -7,7 +7,7 @@ end
function onFinish(player, quest)
end
function onSequence(player, quest, seqNum)
function onStateChange(player, quest, seqNum)
quest:ClearENpcs();
end

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 TotoRak', '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 */;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Map.Actors;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
namespace Meteor.Map.actors.chara.ai.state
{

View file

@ -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;

View file

@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;

View file

@ -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;
@ -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;
@ -46,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
{
@ -147,6 +149,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;
@ -241,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++ )
@ -274,6 +276,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(questScenario, playerWork.questScenarioComplete);
}
public List<SubPacket> Create0x132Packets()
@ -400,9 +405,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, "<Path Companion>", 11, 1, 1));
testComplete[i] = true;
QueuePacket(cutsceneBookPacket.BuildPacket(Id, "<Path Companion>", 11, 1, 1, testComplete));
QueuePacket(SetPlayerDreamPacket.BuildPacket(Id, 0x16, GetInnCode()));
}
@ -801,7 +807,7 @@ namespace Meteor.Map.Actors
foreach (Quest quest in questScenario)
{
if (quest != null)
quest.SaveData();
quest.GetData().Save();
}
}
@ -1151,6 +1157,42 @@ 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)
{
byte[] data = questStateManager.GetCompletionSliceBytes(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;
@ -1413,33 +1455,276 @@ 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;
SendQuestClientUpdate(freeSlot);
if (!isSilent)
{
SendGameMessage(Server.GetWorldManager().GetActor(), 25224, 0x20, (object)questScenario[freeSlot].GetQuestId()); // "<Quest> accepted."
}
instance.OnAccept();
Database.SaveQuest(this, questScenario[freeSlot], freeSlot);
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;
SendQuestClientUpdate(i);
oldQuestInstance.OnComplete();
questStateManager.UpdateQuestCompleted(oldQuestInstance);
newQuestInstance.OnAccept();
Database.SaveQuest(this, questScenario[i], 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;
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
SendGameMessage(Server.GetWorldManager().GetActor(), 25086, 0x20, (object)completed.GetQuestId()); // "<Quest> 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
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
SendGameMessage(this, Server.GetWorldManager().GetActor(), 25236, 0x20, (object)abandoned.GetQuestId()); // "<Quest> 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], 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 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++)
{
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();
@ -1484,179 +1769,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)
{
@ -1668,17 +1781,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)
{
@ -1732,7 +1835,9 @@ namespace Meteor.Map.Actors
public Quest[] GetQuestsForNpc(Npc npc)
{
return Array.FindAll(questScenario, e => e != null && e.IsQuestENPC(this, 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)
@ -2755,6 +2860,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());
}
}

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
namespace Meteor.Map.dataobjects.chara
namespace Meteor.Map.DataObjects.chara
{
class PlayerWork
{

View file

@ -21,7 +21,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;

View file

@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;

View file

@ -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;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
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;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
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;

View file

@ -21,7 +21,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;

View file

@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;

View file

@ -21,7 +21,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;

View file

@ -20,115 +20,126 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
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 = ushort.MaxValue;
public const ushort SEQ_NOT_STARTED = 65535;
public const ushort SEQ_COMPLETED = 65534;
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;
}
}
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;
}
}
private Player Owner;
private Player owner;
private ushort currentSequence;
private QuestData data = new QuestData();
private bool dataDirty = false;
private Dictionary<uint, ENpcQuestInstance> CurrentENPCs = new Dictionary<uint, ENpcQuestInstance>();
private Dictionary<uint, ENpcQuestInstance> OldENPCs = new Dictionary<uint, ENpcQuestInstance>();
private QuestState questState = null;
private QuestData data = null;
public void AddENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false)
// Creates a Static Quest for the StaticActors list.
public Quest(uint actorID, string className, string classPath)
: base(actorID)
{
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);
}
public ENpcQuestInstance GetENpcInstance(uint classId)
{
if (CurrentENPCs.ContainsKey(classId))
return CurrentENPCs[classId];
return null;
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);
}
public void UpdateENPCs()
{
if (data.Dirty)
{
if (questState != null)
questState.UpdateState();
data.ClearDirty();
}
}
public QuestState GetQuestState()
{
return questState;
}
#endregion
#region Script Callbacks
public void OnTalk(Player caller, Npc npc)
{
{
LuaEngine.GetInstance().CallLuaFunction(caller, this, "onTalk", true, npc);
}
@ -152,31 +163,9 @@ namespace Meteor.Map.Actors
LuaEngine.GetInstance().CallLuaFunction(caller, this, "onNpcLS", true, npcLSId);
}
public void UpdateENPCs()
{
if (dataDirty)
{
OldENPCs = CurrentENPCs;
CurrentENPCs = new Dictionary<uint, ENpcQuestInstance>();
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<LuaParam> 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());
}
public object[] GetJournalInformation()
{
List<LuaParam> returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "getJournalInformation", true);
List<LuaParam> returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(owner, this, "getJournalInformation", true);
if (returned != null && returned.Count != 0)
return LuaUtils.CreateLuaParamObjectList(returned);
else
@ -185,199 +174,58 @@ namespace Meteor.Map.Actors
public object[] GetJournalMapMarkerList()
{
List<LuaParam> returned = LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "getJournalMapMarkerList", true);
List<LuaParam> 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<LuaParam> 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();
currentSequence = sequence;
questState.UpdateState();
}
public void ClearData()
public void OnAccept()
{
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;
data = new QuestData(flags, counter1, counter2, counter3);
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 uint GetQuestId()
public void OnComplete()
{
return Id & 0xFFFFF;
LuaEngine.GetInstance().CallLuaFunctionForReturn(owner, this, "onFinish", true);
currentSequence = SEQ_COMPLETED;
data = null;
questState.UpdateState();
}
public void DoComplete()
public void OnAbandon()
{
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", false);
currentSequence = SEQ_NOT_STARTED;
data = null;
questState.UpdateState();
}
public void DoAbandon()
{
LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "onFinish", false);
Owner.SendGameMessage(Owner, Server.GetWorldManager().GetActor(), 25236, 0x20, (object)GetQuestId());
}
}
}

View file

@ -0,0 +1,173 @@
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;
public bool Dirty { get; private set; } = 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);
Dirty = true;
}
}
public void ClearFlag(int index)
{
if (index >= 0 && index < 32)
{
flags &= (uint)~(1 << index);
Dirty = true;
}
}
public ushort IncCounter(int num)
{
Dirty = true;
switch (num)
{
case 0:
counter1++;
return counter1;
case 1:
counter2++;
return counter2;
case 2:
counter3++;
return counter3;
case 3:
counter4++;
return counter4;
}
Dirty = false;
return 0;
}
public ushort DecCounter(int num)
{
Dirty = true;
switch (num)
{
case 0:
counter1--;
return counter1;
case 1:
counter2--;
return counter2;
case 2:
counter3--;
return counter3;
case 3:
counter4--;
return counter4;
}
Dirty = false;
return 0;
}
public void SetCounter(int num, ushort value)
{
Dirty = true;
switch (num)
{
case 0:
counter1 = value;
return;
case 1:
counter2 = value;
return;
case 2:
counter3 = value;
return;
case 3:
counter4 = value;
return;
}
Dirty = 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 ClearDirty()
{
Dirty = false;
}
public void Save()
{
Database.UpdateQuest(owner, parent);
}
}
}

View file

@ -0,0 +1,118 @@
using Meteor.Map.lua;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Meteor.Map.Actors.QuestNS
{
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 readonly Player Owner;
private readonly Quest Parent;
private Dictionary<uint, QuestENpc> CurrentENPCs = new Dictionary<uint, QuestENpc>();
private Dictionary<uint, QuestENpc> OldENPCs = new Dictionary<uint, QuestENpc>();
public QuestState(Player owner, Quest parent)
{
Owner = owner;
Parent = parent;
}
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<uint, QuestENpc>();
LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, Parent, "onStateChange", false, currentSeq);
foreach (var enpc in OldENPCs)
Owner.playerSession.UpdateQuestNpcInInstance(enpc.Value, true);
OldENPCs = null;
}
public void DeleteState()
{
foreach (var enpc in CurrentENPCs)
Owner.playerSession.UpdateQuestNpcInInstance(enpc.Value, true);
CurrentENPCs.Clear();
}
}
}

View file

@ -0,0 +1,178 @@
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.QuestNS
{
class QuestStateManager
{
private const int SCENARIO_START = 110001;
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);
private readonly Bitstream GCRankBitfield = new Bitstream(SCENARIO_MAX, true);
private List<Quest> ActiveQuests = new List<Quest>();
private Dictionary<uint, QuestState> QuestStateTable = new Dictionary<uint, QuestState>();
public QuestStateManager(Player player)
{
this.player = player;
}
public void Init(Quest[] journalQuests, bool[] completedQuests)
{
// Preload any quests that the player loaded
if (journalQuests != null)
{
foreach (var quest in journalQuests)
{
if (quest != null)
{
ActiveQuests.Add(quest);
AvailableQuestsBitfield.Set(quest.GetQuestId() - SCENARIO_START);
}
}
}
// Init MinLv
QuestGameData[] minLvl = Server.GetQuestGamedataByMaxLvl(player.GetHighestLevel(), true);
foreach (var questData in minLvl)
MinLevelBitfield.Set(questData.Id - SCENARIO_START);
// Init Prereq
CompletedQuestsBitfield.SetTo(completedQuests);
foreach (var questData in Server.GetQuestGamedataAllPrerequisite())
{
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);
}
ComputeAvailable();
}
public void UpdateLevel(int level)
{
QuestGameData[] updated = Server.GetQuestGamedataByMaxLvl(level);
foreach (var questData in updated)
MinLevelBitfield.Set(questData.Id - SCENARIO_START);
ComputeAvailable();
}
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);
ComputeAvailable();
}
public void UpdateQuestAbandoned()
{
ComputeAvailable();
}
private void ComputeAvailable()
{
Bitstream result = new Bitstream(SCENARIO_MAX);
result.OR(CompletedQuestsBitfield);
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))
AddActiveQuest(quest);
else
RemoveActiveQuest(quest);
}
}
}
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();
}
public byte[] GetCompletionSliceBytes(ushort from, ushort to)
{
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();
}
}
}

View file

@ -20,6 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
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"))

View file

@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;

View file

@ -21,7 +21,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
using MySql.Data.MySqlClient;
namespace Meteor.Map.dataobjects
namespace Meteor.Map.DataObjects
{
class GuildleveData
{

View file

@ -25,7 +25,7 @@ using MySql.Data.MySqlClient;
using System;
using System.IO;
namespace Meteor.Map.dataobjects
namespace Meteor.Map.DataObjects
{
class InventoryItem
{

View file

@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
using MySql.Data.MySqlClient;
using System;
namespace Meteor.Map.dataobjects
namespace Meteor.Map.DataObjects
{
class ItemData
{

View file

@ -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 QuestGameData
{
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 QuestGameData(uint id, string className, string name, uint prereq, int minLv, int minGcRank)
{
Id = id;
ClassName = className;
Name = Name;
PrerequisiteQuest = prereq;
MinLevel = minLv;
MinGCRank = minGcRank;
}
}
}

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
namespace Meteor.Map.dataobjects
namespace Meteor.Map.DataObjects
{
class RecruitmentDetails
{

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
namespace Meteor.Map.dataobjects
namespace Meteor.Map.DataObjects
{
class SeamlessBoundry
{

View file

@ -23,7 +23,7 @@ using System;
using System.IO;
using System.Text;
namespace Meteor.Map.dataobjects
namespace Meteor.Map.DataObjects
{
class SearchEntry
{

View file

@ -25,9 +25,10 @@ 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 Meteor.Map.Actors.QuestNS;
using static Meteor.Map.Actors.QuestNS.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));

View file

@ -19,6 +19,6 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
namespace Meteor.Map.dataobjects
namespace Meteor.Map.DataObjects
{
}

View file

@ -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
{

View file

@ -26,14 +26,15 @@ 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.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,6 +73,55 @@ namespace Meteor.Map
return id;
}
public static Dictionary<uint, QuestGameData> 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<uint, QuestGameData> gamedataQuests = new Dictionary<uint, QuestGameData>();
try
{
conn.Open();
string query = @"
SELECT
id,
className,
questName,
prerequisite,
minLevel
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 QuestGameData(questId, code, name, prerequisite, minLevel, 0));
}
}
}
catch (MySqlException e)
{
Program.Log.Error(e.ToString());
}
finally
{
conn.Dispose();
}
return gamedataQuests;
}
}
public static Dictionary<uint, ItemData> 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)))
@ -477,23 +527,13 @@ 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;
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
@ -514,10 +554,59 @@ 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();
}
catch (MySqlException e)
{
Program.Log.Error(e.ToString());
}
finally
{
conn.Dispose();
}
}
}
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();
}
@ -674,7 +763,7 @@ namespace Meteor.Map
}
}
public static void CompleteQuest(Player player, uint questId)
public static void SaveCompletedQuests(Player player)
{
string query;
MySqlCommand cmd;
@ -687,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();
}
@ -710,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;
@ -1168,11 +1232,32 @@ 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);
}
}
//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);
}
}

View file

@ -22,12 +22,11 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;
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.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<Command>();
UserData.RegisterType<Npc>();
UserData.RegisterType<Quest>();
UserData.RegisterType<QuestData>();
UserData.RegisterType<Zone>();
UserData.RegisterType<InventoryItem>();
UserData.RegisterType<ItemPackage>();

View file

@ -180,9 +180,13 @@
<Compile Include="Actors\Group\Work\RelationWork.cs" />
<Compile Include="Actors\Judge\Judge.cs" />
<Compile Include="Actors\Quest\Quest.cs" />
<Compile Include="Actors\Quest\QuestData.cs" />
<Compile Include="Actors\Quest\QuestState.cs" />
<Compile Include="Actors\Quest\QuestStateManager.cs" />
<Compile Include="Actors\StaticActors.cs" />
<Compile Include="Actors\World\WorldMaster.cs" />
<Compile Include="DataObjects\GuildleveData.cs" />
<Compile Include="DataObjects\QuestGameData.cs" />
<Compile Include="DataObjects\Recipe.cs" />
<Compile Include="DataObjects\RecipeResolver.cs" />
<Compile Include="DataObjects\TradeTransaction.cs" />
@ -223,7 +227,7 @@
<Compile Include="Packets\Receive\CountdownRequestPacket.cs" />
<Compile Include="Packets\Receive\LangaugeCodePacket.cs" />
<Compile Include="Packets\Receive\UpdateItemPackagePacket.cs" />
<Compile Include="Packets\Receive\ParameterDataRequestPacket.cs" />
<Compile Include="Packets\Receive\WorkSyncRequestPacket.cs" />
<Compile Include="Packets\Receive\Recruitment\RecruitmentDetailsRequestPacket.cs" />
<Compile Include="Packets\Receive\Recruitment\RecruitmentSearchRequestPacket.cs" />
<Compile Include="Packets\Receive\Recruitment\StartRecruitingRequestPacket.cs" />

View file

@ -22,7 +22,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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;
@ -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<long, string>[] { new Tuple<long, string>(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<long, string>[] { new Tuple<long, string>(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;
}
}
}
}

View file

@ -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<byte> strList = new List<byte>();
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;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System.Collections.Generic;
using System.IO;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System.Collections.Generic;
using System.IO;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System.IO;
using Meteor.Common;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.Collections.Generic;
using System.IO;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System.Collections.Generic;
using System.IO;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System.Collections.Generic;
using System.IO;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System.Collections.Generic;
using System.IO;

View file

@ -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
{

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.Collections.Generic;
using System.IO;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.Collections.Generic;
using System.IO;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.Collections.Generic;
using System.IO;

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.Collections.Generic;
using System.IO;

View file

@ -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;
}

View file

@ -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);

View file

@ -19,7 +19,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
===========================================================================
*/
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
namespace Meteor.Map.packets.WorldPackets.Send.Group
{

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;
using System.Text;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;

View file

@ -20,7 +20,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
*/
using Meteor.Common;
using Meteor.Map.dataobjects;
using Meteor.Map.DataObjects;
using System;
using System.IO;

View file

@ -21,6 +21,7 @@ along with Project Meteor Server. If not, see <https:www.gnu.org/licenses/>.
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

View file

@ -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<uint, Session> mSessionList = new Dictionary<uint, Session>();
private Dictionary<uint, Session> SessionList = new Dictionary<uint, Session>();
private static CommandProcessor mCommandProcessor = new CommandProcessor();
private static ZoneConnection mWorldConnection = new ZoneConnection();
private static WorldManager mWorldManager;
private static Dictionary<uint, ItemData> mGamedataItems;
private static Dictionary<uint, GuildleveData> mGamedataGuildleves;
private static StaticActors mStaticActors;
private static CommandProcessor CommandProcessor = new CommandProcessor();
private static ZoneConnection WorldConnection = new ZoneConnection();
private static WorldManager WorldManager;
private static Dictionary<uint, ItemData> GamedataItems;
private static Dictionary<uint, GuildleveData> GamedataGuildleves;
private static Dictionary<uint, QuestGameData> 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<uint, Session> 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<uint, ItemData> 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 QuestGameData GetQuestGamedata(uint id)
{
if (GamedataQuests.ContainsKey(id))
return GamedataQuests[id];
else
return null;
}
public static QuestGameData[] 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 QuestGameData[] GetQuestGamedataByPrerequisite(uint questId)
{
return GamedataQuests.Values.Where(quest => quest.PrerequisiteQuest == questId).ToArray();
}
public static QuestGameData[] GetQuestGamedataAllPrerequisite()
{
return GamedataQuests.Values.Where(quest => quest.PrerequisiteQuest != 0).ToArray();
}
public static QuestGameData[] 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();
//}
}
}

View file

@ -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;