mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Merge branch 'Crests'
This commit is contained in:
commit
69dce5790b
44 changed files with 1668 additions and 1032 deletions
8
.gitmodules
vendored
8
.gitmodules
vendored
|
|
@ -1,16 +1,16 @@
|
|||
[submodule "OtterGui"]
|
||||
path = OtterGui
|
||||
url = git@github.com:Ottermandias/OtterGui.git
|
||||
url = https://github.com/Ottermandias/OtterGui.git
|
||||
branch = main
|
||||
[submodule "Penumbra.GameData"]
|
||||
path = Penumbra.GameData
|
||||
url = git@github.com:Ottermandias/Penumbra.GameData.git
|
||||
url = https://github.com/Ottermandias/Penumbra.GameData.git
|
||||
branch = main
|
||||
[submodule "Penumbra.String"]
|
||||
path = Penumbra.String
|
||||
url = git@github.com:Ottermandias/Penumbra.String.git
|
||||
url = https://github.com/Ottermandias/Penumbra.String.git
|
||||
branch = main
|
||||
[submodule "Penumbra.Api"]
|
||||
path = Penumbra.Api
|
||||
url = git@github.com:Ottermandias/Penumbra.Api.git
|
||||
url = https://github.com/Ottermandias/Penumbra.Api.git
|
||||
branch = main
|
||||
|
|
|
|||
102
Glamourer.GameData/Structs/CrestFlag.cs
Normal file
102
Glamourer.GameData/Structs/CrestFlag.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
[Flags]
|
||||
public enum CrestFlag : ushort
|
||||
{
|
||||
OffHand = 0x0001,
|
||||
Head = 0x0002,
|
||||
Body = 0x0004,
|
||||
Hands = 0x0008,
|
||||
Legs = 0x0010,
|
||||
Feet = 0x0020,
|
||||
Ears = 0x0040,
|
||||
Neck = 0x0080,
|
||||
Wrists = 0x0100,
|
||||
RFinger = 0x0200,
|
||||
LFinger = 0x0400,
|
||||
MainHand = 0x0800,
|
||||
}
|
||||
|
||||
public enum CrestType : byte
|
||||
{
|
||||
None,
|
||||
Human,
|
||||
Mainhand,
|
||||
Offhand,
|
||||
};
|
||||
|
||||
public static class CrestExtensions
|
||||
{
|
||||
public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Mainhand << 1) - 1);
|
||||
public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand;
|
||||
|
||||
public static readonly IReadOnlyList<CrestFlag> AllRelevantSet = Enum.GetValues<CrestFlag>().Where(f => AllRelevant.HasFlag(f)).ToArray();
|
||||
|
||||
public static int ToInternalIndex(this CrestFlag flag)
|
||||
=> flag switch
|
||||
{
|
||||
CrestFlag.Head => 0,
|
||||
CrestFlag.Body => 1,
|
||||
CrestFlag.OffHand => 2,
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
public static (CrestType Type, byte Index) ToIndex(this CrestFlag flag)
|
||||
=> flag switch
|
||||
{
|
||||
CrestFlag.Head => (CrestType.Human, 0),
|
||||
CrestFlag.Body => (CrestType.Human, 1),
|
||||
CrestFlag.Hands => (CrestType.Human, 2),
|
||||
CrestFlag.Legs => (CrestType.Human, 3),
|
||||
CrestFlag.Feet => (CrestType.Human, 4),
|
||||
CrestFlag.Ears => (CrestType.None, 0),
|
||||
CrestFlag.Neck => (CrestType.None, 0),
|
||||
CrestFlag.Wrists => (CrestType.None, 0),
|
||||
CrestFlag.RFinger => (CrestType.None, 0),
|
||||
CrestFlag.LFinger => (CrestType.None, 0),
|
||||
CrestFlag.MainHand => (CrestType.None, 0),
|
||||
CrestFlag.OffHand => (CrestType.Offhand, 0),
|
||||
_ => (CrestType.None, 0),
|
||||
};
|
||||
|
||||
public static CrestFlag ToCrestFlag(this EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => CrestFlag.MainHand,
|
||||
EquipSlot.OffHand => CrestFlag.OffHand,
|
||||
EquipSlot.Head => CrestFlag.Head,
|
||||
EquipSlot.Body => CrestFlag.Body,
|
||||
EquipSlot.Hands => CrestFlag.Hands,
|
||||
EquipSlot.Legs => CrestFlag.Legs,
|
||||
EquipSlot.Feet => CrestFlag.Feet,
|
||||
EquipSlot.Ears => CrestFlag.Ears,
|
||||
EquipSlot.Neck => CrestFlag.Neck,
|
||||
EquipSlot.Wrists => CrestFlag.Wrists,
|
||||
EquipSlot.RFinger => CrestFlag.RFinger,
|
||||
EquipSlot.LFinger => CrestFlag.LFinger,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public static string ToLabel(this CrestFlag flag)
|
||||
=> flag switch
|
||||
{
|
||||
CrestFlag.Head => "Head",
|
||||
CrestFlag.Body => "Chest",
|
||||
CrestFlag.Hands => "Gauntlets",
|
||||
CrestFlag.Legs => "Pants",
|
||||
CrestFlag.Feet => "Boot",
|
||||
CrestFlag.Ears => "Earrings",
|
||||
CrestFlag.Neck => "Necklace",
|
||||
CrestFlag.Wrists => "Bracelet",
|
||||
CrestFlag.RFinger => "Right Ring",
|
||||
CrestFlag.LFinger => "Left Ring",
|
||||
CrestFlag.MainHand => "Weapon",
|
||||
CrestFlag.OffHand => "Shield",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
|
@ -15,18 +15,19 @@ public class AutoDesign
|
|||
[Flags]
|
||||
public enum Type : byte
|
||||
{
|
||||
Armor = 0x01,
|
||||
Customizations = 0x02,
|
||||
Weapons = 0x04,
|
||||
Stains = 0x08,
|
||||
Accessories = 0x10,
|
||||
Armor = 0x01,
|
||||
Customizations = 0x02,
|
||||
Weapons = 0x04,
|
||||
GearCustomization = 0x08,
|
||||
Accessories = 0x10,
|
||||
|
||||
All = Armor | Accessories | Customizations | Weapons | Stains,
|
||||
All = Armor | Accessories | Customizations | Weapons | GearCustomization,
|
||||
}
|
||||
|
||||
public Design? Design;
|
||||
public JobGroup Jobs;
|
||||
public Type ApplicationType;
|
||||
public short GearsetIndex = -1;
|
||||
|
||||
public string Name(bool incognito)
|
||||
=> Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text;
|
||||
|
|
@ -43,10 +44,22 @@ public class AutoDesign
|
|||
Design = Design,
|
||||
ApplicationType = ApplicationType,
|
||||
Jobs = Jobs,
|
||||
GearsetIndex = GearsetIndex,
|
||||
};
|
||||
|
||||
public unsafe bool IsActive(Actor actor)
|
||||
=> actor.IsCharacter && Jobs.Fits(actor.AsCharacter->CharacterData.ClassJob);
|
||||
{
|
||||
if (!actor.IsCharacter)
|
||||
return false;
|
||||
|
||||
var ret = true;
|
||||
if (GearsetIndex < 0)
|
||||
ret &= Jobs.Fits(actor.AsCharacter->CharacterData.ClassJob);
|
||||
else
|
||||
ret &= AutoDesignApplier.CheckGearset(GearsetIndex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public JObject Serialize()
|
||||
=> new()
|
||||
|
|
@ -58,25 +71,29 @@ public class AutoDesign
|
|||
|
||||
private JObject CreateConditionObject()
|
||||
{
|
||||
var ret = new JObject();
|
||||
if (Jobs.Id != 0)
|
||||
ret["JobGroup"] = Jobs.Id;
|
||||
var ret = new JObject
|
||||
{
|
||||
["Gearset"] = GearsetIndex,
|
||||
["JobGroup"] = Jobs.Id,
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public (EquipFlag Equip, CustomizeFlag Customize, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat()
|
||||
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat()
|
||||
{
|
||||
var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0)
|
||||
| (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0)
|
||||
| (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0)
|
||||
| (ApplicationType.HasFlag(Type.Stains) ? StainFlags : 0);
|
||||
| (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0);
|
||||
var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0;
|
||||
var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0;
|
||||
|
||||
if (Revert)
|
||||
return (equipFlags, customizeFlags, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor),
|
||||
return (equipFlags, customizeFlags, crestFlag, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor),
|
||||
ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations));
|
||||
|
||||
return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize,
|
||||
return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest,
|
||||
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(),
|
||||
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(),
|
||||
ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
|
|
@ -26,6 +27,7 @@ public class AutoDesignApplier : IDisposable
|
|||
private readonly AutoDesignManager _manager;
|
||||
private readonly StateManager _state;
|
||||
private readonly JobService _jobs;
|
||||
private readonly EquippedGearset _equippedGearset;
|
||||
private readonly ActorService _actors;
|
||||
private readonly CustomizationService _customizations;
|
||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
|
|
@ -49,7 +51,8 @@ public class AutoDesignApplier : IDisposable
|
|||
|
||||
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs,
|
||||
CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks,
|
||||
AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState)
|
||||
AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState,
|
||||
EquippedGearset equippedGearset)
|
||||
{
|
||||
_config = config;
|
||||
_manager = manager;
|
||||
|
|
@ -64,15 +67,18 @@ public class AutoDesignApplier : IDisposable
|
|||
_weapons = weapons;
|
||||
_humans = humans;
|
||||
_clientState = clientState;
|
||||
_equippedGearset = equippedGearset;
|
||||
_jobs.JobChanged += OnJobChange;
|
||||
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier);
|
||||
_weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier);
|
||||
_equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_weapons.Unsubscribe(OnWeaponLoading);
|
||||
_event.Unsubscribe(OnAutomationChange);
|
||||
_equippedGearset.Unsubscribe(OnEquippedGearset);
|
||||
_jobs.JobChanged -= OnJobChange;
|
||||
}
|
||||
|
||||
|
|
@ -262,6 +268,7 @@ public class AutoDesignApplier : IDisposable
|
|||
{
|
||||
EquipFlag totalEquipFlags = 0;
|
||||
CustomizeFlag totalCustomizeFlags = 0;
|
||||
CrestFlag totalCrestFlags = 0;
|
||||
byte totalMetaFlags = 0;
|
||||
if (set.BaseState == AutoDesignSet.Base.Game)
|
||||
_state.ResetStateFixed(state);
|
||||
|
|
@ -285,10 +292,11 @@ public class AutoDesignApplier : IDisposable
|
|||
if (!data.IsHuman)
|
||||
continue;
|
||||
|
||||
var (equipFlags, customizeFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat();
|
||||
var (equipFlags, customizeFlags, crestFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat();
|
||||
ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source);
|
||||
ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source);
|
||||
ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange);
|
||||
ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source);
|
||||
}
|
||||
|
||||
if (totalCustomizeFlags != 0)
|
||||
|
|
@ -318,6 +326,24 @@ public class AutoDesignApplier : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void ReduceCrests(ActorState state, in DesignData design, CrestFlag crestFlags, ref CrestFlag totalCrestFlags, bool respectManual,
|
||||
StateChanged.Source source)
|
||||
{
|
||||
crestFlags &= ~totalCrestFlags;
|
||||
if (crestFlags == 0)
|
||||
return;
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
if (!crestFlags.HasFlag(slot))
|
||||
continue;
|
||||
|
||||
if (!respectManual || state[slot] is not StateChanged.Source.Manual)
|
||||
_state.ChangeCrest(state, slot, design.Crest(slot), source);
|
||||
totalCrestFlags |= slot;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual,
|
||||
StateChanged.Source source, bool fromJobChange)
|
||||
{
|
||||
|
|
@ -496,4 +522,38 @@ public class AutoDesignApplier : IDisposable
|
|||
totalMetaFlags |= 0x08;
|
||||
}
|
||||
}
|
||||
|
||||
internal static int NewGearsetId = -1;
|
||||
|
||||
private void OnEquippedGearset(string name, int id, int prior, byte _, byte job)
|
||||
{
|
||||
if (!_config.EnableAutoDesigns)
|
||||
return;
|
||||
|
||||
var (player, data) = _objects.PlayerData;
|
||||
if (!player.IsValid)
|
||||
return;
|
||||
|
||||
if (!GetPlayerSet(player, out var set) || !_state.TryGetValue(player, out var state))
|
||||
return;
|
||||
|
||||
var respectManual = prior == id;
|
||||
NewGearsetId = id;
|
||||
Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob);
|
||||
NewGearsetId = -1;
|
||||
foreach (var actor in data.Objects)
|
||||
_state.ReapplyState(actor);
|
||||
}
|
||||
|
||||
public static unsafe bool CheckGearset(short check)
|
||||
{
|
||||
if (NewGearsetId != -1)
|
||||
return check == NewGearsetId;
|
||||
|
||||
var module = RaptureGearsetModule.Instance();
|
||||
if (module == null)
|
||||
return false;
|
||||
|
||||
return check == module->CurrentGearsetIndex;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -306,6 +306,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
return;
|
||||
|
||||
var design = set.Designs[which];
|
||||
|
||||
if (design.Jobs.Id == jobs.Id)
|
||||
return;
|
||||
|
||||
|
|
@ -316,6 +317,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
_event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, jobs));
|
||||
}
|
||||
|
||||
public void ChangeGearsetCondition(AutoDesignSet set, int which, short index)
|
||||
{
|
||||
if (which >= set.Designs.Count || which < 0)
|
||||
return;
|
||||
|
||||
var design = set.Designs[which];
|
||||
if (design.GearsetIndex == index)
|
||||
return;
|
||||
|
||||
var old = design.GearsetIndex;
|
||||
design.GearsetIndex = index;
|
||||
Save();
|
||||
Glamourer.Log.Debug($"Changed gearset condition from {old} to {index} for associated design {which + 1} in design set.");
|
||||
_event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index));
|
||||
}
|
||||
|
||||
public void ChangeApplicationType(AutoDesignSet set, int which, AutoDesign.Type type)
|
||||
{
|
||||
if (which >= set.Designs.Count || which < 0)
|
||||
|
|
@ -338,10 +355,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
using var j = new JsonTextWriter(writer)
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
using var j = new JsonTextWriter(writer);
|
||||
j.Formatting = Formatting.Indented;
|
||||
Serialize().WriteTo(j);
|
||||
}
|
||||
|
||||
|
|
@ -456,13 +471,16 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
{
|
||||
if (designIdentifier.Length == 0)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: No design specified.", NotificationType.Warning);
|
||||
Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: No design specified.",
|
||||
NotificationType.Warning);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Guid.TryParse(designIdentifier, out var guid))
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: {designIdentifier} is not a valid GUID.", NotificationType.Warning);
|
||||
Glamourer.Messager.NotificationMessage(
|
||||
$"Error parsing automatically applied design for set {setName}: {designIdentifier} is not a valid GUID.",
|
||||
NotificationType.Warning);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -471,7 +489,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
if (design == null)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(
|
||||
$"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.", NotificationType.Warning);
|
||||
$"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.",
|
||||
NotificationType.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -483,24 +502,31 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
Design = design,
|
||||
ApplicationType = applicationType & AutoDesign.Type.All,
|
||||
};
|
||||
return ParseConditions(setName, jObj, ret) ? ret : null;
|
||||
}
|
||||
|
||||
private bool ParseConditions(string setName, JObject jObj, AutoDesign ret)
|
||||
{
|
||||
var conditions = jObj["Conditions"];
|
||||
if (conditions == null)
|
||||
return ret;
|
||||
return true;
|
||||
|
||||
var jobs = conditions["JobGroup"]?.ToObject<int>() ?? -1;
|
||||
if (jobs >= 0)
|
||||
{
|
||||
if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup))
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.", NotificationType.Warning);
|
||||
return null;
|
||||
Glamourer.Messager.NotificationMessage(
|
||||
$"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.",
|
||||
NotificationType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret.Jobs = jobGroup;
|
||||
}
|
||||
|
||||
return ret;
|
||||
ret.GearsetIndex = conditions["Gearset"]?.ToObject<short>() ?? -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Save()
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -28,8 +26,8 @@ public sealed class Design : DesignBase, ISavable
|
|||
internal Design(Design other)
|
||||
: base(other)
|
||||
{
|
||||
Tags = Tags.ToArray();
|
||||
Description = Description;
|
||||
Tags = other.Tags.ToArray();
|
||||
Description = other.Description;
|
||||
AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods);
|
||||
}
|
||||
|
||||
|
|
@ -69,8 +67,7 @@ public sealed class Design : DesignBase, ISavable
|
|||
["Equipment"] = SerializeEquipment(),
|
||||
["Customize"] = SerializeCustomize(),
|
||||
["Mods"] = SerializeMods(),
|
||||
}
|
||||
;
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
|
|
@ -9,6 +7,8 @@ using OtterGui.Classes;
|
|||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
|
|
@ -39,7 +39,6 @@ public class DesignBase
|
|||
ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
||||
_designFlags = 0;
|
||||
CustomizationSet = SetCustomizationSet(customize);
|
||||
|
||||
}
|
||||
|
||||
internal DesignBase(DesignBase clone)
|
||||
|
|
@ -83,6 +82,7 @@ public class DesignBase
|
|||
=> _applyCustomize;
|
||||
|
||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
||||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
|
||||
|
||||
public bool SetCustomize(CustomizationService customizationService, Customize customize)
|
||||
|
|
@ -169,6 +169,9 @@ public class DesignBase
|
|||
public bool DoApplyCustomize(CustomizeIndex idx)
|
||||
=> ApplyCustomize.HasFlag(idx.ToFlag());
|
||||
|
||||
public bool DoApplyCrest(CrestFlag slot)
|
||||
=> ApplyCrest.HasFlag(slot);
|
||||
|
||||
internal bool SetApplyEquip(EquipSlot slot, bool value)
|
||||
{
|
||||
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag();
|
||||
|
|
@ -199,28 +202,42 @@ public class DesignBase
|
|||
return true;
|
||||
}
|
||||
|
||||
internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
||||
=> new(this, equipFlags, customizeFlags);
|
||||
internal bool SetApplyCrest(CrestFlag slot, bool value)
|
||||
{
|
||||
var newValue = value ? ApplyCrest | slot : ApplyCrest & ~slot;
|
||||
if (newValue == ApplyCrest)
|
||||
return false;
|
||||
|
||||
ApplyCrest = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
|
||||
=> new(this, equipFlags, customizeFlags, crestFlags);
|
||||
|
||||
internal readonly struct FlagRestrictionResetter : IDisposable
|
||||
{
|
||||
private readonly DesignBase _design;
|
||||
private readonly EquipFlag _oldEquipFlags;
|
||||
private readonly CustomizeFlag _oldCustomizeFlags;
|
||||
private readonly CrestFlag _oldCrestFlags;
|
||||
|
||||
public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
||||
public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
|
||||
{
|
||||
_design = d;
|
||||
_oldEquipFlags = d.ApplyEquip;
|
||||
_oldCustomizeFlags = d.ApplyCustomizeRaw;
|
||||
_oldCrestFlags = d.ApplyCrest;
|
||||
d.ApplyEquip &= equipFlags;
|
||||
d.ApplyCustomize &= customizeFlags;
|
||||
d.ApplyCrest &= crestFlags;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_design.ApplyEquip = _oldEquipFlags;
|
||||
_design.ApplyCustomize = _oldCustomizeFlags;
|
||||
_design.ApplyCrest = _oldCrestFlags;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,13 +263,15 @@ public class DesignBase
|
|||
|
||||
protected JObject SerializeEquipment()
|
||||
{
|
||||
static JObject Serialize(CustomItemId id, StainId stain, bool apply, bool applyStain)
|
||||
static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest)
|
||||
=> new()
|
||||
{
|
||||
["ItemId"] = id.Id,
|
||||
["Stain"] = stain.Id,
|
||||
["Crest"] = crest,
|
||||
["Apply"] = apply,
|
||||
["ApplyStain"] = applyStain,
|
||||
["ApplyCrest"] = applyCrest,
|
||||
};
|
||||
|
||||
var ret = new JObject();
|
||||
|
|
@ -260,9 +279,11 @@ public class DesignBase
|
|||
{
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
||||
{
|
||||
var item = _designData.Item(slot);
|
||||
var stain = _designData.Stain(slot);
|
||||
ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot));
|
||||
var item = _designData.Item(slot);
|
||||
var stain = _designData.Stain(slot);
|
||||
var crestSlot = slot.ToCrestFlag();
|
||||
var crest = _designData.Crest(crestSlot);
|
||||
ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot));
|
||||
}
|
||||
|
||||
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
|
||||
|
|
@ -345,13 +366,15 @@ public class DesignBase
|
|||
return;
|
||||
}
|
||||
|
||||
static (CustomItemId, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item)
|
||||
static (CustomItemId, StainId, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item)
|
||||
{
|
||||
var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot).Id;
|
||||
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
|
||||
var crest = item?["Crest"]?.ToObject<bool>() ?? false;
|
||||
var apply = item?["Apply"]?.ToObject<bool>() ?? false;
|
||||
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
|
||||
return (id, stain, apply, applyStain);
|
||||
var applyCrest = item?["ApplyCrest"]?.ToObject<bool>() ?? false;
|
||||
return (id, stain, crest, apply, applyStain, applyCrest);
|
||||
}
|
||||
|
||||
void PrintWarning(string msg)
|
||||
|
|
@ -362,21 +385,25 @@ public class DesignBase
|
|||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]);
|
||||
var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]);
|
||||
|
||||
PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown));
|
||||
PrintWarning(items.ValidateStain(stain, out stain, allowUnknown));
|
||||
var crestSlot = slot.ToCrestFlag();
|
||||
design._designData.SetItem(slot, item);
|
||||
design._designData.SetStain(slot, stain);
|
||||
design._designData.SetCrest(crestSlot, crest);
|
||||
design.SetApplyEquip(slot, apply);
|
||||
design.SetApplyStain(slot, applyStain);
|
||||
design.SetApplyCrest(crestSlot, applyCrest);
|
||||
}
|
||||
|
||||
{
|
||||
var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]);
|
||||
var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]);
|
||||
if (id == ItemManager.NothingId(EquipSlot.MainHand))
|
||||
id = items.DefaultSword.ItemId;
|
||||
var (idOff, stainOff, applyOff, applyStainOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]);
|
||||
var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) =
|
||||
ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]);
|
||||
if (id == ItemManager.NothingId(EquipSlot.OffHand))
|
||||
id = ItemManager.NothingId(FullEquipType.Shield);
|
||||
|
||||
|
|
@ -387,10 +414,14 @@ public class DesignBase
|
|||
design._designData.SetItem(EquipSlot.OffHand, off);
|
||||
design._designData.SetStain(EquipSlot.MainHand, stain);
|
||||
design._designData.SetStain(EquipSlot.OffHand, stainOff);
|
||||
design._designData.SetCrest(CrestFlag.MainHand, crest);
|
||||
design._designData.SetCrest(CrestFlag.OffHand, crestOff);
|
||||
design.SetApplyEquip(EquipSlot.MainHand, apply);
|
||||
design.SetApplyEquip(EquipSlot.OffHand, applyOff);
|
||||
design.SetApplyStain(EquipSlot.MainHand, applyStain);
|
||||
design.SetApplyStain(EquipSlot.OffHand, applyStainOff);
|
||||
design.SetApplyCrest(CrestFlag.MainHand, applyCrest);
|
||||
design.SetApplyCrest(CrestFlag.OffHand, applyCrestOff);
|
||||
}
|
||||
var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse);
|
||||
design.SetApplyHatVisible(metaValue.Enabled);
|
||||
|
|
|
|||
|
|
@ -13,22 +13,9 @@ using Penumbra.GameData.Enums;
|
|||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignConverter
|
||||
public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizationService _customize, HumanModelList _humans)
|
||||
{
|
||||
public const byte Version = 5;
|
||||
|
||||
private readonly ItemManager _items;
|
||||
private readonly DesignManager _designs;
|
||||
private readonly CustomizationService _customize;
|
||||
private readonly HumanModelList _humans;
|
||||
|
||||
public DesignConverter(ItemManager items, DesignManager designs, CustomizationService customize, HumanModelList humans)
|
||||
{
|
||||
_items = items;
|
||||
_designs = designs;
|
||||
_customize = customize;
|
||||
_humans = humans;
|
||||
}
|
||||
public const byte Version = 6;
|
||||
|
||||
public JObject ShareJObject(DesignBase design)
|
||||
=> design.JsonSerialize();
|
||||
|
|
@ -36,32 +23,33 @@ public class DesignConverter
|
|||
public JObject ShareJObject(Design design)
|
||||
=> design.JsonSerialize();
|
||||
|
||||
public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
||||
public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
|
||||
{
|
||||
var design = Convert(state, equipFlags, customizeFlags);
|
||||
var design = Convert(state, equipFlags, customizeFlags, crestFlags);
|
||||
return ShareJObject(design);
|
||||
}
|
||||
|
||||
public string ShareBase64(Design design)
|
||||
=> ShareBackwardCompatible(ShareJObject(design), design);
|
||||
=> ShareBase64(ShareJObject(design));
|
||||
|
||||
public string ShareBase64(DesignBase design)
|
||||
=> ShareBackwardCompatible(ShareJObject(design), design);
|
||||
=> ShareBase64(ShareJObject(design));
|
||||
|
||||
public string ShareBase64(ActorState state)
|
||||
=> ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All);
|
||||
=> ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All);
|
||||
|
||||
public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
||||
public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
|
||||
{
|
||||
var design = Convert(state, equipFlags, customizeFlags);
|
||||
return ShareBackwardCompatible(ShareJObject(design), design);
|
||||
var design = Convert(state, equipFlags, customizeFlags, crestFlags);
|
||||
return ShareBase64(ShareJObject(design));
|
||||
}
|
||||
|
||||
public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
||||
public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
|
||||
{
|
||||
var design = _designs.CreateTemporary();
|
||||
design.ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
||||
design.ApplyCustomize = customizeFlags;
|
||||
design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
|
||||
design.ApplyCrest = crestFlags & CrestExtensions.All;
|
||||
design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head));
|
||||
design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head));
|
||||
design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
|
||||
|
|
@ -123,6 +111,7 @@ public class DesignConverter
|
|||
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
||||
break;
|
||||
}
|
||||
|
||||
default: throw new Exception($"Unknown Version {bytes[0]}.");
|
||||
}
|
||||
}
|
||||
|
|
@ -138,6 +127,7 @@ public class DesignConverter
|
|||
if (!equip)
|
||||
{
|
||||
ret.ApplyEquip = 0;
|
||||
ret.ApplyCrest = 0;
|
||||
ret.SetApplyHatVisible(false);
|
||||
ret.SetApplyWeaponVisible(false);
|
||||
ret.SetApplyVisorToggle(false);
|
||||
|
|
@ -146,23 +136,10 @@ public class DesignConverter
|
|||
return ret;
|
||||
}
|
||||
|
||||
private static string ShareBase64(JObject jObj)
|
||||
private static string ShareBase64(JObject jObject)
|
||||
{
|
||||
var json = jObj.ToString(Formatting.None);
|
||||
var json = jObject.ToString(Formatting.None);
|
||||
var compressed = json.Compress(Version);
|
||||
return System.Convert.ToBase64String(compressed);
|
||||
}
|
||||
|
||||
private static string ShareBackwardCompatible(JObject jObject, DesignBase design)
|
||||
{
|
||||
var oldBase64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw,
|
||||
design.DoApplyHatVisible(), design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected(), 1f);
|
||||
var oldBytes = System.Convert.FromBase64String(oldBase64);
|
||||
var json = jObject.ToString(Formatting.None);
|
||||
var compressed = json.Compress(Version);
|
||||
var bytes = new byte[oldBytes.Length + compressed.Length];
|
||||
oldBytes.CopyTo(bytes, 0);
|
||||
compressed.CopyTo(bytes, oldBytes.Length);
|
||||
return System.Convert.ToBase64String(bytes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -29,6 +30,7 @@ public unsafe struct DesignData
|
|||
private fixed byte _equipmentBytes[48];
|
||||
public Customize Customize = Customize.Default;
|
||||
public uint ModelId;
|
||||
public CrestFlag CrestVisibility;
|
||||
private WeaponType _secondaryMainhand;
|
||||
private WeaponType _secondaryOffhand;
|
||||
private FullEquipType _typeMainhand;
|
||||
|
|
@ -59,6 +61,10 @@ public unsafe struct DesignData
|
|||
return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3];
|
||||
}
|
||||
|
||||
public readonly bool Crest(CrestFlag slot)
|
||||
=> CrestVisibility.HasFlag(slot);
|
||||
|
||||
|
||||
public FullEquipType MainhandType
|
||||
=> _typeMainhand;
|
||||
|
||||
|
|
@ -173,6 +179,16 @@ public unsafe struct DesignData
|
|||
_ => false,
|
||||
};
|
||||
|
||||
public bool SetCrest(CrestFlag slot, bool visible)
|
||||
{
|
||||
var newValue = visible ? CrestVisibility | slot : CrestVisibility & ~slot;
|
||||
if (newValue == CrestVisibility)
|
||||
return false;
|
||||
|
||||
CrestVisibility = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
public readonly bool IsWet()
|
||||
=> (_states & 0x01) == 0x01;
|
||||
|
||||
|
|
@ -228,12 +244,15 @@ public unsafe struct DesignData
|
|||
{
|
||||
SetItem(slot, ItemManager.NothingItem(slot));
|
||||
SetStain(slot, 0);
|
||||
SetCrest(slot.ToCrestFlag(), false);
|
||||
}
|
||||
|
||||
SetItem(EquipSlot.MainHand, items.DefaultSword);
|
||||
SetStain(EquipSlot.MainHand, 0);
|
||||
SetCrest(CrestFlag.MainHand, false);
|
||||
SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield));
|
||||
SetStain(EquipSlot.OffHand, 0);
|
||||
SetCrest(CrestFlag.OffHand, false);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Glamourer.Events;
|
|||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
|
|
@ -345,7 +346,7 @@ public class DesignManager
|
|||
/// <summary> Change a non-weapon equipment piece. </summary>
|
||||
public void ChangeEquip(Design design, EquipSlot slot, EquipItem item)
|
||||
{
|
||||
if (!_items.IsItemValid(slot, item.ItemId, out item))
|
||||
if (!_items.IsItemValid(slot, item.Id, out item))
|
||||
return;
|
||||
|
||||
var old = design.DesignData.Item(slot);
|
||||
|
|
@ -446,6 +447,31 @@ public class DesignManager
|
|||
_event.Invoke(DesignChanged.Type.ApplyStain, design, slot);
|
||||
}
|
||||
|
||||
/// <summary> Change the crest visibility for any equipment piece. </summary>
|
||||
public void ChangeCrest(Design design, CrestFlag slot, bool crest)
|
||||
{
|
||||
var oldCrest = design.DesignData.Crest(slot);
|
||||
if (!design.GetDesignDataRef().SetCrest(slot, crest))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}.");
|
||||
_event.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot));
|
||||
}
|
||||
|
||||
/// <summary> Change whether to apply a specific crest visibility. </summary>
|
||||
public void ChangeApplyCrest(Design design, CrestFlag slot, bool value)
|
||||
{
|
||||
if (!design.SetApplyCrest(slot, value))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}.");
|
||||
_event.Invoke(DesignChanged.Type.ApplyCrest, design, slot);
|
||||
}
|
||||
|
||||
/// <summary> Change the bool value of one of the meta flags. </summary>
|
||||
public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value)
|
||||
{
|
||||
|
|
@ -515,6 +541,12 @@ public class DesignManager
|
|||
if (other.DoApplyStain(slot))
|
||||
ChangeStain(design, slot, other.DesignData.Stain(slot));
|
||||
}
|
||||
|
||||
foreach (var slot in Enum.GetValues<CrestFlag>())
|
||||
{
|
||||
if (other.DoApplyCrest(slot))
|
||||
ChangeCrest(design, slot, other.DesignData.Crest(slot));
|
||||
}
|
||||
}
|
||||
|
||||
if (other.DoApplyEquip(EquipSlot.MainHand))
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@ public class EphemeralConfig : ISavable
|
|||
|
||||
public void Save(StreamWriter writer)
|
||||
{
|
||||
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
|
||||
using var jWriter = new JsonTextWriter(writer);
|
||||
jWriter.Formatting = Formatting.Indented;
|
||||
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
|
||||
serializer.Serialize(jWriter, this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
|
|||
/// <summary> An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||
Stain,
|
||||
|
||||
/// <summary> An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
|
||||
Crest,
|
||||
|
||||
/// <summary> An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. </summary>
|
||||
ApplyCustomize,
|
||||
|
||||
|
|
@ -71,6 +74,9 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
|
|||
/// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
||||
ApplyStain,
|
||||
|
||||
/// <summary> An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
||||
ApplyCrest,
|
||||
|
||||
/// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary>
|
||||
WriteProtection,
|
||||
|
||||
|
|
|
|||
30
Glamourer/Events/EquippedGearset.cs
Normal file
30
Glamourer/Events/EquippedGearset.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using OtterGui.Classes;
|
||||
|
||||
namespace Glamourer.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the player equips a gear set.
|
||||
/// <list type="number">
|
||||
/// <item>Parameter is the name of the gear set. </item>
|
||||
/// <item>Parameter is the id of the gear set. </item>
|
||||
/// <item>Parameter is the id of the prior gear set. </item>
|
||||
/// <item>Parameter is the id of the associated glamour. </item>
|
||||
/// <item>Parameter is the job id of the associated job. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class EquippedGearset : EventWrapper<Action<string, int, int, byte, byte>, EquippedGearset.Priority>
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Automation.AutoDesignApplier.OnEquippedGearset"/>
|
||||
AutoDesignApplier = 0,
|
||||
}
|
||||
|
||||
public EquippedGearset()
|
||||
: base(nameof(EquippedGearset))
|
||||
{ }
|
||||
|
||||
public void Invoke(string name, int id, int lastId, byte glamour, byte jobId)
|
||||
=> Invoke(this, name, id, lastId, glamour, jobId);
|
||||
}
|
||||
|
|
@ -37,6 +37,9 @@ public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateC
|
|||
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||
Stain,
|
||||
|
||||
/// <summary> A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
|
||||
Crest,
|
||||
|
||||
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
|
||||
Design,
|
||||
|
||||
|
|
|
|||
|
|
@ -194,14 +194,14 @@ public partial class CustomizationDrawer
|
|||
{
|
||||
switch (UiHelpers.DrawMetaToggle(_currentIndex.ToDefaultName(), tmp, _currentApply, out var newValue, out var newApply, _locked))
|
||||
{
|
||||
case DataChange.Item:
|
||||
case (true, false):
|
||||
_customize.Set(idx, newValue ? CustomizeValue.Max : CustomizeValue.Zero);
|
||||
Changed |= _currentFlag;
|
||||
break;
|
||||
case DataChange.ApplyItem:
|
||||
case (false, true):
|
||||
ChangeApply = newApply ? ChangeApply | _currentFlag : ChangeApply & ~_currentFlag;
|
||||
break;
|
||||
case DataChange.Item | DataChange.ApplyItem:
|
||||
case (true, true):
|
||||
ChangeApply = newApply ? ChangeApply | _currentFlag : ChangeApply & ~_currentFlag;
|
||||
_customize.Set(idx, newValue ? CustomizeValue.Max : CustomizeValue.Zero);
|
||||
Changed |= _currentFlag;
|
||||
|
|
|
|||
|
|
@ -14,18 +14,16 @@ using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
|||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
public partial class CustomizationDrawer : IDisposable
|
||||
public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizationService _service, CodeService _codes, Configuration _config)
|
||||
: IDisposable
|
||||
{
|
||||
private readonly CodeService _codes;
|
||||
private readonly Configuration _config;
|
||||
|
||||
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
|
||||
private readonly IDalamudTextureWrap? _legacyTattoo;
|
||||
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
|
||||
private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(pi);
|
||||
|
||||
private Exception? _terminate;
|
||||
|
||||
private Customize _customize;
|
||||
private CustomizationSet _set = null!;
|
||||
private Customize _customize = Customize.Default;
|
||||
private CustomizationSet _set = null!;
|
||||
|
||||
public Customize Customize
|
||||
=> _customize;
|
||||
|
|
@ -46,21 +44,8 @@ public partial class CustomizationDrawer : IDisposable
|
|||
private float _raceSelectorWidth;
|
||||
private bool _withApply;
|
||||
|
||||
private readonly CustomizationService _service;
|
||||
|
||||
public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service, CodeService codes, Configuration config)
|
||||
{
|
||||
_service = service;
|
||||
_codes = codes;
|
||||
_config = config;
|
||||
_legacyTattoo = GetLegacyTattooIcon(pi);
|
||||
_customize = Customize.Default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_legacyTattoo?.Dispose();
|
||||
}
|
||||
=> _legacyTattoo?.Dispose();
|
||||
|
||||
public bool Draw(Customize current, bool locked, bool lockedRedraw)
|
||||
{
|
||||
|
|
@ -125,12 +110,6 @@ public partial class CustomizationDrawer : IDisposable
|
|||
Changed |= _currentFlag;
|
||||
}
|
||||
|
||||
public bool DrawWetnessState(bool currentValue, out bool newValue, bool locked)
|
||||
=> UiHelpers.DrawCheckbox("Force Wetness", "Force the character to be wet or not.", currentValue, out newValue, locked);
|
||||
|
||||
public DataChange DrawWetnessState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked)
|
||||
=> UiHelpers.DrawMetaToggle("Force Wetness", currentValue, currentApply, out newValue, out newApply, locked);
|
||||
|
||||
private bool DrawInternal()
|
||||
{
|
||||
using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _spacing);
|
||||
|
|
@ -199,13 +178,13 @@ public partial class CustomizationDrawer : IDisposable
|
|||
|
||||
private void UpdateSizes()
|
||||
{
|
||||
_spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X };
|
||||
_iconSize = new Vector2(ImGui.GetTextLineHeight() * 2 + _spacing.Y + 2 * ImGui.GetStyle().FramePadding.Y);
|
||||
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
||||
_inputIntSize = 2 * _framedIconSize.X + 1 * _spacing.X;
|
||||
_spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X };
|
||||
_iconSize = new Vector2(ImGui.GetTextLineHeight() * 2 + _spacing.Y + 2 * ImGui.GetStyle().FramePadding.Y);
|
||||
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
||||
_inputIntSize = 2 * _framedIconSize.X + 1 * _spacing.X;
|
||||
_inputIntSizeNoButtons = _inputIntSize - 2 * _spacing.X - 2 * ImGui.GetFrameHeight();
|
||||
_comboSelectorSize = 4 * _framedIconSize.X + 3 * _spacing.X;
|
||||
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
||||
_comboSelectorSize = 4 * _framedIconSize.X + 3 * _spacing.X;
|
||||
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
||||
}
|
||||
|
||||
private static IDalamudTextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi)
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ public class DesignQuickBar : Window, IDisposable
|
|||
{
|
||||
private ImGuiWindowFlags GetFlags
|
||||
=> _config.Ephemeral.LockDesignQuickBar
|
||||
? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove
|
||||
: ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing;
|
||||
? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove
|
||||
: ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing;
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly DesignCombo _designCombo;
|
||||
|
|
@ -37,7 +37,7 @@ public class DesignQuickBar : Window, IDisposable
|
|||
|
||||
public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState,
|
||||
ObjectManager objects, AutoDesignApplier autoDesignApplier)
|
||||
: base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration)
|
||||
: base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking)
|
||||
{
|
||||
_config = config;
|
||||
_designCombo = designCombo;
|
||||
|
|
@ -159,8 +159,8 @@ public class DesignQuickBar : Window, IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize);
|
||||
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest);
|
||||
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
|
||||
}
|
||||
|
||||
|
|
|
|||
49
Glamourer/Gui/Equipment/EquipDrawData.cs
Normal file
49
Glamourer/Gui/Equipment/EquipDrawData.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Equipment;
|
||||
|
||||
public ref struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
||||
{
|
||||
public readonly EquipSlot Slot = slot;
|
||||
public bool Locked;
|
||||
public bool DisplayApplication;
|
||||
|
||||
public Action<EquipItem> ItemSetter = null!;
|
||||
public Action<StainId> StainSetter = null!;
|
||||
public Action<bool> ApplySetter = null!;
|
||||
public Action<bool> ApplyStainSetter = null!;
|
||||
public EquipItem CurrentItem = designData.Item(slot);
|
||||
public StainId CurrentStain = designData.Stain(slot);
|
||||
public bool CurrentApply;
|
||||
public bool CurrentApplyStain;
|
||||
|
||||
public readonly Gender CurrentGender = designData.Customize.Gender;
|
||||
public readonly Race CurrentRace = designData.Customize.Race;
|
||||
|
||||
public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot)
|
||||
=> new(slot, design.DesignData)
|
||||
{
|
||||
ItemSetter = i => manager.ChangeEquip(design, slot, i),
|
||||
StainSetter = i => manager.ChangeStain(design, slot, i),
|
||||
ApplySetter = b => manager.ChangeApplyEquip(design, slot, b),
|
||||
ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b),
|
||||
CurrentApply = design.DoApplyEquip(slot),
|
||||
CurrentApplyStain = design.DoApplyStain(slot),
|
||||
Locked = design.WriteProtected(),
|
||||
DisplayApplication = true,
|
||||
};
|
||||
|
||||
public static EquipDrawData FromState(StateManager manager, ActorState state, EquipSlot slot)
|
||||
=> new(slot, state.ModelData)
|
||||
{
|
||||
ItemSetter = i => manager.ChangeItem(state, slot, i, StateChanged.Source.Manual),
|
||||
StainSetter = i => manager.ChangeStain(state, slot, i, StateChanged.Source.Manual),
|
||||
Locked = state.IsLocked,
|
||||
DisplayApplication = false,
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Services;
|
||||
|
|
@ -22,6 +23,9 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
|||
private ItemId _currentItem;
|
||||
private float _innerWidth;
|
||||
|
||||
public SetId CustomSetId { get; private set; }
|
||||
public Variant CustomVariant { get; private set; }
|
||||
|
||||
public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites)
|
||||
: base(() => GetItems(favorites, items, slot), log)
|
||||
{
|
||||
|
|
@ -50,8 +54,9 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
|||
|
||||
public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth)
|
||||
{
|
||||
_innerWidth = innerWidth;
|
||||
_currentItem = previewIdx;
|
||||
_innerWidth = innerWidth;
|
||||
_currentItem = previewIdx;
|
||||
CustomVariant = 0;
|
||||
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
|
||||
}
|
||||
|
||||
|
|
@ -117,4 +122,18 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
|||
enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot));
|
||||
return enumerable.OrderByDescending(favorites.Contains).ThenBy(i => i.Name).Prepend(nothing).ToList();
|
||||
}
|
||||
|
||||
protected override void OnClosePopup()
|
||||
{
|
||||
// If holding control while the popup closes, try to parse the input as a full pair of set id and variant, and set a custom item for that.
|
||||
if (!ImGui.GetIO().KeyCtrl)
|
||||
return;
|
||||
|
||||
var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (split.Length != 2 || !ushort.TryParse(split[0], out var setId) || !byte.TryParse(split[1], out var variant))
|
||||
return;
|
||||
|
||||
CustomSetId = setId;
|
||||
CustomVariant = variant;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Glamourer.Interop;
|
|||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -24,21 +25,11 @@ using Penumbra.GameData.Enums;
|
|||
|
||||
namespace Glamourer.Gui.Tabs.ActorTab;
|
||||
|
||||
public class ActorPanel
|
||||
public class ActorPanel(ActorSelector _selector, StateManager _stateManager, CustomizationDrawer _customizationDrawer,
|
||||
EquipmentDrawer _equipmentDrawer, IdentifierService _identification, AutoDesignApplier _autoDesignApplier,
|
||||
Configuration _config, DesignConverter _converter, ObjectManager _objects, DesignManager _designManager, ImportService _importService,
|
||||
ICondition _conditions)
|
||||
{
|
||||
private readonly ActorSelector _selector;
|
||||
private readonly StateManager _stateManager;
|
||||
private readonly CustomizationDrawer _customizationDrawer;
|
||||
private readonly EquipmentDrawer _equipmentDrawer;
|
||||
private readonly IdentifierService _identification;
|
||||
private readonly AutoDesignApplier _autoDesignApplier;
|
||||
private readonly Configuration _config;
|
||||
private readonly DesignConverter _converter;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly DesignManager _designManager;
|
||||
private readonly ImportService _importService;
|
||||
private readonly ICondition _conditions;
|
||||
|
||||
private ActorIdentifier _identifier;
|
||||
private string _actorName = string.Empty;
|
||||
private Actor _actor = Actor.Null;
|
||||
|
|
@ -46,29 +37,10 @@ public class ActorPanel
|
|||
private ActorState? _state;
|
||||
private bool _lockedRedraw;
|
||||
|
||||
public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer,
|
||||
EquipmentDrawer equipmentDrawer, IdentifierService identification, AutoDesignApplier autoDesignApplier,
|
||||
Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, ImportService importService,
|
||||
ICondition conditions)
|
||||
{
|
||||
_selector = selector;
|
||||
_stateManager = stateManager;
|
||||
_customizationDrawer = customizationDrawer;
|
||||
_equipmentDrawer = equipmentDrawer;
|
||||
_identification = identification;
|
||||
_autoDesignApplier = autoDesignApplier;
|
||||
_config = config;
|
||||
_converter = converter;
|
||||
_objects = objects;
|
||||
_designManager = designManager;
|
||||
_importService = importService;
|
||||
_conditions = conditions;
|
||||
}
|
||||
|
||||
private CustomizeFlag CustomizeApplicationFlags
|
||||
=> _lockedRedraw ? CustomizeFlagExtensions.AllRelevant & ~CustomizeFlagExtensions.RedrawRequired : CustomizeFlagExtensions.AllRelevant;
|
||||
|
||||
public unsafe void Draw()
|
||||
public void Draw()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
(_identifier, _data) = _selector.Selection;
|
||||
|
|
@ -161,8 +133,7 @@ public class ActorPanel
|
|||
if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw))
|
||||
_stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual);
|
||||
|
||||
if (_customizationDrawer.DrawWetnessState(_state!.ModelData.IsWet(), out var newWetness, _state.IsLocked))
|
||||
_stateManager.ChangeWetness(_state, newWetness, StateChanged.Source.Manual);
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.Wetness, _stateManager, _state));
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
}
|
||||
|
||||
|
|
@ -176,63 +147,42 @@ public class ActorPanel
|
|||
var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _state!.IsLocked);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var changes = _equipmentDrawer.DrawEquip(slot, _state!.ModelData, out var newArmor, out var newStain, null, out _, out _,
|
||||
_state.IsLocked);
|
||||
var data = EquipDrawData.FromState(_stateManager, _state!, slot);
|
||||
_equipmentDrawer.DrawEquip(data);
|
||||
if (usedAllStain)
|
||||
{
|
||||
changes |= DataChange.Stain;
|
||||
newStain = newAllStain;
|
||||
}
|
||||
|
||||
switch (changes)
|
||||
{
|
||||
case DataChange.Item:
|
||||
_stateManager.ChangeItem(_state, slot, newArmor, StateChanged.Source.Manual);
|
||||
break;
|
||||
case DataChange.Stain:
|
||||
_stateManager.ChangeStain(_state, slot, newStain, StateChanged.Source.Manual);
|
||||
break;
|
||||
case DataChange.Item | DataChange.Stain:
|
||||
_stateManager.ChangeEquip(_state, slot, newArmor, newStain, StateChanged.Source.Manual);
|
||||
break;
|
||||
}
|
||||
_stateManager.ChangeStain(_state, slot, newAllStain, StateChanged.Source.Manual);
|
||||
}
|
||||
|
||||
var weaponChanges = _equipmentDrawer.DrawWeapons(_state!.ModelData, out var newMainhand, out var newOffhand, out var newMainhandStain,
|
||||
out var newOffhandStain, null, GameMain.IsInGPose(), out _, out _, out _, out _, _state.IsLocked);
|
||||
if (usedAllStain)
|
||||
var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand);
|
||||
var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand);
|
||||
_equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose());
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
DrawEquipmentMetaToggles();
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
}
|
||||
|
||||
private void DrawEquipmentMetaToggles()
|
||||
{
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
weaponChanges |= DataChange.Stain | DataChange.Stain2;
|
||||
newMainhandStain = newAllStain;
|
||||
newOffhandStain = newAllStain;
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!));
|
||||
}
|
||||
|
||||
if (weaponChanges.HasFlag(DataChange.Item))
|
||||
if (weaponChanges.HasFlag(DataChange.Stain))
|
||||
_stateManager.ChangeEquip(_state, EquipSlot.MainHand, newMainhand, newMainhandStain, StateChanged.Source.Manual);
|
||||
else
|
||||
_stateManager.ChangeItem(_state, EquipSlot.MainHand, newMainhand, StateChanged.Source.Manual);
|
||||
else if (weaponChanges.HasFlag(DataChange.Stain))
|
||||
_stateManager.ChangeStain(_state, EquipSlot.MainHand, newMainhandStain, StateChanged.Source.Manual);
|
||||
|
||||
if (weaponChanges.HasFlag(DataChange.Item2))
|
||||
if (weaponChanges.HasFlag(DataChange.Stain2))
|
||||
_stateManager.ChangeEquip(_state, EquipSlot.OffHand, newOffhand, newOffhandStain, StateChanged.Source.Manual);
|
||||
else
|
||||
_stateManager.ChangeItem(_state, EquipSlot.OffHand, newOffhand, StateChanged.Source.Manual);
|
||||
else if (weaponChanges.HasFlag(DataChange.Stain2))
|
||||
_stateManager.ChangeStain(_state, EquipSlot.OffHand, newOffhandStain, StateChanged.Source.Manual);
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
if (EquipmentDrawer.DrawHatState(_state!.ModelData.IsHatVisible(), out var newHatState, _state!.IsLocked))
|
||||
_stateManager.ChangeHatState(_state, newHatState, StateChanged.Source.Manual);
|
||||
ImGui.SameLine();
|
||||
if (EquipmentDrawer.DrawVisorState(_state!.ModelData.IsVisorToggled(), out var newVisorState, _state!.IsLocked))
|
||||
_stateManager.ChangeVisorState(_state, newVisorState, StateChanged.Source.Manual);
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (EquipmentDrawer.DrawWeaponState(_state!.ModelData.IsWeaponVisible(), out var newWeaponState, _state!.IsLocked))
|
||||
_stateManager.ChangeWeaponState(_state, newWeaponState, StateChanged.Source.Manual);
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMonsterPanel()
|
||||
|
|
@ -346,8 +296,8 @@ public class ActorPanel
|
|||
{
|
||||
ImGui.OpenPopup("Save as Design");
|
||||
_newName = _state!.Identifier.ToName();
|
||||
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags();
|
||||
_newDesign = _converter.Convert(_state, applyGear, applyCustomize);
|
||||
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
|
||||
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest);
|
||||
}
|
||||
|
||||
private void SaveDesignDrawPopup()
|
||||
|
|
@ -382,8 +332,8 @@ public class ActorPanel
|
|||
{
|
||||
try
|
||||
{
|
||||
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags();
|
||||
var text = _converter.ShareBase64(_state!, applyGear, applyCustomize);
|
||||
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
|
||||
var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest);
|
||||
ImGui.SetClipboardText(text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -422,9 +372,9 @@ public class ActorPanel
|
|||
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
|
||||
return;
|
||||
|
||||
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags();
|
||||
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
|
||||
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
||||
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize), state,
|
||||
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state,
|
||||
StateChanged.Source.Manual);
|
||||
}
|
||||
|
||||
|
|
@ -440,9 +390,9 @@ public class ActorPanel
|
|||
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
|
||||
return;
|
||||
|
||||
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags();
|
||||
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
|
||||
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
||||
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize), state,
|
||||
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state,
|
||||
StateChanged.Source.Manual);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
|
@ -29,17 +30,18 @@ public class SetPanel
|
|||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
private readonly CustomizationService _customizations;
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly RevertDesignCombo _designCombo;
|
||||
private readonly JobGroupCombo _jobGroupCombo;
|
||||
private readonly IdentifierDrawer _identifierDrawer;
|
||||
private readonly Configuration _config;
|
||||
private readonly RevertDesignCombo _designCombo;
|
||||
private readonly JobGroupCombo _jobGroupCombo;
|
||||
private readonly IdentifierDrawer _identifierDrawer;
|
||||
|
||||
private string? _tempName;
|
||||
private int _dragIndex = -1;
|
||||
|
||||
private Action? _endAction;
|
||||
|
||||
public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, RevertDesignCombo designCombo,
|
||||
public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks,
|
||||
RevertDesignCombo designCombo,
|
||||
CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config)
|
||||
{
|
||||
_selector = selector;
|
||||
|
|
@ -216,11 +218,11 @@ public class SetPanel
|
|||
ImGui.TableNextColumn();
|
||||
DrawApplicationTypeBoxes(Selection, design, idx, singleRow);
|
||||
ImGui.TableNextColumn();
|
||||
_jobGroupCombo.Draw(Selection, design, idx);
|
||||
DrawConditions(design, idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
_jobGroupCombo.Draw(Selection, design, idx);
|
||||
DrawConditions(design, idx);
|
||||
ImGui.TableNextColumn();
|
||||
DrawApplicationTypeBoxes(Selection, design, idx, singleRow);
|
||||
}
|
||||
|
|
@ -244,6 +246,38 @@ public class SetPanel
|
|||
_endAction = null;
|
||||
}
|
||||
|
||||
private int _tmpGearset = int.MaxValue;
|
||||
|
||||
private void DrawConditions(AutoDesign design, int idx)
|
||||
{
|
||||
var usingGearset = design.GearsetIndex >= 0;
|
||||
if (ImGui.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset"))
|
||||
{
|
||||
usingGearset = !usingGearset;
|
||||
_manager.ChangeGearsetCondition(Selection, idx, (short)(usingGearset ? 0 : -1));
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip("Click to switch between Job and Gearset restrictions.");
|
||||
|
||||
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
if (usingGearset)
|
||||
{
|
||||
var set = 1 + (_tmpGearset == int.MaxValue ? design.GearsetIndex : _tmpGearset);
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.InputInt("##whichGearset", ref set, 0, 0))
|
||||
_tmpGearset = Math.Clamp(set, 1, 100);
|
||||
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||
{
|
||||
_manager.ChangeGearsetCondition(Selection, idx, (short)(_tmpGearset - 1));
|
||||
_tmpGearset = int.MaxValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_jobGroupCombo.Draw(Selection, design, idx);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWarnings(AutoDesign design, int idx)
|
||||
{
|
||||
if (design.Revert)
|
||||
|
|
@ -252,7 +286,7 @@ public class SetPanel
|
|||
var size = new Vector2(ImGui.GetFrameHeight());
|
||||
size.X += ImGuiHelpers.GlobalScale;
|
||||
|
||||
var (equipFlags, customizeFlags, _, _, _, _) = design.ApplyWhat();
|
||||
var (equipFlags, customizeFlags, _, _, _, _, _) = design.ApplyWhat();
|
||||
var sb = new StringBuilder();
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
|
||||
{
|
||||
|
|
@ -423,7 +457,7 @@ public class SetPanel
|
|||
"Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."),
|
||||
(AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."),
|
||||
(AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."),
|
||||
(AutoDesign.Type.Stains, "Apply all dye changes that are enabled in this design."),
|
||||
(AutoDesign.Type.GearCustomization, "Apply all dye and crest changes that are enabled in this design."),
|
||||
(AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
|
|
@ -21,6 +22,7 @@ using Glamourer.Interop.Penumbra;
|
|||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using Glamourer.Utility;
|
||||
using ImGuiNET;
|
||||
|
|
@ -43,6 +45,7 @@ public unsafe class DebugTab : ITab
|
|||
private readonly VisorService _visorService;
|
||||
private readonly ChangeCustomizeService _changeCustomizeService;
|
||||
private readonly UpdateSlotService _updateSlotService;
|
||||
private readonly CrestService _crestService;
|
||||
private readonly WeaponService _weaponService;
|
||||
private readonly MetaService _metaService;
|
||||
private readonly InventoryService _inventoryService;
|
||||
|
|
@ -50,7 +53,7 @@ public unsafe class DebugTab : ITab
|
|||
private readonly ObjectManager _objectManager;
|
||||
private readonly GlamourerIpc _ipc;
|
||||
private readonly CodeService _code;
|
||||
private readonly ImportService _importService;
|
||||
private readonly ImportService _importService;
|
||||
|
||||
private readonly ItemManager _items;
|
||||
private readonly ActorService _actors;
|
||||
|
|
@ -82,7 +85,7 @@ public unsafe class DebugTab : ITab
|
|||
PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface,
|
||||
AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks,
|
||||
ItemUnlockManager itemUnlocks, DesignConverter designConverter, ImportService importService, InventoryService inventoryService,
|
||||
HumanModelList humans, FunModule funModule)
|
||||
HumanModelList humans, FunModule funModule, CrestService crestService)
|
||||
{
|
||||
_changeCustomizeService = changeCustomizeService;
|
||||
_visorService = visorService;
|
||||
|
|
@ -107,10 +110,11 @@ public unsafe class DebugTab : ITab
|
|||
_customizeUnlocks = customizeUnlocks;
|
||||
_itemUnlocks = itemUnlocks;
|
||||
_designConverter = designConverter;
|
||||
_importService = importService;
|
||||
_importService = importService;
|
||||
_inventoryService = inventoryService;
|
||||
_humans = humans;
|
||||
_funModule = funModule;
|
||||
_crestService = crestService;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -200,6 +204,7 @@ public unsafe class DebugTab : ITab
|
|||
DrawWetness(actor, model);
|
||||
DrawEquip(actor, model);
|
||||
DrawCustomize(actor, model);
|
||||
DrawCrests(actor, model);
|
||||
}
|
||||
|
||||
private string _objectFilter = string.Empty;
|
||||
|
|
@ -477,6 +482,32 @@ public unsafe class DebugTab : ITab
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawCrests(Actor actor, Model model)
|
||||
{
|
||||
using var id = ImRaii.PushId("Crests");
|
||||
CrestFlag whichToggle = 0;
|
||||
CrestFlag totalModelFlags = 0;
|
||||
foreach (var crestFlag in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
id.Push((int)crestFlag);
|
||||
var modelCrest = CrestService.GetModelCrest(actor, crestFlag);
|
||||
if (modelCrest)
|
||||
totalModelFlags |= crestFlag;
|
||||
ImGuiUtil.DrawTableColumn($"{crestFlag.ToLabel()} Crest");
|
||||
ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetCrest(crestFlag).ToString() : "No Character");
|
||||
ImGuiUtil.DrawTableColumn(modelCrest.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (model.IsHuman && ImGui.SmallButton("Toggle"))
|
||||
whichToggle = crestFlag;
|
||||
|
||||
id.Pop();
|
||||
}
|
||||
|
||||
if (whichToggle != 0)
|
||||
_crestService.UpdateCrests(actor, totalModelFlags ^ whichToggle);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Penumbra
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
|||
case DesignChanged.Type.ApplyCustomize:
|
||||
case DesignChanged.Type.ApplyEquip:
|
||||
case DesignChanged.Type.ApplyStain:
|
||||
case DesignChanged.Type.ApplyCrest:
|
||||
case DesignChanged.Type.Customize:
|
||||
case DesignChanged.Type.Equip:
|
||||
case DesignChanged.Type.ChangedColor:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Xml.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
|
|
@ -95,45 +94,15 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selector.Selected!.WriteProtected());
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var changes = _equipmentDrawer.DrawEquip(slot, _selector.Selected!.DesignData, out var newArmor, out var newStain,
|
||||
_selector.Selected.ApplyEquip, out var newApply, out var newApplyStain, _selector.Selected!.WriteProtected());
|
||||
if (changes.HasFlag(DataChange.Item))
|
||||
_manager.ChangeEquip(_selector.Selected, slot, newArmor);
|
||||
if (changes.HasFlag(DataChange.Stain))
|
||||
_manager.ChangeStain(_selector.Selected, slot, newStain);
|
||||
else if (usedAllStain)
|
||||
var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot);
|
||||
_equipmentDrawer.DrawEquip(data);
|
||||
if (usedAllStain)
|
||||
_manager.ChangeStain(_selector.Selected, slot, newAllStain);
|
||||
if (changes.HasFlag(DataChange.ApplyItem))
|
||||
_manager.ChangeApplyEquip(_selector.Selected, slot, newApply);
|
||||
if (changes.HasFlag(DataChange.ApplyStain))
|
||||
_manager.ChangeApplyStain(_selector.Selected, slot, newApplyStain);
|
||||
}
|
||||
|
||||
var weaponChanges = _equipmentDrawer.DrawWeapons(_selector.Selected!.DesignData, out var newMainhand, out var newOffhand,
|
||||
out var newMainhandStain, out var newOffhandStain, _selector.Selected.ApplyEquip, true, out var applyMain, out var applyMainStain,
|
||||
out var applyOff, out var applyOffStain, _selector.Selected!.WriteProtected());
|
||||
|
||||
if (weaponChanges.HasFlag(DataChange.Item))
|
||||
_manager.ChangeWeapon(_selector.Selected, EquipSlot.MainHand, newMainhand);
|
||||
if (weaponChanges.HasFlag(DataChange.Stain))
|
||||
_manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newMainhandStain);
|
||||
else if (usedAllStain)
|
||||
_manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newAllStain);
|
||||
if (weaponChanges.HasFlag(DataChange.ApplyItem))
|
||||
_manager.ChangeApplyEquip(_selector.Selected, EquipSlot.MainHand, applyMain);
|
||||
if (weaponChanges.HasFlag(DataChange.ApplyStain))
|
||||
_manager.ChangeApplyStain(_selector.Selected, EquipSlot.MainHand, applyMainStain);
|
||||
if (weaponChanges.HasFlag(DataChange.Item2))
|
||||
_manager.ChangeWeapon(_selector.Selected, EquipSlot.OffHand, newOffhand);
|
||||
if (weaponChanges.HasFlag(DataChange.Stain2))
|
||||
_manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newOffhandStain);
|
||||
else if (usedAllStain)
|
||||
_manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newAllStain);
|
||||
if (weaponChanges.HasFlag(DataChange.ApplyItem2))
|
||||
_manager.ChangeApplyEquip(_selector.Selected, EquipSlot.OffHand, applyOff);
|
||||
if (weaponChanges.HasFlag(DataChange.ApplyStain2))
|
||||
_manager.ChangeApplyStain(_selector.Selected, EquipSlot.OffHand, applyOffStain);
|
||||
|
||||
var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand);
|
||||
var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.OffHand);
|
||||
_equipmentDrawer.DrawWeapons(mainhand, offhand, true);
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
DrawEquipmentMetaToggles();
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
|
|
@ -141,22 +110,25 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
|
||||
private void DrawEquipmentMetaToggles()
|
||||
{
|
||||
var hatChanges = EquipmentDrawer.DrawHatState(_selector.Selected!.DesignData.IsHatVisible(),
|
||||
_selector.Selected.DoApplyHatVisible(),
|
||||
out var newHatState, out var newHatApply, _selector.Selected.WriteProtected());
|
||||
ApplyChanges(ActorState.MetaIndex.HatState, hatChanges, newHatState, newHatApply);
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var visorChanges = EquipmentDrawer.DrawVisorState(_selector.Selected!.DesignData.IsVisorToggled(),
|
||||
_selector.Selected.DoApplyVisorToggle(),
|
||||
out var newVisorState, out var newVisorApply, _selector.Selected.WriteProtected());
|
||||
ApplyChanges(ActorState.MetaIndex.VisorState, visorChanges, newVisorState, newVisorApply);
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var weaponChanges = EquipmentDrawer.DrawWeaponState(_selector.Selected!.DesignData.IsWeaponVisible(),
|
||||
_selector.Selected.DoApplyWeaponVisible(),
|
||||
out var newWeaponState, out var newWeaponApply, _selector.Selected.WriteProtected());
|
||||
ApplyChanges(ActorState.MetaIndex.WeaponState, weaponChanges, newWeaponState, newWeaponApply);
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCustomize()
|
||||
|
|
@ -178,12 +150,53 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
_manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]);
|
||||
}
|
||||
|
||||
var wetnessChanges = _customizationDrawer.DrawWetnessState(_selector.Selected!.DesignData.IsWet(),
|
||||
_selector.Selected!.DoApplyWetness(), out var newWetnessState, out var newWetnessApply, _selector.Selected!.WriteProtected());
|
||||
ApplyChanges(ActorState.MetaIndex.Wetness, wetnessChanges, newWetnessState, newWetnessApply);
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.Wetness, _manager, _selector.Selected!));
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
}
|
||||
|
||||
private void DrawCustomizeApplication()
|
||||
{
|
||||
var set = _selector.Selected!.CustomizationSet;
|
||||
var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender;
|
||||
var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1;
|
||||
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
|
||||
{
|
||||
var newFlags = flags == 3;
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, newFlags);
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, newFlags);
|
||||
foreach (var index in CustomizationExtensions.AllBasic)
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags);
|
||||
}
|
||||
|
||||
var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan);
|
||||
if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan))
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan);
|
||||
|
||||
var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender);
|
||||
if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender))
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender);
|
||||
|
||||
|
||||
foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable))
|
||||
{
|
||||
var apply = _selector.Selected!.DoApplyCustomize(index);
|
||||
if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply))
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, index, apply);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCrestApplication()
|
||||
{
|
||||
var flags = (uint)_selector.Selected!.ApplyCrest;
|
||||
var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant);
|
||||
foreach (var flag in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(flag);
|
||||
if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange)
|
||||
_manager.ChangeApplyCrest(_selector.Selected!, flag, apply);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawApplicationRules()
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("Application Rules"))
|
||||
|
|
@ -191,33 +204,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
var set = _selector.Selected!.CustomizationSet;
|
||||
var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender;
|
||||
var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1;
|
||||
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
|
||||
{
|
||||
var newFlags = flags == 3;
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, newFlags);
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, newFlags);
|
||||
foreach (var index in CustomizationExtensions.AllBasic)
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags);
|
||||
}
|
||||
|
||||
var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan);
|
||||
if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan))
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan);
|
||||
|
||||
var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender);
|
||||
if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender))
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender);
|
||||
|
||||
|
||||
foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable))
|
||||
{
|
||||
var apply = _selector.Selected!.DoApplyCustomize(index);
|
||||
if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply))
|
||||
_manager.ChangeApplyCustomize(_selector.Selected!, index, apply);
|
||||
}
|
||||
DrawCustomizeApplication();
|
||||
ImGui.NewLine();
|
||||
DrawCrestApplication();
|
||||
}
|
||||
|
||||
ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2);
|
||||
|
|
@ -306,12 +295,14 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
_manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]);
|
||||
foreach (var idx in CustomizationExtensions.AllBasic)
|
||||
_manager.ChangeCustomize(_selector.Selected!, idx, dat.Customize[idx]);
|
||||
Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false);
|
||||
Glamourer.Messager.NotificationMessage(
|
||||
$"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false);
|
||||
}
|
||||
else if (_importService.CreateCharaTarget(out var designBase, out var name))
|
||||
{
|
||||
_manager.ApplyDesign(_selector.Selected!, designBase);
|
||||
Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", NotificationType.Success, false);
|
||||
Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.",
|
||||
NotificationType.Success, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -397,8 +388,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
|
||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
{
|
||||
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize);
|
||||
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest);
|
||||
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
|
||||
}
|
||||
}
|
||||
|
|
@ -416,8 +407,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
|
||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
{
|
||||
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize);
|
||||
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest);
|
||||
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
|
||||
}
|
||||
}
|
||||
|
|
@ -441,23 +432,6 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
_fileDialog.Draw();
|
||||
}
|
||||
|
||||
private void ApplyChanges(ActorState.MetaIndex index, DataChange change, bool value, bool apply)
|
||||
{
|
||||
switch (change)
|
||||
{
|
||||
case DataChange.Item:
|
||||
_manager.ChangeMeta(_selector.Selected!, index, value);
|
||||
break;
|
||||
case DataChange.ApplyItem:
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, index, apply);
|
||||
break;
|
||||
case DataChange.Item | DataChange.ApplyItem:
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, index, apply);
|
||||
_manager.ChangeMeta(_selector.Selected!, index, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe string GetUserPath()
|
||||
=> Framework.Instance()->UserPath;
|
||||
}
|
||||
|
|
|
|||
104
Glamourer/Gui/ToggleDrawData.cs
Normal file
104
Glamourer/Gui/ToggleDrawData.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
public ref struct ToggleDrawData
|
||||
{
|
||||
public bool Locked;
|
||||
public bool DisplayApplication;
|
||||
|
||||
public bool CurrentValue;
|
||||
public bool CurrentApply;
|
||||
|
||||
public Action<bool> SetValue = null!;
|
||||
public Action<bool> SetApply = null!;
|
||||
|
||||
public string Label = string.Empty;
|
||||
public string Tooltip = string.Empty;
|
||||
|
||||
public ToggleDrawData()
|
||||
{ }
|
||||
|
||||
public static ToggleDrawData FromDesign(ActorState.MetaIndex index, DesignManager manager, Design design)
|
||||
{
|
||||
var (label, value, apply, setValue, setApply) = index switch
|
||||
{
|
||||
ActorState.MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(),
|
||||
(Action<bool>)(b => manager.ChangeMeta(design, index, b)), (Action<bool>)(b => manager.ChangeApplyMeta(design, index, b))),
|
||||
ActorState.MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(),
|
||||
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
|
||||
ActorState.MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(),
|
||||
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
|
||||
ActorState.MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(),
|
||||
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
|
||||
_ => throw new Exception("Unsupported meta index."),
|
||||
};
|
||||
|
||||
return new ToggleDrawData
|
||||
{
|
||||
Label = label,
|
||||
Tooltip = string.Empty,
|
||||
Locked = design.WriteProtected(),
|
||||
DisplayApplication = true,
|
||||
CurrentValue = value,
|
||||
CurrentApply = apply,
|
||||
SetValue = setValue,
|
||||
SetApply = setApply,
|
||||
};
|
||||
}
|
||||
|
||||
public static ToggleDrawData CrestFromDesign(CrestFlag slot, DesignManager manager, Design design)
|
||||
=> new()
|
||||
{
|
||||
Label = $"{slot.ToLabel()} Crest",
|
||||
Tooltip = string.Empty,
|
||||
Locked = design.WriteProtected(),
|
||||
DisplayApplication = true,
|
||||
CurrentValue = design.DesignData.Crest(slot),
|
||||
CurrentApply = design.DoApplyCrest(slot),
|
||||
SetValue = v => manager.ChangeCrest(design, slot, v),
|
||||
SetApply = v => manager.ChangeApplyCrest(design, slot, v),
|
||||
};
|
||||
|
||||
public static ToggleDrawData CrestFromState(CrestFlag slot, StateManager manager, ActorState state)
|
||||
=> new()
|
||||
{
|
||||
Label = $"{slot.ToLabel()} Crest",
|
||||
Tooltip = "Hide or show your free company crest on this piece of gear.",
|
||||
Locked = state.IsLocked,
|
||||
CurrentValue = state.ModelData.Crest(slot),
|
||||
SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual),
|
||||
};
|
||||
|
||||
public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state)
|
||||
{
|
||||
var (label, tooltip, value, setValue) = index switch
|
||||
{
|
||||
ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(),
|
||||
(Action<bool>)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))),
|
||||
ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.",
|
||||
state.ModelData.IsVisorToggled(),
|
||||
b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)),
|
||||
ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.",
|
||||
state.ModelData.IsWeaponVisible(),
|
||||
b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)),
|
||||
ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(),
|
||||
b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)),
|
||||
_ => throw new Exception("Unsupported meta index."),
|
||||
};
|
||||
|
||||
return new ToggleDrawData
|
||||
{
|
||||
Label = label,
|
||||
Tooltip = tooltip,
|
||||
Locked = state.IsLocked,
|
||||
CurrentValue = value,
|
||||
SetValue = setValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
|
|
@ -9,27 +8,13 @@ using Glamourer.Unlocks;
|
|||
using ImGuiNET;
|
||||
using Lumina.Misc;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
[Flags]
|
||||
public enum DataChange : byte
|
||||
{
|
||||
None = 0x00,
|
||||
Item = 0x01,
|
||||
Stain = 0x02,
|
||||
ApplyItem = 0x04,
|
||||
ApplyStain = 0x08,
|
||||
Item2 = 0x10,
|
||||
Stain2 = 0x20,
|
||||
ApplyItem2 = 0x40,
|
||||
ApplyStain2 = 0x80,
|
||||
}
|
||||
|
||||
public static class UiHelpers
|
||||
{
|
||||
/// <summary> Open a combo popup with another method than the combo itself. </summary>
|
||||
|
|
@ -71,14 +56,15 @@ public static class UiHelpers
|
|||
return ret;
|
||||
}
|
||||
|
||||
public static DataChange DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue,
|
||||
public static (bool, bool) DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue,
|
||||
out bool newApply, bool locked)
|
||||
{
|
||||
var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
|
||||
using (var disabled = ImRaii.Disabled(locked))
|
||||
{
|
||||
if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw("##" + label, flags, out flags))
|
||||
if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw(
|
||||
"##" + label, flags, out flags))
|
||||
{
|
||||
(newValue, newApply) = flags switch
|
||||
{
|
||||
|
|
@ -99,22 +85,16 @@ public static class UiHelpers
|
|||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(label);
|
||||
|
||||
return (currentApply != newApply, currentValue != newValue) switch
|
||||
{
|
||||
(true, true) => DataChange.ApplyItem | DataChange.Item,
|
||||
(true, false) => DataChange.ApplyItem,
|
||||
(false, true) => DataChange.Item,
|
||||
_ => DataChange.None,
|
||||
};
|
||||
return (currentValue != newValue, currentApply != newApply);
|
||||
}
|
||||
|
||||
public static (EquipFlag, CustomizeFlag) ConvertKeysToFlags()
|
||||
public static (EquipFlag, CustomizeFlag, CrestFlag) ConvertKeysToFlags()
|
||||
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
|
||||
{
|
||||
(false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant),
|
||||
(true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant),
|
||||
(true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0),
|
||||
(false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant),
|
||||
(false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All),
|
||||
(true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All),
|
||||
(true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All),
|
||||
(false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0),
|
||||
};
|
||||
|
||||
public static (bool, bool) ConvertKeysToBool()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Cust
|
|||
private readonly delegate* unmanaged[Stdcall]<Human*, byte*, bool, bool> _original;
|
||||
|
||||
/// <summary> Check whether we in a manual customize update, in which case we need to not toggle certain flags. </summary>
|
||||
public static readonly ThreadLocal<bool> InUpdate = new(() => false);
|
||||
public static readonly InMethodChecker InUpdate = new();
|
||||
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -70,9 +70,8 @@ public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Cust
|
|||
return false;
|
||||
|
||||
Glamourer.Log.Verbose($"[ChangeCustomize] Invoked on 0x{model.Address:X} with {customize}.");
|
||||
InUpdate.Value = true;
|
||||
var ret = _original(model.AsHuman, customize.Data, true);
|
||||
InUpdate.Value = false;
|
||||
using var _ = InUpdate.EnterMethod();
|
||||
var ret = _original(model.AsHuman, customize.Data, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +80,7 @@ public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Cust
|
|||
|
||||
private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment)
|
||||
{
|
||||
if (!InUpdate.Value)
|
||||
if (!InUpdate.InMethod)
|
||||
{
|
||||
var customize = new Ref<Customize>(new Customize(*(CustomizeData*)data));
|
||||
Invoke(this, (Model)human, customize);
|
||||
|
|
|
|||
111
Glamourer/Interop/CharaFile/CmaFile.cs
Normal file
111
Glamourer/Interop/CharaFile/CmaFile.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
using System;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Services;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop.CharaFile;
|
||||
|
||||
public sealed class CmaFile
|
||||
{
|
||||
public string Name = string.Empty;
|
||||
public DesignData Data = new();
|
||||
|
||||
public static CmaFile? ParseData(ItemManager items, string data, string? name = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jObj = JObject.Parse(data);
|
||||
var ret = new CmaFile();
|
||||
ret.Data.SetDefaultEquipment(items);
|
||||
ParseMainHand(items, jObj, ref ret.Data);
|
||||
ParseOffHand(items, jObj, ref ret.Data);
|
||||
ret.Name = jObj["Description"]?.ToObject<string>() ?? name ?? "New Design";
|
||||
ParseEquipment(items, jObj, ref ret.Data);
|
||||
ParseCustomization(jObj, ref ret.Data);
|
||||
return ret;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void ParseCustomization(JObject jObj, ref DesignData data)
|
||||
{
|
||||
var bytes = jObj["CharacterBytes"]?.ToObject<string>() ?? string.Empty;
|
||||
if (bytes.Length is not 26 * 3 - 1)
|
||||
return;
|
||||
|
||||
bytes = bytes.Replace(" ", string.Empty);
|
||||
var byteData = Convert.FromHexString(bytes);
|
||||
fixed (byte* ptr = byteData)
|
||||
{
|
||||
data.Customize.Data.Read(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void ParseEquipment(ItemManager items, JObject jObj, ref DesignData data)
|
||||
{
|
||||
var bytes = jObj["EquipmentBytes"]?.ToObject<string>() ?? string.Empty;
|
||||
bytes = bytes.Replace(" ", string.Empty);
|
||||
var byteData = Convert.FromHexString(bytes);
|
||||
fixed (byte* ptr = byteData)
|
||||
{
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var idx = slot.ToIndex();
|
||||
if (idx * 4 + 3 >= byteData.Length)
|
||||
continue;
|
||||
var armor = ((CharacterArmor*)ptr)[idx];
|
||||
var item = items.Identify(slot, armor.Set, armor.Variant);
|
||||
data.SetItem(slot, item);
|
||||
data.SetStain(slot, armor.Stain);
|
||||
}
|
||||
|
||||
data.Customize.Data.Read(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseMainHand(ItemManager items, JObject jObj, ref DesignData data)
|
||||
{
|
||||
var mainhand = jObj["MainHand"];
|
||||
if (mainhand == null)
|
||||
{
|
||||
data.SetItem(EquipSlot.MainHand, items.DefaultSword);
|
||||
data.SetStain(EquipSlot.MainHand, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
var set = mainhand["Item1"]?.ToObject<ushort>() ?? items.DefaultSword.ModelId;
|
||||
var type = mainhand["Item2"]?.ToObject<ushort>() ?? items.DefaultSword.WeaponType;
|
||||
var variant = mainhand["Item3"]?.ToObject<byte>() ?? items.DefaultSword.Variant;
|
||||
var stain = mainhand["Item4"]?.ToObject<byte>() ?? 0;
|
||||
var item = items.Identify(EquipSlot.MainHand, set, type, variant);
|
||||
|
||||
data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword);
|
||||
data.SetStain(EquipSlot.MainHand, stain);
|
||||
}
|
||||
|
||||
private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data)
|
||||
{
|
||||
var offhand = jObj["OffHand"];
|
||||
var defaultOffhand = items.GetDefaultOffhand(data.Item(EquipSlot.MainHand));
|
||||
if (offhand == null)
|
||||
{
|
||||
data.SetItem(EquipSlot.MainHand, defaultOffhand);
|
||||
data.SetStain(EquipSlot.MainHand, defaultOffhand.ModelId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand));
|
||||
return;
|
||||
}
|
||||
|
||||
var set = offhand["Item1"]?.ToObject<ushort>() ?? items.DefaultSword.ModelId;
|
||||
var type = offhand["Item2"]?.ToObject<ushort>() ?? items.DefaultSword.WeaponType;
|
||||
var variant = offhand["Item3"]?.ToObject<byte>() ?? items.DefaultSword.Variant;
|
||||
var stain = offhand["Item4"]?.ToObject<byte>() ?? 0;
|
||||
var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType);
|
||||
|
||||
data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand);
|
||||
data.SetStain(EquipSlot.OffHand, defaultOffhand.ModelId.Id == 0 ? 0 : (StainId)stain);
|
||||
}
|
||||
}
|
||||
155
Glamourer/Interop/CrestService.cs
Normal file
155
Glamourer/Interop/CrestService.cs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Structs;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the crest visibility is updated on a model.
|
||||
/// <list type="number">
|
||||
/// <item>Parameter is the model with an update. </item>
|
||||
/// <item>Parameter is the equipment slot changed. </item>
|
||||
/// <item>Parameter is the whether the crest will be shown. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed unsafe class CrestService : EventWrapper<Action<Actor, CrestFlag, Ref<bool>>, CrestService.Priority>
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="State.StateListener.OnCrestChange"/>
|
||||
StateListener = 0,
|
||||
}
|
||||
|
||||
public CrestService(IGameInteropProvider interop)
|
||||
: base(nameof(CrestService))
|
||||
{
|
||||
interop.InitializeFromAttributes(this);
|
||||
_humanSetFreeCompanyCrestVisibleOnSlot =
|
||||
interop.HookFromAddress<SetCrestDelegateIntern>(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour);
|
||||
_weaponSetFreeCompanyCrestVisibleOnSlot =
|
||||
interop.HookFromAddress<SetCrestDelegateIntern>(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour);
|
||||
_humanSetFreeCompanyCrestVisibleOnSlot.Enable();
|
||||
_weaponSetFreeCompanyCrestVisibleOnSlot.Enable();
|
||||
_crestChangeHook.Enable();
|
||||
}
|
||||
|
||||
public void UpdateCrests(Actor gameObject, CrestFlag flags)
|
||||
{
|
||||
if (!gameObject.IsCharacter)
|
||||
return;
|
||||
|
||||
flags &= CrestExtensions.AllRelevant;
|
||||
var currentCrests = gameObject.CrestBitfield;
|
||||
using var update = _inUpdate.EnterMethod();
|
||||
_crestChangeHook.Original(gameObject.AsCharacter, (byte) flags);
|
||||
gameObject.CrestBitfield = currentCrests;
|
||||
}
|
||||
|
||||
public delegate void DrawObjectCrestUpdateDelegate(Model drawObject, CrestFlag slot, ref bool value);
|
||||
|
||||
public event DrawObjectCrestUpdateDelegate? ModelCrestSetup;
|
||||
|
||||
protected override void Dispose(bool _)
|
||||
{
|
||||
_humanSetFreeCompanyCrestVisibleOnSlot.Dispose();
|
||||
_weaponSetFreeCompanyCrestVisibleOnSlot.Dispose();
|
||||
_crestChangeHook.Dispose();
|
||||
}
|
||||
|
||||
private delegate void CrestChangeDelegate(Character* character, byte crestFlags);
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 48 8B 55 ?? 49 8B CE E8", DetourName = nameof(CrestChangeDetour))]
|
||||
private readonly Hook<CrestChangeDelegate> _crestChangeHook = null!;
|
||||
|
||||
private void CrestChangeDetour(Character* character, byte crestFlags)
|
||||
{
|
||||
var actor = (Actor)character;
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
var newValue = new Ref<bool>(((CrestFlag)crestFlags).HasFlag(slot));
|
||||
Invoke(this, actor, slot, newValue);
|
||||
crestFlags = (byte)(newValue.Value ? crestFlags | (byte)slot : crestFlags & (byte)~slot);
|
||||
}
|
||||
|
||||
Glamourer.Log.Information(
|
||||
$"Called CrestChange on {(ulong)character:X} with {crestFlags:X} and prior flags {((Actor)character).CrestBitfield}.");
|
||||
using var _ = _inUpdate.EnterMethod();
|
||||
_crestChangeHook.Original(character, crestFlags);
|
||||
}
|
||||
|
||||
public static bool GetModelCrest(Actor gameObject, CrestFlag slot)
|
||||
{
|
||||
if (!gameObject.IsCharacter)
|
||||
return false;
|
||||
|
||||
var (type, index) = slot.ToIndex();
|
||||
switch (type)
|
||||
{
|
||||
case CrestType.Human:
|
||||
{
|
||||
var model = gameObject.Model;
|
||||
if (!model.IsHuman)
|
||||
return false;
|
||||
|
||||
var getter = (delegate* unmanaged<Human*, byte, byte>)((nint*)model.AsCharacterBase->VTable)[95];
|
||||
return getter(model.AsHuman, index) != 0;
|
||||
}
|
||||
case CrestType.Offhand:
|
||||
{
|
||||
var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject;
|
||||
if (!model.IsWeapon)
|
||||
return false;
|
||||
|
||||
var getter = (delegate* unmanaged<Weapon*, byte, byte>)((nint*)model.AsCharacterBase->VTable)[95];
|
||||
return getter(model.AsWeapon, index) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly InMethodChecker _inUpdate = new();
|
||||
|
||||
private delegate void SetCrestDelegateIntern(DrawObject* drawObject, byte slot, byte visible);
|
||||
|
||||
[Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)]
|
||||
private readonly nint* _humanVTable = null!;
|
||||
|
||||
[Signature(global::Penumbra.GameData.Sigs.WeaponVTable, ScanType = ScanType.StaticAddress)]
|
||||
private readonly nint* _weaponVTable = null!;
|
||||
|
||||
private readonly Hook<SetCrestDelegateIntern> _humanSetFreeCompanyCrestVisibleOnSlot;
|
||||
private readonly Hook<SetCrestDelegateIntern> _weaponSetFreeCompanyCrestVisibleOnSlot;
|
||||
|
||||
private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(DrawObject* drawObject, byte slotIdx, byte visible)
|
||||
{
|
||||
var rVisible = visible != 0;
|
||||
var inUpdate = _inUpdate.InMethod;
|
||||
var slot = (CrestFlag)((ushort)CrestFlag.Head << slotIdx);
|
||||
if (!inUpdate)
|
||||
ModelCrestSetup?.Invoke(drawObject, slot, ref rVisible);
|
||||
|
||||
Glamourer.Log.Excessive(
|
||||
$"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{(ulong)drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate}).");
|
||||
_humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0);
|
||||
}
|
||||
|
||||
private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(DrawObject* drawObject, byte slotIdx, byte visible)
|
||||
{
|
||||
var rVisible = visible != 0;
|
||||
var inUpdate = _inUpdate.InMethod;
|
||||
if (!inUpdate && slotIdx == 0)
|
||||
ModelCrestSetup?.Invoke(drawObject, CrestFlag.OffHand, ref rVisible);
|
||||
Glamourer.Log.Excessive(
|
||||
$"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{(ulong)drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate}).");
|
||||
_weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,9 @@ using Dalamud.Interface.DragDrop;
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop.CharaFile;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Classes;
|
||||
|
||||
|
|
@ -22,9 +24,9 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
|
|||
});
|
||||
|
||||
public void CreateCharaSource()
|
||||
=> _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara"), m =>
|
||||
=> _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara") || m.Extensions.Contains(".cma"), m =>
|
||||
{
|
||||
ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis data for Glamourer...");
|
||||
ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis/CMTool data for Glamourer...");
|
||||
return true;
|
||||
});
|
||||
|
||||
|
|
@ -47,8 +49,8 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
|
|||
name = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
return LoadChara(files[0], out design, out name);
|
||||
|
||||
return Path.GetExtension(files[0]) is ".chara" ? LoadChara(files[0], out design, out name) : LoadCma(files[0], out design, out name);
|
||||
}
|
||||
|
||||
public bool LoadChara(string path, [NotNullWhen(true)] out DesignBase? design, out string name)
|
||||
|
|
@ -81,6 +83,36 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool LoadCma(string path, [NotNullWhen(true)] out DesignBase? design, out string name)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
design = null;
|
||||
name = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(path);
|
||||
var file = CmaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path));
|
||||
if (file == null)
|
||||
throw new Exception();
|
||||
|
||||
name = file.Name;
|
||||
design = new DesignBase(_customizations, file.Data, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(ex, $"Could not read .cma file {path}.", NotificationType.Error);
|
||||
design = null;
|
||||
name = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool LoadDat(string path, out DatCharacterFile file)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
|
|
|
|||
|
|
@ -7,17 +7,20 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
|||
using Glamourer.Events;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe class InventoryService : IDisposable
|
||||
{
|
||||
private readonly MovedEquipment _event;
|
||||
private readonly MovedEquipment _movedItemsEvent;
|
||||
private readonly EquippedGearset _gearsetEvent;
|
||||
private readonly List<(EquipSlot, uint, StainId)> _itemList = new(12);
|
||||
|
||||
public InventoryService(MovedEquipment @event, IGameInteropProvider interop)
|
||||
public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent)
|
||||
{
|
||||
_event = @event;
|
||||
_movedItemsEvent = movedItemsEvent;
|
||||
_gearsetEvent = gearsetEvent;
|
||||
|
||||
_moveItemHook = interop.HookFromAddress<MoveItemDelegate>((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour);
|
||||
_equipGearsetHook =
|
||||
|
|
@ -39,7 +42,10 @@ public unsafe class InventoryService : IDisposable
|
|||
|
||||
private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId)
|
||||
{
|
||||
var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId);
|
||||
var prior = module->CurrentGearsetIndex;
|
||||
var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId);
|
||||
var set = module->GetGearset(gearsetId);
|
||||
_gearsetEvent.Invoke(new ByteString(set->Name).ToString(), gearsetId, prior, glamourPlateId, set->ClassJob);
|
||||
Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})");
|
||||
if (ret == 0)
|
||||
{
|
||||
|
|
@ -64,7 +70,7 @@ public unsafe class InventoryService : IDisposable
|
|||
else if (item.GlamourId != 0)
|
||||
_itemList.Add((slot, item.GlamourId, item.Stain));
|
||||
else
|
||||
_itemList.Add((slot, item.ItemID, item.Stain));
|
||||
_itemList.Add((slot, FixId(item.ItemID), item.Stain));
|
||||
}
|
||||
|
||||
var plate = MirageManager.Instance()->GlamourPlatesSpan[glamourPlateId - 1];
|
||||
|
|
@ -90,7 +96,7 @@ public unsafe class InventoryService : IDisposable
|
|||
else if (item.GlamourId != 0)
|
||||
_itemList.Add((slot, item.GlamourId, item.Stain));
|
||||
else
|
||||
_itemList.Add((slot, item.ItemID, item.Stain));
|
||||
_itemList.Add((slot, FixId(item.ItemID), item.Stain));
|
||||
}
|
||||
|
||||
Add(EquipSlot.MainHand, ref entry->MainHand);
|
||||
|
|
@ -107,12 +113,15 @@ public unsafe class InventoryService : IDisposable
|
|||
Add(EquipSlot.LFinger, ref entry->RingLeft);
|
||||
}
|
||||
|
||||
_event.Invoke(_itemList.ToArray());
|
||||
_movedItemsEvent.Invoke(_itemList.ToArray());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static uint FixId(uint itemId)
|
||||
=> itemId % 50000;
|
||||
|
||||
private delegate int MoveItemDelegate(InventoryManager* manager, InventoryType sourceContainer, ushort sourceSlot,
|
||||
InventoryType targetContainer, ushort targetSlot, byte unk);
|
||||
|
||||
|
|
@ -127,18 +136,18 @@ public unsafe class InventoryService : IDisposable
|
|||
{
|
||||
if (InvokeSource(sourceContainer, sourceSlot, out var source))
|
||||
if (InvokeTarget(manager, targetContainer, targetSlot, out var target))
|
||||
_event.Invoke(new[]
|
||||
_movedItemsEvent.Invoke(new[]
|
||||
{
|
||||
source,
|
||||
target,
|
||||
});
|
||||
else
|
||||
_event.Invoke(new[]
|
||||
_movedItemsEvent.Invoke(new[]
|
||||
{
|
||||
source,
|
||||
});
|
||||
else if (InvokeTarget(manager, targetContainer, targetSlot, out var target))
|
||||
_event.Invoke(new[]
|
||||
_movedItemsEvent.Invoke(new[]
|
||||
{
|
||||
target,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
|
@ -106,6 +107,9 @@ public readonly unsafe struct Actor : IEquatable<Actor>
|
|||
public CharacterArmor GetArmor(EquipSlot slot)
|
||||
=> ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()];
|
||||
|
||||
public bool GetCrest(CrestFlag slot)
|
||||
=> CrestBitfield.HasFlag(slot);
|
||||
|
||||
public CharacterWeapon GetMainhand()
|
||||
=> new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value);
|
||||
|
||||
|
|
@ -115,6 +119,10 @@ public readonly unsafe struct Actor : IEquatable<Actor>
|
|||
public Customize GetCustomize()
|
||||
=> *(Customize*)&AsCharacter->DrawData.CustomizeData;
|
||||
|
||||
// TODO remove this when available in ClientStructs
|
||||
internal ref CrestFlag CrestBitfield
|
||||
=> ref *(CrestFlag*)((byte*)Address + 0x1BBB);
|
||||
|
||||
public override string ToString()
|
||||
=> $"0x{Address:X}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ public unsafe class UpdateSlotService : IDisposable
|
|||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_flagSlotForUpdateHook.Dispose();
|
||||
}
|
||||
=> _flagSlotForUpdateHook.Dispose();
|
||||
|
||||
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Dalamud.Plugin.Services;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ namespace Glamourer.Interop;
|
|||
public unsafe class WeaponService : IDisposable
|
||||
{
|
||||
private readonly WeaponLoading _event;
|
||||
private readonly CrestService _crestService;
|
||||
private readonly ThreadLocal<bool> _inUpdate = new(() => false);
|
||||
|
||||
|
||||
|
|
@ -20,9 +22,10 @@ public unsafe class WeaponService : IDisposable
|
|||
_original;
|
||||
|
||||
|
||||
public WeaponService(WeaponLoading @event, IGameInteropProvider interop)
|
||||
public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService)
|
||||
{
|
||||
_event = @event;
|
||||
_event = @event;
|
||||
_crestService = crestService;
|
||||
_loadWeaponHook =
|
||||
interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
|
||||
_original =
|
||||
|
|
@ -64,8 +67,12 @@ public unsafe class WeaponService : IDisposable
|
|||
// First call the regular function.
|
||||
if (equipSlot is not EquipSlot.Unknown)
|
||||
_event.Invoke(actor, equipSlot, ref tmpWeapon);
|
||||
// Sage hack for weapons appearing in animations?
|
||||
else if (weaponValue == actor.GetMainhand().Value)
|
||||
_event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon);
|
||||
|
||||
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
|
||||
if (tmpWeapon.Value != weapon.Value)
|
||||
{
|
||||
if (tmpWeapon.Set.Id == 0)
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ public class CommandService : IDisposable
|
|||
.AddInitialPurple("Customizations, ")
|
||||
.AddInitialPurple("Equipment, ")
|
||||
.AddInitialPurple("Accessories, ")
|
||||
.AddInitialPurple("Dyes and ")
|
||||
.AddInitialPurple("Dyes & Crests and ")
|
||||
.AddInitialPurple("Weapons, where ").AddPurple("CEADW")
|
||||
.AddText(" means everything should be toggled on, and no value means nothing should be toggled on.")
|
||||
.BuiltString);
|
||||
|
|
@ -268,7 +268,7 @@ public class CommandService : IDisposable
|
|||
applicationFlags |= AutoDesign.Type.Accessories;
|
||||
break;
|
||||
case 'd':
|
||||
applicationFlags |= AutoDesign.Type.Stains;
|
||||
applicationFlags |= AutoDesign.Type.GearCustomization;
|
||||
break;
|
||||
case 'w':
|
||||
applicationFlags |= AutoDesign.Type.Weapons;
|
||||
|
|
@ -472,7 +472,7 @@ public class CommandService : IDisposable
|
|||
&& _stateManager.GetOrCreate(identifier, data.Objects[0], out state)))
|
||||
continue;
|
||||
|
||||
var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant);
|
||||
var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All);
|
||||
_designManager.CreateClone(design, split[0], true);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public class ItemManager : IDisposable
|
|||
public static EquipItem SmallClothesItem(EquipSlot slot)
|
||||
=> new(SmallClothesNpc, SmallclothesId(slot), 0, SmallClothesNpcModel, 0, 1, slot.ToEquipType(), 0, 0, 0);
|
||||
|
||||
public EquipItem Resolve(EquipSlot slot, ItemId itemId)
|
||||
public EquipItem Resolve(EquipSlot slot, CustomItemId itemId)
|
||||
{
|
||||
slot = slot.ToSlot();
|
||||
if (itemId == NothingId(slot))
|
||||
|
|
@ -74,7 +74,7 @@ public class ItemManager : IDisposable
|
|||
if (itemId == SmallclothesId(slot))
|
||||
return SmallClothesItem(slot);
|
||||
|
||||
if (!ItemService.AwaitedService.TryGetValue(itemId, slot, out var item))
|
||||
if (!itemId.IsItem || !ItemService.AwaitedService.TryGetValue(itemId.Item, slot, out var item))
|
||||
return EquipItem.FromId(itemId);
|
||||
|
||||
if (item.Type.ToSlot() != slot)
|
||||
|
|
@ -151,7 +151,7 @@ public class ItemManager : IDisposable
|
|||
|
||||
/// <summary> Returns whether an item id represents a valid item for a slot and gives the item. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public bool IsItemValid(EquipSlot slot, ItemId itemId, out EquipItem item)
|
||||
public bool IsItemValid(EquipSlot slot, CustomItemId itemId, out EquipItem item)
|
||||
{
|
||||
item = Resolve(slot, itemId);
|
||||
return item.Valid;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ public static class ServiceManager
|
|||
.AddSingleton<ObjectUnlocked>()
|
||||
.AddSingleton<TabSelected>()
|
||||
.AddSingleton<MovedEquipment>()
|
||||
.AddSingleton<EquippedGearset>()
|
||||
.AddSingleton<GPoseService>()
|
||||
.AddSingleton<PenumbraReloaded>();
|
||||
|
||||
|
|
@ -97,6 +98,7 @@ public static class ServiceManager
|
|||
.AddSingleton<CustomizeUnlockManager>()
|
||||
.AddSingleton<ItemUnlockManager>()
|
||||
.AddSingleton<ImportService>()
|
||||
.AddSingleton<CrestService>()
|
||||
.AddSingleton<InventoryService>()
|
||||
.AddSingleton<ContextMenuService>()
|
||||
.AddSingleton<ScalingService>();
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ public class ActorState
|
|||
|
||||
/// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary>
|
||||
private readonly StateChanged.Source[] _sources = Enumerable
|
||||
.Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5).ToArray();
|
||||
.Repeat(StateChanged.Source.Game,
|
||||
EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + CrestExtensions.AllRelevantSet.Count).ToArray();
|
||||
|
||||
internal ActorState(ActorIdentifier identifier)
|
||||
=> Identifier = identifier.CreatePermanent();
|
||||
|
|
@ -88,6 +89,9 @@ public class ActorState
|
|||
public ref StateChanged.Source this[EquipSlot slot, bool stain]
|
||||
=> ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)];
|
||||
|
||||
public ref StateChanged.Source this[CrestFlag slot]
|
||||
=> ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()];
|
||||
|
||||
public ref StateChanged.Source this[CustomizeIndex type]
|
||||
=> ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -17,30 +16,9 @@ namespace Glamourer.State;
|
|||
/// This class applies changes made to state to actual objects in the game.
|
||||
/// It handles applying those changes as well as redrawing the actor if necessary.
|
||||
/// </summary>
|
||||
public class StateApplier
|
||||
public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, WeaponService _weapon, ChangeCustomizeService _changeCustomize,
|
||||
ItemManager _items, PenumbraService _penumbra, MetaService _metaService, ObjectManager _objects, CrestService _crests)
|
||||
{
|
||||
private readonly PenumbraService _penumbra;
|
||||
private readonly UpdateSlotService _updateSlot;
|
||||
private readonly VisorService _visor;
|
||||
private readonly WeaponService _weapon;
|
||||
private readonly MetaService _metaService;
|
||||
private readonly ChangeCustomizeService _changeCustomize;
|
||||
private readonly ItemManager _items;
|
||||
private readonly ObjectManager _objects;
|
||||
|
||||
public StateApplier(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize,
|
||||
ItemManager items, PenumbraService penumbra, MetaService metaService, ObjectManager objects)
|
||||
{
|
||||
_updateSlot = updateSlot;
|
||||
_visor = visor;
|
||||
_weapon = weapon;
|
||||
_changeCustomize = changeCustomize;
|
||||
_items = items;
|
||||
_penumbra = penumbra;
|
||||
_metaService = metaService;
|
||||
_objects = objects;
|
||||
}
|
||||
|
||||
/// <summary> Simply force a redraw regardless of conditions. </summary>
|
||||
public void ForceRedraw(ActorData data)
|
||||
{
|
||||
|
|
@ -279,6 +257,22 @@ public class StateApplier
|
|||
return data;
|
||||
}
|
||||
|
||||
/// <summary> Change the crest state on actors. </summary>
|
||||
public void ChangeCrests(ActorData data, CrestFlag flags)
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
_crests.UpdateCrests(actor, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ChangeCrests(ActorData, CrestFlag)"/>
|
||||
public ActorData ChangeCrests(ActorState state, bool apply)
|
||||
{
|
||||
var data = GetData(state);
|
||||
if (apply)
|
||||
ChangeCrests(data, state.ModelData.CrestVisibility);
|
||||
return data;
|
||||
}
|
||||
|
||||
private ActorData GetData(ActorState state)
|
||||
{
|
||||
_objects.Update();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Dalamud.Plugin.Services;
|
|||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -197,6 +198,18 @@ public class StateEditor
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change the crest of an equipment piece. </summary>
|
||||
public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, out bool oldCrest, uint key = 0)
|
||||
{
|
||||
oldCrest = state.ModelData.Crest(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.SetCrest(slot, crest);
|
||||
state[slot] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue,
|
||||
uint key = 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Penumbra.GameData.Structs;
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Structs;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ public class StateListener : IDisposable
|
|||
private readonly MovedEquipment _movedEquipment;
|
||||
private readonly GPoseService _gPose;
|
||||
private readonly ChangeCustomizeService _changeCustomizeService;
|
||||
private readonly CrestService _crestService;
|
||||
private readonly ICondition _condition;
|
||||
|
||||
private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid;
|
||||
|
|
@ -52,7 +54,7 @@ public class StateListener : IDisposable
|
|||
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
|
||||
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans,
|
||||
StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose,
|
||||
ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition)
|
||||
ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition, CrestService crestService)
|
||||
{
|
||||
_manager = manager;
|
||||
_items = items;
|
||||
|
|
@ -74,6 +76,7 @@ public class StateListener : IDisposable
|
|||
_changeCustomizeService = changeCustomizeService;
|
||||
_customizations = customizations;
|
||||
_condition = condition;
|
||||
_crestService = crestService;
|
||||
Subscribe();
|
||||
}
|
||||
|
||||
|
|
@ -405,6 +408,58 @@ public class StateListener : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void OnCrestChange(Actor actor, CrestFlag slot, Ref<bool> value)
|
||||
{
|
||||
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
|
||||
return;
|
||||
|
||||
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|
||||
|| !_manager.TryGetValue(identifier, out var state))
|
||||
return;
|
||||
|
||||
switch (UpdateBaseCrest(actor, state, slot, value.Value))
|
||||
{
|
||||
case UpdateState.Change:
|
||||
if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game);
|
||||
else
|
||||
value.Value = state.ModelData.Crest(slot);
|
||||
break;
|
||||
case UpdateState.NoChange:
|
||||
case UpdateState.HatHack:
|
||||
value.Value = state.ModelData.Crest(slot);
|
||||
break;
|
||||
case UpdateState.Transformed: break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnModelCrestSetup(Model model, CrestFlag slot, ref bool value)
|
||||
{
|
||||
var actor = _penumbra.GameObjectFromDrawObject(model);
|
||||
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
|
||||
return;
|
||||
|
||||
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|
||||
|| !_manager.TryGetValue(identifier, out var state))
|
||||
return;
|
||||
|
||||
value = state.ModelData.Crest(slot);
|
||||
}
|
||||
|
||||
private static UpdateState UpdateBaseCrest(Actor actor, ActorState state, CrestFlag slot, bool visible)
|
||||
{
|
||||
if (actor.IsTransformed)
|
||||
return UpdateState.Transformed;
|
||||
|
||||
if (state.BaseData.Crest(slot) != visible)
|
||||
{
|
||||
state.BaseData.SetCrest(slot, visible);
|
||||
return UpdateState.Change;
|
||||
}
|
||||
|
||||
return UpdateState.NoChange;
|
||||
}
|
||||
|
||||
/// <summary> Update base data for a single changed weapon slot. </summary>
|
||||
private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
|
|
@ -490,7 +545,7 @@ public class StateListener : IDisposable
|
|||
private void OnVisorChange(Model model, Ref<bool> value)
|
||||
{
|
||||
// Skip updates when in customize update.
|
||||
if (ChangeCustomizeService.InUpdate.IsValueCreated && ChangeCustomizeService.InUpdate.Value)
|
||||
if (ChangeCustomizeService.InUpdate.InMethod)
|
||||
return;
|
||||
|
||||
// Find appropriate actor and state.
|
||||
|
|
@ -616,6 +671,8 @@ public class StateListener : IDisposable
|
|||
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
|
||||
_weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener);
|
||||
_changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener);
|
||||
_crestService.Subscribe(OnCrestChange, CrestService.Priority.StateListener);
|
||||
_crestService.ModelCrestSetup += OnModelCrestSetup;
|
||||
}
|
||||
|
||||
private void Unsubscribe()
|
||||
|
|
@ -629,6 +686,8 @@ public class StateListener : IDisposable
|
|||
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
|
||||
_weaponVisibility.Unsubscribe(OnWeaponVisibilityChange);
|
||||
_changeCustomizeService.Unsubscribe(OnCustomizeChange);
|
||||
_crestService.Unsubscribe(OnCrestChange);
|
||||
_crestService.ModelCrestSetup -= OnModelCrestSetup;
|
||||
}
|
||||
|
||||
private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject)
|
||||
|
|
@ -639,8 +698,9 @@ public class StateListener : IDisposable
|
|||
if (_creatingState == null)
|
||||
return;
|
||||
|
||||
_applier.ChangeHatState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsHatVisible());
|
||||
_applier.ChangeWeaponState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWeaponVisible());
|
||||
_applier.ChangeWetness(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWet());
|
||||
var data = new ActorData(gameObject, _creatingIdentifier.ToName());
|
||||
_applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible());
|
||||
_applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible());
|
||||
_applier.ChangeWetness(data, _creatingState.ModelData.IsWet());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Glamourer.Events;
|
|||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -17,32 +18,12 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||
public class StateManager(ActorService _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor,
|
||||
HumanModelList _humans, ICondition _condition, IClientState _clientState)
|
||||
: IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||
{
|
||||
private readonly ActorService _actors;
|
||||
private readonly ItemManager _items;
|
||||
private readonly HumanModelList _humans;
|
||||
private readonly StateChanged _event;
|
||||
private readonly StateApplier _applier;
|
||||
private readonly StateEditor _editor;
|
||||
private readonly ICondition _condition;
|
||||
private readonly IClientState _clientState;
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
|
||||
|
||||
public StateManager(ActorService actors, ItemManager items, StateChanged @event, StateApplier applier, StateEditor editor,
|
||||
HumanModelList humans, ICondition condition, IClientState clientState)
|
||||
{
|
||||
_actors = actors;
|
||||
_items = items;
|
||||
_event = @event;
|
||||
_applier = applier;
|
||||
_editor = editor;
|
||||
_humans = humans;
|
||||
_condition = condition;
|
||||
_clientState = clientState;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActorState>> GetEnumerator()
|
||||
=> _states.GetEnumerator();
|
||||
|
||||
|
|
@ -83,7 +64,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
// and the draw objects data for the model data (where possible).
|
||||
state = new ActorState(identifier)
|
||||
{
|
||||
ModelData = FromActor(actor, true, false),
|
||||
ModelData = FromActor(actor, true, false),
|
||||
BaseData = FromActor(actor, false, false),
|
||||
LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0),
|
||||
LastTerritory = _clientState.TerritoryType,
|
||||
|
|
@ -162,6 +143,9 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
|
||||
// Visor state is a flag on the game object, but we can see the actual state on the draw object.
|
||||
ret.SetVisor(VisorService.GetVisorState(model));
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -180,6 +164,9 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
off = actor.GetOffhand();
|
||||
FistWeaponHack(ref ret, ref main, ref off);
|
||||
ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
ret.SetCrest(slot, actor.GetCrest(slot));
|
||||
}
|
||||
|
||||
// Set the weapons regardless of source.
|
||||
|
|
@ -206,7 +193,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
if (mainhand.Set.Id is < 1601 or >= 1651)
|
||||
return;
|
||||
|
||||
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant) offhand.Type.Id);
|
||||
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant)offhand.Type.Id);
|
||||
offhand.Set = (SetId)(mainhand.Set.Id + 50);
|
||||
offhand.Variant = mainhand.Variant;
|
||||
offhand.Type = mainhand.Type;
|
||||
|
|
@ -304,6 +291,18 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot));
|
||||
}
|
||||
|
||||
/// <summary> Change the crest of an equipment piece. </summary>
|
||||
public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeCrests(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot));
|
||||
}
|
||||
|
||||
/// <summary> Change hat visibility. </summary>
|
||||
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
|
|
@ -356,19 +355,8 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
|
||||
public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
|
||||
{
|
||||
var unused = (applyPiece, applyStain) switch
|
||||
{
|
||||
(false, false) => false,
|
||||
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key),
|
||||
(false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key),
|
||||
(true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _,
|
||||
out _, key),
|
||||
};
|
||||
}
|
||||
|
||||
if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), source,
|
||||
if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(),
|
||||
source,
|
||||
out var oldModelId, key))
|
||||
return;
|
||||
|
||||
|
|
@ -393,12 +381,28 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
|
||||
foreach (var slot in EquipSlotExtensions.FullSlots)
|
||||
HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot));
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest))
|
||||
_editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key);
|
||||
}
|
||||
|
||||
var actors = ApplyAll(state, redraw, false);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, design);
|
||||
return;
|
||||
|
||||
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
|
||||
{
|
||||
var unused = (applyPiece, applyStain) switch
|
||||
{
|
||||
(false, false) => false,
|
||||
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key),
|
||||
(false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key),
|
||||
(true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _,
|
||||
out _, key),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private ActorData ApplyAll(ActorState state, bool redraw, bool withLock)
|
||||
|
|
@ -430,6 +434,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
_applier.ChangeHatState(actors, state.ModelData.IsHatVisible());
|
||||
_applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible());
|
||||
_applier.ChangeVisor(actors, state.ModelData.IsVisorToggled());
|
||||
_applier.ChangeCrests(actors, state.ModelData.CrestVisibility);
|
||||
}
|
||||
|
||||
return actors;
|
||||
|
|
@ -453,10 +458,13 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
state[slot, true] = StateChanged.Source.Game;
|
||||
state[slot, false] = StateChanged.Source.Game;
|
||||
}
|
||||
|
||||
|
||||
foreach (var type in Enum.GetValues<ActorState.MetaIndex>())
|
||||
state[type] = StateChanged.Source.Game;
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
state[slot] = StateChanged.Source.Game;
|
||||
|
||||
var actors = ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
|
||||
actors = ApplyAll(state, redraw, true);
|
||||
|
|
@ -491,6 +499,15 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
if (state[slot] is StateChanged.Source.Fixed)
|
||||
{
|
||||
state[slot] = StateChanged.Source.Game;
|
||||
state.ModelData.SetCrest(slot, state.BaseData.Crest(slot));
|
||||
}
|
||||
}
|
||||
|
||||
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed)
|
||||
{
|
||||
state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game;
|
||||
|
|
|
|||
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit f55733a96fdc9f82c9bbf8272ca6366079aa8e32
|
||||
Subproject commit f624aca526bd1f36364d63443ed1c6e83499b8b7
|
||||
Loading…
Add table
Add a link
Reference in a new issue