master #3

Open
cacahuetes wants to merge 648 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>
<ProjectReference Include="..\LLib\LLib.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;
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)
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 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 <= 5)

View File

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

View File

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

View File

@ -16,7 +16,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib;
using LLib.GameData;
using LLib.GameUI;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Interactions;
using Questionable.Data;
@ -90,7 +90,7 @@ internal sealed class InteractionUiController : IDisposable
_shopController = shopController;
_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, "CutSceneSelectString", CutsceneSelectStringPostSetup);
@ -713,7 +713,7 @@ internal sealed class InteractionUiController : IDisposable
step.InteractionType == EInteractionType.Gather)
{
if (_gatheringData.TryGetGatheringPointId(step.ItemsToGather[0].ItemId,
(EClassJob?)_clientState.LocalPlayer?.ClassJob.Id ?? EClassJob.Adventurer,
(EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer,
out GatheringPointId? gatheringPointId) &&
_gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? root))
{
@ -757,19 +757,19 @@ internal sealed class InteractionUiController : IDisposable
[NotNullWhen(true)] out string? warpText)
{
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)
{
string? excelName = entry.Name?.ToString();
string? excelQuestion = entry.Question?.ToString();
string? excelName = entry.Name.ToString();
string? excelQuestion = entry.Question.ToString();
if (excelQuestion != null && GameFunctions.GameStringEquals(excelQuestion, actualPrompt))
if (!string.IsNullOrEmpty(excelQuestion) && GameFunctions.GameStringEquals(excelQuestion, actualPrompt))
{
warpId = entry.RowId;
warpText = excelQuestion;
return true;
}
else if (excelName != null && GameFunctions.GameStringEquals(excelName, actualPrompt))
else if (!string.IsNullOrEmpty(excelName) && GameFunctions.GameStringEquals(excelName, actualPrompt))
{
warpId = entry.RowId;
warpText = excelName;

View File

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

View File

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

View File

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

View File

@ -225,7 +225,7 @@ internal static class DoGather
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;
else
return botanistAction;

View File

@ -193,7 +193,7 @@ internal static class DoGatherCollectable
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;
else
return botanistAction;

View File

@ -5,8 +5,7 @@ using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using LLib;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.DependencyInjection;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Functions;
using Questionable.Model.Questing;
@ -63,13 +62,13 @@ internal static class EquipItem
];
private int _attempts;
private Item _item = null!;
private Item? _item;
private List<ushort> _targetSlots = null!;
private DateTime _continueAt = DateTime.MaxValue;
protected override bool Start()
{
_item = dataManager.GetExcelSheet<Item>()!.GetRow(Task.ItemId) ??
_item = dataManager.GetExcelSheet<Item>().GetRowOrDefault(Task.ItemId) ??
throw new ArgumentOutOfRangeException(nameof(Task.ItemId));
_targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment");
@ -118,7 +117,7 @@ internal static class EquipItem
var itemSlot = equippedContainer->GetInventorySlot(slot);
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;
}
}
@ -162,11 +161,13 @@ internal static class EquipItem
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
13 => [0],
17 => [13], // soul crystal

View File

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

View File

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

View File

@ -35,7 +35,7 @@ internal static class Gather
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,
out GatheringPointId? gatheringPointId))
throw new TaskException($"No gathering point found for item {itemToGather.ItemId}");

View File

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

View File

@ -239,7 +239,7 @@ internal static class SkipCondition
{
List<EClassJob> expectedJobs =
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,
string.Join(",", expectedJobs));
if (!expectedJobs.Contains(currentJob))

View File

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

View File

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

View File

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

View File

@ -3,8 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Plugin.Services;
using LLib.GameData;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging;
using Lumina.Excel.Sheets;
using Questionable.Model.Gathering;
namespace Questionable.Data;
@ -18,43 +17,45 @@ internal sealed class GatheringData
public GatheringData(IDataManager dataManager)
{
Dictionary<uint, uint> gatheringItemToItem = dataManager.GetExcelSheet<GatheringItem>()!
.Where(x => x.RowId != 0 && x.Item != 0)
.ToDictionary(x => x.RowId, x => (uint)x.Item);
Dictionary<uint, uint> gatheringItemToItem = dataManager.GetExcelSheet<GatheringItem>()
.Where(x => x.RowId != 0 && x.Item.RowId != 0)
.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);
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);
}
}
}
_itemIdToCollectability = dataManager.GetExcelSheet<SatisfactionSupply>()!
_itemIdToCollectability = dataManager.GetSubrowExcelSheet<SatisfactionSupply>()
.Where(x => x.RowId > 0)
.SelectMany(x => x)
.Where(x => x.Slot is 2)
.Select(x => new
{
ItemId = x.Item.Row,
ItemId = x.Item.RowId,
Collectability = x.CollectabilityHigh,
})
.Distinct()
.ToDictionary(x => x.ItemId, x => x.Collectability);
_npcForCustomDeliveries = dataManager.GetExcelSheet<SatisfactionNpc>()!
_npcForCustomDeliveries = dataManager.GetExcelSheet<SatisfactionNpc>()
.Where(x => x.RowId > 0)
.SelectMany(x => dataManager.GetExcelSheet<SatisfactionSupply>()!
.Where(y => y.RowId == x.SupplyIndex.Last())
.SelectMany(x => dataManager.GetSubrowExcelSheet<SatisfactionSupply>()
.Where(y => y.RowId == x.SatisfactionNpcParams.Last().SupplyIndex)
.SelectMany(y => y)
.Select(y => new
{
ItemId = y.Item.Row,
NpcId = x.Npc.Row
ItemId = y.Item.RowId,
NpcId = x.Npc.RowId
}))
.Where(x => x.ItemId > 0)
.Distinct()

View File

@ -1,7 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.Questing;
@ -9,28 +10,36 @@ namespace Questionable.Data;
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)
.Select(x => new Genre(x, questData.GetAllByJournalGenre(x.RowId)))
.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 gridaniaStart = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(2)!;
var uldahStart = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(3)!;
var limsaStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(1);
var gridaniaStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(2);
var uldahStart = dataManager.GetExcelSheet<QuestRedo>().GetRow(3);
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)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.ToList());
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)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.ToList());
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)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.ToList());
@ -41,12 +50,12 @@ internal sealed class JournalData
genreLimsa.Quests.Contains(x) || genreGridania.Quests.Contains(x) || genreUldah.Quests.Contains(x));
Genres = genres.AsReadOnly();
Categories = dataManager.GetExcelSheet<JournalCategory>()!
Categories = dataManager.GetExcelSheet<JournalCategory>()
.Where(x => x.RowId > 0)
.Select(x => new Category(x, Genres.Where(y => y.CategoryId == x.RowId).ToList()))
.ToList()
.AsReadOnly();
Sections = dataManager.GetExcelSheet<JournalSection>()!
Sections = dataManager.GetExcelSheet<JournalSection>()
.Select(x => new Section(x, Categories.Where(y => y.SectionId == x.RowId).ToList()))
.ToList();
}
@ -61,7 +70,7 @@ internal sealed class JournalData
{
Id = journalGenre.RowId;
Name = journalGenre.Name.ToString();
CategoryId = journalGenre.JournalCategory.Row;
CategoryId = journalGenre.JournalCategory.RowId;
Quests = quests;
}
@ -84,7 +93,7 @@ internal sealed class JournalData
{
public uint Id { get; } = journalCategory.RowId;
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 int QuestCount => Genres.Sum(x => x.QuestCount);
}

View File

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

View File

@ -1,14 +1,12 @@
using System;
using System.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Model.Common;
using Questionable.Model.Questing;
using Action = Lumina.Excel.GeneratedSheets.Action;
using Action = Lumina.Excel.Sheets.Action;
namespace Questionable.Functions;
@ -57,9 +55,10 @@ internal sealed unsafe class AetheryteFunctions
public bool IsTeleportUnlocked()
{
uint unlockLink = _dataManager.GetExcelSheet<Action>()!
.GetRow(5)!
.UnlockLink;
uint unlockLink = _dataManager.GetExcelSheet<Action>()
.GetRow(5)
.UnlockLink
.RowId;
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.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
@ -41,11 +41,11 @@ internal sealed unsafe class ChatFunctions
_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.TextCommand != null && x.TextCommand.Value != null)
.Select(x => (x.RowId, Command: x.TextCommand.Value!.Command?.ToString()))
.Where(x => x.Command != null && x.Command.StartsWith('/'))
.Where(x => x.TextCommand.IsValid)
.Select(x => (x.RowId, Command: x.TextCommand.Value.Command.ToString()))
.Where(x => !string.IsNullOrEmpty(x.Command) && x.Command.StartsWith('/'))
.ToDictionary(x => (EEmote)x.RowId, x => x.Command!)
.AsReadOnly();
}
@ -156,8 +156,8 @@ internal sealed unsafe class ChatFunctions
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 SanitiseString = "E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 0F B6 F0 E8 ?? ?? ?? ?? 48 8D 4D C0";
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 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D AE";
}
[StructLayout(LayoutKind.Explicit)]

View File

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

View File

@ -14,23 +14,22 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.Common;
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 ContentFinderCondition = Lumina.Excel.GeneratedSheets.ContentFinderCondition;
using ContentFinderCondition = Lumina.Excel.Sheets.ContentFinderCondition;
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
using Quest = Questionable.Model.Quest;
using TerritoryType = Lumina.Excel.GeneratedSheets.TerritoryType;
namespace Questionable.Functions;
internal sealed unsafe class GameFunctions
{
private readonly ReadOnlyDictionary<ushort, byte> _territoryToAetherCurrentCompFlgSet;
private readonly ReadOnlyDictionary<uint, ushort> _contentFinderConditionToContentId;
private readonly ReadOnlyDictionary<uint, uint> _contentFinderConditionToContentId;
private readonly QuestFunctions _questFunctions;
private readonly IDataManager _dataManager;
@ -63,14 +62,15 @@ internal sealed unsafe class GameFunctions
_configuration = configuration;
_logger = logger;
_territoryToAetherCurrentCompFlgSet = dataManager.GetExcelSheet<TerritoryType>()!
_territoryToAetherCurrentCompFlgSet = dataManager.GetExcelSheet<TerritoryType>()
.Where(x => x.RowId > 0)
.Where(x => x.Unknown32 > 0)
.ToDictionary(x => (ushort)x.RowId, x => x.Unknown32)
.Where(x => x.Unknown3 > 0)
.ToDictionary(x => (ushort)x.RowId, x => x.Unknown4)
.AsReadOnly();
_contentFinderConditionToContentId = dataManager.GetExcelSheet<ContentFinderCondition>()!
.Where(x => x.RowId > 0 && x.Content > 0)
.ToDictionary(x => x.RowId, x => x.Content)
_territoryToAetherCurrentCompFlgSet = new Dictionary<ushort, byte>().AsReadOnly();
_contentFinderConditionToContentId = dataManager.GetExcelSheet<ContentFinderCondition>()
.Where(x => x.RowId > 0 && x.Content.RowId > 0)
.ToDictionary(x => x.RowId, x => x.Content.RowId)
.AsReadOnly();
}
@ -220,7 +220,7 @@ internal sealed unsafe class GameFunctions
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))
{
_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)
{
if (_contentFinderConditionToContentId.TryGetValue(contentFinderConditionId, out ushort contentId))
if (_contentFinderConditionToContentId.TryGetValue(contentFinderConditionId, out uint contentId))
{
if (UIState.IsInstanceContentUnlocked(contentId))
AgentContentsFinder.Instance()->OpenRegularDuty(contentFinderConditionId);

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using LLib.GameData;
using Lumina.Excel.GeneratedSheets2;
using Lumina.Excel.Sheets;
namespace Questionable.Model;
@ -9,8 +10,12 @@ internal static class QuestInfoUtils
{
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))
return classJobs;
@ -57,8 +62,8 @@ internal static class QuestInfoUtils
{ EClassJob.Dancer, classJobCategory.DNC },
{ EClassJob.Reaper, classJobCategory.RPR },
{ EClassJob.Sage, classJobCategory.SGE },
{ EClassJob.Viper, classJobCategory.Unknown1 },
{ EClassJob.Pictomancer, classJobCategory.Unknown2 }
{ EClassJob.Viper, classJobCategory.VPR },
{ EClassJob.Pictomancer, classJobCategory.PCT }
}
.Where(y => y.Value)
.Select(y => y.Key)

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using LLib.GameData;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using Questionable.Model.Questing;
namespace Questionable.Model;
@ -11,12 +11,12 @@ internal sealed class SatisfactionSupplyInfo : IQuestInfo
public SatisfactionSupplyInfo(SatisfactionNpc npc)
{
QuestId = new SatisfactionSupplyNpcId((ushort)npc.RowId);
Name = npc.Npc.Value!.Singular;
IssuerDataId = npc.Npc.Row;
Name = npc.Npc.Value.Singular.ToString();
IssuerDataId = npc.Npc.RowId;
Level = npc.LevelUnlock;
SortKey = QuestId.Value;
Expansion = (EExpansionVersion)npc.QuestRequired.Value!.Expansion.Row;
PreviousQuests = [new PreviousQuestInfo(new QuestId((ushort)(npc.QuestRequired.Row & 0xFFFF)))];
Expansion = (EExpansionVersion)npc.QuestRequired.Value!.Expansion.RowId;
PreviousQuests = [new PreviousQuestInfo(new QuestId((ushort)(npc.QuestRequired.RowId & 0xFFFF)))];
}
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>
<OutputPath>dist</OutputPath>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>

View File

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

View File

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

View File

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

2
vendor/ECommons vendored

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