muddesigner/MudEngine/Scripting/ScriptEngine.cs
Scionwest_cp c3c2d22ec7 MudEngine:
- Login command now automatically disconnects a user if they are currently logged into the server when another user attempts to login with the same credentials.
 - Login command now loads saved players when a character name is entered that has previously been saved.
 - FileManager.GetData() now supports ignoring simi-colons in data files. These can be used as comments if needed.
 - MudEngine.GameManagement.Game.TimeOfDayOptions has been removed from the Game Type. It is now just MudEngine.GameManagement.TimeOfDayOptions.
 - BaseCharacter will no longer try to save the character if the supplied filename is blank
 - BaseCharacter will now send a disconnect message of 'Goodbye!' upon the player being disconnected from the server.
 - ScriptEngine.ScriptPath now returns a absolute path.
 - Script File compilation within ScriptEngine.Initialization is now supported. This allows developers to write their MUD's using Scripts and letting the server compile them during server startup rather than use the ScriptCompiler.exe to pre-compile scripts now.
 - Custom Game Types are now supported. Only 1 Type may inherit from MudEngine.GameManagement.Game at a time. To use, create a file, write a script that inherits from Game and run the server.
 - ScriptEngine.GetObjectOf(string) method adding. Returns a object that inherits from the Type supplied in the parameter.
 - Deleted StartupObject.cs
 - Deleted Startup.dat 

MudOfflineExample:
 - Updated to reflect the TimeOfDayOptions change in MudEngine.csproj

MudServer:
 - Added MyGame.cs inside Debug/bin/scripts. This is a example script showing how to inherit from Game and write a custom Game Type. This Type is used when the Server runs rather than the Engine Game Type.
 - Server startup re-wrote to compile scripts during startup, scan them via the script engine for any custom Types that inherit from Game, and use the if needed instead of the default Engine Type.
 - Server now uses a Settings.ini file to allow configuration of the Script Engine by users. Provides them the ability to now customize where scripts are stored and their file extension.
 - Deleted Scripts.dll, as the Server now generates one at runtime each time it is ran.

As of this commit, users will not need to use C# to compile their MUD's any longer. Compile the Server and run it. Scripts are now fully implemented (however not fully tested).
2010-08-05 17:46:30 -07:00

314 lines
11 KiB
C#

//Microsoft .NET Framework
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
#if !MOBILE
using Microsoft.CSharp;
#endif
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Reflection;
using MudEngine.FileSystem;
using MudEngine.GameObjects;
using MudEngine.GameObjects.Characters;
using MudEngine.GameManagement;
namespace MudEngine.Scripting
{
public class ScriptEngine
{
public enum ScriptTypes
{
Assembly,
SourceFiles
}
/// <summary>
/// Path to the the script files directory
/// </summary>
public string ScriptPath
{
get
{
return _ScriptPath;
}
set
{
_ScriptPath = Path.Combine(FileManager.GetDataPath(SaveDataTypes.Root), value);
}
}
string _ScriptPath;
public string InstallPath { get; private set; }
public GameObjectCollection ObjectCollection { get; private set; }
/// <summary>
/// Collection of currently loaded objects created from compiled scripts
/// </summary>
public List<GameObject> GameObjects { get; private set; }
/// <summary>
/// Collection of currently loaded game commecnts that can be used. These must be compiled scripts inheriting from IGameCommand
/// </summary>
public List<IGameCommand> GameCommands { get; private set; }
/// <summary>
/// File Extension for the scripts
/// </summary>
public string ScriptExtension { get; set; }
/// <summary>
/// Error Messages logged during script compilation
/// </summary>
public string ErrorMessage
{
get
{
string errorMessages = "Script Compilation Failed!\n";
//Construct our error message.
foreach (string error in _ErrorMessages)
errorMessages += error + "\n";
return errorMessages;
}
private set
{
_ErrorMessages = new string[] { value };
}
}
private ScriptTypes _ScriptTypes;
private Assembly _ScriptAssembly;
private List<Assembly> _AssemblyCollection;
private string[] _ErrorMessages;
Game _Game;
public ScriptEngine(Game game) : this(game, ScriptTypes.Assembly)
{
_Game = game;
}
/// <summary>
/// Instances a new copy of the script engine
/// </summary>
/// <param name="scriptTypes">Tells the engine what kind of scripts will be loaded. Source File or assembly based.</param>
public ScriptEngine(Game game, ScriptTypes scriptTypes)
{
//Initialize our engine fields
_ScriptTypes = scriptTypes;
ScriptExtension = ".cs";
//Get our current install path
ScriptPath = "Scripts";
InstallPath = Environment.CurrentDirectory;
GameObjects = new List<GameObject>();
_AssemblyCollection = new List<Assembly>();
_Game = game;
}
/// <summary>
/// Compiles a collection of scripts stored in ScriptEngine.ScriptPath. Not supported on XBox360.
/// </summary>
/// <returns></returns>
public bool CompileScripts()
{
#if !MOBILE
//Ensure the script path exists.
if (!System.IO.Directory.Exists(ScriptPath))
{
ErrorMessage = "Invalid Script path supplied.";
return false;
}
//Build an array of scripts
string[] scripts = System.IO.Directory.GetFiles(ScriptPath, "*" + ScriptExtension, System.IO.SearchOption.AllDirectories);
//Prepare the scripts. MUD Scripts are wrote without defining a namespace
if (Directory.Exists("temp"))
Directory.Delete("temp", true);
Directory.CreateDirectory("temp");
//Setup the additional sourcecode that's needed in the script.
string[] usingStatements = new string[] { "using System;", "using MudEngine.GameObjects;", "using MudEngine.GameObjects.Characters;", "using MudEngine.GameManagement;", "using MudEngine.FileSystem;" };
foreach (string script in scripts)
{
string tempPath = "temp";
string source = "\nnamespace MudScripts{\n}";
FileStream fr = new FileStream(script, FileMode.Open, FileAccess.Read, FileShare.None);
FileStream fw = new FileStream(Path.Combine(tempPath, Path.GetFileName(script)), FileMode.Create, FileAccess.Write);
StreamWriter sw = new StreamWriter(fw, System.Text.Encoding.Default);
StreamReader sr = new StreamReader(fr, System.Text.Encoding.Default);
string content = sr.ReadToEnd();
foreach (string statement in usingStatements)
source = source.Insert(0, statement);
source = source.Insert(source.Length - 1, content);
sw.Write(source);
sr.Close();
sw.Flush();
sw.Close();
}
string oldPath = ScriptPath;
ScriptPath = "temp";
//Prepare the compiler.
Dictionary<string, string> providerOptions = new Dictionary<string,string>();
providerOptions.Add("CompilerVersion", "v3.5");
CompilerParameters param = new CompilerParameters(new string[] {"mscorlib.dll", "System.dll", "MudEngine.dll"});
param.GenerateExecutable = false;
param.GenerateInMemory = true;
param.OutputAssembly = "Scripts.dll";
param.IncludeDebugInformation = false;
param.TreatWarningsAsErrors = true;
//Compile the scripts with the C# CodeProvider
CSharpCodeProvider codeProvider = new CSharpCodeProvider(providerOptions);
CompilerResults results = new CompilerResults(new TempFileCollection());
scripts = Directory.GetFiles(ScriptPath, "*" + ScriptExtension, SearchOption.AllDirectories);
results = codeProvider.CompileAssemblyFromFile(param, scripts);
//Delete the temp folder
//Directory.Delete("temp", true);
ScriptPath = oldPath;
_ScriptAssembly = results.CompiledAssembly;
//if we encountered errors we need to log them to our ErrorMessages property
if (results.Errors.Count >= 1)
{
List<string> errorCollection = new List<string>();
foreach (CompilerError error in results.Errors)
{
string prefix = "Error: ";
if (error.IsWarning)
prefix = "Warning: ";
errorCollection.Add(prefix + error.FileName + "(" + error.Line + ") - " + error.ErrorText);
_ErrorMessages = errorCollection.ToArray();
}
return false;
}
else
return true;
#endif
}
/// <summary>
/// Initializes the script engine, loading the compiled scripts into memory
/// </summary>
/// <param name="scriptAssembly"></param>
public void Initialize()
{
if (_ScriptTypes == ScriptTypes.Assembly)
{
Log.Write("Loading Assembly based scripts...");
InitializeAssembly();
}
else
{
InitializeSourceFiles();
}
foreach (Assembly assembly in _AssemblyCollection)
{
Log.Write("Checking " + Path.GetFileName(assembly.Location) + " for scripts...");
foreach (Type t in assembly.GetTypes())
{
if (t.BaseType == null)
continue;
if (t.BaseType.Name == "BaseObject")
{
GameObjects.Add(new GameObject(Activator.CreateInstance(t, new object[] {_Game}), t.Name));
Log.Write(t.Name + " script loaded.");
continue;
}
else if (t.BaseType.Name == "BaseCharacter")
{
GameObject obj = new GameObject(Activator.CreateInstance(t, new object[] {_Game}), t.Name);
GameObjects.Add(obj);
obj.GetProperty().CurrentRoom = _Game.InitialRealm.InitialZone.InitialRoom;
Log.Write(t.Name + " script loaded.");
continue;
}
else if (t.BaseType.Name == "Game")
{
GameObject obj = new GameObject(Activator.CreateInstance(t, null), t.Name);
GameObjects.Add(obj);
}
}
}
}
private void InitializeAssembly()
{
if (!Directory.Exists(ScriptPath))
{
Log.Write("Supplied script path does not exist! No scripts loaded.");
return;
}
string[] libraries = Directory.GetFiles(ScriptPath, "*.dll", SearchOption.AllDirectories);
if (libraries.Length == 0)
{
Log.Write("No possible script libraries found.");
return;
}
foreach (string library in libraries)
{
Log.Write("Found possible script libary: " + Path.GetFileName(library));
_AssemblyCollection.Add(Assembly.LoadFile(library));
}
_AssemblyCollection.Add(Assembly.GetExecutingAssembly());
}
private void InitializeSourceFiles()
{
string[] scripts = Directory.GetFiles(ScriptPath, "*.cs", SearchOption.AllDirectories);
if (scripts.Length == 0)
{
Log.Write("No un-compiled scripts located!");
return;
}
CompileScripts();
_AssemblyCollection.Add(_ScriptAssembly);
}
public GameObject GetObject(string objectName)
{
IEnumerable<GameObject> objectQuery =
from gameObject in ObjectCollection._GameObjects
where gameObject.Name == objectName
select gameObject;
foreach (GameObject gameObject in objectQuery)
{
if (gameObject.Name == objectName)
return gameObject;
}
return null;
}
public GameObject GetObjectOf(string baseTypeName)
{
foreach (GameObject obj in GameObjects)
{
if (obj.Instance.GetType().BaseType.Name == baseTypeName)
return obj;
}
return null;
}
}
}