This commit is contained in:
Ottermandias 2023-07-07 20:24:44 +02:00
parent 5c003d8cd4
commit f8e9cc8988
43 changed files with 2215 additions and 668 deletions

View file

@ -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)

View file

@ -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];

View file

@ -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)