Second commit
This commit is contained in:
commit
36840f3ad4
999 files changed
+61364
No files matched your search
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e90b79b320149d0a8e107ccecdfa7f5
|
||||
timeCreated: 1710848891
|
||||
@@ -0,0 +1,461 @@
|
||||
//
|
||||
// This file is part of the Tremble package by Tiny Goose.
|
||||
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
|
||||
// This class based on BSP Map Tools for Unity by John Evans (evans3d512@gmail.com)
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TinyGoose.Tremble
|
||||
{
|
||||
public static class BspParser
|
||||
{
|
||||
public static MapBsp Parse(string bspPath, float importScale, BspOptions options = null, MapGroupData[] foundGroups = null)
|
||||
{
|
||||
StreamReadIO reader = new(bspPath);
|
||||
|
||||
string signature = new (reader.ReadChars(4));
|
||||
int version = reader.ReadInt32();
|
||||
if (signature != "IBSP" || version != 46)
|
||||
{
|
||||
Debug.LogWarning($"BSP Importer: Unknown signature or version. Expected IBSP, 46. Got {signature}, {version}");
|
||||
}
|
||||
|
||||
int entitiesOffset = reader.ReadInt32();
|
||||
int entitiesLength = reader.ReadInt32(); // 16
|
||||
int texturesOffset = reader.ReadInt32();
|
||||
int texturesLength = reader.ReadInt32(); // 24
|
||||
|
||||
int planesOffset = reader.ReadInt32();
|
||||
int planesLength = reader.ReadInt32();
|
||||
int nodesOffset = reader.ReadInt32();
|
||||
int nodesLength = reader.ReadInt32();
|
||||
int leavesOffset = reader.ReadInt32();
|
||||
int leavesLength = reader.ReadInt32();
|
||||
int leafFacesOffset = reader.ReadInt32();
|
||||
int leaffacesLength = reader.ReadInt32();
|
||||
|
||||
reader.Seek(64);
|
||||
int modelsOffset = reader.ReadInt32();
|
||||
int modelsLength = reader.ReadInt32();
|
||||
reader.Seek(88); // Vertices
|
||||
int verticesOffset = reader.ReadInt32();
|
||||
int verticesLength = reader.ReadInt32();
|
||||
int mVerticesOffset = reader.ReadInt32();
|
||||
int mVerticesLength = reader.ReadInt32();
|
||||
reader.Seek(112); // Faces
|
||||
int facesOffset = reader.ReadInt32();
|
||||
int facesLength = reader.ReadInt32();
|
||||
|
||||
int vertexCount = verticesLength / 44;
|
||||
int mVertexCount = mVerticesLength / 4;
|
||||
int faceCount = facesLength / 104;
|
||||
int texCount = texturesLength / 72;
|
||||
int modelCount = modelsLength / 40;
|
||||
|
||||
int planesCount = planesLength / 16;
|
||||
int nodesCount = nodesLength / 36;
|
||||
int leavesCount = leavesLength / 48;
|
||||
int leafFacesCount = leaffacesLength / 4;
|
||||
|
||||
MapBsp newMapBsp = new() {
|
||||
MapName = Path.GetFileNameWithoutExtension(bspPath),
|
||||
ImportScale = importScale,
|
||||
Options = options ?? new(),
|
||||
|
||||
TexInfos = new TexInfo[texCount],
|
||||
Planes = new Plane[planesCount],
|
||||
Nodes = new BspNode[nodesCount],
|
||||
Leaves = new BspLeaf[leavesCount],
|
||||
LeafFaces = new int[leafFacesCount],
|
||||
|
||||
Models = new BspModel[modelCount],
|
||||
Vertices = new BspVertex[vertexCount],
|
||||
Faces = new BspFace[faceCount],
|
||||
MeshVertices = new int[mVertexCount],
|
||||
Entities = new(),
|
||||
|
||||
UsedFaces = new bool[faceCount],
|
||||
|
||||
Groups = foundGroups,
|
||||
};
|
||||
|
||||
|
||||
reader.Seek(texturesOffset);
|
||||
for (int i = 0; i < texCount; i++)
|
||||
{
|
||||
string tempName = reader.ReadString(64);
|
||||
if (tempName.StartsWithInvariant("textures/"))
|
||||
{
|
||||
tempName = tempName.Substring("textures/".Length);
|
||||
}
|
||||
|
||||
newMapBsp.TexInfos[i] = new()
|
||||
{
|
||||
Name = tempName,
|
||||
Flags = reader.ReadInt32(),
|
||||
Contents = reader.ReadInt32(),
|
||||
};
|
||||
}
|
||||
|
||||
reader.Seek(planesOffset);
|
||||
for (int i = 0; i < planesCount; i++)
|
||||
{
|
||||
Vector3 planeNormal = reader.ReadVector3(convertToQ3: true);
|
||||
|
||||
newMapBsp.Planes[i] = new()
|
||||
{
|
||||
normal = planeNormal,
|
||||
distance = reader.ReadSingle() * importScale,
|
||||
};
|
||||
}
|
||||
|
||||
reader.Seek(leavesOffset);
|
||||
for (int i = 0; i < leavesCount; i++)
|
||||
{
|
||||
newMapBsp.Leaves[i] = new()
|
||||
{
|
||||
VisCluster = reader.ReadInt32(),
|
||||
PortalArea = reader.ReadInt32(),
|
||||
Min = reader.ReadVector3Int(),
|
||||
Max = reader.ReadVector3Int(),
|
||||
LeafFaceStartIdx = reader.ReadInt32(),
|
||||
NumLeafFaces = reader.ReadInt32(),
|
||||
LeafBrushStartIdx = reader.ReadInt32(),
|
||||
NumLeafBrushes = reader.ReadInt32(),
|
||||
};
|
||||
}
|
||||
|
||||
reader.Seek(nodesOffset);
|
||||
for (int i = 0; i < nodesCount; i++)
|
||||
{
|
||||
newMapBsp.Nodes[i] = new()
|
||||
{
|
||||
PlaneIndex = reader.ReadInt32(),
|
||||
Child1 = reader.ReadInt32(),
|
||||
Child2 = reader.ReadInt32(),
|
||||
Min = reader.ReadVector3Int(),
|
||||
Max = reader.ReadVector3Int(),
|
||||
};
|
||||
}
|
||||
|
||||
reader.Seek(leafFacesOffset);
|
||||
for (int i = 0; i < leafFacesCount; i++)
|
||||
{
|
||||
newMapBsp.LeafFaces[i] = reader.ReadInt32();
|
||||
}
|
||||
|
||||
reader.Seek(modelsOffset);
|
||||
for (int i = 0; i < modelCount; i++)
|
||||
{
|
||||
newMapBsp.Models[i] = new()
|
||||
{
|
||||
Min = reader.ReadVector3(convertToQ3: true, scale: importScale),
|
||||
Max = reader.ReadVector3(convertToQ3: true, scale: importScale),
|
||||
StartFaceIdx = reader.ReadInt32(),
|
||||
NumFaces = reader.ReadInt32(),
|
||||
StartBrushIdx = reader.ReadInt32(),
|
||||
NumBrushes = reader.ReadInt32(),
|
||||
};
|
||||
}
|
||||
|
||||
reader.Seek(verticesOffset);
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
newMapBsp.Vertices[i] = new()
|
||||
{
|
||||
Position = reader.ReadVector3(convertToQ3: true, scale: importScale),
|
||||
TexCoord = reader.ReadUV(),
|
||||
LightmapCoord = reader.ReadUV(),
|
||||
Normal = Vector3.Normalize(reader.ReadVector3(convertToQ3: true)),
|
||||
Color = reader.ReadColour(),
|
||||
};
|
||||
}
|
||||
|
||||
reader.Seek(facesOffset);
|
||||
for (int i = 0; i < faceCount; i++)
|
||||
{
|
||||
newMapBsp.Faces[i] = new()
|
||||
{
|
||||
TexID = reader.ReadInt32(),
|
||||
Effect = reader.ReadInt32(),
|
||||
Type = reader.ReadInt32(),
|
||||
VertexStartIdx = reader.ReadInt32(),
|
||||
NumVertices = reader.ReadInt32(),
|
||||
MeshVertexStartIdx = reader.ReadInt32(),
|
||||
NumMeshVertices = reader.ReadInt32(),
|
||||
LightmapIndex = reader.ReadInt32(),
|
||||
LightmapStart = reader.ReadVector2Int(),
|
||||
LightmapSize = reader.ReadVector2Int(),
|
||||
LightmapOrigin = reader.ReadVector3Int(),
|
||||
LightmapVector1 = reader.ReadVector3(),
|
||||
LightmapVector2 = reader.ReadVector3(),
|
||||
Normal = reader.ReadVector3(convertToQ3: true),
|
||||
Size = reader.ReadVector2Int(),
|
||||
};
|
||||
}
|
||||
|
||||
reader.Seek(mVerticesOffset);
|
||||
for (int i = 0; i < mVertexCount; i++)
|
||||
{
|
||||
newMapBsp.MeshVertices[i] = reader.ReadInt32();
|
||||
}
|
||||
|
||||
// Read entity key/value pairs
|
||||
reader.Seek(entitiesOffset);
|
||||
|
||||
List<string> kvPairs = new();
|
||||
|
||||
int entityNumber = 0;
|
||||
int endOfEntities = entitiesOffset + entitiesLength - 1;
|
||||
int charsToRead = endOfEntities - reader.Position;
|
||||
string entities = new(reader.ReadChars(charsToRead));
|
||||
|
||||
TokenParser tokenParser = new(entities);
|
||||
do
|
||||
{
|
||||
ReadOnlySpan<char> token = tokenParser.ReadToken();
|
||||
if (token == null || token.Length == 0)
|
||||
{
|
||||
kvPairs.Add("");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (token[0])
|
||||
{
|
||||
case '{':
|
||||
{
|
||||
continue;
|
||||
}
|
||||
case '}':
|
||||
{
|
||||
Dictionary<string, string> entries = new();
|
||||
for (int kvIdx = 0; kvIdx < kvPairs.Count; kvIdx += 2)
|
||||
{
|
||||
entries.Add(kvPairs[kvIdx], kvPairs[kvIdx+1]);
|
||||
}
|
||||
|
||||
newMapBsp.Entities.Add(new() { SerialNumber = entityNumber++, Entries = entries, ImportScale = newMapBsp.ImportScale});
|
||||
kvPairs.Clear();
|
||||
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
{
|
||||
kvPairs.Add(token.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!tokenParser.IsAtEnd);
|
||||
|
||||
return newMapBsp;
|
||||
}
|
||||
|
||||
private static float GetTriangleAreaSqr(in Vector3 v1, in Vector3 v2, in Vector3 v3)
|
||||
{
|
||||
// Sqr Magnitudes inlined!
|
||||
float ax = v1.x - v2.x;
|
||||
float ay = v1.y - v2.y;
|
||||
float az = v1.z - v2.z;
|
||||
float a = ax * ax + ay * ay + az * az;
|
||||
|
||||
float bx = v2.x - v3.x;
|
||||
float by = v2.y - v3.y;
|
||||
float bz = v2.z - v3.z;
|
||||
float b = bx * bx + by * by + bz * bz;
|
||||
|
||||
float cx = v2.x - v3.x;
|
||||
float cy = v2.y - v3.y;
|
||||
float cz = v2.z - v3.z;
|
||||
float c = cx * cx + cy * cy + cz * cz;
|
||||
|
||||
float s = (a + b + c) * 0.25f;
|
||||
|
||||
float surfAreaSqr = Mathf.Sqrt(s * (s - a) * (s - b) * (s - c));
|
||||
return float.IsNaN(surfAreaSqr) ? 0f : surfAreaSqr;
|
||||
}
|
||||
|
||||
public static void GetFaceList(MapBsp mapBsp, BspLeaf leaf, List<int> receiver)
|
||||
{
|
||||
int faceMin = mapBsp.Models[0].StartFaceIdx;
|
||||
int faceMax = faceMin + mapBsp.Models[0].NumFaces;
|
||||
|
||||
for (int faceOffset = leaf.LeafFaceStartIdx; faceOffset < leaf.LeafFaceStartIdx + leaf.NumLeafFaces; faceOffset++)
|
||||
{
|
||||
int leafFaceIdx = mapBsp.LeafFaces[faceOffset];
|
||||
|
||||
if (leafFaceIdx < faceMin || leafFaceIdx > faceMax)
|
||||
continue;
|
||||
|
||||
if (mapBsp.UsedFaces[leafFaceIdx])
|
||||
continue;
|
||||
|
||||
receiver.Add(leafFaceIdx);
|
||||
mapBsp.UsedFaces[leafFaceIdx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetFaceList(MapBsp mapBsp, BspNode node, List<int> receiver)
|
||||
{
|
||||
if (node.Child1 < 0)
|
||||
{
|
||||
GetFaceList(mapBsp, mapBsp.Leaves[-node.Child1 - 1], receiver);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetFaceList(mapBsp, mapBsp.Nodes[node.Child1], receiver);
|
||||
}
|
||||
|
||||
if (node.Child2 < 0)
|
||||
{
|
||||
GetFaceList(mapBsp, mapBsp.Leaves[-node.Child2 - 1], receiver);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetFaceList(mapBsp, mapBsp.Nodes[node.Child2], receiver);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetSurfaceAreaSqr(MapBsp mapBsp, BspLeaf leaf, ref float receiver, HashSet<int> faceList)
|
||||
{
|
||||
int faceMin = mapBsp.Models[0].StartFaceIdx;
|
||||
int faceMax = faceMin + mapBsp.Models[0].NumFaces;
|
||||
|
||||
for (int faceOffset = leaf.LeafFaceStartIdx; faceOffset < leaf.LeafFaceStartIdx + leaf.NumLeafFaces; faceOffset++)
|
||||
{
|
||||
if (mapBsp.LeafFaces[faceOffset] < faceMin || mapBsp.LeafFaces[faceOffset] > faceMax)
|
||||
continue;
|
||||
|
||||
int leafFaceIdx = mapBsp.LeafFaces[faceOffset];
|
||||
if (faceList.Contains(leafFaceIdx))
|
||||
continue;
|
||||
|
||||
BspFace meshFace = mapBsp.Faces[leafFaceIdx];
|
||||
for (int vertexOffset = meshFace.MeshVertexStartIdx; vertexOffset < meshFace.MeshVertexStartIdx + meshFace.NumMeshVertices; vertexOffset += 3)
|
||||
{
|
||||
receiver += GetTriangleAreaSqr
|
||||
(
|
||||
mapBsp.Vertices[mapBsp.MeshVertices[vertexOffset + 0] + meshFace.VertexStartIdx].Position,
|
||||
mapBsp.Vertices[mapBsp.MeshVertices[vertexOffset + 1] + meshFace.VertexStartIdx].Position,
|
||||
mapBsp.Vertices[mapBsp.MeshVertices[vertexOffset + 2] + meshFace.VertexStartIdx].Position
|
||||
);
|
||||
}
|
||||
|
||||
faceList.Add(leafFaceIdx);
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetSurfaceAreaSqr(MapBsp mapBsp, BspNode node, ref float receiver, HashSet<int> faceList)
|
||||
{
|
||||
if (node.Child1 < 0)
|
||||
{
|
||||
GetSurfaceAreaSqr(mapBsp, mapBsp.Leaves[-node.Child1 - 1], ref receiver, faceList);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetSurfaceAreaSqr(mapBsp, mapBsp.Nodes[node.Child1], ref receiver, faceList);
|
||||
}
|
||||
|
||||
if (node.Child2 < 0)
|
||||
{
|
||||
GetSurfaceAreaSqr(mapBsp, mapBsp.Leaves[-node.Child2 - 1], ref receiver, faceList);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetSurfaceAreaSqr(mapBsp, mapBsp.Nodes[node.Child2], ref receiver, faceList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void DumpToFile(this MapBsp mapBsp, string file)
|
||||
{
|
||||
using FileStream fs = new(file, FileMode.Create, FileAccess.Write);
|
||||
using StreamWriter sw = new(fs);
|
||||
|
||||
sw.WriteLine($"m_TexInfos[{mapBsp.TexInfos.Length}]:");
|
||||
for (int i = 0; i < mapBsp.TexInfos.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.TexInfos[i].Contents} {mapBsp.TexInfos[i].Flags} {mapBsp.TexInfos[i].Name}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.Planes[{mapBsp.Planes.Length}]:");
|
||||
for (int i = 0; i < mapBsp.Planes.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.Planes[i].normal} {mapBsp.Planes[i].distance}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.Nodes[{mapBsp.Nodes.Length}]:");
|
||||
for (int i = 0; i < mapBsp.Nodes.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.Nodes[i].Child1} {mapBsp.Nodes[i].Child2} {mapBsp.Nodes[i].PlaneIndex} {mapBsp.Nodes[i].Min} {mapBsp.Nodes[i].Max}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.Leaves[{mapBsp.Leaves.Length}]:");
|
||||
for (int i = 0; i < mapBsp.Leaves.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.Leaves[i].Min} {mapBsp.Leaves[i].Max} {mapBsp.Leaves[i].LeafBrushStartIdx} {mapBsp.Leaves[i].NumLeafBrushes} {mapBsp.Leaves[i].LeafFaceStartIdx} {mapBsp.Leaves[i].NumLeafFaces}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.LeafFaces[{mapBsp.LeafFaces.Length}]:");
|
||||
for (int i = 0; i < mapBsp.LeafFaces.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.LeafFaces[i]}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.UsedFaces[{mapBsp.UsedFaces.Length}]:");
|
||||
for (int i = 0; i < mapBsp.UsedFaces.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.UsedFaces[i]}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.Models[{mapBsp.Models.Length}]:");
|
||||
for (int i = 0; i < mapBsp.Models.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.Models[i].Min} {mapBsp.Models[i].Max} {mapBsp.Models[i].StartBrushIdx} {mapBsp.Models[i].NumBrushes} {mapBsp.Models[i].StartFaceIdx} {mapBsp.Models[i].NumFaces}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.Vertices[{mapBsp.Vertices.Length}]:");
|
||||
for (int i = 0; i < mapBsp.Vertices.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.Vertices[i].Position} {mapBsp.Vertices[i].Normal} {mapBsp.Vertices[i].Color} {mapBsp.Vertices[i].TexCoord} {mapBsp.Vertices[i].LightmapCoord}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.Faces[{mapBsp.Faces.Length}]:");
|
||||
for (int i = 0; i < mapBsp.Faces.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.Faces[i].VertexStartIdx} {mapBsp.Faces[i].NumVertices} {mapBsp.Faces[i].MeshVertexStartIdx} {mapBsp.Faces[i].NumMeshVertices} {mapBsp.Faces[i].Type}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.MeshVertices[{mapBsp.MeshVertices.Length}]:");
|
||||
for (int i = 0; i < mapBsp.MeshVertices.Length; i++)
|
||||
{
|
||||
sw.WriteLine($" [{i}]: {mapBsp.MeshVertices[i]}");
|
||||
}
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine($"bsp.Entities[{mapBsp.Entities.Count}]:");
|
||||
for (int i = 0; i < mapBsp.Entities.Count; i++)
|
||||
{
|
||||
sw.Write($" [{i}]: {mapBsp.Entities[i].GetClassname()} {{");
|
||||
foreach (string key in mapBsp.Entities[i].Entries.Keys)
|
||||
{
|
||||
sw.Write($" {key}=\"{mapBsp.Entities[i].Entries[key]}\"");
|
||||
}
|
||||
sw.WriteLine(" }");
|
||||
}
|
||||
sw.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71af40a1b7d341a6a9a72e17150e1a52
|
||||
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/BSP/BspParser.cs
|
||||
uploadId: 939664
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 377682f99735490bb297fbe22cf1cd1a
|
||||
timeCreated: 1710848878
|
||||
@@ -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("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17bd3f5d03144731a1a86bfdc8852b41
|
||||
timeCreated: 1711272095
|
||||
@@ -0,0 +1,294 @@
|
||||
//
|
||||
// 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.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TinyGoose.Tremble.Editor
|
||||
{
|
||||
public class MapBrushFace
|
||||
{
|
||||
// public Vector3 Position { get; init; }
|
||||
// public Vector3 Normal { get; init; }
|
||||
public Vector3 P1 { get; set; }
|
||||
public Vector3 P2 { get; set; }
|
||||
public Vector3 P3 { get; set; }
|
||||
public string TextureName { get; set; }
|
||||
public float[] TexU { get; init; }
|
||||
public float[] TexV { get; init; }
|
||||
public float Rotation { get; init; }
|
||||
public Vector2 Scale { get; init; }
|
||||
}
|
||||
|
||||
public class MapBrush
|
||||
{
|
||||
public List<MapBrushFace> Faces { get; init; }
|
||||
}
|
||||
|
||||
public class MapEntity
|
||||
{
|
||||
public Dictionary<string, string> Entries { get; init; }
|
||||
public List<MapBrush> Brushes { get; init; }
|
||||
|
||||
public string Classname => Entries.GetValueOrDefault("classname", null);
|
||||
}
|
||||
|
||||
public class Map
|
||||
{
|
||||
public Dictionary<string, string> Metadata { get; init; }
|
||||
public List<MapEntity> Entities { get; init; }
|
||||
|
||||
public void WriteTo(string path)
|
||||
{
|
||||
using FileStream fs = new(path, FileMode.Create, FileAccess.Write);
|
||||
using StreamWriter sw = new(fs);
|
||||
|
||||
sw.WriteLine($"// Game: {TrembleConsts.GAME_NAME}");
|
||||
sw.WriteLine("// Format: Quake3 (Valve)");
|
||||
|
||||
for (int entityIdx = 0; entityIdx < Entities.Count; entityIdx++)
|
||||
{
|
||||
sw.WriteLine($"// entity {entityIdx}");
|
||||
sw.WriteLine("{");
|
||||
foreach ((string key, string value) in Entities[entityIdx].Entries)
|
||||
{
|
||||
sw.WriteLine($"\"{key}\" \"{value}\"");
|
||||
}
|
||||
|
||||
if (Entities[entityIdx].Brushes != null)
|
||||
{
|
||||
for (int brushIdx = 0; brushIdx < Entities[entityIdx].Brushes.Count; brushIdx++)
|
||||
{
|
||||
MapBrush brush = Entities[entityIdx].Brushes[brushIdx];
|
||||
|
||||
sw.WriteLine($"// brush {brushIdx}");
|
||||
sw.WriteLine("{");
|
||||
foreach (MapBrushFace face in brush.Faces)
|
||||
{
|
||||
//Vector3[] points = new Vector3[3];
|
||||
|
||||
// Vector3 arb = Vector3.Normalize(face.Normal + new Vector3(2f, 3f, 1f));
|
||||
// points[0] = face.Position;
|
||||
// points[1] = face.Position + Vector3.Cross(face.Normal, arb);
|
||||
// points[2] = face.Position + Vector3.Cross(points[1], face.Normal);
|
||||
// foreach (Vector3 p in points)
|
||||
// {
|
||||
// sw.Write($"( {p.x} {p.y} {p.z} ) ");
|
||||
// }
|
||||
|
||||
|
||||
sw.Write($"( {face.P1.ToStringInvariant()} ) ");
|
||||
sw.Write($"( {face.P2.ToStringInvariant()} ) ");
|
||||
sw.Write($"( {face.P3.ToStringInvariant()} ) ");
|
||||
|
||||
sw.Write(face.TextureName);
|
||||
sw.Write(" [ ");
|
||||
foreach (float f in face.TexU)
|
||||
{
|
||||
sw.Write(f.ToStringInvariant(5));
|
||||
sw.Write(" ");
|
||||
}
|
||||
|
||||
sw.Write("] [ ");
|
||||
foreach (float f in face.TexV)
|
||||
{
|
||||
sw.Write(f.ToStringInvariant(5));
|
||||
sw.Write(" ");
|
||||
}
|
||||
|
||||
sw.WriteLine($"] {face.Rotation.ToStringInvariant()} {face.Scale.ToStringInvariant()}");
|
||||
}
|
||||
|
||||
sw.WriteLine("}");
|
||||
}
|
||||
}
|
||||
|
||||
sw.WriteLine("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class MapParser
|
||||
{
|
||||
private const string TOKEN_START = "{";
|
||||
private const string TOKEN_END = "}";
|
||||
|
||||
public static Map Parse(string mapPath)
|
||||
{
|
||||
List<MapEntity> entities = new(512);
|
||||
Dictionary<string, string> metadata = new(16);
|
||||
|
||||
// Quicker than using streams (wtf?)
|
||||
string mapContents = File.ReadAllText(mapPath).Replace("\r", "");
|
||||
|
||||
// Read metadata
|
||||
string remaining = mapContents;
|
||||
while (remaining.StartsWithInvariant("//") && remaining.ContainsInvariant(":"))
|
||||
{
|
||||
remaining.Split('\n', out string thisLine, out remaining);
|
||||
|
||||
thisLine.Substring(2).Split(':', out string key, out string value);
|
||||
if (key == null || value == null)
|
||||
break;
|
||||
|
||||
metadata[key] = value;
|
||||
}
|
||||
|
||||
// Parse map
|
||||
TokenParser parser = new(mapContents);
|
||||
while (!parser.IsAtEnd)
|
||||
{
|
||||
entities.Add(parser.ReadEntity());
|
||||
}
|
||||
|
||||
return new() { Metadata = metadata, Entities = entities };
|
||||
}
|
||||
public static Dictionary<string, string> ParseMetadataOnly(string mapPath)
|
||||
{
|
||||
Dictionary<string, string> metadata = new(16);
|
||||
|
||||
// Quicker than using streams (wtf?)
|
||||
using FileStream file = new(mapPath, FileMode.Open, FileAccess.Read);
|
||||
using StreamReader sr = new(file, Encoding.ASCII);
|
||||
|
||||
// Read metadata
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string line = sr.ReadLine();
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
if (!line.StartsWithInvariant("//") || !line.ContainsInvariant(":"))
|
||||
break;
|
||||
|
||||
line.Substring(2).Split(':', out string key, out string value);
|
||||
if (key == null || value == null)
|
||||
break;
|
||||
|
||||
metadata[key] = value;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private static MapEntity ReadEntity(this TokenParser parser)
|
||||
{
|
||||
if (!parser.MatchToken(TOKEN_START))
|
||||
return new();
|
||||
|
||||
Dictionary<string, string> entries = new(64);
|
||||
|
||||
while (!parser.IsAtEnd)
|
||||
{
|
||||
ReadOnlySpan<char> token = parser.PeekToken();
|
||||
|
||||
if (token.EqualsInvariant(TOKEN_START, caseSensitive: true))
|
||||
{
|
||||
// Found brushes, parse them
|
||||
List<MapBrush> brushes = ParseBrushes(parser);
|
||||
parser.MatchToken(TOKEN_END); // Consume end brace of entity
|
||||
|
||||
return new() { Brushes = brushes, Entries = entries };
|
||||
}
|
||||
else if (token.EqualsInvariant(TOKEN_END, caseSensitive: true))
|
||||
{
|
||||
// Found end - with no brushes
|
||||
parser.MatchToken(TOKEN_END); // Consume end brace of entity
|
||||
return new() { Brushes = new(), Entries = entries };
|
||||
}
|
||||
else
|
||||
{
|
||||
// Still parsing entries
|
||||
string key = parser.ReadToken().ToString();
|
||||
string value = parser.ReadToken().ToString();
|
||||
entries.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return new();
|
||||
}
|
||||
|
||||
private static List<MapBrush> ParseBrushes(in TokenParser parser)
|
||||
{
|
||||
List<MapBrush> brushes = new(64);
|
||||
|
||||
while (true)
|
||||
{
|
||||
brushes.Add(parser.ReadBrush());
|
||||
|
||||
if (parser.PeekToken(TOKEN_END))
|
||||
break;
|
||||
}
|
||||
|
||||
return brushes;
|
||||
}
|
||||
|
||||
private static MapBrush ReadBrush(this TokenParser parser)
|
||||
{
|
||||
parser.MatchToken(TOKEN_START); // Consume open brace
|
||||
|
||||
List<MapBrushFace> faces = new(64);
|
||||
while (!parser.IsAtEnd)
|
||||
{
|
||||
ReadOnlySpan<char> token = parser.PeekToken();
|
||||
if (token.EqualsInvariant(TOKEN_END))
|
||||
{
|
||||
// End of brush faces
|
||||
break;
|
||||
}
|
||||
|
||||
faces.Add(parser.ReadBrushFace());
|
||||
}
|
||||
|
||||
parser.MatchToken(TOKEN_END); // Consume close brace
|
||||
|
||||
return new() { Faces = faces };
|
||||
}
|
||||
private static MapBrushFace ReadBrushFace(this TokenParser parser)
|
||||
{
|
||||
Vector3 v0 = parser.ReadVector3(false);
|
||||
Vector3 v1 = parser.ReadVector3(false);
|
||||
Vector3 v2 = parser.ReadVector3(false);
|
||||
|
||||
ReadOnlySpan<char> textureName = parser.ReadToken(disableNumLock: true);
|
||||
float[] texU, texV;
|
||||
if (parser.PeekToken("["))
|
||||
{
|
||||
// Valve format
|
||||
texU = parser.ReadMatrix(4);
|
||||
texV = parser.ReadMatrix(4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standard format
|
||||
Vector3 u = Vector3.Normalize(v0 - v1);
|
||||
Vector3 v = Vector3.Normalize(v2 - v1);
|
||||
texU = new [] { u.x, u.y, u.z, parser.ReadFloat() };
|
||||
texV = new [] { v.x, v.y, v.z, parser.ReadFloat() };
|
||||
}
|
||||
//Vector3 planeNormal = Vector3.Normalize(Vector3.Cross(v1 - v0, v2 - v0));
|
||||
|
||||
float rotation = parser.ReadFloat();
|
||||
Vector2 scale = new() { x = parser.ReadFloat(), y = parser.ReadFloat() };
|
||||
|
||||
return new()
|
||||
{
|
||||
// Position = v0,
|
||||
// Normal = planeNormal,
|
||||
P1 = v0,
|
||||
P2 = v1,
|
||||
P3 = v2,
|
||||
TextureName = textureName.ToString(),
|
||||
TexU = texU,
|
||||
TexV = texV,
|
||||
Rotation = rotation,
|
||||
Scale = scale,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aecd40d2f27c43f1b907b5479abb07f8
|
||||
timeCreated: 1711272189
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 277805
|
||||
packageName: Tremble - Map Importer
|
||||
packageVersion: 1.11.1
|
||||
assetPath: Assets/Tremble/IO/MAP/MapParser.cs
|
||||
uploadId: 939664
|
||||
@@ -0,0 +1,246 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
// -----------------------------------------------------------------------------------------------------------------------------
|
||||
// Writing
|
||||
// -----------------------------------------------------------------------------------------------------------------------------
|
||||
// For writing to the MD3 stream, we bypass BinaryWriter because it's MUCH slower.
|
||||
// I think it's because the BinaryWriter does Endianness and Encoding related things, which we
|
||||
// don't need for our limited use-case of ASCII-encoded MD3 models.
|
||||
public class StreamWriteIO : IDisposable
|
||||
{
|
||||
private const int BUFFER_SIZE_KB = 160; // 160kB buffer
|
||||
|
||||
public StreamWriteIO(Stream stream)
|
||||
{
|
||||
m_Stream = stream;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
|
||||
private readonly Stream m_Stream;
|
||||
private readonly byte[] m_Buffer = new byte[BUFFER_SIZE_KB * 1024];
|
||||
private int m_BufPtr;
|
||||
|
||||
private readonly float[] m_Floats = new float[1];
|
||||
private readonly byte[] m_Bytes = new byte[4];
|
||||
|
||||
public void WriteInt32(int value)
|
||||
{
|
||||
WriteByte((byte)(value & 0xFF));
|
||||
WriteByte((byte)((value >> 8) & 0xFF));
|
||||
WriteByte((byte)((value >> 16) & 0xFF));
|
||||
WriteByte((byte)((value >> 24) & 0xFF));
|
||||
}
|
||||
|
||||
public void WriteFloat(float value)
|
||||
{
|
||||
// Block copy the float value's bytes into preallocated buffer
|
||||
m_Floats[0] = value;
|
||||
Buffer.BlockCopy(m_Floats, 0, m_Bytes, 0, 4);
|
||||
|
||||
// Write out
|
||||
WriteByte(m_Bytes[0]);
|
||||
WriteByte(m_Bytes[1]);
|
||||
WriteByte(m_Bytes[2]);
|
||||
WriteByte(m_Bytes[3]);
|
||||
}
|
||||
|
||||
public void WriteInt16(short value)
|
||||
{
|
||||
WriteByte((byte)(value & 0xFF));
|
||||
WriteByte((byte)((value >> 8) & 0xFF));
|
||||
}
|
||||
|
||||
public void WriteNormalAsLatLong(Vector3 normal)
|
||||
{
|
||||
const float BYTE_TO_DEG = (255.0f / 360.0f) * Mathf.Rad2Deg;
|
||||
Vector3 q3Normal = normal.UnityToQ3Vector(scale: 1f);
|
||||
|
||||
if (q3Normal is { x: 0, y: 0 })
|
||||
{
|
||||
WriteByte(q3Normal.z > 0 ? (byte)0x00 : (byte)0x80); // U
|
||||
WriteByte(0x00); // V
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteByte((byte)(Mathf.Acos(q3Normal.z) * BYTE_TO_DEG)); // U
|
||||
WriteByte((byte)(Mathf.Atan2(q3Normal.y, q3Normal.x) * BYTE_TO_DEG)); // V
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteBytes(byte[] bytes)
|
||||
{
|
||||
foreach (byte b in bytes)
|
||||
{
|
||||
WriteByte(b);
|
||||
}
|
||||
}
|
||||
public void WriteBytes(byte[] buffer, int count)
|
||||
{
|
||||
for (int b = 0; b < count; b++)
|
||||
{
|
||||
WriteByte(buffer[b]);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteString(string value)
|
||||
{
|
||||
foreach (char c in value)
|
||||
{
|
||||
WriteByte((byte)c);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteString(string value, int length)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (i < value.Length)
|
||||
{
|
||||
WriteByte((byte)value[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteByte(0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteByte(byte b)
|
||||
{
|
||||
if (m_BufPtr >= m_Buffer.Length)
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
|
||||
m_Buffer[m_BufPtr++] = b;
|
||||
}
|
||||
|
||||
private void Flush()
|
||||
{
|
||||
if (m_BufPtr == 0)
|
||||
return;
|
||||
|
||||
m_Stream.Write(m_Buffer, 0, m_BufPtr);
|
||||
m_BufPtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------------------
|
||||
// Reading
|
||||
// -----------------------------------------------------------------------------------------------------------------------------
|
||||
public class StreamReadIO
|
||||
{
|
||||
private readonly float[] m_Floats = new float[1];
|
||||
private readonly byte[] m_Bytes = new byte[8];
|
||||
|
||||
private readonly byte[] m_StreamContents;
|
||||
private int m_Offset;
|
||||
|
||||
public StreamReadIO(string path)
|
||||
{
|
||||
m_StreamContents = File.ReadAllBytes(path);
|
||||
}
|
||||
|
||||
public void Seek(int offset)
|
||||
{
|
||||
m_Offset = offset;
|
||||
}
|
||||
public int Position => m_Offset;
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
return m_StreamContents[m_Offset++];
|
||||
}
|
||||
|
||||
public void SkipBytes(int count)
|
||||
{
|
||||
m_Offset += count;
|
||||
}
|
||||
|
||||
public short ReadInt16()
|
||||
{
|
||||
m_Bytes[0] = ReadByte();
|
||||
m_Bytes[1] = ReadByte();
|
||||
|
||||
return (short)(m_Bytes[0] | m_Bytes[1] << 8);
|
||||
}
|
||||
|
||||
public int ReadInt32()
|
||||
{
|
||||
m_Bytes[0] = ReadByte();
|
||||
m_Bytes[1] = ReadByte();
|
||||
m_Bytes[2] = ReadByte();
|
||||
m_Bytes[3] = ReadByte();
|
||||
|
||||
return (int)(m_Bytes[0] | m_Bytes[1] << 8 | m_Bytes[2] << 16 | m_Bytes[3] << 24);
|
||||
}
|
||||
|
||||
public float ReadSingle()
|
||||
{
|
||||
// Block copy the bytes into a float, using a preallocated buffer
|
||||
m_Bytes[0] = ReadByte();
|
||||
m_Bytes[1] = ReadByte();
|
||||
m_Bytes[2] = ReadByte();
|
||||
m_Bytes[3] = ReadByte();
|
||||
Buffer.BlockCopy(m_Bytes, 0, m_Floats, 0, 4);
|
||||
|
||||
// Return
|
||||
return m_Floats[0];
|
||||
}
|
||||
|
||||
public char[] ReadChars(int numChars)
|
||||
{
|
||||
char[] result = new char[numChars];
|
||||
for (int i = 0; i < numChars; i++)
|
||||
{
|
||||
result[i] = (char)ReadByte();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int numBytes)
|
||||
{
|
||||
byte[] result = new byte[numBytes];
|
||||
|
||||
Buffer.BlockCopy(m_StreamContents, m_Offset, result, 0, numBytes);
|
||||
m_Offset += numBytes;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string ReadString(int numChars)
|
||||
{
|
||||
string result = new(ReadChars(numChars));
|
||||
int nulCharacter = result.IndexOf('\0');
|
||||
|
||||
return nulCharacter > 0 ? result.Substring(0, nulCharacter) : result;
|
||||
}
|
||||
|
||||
public Vector3 ReadVector3(bool convertToQ3 = false, float scale = 1f)
|
||||
{
|
||||
Vector3 result = new(ReadSingle() * scale, ReadSingle() * scale, ReadSingle() * scale);
|
||||
return convertToQ3 ? result.Q3ToUnityVector() : result;
|
||||
}
|
||||
public Vector2 ReadUV()
|
||||
=> new(ReadSingle() + 0.5f, 1f - ReadSingle() + 0.5f); // Flip Y and offset by 0.5, 0.5
|
||||
|
||||
public Color32 ReadColour()
|
||||
=> new(ReadByte(), ReadByte(), ReadByte(), ReadByte());
|
||||
|
||||
public Vector3Int ReadVector3Int() => new(ReadInt32(), ReadInt32(), ReadInt32());
|
||||
|
||||
public Vector2Int ReadVector2Int() => new(ReadInt32(), ReadInt32());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06ba2a69ad404fb2a03db07dd4ce5ccf
|
||||
timeCreated: 1710420269
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 277805
|
||||
packageName: Tremble - Map Importer
|
||||
packageVersion: 1.11.1
|
||||
assetPath: Assets/Tremble/IO/StreamIO.cs
|
||||
uploadId: 939664
|
||||
@@ -0,0 +1,251 @@
|
||||
//
|
||||
// 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
|
||||
{
|
||||
public class TokenParser
|
||||
{
|
||||
private readonly string m_String;
|
||||
private int m_Index;
|
||||
|
||||
public TokenParser(string inString)
|
||||
{
|
||||
m_String = inString.Replace("\r", ""); // Remove Windows newlines
|
||||
m_Index = 0;
|
||||
}
|
||||
|
||||
public bool IsAtEnd
|
||||
{
|
||||
get
|
||||
{
|
||||
// Fast-forward to next non-whitespace character, if we can
|
||||
int index = m_Index;
|
||||
while (index < m_String.Length)
|
||||
{
|
||||
if (m_String[index] is '\n' or '\t')
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return (index >= m_String.Length);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public string DEBUG_CurrentBuffer
|
||||
{
|
||||
get
|
||||
{
|
||||
const int WINDOW_SIZE = 20;
|
||||
string before = m_String.Substring(Math.Max(0, m_Index - WINDOW_SIZE), WINDOW_SIZE);
|
||||
string after = m_String.Substring(m_Index + 1, Math.Min(m_String.Length - m_Index - 1, WINDOW_SIZE));
|
||||
|
||||
return $"{before} -->{PeekToken().ToString()}<-- {after}";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private char Take() => !IsAtEnd ? m_String[m_Index++] : '\n';
|
||||
|
||||
private char Peek() => !IsAtEnd ? m_String[m_Index] : '\n';
|
||||
|
||||
private void Skip() => m_Index++;
|
||||
|
||||
private void FastForwardTo(char target)
|
||||
{
|
||||
while (!IsAtEnd && m_String[m_Index] != target)
|
||||
{
|
||||
Skip();
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<char> ReadToken(bool supportCommentsAndQuotes = true, bool eatAllBracketContent = false, bool disableNumLock = false)
|
||||
{
|
||||
if (IsAtEnd)
|
||||
return "\n";
|
||||
|
||||
// Skip newlines and leading whitespace
|
||||
while (Peek() is '\n' or '\t' or ' ')
|
||||
{
|
||||
Take();
|
||||
}
|
||||
|
||||
bool quoteLock = false;
|
||||
if (supportCommentsAndQuotes)
|
||||
{
|
||||
// Handle comment lines:
|
||||
// If it's a /, check for // and if found, skip to end of line
|
||||
while (Peek() == '/')
|
||||
{
|
||||
int commentStartIdx = m_Index;
|
||||
Skip();
|
||||
|
||||
if (Peek() != '/')
|
||||
{
|
||||
// No second /? Weird, roll back and continue as normal
|
||||
m_Index = commentStartIdx;
|
||||
break;
|
||||
}
|
||||
|
||||
// Fast-forwarding to EOL...
|
||||
FastForwardTo('\n');
|
||||
Skip();
|
||||
|
||||
// Skip newlines if found
|
||||
while (Peek() == '\n')
|
||||
{
|
||||
Take();
|
||||
}
|
||||
}
|
||||
|
||||
// Turn on "quote lock" and skip the quote if the token starts with a quote
|
||||
quoteLock = Peek() == '"';
|
||||
if (quoteLock)
|
||||
{
|
||||
Skip();
|
||||
}
|
||||
}
|
||||
|
||||
int startIdx = m_Index;
|
||||
int numChars = 0;
|
||||
bool numLock = false;
|
||||
int inBrackets = 0; // keep track of whether inside brackets
|
||||
|
||||
while (m_Index < m_String.Length)
|
||||
{
|
||||
char nextChar = Take();
|
||||
numChars++;
|
||||
|
||||
// Turn on numlock when we see a number outside brackets/quotes
|
||||
if (!disableNumLock && IsCharNumberFast(nextChar) && inBrackets == 0 && !quoteLock)
|
||||
{
|
||||
numLock = true;
|
||||
}
|
||||
|
||||
// In numlock, but that wasn't a number or "number punctuation"!
|
||||
if (numLock && !IsCharNumberFast(nextChar) && nextChar is not '.' and not ',' and not 'e' and not 'E' and not '-' and not '\'')
|
||||
{
|
||||
if (!IsAtEnd)
|
||||
{
|
||||
m_Index--; // rewind if not at end!
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// End of line - that's it!
|
||||
if (inBrackets == 0 && nextChar == '\n')
|
||||
break;
|
||||
|
||||
if (eatAllBracketContent && nextChar == '(') { inBrackets++; }
|
||||
if (eatAllBracketContent && nextChar == ')') { inBrackets--; }
|
||||
|
||||
// Not in quote-lock - found space
|
||||
if (inBrackets == 0 && !quoteLock && nextChar == ' ')
|
||||
break;
|
||||
|
||||
// We're in quote-lock and we hit the end-quote
|
||||
if (nextChar == '"' && quoteLock)
|
||||
break;
|
||||
}
|
||||
|
||||
// Return string slice
|
||||
return (numChars == 0) ? "" : m_String.AsSpan(startIdx, numChars - 1);
|
||||
}
|
||||
|
||||
private bool IsCharNumberFast(char c) => c is >= '0' and <= '9';
|
||||
|
||||
public ReadOnlySpan<char> PeekToken(bool supportCommentsAndQuotes = true, bool eatAllBracketContent = false, bool disableNumLock = false)
|
||||
{
|
||||
int index = m_Index;
|
||||
|
||||
try
|
||||
{
|
||||
return ReadToken(supportCommentsAndQuotes, eatAllBracketContent, disableNumLock);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
public bool PeekToken(string expected, bool supportCommentsAndQuotes = true, bool eatAllBracketContent = false, bool disableNumLock = false)
|
||||
{
|
||||
return PeekToken(supportCommentsAndQuotes, eatAllBracketContent, disableNumLock).Equals(expected.AsSpan(), StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public bool MatchToken(string expected, bool supportCommentsAndQuotes = true, bool eatAllBracketContent = false, bool disableNumLock = false)
|
||||
{
|
||||
ReadOnlySpan<char> token = ReadToken(supportCommentsAndQuotes, eatAllBracketContent, disableNumLock);
|
||||
|
||||
bool tokenMatches;
|
||||
if (expected.Length == 1)
|
||||
{
|
||||
tokenMatches = (token[0] == expected[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenMatches = token.Equals(expected.AsSpan(), StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
if (tokenMatches)
|
||||
return true;
|
||||
|
||||
throw new InvalidDataException($"Expected a '{expected}', got a '{token.ToString()}'");
|
||||
}
|
||||
|
||||
public Vector3 ReadVector3(bool convertToUnity = true)
|
||||
{
|
||||
MatchToken("(");
|
||||
Vector3 vec = new()
|
||||
{
|
||||
x = ReadFloat(),
|
||||
y = ReadFloat(),
|
||||
z = ReadFloat()
|
||||
};
|
||||
MatchToken(")");
|
||||
|
||||
float scale = TrembleSyncSettings.Get().ImportScale;
|
||||
return convertToUnity ? vec.Q3ToUnityVector(scale) : vec;
|
||||
}
|
||||
|
||||
public float ReadFloat()
|
||||
{
|
||||
ReadOnlySpan<char> floatValue = ReadToken(supportCommentsAndQuotes: false);
|
||||
if (!floatValue.TryParseFloat(out float val))
|
||||
{
|
||||
Debug.Log($"Not a float '{floatValue.ToString()}'!");
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public ulong ReadUlong() => ulong.Parse(ReadToken(supportCommentsAndQuotes: false), NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat);
|
||||
|
||||
|
||||
public float[] ReadMatrix(int length)
|
||||
{
|
||||
float[] matrix = new float[length];
|
||||
|
||||
MatchToken("[");
|
||||
for (int tok = 0; tok < length; tok++)
|
||||
{
|
||||
matrix[tok] = ReadFloat();
|
||||
}
|
||||
MatchToken("]");
|
||||
|
||||
return matrix;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58dbe14b462d43a8a9f6ccb02d2ebf86
|
||||
timeCreated: 1702378526
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 277805
|
||||
packageName: Tremble - Map Importer
|
||||
packageVersion: 1.11.1
|
||||
assetPath: Assets/Tremble/IO/TokenParser.cs
|
||||
uploadId: 939664
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3a206c04d254db180f8492831596dad
|
||||
timeCreated: 1722843889
|
||||
@@ -0,0 +1,304 @@
|
||||
//
|
||||
// This file is part of the Tremble package by Tiny Goose.
|
||||
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TinyGoose.Tremble
|
||||
{
|
||||
public class WadPalette
|
||||
{
|
||||
private Color32[] m_Colours;
|
||||
public ref Color32 Get(int pos) => ref m_Colours[pos];
|
||||
|
||||
private static WadPalette s_QuakePalette;
|
||||
public static WadPalette QuakePalette
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_QuakePalette == null)
|
||||
{
|
||||
s_QuakePalette = new()
|
||||
{
|
||||
m_Colours = new Color32[]
|
||||
{
|
||||
new(0x00, 0x00, 0x00, 0xFF),
|
||||
new(0x0F, 0x0F, 0x0F, 0xFF),
|
||||
new(0x1F, 0x1F, 0x1F, 0xFF),
|
||||
new(0x2F, 0x2F, 0x2F, 0xFF),
|
||||
new(0x3F, 0x3F, 0x3F, 0xFF),
|
||||
new(0x4B, 0x4B, 0x4B, 0xFF),
|
||||
new(0x5B, 0x5B, 0x5B, 0xFF),
|
||||
new(0x6B, 0x6B, 0x6B, 0xFF),
|
||||
new(0x7B, 0x7B, 0x7B, 0xFF),
|
||||
new(0x8B, 0x8B, 0x8B, 0xFF),
|
||||
new(0x9B, 0x9B, 0x9B, 0xFF),
|
||||
new(0xAB, 0xAB, 0xAB, 0xFF),
|
||||
new(0xBB, 0xBB, 0xBB, 0xFF),
|
||||
new(0xCB, 0xCB, 0xCB, 0xFF),
|
||||
new(0xDB, 0xDB, 0xDB, 0xFF),
|
||||
new(0xEB, 0xEB, 0xEB, 0xFF),
|
||||
new(0x0F, 0x0B, 0x07, 0xFF),
|
||||
new(0x17, 0x0F, 0x0B, 0xFF),
|
||||
new(0x1F, 0x17, 0x0B, 0xFF),
|
||||
new(0x27, 0x1B, 0x0F, 0xFF),
|
||||
new(0x2F, 0x23, 0x13, 0xFF),
|
||||
new(0x37, 0x2B, 0x17, 0xFF),
|
||||
new(0x3F, 0x2F, 0x17, 0xFF),
|
||||
new(0x4B, 0x37, 0x1B, 0xFF),
|
||||
new(0x53, 0x3B, 0x1B, 0xFF),
|
||||
new(0x5B, 0x43, 0x1F, 0xFF),
|
||||
new(0x63, 0x4B, 0x1F, 0xFF),
|
||||
new(0x6B, 0x53, 0x1F, 0xFF),
|
||||
new(0x73, 0x57, 0x1F, 0xFF),
|
||||
new(0x7B, 0x5F, 0x23, 0xFF),
|
||||
new(0x83, 0x67, 0x23, 0xFF),
|
||||
new(0x8F, 0x6F, 0x23, 0xFF),
|
||||
new(0x0B, 0x0B, 0x0F, 0xFF),
|
||||
new(0x13, 0x13, 0x1B, 0xFF),
|
||||
new(0x1B, 0x1B, 0x27, 0xFF),
|
||||
new(0x27, 0x27, 0x33, 0xFF),
|
||||
new(0x2F, 0x2F, 0x3F, 0xFF),
|
||||
new(0x37, 0x37, 0x4B, 0xFF),
|
||||
new(0x3F, 0x3F, 0x57, 0xFF),
|
||||
new(0x47, 0x47, 0x67, 0xFF),
|
||||
new(0x4F, 0x4F, 0x73, 0xFF),
|
||||
new(0x5B, 0x5B, 0x7F, 0xFF),
|
||||
new(0x63, 0x63, 0x8B, 0xFF),
|
||||
new(0x6B, 0x6B, 0x97, 0xFF),
|
||||
new(0x73, 0x73, 0xA3, 0xFF),
|
||||
new(0x7B, 0x7B, 0xAF, 0xFF),
|
||||
new(0x83, 0x83, 0xBB, 0xFF),
|
||||
new(0x8B, 0x8B, 0xCB, 0xFF),
|
||||
new(0x00, 0x00, 0x00, 0xFF),
|
||||
new(0x07, 0x07, 0x00, 0xFF),
|
||||
new(0x0B, 0x0B, 0x00, 0xFF),
|
||||
new(0x13, 0x13, 0x00, 0xFF),
|
||||
new(0x1B, 0x1B, 0x00, 0xFF),
|
||||
new(0x23, 0x23, 0x00, 0xFF),
|
||||
new(0x2B, 0x2B, 0x07, 0xFF),
|
||||
new(0x2F, 0x2F, 0x07, 0xFF),
|
||||
new(0x37, 0x37, 0x07, 0xFF),
|
||||
new(0x3F, 0x3F, 0x07, 0xFF),
|
||||
new(0x47, 0x47, 0x07, 0xFF),
|
||||
new(0x4B, 0x4B, 0x0B, 0xFF),
|
||||
new(0x53, 0x53, 0x0B, 0xFF),
|
||||
new(0x5B, 0x5B, 0x0B, 0xFF),
|
||||
new(0x63, 0x63, 0x0B, 0xFF),
|
||||
new(0x6B, 0x6B, 0x0F, 0xFF),
|
||||
new(0x07, 0x00, 0x00, 0xFF),
|
||||
new(0x0F, 0x00, 0x00, 0xFF),
|
||||
new(0x17, 0x00, 0x00, 0xFF),
|
||||
new(0x1F, 0x00, 0x00, 0xFF),
|
||||
new(0x27, 0x00, 0x00, 0xFF),
|
||||
new(0x2F, 0x00, 0x00, 0xFF),
|
||||
new(0x37, 0x00, 0x00, 0xFF),
|
||||
new(0x3F, 0x00, 0x00, 0xFF),
|
||||
new(0x47, 0x00, 0x00, 0xFF),
|
||||
new(0x4F, 0x00, 0x00, 0xFF),
|
||||
new(0x57, 0x00, 0x00, 0xFF),
|
||||
new(0x5F, 0x00, 0x00, 0xFF),
|
||||
new(0x67, 0x00, 0x00, 0xFF),
|
||||
new(0x6F, 0x00, 0x00, 0xFF),
|
||||
new(0x77, 0x00, 0x00, 0xFF),
|
||||
new(0x7F, 0x00, 0x00, 0xFF),
|
||||
new(0x13, 0x13, 0x00, 0xFF),
|
||||
new(0x1B, 0x1B, 0x00, 0xFF),
|
||||
new(0x23, 0x23, 0x00, 0xFF),
|
||||
new(0x2F, 0x2B, 0x00, 0xFF),
|
||||
new(0x37, 0x2F, 0x00, 0xFF),
|
||||
new(0x43, 0x37, 0x00, 0xFF),
|
||||
new(0x4B, 0x3B, 0x07, 0xFF),
|
||||
new(0x57, 0x43, 0x07, 0xFF),
|
||||
new(0x5F, 0x47, 0x07, 0xFF),
|
||||
new(0x6B, 0x4B, 0x0B, 0xFF),
|
||||
new(0x77, 0x53, 0x0F, 0xFF),
|
||||
new(0x83, 0x57, 0x13, 0xFF),
|
||||
new(0x8B, 0x5B, 0x13, 0xFF),
|
||||
new(0x97, 0x5F, 0x1B, 0xFF),
|
||||
new(0xA3, 0x63, 0x1F, 0xFF),
|
||||
new(0xAF, 0x67, 0x23, 0xFF),
|
||||
new(0x23, 0x13, 0x07, 0xFF),
|
||||
new(0x2F, 0x17, 0x0B, 0xFF),
|
||||
new(0x3B, 0x1F, 0x0F, 0xFF),
|
||||
new(0x4B, 0x23, 0x13, 0xFF),
|
||||
new(0x57, 0x2B, 0x17, 0xFF),
|
||||
new(0x63, 0x2F, 0x1F, 0xFF),
|
||||
new(0x73, 0x37, 0x23, 0xFF),
|
||||
new(0x7F, 0x3B, 0x2B, 0xFF),
|
||||
new(0x8F, 0x43, 0x33, 0xFF),
|
||||
new(0x9F, 0x4F, 0x33, 0xFF),
|
||||
new(0xAF, 0x63, 0x2F, 0xFF),
|
||||
new(0xBF, 0x77, 0x2F, 0xFF),
|
||||
new(0xCF, 0x8F, 0x2B, 0xFF),
|
||||
new(0xDF, 0xAB, 0x27, 0xFF),
|
||||
new(0xEF, 0xCB, 0x1F, 0xFF),
|
||||
new(0xFF, 0xF3, 0x1B, 0xFF),
|
||||
new(0x0B, 0x07, 0x00, 0xFF),
|
||||
new(0x1B, 0x13, 0x00, 0xFF),
|
||||
new(0x2B, 0x23, 0x0F, 0xFF),
|
||||
new(0x37, 0x2B, 0x13, 0xFF),
|
||||
new(0x47, 0x33, 0x1B, 0xFF),
|
||||
new(0x53, 0x37, 0x23, 0xFF),
|
||||
new(0x63, 0x3F, 0x2B, 0xFF),
|
||||
new(0x6F, 0x47, 0x33, 0xFF),
|
||||
new(0x7F, 0x53, 0x3F, 0xFF),
|
||||
new(0x8B, 0x5F, 0x47, 0xFF),
|
||||
new(0x9B, 0x6B, 0x53, 0xFF),
|
||||
new(0xA7, 0x7B, 0x5F, 0xFF),
|
||||
new(0xB7, 0x87, 0x6B, 0xFF),
|
||||
new(0xC3, 0x93, 0x7B, 0xFF),
|
||||
new(0xD3, 0xA3, 0x8B, 0xFF),
|
||||
new(0xE3, 0xB3, 0x97, 0xFF),
|
||||
new(0xAB, 0x8B, 0xA3, 0xFF),
|
||||
new(0x9F, 0x7F, 0x97, 0xFF),
|
||||
new(0x93, 0x73, 0x87, 0xFF),
|
||||
new(0x8B, 0x67, 0x7B, 0xFF),
|
||||
new(0x7F, 0x5B, 0x6F, 0xFF),
|
||||
new(0x77, 0x53, 0x63, 0xFF),
|
||||
new(0x6B, 0x4B, 0x57, 0xFF),
|
||||
new(0x5F, 0x3F, 0x4B, 0xFF),
|
||||
new(0x57, 0x37, 0x43, 0xFF),
|
||||
new(0x4B, 0x2F, 0x37, 0xFF),
|
||||
new(0x43, 0x27, 0x2F, 0xFF),
|
||||
new(0x37, 0x1F, 0x23, 0xFF),
|
||||
new(0x2B, 0x17, 0x1B, 0xFF),
|
||||
new(0x23, 0x13, 0x13, 0xFF),
|
||||
new(0x17, 0x0B, 0x0B, 0xFF),
|
||||
new(0x0F, 0x07, 0x07, 0xFF),
|
||||
new(0xBB, 0x73, 0x9F, 0xFF),
|
||||
new(0xAF, 0x6B, 0x8F, 0xFF),
|
||||
new(0xA3, 0x5F, 0x83, 0xFF),
|
||||
new(0x97, 0x57, 0x77, 0xFF),
|
||||
new(0x8B, 0x4F, 0x6B, 0xFF),
|
||||
new(0x7F, 0x4B, 0x5F, 0xFF),
|
||||
new(0x73, 0x43, 0x53, 0xFF),
|
||||
new(0x6B, 0x3B, 0x4B, 0xFF),
|
||||
new(0x5F, 0x33, 0x3F, 0xFF),
|
||||
new(0x53, 0x2B, 0x37, 0xFF),
|
||||
new(0x47, 0x23, 0x2B, 0xFF),
|
||||
new(0x3B, 0x1F, 0x23, 0xFF),
|
||||
new(0x2F, 0x17, 0x1B, 0xFF),
|
||||
new(0x23, 0x13, 0x13, 0xFF),
|
||||
new(0x17, 0x0B, 0x0B, 0xFF),
|
||||
new(0x0F, 0x07, 0x07, 0xFF),
|
||||
new(0xDB, 0xC3, 0xBB, 0xFF),
|
||||
new(0xCB, 0xB3, 0xA7, 0xFF),
|
||||
new(0xBF, 0xA3, 0x9B, 0xFF),
|
||||
new(0xAF, 0x97, 0x8B, 0xFF),
|
||||
new(0xA3, 0x87, 0x7B, 0xFF),
|
||||
new(0x97, 0x7B, 0x6F, 0xFF),
|
||||
new(0x87, 0x6F, 0x5F, 0xFF),
|
||||
new(0x7B, 0x63, 0x53, 0xFF),
|
||||
new(0x6B, 0x57, 0x47, 0xFF),
|
||||
new(0x5F, 0x4B, 0x3B, 0xFF),
|
||||
new(0x53, 0x3F, 0x33, 0xFF),
|
||||
new(0x43, 0x33, 0x27, 0xFF),
|
||||
new(0x37, 0x2B, 0x1F, 0xFF),
|
||||
new(0x27, 0x1F, 0x17, 0xFF),
|
||||
new(0x1B, 0x13, 0x0F, 0xFF),
|
||||
new(0x0F, 0x0B, 0x07, 0xFF),
|
||||
new(0x6F, 0x83, 0x7B, 0xFF),
|
||||
new(0x67, 0x7B, 0x6F, 0xFF),
|
||||
new(0x5F, 0x73, 0x67, 0xFF),
|
||||
new(0x57, 0x6B, 0x5F, 0xFF),
|
||||
new(0x4F, 0x63, 0x57, 0xFF),
|
||||
new(0x47, 0x5B, 0x4F, 0xFF),
|
||||
new(0x3F, 0x53, 0x47, 0xFF),
|
||||
new(0x37, 0x4B, 0x3F, 0xFF),
|
||||
new(0x2F, 0x43, 0x37, 0xFF),
|
||||
new(0x2B, 0x3B, 0x2F, 0xFF),
|
||||
new(0x23, 0x33, 0x27, 0xFF),
|
||||
new(0x1F, 0x2B, 0x1F, 0xFF),
|
||||
new(0x17, 0x23, 0x17, 0xFF),
|
||||
new(0x0F, 0x1B, 0x13, 0xFF),
|
||||
new(0x0B, 0x13, 0x0B, 0xFF),
|
||||
new(0x07, 0x0B, 0x07, 0xFF),
|
||||
new(0xFF, 0xF3, 0x1B, 0xFF),
|
||||
new(0xEF, 0xDF, 0x17, 0xFF),
|
||||
new(0xDB, 0xCB, 0x13, 0xFF),
|
||||
new(0xCB, 0xB7, 0x0F, 0xFF),
|
||||
new(0xBB, 0xA7, 0x0F, 0xFF),
|
||||
new(0xAB, 0x97, 0x0B, 0xFF),
|
||||
new(0x9B, 0x83, 0x07, 0xFF),
|
||||
new(0x8B, 0x73, 0x07, 0xFF),
|
||||
new(0x7B, 0x63, 0x07, 0xFF),
|
||||
new(0x6B, 0x53, 0x00, 0xFF),
|
||||
new(0x5B, 0x47, 0x00, 0xFF),
|
||||
new(0x4B, 0x37, 0x00, 0xFF),
|
||||
new(0x3B, 0x2B, 0x00, 0xFF),
|
||||
new(0x2B, 0x1F, 0x00, 0xFF),
|
||||
new(0x1B, 0x0F, 0x00, 0xFF),
|
||||
new(0x0B, 0x07, 0x00, 0xFF),
|
||||
new(0x00, 0x00, 0xFF, 0xFF),
|
||||
new(0x0B, 0x0B, 0xEF, 0xFF),
|
||||
new(0x13, 0x13, 0xDF, 0xFF),
|
||||
new(0x1B, 0x1B, 0xCF, 0xFF),
|
||||
new(0x23, 0x23, 0xBF, 0xFF),
|
||||
new(0x2B, 0x2B, 0xAF, 0xFF),
|
||||
new(0x2F, 0x2F, 0x9F, 0xFF),
|
||||
new(0x2F, 0x2F, 0x8F, 0xFF),
|
||||
new(0x2F, 0x2F, 0x7F, 0xFF),
|
||||
new(0x2F, 0x2F, 0x6F, 0xFF),
|
||||
new(0x2F, 0x2F, 0x5F, 0xFF),
|
||||
new(0x2B, 0x2B, 0x4F, 0xFF),
|
||||
new(0x23, 0x23, 0x3F, 0xFF),
|
||||
new(0x1B, 0x1B, 0x2F, 0xFF),
|
||||
new(0x13, 0x13, 0x1F, 0xFF),
|
||||
new(0x0B, 0x0B, 0x0F, 0xFF),
|
||||
new(0x2B, 0x00, 0x00, 0xFF),
|
||||
new(0x3B, 0x00, 0x00, 0xFF),
|
||||
new(0x4B, 0x07, 0x00, 0xFF),
|
||||
new(0x5F, 0x07, 0x00, 0xFF),
|
||||
new(0x6F, 0x0F, 0x00, 0xFF),
|
||||
new(0x7F, 0x17, 0x07, 0xFF),
|
||||
new(0x93, 0x1F, 0x07, 0xFF),
|
||||
new(0xA3, 0x27, 0x0B, 0xFF),
|
||||
new(0xB7, 0x33, 0x0F, 0xFF),
|
||||
new(0xC3, 0x4B, 0x1B, 0xFF),
|
||||
new(0xCF, 0x63, 0x2B, 0xFF),
|
||||
new(0xDB, 0x7F, 0x3B, 0xFF),
|
||||
new(0xE3, 0x97, 0x4F, 0xFF),
|
||||
new(0xE7, 0xAB, 0x5F, 0xFF),
|
||||
new(0xEF, 0xBF, 0x77, 0xFF),
|
||||
new(0xF7, 0xD3, 0x8B, 0xFF),
|
||||
new(0xA7, 0x7B, 0x3B, 0xFF),
|
||||
new(0xB7, 0x9B, 0x37, 0xFF),
|
||||
new(0xC7, 0xC3, 0x37, 0xFF),
|
||||
new(0xE7, 0xE3, 0x57, 0xFF),
|
||||
new(0x7F, 0xBF, 0xFF, 0xFF),
|
||||
new(0xAB, 0xE7, 0xFF, 0xFF),
|
||||
new(0xD7, 0xFF, 0xFF, 0xFF),
|
||||
new(0x67, 0x00, 0x00, 0xFF),
|
||||
new(0x8B, 0x00, 0x00, 0xFF),
|
||||
new(0xB3, 0x00, 0x00, 0xFF),
|
||||
new(0xD7, 0x00, 0x00, 0xFF),
|
||||
new(0xFF, 0x00, 0x00, 0xFF),
|
||||
new(0xFF, 0xF3, 0x93, 0xFF),
|
||||
new(0xFF, 0xF7, 0xC7, 0xFF),
|
||||
new(0xFF, 0xFF, 0xFF, 0xFF),
|
||||
new(0x9F, 0x5B, 0x53, 0xFF)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return s_QuakePalette;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct WadEntry
|
||||
{
|
||||
public int Address { get; init; }
|
||||
public int Size { get; init; }
|
||||
public char Type { get; init; }
|
||||
public string Name { get; init; }
|
||||
}
|
||||
|
||||
public record WadFile
|
||||
{
|
||||
public List<WadEntry> Entries { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81577f4367354726bf894c329ef36a6c
|
||||
timeCreated: 1722844184
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 277805
|
||||
packageName: Tremble - Map Importer
|
||||
packageVersion: 1.11.1
|
||||
assetPath: Assets/Tremble/IO/WAD/WadFile.cs
|
||||
uploadId: 939664
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// 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 UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
|
||||
namespace TinyGoose.Tremble
|
||||
{
|
||||
public class WadParser
|
||||
{
|
||||
private readonly StreamReadIO m_Stream;
|
||||
|
||||
public WadParser(string filename)
|
||||
{
|
||||
m_Stream = new(filename);
|
||||
}
|
||||
|
||||
public void WriteTextureToFile(in WadEntry entry, string outFile)
|
||||
{
|
||||
m_Stream.Seek(entry.Address);
|
||||
|
||||
string name = m_Stream.ReadString(16);
|
||||
int width = m_Stream.ReadInt32();
|
||||
int height = m_Stream.ReadInt32();
|
||||
m_Stream.SkipBytes(16);
|
||||
|
||||
byte[] palettedTextureData = m_Stream.ReadBytes(width * height);
|
||||
byte[] rgbTextureData = new byte[palettedTextureData.Length * 3];
|
||||
|
||||
// Copy paletted to RGB, flipping as we go
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
int readOffset = y * width + x;
|
||||
|
||||
int invY = height - y - 1;
|
||||
int writeOffset = (invY * width + x) * 3;
|
||||
|
||||
ref Color32 colour = ref WadPalette.QuakePalette.Get(palettedTextureData[readOffset]);
|
||||
|
||||
rgbTextureData[writeOffset + 0] = colour.r;
|
||||
rgbTextureData[writeOffset + 1] = colour.g;
|
||||
rgbTextureData[writeOffset + 2] = colour.b;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] pngBytes = ImageConversion.EncodeArrayToPNG(rgbTextureData, GraphicsFormat.R8G8B8_UNorm, (uint)width, (uint)height);
|
||||
File.WriteAllBytes(outFile, pngBytes);
|
||||
}
|
||||
|
||||
public WadFile Parse()
|
||||
{
|
||||
m_Stream.Seek(0);
|
||||
|
||||
string magic = new(m_Stream.ReadChars(4));
|
||||
if (!magic.EqualsInvariant("wad2"))
|
||||
{
|
||||
Debug.LogError($"File was not a WAD2 :(");
|
||||
return default;
|
||||
}
|
||||
|
||||
int numEntries = m_Stream.ReadInt32();
|
||||
int dirOffset = m_Stream.ReadInt32();
|
||||
|
||||
m_Stream.Seek(dirOffset);
|
||||
|
||||
List<WadEntry> entries = new(numEntries);
|
||||
for (int entryIdx = 0; entryIdx < numEntries; entryIdx++)
|
||||
{
|
||||
int entryAddress = m_Stream.ReadInt32();
|
||||
int entrySize = m_Stream.ReadInt32();
|
||||
m_Stream.SkipBytes(4);
|
||||
char type = (char)m_Stream.ReadByte();
|
||||
m_Stream.SkipBytes(3);
|
||||
string name = new(m_Stream.ReadString(16));
|
||||
|
||||
// Skip anything that's not a texture
|
||||
if (type != 'D')
|
||||
continue;
|
||||
|
||||
entries.Add(new()
|
||||
{
|
||||
Address = entryAddress,
|
||||
Size = entrySize,
|
||||
Type = type,
|
||||
Name = name.Replace('*', '_') // * is not a valid name for an asset!
|
||||
});
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
Entries = entries
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8ecbbe775a9445a9f8f2148c4a06be2
|
||||
timeCreated: 1722843896
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 277805
|
||||
packageName: Tremble - Map Importer
|
||||
packageVersion: 1.11.1
|
||||
assetPath: Assets/Tremble/IO/WAD/WadParser.cs
|
||||
uploadId: 939664
|
||||
Reference in new issue
Block a user