master #3

Open
cacahuetes wants to merge 653 commits from liza/Questionable:master into cacahuetes-ShB-Healer
41 changed files with 314 additions and 266 deletions
Showing only changes of commit be1e4ed2e6 - Show all commits

View File

@ -1,4 +1,4 @@
<Project Sdk="Dalamud.NET.Sdk/10.0.0"> <Project Sdk="Dalamud.NET.Sdk/11.0.0">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LLib\LLib.csproj" /> <ProjectReference Include="..\LLib\LLib.csproj" />
<ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" /> <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />

2
LLib

@ -1 +1 @@
Subproject commit fde09c705b648f03c287814191a554f0a4b92cc4 Subproject commit 538329a1e80acbcd09e28bd6dd459c35b5563c0a

View File

@ -142,7 +142,7 @@ internal sealed class ItemUseModule : ICombatModule
{ {
BattleChara* battleChara = (BattleChara*)gameObject.Address; BattleChara* battleChara = (BattleChara*)gameObject.Address;
if (_combatData.CombatItemUse.Condition == ECombatItemUseCondition.Incapacitated) if (_combatData.CombatItemUse.Condition == ECombatItemUseCondition.Incapacitated)
return (battleChara->Flags2 & 128u) != 0; return (battleChara->CombatTagType & 128u) != 0; // FIXME 7.1
if (_combatData.CombatItemUse.Condition == ECombatItemUseCondition.HealthPercent) if (_combatData.CombatItemUse.Condition == ECombatItemUseCondition.HealthPercent)
return (100f * battleChara->Health / battleChara->MaxHealth) < _combatData.CombatItemUse.Value; return (100f * battleChara->Health / battleChara->MaxHealth) < _combatData.CombatItemUse.Value;

View File

@ -82,7 +82,7 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius; float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius;
float actualDistance = Vector3.Distance(player.Position, gameObject.Position); float actualDistance = Vector3.Distance(player.Position, gameObject.Position);
float maxDistance = player.ClassJob.GameData?.Role is 3 or 4 ? 20f : 2.9f; float maxDistance = player.ClassJob.ValueNullable?.Role is 3 or 4 ? 20f : 2.9f;
if (actualDistance - hitboxOffset >= maxDistance) if (actualDistance - hitboxOffset >= maxDistance)
{ {
if (actualDistance - hitboxOffset <= 5) if (actualDistance - hitboxOffset <= 5)

View File

@ -3,11 +3,10 @@ using System.Linq;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using Questionable.Windows; using Questionable.Windows;
using Questionable.Windows.QuestComponents;
using Quest = Questionable.Model.Quest; using Quest = Questionable.Model.Quest;
namespace Questionable.Controller; namespace Questionable.Controller;
@ -240,7 +239,7 @@ internal sealed class CommandHandler : IDisposable
ushort? mountId = _gameFunctions.GetMountId(); ushort? mountId = _gameFunctions.GetMountId();
if (mountId != null) if (mountId != null)
{ {
var row = _dataManager.GetExcelSheet<Mount>()!.GetRow(mountId.Value); var row = _dataManager.GetExcelSheet<Mount>().GetRowOrDefault(mountId.Value);
_chatGui.Print( _chatGui.Print(
$"Mount ID: {mountId}, Name: {row?.Singular}, Obtainable: {(row?.Order == -1 ? "No" : "Yes")}", $"Mount ID: {mountId}, Name: {row?.Singular}, Obtainable: {(row?.Order == -1 ? "No" : "Yes")}",
MessageTag, TagColor); MessageTag, TagColor);

View File

@ -79,7 +79,7 @@ internal sealed class ContextMenuController : IDisposable
private void AddContextMenuEntry(IMenuOpenedArgs args, uint itemId, uint npcId, EExtendedClassJob extendedClassJob, private void AddContextMenuEntry(IMenuOpenedArgs args, uint itemId, uint npcId, EExtendedClassJob extendedClassJob,
string verb) string verb)
{ {
EClassJob currentClassJob = (EClassJob)_clientState.LocalPlayer!.ClassJob.Id; EClassJob currentClassJob = (EClassJob)_clientState.LocalPlayer!.ClassJob.RowId;
EClassJob classJob = ClassJobUtils.AsIndividualJobs(extendedClassJob).Single(); EClassJob classJob = ClassJobUtils.AsIndividualJobs(extendedClassJob).Single();
if (classJob != currentClassJob && currentClassJob is EClassJob.Miner or EClassJob.Botanist) if (classJob != currentClassJob && currentClassJob is EClassJob.Miner or EClassJob.Botanist)
return; return;

View File

@ -16,7 +16,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib; using LLib;
using LLib.GameData; using LLib.GameData;
using LLib.GameUI; using LLib.GameUI;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Interactions; using Questionable.Controller.Steps.Interactions;
using Questionable.Data; using Questionable.Data;
@ -90,7 +90,7 @@ internal sealed class InteractionUiController : IDisposable
_shopController = shopController; _shopController = shopController;
_logger = logger; _logger = logger;
_returnRegex = _dataManager.GetExcelSheet<Addon>()!.GetRow(196)!.GetRegex(addon => addon.Text, pluginLog)!; _returnRegex = _dataManager.GetExcelSheet<Addon>().GetRow(196).GetRegex(addon => addon.Text, pluginLog)!;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
@ -713,7 +713,7 @@ internal sealed class InteractionUiController : IDisposable
step.InteractionType == EInteractionType.Gather) step.InteractionType == EInteractionType.Gather)
{ {
if (_gatheringData.TryGetGatheringPointId(step.ItemsToGather[0].ItemId, if (_gatheringData.TryGetGatheringPointId(step.ItemsToGather[0].ItemId,
(EClassJob?)_clientState.LocalPlayer?.ClassJob.Id ?? EClassJob.Adventurer, (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer,
out GatheringPointId? gatheringPointId) && out GatheringPointId? gatheringPointId) &&
_gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? root)) _gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? root))
{ {
@ -757,19 +757,19 @@ internal sealed class InteractionUiController : IDisposable
[NotNullWhen(true)] out string? warpText) [NotNullWhen(true)] out string? warpText)
{ {
var warps = _dataManager.GetExcelSheet<Warp>()! var warps = _dataManager.GetExcelSheet<Warp>()!
.Where(x => x.RowId > 0 && x.TerritoryType.Row == targetTerritoryId); .Where(x => x.RowId > 0 && x.TerritoryType.RowId == targetTerritoryId);
foreach (var entry in warps) foreach (var entry in warps)
{ {
string? excelName = entry.Name?.ToString(); string? excelName = entry.Name.ToString();
string? excelQuestion = entry.Question?.ToString(); string? excelQuestion = entry.Question.ToString();
if (excelQuestion != null && GameFunctions.GameStringEquals(excelQuestion, actualPrompt)) if (!string.IsNullOrEmpty(excelQuestion) && GameFunctions.GameStringEquals(excelQuestion, actualPrompt))
{ {
warpId = entry.RowId; warpId = entry.RowId;
warpText = excelQuestion; warpText = excelQuestion;
return true; return true;
} }
else if (excelName != null && GameFunctions.GameStringEquals(excelName, actualPrompt)) else if (!string.IsNullOrEmpty(excelName) && GameFunctions.GameStringEquals(excelName, actualPrompt))
{ {
warpId = entry.RowId; warpId = entry.RowId;
warpText = excelName; warpText = excelName;

View File

@ -12,7 +12,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.Game.Event;
using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Game.UI;
using LLib; using LLib;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps; using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Gathering; using Questionable.Controller.Steps.Gathering;

View File

@ -5,7 +5,7 @@ using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using LLib; using LLib;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps; using Questionable.Controller.Steps;

View File

@ -8,20 +8,14 @@ using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using LLib;
using LLib.GameData;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps; using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Shared; using Questionable.Controller.Steps.Shared;
using Questionable.Data;
using Questionable.External; using Questionable.External;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using Quest = Questionable.Model.Quest; using Quest = Questionable.Model.Quest;
using Mount = Questionable.Controller.Steps.Common.Mount;
namespace Questionable.Controller; namespace Questionable.Controller;

View File

@ -225,7 +225,7 @@ internal static class DoGather
private EAction PickAction(EAction minerAction, EAction botanistAction) private EAction PickAction(EAction minerAction, EAction botanistAction)
{ {
if ((EClassJob?)clientState.LocalPlayer?.ClassJob.Id == EClassJob.Miner) if ((EClassJob?)clientState.LocalPlayer?.ClassJob.RowId == EClassJob.Miner)
return minerAction; return minerAction;
else else
return botanistAction; return botanistAction;

View File

@ -193,7 +193,7 @@ internal static class DoGatherCollectable
private EAction PickAction(EAction minerAction, EAction botanistAction) private EAction PickAction(EAction minerAction, EAction botanistAction)
{ {
if ((EClassJob?)clientState.LocalPlayer?.ClassJob.Id == EClassJob.Miner) if ((EClassJob?)clientState.LocalPlayer?.ClassJob.RowId == EClassJob.Miner)
return minerAction; return minerAction;
else else
return botanistAction; return botanistAction;

View File

@ -5,8 +5,7 @@ using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using LLib; using LLib;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -63,13 +62,13 @@ internal static class EquipItem
]; ];
private int _attempts; private int _attempts;
private Item _item = null!; private Item? _item;
private List<ushort> _targetSlots = null!; private List<ushort> _targetSlots = null!;
private DateTime _continueAt = DateTime.MaxValue; private DateTime _continueAt = DateTime.MaxValue;
protected override bool Start() protected override bool Start()
{ {
_item = dataManager.GetExcelSheet<Item>()!.GetRow(Task.ItemId) ?? _item = dataManager.GetExcelSheet<Item>().GetRowOrDefault(Task.ItemId) ??
throw new ArgumentOutOfRangeException(nameof(Task.ItemId)); throw new ArgumentOutOfRangeException(nameof(Task.ItemId));
_targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment"); _targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment");
@ -118,7 +117,7 @@ internal static class EquipItem
var itemSlot = equippedContainer->GetInventorySlot(slot); var itemSlot = equippedContainer->GetInventorySlot(slot);
if (itemSlot != null && itemSlot->ItemId == Task.ItemId) if (itemSlot != null && itemSlot->ItemId == Task.ItemId)
{ {
logger.LogInformation("Already equipped {Item}, skipping step", _item.Name?.ToString()); logger.LogInformation("Already equipped {Item}, skipping step", _item?.Name.ToString());
return; return;
} }
} }
@ -162,11 +161,13 @@ internal static class EquipItem
throw new TaskException($"Could not equip item {Task.ItemId}."); throw new TaskException($"Could not equip item {Task.ItemId}.");
} }
private static List<ushort>? GetEquipSlot(Item item) private static List<ushort>? GetEquipSlot(Item? item)
{ {
return item.EquipSlotCategory.Row switch if (item == null)
return [];
return item.Value.EquipSlotCategory.RowId switch
{ {
>= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)], >= 1 and <= 11 => [(ushort)(item.Value.EquipSlotCategory.RowId - 1)],
12 => [11, 12], // rings 12 => [11, 12], // rings
13 => [0], 13 => [0],
17 => [13], // soul crystal 17 => [13], // soul crystal

View File

@ -45,7 +45,7 @@ internal static class EquipRecommended
protected override bool Start() protected override bool Start()
{ {
RecommendEquipModule.Instance()->SetupForClassJob((byte)clientState.LocalPlayer!.ClassJob.Id); RecommendEquipModule.Instance()->SetupForClassJob((byte)clientState.LocalPlayer!.ClassJob.RowId);
return true; return true;
} }

View File

@ -6,7 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.External; using Questionable.External;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -55,20 +55,20 @@ internal static class Craft
return false; return false;
} }
RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>()!.GetRow(Task.ItemId); RecipeLookup? recipeLookup = dataManager.GetExcelSheet<RecipeLookup>().GetRowOrDefault(Task.ItemId);
if (recipeLookup == null) if (recipeLookup == null)
throw new TaskException($"Item {Task.ItemId} is not craftable"); throw new TaskException($"Item {Task.ItemId} is not craftable");
uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.Id switch uint recipeId = (EClassJob)clientState.LocalPlayer!.ClassJob.RowId switch
{ {
EClassJob.Carpenter => recipeLookup.CRP.Row, EClassJob.Carpenter => recipeLookup.Value.CRP.RowId,
EClassJob.Blacksmith => recipeLookup.BSM.Row, EClassJob.Blacksmith => recipeLookup.Value.BSM.RowId,
EClassJob.Armorer => recipeLookup.ARM.Row, EClassJob.Armorer => recipeLookup.Value.ARM.RowId,
EClassJob.Goldsmith => recipeLookup.GSM.Row, EClassJob.Goldsmith => recipeLookup.Value.GSM.RowId,
EClassJob.Leatherworker => recipeLookup.LTW.Row, EClassJob.Leatherworker => recipeLookup.Value.LTW.RowId,
EClassJob.Weaver => recipeLookup.WVR.Row, EClassJob.Weaver => recipeLookup.Value.WVR.RowId,
EClassJob.Alchemist => recipeLookup.ALC.Row, EClassJob.Alchemist => recipeLookup.Value.ALC.RowId,
EClassJob.Culinarian => recipeLookup.CUL.Row, EClassJob.Culinarian => recipeLookup.Value.CUL.RowId,
_ => 0 _ => 0
}; };
@ -76,14 +76,14 @@ internal static class Craft
{ {
recipeId = new[] recipeId = new[]
{ {
recipeLookup.CRP.Row, recipeLookup.Value.CRP.RowId,
recipeLookup.BSM.Row, recipeLookup.Value.BSM.RowId,
recipeLookup.ARM.Row, recipeLookup.Value.ARM.RowId,
recipeLookup.GSM.Row, recipeLookup.Value.GSM.RowId,
recipeLookup.LTW.Row, recipeLookup.Value.LTW.RowId,
recipeLookup.WVR.Row, recipeLookup.Value.WVR.RowId,
recipeLookup.ALC.Row, recipeLookup.Value.ALC.RowId,
recipeLookup.WVR.Row recipeLookup.Value.WVR.RowId
} }
.FirstOrDefault(x => x != 0); .FirstOrDefault(x => x != 0);
} }

View File

@ -35,7 +35,7 @@ internal static class Gather
foreach (var itemToGather in step.ItemsToGather) foreach (var itemToGather in step.ItemsToGather)
{ {
EClassJob currentClassJob = (EClassJob)clientState.LocalPlayer!.ClassJob.Id; EClassJob currentClassJob = (EClassJob)clientState.LocalPlayer!.ClassJob.RowId;
if (!gatheringData.TryGetGatheringPointId(itemToGather.ItemId, currentClassJob, if (!gatheringData.TryGetGatheringPointId(itemToGather.ItemId, currentClassJob,
out GatheringPointId? gatheringPointId)) out GatheringPointId? gatheringPointId))
throw new TaskException($"No gathering point found for item {itemToGather.ItemId}"); throw new TaskException($"No gathering point found for item {itemToGather.ItemId}");

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Numerics; using System.Numerics;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
@ -10,7 +9,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using LLib; using LLib;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Common;
using Questionable.Data; using Questionable.Data;

View File

@ -239,7 +239,7 @@ internal static class SkipCondition
{ {
List<EClassJob> expectedJobs = List<EClassJob> expectedJobs =
step.RequiredCurrentJob.SelectMany(ClassJobUtils.AsIndividualJobs).ToList(); step.RequiredCurrentJob.SelectMany(ClassJobUtils.AsIndividualJobs).ToList();
EClassJob currentJob = (EClassJob)clientState.LocalPlayer!.ClassJob.Id; EClassJob currentJob = (EClassJob)clientState.LocalPlayer!.ClassJob.RowId;
logger.LogInformation("Checking current job {CurrentJob} against {ExpectedJobs}", currentJob, logger.LogInformation("Checking current job {CurrentJob} against {ExpectedJobs}", currentJob,
string.Join(",", expectedJobs)); string.Join(",", expectedJobs));
if (!expectedJobs.Contains(currentJob)) if (!expectedJobs.Contains(currentJob))

View File

@ -31,7 +31,7 @@ internal static class SwitchClassJob
{ {
protected override unsafe bool StartInternal() protected override unsafe bool StartInternal()
{ {
if (clientState.LocalPlayer!.ClassJob.Id == (uint)Task.ClassJob) if (clientState.LocalPlayer!.ClassJob.RowId == (uint)Task.ClassJob)
return false; return false;
var gearsetModule = RaptureGearsetModule.Instance(); var gearsetModule = RaptureGearsetModule.Instance();

View File

@ -1,7 +1,7 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets2; using Lumina.Excel.Sheets;
namespace Questionable.Data; namespace Questionable.Data;
@ -13,12 +13,12 @@ internal sealed class AetherCurrentData
{ {
_overworldCurrents = dataManager.GetExcelSheet<AetherCurrentCompFlgSet>()! _overworldCurrents = dataManager.GetExcelSheet<AetherCurrentCompFlgSet>()!
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Where(x => x.Territory.Value != null) .Where(x => x.Territory.IsValid)
.ToImmutableDictionary( .ToImmutableDictionary(
x => (ushort)x.Territory.Row, x => (ushort)x.Territory.RowId,
x => x.AetherCurrents x => x.AetherCurrents
.Where(y => y.Row > 0 && y.Value?.Quest.Row == 0) .Where(y => y.RowId > 0 && y.Value.Quest.RowId == 0)
.Select(y => y.Row) .Select(y => y.RowId)
.ToImmutableList()); .ToImmutableList());
} }

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Questionable.Model.Common; using Questionable.Model.Common;
namespace Questionable.Data; namespace Questionable.Data;
@ -29,19 +29,19 @@ internal sealed class AetheryteData
void ConfigureAetheryteWithPlaceName(EAetheryteLocation aetheryteLocation, uint placeNameId, ushort territoryId) void ConfigureAetheryteWithPlaceName(EAetheryteLocation aetheryteLocation, uint placeNameId, ushort territoryId)
{ {
ConfigureAetheryte(aetheryteLocation, ConfigureAetheryte(aetheryteLocation,
dataManager.GetExcelSheet<PlaceName>()!.GetRow(placeNameId)!.Name.ToDalamudString().TextValue, dataManager.GetExcelSheet<PlaceName>().GetRow(placeNameId).Name.ToDalamudString().TextValue,
territoryId, territoryId,
(ushort)((int)aetheryteLocation / 100)); (ushort)((int)aetheryteLocation / 100));
} }
foreach (var aetheryte in dataManager.GetExcelSheet<Aetheryte>()!.Where(x => x.RowId > 0)) foreach (var aetheryte in dataManager.GetExcelSheet<Aetheryte>().Where(x => x.RowId > 0))
{ {
string? aethernetName = aetheryte.AethernetName?.Value?.Name.ToString(); string? aethernetName = aetheryte.AethernetName.ValueNullable?.Name.ToString();
if (!string.IsNullOrEmpty(aethernetName)) if (!string.IsNullOrEmpty(aethernetName))
aethernetNames[(EAetheryteLocation)aetheryte.RowId] = aethernetName; aethernetNames[(EAetheryteLocation)aetheryte.RowId] = aethernetName;
if (aetheryte.Territory != null && aetheryte.Territory.Row > 0) if (aetheryte.Territory.RowId > 0)
territoryIds[(EAetheryteLocation)aetheryte.RowId] = (ushort)aetheryte.Territory.Row; territoryIds[(EAetheryteLocation)aetheryte.RowId] = (ushort)aetheryte.Territory.RowId;
if (aetheryte.AethernetGroup > 0) if (aetheryte.AethernetGroup > 0)
aethernetGroups[(EAetheryteLocation)aetheryte.RowId] = aetheryte.AethernetGroup; aethernetGroups[(EAetheryteLocation)aetheryte.RowId] = aetheryte.AethernetGroup;
@ -62,8 +62,8 @@ internal sealed class AetheryteData
TerritoryIds = territoryIds.AsReadOnly(); TerritoryIds = territoryIds.AsReadOnly();
AethernetGroups = aethernetGroups.AsReadOnly(); AethernetGroups = aethernetGroups.AsReadOnly();
TownTerritoryIds = dataManager.GetExcelSheet<TerritoryType>()! TownTerritoryIds = dataManager.GetExcelSheet<TerritoryType>()
.Where(x => x.RowId > 0 && !string.IsNullOrEmpty(x.Name) && x.TerritoryIntendedUse == 0) .Where(x => x.RowId > 0 && !string.IsNullOrEmpty(x.Name.ToString()) && x.TerritoryIntendedUse.RowId == 0)
.Select(x => (ushort)x.RowId) .Select(x => (ushort)x.RowId)
.ToList(); .ToList();
} }

View File

@ -3,8 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model.Gathering; using Questionable.Model.Gathering;
namespace Questionable.Data; namespace Questionable.Data;
@ -18,43 +17,45 @@ internal sealed class GatheringData
public GatheringData(IDataManager dataManager) public GatheringData(IDataManager dataManager)
{ {
Dictionary<uint, uint> gatheringItemToItem = dataManager.GetExcelSheet<GatheringItem>()! Dictionary<uint, uint> gatheringItemToItem = dataManager.GetExcelSheet<GatheringItem>()
.Where(x => x.RowId != 0 && x.Item != 0) .Where(x => x.RowId != 0 && x.Item.RowId != 0)
.ToDictionary(x => x.RowId, x => (uint)x.Item); .ToDictionary(x => x.RowId, x => x.Item.RowId);
foreach (var gatheringPointBase in dataManager.GetExcelSheet<GatheringPointBase>()!) foreach (var gatheringPointBase in dataManager.GetExcelSheet<GatheringPointBase>())
{ {
foreach (var gatheringItemId in gatheringPointBase.Item.Where(x => x != 0)) foreach (var gatheringItem in gatheringPointBase.Item.Where(x => x.RowId != 0))
{ {
if (gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId)) if (gatheringItemToItem.TryGetValue(gatheringItem.RowId, out uint itemId))
{ {
if (gatheringPointBase.GatheringType.Row is 0 or 1) if (gatheringPointBase.GatheringType.RowId is 0 or 1)
_minerGatheringPoints[itemId] = new GatheringPointId((ushort)gatheringPointBase.RowId); _minerGatheringPoints[itemId] = new GatheringPointId((ushort)gatheringPointBase.RowId);
else if (gatheringPointBase.GatheringType.Row is 2 or 3) else if (gatheringPointBase.GatheringType.RowId is 2 or 3)
_botanistGatheringPoints[itemId] = new GatheringPointId((ushort)gatheringPointBase.RowId); _botanistGatheringPoints[itemId] = new GatheringPointId((ushort)gatheringPointBase.RowId);
} }
} }
} }
_itemIdToCollectability = dataManager.GetExcelSheet<SatisfactionSupply>()! _itemIdToCollectability = dataManager.GetSubrowExcelSheet<SatisfactionSupply>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.SelectMany(x => x)
.Where(x => x.Slot is 2) .Where(x => x.Slot is 2)
.Select(x => new .Select(x => new
{ {
ItemId = x.Item.Row, ItemId = x.Item.RowId,
Collectability = x.CollectabilityHigh, Collectability = x.CollectabilityHigh,
}) })
.Distinct() .Distinct()
.ToDictionary(x => x.ItemId, x => x.Collectability); .ToDictionary(x => x.ItemId, x => x.Collectability);
_npcForCustomDeliveries = dataManager.GetExcelSheet<SatisfactionNpc>()! _npcForCustomDeliveries = dataManager.GetExcelSheet<SatisfactionNpc>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.SelectMany(x => dataManager.GetExcelSheet<SatisfactionSupply>()! .SelectMany(x => dataManager.GetSubrowExcelSheet<SatisfactionSupply>()
.Where(y => y.RowId == x.SupplyIndex.Last()) .Where(y => y.RowId == x.SatisfactionNpcParams.Last().SupplyIndex)
.SelectMany(y => y)
.Select(y => new .Select(y => new
{ {
ItemId = y.Item.Row, ItemId = y.Item.RowId,
NpcId = x.Npc.Row NpcId = x.Npc.RowId
})) }))
.Where(x => x.ItemId > 0) .Where(x => x.ItemId > 0)
.Distinct() .Distinct()

View File

@ -1,7 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -9,28 +10,36 @@ namespace Questionable.Data;
internal sealed class JournalData internal sealed class JournalData
{ {
public JournalData(IDataManager dataManager, QuestData questData) public JournalData(IDataManager dataManager, QuestData questData, ILogger<JournalData> logger)
{ {
var genres = dataManager.GetExcelSheet<JournalGenre>()! var genres = dataManager.GetExcelSheet<JournalGenre>()
.Where(x => x.RowId > 0 && x.Icon > 0) .Where(x => x.RowId > 0 && x.Icon > 0)
.Select(x => new Genre(x, questData.GetAllByJournalGenre(x.RowId))) .Select(x => new Genre(x, questData.GetAllByJournalGenre(x.RowId)))
.ToList(); .ToList();
foreach (var genre in genres)
{
logger.LogInformation("Genre {GenreId}: {GenreName} has {QuestCount} quests",
genre.Id, genre.Name, genre.QuestCount);
}
logger.LogInformation("Genre count: {GenreCount}", genres.Count);
var quest = questData.GetQuestInfo(new QuestId(5193));
logger.LogInformation("Q: {N}, {A}, {B}", quest.Name, quest.JournalGenre, quest.IssuerDataId);
var limsaStart = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(1)!; var limsaStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(1);
var gridaniaStart = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(2)!; var gridaniaStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(2);
var uldahStart = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(3)!; var uldahStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(3);
var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1, var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
new uint[] { 108, 109 }.Concat(limsaStart.Quest.Select(x => x.Row)) new uint[] { 108, 109 }.Concat(limsaStart.QuestRedoParam.Select(x => x.Quest.RowId))
.Where(x => x != 0) .Where(x => x != 0)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF)))) .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.ToList()); .ToList());
var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1, var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
new uint[] { 85, 123, 124 }.Concat(gridaniaStart.Quest.Select(x => x.Row)) new uint[] { 85, 123, 124 }.Concat(gridaniaStart.QuestRedoParam.Select(x => x.Quest.RowId))
.Where(x => x != 0) .Where(x => x != 0)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF)))) .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.ToList()); .ToList());
var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1, var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1,
new uint[] { 568, 569, 570 }.Concat(uldahStart.Quest.Select(x => x.Row)) new uint[] { 568, 569, 570 }.Concat(uldahStart.QuestRedoParam.Select(x => x.Quest.RowId))
.Where(x => x != 0) .Where(x => x != 0)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF)))) .Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.ToList()); .ToList());
@ -41,12 +50,12 @@ internal sealed class JournalData
genreLimsa.Quests.Contains(x) || genreGridania.Quests.Contains(x) || genreUldah.Quests.Contains(x)); genreLimsa.Quests.Contains(x) || genreGridania.Quests.Contains(x) || genreUldah.Quests.Contains(x));
Genres = genres.AsReadOnly(); Genres = genres.AsReadOnly();
Categories = dataManager.GetExcelSheet<JournalCategory>()! Categories = dataManager.GetExcelSheet<JournalCategory>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Select(x => new Category(x, Genres.Where(y => y.CategoryId == x.RowId).ToList())) .Select(x => new Category(x, Genres.Where(y => y.CategoryId == x.RowId).ToList()))
.ToList() .ToList()
.AsReadOnly(); .AsReadOnly();
Sections = dataManager.GetExcelSheet<JournalSection>()! Sections = dataManager.GetExcelSheet<JournalSection>()
.Select(x => new Section(x, Categories.Where(y => y.SectionId == x.RowId).ToList())) .Select(x => new Section(x, Categories.Where(y => y.SectionId == x.RowId).ToList()))
.ToList(); .ToList();
} }
@ -61,7 +70,7 @@ internal sealed class JournalData
{ {
Id = journalGenre.RowId; Id = journalGenre.RowId;
Name = journalGenre.Name.ToString(); Name = journalGenre.Name.ToString();
CategoryId = journalGenre.JournalCategory.Row; CategoryId = journalGenre.JournalCategory.RowId;
Quests = quests; Quests = quests;
} }
@ -84,7 +93,7 @@ internal sealed class JournalData
{ {
public uint Id { get; } = journalCategory.RowId; public uint Id { get; } = journalCategory.RowId;
public string Name { get; } = journalCategory.Name.ToString(); public string Name { get; } = journalCategory.Name.ToString();
public uint SectionId { get; } = journalCategory.JournalSection.Row; public uint SectionId { get; } = journalCategory.JournalSection.RowId;
public IReadOnlyList<Genre> Genres { get; } = genres; public IReadOnlyList<Genre> Genres { get; } = genres;
public int QuestCount => Genres.Sum(x => x.QuestCount); public int QuestCount => Genres.Sum(x => x.QuestCount);
} }

View File

@ -5,11 +5,11 @@ using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Game.UI;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Questionable.Data.Sheets;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using Leve = Lumina.Excel.GeneratedSheets2.Leve; using Quest = Lumina.Excel.Sheets.Quest;
using Quest = Lumina.Excel.GeneratedSheets2.Quest;
namespace Questionable.Data; namespace Questionable.Data;
@ -18,13 +18,13 @@ internal sealed class QuestData
public static readonly IReadOnlyList<QuestId> CrystalTowerQuests = public static readonly IReadOnlyList<QuestId> CrystalTowerQuests =
[new(1709), new(1200), new(1201), new(1202), new(1203), new(1474), new(494), new(495)]; [new(1709), new(1200), new(1201), new(1202), new(1203), new(1474), new(494), new(495)];
public static readonly IReadOnlyList<ushort> TankRoleQuests = [136, 154, 178]; public static readonly IReadOnlyList<uint> TankRoleQuests = [136, 154, 178];
public static readonly IReadOnlyList<ushort> HealerRoleQuests = [137, 155, 179]; public static readonly IReadOnlyList<uint> HealerRoleQuests = [137, 155, 179];
public static readonly IReadOnlyList<ushort> MeleeRoleQuests = [138, 156, 180]; public static readonly IReadOnlyList<uint> MeleeRoleQuests = [138, 156, 180];
public static readonly IReadOnlyList<ushort> PhysicalRangedRoleQuests = [138, 157, 181]; public static readonly IReadOnlyList<uint> PhysicalRangedRoleQuests = [138, 157, 181];
public static readonly IReadOnlyList<ushort> CasterRoleQuests = [139, 158, 182]; public static readonly IReadOnlyList<uint> CasterRoleQuests = [139, 158, 182];
public static readonly IReadOnlyList<IReadOnlyList<ushort>> AllRoleQuestChapters = public static readonly IReadOnlyList<IReadOnlyList<uint>> AllRoleQuestChapters =
[ [
TankRoleQuests, TankRoleQuests,
HealerRoleQuests, HealerRoleQuests,
@ -40,33 +40,33 @@ internal sealed class QuestData
public QuestData(IDataManager dataManager) public QuestData(IDataManager dataManager)
{ {
Dictionary<uint, ushort> questChapters = Dictionary<uint, uint> questChapters =
dataManager.GetExcelSheet<QuestChapter>()! dataManager.GetExcelSheet<QuestChapter>()!
.Where(x => x.RowId > 0 && x.Quest.Row > 0) .Where(x => x.RowId > 0 && x.Quest.RowId > 0)
.ToDictionary(x => x.Quest.Row, x => x.Redo); .ToDictionary(x => x.Quest.RowId, x => x.Redo.RowId);
Dictionary<uint, byte> startingCities = new(); Dictionary<uint, byte> startingCities = new();
for (byte redoChapter = 1; redoChapter <= 3; ++redoChapter) for (byte redoChapter = 1; redoChapter <= 3; ++redoChapter)
{ {
var questRedo = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(redoChapter)!; var questRedo = dataManager.GetExcelSheet<QuestRedo>().GetRow(redoChapter);
foreach (var quest in questRedo.Quest.Where(x => x.Row > 0)) foreach (var quest in questRedo.QuestRedoParam.Where(x => x.Quest.IsValid))
startingCities[quest.Row] = redoChapter; startingCities[quest.Quest.RowId] = redoChapter;
} }
List<IQuestInfo> quests = List<IQuestInfo> quests =
[ [
..dataManager.GetExcelSheet<Quest>()! ..dataManager.GetExcelSheet<QuestEx>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Where(x => x.IssuerLocation.Row > 0) .Where(x => x.IssuerLocation.RowId > 0)
.Select(x => new QuestInfo(x, questChapters.GetValueOrDefault(x.RowId), .Select(x => new QuestInfo(x, questChapters.GetValueOrDefault(x.RowId),
startingCities.GetValueOrDefault(x.RowId))) startingCities.GetValueOrDefault(x.RowId)))
.Where(x => x.QuestId.Value != 1428), .Where(x => x.QuestId.Value != 1428),
..dataManager.GetExcelSheet<SatisfactionNpc>()! ..dataManager.GetExcelSheet<SatisfactionNpc>()
.Where(x => x.RowId > 0) .Where(x => x is { RowId: > 0, Npc.RowId: > 0 })
.Select(x => new SatisfactionSupplyInfo(x)), .Select(x => new SatisfactionSupplyInfo(x)),
..dataManager.GetExcelSheet<Leve>()! ..dataManager.GetExcelSheet<Leve>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Where(x => x.LevelLevemete.Row != 0) .Where(x => x.LevelLevemete.RowId != 0)
.Select(x => new LeveInfo(x)), .Select(x => new LeveInfo(x)),
]; ];
_quests = quests.ToDictionary(x => x.QuestId, x => x); _quests = quests.ToDictionary(x => x.QuestId, x => x);
@ -230,7 +230,7 @@ internal sealed class QuestData
public List<QuestInfo> GetClassJobQuests(EClassJob classJob) public List<QuestInfo> GetClassJobQuests(EClassJob classJob)
{ {
List<ushort> chapterIds = classJob switch List<uint> chapterIds = classJob switch
{ {
EClassJob.Adventurer => throw new ArgumentOutOfRangeException(nameof(classJob)), EClassJob.Adventurer => throw new ArgumentOutOfRangeException(nameof(classJob)),
@ -308,7 +308,7 @@ internal sealed class QuestData
return GetQuestsInNewGamePlusChapters(chapterIds); return GetQuestsInNewGamePlusChapters(chapterIds);
} }
private List<QuestInfo> GetQuestsInNewGamePlusChapters(List<ushort> chapterIds) private List<QuestInfo> GetQuestsInNewGamePlusChapters(List<uint> chapterIds)
{ {
return _quests.Values return _quests.Values
.Where(x => x is QuestInfo) .Where(x => x is QuestInfo)

View File

@ -0,0 +1,45 @@
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Lumina.Text.ReadOnly;
namespace Questionable.Data.Sheets;
// TODO Remove once fixed in dalamud
[Sheet("Quest", 0x1F8C7430)]
public readonly unsafe struct QuestEx(ExcelPage page, uint offset, uint row) : IExcelRow<QuestEx>
{
public uint RowId => row;
public Quest Original { get; } = new(page, offset, row);
public RowRef IssuerStart => RowRef.GetFirstValidRowOrUntyped(page.Module, page.ReadUInt32(offset + 2456), [typeof(EObjName), typeof(ENpcResident)], 882056187, page.Language);
public RowRef<Level> IssuerLocation => new(page.Module, page.ReadUInt32(offset + 2460), page.Language);
public RowRef<JournalGenre> JournalGenre => new(page.Module, page.ReadUInt32(offset + 2468), page.Language);
public ushort SortKey => page.ReadUInt16(offset + 2502);
public readonly RowRef<ExVersion> Expansion => new(page.Module, (uint)page.ReadUInt8(offset + 2504), page.Language);
public readonly byte PreviousQuestJoin => page.ReadUInt8(offset + 2508);
public readonly RowRef<ClassJobCategory> ClassJobCategory0 => new(page.Module, (uint)page.ReadUInt8(offset + 2505), page.Language);
public readonly RowRef<ClassJobCategory> ClassJobCategory1 => new(page.Module, (uint)page.ReadUInt8(offset + 2507), page.Language);
public readonly RowRef<Festival> Festival => new(page.Module, (uint)page.ReadUInt8(offset + 2517), page.Language);
public readonly byte Unknown7 => page.ReadUInt8(offset + 2509);
public readonly byte QuestLockJoin => page.ReadUInt8(offset + 2510);
public readonly RowRef<GrandCompany> GrandCompany => new(page.Module, (uint)page.ReadUInt8(offset + 2514), page.Language);
public readonly byte InstanceContentJoin => page.ReadUInt8(offset + 2516);
public readonly RowRef<BeastTribe> BeastTribe => new(page.Module, (uint)page.ReadUInt8(offset + 2520), page.Language);
public bool IsRepeatable => page.ReadPackedBool(offset + 2535, 1);
public readonly Collection<RowRef<Quest>> PreviousQuest => new(page, offset, offset, &PreviousQuestCtor, 3);
private static RowRef<Quest> PreviousQuestCtor(ExcelPage page, uint parentOffset, uint offset, uint i) => new(page.Module, page.ReadUInt32(offset + 2424 + i * 4), page.Language);
public readonly Collection<RowRef<Quest>> QuestLock => new(page, offset, offset, &QuestLockCtor, 2);
private static RowRef<Quest> QuestLockCtor(ExcelPage page, uint parentOffset, uint offset, uint i) => new(page.Module, page.ReadUInt32(offset + 2436 + i * 4), page.Language);
public readonly Collection<ushort> ClassJobLevel => new(page, offset, offset, &ClassJobLevelCtor, 2);
private static ushort ClassJobLevelCtor(ExcelPage page, uint parentOffset, uint offset, uint i) => page.ReadUInt16(offset + 2484 + i * 2);
public Collection<RowRef<InstanceContent>> InstanceContent => new(page, offset, offset, &InstanceContentCtor, 3);
private static RowRef<InstanceContent> InstanceContentCtor(ExcelPage page, uint parentOffset, uint offset, uint i) => new(page.Module, page.ReadUInt32(offset + 2444 + i * 4), page.Language);
static QuestEx IExcelRow<QuestEx>.Create(ExcelPage page, uint offset, uint row) =>
new(page, offset, row);
}

View File

@ -4,7 +4,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
namespace Questionable.Data; namespace Questionable.Data;
@ -13,37 +13,37 @@ internal sealed class TerritoryData
private readonly ImmutableDictionary<uint, string> _territoryNames; private readonly ImmutableDictionary<uint, string> _territoryNames;
private readonly ImmutableHashSet<ushort> _territoriesWithMount; private readonly ImmutableHashSet<ushort> _territoriesWithMount;
private readonly ImmutableDictionary<ushort, uint> _dutyTerritories; private readonly ImmutableDictionary<ushort, uint> _dutyTerritories;
private readonly ImmutableDictionary<ushort, string> _instanceNames; private readonly ImmutableDictionary<uint, string> _instanceNames;
private readonly ImmutableDictionary<uint, string> _contentFinderConditionNames; private readonly ImmutableDictionary<uint, string> _contentFinderConditionNames;
public TerritoryData(IDataManager dataManager) public TerritoryData(IDataManager dataManager)
{ {
_territoryNames = dataManager.GetExcelSheet<TerritoryType>()! _territoryNames = dataManager.GetExcelSheet<TerritoryType>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Select(x => .Select(x =>
new new
{ {
x.RowId, x.RowId,
Name = x.PlaceName.Value?.Name?.ToString() ?? x.PlaceNameZone?.Value?.Name?.ToString(), Name = x.PlaceName.ValueNullable?.Name.ToString() ?? x.PlaceNameZone.ValueNullable?.Name.ToString(),
}) })
.Where(x => !string.IsNullOrEmpty(x.Name)) .Where(x => !string.IsNullOrEmpty(x.Name))
.ToImmutableDictionary(x => x.RowId, x => x.Name!); .ToImmutableDictionary(x => x.RowId, x => x.Name!);
_territoriesWithMount = dataManager.GetExcelSheet<TerritoryType>()! _territoriesWithMount = dataManager.GetExcelSheet<TerritoryType>()
.Where(x => x.RowId > 0 && x.Mount) .Where(x => x.RowId > 0 && x.Mount)
.Select(x => (ushort)x.RowId) .Select(x => (ushort)x.RowId)
.ToImmutableHashSet(); .ToImmutableHashSet();
_dutyTerritories = dataManager.GetExcelSheet<TerritoryType>()! _dutyTerritories = dataManager.GetExcelSheet<TerritoryType>()
.Where(x => x.RowId > 0 && x.ContentFinderCondition.Row != 0) .Where(x => x.RowId > 0 && x.ContentFinderCondition.RowId != 0)
.ToImmutableDictionary(x => (ushort)x.RowId, x => x.ContentFinderCondition.Value!.ContentType.Row); .ToImmutableDictionary(x => (ushort)x.RowId, x => x.ContentFinderCondition.Value.ContentType.RowId);
_instanceNames = dataManager.GetExcelSheet<ContentFinderCondition>()! _instanceNames = dataManager.GetExcelSheet<ContentFinderCondition>()
.Where(x => x.RowId > 0 && x.Content != 0 && x.ContentLinkType == 1 && x.ContentType.Row != 6) .Where(x => x.RowId > 0 && x.Content.RowId != 0 && x.ContentLinkType == 1 && x.ContentType.RowId != 6)
.ToImmutableDictionary(x => x.Content, x => x.Name.ToString()); .ToImmutableDictionary(x => x.Content.RowId, x => x.Name.ToString());
_contentFinderConditionNames = dataManager.GetExcelSheet<ContentFinderCondition>()! _contentFinderConditionNames = dataManager.GetExcelSheet<ContentFinderCondition>()
.Where(x => x.RowId > 0 && x.Content != 0 && x.ContentLinkType == 1 && x.ContentType.Row != 6) .Where(x => x.RowId > 0 && x.Content.RowId != 0 && x.ContentLinkType == 1 && x.ContentType.RowId != 6)
.ToImmutableDictionary(x => x.RowId, x => x.Name.ToString()); .ToImmutableDictionary(x => x.RowId, x => x.Name.ToString());
} }

View File

@ -1,14 +1,12 @@
using System; using System;
using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Model.Common; using Questionable.Model.Common;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using Action = Lumina.Excel.GeneratedSheets.Action; using Action = Lumina.Excel.Sheets.Action;
namespace Questionable.Functions; namespace Questionable.Functions;
@ -57,9 +55,10 @@ internal sealed unsafe class AetheryteFunctions
public bool IsTeleportUnlocked() public bool IsTeleportUnlocked()
{ {
uint unlockLink = _dataManager.GetExcelSheet<Action>()! uint unlockLink = _dataManager.GetExcelSheet<Action>()
.GetRow(5)! .GetRow(5)
.UnlockLink; .UnlockLink
.RowId;
return UIState.Instance()->IsUnlockLinkUnlocked(unlockLink); return UIState.Instance()->IsUnlockLinkUnlocked(unlockLink);
} }

View File

@ -12,7 +12,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.System.Memory; using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.System.String;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -41,11 +41,11 @@ internal sealed unsafe class ChatFunctions
_sanitiseString = _sanitiseString =
(delegate* unmanaged<Utf8String*, int, IntPtr, void>)sigScanner.ScanText(Signatures.SanitiseString); (delegate* unmanaged<Utf8String*, int, IntPtr, void>)sigScanner.ScanText(Signatures.SanitiseString);
_emoteCommands = dataManager.GetExcelSheet<Emote>()! _emoteCommands = dataManager.GetExcelSheet<Emote>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Where(x => x.TextCommand != null && x.TextCommand.Value != null) .Where(x => x.TextCommand.IsValid)
.Select(x => (x.RowId, Command: x.TextCommand.Value!.Command?.ToString())) .Select(x => (x.RowId, Command: x.TextCommand.Value.Command.ToString()))
.Where(x => x.Command != null && x.Command.StartsWith('/')) .Where(x => !string.IsNullOrEmpty(x.Command) && x.Command.StartsWith('/'))
.ToDictionary(x => (EEmote)x.RowId, x => x.Command!) .ToDictionary(x => (EEmote)x.RowId, x => x.Command!)
.AsReadOnly(); .AsReadOnly();
} }
@ -156,8 +156,8 @@ internal sealed unsafe class ChatFunctions
private static class Signatures private static class Signatures
{ {
internal const string SendChat = "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9"; internal const string SendChat = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F2 48 8B F9 45 84 C9";
internal const string SanitiseString = "E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 0F B6 F0 E8 ?? ?? ?? ?? 48 8D 4D C0"; internal const string SanitiseString = "E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D AE";
} }
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]

View File

@ -3,13 +3,13 @@ using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using LLib; using LLib;
using Lumina.Excel.CustomSheets; using Lumina.Excel.Sheets;
using Lumina.Excel.GeneratedSheets;
using Lumina.Text; using Lumina.Text;
using Lumina.Text.ReadOnly;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Model; using Questionable.Model;
using Quest = Questionable.Model.Quest; using Quest = Questionable.Model.Quest;
using GimmickYesNo = Lumina.Excel.GeneratedSheets2.GimmickYesNo; using GimmickYesNo = Lumina.Excel.Sheets.GimmickYesNo;
namespace Questionable.Functions; namespace Questionable.Functions;
@ -33,12 +33,12 @@ internal sealed class ExcelFunctions
return new StringOrRegex(seString?.ToDalamudString().ToString()); return new StringOrRegex(seString?.ToDalamudString().ToString());
} }
public SeString? GetRawDialogueText(Quest? currentQuest, string? excelSheetName, string key) public ReadOnlySeString? GetRawDialogueText(Quest? currentQuest, string? excelSheetName, string key)
{ {
if (currentQuest != null && excelSheetName == null) if (currentQuest != null && excelSheetName == null)
{ {
var questRow = var questRow =
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.Id.Value + _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>().GetRowOrDefault((uint)currentQuest.Id.Value +
0x10000); 0x10000);
if (questRow == null) if (questRow == null)
{ {
@ -46,18 +46,13 @@ internal sealed class ExcelFunctions
return null; return null;
} }
excelSheetName = $"quest/{(currentQuest.Id.Value / 100):000}/{questRow.Id}"; excelSheetName = $"quest/{(currentQuest.Id.Value / 100):000}/{questRow.Value.RowId}";
} }
ArgumentNullException.ThrowIfNull(excelSheetName); ArgumentNullException.ThrowIfNull(excelSheetName);
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName); var excelSheet = _dataManager.GetExcelSheet<QuestDialogueText>(name: excelSheetName);
if (excelSheet == null) return excelSheet.Cast<QuestDialogueText?>()
{ .FirstOrDefault(x => x!.Value.Key == key)?.Value;
_logger.LogError("Unknown excel sheet '{SheetName}'", excelSheetName);
return null;
}
return excelSheet.FirstOrDefault(x => x.Key == key)?.Value;
} }
public StringOrRegex GetDialogueTextByRowId(string? excelSheet, uint rowId, bool isRegex) public StringOrRegex GetDialogueTextByRowId(string? excelSheet, uint rowId, bool isRegex)
@ -69,36 +64,36 @@ internal sealed class ExcelFunctions
return new StringOrRegex(seString?.ToDalamudString().ToString()); return new StringOrRegex(seString?.ToDalamudString().ToString());
} }
public SeString? GetRawDialogueTextByRowId(string? excelSheet, uint rowId) public ReadOnlySeString? GetRawDialogueTextByRowId(string? excelSheet, uint rowId)
{ {
if (excelSheet == "GimmickYesNo") if (excelSheet == "GimmickYesNo")
{ {
var questRow = _dataManager.GetExcelSheet<GimmickYesNo>()!.GetRow(rowId); var questRow = _dataManager.GetExcelSheet<GimmickYesNo>().GetRowOrDefault(rowId);
return questRow?.Unknown0; return questRow?.Unknown0;
} }
else if (excelSheet == "Warp") else if (excelSheet == "Warp")
{ {
var questRow = _dataManager.GetExcelSheet<Warp>()!.GetRow(rowId); var questRow = _dataManager.GetExcelSheet<Warp>().GetRowOrDefault(rowId);
return questRow?.Name; return questRow?.Name;
} }
else if (excelSheet is "Addon") else if (excelSheet is "Addon")
{ {
var questRow = _dataManager.GetExcelSheet<Addon>()!.GetRow(rowId); var questRow = _dataManager.GetExcelSheet<Addon>().GetRowOrDefault(rowId);
return questRow?.Text; return questRow?.Text;
} }
else if (excelSheet is "EventPathMove") else if (excelSheet is "EventPathMove")
{ {
var questRow = _dataManager.GetExcelSheet<EventPathMove>()!.GetRow(rowId); var questRow = _dataManager.GetExcelSheet<EventPathMove>().GetRowOrDefault(rowId);
return questRow?.Unknown10; return questRow?.Unknown0;
} }
else if (excelSheet is "GilShop") else if (excelSheet is "GilShop")
{ {
var questRow = _dataManager.GetExcelSheet<GilShop>()!.GetRow(rowId); var questRow = _dataManager.GetExcelSheet<GilShop>().GetRowOrDefault(rowId);
return questRow?.Name; return questRow?.Name;
} }
else if (excelSheet is "ContentTalk" or null) else if (excelSheet is "ContentTalk" or null)
{ {
var questRow = _dataManager.GetExcelSheet<ContentTalk>()!.GetRow(rowId); var questRow = _dataManager.GetExcelSheet<ContentTalk>().GetRowOrDefault(rowId);
return questRow?.Text; return questRow?.Text;
} }
else else

View File

@ -14,23 +14,22 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI; using LLib.GameUI;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Common;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using Action = Lumina.Excel.GeneratedSheets2.Action; using Action = Lumina.Excel.Sheets.Action;
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara; using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
using ContentFinderCondition = Lumina.Excel.GeneratedSheets.ContentFinderCondition; using ContentFinderCondition = Lumina.Excel.Sheets.ContentFinderCondition;
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
using Quest = Questionable.Model.Quest; using Quest = Questionable.Model.Quest;
using TerritoryType = Lumina.Excel.GeneratedSheets.TerritoryType;
namespace Questionable.Functions; namespace Questionable.Functions;
internal sealed unsafe class GameFunctions internal sealed unsafe class GameFunctions
{ {
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet; private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet;
private readonly ReadOnlyDictionary<uint, ushort> _contentFinderConditionToContentId; private readonly ReadOnlyDictionary<uint, uint> _contentFinderConditionToContentId;
private readonly QuestFunctions _questFunctions; private readonly QuestFunctions _questFunctions;
private readonly IDataManager _dataManager; private readonly IDataManager _dataManager;
@ -63,14 +62,15 @@ internal sealed unsafe class GameFunctions
_configuration = configuration; _configuration = configuration;
_logger = logger; _logger = logger;
_territoryToAetherCurrentCompFlgSet = dataManager.GetExcelSheet<TerritoryType>()! _territoryToAetherCurrentCompFlgSet = dataManager.GetExcelSheet<TerritoryType>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Where(x => x.Unknown32 > 0) .Where(x => x.Unknown3 > 0)
.ToDictionary(x => (ushort)x.RowId, x => x.Unknown32) .ToDictionary(x => (ushort)x.RowId, x => x.Unknown4)
.AsReadOnly(); .AsReadOnly();
_contentFinderConditionToContentId = dataManager.GetExcelSheet<ContentFinderCondition>()! _territoryToAetherCurrentCompFlgSet = new Dictionary<ushort, byte>().AsReadOnly();
.Where(x => x.RowId > 0 && x.Content > 0) _contentFinderConditionToContentId = dataManager.GetExcelSheet<ContentFinderCondition>()
.ToDictionary(x => x.RowId, x => x.Content) .Where(x => x.RowId > 0 && x.Content.RowId > 0)
.ToDictionary(x => x.RowId, x => x.Content.RowId)
.AsReadOnly(); .AsReadOnly();
} }
@ -220,7 +220,7 @@ internal sealed unsafe class GameFunctions
public bool UseAction(IGameObject gameObject, EAction action, bool checkCanUse = true) public bool UseAction(IGameObject gameObject, EAction action, bool checkCanUse = true)
{ {
var actionRow = _dataManager.GetExcelSheet<Action>()!.GetRow((uint)action)!; var actionRow = _dataManager.GetExcelSheet<Action>().GetRow((uint)action);
if (checkCanUse && !ActionManager.CanUseActionOnTarget((uint)action, (GameObject*)gameObject.Address)) if (checkCanUse && !ActionManager.CanUseActionOnTarget((uint)action, (GameObject*)gameObject.Address))
{ {
_logger.LogWarning("Can not use action {Action} on target {Target}", action, gameObject); _logger.LogWarning("Can not use action {Action} on target {Target}", action, gameObject);
@ -378,7 +378,7 @@ internal sealed unsafe class GameFunctions
public void OpenDutyFinder(uint contentFinderConditionId) public void OpenDutyFinder(uint contentFinderConditionId)
{ {
if (_contentFinderConditionToContentId.TryGetValue(contentFinderConditionId, out ushort contentId)) if (_contentFinderConditionToContentId.TryGetValue(contentFinderConditionId, out uint contentId))
{ {
if (UIState.IsInstanceContentUnlocked(contentId)) if (UIState.IsInstanceContentUnlocked(contentId))
AgentContentsFinder.Instance()->OpenRegularDuty(contentFinderConditionId); AgentContentsFinder.Instance()->OpenRegularDuty(contentFinderConditionId);

View File

@ -11,7 +11,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameData; using LLib.GameData;
using LLib.GameUI; using LLib.GameUI;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Controller.Steps.Interactions; using Questionable.Controller.Steps.Interactions;
using Questionable.Data; using Questionable.Data;
@ -311,8 +311,8 @@ internal sealed unsafe class QuestFunctions
..QuestData.CrystalTowerQuests ..QuestData.CrystalTowerQuests
]; ];
EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.Id ?? EClassJob.Adventurer; EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer;
ushort[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray(); uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray();
if (classJob != EClassJob.Adventurer) if (classJob != EClassJob.Adventurer)
{ {
priorityQuests.AddRange(_questRegistry.GetKnownClassJobQuests(classJob) priorityQuests.AddRange(_questRegistry.GetKnownClassJobQuests(classJob)
@ -460,7 +460,7 @@ internal sealed unsafe class QuestFunctions
// this only checks for the current class // this only checks for the current class
IQuestInfo questInfo = _questData.GetQuestInfo(leveId); IQuestInfo questInfo = _questData.GetQuestInfo(leveId);
if (!questInfo.ClassJobs.Contains((EClassJob)_clientState.LocalPlayer!.ClassJob.Id) || if (!questInfo.ClassJobs.Contains((EClassJob)_clientState.LocalPlayer!.ClassJob.RowId) ||
questInfo.Level > _clientState.LocalPlayer.Level) questInfo.Level > _clientState.LocalPlayer.Level)
return true; return true;
@ -597,7 +597,7 @@ internal sealed unsafe class QuestFunctions
public bool IsClassJobUnlocked(EClassJob classJob) public bool IsClassJobUnlocked(EClassJob classJob)
{ {
var classJobRow = _dataManager.GetExcelSheet<ClassJob>()!.GetRow((uint)classJob)!; var classJobRow = _dataManager.GetExcelSheet<ClassJob>()!.GetRow((uint)classJob)!;
var questId = (ushort)classJobRow.UnlockQuest.Row; var questId = (ushort)classJobRow.UnlockQuest.RowId;
if (questId != 0) if (questId != 0)
return IsQuestComplete(new QuestId(questId)); return IsQuestComplete(new QuestId(questId));
@ -608,7 +608,7 @@ internal sealed unsafe class QuestFunctions
public bool IsJobUnlocked(EClassJob classJob) public bool IsJobUnlocked(EClassJob classJob)
{ {
var classJobRow = _dataManager.GetExcelSheet<ClassJob>()!.GetRow((uint)classJob)!; var classJobRow = _dataManager.GetExcelSheet<ClassJob>()!.GetRow((uint)classJob)!;
return IsClassJobUnlocked((EClassJob)classJobRow.ClassJobParent.Row); return IsClassJobUnlocked((EClassJob)classJobRow.ClassJobParent.RowId);
} }
public GrandCompany GetGrandCompany() public GrandCompany GetGrandCompany()

View File

@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets2; using Lumina.Excel.Sheets;
using Questionable.Model.Questing; using Questionable.Model.Questing;
namespace Questionable.Model; namespace Questionable.Model;
@ -11,13 +11,13 @@ internal sealed class LeveInfo : IQuestInfo
public LeveInfo(Leve leve) public LeveInfo(Leve leve)
{ {
QuestId = new LeveId((ushort)leve.RowId); QuestId = new LeveId((ushort)leve.RowId);
Name = leve.Name; Name = leve.Name.ToString();
Level = leve.ClassJobLevel; Level = leve.ClassJobLevel;
JournalGenre = leve.JournalGenre.Row; JournalGenre = leve.JournalGenre.RowId;
SortKey = QuestId.Value; SortKey = QuestId.Value;
IssuerDataId = leve.LevelLevemete.Value!.Object.Row; IssuerDataId = leve.LevelLevemete.Value.Object.RowId;
ClassJobs = QuestInfoUtils.AsList(leve.ClassJobCategory.Value!); ClassJobs = QuestInfoUtils.AsList(leve.ClassJobCategory.Value);
Expansion = (EExpansionVersion)leve.LevelLevemete.Value.Territory.Value!.ExVersion.Row; Expansion = (EExpansionVersion)leve.LevelLevemete.Value.Territory.Value.ExVersion.RowId;
} }
public ElementId QuestId { get; } public ElementId QuestId { get; }

View File

@ -3,16 +3,15 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using JetBrains.Annotations;
using LLib.GameData; using LLib.GameData;
using Questionable.Data.Sheets;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using ExcelQuest = Lumina.Excel.GeneratedSheets2.Quest;
namespace Questionable.Model; namespace Questionable.Model;
internal sealed class QuestInfo : IQuestInfo internal sealed class QuestInfo : IQuestInfo
{ {
public QuestInfo(ExcelQuest quest, ushort newGamePlusChapter, byte startingCity) public QuestInfo(QuestEx quest, uint newGamePlusChapter, byte startingCity)
{ {
QuestId = new QuestId((ushort)(quest.RowId & 0xFFFF)); QuestId = new QuestId((ushort)(quest.RowId & 0xFFFF));
@ -34,38 +33,38 @@ internal sealed class QuestInfo : IQuestInfo
_ => "", _ => "",
}; };
Name = $"{quest.Name}{suffix}"; Name = $"{quest.Original.Name}{suffix}";
Level = quest.ClassJobLevel[0]; Level = quest.ClassJobLevel[0];
IssuerDataId = quest.IssuerStart.Row; IssuerDataId = quest.IssuerStart.RowId;
IsRepeatable = quest.IsRepeatable; IsRepeatable = quest.IsRepeatable;
PreviousQuests = PreviousQuests =
new List<PreviousQuestInfo> new List<PreviousQuestInfo>
{ {
new(new QuestId((ushort)(quest.PreviousQuest[0].Row & 0xFFFF)), quest.Unknown7), new(new QuestId((ushort)(quest.PreviousQuest[0].RowId & 0xFFFF)), quest.Unknown7),
new(new QuestId((ushort)(quest.PreviousQuest[1].Row & 0xFFFF))), new(new QuestId((ushort)(quest.PreviousQuest[1].RowId & 0xFFFF))),
new(new QuestId((ushort)(quest.PreviousQuest[2].Row & 0xFFFF))) new(new QuestId((ushort)(quest.PreviousQuest[2].RowId & 0xFFFF)))
} }
.Where(x => x.QuestId.Value != 0) .Where(x => x.QuestId.Value != 0)
.ToImmutableList(); .ToImmutableList();
PreviousQuestJoin = (EQuestJoin)quest.PreviousQuestJoin; PreviousQuestJoin = (EQuestJoin)quest.PreviousQuestJoin;
QuestLocks = quest.QuestLock QuestLocks = quest.QuestLock
.Select(x => new QuestId((ushort)(x.Row & 0xFFFFF))) .Select(x => new QuestId((ushort)(x.RowId & 0xFFFFF)))
.Where(x => x.Value != 0) .Where(x => x.Value != 0)
.ToImmutableList(); .ToImmutableList();
QuestLockJoin = (EQuestJoin)quest.QuestLockJoin; QuestLockJoin = (EQuestJoin)quest.QuestLockJoin;
JournalGenre = quest.JournalGenre?.Row; JournalGenre = quest.JournalGenre.ValueNullable?.RowId;
SortKey = quest.SortKey; SortKey = quest.SortKey;
IsMainScenarioQuest = quest.JournalGenre?.Value?.JournalCategory?.Value?.JournalSection?.Row is 0 or 1; IsMainScenarioQuest = quest.JournalGenre.ValueNullable?.JournalCategory.ValueNullable?.JournalSection.ValueNullable?.RowId is 0 or 1;
CompletesInstantly = quest.TodoParams[0].ToDoCompleteSeq == 0; CompletesInstantly = quest.Original.TodoParams[0].ToDoCompleteSeq == 0;
PreviousInstanceContent = quest.InstanceContent.Select(x => (ushort)x.Row).Where(x => x != 0).ToList(); PreviousInstanceContent = quest.InstanceContent.Select(x => (ushort)x.RowId).Where(x => x != 0).ToList();
PreviousInstanceContentJoin = (EQuestJoin)quest.InstanceContentJoin; PreviousInstanceContentJoin = (EQuestJoin)quest.InstanceContentJoin;
GrandCompany = (GrandCompany)quest.GrandCompany.Row; GrandCompany = (GrandCompany)quest.GrandCompany.RowId;
AlliedSociety = (EAlliedSociety)quest.BeastTribe.Row; AlliedSociety = (EAlliedSociety)quest.BeastTribe.RowId;
ClassJobs = QuestInfoUtils.AsList(quest.ClassJobCategory0.Value!); ClassJobs = QuestInfoUtils.AsList(quest.ClassJobCategory0.ValueNullable!);
IsSeasonalEvent = quest.Festival.Row != 0; IsSeasonalEvent = quest.Festival.RowId != 0;
NewGamePlusChapter = newGamePlusChapter; NewGamePlusChapter = newGamePlusChapter;
StartingCity = startingCity; StartingCity = startingCity;
Expansion = (EExpansionVersion)quest.Expansion.Row; Expansion = (EExpansionVersion)quest.Expansion.RowId;
} }
@ -88,7 +87,7 @@ internal sealed class QuestInfo : IQuestInfo
public EAlliedSociety AlliedSociety { get; } public EAlliedSociety AlliedSociety { get; }
public IReadOnlyList<EClassJob> ClassJobs { get; } public IReadOnlyList<EClassJob> ClassJobs { get; }
public bool IsSeasonalEvent { get; } public bool IsSeasonalEvent { get; }
public ushort NewGamePlusChapter { get; } public uint NewGamePlusChapter { get; }
public byte StartingCity { get; set; } public byte StartingCity { get; set; }
public EExpansionVersion Expansion { get; } public EExpansionVersion Expansion { get; }

View File

@ -1,7 +1,8 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets2; using Lumina.Excel.Sheets;
namespace Questionable.Model; namespace Questionable.Model;
@ -9,8 +10,12 @@ internal static class QuestInfoUtils
{ {
private static readonly Dictionary<uint, IReadOnlyList<EClassJob>> CachedClassJobs = new(); private static readonly Dictionary<uint, IReadOnlyList<EClassJob>> CachedClassJobs = new();
internal static IReadOnlyList<EClassJob> AsList(ClassJobCategory classJobCategory) internal static IReadOnlyList<EClassJob> AsList(ClassJobCategory? optionalClassJobCategory)
{ {
if (optionalClassJobCategory == null)
return Enum.GetValues<EClassJob>();
ClassJobCategory classJobCategory = optionalClassJobCategory.Value;
if (CachedClassJobs.TryGetValue(classJobCategory.RowId, out IReadOnlyList<EClassJob>? classJobs)) if (CachedClassJobs.TryGetValue(classJobCategory.RowId, out IReadOnlyList<EClassJob>? classJobs))
return classJobs; return classJobs;
@ -57,8 +62,8 @@ internal static class QuestInfoUtils
{ EClassJob.Dancer, classJobCategory.DNC }, { EClassJob.Dancer, classJobCategory.DNC },
{ EClassJob.Reaper, classJobCategory.RPR }, { EClassJob.Reaper, classJobCategory.RPR },
{ EClassJob.Sage, classJobCategory.SGE }, { EClassJob.Sage, classJobCategory.SGE },
{ EClassJob.Viper, classJobCategory.Unknown1 }, { EClassJob.Viper, classJobCategory.VPR },
{ EClassJob.Pictomancer, classJobCategory.Unknown2 } { EClassJob.Pictomancer, classJobCategory.PCT }
} }
.Where(y => y.Value) .Where(y => y.Value)
.Select(y => y.Key) .Select(y => y.Key)

View File

@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Questionable.Model.Questing; using Questionable.Model.Questing;
namespace Questionable.Model; namespace Questionable.Model;
@ -11,12 +11,12 @@ internal sealed class SatisfactionSupplyInfo : IQuestInfo
public SatisfactionSupplyInfo(SatisfactionNpc npc) public SatisfactionSupplyInfo(SatisfactionNpc npc)
{ {
QuestId = new SatisfactionSupplyNpcId((ushort)npc.RowId); QuestId = new SatisfactionSupplyNpcId((ushort)npc.RowId);
Name = npc.Npc.Value!.Singular; Name = npc.Npc.Value.Singular.ToString();
IssuerDataId = npc.Npc.Row; IssuerDataId = npc.Npc.RowId;
Level = npc.LevelUnlock; Level = npc.LevelUnlock;
SortKey = QuestId.Value; SortKey = QuestId.Value;
Expansion = (EExpansionVersion)npc.QuestRequired.Value!.Expansion.Row; Expansion = (EExpansionVersion)npc.QuestRequired.Value!.Expansion.RowId;
PreviousQuests = [new PreviousQuestInfo(new QuestId((ushort)(npc.QuestRequired.Row & 0xFFFF)))]; PreviousQuests = [new PreviousQuestInfo(new QuestId((ushort)(npc.QuestRequired.RowId & 0xFFFF)))];
} }
public ElementId QuestId { get; } public ElementId QuestId { get; }

View File

@ -1,4 +1,4 @@
<Project Sdk="Dalamud.NET.Sdk/10.0.0"> <Project Sdk="Dalamud.NET.Sdk/11.0.0">
<PropertyGroup> <PropertyGroup>
<OutputPath>dist</OutputPath> <OutputPath>dist</OutputPath>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap> <PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Game.Text; using Dalamud.Game.Text;
@ -10,7 +11,7 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using ImGuiNET; using ImGuiNET;
using LLib.ImGui; using LLib.ImGui;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Questionable.External; using Questionable.External;
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany; using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
@ -36,7 +37,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
_notificationMasterIpc = notificationMasterIpc; _notificationMasterIpc = notificationMasterIpc;
_configuration = configuration; _configuration = configuration;
var mounts = dataManager.GetExcelSheet<Mount>()! var mounts = dataManager.GetExcelSheet<Mount>()
.Where(x => x is { RowId: > 0, Icon: > 0 }) .Where(x => x is { RowId: > 0, Icon: > 0 })
.Select(x => (MountId: x.RowId, Name: x.Singular.ToString())) .Select(x => (MountId: x.RowId, Name: x.Singular.ToString()))
.Where(x => !string.IsNullOrEmpty(x.Name)) .Where(x => !string.IsNullOrEmpty(x.Name))

View File

@ -10,7 +10,7 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using ImGuiNET; using ImGuiNET;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Gathering; using Questionable.Model.Gathering;
@ -44,24 +44,24 @@ internal sealed class GatheringJournalComponent
_gatheringPointRegistry = gatheringPointRegistry; _gatheringPointRegistry = gatheringPointRegistry;
// TODO some of the logic here would be better suited elsewhere, in particular the [item] → [gathering item] → [location] lookup // TODO some of the logic here would be better suited elsewhere, in particular the [item] → [gathering item] → [location] lookup
var routeToGatheringPoint = dataManager.GetExcelSheet<GatheringLeveRoute>()! var routeToGatheringPoint = dataManager.GetExcelSheet<GatheringLeveRoute>()
.Where(x => x.UnkData0[0].GatheringPoint != 0) .Where(x => x.GatheringPoint[0].RowId != 0)
.SelectMany(x => x.UnkData0 .SelectMany(x => x.GatheringPoint
.Where(y => y.GatheringPoint != 0) .Where(y => y.RowId != 0)
.Select(y => new .Select(y => new
{ {
RouteId = x.RowId, RouteId = x.RowId,
GatheringPointId = y.GatheringPoint GatheringPointId = y.RowId
})) }))
.GroupBy(x => x.RouteId) .GroupBy(x => x.RouteId)
.ToDictionary(x => x.Key, x => x.Select(y => y.GatheringPointId).ToList()); .ToDictionary(x => x.Key, x => x.Select(y => y.GatheringPointId).ToList());
var gatheringLeveSheet = dataManager.GetExcelSheet<GatheringLeve>()!; var gatheringLeveSheet = dataManager.GetExcelSheet<GatheringLeve>();
var territoryTypeSheet = dataManager.GetExcelSheet<TerritoryType>()!; var territoryTypeSheet = dataManager.GetExcelSheet<TerritoryType>();
var gatheringPointToLeve = dataManager.GetExcelSheet<Leve>()! var gatheringPointToLeve = dataManager.GetExcelSheet<Leve>()
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Select(x => .Select(x =>
{ {
uint startZonePlaceName = x.PlaceNameStartZone.Row; uint startZonePlaceName = x.PlaceNameStartZone.RowId;
startZonePlaceName = startZonePlaceName switch startZonePlaceName = startZonePlaceName switch
{ {
27 => 28, // limsa 27 => 28, // limsa
@ -71,16 +71,17 @@ internal sealed class GatheringJournalComponent
_ => startZonePlaceName _ => startZonePlaceName
}; };
var territoryType = territoryTypeSheet.FirstOrDefault(y => startZonePlaceName == y.PlaceName.Row) var territoryType = territoryTypeSheet.Cast<TerritoryType?>()
.FirstOrDefault(y => startZonePlaceName == y!.Value.PlaceName.RowId)
?? throw new InvalidOperationException($"Unable to use {startZonePlaceName}"); ?? throw new InvalidOperationException($"Unable to use {startZonePlaceName}");
return new return new
{ {
LeveId = x.RowId, LeveId = x.RowId,
LeveName = x.Name.ToString(), LeveName = x.Name.ToString(),
TerritoryType = (ushort)territoryType.RowId, TerritoryType = (ushort)territoryType.RowId,
TerritoryName = territoryType.PlaceName.Value?.Name.ToString(), TerritoryName = territoryType.PlaceName.ValueNullable?.Name.ToString(),
Expansion = (EExpansionVersion)territoryType.ExVersion.Row, Expansion = (EExpansionVersion)territoryType.ExVersion.RowId,
GatheringLeve = gatheringLeveSheet.GetRow((uint)x.DataId), GatheringLeve = gatheringLeveSheet.GetRowOrDefault(x.DataId.RowId),
}; };
}) })
.Where(x => x.GatheringLeve != null) .Where(x => x.GatheringLeve != null)
@ -91,9 +92,9 @@ internal sealed class GatheringJournalComponent
x.TerritoryType, x.TerritoryType,
x.TerritoryName, x.TerritoryName,
x.Expansion, x.Expansion,
GatheringPoints = x.GatheringLeve!.Route GatheringPoints = x.GatheringLeve!.Value.Route
.Where(y => y.Row != 0) .Where(y => y.RowId != 0)
.SelectMany(y => routeToGatheringPoint[y.Row]), .SelectMany(y => routeToGatheringPoint[y.RowId]),
}) })
.SelectMany(x => x.GatheringPoints.Select(y => new .SelectMany(x => x.GatheringPoints.Select(y => new
{ {
@ -110,40 +111,40 @@ internal sealed class GatheringJournalComponent
var itemSheet = dataManager.GetExcelSheet<Item>()!; var itemSheet = dataManager.GetExcelSheet<Item>()!;
_gatheringItems = dataManager.GetExcelSheet<GatheringItem>()! _gatheringItems = dataManager.GetExcelSheet<GatheringItem>()!
.Where(x => x.RowId != 0 && x.GatheringItemLevel.Row != 0) .Where(x => x.RowId != 0 && x.GatheringItemLevel.RowId != 0)
.Select(x => new .Select(x => new
{ {
GatheringItemId = (int)x.RowId, GatheringItemId = (int)x.RowId,
Name = itemSheet.GetRow((uint)x.Item)?.Name.ToString() Name = itemSheet.GetRowOrDefault(x.Item.RowId)?.Name.ToString()
}) })
.Where(x => !string.IsNullOrEmpty(x.Name)) .Where(x => !string.IsNullOrEmpty(x.Name))
.ToDictionary(x => x.GatheringItemId, x => x.Name!); .ToDictionary(x => x.GatheringItemId, x => x.Name!);
_gatheringPointsByExpansion = dataManager.GetExcelSheet<GatheringPoint>()! _gatheringPointsByExpansion = dataManager.GetExcelSheet<GatheringPoint>()!
.Where(x => x.GatheringPointBase.Row != 0) .Where(x => x.GatheringPointBase.RowId != 0)
.Where(x => x.GatheringPointBase.Row is < 653 or > 680) // exclude ishgard restoration phase 1 .Where(x => x.GatheringPointBase.RowId is < 653 or > 680) // exclude ishgard restoration phase 1
.DistinctBy(x => x.GatheringPointBase.Row) .DistinctBy(x => x.GatheringPointBase.RowId)
.Select(x => new .Select(x => new
{ {
GatheringPointId = x.RowId, GatheringPointId = x.RowId,
Point = new DefaultGatheringPoint(new GatheringPointId((ushort)x.GatheringPointBase.Row), Point = new DefaultGatheringPoint(new GatheringPointId((ushort)x.GatheringPointBase.RowId),
x.GatheringPointBase.Value!.GatheringType.Row switch x.GatheringPointBase.Value!.GatheringType.RowId switch
{ {
0 or 1 => EClassJob.Miner, 0 or 1 => EClassJob.Miner,
2 or 3 => EClassJob.Botanist, 2 or 3 => EClassJob.Botanist,
_ => EClassJob.Fisher _ => EClassJob.Fisher
}, },
x.GatheringPointBase.Value.GatheringLevel, x.GatheringPointBase.Value.GatheringLevel,
x.GatheringPointBase.Value.Item.Where(y => y != 0).Select(y => (ushort)y).ToList(), x.GatheringPointBase.Value.Item.Where(y => y.RowId != 0).Select(y => (ushort)y.RowId).ToList(),
(EExpansionVersion?)x.TerritoryType.Value?.ExVersion.Row ?? (EExpansionVersion)byte.MaxValue, (EExpansionVersion?)x.TerritoryType.ValueNullable?.ExVersion.RowId ?? (EExpansionVersion)byte.MaxValue,
(ushort)x.TerritoryType.Row, (ushort)x.TerritoryType.RowId,
x.TerritoryType.Value?.PlaceName.Value?.Name.ToString(), x.TerritoryType.ValueNullable?.PlaceName.ValueNullable?.Name.ToString(),
$"{x.GatheringPointBase.Row} - {x.PlaceName.Value?.Name}") $"{x.GatheringPointBase.RowId} - {x.PlaceName.ValueNullable?.Name}")
}) })
.Where(x => x.Point.ClassJob != EClassJob.Fisher) .Where(x => x.Point.ClassJob != EClassJob.Fisher)
.Select(x => .Select(x =>
{ {
if (gatheringPointToLeve.TryGetValue((int)x.GatheringPointId, out var leve)) if (gatheringPointToLeve.TryGetValue(x.GatheringPointId, out var leve))
{ {
// it's a leve // it's a leve
return x.Point with return x.Point with
@ -161,9 +162,9 @@ internal sealed class GatheringJournalComponent
var territoryType = territoryTypeSheet.GetRow(gatheringRoot.Steps.Last().TerritoryId)!; var territoryType = territoryTypeSheet.GetRow(gatheringRoot.Steps.Last().TerritoryId)!;
return x.Point with return x.Point with
{ {
Expansion = (EExpansionVersion)territoryType.ExVersion.Row, Expansion = (EExpansionVersion)territoryType.ExVersion.RowId,
TerritoryType = (ushort)territoryType.RowId, TerritoryType = (ushort)territoryType.RowId,
TerritoryName = territoryType.PlaceName.Value?.Name.ToString(), TerritoryName = territoryType.PlaceName.ValueNullable?.Name.ToString(),
}; };
} }
else else
@ -429,7 +430,7 @@ internal sealed class GatheringJournalComponent
} }
} }
public void ClearCounts() public void ClearCounts(int type, int code)
{ {
foreach (var expansion in _gatheringPointsByExpansion) foreach (var expansion in _gatheringPointsByExpansion)
{ {

View File

@ -368,7 +368,7 @@ internal sealed class QuestJournalComponent
} }
} }
internal void ClearCounts() internal void ClearCounts(int type, int code)
{ {
foreach (var genreCount in _genreCounts.ToList()) foreach (var genreCount in _genreCounts.ToList())
_genreCounts[genreCount.Key] = genreCount.Value with { Completed = 0 }; _genreCounts[genreCount.Key] = genreCount.Value with { Completed = 0 };

View File

@ -67,7 +67,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
#endif #endif
SizeConstraints = new WindowSizeConstraints SizeConstraints = new WindowSizeConstraints
{ {
MinimumSize = new Vector2(200, 30), MinimumSize = new Vector2(230, 30),
MaximumSize = default MaximumSize = default
}; };
RespectCloseHotkey = false; RespectCloseHotkey = false;

2
vendor/ECommons vendored

@ -1 +1 @@
Subproject commit 147e12e95f2fb781f2c8ddac31d948700ed9051c Subproject commit 71ee09f7cc2230a73503b945422760da1368405c