Load gathering points dynamically, BTN 65-70

pull/15/head
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()
{
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
{
_target = null;
_targetLocation = null;
return;
}
_target = _targetManager.Target;
var gatheringLocations = _plugin.GetLocationsInTerritory(_clientState.TerritoryType);
var location = gatheringLocations.SelectMany(context =>
@ -63,7 +70,7 @@ internal sealed class EditorWindow : Window
if (_target != null)
distance = Vector3.Distance(location.Position, _target.Position);
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 };
})
@ -86,7 +93,7 @@ internal sealed class EditorWindow : Window
.Select(x => new
{
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)
.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",
"Author": "liza",
"TerritoryId": 613,
"AetheryteShortcut": "Ruby Sea - Onokoro",
"Groups": [
{
"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)
{
_locations = [];
#if RELEASE
LoadLocations();
#endif
}
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")!;
[SuppressMessage("ReSharper", "UnusedMember.Local")]

View File

@ -26,7 +26,7 @@
<AdditionalFiles Include="..\Questionable.Model\common-schema.json" />
</ItemGroup>
<ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<None Remove="2.x - A Realm Reborn" />
<None Remove="3.x - Heavensward" />
<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">
<LogicalName>Questionable.QuestPaths.QuestSchema</LogicalName>
</EmbeddedResource>
<AdditionalFiles Include="4.x - Stormblood\Class Quests\BTN\2623_The White Death.json" />
<AdditionalFiles Include="quest-v1.json" />
<AdditionalFiles Include="..\Questionable.Model\common-schema.json" />
</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();
if (agentSatisfactionSupply->IsAgentActive())
{
int maxTurnIns = agentSatisfactionSupply->NpcInfo.SatisfactionRank == 1 ? 3 : 6;
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>
{
private readonly MovementController _movementController;
private readonly GatheringPointRegistry _gatheringPointRegistry;
private readonly GameFunctions _gameFunctions;
private readonly NavmeshIpc _navmeshIpc;
private readonly IObjectTable _objectTable;
@ -33,6 +34,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
public GatheringController(
MovementController movementController,
GatheringPointRegistry gatheringPointRegistry,
GameFunctions gameFunctions,
NavmeshIpc navmeshIpc,
IObjectTable objectTable,
@ -43,6 +45,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
: base(chatGui, logger)
{
_movementController = movementController;
_gatheringPointRegistry = gatheringPointRegistry;
_gameFunctions = gameFunctions;
_navmeshIpc = navmeshIpc;
_objectTable = objectTable;
@ -52,8 +55,8 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
public bool Start(GatheringRequest gatheringRequest)
{
if (!AssemblyGatheringLocationLoader.GetLocations()
.TryGetValue(gatheringRequest.GatheringPointId, out GatheringRoot? gatheringRoot))
if (!_gatheringPointRegistry.TryGetGatheringPoint(gatheringRequest.GatheringPointId,
out GatheringRoot? gatheringRoot))
{
_logger.LogError("Unable to resolve gathering point, no path found for {ItemId} / point {PointId}",
gatheringRequest.ItemId, gatheringRequest.GatheringPointId);
@ -190,7 +193,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
}
public sealed record GatheringRequest(
ushort GatheringPointId,
GatheringPointId GatheringPointId,
uint ItemId,
int Quantity,
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),
// 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 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(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)),
// eastern thanalan
@ -37,7 +37,7 @@ internal sealed class MovementOverrideController
// southern thanalan
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),
// coerthas central highlands
@ -55,9 +55,13 @@ internal sealed class MovementOverrideController
// moghome, mogmug's trial
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),
// 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)),
// kholusia, random rocks
@ -66,7 +70,7 @@ internal sealed class MovementOverrideController
// 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),
// 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),
4),

View File

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

View File

@ -1,16 +1,18 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Plugin.Services;
using LLib.GameData;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging;
using Questionable.Model.Gathering;
namespace Questionable.Data;
internal sealed class GatheringData
{
private readonly Dictionary<uint, ushort> _minerGatheringPoints = [];
private readonly Dictionary<uint, ushort> _botanistGatheringPoints = [];
private readonly Dictionary<uint, GatheringPointId> _minerGatheringPoints = [];
private readonly Dictionary<uint, GatheringPointId> _botanistGatheringPoints = [];
private readonly Dictionary<uint, ushort> _itemIdToCollectability;
private readonly Dictionary<uint, uint> _npcForCustomDeliveries;
@ -27,9 +29,9 @@ internal sealed class GatheringData
if (gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId))
{
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)
_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);
}
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)
return _minerGatheringPoints.TryGetValue(itemId, out gatheringPointId);
@ -67,7 +70,7 @@ internal sealed class GatheringData
return _botanistGatheringPoints.TryGetValue(itemId, out gatheringPointId);
else
{
gatheringPointId = 0;
gatheringPointId = null;
return false;
}
}

View File

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

View File

@ -15,14 +15,11 @@ internal struct AgentSatisfactionSupply2
[FieldOffset(0x70)] public ushort CurrentSatisfaction;
[FieldOffset(0x72)] public ushort MaxSatisfaction;
public int TurnInsToNextRank
public int CalculateTurnInsToNextRank(int maxTurnIns)
{
get
{
if (MaxSatisfaction == 0)
return 6;
if (MaxSatisfaction == 0)
return maxTurnIns;
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<MovementOverrideController>();
serviceCollection.AddSingleton<GatheringPointRegistry>();
serviceCollection.AddSingleton<QuestRegistry>();
serviceCollection.AddSingleton<QuestController>();
serviceCollection.AddSingleton<GameUiController>();
@ -211,6 +212,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
private static void Initialize(IServiceProvider serviceProvider)
{
serviceProvider.GetRequiredService<QuestRegistry>().Reload();
serviceProvider.GetRequiredService<GatheringPointRegistry>().Reload();
serviceProvider.GetRequiredService<CommandHandler>();
serviceProvider.GetRequiredService<ContextMenuController>();
serviceProvider.GetRequiredService<DalamudInitializer>();