Skip to content

Commit

Permalink
Fixes, IPC
Browse files Browse the repository at this point in the history
* Armature manager now assigns permanent ActorIdentifier to armatures
* Slight changes to make sure penumbra redraw doesn't break temporary profiles
* Profile selection UI now gets updated on login/logout in order to display correct colors
* Changed logic of setting Armature.IsVisible
* Implemented OnProfileUpdate IPC
  • Loading branch information
RisaDev committed Jan 22, 2024
1 parent 6dbd6a6 commit 7011914
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 44 deletions.
74 changes: 60 additions & 14 deletions CustomizePlus/Api/Compatibility/CustomizePlusIpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
using CustomizePlus.Profiles.Events;
using CustomizePlus.Templates.Data;
using CustomizePlus.GameData.Data;
using CustomizePlus.Core.Extensions;
using CustomizePlus.Armatures.Events;
using CustomizePlus.Armatures.Data;
using CustomizePlus.GameData.Extensions;

namespace CustomizePlus.Api.Compatibility;

Expand All @@ -28,19 +32,19 @@ public class CustomizePlusIpc : IDisposable
private readonly ProfileManager _profileManager;
private readonly GameObjectService _gameObjectService;

private readonly TemplateChanged _templateChangedEvent;
private readonly ProfileChanged _profileChangedEvent;
private readonly ArmatureChanged _armatureChangedEvent;

private const int _configurationVersion = 3;

public const string ProviderApiVersionLabel = $"CustomizePlus.{nameof(GetApiVersion)}";
public const string GetProfileFromCharacterLabel = $"CustomizePlus.{nameof(GetProfileFromCharacter)}";
public const string SetProfileToCharacterLabel = $"CustomizePlus.{nameof(SetProfileToCharacter)}";
public const string RevertCharacterLabel = $"CustomizePlus.{nameof(RevertCharacter)}";
//public const string OnProfileUpdateLabel = $"CustomizePlus.{nameof(OnProfileUpdate)}"; //I'm honestly not sure this is even used by mare
public const string OnProfileUpdateLabel = $"CustomizePlus.{nameof(OnProfileUpdate)}";
public static readonly (int, int) ApiVersion = (3, 0);

//Sends local player's profile on hooks reload (plugin startup) as well as any updates to their profile.
//Sends local player's profile every time their active profile is changed
//If no profile is applied sends null
internal ICallGateProvider<string?, string?, object?>? ProviderOnProfileUpdate;
internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
Expand All @@ -53,29 +57,72 @@ public CustomizePlusIpc(
DalamudPluginInterface pluginInterface,
Logger logger,
ProfileManager profileManager,
GameObjectService gameObjectService//,
/*TemplateChanged templateChangedEvent,
ProfileChanged profileChangedEvent*/)
GameObjectService gameObjectService,
ArmatureChanged armatureChangedEvent,
ProfileChanged profileChangedEvent)
{
_objectTable = objectTable;
_pluginInterface = pluginInterface;
_logger = logger;
_profileManager = profileManager;
_gameObjectService = gameObjectService;
/* _templateChangedEvent = templateChangedEvent;
_profileChangedEvent = profileChangedEvent;*/
_profileChangedEvent = profileChangedEvent;
_armatureChangedEvent = armatureChangedEvent;

InitializeProviders();

/*_templateChangedEvent.Subscribe(OnTemplateChange, TemplateChanged.Priority.CustomizePlusIpc);
_profileChangedEvent.Subscribe(OnProfileChange, ProfileChanged.Priority.CustomizePlusIpc);*/
_profileChangedEvent.Subscribe(OnProfileChange, ProfileChanged.Priority.CustomizePlusIpc);
_armatureChangedEvent.Subscribe(OnArmatureChanged, ArmatureChanged.Priority.CustomizePlusIpc);
}

public void Dispose()
{
_profileChangedEvent.Unsubscribe(OnProfileChange);
_armatureChangedEvent.Unsubscribe(OnArmatureChanged);
DisposeProviders();
}

//warn: limitation - ignores default profiles but why you would use default profile on your own character
private void OnProfileChange(ProfileChanged.Type type, Profile? profile, object? arg3)
{
if (type != ProfileChanged.Type.AddedTemplate &&
type != ProfileChanged.Type.RemovedTemplate &&
type != ProfileChanged.Type.MovedTemplate &&
type != ProfileChanged.Type.ChangedTemplate)
return;

if (profile == null ||
!profile.Enabled ||
profile.CharacterName.Text != _gameObjectService.GetCurrentPlayerName())
return;

OnProfileUpdate(profile);
}

private void OnArmatureChanged(ArmatureChanged.Type type, Armature armature, object? arg3)
{
string currentPlayerName = _gameObjectService.GetCurrentPlayerName();

if (armature.ActorIdentifier.ToNameWithoutOwnerName() != currentPlayerName)
return;

if (type == ArmatureChanged.Type.Created ||
type == ArmatureChanged.Type.Rebound)
{
if(armature.Profile == null)
_logger.Warning("Armature created/rebound and profile is null");

OnProfileUpdate(armature.Profile);
return;
}

if(type == ArmatureChanged.Type.Deleted)
{
OnProfileUpdate(null);
return;
}
}

private void InitializeProviders()
{
_logger.Debug("Initializing legacy Customize+ IPC providers.");
Expand Down Expand Up @@ -121,15 +168,15 @@ private void InitializeProviders()
{
_logger.Error($"Error registering legacy Customize+ IPC provider for {RevertCharacterLabel}: {ex}");
}
/*

try
{
ProviderOnProfileUpdate = _pluginInterface.GetIpcProvider<string?, string?, object?>(OnProfileUpdateLabel);
}
catch (Exception ex)
{
_logger.Error($"Error registering legacy Customize+ IPC provider for {OnProfileUpdateLabel}: {ex}");
}*/
}
}

private void DisposeProviders()
Expand All @@ -143,8 +190,7 @@ private void DisposeProviders()

private void OnProfileUpdate(Profile? profile)
{
//Get player's body profile string and send IPC message
_logger.Debug($"Sending local player update message: {profile?.Name ?? "no profile"} - {profile?.CharacterName ?? "no profile"}");
_logger.Debug($"Sending local player update message: {(profile != null ? profile.ToString() : "no profile")}");

var convertedProfile = profile != null ? GetVersion3Profile(profile) : null;

Expand Down
23 changes: 13 additions & 10 deletions CustomizePlus/Armatures/Data/Armature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public unsafe class Armature
/// </summary>
public bool IsVisible { get; set; }

/// <summary>
/// Represents date and time when actor associated with this armature was last seen.
/// Implemented mostly as a armature cleanup protection hack for mare and penumbra.
/// </summary>
public DateTime LastSeen { get; private set; }

/// <summary>
/// Gets a value indicating whether or not this armature has successfully built itself with bone information.
/// </summary>
Expand All @@ -41,12 +47,6 @@ public unsafe class Armature
/// </summary>
public bool IsPendingProfileRebind { get; set; }

/// <summary>
/// Represents date and time until which any kind of removal protections will not be applying to this armature.
/// Implemented mostly as a armature cleanup protection hack due to how mare works when downloading files for the first time
/// </summary>
public DateTime ProtectedUntil { get; private set; }

/// <summary>
/// For debugging purposes, each armature is assigned a globally-unique ID number upon creation.
/// </summary>
Expand Down Expand Up @@ -147,7 +147,7 @@ public Armature(ActorIdentifier actorIdentifier, Profile profile)
Profile = profile;
IsVisible = false;

ProtectFromRemoval();
UpdateLastSeen();

Profile.Armatures.Add(this);

Expand Down Expand Up @@ -257,11 +257,14 @@ public void AugmentSkeleton(CharacterBase* cBase)
}

/// <summary>
/// Apply removal protection for 30 seconds starting from current time. For the most part this is a hack for mare.
/// Update last time actor for this armature was last seen in the game
/// </summary>
public void ProtectFromRemoval()
public void UpdateLastSeen(DateTime? dateTime = null)
{
ProtectedUntil = DateTime.UtcNow.AddSeconds(30);
if(dateTime == null)
dateTime = DateTime.UtcNow;

LastSeen = (DateTime)dateTime;
}

private static unsafe List<List<ModelBone>> ParseBonesFromObject(Armature arm, CharacterBase* cBase)
Expand Down
10 changes: 6 additions & 4 deletions CustomizePlus/Armatures/Events/ArmatureChanged.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ namespace CustomizePlus.Armatures.Events;
/// <summary>
/// Triggered when armature is changed
/// </summary>
public sealed class ArmatureChanged() : EventWrapper<ArmatureChanged.Type, Armature?, object?, ArmatureChanged.Priority>(nameof(ArmatureChanged))
public sealed class ArmatureChanged() : EventWrapper<ArmatureChanged.Type, Armature, object?, ArmatureChanged.Priority>(nameof(ArmatureChanged))
{
public enum Type
{
//Created,
Deleted
Created,
Deleted,
Rebound
}

public enum Priority
{
ProfileManager
ProfileManager,
CustomizePlusIpc
}

public enum DeletionReason
Expand Down
38 changes: 27 additions & 11 deletions CustomizePlus/Armatures/Services/ArmatureManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using CustomizePlus.GameData.Services;
using CustomizePlus.GameData.Extensions;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using System.Drawing;

namespace CustomizePlus.Armatures.Services;

Expand Down Expand Up @@ -106,15 +107,21 @@ private void RefreshArmatures()
_objectManager.Update();

var currentTime = DateTime.UtcNow;
var armatureExpirationDateTime = currentTime.AddSeconds(-30);
foreach (var kvPair in Armatures.ToList())
{
var armature = kvPair.Value;
if (!_objectManager.ContainsKey(kvPair.Value.ActorIdentifier) &&
currentTime > armature.ProtectedUntil) //Only remove armatures which are no longer protected
armature.LastSeen <= armatureExpirationDateTime) //Only remove armatures which haven't been seen for a while
{
_logger.Debug($"Removing armature {armature} because {kvPair.Key.IncognitoDebug()} is gone");
RemoveArmature(armature, ArmatureChanged.DeletionReason.Gone);

continue;
}

//armature is considered visible if 1 or less seconds passed since last time we've seen the actor
armature.IsVisible = armature.LastSeen.AddSeconds(1) >= currentTime;
}

Profile? GetProfileForActor(ActorIdentifier identifier)
Expand All @@ -134,28 +141,32 @@ private void RefreshArmatures()

foreach (var obj in _objectManager)
{
if (!Armatures.ContainsKey(obj.Key))
var actorIdentifier = obj.Key.CreatePermanent();
if (!Armatures.ContainsKey(actorIdentifier))
{
var activeProfile = GetProfileForActor(obj.Key);
var activeProfile = GetProfileForActor(actorIdentifier);
if (activeProfile == null)
continue;

var newArm = new Armature(obj.Key, activeProfile);
var newArm = new Armature(actorIdentifier, activeProfile);
TryLinkSkeleton(newArm);
Armatures.Add(obj.Key, newArm);
_logger.Debug($"Added '{newArm}' for {obj.Key.IncognitoDebug()} to cache");
Armatures.Add(actorIdentifier, newArm);
_logger.Debug($"Added '{newArm}' for {actorIdentifier.IncognitoDebug()} to cache");
_event.Invoke(ArmatureChanged.Type.Created, newArm, activeProfile);

continue;
}

var armature = Armatures[obj.Key];
var armature = Armatures[actorIdentifier];

armature.UpdateLastSeen(currentTime);

if (armature.IsPendingProfileRebind)
{
_logger.Debug($"Armature {armature} is pending profile rebind, rebinding...");
armature.IsPendingProfileRebind = false;

var activeProfile = GetProfileForActor(obj.Key);
var activeProfile = GetProfileForActor(actorIdentifier);
if (activeProfile == armature.Profile)
continue;

Expand All @@ -166,14 +177,19 @@ private void RefreshArmatures()
continue;
}

Profile oldProfile = armature.Profile;

armature.Profile.Armatures.Remove(armature);
armature.Profile = activeProfile;
activeProfile.Armatures.Add(armature);
armature.RebuildBoneTemplateBinding();

_event.Invoke(ArmatureChanged.Type.Rebound, armature, activeProfile);
}

armature.IsVisible = armature.Profile.Enabled && TryLinkSkeleton(armature); //todo: remove armatures which are not visible?
//Needed because skeleton sometimes appears to be not ready when armature is created
//and also because we want to augment armature with new bones if they are available
TryLinkSkeleton(armature);
}
}

Expand Down Expand Up @@ -496,7 +512,7 @@ type is not ProfileChanged.Type.ChangedDefaultProfile &&
if (armature.Profile == profile)
return;

armature.ProtectFromRemoval();
armature.UpdateLastSeen();

armature.IsPendingProfileRebind = true;

Expand All @@ -516,7 +532,7 @@ type is not ProfileChanged.Type.ChangedDefaultProfile &&
foreach (var armature in profile.Armatures)
{
if (type == ProfileChanged.Type.TemporaryProfileDeleted)
armature.ProtectFromRemoval(); //just to be safe
armature.UpdateLastSeen(); //just to be safe

armature.IsPendingProfileRebind = true;
}
Expand Down
4 changes: 2 additions & 2 deletions CustomizePlus/Game/Services/GameObjectService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ public bool IsActorHasScalableRoot(Actor actor)
{
if (kvPair.Value.Objects.Count > 1) //in gpose we can have more than a single object for one actor
foreach (var obj in kvPair.Value.Objects)
yield return (kvPair.Key, obj);
yield return (kvPair.Key.CreatePermanent(), obj);
else
yield return (kvPair.Key, kvPair.Value.Objects[0]);
yield return (kvPair.Key.CreatePermanent(), kvPair.Value.Objects[0]);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion CustomizePlus/Profiles/ProfileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ private void OnReload(ReloadEvent.Type type)
}


private void OnArmatureChange(ArmatureChanged.Type type, Armature? armature, object? arg3)
private void OnArmatureChange(ArmatureChanged.Type type, Armature armature, object? arg3)
{
if (type == ArmatureChanged.Type.Deleted)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ private void DrawSingleArmature(string prefix, Armature armature)

ImGui.Text($"Profile: {armature.Profile.Name.Text.Incognify()} ({armature.Profile.UniqueId})");
ImGui.Text($"Actor: {armature.ActorIdentifier.IncognitoDebug()}");
ImGui.Text($"Protection: {(armature.ProtectedUntil >= DateTime.UtcNow ? "Active" : "NOT active")} [{armature.ProtectedUntil} (UTC)]");
ImGui.Text($"Last seen: {armature.LastSeen} (UTC)");
//ImGui.Text("Profile:");
//DrawSingleProfile($"armature-{armature.GetHashCode()}", armature.Profile);

Expand Down
Loading

0 comments on commit 7011914

Please sign in to comment.