From face5992f0df3a79327e93b8c12723a006bf376e Mon Sep 17 00:00:00 2001 From: Kurochi51 Date: Tue, 23 Jul 2024 15:07:58 +0300 Subject: [PATCH] Added all pets without a preset size.sizeMap. Each ComboBox now has its own filter. If HomeWorld or ContentId is know for any given data, it's also matched alongside name in OldParse. Sort combo options only if there's a minimum of 2 entries. Center only custom size text in DisplayEntries. Add Tab state awareness, all praise stateless ImGui. Ritualistic sacrifice backfired, partial brain damage acquired. --- PetScale/Configuration.cs | 2 +- PetScale/Enums/PetEnums.cs | 16 ++ PetScale/GlobalSuppressions.cs | 7 +- PetScale/Helpers/ImGuiUtils.cs | 83 +++++++++ PetScale/Helpers/Utilities.cs | 61 ++++++- PetScale/PetScale.cs | 179 ++++++++++++------ PetScale/Structs/PetStruct.cs | 20 +- PetScale/Windows/ConfigWindow.cs | 303 +++++++++++++++++++++---------- 8 files changed, 509 insertions(+), 162 deletions(-) create mode 100644 PetScale/Helpers/ImGuiUtils.cs diff --git a/PetScale/Configuration.cs b/PetScale/Configuration.cs index 79294cb..9c4337f 100644 --- a/PetScale/Configuration.cs +++ b/PetScale/Configuration.cs @@ -13,7 +13,7 @@ public class Configuration : IPluginConfiguration { public int Version { get; set; } = 0; public IList PetData { get; set; } = []; - public int FairySize { get; set; } = 0; + public PetState FairyState { get; set; } = PetState.Off; public ushort HomeWorld { get; set; } = 0; public bool UpdateNeeded { get; internal set; } diff --git a/PetScale/Enums/PetEnums.cs b/PetScale/Enums/PetEnums.cs index f1ac361..b910b76 100644 --- a/PetScale/Enums/PetEnums.cs +++ b/PetScale/Enums/PetEnums.cs @@ -21,6 +21,14 @@ public enum PetRow : uint AutomatonQueen = 18, // DRK Emo Clone Esteem = 17, + // Custom SMN pets + Carbuncle = 23, + RubyCarbuncle = 24, + TopazCarbuncle = 25, + EmeraldCarbuncle = 26, + IfritEgi = 27, + TitanEgi = 28, + GarudaEgi = 29, } /// @@ -44,6 +52,14 @@ public enum PetModel AutomatonQueen = 2618, // DRK Emo Clone Esteem = 2621, + // Custom SMN pets + Carbuncle = 411, + RubyCarbuncle = 410, + TopazCarbuncle = 412, + EmeraldCarbuncle = 409, + IfritEgi = 415, + TitanEgi = 416, + GarudaEgi = 417, } public enum PetSize diff --git a/PetScale/GlobalSuppressions.cs b/PetScale/GlobalSuppressions.cs index 4bf7d9d..41a9e85 100644 --- a/PetScale/GlobalSuppressions.cs +++ b/PetScale/GlobalSuppressions.cs @@ -8,7 +8,10 @@ [assembly: SuppressMessage("Design", "MA0049:Type name should not match containing namespace", Justification = "...and I took that personally.", Scope = "type", Target = "~T:PetScale.PetScale")] [assembly: SuppressMessage("Design", "MA0038:Make method static (deprecated, use CA1822 instead)", Justification = "Obsolete", Scope = "member", Target = "~M:PetScale.Helpers.Utilities.SetScale(FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*,System.Single)")] [assembly: SuppressMessage("Design", "MA0041:Make property static (deprecated, use CA1822 instead)", Justification = "Obsolete", Scope = "member", Target = "~P:PetScale.PetScale.BattleCharaSpan")] -[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "Not meant for public consumption", Scope = "member", Target = "~P:PetScale.Windows.ConfigWindow.petMap")] -[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "No", Scope = "member", Target = "~P:PetScale.Windows.ConfigWindow.otherPetMap")] +[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "Not meant for public consumption", Scope = "member", Target = "~P:PetScale.Windows.ConfigWindow.presetPetMap")] +[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "No", Scope = "member", Target = "~P:PetScale.Windows.ConfigWindow.customPetMap")] [assembly: SuppressMessage("Performance", "MA0066:Hash table unfriendly type is used in a hash table", Justification = "None", Scope = "member", Target = "~F:PetScale.PetScale.activePetDictionary")] [assembly: SuppressMessage("Design", "MA0038:Make method static (deprecated, use CA1822 instead)", Justification = "Obsolete", Scope = "member", Target = "~M:PetScale.Helpers.Utilities.PetVisible(FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)~System.Boolean")] +[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "No", Scope = "member", Target = "~P:PetScale.Windows.ConfigWindow.worldMap")] +[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "No", Scope = "member", Target = "~P:PetScale.PetScale.presetPetModelMap")] +[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "No", Scope = "member", Target = "~P:PetScale.PetScale.customPetModelMap")] diff --git a/PetScale/Helpers/ImGuiUtils.cs b/PetScale/Helpers/ImGuiUtils.cs new file mode 100644 index 0000000..a2a41f5 --- /dev/null +++ b/PetScale/Helpers/ImGuiUtils.cs @@ -0,0 +1,83 @@ +using System; +using System.Numerics; + +using ImGuiNET; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Windowing; +using Dalamud.Interface.ManagedFontAtlas; + +namespace PetScale.Helpers; + +public static class ImGuiUtils +{ + private static bool IsDrawSafe => PetScale.DrawAvailable; + + public static float GetStyleWidth() + { + if (!IsDrawSafe) + { + throw new InvalidOperationException($"{nameof(GetStyleWidth)} called outside {nameof(WindowSystem.Draw)} instance"); + } + return (ImGui.GetStyle().FramePadding.X * 2) + (ImGui.GetStyle().ItemSpacing.X * 2) + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().ItemInnerSpacing.X; + } + + public static void CenterText(string text, float horizontalSpace, int magicNumber = 4) + { + if (!IsDrawSafe) + { + throw new InvalidOperationException($"{nameof(CenterText)} called outside {nameof(WindowSystem.Draw)} instance"); + } + // Right now magicNumber is setup to match the amount of columns in the given table that this is used for + // Whether that's actually the apropriate way of getting an accurate center, or I just stumbled upon it + // is between god and me, and I forgot + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ((horizontalSpace - ImGui.CalcTextSize(text).X - (GetStyleWidth() / magicNumber)) / 2)); + ImGui.TextUnformatted(text); + } + + public static Vector2 IconButtonSize(IFontHandle fontHandle, string icon) + { + if (!IsDrawSafe) + { + throw new InvalidOperationException($"{nameof(IconButtonSize)} called outside {nameof(WindowSystem.Draw)} instance"); + } + using (fontHandle.Push()) + { + return new Vector2(ImGuiHelpers.GetButtonSize(icon).X, ImGui.GetFrameHeight()); + } + } + + // widthOffset is a pain in the ass, at 100% you want 0, <100% you want 1 or more, >100% it entirely depends on whether you get a non-repeating divison or not... maybe? + // also this entirely varies for each icon, so good luck aligning everything + public static bool IconButton(IFontHandle fontHandle, string icon, string buttonIDLabel, float widthOffset = 0f) + { + if (!IsDrawSafe) + { + throw new InvalidOperationException($"{nameof(IconButton)} called outside {nameof(WindowSystem.Draw)} instance"); + } + using (fontHandle.Push()) + { + var cursorScreenPos = ImGui.GetCursorScreenPos(); + var frameHeight = ImGui.GetFrameHeight(); + var result = ImGui.Button("##" + buttonIDLabel, new Vector2(ImGuiHelpers.GetButtonSize(icon).X, frameHeight)); + var pos = new Vector2(cursorScreenPos.X + ImGui.GetStyle().FramePadding.X + widthOffset, + cursorScreenPos.Y + (frameHeight / 2f) - (ImGui.CalcTextSize(icon).Y / 2f)); + ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), icon); + + return result; + } + } + + public static void CenterCursor(Vector2 windowSize, Vector2? offset = null) + { + if (!IsDrawSafe) + { + throw new InvalidOperationException($"{nameof(CenterCursor)} called outside {nameof(WindowSystem.Draw)} instance"); + } + var center = windowSize / 2; + if (offset.HasValue) + { + center -= offset.Value / 2; + } + ImGui.SetCursorPos(center); + } +} diff --git a/PetScale/Helpers/Utilities.cs b/PetScale/Helpers/Utilities.cs index 69853d7..e21833e 100644 --- a/PetScale/Helpers/Utilities.cs +++ b/PetScale/Helpers/Utilities.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using Lumina.Excel; @@ -20,6 +21,14 @@ public class Utilities(IDataManager _dataManager, IPluginLog _pluginLog, ClientL private readonly IPluginLog log = _pluginLog; private readonly ClientLanguage language = _language; private ExcelSheet? worldSheet = null; + private ExcelSheet? WorldSheet + { + get + { + worldSheet??= GetSheet(language); + return worldSheet; + } + } /// /// Attempt to retrieve an , optionally in a specific . @@ -59,6 +68,7 @@ public unsafe void SetScale(BattleChara* pet, float scale) drawObject->Object.Scale.Y = scale; drawObject->Object.Scale.Z = scale; } + //log.Debug("Set {a} to size {b}", pet->NameString, scale); } private static unsafe DrawState* ActorDrawState(IGameObject actor) @@ -73,7 +83,7 @@ public static unsafe void ToggleVisibility(IGameObject actor) *ActorDrawState(actor) ^= DrawState.Invisibility; } - public unsafe void CachePlayerList(uint playerEntityId, ushort homeWorld, Queue<(string, ulong)> queue, Span> CharacterSpan) + public unsafe void CachePlayerList(uint playerEntityId, ushort homeWorld, Queue<(string Name, ulong ContentId, ushort HomeWorld)> queue, Span> CharacterSpan) { foreach (var chara in CharacterSpan) { @@ -92,7 +102,7 @@ public unsafe void CachePlayerList(uint playerEntityId, ushort homeWorld, Queue< { world = "@" + GetHomeWorldName(chara.Value->Character.HomeWorld); } - queue.Enqueue((chara.Value->Character.NameString + world, chara.Value->ContentId)); + queue.Enqueue((chara.Value->Character.NameString + world, chara.Value->ContentId, chara.Value->HomeWorld)); } } } @@ -116,14 +126,51 @@ public static float GetMinSize(PetModel pet) }; } + public ushort GetHomeWorldId(string name) + { + ushort id = 0; + if (WorldSheet is null) + { + return id; + } + foreach (var world in WorldSheet.Where(item => item.IsPublic)) + { + if (!world.Name.ToDalamudString().TextValue.Equals(name, StringComparison.Ordinal)) + { + continue; + } + id = (ushort)world.RowId; + break; + } + return id; + } + public string GetHomeWorldName(ushort id) { - worldSheet ??= GetSheet(language); - if (worldSheet is not null) + if (WorldSheet is null) { - var world = worldSheet.GetRow(id); - return world?.Name ?? string.Empty; + return string.Empty; + } + return WorldSheet.GetRow(id)?.Name ?? string.Empty; + } + + public void InitWorldMap(IDictionary worldDictionary) + { + if (WorldSheet is null) + { + return; + } + foreach (var currentWorld in WorldSheet) + { + if (!currentWorld.IsPublic) + { + continue; + } + if (currentWorld.Name.ToDalamudString().TextValue.Contains("test", StringComparison.Ordinal)) + { + continue; + } + worldDictionary.Add(currentWorld.Name, currentWorld.DataCenter.Value!.Name.ToDalamudString().TextValue); } - return string.Empty; } } diff --git a/PetScale/PetScale.cs b/PetScale/PetScale.cs index 7df9122..e252aff 100644 --- a/PetScale/PetScale.cs +++ b/PetScale/PetScale.cs @@ -19,6 +19,7 @@ using PetScale.Helpers; using PetScale.Windows; using PetScale.Enums; +using FFXIVClientStructs.FFXIV.Common.Math; namespace PetScale; @@ -42,8 +43,9 @@ public sealed class PetScale : IDalamudPlugin private readonly Dictionary petSizeMap = new(StringComparer.OrdinalIgnoreCase); private readonly Stopwatch stopwatch = new(); private readonly double dictionaryExpirationTime = TimeSpan.FromMilliseconds(500).TotalMilliseconds; + public static bool DrawAvailable { get; private set; } - private readonly Dictionary petModelMap = new() + public static Dictionary presetPetModelMap { get; } = new() { { PetRow.Bahamut, PetModel.Bahamut }, { PetRow.Phoenix, PetModel.Phoenix }, @@ -53,24 +55,32 @@ public sealed class PetScale : IDalamudPlugin { PetRow.SolarBahamut, PetModel.SolarBahamut }, }; - private readonly Dictionary otherPetModelMap = new() + public static Dictionary customPetModelMap { get; } = new() { - { PetRow.Eos, PetModel.Eos }, - { PetRow.Selene, PetModel.Selene }, - { PetRow.Seraph, PetModel.Seraph }, - { PetRow.Rook, PetModel.Rook }, - { PetRow.AutomatonQueen, PetModel.AutomatonQueen }, - { PetRow.Esteem, PetModel.Esteem }, + { PetRow.Eos, PetModel.Eos }, + { PetRow.Selene, PetModel.Selene }, + { PetRow.Seraph, PetModel.Seraph }, + { PetRow.Rook, PetModel.Rook }, + { PetRow.AutomatonQueen, PetModel.AutomatonQueen }, + { PetRow.Esteem, PetModel.Esteem }, + + { PetRow.Carbuncle, PetModel.Carbuncle }, + { PetRow.RubyCarbuncle, PetModel.RubyCarbuncle }, + { PetRow.TopazCarbuncle, PetModel.TopazCarbuncle }, + { PetRow.EmeraldCarbuncle, PetModel.EmeraldCarbuncle }, + { PetRow.IfritEgi, PetModel.IfritEgi }, + { PetRow.TitanEgi, PetModel.TitanEgi }, + { PetRow.GarudaEgi, PetModel.GarudaEgi }, }; - public WindowSystem WindowSystem { get; } = new("PetScale"); - public Queue<(string Name, ulong ContentId)> players { get; } = new(); + public static WindowSystem WindowSystem { get; } = new("PetScale"); + public Queue<(string Name, ulong ContentId, ushort HomeWorld)> players { get; } = new(); public bool requestedCache { get; set; } = true; public int lastIndexOfOthers { get; set; } = -1; private ConfigWindow ConfigWindow { get; init; } #if DEBUG private DevWindow DevWindow { get; init; } - private readonly Dictionary petModelDic = []; + private readonly Dictionary petModelDic = []; private readonly IObjectTable objectTable; #endif @@ -94,7 +104,7 @@ public PetScale(IDalamudPluginInterface _pluginInterface, config = pluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); utilities = new Utilities(_dataManger, log, clientState.ClientLanguage); - ConfigWindow = new ConfigWindow(this, config, pluginInterface, log, _notificationManager); + ConfigWindow = new ConfigWindow(this, config, pluginInterface, log, _notificationManager, utilities); #if DEBUG objectTable = _objectTable; DevWindow = new DevWindow(log, pluginInterface); @@ -109,7 +119,7 @@ public PetScale(IDalamudPluginInterface _pluginInterface, HelpMessage = "Open or close Pet Scale's config window.", }); - pluginInterface.UiBuilder.Draw += WindowSystem.Draw; + pluginInterface.UiBuilder.Draw += UiDraw; pluginInterface.UiBuilder.OpenConfigUi += ConfigWindow.Toggle; clientState.TerritoryChanged += TerritoryChanged; clientState.Login += SetStopwatch; @@ -148,11 +158,11 @@ private void QueueOnlyExistingData() foreach (var entry in config.PetData.DistinctBy(item => item.CharacterName)) { var world = string.Empty; - if (config.HomeWorld is not 0 && config.HomeWorld != entry.HomeWorld && !utilities.GetHomeWorldName(entry.HomeWorld).IsNullOrWhitespace()) + if (config.HomeWorld is not 0 && entry.HomeWorld is not 1 && config.HomeWorld != entry.HomeWorld && !utilities.GetHomeWorldName(entry.HomeWorld).IsNullOrWhitespace()) { world = "@" + utilities.GetHomeWorldName(entry.HomeWorld); } - players.Enqueue((entry.CharacterName + world, entry.ContentId)); + players.Enqueue((entry.CharacterName + world, entry.ContentId, entry.HomeWorld)); } } @@ -163,7 +173,7 @@ private void InitSheet() { return; } - ConfigWindow.petMap.Add(nameof(PetModel.AllPets), PetModel.AllPets); + ConfigWindow.presetPetMap.Add(nameof(PetModel.AllPets), PetModel.AllPets); foreach (var pet in petSheet) { if (!Enum.IsDefined((PetRow)pet.RowId)) @@ -175,14 +185,29 @@ private void InitSheet() { continue; } - if (petModelMap.ContainsKey((PetRow)pet.RowId)) + if (presetPetModelMap.ContainsKey((PetRow)pet.RowId)) { petSizeMap.Add(pet.Name, scales); - ConfigWindow.petMap.Add(pet.Name, petModelMap[(PetRow)pet.RowId]); + ConfigWindow.presetPetMap.Add(pet.Name, presetPetModelMap[(PetRow)pet.RowId]); } } // List of pet rows sorted by SCH pets, MCH pets, DRK pet then in ascending order - List sortedRows = [6, 7, 15, 8, 18, 17]; + List sortedRows = + [ + (uint)PetRow.Eos, + (uint)PetRow.Selene, + (uint)PetRow.Seraph, + (uint)PetRow.Rook, + (uint)PetRow.AutomatonQueen, + (uint)PetRow.Esteem, + (uint)PetRow.Carbuncle, + (uint)PetRow.RubyCarbuncle, + (uint)PetRow.TopazCarbuncle, + (uint)PetRow.EmeraldCarbuncle, + (uint)PetRow.IfritEgi, + (uint)PetRow.TitanEgi, + (uint)PetRow.GarudaEgi, + ]; foreach (var row in sortedRows) { var currentRow = petSheet.GetRow(row); @@ -190,13 +215,13 @@ private void InitSheet() { continue; } - if (!Enum.IsDefined((PetRow)currentRow.RowId) || !otherPetModelMap.ContainsKey((PetRow)currentRow.RowId)) + if (!Enum.IsDefined((PetRow)currentRow.RowId) || !customPetModelMap.ContainsKey((PetRow)currentRow.RowId)) { continue; } - if (currentRow.NonCombatSummon || currentRow.Unknown15) + if (currentRow.NonCombatSummon || currentRow.Unknown15 || sortedRows.Contains(currentRow.RowId)) { - ConfigWindow.otherPetMap.Add(currentRow.Name, otherPetModelMap[(PetRow)currentRow.RowId]); + ConfigWindow.customPetMap.Add(currentRow.Name, customPetModelMap[(PetRow)currentRow.RowId]); } } foreach (var entry in petSizeMap) @@ -223,7 +248,7 @@ private void OnFrameworkUpdate(IFramework framework) if (requestedCache) { playerName ??= player.Name.TextValue; - RefreshCache(playerName, clientState.LocalContentId, player.EntityId); + RefreshCache(playerName, clientState.LocalContentId, player.EntityId, config.HomeWorld); requestedCache = false; } } @@ -259,7 +284,7 @@ private unsafe void PopulateDictionary() if (!petModelDic.ContainsKey(petName)) { PetModel? petModel = Enum.IsDefined(typeof(PetModel), chara.Value->Character.CharacterData.ModelCharaId) ? (PetModel)chara.Value->Character.CharacterData.ModelCharaId : null; - petModelDic.Add(petName, (petModel, chara.Value->Character.CharacterData.ModelCharaId)); + petModelDic.Add(petName, (petModel, chara.Value->Character.CharacterData.ModelCharaId, Vector3.Zero)); } #endif if (!Enum.IsDefined(typeof(PetModel), chara.Value->Character.CharacterData.ModelCharaId)) @@ -267,7 +292,7 @@ private unsafe void PopulateDictionary() continue; } #if DEBUG - DevWindow.AddObjects(objectTable.CreateObjectReference((nint)(&chara.Value->Character))); + //DevWindow.AddObjects(objectTable.CreateObjectReference((nint)(&chara.Value->Character))); #endif activePetDictionary.Add(chara.Value, (null, false)); } @@ -312,7 +337,35 @@ private unsafe void ParseDictionary(uint playerEntityId) } #if DEBUG DevWindow.Print(pet->NameString + ": " + pet->Character.CharacterData.ModelCharaId + " owned by " + character->NameString + " size " + pet->Character.GameObject.Scale); + var drawModel = pet->Character.GetDrawObject(); + if (drawModel is not null && petModelDic.TryGetValue(pet->NameString, out var current)) + { + current.scale = drawModel->Scale; + petModelDic[pet->NameString] = current; + } #endif + if (config.FairyState is not PetState.Off && (PetModel)pet->Character.CharacterData.ModelCharaId is PetModel.Eos or PetModel.Selene) + { + switch (config.FairyState) + { + case PetState.Self when character->GameObject.EntityId == playerEntityId: + case PetState.Others when character->GameObject.EntityId != playerEntityId: + case PetState.All: + { + utilities.SetScale(pet, 1.5f); + activePetDictionary[pair.Key] = (pair.Value.character, true); + if (config.PetData.Any( + item => (item.ContentId == character->ContentId || item.CharacterName.Equals(character->NameString, ordinalComparison)) + && item.PetID == (PetModel)pet->Character.CharacterData.ModelCharaId)) + { + break; + } + continue; + } + default: + break; + } + } if (ParseStruct(pet, character, pet->Character.CharacterData.ModelCharaId, character->GameObject.EntityId == playerEntityId)) { activePetDictionary[pair.Key] = (pair.Value.character, true); @@ -356,12 +409,14 @@ private unsafe bool OldParse(BattleChara* pet, Character* character, bool isLoca if (allPets.Exists(item => item.CharacterName.Equals(userData.CharacterName, ordinalComparison)) && userData.PetID is PetModel.AllPets) { - petSet = SetScale(pet, userData, petName); + SetScale(pet, userData, petName); + petSet = true; } // Specific Pet for General Character - if (userData.Generic && userData.PetID == modelType) + if (userData.PetID == modelType) { - petSet = SetScale(pet, userData, petName); + SetScale(pet, userData, petName); + petSet = true; } } for (var i = index + 1; i < config.PetData.Count; i++) @@ -371,20 +426,21 @@ private unsafe bool OldParse(BattleChara* pet, Character* character, bool isLoca { continue; } - userData.ContentId = character->ContentId; - userData.HomeWorld = character->HomeWorld; + userData.UpdateData(character->HomeWorld, character->ContentId); config.PetData[i] = userData; savePending = true; // General Pet for Specific Character if (allPets.Exists(item => item.CharacterName.Equals(userData.CharacterName, ordinalComparison)) && userData.PetID is PetModel.AllPets) { - petSet = SetScale(pet, userData, petName); + SetScale(pet, userData, petName); + petSet = true; } // Specific Pet for Specific Character - if (userData.CharacterName.Equals(character->NameString, ordinalComparison) && userData.PetID == modelType) + if (userData.PetID == modelType) { - petSet = SetScale(pet, userData, petName); + SetScale(pet, userData, petName); + petSet = true; } } return petSet; @@ -398,19 +454,21 @@ private unsafe bool NewParse(BattleChara* pet, Character* character, bool isLoca for (var i = 0; i <= index; i++) { var userData = config.PetData[i]; - if (isLocalPlayer || !userData.Generic) + if (isLocalPlayer) { continue; } // General Pet for General Character - if (allPets.Exists(item => item.ContentId == userData.ContentId) && userData.PetID is PetModel.AllPets) + if (userData.PetID is PetModel.AllPets && allPets.Exists(item => item.ContentId == userData.ContentId)) { - petSet = SetScale(pet, userData, petName); + SetScale(pet, userData, petName); + petSet = true; } // Specific Pet for General Character if (userData.PetID == modelType) { - petSet = SetScale(pet, userData, petName); + SetScale(pet, userData, petName); + petSet = true; } } for (var i = index + 1; i < config.PetData.Count; i++) @@ -421,30 +479,41 @@ private unsafe bool NewParse(BattleChara* pet, Character* character, bool isLoca continue; } // General Pet for Specific Character - if (allPets.Exists(item => item.ContentId == userData.ContentId) && userData.PetID is PetModel.AllPets && userData.Generic) + if (userData.PetID is PetModel.AllPets && allPets.Exists(item => item.ContentId == userData.ContentId)) { - petSet = SetScale(pet, userData, petName); + SetScale(pet, userData, petName); + petSet = true; } // Specific Pet for Specific Character if (userData.PetID == modelType) { - petSet = SetScale(pet, userData, petName); + SetScale(pet, userData, petName); + petSet = true; } } return petSet; } - private unsafe void RefreshCache(string playerName, ulong contentId, uint entityId) + private unsafe void RefreshCache(string playerName, ulong contentId, uint entityId, ushort homeWorld) { players.Clear(); - players.Enqueue((playerName, contentId)); - players.Enqueue((Others, OthersContendId)); + players.Enqueue((playerName, contentId, homeWorld)); + players.Enqueue((Others, OthersContendId, OthersHomeWorld)); utilities.CachePlayerList(entityId, config.HomeWorld, players, BattleCharaSpan); } - private unsafe bool SetScale(BattleChara* pet, in PetStruct userData, string petName) + private unsafe void SetScale(BattleChara* pet, in PetStruct userData, string petName) { - if (petModelMap.ContainsValue(userData.PetID)) + /*var scale = userData.PetSize switch + { + PetSize.SmallModelScale => petSizeMap[petName].smallScale, + PetSize.MediumModelScale => petSizeMap[petName].mediumScale, + PetSize.LargeModelScale => petSizeMap[petName].largeScale, + PetSize.Custom => Math.Max(userData.AltPetSize, Utilities.GetMinSize(userData.PetID)), + _ => throw new ArgumentException("Invalid PetSize", paramName: userData.PetSize.ToString()), + }; + utilities.SetScale(pet, scale);*/ + if (presetPetModelMap.ContainsValue(userData.PetID)) { var scale = userData.PetSize switch { @@ -454,18 +523,13 @@ private unsafe bool SetScale(BattleChara* pet, in PetStruct userData, string pet _ => throw new ArgumentException("Invalid PetSize", paramName: userData.PetSize.ToString()), }; utilities.SetScale(pet, scale); - return true; + return; } - if (otherPetModelMap.ContainsValue(userData.PetID)) + if (customPetModelMap.ContainsValue(userData.PetID) && userData.PetSize is PetSize.Custom) { - if (userData.PetSize is not PetSize.Custom) - { - return false; - } var scale = Math.Max(userData.AltPetSize, Utilities.GetMinSize(userData.PetID)); utilities.SetScale(pet, scale); } - return true; } #if DEBUG @@ -476,11 +540,18 @@ private unsafe void DevWindowThings() foreach (var entry in petModelDic) { var petModel = entry.Value.Item1 is not null ? "True - " + entry.Value.Item1.ToString() : "False"; - DevWindow.Print($"Pet: {entry.Key} - ModelCharaId: {entry.Value.Item2} - IsPetModel: {petModel}"); + DevWindow.Print($"Pet: {entry.Key} - ModelCharaId: {entry.Value.Item2} - Scale: {entry.Value.scale} - IsPetModel: {petModel}"); } } #endif + private static void UiDraw() + { + DrawAvailable = true; + WindowSystem.Draw(); + DrawAvailable = false; + } + private void OnCommand(string command, string args) { ConfigWindow.Toggle(); @@ -496,7 +567,7 @@ public void Dispose() clientState.Logout -= SetStopwatch; clientState.TerritoryChanged -= TerritoryChanged; pluginInterface.UiBuilder.OpenConfigUi -= ConfigWindow.Toggle; - pluginInterface.UiBuilder.Draw -= WindowSystem.Draw; + pluginInterface.UiBuilder.Draw -= UiDraw; ConfigWindow.Dispose(); WindowSystem.RemoveAllWindows(); diff --git a/PetScale/Structs/PetStruct.cs b/PetScale/Structs/PetStruct.cs index 9d09cf4..b5760f8 100644 --- a/PetScale/Structs/PetStruct.cs +++ b/PetScale/Structs/PetStruct.cs @@ -24,7 +24,23 @@ public PetStruct() public readonly bool IsDefault() => CharacterName.IsNullOrWhitespace() || CharacterName.Equals("Default", StringComparison.Ordinal); - public readonly bool UpdateRequired() - => ContentId is 0 || HomeWorld is 0; + => ContentId is 0 || HomeWorld is 0; + + public void UpdateData(ushort OtherHomeWorld, ulong OtherContentId) + { + if (HomeWorld is 0 && ContentId is 0) + { + ContentId = OtherContentId; + HomeWorld = OtherHomeWorld; + } + else if (HomeWorld is 0 && ContentId == OtherContentId) + { + HomeWorld = OtherHomeWorld; + } + else if (HomeWorld == OtherHomeWorld && ContentId is 0) + { + ContentId = OtherContentId; + } + } } diff --git a/PetScale/Windows/ConfigWindow.cs b/PetScale/Windows/ConfigWindow.cs index f7b9dd4..d408ed5 100644 --- a/PetScale/Windows/ConfigWindow.cs +++ b/PetScale/Windows/ConfigWindow.cs @@ -22,6 +22,7 @@ using PetScale.Helpers; using PetScale.Structs; using PetScale.Enums; +using System.Text; namespace PetScale.Windows; @@ -32,6 +33,8 @@ public sealed class ConfigWindow : Window, IDisposable private readonly PetScale plugin; private readonly IPluginLog log; private readonly INotificationManager notificationManager; + private readonly Utilities utilities; + private readonly Dictionary comboFilter = []; private readonly Dictionary sizeMap = new() { { PetSize.SmallModelScale, "Small" }, @@ -42,34 +45,36 @@ public sealed class ConfigWindow : Window, IDisposable private readonly CancellationTokenSource cts; private readonly CancellationToken cToken; private readonly Notification notification = new(); - private const string DefaultPetSelection = "Pet"; - private const string DefaultSizeSelection = "Size"; - private const string DefaultCharacterSelection = "Characters"; - private const string LongestCharaName = "WWWWWWWWWWWWWWW WWWWW@Pandaemonium"; - private const string LongestSize = "Medium"; - - public Dictionary petMap { get; } = new(StringComparer.Ordinal); - public Dictionary otherPetMap { get; } = new(StringComparer.Ordinal); - private Queue<(string Name, ulong ContentId)> players => plugin.players; + private const string DefaultPetSelection = "Pet", DefaultSizeSelection = "Size", DefaultCharacterSelection = "Characters", DefaultWorldSelection = "World"; + private const string LongestSize = "Medium", LongestWorldName = "Pandaemonium", LongestCharaName = "WWWWWWWWWWWWWWW WWWWW" + "@" + LongestWorldName; + private const string WorldSelectModal = "World Select"; + + public Dictionary presetPetMap { get; } = new(StringComparer.Ordinal); + public Dictionary customPetMap { get; } = new(StringComparer.Ordinal); + public Dictionary worldMap { get; } = new(StringComparer.Ordinal); + private Queue<(string Name, ulong ContentId, ushort HomeWorld)> players => plugin.players; private IList petData => config.PetData; private IFontHandle iconFont => pluginInterface.UiBuilder.IconFontFixedWidthHandle; private string petSelection = DefaultPetSelection, longestPetName = string.Empty, sizeSelection = DefaultSizeSelection, charaName = DefaultCharacterSelection; - private string filterTemp = string.Empty, otherPetSelection = DefaultPetSelection; - private float tableButtonAlignmentOffset, charaWidth, petWidth, sizesWidth, tempPetSize = 1f; - private bool fontChange; + private string filterTemp = string.Empty, otherPetSelection = DefaultPetSelection, world = DefaultWorldSelection; + private float tableButtonAlignmentOffset, charaWidth, petWidth, sizesWidth, worldWidth, tempPetSize = 1f; + private bool fontChange, showModal = false; + private Tab currentTab; public unsafe ConfigWindow(PetScale _plugin, Configuration _config, IDalamudPluginInterface _pluginInterface, IPluginLog _pluginLog, - INotificationManager _notificationManager) : base($"{nameof(PetScale)} Config") + INotificationManager _notificationManager, + Utilities _utils) : base($"{nameof(PetScale)} Config") { plugin = _plugin; config = _config; pluginInterface = _pluginInterface; log = _pluginLog; notificationManager = _notificationManager; + utilities = _utils; SizeConstraints = new WindowSizeConstraints() { MinimumSize = new Vector2(470, 365), @@ -82,6 +87,8 @@ public unsafe ConfigWindow(PetScale _plugin, deleteButtonIcon = FontAwesomeIcon.Trash.ToIconString(); addButtonIcon = FontAwesomeIcon.Plus.ToIconString(); pluginInterface.UiBuilder.DefaultFontHandle.ImFontChanged += QueueColumnWidthChange; + + _ = Task.Run(() => utilities.InitWorldMap(worldMap), cToken); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -99,6 +106,8 @@ public override void OnClose() charaName = DefaultCharacterSelection; petSelection = otherPetSelection = DefaultPetSelection; sizeSelection = DefaultSizeSelection; + world = DefaultWorldSelection; + comboFilter.Clear(); } public override void Draw() @@ -109,9 +118,13 @@ public override void Draw() { return; } - GeneralTab(); - OtherPetsTab(); + PresetTab(); + CustomTab(); MiscTab(); + foreach (var item in comboFilter) + { + DevWindow.Print("Filter stuff: " + item.Key + " - " + (item.Value ?? "null")); + } } private void MiscTab() @@ -121,42 +134,44 @@ private void MiscTab() { return; } + currentTab = Tab.None; DrawRadioButtons( "Scale SCH fairy to the size of other in-game fairies", () => { ImGui.SameLine(); - ImGuiComponents.HelpMarker("Seraph is excluded, as she's bigger by default"); + ImGuiComponents.HelpMarker("Seraph is excluded, as she's bigger by default.\nThis will be overwritten by any size set for Eos or Selene."); }, config, - c => c.FairySize, - (c, value) => c.FairySize = value, + c => (int)c.FairyState, + (c, value) => c.FairyState = (PetState)value, "Off", "Self", "Others", "All"); DrawBottomButtons(onlyClose: true); } - private void OtherPetsTab() + private void CustomTab() { using var otherPetsTab = ImRaii.TabItem("Other Pets"); if (!otherPetsTab) { return; } + currentTab = Tab.Others; ImGui.TextUnformatted("Amount of players: " + GetPlayerCount(players.Count, plugin.clientState.IsLoggedIn).ToString(CultureInfo.InvariantCulture)); var buttonPressed = false; - DrawComboBox("Characters", charaName, charaWidth, out charaName, players.Select(player => player.Name).ToList(), filter: true); + DrawComboBox("Characters", charaName, charaWidth, out charaName, players.Select(player => player.Name).ToList(), filter: true, newEntryPossible: true); ImGui.SameLine(); - DrawComboBox("Pets", otherPetSelection, petWidth, out otherPetSelection, otherPetMap.Keys, filter: false); + DrawComboBox("Pets", otherPetSelection, petWidth, out otherPetSelection, customPetMap.Keys, filter: false); ImGui.SameLine(); ImGui.SetNextItemWidth(sizesWidth); if (!otherPetSelection.Equals(DefaultPetSelection, StringComparison.Ordinal) - && tempPetSize < Utilities.GetMinSize(otherPetMap[otherPetSelection])) + && tempPetSize < Utilities.GetMinSize(customPetMap[otherPetSelection])) { - tempPetSize = Utilities.GetMinSize(otherPetMap[otherPetSelection]); + tempPetSize = Utilities.GetMinSize(customPetMap[otherPetSelection]); } if (!otherPetSelection.Equals(DefaultPetSelection, StringComparison.Ordinal)) { - ImGui.DragFloat("##TempPetSize", ref tempPetSize, 0.01f, Utilities.GetMinSize(otherPetMap[otherPetSelection]), 4f, "%.3g", ImGuiSliderFlags.AlwaysClamp); + ImGui.DragFloat("##TempPetSize", ref tempPetSize, 0.01f, Utilities.GetMinSize(customPetMap[otherPetSelection]), 4f, "%.3g", ImGuiSliderFlags.AlwaysClamp); if (ImGui.IsItemHovered()) { ImGui.SetTooltip("Pet Size"); @@ -171,7 +186,7 @@ private void OtherPetsTab() } } ImGui.SameLine(); - if (IconButton(iconFont, addButtonIcon, "AddButton", 1)) + if (ImGuiUtils.IconButton(iconFont, addButtonIcon, "AddButton", 1)) { buttonPressed = true; } @@ -197,25 +212,26 @@ private void OtherPetsTab() DrawBottomButtons(onlyClose: false, otherData: true); } - private void GeneralTab() + private void PresetTab() { - using var generalTab = ImRaii.TabItem("Summoner Pets"); + using var generalTab = ImRaii.TabItem("Preset Pets"); if (!generalTab) { return; } + currentTab = Tab.Summoner; #if DEBUG DevWindow.Print("Summon entries: " + petData.Count.ToString()); #endif ImGui.TextUnformatted("Amount of players: " + GetPlayerCount(players.Count, plugin.clientState.IsLoggedIn).ToString(CultureInfo.InvariantCulture)); var buttonPressed = false; - DrawComboBox("Characters", charaName, charaWidth, out charaName, players.Select(player => player.Name).ToList(), filter: true); + DrawComboBox("Characters", charaName, charaWidth, out charaName, players.Select(player => player.Name).ToList(), filter: true, newEntryPossible: true); ImGui.SameLine(); - DrawComboBox("Pets", petSelection, petWidth, out petSelection, petMap.Keys, filter: false); + DrawComboBox("Pets", petSelection, petWidth, out petSelection, presetPetMap.Keys, filter: false); ImGui.SameLine(); DrawComboBox("Sizes", sizeSelection, sizesWidth, out sizeSelection, sizeMap.Values, filter: false); ImGui.SameLine(); - if (IconButton(iconFont, addButtonIcon, "AddButton", 1)) + if (ImGuiUtils.IconButton(iconFont, addButtonIcon, "AddButton", 1)) { buttonPressed = true; } @@ -259,10 +275,10 @@ private unsafe void DisplayEntries(bool customSize = false) ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, charaWidth); ImGui.TableSetupColumn("Pet", ImGuiTableColumnFlags.WidthFixed, petWidth); ImGui.TableSetupColumn("PetSize", ImGuiTableColumnFlags.WidthFixed, sizesWidth); - ImGui.TableSetupColumn("DeleteButton", ImGuiTableColumnFlags.WidthFixed, IconButtonSize(iconFont, deleteButtonIcon).X); + ImGui.TableSetupColumn("DeleteButton", ImGuiTableColumnFlags.WidthFixed, ImGuiUtils.IconButtonSize(iconFont, deleteButtonIcon).X); var itemRemoved = false; var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - clipper.Begin(petData.Count, IconButtonSize(iconFont, deleteButtonIcon).Y + (ImGui.GetStyle().FramePadding.Y * 2)); + clipper.Begin(petData.Count, ImGuiUtils.IconButtonSize(iconFont, deleteButtonIcon).Y + (ImGui.GetStyle().FramePadding.Y * 2)); var clipperBreak = false; while (clipper.Step()) @@ -283,24 +299,7 @@ private unsafe void DisplayEntries(bool customSize = false) { continue; } - ImGui.TableNextRow(); - var buttonId = "##" + i.ToString(CultureInfo.CurrentCulture); - var item = petData[i]; - - ImGui.TableSetColumnIndex(0); - ImGui.TextUnformatted(" " + item.CharacterName); - ImGui.TableSetColumnIndex(1); - ImGui.TextUnformatted(item.PetID.ToString()); - ImGui.TableSetColumnIndex(2); - ImGui.TextUnformatted(customSize ? item.AltPetSize.ToString(CultureInfo.CurrentCulture) : sizeMap[item.PetSize]); - ImGui.TableSetColumnIndex(3); - ImGui.SetCursorPosX(tableButtonAlignmentOffset); - if (IconButton(iconFont, deleteButtonIcon, buttonId + deleteButtonIcon, 1)) - { - petData.RemoveAt(i); - CreateNotification("Entry " + item.CharacterName + ", " + petSelection + ", " + (customSize ? item.AltPetSize.ToString(CultureInfo.CurrentCulture) : sizeMap[item.PetSize]) + " was removed.", "Entry removed"); - itemRemoved = true; - } + DisplayTableRow(petData[i], i, customSize, "##" + i.ToString(CultureInfo.CurrentCulture), ref itemRemoved); } } clipper.End(); @@ -311,25 +310,84 @@ private unsafe void DisplayEntries(bool customSize = false) } } - private void DrawComboBox(string label, string current, float width, out string result, IReadOnlyCollection list, bool filter) where T : notnull + private void DisplayTableRow(PetStruct item, int index, bool customSize, string buttonId, ref bool itemRemoved) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.TextUnformatted(" " + item.CharacterName); + ImGui.TableSetColumnIndex(1); + ImGui.TextUnformatted(item.PetID.ToString()); + ImGui.TableSetColumnIndex(2); + if (customSize) + { + ImGuiUtils.CenterText(item.AltPetSize.ToString(CultureInfo.CurrentCulture), sizesWidth); + } + else + { + ImGui.TextUnformatted(sizeMap[item.PetSize]); + } + ImGui.TableSetColumnIndex(3); + ImGui.SetCursorPosX(tableButtonAlignmentOffset); + if (ImGuiUtils.IconButton(iconFont, deleteButtonIcon, buttonId + deleteButtonIcon, 1)) + { + petData.RemoveAt(index); + CreateNotification("Entry " + item.CharacterName + ", " + petSelection + ", " + (customSize ? item.AltPetSize.ToString(CultureInfo.CurrentCulture) : sizeMap[item.PetSize]) + " was removed.", "Entry removed"); + itemRemoved = true; + } + } + + // I don't like the way filters are managed, but I can't think of a better way + private void DrawComboBox(string label, string current, float width, out string result, + IReadOnlyCollection list, bool filter, bool newEntryPossible = false) where T : notnull { ImGui.SetNextItemWidth(width); - using var combo = ImRaii.Combo("##Combo" + label, current); + var comboLabel = "##Combo" + label; + using var combo = ImRaii.Combo(comboLabel, current); result = current; if (!combo) { + if (filter && comboFilter.ContainsValue(comboLabel)) + { + comboFilter.Remove(comboLabel); + } return; } var tempList = list.Select(item => item.ToString()!).ToList(); - if (tempList.Count > 0 && filter) + if (tempList.Count > 1 && filter) { tempList.Sort(2, tempList.Count - 2, StringComparer.InvariantCulture); } if (filter) { + comboLabel += "_" + currentTab.ToString(); + comboFilter.TryAdd(comboLabel, null); + if (comboFilter[comboLabel] is null) + { + comboFilter[comboLabel] = string.Empty; + } ImGui.SetNextItemWidth(width); - ImGui.InputTextWithHint("##Filter" + label, "Filter..", ref filterTemp, 30); - tempList = tempList.Where(item => item.Contains(filterTemp, StringComparison.OrdinalIgnoreCase)).ToList(); + var filterStr = comboFilter[comboLabel]; + if (ImGui.InputTextWithHint("##Filter" + label, newEntryPossible ? "New Entry or Filter.." : "Filter..", ref filterStr, 21, ImGuiInputTextFlags.EnterReturnsTrue)) + { + comboFilter[comboLabel] = filterStr; + log.Debug("filter matches {a}", tempList.Count(item => item.Contains(comboFilter[comboLabel]!, StringComparison.OrdinalIgnoreCase))); + if (newEntryPossible && tempList.Count(item => item.Contains(comboFilter[comboLabel]!, StringComparison.OrdinalIgnoreCase)) is 0) + { + showModal = true; + filterStr = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(comboFilter[comboLabel]!.ToLower(CultureInfo.CurrentCulture)); + ImGui.OpenPopup(WorldSelectModal); + } + } + comboFilter[comboLabel] = filterStr; + if (newEntryPossible && ImGui.IsItemHovered()) + { + ImGui.SetTooltip("New entry can be made if your search doesn't return any returns in the list"); + } + if (PopupModal(WorldSelectModal, comboFilter[comboLabel]!)) + { + comboFilter[comboLabel] = string.Empty; + } + tempList = tempList.Where(item => item.Contains(comboFilter[comboLabel]!, StringComparison.OrdinalIgnoreCase)).ToList(); } var itemCount = tempList.Count; var height = ImGui.GetTextLineHeightWithSpacing() * Math.Min(itemCount + 1.5f, 8); @@ -371,44 +429,24 @@ private static unsafe void DrawClippedList(int itemCount, string preview, IReadO clipper.Destroy(); } - private static Vector2 IconButtonSize(IFontHandle fontHandle, string icon) - { - using (fontHandle.Push()) - { - return new Vector2(ImGuiHelpers.GetButtonSize(icon).X, ImGui.GetFrameHeight()); - } - } - - // widthOffset is a pain in the ass, at 100% you want 0, <100% you want 1 or more, >100% it entirely depends on whether you get a non-repeating divison or not... maybe? - // also this entirely varies for each icon, so good luck aligning everything - private static bool IconButton(IFontHandle fontHandle, string icon, string buttonIDLabel, float widthOffset = 0f) - { - using (fontHandle.Push()) - { - var cursorScreenPos = ImGui.GetCursorScreenPos(); - var frameHeight = ImGui.GetFrameHeight(); - var result = ImGui.Button("##" + buttonIDLabel, new Vector2(ImGuiHelpers.GetButtonSize(icon).X, frameHeight)); - var pos = new Vector2(cursorScreenPos.X + ImGui.GetStyle().FramePadding.X + widthOffset, - cursorScreenPos.Y + (frameHeight / 2f) - (ImGui.CalcTextSize(icon).Y / 2f)); - ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), icon); - - return result; - } - } - - private void CheckOtherPossibleEntry() + private void CheckOtherPossibleEntry(string? altName = null) { - var tempDic = players.ToDictionary(player => player.Name, cid => cid.ContentId, StringComparer.Ordinal); + var tempDic = players.ToDictionary(player => player.Name, cid => (cid.ContentId, cid.HomeWorld), StringComparer.Ordinal); var currentPetData = new PetStruct() { - CharacterName = charaName, - PetID = otherPetMap[otherPetSelection], + CharacterName = altName ?? charaName, + PetID = customPetMap[otherPetSelection], PetSize = PetSize.Custom, AltPetSize = tempPetSize, }; + if (!altName.IsNullOrWhitespace()) + { + currentPetData.HomeWorld = utilities.GetHomeWorldId(world); + } if (tempDic.TryGetValue(charaName, out var cid)) { - currentPetData.ContentId = cid; + currentPetData.ContentId = cid.ContentId; + currentPetData.HomeWorld = cid.HomeWorld; } if (currentPetData.PetID is PetModel.AllPets) { @@ -435,7 +473,8 @@ private void CheckOtherPossibleEntry() checkPet.AltPetSize = tempPetSize; if (checkPet.UpdateRequired()) { - checkPet.ContentId = cid; + checkPet.ContentId = cid.ContentId; + checkPet.HomeWorld = cid.HomeWorld; if (checkPet.PetID is PetModel.AllPets) { checkPet.Generic = true; @@ -448,7 +487,7 @@ private void CheckOtherPossibleEntry() ProcessPetData(save: true); } - private void CheckPossibleEntry() + private void CheckPossibleEntry(string? altName = null) { var currentPetSize = sizeMap.SingleOrDefault(x => x.Value.Equals(sizeSelection, StringComparison.OrdinalIgnoreCase)); if (currentPetSize.Value is null) @@ -458,10 +497,14 @@ private void CheckPossibleEntry() var tempDic = players.ToDictionary(player => player.Name, cid => cid.ContentId, StringComparer.Ordinal); var currentPetData = new PetStruct() { - CharacterName = charaName, - PetID = petMap[petSelection], + CharacterName = altName ?? charaName, + PetID = presetPetMap[petSelection], PetSize = currentPetSize.Key, }; + if (!altName.IsNullOrWhitespace()) + { + currentPetData.HomeWorld = utilities.GetHomeWorldId(world); + } if (tempDic.TryGetValue(charaName, out var cid)) { currentPetData.ContentId = cid; @@ -552,7 +595,7 @@ private void ResizeIfNeeded() if (fontChange || charaWidth is 0 || petWidth is 0 || sizesWidth is 0) { var currentSize = ImGui.CalcTextSize(longestPetName).X; - foreach (var petName in petMap.Select(pet => pet.Key)) + foreach (var petName in presetPetMap.Select(pet => pet.Key)) { var size = ImGui.CalcTextSize(petName).X; if (size > currentSize) @@ -561,7 +604,7 @@ private void ResizeIfNeeded() currentSize = size; } } - foreach (var petName in otherPetMap.Select(pet => pet.Key)) + foreach (var petName in customPetMap.Select(pet => pet.Key)) { var size = ImGui.CalcTextSize(petName).X; if (size > currentSize) @@ -570,13 +613,14 @@ private void ResizeIfNeeded() currentSize = size; } } + worldWidth = ImGui.CalcTextSize(LongestWorldName).X + (ImGui.GetStyle().FramePadding.X * 2); charaWidth = ImGui.CalcTextSize(LongestCharaName).X + (ImGui.GetStyle().FramePadding.X * 2); petWidth = ImGui.CalcTextSize(longestPetName).X + (ImGui.GetStyle().FramePadding.X * 2) + 25; sizesWidth = ImGui.CalcTextSize(LongestSize).X + (ImGui.GetStyle().FramePadding.X * 2) + 25; tableButtonAlignmentOffset = charaWidth + petWidth + sizesWidth + (ImGui.GetStyle().ItemSpacing.X * 3); if (SizeConstraints.HasValue) { - var newWidth = tableButtonAlignmentOffset + IconButtonSize(iconFont, deleteButtonIcon).X + (ImGui.GetStyle().WindowPadding.X * 2) + ImGui.GetStyle().ScrollbarSize; + var newWidth = tableButtonAlignmentOffset + ImGuiUtils.IconButtonSize(iconFont, deleteButtonIcon).X + (ImGui.GetStyle().WindowPadding.X * 2) + ImGui.GetStyle().ScrollbarSize; SizeConstraints = new WindowSizeConstraints() { MinimumSize = new Vector2(newWidth / ImGuiHelpers.GlobalScale, SizeConstraints.Value.MinimumSize.Y), @@ -681,8 +725,8 @@ private void DrawRadioButtons(string label, Action? extra, in Configuration conf setOption(config, radioOption); config.Save(pluginInterface); } - space -= ImGui.CalcTextSize(buttons[i]).X + GetStyleWidth(); - if (i + 1 < buttons.Length && space > ImGui.CalcTextSize(buttons[i + 1]).X + GetStyleWidth()) + space -= ImGui.CalcTextSize(buttons[i]).X + ImGuiUtils.GetStyleWidth(); + if (i + 1 < buttons.Length && space > ImGui.CalcTextSize(buttons[i + 1]).X + ImGuiUtils.GetStyleWidth()) { ImGui.SameLine(); } @@ -693,8 +737,67 @@ private void DrawRadioButtons(string label, Action? extra, in Configuration conf } } - private static float GetStyleWidth() - => (ImGui.GetStyle().FramePadding.X * 2) + (ImGui.GetStyle().ItemSpacing.X * 2) + ImGui.GetStyle().WindowPadding.X + ImGui.GetStyle().ItemInnerSpacing.X; + private bool PopupModal(string label, string newCharacter) + { + var size = new Vector2(ImGui.CalcTextSize(newCharacter + " - ").X + worldWidth + (ImGui.GetStyle().FramePadding.X * 2) + 25, 150f); + var buttonSize = ImGui.CalcTextSize("Add character").X + (ImGui.GetStyle().FramePadding.X * 2) + 25; + if (size.X < buttonSize) + { + size.X = buttonSize; + } + ImGui.SetNextWindowSize(size); + using var modal = ImRaii.PopupModal(label, ref showModal, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoScrollbar); + if (!modal || currentTab is Tab.None) + { + return false; + } + ImGuiUtils.CenterCursor(size, new Vector2(worldWidth + ImGui.CalcTextSize(newCharacter).X, ImGui.GetTextLineHeightWithSpacing())); + ImGui.TextUnformatted(newCharacter); + ImGui.SameLine(); + DrawComboBox("World Selection", world, worldWidth, out world, worldMap.Keys, filter: true); + ImGui.SetCursorPosY(ImGui.GetContentRegionMax().Y - (ImGui.GetFrameHeight() / 2f) - (ImGui.CalcTextSize("Add character").Y / 2f) - (3f * ImGuiHelpers.GlobalScale) + (ImGui.GetScrollY() * 2)); + //ImGui.SetCursorPosY(ImGui.GetWindowContentRegionMax().Y - ImGui.GetFrameHeight() - (3f * ImGuiHelpers.GlobalScale) + (ImGui.GetScrollY() * 2)); + ImGui.SetCursorPosX((ImGui.GetContentRegionMax().X / 2 ) - (ImGuiHelpers.GetButtonSize("Add character").X / 2)); + if (ImGui.Button("Add character")) + { + showModal = false; + var error = false; + if (newCharacter.IsNullOrWhitespace() || !newCharacter.IsValidCharacterName() || newCharacter.Equals("Other Players", StringComparison.Ordinal)) + { + CreateNotification("Invalid Character name", "Invalid entry", NotificationType.Error); + log.Warning("Filter character name: {a}", newCharacter); + error = true; + } + if (currentTab is Tab.Summoner && petSelection.Equals(DefaultPetSelection, StringComparison.Ordinal)) + { + CreateNotification("Invalid Pet selected", "Invalid entry", NotificationType.Error); + error = true; + } + if (currentTab is Tab.Others && otherPetSelection.Equals(DefaultPetSelection, StringComparison.Ordinal)) + { + CreateNotification("Invalid Pet selected", "Invalid entry", NotificationType.Error); + error = true; + } + if (world.Equals(DefaultWorldSelection, StringComparison.Ordinal)) + { + CreateNotification("Invalid World selected", "Invalid entry", NotificationType.Error); + error = true; + } + if (!error) + { + if (currentTab is Tab.Summoner) + { + CheckPossibleEntry(newCharacter); + } + if (currentTab is Tab.Others) + { + CheckOtherPossibleEntry(newCharacter); + } + return true; + } + } + return false; + } public void Dispose() { @@ -703,3 +806,11 @@ public void Dispose() cts.Dispose(); } } + +#pragma warning disable MA0048 // File name must match type name +public enum Tab +{ + None, + Summoner, + Others, +}