1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-22 07:27:44 +00:00

Allow unequipping items, add packet for updating equipped model ids

The unequipped item currently disappears into the aether, but this
works now!
This commit is contained in:
Joshua Goins 2025-03-31 21:49:12 -04:00
parent bc8535cce2
commit 0c76d847d5
8 changed files with 150 additions and 19 deletions

View file

@ -149,6 +149,11 @@
"name": "ActionResult", "name": "ActionResult",
"opcode": 508, "opcode": 508,
"size": 124 "size": 124
},
{
"name": "Equip",
"opcode": 468,
"size": 72
} }
], ],
"ClientZoneIpcType": [ "ClientZoneIpcType": [
@ -260,7 +265,7 @@
{ {
"name": "InventoryModify", "name": "InventoryModify",
"opcode": 564, "opcode": 564,
"size": 112 "size": 48
} }
], ],
"ServerLobbyIpcType": [ "ServerLobbyIpcType": [

BIN
resources/tests/equip.bin Normal file

Binary file not shown.

View file

@ -19,8 +19,8 @@ use kawari::world::ipc::{
ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
}; };
use kawari::world::{ use kawari::world::{
Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, LuaPlayer, PlayerData, ServerHandle, Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Item, LuaPlayer, PlayerData,
StatusEffects, ToServer, WorldDatabase, ServerHandle, StatusEffects, ToServer, WorldDatabase,
}; };
use kawari::world::{ use kawari::world::{
ChatHandler, Inventory, Zone, ZoneConnection, ChatHandler, Inventory, Zone, ZoneConnection,
@ -283,7 +283,7 @@ async fn client_loop(
); );
// Send inventory // Send inventory
connection.send_inventory().await; connection.send_inventory(false).await;
// set chara gear param // set chara gear param
connection connection
@ -815,6 +815,9 @@ async fn client_loop(
} }
ClientZoneIpcData::InventoryModify(action) => { ClientZoneIpcData::InventoryModify(action) => {
tracing::info!("Client is modifying inventory! {action:#?}"); tracing::info!("Client is modifying inventory! {action:#?}");
connection.inventory.process_action(&action);
connection.send_inventory(true).await;
} }
} }
} }

View file

@ -22,7 +22,7 @@ use super::{
chat_handler::CUSTOMIZE_DATA, chat_handler::CUSTOMIZE_DATA,
ipc::{ ipc::{
ActorControlSelf, ActorMove, ActorSetPos, BattleNpcSubKind, ClientZoneIpcSegment, ActorControlSelf, ActorMove, ActorSetPos, BattleNpcSubKind, ClientZoneIpcSegment,
CommonSpawn, ContainerInfo, ContainerType, InitZone, ItemInfo, NpcSpawn, ObjectKind, CommonSpawn, ContainerInfo, ContainerType, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind,
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo,
WeatherChange, WeatherChange,
}, },
@ -447,7 +447,7 @@ impl ZoneConnection {
self.spawn_index self.spawn_index
} }
pub async fn send_inventory(&mut self) { pub async fn send_inventory(&mut self, send_appearance_update: bool) {
// item list // item list
{ {
let equipped = self.inventory.equipped; let equipped = self.inventory.equipped;
@ -510,6 +510,46 @@ impl ZoneConnection {
}) })
.await; .await;
} }
// send them an appearance update
if send_appearance_update {
let ipc;
{
let mut game_data = self.gamedata.lock().unwrap();
let equipped = &self.inventory.equipped;
ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::Equip,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::Equip(Equip {
main_weapon_id: 0,
sub_weapon_id: 0,
crest_enable: 0,
pattern_invalid: 0,
model_ids: [
game_data.get_primary_model_id(equipped.head.id) as u32,
game_data.get_primary_model_id(equipped.body.id) as u32,
game_data.get_primary_model_id(equipped.hands.id) as u32,
game_data.get_primary_model_id(equipped.legs.id) as u32,
game_data.get_primary_model_id(equipped.feet.id) as u32,
game_data.get_primary_model_id(equipped.ears.id) as u32,
game_data.get_primary_model_id(equipped.neck.id) as u32,
game_data.get_primary_model_id(equipped.wrists.id) as u32,
game_data.get_primary_model_id(equipped.left_ring.id) as u32,
game_data.get_primary_model_id(equipped.right_ring.id) as u32,
],
}),
..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;
}
} }
pub async fn send_message(&mut self, message: &str) { pub async fn send_message(&mut self, message: &str) {

View file

@ -5,6 +5,8 @@ use physis::{
use crate::config::get_config; use crate::config::get_config;
use super::ipc::InventoryModify;
#[derive(Default, Copy, Clone)] #[derive(Default, Copy, Clone)]
pub struct Item { pub struct Item {
pub quantity: u32, pub quantity: u32,
@ -111,4 +113,29 @@ impl Inventory {
self.equipped.right_ring = Item::new(1, 0x0000114a); self.equipped.right_ring = Item::new(1, 0x0000114a);
self.equipped.left_ring = Item::new(1, 0x00003b1d); self.equipped.left_ring = Item::new(1, 0x00003b1d);
} }
pub fn process_action(&mut self, action: &InventoryModify) {
// equipped
if action.src_storage_id == 1000 {
let slot = match action.src_container_index {
0 => &mut self.equipped.main_hand,
1 => &mut self.equipped.off_hand,
2 => &mut self.equipped.head,
3 => &mut self.equipped.body,
4 => &mut self.equipped.hands,
6 => &mut self.equipped.legs,
7 => &mut self.equipped.feet,
8 => &mut self.equipped.ears,
9 => &mut self.equipped.neck,
10 => &mut self.equipped.wrists,
11 => &mut self.equipped.right_ring,
12 => &mut self.equipped.left_ring,
13 => &mut self.equipped.soul_crystal,
_ => panic!("Not a valid src_container_index?!?"),
};
// it only unequips for now, doesn't move the item
*slot = Item::default();
}
}
} }

48
src/world/ipc/equip.rs Normal file
View file

@ -0,0 +1,48 @@
use binrw::binrw;
#[binrw]
#[brw(little)]
#[derive(Debug, Clone, Default)]
pub struct Equip {
pub main_weapon_id: u64,
pub sub_weapon_id: u64,
pub crest_enable: u8,
#[brw(pad_before = 1)]
pub pattern_invalid: u16,
#[brw(pad_after = 12)]
pub model_ids: [u32; 10],
}
#[cfg(test)]
mod tests {
use std::{fs::read, io::Cursor, path::PathBuf};
use binrw::BinRead;
use super::*;
#[test]
fn read_containerinfo() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests/equip.bin");
let buffer = read(d).unwrap();
let mut buffer = Cursor::new(&buffer);
let equip = Equip::read_le(&mut buffer).unwrap();
assert_eq!(equip.main_weapon_id, 4297785545);
assert_eq!(equip.sub_weapon_id, 0);
assert_eq!(equip.crest_enable, 0);
assert_eq!(equip.pattern_invalid, 1);
assert_eq!(equip.model_ids[0], 0);
assert_eq!(equip.model_ids[1], 0);
assert_eq!(equip.model_ids[2], 131156);
assert_eq!(equip.model_ids[3], 131156);
assert_eq!(equip.model_ids[4], 131156);
assert_eq!(equip.model_ids[5], 131073);
assert_eq!(equip.model_ids[6], 131073);
assert_eq!(equip.model_ids[7], 131073);
assert_eq!(equip.model_ids[8], 0);
assert_eq!(equip.model_ids[9], 131073);
}
}

View file

@ -3,21 +3,20 @@ use binrw::binrw;
#[binrw] #[binrw]
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct InventoryModify { pub struct InventoryModify {
context_id: u32, pub context_id: u32,
operation_type: u8, pub operation_type: u8,
#[brw(pad_before = 3)] #[brw(pad_before = 3)]
src_actor_id: u32, pub src_actor_id: u32,
src_storage_id: u32, pub src_storage_id: u32,
src_container_index: i16, pub src_container_index: i16,
#[brw(pad_before = 4)] #[brw(pad_before = 4)]
src_stack: u32, pub src_stack: u32,
src_catalog_id: u32, pub src_catalog_id: u32,
dst_actor_id: u32, pub dst_actor_id: u32,
dst_storage_id: u32, pub dst_storage_id: u32,
dst_container_index: i16, pub dst_container_index: i16,
#[brw(pad_before = 2)] pub dst_stack: u32,
dst_stack: u32, pub dst_catalog_id: u32,
dst_catalog_id: u32,
} }
#[cfg(test)] #[cfg(test)]

View file

@ -71,6 +71,9 @@ pub use actor_set_pos::ActorSetPos;
mod inventory_modify; mod inventory_modify;
pub use inventory_modify::InventoryModify; pub use inventory_modify::InventoryModify;
mod equip;
pub use equip::Equip;
use crate::common::Position; use crate::common::Position;
use crate::common::read_string; use crate::common::read_string;
use crate::common::write_string; use crate::common::write_string;
@ -207,6 +210,8 @@ pub enum ServerZoneIpcData {
}, },
/// Sent to inform the client the consequences of their actions /// Sent to inform the client the consequences of their actions
ActionResult(ActionResult), ActionResult(ActionResult),
/// Sent to to the client to update their appearance
Equip(Equip),
} }
#[binrw] #[binrw]
@ -458,6 +463,10 @@ mod tests {
ServerZoneIpcType::ActionResult, ServerZoneIpcType::ActionResult,
ServerZoneIpcData::ActionResult(ActionResult::default()), ServerZoneIpcData::ActionResult(ActionResult::default()),
), ),
(
ServerZoneIpcType::Equip,
ServerZoneIpcData::Equip(Equip::default()),
),
]; ];
for (opcode, data) in &ipc_types { for (opcode, data) in &ipc_types {