Implement min/btn collectable logic
This commit is contained in:
parent
82c20bf76d
commit
f04233a325
@ -93,7 +93,7 @@ internal sealed class EditorCommands : IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
(targetFile, root) = CreateNewFile(gatheringPoint, target, string.Join(" ", arguments));
|
||||
(targetFile, root) = CreateNewFile(gatheringPoint, target);
|
||||
_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,
|
||||
string fileName)
|
||||
public (FileInfo targetFile, GatheringRoot root) CreateNewFile(GatheringPoint gatheringPoint, IGameObject target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fileName))
|
||||
throw new ArgumentException(nameof(fileName));
|
||||
|
||||
// determine target folder
|
||||
DirectoryInfo? targetFolder = _plugin.GetLocationsInTerritory(_clientState.TerritoryType).FirstOrDefault()
|
||||
?.File.Directory;
|
||||
@ -183,7 +179,8 @@ internal sealed class EditorCommands : IDisposable
|
||||
|
||||
FileInfo targetFile =
|
||||
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
|
||||
{
|
||||
TerritoryId = _clientState.TerritoryType,
|
||||
|
@ -7,6 +7,7 @@ using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin.Services;
|
||||
using ImGuiNET;
|
||||
@ -31,8 +32,6 @@ internal sealed class EditorWindow : Window
|
||||
private (RendererPlugin.GatheringLocationContext Context, GatheringNode Node, GatheringLocation Location)?
|
||||
_targetLocation;
|
||||
|
||||
private string _newFileName = string.Empty;
|
||||
|
||||
public EditorWindow(RendererPlugin plugin, EditorCommands editorCommands, IDataManager dataManager,
|
||||
ITargetManager targetManager, IClientState clientState, IObjectTable objectTable)
|
||||
: base("Gathering Path Editor###QuestionableGatheringPathEditor")
|
||||
@ -68,13 +67,19 @@ internal sealed class EditorWindow : Window
|
||||
})
|
||||
.Select(location => new { Context = context, Node = node, Location = location }))))
|
||||
.FirstOrDefault();
|
||||
if (_target != null && _target.ObjectKind != ObjectKind.GatheringPoint || location == null)
|
||||
if (_target != null && _target.ObjectKind != ObjectKind.GatheringPoint)
|
||||
{
|
||||
_target = null;
|
||||
_targetLocation = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (location == null)
|
||||
{
|
||||
_targetLocation = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_target ??= _objectTable.FirstOrDefault(
|
||||
x => x.ObjectKind == ObjectKind.GatheringPoint &&
|
||||
x.DataId == location.Node.DataId &&
|
||||
@ -123,13 +128,18 @@ internal sealed class EditorWindow : Window
|
||||
_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"))
|
||||
{
|
||||
location.MinimumAngle = locationOverride.MinimumAngle;
|
||||
location.MaximumAngle = locationOverride.MaximumAngle;
|
||||
_plugin.Save(context.File, context.Root);
|
||||
}
|
||||
if (unsaved)
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset"))
|
||||
@ -189,16 +199,11 @@ internal sealed class EditorWindow : Window
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.InputText("File Name", ref _newFileName, 128);
|
||||
ImGui.BeginDisabled(string.IsNullOrEmpty(_newFileName));
|
||||
if (ImGui.Button("Create location"))
|
||||
{
|
||||
var (targetFile, root) = _editorCommands.CreateNewFile(gatheringPoint, _target, _newFileName);
|
||||
var (targetFile, root) = _editorCommands.CreateNewFile(gatheringPoint, _target);
|
||||
_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,
|
||||
"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",
|
||||
"Author": "liza",
|
||||
"Disabled": true,
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
@ -30,13 +29,19 @@
|
||||
},
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "CompleteQuest",
|
||||
"AetheryteShortcut": "Old Sharlayan",
|
||||
"AethernetShortcut": [
|
||||
"[Old Sharlayan] Aetheryte Plaza",
|
||||
"[Old Sharlayan] The Studium"
|
||||
],
|
||||
"RequiredGatheredItems": [
|
||||
{
|
||||
"ItemId": 35600,
|
||||
"ItemCount": 6,
|
||||
"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,
|
||||
YellowGulal = 29383,
|
||||
BlueGulal = 29384,
|
||||
|
||||
CollectMiner = 240,
|
||||
ScourMiner = 22182,
|
||||
MeticulousMiner = 22184,
|
||||
ScrutinyMiner = 22185,
|
||||
|
||||
CollectBotanist = 815,
|
||||
ScourBotanist = 22186,
|
||||
MeticulousBotanist = 22188,
|
||||
ScrutinyBotanist = 22189,
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class EActionExtensions
|
||||
|
@ -4,5 +4,5 @@ public sealed class GatheredItem
|
||||
{
|
||||
public uint ItemId { 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.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
@ -12,7 +13,6 @@ using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Gathering;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.GatheringPaths;
|
||||
using Questionable.Model.Gathering;
|
||||
@ -22,25 +22,31 @@ namespace Questionable.Controller;
|
||||
internal sealed unsafe class GatheringController : MiniTaskController<GatheringController>
|
||||
{
|
||||
private readonly MovementController _movementController;
|
||||
private readonly GatheringData _gatheringData;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
private readonly NavmeshIpc _navmeshIpc;
|
||||
private readonly IObjectTable _objectTable;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ICondition _condition;
|
||||
|
||||
private CurrentRequest? _currentRequest;
|
||||
|
||||
public GatheringController(MovementController movementController, GatheringData gatheringData,
|
||||
GameFunctions gameFunctions, NavmeshIpc navmeshIpc, IObjectTable objectTable, IChatGui chatGui,
|
||||
ILogger<GatheringController> logger, IServiceProvider serviceProvider)
|
||||
public GatheringController(
|
||||
MovementController movementController,
|
||||
GameFunctions gameFunctions,
|
||||
NavmeshIpc navmeshIpc,
|
||||
IObjectTable objectTable,
|
||||
IChatGui chatGui,
|
||||
ILogger<GatheringController> logger,
|
||||
IServiceProvider serviceProvider,
|
||||
ICondition condition)
|
||||
: base(chatGui, logger)
|
||||
{
|
||||
_movementController = movementController;
|
||||
_gatheringData = gatheringData;
|
||||
_gameFunctions = gameFunctions;
|
||||
_navmeshIpc = navmeshIpc;
|
||||
_objectTable = objectTable;
|
||||
_serviceProvider = serviceProvider;
|
||||
_condition = condition;
|
||||
}
|
||||
|
||||
public bool Start(GatheringRequest gatheringRequest)
|
||||
@ -58,7 +64,8 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
||||
Data = gatheringRequest,
|
||||
Root = gatheringRoot,
|
||||
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(),
|
||||
};
|
||||
|
||||
@ -79,7 +86,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
||||
if (_movementController.IsPathfinding || _movementController.IsPathfinding)
|
||||
return EStatus.Moving;
|
||||
|
||||
if (HasRequestedItems())
|
||||
if (HasRequestedItems() && !_condition[ConditionFlag.Gathering])
|
||||
return EStatus.Complete;
|
||||
|
||||
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,
|
||||
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)
|
||||
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>()
|
||||
.With(_currentRequest.Root.TerritoryId, pointOnFloor ?? averagePosition, 50f, fly: true,
|
||||
.With(_currentRequest.Root.TerritoryId, pointOnFloor ?? averagePosition, 50f, fly: fly,
|
||||
ignoreDistanceToObject: true));
|
||||
}
|
||||
|
||||
@ -131,7 +139,13 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
||||
.With(_currentRequest.Root.TerritoryId, currentNode));
|
||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||
.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()
|
||||
@ -144,7 +158,13 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
||||
return false;
|
||||
|
||||
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()
|
||||
@ -168,7 +188,11 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
||||
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
|
||||
{
|
||||
|
@ -260,7 +260,7 @@ internal sealed class MovementController : IDisposable
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -461,6 +461,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
_combatController.Stop("Execute next step");
|
||||
_gatheringController.Stop("Execute next step");
|
||||
|
||||
try
|
||||
{
|
||||
var newTasks = _taskFactories
|
||||
.SelectMany(x =>
|
||||
{
|
||||
@ -491,6 +493,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
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()
|
||||
{
|
||||
|
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(
|
||||
IServiceProvider serviceProvider,
|
||||
GameFunctions gameFunctions,
|
||||
IObjectTable objectTable,
|
||||
NavmeshIpc navmeshIpc,
|
||||
ILogger<MoveToLandingLocation> logger) : ITask
|
||||
@ -36,8 +37,11 @@ internal sealed class MoveToLandingLocation(
|
||||
var location = _gatheringNode.Locations.First();
|
||||
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);
|
||||
if (gameObject == null)
|
||||
return false;
|
||||
|
||||
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}",
|
||||
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)
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
_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);
|
||||
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.Collections.Generic;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Questionable.Data;
|
||||
@ -70,6 +71,12 @@ internal static class GatheringRequiredItems
|
||||
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
|
||||
{
|
||||
return _queryPointOnFloor.InvokeFunc(position, true, 1);
|
||||
return _queryPointOnFloor.InvokeFunc(position, unlandable, 0.2f);
|
||||
}
|
||||
catch (IpcError)
|
||||
{
|
||||
|
@ -743,7 +743,7 @@ internal sealed unsafe class GameFunctions
|
||||
_condition[ConditionFlag.OccupiedInQuestEvent] || _condition[ConditionFlag.OccupiedInCutSceneEvent] ||
|
||||
_condition[ConditionFlag.Casting] || _condition[ConditionFlag.Unknown57] ||
|
||||
_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] ||
|
||||
_condition[ConditionFlag.Jumping61];
|
||||
_condition[ConditionFlag.Jumping61] || _condition[ConditionFlag.Gathering42];
|
||||
}
|
||||
|
||||
public bool IsLoadingScreenVisible()
|
||||
|
@ -105,7 +105,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddTransient<MountTask>();
|
||||
serviceCollection.AddTransient<UnmountTask>();
|
||||
serviceCollection.AddTransient<MoveToLandingLocation>();
|
||||
serviceCollection.AddTransient<WaitGather>();
|
||||
serviceCollection.AddTransient<DoGather>();
|
||||
serviceCollection.AddTransient<DoGatherCollectable>();
|
||||
|
||||
// task factories
|
||||
serviceCollection.AddTaskWithFactory<StepDisabled.Factory, StepDisabled.Task>();
|
||||
|
Loading…
Reference in New Issue
Block a user