From a6edfd903c58e42426dfb5360b2ab1f4e0bca535 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 28 Jun 2025 12:44:29 -0400 Subject: [PATCH] Add support for extracting inventory containers in Dalamud --- dalamud/Auracite/CurrencyStep.cs | 34 ------------- dalamud/Auracite/InventoryStep.cs | 80 +++++++++++++++++++++++++++++++ dalamud/Auracite/Plugin.cs | 42 +++++++++++++++- src/data.rs | 50 +++++++++++++++++-- src/lib.rs | 25 ++++++++-- src/package.rs | 40 +++++++++++++++- 6 files changed, 227 insertions(+), 44 deletions(-) delete mode 100644 dalamud/Auracite/CurrencyStep.cs create mode 100644 dalamud/Auracite/InventoryStep.cs diff --git a/dalamud/Auracite/CurrencyStep.cs b/dalamud/Auracite/CurrencyStep.cs deleted file mode 100644 index e09e4df..0000000 --- a/dalamud/Auracite/CurrencyStep.cs +++ /dev/null @@ -1,34 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.Game; - -namespace Auracite; - -public class CurrencyStep : IStep -{ - public event IStep.CompletedDelegate? Completed; - - public void Run() - { - if (Plugin.ClientState.LocalPlayer != null) - { - unsafe - { - Plugin.package.gil = InventoryManager.Instance()->GetGil(); - } - } - Completed?.Invoke(); - } - - public string StepName() - { - return "Currency"; - } - - public string StepDescription() - { - return "No user action required."; - } - - public void Dispose() - { - } -} \ No newline at end of file diff --git a/dalamud/Auracite/InventoryStep.cs b/dalamud/Auracite/InventoryStep.cs new file mode 100644 index 0000000..5594ab7 --- /dev/null +++ b/dalamud/Auracite/InventoryStep.cs @@ -0,0 +1,80 @@ +using FFXIVClientStructs.FFXIV.Client.Game; + +namespace Auracite; + +public class InventoryStep : IStep +{ + public event IStep.CompletedDelegate? Completed; + + public void Run() + { + if (Plugin.ClientState.LocalPlayer != null) + { + unsafe + { + var manager = InventoryManager.Instance(); + Plugin.package.inventory1 = ProcessContainer(manager->GetInventoryContainer(InventoryType.Inventory1)); + Plugin.package.inventory2 = ProcessContainer(manager->GetInventoryContainer(InventoryType.Inventory2)); + Plugin.package.inventory3 = ProcessContainer(manager->GetInventoryContainer(InventoryType.Inventory3)); + Plugin.package.inventory4 = ProcessContainer(manager->GetInventoryContainer(InventoryType.Inventory4)); + + Plugin.package.equipped_items = ProcessContainer(manager->GetInventoryContainer(InventoryType.EquippedItems)); + + Plugin.package.currency = ProcessContainer(manager->GetInventoryContainer(InventoryType.Currency)); + + Plugin.package.armory_off_hand = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryOffHand)); + Plugin.package.armory_head = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryHead)); + Plugin.package.armory_body = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryBody)); + Plugin.package.armory_hands = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryHands)); + Plugin.package.armory_waist = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryWaist)); + Plugin.package.armory_legs = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryLegs)); + Plugin.package.armory_ear = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryEar)); + Plugin.package.armory_neck = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryNeck)); + Plugin.package.armory_wrist = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryWrist)); + Plugin.package.armory_rings = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryRings)); + Plugin.package.armory_soul_crystal = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmorySoulCrystal)); + Plugin.package.armory_main_hand = ProcessContainer(manager->GetInventoryContainer(InventoryType.ArmoryMainHand)); + } + } + Completed?.Invoke(); + } + + private unsafe Auracite.Plugin.InventoryContainer ProcessContainer(FFXIVClientStructs.FFXIV.Client.Game.InventoryContainer *container) { + var serializedContainer = new Auracite.Plugin.InventoryContainer(); + serializedContainer.items = new System.Collections.Generic.List(); // TODO: lol + + for (int i = 0; i < container->Size; i++) { + var item = container->GetInventorySlot(i); + if (item != null) { + if (item->GetQuantity() == 0) { + continue; + } + + var serializedItem = new Auracite.Plugin.InventoryItem(); + serializedItem.condition = item->GetCondition(); + serializedItem.id = item->GetBaseItemId(); + serializedItem.quantity = item->GetQuantity(); + serializedItem.slot = item->GetSlot(); + serializedItem.glamour_id = item->GetGlamourId(); + + serializedContainer.items.Add(serializedItem); + } + } + + return serializedContainer; + } + + public string StepName() + { + return "Currency"; + } + + public string StepDescription() + { + return "No user action required."; + } + + public void Dispose() + { + } +} diff --git a/dalamud/Auracite/Plugin.cs b/dalamud/Auracite/Plugin.cs index 2a434fb..f85a76d 100644 --- a/dalamud/Auracite/Plugin.cs +++ b/dalamud/Auracite/Plugin.cs @@ -15,17 +15,32 @@ public sealed class Plugin : IDalamudPlugin private readonly WindowSystem WindowSystem = new("Auracite"); private readonly List _steps = - [typeof(AppearanceStep), typeof(CurrencyStep), typeof(MiscStep), typeof(PlaytimeStep), typeof(AdventurerPlateStep), typeof(EndStep)]; + [typeof(AppearanceStep), typeof(InventoryStep), typeof(MiscStep), typeof(PlaytimeStep), typeof(AdventurerPlateStep), typeof(EndStep)]; private int _stepIndex; private readonly StepWindow StepWindow; + [SuppressMessage("ReSharper", "InconsistentNaming")] + public class InventoryItem + { + public int slot; + public uint quantity; + public int condition; + public uint id; + public uint glamour_id; + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + public class InventoryContainer + { + public List items; + } + [SuppressMessage("ReSharper", "InconsistentNaming")] public class Package { public string? playtime; - public uint gil; public bool is_battle_mentor; public bool is_trade_mentor; public bool is_novice; @@ -74,6 +89,29 @@ public sealed class Plugin : IDalamudPlugin public string? plate_frame; public string? accent; + // inventory + public InventoryContainer inventory1; + public InventoryContainer inventory2; + public InventoryContainer inventory3; + public InventoryContainer inventory4; + + public InventoryContainer equipped_items; + + public InventoryContainer currency; + + public InventoryContainer armory_off_hand; + public InventoryContainer armory_head; + public InventoryContainer armory_body; + public InventoryContainer armory_hands; + public InventoryContainer armory_waist; + public InventoryContainer armory_legs; + public InventoryContainer armory_ear; + public InventoryContainer armory_neck; + public InventoryContainer armory_wrist; + public InventoryContainer armory_rings; + public InventoryContainer armory_soul_crystal; + public InventoryContainer armory_main_hand; + public int voice; } diff --git a/src/data.rs b/src/data.rs index bd15632..5cfc145 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,8 +1,11 @@ use serde::Serialize; -use crate::value::{ - CityStateValue, ClassJobValue, GenderValue, GrandCompanyValue, GuardianValue, ItemValue, - NamedayValue, RaceValue, TribeValue, WorldValue, +use crate::{ + package::InventoryContainer, + value::{ + CityStateValue, ClassJobValue, GenderValue, GrandCompanyValue, GuardianValue, ItemValue, + NamedayValue, RaceValue, TribeValue, WorldValue, + }, }; #[derive(Default, Serialize)] @@ -120,4 +123,45 @@ pub struct CharacterData { pub face_url: String, #[serde(skip)] pub portrait_url: String, + + // inventory + #[serde(skip_serializing_if = "Option::is_none")] + pub inventory1: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub inventory2: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub inventory3: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub inventory4: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub equipped_items: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub currency: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_off_hand: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_head: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_body: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_hands: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_waist: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_legs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_ear: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_neck: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_wrist: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_rings: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_soul_crystal: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub armory_main_hand: Option, } diff --git a/src/lib.rs b/src/lib.rs index 7074473..3915b3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,10 +226,29 @@ pub async fn archive_character(id: u64, use_dalamud: bool) -> Result, Ar facepaint_color: package.facepaint_color, }); + char_data.inventory1 = Some(package.inventory1); + char_data.inventory2 = Some(package.inventory2); + char_data.inventory3 = Some(package.inventory3); + char_data.inventory4 = Some(package.inventory4); + + char_data.equipped_items = Some(package.equipped_items); + + char_data.currency = Some(package.currency); + + char_data.armory_off_hand = Some(package.armory_off_hand); + char_data.armory_head = Some(package.armory_head); + char_data.armory_body = Some(package.armory_body); + char_data.armory_hands = Some(package.armory_hands); + char_data.armory_waist = Some(package.armory_waist); + char_data.armory_legs = Some(package.armory_legs); + char_data.armory_ear = Some(package.armory_ear); + char_data.armory_neck = Some(package.armory_neck); + char_data.armory_wrist = Some(package.armory_wrist); + char_data.armory_rings = Some(package.armory_rings); + char_data.armory_soul_crystal = Some(package.armory_soul_crystal); + char_data.armory_main_hand = Some(package.armory_main_hand); + char_data.playtime = Some(package.playtime.parse().unwrap()); - char_data.currencies = Some(Currencies { - gil: package.gil, // TODO: also fetch from the lodestone - }); char_data.is_battle_mentor = Some(package.is_battle_mentor); char_data.is_trade_mentor = Some(package.is_trade_mentor); char_data.is_novice = Some(package.is_novice); diff --git a/src/package.rs b/src/package.rs index 11b50cf..f6862d5 100644 --- a/src/package.rs +++ b/src/package.rs @@ -1,9 +1,22 @@ -use serde::Deserialize; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Deserialize, Serialize, Clone)] +pub struct InventoryItem { + pub slot: i32, + pub quantity: u32, + pub condition: i32, + pub id: u32, + pub glamour_id: u32, +} + +#[derive(Default, Deserialize, Serialize, Clone)] +pub struct InventoryContainer { + pub items: Vec, +} #[derive(Default, Deserialize, Clone)] pub struct Package { pub playtime: String, - pub gil: u32, pub is_battle_mentor: bool, pub is_trade_mentor: bool, pub is_novice: bool, @@ -52,4 +65,27 @@ pub struct Package { pub bust_size: i32, pub facepaint: i32, pub facepaint_color: i32, + + // inventory + pub inventory1: InventoryContainer, + pub inventory2: InventoryContainer, + pub inventory3: InventoryContainer, + pub inventory4: InventoryContainer, + + pub equipped_items: InventoryContainer, + + pub currency: InventoryContainer, + + pub armory_off_hand: InventoryContainer, + pub armory_head: InventoryContainer, + pub armory_body: InventoryContainer, + pub armory_hands: InventoryContainer, + pub armory_waist: InventoryContainer, + pub armory_legs: InventoryContainer, + pub armory_ear: InventoryContainer, + pub armory_neck: InventoryContainer, + pub armory_wrist: InventoryContainer, + pub armory_rings: InventoryContainer, + pub armory_soul_crystal: InventoryContainer, + pub armory_main_hand: InventoryContainer, }