Second commit

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

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 874b1f1fda434715b9e8cb37b2936050
timeCreated: 1716200660
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b895dc7f3a74ac1a27fec3a77248e5d
timeCreated: 1716201044
@@ -0,0 +1,159 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
#if ADDRESSABLES_INSTALLED
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
namespace TinyGoose.Tremble
{
public class AddressablesLoaderAdaptor : IAssetLoaderAdaptor
{
public bool SupportsPreloading => true;
private readonly List<AsyncOperationHandle> m_OwnedHandles = new(1024);
private readonly Dictionary<string, object> m_LoadedAssets = new(1024);
public string[] FindAssetPaths(Type type, string name = "")
{
return TrembleAssetLoader.FindAssetDataMany(a => a.Path.ContainsInvariant(name))
.Where(a => a.FullTypeName.EqualsInvariant(type.FullName))
.Select(a => a.Path)
.ToArray();
}
public TAsset LoadAssetByPath<TAsset>(string path) where TAsset : Object
{
if (path.IsNullOrEmpty())
return null;
if (!TrembleAssetLoader.TryGetAssetDataFromPath(path, out AssetMetadata assetData))
{
Debug.LogError($"Not loading invalid addressable: {path}");
return null;
}
if (m_LoadedAssets.TryGetValue(assetData.AddressableName, out object existingAsset) &&
existingAsset is TAsset tAsset)
{
return tAsset;
}
AsyncOperationHandle<TAsset> handle = Addressables.LoadAssetAsync<TAsset>(assetData.AddressableName);
TAsset asset = handle.WaitForCompletion();
if (!asset)
{
Addressables.Release(handle);
return null;
}
m_OwnedHandles.Add(handle);
m_LoadedAssets[path] = handle;
return asset;
}
public async Task PreloadAssetsByPaths(IList<string> paths)
{
if (paths.Count == 0)
return;
List<string> addresses = new(paths.Count);
// First, remove dupes, invalid and ones we already loaded
for (int i = 0; i < paths.Count; i++)
{
string path = paths[i];
if (path.IsNullOrEmpty() || !TrembleAssetLoader.TryGetAssetDataFromPath(path, out AssetMetadata assetData))
{
Debug.LogError($"Not loading invalid addressable: {path}");
paths.RemoveAt(i--);
continue;
}
if (m_LoadedAssets.ContainsKey(assetData.AddressableName))
{
paths.RemoveAt(i--);
continue;
}
if (paths.IndexOf(path) != i)
{
paths.RemoveAt(i--);
continue;
}
// This is valid - add to list of addresses
addresses.Add(assetData.AddressableName);
}
if (addresses.Count == 0)
return;
AsyncOperationHandle<IList<Object>> resultsHandle = Addressables.LoadAssetsAsync<Object>(addresses, null, Addressables.MergeMode.Union);
await resultsHandle.Task;
for (int i = 0; i < paths.Count; i++)
{
string path = paths[i];
if (TrembleAssetLoader.TryGetAssetDataFromPath(path, out AssetMetadata assetData))
{
m_LoadedAssets[assetData.AddressableName] = resultsHandle.Result[i];
}
}
m_OwnedHandles.Add(resultsHandle);
}
public string GetPath(Object obj)
{
Debug.LogError("GetPath is not implemented in Addressables (yet!)");
return null;
}
public TrembleSyncSettings LoadSyncSettings()
{
return Addressables
.LoadAssetAsync<TrembleSyncSettings>(TrembleSyncSettings.ADDRESSABLE_KEY)
.WaitForCompletion();
}
public Material LoadClipMaterial()
{
return Addressables.LoadAssetAsync<Material>("M_NullRender").WaitForCompletion();
}
public void ReleaseAll()
{
foreach (AsyncOperationHandle handle in m_OwnedHandles)
{
Addressables.Release(handle);
}
m_OwnedHandles.Clear();
}
private static bool IsAddressablePathValid(string path)
{
if (path == null)
return false;
AsyncOperationHandle<IList<IResourceLocation>> locationLoadOperation = Addressables.LoadResourceLocationsAsync(path);
IList<IResourceLocation> results = locationLoadOperation.WaitForCompletion();
bool isValidPath = results is { Count: > 0 };
Addressables.Release(locationLoadOperation);
return isValidPath;
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f4959867e61442c2aa78844ff6cd8e06
timeCreated: 1716201342
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/AssetLoading/Adaptors/AddressablesLoaderAdaptor.cs
uploadId: 939664
@@ -0,0 +1,105 @@
//
// 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 System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
namespace TinyGoose.Tremble
{
public class EditorLoaderAdaptor : IAssetLoaderAdaptor
{
public bool SupportsPreloading => false;
private static readonly Dictionary<string, string[]> s_CachedQueryResults = new(1024);
public string[] FindAssetPaths(Type type, string name = "")
{
string typeString = type == typeof(GameObject) ? "prefab" : type.Name;
string fullQuery = $"t:{typeString} {name}";
if (!s_CachedQueryResults.TryGetValue(fullQuery, out string[] results))
{
results = AssetDatabase
.FindAssets(fullQuery, new[] { "Assets" })
.Select(AssetDatabase.GUIDToAssetPath)
.ToArray();
if (results.Length > 0)
{
s_CachedQueryResults[fullQuery] = results;
}
}
return results;
}
public TAsset LoadAssetByPath<TAsset>(string path) where TAsset : Object
=> AssetDatabase.LoadAssetAtPath<TAsset>(path);
public Task PreloadAssetsByPaths(IList<string> paths)
{
// We don't need to preload in Editor ;)
return Task.CompletedTask;
}
public async Task PreloadAssetsByPathsAsync(IList<string> paths)
{
// We don't need to preload in Editor ;)
await Task.CompletedTask;
}
public string GetPath(Object obj) => AssetDatabase.GetAssetPath(obj);
public TrembleSyncSettings GetSyncSettings() => TrembleSyncSettings.Get();
public Material LoadClipMaterial()
{
string clipPath = Path.Combine(TrembleConsts.EDITOR_GetTrembleInstallFolder(), "Materials", "M_NullRender.mat");
return LoadAssetByPath<Material>(clipPath);
}
public TrembleSyncSettings LoadSyncSettings()
{
string[] paths = FindAssetPaths(typeof(TrembleSyncSettings));
switch (paths.Length)
{
case 0:
return null;
case >1:
Debug.LogError("More than one Tremble Sync Settings file found!");
foreach (string path in paths)
{
Debug.LogWarning($"Found at: {path}", AssetDatabase.LoadAssetAtPath<TrembleSyncSettings>(path));
}
Debug.LogError("There should be only one! Picking one arbitrarily for now.");
goto default;
default:
return AssetDatabase.LoadAssetAtPath<TrembleSyncSettings>(paths[0]);
}
}
public void ReleaseAll() { }
public static void DropCachedAssetPaths()
{
s_CachedQueryResults.Clear();
}
}
}
#endif
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a410384015ae43959c21d5c273d941a0
timeCreated: 1716201389
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/AssetLoading/Adaptors/EditorLoaderAdaptor.cs
uploadId: 939664
@@ -0,0 +1,36 @@
//
// 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.Threading.Tasks;
using UnityEngine;
using Object = UnityEngine.Object;
namespace TinyGoose.Tremble
{
public interface IAssetLoaderAdaptor
{
// -----------------------------------------------------------------------------------------------------------------------------
// Finding & loading assets
// -----------------------------------------------------------------------------------------------------------------------------
string[] FindAssetPaths(Type type, string name = "");
TAsset LoadAssetByPath<TAsset>(string path) where TAsset : Object;
Task PreloadAssetsByPaths(IList<string> paths);
bool SupportsPreloading { get; }
// -----------------------------------------------------------------------------------------------------------------------------
// Getting path for a loaded asset
// -----------------------------------------------------------------------------------------------------------------------------
string GetPath(Object obj);
// -----------------------------------------------------------------------------------------------------------------------------
// Misc
// -----------------------------------------------------------------------------------------------------------------------------
TrembleSyncSettings LoadSyncSettings();
Material LoadClipMaterial();
void ReleaseAll();
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7271c3afa0854533a18a87ea7a9690c8
timeCreated: 1716201055
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/AssetLoading/Adaptors/IAssetLoaderAdaptor.cs
uploadId: 939664
@@ -0,0 +1,61 @@
//
// 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.Threading.Tasks;
using UnityEngine;
using Object = UnityEngine.Object;
namespace TinyGoose.Tremble
{
public class NullLoaderAdaptor : IAssetLoaderAdaptor
{
public bool SupportsPreloading => false;
public string[] FindAssetPaths(Type type, string name = "")
{
Debug.LogError("No valid loader adaptor - ensure you have Addressables installed or are in editor!");
return Array.Empty<string>();
}
public TAsset LoadAssetByPath<TAsset>(string path) where TAsset : Object
{
Debug.LogError("No valid loader adaptor - ensure you have Addressables installed or are in editor!");
return null;
}
public Task PreloadAssetsByPaths(IList<string> paths)
{
Debug.LogError("No valid loader adaptor - ensure you have Addressables installed or are in editor!");
return Task.CompletedTask;
}
public string GetPath(Object obj)
{
Debug.LogError("No valid loader adaptor - ensure you have Addressables installed or are in editor!");
return null;
}
public TrembleSyncSettings GetSyncSettings()
{
Debug.LogError("No valid loader adaptor - ensure you have Addressables installed or are in editor!");
return null;
}
public TrembleSyncSettings LoadSyncSettings()
{
Debug.LogError("No valid loader adaptor - ensure you have Addressables installed or are in editor!");
return null;
}
public Material LoadClipMaterial()
{
Debug.LogError("No valid loader adaptor - ensure you have Addressables installed or are in editor!");
return null;
}
public void ReleaseAll() { }
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a3c2e961a9234fcb94549e1f5bb3e8f2
timeCreated: 1716201662
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/AssetLoading/Adaptors/NullLoaderAdaptor.cs
uploadId: 939664
@@ -0,0 +1,170 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
//#define USE_ADDRESSABLES_IN_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using Object = UnityEngine.Object;
namespace TinyGoose.Tremble
{
public static class TrembleAssetLoader
{
// -----------------------------------------------------------------------------------------------------------------------------
// Adaptor
// -----------------------------------------------------------------------------------------------------------------------------
private static IAssetLoaderAdaptor s_LoaderAdaptor;
private static IAssetLoaderAdaptor LoaderAdaptor
{
get
{
if (s_LoaderAdaptor == null)
{
#if UNITY_EDITOR && !USE_ADDRESSABLES_IN_EDITOR
s_LoaderAdaptor = new EditorLoaderAdaptor();
#elif UNITY_SERVER
s_LoaderAdaptor = new NullLoaderAdaptor();
#elif ADDRESSABLES_INSTALLED
s_LoaderAdaptor = new AddressablesLoaderAdaptor();
#else
s_LoaderAdaptor = new NullLoaderAdaptor();
#endif
}
return s_LoaderAdaptor;
}
}
// -----------------------------------------------------------------------------------------------------------------------------
// Generic
// -----------------------------------------------------------------------------------------------------------------------------
public static string FindAssetPath<TAsset>(string name = "") where TAsset : Object
{
string[] paths = FindAssetPaths<TAsset>(name);
switch (paths.Length)
{
// Nothing matched - return null :(
case 0:
return null;
// Multiple matches - check if we have an EXACT match
case > 1:
{
foreach (string path in paths)
{
string filename = Path.GetFileNameWithoutExtension(path);
if (!filename.EqualsInvariant(name))
continue;
return path;
}
break;
}
}
// Only a single match, or couldn't disambiguate - return first
return paths[0];
}
public static string[] FindAssetPaths<TAsset>(string name = "") where TAsset : Object => FindAssetPaths(typeof(TAsset), name);
public static string[] FindAssetPaths(Type type, string name = "") => LoaderAdaptor.FindAssetPaths(type, name);
public static string GetPath(Object obj) => LoaderAdaptor.GetPath(obj);
public static GameObject LoadPrefabByName(string name) => LoadAssetByName<GameObject>(name);
public static GameObject LoadPrefabByPath(string path) => LoadAssetByPath<GameObject>(path);
public static TAsset LoadAssetByName<TAsset>(string name) where TAsset : Object
{
string path = FindAssetPath<TAsset>(name);
return LoadAssetByPath<TAsset>(path);
}
public static TAsset LoadAssetByPath<TAsset>(string path) where TAsset : Object
=> LoaderAdaptor.LoadAssetByPath<TAsset>(path);
public static async Task PreloadAssetsByPaths(IList<string> paths)
=> await LoaderAdaptor.PreloadAssetsByPaths(paths);
public static bool SupportsPreloading => LoaderAdaptor.SupportsPreloading;
public static void ReleaseAll() => LoaderAdaptor.ReleaseAll();
// -----------------------------------------------------------------------------------------------------------------------------
// Queries
// -----------------------------------------------------------------------------------------------------------------------------
private static int s_NumAssetMetadatas;
private static readonly Dictionary<string, int> s_MapNameToMetadataIndex = new();
private static readonly Dictionary<string, int> s_PathToMetadataIndex = new();
public static bool TryGetAssetDataFromMapName(string mapName, out AssetMetadata foundMetadata)
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
CreateLookups(syncSettings);
bool found = s_MapNameToMetadataIndex.TryGetValue(mapName, out int index);
foundMetadata = found ? syncSettings.AssetMetadatas[index] : default;
return found;
}
public static bool TryGetAssetDataFromPath(string path, out AssetMetadata foundMetadata)
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
CreateLookups(syncSettings);
bool found = s_PathToMetadataIndex.TryGetValue(path, out int index);
foundMetadata = found ? syncSettings.AssetMetadatas[index] : default;
return found;
}
private static void CreateLookups(TrembleSyncSettings syncSettings)
{
if (syncSettings.AssetMetadatas == null)
return;
if (s_NumAssetMetadatas == syncSettings.AssetMetadatas.Length)
return;
s_NumAssetMetadatas = syncSettings.AssetMetadatas.Length;
s_MapNameToMetadataIndex.Clear();
s_PathToMetadataIndex.Clear();
for (int i = 0; i < syncSettings.AssetMetadatas.Length; i++)
{
s_MapNameToMetadataIndex[syncSettings.AssetMetadatas[i].MapName] = i;
s_PathToMetadataIndex[syncSettings.AssetMetadatas[i].Path] = i;
}
}
public static AssetMetadata[] FindAssetDataMany(Func<AssetMetadata, bool> filter)
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
return syncSettings.AssetMetadatas != null
? syncSettings.AssetMetadatas.Where(filter).ToArray()
: Array.Empty<AssetMetadata>();
}
public static bool TryFindAssetData(Func<AssetMetadata, bool> filter, out AssetMetadata found)
{
AssetMetadata[] results = FindAssetDataMany(filter);
found = results.Length > 0 ? results[0] : default;
return results.Length > 0;
}
// -----------------------------------------------------------------------------------------------------------------------------
// Specifics
// -----------------------------------------------------------------------------------------------------------------------------
public static Material LoadClipMaterial() => LoaderAdaptor.LoadClipMaterial();
public static TrembleSyncSettings LoadSyncSettings() => LoaderAdaptor.LoadSyncSettings();
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: fe12c1abce564e1d8163e2f1197f500b
timeCreated: 1715666874
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/AssetLoading/TrembleAssetLoader.cs
uploadId: 939664
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aef5a15ddfbd44629a019e803c317adf
timeCreated: 1698412693
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c60f610e41f242fca42d8547953f86a0
timeCreated: 1711702716
@@ -0,0 +1,73 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using UnityEngine;
namespace TinyGoose.Tremble
{
public enum CheckerboardStyle
{
None,
Light,
Dark,
}
public enum BrushType
{
// Solid brush with a texture (default)
Solid,
// Liquid brush (trigger collision) with a texture
Liquid,
// An invisible brush which is a Unity trigger
Trigger,
// An invisible (but solid) brush
Invisible
}
/// <summary>
/// Marks this MonoBehaviour as a component which can be added to arbitrary Brush entities from a map.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class BrushEntityAttribute : EntityAttributeBase
{
/// <summary>
/// A Tremble-compatible Brush entity.
/// </summary>
/// <param name="trenchBroomName">The name in TrenchBroom. e.g. "cool_entity".</param>
/// <param name="category">The category for TrenchBroom. e.g. "ent".</param>
/// <param name="type">The brush type. e.g. Trigger, Solid, Liquid.</param>
/// <param name="colour">The colour of this brush's texture.</param>
/// <param name="checkerStyle">The type of checkerboard for this brush's texture.</param>
/// <param name="layer">The layer name to put this brush on.</param>
public BrushEntityAttribute(
string trenchBroomName = null,
string category = null,
BrushType type = BrushType.Solid,
string colour = "",
CheckerboardStyle checkerStyle = CheckerboardStyle.None,
string layer = "")
: base(trenchBroomName, category)
{
m_BrushType = type;
m_Colour = colour.TryParseQ3Colour(out Color value) ? value : null;
m_CheckerStyle = checkerStyle;
m_LayerName = layer;
}
private readonly BrushType m_BrushType;
private readonly Color? m_Colour;
private readonly CheckerboardStyle m_CheckerStyle;
private readonly string m_LayerName;
public BrushType BrushType => m_BrushType;
public Color? Colour => m_Colour;
public CheckerboardStyle CheckerStyle => m_CheckerStyle;
public string LayerName => m_LayerName;
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 36b1eeadd99b4fd9bfd817b15f131f87
timeCreated: 1711702807
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Attributes/Classes/BrushEntityAttribute.cs
uploadId: 939664
@@ -0,0 +1,26 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using UnityEngine;
namespace TinyGoose.Tremble
{
[AttributeUsage(AttributeTargets.Class)]
public abstract class EntityAttributeBase : SelectionBaseAttribute
{
protected EntityAttributeBase(string trenchBroomName = null, string category = null)
{
m_TrenchBroomName = trenchBroomName.Sanitise();
m_Category = category.Sanitise();
}
private readonly string m_TrenchBroomName;
private readonly string m_Category;
public string TrenchBroomName => m_TrenchBroomName;
public string Category => m_Category;
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 84c2d0a79b3844d98271142f9df938e3
timeCreated: 1711702912
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Attributes/Classes/EntityAttributeBase.cs
uploadId: 939664
@@ -0,0 +1,41 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using UnityEngine;
namespace TinyGoose.Tremble
{
/// <summary>
/// Marks this MonoBehaviour as a component which can be added to empty GameObjects as a Point entity.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class PointEntityAttribute : EntityAttributeBase
{
/// <summary>
/// A Tremble-compatible Point entity.
/// </summary>
/// <param name="trenchBroomName">The name in TrenchBroom. e.g. "cool_entity".</param>
/// <param name="category">The category for TrenchBroom. e.g. "ent".</param>
/// <param name="colour">The colour of the entity in TrenchBroom. e.g. green is "0.0 1.0 0.0".</param>
/// <param name="sprite">The sprite to show in TrenchBroom. Must be the name of a texture in your project. e.g. "T_MyTexture".</param>
/// <param name="size">The size of the cube for this entity in TrenchBroom. e.g. 5.</param>
public PointEntityAttribute(string trenchBroomName = null, string category = null, string colour = null, string sprite = null, float size = 0.25f)
: base(trenchBroomName, category)
{
m_Size = size;
m_Sprite = sprite;
m_Colour = colour.TryParseQ3Colour(out Color value) ? value : Color.white;
}
private readonly Color? m_Colour;
private readonly string m_Sprite;
private readonly float m_Size;
public Color Colour => m_Colour ?? Color.white;
public string Sprite => m_Sprite;
public float Size => m_Size;
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ac6bd87f35f74bf6b78590e9038c0ee4
timeCreated: 1711702870
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Attributes/Classes/PointEntityAttribute.cs
uploadId: 939664
@@ -0,0 +1,71 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
namespace TinyGoose.Tremble
{
/// <summary>
/// Marks this MonoBehaviour as being usable as a Point entity in TrenchBroom, using one or more prefabs in the project.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class PrefabEntityAttribute : EntityAttributeBase
{
/// <summary>
/// A Tremble-compatible Prefab entity.
/// </summary>
/// <param name="trenchBroomName">The name in TrenchBroom. e.g. "P_MyPrefab".</param>
/// <param name="category">The category for TrenchBroom. e.g. "game".</param>
/// <param name="excludePrefabs">Which prefabs containing this script to exclude. e.g. "P_BaseCoinPrefab".</param>
/// <param name="onlyIncludePrefabs">Which prefabs containing this script to include. e.g. "P_TrembleCoinPrefab".</param>
/// <param name="excludePrefab">Which prefabs containing this script to exclude. e.g. "P_BasePrefab".</param>
/// <param name="onlyIncludePrefab">Which prefabs containing this script to include. e.g. "P_TrembleCoinPrefab".</param>
/// <param name="onlyVariants">Only export variants, not root prefabs</param>
public PrefabEntityAttribute(string trenchBroomName = null, string category = null, string[] excludePrefabs = null, string[] onlyIncludePrefabs = null, string excludePrefab = null, string onlyIncludePrefab = null, bool onlyVariants = false, string overrideSprite = null)
: base(trenchBroomName: trenchBroomName, category: category)
{
m_ExcludedPrefabNames = excludePrefab != null ? new [] { excludePrefab } : excludePrefabs;
m_OnlyIncludedPrefabNames = onlyIncludePrefab != null ? new [] { onlyIncludePrefab } : onlyIncludePrefabs;
m_OverrideSprite = overrideSprite;
m_OnlyVariants = onlyVariants;
}
private readonly string[] m_ExcludedPrefabNames;
private readonly string[] m_OnlyIncludedPrefabNames;
private readonly string m_OverrideSprite;
private readonly bool m_OnlyVariants;
public bool IsPrefabIncluded(string prefabName)
{
// Is excluded explicitly?
if (m_ExcludedPrefabNames != null)
{
foreach (string excluded in m_ExcludedPrefabNames)
{
if (excluded.ToSimpleRegex().IsMatch(prefabName))
return true;
}
}
// Include everything?
if (m_OnlyIncludedPrefabNames == null)
return true;
// See if it's on the whitelist
foreach (string included in m_OnlyIncludedPrefabNames)
{
if (included.ToSimpleRegex().IsMatch(prefabName))
return true;
}
return false;
}
public string OverrideSprite => m_OverrideSprite;
public bool OnlyVariants => m_OnlyVariants;
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7573313e4df24673a7c5cbbc3763e392
timeCreated: 1711702750
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Attributes/Classes/PrefabEntityAttribute.cs
uploadId: 939664
@@ -0,0 +1,19 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.Text.RegularExpressions;
namespace TinyGoose.Tremble
{
public static class SimpleRegexUtil
{
public static Regex ToSimpleRegex(this string pattern)
{
// This is overly simplistic. But essentially, changes a pattern like
// "my*thing" into a Regex like "^my.+thing$"
return new($"^{pattern.Replace("*", ".+")}$");
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: eab674612dfd47fd833e72b6bbd5f974
timeCreated: 1713786722
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Attributes/Classes/SimpleRegexUtil.cs
uploadId: 939664
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5fa882a49b914d46a6bdb96c4d52677a
timeCreated: 1711702720
@@ -0,0 +1,36 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
namespace TinyGoose.Tremble
{
/// <summary>
/// Marks this boolean as being a spawnflags field.
/// You can either provide an explicit bit to use, or omit and let the system guess based on order.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class SpawnFlagsAttribute : Attribute
{
public SpawnFlagsAttribute(int bit = -1)
{
//NOTE: Change MapBspGetters if this changes!!
m_Bit = bit;
}
private readonly int m_Bit;
public int Bit => m_Bit;
}
/// <summary>
/// Marks this boolean as never being a spawnflags field.
/// Use this is you have the auto-spawnflags flag set for booleans.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class NoSpawnFlagsAttribute : Attribute
{
//NOTE: Change MapBspGetters if this changes!!
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0102725e5aa1439cbb30a9675ce4513d
timeCreated: 1705750006
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Attributes/Fields/SpawnFlagsAttribute.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;
namespace TinyGoose.Tremble
{
/// <summary>
/// Marks a field as being serialised for maps (even if [SerializeField] is absent)
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class TrembleAttribute : Attribute
{
public TrembleAttribute(string name = null, bool isRotation = false)
{
//NOTE: Change MapBspGetters if this changes!!
m_OverrideName = name;
m_IsRotation = isRotation;
}
private readonly string m_OverrideName;
private readonly bool m_IsRotation;
public string OverrideName => m_OverrideName;
public bool IsRotation => m_IsRotation;
}
/// <summary>
/// Marks a field as NEVER being serialised for maps (even if [SerializeField] is on)
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class NoTrembleAttribute : Attribute
{
//NOTE: Change MapBspGetters if this changes!!
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 071a562d7c7647a18ebeb57869dfad3a
timeCreated: 1711702937
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Attributes/Fields/TrembleAttribute.cs
uploadId: 939664
+9
View File
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 0a01f5c71da3c76438e49ca9f447b5a1
folderAsset: yes
timeCreated: 1628505674
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 613ba1a10f2f46bf811791d8497c3047
timeCreated: 1715620765
@@ -0,0 +1,31 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor.AssetImporters;
using UnityEngine;
using System.IO;
namespace TinyGoose.Tremble.Editor
{
[ScriptedImporter(1, "fgd", -1000)]
public class FgdFileAssetImporter : ScriptedImporter
{
[SerializeField] private bool m_Test;
// -----------------------------------------------------------------------------------------------------------------------------
// Entry Point
// -----------------------------------------------------------------------------------------------------------------------------
public override void OnImportAsset(AssetImportContext ctx)
{
string assetName = Path.GetFileNameWithoutExtension(ctx.assetPath);
FgdFileAsset wadAsset = ScriptableObject.CreateInstance<FgdFileAsset>();
wadAsset.name = assetName;
ctx.AddObjectToAsset(assetName, wadAsset);
ctx.SetMainObject(wadAsset);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: fa734ddeb7244212a39e586474ae10a2
timeCreated: 1724694974
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/AssetImporters/FgdFileAssetImporter.cs
uploadId: 939664
@@ -0,0 +1,231 @@
//
// 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 UnityEditor.AssetImporters;
using UnityEngine;
using System.IO;
using System.Linq;
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ScriptedImporter(1, "map", importQueueOffset: 20000)]
public class TrembleMapAssetImporter : ScriptedImporter
{
// -----------------------------------------------------------------------------------------------------------------------------
// Exposed
// -----------------------------------------------------------------------------------------------------------------------------
[SerializeField, Tooltip("The Map Processors to use (or none). Used to perform logic for individual entities from the map or on the map as a whole.")]
private MapProcessorClass[] m_Processors;
[SerializeField, Tooltip("Whether to split the mesh when surface area is too large?")]
private bool m_SplitMesh = true;
[SerializeField, Tooltip("Maximum surface area (in m^2) before splitting meshes")]
private float m_MaxMeshSurfaceArea = 3000;
[SerializeField, Tooltip("The angle above which to smooth edges.")]
private float m_SmoothingAngle = 45;
[SerializeField, Tooltip("Extra commandline to pass to Q3Map2 - see https://en.wikibooks.org/wiki/Q3Map2 for more details")]
private string m_ExtraCommandLineArgs = "";
// -----------------------------------------------------------------------------------------------------------------------------
// Entry Point
// -----------------------------------------------------------------------------------------------------------------------------
public override async void OnImportAsset(AssetImportContext ctx)
{
// Check autosave and bail
if (String.Equals(Path.GetFileName(Path.GetDirectoryName(ctx.assetPath)), "autosave", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
// Check we have a functioning setup (not first import, for example)
if (!Directory.Exists(TrembleConsts.BASEQ3_PATH))
{
ctx.LogImportWarning($"Can't import map {Path.GetFileName(ctx.assetPath)}. Please sync first.");
return;
}
if (MapRepairWindow.IsMapForeign(ctx.assetPath, out string gameName))
{
gameName ??= "Quake(?)";
bool repairNow = EditorUtility.DisplayDialog(
title: "Map Repair Required",
message: $"You are trying to import a map from '{gameName}'. Before you can use this map, " +
"we'll need to repair it. This converts it for use with Tremble " +
$"and '{TrembleConsts.GAME_NAME}'. Shall we?",
"Repair and Resave", "Not Now");
if (repairNow)
{
MapRepairWindow.OpenMapRepair(ctx.assetPath);
}
ctx.LogImportError($"Map '{Path.GetFileNameWithoutExtension(ctx.assetPath)}' needs repair to function with Tremble. Right-click the map and select 'Repair Map'.");
return;
}
// Clear our cached asset lists
UnityObjectFieldConverterStatics.s_CachedAssetLists.Clear();
MapProcessorClass[] allProcessors = TrembleSyncSettings.Get().PipelineRunMapProcessors // global processors
? TrembleSyncSettings.Get().EnabledMapProcessors
: Array.Empty<MapProcessorClass>();
if (m_Processors != null)
{
allProcessors = allProcessors.Concat(m_Processors).Distinct().ToArray(); // map-based processors
}
TrembleMapImport importer = new(new()
{
ProcessorClasses = allProcessors,
SplitMesh = m_SplitMesh,
MaxMeshSurfaceArea = m_MaxMeshSurfaceArea,
SmoothingAngle = m_SmoothingAngle,
ExtraCommandLineArgs = m_ExtraCommandLineArgs,
AssetGUID = AssetDatabase.GUIDFromAssetPath(ctx.assetPath).ToString(),
OnObjectAdded = ctx.AddObjectToAsset,
OnDependencyAdded = o =>
{
if (TrembleSyncSettings.Get().AutomaticallyReimportWhenDependencyChanges)
{
ctx.DependsOnSourceAsset(AssetDatabase.GetAssetPath(o));
}
},
OnWadsFound = OnWadsFound,
OnEntityAdded = OnEntityAdded,
OnError = err => ctx.LogImportError(err),
OnWarning = warn => ctx.LogImportWarning(warn),
OnQ3Map2Result = OnQ3Map2Result
});
TrembleTimer.BeginSession($"Tremble Map Import for {Path.GetFileNameWithoutExtension(ctx.assetPath)}");
MapDocument document = await importer.ImportMapAndCreatePrefab(ctx.assetPath);
TrembleTimer.EndSession();
ctx.AddObjectToAsset("map", document.gameObject);
ctx.SetMainObject(document.gameObject);
}
private void OnEntityAdded(GameObject gameObject, Type entityType)
{
if (entityType == null)
return;
// Point entity with explicit icon?
entityType.GetCustomAttributes(out PointEntityAttribute pea);
if (pea is { Sprite: not null })
{
string spritePath = TrembleAssetLoader.FindAssetPath<Texture2D>(pea.Sprite);
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(spritePath);
EditorGUIUtility.SetIconForObject(gameObject, texture);
return;
}
// Implicit
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
if (syncSettings.EntityIconStyle == EntityIconStyle.Nothing)
return;
if (entityType.FullName == null)
return;
int hash = Math.Abs(entityType.FullName.GetHashCode());
switch (syncSettings.EntityIconStyle)
{
case EntityIconStyle.ColouredDiamondShapes:
int shapeIdx = hash % (int)EditorGameObjectIconUtil.EditorIcon.MAX;
gameObject.SetEditorIcon((EditorGameObjectIconUtil.EditorIcon)shapeIdx);
break;
case EntityIconStyle.ColouredLabels:
int labelIdx = hash % (int)EditorGameObjectIconUtil.EditorLabel.MAX;
gameObject.SetEditorIcon((EditorGameObjectIconUtil.EditorLabel)labelIdx);
break;
}
}
private void OnWadsFound(string[] allWads)
{
string[] validWads = allWads.Where(File.Exists).ToArray();
foreach (string wad in validWads)
{
Debug.LogWarning($"Wad file found in map: {Path.GetFileName(wad)}. You should drop this file into your Unity project in order to use its materials!");
}
// bool copyThem = false;
//
// if (validWads.Length == 0)
// {
// EditorUtility.DisplayDialog(
// title: "Wad Files",
// message: $"Your map contains {allWads.Length} unknown wad file reference(s). " +
// "These are not directly supported by Tremble. If you want to use the textures " +
// "from your wad file(s), drag them into your Unity project and extract them.", "Ahh, okay");
// }
// else if (validWads.Length == allWads.Length)
// {
// copyThem = EditorUtility.DisplayDialog(
// title: "Wad Files",
// message: $"Your map contains {validWads.Length} wad file reference(s). " +
// "These are not directly supported by Tremble. If you want to use the textures " +
// "from your wad file(s), they need extracting into your Unity project. Want to import them now?", "Yup!", "Nope");
// }
// else
// {
// int numMissing = allWads.Length - validWads.Length;
//
// copyThem = EditorUtility.DisplayDialog(
// title: "Wad Files",
// message: $"Your map contains {validWads.Length} valid wad file reference(s), and {numMissing} which Tremble couldn't locate. " +
// "These are not directly supported by Tremble. If you want to use the textures " +
// "from your wad file(s), they need extracting into your Unity project. Want to import the ones we could find now?", "Yup!", "Nope");
// }
//
// if (copyThem)
// {
// string folderName = Path.GetDirectoryName(assetPath);
//
// foreach (string existingPath in validWads)
// {
// string filename = Path.GetFileName(existingPath);
// File.Copy(existingPath, Path.Combine(folderName, filename));
// }
//
// EditorApplication.delayCall += () => AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
// }
}
private void OnQ3Map2Result(Q3Map2Result result, List<string> output)
{
// Show results?
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
bool alwaysShowResult = syncSettings.Q3Map2ResultDisplayType == Q3Map2ResultDisplayType.AlwaysShow;
bool showWarnings = syncSettings.Q3Map2ResultDisplayType == Q3Map2ResultDisplayType.ShowWhenWarningsOccur;
bool showWindow = result is Q3Map2Result.Failed or Q3Map2Result.FailedWithMissingTextures;
showWindow |= (result is Q3Map2Result.SucceededWithWarnings && showWarnings);
showWindow |= alwaysShowResult;
if (showWindow)
{
Q3Map2OutputWindow.Open(output, alwaysShowResult);
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ab79840212e84ded961df5ef93736275
timeCreated: 1698136122
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/AssetImporters/TrembleMapAssetImporter.cs
uploadId: 939664
@@ -0,0 +1,42 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor.AssetImporters;
using UnityEngine;
using System.IO;
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[ScriptedImporter(1, "wad", -1000)]
public class WadFileAssetImporter : ScriptedImporter
{
// -----------------------------------------------------------------------------------------------------------------------------
// Entry Point
// -----------------------------------------------------------------------------------------------------------------------------
public override void OnImportAsset(AssetImportContext ctx)
{
string assetName = Path.GetFileNameWithoutExtension(ctx.assetPath);
WadFileAsset wadAsset = ScriptableObject.CreateInstance<WadFileAsset>();
wadAsset.Modify(so =>
{
SerializedProperty materials = so.FindBackedProperty(nameof(wadAsset.MaterialNames));
WadParser parser = new(ctx.assetPath);
WadFile wadFile = parser.Parse();
foreach (WadEntry entry in wadFile.Entries)
{
materials.AppendArrayElement().stringValue = entry.Name;
}
});
wadAsset.name = assetName;
ctx.AddObjectToAsset(assetName, wadAsset);
ctx.SetMainObject(wadAsset);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: aa9cb8112a5643be99429353283f1858
timeCreated: 1722847571
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/AssetImporters/WadFileAssetImporter.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ab61355f926040629a80ee28e5683483
timeCreated: 1709458791
@@ -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 UnityEditor;
namespace TinyGoose.Tremble.Editor
{
public class FgdFileAssetPostprocessor : AssetPostprocessor
{
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (string requestFileName in importedAssets)
{
// We imported something, but not a MapFileAsset
if (AssetDatabase.GetMainAssetTypeAtPath(requestFileName) != typeof(FgdFileAsset))
continue;
bool success = false;
try
{
FgdParser.Import(requestFileName);
success = true;
}
finally
{
if (!success)
{
EditorUtility.DisplayDialog("Import Failed!", "Failed to import FGD file :(, try again!", "Okay");
}
AssetDatabase.DeleteAsset(requestFileName);
}
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 04f793041d9e49d7822ba5068a727bb7
timeCreated: 1724694689
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/AssetProcessors/FgdFileAssetPostprocessor.cs
uploadId: 939664
@@ -0,0 +1,146 @@
//
// 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 System.Text;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public class MapFileAssetPostprocessor : AssetPostprocessor
{
private static readonly Stack<string> s_MapsToConvert = new();
private static string s_LastMapPath;
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (string requestFileName in importedAssets)
{
// We imported something, but not a MapFileAsset
if (AssetDatabase.GetMainAssetTypeAtPath(requestFileName) != typeof(MapFileAsset))
continue;
// Check we haven't already processed this map, or already plan to
string mapFileName = GetMapFileName(requestFileName);
if (File.Exists(mapFileName) || s_MapsToConvert.Contains(requestFileName))
continue;
// Add to the work queue
s_MapsToConvert.Push(requestFileName);
// Start conversion process
EditorApplication.update -= PerformConversionOnUpdate;
EditorApplication.update += PerformConversionOnUpdate;
}
}
private static string GetMapFileName(string requestFileName) => requestFileName.Replace(".asset", ".map");
private static void PerformConversionOnUpdate()
{
// Nothing left to do - stop!
if (s_MapsToConvert.Count == 0)
{
EditorApplication.update -= PerformConversionOnUpdate;
return;
}
string requestFileName = s_MapsToConvert.Pop();
// Delete the request file
string mapFileName = GetMapFileName(requestFileName);
AssetDatabase.DeleteAsset(requestFileName);
// Open new file for write
using FileStream file = File.Create(mapFileName);
using StreamWriter sw = new(file, Encoding.ASCII);
// Write a blank map, or copy template
TrembleSyncSettings trembleSyncSettings = TrembleSyncSettings.Get();
if (trembleSyncSettings.TemplateMap && trembleSyncSettings.TemplateMap.TryGetComponent(out MapDocument _))
{
string templatePath = AssetDatabase.GetAssetPath(trembleSyncSettings.TemplateMap);
using FileStream templateFile = File.OpenRead(templatePath);
using StreamReader sr = new(templateFile, Encoding.ASCII);
while (!sr.EndOfStream)
{
sw.WriteLine(sr.ReadLine());
}
sr.Close();
}
else
{
// New file
sw.WriteLine($"// Game: {TrembleConsts.GAME_NAME}");
sw.WriteLine("// Format: Quake3 (Valve)");
sw.WriteLine("// entity 0");
sw.WriteLine("{");
sw.WriteLine("\"mapversion\" \"220\"");
sw.Write("\"_tb_textures\" \"");
List<MaterialGroup> materialGroups = trembleSyncSettings.GetMaterialGroupsOrDefault();
sw.Write(String.Join(';', materialGroups.Select(mg => $"textures/{mg.Name}")));
sw.WriteLine("\"");
sw.WriteLine("\"classname\" \"worldspawn\"");
sw.WriteLine("// brush 0");
sw.WriteLine("{");
sw.WriteLine("( -128 -128 -64 ) ( -128 -127 -64 ) ( -128 -128 -63 ) __TB_empty [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5");
sw.WriteLine("( -128 -128 -64 ) ( -128 -128 -63 ) ( -127 -128 -64 ) __TB_empty [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5");
sw.WriteLine("( -128 -128 -64 ) ( -127 -128 -64 ) ( -128 -127 -64 ) __TB_empty [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 0.5 0.5");
sw.WriteLine("( 128 128 64 ) ( 128 129 64 ) ( 129 128 64 ) __TB_empty [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 0.5 0.5");
sw.WriteLine("( 128 128 64 ) ( 129 128 64 ) ( 128 128 65 ) __TB_empty [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5");
sw.WriteLine("( 128 128 64 ) ( 128 128 65 ) ( 128 129 64 ) __TB_empty [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5");
sw.WriteLine("}");
sw.WriteLine("}");
}
sw.Close();
// Open in TB?
if (EditorUtility.DisplayDialog("Map Created!", "Want to open it now in TrenchBroom?", "Yep!", "Not now..."))
{
TrembleEditorAPI.SyncToTrenchBroom();
TrenchBroomUtil.OpenWithTrenchBroom(mapFileName);
s_LastMapPath = mapFileName;
EditorApplication.update -= PromptToOpenMapOnFocus;
EditorApplication.update += PromptToOpenMapOnFocus;
}
}
private static void PromptToOpenMapOnFocus()
{
#if UNITY_2022_1_OR_NEWER
if (!EditorApplication.isFocused)
return;
#endif
EditorApplication.update -= PromptToOpenMapOnFocus;
// Place in world?
if (EditorUtility.DisplayDialog("Welcome back!", "Want to place your brand new map in this Unity scene?", "Yep!", "No thanks"))
{
AssetDatabase.Refresh();
GameObject mapPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(s_LastMapPath);
if (!mapPrefab)
{
EditorUtility.DisplayDialog("Oh no!", "Your new map failed to import. You may need to restart Unity and then reimport it. (this is a rare bug)", "Okay :(");
return;
}
GameObject mapObject = (GameObject)PrefabUtility.InstantiatePrefab(mapPrefab);
mapObject.transform.position = Vector3.zero;
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6642958d88f3475995ac5c15d631af2d
timeCreated: 1707842798
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/AssetProcessors/MapFileAssetPostprocessor.cs
uploadId: 939664
@@ -0,0 +1,202 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Diagnostics;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using Debug = UnityEngine.Debug;
namespace TinyGoose.Tremble.Editor
{
public class MapLiveUpdatePostprocessor : AssetPostprocessor
{
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (!Application.isPlaying || !TrembleSyncSettings.Get().UseLiveUpdate)
return;
// Get list of imported GUIDs as strings
string[] importedGUIDs = importedAssets
.Select(path => AssetDatabase.GUIDFromAssetPath(path).ToString())
.ToArray();
// If we reimport during gameplay, disable and re-enable the map to refresh the collision
Scene scene = SceneManager.GetActiveScene();
MapDocument[] updatedMaps = scene.GetRootGameObjects()
.SelectMany(go => go.GetComponentsInChildren<MapDocument>())
.Where(doc => importedGUIDs.Contains(doc.OriginalAssetGuid))
.ToArray();
foreach (MapDocument map in updatedMaps)
{
Stopwatch timer = new();
timer.Start();
string assetPath = AssetDatabase.GUIDToAssetPath(map.OriginalAssetGuid);
GameObject prefabMap = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
MapDocument prefabMapDocument = prefabMap.GetComponent<MapDocument>();
// Re-instance worldspawn
GameObject newWorldspawn = InstanceNewWorldspawn(map, prefabMapDocument);
// Spawn any new objects, and transfer vars to existing
int numNewlySpawned = 0;
int numUpdated = 0;
foreach (Transform prefabChild in prefabMap.transform)
{
if (prefabChild.gameObject == prefabMapDocument.WorldspawnObject)
continue;
bool isNew = true;
foreach (Transform mapChild in map.transform)
{
if (mapChild.name != prefabChild.name)
continue;
numUpdated++;
// Object is static :( grab it from the map instead
if (mapChild.gameObject.isStatic && HasEntityMoved(mapChild, prefabChild))
{
Debug.LogWarning($"Tremble LiveUpdate: Replacing static object '{mapChild.name}' with new copy. This could cause issues!");
GameObject.Destroy(mapChild.gameObject);
break;
}
if (TryFindTrembleComponent(prefabChild.gameObject, out Component prefabComp) &&
TryFindTrembleComponent(mapChild.gameObject, out Component mapComp))
{
// Disable, copy across data, re-enable
mapComp.gameObject.SetActive(false);
CopySafeSerialisedData(from: prefabComp, to: mapComp);
mapComp.gameObject.SetActive(true);
// Handle live update callbacks
switch (mapComp)
{
case IOnLiveUpdate_Simple:
mapComp.SendMessage("OnDestroy", SendMessageOptions.DontRequireReceiver);
mapComp.SendMessage("Awake", SendMessageOptions.DontRequireReceiver);
mapComp.SendMessage("Start", SendMessageOptions.DontRequireReceiver);
break;
case IOnLiveUpdate liveUpdateable:
liveUpdateable.OnLiveUpdated();
break;
}
}
isNew = false;
break;
}
if (isNew)
{
numNewlySpawned++;
GameObject.Instantiate(prefabChild.gameObject, newWorldspawn.transform);
}
}
timer.Stop();
TryShowGameViewNotification($"Tremble LiveUpdate:\n{numNewlySpawned} new ents\n{numUpdated} updates\nin {timer.ElapsedMilliseconds}ms");
}
}
private static bool HasEntityMoved(Transform mapChild, Transform prefabChild)
{
if (Vector3.SqrMagnitude(mapChild.position - prefabChild.position) > 0f)
return true;
if (Vector3.SqrMagnitude(mapChild.rotation.eulerAngles - prefabChild.rotation.eulerAngles) > 0f)
return true;
return false;
}
private static GameObject InstanceNewWorldspawn(MapDocument map, MapDocument prefabMapDocument)
{
// Remove worldspawn
GameObject.Destroy(map.WorldspawnObject);
// Reinstance worldspawn
GameObject newWorldspawn = GameObject.Instantiate(prefabMapDocument.WorldspawnObject, map.transform, true);
newWorldspawn.transform.localPosition = Vector3.zero;
newWorldspawn.transform.localRotation = Quaternion.identity;
map.Modify(so => so.FindProperty("m_WorldspawnObject").objectReferenceValue = newWorldspawn);
newWorldspawn.transform.SetAsFirstSibling();
// Disable and re-enable - if you don't do this, Unity does not update the colliders
map.gameObject.SetActive(false);
map.gameObject.SetActive(true);
return newWorldspawn;
}
private static bool TryShowGameViewNotification(string note)
{
Type gameViewT = typeof(EditorWindow).Assembly.GetType("UnityEditor.GameView");
EditorWindow[] windows = Resources.FindObjectsOfTypeAll(gameViewT).Cast<EditorWindow>().ToArray();
foreach (EditorWindow win in windows)
{
win.ShowNotification(new(note), 1f);
}
return windows.Length > 0;
}
private static bool TryFindTrembleComponent(GameObject go, out Component component)
{
foreach (Component c in go.GetComponents<Component>())
{
if (!c.GetType().HasCustomAttribute<EntityAttributeBase>())
continue;
component = c;
return true;
}
component = default;
return false;
}
private static void CopySafeSerialisedData(Component from, Component to)
{
// For now, we just copy simple data - floats, colours, vectors etc.
// References and assets are a bit spoopier and we can't do too much there sadly!
SerializedObject fromObject = new(from);
SerializedObject toObject = new(to);
SerializedProperty iterator = fromObject.GetIterator();
iterator.NextVisible(true);
do
{
if (iterator.propertyType
is SerializedPropertyType.ObjectReference
or SerializedPropertyType.ManagedReference)
{
if (iterator.objectReferenceValue is Component)
continue;
}
toObject.CopyFromSerializedProperty(iterator);
} while (iterator.NextVisible(false));
toObject.ApplyModifiedPropertiesWithoutUndo();
// Also copy basic positioning information
Transform fromTransform = from.transform;
Transform toTransform = to.transform;
toTransform.localPosition = fromTransform.localPosition;
toTransform.localRotation = fromTransform.localRotation;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: de0e937a05b84f928a4e0c9ff6d32146
timeCreated: 1708536137
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/AssetProcessors/MapLiveUpdatePostprocessor.cs
uploadId: 939664
@@ -0,0 +1,22 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
public class TrembleCacheDropperPostprocessor : AssetPostprocessor
{
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (!TrembleSyncSettings.Get().AutomaticallyReimportWhenDependencyChanges)
{
MapHierarchyIcons.ResetModifiedTimes();
}
EditorLoaderAdaptor.DropCachedAssetPaths();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5e639ddfe46c434eb843b1bd7ee8259c
timeCreated: 1713956404
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/AssetProcessors/TrembleCacheDropperPostprocessor.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 UnityEditor;
namespace TinyGoose.Tremble.Editor
{
public class WadFileAssetPostprocessor : AssetPostprocessor
{
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (string requestFileName in importedAssets)
{
// We imported something, but not a WadFileAsset
if (AssetDatabase.GetMainAssetTypeAtPath(requestFileName) != typeof(WadFileAsset))
continue;
bool success = false;
try
{
WadAssetImporterEditor.Extract(requestFileName);
success = true;
}
finally
{
if (!success)
{
EditorUtility.DisplayDialog("Import Failed!", "Failed to import WAD file :(, try again!", "Okay");
}
AssetDatabase.DeleteAsset(requestFileName);
}
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c83945126e2c4fa9aa009f1463530bad
timeCreated: 1722936486
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/AssetProcessors/WadFileAssetPostprocessor.cs
uploadId: 939664
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b41c2b89aca249e7a68bc85e07f144ed
timeCreated: 1707741796
@@ -0,0 +1,146 @@
//
// 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.Globalization;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public class DiagnosticsWindow : EditorWindow
{
// -----------------------------------------------------------------------------------------------------------------------------
// Menu Items
// -----------------------------------------------------------------------------------------------------------------------------
[MenuItem("Tools/Tremble/Advanced/Debug/Diagnostics")]
public static void OpenDiagnosticsWindow()
{
DiagnosticsWindow window = GetWindow<DiagnosticsWindow>(true, "Tremble Diagnostics", true);
Vector2 size = new(900f, 800f);
window.minSize = size;
window.position = new(EditorGUIUtility.GetMainWindowPosition().center - size / 2f, size);
window.Show();
}
private Vector2 m_ScrollPosition = Vector2.zero;
private FgdWriter m_FgdWriter;
private string m_TrenchbroomInstallPath;
private Q3Map2VersionInfo m_Q3Map2VersionInfo;
private void OnEnable()
{
m_Q3Map2VersionInfo = Q3Map2Dll.GetTinyGooseVersionInfo();
m_FgdWriter = TrembleSync.GatherEntities();
m_TrenchbroomInstallPath = Path.GetFullPath(TrenchBroomUtil.TrenchBroomPath);
}
private void OnGUI()
{
m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition);
{
Header("Basic Info");
{
Diagnostic("Tremble Version", () => TrembleConsts.VERSION_STRING);
Diagnostic("Unity Version", () => Application.unityVersion);
Diagnostic("Configured TB Version", () => TrembleSyncSettings.Get().TrenchBroomVersion);
Diagnostic("OS", () => Application.platform.ToString());
Diagnostic("OS Locale", () => CultureInfo.CurrentCulture.DisplayName);
}
Header("Project Info");
{
Diagnostic("Project Name", () => Application.productName);
Diagnostic("Company Name", () => Application.companyName);
Diagnostic("TB Game Name", () => TrembleConsts.GAME_NAME);
}
Header("Paths");
{
Diagnostic("User Path", () => Environment.GetFolderPath(Environment.SpecialFolder.Personal));
Diagnostic("Map Staging Path", () => Application.temporaryCachePath);
Diagnostic("Project Path", () => Path.GetFullPath(Directory.GetCurrentDirectory()));
Diagnostic("BaseQ3 Path", () => TrembleConsts.BASEQ3_PATH);
Diagnostic("TB Game Path", () => Path.GetFullPath(TrenchBroomUtil.GetGameFolder()));
Diagnostic("TB Install Path", () => m_TrenchbroomInstallPath);
}
Header("Q3Map2");
{
Diagnostic("Q3Map2 Loaded?", () => Q3Map2Dll.IsLoaded);
Diagnostic("Q3Map2 Version", () => $"{m_Q3Map2VersionInfo.Major}.{m_Q3Map2VersionInfo.Minor}.{m_Q3Map2VersionInfo.Patch}");
Diagnostic("Q3Map2 Hash", () => m_Q3Map2VersionInfo.GitHash);
Diagnostic("Q3Map2 Cmdline", () => TrembleSyncSettings.Get().ExtraCommandLineArgs);
}
Header("Game");
{
Diagnostic("Worldspawn Entity", () => m_FgdWriter.WorldspawnClass.Name);
Diagnostic("Map Processors", () => TrembleSyncSettings.Get().EnabledMapProcessors.Select(m => m.Class.Name));
int numMaterials = TrembleSyncSettings.Get().MaterialGroups.Sum(mg => mg.Materials.Length);
Diagnostic("Material Groups", () => TrembleSyncSettings.Get().MaterialGroups.Select(mg => $"{mg.Name} ({mg.Materials.Length})").Append($"= {numMaterials} total materials"));
int numBases = m_FgdWriter.BaseClasses.Count;
int numPoints = m_FgdWriter.Classes.Count(e => e.Type == FgdClassType.Point);
int numBrushes = m_FgdWriter.Classes.Count(e => e.Type == FgdClassType.Brush);
Diagnostic("Entity Counts", () => $"{numBases} base(s), {numPoints} point(s), {numBrushes} brush(es)");
}
}
GUILayout.EndScrollView();
}
private void Header(string header)
{
GUILayout.Space(20f);
GUILayout.Label(header, EditorStyles.boldLabel);
}
private void Diagnostic(string labelTitle, Func<object> contents)
{
GUILayout.BeginHorizontal();
{
GUILayout.Space(20f);
GUILayout.Label(labelTitle, GUILayout.Width(200f));
try
{
object contentObject = contents();
if (contentObject is IEnumerable contentObjects and not string)
{
GUILayout.BeginVertical();
{
IEnumerator objectEnumerator = contentObjects.GetEnumerator();
using IDisposable _ = objectEnumerator as IDisposable; // ensure disposed
while (objectEnumerator.MoveNext())
{
GUILayout.Label(objectEnumerator.Current?.ToString() ?? "<null>", ManualStyles.Styles.Code, GUILayout.ExpandWidth(true));
}
}
GUILayout.EndVertical();
}
else
{
GUILayout.Label(contentObject.ToString(), ManualStyles.Styles.Code, GUILayout.ExpandWidth(true));
}
}
catch (Exception ex)
{
GUI.color = Color.red;
GUILayout.Label($"FAILED: {ex.Message}", ManualStyles.Styles.Code, GUILayout.ExpandWidth(true));
GUI.color = Color.white;
}
}
GUILayout.EndHorizontal();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: da8c52d53f104a499424ec4d338cb7d1
timeCreated: 1740735928
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/DiagnosticsWindow.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: de5403ed015546f98e39cd02b728ba9b
timeCreated: 1710244760
@@ -0,0 +1,125 @@
//
// 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.Linq;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public abstract class BaseClassPickerDrawer<TClassType> : PropertyDrawer
{
// -----------------------------------------------------------------------------------------------------------------------------
// Consts
// -----------------------------------------------------------------------------------------------------------------------------
private const string INDENT_STRING = " ";
private const string NONE_STRING = "(none)";
protected abstract string ClassNamePropertyName { get; }
// -----------------------------------------------------------------------------------------------------------------------------
// State
// -----------------------------------------------------------------------------------------------------------------------------
private List<Type> m_AllClasses;
private int m_SelectedClass;
private bool m_Inited;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// Lazy-load class catalog if needed
if (!m_Inited)
{
m_Inited = true;
GenerateClassCatalog();
}
// Draw label & icon
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
EditorGUI.indentLevel = 0;
// Generate class list
string[] classDisplayNames = m_AllClasses.Select(t => GetHumanClassName(t)).ToArray();
// Calculate current index
SerializedProperty classProperty = property.FindPropertyRelative(ClassNamePropertyName);
int prevSelectedClass = m_AllClasses.FindIndex(c => c.FullName == classProperty.stringValue);
if (prevSelectedClass == -1)
{
prevSelectedClass = 0;
}
m_SelectedClass = EditorGUI.Popup(position, "", prevSelectedClass, classDisplayNames);
EditorGUI.BeginProperty(position, GUIContent.none, property);
classProperty.stringValue = m_AllClasses[m_SelectedClass].FullName;
EditorGUI.EndProperty();
// Draw icon overlay
Rect iconRect = position;
iconRect.width = position.height;
iconRect.x += 2f;
iconRect.y += 2f;
iconRect.width -= 4f;
iconRect.height -= 4f;
position.x += position.height;
position.width -= position.height;
GUI.DrawTexture(iconRect, EditorGUIUtility.FindTexture("cs Script Icon"));
}
private void GenerateClassCatalog()
{
List<Type> allClasses = TypeCache.GetTypesDerivedFrom(typeof(TClassType)).ToList();
m_AllClasses = new(allClasses.Count + 1);
if (!typeof(TClassType).IsAbstract)
{
m_AllClasses.Add(typeof(TClassType));
}
m_AllClasses.AddRange(allClasses);
}
private static string GetHumanClassName(Type type, bool indent = true)
{
string fullClassName = type.FullName;
if (fullClassName == null)
return indent ? (INDENT_STRING + NONE_STRING) : NONE_STRING;
int lastDot = fullClassName.LastIndexOf('.');
// If it ends in dot (?!) or has no dot - return class as-is
if (lastDot == -1 || lastDot == fullClassName.Length - 1)
return fullClassName;
string shortClassName = fullClassName.Substring(lastDot + 1);
if (shortClassName.StartsWithInvariant("Null", caseSensitive: true))
{
return indent ? (INDENT_STRING + NONE_STRING) : NONE_STRING;
}
bool doneFirst = false;
List<char> output = new(shortClassName.Length + 10);
foreach (char c in shortClassName)
{
if (Char.IsUpper(c) && doneFirst)
{
output.Add(' ');
}
output.Add(c);
doneFirst = true;
}
string humanName = new(output.ToArray());
return indent ? (INDENT_STRING + humanName) : humanName;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7b7aeb7420ec45cda02dea687dbd8e57
timeCreated: 1710244772
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Drawers/BaseClassPickerDrawer.cs
uploadId: 939664
@@ -0,0 +1,15 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[CustomPropertyDrawer(typeof(MapProcessorClass), useForChildren: true)]
public class MapProcessorClassDrawer : BaseClassPickerDrawer<MapProcessorBase>
{
protected override string ClassNamePropertyName => "m_MapClassName";
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 85a89c98c61044e08811ba3fb0fbe957
timeCreated: 1707730234
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Drawers/MapProcessorClassDrawer.cs
uploadId: 939664
@@ -0,0 +1,15 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
[CustomPropertyDrawer(typeof(WorldspawnScript), useForChildren: true)]
public class WorldspawnScriptDrawer : BaseClassPickerDrawer<Worldspawn>
{
protected override string ClassNamePropertyName => "m_ScriptName";
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 513c5345701d4a0894054ae3c25184fb
timeCreated: 1710152511
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Drawers/WorldspawnScriptDrawer.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d4361d4c49a24bea8a532781a1d0119f
timeCreated: 1722851165
@@ -0,0 +1,139 @@
//
// 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 System.IO;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[CustomEditor(typeof(WadFileAssetImporter))]
public class WadAssetImporterEditor : UnityEditor.Editor
{
private Vector2 m_ScrollPos = Vector2.zero;
private bool m_Running = false;
public override void OnInspectorGUI()
{
if (target is not WadFileAssetImporter wadAssetImporter)
return;
WadFileAsset wadAsset = AssetDatabase.LoadAssetAtPath<WadFileAsset>(wadAssetImporter.assetPath);
GUILayout.Label($"{wadAsset.MaterialNames.Length} material(s):");
GUI.color = new Color(0.8f, 0.8f, 0.8f);
{
m_ScrollPos = GUILayout.BeginScrollView(m_ScrollPos, GUILayout.Height(100f));
{
foreach (string material in wadAsset.MaterialNames)
{
GUILayout.BeginHorizontal();
{
GUIContent materialIcon = EditorGUIUtility.IconContent("Material Icon");
GUILayout.Label(materialIcon, GUILayout.Width(16f), GUILayout.Height(16f));
GUILayout.Label(material);
}
GUILayout.EndHorizontal();
}
}
GUILayout.EndScrollView();
}
GUI.color = Color.white;
GUI.enabled = !m_Running;
if (GUILayout.Button("Extract Wad Materials"))
{
m_Running = true;
Extract(wadAssetImporter.assetPath);
m_Running = false;
}
}
public static void Extract(string wadFilePath)
{
WadParser parser = new(wadFilePath);
WadFile wadFile = parser.Parse();
// Create folder
string folderPath = Path.ChangeExtension(wadFilePath, null);
string texturesPath = Path.Combine(folderPath, "Textures");
string materialsPath = Path.Combine(folderPath, "Materials");
DirectoryUtil.EmptyAndCreateDirectory(texturesPath);
DirectoryUtil.EmptyAndCreateDirectory(materialsPath);
// Step 1: write textures
try
{
AssetDatabase.StartAssetEditing();
EditorUtility.DisplayProgressBar("Extracting Wad Textures", "One mo..", 0f);
for (int entryIdx = 0; entryIdx < wadFile.Entries.Count; entryIdx++)
{
WadEntry entry = wadFile.Entries[entryIdx];
float progress = (float)entryIdx / wadFile.Entries.Count;
EditorUtility.DisplayProgressBar("Extracting Wad Textures", $"{entry.Name} ({entryIdx + 1} of {wadFile.Entries.Count}) - {progress * 100:F0}%", progress);
string texturePath = Path.Combine(texturesPath, $"T_{entry.Name}.png");
parser.WriteTextureToFile(entry, texturePath);
}
}
finally
{
EditorUtility.ClearProgressBar();
AssetDatabase.StopAssetEditing();
}
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
EditorApplication.delayCall += () => CreateMaterials(materialsPath, wadFilePath, wadFile);
}
private static void CreateMaterials(string materialFolderPath, string wadFilePath, WadFile wadFile)
{
List<Material> createdMaterials = new(wadFile.Entries.Count);
// Step 2: Create materials
try
{
AssetDatabase.StartAssetEditing();
EditorUtility.DisplayProgressBar("Creating Materials", "One mo..", 0f);
string texturesPath = Path.Combine(Path.GetDirectoryName(materialFolderPath), "Textures");
for (int entryIdx = 0; entryIdx < wadFile.Entries.Count; entryIdx++)
{
WadEntry entry = wadFile.Entries[entryIdx];
float progress = (float)entryIdx / wadFile.Entries.Count;
EditorUtility.DisplayProgressBar("Creating Materials", $"{entry.Name} ({entryIdx + 1} of {wadFile.Entries.Count}) - {progress * 100:F0}%", progress);
string texturePath = Path.Combine(texturesPath, $"T_{entry.Name}.png");
string materialPath = Path.Combine(materialFolderPath, $"{entry.Name}.mat");
Material mat = TrembleMapImport.CreateEditorMaterial(texturePath, smoothness: 0.1f, metallic: 0f);
AssetDatabase.CreateAsset(mat, materialPath);
createdMaterials.Add(mat);
}
}
finally
{
EditorUtility.ClearProgressBar();
AssetDatabase.StopAssetEditing();
}
// Add to material group, if project is using them
if (TrembleSyncSettings.Get().MaterialGroups.Length > 0)
{
string wadName = Path.GetFileNameWithoutExtension(wadFilePath);
MaterialGroupUtil.MoveMaterialsToGroup(createdMaterials.ToArray(), wadName, createIfNotExist: true);
}
TrembleEditorAPI.SyncToTrenchBroom();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: eaf1a772c03e4686be2389348cf42bf1
timeCreated: 1722851222
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Editors/WadAssetImporterEditor.cs
uploadId: 939664
@@ -0,0 +1,141 @@
//
// 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 UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[InitializeOnLoad]
public static class MapHierarchyIcons
{
static MapHierarchyIcons()
{
TrembleEditorUtility.RegisterOnHierarchyItem(OnHierarchyItem);
}
public static void ResetModifiedTimes() => s_CachedModifiedTimes.Clear();
private static readonly Dictionary<GameObject, ulong> s_CachedModifiedTimes = new();
private static void OnHierarchyItem(GameObject go, Rect selectionRect)
{
Rect r = new(selectionRect);
r.x -= 28;
r.width = 20;
bool areDependenciesUpToDate = TrembleSyncSettings.Get().AutomaticallyReimportWhenDependencyChanges;
if (go.TryGetComponent(out MapDocument thisMap))
{
if (!areDependenciesUpToDate)
{
ulong maxTimestamp = 0ul;
List<string> outOfDate = new(512);
for (int childIdx = 0; childIdx < thisMap.transform.childCount; childIdx++)
{
Transform child = thisMap.transform.GetChild(childIdx);
if (!IsGameObjectOutOfDate(thisMap, child.gameObject, out ulong time))
continue;
outOfDate.Add(child.name);
maxTimestamp = Math.Max(time, maxTimestamp);
}
if (outOfDate.Count > 0)
{
const string PREFIX = "\n * ";
string oodPrefabs = String.Join(PREFIX, outOfDate);
GUI.Label(r, MakeWarningIcon($"Map may require reimport ({FormatTimespanSince(maxTimestamp)} out of date)! Possible out of date entities are:{PREFIX}{oodPrefabs}"));
}
}
// Edit button
Rect buttonRect = new(selectionRect);
buttonRect.width = 62;
buttonRect.x = selectionRect.width;
if (GUI.Button(buttonRect, "edit map", EditorStyles.miniButton))
{
string mapPath = Path.GetFullPath(AssetDatabase.GUIDToAssetPath(thisMap.OriginalAssetGuid));
TrenchBroomUtil.OpenWithTrenchBroom(mapPath);
}
}
else if (!areDependenciesUpToDate)
{
MapDocument parentMap = go.GetComponentInParent<MapDocument>();
if (!parentMap)
return;
if (!IsGameObjectOutOfDate(parentMap, go, out ulong timestamp))
return;
string tooltip = $"This entity may be using an older version of its prefab, from {FormatTimespanSince(timestamp)} ago - reimport the map!";
GUI.Label(r, MakeWarningIcon(tooltip));
}
}
private static GUIContent MakeWarningIcon(string tooltip) => new(EditorGUIUtility.IconContent("console.warnicon.sml").image, tooltip);
private static bool IsGameObjectOutOfDate(MapDocument map, GameObject objectInstance, out ulong timestamp)
{
foreach (Transform child in objectInstance.transform)
{
if (IsGameObjectOutOfDate(map, child.gameObject, out timestamp))
return true;
}
GameObject prefab = objectInstance;
while (prefab)
{
prefab = PrefabUtility.GetCorrespondingObjectFromSource(prefab);
if (!prefab)
{
timestamp = 0ul;
return false;
}
ulong mapTime = map.MapImportedTime;
if (!s_CachedModifiedTimes.TryGetValue(prefab, out ulong time))
{
time = ModTimeDB.CalcModelModifiedTime(prefab);
s_CachedModifiedTimes[prefab] = time;
}
if (mapTime < time)
{
timestamp = time;
return true;
}
}
timestamp = 0ul;
return false;
}
private static string FormatTimespanSince(ulong timestamp)
{
DateTime fileTime = new DateTime((long)timestamp).ToLocalTime();
DateTime now = DateTime.Now;
TimeSpan span = now - fileTime;
if (span.TotalDays >= 1.0)
return $"{(int)span.TotalDays} day(s)";
if (span.TotalHours >= 1.0)
return $"{(int)span.TotalHours} hour(s)";
if (span.TotalMinutes >= 1.0)
return $"{(int)span.TotalMinutes} min(s)";
return "mere moments";
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 986da32076bc407e9d805d1b0de7da27
timeCreated: 1713948862
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/MapHierarchyIcons.cs
uploadId: 939664
@@ -0,0 +1,642 @@
//
// 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 UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public class MapRepairWindow : EditorWindow
{
// -----------------------------------------------------------------------------------------------------------------------------
// Menu Items
// -----------------------------------------------------------------------------------------------------------------------------
[MenuItem("Tools/Tremble/Repair Map...")]
public static void OpenMapRepair() => OpenMapRepairImpl();
public static void OpenMapRepair(string mapPath) => OpenMapRepairImpl(mapPath);
[MenuItem("Assets/Repair Map")]
private static void RepairMap()
{
string assetPath = AssetDatabase.GetAssetPath(Selection.activeObject);
OpenMapRepairImpl(assetPath);
}
[MenuItem("Assets/Repair Map", true)]
private static bool RepairMap_Validation()
{
string assetPath = AssetDatabase.GetAssetPath(Selection.activeObject);
return AssetDatabaseUtil.GetAllTrembleMapPaths().Contains(assetPath);
}
// -----------------------------------------------------------------------------------------------------------------------------
// Utility
// -----------------------------------------------------------------------------------------------------------------------------
public static string GetGameNameFromMap(string mapPath)
{
Dictionary<string, string> metadata = MapParser.ParseMetadataOnly(mapPath);
return metadata.GetValueOrDefault("Game");
}
public static bool IsMapForeign(string mapPath, out string gameName)
{
gameName = GetGameNameFromMap(mapPath);
if (gameName == null)
return true;
return !gameName.EqualsInvariant(TrembleConsts.GAME_NAME, caseSensitive: true) &&
!gameName.EqualsInvariant(TrembleConsts.ASSET_STORE_GAME_NAME, caseSensitive: true);
}
private static void OpenMapRepairImpl(string mapPath = null)
{
MapRepairWindow window = GetWindow<MapRepairWindow>(true, "Map Repair", true);
window.m_MapPath = mapPath;
Vector2 size = new(520f, 640f);
window.minSize = size;
window.position = new(EditorGUIUtility.GetMainWindowPosition().center - size / 2f, size);
window.Show();
}
// -----------------------------------------------------------------------------------------------------------------------------
// Consts
// -----------------------------------------------------------------------------------------------------------------------------
record ClassData(string MappedClassname, FgdClass GeneratedClass);
private const float PADDING_SIZE = 10f;
private GUIStyle m_TitleStyle;
// -----------------------------------------------------------------------------------------------------------------------------
// State
// -----------------------------------------------------------------------------------------------------------------------------
private readonly List<string> m_AvailableMapPaths = new();
private readonly List<string> m_AvailableTrembleEntityClassnames = new();
private readonly List<string> m_AvailableTextureNames = new();
private readonly Dictionary<string, ClassData> m_MapClassToData = new();
private readonly Dictionary<string, string> m_MapTextureNameToMappedName = new();
private TrembleSyncSettings m_SyncSettings;
private MapTypeLookup m_MapTypeLookup;
private PrefabNameLookup m_PrefabNameLookup;
private MaterialNameLookup m_MaterialNameLookup;
private int m_EditingMapIdx;
private string m_MapPath;
private Map m_EditingMap;
private bool m_ClassSettingsFoldOutOpen;
private string m_OutputPath= "Code/Entities";
private string m_GeneratedNamespace = "TrembleEntities";
private readonly string[] m_Tabs = { "Textures/Materials", "Entities" };
private int m_SelectedTab;
private Vector2 m_ScrollPosition = Vector2.zero;
private void OnEnable()
{
m_SyncSettings = TrembleSyncSettings.Get();
m_MapTypeLookup = new(m_SyncSettings);
m_PrefabNameLookup = new(m_SyncSettings);
m_MaterialNameLookup = new(m_SyncSettings);
m_AvailableTrembleEntityClassnames.Clear();
foreach (Type entityType in m_MapTypeLookup.AllTypes)
{
if (!m_MapTypeLookup.TryGetMapNameFromClass(entityType, out string mapName))
continue;
m_AvailableTrembleEntityClassnames.Add(mapName);
}
foreach (string prefabPath in m_PrefabNameLookup.AllPrefabPaths)
{
if (!m_PrefabNameLookup.TryGetMapNameFromPrefabPath(prefabPath, out string prefabName))
continue;
m_AvailableTrembleEntityClassnames.Add(prefabName);
}
m_AvailableTextureNames.Clear();
foreach (MaterialGroup matGroup in m_SyncSettings.GetMaterialGroupsOrDefault())
{
foreach (Material material in matGroup.Materials)
{
string matPath = AssetDatabase.GetAssetPath(material);
if (matPath.IsNullOrEmpty() || !m_MaterialNameLookup.TryGetMapNameFromMaterialPath(matPath, out string mapName))
continue;
m_AvailableTextureNames.Add(mapName);
}
}
m_AvailableTrembleEntityClassnames.Sort();
m_AvailableTrembleEntityClassnames.Insert(0, "(generate new entity class)");
m_AvailableTextureNames.Sort();
m_AvailableTextureNames.Insert(0, "(leave as is)");
m_AvailableMapPaths.Add("(select a map)");
m_AvailableMapPaths.AddRange(AssetDatabaseUtil.GetAllTrembleMapPaths());
if (m_EditingMapIdx != 0)
{
Init(m_EditingMapIdx); // Re-init
}
}
private void Init(int mapIndex)
{
if (mapIndex == 0)
{
m_EditingMap = null;
return;
}
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
m_EditingMap = MapParser.Parse(m_AvailableMapPaths[mapIndex]);
m_EditingMapIdx = mapIndex;
// Format title
string mapFilename = Path.GetFileName(m_AvailableMapPaths[mapIndex]);
titleContent = new($"Repairing '{mapFilename}' - {GetGameOriginInfo()}");
// Parse!
m_MapClassToData.Clear();
m_MapTextureNameToMappedName.Clear();
foreach (MapEntity entity in m_EditingMap.Entities)
{
bool isBrushEntity = entity.Brushes.Count > 0;
if (!entity.Classname.EqualsInvariant("worldspawn", caseSensitive: true)
&& !entity.Classname.EqualsInvariant("func_group", caseSensitive: true))
{
// Find or create class
if (!m_MapClassToData.TryGetValue(entity.Classname, out ClassData classData))
{
FgdClass newClass = new(isBrushEntity ? FgdClassType.Brush : FgdClassType.Point, entity.Classname, $"Class {entity.Classname}");
newClass.AddBaseClass(FgdConsts.CLASS_MAP_BASE);
if (!isBrushEntity)
{
newClass.AddBaseClass(FgdConsts.CLASS_MAP_POINT_BASE);
}
string existingClassname = entity.Classname;
if (!m_MapTypeLookup.TryGetClassFromMapName(entity.Classname, out _) &&
!m_PrefabNameLookup.TryGetPrefabPathFromMapName(entity.Classname, out _))
{
existingClassname = null;
}
classData = new(GeneratedClass: newClass, MappedClassname: existingClassname);
m_MapClassToData[entity.Classname] = classData;
}
foreach ((string key, string value) in entity.Entries)
{
if (key.EqualsInvariant("classname"))
continue;
if (classData.GeneratedClass.HasField(key))
continue;
classData.GeneratedClass.TryAddInferredField(key, value);
}
}
foreach (MapBrush brush in entity.Brushes)
{
foreach (MapBrushFace face in brush.Faces)
{
if (m_MapTextureNameToMappedName.ContainsKey(face.TextureName))
continue;
if (face.TextureName.EqualsInvariant(TBConsts.EMPTY_TEXTURE, caseSensitive: true))
continue;
// Try to find material
string group = null;
string textureName = face.TextureName;
int slashIdx = textureName.IndexOf('/');
if (slashIdx != -1)
{
group = face.TextureName.Substring(0, slashIdx);
textureName = face.TextureName.Substring(slashIdx + 1);
if (group.EqualsInvariant(TBConsts.SPECIAL_TEXTURE, caseSensitive: true))
continue;
}
MaterialGroup materialGroup = MaterialGroup.CreateDefault();
if (!group.IsNullOrEmpty())
{
MaterialGroup[] mg = syncSettings.MaterialGroups
.Where(mg => mg.Name.EqualsInvariant(group))
.ToArray();
if (mg.Length > 0)
{
materialGroup = mg[0];
}
}
string matchingMaterialPath;
Material matchingMaterial = materialGroup.Materials.FirstOrDefault(m => m && m.name.ContainsInvariant(textureName));
if (matchingMaterial)
{
matchingMaterialPath = AssetDatabase.GetAssetPath(matchingMaterial);
}
else
{
matchingMaterialPath = AssetDatabase.FindAssets($"t:material {textureName}")
.Select(AssetDatabase.GUIDToAssetPath)
.FirstOrDefault();
}
if (!matchingMaterialPath.IsNullOrEmpty() && m_MaterialNameLookup.TryGetMapNameFromMaterialPath(matchingMaterialPath, out string mapName))
{
m_MapTextureNameToMappedName.Add(face.TextureName, mapName);
}
else
{
m_MapTextureNameToMappedName.Add(face.TextureName, null);
}
}
}
}
}
private void OnGUI()
{
if (m_EditingMapIdx == 0)
{
m_TitleStyle ??= new(EditorStyles.boldLabel)
{
fontSize = EditorStyles.boldLabel.fontSize + 5
};
if (m_MapPath.IsNullOrEmpty())
{
GUILayout.Label("Select a map to repair:", m_TitleStyle);
int currentMapIdx = EditorGUILayout.Popup(m_EditingMapIdx, m_AvailableMapPaths.ToArray());
Init(currentMapIdx);
GUILayout.Label("Choose a map above to repair it. Repairing a map allows you to fix up broken"
+ " materials and entity references.\n\nIt can also be used to try to import \"foreign\" maps from"
+ " other games, although using it in this way is experimental - particularly you may find that"
+ " texture UVs are completely incorrect for foreign maps.\n\nTremble will however attempt to"
+ " generate new MonoBehaviour classes for any missing entities - but these are best-guesses"
+ " based on the map file and will need manual editing.", EditorStyles.helpBox);
}
else
{
int mapIdx = Math.Max(0, m_AvailableMapPaths.IndexOf(m_MapPath));
Init(mapIdx);
}
}
else
{
m_SelectedTab = GUILayout.Toolbar(m_SelectedTab, m_Tabs, "LargeButton");
using (EditorGUILayout.ScrollViewScope scroll = new(m_ScrollPosition, GUILayout.ExpandHeight(true)))
{
EditorGUILayoutUtil.Pad(PADDING_SIZE, () =>
{
switch (m_SelectedTab)
{
case 0: OnTexturesGUI(); break;
case 1: OnClassesGUI(); break;
}
});
m_ScrollPosition = scroll.scrollPosition;
}
if (GUILayout.Button("Repair and Resave!", GUILayout.Height(50f)))
{
RepairAndSave();
}
}
}
private void OnTexturesGUI()
{
bool hasMissingTextures = m_MapTextureNameToMappedName.Any(kv => kv.Value == null);
if (hasMissingTextures)
{
GUI.color = Color.yellow;
{
GUILayout.BeginVertical(EditorStyles.helpBox);
{
GUILayout.Label("If you have a WAD file for this game, you should first import that to generate Materials for the map's textures.", EditorStyles.wordWrappedLabel);
if (GUILayout.Button("Import WAD"))
{
string wadFilePath = EditorUtility.OpenFilePanel("Select WAD file", "Assets", "wad");
if (wadFilePath.IsNullOrEmpty())
return;
string mapBasePath = Path.GetDirectoryName(m_AvailableMapPaths[m_EditingMapIdx]);
File.Copy(wadFilePath, Path.Combine(mapBasePath, Path.GetFileName(wadFilePath)));
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
EditorApplication.delayCall += () =>
{
EditorUtility.DisplayDialog("WAD Imported!", "The WAD file was imported successfully!", "Okay");
OnEnable();
};
}
}
GUILayout.EndVertical();
GUILayout.Space(20f);
}
GUI.color = Color.white;
}
GUILayoutOption fixedHalfWidth = GUILayout.Width(position.width / 2f - PADDING_SIZE * 2f);
GUILayoutOption smallerHalfWidth = GUILayout.Width(position.width / 2f - PADDING_SIZE * 2f - 25f);
GUILayout.BeginHorizontal();
{
GUILayout.Label("Texture from Map:", EditorStyles.boldLabel, fixedHalfWidth);
GUILayout.Label("Unity Material:", EditorStyles.boldLabel, fixedHalfWidth);
}
GUILayout.EndHorizontal();
GUILayout.Space(5f);
foreach (string textureName in m_MapTextureNameToMappedName.Keys.OrderBy(mt => mt))
{
if (textureName.EqualsInvariant(TBConsts.CLIP_TEXTURE) ||
textureName.EqualsInvariant(TBConsts.SKIP_TEXTURE) ||
textureName.EqualsInvariant(TBConsts.TRIGGER_TEXTURE) ||
textureName.StartsWithInvariant(TBConsts.SPECIAL_TEXTURE))
continue;
GUILayout.BeginHorizontal();
{
GUILayout.Label(textureName, fixedHalfWidth);
Color colour = Color.white;
int prevSelectedIdx = Math.Max(0, m_AvailableTextureNames.IndexOf(m_MapTextureNameToMappedName[textureName]));
if (prevSelectedIdx == 0)
{
colour = Color.red;
}
else if (!m_MapTextureNameToMappedName[textureName].EqualsInvariant(textureName, caseSensitive: true))
{
colour = Color.yellow;
}
GUI.color = colour;
int selectedIdx = EditorGUILayout.Popup(prevSelectedIdx, m_AvailableTextureNames.ToArray(), smallerHalfWidth);
GUI.color = Color.white;
if (prevSelectedIdx != selectedIdx)
{
m_MapTextureNameToMappedName[textureName] = m_AvailableTextureNames[selectedIdx];
}
if (GUILayout.Button(new GUIContent("!", "Copy to all missing"), GUILayout.Width(20f), GUILayout.Height(20f)))
{
foreach (string mapTextureName in m_MapTextureNameToMappedName.Keys.ToArray())
{
if (m_MapTextureNameToMappedName[mapTextureName].IsNullOrEmpty())
{
m_MapTextureNameToMappedName[mapTextureName] = m_AvailableTextureNames[selectedIdx];
}
}
GUILayout.EndHorizontal();
return;
}
}
GUILayout.EndHorizontal();
}
}
private void OnClassesGUI()
{
bool hasMissingClasses = m_MapClassToData.Any(kv => !kv.Key.EqualsInvariant(kv.Value.MappedClassname));
if (hasMissingClasses)
{
GUI.color = Color.yellow;
{
GUILayout.BeginVertical(EditorStyles.helpBox);
{
GUILayout.Label("If you have an FGD file for this game, you should first import that to generate MonoBehaviours for the game's entities. If you do this, the classes generated from Map Repair will have much more accurate names and descriptions!", EditorStyles.wordWrappedLabel);
if (GUILayout.Button("Import FGD"))
{
string fgdFilePath = EditorUtility.OpenFilePanel("Select FGD file", "Assets", "fgd");
if (fgdFilePath.IsNullOrEmpty())
return;
string mapBasePath = Path.GetDirectoryName(m_AvailableMapPaths[m_EditingMapIdx]);
File.Copy(fgdFilePath, Path.Combine(mapBasePath, Path.GetFileName(fgdFilePath)));
EditorCompileUtil.CompileThen(typeof(MapRepairWindow), nameof(OnPostCompileAfterFGDImport));
}
}
GUILayout.EndVertical();
GUILayout.Space(20f);
}
GUI.color = Color.white;
}
GUILayoutOption fixedHalfWidth = GUILayout.Width(position.width / 2f - PADDING_SIZE * 2f - 10f);
GUILayout.BeginHorizontal();
{
GUILayout.Label("Entity Class from Map:", EditorStyles.boldLabel, fixedHalfWidth);
GUILayout.Label("MonoBehaviour:", EditorStyles.boldLabel, fixedHalfWidth);
}
GUILayout.EndHorizontal();
GUILayout.Space(5f);
foreach (string className in m_MapClassToData.Keys.OrderBy(cn => cn))
{
GUILayout.BeginHorizontal();
{
GUILayout.Label(className, fixedHalfWidth);
Color colour = Color.white;
int prevSelectedIdx = Math.Max(0, m_AvailableTrembleEntityClassnames.IndexOf(m_MapClassToData[className].MappedClassname));
if (prevSelectedIdx == 0)
{
colour = Color.green;
}
else if (!m_MapClassToData[className].MappedClassname.EqualsInvariant(className, caseSensitive: true))
{
colour = Color.yellow;
}
GUI.color = colour;
int selectedIdx = EditorGUILayout.Popup(prevSelectedIdx, m_AvailableTrembleEntityClassnames.ToArray(), fixedHalfWidth);
GUI.color = Color.white;
if (prevSelectedIdx != selectedIdx)
{
m_MapClassToData[className] = m_MapClassToData[className] with
{
MappedClassname = m_AvailableTrembleEntityClassnames[selectedIdx]
};
}
}
GUILayout.EndHorizontal();
}
EditorGUILayout.Space(10f);
m_ClassSettingsFoldOutOpen = EditorGUILayout.Foldout(m_ClassSettingsFoldOutOpen, "Class Generation Settings", true);
if (m_ClassSettingsFoldOutOpen)
{
EditorGUI.indentLevel++;
m_OutputPath = EditorGUILayout.TextField("Save To", m_OutputPath);
GUILayout.Label("There is the folder where Tremble will store any new classes for the entities found in the map. This path is relative to your Assets folder. If it doesn't exist, it'll be created!", EditorStyles.helpBox);
m_GeneratedNamespace = EditorGUILayout.TextField("C# Namespace", m_GeneratedNamespace);
GUILayout.Label("This namespace is only applied to newly-generated entity MonoBehaviours.", EditorStyles.helpBox);
EditorGUI.indentLevel--;
}
}
private string GetGameOriginInfo()
{
string gameName = m_EditingMap.Metadata.GetValueOrDefault("Game");
if (gameName.IsNullOrEmpty())
return "map from Quake(?)";
if (gameName.EqualsInvariant(TrembleConsts.GAME_NAME, caseSensitive: true))
return "map from this game";
if (gameName.EqualsInvariant(TrembleConsts.ASSET_STORE_GAME_NAME, caseSensitive: true))
return "map from Tremble sample";
if (gameName.ContainsInvariant("unity:"))
return "map from another Tremble game";
return $"map from {gameName}";
}
private void RepairAndSave()
{
if (m_EditingMap == null)
return;
string mapFilename = Path.GetFileName(m_AvailableMapPaths[m_EditingMapIdx]);
if (TrenchBroomUtil.IsTrenchBroomRunning)
{
EditorUtility.DisplayDialog(
title: "Uh oh - TrenchBroom appears to be running!",
message: $"Hey - if you have {mapFilename} open in TrenchBroom right now, you should "
+ "close it before repairing the map",
"Closed, continue repairing!");
}
bool generatedAnyNewClasses = false;
// Write out classes for any missing entities
foreach ((string _, ClassData classData) in m_MapClassToData)
{
if (!classData.MappedClassname.IsNullOrEmpty())
continue;
string rootFolder = Path.Combine("Assets", m_OutputPath);
classData.GeneratedClass.Name.Split('_', out string category, out string classname);
// Use category as folder
if (!category.IsNullOrEmpty())
{
rootFolder = Path.Combine(rootFolder, category.ToNamingConvention(NamingConvention.UpperCamelCase));
}
string fileName = classname.ToNamingConvention(NamingConvention.UpperCamelCase) + ".cs";
string filePath = Path.Combine(rootFolder, fileName);
DirectoryUtil.CreateAllDirectories(filePath);
classData.GeneratedClass.WriteCSharp(filePath, namespaceName: m_GeneratedNamespace);
generatedAnyNewClasses = true;
}
// Pass 1: remap entity classes & strip 'wad' from worldspawn
foreach (MapEntity entity in m_EditingMap.Entities)
{
// Remove Wad files
if (entity.Classname.EqualsInvariant("worldspawn", caseSensitive: true) && entity.Entries.ContainsKey("wad"))
{
entity.Entries.Remove("wad");
}
if (!m_MapClassToData.TryGetValue(entity.Classname, out ClassData classData))
continue;
// Not remapped?
if (classData.MappedClassname.IsNullOrEmpty())
continue;
entity.Entries["classname"] = classData.MappedClassname;
}
// Pass 2: remap textures
foreach (MapEntity entity in m_EditingMap.Entities)
{
foreach (MapBrush brush in entity.Brushes)
{
foreach (MapBrushFace face in brush.Faces)
{
// Ignore if not mapped, or mapped texture is something like "(leave as is)"
if (m_MapTextureNameToMappedName.TryGetValue(face.TextureName, out string mappedTexture) &&
!mappedTexture.IsNullOrEmpty() && !mappedTexture.ContainsInvariant("("))
{
face.TextureName = mappedTexture;
}
}
}
}
// Rebuild new code
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
// Import fixed map
string mapPath = m_AvailableMapPaths[m_EditingMapIdx];
m_EditingMap.WriteTo(mapPath);
AssetDatabase.ImportAsset(mapPath);
// Warn about re-importing
if (generatedAnyNewClasses)
{
EditorUtility.DisplayDialog("New Classes Generated", "Tremble has generated new MonoBehaviours for your map entities. You might need to manually Reimport the map file once in order for these to be picked up.", "Sure thing!");
}
Close();
}
private static void OnPostCompileAfterFGDImport()
{
EditorUtility.DisplayDialog("FGD Imported!", "The FGD file was imported successfully!", "Okay");
MapRepairWindow mrw = GetWindow<MapRepairWindow>();
mrw.OnEnable();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 75858ce0fbcd4493a482f6081646f26a
timeCreated: 1712045460
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/MapRepairWindow.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3b73ae349f8c4f7dbebc23a21f5ab8e2
timeCreated: 1710848964
@@ -0,0 +1,198 @@
//
// 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.Linq;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
[InitializeOnLoad]
public class MaterialInspectorGUI
{
static MaterialInspectorGUI()
{
UnityEditor.Editor.finishedDefaultHeaderGUI -= OnPostHeaderGUI;
UnityEditor.Editor.finishedDefaultHeaderGUI += OnPostHeaderGUI;
}
private static bool s_IsFoldoutOpen;
static void OnPostHeaderGUI(UnityEditor.Editor editor)
{
if (editor is not MaterialEditor)
return;
GUIContent content = new("Material Settings for Tremble");
Rect rect = GUILayoutUtility.GetRect(content, EditorStyles.foldoutHeader);
rect.x += 20f;
s_IsFoldoutOpen = EditorGUI.BeginFoldoutHeaderGroup(rect, s_IsFoldoutOpen, content, menuIcon: EditorStyles.foldoutHeaderIcon);
if (s_IsFoldoutOpen)
{
EditorGUI.indentLevel++;
TrembleSyncSettings trembleSyncSettings = TrembleSyncSettings.Get();
// Get selected materials and their groups
Material[] selectedMaterials = editor.targets.Cast<Material>().ToArray();
HashSet<string> uniqueGroups = new(selectedMaterials.Select(m => GetMaterialGroupName(trembleSyncSettings, m)));
// Gather list of material groups
List<MaterialGroup> groups = trembleSyncSettings.MaterialGroups.ToList();
List<string> titles = new() { "(none)" };
titles.AddRange(groups.Select(g => g.Name));
titles.Add(" + Create New Group");
EditorGUI.showMixedValue = uniqueGroups.Count > 1;
{
int selectedIdx = uniqueGroups.Count > 1 ? -1 : titles.IndexOf(uniqueGroups.First());
int newSelectedIdx = EditorGUILayout.Popup("Material Group", selectedIdx, titles.ToArray(), EditorStyles.miniPullDown);
if (selectedIdx != newSelectedIdx)
{
if (newSelectedIdx > groups.Count)
{
Rect targetRect = new()
{
x = Event.current.mousePosition.x,
y = Event.current.mousePosition.y,
width = 400f,
height = 50f
};
//Add new?
NewMaterialGroupWindow newGroup = EditorWindow.GetWindow<NewMaterialGroupWindow>(true, "New Material Group Name", true);
newGroup.Show(targetRect, groups.ToArray(), (groupName) => MaterialGroupUtil.MoveMaterialsToGroup(selectedMaterials, groupName, createIfNotExist: true));
return;
}
// Shift from 1-based to 0-based, with -1 meaning none
int index = newSelectedIdx - 1;
if (index == -1)
{
MaterialGroupUtil.RemoveMaterialsFromGroups(selectedMaterials);
}
else
{
string groupName = groups[index].Name;
MaterialGroupUtil.MoveMaterialsToGroup(selectedMaterials, groupName);
}
}
}
EditorGUI.showMixedValue = false;
MaterialRenderData[] datas = selectedMaterials.Select(trembleSyncSettings.GetMaterialRenderData).ToArray();
MaterialRenderData data = trembleSyncSettings.GetMaterialRenderData(selectedMaterials[0]);
EditorGUI.showMixedValue = datas.Select(x => x.ExportMode).Distinct().Count() > 1;
{
int mode = (int)(data?.ExportMode ?? MaterialImportMode.FixedSize);
EditorGUI.BeginChangeCheck();
int newMode = EditorGUILayout.Popup("Render Mode", mode, Enum.GetNames(typeof(MaterialImportMode)), EditorStyles.miniPullDown);
if (EditorGUI.EndChangeCheck())
{
foreach (Material mat in selectedMaterials)
{
mat.SetMaterialImportMode((MaterialImportMode)newMode);
}
}
}
if (datas.Any(x => x.ExportMode == MaterialImportMode.Dynamic))
{
EditorGUILayout.BeginHorizontal();
{
EditorGUILayout.LabelField(new GUIContent
{
text = "Resolution",
tooltip = "Override this to force the texture to render at a certain resolution."
}, GUILayout.MaxWidth(82));
EditorGUI.BeginChangeCheck();
GUIStyle miniLabel = EditorStyles.centeredGreyMiniLabel;
miniLabel.alignment = TextAnchor.MiddleRight;
EditorGUILayout.LabelField("Override", EditorStyles.centeredGreyMiniLabel);
bool overrideRes = EditorGUILayout.Toggle(data?.IsResolutionOverridden ?? false, GUILayout.MinWidth(0));
if (EditorGUI.EndChangeCheck())
{
foreach (Material mat in selectedMaterials)
{
mat.SetMaterialResolutionOverridden(overrideRes);
}
}
string[] resolutionLabels = MaterialExportSizeUtils.Names;
if (data?.IsResolutionOverridden ?? false)
{
MaterialExportSize sizeX = data?.ResolutionX ?? MaterialExportSizeUtils.GetDefault();
MaterialExportSize sizeY = data?.ResolutionY ?? MaterialExportSizeUtils.GetDefault();
// X Resolution
EditorGUI.BeginChangeCheck();
sizeX = (MaterialExportSize)EditorGUILayout.Popup((int)sizeX, resolutionLabels, EditorStyles.miniPullDown);
if (EditorGUI.EndChangeCheck())
{
foreach (Material mat in selectedMaterials)
{
mat.SetMaterialResolutionX(sizeX);
}
}
GUI.contentColor = Color.gray;
GUILayout.Label(" x ");
GUI.contentColor = Color.white;
// Y Resolution
EditorGUI.BeginChangeCheck();
sizeY = (MaterialExportSize)EditorGUILayout.Popup((int)sizeY, resolutionLabels, EditorStyles.miniPullDown);
if (EditorGUI.EndChangeCheck())
{
foreach (Material mat in selectedMaterials)
{
mat.SetMaterialResolutionY(sizeY);
}
}
}
else
{
int[] widths = selectedMaterials.Select(m => m.mainTexture?.width ?? 0).Where(w => w > 0).Distinct().ToArray();
int[] heights = selectedMaterials.Select(m => m.mainTexture?.height ?? 0).Where(h => h > 0).Distinct().ToArray();
string widthStr = widths.Length > 0 ? widths[0].ToString() : "(multiple)";
string heightStr = heights.Length > 0 ? heights[0].ToString() : "(multiple)";
GUI.contentColor = Color.gray;
GUILayout.Label($"{widthStr} x {heightStr}");
GUI.contentColor = Color.white;
}
}
EditorGUILayout.EndHorizontal();
EditorGUI.indentLevel--;
}
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
private static string GetMaterialGroupName(TrembleSyncSettings trembleSyncSettings, Material material)
{
foreach (MaterialGroup group in trembleSyncSettings.MaterialGroups)
{
if (group.Materials.Contains(material))
return group.Name;
}
return "(none)";
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a398d54cc0ba4b90af4093ec13fa6bec
timeCreated: 1709455118
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Materials/MaterialInspectorGUI.cs
uploadId: 939664
@@ -0,0 +1,90 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
internal class NewMaterialGroupWindow : EditorWindow
{
private const string MAT_NAME_ID = "MatNameField";
private MaterialGroup[] m_ExistingGroups;
private Action<string> m_OnAdded;
private string m_Name;
internal void Show(Rect fromRect, MaterialGroup[] existingGroups, Action<string> onAdded)
{
position = fromRect;
m_ExistingGroups = existingGroups;
m_OnAdded = onAdded;
m_Name = "Group " + m_ExistingGroups.Length;
ShowModalUtility();
}
private void OnGUI()
{
GUI.SetNextControlName(MAT_NAME_ID);
m_Name = EditorGUILayout.TextField("Name", m_Name);
GUI.FocusControl(MAT_NAME_ID);
bool escaped = Event.current.keyCode == KeyCode.Escape;
bool entered = Event.current.keyCode == KeyCode.Return;
GUILayout.BeginHorizontal();
{
escaped |= GUILayout.Button("Cancel");
entered |= GUILayout.Button("Create");
}
GUILayout.EndHorizontal();
if (Event.current.type is EventType.Repaint or EventType.Layout)
return;
if (escaped)
{
Close();
}
else if (entered)
{
TrembleSyncSettings trembleSyncSettings = TrembleSyncSettings.Get();
if (trembleSyncSettings.MaterialGroups.Length == 0)
{
string text = "You are adding your first Material Group, which is a great idea to organise your materials. However, you will need to manually assign each material to a material group for it to be available in TrenchBroom. All good?";
int selection = EditorUtility.DisplayDialogComplex("Material Groups", text, "Understood, Continue", "Oh, No Thanks!", "Okay but add all materials into a group");
switch (selection)
{
case 0: //ok
{
break;
}
case 1: // cancel
{
return;
}
case 2: // ok but add
{
// Quickly add a "user" group with current mats, and then continue
MaterialGroup defaultGroup = MaterialGroup.CreateDefault();
MaterialGroupUtil.MoveMaterialsToGroup(defaultGroup.Materials, defaultGroup.Name, createIfNotExist: true);
break;
}
}
}
// Return new name back
m_OnAdded(m_Name);
Close();
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: be384b78cb984abd86673e4aca0d4ea2
timeCreated: 1709543387
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Materials/NewMaterialGroupWindow.cs
uploadId: 939664
@@ -0,0 +1,142 @@
//
// 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 UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public class Q3Map2OutputWindow : EditorWindow
{
private IList<string> m_Lines;
private Vector2 m_Scroll;
private bool m_ShowErrors = true;
private bool m_ShowWarnings = true;
private bool m_ShowInfo = false;
private string m_Filter = "";
private bool m_HasErrors;
private bool m_HasWarnings;
public static void Open(IList<string> output, bool showAllInfo = false)
{
Q3Map2OutputWindow window = GetWindow<Q3Map2OutputWindow>(true, "Map Conversion Output", true);
Vector2 size = new(700f, 350f);
window.minSize = size;
window.position = new(EditorGUIUtility.GetMainWindowPosition().center - size / 2f, size);
window.m_Lines = output;
if (showAllInfo)
{
window.m_ShowInfo = true;
}
window.Show();
}
private bool IsWarning(string line) => line.ContainsInvariant("warning") && !line.ContainsInvariant("__TB_");
private bool IsError(string line) => line.ContainsInvariant("error") && !line.ContainsInvariant("__TB_");
private void OnGUI()
{
if (m_Lines == null)
{
Close();
return;
}
if (m_HasErrors)
{
GUI.color = Color.red;
GUILayout.Label("Q3Map2 produced error(s)!", EditorStyles.largeLabel);
GUI.color = Color.white;
}
else if (m_HasWarnings)
{
GUI.color = Color.yellow;
GUILayout.Label("Q3Map2 produced warning(s)!", EditorStyles.largeLabel);
GUI.color = Color.white;
}
else
{
GUILayout.Label("Q3Map2 Output", EditorStyles.largeLabel);
}
GUILayout.Space(10f);
GUILayout.Box(" ", GUILayout.ExpandWidth(true), GUILayout.Height(2f));
using (EditorGUILayout.ScrollViewScope scroll = new(m_Scroll))
{
foreach (string line in m_Lines)
{
if (m_Filter.Length > 0 && !line.ContainsInvariant(m_Filter))
continue;
if (IsError(line))
{
if (!m_ShowErrors)
continue;
GUI.color = Color.red;
m_HasErrors = true;
}
else if (IsWarning(line))
{
if (!m_ShowWarnings)
continue;
GUI.color = Color.yellow;
m_HasWarnings = true;
}
else
{
if (!m_ShowInfo)
continue;
GUI.color = new Color(0.8f, 0.8f, 0.8f);
}
GUILayout.Label(" " + line, EditorStyles.wordWrappedLabel);
}
m_Scroll = scroll.scrollPosition;
}
GUI.color = Color.white;
GUILayout.Box(" ", GUILayout.ExpandWidth(true), GUILayout.Height(2f));
using (EditorGUILayout.HorizontalScope _ = new())
{
GUILayout.Label("Show:");
GUI.color = Color.red;
m_ShowErrors = GUILayout.Toggle(m_ShowErrors, "Errors");
GUI.color = Color.yellow;
m_ShowWarnings = GUILayout.Toggle(m_ShowWarnings, "Warnings");
GUI.color = Color.white;
m_ShowInfo = GUILayout.Toggle(m_ShowInfo, "Info");
GUILayout.Label("\ud83d\udd0e", GUILayout.Width(40f));
m_Filter = GUILayout.TextArea(m_Filter, GUILayout.Width(150f));
GUI.enabled = m_Filter.Length > 0;
if (GUILayout.Button("x", GUILayout.Width(20f)))
{
m_Filter = "";
}
GUI.enabled = true;
}
GUILayout.Space(10f);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c6c93bcb039a346b586b77785874827f
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/Editor/EditorUI/Q3Map2OutputWindow.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3399abca45114db19b7cb0a3de6cd661
timeCreated: 1721666624
@@ -0,0 +1,126 @@
//
// 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 UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public static class TrembleProjectSettingsProvider
{
// -----------------------------------------------------------------------------------------------------------------------------
// Entry Points
// -----------------------------------------------------------------------------------------------------------------------------
[SettingsProvider] public static SettingsProvider ProvideAboutGUI()
=> ProvideTrembleTab(s => s.OnAboutGUI());
[SettingsProvider] public static SettingsProvider ProvideSettingsGUI()
=> ProvideTrembleTab(order: 1, "Basic Settings", s => s.OnSettingsGUI(isEmbedded: true));
[SettingsProvider] public static SettingsProvider ProvideNamingConventionsGUI()
=> ProvideTrembleTab(order: 2, "Naming Conventions", s => s.OnNamingConventionsGUI(isEmbedded: true));
[SettingsProvider] public static SettingsProvider ProvideMaterialsGUI()
=> ProvideTrembleTab(order: 3, "Materials", s => s.OnMaterialsGUI(isEmbedded: true));
[SettingsProvider] public static SettingsProvider ProvidePipelineGUI()
=> ProvideTrembleTab(order: 4, "Pipeline", s => s.OnPipelineGUI(isEmbedded: true));
[SettingsProvider] public static SettingsProvider ProvideAdvancedGUI()
=> ProvideTrembleTab(order: 5, "Advanced", s => GUILayout.Label("Here be dragons! Most users should not need to change these options. But, if you're brave - select a subcategory on the left...", EditorStyles.wordWrappedLabel), dontCaptureKeywords: true);
[SettingsProvider] public static SettingsProvider ProvideAdvancedGUI_Features()
=> ProvideTrembleTab(order: 5, "Advanced", subOrder: 0, "Features", s => s.OnAdvancedGUI_Features());
[SettingsProvider] public static SettingsProvider ProvideAdvancedGUI_Debugging()
=> ProvideTrembleTab(order: 5, "Advanced", subOrder: 1, "Debugging", s => s.OnAdvancedGUI_Debugging());
[SettingsProvider] public static SettingsProvider ProvideAdvancedGUI_SyncImport()
=> ProvideTrembleTab(order: 5, "Advanced", subOrder: 2, "Sync & Import", s => s.OnAdvancedGUI_SyncImport());
[SettingsProvider] public static SettingsProvider ProvideAdvancedGUI_Materials()
=> ProvideTrembleTab(order: 5, "Advanced", subOrder: 3, "Materials", s => s.OnAdvancedGUI_Materials());
[SettingsProvider] public static SettingsProvider ProvideAdvancedGUI_ImportScale()
=> ProvideTrembleTab(order: 5, "Advanced", subOrder: 4, "Import Scale", s => s.OnAdvancedGUI_ImportScale());
// -----------------------------------------------------------------------------------------------------------------------------
// State
// -----------------------------------------------------------------------------------------------------------------------------
private static TrembleSyncSettingsEditor s_SettingsEditor;
// -----------------------------------------------------------------------------------------------------------------------------
// Internals
// -----------------------------------------------------------------------------------------------------------------------------
private static SettingsProvider ProvideTrembleTab(Action<TrembleSyncSettingsEditor> onGui, bool dontCaptureKeywords = false)
=> ProvideTrembleTab(null, "Tremble", onGui, dontCaptureKeywords);
private static SettingsProvider ProvideTrembleTab(int order, string categoryName, Action<TrembleSyncSettingsEditor> onGui, bool dontCaptureKeywords = false)
=> ProvideTrembleTab($"{order} {categoryName}", categoryName, onGui, dontCaptureKeywords);
private static SettingsProvider ProvideTrembleTab(int order, string categoryName, int subOrder, string subCategoryName, Action<TrembleSyncSettingsEditor> onGui, bool dontCaptureKeywords = false)
=> ProvideTrembleTab($"{order} {categoryName}/{subOrder} {subCategoryName}", subCategoryName, onGui, dontCaptureKeywords);
private static SettingsProvider ProvideTrembleTab(string path, string label, Action<TrembleSyncSettingsEditor> onGui, bool dontCaptureKeywords = false)
{
// Create an editor (or used cached)
TrembleSyncSettings settings = TrembleSyncSettings.Get();
if (!s_SettingsEditor)
{
s_SettingsEditor = (TrembleSyncSettingsEditor)UnityEditor.Editor.CreateEditor(settings);
}
// Prepend root to path
string fullPath = "Project/Tremble";
if (path != null)
{
fullPath += $"/{path}";
}
// Capture keywords
List<string> keywords = new(64) { "Tremble", label };
if (!dontCaptureKeywords)
{
s_SettingsEditor.EnsureInit(onlyKeywordsCapture: true);
TrembleSettingsGUI.BeginKeywordCapture();
{
try
{
onGui(s_SettingsEditor);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
keywords.AddRange(TrembleSettingsGUI.EndKeywordCapture());
}
// Register path with order embedded :)
return new(path: fullPath, SettingsScope.Project)
{
label = label,
guiHandler = (searchContext) =>
{
s_SettingsEditor.EnsureInit();
EditorGUILayoutUtil.Pad(10f, () =>
{
try
{
onGui(s_SettingsEditor);
}
catch (Exception ex)
{
Debug.LogException(ex);
GUILayout.Label($"An error occurred rendering this page: {ex.Message}", EditorStyles.wordWrappedLabel);
}
});
s_SettingsEditor.ResetLabelWidth();
},
keywords = keywords
};
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 898680ff76b449889b5d52a2011c13e9
timeCreated: 1721384604
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Settings/TrembleProjectSettingsProvider.cs
uploadId: 939664
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: c36699011d4c4cbda4b3dd2392a60bf6
timeCreated: 1707738241
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Settings/TrembleSyncSettingsEditor.cs
uploadId: 939664
@@ -0,0 +1,226 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public class EditorTexture
{
public EditorTexture(string textureName, params byte[] data)
{
m_TextureName = textureName;
m_Data = data;
}
private readonly string m_TextureName;
private readonly byte[] m_Data;
public Texture2D Get()
{
// This sucks. On first import, we can't find the textures. So, to preserve the
// initial experience, we also supply the textures as byte arrays.
// If we can find the asset, use that. If not, create a texture on the fly from
// the byte data.
// Once the textures are available, the byte arrays also no longer work - hence the
// double data. Luckily they're only 20x20 PNGs so they're very small.
Texture2D packedTexture = TrembleAssetLoader.LoadAssetByName<Texture2D>(m_TextureName);
if (packedTexture)
{
return packedTexture;
}
Texture2D texture = new(TrembleIcons.ICON_SIZE, TrembleIcons.ICON_SIZE);
texture.LoadImage(m_Data, true);
return texture;
}
}
public static class TrembleIcons
{
internal const int ICON_SIZE = 20;
[MenuItem("Tools/Tremble/Advanced/Debug/Export TB Icon")]
private static void Export()
{
if (Selection.activeObject is not Texture2D tex)
{
Debug.LogError($"{Selection.activeObject?.name ?? "none"} is not a texture!");
return;
}
string path = AssetDatabase.GetAssetPath(Selection.activeObject);
string filename = Path.GetFileNameWithoutExtension(path);
string shoutName = filename.Split('_')[^1].ToUpperInvariant();
StringBuilder snippet = new();
snippet.AppendLine($"public static readonly EditorTexture {shoutName}_PNG = new(\"{filename}\",");
int rowCounter = 0;
foreach (byte b in File.ReadAllBytes(path))
{
if (rowCounter > 0)
{
snippet.Append(", ");
}
if (rowCounter > 16)
{
rowCounter = 0;
snippet.AppendLine();
snippet.Append('\t');
}
snippet.Append($"0x{b:X2}");
rowCounter++;
}
snippet.AppendLine(");");
EditorGUIUtility.systemCopyBuffer = snippet.ToString();
Debug.Log($"{Selection.activeObject.name} copied to clipboard!");
}
public static readonly EditorTexture IMPORT_PNG = new("T_TB_Import",
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D,
0x0D, 0x00, 0x00, 0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0x8D, 0x94, 0xBF, 0x6A, 0x55,
0x41, 0x10, 0xC6, 0x73, 0x6E, 0x12, 0x0D, 0xF1, 0x0F, 0xA2, 0x95, 0xAD, 0x56, 0xD6, 0x76, 0x8A,
0x96, 0x8A, 0x95, 0x8D, 0xA4, 0x14, 0x04, 0xDF, 0x23, 0x68, 0xA1, 0x36, 0x82, 0x08, 0x36, 0xFA,
0x0C, 0xBE, 0x82, 0x8D, 0xAF, 0x60, 0x21, 0x01, 0x6D, 0x44, 0x5B, 0x0B, 0x41, 0xA3, 0xC9, 0xCD,
0xF1, 0xF7, 0x9B, 0xBB, 0xDF, 0x65, 0x73, 0xEF, 0x45, 0xFC, 0xE0, 0x3B, 0x33, 0x3B, 0x33, 0x3B,
0x3B, 0x3B, 0xBB, 0x7B, 0x86, 0x71, 0x1C, 0xD7, 0x82, 0x61, 0x18, 0xA2, 0xAE, 0xF5, 0xF6, 0x18,
0xE3, 0xEF, 0x7C, 0x93, 0xE6, 0x3B, 0x52, 0xEA, 0x8F, 0xA1, 0xEC, 0x5D, 0x60, 0x8B, 0x9B, 0x89,
0x15, 0x89, 0x74, 0x6C, 0x42, 0x13, 0xC9, 0x8B, 0xB0, 0xB0, 0x11, 0x45, 0x99, 0x89, 0xBD, 0x2D,
0x7A, 0xE7, 0x73, 0x1B, 0xCE, 0x3B, 0x68, 0xBE, 0xB7, 0xC8, 0xEF, 0xF0, 0x21, 0x5C, 0xAF, 0xAD,
0x51, 0xD9, 0x04, 0x0E, 0x56, 0xB8, 0x8A, 0x4E, 0x6C, 0xF6, 0xCD, 0xCE, 0x7F, 0x13, 0xFD, 0x1B,
0x3C, 0x84, 0xE7, 0x9B, 0x7D, 0x62, 0x60, 0x12, 0xB9, 0xF2, 0xBC, 0x89, 0x06, 0x74, 0x30, 0x26,
0xC9, 0x5C, 0xFC, 0x05, 0x0C, 0xEE, 0xA0, 0x98, 0xA7, 0xFC, 0x59, 0xF9, 0x92, 0xC6, 0x86, 0x4A,
0xDA, 0x82, 0xF4, 0x6F, 0x74, 0xFA, 0x35, 0xF4, 0x3D, 0x18, 0xBC, 0x41, 0x39, 0x16, 0x93, 0x84,
0xBB, 0x38, 0x5E, 0xEB, 0x04, 0xEB, 0xD0, 0x8A, 0xFA, 0xAA, 0x8C, 0x7B, 0x06, 0x7B, 0x7C, 0x61,
0xB0, 0x0D, 0xF5, 0x59, 0x75, 0xE5, 0x4A, 0xC2, 0x07, 0x18, 0xC4, 0x23, 0xA8, 0xCD, 0xAA, 0x4C,
0xA8, 0x7E, 0x15, 0x7E, 0x80, 0x62, 0x0A, 0x7F, 0x97, 0x36, 0x8E, 0xB7, 0x90, 0xFA, 0xD3, 0x8A,
0xCA, 0x55, 0x1F, 0x8C, 0xF7, 0x5A, 0x90, 0xE2, 0x29, 0x8C, 0xDD, 0x05, 0x82, 0x3F, 0x28, 0xFB,
0x6D, 0xF0, 0x0A, 0xB9, 0x94, 0x4C, 0x5B, 0x26, 0xDE, 0x6E, 0x81, 0x59, 0xFD, 0x25, 0xE3, 0x77,
0xCD, 0x76, 0x84, 0x3C, 0x80, 0x9E, 0xA6, 0xF8, 0x04, 0x67, 0x07, 0xD0, 0x1D, 0xA2, 0xBD, 0xC2,
0x3E, 0x4F, 0x78, 0xDD, 0x48, 0xE0, 0x96, 0x32, 0xD1, 0xB1, 0x55, 0x99, 0x50, 0xE8, 0x13, 0x37,
0xA0, 0x93, 0xFB, 0xC3, 0x4A, 0x61, 0xF3, 0x97, 0xF2, 0xC3, 0x15, 0x80, 0x2F, 0x47, 0x1E, 0xC2,
0x29, 0xF4, 0x35, 0x78, 0xEA, 0x5E, 0x62, 0xED, 0xCF, 0xE1, 0x7B, 0xA8, 0xDD, 0x98, 0x65, 0xB4,
0xD5, 0x2E, 0x23, 0x53, 0x59, 0x2A, 0xC2, 0x54, 0x70, 0xBB, 0xE2, 0x23, 0xAC, 0x83, 0x22, 0xCB,
0xFC, 0xBE, 0x2E, 0x66, 0xCC, 0xD3, 0xFB, 0x89, 0x63, 0x1F, 0x9E, 0x5A, 0x0C, 0x60, 0xEC, 0x35,
0x12, 0x3B, 0xD0, 0x7B, 0x65, 0xFF, 0xF2, 0xEC, 0xB4, 0x1F, 0x43, 0x7E, 0x0E, 0x26, 0xFC, 0xD5,
0x3C, 0x75, 0x19, 0xBB, 0xA8, 0x8C, 0xEF, 0x36, 0xDB, 0x94, 0x77, 0x2D, 0xEA, 0xED, 0x2F, 0xCA,
0x34, 0x73, 0x8B, 0x55, 0x3F, 0x43, 0xE1, 0xD5, 0x70, 0x9B, 0x8B, 0xD4, 0x97, 0x7B, 0x6A, 0x21,
0xAB, 0xB7, 0x4D, 0x90, 0x49, 0xED, 0xCD, 0x57, 0xF8, 0x3F, 0x78, 0x42, 0x50, 0x0A, 0xA9, 0x9E,
0x66, 0xEC, 0x0E, 0xD2, 0x64, 0xF5, 0xFB, 0xF0, 0x0A, 0xDC, 0x82, 0xE7, 0xE0, 0xD9, 0xC6, 0x33,
0xC8, 0xD3, 0xD0, 0xFE, 0x9E, 0x84, 0xFE, 0xFB, 0x76, 0xE1, 0x63, 0x68, 0xA5, 0xF5, 0x73, 0x45,
0x16, 0xFA, 0x84, 0xB1, 0xCD, 0x1C, 0xF4, 0xC8, 0x95, 0xED, 0x51, 0xC3, 0x09, 0xC6, 0x26, 0xDC,
0x86, 0x17, 0xE0, 0x1E, 0x5C, 0xBA, 0x3A, 0x7D, 0x42, 0x4F, 0xDC, 0xD9, 0x75, 0x08, 0x24, 0x62,
0x3E, 0xBD, 0x40, 0xC6, 0xC6, 0x10, 0xF5, 0xDF, 0xF8, 0x0B, 0x1C, 0x07, 0xCE, 0xEF, 0xA8, 0x1B,
0x38, 0x53, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82);
public static readonly EditorTexture SYNC_PNG = new("T_TB_Sync",
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D,
0x0D, 0x00, 0x00, 0x01, 0xE8, 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0x8D, 0x93, 0xBB, 0x6A, 0x54,
0x51, 0x14, 0x86, 0xE7, 0xC4, 0x09, 0x24, 0x31, 0x20, 0xD8, 0xD9, 0x5A, 0xE8, 0x03, 0xD8, 0x29,
0xA6, 0x0C, 0xD8, 0xE7, 0x05, 0x7C, 0x10, 0x91, 0x34, 0x2A, 0xD8, 0x08, 0x92, 0x46, 0x2D, 0xC4,
0x32, 0x90, 0xC2, 0x27, 0xF0, 0x25, 0x02, 0x01, 0x0B, 0x1B, 0x51, 0x0B, 0x11, 0xC4, 0x1B, 0x89,
0xB9, 0x9C, 0x7C, 0xDF, 0x9E, 0xFD, 0x0F, 0x7B, 0x8E, 0x47, 0x74, 0xC1, 0x3F, 0xEB, 0xFE, 0xEF,
0xBD, 0xF6, 0x59, 0xD3, 0xF5, 0x7D, 0x3F, 0xF9, 0x97, 0x74, 0x5D, 0x97, 0x92, 0x8E, 0x7A, 0xDC,
0xAE, 0xE8, 0x1A, 0x34, 0x29, 0xC9, 0x89, 0xFE, 0xD4, 0x9F, 0x11, 0x31, 0x7E, 0x0D, 0x7C, 0x01,
0xBF, 0xC0, 0x11, 0xF8, 0x0D, 0x99, 0x8D, 0x55, 0x61, 0x8D, 0xC8, 0x18, 0xE1, 0x12, 0x75, 0x9E,
0xB6, 0x05, 0xB6, 0xC1, 0x27, 0x18, 0x24, 0xFC, 0x09, 0x7E, 0x80, 0xEF, 0xE0, 0x5B, 0xC5, 0x57,
0xF4, 0x21, 0x38, 0x00, 0xAF, 0xC0, 0x4C, 0x3C, 0xB8, 0x81, 0xE3, 0xC4, 0x7F, 0x80, 0xFD, 0x3F,
0xF2, 0x81, 0xA2, 0xD2, 0x17, 0xCE, 0xA1, 0x36, 0xB9, 0x04, 0x24, 0xDE, 0x06, 0xCA, 0xF1, 0x08,
0x0E, 0x4B, 0xA6, 0xEF, 0xDF, 0xA1, 0x57, 0xC0, 0x90, 0x67, 0xC1, 0x6F, 0x49, 0xEF, 0xD5, 0xC6,
0xD3, 0xAA, 0xA3, 0xE2, 0x7F, 0x26, 0x70, 0x09, 0x4C, 0x72, 0x8B, 0x8C, 0xD8, 0x6A, 0x8F, 0xBB,
0x50, 0x8F, 0x78, 0x5D, 0xF5, 0xFC, 0x73, 0x0F, 0xFC, 0x55, 0xFC, 0x35, 0x63, 0xD3, 0x66, 0x25,
0x6A, 0xCD, 0x5C, 0xD9, 0xEC, 0x98, 0xEA, 0xDD, 0x1A, 0x3D, 0x45, 0x8F, 0x7D, 0xC8, 0x15, 0xE2,
0x33, 0x42, 0xAF, 0xF9, 0x17, 0xB1, 0xF1, 0x18, 0x3C, 0x06, 0xD7, 0xAB, 0xBD, 0x8C, 0x56, 0x24,
0x6E, 0x27, 0x70, 0x92, 0x8B, 0x26, 0xDA, 0x11, 0x5B, 0x7B, 0xEA, 0x41, 0xE0, 0x36, 0x50, 0xF2,
0x56, 0x67, 0xD8, 0x65, 0x1F, 0x4B, 0xB4, 0xEF, 0x4F, 0x9A, 0xDC, 0x2D, 0xEC, 0x89, 0x3B, 0x37,
0x71, 0xEC, 0x06, 0x98, 0x9D, 0x7B, 0xE8, 0x6D, 0x5E, 0x02, 0xC5, 0xDB, 0x94, 0x7F, 0x02, 0xDA,
0xF8, 0x1B, 0xF0, 0x14, 0x78, 0xB3, 0xC4, 0xD7, 0xB1, 0x47, 0x6F, 0xB8, 0xEC, 0x49, 0x60, 0x07,
0x28, 0xAE, 0x46, 0x7B, 0x2B, 0xD7, 0x28, 0xD3, 0x3C, 0xB4, 0xA0, 0xCA, 0x16, 0xFA, 0x0F, 0xC2,
0x90, 0x6D, 0xD6, 0xA2, 0x23, 0x74, 0xC6, 0xDD, 0xC7, 0xBE, 0x61, 0x13, 0x70, 0xA5, 0xF2, 0x2C,
0xD9, 0xD3, 0xBB, 0xE6, 0x72, 0x92, 0x3A, 0x2B, 0xB4, 0x86, 0xFD, 0x1E, 0xB4, 0xF2, 0x08, 0x27,
0xB5, 0x1E, 0xEA, 0x97, 0x17, 0x65, 0xAD, 0xF0, 0x9F, 0x81, 0xFB, 0x60, 0x81, 0x30, 0x27, 0x3E,
0x27, 0x11, 0x79, 0x8B, 0x71, 0x13, 0x84, 0x2C, 0x35, 0xBE, 0x96, 0x22, 0x69, 0x72, 0x57, 0xAD,
0x8B, 0x93, 0x51, 0xEF, 0x84, 0x09, 0xFD, 0x04, 0xE4, 0xD6, 0x7E, 0x88, 0xD2, 0x1C, 0x02, 0x75,
0x15, 0xC7, 0x17, 0x72, 0x75, 0xFE, 0xA4, 0xE9, 0x32, 0xB6, 0x6B, 0xF0, 0x11, 0x6C, 0x80, 0x85,
0xC3, 0x6C, 0x6E, 0x62, 0x43, 0x5B, 0xC2, 0xC2, 0x63, 0x5D, 0xDE, 0xE1, 0x05, 0xC1, 0xBD, 0xA6,
0x69, 0xE1, 0x56, 0x43, 0x42, 0xFD, 0x48, 0xD3, 0x33, 0x0B, 0xD5, 0xC0, 0x95, 0x26, 0x51, 0x9E,
0xC0, 0x6C, 0x62, 0xAD, 0x6D, 0x2C, 0x92, 0x7C, 0xF4, 0xFC, 0x51, 0x6B, 0x41, 0x59, 0x74, 0xEC,
0x33, 0x7D, 0x97, 0x5D, 0x69, 0x09, 0x4A, 0x80, 0x9F, 0xE4, 0x86, 0xF9, 0x73, 0x83, 0xE5, 0xF3,
0x69, 0xCF, 0x05, 0x29, 0x55, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60,
0x82);
public static readonly EditorTexture CONFIG_PNG = new("T_TB_Config",
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D,
0x0D, 0x00, 0x00, 0x01, 0xA8, 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0xAD, 0x94, 0x3B, 0x2F, 0x04,
0x51, 0x14, 0xC7, 0xC7, 0xAB, 0xB1, 0xBB, 0xB2, 0x44, 0xA2, 0x41, 0x48, 0x68, 0x49, 0x48, 0xF4,
0x6C, 0x54, 0x3A, 0x89, 0x42, 0xC5, 0x37, 0x10, 0xA2, 0xF3, 0x31, 0x94, 0xE2, 0xD1, 0x53, 0x4A,
0x24, 0x7A, 0xAD, 0x44, 0xEB, 0x55, 0xE8, 0xBC, 0xD6, 0x62, 0x45, 0xE1, 0xB1, 0x7E, 0xFF, 0x99,
0x3B, 0xD7, 0xC9, 0x5D, 0x8F, 0x89, 0x38, 0xC9, 0x6F, 0xCF, 0x63, 0xEE, 0xFC, 0xE7, 0xDC, 0x7B,
0x66, 0xB6, 0xA1, 0x56, 0xAB, 0x45, 0xFF, 0x69, 0x8D, 0x19, 0xC5, 0x4A, 0xAC, 0x5B, 0x82, 0xE1,
0x5F, 0xD7, 0xAB, 0x43, 0x43, 0x13, 0xF1, 0x18, 0x8C, 0x98, 0x5A, 0x89, 0xF8, 0x00, 0x64, 0x3B,
0x30, 0x0C, 0xBA, 0xA7, 0x03, 0xBA, 0xA0, 0xC5, 0xE5, 0xB1, 0x4E, 0x28, 0x36, 0xC1, 0x45, 0xD9,
0x03, 0x48, 0x34, 0x07, 0x7B, 0x60, 0x6D, 0x81, 0x44, 0x62, 0x47, 0xAE, 0x38, 0x89, 0xF7, 0xA2,
0x56, 0xB0, 0xE8, 0x16, 0xA4, 0xEE, 0x89, 0xE0, 0x36, 0x4D, 0x8C, 0xBF, 0x27, 0x3E, 0x37, 0xB9,
0xC2, 0x51, 0x88, 0xB5, 0xEC, 0x19, 0xBE, 0x71, 0x3E, 0x87, 0xEE, 0x8C, 0x34, 0xA9, 0x56, 0xE8,
0x70, 0xB9, 0x75, 0x6D, 0x24, 0xFD, 0xF0, 0xEE, 0x8A, 0x65, 0xFC, 0x8B, 0x8B, 0x23, 0xDB, 0xA1,
0xE2, 0x02, 0x9C, 0x42, 0x56, 0xAB, 0xB0, 0x70, 0x08, 0xBC, 0x4E, 0xB3, 0x57, 0x8E, 0x22, 0xC5,
0xEA, 0xB8, 0xDD, 0xD4, 0x14, 0x3E, 0xC2, 0xB6, 0xAB, 0xCD, 0xE0, 0x0B, 0x2E, 0x96, 0xD3, 0x4E,
0xD4, 0x71, 0x11, 0x2A, 0xE0, 0x3B, 0xEC, 0x73, 0x2D, 0xDD, 0x04, 0xAD, 0x69, 0x38, 0x83, 0xA6,
0x03, 0xC5, 0xAA, 0x85, 0xB6, 0x4C, 0x41, 0x33, 0x88, 0x05, 0x7B, 0xC2, 0xAB, 0x26, 0x5F, 0xD7,
0xA2, 0x00, 0xD5, 0xBE, 0xB2, 0x69, 0x8A, 0xF1, 0x16, 0xD5, 0xF6, 0x6B, 0xDC, 0xEE, 0xDF, 0x7F,
0xCE, 0xB8, 0xF5, 0x2E, 0xBE, 0x5D, 0xAA, 0xD0, 0x0D, 0x2F, 0x70, 0x09, 0xD6, 0xB2, 0x6C, 0x59,
0x43, 0x1C, 0x87, 0x78, 0x27, 0x0D, 0x0A, 0x8C, 0xE5, 0x88, 0x2F, 0xC0, 0xBE, 0x2E, 0x3F, 0x0D,
0x45, 0xAF, 0x4C, 0x2F, 0x3C, 0x41, 0x62, 0xA9, 0xB2, 0xF1, 0x8B, 0xC4, 0x59, 0x6D, 0x8B, 0x85,
0x76, 0x68, 0x7E, 0xCA, 0xE9, 0xC1, 0x0F, 0xB0, 0x60, 0x37, 0xA3, 0xDA, 0xB5, 0x5B, 0xB7, 0x89,
0xF7, 0xA2, 0xA9, 0x50, 0xEA, 0x37, 0x02, 0xB1, 0x6A, 0x90, 0x2B, 0x2D, 0xC3, 0x41, 0x50, 0x9F,
0x25, 0x8F, 0x35, 0xEC, 0xA7, 0xA7, 0x33, 0xD0, 0xB4, 0xAE, 0x92, 0xC3, 0x88, 0x4E, 0xF1, 0x73,
0xF0, 0xF9, 0x59, 0x25, 0x17, 0x06, 0x71, 0x6B, 0x70, 0x92, 0xA4, 0xD1, 0x3E, 0xFE, 0xD8, 0xC5,
0x75, 0x5B, 0xD6, 0x53, 0x56, 0x5C, 0x07, 0x53, 0xEE, 0xA9, 0x79, 0xFC, 0x33, 0xC8, 0x3A, 0x21,
0xDD, 0xCD, 0x3C, 0xF1, 0x2A, 0xF8, 0x3F, 0x06, 0x5D, 0x0B, 0xA7, 0xEC, 0x1F, 0x14, 0x04, 0x79,
0x97, 0x57, 0x83, 0x7A, 0x5D, 0x9A, 0x55, 0xB0, 0xEE, 0xC6, 0xEF, 0x0A, 0x1F, 0x71, 0x03, 0x2D,
0x56, 0xEC, 0x1B, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60,
0x82);
public static readonly EditorTexture MANUAL_PNG = new("T_TB_Manual",
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00,
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x08, 0x04, 0x00, 0x00, 0x00, 0x27, 0x80, 0xD5, 0x86, 0x00,
0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x01, 0x4F, 0x49,
0x44, 0x41, 0x54, 0x28, 0xCF, 0xBD, 0xD2, 0xBF, 0x4B, 0x95, 0x51, 0x1C, 0xC7, 0xF1, 0xAF, 0xBF, 0xA2,
0xAE, 0x21, 0x14, 0x28, 0x2E, 0xD1, 0xE8, 0x10, 0x09, 0x85, 0x43, 0xD0, 0x10, 0x82, 0x58, 0x54, 0x44,
0x0D, 0x49, 0x81, 0x6B, 0x4B, 0x8B, 0x43, 0xD0, 0x60, 0x08, 0x82, 0x70, 0xFF, 0x01, 0xF1, 0x1F, 0x68,
0x6E, 0x6B, 0xCB, 0x4D, 0x5D, 0x6E, 0x54, 0x90, 0x8B, 0x38, 0x18, 0x21, 0xA1, 0x68, 0x6A, 0x57, 0xF3,
0xDE, 0xD2, 0xE7, 0xDE, 0xFB, 0x72, 0x78, 0xD4, 0xFB, 0x18, 0x78, 0x71, 0xEA, 0x4C, 0xE7, 0xF0, 0x79,
0x73, 0x0E, 0xDF, 0xF7, 0xE7, 0x44, 0xFC, 0x97, 0x25, 0x67, 0xCA, 0xBC, 0x82, 0x51, 0x6D, 0x8D, 0xB0,
0xA7, 0x7E, 0xDB, 0x55, 0x50, 0x50, 0xB1, 0x73, 0x3A, 0xF6, 0x5A, 0xC5, 0xAC, 0x5C, 0x44, 0x84, 0x5B,
0xAA, 0x3A, 0xB2, 0xE1, 0x88, 0x55, 0x45, 0xFB, 0x7E, 0x79, 0xA7, 0x2A, 0x1F, 0xA1, 0xCB, 0xA4, 0x09,
0x1D, 0xF6, 0xBC, 0xAC, 0x63, 0x4F, 0x54, 0xCC, 0x79, 0x61, 0xC8, 0x5B, 0x89, 0x9F, 0x9A, 0x22, 0xAC,
0xF8, 0x63, 0x4B, 0x9F, 0x05, 0x1F, 0xEA, 0xE0, 0x77, 0x33, 0xC7, 0xFB, 0x2B, 0x12, 0xFD, 0x6E, 0x4B,
0xF4, 0x44, 0x44, 0x78, 0xA3, 0xE4, 0x62, 0x1A, 0xB5, 0x2A, 0x1B, 0x88, 0xF0, 0x4C, 0x51, 0x49, 0xCE,
0xA2, 0x82, 0x2E, 0xF7, 0x5C, 0x36, 0x23, 0xEF, 0x9C, 0xB2, 0xF1, 0x14, 0xBC, 0xA3, 0xE6, 0xAA, 0x1B,
0x6A, 0x3E, 0x7B, 0xA0, 0xD9, 0x98, 0xBF, 0xDA, 0x22, 0x7C, 0x53, 0xF6, 0x25, 0xC2, 0x27, 0xCB, 0x29,
0xD8, 0xA9, 0xE8, 0xBD, 0x71, 0x8B, 0x87, 0x8F, 0xB7, 0x4B, 0xBC, 0x8A, 0x50, 0xD4, 0x1F, 0x11, 0xE1,
0x91, 0xC4, 0xA5, 0x34, 0x7A, 0x6E, 0xDF, 0xF5, 0x08, 0xD7, 0xAC, 0xAB, 0x18, 0x34, 0x6B, 0xFE, 0x84,
0xF6, 0x5D, 0xF7, 0x53, 0xB0, 0xC9, 0x82, 0xE5, 0x08, 0x5F, 0x6D, 0x78, 0xEC, 0x82, 0x41, 0x25, 0xB9,
0x8C, 0xF6, 0x4D, 0xF9, 0xA3, 0x59, 0x7B, 0xEC, 0x19, 0xF6, 0xD0, 0xCD, 0xC3, 0xF3, 0x8E, 0xB9, 0x8C,
0xF6, 0x56, 0x2D, 0x75, 0x45, 0xD3, 0x96, 0x32, 0xDA, 0xD7, 0x38, 0xBE, 0xE5, 0x9F, 0xE2, 0x7A, 0x55,
0x4F, 0x68, 0xDF, 0x3E, 0xBD, 0xE3, 0x9A, 0x1F, 0x67, 0xF9, 0x58, 0x2D, 0x3E, 0x66, 0x5A, 0x6D, 0x00,
0x76, 0xBB, 0xAB, 0xF9, 0x2C, 0xE0, 0x79, 0xED, 0x8D, 0x89, 0x03, 0xDB, 0x0E, 0x1A, 0x97, 0x4F, 0xCA,
0x84, 0x01, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4b6cd1e1a8fb4d328fbb09d76179a619
timeCreated: 1711455760
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/TrembleIcons.cs
uploadId: 939664
@@ -0,0 +1,79 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEditor;
using UnityEditor.Overlays;
using UnityEditor.Toolbars;
namespace TinyGoose.Tremble.Editor
{
#if UNITY_2022_1_OR_NEWER
[Overlay(typeof(SceneView), "Tremble Sync", true, id = ID)]
#else
[Overlay(typeof(SceneView), "Tremble Sync", true)]
#endif
public class TrembleSyncOverlay : ToolbarOverlay
{
private const string ID = "TrembleSync";
public TrembleSyncOverlay() : base(ReimportMapButton.ID, SyncButton.ID, ConfigButton.ID, ManualButton.ID)
{
}
protected override Layout supportedLayouts => Layout.HorizontalToolbar | Layout.VerticalToolbar;
}
[EditorToolbarElement(ID, typeof(SceneView))]
public class ReimportMapButton : EditorToolbarButton
{
public const string ID = "Tremble/Reimport";
public ReimportMapButton()
{
icon = TrembleIcons.IMPORT_PNG.Get();
tooltip = "Reimport Current Map";
clicked += TrembleEditorAPI.ReimportCurrentMap;
}
}
[EditorToolbarElement(ID, typeof(SceneView))]
public class SyncButton : EditorToolbarButton
{
public const string ID = "Tremble/Sync";
public SyncButton()
{
icon = TrembleIcons.SYNC_PNG.Get();
tooltip = "Sync to TrenchBroom";
clicked += TrembleEditorAPI.SyncToTrenchBroom;
}
}
[EditorToolbarElement(ID, typeof(SceneView))]
public class ConfigButton : EditorToolbarButton
{
public const string ID = "Tremble/Config";
public ConfigButton()
{
icon = TrembleIcons.CONFIG_PNG.Get();
tooltip = "Tremble Settings";
clicked += TrembleEditorAPI.OpenSettings;
}
}
[EditorToolbarElement(ID, typeof(SceneView))]
public class ManualButton : EditorToolbarButton
{
public const string ID = "Tremble/Manual";
public ManualButton()
{
icon = TrembleIcons.MANUAL_PNG.Get();
tooltip = "Tremble Manual";
clicked += () => TrembleEditorAPI.OpenManual();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a930a79e3a324c35b20894fac9d8b4a2
timeCreated: 1706013427
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/TrembleSyncOverlay.cs
uploadId: 939664
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c63360b568474474a397794178a8f00a
timeCreated: 1721666903
@@ -0,0 +1,117 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using UnityEngine;
using System.Collections.Generic;
using UnityEditor;
namespace TinyGoose.Tremble.Editor
{
internal static class EditorGUILayoutUtil
{
internal static void Pad(float pad, Action content) => Pad(pad, pad, content);
internal static void Pad(float hPad, float vPad, Action content)
{
GUILayout.BeginHorizontal();
{
GUILayout.Space(hPad);
GUILayout.BeginVertical();
{
GUILayout.Space(vPad);
try
{
content();
}
catch (ExitGUIException)
{
// ignore
}
catch (Exception ex)
{
Debug.LogException(ex);
}
GUILayout.Space(vPad);
}
GUILayout.EndVertical();
GUILayout.Space(hPad);
}
GUILayout.EndHorizontal();
}
private static readonly Dictionary<string, bool> s_CollapsibleHeaderStates = new();
internal static void Indent(Action inside)
{
EditorGUI.indentLevel++;
inside();
EditorGUI.indentLevel--;
}
internal static bool FoldoutArrow(bool isOpen)
{
bool toggle = GUILayout.Button(GUIContent.none, EditorStyles.label, GUILayout.Width(20f));
Rect buttonRect = GUILayoutUtility.GetLastRect();
buttonRect.x += 5f;
if (Event.current.type == EventType.Repaint)
{
EditorStyles.foldout.Draw(buttonRect, false, false, isOpen, false);
}
return toggle;
}
internal static bool CollapsibleHeader(string header, bool isOpen, Action inside)
{
GUILayout.BeginHorizontal();
{
bool toggle = FoldoutArrow(isOpen);
toggle |= GUILayout.Button(header, EditorStyles.label);
if (toggle)
{
isOpen = !isOpen;
}
}
GUILayout.EndHorizontal();
if (isOpen)
{
GUILayout.BeginHorizontal();
{
GUILayout.Space(20f);
GUILayout.BeginVertical();
{
inside();
}
GUILayout.EndVertical();
}
GUILayout.EndHorizontal();
}
return isOpen;
}
internal static void CollapsibleHeader(string header, string id, bool defaultOpen, Action inside)
{
bool wasOpen = s_CollapsibleHeaderStates.GetValueOrDefault(id, defaultOpen);
s_CollapsibleHeaderStates[id] = CollapsibleHeader(header, wasOpen, inside);
}
internal static void CollapsibleHeader(string header, string id, Action inside)
=> CollapsibleHeader(header, id, defaultOpen: false, inside);
internal static void SetHeaderOpen(string id, bool isOpened)
{
s_CollapsibleHeaderStates[id] = isOpened;
}
internal static bool IsHeaderOpen(string id) => s_CollapsibleHeaderStates.GetValueOrDefault(id, false);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4c018a50074942338e9d90803f43545a
timeCreated: 1712049970
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Utils/EditorGUILayoutUtil.cs
uploadId: 939664
@@ -0,0 +1,81 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
// Adapted from Unity3D-IconManager by Thundernerd.
// This is only a tiny portion of the original code, but in the interest of attributing to his good work,
// I'll include his licence and a link to the original.
// https://github.com/Thundernerd/Unity3D-IconManager/
// Original Licence follows:
// MIT License
//
// Copyright (c) [2020] [Christiaan Bloemendaal]
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public static class EditorGameObjectIconUtil
{
public enum EditorIcon
{
GrayDiamond,
BlueDiamond,
TealDiamond,
GreenDiamond,
YellowDiamond,
OrangeDiamond,
RedDiamond,
PurpleDiamond,
MAX
}
public enum EditorLabel
{
Gray,
Blue,
Teal,
Green,
Yellow,
Orange,
Red,
Purple,
MAX
}
public static void SetEditorIcon(this GameObject go, EditorIcon icon)
{
//TODO(jwf): cache these!
Texture2D texture = EditorGUIUtility.FindTexture($"sv_icon_dot{(int)icon+8}_pix16_gizmo");
EditorGUIUtility.SetIconForObject(go, texture);
}
public static void SetEditorIcon(this GameObject go, EditorLabel label)
{
//TODO(jwf): cache these!
Texture2D texture = EditorGUIUtility.FindTexture($"sv_label_{(int)label}");
EditorGUIUtility.SetIconForObject(go, texture);
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 053deb05101c4cf29d8d760aa74f619f
timeCreated: 1724832500
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Utils/EditorGameObjectIconUtil.cs
uploadId: 939664
@@ -0,0 +1,154 @@
//
// 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.Linq;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public static class MaterialGroupUtil
{
public static bool RemoveMaterialsFromGroups(Material[] materials)
=> MoveMaterialsToGroupIdx(materials, -1);
public static bool MoveMaterialsToGroup(Material[] materials, string groupName, bool createIfNotExist = false)
{
int index = GetMaterialGroupIndex(groupName);
if (index == -1 && !createIfNotExist)
{
Debug.LogError($"Could not move materials to non-existent group '{groupName}'!");
return false;
}
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
// New group - create and add
if (index == -1)
{
syncSettings.Modify(so =>
{
SerializedProperty materialGroupsArray = so.FindBackedProperty(nameof(TrembleSyncSettings.MaterialGroups));
materialGroupsArray.AppendArrayElement();
});
MaterialGroup[] mgs = syncSettings.MaterialGroups;
mgs[^1].Name = groupName;
mgs[^1].Materials = Array.Empty<Material>();
index = mgs.Length - 1;
}
return MoveMaterialsToGroupIdx(materials, index);
}
public static int GetMaterialGroupIndexForMaterial(Material material)
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
for (int materialGroupIdx = 0; materialGroupIdx < syncSettings.MaterialGroups.Length; materialGroupIdx++)
{
MaterialGroup mg = syncSettings.MaterialGroups[materialGroupIdx];
foreach (Material m in mg.Materials)
{
if (material == m)
return materialGroupIdx;
}
}
return -1;
}
public static bool IsMaterialPartOfAnyMaterialGroup(Material material)
=> GetMaterialGroupIndexForMaterial(material) != -1;
private static bool MoveMaterialsToGroupIdx(Material[] materials, int index)
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
MaterialGroup[] mgs = syncSettings.MaterialGroups;
for (int groupIdx = 0; groupIdx < mgs.Length; groupIdx++)
{
if (groupIdx == index)
{
// Our target group - remove these mats, and add to end
mgs[groupIdx].Materials = mgs[groupIdx].Materials
.Where(m => !materials.Contains(m))
.Concat(materials)
.ToArray();
}
else
{
// Not our target group - remove these mats
mgs[groupIdx].Materials = mgs[groupIdx].Materials
.Where(m => !materials.Contains(m))
.ToArray();
}
}
CleanupMissingMaterials(mgs);
CleanupEmptyGroups(mgs);
return true;
}
private static void CleanupMissingMaterials(MaterialGroup[] mgs)
{
for (int groupIdx = 0; groupIdx < mgs.Length; groupIdx++)
{
mgs[groupIdx].Materials = mgs[groupIdx].Materials
.Where(m => m)
.ToArray();
}
}
private static void CleanupEmptyGroups(MaterialGroup[] mgs)
{
// Now check for empty groups
List<int> removeIndexes = new();
for (int groupIdx = 0; groupIdx < mgs.Length; groupIdx++)
{
if (mgs[groupIdx].Materials.Length != 0)
continue;
bool remove = EditorUtility.DisplayDialog("Empty Material Group", $"Material Group '{mgs[groupIdx].Name}' is empty - delete it?", "Yup", "No - keep it!");
if (!remove)
continue;
removeIndexes.Add(groupIdx);
}
int numRemoved = 0;
foreach (int idx in removeIndexes)
{
int removeIdx = idx - numRemoved;
numRemoved++;
TrembleSyncSettings.Get().Modify(so =>
{
SerializedProperty groupsProp = so.FindBackedProperty(nameof(TrembleSyncSettings.MaterialGroups));
groupsProp.DeleteArrayElementAtIndex(removeIdx);
});
}
}
private static int GetMaterialGroupIndex(string groupName)
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
for (int mgIdx = 0; mgIdx < syncSettings.MaterialGroups.Length; mgIdx++)
{
if (syncSettings.MaterialGroups[mgIdx].Name.EqualsInvariant(groupName))
return mgIdx;
}
return -1;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: bfca730eb4364517a3fa6aafb28dc837
timeCreated: 1722943294
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Utils/MaterialGroupUtil.cs
uploadId: 939664
@@ -0,0 +1,31 @@
using System;
using UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
public static class MaterialRenderDataUtil
{
public static void SetMaterialImportMode(this Material mat, MaterialImportMode mode)
=> ModifyRenderData(mat, mrd => mrd.ExportMode = mode);
public static void SetMaterialResolutionOverridden(this Material mat, bool overrideResolution)
=> ModifyRenderData(mat, mrd => mrd.IsResolutionOverridden = overrideResolution);
public static void SetMaterialResolutionX(this Material mat, MaterialExportSize size)
=> ModifyRenderData(mat, mrd => mrd.ResolutionX = size);
public static void SetMaterialResolutionY(this Material mat, MaterialExportSize size)
=> ModifyRenderData(mat, mrd => mrd.ResolutionY = size);
private static void ModifyRenderData(Material mat, Action<MaterialRenderData> action)
{
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
MaterialRenderData renderData = TrembleSyncSettings.Get().GetMaterialRenderData(mat);
action(renderData);
EditorUtility.SetDirty(syncSettings);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 808c02ac921b9e4638fdb31dd490d61c
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/Editor/EditorUI/Utils/MaterialRenderDataUtil.cs
uploadId: 939664
@@ -0,0 +1,391 @@
//
// 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 UnityEditor;
using UnityEngine;
namespace TinyGoose.Tremble.Editor
{
// This wraps EditorGUI, EditorGUILayout, GUI, and GUILayout calls.
// The purpose is that you can call them in "normal" mode, which forwards the calls on to Unity,
// or "keywords capture" mode, which performs no GUI work at all but captures a list of
// keywords to expose to the Project Settings window's search.
public static class TrembleSettingsGUI
{
private static readonly List<string> s_Keywords = new(256);
private static bool s_IsCapturingKeywords = false;
private static GUIStyle s_LineStyle;
private static readonly Dictionary<string, bool> s_FoldoutStates = new();
public static void BeginKeywordCapture()
{
s_IsCapturingKeywords = true;
s_Keywords.Clear();
}
public static List<string> EndKeywordCapture()
{
s_IsCapturingKeywords = false;
return s_Keywords;
}
public static void Space(float size)
{
if (s_IsCapturingKeywords)
return;
GUILayout.Space(size);
}
public static void FlexibleSpace()
{
if (s_IsCapturingKeywords)
return;
GUILayout.FlexibleSpace();
}
public static void LabelledHyperlink(string label, string buttonText, string url)
=> LabelledHyperlink(label, buttonText, () => Application.OpenURL(url));
public static void LabelledHyperlink(string label, string buttonText, Action action)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(label);
s_Keywords.Add(buttonText);
return;
}
BeginHorizontal();
{
Label(label + ":");
if (Button(buttonText, GUILayout.Width(250f)))
{
action();
}
}
EndHorizontal();
}
public static void BeginHorizontal(GUIStyle style, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
return;
GUILayout.BeginHorizontal(style, options);
}
public static void BeginHorizontal(params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
return;
GUILayout.BeginHorizontal(options);
}
public static void EndHorizontal()
{
if (s_IsCapturingKeywords)
return;
GUILayout.EndHorizontal();
}
public static void BeginVertical(GUIStyle style, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
return;
GUILayout.BeginVertical(style, options);
}
public static void BeginVertical(params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
return;
GUILayout.BeginVertical(options);
}
public static void EndVertical()
{
if (s_IsCapturingKeywords)
return;
GUILayout.EndVertical();
}
public static void BeginScrollView(ref Vector2 scrollPosition, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
return;
scrollPosition = GUILayout.BeginScrollView(scrollPosition, options);
}
public static void EndScrollView()
{
if (s_IsCapturingKeywords)
return;
GUILayout.EndScrollView();
}
public static void MakeLastRectClickable(Action onClick)
{
if (s_IsCapturingKeywords)
return;
Rect verticalRect = GUILayoutUtility.GetLastRect();
if (Event.current.type == EventType.MouseDown && verticalRect.Contains(Event.current.mousePosition))
{
onClick();
Event.current.Use(); // consume
}
}
public static void HelpBox(string message, MessageType type)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(message);
return;
}
EditorGUILayout.HelpBox(message, type);
}
public static void Separator(float height = 1f)
{
if (s_IsCapturingKeywords)
return;
s_LineStyle ??= new() { normal = new() { background = Texture2D.grayTexture } };
GUILayout.Box(Texture2D.grayTexture, s_LineStyle, GUILayout.ExpandWidth(true), GUILayout.Height(height));
}
public static void Label(string text, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(text);
return;
}
GUILayout.Label(text, GUI.skin.label, options);
}
public static void Label(string text, GUIStyle style, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(text);
return;
}
GUILayout.Label(text, style, options);
}
public static bool Button(string text, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(text);
return false;
}
return GUILayout.Button(text, options);
}
public static bool Button(GUIContent content, GUIStyle style, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(content.text);
return false;
}
return GUILayout.Button(content, style, options);
}
public static bool Toggle(bool value, GUIContent content)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(content.text);
return value;
}
return GUILayout.Toggle(value, content);
}
public static bool Toggle(bool value, GUIContent content, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(content.text);
return value;
}
return GUILayout.Toggle(value, content, options);
}
public static float FloatField(float value)
{
if (s_IsCapturingKeywords)
return value;
return EditorGUILayout.FloatField(value);
}
public static void PropertyField(SerializedProperty sp, params GUILayoutOption[] options)
{
if (s_IsCapturingKeywords)
return;
EditorGUILayout.PropertyField(sp, GUIContent.none, options);
}
public static void Image(Texture2D texture, float width, float height)
{
if (s_IsCapturingKeywords)
return;
GUILayout.Box(texture, GUILayout.Width(width), GUILayout.Height(height));
}
public static bool Foldout(bool foldout, string content, bool toggleOnLabelClick)
=> Foldout(foldout, content, toggleOnLabelClick, EditorStyles.foldout);
public static bool Foldout(bool foldout, string content, bool toggleOnLabelClick, GUIStyle style)
{
if (s_IsCapturingKeywords)
return foldout;
return EditorGUILayout.Foldout(foldout, content, toggleOnLabelClick, style);
}
public static void Foldout(string title, Action contents)
=> Foldout(title, contents, EditorStyles.foldout);
public static void Foldout(string title, Action contents, GUIStyle style)
{
if (s_IsCapturingKeywords)
return;
bool foldout = s_FoldoutStates.GetValueOrDefault(title, false);
foldout = Foldout(foldout, title, true, style);
if (foldout)
{
Space(10f);
BeginHorizontal();
{
Space(20f);
BeginVertical();
{
contents();
}
EndVertical();
}
EndHorizontal();
Space(10f);
}
s_FoldoutStates[title] = foldout;
}
public static void PrefixLabel(string text, string tooltip)
{
if (s_IsCapturingKeywords)
{
s_Keywords.Add(text);
return;
}
EditorGUILayout.PrefixLabel(new GUIContent(text, tooltip));
}
public static int Popup(int value, string[] options)
{
if (s_IsCapturingKeywords)
{
s_Keywords.AddRange(options);
return value;
}
return EditorGUILayout.Popup(value, options);
}
public static int Popup(int value, string[] options, GUIStyle style)
{
if (s_IsCapturingKeywords)
{
s_Keywords.AddRange(options);
return value;
}
return EditorGUILayout.Popup(value, options, style);
}
public static void UseDisabled(bool disabled, Action inside)
{
bool prevDisabled = false;
if (!s_IsCapturingKeywords)
{
prevDisabled = !GUI.enabled;
GUI.enabled = !disabled;
}
inside();
if (!s_IsCapturingKeywords)
{
GUI.enabled = !prevDisabled;
}
}
public static void UseColour(Color c, Action inside)
{
Color previousColour = Color.white;
if (!s_IsCapturingKeywords)
{
previousColour = GUI.color;
GUI.color = c;
}
inside();
if (!s_IsCapturingKeywords)
{
GUI.color = previousColour;
}
}
public static void Indent(Action inside)
{
if (!s_IsCapturingKeywords)
{
EditorGUI.indentLevel++;
}
inside();
if (!s_IsCapturingKeywords)
{
EditorGUI.indentLevel--;
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4aa6428101b54401a08834d76320438b
timeCreated: 1721666909
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/EditorUI/Utils/TrembleSettingsGUI.cs
uploadId: 939664
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f20e9f4a105af4a71a347271c79ea536
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,176 @@
fileFormatVersion: 2
guid: c459dc1c802f7437c980e9dd45061327
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: tvOS
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Icons/T_TB_Config.png
uploadId: 939664
Binary file not shown.
@@ -0,0 +1,160 @@
fileFormatVersion: 2
guid: e900b833b9fae460ea0bc74e646c43c8
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Icons/T_TB_Import.png
uploadId: 939664
Binary file not shown.
@@ -0,0 +1,176 @@
fileFormatVersion: 2
guid: d7da2c0fbbbc7469ca79b005587b60f3
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 2
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: tvOS
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Editor/Icons/T_TB_Manual.png
uploadId: 939664
Binary file not shown.
Loaded 100 of 999 files, more files were not shown because too many files have changed in this diff. Show more