using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.IoC; using Dalamud.Plugin; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI; namespace AutoFishingStool; public sealed unsafe class Plugin : IDalamudPlugin { private const int SitDelayMilliseconds = 2500; private const int ActionSettleMilliseconds = 500; private const uint FisherClassJobId = 18; [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 DateTime? sitAt; private bool satThisSession; private bool waitingForActionToFinish; public Plugin() { Condition.ConditionChange += OnConditionChange; Framework.Update += OnFrameworkUpdate; 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() && !satThisSession) { sitAt = DateTime.UtcNow.AddMilliseconds(SitDelayMilliseconds); waitingForActionToFinish = false; Log.Information("Fishing became active; stable Sit timer started"); } else if (!value && !satThisSession) { sitAt = null; waitingForActionToFinish = false; 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; satThisSession = false; sitAt = null; waitingForActionToFinish = false; Log.Information("Fisher gathering session started"); return; } sitAt = null; satThisSession = false; waitingForActionToFinish = false; } private void OnFrameworkUpdate(IFramework framework) { if (satThisSession || sitAt is null) return; if (!Condition[ConditionFlag.Gathering] || !Condition[ConditionFlag.Fishing] || !IsFisher()) { sitAt = null; satThisSession = false; waitingForActionToFinish = false; return; } if (DateTime.UtcNow < sitAt.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; sitAt = DateTime.UtcNow.AddMilliseconds(ActionSettleMilliseconds); Log.Information("Fishing action finished; Sit command scheduled after settling"); return; } var uiModule = UIModule.Instance(); if (uiModule is null) { Log.Warning("Could not access the game's UI module"); sitAt = null; return; } var command = Utf8String.FromString("/sit"); try { uiModule->ProcessChatBoxEntry(command); Log.Information("Submitted /sit after fishing started"); } finally { command->Dtor(true); } satThisSession = true; sitAt = null; waitingForActionToFinish = false; } private static bool IsFisher() { return PlayerState.IsLoaded && PlayerState.ClassJob.RowId == FisherClassJobId; } public void Dispose() { Condition.ConditionChange -= OnConditionChange; Framework.Update -= OnFrameworkUpdate; } }