qstbak/Questionable/Questionable.Controller.Steps.Interactions/SinglePlayerDuty.cs
2025-10-09 08:41:52 +10:00

272 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Shared;
using Questionable.Data;
using Questionable.External;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Interactions;
internal static class SinglePlayerDuty
{
internal static class SpecialTerritories
{
public const ushort Lahabrea = 1052;
public const ushort ItsProbablyATrap = 665;
public const ushort Naadam = 688;
}
internal sealed class Factory(BossModIpc bossModIpc, TerritoryData territoryData, ICondition condition, IClientState clientState) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.SinglePlayerDuty)
{
yield break;
}
yield return new Mount.UnmountTask();
if (!bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions))
{
yield break;
}
if (!territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id, step.SinglePlayerDutyIndex, out TerritoryData.ContentFinderConditionData cfcData))
{
throw new TaskException("Failed to get content finder condition for solo instance");
}
yield return new StartSinglePlayerDuty(cfcData.ContentFinderConditionId);
yield return new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(2L));
yield return new EnableAi(cfcData.TerritoryId == 688);
if (cfcData.TerritoryId == 1052)
{
yield return new SetTarget(14643u);
yield return new WaitCondition.Task(() => condition[ConditionFlag.Unconscious] || clientState.TerritoryType != 1052, "Wait(death)");
yield return new DisableAi();
yield return new WaitCondition.Task(() => !condition[ConditionFlag.Unconscious] || clientState.TerritoryType != 1052, "Wait(resurrection)");
yield return new EnableAi();
}
else if (cfcData.TerritoryId == 665)
{
yield return new WaitCondition.Task(() => DutyActionsAvailable() || clientState.TerritoryType != 665, "Wait(Phase 2)");
yield return new EnableAi(Passive: true);
}
else if (cfcData.TerritoryId == 688)
{
yield return new WaitCondition.Task(delegate
{
if (clientState.TerritoryType != 688)
{
return true;
}
Vector3 vector = clientState.LocalPlayer?.Position ?? default(Vector3);
return (new Vector3(352.01f, -1.45f, 288.59f) - vector).Length() < 10f;
}, "Wait(moving to Ovoo)");
yield return new Mount.UnmountTask();
yield return new EnableAi();
}
yield return new WaitSinglePlayerDuty(cfcData.ContentFinderConditionId);
yield return new DisableAi();
yield return new WaitAtEnd.WaitNextStepOrSequence();
}
private unsafe bool DutyActionsAvailable()
{
ContentDirector* contentDirector = EventFramework.Instance()->GetContentDirector();
if (contentDirector != null)
{
return contentDirector->DutyActionManager.ActionsPresent;
}
return false;
}
}
internal sealed record StartSinglePlayerDuty(uint ContentFinderConditionId) : ITask
{
public override string ToString()
{
return $"Wait(BossMod, entered instance {ContentFinderConditionId})";
}
}
internal sealed class StartSinglePlayerDutyExecutor(ICondition condition) : TaskExecutor<StartSinglePlayerDuty>()
{
private DateTime _enteredAt = DateTime.MinValue;
protected override bool Start()
{
return true;
}
public unsafe override ETaskResult Update()
{
if (GameMain.Instance()->CurrentContentFinderConditionId != base.Task.ContentFinderConditionId)
{
return ETaskResult.StillRunning;
}
if (!condition[ConditionFlag.BoundByDuty])
{
return ETaskResult.StillRunning;
}
if (_enteredAt == DateTime.MinValue)
{
_enteredAt = DateTime.Now;
}
if (!(DateTime.Now - _enteredAt >= TimeSpan.FromSeconds(2L)))
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record EnableAi(bool Passive = false) : ITask
{
public override string ToString()
{
return "BossMod.EnableAi(" + (Passive ? "Passive" : "AutoPull") + ")";
}
}
internal sealed class EnableAiExecutor(BossModIpc bossModIpc) : TaskExecutor<EnableAi>()
{
protected override bool Start()
{
bossModIpc.EnableAi(base.Task.Passive);
return true;
}
public override ETaskResult Update()
{
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record WaitSinglePlayerDuty(uint ContentFinderConditionId) : ITask
{
public override string ToString()
{
return $"Wait(BossMod, left instance {ContentFinderConditionId})";
}
}
internal sealed class WaitSinglePlayerDutyExecutor(BossModIpc bossModIpc, MovementController movementController) : TaskExecutor<WaitSinglePlayerDuty>(), IStoppableTaskExecutor, ITaskExecutor, IDebugStateProvider
{
protected override bool Start()
{
return true;
}
public unsafe override ETaskResult Update()
{
if (GameMain.Instance()->CurrentContentFinderConditionId == base.Task.ContentFinderConditionId)
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
public void StopNow()
{
bossModIpc.DisableAi();
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
public string? GetDebugState()
{
if (!movementController.IsNavmeshReady)
{
return $"Navmesh: {movementController.BuiltNavmeshPercent}%";
}
return null;
}
}
internal sealed record DisableAi : ITask
{
public override string ToString()
{
return "BossMod.DisableAi";
}
}
internal sealed class DisableAiExecutor(BossModIpc bossModIpc) : TaskExecutor<DisableAi>()
{
protected override bool Start()
{
bossModIpc.DisableAi();
return true;
}
public override ETaskResult Update()
{
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record SetTarget(uint DataId) : ITask
{
public override string ToString()
{
return $"SetTarget({DataId})";
}
}
internal sealed class SetTargetExecutor(ITargetManager targetManager, IObjectTable objectTable) : TaskExecutor<SetTarget>()
{
protected override bool Start()
{
return true;
}
public override ETaskResult Update()
{
if (targetManager.Target?.BaseId == base.Task.DataId)
{
return ETaskResult.TaskComplete;
}
IGameObject gameObject = objectTable.FirstOrDefault((IGameObject x) => x.BaseId == base.Task.DataId);
if (gameObject == null)
{
return ETaskResult.StillRunning;
}
targetManager.Target = gameObject;
return ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}