Load gathering points dynamically, BTN 65-70

This commit is contained in:
Liza 2024-08-05 20:00:02 +02:00
parent e186d34f0d
commit 5c3584b88d
Signed by: liza
GPG Key ID: 7199F8D727D55F67
23 changed files with 1290 additions and 32 deletions

View File

@ -52,6 +52,13 @@ internal sealed class EditorWindow : Window
public override void Update() public override void Update()
{ {
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
{
_target = null;
_targetLocation = null;
return;
}
_target = _targetManager.Target; _target = _targetManager.Target;
var gatheringLocations = _plugin.GetLocationsInTerritory(_clientState.TerritoryType); var gatheringLocations = _plugin.GetLocationsInTerritory(_clientState.TerritoryType);
var location = gatheringLocations.SelectMany(context => var location = gatheringLocations.SelectMany(context =>
@ -63,7 +70,7 @@ internal sealed class EditorWindow : Window
if (_target != null) if (_target != null)
distance = Vector3.Distance(location.Position, _target.Position); distance = Vector3.Distance(location.Position, _target.Position);
else else
distance = Vector3.Distance(location.Position, _clientState.LocalPlayer!.Position); distance = Vector3.Distance(location.Position, _clientState.LocalPlayer.Position);
return new { Context = context, Node = node, Location = location, Distance = distance }; return new { Context = context, Node = node, Location = location, Distance = distance };
}) })
@ -86,7 +93,7 @@ internal sealed class EditorWindow : Window
.Select(x => new .Select(x => new
{ {
Object = x, Object = x,
Distance = Vector3.Distance(location.Location.Position, _clientState.LocalPlayer!.Position) Distance = Vector3.Distance(location.Location.Position, _clientState.LocalPlayer.Position)
}) })
.Where(x => x.Distance < 3f) .Where(x => x.Distance < 3f)
.OrderBy(x => x.Distance) .OrderBy(x => x.Distance)

View File

@ -0,0 +1,38 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": "liza",
"TerritoryId": 621,
"AetheryteShortcut": "Lochs - Porta Praetoria",
"Groups": [
{
"Nodes": [
{
"DataId": 32274,
"Locations": [
{
"Position": {
"X": -737.3265,
"Y": 76.50858,
"Z": -591.2296
}
},
{
"Position": {
"X": -787.0406,
"Y": 78.22382,
"Z": -640.0532
}
},
{
"Position": {
"X": -670.0129,
"Y": 70.42876,
"Z": -645.2087
}
}
]
}
]
}
]
}

View File

@ -2,6 +2,7 @@
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": "liza", "Author": "liza",
"TerritoryId": 613, "TerritoryId": 613,
"AetheryteShortcut": "Ruby Sea - Onokoro",
"Groups": [ "Groups": [
{ {
"Nodes": [ "Nodes": [

View File

@ -0,0 +1,133 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": "liza",
"TerritoryId": 613,
"AetheryteShortcut": "Ruby Sea - Onokoro",
"Groups": [
{
"Nodes": [
{
"DataId": 32223,
"Locations": [
{
"Position": {
"X": -741.6174,
"Y": 6.276045,
"Z": -639.2435
}
},
{
"Position": {
"X": -724.2017,
"Y": 2.514658,
"Z": -647.5651
}
}
]
},
{
"DataId": 32222,
"Locations": [
{
"Position": {
"X": -719.345,
"Y": 2.520793,
"Z": -645.692
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32218,
"Locations": [
{
"Position": {
"X": -664.7355,
"Y": 15.23751,
"Z": -801.2688
},
"MinimumAngle": -60,
"MaximumAngle": 185
}
]
},
{
"DataId": 32219,
"Locations": [
{
"Position": {
"X": -673.4941,
"Y": 16.74037,
"Z": -818.1156
}
},
{
"Position": {
"X": -677.8458,
"Y": 15.77772,
"Z": -809.6718
}
},
{
"Position": {
"X": -663.3188,
"Y": 14.57053,
"Z": -797.132
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32221,
"Locations": [
{
"Position": {
"X": -502.177,
"Y": 36.7399,
"Z": -724.8087
}
},
{
"Position": {
"X": -508.0492,
"Y": 33.89026,
"Z": -703.5477
}
},
{
"Position": {
"X": -490.3923,
"Y": 36.73062,
"Z": -685.7646
},
"MinimumAngle": -40,
"MaximumAngle": 150
}
]
},
{
"DataId": 32220,
"Locations": [
{
"Position": {
"X": -493.5531,
"Y": 36.08752,
"Z": -687.7089
},
"MinimumAngle": -70,
"MaximumAngle": 160
}
]
}
]
}
]
}

View File

@ -0,0 +1,134 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": "liza",
"TerritoryId": 613,
"AetheryteShortcut": "Ruby Sea - Tamamizu",
"Groups": [
{
"Nodes": [
{
"DataId": 32225,
"Locations": [
{
"Position": {
"X": 283.2321,
"Y": -105.8964,
"Z": -89.553
}
},
{
"Position": {
"X": 251.241,
"Y": -112.7794,
"Z": -103.9756
}
},
{
"Position": {
"X": 338.0264,
"Y": -86.76475,
"Z": -30.31015
}
}
]
},
{
"DataId": 32224,
"Locations": [
{
"Position": {
"X": 304.9474,
"Y": -98.91968,
"Z": -59.39315
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32228,
"Locations": [
{
"Position": {
"X": 260.0728,
"Y": -124.0964,
"Z": 68.48814
}
}
]
},
{
"DataId": 32229,
"Locations": [
{
"Position": {
"X": 265.4464,
"Y": -128.0422,
"Z": 88.30737
}
},
{
"Position": {
"X": 256.5239,
"Y": -113.9164,
"Z": -3.19258
}
},
{
"Position": {
"X": 237.8169,
"Y": -122.1193,
"Z": -35.11102
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32226,
"Locations": [
{
"Position": {
"X": 318.7197,
"Y": -88.4566,
"Z": 91.66042
}
}
]
},
{
"DataId": 32227,
"Locations": [
{
"Position": {
"X": 281.2504,
"Y": -113.1205,
"Z": 176.6557
}
},
{
"Position": {
"X": 309.1248,
"Y": -88.04077,
"Z": 109.5688
}
},
{
"Position": {
"X": 312.3843,
"Y": -95.51873,
"Z": 79.94166
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,131 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": "liza",
"TerritoryId": 613,
"AetheryteShortcut": "Ruby Sea - Onokoro",
"Groups": [
{
"Nodes": [
{
"DataId": 32282,
"Locations": [
{
"Position": {
"X": -332.5556,
"Y": -139.5151,
"Z": -276.7727
}
},
{
"Position": {
"X": -398.5437,
"Y": -137.4753,
"Z": -264.6068
}
}
]
},
{
"DataId": 32281,
"Locations": [
{
"Position": {
"X": -377.4165,
"Y": -151.0521,
"Z": -211.4394
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32277,
"Locations": [
{
"Position": {
"X": -509.283,
"Y": -107.1854,
"Z": -389.3152
}
}
]
},
{
"DataId": 32278,
"Locations": [
{
"Position": {
"X": -470.5726,
"Y": -93.28533,
"Z": -430.0346
}
},
{
"Position": {
"X": -549.3453,
"Y": -103.2194,
"Z": -352.9945
}
},
{
"Position": {
"X": -548.5269,
"Y": -99.45416,
"Z": -459.6198
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 32279,
"Locations": [
{
"Position": {
"X": -347.0872,
"Y": -72.09894,
"Z": -420.7435
},
"MinimumAngle": -80,
"MaximumAngle": 100
}
]
},
{
"DataId": 32280,
"Locations": [
{
"Position": {
"X": -410.6145,
"Y": -86.76735,
"Z": -432.2196
}
},
{
"Position": {
"X": -407.9324,
"Y": -108.0931,
"Z": -384.4449
}
},
{
"Position": {
"X": -341.0429,
"Y": -89.68655,
"Z": -396.5815
},
"MinimumAngle": -45,
"MaximumAngle": 110
}
]
}
]
}
]
}

View File

@ -16,13 +16,15 @@ public static partial class AssemblyGatheringLocationLoader
if (_locations == null) if (_locations == null)
{ {
_locations = []; _locations = [];
#if RELEASE
LoadLocations(); LoadLocations();
#endif
} }
return _locations ?? throw new InvalidOperationException("location data is not initialized"); return _locations ?? throw new InvalidOperationException("location data is not initialized");
} }
public static Stream QuestSchema => public static Stream GatheringSchema =>
typeof(AssemblyGatheringLocationLoader).Assembly.GetManifestResourceStream("Questionable.GatheringPaths.GatheringLocationSchema")!; typeof(AssemblyGatheringLocationLoader).Assembly.GetManifestResourceStream("Questionable.GatheringPaths.GatheringLocationSchema")!;
[SuppressMessage("ReSharper", "UnusedMember.Local")] [SuppressMessage("ReSharper", "UnusedMember.Local")]

View File

@ -26,7 +26,7 @@
<AdditionalFiles Include="..\Questionable.Model\common-schema.json" /> <AdditionalFiles Include="..\Questionable.Model\common-schema.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup Condition="'$(Configuration)' == 'Release'">
<None Remove="2.x - A Realm Reborn" /> <None Remove="2.x - A Realm Reborn" />
<None Remove="3.x - Heavensward" /> <None Remove="3.x - Heavensward" />
<None Remove="4.x - Stormblood" /> <None Remove="4.x - Stormblood" />

View File

@ -0,0 +1,216 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1021349,
"Position": {
"X": -43.930786,
"Y": 209.23637,
"Z": -90.77594
},
"TerritoryId": 478,
"InteractionType": "AcceptQuest",
"AetheryteShortcut": "Idyllshire",
"AethernetShortcut": [
"[Idyllshire] Aetheryte Plaza",
"[Idyllshire] West Idyllshire"
],
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1000815,
"Position": {
"X": -233.9361,
"Y": 6.668152,
"Z": -171.03839
},
"TerritoryId": 133,
"InteractionType": "Interact",
"AetheryteShortcut": "Gridania",
"AethernetShortcut": [
"[Gridania] Aetheryte Plaza",
"[Gridania] Botanists' Guild"
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1002659,
"Position": {
"X": 532.8297,
"Y": 88.99998,
"Z": -72.09894
},
"TerritoryId": 135,
"InteractionType": "Interact",
"AetheryteShortcut": "Limsa Lominsa",
"AethernetShortcut": [
"[Limsa Lominsa] Aetheryte Plaza",
"[Limsa Lominsa] Tempest Gate (Lower La Noscea)"
],
"SkipConditions": {
"AetheryteShortcutIf": {
"InTerritory": [
135
]
}
},
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1002657,
"Position": {
"X": 540.52026,
"Y": 89,
"Z": -74.02161
},
"TerritoryId": 135,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1021353,
"Position": {
"X": 564.17163,
"Y": 84.45213,
"Z": -100.328125
},
"TerritoryId": 135,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 4,
"Steps": [
{
"DataId": 1019064,
"Position": {
"X": 59.220215,
"Y": 4,
"Z": 67.70422
},
"TerritoryId": 628,
"InteractionType": "Interact",
"AetheryteShortcut": "Kugane",
"AethernetShortcut": [
"[Kugane] Aetheryte Plaza",
"[Kugane] Kogane Dori Markets"
],
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
},
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 1019065,
"Position": {
"X": 80.49133,
"Y": 4,
"Z": 56.443115
},
"TerritoryId": 628,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}
]
},
{
"Sequence": 5,
"Steps": [
{
"DataId": 1019233,
"Position": {
"X": -734.15735,
"Y": 1.9602847,
"Z": -611.38324
},
"TerritoryId": 613,
"InteractionType": "Interact",
"AetheryteShortcut": "Ruby Sea - Onokoro",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1021349,
"Position": {
"X": -43.930786,
"Y": 209.23637,
"Z": -90.77594
},
"TerritoryId": 478,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Idyllshire",
"AethernetShortcut": [
"[Idyllshire] Aetheryte Plaza",
"[Idyllshire] West Idyllshire"
],
"RequiredGatheredItems": [
{
"ItemId": 17946,
"ItemCount": 20
}
],
"NextQuestId": 2623
}
]
}
]
}

View File

@ -0,0 +1,160 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1021349,
"Position": {
"X": -43.930786,
"Y": 209.23637,
"Z": -90.77594
},
"TerritoryId": 478,
"InteractionType": "AcceptQuest",
"AetheryteShortcut": "Idyllshire",
"AethernetShortcut": [
"[Idyllshire] Aetheryte Plaza",
"[Idyllshire] West Idyllshire"
],
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1000815,
"Position": {
"X": -233.9361,
"Y": 6.668152,
"Z": -171.03839
},
"TerritoryId": 133,
"InteractionType": "Interact",
"AetheryteShortcut": "Gridania",
"AethernetShortcut": [
"[Gridania] Aetheryte Plaza",
"[Gridania] Botanists' Guild"
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 2008181,
"Position": {
"X": -46.31122,
"Y": 209.49109,
"Z": -86.35089
},
"TerritoryId": 478,
"InteractionType": "Interact",
"AetheryteShortcut": "Idyllshire",
"AethernetShortcut": [
"[Idyllshire] Aetheryte Plaza",
"[Idyllshire] West Idyllshire"
]
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 2008181,
"Position": {
"X": -46.31122,
"Y": 209.49109,
"Z": -86.35089
},
"TerritoryId": 478,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 4,
"Steps": [
{
"DataId": 1021349,
"Position": {
"X": -43.930786,
"Y": 209.23637,
"Z": -90.77594
},
"TerritoryId": 478,
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_CLSHRV680_02623_Q1_000_000",
"Answer": "TEXT_CLSHRV680_02623_A1_000_002"
}
]
}
]
},
{
"Sequence": 5,
"Steps": [
{
"DataId": 1017106,
"Position": {
"X": 73.99097,
"Y": 214.12,
"Z": -92.57648
},
"TerritoryId": 478,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 6,
"Steps": [
{
"DataId": 1017106,
"Position": {
"X": 73.99097,
"Y": 214.12,
"Z": -92.57648
},
"TerritoryId": 478,
"InteractionType": "Interact",
"AetheryteShortcut": "Idyllshire",
"RequiredGatheredItems": [
{
"ItemId": 17947,
"ItemCount": 20
}
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1021349,
"Position": {
"X": -43.930786,
"Y": 209.23637,
"Z": -90.77594
},
"TerritoryId": 478,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,184 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1021349,
"Position": {
"X": -43.930786,
"Y": 209.23637,
"Z": -90.77594
},
"TerritoryId": 478,
"InteractionType": "AcceptQuest",
"AetheryteShortcut": "Idyllshire",
"AethernetShortcut": [
"[Idyllshire] Aetheryte Plaza",
"[Idyllshire] West Idyllshire"
],
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1000815,
"Position": {
"X": -233.9361,
"Y": 6.668152,
"Z": -171.03839
},
"TerritoryId": 133,
"InteractionType": "Interact",
"AetheryteShortcut": "Gridania",
"AethernetShortcut": [
"[Gridania] Aetheryte Plaza",
"[Gridania] Botanists' Guild"
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1021358,
"Position": {
"X": -599.54224,
"Y": 130,
"Z": -483.32953
},
"TerritoryId": 612,
"InteractionType": "Interact",
"AetheryteShortcut": "Fringes - Castrum Oriens"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1021359,
"Position": {
"X": -262.25684,
"Y": 256.97177,
"Z": 695.52136
},
"TerritoryId": 620,
"InteractionType": "Interact",
"AetheryteShortcut": "Peaks - Ala Ghiri"
}
]
},
{
"Sequence": 4,
"Steps": [
{
"DataId": 1021591,
"Position": {
"X": -277.82104,
"Y": 258.90652,
"Z": 782.77246
},
"TerritoryId": 620,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1021595,
"Position": {
"X": -232.50171,
"Y": 258.90652,
"Z": 783.505
},
"TerritoryId": 620,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"DataId": 1020870,
"Position": {
"X": -218.61603,
"Y": 257.52652,
"Z": 737.1786
},
"TerritoryId": 620,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 5,
"Steps": [
{
"DataId": 1022381,
"Position": {
"X": -243.12207,
"Y": 257.52652,
"Z": 744.0145
},
"TerritoryId": 620,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1021349,
"Position": {
"X": -43.930786,
"Y": 209.23637,
"Z": -90.77594
},
"TerritoryId": 478,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Idyllshire",
"AethernetShortcut": [
"[Idyllshire] Aetheryte Plaza",
"[Idyllshire] West Idyllshire"
],
"Comment": "Eorzean Time: 4:00-5:59 AM/PM",
"RequiredGatheredItems": [
{
"ItemId": 17948,
"ItemCount": 5
}
]
}
]
}
]
}

View File

@ -0,0 +1,36 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1012299,
"Position": {
"X": -16.586609,
"Y": 206.49942,
"Z": 42.98462
},
"TerritoryId": 478,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1018393,
"Position": {
"X": -60.380005,
"Y": 206.50021,
"Z": 26.16919
},
"TerritoryId": 478,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -22,6 +22,7 @@
<EmbeddedResource Include="quest-v1.json"> <EmbeddedResource Include="quest-v1.json">
<LogicalName>Questionable.QuestPaths.QuestSchema</LogicalName> <LogicalName>Questionable.QuestPaths.QuestSchema</LogicalName>
</EmbeddedResource> </EmbeddedResource>
<AdditionalFiles Include="4.x - Stormblood\Class Quests\BTN\2623_The White Death.json" />
<AdditionalFiles Include="quest-v1.json" /> <AdditionalFiles Include="quest-v1.json" />
<AdditionalFiles Include="..\Questionable.Model\common-schema.json" /> <AdditionalFiles Include="..\Questionable.Model\common-schema.json" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,56 @@
using System;
using System.Globalization;
namespace Questionable.Model.Gathering;
public class GatheringPointId : IComparable<GatheringPointId>, IEquatable<GatheringPointId>
{
public GatheringPointId(ushort value)
{
Value = value;
}
public ushort Value { get; }
public int CompareTo(GatheringPointId? other)
{
if (ReferenceEquals(this, other)) return 0;
if (ReferenceEquals(null, other)) return 1;
return Value.CompareTo(other.Value);
}
public bool Equals(GatheringPointId? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Value == other.Value;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((GatheringPointId)obj);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public static bool operator ==(GatheringPointId? left, GatheringPointId? right)
{
return Equals(left, right);
}
public static bool operator !=(GatheringPointId? left, GatheringPointId? right)
{
return !Equals(left, right);
}
public static GatheringPointId FromString(string value)
{
return new GatheringPointId(ushort.Parse(value, CultureInfo.InvariantCulture));
}
}

View File

@ -98,8 +98,9 @@ internal sealed class ContextMenuController : IDisposable
var agentSatisfactionSupply = AgentSatisfactionSupply.Instance(); var agentSatisfactionSupply = AgentSatisfactionSupply.Instance();
if (agentSatisfactionSupply->IsAgentActive()) if (agentSatisfactionSupply->IsAgentActive())
{ {
int maxTurnIns = agentSatisfactionSupply->NpcInfo.SatisfactionRank == 1 ? 3 : 6;
quantityToGather = Math.Min(agentSatisfactionSupply->RemainingAllowances, quantityToGather = Math.Min(agentSatisfactionSupply->RemainingAllowances,
((AgentSatisfactionSupply2*)agentSatisfactionSupply)->TurnInsToNextRank); ((AgentSatisfactionSupply2*)agentSatisfactionSupply)->CalculateTurnInsToNextRank(maxTurnIns));
} }
} }

View File

@ -23,6 +23,7 @@ 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 GatheringPointRegistry _gatheringPointRegistry;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly NavmeshIpc _navmeshIpc; private readonly NavmeshIpc _navmeshIpc;
private readonly IObjectTable _objectTable; private readonly IObjectTable _objectTable;
@ -33,6 +34,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
public GatheringController( public GatheringController(
MovementController movementController, MovementController movementController,
GatheringPointRegistry gatheringPointRegistry,
GameFunctions gameFunctions, GameFunctions gameFunctions,
NavmeshIpc navmeshIpc, NavmeshIpc navmeshIpc,
IObjectTable objectTable, IObjectTable objectTable,
@ -43,6 +45,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
: base(chatGui, logger) : base(chatGui, logger)
{ {
_movementController = movementController; _movementController = movementController;
_gatheringPointRegistry = gatheringPointRegistry;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
_navmeshIpc = navmeshIpc; _navmeshIpc = navmeshIpc;
_objectTable = objectTable; _objectTable = objectTable;
@ -52,8 +55,8 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
public bool Start(GatheringRequest gatheringRequest) public bool Start(GatheringRequest gatheringRequest)
{ {
if (!AssemblyGatheringLocationLoader.GetLocations() if (!_gatheringPointRegistry.TryGetGatheringPoint(gatheringRequest.GatheringPointId,
.TryGetValue(gatheringRequest.GatheringPointId, out GatheringRoot? gatheringRoot)) out GatheringRoot? gatheringRoot))
{ {
_logger.LogError("Unable to resolve gathering point, no path found for {ItemId} / point {PointId}", _logger.LogError("Unable to resolve gathering point, no path found for {ItemId} / point {PointId}",
gatheringRequest.ItemId, gatheringRequest.GatheringPointId); gatheringRequest.ItemId, gatheringRequest.GatheringPointId);
@ -190,7 +193,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
} }
public sealed record GatheringRequest( public sealed record GatheringRequest(
ushort GatheringPointId, GatheringPointId GatheringPointId,
uint ItemId, uint ItemId,
int Quantity, int Quantity,
ushort Collectability = 0); ushort Collectability = 0);

View File

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.Json;
using Dalamud.Plugin;
using Microsoft.Extensions.Logging;
using Questionable.GatheringPaths;
using Questionable.Model;
using Questionable.Model.Gathering;
namespace Questionable.Controller;
internal sealed class GatheringPointRegistry : IDisposable
{
private readonly IDalamudPluginInterface _pluginInterface;
private readonly QuestRegistry _questRegistry;
private readonly ILogger<QuestRegistry> _logger;
private readonly Dictionary<GatheringPointId, GatheringRoot> _gatheringPoints = new();
public GatheringPointRegistry(IDalamudPluginInterface pluginInterface, QuestRegistry questRegistry,
ILogger<QuestRegistry> logger)
{
_pluginInterface = pluginInterface;
_questRegistry = questRegistry;
_logger = logger;
_questRegistry.Reloaded += OnReloaded;
}
private void OnReloaded(object? sender, EventArgs e) => Reload();
public void Reload()
{
_gatheringPoints.Clear();
LoadGatheringPointsFromAssembly();
LoadGatheringPointsFromProjectDirectory();
try
{
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "GatheringPoints")));
}
catch (Exception e)
{
_logger.LogError(e,
"Failed to load gathering points from user directory (some may have been successfully loaded)");
}
_logger.LogInformation("Loaded {Count} gathering points in total", _gatheringPoints.Count);
}
[Conditional("RELEASE")]
private void LoadGatheringPointsFromAssembly()
{
_logger.LogInformation("Loading gathering points from assembly");
foreach ((ushort gatheringPointId, GatheringRoot gatheringRoot) in
AssemblyGatheringLocationLoader.GetLocations())
{
_gatheringPoints[new GatheringPointId(gatheringPointId)] = gatheringRoot;
}
_logger.LogInformation("Loaded {Count} gathering points from assembly", _gatheringPoints.Count);
}
[Conditional("DEBUG")]
private void LoadGatheringPointsFromProjectDirectory()
{
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
if (solutionDirectory != null)
{
DirectoryInfo pathProjectDirectory =
new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "GatheringPaths"));
if (pathProjectDirectory.Exists)
{
try
{
foreach (var expansionFolder in ExpansionData.ExpansionFolders.Values)
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, expansionFolder)),
LogLevel.Trace);
}
catch (Exception e)
{
_gatheringPoints.Clear();
_logger.LogError(e, "Failed to load gathering points from project directory");
}
}
}
}
private void LoadGatheringPointFromStream(string fileName, Stream stream)
{
_logger.LogTrace("Loading gathering point from '{FileName}'", fileName);
GatheringPointId? gatheringPointId = ExtractGatheringPointIdFromName(fileName);
if (gatheringPointId == null)
return;
_gatheringPoints[gatheringPointId] = JsonSerializer.Deserialize<GatheringRoot>(stream)!;
}
private void LoadFromDirectory(DirectoryInfo directory, LogLevel logLevel = LogLevel.Information)
{
if (!directory.Exists)
{
_logger.LogInformation("Not loading gathering points from {DirectoryName} (doesn't exist)", directory);
return;
}
_logger.Log(logLevel, "Loading gathering points from {DirectoryName}", directory);
foreach (FileInfo fileInfo in directory.GetFiles("*.json"))
{
try
{
using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
LoadGatheringPointFromStream(fileInfo.Name, stream);
}
catch (Exception e)
{
throw new InvalidDataException($"Unable to load file {fileInfo.FullName}", e);
}
}
foreach (DirectoryInfo childDirectory in directory.GetDirectories())
LoadFromDirectory(childDirectory, logLevel);
}
private static GatheringPointId? ExtractGatheringPointIdFromName(string resourceName)
{
string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1);
if (!name.Contains('_', StringComparison.Ordinal))
return null;
string[] parts = name.Split('_', 2);
return GatheringPointId.FromString(parts[0]);
}
public bool TryGetGatheringPoint(GatheringPointId gatheringPointId, [NotNullWhen(true)] out GatheringRoot? gatheringRoot)
=> _gatheringPoints.TryGetValue(gatheringPointId, out gatheringRoot);
public void Dispose()
{
_questRegistry.Reloaded -= OnReloaded;
}
}

View File

@ -13,7 +13,7 @@ internal sealed class MovementOverrideController
[ [
new BlacklistedArea(1191, new(-223.0412f, 31.937134f, -584.03906f), 5f, 7.75f), new BlacklistedArea(1191, new(-223.0412f, 31.937134f, -584.03906f), 5f, 7.75f),
// limsa, aftcastle to baderon // limsa, aftcastle to Baderon
new BlacklistedPoint(128, new(2f, 40.25f, 36.5f), new(0.25f, 40.25f, 36.5f)), new BlacklistedPoint(128, new(2f, 40.25f, 36.5f), new(0.25f, 40.25f, 36.5f)),
// New Gridania, Carline Canopy stairs // New Gridania, Carline Canopy stairs
@ -28,7 +28,7 @@ internal sealed class MovementOverrideController
new BlacklistedPoint(132, new(45.5f, -8f, 101f), new(50.53978f, -8.046954f, 101.06045f)), new BlacklistedPoint(132, new(45.5f, -8f, 101f), new(50.53978f, -8.046954f, 101.06045f)),
new BlacklistedPoint(132, new(48.5f, -8f, 98.25f), new(50.53978f, -8.046954f, 101.06045f)), new BlacklistedPoint(132, new(48.5f, -8f, 98.25f), new(50.53978f, -8.046954f, 101.06045f)),
// ul'dah lamp near adventuer's guild // ul'dah lamp near adventurers' guild
new BlacklistedPoint(130, new(59.5f, 4.25f, -118f), new(60.551353f, 4f, -119.76446f)), new BlacklistedPoint(130, new(59.5f, 4.25f, -118f), new(60.551353f, 4f, -119.76446f)),
// eastern thanalan // eastern thanalan
@ -37,7 +37,7 @@ internal sealed class MovementOverrideController
// southern thanalan // southern thanalan
new BlacklistedPoint(146, new(-201.75f, 10.5f, -265.5f), new(-203.75235f, 10.130764f, -265.15314f)), new BlacklistedPoint(146, new(-201.75f, 10.5f, -265.5f), new(-203.75235f, 10.130764f, -265.15314f)),
// lower la noscea - moraby drydocks aetheryte // lower la noscea - Moraby Drydocks aetheryte
new BlacklistedArea(135, new(156.11499f, 15.518433f, 673.21277f), 0.5f, 5f), new BlacklistedArea(135, new(156.11499f, 15.518433f, 673.21277f), 0.5f, 5f),
// coerthas central highlands // coerthas central highlands
@ -55,9 +55,13 @@ internal sealed class MovementOverrideController
// moghome, mogmug's trial // moghome, mogmug's trial
new BlacklistedPoint(400, new(384, -74, 648.75f), new(386.0543f, -72.409454f, 652.0184f), 3), new BlacklistedPoint(400, new(384, -74, 648.75f), new(386.0543f, -72.409454f, 652.0184f), 3),
// leaving idyllshiret through the west gate attempts to run into this wall // leaving Idyllshire through the west gate attempts to run into this wall
new BlacklistedPoint(399, new(-514.4851f, 149.63762f, -480.58087f), new(-528.78656f, 151.17374f, -473.07077f), 5, true), new BlacklistedPoint(399, new(-514.4851f, 149.63762f, -480.58087f), new(-528.78656f, 151.17374f, -473.07077f), 5, true),
// Idyllshire: random rocks in the north, passable one way only
new BlacklistedPoint(478, new(14.5f, 215.25f, -101.5f), new(18.133032f, 215.44998f, -107.83075f), 5),
new BlacklistedPoint(478, new(11, 215.5f, -104.5f), new(18.133032f, 215.44998f, -107.83075f), 5),
new BlacklistedPoint(1189, new(574f, -142.25f, 504.25f), new(574.44183f, -142.12766f, 507.60065f)), new BlacklistedPoint(1189, new(574f, -142.25f, 504.25f), new(574.44183f, -142.12766f, 507.60065f)),
// kholusia, random rocks // kholusia, random rocks
@ -66,7 +70,7 @@ internal sealed class MovementOverrideController
// yak t'el, rock near cenote jayunja // yak t'el, rock near cenote jayunja
new BlacklistedPoint(1189, new(-115.75f, -213.75f, 336.5f), new(-112.40265f, -215.01514f, 339.0067f), 2), new BlacklistedPoint(1189, new(-115.75f, -213.75f, 336.5f), new(-112.40265f, -215.01514f, 339.0067f), 2),
// sheshenewezi springs aetheryte: couple of barrel rings that get in the way if you go north // sheshenewezi springs aetheryte: a couple of barrel rings that get in the way if you go north
new BlacklistedPoint(1190, new(-292.29004f, 18.598045f, -133.83907f), new(-288.20895f, 18.652182f, -132.67445f), new BlacklistedPoint(1190, new(-292.29004f, 18.598045f, -133.83907f), new(-288.20895f, 18.652182f, -132.67445f),
4), 4),

View File

@ -19,6 +19,7 @@ internal static class GatheringRequiredItems
internal sealed class Factory( internal sealed class Factory(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
MovementController movementController, MovementController movementController,
GatheringPointRegistry gatheringPointRegistry,
IClientState clientState, IClientState clientState,
GatheringData gatheringData, GatheringData gatheringData,
TerritoryData territoryData, TerritoryData territoryData,
@ -34,11 +35,10 @@ internal static class GatheringRequiredItems
classJob = (EClassJob)requiredGatheredItems.ClassJob.Value; classJob = (EClassJob)requiredGatheredItems.ClassJob.Value;
if (!gatheringData.TryGetGatheringPointId(requiredGatheredItems.ItemId, classJob, if (!gatheringData.TryGetGatheringPointId(requiredGatheredItems.ItemId, classJob,
out var gatheringPointId)) out GatheringPointId? gatheringPointId))
throw new TaskException($"No gathering point found for item {requiredGatheredItems.ItemId}"); throw new TaskException($"No gathering point found for item {requiredGatheredItems.ItemId}");
if (!AssemblyGatheringLocationLoader.GetLocations() if (!gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot? gatheringRoot))
.TryGetValue(gatheringPointId, out GatheringRoot? gatheringRoot))
throw new TaskException($"No path found for gathering point {gatheringPointId}"); throw new TaskException($"No path found for gathering point {gatheringPointId}");
if (classJob != currentClassJob) if (classJob != currentClassJob)
@ -92,10 +92,10 @@ internal static class GatheringRequiredItems
internal sealed class StartGathering(GatheringController gatheringController) : ITask internal sealed class StartGathering(GatheringController gatheringController) : ITask
{ {
private ushort _gatheringPointId; private GatheringPointId _gatheringPointId = null!;
private GatheredItem _gatheredItem = null!; private GatheredItem _gatheredItem = null!;
public ITask With(ushort gatheringPointId, GatheredItem gatheredItem) public ITask With(GatheringPointId gatheringPointId, GatheredItem gatheredItem)
{ {
_gatheringPointId = gatheringPointId; _gatheringPointId = gatheringPointId;
_gatheredItem = gatheredItem; _gatheredItem = gatheredItem;

View File

@ -1,16 +1,18 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Model.Gathering;
namespace Questionable.Data; namespace Questionable.Data;
internal sealed class GatheringData internal sealed class GatheringData
{ {
private readonly Dictionary<uint, ushort> _minerGatheringPoints = []; private readonly Dictionary<uint, GatheringPointId> _minerGatheringPoints = [];
private readonly Dictionary<uint, ushort> _botanistGatheringPoints = []; private readonly Dictionary<uint, GatheringPointId> _botanistGatheringPoints = [];
private readonly Dictionary<uint, ushort> _itemIdToCollectability; private readonly Dictionary<uint, ushort> _itemIdToCollectability;
private readonly Dictionary<uint, uint> _npcForCustomDeliveries; private readonly Dictionary<uint, uint> _npcForCustomDeliveries;
@ -27,9 +29,9 @@ internal sealed class GatheringData
if (gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId)) if (gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId))
{ {
if (gatheringPointBase.GatheringType.Row is 0 or 1) if (gatheringPointBase.GatheringType.Row is 0 or 1)
_minerGatheringPoints[itemId] = (ushort)gatheringPointBase.RowId; _minerGatheringPoints[itemId] = new GatheringPointId((ushort)gatheringPointBase.RowId);
else if (gatheringPointBase.GatheringType.Row is 2 or 3) else if (gatheringPointBase.GatheringType.Row is 2 or 3)
_botanistGatheringPoints[itemId] = (ushort)gatheringPointBase.RowId; _botanistGatheringPoints[itemId] = new GatheringPointId((ushort)gatheringPointBase.RowId);
} }
} }
} }
@ -59,7 +61,8 @@ internal sealed class GatheringData
.ToDictionary(x => x.ItemId, x => x.NpcId); .ToDictionary(x => x.ItemId, x => x.NpcId);
} }
public bool TryGetGatheringPointId(uint itemId, EClassJob classJobId, out ushort gatheringPointId) public bool TryGetGatheringPointId(uint itemId, EClassJob classJobId,
[NotNullWhen(true)] out GatheringPointId? gatheringPointId)
{ {
if (classJobId == EClassJob.Miner) if (classJobId == EClassJob.Miner)
return _minerGatheringPoints.TryGetValue(itemId, out gatheringPointId); return _minerGatheringPoints.TryGetValue(itemId, out gatheringPointId);
@ -67,7 +70,7 @@ internal sealed class GatheringData
return _botanistGatheringPoints.TryGetValue(itemId, out gatheringPointId); return _botanistGatheringPoints.TryGetValue(itemId, out gatheringPointId);
else else
{ {
gatheringPointId = 0; gatheringPointId = null;
return false; return false;
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;

View File

@ -15,14 +15,11 @@ internal struct AgentSatisfactionSupply2
[FieldOffset(0x70)] public ushort CurrentSatisfaction; [FieldOffset(0x70)] public ushort CurrentSatisfaction;
[FieldOffset(0x72)] public ushort MaxSatisfaction; [FieldOffset(0x72)] public ushort MaxSatisfaction;
public int TurnInsToNextRank public int CalculateTurnInsToNextRank(int maxTurnIns)
{ {
get if (MaxSatisfaction == 0)
{ return maxTurnIns;
if (MaxSatisfaction == 0)
return 6;
return 6 * (MaxSatisfaction - CurrentSatisfaction) / MaxSatisfaction; return maxTurnIns * (MaxSatisfaction - CurrentSatisfaction) / MaxSatisfaction;
}
} }
} }

View File

@ -164,6 +164,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
{ {
serviceCollection.AddSingleton<MovementController>(); serviceCollection.AddSingleton<MovementController>();
serviceCollection.AddSingleton<MovementOverrideController>(); serviceCollection.AddSingleton<MovementOverrideController>();
serviceCollection.AddSingleton<GatheringPointRegistry>();
serviceCollection.AddSingleton<QuestRegistry>(); serviceCollection.AddSingleton<QuestRegistry>();
serviceCollection.AddSingleton<QuestController>(); serviceCollection.AddSingleton<QuestController>();
serviceCollection.AddSingleton<GameUiController>(); serviceCollection.AddSingleton<GameUiController>();
@ -211,6 +212,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
private static void Initialize(IServiceProvider serviceProvider) private static void Initialize(IServiceProvider serviceProvider)
{ {
serviceProvider.GetRequiredService<QuestRegistry>().Reload(); serviceProvider.GetRequiredService<QuestRegistry>().Reload();
serviceProvider.GetRequiredService<GatheringPointRegistry>().Reload();
serviceProvider.GetRequiredService<CommandHandler>(); serviceProvider.GetRequiredService<CommandHandler>();
serviceProvider.GetRequiredService<ContextMenuController>(); serviceProvider.GetRequiredService<ContextMenuController>();
serviceProvider.GetRequiredService<DalamudInitializer>(); serviceProvider.GetRequiredService<DalamudInitializer>();