Skip to content

Commit b60fdde

Browse files
committed
feat(diagnostics): add DevConsoleAutocompleteQualifiedIdPatch for enhanced ID autocomplete functionality
- Introduced `DevConsoleAutocompleteQualifiedIdPatch` to enable shorthand autocomplete for IDs owned by ritsulib-registered content in the developer console. - Updated `PatcherSetup` to register the new patch, improving user experience when working with content IDs. - Refactored existing methods to support the new autocomplete feature, ensuring efficient ID matching and completion.
1 parent 3d974de commit b60fdde

6 files changed

Lines changed: 212 additions & 12 deletions

File tree

CardPiles/ModCardPileRegistry.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public static HoverTip CreateHoverTip(string id)
192192
/// <summary>
193193
/// Snapshot of all registered definitions, stable-ordered by id.
194194
/// </summary>
195-
internal static ModCardPileDefinition[] GetDefinitionsSnapshot()
195+
public static ModCardPileDefinition[] GetDefinitionsSnapshot()
196196
{
197197
lock (SyncRoot)
198198
{

CardTags/ModCardTagRegistry.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ public static bool TryGetId(CardTag value, out string id)
203203
return false;
204204
}
205205

206-
internal static ModCardTagDefinition[] GetDefinitionsSnapshot()
206+
/// <summary>
207+
/// Snapshot of all registered card-tag definitions, stable-ordered by id.
208+
/// </summary>
209+
public static ModCardTagDefinition[] GetDefinitionsSnapshot()
207210
{
208211
lock (SyncRoot)
209212
{

Content/ModContentRegistry.cs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,10 @@ internal static IEnumerable<CharacterModel> GetModCharacters()
803803
return ResolveModels<CharacterModel>(RegisteredCharacters);
804804
}
805805

806-
internal static ModContentRegisteredTypeSnapshot[] GetRegisteredTypeSnapshots()
806+
/// <summary>
807+
/// Snapshot of registered model types with owner and resolved/public-entry diagnostics.
808+
/// </summary>
809+
public static ModContentRegisteredTypeSnapshot[] GetRegisteredTypeSnapshots()
807810
{
808811
lock (SyncRoot)
809812
{
@@ -1227,7 +1230,11 @@ private static List<TModel> MergeDistinctByModelId<TModel>(IEnumerable<TModel> f
12271230
return first.Concat(second).DistinctBy(static m => m.Id).ToList();
12281231
}
12291232

1230-
private static string NormalizePublicStem(string value)
1233+
/// <summary>
1234+
/// Normalizes a public id segment: non-alphanumeric collapsed to underscores, acronym/camel boundaries
1235+
/// split, repeated underscores merged, and final uppercase.
1236+
/// </summary>
1237+
public static string NormalizePublicStem(string value)
12311238
{
12321239
ArgumentException.ThrowIfNullOrWhiteSpace(value);
12331240

@@ -1310,13 +1317,60 @@ private enum CharacterStarterContentKind
13101317
Potion,
13111318
}
13121319

1313-
internal readonly record struct ModContentRegisteredTypeSnapshot(
1314-
string ModId,
1315-
Type ModelType,
1316-
ModelId? ModelDbId,
1317-
string? ExpectedPublicEntry,
1318-
bool HasExplicitPublicEntryOverride,
1319-
string? TypeNamePublicEntry);
1320+
/// <summary>
1321+
/// Immutable snapshot row describing one registered model type and its identity metadata.
1322+
/// </summary>
1323+
public readonly record struct ModContentRegisteredTypeSnapshot
1324+
{
1325+
/// <summary>
1326+
/// Creates a registered-type snapshot row.
1327+
/// </summary>
1328+
public ModContentRegisteredTypeSnapshot(
1329+
string modId,
1330+
Type modelType,
1331+
ModelId? modelDbId,
1332+
string? expectedPublicEntry,
1333+
bool hasExplicitPublicEntryOverride,
1334+
string? typeNamePublicEntry)
1335+
{
1336+
ModId = modId;
1337+
ModelType = modelType;
1338+
ModelDbId = modelDbId;
1339+
ExpectedPublicEntry = expectedPublicEntry;
1340+
HasExplicitPublicEntryOverride = hasExplicitPublicEntryOverride;
1341+
TypeNamePublicEntry = typeNamePublicEntry;
1342+
}
1343+
1344+
/// <summary>
1345+
/// Owning mod id recorded at registration time.
1346+
/// </summary>
1347+
public string ModId { get; }
1348+
1349+
/// <summary>
1350+
/// Registered model CLR type.
1351+
/// </summary>
1352+
public Type ModelType { get; }
1353+
1354+
/// <summary>
1355+
/// Resolved runtime <c>ModelDb</c> id, if currently available.
1356+
/// </summary>
1357+
public ModelId? ModelDbId { get; }
1358+
1359+
/// <summary>
1360+
/// Expected fixed public entry for this model under current registry rules.
1361+
/// </summary>
1362+
public string? ExpectedPublicEntry { get; }
1363+
1364+
/// <summary>
1365+
/// Whether the expected entry comes from an explicit override.
1366+
/// </summary>
1367+
public bool HasExplicitPublicEntryOverride { get; }
1368+
1369+
/// <summary>
1370+
/// Type-name-derived public entry (<c>CATEGORY_TYPENAME</c>) when resolvable.
1371+
/// </summary>
1372+
public string? TypeNamePublicEntry { get; }
1373+
}
13201374

13211375
private readonly record struct CharacterStarterRegistration(
13221376
Type CharacterType,
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using MegaCrit.Sts2.Core.DevConsole;
2+
using MegaCrit.Sts2.Core.DevConsole.ConsoleCommands;
3+
using STS2RitsuLib.CardPiles;
4+
using STS2RitsuLib.CardTags;
5+
using STS2RitsuLib.Content;
6+
using STS2RitsuLib.Keywords;
7+
using STS2RitsuLib.Patching.Models;
8+
using STS2RitsuLib.TopBar;
9+
10+
namespace STS2RitsuLib.Diagnostics.Patches
11+
{
12+
/// <summary>
13+
/// Enables shorthand autocomplete only for IDs owned by ritsulib-registered content.
14+
/// </summary>
15+
public sealed class DevConsoleAutocompleteQualifiedIdPatch : IPatchMethod
16+
{
17+
private static readonly Lazy<OwnedIdCatalog> OwnedCatalog = new(BuildOwnedIdCatalog);
18+
19+
/// <inheritdoc />
20+
public static string PatchId => "dev_console_autocomplete_qualified_id";
21+
22+
/// <inheritdoc />
23+
public static bool IsCritical => false;
24+
25+
/// <inheritdoc />
26+
public static string Description =>
27+
"DevConsole autocomplete: shorthand match for ritsulib-owned ids only";
28+
29+
/// <inheritdoc />
30+
public static ModPatchTarget[] GetTargets()
31+
{
32+
return [new(typeof(AbstractConsoleCmd), nameof(AbstractConsoleCmd.CompleteArgument), true)];
33+
}
34+
35+
/// <summary>
36+
/// Installs a matcher only when no other patch already provided one.
37+
/// </summary>
38+
public static void Prefix(ref Func<string, string, bool>? matchPredicate)
39+
{
40+
if (matchPredicate != null)
41+
return;
42+
43+
matchPredicate = TailAwareOwnedIdMatch;
44+
}
45+
46+
/// <summary>
47+
/// De-duplicates completion candidates while preserving existing order from upstream patches.
48+
/// </summary>
49+
// ReSharper disable once InconsistentNaming
50+
public static void Postfix(ref CompletionResult __result, string partialArg)
51+
{
52+
__result.Candidates = __result.Candidates
53+
.Distinct(StringComparer.OrdinalIgnoreCase)
54+
.ToList();
55+
}
56+
57+
private static bool TailAwareOwnedIdMatch(string candidate, string partial)
58+
{
59+
if (string.IsNullOrWhiteSpace(partial))
60+
return true;
61+
62+
var token = partial.Trim();
63+
if (candidate.StartsWith(token, StringComparison.OrdinalIgnoreCase))
64+
return true;
65+
66+
if (!TryGetOwnedTail(candidate, out var tail))
67+
return false;
68+
69+
return tail.StartsWith(token, StringComparison.OrdinalIgnoreCase) ||
70+
tail.Contains(token, StringComparison.OrdinalIgnoreCase);
71+
}
72+
73+
private static bool TryGetOwnedTail(string candidate, out string tail)
74+
{
75+
tail = string.Empty;
76+
if (string.IsNullOrWhiteSpace(candidate))
77+
return false;
78+
79+
var ownership = OwnedCatalog.Value.IdOwnerMap;
80+
if (!ownership.TryGetValue(candidate.Trim(), out var ownerModId))
81+
return false;
82+
83+
var modPrefix = GetNormalizedModStem(ownerModId) + "_";
84+
if (!candidate.StartsWith(modPrefix, StringComparison.OrdinalIgnoreCase))
85+
return false;
86+
87+
if (candidate.Length <= modPrefix.Length)
88+
return false;
89+
90+
tail = candidate[modPrefix.Length..];
91+
return tail.Contains('_');
92+
}
93+
94+
private static OwnedIdCatalog BuildOwnedIdCatalog()
95+
{
96+
var owned = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
97+
98+
foreach (var s in ModContentRegistry.GetRegisteredTypeSnapshots())
99+
{
100+
if (s.ModelDbId != null)
101+
TryAddOwned(owned, s.ModelDbId.Entry, s.ModId);
102+
103+
if (!string.IsNullOrWhiteSpace(s.ExpectedPublicEntry))
104+
TryAddOwned(owned, s.ExpectedPublicEntry, s.ModId);
105+
}
106+
107+
foreach (var d in ModKeywordRegistry.GetDefinitionsSnapshot())
108+
TryAddOwned(owned, d.Id, d.ModId);
109+
110+
foreach (var d in ModCardTagRegistry.GetDefinitionsSnapshot())
111+
TryAddOwned(owned, d.Id, d.ModId);
112+
113+
foreach (var d in ModCardPileRegistry.GetDefinitionsSnapshot())
114+
TryAddOwned(owned, d.Id, d.ModId);
115+
116+
foreach (var d in ModTopBarButtonRegistry.GetDefinitionsSnapshot())
117+
TryAddOwned(owned, d.Id, d.ModId);
118+
119+
return new(owned);
120+
}
121+
122+
private static void TryAddOwned(Dictionary<string, string> owned, string? id, string? modId)
123+
{
124+
if (string.IsNullOrWhiteSpace(id) || string.IsNullOrWhiteSpace(modId))
125+
return;
126+
127+
var normalizedId = id.Trim();
128+
owned.TryAdd(normalizedId, modId);
129+
}
130+
131+
private static string GetNormalizedModStem(string modId)
132+
{
133+
return ModContentRegistry.NormalizePublicStem(modId);
134+
}
135+
136+
private sealed record OwnedIdCatalog(Dictionary<string, string> IdOwnerMap);
137+
}
138+
}

Keywords/ModKeywordRegistry.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,10 @@ public static CardKeyword GetCardKeyword(string id)
422422
return Get(id).CardKeywordValue;
423423
}
424424

425-
internal static ModKeywordDefinition[] GetDefinitionsSnapshot()
425+
/// <summary>
426+
/// Snapshot of all registered keyword definitions, stable-ordered by id.
427+
/// </summary>
428+
public static ModKeywordDefinition[] GetDefinitionsSnapshot()
426429
{
427430
lock (SyncRoot)
428431
{

RitsuLibFramework.PatcherSetup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using STS2RitsuLib.Combat.HealthBars.Patches;
88
using STS2RitsuLib.Combat.Rewards.Patches;
99
using STS2RitsuLib.Content.Patches;
10+
using STS2RitsuLib.Diagnostics.Patches;
1011
using STS2RitsuLib.Interop.Patches;
1112
using STS2RitsuLib.Keywords.Patches;
1213
using STS2RitsuLib.Lifecycle.Patches;
@@ -75,6 +76,7 @@ private static void RegisterLifecyclePatches()
7576
patcher.RegisterPatch<SavedAttachedStatePatches.SavedPropertiesFromInternalPatch>();
7677
patcher.RegisterPatch<SavedAttachedStatePatches.SavedPropertiesFillInternalPatch>();
7778
patcher.RegisterPatch<CoreInitializationLifecyclePatch>();
79+
patcher.RegisterPatch<DevConsoleAutocompleteQualifiedIdPatch>();
7880
patcher.RegisterPatch<NMainMenuContinueRunMissingCharacterPatch>();
7981
patcher.RegisterPatch<NMainMenuHarmonyPatchDumpPatch>();
8082
patcher.RegisterPatch<NContinueRunInfoShowInfoModelNotFoundPatch>();

0 commit comments

Comments
 (0)