From 8d384c4bd03d04e8e4376f79c78182de2f466569 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Fri, 2 May 2025 16:15:54 -0400 Subject: [PATCH] Overhaul how we send inventory packets again I wanted to add armory chest support but the current state of the inventory was a little frustrating. Adding new containers was too difficult, so I made the system *even more* generic and easier to use. I have also split it up into it's own module with a nicer file layout. Oh yeah, and armory chest works too now. --- src/bin/kawari-world.rs | 5 +- src/inventory/equipped.rs | 82 ++++++++ src/inventory/generic.rs | 34 ++++ src/inventory/item.rs | 14 ++ src/inventory/mod.rs | 293 ++++++++++++++++++++++++++++ src/inventory/storage.rs | 38 ++++ src/ipc/zone/container_info.rs | 14 +- src/ipc/zone/inventory_modify.rs | 4 +- src/ipc/zone/item_info.rs | 2 +- src/ipc/zone/mod.rs | 2 +- src/lib.rs | 3 + src/lobby/connection.rs | 2 +- src/world/connection.rs | 88 ++------- src/world/database.rs | 3 +- src/world/inventory.rs | 315 ------------------------------- src/world/mod.rs | 3 - 16 files changed, 486 insertions(+), 416 deletions(-) create mode 100644 src/inventory/equipped.rs create mode 100644 src/inventory/generic.rs create mode 100644 src/inventory/item.rs create mode 100644 src/inventory/mod.rs create mode 100644 src/inventory/storage.rs delete mode 100644 src/world/inventory.rs diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 3038897..8fa3b2d 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -7,6 +7,7 @@ use kawari::common::workdefinitions::{CharaMake, RemakeMode}; use kawari::common::{GameData, ObjectId, timestamp_secs}; use kawari::common::{Position, determine_initial_starting_zone}; use kawari::config::get_config; +use kawari::inventory::{Inventory, Item}; use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment}; use kawari::ipc::kawari::{CustomIpcData, CustomIpcSegment, CustomIpcType}; use kawari::ipc::zone::{ @@ -24,8 +25,8 @@ use kawari::packet::{ send_keep_alive, send_packet, }; use kawari::world::{ - Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, Item, LuaPlayer, - PlayerData, ServerHandle, StatusEffects, ToServer, WorldDatabase, + Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, LuaPlayer, PlayerData, ServerHandle, + StatusEffects, ToServer, WorldDatabase, }; use kawari::world::{ChatHandler, Zone, ZoneConnection}; diff --git a/src/inventory/equipped.rs b/src/inventory/equipped.rs new file mode 100644 index 0000000..d07e7e2 --- /dev/null +++ b/src/inventory/equipped.rs @@ -0,0 +1,82 @@ +use serde::{Deserialize, Serialize}; + +use super::{Item, Storage}; + +#[derive(Default, Clone, Copy, Deserialize, Serialize, Debug)] +pub struct EquippedStorage { + pub main_hand: Item, + pub off_hand: Item, + pub head: Item, + pub body: Item, + pub hands: Item, + pub belt: Item, + pub legs: Item, + pub feet: Item, + pub ears: Item, + pub neck: Item, + pub wrists: Item, + pub right_ring: Item, + pub left_ring: Item, + pub soul_crystal: Item, +} + +impl Storage for EquippedStorage { + fn max_slots(&self) -> u32 { + 14 + } + + fn num_items(&self) -> u32 { + self.main_hand.quantity + + self.off_hand.quantity + + self.head.quantity + + self.body.quantity + + self.hands.quantity + + self.legs.quantity + + self.feet.quantity + + self.ears.quantity + + self.neck.quantity + + self.wrists.quantity + + self.right_ring.quantity + + self.left_ring.quantity + + self.soul_crystal.quantity + } + + fn get_slot_mut(&mut self, index: u16) -> &mut Item { + match index { + 0 => &mut self.main_hand, + 1 => &mut self.off_hand, + 2 => &mut self.head, + 3 => &mut self.body, + 4 => &mut self.hands, + 6 => &mut self.legs, + 7 => &mut self.feet, + 8 => &mut self.ears, + 9 => &mut self.neck, + 10 => &mut self.wrists, + 11 => &mut self.right_ring, + 12 => &mut self.left_ring, + 13 => &mut self.soul_crystal, + _ => panic!("{} is not a valid src_container_index?!?", index), + } + } + + fn get_slot(&self, index: u16) -> &Item { + match index { + 0 => &self.main_hand, + 1 => &self.off_hand, + 2 => &self.head, + 3 => &self.body, + 4 => &self.hands, + 5 => &self.belt, + 6 => &self.legs, + 7 => &self.feet, + 8 => &self.ears, + 9 => &self.neck, + 10 => &self.wrists, + 11 => &self.right_ring, + 12 => &self.left_ring, + 13 => &self.soul_crystal, + _ => panic!("{} is not a valid src_container_index?!?", index), + } + } +} diff --git a/src/inventory/generic.rs b/src/inventory/generic.rs new file mode 100644 index 0000000..c226c7b --- /dev/null +++ b/src/inventory/generic.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; + +use super::{Item, Storage}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GenericStorage { + pub slots: Vec, +} + +impl GenericStorage { + pub fn default() -> Self { + Self { + slots: vec![Item::default(); N], + } + } +} + +impl Storage for GenericStorage { + fn max_slots(&self) -> u32 { + N as u32 + } + + fn num_items(&self) -> u32 { + self.slots.iter().filter(|item| item.quantity > 0).count() as u32 + } + + fn get_slot_mut(&mut self, index: u16) -> &mut Item { + self.slots.get_mut(index as usize).unwrap() + } + + fn get_slot(&self, index: u16) -> &Item { + self.slots.get(index as usize).unwrap() + } +} diff --git a/src/inventory/item.rs b/src/inventory/item.rs new file mode 100644 index 0000000..0033187 --- /dev/null +++ b/src/inventory/item.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + +/// Represents an item, or if the quanity is zero an empty slot. +#[derive(Default, Copy, Clone, Serialize, Deserialize, Debug)] +pub struct Item { + pub quantity: u32, + pub id: u32, +} + +impl Item { + pub fn new(quantity: u32, id: u32) -> Self { + Self { quantity, id } + } +} diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs new file mode 100644 index 0000000..22809c8 --- /dev/null +++ b/src/inventory/mod.rs @@ -0,0 +1,293 @@ +use physis::common::Language; +use serde::{Deserialize, Serialize}; + +use crate::common::GameData; + +use crate::ipc::zone::InventoryModify; + +mod equipped; +pub use equipped::EquippedStorage; + +mod generic; +pub use generic::GenericStorage; + +mod item; +pub use item::Item; + +mod storage; +pub use storage::{ContainerType, Storage}; + +const MAX_NORMAL_STORAGE: usize = 35; +const MAX_LARGE_STORAGE: usize = 50; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Inventory { + pub equipped: EquippedStorage, + pub pages: [GenericStorage; 4], + pub armoury_main_hand: GenericStorage, + pub armoury_head: GenericStorage, + pub armoury_body: GenericStorage, + pub armoury_hands: GenericStorage, + pub armoury_legs: GenericStorage, + pub armoury_feet: GenericStorage, + pub armoury_off_hand: GenericStorage, + pub armoury_earring: GenericStorage, + pub armoury_necklace: GenericStorage, + pub armoury_bracelet: GenericStorage, + pub armoury_rings: GenericStorage, + pub armoury_soul_crystal: GenericStorage, +} + +impl Default for Inventory { + fn default() -> Self { + Self { + equipped: EquippedStorage::default(), + pages: std::array::from_fn(|_| GenericStorage::default()), + armoury_main_hand: GenericStorage::default(), + armoury_head: GenericStorage::default(), + armoury_body: GenericStorage::default(), + armoury_hands: GenericStorage::default(), + armoury_legs: GenericStorage::default(), + armoury_feet: GenericStorage::default(), + armoury_off_hand: GenericStorage::default(), + armoury_earring: GenericStorage::default(), + armoury_necklace: GenericStorage::default(), + armoury_bracelet: GenericStorage::default(), + armoury_rings: GenericStorage::default(), + armoury_soul_crystal: GenericStorage::default(), + } + } +} + +impl<'a> IntoIterator for &'a Inventory { + type Item = (ContainerType, &'a dyn Storage); + type IntoIter = InventoryIterator<'a>; + fn into_iter(self) -> InventoryIterator<'a> { + InventoryIterator { + inventory: self, + curr: 0, + } + } +} + +pub struct InventoryIterator<'a> { + inventory: &'a Inventory, + curr: u16, +} + +impl<'a> Iterator for InventoryIterator<'a> { + type Item = (ContainerType, &'a dyn Storage); + + fn next(&mut self) -> Option { + let curr = self.curr; + self.curr += 1; + + if curr >= 17 { + return None; + } + + let container_type = match curr { + // inventory + 0 => ContainerType::Inventory0, + 1 => ContainerType::Inventory1, + 2 => ContainerType::Inventory2, + 3 => ContainerType::Inventory3, + + // armory + 4 => ContainerType::ArmoryOffWeapon, + 5 => ContainerType::ArmoryHead, + 6 => ContainerType::ArmoryBody, + 7 => ContainerType::ArmoryHand, + 8 => ContainerType::ArmoryLeg, + 9 => ContainerType::ArmoryFoot, + 10 => ContainerType::ArmoryEarring, + 11 => ContainerType::ArmoryNeck, + 12 => ContainerType::ArmoryWrist, + 13 => ContainerType::ArmoryRing, + 14 => ContainerType::ArmorySoulCrystal, + 15 => ContainerType::ArmoryWeapon, + + // equipped + 16 => ContainerType::Equipped, + _ => panic!("Inventory iterator invalid!"), + }; + + Some(( + container_type, + self.inventory.get_container(&container_type), + )) + } +} + +impl Inventory { + /// Equip the starting items for a given race + pub fn equip_racial_items(&mut self, race_id: u8, gender: u8, game_data: &mut GameData) { + let exh = game_data.game_data.read_excel_sheet_header("Race").unwrap(); + let exd = game_data + .game_data + .read_excel_sheet("Race", &exh, Language::English, 0) + .unwrap(); + + let world_row = &exd.read_row(&exh, race_id as u32).unwrap()[0]; + + let get_column = |column_index: usize| { + let physis::exd::ColumnData::Int32(item_id) = &world_row.data[column_index] else { + panic!("Unexpected type!"); + }; + + *item_id + }; + + if gender == 0 { + self.equipped.body = Item::new(1, get_column(2) as u32); + self.equipped.hands = Item::new(1, get_column(3) as u32); + self.equipped.legs = Item::new(1, get_column(4) as u32); + self.equipped.feet = Item::new(1, get_column(5) as u32); + } else { + self.equipped.body = Item::new(1, get_column(6) as u32); + self.equipped.hands = Item::new(1, get_column(7) as u32); + self.equipped.legs = Item::new(1, get_column(8) as u32); + self.equipped.feet = Item::new(1, get_column(9) as u32); + } + + // TODO: don't hardcode + self.equipped.main_hand = Item::new(1, 0x00000641); + self.equipped.ears = Item::new(1, 0x00003b1b); + self.equipped.neck = Item::new(1, 0x00003b1a); + self.equipped.wrists = Item::new(1, 0x00003b1c); + self.equipped.right_ring = Item::new(1, 0x0000114a); + self.equipped.left_ring = Item::new(1, 0x00003b1d); + } + + pub fn process_action(&mut self, action: &InventoryModify) { + if action.operation_type == 78 { + // discard + let src_container = self.get_container_mut(&action.src_storage_id); + let src_slot = src_container.get_slot_mut(action.src_container_index); + *src_slot = Item::default(); + } else { + // NOTE: only swaps items for now + + let src_item; + // get the source item + { + let src_container = self.get_container_mut(&action.src_storage_id); + let src_slot = src_container.get_slot_mut(action.src_container_index); + src_item = *src_slot; + } + + let dst_item; + // move into dst item + { + let dst_container = self.get_container_mut(&action.dst_storage_id); + let dst_slot = dst_container.get_slot_mut(action.dst_container_index); + + dst_item = *dst_slot; + dst_slot.clone_from(&src_item); + } + + // move dst item into src slot + { + let src_container = self.get_container_mut(&action.src_storage_id); + let src_slot = src_container.get_slot_mut(action.src_container_index); + src_slot.clone_from(&dst_item); + } + } + } + + pub fn add_in_next_free_slot(&mut self, item: Item) { + for page in &mut self.pages { + for slot in &mut page.slots { + if slot.quantity == 0 { + slot.clone_from(&item); + return; + } + } + } + } + + fn get_container_mut(&mut self, container_type: &ContainerType) -> &mut dyn Storage { + match container_type { + ContainerType::Inventory0 => &mut self.pages[0], + ContainerType::Inventory1 => &mut self.pages[1], + ContainerType::Inventory2 => &mut self.pages[2], + ContainerType::Inventory3 => &mut self.pages[3], + ContainerType::Equipped => &mut self.equipped, + ContainerType::ArmoryOffWeapon => &mut self.armoury_off_hand, + ContainerType::ArmoryHead => &mut self.armoury_head, + ContainerType::ArmoryBody => &mut self.armoury_body, + ContainerType::ArmoryHand => &mut self.armoury_hands, + ContainerType::ArmoryLeg => &mut self.armoury_legs, + ContainerType::ArmoryFoot => &mut self.armoury_feet, + ContainerType::ArmoryEarring => &mut self.armoury_earring, + ContainerType::ArmoryNeck => &mut self.armoury_necklace, + ContainerType::ArmoryWrist => &mut self.armoury_bracelet, + ContainerType::ArmoryRing => &mut self.armoury_rings, + ContainerType::ArmorySoulCrystal => &mut self.armoury_soul_crystal, + ContainerType::ArmoryWeapon => &mut self.armoury_main_hand, + } + } + + fn get_container(&self, container_type: &ContainerType) -> &dyn Storage { + match container_type { + ContainerType::Inventory0 => &self.pages[0], + ContainerType::Inventory1 => &self.pages[1], + ContainerType::Inventory2 => &self.pages[2], + ContainerType::Inventory3 => &self.pages[3], + ContainerType::Equipped => &self.equipped, + ContainerType::ArmoryOffWeapon => &self.armoury_off_hand, + ContainerType::ArmoryHead => &self.armoury_head, + ContainerType::ArmoryBody => &self.armoury_body, + ContainerType::ArmoryHand => &self.armoury_hands, + ContainerType::ArmoryLeg => &self.armoury_legs, + ContainerType::ArmoryFoot => &self.armoury_feet, + ContainerType::ArmoryEarring => &self.armoury_earring, + ContainerType::ArmoryNeck => &self.armoury_necklace, + ContainerType::ArmoryWrist => &self.armoury_bracelet, + ContainerType::ArmoryRing => &self.armoury_rings, + ContainerType::ArmorySoulCrystal => &self.armoury_soul_crystal, + ContainerType::ArmoryWeapon => &self.armoury_main_hand, + } + } + + pub fn get_main_weapon_id(&self, game_data: &mut GameData) -> u64 { + game_data + .get_primary_model_id(self.equipped.main_hand.id) + .unwrap_or(0) + } + + pub fn get_model_ids(&self, game_data: &mut GameData) -> [u32; 10] { + [ + game_data + .get_primary_model_id(self.equipped.head.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.body.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.hands.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.legs.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.feet.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.ears.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.neck.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.wrists.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.left_ring.id) + .unwrap_or(0) as u32, + game_data + .get_primary_model_id(self.equipped.right_ring.id) + .unwrap_or(0) as u32, + ] + } +} diff --git a/src/inventory/storage.rs b/src/inventory/storage.rs new file mode 100644 index 0000000..740e69f --- /dev/null +++ b/src/inventory/storage.rs @@ -0,0 +1,38 @@ +use binrw::binrw; + +use super::Item; + +#[binrw] +#[brw(little)] +#[brw(repr = u16)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] +pub enum ContainerType { + #[default] + Inventory0 = 0, + Inventory1 = 1, + Inventory2 = 2, + Inventory3 = 3, + + Equipped = 1000, + + ArmoryOffWeapon = 3200, + ArmoryHead = 3201, + ArmoryBody = 3202, + ArmoryHand = 3203, + ArmoryLeg = 3205, + ArmoryFoot = 3206, + ArmoryEarring = 3207, + ArmoryNeck = 3208, + ArmoryWrist = 3209, + ArmoryRing = 3300, + ArmorySoulCrystal = 3400, + ArmoryWeapon = 3500, +} + +/// Represents a generic item storage. +pub trait Storage: Sync { + fn max_slots(&self) -> u32; + fn num_items(&self) -> u32; + fn get_slot_mut(&mut self, index: u16) -> &mut Item; + fn get_slot(&self, index: u16) -> &Item; +} diff --git a/src/ipc/zone/container_info.rs b/src/ipc/zone/container_info.rs index aba7341..4ea51ce 100644 --- a/src/ipc/zone/container_info.rs +++ b/src/ipc/zone/container_info.rs @@ -1,18 +1,6 @@ use binrw::binrw; -#[binrw] -#[brw(little)] -#[brw(repr = u16)] -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] -pub enum ContainerType { - #[default] - Inventory0 = 0, - Inventory1 = 1, - Inventory2 = 2, - Inventory3 = 3, - Equipped = 1000, - ArmouryBody = 3202, -} +use crate::inventory::ContainerType; #[binrw] #[brw(little)] diff --git a/src/ipc/zone/inventory_modify.rs b/src/ipc/zone/inventory_modify.rs index a102e23..f295af7 100644 --- a/src/ipc/zone/inventory_modify.rs +++ b/src/ipc/zone/inventory_modify.rs @@ -1,6 +1,6 @@ use binrw::binrw; -use super::ContainerType; +use crate::inventory::ContainerType; #[binrw] #[derive(Debug, Clone, Default)] @@ -51,7 +51,7 @@ mod tests { assert_eq!(modify_inventory.src_stack, 1); assert_eq!(modify_inventory.src_catalog_id, 0); assert_eq!(modify_inventory.dst_actor_id, 0); - assert_eq!(modify_inventory.dst_storage_id, ContainerType::ArmouryBody); + assert_eq!(modify_inventory.dst_storage_id, ContainerType::ArmoryBody); assert_eq!(modify_inventory.dst_container_index, 0); assert_eq!(modify_inventory.dst_stack, 0); assert_eq!(modify_inventory.dst_catalog_id, 0); diff --git a/src/ipc/zone/item_info.rs b/src/ipc/zone/item_info.rs index a9c6d9e..20f8a5e 100644 --- a/src/ipc/zone/item_info.rs +++ b/src/ipc/zone/item_info.rs @@ -1,6 +1,6 @@ use binrw::binrw; -use super::ContainerType; +use crate::inventory::ContainerType; #[binrw] #[brw(little)] diff --git a/src/ipc/zone/mod.rs b/src/ipc/zone/mod.rs index 64dc1ac..cdba099 100644 --- a/src/ipc/zone/mod.rs +++ b/src/ipc/zone/mod.rs @@ -48,7 +48,7 @@ mod action_request; pub use action_request::ActionRequest; mod container_info; -pub use container_info::{ContainerInfo, ContainerType}; +pub use container_info::ContainerInfo; mod item_info; pub use item_info::ItemInfo; diff --git a/src/lib.rs b/src/lib.rs index 9e29f49..a3ec257 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,9 @@ pub mod opcodes; /// IPC pub mod ipc; +/// Inventory and storage management. +pub mod inventory; + /// Used in the encryption key. const GAME_VERSION: u16 = 7000; diff --git a/src/lobby/connection.rs b/src/lobby/connection.rs index 4feb916..21ad6d3 100644 --- a/src/lobby/connection.rs +++ b/src/lobby/connection.rs @@ -5,7 +5,7 @@ use tokio::{io::AsyncReadExt, net::TcpStream}; use crate::{ RECEIVE_BUFFER_SIZE, blowfish::Blowfish, - common::{timestamp_secs, workdefinitions::CharaMake}, + common::timestamp_secs, config::get_config, opcodes::ServerLobbyIpcType, packet::oodle::OodleNetwork, diff --git a/src/world/connection.rs b/src/world/connection.rs index 9b0121b..1783999 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -12,12 +12,13 @@ use crate::{ OBFUSCATION_ENABLED_MODE, common::{GameData, ObjectId, Position, timestamp_secs}, config::get_config, + inventory::{Inventory, Item}, ipc::chat::ServerChatIpcSegment, ipc::zone::{ ActorControlSelf, ActorMove, ActorSetPos, ClientZoneIpcSegment, CommonSpawn, ContainerInfo, - ContainerType, DisplayFlag, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind, PlayerStats, - PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, - UpdateClassInfo, WeatherChange, + DisplayFlag, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind, + ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, + WeatherChange, }, opcodes::ServerZoneIpcType, packet::{ @@ -27,8 +28,7 @@ use crate::{ }; use super::{ - Actor, CharacterData, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone, - inventory::Container, lua::Task, + Actor, CharacterData, Event, LuaPlayer, StatusEffects, WorldDatabase, Zone, lua::Task, }; #[derive(Debug, Default, Clone)] @@ -443,23 +443,14 @@ impl ZoneConnection { pub async fn send_inventory(&mut self, send_appearance_update: bool) { let mut sequence = 0; - // pages - for (i, page) in self.player_data.inventory.pages.clone().iter().enumerate() { - let kind = match i { - 0 => ContainerType::Inventory0, - 1 => ContainerType::Inventory1, - 2 => ContainerType::Inventory2, - 3 => ContainerType::Inventory3, - _ => panic!("Shouldn't be anything else!"), - }; - + for (container_type, container) in &self.player_data.inventory.clone() { let mut send_slot = async |slot_index: u16, item: &Item| { let ipc = ServerZoneIpcSegment { op_code: ServerZoneIpcType::UpdateItem, timestamp: timestamp_secs(), data: ServerZoneIpcData::UpdateItem(ItemInfo { sequence, - container: kind, + container: container_type, slot: slot_index, quantity: item.quantity, catalog_id: item.id, @@ -478,8 +469,8 @@ impl ZoneConnection { .await; }; - for (i, slot) in page.slots.iter().enumerate() { - send_slot(i as u16, slot).await; + for i in 0..container.max_slots() { + send_slot(i as u16, container.get_slot(i as u16)).await; } // inform the client of container state @@ -488,8 +479,8 @@ impl ZoneConnection { op_code: ServerZoneIpcType::ContainerInfo, timestamp: timestamp_secs(), data: ServerZoneIpcData::ContainerInfo(ContainerInfo { - container: kind, - num_items: page.num_items(), + container: container_type, + num_items: container.num_items(), sequence, ..Default::default() }), @@ -508,63 +499,6 @@ impl ZoneConnection { sequence += 1; } - // equipped - { - let equipped = self.player_data.inventory.equipped; - - let mut send_slot = async |slot_index: u16, item: &Item| { - let ipc = ServerZoneIpcSegment { - op_code: ServerZoneIpcType::UpdateItem, - timestamp: timestamp_secs(), - data: ServerZoneIpcData::UpdateItem(ItemInfo { - sequence, - container: ContainerType::Equipped, - slot: slot_index, - quantity: item.quantity, - catalog_id: item.id, - condition: 30000, - ..Default::default() - }), - ..Default::default() - }; - - self.send_segment(PacketSegment { - source_actor: self.player_data.actor_id, - target_actor: self.player_data.actor_id, - segment_type: SegmentType::Ipc, - data: SegmentData::Ipc { data: ipc }, - }) - .await; - }; - - for (i, slot) in equipped.into_iter().enumerate() { - send_slot(i as u16, &slot).await; - } - } - - // inform the client they have items equipped - { - let ipc = ServerZoneIpcSegment { - op_code: ServerZoneIpcType::ContainerInfo, - timestamp: timestamp_secs(), - data: ServerZoneIpcData::ContainerInfo(ContainerInfo { - container: ContainerType::Equipped, - num_items: self.player_data.inventory.equipped.num_items(), - sequence, - ..Default::default() - }), - ..Default::default() - }; - - self.send_segment(PacketSegment { - source_actor: self.player_data.actor_id, - target_actor: self.player_data.actor_id, - segment_type: SegmentType::Ipc, - data: SegmentData::Ipc { data: ipc }, - }) - .await; - } - // send them an appearance update if send_appearance_update { let ipc; diff --git a/src/world/database.rs b/src/world/database.rs index 216e1c6..1885903 100644 --- a/src/world/database.rs +++ b/src/world/database.rs @@ -8,10 +8,11 @@ use crate::{ CustomizeData, GameData, Position, workdefinitions::{CharaMake, ClientSelectData, RemakeMode}, }, + inventory::Inventory, ipc::lobby::{CharacterDetails, CharacterFlag}, }; -use super::{Inventory, PlayerData}; +use super::PlayerData; pub struct WorldDatabase { connection: Mutex, diff --git a/src/world/inventory.rs b/src/world/inventory.rs deleted file mode 100644 index b33b3a9..0000000 --- a/src/world/inventory.rs +++ /dev/null @@ -1,315 +0,0 @@ -use physis::common::Language; -use serde::{Deserialize, Serialize}; - -use crate::common::GameData; - -use crate::ipc::zone::{ContainerType, InventoryModify}; - -// TODO: rename to storage? -pub trait Container { - fn num_items(&self) -> u32; - fn get_slot_mut(&mut self, index: u16) -> &mut Item; - fn get_slot(&self, index: u16) -> &Item; -} - -#[derive(Default, Copy, Clone, Serialize, Deserialize, Debug)] -pub struct Item { - pub quantity: u32, - pub id: u32, -} - -impl Item { - pub fn new(quantity: u32, id: u32) -> Self { - Self { quantity, id } - } -} - -#[derive(Default, Clone, Copy, Deserialize, Serialize, Debug)] -pub struct EquippedContainer { - pub main_hand: Item, - pub off_hand: Item, - pub head: Item, - pub body: Item, - pub hands: Item, - pub legs: Item, - pub feet: Item, - pub ears: Item, - pub neck: Item, - pub wrists: Item, - pub right_ring: Item, - pub left_ring: Item, - pub soul_crystal: Item, - - // only for the iterator, so it can skip over it - pub belt: Item, -} - -impl Container for EquippedContainer { - fn num_items(&self) -> u32 { - self.main_hand.quantity - + self.off_hand.quantity - + self.head.quantity - + self.body.quantity - + self.hands.quantity - + self.legs.quantity - + self.feet.quantity - + self.ears.quantity - + self.neck.quantity - + self.wrists.quantity - + self.right_ring.quantity - + self.left_ring.quantity - + self.soul_crystal.quantity - } - - fn get_slot_mut(&mut self, index: u16) -> &mut Item { - match index { - 0 => &mut self.main_hand, - 1 => &mut self.off_hand, - 2 => &mut self.head, - 3 => &mut self.body, - 4 => &mut self.hands, - 6 => &mut self.legs, - 7 => &mut self.feet, - 8 => &mut self.ears, - 9 => &mut self.neck, - 10 => &mut self.wrists, - 11 => &mut self.right_ring, - 12 => &mut self.left_ring, - 13 => &mut self.soul_crystal, - _ => panic!("{} is not a valid src_container_index?!?", index), - } - } - - fn get_slot(&self, index: u16) -> &Item { - match index { - 0 => &self.main_hand, - 1 => &self.off_hand, - 2 => &self.head, - 3 => &self.body, - 4 => &self.hands, - 5 => &self.belt, - 6 => &self.legs, - 7 => &self.feet, - 8 => &self.ears, - 9 => &self.neck, - 10 => &self.wrists, - 11 => &self.right_ring, - 12 => &self.left_ring, - 13 => &self.soul_crystal, - _ => panic!("{} is not a valid src_container_index?!?", index), - } - } -} - -impl<'a> IntoIterator for &'a EquippedContainer { - type Item = &'a Item; - type IntoIter = EquippedContainerIterator<'a>; - fn into_iter(self) -> EquippedContainerIterator<'a> { - EquippedContainerIterator { - equipped: self, - curr: 0, - } - } -} - -pub struct EquippedContainerIterator<'a> { - equipped: &'a EquippedContainer, - curr: u16, -} - -impl<'a> Iterator for EquippedContainerIterator<'a> { - type Item = &'a Item; - - fn next(&mut self) -> Option { - let curr = self.curr; - self.curr += 1; - - if self.curr >= 14 { - return None; - } - - Some(self.equipped.get_slot(curr)) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct InventoryPage { - pub slots: Vec, -} - -impl InventoryPage { - fn default() -> Self { - Self { - slots: vec![Item::default(); 35], - } - } -} - -impl Container for InventoryPage { - fn num_items(&self) -> u32 { - self.slots.iter().filter(|item| item.quantity > 0).count() as u32 - } - - fn get_slot_mut(&mut self, index: u16) -> &mut Item { - self.slots.get_mut(index as usize).unwrap() - } - - fn get_slot(&self, index: u16) -> &Item { - self.slots.get(index as usize).unwrap() - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Inventory { - pub equipped: EquippedContainer, - pub pages: [InventoryPage; 4], -} - -impl Default for Inventory { - fn default() -> Self { - Self { - equipped: EquippedContainer::default(), - pages: std::array::from_fn(|_| InventoryPage::default()), - } - } -} - -impl Inventory { - /// Equip the starting items for a given race - pub fn equip_racial_items(&mut self, race_id: u8, gender: u8, game_data: &mut GameData) { - let exh = game_data.game_data.read_excel_sheet_header("Race").unwrap(); - let exd = game_data - .game_data - .read_excel_sheet("Race", &exh, Language::English, 0) - .unwrap(); - - let world_row = &exd.read_row(&exh, race_id as u32).unwrap()[0]; - - let get_column = |column_index: usize| { - let physis::exd::ColumnData::Int32(item_id) = &world_row.data[column_index] else { - panic!("Unexpected type!"); - }; - - *item_id - }; - - if gender == 0 { - self.equipped.body = Item::new(1, get_column(2) as u32); - self.equipped.hands = Item::new(1, get_column(3) as u32); - self.equipped.legs = Item::new(1, get_column(4) as u32); - self.equipped.feet = Item::new(1, get_column(5) as u32); - } else { - self.equipped.body = Item::new(1, get_column(6) as u32); - self.equipped.hands = Item::new(1, get_column(7) as u32); - self.equipped.legs = Item::new(1, get_column(8) as u32); - self.equipped.feet = Item::new(1, get_column(9) as u32); - } - - // TODO: don't hardcode - self.equipped.main_hand = Item::new(1, 0x00000641); - self.equipped.ears = Item::new(1, 0x00003b1b); - self.equipped.neck = Item::new(1, 0x00003b1a); - self.equipped.wrists = Item::new(1, 0x00003b1c); - self.equipped.right_ring = Item::new(1, 0x0000114a); - self.equipped.left_ring = Item::new(1, 0x00003b1d); - } - - pub fn process_action(&mut self, action: &InventoryModify) { - if action.operation_type == 78 { - // discard - let src_container = self.get_container(&action.src_storage_id); - let src_slot = src_container.get_slot_mut(action.src_container_index); - *src_slot = Item::default(); - } else { - // NOTE: only swaps items for now - - let src_item; - // get the source item - { - let src_container = self.get_container(&action.src_storage_id); - let src_slot = src_container.get_slot_mut(action.src_container_index); - src_item = *src_slot; - } - - let dst_item; - // move into dst item - { - let dst_container = self.get_container(&action.dst_storage_id); - let dst_slot = dst_container.get_slot_mut(action.dst_container_index); - - dst_item = *dst_slot; - dst_slot.clone_from(&src_item); - } - - // move dst item into src slot - { - let src_container = self.get_container(&action.src_storage_id); - let src_slot = src_container.get_slot_mut(action.src_container_index); - src_slot.clone_from(&dst_item); - } - } - } - - pub fn add_in_next_free_slot(&mut self, item: Item) { - for page in &mut self.pages { - for slot in &mut page.slots { - if slot.quantity == 0 { - slot.clone_from(&item); - return; - } - } - } - } - - fn get_container(&mut self, container_type: &ContainerType) -> &mut dyn Container { - match container_type { - ContainerType::Inventory0 => &mut self.pages[0], - ContainerType::Inventory1 => &mut self.pages[1], - ContainerType::Inventory2 => &mut self.pages[2], - ContainerType::Inventory3 => &mut self.pages[3], - ContainerType::Equipped => &mut self.equipped, - ContainerType::ArmouryBody => todo!(), - } - } - - pub fn get_main_weapon_id(&self, game_data: &mut GameData) -> u64 { - game_data - .get_primary_model_id(self.equipped.main_hand.id) - .unwrap_or(0) - } - - pub fn get_model_ids(&self, game_data: &mut GameData) -> [u32; 10] { - [ - game_data - .get_primary_model_id(self.equipped.head.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.body.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.hands.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.legs.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.feet.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.ears.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.neck.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.wrists.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.left_ring.id) - .unwrap_or(0) as u32, - game_data - .get_primary_model_id(self.equipped.right_ring.id) - .unwrap_or(0) as u32, - ] - } -} diff --git a/src/world/mod.rs b/src/world/mod.rs index f704ee0..cdc1a97 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -12,9 +12,6 @@ pub use connection::{ mod database; pub use database::{CharacterData, WorldDatabase}; -mod inventory; -pub use inventory::{EquippedContainer, Inventory, Item}; - mod lua; pub use lua::{EffectsBuilder, LuaPlayer};