Services here, too!

This commit is contained in:
Ottermandias 2023-04-23 19:50:51 +02:00
parent 1eb16b98a8
commit 85adc3626e
47 changed files with 1015 additions and 562 deletions

View file

@ -95,4 +95,14 @@ public static class CustomizeFlagExtensions
CustomizeFlag.FacePaintColor => CustomizeIndex.FacePaintColor,
_ => (CustomizeIndex)byte.MaxValue,
};
public static bool SetIfDifferent(ref this CustomizeFlag flags, CustomizeFlag flag, bool value)
{
var newValue = value ? flags | flag : flags & ~flag;
if (newValue == flags)
return false;
flags = newValue;
return true;
}
}

View file

@ -30,7 +30,6 @@ public partial class Glamourer
private readonly ObjectTable _objectTable;
private readonly DalamudPluginInterface _pluginInterface;
private readonly Glamourer _glamourer;
internal ICallGateProvider<string, string?>? ProviderGetAllCustomization;
internal ICallGateProvider<Character?, string?>? ProviderGetAllCustomizationFromCharacter;
@ -44,10 +43,8 @@ public partial class Glamourer
internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
internal ICallGateProvider<int>? ProviderGetApiVersion;
public GlamourerIpc(Glamourer glamourer, ClientState clientState, ObjectTable objectTable,
DalamudPluginInterface pluginInterface)
public GlamourerIpc(ObjectTable objectTable, DalamudPluginInterface pluginInterface)
{
_glamourer = glamourer;
_objectTable = objectTable;
_pluginInterface = pluginInterface;
@ -214,9 +211,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(customization, true, true);
//var active = _glamourer._stateManager.GetOrCreateSave(character.Address);
//_glamourer._stateManager.ApplyDesign(active, design, false);
}
private void ApplyOnlyCustomization(string customization, string characterName)
@ -235,9 +232,9 @@ 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(customization, true, false);
//var active = _glamourer._stateManager.GetOrCreateSave(character.Address);
//_glamourer._stateManager.ApplyDesign(active, design, false);
}
private void ApplyOnlyEquipment(string customization, string characterName)
@ -256,9 +253,9 @@ public partial class Glamourer
{
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(customization, false, true);
//var active = _glamourer._stateManager.GetOrCreateSave(character.Address);
//_glamourer._stateManager.ApplyDesign(active, design, false);
}
private void Revert(string characterName)
@ -277,9 +274,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.FromObject(character, true, false, false);
//_glamourer._stateManager.DeleteSave(ident);
//_glamourer._penumbra.RedrawObject(character.Address, RedrawType.Redraw);
}
private string? GetAllCustomization(Character? character)
@ -287,11 +284,12 @@ 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();
//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;
}
private string? GetAllCustomization(string characterName)

View file

@ -1,6 +1,7 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using Dalamud.Plugin;
using Glamourer.Interop;
using Penumbra.Api;
using Penumbra.Api.Enums;
@ -8,11 +9,17 @@ using Penumbra.Api.Helpers;
namespace Glamourer.Api;
public class CommunicatorService
{
}
public unsafe class PenumbraAttach : IDisposable
{
public const int RequiredPenumbraBreakingVersion = 4;
public const int RequiredPenumbraFeatureVersion = 15;
private readonly DalamudPluginInterface _pluginInterface;
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
@ -25,14 +32,15 @@ public unsafe class PenumbraAttach : IDisposable
private readonly EventSubscriber _disposedEvent;
public bool Available { get; private set; }
public PenumbraAttach()
public PenumbraAttach(DalamudPluginInterface pi)
{
_initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach);
_disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach);
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface);
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface);
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
_pluginInterface = pi;
_initializedEvent = Ipc.Initialized.Subscriber(pi, Reattach);
_disposedEvent = Ipc.Disposed.Subscriber(pi, Unattach);
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(pi);
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi);
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi);
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi);
Reattach();
}
@ -88,7 +96,7 @@ public unsafe class PenumbraAttach : IDisposable
{
Unattach();
var (breaking, feature) = Ipc.ApiVersions.Subscriber(Dalamud.PluginInterface).Invoke();
var (breaking, feature) = Ipc.ApiVersions.Subscriber(_pluginInterface).Invoke();
if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion)
throw new Exception(
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
@ -97,9 +105,9 @@ public unsafe class PenumbraAttach : IDisposable
_clickSubscriber.Enable();
_creatingCharacterBase.Enable();
_createdCharacterBase.Enable();
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface);
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface);
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(Dalamud.PluginInterface);
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
Available = true;
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
}

View file

@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.IO;
using Dalamud.Configuration;
using Glamourer.Services;
using Newtonsoft.Json;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
namespace Glamourer;
public class Configuration : IPluginConfiguration, ISavable
{
[JsonIgnore]
private readonly SaveService _saveService;
public class FixedDesign
{
public string Name = string.Empty;
public string Path = string.Empty;
public uint JobGroups;
public bool Enabled;
}
public int Version { get; set; } = 1;
public const uint DefaultCustomizationColor = 0xFFC000C0;
public const uint DefaultStateColor = 0xFF00C0C0;
public const uint DefaultEquipmentColor = 0xFF00C000;
public bool UseRestrictedGearProtection { get; set; } = true;
public bool FoldersFirst { get; set; } = false;
public bool ColorDesigns { get; set; } = true;
public bool ShowLocks { get; set; } = true;
public bool ApplyFixedDesigns { get; set; } = true;
public uint CustomizationColor { get; set; } = DefaultCustomizationColor;
public uint StateColor { get; set; } = DefaultStateColor;
public uint EquipmentColor { get; set; } = DefaultEquipmentColor;
public List<FixedDesign> FixedDesigns { get; set; } = new();
public void Save()
=> _saveService.QueueSave(this);
public Configuration(SaveService saveService)
{
_saveService = saveService;
Load();
}
public void Load()
{
static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs)
{
Glamourer.Log.Error(
$"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}");
errorArgs.ErrorContext.Handled = true;
}
if (!File.Exists(_saveService.FileNames.ConfigFile))
return;
var text = File.ReadAllText(_saveService.FileNames.ConfigFile);
JsonConvert.PopulateObject(text, this, new JsonSerializerSettings
{
Error = HandleDeserializationError,
});
}
public string ToFilename(FilenameService fileNames)
=> fileNames.ConfigFile;
public void Save(StreamWriter writer)
{
using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented };
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
serializer.Serialize(jWriter, this);
}
}

View file

@ -7,26 +7,43 @@ using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.IoC;
using Dalamud.Plugin;
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
using Microsoft.Extensions.DependencyInjection;
namespace Glamourer;
public class Dalamud
public class DalamudServices
{
public static void Initialize(DalamudPluginInterface pluginInterface)
=> pluginInterface.Create<Dalamud>();
public DalamudServices(DalamudPluginInterface pi)
{
pi.Inject(this);
}
public void AddServices(IServiceCollection services)
{
services.AddSingleton(PluginInterface);
services.AddSingleton(Commands);
services.AddSingleton(GameData);
services.AddSingleton(ClientState);
services.AddSingleton(GameGui);
services.AddSingleton(Chat);
services.AddSingleton(Framework);
services.AddSingleton(Targets);
services.AddSingleton(Objects);
services.AddSingleton(KeyState);
services.AddSingleton(this);
services.AddSingleton(PluginInterface.UiBuilder);
}
// @formatter:off
[PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static CommandManager Commands { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static DataManager GameData { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static ClientState ClientState { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static ChatGui Chat { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static Framework Framework { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public static KeyState KeyState { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public DalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public CommandManager Commands { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public DataManager GameData { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public ClientState ClientState { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public GameGui GameGui { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public ChatGui Chat { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public Framework Framework { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public TargetManager Targets { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public ObjectTable Objects { get; private set; } = null!;
[PluginService][RequiredVersion("1.0")] public KeyState KeyState { get; private set; } = null!;
// @formatter:on
}

View file

@ -5,6 +5,7 @@ using System.Linq;
using Dalamud.Plugin;
using Dalamud.Utility;
using Glamourer.Customization;
using Glamourer.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
@ -21,8 +22,9 @@ public partial class Design
public const string DesignFolderName = "designs";
public readonly string DesignFolder;
private readonly FrameworkManager _framework;
private readonly List<Design> _designs = new();
private readonly ItemManager _items;
private readonly SaveService _saveService;
private readonly List<Design> _designs = new();
public enum DesignChangeType
{
@ -50,9 +52,10 @@ public partial class Design
public IReadOnlyList<Design> Designs
=> _designs;
public Manager(DalamudPluginInterface pi, FrameworkManager framework)
public Manager(DalamudPluginInterface pi, SaveService saveService, ItemManager items)
{
_framework = framework;
_saveService = saveService;
_items = items;
DesignFolder = SetDesignFolder(pi);
DesignChange += OnChange;
LoadDesigns();
@ -105,7 +108,7 @@ public partial class Design
=> Path.Combine(DesignFolder, $"{design.Identifier}.json");
public void SaveDesign(Design design)
=> _framework.RegisterDelayed($"{nameof(SaveDesign)}_{design.Identifier}", () => SaveDesignInternal(design));
=> _saveService.QueueSave(design);
private void SaveDesignInternal(Design design)
{
@ -133,7 +136,7 @@ public partial class Design
{
var text = File.ReadAllText(file.FullName);
var data = JObject.Parse(text);
var design = LoadDesign(data, out var changes);
var 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))
@ -177,7 +180,7 @@ public partial class Design
public Design Create(string name)
{
var design = new Design()
var design = new Design(_items)
{
CreationDate = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(),
@ -297,7 +300,7 @@ public partial class Design
public void ChangeEquip(Design design, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
{
var old = design.Armor(slot);
if (design.SetArmor(slot, itemId, item))
if (design.SetArmor(_items, slot, itemId, item))
{
var n = design.Armor(slot);
Glamourer.Log.Debug(
@ -309,8 +312,8 @@ public partial class Design
public void ChangeWeapon(Design design, uint itemId, EquipSlot offhand, Lumina.Excel.GeneratedSheets.Item? item = null)
{
var (old, change, n) = offhand == EquipSlot.OffHand
? (design.WeaponOff, design.SetOffhand(itemId, item), design.WeaponOff)
: (design.WeaponMain, design.SetMainhand(itemId, item), design.WeaponMain);
? (design.WeaponOff, design.SetOffhand(_items, itemId, item), design.WeaponOff)
: (design.WeaponMain, design.SetMainhand(_items, itemId, item), design.WeaponMain);
if (change)
{
Glamourer.Log.Debug(
@ -386,13 +389,13 @@ public partial class Design
try
{
var actualName = Path.GetFileName(name);
var design = new Design()
var design = new Design(_items)
{
CreationDate = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(),
Name = actualName,
};
design.MigrateBase64(base64);
design.MigrateBase64(_items, base64);
Add(design, $"Migrated old design to {design.Identifier}.");
migratedFileSystemPaths.Add(design.Identifier.ToString(), name);
++successes;

View file

@ -1,16 +1,17 @@
using System;
using System.IO;
using System.Linq;
using Glamourer.Customization;
using Glamourer.Util;
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 : DesignBase
public partial class Design : DesignBase, ISavable
{
public const int FileVersion = 1;
@ -69,7 +70,8 @@ public partial class Design : DesignBase
}
private Design()
private Design(ItemManager items)
: base(items)
{ }
public JObject JsonSerialize()
@ -142,17 +144,17 @@ public partial class Design : DesignBase
return ret;
}
public static Design LoadDesign(JObject json, out bool changes)
public static Design LoadDesign(ItemManager items, JObject json, out bool changes)
{
var version = json[nameof(FileVersion)]?.ToObject<int>() ?? 0;
return version switch
{
1 => LoadDesignV1(json, out changes),
1 => LoadDesignV1(items, json, out changes),
_ => throw new Exception("The design to be loaded has no valid Version."),
};
}
private static Design LoadDesignV1(JObject json, out bool changes)
private static Design LoadDesignV1(ItemManager items, JObject json, out bool changes)
{
static string[] ParseTags(JObject json)
{
@ -160,7 +162,7 @@ public partial class Design : DesignBase
return tags.OrderBy(t => t).Distinct().ToArray();
}
var design = new Design()
var design = new Design(items)
{
CreationDate = json["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate"),
Identifier = json["Identifier"]?.ToObject<Guid>() ?? throw new ArgumentNullException("Identifier"),
@ -169,12 +171,12 @@ public partial class Design : DesignBase
Tags = ParseTags(json),
};
changes = LoadEquip(json["Equipment"], design);
changes = LoadEquip(items, json["Equipment"], design);
changes |= LoadCustomize(json["Customize"], design);
return design;
}
private static bool LoadEquip(JToken? equip, Design design)
private static bool LoadEquip(ItemManager items, JToken? equip, Design design)
{
if (equip == null)
return true;
@ -192,7 +194,7 @@ public partial class Design : DesignBase
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]);
changes |= !design.SetArmor(slot, id);
changes |= !design.SetArmor(items, slot, id);
changes |= !design.SetStain(slot, stain);
design.SetApplyEquip(slot, apply);
design.SetApplyStain(slot, applyStain);
@ -205,11 +207,11 @@ public partial class Design : DesignBase
}
else
{
var id = main["ItemId"]?.ToObject<uint>() ?? Glamourer.Items.DefaultSword.RowId;
var id = main["ItemId"]?.ToObject<uint>() ?? items.DefaultSword.RowId;
var stain = (StainId)(main["Stain"]?.ToObject<byte>() ?? 0);
var apply = main["Apply"]?.ToObject<bool>() ?? false;
var applyStain = main["ApplyStain"]?.ToObject<bool>() ?? false;
changes |= !design.SetMainhand(id);
changes |= !design.SetMainhand(items, id);
changes |= !design.SetStain(EquipSlot.MainHand, stain);
design.SetApplyEquip(EquipSlot.MainHand, apply);
design.SetApplyStain(EquipSlot.MainHand, applyStain);
@ -226,7 +228,7 @@ public partial class Design : DesignBase
var stain = (StainId)(off["Stain"]?.ToObject<byte>() ?? 0);
var apply = off["Apply"]?.ToObject<bool>() ?? false;
var applyStain = off["ApplyStain"]?.ToObject<bool>() ?? false;
changes |= !design.SetOffhand(id);
changes |= !design.SetOffhand(items, id);
changes |= !design.SetStain(EquipSlot.OffHand, stain);
design.SetApplyEquip(EquipSlot.OffHand, apply);
design.SetApplyStain(EquipSlot.OffHand, applyStain);
@ -259,14 +261,14 @@ public partial class Design : DesignBase
return false;
}
public void MigrateBase64(string base64)
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,
out var visor, out var weapon);
UpdateMainhand(data.MainHand);
UpdateMainhand(data.OffHand);
UpdateMainhand(items, data.MainHand);
UpdateOffhand(items, data.OffHand);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
UpdateArmor(slot, data.Equipment[slot], true);
UpdateArmor(items, slot, data.Equipment[slot], true);
CharacterData.CustomizeData = data.CustomizeData;
ApplyEquip = applyEquip;
ApplyCustomize = applyCustomize;
@ -277,10 +279,10 @@ public partial class Design : DesignBase
Weapon = weapon;
}
public static Design CreateTemporaryFromBase64(string base64, bool customize, bool equip)
public static Design CreateTemporaryFromBase64(ItemManager items, string base64, bool customize, bool equip)
{
var ret = new Design();
ret.MigrateBase64(base64);
var ret = new Design(items);
ret.MigrateBase64(items, base64);
if (!customize)
ret.ApplyCustomize = 0;
if (!equip)
@ -296,4 +298,20 @@ public partial class Design : DesignBase
public string CreateOldBase64()
=> CreateOldBase64(in CharacterData, ApplyEquip, ApplyCustomize, Wetness == QuadBool.True, Hat.ForcedValue, Hat.Enabled,
Visor.ForcedValue, Visor.Enabled, Weapon.ForcedValue, Weapon.Enabled, WriteProtected, 1f);
public string ToFilename(FilenameService fileNames)
=> fileNames.DesignFile(this);
public void Save(StreamWriter writer)
{
using var j = new JsonTextWriter(writer)
{
Formatting = Formatting.Indented,
};
var obj = JsonSerialize();
obj.WriteTo(j);
}
public string LogName(string fileName)
=> Path.GetFileNameWithoutExtension(fileName);
}

View file

@ -1,5 +1,6 @@
using System;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.Util;
using OtterGui.Classes;
using OtterGui;
@ -45,14 +46,14 @@ public class DesignBase
public CharacterEquip Equipment()
=> CharacterData.Equipment;
public DesignBase()
public DesignBase(ItemManager items)
{
MainHand = Glamourer.Items.DefaultSword.RowId;
MainHand = items.DefaultSword.RowId;
(_, CharacterData.MainHand.Set, CharacterData.MainHand.Type, CharacterData.MainHand.Variant, MainhandName, MainhandType) =
Glamourer.Items.Resolve(MainHand, Glamourer.Items.DefaultSword);
items.Resolve(MainHand, items.DefaultSword);
OffHand = ItemManager.NothingId(MainhandType.Offhand());
(_, CharacterData.OffHand.Set, CharacterData.OffHand.Type, CharacterData.OffHand.Variant, OffhandName, _) =
Glamourer.Items.Resolve(OffHand, MainhandType);
items.Resolve(OffHand, MainhandType);
}
public uint ModelId
@ -96,9 +97,9 @@ public class DesignBase
return true;
}
protected bool SetArmor(EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
protected bool SetArmor(ItemManager items, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null)
{
var (valid, set, variant, name) = Glamourer.Items.Resolve(slot, itemId, item);
var (valid, set, variant, name) = items.Resolve(slot, itemId, item);
if (!valid)
return false;
@ -108,7 +109,7 @@ public class DesignBase
protected bool SetArmor(EquipSlot slot, Item item)
=> SetArmor(slot, item.ModelBase, item.Variant, item.Name, item.ItemId);
protected bool UpdateArmor(EquipSlot slot, CharacterArmor armor, bool force)
protected bool UpdateArmor(ItemManager items, EquipSlot slot, CharacterArmor armor, bool force)
{
if (!force)
switch (slot)
@ -125,19 +126,19 @@ public class DesignBase
case EquipSlot.LFinger when CharacterData.LFinger.Value == armor.Value: return false;
}
var (valid, id, name) = Glamourer.Items.Identify(slot, armor.Set, armor.Variant);
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(uint mainId, Lumina.Excel.GeneratedSheets.Item? main = null)
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) = Glamourer.Items.Resolve(mainId, main);
var (valid, set, weapon, variant, name, type) = items.Resolve(mainId, main);
if (!valid)
return false;
@ -150,16 +151,16 @@ public class DesignBase
CharacterData.MainHand.Type = weapon;
CharacterData.MainHand.Variant = variant;
if (fixOffhand)
SetOffhand(ItemManager.NothingId(type.Offhand()));
SetOffhand(items, ItemManager.NothingId(type.Offhand()));
return true;
}
protected bool SetOffhand(uint offId, Lumina.Excel.GeneratedSheets.Item? off = null)
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) = Glamourer.Items.Resolve(offId, MainhandType, off);
var (valid, set, weapon, variant, name, type) = items.Resolve(offId, MainhandType, off);
if (!valid)
return false;
@ -171,12 +172,12 @@ public class DesignBase
return true;
}
protected bool UpdateMainhand(CharacterWeapon weapon)
protected bool UpdateMainhand(ItemManager items, CharacterWeapon weapon)
{
if (weapon.Value == CharacterData.MainHand.Value)
return false;
var (valid, id, name, type) = Glamourer.Items.Identify(EquipSlot.MainHand, weapon.Set, weapon.Type, (byte)weapon.Variant);
var (valid, id, name, type) = items.Identify(EquipSlot.MainHand, weapon.Set, weapon.Type, (byte)weapon.Variant);
if (!valid || id == MainHand)
return false;
@ -190,16 +191,16 @@ public class DesignBase
CharacterData.MainHand.Variant = weapon.Variant;
CharacterData.MainHand.Stain = weapon.Stain;
if (fixOffhand)
SetOffhand(ItemManager.NothingId(type.Offhand()));
SetOffhand(items, ItemManager.NothingId(type.Offhand()));
return true;
}
protected bool UpdateOffhand(CharacterWeapon weapon)
protected bool UpdateOffhand(ItemManager items, CharacterWeapon weapon)
{
if (weapon.Value == CharacterData.OffHand.Value)
return false;
var (valid, id, name, _) = Glamourer.Items.Identify(EquipSlot.OffHand, weapon.Set, weapon.Type, (byte)weapon.Variant, MainhandType);
var (valid, id, name, _) = items.Identify(EquipSlot.OffHand, weapon.Set, weapon.Type, (byte)weapon.Variant, MainhandType);
if (!valid || id == OffHand)
return false;

View file

@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Plugin;
using Glamourer.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
@ -12,20 +13,20 @@ using OtterGui.Filesystem;
namespace Glamourer.Designs;
public sealed class DesignFileSystem : FileSystem<Design>, IDisposable
public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
{
public static string GetDesignFileSystemFile(DalamudPluginInterface pi)
=> Path.Combine(pi.GetPluginConfigDirectory(), "sort_order.json");
public readonly string DesignFileSystemFile;
private readonly FrameworkManager _framework;
private readonly Design.Manager _designManager;
public readonly string DesignFileSystemFile;
private readonly SaveService _saveService;
private readonly Design.Manager _designManager;
public DesignFileSystem(Design.Manager designManager, DalamudPluginInterface pi, FrameworkManager framework)
public DesignFileSystem(Design.Manager designManager, DalamudPluginInterface pi, SaveService saveService)
{
DesignFileSystemFile = GetDesignFileSystemFile(pi);
_designManager = designManager;
_framework = framework;
_saveService = saveService;
_designManager.DesignChange += OnDataChange;
Changed += OnChange;
Reload();
@ -34,7 +35,7 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable
private void Reload()
{
if (Load(new FileInfo(DesignFileSystemFile), _designManager.Designs, DesignToIdentifier, DesignToName))
SaveFilesystem();
_saveService.ImmediateSave(this);
Glamourer.Log.Debug("Reloaded design filesystem.");
}
@ -71,18 +72,9 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable
private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
{
if (type != FileSystemChangeType.Reload)
SaveFilesystem();
_saveService.QueueSave(this);
}
private void SaveFilesystem()
{
SaveToFile(new FileInfo(DesignFileSystemFile), SaveDesign, true);
Glamourer.Log.Verbose("Saved design filesystem.");
}
public void Save()
=> _framework.RegisterDelayed(nameof(SaveFilesystem), SaveFilesystem);
private void OnDataChange(Design.Manager.DesignChangeType type, Design design, object? data)
{
switch (type)
@ -176,4 +168,12 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable
Glamourer.Log.Error($"Could not migrate old folder paths to new version:\n{ex}");
}
}
public string ToFilename(FilenameService fileNames)
=> fileNames.DesignFileSystem;
public void Save(StreamWriter writer)
{
SaveToFile(writer, SaveDesign, true);
}
}

View file

@ -3,9 +3,8 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Api;
using Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Util;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
@ -15,12 +14,12 @@ namespace Glamourer;
public class DrawObjectManager : IDisposable
{
private readonly ItemManager _items;
private readonly ActorManager _actors;
private readonly ActorService _actors;
private readonly ActiveDesign.Manager _manager;
private readonly Interop.Interop _interop;
private readonly PenumbraAttach _penumbra;
public DrawObjectManager(ItemManager items, ActorManager actors, ActiveDesign.Manager manager, Interop.Interop interop,
public DrawObjectManager(ItemManager items, ActorService actors, ActiveDesign.Manager manager, Interop.Interop interop,
PenumbraAttach penumbra)
{
_items = items;
@ -54,7 +53,7 @@ public class DrawObjectManager : IDisposable
if (gameObject.ModelId != modelId)
return;
var identifier = _actors.FromObject((GameObject*)gameObjectPtr, out _, true, true, false);
var identifier = _actors.AwaitedService.FromObject((GameObject*)gameObjectPtr, out _, true, true, false);
if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var design))
return;
@ -75,7 +74,7 @@ public class DrawObjectManager : IDisposable
foreach (var slot in EquipSlotExtensions.EquipmentSlots)
{
(_, equip[slot]) =
Glamourer.Items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender);
_items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender);
}
}
}

View file

@ -17,15 +17,15 @@ public struct FixedCondition
public bool Check(Actor actor)
{
if ((_data & (_territoryFlag | _jobFlag)) == 0)
return true;
if ((_data & _territoryFlag) != 0)
return Dalamud.ClientState.TerritoryType == (ushort)_data;
if (actor && GameData.JobGroups(Dalamud.GameData).TryGetValue((ushort)_data, out var group) && group.Fits(actor.Job))
return true;
//if ((_data & (_territoryFlag | _jobFlag)) == 0)
// return true;
//
//if ((_data & _territoryFlag) != 0)
// return Dalamud.ClientState.TerritoryType == (ushort)_data;
//
//if (actor && GameData.JobGroups(Dalamud.GameData).TryGetValue((ushort)_data, out var group) && group.Fits(actor.Job))
// return true;
//
return true;
}

View file

@ -1,29 +1,15 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing;
using System.Reflection;
using Dalamud.Plugin;
using Glamourer.Api;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.State;
using Glamourer.Util;
using Glamourer.Services;
using Microsoft.Extensions.DependencyInjection;
using OtterGui.Classes;
using OtterGui.Log;
using Penumbra.GameData.Actors;
using FixedDesigns = Glamourer.State.FixedDesigns;
namespace Glamourer;
public partial class Glamourer : IDalamudPlugin
{
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
private const string MainCommandString = "/glamourer";
private const string ApplyCommandString = "/glamour";
public string Name
=> "Glamourer";
@ -33,73 +19,20 @@ public partial class Glamourer : IDalamudPlugin
Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
public static GlamourerConfig Config = null!;
public static Logger Log = null!;
public static ActorManager Actors = null!;
public static ICustomizationManager Customization = null!;
public static RedrawManager RedrawManager = null!;
public static ItemManager Items = null!;
public readonly FixedDesigns FixedDesigns;
private readonly Interop.Interop _interop;
private readonly PenumbraAttach _penumbra;
private readonly ObjectManager _objectManager;
private readonly Design.Manager _designManager;
private readonly ActiveDesign.Manager _stateManager;
private readonly DrawObjectManager _drawObjectManager;
private readonly DesignFileSystem _fileSystem;
private readonly FrameworkManager _framework;
private readonly WindowSystem _windowSystem = new("Glamourer");
private readonly Interface _interface;
//public readonly DesignManager Designs;
//public static RevertableDesigns RevertableDesigns = new();
public readonly GlamourerIpc Ipc;
public static readonly Logger Log = new();
public static ChatService ChatService { get; private set; } = null!;
private readonly ServiceProvider _services;
public Glamourer(DalamudPluginInterface pluginInterface)
{
try
{
Dalamud.Initialize(pluginInterface);
Log = new Logger();
_framework = new FrameworkManager(Dalamud.Framework, Log);
_interop = new Interop.Interop();
_penumbra = new PenumbraAttach();
Items = new ItemManager(Dalamud.PluginInterface, Dalamud.GameData);
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
Backup.CreateBackup(pluginInterface.ConfigDirectory, BackupFiles(Dalamud.PluginInterface));
Config = GlamourerConfig.Load();
_objectManager = new ObjectManager(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects);
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.Framework, Dalamud.GameData,
Dalamud.GameGui, i => (short)_penumbra.CutsceneParent(i));
_designManager = new Design.Manager(Dalamud.PluginInterface, _framework);
_fileSystem = new DesignFileSystem(_designManager, Dalamud.PluginInterface, _framework);
FixedDesigns = new FixedDesigns();
_stateManager = new ActiveDesign.Manager(Actors, _objectManager, _interop, _penumbra);
//GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
RedrawManager = new RedrawManager(FixedDesigns, _stateManager);
_drawObjectManager = new DrawObjectManager(Items, Actors, _stateManager, _interop, _penumbra);
Dalamud.Commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer)
{
HelpMessage = "Open or close the Glamourer window.",
});
Dalamud.Commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour)
{
HelpMessage = $"Use Glamourer Functions: {HelpString}",
});
_interface = new Interface(Dalamud.PluginInterface, Items, _stateManager, _designManager, _fileSystem, _objectManager);
_windowSystem.AddWindow(_interface);
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
Ipc = new GlamourerIpc(this, Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
_services = ServiceManager.CreateProvider(pluginInterface, Log);
ChatService = _services.GetRequiredService<ChatService>();
_services.GetRequiredService<BackupService>();
_services.GetRequiredService<GlamourerWindowSystem>();
_services.GetRequiredService<CommandService>();
_services.GetRequiredService<GlamourerIpc>();
}
catch
{
@ -111,25 +44,9 @@ public partial class Glamourer : IDalamudPlugin
public void Dispose()
{
Ipc?.Dispose();
_drawObjectManager?.Dispose();
RedrawManager?.Dispose();
if (_windowSystem != null)
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_interface?.Dispose();
_fileSystem?.Dispose();
//GlamourerIpc.Dispose();
_interop?.Dispose();
_penumbra?.Dispose();
_framework?.Dispose();
Items?.Dispose();
Dalamud.Commands.RemoveHandler(ApplyCommandString);
Dalamud.Commands.RemoveHandler(MainCommandString);
_services?.Dispose();
}
public void OnGlamourer(string command, string arguments)
=> _interface.Toggle();
//private static GameObject? GetPlayer(string name)
//{
// var lowerName = name.ToLowerInvariant();
@ -262,25 +179,4 @@ public partial class Glamourer : IDalamudPlugin
// return;
//}
}
// Collect all relevant files for glamourer configuration.
private static IReadOnlyList<FileInfo> BackupFiles(DalamudPluginInterface pi)
{
var list = new List<FileInfo>(16)
{
pi.ConfigFile,
new(DesignFileSystem.GetDesignFileSystemFile(pi)),
};
var configDir = Dalamud.PluginInterface.ConfigDirectory;
if (Directory.Exists(configDir.FullName))
{
list.Add(new FileInfo(Path.Combine(configDir.FullName, "Designs.json"))); // migration
var designDir = new DirectoryInfo(Path.Combine(configDir.FullName, Design.Manager.DesignFolderName));
if (designDir.Exists)
list.AddRange(designDir.EnumerateFiles("*.json", SearchOption.TopDirectoryOnly));
}
return list;
}
}

View file

@ -86,6 +86,7 @@
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
<ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" />
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup>
<ItemGroup>

View file

@ -19,8 +19,8 @@ public partial class CustomizationDrawer
ImGui.SameLine();
using var group = ImRaii.Group();
DrawRaceCombo();
var gender = Glamourer.Customization.GetName(CustomName.Gender);
var clan = Glamourer.Customization.GetName(CustomName.Clan);
var gender = _service.AwaitedService.GetName(CustomName.Gender);
var clan = _service.AwaitedService.GetName(CustomName.Clan);
ImGui.TextUnformatted($"{gender} & {clan}");
}
@ -39,20 +39,20 @@ public partial class CustomizationDrawer
if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, icon == FontAwesomeIcon.MarsDouble, true))
return;
Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male);
Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male, _items, _service.AwaitedService);
}
private void DrawRaceCombo()
{
ImGui.SetNextItemWidth(_raceSelectorWidth);
using var combo = ImRaii.Combo("##subRaceCombo", _customize.ClanName());
using var combo = ImRaii.Combo("##subRaceCombo", _customize.ClanName(_service.AwaitedService));
if (!combo)
return;
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
{
if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan))
Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace);
if (ImGui.Selectable(CustomizeExtensions.ClanName(_service.AwaitedService, subRace, _customize.Gender), subRace == _customize.Clan))
Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace, _items, _service.AwaitedService);
}
}
}

View file

@ -26,7 +26,7 @@ public partial class CustomizationDrawer
custom = _set.Data(index, 0);
}
var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId);
var icon = _service.AwaitedService.GetIcon(custom!.Value.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
ImGui.OpenPopup(IconSelectorPopup);
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
@ -77,13 +77,12 @@ public partial class CustomizationDrawer
if (!popup)
return;
var ret = false;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
for (var i = 0; i < _currentCount; ++i)
{
var custom = _set.Data(_currentIndex, i, _customize.Face);
var icon = Glamourer.Customization.GetIcon(custom.IconId);
var icon = _service.AwaitedService.GetIcon(custom.IconId);
using (var _ = ImRaii.Group())
{
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
@ -134,8 +133,8 @@ public partial class CustomizationDrawer
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
var feature = _set.Data(featureIdx, 0, _customize.Face);
var icon = featureIdx == CustomizeIndex.LegacyTattoo
? _legacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
: Glamourer.Customization.GetIcon(feature.IconId);
? _legacyTattoo ?? _service.AwaitedService.GetIcon(feature.IconId)
: _service.AwaitedService.GetIcon(feature.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
Vector4.Zero, enabled ? Vector4.One : _redTint))
{

View file

@ -4,6 +4,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using Dalamud.Plugin;
using Glamourer.Customization;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
@ -40,8 +41,13 @@ public partial class CustomizationDrawer : IDisposable
private float _comboSelectorSize;
private float _raceSelectorWidth;
public CustomizationDrawer(DalamudPluginInterface pi)
private readonly CustomizationService _service;
private readonly ItemManager _items;
public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service, ItemManager items)
{
_service = service;
_items = items;
_legacyTattoo = GetLegacyTattooIcon(pi);
unsafe
{
@ -120,7 +126,7 @@ public partial class CustomizationDrawer : IDisposable
try
{
DrawRaceGenderSelector();
_set = Glamourer.Customization.GetList(_customize.Clan, _customize.Gender);
_set = _service.AwaitedService.GetList(_customize.Clan, _customize.Gender);
foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage])
PercentageSelector(id);

View file

@ -1,4 +1,8 @@
using Glamourer.Designs;
using System.Numerics;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Interface;
using Glamourer.Designs;
using OtterGui;
using OtterGui.FileSystem.Selector;
namespace Glamourer.Gui.Designs;
@ -10,11 +14,12 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
public struct DesignState
{ }
public DesignFileSystemSelector(Design.Manager manager, DesignFileSystem fileSystem)
: base(fileSystem, Dalamud.KeyState)
public DesignFileSystemSelector(Design.Manager manager, DesignFileSystem fileSystem, KeyState keyState)
: base(fileSystem, keyState)
{
_manager = manager;
_manager.DesignChange += OnDesignChange;
AddButton(DeleteButton, 1000);
}
public override void Dispose()
@ -36,4 +41,19 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
break;
}
}
private void DeleteButton(Vector2 size)
{
var keys = true;
var tt = SelectedLeaf == null
? "No design selected."
: "Delete the currently selected design entirely from your drive.\n"
+ "This can not be undone.";
//if (!keys)
// tt += $"\nHold {_config.DeleteModModifier} while clicking to delete the mod.";
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
&& Selected != null)
_manager.Delete(Selected);
}
}

View file

@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Dalamud.Data;
using Dalamud.Interface;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Util;
using ImGuiNET;
using OtterGui;
using OtterGui.Widgets;
@ -23,27 +24,27 @@ public class EquipmentDrawer
private readonly ItemCombo[] _itemCombo;
private readonly Dictionary<(FullEquipType, EquipSlot), WeaponCombo> _weaponCombo;
public EquipmentDrawer(ItemManager items)
public EquipmentDrawer(DataManager gameData, ItemManager items)
{
_items = items;
_stainData = items.Stains;
_stainCombo = new FilterComboColors(140,
_stainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(items, e)).ToArray();
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e)).ToArray();
_weaponCombo = new Dictionary<(FullEquipType, EquipSlot), WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
foreach (var type in Enum.GetValues<FullEquipType>())
{
if (type.ToSlot() is EquipSlot.MainHand)
_weaponCombo.TryAdd((type, EquipSlot.MainHand), new WeaponCombo(items, type, EquipSlot.MainHand));
_weaponCombo.TryAdd((type, EquipSlot.MainHand), new WeaponCombo(gameData, items, type, EquipSlot.MainHand));
else if (type.ToSlot() is EquipSlot.OffHand)
_weaponCombo.TryAdd((type, EquipSlot.OffHand), new WeaponCombo(items, type, EquipSlot.OffHand));
_weaponCombo.TryAdd((type, EquipSlot.OffHand), new WeaponCombo(gameData, items, type, EquipSlot.OffHand));
var offhand = type.Offhand();
if (offhand is not FullEquipType.Unknown && !_weaponCombo.ContainsKey((offhand, EquipSlot.OffHand)))
_weaponCombo.TryAdd((offhand, EquipSlot.OffHand), new WeaponCombo(items, type, EquipSlot.OffHand));
_weaponCombo.TryAdd((offhand, EquipSlot.OffHand), new WeaponCombo(gameData, items, type, EquipSlot.OffHand));
}
_weaponCombo.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(items, FullEquipType.Unknown, EquipSlot.MainHand));
_weaponCombo.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(gameData, items, FullEquipType.Unknown, EquipSlot.MainHand));
}
private string VerifyRestrictedGear(Item gear, EquipSlot slot, Gender gender, Race race)

View file

@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using Glamourer.Util;
using Dalamud.Data;
using Glamourer.Designs;
using Glamourer.Services;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
@ -17,10 +19,10 @@ public sealed class ItemCombo : FilterComboCache<Item>
public readonly string Label;
private uint _currentItem;
public ItemCombo(ItemManager items, EquipSlot slot)
public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot)
: base(() => GetItems(items, slot))
{
Label = GetLabel(slot);
Label = GetLabel(gameData, slot);
_currentItem = ItemManager.NothingId(slot);
}
@ -66,9 +68,9 @@ public sealed class ItemCombo : FilterComboCache<Item>
protected override string ToString(Item obj)
=> obj.Name;
private static string GetLabel(EquipSlot slot)
private static string GetLabel(DataManager gameData, EquipSlot slot)
{
var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
var sheet = gameData.GetExcelSheet<Addon>()!;
return slot switch
{
@ -89,7 +91,7 @@ public sealed class ItemCombo : FilterComboCache<Item>
private static IReadOnlyList<Item> GetItems(ItemManager items, EquipSlot slot)
{
var nothing = ItemManager.NothingItem(slot);
if (!items.Items.TryGetValue(slot.ToEquipType(), out var list))
if (!items.ItemService.AwaitedService.TryGetValue(slot.ToEquipType(), out var list))
return new[]
{
nothing,

View file

@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Data;
using Glamourer.Designs;
using Glamourer.Util;
using Glamourer.Services;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
@ -18,9 +19,9 @@ public sealed class WeaponCombo : FilterComboCache<Weapon>
public readonly string Label;
private uint _currentItem;
public WeaponCombo(ItemManager items, FullEquipType type, EquipSlot offhand)
public WeaponCombo(DataManager gameData, ItemManager items, FullEquipType type, EquipSlot offhand)
: base(offhand is EquipSlot.OffHand ? () => GetOff(items, type) : () => GetMain(items, type))
=> Label = GetLabel(offhand);
=> Label = GetLabel(gameData, offhand);
protected override void DrawList(float width, float itemHeight)
{
@ -64,9 +65,9 @@ public sealed class WeaponCombo : FilterComboCache<Weapon>
protected override string ToString(Weapon obj)
=> obj.Name;
private static string GetLabel(EquipSlot offhand)
private static string GetLabel(DataManager gameData, EquipSlot offhand)
{
var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
var sheet = gameData.GetExcelSheet<Addon>()!;
return offhand is EquipSlot.OffHand
? sheet.GetRow(739)?.Text.ToString() ?? "Off Hand"
: sheet.GetRow(738)?.Text.ToString() ?? "Main Hand";
@ -77,9 +78,9 @@ public sealed class WeaponCombo : FilterComboCache<Weapon>
var list = new List<Weapon>();
if (type is FullEquipType.Unknown)
foreach (var t in Enum.GetValues<FullEquipType>().Where(t => t.ToSlot() == EquipSlot.MainHand))
list.AddRange(items.Items[t].Select(w => new Weapon(w, false)));
list.AddRange(items.ItemService.AwaitedService[t].Select(w => new Weapon(w, false)));
else if (type.ToSlot() is EquipSlot.MainHand)
list.AddRange(items.Items[type].Select(w => new Weapon(w, false)));
list.AddRange(items.ItemService.AwaitedService[type].Select(w => new Weapon(w, false)));
list.Sort((w1, w2) => string.CompareOrdinal(w1.Name, w2.Name));
return list;
}
@ -89,7 +90,7 @@ public sealed class WeaponCombo : FilterComboCache<Weapon>
if (type.ToSlot() == EquipSlot.OffHand)
{
var nothing = ItemManager.NothingItem(type);
if (!items.Items.TryGetValue(type, out var list))
if (!items.ItemService.AwaitedService.TryGetValue(type, out var list))
return new[]
{
nothing,
@ -97,7 +98,7 @@ public sealed class WeaponCombo : FilterComboCache<Weapon>
return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).Prepend(nothing).ToList();
}
else if (items.Items.TryGetValue(type, out var list))
else if (items.ItemService.AwaitedService.TryGetValue(type, out var list))
{
return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).ToList();
}

View file

@ -0,0 +1,30 @@
using System;
using Dalamud.Interface;
using Dalamud.Interface.Windowing;
namespace Glamourer.Gui;
public class GlamourerWindowSystem : IDisposable
{
private readonly WindowSystem _windowSystem = new("Glamourer");
private readonly UiBuilder _uiBuilder;
private readonly Interface _ui;
public GlamourerWindowSystem(UiBuilder uiBuilder, Interface ui)
{
_uiBuilder = uiBuilder;
_ui = ui;
_windowSystem.AddWindow(ui);
_uiBuilder.Draw += _windowSystem.Draw;
_uiBuilder.OpenConfigUi += _ui.Toggle;
}
public void Dispose()
{
_uiBuilder.Draw -= _windowSystem.Draw;
_uiBuilder.OpenConfigUi -= _ui.Toggle;
}
public void Toggle()
=> _ui.Toggle();
}

View file

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
@ -14,25 +16,32 @@ using ImGui = ImGuiNET.ImGui;
namespace Glamourer.Gui;
internal partial class Interface
public partial class Interface
{
private class ActorTab
{
private readonly Interface _main;
private readonly ActiveDesign.Manager _activeDesigns;
private readonly ObjectManager _objects;
private readonly TargetManager _targets;
private readonly ActorService _actors;
private readonly ItemManager _items;
public ActorTab(Interface main, ActiveDesign.Manager activeDesigns, ObjectManager objects)
public ActorTab(Interface main, ActiveDesign.Manager activeDesigns, ObjectManager objects, TargetManager targets, ActorService actors,
ItemManager items)
{
_main = main;
_activeDesigns = activeDesigns;
_objects = objects;
_targets = targets;
_actors = actors;
_items = items;
}
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid;
private string _currentLabel = string.Empty;
private ActiveDesign? _currentSave;
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
private ActorData _currentData = ActorData.Invalid;
private string _currentLabel = string.Empty;
private ActiveDesign? _currentSave;
public void Draw()
{
@ -42,7 +51,7 @@ internal partial class Interface
DrawActorSelector();
if (!_objects.TryGetValue(_identifier, out _currentData))
_currentData = ObjectManager.ActorData.Invalid;
_currentData = ActorData.Invalid;
else
_currentLabel = _currentData.Label;
@ -64,11 +73,12 @@ internal partial class Interface
return;
if (_currentData.Valid)
_currentSave.Initialize(_currentData.Objects[0]);
_currentSave.Initialize(_items, _currentData.Objects[0]);
RevertButton();
if (_main._customizationDrawer.Draw(_currentSave.Customize(), _identifier.Type == IdentifierType.Special))
_activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData, false);
_activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData,
false);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
@ -76,7 +86,8 @@ internal 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);
}
@ -95,9 +106,7 @@ internal partial class Interface
}
if (_main._equipmentDrawer.DrawVisor(_currentSave, out var value))
{
_activeDesigns.ChangeVisor(_currentSave, value, false);
}
}
private const uint RedHeaderColor = 0xFF1818C0;
@ -241,10 +250,10 @@ internal partial class Interface
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
}
private bool CheckFilter(KeyValuePair<ActorIdentifier, ObjectManager.ActorData> pair)
private bool CheckFilter(KeyValuePair<ActorIdentifier, ActorData> pair)
=> _actorFilter.IsEmpty || pair.Value.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
private void DrawSelectable(KeyValuePair<ActorIdentifier, ObjectManager.ActorData> pair)
private void DrawSelectable(KeyValuePair<ActorIdentifier, ActorData> pair)
{
var equal = pair.Key.Equals(_identifier);
if (ImGui.Selectable(pair.Value.Label, equal) && !equal)
@ -263,13 +272,13 @@ internal partial class Interface
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
, "Select the local player character.", !_objects.Player, true))
_identifier = _objects.Player.GetIdentifier();
_identifier = _objects.Player.GetIdentifier(_actors.AwaitedService);
ImGui.SameLine();
Actor targetActor = Dalamud.Targets.Target?.Address;
Actor targetActor = _targets.Target?.Address;
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth,
"Select the current target, if it is in the list.", _objects.IsInGPose || !targetActor, true))
_identifier = targetActor.GetIdentifier();
_identifier = targetActor.GetIdentifier(_actors.AwaitedService);
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.Util;
using ImGuiNET;
using OtterGui;
@ -7,31 +8,34 @@ using OtterGui.Raii;
namespace Glamourer.Gui;
internal partial class Interface
public partial class Interface
{
private class DebugDataTab
{
private readonly ICustomizationManager _mg;
private readonly CustomizationService _service;
public DebugDataTab(ICustomizationManager manager)
=> _mg = manager;
public DebugDataTab(CustomizationService service)
=> _service = service;
public void Draw()
{
if (!_service.Valid)
return;
using var tab = ImRaii.TabItem("Debug");
if (!tab)
return;
foreach (var clan in _mg.Clans)
foreach (var clan in _service.AwaitedService.Clans)
{
foreach (var gender in _mg.Genders)
DrawCustomizationInfo(_mg.GetList(clan, gender));
foreach (var gender in _service.AwaitedService.Genders)
DrawCustomizationInfo(_service.AwaitedService.GetList(clan, gender));
}
}
public static void DrawCustomizationInfo(CustomizationSet set)
public void DrawCustomizationInfo(CustomizationSet set)
{
if (!ImGui.CollapsingHeader($"{CustomizeExtensions.ClanName(set.Clan, set.Gender)} {set.Gender}"))
if (!ImGui.CollapsingHeader($"{CustomizeExtensions.ClanName(_service.AwaitedService, set.Clan, set.Gender)} {set.Gender}"))
return;
using var table = ImRaii.Table("data", 5);

View file

@ -11,7 +11,7 @@ using Penumbra.GameData.Actors;
namespace Glamourer.Gui;
internal partial class Interface
public partial class Interface
{
private class DebugStateTab
{

View file

@ -1,34 +1,31 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Designs;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui;
internal partial class Interface
public partial class Interface
{
private class DesignTab : IDisposable
{
public readonly DesignFileSystemSelector Selector;
private readonly Interface _main;
private readonly DesignFileSystem _fileSystem;
private readonly Design.Manager _manager;
private readonly Design.Manager _manager;
public DesignTab(Interface main, Design.Manager manager, DesignFileSystem fileSystem)
public DesignTab(Interface main, Design.Manager manager, DesignFileSystem fileSystem, KeyState keyState)
{
_main = main;
_manager = manager;
_fileSystem = fileSystem;
Selector = new DesignFileSystemSelector(manager, fileSystem);
Selector = new DesignFileSystemSelector(manager, fileSystem, keyState);
}
public void Dispose()

View file

@ -1,29 +1,28 @@
using System;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui;
internal partial class Interface
public partial class Interface
{
private static void Checkmark(string label, string tooltip, bool value, Action<bool> setter)
private void Checkmark(string label, string tooltip, bool value, Action<bool> setter)
{
if (ImGuiUtil.Checkbox(label, tooltip, value, setter))
Glamourer.Config.Save();
_config.Save();
}
private static void ChangeAndSave<T>(T value, T currentValue, Action<T> setter) where T : IEquatable<T>
private void ChangeAndSave<T>(T value, T currentValue, Action<T> setter) where T : IEquatable<T>
{
if (value.Equals(currentValue))
return;
setter(value);
Glamourer.Config.Save();
_config.Save();
}
private static void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action<uint> setter)
private void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action<uint> setter)
{
const ImGuiColorEditFlags flags = ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs;
@ -42,7 +41,7 @@ internal partial class Interface
private static void DrawRestorePenumbraButton()
{
const string buttonLabel = "Re-Register Penumbra";
//const string buttonLabel = "Re-Register Penumbra";
// TODO
//if (ImGui.Button(buttonLabel))
// Glamourer.Penumbra.Reattach(true);
@ -51,36 +50,35 @@ internal partial class Interface
// "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
}
private static void DrawSettingsTab()
private void DrawSettingsTab()
{
using var tab = ImRaii.TabItem("Settings");
if (!tab)
return;
var cfg = Glamourer.Config;
ImGui.Dummy(_spacing);
Checkmark("Folders First", "Sort Folders before all designs instead of lexicographically.", cfg.FoldersFirst,
v => cfg.FoldersFirst = v);
Checkmark("Folders First", "Sort Folders before all designs instead of lexicographically.", _config.FoldersFirst,
v => _config.FoldersFirst = v);
Checkmark("Color Designs", "Color the names of designs in the selector using the colors from below for the given cases.",
cfg.ColorDesigns,
v => cfg.ColorDesigns = v);
Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks,
v => cfg.ShowLocks = v);
_config.ColorDesigns,
v => _config.ColorDesigns = v);
Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", _config.ShowLocks,
v => _config.ShowLocks = v);
DrawRestorePenumbraButton();
Checkmark("Apply Fixed Designs",
"Automatically apply fixed designs to characters and redraw them when anything changes.",
cfg.ApplyFixedDesigns,
v => { cfg.ApplyFixedDesigns = v; });
_config.ApplyFixedDesigns,
v => { _config.ApplyFixedDesigns = v; });
ImGui.Dummy(_spacing);
DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.",
cfg.CustomizationColor, GlamourerConfig.DefaultCustomizationColor, c => cfg.CustomizationColor = c);
_config.CustomizationColor, Configuration.DefaultCustomizationColor, c => _config.CustomizationColor = c);
DrawColorPicker("Equipment Color", "The color for designs that only apply some or all of their equipment slots and stains.",
cfg.EquipmentColor, GlamourerConfig.DefaultEquipmentColor, c => cfg.EquipmentColor = c);
_config.EquipmentColor, Configuration.DefaultEquipmentColor, c => _config.EquipmentColor = c);
DrawColorPicker("State Color", "The color for designs that only apply some state modification.",
cfg.StateColor, GlamourerConfig.DefaultStateColor, c => cfg.StateColor = c);
_config.StateColor, Configuration.DefaultStateColor, c => _config.StateColor = c);
}
}

View file

@ -4,7 +4,7 @@ using ImGuiNET;
namespace Glamourer.Gui;
internal partial class Interface
public partial class Interface
{
private static Vector2 _spacing = Vector2.Zero;
private static float _actorSelectorWidth;

View file

@ -1,5 +1,8 @@
using System;
using System.Numerics;
using Dalamud.Data;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Plugin;
@ -7,43 +10,43 @@ using Glamourer.Designs;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Util;
using ImGuiNET;
using OtterGui.Raii;
namespace Glamourer.Gui;
internal partial class Interface : Window, IDisposable
public partial class Interface : Window, IDisposable
{
private readonly DalamudPluginInterface _pi;
private readonly EquipmentDrawer _equipmentDrawer;
private readonly CustomizationDrawer _customizationDrawer;
private readonly ActorTab _actorTab;
private readonly DesignTab _designTab;
private readonly DebugStateTab _debugStateTab;
private readonly DebugDataTab _debugDataTab;
private readonly Configuration _config;
private readonly ActorTab _actorTab;
private readonly DesignTab _designTab;
private readonly DebugStateTab _debugStateTab;
private readonly DebugDataTab _debugDataTab;
public Interface(DalamudPluginInterface pi, ItemManager items, ActiveDesign.Manager activeDesigns, Design.Manager manager,
DesignFileSystem fileSystem, ObjectManager objects)
DesignFileSystem fileSystem, ObjectManager objects, CustomizationService customization, Configuration config, DataManager gameData, TargetManager targets, ActorService actors, KeyState keyState)
: base(GetLabel())
{
_pi = pi;
_equipmentDrawer = new EquipmentDrawer(items);
_customizationDrawer = new CustomizationDrawer(pi);
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle;
_pi = pi;
_config = config;
_equipmentDrawer = new EquipmentDrawer(gameData, items);
_customizationDrawer = new CustomizationDrawer(pi, customization, items);
pi.UiBuilder.DisableGposeUiHide = true;
SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new Vector2(675, 675),
MaximumSize = ImGui.GetIO().DisplaySize,
};
_actorTab = new ActorTab(this, activeDesigns, objects);
_actorTab = new ActorTab(this, activeDesigns, objects, targets, actors, items);
_debugStateTab = new DebugStateTab(activeDesigns);
_debugDataTab = new DebugDataTab(Glamourer.Customization);
_designTab = new DesignTab(this, manager, fileSystem);
_debugDataTab = new DebugDataTab(customization);
_designTab = new DesignTab(this, manager, fileSystem, keyState);
}
public override void Draw()

View file

@ -1,7 +1,7 @@

namespace Glamourer.Gui;
internal partial class Interface
public partial class Interface
{
//private readonly CharacterSave _currentSave = new();
//private string _newDesignName = string.Empty;

View file

@ -24,14 +24,14 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
public static implicit operator IntPtr(Actor actor)
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
public ActorIdentifier GetIdentifier()
=> Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true, false);
public ActorIdentifier GetIdentifier(ActorManager actors)
=> actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true, false);
public bool Identifier(out ActorIdentifier ident)
public bool Identifier(ActorManager actors, out ActorIdentifier ident)
{
if (Valid)
{
ident = GetIdentifier();
ident = GetIdentifier(actors);
return true;
}
@ -39,9 +39,6 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
return false;
}
public Character? Character
=> Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character;
public bool IsAvailable
=> Pointer->GameObject.GetIsTargetable();

View file

@ -4,35 +4,50 @@ using System.Collections.Generic;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Glamourer.Services;
using Penumbra.GameData.Actors;
namespace Glamourer.Interop;
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ObjectManager.ActorData>
public readonly struct ActorData
{
public readonly struct ActorData
public readonly List<Actor> Objects;
public readonly string Label;
public bool Valid
=> Objects.Count > 0;
public ActorData(Actor actor, string label)
{
public readonly List<Actor> Objects;
public readonly string Label;
public bool Valid
=> Objects.Count > 0;
public ActorData(Actor actor, string label)
{
Objects = new List<Actor> { actor };
Label = label;
}
public static readonly ActorData Invalid = new(false);
private ActorData(bool _)
{
Objects = new List<Actor>(0);
Label = string.Empty;
}
Objects = new List<Actor> { actor };
Label = label;
}
public static readonly ActorData Invalid = new(false);
private ActorData(bool _)
{
Objects = new List<Actor>(0);
Label = string.Empty;
}
}
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
{
private readonly Framework _framework;
private readonly ClientState _clientState;
private readonly ObjectTable _objects;
private readonly ActorService _actors;
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects, ActorService actors)
{
_framework = framework;
_clientState = clientState;
_objects = objects;
_actors = actors;
}
public DateTime LastUpdate { get; private set; }
public bool IsInGPose { get; private set; }
@ -56,16 +71,6 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ObjectManager.
}
}
private readonly Framework _framework;
private readonly ClientState _clientState;
private readonly ObjectTable _objects;
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects)
{
_framework = framework;
_clientState = clientState;
_objects = objects;
}
public void Update()
{
@ -80,14 +85,14 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ObjectManager.
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
{
Actor character = _objects.GetObjectAddress(i);
if (character.Identifier(out var identifier))
if (character.Identifier(_actors.AwaitedService, out var identifier))
HandleIdentifier(identifier, character);
}
for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i)
{
Actor character = _objects.GetObjectAddress(i);
if (!character.Identifier(out var identifier))
if (!character.Identifier(_actors.AwaitedService, out var identifier))
break;
HandleIdentifier(identifier, character);
@ -96,7 +101,7 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ObjectManager.
void AddSpecial(ScreenActor idx, string label)
{
Actor actor = _objects.GetObjectAddress((int)idx);
if (actor.Identifier(out var ident))
if (actor.Identifier(_actors.AwaitedService, out var ident))
{
var data = new ActorData(actor, label);
_identifiers.Add(ident, data);
@ -112,10 +117,10 @@ public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ObjectManager.
AddSpecial(ScreenActor.Card7, "Card Actor 7");
AddSpecial(ScreenActor.Card8, "Card Actor 8");
for (var i = (int)ScreenActor.ScreenEnd; i < Dalamud.Objects.Length; ++i)
for (var i = (int)ScreenActor.ScreenEnd; i < _objects.Length; ++i)
{
Actor character = _objects.GetObjectAddress(i);
if (character.Identifier(out var identifier))
if (character.Identifier(_actors.AwaitedService, out var identifier))
HandleIdentifier(identifier, character);
}

View file

@ -34,8 +34,8 @@ public unsafe partial class RedrawManager
var character = (Actor)(characterOffset - CharacterWeaponOffset);
try
{
var identifier = character.GetIdentifier();
if (_fixedDesigns.TryGetDesign(identifier, out var save))
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

View file

@ -6,6 +6,7 @@ using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using Penumbra.GameData.Enums;
@ -16,8 +17,11 @@ namespace Glamourer.Interop;
public partial class Interop : IDisposable
{
public Interop()
private readonly JobService _jobService;
public Interop(JobService jobService)
{
_jobService = jobService;
SignatureHelper.Initialise(this);
_changeJobHook.Enable();
_flagSlotForUpdateHook.Enable();
@ -187,7 +191,7 @@ public partial class Interop
private void ChangeJobDetour(IntPtr data, uint job)
{
_changeJobHook.Original(data, job);
JobChanged?.Invoke(data - Offsets.Character.ClassJobContainer, GameData.Jobs(Dalamud.GameData)[(byte)job]);
JobChanged?.Invoke(data - Offsets.Character.ClassJobContainer, _jobService.Jobs[(byte)job]);
}
public event Action<Actor, Job>? JobChanged;
@ -195,14 +199,18 @@ public partial class Interop
public unsafe partial class RedrawManager : IDisposable
{
private readonly FixedDesigns _fixedDesigns;
private readonly ItemManager _items;
private readonly ActorService _actors;
private readonly FixedDesignManager _fixedDesignManager;
private readonly ActiveDesign.Manager _stateManager;
public RedrawManager(FixedDesigns fixedDesigns, ActiveDesign.Manager stateManager)
public RedrawManager(FixedDesignManager fixedDesignManager, ActiveDesign.Manager stateManager, ItemManager items, ActorService actors)
{
SignatureHelper.Initialise(this);
_fixedDesigns = fixedDesigns;
_stateManager = stateManager;
_fixedDesignManager = fixedDesignManager;
_stateManager = stateManager;
_items = items;
_actors = actors;
_flagSlotForUpdateHook.Enable();
_loadWeaponHook.Enable();
}
@ -221,8 +229,8 @@ public unsafe partial class RedrawManager : IDisposable
return;
// Check if we have a current design in use, or if not if the actor has a fixed design.
var identifier = actor.GetIdentifier();
if (!(_stateManager.TryGetValue(identifier, out var save) || _fixedDesigns.TryGetDesign(identifier, out var save2)))
var identifier = actor.GetIdentifier(_actors.AwaitedService);
if (!(_stateManager.TryGetValue(identifier, out var save) || _fixedDesignManager.TryGetDesign(identifier, out var save2)))
return;
// Compare game object customize data against draw object customize data for transformations.
@ -240,7 +248,7 @@ public unsafe partial class RedrawManager : IDisposable
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
(_, equip[slot]) =
Glamourer.Items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender);
_items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender);
}
}
}

View file

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.IO;
using OtterGui.Classes;
using OtterGui.Log;
namespace Glamourer.Services;
public class BackupService
{
public BackupService(Logger logger, FilenameService fileNames)
{
var files = GlamourerFiles(fileNames);
Backup.CreateBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), files);
}
/// <summary> Collect all relevant files for glamourer configuration. </summary>
private static IReadOnlyList<FileInfo> GlamourerFiles(FilenameService fileNames)
{
var list = new List<FileInfo>(16)
{
new(fileNames.ConfigFile),
new(fileNames.DesignFileSystem),
new(fileNames.MigrationDesignFile),
};
list.AddRange(fileNames.Designs());
return list;
}
}

View file

@ -0,0 +1,36 @@
using System;
using Dalamud.Game.Command;
using Glamourer.Gui;
namespace Glamourer.Services;
public class CommandService : IDisposable
{
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
private const string MainCommandString = "/glamourer";
private const string ApplyCommandString = "/glamour";
private readonly CommandManager _commands;
private readonly Interface _interface;
public CommandService(CommandManager commands, Interface ui)
{
_commands = commands;
_interface = ui;
_commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." });
_commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour) { HelpMessage = $"Use Glamourer Functions: {HelpString}" });
}
public void Dispose()
{
_commands.RemoveHandler(MainCommandString);
_commands.RemoveHandler(ApplyCommandString);
}
private void OnGlamourer(string command, string arguments)
=> _interface.Toggle();
private void OnGlamour(string command, string arguments)
{ }
}

View file

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.IO;
using Dalamud.Plugin;
using Glamourer.Designs;
namespace Glamourer.Services;
public class FilenameService
{
public readonly string ConfigDirectory;
public readonly string ConfigFile;
public readonly string DesignFileSystem;
public readonly string MigrationDesignFile;
public readonly string DesignDirectory;
public FilenameService(DalamudPluginInterface pi)
{
ConfigDirectory = pi.ConfigDirectory.FullName;
ConfigFile = pi.ConfigFile.FullName;
DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json");
MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json");
DesignDirectory = Path.Combine(ConfigDirectory, "designs");
}
public IEnumerable<FileInfo> Designs()
{
if (!Directory.Exists(DesignDirectory))
yield break;
foreach (var file in Directory.EnumerateFiles(DesignDirectory, "*.json", SearchOption.TopDirectoryOnly))
yield return new FileInfo(file);
}
public string DesignFile(Design design)
=> DesignFile(design.Identifier.ToString());
public string DesignFile(string identifier)
=> Path.Combine(DesignDirectory, $"{identifier}.json");
}

View file

@ -6,13 +6,13 @@ using Dalamud.Plugin;
using Dalamud.Utility;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData;
using Lumina.Text;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.Util;
namespace Glamourer.Services;
public class ItemManager : IDisposable
{
@ -20,33 +20,33 @@ public class ItemManager : IDisposable
public const string SmallClothesNpc = "Smallclothes (NPC)";
public const ushort SmallClothesNpcModel = 9903;
public readonly IObjectIdentifier Identifier;
public readonly ExcelSheet<Item> ItemSheet;
public readonly StainData Stains;
public readonly ItemData Items;
public readonly RestrictedGear RestrictedGear;
private readonly Configuration _config;
public readonly IdentifierService IdentifierService;
public readonly ExcelSheet<Item> ItemSheet;
public readonly StainData Stains;
public readonly ItemService ItemService;
public readonly RestrictedGear RestrictedGear;
public ItemManager(DalamudPluginInterface pi, DataManager gameData)
public ItemManager(DalamudPluginInterface pi, DataManager gameData, IdentifierService identifierService, ItemService itemService, Configuration config)
{
ItemSheet = gameData.GetExcelSheet<Item>()!;
Identifier = Penumbra.GameData.GameData.GetIdentifier(pi, gameData, gameData.Language);
Stains = new StainData(pi, gameData, gameData.Language);
Items = new ItemData(pi, gameData, gameData.Language);
RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData);
DefaultSword = ItemSheet.GetRow(1601)!; // Weathered Shortsword
_config = config;
ItemSheet = gameData.GetExcelSheet<Item>()!;
IdentifierService = identifierService;
Stains = new StainData(pi, gameData, gameData.Language);
ItemService = itemService;
RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData);
DefaultSword = ItemSheet.GetRow(1601)!; // Weathered Shortsword
}
public void Dispose()
{
Stains.Dispose();
Items.Dispose();
Identifier.Dispose();
RestrictedGear.Dispose();
}
public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
{
if (Glamourer.Config.UseRestrictedGearProtection)
if (_config.UseRestrictedGearProtection)
return RestrictedGear.ResolveRestricted(armor, slot, race, gender);
return (false, armor);
@ -152,7 +152,7 @@ public class ItemManager : IDisposable
case 0: return (true, NothingId(slot), Nothing);
case SmallClothesNpcModel: return (true, SmallclothesId(slot), SmallClothesNpc);
default:
var item = Identifier.Identify(id, variant, slot).FirstOrDefault();
var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault();
return item == null
? (false, 0, string.Intern($"Unknown ({id.Value}-{variant})"))
: (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue));
@ -166,7 +166,7 @@ public class ItemManager : IDisposable
{
case EquipSlot.MainHand:
{
var item = Identifier.Identify(id, type, variant, slot).FirstOrDefault();
var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault();
return item != null
? (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue), item.ToEquipType())
: (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"), mainhandType);
@ -177,7 +177,7 @@ public class ItemManager : IDisposable
if (id.Value == 0)
return (true, NothingId(weaponType), Nothing, weaponType);
var item = Identifier.Identify(id, type, variant, slot).FirstOrDefault();
var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault();
return item != null
? (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue), item.ToEquipType())
: (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"),

View file

@ -0,0 +1,16 @@
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);
}
}

View file

@ -0,0 +1,97 @@
using System;
using System.IO;
using System.Text;
using OtterGui.Classes;
using OtterGui.Log;
namespace Glamourer.Services;
/// <summary>
/// Any file type that we want to save via SaveService.
/// </summary>
public interface ISavable
{
/// <summary> The full file name of a given object. </summary>
public string ToFilename(FilenameService fileNames);
/// <summary> Write the objects data to the given stream writer. </summary>
public void Save(StreamWriter writer);
/// <summary> An arbitrary message printed to Debug before saving. </summary>
public string LogName(string fileName)
=> fileName;
public string TypeName
=> GetType().Name;
}
public class SaveService
{
private readonly Logger _log;
private readonly FrameworkManager _framework;
public readonly FilenameService FileNames;
public SaveService(Logger log, FrameworkManager framework, FilenameService fileNames)
{
_log = log;
_framework = framework;
FileNames = fileNames;
}
/// <summary> Queue a save for the next framework tick. </summary>
public void QueueSave(ISavable value)
{
var file = value.ToFilename(FileNames);
_framework.RegisterDelayed(value.GetType().Name + file, () =>
{
ImmediateSave(value);
});
}
/// <summary> Immediately trigger a save. </summary>
public void ImmediateSave(ISavable value)
{
var name = value.ToFilename(FileNames);
try
{
if (name.Length == 0)
{
throw new Exception("Invalid object returned empty filename.");
}
_log.Debug($"Saving {value.TypeName} {value.LogName(name)}...");
var file = new FileInfo(name);
file.Directory?.Create();
using var s = file.Exists ? file.Open(FileMode.Truncate) : file.Open(FileMode.CreateNew);
using var w = new StreamWriter(s, Encoding.UTF8);
value.Save(w);
}
catch (Exception ex)
{
_log.Error($"Could not save {value.GetType().Name} {value.LogName(name)}:\n{ex}");
}
}
public void ImmediateDelete(ISavable value)
{
var name = value.ToFilename(FileNames);
try
{
if (name.Length == 0)
{
throw new Exception("Invalid object returned empty filename.");
}
if (!File.Exists(name))
return;
_log.Information($"Deleting {value.GetType().Name} {value.LogName(name)}...");
File.Delete(name);
}
catch (Exception ex)
{
_log.Error($"Could not delete {value.GetType().Name} {value.LogName(name)}:\n{ex}");
}
}
}

View file

@ -0,0 +1,76 @@
using Dalamud.Plugin;
using Glamourer.Api;
using Glamourer.Designs;
using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.State;
using Microsoft.Extensions.DependencyInjection;
using OtterGui.Classes;
using OtterGui.Log;
namespace Glamourer.Services;
public static class ServiceManager
{
public static ServiceProvider CreateProvider(DalamudPluginInterface pi, Logger log)
{
var services = new ServiceCollection()
.AddSingleton(log)
.AddDalamud(pi)
.AddMeta()
.AddConfig()
.AddPenumbra()
.AddInterop()
.AddGameData()
.AddDesigns()
.AddInterface()
.AddApi();
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
}
private static IServiceCollection AddDalamud(this IServiceCollection services, DalamudPluginInterface pi)
{
new DalamudServices(pi).AddServices(services);
return services;
}
private static IServiceCollection AddMeta(this IServiceCollection services)
=> services.AddSingleton<FilenameService>()
.AddSingleton<SaveService>()
.AddSingleton<FrameworkManager>()
.AddSingleton<ChatService>();
private static IServiceCollection AddConfig(this IServiceCollection services)
=> services.AddSingleton<Configuration>()
.AddSingleton<BackupService>();
private static IServiceCollection AddPenumbra(this IServiceCollection services)
=> services.AddSingleton<PenumbraAttach>();
private static IServiceCollection AddGameData(this IServiceCollection services)
=> services.AddSingleton<IdentifierService>()
.AddSingleton<ActorService>()
.AddSingleton<ItemService>()
.AddSingleton<ItemManager>()
.AddSingleton<CustomizationService>()
.AddSingleton<JobService>();
private static IServiceCollection AddInterop(this IServiceCollection services)
=> services.AddSingleton<Interop.Interop>()
.AddSingleton<ObjectManager>();
private static IServiceCollection AddDesigns(this IServiceCollection services)
=> services.AddSingleton<Design.Manager>()
.AddSingleton<DesignFileSystem>()
.AddSingleton<ActiveDesign.Manager>()
.AddSingleton<FixedDesignManager>();
private static IServiceCollection AddInterface(this IServiceCollection services)
=> services.AddSingleton<Interface>()
.AddSingleton<GlamourerWindowSystem>();
private static IServiceCollection AddApi(this IServiceCollection services)
=> services.AddSingleton<CommandService>()
.AddSingleton<Glamourer.GlamourerIpc>();
}

View file

@ -0,0 +1,105 @@
using Dalamud.Data;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState;
using Dalamud.Game.Gui;
using Dalamud.Plugin;
using Penumbra.GameData.Actors;
using System;
using System.Threading.Tasks;
using Dalamud.Game;
using Glamourer.Api;
using Glamourer.Customization;
using Penumbra.GameData.Data;
using Penumbra.GameData;
namespace Glamourer.Services;
public abstract class AsyncServiceWrapper<T>
{
public string Name { get; }
public T? Service { get; private set; }
public T AwaitedService
{
get
{
_task?.Wait();
return Service!;
}
}
public bool Valid
=> Service != null && !_isDisposed;
public event Action? FinishedCreation;
private Task? _task;
private bool _isDisposed;
protected AsyncServiceWrapper(string name, Func<T> factory)
{
Name = name;
_task = Task.Run(() =>
{
var service = factory();
if (_isDisposed)
{
if (service is IDisposable d)
d.Dispose();
}
else
{
Service = service;
Glamourer.Log.Verbose($"[{Name}] Created.");
_task = null;
}
});
_task.ContinueWith((t, x) =>
{
if (!_isDisposed)
FinishedCreation?.Invoke();
}, null);
}
public void Dispose()
{
if (_isDisposed)
return;
_isDisposed = true;
_task = null;
if (Service is IDisposable d)
d.Dispose();
Glamourer.Log.Verbose($"[{Name}] Disposed.");
}
}
public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier>
{
public IdentifierService(DalamudPluginInterface pi, DataManager data)
: base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data))
{ }
}
public sealed class ItemService : AsyncServiceWrapper<ItemData>
{
public ItemService(DalamudPluginInterface pi, DataManager gameData)
: base(nameof(ItemService), () => new ItemData(pi, gameData, gameData.Language))
{ }
}
public sealed class ActorService : AsyncServiceWrapper<ActorManager>
{
public ActorService(DalamudPluginInterface pi, ObjectTable objects, ClientState clientState, Framework framework, DataManager gameData,
GameGui gui, PenumbraAttach penumbra)
: base(nameof(ActorService),
() => new ActorManager(pi, objects, clientState, framework, gameData, gui, idx => (short)penumbra.CutsceneParent(idx)))
{ }
}
public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationManager>
{
public CustomizationService(DalamudPluginInterface pi, DataManager gameData)
: base(nameof(CustomizationService), () => CustomizationManager.Create(pi, gameData))
{ }
}

View file

@ -6,16 +6,14 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Api;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Services;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
using Item = Glamourer.Designs.Item;
using Lumina.Excel.GeneratedSheets;
namespace Glamourer.State;
@ -23,19 +21,21 @@ public sealed partial class ActiveDesign
{
public partial class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
{
private readonly ActorManager _actors;
private readonly ActorService _actors;
private readonly ObjectManager _objects;
private readonly Interop.Interop _interop;
private readonly PenumbraAttach _penumbra;
private readonly ItemManager _items;
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
public Manager(ActorManager actors, ObjectManager objects, Interop.Interop interop, PenumbraAttach penumbra)
public Manager(ActorService actors, ObjectManager objects, Interop.Interop interop, PenumbraAttach penumbra, ItemManager items)
{
_actors = actors;
_objects = objects;
_interop = interop;
_penumbra = penumbra;
_items = items;
}
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
@ -67,16 +67,16 @@ public sealed partial class ActiveDesign
public unsafe ActiveDesign GetOrCreateSave(Actor actor)
{
var id = _actors.FromObject((GameObject*)actor.Pointer, out _, false, false, false);
var id = _actors.AwaitedService.FromObject((GameObject*)actor.Pointer, out _, false, false, false);
if (_characterSaves.TryGetValue(id, out var save))
{
save.Initialize(actor);
save.Initialize(_items, actor);
return save;
}
id = id.CreatePermanent();
save = new ActiveDesign(id, actor);
save.Initialize(actor);
save = new ActiveDesign(_items, id, actor);
save.Initialize(_items, actor);
_characterSaves.Add(id, save);
return save;
}
@ -100,7 +100,7 @@ public sealed partial class ActiveDesign
if (from.DoApplyEquip(EquipSlot.MainHand))
ChangeMainHand(to, from.MainHand, fromFixed);
if (from.DoApplyEquip(EquipSlot.OffHand))
ChangeOffHand(to, from.OffHand, fromFixed);
@ -130,10 +130,10 @@ public sealed partial class ActiveDesign
}
public void ChangeMainHand(ActiveDesign design, uint itemId, bool fromFixed)
=> design.SetMainhand(itemId);
=> design.SetMainhand(_items, itemId);
public void ChangeOffHand(ActiveDesign design, uint itemId, bool fromFixed)
=> design.SetOffhand(itemId);
=> design.SetOffhand(_items, itemId);
public void RevertMainHand(ActiveDesign design)
{ }
@ -187,7 +187,7 @@ public sealed partial class ActiveDesign
if (equip)
{
var flag = slot.ToFlag();
design.UpdateArmor(slot, item, true);
design.UpdateArmor(_items, slot, item, true);
design.ChangedEquip &= ~flag;
design.FixedEquip &= ~flag;
}
@ -265,7 +265,7 @@ public sealed partial class ActiveDesign
if (!_objects.TryGetValue(design.Identifier, out var data))
return;
foreach (var obj in data.Objects)
foreach (var obj in data.Objects)
Interop.Interop.SetVisorState(obj.DrawObject, on);
}
}

View file

@ -1,9 +1,7 @@
using System;
using System.Linq;
using Glamourer.Customization;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Interop;
using Penumbra.Api.Enums;
using Glamourer.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
@ -26,13 +24,15 @@ public sealed partial class ActiveDesign : DesignBase
public bool IsVisorToggled { get; private set; } = false;
public bool IsWet { get; private set; } = false;
private ActiveDesign(ActorIdentifier identifier)
private ActiveDesign(ItemManager items, ActorIdentifier identifier)
: base(items)
=> Identifier = identifier;
public ActiveDesign(ActorIdentifier identifier, Actor actor)
public ActiveDesign(ItemManager items, ActorIdentifier identifier, Actor actor)
: base(items)
{
Identifier = identifier;
Initialize(actor);
Initialize(items, actor);
}
//public void ApplyToActor(Actor actor)
@ -65,7 +65,7 @@ public sealed partial class ActiveDesign : DesignBase
// RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled);
//}
//
public void Initialize(Actor actor)
public void Initialize(ItemManager items, Actor actor)
{
if (!actor)
return;
@ -84,7 +84,7 @@ public sealed partial class ActiveDesign : DesignBase
if (initialEquip[slot] != current)
{
initialEquip[slot] = current;
UpdateArmor(slot, current, true);
UpdateArmor(items, slot, current, true);
SetStain(slot, current.Stain);
}
}
@ -92,14 +92,14 @@ public sealed partial class ActiveDesign : DesignBase
if (_initialData.MainHand != actor.MainHand)
{
_initialData.MainHand = actor.MainHand;
UpdateMainhand(actor.MainHand);
UpdateMainhand(items, actor.MainHand);
SetStain(EquipSlot.MainHand, actor.MainHand.Stain);
}
if (_initialData.OffHand != actor.OffHand)
{
_initialData.OffHand = actor.OffHand;
UpdateOffhand(actor.OffHand);
UpdateOffhand(items, actor.OffHand);
SetStain(EquipSlot.OffHand, actor.OffHand.Stain);
}

View file

@ -5,7 +5,7 @@ using Penumbra.GameData.Actors;
namespace Glamourer.State;
public class FixedDesigns
public class FixedDesignManager
{
public bool TryGetDesign(ActorIdentifier actor, [NotNullWhen(true)] out Design? save)
{

View file

@ -1,47 +0,0 @@
using System.Collections.Generic;
using Dalamud.Configuration;
namespace Glamourer.State;
public class GlamourerConfig : IPluginConfiguration
{
public class FixedDesign
{
public string Name = string.Empty;
public string Path = string.Empty;
public uint JobGroups;
public bool Enabled;
}
public int Version { get; set; } = 1;
public const uint DefaultCustomizationColor = 0xFFC000C0;
public const uint DefaultStateColor = 0xFF00C0C0;
public const uint DefaultEquipmentColor = 0xFF00C000;
public bool UseRestrictedGearProtection { get; set; } = true;
public bool FoldersFirst { get; set; } = false;
public bool ColorDesigns { get; set; } = true;
public bool ShowLocks { get; set; } = true;
public bool ApplyFixedDesigns { get; set; } = true;
public uint CustomizationColor { get; set; } = DefaultCustomizationColor;
public uint StateColor { get; set; } = DefaultStateColor;
public uint EquipmentColor { get; set; } = DefaultEquipmentColor;
public List<FixedDesign> FixedDesigns { get; set; } = new();
public void Save()
=> Dalamud.PluginInterface.SavePluginConfig(this);
public static GlamourerConfig Load()
{
if (Dalamud.PluginInterface.GetPluginConfig() is GlamourerConfig config)
return config;
config = new GlamourerConfig();
config.Save();
return config;
}
}

View file

@ -1,11 +0,0 @@
using Glamourer.Designs;
using Glamourer.Interop;
namespace Glamourer.State;
public interface IDesign
{
public ref CharacterData Data { get; }
public void ApplyToActor(Actor a);
}

View file

@ -1,5 +1,6 @@
using System;
using Glamourer.Customization;
using Glamourer.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -8,7 +9,7 @@ namespace Glamourer.Util;
public static class CustomizeExtensions
{
// In languages other than english the actual clan name may depend on gender.
public static string ClanName(SubRace race, Gender gender)
public static string ClanName(ICustomizationManager customization, SubRace race, Gender gender)
{
if (gender == Gender.FemaleNpc)
gender = Gender.Female;
@ -16,79 +17,79 @@ public static class CustomizeExtensions
gender = Gender.Male;
return (gender, race) switch
{
(Gender.Male, SubRace.Midlander) => Glamourer.Customization.GetName(CustomName.MidlanderM),
(Gender.Male, SubRace.Highlander) => Glamourer.Customization.GetName(CustomName.HighlanderM),
(Gender.Male, SubRace.Wildwood) => Glamourer.Customization.GetName(CustomName.WildwoodM),
(Gender.Male, SubRace.Duskwight) => Glamourer.Customization.GetName(CustomName.DuskwightM),
(Gender.Male, SubRace.Plainsfolk) => Glamourer.Customization.GetName(CustomName.PlainsfolkM),
(Gender.Male, SubRace.Dunesfolk) => Glamourer.Customization.GetName(CustomName.DunesfolkM),
(Gender.Male, SubRace.SeekerOfTheSun) => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunM),
(Gender.Male, SubRace.KeeperOfTheMoon) => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonM),
(Gender.Male, SubRace.Seawolf) => Glamourer.Customization.GetName(CustomName.SeawolfM),
(Gender.Male, SubRace.Hellsguard) => Glamourer.Customization.GetName(CustomName.HellsguardM),
(Gender.Male, SubRace.Raen) => Glamourer.Customization.GetName(CustomName.RaenM),
(Gender.Male, SubRace.Xaela) => Glamourer.Customization.GetName(CustomName.XaelaM),
(Gender.Male, SubRace.Helion) => Glamourer.Customization.GetName(CustomName.HelionM),
(Gender.Male, SubRace.Lost) => Glamourer.Customization.GetName(CustomName.LostM),
(Gender.Male, SubRace.Rava) => Glamourer.Customization.GetName(CustomName.RavaM),
(Gender.Male, SubRace.Veena) => Glamourer.Customization.GetName(CustomName.VeenaM),
(Gender.Female, SubRace.Midlander) => Glamourer.Customization.GetName(CustomName.MidlanderF),
(Gender.Female, SubRace.Highlander) => Glamourer.Customization.GetName(CustomName.HighlanderF),
(Gender.Female, SubRace.Wildwood) => Glamourer.Customization.GetName(CustomName.WildwoodF),
(Gender.Female, SubRace.Duskwight) => Glamourer.Customization.GetName(CustomName.DuskwightF),
(Gender.Female, SubRace.Plainsfolk) => Glamourer.Customization.GetName(CustomName.PlainsfolkF),
(Gender.Female, SubRace.Dunesfolk) => Glamourer.Customization.GetName(CustomName.DunesfolkF),
(Gender.Female, SubRace.SeekerOfTheSun) => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunF),
(Gender.Female, SubRace.KeeperOfTheMoon) => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonF),
(Gender.Female, SubRace.Seawolf) => Glamourer.Customization.GetName(CustomName.SeawolfF),
(Gender.Female, SubRace.Hellsguard) => Glamourer.Customization.GetName(CustomName.HellsguardF),
(Gender.Female, SubRace.Raen) => Glamourer.Customization.GetName(CustomName.RaenF),
(Gender.Female, SubRace.Xaela) => Glamourer.Customization.GetName(CustomName.XaelaF),
(Gender.Female, SubRace.Helion) => Glamourer.Customization.GetName(CustomName.HelionM),
(Gender.Female, SubRace.Lost) => Glamourer.Customization.GetName(CustomName.LostM),
(Gender.Female, SubRace.Rava) => Glamourer.Customization.GetName(CustomName.RavaF),
(Gender.Female, SubRace.Veena) => Glamourer.Customization.GetName(CustomName.VeenaF),
(Gender.Male, SubRace.Midlander) => customization.GetName(CustomName.MidlanderM),
(Gender.Male, SubRace.Highlander) => customization.GetName(CustomName.HighlanderM),
(Gender.Male, SubRace.Wildwood) => customization.GetName(CustomName.WildwoodM),
(Gender.Male, SubRace.Duskwight) => customization.GetName(CustomName.DuskwightM),
(Gender.Male, SubRace.Plainsfolk) => customization.GetName(CustomName.PlainsfolkM),
(Gender.Male, SubRace.Dunesfolk) => customization.GetName(CustomName.DunesfolkM),
(Gender.Male, SubRace.SeekerOfTheSun) => customization.GetName(CustomName.SeekerOfTheSunM),
(Gender.Male, SubRace.KeeperOfTheMoon) => customization.GetName(CustomName.KeeperOfTheMoonM),
(Gender.Male, SubRace.Seawolf) => customization.GetName(CustomName.SeawolfM),
(Gender.Male, SubRace.Hellsguard) => customization.GetName(CustomName.HellsguardM),
(Gender.Male, SubRace.Raen) => customization.GetName(CustomName.RaenM),
(Gender.Male, SubRace.Xaela) => customization.GetName(CustomName.XaelaM),
(Gender.Male, SubRace.Helion) => customization.GetName(CustomName.HelionM),
(Gender.Male, SubRace.Lost) => customization.GetName(CustomName.LostM),
(Gender.Male, SubRace.Rava) => customization.GetName(CustomName.RavaM),
(Gender.Male, SubRace.Veena) => customization.GetName(CustomName.VeenaM),
(Gender.Female, SubRace.Midlander) => customization.GetName(CustomName.MidlanderF),
(Gender.Female, SubRace.Highlander) => customization.GetName(CustomName.HighlanderF),
(Gender.Female, SubRace.Wildwood) => customization.GetName(CustomName.WildwoodF),
(Gender.Female, SubRace.Duskwight) => customization.GetName(CustomName.DuskwightF),
(Gender.Female, SubRace.Plainsfolk) => customization.GetName(CustomName.PlainsfolkF),
(Gender.Female, SubRace.Dunesfolk) => customization.GetName(CustomName.DunesfolkF),
(Gender.Female, SubRace.SeekerOfTheSun) => customization.GetName(CustomName.SeekerOfTheSunF),
(Gender.Female, SubRace.KeeperOfTheMoon) => customization.GetName(CustomName.KeeperOfTheMoonF),
(Gender.Female, SubRace.Seawolf) => customization.GetName(CustomName.SeawolfF),
(Gender.Female, SubRace.Hellsguard) => customization.GetName(CustomName.HellsguardF),
(Gender.Female, SubRace.Raen) => customization.GetName(CustomName.RaenF),
(Gender.Female, SubRace.Xaela) => customization.GetName(CustomName.XaelaF),
(Gender.Female, SubRace.Helion) => customization.GetName(CustomName.HelionM),
(Gender.Female, SubRace.Lost) => customization.GetName(CustomName.LostM),
(Gender.Female, SubRace.Rava) => customization.GetName(CustomName.RavaF),
(Gender.Female, SubRace.Veena) => customization.GetName(CustomName.VeenaF),
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
};
}
public static string ClanName(this Customize customize)
=> ClanName(customize.Clan, customize.Gender);
public static string ClanName(this Customize customize, ICustomizationManager customization)
=> ClanName(customization, customize.Clan, customize.Gender);
// Change a gender and fix up all required customizations afterwards.
public static CustomizeFlag ChangeGender(this Customize customize, CharacterEquip equip, Gender gender)
public static CustomizeFlag ChangeGender(this Customize customize, CharacterEquip equip, Gender gender, ItemManager items, ICustomizationManager customization)
{
if (customize.Gender == gender)
return 0;
FixRestrictedGear(customize, equip, gender, customize.Race);
FixRestrictedGear(items, customize, equip, gender, customize.Race);
customize.Gender = gender;
return CustomizeFlag.Gender | FixUpAttributes(customize);
return CustomizeFlag.Gender | FixUpAttributes(customization, customize);
}
// Change a race and fix up all required customizations afterwards.
public static CustomizeFlag ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan)
public static CustomizeFlag ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan, ItemManager items, ICustomizationManager customization)
{
if (customize.Clan == clan)
return 0;
var race = clan.ToRace();
var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar
FixRestrictedGear(customize, equip, gender, race);
FixRestrictedGear(items, customize, equip, gender, race);
var flags = CustomizeFlag.Race | CustomizeFlag.Clan;
if (gender != customize.Gender)
flags |= CustomizeFlag.Gender;
customize.Gender = gender;
customize.Race = race;
customize.Clan = clan;
return flags | FixUpAttributes(customize);
return flags | FixUpAttributes(customization, customize);
}
// Go through a whole customization struct and fix up all settings that need fixing.
private static CustomizeFlag FixUpAttributes(Customize customize)
private static CustomizeFlag FixUpAttributes(ICustomizationManager customization, Customize customize)
{
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
var set = customization.GetList(customize.Clan, customize.Gender);
CustomizeFlag flags = 0;
foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex)))
{
@ -115,12 +116,12 @@ public static class CustomizeExtensions
return flags;
}
private static void FixRestrictedGear(Customize customize, CharacterEquip equip, Gender gender, Race race)
private static void FixRestrictedGear(ItemManager items, Customize customize, CharacterEquip equip, Gender gender, Race race)
{
if (!equip || race == customize.Race && gender == customize.Gender)
return;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
(_, equip[slot]) = Glamourer.Items.ResolveRestrictedGear(equip[slot], slot, race, gender);
(_, equip[slot]) = items.ResolveRestrictedGear(equip[slot], slot, race, gender);
}
}