#region Arthea License
/***********************************************************************
* Arthea MUD by R. Jennings (2007) http://arthea.googlecode.com/ *
* By using this code you comply with the Artistic and GPLv2 Licenses. *
***********************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Xml.Serialization;
using Arthea.Continents.Areas.Characters;
using Arthea.Continents.Areas.Items;
using Arthea.Creation;
using Arthea.Environment;
using Arthea.Interfaces;
using Arthea.Scripts.Enums;
namespace Arthea.Scripts
{
/// <summary>
/// Implementation of a script.
/// </summary>
[Serializable]
[XmlInclude(typeof (CharScript))]
[XmlInclude(typeof (ItemScript))]
[XmlInclude(typeof (RoomScript))]
public abstract class Script
{
#region [rgn] Fields (10)
private const int beginBlock = 0;
private const int endBlock = -2;
private const int inBlock = -1;
private const int maxCallLevel = 5;
private const int maxNestedLevel = 12;
private static readonly Dictionary<string, OperatorType> operators = new Dictionary<string, OperatorType>();
private static int callLevel = 0;
private ScriptCode code;
private string trigger;
private TriggerType type;
#endregion [rgn]
#region [rgn] Constructors (3)
// initialize operand list
static Script()
{
operators.Add("<=", OperatorType.LessThanOrEqualTo);
operators.Add("<", OperatorType.LessThan);
operators.Add("==", OperatorType.EqualTo);
operators.Add("=", OperatorType.EqualTo);
operators.Add(">", OperatorType.GreaterThan);
operators.Add(">=", OperatorType.GreaterThanOrEqualTo);
operators.Add("!=", OperatorType.NotEqualTo);
}
/// <summary>
/// Initializes a new instance of the <see cref="Script"/> class.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="trigger">The trigger.</param>
public Script(ScriptCode code, string trigger)
{
this.code = code;
this.trigger = trigger;
}
/// <summary>
/// Initializes a new instance of the <see cref="Script"/> class.
/// </summary>
public Script()
{
}
#endregion [rgn]
#region [rgn] Properties (3)
/// <summary>
/// Gets or sets the code.
/// </summary>
/// <value>The code.</value>
public ScriptCode Code
{
get { return code; }
set { code = value; }
}
/// <summary>
/// Gets or sets the trigger.
/// </summary>
/// <value>The trigger.</value>
public string Trigger
{
get { return trigger; }
set { trigger = value; }
}
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public TriggerType Type
{
get { return type; }
set { type = value; }
}
#endregion [rgn]
#region [rgn] Methods (6)
// [rgn] Public Methods (1)
/// <summary>
/// Interprets the script code.
/// </summary>
/// <param name="from">The object initiating the script.</param>
/// <param name="ch">The character who triggered the script.</param>
/// <param name="arg1">The arg1.</param>
/// <param name="arg2">The arg2.</param>
public void Interpret(Scriptable from, Character ch, object arg1, object arg2)
{
if (string.IsNullOrEmpty(code.Text))
return;
string[] lines = code.Text.Split(new string[] {System.Environment.NewLine},
StringSplitOptions.RemoveEmptyEntries);
int[] state = new int[maxNestedLevel];
bool[] cond = new bool[maxNestedLevel];
int level = 0;
if (++callLevel > maxCallLevel)
{
Log.Bug("Script.Interpret: Max call level exceeded. (script {0})", code.Id);
return;
}
foreach (String line in lines)
{
// skip comments
if (line.Substring(0, 2) == "//")
continue;
String ctrl = line.FirstArg();
switch (ctrl)
{
case "if":
if (state[level] == beginBlock)
{
Log.Bug("Script.Interpret: misplaced if statement (script {0})", code.Id);
callLevel--;
return;
}
state[level] = beginBlock;
if (++level > maxCallLevel)
{
Log.Bug("Script.Interpret: max call level exceeded (script {0})", code.Id);
callLevel--;
return;
}
if (level > 0 && cond[level - 1] == false)
{
cond[level] = false;
continue;
}
try
{
cond[level] = CheckCondition(from, ch, line, arg1, arg2);
}
catch
{
Log.Bug("Script.Interpret: Invalid if check (script {0})", code.Id);
callLevel--;
return;
}
state[level] = endBlock;
break;
case "or":
if (level == 0 || state[level - 1] != beginBlock)
{
Log.Bug("Script.Interpret: or without if (script {0})", code.Id);
return;
}
if (level > 0 && cond[level - 1] == false)
continue;
try
{
cond[level] = (CheckCondition(from, ch, line, arg1, arg2)) ? true : cond[level];
}
catch
{
Log.Bug("Script.Interpret: Invalid or check (script {0})", code.Id);
callLevel--;
return;
}
break;
case "and":
if (level == 0 || state[level - 1] != beginBlock)
{
Log.Bug("Script.Interpret: and without if (script {0})", code.Id);
return;
}
if (level > 0 && cond[level - 1] == false)
continue;
try
{
cond[level] = (cond[level] && CheckCondition(from, ch, line, arg1, arg2))
? true
: cond[level];
}
catch
{
Log.Bug("Script.Interpret: Invalid or check (script {0})", code.Id);
callLevel--;
return;
}
break;
case "endif":
if (level == 0 || state[level - 1] != beginBlock)
{
Log.Bug("Script.Interpret: endif without if (script {0})", code.Id);
return;
}
cond[level] = true;
state[level] = inBlock;
state[--level] = endBlock;
break;
case "else":
if (level == 0 || state[level - 1] != beginBlock)
{
Log.Bug("Script.Interpret: else without if (script {0})", code.Id);
return;
}
if (level > 0 && cond[level - 1] == false)
continue;
state[level] = inBlock;
cond[level] = (cond[level] ? false : true);
break;
case "end":
case "break":
if (cond[level])
callLevel--;
return;
default:
if (level == 0 || cond[level])
{
state[level] = inBlock;
// expand args
String args = ExpandArgs(from, ch, line, arg1, arg2);
// process command
if (!InterpretCommand(from, ctrl, args))
{
Log.Bug("Unknown command '{0}' (script {1})", ctrl, code.Id);
}
}
break;
}
}
}
// [rgn] Protected Methods (1)
/// <summary>
/// Interprets a script command.
/// </summary>
/// <param name="from">The object with the script.</param>
/// <param name="cmd">The command (method name).</param>
/// <param name="argument">The argument.</param>
/// <returns>true if a command was interpreted</returns>
protected abstract bool InterpretCommand(Scriptable from, String cmd, String argument);
// [rgn] Private Methods (4)
// evalute args in a line and compare them
private bool CheckCondition(Scriptable from, Character ch, String line, object arg1, object arg2)
{
String[] values = line.GetArgs();
switch (values.Length)
{
default:
Log.Bug("Invalid if check (script {0})", code.Id);
throw new Exception();
case 1: // a boolean value
return GetOperand(from, ch, values[0], arg1, arg2) != null;
case 3: // compare two operands
return Operate(GetOperand(from, ch, values[0], arg1, arg2),
operators[values[1]],
GetOperand(from, ch, values[2], arg1, arg2));
}
}
private string ExpandArgs(Scriptable from, Scriptable ch, string format, object arg1, object arg2)
{
StringBuilder buf = new StringBuilder();
for (CharEnumerator c = format.GetEnumerator(); c.MoveNext();)
{
if (c.Current != '$')
{
buf.Append(c.Current);
continue;
}
if (!c.MoveNext())
break;
switch (c.Current)
{
default:
Log.Bug("Bad argument ${0} (script {1}", c.Current, code.Id);
break;
case 'i':
buf.Append(new String(from.Name).FirstArg());
break;
case 'I':
buf.Append(from.ShortDescr);
break;
case 'n':
buf.Append(new String(ch.Name).FirstArg());
break;
case 'N':
buf.Append(ch.ShortDescr);
break;
case 't':
if (arg2 is Character)
buf.Append(new String((arg2 as Character).Name).FirstArg());
break;
case 'T':
if (arg2 is Character)
buf.Append((arg2 as Character).ShortDescr);
break;
case 'o':
if (arg1 is Item)
buf.Append(new String((arg1 as Item).Name).FirstArg());
break;
case 'O':
if (arg1 is Item)
buf.Append((arg1 as Item).ShortDescr);
break;
case 'p':
if (arg2 is Item)
buf.Append(new String((arg2 as Item).Name).FirstArg());
break;
case 'P':
if (arg2 is Item)
buf.Append((arg2 as Item).ShortDescr);
break;
}
}
return buf.ToString();
}
private static object GetOperand(Scriptable from, Character ch, string line, object arg1, object arg2)
{
object operand;
// check for an expanded arg value
if (line[0] == '$')
{
// get the base object
switch (char.ToLower(line[1]))
{
case 'i':
operand = from;
break;
case 'n':
operand = ch;
break;
case 't':
case 'p':
operand = arg2;
break;
case 'o':
operand = arg1;
break;
default:
throw new Exception();
}
// ok now parse lines like: $n.clan.name
string[] ops = line.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
for (int i = 1; i < ops.Length; i++)
{
FieldInfo info = Olc.GetField(operand.GetType(), ops[i]);
if (info != null)
{
operand = info.GetValue(operand);
}
}
}
// else check for implemented conditional keywords
else
{
ConditionKeyword condition;
try
{
condition = (ConditionKeyword) Enum.Parse(typeof (ConditionKeyword), line, true);
}
catch
{
// no implemented keyword, pass the line through for comparing
return line;
}
switch (condition)
{
case ConditionKeyword.Random:
operand = Randomizer.Next(1, 100);
break;
default:
throw new Exception();
}
}
return operand;
}
// compare two values based on given operator
private bool Operate(object lvalue, OperatorType oper, object rvalue)
{
switch (oper)
{
case OperatorType.EqualTo:
return lvalue == rvalue;
case OperatorType.NotEqualTo:
return lvalue != rvalue;
case OperatorType.GreaterThan:
return lvalue.ToString().CompareTo(rvalue) > 0;
case OperatorType.GreaterThanOrEqualTo:
return lvalue.ToString().CompareTo(rvalue) >= 0;
case OperatorType.LessThan:
return lvalue.ToString().CompareTo(rvalue) < 0;
case OperatorType.LessThanOrEqualTo:
return lvalue.ToString().CompareTo(rvalue) <= 0;
default:
Log.Bug("Unknown operator (script {0})", code.Id);
return false;
}
}
#endregion [rgn]
}
}