194 lines
6.1 KiB
C#
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;
|
|
}
|
|
}
|