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 uint? _uncheckedTerritoryId; 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(uint newTerritoryId) { if (!_configuration.Advanced.DisablePartyWatchdog) { switch ((ETerritoryIntendedUse)GameMain.Instance()->CurrentTerritoryIntendedUseId) { case ETerritoryIntendedUse.MordionGaol: case ETerritoryIntendedUse.Frontline: case ETerritoryIntendedUse.LordOfVerminion: case ETerritoryIntendedUse.ExploratoryMissions: case ETerritoryIntendedUse.CrystallineConflict: case ETerritoryIntendedUse.DeepDungeon: case ETerritoryIntendedUse.TreasureMapInstance: case ETerritoryIntendedUse.TripleTriadBattlehall: case ETerritoryIntendedUse.CrystallineConflictCustomMatch: case ETerritoryIntendedUse.DiademHuntingGrounds: case ETerritoryIntendedUse.RivalWings: case ETerritoryIntendedUse.Eureka: case ETerritoryIntendedUse.LeapOfFaith: case ETerritoryIntendedUse.OceanFishing: case ETerritoryIntendedUse.Diadem: case ETerritoryIntendedUse.Bozja: case ETerritoryIntendedUse.TripleTriadOpenTournament: case ETerritoryIntendedUse.TripleTriadInvitationalParlor: case ETerritoryIntendedUse.DelubrumReginae: case ETerritoryIntendedUse.DelubrumReginaeSavage: case ETerritoryIntendedUse.Blunderville: StopIfRunning($"Unsupported Area entered ({newTerritoryId})"); break; case ETerritoryIntendedUse.Dungeon: case ETerritoryIntendedUse.VariantDungeon: case ETerritoryIntendedUse.AllianceRaid: case ETerritoryIntendedUse.Trial: case ETerritoryIntendedUse.Raid1: case ETerritoryIntendedUse.Raid2: case ETerritoryIntendedUse.Seasonal: case ETerritoryIntendedUse.TheCalamityRetold: case ETerritoryIntendedUse.CriterionDungeon: case ETerritoryIntendedUse.CriterionDungeonSavage: _uncheckedTerritoryId = newTerritoryId; _logger.LogDebug("Will check territory {TerritoryId} after loading", newTerritoryId); break; case ETerritoryIntendedUse.OpeningArea: case ETerritoryIntendedUse.BeforeTrialDung: case ETerritoryIntendedUse.PreEwOverworldQuestBattle: case ETerritoryIntendedUse.Unknown11: case ETerritoryIntendedUse.WaitingRoom: case ETerritoryIntendedUse.HousingOutdoor: case ETerritoryIntendedUse.HousingIndoor: case ETerritoryIntendedUse.SoloOverworldInstances: case ETerritoryIntendedUse.ChocoboSquareOld: case ETerritoryIntendedUse.ChocoboRacing: case ETerritoryIntendedUse.Firmament: case ETerritoryIntendedUse.SanctumOfTheTwelve: case ETerritoryIntendedUse.GoldSaucer: case ETerritoryIntendedUse.OriginalStepsOfFaith: case ETerritoryIntendedUse.HallOfTheNovice: case ETerritoryIntendedUse.SoloDuty: case ETerritoryIntendedUse.GrandCompanyBarracks: case ETerritoryIntendedUse.SeasonalInstancedArea: case ETerritoryIntendedUse.ChaoticRaid: case ETerritoryIntendedUse.Unknown40: case ETerritoryIntendedUse.Unknown42: case ETerritoryIntendedUse.MaskedCarnival: case ETerritoryIntendedUse.IslandSanctuary: case ETerritoryIntendedUse.EndwalkerMsqSoloOverworld: case ETerritoryIntendedUse.Unknown55: case ETerritoryIntendedUse.Elysion: break; } } } public unsafe void Update() { if (_configuration.Advanced.DisablePartyWatchdog || _uncheckedTerritoryId != _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}", _uncheckedTerritoryId, memberCount, isAlliance); if (memberCount > 1 || isAlliance) { if (!IsDutyConfiguredForParty(_uncheckedTerritoryId.Value)) { StopIfRunning("Other party members present"); } else { _logger.LogDebug("Party detected but duty is configured for Unsync (Party) mode, continuing"); } } _uncheckedTerritoryId = null; } private bool IsDutyConfiguredForParty(uint 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; } }