Remove Turn Human, update HumanNpcCombo

This commit is contained in:
Ottermandias 2026-02-07 18:15:36 +01:00
parent 06e1fb2a3b
commit bc5538dd9d
3 changed files with 105 additions and 62 deletions

View file

@ -262,8 +262,6 @@ public sealed class ActorPanel : IPanel
private void DrawMonsterPanel() private void DrawMonsterPanel()
{ {
var names = _modelChara[_selection.State!.ModelData.ModelId]; var names = _modelChara[_selection.State!.ModelData.ModelId];
var turnHuman = Im.Button("Turn Human"u8);
Im.Separator();
using (Im.ListBox.Begin("##MonsterList"u8, Im.ContentRegion.Available with { Y = 10 * Im.Style.TextHeightWithSpacing })) using (Im.ListBox.Begin("##MonsterList"u8, Im.ContentRegion.Available with { Y = 10 * Im.Style.TextHeightWithSpacing }))
{ {
if (names.Count is 0) if (names.Count is 0)

View file

@ -1,103 +1,148 @@
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Utility; using Dalamud.Utility;
using Dalamud.Bindings.ImGui;
using ImSharp; using ImSharp;
using OtterGui; using Luna;
using OtterGui.Extensions; using Penumbra.GameData.Actors;
using OtterGui.Log;
using OtterGui.Widgets;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using OtterGui.Custom;
using MouseWheelType = OtterGui.Widgets.MouseWheelType;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class HumanNpcCombo( public sealed class HumanNpcCombo(DictBNpcNames bNpcNames, DictModelChara modelCharaDict, HumanModelList humans, DictBNpc bNpcs)
string label, : FilterComboBase<HumanNpcCombo.NpcCacheItem>(new NpcFilter())
DictModelChara modelCharaDict,
DictBNpcNames bNpcNames,
DictBNpc bNpcs,
HumanModelList humans,
Logger log)
: FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>(() => CreateList(modelCharaDict, bNpcNames, bNpcs, humans), MouseWheelType.None, log)
{ {
protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj) private NpcCacheItem _selection = new(string.Empty, ObjectKind.None, new HashSet<uint>());
=> obj.Name;
protected override bool DrawSelectable(int globalIdx, bool selected) public NpcCacheItem Selection
=> _selection;
public readonly struct NpcCacheItem(string name, ObjectKind kind, IReadOnlySet<uint> ids) : IComparable<NpcCacheItem>
{ {
var (name, kind, ids) = Items[globalIdx]; public readonly StringPair Name = new(name);
if (globalIdx > 0 && Items[globalIdx - 1].Name == name || globalIdx + 1 < Items.Count && Items[globalIdx + 1].Name == name) public readonly ObjectKind Kind = kind;
name = $"{name} ({kind.ToName()})"; public readonly IReadOnlySet<uint> Ids = ids;
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.IsNullOrEmpty() ? "Human Non-Player-Characters..." : CurrentSelection.Name, string.Empty, width,
Im.Style.TextHeightWithSpacing);
/// <summary> Compare strings in a way that letters and numbers are sorted before any special symbols. </summary> /// <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 CompareTo(NpcCacheItem other)
{ {
public int Compare((string, ObjectKind) x, (string, ObjectKind) y) if (Name.Utf16.IsNullOrWhitespace() || other.Name.Utf16.IsNullOrWhitespace())
{ return StringComparer.OrdinalIgnoreCase.Compare(Name.Utf16, other.Name.Utf16);
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 var comp = (char.IsAsciiLetterOrDigit(Name.Utf16[0]), char.IsAsciiLetterOrDigit(other.Name.Utf16[0])) switch
{ {
(true, false) => -1, (true, false) => -1,
(false, true) => 1, (false, true) => 1,
_ => StringComparer.OrdinalIgnoreCase.Compare(x.Item1, y.Item1), _ => StringComparer.OrdinalIgnoreCase.Compare(Name.Utf16, other.Name.Utf16),
}; };
if (comp != 0) if (comp is not 0)
return comp; return comp;
return Comparer<ObjectKind>.Default.Compare(x.Item2, y.Item2); return Comparer<ObjectKind>.Default.Compare(Kind, other.Kind);
} }
} }
private static IReadOnlyList<(string Name, ObjectKind Kind, uint[] Ids)> CreateList(DictModelChara modelCharaDict, DictBNpcNames bNpcNames, private sealed class NpcFilter : TextFilterBase<NpcCacheItem>
DictBNpc bNpcs, HumanModelList humans)
{ {
var ret = new List<(string Name, ObjectKind Kind, uint Id)>(1024); protected override string ToFilterString(in NpcCacheItem item, int globalIndex)
=> item.Name.Utf16;
}
protected override IEnumerable<NpcCacheItem> GetItems()
{
var bNpcDict = new SetDictionary<string, uint>(1024);
var eNpcDict = new SetDictionary<string, uint>(1024);
var bothSet = new HashSet<string>(1024);
for (var modelChara = 0u; modelChara < modelCharaDict.Count; ++modelChara) for (var modelChara = 0u; modelChara < modelCharaDict.Count; ++modelChara)
{ {
if (!humans.IsHuman(modelChara)) if (!humans.IsHuman(modelChara))
continue; continue;
var list = modelCharaDict[modelChara]; var list = modelCharaDict[modelChara];
if (list.Count == 0) if (list.Count is 0)
continue; continue;
foreach (var (name, kind, id) in list.Where(t => !t.Name.IsNullOrWhitespace())) foreach (var (name, kind, id) in list.Where(t => !t.Name.IsNullOrWhitespace()))
{ {
string actualName;
switch (kind) switch (kind)
{ {
case ObjectKind.BattleNpc: case ObjectKind.BattleNpc:
if (!bNpcNames.TryGetValue(id, out var nameIds)) if (!bNpcNames.TryGetValue(id, out var nameIds))
continue; continue;
ret.AddRange(nameIds.SelectWhere(nameId => (bNpcs.TryGetValue(nameId, out var s), (s!, kind, nameId.Id)))); foreach (var nameId in nameIds)
{
if (!bNpcs.TryGetValue(nameId, out var s))
continue;
if (bothSet.Contains(s))
{
actualName = $"{s} ({ObjectKind.BattleNpc.ToName()})";
}
else if (eNpcDict.Remove(s, out var values))
{
actualName = $"{s} ({ObjectKind.BattleNpc.ToName()})";
eNpcDict.TryAdd(actualName, values);
bothSet.Add(s);
}
else
{
actualName = s;
}
bNpcDict.TryAdd(actualName, nameId.Id);
}
break; break;
case ObjectKind.EventNpc: case ObjectKind.EventNpc:
ret.Add((name, kind, id)); if (bothSet.Contains(name))
{
actualName = $"{name} ({ObjectKind.EventNpc.ToName()})";
}
else if (bNpcDict.Remove(name, out var values))
{
actualName = $"{name} ({ObjectKind.EventNpc.ToName()})";
bNpcDict.TryAdd(actualName, values);
bothSet.Add(name);
}
else
{
actualName = name;
}
eNpcDict.TryAdd(actualName, id);
break; break;
} }
} }
} }
return ret.GroupBy(t => (t.Name, t.Kind)) return bNpcDict.Grouped.Select(p => new NpcCacheItem(p.Key, ObjectKind.BattleNpc, p.Value))
.OrderBy(g => g.Key, Comparer) .Concat(eNpcDict.Grouped.Select(p => new NpcCacheItem(p.Key, ObjectKind.EventNpc, p.Value)))
.Select(g => (g.Key.Name, g.Key.Kind, g.Select(g => g.Id).Distinct().ToArray())) .OrderBy(p => p);
.ToList();
} }
private static readonly NameComparer Comparer = new();
protected override float ItemHeight
=> Im.Style.TextHeightWithSpacing;
protected override bool DrawItem(in NpcCacheItem item, int globalIndex, bool selected)
{
var ret = Im.Selectable(item.Name.Utf8, selected);
if (Im.Item.Hovered())
{
using var style = Im.Style.PushDefault();
using var tt = Im.Tooltip.Begin();
foreach (var id in item.Ids)
Im.Text($"{id}");
}
return ret;
}
public bool Draw(Utf8StringHandler<LabelStringHandlerBuffer> label, float width)
=> base.Draw(label, _selection.Kind is ObjectKind.None ? "Human Non-Player-Characters..."u8 : _selection.Name.Utf8, StringU8.Empty,
width, out _selection);
protected override bool IsSelected(NpcCacheItem item, int globalIndex)
=> item.Kind == _selection.Kind && item.Name.Utf16 == _selection.Name.Utf16;
} }

View file

@ -16,7 +16,7 @@ public class IdentifierDrawer(
HumanModelList humans) HumanModelList humans)
{ {
private readonly WorldCombo _worldCombo = new(dictWorld); private readonly WorldCombo _worldCombo = new(dictWorld);
private readonly HumanNpcCombo _humanNpcCombo = new("##npcs", dictModelChara, bNpcNames, bNpc, humans, Glamourer.Log); private readonly HumanNpcCombo _humanNpcCombo = new(bNpcNames, dictModelChara, humans, bNpc);
private string _characterName = string.Empty; private string _characterName = string.Empty;
@ -41,7 +41,7 @@ public class IdentifierDrawer(
public void DrawNpcs(float width) public void DrawNpcs(float width)
{ {
if (_humanNpcCombo.Draw(width)) if (_humanNpcCombo.Draw("##npcs"u8, width))
UpdateIdentifiers(); UpdateIdentifiers();
} }
@ -68,15 +68,15 @@ public class IdentifierDrawer(
RetainerIdentifier = actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell); RetainerIdentifier = actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell);
MannequinIdentifier = actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin); MannequinIdentifier = actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin);
if (_humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc) if (_humanNpcCombo.Selection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc)
OwnedIdentifier = actors.CreateOwned(byteName, _worldCombo.Selected.Key, _humanNpcCombo.CurrentSelection.Kind, OwnedIdentifier = actors.CreateOwned(byteName, _worldCombo.Selected.Key, _humanNpcCombo.Selection.Kind,
_humanNpcCombo.CurrentSelection.Ids[0]); _humanNpcCombo.Selection.Ids.First());
else else
OwnedIdentifier = ActorIdentifier.Invalid; OwnedIdentifier = ActorIdentifier.Invalid;
} }
NpcIdentifier = _humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc NpcIdentifier = _humanNpcCombo.Selection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc
? actors.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]) ? actors.CreateNpc(_humanNpcCombo.Selection.Kind, _humanNpcCombo.Selection.Ids.First())
: ActorIdentifier.Invalid; : ActorIdentifier.Invalid;
} }
} }