using System; using System.Collections.Generic; using System.Linq; using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Shared; using Questionable.Controller.Utils; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; internal static class Combat { internal sealed class Factory(GameFunctions gameFunctions) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.InteractionType != EInteractionType.Combat) { yield break; } ArgumentNullException.ThrowIfNull(step.EnemySpawnType, "step.EnemySpawnType"); if (gameFunctions.GetMountId() != 128 && gameFunctions.GetMountId() != 147) { yield return new Mount.UnmountTask(); } if (step.CombatDelaySecondsAtStart.HasValue) { yield return new WaitAtStart.WaitDelay(TimeSpan.FromSeconds(step.CombatDelaySecondsAtStart.Value)); } switch (step.EnemySpawnType) { case EEnemySpawnType.AfterInteraction: ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId"); yield return new Interact.Task(step.DataId.Value, quest, EInteractionType.None, SkipMarkerCheck: true); yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L)); yield return CreateTask(quest, sequence, step); break; case EEnemySpawnType.AfterItemUse: ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId"); if (step.GroundTarget == true) { if (step.DataId.HasValue) { yield return new UseItem.UseOnGround(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags, StartingCombat: true); } else { ArgumentNullException.ThrowIfNull(step.Position, "step.Position"); yield return new UseItem.UseOnPosition(quest.Id, step.Position.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags, StartingCombat: true); } } else if (step.DataId.HasValue) { yield return new UseItem.UseOnObject(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags, StartingCombat: true); } else { yield return new UseItem.UseOnSelf(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags, StartingCombat: true); } yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L)); yield return CreateTask(quest, sequence, step); break; case EEnemySpawnType.AfterAction: ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId"); ArgumentNullException.ThrowIfNull(step.Action, "step.Action"); if (!step.Action.Value.RequiresMount()) { yield return new Mount.UnmountTask(); } yield return new Action.UseOnObject(step.DataId.Value, null, step.Action.Value, null); yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L)); yield return CreateTask(quest, sequence, step); break; case EEnemySpawnType.AfterEmote: ArgumentNullException.ThrowIfNull(step.Emote, "step.Emote"); yield return new Mount.UnmountTask(); if (step.DataId.HasValue) { yield return new Emote.UseOnObject(step.Emote.Value, step.DataId.Value); } else { yield return new Emote.UseOnSelf(step.Emote.Value); } yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L)); yield return CreateTask(quest, sequence, step); break; case EEnemySpawnType.AutoOnEnterArea: if (!step.CombatDelaySecondsAtStart.HasValue) { yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1L)); } yield return CreateTask(quest, sequence, step); break; case EEnemySpawnType.OverworldEnemies: case EEnemySpawnType.FateEnemies: yield return CreateTask(quest, sequence, step); break; case EEnemySpawnType.FinishCombatIfAny: yield return CreateTask(quest, sequence, step); break; default: throw new ArgumentOutOfRangeException("step", $"Unknown spawn type {step.EnemySpawnType}"); } } private static Task CreateTask(Quest quest, QuestSequence sequence, QuestStep step) { ArgumentNullException.ThrowIfNull(step.EnemySpawnType, "step.EnemySpawnType"); bool isLastStep = sequence.Steps.Last() == step; return CreateTask(quest.Id, sequence.Sequence, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds, step.CompletionQuestVariablesFlags, step.ComplexCombatData, step.CombatItemUse); } internal static Task CreateTask(ElementId? elementId, int sequence, bool isLastStep, EEnemySpawnType enemySpawnType, IList killEnemyDataIds, IList completionQuestVariablesFlags, IList complexCombatData, CombatItemUse? combatItemUse) { return new Task(new CombatController.CombatData { ElementId = elementId, Sequence = sequence, CompletionQuestVariablesFlags = completionQuestVariablesFlags, SpawnType = enemySpawnType, KillEnemyDataIds = killEnemyDataIds.ToList(), ComplexCombatDatas = complexCombatData.ToList(), CombatItemUse = combatItemUse }, completionQuestVariablesFlags, isLastStep); } } internal sealed record Task(CombatController.CombatData CombatData, IList CompletionQuestVariableFlags, bool IsLastStep) : ITask { public override string ToString() { if (CombatData.SpawnType == EEnemySpawnType.FinishCombatIfAny) { return "HandleCombat(wait: not in combat, optional)"; } if (QuestWorkUtils.HasCompletionFlags(CompletionQuestVariableFlags)) { return "HandleCombat(wait: QW flags)"; } if (IsLastStep) { return "HandleCombat(wait: next sequence)"; } return "HandleCombat(wait: not in combat)"; } } internal sealed class HandleCombat(CombatController combatController, QuestFunctions questFunctions) : TaskExecutor() { private CombatController.EStatus _status; protected override bool Start() { return combatController.Start(base.Task.CombatData); } public override ETaskResult Update() { _status = combatController.Update(); if (_status != CombatController.EStatus.Complete) { return ETaskResult.StillRunning; } if (QuestWorkUtils.HasCompletionFlags(base.Task.CompletionQuestVariableFlags) && base.Task.CombatData.ElementId is QuestId elementId) { QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(elementId); if (questProgressInfo == null) { return ETaskResult.StillRunning; } if (QuestWorkUtils.MatchesQuestWork(base.Task.CompletionQuestVariableFlags, questProgressInfo)) { return ETaskResult.TaskComplete; } return ETaskResult.StillRunning; } if (base.Task.IsLastStep) { return ETaskResult.StillRunning; } combatController.Stop("Combat task complete"); return ETaskResult.TaskComplete; } public override bool ShouldInterruptOnDamage() { return false; } } }