mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-06-09 22:14:39 +02:00
Finished quest state system idea
This commit is contained in:
parent
aae051d73f
commit
1523ae200b
21 changed files with 720 additions and 373 deletions
|
@ -28,48 +28,8 @@ namespace Meteor.Map.Actors
|
|||
{
|
||||
class Quest : Actor
|
||||
{
|
||||
public const ushort SEQ_NOT_STARTED = ushort.MaxValue;
|
||||
|
||||
public enum QuestFlag { None = 0, Map = 1, Plate = 2 }
|
||||
public enum ENpcProperty { QuestFlag = 0, CanTalk = 1, CanPush = 2, CanEmote = 3, CanNotice = 4}
|
||||
|
||||
public class ENpcQuestInstance
|
||||
{
|
||||
public readonly uint actorClassId;
|
||||
public byte questFlagType { set; get; }
|
||||
public bool isSpawned { set; get; }
|
||||
public bool isTalkEnabled { set; get; }
|
||||
public bool isEmoteEnabled { set; get; }
|
||||
public bool isPushEnabled { set; get; }
|
||||
|
||||
public ENpcQuestInstance(uint actorClassId, byte questFlagType, bool isSpawned, bool isTalkEnabled, bool isEmoteEnabled, bool isPushEnabled)
|
||||
{
|
||||
this.actorClassId = actorClassId;
|
||||
this.questFlagType = questFlagType;
|
||||
this.isSpawned = isSpawned;
|
||||
this.isTalkEnabled = isTalkEnabled;
|
||||
this.isEmoteEnabled = isEmoteEnabled;
|
||||
this.isPushEnabled = isPushEnabled;
|
||||
}
|
||||
|
||||
public bool IsChanged(byte flagType, bool isTalkEnabled, bool isPushEnabled, bool isEmoteEnabled, bool isSpawned)
|
||||
{
|
||||
return flagType != this.questFlagType
|
||||
|| isTalkEnabled != this.isTalkEnabled
|
||||
|| isPushEnabled != this.isPushEnabled
|
||||
|| isEmoteEnabled != this.isEmoteEnabled
|
||||
|| isSpawned != this.isSpawned;
|
||||
}
|
||||
|
||||
public void Update(byte flagType, bool isTalkEnabled, bool isPushEnabled, bool isEmoteEnabled, bool isSpawned)
|
||||
{
|
||||
this.questFlagType = flagType;
|
||||
this.isSpawned = isSpawned;
|
||||
this.isTalkEnabled = isTalkEnabled;
|
||||
this.isEmoteEnabled = isEmoteEnabled;
|
||||
this.isPushEnabled = isPushEnabled;
|
||||
}
|
||||
}
|
||||
public const ushort SEQ_NOT_STARTED = 65535;
|
||||
public const ushort SEQ_COMPLETED = 65534;
|
||||
|
||||
private struct QuestData
|
||||
{
|
||||
|
@ -88,43 +48,37 @@ namespace Meteor.Map.Actors
|
|||
}
|
||||
}
|
||||
|
||||
// This is only set on instance quests (non static)
|
||||
private Player Owner;
|
||||
private ushort currentSequence;
|
||||
private QuestData data = new QuestData();
|
||||
private QuestState QuestState;
|
||||
private QuestData Data;
|
||||
private bool dataDirty = false;
|
||||
private Dictionary<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 isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false)
|
||||
public void SetENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false)
|
||||
{
|
||||
ENpcQuestInstance instanceUpdated = null;
|
||||
|
||||
if (OldENPCs.ContainsKey(classId))
|
||||
{
|
||||
if (OldENPCs[classId].IsChanged(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned))
|
||||
{
|
||||
OldENPCs[classId].Update(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned);
|
||||
instanceUpdated = OldENPCs[classId];
|
||||
}
|
||||
|
||||
CurrentENPCs.Add(classId, OldENPCs[classId]);
|
||||
OldENPCs.Remove(classId);
|
||||
}
|
||||
else
|
||||
{
|
||||
instanceUpdated = new ENpcQuestInstance(classId, flagType, isSpawned, isTalkEnabled, isEmoteEnabled, isPushEnabled);
|
||||
CurrentENPCs.Add(classId, instanceUpdated);
|
||||
}
|
||||
|
||||
if (instanceUpdated != null)
|
||||
Owner.playerSession.UpdateQuestNpcInInstance(instanceUpdated);
|
||||
if (QuestState != null)
|
||||
QuestState.AddENpc(classId, flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned);
|
||||
}
|
||||
|
||||
public ENpcQuestInstance GetENpcInstance(uint classId)
|
||||
|
||||
public void UpdateENPCs()
|
||||
{
|
||||
if (CurrentENPCs.ContainsKey(classId))
|
||||
return CurrentENPCs[classId];
|
||||
return null;
|
||||
if (dataDirty)
|
||||
{
|
||||
if (QuestState != null)
|
||||
QuestState.UpdateState();
|
||||
dataDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public QuestState GetQuestState()
|
||||
{
|
||||
return QuestState;
|
||||
}
|
||||
|
||||
public bool IsInstance()
|
||||
{
|
||||
return Owner != null;
|
||||
}
|
||||
|
||||
public void OnTalk(Player caller, Npc npc)
|
||||
|
@ -152,25 +106,11 @@ namespace Meteor.Map.Actors
|
|||
LuaEngine.GetInstance().CallLuaFunction(caller, this, "onNpcLS", true, npcLSId);
|
||||
}
|
||||
|
||||
public void UpdateENPCs()
|
||||
{
|
||||
if (dataDirty)
|
||||
{
|
||||
OldENPCs = CurrentENPCs;
|
||||
CurrentENPCs = new Dictionary<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());
|
||||
return scriptReturned || QuestState.HasENpc(npc.GetActorClassId());
|
||||
}
|
||||
|
||||
|
||||
|
@ -213,14 +153,14 @@ namespace Meteor.Map.Actors
|
|||
|
||||
public void ClearData()
|
||||
{
|
||||
data.flags = data.counter1 = data.counter2 = data.counter3 = data.counter4 = 0;
|
||||
Data.flags = Data.counter1 = Data.counter2 = Data.counter3 = Data.counter4 = 0;
|
||||
}
|
||||
|
||||
public void SetFlag(int index)
|
||||
{
|
||||
if (index >= 0 && index < 32)
|
||||
{
|
||||
data.flags |= (uint)(1 << index);
|
||||
Data.flags |= (uint)(1 << index);
|
||||
dataDirty = true;
|
||||
}
|
||||
}
|
||||
|
@ -229,7 +169,7 @@ namespace Meteor.Map.Actors
|
|||
{
|
||||
if (index >= 0 && index < 32)
|
||||
{
|
||||
data.flags &= (uint)~(1 << index);
|
||||
Data.flags &= (uint)~(1 << index);
|
||||
dataDirty = true;
|
||||
}
|
||||
}
|
||||
|
@ -241,16 +181,16 @@ namespace Meteor.Map.Actors
|
|||
switch (num)
|
||||
{
|
||||
case 0:
|
||||
data.counter1++;
|
||||
Data.counter1++;
|
||||
return;
|
||||
case 1:
|
||||
data.counter2++;
|
||||
Data.counter2++;
|
||||
return;
|
||||
case 2:
|
||||
data.counter3++;
|
||||
Data.counter3++;
|
||||
return;
|
||||
case 3:
|
||||
data.counter4++;
|
||||
Data.counter4++;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -264,16 +204,16 @@ namespace Meteor.Map.Actors
|
|||
switch (num)
|
||||
{
|
||||
case 0:
|
||||
data.counter1--;
|
||||
Data.counter1--;
|
||||
return;
|
||||
case 1:
|
||||
data.counter2--;
|
||||
Data.counter2--;
|
||||
return;
|
||||
case 2:
|
||||
data.counter3--;
|
||||
Data.counter3--;
|
||||
return;
|
||||
case 3:
|
||||
data.counter4--;
|
||||
Data.counter4--;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -287,16 +227,16 @@ namespace Meteor.Map.Actors
|
|||
switch (num)
|
||||
{
|
||||
case 0:
|
||||
data.counter1 = value;
|
||||
Data.counter1 = value;
|
||||
return;
|
||||
case 1:
|
||||
data.counter2 = value;
|
||||
Data.counter2 = value;
|
||||
return;
|
||||
case 2:
|
||||
data.counter3 = value;
|
||||
Data.counter3 = value;
|
||||
return;
|
||||
case 3:
|
||||
data.counter4 = value;
|
||||
Data.counter4 = value;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -306,13 +246,13 @@ namespace Meteor.Map.Actors
|
|||
public bool GetFlag(int index)
|
||||
{
|
||||
if (index >= 0 && index < 32)
|
||||
return (data.flags & (uint) (1 << index)) != 0;
|
||||
return (Data.flags & (uint) (1 << index)) != 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public uint GetFlags()
|
||||
{
|
||||
return data.flags;
|
||||
return Data.flags;
|
||||
}
|
||||
|
||||
public ushort GetCounter(int num)
|
||||
|
@ -320,13 +260,13 @@ namespace Meteor.Map.Actors
|
|||
switch (num)
|
||||
{
|
||||
case 0:
|
||||
return data.counter1;
|
||||
return Data.counter1;
|
||||
case 1:
|
||||
return data.counter2;
|
||||
return Data.counter2;
|
||||
case 2:
|
||||
return data.counter3;
|
||||
return Data.counter3;
|
||||
case 3:
|
||||
return data.counter4;
|
||||
return Data.counter4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -354,12 +294,8 @@ namespace Meteor.Map.Actors
|
|||
className = baseQuest.className;
|
||||
classPath = baseQuest.classPath;
|
||||
currentSequence = sequence;
|
||||
data = new QuestData(flags, counter1, counter2, counter3);
|
||||
|
||||
if (currentSequence == SEQ_NOT_STARTED)
|
||||
LuaEngine.GetInstance().CallLuaFunction(Owner, this, "onStart", false);
|
||||
else
|
||||
StartSequence(currentSequence);
|
||||
QuestState = new QuestState(owner, this);
|
||||
Data = new QuestData(flags, counter1, counter2, counter3);
|
||||
}
|
||||
|
||||
public uint GetQuestId()
|
||||
|
@ -367,17 +303,34 @@ namespace Meteor.Map.Actors
|
|||
return Id & 0xFFFFF;
|
||||
}
|
||||
|
||||
public void DoAccept()
|
||||
{
|
||||
if (currentSequence == SEQ_NOT_STARTED)
|
||||
LuaEngine.GetInstance().CallLuaFunction(Owner, this, "onStart", false);
|
||||
else
|
||||
StartSequence(currentSequence);
|
||||
}
|
||||
|
||||
public void DoComplete()
|
||||
{
|
||||
LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "onFinish", true);
|
||||
Owner.SendDataPacket("attention", Server.GetWorldManager().GetActor(), "", 25225, (object)GetQuestId());
|
||||
Owner.SendGameMessage(Server.GetWorldManager().GetActor(), 25225, 0x20, (object)GetQuestId());
|
||||
currentSequence = SEQ_COMPLETED;
|
||||
}
|
||||
|
||||
public void DoAbandon()
|
||||
{
|
||||
LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, this, "onFinish", false);
|
||||
Owner.SendGameMessage(Owner, Server.GetWorldManager().GetActor(), 25236, 0x20, (object)GetQuestId());
|
||||
currentSequence = SEQ_NOT_STARTED;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Quest quest)
|
||||
return quest.Id == this.Id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
112
Map Server/Actors/Quest/QuestState.cs
Normal file
112
Map Server/Actors/Quest/QuestState.cs
Normal file
|
@ -0,0 +1,112 @@
|
|||
using Meteor.Map.lua;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Meteor.Map.Actors
|
||||
{
|
||||
class QuestState
|
||||
{
|
||||
public enum QuestFlag { None = 0, Map = 1, Plate = 2 }
|
||||
|
||||
public class QuestENpc
|
||||
{
|
||||
public readonly uint actorClassId;
|
||||
public byte questFlagType { set; get; }
|
||||
public bool isSpawned { set; get; }
|
||||
public bool isTalkEnabled { set; get; }
|
||||
public bool isEmoteEnabled { set; get; }
|
||||
public bool isPushEnabled { set; get; }
|
||||
|
||||
public QuestENpc(uint actorClassId, byte questFlagType, bool isSpawned, bool isTalkEnabled, bool isEmoteEnabled, bool isPushEnabled)
|
||||
{
|
||||
this.actorClassId = actorClassId;
|
||||
this.questFlagType = questFlagType;
|
||||
this.isSpawned = isSpawned;
|
||||
this.isTalkEnabled = isTalkEnabled;
|
||||
this.isEmoteEnabled = isEmoteEnabled;
|
||||
this.isPushEnabled = isPushEnabled;
|
||||
}
|
||||
|
||||
public bool IsChanged(byte flagType, bool isTalkEnabled, bool isPushEnabled, bool isEmoteEnabled, bool isSpawned)
|
||||
{
|
||||
return flagType != this.questFlagType
|
||||
|| isTalkEnabled != this.isTalkEnabled
|
||||
|| isPushEnabled != this.isPushEnabled
|
||||
|| isEmoteEnabled != this.isEmoteEnabled
|
||||
|| isSpawned != this.isSpawned;
|
||||
}
|
||||
|
||||
public void Update(byte flagType, bool isTalkEnabled, bool isPushEnabled, bool isEmoteEnabled, bool isSpawned)
|
||||
{
|
||||
this.questFlagType = flagType;
|
||||
this.isSpawned = isSpawned;
|
||||
this.isTalkEnabled = isTalkEnabled;
|
||||
this.isEmoteEnabled = isEmoteEnabled;
|
||||
this.isPushEnabled = isPushEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
private Player Owner;
|
||||
private Quest Parent;
|
||||
private Dictionary<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;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
public void AddENpc(uint classId, byte flagType = 0, bool isTalkEnabled = true, bool isPushEnabled = false, bool isEmoteEnabled = false, bool isSpawned = false)
|
||||
{
|
||||
QuestENpc instanceUpdated = null;
|
||||
|
||||
if (OldENPCs.ContainsKey(classId))
|
||||
{
|
||||
if (OldENPCs[classId].IsChanged(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned))
|
||||
{
|
||||
OldENPCs[classId].Update(flagType, isTalkEnabled, isPushEnabled, isEmoteEnabled, isSpawned);
|
||||
instanceUpdated = OldENPCs[classId];
|
||||
}
|
||||
|
||||
CurrentENPCs.Add(classId, OldENPCs[classId]);
|
||||
OldENPCs.Remove(classId);
|
||||
}
|
||||
else
|
||||
{
|
||||
instanceUpdated = new QuestENpc(classId, flagType, isSpawned, isTalkEnabled, isEmoteEnabled, isPushEnabled);
|
||||
CurrentENPCs.Add(classId, instanceUpdated);
|
||||
}
|
||||
|
||||
if (instanceUpdated != null)
|
||||
Owner.playerSession.UpdateQuestNpcInInstance(instanceUpdated);
|
||||
}
|
||||
|
||||
public QuestENpc GetENpc(uint classId)
|
||||
{
|
||||
if (CurrentENPCs.ContainsKey(classId))
|
||||
return CurrentENPCs[classId];
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool HasENpc(uint classId)
|
||||
{
|
||||
return CurrentENPCs.ContainsKey(classId);
|
||||
}
|
||||
|
||||
public void UpdateState()
|
||||
{
|
||||
ushort currentSeq = Parent.GetSequence();
|
||||
OldENPCs = CurrentENPCs;
|
||||
CurrentENPCs = new Dictionary<uint, QuestENpc>();
|
||||
LuaEngine.GetInstance().CallLuaFunctionForReturn(Owner, Parent, "onStateChange", false, currentSeq);
|
||||
foreach (var enpc in OldENPCs)
|
||||
Owner.playerSession.UpdateQuestNpcInInstance(enpc.Value);
|
||||
OldENPCs = null;
|
||||
}
|
||||
}
|
||||
}
|
108
Map Server/Actors/Quest/QuestStateManager.cs
Normal file
108
Map Server/Actors/Quest/QuestStateManager.cs
Normal file
|
@ -0,0 +1,108 @@
|
|||
using Meteor.Common;
|
||||
using Meteor.Map.DataObjects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Meteor.Map.Actors
|
||||
{
|
||||
class QuestStateManager
|
||||
{
|
||||
private const int SCENARIO_START = 110001;
|
||||
private const int SCENARIO_MAX = 2048;
|
||||
|
||||
private readonly Player player;
|
||||
private readonly Bitstream AvailableQuestsBitfield = new Bitstream(SCENARIO_MAX);
|
||||
private readonly Bitstream MinLevelBitfield = new Bitstream(SCENARIO_MAX);
|
||||
private readonly Bitstream PrereqBitfield = new Bitstream(SCENARIO_MAX, true);
|
||||
private readonly Bitstream GCRankBitfield = new Bitstream(SCENARIO_MAX, true);
|
||||
|
||||
private List<Quest> ActiveQuests = new List<Quest>();
|
||||
|
||||
public QuestStateManager(Player player)
|
||||
{
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Init MinLv
|
||||
QuestData[] minLvl = Server.GetQuestGamedataByMaxLvl(player.GetHighestLevel(), true);
|
||||
foreach (var questData in minLvl)
|
||||
MinLevelBitfield.Set(questData.Id - SCENARIO_START);
|
||||
|
||||
// Init Prereq
|
||||
Bitstream completed = new Bitstream(player.playerWork.questScenarioComplete);
|
||||
foreach (var questData in Server.GetQuestGamedataAllPrerequisite())
|
||||
{
|
||||
if (completed.Get(((Quest)Server.GetStaticActors(0xA0F00000 | questData.PrerequisiteQuest)).GetQuestId() - SCENARIO_START))
|
||||
PrereqBitfield.Set(questData.Id - SCENARIO_START);
|
||||
else
|
||||
PrereqBitfield.Clear(questData.Id - SCENARIO_START);
|
||||
}
|
||||
ComputeAvailable();
|
||||
}
|
||||
|
||||
public void UpdateLevel(int level)
|
||||
{
|
||||
QuestData[] updated = Server.GetQuestGamedataByMaxLvl(level);
|
||||
foreach (var questData in updated)
|
||||
MinLevelBitfield.Set(questData.Id - SCENARIO_START);
|
||||
ComputeAvailable();
|
||||
}
|
||||
|
||||
public void UpdateQuestComplete(Quest quest)
|
||||
{
|
||||
QuestData[] updated = Server.GetQuestGamedataByPrerequisite(quest.GetQuestId());
|
||||
foreach (var questData in updated)
|
||||
PrereqBitfield.Set(questData.Id - SCENARIO_START);
|
||||
ComputeAvailable();
|
||||
}
|
||||
|
||||
public void QuestAdded(Quest quest)
|
||||
{
|
||||
ActiveQuests.Remove(quest);
|
||||
}
|
||||
|
||||
private void ComputeAvailable()
|
||||
{
|
||||
Bitstream result = new Bitstream(player.playerWork.questScenarioComplete);
|
||||
result.NOT();
|
||||
result.AND(MinLevelBitfield);
|
||||
result.AND(PrereqBitfield);
|
||||
result.AND(GCRankBitfield);
|
||||
|
||||
Bitstream difference = AvailableQuestsBitfield.Copy();
|
||||
difference.XOR(result);
|
||||
byte[] diffBytes = difference.GetBytes();
|
||||
|
||||
for (int i = 0; i < diffBytes.Length; i++)
|
||||
{
|
||||
if (diffBytes[i] == 0)
|
||||
continue;
|
||||
for (int shift = 0; shift < 8; shift++)
|
||||
{
|
||||
if ((diffBytes[i] >> shift & 1) == 1)
|
||||
{
|
||||
int index = i * 8 + shift;
|
||||
Quest quest = (Quest)Server.GetStaticActors(0xA0F00000 | (SCENARIO_START + (uint)index));
|
||||
if (!AvailableQuestsBitfield.Get(index))
|
||||
ActiveQuests.Add(new Quest(player, quest));
|
||||
else
|
||||
ActiveQuests.Remove(quest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvailableQuestsBitfield.SetTo(result);
|
||||
}
|
||||
|
||||
public Quest[] GetQuestsForNpc(Npc npc)
|
||||
{
|
||||
return ActiveQuests.FindAll(quest => quest.IsQuestENPC(player, npc)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue