Chara file data export (#38)
* add rudimentary saving of chara file data * fix building * working prototype for MCDF import and application * adjust code to use streams * rename cache -> storage add ui for import/export mcdf * minor wording adjustments, version bump Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com> Co-authored-by: Stanley Dimant <stanley.dimant@varian.com>
This commit is contained in:
@@ -83,6 +83,7 @@ public class Configuration : IPluginConfiguration
|
||||
public bool FullPause { get; set; } = false;
|
||||
public bool HideInfoMessages { get; set; } = false;
|
||||
public bool DisableOptionalPluginWarnings { get; set; } = false;
|
||||
public bool OpenGposeImportOnGposeStart { get; set; } = false;
|
||||
public Dictionary<string, Dictionary<string, string>> UidServerComments { get; set; } = new(StringComparer.Ordinal);
|
||||
public Dictionary<string, Dictionary<string, string>> GidServerComments { get; set; } = new(StringComparer.Ordinal);
|
||||
|
||||
|
||||
65
MareSynchronos/Export/MareCharaFileData.cs
Normal file
65
MareSynchronos/Export/MareCharaFileData.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.FileCache;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronos.Export;
|
||||
|
||||
public record MareCharaFileData
|
||||
{
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string GlamourerData { get; set; } = string.Empty;
|
||||
public string CustomizePlusData { get; set; } = string.Empty;
|
||||
public string ManipulationData { get; set; } = string.Empty;
|
||||
public List<FileData> Files { get; set; } = new();
|
||||
public List<FileSwap> FileSwaps { get; set; } = new();
|
||||
|
||||
public MareCharaFileData() { }
|
||||
public MareCharaFileData(FileCacheManager manager, string description, CharacterCacheDto dto)
|
||||
{
|
||||
Description = description;
|
||||
|
||||
if (dto.GlamourerData.TryGetValue(ObjectKind.Player, out var glamourerData))
|
||||
{
|
||||
GlamourerData = glamourerData;
|
||||
}
|
||||
|
||||
CustomizePlusData = dto.CustomizePlusData;
|
||||
ManipulationData = dto.ManipulationData;
|
||||
|
||||
if (dto.FileReplacements.TryGetValue(ObjectKind.Player, out var fileReplacements))
|
||||
{
|
||||
foreach (var file in fileReplacements)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(file.FileSwapPath))
|
||||
{
|
||||
FileSwaps.Add(new FileSwap(file.GamePaths, file.FileSwapPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
var filePath = manager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath;
|
||||
if (filePath != null)
|
||||
{
|
||||
Files.Add(new FileData(file.GamePaths, new FileInfo(filePath).Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(this));
|
||||
}
|
||||
|
||||
public static MareCharaFileData FromByteArray(byte[] data)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<MareCharaFileData>(Encoding.UTF8.GetString(data))!;
|
||||
}
|
||||
|
||||
public record FileSwap(IEnumerable<string> GamePaths, string FileSwapPath);
|
||||
|
||||
public record FileData(IEnumerable<string> GamePaths, long Length);
|
||||
}
|
||||
19
MareSynchronos/Export/MareCharaFileDataFactory.cs
Normal file
19
MareSynchronos/Export/MareCharaFileDataFactory.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.FileCache;
|
||||
|
||||
namespace MareSynchronos.Export;
|
||||
|
||||
internal class MareCharaFileDataFactory
|
||||
{
|
||||
private readonly FileCacheManager _fileCacheManager;
|
||||
|
||||
public MareCharaFileDataFactory(FileCacheManager fileCacheManager)
|
||||
{
|
||||
_fileCacheManager = fileCacheManager;
|
||||
}
|
||||
|
||||
public MareCharaFileData Create(string description, CharacterCacheDto characterCacheDto)
|
||||
{
|
||||
return new MareCharaFileData(_fileCacheManager, description, characterCacheDto);
|
||||
}
|
||||
}
|
||||
57
MareSynchronos/Export/MareCharaFileHeader.cs
Normal file
57
MareSynchronos/Export/MareCharaFileHeader.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Lumina.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MareSynchronos.Export;
|
||||
|
||||
public record MareCharaFileHeader(byte Version, MareCharaFileData CharaFileData)
|
||||
{
|
||||
public static readonly byte CurrentVersion = 1;
|
||||
|
||||
public byte Version { get; set; } = Version;
|
||||
public MareCharaFileData CharaFileData { get; set; } = CharaFileData;
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
public void WriteToStream(BinaryWriter writer)
|
||||
{
|
||||
writer.Write('M');
|
||||
writer.Write('C');
|
||||
writer.Write('D');
|
||||
writer.Write('F');
|
||||
writer.Write(Version);
|
||||
var charaFileDataArray = CharaFileData.ToByteArray();
|
||||
writer.Write(charaFileDataArray.Length);
|
||||
writer.Write(charaFileDataArray);
|
||||
}
|
||||
|
||||
public static MareCharaFileHeader? FromBinaryReader(string path, BinaryReader reader)
|
||||
{
|
||||
var chars = new string(reader.ReadChars(4));
|
||||
if (!string.Equals(chars, "MCDF", System.StringComparison.Ordinal)) throw new System.Exception("Not a Mare Chara File");
|
||||
|
||||
MareCharaFileHeader? decoded = null;
|
||||
|
||||
var version = reader.ReadByte();
|
||||
if (version == 1)
|
||||
{
|
||||
var dataLength = reader.ReadInt32();
|
||||
|
||||
decoded = new(version, MareCharaFileData.FromByteArray(reader.ReadBytes(dataLength)));
|
||||
decoded.FilePath = path;
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
public void AdvanceReaderToData(BinaryReader reader)
|
||||
{
|
||||
reader.ReadChars(4);
|
||||
var version = reader.ReadByte();
|
||||
if (version == 1)
|
||||
{
|
||||
var length = reader.ReadInt32();
|
||||
_ = reader.ReadBytes(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
163
MareSynchronos/Export/MareCharaFileManager.cs
Normal file
163
MareSynchronos/Export/MareCharaFileManager.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using LZ4;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Managers;
|
||||
using MareSynchronos.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.Export;
|
||||
public class MareCharaFileManager
|
||||
{
|
||||
private readonly FileCacheManager _manager;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly MareCharaFileDataFactory _factory;
|
||||
public MareCharaFileHeader? LoadedCharaFile { get; private set; }
|
||||
public bool CurrentlyWorking { get; private set; } = false;
|
||||
|
||||
public MareCharaFileManager(FileCacheManager manager, IpcManager ipcManager, DalamudUtil dalamudUtil)
|
||||
{
|
||||
_factory = new(manager);
|
||||
_manager = manager;
|
||||
_ipcManager = ipcManager;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
}
|
||||
|
||||
public void ClearMareCharaFile()
|
||||
{
|
||||
LoadedCharaFile = null;
|
||||
}
|
||||
|
||||
public void LoadMareCharaFile(string filePath)
|
||||
{
|
||||
CurrentlyWorking = true;
|
||||
try
|
||||
{
|
||||
using var unwrapped = File.OpenRead(filePath);
|
||||
using var lz4Stream = new LZ4Stream(unwrapped, LZ4StreamMode.Decompress, LZ4StreamFlags.HighCompression);
|
||||
using var reader = new BinaryReader(lz4Stream);
|
||||
LoadedCharaFile = MareCharaFileHeader.FromBinaryReader(filePath, reader);
|
||||
Logger.Debug("Read Mare Chara File");
|
||||
Logger.Debug("Version: " + LoadedCharaFile.Version);
|
||||
|
||||
}
|
||||
catch { throw; }
|
||||
finally { CurrentlyWorking = false; }
|
||||
}
|
||||
|
||||
public async Task ApplyMareCharaFile(GameObject charaTarget)
|
||||
{
|
||||
Dictionary<string, string> extractedFiles = new();
|
||||
CurrentlyWorking = true;
|
||||
try
|
||||
{
|
||||
if (LoadedCharaFile == null || charaTarget == null || !File.Exists(LoadedCharaFile.FilePath)) return;
|
||||
|
||||
using var unwrapped = File.OpenRead(LoadedCharaFile.FilePath);
|
||||
using var lz4Stream = new LZ4Stream(unwrapped, LZ4StreamMode.Decompress, LZ4StreamFlags.HighCompression);
|
||||
using var reader = new BinaryReader(lz4Stream);
|
||||
LoadedCharaFile.AdvanceReaderToData(reader);
|
||||
Logger.Debug("Applying to " + charaTarget.Name.TextValue);
|
||||
extractedFiles = ExtractFilesFromCharaFile(LoadedCharaFile, reader);
|
||||
Dictionary<string, string> fileSwaps = new(StringComparer.Ordinal);
|
||||
foreach (var fileSwap in LoadedCharaFile.CharaFileData.FileSwaps)
|
||||
{
|
||||
foreach (var path in fileSwap.GamePaths)
|
||||
{
|
||||
fileSwaps.Add(path, fileSwap.FileSwapPath);
|
||||
}
|
||||
}
|
||||
_ipcManager.ToggleGposeQueueMode(true);
|
||||
_ipcManager.PenumbraRemoveTemporaryCollection(charaTarget.Name.TextValue);
|
||||
_ipcManager.PenumbraSetTemporaryMods(charaTarget.Name.TextValue,
|
||||
extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal),
|
||||
LoadedCharaFile.CharaFileData.ManipulationData);
|
||||
_ipcManager.GlamourerApplyAll(LoadedCharaFile.CharaFileData.GlamourerData, charaTarget.Address);
|
||||
_dalamudUtil.WaitWhileGposeCharacterIsDrawing(charaTarget.Address);
|
||||
_ipcManager.PenumbraRemoveTemporaryCollection(charaTarget.Name.TextValue);
|
||||
_ipcManager.ToggleGposeQueueMode(false);
|
||||
}
|
||||
catch { throw; }
|
||||
finally
|
||||
{
|
||||
CurrentlyWorking = false;
|
||||
|
||||
Logger.Debug("Clearing local files");
|
||||
foreach (var file in extractedFiles)
|
||||
{
|
||||
File.Delete(file.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ExtractFilesFromCharaFile(MareCharaFileHeader charaFileHeader, BinaryReader reader)
|
||||
{
|
||||
Dictionary<string, string> gamePathToFilePath = new(StringComparer.Ordinal);
|
||||
int i = 0;
|
||||
foreach (var fileData in charaFileHeader.CharaFileData.Files)
|
||||
{
|
||||
var fileName = Path.Combine(Path.GetTempPath(), "mare_" + (i++) + ".tmp");
|
||||
var length = fileData.Length;
|
||||
var bufferSize = 4 * 1024 * 1024;
|
||||
var buffer = new byte[bufferSize];
|
||||
using var fs = File.OpenWrite(fileName);
|
||||
using var wr = new BinaryWriter(fs);
|
||||
while (length > 0)
|
||||
{
|
||||
if (length < bufferSize) bufferSize = (int)length;
|
||||
buffer = reader.ReadBytes(bufferSize);
|
||||
wr.Write(length > bufferSize ? buffer : buffer.Take((int)length).ToArray());
|
||||
length -= bufferSize;
|
||||
}
|
||||
foreach (var path in fileData.GamePaths)
|
||||
{
|
||||
gamePathToFilePath[path] = fileName;
|
||||
Logger.Verbose(path + " => " + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
return gamePathToFilePath;
|
||||
}
|
||||
|
||||
public void SaveMareCharaFile(CharacterCacheDto? dto, string description, string filePath)
|
||||
{
|
||||
CurrentlyWorking = true;
|
||||
try
|
||||
{
|
||||
if (dto == null) return;
|
||||
|
||||
var mareCharaFileData = _factory.Create(description, dto);
|
||||
MareCharaFileHeader output = new(MareCharaFileHeader.CurrentVersion, mareCharaFileData);
|
||||
|
||||
using var fs = new FileStream(filePath, FileMode.Create);
|
||||
using var lz4 = new LZ4Stream(fs, LZ4StreamMode.Compress, LZ4StreamFlags.HighCompression);
|
||||
using var writer = new BinaryWriter(lz4);
|
||||
output.WriteToStream(writer);
|
||||
var bufferSize = 4 * 1024 * 1024;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
if (dto.FileReplacements.TryGetValue(ObjectKind.Player, out var replacement))
|
||||
{
|
||||
foreach (var file in replacement.Select(item => _manager.GetFileCacheByHash(item.Hash)).Where(file => file != null))
|
||||
{
|
||||
var length = new FileInfo(file.ResolvedFilepath).Length;
|
||||
using var fsRead = File.OpenRead(file.ResolvedFilepath);
|
||||
using var br = new BinaryReader(fsRead);
|
||||
int readBytes = 0;
|
||||
while ((readBytes = br.Read(buffer, 0, bufferSize)) > 0)
|
||||
{
|
||||
writer.Write(readBytes == bufferSize ? buffer : buffer.Take(readBytes).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { throw; }
|
||||
finally { CurrentlyWorking = false; }
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,10 @@ public class IpcManager : IDisposable
|
||||
private readonly ICallGateSubscriber<string?, object> _customizePlusOnScaleUpdate;
|
||||
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly ConcurrentQueue<Action> actionQueue = new();
|
||||
private bool inGposeQueueMode = false;
|
||||
private ConcurrentQueue<Action> actionQueue => inGposeQueueMode ? gposeActionQueue : normalQueue;
|
||||
private readonly ConcurrentQueue<Action> normalQueue = new();
|
||||
private readonly ConcurrentQueue<Action> gposeActionQueue = new();
|
||||
|
||||
public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil)
|
||||
{
|
||||
@@ -112,9 +115,25 @@ public class IpcManager : IDisposable
|
||||
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_dalamudUtil.FrameworkUpdate += HandleActionQueue;
|
||||
_dalamudUtil.GposeFrameworkUpdate += HandleGposeActionQueue;
|
||||
_dalamudUtil.ZoneSwitchEnd += ClearActionQueue;
|
||||
}
|
||||
|
||||
private void HandleGposeActionQueue()
|
||||
{
|
||||
if (gposeActionQueue.TryDequeue(out var action))
|
||||
{
|
||||
if (action == null) return;
|
||||
Logger.Debug("Execution action in gpose queue: " + action.Method);
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleGposeQueueMode(bool on)
|
||||
{
|
||||
inGposeQueueMode = on;
|
||||
}
|
||||
|
||||
private void PenumbraModSettingChangedHandler()
|
||||
{
|
||||
PenumbraModSettingChanged?.Invoke();
|
||||
@@ -123,6 +142,7 @@ public class IpcManager : IDisposable
|
||||
private void ClearActionQueue()
|
||||
{
|
||||
actionQueue.Clear();
|
||||
gposeActionQueue.Clear();
|
||||
}
|
||||
|
||||
private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
|
||||
@@ -223,6 +243,7 @@ public class IpcManager : IDisposable
|
||||
|
||||
_dalamudUtil.FrameworkUpdate -= HandleActionQueue;
|
||||
_dalamudUtil.ZoneSwitchEnd -= ClearActionQueue;
|
||||
_dalamudUtil.GposeFrameworkUpdate -= HandleGposeActionQueue;
|
||||
actionQueue.Clear();
|
||||
|
||||
_penumbraGameObjectResourcePathResolved.Dispose();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors></Authors>
|
||||
<Company></Company>
|
||||
<Version>0.6.8</Version>
|
||||
<Version>0.6.9</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
|
||||
|
||||
@@ -15,6 +15,7 @@ using MareSynchronos.Utils;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using MareSynchronos.FileCache;
|
||||
using Dalamud.Game.Gui;
|
||||
using MareSynchronos.Export;
|
||||
|
||||
namespace MareSynchronos;
|
||||
|
||||
@@ -42,6 +43,8 @@ public sealed class Plugin : IDalamudPlugin
|
||||
private readonly UiShared _uiSharedComponent;
|
||||
private readonly Dalamud.Localization _localization;
|
||||
private readonly FileReplacementFactory _fileReplacementFactory;
|
||||
private readonly MareCharaFileManager _mareCharaFileManager;
|
||||
private readonly GposeUi _gposeUi;
|
||||
|
||||
|
||||
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
|
||||
@@ -50,6 +53,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
{
|
||||
Logger.Debug("Launching " + Name);
|
||||
_pluginInterface = pluginInterface;
|
||||
_pluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
_commandManager = commandManager;
|
||||
_configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
_configuration.Initialize(_pluginInterface);
|
||||
@@ -69,11 +73,13 @@ public sealed class Plugin : IDalamudPlugin
|
||||
_apiController = new ApiController(_configuration, _dalamudUtil, _fileCacheManager);
|
||||
_periodicFileScanner = new PeriodicFileScanner(_ipcManager, _configuration, _fileCacheManager, _apiController, _dalamudUtil);
|
||||
_fileReplacementFactory = new FileReplacementFactory(_fileCacheManager, _ipcManager);
|
||||
_mareCharaFileManager = new(_fileCacheManager, _ipcManager, _dalamudUtil);
|
||||
|
||||
_uiSharedComponent =
|
||||
new UiShared(_ipcManager, _apiController, _periodicFileScanner, _fileDialogManager, _configuration, _dalamudUtil, _pluginInterface, _localization);
|
||||
_settingsUi = new SettingsUi(_windowSystem, _uiSharedComponent, _configuration, _apiController);
|
||||
_settingsUi = new SettingsUi(_windowSystem, _uiSharedComponent, _configuration, _apiController, _mareCharaFileManager);
|
||||
_compactUi = new CompactUi(_windowSystem, _uiSharedComponent, _configuration, _apiController);
|
||||
_gposeUi = new GposeUi(_windowSystem, _mareCharaFileManager, _dalamudUtil, _fileDialogManager, _configuration);
|
||||
|
||||
_introUi = new IntroUi(_windowSystem, _uiSharedComponent, _configuration, _periodicFileScanner);
|
||||
_settingsUi.SwitchToIntroUi += () =>
|
||||
@@ -120,6 +126,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
_introUi?.Dispose();
|
||||
_downloadUi?.Dispose();
|
||||
_compactUi?.Dispose();
|
||||
_gposeUi?.Dispose();
|
||||
|
||||
_periodicFileScanner?.Dispose();
|
||||
_fileCacheManager?.Dispose();
|
||||
@@ -231,6 +238,10 @@ public sealed class Plugin : IDalamudPlugin
|
||||
_ = _apiController.CreateConnections();
|
||||
}
|
||||
}
|
||||
else if (splitArgs[0] == "gpose")
|
||||
{
|
||||
_gposeUi.Toggle();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenUi()
|
||||
|
||||
@@ -40,6 +40,7 @@ public class CompactUi : Window, IDisposable
|
||||
public float TransferPartHeight = 0;
|
||||
public float _windowContentWidth = 0;
|
||||
private bool _showModalForUserAddition = false;
|
||||
private bool _wasOpen = false;
|
||||
|
||||
private bool showSyncShells = false;
|
||||
private GroupPanel groupPanel;
|
||||
@@ -85,6 +86,9 @@ public class CompactUi : Window, IDisposable
|
||||
_selectPairsForGroupUi = new(_tagHandler, configuration);
|
||||
_pairGroupsUi = new(_tagHandler, DrawPairedClient, apiController, _selectPairsForGroupUi);
|
||||
|
||||
_uiShared.GposeStart += UiShared_GposeStart;
|
||||
_uiShared.GposeEnd += UiShared_GposeEnd;
|
||||
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
{
|
||||
MinimumSize = new Vector2(350, 400),
|
||||
@@ -94,10 +98,23 @@ public class CompactUi : Window, IDisposable
|
||||
windowSystem.AddWindow(this);
|
||||
}
|
||||
|
||||
private void UiShared_GposeEnd()
|
||||
{
|
||||
IsOpen = _wasOpen;
|
||||
}
|
||||
|
||||
private void UiShared_GposeStart()
|
||||
{
|
||||
_wasOpen = IsOpen;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
public event SwitchUi? OpenSettingsUi;
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(CompactUi));
|
||||
_uiShared.GposeStart -= UiShared_GposeStart;
|
||||
_uiShared.GposeEnd -= UiShared_GposeEnd;
|
||||
_windowSystem.RemoveWindow(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ public class DownloadUi : Window, IDisposable
|
||||
private readonly Configuration _pluginConfiguration;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly UiShared _uiShared;
|
||||
private bool _wasOpen = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -52,6 +53,13 @@ public class DownloadUi : Window, IDisposable
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
if (_uiShared.IsInGpose)
|
||||
{
|
||||
_wasOpen = IsOpen;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
|
||||
base.PreDraw();
|
||||
if (_uiShared.EditTrackerPosition)
|
||||
{
|
||||
|
||||
87
MareSynchronos/UI/GposeUi.cs
Normal file
87
MareSynchronos/UI/GposeUi.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
using MareSynchronos.Export;
|
||||
using MareSynchronos.Utils;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
public class GposeUi : Window, IDisposable
|
||||
{
|
||||
private readonly WindowSystem _windowSystem;
|
||||
private readonly MareCharaFileManager _mareCharaFileManager;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly FileDialogManager _fileDialogManager;
|
||||
private readonly Configuration _configuration;
|
||||
|
||||
public GposeUi(WindowSystem windowSystem, MareCharaFileManager mareCharaFileManager, DalamudUtil dalamudUtil, FileDialogManager fileDialogManager, Configuration configuration) : base("Mare Synchronos Gpose Import UI###MareSynchronosGposeUI")
|
||||
{
|
||||
_windowSystem = windowSystem;
|
||||
_mareCharaFileManager = mareCharaFileManager;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_fileDialogManager = fileDialogManager;
|
||||
_configuration = configuration;
|
||||
_dalamudUtil.GposeStart += StartGpose;
|
||||
_dalamudUtil.GposeEnd += EndGpose;
|
||||
IsOpen = _dalamudUtil.IsInGpose;
|
||||
Flags = ImGuiWindowFlags.AlwaysAutoResize;
|
||||
_windowSystem.AddWindow(this);
|
||||
}
|
||||
|
||||
private void EndGpose()
|
||||
{
|
||||
IsOpen = false;
|
||||
_mareCharaFileManager.ClearMareCharaFile();
|
||||
}
|
||||
|
||||
private void StartGpose()
|
||||
{
|
||||
IsOpen = _configuration.OpenGposeImportOnGposeStart;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dalamudUtil.GposeStart -= StartGpose;
|
||||
_dalamudUtil.GposeEnd -= EndGpose;
|
||||
_windowSystem.RemoveWindow(this);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (!_dalamudUtil.IsInGpose) IsOpen = false;
|
||||
|
||||
if (!_mareCharaFileManager.CurrentlyWorking)
|
||||
{
|
||||
if (UiShared.IconTextButton(FontAwesomeIcon.FolderOpen, "Load MCDF"))
|
||||
{
|
||||
_fileDialogManager.OpenFileDialog("Pick MCDF file", ".mcdf", (success, path) =>
|
||||
{
|
||||
if (!success) return;
|
||||
|
||||
Task.Run(() => _mareCharaFileManager.LoadMareCharaFile(path));
|
||||
});
|
||||
}
|
||||
UiShared.AttachToolTip("Applies it to the currently selected GPose actor");
|
||||
if (_mareCharaFileManager.LoadedCharaFile != null)
|
||||
{
|
||||
UiShared.TextWrapped("Loaded file: " + _mareCharaFileManager.LoadedCharaFile.FilePath);
|
||||
UiShared.TextWrapped("File Description: " + _mareCharaFileManager.LoadedCharaFile.CharaFileData.Description);
|
||||
if (UiShared.IconTextButton(FontAwesomeIcon.Check, "Apply loaded MCDF"))
|
||||
{
|
||||
Task.Run(async () => await _mareCharaFileManager.ApplyMareCharaFile(_dalamudUtil.GposeTargetGameObject).ConfigureAwait(false));
|
||||
}
|
||||
UiShared.AttachToolTip("Applies it to the currently selected GPose actor");
|
||||
UiShared.ColorTextWrapped("Warning: redrawing or changing the character will revert all applied mods.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UiShared.ColorTextWrapped("Loading Character...", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
UiShared.TextWrapped("Hint: You can disable the automatic loading of this window in the Mare settings and open it manually with /mare gpose");
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,8 @@ internal class IntroUi : Window, IDisposable
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (_uiShared.IsInGpose) return;
|
||||
|
||||
if (!_pluginConfiguration.AcceptedAgreement && !_readFirstPage)
|
||||
{
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
@@ -149,7 +151,7 @@ internal class IntroUi : Window, IDisposable
|
||||
|| !Directory.Exists(_pluginConfiguration.CacheFolder)))
|
||||
{
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
ImGui.TextUnformatted("File Cache Setup");
|
||||
ImGui.TextUnformatted("File Storage Setup");
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
ImGui.Separator();
|
||||
|
||||
@@ -160,11 +162,12 @@ internal class IntroUi : Window, IDisposable
|
||||
else
|
||||
{
|
||||
UiShared.TextWrapped("To not unnecessary download files already present on your computer, Mare Synchronos will have to scan your Penumbra mod directory. " +
|
||||
"Additionally, a local cache folder must be set where Mare Synchronos will download its local file cache to. " +
|
||||
"Once the Cache Folder is set and the scan complete, this page will automatically forward to registration at a service.");
|
||||
"Additionally, a local storage folder must be set where Mare Synchronos will download other character files to. " +
|
||||
"Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service.");
|
||||
UiShared.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed.");
|
||||
UiShared.ColorTextWrapped("Warning: once past this step you should not delete the FileCache.csv of Mare Synchronos in the Plugin Configurations folder of Dalamud. " +
|
||||
"Otherwise on the next launch a full re-scan of the file cache database will be initiated.", ImGuiColors.DalamudYellow);
|
||||
UiShared.ColorTextWrapped("Warning: if the scan is hanging and does nothing for a long time, chances are high your Penumbra folder is not set up properly.", ImGuiColors.DalamudYellow);
|
||||
_uiShared.DrawCacheDirectorySetting();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.Utils;
|
||||
using Dalamud.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using MareSynchronos.Export;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
@@ -22,6 +23,7 @@ public class SettingsUi : Window, IDisposable
|
||||
private readonly Configuration _configuration;
|
||||
private readonly WindowSystem _windowSystem;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly MareCharaFileManager _mareCharaFileManager;
|
||||
private readonly UiShared _uiShared;
|
||||
public CharacterCacheDto LastCreatedCharacterData { private get; set; }
|
||||
|
||||
@@ -32,9 +34,11 @@ public class SettingsUi : Window, IDisposable
|
||||
private bool _openPopupOnAddition;
|
||||
private bool _hideInfoMessages;
|
||||
private bool _disableOptionalPluginsWarnings;
|
||||
private bool _wasOpen = false;
|
||||
|
||||
public SettingsUi(WindowSystem windowSystem,
|
||||
UiShared uiShared, Configuration configuration, ApiController apiController) : base("Mare Synchronos Settings")
|
||||
UiShared uiShared, Configuration configuration, ApiController apiController,
|
||||
MareCharaFileManager mareCharaFileManager) : base("Mare Synchronos Settings")
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(SettingsUi));
|
||||
|
||||
@@ -47,17 +51,36 @@ public class SettingsUi : Window, IDisposable
|
||||
_configuration = configuration;
|
||||
_windowSystem = windowSystem;
|
||||
_apiController = apiController;
|
||||
_mareCharaFileManager = mareCharaFileManager;
|
||||
_uiShared = uiShared;
|
||||
_openPopupOnAddition = _configuration.OpenPopupOnAdd;
|
||||
_hideInfoMessages = _configuration.HideInfoMessages;
|
||||
_disableOptionalPluginsWarnings = _configuration.DisableOptionalPluginWarnings;
|
||||
|
||||
_uiShared.GposeStart += _uiShared_GposeStart;
|
||||
_uiShared.GposeEnd += _uiShared_GposeEnd;
|
||||
|
||||
windowSystem.AddWindow(this);
|
||||
}
|
||||
|
||||
private void _uiShared_GposeEnd()
|
||||
{
|
||||
IsOpen = _wasOpen;
|
||||
}
|
||||
|
||||
private void _uiShared_GposeStart()
|
||||
{
|
||||
_wasOpen = IsOpen;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(SettingsUi));
|
||||
|
||||
_uiShared.GposeStart -= _uiShared_GposeStart;
|
||||
_uiShared.GposeEnd -= _uiShared_GposeEnd;
|
||||
|
||||
_windowSystem.RemoveWindow(this);
|
||||
}
|
||||
|
||||
@@ -87,9 +110,9 @@ public class SettingsUi : Window, IDisposable
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui.BeginTabItem("Cache Settings"))
|
||||
if (ImGui.BeginTabItem("Export & Storage"))
|
||||
{
|
||||
DrawFileCacheSettings();
|
||||
DrawFileStorageSettings();
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
|
||||
@@ -140,6 +163,7 @@ public class SettingsUi : Window, IDisposable
|
||||
}
|
||||
|
||||
_lastTab = "General";
|
||||
UiShared.FontText("Notes", _uiShared.UidFont);
|
||||
if (UiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard"))
|
||||
{
|
||||
ImGui.SetClipboardText(_uiShared.GetNotes());
|
||||
@@ -162,7 +186,6 @@ public class SettingsUi : Window, IDisposable
|
||||
{
|
||||
UiShared.ColorTextWrapped("Attempt to import notes from clipboard failed. Check formatting and try again", ImGuiColors.DalamudRed);
|
||||
}
|
||||
ImGui.Separator();
|
||||
if (ImGui.Checkbox("Open Notes Popup on user addition", ref _openPopupOnAddition))
|
||||
{
|
||||
_apiController.LastAddedUser = null;
|
||||
@@ -170,6 +193,9 @@ public class SettingsUi : Window, IDisposable
|
||||
_configuration.Save();
|
||||
}
|
||||
UiShared.DrawHelpText("This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs.");
|
||||
|
||||
ImGui.Separator();
|
||||
UiShared.FontText("Server Messages", _uiShared.UidFont);
|
||||
if (ImGui.Checkbox("Hide Server Info Messages", ref _hideInfoMessages))
|
||||
{
|
||||
_configuration.HideInfoMessages = _hideInfoMessages;
|
||||
@@ -520,7 +546,7 @@ public class SettingsUi : Window, IDisposable
|
||||
|
||||
if (!_configuration.FullPause)
|
||||
{
|
||||
UiShared.ColorTextWrapped("Note: to change servers you need to disconnect from your current Mare Synchronos server.", ImGuiColors.DalamudYellow);
|
||||
UiShared.ColorTextWrapped("Note: to change servers or your secret key you need to disconnect from your current Mare Synchronos server.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
|
||||
var marePaused = _configuration.FullPause;
|
||||
@@ -546,6 +572,10 @@ public class SettingsUi : Window, IDisposable
|
||||
_uiShared.DrawServiceSelection(() => { });
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
UiShared.FontText("Debug", _uiShared.UidFont);
|
||||
|
||||
if (UiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard"))
|
||||
{
|
||||
if (LastCreatedCharacterData != null)
|
||||
@@ -560,6 +590,9 @@ public class SettingsUi : Window, IDisposable
|
||||
UiShared.AttachToolTip("Use this when reporting mods being rejected from the server.");
|
||||
}
|
||||
|
||||
private string _charaFileSavePath = string.Empty;
|
||||
private string _charaFileLoadPath = string.Empty;
|
||||
|
||||
private void DrawBlockedTransfers()
|
||||
{
|
||||
_lastTab = "BlockedTransfers";
|
||||
@@ -675,15 +708,77 @@ public class SettingsUi : Window, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFileCacheSettings()
|
||||
private bool _readExport = false;
|
||||
private string _exportDescription = string.Empty;
|
||||
|
||||
private void DrawFileStorageSettings()
|
||||
{
|
||||
_lastTab = "FileCache";
|
||||
|
||||
UiShared.FontText("Export MCDF", _uiShared.UidFont);
|
||||
|
||||
UiShared.TextWrapped("This feature allows you to pack your character into a MCDF file and manually send it to other people. MCDF files can officially only be imported during GPose through Mare. " +
|
||||
"Be aware that the possibility exists that people write unoffocial custom exporters to extract the containing data.");
|
||||
|
||||
ImGui.Checkbox("##readExport", ref _readExport);
|
||||
ImGui.SameLine();
|
||||
UiShared.TextWrapped("I understand that by exporting my character data and sending it to other people I am giving away my current character appearance irrevocably. People I am sharing my data with have the ability to share it with other people without limitations.");
|
||||
|
||||
if (_readExport)
|
||||
{
|
||||
if (!_mareCharaFileManager.CurrentlyWorking)
|
||||
{
|
||||
ImGui.Indent();
|
||||
ImGui.InputTextWithHint("Export Descriptor", "This description will be shown on loading the data", ref _exportDescription, 255);
|
||||
if (UiShared.IconTextButton(FontAwesomeIcon.Save, "Export Character as MCDF"))
|
||||
{
|
||||
_uiShared.FileDialogManager.SaveFileDialog("Export Character to file", ".mcdf", "export.mcdf", ".mcdf", (success, path) =>
|
||||
{
|
||||
if (!success) return;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mareCharaFileManager.SaveMareCharaFile(LastCreatedCharacterData, _exportDescription, path);
|
||||
_exportDescription = string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error saving data", ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
ImGui.Unindent();
|
||||
}
|
||||
else
|
||||
{
|
||||
UiShared.ColorTextWrapped("Export in progress", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
}
|
||||
bool openInGpose = _configuration.OpenGposeImportOnGposeStart;
|
||||
if (ImGui.Checkbox("Open MCDF import window when GPose loads", ref openInGpose))
|
||||
{
|
||||
_configuration.OpenGposeImportOnGposeStart = openInGpose;
|
||||
_configuration.Save();
|
||||
}
|
||||
UiShared.DrawHelpText("This will automatically open the import menu when loading into Gpose. If unchecked you can open the menu manually with /mare gpose");
|
||||
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
UiShared.FontText("Storage", _uiShared.UidFont);
|
||||
|
||||
UiShared.TextWrapped("Mare stores downloaded files from paired people permanently. This is to improve loading performance and requiring less downloads. " +
|
||||
"The storage governs itself by clearing data beyond the set storage size. Please set the storage size accordingly. It is not necessary to manually clear the storage.");
|
||||
|
||||
_uiShared.DrawFileScanState();
|
||||
_uiShared.DrawTimeSpanBetweenScansSetting();
|
||||
_uiShared.DrawCacheDirectorySetting();
|
||||
ImGui.Text($"Local cache size: {UiShared.ByteToString(_uiShared.FileCacheSize)}");
|
||||
ImGui.Text($"Local storage size: {UiShared.ByteToString(_uiShared.FileCacheSize)}");
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Clear local cache"))
|
||||
if (ImGui.Button("Clear local storage"))
|
||||
{
|
||||
if (UiShared.CtrlPressed())
|
||||
{
|
||||
@@ -699,7 +794,7 @@ public class SettingsUi : Window, IDisposable
|
||||
}
|
||||
}
|
||||
UiShared.AttachToolTip("You normally do not need to do this. This will solely remove all downloaded data from all players and will require you to re-download everything again." + Environment.NewLine
|
||||
+ "Mares Cache is self-clearing and will not surpass the limit you have set it to." + Environment.NewLine
|
||||
+ "Mares storage is self-clearing and will not surpass the limit you have set it to." + Environment.NewLine
|
||||
+ "If you still think you need to do this hold CTRL while pressing the button.");
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public class UiShared : IDisposable
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly PeriodicFileScanner _cacheScanner;
|
||||
private readonly FileDialogManager _fileDialogManager;
|
||||
public readonly FileDialogManager FileDialogManager;
|
||||
private readonly Configuration _pluginConfiguration;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
@@ -39,6 +39,9 @@ public class UiShared : IDisposable
|
||||
public bool EditTrackerPosition { get; set; }
|
||||
public ImFontPtr UidFont { get; private set; }
|
||||
public bool UidFontBuilt { get; private set; }
|
||||
public bool IsInGpose => _dalamudUtil.IsInGpose;
|
||||
public event VoidDelegate? GposeStart;
|
||||
public event VoidDelegate? GposeEnd;
|
||||
public static bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0;
|
||||
public static bool ShiftPressed() => (GetKeyState(0xA1) & 0x8000) != 0 || (GetKeyState(0xA0) & 0x8000) != 0;
|
||||
|
||||
@@ -54,7 +57,7 @@ public class UiShared : IDisposable
|
||||
_ipcManager = ipcManager;
|
||||
_apiController = apiController;
|
||||
_cacheScanner = cacheScanner;
|
||||
_fileDialogManager = fileDialogManager;
|
||||
FileDialogManager = fileDialogManager;
|
||||
_pluginConfiguration = pluginConfiguration;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_pluginInterface = pluginInterface;
|
||||
@@ -63,6 +66,19 @@ public class UiShared : IDisposable
|
||||
|
||||
_pluginInterface.UiBuilder.BuildFonts += BuildFont;
|
||||
_pluginInterface.UiBuilder.RebuildFonts();
|
||||
|
||||
_dalamudUtil.GposeStart += _dalamudUtil_GposeStart;
|
||||
_dalamudUtil.GposeEnd += _dalamudUtil_GposeEnd;
|
||||
}
|
||||
|
||||
private void _dalamudUtil_GposeEnd()
|
||||
{
|
||||
GposeEnd?.Invoke();
|
||||
}
|
||||
|
||||
private void _dalamudUtil_GposeStart()
|
||||
{
|
||||
GposeStart?.Invoke();
|
||||
}
|
||||
|
||||
public static float GetWindowContentRegionWidth()
|
||||
@@ -490,16 +506,16 @@ public class UiShared : IDisposable
|
||||
|
||||
public void DrawCacheDirectorySetting()
|
||||
{
|
||||
ColorTextWrapped("Note: The cache folder should be somewhere close to root (i.e. C:\\MareCache) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow);
|
||||
ColorTextWrapped("Note: The storage folder should be somewhere close to root (i.e. C:\\MareStorage) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow);
|
||||
var cacheDirectory = _pluginConfiguration.CacheFolder;
|
||||
ImGui.InputText("Cache Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly);
|
||||
ImGui.InputText("Storage Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
string folderIcon = FontAwesomeIcon.Folder.ToIconString();
|
||||
if (ImGui.Button(folderIcon + "##chooseCacheFolder"))
|
||||
{
|
||||
_fileDialogManager.OpenFolderDialog("Pick Mare Synchronos Cache Folder", (success, path) =>
|
||||
FileDialogManager.OpenFolderDialog("Pick Mare Synchronos Storage Folder", (success, path) =>
|
||||
{
|
||||
if (!success) return;
|
||||
|
||||
@@ -525,7 +541,7 @@ public class UiShared : IDisposable
|
||||
|
||||
if (_isPenumbraDirectory)
|
||||
{
|
||||
ColorTextWrapped("Do not point the cache path directly to the Penumbra directory. If necessary, make a subfolder in it.", ImGuiColors.DalamudRed);
|
||||
ColorTextWrapped("Do not point the storage path directly to the Penumbra directory. If necessary, make a subfolder in it.", ImGuiColors.DalamudRed);
|
||||
}
|
||||
else if (!_isDirectoryWritable)
|
||||
{
|
||||
@@ -533,7 +549,7 @@ public class UiShared : IDisposable
|
||||
}
|
||||
else if (_cacheDirectoryHasOtherFilesThanCache)
|
||||
{
|
||||
ColorTextWrapped("Your selected directory has files inside that are not Mare related. Use an empty directory or a previous Mare cache directory only.", ImGuiColors.DalamudRed);
|
||||
ColorTextWrapped("Your selected directory has files inside that are not Mare related. Use an empty directory or a previous Mare storage directory only.", ImGuiColors.DalamudRed);
|
||||
}
|
||||
else if (!_cacheDirectoryIsValidPath)
|
||||
{
|
||||
@@ -542,12 +558,12 @@ public class UiShared : IDisposable
|
||||
}
|
||||
|
||||
float maxCacheSize = (float)_pluginConfiguration.MaxLocalCacheInGiB;
|
||||
if (ImGui.SliderFloat("Maximum Cache Size in GiB", ref maxCacheSize, 1f, 200f, "%.2f GiB"))
|
||||
if (ImGui.SliderFloat("Maximum Storage Size in GiB", ref maxCacheSize, 1f, 200f, "%.2f GiB"))
|
||||
{
|
||||
_pluginConfiguration.MaxLocalCacheInGiB = maxCacheSize;
|
||||
_pluginConfiguration.Save();
|
||||
}
|
||||
DrawHelpText("The cache is automatically governed by Mare. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself.");
|
||||
DrawHelpText("The storage is automatically governed by Mare. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself.");
|
||||
}
|
||||
|
||||
private bool _isDirectoryWritable = false;
|
||||
@@ -697,5 +713,7 @@ public class UiShared : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
_pluginInterface.UiBuilder.BuildFonts -= BuildFont;
|
||||
_dalamudUtil.GposeStart -= _dalamudUtil_GposeStart;
|
||||
_dalamudUtil.GposeEnd -= _dalamudUtil_GposeEnd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@ using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
|
||||
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
|
||||
@@ -35,8 +38,12 @@ public class DalamudUtil : IDisposable
|
||||
public event VoidDelegate? DelayedFrameworkUpdate;
|
||||
public event VoidDelegate? ZoneSwitchStart;
|
||||
public event VoidDelegate? ZoneSwitchEnd;
|
||||
public event VoidDelegate? GposeStart;
|
||||
public event VoidDelegate? GposeEnd;
|
||||
public event VoidDelegate? GposeFrameworkUpdate;
|
||||
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
|
||||
private bool _sentBetweenAreas = false;
|
||||
public bool IsInGpose { get; private set; } = false;
|
||||
|
||||
public unsafe bool IsGameObjectPresent(IntPtr key)
|
||||
{
|
||||
@@ -84,8 +91,21 @@ public class DalamudUtil : IDisposable
|
||||
_chatGui.Print(se.BuiltString);
|
||||
}
|
||||
|
||||
private void FrameworkOnUpdate(Framework framework)
|
||||
private unsafe void FrameworkOnUpdate(Framework framework)
|
||||
{
|
||||
if (GposeTarget != null && !IsInGpose)
|
||||
{
|
||||
Logger.Debug("Gpose start");
|
||||
IsInGpose = true;
|
||||
GposeStart?.Invoke();
|
||||
}
|
||||
else if (GposeTarget == null && IsInGpose)
|
||||
{
|
||||
Logger.Debug("Gpose end");
|
||||
IsInGpose = false;
|
||||
GposeEnd?.Invoke();
|
||||
}
|
||||
|
||||
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose)
|
||||
{
|
||||
if (!_sentBetweenAreas)
|
||||
@@ -95,6 +115,8 @@ public class DalamudUtil : IDisposable
|
||||
ZoneSwitchStart?.Invoke();
|
||||
}
|
||||
|
||||
if (IsInGpose) GposeFrameworkUpdate?.Invoke();
|
||||
|
||||
return;
|
||||
}
|
||||
else if (_sentBetweenAreas)
|
||||
@@ -175,6 +197,10 @@ public class DalamudUtil : IDisposable
|
||||
return _objectTable.CreateObjectReference(reference);
|
||||
}
|
||||
|
||||
public unsafe GameObject* GposeTarget => TargetSystem.Instance()->GPoseTarget;
|
||||
|
||||
public unsafe Dalamud.Game.ClientState.Objects.Types.GameObject? GposeTargetGameObject => GposeTarget == null ? null : _objectTable[GposeTarget->ObjectIndex];
|
||||
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
|
||||
public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
|
||||
@@ -186,21 +212,21 @@ public class DalamudUtil : IDisposable
|
||||
|
||||
public unsafe IntPtr GetMinion()
|
||||
{
|
||||
return (IntPtr)((Character*)PlayerPointer)->CompanionObject;
|
||||
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject;
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
|
||||
{
|
||||
var mgr = CharacterManager.Instance();
|
||||
if (playerPointer == null) playerPointer = PlayerPointer;
|
||||
return (IntPtr)mgr->LookupPetByOwnerObject((BattleChara*)playerPointer);
|
||||
return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
|
||||
{
|
||||
var mgr = CharacterManager.Instance();
|
||||
if (playerPointer == null) playerPointer = PlayerPointer;
|
||||
return (IntPtr)mgr->LookupBuddyByOwnerObject((BattleChara*)playerPointer);
|
||||
return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
|
||||
}
|
||||
|
||||
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
|
||||
@@ -211,8 +237,6 @@ public class DalamudUtil : IDisposable
|
||||
|
||||
public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id);
|
||||
|
||||
public bool IsInGpose => _objectTable[201] != null;
|
||||
|
||||
public List<PlayerCharacter> GetPlayerCharacters()
|
||||
{
|
||||
return _objectTable.Where(obj =>
|
||||
@@ -275,6 +299,30 @@ public class DalamudUtil : IDisposable
|
||||
Thread.Sleep(tick);
|
||||
}
|
||||
|
||||
public unsafe void DisableDraw(IntPtr characterAddress)
|
||||
{
|
||||
var obj = (GameObject*)characterAddress;
|
||||
obj->DisableDraw();
|
||||
}
|
||||
|
||||
public unsafe void WaitWhileGposeCharacterIsDrawing(IntPtr characterAddress, int timeOut = 5000)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
var obj = (GameObject*)characterAddress;
|
||||
const int tick = 250;
|
||||
int curWaitTime = 0;
|
||||
Logger.Verbose("RenderFlags:" + obj->RenderFlags.ToString("X"));
|
||||
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
|
||||
while (obj->RenderFlags != 0x00 && curWaitTime < timeOut)
|
||||
{
|
||||
Logger.Verbose($"Waiting for gpose actor to finish drawing");
|
||||
curWaitTime += tick;
|
||||
Thread.Sleep(tick);
|
||||
}
|
||||
|
||||
Thread.Sleep(tick * 2);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clientState.Login -= ClientStateOnLogin;
|
||||
|
||||
Reference in New Issue
Block a user