Files
xiv-plugins/AutoFishingStool/Plugin.cs

318 lines
9.4 KiB
C#

using System;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Configuration;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
namespace AutoFishingStool;
public sealed unsafe class Plugin : IDalamudPlugin
{
private enum PostureAction
{
None,
Sit,
Stand,
}
private const int SitDelayMilliseconds = 2500;
private const int ActionSettleMilliseconds = 500;
private const uint FisherClassJobId = 18;
[PluginService]
internal static IDalamudPluginInterface PluginInterface { get; private set; } = null!;
[PluginService]
internal static ICondition Condition { get; private set; } = null!;
[PluginService]
internal static IFramework Framework { get; private set; } = null!;
[PluginService]
internal static IPlayerState PlayerState { get; private set; } = null!;
[PluginService]
internal static IPluginLog Log { get; private set; } = null!;
private readonly Configuration configuration;
private DateTime? postureAt;
private PostureAction pendingPosture;
private bool satByPlugin;
private bool stoodForSpectral;
private bool spectralCurrentActive;
private bool waitingForActionToFinish;
private bool configWindowOpen;
public Plugin()
{
configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
spectralCurrentActive = IsSpectralCurrentActive();
Condition.ConditionChange += OnConditionChange;
Framework.Update += OnFrameworkUpdate;
PluginInterface.UiBuilder.Draw += DrawConfigWindow;
PluginInterface.UiBuilder.OpenConfigUi += OpenConfigWindow;
PluginInterface.UiBuilder.OpenMainUi += OpenConfigWindow;
Log.Information("Auto Fishing Stool loaded");
}
private void OnConditionChange(ConditionFlag flag, bool value)
{
if (flag == ConditionFlag.Fishing)
{
Log.Information("Fishing condition changed to {Value}", value);
if (value && Condition[ConditionFlag.Gathering] && IsFisher() && !satByPlugin && !ShouldStandForSpectral())
{
SchedulePosture(PostureAction.Sit, SitDelayMilliseconds);
Log.Information("Fishing became active; stable Sit timer started");
}
else if (!value && !satByPlugin)
{
CancelPendingPosture();
Log.Information("Fishing became inactive; stable Sit timer reset");
}
return;
}
if (flag != ConditionFlag.Gathering)
return;
Log.Information("Gathering condition changed to {Value}", value);
if (value)
{
if (!IsFisher())
return;
satByPlugin = false;
stoodForSpectral = false;
CancelPendingPosture();
Log.Information("Fisher gathering session started");
return;
}
satByPlugin = false;
stoodForSpectral = false;
CancelPendingPosture();
}
private void OnFrameworkUpdate(IFramework framework)
{
UpdateSpectralCurrentState();
if (pendingPosture == PostureAction.None || postureAt is null)
return;
if (!Condition[ConditionFlag.Gathering] || !Condition[ConditionFlag.Fishing] || !IsFisher())
{
CancelPendingPosture();
return;
}
if (pendingPosture == PostureAction.Sit && ShouldStandForSpectral())
{
CancelPendingPosture();
return;
}
if (pendingPosture == PostureAction.Stand && !ShouldStandForSpectral())
{
CancelPendingPosture();
return;
}
if (DateTime.UtcNow < postureAt.Value)
return;
if (Condition[ConditionFlag.ExecutingGatheringAction])
{
if (!waitingForActionToFinish)
{
waitingForActionToFinish = true;
Log.Information("Sit timer elapsed during a fishing action; waiting for it to finish");
}
return;
}
if (waitingForActionToFinish)
{
waitingForActionToFinish = false;
postureAt = DateTime.UtcNow.AddMilliseconds(ActionSettleMilliseconds);
Log.Information("Fishing action finished; posture command scheduled after settling");
return;
}
var completedPosture = pendingPosture;
var uiModule = UIModule.Instance();
if (uiModule is null)
{
Log.Warning("Could not access the game's UI module");
CancelPendingPosture();
return;
}
var command = Utf8String.FromString("/sit");
try
{
uiModule->ProcessChatBoxEntry(command);
Log.Information("Submitted /sit to {Posture}", completedPosture);
}
finally
{
command->Dtor(true);
}
if (completedPosture == PostureAction.Sit)
{
satByPlugin = true;
stoodForSpectral = false;
}
else
{
satByPlugin = false;
stoodForSpectral = true;
}
CancelPendingPosture();
}
private void UpdateSpectralCurrentState()
{
var active = IsSpectralCurrentActive();
if (active == spectralCurrentActive)
return;
spectralCurrentActive = active;
Log.Information("Spectral Current changed to {Value}", active);
if (!configuration.StandDuringSpectralCurrents)
return;
if (active)
{
if (pendingPosture == PostureAction.Sit)
CancelPendingPosture();
if (satByPlugin)
SchedulePosture(PostureAction.Stand, 0);
return;
}
if (stoodForSpectral && Condition[ConditionFlag.Gathering] && Condition[ConditionFlag.Fishing] && IsFisher())
SchedulePosture(PostureAction.Sit, ActionSettleMilliseconds);
}
private static bool IsSpectralCurrentActive()
{
try
{
var eventFramework = EventFramework.Instance();
var oceanFishing = eventFramework is null ? null : eventFramework->GetInstanceContentOceanFishing();
return oceanFishing is not null && oceanFishing->SpectralCurrentActive;
}
catch (Exception exception)
{
Log.Warning(exception, "Could not read Ocean Fishing state");
return false;
}
}
private bool ShouldStandForSpectral()
{
return configuration.StandDuringSpectralCurrents && spectralCurrentActive;
}
private void SchedulePosture(PostureAction posture, int delayMilliseconds)
{
pendingPosture = posture;
postureAt = DateTime.UtcNow.AddMilliseconds(delayMilliseconds);
waitingForActionToFinish = false;
}
private void CancelPendingPosture()
{
pendingPosture = PostureAction.None;
postureAt = null;
waitingForActionToFinish = false;
}
private void OpenConfigWindow()
{
configWindowOpen = true;
}
private void DrawConfigWindow()
{
if (!configWindowOpen)
return;
ImGui.SetNextWindowSize(new Vector2(430, 0), ImGuiCond.FirstUseEver);
if (!ImGui.Begin("Auto Fishing Stool Settings###AutoFishingStoolSettings", ref configWindowOpen, ImGuiWindowFlags.AlwaysAutoResize))
{
ImGui.End();
return;
}
var standDuringSpectralCurrents = configuration.StandDuringSpectralCurrents;
if (ImGui.Checkbox("Stand during Spectral Currents", ref standDuringSpectralCurrents))
{
configuration.StandDuringSpectralCurrents = standDuringSpectralCurrents;
PluginInterface.SavePluginConfig(configuration);
ApplySpectralSettingChange();
}
ImGui.TextWrapped("Stand while a Spectral Current is active, then sit again when it ends.");
ImGui.Separator();
ImGui.Text($"Spectral Current detected: {(spectralCurrentActive ? "Yes" : "No")}");
ImGui.End();
}
private void ApplySpectralSettingChange()
{
if (!spectralCurrentActive)
return;
if (configuration.StandDuringSpectralCurrents)
{
if (pendingPosture == PostureAction.Sit)
CancelPendingPosture();
if (satByPlugin)
SchedulePosture(PostureAction.Stand, 0);
return;
}
if (pendingPosture == PostureAction.Stand)
CancelPendingPosture();
if (stoodForSpectral && Condition[ConditionFlag.Gathering] && Condition[ConditionFlag.Fishing] && IsFisher())
SchedulePosture(PostureAction.Sit, ActionSettleMilliseconds);
}
private static bool IsFisher()
{
return PlayerState.IsLoaded && PlayerState.ClassJob.RowId == FisherClassJobId;
}
public void Dispose()
{
Condition.ConditionChange -= OnConditionChange;
Framework.Update -= OnFrameworkUpdate;
PluginInterface.UiBuilder.Draw -= DrawConfigWindow;
PluginInterface.UiBuilder.OpenConfigUi -= OpenConfigWindow;
PluginInterface.UiBuilder.OpenMainUi -= OpenConfigWindow;
}
}