Update to API 9 without the Kami touch

This commit is contained in:
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));
}
else
{
DrawActions.Add(() => ImGui.TextColored(color.Value, message));
}
return DrawListOwner;
}
public T AddStringCentered(string message, float? availableArea = null, Vector4? color = null)
{
if (color == null)
{
DrawActions.Add(() =>
{
var area = availableArea / 2.0f ?? ImGui.GetContentRegionAvail().X / 2.0f;
ImGui.SetCursorPos(ImGui.GetCursorPos() with {X = ImGui.GetCursorPos().X + area - ImGui.CalcTextSize(message).X / 2.0f});
ImGui.Text(message);
});
}
else
{
DrawActions.Add(() =>
{
var area = availableArea / 2.0f ?? ImGui.GetContentRegionAvail().X / 2.0f;
ImGui.SetCursorPos(ImGui.GetCursorPos() with {X = ImGui.GetCursorPos().X + area - ImGui.CalcTextSize(message).X / 2.0f});
ImGui.TextColored(color.Value, message);
});
}
return DrawListOwner;
}
public T Indent(int indent)
{
DrawActions.Add( () => ImGui.Indent(indent) );
return DrawListOwner;
}
public T UnIndent(int indent)
{
DrawActions.Add( () => ImGui.Unindent(indent) );
return DrawListOwner;
}
public T AddConfigCheckbox(string label, Setting<bool> setting, string? helpText = null, string? additionalID = null)
{
DrawActions.Add(() =>
{
if (additionalID != null) ImGui.PushID(additionalID);
var cursorPosition = ImGui.GetCursorPos();
if (ImGui.Checkbox($"##{label}", ref setting.Value))
{
KamiCommon.SaveConfiguration();
}
var spacing = ImGui.GetStyle().ItemSpacing;
cursorPosition += spacing;
ImGui.SetCursorPos(cursorPosition with { X = cursorPosition.X + 27.0f * ImGuiHelpers.GlobalScale });
ImGui.TextUnformatted(label);
if (helpText != null)
{
ImGuiComponents.HelpMarker(helpText);
}
if (additionalID != null) ImGui.PopID();
});
return DrawListOwner;
}
public T AddConfigCombo<TU>(IEnumerable<TU> values, Setting<TU> setting, Func<TU, string> localizeFunction, string label = "", float width = 0.0f) where TU : struct
{
DrawActions.Add(() =>
{
if (width != 0.0f)
{
ImGui.SetNextItemWidth(width);
}
else
{
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
}
if (ImGui.BeginCombo(label, localizeFunction(setting.Value)))
{
foreach (var value in values)
{
if (ImGui.Selectable(localizeFunction(value), setting.Value.Equals(value)))
{
setting.Value = value;
KamiCommon.SaveConfiguration();
}
}
ImGui.EndCombo();
}
});
return DrawListOwner;
}
public T AddConfigColor(string label, Setting<Vector4> setting)
{
DrawActions.Add(() =>
{
if (ImGui.ColorEdit4(label, ref setting.Value, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf))
{
KamiCommon.SaveConfiguration();
}
});
return DrawListOwner;
}
public T AddConfigColor(string label, string defaultLabel, Setting<Vector4> setting, Vector4 defaultValue)
{
DrawActions.Add(() =>
{
ImGui.PushID(label);
if (ImGui.ColorEdit4($"##{label}", ref setting.Value, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf))
{
KamiCommon.SaveConfiguration();
}
ImGui.SameLine();
ImGui.BeginDisabled(setting == defaultValue);
if (ImGui.Button(defaultLabel))
{
setting.Value = defaultValue;
KamiCommon.SaveConfiguration();
}
ImGui.EndDisabled();
ImGui.SameLine();
ImGui.TextUnformatted(label);
ImGui.PopID();
});
return DrawListOwner;
}
public T AddDragFloat(string label, Setting<float> setting, float minValue, float maxValue, float width = 0.0f, int precision = 2)
{
DrawActions.Add(() =>
{
if (width != 0.0f)
{
ImGui.SetNextItemWidth(width);
}
else
{
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
}
ImGui.DragFloat(label, ref setting.Value, 0.01f * maxValue, minValue, maxValue, $"%.{precision}f");
if (ImGui.IsItemDeactivatedAfterEdit())
{
KamiCommon.SaveConfiguration();
}
});
return DrawListOwner;
}
public T AddAction(Action action)
{
DrawActions.Add(action);
return DrawListOwner;
}
public T AddSliderInt(string label, Setting<int> setting, int minValue, int maxValue, float width = 200.0f)
{
DrawActions.Add(() =>
{
if (width != 0.0f)
{
ImGui.SetNextItemWidth(width);
}
else
{
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
}
ImGui.SliderInt(label, ref setting.Value, minValue, maxValue);
if (ImGui.IsItemDeactivatedAfterEdit())
{
KamiCommon.SaveConfiguration();
}
});
return DrawListOwner;
}
public T AddConfigRadio<TU>(string label, Setting<TU> setting, TU buttonValue, string? helpText = null ) where TU : struct
{
DrawActions.Add(() =>
{
var value = Convert.ToInt32(setting.Value);
if (ImGui.RadioButton(label, ref value, Convert.ToInt32(buttonValue)))
{
setting.Value = (TU)Enum.ToObject(typeof(TU), value);
KamiCommon.SaveConfiguration();
}
if (helpText != null)
{
ImGuiComponents.HelpMarker(helpText);
}
});
return DrawListOwner;
}
public T AddConfigString(Setting<string> settingsCustomName, float width = 0.0f)
{
DrawActions.Add(() =>
{
if (width != 0.0f)
{
ImGui.SetNextItemWidth(width);
}
ImGui.InputText("", ref settingsCustomName.Value, 24);
if (ImGui.IsItemDeactivatedAfterEdit())
{
KamiCommon.SaveConfiguration();
}
});
return DrawListOwner;
}
public T AddConfigVector2(Setting<Vector2> setting, float width = 200.0f)
{
DrawActions.Add(() =>
{
if (width != 0.0f)
{
ImGui.SetNextItemWidth(width);
}
else
{
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
}
ImGui.InputFloat2("", ref setting.Value);
if (ImGui.IsItemDeactivatedAfterEdit())
{
KamiCommon.SaveConfiguration();
}
});
return DrawListOwner;
}
public T AddInputInt(string label, Setting<int> settingsPriority, int min, int max, int step = 1, int stepFast = 1, float width = 77.0f)
{
DrawActions.Add(() =>
{
ImGui.SetNextItemWidth(width);
ImGui.InputInt(label, ref settingsPriority.Value, step, stepFast);
if (ImGui.IsItemDeactivatedAfterEdit())
{
settingsPriority.Value = Math.Clamp(settingsPriority.Value, min, max);
KamiCommon.SaveConfiguration();
}
});
return DrawListOwner;
}
public T AddHelpMarker(string helpText)
{
DrawActions.Add(() =>
{
ImGuiComponents.HelpMarker(helpText);
});
return DrawListOwner;
}
public T BeginDisabled(bool shouldDisable)
{
DrawActions.Add(() =>
{
ImGui.BeginDisabled(shouldDisable);
});
return DrawListOwner;
}
public T EndDisabled()
{
DrawActions.Add(ImGui.EndDisabled);
return DrawListOwner;
}
public T AddSeparator()
{
DrawActions.Add(() =>
{
var startPosition = ImGui.GetCursorScreenPos();
var stopPosition = startPosition with { X = startPosition.X + InfoBox.Instance.InnerWidth };
var color = ImGui.GetColorU32(Colors.White);
ImGui.GetWindowDrawList().AddLine(startPosition, stopPosition, color);
});
return DrawListOwner;
}
public T SameLine(float width = 0)
{
if (width == 0)
{
DrawActions.Add(ImGui.SameLine);
}
else
{
DrawActions.Add(() => ImGui.SameLine(width));
}
return DrawListOwner;
}
public T AddDisabledButton(string label, Action action, bool disable, string? hoverTooltip = null, float? buttonSize = null)
{
if (buttonSize is not null)
{
AddDisabledButtonWithSize(label, action, disable, buttonSize.Value, hoverTooltip);
}
else
{
AddDisabledButtonWithoutSize(label, action, disable, hoverTooltip);
}
return DrawListOwner;
}
private void AddDisabledButtonWithSize(string label, Action action, bool disable, float buttonSize, string? hoverTooltip = null)
{
DrawActions.Add(() =>
{
if(disable) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
if (ImGui.Button(label, new Vector2(buttonSize, 23.0f * ImGuiHelpers.GlobalScale)) && !disable)
{
action.Invoke();
}
if(disable) ImGui.PopStyleVar();
if (hoverTooltip is not null && ImGui.IsItemHovered() && disable)
{
ImGui.SetTooltip(hoverTooltip);
}
});
}
private void AddDisabledButtonWithoutSize(string label, Action action, bool disable, string? hoverTooltip = null)
{
DrawActions.Add(() =>
{
if(disable) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
if (ImGui.Button(label) && !disable)
{
action.Invoke();
}
if(disable) ImGui.PopStyleVar();
if (hoverTooltip is not null && ImGui.IsItemHovered() && disable)
{
ImGui.SetTooltip(hoverTooltip);
}
});
}
public T AddButton(string label, Action action, Vector2? buttonSize = null)
{
if (buttonSize is not null)
{
DrawActions.Add(() =>
{
if (ImGui.Button(label, buttonSize.Value))
{
action.Invoke();
}
});
}
else
{
DrawActions.Add(() =>
{
if (ImGui.Button(label))
{
action.Invoke();
}
});
}
return DrawListOwner;
}
}

144
KamiLib/Drawing/InfoBox.cs Normal file
View File

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using ImGuiNET;
using KamiLib.Interfaces;
namespace KamiLib.Drawing;
public class InfoBox : DrawList<InfoBox>, IDrawable
{
private static float CurveRadius => 13.0f * ImGuiHelpers.GlobalScale;
private static float BorderThickness => 2.0f;
private static ImDrawListPtr DrawList => ImGui.GetWindowDrawList();
private static Vector2 RegionAvailable => ImGui.GetContentRegionAvail();
private static Vector2 StartPosition { get; set; }
private static Vector2 Size { get; set; }
private static Vector4 BorderColor { get; } = Colors.White;
private static Vector4 TitleColor { get; } = Colors.White;
private static float TotalWidth { get; set; }
private string Label { get; set; } = "Label Not Set";
private float WidthPercentage { get; set; }
public float InnerWidth { get; private set; }
private InfoBox()
{
DrawListOwner = this;
}
public static readonly InfoBox Instance = new();
public void Draw()
{
ImGuiHelpers.ScaledDummy(5.0f);
TotalWidth = RegionAvailable.X * WidthPercentage;
InnerWidth = TotalWidth - CurveRadius * 3.0f;
var startX = ImGui.GetCursorPos().X + RegionAvailable.X * ( 0.5f - WidthPercentage / 2.0f ) + CurveRadius / 2.0f;
ImGui.SetCursorPos(ImGui.GetCursorPos() with { X = startX });
StartPosition = ImGui.GetCursorScreenPos();
Size = new Vector2(InnerWidth + CurveRadius * 2.0f, 0);
DrawContents();
var calculatedHeight = ImGui.GetItemRectMax().Y - ImGui.GetItemRectMin().Y + CurveRadius * 2.0f;
Size = new Vector2(InnerWidth + CurveRadius * 2.0f, calculatedHeight);
DrawCorners();
DrawBorders();
ImGuiHelpers.ScaledDummy(10.0f);
}
private void DrawContents()
{
var topLeftCurveCenter = new Vector2(StartPosition.X + CurveRadius, StartPosition.Y + CurveRadius);
ImGui.SetCursorScreenPos(topLeftCurveCenter);
ImGui.PushTextWrapPos(ImGui.GetCursorPos().X + Size.X - CurveRadius * 2.0f);
ImGui.BeginGroup();
ImGui.PushID(Label);
DrawListContents();
ImGui.PopID();
ImGui.EndGroup();
ImGui.PopTextWrapPos();
DrawActions.Clear();
}
private void DrawCorners()
{
var topLeftCurveCenter = new Vector2(StartPosition.X + CurveRadius, StartPosition.Y + CurveRadius);
var topRightCurveCenter = new Vector2(StartPosition.X + Size.X - CurveRadius, StartPosition.Y + CurveRadius);
var bottomLeftCurveCenter = new Vector2(StartPosition.X + CurveRadius, StartPosition.Y + Size.Y - CurveRadius);
var bottomRightCurveCenter = new Vector2(StartPosition.X + Size.X - CurveRadius, StartPosition.Y + Size.Y - CurveRadius);
DrawList.PathArcTo(topLeftCurveCenter, CurveRadius, MathF.PI, 1.55f * MathF.PI);
DrawList.PathStroke(BorderColor.ToU32(), ImDrawFlags.None, BorderThickness);
DrawList.PathArcTo(topRightCurveCenter, CurveRadius, 2.0f * MathF.PI, 1.45f * MathF.PI);
DrawList.PathStroke(BorderColor.ToU32(), ImDrawFlags.None, BorderThickness);
DrawList.PathArcTo(bottomLeftCurveCenter, CurveRadius, 0.5f * MathF.PI, 1.0f * MathF.PI);
DrawList.PathStroke(BorderColor.ToU32(), ImDrawFlags.None, BorderThickness);
DrawList.PathArcTo(bottomRightCurveCenter, CurveRadius, 0.0f, 0.5f * MathF.PI);
DrawList.PathStroke(BorderColor.ToU32(), ImDrawFlags.None, BorderThickness);
}
private void DrawBorders()
{
var color = BorderColor.ToU32();
DrawList.AddLine(new Vector2(StartPosition.X - 0.5f, StartPosition.Y + CurveRadius - 0.5f), new Vector2(StartPosition.X - 0.5f, StartPosition.Y + Size.Y - CurveRadius + 0.5f), color, BorderThickness);
DrawList.AddLine(new Vector2(StartPosition.X + Size.X - 0.5f, StartPosition.Y + CurveRadius - 0.5f), new Vector2(StartPosition.X + Size.X - 0.5f, StartPosition.Y + Size.Y - CurveRadius + 0.5f), color, BorderThickness);
DrawList.AddLine(new Vector2(StartPosition.X + CurveRadius - 0.5f, StartPosition.Y + Size.Y - 0.5f), new Vector2(StartPosition.X + Size.X - CurveRadius + 0.5f, StartPosition.Y + Size.Y - 0.5f), color, BorderThickness);
var textSize = ImGui.CalcTextSize(Label);
var textStartPadding = 7.0f * ImGuiHelpers.GlobalScale;
var textEndPadding = 7.0f * ImGuiHelpers.GlobalScale;
var textVerticalOffset = textSize.Y / 2.0f;
DrawList.AddText(new Vector2(StartPosition.X + CurveRadius + textStartPadding, StartPosition.Y - textVerticalOffset), TitleColor.ToU32(), Label);
DrawList.AddLine(new Vector2(StartPosition.X + CurveRadius + textStartPadding + textSize.X + textEndPadding, StartPosition.Y - 0.5f), new Vector2(StartPosition.X + Size.X - CurveRadius - 0.5f, StartPosition.Y - 0.5f), color, BorderThickness);
}
public InfoBox AddTitle(string title, float percentFill = 0.95f)
{
Label = title;
WidthPercentage = percentFill;
return DrawListOwner;
}
public InfoBox AddTitle(string title, out float innerWidth, float percentFill = .95f)
{
Label = title;
WidthPercentage = percentFill;
TotalWidth = RegionAvailable.X * WidthPercentage;
InnerWidth = TotalWidth - CurveRadius * 3.0f;
innerWidth = InnerWidth;
return DrawListOwner;
}
public InfoBoxTable BeginTable(float weight = 0.50f) => new InfoBoxTable(this, weight);
public InfoBoxList BeginList() => new InfoBoxList(this);
public InfoBox AddList(IEnumerable<IInfoBoxListConfigurationRow> rows)
{
return BeginList()
.AddRows(rows)
.EndList();
}
}

View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using KamiLib.Interfaces;
namespace KamiLib.Drawing;
public class InfoBoxList : DrawList<InfoBoxList>
{
private readonly InfoBox owner;
public InfoBoxList(InfoBox owner)
{
this.owner = owner;
DrawListOwner = this;
}
public InfoBox EndList()
{
foreach (var row in DrawActions)
{
owner.AddAction(row);
}
return owner;
}
public InfoBoxList AddRows(IEnumerable<IInfoBoxListConfigurationRow> rows)
{
foreach (var row in rows)
{
row.GetConfigurationRow(this);
}
return this;
}
}

View File

@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Numerics;
using ImGuiNET;
using KamiLib.Interfaces;
namespace KamiLib.Drawing;
public class InfoBoxTable
{
private readonly InfoBox owner;
private readonly float weight;
private readonly List<InfoBoxTableRow> rows = new();
private string emptyListString = string.Empty;
public InfoBoxTable(InfoBox owner, float weight = 0.5f)
{
this.owner = owner;
this.weight = weight;
}
public InfoBoxTableRow BeginRow()
{
return new InfoBoxTableRow(this);
}
public InfoBoxTable AddRow(InfoBoxTableRow row)
{
rows.Add(row);
return this;
}
public InfoBox EndTable()
{
owner.AddAction(() =>
{
if (rows.Count == 0)
{
if (emptyListString != string.Empty)
{
ImGui.TextColored(Colors.Orange, emptyListString);
}
}
else
{
if (ImGui.BeginTable($"", 2, ImGuiTableFlags.None, new Vector2(owner.InnerWidth, 0)))
{
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.None, 1f * (weight));
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.None, 1f * (1 - weight));
foreach (var row in rows)
{
ImGui.TableNextColumn();
ImGui.PushTextWrapPos(GetWrapPosition());
row.FirstColumn?.Invoke();
ImGui.PopTextWrapPos();
ImGui.TableNextColumn();
ImGui.PushTextWrapPos(GetWrapPosition());
row.SecondColumn?.Invoke();
ImGui.PopTextWrapPos();
}
ImGui.EndTable();
}
}
});
return owner;
}
private static float GetWrapPosition()
{
var region = ImGui.GetContentRegionAvail();
var cursor = ImGui.GetCursorPos();
var wrapPosition = cursor.X + region.X;
return wrapPosition;
}
public InfoBoxTable AddConfigurationRows(IEnumerable<IInfoBoxTableConfigurationRow> configurableRows, string? emptyEnumerableString = null)
{
if(emptyEnumerableString is not null)
{
emptyListString = emptyEnumerableString;
}
foreach (var row in configurableRows)
{
row.GetConfigurationRow(this);
}
return this;
}
public InfoBoxTable AddDataRows(IEnumerable<IInfoBoxTableDataRow> dataRows, string? emptyEnumerableString = null)
{
if(emptyEnumerableString is not null)
{
emptyListString = emptyEnumerableString;
}
foreach (var row in dataRows)
{
row.GetDataRow(this);
}
return this;
}
}

View File

@ -0,0 +1,24 @@
using System;
namespace KamiLib.Drawing;
public class InfoBoxTableRow : DrawList<InfoBoxTableRow>
{
private readonly InfoBoxTable owner;
public Action? FirstColumn => DrawActions.Count > 0 ? DrawActions[0] : null;
public Action? SecondColumn => DrawActions.Count > 1 ? DrawActions[1] : null;
public InfoBoxTableRow(InfoBoxTable owner)
{
this.owner = owner;
DrawListOwner = this;
}
public InfoBoxTable EndRow()
{
owner.AddRow(this);
return owner;
}
}

50
KamiLib/Drawing/TabBar.cs Normal file
View File

@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Numerics;
using ImGuiNET;
using KamiLib.Interfaces;
namespace KamiLib.Drawing;
public class TabBar : IDrawable
{
private readonly List<ITabItem> tabs = new();
private readonly string tabBarID;
private readonly Vector2 childSize;
public TabBar(string id, Vector2? size = null)
{
tabBarID = id;
childSize = size ?? Vector2.Zero;
}
public void AddTab(ITabItem tab) => tabs.Add(tab);
public void AddTab(IEnumerable<ITabItem> multipleTabs) => tabs.AddRange(multipleTabs);
public void Draw()
{
ImGui.PushID(tabBarID);
if (ImGui.BeginTabBar($"###{KamiCommon.PluginName}TabBar", ImGuiTabBarFlags.NoTooltip))
{
foreach (var tab in tabs)
{
if(tab.Enabled == false) continue;
if (ImGui.BeginTabItem(tab.TabName))
{
if (ImGui.BeginChild($"###{KamiCommon.PluginName}TabBarChild", childSize, false, ImGuiWindowFlags.NoScrollbar))
{
ImGui.PushID(tab.TabName);
tab.Draw();
ImGui.PopID();
}
ImGui.EndChild();
ImGui.EndTabItem();
}
}
}
ImGui.PopID();
}
}

View File

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Party;
namespace KamiLib.Extensions;
public static class PartyListExtensions
{
public static IEnumerable<PartyMember> Alive(this IEnumerable<PartyMember> list)
{
return list.Where(member => member.GameObject != null && !member.GameObject.IsDead);
}
public static IEnumerable<PartyMember> WithRole(this IEnumerable<PartyMember> list, uint roleID)
{
return list.Where(member => member.ClassJob.GameData?.Role == roleID);
}
public static IEnumerable<PartyMember> WithJob(this IEnumerable<PartyMember> list, uint jobID)
{
return list.Where(member => member.ClassJob.Id == jobID);
}
public static IEnumerable<PartyMember> WithJob(this IEnumerable<PartyMember> list, List<uint> jobList)
{
return list.Where(member => jobList.Contains(member.ClassJob.Id));
}
public static IEnumerable<PartyMember> WithStatus(this IEnumerable<PartyMember> list, uint statusID)
{
return list.Where(member => member.HasStatus(statusID));
}
public static IEnumerable<PartyMember> WithStatus(this IEnumerable<PartyMember> list, List<uint> statusList)
{
return list.Where(member => member.HasStatus(statusList));
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Party;
namespace KamiLib.Extensions;
public static class PartyMemberExtensions
{
public static bool HasStatus(this PartyMember character, uint statusId)
{
return character.Statuses.Any(status => status.StatusId == statusId);
}
public static bool HasStatus(this PartyMember character, List<uint> statusList)
{
return character.Statuses.Any(status => statusList.Contains(status.StatusId));
}
}

View File

@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
namespace KamiLib.Extensions;
public static class PlayerCharacterExtensions
{
public static bool HasStatus(this PlayerCharacter character, uint statusId)
{
return character.StatusList.Any(status => status.StatusId == statusId);
}
public static bool HasStatus(this PlayerCharacter character, List<uint> statusList)
{
return character.StatusList.Any(status => statusList.Contains(status.StatusId));
}
public static bool HasOnlineStatus(this PlayerCharacter character, uint statusId)
{
return character.OnlineStatus.Id == statusId;
}
public static int StatusCount(this PlayerCharacter character, List<uint> statusList)
{
return character.StatusList.Count(status => statusList.Contains(status.StatusId));
}
public static bool HasPet(this PlayerCharacter character)
{
var ownedObjects = Service.ObjectTable.Where(obj => obj.OwnerId == character.ObjectId);
return ownedObjects.Any(obj => obj.ObjectKind == ObjectKind.BattleNpc && (obj as BattleNpc)?.SubKind == (byte) BattleNpcSubKind.Pet);
}
public static IEnumerable<PlayerCharacter> Alive(this IEnumerable<PlayerCharacter> list)
{
return list.Where(member => member.CurrentHp > 0);
}
public static IEnumerable<PlayerCharacter> WithJob(this IEnumerable<PlayerCharacter> list, uint jobID)
{
return list.Where(member => member.ClassJob.Id == jobID);
}
public static IEnumerable<PlayerCharacter> WithJob(this IEnumerable<PlayerCharacter> list, List<uint> jobList)
{
return list.Where(member => jobList.Contains(member.ClassJob.Id));
}
public static IEnumerable<PlayerCharacter> WithStatus(this IEnumerable<PlayerCharacter> list, uint statusID)
{
return list.Where(member => member.HasStatus(statusID));
}
public static IEnumerable<PlayerCharacter> WithStatus(this IEnumerable<PlayerCharacter> list, List<uint> statusList)
{
return list.Where(member => member.HasStatus(statusList));
}
}

View File

@ -0,0 +1,30 @@
using Dalamud.Interface.Utility;
using Dalamud.Utility;
using ImGuiNET;
using KamiLib.Caching;
using KamiLib.Drawing;
using Lumina.Excel.GeneratedSheets;
namespace KamiLib.Extensions;
public static class TerritoryTypeExtensions
{
public static void DrawLabel(this TerritoryType data)
{
var placeString = data.GetPlaceNameString();
var startPosition = ImGui.GetCursorPos();
ImGui.TextColored(Colors.Grey, data.RowId.ToString());
ImGui.SameLine(startPosition.X + 50.0f * ImGuiHelpers.GlobalScale);
ImGui.Text(placeString);
}
public static string GetPlaceNameString(this TerritoryType data)
{
var placeNameRow = data.PlaceName.Row;
var placeName = LuminaCache<PlaceName>.Instance.GetRow(placeNameRow);
var placeString = placeName?.Name.ToDalamudString().TextValue ?? "Unknown PlaceName";
return placeString;
}
}

View File

@ -0,0 +1,79 @@
using Dalamud.Game.ClientState.Conditions;
using FFXIVClientStructs.FFXIV.Client.Game;
using KamiLib.Caching;
using Lumina.Excel.GeneratedSheets;
namespace KamiLib.GameState;
public static class Condition
{
public static bool IsBoundByDuty()
{
if(IsInIslandSanctuary()) return false;
return Service.Condition[ConditionFlag.BoundByDuty] ||
Service.Condition[ConditionFlag.BoundByDuty56] ||
Service.Condition[ConditionFlag.BoundByDuty95];
}
public static bool IsInCombat() => Service.Condition[ConditionFlag.InCombat];
public static bool IsInCutsceneOrQuestEvent() => IsInCutscene() || IsInQuestEvent();
public static bool IsDutyRecorderPlayback() => Service.Condition[ConditionFlag.DutyRecorderPlayback];
public static bool IsIslandDoingSomethingMode() => Service.GameGui.GetAddonByName("MJIPadGuide", 1) != nint.Zero;
public static bool IsInCutscene()
{
return Service.Condition[ConditionFlag.OccupiedInCutSceneEvent] ||
Service.Condition[ConditionFlag.WatchingCutscene] ||
Service.Condition[ConditionFlag.WatchingCutscene78];
}
public static bool IsInQuestEvent()
{
if (IsInIslandSanctuary() && IsIslandDoingSomethingMode()) return false;
return Service.Condition[ConditionFlag.OccupiedInQuestEvent];
}
public static bool IsBetweenAreas()
{
return Service.Condition[ConditionFlag.BetweenAreas] ||
Service.Condition[ConditionFlag.BetweenAreas51];
}
public static bool IsInIslandSanctuary()
{
var territoryInfo = LuminaCache<TerritoryType>.Instance.GetRow(Service.ClientState.TerritoryType);
if (territoryInfo is null) return false;
// Island Sanctuary
return territoryInfo.TerritoryIntendedUse == 49;
}
public static bool IsCrafting()
{
return Service.Condition[ConditionFlag.Crafting] ||
Service.Condition[ConditionFlag.Crafting40];
}
public static bool IsCrossWorld()
{
return Service.Condition[ConditionFlag.ParticipatingInCrossWorldPartyOrAlliance];
}
public static bool IsInSanctuary()
{
return GameMain.IsInSanctuary();
}
public static bool CheckFlag(ConditionFlag flag)
{
return Service.Condition[flag];
}
public static bool IsGathering()
{
return Service.Condition[ConditionFlag.Gathering] ||
Service.Condition[ConditionFlag.Gathering42];
}
}

View File

@ -0,0 +1,124 @@
using System;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using KamiLib.Hooking;
namespace KamiLib.GameState;
public unsafe class DutyState : IDisposable
{
private delegate byte DutyEventDelegate(void* a1, void* a2, ushort* a3);
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8B D9 49 8B F8 41 0F B7 08", DetourName = nameof(DutyEventFunction))]
private readonly Hook<DutyEventDelegate>? dutyEventHook = null;
public bool IsDutyStarted { get; private set; }
public delegate void DutyStateEvent(uint duty);
public event DutyStateEvent? DutyStarted;
public event DutyStateEvent? DutyWiped;
public event DutyStateEvent? DutyRecommenced;
public event DutyStateEvent? DutyCompleted;
private bool completedThisTerritory;
private static DutyState? _instance;
public static DutyState Instance => _instance ??= new DutyState();
private DutyState()
{
Service.GameInteropProvider.InitializeFromAttributes(this);
dutyEventHook?.Enable();
if (Condition.IsBoundByDuty())
{
IsDutyStarted = true;
}
Service.Framework.Update += FrameworkUpdate;
Service.ClientState.TerritoryChanged += TerritoryChanged;
}
public static void Cleanup()
{
_instance?.Dispose();
}
public void Dispose()
{
dutyEventHook?.Dispose();
Service.Framework.Update -= FrameworkUpdate;
Service.ClientState.TerritoryChanged -= TerritoryChanged;
}
private void FrameworkUpdate(IFramework framework)
{
if (!IsDutyStarted && !completedThisTerritory)
{
if (Condition.IsBoundByDuty() && Condition.IsInCombat())
{
IsDutyStarted = true;
}
}
else if (!Condition.IsBoundByDuty())
{
IsDutyStarted = false;
}
}
private void TerritoryChanged(ushort e)
{
if (IsDutyStarted)
{
IsDutyStarted = false;
}
completedThisTerritory = false;
}
private byte DutyEventFunction(void* a1, void* a2, ushort* a3)
{
Safety.ExecuteSafe(() =>
{
var category = *(a3);
var type = *(uint*)(a3 + 4);
// DirectorUpdate Category
if (category == 0x6D)
{
switch (type)
{
// Duty Commenced
case 0x40000001:
IsDutyStarted = true;
DutyStarted?.Invoke(Service.ClientState.TerritoryType);
break;
// Party Wipe
case 0x40000005:
IsDutyStarted = false;
DutyWiped?.Invoke(Service.ClientState.TerritoryType);
break;
// Duty Recommence
case 0x40000006:
IsDutyStarted = true;
DutyRecommenced?.Invoke(Service.ClientState.TerritoryType);
break;
// Duty Completed
case 0x40000003:
IsDutyStarted = false;
completedThisTerritory = true;
DutyCompleted?.Invoke(Service.ClientState.TerritoryType);
break;
}
}
});
return dutyEventHook!.Original(a1, a2, a3);
}
}

View File

@ -0,0 +1,26 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiLib.Hooking;
public static class Delegates
{
public static unsafe class Addon
{
public delegate nint OnSetup(AtkUnitBase* addon, int valueCount, AtkValue* values);
public delegate void Draw(AtkUnitBase* addon);
public delegate byte OnRefresh(AtkUnitBase* addon, int valueCount, AtkValue* values);
public delegate void Finalize(AtkUnitBase* addon);
public delegate byte Update(AtkUnitBase* addon);
}
public static unsafe class Agent
{
public delegate void Show(AgentInterface* agent);
public delegate nint ReceiveEvent(AgentInterface* agent, nint rawData, AtkValue* args, uint argCount, ulong sender);
}
public static unsafe class Other
{
public delegate void* GoldSaucerUpdate(void* a1, byte* a2, uint a3, ushort a4, void* a5, int* data, byte eventID);
}
}

32
KamiLib/Hooking/Safety.cs Normal file
View File

@ -0,0 +1,32 @@
using System;
using System.Diagnostics;
using System.Reflection;
using Dalamud.Logging;
namespace KamiLib.Hooking;
public static class Safety
{
public static void ExecuteSafe(Action action, string? message = null)
{
try
{
action();
}
catch (Exception exception)
{
var trace = new StackTrace().GetFrame(1);
var callingAssembly = Assembly.GetCallingAssembly().GetName().Name;
if (trace is not null)
{
var callingClass = trace.GetMethod()?.DeclaringType;
var callingName = trace.GetMethod()?.Name;
PluginLog.Error($"Exception Source: {callingAssembly} :: {callingClass} :: {callingName}");
}
PluginLog.Error(exception, message ?? "Caught Exception Safely");
}
}
}

View File

@ -0,0 +1,6 @@
namespace KamiLib.Interfaces;
public interface IDrawable
{
void Draw();
}

View File

@ -0,0 +1,18 @@
using KamiLib.Drawing;
namespace KamiLib.Interfaces;
public interface IInfoBoxTableConfigurationRow
{
void GetConfigurationRow(InfoBoxTable owner);
}
public interface IInfoBoxTableDataRow
{
void GetDataRow(InfoBoxTable owner);
}
public interface IInfoBoxListConfigurationRow
{
void GetConfigurationRow(InfoBoxList owner);
}

View File

@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using KamiLib.ChatCommands;
namespace KamiLib.Interfaces;
public interface IPluginCommand
{
string? CommandArgument { get; }
IEnumerable<ISubCommand> SubCommands { get; }
public bool Execute(CommandData data)
{
var matchingSubCommands = SubCommands
.Where(subCommand => MatchingSubCommand(subCommand, data.SubCommand))
.ToList();
if (matchingSubCommands.Any())
{
foreach (var subCommand in matchingSubCommands)
{
subCommand.Execute(data);
}
return true;
}
return false;
}
private static bool MatchingSubCommand(ISubCommand subCommand, string? targetCommand)
{
if (subCommand.GetCommand() == targetCommand) return true;
if (subCommand.GetAliases()?.Contains(targetCommand) ?? false) return true;
return false;
}
}

View File

@ -0,0 +1,8 @@
namespace KamiLib.Interfaces;
public interface ISelectable
{
IDrawable Contents { get; }
void DrawLabel();
string ID { get; }
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using KamiLib.ChatCommands;
namespace KamiLib.Interfaces;
public interface ISubCommand
{
string? GetCommand();
IEnumerable<string>? GetAliases();
bool Execute(CommandData commandData);
string? GetHelpText();
bool Hidden { get; }
bool HasParameterAction { get; }
}

View File

@ -0,0 +1,8 @@
namespace KamiLib.Interfaces;
public interface ITabItem
{
string TabName { get; }
bool Enabled { get; }
void Draw();
}

51
KamiLib/KamiCommon.cs Normal file
View File

@ -0,0 +1,51 @@
using System;
using Dalamud.Plugin;
using KamiLib.Blacklist;
using KamiLib.Caching;
using KamiLib.ChatCommands;
using KamiLib.GameState;
using KamiLib.Localization;
using KamiLib.Teleporter;
using KamiLib.UserInterface;
using KamiLib.Windows;
namespace KamiLib;
public static class KamiCommon
{
public static string PluginName { get; private set; } = string.Empty;
public static CommandManager CommandManager { get; private set; } = null!;
public static WindowManager WindowManager { get; private set; } = null!;
private static Action _saveConfigFunction = null!;
public static void Initialize(DalamudPluginInterface pluginInterface, string pluginName, Action saveConfig)
{
pluginInterface.Create<Service>();
PluginName = pluginName;
_saveConfigFunction = saveConfig;
LocalizationManager.Instance.Initialize();
BlacklistDraw.PrimeSearch();
CommandManager = new CommandManager();
WindowManager = new WindowManager();
}
public static void Dispose()
{
CommandManager.Dispose();
WindowManager.Dispose();
IconCache.Cleanup();
GameUserInterface.Cleanup();
DutyState.Cleanup();
ChatPayloadManager.Cleanup();
TeleportManager.Cleanup();
LocalizationManager.Cleanup();
}
public static void SaveConfiguration() => _saveConfigFunction();
}

72
KamiLib/KamiLib.csproj Normal file
View File

@ -0,0 +1,72 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<Platforms>x64</Platforms>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<None Remove="LICENSE" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="CheapLoc">
<HintPath>$(DalamudLibPath)CheapLoc.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ImGuiScene">
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localization\Strings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Localization\Strings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Strings.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>

16
KamiLib/KamiLib.sln Normal file
View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KamiLib", "KamiLib.csproj", "{52A9E8E2-ACC7-4696-8684-5C4984D0350C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{52A9E8E2-ACC7-4696-8684-5C4984D0350C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52A9E8E2-ACC7-4696-8684-5C4984D0350C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52A9E8E2-ACC7-4696-8684-5C4984D0350C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52A9E8E2-ACC7-4696-8684-5C4984D0350C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

14
KamiLib/LICENSE Normal file
View File

@ -0,0 +1,14 @@
Copyright (C) 2022 MidoriKami
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,41 @@
using System;
using System.Globalization;
using Dalamud.Logging;
namespace KamiLib.Localization;
internal class LocalizationManager : IDisposable
{
private static LocalizationManager? _instance;
public static LocalizationManager Instance => _instance ??= new LocalizationManager();
public void Initialize()
{
Strings.Culture = new CultureInfo(Service.PluginInterface.UiLanguage);
Service.PluginInterface.LanguageChanged += OnLanguageChange;
}
public static void Cleanup()
{
_instance?.Dispose();
}
public void Dispose()
{
Service.PluginInterface.LanguageChanged -= OnLanguageChange;
}
private void OnLanguageChange(string languageCode)
{
try
{
PluginLog.Information($"Loading Localization for {languageCode}");
Strings.Culture = new CultureInfo(languageCode);
}
catch (Exception ex)
{
PluginLog.Error(ex, "Unable to Load Localization");
}
}
}

331
KamiLib/Localization/Strings.Designer.cs generated Normal file
View File

@ -0,0 +1,331 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace KamiLib.Localization {
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Strings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("KamiLib.Localization.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Add or Remove Current Zone.
/// </summary>
internal static string Blacklist_AddRemoveZone {
get {
return ResourceManager.GetString("Blacklist_AddRemoveZone", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add {0} Selected Areas.
/// </summary>
internal static string Blacklist_AddSelectedAreas {
get {
return ResourceManager.GetString("Blacklist_AddSelectedAreas", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Clear Blacklist.
/// </summary>
internal static string Blacklist_ClearBlacklist {
get {
return ResourceManager.GetString("Blacklist_ClearBlacklist", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Currently Blacklisted Areas.
/// </summary>
internal static string Blacklist_CurrentlyBlacklisted {
get {
return ResourceManager.GetString("Blacklist_CurrentlyBlacklisted", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Blacklist is Empty.
/// </summary>
internal static string Blacklist_Empty {
get {
return ResourceManager.GetString("Blacklist_Empty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove {0} Selected Areas.
/// </summary>
internal static string Blacklist_RemoveSelectedAreas {
get {
return ResourceManager.GetString("Blacklist_RemoveSelectedAreas", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search . . . .
/// </summary>
internal static string Blacklist_Search {
get {
return ResourceManager.GetString("Blacklist_Search", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select zones to add to blacklist.
/// </summary>
internal static string Blacklist_SelectZones {
get {
return ResourceManager.GetString("Blacklist_SelectZones", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Zone Search.
/// </summary>
internal static string Blacklist_ZoneSearch {
get {
return ResourceManager.GetString("Blacklist_ZoneSearch", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Closing {0} Window.
/// </summary>
internal static string Command_ClosingWindow {
get {
return ResourceManager.GetString("Command_ClosingWindow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to display a list of all available sub-commands.
/// </summary>
internal static string Command_DisplayHelpText {
get {
return ResourceManager.GetString("Command_DisplayHelpText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The command &apos;{0} {1}&apos; does not exist..
/// </summary>
internal static string Command_DoesntExist {
get {
return ResourceManager.GetString("Command_DoesntExist", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The command &apos;{0} {1} {2}&apos; does not exist..
/// </summary>
internal static string Command_DoesntExistExtended {
get {
return ResourceManager.GetString("Command_DoesntExistExtended", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Command.
/// </summary>
internal static string Command_Label {
get {
return ResourceManager.GetString("Command_Label", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open Configuration Window.
/// </summary>
internal static string Command_OpenConfigWindow {
get {
return ResourceManager.GetString("Command_OpenConfigWindow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Opening {0} Window.
/// </summary>
internal static string Command_OpeningWindow {
get {
return ResourceManager.GetString("Command_OpeningWindow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open {0} Window.
/// </summary>
internal static string Command_OpenWindow {
get {
return ResourceManager.GetString("Command_OpenWindow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The {0} Window cannot be opened while in a PvP area.
/// </summary>
internal static string Command_PvPError {
get {
return ResourceManager.GetString("Command_PvPError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show this message.
/// </summary>
internal static string Command_ShowThisMessage {
get {
return ResourceManager.GetString("Command_ShowThisMessage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add.
/// </summary>
internal static string Common_Add {
get {
return ResourceManager.GetString("Common_Add", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error.
/// </summary>
internal static string Common_Error {
get {
return ResourceManager.GetString("Common_Error", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove.
/// </summary>
internal static string Common_Remove {
get {
return ResourceManager.GetString("Common_Remove", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to value.
/// </summary>
internal static string Common_Value {
get {
return ResourceManager.GetString("Common_Value", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Hold &apos;Shift&apos; to enable button.
/// </summary>
internal static string DisabledButton_HoldShift {
get {
return ResourceManager.GetString("DisabledButton_HoldShift", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select an item in the left pane.
/// </summary>
internal static string Selection_NoItemSelected {
get {
return ResourceManager.GetString("Selection_NoItemSelected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot teleport in this situation.
/// </summary>
internal static string Teleport_BadSituation {
get {
return ResourceManager.GetString("Teleport_BadSituation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to To use the teleport function, you must install the &quot;Teleporter&quot; plugin.
/// </summary>
internal static string Teleport_InstallTeleporter {
get {
return ResourceManager.GetString("Teleport_InstallTeleporter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Teleport.
/// </summary>
internal static string Teleport_Label {
get {
return ResourceManager.GetString("Teleport_Label", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Destination Aetheryte is not unlocked, teleport cancelled.
/// </summary>
internal static string Teleport_NotUnlocked {
get {
return ResourceManager.GetString("Teleport_NotUnlocked", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Teleporting to &apos;{0}&apos;.
/// </summary>
internal static string Teleport_TeleportingTo {
get {
return ResourceManager.GetString("Teleport_TeleportingTo", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Blacklist_CurrentlyBlacklisted" xml:space="preserve">
<value>Bereiche aktuell auf der Schwarzen Liste</value>
</data>
<data name="Blacklist_ClearBlacklist" xml:space="preserve">
<value>Schwarze Liste leeren</value>
</data>
<data name="Blacklist_RemoveSelectedAreas" xml:space="preserve">
<value>Löschen der {0} ausgewählten Zonen</value>
</data>
<data name="DisabledButton_HoldShift" xml:space="preserve">
<value>Halten Sie 'Umschalt' gedrückt, um den Knopf zu aktivieren</value>
</data>
<data name="Blacklist_AddRemoveZone" xml:space="preserve">
<value>Aktuelle Zone hinzufügen oder entfernen</value>
</data>
<data name="Common_Add" xml:space="preserve">
<value>Hinzufügen</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Entfernen</value>
</data>
<data name="Blacklist_ZoneSearch" xml:space="preserve">
<value>Zonen Suche</value>
</data>
<data name="Blacklist_AddSelectedAreas" xml:space="preserve">
<value>Hinzufügen der {0} ausgewählten Zonen</value>
</data>
<data name="Blacklist_SelectZones" xml:space="preserve">
<value>Wählen sie Zonen aus um sie der Schwarzen Liste hinzuzufügen</value>
</data>
<data name="Blacklist_Empty" xml:space="preserve">
<value>Die Schwarze Liste ist leer</value>
</data>
<data name="Command_DoesntExist" xml:space="preserve">
<value>Der Befehl "{0} {1}" existiert nicht.</value>
</data>
<data name="Command_OpenConfigWindow" xml:space="preserve">
<value>Öffne Konfigurationsfenster</value>
</data>
<data name="Command_DisplayHelpText" xml:space="preserve">
<value>Zeige eine Liste aller vorhandenen Sub-Befehle an</value>
</data>
<data name="Command_Label" xml:space="preserve">
<value>Befehl</value>
</data>
<data name="Blacklist_Search" xml:space="preserve">
<value>Die Suche . . . </value>
</data>
<data name="Command_ShowThisMessage" xml:space="preserve">
<value>Diese Nachricht anzeigen</value>
</data>
<data name="Common_Value" xml:space="preserve">
<value>Wert</value>
</data>
<data name="Command_PvPError" xml:space="preserve">
<value>Das {0}-Fenster kann in einem PvP-Bereich nicht geöffnet werden</value>
</data>
<data name="Command_OpenWindow" xml:space="preserve">
<value>Öffne {0} Fenster</value>
</data>
<data name="Command_OpeningWindow" xml:space="preserve">
<value>Öffne {0} Fenster</value>
</data>
<data name="Command_ClosingWindow" xml:space="preserve">
<value>Schließe {0} Fenster</value>
</data>
<data name="Command_DoesntExistExtended" xml:space="preserve">
<value>Der Befehl "{0} {1} {2}" existiert nicht.</value>
</data>
<data name="Common_Error" xml:space="preserve">
<value>Fehler</value>
</data>
<data name="Teleport_NotUnlocked" xml:space="preserve">
<value>Ziel-Aetheryte ist nicht freigeschaltet, Teleport abgebrochen</value>
</data>
<data name="Teleport_BadSituation" xml:space="preserve">
<value>In dieser Situation kann nicht teleportiert werden</value>
</data>
<data name="Teleport_TeleportingTo" xml:space="preserve">
<value>Teleportiere zu {0}</value>
</data>
<data name="Teleport_Label" xml:space="preserve">
<value>Teleport</value>
</data>
<data name="Teleport_InstallTeleporter" xml:space="preserve">
<value>Um die Teleport-Funktion nutzen zu können, müssen Sie das "Teleporter"-Plugin installieren</value>
</data>
<data name="Selection_NoItemSelected" xml:space="preserve">
<value>Wählen sie einen Gegenstand im linken Bereich aus</value>
</data>
</root>

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Blacklist_CurrentlyBlacklisted" xml:space="preserve">
<value>Zonas en la lista negra</value>
</data>
<data name="Blacklist_ClearBlacklist" xml:space="preserve">
<value>Limpiar Lista Negra</value>
</data>
<data name="Blacklist_RemoveSelectedAreas" xml:space="preserve">
<value>Eliminar las {0} zonas seleccionadas</value>
</data>
<data name="DisabledButton_HoldShift" xml:space="preserve">
<value>Mantén «mayús» para habilitar botón</value>
</data>
<data name="Blacklist_AddRemoveZone" xml:space="preserve">
<value>Añadir o Eliminar zona actual</value>
</data>
<data name="Common_Add" xml:space="preserve">
<value>Añadir</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Eliminar</value>
</data>
<data name="Blacklist_ZoneSearch" xml:space="preserve">
<value>Buscar zonas</value>
</data>
<data name="Blacklist_AddSelectedAreas" xml:space="preserve">
<value>Añadir las {0} zonas seleccionadas</value>
</data>
<data name="Blacklist_SelectZones" xml:space="preserve">
<value>Selecciona qué zonas añadir a la lista negra</value>
</data>
<data name="Blacklist_Empty" xml:space="preserve">
<value>La lista negra está vacía</value>
</data>
<data name="Command_DoesntExist" xml:space="preserve">
<value>El comando «{0} {1}» no existe.</value>
</data>
<data name="Command_OpenConfigWindow" xml:space="preserve">
<value>Abrir ventana de configuración</value>
</data>
<data name="Command_DisplayHelpText" xml:space="preserve">
<value>muestra una lista de todos los subcomandos disponibles</value>
</data>
<data name="Command_Label" xml:space="preserve">
<value>Comando</value>
</data>
<data name="Blacklist_Search" xml:space="preserve">
<value>Buscar . . . </value>
</data>
<data name="Command_ShowThisMessage" xml:space="preserve">
<value>Mostrar este mensaje</value>
</data>
<data name="Common_Value" xml:space="preserve">
<value>valor</value>
</data>
<data name="Command_PvPError" xml:space="preserve">
<value>La ventana {0} no puede abrirse en las zonas PvP</value>
</data>
<data name="Command_OpenWindow" xml:space="preserve">
<value>Abrir ventana {0}</value>
</data>
<data name="Command_OpeningWindow" xml:space="preserve">
<value>Abriendo ventana {0}</value>
</data>
<data name="Command_ClosingWindow" xml:space="preserve">
<value>Cerrando ventana {0}</value>
</data>
<data name="Command_DoesntExistExtended" xml:space="preserve">
<value>El comando «{0} {1} {2}» no existe.</value>
</data>
<data name="Common_Error" xml:space="preserve">
<value>Error</value>
</data>
<data name="Teleport_NotUnlocked" xml:space="preserve">
<value>La eterita de destino no está desbloqueada. Teletransporte cancelado</value>
</data>
<data name="Teleport_BadSituation" xml:space="preserve">
<value>Imposible teletransportarse en esta situación</value>
</data>
<data name="Teleport_TeleportingTo" xml:space="preserve">
<value>Teletransportándose a {0}</value>
</data>
<data name="Teleport_Label" xml:space="preserve">
<value>Teletransporte</value>
</data>
<data name="Teleport_InstallTeleporter" xml:space="preserve">
<value>Para usar la función de teletransporte, debes instalar el plugin «Teleporter»</value>
</data>
<data name="Selection_NoItemSelected" xml:space="preserve">
<value>Selecciona un objeto en el panel izquierdo</value>
</data>
</root>

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Blacklist_CurrentlyBlacklisted" xml:space="preserve">
<value>Zones actuellement dans la liste noire</value>
</data>
<data name="Blacklist_ClearBlacklist" xml:space="preserve">
<value>Vider la liste noire</value>
</data>
<data name="Blacklist_RemoveSelectedAreas" xml:space="preserve">
<value>Enlever les {0} zones sélectionnées</value>
</data>
<data name="DisabledButton_HoldShift" xml:space="preserve">
<value>Tenir 'Maj' pour activer le bouton</value>
</data>
<data name="Blacklist_AddRemoveZone" xml:space="preserve">
<value>Ajouter ou enlever la zone actuelle</value>
</data>
<data name="Common_Add" xml:space="preserve">
<value>Ajouter</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Enlever</value>
</data>
<data name="Blacklist_ZoneSearch" xml:space="preserve">
<value>Recherche de zone</value>
</data>
<data name="Blacklist_AddSelectedAreas" xml:space="preserve">
<value>Ajouter les {0} zones sélectionnées</value>
</data>
<data name="Blacklist_SelectZones" xml:space="preserve">
<value>Sélectionner les zones à ajouter à la liste noire</value>
</data>
<data name="Blacklist_Empty" xml:space="preserve">
<value>La liste noire est vide</value>
</data>
<data name="Command_DoesntExist" xml:space="preserve">
<value>La commande '{0} {1}' n'existe pas.</value>
</data>
<data name="Command_OpenConfigWindow" xml:space="preserve">
<value>Ouvrir la fenêtre de configuration</value>
</data>
<data name="Command_DisplayHelpText" xml:space="preserve">
<value>afficher une liste de toutes les sous-commandes disponibles</value>
</data>
<data name="Command_Label" xml:space="preserve">
<value>Commande</value>
</data>
<data name="Blacklist_Search" xml:space="preserve">
<value>Rechercher . . . </value>
</data>
<data name="Command_ShowThisMessage" xml:space="preserve">
<value>Afficher ce message</value>
</data>
<data name="Common_Value" xml:space="preserve">
<value>valeur</value>
</data>
<data name="Command_PvPError" xml:space="preserve">
<value>La fenêtre {0} ne peut pas être ouverte lorsque vous êtes dans une zone JcJ</value>
</data>
<data name="Command_OpenWindow" xml:space="preserve">
<value>Ouvrir la fenêtre {0}</value>
</data>
<data name="Command_OpeningWindow" xml:space="preserve">
<value>Ouverture de la fenêtre {0}</value>
</data>
<data name="Command_ClosingWindow" xml:space="preserve">
<value>Fermeture de la fenêtre {0}</value>
</data>
<data name="Command_DoesntExistExtended" xml:space="preserve">
<value>La commande '{0} {1} {2}' n'existe pas.</value>
</data>
<data name="Common_Error" xml:space="preserve">
<value>Erreur</value>
</data>
<data name="Teleport_NotUnlocked" xml:space="preserve">
<value>L'éthérite de destination n'est pas harmonisée, la téléportation a été annulée</value>
</data>
<data name="Teleport_BadSituation" xml:space="preserve">
<value>La téléportation ne peut pas être effectuée dans cette situation</value>
</data>
<data name="Teleport_TeleportingTo" xml:space="preserve">
<value>Téléportation vers '{0}'</value>
</data>
<data name="Teleport_Label" xml:space="preserve">
<value>Téléportation</value>
</data>
<data name="Teleport_InstallTeleporter" xml:space="preserve">
<value>Pour utiliser la fonction de téléportation, vous devez installer le plugin "Teleporter"</value>
</data>
<data name="Selection_NoItemSelected" xml:space="preserve">
<value>Sélectionnez un élément dans le panneau à gauche</value>
</data>
</root>

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Blacklist_CurrentlyBlacklisted" xml:space="preserve">
<value>ブラックリストに登録されているエリア</value>
</data>
<data name="Blacklist_ClearBlacklist" xml:space="preserve">
<value>ブラックリストをクリア</value>
</data>
<data name="Blacklist_RemoveSelectedAreas" xml:space="preserve">
<value>{0} 個の選択された領域を削除</value>
</data>
<data name="DisabledButton_HoldShift" xml:space="preserve">
<value>Shiftキーを押しながらボタンを有効にします</value>
</data>
<data name="Blacklist_AddRemoveZone" xml:space="preserve">
<value>現在のゾーンを追加または削除</value>
</data>
<data name="Common_Add" xml:space="preserve">
<value>追加</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>削除</value>
</data>
<data name="Blacklist_ZoneSearch" xml:space="preserve">
<value>ゾーン検索</value>
</data>
<data name="Blacklist_AddSelectedAreas" xml:space="preserve">
<value>{0} 個の選択されたエリアを削除</value>
</data>
<data name="Blacklist_SelectZones" xml:space="preserve">
<value>ブラックリストに追加するゾーンを選択</value>
</data>
<data name="Blacklist_Empty" xml:space="preserve">
<value>ブラックリストは空です。</value>
</data>
<data name="Command_DoesntExist" xml:space="preserve">
<value>コマンド '{0} {1}' は存在しません。</value>
</data>
<data name="Command_OpenConfigWindow" xml:space="preserve">
<value>設定ウィンドウを開く</value>
</data>
<data name="Command_DisplayHelpText" xml:space="preserve">
<value>利用可能なコマンドの一覧を表示します。</value>
</data>
<data name="Command_Label" xml:space="preserve">
<value>コマンド</value>
</data>
<data name="Blacklist_Search" xml:space="preserve">
<value>検索 ... </value>
</data>
<data name="Command_ShowThisMessage" xml:space="preserve">
<value>このメッセージを表示</value>
</data>
<data name="Common_Value" xml:space="preserve">
<value>値</value>
</data>
<data name="Command_PvPError" xml:space="preserve">
<value>PvPエリアでは {0} ウィンドウを開くことができません</value>
</data>
<data name="Command_OpenWindow" xml:space="preserve">
<value>{0} のウィンドウを開く</value>
</data>
<data name="Command_OpeningWindow" xml:space="preserve">
<value>{0} のウィンドウを開いています...</value>
</data>
<data name="Command_ClosingWindow" xml:space="preserve">
<value>{0} のウィンドウを閉じる</value>
</data>
<data name="Command_DoesntExistExtended" xml:space="preserve">
<value>コマンド '{0} {1} {2}' は存在しません。</value>
</data>
<data name="Common_Error" xml:space="preserve">
<value>エラー</value>
</data>
<data name="Teleport_NotUnlocked" xml:space="preserve">
<value>宛先のエーテライトが未解放のため、テレポートはキャンセルされました。</value>
</data>
<data name="Teleport_BadSituation" xml:space="preserve">
<value>この状況ではテレポートできません</value>
</data>
<data name="Teleport_TeleportingTo" xml:space="preserve">
<value>'{0} ' にテレポート中</value>
</data>
<data name="Teleport_Label" xml:space="preserve">
<value>テレポート</value>
</data>
<data name="Teleport_InstallTeleporter" xml:space="preserve">
<value>テレポート機能を使用するには、"Teleporter" プラグインをインストールする必要があります</value>
</data>
<data name="Selection_NoItemSelected" xml:space="preserve">
<value>左から設定する項目を選択してください</value>
</data>
</root>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Blacklist_CurrentlyBlacklisted" xml:space="preserve">
<value>Currently Blacklisted Areas</value>
</data>
<data name="Blacklist_ClearBlacklist" xml:space="preserve">
<value>Clear Blacklist</value>
</data>
<data name="Blacklist_RemoveSelectedAreas" xml:space="preserve">
<value>Remove {0} Selected Areas</value>
</data>
<data name="DisabledButton_HoldShift" xml:space="preserve">
<value>Hold 'Shift' to enable button</value>
</data>
<data name="Blacklist_AddRemoveZone" xml:space="preserve">
<value>Add or Remove Current Zone</value>
</data>
<data name="Common_Add" xml:space="preserve">
<value>Add</value>
</data>
<data name="Common_Remove" xml:space="preserve">
<value>Remove</value>
</data>
<data name="Blacklist_ZoneSearch" xml:space="preserve">
<value>Zone Search</value>
</data>
<data name="Blacklist_AddSelectedAreas" xml:space="preserve">
<value>Add {0} Selected Areas</value>
</data>
<data name="Blacklist_SelectZones" xml:space="preserve">
<value>Select zones to add to blacklist</value>
</data>
<data name="Blacklist_Empty" xml:space="preserve">
<value>Blacklist is Empty</value>
</data>
<data name="Command_DoesntExist" xml:space="preserve">
<value>The command '{0} {1}' does not exist.</value>
</data>
<data name="Command_OpenConfigWindow" xml:space="preserve">
<value>Open Configuration Window</value>
</data>
<data name="Command_DisplayHelpText" xml:space="preserve">
<value>display a list of all available sub-commands</value>
</data>
<data name="Command_Label" xml:space="preserve">
<value>Command</value>
</data>
<data name="Blacklist_Search" xml:space="preserve">
<value>Search . . . </value>
</data>
<data name="Command_ShowThisMessage" xml:space="preserve">
<value>Show this message</value>
</data>
<data name="Common_Value" xml:space="preserve">
<value>value</value>
</data>
<data name="Command_PvPError" xml:space="preserve">
<value>The {0} Window cannot be opened while in a PvP area</value>
</data>
<data name="Command_OpenWindow" xml:space="preserve">
<value>Open {0} Window</value>
</data>
<data name="Command_OpeningWindow" xml:space="preserve">
<value>Opening {0} Window</value>
</data>
<data name="Command_ClosingWindow" xml:space="preserve">
<value>Closing {0} Window</value>
</data>
<data name="Command_DoesntExistExtended" xml:space="preserve">
<value>The command '{0} {1} {2}' does not exist.</value>
</data>
<data name="Common_Error" xml:space="preserve">
<value>Error</value>
</data>
<data name="Teleport_NotUnlocked" xml:space="preserve">
<value>Destination Aetheryte is not unlocked, teleport cancelled</value>
</data>
<data name="Teleport_BadSituation" xml:space="preserve">
<value>Cannot teleport in this situation</value>
</data>
<data name="Teleport_TeleportingTo" xml:space="preserve">
<value>Teleporting to '{0}'</value>
</data>
<data name="Teleport_Label" xml:space="preserve">
<value>Teleport</value>
</data>
<data name="Teleport_InstallTeleporter" xml:space="preserve">
<value>To use the teleport function, you must install the "Teleporter" plugin</value>
</data>
<data name="Selection_NoItemSelected" xml:space="preserve">
<value>Select an item in the left pane</value>
</data>
</root>

76
KamiLib/Misc/DutyLists.cs Normal file
View File

@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud;
using KamiLib.Caching;
using Lumina.Excel.GeneratedSheets;
namespace KamiLib.Misc;
public enum DutyType
{
Savage,
Ultimate,
ExtremeUnreal,
Criterion,
Alliance,
None,
}
public class DutyLists
{
private List<uint> Savage { get; }
private List<uint> Ultimate { get; }
private List<uint> ExtremeUnreal { get; }
private List<uint> Criterion { get; }
private List<uint> Alliance { get; }
private static DutyLists? _instance;
public static DutyLists Instance => _instance ??= new DutyLists();
private DutyLists()
{
// ContentType.Row 5 == Raids
Savage = LuminaCache<ContentFinderCondition>.Instance.OfLanguage(ClientLanguage.English)
.Where(t => t.ContentType.Row == 5)
.Where(t => t.Name.RawString.Contains("Savage"))
.Select(r => r.TerritoryType.Row)
.ToList();
// ContentType.Row 28 == Ultimate Raids
Ultimate = LuminaCache<ContentFinderCondition>.Instance
.Where(t => t.ContentType.Row == 28)
.Select(t => t.TerritoryType.Row)
.ToList();
// ContentType.Row 4 == Trials
ExtremeUnreal = LuminaCache<ContentFinderCondition>.Instance.OfLanguage(ClientLanguage.English)
.Where(t => t.ContentType.Row == 4)
.Where(t => t.Name.RawString.Contains("Extreme") || t.Name.RawString.Contains("Unreal") || t.Name.RawString.Contains("The Minstrel"))
.Select(t => t.TerritoryType.Row)
.ToList();
Criterion = LuminaCache<ContentFinderCondition>.Instance
.Where(row => row.ContentType.Row is 30)
.Select(row => row.TerritoryType.Row)
.ToList();
Alliance = LuminaCache<TerritoryType>.Instance
.Where(r => r.TerritoryIntendedUse is 8)
.Select(r => r.RowId)
.ToList();
}
private DutyType GetDutyType(uint dutyId)
{
if (Savage.Contains(dutyId)) return DutyType.Savage;
if (Ultimate.Contains(dutyId)) return DutyType.Ultimate;
if (ExtremeUnreal.Contains(dutyId)) return DutyType.ExtremeUnreal;
if (Criterion.Contains(dutyId)) return DutyType.Criterion;
if (Alliance.Contains(dutyId)) return DutyType.Alliance;
return DutyType.None;
}
public bool IsType(uint dutyId, DutyType type) => GetDutyType(dutyId) == type;
public bool IsType(uint dutyId, IEnumerable<DutyType> types) => types.Any(type => IsType(dutyId, type));
}

116
KamiLib/Misc/Time.cs Normal file
View File

@ -0,0 +1,116 @@
using System;
using System.Linq;
using KamiLib.Caching;
using Lumina.Excel.GeneratedSheets;
namespace KamiLib.Misc;
public static class Time
{
public static DateTime NextDailyReset()
{
var now = DateTime.UtcNow;
if( now.Hour < 15 )
{
return now.Date.AddHours(15);
}
else
{
return now.AddDays(1).Date.AddHours(15);
}
}
public static DateTime NextWeeklyReset()
{
return NextDayOfWeek(DayOfWeek.Tuesday, 8);
}
public static DateTime NextFashionReportReset()
{
return NextWeeklyReset().AddDays(-4);
}
public static DateTime NextGrandCompanyReset()
{
var now = DateTime.UtcNow;
var targetHour = 20;
if( now.Hour < targetHour )
{
return now.Date.AddHours(targetHour);
}
else
{
return now.AddDays(1).Date.AddHours(targetHour);
}
}
public static DateTime NextLeveAllowanceReset()
{
var now = DateTime.UtcNow;
if( now.Hour < 12 )
{
return now.Date.AddHours(12);
}
else
{
return now.Date.AddDays(1);
}
}
public static DateTime NextDayOfWeek(DayOfWeek weekday, int hour)
{
var today = DateTime.UtcNow;
if(today.Hour < hour && today.DayOfWeek == weekday)
{
return today.Date.AddHours(hour);
}
else
{
var nextReset = today.AddDays(1);
while (nextReset.DayOfWeek != weekday)
{
nextReset = nextReset.AddDays(1);
}
return nextReset.Date.AddHours(hour);
}
}
public static DateTime NextJumboCactpotReset()
{
var region = LookupDatacenterRegion(Service.ClientState.LocalPlayer?.HomeWorld.GameData?.DataCenter.Row);
return region switch
{
// Japan
1 => NextDayOfWeek(DayOfWeek.Saturday, 12),
// North America
2 => NextDayOfWeek(DayOfWeek.Sunday, 2),
// Europe
3 => NextDayOfWeek(DayOfWeek.Saturday, 19),
// Australia
4 => NextDayOfWeek(DayOfWeek.Saturday, 9),
// Unknown Region
_ => DateTime.MinValue
};
}
private static byte LookupDatacenterRegion(uint? playerDatacenterID)
{
if (playerDatacenterID == null) return 0;
return LuminaCache<WorldDCGroupType>.Instance
.Where(world => world.RowId == playerDatacenterID.Value)
.Select(dc => dc.Region)
.FirstOrDefault();
}
}

22
KamiLib/Service.cs Normal file
View File

@ -0,0 +1,22 @@
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
namespace KamiLib;
internal class Service
{
[PluginService] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService] public static ICommandManager Commands { get; private set; } = null!;
[PluginService] public static IClientState ClientState { get; private set; } = null!;
[PluginService] public static IChatGui Chat { get; private set; } = null!;
[PluginService] public static IGameGui GameGui { get; private set; } = null!;
[PluginService] public static ICondition Condition { get; private set; } = null!;
[PluginService] public static IDataManager DataManager { get; private set; } = null!;
[PluginService] public static IObjectTable ObjectTable { get; private set; } = null!;
[PluginService] public static IFramework Framework { get; private set; } = null!;
[PluginService] public static IToastGui Toast { get; private set;} = null!;
[PluginService] public static IAetheryteList AetheryteList { get; private set;} = null!;
[PluginService] public static IGameInteropProvider GameInteropProvider { get; private set;} = null!;
[PluginService] public static ITextureProvider TextureProvider { get; private set;} = null!;
}

View File

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Aetherytes;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Logging;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Ipc.Exceptions;
using KamiLib.Caching;
using KamiLib.ChatCommands;
using KamiLib.Localization;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
namespace KamiLib.Teleporter;
public class TeleportInfo
{
public uint CommandID { get; }
public Enum Target { get; }
public Aetheryte Aetherite { get; }
public TeleportInfo(uint commandID, Enum target, uint aetheriteID)
{
CommandID = commandID;
Target = target;
Aetherite = GetAetheryte(aetheriteID);
}
private static Aetheryte GetAetheryte(uint id) => LuminaCache<Aetheryte>.Instance.GetRow(id)!;
}
public record TeleportLinkPayloads(Enum Location, DalamudLinkPayload Payload);
public class TeleportManager : IDisposable
{
private static TeleportManager? _instance;
public static TeleportManager Instance => _instance ??= new TeleportManager();
private readonly ICallGateSubscriber<uint, byte, bool> teleportIpc;
private readonly ICallGateSubscriber<bool> showChatMessageIpc;
private readonly List<TeleportInfo> teleportInfoList = new();
private List<TeleportLinkPayloads> ChatLinkPayloads { get; } = new();
private TeleportManager()
{
teleportIpc = Service.PluginInterface.GetIpcSubscriber<uint, byte, bool>(Strings.Teleport_Label);
showChatMessageIpc = Service.PluginInterface.GetIpcSubscriber<bool>("Teleport.ChatMessage");
}
public static void Cleanup()
{
_instance?.Dispose();
}
public void Dispose()
{
foreach (var payload in teleportInfoList)
{
Service.PluginInterface.RemoveChatLinkHandler(payload.CommandID);
}
}
public void AddTeleports(IEnumerable<TeleportInfo> teleports)
{
teleportInfoList.AddRange(teleports);
foreach (var teleport in teleportInfoList)
{
Service.PluginInterface.RemoveChatLinkHandler(teleport.CommandID);
var linkPayload = Service.PluginInterface.AddChatLinkHandler(teleport.CommandID, TeleportAction);
ChatLinkPayloads.Add(new TeleportLinkPayloads(teleport.Target, linkPayload));
}
}
private void TeleportAction(uint command, SeString message)
{
var teleportInfo = teleportInfoList.First(teleport => teleport.CommandID == command);
if (AetheryteUnlocked(teleportInfo.Aetherite, out var targetAetheriteEntry))
{
Teleport(targetAetheriteEntry!);
}
else
{
PluginLog.Error("User attempted to teleport to an aetheryte that is not unlocked");
UserError(Strings.Teleport_NotUnlocked);
}
}
public DalamudLinkPayload GetPayload(Enum targetLocation)
{
return ChatLinkPayloads.First(payload => Equals(payload.Location, targetLocation)).Payload;
}
private void Teleport(AetheryteEntry aetheryte)
{
try
{
var didTeleport = teleportIpc.InvokeFunc(aetheryte.AetheryteId, aetheryte.SubIndex);
var showMessage = showChatMessageIpc.InvokeFunc();
if (!didTeleport)
{
UserError(Strings.Teleport_BadSituation);
}
else if (showMessage)
{
Chat.Print(Strings.Teleport_Label, string.Format(Strings.Teleport_TeleportingTo, GetAetheryteName(aetheryte)));
}
}
catch (IpcNotReadyError)
{
PluginLog.Error("Teleport IPC not found");
UserError(Strings.Teleport_InstallTeleporter);
}
}
private void UserError(string error)
{
Service.Chat.PrintError(error);
Service.Toast.ShowError(error);
}
private string GetAetheryteName(AetheryteEntry aetheryte)
{
var gameData = aetheryte.AetheryteData.GameData;
var placeName = gameData?.PlaceName.Value;
return placeName == null ? "[Name Lookup Failed]" : placeName.Name;
}
private bool AetheryteUnlocked(ExcelRow aetheryte, out AetheryteEntry? entry)
{
if (Service.AetheryteList.Any(entry => entry.AetheryteId == aetheryte.RowId))
{
entry = Service.AetheryteList.First(entry => entry.AetheryteId == aetheryte.RowId);
return true;
}
else
{
entry = null;
return false;
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using Dalamud.Game;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
// ReSharper disable ConditionIsAlwaysTrueOrFalse
namespace KamiLib.UserInterface;
public unsafe class GameUserInterface : IDisposable
{
public event EventHandler? UiHidden;
public event EventHandler? UiShown;
private static GameUserInterface? _instance;
public static GameUserInterface Instance => _instance ??= new GameUserInterface();
public bool IsVisible => !lastState;
private bool lastState;
private GameUserInterface()
{
Service.Framework.Update += FrameworkUpdate;
}
public static void Cleanup()
{
_instance?.Dispose();
}
public void Dispose()
{
Service.Framework.Update -= FrameworkUpdate;
}
private void FrameworkUpdate(IFramework framework)
{
var partyList = (AtkUnitBase*) Service.GameGui.GetAddonByName("_PartyList", 1);
var todoList = (AtkUnitBase*) Service.GameGui.GetAddonByName("_ToDoList", 1);
var enemyList = (AtkUnitBase*) Service.GameGui.GetAddonByName("_EnemyList", 1);
var partyListVisible = partyList != null && partyList->IsVisible;
var todoListVisible = todoList != null && todoList->IsVisible;
var enemyListVisible = enemyList != null && enemyList->IsVisible;
var shouldHideUi = !partyListVisible && !todoListVisible && !enemyListVisible;
if (lastState != shouldHideUi)
{
if (shouldHideUi)
{
UiHidden?.Invoke(this, EventArgs.Empty);
}
else
{
UiShown?.Invoke(this, EventArgs.Empty);
}
}
lastState = shouldHideUi;
}
}

View File

@ -0,0 +1,19 @@
using ImGuiNET;
namespace KamiLib.Windows;
public static class DrawFlags
{
public const ImGuiWindowFlags AutoResize = ImGuiWindowFlags.NoFocusOnAppearing |
ImGuiWindowFlags.NoTitleBar |
ImGuiWindowFlags.NoScrollbar |
ImGuiWindowFlags.NoCollapse |
ImGuiWindowFlags.AlwaysAutoResize;
public const ImGuiWindowFlags ManualSize = ImGuiWindowFlags.NoFocusOnAppearing |
ImGuiWindowFlags.NoTitleBar |
ImGuiWindowFlags.NoCollapse;
public const ImGuiWindowFlags LockPosition = ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoResize;
}

View File

@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Numerics;
using ImGuiNET;
using KamiLib.Interfaces;
using KamiLib.Localization;
namespace KamiLib.Windows;
public class SelectionList
{
private ISelectable? Selected { get; set; }
public void Draw(IEnumerable<ISelectable> selectables)
{
var frameBgColor = ImGui.GetStyle().Colors[(int)ImGuiCol.FrameBg];
ImGui.PushStyleColor(ImGuiCol.FrameBg, frameBgColor with { W = 0.05f });
ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, 0.0f);
var listBoxValid = ImGui.BeginListBox("", new Vector2(-1, -1));
ImGui.PopStyleVar();
ImGui.PopStyleColor();
if (listBoxValid)
{
foreach (var item in selectables)
{
var headerHoveredColor = ImGui.GetStyle().Colors[(int)ImGuiCol.HeaderHovered];
var textSelectedColor = ImGui.GetStyle().Colors[(int)ImGuiCol.Header];
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, headerHoveredColor with { W = 0.1f });
ImGui.PushStyleColor(ImGuiCol.Header, textSelectedColor with { W = 0.1f });
if (ImGui.Selectable($"##SelectableID{item.ID}", Selected?.ID == item.ID))
{
Selected = Selected == item ? null : item;
}
ImGui.PopStyleColor();
ImGui.PopStyleColor();
ImGui.SameLine(3.0f);
item.DrawLabel();
ImGui.Spacing();
}
ImGui.EndListBox();
}
}
public void DrawSelected()
{
if (Selected is null)
{
var available = ImGui.GetContentRegionAvail() / 2.0f;
var textSize = ImGui.CalcTextSize(Strings.Selection_NoItemSelected) / 2.0f;
var center = new Vector2(available.X - textSize.X, available.Y - textSize.Y);
ImGui.SetCursorPos(center);
ImGui.TextWrapped(Strings.Selection_NoItemSelected);
}
else
{
Selected.Contents.Draw();
}
}
}

View File

@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using KamiLib.Drawing;
using KamiLib.Interfaces;
namespace KamiLib.Windows;
public abstract class SelectionWindow : Window, IDrawable
{
private readonly float horizontalWeight;
private readonly float verticalHeight;
private const bool ShowBorders = false;
private readonly SelectionList selectionList = new();
protected abstract IEnumerable<ISelectable> GetSelectables();
protected bool ShowScrollBar = true;
protected SelectionWindow(string windowName, float xPercent, float height) : base(windowName)
{
horizontalWeight = xPercent;
verticalHeight = height;
}
public override void Draw()
{
var region = ImGui.GetContentRegionAvail();
var itemSpacing = ImGui.GetStyle().ItemSpacing;
var leftSideWidth = region.X * horizontalWeight - itemSpacing.X / 2.0f;
var topLeftSideHeight = region.Y - verticalHeight * ImGuiHelpers.GlobalScale - itemSpacing.Y / 2.0f;
if(ImGui.BeginChild($"###{KamiCommon.PluginName}LeftSide", new Vector2( leftSideWidth, topLeftSideHeight), ShowBorders, ImGuiWindowFlags.NoDecoration))
{
selectionList.Draw(GetSelectables());
}
ImGui.EndChild();
var bottomLeftChildPosition = ImGui.GetCursorPos();
ImGui.SameLine();
DrawVerticalLine();
var rightSideWidth = region.X * (1.0f - horizontalWeight) - itemSpacing.X / 2.0f;
if(ImGui.BeginChild($"###{KamiCommon.PluginName}RightSide", new Vector2(rightSideWidth, 0), ShowBorders, (ShowScrollBar ? ImGuiWindowFlags.AlwaysVerticalScrollbar : ImGuiWindowFlags.None) | ImGuiWindowFlags.NoDecoration))
{
selectionList.DrawSelected();
}
ImGui.EndChild();
ImGui.SetCursorPos(bottomLeftChildPosition);
if(ImGui.BeginChild($"###{KamiCommon.PluginName}BottomLeftSide", new Vector2(leftSideWidth, verticalHeight * ImGuiHelpers.GlobalScale), ShowBorders, ImGuiWindowFlags.NoDecoration))
{
DrawExtras();
}
ImGui.EndChild();
DrawSpecial();
}
protected virtual void DrawExtras()
{
}
protected virtual void DrawSpecial()
{
}
private static void DrawVerticalLine()
{
var contentArea = ImGui.GetContentRegionAvail();
var itemSpacing = ImGui.GetStyle().ItemSpacing;
var cursor = new Vector2(ImGui.GetCursorScreenPos().X - itemSpacing.X / 2.0f, ImGui.GetCursorScreenPos().Y );
var drawList = ImGui.GetWindowDrawList();
var color = ImGui.GetColorU32(Colors.White);
drawList.AddLine(cursor, cursor with {Y = cursor.Y + contentArea.Y}, color, 1.0f);
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Interface.Windowing;
using KamiLib.ChatCommands;
namespace KamiLib.Windows;
public class WindowManager : IDisposable
{
private readonly WindowSystem windowSystem;
private readonly List<Window> windows = new();
public WindowManager()
{
windowSystem = new WindowSystem(KamiCommon.PluginName);
windows.ForEach(window => windowSystem.AddWindow(window));
Service.PluginInterface.UiBuilder.Draw += DrawUI;
Service.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
}
public void Dispose()
{
Service.PluginInterface.UiBuilder.Draw -= DrawUI;
Service.PluginInterface.UiBuilder.OpenConfigUi -= DrawConfigUI;
windowSystem.RemoveAllWindows();
}
public void AddWindow(Window newWindow)
{
if (windowSystem.Windows.All(w => w.WindowName != newWindow.WindowName))
{
windows.Add(newWindow);
windowSystem.AddWindow(newWindow);
}
}
public void AddConfigurationWindow<T>(T configWindow) where T : Window
{
windows.Add(configWindow);
windowSystem.AddWindow(configWindow);
KamiCommon.CommandManager.AddCommand(new OpenWindowCommand<T>(null, false, "Configuration"));
KamiCommon.CommandManager.AddCommand(new OpenWindowCommand<T>("silent", true, "Configuration"));
}
public void RemoveWindow(Window window)
{
windows.Remove(window);
windowSystem.RemoveWindow(window);
}
public IReadOnlyCollection<Window> GetWindows() => windows;
public T? GetWindowOfType<T>() => windows.OfType<T>().FirstOrDefault();
private void DrawUI() => windowSystem.Draw();
private void DrawConfigUI() => KamiCommon.CommandManager.OnCommand($"{KamiCommon.PluginName}", "silent");
}