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

@@ -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