using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Gamecraft.Tweaks; using RobocraftX.Common; using Svelto.ECS; namespace CodeGenerator { public class BlockClassGenerator { public void Generate(string name, string group = null, Dictionary renames = null, params Type[] types) { if (group is null) { group = GetGroup(name) + "_BLOCK_GROUP"; if (typeof(CommonExclusiveGroups).GetFields().All(field => field.Name != group)) group = GetGroup(name) + "_BLOCK_BUILD_GROUP"; } if (!group.Contains('.')) group = "CommonExclusiveGroups." + group; var codeUnit = new CodeCompileUnit(); var ns = new CodeNamespace("TechbloxModdingAPI.Blocks"); ns.Imports.Add(new CodeNamespaceImport("RobocraftX.Common")); ns.Imports.Add(new CodeNamespaceImport("Svelto.ECS")); var cl = new CodeTypeDeclaration(name); //cl.BaseTypes.Add(baseClass != null ? new CodeTypeReference(baseClass) : new CodeTypeReference("Block")); cl.BaseTypes.Add(new CodeTypeReference("SignalingBlock")); cl.Members.Add(new CodeConstructor { Parameters = {new CodeParameterDeclarationExpression("EGID", "egid")}, Comments = { _start, new CodeCommentStatement($"Constructs a(n) {name} object representing an existing block.", true), _end }, BaseConstructorArgs = {new CodeVariableReferenceExpression("egid")}, Attributes = MemberAttributes.Public | MemberAttributes.Final }); cl.Members.Add(new CodeConstructor { Parameters = { new CodeParameterDeclarationExpression(typeof(uint), "id") }, Comments = { _start, new CodeCommentStatement($"Constructs a(n) {name} object representing an existing block.", true), _end }, BaseConstructorArgs = { new CodeObjectCreateExpression("EGID", new CodeVariableReferenceExpression("id"), new CodeVariableReferenceExpression(group)) }, Attributes = MemberAttributes.Public | MemberAttributes.Final }); foreach (var type in types) { GenerateProperties(cl, type, name, renames); } ns.Types.Add(cl); codeUnit.Namespaces.Add(ns); var provider = CodeDomProvider.CreateProvider("CSharp"); var path = $@"../../../../TechbloxModdingAPI/Blocks/{name}.cs"; using (var sw = new StreamWriter(path)) { provider.GenerateCodeFromCompileUnit(codeUnit, sw, new CodeGeneratorOptions {BracingStyle = "C"}); } File.WriteAllLines(path, File.ReadAllLines(path).SkipWhile(line => line.StartsWith("//") || line.Length == 0)); } private static string GetGroup(string name) { var ret = ""; foreach (var ch in name) { if (char.IsUpper(ch) && ret.Length > 0) ret += "_" + ch; else ret += char.ToUpper(ch); } return ret; } private void GenerateProperties(CodeTypeDeclaration cl, Type type, string baseClass, Dictionary renames) { if (!typeof(IEntityComponent).IsAssignableFrom(type)) throw new ArgumentException("Type must be an entity component"); bool reflection = type.IsNotPublic; var reflectedType = new CodeSnippetExpression($"HarmonyLib.AccessTools.TypeByName(\"{type.FullName}\")"); foreach (var field in type.GetFields()) { var attr = field.GetCustomAttribute(); if (renames == null || !renames.TryGetValue(field.Name, out var propName)) { propName = field.Name; if (attr != null) propName = attr.propertyName; } propName = char.ToUpper(propName[0]) + propName.Substring(1); var getStruct = new CodeMethodInvokeExpression( new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"), "GetBlockInfo", new CodeTypeReference(type)), new CodeThisReferenceExpression()); CodeExpression structFieldReference = new CodeFieldReferenceExpression(getStruct, field.Name); CodeExpression reflectedGet = new CodeCastExpression(field.FieldType, new CodeMethodInvokeExpression( new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"), "GetBlockInfo"), new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(field.Name))); CodeExpression reflectedSet = new CodeMethodInvokeExpression( new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"), "SetBlockInfo"), new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(field.Name), new CodePropertySetValueReferenceExpression()); cl.Members.Add(new CodeMemberProperty { Name = propName, HasGet = true, HasSet = true, GetStatements = { new CodeMethodReturnStatement(reflection ? reflectedGet : structFieldReference) }, SetStatements = { reflection ? (CodeStatement)new CodeExpressionStatement(reflectedSet) : new CodeAssignStatement(structFieldReference, new CodePropertySetValueReferenceExpression()) }, Type = new CodeTypeReference(field.FieldType), Attributes = MemberAttributes.Public | MemberAttributes.Final, Comments = { _start, new CodeCommentStatement($"Gets or sets the {baseClass}'s {propName} property." + $" {(attr != null ? "Tweakable stat." : "May not be saved.")}", true), _end } }); } } private static readonly CodeCommentStatement _start = new CodeCommentStatement("", true); private static readonly CodeCommentStatement _end = new CodeCommentStatement("", true); } }