mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
.
This commit is contained in:
parent
85adc3626e
commit
10631341cb
31 changed files with 1883 additions and 1507 deletions
|
|
@ -5,61 +5,53 @@ namespace Glamourer.Customization;
|
|||
|
||||
public unsafe struct Customize
|
||||
{
|
||||
public readonly Penumbra.GameData.Structs.CustomizeData* Data;
|
||||
public Penumbra.GameData.Structs.CustomizeData Data;
|
||||
|
||||
public Customize(Penumbra.GameData.Structs.CustomizeData* data)
|
||||
public Customize(in Penumbra.GameData.Structs.CustomizeData data)
|
||||
=> Data = data;
|
||||
|
||||
public Customize(ref Penumbra.GameData.Structs.CustomizeData data)
|
||||
{
|
||||
fixed (Penumbra.GameData.Structs.CustomizeData* ptr = &data)
|
||||
{
|
||||
Data = ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public Race Race
|
||||
{
|
||||
get => (Race)Data->Get(CustomizeIndex.Race).Value;
|
||||
set => Data->Set(CustomizeIndex.Race, (CustomizeValue)(byte)value);
|
||||
get => (Race)Data.Get(CustomizeIndex.Race).Value;
|
||||
set => Data.Set(CustomizeIndex.Race, (CustomizeValue)(byte)value);
|
||||
}
|
||||
|
||||
public Gender Gender
|
||||
{
|
||||
get => (Gender)Data->Get(CustomizeIndex.Gender).Value + 1;
|
||||
set => Data->Set(CustomizeIndex.Gender, (CustomizeValue)(byte)value - 1);
|
||||
get => (Gender)Data.Get(CustomizeIndex.Gender).Value + 1;
|
||||
set => Data.Set(CustomizeIndex.Gender, (CustomizeValue)(byte)value - 1);
|
||||
}
|
||||
|
||||
public CustomizeValue BodyType
|
||||
{
|
||||
get => Data->Get(CustomizeIndex.BodyType);
|
||||
set => Data->Set(CustomizeIndex.BodyType, value);
|
||||
get => Data.Get(CustomizeIndex.BodyType);
|
||||
set => Data.Set(CustomizeIndex.BodyType, value);
|
||||
}
|
||||
|
||||
public SubRace Clan
|
||||
{
|
||||
get => (SubRace)Data->Get(CustomizeIndex.Clan).Value;
|
||||
set => Data->Set(CustomizeIndex.Clan, (CustomizeValue)(byte)value);
|
||||
get => (SubRace)Data.Get(CustomizeIndex.Clan).Value;
|
||||
set => Data.Set(CustomizeIndex.Clan, (CustomizeValue)(byte)value);
|
||||
}
|
||||
|
||||
public CustomizeValue Face
|
||||
{
|
||||
get => Data->Get(CustomizeIndex.Face);
|
||||
set => Data->Set(CustomizeIndex.Face, value);
|
||||
get => Data.Get(CustomizeIndex.Face);
|
||||
set => Data.Set(CustomizeIndex.Face, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly Penumbra.GameData.Structs.CustomizeData Default = GenerateDefault();
|
||||
public static readonly Penumbra.GameData.Structs.CustomizeData Empty = new();
|
||||
public static readonly Customize Default = GenerateDefault();
|
||||
public static readonly Customize Empty = new();
|
||||
|
||||
public CustomizeValue Get(CustomizeIndex index)
|
||||
=> Data->Get(index);
|
||||
=> Data.Get(index);
|
||||
|
||||
public void Set(CustomizeIndex flag, CustomizeValue index)
|
||||
=> Data->Set(flag, index);
|
||||
public bool Set(CustomizeIndex flag, CustomizeValue index)
|
||||
=> Data.Set(flag, index);
|
||||
|
||||
public bool Equals(Customize other)
|
||||
=> Penumbra.GameData.Structs.CustomizeData.Equals(Data, other.Data);
|
||||
=> Equals(Data, other.Data);
|
||||
|
||||
public CustomizeValue this[CustomizeIndex index]
|
||||
{
|
||||
|
|
@ -67,9 +59,9 @@ public unsafe struct Customize
|
|||
set => Set(index, value);
|
||||
}
|
||||
|
||||
private static Penumbra.GameData.Structs.CustomizeData GenerateDefault()
|
||||
private static Customize GenerateDefault()
|
||||
{
|
||||
var ret = new Penumbra.GameData.Structs.CustomizeData();
|
||||
var ret = new Customize();
|
||||
ret.Set(CustomizeIndex.BodyType, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.Height, (CustomizeValue)50);
|
||||
ret.Set(CustomizeIndex.Face, (CustomizeValue)1);
|
||||
|
|
@ -94,16 +86,16 @@ public unsafe struct Customize
|
|||
}
|
||||
|
||||
public void Load(Customize other)
|
||||
=> Data->Read(other.Data);
|
||||
=> Data.Read(&other.Data);
|
||||
|
||||
public void Write(IntPtr target)
|
||||
=> Data->Write((void*)target);
|
||||
public void Write(nint target)
|
||||
=> Data.Write((void*)target);
|
||||
|
||||
public bool LoadBase64(string data)
|
||||
=> Data->LoadBase64(data);
|
||||
=> Data.LoadBase64(data);
|
||||
|
||||
public string WriteBase64()
|
||||
=> Data->WriteBase64();
|
||||
=> Data.WriteBase64();
|
||||
|
||||
public static CustomizeFlag Compare(Customize lhs, Customize rhs)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ using Dalamud.Logging;
|
|||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using System;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Penumbra.Api.Enums;
|
||||
|
||||
|
|
@ -30,6 +32,10 @@ public partial class Glamourer
|
|||
|
||||
private readonly ObjectTable _objectTable;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly ActiveDesign.Manager _stateManager;
|
||||
private readonly ItemManager _items;
|
||||
private readonly PenumbraAttach _penumbra;
|
||||
private readonly ActorService _actors;
|
||||
|
||||
internal ICallGateProvider<string, string?>? ProviderGetAllCustomization;
|
||||
internal ICallGateProvider<Character?, string?>? ProviderGetAllCustomizationFromCharacter;
|
||||
|
|
@ -43,10 +49,15 @@ public partial class Glamourer
|
|||
internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
|
||||
internal ICallGateProvider<int>? ProviderGetApiVersion;
|
||||
|
||||
public GlamourerIpc(ObjectTable objectTable, DalamudPluginInterface pluginInterface)
|
||||
public GlamourerIpc(ObjectTable objectTable, DalamudPluginInterface pluginInterface, ActiveDesign.Manager stateManager,
|
||||
ItemManager items, PenumbraAttach penumbra, ActorService actors)
|
||||
{
|
||||
_objectTable = objectTable;
|
||||
_pluginInterface = pluginInterface;
|
||||
_stateManager = stateManager;
|
||||
_items = items;
|
||||
_penumbra = penumbra;
|
||||
_actors = actors;
|
||||
|
||||
InitializeProviders();
|
||||
}
|
||||
|
|
@ -211,9 +222,9 @@ public partial class Glamourer
|
|||
if (character == null)
|
||||
return;
|
||||
|
||||
//var design = Design.CreateTemporaryFromBase64(customization, true, true);
|
||||
//var active = _glamourer._stateManager.GetOrCreateSave(character.Address);
|
||||
//_glamourer._stateManager.ApplyDesign(active, design, false);
|
||||
var design = Design.CreateTemporaryFromBase64(_items, customization, true, true);
|
||||
var active = _stateManager.GetOrCreateSave(character.Address);
|
||||
_stateManager.ApplyDesign(active, design, false);
|
||||
}
|
||||
|
||||
private void ApplyOnlyCustomization(string customization, string characterName)
|
||||
|
|
@ -232,30 +243,32 @@ public partial class Glamourer
|
|||
{
|
||||
if (character == null)
|
||||
return;
|
||||
//var design = Design.CreateTemporaryFromBase64(customization, true, false);
|
||||
//var active = _glamourer._stateManager.GetOrCreateSave(character.Address);
|
||||
//_glamourer._stateManager.ApplyDesign(active, design, false);
|
||||
|
||||
var design = Design.CreateTemporaryFromBase64(_items, customization, true, false);
|
||||
var active = _stateManager.GetOrCreateSave(character.Address);
|
||||
_stateManager.ApplyDesign(active, design, false);
|
||||
}
|
||||
|
||||
private void ApplyOnlyEquipment(string customization, string characterName)
|
||||
{
|
||||
foreach (var gameObject in _objectTable)
|
||||
{
|
||||
if (gameObject.Name.ToString() == characterName)
|
||||
{
|
||||
if (gameObject.Name.ToString() != characterName)
|
||||
continue;
|
||||
|
||||
ApplyOnlyEquipment(customization, gameObject as Character);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyOnlyEquipment(string customization, Character? character)
|
||||
{
|
||||
if (character == null)
|
||||
return;
|
||||
//var design = Design.CreateTemporaryFromBase64(customization, false, true);
|
||||
//var active = _glamourer._stateManager.GetOrCreateSave(character.Address);
|
||||
//_glamourer._stateManager.ApplyDesign(active, design, false);
|
||||
|
||||
var design = Design.CreateTemporaryFromBase64(_items, customization, false, true);
|
||||
var active = _stateManager.GetOrCreateSave(character.Address);
|
||||
_stateManager.ApplyDesign(active, design, false);
|
||||
}
|
||||
|
||||
private void Revert(string characterName)
|
||||
|
|
@ -274,9 +287,9 @@ public partial class Glamourer
|
|||
if (character == null)
|
||||
return;
|
||||
|
||||
//var ident = Actors.FromObject(character, true, false, false);
|
||||
//_glamourer._stateManager.DeleteSave(ident);
|
||||
//_glamourer._penumbra.RedrawObject(character.Address, RedrawType.Redraw);
|
||||
var ident = _actors.AwaitedService.FromObject(character, true, false, false);
|
||||
_stateManager.DeleteSave(ident);
|
||||
_penumbra.RedrawObject(character.Address, RedrawType.Redraw);
|
||||
}
|
||||
|
||||
private string? GetAllCustomization(Character? character)
|
||||
|
|
@ -284,12 +297,11 @@ public partial class Glamourer
|
|||
if (character == null)
|
||||
return null;
|
||||
|
||||
//var ident = Actors.FromObject(character, true, false, false);
|
||||
//if (!_glamourer._stateManager.TryGetValue(ident, out var design))
|
||||
// design = new ActiveDesign(ident, character.Address);
|
||||
//
|
||||
//return design.CreateOldBase64();
|
||||
return null;
|
||||
var ident = _actors.AwaitedService.FromObject(character, true, false, false);
|
||||
if (!_stateManager.TryGetValue(ident, out var design))
|
||||
design = new ActiveDesign(_items, ident, character.Address);
|
||||
|
||||
return design.CreateOldBase64();
|
||||
}
|
||||
|
||||
private string? GetAllCustomization(string characterName)
|
||||
|
|
|
|||
|
|
@ -9,11 +9,6 @@ using Penumbra.Api.Helpers;
|
|||
|
||||
namespace Glamourer.Api;
|
||||
|
||||
public class CommunicatorService
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public unsafe class PenumbraAttach : IDisposable
|
||||
{
|
||||
public const int RequiredPenumbraBreakingVersion = 4;
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String.Functions;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CharacterData
|
||||
{
|
||||
public uint ModelId;
|
||||
public CustomizeData CustomizeData;
|
||||
public CharacterWeapon MainHand;
|
||||
public CharacterWeapon OffHand;
|
||||
public CharacterArmor Head;
|
||||
public CharacterArmor Body;
|
||||
public CharacterArmor Hands;
|
||||
public CharacterArmor Legs;
|
||||
public CharacterArmor Feet;
|
||||
public CharacterArmor Ears;
|
||||
public CharacterArmor Neck;
|
||||
public CharacterArmor Wrists;
|
||||
public CharacterArmor RFinger;
|
||||
public CharacterArmor LFinger;
|
||||
|
||||
public unsafe Customize Customize
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (CustomizeData* ptr = &CustomizeData)
|
||||
{
|
||||
return new Customize(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe CharacterEquip Equipment
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (CharacterArmor* ptr = &Head)
|
||||
{
|
||||
return new CharacterEquip(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly CharacterData Default
|
||||
= new()
|
||||
{
|
||||
ModelId = 0,
|
||||
CustomizeData = Customize.Default,
|
||||
MainHand = CharacterWeapon.Empty,
|
||||
OffHand = CharacterWeapon.Empty,
|
||||
Head = CharacterArmor.Empty,
|
||||
Body = CharacterArmor.Empty,
|
||||
Hands = CharacterArmor.Empty,
|
||||
Legs = CharacterArmor.Empty,
|
||||
Feet = CharacterArmor.Empty,
|
||||
Ears = CharacterArmor.Empty,
|
||||
Neck = CharacterArmor.Empty,
|
||||
Wrists = CharacterArmor.Empty,
|
||||
RFinger = CharacterArmor.Empty,
|
||||
LFinger = CharacterArmor.Empty,
|
||||
};
|
||||
|
||||
public readonly unsafe CharacterData Clone()
|
||||
{
|
||||
var data = new CharacterData();
|
||||
fixed (void* ptr = &this)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(&data, ptr, sizeof(CharacterData));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void Load(IDesignable designable)
|
||||
{
|
||||
ModelId = designable.ModelId;
|
||||
Customize.Load(designable.Customize);
|
||||
Equipment.Load(designable.Equip);
|
||||
MainHand = designable.MainHand;
|
||||
OffHand = designable.OffHand;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,15 +9,12 @@ using Glamourer.Services;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public partial class Design
|
||||
{
|
||||
public partial class Manager
|
||||
public class DesignManager
|
||||
{
|
||||
public const string DesignFolderName = "designs";
|
||||
public readonly string DesignFolder;
|
||||
|
|
@ -47,45 +44,20 @@ public partial class Design
|
|||
|
||||
public delegate void DesignChangeDelegate(DesignChangeType type, Design design, object? changeData = null);
|
||||
|
||||
public event DesignChangeDelegate DesignChange;
|
||||
public event DesignChangeDelegate? DesignChange;
|
||||
|
||||
public IReadOnlyList<Design> Designs
|
||||
=> _designs;
|
||||
|
||||
public Manager(DalamudPluginInterface pi, SaveService saveService, ItemManager items)
|
||||
public DesignManager(DalamudPluginInterface pi, SaveService saveService, ItemManager items)
|
||||
{
|
||||
_saveService = saveService;
|
||||
_items = items;
|
||||
DesignFolder = SetDesignFolder(pi);
|
||||
DesignChange += OnChange;
|
||||
LoadDesigns();
|
||||
MigrateOldDesigns(pi, Path.Combine(new DirectoryInfo(DesignFolder).Parent!.FullName, "Designs.json"));
|
||||
}
|
||||
|
||||
private void OnChange(DesignChangeType type, Design design, object? _)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DesignChangeType.Created:
|
||||
SaveDesignInternal(design);
|
||||
return;
|
||||
case DesignChangeType.Renamed:
|
||||
case DesignChangeType.ChangedDescription:
|
||||
case DesignChangeType.AddedTag:
|
||||
case DesignChangeType.RemovedTag:
|
||||
case DesignChangeType.ChangedTag:
|
||||
case DesignChangeType.Customize:
|
||||
case DesignChangeType.Equip:
|
||||
case DesignChangeType.Weapon:
|
||||
case DesignChangeType.Stain:
|
||||
case DesignChangeType.ApplyCustomize:
|
||||
case DesignChangeType.ApplyEquip:
|
||||
case DesignChangeType.Other:
|
||||
SaveDesign(design);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static string SetDesignFolder(DalamudPluginInterface pi)
|
||||
{
|
||||
var ret = Path.Combine(pi.GetPluginConfigDirectory(), DesignFolderName);
|
||||
|
|
@ -104,27 +76,6 @@ public partial class Design
|
|||
return ret;
|
||||
}
|
||||
|
||||
private string CreateFileName(Design design)
|
||||
=> Path.Combine(DesignFolder, $"{design.Identifier}.json");
|
||||
|
||||
public void SaveDesign(Design design)
|
||||
=> _saveService.QueueSave(design);
|
||||
|
||||
private void SaveDesignInternal(Design design)
|
||||
{
|
||||
var fileName = CreateFileName(design);
|
||||
try
|
||||
{
|
||||
var data = design.JsonSerialize().ToString(Formatting.Indented);
|
||||
File.WriteAllText(fileName, data);
|
||||
Glamourer.Log.Debug($"Saved design {design.Identifier}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not save design {design.Identifier} to file:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadDesigns()
|
||||
{
|
||||
_designs.Clear();
|
||||
|
|
@ -136,7 +87,7 @@ public partial class Design
|
|||
{
|
||||
var text = File.ReadAllText(file.FullName);
|
||||
var data = JObject.Parse(text);
|
||||
var design = LoadDesign(_items, data, out var changes);
|
||||
var design = Design.LoadDesign(_items, data, out var changes);
|
||||
if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(file.Name))
|
||||
invalidNames.Add((design, file.FullName));
|
||||
if (_designs.Any(f => f.Identifier == design.Identifier))
|
||||
|
|
@ -158,7 +109,7 @@ public partial class Design
|
|||
{
|
||||
try
|
||||
{
|
||||
var correctName = CreateFileName(design);
|
||||
var correctName = _saveService.FileNames.DesignFile(design);
|
||||
File.Move(name, correctName, false);
|
||||
Glamourer.Log.Information($"Moved invalid design file from {Path.GetFileName(name)} to {Path.GetFileName(correctName)}.");
|
||||
}
|
||||
|
|
@ -175,7 +126,7 @@ public partial class Design
|
|||
|
||||
Glamourer.Log.Information(
|
||||
$"Loaded {_designs.Count} designs.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
|
||||
DesignChange.Invoke(DesignChangeType.ReloadedAll, null!);
|
||||
DesignChange?.Invoke(DesignChangeType.ReloadedAll, null!);
|
||||
}
|
||||
|
||||
public Design Create(string name)
|
||||
|
|
@ -189,7 +140,8 @@ public partial class Design
|
|||
};
|
||||
_designs.Add(design);
|
||||
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.Created, design);
|
||||
_saveService.ImmediateSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.Created, design);
|
||||
return design;
|
||||
}
|
||||
|
||||
|
|
@ -198,44 +150,25 @@ public partial class Design
|
|||
_designs.RemoveAt(design.Index);
|
||||
foreach (var d in _designs.Skip(design.Index + 1))
|
||||
--d.Index;
|
||||
var fileName = CreateFileName(design);
|
||||
try
|
||||
{
|
||||
File.Delete(fileName);
|
||||
Glamourer.Log.Debug($"Deleted design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.Deleted, design);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not delete design file for {design.Identifier}:\n{ex}");
|
||||
}
|
||||
_saveService.ImmediateDelete(design);
|
||||
}
|
||||
|
||||
public void Rename(Design design, string newName)
|
||||
{
|
||||
var oldName = design.Name.Text;
|
||||
var oldFileName = CreateFileName(design);
|
||||
if (File.Exists(oldFileName))
|
||||
try
|
||||
{
|
||||
File.Delete(oldFileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not delete old design file for rename from {design.Identifier}:\n{ex}");
|
||||
return;
|
||||
}
|
||||
|
||||
_saveService.ImmediateDelete(design);
|
||||
design.Name = newName;
|
||||
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.Renamed, design, oldName);
|
||||
_saveService.ImmediateSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.Renamed, design, oldName);
|
||||
}
|
||||
|
||||
public void ChangeDescription(Design design, string description)
|
||||
{
|
||||
design.Description = description;
|
||||
Glamourer.Log.Debug($"Changed description of design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.ChangedDescription, design);
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.ChangedDescription, design);
|
||||
}
|
||||
|
||||
public void AddTag(Design design, string tag)
|
||||
|
|
@ -246,7 +179,8 @@ public partial class Design
|
|||
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
|
||||
var idx = design.Tags.IndexOf(tag);
|
||||
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.AddedTag, design);
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.AddedTag, design);
|
||||
}
|
||||
|
||||
public void RemoveTag(Design design, string tag)
|
||||
|
|
@ -261,7 +195,8 @@ public partial class Design
|
|||
var oldTag = design.Tags[tagIdx];
|
||||
design.Tags = design.Tags.Take(tagIdx).Concat(design.Tags.Skip(tagIdx + 1)).ToArray();
|
||||
Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}.");
|
||||
DesignChange.Invoke(DesignChangeType.RemovedTag, design);
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.RemovedTag, design);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -273,40 +208,43 @@ public partial class Design
|
|||
|
||||
design.Tags[tagIdx] = newTag;
|
||||
Array.Sort(design.Tags);
|
||||
SaveDesign(design);
|
||||
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
|
||||
DesignChange.Invoke(DesignChangeType.ChangedTag, design);
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.ChangedTag, design);
|
||||
}
|
||||
|
||||
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
|
||||
{
|
||||
var old = design.GetCustomize(idx);
|
||||
if (design.SetCustomize(idx, value))
|
||||
{
|
||||
if (!design.SetCustomize(idx, value))
|
||||
return;
|
||||
|
||||
Glamourer.Log.Debug($"Changed customize {idx} in design {design.Identifier} from {old.Value} to {value.Value}");
|
||||
DesignChange.Invoke(DesignChangeType.Customize, design, idx);
|
||||
}
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.Customize, design, idx);
|
||||
}
|
||||
|
||||
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
||||
{
|
||||
if (design.SetApplyCustomize(idx, value))
|
||||
{
|
||||
if (!design.SetApplyCustomize(idx, value))
|
||||
return;
|
||||
|
||||
Glamourer.Log.Debug($"Set applying of customization {idx} to {value}.");
|
||||
DesignChange.Invoke(DesignChangeType.ApplyCustomize, design, idx);
|
||||
}
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.ApplyCustomize, design, idx);
|
||||
}
|
||||
|
||||
public void ChangeEquip(Design design, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||
{
|
||||
var old = design.Armor(slot);
|
||||
if (design.SetArmor(_items, slot, itemId, item))
|
||||
{
|
||||
if (!design.SetArmor(_items, slot, itemId, item))
|
||||
return;
|
||||
|
||||
var n = design.Armor(slot);
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {slot} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {n.Name} ({n.ItemId}).");
|
||||
DesignChange.Invoke(DesignChangeType.Equip, design, slot);
|
||||
}
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.Equip, design, slot);
|
||||
}
|
||||
|
||||
public void ChangeWeapon(Design design, uint itemId, EquipSlot offhand, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||
|
|
@ -314,39 +252,43 @@ public partial class Design
|
|||
var (old, change, n) = offhand == EquipSlot.OffHand
|
||||
? (design.WeaponOff, design.SetOffhand(_items, itemId, item), design.WeaponOff)
|
||||
: (design.WeaponMain, design.SetMainhand(_items, itemId, item), design.WeaponMain);
|
||||
if (change)
|
||||
{
|
||||
if (!change)
|
||||
return;
|
||||
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {offhand} weapon in design {design.Identifier} from {old.Name} ({old.ItemId}) to {n.Name} ({n.ItemId}).");
|
||||
DesignChange.Invoke(DesignChangeType.Weapon, design, offhand);
|
||||
}
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.Weapon, design, offhand);
|
||||
}
|
||||
|
||||
public void ChangeApplyEquip(Design design, EquipSlot slot, bool value)
|
||||
{
|
||||
if (design.SetApplyEquip(slot, value))
|
||||
{
|
||||
if (!design.SetApplyEquip(slot, value))
|
||||
return;
|
||||
|
||||
Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
|
||||
DesignChange.Invoke(DesignChangeType.ApplyEquip, design, slot);
|
||||
}
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.ApplyEquip, design, slot);
|
||||
}
|
||||
|
||||
public void ChangeStain(Design design, EquipSlot slot, StainId stain)
|
||||
{
|
||||
if (design.SetStain(slot, stain))
|
||||
{
|
||||
if (!design.SetStain(slot, stain))
|
||||
return;
|
||||
|
||||
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Value}.");
|
||||
DesignChange.Invoke(DesignChangeType.Stain, design, slot);
|
||||
}
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.Stain, design, slot);
|
||||
}
|
||||
|
||||
public void ChangeApplyStain(Design design, EquipSlot slot, bool value)
|
||||
{
|
||||
if (design.SetApplyStain(slot, value))
|
||||
{
|
||||
if (!design.SetApplyStain(slot, value))
|
||||
return;
|
||||
|
||||
Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
|
||||
DesignChange.Invoke(DesignChangeType.Stain, design, slot);
|
||||
}
|
||||
_saveService.QueueSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.Stain, design, slot);
|
||||
}
|
||||
|
||||
private Guid CreateNewGuid()
|
||||
|
|
@ -368,7 +310,8 @@ public partial class Design
|
|||
_designs.Add(design);
|
||||
if (!message.IsNullOrEmpty())
|
||||
Glamourer.Log.Debug(message);
|
||||
DesignChange.Invoke(DesignChangeType.Created, design);
|
||||
_saveService.ImmediateSave(design);
|
||||
DesignChange?.Invoke(DesignChangeType.Created, design);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -426,4 +369,3 @@ public partial class Design
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,24 +11,24 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public partial class Design : DesignBase, ISavable
|
||||
public partial class Design : DesignData, ISavable
|
||||
{
|
||||
public const int FileVersion = 1;
|
||||
|
||||
public Guid Identifier { get; private init; }
|
||||
public DateTimeOffset CreationDate { get; private init; }
|
||||
public LowerString Name { get; private set; } = LowerString.Empty;
|
||||
public string Description { get; private set; } = string.Empty;
|
||||
public string[] Tags { get; private set; } = Array.Empty<string>();
|
||||
public int Index { get; private set; }
|
||||
public Guid Identifier { get; internal init; }
|
||||
public DateTimeOffset CreationDate { get; internal init; }
|
||||
public LowerString Name { get; internal set; } = LowerString.Empty;
|
||||
public string Description { get; internal set; } = string.Empty;
|
||||
public string[] Tags { get; internal set; } = Array.Empty<string>();
|
||||
public int Index { get; internal set; }
|
||||
|
||||
public EquipFlag ApplyEquip { get; private set; }
|
||||
public CustomizeFlag ApplyCustomize { get; private set; }
|
||||
public QuadBool Wetness { get; private set; } = QuadBool.NullFalse;
|
||||
public QuadBool Visor { get; private set; } = QuadBool.NullFalse;
|
||||
public QuadBool Hat { get; private set; } = QuadBool.NullFalse;
|
||||
public QuadBool Weapon { get; private set; } = QuadBool.NullFalse;
|
||||
public bool WriteProtected { get; private set; }
|
||||
public EquipFlag ApplyEquip { get; internal set; }
|
||||
public CustomizeFlag ApplyCustomize { get; internal set; }
|
||||
public QuadBool Wetness { get; internal set; } = QuadBool.NullFalse;
|
||||
public QuadBool Visor { get; internal set; } = QuadBool.NullFalse;
|
||||
public QuadBool Hat { get; internal set; } = QuadBool.NullFalse;
|
||||
public QuadBool Weapon { get; internal set; } = QuadBool.NullFalse;
|
||||
public bool WriteProtected { get; internal set; }
|
||||
|
||||
public bool DoApplyEquip(EquipSlot slot)
|
||||
=> ApplyEquip.HasFlag(slot.ToFlag());
|
||||
|
|
@ -39,7 +39,7 @@ public partial class Design : DesignBase, ISavable
|
|||
public bool DoApplyCustomize(CustomizeIndex idx)
|
||||
=> ApplyCustomize.HasFlag(idx.ToFlag());
|
||||
|
||||
private bool SetApplyEquip(EquipSlot slot, bool value)
|
||||
internal bool SetApplyEquip(EquipSlot slot, bool value)
|
||||
{
|
||||
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag();
|
||||
if (newValue == ApplyEquip)
|
||||
|
|
@ -49,7 +49,7 @@ public partial class Design : DesignBase, ISavable
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool SetApplyStain(EquipSlot slot, bool value)
|
||||
internal bool SetApplyStain(EquipSlot slot, bool value)
|
||||
{
|
||||
var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag();
|
||||
if (newValue == ApplyEquip)
|
||||
|
|
@ -59,7 +59,7 @@ public partial class Design : DesignBase, ISavable
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool SetApplyCustomize(CustomizeIndex idx, bool value)
|
||||
internal bool SetApplyCustomize(CustomizeIndex idx, bool value)
|
||||
{
|
||||
var newValue = value ? ApplyCustomize | idx.ToFlag() : ApplyCustomize & ~idx.ToFlag();
|
||||
if (newValue == ApplyCustomize)
|
||||
|
|
@ -70,7 +70,7 @@ public partial class Design : DesignBase, ISavable
|
|||
}
|
||||
|
||||
|
||||
private Design(ItemManager items)
|
||||
internal Design(ItemManager items)
|
||||
: base(items)
|
||||
{ }
|
||||
|
||||
|
|
@ -85,8 +85,8 @@ public partial class Design : DesignBase, ISavable
|
|||
[nameof(Description)] = Description,
|
||||
[nameof(Tags)] = JArray.FromObject(Tags),
|
||||
[nameof(WriteProtected)] = WriteProtected,
|
||||
[nameof(CharacterData.Equipment)] = SerializeEquipment(),
|
||||
[nameof(CharacterData.Customize)] = SerializeCustomize(),
|
||||
[nameof(ModelData.Equipment)] = SerializeEquipment(),
|
||||
[nameof(ModelData.Customize)] = SerializeCustomize(),
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -104,9 +104,9 @@ public partial class Design : DesignBase, ISavable
|
|||
|
||||
var ret = new JObject()
|
||||
{
|
||||
[nameof(MainHand)] =
|
||||
Serialize(MainHand, CharacterData.MainHand.Stain, DoApplyEquip(EquipSlot.MainHand), DoApplyStain(EquipSlot.MainHand)),
|
||||
[nameof(OffHand)] = Serialize(OffHand, CharacterData.OffHand.Stain, DoApplyEquip(EquipSlot.OffHand),
|
||||
[nameof(MainHandId)] =
|
||||
Serialize(MainHandId, ModelData.MainHand.Stain, DoApplyEquip(EquipSlot.MainHand), DoApplyStain(EquipSlot.MainHand)),
|
||||
[nameof(OffHandId)] = Serialize(OffHandId, ModelData.OffHand.Stain, DoApplyEquip(EquipSlot.OffHand),
|
||||
DoApplyStain(EquipSlot.OffHand)),
|
||||
};
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ public partial class Design : DesignBase, ISavable
|
|||
{
|
||||
[nameof(ModelId)] = ModelId,
|
||||
};
|
||||
var customize = CharacterData.Customize;
|
||||
var customize = ModelData.Customize;
|
||||
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var data = customize[idx];
|
||||
|
|
@ -154,7 +154,7 @@ public partial class Design : DesignBase, ISavable
|
|||
};
|
||||
}
|
||||
|
||||
private static Design LoadDesignV1(ItemManager items, JObject json, out bool changes)
|
||||
internal static Design LoadDesignV1(ItemManager items, JObject json, out bool changes)
|
||||
{
|
||||
static string[] ParseTags(JObject json)
|
||||
{
|
||||
|
|
@ -176,7 +176,7 @@ public partial class Design : DesignBase, ISavable
|
|||
return design;
|
||||
}
|
||||
|
||||
private static bool LoadEquip(ItemManager items, JToken? equip, Design design)
|
||||
internal static bool LoadEquip(ItemManager items, JToken? equip, Design design)
|
||||
{
|
||||
if (equip == null)
|
||||
return true;
|
||||
|
|
@ -241,12 +241,12 @@ public partial class Design : DesignBase, ISavable
|
|||
return changes;
|
||||
}
|
||||
|
||||
private static bool LoadCustomize(JToken? json, Design design)
|
||||
internal static bool LoadCustomize(JToken? json, Design design)
|
||||
{
|
||||
if (json == null)
|
||||
return true;
|
||||
|
||||
var customize = design.CharacterData.Customize;
|
||||
var customize = design.ModelData.Customize;
|
||||
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var tok = json[idx.ToString()];
|
||||
|
|
@ -263,13 +263,14 @@ public partial class Design : DesignBase, ISavable
|
|||
|
||||
public void MigrateBase64(ItemManager items, string base64)
|
||||
{
|
||||
var data = MigrateBase64(base64, out var applyEquip, out var applyCustomize, out var writeProtected, out var wet, out var hat,
|
||||
var data = DesignBase64Migration.MigrateBase64(base64, out var applyEquip, out var applyCustomize, out var writeProtected, out var wet,
|
||||
out var hat,
|
||||
out var visor, out var weapon);
|
||||
UpdateMainhand(items, data.MainHand);
|
||||
UpdateOffhand(items, data.OffHand);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
UpdateArmor(items, slot, data.Equipment[slot], true);
|
||||
CharacterData.CustomizeData = data.CustomizeData;
|
||||
ModelData.CustomizeData = data.CustomizeData;
|
||||
ApplyEquip = applyEquip;
|
||||
ApplyCustomize = applyCustomize;
|
||||
WriteProtected = writeProtected;
|
||||
|
|
@ -296,7 +297,8 @@ public partial class Design : DesignBase, ISavable
|
|||
|
||||
// Outdated.
|
||||
public string CreateOldBase64()
|
||||
=> CreateOldBase64(in CharacterData, ApplyEquip, ApplyCustomize, Wetness == QuadBool.True, Hat.ForcedValue, Hat.Enabled,
|
||||
=> DesignBase64Migration.CreateOldBase64(in ModelData, ApplyEquip, ApplyCustomize, Wetness == QuadBool.True, Hat.ForcedValue,
|
||||
Hat.Enabled,
|
||||
Visor.ForcedValue, Visor.Enabled, Weapon.ForcedValue, Weapon.Enabled, WriteProtected, 1f);
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
|
|
|
|||
|
|
@ -1,453 +0,0 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Util;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignBase
|
||||
{
|
||||
protected CharacterData CharacterData = CharacterData.Default;
|
||||
public FullEquipType MainhandType { get; protected set; }
|
||||
|
||||
public uint Head { get; protected set; } = ItemManager.NothingId(EquipSlot.Head);
|
||||
public uint Body { get; protected set; } = ItemManager.NothingId(EquipSlot.Body);
|
||||
public uint Hands { get; protected set; } = ItemManager.NothingId(EquipSlot.Hands);
|
||||
public uint Legs { get; protected set; } = ItemManager.NothingId(EquipSlot.Legs);
|
||||
public uint Feet { get; protected set; } = ItemManager.NothingId(EquipSlot.Feet);
|
||||
public uint Ears { get; protected set; } = ItemManager.NothingId(EquipSlot.Ears);
|
||||
public uint Neck { get; protected set; } = ItemManager.NothingId(EquipSlot.Neck);
|
||||
public uint Wrists { get; protected set; } = ItemManager.NothingId(EquipSlot.Wrists);
|
||||
public uint RFinger { get; protected set; } = ItemManager.NothingId(EquipSlot.RFinger);
|
||||
public uint LFinger { get; protected set; } = ItemManager.NothingId(EquipSlot.RFinger);
|
||||
public uint MainHand { get; protected set; }
|
||||
public uint OffHand { get; protected set; }
|
||||
|
||||
public string HeadName { get; protected set; } = ItemManager.Nothing;
|
||||
public string BodyName { get; protected set; } = ItemManager.Nothing;
|
||||
public string HandsName { get; protected set; } = ItemManager.Nothing;
|
||||
public string LegsName { get; protected set; } = ItemManager.Nothing;
|
||||
public string FeetName { get; protected set; } = ItemManager.Nothing;
|
||||
public string EarsName { get; protected set; } = ItemManager.Nothing;
|
||||
public string NeckName { get; protected set; } = ItemManager.Nothing;
|
||||
public string WristsName { get; protected set; } = ItemManager.Nothing;
|
||||
public string RFingerName { get; protected set; } = ItemManager.Nothing;
|
||||
public string LFingerName { get; protected set; } = ItemManager.Nothing;
|
||||
public string MainhandName { get; protected set; }
|
||||
public string OffhandName { get; protected set; }
|
||||
|
||||
public Customize Customize()
|
||||
=> CharacterData.Customize;
|
||||
|
||||
public CharacterEquip Equipment()
|
||||
=> CharacterData.Equipment;
|
||||
|
||||
public DesignBase(ItemManager items)
|
||||
{
|
||||
MainHand = items.DefaultSword.RowId;
|
||||
(_, CharacterData.MainHand.Set, CharacterData.MainHand.Type, CharacterData.MainHand.Variant, MainhandName, MainhandType) =
|
||||
items.Resolve(MainHand, items.DefaultSword);
|
||||
OffHand = ItemManager.NothingId(MainhandType.Offhand());
|
||||
(_, CharacterData.OffHand.Set, CharacterData.OffHand.Type, CharacterData.OffHand.Variant, OffhandName, _) =
|
||||
items.Resolve(OffHand, MainhandType);
|
||||
}
|
||||
|
||||
public uint ModelId
|
||||
=> CharacterData.ModelId;
|
||||
|
||||
public Item Armor(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Head => new Item(HeadName, Head, CharacterData.Head),
|
||||
EquipSlot.Body => new Item(BodyName, Body, CharacterData.Body),
|
||||
EquipSlot.Hands => new Item(HandsName, Hands, CharacterData.Hands),
|
||||
EquipSlot.Legs => new Item(LegsName, Legs, CharacterData.Legs),
|
||||
EquipSlot.Feet => new Item(FeetName, Feet, CharacterData.Feet),
|
||||
EquipSlot.Ears => new Item(EarsName, Ears, CharacterData.Ears),
|
||||
EquipSlot.Neck => new Item(NeckName, Neck, CharacterData.Neck),
|
||||
EquipSlot.Wrists => new Item(WristsName, Wrists, CharacterData.Wrists),
|
||||
EquipSlot.RFinger => new Item(RFingerName, RFinger, CharacterData.RFinger),
|
||||
EquipSlot.LFinger => new Item(LFingerName, LFinger, CharacterData.LFinger),
|
||||
_ => throw new Exception("Invalid equip slot for item."),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Weapon WeaponMain
|
||||
=> new(MainhandName, MainHand, CharacterData.MainHand, MainhandType);
|
||||
|
||||
public Weapon WeaponOff
|
||||
=> Weapon.Offhand(OffhandName, OffHand, CharacterData.OffHand, MainhandType);
|
||||
|
||||
public CustomizeValue GetCustomize(CustomizeIndex idx)
|
||||
=> Customize()[idx];
|
||||
|
||||
protected bool SetCustomize(CustomizeIndex idx, CustomizeValue value)
|
||||
{
|
||||
var c = Customize();
|
||||
if (c[idx] == value)
|
||||
return false;
|
||||
|
||||
c[idx] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool SetArmor(ItemManager items, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||
{
|
||||
var (valid, set, variant, name) = items.Resolve(slot, itemId, item);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
return SetArmor(slot, set, variant, name, itemId);
|
||||
}
|
||||
|
||||
protected bool SetArmor(EquipSlot slot, Item item)
|
||||
=> SetArmor(slot, item.ModelBase, item.Variant, item.Name, item.ItemId);
|
||||
|
||||
protected bool UpdateArmor(ItemManager items, EquipSlot slot, CharacterArmor armor, bool force)
|
||||
{
|
||||
if (!force)
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.Head when CharacterData.Head.Value == armor.Value: return false;
|
||||
case EquipSlot.Body when CharacterData.Body.Value == armor.Value: return false;
|
||||
case EquipSlot.Hands when CharacterData.Hands.Value == armor.Value: return false;
|
||||
case EquipSlot.Legs when CharacterData.Legs.Value == armor.Value: return false;
|
||||
case EquipSlot.Feet when CharacterData.Feet.Value == armor.Value: return false;
|
||||
case EquipSlot.Ears when CharacterData.Ears.Value == armor.Value: return false;
|
||||
case EquipSlot.Neck when CharacterData.Neck.Value == armor.Value: return false;
|
||||
case EquipSlot.Wrists when CharacterData.Wrists.Value == armor.Value: return false;
|
||||
case EquipSlot.RFinger when CharacterData.RFinger.Value == armor.Value: return false;
|
||||
case EquipSlot.LFinger when CharacterData.LFinger.Value == armor.Value: return false;
|
||||
}
|
||||
|
||||
var (valid, id, name) = items.Identify(slot, armor.Set, armor.Variant);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
return SetArmor(slot, armor.Set, armor.Variant, name, id);
|
||||
}
|
||||
|
||||
protected bool SetMainhand(ItemManager items, uint mainId, Lumina.Excel.GeneratedSheets.Item? main = null)
|
||||
{
|
||||
if (mainId == MainHand)
|
||||
return false;
|
||||
|
||||
var (valid, set, weapon, variant, name, type) = items.Resolve(mainId, main);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
var fixOffhand = type.Offhand() != MainhandType.Offhand();
|
||||
|
||||
MainHand = mainId;
|
||||
MainhandName = name;
|
||||
MainhandType = type;
|
||||
CharacterData.MainHand.Set = set;
|
||||
CharacterData.MainHand.Type = weapon;
|
||||
CharacterData.MainHand.Variant = variant;
|
||||
if (fixOffhand)
|
||||
SetOffhand(items, ItemManager.NothingId(type.Offhand()));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool SetOffhand(ItemManager items, uint offId, Lumina.Excel.GeneratedSheets.Item? off = null)
|
||||
{
|
||||
if (offId == OffHand)
|
||||
return false;
|
||||
|
||||
var (valid, set, weapon, variant, name, type) = items.Resolve(offId, MainhandType, off);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
OffHand = offId;
|
||||
OffhandName = name;
|
||||
CharacterData.OffHand.Set = set;
|
||||
CharacterData.OffHand.Type = weapon;
|
||||
CharacterData.OffHand.Variant = variant;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool UpdateMainhand(ItemManager items, CharacterWeapon weapon)
|
||||
{
|
||||
if (weapon.Value == CharacterData.MainHand.Value)
|
||||
return false;
|
||||
|
||||
var (valid, id, name, type) = items.Identify(EquipSlot.MainHand, weapon.Set, weapon.Type, (byte)weapon.Variant);
|
||||
if (!valid || id == MainHand)
|
||||
return false;
|
||||
|
||||
var fixOffhand = type.Offhand() != MainhandType.Offhand();
|
||||
|
||||
MainHand = id;
|
||||
MainhandName = name;
|
||||
MainhandType = type;
|
||||
CharacterData.MainHand.Set = weapon.Set;
|
||||
CharacterData.MainHand.Type = weapon.Type;
|
||||
CharacterData.MainHand.Variant = weapon.Variant;
|
||||
CharacterData.MainHand.Stain = weapon.Stain;
|
||||
if (fixOffhand)
|
||||
SetOffhand(items, ItemManager.NothingId(type.Offhand()));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool UpdateOffhand(ItemManager items, CharacterWeapon weapon)
|
||||
{
|
||||
if (weapon.Value == CharacterData.OffHand.Value)
|
||||
return false;
|
||||
|
||||
var (valid, id, name, _) = items.Identify(EquipSlot.OffHand, weapon.Set, weapon.Type, (byte)weapon.Variant, MainhandType);
|
||||
if (!valid || id == OffHand)
|
||||
return false;
|
||||
|
||||
OffHand = id;
|
||||
OffhandName = name;
|
||||
CharacterData.OffHand.Set = weapon.Set;
|
||||
CharacterData.OffHand.Type = weapon.Type;
|
||||
CharacterData.OffHand.Variant = weapon.Variant;
|
||||
CharacterData.OffHand.Stain = weapon.Stain;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool SetStain(EquipSlot slot, StainId id)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.MainHand => SetIfDifferent(ref CharacterData.MainHand.Stain, id),
|
||||
EquipSlot.OffHand => SetIfDifferent(ref CharacterData.OffHand.Stain, id),
|
||||
EquipSlot.Head => SetIfDifferent(ref CharacterData.Head.Stain, id),
|
||||
EquipSlot.Body => SetIfDifferent(ref CharacterData.Body.Stain, id),
|
||||
EquipSlot.Hands => SetIfDifferent(ref CharacterData.Hands.Stain, id),
|
||||
EquipSlot.Legs => SetIfDifferent(ref CharacterData.Legs.Stain, id),
|
||||
EquipSlot.Feet => SetIfDifferent(ref CharacterData.Feet.Stain, id),
|
||||
EquipSlot.Ears => SetIfDifferent(ref CharacterData.Ears.Stain, id),
|
||||
EquipSlot.Neck => SetIfDifferent(ref CharacterData.Neck.Stain, id),
|
||||
EquipSlot.Wrists => SetIfDifferent(ref CharacterData.Wrists.Stain, id),
|
||||
EquipSlot.RFinger => SetIfDifferent(ref CharacterData.RFinger.Stain, id),
|
||||
EquipSlot.LFinger => SetIfDifferent(ref CharacterData.LFinger.Stain, id),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
protected static bool SetIfDifferent<T>(ref T old, T value) where T : IEquatable<T>
|
||||
{
|
||||
if (old.Equals(value))
|
||||
return false;
|
||||
|
||||
old = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool SetArmor(EquipSlot slot, SetId set, byte variant, string name, uint id)
|
||||
{
|
||||
var changes = false;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.Head:
|
||||
changes |= SetIfDifferent(ref CharacterData.Head.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Head.Variant, variant);
|
||||
changes |= HeadName != name;
|
||||
HeadName = name;
|
||||
changes |= Head != id;
|
||||
Head = id;
|
||||
return changes;
|
||||
case EquipSlot.Body:
|
||||
changes |= SetIfDifferent(ref CharacterData.Body.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Body.Variant, variant);
|
||||
changes |= BodyName != name;
|
||||
BodyName = name;
|
||||
changes |= Body != id;
|
||||
Body = id;
|
||||
return changes;
|
||||
case EquipSlot.Hands:
|
||||
changes |= SetIfDifferent(ref CharacterData.Hands.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Hands.Variant, variant);
|
||||
changes |= HandsName != name;
|
||||
HandsName = name;
|
||||
changes |= Hands != id;
|
||||
Hands = id;
|
||||
return changes;
|
||||
case EquipSlot.Legs:
|
||||
changes |= SetIfDifferent(ref CharacterData.Legs.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Legs.Variant, variant);
|
||||
changes |= LegsName != name;
|
||||
LegsName = name;
|
||||
changes |= Legs != id;
|
||||
Legs = id;
|
||||
return changes;
|
||||
case EquipSlot.Feet:
|
||||
changes |= SetIfDifferent(ref CharacterData.Feet.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Feet.Variant, variant);
|
||||
changes |= FeetName != name;
|
||||
FeetName = name;
|
||||
changes |= Feet != id;
|
||||
Feet = id;
|
||||
return changes;
|
||||
case EquipSlot.Ears:
|
||||
changes |= SetIfDifferent(ref CharacterData.Ears.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Ears.Variant, variant);
|
||||
changes |= EarsName != name;
|
||||
EarsName = name;
|
||||
changes |= Ears != id;
|
||||
Ears = id;
|
||||
return changes;
|
||||
case EquipSlot.Neck:
|
||||
changes |= SetIfDifferent(ref CharacterData.Neck.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Neck.Variant, variant);
|
||||
changes |= NeckName != name;
|
||||
NeckName = name;
|
||||
changes |= Neck != id;
|
||||
Neck = id;
|
||||
return changes;
|
||||
case EquipSlot.Wrists:
|
||||
changes |= SetIfDifferent(ref CharacterData.Wrists.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.Wrists.Variant, variant);
|
||||
changes |= WristsName != name;
|
||||
WristsName = name;
|
||||
changes |= Wrists != id;
|
||||
Wrists = id;
|
||||
return changes;
|
||||
case EquipSlot.RFinger:
|
||||
changes |= SetIfDifferent(ref CharacterData.RFinger.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.RFinger.Variant, variant);
|
||||
changes |= RFingerName != name;
|
||||
RFingerName = name;
|
||||
changes |= RFinger != id;
|
||||
RFinger = id;
|
||||
return changes;
|
||||
case EquipSlot.LFinger:
|
||||
changes |= SetIfDifferent(ref CharacterData.LFinger.Set, set);
|
||||
changes |= SetIfDifferent(ref CharacterData.LFinger.Variant, variant);
|
||||
changes |= LFingerName != name;
|
||||
LFingerName = name;
|
||||
changes |= LFinger != id;
|
||||
LFinger = id;
|
||||
return changes;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected const int Base64Size = 89;
|
||||
|
||||
public static CharacterData MigrateBase64(string base64, out EquipFlag equipFlags, out CustomizeFlag customizeFlags,
|
||||
out bool writeProtected, out QuadBool wet, out QuadBool hat, out QuadBool visor, out QuadBool weapon)
|
||||
{
|
||||
static void CheckSize(int length, int requiredLength)
|
||||
{
|
||||
if (length != requiredLength)
|
||||
throw new Exception(
|
||||
$"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}.");
|
||||
}
|
||||
|
||||
byte applicationFlags;
|
||||
ushort equipFlagsS;
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
hat = QuadBool.Null;
|
||||
visor = QuadBool.Null;
|
||||
weapon = QuadBool.Null;
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
CheckSize(bytes.Length, 86);
|
||||
applicationFlags = bytes[1];
|
||||
equipFlagsS = BitConverter.ToUInt16(bytes, 2);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
CheckSize(bytes.Length, Base64Size);
|
||||
applicationFlags = bytes[1];
|
||||
equipFlagsS = BitConverter.ToUInt16(bytes, 2);
|
||||
hat = hat.SetValue((bytes[90] & 0x01) == 0);
|
||||
visor = visor.SetValue((bytes[90] & 0x10) != 0);
|
||||
weapon = weapon.SetValue((bytes[90] & 0x02) == 0);
|
||||
break;
|
||||
}
|
||||
default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}.");
|
||||
}
|
||||
|
||||
customizeFlags = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0;
|
||||
wet = (applicationFlags & 0x02) != 0 ? QuadBool.True : QuadBool.NullFalse;
|
||||
hat = hat.SetEnabled((applicationFlags & 0x04) != 0);
|
||||
weapon = weapon.SetEnabled((applicationFlags & 0x08) != 0);
|
||||
visor = visor.SetEnabled((applicationFlags & 0x10) != 0);
|
||||
writeProtected = (applicationFlags & 0x20) != 0;
|
||||
|
||||
equipFlags = 0;
|
||||
equipFlags |= (equipFlagsS & 0x0001) != 0 ? EquipFlag.Mainhand | EquipFlag.MainhandStain : 0;
|
||||
equipFlags |= (equipFlagsS & 0x0002) != 0 ? EquipFlag.Offhand | EquipFlag.OffhandStain : 0;
|
||||
var flag = 0x0002u;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
flag <<= 1;
|
||||
equipFlags |= (equipFlagsS & flag) != 0 ? slot.ToFlag() | slot.ToStainFlag() : 0;
|
||||
}
|
||||
|
||||
var data = new CharacterData();
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
data.CustomizeData.Read(ptr + 4);
|
||||
var cur = (CharacterWeapon*)(ptr + 30);
|
||||
data.MainHand = cur[0];
|
||||
data.OffHand = cur[1];
|
||||
var eq = (CharacterArmor*)(cur + 2);
|
||||
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
|
||||
data.Equipment[slot] = eq[idx];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static unsafe string CreateOldBase64(in CharacterData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, bool wet, bool hat,
|
||||
bool setHat, bool visor, bool setVisor, bool weapon, bool setWeapon, bool writeProtected, float alpha)
|
||||
{
|
||||
var data = stackalloc byte[Base64Size];
|
||||
data[0] = 2;
|
||||
data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0)
|
||||
| (wet ? 0x02 : 0)
|
||||
| (setHat ? 0x04 : 0)
|
||||
| (setWeapon ? 0x08 : 0)
|
||||
| (setVisor ? 0x10 : 0)
|
||||
| (writeProtected ? 0x20 : 0));
|
||||
data[2] = (byte)((equipFlags.HasFlag(EquipFlag.Mainhand) ? 0x01 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Offhand) ? 0x02 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Head) ? 0x04 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Body) ? 0x08 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Hands) ? 0x10 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Legs) ? 0x20 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Feet) ? 0x40 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Ears) ? 0x80 : 0));
|
||||
data[3] = (byte)((equipFlags.HasFlag(EquipFlag.Neck) ? 0x01 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
|
||||
save.CustomizeData.Write(data + 4);
|
||||
((CharacterWeapon*)(data + 30))[0] = save.MainHand;
|
||||
((CharacterWeapon*)(data + 30))[1] = save.OffHand;
|
||||
((CharacterArmor*)(data + 44))[0] = save.Head;
|
||||
((CharacterArmor*)(data + 44))[1] = save.Body;
|
||||
((CharacterArmor*)(data + 44))[2] = save.Hands;
|
||||
((CharacterArmor*)(data + 44))[3] = save.Legs;
|
||||
((CharacterArmor*)(data + 44))[4] = save.Feet;
|
||||
((CharacterArmor*)(data + 44))[5] = save.Ears;
|
||||
((CharacterArmor*)(data + 44))[6] = save.Neck;
|
||||
((CharacterArmor*)(data + 44))[7] = save.Wrists;
|
||||
((CharacterArmor*)(data + 44))[8] = save.RFinger;
|
||||
((CharacterArmor*)(data + 44))[9] = save.LFinger;
|
||||
*(float*)(data + 84) = 1f;
|
||||
data[88] = (byte)((hat ? 0x01 : 0)
|
||||
| (visor ? 0x10 : 0)
|
||||
| (weapon ? 0x02 : 0));
|
||||
|
||||
return Convert.ToBase64String(new Span<byte>(data, Base64Size));
|
||||
}
|
||||
}
|
||||
432
Glamourer/Designs/DesignData.cs
Normal file
432
Glamourer/Designs/DesignData.cs
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignData
|
||||
{
|
||||
internal ModelData ModelData;
|
||||
public FullEquipType MainhandType { get; internal set; }
|
||||
|
||||
public uint Head = ItemManager.NothingId(EquipSlot.Head);
|
||||
public uint Body = ItemManager.NothingId(EquipSlot.Body);
|
||||
public uint Hands = ItemManager.NothingId(EquipSlot.Hands);
|
||||
public uint Legs = ItemManager.NothingId(EquipSlot.Legs);
|
||||
public uint Feet = ItemManager.NothingId(EquipSlot.Feet);
|
||||
public uint Ears = ItemManager.NothingId(EquipSlot.Ears);
|
||||
public uint Neck = ItemManager.NothingId(EquipSlot.Neck);
|
||||
public uint Wrists = ItemManager.NothingId(EquipSlot.Wrists);
|
||||
public uint RFinger = ItemManager.NothingId(EquipSlot.RFinger);
|
||||
public uint LFinger = ItemManager.NothingId(EquipSlot.RFinger);
|
||||
public uint MainHandId;
|
||||
public uint OffHandId;
|
||||
|
||||
public string HeadName = ItemManager.Nothing;
|
||||
public string BodyName = ItemManager.Nothing;
|
||||
public string HandsName = ItemManager.Nothing;
|
||||
public string LegsName = ItemManager.Nothing;
|
||||
public string FeetName = ItemManager.Nothing;
|
||||
public string EarsName = ItemManager.Nothing;
|
||||
public string NeckName = ItemManager.Nothing;
|
||||
public string WristsName = ItemManager.Nothing;
|
||||
public string RFingerName = ItemManager.Nothing;
|
||||
public string LFingerName = ItemManager.Nothing;
|
||||
public string MainhandName;
|
||||
public string OffhandName;
|
||||
|
||||
public DesignData(ItemManager items)
|
||||
{
|
||||
MainHandId = items.DefaultSword.RowId;
|
||||
(_, var set, var type, var variant, MainhandName, MainhandType) = items.Resolve(MainHandId, items.DefaultSword);
|
||||
|
||||
ModelData = new ModelData(new CharacterWeapon(set, type, variant, 0));
|
||||
OffHandId = ItemManager.NothingId(MainhandType.Offhand());
|
||||
(_, ModelData.OffHand.Set, ModelData.OffHand.Type, ModelData.OffHand.Variant, OffhandName, _) =
|
||||
items.Resolve(OffHandId, MainhandType);
|
||||
}
|
||||
|
||||
public uint ModelId
|
||||
=> ModelData.ModelId;
|
||||
|
||||
public Item Armor(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Head => new Item(HeadName, Head, ModelData.Head),
|
||||
EquipSlot.Body => new Item(BodyName, Body, ModelData.Body),
|
||||
EquipSlot.Hands => new Item(HandsName, Hands, ModelData.Hands),
|
||||
EquipSlot.Legs => new Item(LegsName, Legs, ModelData.Legs),
|
||||
EquipSlot.Feet => new Item(FeetName, Feet, ModelData.Feet),
|
||||
EquipSlot.Ears => new Item(EarsName, Ears, ModelData.Ears),
|
||||
EquipSlot.Neck => new Item(NeckName, Neck, ModelData.Neck),
|
||||
EquipSlot.Wrists => new Item(WristsName, Wrists, ModelData.Wrists),
|
||||
EquipSlot.RFinger => new Item(RFingerName, RFinger, ModelData.RFinger),
|
||||
EquipSlot.LFinger => new Item(LFingerName, LFinger, ModelData.LFinger),
|
||||
_ => throw new Exception("Invalid equip slot for item."),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Weapon WeaponMain
|
||||
=> new(MainhandName, MainHandId, ModelData.MainHand, MainhandType);
|
||||
|
||||
public Weapon WeaponOff
|
||||
=> Weapon.Offhand(OffhandName, OffHandId, ModelData.OffHand, MainhandType);
|
||||
|
||||
public CustomizeValue GetCustomize(CustomizeIndex idx)
|
||||
=> ModelData.Customize[idx];
|
||||
|
||||
internal bool SetCustomize(CustomizeIndex idx, CustomizeValue value)
|
||||
=> ModelData.Customize.Set(idx, value);
|
||||
|
||||
internal bool SetArmor(ItemManager items, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
|
||||
{
|
||||
var (valid, set, variant, name) = items.Resolve(slot, itemId, item);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
return SetArmor(slot, set, variant, name, itemId);
|
||||
}
|
||||
|
||||
internal bool SetArmor(EquipSlot slot, Item item)
|
||||
=> SetArmor(slot, item.ModelBase, item.Variant, item.Name, item.ItemId);
|
||||
|
||||
internal bool UpdateArmor(ItemManager items, EquipSlot slot, CharacterArmor armor, bool force)
|
||||
{
|
||||
var (valid, id, name) = items.Identify(slot, armor.Set, armor.Variant);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
return SetArmor(slot, armor.Set, armor.Variant, name, id) | SetStain(slot, armor.Stain);
|
||||
}
|
||||
|
||||
internal bool SetMainhand(ItemManager items, uint mainId, Lumina.Excel.GeneratedSheets.Item? main = null)
|
||||
{
|
||||
if (mainId == MainHandId)
|
||||
return false;
|
||||
|
||||
var (valid, set, weapon, variant, name, type) = items.Resolve(mainId, main);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
var fixOffhand = type.Offhand() != MainhandType.Offhand();
|
||||
|
||||
MainHandId = mainId;
|
||||
MainhandName = name;
|
||||
MainhandType = type;
|
||||
ModelData.MainHand.Set = set;
|
||||
ModelData.MainHand.Type = weapon;
|
||||
ModelData.MainHand.Variant = variant;
|
||||
if (fixOffhand)
|
||||
SetOffhand(items, ItemManager.NothingId(type.Offhand()));
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool SetOffhand(ItemManager items, uint offId, Lumina.Excel.GeneratedSheets.Item? off = null)
|
||||
{
|
||||
if (offId == OffHandId)
|
||||
return false;
|
||||
|
||||
var (valid, set, weapon, variant, name, type) = items.Resolve(offId, MainhandType, off);
|
||||
if (!valid)
|
||||
return false;
|
||||
|
||||
OffHandId = offId;
|
||||
OffhandName = name;
|
||||
ModelData.OffHand.Set = set;
|
||||
ModelData.OffHand.Type = weapon;
|
||||
ModelData.OffHand.Variant = variant;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool UpdateMainhand(ItemManager items, CharacterWeapon weapon)
|
||||
{
|
||||
if (weapon.Value == ModelData.MainHand.Value)
|
||||
return false;
|
||||
|
||||
var (valid, id, name, type) = items.Identify(EquipSlot.MainHand, weapon.Set, weapon.Type, (byte)weapon.Variant);
|
||||
if (!valid || id == MainHandId)
|
||||
return false;
|
||||
|
||||
var fixOffhand = type.Offhand() != MainhandType.Offhand();
|
||||
|
||||
MainHandId = id;
|
||||
MainhandName = name;
|
||||
MainhandType = type;
|
||||
ModelData.MainHand.Set = weapon.Set;
|
||||
ModelData.MainHand.Type = weapon.Type;
|
||||
ModelData.MainHand.Variant = weapon.Variant;
|
||||
ModelData.MainHand.Stain = weapon.Stain;
|
||||
if (fixOffhand)
|
||||
SetOffhand(items, ItemManager.NothingId(type.Offhand()));
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool UpdateOffhand(ItemManager items, CharacterWeapon weapon)
|
||||
{
|
||||
if (weapon.Value == ModelData.OffHand.Value)
|
||||
return false;
|
||||
|
||||
var (valid, id, name, _) = items.Identify(EquipSlot.OffHand, weapon.Set, weapon.Type, (byte)weapon.Variant, MainhandType);
|
||||
if (!valid || id == OffHandId)
|
||||
return false;
|
||||
|
||||
OffHandId = id;
|
||||
OffhandName = name;
|
||||
ModelData.OffHand.Set = weapon.Set;
|
||||
ModelData.OffHand.Type = weapon.Type;
|
||||
ModelData.OffHand.Variant = weapon.Variant;
|
||||
ModelData.OffHand.Stain = weapon.Stain;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool SetStain(EquipSlot slot, StainId id)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.MainHand => SetIfDifferent(ref ModelData.MainHand.Stain, id),
|
||||
EquipSlot.OffHand => SetIfDifferent(ref ModelData.OffHand.Stain, id),
|
||||
EquipSlot.Head => SetIfDifferent(ref ModelData.Head.Stain, id),
|
||||
EquipSlot.Body => SetIfDifferent(ref ModelData.Body.Stain, id),
|
||||
EquipSlot.Hands => SetIfDifferent(ref ModelData.Hands.Stain, id),
|
||||
EquipSlot.Legs => SetIfDifferent(ref ModelData.Legs.Stain, id),
|
||||
EquipSlot.Feet => SetIfDifferent(ref ModelData.Feet.Stain, id),
|
||||
EquipSlot.Ears => SetIfDifferent(ref ModelData.Ears.Stain, id),
|
||||
EquipSlot.Neck => SetIfDifferent(ref ModelData.Neck.Stain, id),
|
||||
EquipSlot.Wrists => SetIfDifferent(ref ModelData.Wrists.Stain, id),
|
||||
EquipSlot.RFinger => SetIfDifferent(ref ModelData.RFinger.Stain, id),
|
||||
EquipSlot.LFinger => SetIfDifferent(ref ModelData.LFinger.Stain, id),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
internal static bool SetIfDifferent<T>(ref T old, T value) where T : IEquatable<T>
|
||||
{
|
||||
if (old.Equals(value))
|
||||
return false;
|
||||
|
||||
old = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool SetArmor(EquipSlot slot, SetId set, byte variant, string name, uint id)
|
||||
{
|
||||
var changes = false;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.Head:
|
||||
changes |= SetIfDifferent(ref ModelData.Head.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.Head.Variant, variant);
|
||||
changes |= HeadName != name;
|
||||
HeadName = name;
|
||||
changes |= Head != id;
|
||||
Head = id;
|
||||
return changes;
|
||||
case EquipSlot.Body:
|
||||
changes |= SetIfDifferent(ref ModelData.Body.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.Body.Variant, variant);
|
||||
changes |= BodyName != name;
|
||||
BodyName = name;
|
||||
changes |= Body != id;
|
||||
Body = id;
|
||||
return changes;
|
||||
case EquipSlot.Hands:
|
||||
changes |= SetIfDifferent(ref ModelData.Hands.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.Hands.Variant, variant);
|
||||
changes |= HandsName != name;
|
||||
HandsName = name;
|
||||
changes |= Hands != id;
|
||||
Hands = id;
|
||||
return changes;
|
||||
case EquipSlot.Legs:
|
||||
changes |= SetIfDifferent(ref ModelData.Legs.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.Legs.Variant, variant);
|
||||
changes |= LegsName != name;
|
||||
LegsName = name;
|
||||
changes |= Legs != id;
|
||||
Legs = id;
|
||||
return changes;
|
||||
case EquipSlot.Feet:
|
||||
changes |= SetIfDifferent(ref ModelData.Feet.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.Feet.Variant, variant);
|
||||
changes |= FeetName != name;
|
||||
FeetName = name;
|
||||
changes |= Feet != id;
|
||||
Feet = id;
|
||||
return changes;
|
||||
case EquipSlot.Ears:
|
||||
changes |= SetIfDifferent(ref ModelData.Ears.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.Ears.Variant, variant);
|
||||
changes |= EarsName != name;
|
||||
EarsName = name;
|
||||
changes |= Ears != id;
|
||||
Ears = id;
|
||||
return changes;
|
||||
case EquipSlot.Neck:
|
||||
changes |= SetIfDifferent(ref ModelData.Neck.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.Neck.Variant, variant);
|
||||
changes |= NeckName != name;
|
||||
NeckName = name;
|
||||
changes |= Neck != id;
|
||||
Neck = id;
|
||||
return changes;
|
||||
case EquipSlot.Wrists:
|
||||
changes |= SetIfDifferent(ref ModelData.Wrists.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.Wrists.Variant, variant);
|
||||
changes |= WristsName != name;
|
||||
WristsName = name;
|
||||
changes |= Wrists != id;
|
||||
Wrists = id;
|
||||
return changes;
|
||||
case EquipSlot.RFinger:
|
||||
changes |= SetIfDifferent(ref ModelData.RFinger.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.RFinger.Variant, variant);
|
||||
changes |= RFingerName != name;
|
||||
RFingerName = name;
|
||||
changes |= RFinger != id;
|
||||
RFinger = id;
|
||||
return changes;
|
||||
case EquipSlot.LFinger:
|
||||
changes |= SetIfDifferent(ref ModelData.LFinger.Set, set);
|
||||
changes |= SetIfDifferent(ref ModelData.LFinger.Variant, variant);
|
||||
changes |= LFingerName != name;
|
||||
LFingerName = name;
|
||||
changes |= LFinger != id;
|
||||
LFinger = id;
|
||||
return changes;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DesignBase64Migration
|
||||
{
|
||||
public const int Base64Size = 91;
|
||||
|
||||
public static ModelData MigrateBase64(string base64, out EquipFlag equipFlags, out CustomizeFlag customizeFlags,
|
||||
out bool writeinternal, out QuadBool wet, out QuadBool hat, out QuadBool visor, out QuadBool weapon)
|
||||
{
|
||||
static void CheckSize(int length, int requiredLength)
|
||||
{
|
||||
if (length != requiredLength)
|
||||
throw new Exception(
|
||||
$"Can not parse Base64 string into CharacterSave:\n\tInvalid size {length} instead of {requiredLength}.");
|
||||
}
|
||||
|
||||
byte applicationFlags;
|
||||
ushort equipFlagsS;
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
hat = QuadBool.Null;
|
||||
visor = QuadBool.Null;
|
||||
weapon = QuadBool.Null;
|
||||
switch (bytes[0])
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
CheckSize(bytes.Length, 86);
|
||||
applicationFlags = bytes[1];
|
||||
equipFlagsS = BitConverter.ToUInt16(bytes, 2);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
CheckSize(bytes.Length, Base64Size);
|
||||
applicationFlags = bytes[1];
|
||||
equipFlagsS = BitConverter.ToUInt16(bytes, 2);
|
||||
hat = hat.SetValue((bytes[90] & 0x01) == 0);
|
||||
visor = visor.SetValue((bytes[90] & 0x10) != 0);
|
||||
weapon = weapon.SetValue((bytes[90] & 0x02) == 0);
|
||||
break;
|
||||
}
|
||||
default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}.");
|
||||
}
|
||||
|
||||
customizeFlags = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0;
|
||||
wet = (applicationFlags & 0x02) != 0 ? QuadBool.True : QuadBool.NullFalse;
|
||||
hat = hat.SetEnabled((applicationFlags & 0x04) != 0);
|
||||
weapon = weapon.SetEnabled((applicationFlags & 0x08) != 0);
|
||||
visor = visor.SetEnabled((applicationFlags & 0x10) != 0);
|
||||
writeinternal = (applicationFlags & 0x20) != 0;
|
||||
|
||||
equipFlags = 0;
|
||||
equipFlags |= (equipFlagsS & 0x0001) != 0 ? EquipFlag.Mainhand | EquipFlag.MainhandStain : 0;
|
||||
equipFlags |= (equipFlagsS & 0x0002) != 0 ? EquipFlag.Offhand | EquipFlag.OffhandStain : 0;
|
||||
var flag = 0x0002u;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
flag <<= 1;
|
||||
equipFlags |= (equipFlagsS & flag) != 0 ? slot.ToFlag() | slot.ToStainFlag() : 0;
|
||||
}
|
||||
|
||||
var data = new ModelData();
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
data.CustomizeData.Read(ptr + 4);
|
||||
var cur = (CharacterWeapon*)(ptr + 30);
|
||||
data.MainHand = cur[0];
|
||||
data.OffHand = cur[1];
|
||||
var eq = (CharacterArmor*)(cur + 2);
|
||||
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
|
||||
data.Equipment[slot] = eq[idx];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static unsafe string CreateOldBase64(in ModelData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, bool wet, bool hat,
|
||||
bool setHat, bool visor, bool setVisor, bool weapon, bool setWeapon, bool writeinternal, float alpha)
|
||||
{
|
||||
var data = stackalloc byte[Base64Size];
|
||||
data[0] = 2;
|
||||
data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0)
|
||||
| (wet ? 0x02 : 0)
|
||||
| (setHat ? 0x04 : 0)
|
||||
| (setWeapon ? 0x08 : 0)
|
||||
| (setVisor ? 0x10 : 0)
|
||||
| (writeinternal ? 0x20 : 0));
|
||||
data[2] = (byte)((equipFlags.HasFlag(EquipFlag.Mainhand) ? 0x01 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Offhand) ? 0x02 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Head) ? 0x04 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Body) ? 0x08 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Hands) ? 0x10 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Legs) ? 0x20 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Feet) ? 0x40 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Ears) ? 0x80 : 0));
|
||||
data[3] = (byte)((equipFlags.HasFlag(EquipFlag.Neck) ? 0x01 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
|
||||
save.CustomizeData.Write(data + 4);
|
||||
((CharacterWeapon*)(data + 30))[0] = save.MainHand;
|
||||
((CharacterWeapon*)(data + 30))[1] = save.OffHand;
|
||||
((CharacterArmor*)(data + 44))[0] = save.Head;
|
||||
((CharacterArmor*)(data + 44))[1] = save.Body;
|
||||
((CharacterArmor*)(data + 44))[2] = save.Hands;
|
||||
((CharacterArmor*)(data + 44))[3] = save.Legs;
|
||||
((CharacterArmor*)(data + 44))[4] = save.Feet;
|
||||
((CharacterArmor*)(data + 44))[5] = save.Ears;
|
||||
((CharacterArmor*)(data + 44))[6] = save.Neck;
|
||||
((CharacterArmor*)(data + 44))[7] = save.Wrists;
|
||||
((CharacterArmor*)(data + 44))[8] = save.RFinger;
|
||||
((CharacterArmor*)(data + 44))[9] = save.LFinger;
|
||||
*(float*)(data + 84) = 1f;
|
||||
data[88] = (byte)((hat ? 0x01 : 0)
|
||||
| (visor ? 0x10 : 0)
|
||||
| (weapon ? 0x02 : 0));
|
||||
|
||||
return Convert.ToBase64String(new Span<byte>(data, Base64Size));
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ using Dalamud.Plugin;
|
|||
using Glamourer.Services;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Filesystem;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
|
@ -20,9 +19,9 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
|||
|
||||
public readonly string DesignFileSystemFile;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly Design.Manager _designManager;
|
||||
private readonly DesignManager _designManager;
|
||||
|
||||
public DesignFileSystem(Design.Manager designManager, DalamudPluginInterface pi, SaveService saveService)
|
||||
public DesignFileSystem(DesignManager designManager, DalamudPluginInterface pi, SaveService saveService)
|
||||
{
|
||||
DesignFileSystemFile = GetDesignFileSystemFile(pi);
|
||||
_designManager = designManager;
|
||||
|
|
@ -75,11 +74,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
|||
_saveService.QueueSave(this);
|
||||
}
|
||||
|
||||
private void OnDataChange(Design.Manager.DesignChangeType type, Design design, object? data)
|
||||
private void OnDataChange(DesignManager.DesignChangeType type, Design design, object? data)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Design.Manager.DesignChangeType.Created:
|
||||
case DesignManager.DesignChangeType.Created:
|
||||
var originalName = design.Name.Text.FixName();
|
||||
var name = originalName;
|
||||
var counter = 1;
|
||||
|
|
@ -88,14 +87,14 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
|||
|
||||
CreateLeaf(Root, name, design);
|
||||
break;
|
||||
case Design.Manager.DesignChangeType.Deleted:
|
||||
case DesignManager.DesignChangeType.Deleted:
|
||||
if (FindLeaf(design, out var leaf))
|
||||
Delete(leaf);
|
||||
break;
|
||||
case Design.Manager.DesignChangeType.ReloadedAll:
|
||||
case DesignManager.DesignChangeType.ReloadedAll:
|
||||
Reload();
|
||||
break;
|
||||
case Design.Manager.DesignChangeType.Renamed when data is string oldName:
|
||||
case DesignManager.DesignChangeType.Renamed when data is string oldName:
|
||||
var old = oldName.FixName();
|
||||
if (Find(old, out var child) && child is not Folder)
|
||||
Rename(child, design.Name);
|
||||
|
|
|
|||
489
Glamourer/Designs/ModelData.cs
Normal file
489
Glamourer/Designs/ModelData.cs
Normal file
|
|
@ -0,0 +1,489 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
[Flags]
|
||||
public enum ModelFlags : ushort
|
||||
{
|
||||
HatVisible = 0x01,
|
||||
WeaponVisible = 0x02,
|
||||
VisorToggled = 0x04,
|
||||
}
|
||||
|
||||
public struct ModelData
|
||||
{
|
||||
public readonly unsafe ModelData Clone()
|
||||
{
|
||||
var data = new ModelData(MainHand);
|
||||
fixed (void* ptr = &this)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(&data, ptr, sizeof(ModelData));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public Customize Customize = Customize.Default;
|
||||
public ModelFlags Flags = ModelFlags.HatVisible | ModelFlags.WeaponVisible;
|
||||
public CharacterWeapon MainHand;
|
||||
public CharacterWeapon OffHand = CharacterWeapon.Empty;
|
||||
|
||||
public uint ModelId = 0;
|
||||
public CharacterArmor Head = CharacterArmor.Empty;
|
||||
public CharacterArmor Body = CharacterArmor.Empty;
|
||||
public CharacterArmor Hands = CharacterArmor.Empty;
|
||||
public CharacterArmor Legs = CharacterArmor.Empty;
|
||||
public CharacterArmor Feet = CharacterArmor.Empty;
|
||||
public CharacterArmor Ears = CharacterArmor.Empty;
|
||||
public CharacterArmor Neck = CharacterArmor.Empty;
|
||||
public CharacterArmor Wrists = CharacterArmor.Empty;
|
||||
public CharacterArmor RFinger = CharacterArmor.Empty;
|
||||
public CharacterArmor LFinger = CharacterArmor.Empty;
|
||||
|
||||
public ModelData(CharacterWeapon mainHand)
|
||||
=> MainHand = mainHand;
|
||||
|
||||
public CharacterArmor Armor(EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => MainHand.ToArmor(),
|
||||
EquipSlot.OffHand => OffHand.ToArmor(),
|
||||
EquipSlot.Head => Head,
|
||||
EquipSlot.Body => Body,
|
||||
EquipSlot.Hands => Hands,
|
||||
EquipSlot.Legs => Legs,
|
||||
EquipSlot.Feet => Feet,
|
||||
EquipSlot.Ears => Ears,
|
||||
EquipSlot.Neck => Neck,
|
||||
EquipSlot.Wrists => Wrists,
|
||||
EquipSlot.RFinger => RFinger,
|
||||
EquipSlot.LFinger => LFinger,
|
||||
_ => CharacterArmor.Empty,
|
||||
};
|
||||
|
||||
public CharacterWeapon Piece(EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => MainHand,
|
||||
EquipSlot.OffHand => OffHand,
|
||||
EquipSlot.Head => Head.ToWeapon(),
|
||||
EquipSlot.Body => Body.ToWeapon(),
|
||||
EquipSlot.Hands => Hands.ToWeapon(),
|
||||
EquipSlot.Legs => Legs.ToWeapon(),
|
||||
EquipSlot.Feet => Feet.ToWeapon(),
|
||||
EquipSlot.Ears => Ears.ToWeapon(),
|
||||
EquipSlot.Neck => Neck.ToWeapon(),
|
||||
EquipSlot.Wrists => Wrists.ToWeapon(),
|
||||
EquipSlot.RFinger => RFinger.ToWeapon(),
|
||||
EquipSlot.LFinger => LFinger.ToWeapon(),
|
||||
_ => CharacterWeapon.Empty,
|
||||
};
|
||||
|
||||
public bool SetPiece(EquipSlot slot, SetId model, byte variant, out CharacterWeapon ret)
|
||||
{
|
||||
var changes = false;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
changes |= SetIfDifferent(ref MainHand.Set, model);
|
||||
changes |= SetIfDifferent(ref MainHand.Variant, variant);
|
||||
ret = MainHand;
|
||||
return changes;
|
||||
case EquipSlot.OffHand:
|
||||
changes |= SetIfDifferent(ref OffHand.Set, model);
|
||||
changes |= SetIfDifferent(ref OffHand.Variant, variant);
|
||||
ret = OffHand;
|
||||
return changes;
|
||||
case EquipSlot.Head:
|
||||
changes |= SetIfDifferent(ref Head.Set, model);
|
||||
changes |= SetIfDifferent(ref Head.Variant, variant);
|
||||
ret = Head.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Body:
|
||||
changes |= SetIfDifferent(ref Body.Set, model);
|
||||
changes |= SetIfDifferent(ref Body.Variant, variant);
|
||||
ret = Body.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Hands:
|
||||
changes |= SetIfDifferent(ref Hands.Set, model);
|
||||
changes |= SetIfDifferent(ref Hands.Variant, variant);
|
||||
ret = Hands.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Legs:
|
||||
changes |= SetIfDifferent(ref Legs.Set, model);
|
||||
changes |= SetIfDifferent(ref Legs.Variant, variant);
|
||||
ret = Legs.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Feet:
|
||||
changes |= SetIfDifferent(ref Feet.Set, model);
|
||||
changes |= SetIfDifferent(ref Feet.Variant, variant);
|
||||
ret = Feet.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Ears:
|
||||
changes |= SetIfDifferent(ref Ears.Set, model);
|
||||
changes |= SetIfDifferent(ref Ears.Variant, variant);
|
||||
ret = Ears.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Neck:
|
||||
changes |= SetIfDifferent(ref Neck.Set, model);
|
||||
changes |= SetIfDifferent(ref Neck.Variant, variant);
|
||||
ret = Neck.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Wrists:
|
||||
changes |= SetIfDifferent(ref Wrists.Set, model);
|
||||
changes |= SetIfDifferent(ref Wrists.Variant, variant);
|
||||
ret = Wrists.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.RFinger:
|
||||
changes |= SetIfDifferent(ref RFinger.Set, model);
|
||||
changes |= SetIfDifferent(ref RFinger.Variant, variant);
|
||||
ret = RFinger.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.LFinger:
|
||||
changes |= SetIfDifferent(ref LFinger.Set, model);
|
||||
changes |= SetIfDifferent(ref LFinger.Variant, variant);
|
||||
ret = LFinger.ToWeapon();
|
||||
return changes;
|
||||
default:
|
||||
ret = CharacterWeapon.Empty;
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetPiece(EquipSlot slot, SetId model, WeaponType type, byte variant, out CharacterWeapon ret)
|
||||
{
|
||||
var changes = false;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
changes |= SetIfDifferent(ref MainHand.Set, model);
|
||||
changes |= SetIfDifferent(ref MainHand.Type, type);
|
||||
changes |= SetIfDifferent(ref MainHand.Variant, variant);
|
||||
ret = MainHand;
|
||||
return changes;
|
||||
case EquipSlot.OffHand:
|
||||
changes |= SetIfDifferent(ref OffHand.Set, model);
|
||||
changes |= SetIfDifferent(ref OffHand.Type, type);
|
||||
changes |= SetIfDifferent(ref OffHand.Variant, variant);
|
||||
ret = OffHand;
|
||||
return changes;
|
||||
case EquipSlot.Head:
|
||||
changes |= SetIfDifferent(ref Head.Set, model);
|
||||
changes |= SetIfDifferent(ref Head.Variant, variant);
|
||||
ret = Head.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Body:
|
||||
changes |= SetIfDifferent(ref Body.Set, model);
|
||||
changes |= SetIfDifferent(ref Body.Variant, variant);
|
||||
ret = Body.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Hands:
|
||||
changes |= SetIfDifferent(ref Hands.Set, model);
|
||||
changes |= SetIfDifferent(ref Hands.Variant, variant);
|
||||
ret = Hands.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Legs:
|
||||
changes |= SetIfDifferent(ref Legs.Set, model);
|
||||
changes |= SetIfDifferent(ref Legs.Variant, variant);
|
||||
ret = Legs.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Feet:
|
||||
changes |= SetIfDifferent(ref Feet.Set, model);
|
||||
changes |= SetIfDifferent(ref Feet.Variant, variant);
|
||||
ret = Feet.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Ears:
|
||||
changes |= SetIfDifferent(ref Ears.Set, model);
|
||||
changes |= SetIfDifferent(ref Ears.Variant, variant);
|
||||
ret = Ears.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Neck:
|
||||
changes |= SetIfDifferent(ref Neck.Set, model);
|
||||
changes |= SetIfDifferent(ref Neck.Variant, variant);
|
||||
ret = Neck.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Wrists:
|
||||
changes |= SetIfDifferent(ref Wrists.Set, model);
|
||||
changes |= SetIfDifferent(ref Wrists.Variant, variant);
|
||||
ret = Wrists.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.RFinger:
|
||||
changes |= SetIfDifferent(ref RFinger.Set, model);
|
||||
changes |= SetIfDifferent(ref RFinger.Variant, variant);
|
||||
ret = RFinger.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.LFinger:
|
||||
changes |= SetIfDifferent(ref LFinger.Set, model);
|
||||
changes |= SetIfDifferent(ref LFinger.Variant, variant);
|
||||
ret = LFinger.ToWeapon();
|
||||
return changes;
|
||||
default:
|
||||
ret = CharacterWeapon.Empty;
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetPiece(EquipSlot slot, SetId model, byte variant, StainId stain, out CharacterWeapon ret)
|
||||
{
|
||||
var changes = false;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
changes |= SetIfDifferent(ref MainHand.Set, model);
|
||||
changes |= SetIfDifferent(ref MainHand.Variant, variant);
|
||||
changes |= SetIfDifferent(ref MainHand.Stain, stain);
|
||||
ret = MainHand;
|
||||
return changes;
|
||||
case EquipSlot.OffHand:
|
||||
changes |= SetIfDifferent(ref OffHand.Set, model);
|
||||
changes |= SetIfDifferent(ref OffHand.Variant, variant);
|
||||
changes |= SetIfDifferent(ref OffHand.Stain, stain);
|
||||
ret = OffHand;
|
||||
return changes;
|
||||
case EquipSlot.Head:
|
||||
changes |= SetIfDifferent(ref Head.Set, model);
|
||||
changes |= SetIfDifferent(ref Head.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Head.Stain, stain);
|
||||
ret = Head.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Body:
|
||||
changes |= SetIfDifferent(ref Body.Set, model);
|
||||
changes |= SetIfDifferent(ref Body.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Body.Stain, stain);
|
||||
ret = Body.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Hands:
|
||||
changes |= SetIfDifferent(ref Hands.Set, model);
|
||||
changes |= SetIfDifferent(ref Hands.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Hands.Stain, stain);
|
||||
ret = Hands.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Legs:
|
||||
changes |= SetIfDifferent(ref Legs.Set, model);
|
||||
changes |= SetIfDifferent(ref Legs.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Legs.Stain, stain);
|
||||
ret = Legs.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Feet:
|
||||
changes |= SetIfDifferent(ref Feet.Set, model);
|
||||
changes |= SetIfDifferent(ref Feet.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Feet.Stain, stain);
|
||||
ret = Feet.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Ears:
|
||||
changes |= SetIfDifferent(ref Ears.Set, model);
|
||||
changes |= SetIfDifferent(ref Ears.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Ears.Stain, stain);
|
||||
ret = Ears.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Neck:
|
||||
changes |= SetIfDifferent(ref Neck.Set, model);
|
||||
changes |= SetIfDifferent(ref Neck.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Neck.Stain, stain);
|
||||
ret = Neck.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Wrists:
|
||||
changes |= SetIfDifferent(ref Wrists.Set, model);
|
||||
changes |= SetIfDifferent(ref Wrists.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Wrists.Stain, stain);
|
||||
ret = Wrists.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.RFinger:
|
||||
changes |= SetIfDifferent(ref RFinger.Set, model);
|
||||
changes |= SetIfDifferent(ref RFinger.Variant, variant);
|
||||
changes |= SetIfDifferent(ref RFinger.Stain, stain);
|
||||
ret = RFinger.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.LFinger:
|
||||
changes |= SetIfDifferent(ref LFinger.Set, model);
|
||||
changes |= SetIfDifferent(ref LFinger.Variant, variant);
|
||||
changes |= SetIfDifferent(ref LFinger.Stain, stain);
|
||||
ret = LFinger.ToWeapon();
|
||||
return changes;
|
||||
default:
|
||||
ret = CharacterWeapon.Empty;
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetPiece(EquipSlot slot, CharacterWeapon weapon, out CharacterWeapon ret)
|
||||
=> SetPiece(slot, weapon.Set, weapon.Type, (byte)weapon.Variant, weapon.Stain, out ret);
|
||||
|
||||
public bool SetPiece(EquipSlot slot, CharacterArmor armor, out CharacterWeapon ret)
|
||||
=> SetPiece(slot, armor.Set, armor.Variant, armor.Stain, out ret);
|
||||
|
||||
public bool SetPiece(EquipSlot slot, SetId model, WeaponType type, byte variant, StainId stain, out CharacterWeapon ret)
|
||||
{
|
||||
var changes = false;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
changes |= SetIfDifferent(ref MainHand.Set, model);
|
||||
changes |= SetIfDifferent(ref MainHand.Type, type);
|
||||
changes |= SetIfDifferent(ref MainHand.Variant, variant);
|
||||
changes |= SetIfDifferent(ref MainHand.Stain, stain);
|
||||
ret = MainHand;
|
||||
return changes;
|
||||
case EquipSlot.OffHand:
|
||||
changes |= SetIfDifferent(ref OffHand.Set, model);
|
||||
changes |= SetIfDifferent(ref OffHand.Type, type);
|
||||
changes |= SetIfDifferent(ref OffHand.Variant, variant);
|
||||
changes |= SetIfDifferent(ref OffHand.Stain, stain);
|
||||
ret = OffHand;
|
||||
return changes;
|
||||
case EquipSlot.Head:
|
||||
changes |= SetIfDifferent(ref Head.Set, model);
|
||||
changes |= SetIfDifferent(ref Head.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Head.Stain, stain);
|
||||
ret = Head.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Body:
|
||||
changes |= SetIfDifferent(ref Body.Set, model);
|
||||
changes |= SetIfDifferent(ref Body.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Body.Stain, stain);
|
||||
ret = Body.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Hands:
|
||||
changes |= SetIfDifferent(ref Hands.Set, model);
|
||||
changes |= SetIfDifferent(ref Hands.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Hands.Stain, stain);
|
||||
ret = Hands.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Legs:
|
||||
changes |= SetIfDifferent(ref Legs.Set, model);
|
||||
changes |= SetIfDifferent(ref Legs.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Legs.Stain, stain);
|
||||
ret = Legs.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Feet:
|
||||
changes |= SetIfDifferent(ref Feet.Set, model);
|
||||
changes |= SetIfDifferent(ref Feet.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Feet.Stain, stain);
|
||||
ret = Feet.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Ears:
|
||||
changes |= SetIfDifferent(ref Ears.Set, model);
|
||||
changes |= SetIfDifferent(ref Ears.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Ears.Stain, stain);
|
||||
ret = Ears.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Neck:
|
||||
changes |= SetIfDifferent(ref Neck.Set, model);
|
||||
changes |= SetIfDifferent(ref Neck.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Neck.Stain, stain);
|
||||
ret = Neck.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Wrists:
|
||||
changes |= SetIfDifferent(ref Wrists.Set, model);
|
||||
changes |= SetIfDifferent(ref Wrists.Variant, variant);
|
||||
changes |= SetIfDifferent(ref Wrists.Stain, stain);
|
||||
ret = Wrists.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.RFinger:
|
||||
changes |= SetIfDifferent(ref RFinger.Set, model);
|
||||
changes |= SetIfDifferent(ref RFinger.Variant, variant);
|
||||
changes |= SetIfDifferent(ref RFinger.Stain, stain);
|
||||
ret = RFinger.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.LFinger:
|
||||
changes |= SetIfDifferent(ref LFinger.Set, model);
|
||||
changes |= SetIfDifferent(ref LFinger.Variant, variant);
|
||||
changes |= SetIfDifferent(ref LFinger.Stain, stain);
|
||||
ret = LFinger.ToWeapon();
|
||||
return changes;
|
||||
default:
|
||||
ret = CharacterWeapon.Empty;
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
public StainId Stain(EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => MainHand.Stain,
|
||||
EquipSlot.OffHand => OffHand.Stain,
|
||||
EquipSlot.Head => Head.Stain,
|
||||
EquipSlot.Body => Body.Stain,
|
||||
EquipSlot.Hands => Hands.Stain,
|
||||
EquipSlot.Legs => Legs.Stain,
|
||||
EquipSlot.Feet => Feet.Stain,
|
||||
EquipSlot.Ears => Ears.Stain,
|
||||
EquipSlot.Neck => Neck.Stain,
|
||||
EquipSlot.Wrists => Wrists.Stain,
|
||||
EquipSlot.RFinger => RFinger.Stain,
|
||||
EquipSlot.LFinger => LFinger.Stain,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public bool SetStain(EquipSlot slot, StainId stain, out CharacterWeapon ret)
|
||||
{
|
||||
var changes = false;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
changes = SetIfDifferent(ref MainHand.Stain, stain);
|
||||
ret = MainHand;
|
||||
return changes;
|
||||
case EquipSlot.OffHand:
|
||||
changes = SetIfDifferent(ref OffHand.Stain, stain);
|
||||
ret = OffHand;
|
||||
return changes;
|
||||
case EquipSlot.Head:
|
||||
changes = SetIfDifferent(ref Head.Stain, stain);
|
||||
ret = Head.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Body:
|
||||
changes = SetIfDifferent(ref Body.Stain, stain);
|
||||
ret = Body.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Hands:
|
||||
changes = SetIfDifferent(ref Hands.Stain, stain);
|
||||
ret = Hands.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Legs:
|
||||
changes = SetIfDifferent(ref Legs.Stain, stain);
|
||||
ret = Legs.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Feet:
|
||||
changes = SetIfDifferent(ref Feet.Stain, stain);
|
||||
ret = Feet.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Ears:
|
||||
changes = SetIfDifferent(ref Ears.Stain, stain);
|
||||
ret = Ears.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Neck:
|
||||
changes = SetIfDifferent(ref Neck.Stain, stain);
|
||||
ret = Neck.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.Wrists:
|
||||
changes = SetIfDifferent(ref Wrists.Stain, stain);
|
||||
ret = Wrists.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.RFinger:
|
||||
changes = SetIfDifferent(ref RFinger.Stain, stain);
|
||||
ret = RFinger.ToWeapon();
|
||||
return changes;
|
||||
case EquipSlot.LFinger:
|
||||
changes = SetIfDifferent(ref LFinger.Stain, stain);
|
||||
ret = LFinger.ToWeapon();
|
||||
return changes;
|
||||
default:
|
||||
ret = CharacterWeapon.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool SetIfDifferent<T>(ref T old, T value) where T : IEquatable<T>
|
||||
{
|
||||
if (old.Equals(value))
|
||||
return false;
|
||||
|
||||
old = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,19 +16,17 @@ public class DrawObjectManager : IDisposable
|
|||
private readonly ItemManager _items;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ActiveDesign.Manager _manager;
|
||||
private readonly Interop.Interop _interop;
|
||||
private readonly PenumbraAttach _penumbra;
|
||||
|
||||
public DrawObjectManager(ItemManager items, ActorService actors, ActiveDesign.Manager manager, Interop.Interop interop,
|
||||
public DrawObjectManager(ItemManager items, ActorService actors, ActiveDesign.Manager manager,
|
||||
PenumbraAttach penumbra)
|
||||
{
|
||||
_items = items;
|
||||
_actors = actors;
|
||||
_manager = manager;
|
||||
_interop = interop;
|
||||
_penumbra = penumbra;
|
||||
|
||||
_interop.EquipUpdate += FixEquipment;
|
||||
//_interop.EquipUpdate += FixEquipment;
|
||||
_penumbra.CreatingCharacterBase += ApplyActiveDesign;
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +60,7 @@ public class DrawObjectManager : IDisposable
|
|||
var gameObjectCustomize = gameObject.Customize;
|
||||
var customize = new Customize((CustomizeData*)customizePtr);
|
||||
if (gameObjectCustomize.Equals(customize))
|
||||
customize.Load(design.Customize());
|
||||
customize.Load(design.Customize);
|
||||
|
||||
// Compare game object equip data against draw object equip data for transformations.
|
||||
// Apply each piece of equip that should be applied if they correspond.
|
||||
|
|
@ -70,7 +68,7 @@ public class DrawObjectManager : IDisposable
|
|||
var equip = new CharacterEquip((CharacterArmor*)equipDataPtr);
|
||||
if (gameObjectEquip.Equals(equip))
|
||||
{
|
||||
var saveEquip = design.Equipment();
|
||||
var saveEquip = design.Equipment;
|
||||
foreach (var slot in EquipSlotExtensions.EquipmentSlots)
|
||||
{
|
||||
(_, equip[slot]) =
|
||||
|
|
@ -82,6 +80,6 @@ public class DrawObjectManager : IDisposable
|
|||
public void Dispose()
|
||||
{
|
||||
_penumbra.CreatingCharacterBase -= ApplyActiveDesign;
|
||||
_interop.EquipUpdate -= FixEquipment;
|
||||
//_interop.EquipUpdate -= FixEquipment;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Reflection;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -33,6 +34,12 @@ public partial class Glamourer : IDalamudPlugin
|
|||
_services.GetRequiredService<GlamourerWindowSystem>();
|
||||
_services.GetRequiredService<CommandService>();
|
||||
_services.GetRequiredService<GlamourerIpc>();
|
||||
_services.GetRequiredService<ChangeCustomizeService>();
|
||||
_services.GetRequiredService<JobService>();
|
||||
_services.GetRequiredService<UpdateSlotService>();
|
||||
_services.GetRequiredService<VisorService>();
|
||||
_services.GetRequiredService<WeaponService>();
|
||||
_services.GetRequiredService<RedrawManager>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,34 +9,34 @@ namespace Glamourer.Gui.Designs;
|
|||
|
||||
public sealed class DesignFileSystemSelector : FileSystemSelector<Design, DesignFileSystemSelector.DesignState>
|
||||
{
|
||||
private readonly Design.Manager _manager;
|
||||
private readonly DesignManager _designManager;
|
||||
|
||||
public struct DesignState
|
||||
{ }
|
||||
|
||||
public DesignFileSystemSelector(Design.Manager manager, DesignFileSystem fileSystem, KeyState keyState)
|
||||
public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, KeyState keyState)
|
||||
: base(fileSystem, keyState)
|
||||
{
|
||||
_manager = manager;
|
||||
_manager.DesignChange += OnDesignChange;
|
||||
_designManager = designManager;
|
||||
_designManager.DesignChange += OnDesignChange;
|
||||
AddButton(DeleteButton, 1000);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
_manager.DesignChange -= OnDesignChange;
|
||||
_designManager.DesignChange -= OnDesignChange;
|
||||
}
|
||||
|
||||
private void OnDesignChange(Design.Manager.DesignChangeType type, Design design, object? oldData)
|
||||
private void OnDesignChange(DesignManager.DesignChangeType type, Design design, object? oldData)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Design.Manager.DesignChangeType.ReloadedAll:
|
||||
case Design.Manager.DesignChangeType.Renamed:
|
||||
case Design.Manager.DesignChangeType.AddedTag:
|
||||
case Design.Manager.DesignChangeType.ChangedTag:
|
||||
case Design.Manager.DesignChangeType.RemovedTag:
|
||||
case DesignManager.DesignChangeType.ReloadedAll:
|
||||
case DesignManager.DesignChangeType.Renamed:
|
||||
case DesignManager.DesignChangeType.AddedTag:
|
||||
case DesignManager.DesignChangeType.ChangedTag:
|
||||
case DesignManager.DesignChangeType.RemovedTag:
|
||||
SetFilterDirty();
|
||||
break;
|
||||
}
|
||||
|
|
@ -54,6 +54,6 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
|||
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
|
||||
&& Selected != null)
|
||||
_manager.Delete(Selected);
|
||||
_designManager.Delete(Selected);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ public partial class Interface
|
|||
|
||||
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
|
||||
private ActorData _currentData = ActorData.Invalid;
|
||||
private string _currentLabel = string.Empty;
|
||||
private ActiveDesign? _currentSave;
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -52,8 +51,6 @@ public partial class Interface
|
|||
DrawActorSelector();
|
||||
if (!_objects.TryGetValue(_identifier, out _currentData))
|
||||
_currentData = ActorData.Invalid;
|
||||
else
|
||||
_currentLabel = _currentData.Label;
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
|
|
@ -65,7 +62,6 @@ public partial class Interface
|
|||
if (_identifier == ActorIdentifier.Invalid)
|
||||
return;
|
||||
|
||||
|
||||
using var group = ImRaii.Group();
|
||||
DrawPanelHeader();
|
||||
using var child = ImRaii.Child("##ActorPanel", -Vector2.One, true);
|
||||
|
|
@ -76,7 +72,7 @@ public partial class Interface
|
|||
_currentSave.Initialize(_items, _currentData.Objects[0]);
|
||||
|
||||
RevertButton();
|
||||
if (_main._customizationDrawer.Draw(_currentSave.Customize(), _identifier.Type == IdentifierType.Special))
|
||||
if (_main._customizationDrawer.Draw(_currentSave.Customize, _identifier.Type == IdentifierType.Special))
|
||||
_activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData,
|
||||
false);
|
||||
|
||||
|
|
@ -86,8 +82,8 @@ public partial class Interface
|
|||
if (_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain))
|
||||
_activeDesigns.ChangeStain(_currentSave, slot, stain.RowIndex, false);
|
||||
ImGui.SameLine();
|
||||
if (_main._equipmentDrawer.DrawArmor(current, slot, out var armor, _currentSave.Customize().Gender,
|
||||
_currentSave.Customize().Race))
|
||||
if (_main._equipmentDrawer.DrawArmor(current, slot, out var armor, _currentSave.Customize.Gender,
|
||||
_currentSave.Customize.Race))
|
||||
_activeDesigns.ChangeEquipment(_currentSave, slot, armor, false);
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +162,7 @@ public partial class Interface
|
|||
.Push(ImGuiCol.ButtonActive, buttonColor);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX);
|
||||
ImGui.Button($"{_currentData.Label}##playerHeader", -Vector2.UnitX);
|
||||
}
|
||||
|
||||
//private void DrawActorPanel()
|
||||
|
|
@ -244,10 +240,18 @@ public partial class Interface
|
|||
return;
|
||||
|
||||
_objects.Update();
|
||||
if (!_activeDesigns.TryGetValue(_identifier, out _currentSave))
|
||||
_currentSave = null;
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
|
||||
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
|
||||
var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable);
|
||||
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
|
||||
if (_currentSave == null)
|
||||
{
|
||||
_identifier = ActorIdentifier.Invalid;
|
||||
_currentData = ActorData.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckFilter(KeyValuePair<ActorIdentifier, ActorData> pair)
|
||||
|
|
@ -256,10 +260,11 @@ public partial class Interface
|
|||
private void DrawSelectable(KeyValuePair<ActorIdentifier, ActorData> pair)
|
||||
{
|
||||
var equal = pair.Key.Equals(_identifier);
|
||||
if (ImGui.Selectable(pair.Value.Label, equal) && !equal)
|
||||
if (ImGui.Selectable(pair.Value.Label, equal) || equal)
|
||||
{
|
||||
_identifier = pair.Key.CreatePermanent();
|
||||
_currentData = pair.Value;
|
||||
if (!_activeDesigns.TryGetValue(_identifier, out _currentSave))
|
||||
_currentSave = _currentData.Valid ? _activeDesigns.GetOrCreateSave(_currentData.Objects[0]) : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,16 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using Microsoft.VisualBasic;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
@ -18,14 +24,19 @@ public partial class Interface
|
|||
public readonly DesignFileSystemSelector Selector;
|
||||
private readonly Interface _main;
|
||||
private readonly DesignFileSystem _fileSystem;
|
||||
private readonly Design.Manager _manager;
|
||||
private readonly DesignManager _designManager;
|
||||
private readonly ActiveDesign.Manager _activeDesignManager;
|
||||
private readonly ObjectManager _objects;
|
||||
|
||||
public DesignTab(Interface main, Design.Manager manager, DesignFileSystem fileSystem, KeyState keyState)
|
||||
public DesignTab(Interface main, DesignManager designManager, DesignFileSystem fileSystem, KeyState keyState,
|
||||
ActiveDesign.Manager activeDesignManager, ObjectManager objects)
|
||||
{
|
||||
_main = main;
|
||||
_manager = manager;
|
||||
_designManager = designManager;
|
||||
_fileSystem = fileSystem;
|
||||
Selector = new DesignFileSystemSelector(manager, fileSystem, keyState);
|
||||
_activeDesignManager = activeDesignManager;
|
||||
_objects = objects;
|
||||
Selector = new DesignFileSystemSelector(designManager, fileSystem, keyState);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -45,13 +56,30 @@ public partial class Interface
|
|||
public float GetDesignSelectorSize()
|
||||
=> 200f * ImGuiHelpers.GlobalScale;
|
||||
|
||||
public void DrawDesignPanel()
|
||||
private void ApplySelfButton()
|
||||
{
|
||||
using var child = ImRaii.Child("##DesignPanel", new Vector2(-0.001f), true, ImGuiWindowFlags.HorizontalScrollbar);
|
||||
if (!child || Selector.Selected == null)
|
||||
var self = _objects.Player;
|
||||
if (!ImGuiUtil.DrawDisabledButton("Apply to Self", Vector2.Zero, string.Empty, !self.Valid))
|
||||
return;
|
||||
|
||||
_main._customizationDrawer.Draw(Selector.Selected.Customize(), CustomizeFlagExtensions.All, true);
|
||||
var design = _activeDesignManager.GetOrCreateSave(self);
|
||||
_activeDesignManager.ApplyDesign(design, Selector.Selected!, false);
|
||||
}
|
||||
|
||||
public void DrawDesignPanel()
|
||||
{
|
||||
if (Selector.Selected == null)
|
||||
return;
|
||||
|
||||
using var group = ImRaii.Group();
|
||||
|
||||
ApplySelfButton();
|
||||
|
||||
using var child = ImRaii.Child("##DesignPanel", new Vector2(-0.001f), true, ImGuiWindowFlags.HorizontalScrollbar);
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
_main._customizationDrawer.Draw(Selector.Selected.Customize, CustomizeFlagExtensions.All, true);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var current = Selector.Selected.Armor(slot);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public partial class Interface : Window, IDisposable
|
|||
private readonly DebugStateTab _debugStateTab;
|
||||
private readonly DebugDataTab _debugDataTab;
|
||||
|
||||
public Interface(DalamudPluginInterface pi, ItemManager items, ActiveDesign.Manager activeDesigns, Design.Manager manager,
|
||||
public Interface(DalamudPluginInterface pi, ItemManager items, ActiveDesign.Manager activeDesigns, DesignManager designManager,
|
||||
DesignFileSystem fileSystem, ObjectManager objects, CustomizationService customization, Configuration config, DataManager gameData, TargetManager targets, ActorService actors, KeyState keyState)
|
||||
: base(GetLabel())
|
||||
{
|
||||
|
|
@ -46,7 +46,7 @@ public partial class Interface : Window, IDisposable
|
|||
_actorTab = new ActorTab(this, activeDesigns, objects, targets, actors, items);
|
||||
_debugStateTab = new DebugStateTab(activeDesigns);
|
||||
_debugDataTab = new DebugDataTab(customization);
|
||||
_designTab = new DesignTab(this, manager, fileSystem, keyState);
|
||||
_designTab = new DesignTab(this, designManager, fileSystem, keyState, activeDesigns, objects);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
|||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Pointer != null ? Utf8Name.ToString() : "Invalid";
|
||||
|
||||
public bool IsAvailable
|
||||
=> Pointer->GameObject.GetIsTargetable();
|
||||
|
||||
|
|
|
|||
24
Glamourer/Interop/ChangeCustomizeService.cs
Normal file
24
Glamourer/Interop/ChangeCustomizeService.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe class ChangeCustomizeService
|
||||
{
|
||||
public ChangeCustomizeService()
|
||||
=> SignatureHelper.Initialise(this);
|
||||
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature(Sigs.ChangeCustomize)]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||
{
|
||||
if (customize.Data == null || !actor.Valid || !actor.DrawObject.Valid)
|
||||
return false;
|
||||
|
||||
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
|
||||
}
|
||||
}
|
||||
43
Glamourer/Interop/JobService.cs
Normal file
43
Glamourer/Interop/JobService.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Glamourer.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public class JobService : IDisposable
|
||||
{
|
||||
public readonly IReadOnlyDictionary<byte, Job> Jobs;
|
||||
public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
|
||||
|
||||
public event Action<Actor, Job>? JobChanged;
|
||||
|
||||
public JobService(DataManager gameData)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
Jobs = GameData.Jobs(gameData);
|
||||
JobGroups = GameData.JobGroups(gameData);
|
||||
_changeJobHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_changeJobHook.Dispose();
|
||||
}
|
||||
|
||||
private delegate void ChangeJobDelegate(nint data, uint job);
|
||||
|
||||
[Signature(Sigs.ChangeJob, DetourName = nameof(ChangeJobDetour))]
|
||||
private readonly Hook<ChangeJobDelegate> _changeJobHook = null!;
|
||||
|
||||
private void ChangeJobDetour(nint data, uint jobIndex)
|
||||
{
|
||||
_changeJobHook.Original(data, jobIndex);
|
||||
var actor = (Actor)(data - Offsets.Character.ClassJobContainer);
|
||||
var job = Jobs[(byte)jobIndex];
|
||||
Glamourer.Log.Excessive($"{actor} changed job to {job}");
|
||||
JobChanged?.Invoke(actor, job);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Glamourer.Designs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
private delegate ulong FlagSlotForUpdateDelegate(nint drawObject, uint slot, CharacterArmor* data);
|
||||
|
||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
private readonly Hook<FlagSlotForUpdateDelegate> _flagSlotForUpdateHook = null!;
|
||||
|
||||
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
=> FlagSlotForUpdateDetourBase(drawObject.Address, slot.ToIndex(), &data, true);
|
||||
|
||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var armor = drawObject.Equip[slot] with { Stain = stain};
|
||||
UpdateSlot(drawObject, slot, armor);
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
=> FlagSlotForUpdateDetourBase(drawObject, slotIdx, data, false);
|
||||
|
||||
private ulong FlagSlotForUpdateDetourBase(nint drawObject, uint slotIdx, CharacterArmor* data, bool manual)
|
||||
{
|
||||
// try
|
||||
// {
|
||||
// var slot = slotIdx.ToEquipSlot();
|
||||
// Glamourer.Log.Verbose(
|
||||
// $"Flagged slot {slot} of 0x{(ulong)drawObject:X} for update with {data->Set.Value}-{data->Variant} (Stain {data->Stain.Value}).");
|
||||
// HandleEquipUpdate(drawObject, slot, ref *data, manual);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Glamourer.Log.Error($"Error invoking SlotUpdate:\n{ex}");
|
||||
// }
|
||||
//
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
|
||||
//try
|
||||
//{
|
||||
// var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
// var identifier = actor.GetIdentifier();
|
||||
//
|
||||
// if (_fixedDesigns.TryGetDesign(identifier, out var design))
|
||||
// {
|
||||
// PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||
// (var replaced, *data) =
|
||||
// Glamourer.Items.RestrictedGear.ResolveRestricted(design.Armor(slot).Model, slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
||||
// }
|
||||
// else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
// {
|
||||
// PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
||||
// (var replaced, *data) =
|
||||
// Glamourer.Items.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
||||
// save2.Data.Equipment[slot] = *data;
|
||||
// }
|
||||
//}
|
||||
//catch (Exception e)
|
||||
//{
|
||||
// PluginLog.Error($"Error on loading new gear:\n{e}");
|
||||
//}
|
||||
//
|
||||
//return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
|
||||
|
||||
public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject,
|
||||
byte unk4);
|
||||
|
||||
// Weapons for a specific character are reloaded with this function.
|
||||
// The first argument is a pointer to the game object but shifted a bit inside.
|
||||
// slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data.
|
||||
// weapon argument is the new weapon data.
|
||||
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
|
||||
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
|
||||
// unk4 seemed to be the same as unk1.
|
||||
[Signature(Penumbra.GameData.Sigs.WeaponReload, DetourName = nameof(LoadWeaponDetour))]
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||
byte unk4)
|
||||
{
|
||||
var oldWeapon = weapon;
|
||||
var character = (Actor)(characterOffset - CharacterWeaponOffset);
|
||||
try
|
||||
{
|
||||
var identifier = character.GetIdentifier(_actors.AwaitedService);
|
||||
if (_fixedDesignManager.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||
weapon = slot switch
|
||||
{
|
||||
0 => save.WeaponMain.Model.Value,
|
||||
1 => save.WeaponOff.Model.Value,
|
||||
_ => weapon,
|
||||
};
|
||||
}
|
||||
else if (redrawOnEquality == 1 && _stateManager.TryGetValue(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||
//switch (slot)
|
||||
//{
|
||||
// case 0:
|
||||
// save2.MainHand = new CharacterWeapon(weapon);
|
||||
// break;
|
||||
// case 1:
|
||||
// save2.Data.OffHand = new CharacterWeapon(weapon);
|
||||
// break;
|
||||
//}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new weapon:\n{e}");
|
||||
}
|
||||
|
||||
// First call the regular function.
|
||||
_loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
// If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
|
||||
if (oldWeapon != weapon)
|
||||
_loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
// If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
|
||||
else if (slot != 1 && character.OffHand.Value == 0)
|
||||
_loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
}
|
||||
|
||||
// Load a specific weapon for a character by its data and slot.
|
||||
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.BothHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
// function can also be called with '2', but does not seem to ever be.
|
||||
}
|
||||
}
|
||||
|
||||
// Load specific Main- and Offhand weapons.
|
||||
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
||||
{
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 1, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 1, 0, 1, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,224 +1,45 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public partial class Interop : IDisposable
|
||||
{
|
||||
private readonly JobService _jobService;
|
||||
|
||||
public Interop(JobService jobService)
|
||||
{
|
||||
_jobService = jobService;
|
||||
SignatureHelper.Initialise(this);
|
||||
_changeJobHook.Enable();
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
_setupVisorHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_changeJobHook.Dispose();
|
||||
_flagSlotForUpdateHook.Dispose();
|
||||
_setupVisorHook.Dispose();
|
||||
}
|
||||
|
||||
public static unsafe bool GetVisorState(nint humanPtr)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
return (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
}
|
||||
|
||||
public static unsafe void SetVisorState(nint humanPtr, bool on)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
var state = (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
if (state == on)
|
||||
return;
|
||||
|
||||
var newFlag = (byte)(on ? *flags | Offsets.DrawObjectVisorStateFlag : *flags & ~Offsets.DrawObjectVisorStateFlag);
|
||||
*flags = (byte)(newFlag | Offsets.DrawObjectVisorToggleFlag);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Interop
|
||||
{
|
||||
private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, bool on);
|
||||
public delegate void UpdateVisorDelegate(DrawObject human, SetId modelId, ref bool on);
|
||||
|
||||
[Signature(Penumbra.GameData.Sigs.SetupVisor, DetourName = nameof(SetupVisorDetour))]
|
||||
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook = null!;
|
||||
|
||||
public event UpdateVisorDelegate? VisorUpdate;
|
||||
|
||||
private void SetupVisorDetour(nint humanPtr, ushort modelId, bool on)
|
||||
{
|
||||
InvokeVisorEvent(humanPtr, modelId, ref on);
|
||||
_setupVisorHook.Original(humanPtr, modelId, on);
|
||||
}
|
||||
|
||||
private void InvokeVisorEvent(DrawObject drawObject, SetId modelId, ref bool on)
|
||||
{
|
||||
if (VisorUpdate == null)
|
||||
{
|
||||
Glamourer.Log.Verbose($"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var initialValue = on;
|
||||
foreach (var del in VisorUpdate.GetInvocationList().OfType<UpdateVisorDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
del(drawObject, modelId, ref on);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not invoke {nameof(VisorUpdate)} Subscriber:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Glamourer.Log.Verbose(
|
||||
$"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}, initial call was {initialValue}.");
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial class Interop
|
||||
{
|
||||
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
|
||||
public delegate void FlagSlotForUpdateDelegate(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item);
|
||||
|
||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
|
||||
|
||||
public event FlagSlotForUpdateDelegate? EquipUpdate;
|
||||
|
||||
public ulong FlagSlotForUpdateInterop(DrawObject drawObject, EquipSlot slot, CharacterArmor armor)
|
||||
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
|
||||
|
||||
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
{
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref data);
|
||||
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||
}
|
||||
|
||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var armor = drawObject.Equip[slot] with { Stain = stain };
|
||||
UpdateSlot(drawObject, slot, armor);
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref *data);
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor)
|
||||
{
|
||||
if (EquipUpdate == null)
|
||||
{
|
||||
Glamourer.Log.Verbose(
|
||||
$"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var iv = armor;
|
||||
foreach (var del in EquipUpdate.GetInvocationList().OfType<FlagSlotForUpdateDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
del(drawObject, slot, ref armor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not invoke {nameof(EquipUpdate)} Subscriber:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Glamourer.Log.Verbose(
|
||||
$"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}, initial armor was {iv.Set.Value}-{iv.Variant} with stain {iv.Stain.Value}.");
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial class Interop
|
||||
{
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature(Sigs.ChangeCustomize)]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||
{
|
||||
Debug.Assert(customize.Data != null, "Customize was invalid.");
|
||||
|
||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||
return false;
|
||||
|
||||
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Interop
|
||||
{
|
||||
private delegate void ChangeJobDelegate(IntPtr data, uint job);
|
||||
|
||||
[Signature(Sigs.ChangeJob, DetourName = nameof(ChangeJobDetour))]
|
||||
private readonly Hook<ChangeJobDelegate> _changeJobHook = null!;
|
||||
|
||||
private void ChangeJobDetour(IntPtr data, uint job)
|
||||
{
|
||||
_changeJobHook.Original(data, job);
|
||||
JobChanged?.Invoke(data - Offsets.Character.ClassJobContainer, _jobService.Jobs[(byte)job]);
|
||||
}
|
||||
|
||||
public event Action<Actor, Job>? JobChanged;
|
||||
}
|
||||
|
||||
public unsafe partial class RedrawManager : IDisposable
|
||||
{
|
||||
private readonly ItemManager _items;
|
||||
private readonly ActorService _actors;
|
||||
private readonly FixedDesignManager _fixedDesignManager;
|
||||
private readonly ActiveDesign.Manager _stateManager;
|
||||
private readonly PenumbraAttach _penumbra;
|
||||
private readonly WeaponService _weapons;
|
||||
|
||||
public RedrawManager(FixedDesignManager fixedDesignManager, ActiveDesign.Manager stateManager, ItemManager items, ActorService actors)
|
||||
public RedrawManager(FixedDesignManager fixedDesignManager, ActiveDesign.Manager stateManager, ItemManager items, ActorService actors,
|
||||
PenumbraAttach penumbra, WeaponService weapons)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_fixedDesignManager = fixedDesignManager;
|
||||
_stateManager = stateManager;
|
||||
_items = items;
|
||||
_actors = actors;
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
_loadWeaponHook.Enable();
|
||||
_penumbra = penumbra;
|
||||
_weapons = weapons;
|
||||
|
||||
_penumbra.CreatingCharacterBase += OnCharacterRedraw;
|
||||
_penumbra.CreatedCharacterBase += OnCharacterRedrawFinished;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_flagSlotForUpdateHook.Dispose();
|
||||
_loadWeaponHook.Dispose();
|
||||
}
|
||||
|
||||
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
||||
|
|
@ -230,21 +51,21 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
|
||||
// Check if we have a current design in use, or if not if the actor has a fixed design.
|
||||
var identifier = actor.GetIdentifier(_actors.AwaitedService);
|
||||
if (!(_stateManager.TryGetValue(identifier, out var save) || _fixedDesignManager.TryGetDesign(identifier, out var save2)))
|
||||
if (!_stateManager.TryGetValue(identifier, out var save))
|
||||
return;
|
||||
|
||||
// Compare game object customize data against draw object customize data for transformations.
|
||||
// Apply customization if they correspond and there is customization to apply.
|
||||
var gameObjectCustomize = new Customize((CustomizeData*)actor.Pointer->CustomizeData);
|
||||
if (gameObjectCustomize.Equals(customize))
|
||||
customize.Load(save!.Customize());
|
||||
customize.Load(save!.Customize);
|
||||
|
||||
// Compare game object equip data against draw object equip data for transformations.
|
||||
// Apply each piece of equip that should be applied if they correspond.
|
||||
var gameObjectEquip = new CharacterEquip((CharacterArmor*)actor.Pointer->EquipSlotData);
|
||||
if (gameObjectEquip.Equals(equip))
|
||||
{
|
||||
var saveEquip = save!.Equipment();
|
||||
var saveEquip = save!.Equipment;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
(_, equip[slot]) =
|
||||
|
|
|
|||
77
Glamourer/Interop/UpdateSlotService.cs
Normal file
77
Glamourer/Interop/UpdateSlotService.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe class UpdateSlotService : IDisposable
|
||||
{
|
||||
public UpdateSlotService()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _flagSlotForUpdateHook.Dispose();
|
||||
|
||||
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
|
||||
public delegate void FlagSlotForUpdateDelegate(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item);
|
||||
|
||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
|
||||
|
||||
public event FlagSlotForUpdateDelegate? EquipUpdate;
|
||||
|
||||
public ulong FlagSlotForUpdateInterop(DrawObject drawObject, EquipSlot slot, CharacterArmor armor)
|
||||
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
|
||||
|
||||
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
{
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref data);
|
||||
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||
}
|
||||
|
||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var armor = drawObject.Equip[slot] with { Stain = stain };
|
||||
UpdateSlot(drawObject, slot, armor);
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref *data);
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor)
|
||||
{
|
||||
if (EquipUpdate == null)
|
||||
{
|
||||
Glamourer.Log.Excessive(
|
||||
$"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var iv = armor;
|
||||
foreach (var del in EquipUpdate.GetInvocationList().OfType<FlagSlotForUpdateDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
del(drawObject, slot, ref armor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not invoke {nameof(EquipUpdate)} Subscriber:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Glamourer.Log.Excessive(
|
||||
$"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}, initial armor was {iv.Set.Value}-{iv.Variant} with stain {iv.Stain.Value}.");
|
||||
}
|
||||
}
|
||||
78
Glamourer/Interop/VisorService.cs
Normal file
78
Glamourer/Interop/VisorService.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public class VisorService : IDisposable
|
||||
{
|
||||
public VisorService()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_setupVisorHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _setupVisorHook.Dispose();
|
||||
|
||||
public static unsafe bool GetVisorState(nint humanPtr)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
return (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
}
|
||||
|
||||
public unsafe void SetVisorState(nint humanPtr, bool on)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
_setupVisorHook.Original(humanPtr, (ushort) data->HeadSetID, on);
|
||||
}
|
||||
|
||||
private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, bool on);
|
||||
public delegate void UpdateVisorDelegate(DrawObject human, SetId modelId, ref bool on);
|
||||
|
||||
[Signature(Penumbra.GameData.Sigs.SetupVisor, DetourName = nameof(SetupVisorDetour))]
|
||||
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook = null!;
|
||||
|
||||
public event UpdateVisorDelegate? VisorUpdate;
|
||||
|
||||
private void SetupVisorDetour(nint humanPtr, ushort modelId, bool on)
|
||||
{
|
||||
InvokeVisorEvent(humanPtr, modelId, ref on);
|
||||
_setupVisorHook.Original(humanPtr, modelId, on);
|
||||
}
|
||||
|
||||
private void InvokeVisorEvent(DrawObject drawObject, SetId modelId, ref bool on)
|
||||
{
|
||||
if (VisorUpdate == null)
|
||||
{
|
||||
Glamourer.Log.Excessive($"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var initialValue = on;
|
||||
foreach (var del in VisorUpdate.GetInvocationList().OfType<UpdateVisorDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
del(drawObject, modelId, ref on);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not invoke {nameof(VisorUpdate)} Subscriber:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Glamourer.Log.Excessive(
|
||||
$"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}, initial call was {initialValue}.");
|
||||
}
|
||||
}
|
||||
120
Glamourer/Interop/WeaponService.cs
Normal file
120
Glamourer/Interop/WeaponService.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe class WeaponService : IDisposable
|
||||
{
|
||||
public WeaponService()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_loadWeaponHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loadWeaponHook.Dispose();
|
||||
}
|
||||
|
||||
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
|
||||
|
||||
public delegate void LoadWeaponDelegate(nint offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject,
|
||||
byte unk4);
|
||||
|
||||
// Weapons for a specific character are reloaded with this function.
|
||||
// The first argument is a pointer to the game object but shifted a bit inside.
|
||||
// slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data.
|
||||
// weapon argument is the new weapon data.
|
||||
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
|
||||
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
|
||||
// unk4 seemed to be the same as unk1.
|
||||
[Signature(Penumbra.GameData.Sigs.WeaponReload, DetourName = nameof(LoadWeaponDetour))]
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(nint characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||
byte unk4)
|
||||
{
|
||||
//var oldWeapon = weapon;
|
||||
//var character = (Actor)(characterOffset - CharacterWeaponOffset);
|
||||
//try
|
||||
//{
|
||||
// var identifier = character.GetIdentifier(_actors.AwaitedService);
|
||||
// if (_fixedDesignManager.TryGetDesign(identifier, out var save))
|
||||
// {
|
||||
// PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||
// weapon = slot switch
|
||||
// {
|
||||
// 0 => save.WeaponMain.Model.Value,
|
||||
// 1 => save.WeaponOff.Model.Value,
|
||||
// _ => weapon,
|
||||
// };
|
||||
// }
|
||||
// else if (redrawOnEquality == 1 && _stateManager.TryGetValue(identifier, out var save2))
|
||||
// {
|
||||
// PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||
// //switch (slot)
|
||||
// //{
|
||||
// // case 0:
|
||||
// // save2.MainHand = new CharacterWeapon(weapon);
|
||||
// // break;
|
||||
// // case 1:
|
||||
// // save2.Data.OffHand = new CharacterWeapon(weapon);
|
||||
// // break;
|
||||
// //}
|
||||
// }
|
||||
//}
|
||||
//catch (Exception e)
|
||||
//{
|
||||
// PluginLog.Error($"Error on loading new weapon:\n{e}");
|
||||
//}
|
||||
|
||||
// First call the regular function.
|
||||
_loadWeaponHook.Original(characterOffset, slot, weapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
Glamourer.Log.Excessive($"Weapon reloaded for {(Actor)(characterOffset - CharacterWeaponOffset)} with attributes {slot} {weapon:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}");
|
||||
// // If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
|
||||
// if (oldWeapon != weapon)
|
||||
// _loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
// // If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
|
||||
// else if (slot != 1 && character.OffHand.Value == 0)
|
||||
// _loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
}
|
||||
|
||||
// Load a specific weapon for a character by its data and slot.
|
||||
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.BothHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
// function can also be called with '2', but does not seem to ever be.
|
||||
}
|
||||
}
|
||||
|
||||
// Load specific Main- and Offhand weapons.
|
||||
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
||||
{
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 1, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 1, 0, 1, 0);
|
||||
}
|
||||
|
||||
public void LoadStain(Actor character, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var weapon = slot == EquipSlot.OffHand ? character.OffHand : character.MainHand;
|
||||
weapon.Stain = stain;
|
||||
LoadWeapon(character, slot, weapon);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud.Data;
|
||||
|
||||
namespace Glamourer.Services;
|
||||
|
||||
public class JobService
|
||||
{
|
||||
public readonly IReadOnlyDictionary<byte, Structs.Job> Jobs;
|
||||
public readonly IReadOnlyDictionary<ushort, Structs.JobGroup> JobGroups;
|
||||
|
||||
public JobService(DataManager gameData)
|
||||
{
|
||||
Jobs = GameData.Jobs(gameData);
|
||||
JobGroups = GameData.JobGroups(gameData);
|
||||
}
|
||||
}
|
||||
|
|
@ -53,18 +53,22 @@ public static class ServiceManager
|
|||
.AddSingleton<ActorService>()
|
||||
.AddSingleton<ItemService>()
|
||||
.AddSingleton<ItemManager>()
|
||||
.AddSingleton<CustomizationService>()
|
||||
.AddSingleton<JobService>();
|
||||
.AddSingleton<CustomizationService>();
|
||||
|
||||
private static IServiceCollection AddInterop(this IServiceCollection services)
|
||||
=> services.AddSingleton<Interop.Interop>()
|
||||
=> services.AddSingleton<ChangeCustomizeService>()
|
||||
.AddSingleton<JobService>()
|
||||
.AddSingleton<UpdateSlotService>()
|
||||
.AddSingleton<VisorService>()
|
||||
.AddSingleton<WeaponService>()
|
||||
.AddSingleton<ObjectManager>();
|
||||
|
||||
private static IServiceCollection AddDesigns(this IServiceCollection services)
|
||||
=> services.AddSingleton<Design.Manager>()
|
||||
=> services.AddSingleton<DesignManager>()
|
||||
.AddSingleton<DesignFileSystem>()
|
||||
.AddSingleton<ActiveDesign.Manager>()
|
||||
.AddSingleton<FixedDesignManager>();
|
||||
.AddSingleton<FixedDesignManager>()
|
||||
.AddSingleton<RedrawManager>();
|
||||
|
||||
private static IServiceCollection AddInterface(this IServiceCollection services)
|
||||
=> services.AddSingleton<Interface>()
|
||||
|
|
|
|||
|
|
@ -14,28 +14,44 @@ using Penumbra.Api.Enums;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public sealed partial class ActiveDesign
|
||||
{
|
||||
public partial class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
|
||||
[Flags]
|
||||
public enum ChangeType
|
||||
{
|
||||
Default = 0x00,
|
||||
Changed = 0x01,
|
||||
Fixed = 0x02,
|
||||
}
|
||||
|
||||
public class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
|
||||
{
|
||||
private readonly ActorService _actors;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly Interop.Interop _interop;
|
||||
private readonly PenumbraAttach _penumbra;
|
||||
private readonly ItemManager _items;
|
||||
private readonly VisorService _visor;
|
||||
private readonly ChangeCustomizeService _customize;
|
||||
private readonly UpdateSlotService _updateSlot;
|
||||
private readonly WeaponService _weaponService;
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
|
||||
|
||||
public Manager(ActorService actors, ObjectManager objects, Interop.Interop interop, PenumbraAttach penumbra, ItemManager items)
|
||||
public Manager(ActorService actors, ObjectManager objects, PenumbraAttach penumbra, ItemManager items, VisorService visor,
|
||||
ChangeCustomizeService customize, UpdateSlotService updateSlot, WeaponService weaponService)
|
||||
{
|
||||
_actors = actors;
|
||||
_objects = objects;
|
||||
_interop = interop;
|
||||
_penumbra = penumbra;
|
||||
_items = items;
|
||||
_visor = visor;
|
||||
_customize = customize;
|
||||
_updateSlot = updateSlot;
|
||||
_weaponService = weaponService;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
|
||||
|
|
@ -99,15 +115,25 @@ public sealed partial class ActiveDesign
|
|||
return;
|
||||
|
||||
if (from.DoApplyEquip(EquipSlot.MainHand))
|
||||
ChangeMainHand(to, from.MainHand, fromFixed);
|
||||
ChangeMainHand(to, from.MainHandId, fromFixed);
|
||||
if (from.DoApplyStain(EquipSlot.MainHand))
|
||||
ChangeStain(to, EquipSlot.MainHand, from.WeaponMain.Stain, fromFixed);
|
||||
|
||||
if (from.DoApplyEquip(EquipSlot.OffHand))
|
||||
ChangeOffHand(to, from.OffHand, fromFixed);
|
||||
ChangeOffHand(to, from.OffHandId, fromFixed);
|
||||
if (from.DoApplyStain(EquipSlot.OffHand))
|
||||
ChangeStain(to, EquipSlot.OffHand, from.WeaponOff.Stain, fromFixed);
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Where(from.DoApplyEquip))
|
||||
ChangeEquipment(to, slot, from.Armor(slot), fromFixed);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var armor = from.Armor(slot);
|
||||
if (from.DoApplyEquip(slot))
|
||||
ChangeEquipment(to, slot, armor, fromFixed);
|
||||
if (from.DoApplyStain(slot))
|
||||
ChangeStain(to, slot, armor.Stain, fromFixed);
|
||||
}
|
||||
|
||||
ChangeCustomize(to, from.ApplyCustomize, *from.Customize().Data, fromFixed);
|
||||
ChangeCustomize(to, from.ApplyCustomize, *from.Customize.Data, fromFixed);
|
||||
|
||||
if (from.Wetness.Enabled)
|
||||
SetWetness(to, from.Wetness.ForcedValue, fromFixed);
|
||||
|
|
@ -177,7 +203,7 @@ public sealed partial class ActiveDesign
|
|||
if (redraw)
|
||||
_penumbra.RedrawObject(obj, RedrawType.Redraw);
|
||||
else
|
||||
_interop.UpdateCustomize(obj, design.CharacterData.CustomizeData);
|
||||
_customize.UpdateCustomize(obj, design.ModelData.CustomizeData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +231,7 @@ public sealed partial class ActiveDesign
|
|||
return;
|
||||
|
||||
foreach (var obj in data.Objects)
|
||||
_interop.UpdateSlot(obj.DrawObject, slot, item);
|
||||
_updateSlot.UpdateSlot(obj.DrawObject, slot, item);
|
||||
}
|
||||
|
||||
public void ChangeEquipment(ActiveDesign design, EquipSlot slot, Item item, bool fromFixed)
|
||||
|
|
@ -228,16 +254,20 @@ public sealed partial class ActiveDesign
|
|||
return;
|
||||
|
||||
foreach (var obj in data.Objects)
|
||||
_interop.UpdateSlot(obj.DrawObject, slot, item.Model);
|
||||
_updateSlot.UpdateSlot(obj.DrawObject, slot, item.Model);
|
||||
}
|
||||
|
||||
public void ChangeStain(ActiveDesign design, EquipSlot slot, StainId stain, bool fromFixed)
|
||||
{
|
||||
var flag = slot.ToStainFlag();
|
||||
design.SetStain(slot, stain);
|
||||
var current = design.Armor(slot);
|
||||
var initial = design._initialData.Equipment[slot];
|
||||
if (current.Stain.Value != initial.Stain.Value)
|
||||
var (current, initial, weapon) = slot switch
|
||||
{
|
||||
EquipSlot.MainHand => (design.WeaponMain.Stain, design._initialData.MainHand.Stain, true),
|
||||
EquipSlot.OffHand => (design.WeaponOff.Stain, design._initialData.OffHand.Stain, true),
|
||||
_ => (design.Armor(slot).Stain, design._initialData.Equipment[slot].Stain, false),
|
||||
};
|
||||
if (current.Value != initial.Value)
|
||||
design.ChangedEquip |= flag;
|
||||
else
|
||||
design.ChangedEquip &= ~flag;
|
||||
|
|
@ -251,7 +281,12 @@ public sealed partial class ActiveDesign
|
|||
return;
|
||||
|
||||
foreach (var obj in data.Objects)
|
||||
_interop.UpdateStain(obj.DrawObject, slot, stain);
|
||||
{
|
||||
if (weapon)
|
||||
_weaponService.LoadStain(obj, EquipSlot.MainHand, stain);
|
||||
else
|
||||
_updateSlot.UpdateStain(obj.DrawObject, slot, stain);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeVisor(ActiveDesign design, bool on, bool fromFixed)
|
||||
|
|
@ -266,7 +301,7 @@ public sealed partial class ActiveDesign
|
|||
return;
|
||||
|
||||
foreach (var obj in data.Objects)
|
||||
Interop.Interop.SetVisorState(obj.DrawObject, on);
|
||||
_visor.SetVisorState(obj.DrawObject, on);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ using Penumbra.GameData.Enums;
|
|||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public sealed partial class ActiveDesign : DesignBase
|
||||
public sealed partial class ActiveDesign : DesignData
|
||||
{
|
||||
public readonly ActorIdentifier Identifier;
|
||||
|
||||
private CharacterData _initialData = new();
|
||||
private ModelData _initialData = new();
|
||||
|
||||
public CustomizeFlag ChangedCustomize { get; private set; } = 0;
|
||||
public CustomizeFlag FixedCustomize { get; private set; } = 0;
|
||||
|
|
@ -19,6 +19,7 @@ public sealed partial class ActiveDesign : DesignBase
|
|||
public EquipFlag ChangedEquip { get; private set; } = 0;
|
||||
public EquipFlag FixedEquip { get; private set; } = 0;
|
||||
|
||||
|
||||
public bool IsHatVisible { get; private set; } = false;
|
||||
public bool IsWeaponVisible { get; private set; } = false;
|
||||
public bool IsVisorToggled { get; private set; } = false;
|
||||
|
|
@ -73,7 +74,7 @@ public sealed partial class ActiveDesign : DesignBase
|
|||
if (!_initialData.Customize.Equals(actor.Customize))
|
||||
{
|
||||
_initialData.Customize.Load(actor.Customize);
|
||||
Customize().Load(actor.Customize);
|
||||
Customize.Load(actor.Customize);
|
||||
}
|
||||
|
||||
var initialEquip = _initialData.Equipment;
|
||||
|
|
@ -103,13 +104,13 @@ public sealed partial class ActiveDesign : DesignBase
|
|||
SetStain(EquipSlot.OffHand, actor.OffHand.Stain);
|
||||
}
|
||||
|
||||
var visor = Interop.Interop.GetVisorState(actor.DrawObject);
|
||||
var visor = VisorService.GetVisorState(actor.DrawObject);
|
||||
if (IsVisorToggled != visor)
|
||||
IsVisorToggled = visor;
|
||||
}
|
||||
|
||||
public string CreateOldBase64()
|
||||
=> CreateOldBase64(in CharacterData, EquipFlagExtensions.All, CustomizeFlagExtensions.All, IsWet, IsHatVisible, true,
|
||||
=> DesignBase64Migration.CreateOldBase64(in ModelData, EquipFlagExtensions.All, CustomizeFlagExtensions.All, IsWet, IsHatVisible, true,
|
||||
IsVisorToggled,
|
||||
true, IsWeaponVisible, true, false, 1f);
|
||||
}
|
||||
|
|
@ -1,15 +1,29 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public class FixedDesignManager
|
||||
{
|
||||
public bool TryGetDesign(ActorIdentifier actor, [NotNullWhen(true)] out Design? save)
|
||||
public class FixedDesign
|
||||
{
|
||||
save = null;
|
||||
return false;
|
||||
public Design Design;
|
||||
public byte? JobCondition;
|
||||
public ushort? TerritoryCondition;
|
||||
|
||||
public bool Applies(byte job, ushort territoryType)
|
||||
=> (!JobCondition.HasValue || JobCondition.Value == job)
|
||||
&& (!TerritoryCondition.HasValue || TerritoryCondition.Value == territoryType);
|
||||
}
|
||||
|
||||
public IReadOnlyList<FixedDesign> GetDesigns(ActorIdentifier actor)
|
||||
{
|
||||
return Array.Empty<FixedDesign>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue