Compare commits

...

4 Commits

@ -38,5 +38,14 @@
<Reference Include="Unity.TextMeshPro"> <Reference Include="Unity.TextMeshPro">
<HintPath>$(LogicWorldGameLocation)\Logic_World_Data\Managed\Unity.TextMeshPro.dll</HintPath> <HintPath>$(LogicWorldGameLocation)\Logic_World_Data\Managed\Unity.TextMeshPro.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(LogicWorldGameLocation)\Logic_World_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>$(LogicWorldGameLocation)\Logic_World_Data\Managed\UnityEngine.UI.dll</HintPath>
</Reference>
<Reference Include="LICC">
<HintPath>$(LogicWorldGameLocation)\Logic_World_Data\Managed\LICC.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -1,8 +1,8 @@
ID: D4VID_ConsoleImprovements ID: D4VID_ConsoleImprovements
Name: ConsoleImprovements Name: ConsoleImprovements
Author: D4VID Author: D4VID
Version: 1.1.0 Version: 1.2.1
Priority: 0 Priority: -90
ClientOnly: true ClientOnly: true
Dependencies: Dependencies:
- HarmonyForLogicWorld - HarmonyForLogicWorld

@ -1,14 +1,17 @@
using System.Collections;
using System.Reflection; using System.Reflection;
using System.Collections.Generic; using System.Collections.Generic;
using EccsLogicWorldAPI.Shared.AccessHelper; using EccsLogicWorldAPI.Shared.AccessHelper;
using FancyInput; using FancyInput;
using HarmonyLib; using HarmonyLib;
using JimmysUnityUtilities;
using LICC;
using LogicLog; using LogicLog;
using TMPro; using TMPro;
using Console = FancyPantsConsole.Console; using Console = FancyPantsConsole.Console;
namespace ConsoleImprovements.Client { namespace ConsoleImprovements.Client {
public class CommandHistoryPatch { public class CommandInputPatch {
const int HistLength = 1000; const int HistLength = 1000;
private static ILogicLogger _logger = null!; private static ILogicLogger _logger = null!;
@ -16,14 +19,32 @@ namespace ConsoleImprovements.Client {
private static LinkedList<string> _history = new LinkedList<string>(); private static LinkedList<string> _history = new LinkedList<string>();
private static LinkedListNode<string>? _command; private static LinkedListNode<string>? _command;
private static Trie _commandRegistry = new Trie();
public static bool Prepare(ILogicLogger logger) { public static bool Prepare(ILogicLogger logger) {
_logger = logger; _logger = logger;
// Get access to the rich text string inside the MessageData class try {
_commandInputFieldField = AccessTools.Field(typeof(Console), "CommandInputField"); // Get access to the rich text string inside the MessageData class
if (_commandInputFieldField == null) { _commandInputFieldField = Fields.getPrivate(typeof(Console), "CommandInputField");
_logger.Error("Cannot get field CommandInputField of Console");
CommandConsole commandConsole = CommandConsole.Current;
// ICommandRegistryInternal
var registry = Fields.getPrivate(typeof(CommandConsole), "CommandRegistry").GetValue(commandConsole);
// CommandRegistry.CommandCollectionByName
var commandCollection = Properties.get(registry, "AllRegisteredCommands").GetValue(registry);
// IEnumerable<Command>
var commands = (IEnumerable) Methods.getPublic(commandCollection, "EnumerateAllCommands")
.Invoke(commandCollection, null)!;
foreach (var command in commands) {
var nameProperty = Properties.getPublic(command, "Name");
var hiddenProperty = Properties.getPublic(command, "Hidden");
if ((bool) hiddenProperty.GetValue(command)!) continue;
_commandRegistry.AddEntry((string) nameProperty.GetValue(command)!);
}
} catch (AccessHelperException e) {
_logger.Error($"Error occured while preparing command history patch: {e}");
return false; return false;
} }
@ -37,13 +58,13 @@ namespace ConsoleImprovements.Client {
private static void PatchUpdate(Harmony harmony) { private static void PatchUpdate(Harmony harmony) {
var methodTarget = Methods.getPrivate(typeof(Console), "Update"); var methodTarget = Methods.getPrivate(typeof(Console), "Update");
var methodHook = Methods.get(typeof(CommandHistoryPatch), nameof(HookUpdate)); var methodHook = Methods.get(typeof(CommandInputPatch), nameof(HookUpdate));
harmony.Patch(methodTarget, prefix: new HarmonyMethod(methodHook)); harmony.Patch(methodTarget, prefix: new HarmonyMethod(methodHook));
} }
private static void PatchCommandSubmit(Harmony harmony) { private static void PatchCommandSubmit(Harmony harmony) {
var methodTarget = Methods.getPrivate(typeof(Console), "OnCommandInputFieldSubmit"); var methodTarget = Methods.getPrivate(typeof(Console), "OnCommandInputFieldSubmit");
var methodHook = Methods.get(typeof(CommandHistoryPatch), nameof(HookCommandSubmit)); var methodHook = Methods.get(typeof(CommandInputPatch), nameof(HookCommandSubmit));
harmony.Patch(methodTarget, prefix: new HarmonyMethod(methodHook)); harmony.Patch(methodTarget, prefix: new HarmonyMethod(methodHook));
} }
@ -68,6 +89,27 @@ namespace ConsoleImprovements.Client {
} }
} }
if (RawInput.Tab.DownThisFrame()) {
TMP_InputField? commandInputField = _commandInputFieldField!.GetValue(__instance) as TMP_InputField;
if (commandInputField == null) {
_logger.Error("Cannot get CommandInputField value");
return true;
}
string current = commandInputField.text;
string? completed = _commandRegistry.LookupNext(current, out Trie.Node? node);
if (completed != null) {
if (completed == current) {
List<string> commands = node?.CollectChildren(completed)!;
if (commands.Count > 0) {
_logger.Info($"Available commands: {string.Join(", ", commands)}");
}
} else {
UpdateCommandTextField(__instance, completed);
}
}
}
return true; // Resume original functionality return true; // Resume original functionality
} }
@ -79,6 +121,7 @@ namespace ConsoleImprovements.Client {
} }
commandInputField.text = text; commandInputField.text = text;
CoroutineUtility.RunAfterOneFrame(() => commandInputField.MoveTextEnd(false));
} }
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming

@ -11,8 +11,8 @@ namespace ConsoleImprovements.Client {
Logger.Info("Message Copy Patch Successful"); Logger.Info("Message Copy Patch Successful");
} }
if (CommandHistoryPatch.Prepare(Logger)) { if (CommandInputPatch.Prepare(Logger)) {
CommandHistoryPatch.Inject(harmony); CommandInputPatch.Inject(harmony);
Logger.Info("Command History Patch Successful"); Logger.Info("Command History Patch Successful");
} }

@ -9,42 +9,27 @@ namespace ConsoleImprovements.Client {
public class MessageCopyPatch { public class MessageCopyPatch {
private static ILogicLogger _logger = null!; private static ILogicLogger _logger = null!;
private static MethodInfo? _removeRichTextTagsMethod; private static MethodInfo? _removeRichTextTagsMethod;
private static FieldInfo? _dataProperty; private static FieldInfo? _dataField;
private static FieldInfo? _messageRichTextField; private static FieldInfo? _messageRichTextField;
private static FieldInfo? _messageTypeField; private static FieldInfo? _messageTypeField;
public static bool Prepare(ILogicLogger logger) { public static bool Prepare(ILogicLogger logger) {
_logger = logger; _logger = logger;
// Get the private RemoveRichTextTags method of Message try {
_removeRichTextTagsMethod = AccessTools.Method( // Get the private RemoveRichTextTags method of Message
typeof(Message), _removeRichTextTagsMethod = Methods.getPrivate(typeof(Message), "RemoveRichTextTags");
"RemoveRichTextTags",
new[] {typeof(string)}
);
if (_removeRichTextTagsMethod == null) {
_logger.Error("Cannot get method RemoveRichTextTags of message");
return false;
}
// Get access to the Data property of Message, which is of internal type MessageData // Get access to the Data field of Message, which is of internal type MessageData
_dataProperty = AccessTools.Field(typeof(Message), "Data"); _dataField = Fields.getPrivate(typeof(Message), "Data");
if (_dataProperty == null) {
_logger.Error("Cannot get field Data of message");
return false;
}
// Get access to the rich text string inside the MessageData class // Get access to the rich text string inside the MessageData class
_messageRichTextField = AccessTools.Field(_dataProperty.FieldType, "messageRichText"); _messageRichTextField = Fields.getPublic(_dataField.FieldType, "messageRichText");
if (_messageRichTextField == null) {
_logger.Error("Cannot get field messageRichText of message data");
return false;
}
// Get access to the message type enum inside the MessageData class // Get access to the message type enum inside the MessageData class
_messageTypeField = AccessTools.Field(_dataProperty.FieldType, "messageType"); _messageTypeField = Fields.getPublic(_dataField.FieldType, "messageType");
if (_messageTypeField == null) { } catch (AccessHelperException e) {
_logger.Error("Cannot get field messageRichText of message data"); _logger.Error($"Error occured while preparing message copy patch: {e}");
return false; return false;
} }
@ -60,7 +45,7 @@ namespace ConsoleImprovements.Client {
// Prefix the CopyMessageToClipboard method with this one // Prefix the CopyMessageToClipboard method with this one
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
public static bool Hook(Message __instance) { public static bool Hook(Message __instance) {
var dataInstance = _dataProperty!.GetValue(__instance); var dataInstance = _dataField!.GetValue(__instance);
if (dataInstance == null) { if (dataInstance == null) {
_logger.Error("Cannot get instance of MessageData"); _logger.Error("Cannot get instance of MessageData");
return true; // Resume original functionality return true; // Resume original functionality

@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleImprovements.Client {
/// <summary>
/// A crude implementation of the trie data structure for fast lookups
/// </summary>
public class Trie {
private readonly Node _root = new Node() {
Children = new Dictionary<char, Node>()
};
/// <summary>
/// Add a new string entry to the structure
/// </summary>
public void AddEntry(string str) {
Node current = _root;
foreach (char ch in str.ToLower()) {
if (!current.Children.TryGetValue(ch, out Node? next)) {
next = new Node();
current.Children[ch] = next;
}
current = next;
}
current.End = true;
}
/// <summary>
/// Look up how the given string may continue
/// </summary>
/// <param name="prefix">The string to base the search on</param>
/// <param name="node">outputs the reached node which is either an end node or has multiple children</param>
/// <returns>Continuation of the prefix (including the prefix)</returns>
public string? LookupNext(string prefix, out Node? node) {
string lowercase = prefix.ToLower();
Node current = _root;
foreach (char ch in lowercase) {
if (!current.Children.TryGetValue(ch, out Node? next)) {
node = null;
return null;
}
current = next;
}
var commonBuilder = new StringBuilder(lowercase);
while (current.Children.Count == 1) {
if (current.End) {
break;
}
KeyValuePair<char, Node> single = current.Children.First();
commonBuilder.Append(single.Key);
current = single.Value;
}
node = current;
return commonBuilder.ToString();
}
public class Node {
public bool End;
public Dictionary<char, Node> Children = new Dictionary<char, Node>();
/// <summary>
/// Get all possible strings that can be made from the children of this node
/// </summary>
/// <param name="prefix">Pass in the previously obtained string path to this node</param>
public List<string> CollectChildren(string prefix) {
var collected = new List<string>();
if (End) {
collected.Add(prefix.ToLower());
}
foreach ((char ch, Node node) in Children) {
collected.AddRange(node.CollectChildren(prefix + ch));
}
return collected;
}
}
}
}

@ -1,8 +1,9 @@
# Console Improvements Mod # Console Improvements Mod
This mod does 2 things: This mod does 3 things:
1. It removes the `> ` prefix from copied commands 1. It removes the `> ` prefix from copied commands
2. Adds a command history that can be cycled using up and down arrow keys 2. Adds a command history that can be cycled using up and down arrow keys
3. Adds tab completion
## Dependencies ## Dependencies

Loading…
Cancel
Save