330 lines
8.5 KiB
C#
330 lines
8.5 KiB
C#
//
|
|
// This file is part of the Tremble package by Tiny Goose.
|
|
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
|
|
namespace TinyGoose.Tremble
|
|
{
|
|
public static class EntityClassWriter
|
|
{
|
|
public static void WriteCSharp(this FgdClass fgdClass, string file, List<FgdClass> allBaseClasses = null, string namespaceName = null)
|
|
{
|
|
if (fgdClass.Type == FgdClassType.Base)
|
|
{
|
|
WriteCSharpInterface(fgdClass, file, namespaceName);
|
|
}
|
|
else
|
|
{
|
|
WriteCSharpClass(fgdClass, file, allBaseClasses, namespaceName);
|
|
}
|
|
}
|
|
|
|
private static void WriteCSharpClass(this FgdClass fgdClass, string file, List<FgdClass> allBaseClasses = null, string namespaceName = null)
|
|
{
|
|
using FileStream classFile = new(file, FileMode.Create, FileAccess.Write);
|
|
using StreamWriter writer = new(classFile);
|
|
|
|
writer.WriteLine($"// Class '{fgdClass.Name}'");
|
|
writer.WriteLine($"// Generated by Tremble v{TrembleConsts.VERSION_STRING}");
|
|
writer.WriteLine();
|
|
|
|
writer.WriteLine("using UnityEngine;");
|
|
writer.WriteLine("using TinyGoose.Tremble;");
|
|
writer.WriteLine();
|
|
|
|
writer.WriteLine("// Disable 'Field is assigned but its value is never used' warning");
|
|
writer.WriteLine("#pragma warning disable CS0414");
|
|
writer.WriteLine();
|
|
|
|
bool hasNamespace = !namespaceName.IsNullOrEmpty();
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.WriteLine($"namespace {namespaceName}");
|
|
writer.WriteLine("{");
|
|
}
|
|
|
|
if (!fgdClass.Description.IsNullOrEmpty())
|
|
{
|
|
writer.WriteLine($"\t// \"{fgdClass.Description}\"");
|
|
}
|
|
|
|
{
|
|
fgdClass.Name.Split('_', out string category, out string classname);
|
|
|
|
if (fgdClass.Type == FgdClassType.Brush)
|
|
{
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.Write("[BrushEntity(");
|
|
}
|
|
else
|
|
{
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.Write("[PointEntity(");
|
|
}
|
|
|
|
writer.Write($"\"{classname}\"");
|
|
|
|
if (category.IsNullOrEmpty())
|
|
{
|
|
writer.Write($", category: \"\" /* empty category - gross, but necessary */");
|
|
}
|
|
else
|
|
{
|
|
writer.Write($", category: \"{category}\"");
|
|
}
|
|
|
|
if (fgdClass.Type == FgdClassType.Brush && fgdClass.Name.ContainsInvariant("trigger"))
|
|
{
|
|
writer.Write($", type: BrushType.Trigger");
|
|
}
|
|
|
|
writer.WriteLine(")]");
|
|
|
|
string csharpClassname = classname.ToNamingConvention(NamingConvention.UpperCamelCase);
|
|
if (!category.IsNullOrEmpty()
|
|
&& !category.EqualsInvariant("misc")
|
|
&& !category.EqualsInvariant("func"))
|
|
{
|
|
csharpClassname += category.ToNamingConvention(NamingConvention.UpperCamelCase);
|
|
}
|
|
|
|
if (csharpClassname.EqualsInvariant("light"))
|
|
{
|
|
// Hack: you can't call a MonoBehaviour "Light" in Unity, but Quake for example
|
|
// has an entity called "light" - let's call it LightEntity
|
|
csharpClassname = "LightEntity";
|
|
}
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.Write($"public class {csharpClassname} : MonoBehaviour");
|
|
|
|
foreach (string baseClass in fgdClass.BaseClassNames)
|
|
{
|
|
// Not the Tremble ones!
|
|
if (baseClass.EqualsInvariant(FgdConsts.CLASS_MAP_BASE, caseSensitive: true)
|
|
|| baseClass.EqualsInvariant(FgdConsts.CLASS_MAP_POINT_BASE, caseSensitive: true))
|
|
continue;
|
|
|
|
writer.Write($", I{baseClass}");
|
|
}
|
|
writer.WriteLine();
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.WriteLine("{");
|
|
|
|
if (fgdClass.AllFields.Count > 0)
|
|
{
|
|
writer.WriteIndent(hasNamespace ? 2 : 1);
|
|
writer.WriteLine("// Fields:");
|
|
|
|
foreach (FgdFieldBase field in fgdClass.AllFields)
|
|
{
|
|
field.WriteCSharpClass(writer, hasNamespace ? 2 : 1);
|
|
}
|
|
|
|
writer.WriteLine();
|
|
}
|
|
|
|
if (fgdClass.AllSpawnFlags.Count > 0)
|
|
{
|
|
writer.WriteIndent(hasNamespace ? 2 : 1);
|
|
writer.WriteLine("// Spawnflags: ");
|
|
foreach (FgdSpawnFlagField spawnFlagField in fgdClass.AllSpawnFlags)
|
|
{
|
|
spawnFlagField.WriteCSharpClass(writer, hasNamespace ? 2 : 1);
|
|
}
|
|
|
|
writer.WriteLine();
|
|
}
|
|
|
|
// Write base class fields
|
|
if (allBaseClasses != null)
|
|
{
|
|
List<string> writtenBaseClasses = new(64);
|
|
List<string> writtenFields = new(64);
|
|
|
|
foreach (string baseClassName in fgdClass.BaseClassNames)
|
|
{
|
|
AddBaseClassFields(fgdClass, writer, hasNamespace, baseClassName, writtenBaseClasses, writtenFields, allBaseClasses);
|
|
}
|
|
}
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.WriteLine("}");
|
|
}
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.WriteLine("}");
|
|
}
|
|
|
|
writer.WriteLine();
|
|
writer.WriteLine("#pragma warning restore CS0414");
|
|
}
|
|
|
|
private static void AddBaseClassFields(FgdClass thisClass, StreamWriter writer, bool hasNamespace, string baseClassName, List<string> writtenBaseClasses, List<string> writtenFields, List<FgdClass> allBaseClasses)
|
|
{
|
|
// Find the base class and add!
|
|
FgdClass baseClass = allBaseClasses.FirstOrDefault(bc => bc.Name.EqualsInvariant(baseClassName, caseSensitive: true));
|
|
if (baseClass == null)
|
|
return;
|
|
|
|
if (baseClass.AllFields.Count + baseClass.AllSpawnFlags.Count == 0)
|
|
return;
|
|
|
|
if (writtenBaseClasses.Contains(baseClassName))
|
|
return;
|
|
|
|
writtenBaseClasses.Add(baseClassName);
|
|
|
|
foreach (string baseBaseClassName in baseClass.BaseClassNames)
|
|
{
|
|
AddBaseClassFields(thisClass, writer, hasNamespace, baseBaseClassName, writtenBaseClasses, writtenFields, allBaseClasses);
|
|
}
|
|
|
|
writer.WriteLine();
|
|
writer.WriteIndent(hasNamespace ? 2 : 1);
|
|
writer.WriteLine($"// From Base Class '{baseClassName}':");
|
|
|
|
foreach (FgdFieldBase field in baseClass.AllFields)
|
|
{
|
|
if (writtenFields.Contains(field.FieldName))
|
|
{
|
|
writer.WriteIndent(hasNamespace ? 2 : 1);
|
|
writer.WriteLine($"// Skipped '{field.FieldName}' - already defined by a base class!");
|
|
continue;
|
|
}
|
|
|
|
writtenFields.Add(field.FieldName);
|
|
|
|
bool overridden = thisClass.AllFields.Any(m => m.FieldName.EqualsInvariant(field.FieldName, caseSensitive: true));
|
|
if (overridden)
|
|
{
|
|
writer.WriteLine("/* (overriden by subclass)");
|
|
}
|
|
|
|
field.WriteCSharpClass(writer, hasNamespace ? 2 : 1);
|
|
|
|
if (overridden)
|
|
{
|
|
writer.WriteLine("(overriden by subclass) */");
|
|
}
|
|
}
|
|
|
|
foreach (FgdSpawnFlagField spawnFlag in baseClass.AllSpawnFlags)
|
|
{
|
|
bool overridden = thisClass.AllSpawnFlags.Any(m => m.FieldName.EqualsInvariant(spawnFlag.FieldName, caseSensitive: true));
|
|
if (overridden)
|
|
{
|
|
writer.WriteLine("/* (overriden by subclass)");
|
|
}
|
|
|
|
spawnFlag.WriteCSharpClass(writer, hasNamespace ? 2 : 1);
|
|
|
|
if (overridden)
|
|
{
|
|
writer.WriteLine("(overriden by subclass) */");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private static void WriteCSharpInterface(this FgdClass fgdClass, string file, string namespaceOrNull)
|
|
{
|
|
using FileStream classFile = new(file, FileMode.Create, FileAccess.Write);
|
|
using StreamWriter writer = new(classFile);
|
|
|
|
writer.WriteLine($"// Interface 'I{fgdClass.Name}'");
|
|
writer.WriteLine($"// Generated by Tremble v{TrembleConsts.VERSION_STRING}");
|
|
writer.WriteLine();
|
|
|
|
writer.WriteLine("using UnityEngine;");
|
|
writer.WriteLine("using TinyGoose.Tremble;");
|
|
writer.WriteLine();
|
|
|
|
bool hasNamespace = !namespaceOrNull.IsNullOrEmpty();
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.WriteLine($"namespace {namespaceOrNull}");
|
|
writer.WriteLine("{");
|
|
}
|
|
|
|
{
|
|
fgdClass.Name.Split('_', out string _, out string classname);
|
|
string csharpClassname = classname.ToNamingConvention(NamingConvention.UpperCamelCase);
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.WriteLine($"public interface I{csharpClassname} : ITrembleBaseClass");
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.WriteLine("{");
|
|
|
|
if (fgdClass.AllFields.Count > 0)
|
|
{
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.WriteLine("\t// Fields:");
|
|
|
|
foreach (FgdFieldBase field in fgdClass.AllFields)
|
|
{
|
|
field.WriteCSharpInterface(writer, hasNamespace ? 2 : 1);
|
|
}
|
|
}
|
|
|
|
if (fgdClass.AllSpawnFlags.Count > 0)
|
|
{
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.WriteLine("\t// Spawnflags: ");
|
|
foreach (FgdSpawnFlagField spawnFlagField in fgdClass.AllSpawnFlags)
|
|
{
|
|
spawnFlagField.WriteCSharpInterface(writer, hasNamespace ? 2 : 1);
|
|
}
|
|
}
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.Write("\t");
|
|
}
|
|
writer.WriteLine("}");
|
|
}
|
|
|
|
if (hasNamespace)
|
|
{
|
|
writer.WriteLine("}");
|
|
}
|
|
}
|
|
}
|
|
} |