1511 lines
44 KiB
C#
1511 lines
44 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|
using Dalamud.Interface.Textures.TextureWraps;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Plugin.Services;
|
|
using Dalamud.Utility;
|
|
using LLib;
|
|
using Lumina.Excel;
|
|
using Lumina.Excel.Sheets;
|
|
using Questionable.Controller;
|
|
using Questionable.Data;
|
|
using Questionable.Functions;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
using Questionable.Windows.QuestComponents;
|
|
|
|
namespace Questionable.Windows.JournalComponents;
|
|
|
|
internal sealed class QuestChainComponent
|
|
{
|
|
private readonly record struct QuestStatusEntry(Vector4 Color, bool IsCompleted);
|
|
|
|
private sealed class CachedGraphLayout
|
|
{
|
|
public required List<List<QuestId>> Layers { get; init; }
|
|
|
|
public required Dictionary<QuestId, float> NodeWidths { get; init; }
|
|
|
|
public required float[] LayerWidths { get; init; }
|
|
|
|
public required Dictionary<QuestId, string> TruncatedLabels { get; init; }
|
|
}
|
|
|
|
private readonly record struct NodeStatusEntry(Vector4 BgColor, bool IsDone);
|
|
|
|
private static readonly string[] ExpansionNames = new string[7] { "All", "A Realm Reborn", "Heavensward", "Stormblood", "Shadowbringers", "Endwalker", "Dawntrail" };
|
|
|
|
private readonly QuestChainBuilder _questChainBuilder;
|
|
|
|
private readonly QuestData _questData;
|
|
|
|
private readonly QuestFunctions _questFunctions;
|
|
|
|
private readonly UiUtils _uiUtils;
|
|
|
|
private readonly ITextureProvider _textureProvider;
|
|
|
|
private readonly IDataManager _dataManager;
|
|
|
|
private readonly QuestMapComponent _questMapComponent;
|
|
|
|
private readonly QuestRegistry _questRegistry;
|
|
|
|
private readonly IGameGui _gameGui;
|
|
|
|
private readonly QuestTooltipComponent _questTooltipComponent;
|
|
|
|
private string _searchText = string.Empty;
|
|
|
|
private int _selectedExpansion = -1;
|
|
|
|
private bool _showUnobtainable;
|
|
|
|
private ElementId? _selectedQuestId;
|
|
|
|
private List<IQuestInfo> _filteredQuests = new List<IQuestInfo>();
|
|
|
|
private bool _filterDirty = true;
|
|
|
|
private string _previousSearchText = string.Empty;
|
|
|
|
private int _previousExpansion = -1;
|
|
|
|
private ElementId? _previousChainQuestId;
|
|
|
|
private bool _scrollToSelected;
|
|
|
|
private bool _showDetail;
|
|
|
|
private bool _scrollToSelectedInList;
|
|
|
|
private readonly Dictionary<ElementId, QuestStatusEntry> _questStatusCache = new Dictionary<ElementId, QuestStatusEntry>();
|
|
|
|
private long _statusCacheRefreshedAt;
|
|
|
|
private const long StatusCacheLifetimeMs = 3000L;
|
|
|
|
private CachedGraphLayout? _cachedLayout;
|
|
|
|
private QuestId? _cachedLayoutQuestId;
|
|
|
|
private QuestId? _cachedDescriptionQuestId;
|
|
|
|
private string? _cachedDescription;
|
|
|
|
private readonly Dictionary<QuestId, NodeStatusEntry> _nodeStatusCache = new Dictionary<QuestId, NodeStatusEntry>();
|
|
|
|
private long _nodeStatusRefreshedAt;
|
|
|
|
private readonly List<ElementId> _addableQuestIds = new List<ElementId>();
|
|
|
|
private ElementId? _addableCachedForQuestId;
|
|
|
|
private long _addableRefreshedAt;
|
|
|
|
private Vector2 _graphOffset = Vector2.Zero;
|
|
|
|
private bool _isDragging;
|
|
|
|
private Vector2 _dragStart;
|
|
|
|
private Vector2 _dragOrigin;
|
|
|
|
private bool _dragMoved;
|
|
|
|
private QuestId? _contextMenuNodeId;
|
|
|
|
private float _graphZoom = 1f;
|
|
|
|
private const float GraphMinZoom = 0.25f;
|
|
|
|
private const float GraphMaxZoom = 2f;
|
|
|
|
private const float GraphZoomSpeed = 0.1f;
|
|
|
|
private static readonly Vector4 GraphBackgroundColor = new Vector4(0.06f, 0.05f, 0.1f, 1f);
|
|
|
|
private static readonly Vector4 NodeFillActive = Vector4.Lerp(GraphBackgroundColor, UiThemeUtils.StatusActive, 0.48f);
|
|
|
|
private static readonly Vector4 NodeFillComplete = Vector4.Lerp(GraphBackgroundColor, UiThemeUtils.StatusComplete, 0.42f);
|
|
|
|
private static readonly Vector4 NodeFillUnobtainable = Vector4.Lerp(GraphBackgroundColor, UiThemeUtils.StatusUnobtainable, 0.36f);
|
|
|
|
private static readonly Vector4 NodeFillAvailable = Vector4.Lerp(GraphBackgroundColor, UiThemeUtils.StatusAvailable, 0.22f);
|
|
|
|
private static readonly Vector4 NodeFillLocked = Vector4.Lerp(GraphBackgroundColor, UiThemeUtils.StatusLocked, 0.45f);
|
|
|
|
public Action<string>? SelectTabAction { get; set; }
|
|
|
|
public QuestJournalUtils? QuestJournalUtils { get; set; }
|
|
|
|
public QuestChainComponent(QuestChainBuilder questChainBuilder, QuestData questData, QuestFunctions questFunctions, UiUtils uiUtils, ITextureProvider textureProvider, IDataManager dataManager, QuestMapComponent questMapComponent, QuestRegistry questRegistry, IGameGui gameGui, QuestTooltipComponent questTooltipComponent)
|
|
{
|
|
_questChainBuilder = questChainBuilder;
|
|
_questData = questData;
|
|
_questFunctions = questFunctions;
|
|
_uiUtils = uiUtils;
|
|
_textureProvider = textureProvider;
|
|
_dataManager = dataManager;
|
|
_questMapComponent = questMapComponent;
|
|
_questRegistry = questRegistry;
|
|
_gameGui = gameGui;
|
|
_questTooltipComponent = questTooltipComponent;
|
|
}
|
|
|
|
public void DrawQuestChain()
|
|
{
|
|
if (_searchText != _previousSearchText || _selectedExpansion != _previousExpansion)
|
|
{
|
|
_filterDirty = true;
|
|
_previousSearchText = _searchText;
|
|
_previousExpansion = _selectedExpansion;
|
|
}
|
|
if (_filterDirty)
|
|
{
|
|
RebuildFilteredQuests();
|
|
_filterDirty = false;
|
|
}
|
|
DrawSearchBar();
|
|
ImGui.Separator();
|
|
Vector2 contentRegionAvail = ImGui.GetContentRegionAvail();
|
|
float num = MathF.Max(contentRegionAvail.X * 0.25f, 180f);
|
|
float width = contentRegionAvail.X - num - ImGui.GetStyle().ItemSpacing.X;
|
|
if (_showDetail && _selectedQuestId != null)
|
|
{
|
|
DrawDetailPanel(num, contentRegionAvail.Y);
|
|
}
|
|
else
|
|
{
|
|
DrawQuestList(num, contentRegionAvail.Y);
|
|
}
|
|
ImGui.SameLine();
|
|
DrawChainPanel(width, contentRegionAvail.Y);
|
|
}
|
|
|
|
public void SelectQuest(ElementId questId)
|
|
{
|
|
_selectedQuestId = questId;
|
|
_filterDirty = true;
|
|
_scrollToSelected = true;
|
|
}
|
|
|
|
public void MarkDirty()
|
|
{
|
|
_filterDirty = true;
|
|
_cachedLayout = null;
|
|
_questStatusCache.Clear();
|
|
}
|
|
|
|
private void DrawSearchBar()
|
|
{
|
|
UiThemeUtils.SearchInput("##ChainSearch", ref _searchText, -280f * ImGui.GetIO().FontGlobalScale, "Search quests...");
|
|
ImGui.SameLine();
|
|
int currentItem = _selectedExpansion + 1;
|
|
ImGui.SetNextItemWidth(140f * ImGui.GetIO().FontGlobalScale);
|
|
if (UiThemeUtils.ThemedCombo("##ChainExpansion", ref currentItem, ExpansionNames, ExpansionNames.Length))
|
|
{
|
|
_selectedExpansion = currentItem - 1;
|
|
}
|
|
ImGui.SameLine();
|
|
if (UiThemeUtils.WrappedCheckbox("Unobtainable", ref _showUnobtainable))
|
|
{
|
|
_filterDirty = true;
|
|
}
|
|
}
|
|
|
|
private void RebuildFilteredQuests()
|
|
{
|
|
_filteredQuests = (from kvp in _questData.AllQuests
|
|
where kvp.Key is QuestId
|
|
select kvp.Value into q
|
|
where !string.IsNullOrEmpty(q.Name)
|
|
where q.Expansion != (EExpansionVersion)255
|
|
where _showUnobtainable || !_questFunctions.IsQuestUnobtainable(q.QuestId)
|
|
select q).Where(delegate(IQuestInfo q)
|
|
{
|
|
if (_selectedExpansion >= 0 && (uint)q.Expansion != (byte)_selectedExpansion)
|
|
{
|
|
return false;
|
|
}
|
|
return (string.IsNullOrEmpty(_searchText) || q.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase)) ? true : false;
|
|
}).OrderBy<IQuestInfo, string>((IQuestInfo q) => q.Name, StringComparer.OrdinalIgnoreCase).ToList();
|
|
}
|
|
|
|
private QuestStatusEntry GetCachedQuestStatus(ElementId questId)
|
|
{
|
|
long tickCount = Environment.TickCount64;
|
|
if (tickCount - _statusCacheRefreshedAt > 3000)
|
|
{
|
|
_questStatusCache.Clear();
|
|
_statusCacheRefreshedAt = tickCount;
|
|
}
|
|
if (!_questStatusCache.TryGetValue(questId, out var value))
|
|
{
|
|
Vector4 item = _uiUtils.GetQuestStyle(questId).Color;
|
|
bool isCompleted = _questFunctions.IsQuestAcceptedOrComplete(questId);
|
|
value = new QuestStatusEntry(item, isCompleted);
|
|
_questStatusCache[questId] = value;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private unsafe void DrawQuestList(float width, float height)
|
|
{
|
|
using ImRaii.ChildDisposable childDisposable = ImRaii.Child("QuestChainList", new Vector2(width, height), border: true);
|
|
if (!childDisposable)
|
|
{
|
|
return;
|
|
}
|
|
if (_filteredQuests.Count == 0)
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, "No quests match your search.");
|
|
return;
|
|
}
|
|
float textLineHeightWithSpacing = ImGui.GetTextLineHeightWithSpacing();
|
|
if (_scrollToSelectedInList && _selectedQuestId != null)
|
|
{
|
|
int num = _filteredQuests.FindIndex((IQuestInfo q) => q.QuestId == _selectedQuestId);
|
|
if (num >= 0)
|
|
{
|
|
float y = (float)num * textLineHeightWithSpacing - ImGui.GetWindowHeight() * 0.5f;
|
|
ImGui.SetScrollY(MathF.Max(0f, y));
|
|
}
|
|
_scrollToSelectedInList = false;
|
|
}
|
|
ImGuiListClipper* intPtr = ImGuiNative.ImGuiListClipper();
|
|
ImGuiNative.Begin(intPtr, _filteredQuests.Count, textLineHeightWithSpacing);
|
|
ImGuiListClipperPtr imGuiListClipperPtr = intPtr;
|
|
try
|
|
{
|
|
while (imGuiListClipperPtr.Step())
|
|
{
|
|
for (int num2 = imGuiListClipperPtr.DisplayStart; num2 < imGuiListClipperPtr.DisplayEnd; num2++)
|
|
{
|
|
IQuestInfo questInfo = _filteredQuests[num2];
|
|
UiThemeUtils.DrawAltRowBackground(num2);
|
|
bool selected = _selectedQuestId != null && _selectedQuestId == questInfo.QuestId;
|
|
QuestStatusEntry cachedQuestStatus = GetCachedQuestStatus(questInfo.QuestId);
|
|
using (cachedQuestStatus.IsCompleted ? ImRaii.PushColor(ImGuiCol.Text, UiThemeUtils.DimTextColor) : null)
|
|
{
|
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 14f);
|
|
if (ImGui.Selectable(UiThemeUtils.CleanSeString(questInfo.Name), selected, ImGuiSelectableFlags.AllowDoubleClick))
|
|
{
|
|
_selectedQuestId = questInfo.QuestId;
|
|
if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left))
|
|
{
|
|
_showDetail = true;
|
|
}
|
|
}
|
|
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
|
|
Vector2 itemRectMin = ImGui.GetItemRectMin();
|
|
Vector2 itemRectMax = ImGui.GetItemRectMax();
|
|
float y2 = (itemRectMin.Y + itemRectMax.Y) * 0.5f;
|
|
float x = itemRectMin.X - 14f + 5f;
|
|
windowDrawList.AddCircleFilled(new Vector2(x, y2), 3f, ImGui.ColorConvertFloat4ToU32(cachedQuestStatus.Color), 12);
|
|
Questionable.Model.Quest quest = null;
|
|
_questRegistry.TryGetQuest(questInfo.QuestId, out quest);
|
|
QuestJournalUtils?.ShowContextMenu(questInfo, quest, "QuestChainComponent");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
imGuiListClipperPtr.Destroy();
|
|
}
|
|
}
|
|
|
|
private void DrawChainPanel(float width, float height)
|
|
{
|
|
using ImRaii.ChildDisposable childDisposable = ImRaii.Child("QuestChainView", new Vector2(width, height), border: true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse);
|
|
if (!childDisposable)
|
|
{
|
|
return;
|
|
}
|
|
if (_selectedQuestId == null)
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, "Select a quest to view its chain");
|
|
return;
|
|
}
|
|
if (!(_selectedQuestId is QuestId questId))
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, "Chain view is only available for quests.");
|
|
return;
|
|
}
|
|
if (!_questData.TryGetQuestInfo(_selectedQuestId, out IQuestInfo _))
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.StatusLocked, "Quest info not found.");
|
|
return;
|
|
}
|
|
if (_previousChainQuestId != _selectedQuestId)
|
|
{
|
|
_previousChainQuestId = _selectedQuestId;
|
|
_scrollToSelected = true;
|
|
}
|
|
QuestChainGraph chainGraph = _questChainBuilder.GetChainGraph(questId);
|
|
if (chainGraph.LayerOrder.Count == 0)
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, "No chain data available.");
|
|
return;
|
|
}
|
|
if (UiThemeUtils.PillButton("Reset View", selected: false))
|
|
{
|
|
_scrollToSelected = true;
|
|
_graphZoom = 1f;
|
|
}
|
|
ImGui.SameLine();
|
|
ImU8String text = new ImU8String(1, 1);
|
|
text.AppendFormatted((int)(_graphZoom * 100f));
|
|
text.AppendLiteral("%");
|
|
ImGui.TextColored(in UiThemeUtils.MutedTextColor, text);
|
|
DrawChainGraph(chainGraph, width);
|
|
}
|
|
|
|
private void DrawDetailPanel(float width, float height)
|
|
{
|
|
using ImRaii.ChildDisposable childDisposable = ImRaii.Child("QuestDetailPanel", new Vector2(width, height), border: true);
|
|
if (!childDisposable)
|
|
{
|
|
return;
|
|
}
|
|
if (!(_selectedQuestId is QuestId questId) || !_questData.TryGetQuestInfo(_selectedQuestId, out IQuestInfo questInfo))
|
|
{
|
|
_showDetail = false;
|
|
return;
|
|
}
|
|
if (UiThemeUtils.PillButton("← Back to list", selected: false))
|
|
{
|
|
_showDetail = false;
|
|
_scrollToSelectedInList = true;
|
|
return;
|
|
}
|
|
ImGui.Separator();
|
|
UiThemeUtils.WrappedTextColored(UiThemeUtils.AccentColor, UiThemeUtils.CleanSeString(questInfo.Name));
|
|
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X);
|
|
try
|
|
{
|
|
Lumina.Excel.Sheets.Quest? rowOrDefault = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>().GetRowOrDefault((uint)(questId.Value + 65536));
|
|
ImU8String text = new ImU8String(7, 1);
|
|
text.AppendLiteral("Level: ");
|
|
text.AppendFormatted(questInfo.Level);
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, text);
|
|
if (rowOrDefault.HasValue && rowOrDefault.Value.Icon != 0)
|
|
{
|
|
DrawQuestBanner(rowOrDefault.Value.Icon);
|
|
}
|
|
if (rowOrDefault.HasValue)
|
|
{
|
|
ushort expFactor = rowOrDefault.Value.ExpFactor;
|
|
uint gilReward = rowOrDefault.Value.GilReward;
|
|
if (expFactor > 0)
|
|
{
|
|
ImU8String text2 = new ImU8String(5, 1);
|
|
text2.AppendLiteral("Exp: ");
|
|
text2.AppendFormatted(expFactor, "N0");
|
|
ImGui.TextUnformatted(text2);
|
|
}
|
|
if (gilReward != 0)
|
|
{
|
|
if (expFactor > 0)
|
|
{
|
|
ImGui.SameLine(0f, 20f);
|
|
}
|
|
ImU8String text3 = new ImU8String(5, 1);
|
|
text3.AppendLiteral("Gil: ");
|
|
text3.AppendFormatted(gilReward, "N0");
|
|
ImGui.TextUnformatted(text3);
|
|
}
|
|
DrawRewards(rowOrDefault.Value);
|
|
}
|
|
DrawQuestDescription(questId, rowOrDefault);
|
|
DrawNpcLine(rowOrDefault, questInfo);
|
|
ImU8String text4 = new ImU8String(4, 1);
|
|
text4.AppendLiteral("ID: ");
|
|
text4.AppendFormatted(questId.Value);
|
|
ImGui.TextColored(in UiThemeUtils.MutedTextColor, text4);
|
|
ImGui.Spacing();
|
|
ushort num = 0;
|
|
Vector3 vector = Vector3.Zero;
|
|
if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest))
|
|
{
|
|
QuestSequence questSequence = quest.FindSequence(0);
|
|
if (questSequence != null)
|
|
{
|
|
List<QuestStep> steps = questSequence.Steps;
|
|
if (steps != null && steps.Count > 0)
|
|
{
|
|
num = questSequence.Steps[0].TerritoryId;
|
|
Vector3? position = questSequence.Steps[0].Position;
|
|
if (position.HasValue)
|
|
{
|
|
Vector3 valueOrDefault = position.GetValueOrDefault();
|
|
vector = valueOrDefault;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (num == 0 && rowOrDefault.HasValue)
|
|
{
|
|
try
|
|
{
|
|
Level? valueNullable = rowOrDefault.Value.IssuerLocation.ValueNullable;
|
|
if (valueNullable.HasValue)
|
|
{
|
|
num = (ushort)valueNullable.Value.Territory.RowId;
|
|
vector = new Vector3(valueNullable.Value.X, valueNullable.Value.Y, valueNullable.Value.Z);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
if (num != 0 && vector != Vector3.Zero && UiThemeUtils.PillButton("Open on Map", selected: false))
|
|
{
|
|
TerritoryType? rowOrDefault2 = _dataManager.GetExcelSheet<TerritoryType>().GetRowOrDefault(num);
|
|
if (rowOrDefault2.HasValue)
|
|
{
|
|
Map? valueNullable2 = rowOrDefault2.Value.Map.ValueNullable;
|
|
if (valueNullable2.HasValue)
|
|
{
|
|
Vector2 vector2 = MapUtil.WorldToMap(new Vector2(vector.X, vector.Z), valueNullable2.Value);
|
|
MapLinkPayload mapLink = new MapLinkPayload(num, valueNullable2.Value.RowId, vector2.X, vector2.Y);
|
|
_gameGui.OpenMapWithMapLink(mapLink);
|
|
}
|
|
}
|
|
}
|
|
if (num != 0 && UiThemeUtils.PillButton("Show in Quest Map", selected: false))
|
|
{
|
|
_questMapComponent.SelectQuest(questId, num);
|
|
SelectTabAction?.Invoke("Quest Map");
|
|
}
|
|
QuestJournalUtils questJournalUtils = QuestJournalUtils;
|
|
if (questJournalUtils != null)
|
|
{
|
|
long tickCount = Environment.TickCount64;
|
|
if (!questId.Equals(_addableCachedForQuestId) || tickCount - _addableRefreshedAt > 3000)
|
|
{
|
|
_addableQuestIds.Clear();
|
|
_addableQuestIds.AddRange(questJournalUtils.GetIncompletePrerequisiteQuests(questInfo));
|
|
_addableCachedForQuestId = questId;
|
|
_addableRefreshedAt = tickCount;
|
|
}
|
|
int count = _addableQuestIds.Count;
|
|
using (ImRaii.Disabled(count == 0))
|
|
{
|
|
if (UiThemeUtils.PillButton("Add to Priority", selected: false))
|
|
{
|
|
questJournalUtils.AddRequiredQuestsToPriority(questInfo, _addableQuestIds);
|
|
_addableCachedForQuestId = null;
|
|
}
|
|
}
|
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
|
{
|
|
object obj2;
|
|
switch (count)
|
|
{
|
|
case 0:
|
|
ImGui.SetTooltip("No quests to add (quest may be complete, disabled, locked with no unlock path, or already in priority)");
|
|
goto end_IL_00a4;
|
|
default:
|
|
obj2 = $"Add this quest and {count - 1} required {((count - 1 == 1) ? "quest" : "quests")} to the priority list in completion order";
|
|
break;
|
|
case 1:
|
|
obj2 = "Add this quest to the priority list";
|
|
break;
|
|
}
|
|
ImGui.SetTooltip((string?)obj2);
|
|
}
|
|
}
|
|
end_IL_00a4:;
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
ImGui.PopTextWrapPos();
|
|
}
|
|
|
|
private void DrawQuestDescription(QuestId questId, Lumina.Excel.Sheets.Quest? questEx)
|
|
{
|
|
if (questId.Equals(_cachedDescriptionQuestId))
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(_cachedDescription))
|
|
{
|
|
ImGui.Spacing();
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, _cachedDescription);
|
|
}
|
|
return;
|
|
}
|
|
_cachedDescriptionQuestId = questId;
|
|
_cachedDescription = null;
|
|
try
|
|
{
|
|
if (!questEx.HasValue)
|
|
{
|
|
return;
|
|
}
|
|
string value = questEx.Value.Id.ExtractText();
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
return;
|
|
}
|
|
string text = $"quest/{questId.Value / 100:000}/{value}";
|
|
IDataManager dataManager = _dataManager;
|
|
string name = text;
|
|
ExcelSheet<QuestDialogueText> excelSheet = dataManager.GetExcelSheet<QuestDialogueText>(null, name);
|
|
string text2 = null;
|
|
foreach (QuestDialogueText item in excelSheet)
|
|
{
|
|
string text3 = item.Key.ExtractText();
|
|
if (text3.Contains("SEQ_00", StringComparison.Ordinal) && text3.Contains("BODY", StringComparison.Ordinal))
|
|
{
|
|
string text4 = item.Value.ExtractText();
|
|
if (!string.IsNullOrWhiteSpace(text4))
|
|
{
|
|
text2 = text4;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (text2 == null)
|
|
{
|
|
foreach (QuestDialogueText item2 in excelSheet)
|
|
{
|
|
string text5 = item2.Key.ExtractText();
|
|
if (text5.Contains("TODO_01", StringComparison.Ordinal) || text5.Contains("OBJECTIVE", StringComparison.Ordinal))
|
|
{
|
|
string text6 = item2.Value.ExtractText();
|
|
if (!string.IsNullOrWhiteSpace(text6))
|
|
{
|
|
text2 = text6;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(text2))
|
|
{
|
|
_cachedDescription = text2;
|
|
ImGui.Spacing();
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, text2);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private void DrawNpcLine(Lumina.Excel.Sheets.Quest? questEx, IQuestInfo questInfo)
|
|
{
|
|
string text = null;
|
|
string text2 = null;
|
|
try
|
|
{
|
|
if (questEx.HasValue)
|
|
{
|
|
uint rowId = questEx.Value.IssuerStart.RowId;
|
|
if (rowId != 0)
|
|
{
|
|
text = ResolveNpcName(rowId);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
if (text == null && questInfo.IssuerDataId != 0)
|
|
{
|
|
try
|
|
{
|
|
text = ResolveNpcName(questInfo.IssuerDataId);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
try
|
|
{
|
|
if (questEx.HasValue)
|
|
{
|
|
uint rowId2 = questEx.Value.TargetEnd.RowId;
|
|
if (rowId2 != 0)
|
|
{
|
|
text2 = ResolveNpcName(rowId2);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
if (text != null || text2 != null)
|
|
{
|
|
if (text != null && text2 != null)
|
|
{
|
|
ImU8String text3 = new ImU8String(3, 2);
|
|
text3.AppendFormatted(text);
|
|
text3.AppendLiteral(" → ");
|
|
text3.AppendFormatted(text2);
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, text3);
|
|
}
|
|
else if (text != null)
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, text);
|
|
}
|
|
else
|
|
{
|
|
ImU8String text4 = new ImU8String(2, 1);
|
|
text4.AppendLiteral("→ ");
|
|
text4.AppendFormatted(text2);
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, text4);
|
|
}
|
|
}
|
|
}
|
|
|
|
private string? ResolveNpcName(uint npcId)
|
|
{
|
|
ENpcResident? rowOrDefault = _dataManager.GetExcelSheet<ENpcResident>().GetRowOrDefault(npcId);
|
|
if (rowOrDefault.HasValue)
|
|
{
|
|
string text = rowOrDefault.Value.Singular.ExtractText();
|
|
if (!string.IsNullOrEmpty(text))
|
|
{
|
|
return text;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void DrawRewards(Lumina.Excel.Sheets.Quest questEx)
|
|
{
|
|
bool flag;
|
|
switch (questEx.ItemRewardType)
|
|
{
|
|
case 1:
|
|
case 3:
|
|
case 5:
|
|
flag = true;
|
|
break;
|
|
default:
|
|
flag = false;
|
|
break;
|
|
}
|
|
if (flag)
|
|
{
|
|
bool flag2 = false;
|
|
for (int i = 0; i < questEx.Reward.Count; i++)
|
|
{
|
|
try
|
|
{
|
|
RowRef rowRef = questEx.Reward[i];
|
|
if (rowRef.RowId == 0 || !rowRef.Is<Item>())
|
|
{
|
|
continue;
|
|
}
|
|
Item? valueOrDefault = rowRef.GetValueOrDefault<Item>();
|
|
if (valueOrDefault.HasValue)
|
|
{
|
|
if (!flag2)
|
|
{
|
|
UiThemeUtils.SubSectionHeader("Rewards");
|
|
flag2 = true;
|
|
}
|
|
else
|
|
{
|
|
ImGui.SameLine();
|
|
}
|
|
byte count = (byte)((i >= questEx.ItemCountReward.Count) ? 1 : questEx.ItemCountReward[i]);
|
|
DrawItemIcon(valueOrDefault.Value, count);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
}
|
|
bool flag3 = false;
|
|
for (int j = 0; j < questEx.OptionalItemReward.Count; j++)
|
|
{
|
|
try
|
|
{
|
|
RowRef<Item> rowRef2 = questEx.OptionalItemReward[j];
|
|
if (rowRef2.RowId == 0)
|
|
{
|
|
continue;
|
|
}
|
|
Item? valueNullable = rowRef2.ValueNullable;
|
|
if (valueNullable.HasValue)
|
|
{
|
|
if (!flag3)
|
|
{
|
|
UiThemeUtils.SubSectionHeader("Optional rewards");
|
|
flag3 = true;
|
|
}
|
|
else
|
|
{
|
|
ImGui.SameLine();
|
|
}
|
|
byte count2 = (byte)((j >= questEx.OptionalItemCountReward.Count) ? 1 : questEx.OptionalItemCountReward[j]);
|
|
DrawItemIcon(valueNullable.Value, count2);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawItemIcon(Item item, byte count)
|
|
{
|
|
try
|
|
{
|
|
ushort icon = item.Icon;
|
|
if (icon > 0)
|
|
{
|
|
string path = $"ui/icon/{icon / 1000 * 1000:000000}/{icon:000000}_hr1.tex";
|
|
IDalamudTextureWrap wrapOrDefault = _textureProvider.GetFromGame(path).GetWrapOrDefault();
|
|
if (wrapOrDefault != null)
|
|
{
|
|
ImGui.Image(wrapOrDefault.Handle, new Vector2(32f, 32f));
|
|
bool flag = ImGui.IsItemHovered();
|
|
if (count > 1)
|
|
{
|
|
ImGui.SameLine(0f, 2f);
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 16f);
|
|
ImU8String text = new ImU8String(1, 1);
|
|
text.AppendLiteral("x");
|
|
text.AppendFormatted(count);
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, text);
|
|
flag |= ImGui.IsItemHovered();
|
|
}
|
|
if (flag)
|
|
{
|
|
ImGui.BeginTooltip();
|
|
string text2 = item.Name.ExtractText();
|
|
ImGui.TextUnformatted((count > 1) ? $"{text2} x{count}" : text2);
|
|
ImGui.EndTooltip();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
string text3 = item.Name.ExtractText();
|
|
ImGui.TextUnformatted((count > 1) ? $"[{text3} x{count}]" : ("[" + text3 + "]"));
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.BeginTooltip();
|
|
ImGui.TextUnformatted(text3);
|
|
ImGui.EndTooltip();
|
|
}
|
|
}
|
|
|
|
private void DrawQuestBanner(uint iconId)
|
|
{
|
|
if (iconId == 0)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
string path = $"ui/icon/{iconId / 1000 * 1000:000000}/{iconId:000000}_hr1.tex";
|
|
IDalamudTextureWrap wrapOrDefault = _textureProvider.GetFromGame(path).GetWrapOrDefault();
|
|
if (wrapOrDefault != null)
|
|
{
|
|
float num = MathF.Min(ImGui.GetContentRegionAvail().X, (float)wrapOrDefault.Width * 0.5f);
|
|
float num2 = (float)wrapOrDefault.Height / (float)wrapOrDefault.Width;
|
|
float y = num * num2;
|
|
ImGui.Image(wrapOrDefault.Handle, new Vector2(num, y));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private CachedGraphLayout ComputeLayout(QuestChainGraph graph)
|
|
{
|
|
List<List<QuestId>> list = ReorderLayers(graph);
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
list[i] = list[i].Where((QuestId qid) => _questData.TryGetQuestInfo(qid, out IQuestInfo _)).ToList();
|
|
}
|
|
list.RemoveAll((List<QuestId> l) => l.Count == 0);
|
|
Dictionary<QuestId, float> dictionary = new Dictionary<QuestId, float>();
|
|
foreach (List<QuestId> item in list)
|
|
{
|
|
foreach (QuestId item2 in item)
|
|
{
|
|
IQuestInfo questInfo;
|
|
float y = (_questData.TryGetQuestInfo(item2, out questInfo) ? (ImGui.CalcTextSize(UiThemeUtils.CleanSeString(questInfo.Name)).X + 16f) : 150f);
|
|
dictionary[item2] = MathF.Max(150f, MathF.Min(240f, y));
|
|
}
|
|
}
|
|
float[] array = new float[list.Count];
|
|
for (int num = 0; num < list.Count; num++)
|
|
{
|
|
float num2 = 0f;
|
|
for (int num3 = 0; num3 < list[num].Count; num3++)
|
|
{
|
|
if (num3 > 0)
|
|
{
|
|
num2 += 20f;
|
|
}
|
|
num2 += dictionary[list[num][num3]];
|
|
}
|
|
array[num] = num2;
|
|
}
|
|
Dictionary<QuestId, string> dictionary2 = new Dictionary<QuestId, string>();
|
|
foreach (List<QuestId> item3 in list)
|
|
{
|
|
foreach (QuestId item4 in item3)
|
|
{
|
|
if (!_questData.TryGetQuestInfo(item4, out IQuestInfo questInfo2))
|
|
{
|
|
continue;
|
|
}
|
|
string text = UiThemeUtils.CleanSeString(questInfo2.Name);
|
|
float num4 = dictionary[item4];
|
|
float num5 = 8f;
|
|
if (ImGui.CalcTextSize(text).X + num5 > num4)
|
|
{
|
|
for (int num6 = text.Length - 1; num6 > 3; num6--)
|
|
{
|
|
string text2 = text.Substring(0, num6) + "...";
|
|
if (ImGui.CalcTextSize(text2).X + num5 <= num4)
|
|
{
|
|
text = text2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
dictionary2[item4] = text;
|
|
}
|
|
}
|
|
return new CachedGraphLayout
|
|
{
|
|
Layers = list,
|
|
NodeWidths = dictionary,
|
|
LayerWidths = array,
|
|
TruncatedLabels = dictionary2
|
|
};
|
|
}
|
|
|
|
private void RefreshNodeStatusCache()
|
|
{
|
|
_nodeStatusCache.Clear();
|
|
if (_cachedLayout == null)
|
|
{
|
|
return;
|
|
}
|
|
foreach (List<QuestId> layer in _cachedLayout.Layers)
|
|
{
|
|
foreach (QuestId item in layer)
|
|
{
|
|
Vector4 questNodeColor = GetQuestNodeColor(item);
|
|
bool isDone = _questFunctions.IsQuestAcceptedOrComplete(item);
|
|
_nodeStatusCache[item] = new NodeStatusEntry(questNodeColor, isDone);
|
|
}
|
|
}
|
|
_nodeStatusRefreshedAt = Environment.TickCount64;
|
|
}
|
|
|
|
private void DrawChainGraph(QuestChainGraph graph, float panelWidth)
|
|
{
|
|
float graphZoom = _graphZoom;
|
|
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
|
|
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
|
|
Vector2 contentRegionAvail = ImGui.GetContentRegionAvail();
|
|
if (contentRegionAvail.X < 1f)
|
|
{
|
|
contentRegionAvail.X = 1f;
|
|
}
|
|
if (contentRegionAvail.Y < 1f)
|
|
{
|
|
contentRegionAvail.Y = 1f;
|
|
}
|
|
ImGui.InvisibleButton("##chainCanvas", contentRegionAvail, ImGuiButtonFlags.MouseButtonMask);
|
|
bool flag = ImGui.IsItemHovered();
|
|
bool flag2 = ImGui.IsItemActive();
|
|
Vector2 mousePos = ImGui.GetMousePos();
|
|
windowDrawList.PushClipRect(cursorScreenPos, cursorScreenPos + contentRegionAvail, intersectWithCurrentClipRect: true);
|
|
windowDrawList.AddRectFilled(cursorScreenPos, cursorScreenPos + contentRegionAvail, ImGui.ColorConvertFloat4ToU32(GraphBackgroundColor));
|
|
float mouseWheel = ImGui.GetIO().MouseWheel;
|
|
if (flag && MathF.Abs(mouseWheel) > 0.01f)
|
|
{
|
|
float graphZoom2 = _graphZoom;
|
|
_graphZoom = Math.Clamp(_graphZoom + mouseWheel * 0.1f, 0.25f, 2f);
|
|
Vector2 vector = mousePos - cursorScreenPos - contentRegionAvail * 0.5f - _graphOffset;
|
|
_graphOffset -= vector * (_graphZoom / graphZoom2 - 1f);
|
|
graphZoom = _graphZoom;
|
|
}
|
|
if (flag2)
|
|
{
|
|
if (!_isDragging)
|
|
{
|
|
_isDragging = true;
|
|
_dragStart = mousePos;
|
|
_dragOrigin = mousePos;
|
|
}
|
|
if (Vector2.Distance(mousePos, _dragOrigin) > 4f)
|
|
{
|
|
_dragMoved = true;
|
|
}
|
|
Vector2 vector2 = mousePos - _dragStart;
|
|
_graphOffset += vector2;
|
|
_dragStart = mousePos;
|
|
}
|
|
else
|
|
{
|
|
_isDragging = false;
|
|
}
|
|
if (_cachedLayout == null || _cachedLayoutQuestId != graph.SelectedQuest)
|
|
{
|
|
_cachedLayout = ComputeLayout(graph);
|
|
_cachedLayoutQuestId = graph.SelectedQuest;
|
|
RefreshNodeStatusCache();
|
|
}
|
|
Vector2 vector3 = cursorScreenPos + contentRegionAvail * 0.5f;
|
|
List<List<QuestId>> layers = _cachedLayout.Layers;
|
|
Dictionary<QuestId, float> nodeWidths = _cachedLayout.NodeWidths;
|
|
float[] layerWidths = _cachedLayout.LayerWidths;
|
|
if (Environment.TickCount64 - _nodeStatusRefreshedAt > 3000)
|
|
{
|
|
RefreshNodeStatusCache();
|
|
}
|
|
if (_scrollToSelected)
|
|
{
|
|
_scrollToSelected = false;
|
|
for (int i = 0; i < layers.Count; i++)
|
|
{
|
|
int num = layers[i].IndexOf(graph.SelectedQuest);
|
|
if (num >= 0)
|
|
{
|
|
float num2 = (0f - layerWidths[i]) * 0.5f;
|
|
for (int j = 0; j < num; j++)
|
|
{
|
|
num2 += nodeWidths[layers[i][j]] + 20f;
|
|
}
|
|
float num3 = nodeWidths[graph.SelectedQuest];
|
|
float num4 = (float)i * 81f;
|
|
_graphOffset = -new Vector2(num2 + num3 * 0.5f, num4 + 13f) * graphZoom;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Dictionary<QuestId, (Vector2 Min, Vector2 Max)> nodePos = new Dictionary<QuestId, (Vector2, Vector2)>();
|
|
for (int k = 0; k < layers.Count; k++)
|
|
{
|
|
List<QuestId> list = layers[k];
|
|
float num5 = (0f - layerWidths[k]) * 0.5f;
|
|
float num6 = (float)k * 81f;
|
|
foreach (QuestId item11 in list)
|
|
{
|
|
float num7 = nodeWidths[item11];
|
|
Vector2 item = vector3 + new Vector2(num5, num6) * graphZoom + _graphOffset;
|
|
Vector2 item2 = vector3 + new Vector2(num5 + num7, num6 + 26f) * graphZoom + _graphOffset;
|
|
nodePos[item11] = (item, item2);
|
|
num5 += num7 + 20f;
|
|
}
|
|
}
|
|
QuestId questId = null;
|
|
bool dragMoved = _dragMoved;
|
|
QuestId key;
|
|
if (flag && !dragMoved)
|
|
{
|
|
foreach (KeyValuePair<QuestId, (Vector2, Vector2)> item12 in nodePos)
|
|
{
|
|
item12.Deconstruct(out key, out var value);
|
|
QuestId questId2 = key;
|
|
(Vector2, Vector2) tuple = value;
|
|
if (mousePos.X >= tuple.Item1.X && mousePos.X <= tuple.Item2.X && mousePos.Y >= tuple.Item1.Y && mousePos.Y <= tuple.Item2.Y)
|
|
{
|
|
questId = questId2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
QuestId questId3 = questId ?? graph.SelectedQuest;
|
|
Dictionary<(QuestId, QuestId), (float, float)> dictionary = new Dictionary<(QuestId, QuestId), (float, float)>();
|
|
Dictionary<QuestId, List<QuestId>> dictionary2 = new Dictionary<QuestId, List<QuestId>>();
|
|
Dictionary<QuestId, List<QuestId>> dictionary3 = new Dictionary<QuestId, List<QuestId>>();
|
|
List<QuestId> value2;
|
|
foreach (KeyValuePair<QuestId, List<QuestId>> prerequisite in graph.Prerequisites)
|
|
{
|
|
prerequisite.Deconstruct(out key, out value2);
|
|
QuestId questId4 = key;
|
|
foreach (QuestId item13 in value2)
|
|
{
|
|
if (nodePos.ContainsKey(item13) && nodePos.ContainsKey(questId4))
|
|
{
|
|
if (!dictionary2.TryGetValue(item13, out var value3))
|
|
{
|
|
value3 = (dictionary2[item13] = new List<QuestId>());
|
|
}
|
|
value3.Add(questId4);
|
|
if (!dictionary3.TryGetValue(questId4, out var value4))
|
|
{
|
|
value4 = (dictionary3[questId4] = new List<QuestId>());
|
|
}
|
|
value4.Add(item13);
|
|
}
|
|
}
|
|
}
|
|
foreach (KeyValuePair<QuestId, List<QuestId>> item14 in dictionary2)
|
|
{
|
|
item14.Deconstruct(out key, out value2);
|
|
QuestId item3 = key;
|
|
List<QuestId> list4 = value2;
|
|
list4.Sort(delegate(QuestId a, QuestId b)
|
|
{
|
|
float num26 = (nodePos[a].Min.X + nodePos[a].Max.X) * 0.5f;
|
|
float value9 = (nodePos[b].Min.X + nodePos[b].Max.X) * 0.5f;
|
|
return num26.CompareTo(value9);
|
|
});
|
|
for (int num8 = 0; num8 < list4.Count; num8++)
|
|
{
|
|
float item4 = ((list4.Count > 1) ? ((float)num8 / (float)(list4.Count - 1)) : 0.5f);
|
|
dictionary[(item3, list4[num8])] = (item4, 0.5f);
|
|
}
|
|
}
|
|
foreach (KeyValuePair<QuestId, List<QuestId>> item15 in dictionary3)
|
|
{
|
|
item15.Deconstruct(out key, out value2);
|
|
QuestId item5 = key;
|
|
List<QuestId> list5 = value2;
|
|
list5.Sort(delegate(QuestId a, QuestId b)
|
|
{
|
|
float num26 = (nodePos[a].Min.X + nodePos[a].Max.X) * 0.5f;
|
|
float value9 = (nodePos[b].Min.X + nodePos[b].Max.X) * 0.5f;
|
|
return num26.CompareTo(value9);
|
|
});
|
|
for (int num9 = 0; num9 < list5.Count; num9++)
|
|
{
|
|
float item6 = ((list5.Count > 1) ? ((float)num9 / (float)(list5.Count - 1)) : 0.5f);
|
|
(QuestId, QuestId) key2 = (list5[num9], item5);
|
|
dictionary[key2] = (dictionary.GetValueOrDefault(key2, (0.5f, 0.5f)).Item1, item6);
|
|
}
|
|
}
|
|
Vector4 accentColor = UiThemeUtils.AccentColor;
|
|
accentColor.W = 0.9f;
|
|
uint num10 = ImGui.ColorConvertFloat4ToU32(accentColor);
|
|
float size = 6f * graphZoom;
|
|
for (int num11 = 0; num11 < 2; num11++)
|
|
{
|
|
foreach (KeyValuePair<QuestId, List<QuestId>> prerequisite2 in graph.Prerequisites)
|
|
{
|
|
prerequisite2.Deconstruct(out key, out value2);
|
|
QuestId questId5 = key;
|
|
List<QuestId> list6 = value2;
|
|
if (!nodePos.TryGetValue(questId5, out (Vector2, Vector2) value5))
|
|
{
|
|
continue;
|
|
}
|
|
foreach (QuestId item16 in list6)
|
|
{
|
|
if (!nodePos.TryGetValue(item16, out (Vector2, Vector2) value6))
|
|
{
|
|
continue;
|
|
}
|
|
bool flag3 = questId3 != null && (questId5 == questId3 || item16 == questId3);
|
|
if (num11 == 0 == flag3)
|
|
{
|
|
continue;
|
|
}
|
|
uint num12;
|
|
float thickness;
|
|
if (flag3)
|
|
{
|
|
num12 = num10;
|
|
thickness = 2.5f;
|
|
}
|
|
else if (questId3 != null)
|
|
{
|
|
num12 = ImGui.ColorConvertFloat4ToU32(HueToColor((float)(int)item16.Value * 0.618034f % 1f, 0.15f, 0.35f));
|
|
thickness = 1.5f;
|
|
}
|
|
else
|
|
{
|
|
num12 = ImGui.ColorConvertFloat4ToU32(HueToColor((float)(int)item16.Value * 0.618034f % 1f, 0.35f, 0.85f));
|
|
thickness = 2f;
|
|
}
|
|
(float, float) valueOrDefault = dictionary.GetValueOrDefault((item16, questId5), (0.5f, 0.5f));
|
|
float item7 = valueOrDefault.Item1;
|
|
float item8 = valueOrDefault.Item2;
|
|
float num13 = value6.Item2.X - value6.Item1.X;
|
|
float num14 = value5.Item2.X - value5.Item1.X;
|
|
float num15 = 10f * graphZoom;
|
|
float x = value6.Item1.X + num15 + (num13 - num15 * 2f) * item7;
|
|
float x2 = value5.Item1.X + num15 + (num14 - num15 * 2f) * item8;
|
|
Vector2 vector4 = new Vector2(x, value6.Item2.Y);
|
|
Vector2 vector5 = new Vector2(x2, value5.Item1.Y);
|
|
int num16 = graph.Layers.GetValueOrDefault(questId5) - graph.Layers.GetValueOrDefault(item16);
|
|
if (num16 > 1)
|
|
{
|
|
int valueOrDefault2 = graph.Layers.GetValueOrDefault(item16);
|
|
float num17 = 0f;
|
|
for (int num18 = valueOrDefault2 + 1; num18 < valueOrDefault2 + num16 && num18 < layerWidths.Length; num18++)
|
|
{
|
|
num17 = MathF.Max(num17, layerWidths[num18] * 0.5f);
|
|
}
|
|
float num19 = vector3.X + _graphOffset.X;
|
|
float x3 = (vector4.X + vector5.X) * 0.5f - num19;
|
|
float num20 = ((MathF.Abs(x3) > 1f) ? ((float)MathF.Sign(x3)) : ((item16.Value % 2 == 0) ? 1f : (-1f)));
|
|
float x4 = num19 + num20 * (num17 + 40f + (float)(item16.Value % 3) * 16f) * graphZoom;
|
|
float num21 = MathF.Min(30f * graphZoom, (vector5.Y - vector4.Y) * 0.2f);
|
|
Vector2 p = new Vector2(x4, vector4.Y + num21 * 2.5f);
|
|
Vector2 pos = new Vector2(x4, MathF.Max(vector5.Y - num21 * 2.5f, vector4.Y + num21 * 2.5f));
|
|
windowDrawList.PathLineTo(vector4);
|
|
windowDrawList.PathBezierCubicCurveTo(new Vector2(vector4.X, vector4.Y + num21 * 1.5f), new Vector2(x4, vector4.Y + num21), p, 20);
|
|
windowDrawList.PathLineTo(pos);
|
|
windowDrawList.PathBezierCubicCurveTo(new Vector2(x4, vector5.Y - num21), new Vector2(vector5.X, vector5.Y - num21 * 1.5f), vector5, 20);
|
|
windowDrawList.PathStroke(num12, ImDrawFlags.None, thickness);
|
|
DrawArrowhead(windowDrawList, vector5, new Vector2(vector5.X, vector5.Y - num21), size, num12);
|
|
}
|
|
else
|
|
{
|
|
float num22 = (vector5.Y - vector4.Y) * 0.4f;
|
|
Vector2 p2 = new Vector2(vector4.X, vector4.Y + num22);
|
|
Vector2 vector6 = new Vector2(vector5.X, vector5.Y - num22);
|
|
windowDrawList.AddBezierCubic(vector4, p2, vector6, vector5, num12, thickness, 28);
|
|
DrawArrowhead(windowDrawList, vector5, vector6, size, num12);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
float fontSize = ImGui.GetFontSize() * graphZoom;
|
|
ImFontPtr font = ImGui.GetFont();
|
|
foreach (List<QuestId> item17 in layers)
|
|
{
|
|
foreach (QuestId item18 in item17)
|
|
{
|
|
if (nodePos.TryGetValue(item18, out (Vector2, Vector2) value7))
|
|
{
|
|
bool num23 = item18 == graph.SelectedQuest;
|
|
bool flag4 = questId == item18;
|
|
NodeStatusEntry valueOrDefault3 = _nodeStatusCache.GetValueOrDefault(item18, new NodeStatusEntry(NodeFillLocked, IsDone: false));
|
|
Vector4 vector7 = valueOrDefault3.BgColor;
|
|
bool isDone = valueOrDefault3.IsDone;
|
|
if (isDone)
|
|
{
|
|
accentColor = GraphBackgroundColor;
|
|
accentColor.W = 1f;
|
|
Vector4 value8 = accentColor;
|
|
accentColor = vector7;
|
|
accentColor.W = 1f;
|
|
vector7 = Vector4.Lerp(value8, accentColor, 0.5f);
|
|
}
|
|
windowDrawList.AddRectFilled(value7.Item1, value7.Item2, ImGui.ColorConvertFloat4ToU32(vector7), 5f);
|
|
if (num23)
|
|
{
|
|
windowDrawList.AddRect(value7.Item1 - Vector2.One, value7.Item2 + Vector2.One, ImGui.ColorConvertFloat4ToU32(UiThemeUtils.AccentColor), 5f, ImDrawFlags.None, 3f);
|
|
}
|
|
else if (flag4)
|
|
{
|
|
Vector2 item9 = value7.Item1;
|
|
Vector2 item10 = value7.Item2;
|
|
accentColor = UiThemeUtils.AccentColor;
|
|
accentColor.W = 0.6f;
|
|
windowDrawList.AddRect(item9, item10, ImGui.ColorConvertFloat4ToU32(accentColor), 5f, ImDrawFlags.None, 1.5f);
|
|
}
|
|
string valueOrDefault4 = _cachedLayout.TruncatedLabels.GetValueOrDefault(item18, item18.ToString());
|
|
float num24 = value7.Item2.X - value7.Item1.X;
|
|
float num25 = value7.Item2.Y - value7.Item1.Y;
|
|
Vector2 vector8 = ImGui.CalcTextSize(valueOrDefault4) * graphZoom;
|
|
Vector2 pos2 = new Vector2(value7.Item1.X + (num24 - vector8.X) * 0.5f, value7.Item1.Y + (num25 - vector8.Y) * 0.5f);
|
|
uint col = (isDone ? ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.5f)) : ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 1f)));
|
|
windowDrawList.AddText(font, fontSize, pos2, col, valueOrDefault4);
|
|
}
|
|
}
|
|
}
|
|
windowDrawList.PopClipRect();
|
|
if (questId != null && ImGui.IsMouseReleased(ImGuiMouseButton.Left) && !dragMoved)
|
|
{
|
|
_selectedQuestId = questId;
|
|
_showDetail = true;
|
|
}
|
|
if (questId != null && ImGui.IsMouseReleased(ImGuiMouseButton.Right) && !dragMoved && _questData.TryGetQuestInfo(questId, out IQuestInfo questInfo))
|
|
{
|
|
_contextMenuNodeId = questId;
|
|
QuestJournalUtils?.OpenContextMenu(questInfo);
|
|
}
|
|
if (!flag2)
|
|
{
|
|
_dragMoved = false;
|
|
}
|
|
if (questId != null && _questData.TryGetQuestInfo(questId, out IQuestInfo questInfo2))
|
|
{
|
|
_questTooltipComponent.Draw(questInfo2);
|
|
}
|
|
DrawNodeContextMenu();
|
|
static Vector4 HueToColor(float hue, float sat, float alpha)
|
|
{
|
|
float num26 = hue * 6f;
|
|
float num27 = sat * (1f - MathF.Abs(num26 % 2f - 1f));
|
|
float num28;
|
|
float num29;
|
|
float num30;
|
|
if (num26 < 1f)
|
|
{
|
|
num28 = sat;
|
|
num29 = num27;
|
|
num30 = 0f;
|
|
}
|
|
else if (num26 < 2f)
|
|
{
|
|
num28 = num27;
|
|
num29 = sat;
|
|
num30 = 0f;
|
|
}
|
|
else if (num26 < 3f)
|
|
{
|
|
num28 = 0f;
|
|
num29 = sat;
|
|
num30 = num27;
|
|
}
|
|
else if (num26 < 4f)
|
|
{
|
|
num28 = 0f;
|
|
num29 = num27;
|
|
num30 = sat;
|
|
}
|
|
else if (num26 < 5f)
|
|
{
|
|
num28 = num27;
|
|
num29 = 0f;
|
|
num30 = sat;
|
|
}
|
|
else
|
|
{
|
|
num28 = sat;
|
|
num29 = 0f;
|
|
num30 = num27;
|
|
}
|
|
float num31 = 1f - sat;
|
|
return new Vector4(num28 + num31, num29 + num31, num30 + num31, alpha);
|
|
}
|
|
}
|
|
|
|
private void DrawNodeContextMenu()
|
|
{
|
|
QuestId contextMenuNodeId = _contextMenuNodeId;
|
|
if ((object)contextMenuNodeId == null)
|
|
{
|
|
return;
|
|
}
|
|
QuestJournalUtils questJournalUtils = QuestJournalUtils;
|
|
if (questJournalUtils == null)
|
|
{
|
|
return;
|
|
}
|
|
if (!_questData.TryGetQuestInfo(contextMenuNodeId, out IQuestInfo questInfo))
|
|
{
|
|
_contextMenuNodeId = null;
|
|
return;
|
|
}
|
|
Questionable.Model.Quest quest = null;
|
|
_questRegistry.TryGetQuest(contextMenuNodeId, out quest);
|
|
if (!questJournalUtils.DrawContextMenu(questInfo, quest, "QuestChainComponent"))
|
|
{
|
|
_contextMenuNodeId = null;
|
|
}
|
|
}
|
|
|
|
private static List<List<QuestId>> ReorderLayers(QuestChainGraph graph)
|
|
{
|
|
List<List<QuestId>> list = new List<List<QuestId>>();
|
|
for (int i = 0; i < graph.LayerOrder.Count; i++)
|
|
{
|
|
list.Add(new List<QuestId>(graph.LayerOrder[i]));
|
|
}
|
|
if (list.Count <= 1)
|
|
{
|
|
return list;
|
|
}
|
|
Dictionary<QuestId, List<QuestId>> dependents = new Dictionary<QuestId, List<QuestId>>();
|
|
foreach (var (item, list3) in graph.Prerequisites)
|
|
{
|
|
foreach (QuestId item2 in list3)
|
|
{
|
|
if (!dependents.TryGetValue(item2, out List<QuestId> value))
|
|
{
|
|
value = new List<QuestId>();
|
|
dependents[item2] = value;
|
|
}
|
|
value.Add(item);
|
|
}
|
|
}
|
|
for (int j = 0; j < 6; j++)
|
|
{
|
|
if (j % 2 == 0)
|
|
{
|
|
for (int k = 1; k < list.Count; k++)
|
|
{
|
|
List<QuestId> referenceLayer = list[k - 1];
|
|
Dictionary<QuestId, float> bary = ComputeBarycenters(list[k], referenceLayer, (QuestId qid) => graph.Prerequisites.GetValueOrDefault(qid));
|
|
list[k].Sort((QuestId a, QuestId b) => bary.GetValueOrDefault(a).CompareTo(bary.GetValueOrDefault(b)));
|
|
}
|
|
continue;
|
|
}
|
|
for (int num = list.Count - 2; num >= 0; num--)
|
|
{
|
|
List<QuestId> referenceLayer2 = list[num + 1];
|
|
Dictionary<QuestId, float> bary2 = ComputeBarycenters(list[num], referenceLayer2, (QuestId qid) => dependents.GetValueOrDefault(qid));
|
|
list[num].Sort((QuestId a, QuestId b) => bary2.GetValueOrDefault(a).CompareTo(bary2.GetValueOrDefault(b)));
|
|
}
|
|
}
|
|
for (int num2 = 0; num2 < 3; num2++)
|
|
{
|
|
bool flag = false;
|
|
for (int num3 = 0; num3 < list.Count; num3++)
|
|
{
|
|
List<QuestId> list4 = list[num3];
|
|
if (list4.Count < 2)
|
|
{
|
|
continue;
|
|
}
|
|
List<QuestId> adjAbove = ((num3 > 0) ? list[num3 - 1] : null);
|
|
List<QuestId> adjBelow = ((num3 < list.Count - 1) ? list[num3 + 1] : null);
|
|
for (int num4 = 0; num4 < list4.Count - 1; num4++)
|
|
{
|
|
int num5 = CountCrossings(list4, adjAbove, adjBelow, graph.Prerequisites, dependents);
|
|
List<QuestId> list5 = list4;
|
|
int index = num4;
|
|
int index2 = num4 + 1;
|
|
QuestId value2 = list4[num4 + 1];
|
|
QuestId value3 = list4[num4];
|
|
list5[index] = value2;
|
|
list4[index2] = value3;
|
|
if (CountCrossings(list4, adjAbove, adjBelow, graph.Prerequisites, dependents) < num5)
|
|
{
|
|
flag = true;
|
|
continue;
|
|
}
|
|
list5 = list4;
|
|
index2 = num4;
|
|
index = num4 + 1;
|
|
value3 = list4[num4 + 1];
|
|
value2 = list4[num4];
|
|
list5[index2] = value3;
|
|
list4[index] = value2;
|
|
}
|
|
}
|
|
if (!flag)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static int CountCrossings(List<QuestId> layer, List<QuestId>? adjAbove, List<QuestId>? adjBelow, Dictionary<QuestId, List<QuestId>> prerequisites, Dictionary<QuestId, List<QuestId>> dependents)
|
|
{
|
|
int num = 0;
|
|
if (adjAbove != null)
|
|
{
|
|
num += CountLayerCrossings(adjAbove, layer, (QuestId qid) => prerequisites.GetValueOrDefault(qid));
|
|
}
|
|
if (adjBelow != null)
|
|
{
|
|
num += CountLayerCrossings(layer, adjBelow, (QuestId qid) => dependents.GetValueOrDefault(qid));
|
|
}
|
|
return num;
|
|
}
|
|
|
|
private static int CountLayerCrossings(List<QuestId> upperLayer, List<QuestId> lowerLayer, Func<QuestId, List<QuestId>?> getUpperConnections)
|
|
{
|
|
int num = 0;
|
|
for (int i = 0; i < lowerLayer.Count; i++)
|
|
{
|
|
List<QuestId> list = getUpperConnections(lowerLayer[i]);
|
|
if (list == null)
|
|
{
|
|
continue;
|
|
}
|
|
for (int j = i + 1; j < lowerLayer.Count; j++)
|
|
{
|
|
List<QuestId> list2 = getUpperConnections(lowerLayer[j]);
|
|
if (list2 == null)
|
|
{
|
|
continue;
|
|
}
|
|
foreach (QuestId item in list)
|
|
{
|
|
int num2 = upperLayer.IndexOf(item);
|
|
if (num2 < 0)
|
|
{
|
|
continue;
|
|
}
|
|
foreach (QuestId item2 in list2)
|
|
{
|
|
int num3 = upperLayer.IndexOf(item2);
|
|
if (num3 >= 0 && num2 > num3)
|
|
{
|
|
num++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
private static Dictionary<QuestId, float> ComputeBarycenters(List<QuestId> layer, List<QuestId> referenceLayer, Func<QuestId, List<QuestId>?> getConnected)
|
|
{
|
|
Dictionary<QuestId, float> dictionary = new Dictionary<QuestId, float>();
|
|
foreach (QuestId item in layer)
|
|
{
|
|
List<QuestId> list = getConnected(item);
|
|
if (list != null)
|
|
{
|
|
List<int> list2 = (from p in list
|
|
select referenceLayer.IndexOf(p) into idx
|
|
where idx >= 0
|
|
select idx).ToList();
|
|
dictionary[item] = ((list2.Count > 0) ? ((float)list2.Average()) : ((float)referenceLayer.Count * 0.5f));
|
|
}
|
|
else
|
|
{
|
|
dictionary[item] = (float)referenceLayer.Count * 0.5f;
|
|
}
|
|
}
|
|
return dictionary;
|
|
}
|
|
|
|
private static void DrawArrowhead(ImDrawListPtr drawList, Vector2 tip, Vector2 controlPt, float size, uint color)
|
|
{
|
|
if (!(size <= 1.5f))
|
|
{
|
|
Vector2 vector = Vector2.Normalize(tip - controlPt);
|
|
Vector2 vector2 = new Vector2(0f - vector.Y, vector.X);
|
|
Vector2 vector3 = tip - vector * size;
|
|
drawList.AddTriangleFilled(tip, vector3 - vector2 * size * 0.45f, vector3 + vector2 * size * 0.45f, color);
|
|
}
|
|
}
|
|
|
|
private Vector4 GetQuestNodeColor(QuestId questId)
|
|
{
|
|
if (_questFunctions.IsQuestAccepted(questId))
|
|
{
|
|
return NodeFillActive;
|
|
}
|
|
if (_questFunctions.IsQuestComplete(questId))
|
|
{
|
|
return NodeFillComplete;
|
|
}
|
|
if (_questFunctions.IsQuestUnobtainable(questId))
|
|
{
|
|
return NodeFillUnobtainable;
|
|
}
|
|
if (!_questFunctions.IsQuestLocked(questId) && _questFunctions.IsReadyToAcceptQuest(questId))
|
|
{
|
|
return NodeFillAvailable;
|
|
}
|
|
return NodeFillLocked;
|
|
}
|
|
}
|