public GameAttributeDependency(GameAttribute attribute, int?key, bool usesExplicitKey, bool isManualDependency) { Attribute = attribute; Key = key; UsesExplicitKey = usesExplicitKey; IsManualDependency = isManualDependency; }
public static void ProcessAttributes(GameAttribute[] attributes) { // build string -> GameAttribute lookup var attributeLookup = attributes.ToDictionary(attr => attr.Name); // will contain C# code for the func<> body that represents each attribute's script. var csharpScripts = new Dictionary <GameAttribute, string>(); // generate C#-compatible source lines from scripts and create attribute dependency lists foreach (GameAttribute attr in attributes) { // check for valid script in the attribute and select it string script; if (attr.ScriptA.Length > 0 && attr.ScriptA != "0") { script = attr.ScriptA; } else if (attr.ScriptB.Length > 0 && attr.ScriptB != "0") { script = attr.ScriptB; } else { continue; // no valid script, done processing this attribute } // by default all scripts are not settable // can be set to true if self-referring identifier is found attr.ScriptedAndSettable = false; // replace attribute references with GameAttributeMap lookups // also record all attributes used by script into each attribute's dependency list script = Regex.Replace(script, @"([A-Za-z_]\w*)(\.Agg)?(\#[A-Za-z_]\w*)?(?=[^\(\w]|\z)( \?)?", (match) => { // lookup attribute object string identifierName = match.Groups[1].Value; if (!attributeLookup.ContainsKey(identifierName)) { throw new ScriptedAttributeInitializerError("invalid identifer parsed: " + identifierName); } GameAttribute identifier = attributeLookup[identifierName]; // key selection int?key = null; string keyString = "_key"; bool usesExplicitKey = false; if (match.Groups[3].Success) { switch (match.Groups[3].Value.ToUpper()) { case "#NONE": key = null; break; case "#PHYSICAL": key = 0; break; case "#FIRE": key = 1; break; case "#LIGHTNING": key = 2; break; case "#COLD": key = 3; break; case "#POISON": key = 4; break; case "#ARCANE": key = 5; break; case "#HOLY": key = 6; break; default: throw new ScriptedAttributeInitializerError("error processing attribute script, invalid key in identifier: " + match.Groups[3].Value); } if (key == null) { keyString = "null"; } else { keyString = key.ToString(); } usesExplicitKey = true; } // add comparsion for int attributes that are directly used in an ?: expression. string compare = ""; if (match.Groups[4].Success) { compare = identifier is GameAttributeI ? " > 0 ?" : " ?"; } // handle self-referring lookup. example: Resource.Agg if (match.Groups[2].Success) { attr.ScriptedAndSettable = true; return("_map._RawGetAttribute(GameAttribute." + identifierName + ", " + keyString + ")" + compare); } // record dependency if (identifier.Dependents == null) { identifier.Dependents = new List <GameAttributeDependency>(); } identifier.Dependents.Add(new GameAttributeDependency(attr, key, usesExplicitKey, false)); // generate normal lookup return("_map[GameAttribute." + identifierName + ", " + keyString + "]" + compare); }); // transform function calls into C# equivalents script = Regex.Replace(script, @"floor\(", "(float)Math.Floor(", RegexOptions.IgnoreCase); script = Regex.Replace(script, @"max\(", "Math.Max(", RegexOptions.IgnoreCase); script = Regex.Replace(script, @"min\(", "Math.Min(", RegexOptions.IgnoreCase); script = Regex.Replace(script, @"pin\(", "ScriptedAttributeInitializer.Pin(", RegexOptions.IgnoreCase); // add C# single-precision affix to decimal literals. example: 1.25 => 1.25f script = Regex.Replace(script, @"\d+\.\d+", "$0f"); csharpScripts[attr] = script; } // generate and write final C# code to file string sourcePathBase = Path.Combine(Path.GetTempPath(), "NullDScriptedAttributeFuncs"); using (StreamWriter fout = new StreamWriter(sourcePathBase + ".cs")) { fout.Write( @"// This file was auto-generated by NullD class ScriptedAttributeInitializer // It contains Funcs derived from GameAttribute.ScriptA/B scripts. // Funcs will be assigned to their respective GameAttribute.ScriptFunc member. using System; using NullD.Net.GS.Message; using NullD.Core.GS.Objects; namespace NullD.Net.GS.Message.GeneratedCode { public class ScriptedAttributeFuncs { "); foreach (var scriptEntry in csharpScripts) { // select output type cast to ensure it matches attribute type string castType = scriptEntry.Key is GameAttributeF ? "float" : "int"; // write out full Func static class field fout.WriteLine(" public static Func<GameAttributeMap, int?, GameAttributeValue> {0} = (_map, _key) => new GameAttributeValue(({1})({2}));", scriptEntry.Key.Name, castType, scriptEntry.Value); } fout.Write( @" } } "); } // compile code var options = new CompilerParameters(); options.GenerateExecutable = false; options.OutputAssembly = sourcePathBase + ".dll"; options.IncludeDebugInformation = true; options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location); var results = new CSharpCodeProvider().CompileAssemblyFromFile(options, sourcePathBase + ".cs"); if (results.Errors.Count > 0) { StringBuilder emsg = new StringBuilder(); emsg.AppendLine("encountered errors compiling attribute funcs:"); foreach (var e in results.Errors) { emsg.AppendLine(e.ToString()); } throw new ScriptedAttributeInitializerError(emsg.ToString()); } // pull funcs from new assembly and assign them to their respective attributes Type funcs = results.CompiledAssembly.GetType("NullD.Net.GS.Message.GeneratedCode.ScriptedAttributeFuncs"); foreach (var attr in csharpScripts.Keys) { attr.ScriptFunc = (Func <GameAttributeMap, int?, GameAttributeValue>)funcs .GetField(attr.Name).GetValue(null); } }