Update to API 9 without the Kami touch

main
Liza 2023-10-13 11:38:52 +02:00
parent 32f251720c
commit 2cf6ded1cd
Signed by: liza
GPG Key ID: 7199F8D727D55F67
74 changed files with 4235 additions and 18 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
.vs/
obj/
bin/
*.user
*.user
/.idea

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "KamiLib"]
path = KamiLib
url = https://github.com/MidoriKami/KamiLib

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>CurrencyAlertClassic</AssemblyName>
<Version>0.5.0.1</Version>
<Description>Currency Alert</Description>
<PackageProjectUrl>https://github.com/Lharz/xiv-currency-alert</PackageProjectUrl>
@ -26,7 +27,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.10" />
<PackageReference Include="DalamudPackager" Version="2.1.12" />
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private>

View File

@ -1,6 +1,6 @@
{
"Author": "Lharz",
"Name": "CurrencyAlert",
"Name": "CurrencyAlert Classic",
"Punchline": "Display alerts upon reaching configurable currencies thresholds (such as Poetics or PVP marks).",
"Description": "This plugin lets you easily manage your various currencies to prevent from wasting precious resources.",
"Tags": [

View File

@ -1,5 +1,6 @@
using System;
using System.Linq;
using Dalamud.Interface.Internal;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using ImGuiScene;
@ -13,7 +14,7 @@ public class CurrencyInfo : IDisposable
public uint ItemID { get; }
public string ItemName { get; } = string.Empty;
public uint IconID { get; }
public TextureWrap? IconTexture { get; }
public IDalamudTextureWrap? IconTexture { get; }
public CurrencyInfo(CurrencyName currency)
{
@ -81,4 +82,4 @@ public class CurrencyInfo : IDisposable
.First()
.Item.Row;
}
}
}

View File

@ -3,15 +3,16 @@ using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
namespace CurrencyAlert;
public class Service
{
[PluginService] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService] public static Framework Framework { get; private set; } = null!;
[PluginService] public static ClientState ClientState { get; private set; } = null!;
[PluginService] public static IFramework Framework { get; private set; } = null!;
[PluginService] public static IClientState ClientState { get; private set; } = null!;
public static Configuration Configuration { get; set; } = null!;
public static CurrencyTracker CurrencyTracker { get; set; } = null!;
}
}

View File

@ -43,7 +43,7 @@ public class CurrencyTracker : IDisposable
}
}
private void OnZoneChange(object? sender, ushort e)
private void OnZoneChange(ushort e)
{
if (!Service.Configuration.ChatNotification) return;
if (Condition.IsBoundByDuty()) return;

View File

@ -1,6 +1,7 @@
using CurrencyAlert.DataModels;
using CurrencyAlert.Localization;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using ImGuiNET;
using KamiLib.Drawing;
using KamiLib.Interfaces;
@ -45,4 +46,4 @@ public class GeneralSettingsSelectable : ISelectable, IDrawable
.AddConfigColor(Strings.TextColor, Strings.Default, DisplaySettings.TextColor, Colors.White)
.Draw();
}
}
}

View File

@ -2,6 +2,7 @@
using CurrencyAlert.DataModels;
using CurrencyAlert.Localization;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using ImGuiNET;
using KamiLib.Drawing;
using KamiLib.Interfaces;
@ -43,4 +44,4 @@ public class TrackedCurrencySelectable : ISelectable, IDrawable
.AddInputInt(Strings.Threshold, currency.Threshold, 0, 100000, 0, 0, innerWidth / 2.0f)
.Draw();
}
}
}

View File

@ -4,6 +4,7 @@ using System.Numerics;
using System.Reflection;
using CurrencyAlert.Windows.Components;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using ImGuiNET;
using KamiLib.Drawing;
using KamiLib.Interfaces;

View File

@ -4,9 +4,9 @@
"net7.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.10, )",
"resolved": "2.1.10",
"contentHash": "S6NrvvOnLgT4GDdgwuKVJjbFo+8ZEj+JsEYk9ojjOR/MMfv1dIFpT8aRJQfI24rtDcw1uF+GnSSMN4WW1yt7fw=="
"requested": "[2.1.12, )",
"resolved": "2.1.12",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
},
"kamilib": {
"type": "Project"

@ -1 +0,0 @@
Subproject commit f50ff8903e0adeb2dfe1d64103f54da13e055f89

4
KamiLib/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/obj/
/bin/x64/Debug/KamiLib.deps.json
/bin/x64/Debug/KamiLib.dll
/bin/x64/Debug/KamiLib.pdb

View File

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/projectSettingsUpdater.xml
/modules.xml
/.idea.KamiLib.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,55 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Component.GUI;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace KamiLib.Atk;
public static class AtkValueHelper
{
public static unsafe void PrintAtkValue(AtkValue value, int index)
{
switch (value.Type)
{
case ValueType.Int:
PluginLog.Debug($"[{index:D3}] [{"int", 7}]: {value.Int}");
break;
case ValueType.Bool:
PluginLog.Debug($"[{index:D3}] [{"bool", 7}]: {(value.Byte != 0 ? "true" : "false")}");
break;
case ValueType.UInt:
PluginLog.Debug($"[{index:D3}] [{"uint", 7}]: {value.UInt}");
break;
case ValueType.Float:
PluginLog.Debug($"[{index:D3}] [{"float", 7}]: {value.Float}");
break;
case ValueType.String:
PluginLog.Debug($"[{index:D3}] [{"string", 7}]: {Marshal.PtrToStringUTF8(new nint(value.String))}");
break;
case ValueType.String8:
PluginLog.Debug($"[{index:D3}] [{"string8", 7}]: {Marshal.PtrToStringUTF8(new nint(value.String))}");
break;
case ValueType.Vector:
PluginLog.Debug($"[{index:D3}] [{"vector", 7}]: No Representation Implemented");
break;
case ValueType.AllocatedString:
PluginLog.Debug($"[{index:D3}] [{"aString", 7}]: {Marshal.PtrToStringUTF8(new nint(value.String))}");
break;
case ValueType.AllocatedVector:
PluginLog.Debug($"[{index:D3}] [{"aVector", 7}]: No Representation Implemented");
break;
default:
PluginLog.Debug($"[{index:D3}] [{"unknown", 7}]: [{value.Type}]: {BitConverter.ToString(BitConverter.GetBytes((long)value.String)).Replace("-", " ")}");
break;
}
}
}
public static class AtkValueExtensions
{
public static unsafe string GetString(this AtkValue value)
{
return Marshal.PtrToStringUTF8(new nint(value.String)) ?? "Unable to Allocate String";
}
}

106
KamiLib/Atk/NodeHelper.cs Normal file
View File

@ -0,0 +1,106 @@
using System.Linq;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiLib.ChatCommands;
namespace KamiLib.Atk;
public unsafe class BaseNode
{
private readonly AtkUnitBase* node;
public bool NodeValid => node != null;
public BaseNode(string addon)
{
node = (AtkUnitBase*) Service.GameGui.GetAddonByName(addon, 1);
}
public BaseNode Print()
{
Chat.Print("AtkUnitBase", $"{new nint(node):X8}");
return this;
}
public AtkResNode* GetRootNode() => node == null ? null : node->RootNode;
public T* GetNode<T>(uint id) where T : unmanaged
{
if (node == null) return null;
var targetNode = (T*) node->GetNodeById(id);
return targetNode;
}
public ComponentNode GetComponentNode(uint id)
{
if (node == null) return new ComponentNode(null);
var targetNode = (AtkComponentNode*) node->GetNodeById(id);
return new ComponentNode(targetNode);
}
public ComponentNode GetNestedNode(params uint[] idList)
{
uint index = 0;
ComponentNode startingNode;
do
{
startingNode = GetComponentNode(idList[index]);
} while (index++ < idList.Length);
return startingNode;
}
}
public unsafe class ComponentNode
{
private readonly AtkComponentNode* node;
private readonly AtkComponentBase* componentBase;
public ComponentNode(AtkComponentNode* node)
{
this.node = node;
componentBase = node == null ? null : node->Component;
}
public ComponentNode Print()
{
Chat.Print("AtkComponentNode", $"{new nint(node):X8}");
return this;
}
public ComponentNode GetComponentNode(uint id)
{
if (componentBase == null) return new ComponentNode(null);
var targetNode = Node.GetNodeByID<AtkComponentNode>(componentBase->UldManager, id);
return new ComponentNode(targetNode);
}
public T* GetNode<T>(uint id) where T : unmanaged => componentBase == null ? null : Node.GetNodeByID<T>(componentBase->UldManager, id);
public AtkComponentNode* GetPointer() => node;
}
public static unsafe class Node
{
public static T* GetNodeByID<T>(AtkUldManager uldManager, uint nodeId) where T : unmanaged
{
foreach (var index in Enumerable.Range(0, uldManager.NodeListCount))
{
var currentNode = uldManager.NodeList[index];
if (currentNode->NodeID != nodeId) continue;
return (T*) currentNode;
}
return null;
}
}

View File

@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Logging;
using Dalamud.Utility;
using ImGuiNET;
using KamiLib.Caching;
using KamiLib.Configuration;
using KamiLib.Drawing;
using KamiLib.Extensions;
using KamiLib.Localization;
using Lumina.Excel.GeneratedSheets;
namespace KamiLib.Blacklist;
public static class BlacklistDraw
{
private static readonly List<uint> EntriesToRemove = new();
private static readonly List<uint> EntriesToAdd = new();
private static string _searchString = string.Empty;
private static List<SearchResult>? _searchResults = new();
public static void DrawBlacklist(Setting<List<uint>> blacklistedAreas)
{
InfoBox.Instance
.AddTitle(Strings.Blacklist_CurrentlyBlacklisted, out var innerWidth, 1.0f)
.AddDummy(5.0f)
.AddAction(() => BlacklistedAreasList(blacklistedAreas))
.AddDisabledButton(EntriesToRemove.Count == 0 ? Strings.Blacklist_ClearBlacklist : Strings.Blacklist_RemoveSelectedAreas.Format(EntriesToRemove.Count), () =>
{
if (EntriesToRemove.Count == 0)
{
blacklistedAreas.Value.Clear();
KamiCommon.SaveConfiguration();
}
else
{
blacklistedAreas.Value.RemoveAll(entry => EntriesToRemove.Contains(entry));
EntriesToRemove.Clear();
KamiCommon.SaveConfiguration();
}
}, !ImGui.GetIO().KeyShift, Strings.DisabledButton_HoldShift, innerWidth)
.Draw();
}
public static void DrawAddRemoveHere(Setting<List<uint>> blacklistedZones)
{
InfoBox.Instance
.AddTitle(Strings.Blacklist_AddRemoveZone, 1.0f)
.AddAction(() => LuminaCache<TerritoryType>.Instance.GetRow(Service.ClientState.TerritoryType)?.DrawLabel())
.BeginTable()
.BeginRow()
.AddDisabledButton(Strings.Common_Add, () =>
{
Add(blacklistedZones, Service.ClientState.TerritoryType);
}, blacklistedZones.Value.Contains(Service.ClientState.TerritoryType), buttonSize: InfoBox.Instance.InnerWidth / 2.0f - 5.0f * ImGuiHelpers.GlobalScale)
.AddDisabledButton(Strings.Common_Remove, () =>
{
Remove(blacklistedZones, Service.ClientState.TerritoryType);
}, !blacklistedZones.Value.Contains(Service.ClientState.TerritoryType), buttonSize: InfoBox.Instance.InnerWidth / 2.0f - 5.0f * ImGuiHelpers.GlobalScale)
.EndRow()
.EndTable()
.Draw();
}
public static void DrawTerritorySearch(Setting<List<uint>> blacklistedZones)
{
InfoBox.Instance
.AddTitle(Strings.Blacklist_ZoneSearch, out var innerWidth, 1.0f)
.AddAction(() =>
{
ImGui.PushItemWidth(InfoBox.Instance.InnerWidth);
if (ImGui.InputTextWithHint("###TerritorySearch", Strings.Blacklist_Search, ref _searchString, 60, ImGuiInputTextFlags.AutoSelectAll))
{
_searchResults = Search(_searchString, 5);
PluginLog.Debug("Updating TerritorySearch Results");
}
})
.AddAction(() => DisplayResults(_searchResults))
.AddDisabledButton(Strings.Blacklist_AddSelectedAreas.Format(EntriesToAdd.Count), () =>
{
blacklistedZones.Value.AddRange(EntriesToAdd);
EntriesToAdd.Clear();
KamiCommon.SaveConfiguration();
}, !EntriesToAdd.Any(), Strings.Blacklist_SelectZones, innerWidth)
.Draw();
}
public static void PrimeSearch()
{
_searchResults = Search("", 5);
}
private static List<SearchResult> Search(string searchTerms, int numResults)
{
return Service.DataManager.GetExcelSheet<TerritoryType>()!
.Where(territory => territory.PlaceName.Row is not 0)
.Where(territory => territory.PlaceName.Value is not null)
.GroupBy(territory => territory.PlaceName.Value!.Name.ToDalamudString().TextValue)
.Select(territory => territory.First())
.Where(territory => territory.PlaceName.Value!.Name.ToDalamudString().TextValue.ToLower().Contains(searchTerms.ToLower()))
.Select(territory => new SearchResult {
TerritoryID = territory.RowId
})
.OrderBy(searchResult => searchResult.TerritoryName)
.Take(numResults)
.ToList();
}
private static void DisplayResults(List<SearchResult>? results)
{
if (results is null) return;
if (ImGui.BeginChild("###SearchResultsChild", new Vector2(InfoBox.Instance.InnerWidth, 21.0f * 5 * ImGuiHelpers.GlobalScale )))
{
foreach (var result in results)
{
if (ImGui.Selectable($"###SearchResult{result.TerritoryID}", EntriesToAdd.Contains(result.TerritoryID)))
{
if (!EntriesToAdd.Contains(result.TerritoryID))
{
EntriesToAdd.Add(result.TerritoryID);
}
else
{
EntriesToAdd.Remove(result.TerritoryID);
}
}
ImGui.SameLine();
LuminaCache<TerritoryType>.Instance.GetRow(result.TerritoryID)?.DrawLabel();
}
}
ImGui.EndChild();
}
private static void BlacklistedAreasList(Setting<List<uint>> blacklistedAreas)
{
var itemCount = Math.Min(blacklistedAreas.Value.Count, 10);
var listHeight = itemCount * ImGuiHelpers.GlobalScale * 21.0f;
var minHeight = 21.0f * ImGuiHelpers.GlobalScale;
var size = new Vector2(InfoBox.Instance.InnerWidth, MathF.Max(listHeight, minHeight));
if(ImGui.BeginChild("###BlacklistFrame", size, false))
{
if (!blacklistedAreas.Value.Any())
{
ImGui.SetCursorPos(ImGui.GetCursorPos() with { X = ImGui.GetContentRegionAvail().X / 2 - ImGui.CalcTextSize(Strings.Blacklist_Empty).X / 2.0f});
ImGui.TextColored(Colors.Orange, Strings.Blacklist_Empty);
}
else
{
DrawBlacklistedAreas(blacklistedAreas);
}
}
ImGui.EndChild();
}
private static void DrawBlacklistedAreas(Setting<List<uint>> blacklistedAreas)
{
var territories = blacklistedAreas.Value
.Select(area => LuminaCache<TerritoryType>.Instance.GetRow(area))
.OfType<TerritoryType>()
.OrderBy(territory => territory.GetPlaceNameString());
foreach (var territory in territories)
{
ImGui.PushItemWidth(InfoBox.Instance.InnerWidth);
if (ImGui.Selectable($"###{territory}", EntriesToRemove.Contains(territory.RowId)))
{
if (!EntriesToRemove.Contains(territory.RowId))
{
EntriesToRemove.Add(territory.RowId);
}
else
{
EntriesToRemove.Remove(territory.RowId);
}
}
ImGui.SameLine();
territory.DrawLabel();
}
}
private static void Add(Setting<List<uint>> zones, uint id)
{
if (!zones.Value.Contains(id))
{
zones.Value.Add(id);
KamiCommon.SaveConfiguration();
}
}
private static void Remove(Setting<List<uint>> zones, uint id)
{
if (zones.Value.Contains(id))
{
zones.Value.Remove(id);
KamiCommon.SaveConfiguration();
}
}
}

View File

@ -0,0 +1,12 @@
using Dalamud.Utility;
using KamiLib.Caching;
using Lumina.Excel.GeneratedSheets;
namespace KamiLib.Blacklist;
public class SearchResult
{
public uint TerritoryID { get; init; }
private uint PlaceNameRow => LuminaCache<TerritoryType>.Instance.GetRow(TerritoryID)?.PlaceName.Row ?? 0;
public string TerritoryName => LuminaCache<PlaceName>.Instance.GetRow(PlaceNameRow)?.Name.ToDalamudString().TextValue ?? "Unknown PlaceName Row";
}

View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Logging;
using Dalamud.Utility;
using ImGuiScene;
namespace KamiLib.Caching;
public class IconCache : IDisposable
{
private readonly Dictionary<uint, IDalamudTextureWrap?> iconTextures = new();
private const string IconFilePath = "ui/icon/{0:D3}000/{1:D6}_hr1.tex";
private static IconCache? _instance;
public static IconCache Instance => _instance ??= new IconCache();
public static void Cleanup()
{
_instance?.Dispose();
}
public void Dispose()
{
foreach (var texture in iconTextures.Values)
{
texture?.Dispose();
}
iconTextures.Clear();
}
private void LoadIconTexture(uint iconId)
{
Task.Run(() =>
{
try
{
var path = IconFilePath.Format(iconId / 1000, iconId);
var tex = Service.TextureProvider.GetTextureFromGame(path);
if (tex is not null && tex.ImGuiHandle != nint.Zero)
{
iconTextures[iconId] = tex;
}
else
{
tex?.Dispose();
}
}
catch (Exception ex)
{
PluginLog.LogError($"Failed loading texture for icon {iconId} - {ex.Message}");
}
});
}
public IDalamudTextureWrap? GetIcon(uint iconId)
{
if (iconTextures.TryGetValue(iconId, out var value)) return value;
iconTextures.Add(iconId, null);
LoadIconTexture(iconId);
return iconTextures[iconId];
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud;
using Lumina.Excel;
namespace KamiLib.Caching;
public class LuminaCache<T> : IEnumerable<T> where T : ExcelRow
{
private readonly Func<uint, T?> searchAction;
private static LuminaCache<T>? _instance;
public static LuminaCache<T> Instance => _instance ??= new LuminaCache<T>();
private LuminaCache(Func<uint, T?>? action = null)
{
searchAction = action ?? (row => Service.DataManager.GetExcelSheet<T>()!.GetRow(row));
}
private readonly Dictionary<uint, T> cache = new();
private readonly Dictionary<Tuple<uint, uint>, T> subRowCache = new ();
public ExcelSheet<T> OfLanguage(ClientLanguage language)
{
return Service.DataManager.GetExcelSheet<T>(language)!;
}
public T? GetRow(uint id)
{
if (cache.TryGetValue(id, out var value))
{
return value;
}
else
{
if (searchAction(id) is not { } result) return null;
return cache[id] = result;
}
}
public T? GetRow(uint row, uint subRow)
{
var targetRow = new Tuple<uint, uint>(row, subRow);
if (subRowCache.TryGetValue(targetRow, out var value))
{
return value;
}
else
{
if (Service.DataManager.GetExcelSheet<T>()!.GetRow(row, subRow) is not { } result) return null;
return subRowCache[targetRow] = result;
}
}
public IEnumerator<T> GetEnumerator() => Service.DataManager.GetExcelSheet<T>()!.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@ -0,0 +1,55 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using KamiLib.Localization;
namespace KamiLib.ChatCommands;
public static class Chat
{
public static void Print(string tag, string message) => Service.Chat.Print(GetBaseString(tag, message).BuiltString);
public static void PrintHelp(string command, string? helpText = null)
{
var message = GetBaseString(Strings.Command_Label, command);
if (helpText is not null)
{
message.AddUiForeground("- " + helpText, 32);
}
Service.Chat.Print(message.BuiltString);
}
public static void Print(string tag, string message, DalamudLinkPayload? payload)
{
if (payload is null)
{
Print(tag, message);
return;
}
Service.Chat.Print(GetBaseString(tag, message, payload).BuiltString);
}
public static void PrintError(string message) => Service.Chat.PrintError(GetBaseString(Strings.Common_Error, message).BuiltString);
private static SeStringBuilder GetBaseString(string tag, string message, DalamudLinkPayload? payload = null)
{
if (payload is null)
{
return new SeStringBuilder()
.AddUiForeground($"[{KamiCommon.PluginName}] ", 45)
.AddUiForeground($"[{tag}] ", 62)
.AddText(message);
}
else
{
return new SeStringBuilder()
.AddUiForeground($"[{KamiCommon.PluginName}] ", 45)
.AddUiForeground($"[{tag}] ", 62)
.Add(payload)
.AddUiForeground(message, 35)
.Add(RawPayload.LinkTerminator);
}
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
namespace KamiLib.ChatCommands;
internal record ChatLinkPayload(uint CommandID, uint Type, DalamudLinkPayload Payload);
public class ChatPayloadManager : IDisposable
{
private static ChatPayloadManager? _instance;
public static ChatPayloadManager Instance => _instance ??= new ChatPayloadManager();
private List<ChatLinkPayload> ChatLinkPayloads { get; } = new();
public static void Cleanup()
{
_instance?.Dispose();
}
public void Dispose()
{
foreach (var payload in ChatLinkPayloads)
{
Service.PluginInterface.RemoveChatLinkHandler( payload.Type + 1000 );
}
}
public DalamudLinkPayload AddChatLink(Enum type, Action<uint, SeString> payloadAction) => AddChatLink(Convert.ToUInt32(type), payloadAction);
private DalamudLinkPayload AddChatLink(uint type, Action<uint, SeString> payloadAction)
{
// If the payload is already registered
var payload = ChatLinkPayloads.FirstOrDefault(linkPayload => linkPayload.CommandID == type + 1000)?.Payload;
if (payload != null) return payload;
// else
Service.PluginInterface.RemoveChatLinkHandler(type + 1000);
payload = Service.PluginInterface.AddChatLinkHandler(type + 1000, payloadAction);
ChatLinkPayloads.Add(new ChatLinkPayload(type + 1000, type, payload));
return payload;
}
}

View File

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using KamiLib.Interfaces;
using KamiLib.Localization;
namespace KamiLib.ChatCommands;
public static class Command
{
public static CommandData GetCommandData(string baseCommand, string arguments) => new(baseCommand, arguments);
public static void ProcessCommand(CommandData data, IEnumerable<IPluginCommand> commands)
{
var matchingCommands = commands.Where(command => command.CommandArgument == data.Command).ToList();
if (matchingCommands.Any())
{
if (!matchingCommands.Any(command => command.Execute(data)))
{
Chat.PrintError(string.Format(Strings.Command_DoesntExistExtended, data.BaseCommand, data.Command, data.SubCommand));
}
}
else
{
Chat.PrintError(string.Format(Strings.Command_DoesntExist, data.BaseCommand, data.Command));
}
}
}

View File

@ -0,0 +1,39 @@
namespace KamiLib.ChatCommands;
public class CommandData
{
public string? BaseCommand;
public string? Command;
public string? SubCommand;
public string?[]? Arguments;
public CommandData(string rootCommand, string arguments)
{
BaseCommand = rootCommand;
if (arguments != string.Empty)
{
var splits = arguments.Split(' ');
if (splits.Length >= 1)
{
Command = splits[0];
}
if (splits.Length >= 2)
{
SubCommand = splits[1];
}
if (splits.Length >= 3)
{
Arguments = splits[2..];
}
}
}
public override string ToString() => $"{BaseCommand ?? "Empty Base Command"}, " +
$"{Command ?? "Empty Command"}, " +
$"{SubCommand ?? "Empty SubCommand"}, " +
$"{(Arguments is null ? "Empty Args" : string.Join(", ", Arguments))}";
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using Dalamud.Game.Command;
using Dalamud.Logging;
using KamiLib.Interfaces;
using KamiLib.Localization;
namespace KamiLib.ChatCommands;
public class CommandManager : IDisposable
{
private static string SettingsCommand => $"/{KamiCommon.PluginName.ToLower()}";
private static string HelpCommand => $"/{KamiCommon.PluginName.ToLower()} help";
public readonly List<IPluginCommand> Commands = new();
private readonly List<string> additionalCommands = new();
public CommandManager()
{
Commands.Add(new HelpCommands());
Service.Commands.AddHandler(SettingsCommand, new CommandInfo(OnCommand)
{
HelpMessage = Strings.Command_OpenConfigWindow
});
Service.Commands.AddHandler(HelpCommand, new CommandInfo(OnCommand)
{
HelpMessage = Strings.Command_DisplayHelpText
});
}
public void Dispose()
{
Service.Commands.RemoveHandler(SettingsCommand);
Service.Commands.RemoveHandler(HelpCommand);
foreach (var additionalCommand in additionalCommands)
{
Service.Commands.RemoveHandler(additionalCommand);
}
}
public void AddHandler(string additionalCommand, string description)
{
additionalCommands.Add(additionalCommand);
Service.Commands.AddHandler(additionalCommand, new CommandInfo(OnCommand)
{
HelpMessage = description
});
}
public void AddCommand(IPluginCommand command)
{
Commands.Add(command);
}
public void OnCommand(string command, string arguments)
{
var commandData = Command.GetCommandData(command.ToLower(), arguments.ToLower());
PluginLog.Debug($"[{KamiCommon.PluginName}] Received Command: {commandData}");
Command.ProcessCommand(commandData, Commands);
}
}

View File

@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.Linq;
using KamiLib.Interfaces;
using KamiLib.Localization;
namespace KamiLib.ChatCommands;
public class HelpCommands: IPluginCommand
{
public string CommandArgument => "help";
public IEnumerable<ISubCommand> SubCommands { get; } = new List<ISubCommand>
{
new SubCommand
{
CommandKeyword = null,
CommandAction = () =>
{
foreach (var command in KamiCommon.CommandManager.Commands)
{
PrintSubCommands(command);
}
},
GetHelpText = () => Strings.Command_ShowThisMessage
}
};
private static void PrintSubCommands(IPluginCommand command)
{
foreach (var subCommand in command.SubCommands.GroupBy(subCommand => subCommand.GetCommand()))
{
var selectedSubCommand = subCommand.First();
if (!selectedSubCommand.Hidden)
{
PrintHelpText(command, selectedSubCommand);
if (selectedSubCommand.GetAliases() is { } aliases)
{
foreach (var alias in aliases)
{
PrintAliasHelpText(command, alias, selectedSubCommand);
}
}
}
}
}
private static void PrintHelpText(IPluginCommand mainCommand, ISubCommand subCommand)
{
var commandString = $"/{KamiCommon.PluginName.ToLower()} ";
if (mainCommand.CommandArgument is not null)
{
commandString += mainCommand.CommandArgument + " ";
}
if (subCommand.GetCommand() is not null)
{
commandString += subCommand.GetCommand() + " ";
}
if (subCommand.HasParameterAction)
{
commandString += $"[{Strings.Common_Value}] ";
}
Chat.PrintHelp(commandString, subCommand.GetHelpText());
}
private static void PrintAliasHelpText(IPluginCommand mainCommand, string? alias, ISubCommand subCommand)
{
var commandString = $"/{KamiCommon.PluginName.ToLower()} ";
if (mainCommand.CommandArgument is not null)
{
commandString += mainCommand.CommandArgument + " ";
}
if (alias is not null)
{
commandString += alias + " ";
}
if (subCommand.HasParameterAction)
{
commandString += $"[{Strings.Common_Value}] ";
}
Chat.PrintHelp(commandString, subCommand.GetHelpText());
}
}

View File

@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Globalization;
using Dalamud.Interface.Windowing;
using KamiLib.Interfaces;
using KamiLib.Localization;
namespace KamiLib.ChatCommands;
public class OpenWindowCommand<T> : IPluginCommand where T : Window
{
public string? CommandArgument { get; }
public OpenWindowCommand( string? commandArgument = null, bool silent = false, string? windowName = null)
{
CommandArgument = commandArgument?.ToLower();
if (commandArgument is not null)
{
windowName ??= CultureInfo.CurrentCulture.TextInfo.ToTitleCase(commandArgument);
}
SubCommands = new List<ISubCommand>
{
new SubCommand
{
CommandKeyword = null,
CommandAction = () => Chat.PrintError(string.Format(Strings.Command_PvPError, windowName)),
CanExecute = () => Service.ClientState.IsPvP,
GetHelpText = () => string.Format(Strings.Command_OpenWindow, windowName),
Hidden = silent,
},
new SubCommand
{
CommandKeyword = null,
CommandAction = () =>
{
if ( KamiCommon.WindowManager.GetWindowOfType<T>() is {} mainWindow )
{
if (!silent)
{
Chat.Print(Strings.Command_Label,
!mainWindow.IsOpen ?
string.Format(Strings.Command_OpeningWindow, windowName) :
string.Format(Strings.Command_ClosingWindow, windowName));
}
mainWindow.IsOpen = !mainWindow.IsOpen;
}
else
{
Chat.PrintError($"Something went wrong trying to open {windowName} Window");
}
},
CanExecute = () => !Service.ClientState.IsPvP,
GetHelpText = () => string.Format(Strings.Command_OpenWindow, windowName),
Hidden = silent,
},
};
}
public IEnumerable<ISubCommand> SubCommands { get; }
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using KamiLib.Interfaces;
namespace KamiLib.ChatCommands;
public class SubCommand : ISubCommand
{
public string? CommandKeyword { get; init; }
public List<string>? Aliases { get; init; }
public Action? CommandAction { get; init; }
public Action<string?[]?>? ParameterAction { get; init; }
public Func<bool>? CanExecute { get; init; }
public Func<string>? GetHelpText { get; init; }
public bool Hidden { get; init; }
public bool HasParameterAction => ParameterAction is not null;
public string? GetCommand() => CommandKeyword;
public IEnumerable<string>? GetAliases() => Aliases;
string? ISubCommand.GetHelpText() => GetHelpText?.Invoke();
public bool Execute(CommandData commandData)
{
if (CanExecute?.Invoke() is null or true)
{
if (CommandAction is not null)
{
CommandAction.Invoke();
return true;
}
if (ParameterAction is not null)
{
ParameterAction.Invoke(commandData.Arguments);
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using Newtonsoft.Json.Linq;
namespace KamiLib.Configuration;
public static class Migrate
{
private static JObject? _parsedJson;
public static void LoadFile(FileInfo configFilePath)
{
var reader = new StreamReader(configFilePath.FullName);
var fileText = reader.ReadToEnd();
reader.Dispose();
_parsedJson = JObject.Parse(fileText);
}
public static int GetFileVersion()
{
return _parsedJson?.GetValue("Version")?.Value<int>() ?? 0;
}
public static Setting<T> GetSettingValue<T>(string key) where T : struct
{
return new Setting<T>(_parsedJson!.SelectToken(key)!.Value<T>());
}
public static Setting<T> GetSettingEnum<T>(string key) where T : struct
{
var readValue = _parsedJson!.SelectToken(key)!.Value<int>();
return new Setting<T>((T) Enum.ToObject(typeof(T), readValue));
}
public static T GetValue<T>(string key)
{
return _parsedJson!.SelectToken(key)!.Value<T>()!;
}
public static JArray GetArray(string key)
{
return (JArray) _parsedJson!.SelectToken(key)!;
}
public static List<T> GetArray<T>(string key)
{
var array = GetArray(key);
return array.ToObject<List<T>>()!;
}
public static Setting<Vector4> GetVector4(string key)
{
return new Setting<Vector4>(new Vector4
{
X = GetValue<float>($"{key}.X"),
Y = GetValue<float>($"{key}.Y"),
Z = GetValue<float>($"{key}.Z"),
W = GetValue<float>($"{key}.W"),
});
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
namespace KamiLib.Configuration;
public sealed record Setting<T>(T Value) : IEquatable<T> where T : notnull
{
public T Value = Value;
public override string ToString() => Value.ToString() ?? "Null";
public bool Equals(T? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return EqualityComparer<T>.Default.Equals(Value, other);
}
public static bool operator ==(Setting<T> leftSide, T rightSide)
{
return leftSide.Equals(rightSide);
}
public static bool operator !=(Setting<T> leftSide, T rightSide)
{
return !leftSide.Equals(rightSide);
}
public static implicit operator bool(Setting<T> obj)
{
if (obj.Value is bool value)
{
return value;
}
throw new Exception("Invalid implicit conversion to bool");
}
public override int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return EqualityComparer<T>.Default.GetHashCode(Value);
}
}

31
KamiLib/Drawing/Colors.cs Normal file
View File

@ -0,0 +1,31 @@
using System.Numerics;
using ImGuiNET;
namespace KamiLib.Drawing;
public static class Colors
{
public static Vector4 Purple = new(176 / 255.0f, 38 / 255.0f, 236 / 255.0f, 1.0f);
public static Vector4 Blue = new(37 / 255.0f, 168 / 255.0f, 1.0f, 1.0f);
public static Vector4 ForestGreen = new(0.133f, 0.545f, 0.1333f, 1.0f);
public static Vector4 White = new(1.0f, 1.0f, 1.0f,1.0f);
public static Vector4 Red = new(1.0f, 0.0f, 0.0f, 1.0f);
public static Vector4 Green = new(0.0f, 1.0f, 0.0f, 1.0f);
public static Vector4 Black = new(0.0f, 0.0f, 0.0f, 1.0f);
public static Vector4 HealerGreen = new(33 / 255f, 193 / 255f, 0, 1.0f);
public static Vector4 DPSRed = new(210/255f, 42/255f, 43/255f, 1.0f);
public static Vector4 SoftRed = new(0.8f, 0.2f, 0.2f, 1.0f);
public static Vector4 Grey = new(0.6f, 0.6f, 0.6f, 1.0f);
public static Vector4 LightGrey = new(220/250.0f, 220/250.0f, 220/250f, 1.0f);
public static Vector4 Orange = new(1.0f, 165.0f / 255.0f, 0.0f, 1.0f);
public static Vector4 SoftGreen = new(0.2f, 0.8f, 0.2f, 1.0f);
public static Vector4 FatePink = new(0.58f, 0.388f, 0.827f, 0.33f);
public static Vector4 FateDarkPink = new(0.58f, 0.388f, 0.827f, 1.0f);
public static Vector4 MapTextBrown = new(0.655f, 0.396f, 0.149f, 1.0f);
public static Vector4 BabyBlue = new(0.537f, 0.812f, 0.941f, 1.0f);
}
public static class ColorExtensions
{
public static uint ToU32(this Vector4 color) => ImGui.GetColorU32(color);
}

491
KamiLib/Drawing/DrawList.cs Normal file
View File

@ -0,0 +1,491 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Interface.Components;
using Dalamud.Interface.Utility;
using ImGuiNET;
using KamiLib.Caching;
using KamiLib.Configuration;
using Action = System.Action;
namespace KamiLib.Drawing;
public abstract class DrawList<T>
{
protected T DrawListOwner { get; init; } = default!;
protected List<Action> DrawActions { get; } = new();
protected void DrawListContents()
{
foreach (var action in DrawActions)
{
action();
}
}
public T AddDummy(float value)
{
DrawActions.Add(() => ImGuiHelpers.ScaledDummy(value));
return DrawListOwner;
}
public T AddIndent(int tab)
{
DrawActions.Add(() => ImGui.Indent(15.0f * tab));
return DrawListOwner;
}
public T AddIcon(uint iconID, Vector2 size, Vector4 color)
{
var icon = IconCache.Instance.GetIcon(iconID);
if (icon != null)
{
DrawActions.Add(() =>
{
ImGui.Image(icon.ImGuiHandle, size, Vector2.Zero, Vector2.One, color);
});
}
return DrawListOwner;
}
public T AddIcon(uint iconID, Vector2 size, float transparency)
{
var icon = IconCache.Instance.GetIcon(iconID);
if (icon != null)
{
DrawActions.Add(() =>
{
ImGui.Image(icon.ImGuiHandle, size, Vector2.Zero, Vector2.One, Vector4.One with {W = transparency});
});
}
return DrawListOwner;
}
public T AddString(string message, Vector4? color = null)
{
if (color == null)
{
DrawActions.Add(() => ImGui.Text(message));