using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Plugin.Services; using LLib.GameData; using LLib.Gear; using Lumina.Excel; using Lumina.Excel.Sheets; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps; using Questionable.Controller.Steps.Common; using Questionable.Data; using Questionable.Functions; using Questionable.Model.Questing; using Questionable.Navigation; namespace Questionable.Controller; internal sealed class GearRepairService(Configuration configuration, ICondition condition, IClientState clientState, IObjectTable objectTable, IDataManager dataManager, MenderDataService menderDataService, SmartNavRouteEnqueuer routeEnqueuer, AetheryteFunctions aetheryteFunctions, IChatGui chatGui, ILogger logger) { private const int CriticalConditionPercent = 10; private const long AttemptCooldownMs = 300000L; private const long WarningCooldownMs = 300000L; private long _nextAttemptAtMs; private long _nextWarningAtMs; public void ResetAttemptCooldown() { _nextAttemptAtMs = 0L; _nextWarningAtMs = 0L; } public ERepairDecision EvaluateAndEnqueue(EInteractionType upcomingType, TaskQueue taskQueue) { bool flag = (uint)(upcomingType - 19) <= 1u; bool flag2 = flag; flag = (uint)(upcomingType - 30) <= 1u; bool flag3 = flag; if (!flag2 && !flag3) { return ERepairDecision.NotNeeded; } if (!configuration.General.RepairGear) { return ERepairDecision.NotNeeded; } if (condition[ConditionFlag.InCombat]) { return ERepairDecision.NotNeeded; } int? lowestConditionPercent = GearConditionHelper.GetLowestConditionPercent(); if (!lowestConditionPercent.HasValue || lowestConditionPercent.Value >= configuration.General.RepairThresholdPercent) { return ERepairDecision.NotNeeded; } bool flag4 = flag2 && lowestConditionPercent.Value < 10; if (Environment.TickCount64 < _nextAttemptAtMs) { if (flag4) { logger.LogError("Gear still critically damaged ({Condition}%) after recent repair attempt", lowestConditionPercent.Value); return ERepairDecision.BlockDuty; } return ERepairDecision.NotNeeded; } if (TryEnqueueRepair(taskQueue, lowestConditionPercent.Value, out string failureReason)) { return ERepairDecision.Enqueued; } if (flag4) { logger.LogError("Gear critically damaged ({Condition}%) and unrepairable: {Reason}", lowestConditionPercent.Value, failureReason); return ERepairDecision.BlockDuty; } if (Environment.TickCount64 >= _nextWarningAtMs) { _nextWarningAtMs = Environment.TickCount64 + 300000; chatGui.Print($"Gear is at {lowestConditionPercent.Value}% condition but can't be repaired: {failureReason}", "Questionable", 576); } return ERepairDecision.NotNeeded; } private bool TryEnqueueRepair(TaskQueue taskQueue, int lowestCondition, out string failureReason) { if (CanSelfRepair()) { logger.LogInformation("Lowest gear condition {Condition}%, self-repairing", lowestCondition); taskQueue.Enqueue(new Questionable.Controller.Steps.Common.Mount.UnmountTask()); taskQueue.Enqueue(new RepairGear.SelfRepairTask()); MarkAttempt(); failureReason = string.Empty; return true; } IPlayerCharacter localPlayer = objectTable.LocalPlayer; if (localPlayer != null) { MenderNpc menderNpc = menderDataService.FindNearestInTerritory((ushort)clientState.TerritoryType, localPlayer.Position); if ((object)menderNpc != null) { logger.LogInformation("Lowest gear condition {Condition}%, visiting mender {DataId} in current zone", lowestCondition, menderNpc.DataId); routeEnqueuer.Enqueue(taskQueue, menderNpc.TerritoryId, menderNpc.Position, 3f); taskQueue.Enqueue(new Questionable.Controller.Steps.Common.Mount.UnmountTask()); taskQueue.Enqueue(new RepairGear.MenderRepairTask(menderNpc)); MarkAttempt(); failureReason = string.Empty; return true; } } if (aetheryteFunctions.IsTeleportUnlocked()) { foreach (MenderNpc cityFallback in MenderDataService.CityFallbacks) { if (routeEnqueuer.TryEnqueueRouted(taskQueue, cityFallback.TerritoryId, cityFallback.Position)) { logger.LogInformation("Lowest gear condition {Condition}%, no zone mender - routing to city mender {DataId}", lowestCondition, cityFallback.DataId); taskQueue.Enqueue(new Questionable.Controller.Steps.Common.Mount.UnmountTask()); taskQueue.Enqueue(new RepairGear.MenderRepairTask(cityFallback)); MarkAttempt(); failureReason = string.Empty; return true; } } } failureReason = "no dark matter or crafter levels for self-repair, and no reachable mender"; return false; } private bool CanSelfRepair() { ExcelSheet classJobSheet = dataManager.GetExcelSheet(); ClassJob row; return RepairActions.CanSelfRepairAny(dataManager, (uint classJobId) => (byte)(classJobSheet.TryGetRow(classJobId, out row) ? ((byte)PlayerStateHelper.GetClassJobLevel(row.ExpArrayIndex)) : 0), configuration.General.RepairThresholdPercent); } private void MarkAttempt() { _nextAttemptAtMs = Environment.TickCount64 + 300000; } }