diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index e5e16e8..a847685 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -18,8 +18,8 @@ use kawari::world::ipc::{ GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType, }; use kawari::world::{ - Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, LuaPlayer, PlayerData, - ServerHandle, StatusEffects, ToServer, WorldDatabase, + Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, Item, LuaPlayer, + PlayerData, ServerHandle, StatusEffects, ToServer, WorldDatabase, }; use kawari::world::{ ChatHandler, Zone, ZoneConnection, @@ -615,8 +615,7 @@ async fn client_loop( }) .await, GameMasterCommandType::GiveItem => { - connection.player_data.inventory.extra_slot.id = *arg; - connection.player_data.inventory.extra_slot.quantity = 1; + connection.player_data.inventory.add_in_next_free_slot(Item { id: *arg, quantity: 1 }); connection.send_inventory(false).await; } } @@ -816,7 +815,7 @@ async fn client_loop( game_data.get_citystate(chara_make.classjob_id as u16); } - let mut inventory = Inventory::new(); + let mut inventory = Inventory::default(); // fill inventory inventory.equip_racial_items( @@ -1002,7 +1001,7 @@ async fn client_loop( connection.process_effects_list().await; // update lua player - lua_player.player_data = connection.player_data; + lua_player.player_data = connection.player_data.clone(); lua_player.status_effects = connection.status_effects.clone(); } } diff --git a/src/world/chat_handler.rs b/src/world/chat_handler.rs index fc22a50..db38d54 100644 --- a/src/world/chat_handler.rs +++ b/src/world/chat_handler.rs @@ -322,7 +322,7 @@ impl ChatHandler { "!spawnclone" => { // spawn another one of us - let player = connection.player_data; + let player = &connection.player_data; let mut common = connection .get_player_common_spawn(Some(player.position), Some(player.rotation)); diff --git a/src/world/connection.rs b/src/world/connection.rs index 1b8c818..f3fe69e 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -19,6 +19,7 @@ use crate::{ use super::{ Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone, + inventory::Container, ipc::{ ActorControlSelf, ActorMove, ActorSetPos, ClientZoneIpcSegment, CommonSpawn, ContainerInfo, ContainerType, DisplayFlag, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind, PlayerSubKind, @@ -27,7 +28,7 @@ use super::{ }, }; -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone)] pub struct PlayerData { // Static data pub actor_id: u32, @@ -428,16 +429,25 @@ impl ZoneConnection { } pub async fn send_inventory(&mut self, send_appearance_update: bool) { - // page 1 - { - let extra_slot = self.player_data.inventory.extra_slot; + 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!"), + }; let mut send_slot = async |slot_index: u16, item: &Item| { let ipc = ServerZoneIpcSegment { op_code: ServerZoneIpcType::ItemInfo, timestamp: timestamp_secs(), data: ServerZoneIpcData::ItemInfo(ItemInfo { - container: ContainerType::Inventory0, + sequence, + container: kind, slot: slot_index, quantity: item.quantity, catalog_id: item.id, @@ -455,7 +465,33 @@ impl ZoneConnection { .await; }; - send_slot(0, &extra_slot).await; + for (i, slot) in page.slots.iter().enumerate() { + send_slot(i as u16, slot).await; + } + + // inform the client of container state + { + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::ContainerInfo, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::ContainerInfo(ContainerInfo { + container: kind, + num_items: page.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: ipc }, + }) + .await; + } + + sequence += 1; } // equipped @@ -467,6 +503,7 @@ impl ZoneConnection { op_code: ServerZoneIpcType::ItemInfo, timestamp: timestamp_secs(), data: ServerZoneIpcData::ItemInfo(ItemInfo { + sequence, container: ContainerType::Equipped, slot: slot_index, quantity: item.quantity, @@ -485,6 +522,7 @@ impl ZoneConnection { .await; }; + // TODO: make containers enumerable to vastly simplify this code send_slot(0, &equipped.main_hand).await; send_slot(1, &equipped.off_hand).await; send_slot(2, &equipped.head).await; @@ -500,27 +538,6 @@ impl ZoneConnection { send_slot(13, &equipped.soul_crystal).await; } - // inform the client of page 1 - { - let ipc = ServerZoneIpcSegment { - op_code: ServerZoneIpcType::ContainerInfo, - timestamp: timestamp_secs(), - data: ServerZoneIpcData::ContainerInfo(ContainerInfo { - container: ContainerType::Inventory0, - num_items: 1, - ..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: ipc }, - }) - .await; - } - // inform the client they have items equipped { let ipc = ServerZoneIpcSegment { @@ -529,7 +546,7 @@ impl ZoneConnection { data: ServerZoneIpcData::ContainerInfo(ContainerInfo { container: ContainerType::Equipped, num_items: self.player_data.inventory.equipped.num_items(), - sequence: 1, + sequence, ..Default::default() }), ..Default::default() diff --git a/src/world/database.rs b/src/world/database.rs index e9c7b00..87d78eb 100644 --- a/src/world/database.rs +++ b/src/world/database.rs @@ -117,7 +117,7 @@ impl WorldDatabase { &chara_make.to_json(), 2, 132, - Inventory::new(), + Inventory::default(), ); tracing::info!("{} added to the world!", character.name); diff --git a/src/world/inventory.rs b/src/world/inventory.rs index 076eb78..ebb08ee 100644 --- a/src/world/inventory.rs +++ b/src/world/inventory.rs @@ -8,6 +8,12 @@ use crate::config::get_config; use super::ipc::{ContainerType, InventoryModify}; +// TODO: rename to storage? +pub trait Container { + fn num_items(&self) -> u32; + fn get_slot<'a>(&'a mut self, index: u16) -> &'a mut Item; +} + #[derive(Default, Copy, Clone, Serialize, Deserialize, Debug)] pub struct Item { pub quantity: u32, @@ -37,8 +43,8 @@ pub struct EquippedContainer { pub soul_crystal: Item, } -impl EquippedContainer { - pub fn num_items(&self) -> u32 { +impl Container for EquippedContainer { + fn num_items(&self) -> u32 { self.main_hand.quantity + self.off_hand.quantity + self.head.quantity @@ -54,7 +60,7 @@ impl EquippedContainer { + self.soul_crystal.quantity } - pub fn get_slot(&mut self, index: u16) -> &mut Item { + fn get_slot(&mut self, index: u16) -> &mut Item { match index { 0 => &mut self.main_hand, 1 => &mut self.off_hand, @@ -74,26 +80,45 @@ impl EquippedContainer { } } -#[derive(Debug, Clone, Copy, Deserialize, Serialize)] +#[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 self, index: u16) -> &mut Item { + self.slots.get_mut(index as usize).unwrap() + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct Inventory { pub equipped: EquippedContainer, - pub extra_slot: Item, // WIP for inventory pages + pub pages: [InventoryPage; 4], } impl Default for Inventory { fn default() -> Self { - Self::new() + Self { + equipped: EquippedContainer::default(), + pages: std::array::from_fn(|_| InventoryPage::default()), + } } } impl Inventory { - pub fn new() -> Self { - Self { - equipped: EquippedContainer::default(), - extra_slot: Item::default(), - } - } - /// Equip the starting items for a given race pub fn equip_racial_items(&mut self, race_id: u8, gender: u8) { let config = get_config(); @@ -138,16 +163,60 @@ impl Inventory { } pub fn process_action(&mut self, action: &InventoryModify) { - // equipped - if action.src_storage_id == ContainerType::Equipped { - let src_slot = self.equipped.get_slot(action.src_container_index); - - // it only unequips for now, doesn't move the item + if action.operation_type == 571 { + // discard + let src_container = self.get_container(&action.src_storage_id); + let src_slot = src_container.get_slot(action.src_container_index); *src_slot = Item::default(); - } else if action.src_storage_id == ContainerType::Inventory0 { - let dst_slot = self.equipped.get_slot(action.dst_container_index); + } else { + // NOTE: only swaps items for now - *dst_slot = self.extra_slot; + let src_item; + // get the source item + { + let src_container = self.get_container(&action.src_storage_id); + let src_slot = src_container.get_slot(action.src_container_index); + src_item = src_slot.clone(); + } + + let dst_item; + // move into dst item + { + let dst_container = self.get_container(&action.dst_storage_id); + let dst_slot = dst_container.get_slot(action.dst_container_index); + + dst_item = dst_slot.clone(); + 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(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!(), } } } diff --git a/src/world/ipc/container_info.rs b/src/world/ipc/container_info.rs index 560531d..aba7341 100644 --- a/src/world/ipc/container_info.rs +++ b/src/world/ipc/container_info.rs @@ -3,7 +3,7 @@ use binrw::binrw; #[binrw] #[brw(little)] #[brw(repr = u16)] -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] pub enum ContainerType { #[default] Inventory0 = 0,