384 lines
11 KiB
C#
384 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Dalamud.Game;
|
|
using Dalamud.Plugin.Services;
|
|
using Dalamud.Utility;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|
using Lumina.Excel;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
|
|
namespace Questionable.Data;
|
|
|
|
internal sealed class DutyUnlockData
|
|
{
|
|
private static class ContentTypeIds
|
|
{
|
|
public const uint Dungeon = 2u;
|
|
|
|
public const uint Guildhest = 3u;
|
|
|
|
public const uint Trial = 4u;
|
|
|
|
public const uint Raid = 5u;
|
|
|
|
public const uint PvP = 6u;
|
|
|
|
public const uint QuestBattle = 7u;
|
|
|
|
public const uint DeepDungeon = 21u;
|
|
|
|
public const uint AllianceRaid = 26u;
|
|
|
|
public const uint Ultimate = 28u;
|
|
|
|
public const uint VCDungeonFinder = 29u;
|
|
|
|
public const uint Criterion = 30u;
|
|
|
|
public const uint CriterionSavage = 31u;
|
|
}
|
|
|
|
private static class ContentMemberTypeIds
|
|
{
|
|
public const byte LightParty = 2;
|
|
|
|
public const byte FullParty = 3;
|
|
|
|
public const byte Alliance = 4;
|
|
}
|
|
|
|
private readonly ImmutableDictionary<uint, DutyInfo> _duties;
|
|
|
|
private readonly ImmutableDictionary<QuestId, ImmutableList<uint>> _questToDuties;
|
|
|
|
private readonly ImmutableDictionary<uint, ImmutableList<QuestId>> _dutyToQuests;
|
|
|
|
private readonly ILogger<DutyUnlockData> _logger;
|
|
|
|
private static readonly HashSet<uint> VariantDungeonCfcIds = new HashSet<uint> { 868u, 945u, 961u };
|
|
|
|
public DutyUnlockData(IDataManager dataManager, ILogger<DutyUnlockData> logger)
|
|
{
|
|
_logger = logger;
|
|
ExcelSheet<ContentFinderCondition> excelSheet = dataManager.GetExcelSheet<ContentFinderCondition>();
|
|
ExcelSheet<QuestEx> excelSheet2 = dataManager.GetExcelSheet<QuestEx>();
|
|
ImmutableDictionary<uint, uint> immutableDictionary = (from c in excelSheet
|
|
where c.RowId != 0 && c.Content.RowId != 0 && c.ContentLinkType == 1
|
|
group c by c.Content.RowId).ToImmutableDictionary((IGrouping<uint, ContentFinderCondition> g) => g.Key, (IGrouping<uint, ContentFinderCondition> g) => g.First().RowId);
|
|
Dictionary<QuestId, List<uint>> dictionary = new Dictionary<QuestId, List<uint>>();
|
|
Dictionary<uint, List<QuestId>> dictionary2 = new Dictionary<uint, List<QuestId>>();
|
|
foreach (QuestEx item in excelSheet2.Where((QuestEx q) => q.RowId != 0))
|
|
{
|
|
uint rowId = item.InstanceContentUnlock.RowId;
|
|
if (rowId != 0 && immutableDictionary.TryGetValue(rowId, out var value))
|
|
{
|
|
QuestId questId = QuestId.FromRowId(item.RowId);
|
|
if (!dictionary.ContainsKey(questId))
|
|
{
|
|
dictionary[questId] = new List<uint>();
|
|
}
|
|
dictionary[questId].Add(value);
|
|
if (!dictionary2.ContainsKey(value))
|
|
{
|
|
dictionary2[value] = new List<QuestId>();
|
|
}
|
|
dictionary2[value].Add(questId);
|
|
}
|
|
}
|
|
_questToDuties = dictionary.ToImmutableDictionary((KeyValuePair<QuestId, List<uint>> x) => x.Key, (KeyValuePair<QuestId, List<uint>> x) => x.Value.ToImmutableList());
|
|
_dutyToQuests = dictionary2.ToImmutableDictionary((KeyValuePair<uint, List<QuestId>> x) => x.Key, (KeyValuePair<uint, List<QuestId>> x) => x.Value.ToImmutableList());
|
|
Dictionary<uint, DutyInfo> dictionary3 = new Dictionary<uint, DutyInfo>();
|
|
foreach (ContentFinderCondition item2 in excelSheet.Where((ContentFinderCondition x) => x.RowId != 0 && x.Content.RowId != 0))
|
|
{
|
|
if (item2.ContentLinkType != 1)
|
|
{
|
|
continue;
|
|
}
|
|
ContentType? valueNullable = item2.ContentType.ValueNullable;
|
|
if (!valueNullable.HasValue)
|
|
{
|
|
continue;
|
|
}
|
|
uint rowId2 = valueNullable.Value.RowId;
|
|
if (rowId2 - 6 > 1)
|
|
{
|
|
string text = FixName(item2.Name.ToDalamudString().ToString(), dataManager.Language);
|
|
if (!string.IsNullOrWhiteSpace(text) && !IsExcludedContent(text, item2.RowId))
|
|
{
|
|
List<QuestId> value2;
|
|
ImmutableList<QuestId> unlockQuests = (dictionary2.TryGetValue(item2.RowId, out value2) ? value2.ToImmutableList() : ImmutableList<QuestId>.Empty);
|
|
uint rowId3 = item2.ContentMemberType.RowId;
|
|
EDutyCategory dutyCategory = DetermineDutyCategory(rowId2, text, item2.HighEndDuty, item2.RowId, (byte)rowId3);
|
|
DutyInfo value3 = new DutyInfo(item2.RowId, item2.Content.RowId, text, rowId2, valueNullable.Value.Name.ToDalamudString().ToString(), item2.ClassJobLevelRequired, item2.ItemLevelRequired, unlockQuests, item2.HighEndDuty, dutyCategory);
|
|
dictionary3[item2.RowId] = value3;
|
|
}
|
|
}
|
|
}
|
|
_duties = dictionary3.ToImmutableDictionary();
|
|
_logger.LogInformation("Loaded {DutyCount} duties with {QuestMappings} quest unlock mappings", _duties.Count, _questToDuties.Values.Sum((ImmutableList<uint> x) => x.Count));
|
|
}
|
|
|
|
private static bool IsExcludedContent(string name, uint cfcId)
|
|
{
|
|
if (cfcId - 69 <= 2)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static EDutyCategory DetermineDutyCategory(uint contentTypeId, string name, bool isHighEndDuty, uint cfcId, byte contentMemberType)
|
|
{
|
|
if (VariantDungeonCfcIds.Contains(cfcId))
|
|
{
|
|
return EDutyCategory.VariantDungeon;
|
|
}
|
|
switch (contentTypeId)
|
|
{
|
|
case 2u:
|
|
if (name.EndsWith("(Hard)", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return EDutyCategory.HardDungeon;
|
|
}
|
|
return EDutyCategory.Dungeon;
|
|
case 3u:
|
|
return EDutyCategory.Guildhest;
|
|
case 4u:
|
|
if (name.EndsWith("(Unreal)", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return EDutyCategory.UnrealTrial;
|
|
}
|
|
if (name.EndsWith("(Extreme)", StringComparison.OrdinalIgnoreCase) || name.StartsWith("The Minstrel's Ballad:", StringComparison.OrdinalIgnoreCase) || isHighEndDuty)
|
|
{
|
|
return EDutyCategory.ExtremeTrial;
|
|
}
|
|
if (name.EndsWith("(Hard)", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return EDutyCategory.HardTrial;
|
|
}
|
|
return EDutyCategory.Trial;
|
|
case 5u:
|
|
if (contentMemberType == 4)
|
|
{
|
|
if (isHighEndDuty)
|
|
{
|
|
return EDutyCategory.ChaoticAllianceRaid;
|
|
}
|
|
return EDutyCategory.AllianceRaid;
|
|
}
|
|
if (name.EndsWith("(Savage)", StringComparison.OrdinalIgnoreCase) || isHighEndDuty)
|
|
{
|
|
return EDutyCategory.SavageRaid;
|
|
}
|
|
return EDutyCategory.NormalRaid;
|
|
case 21u:
|
|
return EDutyCategory.DeepDungeon;
|
|
case 26u:
|
|
if (isHighEndDuty)
|
|
{
|
|
return EDutyCategory.ChaoticAllianceRaid;
|
|
}
|
|
return EDutyCategory.AllianceRaid;
|
|
case 28u:
|
|
return EDutyCategory.Ultimate;
|
|
case 29u:
|
|
if (name.EndsWith("(Savage)", StringComparison.OrdinalIgnoreCase) || isHighEndDuty)
|
|
{
|
|
return EDutyCategory.CriterionSavage;
|
|
}
|
|
return EDutyCategory.CriterionDungeon;
|
|
case 30u:
|
|
if (name.EndsWith("(Savage)", StringComparison.OrdinalIgnoreCase) || isHighEndDuty)
|
|
{
|
|
return EDutyCategory.CriterionSavage;
|
|
}
|
|
return EDutyCategory.CriterionDungeon;
|
|
case 31u:
|
|
return EDutyCategory.CriterionSavage;
|
|
default:
|
|
if (name.EndsWith("(Savage)", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return EDutyCategory.SavageRaid;
|
|
}
|
|
if (name.EndsWith("(Extreme)", StringComparison.OrdinalIgnoreCase) || name.StartsWith("The Minstrel's Ballad:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return EDutyCategory.ExtremeTrial;
|
|
}
|
|
return EDutyCategory.Other;
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetAllDuties()
|
|
{
|
|
return _duties.Values;
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetDutiesByContentType(uint contentTypeId)
|
|
{
|
|
return _duties.Values.Where((DutyInfo d) => d.ContentTypeId == contentTypeId);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetDutiesByCategory(EDutyCategory category)
|
|
{
|
|
return _duties.Values.Where((DutyInfo d) => d.DutyCategory == category);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetDungeons()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.Dungeon);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetHardDungeons()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.HardDungeon);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetGuildhests()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.Guildhest);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetTrials()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.Trial);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetHardTrials()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.HardTrial);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetExtremeTrials()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.ExtremeTrial);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetUnrealTrials()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.UnrealTrial);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetNormalRaids()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.NormalRaid);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetSavageRaids()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.SavageRaid);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetUltimateRaids()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.Ultimate);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetAllianceRaids()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.AllianceRaid);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetChaoticAllianceRaids()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.ChaoticAllianceRaid);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetDeepDungeons()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.DeepDungeon);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetVariantDungeons()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.VariantDungeon);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetCriterionDungeons()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.CriterionDungeon);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetCriterionSavageDungeons()
|
|
{
|
|
return GetDutiesByCategory(EDutyCategory.CriterionSavage);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetDutiesUnlockedByQuest(QuestId questId)
|
|
{
|
|
if (!_questToDuties.TryGetValue(questId, out ImmutableList<uint> value))
|
|
{
|
|
yield break;
|
|
}
|
|
foreach (uint item in value)
|
|
{
|
|
if (_duties.TryGetValue(item, out DutyInfo value2))
|
|
{
|
|
yield return value2;
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<QuestId> GetUnlockQuestsForDuty(uint cfcId)
|
|
{
|
|
if (_dutyToQuests.TryGetValue(cfcId, out ImmutableList<QuestId> value))
|
|
{
|
|
return value;
|
|
}
|
|
return Array.Empty<QuestId>();
|
|
}
|
|
|
|
public bool IsDutyUnlocked(uint cfcId)
|
|
{
|
|
if (!_duties.TryGetValue(cfcId, out DutyInfo value))
|
|
{
|
|
return false;
|
|
}
|
|
return UIState.IsInstanceContentUnlocked(value.ContentId);
|
|
}
|
|
|
|
public static bool IsContentUnlocked(uint contentId)
|
|
{
|
|
return UIState.IsInstanceContentUnlocked(contentId);
|
|
}
|
|
|
|
public bool TryGetDuty(uint cfcId, [NotNullWhen(true)] out DutyInfo? duty)
|
|
{
|
|
return _duties.TryGetValue(cfcId, out duty);
|
|
}
|
|
|
|
public DutyInfo? GetDuty(uint cfcId)
|
|
{
|
|
return _duties.GetValueOrDefault(cfcId);
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetUnlockedDuties()
|
|
{
|
|
return _duties.Values.Where((DutyInfo d) => UIState.IsInstanceContentUnlocked(d.ContentId));
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetLockedDuties()
|
|
{
|
|
return _duties.Values.Where((DutyInfo d) => !UIState.IsInstanceContentUnlocked(d.ContentId));
|
|
}
|
|
|
|
public IEnumerable<DutyInfo> GetDutiesWithUnlockQuests()
|
|
{
|
|
return _duties.Values.Where((DutyInfo d) => d.UnlockQuests.Count > 0);
|
|
}
|
|
}
|