Implement min/btn collectable logic
This commit is contained in:
parent
82c20bf76d
commit
f04233a325
@ -93,7 +93,7 @@ internal sealed class EditorCommands : IDisposable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(targetFile, root) = CreateNewFile(gatheringPoint, target, string.Join(" ", arguments));
|
(targetFile, root) = CreateNewFile(gatheringPoint, target);
|
||||||
_chatGui.Print($"Creating new file under {targetFile.FullName}", "qG");
|
_chatGui.Print($"Creating new file under {targetFile.FullName}", "qG");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,12 +164,8 @@ internal sealed class EditorCommands : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public (FileInfo targetFile, GatheringRoot root) CreateNewFile(GatheringPoint gatheringPoint, IGameObject target,
|
public (FileInfo targetFile, GatheringRoot root) CreateNewFile(GatheringPoint gatheringPoint, IGameObject target)
|
||||||
string fileName)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(fileName))
|
|
||||||
throw new ArgumentException(nameof(fileName));
|
|
||||||
|
|
||||||
// determine target folder
|
// determine target folder
|
||||||
DirectoryInfo? targetFolder = _plugin.GetLocationsInTerritory(_clientState.TerritoryType).FirstOrDefault()
|
DirectoryInfo? targetFolder = _plugin.GetLocationsInTerritory(_clientState.TerritoryType).FirstOrDefault()
|
||||||
?.File.Directory;
|
?.File.Directory;
|
||||||
@ -183,7 +179,8 @@ internal sealed class EditorCommands : IDisposable
|
|||||||
|
|
||||||
FileInfo targetFile =
|
FileInfo targetFile =
|
||||||
new FileInfo(
|
new FileInfo(
|
||||||
Path.Combine(targetFolder.FullName, $"{gatheringPoint.GatheringPointBase.Row}_{fileName}.json"));
|
Path.Combine(targetFolder.FullName,
|
||||||
|
$"{gatheringPoint.GatheringPointBase.Row}_{gatheringPoint.PlaceName.Value!.Name}_{(_clientState.LocalPlayer!.ClassJob.Id == 16 ? "MIN" : "BTN")}.json"));
|
||||||
var root = new GatheringRoot
|
var root = new GatheringRoot
|
||||||
{
|
{
|
||||||
TerritoryId = _clientState.TerritoryType,
|
TerritoryId = _clientState.TerritoryType,
|
||||||
|
@ -7,6 +7,7 @@ using System.Numerics;
|
|||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
@ -31,8 +32,6 @@ internal sealed class EditorWindow : Window
|
|||||||
private (RendererPlugin.GatheringLocationContext Context, GatheringNode Node, GatheringLocation Location)?
|
private (RendererPlugin.GatheringLocationContext Context, GatheringNode Node, GatheringLocation Location)?
|
||||||
_targetLocation;
|
_targetLocation;
|
||||||
|
|
||||||
private string _newFileName = string.Empty;
|
|
||||||
|
|
||||||
public EditorWindow(RendererPlugin plugin, EditorCommands editorCommands, IDataManager dataManager,
|
public EditorWindow(RendererPlugin plugin, EditorCommands editorCommands, IDataManager dataManager,
|
||||||
ITargetManager targetManager, IClientState clientState, IObjectTable objectTable)
|
ITargetManager targetManager, IClientState clientState, IObjectTable objectTable)
|
||||||
: base("Gathering Path Editor###QuestionableGatheringPathEditor")
|
: base("Gathering Path Editor###QuestionableGatheringPathEditor")
|
||||||
@ -68,13 +67,19 @@ internal sealed class EditorWindow : Window
|
|||||||
})
|
})
|
||||||
.Select(location => new { Context = context, Node = node, Location = location }))))
|
.Select(location => new { Context = context, Node = node, Location = location }))))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
if (_target != null && _target.ObjectKind != ObjectKind.GatheringPoint || location == null)
|
if (_target != null && _target.ObjectKind != ObjectKind.GatheringPoint)
|
||||||
{
|
{
|
||||||
_target = null;
|
_target = null;
|
||||||
_targetLocation = null;
|
_targetLocation = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (location == null)
|
||||||
|
{
|
||||||
|
_targetLocation = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_target ??= _objectTable.FirstOrDefault(
|
_target ??= _objectTable.FirstOrDefault(
|
||||||
x => x.ObjectKind == ObjectKind.GatheringPoint &&
|
x => x.ObjectKind == ObjectKind.GatheringPoint &&
|
||||||
x.DataId == location.Node.DataId &&
|
x.DataId == location.Node.DataId &&
|
||||||
@ -123,13 +128,18 @@ internal sealed class EditorWindow : Window
|
|||||||
_plugin.Redraw();
|
_plugin.Redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.BeginDisabled(locationOverride.MinimumAngle == null && locationOverride.MaximumAngle == null);
|
bool unsaved = locationOverride is { MinimumAngle: not null, MaximumAngle: not null };
|
||||||
|
ImGui.BeginDisabled(!unsaved);
|
||||||
|
if (unsaved)
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, ImGuiColors.DalamudRed);
|
||||||
if (ImGui.Button("Save"))
|
if (ImGui.Button("Save"))
|
||||||
{
|
{
|
||||||
location.MinimumAngle = locationOverride.MinimumAngle;
|
location.MinimumAngle = locationOverride.MinimumAngle;
|
||||||
location.MaximumAngle = locationOverride.MaximumAngle;
|
location.MaximumAngle = locationOverride.MaximumAngle;
|
||||||
_plugin.Save(context.File, context.Root);
|
_plugin.Save(context.File, context.Root);
|
||||||
}
|
}
|
||||||
|
if (unsaved)
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Reset"))
|
if (ImGui.Button("Reset"))
|
||||||
@ -189,16 +199,11 @@ internal sealed class EditorWindow : Window
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.InputText("File Name", ref _newFileName, 128);
|
|
||||||
ImGui.BeginDisabled(string.IsNullOrEmpty(_newFileName));
|
|
||||||
if (ImGui.Button("Create location"))
|
if (ImGui.Button("Create location"))
|
||||||
{
|
{
|
||||||
var (targetFile, root) = _editorCommands.CreateNewFile(gatheringPoint, _target, _newFileName);
|
var (targetFile, root) = _editorCommands.CreateNewFile(gatheringPoint, _target);
|
||||||
_plugin.Save(targetFile, root);
|
_plugin.Save(targetFile, root);
|
||||||
_newFileName = string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,158 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
|
||||||
|
"Author": [],
|
||||||
|
"TerritoryId": 958,
|
||||||
|
"AetheryteShortcut": "Garlemald - Camp Broken Glass",
|
||||||
|
"Groups": [
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 33932,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -80.95969,
|
||||||
|
"Y": -9.810837,
|
||||||
|
"Z": 462.2579
|
||||||
|
},
|
||||||
|
"MinimumAngle": 130,
|
||||||
|
"MaximumAngle": 260
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DataId": 33933,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -72.11935,
|
||||||
|
"Y": -10.90324,
|
||||||
|
"Z": 471.2258
|
||||||
|
},
|
||||||
|
"MinimumAngle": 105,
|
||||||
|
"MaximumAngle": 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -98.97565,
|
||||||
|
"Y": -5.664787,
|
||||||
|
"Z": 463.9966
|
||||||
|
},
|
||||||
|
"MinimumAngle": 60,
|
||||||
|
"MaximumAngle": 230
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -63.49503,
|
||||||
|
"Y": -11.21235,
|
||||||
|
"Z": 469.3839
|
||||||
|
},
|
||||||
|
"MinimumAngle": 80,
|
||||||
|
"MaximumAngle": 255
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 33931,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -61.34306,
|
||||||
|
"Y": 6.11244,
|
||||||
|
"Z": 318.3409
|
||||||
|
},
|
||||||
|
"MinimumAngle": -120,
|
||||||
|
"MaximumAngle": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -61.47854,
|
||||||
|
"Y": 6.076105,
|
||||||
|
"Z": 281.4938
|
||||||
|
},
|
||||||
|
"MinimumAngle": 65,
|
||||||
|
"MaximumAngle": 240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -73.25829,
|
||||||
|
"Y": 6.108262,
|
||||||
|
"Z": 302.9926
|
||||||
|
},
|
||||||
|
"MinimumAngle": 50,
|
||||||
|
"MaximumAngle": 220
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DataId": 33930,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -51.28564,
|
||||||
|
"Y": 6.088318,
|
||||||
|
"Z": 318.0529
|
||||||
|
},
|
||||||
|
"MinimumAngle": -65,
|
||||||
|
"MaximumAngle": 110
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 33935,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 72.58704,
|
||||||
|
"Y": -11.59895,
|
||||||
|
"Z": 354.757
|
||||||
|
},
|
||||||
|
"MinimumAngle": 75,
|
||||||
|
"MaximumAngle": 235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 65.33016,
|
||||||
|
"Y": -11.61111,
|
||||||
|
"Z": 358.7321
|
||||||
|
},
|
||||||
|
"MinimumAngle": 65,
|
||||||
|
"MaximumAngle": 235
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 68.21196,
|
||||||
|
"Y": -11.81954,
|
||||||
|
"Z": 366.5172
|
||||||
|
},
|
||||||
|
"MinimumAngle": 5,
|
||||||
|
"MaximumAngle": 85
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DataId": 33934,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 81.30492,
|
||||||
|
"Y": -11.53227,
|
||||||
|
"Z": 347.9922
|
||||||
|
},
|
||||||
|
"MinimumAngle": 50,
|
||||||
|
"MaximumAngle": 215
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
|
||||||
|
"Author": [],
|
||||||
|
"TerritoryId": 959,
|
||||||
|
"Groups": [
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 33929,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 304.4121,
|
||||||
|
"Y": 118.8077,
|
||||||
|
"Z": 673.4494
|
||||||
|
},
|
||||||
|
"MinimumAngle": 50,
|
||||||
|
"MaximumAngle": 230
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 297.7666,
|
||||||
|
"Y": 119.4976,
|
||||||
|
"Z": 679.5604
|
||||||
|
},
|
||||||
|
"MinimumAngle": 50,
|
||||||
|
"MaximumAngle": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 322.163,
|
||||||
|
"Y": 119.0883,
|
||||||
|
"Z": 657.4384
|
||||||
|
},
|
||||||
|
"MinimumAngle": 55,
|
||||||
|
"MaximumAngle": 235
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DataId": 33928,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 313.72,
|
||||||
|
"Y": 118.3442,
|
||||||
|
"Z": 664.8668
|
||||||
|
},
|
||||||
|
"MinimumAngle": 60,
|
||||||
|
"MaximumAngle": 230
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 33927,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 394.3838,
|
||||||
|
"Y": 144.7951,
|
||||||
|
"Z": 820.7851
|
||||||
|
},
|
||||||
|
"MinimumAngle": 75,
|
||||||
|
"MaximumAngle": 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 421.0549,
|
||||||
|
"Y": 143.6111,
|
||||||
|
"Z": 805.9457
|
||||||
|
},
|
||||||
|
"MinimumAngle": 60,
|
||||||
|
"MaximumAngle": 225
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 414.2961,
|
||||||
|
"Y": 143.2405,
|
||||||
|
"Z": 811.3884
|
||||||
|
},
|
||||||
|
"MinimumAngle": 65,
|
||||||
|
"MaximumAngle": 230
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DataId": 33926,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 405.2481,
|
||||||
|
"Y": 143.6621,
|
||||||
|
"Z": 816.6496
|
||||||
|
},
|
||||||
|
"MinimumAngle": 75,
|
||||||
|
"MaximumAngle": 230
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"DataId": 33925,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 474.679,
|
||||||
|
"Y": 143.4776,
|
||||||
|
"Z": 698.5961
|
||||||
|
},
|
||||||
|
"MinimumAngle": 20,
|
||||||
|
"MaximumAngle": 170
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 474.8585,
|
||||||
|
"Y": 144.2588,
|
||||||
|
"Z": 685.7468
|
||||||
|
},
|
||||||
|
"MinimumAngle": 0,
|
||||||
|
"MaximumAngle": 155
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 467.506,
|
||||||
|
"Y": 144.9235,
|
||||||
|
"Z": 654.2
|
||||||
|
},
|
||||||
|
"MinimumAngle": 0,
|
||||||
|
"MaximumAngle": 150
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DataId": 33924,
|
||||||
|
"Locations": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": 470.7754,
|
||||||
|
"Y": 144.8793,
|
||||||
|
"Z": 672.114
|
||||||
|
},
|
||||||
|
"MinimumAngle": -5,
|
||||||
|
"MaximumAngle": 165
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
27
QuestPaths/6.x - Endwalker/4807_DebugGathering.json
Normal file
27
QuestPaths/6.x - Endwalker/4807_DebugGathering.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||||
|
"Author": "liza",
|
||||||
|
"QuestSequence": [
|
||||||
|
{
|
||||||
|
"Sequence": 255,
|
||||||
|
"Steps": [
|
||||||
|
{
|
||||||
|
"Position": {
|
||||||
|
"X": -435.39066,
|
||||||
|
"Y": -9.809827,
|
||||||
|
"Z": -594.5472
|
||||||
|
},
|
||||||
|
"TerritoryId": 1187,
|
||||||
|
"InteractionType": "WalkTo",
|
||||||
|
"Fly": true,
|
||||||
|
"RequiredGatheredItems": [
|
||||||
|
{
|
||||||
|
"ItemId": 43992,
|
||||||
|
"ItemCount": 1234
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -59,7 +59,8 @@
|
|||||||
},
|
},
|
||||||
"StopDistance": 7,
|
"StopDistance": 7,
|
||||||
"TerritoryId": 962,
|
"TerritoryId": 962,
|
||||||
"InteractionType": "CompleteQuest"
|
"InteractionType": "CompleteQuest",
|
||||||
|
"NextQuestId": 4154
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||||
"Author": "liza",
|
"Author": "liza",
|
||||||
"Disabled": true,
|
|
||||||
"QuestSequence": [
|
"QuestSequence": [
|
||||||
{
|
{
|
||||||
"Sequence": 0,
|
"Sequence": 0,
|
||||||
@ -30,13 +29,19 @@
|
|||||||
},
|
},
|
||||||
"TerritoryId": 962,
|
"TerritoryId": 962,
|
||||||
"InteractionType": "CompleteQuest",
|
"InteractionType": "CompleteQuest",
|
||||||
|
"AetheryteShortcut": "Old Sharlayan",
|
||||||
|
"AethernetShortcut": [
|
||||||
|
"[Old Sharlayan] Aetheryte Plaza",
|
||||||
|
"[Old Sharlayan] The Studium"
|
||||||
|
],
|
||||||
"RequiredGatheredItems": [
|
"RequiredGatheredItems": [
|
||||||
{
|
{
|
||||||
"ItemId": 35600,
|
"ItemId": 35600,
|
||||||
"ItemCount": 6,
|
"ItemCount": 6,
|
||||||
"Collectability": 600
|
"Collectability": 600
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"NextQuestId": 4155
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||||
|
"Author": "liza",
|
||||||
|
"QuestSequence": [
|
||||||
|
{
|
||||||
|
"Sequence": 0,
|
||||||
|
"Steps": [
|
||||||
|
{
|
||||||
|
"DataId": 1038501,
|
||||||
|
"Position": {
|
||||||
|
"X": -367.3305,
|
||||||
|
"Y": 21.846018,
|
||||||
|
"Z": -102.983154
|
||||||
|
},
|
||||||
|
"TerritoryId": 962,
|
||||||
|
"InteractionType": "AcceptQuest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sequence": 255,
|
||||||
|
"Steps": [
|
||||||
|
{
|
||||||
|
"DataId": 1038501,
|
||||||
|
"Position": {
|
||||||
|
"X": -367.3305,
|
||||||
|
"Y": 21.846018,
|
||||||
|
"Z": -102.983154
|
||||||
|
},
|
||||||
|
"TerritoryId": 962,
|
||||||
|
"InteractionType": "CompleteQuest",
|
||||||
|
"AetheryteShortcut": "Old Sharlayan",
|
||||||
|
"AethernetShortcut": [
|
||||||
|
"[Old Sharlayan] Aetheryte Plaza",
|
||||||
|
"[Old Sharlayan] The Studium"
|
||||||
|
],
|
||||||
|
"RequiredGatheredItems": [
|
||||||
|
{
|
||||||
|
"ItemId": 35601,
|
||||||
|
"ItemCount": 6,
|
||||||
|
"Collectability": 600
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"NextQuestId": 4156
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||||
|
"Author": "liza",
|
||||||
|
"QuestSequence": [
|
||||||
|
{
|
||||||
|
"Sequence": 0,
|
||||||
|
"Steps": [
|
||||||
|
{
|
||||||
|
"DataId": 1038501,
|
||||||
|
"Position": {
|
||||||
|
"X": -367.3305,
|
||||||
|
"Y": 21.846018,
|
||||||
|
"Z": -102.983154
|
||||||
|
},
|
||||||
|
"TerritoryId": 962,
|
||||||
|
"InteractionType": "AcceptQuest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sequence": 255,
|
||||||
|
"Steps": [
|
||||||
|
{
|
||||||
|
"DataId": 1038501,
|
||||||
|
"Position": {
|
||||||
|
"X": -367.3305,
|
||||||
|
"Y": 21.846018,
|
||||||
|
"Z": -102.983154
|
||||||
|
},
|
||||||
|
"TerritoryId": 962,
|
||||||
|
"InteractionType": "CompleteQuest",
|
||||||
|
"AetheryteShortcut": "Old Sharlayan",
|
||||||
|
"AethernetShortcut": [
|
||||||
|
"[Old Sharlayan] Aetheryte Plaza",
|
||||||
|
"[Old Sharlayan] The Studium"
|
||||||
|
],
|
||||||
|
"RequiredGatheredItems": [
|
||||||
|
{
|
||||||
|
"ItemId": 35602,
|
||||||
|
"ItemCount": 6,
|
||||||
|
"Collectability": 600
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"NextQuestId": 4157
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||||
|
"Author": "liza",
|
||||||
|
"QuestSequence": [
|
||||||
|
{
|
||||||
|
"Sequence": 0,
|
||||||
|
"Steps": [
|
||||||
|
{
|
||||||
|
"DataId": 1038501,
|
||||||
|
"Position": {
|
||||||
|
"X": -367.3305,
|
||||||
|
"Y": 21.846018,
|
||||||
|
"Z": -102.983154
|
||||||
|
},
|
||||||
|
"TerritoryId": 962,
|
||||||
|
"InteractionType": "AcceptQuest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -15,6 +15,18 @@ public enum EAction
|
|||||||
RedGulal = 29382,
|
RedGulal = 29382,
|
||||||
YellowGulal = 29383,
|
YellowGulal = 29383,
|
||||||
BlueGulal = 29384,
|
BlueGulal = 29384,
|
||||||
|
|
||||||
|
CollectMiner = 240,
|
||||||
|
ScourMiner = 22182,
|
||||||
|
MeticulousMiner = 22184,
|
||||||
|
ScrutinyMiner = 22185,
|
||||||
|
|
||||||
|
CollectBotanist = 815,
|
||||||
|
ScourBotanist = 22186,
|
||||||
|
MeticulousBotanist = 22188,
|
||||||
|
ScrutinyBotanist = 22189,
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EActionExtensions
|
public static class EActionExtensions
|
||||||
|
@ -4,5 +4,5 @@ public sealed class GatheredItem
|
|||||||
{
|
{
|
||||||
public uint ItemId { get; set; }
|
public uint ItemId { get; set; }
|
||||||
public int ItemCount { get; set; }
|
public int ItemCount { get; set; }
|
||||||
public short Collectability { get; set; }
|
public ushort Collectability { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
@ -12,7 +13,6 @@ using Questionable.Controller.Steps.Common;
|
|||||||
using Questionable.Controller.Steps.Gathering;
|
using Questionable.Controller.Steps.Gathering;
|
||||||
using Questionable.Controller.Steps.Interactions;
|
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.GatheringPaths;
|
using Questionable.GatheringPaths;
|
||||||
using Questionable.Model.Gathering;
|
using Questionable.Model.Gathering;
|
||||||
@ -22,25 +22,31 @@ namespace Questionable.Controller;
|
|||||||
internal sealed unsafe class GatheringController : MiniTaskController<GatheringController>
|
internal sealed unsafe class GatheringController : MiniTaskController<GatheringController>
|
||||||
{
|
{
|
||||||
private readonly MovementController _movementController;
|
private readonly MovementController _movementController;
|
||||||
private readonly GatheringData _gatheringData;
|
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
private readonly NavmeshIpc _navmeshIpc;
|
private readonly NavmeshIpc _navmeshIpc;
|
||||||
private readonly IObjectTable _objectTable;
|
private readonly IObjectTable _objectTable;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ICondition _condition;
|
||||||
|
|
||||||
private CurrentRequest? _currentRequest;
|
private CurrentRequest? _currentRequest;
|
||||||
|
|
||||||
public GatheringController(MovementController movementController, GatheringData gatheringData,
|
public GatheringController(
|
||||||
GameFunctions gameFunctions, NavmeshIpc navmeshIpc, IObjectTable objectTable, IChatGui chatGui,
|
MovementController movementController,
|
||||||
ILogger<GatheringController> logger, IServiceProvider serviceProvider)
|
GameFunctions gameFunctions,
|
||||||
|
NavmeshIpc navmeshIpc,
|
||||||
|
IObjectTable objectTable,
|
||||||
|
IChatGui chatGui,
|
||||||
|
ILogger<GatheringController> logger,
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
ICondition condition)
|
||||||
: base(chatGui, logger)
|
: base(chatGui, logger)
|
||||||
{
|
{
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
_gatheringData = gatheringData;
|
|
||||||
_gameFunctions = gameFunctions;
|
_gameFunctions = gameFunctions;
|
||||||
_navmeshIpc = navmeshIpc;
|
_navmeshIpc = navmeshIpc;
|
||||||
_objectTable = objectTable;
|
_objectTable = objectTable;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
|
_condition = condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Start(GatheringRequest gatheringRequest)
|
public bool Start(GatheringRequest gatheringRequest)
|
||||||
@ -58,7 +64,8 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
Data = gatheringRequest,
|
Data = gatheringRequest,
|
||||||
Root = gatheringRoot,
|
Root = gatheringRoot,
|
||||||
Nodes = gatheringRoot.Groups
|
Nodes = gatheringRoot.Groups
|
||||||
.SelectMany(x => x.Nodes)
|
// at least in EW-ish, there's one node with 1 fixed location and one node with 3 random locations
|
||||||
|
.SelectMany(x => x.Nodes.OrderBy(y => y.Locations.Count))
|
||||||
.ToList(),
|
.ToList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,7 +86,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
if (_movementController.IsPathfinding || _movementController.IsPathfinding)
|
if (_movementController.IsPathfinding || _movementController.IsPathfinding)
|
||||||
return EStatus.Moving;
|
return EStatus.Moving;
|
||||||
|
|
||||||
if (HasRequestedItems())
|
if (HasRequestedItems() && !_condition[ConditionFlag.Gathering])
|
||||||
return EStatus.Complete;
|
return EStatus.Complete;
|
||||||
|
|
||||||
if (_currentTask == null && _taskQueue.Count == 0)
|
if (_currentTask == null && _taskQueue.Count == 0)
|
||||||
@ -118,12 +125,13 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
Y = currentNode.Locations.Select(x => x.Position.Y).Max() + 5f,
|
Y = currentNode.Locations.Select(x => x.Position.Y).Max() + 5f,
|
||||||
Z = currentNode.Locations.Sum(x => x.Position.Z) / currentNode.Locations.Count,
|
Z = currentNode.Locations.Sum(x => x.Position.Z) / currentNode.Locations.Count,
|
||||||
};
|
};
|
||||||
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(averagePosition);
|
bool fly = _gameFunctions.IsFlyingUnlocked(_currentRequest.Root.TerritoryId);
|
||||||
|
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(averagePosition, true);
|
||||||
if (pointOnFloor != null)
|
if (pointOnFloor != null)
|
||||||
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + 3f };
|
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) };
|
||||||
|
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Move.MoveInternal>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Move.MoveInternal>()
|
||||||
.With(_currentRequest.Root.TerritoryId, pointOnFloor ?? averagePosition, 50f, fly: true,
|
.With(_currentRequest.Root.TerritoryId, pointOnFloor ?? averagePosition, 50f, fly: fly,
|
||||||
ignoreDistanceToObject: true));
|
ignoreDistanceToObject: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +139,13 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
.With(_currentRequest.Root.TerritoryId, currentNode));
|
.With(_currentRequest.Root.TerritoryId, currentNode));
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||||
.With(currentNode.DataId, true));
|
.With(currentNode.DataId, true));
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<WaitGather>());
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
|
||||||
|
.With(_currentRequest.Data, currentNode));
|
||||||
|
if (_currentRequest.Data.Collectability > 0)
|
||||||
|
{
|
||||||
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGatherCollectable>()
|
||||||
|
.With(_currentRequest.Data, currentNode));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasRequestedItems()
|
private bool HasRequestedItems()
|
||||||
@ -144,7 +158,13 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
return inventoryManager->GetInventoryItemCount(_currentRequest.Data.ItemId,
|
return inventoryManager->GetInventoryItemCount(_currentRequest.Data.ItemId,
|
||||||
minCollectability: _currentRequest.Data.Collectability) >= _currentRequest.Data.Quantity;
|
minCollectability: (short)_currentRequest.Data.Collectability) >= _currentRequest.Data.Quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasNodeDisappeared(GatheringNode node)
|
||||||
|
{
|
||||||
|
return !_objectTable.Any(x =>
|
||||||
|
x.ObjectKind == ObjectKind.GatheringPoint && x.IsTargetable && x.DataId == node.DataId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IList<string> GetRemainingTaskNames()
|
public override IList<string> GetRemainingTaskNames()
|
||||||
@ -168,7 +188,11 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
public int CurrentIndex { get; set; }
|
public int CurrentIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record GatheringRequest(ushort GatheringPointId, uint ItemId, int Quantity, short Collectability = 0);
|
public sealed record GatheringRequest(
|
||||||
|
ushort GatheringPointId,
|
||||||
|
uint ItemId,
|
||||||
|
int Quantity,
|
||||||
|
ushort Collectability = 0);
|
||||||
|
|
||||||
public enum EStatus
|
public enum EStatus
|
||||||
{
|
{
|
||||||
|
@ -260,7 +260,7 @@ internal sealed class MovementController : IDisposable
|
|||||||
|
|
||||||
private bool IsOnFlightPath(Vector3 p)
|
private bool IsOnFlightPath(Vector3 p)
|
||||||
{
|
{
|
||||||
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(p);
|
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(p, true);
|
||||||
return pointOnFloor != null && Math.Abs(pointOnFloor.Value.Y - p.Y) > 0.5f;
|
return pointOnFloor != null && Math.Abs(pointOnFloor.Value.Y - p.Y) > 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,35 +461,44 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
_combatController.Stop("Execute next step");
|
_combatController.Stop("Execute next step");
|
||||||
_gatheringController.Stop("Execute next step");
|
_gatheringController.Stop("Execute next step");
|
||||||
|
|
||||||
var newTasks = _taskFactories
|
try
|
||||||
.SelectMany(x =>
|
|
||||||
{
|
|
||||||
IList<ITask> tasks = x.CreateAllTasks(CurrentQuest.Quest, seq, step).ToList();
|
|
||||||
|
|
||||||
if (tasks.Count > 0 && _logger.IsEnabled(LogLevel.Trace))
|
|
||||||
{
|
|
||||||
string factoryName = x.GetType().FullName ?? x.GetType().Name;
|
|
||||||
if (factoryName.Contains('.', StringComparison.Ordinal))
|
|
||||||
factoryName = factoryName[(factoryName.LastIndexOf('.') + 1)..];
|
|
||||||
|
|
||||||
_logger.LogTrace("Factory {FactoryName} created Task {TaskNames}",
|
|
||||||
factoryName, string.Join(", ", tasks.Select(y => y.ToString())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return tasks;
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
if (newTasks.Count == 0)
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Nothing to execute for step?");
|
var newTasks = _taskFactories
|
||||||
return;
|
.SelectMany(x =>
|
||||||
}
|
{
|
||||||
|
IList<ITask> tasks = x.CreateAllTasks(CurrentQuest.Quest, seq, step).ToList();
|
||||||
|
|
||||||
_logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}",
|
if (tasks.Count > 0 && _logger.IsEnabled(LogLevel.Trace))
|
||||||
CurrentQuest.Quest.QuestId, seq.Sequence, seq.Steps.IndexOf(step),
|
{
|
||||||
string.Join(", ", newTasks.Select(x => x.ToString())));
|
string factoryName = x.GetType().FullName ?? x.GetType().Name;
|
||||||
foreach (var task in newTasks)
|
if (factoryName.Contains('.', StringComparison.Ordinal))
|
||||||
_taskQueue.Enqueue(task);
|
factoryName = factoryName[(factoryName.LastIndexOf('.') + 1)..];
|
||||||
|
|
||||||
|
_logger.LogTrace("Factory {FactoryName} created Task {TaskNames}",
|
||||||
|
factoryName, string.Join(", ", tasks.Select(y => y.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
if (newTasks.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Nothing to execute for step?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}",
|
||||||
|
CurrentQuest.Quest.QuestId, seq.Sequence, seq.Steps.IndexOf(step),
|
||||||
|
string.Join(", ", newTasks.Select(x => x.ToString())));
|
||||||
|
foreach (var task in newTasks)
|
||||||
|
_taskQueue.Enqueue(task);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Failed to create tasks");
|
||||||
|
_chatGui.PrintError("[Questionable] Failed to start next task sequence, please check /xllog for details.");
|
||||||
|
Stop("Tasks failed to create");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToStatString()
|
public string ToStatString()
|
||||||
|
77
Questionable/Controller/Steps/Gathering/DoGather.cs
Normal file
77
Questionable/Controller/Steps/Gathering/DoGather.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using LLib.GameUI;
|
||||||
|
using Questionable.Model.Gathering;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
|
internal sealed class DoGather(
|
||||||
|
GatheringController gatheringController,
|
||||||
|
IGameGui gameGui,
|
||||||
|
ICondition condition) : ITask
|
||||||
|
{
|
||||||
|
private GatheringController.GatheringRequest _currentRequest = null!;
|
||||||
|
private GatheringNode _currentNode = null!;
|
||||||
|
private bool _wasGathering;
|
||||||
|
private List<SlotInfo>? _slots;
|
||||||
|
|
||||||
|
|
||||||
|
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode)
|
||||||
|
{
|
||||||
|
_currentRequest = currentRequest;
|
||||||
|
_currentNode = currentNode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Start() => true;
|
||||||
|
|
||||||
|
public unsafe ETaskResult Update()
|
||||||
|
{
|
||||||
|
if (gatheringController.HasNodeDisappeared(_currentNode))
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
|
if (condition[ConditionFlag.Gathering])
|
||||||
|
{
|
||||||
|
if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* _))
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
|
_wasGathering = true;
|
||||||
|
|
||||||
|
if (gameGui.TryGetAddonByName("Gathering", out AtkUnitBase* atkUnitBase))
|
||||||
|
{
|
||||||
|
_slots ??= ReadSlots(atkUnitBase);
|
||||||
|
var slot = _slots.Single(x => x.ItemId == _currentRequest.ItemId);
|
||||||
|
atkUnitBase->FireCallbackInt(slot.Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _wasGathering && !condition[ConditionFlag.Gathering]
|
||||||
|
? ETaskResult.TaskComplete
|
||||||
|
: ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe List<SlotInfo> ReadSlots(AtkUnitBase* atkUnitBase)
|
||||||
|
{
|
||||||
|
var atkValues = atkUnitBase->AtkValues;
|
||||||
|
List<SlotInfo> slots = new List<SlotInfo>();
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
// +8 = new item?
|
||||||
|
uint itemId = atkValues[i * 11 + 7].UInt;
|
||||||
|
if (itemId == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var slot = new SlotInfo(i, itemId);
|
||||||
|
slots.Add(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => "DoGather";
|
||||||
|
|
||||||
|
private sealed record SlotInfo(int Index, uint ItemId);
|
||||||
|
}
|
151
Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs
Normal file
151
Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using LLib.GameUI;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Model.Gathering;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.Steps.Gathering;
|
||||||
|
|
||||||
|
internal sealed class DoGatherCollectable(
|
||||||
|
GatheringController gatheringController,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
|
IClientState clientState,
|
||||||
|
IGameGui gameGui,
|
||||||
|
ILogger<DoGatherCollectable> logger) : ITask
|
||||||
|
{
|
||||||
|
private GatheringController.GatheringRequest _currentRequest = null!;
|
||||||
|
private GatheringNode _currentNode = null!;
|
||||||
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
|
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode)
|
||||||
|
{
|
||||||
|
_currentRequest = currentRequest;
|
||||||
|
_currentNode = currentNode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Start() => true;
|
||||||
|
|
||||||
|
public ETaskResult Update()
|
||||||
|
{
|
||||||
|
if (gatheringController.HasNodeDisappeared(_currentNode))
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
|
NodeCondition? nodeCondition = GetNodeCondition();
|
||||||
|
if (nodeCondition == null)
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
|
||||||
|
if (_actionQueue != null && _actionQueue.TryPeek(out EAction nextAction))
|
||||||
|
{
|
||||||
|
if (gameFunctions.UseAction(nextAction))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Used action {Action} on node", nextAction);
|
||||||
|
_actionQueue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeCondition.CollectabilityToGoal(_currentRequest.Collectability) > 0)
|
||||||
|
{
|
||||||
|
_actionQueue = GetNextActions(nodeCondition);
|
||||||
|
if (_actionQueue != null)
|
||||||
|
{
|
||||||
|
foreach (var action in _actionQueue)
|
||||||
|
logger.LogInformation("Next Actions {Action}", action);
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_actionQueue = new Queue<EAction>();
|
||||||
|
_actionQueue.Enqueue(PickAction(EAction.CollectMiner, EAction.CollectBotanist));
|
||||||
|
return ETaskResult.StillRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe NodeCondition? GetNodeCondition()
|
||||||
|
{
|
||||||
|
if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* atkUnitBase))
|
||||||
|
{
|
||||||
|
var atkValues = atkUnitBase->AtkValues;
|
||||||
|
return new NodeCondition(
|
||||||
|
CurrentCollectability: atkValues[13].UInt,
|
||||||
|
MaxCollectability: atkValues[14].UInt,
|
||||||
|
CurrentIntegrity: atkValues[62].UInt,
|
||||||
|
MaxIntegrity: atkValues[63].UInt,
|
||||||
|
ScrutinyActive: atkValues[80].Bool,
|
||||||
|
CollectabilityFromScour: atkValues[48].UInt,
|
||||||
|
CollectabilityFromMeticulous: atkValues[51].UInt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Queue<EAction>? GetNextActions(NodeCondition nodeCondition)
|
||||||
|
{
|
||||||
|
uint gp = clientState.LocalPlayer!.CurrentGp;
|
||||||
|
Queue<EAction> actions = new();
|
||||||
|
|
||||||
|
uint neededCollectability = nodeCondition.CollectabilityToGoal(_currentRequest.Collectability);
|
||||||
|
if (neededCollectability <= nodeCondition.CollectabilityFromMeticulous)
|
||||||
|
{
|
||||||
|
actions.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neededCollectability <= nodeCondition.CollectabilityFromScour)
|
||||||
|
{
|
||||||
|
actions.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// neither action directly solves our problem
|
||||||
|
if (!nodeCondition.ScrutinyActive && gp >= 200)
|
||||||
|
{
|
||||||
|
actions.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeCondition.ScrutinyActive)
|
||||||
|
{
|
||||||
|
actions.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actions.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EAction PickAction(EAction minerAction, EAction botanistAction)
|
||||||
|
{
|
||||||
|
if (clientState.LocalPlayer?.ClassJob.Id == 16)
|
||||||
|
return minerAction;
|
||||||
|
else
|
||||||
|
return botanistAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() =>
|
||||||
|
$"DoGatherCollectable({SeIconChar.Collectible.ToIconString()} {_currentRequest.Collectability})";
|
||||||
|
|
||||||
|
private sealed record NodeCondition(
|
||||||
|
uint CurrentCollectability,
|
||||||
|
uint MaxCollectability,
|
||||||
|
uint CurrentIntegrity,
|
||||||
|
uint MaxIntegrity,
|
||||||
|
bool ScrutinyActive,
|
||||||
|
uint CollectabilityFromScour,
|
||||||
|
uint CollectabilityFromMeticulous)
|
||||||
|
{
|
||||||
|
public uint CollectabilityToGoal(uint goal)
|
||||||
|
{
|
||||||
|
if (goal >= CurrentCollectability)
|
||||||
|
return goal - CurrentCollectability;
|
||||||
|
return CurrentCollectability == 0 ? 1u : 0u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ namespace Questionable.Controller.Steps.Gathering;
|
|||||||
|
|
||||||
internal sealed class MoveToLandingLocation(
|
internal sealed class MoveToLandingLocation(
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
|
GameFunctions gameFunctions,
|
||||||
IObjectTable objectTable,
|
IObjectTable objectTable,
|
||||||
NavmeshIpc navmeshIpc,
|
NavmeshIpc navmeshIpc,
|
||||||
ILogger<MoveToLandingLocation> logger) : ITask
|
ILogger<MoveToLandingLocation> logger) : ITask
|
||||||
@ -36,8 +37,11 @@ internal sealed class MoveToLandingLocation(
|
|||||||
var location = _gatheringNode.Locations.First();
|
var location = _gatheringNode.Locations.First();
|
||||||
if (_gatheringNode.Locations.Count > 1)
|
if (_gatheringNode.Locations.Count > 1)
|
||||||
{
|
{
|
||||||
var gameObject = objectTable.Single(x =>
|
var gameObject = objectTable.SingleOrDefault(x =>
|
||||||
x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == _gatheringNode.DataId && x.IsTargetable);
|
x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == _gatheringNode.DataId && x.IsTargetable);
|
||||||
|
if (gameObject == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
location = _gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
|
location = _gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,15 +49,26 @@ internal sealed class MoveToLandingLocation(
|
|||||||
logger.LogInformation("Preliminary landing location: {Location}, with degrees = {Degrees}, range = {Range}",
|
logger.LogInformation("Preliminary landing location: {Location}, with degrees = {Degrees}, range = {Range}",
|
||||||
target.ToString("G", CultureInfo.InvariantCulture), degrees, range);
|
target.ToString("G", CultureInfo.InvariantCulture), degrees, range);
|
||||||
|
|
||||||
Vector3? pointOnFloor = navmeshIpc.GetPointOnFloor(target with { Y = target.Y + 5f });
|
bool fly = gameFunctions.IsFlyingUnlocked(_territoryId);
|
||||||
|
Vector3? pointOnFloor = navmeshIpc.GetPointOnFloor(target with { Y = target.Y + 5f }, false);
|
||||||
if (pointOnFloor != null)
|
if (pointOnFloor != null)
|
||||||
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + 0.5f };
|
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 0.5f : 0f) };
|
||||||
|
|
||||||
|
// since we only allow points that can be landed on, the distance is important but the angle shouldn't matter
|
||||||
|
if (pointOnFloor != null && Vector3.Distance(pointOnFloor.Value, location.Position) >
|
||||||
|
location.CalculateMaximumDistance())
|
||||||
|
{
|
||||||
|
pointOnFloor = location.Position + Vector3.Normalize(pointOnFloor.Value - location.Position) * location.CalculateMaximumDistance();
|
||||||
|
logger.LogInformation("Adjusted landing location: {Location}", pointOnFloor.Value.ToString("G", CultureInfo.InvariantCulture)); }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogInformation("Final landing location: {Location}",
|
||||||
|
(pointOnFloor ?? target).ToString("G", CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
logger.LogInformation("Final landing location: {Location}",
|
|
||||||
(pointOnFloor ?? target).ToString("G", CultureInfo.InvariantCulture));
|
|
||||||
|
|
||||||
_moveTask = serviceProvider.GetRequiredService<Move.MoveInternal>()
|
_moveTask = serviceProvider.GetRequiredService<Move.MoveInternal>()
|
||||||
.With(_territoryId, pointOnFloor ?? target, 0.25f, dataId: _gatheringNode.DataId, fly: true,
|
.With(_territoryId, pointOnFloor ?? target, 0.25f, dataId: _gatheringNode.DataId, fly: fly,
|
||||||
ignoreDistanceToObject: true);
|
ignoreDistanceToObject: true);
|
||||||
return _moveTask.Start();
|
return _moveTask.Start();
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
using Dalamud.Game.ClientState.Conditions;
|
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
|
|
||||||
namespace Questionable.Controller.Steps.Gathering;
|
|
||||||
|
|
||||||
internal sealed class WaitGather(ICondition condition) : ITask
|
|
||||||
{
|
|
||||||
private bool _wasGathering;
|
|
||||||
|
|
||||||
public bool Start() => true;
|
|
||||||
|
|
||||||
public ETaskResult Update()
|
|
||||||
{
|
|
||||||
if (condition[ConditionFlag.Gathering])
|
|
||||||
{
|
|
||||||
_wasGathering = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _wasGathering && !condition[ConditionFlag.Gathering]
|
|
||||||
? ETaskResult.TaskComplete
|
|
||||||
: ETaskResult.StillRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => "WaitGather";
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
@ -70,6 +71,12 @@ internal static class GatheringRequiredItems
|
|||||||
return ETaskResult.StillRunning;
|
return ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId})";
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (_gatheredItem.Collectability == 0)
|
||||||
|
return $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId})";
|
||||||
|
else
|
||||||
|
return $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {_gatheredItem.Collectability})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
Questionable/External/NavmeshIpc.cs
vendored
4
Questionable/External/NavmeshIpc.cs
vendored
@ -108,11 +108,11 @@ internal sealed class NavmeshIpc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3? GetPointOnFloor(Vector3 position)
|
public Vector3? GetPointOnFloor(Vector3 position, bool unlandable)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _queryPointOnFloor.InvokeFunc(position, true, 1);
|
return _queryPointOnFloor.InvokeFunc(position, unlandable, 0.2f);
|
||||||
}
|
}
|
||||||
catch (IpcError)
|
catch (IpcError)
|
||||||
{
|
{
|
||||||
|
@ -743,7 +743,7 @@ internal sealed unsafe class GameFunctions
|
|||||||
_condition[ConditionFlag.OccupiedInQuestEvent] || _condition[ConditionFlag.OccupiedInCutSceneEvent] ||
|
_condition[ConditionFlag.OccupiedInQuestEvent] || _condition[ConditionFlag.OccupiedInCutSceneEvent] ||
|
||||||
_condition[ConditionFlag.Casting] || _condition[ConditionFlag.Unknown57] ||
|
_condition[ConditionFlag.Casting] || _condition[ConditionFlag.Unknown57] ||
|
||||||
_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] ||
|
_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] ||
|
||||||
_condition[ConditionFlag.Jumping61];
|
_condition[ConditionFlag.Jumping61] || _condition[ConditionFlag.Gathering42];
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLoadingScreenVisible()
|
public bool IsLoadingScreenVisible()
|
||||||
|
@ -105,7 +105,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
serviceCollection.AddTransient<MountTask>();
|
serviceCollection.AddTransient<MountTask>();
|
||||||
serviceCollection.AddTransient<UnmountTask>();
|
serviceCollection.AddTransient<UnmountTask>();
|
||||||
serviceCollection.AddTransient<MoveToLandingLocation>();
|
serviceCollection.AddTransient<MoveToLandingLocation>();
|
||||||
serviceCollection.AddTransient<WaitGather>();
|
serviceCollection.AddTransient<DoGather>();
|
||||||
|
serviceCollection.AddTransient<DoGatherCollectable>();
|
||||||
|
|
||||||
// task factories
|
// task factories
|
||||||
serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
|
serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
|
||||||
|
Loading…
Reference in New Issue
Block a user