main
Camille M 2022-01-05 15:46:15 +01:00 committed by GitHub
parent cd26385f6c
commit 577ced24a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 304 additions and 159 deletions

View File

@ -0,0 +1,14 @@
using System;
namespace CurrencyAlert
{
internal class DefaultThresholdAttribute : Attribute
{
public DefaultThresholdAttribute(int v)
{
Value = v;
}
public int Value { get; }
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace CurrencyAlert
{
internal class SlotAttribute : Attribute
{
public SlotAttribute(int v)
{
Value = v;
}
public int Value { get; }
}
}

View File

@ -1,32 +1,37 @@
using Dalamud.Configuration; using CurrencyAlert.Enum;
using Dalamud.Plugin; using Dalamud.Configuration;
using System; using Dalamud.Plugin;
using System;
namespace CurrencyAlert using System.Collections.Generic;
{
[Serializable]
public class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 1;
public bool PoeticsThresholdEnabled { get; set; } = true; namespace CurrencyAlert
public int PoeticsThreshold { get; set; } = 1500; {
public bool StormSealsThresholdEnabled { get; set; } = true; [Serializable]
public int StormSealsThreshold { get; set; } = 40000; public class Configuration : IPluginConfiguration
{
// the below exist just to make saving less cumbersome public int Version { get; set; } = 2;
[NonSerialized] public Dictionary<Currency, bool> AlertEnabled { get; set; } = new Dictionary<Currency, bool>();
private DalamudPluginInterface? pluginInterface; public Dictionary<Currency, int> Threshold { get; set; } = new Dictionary<Currency , int>();
public void Initialize(DalamudPluginInterface pluginInterface) [NonSerialized]
{ private DalamudPluginInterface? pluginInterface;
this.pluginInterface = pluginInterface;
} public void Initialize(DalamudPluginInterface pluginInterface)
{
public void Save() this.pluginInterface = pluginInterface;
{
this.pluginInterface!.SavePluginConfig(this); EnumHelper.Each<Currency>(currency =>
} {
} this.AlertEnabled[currency] = true;
} var defaultValue = EnumHelper.GetAttributeOfType<DefaultThresholdAttribute>(currency);
this.Threshold[currency] = defaultValue.Value;
});
}
public void Save()
{
this.pluginInterface!.SavePluginConfig(this);
}
}
}

View File

@ -3,10 +3,10 @@
<PropertyGroup> <PropertyGroup>
<Authors></Authors> <Authors></Authors>
<Company></Company> <Company></Company>
<Version>0.1.0.0</Version> <Version>0.2.0.0</Version>
<Description>Currency Alert</Description> <Description>Currency Alert</Description>
<Copyright></Copyright> <Copyright></Copyright>
<PackageProjectUrl>https://github.com/goatcorp/SamplePlugin</PackageProjectUrl> <PackageProjectUrl>https://github.com/Lharz/xiv-currency-alert</PackageProjectUrl>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CurrencyAlert.Enum
{
public enum Currency
{
[Slot(1), DefaultThreshold(75000)]
StormSeal,
[Slot(2), DefaultThreshold(75000)]
SerpentSeal,
[Slot(3), DefaultThreshold(75000)]
FlameSeal,
[Slot(4), DefaultThreshold(18000)]
WolfMark,
[Slot(6), DefaultThreshold(1500)]
TomestoneOfPoetics,
[Slot(8), DefaultThreshold(3500)]
AlliedSeal,
[Slot(10), DefaultThreshold(1500)]
TomestoneOfAstronomy
}
}

View File

@ -0,0 +1,32 @@
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CurrencyAlert
{
public static class EnumHelper
{
/// <summary>
/// Gets an attribute on an enum field value
/// </summary>
/// <typeparam name="T">The type of the attribute you want to retrieve</typeparam>
/// <param name="enumVal">The enum value</param>
/// <returns>The attribute of type T that exists on the enum value</returns>
/// <example><![CDATA[string desc = myEnumVariable.GetAttributeOfType<DescriptionAttribute>().Description;]]></example>
public static T GetAttributeOfType<T>(this System.Enum enumVal) where T : System.Attribute
{
var type = enumVal.GetType();
var memInfo = type.GetMember(enumVal.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(T), false);
return (attributes.Length > 0) ? (T)attributes[0] : null;
}
public static void Each<T>(Action<T> action)
{
foreach (var item in System.Enum.GetValues(typeof(T)))
action((T)item);
}
}
}

View File

@ -1,6 +1,9 @@
using Dalamud.Game.Command; using CurrencyAlert.Enum;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.Plugin; using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Client.Game;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
@ -15,7 +18,9 @@ namespace CurrencyAlert
private DalamudPluginInterface PluginInterface { get; init; } private DalamudPluginInterface PluginInterface { get; init; }
private CommandManager CommandManager { get; init; } private CommandManager CommandManager { get; init; }
private Configuration Configuration { get; init; } private Configuration Configuration { get; init; }
private PluginUI PluginUi { get; init; } private PluginUI PluginUI { get; init; }
[PluginService] public static ChatGui Chat { get; private set; } = null!;
public Plugin( public Plugin(
[RequiredVersion("1.0")] DalamudPluginInterface pluginInterface, [RequiredVersion("1.0")] DalamudPluginInterface pluginInterface,
@ -29,20 +34,20 @@ namespace CurrencyAlert
// you might normally want to embed resources and load them from the manifest stream // you might normally want to embed resources and load them from the manifest stream
var assemblyLocation = Assembly.GetExecutingAssembly().Location; var assemblyLocation = Assembly.GetExecutingAssembly().Location;
this.PluginUi = new PluginUI(this.Configuration); this.PluginUI = new PluginUI(this.Configuration);
this.CommandManager.AddHandler(commandName, new CommandInfo(OnCommand) this.CommandManager.AddHandler(commandName, new CommandInfo(OnCommand)
{ {
HelpMessage = "A useful message to display in /xlhelp" HelpMessage = "Lets you configure alert thresholds for various currencies"
}); });
this.PluginInterface.UiBuilder.Draw += DrawUI; this.PluginInterface.UiBuilder.Draw += DrawUI;
this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI; this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
} }
public void Dispose() public void Dispose()
{ {
this.PluginUi.Dispose(); this.PluginUI.Dispose();
this.CommandManager.RemoveHandler(commandName); this.CommandManager.RemoveHandler(commandName);
} }
@ -53,12 +58,34 @@ namespace CurrencyAlert
private void DrawUI() private void DrawUI()
{ {
this.PluginUi.Draw(); // TODO: move this logic elsewhere
unsafe
{
InventoryManager* inventoryManager = InventoryManager.Instance();
InventoryContainer* currencyContainer = inventoryManager->GetInventoryContainer(InventoryType.Currency);
EnumHelper.Each<Currency>(currency =>
{
var slot = EnumHelper.GetAttributeOfType<SlotAttribute>(currency).Value;
uint quantity = currencyContainer->GetInventorySlot((int)slot)->Quantity;
if (this.Configuration.AlertEnabled[currency] && quantity >= this.Configuration.Threshold[currency])
{
this.PluginUI.AlertVisible[currency] = true;
}
else
{
this.PluginUI.AlertVisible[currency] = false;
}
});
}
this.PluginUI.Draw();
} }
private void DrawConfigUI() private void DrawConfigUI()
{ {
this.PluginUi.SettingsVisible = true; this.PluginUI.SettingsVisible = true;
} }
} }
} }

View File

@ -1,20 +1,14 @@
using FFXIVClientStructs.FFXIV.Client.Game; using CurrencyAlert.Enum;
using Dalamud.Game.Gui;
using Dalamud.IoC;
using FFXIVClientStructs.FFXIV.Client.Game;
using ImGuiNET; using ImGuiNET;
using System; using System;
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
namespace CurrencyAlert namespace CurrencyAlert
{ {
public enum CurrencySlot
{
StormSeals = 1,
WolfMarks = 4,
Poetics = 6,
AlliedSeals = 8
};
// It is good to have this be disposable in general, in case you ever need it
// to do any cleanup
class PluginUI : IDisposable class PluginUI : IDisposable
{ {
private Configuration configuration; private Configuration configuration;
@ -22,103 +16,126 @@ namespace CurrencyAlert
private bool settingsVisible = false; private bool settingsVisible = false;
public bool SettingsVisible public bool SettingsVisible
{ {
get { return this.settingsVisible; } get { return settingsVisible; }
set { this.settingsVisible = value; } set { settingsVisible = value; }
} }
private bool debugVisible = false;
private bool poeticsAlertVisible = false; public bool DebugVisible
public bool PoeticsAlertVisible
{ {
get { return this.poeticsAlertVisible; } get { return debugVisible; }
set { this.poeticsAlertVisible = value; } set { debugVisible = value; }
} }
public Dictionary<Currency, bool> AlertVisible { get; set; } = new Dictionary<Currency, bool>();
public PluginUI(Configuration configuration) public PluginUI(Configuration configuration)
{ {
this.configuration = configuration; this.configuration = configuration;
EnumHelper.Each<Currency>(currency => this.AlertVisible[currency] = false);
} }
public void Dispose() public void Dispose()
{ {
} }
public void Draw() public void Draw()
{ {
// This is our only draw handler attached to UIBuilder, so it needs to be
// able to draw any windows we might have open.
// Each method checks its own visibility/state to ensure it only draws when
// it actually makes sense.
// There are other ways to do this, but it is generally best to keep the number of
// draw delegates as low as possible.
DrawMainWindow(); DrawMainWindow();
DrawSettingsWindow();
if (this.SettingsVisible)
DrawSettingsWindow();
if (this.DebugVisible)
DrawDebugWindow();
} }
public void DrawMainWindow() public void DrawMainWindow()
{ {
unsafe EnumHelper.Each<Currency>(currency =>
{ {
InventoryManager* inventoryManager = InventoryManager.Instance(); if (!this.AlertVisible[currency])
InventoryContainer* currencyContainer = inventoryManager->GetInventoryContainer(InventoryType.Currency);
// Poetics: 6
// Wolf Marks: 4
// Allied Seal: 8
// Company Seals: 1,2,3
uint poetics = currencyContainer->GetInventorySlot((int) CurrencySlot.Poetics)->Quantity;
bool poeticsThresholdEnabled = this.configuration.PoeticsThresholdEnabled;
uint poeticsThreshold = (uint) this.configuration.PoeticsThreshold;
PoeticsAlertVisible = poeticsThresholdEnabled && poetics >= poeticsThreshold;
if (!PoeticsAlertVisible)
{
return; return;
}
ImGui.SetNextWindowSize(new Vector2(375, 330), ImGuiCond.FirstUseEver); ImGui.SetNextWindowSize(new Vector2(375, 10), ImGuiCond.FirstUseEver);
ImGui.SetNextWindowSizeConstraints(new Vector2(375, 330), new Vector2(float.MaxValue, float.MaxValue)); ImGui.SetNextWindowSizeConstraints(new Vector2(375, 10), new Vector2(float.MaxValue, float.MaxValue));
if (ImGui.Begin("DEPENSE TES POETICS MERDE", ref this.poeticsAlertVisible, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.AlwaysAutoResize)) var isVisible = this.AlertVisible[currency];
if (ImGui.Begin("Currency Alert", ref isVisible,
ImGuiWindowFlags.NoScrollbar |
ImGuiWindowFlags.NoScrollWithMouse |
ImGuiWindowFlags.AlwaysAutoResize |
ImGuiWindowFlags.NoTitleBar |
ImGuiWindowFlags.NoFocusOnAppearing
))
{ {
ImGui.Text("DEPENSE TES POETICS MEEEEERDE"); ImGui.Text($"You need to spend your {currency}");
} }
ImGui.End(); ImGui.End();
} });
} }
public void DrawSettingsWindow() public void DrawSettingsWindow()
{ {
if (!SettingsVisible) ImGui.SetNextWindowSize(new Vector2(400, 600), ImGuiCond.Always);
{
return;
}
ImGui.SetNextWindowSize(new Vector2(330, 120), ImGuiCond.Always);
if (ImGui.Begin("Currency Alert Configuration Window", ref this.settingsVisible, if (ImGui.Begin("Currency Alert Configuration Window", ref this.settingsVisible,
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)) ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
{ {
var poeticsThresholdEnabledConfig = this.configuration.PoeticsThresholdEnabled; EnumHelper.Each<Currency>(currency =>
if (ImGui.Checkbox("Poetics Threshold Enabled", ref poeticsThresholdEnabledConfig))
{ {
this.configuration.PoeticsThresholdEnabled = poeticsThresholdEnabledConfig; var alertEnabled = this.configuration.AlertEnabled[currency];
this.configuration.Save();
}
var poeticsThresholdConfig = this.configuration.PoeticsThreshold; if (ImGui.Checkbox($"{currency.ToString()} Alert Enabled", ref alertEnabled))
{
this.configuration.AlertEnabled[currency] = alertEnabled;
this.configuration.Save();
}
if (ImGui.InputInt("Poetics Threshold", ref poeticsThresholdConfig, 1, 1, this.configuration.PoeticsThresholdEnabled ? ImGuiInputTextFlags.None : ImGuiInputTextFlags.ReadOnly)) var thresholdValue = this.configuration.Threshold[currency];
if (ImGui.InputInt($"{currency.ToString()} Threshold Value", ref thresholdValue, 1, 1,
this.configuration.AlertEnabled[currency] ? ImGuiInputTextFlags.None : ImGuiInputTextFlags.ReadOnly))
{
this.configuration.Threshold[currency] = thresholdValue;
this.configuration.Save();
}
});
}
#if DEBUG
if (ImGui.Button("Open Debug"))
this.DebugVisible = true;
#endif
ImGui.End();
}
public void DrawDebugWindow()
{
ImGui.SetNextWindowSize(new Vector2(375, 330), ImGuiCond.FirstUseEver);
ImGui.SetNextWindowSizeConstraints(new Vector2(375, 330), new Vector2(float.MaxValue, float.MaxValue));
if (ImGui.Begin("Currency Alert Debug", ref this.debugVisible))
{
unsafe
{ {
this.configuration.PoeticsThreshold = poeticsThresholdConfig; InventoryManager* inventoryManager = InventoryManager.Instance();
this.configuration.Save(); InventoryContainer* currencyContainer = inventoryManager->GetInventoryContainer(InventoryType.Currency);
for (var i = 0; i < 100000; ++i)
{
var item = currencyContainer->GetInventorySlot(i);
if (item == null)
continue;
ImGui.Text($"Index: {i} Value: {item->Quantity}");
}
} }
} }
ImGui.End(); ImGui.End();
} }
} }

View File

@ -1,8 +1,8 @@
{ {
"Author": "Lharz Zobby@Ragnarok", "Author": "Lharz Zobby@Ragnarok",
"Name": "Currency Alert", "Name": "Currency Alert",
"Punchline": "A short one-liner that shows up in /xlplugins.", "Punchline": "Configure alerts thresholds for various currencies.",
"Description": "A description that shows up in /xlplugins. List any major slash-command(s).", "Description": "/currencyalert: show the configuration panel.",
"InternalName": "currencyAlert", "InternalName": "currencyAlert",
"ApplicableVersion": "any", "ApplicableVersion": "any",
"Tags": [ "Tags": [

View File

@ -1,45 +1,49 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework> <TargetFramework>net5.0-windows</TargetFramework>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath> <DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json"> <Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath> <HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="ImGui.NET"> <Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath> <HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
</Reference> </Reference>
<Reference Include="ImGuiScene"> <Reference Include="ImGuiScene">
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath> <HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
</Reference> </Reference>
<Reference Include="SDL2-CS"> <Reference Include="SDL2-CS">
<HintPath>$(DalamudLibPath)SDL2-CS.dll</HintPath> <HintPath>$(DalamudLibPath)SDL2-CS.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="..\Data\gamesym.ttf"> <Content Include="..\Data\gamesym.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible> <Visible>false</Visible>
</Content> </Content>
<Content Include="..\Data\NotoSansCJKjp-Medium.otf"> <Content Include="..\Data\NotoSansCJKjp-Medium.otf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible> <Visible>false</Visible>
</Content> </Content>
<Content Include="..\Data\goat.png"> <Content Include="..\Data\goat.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible> <Visible>false</Visible>
</Content> </Content>
</ItemGroup> </ItemGroup>
</Project>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.5" />
</ItemGroup>
</Project>