Changed how Quest ENPCs work. They should now automatically update themselves whenever data is update for immediate effect changes. Fixed wrong map qflag value. Fixed quest gm command. Fixed Director actorId clobbering areaMasters.

This commit is contained in:
Filip Maj 2022-02-06 12:12:26 -05:00
parent b11007b263
commit 8b3431e557
8 changed files with 180 additions and 179 deletions

View File

@ -18,6 +18,7 @@ function onTrigger(player, argc, command, var1, var2, var3)
local sender = "[quest] ";
local message = "Error";
print(tostring(argc));
if player then
if argc == 2 then
if command == "add" or command == "give" or command == "+" then
@ -57,19 +58,45 @@ function onTrigger(player, argc, command, var1, var2, var3)
else
message = ("remove error: either incorrect ID or quest "..var1.." isn't active on character");
end
end
elseif command == "info" then
if tonumber(var1) then
if player:HasQuest(tonumber(var1)) then
quest = player:GetQuest(tonumber(var1));
local flagStr = "";
for i=0,31,1 do
if (quest:GetFlag(i)) then
flagStr = flagStr .. "O";
else
flagStr = flagStr .. "X";
end
if (i == 15) then
flagStr = flagStr .. "\n";
end
end
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(1), quest:getCounter(2), quest:getCounter(3), quest:getCounter(4));
else
message = ("Quest not active: "..var1);
end
else
message = ("error: invalid parameters used");
end
else
message = ("error: command "..command.." not recognized");
end
end
elseif argc == 3 then
if command == "phase" or command == "step" then
if command == "seq" or command == "sequence" then
if (tonumber(var1) and tonumber(var2)) ~= nil then
if player:HasQuest(tonumber(var1)) == true then
player:GetQuest(tonumber(var1)):NextPhase(tonumber(var2));
message = ("changing phase of quest "..var1.." to "..var2);
player:GetQuest(tonumber(var1)):StartSequence(tonumber(var2));
message = ("changing sequence of quest "..var1.." to "..var2);
else
message = ("phase error: either incorrect ID or quest "..var1.." isn't active on character");
message = ("sequence error: either incorrect ID or quest "..var1.." isn't active on character");
end
else
message = ("error: invalid parameters used");
@ -85,13 +112,13 @@ function onTrigger(player, argc, command, var1, var2, var3)
flagvar = (tonumber(var2));
boolvar = 0;
if var3 == "true" or var3 == "1" or var3 == "on" then
if var3 == "true" or var3 == "1" or var3 == "on" or var3 == "O" then
boolvar = true;
elseif var3 == "false" or var3 == "0" or var3 == "off" then
elseif var3 == "false" or var3 == "0" or var3 == "off" or var3 == "X" then
boolvar = false;
elseif var3 == "flip" or var3 == "toggle" then
if player:HasQuest(questvar) == true then
boolvar = not player:GetQuest(questvar):GetQuestFlag(flagvar);
boolvar = not player:GetQuest(questvar):GetFlag(flagvar);
end
else
message = ("error: flag: boolean not recognized");
@ -99,10 +126,15 @@ function onTrigger(player, argc, command, var1, var2, var3)
return;
end
var4 = player:GetQuest(questvar):GetQuestFlag(flagvar);
var4 = player:GetQuest(questvar):GetFlag(flagvar);
if var4 ~= boolvar then
player:GetQuest(questvar):SetQuestFlag(flagvar, boolvar);
if (boolvar == true) then
player:GetQuest(questvar):SetFlag(flagvar);
else
player:GetQuest(questvar):ClearFlag(flagvar);
end
player:GetQuest(questvar):UpdateENPCs();
player:GetQuest(questvar):SaveData();
if boolvar == true then
message = ("changing flag "..tonumber(var2).." to true on quest "..questvar);
@ -114,6 +146,18 @@ function onTrigger(player, argc, command, var1, var2, var3)
end
else
message = ("error: command "..command.." not recognized");
end
elseif command == "counter" then
if tonumber(var1) and (tonumber(var2) >= 0 and tonumber(var2) <= 4) then
questvar = tonumber(var1);
index = (tonumber(var2));
player:GetQuest(questvar):SetCounter(index, tonumber(var3));
player:GetQuest(questvar):UpdateENPCs();
player:GetQuest(questvar):SaveData();
message = ("changing counter "..tonumber(var2).." to "..var3);
else
message = ("error: command "..command.." not recognized");
end
end
end

View File

@ -161,8 +161,8 @@ ENPC_PROP_CAN_EMOTE = 3;
ENPC_PROP_CAN_NOTICE = 4;
QFLAG_NONE = 0;
QFLAG_MAP = 1;
QFLAG_PLATE = 2;
QFLAG_MAP = 3;
-- Common Helper Functions
function attentionMessage(player, textId, ...)

View File

@ -63,9 +63,6 @@ function onFinish(player, quest)
end
function onSequence(player, quest, sequence)
quest:ClearENpcs();
quest:ClearData();
if (sequence == SEQ_000) then
-- Setup states incase we loaded in.
local rostnsthalFlag = quest:GetFlag(FLAG_SEQ000_MINITUT1) and QFLAG_NONE or QFLAG_PLATE;
@ -73,7 +70,7 @@ function onSequence(player, quest, sequence)
local babyfaceFlag = quest:GetFlag(FLAG_SEQ000_MINITUT3) and QFLAG_NONE or QFLAG_PLATE;
local rostnsthalCanPush = not quest:GetFlag(FLAG_SEQ000_MINITUT0);
local exitCanPush = quest:GetFlags() == 0xF;
local exitFlag = quest:GetFlags() == 0xF and QFLAG_PLATE or QFLAG_NONE;
local exitFlag = quest:GetFlags() == 0xF and QFLAG_MAP or QFLAG_NONE;
quest:AddENpc(WELLTRAVELED_MERCHANT);
quest:AddENpc(TIPSY_ADVENTURER);
@ -90,7 +87,7 @@ function onSequence(player, quest, sequence)
quest:AddENpc(LANKY_TRAVELER);
quest:AddENpc(GRINNING_ADVENTURER);
quest:AddENpc(ROSTNSTHAL, rostnsthalFlag, true, rostnsthalCanPush);
quest:AddENpc(EXIT_TRIGGER, exitFlag, false, false, exitCanPush);
quest:AddENpc(EXIT_TRIGGER, exitFlag, false, exitCanPush);
elseif (sequence == SEQ_005) then
elseif (sequence == SEQ_010) then
quest:AddENpc(HOB);
@ -102,7 +99,7 @@ function onSequence(player, quest, sequence)
quest:AddENpc(WELLTRAVELED_MERCHANT);
quest:AddENpc(VOLUPTUOUS_VIXEN);
quest:AddENpc(LANKY_TRAVELER);
quest:AddENpc(PRIVAREA_PAST_EXIT, QFLAG_NONE, false, false, true);
quest:AddENpc(PRIVAREA_PAST_EXIT, QFLAG_NONE, false, true);
end
end
@ -115,6 +112,8 @@ function onTalk(player, quest, npc)
elseif (sequence == SEQ_010) then
seq010_onTalk(player, quest, npc, classId);
end
quest:UpdateENPCs();
end
function onPush(player, quest, npc)
@ -138,6 +137,8 @@ function onPush(player, quest, npc)
end
end
end
quest:UpdateENPCs();
end
function onNotice(player, quest, target)
@ -147,7 +148,8 @@ function onNotice(player, quest, target)
callClientFunction(player, "delegateEvent", player, quest, "processTtrNomal001withHQ");
end
player:EndEvent();
player:EndEvent();
quest:UpdateENPCs();
end
function seq000_onTalk(player, quest, npc, classId)
@ -162,7 +164,6 @@ function seq000_onTalk(player, quest, npc, classId)
elseif (classId == BABYFACED_ADVENTURER) then
if (not quest:GetFlag(FLAG_SEQ000_MINITUT3)) then
callClientFunction(player, "delegateEvent", player, quest, "processTtrMini003");
quest:UpdateENpc(BABYFACED_ADVENTURER, ENPC_PROP_QFLAG, QFLAG_NONE);
quest:SetFlag(FLAG_SEQ000_MINITUT3);
else
callClientFunction(player, "delegateEvent", player, quest, "processEvent000_8");
@ -178,7 +179,6 @@ function seq000_onTalk(player, quest, npc, classId)
elseif (classId == VOLUPTUOUS_VIXEN) then
if (not quest:GetFlag(FLAG_SEQ000_MINITUT2)) then
callClientFunction(player, "delegateEvent", player, quest, "processTtrMini002");
quest:UpdateENpc(VOLUPTUOUS_VIXEN, ENPC_PROP_QFLAG, QFLAG_NONE);
quest:SetFlag(FLAG_SEQ000_MINITUT2);
else
callClientFunction(player, "delegateEvent", player, quest, "processEvent000_13");
@ -195,25 +195,15 @@ function seq000_onTalk(player, quest, npc, classId)
-- Handle the talk tutorial after the push one.
if (not quest:GetFlag(FLAG_SEQ000_MINITUT0)) then
callClientFunction(player, "delegateEvent", player, quest, "processTtrNomal003");
quest:SetFlag(FLAG_SEQ000_MINITUT0);
quest:UpdateENpc(ROSTNSTHAL, ENPC_PROP_CAN_PUSH, false);
quest:UpdateENpc(ROSTNSTHAL, ENPC_PROP_QFLAG, QFLAG_PLATE);
quest:UpdateENpc(VOLUPTUOUS_VIXEN, ENPC_PROP_QFLAG, QFLAG_PLATE);
quest:UpdateENpc(BABYFACED_ADVENTURER, ENPC_PROP_QFLAG, QFLAG_PLATE);
quest:SetFlag(FLAG_SEQ000_MINITUT0);
else
callClientFunction(player, "delegateEvent", player, quest, "processTtrMini001");
if (not quest:GetFlag(FLAG_SEQ000_MINITUT1)) then
quest:UpdateENpc(ROSTNSTHAL, ENPC_PROP_QFLAG, QFLAG_NONE);
quest:SetFlag(FLAG_SEQ000_MINITUT1);
end
end
end
if (quest:GetFlags() == 0xF) then
quest:UpdateENpc(EXIT_TRIGGER, ENPC_PROP_CAN_PUSH, true);
quest:UpdateENpc(EXIT_TRIGGER, ENPC_PROP_QFLAG, QFLAG_MAP);
end
player:EndEvent();
end
@ -233,7 +223,7 @@ function seq010_onTalk(player, quest, npc, classId)
elseif (classId == HOB) then
local choice = callClientFunction(player, "delegateEvent", player, quest, "processEvent020_9");
if (choice == 1) then
quest:completeAndReplace(110002);
player:ReplaceQuest(quest, "Man0l1");
return;
end
elseif (classId == GERT) then

View File

@ -41,92 +41,20 @@ end
function onSequence(player, quest, sequence)
quest:ClearENpcs();
if (sequence == SEQ_000) then
-- Setup states incase we loaded in.
local rostnsthalFlag = quest:GetFlag(FLAG_SEQ000_MINITUT1) and QFLAG_NONE or QFLAG_PLATE;
local vixenFlag = quest:GetFlag(FLAG_SEQ000_MINITUT2) and QFLAG_NONE or QFLAG_PLATE;
local babyfaceFlag = quest:GetFlag(FLAG_SEQ000_MINITUT3) and QFLAG_NONE or QFLAG_PLATE;
local rostnsthalCanPush = not quest:GetFlag(FLAG_SEQ000_MINITUT0);
local exitCanPush = quest:GetFlags() == 0xF;
local exitFlag = quest:GetFlags() == 0xF and QFLAG_PLATE 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, 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, false, true);
end
end
function onTalk(player, quest, npc)
local sequence = quest:getSequence();
local classId = npc:GetActorClassId();
if (sequence == SEQ_000) then
seq000_onTalk(player, quest, npc, classId);
elseif (sequence == SEQ_010) then
seq010_onTalk(player, quest, npc, classId);
end
end
function onPush(player, quest, npc)
local sequence = quest:getSequence();
local classId = npc:GetActorClassId();
if (sequence == SEQ_000) then
elseif (sequence == SEQ_010) then
end
end
function onNotice(player, quest, target)
local sequence = quest:getSequence();
if (sequence == SEQ_000) then
callClientFunction(player, "delegateEvent", player, quest, "processTtrNomal001withHQ");
end
end
function seq000_onTalk(player, quest, npc, classId)
if (classId == WELLTRAVELED_MERCHANT) then
callClientFunction(player, "delegateEvent", player, quest, "processEvent000_4");
elseif (classId == TIPSY_ADVENTURER) then
callClientFunction(player, "delegateEvent", player, quest, "processEvent000_5");
end
player:EndEvent();
end
function getJournalMapMarkerList(player, quest)
local sequence = quest:getSequence();
if (sequence == SEQ_000) then
-- return MRKR_ROSTNSTHAL, MRKR_BABYFACED_ADVENTURER, MRKR_VOLUPTUOUS_VIXEN;
elseif (sequence == SEQ_010) then
-- return MRKR_HOB;
end
end

View File

@ -1550,23 +1550,20 @@ namespace Meteor.Map.Actors
}
}
public void ReplaceQuest(uint oldId, uint newId)
public void ReplaceQuest(Quest oldQuest, string questCode)
{
if (HasQuest(oldId))
for (int i = 0; i < questScenario.Length; i++)
{
for (int i = 0; i < questScenario.Length; i++)
if (questScenario[i] != null && questScenario[i].Equals(oldQuest))
{
if (questScenario[i] != null && questScenario[i].GetQuestId() == oldId)
{
Quest baseQuest = (Quest) Server.GetStaticActors((0xA0F00000 | newId));
playerWork.questScenario[i] = (0xA0F00000 | newId);
questScenario[i] = new Quest(this, baseQuest);
Database.SaveQuest(this, questScenario[i]);
SendQuestClientUpdate(i);
break;
}
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)
@ -1638,6 +1635,11 @@ namespace Meteor.Map.Actors
return false;
}
public bool HasQuest(Quest quest)
{
return HasQuest(quest.className);
}
public bool HasGuildleve(uint id)
{
for (int i = 0; i < work.guildleveId.Length; i++)

View File

@ -46,7 +46,7 @@ namespace Meteor.Map.actors.director
private Coroutine currentCoroutine;
public Director(uint id, Area zone, string directorPath, bool hasContentGroup, params object[] args)
: base((6 << 28 | zone.CurrentArea.ZoneId << 19 | (uint)id))
: base((6 << 28 | zone.ZoneId << 19 | (uint)id + 2))
{
directorId = id;
CurrentArea = zone;

View File

@ -51,6 +51,24 @@ namespace Meteor.Map.Actors
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
@ -73,65 +91,44 @@ namespace Meteor.Map.Actors
private Player Owner;
private ushort currentSequence;
private QuestData data = new QuestData();
private Dictionary<uint, ENpcQuestInstance> ActiveENpcs = new Dictionary<uint, ENpcQuestInstance>();
private bool dataDirty = false;
private Dictionary<uint, ENpcQuestInstance> CurrentENPCs = new Dictionary<uint, ENpcQuestInstance>();
private Dictionary<uint, ENpcQuestInstance> OldENPCs = new Dictionary<uint, ENpcQuestInstance>();
public void AddENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isEmoteEnabled = false, bool isPushEnabled = false, bool isSpawned = false)
public void AddENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false)
{
if (ActiveENpcs.ContainsKey(classId))
return;
ENpcQuestInstance instanceUpdated = null;
ENpcQuestInstance instance = new ENpcQuestInstance(classId, flagType, isSpawned, isTalkEnabled, isEmoteEnabled, isPushEnabled);
ActiveENpcs.Add(classId, instance);
Owner.playerSession.UpdateQuestNpcInInstance(instance);
}
public void ClearENpcs()
{
foreach (ENpcQuestInstance instance in ActiveENpcs.Values)
Owner.playerSession.UpdateQuestNpcInInstance(instance, true);
ActiveENpcs.Clear();
}
public void UpdateENpc(uint classId, ENpcProperty property, object value)
{
if (!ActiveENpcs.ContainsKey(classId))
return;
ENpcQuestInstance instance = ActiveENpcs[classId];
switch (property)
if (OldENPCs.ContainsKey(classId))
{
case ENpcProperty.QuestFlag:
if (value is double)
instance.questFlagType = (byte)(double)value;
else if (value is int)
instance.questFlagType = (byte)value;
break;
case ENpcProperty.CanTalk:
instance.isTalkEnabled = (bool)value;
break;
case ENpcProperty.CanPush:
instance.isPushEnabled = (bool)value;
break;
case ENpcProperty.CanEmote:
instance.isEmoteEnabled = (bool)value;
break;
if (OldENPCs[classId].IsChanged(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned))
{
instanceUpdated = OldENPCs[classId];
instanceUpdated.Update(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned);
CurrentENPCs.Add(classId, instanceUpdated);
}
OldENPCs.Remove(classId);
}
else
{
instanceUpdated = new ENpcQuestInstance(classId, flagType, isSpawned, isTalkEnabled, isEmoteEnabled, isPushEnabled);
CurrentENPCs.Add(classId, instanceUpdated);
}
Owner.playerSession.UpdateQuestNpcInInstance(instance, false);
if (instanceUpdated != null)
Owner.playerSession.UpdateQuestNpcInInstance(instanceUpdated);
}
public ENpcQuestInstance GetENpcInstance(uint classId)
{
if (ActiveENpcs.ContainsKey(classId))
return ActiveENpcs[classId];
if (CurrentENPCs.ContainsKey(classId))
return CurrentENPCs[classId];
return null;
}
public void OnTalk(Player caller, Npc npc)
{
LuaEngine.GetInstance().CallLuaFunction(caller, this, "onTalk", true, npc);
LuaEngine.GetInstance().CallLuaFunction(caller, this, "onTalk", true, npc);
}
public void OnEmote(Player caller, Npc npc, Command command)
@ -154,11 +151,25 @@ 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 || ActiveENpcs.ContainsKey(npc.GetActorClassId());
return scriptReturned || CurrentENPCs.ContainsKey(npc.GetActorClassId());
}
@ -195,7 +206,8 @@ namespace Meteor.Map.Actors
Owner.SendGameMessage(Server.GetWorldManager().GetActor(), 25116, 0x20, (object)GetQuestId());
currentSequence = sequence;
LuaEngine.GetInstance().CallLuaFunction(Owner, this, "onSequence", false, currentSequence);
dataDirty = true;
UpdateENPCs();
}
public void ClearData()
@ -206,70 +218,88 @@ namespace Meteor.Map.Actors
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++;
break;
return;
case 1:
data.counter2++;
break;
return;
case 2:
data.counter3++;
break;
return;
case 3:
data.counter4++;
break;
}
return;
}
dataDirty = false;
}
public void DecCounter(int num)
{
dataDirty = true;
switch (num)
{
case 0:
data.counter1--;
break;
return;
case 1:
data.counter2--;
break;
return;
case 2:
data.counter3--;
break;
return;
case 3:
data.counter4--;
break;
return;
}
dataDirty = false;
}
public void SetCounter(int num, ushort value)
{
dataDirty = true;
switch (num)
{
case 0:
data.counter1 = value;
break;
return;
case 1:
data.counter2 = value;
break;
return;
case 2:
data.counter3 = value;
break;
return;
case 3:
data.counter4 = value;
break;
return;
}
dataDirty = false;
}
public bool GetFlag(int index)

View File

@ -39,6 +39,9 @@ using Meteor.Map.actors.chara.ai;
using Meteor.Map.actors.chara.ai.controllers;
using Meteor.Map.DataObjects;
using Meteor.Map.actors.chara.player;
using Meteor.Map.Actors.Chara;
using Meteor.Map.dataobjects.chara;
using Meteor.Map.actors.chara;
namespace Meteor.Map.lua
{
@ -69,12 +72,16 @@ namespace Meteor.Map.lua
UserData.RegisterType<LuaEngine>();
UserData.RegisterType<Player>();
UserData.RegisterType<CharaWork>();
UserData.RegisterType<ParameterSave>();
UserData.RegisterType<PlayerWork>();
UserData.RegisterType<Command>();
UserData.RegisterType<Npc>();
UserData.RegisterType<Quest>();
UserData.RegisterType<Zone>();
UserData.RegisterType<InventoryItem>();
UserData.RegisterType<ItemPackage>();
UserData.RegisterType<ReferencedItemPackage>();
UserData.RegisterType<PrivateArea>();
UserData.RegisterType<PrivateAreaContent>();
UserData.RegisterType<Director>();