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

239 lines
7.6 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;
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);
}
}
}