Use a script engine, I was writing one some time ago for a C# script engine specific for NPC's and commands, but my laptop broke some days ago, so I haven't taken backup. I might make a new later today or something that you could take a look at :P
If you want to try yourself then my script engine basically loaded all the npc scripts at startup and compiled them in memory so they could interact with the actual source.
Then once in a while (running in a new thread) it would check if there was any npc scripts that was changed. This could be done 2 ways, check if the md5 hash is not the same or if the last edited date is different. Then it would update the npc scripts that have been changed.
It wn't make a difference in speed at all since the methods for each npc is compiled, it may be a VERY LITTLE slower than actually coding the npcs is the source, but it's far faster than having scripts and parsing it.
This could be done with the same way binaries are doing their npc scripts (through database) as well, but you'd just load the actions and convert it to actual code that can be compiled.
You could also just parse the npc scripts from the database, but I'd rather not because the speed will be far slower.
If you want to know how to compile in memory here is an example from my very old game engine for Sdl in c#.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.CSharp;
using System.Reflection;
using System.CodeDom.Compiler;
namespace Sdl_Game_Project.ScriptEngine
{
/// <summary>
/// A C# script engine.
/// Notice: This will be optimized to support a new thread for better performance.
/// </summary>
public class CSharpEngine
{
/// <summary>
/// Handling a script file.
/// </summary>
/// <param name="scriptFile">The file to handle.</param>
/// <param name="methodName">The method to invoke.</param>
/// <param name="nameSpace">The namespace.</param>
/// <param name="className">The class.</param>
/// <param name="Params">The parameters of the methods. Null if none parameters.</param>
public static void Handle(string scriptFile, string methodName, string nameSpace, string className, object[] Params = null)
{
if (!scriptFile.EndsWith(".cs"))
{
throw new Exception("Make sure the script file is a CSharp (C#) file.");
}
string code = File.ReadAllText(scriptFile);
Dictionary<string, string> providerOptions = new Dictionary<string, string>()
{
{"CompilerVersion", Sdl.GameCore.ScriptFramework}
};
CompilerParameters compilerParams = new CompilerParameters()
{
GenerateInMemory = true,
GenerateExecutable = false
};
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
compilerParams.ReferencedAssemblies.Add(assembly.Location);
}
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, code);
if (results == null)
throw new Exception("Unknown compiler error.");
if (results.Errors.Count != 0)
throw new Exception("Failed to compile the game script.");
MethodInfo method = FindMethod(results.CompiledAssembly, methodName, nameSpace, className);
if (method != null)
method.Invoke(null, (Params == null ? null : Params));
}
/// <summary>
/// Finds a method within an assembly.
/// </summary>
/// <param name="assembly">The assembly</param>
/// <param name="methodName">The method to find.</param>
/// <param name="nameSpace">The namespace.</param>
/// <param name="className">The class.</param>
/// <returns>Returns null if no methods were found.</returns>
internal static MethodInfo FindMethod(Assembly assembly, string methodName, string nameSpace, string className)
{
foreach (Type type in assembly.GetTypes())
{
if (type.Namespace == nameSpace && type.IsClass && type.Name == className)
{
foreach (MethodInfo method in type.GetMethods())
{
if (method.IsStatic && method.Name == methodName)
{
return method;
}
}
}
}
return null;
}
}
}