removed a ton of xml comments and such

This commit is contained in:
Sebastian Bularca
2026-04-19 12:46:26 +02:00
parent 8ce041e2d8
commit d05641c979
25 changed files with 221 additions and 367 deletions

View File

@@ -3,50 +3,39 @@ using UnityEditor;
using UnityEngine;
namespace Jovian.EncounterSystem.Editor {
/// <summary>
/// Drawer for <see cref="DialogLineRef"/>. Shows the library + id picker on one row, the inline
/// fallback text area underneath, and a resolved preview below that. Preserves WYSIWYG — the
/// final text is always visible.
/// </summary>
/// <summary>Id dropdown (library inherited from the owning asset's <c>library</c> field) + inline fallback + resolved preview.</summary>
[CustomPropertyDrawer(typeof(DialogLineRef))]
public class DialogLineRefDrawer : PropertyDrawer {
private const string NonePlaceholder = "<none>";
private const string EmptyLibraryPlaceholder = "<assign a library first>";
private const string EmptyLibraryPlaceholder = "<set library on the parent asset>";
private const float PreviewHeight = 32f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
var libraryProp = property.FindPropertyRelative("library");
var idProp = property.FindPropertyRelative("id");
var inlineProp = property.FindPropertyRelative("inlineText");
var library = ResolveLibrary(property);
EditorGUI.BeginProperty(position, label, property);
var lineHeight = EditorGUIUtility.singleLineHeight;
var spacing = EditorGUIUtility.standardVerticalSpacing;
var libraryRect = new Rect(position.x, position.y, position.width, lineHeight);
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(libraryRect, libraryProp, label);
var libraryChanged = EditorGUI.EndChangeCheck();
var idRect = new Rect(position.x, position.y, position.width, lineHeight);
DrawIdPicker(idRect, library, idProp, label);
using(new EditorGUI.IndentLevelScope()) {
var idRect = new Rect(position.x, position.y + lineHeight + spacing, position.width, lineHeight);
DrawIdPicker(idRect, libraryProp, idProp, libraryChanged);
var inlineRect = new Rect(
position.x,
position.y + lineHeight + spacing,
position.width,
lineHeight * 2);
EditorGUI.PropertyField(inlineRect, inlineProp, new GUIContent("Inline"));
var inlineRect = new Rect(
position.x,
position.y + (lineHeight + spacing) * 2,
position.width,
lineHeight * 2);
EditorGUI.PropertyField(inlineRect, inlineProp, new GUIContent("Inline"));
var previewRect = new Rect(
position.x,
position.y + (lineHeight + spacing) * 2 + lineHeight * 2 + spacing,
position.width,
PreviewHeight);
DrawPreview(previewRect, libraryProp, idProp, inlineProp);
}
var previewRect = new Rect(
position.x,
position.y + lineHeight + spacing + lineHeight * 2 + spacing,
position.width,
PreviewHeight);
DrawPreview(previewRect, library, idProp, inlineProp);
EditorGUI.EndProperty();
}
@@ -54,18 +43,19 @@ namespace Jovian.EncounterSystem.Editor {
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
var lineHeight = EditorGUIUtility.singleLineHeight;
var spacing = EditorGUIUtility.standardVerticalSpacing;
// library + id + (inline 2 lines) + preview
return (lineHeight + spacing) * 2 + lineHeight * 2 + spacing + PreviewHeight;
// id + (inline 2 lines) + preview
return lineHeight + spacing + lineHeight * 2 + spacing + PreviewHeight;
}
private static void DrawIdPicker(Rect rect, SerializedProperty libraryProp, SerializedProperty idProp, bool libraryChanged) {
var library = libraryProp.objectReferenceValue as DialogLineLibrary;
private static DialogLineLibrary ResolveLibrary(SerializedProperty property) {
var libraryProp = property.serializedObject.FindProperty("library");
return libraryProp?.objectReferenceValue as DialogLineLibrary;
}
private static void DrawIdPicker(Rect rect, DialogLineLibrary library, SerializedProperty idProp, GUIContent label) {
if(library == null || library.lines == null || library.lines.Count == 0) {
using(new EditorGUI.DisabledScope(true)) {
EditorGUI.Popup(rect, "Id", 0, new[] { EmptyLibraryPlaceholder });
}
if(libraryChanged) {
idProp.stringValue = string.Empty;
EditorGUI.Popup(rect, label.text, 0, new[] { EmptyLibraryPlaceholder });
}
return;
}
@@ -82,24 +72,18 @@ namespace Jovian.EncounterSystem.Editor {
var id = line?.id ?? string.Empty;
ids[i + 1] = id;
names[i + 1] = string.IsNullOrEmpty(id) ? $"<unnamed {i}>" : id;
if(!libraryChanged && id == idProp.stringValue && !string.IsNullOrEmpty(id)) {
if(id == idProp.stringValue && !string.IsNullOrEmpty(id)) {
currentIndex = i + 1;
}
}
if(libraryChanged) {
idProp.stringValue = string.Empty;
currentIndex = 0;
}
var newIndex = EditorGUI.Popup(rect, "Id", currentIndex, names);
var newIndex = EditorGUI.Popup(rect, label.text, currentIndex, names);
if(newIndex != currentIndex) {
idProp.stringValue = ids[newIndex];
}
}
private static void DrawPreview(Rect rect, SerializedProperty libraryProp, SerializedProperty idProp, SerializedProperty inlineProp) {
var library = libraryProp.objectReferenceValue as DialogLineLibrary;
private static void DrawPreview(Rect rect, DialogLineLibrary library, SerializedProperty idProp, SerializedProperty inlineProp) {
var id = idProp.stringValue;
var inline = inlineProp.stringValue;

View File

@@ -8,11 +8,7 @@ using UnityEngine;
using UnityEngine.UIElements;
namespace Jovian.EncounterSystem.Editor {
/// <summary>
/// Designer-facing browser for every <see cref="IEncounter"/> authored across all
/// <see cref="EncounterTable"/> assets in the project. Virtualised ListView on the left,
/// property-field detail pane on the right. Search by id/name/description; filter by kind.
/// </summary>
/// <summary>Browser for every encounter across all tables. Search + kind filter + detail pane. Quest chains render as a tree rooted at the first step.</summary>
public class EncounterBrowserWindow : EditorWindow {
private const string AllKinds = "All";
@@ -23,13 +19,12 @@ namespace Jovian.EncounterSystem.Editor {
}
private readonly List<Record> allRecords = new();
private List<Record> filteredRecords = new();
private string searchText = string.Empty;
private string kindFilter = AllKinds;
private readonly Dictionary<IEncounter, List<ValidationIssue>> issuesByEncounter = new();
private ListView listView;
private TreeView treeView;
private VisualElement detailPane;
private ToolbarMenu kindDropdown;
@@ -78,15 +73,15 @@ namespace Jovian.EncounterSystem.Editor {
split.style.flexGrow = 1f;
rootVisualElement.Add(split);
listView = new ListView {
treeView = new TreeView {
makeItem = MakeRow,
bindItem = BindRow,
fixedItemHeight = 22,
selectionType = SelectionType.Single
};
listView.selectionChanged += OnSelectionChanged;
listView.style.flexGrow = 1f;
split.Add(listView);
treeView.selectionChanged += OnSelectionChanged;
treeView.style.flexGrow = 1f;
split.Add(treeView);
detailPane = new ScrollView(ScrollViewMode.Vertical) {
style = { paddingLeft = 8, paddingTop = 8, paddingRight = 8, flexGrow = 1f }
@@ -134,7 +129,7 @@ namespace Jovian.EncounterSystem.Editor {
}
private void BindRow(VisualElement element, int index) {
var record = filteredRecords[index];
var record = treeView.GetItemDataForIndex<Record>(index);
var label = element.Q<Label>("row-label");
var badge = element.Q<VisualElement>("issue-badge");
@@ -222,15 +217,73 @@ namespace Jovian.EncounterSystem.Editor {
}
private void ApplyFilter() {
filteredRecords = allRecords.Where(Matches).ToList();
if(listView != null) {
listView.itemsSource = filteredRecords;
listView.Rebuild();
listView.ClearSelection();
var filtered = allRecords.Where(Matches).ToList();
var items = BuildTreeItems(filtered);
if(treeView != null) {
treeView.SetRootItems(items);
treeView.Rebuild();
treeView.ClearSelection();
treeView.ExpandAll();
}
ShowEmptyDetail();
}
private static List<TreeViewItemData<Record>> BuildTreeItems(List<Record> records) {
var byEncounter = new Dictionary<IEncounter, Record>();
foreach(var r in records) {
if(r.encounter != null) {
byEncounter[r.encounter] = r;
}
}
// Any encounter that is a `nextEncounter` target of another record in the filtered set
// becomes a non-root (rendered as a child), not a top-level item.
var nonRoot = new HashSet<IEncounter>();
foreach(var r in records) {
if(r.encounter?.Kind is not QuestKind quest) {
continue;
}
var next = quest.nextEncounter.Resolve();
if(next != null && byEncounter.ContainsKey(next)) {
nonRoot.Add(next);
}
}
var result = new List<TreeViewItemData<Record>>();
var uid = 0;
foreach(var r in records) {
if(r.encounter != null && nonRoot.Contains(r.encounter)) {
continue;
}
var children = BuildChainChildren(r, byEncounter, ref uid);
result.Add(new TreeViewItemData<Record>(uid++, r, children));
}
return result;
}
private static List<TreeViewItemData<Record>> BuildChainChildren(Record root, Dictionary<IEncounter, Record> byEncounter, ref int uid) {
var children = new List<TreeViewItemData<Record>>();
if(root.encounter?.Kind is not QuestKind) {
return children;
}
var current = root.encounter;
var visited = new HashSet<IEncounter> { current };
while(current?.Kind is QuestKind quest) {
var next = quest.nextEncounter.Resolve();
if(next == null || !visited.Add(next)) {
break;
}
if(!byEncounter.TryGetValue(next, out var nextRecord)) {
break;
}
children.Add(new TreeViewItemData<Record>(uid++, nextRecord));
current = next;
}
return children;
}
private bool Matches(Record record) {
var kindName = record.encounter?.Kind?.GetType().Name ?? string.Empty;
if(kindFilter != AllKinds && kindName != kindFilter) {

View File

@@ -0,0 +1,76 @@
using Jovian.EncounterSystem;
using UnityEditor;
using UnityEngine;
namespace Jovian.EncounterSystem.Editor {
/// <summary>Label list elements with the option's text id (fallback: inline text preview, then default).</summary>
[CustomPropertyDrawer(typeof(EncounterDialogOption))]
public class EncounterDialogOptionDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
var displayLabel = ResolveLabel(property, label);
var foldoutRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, displayLabel, true);
if(!property.isExpanded) {
return;
}
EditorGUI.indentLevel++;
var y = position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
var iterator = property.Copy();
var end = iterator.GetEndProperty();
if(iterator.NextVisible(true)) {
while(!SerializedProperty.EqualContents(iterator, end)) {
var h = EditorGUI.GetPropertyHeight(iterator, true);
var r = new Rect(position.x, y, position.width, h);
EditorGUI.PropertyField(r, iterator, true);
y += h + EditorGUIUtility.standardVerticalSpacing;
if(!iterator.NextVisible(false)) {
break;
}
}
}
EditorGUI.indentLevel--;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
var height = EditorGUIUtility.singleLineHeight;
if(!property.isExpanded) {
return height;
}
var iterator = property.Copy();
var end = iterator.GetEndProperty();
if(iterator.NextVisible(true)) {
while(!SerializedProperty.EqualContents(iterator, end)) {
height += EditorGUI.GetPropertyHeight(iterator, true) + EditorGUIUtility.standardVerticalSpacing;
if(!iterator.NextVisible(false)) {
break;
}
}
}
return height;
}
private static GUIContent ResolveLabel(SerializedProperty property, GUIContent fallback) {
var textProp = property.FindPropertyRelative("text");
if(textProp == null) {
return fallback;
}
var id = textProp.FindPropertyRelative("id")?.stringValue;
if(!string.IsNullOrEmpty(id)) {
return new GUIContent(id);
}
var inline = textProp.FindPropertyRelative("inlineText")?.stringValue;
if(!string.IsNullOrEmpty(inline)) {
var preview = inline.Length > 40 ? inline.Substring(0, 40) + "…" : inline;
return new GUIContent(preview);
}
return fallback;
}
}
}

View File

@@ -3,11 +3,7 @@ using UnityEditor;
using UnityEngine;
namespace Jovian.EncounterSystem.Editor {
/// <summary>
/// Draws each <see cref="Encounter"/> list element with its <see cref="EncounterDefinition.id"/>
/// as the foldout label (falling back to <c>name</c>, then the default element label).
/// Children are iterated manually to avoid recursing into this drawer.
/// </summary>
/// <summary>Label list elements with the encounter id (fallback: name, then default).</summary>
[CustomPropertyDrawer(typeof(Encounter))]
public class EncounterDrawer : PropertyDrawer {
private const string DefinitionBackingField = "<EncounterDefinition>k__BackingField";

View File

@@ -3,11 +3,7 @@ using UnityEditor;
using UnityEngine;
namespace Jovian.EncounterSystem.Editor {
/// <summary>
/// Two-row drawer for <see cref="EncounterLink"/>. Row 1 is an asset object-field for the target
/// <see cref="EncounterTable"/>; row 2 is a dropdown of encounters inside that table labelled by
/// <c>EncounterDefinition.name</c>. Picking a different table clears the stored internalId.
/// </summary>
/// <summary>Table object-field + encounter dropdown picker. Changing tables clears the id.</summary>
[CustomPropertyDrawer(typeof(EncounterLink))]
public class EncounterLinkDrawer : PropertyDrawer {
private const string NonePlaceholder = "<none>";

View File

@@ -4,18 +4,11 @@ using UnityEditor;
using UnityEngine;
namespace Jovian.EncounterSystem.Editor {
/// <summary>Severity of a <see cref="ValidationIssue"/>.</summary>
public enum ValidationSeverity {
Warning,
Error
}
/// <summary>
/// One thing the validator found wrong. <see cref="asset"/> is the click-through target
/// (table, dialog option set, reward), <see cref="encounter"/> identifies the specific
/// encounter the issue pertains to when applicable, and <see cref="path"/> describes where
/// inside the asset the issue lives.
/// </summary>
public class ValidationIssue {
public Object asset;
public Encounter encounter;
@@ -24,13 +17,8 @@ namespace Jovian.EncounterSystem.Editor {
public string message;
}
/// <summary>
/// Scans every <see cref="EncounterTable"/> and <see cref="Reward"/> asset in the project and
/// returns the list of issues found. Intended to be driven from a menu item, the browser window,
/// or pre-commit tooling. Runs on demand — does not cache.
/// </summary>
/// <summary>Project-wide scan of encounter tables and rewards. Runs on demand, no caching.</summary>
public static class EncounterValidator {
/// <summary>Walk every encounter table and reward asset, returning all issues found.</summary>
public static List<ValidationIssue> ValidateProject() {
var issues = new List<ValidationIssue>();
@@ -48,7 +36,6 @@ namespace Jovian.EncounterSystem.Editor {
return issues;
}
/// <summary>Return the issues for a single encounter inside a table. Useful for per-row badges.</summary>
public static List<ValidationIssue> ValidateEncounter(EncounterTable table, int index) {
var issues = new List<ValidationIssue>();
if(table?.encounters == null || index < 0 || index >= table.encounters.Count) {
@@ -197,38 +184,39 @@ namespace Jovian.EncounterSystem.Editor {
}
private static void ValidateDialogLineRef(EncounterDialogOptionSet optionSet, Encounter encounter, string path, DialogLineRef lineRef, List<ValidationIssue> issues) {
var hasLibrary = lineRef.library != null;
var library = optionSet.library;
var hasId = !string.IsNullOrEmpty(lineRef.id);
var hasInline = !string.IsNullOrEmpty(lineRef.inlineText);
if(!hasLibrary && !hasInline) {
if(!hasId && !hasInline) {
issues.Add(new ValidationIssue {
asset = optionSet,
encounter = encounter,
path = path,
severity = ValidationSeverity.Warning,
message = "Dialog line is empty (no library reference and no inline text)."
message = "Dialog line is empty (no id and no inline text)."
});
return;
}
if(hasLibrary && hasId && string.IsNullOrEmpty(lineRef.library.Resolve(lineRef.id))) {
if(hasId && library == null) {
issues.Add(new ValidationIssue {
asset = optionSet,
encounter = encounter,
path = path,
severity = ValidationSeverity.Error,
message = $"DialogLineRef id '{lineRef.id}' not found in library '{lineRef.library.name}'."
message = $"DialogLineRef references id '{lineRef.id}' but the option set has no library assigned."
});
return;
}
if(hasLibrary && !hasId && !hasInline) {
if(hasId && library != null && string.IsNullOrEmpty(library.Resolve(lineRef.id))) {
issues.Add(new ValidationIssue {
asset = optionSet,
encounter = encounter,
path = path,
severity = ValidationSeverity.Warning,
message = "Library assigned but no id picked; will fall back to empty inline text."
severity = ValidationSeverity.Error,
message = $"DialogLineRef id '{lineRef.id}' not found in library '{library.name}'."
});
}
}
@@ -271,7 +259,6 @@ namespace Jovian.EncounterSystem.Editor {
}
}
/// <summary>Menu-driven runner that prints the validator report to the console with click-through asset pings.</summary>
public static class EncounterValidatorMenu {
[MenuItem("Jovian/Encounters/Validate All")]
public static void ValidateAll() {

View File

@@ -6,16 +6,7 @@ using UnityEditor;
using UnityEngine;
namespace Jovian.EncounterSystem.Editor {
/// <summary>
/// Custom drawer for any field marked <c>[SerializeReference, SubclassSelector]</c>. Renders a
/// "pick a concrete type" dropdown populated by reflection over the declared base type, then
/// draws the chosen instance's serialized children underneath. Works for single fields, arrays,
/// and <see cref="List{T}"/> elements.
/// </summary>
/// <remarks>
/// The type cache is a <c>static readonly</c> dictionary; it is implicitly cleared on domain
/// reload because the static field is recreated with the new assembly image.
/// </remarks>
/// <summary>Concrete-type dropdown for <c>[SerializeReference]</c> fields, arrays, and list elements.</summary>
[CustomPropertyDrawer(typeof(SubclassSelectorAttribute))]
public class SubclassSelectorDrawer : PropertyDrawer {
private static readonly Dictionary<Type, Type[]> TypeCache = new();