1468 lines
50 KiB
C#
1468 lines
50 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using QuestionableCompanion.Models;
|
|
|
|
namespace QuestionableCompanion.Services;
|
|
|
|
public class QuestRotationExecutionService : IDisposable
|
|
{
|
|
private readonly AutoRetainerIPC autoRetainerIpc;
|
|
|
|
private readonly QuestTrackingService questTrackingService;
|
|
|
|
private readonly IPluginLog log;
|
|
|
|
private readonly IFramework framework;
|
|
|
|
private readonly ICommandManager commandManager;
|
|
|
|
private readonly ICondition condition;
|
|
|
|
private readonly IClientState clientState;
|
|
|
|
private readonly SubmarineManager submarineManager;
|
|
|
|
private readonly QuestionableIPC questionableIPC;
|
|
|
|
private readonly Configuration configuration;
|
|
|
|
private DCTravelService? dcTravelService;
|
|
|
|
private CharacterSafeWaitService? safeWaitService;
|
|
|
|
private QuestPreCheckService? preCheckService;
|
|
|
|
private MovementMonitorService? movementMonitor;
|
|
|
|
private CombatDutyDetectionService? combatDutyDetection;
|
|
|
|
private DeathHandlerService? deathHandler;
|
|
|
|
private DungeonAutomationService? dungeonAutomation;
|
|
|
|
private StepsOfFaithHandler? stepsOfFaithHandler;
|
|
|
|
private ErrorRecoveryService? errorRecoveryService;
|
|
|
|
private readonly List<StopPoint> stopPoints = new List<StopPoint>();
|
|
|
|
private RotationState currentState = new RotationState();
|
|
|
|
private Dictionary<uint, List<string>> questCompletionByCharacter = new Dictionary<uint, List<string>>();
|
|
|
|
private Action? onDataChanged;
|
|
|
|
private DateTime lastCheckTime = DateTime.MinValue;
|
|
|
|
private const double CheckIntervalMs = 250.0;
|
|
|
|
private DateTime lastSubmarineCheckTime = DateTime.MinValue;
|
|
|
|
private bool waitingForQuestAcceptForSubmarines;
|
|
|
|
private uint? lastSoloDutyQuestId;
|
|
|
|
private const double CharacterLoginTimeoutSeconds = 60.0;
|
|
|
|
private const double PhaseTimeoutSeconds = 120.0;
|
|
|
|
private bool isRotationActive;
|
|
|
|
public bool IsRotationActive => isRotationActive;
|
|
|
|
public QuestRotationExecutionService(AutoRetainerIPC autoRetainerIpc, QuestTrackingService questTrackingService, SubmarineManager submarineManager, QuestionableIPC questionableIPC, Configuration configuration, IPluginLog log, IFramework framework, ICommandManager commandManager, ICondition condition, IClientState clientState, Action? onDataChanged = null)
|
|
{
|
|
this.autoRetainerIpc = autoRetainerIpc;
|
|
this.questTrackingService = questTrackingService;
|
|
this.submarineManager = submarineManager;
|
|
this.questionableIPC = questionableIPC;
|
|
this.configuration = configuration;
|
|
this.log = log;
|
|
this.framework = framework;
|
|
this.commandManager = commandManager;
|
|
this.condition = condition;
|
|
this.clientState = clientState;
|
|
this.onDataChanged = onDataChanged;
|
|
framework.Update += OnFrameworkUpdate;
|
|
log.Information("[QuestRotation] Service initialized");
|
|
}
|
|
|
|
public bool AddStopPoint(uint questId, byte? sequence = null)
|
|
{
|
|
if (stopPoints.Any((StopPoint sp) => sp.QuestId == questId && sp.Sequence == sequence))
|
|
{
|
|
log.Warning($"[QuestRotation] Stop point {questId}" + (sequence.HasValue ? $" Seq {sequence.Value}" : "") + " already exists");
|
|
return false;
|
|
}
|
|
StopPoint stopPoint = new StopPoint
|
|
{
|
|
QuestId = questId,
|
|
Sequence = sequence,
|
|
IsActive = false,
|
|
CreatedAt = DateTime.Now
|
|
};
|
|
stopPoints.Add(stopPoint);
|
|
log.Information($"[QuestRotation] Added stop point: Quest {questId}");
|
|
return true;
|
|
}
|
|
|
|
public void ImportStopPointsFromQuestionable()
|
|
{
|
|
try
|
|
{
|
|
log.Information("[QuestRotation] Importing stop points from Questionable...");
|
|
List<string> stopQuestIds = questionableIPC.GetStopQuestList();
|
|
Dictionary<string, object> sequenceConditions = null;
|
|
try
|
|
{
|
|
sequenceConditions = questionableIPC.GetAllQuestSequenceStopConditions();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Wrong Questionable Version ");
|
|
log.Error("[QuestRotation] Import failed: " + ex.Message);
|
|
return;
|
|
}
|
|
stopPoints.Clear();
|
|
if (stopQuestIds != null)
|
|
{
|
|
foreach (string questIdStr in stopQuestIds)
|
|
{
|
|
if (!uint.TryParse(questIdStr, out var questId))
|
|
{
|
|
continue;
|
|
}
|
|
byte? sequence = null;
|
|
if (sequenceConditions != null && sequenceConditions.ContainsKey(questIdStr))
|
|
{
|
|
try
|
|
{
|
|
object seqObj = sequenceConditions[questId.ToString()];
|
|
if (seqObj != null)
|
|
{
|
|
sequence = Convert.ToByte(seqObj);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
stopPoints.Add(new StopPoint
|
|
{
|
|
QuestId = questId,
|
|
Sequence = sequence,
|
|
IsActive = false,
|
|
CreatedAt = DateTime.Now
|
|
});
|
|
}
|
|
}
|
|
if (sequenceConditions != null)
|
|
{
|
|
foreach (KeyValuePair<string, object> kvp in sequenceConditions)
|
|
{
|
|
if (uint.TryParse(kvp.Key, out var questId2) && !stopPoints.Any((StopPoint sp) => sp.QuestId == questId2))
|
|
{
|
|
byte? sequence2 = null;
|
|
try
|
|
{
|
|
sequence2 = Convert.ToByte(kvp.Value);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
stopPoints.Add(new StopPoint
|
|
{
|
|
QuestId = questId2,
|
|
Sequence = sequence2,
|
|
IsActive = false,
|
|
CreatedAt = DateTime.Now
|
|
});
|
|
}
|
|
}
|
|
}
|
|
log.Information($"[QuestRotation] Imported {stopPoints.Count} stop points");
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
log.Error("[QuestRotation] Failed to import stop points: " + ex2.Message);
|
|
}
|
|
}
|
|
|
|
public bool RemoveStopPoint(uint questId)
|
|
{
|
|
if (isRotationActive)
|
|
{
|
|
log.Error($"[QuestRotation] Cannot remove stop point {questId} during active rotation!");
|
|
return false;
|
|
}
|
|
StopPoint stopPoint = stopPoints.FirstOrDefault((StopPoint sp) => sp.QuestId == questId);
|
|
if (stopPoint == null)
|
|
{
|
|
log.Warning($"[QuestRotation] Stop point {questId} not found");
|
|
return false;
|
|
}
|
|
stopPoints.Remove(stopPoint);
|
|
log.Information($"[QuestRotation] Removed stop point: Quest {questId}");
|
|
return true;
|
|
}
|
|
|
|
public bool StartRotation(uint questId, List<string> characters)
|
|
{
|
|
if (characters == null || characters.Count == 0)
|
|
{
|
|
log.Error("[QuestRotation] Cannot start rotation: No characters selected");
|
|
return false;
|
|
}
|
|
StopPoint stopPoint = stopPoints.FirstOrDefault((StopPoint sp) => sp.QuestId == questId);
|
|
if (stopPoint == null)
|
|
{
|
|
log.Error($"[QuestRotation] Cannot start rotation: Quest {questId} not in stop points");
|
|
return false;
|
|
}
|
|
log.Information("[QuestRotation] Found stop point: " + stopPoint.DisplayName);
|
|
List<string> remainingChars = new List<string>();
|
|
List<string> completedChars = new List<string>();
|
|
if (questCompletionByCharacter.TryGetValue(questId, out List<string> savedCompletedChars))
|
|
{
|
|
log.Debug($"[QuestRotation] Quest {questId} has {savedCompletedChars.Count} characters marked as completed in saved data");
|
|
}
|
|
else
|
|
{
|
|
log.Debug($"[QuestRotation] Quest {questId} has NO saved completion data");
|
|
}
|
|
foreach (string character in characters)
|
|
{
|
|
if (HasCharacterCompletedQuest(questId, character))
|
|
{
|
|
completedChars.Add(character);
|
|
log.Debug($"[QuestRotation] {character} already completed quest {questId} (from saved data)");
|
|
}
|
|
else
|
|
{
|
|
remainingChars.Add(character);
|
|
log.Debug($"[QuestRotation] {character} needs to complete quest {questId}");
|
|
}
|
|
}
|
|
if (remainingChars.Count == 0)
|
|
{
|
|
log.Information($"[QuestRotation] All characters have already completed quest {questId}");
|
|
return false;
|
|
}
|
|
currentState = new RotationState
|
|
{
|
|
CurrentStopQuestId = questId,
|
|
SelectedCharacters = new List<string>(characters),
|
|
RemainingCharacters = remainingChars,
|
|
CompletedCharacters = completedChars,
|
|
Phase = RotationPhase.InitializingFirstCharacter,
|
|
PhaseStartTime = DateTime.Now,
|
|
RotationStartTime = DateTime.Now,
|
|
HasQuestBeenAccepted = false
|
|
};
|
|
stopPoint.IsActive = true;
|
|
isRotationActive = true;
|
|
combatDutyDetection?.SetRotationActive(active: true);
|
|
deathHandler?.SetRotationActive(active: true);
|
|
if (configuration.EnableMovementMonitor && movementMonitor != null && !movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StartMonitoring();
|
|
log.Information("[QuestRotation] Movement monitor started");
|
|
}
|
|
log.Information("[QuestRotation] ═══ Starting Rotation ═══");
|
|
log.Information($"[QuestRotation] Quest ID: {questId}");
|
|
log.Information($"[QuestRotation] Total Characters: {characters.Count}");
|
|
log.Information($"[QuestRotation] Remaining: {remainingChars.Count} | Completed: {completedChars.Count}");
|
|
log.Information("[QuestRotation] Characters to process: " + string.Join(", ", remainingChars));
|
|
return true;
|
|
}
|
|
|
|
public bool StartRotationLevelOnly(List<string> characters)
|
|
{
|
|
if (characters == null || characters.Count == 0)
|
|
{
|
|
log.Error("[QuestRotation] Cannot start rotation: No characters selected");
|
|
return false;
|
|
}
|
|
StopConditionData levelStopCondition = questionableIPC.GetLevelStopCondition();
|
|
if (levelStopCondition == null || !levelStopCondition.Enabled)
|
|
{
|
|
log.Error("[QuestRotation] Cannot start level-only rotation: Level stop condition not configured");
|
|
return false;
|
|
}
|
|
log.Information($"[QuestRotation] Starting level-only rotation (target level: {levelStopCondition.TargetValue})");
|
|
List<string> remainingChars = new List<string>(characters);
|
|
currentState = new RotationState
|
|
{
|
|
CurrentStopQuestId = 0u,
|
|
SelectedCharacters = new List<string>(characters),
|
|
RemainingCharacters = remainingChars,
|
|
CompletedCharacters = new List<string>(),
|
|
Phase = RotationPhase.InitializingFirstCharacter,
|
|
PhaseStartTime = DateTime.Now,
|
|
RotationStartTime = DateTime.Now,
|
|
HasQuestBeenAccepted = false
|
|
};
|
|
isRotationActive = true;
|
|
combatDutyDetection?.SetRotationActive(active: true);
|
|
deathHandler?.SetRotationActive(active: true);
|
|
if (configuration.EnableMovementMonitor && movementMonitor != null && !movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StartMonitoring();
|
|
log.Information("[QuestRotation] Movement monitor started");
|
|
}
|
|
log.Information("[QuestRotation] ═══ Starting Level-Only Rotation ═══");
|
|
log.Information($"[QuestRotation] Target Level: {levelStopCondition.TargetValue}");
|
|
log.Information($"[QuestRotation] Total Characters: {characters.Count}");
|
|
log.Information("[QuestRotation] Characters to process: " + string.Join(", ", remainingChars));
|
|
return true;
|
|
}
|
|
|
|
public bool StartSyncRotation(List<string> characters)
|
|
{
|
|
if (characters == null || characters.Count == 0)
|
|
{
|
|
log.Error("[QuestRotation] Cannot start sync rotation: No characters provided");
|
|
return false;
|
|
}
|
|
List<string> charactersToSync = new List<string>();
|
|
foreach (string character in characters)
|
|
{
|
|
if (GetCompletedQuestsByCharacter(character).Count == 0)
|
|
{
|
|
charactersToSync.Add(character);
|
|
}
|
|
}
|
|
if (charactersToSync.Count == 0)
|
|
{
|
|
log.Information("[QuestRotation] No characters need sync - all have existing data");
|
|
return false;
|
|
}
|
|
log.Information("[QuestRotation] ═══ Starting Sync Rotation ═══");
|
|
log.Information($"[QuestRotation] Characters to sync: {charactersToSync.Count}");
|
|
log.Information("[QuestRotation] Characters: " + string.Join(", ", charactersToSync));
|
|
currentState = new RotationState
|
|
{
|
|
CurrentStopQuestId = 0u,
|
|
SelectedCharacters = new List<string>(charactersToSync),
|
|
RemainingCharacters = new List<string>(charactersToSync),
|
|
CompletedCharacters = new List<string>(),
|
|
Phase = RotationPhase.InitializingFirstCharacter,
|
|
PhaseStartTime = DateTime.Now,
|
|
RotationStartTime = DateTime.Now,
|
|
HasQuestBeenAccepted = false,
|
|
IsSyncOnlyMode = true
|
|
};
|
|
isRotationActive = true;
|
|
log.Information("[QuestRotation] Sync rotation started successfully!");
|
|
return true;
|
|
}
|
|
|
|
public RotationState GetCurrentState()
|
|
{
|
|
return currentState;
|
|
}
|
|
|
|
public (int completed, int total) GetRotationProgress(uint questId)
|
|
{
|
|
if (currentState.SelectedCharacters.Count == 0)
|
|
{
|
|
return (completed: 0, total: 0);
|
|
}
|
|
return GetRotationProgress(questId, currentState.SelectedCharacters);
|
|
}
|
|
|
|
public (int completed, int total) GetRotationProgress(uint questId, List<string> characters)
|
|
{
|
|
if (characters == null || characters.Count == 0)
|
|
{
|
|
return (completed: 0, total: 0);
|
|
}
|
|
int completed = 0;
|
|
foreach (string character in characters)
|
|
{
|
|
if (HasCharacterCompletedQuest(questId, character))
|
|
{
|
|
completed++;
|
|
}
|
|
}
|
|
return (completed: completed, total: characters.Count);
|
|
}
|
|
|
|
public List<StopPoint> GetAllStopPoints()
|
|
{
|
|
return new List<StopPoint>(stopPoints);
|
|
}
|
|
|
|
public void LoadQuestCompletionData(Dictionary<uint, List<string>> data)
|
|
{
|
|
if (data != null && data.Count > 0)
|
|
{
|
|
questCompletionByCharacter = new Dictionary<uint, List<string>>(data);
|
|
log.Information($"[QuestRotation] Loaded quest completion data for {data.Count} quests");
|
|
int count = 0;
|
|
foreach (KeyValuePair<uint, List<string>> kvp in data)
|
|
{
|
|
if (count < 5)
|
|
{
|
|
log.Information($"[QuestRotation] DEBUG: Quest {kvp.Key} -> {kvp.Value.Count} characters: {string.Join(", ", kvp.Value)}");
|
|
count++;
|
|
}
|
|
}
|
|
if (data.Count > 5)
|
|
{
|
|
log.Information($"[QuestRotation] DEBUG: ... and {data.Count - 5} more quests");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log.Information("[QuestRotation] No quest completion data to load (empty or null)");
|
|
}
|
|
}
|
|
|
|
public Dictionary<uint, List<string>> GetQuestCompletionData()
|
|
{
|
|
return new Dictionary<uint, List<string>>(questCompletionByCharacter);
|
|
}
|
|
|
|
public void SetDCTravelService(DCTravelService service)
|
|
{
|
|
dcTravelService = service;
|
|
log.Information("[QuestRotation] DC Travel service linked");
|
|
}
|
|
|
|
public void SetSafeWaitService(CharacterSafeWaitService service)
|
|
{
|
|
safeWaitService = service;
|
|
log.Information("[QuestRotation] Safe Wait service linked");
|
|
}
|
|
|
|
public void SetPreCheckService(QuestPreCheckService service)
|
|
{
|
|
preCheckService = service;
|
|
log.Information("[QuestRotation] PreCheck service linked");
|
|
}
|
|
|
|
public void SetMovementMonitor(MovementMonitorService service)
|
|
{
|
|
movementMonitor = service;
|
|
log.Information("[QuestRotation] Movement Monitor service linked");
|
|
}
|
|
|
|
public void SetCombatDutyDetection(CombatDutyDetectionService service)
|
|
{
|
|
combatDutyDetection = service;
|
|
log.Information("[QuestRotation] Combat/Duty Detection service linked");
|
|
}
|
|
|
|
public void SetDeathHandler(DeathHandlerService service)
|
|
{
|
|
deathHandler = service;
|
|
log.Information("[QuestRotation] Death Handler service linked");
|
|
}
|
|
|
|
public void SetDungeonAutomation(DungeonAutomationService service)
|
|
{
|
|
dungeonAutomation = service;
|
|
log.Information("[QuestRotation] Dungeon Automation service linked");
|
|
}
|
|
|
|
public void SetStepsOfFaithHandler(StepsOfFaithHandler service)
|
|
{
|
|
stepsOfFaithHandler = service;
|
|
log.Information("[QuestRotation] Steps of Faith Handler service linked");
|
|
}
|
|
|
|
public void SetErrorRecoveryService(ErrorRecoveryService service)
|
|
{
|
|
errorRecoveryService = service;
|
|
log.Information("[QuestRotation] Error Recovery service linked");
|
|
}
|
|
|
|
private void MarkQuestCompleted(uint questId, string characterName)
|
|
{
|
|
if (!questCompletionByCharacter.ContainsKey(questId))
|
|
{
|
|
questCompletionByCharacter[questId] = new List<string>();
|
|
}
|
|
if (!questCompletionByCharacter[questId].Contains(characterName))
|
|
{
|
|
questCompletionByCharacter[questId].Add(characterName);
|
|
log.Debug($"[QuestRotation] Marked {characterName} as completed quest {questId}");
|
|
onDataChanged?.Invoke();
|
|
}
|
|
}
|
|
|
|
public List<uint> GetCompletedQuestsByCharacter(string characterName)
|
|
{
|
|
List<uint> completedQuests = new List<uint>();
|
|
foreach (KeyValuePair<uint, List<string>> kvp in questCompletionByCharacter)
|
|
{
|
|
if (kvp.Value.Contains(characterName))
|
|
{
|
|
completedQuests.Add(kvp.Key);
|
|
}
|
|
}
|
|
if (preCheckService != null)
|
|
{
|
|
foreach (uint questId in preCheckService.GetCompletedQuests(characterName))
|
|
{
|
|
if (!completedQuests.Contains(questId))
|
|
{
|
|
completedQuests.Add(questId);
|
|
}
|
|
}
|
|
}
|
|
return completedQuests;
|
|
}
|
|
|
|
private bool HasCharacterCompletedQuest(uint questId, string characterName)
|
|
{
|
|
if (questCompletionByCharacter.TryGetValue(questId, out List<string> characters) && characters.Contains(characterName))
|
|
{
|
|
return true;
|
|
}
|
|
if (preCheckService != null)
|
|
{
|
|
bool? status = preCheckService.GetQuestStatus(characterName, questId);
|
|
if (status.HasValue && status.Value)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void ScanAndSaveAllCompletedQuests(string characterName)
|
|
{
|
|
if (string.IsNullOrEmpty(characterName))
|
|
{
|
|
return;
|
|
}
|
|
int questsScanned = 0;
|
|
int questsCompleted = 0;
|
|
try
|
|
{
|
|
for (uint questId = 65536u; questId < 72000; questId++)
|
|
{
|
|
try
|
|
{
|
|
if (QuestManager.IsQuestComplete(questId))
|
|
{
|
|
MarkQuestCompleted(questId, characterName);
|
|
questsCompleted++;
|
|
}
|
|
questsScanned++;
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
log.Information($"[QuestRotation] Scanned {questsScanned} quests, found {questsCompleted} completed for {characterName}");
|
|
onDataChanged?.Invoke();
|
|
framework.RunOnFrameworkThread(delegate
|
|
{
|
|
if (currentState.Phase == RotationPhase.ScanningQuests)
|
|
{
|
|
currentState.Phase = RotationPhase.CheckingQuestCompletion;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
log.Information("[QuestRotation] Quest scan complete - moving to quest check");
|
|
}
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Error scanning quests for " + characterName + ": " + ex.Message);
|
|
}
|
|
}
|
|
|
|
public void SyncQuestDataForCurrentCharacter()
|
|
{
|
|
IPlayerCharacter player = clientState.LocalPlayer;
|
|
if (player == null)
|
|
{
|
|
log.Warning("[QuestRotation] No character logged in - cannot sync quest data");
|
|
return;
|
|
}
|
|
string characterName = $"{player.Name}@{player.HomeWorld.Value.Name}";
|
|
log.Information("[QuestRotation] Starting quest data sync for " + characterName);
|
|
ScanAndSaveAllCompletedQuests(characterName);
|
|
}
|
|
|
|
public void AbortRotation()
|
|
{
|
|
log.Information("[QuestRotation] Aborting rotation");
|
|
foreach (StopPoint stopPoint in stopPoints)
|
|
{
|
|
stopPoint.IsActive = false;
|
|
}
|
|
currentState = new RotationState
|
|
{
|
|
Phase = RotationPhase.Idle
|
|
};
|
|
isRotationActive = false;
|
|
combatDutyDetection?.SetRotationActive(active: false);
|
|
combatDutyDetection?.Reset();
|
|
deathHandler?.SetRotationActive(active: false);
|
|
deathHandler?.Reset();
|
|
if (dungeonAutomation != null)
|
|
{
|
|
dungeonAutomation.Reset();
|
|
log.Information("[QuestRotation] Dungeon automation reset");
|
|
}
|
|
if (movementMonitor != null && movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StopMonitoring();
|
|
log.Information("[QuestRotation] Movement monitor stopped");
|
|
}
|
|
}
|
|
|
|
private void OnFrameworkUpdate(IFramework framework)
|
|
{
|
|
DateTime now = DateTime.Now;
|
|
if ((now - lastCheckTime).TotalMilliseconds < 250.0)
|
|
{
|
|
return;
|
|
}
|
|
lastCheckTime = now;
|
|
if (isRotationActive && errorRecoveryService != null && errorRecoveryService.IsErrorDisconnect)
|
|
{
|
|
string charToRelog = errorRecoveryService.LastDisconnectedCharacter ?? currentState.CurrentCharacter;
|
|
if (!string.IsNullOrEmpty(charToRelog))
|
|
{
|
|
log.Warning("[ErrorRecovery] Disconnect detected for " + charToRelog);
|
|
log.Information("[ErrorRecovery] Automatically relogging to " + charToRelog + "...");
|
|
errorRecoveryService.Reset();
|
|
currentState.Phase = RotationPhase.WaitingForCharacterLogin;
|
|
currentState.CurrentCharacter = charToRelog;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
autoRetainerIpc.SwitchCharacter(charToRelog);
|
|
log.Information("[ErrorRecovery] Relog initiated for " + charToRelog);
|
|
return;
|
|
}
|
|
log.Warning("[ErrorRecovery] Disconnect detected but no character to relog to");
|
|
errorRecoveryService.Reset();
|
|
}
|
|
if (deathHandler != null && combatDutyDetection != null && !combatDutyDetection.IsInDuty)
|
|
{
|
|
deathHandler.Update();
|
|
}
|
|
if (dungeonAutomation != null)
|
|
{
|
|
if (submarineManager.IsSubmarinePaused)
|
|
{
|
|
log.Debug("[QuestRotation] Submarine multi-mode active - skipping dungeon validation");
|
|
}
|
|
else
|
|
{
|
|
dungeonAutomation.Update();
|
|
if (isRotationActive && configuration.EnableAutoDutyUnsynced && !dungeonAutomation.IsWaitingForParty && currentState.Phase != RotationPhase.WaitingForCharacterLogin && currentState.Phase != RotationPhase.WaitingBeforeCharacterSwitch && currentState.Phase != RotationPhase.WaitingForHomeworldReturn && currentState.Phase != RotationPhase.ScanningQuests && currentState.Phase != RotationPhase.CheckingQuestCompletion && currentState.Phase != RotationPhase.InitializingFirstCharacter)
|
|
{
|
|
_ = submarineManager.IsSubmarinePaused;
|
|
}
|
|
}
|
|
}
|
|
if (combatDutyDetection != null)
|
|
{
|
|
combatDutyDetection.Update();
|
|
if (combatDutyDetection.JustEnteredDuty && isRotationActive)
|
|
{
|
|
string currentQuestIdStr = questionableIPC.GetCurrentQuestId();
|
|
uint questId = 0u;
|
|
if (!string.IsNullOrEmpty(currentQuestIdStr))
|
|
{
|
|
uint.TryParse(currentQuestIdStr, out questId);
|
|
}
|
|
combatDutyDetection.SetCurrentQuestId(questId);
|
|
bool isAutoDutyDungeon = dungeonAutomation != null && dungeonAutomation.IsInAutoDutyDungeon;
|
|
combatDutyDetection.SetAutoDutyDungeon(isAutoDutyDungeon);
|
|
if (isAutoDutyDungeon)
|
|
{
|
|
log.Information("[QuestRotation] AutoDuty Dungeon entered - /ad stop after 1s");
|
|
dungeonAutomation?.OnDutyEntered();
|
|
}
|
|
else if (questId != 0)
|
|
{
|
|
bool shouldLog = lastSoloDutyQuestId != questId;
|
|
if (questId == 811)
|
|
{
|
|
if (shouldLog)
|
|
{
|
|
log.Information("[QuestRotation] Quest 811 Solo Duty - stopping RSR, NO combat commands");
|
|
lastSoloDutyQuestId = questId;
|
|
}
|
|
}
|
|
else if (shouldLog)
|
|
{
|
|
log.Information("[QuestRotation] Solo Duty entered - combat commands will activate after 8s");
|
|
lastSoloDutyQuestId = questId;
|
|
}
|
|
if (questId == 4591 && stepsOfFaithHandler != null)
|
|
{
|
|
IPlayerCharacter player = clientState.LocalPlayer;
|
|
string characterName = ((player != null) ? $"{player.Name}@{player.HomeWorld.Value.Name}" : string.Empty);
|
|
if (stepsOfFaithHandler.ShouldActivate(questId, isInSoloDuty: true))
|
|
{
|
|
log.Information("[QuestRotation] Triggering Steps of Faith handler (will wait for conditions inside)...");
|
|
Task.Run(delegate
|
|
{
|
|
stepsOfFaithHandler.Execute(characterName);
|
|
});
|
|
}
|
|
}
|
|
if (questId == 811)
|
|
{
|
|
try
|
|
{
|
|
commandManager.ProcessCommand("/rotation off");
|
|
log.Information("[QuestRotation] ✓ /rotation off sent for Quest 811");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Failed to stop RSR: " + ex.Message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (combatDutyDetection.JustExitedDuty && isRotationActive && dungeonAutomation != null)
|
|
{
|
|
dungeonAutomation.OnDutyExited();
|
|
if ((DateTime.Now - combatDutyDetection.DutyExitTime).TotalSeconds >= 8.0)
|
|
{
|
|
if (dungeonAutomation.IsInAutoDutyDungeon)
|
|
{
|
|
dungeonAutomation.DisbandParty();
|
|
}
|
|
try
|
|
{
|
|
commandManager.ProcessCommand("/qst start");
|
|
log.Information("[QuestRotation] ✓ /qst start (after duty exit)");
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
log.Error("[QuestRotation] Failed to restart quest: " + ex2.Message);
|
|
}
|
|
combatDutyDetection.ClearDutyExitFlag();
|
|
}
|
|
}
|
|
if (combatDutyDetection.ShouldPauseAutomation && movementMonitor != null && movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StopMonitoring();
|
|
log.Debug("[QuestRotation] Movement monitor paused for combat/duty");
|
|
}
|
|
else if (!combatDutyDetection.ShouldPauseAutomation && movementMonitor != null && !movementMonitor.IsMonitoring && isRotationActive && configuration.EnableMovementMonitor && currentState.Phase != RotationPhase.WaitingForCharacterLogin && currentState.Phase != RotationPhase.WaitingBeforeCharacterSwitch && currentState.Phase != RotationPhase.WaitingForHomeworldReturn && currentState.Phase != RotationPhase.ScanningQuests && currentState.Phase != RotationPhase.CheckingQuestCompletion && currentState.Phase != RotationPhase.InitializingFirstCharacter)
|
|
{
|
|
movementMonitor.StartMonitoring();
|
|
log.Debug("[QuestRotation] Movement monitor resumed after combat/duty");
|
|
}
|
|
}
|
|
if (submarineManager.IsSubmarineJustCompleted && !submarineManager.IsSubmarineCooldownActive())
|
|
{
|
|
log.Information("[QuestRotation] Submarine cooldown expired - re-enabling submarine checks");
|
|
submarineManager.ClearSubmarineJustCompleted();
|
|
}
|
|
if (!isRotationActive)
|
|
{
|
|
return;
|
|
}
|
|
CheckLevelStopCondition();
|
|
switch (currentState.Phase)
|
|
{
|
|
case RotationPhase.InitializingFirstCharacter:
|
|
HandleInitializingFirstCharacter();
|
|
break;
|
|
case RotationPhase.WaitingForCharacterLogin:
|
|
HandleWaitingForCharacterLogin();
|
|
break;
|
|
case RotationPhase.ScanningQuests:
|
|
HandleScanningQuests();
|
|
break;
|
|
case RotationPhase.CheckingQuestCompletion:
|
|
HandleCheckingQuestCompletion();
|
|
break;
|
|
case RotationPhase.DCTraveling:
|
|
if ((DateTime.Now - currentState.PhaseStartTime).TotalMinutes > 3.0)
|
|
{
|
|
log.Error("[QuestRotation] DC Travel timeout after 3 minutes - skipping character");
|
|
SkipToNextCharacter();
|
|
}
|
|
break;
|
|
case RotationPhase.WaitingForQuestStart:
|
|
case RotationPhase.QuestActive:
|
|
HandleQuestMonitoring();
|
|
break;
|
|
case RotationPhase.WaitingBeforeCharacterSwitch:
|
|
HandleWaitingBeforeCharacterSwitch();
|
|
break;
|
|
case RotationPhase.WaitingForHomeworldReturn:
|
|
HandleWaitingForHomeworldReturn();
|
|
break;
|
|
case RotationPhase.Completed:
|
|
HandleCompleted();
|
|
break;
|
|
case RotationPhase.Questing:
|
|
case RotationPhase.InCombat:
|
|
case RotationPhase.InDungeon:
|
|
case RotationPhase.HandlingSubmarines:
|
|
case RotationPhase.SyncingCharacterData:
|
|
case RotationPhase.WaitingForChauffeur:
|
|
case RotationPhase.TravellingWithChauffeur:
|
|
case RotationPhase.WaitingForNextCharacterSwitch:
|
|
case RotationPhase.Error:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void CheckLevelStopCondition()
|
|
{
|
|
StopConditionData stopLevelData = questionableIPC.GetLevelStopCondition();
|
|
if (stopLevelData == null || !stopLevelData.Enabled)
|
|
{
|
|
return;
|
|
}
|
|
int stopLevel = stopLevelData.TargetValue;
|
|
int currentLevel = 0;
|
|
if (clientState.LocalPlayer != null)
|
|
{
|
|
currentLevel = clientState.LocalPlayer.Level;
|
|
}
|
|
if (currentLevel < stopLevel)
|
|
{
|
|
return;
|
|
}
|
|
log.Information($"[QuestRotation] Level Stop Condition reached (Level {currentLevel} >= {stopLevel})");
|
|
if (currentState.Phase == RotationPhase.Questing || currentState.Phase == RotationPhase.QuestActive || currentState.Phase == RotationPhase.WaitingForQuestStart)
|
|
{
|
|
log.Information("[QuestRotation] Level reached - stopping quest and switching character...");
|
|
try
|
|
{
|
|
commandManager.ProcessCommand("/qst stop");
|
|
log.Information("[QuestRotation] ✓ /qst stop (level stop condition)");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Failed to send /qst stop: " + ex.Message);
|
|
}
|
|
MarkCharacterCompleted(currentState.CurrentCharacter, "level reached");
|
|
currentState.Phase = RotationPhase.WaitingForHomeworldReturn;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
log.Information("[QuestRotation] Waiting 5s before homeworld return...");
|
|
}
|
|
}
|
|
|
|
private unsafe bool IsTerritoryLoaded()
|
|
{
|
|
GameMain* gameMain = GameMain.Instance();
|
|
if (gameMain == null)
|
|
{
|
|
return false;
|
|
}
|
|
return gameMain->TerritoryLoadState == 2;
|
|
}
|
|
|
|
private void HandleInitializingFirstCharacter()
|
|
{
|
|
if (currentState.RemainingCharacters.Count == 0)
|
|
{
|
|
log.Information("[QuestRotation] No remaining characters - rotation complete");
|
|
currentState.Phase = RotationPhase.Completed;
|
|
isRotationActive = false;
|
|
combatDutyDetection?.SetRotationActive(active: false);
|
|
deathHandler?.SetRotationActive(active: false);
|
|
return;
|
|
}
|
|
string firstChar = currentState.RemainingCharacters[0];
|
|
currentState.CurrentCharacter = firstChar;
|
|
log.Information("[QuestRotation] >>> Phase 1: Initializing first character: " + firstChar);
|
|
if (autoRetainerIpc.SwitchCharacter(firstChar))
|
|
{
|
|
currentState.Phase = RotationPhase.WaitingForCharacterLogin;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
log.Information("[QuestRotation] Character switch initiated to " + firstChar);
|
|
}
|
|
else
|
|
{
|
|
log.Error("[QuestRotation] Failed to switch to " + firstChar);
|
|
currentState.Phase = RotationPhase.Error;
|
|
currentState.ErrorMessage = "Failed to switch to " + firstChar;
|
|
}
|
|
}
|
|
|
|
private void HandleWaitingForCharacterLogin()
|
|
{
|
|
if (movementMonitor != null && movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StopMonitoring();
|
|
log.Debug("[QuestRotation] Movement monitor stopped during character login");
|
|
}
|
|
if ((DateTime.Now - currentState.PhaseStartTime).TotalSeconds > 60.0)
|
|
{
|
|
log.Error("[QuestRotation] Login timeout for " + currentState.CurrentCharacter);
|
|
SkipToNextCharacter();
|
|
return;
|
|
}
|
|
string currentLoggedInChar = autoRetainerIpc.GetCurrentCharacter();
|
|
if (string.IsNullOrEmpty(currentLoggedInChar) || !(currentLoggedInChar == currentState.CurrentCharacter) || (DateTime.Now - currentState.PhaseStartTime).TotalSeconds < 2.0 || (DateTime.Now - currentState.PhaseStartTime).TotalSeconds < 5.0)
|
|
{
|
|
return;
|
|
}
|
|
log.Information("[QuestRotation] >>> Phase 2: Successfully logged in as " + currentLoggedInChar);
|
|
if (preCheckService != null)
|
|
{
|
|
log.Information("[QuestRotation] Scanning quest status for current character...");
|
|
try
|
|
{
|
|
preCheckService.ScanCurrentCharacterQuestStatus();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Error scanning quest status: " + ex.Message);
|
|
}
|
|
}
|
|
currentState.Phase = RotationPhase.ScanningQuests;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
log.Information("[QuestRotation] Starting quest scan for " + currentState.CurrentCharacter + "...");
|
|
Task.Run(delegate
|
|
{
|
|
ScanAndSaveAllCompletedQuests(currentState.CurrentCharacter);
|
|
});
|
|
}
|
|
|
|
private void HandleScanningQuests()
|
|
{
|
|
if ((DateTime.Now - currentState.PhaseStartTime).TotalSeconds > 30.0)
|
|
{
|
|
log.Warning("[QuestRotation] Quest scan timeout - proceeding anyway");
|
|
currentState.Phase = RotationPhase.CheckingQuestCompletion;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
}
|
|
}
|
|
|
|
private void HandleCheckingQuestCompletion()
|
|
{
|
|
uint questId = currentState.CurrentStopQuestId;
|
|
bool isQuestComplete = false;
|
|
try
|
|
{
|
|
isQuestComplete = QuestManager.IsQuestComplete(questId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Error checking quest completion: " + ex.Message);
|
|
}
|
|
if (isQuestComplete)
|
|
{
|
|
log.Information($"[QuestRotation] {currentState.CurrentCharacter} already completed quest {questId} - skipping");
|
|
MarkCharacterCompleted(currentState.CurrentCharacter, $"quest {questId} already complete");
|
|
MarkQuestCompleted(questId, currentState.CurrentCharacter);
|
|
SkipToNextCharacter();
|
|
return;
|
|
}
|
|
if (questId == 0)
|
|
{
|
|
if (currentState.IsSyncOnlyMode)
|
|
{
|
|
log.Information("[QuestRotation] Sync-Only Mode: Quest scan complete for " + currentState.CurrentCharacter + " - moving to next character");
|
|
MarkCharacterCompleted(currentState.CurrentCharacter, "quest data synchronized");
|
|
SkipToNextCharacter();
|
|
return;
|
|
}
|
|
StopConditionData stopLevelData = questionableIPC.GetLevelStopCondition();
|
|
if (stopLevelData != null && stopLevelData.Enabled)
|
|
{
|
|
byte currentLevel = clientState.LocalPlayer?.Level ?? 0;
|
|
if (currentLevel >= stopLevelData.TargetValue)
|
|
{
|
|
log.Information($"[QuestRotation] {currentState.CurrentCharacter} already at target level ({currentLevel} >= {stopLevelData.TargetValue}) - skipping");
|
|
MarkCharacterCompleted(currentState.CurrentCharacter, "target level reached");
|
|
SkipToNextCharacter();
|
|
return;
|
|
}
|
|
log.Information($"[QuestRotation] Level-Only Mode: {currentState.CurrentCharacter} at level {currentLevel}, targeting {stopLevelData.TargetValue}");
|
|
}
|
|
}
|
|
log.Information($"[QuestRotation] {currentState.CurrentCharacter} needs to complete quest {questId}");
|
|
if (configuration.EnableDCTravel && dcTravelService != null && dcTravelService.ShouldPerformDCTravel())
|
|
{
|
|
log.Information("[QuestRotation] === DC TRAVEL REQUIRED ===");
|
|
log.Information("[QuestRotation] Performing DC travel before starting quest...");
|
|
currentState.Phase = RotationPhase.DCTraveling;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
PerformDCTravelAndStartQuest();
|
|
return;
|
|
}
|
|
log.Information("[QuestRotation] >>> Phase 4: Waiting for quest to start...");
|
|
if (configuration.EnableMovementMonitor && movementMonitor != null && !movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StartMonitoring();
|
|
log.Information("[QuestRotation] Movement monitor started for quest");
|
|
}
|
|
try
|
|
{
|
|
commandManager.ProcessCommand("/qst start");
|
|
log.Information("[QuestRotation] Sent /qst start command");
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
log.Error("[QuestRotation] Failed to send /qst start: " + ex2.Message);
|
|
}
|
|
currentState.Phase = RotationPhase.WaitingForQuestStart;
|
|
currentState.HasQuestBeenAccepted = false;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
}
|
|
|
|
private void PerformDCTravelAndStartQuest()
|
|
{
|
|
if (dcTravelService == null)
|
|
{
|
|
return;
|
|
}
|
|
Task.Run(async delegate
|
|
{
|
|
try
|
|
{
|
|
if (await dcTravelService.PerformDCTravel())
|
|
{
|
|
log.Information("[QuestRotation] DC travel completed - starting quest");
|
|
try
|
|
{
|
|
commandManager.ProcessCommand("/qst start");
|
|
log.Information("[QuestRotation] Sent /qst start command");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Failed to send /qst start: " + ex.Message);
|
|
}
|
|
currentState.Phase = RotationPhase.WaitingForQuestStart;
|
|
currentState.HasQuestBeenAccepted = false;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
}
|
|
else
|
|
{
|
|
log.Error("[QuestRotation] DC travel failed - skipping character");
|
|
SkipToNextCharacter();
|
|
}
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
log.Error("[QuestRotation] DC travel error: " + ex2.Message);
|
|
SkipToNextCharacter();
|
|
}
|
|
});
|
|
}
|
|
|
|
private unsafe void HandleQuestMonitoring()
|
|
{
|
|
uint questId = currentState.CurrentStopQuestId;
|
|
bool hasReachedStopSequence = false;
|
|
StopPoint stopPointForSubmarine = stopPoints.FirstOrDefault((StopPoint sp) => sp.QuestId == questId && sp.IsActive);
|
|
if (stopPointForSubmarine != null && stopPointForSubmarine.Sequence.HasValue)
|
|
{
|
|
string currentQuestIdStr = questionableIPC.GetCurrentQuestId();
|
|
byte? currentSequence = questionableIPC.GetCurrentSequence();
|
|
if (!string.IsNullOrEmpty(currentQuestIdStr) && currentSequence.HasValue && uint.TryParse(currentQuestIdStr, out var currentQuestId) && currentQuestId == questId && currentSequence.Value >= stopPointForSubmarine.Sequence.Value)
|
|
{
|
|
hasReachedStopSequence = true;
|
|
log.Debug($"[QuestRotation] Stop sequence reached (Quest {questId} Seq {currentSequence.Value} >= {stopPointForSubmarine.Sequence.Value}) - skipping submarine check");
|
|
}
|
|
}
|
|
if (!hasReachedStopSequence && !submarineManager.IsSubmarinePaused && !submarineManager.IsSubmarineCooldownActive())
|
|
{
|
|
TimeSpan submarineCheckInterval = TimeSpan.FromSeconds(configuration.SubmarineCheckInterval);
|
|
if (DateTime.Now - lastSubmarineCheckTime >= submarineCheckInterval)
|
|
{
|
|
lastSubmarineCheckTime = DateTime.Now;
|
|
if (submarineManager.CheckSubmarines())
|
|
{
|
|
log.Information("[QuestRotation] ========================================");
|
|
log.Information("[QuestRotation] === SUBMARINES READY - WAITING FOR QUEST COMPLETION ===");
|
|
log.Information("[QuestRotation] ========================================");
|
|
waitingForQuestAcceptForSubmarines = true;
|
|
log.Information("[QuestRotation] Waiting for current quest to complete, then will pause for submarines...");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (waitingForQuestAcceptForSubmarines)
|
|
{
|
|
if (QuestManager.Instance() != null && questionableIPC.GetCurrentSequence() == 0)
|
|
{
|
|
log.Information("[QuestRotation] ========================================");
|
|
log.Information("[QuestRotation] === QUEST ACCEPTED - STOPPING QUESTIONABLE ===");
|
|
log.Information("[QuestRotation] ========================================");
|
|
log.Information("[QuestRotation] Stopping Questionable...");
|
|
framework.RunOnFrameworkThread(delegate
|
|
{
|
|
commandManager.ProcessCommand("/qst stop");
|
|
}).Wait();
|
|
log.Information("[QuestRotation] ✓ /qst stop command sent");
|
|
log.Information("[QuestRotation] Enabling Multi-Mode...");
|
|
submarineManager.EnableMultiMode();
|
|
waitingForQuestAcceptForSubmarines = false;
|
|
if (movementMonitor != null && movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StopMonitoring();
|
|
log.Information("[QuestRotation] Movement monitor stopped for submarine operations");
|
|
}
|
|
log.Information("[QuestRotation] Multi-Mode enabled - submarines will now run");
|
|
}
|
|
}
|
|
else if (submarineManager.IsSubmarinePaused)
|
|
{
|
|
if (movementMonitor != null && movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StopMonitoring();
|
|
log.Debug("[QuestRotation] Movement monitor stopped during submarine multi-mode");
|
|
}
|
|
log.Debug("[QuestRotation] Submarines running in Multi-Mode...");
|
|
int waitTime = submarineManager.CheckSubmarinesSoon();
|
|
if (waitTime == 0)
|
|
{
|
|
log.Information("[QuestRotation] ========================================");
|
|
log.Information("[QuestRotation] === NO SUBMARINES IN NEXT 2 MINUTES ===");
|
|
log.Information("[QuestRotation] ========================================");
|
|
log.Information("[QuestRotation] Disabling Multi-Mode and returning to character...");
|
|
submarineManager.DisableMultiModeAndReturn();
|
|
string currentChar = currentState.CurrentCharacter;
|
|
log.Information("[QuestRotation] Relogging to " + currentChar + "...");
|
|
if (autoRetainerIpc.SwitchCharacter(currentChar))
|
|
{
|
|
log.Information("[QuestRotation] Relog initiated - waiting for character login...");
|
|
submarineManager.CompleteSubmarineRelog();
|
|
if (configuration.EnableMovementMonitor && movementMonitor != null && !movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StartMonitoring();
|
|
log.Information("[QuestRotation] Movement monitor restarted after submarine operations");
|
|
}
|
|
log.Information("[QuestRotation] Questionable will auto-resume when character is ready");
|
|
}
|
|
else
|
|
{
|
|
log.Error("[QuestRotation] Failed to relog to " + currentChar + "!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log.Debug($"[QuestRotation] Submarine ready in {waitTime}s - waiting...");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (submarineManager.IsSubmarineCooldownActive() || waitingForQuestAcceptForSubmarines)
|
|
{
|
|
return;
|
|
}
|
|
questTrackingService.UpdateCurrentCharacterQuests(currentState.CurrentCharacter);
|
|
StopPoint activeStopPoint = stopPoints.FirstOrDefault((StopPoint sp) => sp.QuestId == questId && sp.IsActive);
|
|
bool shouldRotate = false;
|
|
if (activeStopPoint != null && activeStopPoint.Sequence.HasValue)
|
|
{
|
|
string currentQuestIdStr2 = questionableIPC.GetCurrentQuestId();
|
|
byte? currentSequence2 = questionableIPC.GetCurrentSequence();
|
|
if (!string.IsNullOrEmpty(currentQuestIdStr2) && currentSequence2.HasValue && uint.TryParse(currentQuestIdStr2, out var currentQuestId2))
|
|
{
|
|
if (currentQuestId2 == questId)
|
|
{
|
|
if (currentSequence2.Value >= activeStopPoint.Sequence.Value)
|
|
{
|
|
log.Information($"[QuestRotation] ✓ Quest {questId} Sequence {activeStopPoint.Sequence.Value} reached by {currentState.CurrentCharacter}!");
|
|
log.Information($"[QuestRotation] Current Sequence: {currentSequence2.Value} (reached {activeStopPoint.Sequence.Value})");
|
|
shouldRotate = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool isQuestComplete = false;
|
|
try
|
|
{
|
|
isQuestComplete = QuestManager.IsQuestComplete(questId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Error checking quest completion: " + ex.Message);
|
|
return;
|
|
}
|
|
if (isQuestComplete)
|
|
{
|
|
log.Information($"[QuestRotation] ✓ Quest {questId} completed by {currentState.CurrentCharacter}!");
|
|
shouldRotate = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool isQuestComplete2 = false;
|
|
try
|
|
{
|
|
isQuestComplete2 = QuestManager.IsQuestComplete(questId);
|
|
}
|
|
catch (Exception ex2)
|
|
{
|
|
log.Error("[QuestRotation] Error checking quest completion: " + ex2.Message);
|
|
return;
|
|
}
|
|
if (isQuestComplete2)
|
|
{
|
|
log.Information($"[QuestRotation] ✓ Quest {questId} completed by {currentState.CurrentCharacter}!");
|
|
shouldRotate = true;
|
|
}
|
|
}
|
|
if (shouldRotate)
|
|
{
|
|
log.Information($"[QuestRotation] ✓ Quest {questId} completed by {currentState.CurrentCharacter}!");
|
|
if (stepsOfFaithHandler != null && questId == 4591)
|
|
{
|
|
stepsOfFaithHandler.Reset();
|
|
}
|
|
try
|
|
{
|
|
commandManager.ProcessCommand("/qst stop");
|
|
log.Information("[QuestRotation] Sent /qst stop command");
|
|
}
|
|
catch (Exception ex3)
|
|
{
|
|
log.Error("[QuestRotation] Failed to send /qst stop: " + ex3.Message);
|
|
}
|
|
log.Information("[QuestRotation] Updating quest completion data for " + currentState.CurrentCharacter + "...");
|
|
ScanAndSaveAllCompletedQuests(currentState.CurrentCharacter);
|
|
List<string> completed = currentState.CompletedCharacters;
|
|
List<string> remaining = currentState.RemainingCharacters;
|
|
log.Debug($"[QuestRotation] DEBUG - Before update: CompletedCharacters count = {completed.Count}, RemainingCharacters count = {remaining.Count}");
|
|
if (!completed.Contains(currentState.CurrentCharacter))
|
|
{
|
|
completed.Add(currentState.CurrentCharacter);
|
|
log.Information("[QuestRotation] ✓ Added " + currentState.CurrentCharacter + " to CompletedCharacters list");
|
|
}
|
|
else
|
|
{
|
|
log.Warning("[QuestRotation] " + currentState.CurrentCharacter + " was already in CompletedCharacters list!");
|
|
}
|
|
bool wasRemoved = remaining.Remove(currentState.CurrentCharacter);
|
|
log.Information($"[QuestRotation] Removed {currentState.CurrentCharacter} from RemainingCharacters list: {wasRemoved}");
|
|
currentState.CompletedCharacters = completed;
|
|
currentState.RemainingCharacters = remaining;
|
|
log.Debug($"[QuestRotation] DEBUG - After update: CompletedCharacters count = {currentState.CompletedCharacters.Count}, RemainingCharacters count = {currentState.RemainingCharacters.Count}");
|
|
log.Debug("[QuestRotation] DEBUG - CompletedCharacters: " + string.Join(", ", currentState.CompletedCharacters));
|
|
log.Debug("[QuestRotation] DEBUG - RemainingCharacters: " + string.Join(", ", currentState.RemainingCharacters));
|
|
currentState.Phase = RotationPhase.WaitingForHomeworldReturn;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
log.Information("[QuestRotation] Waiting 5s before homeworld return...");
|
|
}
|
|
else
|
|
{
|
|
if (currentState.Phase != RotationPhase.WaitingForQuestStart)
|
|
{
|
|
return;
|
|
}
|
|
bool isQuestAccepted = false;
|
|
try
|
|
{
|
|
QuestManager* questManager = QuestManager.Instance();
|
|
if (questManager != null)
|
|
{
|
|
isQuestAccepted = questManager->IsQuestAccepted((ushort)questId);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
if (isQuestAccepted)
|
|
{
|
|
log.Information($"[QuestRotation] Quest {questId} accepted by {currentState.CurrentCharacter} - now monitoring for completion");
|
|
currentState.Phase = RotationPhase.QuestActive;
|
|
currentState.HasQuestBeenAccepted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandleCompleted()
|
|
{
|
|
log.Information("[QuestRotation] ═══ ROTATION COMPLETED ═══");
|
|
log.Information($"[QuestRotation] All {currentState.CompletedCharacters.Count} characters completed quest {currentState.CurrentStopQuestId}");
|
|
if (dcTravelService != null && dcTravelService.IsDCTravelCompleted())
|
|
{
|
|
log.Information("[QuestRotation] DC Travel state reset after rotation completion");
|
|
dcTravelService.ResetDCTravelState();
|
|
}
|
|
StopPoint stopPoint = stopPoints.FirstOrDefault((StopPoint sp) => sp.QuestId == currentState.CurrentStopQuestId);
|
|
if (stopPoint != null)
|
|
{
|
|
stopPoint.IsActive = false;
|
|
}
|
|
isRotationActive = false;
|
|
combatDutyDetection?.SetRotationActive(active: false);
|
|
combatDutyDetection?.Reset();
|
|
deathHandler?.SetRotationActive(active: false);
|
|
deathHandler?.Reset();
|
|
if (movementMonitor != null && movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StopMonitoring();
|
|
log.Information("[QuestRotation] Movement monitor stopped");
|
|
}
|
|
currentState.Phase = RotationPhase.Idle;
|
|
}
|
|
|
|
private void HandleWaitingForHomeworldReturn()
|
|
{
|
|
if (!((DateTime.Now - currentState.PhaseStartTime).TotalSeconds >= 5.0))
|
|
{
|
|
return;
|
|
}
|
|
log.Information("[QuestRotation] ========================================");
|
|
log.Information("[QuestRotation] === SENDING HOMEWORLD RETURN COMMAND ===");
|
|
log.Information("[QuestRotation] ========================================");
|
|
try
|
|
{
|
|
commandManager.ProcessCommand("/li");
|
|
log.Information("[QuestRotation] ✓ /li command sent (homeworld return)");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[QuestRotation] Failed to send /li command: " + ex.Message);
|
|
}
|
|
Task.Delay(2000).ContinueWith(delegate
|
|
{
|
|
framework.RunOnFrameworkThread(delegate
|
|
{
|
|
log.Information("[QuestRotation] Homeworld return initiated, moving to next character...");
|
|
SkipToNextCharacter();
|
|
});
|
|
});
|
|
}
|
|
|
|
private void SkipToNextCharacter()
|
|
{
|
|
if (preCheckService != null)
|
|
{
|
|
log.Information("[QuestRotation] Logging completed quests before logout...");
|
|
preCheckService.LogCompletedQuestsBeforeLogout();
|
|
}
|
|
if (dcTravelService != null && dcTravelService.IsDCTravelCompleted())
|
|
{
|
|
log.Information("[QuestRotation] DC Travel state reset for next character");
|
|
dcTravelService.ResetDCTravelState();
|
|
}
|
|
log.Information("[QuestRotation] Waiting 2s before character switch...");
|
|
currentState.Phase = RotationPhase.WaitingBeforeCharacterSwitch;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
}
|
|
|
|
private void MarkCharacterCompleted(string characterName, string reason = "")
|
|
{
|
|
List<string> completedList = currentState.CompletedCharacters;
|
|
if (!completedList.Contains(characterName))
|
|
{
|
|
completedList.Add(characterName);
|
|
currentState.CompletedCharacters = completedList;
|
|
log.Debug("[QuestRotation] Added '" + characterName + "' to completed list" + (string.IsNullOrEmpty(reason) ? "" : (" (" + reason + ")")));
|
|
}
|
|
List<string> remainingList = currentState.RemainingCharacters;
|
|
bool num = remainingList.Remove(characterName);
|
|
currentState.RemainingCharacters = remainingList;
|
|
if (num)
|
|
{
|
|
log.Information("[QuestRotation] Removed '" + characterName + "' from remaining list");
|
|
log.Information($"[QuestRotation] Progress: {currentState.CompletedCharacters.Count}/{currentState.SelectedCharacters.Count} completed");
|
|
}
|
|
}
|
|
|
|
private void HandleWaitingBeforeCharacterSwitch()
|
|
{
|
|
if (movementMonitor != null && movementMonitor.IsMonitoring)
|
|
{
|
|
movementMonitor.StopMonitoring();
|
|
log.Debug("[QuestRotation] Movement monitor stopped during character switch wait");
|
|
}
|
|
if (condition[ConditionFlag.BetweenAreas])
|
|
{
|
|
log.Debug("[QuestRotation] Character is between areas (Condition 32) - waiting...");
|
|
}
|
|
else if ((DateTime.Now - currentState.PhaseStartTime).TotalSeconds >= 2.0)
|
|
{
|
|
log.Debug("[QuestRotation] 2s wait complete and not between areas, performing character switch...");
|
|
PerformCharacterSwitch();
|
|
}
|
|
}
|
|
|
|
private void PerformCharacterSwitch()
|
|
{
|
|
if (currentState.RemainingCharacters.Count == 0)
|
|
{
|
|
StopPoint currentStopPoint = stopPoints.FirstOrDefault((StopPoint sp) => sp.IsActive);
|
|
StopPoint nextStopPoint = stopPoints.FirstOrDefault((StopPoint sp) => !sp.IsActive);
|
|
if (currentStopPoint != null && nextStopPoint != null)
|
|
{
|
|
log.Information("[QuestRotation] ========================================");
|
|
log.Information("[QuestRotation] === CURRENT STOP POINT COMPLETED ===");
|
|
log.Information("[QuestRotation] ========================================");
|
|
log.Information("[QuestRotation] Completed: " + currentStopPoint.DisplayName);
|
|
log.Information("[QuestRotation] Moving to next stop point: " + nextStopPoint.DisplayName);
|
|
currentStopPoint.IsActive = false;
|
|
nextStopPoint.IsActive = true;
|
|
currentState.CompletedCharacters.Clear();
|
|
currentState.RemainingCharacters = new List<string>(currentState.SelectedCharacters);
|
|
currentState.CurrentStopQuestId = nextStopPoint.QuestId;
|
|
string firstChar = currentState.RemainingCharacters[0];
|
|
currentState.CurrentCharacter = firstChar;
|
|
currentState.NextCharacter = firstChar;
|
|
log.Information("[QuestRotation] Starting next stop point with " + firstChar);
|
|
if (autoRetainerIpc.SwitchCharacter(firstChar))
|
|
{
|
|
currentState.Phase = RotationPhase.WaitingForCharacterLogin;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
}
|
|
else
|
|
{
|
|
log.Error("[QuestRotation] Failed to switch to " + firstChar);
|
|
currentState.Phase = RotationPhase.Error;
|
|
currentState.ErrorMessage = "Failed to switch to " + firstChar;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log.Information("[QuestRotation] No more stop points to process");
|
|
currentState.Phase = RotationPhase.Completed;
|
|
}
|
|
return;
|
|
}
|
|
if (dcTravelService != null)
|
|
{
|
|
dcTravelService.ResetDCTravelState();
|
|
log.Information("[QuestRotation] DC Travel state reset for next character");
|
|
}
|
|
log.Debug($"[QuestRotation] DEBUG - RemainingCharacters count: {currentState.RemainingCharacters.Count}");
|
|
log.Debug("[QuestRotation] DEBUG - RemainingCharacters list: " + string.Join(", ", currentState.RemainingCharacters));
|
|
log.Debug($"[QuestRotation] DEBUG - CompletedCharacters count: {currentState.CompletedCharacters.Count}");
|
|
log.Debug("[QuestRotation] DEBUG - CompletedCharacters list: " + string.Join(", ", currentState.CompletedCharacters));
|
|
string nextChar = currentState.RemainingCharacters[0];
|
|
currentState.CurrentCharacter = nextChar;
|
|
currentState.NextCharacter = nextChar;
|
|
log.Information("[QuestRotation] Switching to next character: " + nextChar);
|
|
log.Information($"[QuestRotation] Progress: {currentState.CompletedCharacters.Count}/{currentState.SelectedCharacters.Count} completed");
|
|
if (autoRetainerIpc.SwitchCharacter(nextChar))
|
|
{
|
|
currentState.Phase = RotationPhase.WaitingForCharacterLogin;
|
|
currentState.PhaseStartTime = DateTime.Now;
|
|
log.Information("[QuestRotation] Character switch initiated to " + nextChar);
|
|
}
|
|
else
|
|
{
|
|
log.Error("[QuestRotation] Failed to switch to " + nextChar);
|
|
currentState.Phase = RotationPhase.Error;
|
|
currentState.ErrorMessage = "Failed to switch character";
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
framework.Update -= OnFrameworkUpdate;
|
|
log.Information("[QuestRotation] Service disposed");
|
|
}
|
|
}
|