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 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() { 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() { 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(), 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() { 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() { protected override bool Start() { return true; } public override ETaskResult Update() { if (targetManager.Target?.DataId == base.Task.DataId) { return ETaskResult.TaskComplete; } IGameObject gameObject = objectTable.FirstOrDefault((IGameObject x) => x.DataId == base.Task.DataId); if (gameObject == null) { return ETaskResult.StillRunning; } targetManager.Target = gameObject; return ETaskResult.StillRunning; } public override bool ShouldInterruptOnDamage() { return false; } } }