some cleanup and sanitizing
This commit is contained in:
@@ -169,7 +169,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
|
||||
private void DalamudUtil_FrameworkUpdate()
|
||||
{
|
||||
_cachedFrameAddresses = _playerRelatedPointers.Select(c => c.CurrentAddress().GetAwaiter().GetResult()).ToHashSet();
|
||||
_cachedFrameAddresses = _playerRelatedPointers.Select(c => c.CurrentAddress()).ToHashSet();
|
||||
_cachedHandledPaths.Clear();
|
||||
foreach (var item in TransientResources.Where(item => !_dalamudUtil.IsGameObjectPresent(item.Key)).Select(i => i.Key).ToList())
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
private readonly ICallGateSubscriber<string, Character?, object> _customizePlusSetBodyScaleToCharacter;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
|
||||
private readonly SemaphoreSlim _glamourerApplicationSemaphore = new(2);
|
||||
private readonly SemaphoreSlim _redrawSemaphore = new(2);
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyAll;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyOnlyCustomization;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyOnlyEquipment;
|
||||
@@ -167,10 +167,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
|
||||
public bool CheckPenumbraApi() => _penumbraAvailable;
|
||||
|
||||
public async Task CustomizePlusRevert(IntPtr character)
|
||||
public async Task CustomizePlusRevertAsync(IntPtr character)
|
||||
{
|
||||
if (!CheckCustomizePlusApi()) return;
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X"));
|
||||
@@ -178,10 +178,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CustomizePlusSetBodyScale(IntPtr character, string scale)
|
||||
public async Task CustomizePlusSetBodyScaleAsync(IntPtr character, string scale)
|
||||
{
|
||||
if (!CheckCustomizePlusApi() || string.IsNullOrEmpty(scale)) return;
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale));
|
||||
@@ -190,7 +190,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetCustomizePlusScale()
|
||||
public async Task<string> GetCustomizePlusScaleAsync()
|
||||
{
|
||||
if (!CheckCustomizePlusApi()) return string.Empty;
|
||||
var scale = await _dalamudUtil.RunOnFrameworkThread(() => _customizePlusGetBodyScale.InvokeFunc(_dalamudUtil.PlayerName)).ConfigureAwait(false);
|
||||
@@ -204,61 +204,61 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
return _heelsGetOffset.InvokeFunc();
|
||||
}
|
||||
|
||||
public async Task GlamourerApplyAll(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false)
|
||||
public async Task GlamourerApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return;
|
||||
try
|
||||
{
|
||||
await _glamourerApplicationSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(handler.Address).ConfigureAwait(false);
|
||||
await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
await PenumbraRedrawAsync(logger, handler, applicationId, () =>
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId);
|
||||
_glamourerApplyAll!.InvokeAction(customization, c);
|
||||
}, fireAndForget).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_glamourerApplicationSemaphore.Release();
|
||||
_redrawSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GlamourerApplyCustomizationAndEquipment(ILogger logger, GameObjectHandler handler, string customization, string equipment, Guid applicationid, CancellationToken token, bool fireAndForget = false)
|
||||
public async Task GlamourerApplyCustomizationAndEquipmentAsync(ILogger logger, GameObjectHandler handler, string customization, string equipment, Guid applicationid, CancellationToken token, bool fireAndForget = false)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return;
|
||||
try
|
||||
{
|
||||
await _glamourerApplicationSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(handler.Address).ConfigureAwait(false);
|
||||
await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
await PenumbraRedrawAsync(logger, handler, applicationid, () =>
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyCustomization", applicationid);
|
||||
_glamourerApplyOnlyCustomization!.InvokeAction(customization, c);
|
||||
}, fireAndForget).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
await PenumbraRedrawAsync(logger, handler, applicationid, () =>
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyEquipment", applicationid);
|
||||
_glamourerApplyOnlyEquipment!.InvokeAction(equipment, c);
|
||||
}, fireAndForget).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_glamourerApplicationSemaphore.Release();
|
||||
_redrawSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GlamourerGetCharacterCustomization(IntPtr character)
|
||||
public async Task<string> GlamourerGetCharacterCustomizationAsync(IntPtr character)
|
||||
{
|
||||
if (!CheckGlamourerApi()) return string.Empty;
|
||||
try
|
||||
{
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
var glamourerString = await _dalamudUtil.RunOnFrameworkThread(() => _glamourerGetAllCustomization!.InvokeFunc(c)).ConfigureAwait(false);
|
||||
@@ -276,10 +276,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HeelsRestoreOffsetForPlayer(IntPtr character)
|
||||
public async Task HeelsRestoreOffsetForPlayerAsync(IntPtr character)
|
||||
{
|
||||
if (!CheckHeelsApi()) return;
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.LogTrace("Restoring Heels data to {chara}", character.ToString("X"));
|
||||
@@ -287,10 +287,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HeelsSetOffsetForPlayer(IntPtr character, float offset)
|
||||
public async Task HeelsSetOffsetForPlayerAsync(IntPtr character, float offset)
|
||||
{
|
||||
if (!CheckHeelsApi()) return;
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.LogTrace("Applying Heels data to {chara}", character.ToString("X"));
|
||||
@@ -298,10 +298,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HonorificClearTitle(nint character)
|
||||
public async Task HonorificClearTitleAsync(nint character)
|
||||
{
|
||||
if (!CheckHonorificApi()) return;
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj is PlayerCharacter c)
|
||||
{
|
||||
Logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X"));
|
||||
@@ -316,11 +316,11 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
return string.IsNullOrEmpty(title) ? string.Empty : $"{(isPrefix ? 1 : 0)}{title}";
|
||||
}
|
||||
|
||||
public async Task HonorificSetTitle(IntPtr character, string honorificData)
|
||||
public async Task HonorificSetTitleAsync(IntPtr character, string honorificData)
|
||||
{
|
||||
if (!CheckHonorificApi()) return;
|
||||
Logger.LogTrace("Applying Honorific data to {chara}", character.ToString("X"));
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj is PlayerCharacter pc)
|
||||
{
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
@@ -337,7 +337,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> PalettePlusBuildPalette()
|
||||
public async Task<string> PalettePlusBuildPaletteAsync()
|
||||
{
|
||||
if (!CheckPalettePlusApi()) return string.Empty;
|
||||
var palette = await _dalamudUtil.RunOnFrameworkThread(() => _palettePlusBuildCharaPalette.InvokeFunc(_dalamudUtil.PlayerCharacter)).ConfigureAwait(false);
|
||||
@@ -345,10 +345,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(palette));
|
||||
}
|
||||
|
||||
public async Task PalettePlusRemovePalette(IntPtr character)
|
||||
public async Task PalettePlusRemovePaletteAsync(IntPtr character)
|
||||
{
|
||||
if (!CheckPalettePlusApi()) return;
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.LogTrace("PalettePlus removing for {addr}", c.Address.ToString("X"));
|
||||
@@ -356,11 +356,11 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PalettePlusSetPalette(IntPtr character, string palette)
|
||||
public async Task PalettePlusSetPaletteAsync(IntPtr character, string palette)
|
||||
{
|
||||
if (!CheckPalettePlusApi()) return;
|
||||
string decodedPalette = Encoding.UTF8.GetString(Convert.FromBase64String(palette));
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(character).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
@@ -385,29 +385,29 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
return _penumbraGetMetaManipulations.Invoke();
|
||||
}
|
||||
|
||||
public async Task PenumbraRedraw(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token, bool fireAndForget = false)
|
||||
public async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token, bool fireAndForget = false)
|
||||
{
|
||||
if (!CheckPenumbraApi() || _dalamudUtil.IsZoning) return;
|
||||
try
|
||||
{
|
||||
await _glamourerApplicationSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObject(handler.Address).ConfigureAwait(false);
|
||||
await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
await PenumbraRedrawAsync(logger, handler, applicationId, () =>
|
||||
{
|
||||
logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId);
|
||||
_penumbraRedrawObject!.Invoke(c, RedrawType.Redraw);
|
||||
}, fireAndForget).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_glamourerApplicationSemaphore.Release();
|
||||
_redrawSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PenumbraRemoveTemporaryCollection(ILogger logger, Guid applicationId, string characterName)
|
||||
public async Task PenumbraRemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, string characterName)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
@@ -421,12 +421,12 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<(string[] forward, string[][] reverse)> PenumbraResolvePaths(string[] forward, string[] reverse)
|
||||
public async Task<(string[] forward, string[][] reverse)> PenumbraResolvePathsAsync(string[] forward, string[] reverse)
|
||||
{
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() => _penumbraResolvePaths.Invoke(forward, reverse)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task PenumbraSetTemporaryMods(ILogger logger, Guid applicationId, string characterName, int? idx, Dictionary<string, string> modPaths, string manipulationData)
|
||||
public async Task PenumbraSetTemporaryModsAsync(ILogger logger, Guid applicationId, string characterName, int? idx, Dictionary<string, string> modPaths, string manipulationData)
|
||||
{
|
||||
if (!CheckPenumbraApi() || idx == null) return;
|
||||
|
||||
@@ -650,36 +650,23 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
||||
_penumbraRedraw!.Invoke("self", RedrawType.Redraw);
|
||||
}
|
||||
|
||||
private async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler obj, Guid applicationId, Action action, bool fireAndForget)
|
||||
private async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler obj, Guid applicationId, Action action)
|
||||
{
|
||||
Mediator.Publish(new PenumbraStartRedrawMessage(obj.Address));
|
||||
|
||||
_penumbraRedrawRequests[obj.Address] = !fireAndForget;
|
||||
_penumbraRedrawRequests[obj.Address] = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (!fireAndForget)
|
||||
{
|
||||
while (!await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
if (obj.IsBeingDrawn()) return false;
|
||||
action();
|
||||
return true;
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
await Task.Delay(250).ConfigureAwait(false);
|
||||
}
|
||||
CancellationTokenSource cancelToken = new CancellationTokenSource();
|
||||
cancelToken.CancelAfter(TimeSpan.FromSeconds(15));
|
||||
await obj.ActOnFrameworkAfterEnsureNoDrawAsync(action, cancelToken.Token);
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), _disposalCts.Token).ConfigureAwait(false);
|
||||
|
||||
if (!_disposalCts.Token.IsCancellationRequested)
|
||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, obj, applicationId, 30000, _disposalCts.Token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _dalamudUtil.RunOnFrameworkThread(action);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_penumbraRedrawRequests[obj.Address] = false;
|
||||
|
||||
@@ -65,14 +65,14 @@ public class MareCharaFileManager
|
||||
}
|
||||
var applicationId = Guid.NewGuid();
|
||||
_ipcManager.ToggleGposeQueueMode(on: true);
|
||||
await _ipcManager.PenumbraRemoveTemporaryCollection(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraSetTemporaryMods(_logger, applicationId, charaTarget.Name.TextValue, charaTarget.ObjectTableIndex(),
|
||||
await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraSetTemporaryModsAsync(_logger, applicationId, charaTarget.Name.TextValue, charaTarget.ObjectTableIndex(),
|
||||
extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal),
|
||||
LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false);
|
||||
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => charaTarget.Address, false).ConfigureAwait(false);
|
||||
await _ipcManager.GlamourerApplyAll(_logger, tempHandler, LoadedCharaFile.CharaFileData.GlamourerData, applicationId, disposeCts.Token).ConfigureAwait(false);
|
||||
await _ipcManager.GlamourerApplyAllAsync(_logger, tempHandler, LoadedCharaFile.CharaFileData.GlamourerData, applicationId, disposeCts.Token).ConfigureAwait(false);
|
||||
_dalamudUtil.WaitWhileGposeCharacterIsDrawing(charaTarget.Address, 30000);
|
||||
await _ipcManager.PenumbraRemoveTemporaryCollection(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false);
|
||||
_ipcManager.ToggleGposeQueueMode(on: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ public class PlayerDataFactory
|
||||
// wait until chara is not drawing and present so nothing spontaneously explodes
|
||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: token).ConfigureAwait(false);
|
||||
int totalWaitTime = 10000;
|
||||
while (!DalamudUtilService.IsObjectPresent(await _dalamudUtil.RunOnFrameworkThread(() => _dalamudUtil.CreateGameObject(charaPointer).GetAwaiter().GetResult()).ConfigureAwait(false)) && totalWaitTime > 0)
|
||||
while (!DalamudUtilService.IsObjectPresent(await _dalamudUtil.RunOnFrameworkThread(() => _dalamudUtil.CreateGameObjectAsync(charaPointer).GetAwaiter().GetResult()).ConfigureAwait(false)) && totalWaitTime > 0)
|
||||
{
|
||||
_logger.LogTrace("Character is null but it shouldn't be, waiting");
|
||||
await Task.Delay(50, token).ConfigureAwait(false);
|
||||
@@ -376,9 +376,9 @@ public class PlayerDataFactory
|
||||
// gather up data from ipc
|
||||
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
|
||||
previousData.HeelsOffset = _ipcManager.GetHeelsOffset();
|
||||
Task<string> getGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(playerRelatedObject.Address);
|
||||
Task<string> getCustomizeData = _ipcManager.GetCustomizePlusScale();
|
||||
Task<string> getPalettePlusData = _ipcManager.PalettePlusBuildPalette();
|
||||
Task<string> getGlamourerData = _ipcManager.GlamourerGetCharacterCustomizationAsync(playerRelatedObject.Address);
|
||||
Task<string> getCustomizeData = _ipcManager.GetCustomizePlusScaleAsync();
|
||||
Task<string> getPalettePlusData = _ipcManager.PalettePlusBuildPaletteAsync();
|
||||
previousData.GlamourerString[playerRelatedObject.ObjectKind] = await getGlamourerData.ConfigureAwait(false);
|
||||
_logger.LogDebug("Glamourer is now: {data}", previousData.GlamourerString[playerRelatedObject.ObjectKind]);
|
||||
previousData.CustomizePlusScale = await getCustomizeData.ConfigureAwait(false);
|
||||
@@ -399,7 +399,7 @@ public class PlayerDataFactory
|
||||
var forwardPaths = forwardResolve.ToArray();
|
||||
var reversePaths = reverseResolve.ToArray();
|
||||
Dictionary<string, List<string>> resolvedPaths = new(StringComparer.Ordinal);
|
||||
var (forward, reverse) = await _ipcManager.PenumbraResolvePaths(forwardPaths, reversePaths).ConfigureAwait(false);
|
||||
var (forward, reverse) = await _ipcManager.PenumbraResolvePathsAsync(forwardPaths, reversePaths).ConfigureAwait(false);
|
||||
for (int i = 0; i < forwardPaths.Length; i++)
|
||||
{
|
||||
var filePath = forward[i].ToLowerInvariant();
|
||||
|
||||
@@ -28,7 +28,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
||||
_performanceCollector = performanceCollector;
|
||||
ObjectKind = objectKind;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_getAddress = getAddress;
|
||||
_getAddress = () =>
|
||||
{
|
||||
_dalamudUtil.EnsureIsOnFramework();
|
||||
return getAddress.Invoke();
|
||||
};
|
||||
_isOwnedObject = watchedObject;
|
||||
Name = string.Empty;
|
||||
|
||||
@@ -82,29 +86,25 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
||||
CheckAndUpdateObject();
|
||||
}
|
||||
|
||||
public IntPtr Address { get; set; }
|
||||
public unsafe Character* Character => (Character*)Address;
|
||||
public IntPtr Address { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
public ObjectKind ObjectKind { get; }
|
||||
|
||||
private byte[] CustomizeData { get; set; } = new byte[26];
|
||||
|
||||
private IntPtr DrawObjectAddress { get; set; }
|
||||
|
||||
private byte[] EquipSlotData { get; set; } = new byte[40];
|
||||
|
||||
public async Task<IntPtr> CurrentAddress()
|
||||
public IntPtr CurrentAddress()
|
||||
{
|
||||
return await _dalamudUtil.RunOnFrameworkThread(_getAddress.Invoke).ConfigureAwait(true);
|
||||
_dalamudUtil.EnsureIsOnFramework();
|
||||
return _getAddress.Invoke();
|
||||
}
|
||||
|
||||
public async Task<Dalamud.Game.ClientState.Objects.Types.GameObject?> GetGameObject()
|
||||
public Dalamud.Game.ClientState.Objects.Types.GameObject? GetGameObject()
|
||||
{
|
||||
return await _dalamudUtil.CreateGameObject(Address).ConfigureAwait(true);
|
||||
return _dalamudUtil.CreateGameObject(Address);
|
||||
}
|
||||
|
||||
public bool IsBeingDrawn()
|
||||
private bool IsBeingDrawn()
|
||||
{
|
||||
var curPtr = _getAddress();
|
||||
Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, CurPtr: {ptr}", this, curPtr.ToString("X"));
|
||||
@@ -123,7 +123,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
||||
return IsBeingDrawn(drawObj, curPtr);
|
||||
}
|
||||
|
||||
public async Task<bool> IsBeingDrawnRunOnFramework()
|
||||
public async Task<bool> IsBeingDrawnRunOnFrameworkAsync()
|
||||
{
|
||||
return await _dalamudUtil.RunOnFrameworkThread(IsBeingDrawn).ConfigureAwait(false);
|
||||
}
|
||||
@@ -142,6 +142,19 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
||||
Mediator.Publish(new RemoveWatchedGameObjectHandler(this));
|
||||
}
|
||||
|
||||
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action act, CancellationToken token)
|
||||
{
|
||||
while (await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
if (IsBeingDrawn()) return true;
|
||||
act();
|
||||
return false;
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
await Task.Delay(250, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void CheckAndUpdateObject()
|
||||
{
|
||||
if (_haltProcessing) return;
|
||||
@@ -200,12 +213,12 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
||||
_clearCts?.Dispose();
|
||||
_clearCts = new();
|
||||
var token = _clearCts.Token;
|
||||
_ = Task.Run(() => ClearTask(token), token);
|
||||
_ = Task.Run(() => ClearAsync(token), token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearTask(CancellationToken token)
|
||||
private async Task ClearAsync(CancellationToken token)
|
||||
{
|
||||
Logger.LogDebug("[{this}] Running Clear Task", this);
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
|
||||
|
||||
@@ -14,8 +14,10 @@ using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.Files;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind;
|
||||
|
||||
namespace MareSynchronos.PlayerData.Pairs;
|
||||
@@ -161,13 +163,13 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
if (_dalamudUtil is { IsZoning: false, IsInCutscene: false })
|
||||
{
|
||||
Logger.LogTrace("[{applicationId}] Restoring state for {name} ({OnlineUser})", applicationId, name, OnlineUser);
|
||||
_ipcManager.PenumbraRemoveTemporaryCollection(Logger, applicationId, name).GetAwaiter().GetResult();
|
||||
_ipcManager.PenumbraRemoveTemporaryCollectionAsync(Logger, applicationId, name).GetAwaiter().GetResult();
|
||||
|
||||
foreach (KeyValuePair<ObjectKind, List<FileReplacementData>> item in _cachedData?.FileReplacements ?? new())
|
||||
{
|
||||
try
|
||||
{
|
||||
RevertCustomizationData(item.Key, name, applicationId).GetAwaiter().GetResult();
|
||||
RevertCustomizationDataAsync(item.Key, name, applicationId).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
@@ -201,18 +203,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyBaseData(Guid applicationId, Dictionary<string, string> moddedPaths, string manipulationData, CancellationToken token)
|
||||
{
|
||||
await _ipcManager.PenumbraRemoveTemporaryCollection(Logger, applicationId, PlayerName!).ConfigureAwait(true);
|
||||
token.ThrowIfCancellationRequested();
|
||||
var gameObj = await _charaHandler!.GetGameObject().ConfigureAwait(true);
|
||||
if (gameObj == null) return;
|
||||
var objTableIndex = await _dalamudUtil.RunOnFrameworkThread(() => gameObj.ObjectTableIndex()).ConfigureAwait(true);
|
||||
await _ipcManager.PenumbraSetTemporaryMods(Logger, applicationId, PlayerName!, objTableIndex, moddedPaths, manipulationData).ConfigureAwait(true);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
private async Task ApplyCustomizationData(Guid applicationId, KeyValuePair<ObjectKind, HashSet<PlayerChanges>> changes, CharacterData charaData, CancellationToken token)
|
||||
private async Task ApplyCustomizationDataAsync(Guid applicationId, KeyValuePair<ObjectKind, HashSet<PlayerChanges>> changes, CharacterData charaData, CancellationToken token)
|
||||
{
|
||||
if (PlayerCharacter == IntPtr.Zero) return;
|
||||
var ptr = PlayerCharacter;
|
||||
@@ -242,29 +233,29 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
switch (change)
|
||||
{
|
||||
case PlayerChanges.Palette:
|
||||
await _ipcManager.PalettePlusSetPalette(handler.Address, charaData.PalettePlusData).ConfigureAwait(false);
|
||||
await _ipcManager.PalettePlusSetPaletteAsync(handler.Address, charaData.PalettePlusData).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case PlayerChanges.Customize:
|
||||
await _ipcManager.CustomizePlusSetBodyScale(handler.Address, charaData.CustomizePlusData).ConfigureAwait(false);
|
||||
await _ipcManager.CustomizePlusSetBodyScaleAsync(handler.Address, charaData.CustomizePlusData).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case PlayerChanges.Heels:
|
||||
await _ipcManager.HeelsSetOffsetForPlayer(handler.Address, charaData.HeelsOffset).ConfigureAwait(false);
|
||||
await _ipcManager.HeelsSetOffsetForPlayerAsync(handler.Address, charaData.HeelsOffset).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case PlayerChanges.Honorific:
|
||||
await _ipcManager.HonorificSetTitle(handler.Address, charaData.HonorificData).ConfigureAwait(false);
|
||||
await _ipcManager.HonorificSetTitleAsync(handler.Address, charaData.HonorificData).ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case PlayerChanges.Mods:
|
||||
if (charaData.GlamourerData.TryGetValue(changes.Key, out var glamourerData))
|
||||
{
|
||||
await _ipcManager.GlamourerApplyAll(Logger, handler, glamourerData, applicationId, token).ConfigureAwait(false);
|
||||
await _ipcManager.GlamourerApplyAllAsync(Logger, handler, glamourerData, applicationId, token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _ipcManager.PenumbraRedraw(Logger, handler, applicationId, token).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraRedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -452,26 +443,29 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var applyBaseData = new Action(() => ApplyBaseData(_applicationId, moddedPaths, charaData.ManipulationData, token).ConfigureAwait(true).GetAwaiter().GetResult());
|
||||
|
||||
if (updateModdedPaths && (moddedPaths.Any() || !string.IsNullOrEmpty(charaData.ManipulationData)))
|
||||
{
|
||||
while (!await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync(() => _ipcManager
|
||||
.PenumbraRemoveTemporaryCollectionAsync(Logger, _applicationId, PlayerName!)
|
||||
.ConfigureAwait(true).GetAwaiter().GetResult(), token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync(() =>
|
||||
{
|
||||
if (_charaHandler!.IsBeingDrawn()) return false;
|
||||
applyBaseData();
|
||||
return true;
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
await Task.Delay(250, token).ConfigureAwait(false);
|
||||
}
|
||||
var gameObj = _charaHandler!.GetGameObject();
|
||||
if (gameObj == null) return;
|
||||
var objTableIndex = gameObj.ObjectTableIndex();
|
||||
_ipcManager.PenumbraSetTemporaryModsAsync(Logger, _applicationId, PlayerName!, objTableIndex, moddedPaths, charaData.ManipulationData)
|
||||
.ConfigureAwait(true).GetAwaiter().GetResult();
|
||||
}, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var kind in updatedData)
|
||||
{
|
||||
await ApplyCustomizationData(_applicationId, kind, charaData, token).ConfigureAwait(false);
|
||||
await ApplyCustomizationDataAsync(_applicationId, kind, charaData, token).ConfigureAwait(false);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
@@ -506,7 +500,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
_lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false);
|
||||
_lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false);
|
||||
ApplyCharacterData(_cachedData, true);
|
||||
});
|
||||
}
|
||||
@@ -527,7 +521,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
PlayerName = name;
|
||||
_charaHandler = _gameObjectHandlerFactory.Create(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByIdent(OnlineUser.Ident), false).GetAwaiter().GetResult();
|
||||
|
||||
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
_lastGlamourerData = _originalGlamourerData;
|
||||
Mediator.Subscribe<PenumbraRedrawMessage>(this, IpcManagerOnPenumbraRedrawEvent);
|
||||
Mediator.Subscribe<CharacterChangedMessage>(this, async (msg) =>
|
||||
@@ -535,7 +529,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
if (msg.GameObjectHandler == _charaHandler && (_applicationTask?.IsCompleted ?? true))
|
||||
{
|
||||
Logger.LogTrace("Saving new Glamourer Data for {this}", this);
|
||||
_lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false);
|
||||
_lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -557,7 +551,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
Logger.LogDebug("Unauthorized character change detected");
|
||||
if (_cachedData != null)
|
||||
{
|
||||
await ApplyCustomizationData(applicationId, new(ObjectKind.Player,
|
||||
await ApplyCustomizationDataAsync(applicationId, new(ObjectKind.Player,
|
||||
new HashSet<PlayerChanges>(new[] { PlayerChanges.Palette, PlayerChanges.Customize, PlayerChanges.Heels, PlayerChanges.Mods })),
|
||||
_cachedData, token).ConfigureAwait(false);
|
||||
}
|
||||
@@ -598,7 +592,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RevertCustomizationData(ObjectKind objectKind, string name, Guid applicationId)
|
||||
private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId)
|
||||
{
|
||||
nint address = _dalamudUtil.GetPlayerCharacterFromObjectTableByIdent(OnlineUser.Ident);
|
||||
if (address == IntPtr.Zero) return;
|
||||
@@ -613,19 +607,19 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, false).ConfigureAwait(false);
|
||||
CheckForNameAndThrow(tempHandler, name);
|
||||
Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}: {data}", applicationId, OnlineUser.User.AliasOrUID, name, _originalGlamourerData);
|
||||
await _ipcManager.GlamourerApplyCustomizationAndEquipment(Logger, tempHandler, _originalGlamourerData, _lastGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
|
||||
await _ipcManager.GlamourerApplyCustomizationAndEquipmentAsync(Logger, tempHandler, _originalGlamourerData, _lastGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
|
||||
CheckForNameAndThrow(tempHandler, name);
|
||||
Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name);
|
||||
await _ipcManager.HeelsRestoreOffsetForPlayer(address).ConfigureAwait(false);
|
||||
await _ipcManager.HeelsRestoreOffsetForPlayerAsync(address).ConfigureAwait(false);
|
||||
CheckForNameAndThrow(tempHandler, name);
|
||||
Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name);
|
||||
await _ipcManager.CustomizePlusRevert(address).ConfigureAwait(false);
|
||||
await _ipcManager.CustomizePlusRevertAsync(address).ConfigureAwait(false);
|
||||
CheckForNameAndThrow(tempHandler, name);
|
||||
Logger.LogDebug("[{applicationId}] Restoring Palette+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name);
|
||||
await _ipcManager.PalettePlusRemovePalette(address).ConfigureAwait(false);
|
||||
await _ipcManager.PalettePlusRemovePaletteAsync(address).ConfigureAwait(false);
|
||||
CheckForNameAndThrow(tempHandler, name);
|
||||
Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name);
|
||||
await _ipcManager.HonorificClearTitle(address).ConfigureAwait(false);
|
||||
await _ipcManager.HonorificClearTitleAsync(address).ConfigureAwait(false);
|
||||
}
|
||||
else if (objectKind == ObjectKind.MinionOrMount)
|
||||
{
|
||||
@@ -633,7 +627,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
if (minionOrMount != IntPtr.Zero)
|
||||
{
|
||||
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => minionOrMount, false).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraRedraw(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else if (objectKind == ObjectKind.Pet)
|
||||
@@ -642,7 +636,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
if (pet != IntPtr.Zero)
|
||||
{
|
||||
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => pet, false).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraRedraw(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else if (objectKind == ObjectKind.Companion)
|
||||
@@ -651,7 +645,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
|
||||
if (companion != IntPtr.Zero)
|
||||
{
|
||||
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => companion, false).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraRedraw(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
|
||||
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,11 +76,22 @@ public class DalamudUtilService : IHostedService
|
||||
return obj != null && obj.IsValid();
|
||||
}
|
||||
|
||||
public async Task<Dalamud.Game.ClientState.Objects.Types.GameObject?> CreateGameObject(IntPtr reference)
|
||||
public async Task<Dalamud.Game.ClientState.Objects.Types.GameObject?> CreateGameObjectAsync(IntPtr reference)
|
||||
{
|
||||
return await RunOnFrameworkThread(() => _objectTable.CreateObjectReference(reference)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void EnsureIsOnFramework()
|
||||
{
|
||||
if (!_framework.IsInFrameworkUpdateThread) throw new InvalidOperationException("Can only be run on Framework");
|
||||
}
|
||||
|
||||
public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference)
|
||||
{
|
||||
EnsureIsOnFramework();
|
||||
return _objectTable.CreateObjectReference(reference);
|
||||
}
|
||||
|
||||
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
|
||||
{
|
||||
var objTableObj = _objectTable[index];
|
||||
@@ -176,7 +187,7 @@ public class DalamudUtilService : IHostedService
|
||||
{
|
||||
while ((!ct?.IsCancellationRequested ?? true)
|
||||
&& curWaitTime < timeOut
|
||||
&& await handler.IsBeingDrawnRunOnFramework().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something
|
||||
&& await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something
|
||||
{
|
||||
logger.LogTrace("[{redrawId}] Waiting for {handler} to finish drawing", redrawId, handler);
|
||||
curWaitTime += tick;
|
||||
|
||||
@@ -139,7 +139,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
|
||||
foreach (var transfer in _currentDownloads.ToList())
|
||||
{
|
||||
var screenPos = _dalamudUtilService.WorldToScreen(transfer.Key.GetGameObject().ConfigureAwait(true).GetAwaiter().GetResult());
|
||||
var screenPos = _dalamudUtilService.WorldToScreen(transfer.Key.GetGameObject());
|
||||
if (screenPos == Vector2.Zero) continue;
|
||||
|
||||
var totalBytes = transfer.Value.Sum(c => c.Value.TotalBytes);
|
||||
@@ -182,7 +182,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
foreach (var player in _uploadingPlayers.Select(p => p.Key).ToList())
|
||||
{
|
||||
var screenPos = _dalamudUtilService.WorldToScreen(player.GetGameObject().GetAwaiter().GetResult());
|
||||
var screenPos = _dalamudUtilService.WorldToScreen(player.GetGameObject());
|
||||
if (screenPos == Vector2.Zero) continue;
|
||||
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user