diff --git a/resources/opcodes.json b/resources/opcodes.json index 9794bd1..c8a09e6 100644 --- a/resources/opcodes.json +++ b/resources/opcodes.json @@ -144,6 +144,11 @@ "name": "UpdateHpMpTp", "opcode": 325, "size": 8 + }, + { + "name": "ActionResult", + "opcode": 785, + "size": 124 } ], "ClientZoneIpcType": [ diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 6f8df62..24bebdc 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -2,8 +2,8 @@ use std::sync::{Arc, Mutex}; use kawari::common::custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType}; use kawari::common::{ - INVALID_OBJECT_ID, ObjectId, Position, determine_initial_starting_zone, get_citystate, - get_world_name, + INVALID_OBJECT_ID, ObjectId, ObjectTypeId, Position, determine_initial_starting_zone, + get_citystate, get_world_name, }; use kawari::common::{get_racial_base_attributes, timestamp_secs}; use kawari::config::get_config; @@ -15,8 +15,9 @@ use kawari::packet::{ send_packet, }; use kawari::world::ipc::{ - ClientZoneIpcData, CommonSpawn, DisplayFlag, GameMasterCommandType, GameMasterRank, ObjectKind, - OnlineStatus, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType, + ActionEffect, ActionResult, ClientZoneIpcData, CommonSpawn, DisplayFlag, GameMasterCommandType, + GameMasterRank, ObjectKind, OnlineStatus, PlayerSubKind, ServerZoneIpcData, + ServerZoneIpcSegment, SocialListRequestType, }; use kawari::world::{ ChatHandler, Inventory, Zone, ZoneConnection, @@ -362,7 +363,7 @@ async fn main() { gm_rank: GameMasterRank::Debug, online_status: OnlineStatus::GameMasterBlue, common: CommonSpawn { - class_job: 35, + class_job: 1, name: chara_details.name, hp_curr: 100, hp_max: 100, @@ -782,7 +783,7 @@ async fn main() { println!("Found action: {:#?}", action_row); - //if request.target.object_id == INVALID_OBJECT_ID { + // placeholder for now if let Some(actor) = connection.get_actor(ObjectId(0x106ad804)) { @@ -793,22 +794,61 @@ async fn main() { .update_hp_mp(actor.id, actor.hp, 10000) .await; } - //} else { - let lua = lua.lock().unwrap(); - lua.scope(|scope| { - let connection_data = scope - .create_userdata_ref_mut(&mut lua_player) - .unwrap(); - let func: Function = - lua.globals().get("doAction").unwrap(); + { + let lua = lua.lock().unwrap(); + lua.scope(|scope| { + let connection_data = scope + .create_userdata_ref_mut(&mut lua_player) + .unwrap(); - func.call::<()>(connection_data).unwrap(); + let func: Function = + lua.globals().get("doAction").unwrap(); - Ok(()) - }) - .unwrap(); - //} + func.call::<()>(connection_data).unwrap(); + + Ok(()) + }) + .unwrap(); + } + + // tell them the action results + { + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::ActionResult, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::ActionResult( + ActionResult { + main_target: ObjectTypeId { + object_id: ObjectId(0x106ad804), + object_type: 0, + }, + action_id: 31, + animation_lock_time: 0.6, + rotation: connection.player_data.rotation, + action_animation_id: 31, + flag: 1, + effect_count: 1, + effects: [ActionEffect { + action_type: 3, + value: 50, + ..Default::default() + }; + 8], + ..Default::default() + }, + ), + ..Default::default() + }; + + connection + .send_segment(PacketSegment { + source_actor: connection.player_data.actor_id, + target_actor: connection.player_data.actor_id, + segment_type: SegmentType::Ipc { data: ipc }, + }) + .await; + } } ClientZoneIpcData::Unk16 { .. } => { tracing::info!("Recieved Unk16!"); diff --git a/src/world/connection.rs b/src/world/connection.rs index 50a9e7e..541a350 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -132,7 +132,7 @@ impl ZoneConnection { op_code: ServerZoneIpcType::UpdateClassInfo, timestamp: timestamp_secs(), data: ServerZoneIpcData::UpdateClassInfo(UpdateClassInfo { - class_id: 35, + class_id: 1, unknown: 1, synced_level: 90, class_level: 90, @@ -348,7 +348,7 @@ impl ZoneConnection { }; self.send_segment(PacketSegment { - source_actor: actor_id.0, + source_actor: self.player_data.actor_id, target_actor: actor_id.0, segment_type: SegmentType::Ipc { data: ipc }, }) diff --git a/src/world/ipc/action_request.rs b/src/world/ipc/action_request.rs index 366ec00..9a44519 100644 --- a/src/world/ipc/action_request.rs +++ b/src/world/ipc/action_request.rs @@ -1,6 +1,6 @@ use binrw::binrw; -use crate::common::ObjectTypeId; +use crate::common::{ObjectTypeId, read_quantized_rotation, write_quantized_rotation}; #[binrw] #[derive(Debug, Eq, PartialEq, Clone, Default)] @@ -18,10 +18,38 @@ pub struct ActionRequest { pub action_kind: ActionKind, #[brw(pad_before = 2)] // this ISNT empty pub action_id: u32, // See Action Excel sheet - pub request_id: u32, + pub request_id: u16, + #[br(map = read_quantized_rotation)] + #[bw(map = write_quantized_rotation)] + pub rotation: f32, pub dir: u16, pub dir_target: u16, pub target: ObjectTypeId, pub arg: u32, pub padding_prob: u32, } + +#[cfg(test)] +mod tests { + use std::{fs::read, io::Cursor, path::PathBuf}; + + use binrw::BinRead; + + use crate::common::ObjectId; + + use super::*; + + #[test] + fn read_actionrequest() { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources/tests/action_request.bin"); + + let buffer = read(d).unwrap(); + let mut buffer = Cursor::new(&buffer); + + let action_request = ActionRequest::read_le(&mut buffer).unwrap(); + assert_eq!(action_request.target.object_id, ObjectId(0x400097d0)); + assert_eq!(action_request.request_id, 0x2); + assert_eq!(action_request.rotation, 1.9694216); + } +} diff --git a/src/world/ipc/action_result.rs b/src/world/ipc/action_result.rs new file mode 100644 index 0000000..48cf59f --- /dev/null +++ b/src/world/ipc/action_result.rs @@ -0,0 +1,75 @@ +use binrw::binrw; + +use crate::common::{ObjectTypeId, read_quantized_rotation, write_quantized_rotation}; + +#[binrw] +#[brw(little)] +#[derive(Debug, Clone, Copy, Default)] +pub struct ActionEffect { + pub action_type: u8, + pub param0: u8, + pub param1: u8, + pub param2: u8, + pub param3: u8, + pub param4: u8, + pub value: u16, +} + +#[binrw] +#[brw(little)] +#[derive(Debug, Clone, Default)] +pub struct ActionResult { + pub main_target: ObjectTypeId, + pub action_id: u32, + pub unk1: u32, + pub animation_lock_time: f32, + pub unk2: u32, + pub hidden_animation: u16, + #[br(map = read_quantized_rotation)] + #[bw(map = write_quantized_rotation)] + pub rotation: f32, + pub action_animation_id: u16, + pub variation: u8, + pub flag: u8, + pub unk3: u8, + pub effect_count: u8, + pub unk4: u16, + pub unk5: [u8; 6], + #[brw(pad_after = 18)] // idk, target is here too? + pub effects: [ActionEffect; 8], +} + +#[cfg(test)] +mod tests { + use std::{fs::read, io::Cursor, path::PathBuf}; + + use binrw::BinRead; + + use crate::common::ObjectId; + + use super::*; + + #[test] + fn read_actionresult() { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources/tests/action_result.bin"); + + let buffer = read(d).unwrap(); + let mut buffer = Cursor::new(&buffer); + + let action_result = ActionResult::read_le(&mut buffer).unwrap(); + assert_eq!(action_result.main_target.object_id, ObjectId(0x400097d0)); + assert_eq!(action_result.action_id, 31); + assert_eq!(action_result.animation_lock_time, 0.6); + assert_eq!(action_result.rotation, 1.9694216); + assert_eq!(action_result.action_animation_id, 31); + assert_eq!(action_result.flag, 1); + assert_eq!(action_result.effect_count, 1); + + // effect 0: attack + assert_eq!(action_result.effects[0].action_type, 3); + + // effect 1: start action combo + assert_eq!(action_result.effects[1].action_type, 27); + } +} diff --git a/src/world/ipc/mod.rs b/src/world/ipc/mod.rs index 73c3b97..6fcb451 100644 --- a/src/world/ipc/mod.rs +++ b/src/world/ipc/mod.rs @@ -59,6 +59,9 @@ pub use event_play::EventPlay; mod event_start; pub use event_start::EventStart; +mod action_result; +pub use action_result::{ActionEffect, ActionResult}; + use crate::common::Position; use crate::common::read_string; use crate::common::write_string; @@ -222,6 +225,8 @@ pub enum ServerZoneIpcData { mp: u16, unk: u16, // it's filled with... something }, + /// Sent to inform the client the consequences of their actions + ActionResult(ActionResult), } #[binrw] @@ -437,6 +442,10 @@ mod tests { ServerZoneIpcType::EventStart, ServerZoneIpcData::EventStart(EventStart::default()), ), + ( + ServerZoneIpcType::ActionResult, + ServerZoneIpcData::ActionResult(ActionResult::default()), + ), ]; for (opcode, data) in &ipc_types {