Kursach_StealthAction/Assets/Tremble/Editor/EditorUI/MapHierarchyIcons.cs
2026-10-11 19:16:11 +03:00

141 lines
3.8 KiB
C#

//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.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";
}
}
}