/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using MessagePack; using Newtonsoft.Json; using Unity.Mathematics; using UnityEditor; using UnityEngine; using static Unity.Mathematics.math; using static UnityEditor.EditorGUILayout; using int2 = Unity.Mathematics.int2; using Object = UnityEngine.Object; public class DatabaseInspector : EditorWindow { public static DatabaseCache DatabaseCache; public DatabaseEntry entry; // Singleton to avoid multiple instances of window. private static DatabaseInspector _instance; public static DatabaseInspector Instance => _instance ? _instance : GetWindow(); private DatabaseListView _list; private GUIStyle _labelStyle; private Vector2 _view; private const float IconSize = 1.0f / 16; // private static HardpointData _hardpoint; private static float2 _dragOffset; private bool _ctrlDown; private string[] _hardpointTypes; private const int width = 150; private const int labelWidth = 20; private const int toggleWidth = 18; private const int arrowWidth = 22; private Material _galaxyMat; private Texture2D _white; private GUIStyle _warning; private HashSet _listItemFoldouts = new HashSet(); private Material _schematicMat; public Color LabelColor => EditorGUIUtility.isProSkin ? Color.white : Color.black; void OnEnable() { _instance = this; _list = DatabaseListView.Instance; _list.Show(); DatabaseCache.OnDataUpdateLocal += _ => Repaint(); DatabaseCache.OnDataUpdateRemote += _ => EditorDispatcher.Dispatch(Repaint); wantsMouseMove = true; _hardpointTypes = Enum.GetNames(typeof(HardpointType)); _galaxyMat = new Material(Shader.Find("Unlit/GalaxyMap")); _schematicMat = new Material(Shader.Find("Legacy Shaders/Particles/Additive")); _white = Color.white.ToTexture(); _warning = new GUIStyle(EditorStyles.boldLabel); _warning.normal.textColor = Color.red; } public void Inspect(object obj, bool inspectablesOnly = false) { foreach (var field in obj.GetType().GetFields().OrderBy(f=>f.GetCustomAttribute()?.IntKey ?? 0)) { Inspect(obj, field, inspectablesOnly); } if (obj is EquippableItemData equippableItemData) { var restricted = false; var doubleRestricted = false; HullType type = HullType.Ship; foreach (var behavior in equippableItemData.Behaviors) { var restriction = behavior.GetType().GetCustomAttribute(); if (restriction != null) { if (restricted && restriction.Type != type) doubleRestricted = true; restricted = true; type = restriction.Type; } } if(doubleRestricted) GUILayout.Label("ITEM UNEQUIPPABLE: BEHAVIOR HULL TYPE CONFLICT", _warning); else if (restricted) GUILayout.Label($"Item restricted by behavior to hull type {Enum.GetName(typeof(HullType), type)}", EditorStyles.boldLabel); } } public void Inspect(object obj, FieldInfo field, bool inspectablesOnly = false) { var inspectable = field.GetCustomAttribute(); if (inspectable == null && inspectablesOnly) return; var type = field.FieldType; var value = field.GetValue(obj); var link = inspectable as InspectableDatabaseLinkAttribute; if (type == typeof(float)) { if (inspectable is RangedFloatInspectableAttribute) { var range = inspectable as RangedFloatInspectableAttribute; field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (float) value, range.Min, range.Max)); } else if (inspectable is InspectableTemperatureAttribute) field.SetValue(obj, InspectTemperature(field.Name.SplitCamelCase(), (float) value)); else field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (float) value)); } else if (type == typeof(float4[]) && inspectable is InspectableAnimationCurveAttribute) { field.SetValue(obj, InspectAnimationCurve(field.Name.SplitCamelCase(), (float4[]) value)); } else if (type == typeof(int)) { if (inspectable is RangedIntInspectableAttribute) { var range = inspectable as RangedIntInspectableAttribute; field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (int) value, range.Min, range.Max)); } else field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (int) value)); } else if (type == typeof(int2)) { field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (int2) field.GetValue(obj))); } else if (type == typeof(float3)) { if (inspectable is InspectableColorAttribute) { field.SetValue(obj, ColorField(field.Name.SplitCamelCase(), ((float3)field.GetValue(obj)).ToColor()).ToFloat3()); } } else if (type.IsEnum) { var isflags = type.GetCustomAttributes().Any(); var names = Enum.GetNames(field.FieldType); field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (int) field.GetValue(obj), isflags ? names.Skip(1).ToArray() : names, isflags)); } //else if (field.FieldType == typeof(Color)) Inspect(field.Name, () => (Color) field.GetValue(obj), c => field.SetValue(obj, c)); else if (type == typeof(bool)) field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (bool) value)); else if (type == typeof(string)) { if (inspectable is InspectableUnityObjectAttribute unityObjectAttribute) { var inspectMethod = typeof(DatabaseInspector).GetMethod("InspectUnityObject"); var generic = inspectMethod.MakeGenericMethod(unityObjectAttribute.ObjectType); field.SetValue(obj, generic.Invoke(this, new[] {value})); } if(inspectable is InspectablePrefabAttribute) field.SetValue(obj, InspectUnityObject(field.Name.SplitCamelCase(), (string) value)); else if(inspectable is InspectableTextureAttribute) field.SetValue(obj, InspectUnityObject(field.Name.SplitCamelCase(), (string) value)); else if(inspectable is InspectableTextAssetAttribute) field.SetValue(obj, InspectUnityObject(field.Name.SplitCamelCase(), (string) value)); else field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (string) value, inspectable is InspectableTextAttribute)); } else if (type == typeof(Type) && inspectable is InspectableTypeAttribute typeAttribute) field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (Type) value, typeAttribute.Type)); else if (type == typeof(GameObject)) field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (GameObject) value)); else if (type == typeof(AnimationCurve)) field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (AnimationCurve) value)); else if (type == typeof(Shape)) field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (Shape) value, (obj is HullData hull) ? hull : null)); else if (type == typeof(PerformanceStat)) field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (PerformanceStat) value, field.GetCustomAttribute() != null)); else if (type == typeof(PerformanceStat[])) { LabelField(field.Name.SplitCamelCase(), EditorStyles.boldLabel); using (new VerticalScope(GUI.skin.box)) { var stats = (PerformanceStat[]) value; var damageTypes = Enum.GetNames(typeof(DamageType)); if(stats==null || stats.Length!=damageTypes.Length) stats = new PerformanceStat[damageTypes.Length]; for (var i = 0; i < stats.Length; i++) { Inspect(damageTypes[i].SplitCamelCase(), stats[i]); } field.SetValue(obj, stats); } } else if (type == typeof(StatReference)) { var statRef = (StatReference) field.GetValue(obj); Inspect(field.Name.SplitCamelCase(), ref statRef); } else if (type == typeof(Dictionary)) { var dict = (Dictionary) field.GetValue(obj); Inspect(field.Name.SplitCamelCase(), ref dict, link.EntryType); } else if (type == typeof(Dictionary)) { var dict = (Dictionary) field.GetValue(obj); var ranged = field.GetCustomAttribute(); if(ranged != null) Inspect(field.Name.SplitCamelCase(), ref dict, link.EntryType, ranged.Min, ranged.Max); else Inspect(field.Name.SplitCamelCase(), ref dict, link.EntryType); } else if (type == typeof(List) && link != null) { var list = (List) field.GetValue(obj); if(list==null) field.SetValue(obj, list = new List()); Inspect(field.Name.SplitCamelCase(), ref list, link.EntryType); } else if (type == typeof(Guid) && link != null) field.SetValue(obj, Inspect(field.Name.SplitCamelCase(), (Guid) value, link.EntryType)); else if (type.GetCustomAttribute() != null) { Space(); using (new HorizontalScope()) { LabelField(field.Name.SplitCamelCase(), EditorStyles.boldLabel); if (type.IsInterface) { var subTypes = type.GetAllInterfaceClasses(); var selected = value != null ? Array.IndexOf(subTypes, value.GetType()) + 1 : 0; var newSelection = Popup(selected, subTypes.Select(t => t.Name.SplitCamelCase()).Prepend("None").ToArray()); if(newSelection != selected) field.SetValue(obj, value = newSelection==0?null:Activator.CreateInstance(subTypes[newSelection-1])); } else if(value == null) field.SetValue(obj, value = Activator.CreateInstance(type)); } if(value != null) Inspect(field.GetValue(obj), inspectablesOnly); } else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { var listType = field.FieldType.GenericTypeArguments[0]; // if (listType.IsEnum) // { // Space(); // using (new VerticalScope(GUI.skin.box)) // { // var list = (IList) field.GetValue(obj); // if (list == null) // { // list = (IList) Activator.CreateInstance(type); // field.SetValue(obj, list); // GUI.changed = true; // } // var isflags = type.GetCustomAttributes().Any(); // var names = Enum.GetNames(field.FieldType); // // for (var i = 0; i < list.Count; i++) // { // list[i] = Inspect(field.Name.SplitCamelCase(), (int) list[i], isflags ? names.Skip(1).ToArray() : names, isflags); // } // } // } if (listType.GetCustomAttribute() != null) { Space(); using (new VerticalScope(GUI.skin.box)) { var list = (IList) field.GetValue(obj); if (list == null) { list = (IList) Activator.CreateInstance(type); field.SetValue(obj, list); GUI.changed = true; } else { var sorted = true; var order = int.MinValue; foreach(var element in list) { var elementType = element.GetType(); var elementOrder = elementType.GetCustomAttribute()?.Order ?? 0; if (elementOrder < order) sorted = false; order = elementOrder; } if (!sorted) { var sortedList = list .Cast() .OrderBy(o => o.GetType().GetCustomAttribute()?.Order ?? 0); list = (IList) Activator.CreateInstance(type); foreach (var o in sortedList) list.Add(o); field.SetValue(obj, list); GUI.changed = true; } } foreach (var o in list) { if (o == null) { list.Remove(o); break; } bool _tinted = false; var originalColor = GUI.backgroundColor; if (o is ITintInspector tint) { _tinted = true; GUI.backgroundColor = tint.TintColor.ToColor(); } using (new VerticalScope(_list.ListItemStyle)) { using (new HorizontalScope()) { //if (listType.IsInterface) if(Foldout(_listItemFoldouts.Contains(o.GetHashCode()), o.ToString(), true)) _listItemFoldouts.Add(o.GetHashCode()); else _listItemFoldouts.Remove(o.GetHashCode()); var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0); if (GUI.Button(rect, GUIContent.none, GUIStyle.none)) { list.Remove(o); break; } } if(_listItemFoldouts.Contains(o.GetHashCode())) { Space(); Inspect(o, inspectablesOnly); } } if(_tinted) GUI.backgroundColor = originalColor; } using (new HorizontalScope(_list.ListItemStyle)) { GUILayout.Label($"Add new {listType.Name}", GUILayout.ExpandWidth(true)); var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); GUI.DrawTexture(rect, Icons.Instance.plus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0); if (GUI.Button(rect, GUIContent.none, GUIStyle.none)) { if (listType.IsInterface) { var menu = new GenericMenu(); foreach (var behaviorType in listType.GetAllInterfaceClasses()) { menu.AddItem(new GUIContent(behaviorType.Name), false, () => { list.Add(Activator.CreateInstance(behaviorType)); DatabaseCache.Add(entry); }); } menu.ShowAsContext(); } else if (listType.IsAbstract) { var menu = new GenericMenu(); foreach (var behaviorType in listType.GetAllChildClasses().Where(t=>!t.IsAbstract)) { menu.AddItem(new GUIContent(behaviorType.Name), false, () => { list.Add(Activator.CreateInstance(behaviorType)); DatabaseCache.Add(entry); }); } menu.ShowAsContext(); } else { list.Add(Activator.CreateInstance(listType)); } } } } } else if (typeof(Object).IsAssignableFrom(listType)) { Space(); using (var v = new VerticalScope(GUI.skin.box)) { var list = (IList) field.GetValue(obj); if (list == null) { list = (IList) Activator.CreateInstance(type); field.SetValue(obj, list); } if(list.Count==0) using (new HorizontalScope(_list.ListItemStyle)) GUILayout.Label($"Add a {listType.Name} by dragging from the project hierarchy."); foreach (var oo in list) { var o = (Object) oo; using (new HorizontalScope(_list.ListItemStyle)) { GUILayout.Label(o.name.SplitCamelCase(), EditorStyles.boldLabel, GUILayout.ExpandWidth(true)); var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0); if (GUI.Button(rect, GUIContent.none, GUIStyle.none)) { list.Remove(o); break; } } } if (v.rect.Contains(Event.current.mousePosition) && (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform)) { var dragObj = DragAndDrop.objectReferences.FirstOrDefault(); //var dragEntry = Database.Get(guid); if(Event.current.type == EventType.DragUpdated) DragAndDrop.visualMode = dragObj?.GetType() == listType ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected; else if(Event.current.type == EventType.DragPerform) { if (dragObj?.GetType() == listType) { DragAndDrop.AcceptDrag(); GUI.changed = true; list.Add(dragObj); } } } } } else Debug.Log($"Field \"{field.Name}\" is a list of non-Inspectable type {listType.Name}. No inspector was generated."); } else if (typeof(Object).IsAssignableFrom(type)) { using (new HorizontalScope()) { GUILayout.Label(field.Name.SplitCamelCase(), GUILayout.Width(width)); field.SetValue(obj, ObjectField((Object)field.GetValue(obj),type,false)); } } // else if (type == typeof(AudioClip)) // { // using (new EditorGUILayout.HorizontalScope()) // { // GUILayout.Label(field.Name.SplitCamelCase(), GUILayout.Width(width)); // field.SetValue(obj, EditorGUILayout.ObjectField((AudioClip)field.GetValue(obj),typeof(AudioClip),false)); // } // } else Debug.Log($"Field \"{field.Name}\" has unknown type {field.FieldType.Name}. No inspector was generated."); } public bool Inspect(string label, bool value) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); return Toggle(value); } } public float Inspect(string label, float value) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); return DelayedFloatField(value); } } public string Inspect(string label, string value, bool area = false) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); if (area) { EditorStyles.textArea.wordWrap = true; return DelayedTextField(value, EditorStyles.textArea, /*GUILayout.Width(EditorGUIUtility.currentViewWidth-width-10), */GUILayout.Height(100)); } return DelayedTextField(value); } } public float InspectTemperature(string label, float value) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); GUILayout.Label("°K", _labelStyle, GUILayout.Width(labelWidth)); value = DelayedFloatField(value);//, GUILayout.ExpandWidth(false) GUILayout.Label("°C", _labelStyle, GUILayout.Width(labelWidth)); value = DelayedFloatField(value - 273.15f) + 273.15f; GUILayout.Label("°F", _labelStyle, GUILayout.Width(labelWidth)); value = (DelayedFloatField((value - 273.15f) * 1.8f + 32) - 32) / 1.8f + 273.15f; return value; } } public float Inspect(string label, float value, float min, float max) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); return Slider(value, min, max); } } public int Inspect(string label, int value) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); return DelayedIntField(value); } } public int2 Inspect(string label, int2 value) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); GUILayout.Label("X", GUILayout.Width(labelWidth)); value.x = DelayedIntField(value.x); GUILayout.Label("Y", GUILayout.Width(labelWidth)); value.y = DelayedIntField(value.y); if (GUILayout.Button("\u2190", GUILayout.Width(arrowWidth))) value.x--; if (GUILayout.Button("\u2192", GUILayout.Width(arrowWidth))) value.x++; if (GUILayout.Button("\u2191", GUILayout.Width(arrowWidth))) value.y++; if (GUILayout.Button("\u2193", GUILayout.Width(arrowWidth))) value.y--; return value; } } public int Inspect(string label, int value, int min, int max) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); return IntSlider(value, min, max); } } public int Inspect(string label, int value, string[] enumOptions, bool flags) { if (flags) { Space(); LabelField(label, EditorStyles.boldLabel); using (var v = new VerticalScope(GUI.skin.box)) { for (var i = 0; i < enumOptions.Length; i++) { using (var h = new HorizontalScope()) { GUILayout.Label(enumOptions[i], GUILayout.Width(width)); var set = Toggle(1 << i == (1 << i & value)); if (set) value |= 1 << i; else value &= ~(1 << i); } } } return value; } using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); return Popup(value, enumOptions); } } public Type Inspect(string label, Type value, Type parentType) { var types = parentType.GetAllChildClasses(); var enumOptions = new[] {"None"}.Concat(types.Select(t => t.Name)).ToArray(); var index = Array.IndexOf(types, value) + 1; using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); index = Popup(index, enumOptions); } return index == 0 ? null : types[index - 1]; } public string InspectUnityObject(string label, string value) where T : Object { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); return AssetDatabase.GetAssetPath(ObjectField(AssetDatabase.LoadAssetAtPath(value), typeof(T), false)); } } public GameObject Inspect(string label, GameObject value) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); return (GameObject) ObjectField(value, typeof(GameObject), false); } } public float4[] InspectAnimationCurve(string label, float4[] value) { var val = value != null && value.Length > 0 ? value.ToCurve() : new AnimationCurve(); using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); val = CurveField(val, Color.yellow, new Rect(0, 0, 1, 1)); } return val.keys.Select(k => float4(k.time, k.value, k.inTangent, k.outTangent)).ToArray(); } public AnimationCurve Inspect(string label, AnimationCurve value) { using (var h = new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); if (value == null) value = new AnimationCurve(); value = CurveField(value, Color.yellow, new Rect(0, 0, 1, 1)); } return value; } public Guid Inspect(string label, Guid value, Type entryType) { LabelField(label, EditorStyles.boldLabel); var valueEntry = DatabaseCache.Get(value); using (var h = new HorizontalScope(GUI.skin.box)) { GUILayout.Label((valueEntry as INamedEntry)?.EntryName ?? valueEntry?.ID.ToString() ?? $"Assign a {entryType.Name} by dragging from the list panel."); if (h.rect.Contains(Event.current.mousePosition) && (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform)) { var guid = (Guid) DragAndDrop.GetGenericData("Item"); var dragEntry = DatabaseCache.Get(guid); var dragValid = dragEntry != null && entryType.IsInstanceOfType(dragEntry) && dragEntry != entry; if(Event.current.type == EventType.DragUpdated) DragAndDrop.visualMode = dragValid ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected; else if(Event.current.type == EventType.DragPerform) { if (dragValid) { DragAndDrop.AcceptDrag(); GUI.changed = true; return guid; } } } var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0); if (GUI.Button(rect, GUIContent.none, GUIStyle.none)) { return Guid.Empty; } } return value; } public void Inspect(string label, ref Dictionary value, Type referenceType) { Space(); LabelField(label, EditorStyles.boldLabel); using (var v = new VerticalScope(GUI.skin.box)) { if (value.Count == 0) { using (var h = new HorizontalScope(_list.ListItemStyle)) { GUILayout.Label("Drag from list to add item"); } } foreach (var ingredient in value.ToArray()) { using (var h = new HorizontalScope(_list.ListItemStyle)) { var entry = DatabaseCache.Get(ingredient.Key); if (entry == null) { value.Remove(ingredient.Key); GUI.changed = true; return; } if(entry is INamedEntry named) GUILayout.Label(named.EntryName); else GUILayout.Label(entry.ID.ToString()); value[ingredient.Key] = DelayedIntField(value[ingredient.Key], GUILayout.Width(50)); var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0); if (GUI.Button(rect, GUIContent.none, GUIStyle.none) || value[ingredient.Key]==0) value.Remove(ingredient.Key); } } if (v.rect.Contains(Event.current.mousePosition)) { var dragData = DragAndDrop.GetGenericData("Item"); var isId = dragData is Guid guid; var dragEntry = isId ? DatabaseCache.Get(guid) : null; var correctType = referenceType.IsInstanceOfType(dragEntry); if(Event.current.type == EventType.DragUpdated) DragAndDrop.visualMode = correctType ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected; else if(Event.current.type == EventType.DragPerform) { if (isId && correctType) { DragAndDrop.AcceptDrag(); value[guid] = 1; GUI.changed = true; } } } } } public void Inspect(string label, ref Dictionary value, Type referenceType) { Space(); LabelField(label, EditorStyles.boldLabel); using (var v = new VerticalScope(GUI.skin.box)) { if (value.Count == 0) { using (var h = new HorizontalScope(_list.ListItemStyle)) { GUILayout.Label("Drag from list to add item"); } } foreach (var ingredient in value.ToArray()) { using (var h = new HorizontalScope(_list.ListItemStyle)) { var entry = DatabaseCache.Get(ingredient.Key); if (entry == null) { value.Remove(ingredient.Key); GUI.changed = true; return; } if(entry is INamedEntry named) GUILayout.Label(named.EntryName); else GUILayout.Label(entry.ID.ToString()); value[ingredient.Key] = DelayedFloatField(value[ingredient.Key], GUILayout.Width(50)); var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0); if (GUI.Button(rect, GUIContent.none, GUIStyle.none)) value.Remove(ingredient.Key); } } if (v.rect.Contains(Event.current.mousePosition)) { var dragData = DragAndDrop.GetGenericData("Item"); var isId = dragData is Guid guid; var dragEntry = isId ? DatabaseCache.Get(guid) : null; var correctType = referenceType.IsInstanceOfType(dragEntry); if(Event.current.type == EventType.DragUpdated) DragAndDrop.visualMode = correctType ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected; else if(Event.current.type == EventType.DragPerform) { if (isId && correctType) { DragAndDrop.AcceptDrag(); value[guid] = 1; GUI.changed = true; } } } } } public void Inspect(string label, ref Dictionary value, Type referenceType, float min, float max) { Space(); LabelField(label, EditorStyles.boldLabel); using (var v = new VerticalScope(GUI.skin.box)) { if (value.Count == 0) { using (var h = new HorizontalScope(_list.ListItemStyle)) { GUILayout.Label("Drag from list to add item"); } } foreach (var ingredient in value.ToArray()) { using (var h = new HorizontalScope(_list.ListItemStyle)) { var entry = DatabaseCache.Get(ingredient.Key); if (entry == null) { value.Remove(ingredient.Key); GUI.changed = true; return; } if(entry is INamedEntry named) GUILayout.Label(named.EntryName); else GUILayout.Label(entry.ID.ToString()); value[ingredient.Key] = Slider(value[ingredient.Key], min, max); var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0); if (GUI.Button(rect, GUIContent.none, GUIStyle.none)) value.Remove(ingredient.Key); } } if (v.rect.Contains(Event.current.mousePosition)) { var dragData = DragAndDrop.GetGenericData("Item"); var isId = dragData is Guid guid; var dragEntry = isId ? DatabaseCache.Get(guid) : null; var correctType = referenceType.IsInstanceOfType(dragEntry); if(Event.current.type == EventType.DragUpdated) DragAndDrop.visualMode = correctType ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected; else if(Event.current.type == EventType.DragPerform) { if (isId && correctType) { DragAndDrop.AcceptDrag(); value[guid] = max; GUI.changed = true; } } } } } public void Inspect(string label, ref List value, Type entryType) { Space(); LabelField(label, EditorStyles.boldLabel); using (var v = new VerticalScope(GUI.skin.box)) { if (value.Count == 0) { using (var h = new HorizontalScope(_list.ListItemStyle)) { GUILayout.Label($"Drag from list to add {entryType.Name}"); } } foreach (var guid in value.ToArray()) { using (var h = new HorizontalScope(_list.ListItemStyle)) { var entry = DatabaseCache.Get(guid); if (entry == null) { value.Remove(guid); GUI.changed = true; } else { GUILayout.Label((entry as INamedEntry)?.EntryName ?? entry.ID.ToString()); var rect = GetControlRect(false, GUILayout.Width(EditorGUIUtility.singleLineHeight)); GUI.DrawTexture(rect, Icons.Instance.minus, ScaleMode.StretchToFill, true, 1, LabelColor, 0, 0); if (GUI.Button(rect, GUIContent.none, GUIStyle.none)) { value.Remove(guid); } } } } if (v.rect.Contains(Event.current.mousePosition)) { var guid = DragAndDrop.GetGenericData("Item"); var dragObj = DragAndDrop.GetGenericData("Item") is Guid ? DatabaseCache.Get((Guid) guid) : null; var dragValid = entryType.IsInstanceOfType(dragObj) && dragObj != entry; if(Event.current.type == EventType.DragUpdated) DragAndDrop.visualMode = dragValid ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected; else if(Event.current.type == EventType.DragPerform) { if (dragValid) { DragAndDrop.AcceptDrag(); value.Add((Guid) guid); GUI.changed = true; } } } } } public Shape Inspect(string label, Shape value, HullData hull = null) { if(value == null) { value = new Shape(1, 1); value[int2.zero] = true; } using (new VerticalScope()) { Texture2D schematic = null; if(hull != null && !string.IsNullOrEmpty(hull.Schematic)) schematic = AssetDatabase.LoadAssetAtPath(hull.Schematic); using (new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); GUILayout.Label("Size", GUILayout.ExpandWidth(false)); if(schematic == null) { GUILayout.Label("X", GUILayout.Width(labelWidth)); value.Width = DelayedIntField(value.Width); GUILayout.Label("Y", GUILayout.Width(labelWidth)); value.Height = DelayedIntField(value.Height); } else { GUILayout.Label("X", GUILayout.Width(labelWidth)); value.Width = DelayedIntField(value.Width); value.Height = Mathf.RoundToInt(value.Width * ((float)schematic.height / schematic.width)); GUILayout.Label("Y", GUILayout.Width(labelWidth)); LabelField($"{value.Height}"); } } using (new HorizontalScope()) { GUILayout.Label("", GUILayout.Width(width)); using (var a = new VerticalScope()) { if(schematic != null) { EditorGUI.DrawPreviewTexture(a.rect, schematic, _schematicMat); } for (var y = value.Height - 1; y >= 0; y--) { using (new HorizontalScope()) { for (var x = 0; x < value.Width; x++) { var oldColor = GUI.backgroundColor; if(hull != null) { var hardpoint = hull.Hardpoints .FirstOrDefault(hp => hp.Shape.Coordinates .Select(v => v + hp.Position) .Any(v => v.x == x && v.y == y)); if (hardpoint != null) GUI.backgroundColor = hardpoint.TintColor.ToColor(); } value[int2(x, y)] = Toggle(value[int2(x, y)], GUILayout.Width(toggleWidth)); GUI.backgroundColor = oldColor; } } } } GUILayout.FlexibleSpace(); } } return value; } public void Inspect(string label, ref StatReference effect) { using (new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); using (new VerticalScope()) { var targetObjects = typeof(BehaviorData).GetAllChildClasses() .Concat(typeof(EquippableItemData).GetAllChildClasses()).ToArray(); var objectNames = targetObjects.Select(b => b.Name).ToArray(); var selectedIndex = Array.IndexOf(objectNames, effect.Target); if (selectedIndex == -1) { selectedIndex = 0; GUI.changed = true; } using (new HorizontalScope()) { GUILayout.Label("Target", GUILayout.Width(width)); var newSelection = Popup(selectedIndex, objectNames); if (newSelection != selectedIndex) GUI.changed = true; effect.Target = objectNames[newSelection]; } using (new HorizontalScope()) { GUILayout.Label("Stat", GUILayout.Width(width)); var stats = targetObjects[selectedIndex].GetFields() .Where(f => f.FieldType == typeof(PerformanceStat)).ToArray(); if(stats.Length==0) GUILayout.Label("No Stats!"); else { var statNames = stats.Select(s => s.Name).ToArray(); var selectedStatIndex = Array.IndexOf(statNames, effect.Stat); if (selectedStatIndex == -1) { selectedStatIndex = 0; GUI.changed = true; } var newSelection = Popup(selectedStatIndex, statNames); if (newSelection != selectedStatIndex) GUI.changed = true; effect.Stat = statNames[newSelection]; } } } } } public PerformanceStat Inspect(string label, PerformanceStat value, bool isSimple = false) { if(value == null) value = new PerformanceStat(); using (new VerticalScope()) { using (new HorizontalScope()) { GUILayout.Label(label, GUILayout.Width(width)); GUILayout.Label("Min", _labelStyle, GUILayout.Width(labelWidth)); value.Min = DelayedFloatField(value.Min); GUILayout.Label("Max", _labelStyle, GUILayout.Width(labelWidth + 5)); value.Max = DelayedFloatField(value.Max); } // Only display heat, durability and quality effects if the stat actually varies if (Math.Abs(value.Min - value.Max) > .0001f) { using (new HorizontalScope()) { GUILayout.Label("", GUILayout.Width(width)); if (!isSimple) { GUILayout.Label("H", _labelStyle, GUILayout.Width(labelWidth)); value.HeatExponentMultiplier = DelayedFloatField(value.HeatExponentMultiplier); GUILayout.Label("D", _labelStyle, GUILayout.Width(labelWidth)); value.DurabilityExponentMultiplier = DelayedFloatField(value.DurabilityExponentMultiplier); GUILayout.Label("Q", _labelStyle, GUILayout.Width(labelWidth)); value.QualityExponent = DelayedFloatField(value.QualityExponent); } else { GUILayout.Label("Quality Exponent", _labelStyle); value.QualityExponent = DelayedFloatField(value.QualityExponent); } } } // using (new HorizontalScope()) // { // GUILayout.Label("", GUILayout.Width(width)); // GUILayout.Label("Ingredient", GUILayout.ExpandWidth(false)); // var ingredients = crafted.Ingredients.Keys.Select(DatabaseCache.Get).Where(i => i is CraftedItemData).Cast().ToList(); // var ingredient = value.Ingredient != null ? DatabaseCache.Get(value.Ingredient.Value) as CraftedItemData : null; // var names = ingredients.Select(i => i.Name).Append("None").ToArray(); // var selected = value.Ingredient == null || !crafted.Ingredients.ContainsKey(value.Ingredient.Value) // ? names.Length - 1 // : ingredients.IndexOf(ingredient); // var selection = Popup(selected, names); // value.Ingredient = selection == names.Length - 1 ? (Guid?) null : ingredients[selection].ID; // } Space(); } return value; } public void OnGUI() { _labelStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel) {normal = {textColor = LabelColor}}; Event current = Event.current; EventType currentEventType = current.type; if (currentEventType == EventType.KeyDown && current.keyCode == KeyCode.LeftControl) _ctrlDown = true; if (currentEventType == EventType.KeyUp && current.keyCode == KeyCode.LeftControl) _ctrlDown = false; if (entry==null) { LabelField($"{(_list.SelectedItem==Guid.Empty?"No Entry Selected":"Selected Entry Not Found in Database")}\n{_list.SelectedItem}"); return; } if(entry is INamedEntry namedEntry) GUILayout.Label(namedEntry.EntryName, EditorStyles.boldLabel); _view = BeginScrollView( _view, false, false, GUIStyle.none, GUI.skin.verticalScrollbar, GUI.skin.scrollView, GUILayout.Width(EditorGUIUtility.currentViewWidth), GUILayout.ExpandHeight(true)); // Serialize inspected object so we can check whether it has actually changed RegisterResolver.Register(); var previousBytes = MessagePackSerializer.Serialize(entry); EditorGUI.BeginChangeCheck(); var dataEntry = entry; using (new HorizontalScope()) { if(GUILayout.Button("Print JSON")) Debug.Log(entry.ToJson()); // if (GUILayout.Button("Duplicate")) // { // DatabaseCache.Duplicate(entry); // } if (GUILayout.Button("Delete Entry")) { DatabaseCache.Delete(entry); entry = null; return; } if (GUILayout.Button("Clone Entry")) { var copy = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(entry)); copy.ID = Guid.NewGuid(); DatabaseCache.Add(copy); } } using (var h = new HorizontalScope()) { GUILayout.Label("ID"); LabelField(dataEntry.ID.ToString()); } Inspect(entry, true); #region Hull var hull = entry as HullData; if (hull != null) { Space(); // TODO: Hull Hardpoints Inspector } #endregion if (EditorGUI.EndChangeCheck()) { if(!previousBytes.ByteEquals(MessagePackSerializer.Serialize(entry))) DatabaseCache.Add(entry); } EndScrollView(); } }