mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-20 22:47:45 +01:00
.
This commit is contained in:
parent
5c003d8cd4
commit
f8e9cc8988
43 changed files with 2215 additions and 668 deletions
|
|
@ -38,7 +38,12 @@ public unsafe class MetaService : IDisposable
|
|||
if (!actor.IsCharacter)
|
||||
return;
|
||||
|
||||
_hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte)(value ? 1 : 0));
|
||||
// The function seems to not do anything if the head is 0, sometimes?
|
||||
var old = actor.AsCharacter->DrawData.Head.Id;
|
||||
if (old == 0)
|
||||
actor.AsCharacter->DrawData.Head.Id = 1;
|
||||
_hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte)(value ? 0 : 1));
|
||||
actor.AsCharacter->DrawData.Head.Id = old;
|
||||
}
|
||||
|
||||
public void SetWeaponState(Actor actor, bool value)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
|
|
@ -13,17 +12,19 @@ namespace Glamourer.Interop;
|
|||
|
||||
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||
{
|
||||
private readonly Framework _framework;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objects;
|
||||
private readonly ActorService _actors;
|
||||
private readonly Framework _framework;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objects;
|
||||
private readonly ActorService _actors;
|
||||
private readonly TargetManager _targets;
|
||||
|
||||
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects, ActorService actors)
|
||||
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects, ActorService actors, TargetManager targets)
|
||||
{
|
||||
_framework = framework;
|
||||
_clientState = clientState;
|
||||
_objects = objects;
|
||||
_actors = actors;
|
||||
_targets = targets;
|
||||
}
|
||||
|
||||
public DateTime LastUpdate { get; private set; }
|
||||
|
|
@ -31,7 +32,8 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
public bool IsInGPose { get; private set; }
|
||||
public ushort World { get; private set; }
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
|
||||
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
|
||||
private readonly Dictionary<ActorIdentifier, ActorData> _allWorldIdentifiers = new(200);
|
||||
|
||||
public IReadOnlyDictionary<ActorIdentifier, ActorData> Identifiers
|
||||
=> _identifiers;
|
||||
|
|
@ -45,6 +47,7 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
LastUpdate = lastUpdate;
|
||||
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
||||
_identifiers.Clear();
|
||||
_allWorldIdentifiers.Clear();
|
||||
|
||||
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
|
||||
{
|
||||
|
|
@ -106,6 +109,23 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
{
|
||||
data.Objects.Add(character);
|
||||
}
|
||||
|
||||
if (identifier.Type is not (IdentifierType.Player or IdentifierType.Owned))
|
||||
return;
|
||||
|
||||
var allWorld = _actors.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue,
|
||||
identifier.Kind,
|
||||
identifier.DataId);
|
||||
|
||||
if (!_allWorldIdentifiers.TryGetValue(allWorld, out var allWorldData))
|
||||
{
|
||||
allWorldData = new ActorData(character, allWorld.ToString());
|
||||
_allWorldIdentifiers[allWorld] = allWorldData;
|
||||
}
|
||||
else
|
||||
{
|
||||
allWorldData.Objects.Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
public Actor GPosePlayer
|
||||
|
|
@ -114,6 +134,9 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
public Actor Player
|
||||
=> _objects.GetObjectAddress(0);
|
||||
|
||||
public Actor Target
|
||||
=> _targets.Target?.Address ?? nint.Zero;
|
||||
|
||||
public (ActorIdentifier Identifier, ActorData Data) PlayerData
|
||||
{
|
||||
get
|
||||
|
|
@ -121,7 +144,18 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
Update();
|
||||
return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data)
|
||||
? (ident, data)
|
||||
: (ActorIdentifier.Invalid, ActorData.Invalid);
|
||||
: (ident, ActorData.Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public (ActorIdentifier Identifier, ActorData Data) TargetData
|
||||
{
|
||||
get
|
||||
{
|
||||
Update();
|
||||
return Target.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data)
|
||||
? (ident, data)
|
||||
: (ident, ActorData.Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,15 +168,16 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
|||
public int Count
|
||||
=> Identifiers.Count;
|
||||
|
||||
/// <summary> Also (inefficiently) handles All Worlds players. </summary>
|
||||
/// <summary> Also handles All Worlds players. </summary>
|
||||
public bool ContainsKey(ActorIdentifier key)
|
||||
=> Identifiers.ContainsKey(key)
|
||||
|| key.HomeWorld == ushort.MaxValue
|
||||
&& Identifiers.Keys.FirstOrDefault(i => i.Type is IdentifierType.Player && i.PlayerName == key.PlayerName).IsValid;
|
||||
=> Identifiers.ContainsKey(key) || _allWorldIdentifiers.ContainsKey(key);
|
||||
|
||||
public bool TryGetValue(ActorIdentifier key, out ActorData value)
|
||||
=> Identifiers.TryGetValue(key, out value);
|
||||
|
||||
public bool TryGetValueAllWorld(ActorIdentifier key, out ActorData value)
|
||||
=> _allWorldIdentifiers.TryGetValue(key, out value);
|
||||
|
||||
public ActorData this[ActorIdentifier key]
|
||||
=> Identifiers[key];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Interop.Structs;
|
||||
|
|
@ -8,21 +12,52 @@ using Penumbra.Api.Helpers;
|
|||
|
||||
namespace Glamourer.Interop.Penumbra;
|
||||
|
||||
using CurrentSettings = ValueTuple<PenumbraApiEc, (bool, int, IDictionary<string, IList<string>>, bool)?>;
|
||||
|
||||
public readonly record struct Mod(string Name, string DirectoryName) : IComparable<Mod>
|
||||
{
|
||||
public int CompareTo(Mod other)
|
||||
{
|
||||
var nameComparison = string.Compare(Name, other.Name, StringComparison.Ordinal);
|
||||
if (nameComparison != 0)
|
||||
return nameComparison;
|
||||
|
||||
return string.Compare(DirectoryName, other.DirectoryName, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ModSettings(IDictionary<string, IList<string>> Settings, int Priority, bool Enabled)
|
||||
{
|
||||
public ModSettings()
|
||||
: this(new Dictionary<string, IList<string>>(), 0, false)
|
||||
{ }
|
||||
|
||||
public static ModSettings Empty
|
||||
=> new();
|
||||
}
|
||||
|
||||
public unsafe class PenumbraService : IDisposable
|
||||
{
|
||||
public const int RequiredPenumbraBreakingVersion = 4;
|
||||
public const int RequiredPenumbraFeatureVersion = 15;
|
||||
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
private FuncSubscriber<int, (bool, bool, string)> _objectCollection;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _modSettingChanged;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
private FuncSubscriber<int, (bool, bool, string)> _objectCollection;
|
||||
private FuncSubscriber<IList<(string, string)>> _getMods;
|
||||
private FuncSubscriber<ApiCollectionType, string> _currentCollection;
|
||||
private FuncSubscriber<string, string, string, bool, CurrentSettings> _getCurrentSettings;
|
||||
private FuncSubscriber<string, string, string, bool, PenumbraApiEc> _setMod;
|
||||
private FuncSubscriber<string, string, string, int, PenumbraApiEc> _setModPriority;
|
||||
private FuncSubscriber<string, string, string, string, string, PenumbraApiEc> _setModSetting;
|
||||
private FuncSubscriber<string, string, string, string, IReadOnlyList<string>, PenumbraApiEc> _setModSettings;
|
||||
|
||||
private readonly EventSubscriber _initializedEvent;
|
||||
private readonly EventSubscriber _disposedEvent;
|
||||
|
|
@ -72,6 +107,90 @@ public unsafe class PenumbraService : IDisposable
|
|||
remove => _modSettingChanged.Event -= value;
|
||||
}
|
||||
|
||||
public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods()
|
||||
{
|
||||
if (!Available)
|
||||
return Array.Empty<(Mod Mod, ModSettings Settings)>();
|
||||
|
||||
try
|
||||
{
|
||||
var allMods = _getMods.Invoke();
|
||||
var collection = _currentCollection.Invoke(ApiCollectionType.Current);
|
||||
return allMods
|
||||
.Select(m => (m.Item1, m.Item2, _getCurrentSettings.Invoke(collection, m.Item1, m.Item2, true)))
|
||||
.Where(t => t.Item3.Item1 is PenumbraApiEc.Success)
|
||||
.Select(t => (new Mod(t.Item2, t.Item1),
|
||||
!t.Item3.Item2.HasValue
|
||||
? ModSettings.Empty
|
||||
: new ModSettings(t.Item3.Item2!.Value.Item3, t.Item3.Item2!.Value.Item2, t.Item3.Item2!.Value.Item1)))
|
||||
.OrderByDescending(p => p.Item2.Enabled)
|
||||
.ThenBy(p => p.Item1.Name)
|
||||
.ThenBy(p => p.Item1.DirectoryName)
|
||||
.ThenByDescending(p => p.Item2.Priority)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Error fetching mods from Penumbra:\n{ex}");
|
||||
return Array.Empty<(Mod Mod, ModSettings Settings)>();
|
||||
}
|
||||
}
|
||||
|
||||
public string CurrentCollection
|
||||
=> Available ? _currentCollection.Invoke(ApiCollectionType.Current) : "<Unavailable>";
|
||||
|
||||
/// <summary>
|
||||
/// Try to set all mod settings as desired. Only sets when the mod should be enabled.
|
||||
/// If it is disabled, ignore all other settings.
|
||||
/// </summary>
|
||||
public string SetMod(Mod mod, ModSettings settings)
|
||||
{
|
||||
if (!Available)
|
||||
return "Penumbra is not available.";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
try
|
||||
{
|
||||
var collection = _currentCollection.Invoke(ApiCollectionType.Current);
|
||||
var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled);
|
||||
if (ec is PenumbraApiEc.ModMissing)
|
||||
return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found.";
|
||||
|
||||
Debug.Assert(ec is not PenumbraApiEc.CollectionMissing, "Missing collection should not be possible.");
|
||||
|
||||
if (!settings.Enabled)
|
||||
return string.Empty;
|
||||
|
||||
ec = _setModPriority.Invoke(collection, mod.DirectoryName, mod.Name, settings.Priority);
|
||||
Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail.");
|
||||
|
||||
foreach (var (setting, list) in settings.Settings)
|
||||
{
|
||||
ec = list.Count == 1
|
||||
? _setModSetting.Invoke(collection, mod.DirectoryName, mod.Name, setting, list[0])
|
||||
: _setModSettings.Invoke(collection, mod.DirectoryName, mod.Name, setting, (IReadOnlyList<string>)list);
|
||||
switch (ec)
|
||||
{
|
||||
case PenumbraApiEc.OptionGroupMissing:
|
||||
sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}.");
|
||||
break;
|
||||
case PenumbraApiEc.OptionMissing:
|
||||
sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}.");
|
||||
break;
|
||||
}
|
||||
|
||||
Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged,
|
||||
"Missing Mod or Collection should not be possible here.");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return sb.AppendLine(ex.Message).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Obtain the name of the collection currently assigned to the player. </summary>
|
||||
public string GetCurrentPlayerCollection()
|
||||
{
|
||||
|
|
@ -123,11 +242,19 @@ public unsafe class PenumbraService : IDisposable
|
|||
_creatingCharacterBase.Enable();
|
||||
_createdCharacterBase.Enable();
|
||||
_modSettingChanged.Enable();
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
||||
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface);
|
||||
Available = true;
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
||||
_objectCollection = Ipc.GetCollectionForObject.Subscriber(_pluginInterface);
|
||||
_getMods = Ipc.GetMods.Subscriber(_pluginInterface);
|
||||
_currentCollection = Ipc.GetCollectionForType.Subscriber(_pluginInterface);
|
||||
_getCurrentSettings = Ipc.GetCurrentModSettings.Subscriber(_pluginInterface);
|
||||
_setMod = Ipc.TrySetMod.Subscriber(_pluginInterface);
|
||||
_setModPriority = Ipc.TrySetModPriority.Subscriber(_pluginInterface);
|
||||
_setModSetting = Ipc.TrySetModSetting.Subscriber(_pluginInterface);
|
||||
_setModSettings = Ipc.TrySetModSettings.Subscriber(_pluginInterface);
|
||||
|
||||
Available = true;
|
||||
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue