using System.Collections.Concurrent; using System.Collections.Generic; using Dalamud.Data; using Dalamud.Game.Gui; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Component.Exd; using Lumina.Excel; using Lumina.Excel.Sheets; using ActionSheet = Lumina.Excel.Sheets.Action; using InstanceContentSheet = Lumina.Excel.Sheets.InstanceContent; using PublicContentSheet = Lumina.Excel.Sheets.PublicContent; namespace Dalamud.Game.UnlockState; #pragma warning disable Dalamud001 /// /// This class provides unlock state of various content in the game. /// [ServiceManager.EarlyLoadedService] internal unsafe class UnlockState : IInternalDisposableService, IUnlockState { private static readonly ModuleLog Log = new(nameof(UnlockState)); private readonly ConcurrentDictionary> cachedUnlockedRowIds = []; [ServiceManager.ServiceDependency] private readonly DataManager dataManager = Service.Get(); [ServiceManager.ServiceDependency] private readonly ClientState.ClientState clientState = Service.Get(); [ServiceManager.ServiceDependency] private readonly GameGui gameGui = Service.Get(); [ServiceManager.ServiceDependency] private readonly RecipeData recipeData = Service.Get(); [ServiceManager.ServiceConstructor] private UnlockState() { this.clientState.Login += this.OnLogin; this.clientState.Logout += this.OnLogout; this.gameGui.AgentUpdate += this.OnAgentUpdate; } /// public event IUnlockState.UnlockDelegate Unlock; private bool IsLoaded => PlayerState.Instance()->IsLoaded; /// void IInternalDisposableService.DisposeService() { this.clientState.Login -= this.OnLogin; this.clientState.Logout -= this.OnLogout; this.gameGui.AgentUpdate -= this.OnAgentUpdate; } /// public bool IsActionUnlocked(ActionSheet row) { return this.IsUnlockLinkUnlocked(row.UnlockLink.RowId); } /// public bool IsAetherCurrentUnlocked(AetherCurrent row) { if (!this.IsLoaded) return false; return PlayerState.Instance()->IsAetherCurrentUnlocked(row.RowId); } /// public bool IsAetherCurrentCompFlgSetUnlocked(AetherCurrentCompFlgSet row) { if (!this.IsLoaded) return false; return PlayerState.Instance()->IsAetherCurrentZoneComplete(row.RowId); } /// public bool IsAozActionUnlocked(AozAction row) { if (!this.IsLoaded) return false; if (row.RowId == 0 || !row.Action.IsValid) return false; return UIState.Instance()->IsUnlockLinkUnlockedOrQuestCompleted(row.Action.Value.UnlockLink.RowId); } /// public bool IsBannerBgUnlocked(BannerBg row) { return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value); } /// public bool IsBannerConditionUnlocked(BannerCondition row) { if (row.RowId == 0) return false; if (!this.IsLoaded) return false; var rowPtr = ExdModule.GetBannerConditionByIndex(row.RowId); if (rowPtr == null) return false; return ExdModule.GetBannerConditionUnlockState(rowPtr) == 0; } /// public bool IsBannerDecorationUnlocked(BannerDecoration row) { return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value); } /// public bool IsBannerFacialUnlocked(BannerFacial row) { return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value); } /// public bool IsBannerFrameUnlocked(BannerFrame row) { return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value); } /// public bool IsBannerTimelineUnlocked(BannerTimeline row) { return row.UnlockCondition.IsValid && this.IsBannerConditionUnlocked(row.UnlockCondition.Value); } /// public bool IsBuddyActionUnlocked(BuddyAction row) { return this.IsUnlockLinkUnlocked(row.UnlockLink); } /// public bool IsBuddyEquipUnlocked(BuddyEquip row) { if (!this.IsLoaded) return false; return UIState.Instance()->Buddy.CompanionInfo.IsBuddyEquipUnlocked(row.RowId); } /// public bool IsCharaMakeCustomizeUnlocked(CharaMakeCustomize row) { return row.IsPurchasable && this.IsUnlockLinkUnlocked(row.UnlockLink); } /// public bool IsChocoboTaxiStandUnlocked(ChocoboTaxiStand row) { if (!this.IsLoaded) return false; return UIState.Instance()->IsChocoboTaxiStandUnlocked(row.RowId); } /// public bool IsCompanionUnlocked(Companion row) { if (!this.IsLoaded) return false; return UIState.Instance()->IsCompanionUnlocked(row.RowId); } /// public bool IsCraftActionUnlocked(CraftAction row) { return this.IsUnlockLinkUnlocked(row.QuestRequirement.RowId); } /// public bool IsCSBonusContentTypeUnlocked(CSBonusContentType row) { return this.IsUnlockLinkUnlocked(row.UnlockLink); } /// public bool IsEmoteUnlocked(Emote row) { return this.IsUnlockLinkUnlocked(row.UnlockLink); } /// public bool IsEmjVoiceNpcUnlocked(EmjVoiceNpc row) { return this.IsUnlockLinkUnlocked(row.Unknown26); } /// public bool IsEmjCostumeUnlocked(EmjCostume row) { return this.dataManager.GetExcelSheet().TryGetRow(row.RowId, out var emjVoiceNpcRow) && this.IsEmjVoiceNpcUnlocked(emjVoiceNpcRow) && QuestManager.IsQuestComplete(row.Unknown1); } /// public bool IsGeneralActionUnlocked(GeneralAction row) { return this.IsUnlockLinkUnlocked(row.UnlockLink); } /// public bool IsGlassesUnlocked(Glasses row) { if (!this.IsLoaded) return false; return PlayerState.Instance()->IsGlassesUnlocked((ushort)row.RowId); } /// public bool IsHowToUnlocked(HowTo row) { if (!this.IsLoaded) return false; return UIState.Instance()->IsHowToUnlocked(row.RowId); } /// public bool IsInstanceContentUnlocked(InstanceContentSheet row) { if (!this.IsLoaded) return false; return UIState.IsInstanceContentUnlocked(row.RowId); } /// public unsafe bool IsItemUnlocked(Item row) { if (row.ItemAction.RowId == 0) return false; if (!this.IsLoaded) return false; // To avoid the ExdModule.GetItemRowById call, which can return null if the excel page // is not loaded, we're going to imitate the IsItemActionUnlocked call first: switch ((ItemActionType)row.ItemAction.Value.Type) { case ItemActionType.Companion: return UIState.Instance()->IsCompanionUnlocked(row.ItemAction.Value.Data[0]); case ItemActionType.BuddyEquip: return UIState.Instance()->Buddy.CompanionInfo.IsBuddyEquipUnlocked(row.ItemAction.Value.Data[0]); case ItemActionType.Mount: return PlayerState.Instance()->IsMountUnlocked(row.ItemAction.Value.Data[0]); case ItemActionType.SecretRecipeBook: return PlayerState.Instance()->IsSecretRecipeBookUnlocked(row.ItemAction.Value.Data[0]); case ItemActionType.UnlockLink: case ItemActionType.OccultRecords: return UIState.Instance()->IsUnlockLinkUnlocked(row.ItemAction.Value.Data[0]); case ItemActionType.TripleTriadCard when row.AdditionalData.Is(): return UIState.Instance()->IsTripleTriadCardUnlocked((ushort)row.AdditionalData.RowId); case ItemActionType.FolkloreTome: return PlayerState.Instance()->IsFolkloreBookUnlocked(row.ItemAction.Value.Data[0]); case ItemActionType.OrchestrionRoll when row.AdditionalData.Is(): return PlayerState.Instance()->IsOrchestrionRollUnlocked(row.AdditionalData.RowId); case ItemActionType.FramersKit: return PlayerState.Instance()->IsFramersKitUnlocked(row.AdditionalData.RowId); case ItemActionType.Ornament: return PlayerState.Instance()->IsOrnamentUnlocked(row.ItemAction.Value.Data[0]); case ItemActionType.Glasses: return PlayerState.Instance()->IsGlassesUnlocked((ushort)row.AdditionalData.RowId); case ItemActionType.SoulShards when PublicContentOccultCrescent.GetState() is var occultCrescentState && occultCrescentState != null: var supportJobId = (byte)row.ItemAction.Value.Data[0]; return supportJobId < occultCrescentState->SupportJobLevels.Length && occultCrescentState->SupportJobLevels[supportJobId] != 0; case ItemActionType.CompanySealVouchers: return false; } var nativeRow = ExdModule.GetItemRowById(row.RowId); return nativeRow != null && UIState.Instance()->IsItemActionUnlocked(nativeRow) == 1; } /// public bool IsMcGuffinUnlocked(McGuffin row) { return PlayerState.Instance()->IsMcGuffinUnlocked(row.RowId); } /// public bool IsMJILandmarkUnlocked(MJILandmark row) { return this.IsUnlockLinkUnlocked(row.UnlockLink); } /// public bool IsMKDLoreUnlocked(MKDLore row) { return this.IsUnlockLinkUnlocked(row.Unknown2); } /// public bool IsMountUnlocked(Mount row) { if (!this.IsLoaded) return false; return PlayerState.Instance()->IsMountUnlocked(row.RowId); } /// public bool IsNotebookDivisionUnlocked(NotebookDivision row) { return this.IsUnlockLinkUnlocked(row.QuestUnlock.RowId); } /// public bool IsOrchestrionUnlocked(Orchestrion row) { if (!this.IsLoaded) return false; return PlayerState.Instance()->IsOrchestrionRollUnlocked(row.RowId); } /// public bool IsOrnamentUnlocked(Ornament row) { if (!this.IsLoaded) return false; return PlayerState.Instance()->IsOrnamentUnlocked(row.RowId); } /// public bool IsPerformUnlocked(Perform row) { return this.IsUnlockLinkUnlocked((uint)row.UnlockLink); } /// public bool IsPublicContentUnlocked(PublicContentSheet row) { if (!this.IsLoaded) return false; return UIState.IsPublicContentUnlocked(row.RowId); } /// public bool IsRecipeUnlocked(Recipe row) { return this.recipeData.IsRecipeUnlocked(row); } /// public bool IsSecretRecipeBookUnlocked(SecretRecipeBook row) { if (!this.IsLoaded) return false; return PlayerState.Instance()->IsSecretRecipeBookUnlocked(row.RowId); } /// public bool IsTraitUnlocked(Trait row) { return this.IsUnlockLinkUnlocked(row.Quest.RowId); } /// public bool IsTripleTriadCardUnlocked(TripleTriadCard row) { if (!this.IsLoaded) return false; return UIState.Instance()->IsTripleTriadCardUnlocked((ushort)row.RowId); } /// public bool IsItemUnlockable(Item row) { if (row.ItemAction.RowId == 0) return false; return (ItemActionType)row.ItemAction.Value.Type is ItemActionType.Companion or ItemActionType.BuddyEquip or ItemActionType.Mount or ItemActionType.SecretRecipeBook or ItemActionType.UnlockLink or ItemActionType.TripleTriadCard or ItemActionType.FolkloreTome or ItemActionType.OrchestrionRoll or ItemActionType.FramersKit or ItemActionType.Ornament or ItemActionType.Glasses or ItemActionType.OccultRecords or ItemActionType.SoulShards; } /// public bool IsRowRefUnlocked(RowRef rowRef) where T : struct, IExcelRow { return this.IsRowRefUnlocked((RowRef)rowRef); } /// public bool IsRowRefUnlocked(RowRef rowRef) { if (!this.IsLoaded || rowRef.IsUntyped) return false; if (rowRef.TryGetValue(out var actionRow)) return this.IsActionUnlocked(actionRow); if (rowRef.TryGetValue(out var aetherCurrentRow)) return this.IsAetherCurrentUnlocked(aetherCurrentRow); if (rowRef.TryGetValue(out var aetherCurrentCompFlgSetRow)) return this.IsAetherCurrentCompFlgSetUnlocked(aetherCurrentCompFlgSetRow); if (rowRef.TryGetValue(out var aozActionRow)) return this.IsAozActionUnlocked(aozActionRow); if (rowRef.TryGetValue(out var bannerBgRow)) return this.IsBannerBgUnlocked(bannerBgRow); if (rowRef.TryGetValue(out var bannerConditionRow)) return this.IsBannerConditionUnlocked(bannerConditionRow); if (rowRef.TryGetValue(out var bannerDecorationRow)) return this.IsBannerDecorationUnlocked(bannerDecorationRow); if (rowRef.TryGetValue(out var bannerFacialRow)) return this.IsBannerFacialUnlocked(bannerFacialRow); if (rowRef.TryGetValue(out var bannerFrameRow)) return this.IsBannerFrameUnlocked(bannerFrameRow); if (rowRef.TryGetValue(out var bannerTimelineRow)) return this.IsBannerTimelineUnlocked(bannerTimelineRow); if (rowRef.TryGetValue(out var buddyActionRow)) return this.IsBuddyActionUnlocked(buddyActionRow); if (rowRef.TryGetValue(out var buddyEquipRow)) return this.IsBuddyEquipUnlocked(buddyEquipRow); if (rowRef.TryGetValue(out var csBonusContentTypeRow)) return this.IsCSBonusContentTypeUnlocked(csBonusContentTypeRow); if (rowRef.TryGetValue(out var charaMakeCustomizeRow)) return this.IsCharaMakeCustomizeUnlocked(charaMakeCustomizeRow); if (rowRef.TryGetValue(out var chocoboTaxiStandRow)) return this.IsChocoboTaxiStandUnlocked(chocoboTaxiStandRow); if (rowRef.TryGetValue(out var companionRow)) return this.IsCompanionUnlocked(companionRow); if (rowRef.TryGetValue(out var craftActionRow)) return this.IsCraftActionUnlocked(craftActionRow); if (rowRef.TryGetValue(out var emoteRow)) return this.IsEmoteUnlocked(emoteRow); if (rowRef.TryGetValue(out var generalActionRow)) return this.IsGeneralActionUnlocked(generalActionRow); if (rowRef.TryGetValue(out var glassesRow)) return this.IsGlassesUnlocked(glassesRow); if (rowRef.TryGetValue(out var howToRow)) return this.IsHowToUnlocked(howToRow); if (rowRef.TryGetValue(out var instanceContentRow)) return this.IsInstanceContentUnlocked(instanceContentRow); if (rowRef.TryGetValue(out var itemRow)) return this.IsItemUnlocked(itemRow); if (rowRef.TryGetValue(out var mjiLandmarkRow)) return this.IsMJILandmarkUnlocked(mjiLandmarkRow); if (rowRef.TryGetValue(out var mkdLoreRow)) return this.IsMKDLoreUnlocked(mkdLoreRow); if (rowRef.TryGetValue(out var mcGuffinRow)) return this.IsMcGuffinUnlocked(mcGuffinRow); if (rowRef.TryGetValue(out var mountRow)) return this.IsMountUnlocked(mountRow); if (rowRef.TryGetValue(out var notebookDivisionRow)) return this.IsNotebookDivisionUnlocked(notebookDivisionRow); if (rowRef.TryGetValue(out var orchestrionRow)) return this.IsOrchestrionUnlocked(orchestrionRow); if (rowRef.TryGetValue(out var ornamentRow)) return this.IsOrnamentUnlocked(ornamentRow); if (rowRef.TryGetValue(out var performRow)) return this.IsPerformUnlocked(performRow); if (rowRef.TryGetValue(out var publicContentRow)) return this.IsPublicContentUnlocked(publicContentRow); if (rowRef.TryGetValue(out var recipeRow)) return this.IsRecipeUnlocked(recipeRow); if (rowRef.TryGetValue(out var secretRecipeBookRow)) return this.IsSecretRecipeBookUnlocked(secretRecipeBookRow); if (rowRef.TryGetValue(out var traitRow)) return this.IsTraitUnlocked(traitRow); if (rowRef.TryGetValue(out var tripleTriadCardRow)) return this.IsTripleTriadCardUnlocked(tripleTriadCardRow); return false; } /// public bool IsUnlockLinkUnlocked(ushort unlockLink) { if (!this.IsLoaded) return false; if (unlockLink == 0) return false; return UIState.Instance()->IsUnlockLinkUnlocked(unlockLink); } /// public bool IsUnlockLinkUnlocked(uint unlockLink) { if (!this.IsLoaded) return false; if (unlockLink == 0) return false; return UIState.Instance()->IsUnlockLinkUnlockedOrQuestCompleted(unlockLink); } private void OnLogin() { this.Update(); } private void OnLogout(int type, int code) { this.cachedUnlockedRowIds.Clear(); } private void OnAgentUpdate(AgentUpdateFlag agentUpdateFlag) { if (agentUpdateFlag.HasFlag(AgentUpdateFlag.UnlocksUpdate)) this.Update(); } private void Update() { if (!this.IsLoaded) return; this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); this.UpdateUnlocksForSheet(); // Not implemented: // - DescriptionPage: quite complex // - QuestAcceptAdditionCondition: ignored // For some other day: // - FishingSpot // - Spearfishing // - Adventure (Sightseeing) // - MinerFolkloreTome // - BotanistFolkloreTome // - FishingFolkloreTome // - VVD or is that unlocked via quest? // - VVDNotebookContents? // - FramersKit (is that just an Item?) // - ... more? // Subrow sheets, which are incompatible with the current Unlock event, since RowRef doesn't carry the SubrowId: // - EmjCostume // Probably not happening, because it requires fetching data from server: // - Achievements // - Titles // - Bozjan Field Notes // - Support/Phantom Jobs, which require to be in Occult Crescent, because it checks the jobs level for != 0 } private void UpdateUnlocksForSheet() where T : struct, IExcelRow { var unlockedRowIds = this.cachedUnlockedRowIds.GetOrAdd(typeof(T), _ => []); foreach (var row in this.dataManager.GetExcelSheet()) { if (unlockedRowIds.Contains(row.RowId)) continue; var rowRef = LuminaUtils.CreateRef(row.RowId); if (!this.IsRowRefUnlocked(rowRef)) continue; unlockedRowIds.Add(row.RowId); Log.Verbose($"Unlock detected: {typeof(T).Name}#{row.RowId}"); foreach (var action in Delegate.EnumerateInvocationList(this.Unlock)) { try { action((RowRef)rowRef); } catch (Exception ex) { Log.Error(ex, "Exception during raise of {handler}", action.Method); } } } } } /// /// Plugin-scoped version of a service. /// [PluginInterface] [ServiceManager.ScopedService] #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 internal class UnlockStatePluginScoped : IInternalDisposableService, IUnlockState { [ServiceManager.ServiceDependency] private readonly UnlockState unlockStateService = Service.Get(); /// /// Initializes a new instance of the class. /// internal UnlockStatePluginScoped() { this.unlockStateService.Unlock += this.UnlockForward; } /// public event IUnlockState.UnlockDelegate? Unlock; /// public bool IsActionUnlocked(ActionSheet row) => this.unlockStateService.IsActionUnlocked(row); /// public bool IsAetherCurrentCompFlgSetUnlocked(AetherCurrentCompFlgSet row) => this.unlockStateService.IsAetherCurrentCompFlgSetUnlocked(row); /// public bool IsAetherCurrentUnlocked(AetherCurrent row) => this.unlockStateService.IsAetherCurrentUnlocked(row); /// public bool IsAozActionUnlocked(AozAction row) => this.unlockStateService.IsAozActionUnlocked(row); /// public bool IsBannerBgUnlocked(BannerBg row) => this.unlockStateService.IsBannerBgUnlocked(row); /// public bool IsBannerConditionUnlocked(BannerCondition row) => this.unlockStateService.IsBannerConditionUnlocked(row); /// public bool IsBannerDecorationUnlocked(BannerDecoration row) => this.unlockStateService.IsBannerDecorationUnlocked(row); /// public bool IsBannerFacialUnlocked(BannerFacial row) => this.unlockStateService.IsBannerFacialUnlocked(row); /// public bool IsBannerFrameUnlocked(BannerFrame row) => this.unlockStateService.IsBannerFrameUnlocked(row); /// public bool IsBannerTimelineUnlocked(BannerTimeline row) => this.unlockStateService.IsBannerTimelineUnlocked(row); /// public bool IsBuddyActionUnlocked(BuddyAction row) => this.unlockStateService.IsBuddyActionUnlocked(row); /// public bool IsBuddyEquipUnlocked(BuddyEquip row) => this.unlockStateService.IsBuddyEquipUnlocked(row); /// public bool IsCharaMakeCustomizeUnlocked(CharaMakeCustomize row) => this.unlockStateService.IsCharaMakeCustomizeUnlocked(row); /// public bool IsChocoboTaxiStandUnlocked(ChocoboTaxiStand row) => this.unlockStateService.IsChocoboTaxiStandUnlocked(row); /// public bool IsCompanionUnlocked(Companion row) => this.unlockStateService.IsCompanionUnlocked(row); /// public bool IsCraftActionUnlocked(CraftAction row) => this.unlockStateService.IsCraftActionUnlocked(row); /// public bool IsCSBonusContentTypeUnlocked(CSBonusContentType row) => this.unlockStateService.IsCSBonusContentTypeUnlocked(row); /// public bool IsEmoteUnlocked(Emote row) => this.unlockStateService.IsEmoteUnlocked(row); /// public bool IsEmjVoiceNpcUnlocked(EmjVoiceNpc row) => this.unlockStateService.IsEmjVoiceNpcUnlocked(row); /// public bool IsEmjCostumeUnlocked(EmjCostume row) => this.unlockStateService.IsEmjCostumeUnlocked(row); /// public bool IsGeneralActionUnlocked(GeneralAction row) => this.unlockStateService.IsGeneralActionUnlocked(row); /// public bool IsGlassesUnlocked(Glasses row) => this.unlockStateService.IsGlassesUnlocked(row); /// public bool IsHowToUnlocked(HowTo row) => this.unlockStateService.IsHowToUnlocked(row); /// public bool IsInstanceContentUnlocked(InstanceContentSheet row) => this.unlockStateService.IsInstanceContentUnlocked(row); /// public bool IsItemUnlockable(Item row) => this.unlockStateService.IsItemUnlockable(row); /// public bool IsItemUnlocked(Item row) => this.unlockStateService.IsItemUnlocked(row); /// public bool IsMcGuffinUnlocked(McGuffin row) => this.unlockStateService.IsMcGuffinUnlocked(row); /// public bool IsMJILandmarkUnlocked(MJILandmark row) => this.unlockStateService.IsMJILandmarkUnlocked(row); /// public bool IsMKDLoreUnlocked(MKDLore row) => this.unlockStateService.IsMKDLoreUnlocked(row); /// public bool IsMountUnlocked(Mount row) => this.unlockStateService.IsMountUnlocked(row); /// public bool IsNotebookDivisionUnlocked(NotebookDivision row) => this.unlockStateService.IsNotebookDivisionUnlocked(row); /// public bool IsOrchestrionUnlocked(Orchestrion row) => this.unlockStateService.IsOrchestrionUnlocked(row); /// public bool IsOrnamentUnlocked(Ornament row) => this.unlockStateService.IsOrnamentUnlocked(row); /// public bool IsPerformUnlocked(Perform row) => this.unlockStateService.IsPerformUnlocked(row); /// public bool IsPublicContentUnlocked(PublicContentSheet row) => this.unlockStateService.IsPublicContentUnlocked(row); /// public bool IsRecipeUnlocked(Recipe row) => this.unlockStateService.IsRecipeUnlocked(row); /// public bool IsRowRefUnlocked(RowRef rowRef) => this.unlockStateService.IsRowRefUnlocked(rowRef); /// public bool IsRowRefUnlocked(RowRef rowRef) where T : struct, IExcelRow => this.unlockStateService.IsRowRefUnlocked(rowRef); /// public bool IsSecretRecipeBookUnlocked(SecretRecipeBook row) => this.unlockStateService.IsSecretRecipeBookUnlocked(row); /// public bool IsTraitUnlocked(Trait row) => this.unlockStateService.IsTraitUnlocked(row); /// public bool IsTripleTriadCardUnlocked(TripleTriadCard row) => this.unlockStateService.IsTripleTriadCardUnlocked(row); /// public bool IsUnlockLinkUnlocked(uint unlockLink) => this.unlockStateService.IsUnlockLinkUnlocked(unlockLink); /// public bool IsUnlockLinkUnlocked(ushort unlockLink) => this.unlockStateService.IsUnlockLinkUnlocked(unlockLink); /// void IInternalDisposableService.DisposeService() { this.unlockStateService.Unlock -= this.UnlockForward; } private void UnlockForward(RowRef rowRef) => this.Unlock?.Invoke(rowRef); }