1
0
This commit is contained in:
Liza 2024-11-25 19:53:44 +01:00
parent 8f7b0a6ffd
commit 7a3acf9da6
Signed by: liza
GPG Key ID: 8DD6D21C03BB0848
8 changed files with 143 additions and 122 deletions

View File

@ -1,7 +1,7 @@
using Lumina.Excel.GeneratedSheets; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Lumina.Excel.Sheets;
namespace QuestMap namespace QuestMap
{ {

28
QuestMap/IQuestInfo.cs Normal file
View File

@ -0,0 +1,28 @@
using System.Collections.Generic;
using Lumina.Excel.Sheets;
namespace QuestMap;
public interface IQuestInfo
{
public Quest Quest { get; }
public uint RowId { get; }
public string Name { get; }
public IEnumerable<Quest> PreviousQuests();
}
public sealed class SheetQuestInfo(Quest quest) : IQuestInfo
{
public Quest Quest => quest;
public uint RowId => quest.RowId;
public string Name => quest.Name.ToString();
public IEnumerable<Quest> PreviousQuests() => quest.PreviousQuests();
}
public sealed class ConsolidatedQuestInfo(IQuestInfo baseQuest, string name) : IQuestInfo
{
public Quest Quest => baseQuest.Quest;
public uint RowId => baseQuest.RowId;
public string Name => name;
public IEnumerable<Quest> PreviousQuests() => baseQuest.PreviousQuests();
}

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
namespace QuestMap { namespace QuestMap {
internal class Node<T> { internal class Node<T> where T : IQuestInfo {
internal uint Id { get; } internal uint Id { get; }
internal List<Node<T>> Parents { get; set; } internal List<Node<T>> Parents { get; set; }
internal T Value { get; set; } internal T Value { get; set; }
@ -101,16 +101,16 @@ namespace QuestMap {
} }
} }
internal static (List<Node<Quest>>, Dictionary<uint, Node<Quest>>) BuildTree(Dictionary<uint, Quest> layouts) { internal static (List<Node<IQuestInfo>>, Dictionary<uint, Node<IQuestInfo>>) BuildTree(Dictionary<uint, IQuestInfo> layouts) {
var lookup = new Dictionary<uint, Node<Quest>>(); var lookup = new Dictionary<uint, Node<IQuestInfo>>();
var rootNodes = new List<Node<Quest>>(); var rootNodes = new List<Node<IQuestInfo>>();
var allNodes = new Dictionary<uint, Node<Quest>>(); var allNodes = new Dictionary<uint, Node<IQuestInfo>>();
foreach (var item in layouts) { foreach (var item in layouts) {
if (lookup.TryGetValue(item.Key, out var ourNode)) { if (lookup.TryGetValue(item.Key, out var ourNode)) {
ourNode.Value = item.Value; ourNode.Value = item.Value;
} else { } else {
ourNode = new Node<Quest>([], item.Key, item.Value); ourNode = new Node<IQuestInfo>([], item.Key, item.Value);
lookup[item.Key] = ourNode; lookup[item.Key] = ourNode;
allNodes[item.Key] = ourNode; allNodes[item.Key] = ourNode;
} }
@ -122,7 +122,7 @@ namespace QuestMap {
foreach (var prev in previous) { foreach (var prev in previous) {
if (!lookup.TryGetValue(prev.RowId, out var parentNode)) { if (!lookup.TryGetValue(prev.RowId, out var parentNode)) {
// create preliminary parent // create preliminary parent
parentNode = new Node<Quest>(prev.RowId); parentNode = new Node<IQuestInfo>(prev.RowId);
lookup[prev.RowId] = parentNode; lookup[prev.RowId] = parentNode;
allNodes[prev.RowId] = parentNode; allNodes[prev.RowId] = parentNode;
} }
@ -138,7 +138,8 @@ namespace QuestMap {
} }
internal static class NodeExt { internal static class NodeExt {
internal static Node<T>? Find<T>(this IEnumerable<Node<T>> nodes, uint id) { internal static Node<T>? Find<T>(this IEnumerable<Node<T>> nodes, uint id)
where T : IQuestInfo {
foreach (var node in nodes) { foreach (var node in nodes) {
var found = node.Find(id); var found = node.Find(id);
@ -152,7 +153,7 @@ namespace QuestMap {
internal static IEnumerable<Quest> PreviousQuests(this Quest quest) { internal static IEnumerable<Quest> PreviousQuests(this Quest quest) {
foreach (var previous in quest.PreviousQuest) { foreach (var previous in quest.PreviousQuest) {
if (previous != null && previous.Row != 0) { if (previous.RowId != 0) {
yield return previous.Value!; yield return previous.Value!;
} }
} }

View File

@ -11,12 +11,14 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Textures; using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using ImGuiNET; using ImGuiNET;
using Lumina.Data; using Lumina.Data;
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Lumina.Text.ReadOnly;
using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Geometry.Curves;
using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core.Layout;
@ -88,7 +90,7 @@ namespace QuestMap {
return false; return false;
} }
if (!this.Plugin.Config.ShowSeasonal && quest.Festival.Row != 0) { if (!this.Plugin.Config.ShowSeasonal && quest.Festival.RowId != 0) {
return false; return false;
} }
@ -113,7 +115,7 @@ namespace QuestMap {
return false; return false;
} }
if (items.All(item => item.ItemUICategory.Row != 81)) { if (items.All(item => item.ItemUICategory.RowId != 81)) {
return false; return false;
} }
} }
@ -154,7 +156,7 @@ namespace QuestMap {
var allItems = this.Plugin.Config.ItemVis != Visibility.Hidden; var allItems = this.Plugin.Config.ItemVis != Visibility.Hidden;
var anyItemVisible = allItems || this.Plugin.Config.MinionVis != Visibility.Hidden; var anyItemVisible = allItems || this.Plugin.Config.MinionVis != Visibility.Hidden;
if (anyItemVisible && this.Plugin.Quests.ItemRewards.TryGetValue(quest.RowId, out var items)) { if (anyItemVisible && this.Plugin.Quests.ItemRewards.TryGetValue(quest.RowId, out var items)) {
var toShow = items.Where(item => allItems || item.ItemUICategory.Row == 81); var toShow = items.Where(item => allItems || item.ItemUICategory.RowId == 81);
drawItems.AddRange(toShow.Select(item => (quest, true, $"{this.Convert(item.Name)}##item-{quest.RowId}-{item.RowId}"))); drawItems.AddRange(toShow.Select(item => (quest, true, $"{this.Convert(item.Name)}##item-{quest.RowId}-{item.RowId}")));
} }
@ -300,7 +302,7 @@ namespace QuestMap {
ImGui.PushStyleColor(ImGuiCol.Text, disabled); ImGui.PushStyleColor(ImGuiCol.Text, disabled);
} }
var ret = ImGui.Selectable(name, this.Quest == quest); var ret = ImGui.Selectable(name, this.Quest?.RowId == quest.RowId);
if (completed) { if (completed) {
ImGui.PopStyleColor(); ImGui.PopStyleColor();
@ -352,7 +354,7 @@ namespace QuestMap {
if (this._relayout && this.Quest != null) { if (this._relayout && this.Quest != null) {
this.Graph = null; this.Graph = null;
this.CancellationTokenSource?.Cancel(); this.CancellationTokenSource?.Cancel();
this.CancellationTokenSource = this.Plugin.Quests.StartGraphRecalculation(this.Quest); this.CancellationTokenSource = this.Plugin.Quests.StartGraphRecalculation(this.Quest.Value);
this._relayout = false; this._relayout = false;
} }
@ -363,14 +365,14 @@ namespace QuestMap {
var remove = 0u; var remove = 0u;
foreach (var id in this.InfoWindows) { foreach (var id in this.InfoWindows) {
var quest = this.Plugin.DataManager.GetExcelSheet<Quest>()!.GetRow(id); var quest = this.Plugin.DataManager.GetExcelSheet<Quest>()!.GetRowOrDefault(id);
if (quest == null) if (quest == null)
{ {
remove = id; remove = id;
continue; continue;
} }
if (this.DrawInfoWindow(quest)) { if (this.DrawInfoWindow(quest.Value)) {
remove = id; remove = id;
} }
} }
@ -390,7 +392,7 @@ namespace QuestMap {
var completed = QuestManager.IsQuestComplete(quest.RowId); var completed = QuestManager.IsQuestComplete(quest.RowId);
ImGui.TextUnformatted($"Level: {quest.ClassJobLevel0}"); ImGui.TextUnformatted($"Level: {quest.ClassJobLevel[0]}");
if (completed) { if (completed) {
ImGui.PushFont(UiBuilder.IconFont); ImGui.PushFont(UiBuilder.IconFont);
@ -416,10 +418,10 @@ namespace QuestMap {
} }
var rewards = new List<string>(); var rewards = new List<string>();
var paramGrow = this.Plugin.DataManager.GetExcelSheet<ParamGrow>()!.GetRow(quest.ClassJobLevel0); var paramGrow = this.Plugin.DataManager.GetExcelSheet<ParamGrow>()!.GetRowOrDefault(quest.ClassJobLevel[0]);
var xp = 0; var xp = 0;
if (paramGrow != null) { if (paramGrow != null) {
xp = quest.ExpFactor * paramGrow.ScaledQuestXP * paramGrow.QuestExpModifier / 100; xp = quest.ExpFactor * paramGrow.Value.ScaledQuestXP * paramGrow.Value.QuestExpModifier / 100;
} }
if (xp > 0) { if (xp > 0) {
@ -486,16 +488,16 @@ namespace QuestMap {
additionalRewards.Add((this.Convert(job.Name).ToString(), 62000 + job.RowId, 1)); additionalRewards.Add((this.Convert(job.Name).ToString(), 62000 + job.RowId, 1));
} }
for (var i = 0; i < quest.ItemCatalyst.Length; i++) { for (var i = 0; i < quest.ItemCatalyst.Count; i++) {
var catalyst = quest.ItemCatalyst[i]; var catalyst = quest.ItemCatalyst[i];
var amount = quest.ItemCountCatalyst[i]; var amount = quest.ItemCountCatalyst[i];
if (catalyst.Row != 0) { if (catalyst.RowId != 0) {
additionalRewards.Add((this.Convert(catalyst.Value!.Name), catalyst.Value.Icon, amount)); additionalRewards.Add((this.Convert(catalyst.Value!.Name), catalyst.Value.Icon, amount));
} }
} }
foreach (var generalAction in quest.GeneralActionReward.Where(row => row.Row != 0)) { foreach (var generalAction in quest.GeneralActionReward.Where(row => row.RowId != 0)) {
additionalRewards.Add((this.Convert(generalAction.Value!.Name), (uint) generalAction.Value.Icon, 1)); additionalRewards.Add((this.Convert(generalAction.Value!.Name), (uint) generalAction.Value.Icon, 1));
} }
@ -507,34 +509,34 @@ namespace QuestMap {
additionalRewards.Add((this.Convert(emote.Name), emote.Icon, 1)); additionalRewards.Add((this.Convert(emote.Name), emote.Icon, 1));
} }
if (quest.OtherReward.Row != 0) { if (quest.OtherReward.RowId != 0) {
additionalRewards.Add((this.Convert(quest.OtherReward.Value!.Name), quest.OtherReward.Value.Icon, 1)); additionalRewards.Add((this.Convert(quest.OtherReward.Value!.Name), quest.OtherReward.Value.Icon, 1));
} }
if (quest.ReputationReward > 0) { if (quest.ReputationReward > 0) {
var beastTribe = quest.BeastTribe.Value; var beastTribe = quest.BeastTribe.ValueNullable;
if (beastTribe != null) { if (beastTribe != null) {
additionalRewards.Add((this.Convert(beastTribe.NameRelation), beastTribe.Icon, quest.ReputationReward)); additionalRewards.Add((this.Convert(beastTribe.Value.NameRelation), beastTribe.Value.Icon, quest.ReputationReward));
} }
} }
if (quest.TomestoneReward > 0) { if (quest.TomestoneReward > 0) {
var tomestone = this.Plugin.DataManager.GetExcelSheet<TomestonesItem>()!.FirstOrDefault(row => row.Tomestones.Row == quest.TomestoneReward); var tomestone = this.Plugin.DataManager.GetExcelSheet<TomestonesItem>().Cast<TomestonesItem?>().FirstOrDefault(row => row.Value.Tomestones.RowId == quest.TomestoneReward);
var item = tomestone?.Item?.Value; var item = tomestone?.Item.ValueNullable;
if (item != null) { if (item != null) {
additionalRewards.Add((this.Convert(item.Name), item.Icon, quest.TomestoneCountReward)); additionalRewards.Add((this.Convert(item.Value.Name), item.Value.Icon, quest.TomestoneCountReward));
} }
} }
if (quest.ItemRewardType is 0 or 1 or 3 or 5) { if (quest.ItemRewardType is 0 or 1 or 3 or 5) {
DrawItemRewards( DrawItemRewards(
"Rewards", "Rewards",
quest.ItemReward quest.Reward
.Zip(quest.ItemCountReward, (id, qty) => (id, qty)) .Zip(quest.ItemCountReward, (id, qty) => (id: id.RowId, qty))
.Where(entry => entry.id != 0) .Where(entry => entry.id != 0)
.Select(entry => (item: this.Plugin.DataManager.GetExcelSheet<Item>()!.GetRow(entry.id), entry.qty)) .Select(entry => (item: this.Plugin.DataManager.GetExcelSheet<Item>()!.GetRowOrDefault(entry.id), entry.qty))
.Where(entry => entry.item != null) .Where(entry => entry.item != null)
.Select(entry => (this.Convert(entry.item!.Name), (uint) entry.item.Icon, entry.qty)) .Select(entry => (this.Convert(entry.item.Value.Name), (uint) entry.item.Value.Icon, entry.qty))
.Concat(additionalRewards) .Concat(additionalRewards)
); );
@ -542,10 +544,10 @@ namespace QuestMap {
"Optional rewards", "Optional rewards",
quest.OptionalItemReward quest.OptionalItemReward
.Zip(quest.OptionalItemCountReward, (row, qty) => (row, qty)) .Zip(quest.OptionalItemCountReward, (row, qty) => (row, qty))
.Where(entry => entry.row.Row != 0) .Where(entry => entry.row.RowId != 0)
.Select(entry => (item: entry.row.Value, entry.qty)) .Select(entry => (item: entry.row.ValueNullable, entry.qty))
.Where(entry => entry.item != null) .Where(entry => entry.item != null)
.Select(entry => (this.Convert(entry.item!.Name), (uint) entry.item.Icon, entry.qty)) .Select(entry => (this.Convert(entry.item.Value.Name), (uint) entry.item.Value.Icon, entry.qty))
); );
} }
@ -553,7 +555,7 @@ namespace QuestMap {
ImGui.TextUnformatted("Instances"); ImGui.TextUnformatted("Instances");
foreach (var instance in instances) { foreach (var instance in instances) {
var icon = instance.ContentType.Value?.Icon ?? 0; var icon = instance.ContentType.ValueNullable?.Icon ?? 0;
if (icon > 0) { if (icon > 0) {
var image = GetIcon(icon); var image = GetIcon(icon);
if (image != null) { if (image != null) {
@ -592,9 +594,9 @@ namespace QuestMap {
ClientLanguage.French => Language.French, ClientLanguage.French => Language.French,
_ => Language.English, _ => Language.English,
}; };
var path = $"quest/{id.ToString("00000")[..3]}/{quest.Id.RawString.ToLowerInvariant()}"; var path = $"quest/{id.ToString("00000")[..3]}/{quest.Id.ToString().ToLowerInvariant()}";
// FIXME: this is gross, but lumina caches incorrectly // FIXME: this is gross, but lumina caches incorrectly
this.Plugin.DataManager.Excel.RemoveSheetFromCache<QuestData>(); // this.Plugin.DataManager.Excel.RemoveSheetFromCache<QuestData>();
var sheet = this.Plugin.DataManager.Excel.GetType() var sheet = this.Plugin.DataManager.Excel.GetType()
.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? .GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
// ReSharper disable once ConstantConditionalAccessQualifier // ReSharper disable once ConstantConditionalAccessQualifier
@ -606,11 +608,11 @@ namespace QuestMap {
null, null,
]) as ExcelSheet<QuestData>; ]) as ExcelSheet<QuestData>;
// default to english if reflection failed // default to english if reflection failed
sheet ??= this.Plugin.DataManager.Excel.GetSheet<QuestData>(path); sheet ??= this.Plugin.DataManager.Excel.GetSheet<QuestData>(name: path);
var firstData = sheet?.GetRow(0); var firstData = sheet?.GetRow(0);
if (firstData != null) { if (firstData != null) {
ImGui.PushTextWrapPos(textWrap); ImGui.PushTextWrapPos(textWrap);
ImGui.TextUnformatted(this.Convert(firstData.Text).ToString()); ImGui.TextUnformatted(this.Convert(firstData.Value.Text).ToString());
ImGui.PopTextWrapPos(); ImGui.PopTextWrapPos();
} }
@ -622,24 +624,24 @@ namespace QuestMap {
} }
var mapLink = new MapLinkPayload( var mapLink = new MapLinkPayload(
level.Territory.Row, level.Value.Territory.RowId,
level.Map.Row, level.Value.Map.RowId,
(int) (level.X * 1_000f), (int) (level.Value.X * 1_000f),
(int) (level.Z * 1_000f) (int) (level.Value.Z * 1_000f)
); );
this.Plugin.GameGui.OpenMapWithMapLink(mapLink); this.Plugin.GameGui.OpenMapWithMapLink(mapLink);
} }
var issuer = this.Plugin.DataManager.GetExcelSheet<ENpcResident>()!.GetRow(quest.IssuerStart)?.Singular ?? "Unknown"; var issuer = this.Plugin.DataManager.GetExcelSheet<ENpcResident>()!.GetRowOrDefault(quest.IssuerStart.RowId)?.Singular ?? "Unknown";
var target = this.Plugin.DataManager.GetExcelSheet<ENpcResident>()!.GetRow(quest.TargetEnd)?.Singular ?? "Unknown"; var target = this.Plugin.DataManager.GetExcelSheet<ENpcResident>()!.GetRowOrDefault(quest.TargetEnd.RowId)?.Singular ?? "Unknown";
ImGui.TextUnformatted(issuer); ImGui.TextUnformatted(issuer.ToString());
ImGui.PushFont(UiBuilder.IconFont); ImGui.PushFont(UiBuilder.IconFont);
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(FontAwesomeIcon.ArrowRight.ToIconString()); ImGui.TextUnformatted(FontAwesomeIcon.ArrowRight.ToIconString());
ImGui.PopFont(); ImGui.PopFont();
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(target); ImGui.TextUnformatted(target.ToString());
ImGui.Separator(); ImGui.Separator();
@ -822,9 +824,10 @@ namespace QuestMap {
continue; continue;
} }
var quest = (Quest) node.UserData; var questInfo = (IQuestInfo)node.UserData;
var quest = questInfo.Quest;
var colour = quest.EventIconType.Row switch { var colour = quest.EventIconType.RowId switch {
1 => Colours.NormalQuest, // normal 1 => Colours.NormalQuest, // normal
3 => Colours.MsqQuest, // msq 3 => Colours.MsqQuest, // msq
8 => Colours.BlueQuest, // blue 8 => Colours.BlueQuest, // blue
@ -846,12 +849,12 @@ namespace QuestMap {
drawn.Add((start, end, quest.RowId)); drawn.Add((start, end, quest.RowId));
if (quest == this.Quest) { if (quest.RowId == this.Quest?.RowId) {
drawList.AddRect(start - Vector2.One, end + Vector2.One, Colours.Line, 5, ImDrawFlags.RoundCornersAll); drawList.AddRect(start - Vector2.One, end + Vector2.One, Colours.Line, 5, ImDrawFlags.RoundCornersAll);
} }
drawList.AddRectFilled(start, end, ImGui.GetColorU32(colour), 5, ImDrawFlags.RoundCornersAll); drawList.AddRectFilled(start, end, ImGui.GetColorU32(colour), 5, ImDrawFlags.RoundCornersAll);
drawList.AddText(start + TextOffset, textColour, this.Convert(quest.Name).ToString()); drawList.AddText(start + TextOffset, textColour, questInfo.Name);
} }
// HOW ABOUT DRAGGING THE VIEW? // HOW ABOUT DRAGGING THE VIEW?
@ -905,8 +908,8 @@ namespace QuestMap {
private static readonly byte[] NewLinePayload = [0x02, 0x10, 0x01, 0x03]; private static readonly byte[] NewLinePayload = [0x02, 0x10, 0x01, 0x03];
private SeString Convert(Lumina.Text.SeString lumina) { private SeString Convert(ReadOnlySeString lumina) {
var se = (SeString) lumina; var se = lumina.ToDalamudString();
for (var i = 0; i < se.Payloads.Count; i++) { for (var i = 0; i < se.Payloads.Count; i++) {
switch (se.Payloads[i].Type) { switch (se.Payloads[i].Type) {
case PayloadType.Unknown: case PayloadType.Unknown:

View File

@ -1,24 +1,20 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Lumina;
using Lumina.Data;
using Lumina.Excel; using Lumina.Excel;
using Lumina.Text; using Lumina.Text.ReadOnly;
namespace QuestMap { namespace QuestMap {
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal class QuestData : ExcelRow { [Sheet("")]
#pragma warning disable 8618 public readonly struct QuestData(ExcelPage page, uint offset, uint row) : IExcelRow<QuestData>
public string Id { get; set; } {
public SeString Text { get; set; } public uint RowId => row;
#pragma warning restore 8618
public override void PopulateData(RowParser parser, GameData gameData, Language language) { public ReadOnlySeString Id => page.ReadString(offset, offset);
base.PopulateData(parser, gameData, language); public ReadOnlySeString Text => page.ReadString(offset + 4, offset);
this.Id = parser.ReadColumn<string>(0)!; static QuestData IExcelRow<QuestData>.Create(ExcelPage page, uint offset, uint row) =>
this.Text = parser.ReadColumn<SeString>(1)!; new(page, offset, row);
}
} }
} }

View File

@ -49,7 +49,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutomaticGraphLayout" Version="1.1.12"/> <PackageReference Include="AutomaticGraphLayout" Version="1.1.12"/>
<PackageReference Include="DalamudPackager" Version="2.1.13" /> <PackageReference Include="DalamudPackager" Version="11.0.0" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" /> <PackageReference Include="System.Threading.Channels" Version="8.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -4,21 +4,21 @@ using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Channels; using System.Threading.Channels;
using FFXIVClientStructs.FFXIV.Component.Excel;
using ImGuiNET; using ImGuiNET;
using Lumina.Excel; using Lumina.Excel.Sheets;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Geometry.Curves;
using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core.Layout;
using Microsoft.Msagl.Layout.Layered; using Microsoft.Msagl.Layout.Layered;
using Microsoft.Msagl.Miscellaneous; using Microsoft.Msagl.Miscellaneous;
using Action = Lumina.Excel.GeneratedSheets.Action; using Action = Lumina.Excel.Sheets.Action;
namespace QuestMap { namespace QuestMap {
internal class Quests { internal class Quests {
private Plugin Plugin { get; } private Plugin Plugin { get; }
private Dictionary<uint, Node<Quest>> AllNodes { get; } private Dictionary<uint, Node<IQuestInfo>> AllNodes { get; }
internal IReadOnlyDictionary<uint, List<Item>> ItemRewards { get; } internal IReadOnlyDictionary<uint, List<Item>> ItemRewards { get; }
internal IReadOnlyDictionary<uint, Emote> EmoteRewards { get; } internal IReadOnlyDictionary<uint, Emote> EmoteRewards { get; }
internal IReadOnlyDictionary<uint, Action> ActionRewards { get; } internal IReadOnlyDictionary<uint, Action> ActionRewards { get; }
@ -40,20 +40,20 @@ namespace QuestMap {
var jobRewards = new Dictionary<uint, ClassJob>(); var jobRewards = new Dictionary<uint, ClassJob>();
var linkedInstances = new HashSet<ContentFinderCondition>(); var linkedInstances = new HashSet<ContentFinderCondition>();
var allQuests = new Dictionary<uint, Quest>(); var allQuests = new Dictionary<uint, IQuestInfo>();
foreach (var quest in this.Plugin.DataManager.GetExcelSheet<Quest>()!) { foreach (var quest in this.Plugin.DataManager.GetExcelSheet<Quest>()!) {
if (quest.Name.RawString.Length == 0 || quest.RowId == 65536) { if (quest.Name.ToString().Length == 0 || quest.RowId == 65536) {
continue; continue;
} }
allQuests[quest.RowId] = quest; allQuests[quest.RowId] = new SheetQuestInfo(quest);
if (quest.EmoteReward.Row != 0) { if (quest.EmoteReward.RowId != 0) {
emoteRewards[quest.RowId] = quest.EmoteReward.Value!; emoteRewards[quest.RowId] = quest.EmoteReward.Value!;
} }
foreach (var row in quest.ItemReward.Where(item => item != 0)) { foreach (var row in quest.Reward.Where(item => item.RowId != 0)) {
var item = this.Plugin.DataManager.GetExcelSheet<Item>()!.GetRow(row); var item = row.GetValueOrDefault<Item>();
if (item == null) { if (item == null) {
continue; continue;
} }
@ -66,10 +66,10 @@ namespace QuestMap {
itemRewards[quest.RowId] = rewards; itemRewards[quest.RowId] = rewards;
} }
rewards.Add(item); rewards.Add(item.Value);
} }
foreach (var row in quest.OptionalItemReward.Where(item => item.Row != 0)) { foreach (var row in quest.OptionalItemReward.Where(item => item.RowId != 0)) {
var item = row.Value; var item = row.Value;
List<Item> rewards; List<Item> rewards;
@ -83,7 +83,7 @@ namespace QuestMap {
rewards.Add(item!); rewards.Add(item!);
} }
if (quest.ActionReward.Row != 0) { if (quest.ActionReward.RowId != 0) {
actionRewards[quest.RowId] = quest.ActionReward.Value!; actionRewards[quest.RowId] = quest.ActionReward.Value!;
} }
@ -95,13 +95,13 @@ namespace QuestMap {
} }
} }
if (quest.BeastTribe.Row != 0 && !quest.IsRepeatable && quest.BeastReputationRank.Row == 0) { if (quest.BeastTribe.RowId != 0 && !quest.IsRepeatable && quest.BeastReputationRank.RowId == 0) {
beastRewards[quest.RowId] = quest.BeastTribe.Value!; beastRewards[quest.RowId] = quest.BeastTribe.Value!;
} }
var jobReward = this.JobUnlocks(quest); var jobReward = this.JobUnlocks(quest);
if (jobReward != null) { if (jobReward != null) {
jobRewards[quest.RowId] = jobReward; jobRewards[quest.RowId] = jobReward.Value;
} }
} }
@ -112,13 +112,13 @@ namespace QuestMap {
this.BeastRewards = beastRewards; this.BeastRewards = beastRewards;
this.JobRewards = jobRewards; this.JobRewards = jobRewards;
var (_, nodes) = Node<Quest>.BuildTree(allQuests); var (_, nodes) = Node<IQuestInfo>.BuildTree(allQuests);
this.AllNodes = nodes; this.AllNodes = nodes;
} }
private static readonly Vector2 TextOffset = new(5, 2); private static readonly Vector2 TextOffset = new(5, 2);
internal CancellationTokenSource StartGraphRecalculation(ExcelRow quest) { internal CancellationTokenSource StartGraphRecalculation(Quest quest) {
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
new Thread(async () => { new Thread(async () => {
var info = this.GetGraphInfo(quest, cts.Token); var info = this.GetGraphInfo(quest, cts.Token);
@ -130,7 +130,7 @@ namespace QuestMap {
return cts; return cts;
} }
private GraphInfo? GetGraphInfo(ExcelRow quest, CancellationToken cancel) { private GraphInfo? GetGraphInfo(Quest quest, CancellationToken cancel) {
if (!this.AllNodes.TryGetValue(quest.RowId, out var first)) { if (!this.AllNodes.TryGetValue(quest.RowId, out var first)) {
return null; return null;
} }
@ -139,7 +139,7 @@ namespace QuestMap {
var links = new List<(uint, uint)>(); var links = new List<(uint, uint)>();
var g = new GeometryGraph(); var g = new GeometryGraph();
void AddNode(Node<Quest> node) { void AddNode(Node<IQuestInfo> node) {
if (msaglNodes.ContainsKey(node.Id)) { if (msaglNodes.ContainsKey(node.Id)) {
return; return;
} }
@ -149,7 +149,7 @@ namespace QuestMap {
g.Nodes.Add(graphNode); g.Nodes.Add(graphNode);
msaglNodes[node.Id] = graphNode; msaglNodes[node.Id] = graphNode;
IEnumerable<Node<Quest>> parents; IEnumerable<Node<IQuestInfo>> parents;
if (this.Plugin.Config.ShowRedundantArrows) { if (this.Plugin.Config.ShowRedundantArrows) {
parents = node.Parents; parents = node.Parents;
} else { } else {
@ -214,7 +214,7 @@ namespace QuestMap {
: new GraphInfo(g, centre); : new GraphInfo(g, centre);
} }
private Quest? ConsolidateMsq(Quest quest) { private IQuestInfo? ConsolidateMsq(IQuestInfo quest) {
if (!this.Plugin.Config.CondenseMsq) { if (!this.Plugin.Config.CondenseMsq) {
return null; return null;
} }
@ -256,6 +256,7 @@ namespace QuestMap {
70286 => "Growing Light (6.5)", 70286 => "Growing Light (6.5)",
70289 => "The Coming Dawn (6.55)", 70289 => "The Coming Dawn (6.55)",
70495 => "Dawntrail (7.0)", 70495 => "Dawntrail (7.0)",
70786 => "Crossroads (7.1)",
_ => null, _ => null,
}; };
@ -263,13 +264,7 @@ namespace QuestMap {
return null; return null;
} }
var newQuest = new Quest(); return new ConsolidatedQuestInfo(quest, $"{name} MSQ");
foreach (var property in newQuest.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) {
property.SetValue(newQuest, property.GetValue(quest));
}
newQuest.Name = new Lumina.Text.SeString($"{name} MSQ");
return newQuest;
} }
private HashSet<ContentFinderCondition> InstanceUnlocks(Quest quest, ICollection<ContentFinderCondition> others) { private HashSet<ContentFinderCondition> InstanceUnlocks(Quest quest, ICollection<ContentFinderCondition> others) {
@ -279,52 +274,50 @@ namespace QuestMap {
var unlocks = new HashSet<ContentFinderCondition>(); var unlocks = new HashSet<ContentFinderCondition>();
if (quest.InstanceContentUnlock.Row != 0) { if (quest.InstanceContentUnlock.RowId != 0) {
var cfc = this.Plugin.DataManager.GetExcelSheet<ContentFinderCondition>()!.FirstOrDefault(cfc => cfc.Content == quest.InstanceContentUnlock.Row && cfc.ContentLinkType == 1); var cfc = this.Plugin.DataManager.GetExcelSheet<ContentFinderCondition>().Cast<ContentFinderCondition?>().FirstOrDefault(cfc => cfc.Value.Content.RowId == quest.InstanceContentUnlock.RowId && cfc.Value.ContentLinkType == 1);
if (cfc != null && cfc.UnlockQuest.Row == 0) { if (cfc != null && cfc.Value.UnlockQuest.RowId == 0) {
unlocks.Add(cfc); unlocks.Add(cfc.Value);
} }
} }
var instanceRefs = quest.ScriptInstruction var instanceRefs = quest.QuestParams
.Zip(quest.ScriptArg, (ins, arg) => (ins, arg)) .Where(x => x.ScriptInstruction.ToString().StartsWith("INSTANCEDUNGEON"));
.Where(x => x.ins.RawString.StartsWith("INSTANCEDUNGEON"));
foreach (var reference in instanceRefs) { foreach (var reference in instanceRefs) {
var key = reference.arg; var key = reference.ScriptArg;
// var content = this.Plugin.Interface.Data.GetExcelSheet<InstanceContent>().GetRow(key); // var content = this.Plugin.Interface.Data.GetExcelSheet<InstanceContent>().GetRow(key);
var cfc = this.Plugin.DataManager.GetExcelSheet<ContentFinderCondition>()!.FirstOrDefault(cfc => cfc.Content == key && cfc.ContentLinkType == 1); var cfc = this.Plugin.DataManager.GetExcelSheet<ContentFinderCondition>().Cast<ContentFinderCondition?>().FirstOrDefault(cfc => cfc.Value.Content.RowId == key && cfc.Value.ContentLinkType == 1);
if (cfc == null || cfc.UnlockQuest.Row != 0 || others.Contains(cfc)) { if (cfc == null || cfc.Value.UnlockQuest.RowId != 0 || others.Contains(cfc.Value)) {
continue; continue;
} }
if (!quest.ScriptInstruction.Any(i => i.RawString == "UNLOCK_ADD_NEW_CONTENT_TO_CF" || i.RawString.StartsWith("UNLOCK_DUNGEON"))) { if (!quest.QuestParams.Any(i => i.ScriptInstruction.ToString() == "UNLOCK_ADD_NEW_CONTENT_TO_CF" || i.ScriptInstruction.ToString().StartsWith("UNLOCK_DUNGEON"))) {
if (quest.ScriptInstruction.Any(i => i.RawString.StartsWith("LOC_ITEM"))) { if (quest.QuestParams.Any(i => i.ScriptInstruction.ToString().StartsWith("LOC_ITEM"))) {
continue; continue;
} }
} }
unlocks.Add(cfc); unlocks.Add(cfc.Value);
} }
return unlocks; return unlocks;
} }
private ClassJob? JobUnlocks(Quest quest) { private ClassJob? JobUnlocks(Quest quest) {
if (quest.ClassJobUnlock.Row > 0) { if (quest.ClassJobUnlock.RowId > 0) {
return quest.ClassJobUnlock.Value; return quest.ClassJobUnlock.Value;
} }
if (quest.ScriptInstruction.All(ins => ins.RawString.StartsWith("UNLOCK_IMAGE_CLASS"))) { if (quest.QuestParams.All(ins => ins.ScriptInstruction.ToString().StartsWith("UNLOCK_IMAGE_CLASS"))) {
return null; return null;
} }
var jobId = quest.ScriptInstruction var jobId = quest.QuestParams.Cast<Quest.QuestParamsStruct?>()
.Zip(quest.ScriptArg, (ins, arg) => (ins, arg)) .FirstOrDefault(entry => entry.Value.ScriptInstruction.ToString().StartsWith("CLASSJOB"))
.FirstOrDefault(entry => entry.ins.RawString.StartsWith("CLASSJOB")) ?.ScriptArg ?? 0;
.arg;
return jobId == 0 return jobId == 0
? null ? null
: this.Plugin.DataManager.GetExcelSheet<ClassJob>()!.GetRow(jobId); : this.Plugin.DataManager.GetExcelSheet<ClassJob>()!.GetRow(jobId);

View File

@ -10,9 +10,9 @@
}, },
"DalamudPackager": { "DalamudPackager": {
"type": "Direct", "type": "Direct",
"requested": "[2.1.13, )", "requested": "[11.0.0, )",
"resolved": "2.1.13", "resolved": "11.0.0",
"contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" "contentHash": "bjT7XUlhIJSmsE/O76b7weUX+evvGQctbQB8aKXt94o+oPWxHpCepxAGMs7Thow3AzCyqWs7cOpp9/2wcgRRQA=="
}, },
"System.Threading.Channels": { "System.Threading.Channels": {
"type": "Direct", "type": "Direct",