343 lines
9.7 KiB
C#
343 lines
9.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using Dalamud.Game.ClientState.Objects.Enums;
|
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
using Dalamud.Game.ClientState.Objects.Types;
|
|
using Dalamud.Plugin.Services;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Functions;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
|
|
namespace Questionable.Controller.Steps.Interactions;
|
|
|
|
internal static class ClearObjectsWithAction
|
|
{
|
|
internal sealed class Factory : ITaskFactory
|
|
{
|
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
|
{
|
|
if (step.InteractionType != EInteractionType.Action)
|
|
{
|
|
return Array.Empty<ITask>();
|
|
}
|
|
if (step.DataIds.Count == 0)
|
|
{
|
|
return Array.Empty<ITask>();
|
|
}
|
|
if (!step.Action.HasValue)
|
|
{
|
|
return Array.Empty<ITask>();
|
|
}
|
|
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(new ClearTask(step.DataIds, step.Action.Value, step.TerritoryId, step.CalculateActualStopDistance(), step.WaypointPositions));
|
|
}
|
|
}
|
|
|
|
internal sealed record ClearTask(List<uint> DataIds, EAction Action, ushort TerritoryId, float StopDistance, List<Vector3> WaypointPositions) : ITask
|
|
{
|
|
public bool ShouldRedoOnInterrupt()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"ClearObjects({Action}, {DataIds.Count} types)";
|
|
}
|
|
}
|
|
|
|
internal sealed class ClearTaskExecutor(MovementController movementController, GameFunctions gameFunctions, IClientState clientState, IObjectTable objectTable, ILogger<ClearTaskExecutor> logger) : TaskExecutor<ClearTask>()
|
|
{
|
|
private enum State
|
|
{
|
|
FindingObject,
|
|
MovingToObject,
|
|
UsingAction,
|
|
WaitingAfterAction,
|
|
MovingToWaypoint,
|
|
WaitingForTransition
|
|
}
|
|
|
|
private const float VisitedPositionRadius = 5f;
|
|
|
|
private State _state;
|
|
|
|
private long? _noObjectFoundSince;
|
|
|
|
private long _waitUntil;
|
|
|
|
private long? _waypointReachedAt;
|
|
|
|
private Vector3 _lastPlayerPosition;
|
|
|
|
private readonly List<Vector3> _visitedPositions = new List<Vector3>();
|
|
|
|
private readonly List<int> _triedWaypointIndices = new List<int>();
|
|
|
|
protected override bool Start()
|
|
{
|
|
if (clientState.TerritoryType != base.Task.TerritoryId)
|
|
{
|
|
return true;
|
|
}
|
|
return TryFindAndMove();
|
|
}
|
|
|
|
private bool IsVisitedPosition(Vector3 position)
|
|
{
|
|
foreach (Vector3 visitedPosition in _visitedPositions)
|
|
{
|
|
if (Vector3.Distance(visitedPosition, position) <= 5f)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private IGameObject? FindNearestUnvisitedObject()
|
|
{
|
|
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
|
|
if (localPlayer == null)
|
|
{
|
|
return null;
|
|
}
|
|
IGameObject result = null;
|
|
float num = float.MaxValue;
|
|
foreach (IGameObject item in objectTable)
|
|
{
|
|
ObjectKind objectKind = item.ObjectKind;
|
|
bool flag = ((objectKind == ObjectKind.Pc || objectKind - 8 <= ObjectKind.BattleNpc || objectKind == ObjectKind.HousingEventObject) ? true : false);
|
|
if (!flag && base.Task.DataIds.Contains(item.BaseId) && !IsVisitedPosition(item.Position))
|
|
{
|
|
float num2 = Vector3.Distance(localPlayer.Position, item.Position);
|
|
if (num2 < num)
|
|
{
|
|
result = item;
|
|
num = num2;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private bool AnyMatchingObjectsExist()
|
|
{
|
|
foreach (IGameObject item in objectTable)
|
|
{
|
|
ObjectKind objectKind = item.ObjectKind;
|
|
bool flag = ((objectKind == ObjectKind.Pc || objectKind - 8 <= ObjectKind.BattleNpc || objectKind == ObjectKind.HousingEventObject) ? true : false);
|
|
if (!flag && base.Task.DataIds.Contains(item.BaseId))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool TryFindAndMove()
|
|
{
|
|
IGameObject gameObject = FindNearestUnvisitedObject();
|
|
if (gameObject == null)
|
|
{
|
|
_state = State.FindingObject;
|
|
long valueOrDefault = _noObjectFoundSince.GetValueOrDefault();
|
|
if (!_noObjectFoundSince.HasValue)
|
|
{
|
|
valueOrDefault = Environment.TickCount64;
|
|
_noObjectFoundSince = valueOrDefault;
|
|
}
|
|
return true;
|
|
}
|
|
_noObjectFoundSince = null;
|
|
Vector3 position = gameObject.Position;
|
|
logger.LogDebug("Moving to object {DataId} at {Position}", gameObject.BaseId, position);
|
|
movementController.NavigateTo(EMovementType.Quest, gameObject.BaseId, position, fly: false, sprint: true, base.Task.StopDistance);
|
|
_state = State.MovingToObject;
|
|
return true;
|
|
}
|
|
|
|
private (Vector3 Position, int Index)? FindNextWaypoint()
|
|
{
|
|
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
|
|
if (localPlayer == null || base.Task.WaypointPositions.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
(Vector3, int)? result = null;
|
|
float num = float.MaxValue;
|
|
for (int i = 0; i < base.Task.WaypointPositions.Count; i++)
|
|
{
|
|
if (!_triedWaypointIndices.Contains(i))
|
|
{
|
|
float num2 = Vector3.Distance(localPlayer.Position, base.Task.WaypointPositions[i]);
|
|
if (num2 < num)
|
|
{
|
|
result = (base.Task.WaypointPositions[i], i);
|
|
num = num2;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void NavigateToNextWaypoint()
|
|
{
|
|
(Vector3, int)? tuple = FindNextWaypoint();
|
|
if (!tuple.HasValue)
|
|
{
|
|
logger.LogDebug("All waypoints tried without transition, resetting");
|
|
_triedWaypointIndices.Clear();
|
|
tuple = FindNextWaypoint();
|
|
if (!tuple.HasValue)
|
|
{
|
|
logger.LogWarning("No waypoint positions configured, waiting for transition");
|
|
_state = State.WaitingForTransition;
|
|
_lastPlayerPosition = objectTable.LocalPlayer?.Position ?? Vector3.Zero;
|
|
return;
|
|
}
|
|
}
|
|
_triedWaypointIndices.Add(tuple.Value.Item2);
|
|
logger.LogDebug("Moving to waypoint {Index} at {Position}", tuple.Value.Item2, tuple.Value.Item1);
|
|
movementController.NavigateTo(EMovementType.Quest, null, tuple.Value.Item1, fly: false, sprint: true, 0f);
|
|
_state = State.MovingToWaypoint;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
if (clientState.TerritoryType != base.Task.TerritoryId)
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
switch (_state)
|
|
{
|
|
case State.FindingObject:
|
|
{
|
|
long? waypointReachedAt = _noObjectFoundSince;
|
|
if (waypointReachedAt.HasValue)
|
|
{
|
|
long valueOrDefault2 = waypointReachedAt.GetValueOrDefault();
|
|
if (Environment.TickCount64 > valueOrDefault2 + 3000)
|
|
{
|
|
if (AnyMatchingObjectsExist())
|
|
{
|
|
logger.LogDebug("Matching objects still exist, clearing visited positions and retrying");
|
|
_visitedPositions.Clear();
|
|
_noObjectFoundSince = null;
|
|
}
|
|
else
|
|
{
|
|
logger.LogDebug("No matching objects remain, moving to waypoint");
|
|
NavigateToNextWaypoint();
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
}
|
|
TryFindAndMove();
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
case State.MovingToObject:
|
|
{
|
|
if (movementController.IsPathfinding || movementController.IsPathRunning)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (!movementController.HasBeenMovingForAtLeast(2000))
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
IGameObject gameObject = FindNearestUnvisitedObject();
|
|
IPlayerCharacter localPlayer2 = objectTable.LocalPlayer;
|
|
if (gameObject != null && localPlayer2 != null && Vector3.Distance(localPlayer2.Position, gameObject.Position) <= 5f)
|
|
{
|
|
_visitedPositions.Add(gameObject.Position);
|
|
_state = State.UsingAction;
|
|
}
|
|
else
|
|
{
|
|
logger.LogDebug("Target no longer valid at destination, finding next");
|
|
TryFindAndMove();
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
case State.UsingAction:
|
|
if (gameFunctions.UseAction(base.Task.Action))
|
|
{
|
|
logger.LogDebug("Used action {Action}", base.Task.Action);
|
|
_waitUntil = Environment.TickCount64 + 1000;
|
|
_state = State.WaitingAfterAction;
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
case State.WaitingAfterAction:
|
|
if (Environment.TickCount64 < _waitUntil)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
TryFindAndMove();
|
|
return ETaskResult.StillRunning;
|
|
case State.MovingToWaypoint:
|
|
if (movementController.IsPathfinding || movementController.IsPathRunning)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (!movementController.HasBeenMovingForAtLeast(2000))
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
logger.LogDebug("Reached waypoint, waiting for zone transition");
|
|
_state = State.WaitingForTransition;
|
|
_lastPlayerPosition = objectTable.LocalPlayer?.Position ?? Vector3.Zero;
|
|
_waypointReachedAt = Environment.TickCount64;
|
|
return ETaskResult.StillRunning;
|
|
case State.WaitingForTransition:
|
|
{
|
|
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
|
|
if (localPlayer != null)
|
|
{
|
|
float num = Vector3.Distance(_lastPlayerPosition, localPlayer.Position);
|
|
if (num > 50f)
|
|
{
|
|
logger.LogDebug("Zone transition detected (moved {Distance}), resuming plant clearing", num);
|
|
ResetSearchState();
|
|
_state = State.FindingObject;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
}
|
|
if (FindNearestUnvisitedObject() != null)
|
|
{
|
|
logger.LogDebug("New objects detected, resuming plant clearing");
|
|
ResetSearchState();
|
|
TryFindAndMove();
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
long? waypointReachedAt = _waypointReachedAt;
|
|
if (waypointReachedAt.HasValue)
|
|
{
|
|
long valueOrDefault = waypointReachedAt.GetValueOrDefault();
|
|
if (Environment.TickCount64 > valueOrDefault + 3000)
|
|
{
|
|
logger.LogDebug("No transition at this waypoint, trying next");
|
|
NavigateToNextWaypoint();
|
|
}
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
default:
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
}
|
|
|
|
private void ResetSearchState()
|
|
{
|
|
_visitedPositions.Clear();
|
|
_triedWaypointIndices.Clear();
|
|
_noObjectFoundSince = null;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|