Compare commits

...

26 commits

Author SHA1 Message Date
Actions User
5b6517aae8 [CI] Updating repo.json for 1.5.1.5 2025-11-28 22:09:08 +00:00
Ottermandias
aadcf771e7 1.5.1.5 2025-11-28 23:06:53 +01:00
Ottermandias
bf4673a1d9 Save Remove and Inherit for mod associations. 2025-11-07 23:30:18 +01:00
Ottermandias
76b214c643 Fix try-on interaction with Penumbra for facewear. 2025-11-07 23:30:18 +01:00
Ottermandias
434a5a809e Make old backup files overwrite instead of throwing. 2025-11-07 23:30:18 +01:00
Actions User
88fe25f69e [CI] Updating repo.json for testing_1.5.1.4 2025-10-23 15:40:25 +00:00
Ottermandias
bef1e39ac3 Update Libraries. 2025-10-23 17:37:27 +02:00
Ottermandias
c604d5dbe5 Add DeletePlayerState. 2025-10-23 17:25:59 +02:00
Ottermandias
a0d912a395 Fix issue with reverting state of unavailable actors. 2025-10-23 17:25:59 +02:00
Actions User
a56852f918 [CI] Updating repo.json for 1.5.1.3 2025-10-07 11:02:02 +00:00
Ottermandias
4228fc1b89 fu 2025-10-07 12:59:39 +02:00
Ottermandias
e644b8da28 Fix span issue. 2025-10-07 12:53:55 +02:00
Ottermandias
ace3a8f755 Again. 2025-10-07 12:43:40 +02:00
Ottermandias
76ed347cbf Update signatures. 2025-10-07 12:28:18 +02:00
Ottermandias
c3469a1687 Fix facewear advanced dyes, fix backup service not running in task, update libraries. 2025-09-28 23:55:44 +02:00
Ottermandias
0a9693daea
Update CodeService.cs 2025-09-15 20:29:13 +02:00
Actions User
414bd8bee7 [CI] Updating repo.json for 1.5.1.2 2025-08-28 16:52:43 +00:00
Ottermandias
8e1745d67a Once more with feeling 2025-08-28 18:47:57 +02:00
Actions User
889f01a724 [CI] Updating repo.json for 1.5.1.1 2025-08-26 09:58:08 +00:00
Ottermandias
6e62905fa7 Fix staging incompatibility with CS. 2025-08-26 11:55:00 +02:00
Actions User
654787fa0d [CI] Updating repo.json for 1.5.1.0 2025-08-25 08:45:28 +00:00
Ottermandias
835ba23935 1.5.1.0 2025-08-25 10:43:14 +02:00
Ottermandias
389a8781d6 Update library. 2025-08-25 10:39:38 +02:00
Actions User
3eabe591df [CI] Updating repo.json for testing_1.5.0.9 2025-08-24 13:59:02 +00:00
Ottermandias
487d3b9399 Update PCP Service. 2025-08-24 15:49:29 +02:00
Actions User
4d4e4669dd [CI] Updating repo.json for testing_1.5.0.8 2025-08-22 18:34:49 +00:00
27 changed files with 180 additions and 56 deletions

@ -1 +1 @@
Subproject commit 54c1944dc7db704733b4788520e494761bb0b58e Subproject commit 59a7ab5fa9941eb754757b62e4cb189e455e9514

View file

@ -1,13 +1,13 @@
using Glamourer.Api.Enums; using Glamourer.Api.Enums;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.State; using Glamourer.State;
using OtterGui;
using OtterGui.Extensions; using OtterGui.Extensions;
using OtterGui.Log; using OtterGui.Log;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
using Penumbra.String; using Penumbra.String;
namespace Glamourer.Api; namespace Glamourer.Api;
@ -15,14 +15,23 @@ namespace Glamourer.Api;
public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [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)) if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
yield break; yield break;
foreach (var state in stateManager.Values.Where(state if (worldId == WorldId.AnyWorld.Id)
=> state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)) {
yield return state; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]

View file

@ -6,7 +6,7 @@ namespace Glamourer.Api;
public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService
{ {
public const int CurrentApiVersionMajor = 1; public const int CurrentApiVersionMajor = 1;
public const int CurrentApiVersionMinor = 6; public const int CurrentApiVersionMinor = 7;
public (int Major, int Minor) ApiVersion public (int Major, int Minor) ApiVersion
=> (CurrentApiVersionMajor, CurrentApiVersionMinor); => (CurrentApiVersionMajor, CurrentApiVersionMinor);

View file

@ -54,6 +54,7 @@ public sealed class IpcProviders : IDisposable, IApiService
IpcSubscribers.RevertStateName.Provider(pi, api.State), IpcSubscribers.RevertStateName.Provider(pi, api.State),
IpcSubscribers.UnlockState.Provider(pi, api.State), IpcSubscribers.UnlockState.Provider(pi, api.State),
IpcSubscribers.UnlockStateName.Provider(pi, api.State), IpcSubscribers.UnlockStateName.Provider(pi, api.State),
IpcSubscribers.DeletePlayerState.Provider(pi, api.State),
IpcSubscribers.UnlockAll.Provider(pi, api.State), IpcSubscribers.UnlockAll.Provider(pi, api.State),
IpcSubscribers.RevertToAutomation.Provider(pi, api.State), IpcSubscribers.RevertToAutomation.Provider(pi, api.State),
IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), IpcSubscribers.RevertToAutomationName.Provider(pi, api.State),

View file

@ -8,6 +8,7 @@ using Glamourer.State;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
using StateChanged = Glamourer.Events.StateChanged; using StateChanged = Glamourer.Events.StateChanged;
namespace Glamourer.Api; namespace Glamourer.Api;
@ -17,7 +18,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
private readonly ApiHelpers _helpers; private readonly ApiHelpers _helpers;
private readonly StateManager _stateManager; private readonly StateManager _stateManager;
private readonly DesignConverter _converter; private readonly DesignConverter _converter;
private readonly Configuration _config;
private readonly AutoDesignApplier _autoDesigns; private readonly AutoDesignApplier _autoDesigns;
private readonly ActorObjectManager _objects; private readonly ActorObjectManager _objects;
private readonly StateChanged _stateChanged; private readonly StateChanged _stateChanged;
@ -27,7 +27,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
public StateApi(ApiHelpers helpers, public StateApi(ApiHelpers helpers,
StateManager stateManager, StateManager stateManager,
DesignConverter converter, DesignConverter converter,
Configuration config,
AutoDesignApplier autoDesigns, AutoDesignApplier autoDesigns,
ActorObjectManager objects, ActorObjectManager objects,
StateChanged stateChanged, StateChanged stateChanged,
@ -37,7 +36,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
_helpers = helpers; _helpers = helpers;
_stateManager = stateManager; _stateManager = stateManager;
_converter = converter; _converter = converter;
_config = config;
_autoDesigns = autoDesigns; _autoDesigns = autoDesigns;
_objects = objects; _objects = objects;
_stateChanged = stateChanged; _stateChanged = stateChanged;
@ -202,6 +200,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
return ApiHelpers.Return(GlamourerApiEc.Success, args); 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) public int UnlockAll(uint key)
=> _stateManager.Values.Count(state => state.Unlock(key)); => _stateManager.Values.Count(state => state.Unlock(key));

View file

@ -100,7 +100,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
public new JObject JsonSerialize() public new JObject JsonSerialize()
{ {
var ret = new JObject() var ret = new JObject
{ {
["FileVersion"] = FileVersion, ["FileVersion"] = FileVersion,
["Identifier"] = Identifier, ["Identifier"] = Identifier,
@ -131,12 +131,17 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
var ret = new JArray(); var ret = new JArray();
foreach (var (mod, settings) in AssociatedMods) foreach (var (mod, settings) in AssociatedMods)
{ {
var obj = new JObject() var obj = new JObject
{ {
["Name"] = mod.Name, ["Name"] = mod.Name,
["Directory"] = mod.DirectoryName, ["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) if (settings.Enabled)
{ {
obj["Priority"] = settings.Priority; obj["Priority"] = settings.Priority;

View file

@ -6,12 +6,13 @@ using Glamourer.GameData;
using Glamourer.Interop.Material; using Glamourer.Interop.Material;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using OtterGui.Extensions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui.Extensions;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public sealed class DesignManager : DesignEditor 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.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
var idx = design.Tags.IndexOf(tag); var idx = design.Tags.AsEnumerable().IndexOf(tag);
SaveService.QueueSave(design); SaveService.QueueSave(design);
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx)); DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx));
@ -261,7 +262,7 @@ public sealed class DesignManager : DesignEditor
SaveService.QueueSave(design); SaveService.QueueSave(design);
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, 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> /// <summary> Add an associated mod to a design. </summary>
@ -556,7 +557,7 @@ public sealed class DesignManager : DesignEditor
try try
{ {
File.Move(SaveService.FileNames.MigrationDesignFile, 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."); Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file.");
} }
catch (Exception ex) catch (Exception ex)

View file

@ -1,4 +1,4 @@
<Project Sdk="Dalamud.NET.Sdk/13.0.0"> <Project Sdk="Dalamud.NET.Sdk/13.1.0">
<PropertyGroup> <PropertyGroup>
<RootNamespace>Glamourer</RootNamespace> <RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer</AssemblyName> <AssemblyName>Glamourer</AssemblyName>

View file

@ -44,6 +44,7 @@ public class GlamourerChangelog
Add1_3_8_0(Changelog); Add1_3_8_0(Changelog);
Add1_4_0_0(Changelog); Add1_4_0_0(Changelog);
Add1_5_0_0(Changelog); Add1_5_0_0(Changelog);
Add1_5_1_0(Changelog);
} }
private (int, ChangeLogDisplayType) ConfigData() 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) private static void Add1_5_0_0(Changelog log)
=> log.NextVersion("Version 1.5.0.0") => 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.") .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.")

View file

@ -22,11 +22,12 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
private readonly CustomizeService _customize; private readonly CustomizeService _customize;
private readonly GPoseService _gpose; 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 public IEnumerable<KeyValuePair<object, EquipItem>> LastItems
=> EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand).Zip(_lastItems) => EquipSlotExtensions.EqdpSlots.Cast<object>().Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)
.Select(p => new KeyValuePair<EquipSlot, EquipItem>(p.First, p.Second)); .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 ChangedItemType LastType { get; private set; } = ChangedItemType.None;
public uint LastId { get; private set; } public uint LastId { get; private set; }
@ -72,6 +73,21 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
if (!Player()) if (!Player())
return; 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 slot = item.Type.ToSlot();
var last = _lastItems[slot.ToIndex()]; var last = _lastItems[slot.ToIndex()];
switch (slot) switch (slot)
@ -109,6 +125,27 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
public void ApplyItem(ActorState state, EquipItem item) 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 slot = item.Type.ToSlot();
var last = _lastItems[slot.ToIndex()]; var last = _lastItems[slot.ToIndex()];
switch (slot) switch (slot)
@ -265,7 +302,22 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
{ {
var oldItem = state.ModelData.Item(slot); var oldItem = state.ModelData.Item(slot);
if (oldItem.Id != item.Id) 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;
} }
} }

View file

@ -89,7 +89,13 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
ImGui.Separator(); ImGui.Separator();
foreach (var (slot, item) in _penumbraTooltip.LastItems) 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"); ImGuiUtil.DrawTableColumn(item.Valid ? item.Name : "None");
ImGui.TableNextColumn(); ImGui.TableNextColumn();
} }

View file

@ -71,6 +71,8 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect
private void DrawApplyAllButton() private void DrawApplyAllButton()
{ {
var (id, name) = penumbra.CurrentCollection; var (id, name) = penumbra.CurrentCollection;
if (config.Ephemeral.IncognitoMode)
name = id.ShortGuid();
if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll", if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll",
new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty)) new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty))
ApplyAll(); ApplyAll();

View file

@ -490,7 +490,7 @@ public class MultiDesignPanel(
foreach (var leaf in selector.SelectedPaths.OfType<DesignFileSystem.Leaf>()) 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) if (index >= 0)
_removeDesigns.Add((leaf.Value, index)); _removeDesigns.Add((leaf.Value, index));
else else

View file

@ -62,7 +62,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
var drawData = type switch var drawData = type switch
{ {
MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId), MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, (HumanSlot)slotId),
_ => GetTempSlot((Weapon*)characterBase), _ => GetTempSlot((Weapon*)characterBase),
}; };
var mode = PrepareColorSet.GetMode(material); var mode = PrepareColorSet.GetMode(material);
@ -192,13 +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> /// <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) if (human->ChangedEquipData is null)
return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); return slotId.ToSpecificEnum() switch
{
EquipSlot slot => ((Model)human).GetArmor(slot).ToWeapon(0),
BonusItemFlag bonus => ((Model)human).GetBonus(bonus).ToWeapon(0),
_ => default,
};
var item = (ChangedEquipData*)human->ChangedEquipData + slotId; if (!slotId.ToSlotIndex(out var index))
return ((CharacterArmor*)item)->ToWeapon(0); 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> /// <summary>

View file

@ -1,6 +1,5 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Lumina.Data.Files;
using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Files.MaterialStructs;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture;
@ -69,9 +68,9 @@ public static unsafe class MaterialService
return null; return null;
var material = (MaterialResourceHandle*) model.AsCharacterBase->MaterialsSpan[index].Value; 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 null;
return (ColorTable.Table*)material->ColorTable; return (ColorTable.Table*)material->DataSet;
} }
} }

View file

@ -69,13 +69,13 @@ public sealed unsafe class PrepareColorSet
public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds,
out ColorTable.Table table) out ColorTable.Table table)
{ {
if (material->ColorTable == null) if (material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable)
{ {
table = default; table = default;
return false; return false;
} }
var newTable = *(ColorTable.Table*)material->ColorTable; var newTable = *(ColorTable.Table*)material->DataSet;
if (GetDyeTable(material, out var dyeTable)) if (GetDyeTable(material, out var dyeTable))
{ {
if (stainIds.Stain1.Id != 0) if (stainIds.Stain1.Id != 0)

View file

@ -51,7 +51,7 @@ public class PenumbraService : IDisposable
private readonly EventSubscriber<nint, Guid, nint> _createdCharacterBase; private readonly EventSubscriber<nint, Guid, nint> _createdCharacterBase;
private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _modSettingChanged; private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _modSettingChanged;
private readonly EventSubscriber<JObject, string, Guid> _pcpParsed; private readonly EventSubscriber<JObject, string, Guid> _pcpParsed;
private readonly EventSubscriber<JObject, ushort> _pcpCreated; private readonly EventSubscriber<JObject, ushort, string> _pcpCreated;
private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier;
private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections;
@ -140,7 +140,7 @@ public class PenumbraService : IDisposable
remove => _modSettingChanged.Event -= value; remove => _modSettingChanged.Event -= value;
} }
public event Action<JObject, ushort> PcpCreated public event Action<JObject, ushort, string> PcpCreated
{ {
add => _pcpCreated.Event += value; add => _pcpCreated.Event += value;
remove => _pcpCreated.Event -= value; remove => _pcpCreated.Event -= value;

View file

@ -1,9 +1,10 @@
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Log; using OtterGui.Log;
using OtterGui.Services;
namespace Glamourer.Services; namespace Glamourer.Services;
public class BackupService public class BackupService : IAsyncService
{ {
private readonly Logger _logger; private readonly Logger _logger;
private readonly DirectoryInfo _configDirectory; private readonly DirectoryInfo _configDirectory;
@ -14,7 +15,7 @@ public class BackupService
_logger = logger; _logger = logger;
_fileNames = GlamourerFiles(fileNames); _fileNames = GlamourerFiles(fileNames);
_configDirectory = new DirectoryInfo(fileNames.ConfigDirectory); _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> /// <summary> Create a permanent backup with a given name for migrations. </summary>
@ -40,4 +41,9 @@ public class BackupService
return list; return list;
} }
public Task Awaiter { get; }
public bool Finished
=> Awaiter.IsCompletedSuccessfully;
} }

View file

@ -50,7 +50,8 @@ public class CodeService
| CodeFlag.OopsMiqote | CodeFlag.OopsMiqote
| CodeFlag.OopsRoegadyn | CodeFlag.OopsRoegadyn
| CodeFlag.OopsAuRa | CodeFlag.OopsAuRa
| CodeFlag.OopsHrothgar; | CodeFlag.OopsHrothgar
| CodeFlag.OopsViera;
public const CodeFlag FullCodes = CodeFlag.Face | CodeFlag.Manderville | CodeFlag.Smiles; 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), _ => (false, 0, string.Empty, string.Empty, string.Empty),
}; };
} }

View file

@ -96,7 +96,7 @@ public class PcpService : IRequiredService
} }
} }
private void OnPcpCreation(JObject jObj, ushort index) private void OnPcpCreation(JObject jObj, ushort index, string path)
{ {
Glamourer.Log.Debug("[PCPService] Adding Glamourer data to PCP file."); Glamourer.Log.Debug("[PCPService] Adding Glamourer data to PCP file.");
var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject); var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject);
@ -110,7 +110,7 @@ public class PcpService : IRequiredService
} }
var design = _designConverter.Convert(state, ApplicationRules.All); var design = _designConverter.Convert(state, ApplicationRules.All);
jObj["Glamourer"] = new JObject() jObj["Glamourer"] = new JObject
{ {
["Version"] = 1, ["Version"] = 1,
["Design"] = design.JsonSerialize(), ["Design"] = design.JsonSerialize(),

View file

@ -385,7 +385,7 @@ public class StateApplier(
var actors = ChangeMetaState(state, MetaIndex.Wetness, true); var actors = ChangeMetaState(state, MetaIndex.Wetness, true);
if (redraw) if (redraw)
{ {
if (withLock) if (withLock && actors.Valid)
state.TempLock(); state.TempLock();
ForceRedraw(actors); ForceRedraw(actors);
} }

View file

@ -4,9 +4,9 @@
"net9.0-windows7.0": { "net9.0-windows7.0": {
"DalamudPackager": { "DalamudPackager": {
"type": "Direct", "type": "Direct",
"requested": "[13.0.0, )", "requested": "[13.1.0, )",
"resolved": "13.0.0", "resolved": "13.1.0",
"contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w==" "contentHash": "XdoNhJGyFby5M/sdcRhnc5xTop9PHy+H50PTWpzLhJugjB19EDBiHD/AsiDF66RETM+0qKUdJBZrNuebn7qswQ=="
}, },
"DotNet.ReproducibleBuilds": { "DotNet.ReproducibleBuilds": {
"type": "Direct", "type": "Direct",

@ -1 +1 @@
Subproject commit 4a9b71a93e76aa5eed818542288329e34ec0dd89 Subproject commit 1459e2b8f5e1687f659836709e23571235d4206c

@ -1 +1 @@
Subproject commit 0a970295b2398683b1e49c46fd613541e2486210 Subproject commit c23ee05c1e9fa103eaa52e6aa7e855ef568ee669

@ -1 +1 @@
Subproject commit 15e7c8eb41867e6bbd3fe6a8885404df087bc7e7 Subproject commit d889f9ef918514a46049725052d378b441915b00

@ -1 +1 @@
Subproject commit 878acce46e286867d6ef1f8ecedb390f7bac34fd Subproject commit c8611a0c546b6b2ec29214ab319fc2c38fe74793

View file

@ -17,8 +17,8 @@
"Character" "Character"
], ],
"InternalName": "Glamourer", "InternalName": "Glamourer",
"AssemblyVersion": "1.5.0.7", "AssemblyVersion": "1.5.1.5",
"TestingAssemblyVersion": "1.5.0.7", "TestingAssemblyVersion": "1.5.1.5",
"RepoUrl": "https://github.com/Ottermandias/Glamourer", "RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any", "ApplicableVersion": "any",
"DalamudApiLevel": 13, "DalamudApiLevel": 13,
@ -27,9 +27,9 @@
"IsTestingExclusive": "False", "IsTestingExclusive": "False",
"DownloadCount": 1, "DownloadCount": 1,
"LastUpdate": 1618608322, "LastUpdate": 1618608322,
"DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/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.0.7/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.0.7/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" "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png"
} }
] ]