Support human NPCs for identifiers.

This commit is contained in:
Ottermandias 2023-07-12 02:44:04 +02:00
parent a310e01296
commit b00e6bd98b
17 changed files with 536 additions and 162 deletions

View file

@ -68,24 +68,27 @@ public class AutoDesignApplier : IDisposable
case AutomationChanged.Type.ChangedDesign:
case AutomationChanged.Type.ChangedConditions:
_objects.Update();
if (_objects.TryGetValue(set.Identifier, out var data))
foreach (var id1 in set.Identifiers)
{
if (_state.GetOrCreate(set.Identifier, data.Objects[0], out var state))
if (_objects.TryGetValue(id1, out var data))
{
Reduce(data.Objects[0], state, set, false);
foreach (var actor in data.Objects)
_state.ReapplyState(actor);
}
}
else if (_objects.TryGetValueAllWorld(set.Identifier, out data))
{
foreach (var actor in data.Objects)
{
var id = actor.GetIdentifier(_actors.AwaitedService);
if (_state.GetOrCreate(id, actor, out var state))
if (_state.GetOrCreate(id1, data.Objects[0], out var state))
{
Reduce(actor, state, set, false);
_state.ReapplyState(actor);
Reduce(data.Objects[0], state, set, false);
foreach (var actor in data.Objects)
_state.ReapplyState(actor);
}
}
else if (_objects.TryGetValueAllWorld(id1, out data))
{
foreach (var actor in data.Objects)
{
var id = actor.GetIdentifier(_actors.AwaitedService);
if (_state.GetOrCreate(id, actor, out var state))
{
Reduce(actor, state, set, false);
_state.ReapplyState(actor);
}
}
}
}
@ -265,6 +268,10 @@ public class AutoDesignApplier : IDisposable
var customize = state.ModelData.Customize;
CustomizeFlag fixFlags = 0;
// Skip anything not human.
if (!state.ModelData.IsHuman || !design.IsHuman)
return;
// Skip invalid designs entirely.
if (_config.SkipInvalidCustomizations
&& !_code.EnabledMesmer

View file

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Utility;
using Glamourer.Designs;
@ -11,7 +12,6 @@ using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Structs;
using Glamourer.Unlocks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui.Filesystem;
@ -29,7 +29,6 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
private readonly DesignManager _designs;
private readonly ActorService _actors;
private readonly AutomationChanged _event;
private readonly ItemUnlockManager _unlockManager;
private readonly List<AutoDesignSet> _data = new();
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = new();
@ -38,14 +37,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
=> _enabled;
public AutoDesignManager(JobService jobs, ActorService actors, SaveService saveService, DesignManager designs, AutomationChanged @event,
FixedDesignMigrator migrator, DesignFileSystem fileSystem, ItemUnlockManager unlockManager)
FixedDesignMigrator migrator, DesignFileSystem fileSystem)
{
_jobs = jobs;
_actors = actors;
_saveService = saveService;
_designs = designs;
_event = @event;
_unlockManager = unlockManager;
_jobs = jobs;
_actors = actors;
_saveService = saveService;
_designs = designs;
_event = @event;
Load();
migrator.ConsumeMigratedData(_actors, fileSystem, this);
}
@ -64,13 +62,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
public void AddDesignSet(string name, ActorIdentifier identifier)
{
if (!IdentifierValid(identifier) || name.Length == 0)
if (!IdentifierValid(identifier, out var group) || name.Length == 0)
return;
var newSet = new AutoDesignSet(name, identifier.CreatePermanent()) { Enabled = false };
var newSet = new AutoDesignSet(name, group) { Enabled = false };
_data.Add(newSet);
Save();
Glamourer.Log.Debug($"Created new design set for {newSet.Identifier.Incognito(null)}.");
Glamourer.Log.Debug($"Created new design set for {newSet.Identifiers[0].Incognito(null)}.");
_event.Invoke(AutomationChanged.Type.AddedSet, newSet, (_data.Count - 1, name));
}
@ -90,12 +88,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
name += " (Duplicate)";
}
var newSet = new AutoDesignSet(name, set.Identifier) { Enabled = false };
var newSet = new AutoDesignSet(name, set.Identifiers) { Enabled = false };
newSet.Designs.AddRange(set.Designs.Select(d => d.Clone()));
_data.Add(newSet);
Save();
Glamourer.Log.Debug(
$"Duplicated new design set for {newSet.Identifier.Incognito(null)} with {newSet.Designs.Count} auto designs from existing set.");
$"Duplicated new design set for {newSet.Identifiers[0].Incognito(null)} with {newSet.Designs.Count} auto designs from existing set.");
_event.Invoke(AutomationChanged.Type.AddedSet, newSet, (_data.Count - 1, name));
}
@ -108,7 +106,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
if (set.Enabled)
{
set.Enabled = false;
_enabled.Remove(set.Identifier);
foreach (var id in set.Identifiers)
_enabled.Remove(id);
}
_data.RemoveAt(whichSet);
@ -146,26 +145,33 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
public void ChangeIdentifier(int whichSet, ActorIdentifier to)
{
if (whichSet >= _data.Count || whichSet < 0 || !IdentifierValid(to))
if (whichSet >= _data.Count || whichSet < 0 || !IdentifierValid(to, out var group))
return;
var set = _data[whichSet];
if (set.Identifier == to)
if (set.Identifiers.Any(id => id == to))
return;
var old = set.Identifier;
set.Identifier = to.CreatePermanent();
var old = set.Identifiers;
set.Identifiers = group;
AutoDesignSet? oldEnabled = null;
if (set.Enabled)
{
_enabled.Remove(old);
foreach (var id in old)
_enabled.Remove(id);
if (_enabled.Remove(to, out oldEnabled))
{
foreach (var id in oldEnabled.Identifiers)
_enabled.Remove(id);
oldEnabled.Enabled = false;
_enabled.Add(set.Identifier, set);
}
foreach (var id in group)
_enabled.Add(id, set);
}
Save();
Glamourer.Log.Debug($"Changed Identifier of design set {whichSet + 1} from {old.Incognito(null)} to {to.Incognito(null)}.");
Glamourer.Log.Debug($"Changed Identifier of design set {whichSet + 1} from {old[0].Incognito(null)} to {to.Incognito(null)}.");
_event.Invoke(AutomationChanged.Type.ChangeIdentifier, set, (old, to, oldEnabled));
}
@ -182,13 +188,20 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
AutoDesignSet? oldEnabled = null;
if (value)
{
if (_enabled.Remove(set.Identifier, out oldEnabled))
if (_enabled.Remove(set.Identifiers[0], out oldEnabled))
{
foreach (var id in oldEnabled.Identifiers)
_enabled.Remove(id);
oldEnabled.Enabled = false;
_enabled.Add(set.Identifier, set);
}
foreach (var id in set.Identifiers)
_enabled.Add(id, set);
}
else
else if (_enabled.Remove(set.Identifiers[0], out oldEnabled))
{
_enabled.Remove(set.Identifier, out oldEnabled);
foreach (var id in oldEnabled.Identifiers)
_enabled.Remove(id);
}
Save();
@ -353,7 +366,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
}
var id = _actors.AwaitedService.FromJson(obj["Identifier"] as JObject);
if (!IdentifierValid(id))
if (!IdentifierValid(id, out var group))
{
Glamourer.Chat.NotificationMessage("Skipped loading Automation Set: Invalid Identifier.", "Warning", NotificationType.Warning);
continue;
@ -365,8 +378,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
};
if (set.Enabled)
if (!_enabled.TryAdd(set.Identifier, set))
{
if (_enabled.TryAdd(group[0], set))
foreach (var id2 in group.Skip(1))
_enabled[id2] = set;
else
set.Enabled = false;
}
_data.Add(set);
@ -377,7 +395,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
{
if (designObj is not JObject j)
{
Glamourer.Chat.NotificationMessage($"Skipped loading design in Automation Set for {set.Identifier}: Unknown design.");
Glamourer.Chat.NotificationMessage($"Skipped loading design in Automation Set for {id.Incognito(null)}: Unknown design.");
continue;
}
@ -442,16 +460,57 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>
private void Save()
=> _saveService.DelaySave(this);
private static bool IdentifierValid(ActorIdentifier identifier)
private bool IdentifierValid(ActorIdentifier identifier, out ActorIdentifier[] group)
{
if (!identifier.IsValid)
return false;
return identifier.Type switch
var validType = identifier.Type switch
{
IdentifierType.Player => true,
IdentifierType.Retainer => true,
IdentifierType.Npc => true,
_ => false,
};
if (!validType)
{
group = Array.Empty<ActorIdentifier>();
return false;
}
group = GetGroup(identifier);
return group.Length > 0;
}
private ActorIdentifier[] GetGroup(ActorIdentifier identifier)
{
if (!identifier.IsValid)
return Array.Empty<ActorIdentifier>();
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
{
var name = manager.Data.ToName(identifier.Kind, identifier.DataId);
var table = identifier.Kind switch
{
ObjectKind.BattleNpc => manager.Data.BNpcs,
ObjectKind.EventNpc => manager.Data.ENpcs,
_ => throw new NotImplementedException(),
};
return table.Where(kvp => kvp.Value == name)
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld, identifier.Kind,
kvp.Key)).ToArray();
}
return identifier.Type switch
{
IdentifierType.Player => new[]
{
identifier.CreatePermanent(),
},
IdentifierType.Retainer => new[]
{
identifier.CreatePermanent(),
},
IdentifierType.Npc => CreateNpcs(_actors.AwaitedService, identifier),
_ => Array.Empty<ActorIdentifier>(),
};
}
}

View file

@ -8,9 +8,9 @@ public class AutoDesignSet
{
public readonly List<AutoDesign> Designs;
public string Name;
public ActorIdentifier Identifier;
public bool Enabled;
public string Name;
public ActorIdentifier[] Identifiers;
public bool Enabled;
public JObject Serialize()
{
@ -21,20 +21,20 @@ public class AutoDesignSet
return new JObject()
{
["Name"] = Name,
["Identifier"] = Identifier.ToJson(),
["Identifier"] = Identifiers[0].ToJson(),
["Enabled"] = Enabled,
["Designs"] = list,
};
}
public AutoDesignSet(string name, ActorIdentifier identifier)
: this(name, identifier, new List<AutoDesign>())
public AutoDesignSet(string name, params ActorIdentifier[] identifiers)
: this(name, identifiers, new List<AutoDesign>())
{ }
public AutoDesignSet(string name, ActorIdentifier identifier, List<AutoDesign> designs)
public AutoDesignSet(string name, ActorIdentifier[] identifiers, List<AutoDesign> designs)
{
Name = name;
Identifier = identifier;
Designs = designs;
Name = name;
Identifiers = identifiers;
Designs = designs;
}
}

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using Dalamud.Data;
using Dalamud.Interface;
using Glamourer.Designs;
@ -24,16 +25,17 @@ public class EquipmentDrawer
private readonly ItemCombo[] _itemCombo;
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
private readonly CodeService _codes;
private readonly TextureService _textures;
public EquipmentDrawer(DataManager gameData, ItemManager items, CodeService codes)
public EquipmentDrawer(DataManager gameData, ItemManager items, CodeService codes, TextureService textures)
{
_items = items;
_codes = codes;
_textures = textures;
_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(gameData, items, e)).ToArray();
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, textures)).ToArray();
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
foreach (var type in Enum.GetValues<FullEquipType>())
{
@ -46,6 +48,15 @@ public class EquipmentDrawer
_weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(items, FullEquipType.Unknown));
}
private Vector2 _iconSize;
private float _comboLength;
public void Prepare()
{
_iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y);
_comboLength = 320 * ImGuiHelpers.GlobalScale;
}
private string VerifyRestrictedGear(EquipItem gear, EquipSlot slot, Gender gender, Race race)
{
if (slot.IsAccessory())
@ -61,12 +72,20 @@ public class EquipmentDrawer
public bool DrawArmor(EquipItem current, EquipSlot slot, out EquipItem armor, Gender gender = Gender.Unknown, Race race = Race.Unknown)
{
Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(DrawArmor)} on {slot}.");
if (_codes.EnabledArtisan)
return DrawArmorArtisan(current, slot, out armor, gender, race);
current.DrawIcon(_textures, _iconSize);
ImGui.SameLine();
using var group = ImRaii.Group();
var combo = _itemCombo[slot.ToIndex()];
armor = current;
var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.ItemId, 320 * ImGuiHelpers.GlobalScale);
var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.ItemId, _comboLength);
if (change)
armor = combo.CurrentSelection;
if (armor.ModelId.Value != 0)
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
@ -75,95 +94,30 @@ public class EquipmentDrawer
change = true;
armor = ItemManager.NothingItem(slot);
}
else if (change)
{
armor = combo.CurrentSelection;
}
}
else if (change)
{
armor = combo.CurrentSelection;
}
return change;
}
public bool DrawArmorArtisan(EquipItem current, EquipSlot slot, out EquipItem armor, Gender gender = Gender.Unknown,
Race race = Race.Unknown)
{
using var id = ImRaii.PushId((int)slot);
int setId = current.ModelId.Value;
int variant = current.Variant;
var ret = false;
armor = current;
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##setId", ref setId, 0, 0))
{
var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue);
if (newSetId.Value != current.ModelId.Value)
{
armor = _items.Identify(slot, newSetId, current.Variant);
ret = true;
}
}
ImGui.SameLine();
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##variant", ref variant, 0, 0))
{
var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue);
if (newVariant != current.Variant)
{
armor = _items.Identify(slot, current.ModelId, newVariant);
ret = true;
}
}
return ret;
}
public bool DrawStain(StainId current, EquipSlot slot, out StainId ret)
{
if (_codes.EnabledArtisan)
return DrawStainArtisan(current, slot, out ret);
var found = _stainData.TryGetValue(current, out var stain);
var change = _stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found);
var change = _stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength);
ret = current;
if (change && _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain))
ret = stain.RowIndex;
ImGuiUtil.HoverTooltip("Right-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
stain = Stain.None;
ret = stain.RowIndex;
return true;
ret = Stain.None.RowIndex;
change = true;
}
if (change && _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain))
{
ret = stain.RowIndex;
return true;
}
ret = current;
return false;
}
public bool DrawStainArtisan(StainId current, EquipSlot slot, out StainId stain)
{
using var id = ImRaii.PushId((int)slot);
int stainId = current.Value;
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##stain", ref stainId, 0, 0))
{
var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue);
if (newStainId != current)
{
stain = newStainId;
return true;
}
}
stain = current;
return false;
return change;
}
public bool DrawMainhand(EquipItem current, bool drawAll, out EquipItem weapon)
@ -191,7 +145,9 @@ public class EquipmentDrawer
var change = combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale);
if (change)
{
weapon = combo.CurrentSelection;
}
else if (!offType.IsOffhandType() && weapon.ModelId.Value != 0)
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
@ -229,4 +185,59 @@ public class EquipmentDrawer
public bool DrawWetness(bool current, out bool on)
=> DrawCheckbox("##wetness", current, out on);
/// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary>
private bool DrawArmorArtisan(EquipItem current, EquipSlot slot, out EquipItem armor, Gender gender = Gender.Unknown,
Race race = Race.Unknown)
{
using var id = ImRaii.PushId((int)slot);
int setId = current.ModelId.Value;
int variant = current.Variant;
var ret = false;
armor = current;
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##setId", ref setId, 0, 0))
{
var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue);
if (newSetId.Value != current.ModelId.Value)
{
armor = _items.Identify(slot, newSetId, current.Variant);
ret = true;
}
}
ImGui.SameLine();
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##variant", ref variant, 0, 0))
{
var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue);
if (newVariant != current.Variant)
{
armor = _items.Identify(slot, current.ModelId, newVariant);
ret = true;
}
}
return ret;
}
/// <summary> Draw an input for stain that can set arbitrary values instead of choosing valid stains. </summary>
private bool DrawStainArtisan(StainId current, EquipSlot slot, out StainId stain)
{
using var id = ImRaii.PushId((int)slot);
int stainId = current.Value;
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##stain", ref stainId, 0, 0))
{
var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue);
if (newStainId != current)
{
stain = newStainId;
return true;
}
}
stain = current;
return false;
}
}

View file

@ -15,12 +15,15 @@ namespace Glamourer.Gui.Equipment;
public sealed class ItemCombo : FilterComboCache<EquipItem>
{
private readonly TextureService _textures;
public readonly string Label;
private uint _currentItem;
public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot)
public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot, TextureService textures)
: base(() => GetItems(items, slot))
{
_textures = textures;
Label = GetLabel(gameData, slot);
_currentItem = ItemManager.NothingId(slot);
}
@ -40,7 +43,6 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem);
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
return base.UpdateCurrentSelected(CurrentSelectionIdx);
}
public bool Draw(string previewName, uint previewIdx, float width)

View file

@ -130,6 +130,7 @@ public class ActorPanel
if (_customizationDrawer.Draw(_state!.ModelData.Customize, false))
_stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual);
_equipmentDrawer.Prepare();
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var stain = _state.ModelData.Stain(slot);

View file

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Utility;
using Glamourer.Services;
using ImGuiNET;
using OtterGui.Custom;
using OtterGui.Widgets;
using Penumbra.GameData.Data;
namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>
{
private readonly string _label;
public HumanNpcCombo(string label, IdentifierService service, HumanModelList humans)
: base(() => CreateList(service, humans))
=> _label = label;
protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj)
=> obj.Name;
protected override bool DrawSelectable(int globalIdx, bool selected)
{
var (name, kind, ids) = Items[globalIdx];
if (globalIdx > 0 && Items[globalIdx - 1].Name == name || globalIdx + 1 < Items.Count && Items[globalIdx + 1].Name == name)
name = $"{name} ({kind.ToName()})";
var ret = ImGui.Selectable(name, selected);
if (ImGui.IsItemHovered())
ImGui.SetTooltip(string.Join('\n', ids.Select(i => i.ToString())));
return ret;
}
public bool Draw(float width)
=> Draw(_label, CurrentSelection.Name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
/// <summary> Compare strings in a way that letters and numbers are sorted before any special symbols. </summary>
private class NameComparer : IComparer<(string, ObjectKind)>
{
public int Compare((string, ObjectKind) x, (string, ObjectKind) y)
{
if (x.Item1.IsNullOrWhitespace() || y.Item1.IsNullOrWhitespace())
return StringComparer.OrdinalIgnoreCase.Compare(x.Item1, y.Item1);
var comp = (char.IsAsciiLetterOrDigit(x.Item1[0]), char.IsAsciiLetterOrDigit(y.Item1[0])) switch
{
(true, false) => -1,
(false, true) => 1,
_ => StringComparer.OrdinalIgnoreCase.Compare(x.Item1, y.Item1),
};
if (comp != 0)
return comp;
return Comparer<ObjectKind>.Default.Compare(x.Item2, y.Item2);
}
}
private static IReadOnlyList<(string Name, ObjectKind Kind, uint[] Ids)> CreateList(IdentifierService service, HumanModelList humans)
{
var ret = new List<(string Name, ObjectKind Kind, uint Id)>(1024);
for (var modelChara = 0u; modelChara < service.AwaitedService.NumModelChara; ++modelChara)
{
if (!humans.IsHuman(modelChara))
continue;
var list = service.AwaitedService.ModelCharaNames(modelChara);
if (list.Count == 0)
continue;
foreach (var (name, kind, id) in list.Where(t => !t.Name.IsNullOrWhitespace()))
{
switch (kind)
{
case ObjectKind.BattleNpc:
var nameIds = service.AwaitedService.GetBnpcNames(id);
ret.AddRange(nameIds.Select(nameId => (name, kind, nameId)));
break;
case ObjectKind.EventNpc:
ret.Add((name, kind, id));
break;
}
}
}
return ret.GroupBy(t => (t.Name, t.Kind)).OrderBy(g => g.Key, Comparer)
.Select(g => (g.Key.Name, g.Key.Kind, g.Select(g => g.Id).ToArray())).ToList();
}
private static readonly NameComparer Comparer = new();
}

View file

@ -0,0 +1,70 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Glamourer.Services;
using ImGuiNET;
using OtterGui.Custom;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Data;
using Penumbra.String;
namespace Glamourer.Gui.Tabs.AutomationTab;
public class IdentifierDrawer
{
private readonly WorldCombo _worldCombo;
private readonly HumanNpcCombo _humanNpcCombo;
private readonly ActorService _actors;
private string _characterName = string.Empty;
public ActorIdentifier NpcIdentifier { get; private set; } = ActorIdentifier.Invalid;
public ActorIdentifier PlayerIdentifier { get; private set; } = ActorIdentifier.Invalid;
public ActorIdentifier RetainerIdentifier { get; private set; } = ActorIdentifier.Invalid;
public IdentifierDrawer(ActorService actors, IdentifierService identifier, HumanModelList humans)
{
_actors = actors;
_worldCombo = new WorldCombo(actors.AwaitedService.Data.Worlds);
_humanNpcCombo = new HumanNpcCombo("Human Event NPCs", identifier, humans);
}
public void DrawName(float width)
{
ImGui.SetNextItemWidth(width);
if (ImGui.InputTextWithHint("##Name", "Character Name...", ref _characterName, 32))
UpdateIdentifiers();
}
public void DrawWorld(float width)
{
if (_worldCombo.Draw(width))
UpdateIdentifiers();
}
public void DrawNpcs(float width)
{
if (_humanNpcCombo.Draw(width))
UpdateIdentifiers();
}
public bool CanSetPlayer
=> PlayerIdentifier.IsValid;
public bool CanSetRetainer
=> RetainerIdentifier.IsValid;
public bool CanSetNpc
=> NpcIdentifier.IsValid;
private void UpdateIdentifiers()
{
if (ByteString.FromString(_characterName, out var byteName))
{
PlayerIdentifier = _actors.AwaitedService.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key);
RetainerIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Both);
}
NpcIdentifier = _humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc
? _actors.AwaitedService.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0])
: ActorIdentifier.Invalid;
}
}

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
@ -12,12 +11,12 @@ using Glamourer.Services;
using Glamourer.Structs;
using Glamourer.Unlocks;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
using Action = System.Action;
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
namespace Glamourer.Gui.Tabs.AutomationTab;
@ -29,8 +28,9 @@ public class SetPanel
private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly CustomizationService _customizations;
private readonly DesignCombo _designCombo;
private readonly JobGroupCombo _jobGroupCombo;
private readonly DesignCombo _designCombo;
private readonly JobGroupCombo _jobGroupCombo;
private readonly IdentifierDrawer _identifierDrawer;
private string? _tempName;
private int _dragIndex = -1;
@ -38,13 +38,14 @@ public class SetPanel
private Action? _endAction;
public SetPanel(SetSelector selector, AutoDesignManager manager, DesignManager designs, JobService jobs, ItemUnlockManager itemUnlocks,
CustomizeUnlockManager customizeUnlocks, CustomizationService customizations)
CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer)
{
_selector = selector;
_manager = manager;
_itemUnlocks = itemUnlocks;
_customizeUnlocks = customizeUnlocks;
_customizations = customizations;
_identifierDrawer = identifierDrawer;
_designCombo = new DesignCombo(_manager, designs);
_jobGroupCombo = new JobGroupCombo(manager, jobs);
}
@ -101,6 +102,9 @@ public class SetPanel
if (ImGui.Checkbox("Enabled", ref enabled))
_manager.SetState(_selector.SelectionIndex, enabled);
ImGui.Separator();
DrawIdentifierSelection(_selector.SelectionIndex);
DrawDesignTable();
}
@ -201,7 +205,10 @@ public class SetPanel
sb.Clear();
var sb2 = new StringBuilder();
var customize = design.Design.DesignData.Customize;
var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender);
if (!design.Design.DesignData.IsHuman)
sb.AppendLine("The base model id can not be changed automatically to something non-human.");
var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender);
foreach (var type in CustomizationExtensions.All)
{
var flag = type.ToFlag();
@ -268,6 +275,21 @@ public class SetPanel
_manager.ChangeApplicationType(set, autoDesignIndex, newType);
}
private void DrawIdentifierSelection(int setIndex)
{
using var id = ImRaii.PushId("Identifiers");
_identifierDrawer.DrawWorld(200);
_identifierDrawer.DrawName(300);
_identifierDrawer.DrawNpcs(300);
if (ImGuiUtil.DrawDisabledButton("Set to Retainer", new Vector2(100, 0), string.Empty, !_identifierDrawer.CanSetRetainer))
_manager.ChangeIdentifier(setIndex, _identifierDrawer.RetainerIdentifier);
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Set to Character", new Vector2(100, 0), string.Empty, !_identifierDrawer.CanSetPlayer))
_manager.ChangeIdentifier(setIndex, _identifierDrawer.PlayerIdentifier);
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Set to Npc", new Vector2(100, 0), string.Empty, !_identifierDrawer.CanSetNpc))
_manager.ChangeIdentifier(setIndex, _identifierDrawer.NpcIdentifier);
}
private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[]

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Glamourer.Automation;
@ -118,7 +119,7 @@ public class SetSelector : IDisposable
_list.Clear();
foreach (var set in _manager)
{
var id = set.Identifier.ToString();
var id = set.Identifiers[0].ToString();
if (CheckFilters(set, id))
_list.Add(set);
}
@ -195,11 +196,11 @@ public class SetSelector : IDisposable
DrawDragDrop(set, index);
var text = set.Identifier.ToString();
var text = set.Identifiers[0].ToString();
if (IncognitoMode)
text = set.Identifier.Incognito(text);
text = set.Identifiers[0].Incognito(text);
var textSize = ImGui.CalcTextSize(text);
var textColor = _objects.ContainsKey(set.Identifier) ? ColorId.AutomationActorAvailable : ColorId.AutomationActorUnavailable;
var textColor = set.Identifiers.Any(_objects.ContainsKey) ? ColorId.AutomationActorAvailable : ColorId.AutomationActorUnavailable;
ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionAvail().X - textSize.X,
ImGui.GetCursorPosY() - ImGui.GetTextLineHeightWithSpacing()));
ImGuiUtil.TextColored(textColor.Value(), text);

View file

@ -1304,7 +1304,7 @@ public unsafe class DebugTab : ITab
ImGuiUtil.DrawTableColumn(set.Enabled.ToString());
ImGuiUtil.DrawTableColumn("Actor");
ImGuiUtil.DrawTableColumn(set.Identifier.ToString());
ImGuiUtil.DrawTableColumn(set.Identifiers[0].ToString());
foreach (var (design, designIdx) in set.Designs.WithIndex())
{

View file

@ -127,6 +127,7 @@ public class DesignPanel
if (!ImGui.CollapsingHeader("Equipment"))
return;
_equipmentDrawer.Prepare();
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var stain = _selector.Selected!.DesignData.Stain(slot);

View file

@ -21,7 +21,7 @@ public class UnlockOverview
private readonly CustomizationService _customizations;
private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly PenumbraChangedItemTooltip _tooltip;
private readonly TextureCache _textureCache;
private readonly TextureService _textures;
private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f);
@ -67,14 +67,14 @@ public class UnlockOverview
}
public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks,
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureCache textureCache)
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureService textures)
{
_items = items;
_customizations = customizations;
_itemUnlocks = itemUnlocks;
_customizeUnlocks = customizeUnlocks;
_tooltip = tooltip;
_textureCache = textureCache;
_textures = textures;
}
public void Draw()
@ -154,7 +154,7 @@ public class UnlockOverview
void DrawItem(EquipItem item)
{
var unlocked = _itemUnlocks.IsUnlocked(item.ItemId, out var time);
var iconHandle = _textureCache.LoadIcon(item.IconId);
var iconHandle = _textures.LoadIcon(item.IconId);
if (!iconHandle.HasValue)
return;

View file

@ -10,7 +10,6 @@ using Glamourer.Structs;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Table;
using Penumbra.GameData.Enums;
@ -22,10 +21,10 @@ public class UnlockTable : Table<EquipItem>, IDisposable
{
private readonly ObjectUnlocked _event;
public UnlockTable(ItemManager items, TextureCache cache, ItemUnlockManager itemUnlocks,
public UnlockTable(ItemManager items, TextureService textures, ItemUnlockManager itemUnlocks,
PenumbraChangedItemTooltip tooltip, ObjectUnlocked @event)
: base("ItemUnlockTable", new ItemList(items),
new NameColumn(cache, tooltip) { Label = "Item Name..." },
new NameColumn(textures, tooltip) { Label = "Item Name..." },
new SlotColumn() { Label = "Equip Slot" },
new TypeColumn() { Label = "Item Type..." },
new UnlockDateColumn(itemUnlocks) { Label = "Unlocked" },
@ -36,7 +35,7 @@ public class UnlockTable : Table<EquipItem>, IDisposable
Sortable = true;
Flags |= ImGuiTableFlags.Hideable;
_event.Subscribe(OnObjectUnlock, ObjectUnlocked.Priority.UnlockTable);
cache.Logger = Glamourer.Log;
textures.Logger = Glamourer.Log;
}
public void Dispose()
@ -44,13 +43,13 @@ public class UnlockTable : Table<EquipItem>, IDisposable
private sealed class NameColumn : ColumnString<EquipItem>
{
private readonly TextureCache _textures;
private readonly TextureService _textures;
private readonly PenumbraChangedItemTooltip _tooltip;
public override float Width
=> 400 * ImGuiHelpers.GlobalScale;
public NameColumn(TextureCache textures, PenumbraChangedItemTooltip tooltip)
public NameColumn(TextureService textures, PenumbraChangedItemTooltip tooltip)
{
_textures = textures;
_tooltip = tooltip;

View file

@ -0,0 +1,34 @@
using System.Numerics;
using Dalamud.Interface;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui;
public static class UiHelpers
{
public static void DrawIcon(this EquipItem item, TextureService textures, Vector2 size)
{
var isEmpty = item.ModelId.Value == 0;
var (ptr, textureSize, empty) = textures.GetIcon(item);
if (empty)
{
var pos = ImGui.GetCursorScreenPos();
ImGui.GetWindowDrawList().AddRectFilled(pos, pos + size,
ImGui.GetColorU32(isEmpty ? ImGuiCol.FrameBg : ImGuiCol.FrameBgActive), 5 * ImGuiHelpers.GlobalScale);
if (ptr != nint.Zero)
ImGui.Image(ptr, size, Vector2.Zero, Vector2.One,
isEmpty ? new Vector4(0.1f, 0.1f, 0.1f, 0.5f) : new Vector4(0.3f, 0.3f, 0.3f, 0.8f));
else
ImGui.Dummy(size);
}
else
{
ImGuiUtil.HoverIcon(ptr, textureSize, size);
}
}
}

View file

@ -56,7 +56,7 @@ public static class ServiceManager
.AddSingleton<CodeService>()
.AddSingleton<ConfigMigrationService>()
.AddSingleton<Configuration>()
.AddSingleton<TextureCache>();
.AddSingleton<TextureService>();
private static IServiceCollection AddEvents(this IServiceCollection services)
=> services.AddSingleton<VisorStateChanged>()
@ -126,7 +126,8 @@ public static class ServiceManager
.AddSingleton<PenumbraChangedItemTooltip>()
.AddSingleton<AutomationTab>()
.AddSingleton<SetSelector>()
.AddSingleton<SetPanel>();
.AddSingleton<SetPanel>()
.AddSingleton<IdentifierDrawer>();
private static IServiceCollection AddApi(this IServiceCollection services)
=> services.AddSingleton<CommandService>()

View file

@ -0,0 +1,71 @@
using System;
using System.Linq;
using Dalamud.Data;
using System.Numerics;
using Dalamud.Game;
using Dalamud.Interface;
using ImGuiScene;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Services;
public sealed class TextureService : TextureCache, IDisposable
{
public TextureService(Framework framework, UiBuilder uiBuilder, DataManager dataManager)
: base(framework, uiBuilder, dataManager)
=> _slotIcons = CreateSlotIcons(uiBuilder);
private readonly TextureWrap?[] _slotIcons;
public (nint, Vector2, bool) GetIcon(EquipItem item)
{
if (item.IconId != 0 && TryLoadIcon(item.IconId, out var ret))
return (ret.Value.Texture, ret.Value.Dimensions, false);
var idx = item.Type.ToSlot().ToIndex();
return idx < 12 && _slotIcons[idx] != null
? (_slotIcons[idx]!.ImGuiHandle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true)
: (nint.Zero, Vector2.Zero, true);
}
public new void Dispose()
{
base.Dispose();
for (var i = 0; i < _slotIcons.Length; ++i)
{
_slotIcons[i]?.Dispose();
_slotIcons[i] = null;
}
}
private static TextureWrap[] CreateSlotIcons(UiBuilder uiBuilder)
{
var ret = new TextureWrap[12];
using var uldWrapper = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld");
if (!uldWrapper.Valid)
{
Glamourer.Log.Error($"Could not get empty slot uld.");
return ret;
}
ret[0] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 1)!;
ret[1] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 2)!;
ret[2] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 3)!;
ret[3] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 5)!;
ret[4] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 6)!;
ret[5] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 8)!;
ret[6] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 9)!;
ret[7] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 10)!;
ret[8] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 11)!;
ret[9] = ret[10];
ret[10] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 0)!;
ret[11] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 7)!;
uldWrapper.Dispose();
return ret;
}
}