Kursach_StealthAction/Assets/Tremble/IO/FGD/Writers/EntityClassWriter.cs
2026-10-11 19:16:11 +03:00

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("}");
}
}
}
}