mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge remote-tracking branch 'origin/master' into apiX-rollup
This commit is contained in:
commit
4e331b1d85
28 changed files with 1320 additions and 341 deletions
|
|
@ -1,6 +1,9 @@
|
|||
namespace Dalamud.Configuration.Internal;
|
||||
|
||||
public record PluginTestingOptIn
|
||||
/// <summary>
|
||||
/// Represents a plugin that has opted in to testing.
|
||||
/// </summary>
|
||||
internal record PluginTestingOptIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginTestingOptIn"/> class.
|
||||
|
|
|
|||
27
Dalamud/Console/ConsoleArgumentType.cs
Normal file
27
Dalamud/Console/ConsoleArgumentType.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace Dalamud.Console;
|
||||
|
||||
/// <summary>
|
||||
/// Possible console argument types.
|
||||
/// </summary>
|
||||
internal enum ConsoleArgumentType
|
||||
{
|
||||
/// <summary>
|
||||
/// A regular string.
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// A signed integer.
|
||||
/// </summary>
|
||||
Integer,
|
||||
|
||||
/// <summary>
|
||||
/// A floating point value.
|
||||
/// </summary>
|
||||
Float,
|
||||
|
||||
/// <summary>
|
||||
/// A boolean value.
|
||||
/// </summary>
|
||||
Bool,
|
||||
}
|
||||
44
Dalamud/Console/ConsoleEntry.cs
Normal file
44
Dalamud/Console/ConsoleEntry.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Dalamud.Console;
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing an entry in the console.
|
||||
/// </summary>
|
||||
public interface IConsoleEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the entry.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the entry.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing a command in the console.
|
||||
/// </summary>
|
||||
public interface IConsoleCommand : IConsoleEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute this command.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Arguments to invoke the entry with.</param>
|
||||
/// <returns>Whether or not execution succeeded.</returns>
|
||||
public bool Invoke(IEnumerable<object> arguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface representing a variable in the console.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
public interface IConsoleVariable<T> : IConsoleEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of this variable.
|
||||
/// </summary>
|
||||
T Value { get; set; }
|
||||
}
|
||||
526
Dalamud/Console/ConsoleManager.cs
Normal file
526
Dalamud/Console/ConsoleManager.cs
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Dalamud.Console;
|
||||
|
||||
// TODO: Mayhaps overloads with Func<bool, T1, T2, ...> for commands?
|
||||
|
||||
/// <summary>
|
||||
/// Class managing console commands and variables.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService("Console is needed by other blocking early loaded services.")]
|
||||
internal partial class ConsoleManager : IServiceType
|
||||
{
|
||||
private static readonly ModuleLog Log = new("CON");
|
||||
|
||||
private Dictionary<string, IConsoleEntry> entries = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleManager"/> class.
|
||||
/// </summary>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public ConsoleManager()
|
||||
{
|
||||
this.AddCommand("toggle", "Toggle a boolean variable.", this.OnToggleVariable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event that is triggered when a command is processed. Return true to stop the command from being processed any further.
|
||||
/// </summary>
|
||||
public event Func<string, bool>? Invoke;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only dictionary of console entries.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, IConsoleEntry> Entries => this.entries;
|
||||
|
||||
/// <summary>
|
||||
/// Add a command to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="description">A description of the command.</param>
|
||||
/// <param name="func">Function to invoke when the command has been called. Must return a <see cref="bool"/> indicating success.</param>
|
||||
/// <returns>The added command.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the command already exists.</exception>
|
||||
public IConsoleCommand AddCommand(string name, string description, Delegate func)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
ArgumentNullException.ThrowIfNull(description);
|
||||
ArgumentNullException.ThrowIfNull(func);
|
||||
|
||||
if (this.FindEntry(name) != null)
|
||||
throw new InvalidOperationException($"Entry '{name}' already exists.");
|
||||
|
||||
var command = new ConsoleCommand(name, description, func);
|
||||
this.entries.Add(name, command);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a variable to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="description">A description of the variable.</param>
|
||||
/// <param name="defaultValue">The default value of the variable.</param>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
/// <returns>The added variable.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the variable already exists.</exception>
|
||||
public IConsoleVariable<T> AddVariable<T>(string name, string description, T defaultValue)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
ArgumentNullException.ThrowIfNull(description);
|
||||
Traits.ThrowIfTIsNullableAndNull(defaultValue);
|
||||
|
||||
if (this.FindEntry(name) != null)
|
||||
throw new InvalidOperationException($"Entry '{name}' already exists.");
|
||||
|
||||
var variable = new ConsoleVariable<T>(name, description);
|
||||
variable.Value = defaultValue;
|
||||
this.entries.Add(name, variable);
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an alias to a console entry.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the entry to add an alias for.</param>
|
||||
/// <param name="alias">The alias to use.</param>
|
||||
/// <returns>The added alias.</returns>
|
||||
public IConsoleEntry AddAlias(string name, string alias)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
ArgumentNullException.ThrowIfNull(alias);
|
||||
|
||||
var target = this.FindEntry(name);
|
||||
if (target == null)
|
||||
throw new EntryNotFoundException(name);
|
||||
|
||||
if (this.FindEntry(alias) != null)
|
||||
throw new InvalidOperationException($"Entry '{alias}' already exists.");
|
||||
|
||||
var aliasEntry = new ConsoleAlias(name, target);
|
||||
this.entries.Add(alias, aliasEntry);
|
||||
|
||||
return aliasEntry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove an entry from the console.
|
||||
/// </summary>
|
||||
/// <param name="entry">The entry to remove.</param>
|
||||
public void RemoveEntry(IConsoleEntry entry)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entry);
|
||||
|
||||
if (!this.entries.Remove(entry.Name))
|
||||
throw new EntryNotFoundException(entry.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of a variable.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
/// <returns>The value of the variable.</returns>
|
||||
/// <exception cref="EntryNotFoundException">Thrown if the variable could not be found.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the found console entry is not of the expected type.</exception>
|
||||
public T GetVariable<T>(string name)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
var entry = this.FindEntry(name);
|
||||
|
||||
if (entry is ConsoleVariable<T> variable)
|
||||
return variable.Value;
|
||||
|
||||
if (entry is ConsoleVariable)
|
||||
throw new InvalidOperationException($"Variable '{name}' is not of type {typeof(T).Name}.");
|
||||
|
||||
if (entry is null)
|
||||
throw new EntryNotFoundException(name);
|
||||
|
||||
throw new InvalidOperationException($"Command '{name}' is not a variable.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the value of a variable.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <typeparam name="T">The type of the value to set.</typeparam>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the found console entry is not of the expected type.</exception>
|
||||
/// <exception cref="EntryNotFoundException">Thrown if the variable could not be found.</exception>
|
||||
public void SetVariable<T>(string name, T value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
Traits.ThrowIfTIsNullableAndNull(value);
|
||||
|
||||
var entry = this.FindEntry(name);
|
||||
|
||||
if (entry is ConsoleVariable<T> variable)
|
||||
variable.Value = value;
|
||||
|
||||
if (entry is ConsoleVariable)
|
||||
throw new InvalidOperationException($"Variable '{name}' is not of type {typeof(T).Name}.");
|
||||
|
||||
if (entry is null)
|
||||
throw new EntryNotFoundException(name);
|
||||
|
||||
throw new InvalidOperationException($"Command '{name}' is not a variable.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a console command.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to process.</param>
|
||||
/// <returns>Whether or not the command was successfully processed.</returns>
|
||||
public bool ProcessCommand(string command)
|
||||
{
|
||||
if (this.Invoke?.Invoke(command) == true)
|
||||
return true;
|
||||
|
||||
var matches = GetCommandParsingRegex().Matches(command);
|
||||
if (matches.Count == 0)
|
||||
return false;
|
||||
|
||||
var entryName = matches[0].Value;
|
||||
if (string.IsNullOrEmpty(entryName) || entryName.Any(char.IsWhiteSpace))
|
||||
{
|
||||
Log.Error("No valid command specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
var entry = this.FindEntry(entryName);
|
||||
if (entry == null)
|
||||
{
|
||||
Log.Error("Command {CommandName} not found", entryName);
|
||||
return false;
|
||||
}
|
||||
|
||||
var parsedArguments = new List<object>();
|
||||
|
||||
if (entry.ValidArguments != null)
|
||||
{
|
||||
for (var i = 1; i < matches.Count; i++)
|
||||
{
|
||||
if (i - 1 >= entry.ValidArguments.Count)
|
||||
{
|
||||
Log.Error("Too many arguments for command {CommandName}", entryName);
|
||||
PrintUsage(entry);
|
||||
return false;
|
||||
}
|
||||
|
||||
var argumentToMatch = entry.ValidArguments[i - 1];
|
||||
|
||||
var group = matches[i];
|
||||
if (!group.Success)
|
||||
continue;
|
||||
|
||||
var value = group.Value;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
continue;
|
||||
|
||||
switch (argumentToMatch.Type)
|
||||
{
|
||||
case ConsoleArgumentType.String:
|
||||
parsedArguments.Add(value);
|
||||
break;
|
||||
|
||||
case ConsoleArgumentType.Integer when int.TryParse(value, out var intValue):
|
||||
parsedArguments.Add(intValue);
|
||||
break;
|
||||
case ConsoleArgumentType.Integer:
|
||||
Log.Error("Argument {Argument} for command {CommandName} is not an integer", value, entryName);
|
||||
PrintUsage(entry);
|
||||
return false;
|
||||
|
||||
case ConsoleArgumentType.Float when float.TryParse(value, out var floatValue):
|
||||
parsedArguments.Add(floatValue);
|
||||
break;
|
||||
case ConsoleArgumentType.Float:
|
||||
Log.Error("Argument {Argument} for command {CommandName} is not a float", value, entryName);
|
||||
PrintUsage(entry);
|
||||
return false;
|
||||
|
||||
case ConsoleArgumentType.Bool when bool.TryParse(value, out var boolValue):
|
||||
parsedArguments.Add(boolValue);
|
||||
break;
|
||||
case ConsoleArgumentType.Bool:
|
||||
Log.Error("Argument {Argument} for command {CommandName} is not a boolean", value, entryName);
|
||||
PrintUsage(entry);
|
||||
return false;
|
||||
|
||||
default:
|
||||
throw new Exception("Unhandled argument type.");
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedArguments.Count != entry.ValidArguments.Count)
|
||||
{
|
||||
// Either fill in the default values or error out
|
||||
|
||||
for (var i = parsedArguments.Count; i < entry.ValidArguments.Count; i++)
|
||||
{
|
||||
var argument = entry.ValidArguments[i];
|
||||
if (argument.DefaultValue == null)
|
||||
{
|
||||
Log.Error("Not enough arguments for command {CommandName}", entryName);
|
||||
PrintUsage(entry);
|
||||
return false;
|
||||
}
|
||||
|
||||
parsedArguments.Add(argument.DefaultValue);
|
||||
}
|
||||
|
||||
if (parsedArguments.Count != entry.ValidArguments.Count)
|
||||
{
|
||||
Log.Error("Too many arguments for command {CommandName}", entryName);
|
||||
PrintUsage(entry);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (matches.Count > 1)
|
||||
{
|
||||
Log.Error("Command {CommandName} does not take any arguments", entryName);
|
||||
PrintUsage(entry);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return entry.Invoke(parsedArguments);
|
||||
}
|
||||
|
||||
[GeneratedRegex("""("[^"]+"|[^\s"]+)""", RegexOptions.Compiled)]
|
||||
private static partial Regex GetCommandParsingRegex();
|
||||
|
||||
private static void PrintUsage(ConsoleEntry entry, bool error = true)
|
||||
{
|
||||
Log.WriteLog(
|
||||
error ? LogEventLevel.Error : LogEventLevel.Information,
|
||||
"Usage: {CommandName} {Arguments}",
|
||||
null,
|
||||
entry.Name,
|
||||
string.Join(" ", entry.ValidArguments?.Select(x => $"<{x.Type.ToString().ToLowerInvariant()}>") ?? Enumerable.Empty<string>()));
|
||||
}
|
||||
|
||||
private ConsoleEntry? FindEntry(string name)
|
||||
{
|
||||
return this.entries.TryGetValue(name, out var entry) ? entry as ConsoleEntry : null;
|
||||
}
|
||||
|
||||
private bool OnToggleVariable(string name)
|
||||
{
|
||||
if (this.FindEntry(name) is not IConsoleVariable<bool> variable)
|
||||
{
|
||||
Log.Error("Variable {VariableName} not found or not a boolean", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
variable.Value = !variable.Value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class Traits
|
||||
{
|
||||
public static void ThrowIfTIsNullableAndNull<T>(T? argument, [CallerArgumentExpression("argument")] string? paramName = null)
|
||||
{
|
||||
if (argument == null && !typeof(T).IsValueType)
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class representing an entry in the console.
|
||||
/// </summary>
|
||||
private abstract class ConsoleEntry : IConsoleEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleEntry"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the entry.</param>
|
||||
/// <param name="description">A description of the entry.</param>
|
||||
public ConsoleEntry(string name, string description)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of valid argument types for this console entry.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ArgumentInfo>? ValidArguments { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Execute this command.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Arguments to invoke the entry with.</param>
|
||||
/// <returns>Whether or not execution succeeded.</returns>
|
||||
public abstract bool Invoke(IEnumerable<object> arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Get an instance of <see cref="ArgumentInfo"/> for a given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the argument.</param>
|
||||
/// <param name="defaultValue">The default value to use if none is specified.</param>
|
||||
/// <returns>An <see cref="ArgumentInfo"/> instance.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the given type cannot be handled by the console system.</exception>
|
||||
protected static ArgumentInfo TypeToArgument(Type type, object? defaultValue = null)
|
||||
{
|
||||
// If the default value is DBNull, we want to treat it as null
|
||||
defaultValue = defaultValue == DBNull.Value ? null : defaultValue;
|
||||
|
||||
if (type == typeof(string))
|
||||
return new ArgumentInfo(ConsoleArgumentType.String, defaultValue);
|
||||
|
||||
if (type == typeof(int))
|
||||
return new ArgumentInfo(ConsoleArgumentType.Integer, defaultValue);
|
||||
|
||||
if (type == typeof(float))
|
||||
return new ArgumentInfo(ConsoleArgumentType.Float, defaultValue);
|
||||
|
||||
if (type == typeof(bool))
|
||||
return new ArgumentInfo(ConsoleArgumentType.Bool, defaultValue);
|
||||
|
||||
throw new ArgumentException($"Invalid argument type: {type.Name}");
|
||||
}
|
||||
|
||||
public record ArgumentInfo(ConsoleArgumentType Type, object? DefaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class representing an alias to another console entry.
|
||||
/// </summary>
|
||||
private class ConsoleAlias : ConsoleEntry
|
||||
{
|
||||
private readonly ConsoleEntry target;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleAlias"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the alias.</param>
|
||||
/// <param name="target">The target entry to alias to.</param>
|
||||
public ConsoleAlias(string name, ConsoleEntry target)
|
||||
: base(name, target.Description)
|
||||
{
|
||||
this.target = target;
|
||||
this.ValidArguments = target.ValidArguments;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Invoke(IEnumerable<object> arguments)
|
||||
{
|
||||
return this.target.Invoke(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class representing a console command.
|
||||
/// </summary>
|
||||
private class ConsoleCommand : ConsoleEntry, IConsoleCommand
|
||||
{
|
||||
private readonly Delegate func;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleCommand"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="description">A description of the variable.</param>
|
||||
/// <param name="func">The function to invoke.</param>
|
||||
public ConsoleCommand(string name, string description, Delegate func)
|
||||
: base(name, description)
|
||||
{
|
||||
this.func = func;
|
||||
|
||||
if (func.Method.ReturnType != typeof(bool))
|
||||
throw new ArgumentException("Console command functions must return a boolean indicating success.");
|
||||
|
||||
var validArguments = new List<ArgumentInfo>();
|
||||
foreach (var parameterInfo in func.Method.GetParameters())
|
||||
{
|
||||
var paraT = parameterInfo.ParameterType;
|
||||
validArguments.Add(TypeToArgument(paraT, parameterInfo.DefaultValue));
|
||||
}
|
||||
|
||||
this.ValidArguments = validArguments;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ConsoleEntry.Invoke" />
|
||||
public override bool Invoke(IEnumerable<object> arguments)
|
||||
{
|
||||
return (bool)this.func.DynamicInvoke(arguments.ToArray())!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class representing a basic console variable.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="description">A description of the variable.</param>
|
||||
private abstract class ConsoleVariable(string name, string description) : ConsoleEntry(name, description);
|
||||
|
||||
/// <summary>
|
||||
/// Class representing a generic console variable.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
private class ConsoleVariable<T> : ConsoleVariable, IConsoleVariable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleVariable{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="description">A description of the variable.</param>
|
||||
public ConsoleVariable(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
this.ValidArguments = new List<ArgumentInfo> { TypeToArgument(typeof(T)) };
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Value { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Invoke(IEnumerable<object> arguments)
|
||||
{
|
||||
var first = arguments.FirstOrDefault();
|
||||
if (first == null || first.GetType() != typeof(T))
|
||||
throw new ArgumentException($"Console variable must be set with an argument of type {typeof(T).Name}.");
|
||||
|
||||
this.Value = (T)first;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a console entry is not found.
|
||||
/// </summary>
|
||||
internal class EntryNotFoundException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EntryNotFoundException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the entry.</param>
|
||||
public EntryNotFoundException(string name)
|
||||
: base($"Console entry '{name}' does not exist.")
|
||||
{
|
||||
}
|
||||
}
|
||||
176
Dalamud/Console/ConsoleManagerPluginScoped.cs
Normal file
176
Dalamud/Console/ConsoleManagerPluginScoped.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Console;
|
||||
|
||||
#pragma warning disable Dalamud001
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of the console service.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IConsole>]
|
||||
#pragma warning restore SA1015
|
||||
public class ConsoleManagerPluginScoped : IConsole, IInternalDisposableService
|
||||
{
|
||||
private readonly ConsoleManager console;
|
||||
|
||||
private readonly List<IConsoleEntry> trackedEntries = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConsoleManagerPluginScoped"/> class.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin this service belongs to.</param>
|
||||
/// <param name="console">The console manager.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
internal ConsoleManagerPluginScoped(LocalPlugin plugin, ConsoleManager console)
|
||||
{
|
||||
this.console = console;
|
||||
|
||||
this.Prefix = ConsoleManagerPluginUtil.GetSanitizedNamespaceName(plugin.InternalName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Prefix { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
foreach (var trackedEntry in this.trackedEntries)
|
||||
{
|
||||
this.console.RemoveEntry(trackedEntry);
|
||||
}
|
||||
|
||||
this.trackedEntries.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConsoleCommand AddCommand(string name, string description, Func<bool> func)
|
||||
=> this.InternalAddCommand(name, description, func);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConsoleCommand AddCommand<T1>(string name, string description, Func<bool, T1> func)
|
||||
=> this.InternalAddCommand(name, description, func);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConsoleCommand AddCommand<T1, T2>(string name, string description, Func<bool, T1, T2> func)
|
||||
=> this.InternalAddCommand(name, description, func);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConsoleCommand AddCommand<T1, T2, T3>(string name, string description, Func<bool, T1, T2, T3> func)
|
||||
=> this.InternalAddCommand(name, description, func);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConsoleCommand AddCommand<T1, T2, T3, T4>(string name, string description, Func<bool, T1, T2, T3, T4> func)
|
||||
=> this.InternalAddCommand(name, description, func);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConsoleCommand AddCommand<T1, T2, T3, T4, T5>(string name, string description, Func<bool, T1, T2, T3, T4, T5> func)
|
||||
=> this.InternalAddCommand(name, description, func);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConsoleVariable<T> AddVariable<T>(string name, string description, T defaultValue)
|
||||
{
|
||||
var variable = this.console.AddVariable(this.GetPrefixedName(name), description, defaultValue);
|
||||
this.trackedEntries.Add(variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IConsoleEntry AddAlias(string name, string alias)
|
||||
{
|
||||
var entry = this.console.AddAlias(this.GetPrefixedName(name), alias);
|
||||
this.trackedEntries.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T GetVariable<T>(string name)
|
||||
{
|
||||
return this.console.GetVariable<T>(this.GetPrefixedName(name));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetVariable<T>(string name, T value)
|
||||
{
|
||||
this.console.SetVariable(this.GetPrefixedName(name), value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RemoveEntry(IConsoleEntry entry)
|
||||
{
|
||||
this.console.RemoveEntry(entry);
|
||||
this.trackedEntries.Remove(entry);
|
||||
}
|
||||
|
||||
private string GetPrefixedName(string name)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
|
||||
// If the name is empty, return the prefix to allow for a single command or variable to be top-level.
|
||||
if (name.Length == 0)
|
||||
return this.Prefix;
|
||||
|
||||
if (name.Any(char.IsWhiteSpace))
|
||||
throw new ArgumentException("Name cannot contain whitespace.", nameof(name));
|
||||
|
||||
return $"{this.Prefix}.{name}";
|
||||
}
|
||||
|
||||
private IConsoleCommand InternalAddCommand(string name, string description, Delegate func)
|
||||
{
|
||||
var command = this.console.AddCommand(this.GetPrefixedName(name), description, func);
|
||||
this.trackedEntries.Add(command);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility functions for the console manager.
|
||||
/// </summary>
|
||||
internal static partial class ConsoleManagerPluginUtil
|
||||
{
|
||||
private static readonly string[] ReservedNamespaces = ["dalamud", "xl", "plugin"];
|
||||
|
||||
/// <summary>
|
||||
/// Get a sanitized namespace name from a plugin's internal name.
|
||||
/// </summary>
|
||||
/// <param name="pluginInternalName">The plugin's internal name.</param>
|
||||
/// <returns>A sanitized namespace.</returns>
|
||||
public static string GetSanitizedNamespaceName(string pluginInternalName)
|
||||
{
|
||||
// Must be lowercase
|
||||
pluginInternalName = pluginInternalName.ToLowerInvariant();
|
||||
|
||||
// Remove all non-alphabetic characters
|
||||
pluginInternalName = NonAlphaRegex().Replace(pluginInternalName, string.Empty);
|
||||
|
||||
// Remove reserved namespaces from the start or end
|
||||
foreach (var reservedNamespace in ReservedNamespaces)
|
||||
{
|
||||
if (pluginInternalName.StartsWith(reservedNamespace))
|
||||
{
|
||||
pluginInternalName = pluginInternalName[reservedNamespace.Length..];
|
||||
}
|
||||
|
||||
if (pluginInternalName.EndsWith(reservedNamespace))
|
||||
{
|
||||
pluginInternalName = pluginInternalName[..^reservedNamespace.Length];
|
||||
}
|
||||
}
|
||||
|
||||
return pluginInternalName;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"[^a-z]")]
|
||||
private static partial Regex NonAlphaRegex();
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<DalamudVersion>9.1.0.9</DalamudVersion>
|
||||
<DalamudVersion>9.1.0.10</DalamudVersion>
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -30,27 +30,27 @@ internal unsafe class AddonEventEntry
|
|||
/// <summary>
|
||||
/// Gets the pointer to the event source.
|
||||
/// </summary>
|
||||
required public nint Node { get; init; }
|
||||
public required nint Node { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handler that gets called when this event is triggered.
|
||||
/// </summary>
|
||||
required public IAddonEventManager.AddonEventHandler Handler { get; init; }
|
||||
public required IAddonEventManager.AddonEventHandler Handler { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique id for this event.
|
||||
/// </summary>
|
||||
required public uint ParamKey { get; init; }
|
||||
public required uint ParamKey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event type for this event.
|
||||
/// </summary>
|
||||
required public AddonEventType EventType { get; init; }
|
||||
public required AddonEventType EventType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event handle for this event.
|
||||
/// </summary>
|
||||
required internal IAddonEventHandle Handle { get; init; }
|
||||
internal required IAddonEventHandle Handle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the formatted log string for this AddonEventEntry.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Dalamud.Console;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
|
@ -33,6 +34,9 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
|
|||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ChatGui chatGui = Service<ChatGui>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ConsoleManager console = Service<ConsoleManager>.Get();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private CommandManager(Dalamud dalamud)
|
||||
|
|
@ -47,6 +51,7 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
|
|||
};
|
||||
|
||||
this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||
this.console.Invoke += this.ConsoleOnInvoke;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -132,8 +137,14 @@ internal sealed class CommandManager : IInternalDisposableService, ICommandManag
|
|||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.console.Invoke -= this.ConsoleOnInvoke;
|
||||
this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
|
||||
}
|
||||
|
||||
private bool ConsoleOnInvoke(string arg)
|
||||
{
|
||||
return arg.StartsWith('/') && this.ProcessCommand(arg);
|
||||
}
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
namespace Dalamud.Game.Config;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a change in the configuration.
|
||||
/// </summary>
|
||||
/// <param name="Option">The option tha twas changed.</param>
|
||||
public abstract record ConfigChangeEvent(Enum Option);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic change in the configuration.
|
||||
/// </summary>
|
||||
/// <param name="ConfigOption">The option that was changed.</param>
|
||||
/// <typeparam name="T">The type of the option.</typeparam>
|
||||
public record ConfigChangeEvent<T>(T ConfigOption) : ConfigChangeEvent(ConfigOption) where T : Enum;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,24 @@
|
|||
|
||||
namespace Dalamud.Game.Config;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a string configuration property.
|
||||
/// </summary>
|
||||
/// <param name="Default">The default value.</param>
|
||||
public record StringConfigProperties(SeString? Default);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a uint configuration property.
|
||||
/// </summary>
|
||||
/// <param name="Default">The default value.</param>
|
||||
/// <param name="Minimum">The minimum value.</param>
|
||||
/// <param name="Maximum">The maximum value.</param>
|
||||
public record UIntConfigProperties(uint Default, uint Minimum, uint Maximum);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a floating point configuration property.
|
||||
/// </summary>
|
||||
/// <param name="Default">The default value.</param>
|
||||
/// <param name="Minimum">The minimum value.</param>
|
||||
/// <param name="Maximum">The maximum value.</param>
|
||||
public record FloatConfigProperties(float Default, float Minimum, float Maximum);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,57 @@ using Lumina.Excel.GeneratedSheets;
|
|||
|
||||
namespace Dalamud.Game.Network.Structures.InfoProxy;
|
||||
|
||||
/// <summary>
|
||||
/// Display group of a character. Used for friends.
|
||||
/// </summary>
|
||||
public enum DisplayGroup : sbyte
|
||||
{
|
||||
/// <summary>
|
||||
/// All display groups.
|
||||
/// </summary>
|
||||
All = -1,
|
||||
|
||||
/// <summary>
|
||||
/// No display group.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Star display group.
|
||||
/// </summary>
|
||||
Star,
|
||||
|
||||
/// <summary>
|
||||
/// Circle display group.
|
||||
/// </summary>
|
||||
Circle,
|
||||
|
||||
/// <summary>
|
||||
/// Triangle display group.
|
||||
/// </summary>
|
||||
Triangle,
|
||||
|
||||
/// <summary>
|
||||
/// Diamond display group.
|
||||
/// </summary>
|
||||
Diamond,
|
||||
|
||||
/// <summary>
|
||||
/// Heart display group.
|
||||
/// </summary>
|
||||
Heart,
|
||||
|
||||
/// <summary>
|
||||
/// Spade display group.
|
||||
/// </summary>
|
||||
Spade,
|
||||
|
||||
/// <summary>
|
||||
/// Club display group.
|
||||
/// </summary>
|
||||
Club,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dalamud wrapper around a client structs <see cref="InfoProxyCommonList.CharacterData"/>.
|
||||
/// </summary>
|
||||
|
|
@ -144,54 +195,3 @@ public unsafe class CharacterData
|
|||
/// </summary>
|
||||
internal InfoProxyCommonList.CharacterData* Struct => (InfoProxyCommonList.CharacterData*)this.Address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display group of a character. Used for friends.
|
||||
/// </summary>
|
||||
public enum DisplayGroup : sbyte
|
||||
{
|
||||
/// <summary>
|
||||
/// All display groups.
|
||||
/// </summary>
|
||||
All = -1,
|
||||
|
||||
/// <summary>
|
||||
/// No display group.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Star display group.
|
||||
/// </summary>
|
||||
Star,
|
||||
|
||||
/// <summary>
|
||||
/// Circle display group.
|
||||
/// </summary>
|
||||
Circle,
|
||||
|
||||
/// <summary>
|
||||
/// Triangle display group.
|
||||
/// </summary>
|
||||
Triangle,
|
||||
|
||||
/// <summary>
|
||||
/// Diamond display group.
|
||||
/// </summary>
|
||||
Diamond,
|
||||
|
||||
/// <summary>
|
||||
/// Heart display group.
|
||||
/// </summary>
|
||||
Heart,
|
||||
|
||||
/// <summary>
|
||||
/// Spade display group.
|
||||
/// </summary>
|
||||
Spade,
|
||||
|
||||
/// <summary>
|
||||
/// Club display group.
|
||||
/// </summary>
|
||||
Club,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,7 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
/// </summary>
|
||||
public class AutoTranslatePayload : Payload, ITextProvider
|
||||
{
|
||||
private string text;
|
||||
|
||||
[JsonProperty("group")]
|
||||
public uint Group { get; private set; }
|
||||
|
||||
[JsonProperty("key")]
|
||||
public uint Key { get; private set; }
|
||||
private string? text;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoTranslatePayload"/> class.
|
||||
|
|
@ -44,6 +38,18 @@ public class AutoTranslatePayload : Payload, ITextProvider
|
|||
internal AutoTranslatePayload()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the autotranslate group.
|
||||
/// </summary>
|
||||
[JsonProperty("group")]
|
||||
public uint Group { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the autotranslate key.
|
||||
/// </summary>
|
||||
[JsonProperty("key")]
|
||||
public uint Key { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.AutoTranslateText;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ using System.Diagnostics.CodeAnalysis;
|
|||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1117:ParametersMustBeOnSameLineOrSeparateLines", Justification = "I don't care anymore")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1407:ArithmeticExpressionsMustDeclarePrecedence", Justification = "I don't care anymore")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1116:SplitParametersMustStartOnLineAfterDeclaration", Justification = "Reviewed.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "This would be nice, but a big refactor")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:FileNameMustMatchTypeName", Justification = "I don't like this one so much")]
|
||||
|
||||
// ImRAII stuff
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", Justification = "Reviewed.", Scope = "namespaceanddescendants", Target = "Dalamud.Interface.Utility.Raii")]
|
||||
|
|
|
|||
|
|
@ -10,17 +10,6 @@ namespace Dalamud.Interface;
|
|||
/// </summary>
|
||||
public static class ColorHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A struct representing a color using HSVA coordinates.
|
||||
/// </summary>
|
||||
/// <param name="H">The hue represented by this struct.</param>
|
||||
/// <param name="S">The saturation represented by this struct.</param>
|
||||
/// <param name="V">The value represented by this struct.</param>
|
||||
/// <param name="A">The alpha represented by this struct.</param>
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter",
|
||||
Justification = "I don't like it.")]
|
||||
public record struct HsvaColor(float H, float S, float V, float A);
|
||||
|
||||
/// <summary>
|
||||
/// Pack a vector4 color into a uint for use in ImGui APIs.
|
||||
/// </summary>
|
||||
|
|
@ -316,4 +305,15 @@ public static class ColorHelpers
|
|||
|
||||
_ => color / 255.0f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A struct representing a color using HSVA coordinates.
|
||||
/// </summary>
|
||||
/// <param name="H">The hue represented by this struct.</param>
|
||||
/// <param name="S">The saturation represented by this struct.</param>
|
||||
/// <param name="V">The value represented by this struct.</param>
|
||||
/// <param name="A">The alpha represented by this struct.</param>
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter",
|
||||
Justification = "I don't like it.")]
|
||||
public record struct HsvaColor(float H, float S, float V, float A);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,19 +25,19 @@ internal class DalamudCommands : IServiceType
|
|||
{
|
||||
commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand)
|
||||
{
|
||||
HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."),
|
||||
HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon. For debug use only!"),
|
||||
ShowInHelp = false,
|
||||
});
|
||||
|
||||
commandManager.AddHandler("/xlkill", new CommandInfo(this.OnKillCommand)
|
||||
{
|
||||
HelpMessage = "Kill the game.",
|
||||
HelpMessage = "Kill the game. For debug use only!",
|
||||
ShowInHelp = false,
|
||||
});
|
||||
|
||||
commandManager.AddHandler("/xlrestart", new CommandInfo(this.OnRestartCommand)
|
||||
{
|
||||
HelpMessage = "Restart the game.",
|
||||
HelpMessage = "Restart the game. For debug use only!",
|
||||
ShowInHelp = false,
|
||||
});
|
||||
|
||||
|
|
@ -80,13 +80,11 @@ internal class DalamudCommands : IServiceType
|
|||
commandManager.AddHandler("/xlstats", new CommandInfo(this.OnTogglePluginStats)
|
||||
{
|
||||
HelpMessage = Loc.Localize("DalamudPluginStats", "Draw plugin statistics window"),
|
||||
ShowInHelp = false,
|
||||
});
|
||||
|
||||
commandManager.AddHandler("/xlbranch", new CommandInfo(this.OnToggleBranchSwitcher)
|
||||
{
|
||||
HelpMessage = Loc.Localize("DalamudBranchSwitcher", "Draw branch switcher"),
|
||||
ShowInHelp = false,
|
||||
HelpMessage = Loc.Localize("DalamudBranchSwitcher", "Open the branch switcher"),
|
||||
});
|
||||
|
||||
commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu)
|
||||
|
|
@ -97,8 +95,7 @@ internal class DalamudCommands : IServiceType
|
|||
|
||||
commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog)
|
||||
{
|
||||
HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"),
|
||||
ShowInHelp = false,
|
||||
HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open the plugin log window/console"),
|
||||
});
|
||||
|
||||
commandManager.AddHandler("/xlplugins", new CommandInfo(this.OnOpenInstallerCommand)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
using CheapLoc;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Console;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
|
|
@ -101,7 +102,8 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
Game.Framework framework,
|
||||
ClientState clientState,
|
||||
TitleScreenMenu titleScreenMenu,
|
||||
GameGui gameGui)
|
||||
GameGui gameGui,
|
||||
ConsoleManager consoleManager)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
this.configuration = configuration;
|
||||
|
|
@ -126,7 +128,8 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
fontAtlasFactory,
|
||||
framework,
|
||||
gameGui,
|
||||
titleScreenMenu) { IsOpen = false };
|
||||
titleScreenMenu,
|
||||
consoleManager) { IsOpen = false };
|
||||
this.changelogWindow = new ChangelogWindow(
|
||||
this.titleScreenMenuWindow,
|
||||
fontAtlasFactory,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Text;
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Console;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Colors;
|
||||
|
|
@ -89,6 +90,14 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
SerilogEventSink.Instance.LogLine += this.OnLogLine;
|
||||
|
||||
Service<Framework>.GetAsync().ContinueWith(r => r.Result.Update += this.FrameworkOnUpdate);
|
||||
|
||||
var cm = Service<ConsoleManager>.Get();
|
||||
cm.AddCommand("clear", "Clear the console log", () =>
|
||||
{
|
||||
this.QueueClear();
|
||||
return true;
|
||||
});
|
||||
cm.AddAlias("clear", "cls");
|
||||
|
||||
this.Size = new Vector2(500, 400);
|
||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||
|
|
@ -786,11 +795,6 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
{
|
||||
try
|
||||
{
|
||||
if (this.commandText.StartsWith('/'))
|
||||
{
|
||||
this.commandText = this.commandText[1..];
|
||||
}
|
||||
|
||||
this.historyPos = -1;
|
||||
for (var i = this.history.Count - 1; i >= 0; i--)
|
||||
{
|
||||
|
|
@ -809,7 +813,7 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
this.lastCmdSuccess = Service<CommandManager>.Get().ProcessCommand("/" + this.commandText);
|
||||
this.lastCmdSuccess = Service<ConsoleManager>.Get().ProcessCommand(this.commandText);
|
||||
this.commandText = string.Empty;
|
||||
|
||||
// TODO: Force scroll to bottom
|
||||
|
|
@ -838,15 +842,21 @@ internal class ConsoleWindow : Window, IDisposable
|
|||
if (words.Length > 1)
|
||||
return 0;
|
||||
|
||||
// TODO: Improve this, add partial completion
|
||||
// TODO: Improve this, add partial completion, arguments, description, etc.
|
||||
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
|
||||
var candidates = Service<CommandManager>.Get().Commands
|
||||
.Where(x => x.Key.Contains("/" + words[0]))
|
||||
.ToList();
|
||||
if (candidates.Count > 0)
|
||||
var candidates = Service<ConsoleManager>.Get().Entries
|
||||
.Where(x => x.Key.StartsWith(words[0]))
|
||||
.Select(x => x.Key);
|
||||
|
||||
candidates = candidates.Union(
|
||||
Service<CommandManager>.Get().Commands
|
||||
.Where(x => x.Key.StartsWith(words[0])).Select(x => x.Key));
|
||||
|
||||
var enumerable = candidates as string[] ?? candidates.ToArray();
|
||||
if (enumerable.Length != 0)
|
||||
{
|
||||
ptr.DeleteChars(0, ptr.BufTextLen);
|
||||
ptr.InsertChars(0, candidates[0].Key.Replace("/", string.Empty));
|
||||
ptr.InsertChars(0, enumerable[0]);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -17,17 +17,6 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
/// </summary>
|
||||
internal class NetworkMonitorWidget : IDataWindowWidget
|
||||
{
|
||||
#pragma warning disable SA1313
|
||||
private readonly record struct NetworkPacketData(ushort OpCode, NetworkMessageDirection Direction, uint SourceActorId, uint TargetActorId)
|
||||
#pragma warning restore SA1313
|
||||
{
|
||||
public readonly IReadOnlyList<byte> Data = Array.Empty<byte>();
|
||||
|
||||
public NetworkPacketData(NetworkMonitorWidget widget, ushort opCode, NetworkMessageDirection direction, uint sourceActorId, uint targetActorId, nint dataPtr)
|
||||
: this(opCode, direction, sourceActorId, targetActorId)
|
||||
=> this.Data = MemoryHelper.Read<byte>(dataPtr, widget.GetSizeFromOpCode(opCode), false);
|
||||
}
|
||||
|
||||
private readonly ConcurrentQueue<NetworkPacketData> packets = new();
|
||||
|
||||
private bool trackNetwork;
|
||||
|
|
@ -214,4 +203,15 @@ internal class NetworkMonitorWidget : IDataWindowWidget
|
|||
/// <remarks> The filter should find opCodes by number (decimal and hex) and name, if existing. </remarks>
|
||||
private string OpCodeToString(ushort opCode)
|
||||
=> $"{opCode}\0{opCode:X}";
|
||||
|
||||
#pragma warning disable SA1313
|
||||
private readonly record struct NetworkPacketData(ushort OpCode, NetworkMessageDirection Direction, uint SourceActorId, uint TargetActorId)
|
||||
#pragma warning restore SA1313
|
||||
{
|
||||
public readonly IReadOnlyList<byte> Data = Array.Empty<byte>();
|
||||
|
||||
public NetworkPacketData(NetworkMonitorWidget widget, ushort opCode, NetworkMessageDirection direction, uint sourceActorId, uint targetActorId, nint dataPtr)
|
||||
: this(opCode, direction, sourceActorId, targetActorId)
|
||||
=> this.Data = MemoryHelper.Read<byte>(dataPtr, widget.GetSizeFromOpCode(opCode), false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
|||
using CheapLoc;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Console;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Animation.EasingFunctions;
|
||||
using Dalamud.Interface.Colors;
|
||||
|
|
@ -365,10 +366,13 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
/// <returns>A value indicating whether to continue with the next task.</returns>
|
||||
public bool DisplayErrorContinuation(Task task, object state)
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
var errorModalMessage = state as string;
|
||||
if (!task.IsFaulted && !task.IsCanceled)
|
||||
return true;
|
||||
|
||||
var newErrorMessage = state as string;
|
||||
|
||||
if (task.Exception != null)
|
||||
{
|
||||
foreach (var ex in task.Exception.InnerExceptions)
|
||||
{
|
||||
if (ex is PluginException)
|
||||
|
|
@ -376,7 +380,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
Log.Error(ex, "Plugin installer threw an error");
|
||||
#if DEBUG
|
||||
if (!string.IsNullOrEmpty(ex.Message))
|
||||
errorModalMessage += $"\n\n{ex.Message}";
|
||||
newErrorMessage += $"\n\n{ex.Message}";
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
|
@ -384,17 +388,18 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
Log.Error(ex, "Plugin installer threw an unexpected error");
|
||||
#if DEBUG
|
||||
if (!string.IsNullOrEmpty(ex.Message))
|
||||
errorModalMessage += $"\n\n{ex.Message}";
|
||||
newErrorMessage += $"\n\n{ex.Message}";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
this.ShowErrorModal(errorModalMessage);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (task.IsCanceled)
|
||||
Log.Error("A task was cancelled");
|
||||
|
||||
return true;
|
||||
this.ShowErrorModal(newErrorMessage ?? "An unknown error occurred.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetOpenPage(PluginInstallerOpenKind kind)
|
||||
|
|
@ -2472,6 +2477,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
{
|
||||
ImGuiHelpers.ScaledDummy(3);
|
||||
ImGui.TextColored(ImGuiColors.DalamudGrey, $"WorkingPluginId: {plugin.EffectiveWorkingPluginId}");
|
||||
ImGui.TextColored(ImGuiColors.DalamudGrey, $"Command prefix: {ConsoleManagerPluginUtil.GetSanitizedNamespaceName(plugin.InternalName)}");
|
||||
ImGuiHelpers.ScaledDummy(3);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Console;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.Gui;
|
||||
|
|
@ -41,6 +42,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
private readonly Dictionary<Guid, InOutCubic> shadeEasings = new();
|
||||
private readonly Dictionary<Guid, InOutQuint> moveEasings = new();
|
||||
private readonly Dictionary<Guid, InOutCubic> logoEasings = new();
|
||||
|
||||
private readonly IConsoleVariable<bool> showTsm;
|
||||
|
||||
private InOutCubic? fadeOutEasing;
|
||||
|
||||
|
|
@ -55,7 +58,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
/// <param name="fontAtlasFactory">An instance of <see cref="FontAtlasFactory"/>.</param>
|
||||
/// <param name="framework">An instance of <see cref="Framework"/>.</param>
|
||||
/// <param name="titleScreenMenu">An instance of <see cref="TitleScreenMenu"/>.</param>
|
||||
/// <param name="gameGui">An instance of <see cref="gameGui"/>.</param>
|
||||
/// <param name="gameGui">An instance of <see cref="GameGui"/>.</param>
|
||||
/// <param name="consoleManager">An instance of <see cref="ConsoleManager"/>.</param>
|
||||
public TitleScreenMenuWindow(
|
||||
ClientState clientState,
|
||||
DalamudConfiguration configuration,
|
||||
|
|
@ -63,12 +67,15 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
FontAtlasFactory fontAtlasFactory,
|
||||
Framework framework,
|
||||
GameGui gameGui,
|
||||
TitleScreenMenu titleScreenMenu)
|
||||
TitleScreenMenu titleScreenMenu,
|
||||
ConsoleManager consoleManager)
|
||||
: base(
|
||||
"TitleScreenMenuOverlay",
|
||||
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
|
||||
ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus)
|
||||
{
|
||||
this.showTsm = consoleManager.AddVariable("dalamud.show_tsm", "Show the Title Screen Menu", true);
|
||||
|
||||
this.clientState = clientState;
|
||||
this.configuration = configuration;
|
||||
this.gameGui = gameGui;
|
||||
|
|
@ -136,7 +143,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
if (!this.AllowDrawing)
|
||||
if (!this.AllowDrawing || !this.showTsm.Value)
|
||||
return;
|
||||
|
||||
var scale = ImGui.GetIO().FontGlobalScale;
|
||||
|
|
|
|||
|
|
@ -10,166 +10,6 @@ using JetBrains.Annotations;
|
|||
|
||||
namespace Dalamud.Interface.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Utility methods for <see cref="ImVectorWrapper{T}"/>.
|
||||
/// </summary>
|
||||
public static class ImVectorWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ImVectorWrapper{T}"/> struct, initialized with
|
||||
/// <paramref name="sourceEnumerable"/>.<br />
|
||||
/// You must call <see cref="ImVectorWrapper{T}.Dispose"/> after use.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="sourceEnumerable">The initial data.</param>
|
||||
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||
/// <param name="minCapacity">The minimum capacity of the new vector.</param>
|
||||
/// <returns>The new wrapped vector, that has to be disposed after use.</returns>
|
||||
public static ImVectorWrapper<T> CreateFromEnumerable<T>(
|
||||
IEnumerable<T> sourceEnumerable,
|
||||
ImVectorWrapper<T>.ImGuiNativeDestroyDelegate? destroyer = null,
|
||||
int minCapacity = 0)
|
||||
where T : unmanaged
|
||||
{
|
||||
var res = new ImVectorWrapper<T>(0, destroyer);
|
||||
try
|
||||
{
|
||||
switch (sourceEnumerable)
|
||||
{
|
||||
case T[] c:
|
||||
res.SetCapacity(Math.Max(minCapacity, c.Length + 1));
|
||||
res.LengthUnsafe = c.Length;
|
||||
c.AsSpan().CopyTo(res.DataSpan);
|
||||
break;
|
||||
case ICollection c:
|
||||
res.SetCapacity(Math.Max(minCapacity, c.Count + 1));
|
||||
res.AddRange(sourceEnumerable);
|
||||
break;
|
||||
case ICollection<T> c:
|
||||
res.SetCapacity(Math.Max(minCapacity, c.Count + 1));
|
||||
res.AddRange(sourceEnumerable);
|
||||
break;
|
||||
default:
|
||||
res.SetCapacity(minCapacity);
|
||||
res.AddRange(sourceEnumerable);
|
||||
res.EnsureCapacity(res.LengthUnsafe + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Null termination
|
||||
Debug.Assert(res.LengthUnsafe < res.CapacityUnsafe, "Capacity must be more than source length + 1");
|
||||
res.StorageSpan[res.LengthUnsafe] = default;
|
||||
|
||||
return res;
|
||||
}
|
||||
catch
|
||||
{
|
||||
res.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ImVectorWrapper{T}"/> struct, initialized with
|
||||
/// <paramref name="sourceSpan"/>.<br />
|
||||
/// You must call <see cref="ImVectorWrapper{T}.Dispose"/> after use.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="sourceSpan">The initial data.</param>
|
||||
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||
/// <param name="minCapacity">The minimum capacity of the new vector.</param>
|
||||
/// <returns>The new wrapped vector, that has to be disposed after use.</returns>
|
||||
public static ImVectorWrapper<T> CreateFromSpan<T>(
|
||||
ReadOnlySpan<T> sourceSpan,
|
||||
ImVectorWrapper<T>.ImGuiNativeDestroyDelegate? destroyer = null,
|
||||
int minCapacity = 0)
|
||||
where T : unmanaged
|
||||
{
|
||||
var res = new ImVectorWrapper<T>(Math.Max(minCapacity, sourceSpan.Length + 1), destroyer);
|
||||
try
|
||||
{
|
||||
res.LengthUnsafe = sourceSpan.Length;
|
||||
sourceSpan.CopyTo(res.DataSpan);
|
||||
|
||||
// Null termination
|
||||
Debug.Assert(res.LengthUnsafe < res.CapacityUnsafe, "Capacity must be more than source length + 1");
|
||||
res.StorageSpan[res.LengthUnsafe] = default;
|
||||
return res;
|
||||
}
|
||||
catch
|
||||
{
|
||||
res.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFontAtlas.ConfigData"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImFontConfig> ConfigDataWrapped(this ImFontAtlasPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->ConfigData, ImGuiNative.ImFontConfig_destroy);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFontAtlas.Fonts"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImFontPtr> FontsWrapped(this ImFontAtlasPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->Fonts, x => ImGuiNative.ImFont_destroy(x->NativePtr));
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFontAtlas.Textures"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImFontAtlasTexture> TexturesWrapped(this ImFontAtlasPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->Textures);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFont.Glyphs"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImGuiHelpers.ImFontGlyphReal> GlyphsWrapped(this ImFontPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->Glyphs);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFont.IndexedHotData"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImGuiHelpers.ImFontGlyphHotDataReal> IndexedHotDataWrapped(this ImFontPtr obj)
|
||||
=> obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->IndexedHotData);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFont.IndexLookup"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ushort> IndexLookupWrapped(this ImFontPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->IndexLookup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for ImVector.
|
||||
/// </summary>
|
||||
|
|
@ -744,3 +584,163 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
|
||||
private int EnsureIndex(int i) => i >= 0 && i < this.LengthUnsafe ? i : throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility methods for <see cref="ImVectorWrapper{T}"/>.
|
||||
/// </summary>
|
||||
public static class ImVectorWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ImVectorWrapper{T}"/> struct, initialized with
|
||||
/// <paramref name="sourceEnumerable"/>.<br />
|
||||
/// You must call <see cref="ImVectorWrapper{T}.Dispose"/> after use.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="sourceEnumerable">The initial data.</param>
|
||||
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||
/// <param name="minCapacity">The minimum capacity of the new vector.</param>
|
||||
/// <returns>The new wrapped vector, that has to be disposed after use.</returns>
|
||||
public static ImVectorWrapper<T> CreateFromEnumerable<T>(
|
||||
IEnumerable<T> sourceEnumerable,
|
||||
ImVectorWrapper<T>.ImGuiNativeDestroyDelegate? destroyer = null,
|
||||
int minCapacity = 0)
|
||||
where T : unmanaged
|
||||
{
|
||||
var res = new ImVectorWrapper<T>(0, destroyer);
|
||||
try
|
||||
{
|
||||
switch (sourceEnumerable)
|
||||
{
|
||||
case T[] c:
|
||||
res.SetCapacity(Math.Max(minCapacity, c.Length + 1));
|
||||
res.LengthUnsafe = c.Length;
|
||||
c.AsSpan().CopyTo(res.DataSpan);
|
||||
break;
|
||||
case ICollection c:
|
||||
res.SetCapacity(Math.Max(minCapacity, c.Count + 1));
|
||||
res.AddRange(sourceEnumerable);
|
||||
break;
|
||||
case ICollection<T> c:
|
||||
res.SetCapacity(Math.Max(minCapacity, c.Count + 1));
|
||||
res.AddRange(sourceEnumerable);
|
||||
break;
|
||||
default:
|
||||
res.SetCapacity(minCapacity);
|
||||
res.AddRange(sourceEnumerable);
|
||||
res.EnsureCapacity(res.LengthUnsafe + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Null termination
|
||||
Debug.Assert(res.LengthUnsafe < res.CapacityUnsafe, "Capacity must be more than source length + 1");
|
||||
res.StorageSpan[res.LengthUnsafe] = default;
|
||||
|
||||
return res;
|
||||
}
|
||||
catch
|
||||
{
|
||||
res.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ImVectorWrapper{T}"/> struct, initialized with
|
||||
/// <paramref name="sourceSpan"/>.<br />
|
||||
/// You must call <see cref="ImVectorWrapper{T}.Dispose"/> after use.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="sourceSpan">The initial data.</param>
|
||||
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||
/// <param name="minCapacity">The minimum capacity of the new vector.</param>
|
||||
/// <returns>The new wrapped vector, that has to be disposed after use.</returns>
|
||||
public static ImVectorWrapper<T> CreateFromSpan<T>(
|
||||
ReadOnlySpan<T> sourceSpan,
|
||||
ImVectorWrapper<T>.ImGuiNativeDestroyDelegate? destroyer = null,
|
||||
int minCapacity = 0)
|
||||
where T : unmanaged
|
||||
{
|
||||
var res = new ImVectorWrapper<T>(Math.Max(minCapacity, sourceSpan.Length + 1), destroyer);
|
||||
try
|
||||
{
|
||||
res.LengthUnsafe = sourceSpan.Length;
|
||||
sourceSpan.CopyTo(res.DataSpan);
|
||||
|
||||
// Null termination
|
||||
Debug.Assert(res.LengthUnsafe < res.CapacityUnsafe, "Capacity must be more than source length + 1");
|
||||
res.StorageSpan[res.LengthUnsafe] = default;
|
||||
return res;
|
||||
}
|
||||
catch
|
||||
{
|
||||
res.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFontAtlas.ConfigData"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImFontConfig> ConfigDataWrapped(this ImFontAtlasPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->ConfigData, ImGuiNative.ImFontConfig_destroy);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFontAtlas.Fonts"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImFontPtr> FontsWrapped(this ImFontAtlasPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->Fonts, x => ImGuiNative.ImFont_destroy(x->NativePtr));
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFontAtlas.Textures"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImFontAtlasTexture> TexturesWrapped(this ImFontAtlasPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->Textures);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFont.Glyphs"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImGuiHelpers.ImFontGlyphReal> GlyphsWrapped(this ImFontPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->Glyphs);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFont.IndexedHotData"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ImGuiHelpers.ImFontGlyphHotDataReal> IndexedHotDataWrapped(this ImFontPtr obj)
|
||||
=> obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->IndexedHotData);
|
||||
|
||||
/// <summary>
|
||||
/// Wraps <see cref="ImFont.IndexLookup"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||
/// This does not need to be disposed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The owner object.</param>
|
||||
/// <returns>The wrapped vector.</returns>
|
||||
public static unsafe ImVectorWrapper<ushort> IndexLookupWrapped(this ImFontPtr obj) =>
|
||||
obj.NativePtr is null
|
||||
? throw new NullReferenceException()
|
||||
: new(&obj.NativePtr->IndexLookup);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,8 +141,15 @@ public class ModuleLog
|
|||
public void Fatal(Exception? exception, string messageTemplate, params object?[] values)
|
||||
=> this.WriteLog(LogEventLevel.Fatal, messageTemplate, exception, values);
|
||||
|
||||
/// <summary>
|
||||
/// Log a templated message to the in-game debug log.
|
||||
/// </summary>
|
||||
/// <param name="level">The log level to log with.</param>
|
||||
/// <param name="messageTemplate">The message template to log.</param>
|
||||
/// <param name="exception">The exception to log.</param>
|
||||
/// <param name="values">Values to log.</param>
|
||||
[MessageTemplateFormatMethod("messageTemplate")]
|
||||
private void WriteLog(
|
||||
public void WriteLog(
|
||||
LogEventLevel level, string messageTemplate, Exception? exception = null, params object?[] values)
|
||||
{
|
||||
// FIXME: Eventually, the `pluginName` tag should be removed from here and moved over to the actual log
|
||||
|
|
|
|||
|
|
@ -2,5 +2,12 @@
|
|||
|
||||
namespace Dalamud.Plugin;
|
||||
|
||||
/// <summary>
|
||||
/// State of an installed plugin.
|
||||
/// </summary>
|
||||
/// <param name="Name">The name of the plugin.</param>
|
||||
/// <param name="InternalName">The internal name of the plugin.</param>
|
||||
/// <param name="IsLoaded">Whether or not the plugin is loaded.</param>
|
||||
/// <param name="Version">The version of the plugin.</param>
|
||||
[Api10ToDo("Refactor into an interface, add wrappers for OpenMainUI and OpenConfigUI")]
|
||||
public record InstalledPluginState(string Name, string InternalName, bool IsLoaded, Version Version);
|
||||
|
|
|
|||
|
|
@ -178,7 +178,6 @@ internal class LocalPlugin : IDisposable
|
|||
/// Gets a value indicating whether or not this plugin is orphaned(belongs to a repo) or not.
|
||||
/// </summary>
|
||||
public bool IsOrphaned => !this.IsDev &&
|
||||
!this.manifest.InstalledFromUrl.IsNullOrEmpty() && // TODO(api8): Remove this, all plugins will have a proper flag
|
||||
this.GetSourceRepository() == null;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -413,28 +412,39 @@ internal class LocalPlugin : IDisposable
|
|||
this.ServiceScope = ioc.GetScope();
|
||||
this.ServiceScope.RegisterPrivateScopes(this); // Add this LocalPlugin as a private scope, so services can get it
|
||||
|
||||
if (this.manifest.LoadSync && this.manifest.LoadRequiredState is 0 or 1)
|
||||
try
|
||||
{
|
||||
this.instance = await framework.RunOnFrameworkThread(
|
||||
() => this.ServiceScope.CreateAsync(this.pluginType!, this.DalamudInterface!)) as IDalamudPlugin;
|
||||
if (this.manifest.LoadSync && this.manifest.LoadRequiredState is 0 or 1)
|
||||
{
|
||||
this.instance = await framework.RunOnFrameworkThread(
|
||||
() => this.ServiceScope.CreateAsync(
|
||||
this.pluginType!,
|
||||
this.DalamudInterface!)) as IDalamudPlugin;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.instance =
|
||||
await this.ServiceScope.CreateAsync(this.pluginType!, this.DalamudInterface!) as IDalamudPlugin;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.instance =
|
||||
await this.ServiceScope.CreateAsync(this.pluginType!, this.DalamudInterface!) as IDalamudPlugin;
|
||||
Log.Error(ex, "Exception in plugin constructor");
|
||||
this.instance = null;
|
||||
}
|
||||
|
||||
if (this.instance == null)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
this.DalamudInterface.DisposeInternal();
|
||||
this.UnloadAndDisposeState();
|
||||
|
||||
Log.Error(
|
||||
$"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
||||
"Error while loading {PluginName}, failed to bind and call the plugin constructor", this.InternalName);
|
||||
return;
|
||||
}
|
||||
|
||||
this.State = PluginState.Loaded;
|
||||
Log.Information($"Finished loading {this.DllFile.Name}");
|
||||
Log.Information("Finished loading {PluginName}", this.InternalName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -444,7 +454,7 @@ internal class LocalPlugin : IDisposable
|
|||
if (ex is PluginPreconditionFailedException)
|
||||
Log.Warning(ex.Message);
|
||||
else
|
||||
Log.Error(ex, $"Error while loading {this.Name}");
|
||||
Log.Error(ex, "Error while loading {PluginName}", this.InternalName);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
|
@ -499,15 +509,7 @@ internal class LocalPlugin : IDisposable
|
|||
await framework.RunOnFrameworkThread(() => this.instance?.Dispose());
|
||||
|
||||
this.instance = null;
|
||||
|
||||
this.DalamudInterface?.DisposeInternal();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.ServiceScope?.Dispose();
|
||||
this.ServiceScope = null;
|
||||
|
||||
this.pluginType = null;
|
||||
this.pluginAssembly = null;
|
||||
this.UnloadAndDisposeState();
|
||||
|
||||
if (!reloading)
|
||||
{
|
||||
|
|
@ -676,4 +678,19 @@ internal class LocalPlugin : IDisposable
|
|||
throw new InvalidPluginException(this.DllFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnloadAndDisposeState()
|
||||
{
|
||||
if (this.instance != null)
|
||||
throw new InvalidOperationException("Plugin instance should be disposed at this point");
|
||||
|
||||
this.DalamudInterface?.DisposeInternal();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.ServiceScope?.Dispose();
|
||||
this.ServiceScope = null;
|
||||
|
||||
this.pluginType = null;
|
||||
this.pluginAssembly = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ internal record LocalPluginManifest : PluginManifest, ILocalPluginManifest
|
|||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party
|
||||
/// repo. Unless the manifest has been manually modified, this is determined by the InstalledFromUrl being null.
|
||||
/// repo.
|
||||
/// </summary>
|
||||
public bool IsThirdParty => !this.InstalledFromUrl.IsNullOrEmpty() && this.InstalledFromUrl != SpecialPluginSource.MainRepo;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
internal record PluginPatchData
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginPatchData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">DLL file being loaded.</param>
|
||||
public PluginPatchData(FileSystemInfo dllFile)
|
||||
{
|
||||
this.Location = dllFile.FullName;
|
||||
this.CodeBase = new Uri(dllFile.FullName).AbsoluteUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets simulated Assembly.Location output.
|
||||
/// </summary>
|
||||
public string Location { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets simulated Assembly.CodeBase output.
|
||||
/// </summary>
|
||||
public string CodeBase { get; }
|
||||
}
|
||||
130
Dalamud/Plugin/Services/IConsole.cs
Normal file
130
Dalamud/Plugin/Services/IConsole.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Dalamud.Console;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functions to register console commands and variables.
|
||||
/// </summary>
|
||||
[Experimental("Dalamud001")]
|
||||
public interface IConsole
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets this plugin's namespace prefix, derived off its internal name.
|
||||
/// This is the prefix that all commands and variables registered by this plugin will have.
|
||||
/// If the internal name is "SamplePlugin", the prefix will be "sampleplugin.".
|
||||
/// </summary>
|
||||
public string Prefix { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Add a command to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="description">A description of the command.</param>
|
||||
/// <param name="func">Function to invoke when the command has been called. Must return a <see cref="bool"/> indicating success.</param>
|
||||
/// <returns>The added command.</returns>
|
||||
public IConsoleCommand AddCommand(string name, string description, Func<bool> func);
|
||||
|
||||
/// <summary>
|
||||
/// Add a command to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="description">A description of the command.</param>
|
||||
/// <param name="func">Function to invoke when the command has been called. Must return a <see cref="bool"/> indicating success.</param>
|
||||
/// <typeparam name="T1">The first argument to the command.</typeparam>
|
||||
/// <returns>The added command.</returns>
|
||||
public IConsoleCommand AddCommand<T1>(string name, string description, Func<bool, T1> func);
|
||||
|
||||
/// <summary>
|
||||
/// Add a command to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="description">A description of the command.</param>
|
||||
/// <param name="func">Function to invoke when the command has been called. Must return a <see cref="bool"/> indicating success.</param>
|
||||
/// <typeparam name="T1">The first argument to the command.</typeparam>
|
||||
/// <typeparam name="T2">The second argument to the command.</typeparam>
|
||||
/// <returns>The added command.</returns>
|
||||
public IConsoleCommand AddCommand<T1, T2>(string name, string description, Func<bool, T1, T2> func);
|
||||
|
||||
/// <summary>
|
||||
/// Add a command to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="description">A description of the command.</param>
|
||||
/// <param name="func">Function to invoke when the command has been called. Must return a <see cref="bool"/> indicating success.</param>
|
||||
/// <typeparam name="T1">The first argument to the command.</typeparam>
|
||||
/// <typeparam name="T2">The second argument to the command.</typeparam>
|
||||
/// <typeparam name="T3">The third argument to the command.</typeparam>
|
||||
/// <returns>The added command.</returns>
|
||||
public IConsoleCommand AddCommand<T1, T2, T3>(string name, string description, Func<bool, T1, T2, T3> func);
|
||||
|
||||
/// <summary>
|
||||
/// Add a command to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="description">A description of the command.</param>
|
||||
/// <param name="func">Function to invoke when the command has been called. Must return a <see cref="bool"/> indicating success.</param>
|
||||
/// <typeparam name="T1">The first argument to the command.</typeparam>
|
||||
/// <typeparam name="T2">The second argument to the command.</typeparam>
|
||||
/// <typeparam name="T3">The third argument to the command.</typeparam>
|
||||
/// <typeparam name="T4">The fourth argument to the command.</typeparam>
|
||||
/// <returns>The added command.</returns>
|
||||
public IConsoleCommand AddCommand<T1, T2, T3, T4>(
|
||||
string name, string description, Func<bool, T1, T2, T3, T4> func);
|
||||
|
||||
/// <summary>
|
||||
/// Add a command to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="description">A description of the command.</param>
|
||||
/// <param name="func">Function to invoke when the command has been called. Must return a <see cref="bool"/> indicating success.</param>
|
||||
/// <typeparam name="T1">The first argument to the command.</typeparam>
|
||||
/// <typeparam name="T2">The second argument to the command.</typeparam>
|
||||
/// <typeparam name="T3">The third argument to the command.</typeparam>
|
||||
/// <typeparam name="T4">The fourth argument to the command.</typeparam>
|
||||
/// <typeparam name="T5">The fifth argument to the command.</typeparam>
|
||||
/// <returns>The added command.</returns>
|
||||
public IConsoleCommand AddCommand<T1, T2, T3, T4, T5>(
|
||||
string name, string description, Func<bool, T1, T2, T3, T4, T5> func);
|
||||
|
||||
/// <summary>
|
||||
/// Add a variable to the console.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="description">A description of the variable.</param>
|
||||
/// <param name="defaultValue">The default value of the variable.</param>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
/// <returns>The added variable.</returns>
|
||||
public IConsoleVariable<T> AddVariable<T>(string name, string description, T defaultValue);
|
||||
|
||||
/// <summary>
|
||||
/// Add an alias to a console entry.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the entry to add an alias for.</param>
|
||||
/// <param name="alias">The alias to use.</param>
|
||||
/// <returns>The added alias.</returns>
|
||||
public IConsoleEntry AddAlias(string name, string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of a variable.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <typeparam name="T">The type of the variable.</typeparam>
|
||||
/// <returns>The value of the variable.</returns>
|
||||
public T GetVariable<T>(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Set the value of a variable.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <typeparam name="T">The type of the value to set.</typeparam>
|
||||
public void SetVariable<T>(string name, T value);
|
||||
|
||||
/// <summary>
|
||||
/// Remove an entry from the console.
|
||||
/// </summary>
|
||||
/// <param name="entry">The entry to remove.</param>
|
||||
public void RemoveEntry(IConsoleEntry entry);
|
||||
}
|
||||
|
|
@ -1,14 +1,20 @@
|
|||
#define BORDER_MATCHING
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
#pragma warning disable SA1600
|
||||
#pragma warning disable SA1602
|
||||
|
||||
internal enum MatchMode
|
||||
{
|
||||
Simple,
|
||||
Fuzzy,
|
||||
FuzzyParts,
|
||||
}
|
||||
|
||||
internal readonly ref struct FuzzyMatcher
|
||||
{
|
||||
private static readonly (int, int)[] EmptySegArray = Array.Empty<(int, int)>();
|
||||
|
|
@ -272,12 +278,5 @@ internal readonly ref struct FuzzyMatcher
|
|||
}
|
||||
}
|
||||
|
||||
internal enum MatchMode
|
||||
{
|
||||
Simple,
|
||||
Fuzzy,
|
||||
FuzzyParts,
|
||||
}
|
||||
|
||||
#pragma warning restore SA1600
|
||||
#pragma warning restore SA1602
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue