Second commit

This commit is contained in:
ThomasCharester committed 2026-09-20 15:26:03 +03:00
commit 36840f3ad4
999 files changed
+61364

No files matched your search

+166
View File
@@ -0,0 +1,166 @@
//
// 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;
using UnityEngine;
namespace TinyGoose.Tremble
{
public enum FgdClassType
{
Base,
Point,
Brush,
}
public class FgdClass
{
public FgdClass(FgdClassType type, string name, string description)
{
Type = type;
Name = name;
Description = description;
}
public FgdClassType Type { get; }
public string Name { get; }
public string Description { get; set; }
public bool HasModel { get; init; }
public Color? Colour { get; init; }
public string Sprite { get; init; }
public Bounds? Box { get; init; }
private readonly List<string> m_BaseClasses = new();
private readonly List<FgdFieldBase> m_Fields = new();
private readonly List<FgdSpawnFlagField> m_SpawnFlags = new();
public void AddBaseClass(string baseClass) => m_BaseClasses.Add(baseClass);
public void AddBaseClassInterfaces(Type entityType)
{
foreach (Type iface in entityType.GetInterfaces())
{
if (!iface.GetInterfaces().Contains(typeof(ITrembleBaseClass)))
continue;
AddBaseClass(iface.Name.Substring(1)); // Remove 'I'
break;
}
}
public void AddField(FgdFieldBase fgdField)
{
if (fgdField is FgdSpawnFlagField flag)
{
m_SpawnFlags.Add(flag);
}
else
{
m_Fields.Add(fgdField);
}
}
public List<FgdFieldBase> AllFields => m_Fields;
public List<FgdSpawnFlagField> AllSpawnFlags => m_SpawnFlags;
public List<string> BaseClassNames => m_BaseClasses;
public bool HasField(string name) => m_Fields.Any(m => m.FieldName.EqualsInvariant(name, caseSensitive: true));
public bool HasSpawnFlag(int bit) => m_SpawnFlags.Any(sf => sf.Bit == bit);
public void AddComment(string comment)
{
m_Fields.Add(new FgdComment { Comment = comment });
}
public void WriteFgd(StreamWriter stream, float importScale)
{
string typeString = Type switch
{
FgdClassType.Base => "@BaseClass",
FgdClassType.Point => "@PointClass",
FgdClassType.Brush => "@SolidClass",
_ => throw new ArgumentOutOfRangeException()
};
stream.WriteLine(typeString);
if (m_BaseClasses.Count > 0)
{
string baseList = String.Join(", ", m_BaseClasses);
stream.WriteLine($"\tbase({baseList})");
}
if (HasModel)
{
stream.WriteLine($"\tmodel({{ path: \"models/{Name}.md3\", scale: scale }})");
}
if (Colour.HasValue)
{
stream.WriteLine($"\tcolor({Colour.Value.ToStringInvariant()})");
}
if (!Sprite.IsNullOrEmpty())
{
stream.WriteLine($"\tsprite(\"models/{Sprite}.png\")");
}
if (Box.HasValue)
{
Vector3 q3Min = Box.Value.min.UnityToQ3Vector(scale: importScale);
Vector3 q3Max = Box.Value.max.UnityToQ3Vector(scale: importScale);
// From xage on TB Discord: bboxes need to be symmetrical
Vector3 extents = new()
{
x = (Mathf.Abs(q3Min.x) + Mathf.Abs(q3Max.x)),
y = (Mathf.Abs(q3Min.y) + Mathf.Abs(q3Max.y)),
z = (Mathf.Abs(q3Min.z) + Mathf.Abs(q3Max.z))
};
stream.WriteLine($"\tsize({Mathf.RoundToInt(extents.x)} {Mathf.RoundToInt(extents.y)} {Mathf.RoundToInt(extents.z)})");
}
stream.Write($" = {Name} : \"{Description}\"");
// Write fields, and then spawnflags fields
if (m_Fields.Count == 0 && m_SpawnFlags.Count == 0)
{
stream.WriteLine(" []");
}
else
{
stream.WriteLine();
stream.WriteLine("[");
foreach (FgdFieldBase variable in m_Fields)
{
stream.Write("\t");
variable.Write(stream);
}
if (m_SpawnFlags.Count > 0)
{
stream.WriteLine("\tspawnflags(Flags) = ");
stream.WriteLine("\t[");
foreach (FgdSpawnFlagField flag in m_SpawnFlags.OrderBy(sf => sf.Bit))
{
stream.Write("\t\t");
flag.Write(stream);
}
stream.WriteLine("\t]");
}
stream.WriteLine("]");
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e3de2d23101644c9a58c281bfcca7eae
timeCreated: 1709808579
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FgdClass.cs
uploadId: 939664
+89
View File
@@ -0,0 +1,89 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
public abstract class FgdFieldBase
{
public abstract void Write(StreamWriter sw);
public abstract void WriteCSharpClass(StreamWriter sw, int indent);
public abstract void WriteCSharpInterface(StreamWriter sw, int indent);
public virtual string FieldName => null;
public string FriendlyTypeName
{
get
{
string friendlyTypeName = GetType().Name;
if (friendlyTypeName.StartsWithInvariant("Fgd", caseSensitive: true))
{
friendlyTypeName = friendlyTypeName.Remove(0, 3);
}
if (friendlyTypeName.EndsWithInvariant("Field", caseSensitive: true))
{
friendlyTypeName = friendlyTypeName.Remove(friendlyTypeName.Length - 5, 5);
}
return friendlyTypeName;
}
}
protected void WriteClassField(StreamWriter sw, int indent, string unityType, string name, string description, string defaultValue = null, string extraAttributes = null)
{
sw.WriteIndent(indent);
sw.Write("[");
{
if (!TrembleSyncSettings.Get().SyncSerializedFields)
{
sw.Write("Tremble, ");
}
sw.Write($"SerializeField");
if (!description.IsNullOrEmpty())
{
sw.Write($", Tooltip(\"{description}\")");
}
}
if (!extraAttributes.IsNullOrEmpty())
{
sw.Write(", ");
sw.Write(extraAttributes);
}
sw.Write("] ");
sw.Write($"private {unityType} m_{name.ToNamingConvention(NamingConvention.UpperCamelCase)}");
if (defaultValue == null)
{
sw.WriteLine(";");
}
else
{
sw.WriteLine($" = {defaultValue};");
}
}
protected void WriteInterfaceProperty(StreamWriter sw, int indent, string unityType, string name, string description, string attributes = null)
{
sw.WriteIndent(indent);
if (!attributes.IsNullOrEmpty())
{
sw.Write("[");
sw.Write(attributes);
sw.Write("] ");
}
sw.Write($"public {unityType} {name.ToNamingConvention(NamingConvention.UpperCamelCase)} => default;");
sw.WriteLine(description.IsNullOrEmpty() ? "" : $" // \"{description}\"");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f13869dde160409ca3a827a856d24d48
timeCreated: 1698227234
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FgdFieldBase.cs
uploadId: 939664
+86
View File
@@ -0,0 +1,86 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.Globalization;
using UnityEngine;
namespace TinyGoose.Tremble
{
public static class FgdFieldFactory
{
public static FgdFieldBase CreateField(string typename, string name, string description, string defaultValue)
{
switch (typename.ToLowerInvariant())
{
case "float":
return new FgdFloatField
{
Name = name,
Description = description,
DefaultValue = defaultValue != null ? float.Parse(defaultValue, NumberStyles.Any, CultureInfo.InvariantCulture) : 0f
};
case "integer":
return new FgdIntegerField
{
Name = name,
Description = description,
DefaultValue = defaultValue != null ? int.Parse(defaultValue, NumberStyles.Any, CultureInfo.InvariantCulture) : 0
};
case "vector":
if (defaultValue == null || !defaultValue.TryParseQ3Vector(out Vector3 vector))
{
vector = Vector3.zero;
}
return new FgdVectorField
{
Name = name,
Description = description,
DefaultValue = vector
};
case "color":
if (!defaultValue.TryParseQ3Colour(out Color colour))
{
colour = Color.white;
}
return new FgdColourField
{
Name = name,
Description = description,
DefaultColour = colour
};
case "string":
return new FgdStringField
{
Name = name,
Description = description,
DefaultValue = defaultValue
};
case "target_source":
return new FgdTargetSourceField
{
Name = name,
Description = description,
DefaultValue = defaultValue
};
case "target_destination":
return new FgdTargetDestinationField
{
Name = name,
Description = description,
DefaultValue = defaultValue
};
}
return new FgdStringField
{
Name = name,
Description = description,
DefaultValue = defaultValue
};
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b3806920a91d5469f8c9ef060771e83b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FgdFieldFactory.cs
uploadId: 939664
@@ -0,0 +1,130 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Globalization;
namespace TinyGoose.Tremble
{
public static class FgdInferredFieldExtensions
{
public static void TryAddInferredField(this FgdClass fgdClass, string key, string value)
{
// ID property (or "targetname")
string identityProperty = TrembleSyncSettings.Get().IdentityPropertyName;
if (key.EqualsInvariant("targetname") || key.EqualsInvariant(identityProperty, caseSensitive: true))
{
return;
}
// "Target"
if (key.ContainsInvariant(FgdConsts.PROPERTY_TARGET))
{
fgdClass.AddField(new FgdTargetDestinationField
{
Name = key,
Description = $"{key.ToNamingConvention(NamingConvention.UpperCamelCase)} of this Entity"
});
return;
}
// "Origin" - skip
if (key.EqualsInvariant(FgdConsts.PROPERTY_ORIGIN))
{
return;
}
// "Scale" - skip
if (key.EqualsInvariant(FgdConsts.PROPERTY_SCALE))
{
return;
}
// "Parent" - skip
if (key.EqualsInvariant(FgdConsts.PROPERTY_PARENT))
{
return;
}
// Angles - skip
if (key.EqualsInvariant(FgdConsts.PROPERTY_ANGLES))
{
return;
}
// _tb_textures, group, etc - skip
if (key.StartsWithInvariant(TBConsts.TB_PREFIX))
{
return;
}
// Spawnflags
if (key.EqualsInvariant(FgdConsts.PROPERTY_SPAWNFLAGS))
{
if (!int.TryParse(value, out int flags))
return;
int bit = 0;
while (flags > 0)
{
if (!fgdClass.HasSpawnFlag(bit))
{
fgdClass.AddField(new FgdSpawnFlagField
{
Bit = bit,
Description = $"Unknown spawnflag bit #{bit}"
});
}
bit++;
flags >>= 1;
}
return;
}
// Anything else? Try to infer type.
if (IsValueVector(value))
{
fgdClass.AddField(new FgdVectorField
{
Name = key,
Description = $"Vector {key}",
});
}
else if (IsValueFloat(value))
{
fgdClass.AddField(new FgdFloatField
{
Name = key,
Description = $"Float {key}",
});
}
else
{
fgdClass.AddField(new FgdStringField
{
Name = key,
Description = $"String {key}",
});
}
}
private static bool IsValueFloat(string value) => value.TryParseFloat(out float _);
private static bool IsValueVector(string value)
{
if (value.IsNullOrEmpty())
return false;
string[] parts = value.Split(' ');
if (parts.Length != 3)
return false;
return IsValueFloat(parts[0]) && IsValueFloat(parts[1]) && IsValueFloat(parts[2]);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a207e07b3115419e90f7b5092c0ea67f
timeCreated: 1712223098
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FgdInferredFieldExtensions.cs
uploadId: 939664
+283
View File
@@ -0,0 +1,283 @@
//
// 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.Globalization;
using System.IO;
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace TinyGoose.Tremble
{
public record EnumDefinition(string Name, string[] Values);
public static class FgdParser
{
public static void Import(string fgdFilePath)
{
string fgdName = Path.GetFileNameWithoutExtension(fgdFilePath).ToNamingConvention(NamingConvention.UpperCamelCase);
TokenParser tokenParser = new(File.ReadAllText(fgdFilePath));
List<FgdClass> baseClasses = new(16);
List<FgdClass> concreteClasses = new(64);
List<EnumDefinition> enums = new(64);
// Work out type of class and parse
while (!tokenParser.IsAtEnd)
{
string classType = tokenParser.ReadToken(eatAllBracketContent: true).ToString();
switch (classType.ToLowerInvariant())
{
case "@baseclass":
baseClasses.Add(ParseClass(FgdClassType.Base, tokenParser, fgdName, enums));
break;
case "@pointclass":
concreteClasses.Add(ParseClass(FgdClassType.Point, tokenParser, fgdName, enums));
break;
case "@solidclass":
concreteClasses.Add(ParseClass(FgdClassType.Brush, tokenParser, fgdName, enums));
break;
default:
continue;
}
}
string basePath = Path.GetDirectoryName(fgdFilePath);
string baseEnumPath = Path.Combine(basePath, fgdName, "Enums");
string baseEntitiesPath = Path.Combine(basePath, fgdName, "Entities");
DirectoryUtil.CreateAllDirectories(baseEnumPath);
DirectoryUtil.CreateAllDirectories(baseEntitiesPath);
// Write Enums first
foreach (EnumDefinition enumDef in enums)
{
string enumFile = Path.Combine(baseEnumPath, enumDef.Name + ".cs");
enumDef.WriteCSharp(enumFile, fgdName);
}
// ... then base classes ...
foreach (FgdClass baseClass in baseClasses)
{
string namespaceName = fgdName;
string folderPath = baseEntitiesPath;
baseClass.Name.Split('_', out string folderName, out string bareClassName);
if (!folderName.IsNullOrEmpty())
{
namespaceName = $"{fgdName.ToNamingConvention(NamingConvention.UpperCamelCase)}.{folderName.ToNamingConvention(NamingConvention.UpperCamelCase)}";
folderPath = Path.Combine(folderPath, folderName);
}
DirectoryUtil.CreateAllDirectories(folderPath);
string classFile = Path.Combine(folderPath, "I" + bareClassName.ToNamingConvention(NamingConvention.UpperCamelCase) + ".cs");
baseClass.WriteCSharp(classFile, baseClasses, namespaceName);
}
// ... finally concrete classes
foreach (FgdClass concreteClass in concreteClasses)
{
string namespaceName = fgdName;
string folderPath = baseEntitiesPath;
concreteClass.Name.Split('_', out string folderName, out string bareClassName);
if (!folderName.IsNullOrEmpty())
{
namespaceName = $"{fgdName.ToNamingConvention(NamingConvention.UpperCamelCase)}.{folderName.ToNamingConvention(NamingConvention.UpperCamelCase)}";
folderPath = Path.Combine(folderPath, folderName);
}
DirectoryUtil.CreateAllDirectories(folderPath);
string classFile = Path.Combine(folderPath, bareClassName.ToNamingConvention(NamingConvention.UpperCamelCase) + ".cs");
concreteClass.WriteCSharp(classFile, baseClasses, namespaceName);
}
// Refresh code, in editor
#if UNITY_EDITOR
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
#endif
}
private static FgdClass ParseClass(FgdClassType type, TokenParser tokenParser, string fgdNamespace, List<EnumDefinition> outEnumDefinitions)
{
Dictionary<string, string> metadata = new();
while (!tokenParser.PeekToken("=", eatAllBracketContent: true))
{
string metadataString = tokenParser.ReadToken(eatAllBracketContent: true).ToString(); // e.g. base(asfasf, asdgs) or model({f: "fasf"})
metadataString.Split('(', out string key, out string value);
metadata[key.ToLowerInvariant()] = value[..^1];
}
tokenParser.MatchToken("=", eatAllBracketContent: true);
ReadOnlySpan<char> classname = tokenParser.ReadToken();
string classDescription = null;
classname.Split('_', out string _, out string unityClassname);
unityClassname = unityClassname.ToNamingConvention(NamingConvention.UpperCamelCase);
if (tokenParser.PeekToken(":"))
{
tokenParser.ReadToken();
classDescription = tokenParser.ReadToken().ToString();
}
FgdClass newClass = new(type, classname.ToString(), classDescription ?? $"{unityClassname} Class");
// Add base classes
if (metadata.TryGetValue("base", out string bases))
{
foreach (string baseClass in bases.Split(","))
{
newClass.AddBaseClass(baseClass.Trim());
}
}
//TODO(jwf): what about model, colour, size, etc?
if (!tokenParser.PeekToken("[]"))
{
tokenParser.MatchToken("["); // Eat '['
while (!tokenParser.IsAtEnd && !tokenParser.PeekToken("]"))
{
// Get name and type
ReadOnlySpan<char> typeString = tokenParser.ReadToken(eatAllBracketContent: true); // e.g. thing(Integer)
typeString.Split('(', out string varName, out string typename);
typename = typename[..^1];
string description = null;
string defaultValue = null;
// Has description?
if (tokenParser.PeekToken(":"))
{
tokenParser.MatchToken(":");
description = tokenParser.ReadToken().ToString();
}
// Has default value?
if (tokenParser.PeekToken(":"))
{
tokenParser.MatchToken(":");
defaultValue = tokenParser.ReadToken().ToString();
}
// Has multiple selections?
if (tokenParser.PeekToken("="))
{
tokenParser.MatchToken("=");
tokenParser.MatchToken("[");
if (typename.EqualsInvariant("choices"))
{
// Collect values (not necessarily in order, not necessarily sequential)
Dictionary<int, string> values = new(64);
int counter = 0;
while (!tokenParser.PeekToken("]"))
{
if (!int.TryParse(tokenParser.ReadToken(supportCommentsAndQuotes: false), NumberStyles.Any, CultureInfo.InvariantCulture, out int enumValue))
{
enumValue = counter++;
}
tokenParser.MatchToken(":");
string enumValueName = NamingConventionUtil.FromHuman(tokenParser.ReadToken());
values[enumValue] = enumValueName;
}
tokenParser.MatchToken("]");
int maxEnumValue = values.Keys.Max();
string[] valueStrings = new string[maxEnumValue+1];
for (int i = 0; i <= maxEnumValue; i++)
{
valueStrings[i] = values.TryGetValue(i, out string valueName) ? valueName : $"Value_{i}";
}
string enumName = $"{unityClassname.ToNamingConvention(NamingConvention.UpperCamelCase)}{varName.ToNamingConvention(NamingConvention.UpperCamelCase)}";
outEnumDefinitions.Add(new
(
Name: enumName,
Values: valueStrings
));
newClass.AddField(new FgdEnumField
{
Name = varName,
Description = description,
EnumClassName = $"{fgdNamespace}.{enumName}",
DefaultValue = 0,
Values = valueStrings,
});
}
else if (typename.EqualsInvariant("flags"))
{
//TODO(jwf): this assumes all "flags" are "spawnflags" - we should support other flags
while (!tokenParser.PeekToken("]"))
{
int flagBit = (int)Math.Log(int.Parse(tokenParser.ReadToken(), NumberStyles.Any, CultureInfo.InvariantCulture), 2);
tokenParser.MatchToken(":");
ReadOnlySpan<char> flagName = tokenParser.ReadToken();
int flagDefaultValue = 0;
if (tokenParser.PeekToken(":"))
{
tokenParser.MatchToken(":");
flagDefaultValue = int.Parse(tokenParser.ReadToken(), NumberStyles.Any, CultureInfo.InvariantCulture);
}
newClass.AddField(new FgdSpawnFlagField
{
Bit = flagBit,
Description = flagName.ToString(),
DefaultValue = flagDefaultValue == 1
});
}
}
else
{
Debug.LogWarning("Unexpected options list... skipping...");
while (!tokenParser.PeekToken("]"))
{
tokenParser.ReadToken();
}
}
}
else
{
FgdFieldBase field = FgdFieldFactory.CreateField(typename, varName, description, defaultValue);
if (field == null)
{
Debug.LogWarning($"Could not parse field '{varName}' of type '{typename}'");
continue;
}
newClass.AddField(field);
}
}
}
else
{
// Eat "[]"
tokenParser.MatchToken("[]");
}
return newClass;
}
}
}
+18
View File
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: af5ff8df18459483090b8292fe668523
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FgdParser.cs
uploadId: 939664
+239
View File
@@ -0,0 +1,239 @@
//
// 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;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace TinyGoose.Tremble
{
public class FgdWriter
{
// -----------------------------------------------------------------------------------------------------------------------------
// State
// -----------------------------------------------------------------------------------------------------------------------------
private FgdClass m_WorldSpawn;
private readonly List<FgdClass> m_OtherClasses = new();
private readonly List<FgdClass> m_BaseClasses = new();
private readonly string m_VersionString;
private readonly float m_ImportScale;
public FgdWriter(string versionString, TrembleSyncSettings settings)
{
m_VersionString = versionString;
m_ImportScale = settings.ImportScale;
// Add base entities
AddMapBaseEntity();
AddMapPointBaseEntity();
AddMapPrefabBaseEntity();
}
public FgdClass WorldspawnClass
{
get => m_WorldSpawn;
set => m_WorldSpawn = value;
}
public IReadOnlyList<FgdClass> BaseClasses => m_BaseClasses;
public IReadOnlyList<FgdClass> Classes => m_OtherClasses;
public void AddClass(FgdClass entityClass)
{
if (entityClass.Name.Length > 31)
{
Debug.LogError($"Class '{entityClass.Name}' is too long - it cannot be longer than 31 characters." +
" Add a custom [PrefabEntity], [BrushEntity], or [PointEntity] attribute to this MonoBehaviour and set a shorter name!");
}
if (entityClass.Type == FgdClassType.Base)
{
int insertionIdx = m_BaseClasses.BinarySearch(entityClass, Comparer<FgdClass>.Create((c1, c2) => String.Compare(c1.Name, c2.Name, StringComparison.Ordinal)));
if (insertionIdx < 0)
{
m_BaseClasses.Add(entityClass);
}
else
{
m_BaseClasses.Insert(insertionIdx, entityClass);
}
}
else
{
int insertionIdx = m_OtherClasses.BinarySearch(entityClass, Comparer<FgdClass>.Create((c1, c2) => String.Compare(c1.Name, c2.Name, StringComparison.Ordinal)));
if (insertionIdx < 0)
{
m_OtherClasses.Add(entityClass);
}
else
{
m_OtherClasses.Insert(insertionIdx, entityClass);
}
}
}
public void WriteFgd(StreamWriter stream, bool humanReadable = true)
{
if (humanReadable)
{
stream.WriteLine("// TrenchBroom game definition file (.fgd)");
stream.WriteLine($"// Generated by Tremble for Unity v{m_VersionString}");
stream.WriteLine($"// For: {Application.productName}. (c) {DateTime.UtcNow.Year} {Application.companyName}");
stream.WriteLine();
stream.WriteLine();
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
stream.WriteLine("//");
stream.WriteLine("// Base Classes");
stream.WriteLine("//");
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
}
// Base entities
foreach (FgdClass entity in m_BaseClasses)
{
if (humanReadable)
{
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
stream.WriteLine($"// Tremble Base Class '{entity.Name}'");
stream.WriteLine($"// {entity.Description}");
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
}
entity.WriteFgd(stream, m_ImportScale);
if (humanReadable)
{
stream.WriteLine();
stream.WriteLine();
}
}
if (m_WorldSpawn != null)
{
if (humanReadable)
{
stream.WriteLine();
stream.WriteLine();
stream.WriteLine();
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
stream.WriteLine("//");
stream.WriteLine("// worldspawn - World Root Properties");
stream.WriteLine("//");
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
}
m_WorldSpawn.WriteFgd(stream, m_ImportScale);
if (humanReadable)
{
stream.WriteLine();
stream.WriteLine();
}
}
if (humanReadable)
{
stream.WriteLine();
stream.WriteLine();
stream.WriteLine();
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
stream.WriteLine("//");
stream.WriteLine("// Game Entities");
stream.WriteLine("//");
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
stream.WriteLine();
}
foreach (FgdClass entity in m_OtherClasses)
{
if (humanReadable)
{
string type = entity.Type switch
{
FgdClassType.Base => nameof(ITrembleBaseClass),
FgdClassType.Point => "Point Entity",
FgdClassType.Brush => "Brush Entity",
_ => throw new ArgumentOutOfRangeException()
};
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
stream.WriteLine($"// {type}");
stream.WriteLine($"// {entity.Description}");
stream.WriteLine("// ----------------------------------------------------------------------------------------------------");
}
entity.WriteFgd(stream, m_ImportScale);
}
}
// -----------------------------------------------------------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------------------------------------------------------
private void AddMapBaseEntity()
{
FgdClass mapBase = new(FgdClassType.Base, FgdConsts.CLASS_MAP_BASE, "The base class for all map entities");
mapBase.Description = "Base class for common map properties";
mapBase.AddField(new FgdTargetSourceField
{
Name = TrembleSyncSettings.Get().IdentityPropertyName,
Description = "The ID/Name of this entity",
DefaultValue = ""
});
mapBase.AddField(new FgdAnglesField
{
Name = FgdConsts.PROPERTY_ANGLES,
Description = "Rotation Angles",
DefaultValue = Vector3.zero
});
mapBase.AddField(new FgdVectorField
{
Name = FgdConsts.PROPERTY_ORIGIN,
Description = "Spawn position of this entity",
DefaultValue = Vector3.zero,
ConvertToQ3 = true,
ImportScale = m_ImportScale
});
AddClass(mapBase);
}
private void AddMapPointBaseEntity()
{
FgdClass mapBase = new(FgdClassType.Base, FgdConsts.CLASS_MAP_POINT_BASE, "The base class for all Point entities");
mapBase.Description = "Base class for common Point properties";
mapBase.AddField(new FgdTargetDestinationField
{
Name = FgdConsts.PROPERTY_PARENT,
Description = "Parent Transform",
DefaultValue = null
});
AddClass(mapBase);
}
private void AddMapPrefabBaseEntity()
{
FgdClass mapBase = new(FgdClassType.Base, FgdConsts.CLASS_MAP_PREFAB_BASE, "The base class for all Prefab entities");
mapBase.Description = "Base class for common Prefab properties";
mapBase.AddBaseClass(FgdConsts.CLASS_MAP_POINT_BASE);
mapBase.AddField(new FgdVectorField
{
Name = FgdConsts.PROPERTY_SCALE,
Description = "Prefab Scale",
DefaultValue = Vector3.one
});
AddClass(mapBase);
}
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 8ae2428dfb70468ab002a264f8bc17de
timeCreated: 1698227021
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FgdWriter.cs
uploadId: 939664
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 80fdd27eed8a4e8cbca74e05b26cfc12
timeCreated: 1712223157
@@ -0,0 +1,44 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
using UnityEngine;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdAnglesField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public Vector3 DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
if (Mathf.Approximately(DefaultValue.x, DefaultValue.y) && Mathf.Approximately(DefaultValue.y, DefaultValue.z))
{
sw.WriteLine($"{Name}(float) : \"{Description.Replace("\"", "'")}\" : \"{DefaultValue.x.ToStringInvariant()}\"");
}
else
{
sw.WriteLine($"{Name}(float) : \"{Description.Replace("\"", "'")}\" : \"{DefaultValue.ToStringInvariant()}\"");
}
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
{
string defaultValueString = DefaultValue == Vector3.zero
? "Vector3.zero"
: $"new Vector3({DefaultValue.x.ToStringInvariant()}, {DefaultValue.y.ToStringInvariant()}, {DefaultValue.z.ToStringInvariant()})";
WriteClassField(sw, indent, nameof(Vector3), Name, Description, defaultValueString);
}
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, nameof(Vector3), Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 67ec51e0586d4dedaad0c34cef140416
timeCreated: 1712223260
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdAnglesField.cs
uploadId: 939664
@@ -0,0 +1,39 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdBooleanField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public bool DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
string description = Description != null ? Description.Replace("\"", "'") : Name;
sw.WriteLine($"{Name}(choices) : \"{description}\" : {(DefaultValue ? "1" : "0")} = ");
sw.WriteLine("\t[");
{
sw.WriteLine("\t\t0 : \"False\"");
sw.WriteLine("\t\t1 : \"True\"");
}
sw.WriteLine("\t]");
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, "bool", Name, Description, DefaultValue ? "true" : null);
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, "bool", Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 31b88cf082164f41b2a7d78f67919483
timeCreated: 1712223319
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdBooleanField.cs
uploadId: 939664
@@ -0,0 +1,48 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdChoicesIntField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public string[] Choices { get; init; }
public int DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
if (DefaultValue == 0)
{
sw.WriteLine($"{Name}(choices) : \"{Description.Replace("\"", "'")}\" = ");
}
else
{
sw.WriteLine($"{Name}(choices) : \"{Description.Replace("\"", "'")}\" : {DefaultValue} = ");
}
sw.WriteLine("\t[");
{
for (int i = 0; i < Choices.Length; i++)
{
sw.WriteLine($"\t\t{i} : \"{Choices[i]}\"");
}
}
sw.WriteLine("\t]");
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, "int", Name, Description, DefaultValue == 0 ? null : DefaultValue.ToString());
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, "int", Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 8b1808df0f9246d981380501211318d8
timeCreated: 1712223378
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdChoicesIntField.cs
uploadId: 939664
@@ -0,0 +1,48 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdChoicesStringField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public string[] Choices { get; init; }
public string DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
sw.WriteLine($"{Name}(choices) : \"{Description.Replace("\"", "'")}\" : \"{DefaultValue ?? Choices[0]}\" = ");
sw.WriteLine("\t[");
{
foreach (string choice in Choices)
{
if (choice.IsNullOrEmpty())
{
sw.WriteLine("\t\t\"\" : \"(none)\"");
}
else
{
sw.WriteLine($"\t\t\"{choice}\" : \"{choice.ToNamingConvention(NamingConvention.HumanFriendly)}\"");
}
}
}
sw.WriteLine("\t]");
}
//TODO(jwf): support default values?
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, "string", Name, Description);
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, "string", Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6c59811822b3441d835a848021e6ef17
timeCreated: 1712223387
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdChoicesStringField.cs
uploadId: 939664
@@ -0,0 +1,39 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Globalization;
using System.IO;
using UnityEngine;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdColourField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public Color DefaultColour { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
sw.WriteLine($"{Name}(color) : \"{Description.Replace("\"", "'")}\" : \"{DefaultColour.ToStringInvariant()}\"");
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
{
string defaultValueString = DefaultColour == Color.white
? "Color.white"
: $"new Color({DefaultColour.r.ToStringInvariant()}, {DefaultColour.g.ToStringInvariant()}, {DefaultColour.b.ToStringInvariant()})";
WriteClassField(sw, indent, nameof(Color), Name, Description, DefaultColour == Color.white ? null : defaultValueString);
}
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, nameof(Color), Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d14b410b2bd14d4e8a9a0b9e4cc5d984
timeCreated: 1712223272
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdColourField.cs
uploadId: 939664
@@ -0,0 +1,33 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdComment : FgdFieldBase
{
public string Comment { get; init; }
public override void Write(StreamWriter sw)
{
sw.WriteLine($"// {Comment}");
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
{
sw.WriteIndent(indent);
sw.WriteLine($"// {Comment}");
}
public override void WriteCSharpInterface(StreamWriter sw, int indent)
{
sw.WriteIndent(indent);
sw.WriteLine($"// {Comment}");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a46440a8a9254de1aad67d79e4cb1509
timeCreated: 1712223182
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdComment.cs
uploadId: 939664
@@ -0,0 +1,124 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
using System.Linq;
using UnityEngine;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdEnumField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public string EnumClassName { get; init; }
public int DefaultValue { get; init; }
public string[] Values { get; set; }
public override string FieldName => Name;
private string[] EnumValuesOrCached
{
get
{
if (Values == null || Values.Length == 0)
{
if (EnumClassName.ContainsInvariant("+"))
{
EnumClassName.Split('+', out string className, out string enumName);
Debug.LogError($"Couldn't use {enumName} enum. Enums embedded in classes are "
+ $"not supported by Tremble :(. Move your '{enumName}' enum "
+ $"outside of {className} to use it!");
return Array.Empty<string>();
}
Type enumType = TypeDatabase.GetType(EnumClassName);
string[] names = Enum.GetNames(enumType);
int[] values = Enum.GetValues(enumType).Cast<int>().ToArray();
int maxValue = values.Max();
Values = new string[maxValue + 1];
for (int i = 0; i <= maxValue; i++)
{
int nameIdx = Array.FindIndex(values, v => v == i);
if (nameIdx == -1)
{
Values[i] = "_";
}
else
{
Values[i] = names[nameIdx];
}
}
}
return Values;
}
}
public override void Write(StreamWriter sw)
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
if (syncSettings.SyncEnumsAsStringValue)
{
string[] values = EnumValuesOrCached;
string defaultString = values.Length > DefaultValue && !values[DefaultValue].EqualsInvariant("_") ? values[DefaultValue] : "(none)";
sw.WriteLine($"{Name}(choices) : \"{Description.Replace("\"", "'")}\" : \"{defaultString}\" = ");
}
else
{
if (DefaultValue == 0)
{
sw.WriteLine($"{Name}(choices) : \"{Description.Replace("\"", "'")}\"");
}
else
{
sw.WriteLine($"{Name}(choices) : \"{Description.Replace("\"", "'")}\" : {DefaultValue} = ");
}
}
sw.WriteLine("\t[");
{
for (int i = 0; i < EnumValuesOrCached.Length; i++)
{
string value = EnumValuesOrCached[i];
if (value.EqualsInvariant("_"))
continue;
if (syncSettings.SyncEnumsAsStringValue)
{
sw.WriteLine($"\t\t\"{value}\" : \"{value}\"");
}
else
{
sw.WriteLine($"\t\t{i} : \"{value}\"");
}
}
}
sw.WriteLine("\t]");
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
{
string enumTypeName = EnumClassName;
int lastDot = enumTypeName.LastIndexOf('.');
if (lastDot != -1)
{
enumTypeName = enumTypeName.Substring(lastDot + 1);
}
string defaultValueString = $"{EnumClassName}.{EnumValuesOrCached[DefaultValue]}";
WriteClassField(sw, indent, enumTypeName, Name, Description, defaultValueString);
}
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, EnumClassName, Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7cb83d47f4a14179baba19b9197b90ea
timeCreated: 1712223369
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdEnumField.cs
uploadId: 939664
@@ -0,0 +1,97 @@
//
// 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;
using UnityEngine;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdFlagsField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public string EnumClassName { get; init; }
public string[] EnumNames { get; set; }
public override string FieldName => Name;
private string[] EnumNamesOrCached
{
get
{
if (EnumNames == null || EnumNames.Length == 0)
{
if (EnumClassName.ContainsInvariant("+"))
{
EnumClassName.Split('+', out string className, out string enumName);
Debug.LogError($"Couldn't use {enumName} enum. Enums embedded in classes are "
+ $"not supported by Tremble :(. Move your '{enumName}' enum "
+ $"outside of {className} to use it!");
return Array.Empty<string>();
}
Type enumType = TypeDatabase.GetType(EnumClassName);
string[] names = Enum.GetNames(enumType);
int[] values = Enum.GetValues(enumType).Cast<int>().ToArray();
int maxValue = values.Max();
EnumNames = new string[maxValue + 1];
for (int i = 0; i <= maxValue; i++)
{
int nameIdx = Array.FindIndex(values, v => v == i);
if (nameIdx == -1)
{
EnumNames[i] = "_";
}
else
{
EnumNames[i] = names[nameIdx];
}
}
}
return EnumNames;
}
}
public override void Write(StreamWriter sw)
{
sw.WriteLine($"{Name}(flags) = ");
sw.WriteLine("\t[");
{
for (int i = 0; i < EnumNamesOrCached.Length; i++)
{
string name = EnumNamesOrCached[i];
if (name.EqualsInvariant("_"))
continue;
sw.WriteLine($"\t\t{i} : \"{name}\" : 0");
}
}
sw.WriteLine("\t]");
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
{
string enumTypeName = EnumClassName;
int lastDot = enumTypeName.LastIndexOf('.');
if (lastDot != -1)
{
enumTypeName = enumTypeName.Substring(lastDot + 1);
}
WriteClassField(sw, indent, enumTypeName, Name, Description, "default");
}
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, EnumClassName, Name, Description, "Flags");
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 86f42cfbe8b54eeeaf7d8102766a3fba
timeCreated: 1774358034
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdFlagsField.cs
uploadId: 939664
@@ -0,0 +1,38 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdFloatField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public float DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
if (DefaultValue == 0f)
{
sw.WriteLine($"{Name}(float) : \"{Description.Replace("\"", "'")}\"");
}
else
{
sw.WriteLine($"{Name}(float) : \"{Description.Replace("\"", "'")}\" : \"{DefaultValue.ToStringInvariant()}\"");
}
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, "float", Name, Description, DefaultValue == 0f ? null : DefaultValue.ToStringInvariant() + "f");
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, "float", Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 56cba705d7a240a8b2b05ae6edb8ddeb
timeCreated: 1712223287
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdFloatField.cs
uploadId: 939664
@@ -0,0 +1,38 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdIntegerField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public int DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
if (DefaultValue == 0)
{
sw.WriteLine($"{Name}(integer) : \"{Description.Replace("\"", "'")}\"");
}
else
{
sw.WriteLine($"{Name}(integer) : \"{Description.Replace("\"", "'")}\" : {DefaultValue}");
}
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, "int", Name, Description, DefaultValue == 0 ? null : DefaultValue.ToString());
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, "int", Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 96bcc84614cf430590935b78d98f0ca0
timeCreated: 1712223311
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdIntegerField.cs
uploadId: 939664
@@ -0,0 +1,33 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdSpawnFlagField : FgdFieldBase
{
public int Bit { get; init; }
public string Description { get; init; }
public bool DefaultValue { get; init; }
public override string FieldName => NamingConventionUtil.FromHuman(Description);
public override void Write(StreamWriter sw)
{
int bitDefaultValue = 1 << Bit;
sw.WriteLine($"{bitDefaultValue} : \"{Description.Replace("\"", "'")}\" : {(DefaultValue ? "1" : "0")}");
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, "bool", FieldName, Description, DefaultValue ? "true" : null, extraAttributes: $"SpawnFlags({Bit})");
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, "bool", FieldName, Description, attributes: $"SpawnFlags({Bit})");
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d5d3344597c842eb8c287ffb9e4e6854
timeCreated: 1712223402
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdSpawnFlagField.cs
uploadId: 939664
@@ -0,0 +1,38 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdStringField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public string DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
if (DefaultValue.IsNullOrEmpty())
{
sw.WriteLine($"{Name}(string) : \"{Description.Replace("\"", "'")}\"");
}
else
{
sw.WriteLine($"{Name}(string) : \"{Description.Replace("\"", "'")}\" : \"{DefaultValue}\"");
}
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, "string", Name, Description, DefaultValue.IsNullOrEmpty() ? null : $"\"{DefaultValue}\"");
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, "string", Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b475d7d5cead4105af48ebd7252e50db
timeCreated: 1712223334
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdStringField.cs
uploadId: 939664
@@ -0,0 +1,40 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
using UnityEngine;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdTargetDestinationField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public string DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
if (DefaultValue.IsNullOrEmpty())
{
sw.WriteLine($"{Name}(target_destination) : \"{Description.Replace("\"", "'")}\"");
}
else
{
sw.WriteLine($"{Name}(target_destination) : \"{Description.Replace("\"", "'")}\" : \"{DefaultValue}\"");
}
}
//TODO(jwf): support default values?
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, nameof(Transform), Name, Description);
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, nameof(Transform), Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ce6a69e950e34780a3a25026a94798b1
timeCreated: 1712223349
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdTargetDestinationField.cs
uploadId: 939664
@@ -0,0 +1,38 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdTargetSourceField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public string DefaultValue { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
if (DefaultValue.IsNullOrEmpty())
{
sw.WriteLine($"{Name}(target_source) : \"{Description.Replace("\"", "'")}\"");
}
else
{
sw.WriteLine($"{Name}(target_source) : \"{Description.Replace("\"", "'")}\" : \"{DefaultValue}\"");
}
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
=> WriteClassField(sw, indent, "string", Name, Description, DefaultValue.IsNullOrEmpty() ? null : $"\"{DefaultValue}\"");
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, "string", Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ef716bdc4952478b80d299a922219b1a
timeCreated: 1712223341
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdTargetSourceField.cs
uploadId: 939664
@@ -0,0 +1,53 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.IO;
using UnityEngine;
namespace TinyGoose.Tremble
{
[Serializable]
public class FgdVectorField : FgdFieldBase
{
public string Name { get; init; }
public string Description { get; init; }
public Vector3 DefaultValue { get; init; }
public bool ConvertToQ3 { get; init; }
public float ImportScale { get; init; }
public override string FieldName => Name;
public override void Write(StreamWriter sw)
{
Vector3 value = DefaultValue;
if (ConvertToQ3)
{
value = new Vector3(-value.x, -value.z, value.y) / ImportScale;
}
if (Mathf.Approximately(value.x, value.y) && Mathf.Approximately(value.y, value.z))
{
sw.WriteLine($"{Name}(float) : \"{Description.Replace("\"", "'")}\" : \"{value.x.ToStringInvariant()}\"");
}
else
{
sw.WriteLine($"{Name}(float) : \"{Description.Replace("\"", "'")}\" : \"{value.ToStringInvariant()}\"");
}
}
public override void WriteCSharpClass(StreamWriter sw, int indent)
{
string defaultValueString = DefaultValue == Vector3.zero
? "Vector3.zero"
: $"new Vector3({DefaultValue.x.ToStringInvariant()}, {DefaultValue.y.ToStringInvariant()}, {DefaultValue.z.ToStringInvariant()})";
WriteClassField(sw, indent, nameof(Vector3), Name, Description, defaultValueString);
}
public override void WriteCSharpInterface(StreamWriter sw, int indent)
=> WriteInterfaceProperty(sw, indent, nameof(Vector3), Name, Description);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 078725a3929747ceb3f93ad3e68e36d5
timeCreated: 1712223216
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/FieldTypes/FgdVectorField.cs
uploadId: 939664
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f8e7b72eacf0406e90949f0230906787
timeCreated: 1712323557
@@ -0,0 +1,330 @@
//
// 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("}");
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ae5ecd0fb11d4baf988975b57deb3f53
timeCreated: 1712132457
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/Writers/EntityClassWriter.cs
uploadId: 939664
@@ -0,0 +1,69 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.IO;
namespace TinyGoose.Tremble
{
public static class EntityEnumWriter
{
public static void WriteCSharp(this EnumDefinition enumDefinition, string file, string namespaceOrNull)
{
using FileStream classFile = new(file, FileMode.Create, FileAccess.Write);
using StreamWriter writer = new(classFile);
writer.WriteLine($"// Enum '{enumDefinition.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("{");
}
if (hasNamespace)
{
writer.Write("\t");
}
writer.WriteLine($"public enum {enumDefinition.Name}");
if (hasNamespace)
{
writer.Write("\t");
}
writer.WriteLine("{");
for (int i = 0; i < enumDefinition.Values.Length; i++)
{
string value = enumDefinition.Values[i];
writer.Write("\t");
if (hasNamespace)
{
writer.Write("\t");
}
writer.WriteLine($"{value} = {i},");
}
if (hasNamespace)
{
writer.Write("\t");
}
writer.WriteLine("}");
if (hasNamespace)
{
writer.WriteLine("}");
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a0ba86bd49f1a4af5b7b8522b5e470de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/IO/FGD/Writers/EntityEnumWriter.cs
uploadId: 939664