399 lines
14 KiB
C#
399 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Dalamud.Game;
|
|
using Dalamud.Plugin.Services;
|
|
using Lumina.Excel;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
|
|
namespace Questionable.Data;
|
|
|
|
internal sealed class JournalData
|
|
{
|
|
internal sealed class Genre
|
|
{
|
|
public uint Id { get; }
|
|
|
|
public string Name { get; }
|
|
|
|
public uint CategoryId { get; }
|
|
|
|
public List<IQuestInfo> Quests { get; }
|
|
|
|
public bool IsUnderOtherQuests { get; set; }
|
|
|
|
public Genre(JournalGenre journalGenre, List<IQuestInfo> quests)
|
|
{
|
|
Id = journalGenre.RowId;
|
|
Name = journalGenre.Name.ToString();
|
|
CategoryId = journalGenre.JournalCategory.RowId;
|
|
Quests = quests;
|
|
IsUnderOtherQuests = false;
|
|
}
|
|
|
|
public Genre(uint id, string name, uint categoryId, List<IQuestInfo> quests)
|
|
{
|
|
Id = id;
|
|
Name = name;
|
|
CategoryId = categoryId;
|
|
Quests = quests;
|
|
IsUnderOtherQuests = false;
|
|
}
|
|
|
|
public Genre(uint id, string name, uint categoryId, List<IQuestInfo> quests, bool isUnderOtherQuests = false)
|
|
{
|
|
Id = id;
|
|
Name = name;
|
|
CategoryId = categoryId;
|
|
Quests = quests;
|
|
IsUnderOtherQuests = isUnderOtherQuests;
|
|
}
|
|
}
|
|
|
|
internal sealed class Category
|
|
{
|
|
public uint Id { get; }
|
|
|
|
public string Name { get; }
|
|
|
|
public uint SectionId { get; }
|
|
|
|
public IReadOnlyList<Genre> Genres { get; }
|
|
|
|
public Category(JournalCategory journalCategory, IReadOnlyList<Genre> genres)
|
|
{
|
|
Id = journalCategory.RowId;
|
|
Name = journalCategory.Name.ToString();
|
|
SectionId = journalCategory.JournalSection.RowId;
|
|
Genres = genres;
|
|
}
|
|
|
|
public Category(uint id, string name, uint sectionId, IReadOnlyList<Genre> genres)
|
|
{
|
|
Id = id;
|
|
Name = name;
|
|
SectionId = sectionId;
|
|
Genres = genres;
|
|
}
|
|
}
|
|
|
|
internal sealed class Section
|
|
{
|
|
public uint Id { get; } = journalSection.RowId;
|
|
|
|
public string Name { get; } = journalSection.Name.ToString();
|
|
|
|
public IReadOnlyList<Category> Categories { get; }
|
|
|
|
public Section(JournalSection journalSection, IReadOnlyList<Category> categories)
|
|
{
|
|
Categories = categories;
|
|
base._002Ector();
|
|
}
|
|
}
|
|
|
|
private readonly ILogger<JournalData> _logger;
|
|
|
|
private const uint UnassignedGenreId = 4294967291u;
|
|
|
|
private const uint UnassignedCategoryId = 4294967291u;
|
|
|
|
private static readonly Dictionary<ushort, string> QuestGenreOverrides = new Dictionary<ushort, string>
|
|
{
|
|
{ 1119, "Zodiac Weapons" },
|
|
{ 1120, "Zodiac Weapons" },
|
|
{ 1121, "Zodiac Weapons" },
|
|
{ 1122, "Zodiac Weapons" },
|
|
{ 1123, "Zodiac Weapons" },
|
|
{ 1124, "Zodiac Weapons" },
|
|
{ 1125, "Zodiac Weapons" },
|
|
{ 1126, "Zodiac Weapons" },
|
|
{ 1127, "Zodiac Weapons" },
|
|
{ 1579, "Zodiac Weapons" },
|
|
{ 4173, "Radz-at-Han Sidequests" },
|
|
{ 4196, "Thavnairian Sidequests" },
|
|
{ 4197, "Thavnairian Sidequests" },
|
|
{ 4198, "Thavnairian Sidequests" },
|
|
{ 4199, "Thavnairian Sidequests" },
|
|
{ 4200, "Thavnairian Sidequests" },
|
|
{ 4201, "Thavnairian Sidequests" },
|
|
{ 4202, "Thavnairian Sidequests" },
|
|
{ 4203, "Thavnairian Sidequests" },
|
|
{ 4204, "Thavnairian Sidequests" },
|
|
{ 4205, "Thavnairian Sidequests" },
|
|
{ 4206, "Thavnairian Sidequests" },
|
|
{ 4207, "Thavnairian Sidequests" },
|
|
{ 4208, "Thavnairian Sidequests" },
|
|
{ 4209, "Thavnairian Sidequests" },
|
|
{ 101, "Rogue Quests" },
|
|
{ 177, "Gladiator Quests" },
|
|
{ 178, "Pugilist Quests" },
|
|
{ 179, "Marauder Quests" },
|
|
{ 180, "Lancer Quests" },
|
|
{ 181, "Archer Quests" },
|
|
{ 182, "Conjurer Quests" },
|
|
{ 183, "Thaumaturge Quests" },
|
|
{ 184, "Carpenter Quests" },
|
|
{ 185, "Blacksmith Quests" },
|
|
{ 186, "Armorer Quests" },
|
|
{ 187, "Goldsmith Quests" },
|
|
{ 188, "Leatherworker Quests" },
|
|
{ 189, "Weaver Quests" },
|
|
{ 190, "Alchemist Quests" },
|
|
{ 191, "Culinarian Quests" },
|
|
{ 192, "Miner Quests" },
|
|
{ 193, "Botanist Quests" },
|
|
{ 451, "Arcanist Quests" },
|
|
{ 1134, "Fisher Quests" },
|
|
{ 2109, "Machinist Quests" },
|
|
{ 437, "Ul'dahn Sidequests" },
|
|
{ 488, "Ul'dahn Sidequests" },
|
|
{ 489, "Ul'dahn Sidequests" },
|
|
{ 2106, "Ishgardian Sidequests" },
|
|
{ 2107, "Dravanian Forelands Sidequests" },
|
|
{ 2110, "Ishgardian Sidequests" },
|
|
{ 2123, "Ishgardian Sidequests" },
|
|
{ 2334, "Anima Weapons" },
|
|
{ 2387, "Palace of the Dead" },
|
|
{ 2389, "Order of the Twin Adder Quests" },
|
|
{ 2390, "Maelstrom Quests" },
|
|
{ 2391, "Immortal Flames Quests" },
|
|
{ 2612, "The Forbidden Land, Eureka" },
|
|
{ 2613, "The Forbidden Land, Eureka" },
|
|
{ 2920, "Rhalgr's Reach Sidequests" },
|
|
{ 2921, "Kugane Sidequests" },
|
|
{ 2941, "Rhalgr's Reach Sidequests" },
|
|
{ 2942, "The Forbidden Land, Eureka" },
|
|
{ 2943, "Kugane Sidequests" },
|
|
{ 3019, "Gridanian Sidequests" },
|
|
{ 3088, "Mor Dhonan Sidequests" },
|
|
{ 3602, "Crystarium Sidequests" },
|
|
{ 3603, "Eulmore Sidequests" },
|
|
{ 3604, "Crystarium Sidequests" },
|
|
{ 3605, "Crystarium Sidequests" },
|
|
{ 3662, "Crystarium Sidequests" },
|
|
{ 3759, "Ul'dahn Sidequests" },
|
|
{ 3760, "Ishgardian Restoration Main Quests" },
|
|
{ 3841, "Ishgardian Restoration Main Quests" },
|
|
{ 3842, "Lochs Sidequests" },
|
|
{ 3942, "Resistance Weapons" },
|
|
{ 3972, "Ishgardian Restoration Main Quests" },
|
|
{ 4027, "Resistance Weapons" },
|
|
{ 4030, "Ul'dahn Sidequests" },
|
|
{ 4041, "Resistance Weapons" },
|
|
{ 4042, "Ishgardian Restoration Main Quests" },
|
|
{ 4044, "YoRHa: Dark Apocalypse" },
|
|
{ 4045, "YoRHa: Dark Apocalypse" },
|
|
{ 4046, "YoRHa: Dark Apocalypse" },
|
|
{ 4047, "YoRHa: Dark Apocalypse" },
|
|
{ 4048, "YoRHa: Dark Apocalypse" },
|
|
{ 4049, "YoRHa: Dark Apocalypse" },
|
|
{ 4094, "Resistance Weapons" },
|
|
{ 4170, "Old Sharlayan Sidequests" },
|
|
{ 4171, "Old Sharlayan Sidequests" },
|
|
{ 4174, "Old Sharlayan Sidequests" },
|
|
{ 4175, "Radz-at-Han Sidequests" },
|
|
{ 4644, "Island Sanctuary Quests" },
|
|
{ 4651, "Old Sharlayan Sidequests" },
|
|
{ 5003, "Tuliyollal Sidequests" },
|
|
{ 5004, "Tuliyollal Sidequests" },
|
|
{ 5006, "Tuliyollal Sidequests" },
|
|
{ 5007, "Solution Nine Sidequests" },
|
|
{ 5008, "Solution Nine Sidequests" },
|
|
{ 5408, "Crystarium Sidequests" },
|
|
{ 5459, "Old Sharlayan Sidequests" }
|
|
};
|
|
|
|
private static readonly HashSet<ushort> SortKeyZeroOverrides = new HashSet<ushort> { 1119, 1120, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1579 };
|
|
|
|
public uint? SeasonalEventsCategoryId { get; }
|
|
|
|
public IReadOnlySet<uint> SeasonalEventGenreIds { get; }
|
|
|
|
public uint? MoogleDeliveryGenreId { get; }
|
|
|
|
public List<Genre> Genres { get; }
|
|
|
|
public List<Category> Categories { get; }
|
|
|
|
public List<Section> Sections { get; }
|
|
|
|
public int? OtherQuestsSectionRowId { get; private set; }
|
|
|
|
public JournalData(IDataManager dataManager, QuestData questData, ILogger<JournalData> logger)
|
|
{
|
|
JournalData journalData = this;
|
|
_logger = logger;
|
|
ExcelSheet<JournalGenre> excelSheet = dataManager.GetExcelSheet<JournalGenre>();
|
|
ExcelSheet<JournalGenre> excelSheet2 = dataManager.GetExcelSheet<JournalGenre>(ClientLanguage.English);
|
|
JournalCategory journalCategory = dataManager.GetExcelSheet<JournalCategory>(ClientLanguage.English).FirstOrDefault((JournalCategory x) => x.Name.ToString().Equals("Seasonal Events", StringComparison.OrdinalIgnoreCase));
|
|
if (journalCategory.RowId != 0)
|
|
{
|
|
SeasonalEventsCategoryId = journalCategory.RowId;
|
|
_logger.LogDebug("Resolved 'Seasonal Events' JournalCategory ID: {CategoryId}", SeasonalEventsCategoryId);
|
|
}
|
|
else
|
|
{
|
|
SeasonalEventsCategoryId = null;
|
|
_logger.LogWarning("Could not find 'Seasonal Events' JournalCategory - seasonal event detection may not work correctly");
|
|
}
|
|
if (SeasonalEventsCategoryId.HasValue)
|
|
{
|
|
SeasonalEventGenreIds = (from x in excelSheet
|
|
where x.RowId != 0 && x.JournalCategory.RowId == journalData.SeasonalEventsCategoryId.Value
|
|
select x.RowId).ToHashSet();
|
|
_logger.LogDebug("Resolved {Count} seasonal event JournalGenre IDs from JournalCategory {CategoryId}: [{Ids}]", SeasonalEventGenreIds.Count, SeasonalEventsCategoryId.Value, string.Join(", ", SeasonalEventGenreIds.OrderBy((uint x) => x)));
|
|
}
|
|
else
|
|
{
|
|
SeasonalEventGenreIds = new HashSet<uint>();
|
|
}
|
|
JournalGenre journalGenre = excelSheet2.FirstOrDefault((JournalGenre x) => x.Name.ToString().Equals("Delivery Moogle Quests", StringComparison.OrdinalIgnoreCase));
|
|
if (journalGenre.RowId != 0)
|
|
{
|
|
MoogleDeliveryGenreId = journalGenre.RowId;
|
|
_logger.LogDebug("Resolved 'Delivery Moogle Quests' JournalGenre ID: {GenreId}", MoogleDeliveryGenreId);
|
|
}
|
|
else
|
|
{
|
|
MoogleDeliveryGenreId = null;
|
|
_logger.LogWarning("Could not find 'Delivery Moogle Quests' JournalGenre - moogle delivery quest detection may not work correctly");
|
|
}
|
|
ApplyGenreOverrides(excelSheet2, questData);
|
|
List<Genre> list = (from x in excelSheet
|
|
where x.RowId != 0 && x.Icon > 0
|
|
select new Genre(x, questData.GetAllByJournalGenre(x.RowId))).ToList();
|
|
QuestRedo row = dataManager.GetExcelSheet<QuestRedo>().GetRow(1u);
|
|
QuestRedo row2 = dataManager.GetExcelSheet<QuestRedo>().GetRow(2u);
|
|
QuestRedo row3 = dataManager.GetExcelSheet<QuestRedo>().GetRow(3u);
|
|
Genre genreLimsa = new Genre(4294967292u, "Starting in Limsa Lominsa", 1u, (from x in new uint[2] { 108u, 109u }.Concat(row.QuestRedoParam.Select((QuestRedo.QuestRedoParamStruct x) => x.Quest.RowId))
|
|
where x != 0
|
|
select questData.GetQuestInfo(QuestId.FromRowId(x))).ToList());
|
|
Genre genreGridania = new Genre(4294967293u, "Starting in Gridania", 1u, (from x in new uint[3] { 85u, 123u, 124u }.Concat(row2.QuestRedoParam.Select((QuestRedo.QuestRedoParamStruct x) => x.Quest.RowId))
|
|
where x != 0
|
|
select questData.GetQuestInfo(QuestId.FromRowId(x))).ToList());
|
|
Genre genreUldah = new Genre(4294967294u, "Starting in Ul'dah", 1u, (from x in new uint[3] { 568u, 569u, 570u }.Concat(row3.QuestRedoParam.Select((QuestRedo.QuestRedoParamStruct x) => x.Quest.RowId))
|
|
where x != 0
|
|
select questData.GetQuestInfo(QuestId.FromRowId(x))).ToList());
|
|
list.InsertRange(0, new Genre[3] { genreLimsa, genreGridania, genreUldah });
|
|
list.Single((Genre x) => x.Id == 1).Quests.RemoveAll((IQuestInfo x) => genreLimsa.Quests.Contains(x) || genreGridania.Quests.Contains(x) || genreUldah.Quests.Contains(x));
|
|
HashSet<ElementId> assignedQuestIds = new HashSet<ElementId>(from q in list.SelectMany((Genre g) => g.Quests)
|
|
select q.QuestId);
|
|
List<QuestInfo> list2 = (from QuestInfo x in questData.AllQuests.Values.Where((IQuestInfo x) => x is QuestInfo && !assignedQuestIds.Contains(x.QuestId))
|
|
orderby x.SortKey, x.QuestId
|
|
select x).ToList();
|
|
Genre genre = null;
|
|
if (list2.Count > 0)
|
|
{
|
|
_logger.LogWarning("Found {Count} quests not assigned to any journal genre:", list2.Count);
|
|
foreach (QuestInfo item in list2)
|
|
{
|
|
_logger.LogWarning(" Orphaned quest {QuestId} '{Name}' (JournalGenre={Genre})", item.QuestId, item.Name, item.JournalGenre);
|
|
}
|
|
genre = new Genre(4294967291u, "Unassigned Quests", 4294967291u, list2.Cast<IQuestInfo>().ToList());
|
|
list.Add(genre);
|
|
}
|
|
Genres = list.ToList();
|
|
OtherQuestsSectionRowId = GetOtherQuestsSectionRowId(dataManager);
|
|
Categories = (from x in dataManager.GetExcelSheet<JournalCategory>()
|
|
where x.RowId != 0
|
|
select new Category(x, journalData.Genres.Where((Genre y) => y.CategoryId == x.RowId).ToList())).ToList();
|
|
int? otherQuestsSectionRowId;
|
|
if (genre != null)
|
|
{
|
|
otherQuestsSectionRowId = OtherQuestsSectionRowId;
|
|
if (otherQuestsSectionRowId.HasValue)
|
|
{
|
|
int valueOrDefault = otherQuestsSectionRowId.GetValueOrDefault();
|
|
Categories.Add(new Category(4294967291u, "Unassigned", (uint)valueOrDefault, new global::_003C_003Ez__ReadOnlySingleElementList<Genre>(genre)));
|
|
}
|
|
else
|
|
{
|
|
_logger.LogWarning("'Other Quests' section not found - {Count} unassigned quests will not be shown in the journal", list2.Count);
|
|
}
|
|
}
|
|
Sections = (from x in dataManager.GetExcelSheet<JournalSection>()
|
|
select new Section(x, journalData.Categories.Where((Category y) => y.SectionId == x.RowId).ToList())).ToList();
|
|
otherQuestsSectionRowId = OtherQuestsSectionRowId;
|
|
if (otherQuestsSectionRowId.HasValue)
|
|
{
|
|
int valueOrDefault2 = otherQuestsSectionRowId.GetValueOrDefault();
|
|
uint otherIdU = (uint)valueOrDefault2;
|
|
Section section = Sections.FirstOrDefault((Section s) => s.Id == otherIdU);
|
|
if (section != null)
|
|
{
|
|
int num = 0;
|
|
foreach (Category category in section.Categories)
|
|
{
|
|
foreach (Genre genre2 in category.Genres)
|
|
{
|
|
genre2.IsUnderOtherQuests = true;
|
|
num++;
|
|
}
|
|
}
|
|
_logger.LogDebug("Marked {Count} genres as under 'Other Quests' (OtherQuestsSectionRowId={Id})", num, valueOrDefault2);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogWarning("OtherQuestsSectionRowId = {Id} found, but matching Section not present in constructed Sections", valueOrDefault2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_logger.LogDebug("OtherQuestsSectionRowId not found - 'Other Quests' genre marking skipped");
|
|
}
|
|
}
|
|
|
|
private void ApplyGenreOverrides(ExcelSheet<JournalGenre> journalGenreSheetEnglish, QuestData questData)
|
|
{
|
|
Dictionary<string, uint> dictionary = new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (JournalGenre item in journalGenreSheetEnglish)
|
|
{
|
|
if (item.RowId != 0 && !item.Name.IsEmpty)
|
|
{
|
|
dictionary.TryAdd(item.Name.ToString(), item.RowId);
|
|
}
|
|
}
|
|
int num = 0;
|
|
foreach (var (num3, text2) in QuestGenreOverrides)
|
|
{
|
|
if (!dictionary.TryGetValue(text2, out var value))
|
|
{
|
|
_logger.LogWarning("Genre '{GenreName}' not found for quest {QuestId}", text2, num3);
|
|
}
|
|
else
|
|
{
|
|
if (!questData.TryGetQuestInfo(new QuestId(num3), out IQuestInfo questInfo))
|
|
{
|
|
continue;
|
|
}
|
|
if (questInfo is QuestInfo questInfo2)
|
|
{
|
|
questInfo2.JournalGenre = value;
|
|
if (SortKeyZeroOverrides.Contains(num3))
|
|
{
|
|
questInfo2.SortKey = 0;
|
|
}
|
|
}
|
|
num++;
|
|
}
|
|
}
|
|
if (num > 0)
|
|
{
|
|
_logger.LogDebug("Applied {Count} journal genre overrides", num);
|
|
}
|
|
}
|
|
|
|
private static int? GetOtherQuestsSectionRowId(IDataManager dataManager)
|
|
{
|
|
JournalSection journalSection = dataManager.GetExcelSheet<JournalSection>(ClientLanguage.English).FirstOrDefault((JournalSection s) => s.Name.ToString() == "Other Quests");
|
|
if (journalSection.RowId == 0)
|
|
{
|
|
return null;
|
|
}
|
|
return (int)journalSection.RowId;
|
|
}
|
|
}
|