I'm writing a scripting interface for a server emulator for NPC chats. The player can initiate a NPC chat by clicking on the NPC. The NPC can send text dialogs. Those text dialogs also contain a End Chat
button to end the chat before the script finishes execution, or the player can continue with the text dialogs normally until they end.
When the player interrupts the chat, a special packet is sent.
I've created a class called WaitableResult
which takes advantage of ManualResetEvent
to block the current thread until given result and then returns the result:
public sealed class WaitableResult<T> where T : struct
{
public T Value { get; private set; }
private ManualResetEvent mEvent;
public WaitableResult()
{
mEvent = new ManualResetEvent(false);
}
public void Wait()
{
mEvent.WaitOne();
}
public void Set(T value)
{
mEvent.Set();
this.Value = value;
}
}
This is my script classes:
internal sealed class NpcScript : ScriptBase
{
public WaitableResult<bool> BoolResult { get; private set; }
private Npc mNpc;
private Player mPlayer;
public NpcScript(Npc npc, Player player)
: base(string.Format(@"..\..\scripts\npcs\{0}.lua", npc.Script), true)
{
mNpc = npc;
mPlayer = player;
mPlayer.NpcConversation = this;
this.Expose("answer_no", false);
this.Expose("answer_yes", true);
this.Expose("answer_decline", false);
this.Expose("answer_accept", true);
this.Expose("say", new Func<string, bool>(this.Say));
this.Expose("askYesNo", new Func<string, bool>(this.AskYesNo));
}
public override void Dispose()
{
base.Dispose();
mPlayer.NpcConversation = null;
}
private bool Say(string text)
{
this.BoolResult = new WaitableResult<bool>();
using (OutPacket outPacket = mNpc.GetDialogPacket(ENpcDialogType.Standard, text, 0, 0))
{
mPlayer.Client.SendPacket(outPacket);
}
this.BoolResult.Wait();
return this.BoolResult.Value;
}
private bool AskYesNo(string text)
{
this.BoolResult = new WaitableResult<bool>();
using (OutPacket outPacket = mNpc.GetDialogPacket(ENpcDialogType.YesNo, text))
{
mPlayer.Client.SendPacket(outPacket);
}
this.BoolResult.Wait();
return this.BoolResult.Value;
}
}
public abstract class ScriptBase
{
private string mPath;
private Thread mThread;
private MoonSharp.Interpreter.Script mScript;
public ScriptBase(string path, bool useThread = false, CoreModules modules = CoreModules.None)
{
mPath = path;
if (useThread) mThread = new Thread(new ThreadStart(() => mScript.DoFile(mPath)));
mScript = new MoonSharp.Interpreter.Script(modules);
}
public void Execute()
{
if (mThread != null)
{
mThread.Start();
}
else
{
mScript.DoFile(mPath);
}
}
public virtual void Dispose()
{
if (mThread != null)
{
mThread.Abort();
mThread = null;
}
}
protected void Expose(string key, object value)
{
mScript.Globals[key] = value;
}
}
And here's an example of a script:
say('test')
say('some more stuff')
say('good bye')
When a player initiates a chat with a NPC and finishes it without interrupting it (aka closing it using the End Chat
button), the thread should be aborted by itself (as it finished all it's instructions).
However, when a player aborts the chat before it finishes execution, I'm calling the Dispose
button to manually abort the thread - but I'm not sure it's the right way to do it.
My memory usage also increases by 1 MB everytime a player starts a chat, so that's also kind of weird.