using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Microsoft.Xna.Framework;
namespace DataDrivenExample
{
class Scripting
{
public static Scripting GetInstance()
{
if (mInstance == null)
{
mInstance = new Scripting();
}
return mInstance;
}
public void initialize(World w)
{
mWorld = w;
}
// Methods that scritps can call:
///
/// Get a pointer to a WorldElement, given a string Identifier
///
/// String identifier for the WorldElement we want
/// Pointer to a WorldElement with the given ID
public WorldElement GetWorldElement(string id)
{
return mWorld.GetWorldElement(id);
}
public void PrintDebugText(String txt)
{
DebugText.GetInstance().AddDebugText(txt);
}
///
/// Execute a script after a certain amount of time has gone by
///
/// Script to execute
/// Time to wait to execute script
public void TimedScript(string script, float time)
{
mScriptTimers.Add(new ScriptTimingPair(script, time));
}
///
/// Execute a script. The script is a string, representing a Method call. The first
/// method invoked needs to be one of the methods in this Scripting class (such as
/// GetWroldElement, or TimedScript). Example use:
/// Assuming that the WorldElement with the ID "elemID" has a method SetPosition that
/// takes two floating point numbers, we could invoke that method with the following script
/// call:
///
/// ExecuteScript("GetWorldElement(\"elemID\").SetPosition(3,4)")
///
/// Both ' and " can be used as delimiters within the string, to allow calls like:
/// ExecuteScript("TimedScript(\"GetWorldElement(\'pf1\').ResetPosition()\",5)"
///
///
///
public void ExecuteScript(string scriptsString)
{
int offset = 0;
while (offset < scriptsString.Length)
{
Eval(this, scriptsString, ref offset, false);
SkipWhiteSpaces(scriptsString, ref offset);
if (offset < scriptsString.Length && scriptsString[offset] == ';')
{
offset++;
SkipWhiteSpaces(scriptsString, ref offset);
}
else if (offset < scriptsString.Length)
{
throw new ArgumentException("Poorly formed script: " + scriptsString);
}
}
}
public void Update(GameTime time)
{
List elemsToRemove = null;
foreach (ScriptTimingPair stp in mScriptTimers)
{
stp.TimeRemaining -= (float) time.ElapsedGameTime.TotalSeconds;
if (stp.TimeRemaining < 0)
{
if (elemsToRemove == null)
elemsToRemove = new List();
elemsToRemove.Add(stp);
ExecuteScript(stp.Script);
}
}
if (elemsToRemove != null)
{
foreach (ScriptTimingPair stp in elemsToRemove)
{
mScriptTimers.Remove(stp);
}
}
}
public void RemoveWorldElement(string id)
{
mWorld.RemoveWorldElement(id);
}
// Methods to parse and execute scripts
///
/// Parse and evaluate a script (or a piece of a script). The offset will be advanced over the
/// portion of the script that is parsed
///
/// Object to use for any references to methods / properties. Initially,
/// this should be a reference to the global script object
/// Script to parse
/// Offset in scriptt to start parsing -- will be advanced to point
/// to just past the section of the script that is evaluated
/// True if literals are allowed
/// Object representing the value of the evaluated script
protected Object Eval(Object baseObject, string script, ref int offset, bool literalsOK)
{
SkipWhiteSpaces(script, ref offset);
if (offset == script.Length)
return null;
if (literalsOK)
{
if (script[offset] == '\"' || script[offset] == '\'')
{
char stringDelimiter = script[offset];
offset++;
int literalInitialOffset = offset;
while (offset < script.Length && script[offset] != stringDelimiter)
{
offset++;
}
if (offset == script.Length)
{
throw new ArgumentException("Bad string literal in script:" + script);
}
string literalVal = script.Substring(literalInitialOffset, offset - literalInitialOffset);
offset++;
return literalVal;
}
else if (char.IsDigit(script[offset]))
{
int commaPos = script.IndexOf(',', offset);
int closeParenPos = script.IndexOf(')', offset);
if (commaPos > 0 && commaPos < closeParenPos)
{
float number = float.Parse(script.Substring(offset, commaPos - offset));
offset = commaPos;
return number;
}
else if (closeParenPos > 0)
{
float number = float.Parse(script.Substring(offset, closeParenPos - offset));
offset = closeParenPos;
return number;
}
else
{
throw new ArgumentException("Bad floating literal in script " + script + " at position " + offset.ToString());
}
}
else if (script.Length > offset + 4 && script.Substring(offset, 4) == "true" && (script[offset + 4] == ',' || script[offset + 4] == ')' || script[offset + 4] == ' '))
{
offset += 4;
return true;
}
else if (script.Length > offset + 5 && script.Substring(offset, 5) == "false" && (script[offset + 5] == ',' || script[offset + 5] == ')' || script[offset + 5] == ' '))
{
offset += 5;
return false;
}
}
// TODO: Add support for other literals (int, float, etc)
int nextDot = script.IndexOf('.', offset);
int nextParen = script.IndexOf('(', offset);
int nextSemi = script.IndexOf(';', offset);
if ((nextParen > 0) && (nextParen < nextDot || nextDot < 0) && (nextParen < nextSemi || nextSemi < 0))
{
String methodName = script.Substring(offset, nextParen - offset);
MethodInfo method = baseObject.GetType().GetMethod(methodName);
if (method == null)
{
throw new ArgumentException("Bad method name in script: " + methodName);
}
else
{
offset = nextParen + 1;
Object[] parameters = ParseParamters(this, script, ref offset);
Object methodReturn = method.Invoke(baseObject, parameters);
if (offset < script.Length && script[offset] == '.')
{
offset++;
return Eval(methodReturn, script, ref offset, false);
}
SkipWhiteSpaces(script, ref offset);
return methodReturn;
}
}
else if (nextDot > 0 && ((nextDot < nextSemi) || nextSemi < 0))
{
PropertyInfo propInfo = baseObject.GetType().GetProperty(script.Substring(offset, nextDot - offset));
offset = nextDot + 1;
return Eval(propInfo.GetValue(baseObject, null), script, ref offset, false);
}
else if (nextSemi > 0)
{
PropertyInfo propInfo = baseObject.GetType().GetProperty(script.Substring(offset, nextSemi - offset));
offset = nextSemi;
if (propInfo != null)
{
return propInfo.GetValue(baseObject, null);
}
else
{
throw new ArgumentException("Bad property name in script: " + script.Substring(offset, nextSemi - offset));
}
}
else
{
PropertyInfo propInfo = baseObject.GetType().GetProperty(script.Substring(offset));
if (propInfo != null)
{
return propInfo.GetValue(baseObject, null);
}
else
{
throw new ArgumentException("Bad property name in script: " + script.Substring(offset));
}
}
}
///
/// Given a string and an offset, increment the offset until the string at that
/// index is not a whitespace character
///
/// String to search for non-whitespace character
/// Index to search from for non-whitespace characeter
void SkipWhiteSpaces(string str, ref int offset)
{
while (offset < str.Length && char.IsWhiteSpace(str[offset]))
offset++;
}
///
/// Parse all of the parameters of a method call imbedded within a script.
/// The "offset" parameter should point to the very beginning of the parameter list
/// [right after the opening paren (]. After this method is finished, the offset
/// parameter should point just past the parameter list [just past ending param )]
///
/// Object to use as base for properties / methods when evaluating parameters
/// The entire script
/// The offset in script of the beginning of the parameter list
/// List of parsed parameters
protected Object[] ParseParamters(Object baseObject, string script, ref int offset)
{
SkipWhiteSpaces(script, ref offset);
if (script[offset] == ')')
{
offset++;
return new Object[0];
}
List