muffin v7.38.9

This commit is contained in:
alydev 2025-12-07 10:55:56 +10:00
parent ada27cf05b
commit 8a7847ff37
21 changed files with 1296 additions and 689 deletions

View file

@ -15,11 +15,6 @@ internal sealed class InterruptHandler : IDisposable
{
private unsafe delegate void ProcessActionEffect(uint sourceId, Character* sourceCharacter, Vector3* pos, EffectHeader* effectHeader, EffectEntry* effectArray, ulong* effectTail);
private static class Signatures
{
internal const string ActionEffect = "40 ?? 56 57 41 ?? 41 ?? 41 ?? 48 ?? ?? ?? ?? ?? ?? ?? 48";
}
[StructLayout(LayoutKind.Explicit)]
private struct EffectEntry
{
@ -150,7 +145,7 @@ internal sealed class InterruptHandler : IDisposable
_objectTable = objectTable;
_territoryData = territoryData;
_logger = logger;
_processActionEffectHook = gameInteropProvider.HookFromSignature<ProcessActionEffect>("40 ?? 56 57 41 ?? 41 ?? 41 ?? 48 ?? ?? ?? ?? ?? ?? ?? 48", HandleProcessActionEffect);
_processActionEffectHook = gameInteropProvider.HookFromAddress<ProcessActionEffect>(ActionEffectHandler.Addresses.Receive.Value, HandleProcessActionEffect);
_processActionEffectHook.Enable();
}

View file

@ -85,6 +85,8 @@ internal sealed class MovementController : IDisposable
private readonly AetheryteData _aetheryteData;
private readonly Configuration _configuration;
private readonly ILogger<MovementController> _logger;
private CancellationTokenSource? _cancellationTokenSource;
@ -93,6 +95,14 @@ internal sealed class MovementController : IDisposable
private long _pathfindStartTime;
private Vector3? _lastKnownPosition;
private long _lastPositionUpdateTime;
private Vector3? _expectedPosition;
private bool _isTrackingPlayerInput;
public bool IsNavmeshReady
{
get
@ -146,7 +156,9 @@ internal sealed class MovementController : IDisposable
public int NumQueuedPathfindRequests => _navmeshIpc.NumQueuedPathfindRequests;
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, ILogger<MovementController> logger)
public event EventHandler? PlayerInputDetected;
public MovementController(NavmeshIpc navmeshIpc, IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, ChatFunctions chatFunctions, ICondition condition, MovementOverrideController movementOverrideController, AetheryteData aetheryteData, Configuration configuration, ILogger<MovementController> logger)
{
_navmeshIpc = navmeshIpc;
_clientState = clientState;
@ -156,11 +168,19 @@ internal sealed class MovementController : IDisposable
_condition = condition;
_movementOverrideController = movementOverrideController;
_aetheryteData = aetheryteData;
_configuration = configuration;
_logger = logger;
}
public unsafe void Update()
{
if (IsPathRunning && _isTrackingPlayerInput && DetectPlayerInputInterference())
{
_logger.LogInformation("Player input detected during automatic movement, raising event to stop automation");
this.PlayerInputDetected?.Invoke(this, EventArgs.Empty);
Stop();
return;
}
if (_pathfindTask != null && Destination != null)
{
if (!_pathfindTask.IsCompleted && Environment.TickCount64 - _pathfindStartTime > 30000 && _navmeshIpc.NumQueuedPathfindRequests > 5)
@ -188,6 +208,11 @@ internal sealed class MovementController : IDisposable
if (Destination.IsFlying && Destination.Land)
{
_logger.LogWarning("Adjusted destination failed, trying tolerance-based pathfinding");
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready for tolerance-based pathfinding");
return;
}
_cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30L));
Vector3 vector2 = _objectTable[0]?.Position ?? Vector3.Zero;
@ -218,6 +243,11 @@ internal sealed class MovementController : IDisposable
(list, _) = tuple;
if (tuple.Item2 && Destination.ShouldRecalculateNavmesh())
{
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready for recalculation");
return;
}
Destination.NavmeshCalculations++;
Destination.PartialRoute.AddRange(list);
_logger.LogInformation("Running navmesh recalculation with fudged point ({From} to {To})", list.Last(), Destination.Position);
@ -232,6 +262,7 @@ internal sealed class MovementController : IDisposable
_logger.LogInformation("Navigating via route: [{Route}]", string.Join(" → ", _pathfindTask.Result.Select((Vector3 x) => x.ToString("G", CultureInfo.InvariantCulture))));
_navmeshIpc.MoveTo(list, Destination.IsFlying);
MovementStartedAt = DateTime.Now;
StartPlayerInputTracking();
ResetPathfinding();
}
else if (_pathfindTask.IsCompleted)
@ -321,6 +352,72 @@ internal sealed class MovementController : IDisposable
}
}
private void StartPlayerInputTracking()
{
IGameObject gameObject = _objectTable[0];
if (gameObject != null)
{
_lastKnownPosition = gameObject.Position;
_expectedPosition = gameObject.Position;
_lastPositionUpdateTime = Environment.TickCount64;
_isTrackingPlayerInput = true;
}
}
private bool DetectPlayerInputInterference()
{
if (!_configuration.General.StopOnPlayerInput)
{
return false;
}
if (!_isTrackingPlayerInput || !_lastKnownPosition.HasValue)
{
return false;
}
IGameObject gameObject = _objectTable[0];
if (gameObject == null)
{
return false;
}
Vector3 position = gameObject.Position;
long tickCount = Environment.TickCount64;
if (tickCount - _lastPositionUpdateTime < 100)
{
return false;
}
List<Vector3> waypoints = _navmeshIpc.GetWaypoints();
if (waypoints.Count > 0)
{
_expectedPosition = waypoints[0];
}
if (_expectedPosition.HasValue)
{
Vector3 vector = Vector3.Normalize(_expectedPosition.Value - _lastKnownPosition.Value);
Vector3 value = position - _lastKnownPosition.Value;
if (value.Length() > 0.1f)
{
Vector3 vector2 = Vector3.Normalize(value);
float num = Vector3.Dot(vector, vector2);
if (num < 0.7f)
{
_logger.LogDebug("Player movement detected: alignment={Alignment:F2}, actual={Actual}, expected={Expected}", num, value.ToString("G", CultureInfo.InvariantCulture), vector.ToString("G", CultureInfo.InvariantCulture));
return true;
}
}
}
_lastKnownPosition = position;
_lastPositionUpdateTime = tickCount;
return false;
}
private void StopPlayerInputTracking()
{
_isTrackingPlayerInput = false;
_lastKnownPosition = null;
_expectedPosition = null;
_lastPositionUpdateTime = 0L;
}
private void Restart(DestinationData destination)
{
Stop();
@ -364,6 +461,11 @@ internal sealed class MovementController : IDisposable
public void NavigateTo(EMovementType type, uint? dataId, Vector3 to, bool fly, bool sprint, float? stopDistance = null, float? verticalStopDistance = null, bool land = false)
{
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready, cannot start navigation to {Position}", to.ToString("G", CultureInfo.InvariantCulture));
return;
}
fly |= _condition[ConditionFlag.Diving];
if (fly && land)
{
@ -404,6 +506,11 @@ internal sealed class MovementController : IDisposable
public void NavigateTo(EMovementType type, uint? dataId, List<Vector3> to, bool fly, bool sprint, float? stopDistance, float? verticalStopDistance = null, bool land = false)
{
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready, cannot start navigation to {Position}", to.Last().ToString("G", CultureInfo.InvariantCulture));
return;
}
fly |= _condition[ConditionFlag.Diving];
if (fly && land && to.Count > 0)
{
@ -416,6 +523,7 @@ internal sealed class MovementController : IDisposable
_logger.LogInformation("Moving to {Destination}", Destination);
_navmeshIpc.MoveTo(to, fly);
MovementStartedAt = DateTime.Now;
StartPlayerInputTracking();
}
public void ResetPathfinding()
@ -436,6 +544,11 @@ internal sealed class MovementController : IDisposable
private Vector3? TryFindAccessibleDestination(Vector3 target, bool flying, bool landing)
{
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready, cannot find accessible destination");
return null;
}
float[] array = ((!(flying && landing)) ? ((!flying) ? new float[3] { 1f, 3f, 5f } : new float[3] { 2f, 5f, 10f }) : new float[3] { 5f, 10f, 15f });
float[] array2 = ((!flying) ? new float[3] { 1f, 2f, 3f } : new float[3] { 3f, 5f, 10f });
for (int i = 0; i < array.Length; i++)
@ -504,17 +617,52 @@ internal sealed class MovementController : IDisposable
if (Math.Abs((double)num - Destination.LastWaypoint.Distance2DAtLastUpdate) < 0.5)
{
int navmeshCalculations = Destination.NavmeshCalculations;
if (navmeshCalculations % 6 == 1)
switch (navmeshCalculations)
{
_logger.LogWarning("Jumping to try and resolve navmesh problem (n = {Calculations})", navmeshCalculations);
case 1:
case 7:
_logger.LogWarning("Jumping to try and resolve navmesh problem (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
ActionManager.Instance()->UseAction(ActionType.GeneralAction, 2u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
Destination.NavmeshCalculations++;
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
}
else
{
_logger.LogWarning("Recalculating navmesh (n = {Calculations})", navmeshCalculations);
break;
case 5:
_logger.LogWarning("Reloading navmesh (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
_navmeshIpc.Reload();
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
break;
case 6:
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready after reload (n = {Calculations})", navmeshCalculations);
return false;
}
_logger.LogInformation("Navmesh ready after reload, restarting navigation (n = {Calculations})", navmeshCalculations);
Restart(Destination);
break;
case 8:
_logger.LogWarning("Rebuilding navmesh (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
_navmeshIpc.Rebuild();
Destination.LastWaypoint.UpdatedAt = Environment.TickCount64;
break;
case 9:
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready after rebuild (n = {Calculations})", navmeshCalculations);
return false;
}
_logger.LogInformation("Navmesh ready after rebuild, restarting navigation (n = {Calculations})", navmeshCalculations);
Restart(Destination);
break;
default:
if (!IsNavmeshReady)
{
_logger.LogWarning("Navmesh not ready for recalculation (n = {Calculations})", navmeshCalculations);
return false;
}
_logger.LogWarning("Recalculating navmesh (n = {Calculations}) at {Position}", navmeshCalculations, Destination.Position.ToString("G", CultureInfo.InvariantCulture));
Restart(Destination);
break;
}
Destination.NavmeshCalculations = navmeshCalculations + 1;
return true;
@ -570,6 +718,7 @@ internal sealed class MovementController : IDisposable
public void Stop()
{
StopPlayerInputTracking();
_navmeshIpc.Stop();
ResetPathfinding();
Destination = null;

View file

@ -257,6 +257,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_toastGui.Toast += OnNormalToast;
_condition.ConditionChange += OnConditionChange;
_clientState.Logout += OnLogout;
_movementController.PlayerInputDetected += OnPlayerInputDetected;
}
public void Reload()
@ -683,55 +684,71 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
DebugState = "No quest active";
Stop("No quest active");
return;
}
if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(questProgress.Quest))
else if (questProgress.Step == 255)
{
DebugState = $"Waiting for sequence update (current: {questProgress.Sequence})";
if (!_taskQueue.AllTasksComplete)
{
DebugState = "Step 255 - processing interrupted tasks";
}
else
{
if (this.CurrentQuest == null)
{
return;
}
TimeSpan timeSpan = DateTime.Now - this.CurrentQuest.StepProgress.StartedAt;
if (timeSpan > TimeSpan.FromSeconds(3L))
{
_logger.LogWarning("Step 255 with no tasks for {WaitTime:F1}s, retrying step to ensure completion (quest: {QuestId}, sequence: {Sequence})", timeSpan.TotalSeconds, questProgress.Quest.Id, questProgress.Sequence);
QuestSequence questSequence = questProgress.Quest.FindSequence(questProgress.Sequence);
if (questSequence != null && questSequence.Steps.Count > 0)
{
this.CurrentQuest.SetStep(questSequence.Steps.Count - 1);
CheckNextTasks("Retry last step at 255");
}
}
}
}
else if (_gameFunctions.IsOccupied() && !_gameFunctions.IsOccupiedWithCustomDeliveryNpc(questProgress.Quest))
{
DebugState = "Occupied";
return;
}
if (_movementController.IsPathfinding)
else if (_movementController.IsPathfinding)
{
DebugState = "Pathfinding is running";
return;
}
if (_movementController.IsPathRunning)
else if (_movementController.IsPathRunning)
{
DebugState = "Path is running";
return;
}
if (DateTime.Now < _safeAnimationEnd)
else if (DateTime.Now < _safeAnimationEnd)
{
DebugState = "Waiting for Animation";
return;
}
if (questProgress.Sequence != b)
else if (questProgress.Sequence != b)
{
questProgress.SetSequence(b);
CheckNextTasks($"New sequence {questProgress == _startedQuest}/{_questFunctions.GetCurrentQuestInternal(allowNewMsq: true)}");
}
QuestSequence questSequence = questProgress.Quest.FindSequence(questProgress.Sequence);
if (questSequence == null)
{
DebugState = $"Sequence {questProgress.Sequence} not found";
Stop("Unknown sequence");
}
else if (questProgress.Step == 255)
{
DebugState = "Step completed";
if (!_taskQueue.AllTasksComplete)
{
CheckNextTasks("Step complete");
}
}
else if (questSequence.Steps.Count > 0 && questProgress.Step >= questSequence.Steps.Count)
{
DebugState = "Step not found";
Stop("Unknown step");
}
else
{
DebugState = null;
QuestSequence questSequence2 = questProgress.Quest.FindSequence(questProgress.Sequence);
if (questSequence2 == null)
{
DebugState = $"Sequence {questProgress.Sequence} not found";
Stop("Unknown sequence");
}
else if (questSequence2.Steps.Count > 0 && questProgress.Step >= questSequence2.Steps.Count)
{
DebugState = "Step not found";
Stop("Unknown step");
}
else
{
DebugState = null;
}
}
}
}
@ -778,15 +795,21 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_logger.LogWarning("Ignoring 'increase step count' for different sequence (expected {ExpectedSequence}, but we are at {CurrentSequence}", sequence, questSequence.Sequence);
}
_logger.LogInformation("Increasing step count from {CurrentValue}", CurrentQuest.Step);
if (CurrentQuest.Step + 1 < questSequence.Steps.Count)
{
CurrentQuest.SetStep(CurrentQuest.Step + 1);
}
else
bool num = CurrentQuest.Step + 1 >= questSequence.Steps.Count;
if (num)
{
CurrentQuest.SetStep(255);
}
else
{
CurrentQuest.SetStep(CurrentQuest.Step + 1);
}
ResetAutoRefreshState();
if (num)
{
_logger.LogInformation("Completed last step in sequence, waiting for game to update sequence");
return;
}
}
using (_logger.BeginScope("IncStepCt"))
{
@ -1291,12 +1314,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>
}
}
private void OnPlayerInputDetected(object? sender, EventArgs e)
{
if (AutomationType != EAutomationType.Manual && IsRunning)
{
_logger.LogInformation("Player input detected during movement, stopping quest automation");
_chatGui.Print("Player input detected - stopping quest automation.", "Questionable", 576);
Stop("Player input detected");
}
}
public override void Dispose()
{
_toastGui.ErrorToast -= base.OnErrorToast;
_toastGui.Toast -= OnNormalToast;
_condition.ConditionChange -= OnConditionChange;
_clientState.Logout -= OnLogout;
_movementController.PlayerInputDetected -= OnPlayerInputDetected;
base.Dispose();
}
}