using System; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Group; using LLib.GameData; using Microsoft.Extensions.Logging; using Questionable.Data; using Questionable.Model.Questing; namespace Questionable.Controller.Utils; internal sealed class PartyWatchdog : IDisposable { private readonly QuestController _questController; private readonly Configuration _configuration; private readonly TerritoryData _territoryData; private readonly IClientState _clientState; private readonly IChatGui _chatGui; private readonly ILogger _logger; private ushort? _uncheckedTeritoryId; public PartyWatchdog(QuestController questController, Configuration configuration, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, ILogger logger) { _questController = questController; _configuration = configuration; _territoryData = territoryData; _clientState = clientState; _chatGui = chatGui; _logger = logger; _clientState.TerritoryChanged += TerritoryChanged; } private unsafe void TerritoryChanged(ushort newTerritoryId) { if (!_configuration.Advanced.DisablePartyWatchdog) { switch ((ETerritoryIntendedUse)GameMain.Instance()->CurrentTerritoryIntendedUseId) { case ETerritoryIntendedUse.Gaol: case ETerritoryIntendedUse.Frontline: case ETerritoryIntendedUse.LordOfVerminion: case ETerritoryIntendedUse.Diadem: case ETerritoryIntendedUse.CrystallineConflict: case ETerritoryIntendedUse.DeepDungeon: case ETerritoryIntendedUse.TreasureMapDuty: case ETerritoryIntendedUse.Battlehall: case ETerritoryIntendedUse.CrystallineConflict2: case ETerritoryIntendedUse.Diadem2: case ETerritoryIntendedUse.RivalWings: case ETerritoryIntendedUse.Eureka: case ETerritoryIntendedUse.LeapOfFaith: case ETerritoryIntendedUse.OceanFishing: case ETerritoryIntendedUse.Diadem3: case ETerritoryIntendedUse.Bozja: case ETerritoryIntendedUse.Battlehall2: case ETerritoryIntendedUse.Battlehall3: case ETerritoryIntendedUse.LargeScaleRaid: case ETerritoryIntendedUse.LargeScaleSavageRaid: case ETerritoryIntendedUse.Blunderville: StopIfRunning($"Unsupported Area entered ({newTerritoryId})"); break; case ETerritoryIntendedUse.Dungeon: case ETerritoryIntendedUse.VariantDungeon: case ETerritoryIntendedUse.AllianceRaid: case ETerritoryIntendedUse.Trial: case ETerritoryIntendedUse.Raid: case ETerritoryIntendedUse.Raid2: case ETerritoryIntendedUse.SeasonalEvent: case ETerritoryIntendedUse.SeasonalEvent2: case ETerritoryIntendedUse.CriterionDuty: case ETerritoryIntendedUse.CriterionSavageDuty: _uncheckedTeritoryId = newTerritoryId; _logger.LogInformation("Will check territory {TerritoryId} after loading", newTerritoryId); break; case ETerritoryIntendedUse.StartingArea: case ETerritoryIntendedUse.QuestArea: case ETerritoryIntendedUse.QuestBattle: case (ETerritoryIntendedUse)11: case ETerritoryIntendedUse.QuestArea2: case ETerritoryIntendedUse.ResidentialArea: case ETerritoryIntendedUse.HousingInstances: case ETerritoryIntendedUse.QuestArea3: case (ETerritoryIntendedUse)19: case ETerritoryIntendedUse.ChocoboSquare: case ETerritoryIntendedUse.RestorationEvent: case ETerritoryIntendedUse.Sanctum: case ETerritoryIntendedUse.GoldSaucer: case (ETerritoryIntendedUse)24: case ETerritoryIntendedUse.HallOfTheNovice: case ETerritoryIntendedUse.QuestBattle2: case ETerritoryIntendedUse.Barracks: case ETerritoryIntendedUse.SeasonalEventDuty: case (ETerritoryIntendedUse)36: case ETerritoryIntendedUse.Unknown1: case (ETerritoryIntendedUse)42: case ETerritoryIntendedUse.MaskedCarnivale: case ETerritoryIntendedUse.IslandSanctuary: case ETerritoryIntendedUse.QuestArea4: case (ETerritoryIntendedUse)55: case ETerritoryIntendedUse.TribalInstance: break; } } } public unsafe void Update() { if (_configuration.Advanced.DisablePartyWatchdog || _uncheckedTeritoryId != _clientState.TerritoryType || GameMain.Instance()->TerritoryLoadState != 2) { return; } GroupManager* ptr = GroupManager.Instance(); if (ptr == null) { return; } byte memberCount = ptr->MainGroup.MemberCount; bool isAlliance = ptr->MainGroup.IsAlliance; _logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}", _uncheckedTeritoryId, memberCount, isAlliance); if (memberCount > 1 || isAlliance) { if (!IsDutyConfiguredForParty(_uncheckedTeritoryId.Value)) { StopIfRunning("Other party members present"); } else { _logger.LogInformation("Party detected but duty is configured for Unsync (Party) mode, continuing"); } } _uncheckedTeritoryId = null; } private bool IsDutyConfiguredForParty(ushort territoryId) { if (!_territoryData.TryGetContentFinderConditionIdForTerritory(territoryId, out var cfcId)) { return false; } if (_configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value)) { return value == EDutyMode.UnsyncParty; } return _configuration.Duties.DefaultDutyMode == EDutyMode.UnsyncParty; } private void StopIfRunning(string reason) { if (_questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual) { _chatGui.PrintError("Stopping Questionable: " + reason + ". If you believe this to be correct, please restart Questionable manually.", "Questionable", 576); _questController.Stop(reason); } } public void Dispose() { _clientState.TerritoryChanged -= TerritoryChanged; } }