1
0
Fork 0
forked from aly/qstbak
qstbak/Questionable/Questionable.Controller.Steps.Shared/Fish.cs
2026-08-17 20:29:32 +10:00

194 lines
6.1 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using LLib.Fishing;
using LLib.GameData;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Utils;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Shared;
internal static class Fish
{
internal sealed class Factory(AutoHookIpc autoHookIpc) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.Fish || !autoHookIpc.IsAvailable())
{
yield break;
}
yield return new Mount.UnmountTask();
yield return new SwitchClassJob.Task(EClassJob.Fisher);
yield return new FishTask(quest, step.ItemsToGather.FirstOrDefault(), step.CompletionQuestVariablesFlags);
foreach (GatheredItem item in step.ItemsToGather.Skip(1))
{
yield return new FishTask(quest, item, step.CompletionQuestVariablesFlags);
}
}
}
internal sealed record FishTask(Quest Quest, GatheredItem? GatheredItem, IList<QuestWorkValue?> CompletionQuestVariablesFlags) : ITask
{
public bool HasCompletionQuestVariablesFlags { get; } = QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags);
public override string ToString()
{
return "Fish" + (HasCompletionQuestVariablesFlags ? "*" : "") + ((GatheredItem != null) ? $"({GatheredItem.ItemCount}x {GatheredItem.ItemId})" : "");
}
}
internal sealed class DoFish(AutoHookIpc autoHookIpc, QuestFunctions questFunctions, IObjectTable objectTable, IChatGui chatGui, SendNotification.Executor sendNotificationExecutor, ILogger<DoFish> logger) : TaskExecutor<FishTask>(), IStoppableTaskExecutor, ITaskExecutor
{
private readonly bool _wasAutoHookEnabled = autoHookIpc.GetPluginState();
private bool _started;
private bool _cleanupDone;
protected override bool Start()
{
if (HasRequestedItem(base.Task.GatheredItem))
{
logger.LogInformation("Already have the requested fish in inventory, skipping fish task.");
return false;
}
if (HasMatchingCompletionQuestWork())
{
logger.LogInformation("Quest variables already match, skipping fish task.");
return false;
}
if (!_wasAutoHookEnabled)
{
autoHookIpc.SetPluginState(enabled: true);
if (!autoHookIpc.GetPluginState())
{
logger.LogWarning("{ErrorText}", "AutoHook is required for fishing but could not be enabled. Please install or enable AutoHook.");
if (!sendNotificationExecutor.Start(new SendNotification.Task(EInteractionType.Fish, "AutoHook is required for fishing but could not be enabled. Please install or enable AutoHook.")))
{
chatGui.PrintError("AutoHook is required for fishing but could not be enabled. Please install or enable AutoHook.", "Questionable", 576);
}
throw new TaskException("AutoHook is required for fishing but could not be enabled. Please install or enable AutoHook.");
}
}
if (!AutoHookPresetData.FishingPresets.TryGetValue((QuestId)base.Task.Quest.Id, out string value))
{
logger.LogWarning("No fishing preset found for quest {QuestId}", base.Task.Quest.Id);
throw new TaskException($"No fishing preset found for quest {base.Task.Quest.Id}");
}
logger.LogInformation("Creating anonymous AutoHook preset for quest {QuestId}", base.Task.Quest.Id);
autoHookIpc.CreateAndSelectAnonymousPreset(value);
OrientToWater();
autoHookIpc.SetAutoStartFishing(enabled: true);
_started = true;
return true;
}
public override ETaskResult Update()
{
if (HasRequestedItem(base.Task.GatheredItem))
{
logger.LogDebug("Requested fish collected, completing task.");
Cleanup();
return ETaskResult.TaskComplete;
}
if (HasMatchingCompletionQuestWork())
{
logger.LogDebug("Quest variables match, completing task.");
Cleanup();
return ETaskResult.TaskComplete;
}
return ETaskResult.StillRunning;
}
public void StopNow()
{
if (_started)
{
Cleanup();
}
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
private bool HasMatchingCompletionQuestWork()
{
if (!base.Task.HasCompletionQuestVariablesFlags)
{
return false;
}
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(base.Task.Quest.Id);
if (questProgressInfo != null)
{
return QuestWorkUtils.MatchesQuestWork(base.Task.CompletionQuestVariablesFlags, questProgressInfo);
}
return false;
}
private void Cleanup()
{
if (!_cleanupDone)
{
logger.LogDebug("Cleaning up fish task.");
autoHookIpc.SetAutoStartFishing(enabled: false);
StopFishing();
autoHookIpc.DeleteAllAnonymousPresets();
autoHookIpc.SetPluginState(_wasAutoHookEnabled);
_cleanupDone = true;
}
}
private unsafe void OrientToWater()
{
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null)
{
(float, Vector3)? tuple = WaterSurfaceDetector.FindFishableRotation(localPlayer.Position);
if (!tuple.HasValue)
{
logger.LogWarning("No fishable water found near the player; AutoHook may fail to cast.");
return;
}
Vector3 item = tuple.Value.Item2;
ActionManager.Instance()->AutoFaceTargetPosition(&item, 0uL);
}
}
private unsafe static void StopFishing()
{
ActionManager* ptr = ActionManager.Instance();
if (ptr != null)
{
ptr->UseAction(ActionType.Action, 299u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
}
}
}
private const uint QuitActionId = 299u;
private unsafe static bool HasRequestedItem(GatheredItem? item)
{
if (item == null)
{
return false;
}
InventoryManager* ptr = InventoryManager.Instance();
if (ptr == null)
{
return false;
}
return ptr->GetInventoryItemCount(item.ItemId, isHq: false, checkEquipped: true, checkArmory: true, (short)item.Collectability) >= item.ItemCount;
}
}