mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Compare commits
44 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b6517aae8 | ||
|
|
aadcf771e7 | ||
|
|
bf4673a1d9 | ||
|
|
76b214c643 | ||
|
|
434a5a809e | ||
|
|
88fe25f69e | ||
|
|
bef1e39ac3 | ||
|
|
c604d5dbe5 | ||
|
|
a0d912a395 | ||
|
|
a56852f918 | ||
|
|
4228fc1b89 | ||
|
|
e644b8da28 | ||
|
|
ace3a8f755 | ||
|
|
76ed347cbf | ||
|
|
c3469a1687 | ||
|
|
0a9693daea | ||
|
|
414bd8bee7 | ||
|
|
8e1745d67a | ||
|
|
889f01a724 | ||
|
|
6e62905fa7 | ||
|
|
654787fa0d | ||
|
|
835ba23935 | ||
|
|
389a8781d6 | ||
|
|
3eabe591df | ||
|
|
487d3b9399 | ||
|
|
4d4e4669dd | ||
|
|
fb065549e9 | ||
|
|
2c34154915 | ||
|
|
3704051b0f | ||
|
|
b2b8f2b6eb | ||
|
|
22e6c0655b | ||
|
|
bb2ba0cf11 | ||
|
|
e854386b23 | ||
|
|
49d24df2e7 | ||
|
|
c9b291c2f3 | ||
|
|
65f789880d | ||
|
|
abf998a727 | ||
|
|
4cc191cb25 | ||
|
|
dc431c10a5 | ||
|
|
26862ba78f | ||
|
|
e4374337f2 | ||
|
|
240c889fff | ||
|
|
612cd31c3e | ||
|
|
304b362002 |
38 changed files with 515 additions and 134 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 54c1944dc7db704733b4788520e494761bb0b58e
|
||||
Subproject commit 59a7ab5fa9941eb754757b62e4cb189e455e9514
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
using Glamourer.Api.Enums;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.State;
|
||||
using OtterGui;
|
||||
using OtterGui.Extensions;
|
||||
using OtterGui.Log;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Glamourer.Api;
|
||||
|
|
@ -15,14 +15,23 @@ namespace Glamourer.Api;
|
|||
public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal IEnumerable<ActorState> FindExistingStates(string actorName)
|
||||
internal IEnumerable<ActorState> FindExistingStates(string actorName, ushort worldId = ushort.MaxValue)
|
||||
{
|
||||
if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
|
||||
yield break;
|
||||
|
||||
foreach (var state in stateManager.Values.Where(state
|
||||
=> state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString))
|
||||
yield return state;
|
||||
if (worldId == WorldId.AnyWorld.Id)
|
||||
{
|
||||
foreach (var state in stateManager.Values.Where(state
|
||||
=> state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString))
|
||||
yield return state;
|
||||
}
|
||||
else
|
||||
{
|
||||
var identifier = actors.CreatePlayer(byteString, worldId);
|
||||
if (stateManager.TryGetValue(identifier, out var state))
|
||||
yield return state;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Glamourer.Api;
|
|||
public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService
|
||||
{
|
||||
public const int CurrentApiVersionMajor = 1;
|
||||
public const int CurrentApiVersionMinor = 6;
|
||||
public const int CurrentApiVersionMinor = 7;
|
||||
|
||||
public (int Major, int Minor) ApiVersion
|
||||
=> (CurrentApiVersionMajor, CurrentApiVersionMinor);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ public sealed class IpcProviders : IDisposable, IApiService
|
|||
IpcSubscribers.RevertStateName.Provider(pi, api.State),
|
||||
IpcSubscribers.UnlockState.Provider(pi, api.State),
|
||||
IpcSubscribers.UnlockStateName.Provider(pi, api.State),
|
||||
IpcSubscribers.DeletePlayerState.Provider(pi, api.State),
|
||||
IpcSubscribers.UnlockAll.Provider(pi, api.State),
|
||||
IpcSubscribers.RevertToAutomation.Provider(pi, api.State),
|
||||
IpcSubscribers.RevertToAutomationName.Provider(pi, api.State),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Glamourer.State;
|
|||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
using StateChanged = Glamourer.Events.StateChanged;
|
||||
|
||||
namespace Glamourer.Api;
|
||||
|
|
@ -17,7 +18,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
|
|||
private readonly ApiHelpers _helpers;
|
||||
private readonly StateManager _stateManager;
|
||||
private readonly DesignConverter _converter;
|
||||
private readonly Configuration _config;
|
||||
private readonly AutoDesignApplier _autoDesigns;
|
||||
private readonly ActorObjectManager _objects;
|
||||
private readonly StateChanged _stateChanged;
|
||||
|
|
@ -27,7 +27,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
|
|||
public StateApi(ApiHelpers helpers,
|
||||
StateManager stateManager,
|
||||
DesignConverter converter,
|
||||
Configuration config,
|
||||
AutoDesignApplier autoDesigns,
|
||||
ActorObjectManager objects,
|
||||
StateChanged stateChanged,
|
||||
|
|
@ -37,7 +36,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
|
|||
_helpers = helpers;
|
||||
_stateManager = stateManager;
|
||||
_converter = converter;
|
||||
_config = config;
|
||||
_autoDesigns = autoDesigns;
|
||||
_objects = objects;
|
||||
_stateChanged = stateChanged;
|
||||
|
|
@ -202,6 +200,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
|
|||
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||
}
|
||||
|
||||
public GlamourerApiEc DeletePlayerState(string playerName, ushort worldId, uint key)
|
||||
{
|
||||
var args = ApiHelpers.Args("Name", playerName, "World", worldId, "Key", key);
|
||||
var states = _helpers.FindExistingStates(playerName).ToList();
|
||||
if (states.Count is 0)
|
||||
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||
|
||||
var anyLocked = false;
|
||||
foreach (var state in states)
|
||||
{
|
||||
if (state.CanUnlock(key))
|
||||
_stateManager.DeleteState(state.Identifier);
|
||||
else
|
||||
anyLocked = true;
|
||||
}
|
||||
|
||||
return ApiHelpers.Return(anyLocked
|
||||
? GlamourerApiEc.InvalidKey
|
||||
: GlamourerApiEc.Success, args);
|
||||
}
|
||||
|
||||
public int UnlockAll(uint key)
|
||||
=> _stateManager.Values.Count(state => state.Unlock(key));
|
||||
|
||||
|
|
|
|||
|
|
@ -40,34 +40,37 @@ public class Configuration : IPluginConfiguration, ISavable
|
|||
[JsonIgnore]
|
||||
public readonly EphemeralConfig Ephemeral;
|
||||
|
||||
public bool UseRestrictedGearProtection { get; set; } = false;
|
||||
public bool OpenFoldersByDefault { get; set; } = false;
|
||||
public bool AutoRedrawEquipOnChanges { get; set; } = false;
|
||||
public bool EnableAutoDesigns { get; set; } = true;
|
||||
public bool HideApplyCheckmarks { get; set; } = false;
|
||||
public bool SmallEquip { get; set; } = false;
|
||||
public bool UnlockedItemMode { get; set; } = false;
|
||||
public byte DisableFestivals { get; set; } = 1;
|
||||
public bool EnableGameContextMenu { get; set; } = true;
|
||||
public bool HideWindowInCutscene { get; set; } = false;
|
||||
public bool ShowAutomationSetEditing { get; set; } = true;
|
||||
public bool ShowAllAutomatedApplicationRules { get; set; } = true;
|
||||
public bool ShowUnlockedItemWarnings { get; set; } = true;
|
||||
public bool RevertManualChangesOnZoneChange { get; set; } = false;
|
||||
public bool ShowQuickBarInTabs { get; set; } = true;
|
||||
public bool OpenWindowAtStart { get; set; } = false;
|
||||
public bool ShowWindowWhenUiHidden { get; set; } = false;
|
||||
public bool KeepAdvancedDyesAttached { get; set; } = true;
|
||||
public bool ShowPalettePlusImport { get; set; } = true;
|
||||
public bool UseFloatForColors { get; set; } = true;
|
||||
public bool UseRgbForColors { get; set; } = true;
|
||||
public bool ShowColorConfig { get; set; } = true;
|
||||
public bool ChangeEntireItem { get; set; } = false;
|
||||
public bool AlwaysApplyAssociatedMods { get; set; } = false;
|
||||
public bool UseTemporarySettings { get; set; } = true;
|
||||
public bool AllowDoubleClickToApply { get; set; } = false;
|
||||
public bool RespectManualOnAutomationUpdate { get; set; } = false;
|
||||
public bool PreventRandomRepeats { get; set; } = false;
|
||||
public bool AttachToPcp { get; set; } = true;
|
||||
public bool UseRestrictedGearProtection { get; set; } = false;
|
||||
public bool OpenFoldersByDefault { get; set; } = false;
|
||||
public bool AutoRedrawEquipOnChanges { get; set; } = false;
|
||||
public bool EnableAutoDesigns { get; set; } = true;
|
||||
public bool HideApplyCheckmarks { get; set; } = false;
|
||||
public bool SmallEquip { get; set; } = false;
|
||||
public bool UnlockedItemMode { get; set; } = false;
|
||||
public byte DisableFestivals { get; set; } = 1;
|
||||
public bool EnableGameContextMenu { get; set; } = true;
|
||||
public bool HideWindowInCutscene { get; set; } = false;
|
||||
public bool ShowAutomationSetEditing { get; set; } = true;
|
||||
public bool ShowAllAutomatedApplicationRules { get; set; } = true;
|
||||
public bool ShowUnlockedItemWarnings { get; set; } = true;
|
||||
public bool RevertManualChangesOnZoneChange { get; set; } = false;
|
||||
public bool ShowQuickBarInTabs { get; set; } = true;
|
||||
public bool OpenWindowAtStart { get; set; } = false;
|
||||
public bool ShowWindowWhenUiHidden { get; set; } = false;
|
||||
public bool KeepAdvancedDyesAttached { get; set; } = true;
|
||||
public bool ShowPalettePlusImport { get; set; } = true;
|
||||
public bool UseFloatForColors { get; set; } = true;
|
||||
public bool UseRgbForColors { get; set; } = true;
|
||||
public bool ShowColorConfig { get; set; } = true;
|
||||
public bool ChangeEntireItem { get; set; } = false;
|
||||
public bool AlwaysApplyAssociatedMods { get; set; } = true;
|
||||
public bool UseTemporarySettings { get; set; } = true;
|
||||
public bool AllowDoubleClickToApply { get; set; } = false;
|
||||
public bool RespectManualOnAutomationUpdate { get; set; } = false;
|
||||
public bool PreventRandomRepeats { get; set; } = false;
|
||||
public string PcpFolder { get; set; } = "PCP";
|
||||
public string PcpColor { get; set; } = "";
|
||||
|
||||
public DesignPanelFlag HideDesignPanel { get; set; } = 0;
|
||||
public DesignPanelFlag AutoExpandDesignPanel { get; set; } = 0;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
|||
|
||||
public new JObject JsonSerialize()
|
||||
{
|
||||
var ret = new JObject()
|
||||
var ret = new JObject
|
||||
{
|
||||
["FileVersion"] = FileVersion,
|
||||
["Identifier"] = Identifier,
|
||||
|
|
@ -131,12 +131,17 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
|||
var ret = new JArray();
|
||||
foreach (var (mod, settings) in AssociatedMods)
|
||||
{
|
||||
var obj = new JObject()
|
||||
var obj = new JObject
|
||||
{
|
||||
["Name"] = mod.Name,
|
||||
["Directory"] = mod.DirectoryName,
|
||||
["Enabled"] = settings.Enabled,
|
||||
};
|
||||
if (settings.Remove)
|
||||
obj["Remove"] = true;
|
||||
else if (settings.ForceInherit)
|
||||
obj["Inherit"] = true;
|
||||
else
|
||||
obj["Enabled"] = settings.Enabled;
|
||||
if (settings.Enabled)
|
||||
{
|
||||
obj["Priority"] = settings.Priority;
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ using Glamourer.GameData;
|
|||
using Glamourer.Interop.Material;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using OtterGui.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Extensions;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public sealed class DesignManager : DesignEditor
|
||||
|
|
@ -228,7 +229,7 @@ public sealed class DesignManager : DesignEditor
|
|||
|
||||
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
var idx = design.Tags.IndexOf(tag);
|
||||
var idx = design.Tags.AsEnumerable().IndexOf(tag);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
|
||||
DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx));
|
||||
|
|
@ -261,7 +262,7 @@ public sealed class DesignManager : DesignEditor
|
|||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
|
||||
DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design,
|
||||
new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.IndexOf(newTag)));
|
||||
new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag)));
|
||||
}
|
||||
|
||||
/// <summary> Add an associated mod to a design. </summary>
|
||||
|
|
@ -556,7 +557,7 @@ public sealed class DesignManager : DesignEditor
|
|||
try
|
||||
{
|
||||
File.Move(SaveService.FileNames.MigrationDesignFile,
|
||||
Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"));
|
||||
Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"), true);
|
||||
Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ public class Glamourer : IDalamudPlugin
|
|||
sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n");
|
||||
sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n");
|
||||
sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n");
|
||||
sb.Append($"> **`Attach to PCP: `** {config.AttachToPcp}\n");
|
||||
sb.Append($"> **`Hidden Panels: `** {config.HideDesignPanel}\n");
|
||||
sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n");
|
||||
sb.Append($"> **`QDB Hotkey: `** {config.ToggleQuickDesignBar}\n");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Dalamud.NET.Sdk/13.0.0">
|
||||
<Project Sdk="Dalamud.NET.Sdk/13.1.0">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Glamourer</RootNamespace>
|
||||
<AssemblyName>Glamourer</AssemblyName>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public class GlamourerChangelog
|
|||
Add1_3_8_0(Changelog);
|
||||
Add1_4_0_0(Changelog);
|
||||
Add1_5_0_0(Changelog);
|
||||
Add1_5_1_0(Changelog);
|
||||
}
|
||||
|
||||
private (int, ChangeLogDisplayType) ConfigData()
|
||||
|
|
@ -64,6 +65,16 @@ public class GlamourerChangelog
|
|||
}
|
||||
}
|
||||
|
||||
private static void Add1_5_1_0(Changelog log)
|
||||
=> log.NextVersion("Version 1.5.1.0")
|
||||
.RegisterHighlight("Added support for Penumbras PCP functionality to add the current state of the character as a design.")
|
||||
.RegisterEntry("On import, a design for the PCP is created and, if possible, applied to the character.", 1)
|
||||
.RegisterEntry("No automation is assigned.", 1)
|
||||
.RegisterEntry("Finer control about this can be found in the settings.", 1)
|
||||
.RegisterEntry("Fixed an issue with static visors not toggling through Glamourer (1.5.0.7).")
|
||||
.RegisterEntry("The advanced dye slot combo now contains glasses (1.5.0.7).")
|
||||
.RegisterEntry("Several fixes for patch-related issues (1.5.0.1 - 1.5.0.6");
|
||||
|
||||
private static void Add1_5_0_0(Changelog log)
|
||||
=> log.NextVersion("Version 1.5.0.0")
|
||||
.RegisterImportant("Updated for game version 7.30 and Dalamud API13, which uses a new GUI backend. Some things may not work as expected. Please let me know any issues you encounter.")
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config)
|
|||
public const float GlossWidth = 100;
|
||||
public const float SpecularStrengthWidth = 125;
|
||||
|
||||
private EquipSlot _newSlot = EquipSlot.Head;
|
||||
private int _newMaterialIdx;
|
||||
private int _newRowIdx;
|
||||
private MaterialValueIndex _newKey = MaterialValueIndex.FromSlot(EquipSlot.Head);
|
||||
|
|
@ -178,14 +177,42 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config)
|
|||
|
||||
public sealed class MaterialSlotCombo;
|
||||
|
||||
private void DrawSlotCombo()
|
||||
{
|
||||
var width = ImUtf8.CalcTextSize(EquipSlot.OffHand.ToName()).X + ImGui.GetFrameHeightWithSpacing();
|
||||
ImGui.SetNextItemWidth(width);
|
||||
using var combo = ImUtf8.Combo("##slot"u8, _newKey.SlotName());
|
||||
if (combo)
|
||||
{
|
||||
var currentSlot = _newKey.ToEquipSlot();
|
||||
foreach (var tmpSlot in EquipSlotExtensions.FullSlots)
|
||||
{
|
||||
if (ImUtf8.Selectable(tmpSlot.ToName(), tmpSlot == currentSlot) && currentSlot != tmpSlot)
|
||||
_newKey = MaterialValueIndex.FromSlot(tmpSlot) with
|
||||
{
|
||||
MaterialIndex = (byte)_newMaterialIdx,
|
||||
RowIndex = (byte)_newRowIdx,
|
||||
};
|
||||
}
|
||||
|
||||
var currentBonus = _newKey.ToBonusSlot();
|
||||
foreach (var bonusSlot in BonusExtensions.AllFlags)
|
||||
{
|
||||
if (ImUtf8.Selectable(bonusSlot.ToName(), bonusSlot == currentBonus) && bonusSlot != currentBonus)
|
||||
_newKey = MaterialValueIndex.FromSlot(bonusSlot) with
|
||||
{
|
||||
MaterialIndex = (byte)_newMaterialIdx,
|
||||
RowIndex = (byte)_newRowIdx,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ImUtf8.HoverTooltip("Choose a slot for an advanced dye row."u8);
|
||||
}
|
||||
|
||||
public void DrawNew(Design design)
|
||||
{
|
||||
if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot))
|
||||
_newKey = MaterialValueIndex.FromSlot(_newSlot) with
|
||||
{
|
||||
MaterialIndex = (byte)_newMaterialIdx,
|
||||
RowIndex = (byte)_newRowIdx,
|
||||
};
|
||||
DrawSlotCombo();
|
||||
ImUtf8.SameLineInner();
|
||||
DrawMaterialIdxDrag();
|
||||
ImUtf8.SameLineInner();
|
||||
|
|
|
|||
|
|
@ -22,11 +22,12 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
|
|||
private readonly CustomizeService _customize;
|
||||
private readonly GPoseService _gpose;
|
||||
|
||||
private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2];
|
||||
private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2 + BonusExtensions.AllFlags.Count];
|
||||
|
||||
public IEnumerable<KeyValuePair<EquipSlot, EquipItem>> LastItems
|
||||
=> EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand).Zip(_lastItems)
|
||||
.Select(p => new KeyValuePair<EquipSlot, EquipItem>(p.First, p.Second));
|
||||
public IEnumerable<KeyValuePair<object, EquipItem>> LastItems
|
||||
=> EquipSlotExtensions.EqdpSlots.Cast<object>().Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)
|
||||
.Concat(BonusExtensions.AllFlags.Cast<object>()).Zip(_lastItems)
|
||||
.Select(p => new KeyValuePair<object, EquipItem>(p.First, p.Second));
|
||||
|
||||
public ChangedItemType LastType { get; private set; } = ChangedItemType.None;
|
||||
public uint LastId { get; private set; }
|
||||
|
|
@ -72,6 +73,21 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
|
|||
if (!Player())
|
||||
return;
|
||||
|
||||
var bonusSlot = item.Type.ToBonus();
|
||||
if (bonusSlot is not BonusItemFlag.Unknown)
|
||||
{
|
||||
// + 2 due to weapons.
|
||||
var glasses = _lastItems[bonusSlot.ToSlot() + 2];
|
||||
using (_ = !openTooltip ? null : ImRaii.Tooltip())
|
||||
{
|
||||
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor.");
|
||||
if (glasses.Valid)
|
||||
ImGui.TextUnformatted($"{prefix}Control + Right-Click to re-apply {glasses.Name} to current actor.");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var slot = item.Type.ToSlot();
|
||||
var last = _lastItems[slot.ToIndex()];
|
||||
switch (slot)
|
||||
|
|
@ -109,6 +125,27 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
|
|||
|
||||
public void ApplyItem(ActorState state, EquipItem item)
|
||||
{
|
||||
var bonusSlot = item.Type.ToBonus();
|
||||
if (bonusSlot is not BonusItemFlag.Unknown)
|
||||
{
|
||||
// + 2 due to weapons.
|
||||
var glasses = _lastItems[bonusSlot.ToSlot() + 2];
|
||||
if (ImGui.GetIO().KeyCtrl && glasses.Valid)
|
||||
{
|
||||
Glamourer.Log.Debug($"Re-Applying {glasses.Name} to {bonusSlot.ToName()}.");
|
||||
SetLastItem(bonusSlot, default, state);
|
||||
_stateManager.ChangeBonusItem(state, bonusSlot, glasses, ApplySettings.Manual);
|
||||
}
|
||||
else
|
||||
{
|
||||
Glamourer.Log.Debug($"Applying {item.Name} to {bonusSlot.ToName()}.");
|
||||
SetLastItem(bonusSlot, item, state);
|
||||
_stateManager.ChangeBonusItem(state, bonusSlot, item, ApplySettings.Manual);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var slot = item.Type.ToSlot();
|
||||
var last = _lastItems[slot.ToIndex()];
|
||||
switch (slot)
|
||||
|
|
@ -265,7 +302,22 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
|
|||
{
|
||||
var oldItem = state.ModelData.Item(slot);
|
||||
if (oldItem.Id != item.Id)
|
||||
_lastItems[slot.ToIndex()] = oldItem;
|
||||
last = oldItem;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLastItem(BonusItemFlag slot, EquipItem item, ActorState state)
|
||||
{
|
||||
ref var last = ref _lastItems[slot.ToSlot() + 2];
|
||||
if (!item.Valid)
|
||||
{
|
||||
last = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
var oldItem = state.ModelData.BonusItem(slot);
|
||||
if (oldItem.Id != item.Id)
|
||||
last = oldItem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
using Dalamud.Utility;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using OtterGui;
|
||||
using OtterGui.Custom;
|
||||
using OtterGui.Extensions;
|
||||
using OtterGui.Log;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using OtterGui.Custom;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.AutomationTab;
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,13 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
|
|||
ImGui.Separator();
|
||||
foreach (var (slot, item) in _penumbraTooltip.LastItems)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn($"{slot.ToName()} Revert-Item");
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot e: ImGuiUtil.DrawTableColumn($"{e.ToName()} Revert-Item"); break;
|
||||
case BonusItemFlag f: ImGuiUtil.DrawTableColumn($"{f.ToName()} Revert-Item"); break;
|
||||
default: ImGuiUtil.DrawTableColumn("Unk Revert-Item"); break;
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTableColumn(item.Valid ? item.Name : "None");
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,10 +189,7 @@ public class DesignDetailTab
|
|||
else if (_selector.Selected!.Color.Length != 0)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
var size = new Vector2(ImGui.GetFrameHeight());
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor);
|
||||
ImUtf8.HoverTooltip("The color associated with this design does not exist."u8);
|
||||
ImUtf8.Icon(FontAwesomeIcon.ExclamationCircle, "The color associated with this design does not exist."u8, _colors.MissingColor);
|
||||
}
|
||||
|
||||
ImUtf8.DrawFrameColumn("Creation Date"u8);
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ public class DesignPanel
|
|||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
|
|
@ -270,11 +271,9 @@ public class DesignPanel
|
|||
DrawCrestApplication();
|
||||
ImUtf8.IconDummy();
|
||||
DrawMetaApplication();
|
||||
ImUtf8.IconDummy();
|
||||
DrawBonusSlotApplication();
|
||||
}
|
||||
|
||||
ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2);
|
||||
ImGui.SameLine(210 * ImUtf8.GlobalScale + ImGui.GetStyle().ItemSpacing.X);
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable<EquipSlot> slots)
|
||||
|
|
@ -316,6 +315,9 @@ public class DesignPanel
|
|||
|
||||
ImUtf8.IconDummy();
|
||||
DrawParameterApplication();
|
||||
|
||||
ImUtf8.IconDummy();
|
||||
DrawBonusSlotApplication();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -324,7 +326,7 @@ public class DesignPanel
|
|||
var enabled = _config.DeleteDesignModifier.IsActive();
|
||||
bool? equip = null;
|
||||
bool? customize = null;
|
||||
var size = new Vector2(200 * ImUtf8.GlobalScale, 0);
|
||||
var size = new Vector2(210 * ImUtf8.GlobalScale, 0);
|
||||
if (ImUtf8.ButtonEx("Disable Everything"u8,
|
||||
"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size,
|
||||
!enabled))
|
||||
|
|
@ -402,6 +404,7 @@ public class DesignPanel
|
|||
_manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value);
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, equip.Value);
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, equip.Value);
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.EarState, equip.Value);
|
||||
}
|
||||
|
||||
if (customize.HasValue)
|
||||
|
|
@ -414,6 +417,7 @@ public class DesignPanel
|
|||
"Apply Hat Visibility",
|
||||
"Apply Visor State",
|
||||
"Apply Weapon Visibility",
|
||||
"Apply Viera Ear Visibility",
|
||||
];
|
||||
|
||||
private void DrawMetaApplication()
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
|
|||
private void DrawApplyAllButton()
|
||||
{
|
||||
var (id, name) = penumbra.CurrentCollection;
|
||||
if (config.Ephemeral.IncognitoMode)
|
||||
name = id.ShortGuid();
|
||||
if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll",
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty))
|
||||
ApplyAll();
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ public class MultiDesignPanel(
|
|||
|
||||
foreach (var leaf in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>())
|
||||
{
|
||||
var index = leaf.Value.Tags.IndexOf(_tag);
|
||||
var index = leaf.Value.Tags.AsEnumerable().IndexOf(_tag);
|
||||
if (index >= 0)
|
||||
_removeDesigns.Add((leaf.Value, index));
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
|
@ -8,7 +9,8 @@ using Glamourer.Designs;
|
|||
using Glamourer.Gui.Tabs.DesignTab;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.PalettePlus;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Glamourer.Services;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Text;
|
||||
using OtterGui.Widgets;
|
||||
|
|
@ -27,7 +29,8 @@ public class SettingsTab(
|
|||
CollectionOverrideDrawer overrides,
|
||||
CodeDrawer codeDrawer,
|
||||
Glamourer glamourer,
|
||||
AutoDesignApplier autoDesignApplier)
|
||||
AutoDesignApplier autoDesignApplier,
|
||||
PcpService pcpService)
|
||||
: ITab
|
||||
{
|
||||
private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray();
|
||||
|
|
@ -89,6 +92,15 @@ public class SettingsTab(
|
|||
Checkbox("Auto-Reload Gear"u8,
|
||||
"Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8,
|
||||
config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v);
|
||||
Checkbox("Attach to PCP-Handling"u8,
|
||||
"Add the actor's glamourer state when a PCP is created by Penumbra, and create a design and apply it if possible when a PCP is installed by Penumbra."u8,
|
||||
config.AttachToPcp, pcpService.Set);
|
||||
var active = config.DeleteDesignModifier.IsActive();
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.ButtonEx("Delete all PCP Designs"u8, "Deletes all designs tagged with 'PCP' from the design list."u8, disabled: !active))
|
||||
pcpService.CleanPcpDesigns();
|
||||
if (!active)
|
||||
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"\nHold {config.DeleteDesignModifier} while clicking.");
|
||||
Checkbox("Revert Manual Changes on Zone Change"u8,
|
||||
"Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8,
|
||||
config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v);
|
||||
|
|
@ -124,6 +136,28 @@ public class SettingsTab(
|
|||
Checkbox("Reset Temporary Settings"u8,
|
||||
"Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default."u8,
|
||||
config.DefaultDesignSettings.ResetTemporarySettings, v => config.DefaultDesignSettings.ResetTemporarySettings = v);
|
||||
|
||||
var tmp = config.PcpFolder;
|
||||
ImGui.SetNextItemWidth(0.4f * ImGui.GetContentRegionAvail().X);
|
||||
if (ImUtf8.InputText("##pcpFolder"u8, ref tmp))
|
||||
config.PcpFolder = tmp;
|
||||
|
||||
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||
config.Save();
|
||||
|
||||
ImGuiUtil.LabeledHelpMarker("Default PCP Organizational Folder",
|
||||
"The folder any designs created due to penumbra character packs are moved to on creation.\nLeave blank to import into Root.");
|
||||
|
||||
tmp = config.PcpColor;
|
||||
ImGui.SetNextItemWidth(0.4f * ImGui.GetContentRegionAvail().X);
|
||||
if (ImUtf8.InputText("##pcpColor"u8, ref tmp))
|
||||
config.PcpColor = tmp;
|
||||
|
||||
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||
config.Save();
|
||||
|
||||
ImGuiUtil.LabeledHelpMarker("Default PCP Design Color",
|
||||
"The name of the color group any designs created due to penumbra character packs are assigned.\nLeave blank for no specific color assignment.");
|
||||
}
|
||||
|
||||
private void DrawInterfaceSettings()
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|
|||
|
||||
var drawData = type switch
|
||||
{
|
||||
MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId),
|
||||
MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, (HumanSlot)slotId),
|
||||
_ => GetTempSlot((Weapon*)characterBase),
|
||||
};
|
||||
var mode = PrepareColorSet.GetMode(material);
|
||||
|
|
@ -192,12 +192,24 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
|
|||
}
|
||||
|
||||
/// <summary> We need to get the temporary set, variant and stain that is currently being set if it is available. </summary>
|
||||
private static CharacterWeapon GetTempSlot(Human* human, byte slotId)
|
||||
private static CharacterWeapon GetTempSlot(Human* human, HumanSlot slotId)
|
||||
{
|
||||
if (human->ChangedEquipData == null)
|
||||
return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0);
|
||||
if (human->ChangedEquipData is null)
|
||||
return slotId.ToSpecificEnum() switch
|
||||
{
|
||||
EquipSlot slot => ((Model)human).GetArmor(slot).ToWeapon(0),
|
||||
BonusItemFlag bonus => ((Model)human).GetBonus(bonus).ToWeapon(0),
|
||||
_ => default,
|
||||
};
|
||||
|
||||
return ((CharacterArmor*)human->ChangedEquipData + slotId * 3)->ToWeapon(0);
|
||||
if (!slotId.ToSlotIndex(out var index))
|
||||
return default;
|
||||
|
||||
var item = (ChangedEquipData*)human->ChangedEquipData + index;
|
||||
if (index < 10)
|
||||
return ((CharacterArmor*)item)->ToWeapon(0);
|
||||
|
||||
return new CharacterWeapon(item->BonusModel, 0, item->BonusVariant, StainIds.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using Lumina.Data.Files;
|
||||
using Penumbra.GameData.Files.MaterialStructs;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture;
|
||||
|
|
@ -69,9 +68,9 @@ public static unsafe class MaterialService
|
|||
return null;
|
||||
|
||||
var material = (MaterialResourceHandle*) model.AsCharacterBase->MaterialsSpan[index].Value;
|
||||
if (material == null || material->ColorTable == null)
|
||||
if (material == null || material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable)
|
||||
return null;
|
||||
|
||||
return (ColorTable.Table*)material->ColorTable;
|
||||
return (ColorTable.Table*)material->DataSet;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,18 @@ public readonly record struct MaterialValueIndex(
|
|||
return idx > 2 ? Invalid : new MaterialValueIndex(DrawObjectType.Human, (byte)(idx + 16), 0, 0);
|
||||
}
|
||||
|
||||
public string SlotName()
|
||||
{
|
||||
var slot = ToEquipSlot();
|
||||
if (slot is not EquipSlot.Unknown)
|
||||
return slot.ToName();
|
||||
|
||||
if (DrawObject is DrawObjectType.Human && SlotIndex is 16)
|
||||
return BonusItemFlag.Glasses.ToString();
|
||||
|
||||
return EquipSlot.Unknown.ToName();
|
||||
}
|
||||
|
||||
public EquipSlot ToEquipSlot()
|
||||
=> DrawObject switch
|
||||
{
|
||||
|
|
@ -59,6 +71,13 @@ public readonly record struct MaterialValueIndex(
|
|||
_ => EquipSlot.Unknown,
|
||||
};
|
||||
|
||||
public BonusItemFlag ToBonusSlot()
|
||||
=> DrawObject switch
|
||||
{
|
||||
DrawObjectType.Human when SlotIndex > 15 => ((uint)SlotIndex - 16).ToBonusSlot(),
|
||||
_ => BonusItemFlag.Unknown,
|
||||
};
|
||||
|
||||
public unsafe bool TryGetModel(Actor actor, out Model model)
|
||||
{
|
||||
if (!actor.Valid)
|
||||
|
|
|
|||
|
|
@ -69,13 +69,13 @@ public sealed unsafe class PrepareColorSet
|
|||
public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds,
|
||||
out ColorTable.Table table)
|
||||
{
|
||||
if (material->ColorTable == null)
|
||||
if (material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable)
|
||||
{
|
||||
table = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var newTable = *(ColorTable.Table*)material->ColorTable;
|
||||
var newTable = *(ColorTable.Table*)material->DataSet;
|
||||
if (GetDyeTable(material, out var dyeTable))
|
||||
{
|
||||
if (stainIds.Stain1.Id != 0)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using Dalamud.Plugin;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.State;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
|
|
@ -49,6 +50,8 @@ public class PenumbraService : IDisposable
|
|||
private readonly EventSubscriber<nint, Guid, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, Guid, nint> _createdCharacterBase;
|
||||
private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _modSettingChanged;
|
||||
private readonly EventSubscriber<JObject, string, Guid> _pcpParsed;
|
||||
private readonly EventSubscriber<JObject, ushort, string> _pcpCreated;
|
||||
|
||||
private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier;
|
||||
private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections;
|
||||
|
|
@ -101,6 +104,8 @@ public class PenumbraService : IDisposable
|
|||
_createdCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatedCharacterBase.Subscriber(pi);
|
||||
_creatingCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatingCharacterBase.Subscriber(pi);
|
||||
_modSettingChanged = global::Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi);
|
||||
_pcpCreated = global::Penumbra.Api.IpcSubscribers.CreatingPcp.Subscriber(pi);
|
||||
_pcpParsed = global::Penumbra.Api.IpcSubscribers.ParsingPcp.Subscriber(pi);
|
||||
Reattach();
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +140,18 @@ public class PenumbraService : IDisposable
|
|||
remove => _modSettingChanged.Event -= value;
|
||||
}
|
||||
|
||||
public event Action<JObject, ushort, string> PcpCreated
|
||||
{
|
||||
add => _pcpCreated.Event += value;
|
||||
remove => _pcpCreated.Event -= value;
|
||||
}
|
||||
|
||||
public event Action<JObject, string, Guid> PcpParsed
|
||||
{
|
||||
add => _pcpParsed.Event += value;
|
||||
remove => _pcpParsed.Event -= value;
|
||||
}
|
||||
|
||||
public Dictionary<Guid, string> GetCollections()
|
||||
=> Available ? _collections!.Invoke() : [];
|
||||
|
||||
|
|
@ -514,28 +531,30 @@ public class PenumbraService : IDisposable
|
|||
_clickSubscriber.Enable();
|
||||
_creatingCharacterBase.Enable();
|
||||
_createdCharacterBase.Enable();
|
||||
_pcpCreated.Enable();
|
||||
_pcpParsed.Enable();
|
||||
_modSettingChanged.Enable();
|
||||
_collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface);
|
||||
_collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface);
|
||||
_redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface);
|
||||
_checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke();
|
||||
_getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke();
|
||||
_objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface);
|
||||
_getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface);
|
||||
_currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface);
|
||||
_getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface);
|
||||
_inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface);
|
||||
_setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface);
|
||||
_setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface);
|
||||
_setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface);
|
||||
_setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface);
|
||||
_openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface);
|
||||
_getChangedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItems(_pluginInterface);
|
||||
_setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface);
|
||||
_setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface);
|
||||
_removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface);
|
||||
_collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface);
|
||||
_collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface);
|
||||
_redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface);
|
||||
_checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke();
|
||||
_getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke();
|
||||
_objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface);
|
||||
_getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface);
|
||||
_currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface);
|
||||
_getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface);
|
||||
_inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface);
|
||||
_setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface);
|
||||
_setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface);
|
||||
_setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface);
|
||||
_setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface);
|
||||
_openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface);
|
||||
_getChangedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItems(_pluginInterface);
|
||||
_setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface);
|
||||
_setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface);
|
||||
_removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface);
|
||||
_removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface);
|
||||
_removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface);
|
||||
_removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface);
|
||||
_removeAllTemporaryModSettingsPlayer =
|
||||
new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface);
|
||||
_queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface);
|
||||
|
|
@ -566,6 +585,8 @@ public class PenumbraService : IDisposable
|
|||
_creatingCharacterBase.Disable();
|
||||
_createdCharacterBase.Disable();
|
||||
_modSettingChanged.Disable();
|
||||
_pcpCreated.Disable();
|
||||
_pcpParsed.Disable();
|
||||
if (Available)
|
||||
{
|
||||
_collectionByIdentifier = null;
|
||||
|
|
@ -612,5 +633,7 @@ public class PenumbraService : IDisposable
|
|||
_initializedEvent.Dispose();
|
||||
_disposedEvent.Dispose();
|
||||
_modSettingChanged.Dispose();
|
||||
_pcpCreated.Dispose();
|
||||
_pcpParsed.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ namespace Glamourer.Interop;
|
|||
|
||||
public class VisorService : IDisposable
|
||||
{
|
||||
private readonly PenumbraReloaded _penumbra;
|
||||
private readonly IGameInteropProvider _interop;
|
||||
public readonly VisorStateChanged Event;
|
||||
private readonly PenumbraReloaded _penumbra;
|
||||
private readonly IGameInteropProvider _interop;
|
||||
public readonly VisorStateChanged Event;
|
||||
|
||||
public VisorService(VisorStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra)
|
||||
{
|
||||
|
|
@ -36,7 +36,7 @@ public class VisorService : IDisposable
|
|||
/// <param name="human"> The draw object. </param>
|
||||
/// <param name="on"> The desired state (true: toggled). </param>
|
||||
/// <returns> Whether the state was changed. </returns>
|
||||
public bool SetVisorState(Model human, bool on)
|
||||
public unsafe bool SetVisorState(Model human, bool on)
|
||||
{
|
||||
if (!human.IsHuman)
|
||||
return false;
|
||||
|
|
@ -46,6 +46,8 @@ public class VisorService : IDisposable
|
|||
if (oldState == on)
|
||||
return false;
|
||||
|
||||
// No clue what this flag does, but it's necessary for toggling static visors on or off, e.g. Alternate Cap (6229-1).
|
||||
human.AsHuman->StateFlags |= (CharacterBase.StateFlag)0x40000000;
|
||||
SetupVisorDetour(human, human.GetArmor(EquipSlot.Head).Set.Id, on);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public unsafe class WeaponService : IDisposable
|
|||
private readonly WeaponLoading _event;
|
||||
private readonly ThreadLocal<bool> _inUpdate = new(() => false);
|
||||
|
||||
private readonly delegate* unmanaged[Stdcall]<DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void>
|
||||
private readonly delegate* unmanaged[Stdcall]<DrawDataContainer*, uint, ulong, byte, byte, byte, byte, int, void>
|
||||
_original;
|
||||
|
||||
public WeaponService(WeaponLoading @event, IGameInteropProvider interop)
|
||||
|
|
@ -22,7 +22,7 @@ public unsafe class WeaponService : IDisposable
|
|||
_loadWeaponHook =
|
||||
interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
|
||||
_original =
|
||||
(delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void >)
|
||||
(delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, int, void >)
|
||||
DrawDataContainer.MemberFunctionPointers.LoadWeapon;
|
||||
_loadWeaponHook.Enable();
|
||||
}
|
||||
|
|
@ -36,13 +36,14 @@ public unsafe class WeaponService : IDisposable
|
|||
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
|
||||
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
|
||||
// unk4 seemed to be the same as unk1.
|
||||
// unk5 is new in 7.30 and is checked at the beginning of the function to call some timeline related function.
|
||||
private delegate void LoadWeaponDelegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject, byte unk4);
|
||||
byte skipGameObject, byte unk4, byte unk5);
|
||||
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook;
|
||||
|
||||
private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weaponValue, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject, byte unk4)
|
||||
byte skipGameObject, byte unk4, byte unk5)
|
||||
{
|
||||
if (!_inUpdate.Value)
|
||||
{
|
||||
|
|
@ -64,21 +65,21 @@ public unsafe class WeaponService : IDisposable
|
|||
else if (weaponValue == actor.GetMainhand().Value && weaponValue != 0)
|
||||
_event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon);
|
||||
|
||||
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4, unk5);
|
||||
|
||||
if (tmpWeapon.Value != weapon.Value)
|
||||
{
|
||||
if (tmpWeapon.Skeleton.Id == 0)
|
||||
tmpWeapon.Stains = StainIds.None;
|
||||
_loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4);
|
||||
_loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4, unk5);
|
||||
}
|
||||
|
||||
Glamourer.Log.Excessive(
|
||||
$"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}");
|
||||
$"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}, {unk5}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_loadWeaponHook.Original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
_loadWeaponHook.Original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4, unk5);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,18 +90,18 @@ public unsafe class WeaponService : IDisposable
|
|||
{
|
||||
case EquipSlot.MainHand:
|
||||
_inUpdate.Value = true;
|
||||
_original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0);
|
||||
_original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0, 0);
|
||||
_inUpdate.Value = false;
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
_inUpdate.Value = true;
|
||||
_original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0);
|
||||
_original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0, 0);
|
||||
_inUpdate.Value = false;
|
||||
return;
|
||||
case EquipSlot.BothHand:
|
||||
_inUpdate.Value = true;
|
||||
_original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0);
|
||||
_original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0);
|
||||
_original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0, 0);
|
||||
_original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0, 0);
|
||||
_inUpdate.Value = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using OtterGui.Services;
|
||||
|
||||
namespace Glamourer.Services;
|
||||
|
||||
public class BackupService
|
||||
public class BackupService : IAsyncService
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly DirectoryInfo _configDirectory;
|
||||
|
|
@ -14,7 +15,7 @@ public class BackupService
|
|||
_logger = logger;
|
||||
_fileNames = GlamourerFiles(fileNames);
|
||||
_configDirectory = new DirectoryInfo(fileNames.ConfigDirectory);
|
||||
Backup.CreateAutomaticBackup(logger, _configDirectory, _fileNames);
|
||||
Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), _fileNames));
|
||||
}
|
||||
|
||||
/// <summary> Create a permanent backup with a given name for migrations. </summary>
|
||||
|
|
@ -40,4 +41,9 @@ public class BackupService
|
|||
|
||||
return list;
|
||||
}
|
||||
|
||||
public Task Awaiter { get; }
|
||||
|
||||
public bool Finished
|
||||
=> Awaiter.IsCompletedSuccessfully;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ public class CodeService
|
|||
| CodeFlag.OopsMiqote
|
||||
| CodeFlag.OopsRoegadyn
|
||||
| CodeFlag.OopsAuRa
|
||||
| CodeFlag.OopsHrothgar;
|
||||
| CodeFlag.OopsHrothgar
|
||||
| CodeFlag.OopsViera;
|
||||
|
||||
public const CodeFlag FullCodes = CodeFlag.Face | CodeFlag.Manderville | CodeFlag.Smiles;
|
||||
|
||||
|
|
@ -250,3 +251,4 @@ public class CodeService
|
|||
_ => (false, 0, string.Empty, string.Empty, string.Empty),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,12 @@ public class CommandService : IDisposable, IApiService
|
|||
_config.Ephemeral.LockMainWindow = !_config.Ephemeral.LockMainWindow;
|
||||
_config.Ephemeral.Save();
|
||||
return;
|
||||
case "automation":
|
||||
var newValue = !_config.EnableAutoDesigns;
|
||||
_config.EnableAutoDesigns = newValue;
|
||||
_autoDesignApplier.OnEnableAutoDesignsChanged(newValue);
|
||||
_config.Save();
|
||||
return;
|
||||
default:
|
||||
_chat.Print("Use without argument to toggle the main window.");
|
||||
_chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer")
|
||||
|
|
|
|||
119
Glamourer/Services/PcpService.cs
Normal file
119
Glamourer/Services/PcpService.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.State;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Interop;
|
||||
|
||||
namespace Glamourer.Services;
|
||||
|
||||
public class PcpService : IRequiredService
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly PenumbraService _penumbra;
|
||||
private readonly ActorObjectManager _objects;
|
||||
private readonly StateManager _state;
|
||||
private readonly DesignConverter _designConverter;
|
||||
private readonly DesignManager _designManager;
|
||||
|
||||
public PcpService(Configuration config, PenumbraService penumbra, ActorObjectManager objects, StateManager state,
|
||||
DesignConverter designConverter, DesignManager designManager)
|
||||
{
|
||||
_config = config;
|
||||
_penumbra = penumbra;
|
||||
_objects = objects;
|
||||
_state = state;
|
||||
_designConverter = designConverter;
|
||||
_designManager = designManager;
|
||||
|
||||
_config.AttachToPcp = !_config.AttachToPcp;
|
||||
Set(!_config.AttachToPcp);
|
||||
}
|
||||
|
||||
public void CleanPcpDesigns()
|
||||
{
|
||||
var designs = _designManager.Designs.Where(d => d.Tags.Contains("PCP")).ToList();
|
||||
Glamourer.Log.Information($"[PCPService] Deleting {designs.Count} designs containing the tag PCP.");
|
||||
foreach (var design in designs)
|
||||
_designManager.Delete(design);
|
||||
}
|
||||
|
||||
public void Set(bool value)
|
||||
{
|
||||
if (value == _config.AttachToPcp)
|
||||
return;
|
||||
|
||||
_config.AttachToPcp = value;
|
||||
_config.Save();
|
||||
if (value)
|
||||
{
|
||||
Glamourer.Log.Information("[PCPService] Attached to PCP handling.");
|
||||
_penumbra.PcpCreated += OnPcpCreation;
|
||||
_penumbra.PcpParsed += OnPcpParse;
|
||||
}
|
||||
else
|
||||
{
|
||||
Glamourer.Log.Information("[PCPService] Detached from PCP handling.");
|
||||
_penumbra.PcpCreated -= OnPcpCreation;
|
||||
_penumbra.PcpParsed -= OnPcpParse;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPcpParse(JObject jObj, string modDirectory, Guid collection)
|
||||
{
|
||||
Glamourer.Log.Debug("[PCPService] Parsing PCP file.");
|
||||
if (jObj["Glamourer"] is not JObject glamourer)
|
||||
return;
|
||||
|
||||
if (glamourer["Version"]!.ToObject<int>() is not 1)
|
||||
return;
|
||||
|
||||
if (_designConverter.FromJObject(glamourer["Design"] as JObject, true, true) is not { } designBase)
|
||||
return;
|
||||
|
||||
var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject);
|
||||
if (!actorIdentifier.IsValid)
|
||||
return;
|
||||
|
||||
var time = new DateTimeOffset(jObj["Time"]?.ToObject<DateTime>() ?? DateTime.UtcNow);
|
||||
var design = _designManager.CreateClone(designBase,
|
||||
$"{_config.PcpFolder}/{actorIdentifier} - {jObj["Note"]?.ToObject<string>() ?? string.Empty}", true);
|
||||
_designManager.AddTag(design, "PCP");
|
||||
_designManager.SetWriteProtection(design, true);
|
||||
_designManager.AddMod(design, new Mod(modDirectory, modDirectory), new ModSettings([], 0, true, false, false));
|
||||
_designManager.ChangeDescription(design, $"PCP design created for {actorIdentifier} on {time}.");
|
||||
_designManager.ChangeResetAdvancedDyes(design, true);
|
||||
_designManager.SetQuickDesign(design, false);
|
||||
_designManager.ChangeColor(design, _config.PcpColor);
|
||||
|
||||
Glamourer.Log.Debug("[PCPService] Created PCP design.");
|
||||
if (_state.GetOrCreate(actorIdentifier, _objects.TryGetValue(actorIdentifier, out var data) ? data.Objects[0] : Actor.Null,
|
||||
out var state))
|
||||
{
|
||||
_state.ApplyDesign(state!, design, ApplySettings.Manual);
|
||||
Glamourer.Log.Debug($"[PCPService] Applied PCP design to {actorIdentifier.Incognito(null)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPcpCreation(JObject jObj, ushort index, string path)
|
||||
{
|
||||
Glamourer.Log.Debug("[PCPService] Adding Glamourer data to PCP file.");
|
||||
var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject);
|
||||
if (!actorIdentifier.IsValid)
|
||||
return;
|
||||
|
||||
if (!_state.GetOrCreate(actorIdentifier, _objects.Objects[(int)index], out var state))
|
||||
{
|
||||
Glamourer.Log.Debug($"[PCPService] Could not get or create state for actor {index}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var design = _designConverter.Convert(state, ApplicationRules.All);
|
||||
jObj["Glamourer"] = new JObject
|
||||
{
|
||||
["Version"] = 1,
|
||||
["Design"] = design.JsonSerialize(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -385,7 +385,7 @@ public class StateApplier(
|
|||
var actors = ChangeMetaState(state, MetaIndex.Wetness, true);
|
||||
if (redraw)
|
||||
{
|
||||
if (withLock)
|
||||
if (withLock && actors.Valid)
|
||||
state.TempLock();
|
||||
ForceRedraw(actors);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
"net9.0-windows7.0": {
|
||||
"DalamudPackager": {
|
||||
"type": "Direct",
|
||||
"requested": "[13.0.0, )",
|
||||
"resolved": "13.0.0",
|
||||
"contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w=="
|
||||
"requested": "[13.1.0, )",
|
||||
"resolved": "13.1.0",
|
||||
"contentHash": "XdoNhJGyFby5M/sdcRhnc5xTop9PHy+H50PTWpzLhJugjB19EDBiHD/AsiDF66RETM+0qKUdJBZrNuebn7qswQ=="
|
||||
},
|
||||
"DotNet.ReproducibleBuilds": {
|
||||
"type": "Direct",
|
||||
|
|
@ -24,6 +24,19 @@
|
|||
"Vortice.DXGI": "3.4.2-beta"
|
||||
}
|
||||
},
|
||||
"FlatSharp.Compiler": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.9.0",
|
||||
"contentHash": "MU6808xvdbWJ3Ev+5PKalqQuzvVbn1DzzQH8txRDHGFUNDvHjd+ejqpvnYc9BSJ8Qp8VjkkpJD8OzRYilbPp3A=="
|
||||
},
|
||||
"FlatSharp.Runtime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.9.0",
|
||||
"contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.5"
|
||||
}
|
||||
},
|
||||
"JetBrains.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2024.3.0",
|
||||
|
|
@ -55,6 +68,11 @@
|
|||
"SharpGen.Runtime": "2.1.2-beta"
|
||||
}
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.5",
|
||||
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
|
||||
},
|
||||
"Vortice.DirectX": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.4.2-beta",
|
||||
|
|
@ -95,6 +113,8 @@
|
|||
"penumbra.gamedata": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FlatSharp.Compiler": "[7.9.0, )",
|
||||
"FlatSharp.Runtime": "[7.9.0, )",
|
||||
"OtterGui": "[1.0.0, )",
|
||||
"Penumbra.Api": "[5.10.0, )",
|
||||
"Penumbra.String": "[1.0.6, )"
|
||||
|
|
|
|||
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 5e32bb225e5fbb60ed8426ed887fd7e8a831ebae
|
||||
Subproject commit 1459e2b8f5e1687f659836709e23571235d4206c
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit c27a06004138f2ec80ccdb494bb6ddf6d39d2165
|
||||
Subproject commit c23ee05c1e9fa103eaa52e6aa7e855ef568ee669
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit fd875c43ee910350107b2609809335285bd4ac0f
|
||||
Subproject commit d889f9ef918514a46049725052d378b441915b00
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 878acce46e286867d6ef1f8ecedb390f7bac34fd
|
||||
Subproject commit c8611a0c546b6b2ec29214ab319fc2c38fe74793
|
||||
10
repo.json
10
repo.json
|
|
@ -17,8 +17,8 @@
|
|||
"Character"
|
||||
],
|
||||
"InternalName": "Glamourer",
|
||||
"AssemblyVersion": "1.5.0.2",
|
||||
"TestingAssemblyVersion": "1.5.0.2",
|
||||
"AssemblyVersion": "1.5.1.5",
|
||||
"TestingAssemblyVersion": "1.5.1.5",
|
||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 13,
|
||||
|
|
@ -27,9 +27,9 @@
|
|||
"IsTestingExclusive": "False",
|
||||
"DownloadCount": 1,
|
||||
"LastUpdate": 1618608322,
|
||||
"DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip",
|
||||
"DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip",
|
||||
"DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip",
|
||||
"DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip",
|
||||
"DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip",
|
||||
"DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip",
|
||||
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue