using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using Dalamud.Game; using Dalamud.Plugin.Services; using Dalamud.Utility; using Lumina.Excel; using Lumina.Excel.Sheets; using Microsoft.Extensions.Logging; using Questionable.Model.Questing; using SmartNav; namespace Questionable.Data; internal sealed class TerritoryData : ITerritoryInfo { public readonly record struct ZoneAccessRequirement(byte RequirementType, uint QuestRowId, uint RequiredSequence); public sealed record ContentFinderConditionData(uint ContentFinderConditionId, string Name, uint TerritoryId, ushort RequiredItemLevel, byte ClassJobLevelSync, bool AllowUndersized, bool IsDungeon) { public ContentFinderConditionData(ContentFinderCondition condition, ClientLanguage clientLanguage) : this(condition.RowId, FixName(condition.Name.ToDalamudString().ToString(), clientLanguage), condition.TerritoryType.RowId, condition.ItemLevelRequired, condition.ClassJobLevelSync, condition.AllowUndersized, condition.ContentType.RowId == 2) { } } private readonly ImmutableDictionary _territoryNames; private readonly ImmutableHashSet _territoriesWithMount; private readonly ImmutableDictionary _territoryExVersions; private readonly ImmutableDictionary _territoryZoneAccessRequirements; private readonly ImmutableDictionary _dutyTerritories; private readonly ImmutableDictionary _instanceNames; private readonly ImmutableDictionary _contentFinderConditions; private readonly ImmutableDictionary _territoryToContentFinderCondition; private readonly ImmutableDictionary<(ElementId QuestId, byte Index), uint> _questBattlesToContentFinderCondition; public TerritoryData(IDataManager dataManager, ILogger logger) { Dictionary dictionary = dataManager.GetSubrowExcelSheet().Flatten().Where(delegate(ZoneSharedGroup x) { bool flag = x.SubrowId == 0; if (flag) { byte b = x.RequirementType[0]; bool flag2 = (uint)(b - 1) <= 1u; flag = flag2; } return flag; }) .ToDictionary((ZoneSharedGroup x) => x.RowId, (ZoneSharedGroup x) => new ZoneAccessRequirement(x.RequirementType[0], x.RequirementRow[0].RowId, x.RequirementQuestSequence[0])); ImmutableDictionary.Builder builder = ImmutableDictionary.CreateBuilder(); ImmutableHashSet.Builder builder2 = ImmutableHashSet.CreateBuilder(); ImmutableDictionary.Builder builder3 = ImmutableDictionary.CreateBuilder(); ImmutableDictionary.Builder builder4 = ImmutableDictionary.CreateBuilder(); ImmutableDictionary.Builder builder5 = ImmutableDictionary.CreateBuilder(); List list = new List(); foreach (TerritoryType item in dataManager.GetExcelSheet()) { if (item.RowId == 0) { continue; } string value = item.PlaceName.ValueNullable?.Name.ToString() ?? item.PlaceNameZone.ValueNullable?.Name.ToString(); if (!string.IsNullOrEmpty(value)) { builder[item.RowId] = value; } if (item.Mount) { builder2.Add(item.RowId); } if (item.ExVersion.RowId != 0) { builder3[item.RowId] = (byte)item.ExVersion.RowId; } if (item.ZoneSharedGroup.RowId != 0 && dictionary.TryGetValue(item.ZoneSharedGroup.RowId, out var value2)) { if ((dataManager.GetExcelSheet().GetRowOrDefault(value2.QuestRowId)?.Expansion.RowId ?? 0) > item.ExVersion.RowId) { list.Add(item.RowId); } else { builder4[item.RowId] = value2; } } RowRef contentFinderCondition = item.ContentFinderCondition; if (contentFinderCondition.RowId != 0) { ContentFinderCondition? valueNullable = contentFinderCondition.ValueNullable; if (valueNullable.HasValue) { ContentFinderCondition valueOrDefault = valueNullable.GetValueOrDefault(); builder5[item.RowId] = valueOrDefault.ContentType.RowId; } } } _territoryNames = builder.ToImmutable(); _territoriesWithMount = builder2.ToImmutable(); _territoryExVersions = builder3.ToImmutable(); _territoryZoneAccessRequirements = builder4.ToImmutable(); _dutyTerritories = builder5.ToImmutable(); if (list.Count > 0) { logger.LogDebug("Ignoring later-expansion ZoneSharedGroup gate for {Count} walkable zones: {Zones}", list.Count, string.Join(", ", list.Select(GetNameAndId))); } _instanceNames = (from x in dataManager.GetExcelSheet() where x.RowId != 0 && x.Content.RowId != 0 && x.ContentLinkType == 1 && x.ContentType.RowId != 6 group x by x.Content.RowId).ToImmutableDictionary((IGrouping g) => g.Key, (IGrouping g) => g.First().Name.ToDalamudString().ToString()); _contentFinderConditions = (from x in dataManager.GetExcelSheet().Where(delegate(ContentFinderCondition x) { bool flag = x.RowId != 0 && x.Content.RowId != 0; if (flag) { byte contentLinkType = x.ContentLinkType; bool flag2 = ((contentLinkType == 1 || contentLinkType == 5) ? true : false); flag = flag2; } return flag && x.ContentType.RowId != 6; }) select new ContentFinderConditionData(x, dataManager.Language)).ToImmutableDictionary((ContentFinderConditionData x) => x.ContentFinderConditionId, (ContentFinderConditionData x) => x); _territoryToContentFinderCondition = (from x in dataManager.GetExcelSheet() where x.RowId != 0 && x.TerritoryType.RowId != 0 group x by x.TerritoryType.RowId).ToImmutableDictionary((IGrouping g) => g.Key, (IGrouping g) => g.First().RowId); _questBattlesToContentFinderCondition = (from x in (from x in dataManager.GetExcelSheet() where x.RowId != 0 && x.IssuerLocation.RowId != 0 select x).SelectMany(GetQuestBattles) select (QuestId: x.QuestId, Index: x.Index, CfcId: LookupContentFinderConditionForQuestBattle(dataManager, x.QuestBattleId))).ToImmutableDictionary<(ElementId, byte, uint), (ElementId, byte), uint>(((ElementId QuestId, byte Index, uint CfcId) x) => (QuestId: x.QuestId, Index: x.Index), ((ElementId QuestId, byte Index, uint CfcId) x) => x.CfcId); } public string? GetName(uint territoryId) { return _territoryNames.GetValueOrDefault(territoryId); } public string GetNameAndId(uint territoryId) { string name = GetName(territoryId); if (name != null) { IFormatProvider invariantCulture = CultureInfo.InvariantCulture; DefaultInterpolatedStringHandler handler = new DefaultInterpolatedStringHandler(3, 2, invariantCulture); handler.AppendFormatted(name); handler.AppendLiteral(" ("); handler.AppendFormatted(territoryId); handler.AppendLiteral(")"); return string.Create(invariantCulture, ref handler); } return territoryId.ToString(CultureInfo.InvariantCulture); } public bool CanUseMount(uint territoryId) { return _territoriesWithMount.Contains(territoryId); } public byte GetExVersion(uint territoryId) { if (!_territoryExVersions.TryGetValue(territoryId, out var value)) { return 0; } return value; } public ZoneAccessRequirement? GetZoneAccessRequirement(uint territoryId) { if (!_territoryZoneAccessRequirements.TryGetValue(territoryId, out var value)) { return null; } return value; } public bool IsDutyInstance(uint territoryId) { return _dutyTerritories.ContainsKey(territoryId); } public bool IsQuestBattleInstance(uint territoryId) { if (_dutyTerritories.TryGetValue(territoryId, out var value)) { return value == 7; } return false; } public bool IsFieldOperation(uint territoryId) { uint value; bool flag = _dutyTerritories.TryGetValue(territoryId, out value); if (flag) { bool flag2 = ((value == 26 || value == 29 || value == 38) ? true : false); flag = flag2; } return flag; } public string? GetInstanceName(uint instanceId) { return _instanceNames.GetValueOrDefault(instanceId); } public ContentFinderConditionData? GetContentFinderCondition(uint cfcId) { return _contentFinderConditions.GetValueOrDefault(cfcId); } public bool TryGetContentFinderCondition(uint cfcId, [NotNullWhen(true)] out ContentFinderConditionData? contentFinderConditionData) { return _contentFinderConditions.TryGetValue(cfcId, out contentFinderConditionData); } public bool TryGetContentFinderConditionForSoloInstance(ElementId questId, byte index, [NotNullWhen(true)] out ContentFinderConditionData? contentFinderConditionData) { if (_questBattlesToContentFinderCondition.TryGetValue((questId, index), out var value)) { return _contentFinderConditions.TryGetValue(value, out contentFinderConditionData); } contentFinderConditionData = null; return false; } public bool TryGetContentFinderConditionIdForTerritory(uint territoryId, out uint cfcId) { return _territoryToContentFinderCondition.TryGetValue(territoryId, out cfcId); } public IEnumerable<(ElementId QuestId, byte Index, ContentFinderConditionData Data)> GetAllQuestsWithQuestBattles() { return _questBattlesToContentFinderCondition.Select, (ElementId, byte, ContentFinderConditionData)>((KeyValuePair<(ElementId QuestId, byte Index), uint> x) => (QuestId: x.Key.QuestId, Index: x.Key.Index, _contentFinderConditions[x.Value])); } private static string FixName(string name, ClientLanguage language) { if (string.IsNullOrEmpty(name) || language != ClientLanguage.English) { return name; } return string.Concat(name[0].ToString().ToUpper(CultureInfo.InvariantCulture).AsSpan(), name.AsSpan(1)); } private static IEnumerable<(ElementId QuestId, byte Index, uint QuestBattleId)> GetQuestBattles(Quest quest) { foreach (Quest.QuestParamsStruct questParam in quest.QuestParams) { if (questParam.ScriptInstruction == "QUESTBATTLE0") { yield return (QuestId: QuestId.FromRowId(quest.RowId), Index: 0, QuestBattleId: questParam.ScriptArg); } else if (questParam.ScriptInstruction == "QUESTBATTLE1") { yield return (QuestId: QuestId.FromRowId(quest.RowId), Index: 1, QuestBattleId: questParam.ScriptArg); } else if (questParam.ScriptInstruction.IsEmpty) { break; } } } private static uint LookupContentFinderConditionForQuestBattle(IDataManager dataManager, uint questBattleId) { if (questBattleId >= 5000) { return dataManager.GetExcelSheet().GetRow(questBattleId).ContentFinderCondition.RowId; } return dataManager.GetExcelSheet().GetRow(questBattleId).SoloDuty.RowId; } }