#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] } }