using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Plugin.Services; namespace QuestionableCompanion.Services; public class CombatDutyDetectionService : IDisposable { private readonly ICondition condition; private readonly IPluginLog log; private readonly IClientState clientState; private readonly ICommandManager commandManager; private readonly IFramework framework; private readonly Configuration config; private bool wasInCombat; private bool wasInDuty; private DateTime dutyExitTime = DateTime.MinValue; private DateTime dutyEntryTime = DateTime.MinValue; private DateTime lastStateChange = DateTime.MinValue; private bool combatCommandsActive; private bool hasCombatCommandsForDuty; private bool isInAutoDutyDungeon; private uint currentQuestId; private bool isRotationActive; public bool JustEnteredDuty { get; private set; } public bool JustExitedDuty { get; private set; } public DateTime DutyExitTime => dutyExitTime; public bool IsInCombat { get; private set; } public bool IsInDuty { get; private set; } public bool IsInDutyQueue { get; private set; } public bool ShouldPauseAutomation { get { if (!IsInCombat && !IsInDuty) { return IsInDutyQueue; } return true; } } public void AcknowledgeDutyEntry() { JustEnteredDuty = false; } public void AcknowledgeDutyExit() { JustExitedDuty = false; } public CombatDutyDetectionService(ICondition condition, IPluginLog log, IClientState clientState, ICommandManager commandManager, IFramework framework, Configuration config) { this.condition = condition; this.log = log; this.clientState = clientState; this.commandManager = commandManager; this.framework = framework; this.config = config; log.Information("[CombatDuty] Service initialized"); } public void SetRotationActive(bool active) { isRotationActive = active; } public void SetAutoDutyDungeon(bool isAutoDuty) { isInAutoDutyDungeon = isAutoDuty; } public void SetCurrentQuestId(uint questId) { currentQuestId = questId; } public void Update() { if (clientState.LocalPlayer == null || !clientState.IsLoggedIn) { return; } if (isRotationActive) { bool inCombat = condition[ConditionFlag.InCombat]; if (inCombat != wasInCombat) { IsInCombat = inCombat; wasInCombat = inCombat; lastStateChange = DateTime.Now; if (inCombat) { log.Information("[CombatDuty] Combat started - pausing automation"); if (currentQuestId == 811) { log.Information("[CombatDuty] Quest 811 - combat commands DISABLED (RSR off)"); return; } } else { log.Information("[CombatDuty] Combat ended - resuming automation"); if (combatCommandsActive && !IsInDuty) { log.Information("[CombatDuty] Not in duty - disabling combat commands"); DisableCombatCommands(); } else if (combatCommandsActive && IsInDuty) { log.Information("[CombatDuty] In duty - keeping combat commands active"); } } } } if (isRotationActive && config.EnableCombatHandling && IsInCombat && !combatCommandsActive && currentQuestId != 811) { IPlayerCharacter player = clientState.LocalPlayer; if (player != null) { float hpPercent = (float)player.CurrentHp / (float)player.MaxHp * 100f; if (hpPercent <= (float)config.CombatHPThreshold) { log.Warning($"[CombatDuty] HP at {hpPercent:F1}% (threshold: {config.CombatHPThreshold}%) - enabling combat commands"); EnableCombatCommands(); } } } bool inDuty = condition[ConditionFlag.BoundByDuty] || condition[ConditionFlag.BoundByDuty56] || condition[ConditionFlag.BoundByDuty95]; if (inDuty != wasInDuty) { IsInDuty = inDuty; wasInDuty = inDuty; lastStateChange = DateTime.Now; if (inDuty) { log.Information("[CombatDuty] Duty started - pausing automation"); JustEnteredDuty = true; JustExitedDuty = false; dutyEntryTime = DateTime.Now; hasCombatCommandsForDuty = false; } else { log.Information("[CombatDuty] Duty completed - resuming automation"); JustEnteredDuty = false; JustExitedDuty = true; dutyExitTime = DateTime.Now; dutyEntryTime = DateTime.MinValue; hasCombatCommandsForDuty = false; if (combatCommandsActive) { log.Information("[CombatDuty] Duty ended - disabling combat commands"); DisableCombatCommands(); } } } if (isRotationActive && IsInDuty && !isInAutoDutyDungeon && !hasCombatCommandsForDuty && dutyEntryTime != DateTime.MinValue) { if (currentQuestId == 811) { log.Information("[CombatDuty] Quest 811 - skipping combat commands (RSR disabled)"); hasCombatCommandsForDuty = true; return; } if (currentQuestId == 4591) { log.Information("[CombatDuty] Quest 4591 (Steps of Faith) - skipping combat commands (handler does it)"); hasCombatCommandsForDuty = true; return; } if ((DateTime.Now - dutyEntryTime).TotalSeconds >= 8.0) { log.Information("[CombatDuty] 8 seconds in Solo Duty - enabling combat commands"); EnableCombatCommands(); hasCombatCommandsForDuty = true; } } bool inQueue = condition[ConditionFlag.WaitingForDuty] || condition[ConditionFlag.WaitingForDutyFinder]; if (inQueue != IsInDutyQueue) { IsInDutyQueue = inQueue; lastStateChange = DateTime.Now; if (inQueue) { log.Information("[CombatDuty] Duty queue active - pausing automation"); } else { log.Information("[CombatDuty] Duty queue ended - resuming automation"); } } } public TimeSpan TimeSinceLastStateChange() { if (lastStateChange == DateTime.MinValue) { return TimeSpan.Zero; } return DateTime.Now - lastStateChange; } private void EnableCombatCommands() { if (combatCommandsActive) { return; } try { log.Information("[CombatDuty] ========================================"); log.Information("[CombatDuty] === ENABLING COMBAT AUTOMATION ==="); log.Information("[CombatDuty] ========================================"); framework.RunOnTick(delegate { try { commandManager.ProcessCommand("/rsr auto"); log.Information("[CombatDuty] /rsr auto sent"); } catch (Exception ex2) { log.Error("[CombatDuty] Failed to send /rsr auto: " + ex2.Message); } }, TimeSpan.Zero); framework.RunOnTick(delegate { try { commandManager.ProcessCommand("/vbmai on"); log.Information("[CombatDuty] /vbmai on sent"); } catch (Exception ex2) { log.Error("[CombatDuty] Failed to send /vbmai on: " + ex2.Message); } }, TimeSpan.FromMilliseconds(100L, 0L)); framework.RunOnTick(delegate { try { commandManager.ProcessCommand("/bmrai on"); log.Information("[CombatDuty] /bmrai on sent"); } catch (Exception ex2) { log.Error("[CombatDuty] Failed to send /bmrai on: " + ex2.Message); } }, TimeSpan.FromMilliseconds(200L, 0L)); combatCommandsActive = true; log.Information("[CombatDuty] Combat automation enabled"); } catch (Exception ex) { log.Error("[CombatDuty] Error enabling combat commands: " + ex.Message); } } private void DisableCombatCommands() { if (!combatCommandsActive) { return; } try { log.Information("[CombatDuty] ========================================"); log.Information("[CombatDuty] === DISABLING COMBAT AUTOMATION ==="); log.Information("[CombatDuty] ========================================"); framework.RunOnTick(delegate { try { commandManager.ProcessCommand("/rsr off"); log.Information("[CombatDuty] /rsr off sent"); } catch (Exception ex2) { log.Error("[CombatDuty] Failed to send /rsr off: " + ex2.Message); } }, TimeSpan.Zero); framework.RunOnTick(delegate { try { commandManager.ProcessCommand("/vbmai off"); log.Information("[CombatDuty] /vbmai off sent"); } catch (Exception ex2) { log.Error("[CombatDuty] Failed to send /vbmai off: " + ex2.Message); } }, TimeSpan.FromMilliseconds(100L, 0L)); framework.RunOnTick(delegate { try { commandManager.ProcessCommand("/bmrai off"); log.Information("[CombatDuty] /bmrai off sent"); } catch (Exception ex2) { log.Error("[CombatDuty] Failed to send /bmrai off: " + ex2.Message); } }, TimeSpan.FromMilliseconds(200L, 0L)); combatCommandsActive = false; log.Information("[CombatDuty] Combat automation disabled"); } catch (Exception ex) { log.Error("[CombatDuty] Error disabling combat commands: " + ex.Message); } } public void ClearDutyExitFlag() { JustExitedDuty = false; } public void Reset() { IsInCombat = false; IsInDuty = false; IsInDutyQueue = false; wasInCombat = false; wasInDuty = false; combatCommandsActive = false; hasCombatCommandsForDuty = false; JustEnteredDuty = false; JustExitedDuty = false; dutyExitTime = DateTime.MinValue; dutyEntryTime = DateTime.MinValue; log.Information("[CombatDuty] State reset"); } public void Dispose() { if (combatCommandsActive) { DisableCombatCommands(); } } }