diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index db4d5ed..a2a4e4e 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; @@ -10,19 +9,18 @@ use kawari::config::get_config; use kawari::inventory::Item; use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment}; use kawari::ipc::zone::{ - ActionEffect, ActionResult, ClientTriggerCommand, ClientZoneIpcData, CommonSpawn, EffectKind, - EventStart, GameMasterCommandType, GameMasterRank, OnlineStatus, ServerZoneIpcData, - ServerZoneIpcSegment, SocialListRequestType, + ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList, }; use kawari::ipc::zone::{ - ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList, + ClientTriggerCommand, ClientZoneIpcData, CommonSpawn, EventStart, GameMasterCommandType, + GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType, }; use kawari::opcodes::{ServerChatIpcType, ServerZoneIpcType}; use kawari::packet::oodle::OodleNetwork; use kawari::packet::{ ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, send_keep_alive, }; -use kawari::world::{ChatHandler, Zone, ZoneConnection}; +use kawari::world::{ChatHandler, ExtraLuaState, Zone, ZoneConnection}; use kawari::world::{ ClientHandle, EffectsBuilder, Event, FromServer, LuaPlayer, PlayerData, ServerHandle, StatusEffects, ToServer, WorldDatabase, handle_custom_ipc, server_main_loop, @@ -36,13 +34,6 @@ use tokio::sync::mpsc::{Receiver, UnboundedReceiver, UnboundedSender, channel, u use tokio::sync::oneshot; use tokio::task::JoinHandle; -#[derive(Default)] -struct ExtraLuaState { - action_scripts: HashMap, - event_scripts: HashMap, - command_scripts: HashMap, -} - fn spawn_main_loop() -> (ServerHandle, JoinHandle<()>) { let (send, recv) = channel(64); @@ -683,116 +674,14 @@ async fn client_loop( connection.change_zone(new_territory).await; } ClientZoneIpcData::ActionRequest(request) => { - let mut effects_builder = None; - - // run action script - { - let lua = lua.lock().unwrap(); - let state = lua.app_data_ref::().unwrap(); - - let key = request.action_key; - if let Some(action_script) = - state.action_scripts.get(&key) - { - lua.scope(|scope| { - let connection_data = scope - .create_userdata_ref_mut(&mut lua_player) - .unwrap(); - - let config = get_config(); - - let file_name = format!( - "{}/{}", - &config.world.scripts_location, action_script - ); - lua.load( - std::fs::read(&file_name) - .expect("Failed to locate scripts directory!"), - ) - .set_name("@".to_string() + &file_name) - .exec() - .unwrap(); - - let func: Function = - lua.globals().get("doAction").unwrap(); - - effects_builder = Some( - func.call::(connection_data) - .unwrap(), - ); - - Ok(()) - }) - .unwrap(); - } else { - tracing::warn!("Action {key} isn't scripted yet! Ignoring..."); - } - } - - // tell them the action results - if let Some(effects_builder) = effects_builder { - let mut effects = [ActionEffect::default(); 8]; - effects[..effects_builder.effects.len()] - .copy_from_slice(&effects_builder.effects); - - if let Some(actor) = - connection.get_actor_mut(request.target.object_id) - { - for effect in &effects_builder.effects { - match effect.kind { - EffectKind::Damage { amount, .. } => { - actor.hp = actor.hp.saturating_sub(amount as u32); - } - _ => todo!() - } - } - - let actor = *actor; - connection.update_hp_mp(actor.id, actor.hp, 10000).await; - } - - let ipc = ServerZoneIpcSegment { - op_code: ServerZoneIpcType::ActionResult, - timestamp: timestamp_secs(), - data: ServerZoneIpcData::ActionResult(ActionResult { - main_target: request.target, - target_id_again: request.target, - action_id: request.action_key, - animation_lock_time: 0.6, - rotation: connection.player_data.rotation, - action_animation_id: request.action_key as u16, // assuming action id == animation id - flag: 1, - effect_count: effects_builder.effects.len() as u8, - effects, - unk1: 2662353, - unk2: 3758096384, - hidden_animation: 1, - ..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: SegmentData::Ipc { data: ipc }, - }) - .await; - - if let Some(actor) = - connection.get_actor(request.target.object_id) - { - if actor.hp == 0 { - tracing::info!("Despawning {} because they died!", actor.id.0); - // if the actor died, despawn them - /*connection.handle - * .send(ToServer::ActorDespawned(connection.id, actor.id.0)) - * .await;*/ - } - } - } + connection + .handle + .send(ToServer::ActionRequest( + connection.id, + connection.player_data.actor_id, + request.clone(), + )) + .await; } ClientZoneIpcData::Unk16 { .. } => { // no-op @@ -938,6 +827,8 @@ async fn client_loop( FromServer::ActorControl(actor_id, actor_control) => connection.actor_control(actor_id, actor_control).await, FromServer::ActorControlTarget(actor_id, actor_control) => connection.actor_control_target(actor_id, actor_control).await, FromServer::ActorControlSelf(actor_control) => connection.actor_control_self(actor_control).await, + FromServer::ActionComplete(request) => connection.execute_action(request, &mut lua_player).await, + FromServer::ActionCancelled() => connection.cancel_action().await, }, None => break, } diff --git a/src/ipc/zone/actor_control.rs b/src/ipc/zone/actor_control.rs index 37c2418..f36e0a1 100644 --- a/src/ipc/zone/actor_control.rs +++ b/src/ipc/zone/actor_control.rs @@ -82,6 +82,8 @@ pub enum ActorControlCategory { festival3: u32, festival4: u32, }, + #[brw(magic = 0xFu16)] + CancelCast {}, } #[binrw] diff --git a/src/world/common.rs b/src/world/common.rs index ca667db..791cbbc 100644 --- a/src/world/common.rs +++ b/src/world/common.rs @@ -11,7 +11,8 @@ use tokio::sync::mpsc::Sender; use crate::{ common::Position, ipc::zone::{ - ActorControl, ActorControlSelf, ActorControlTarget, ClientTrigger, CommonSpawn, NpcSpawn, + ActionRequest, ActorControl, ActorControlSelf, ActorControlTarget, ClientTrigger, + CommonSpawn, NpcSpawn, }, }; @@ -35,6 +36,10 @@ pub enum FromServer { ActorControlTarget(u32, ActorControlTarget), /// We need to update the player actor ActorControlSelf(ActorControlSelf), + /// Action has completed and needs to be executed + ActionComplete(ActionRequest), + /// Action has been cancelled + ActionCancelled(), } #[derive(Debug, Clone)] @@ -92,6 +97,8 @@ pub enum ToServer { DebugNewEnemy(ClientId, u32), /// Spawn a debug clone. DebugSpawnClone(ClientId, u32), + /// Request to perform an action + ActionRequest(ClientId, u32, ActionRequest), } #[derive(Clone, Debug)] diff --git a/src/world/connection.rs b/src/world/connection.rs index 79b83c7..d9e4efa 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -1,9 +1,11 @@ use std::{ + collections::HashMap, net::SocketAddr, sync::{Arc, Mutex}, time::Instant, }; +use mlua::Function; use tokio::net::TcpStream; use crate::{ @@ -14,8 +16,9 @@ use crate::{ ipc::{ chat::ServerChatIpcSegment, zone::{ - ActorControl, ActorControlSelf, ActorControlTarget, ClientZoneIpcSegment, CommonSpawn, - ContainerInfo, DisplayFlag, Equip, GameMasterRank, InitZone, ItemInfo, Move, NpcSpawn, + ActionEffect, ActionRequest, ActionResult, ActorControl, ActorControlCategory, + ActorControlSelf, ActorControlTarget, ClientZoneIpcSegment, CommonSpawn, ContainerInfo, + DisplayFlag, EffectKind, Equip, GameMasterRank, InitZone, ItemInfo, Move, NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, Warp, WeatherChange, }, @@ -28,11 +31,19 @@ use crate::{ }; use super::{ - Actor, CharacterData, Event, LuaPlayer, StatusEffects, ToServer, WorldDatabase, Zone, + Actor, CharacterData, EffectsBuilder, Event, LuaPlayer, StatusEffects, ToServer, WorldDatabase, + Zone, common::{ClientId, ServerHandle}, lua::Task, }; +#[derive(Default)] +pub struct ExtraLuaState { + pub action_scripts: HashMap, + pub event_scripts: HashMap, + pub command_scripts: HashMap, +} + #[derive(Debug, Default, Clone)] pub struct TeleportQuery { pub aetheryte_id: u16, @@ -834,4 +845,106 @@ impl ZoneConnection { }) .await; } + + pub async fn execute_action(&mut self, request: ActionRequest, lua_player: &mut LuaPlayer) { + let mut effects_builder = None; + + // run action script + { + let lua = self.lua.lock().unwrap(); + let state = lua.app_data_ref::().unwrap(); + + let key = request.action_key; + if let Some(action_script) = state.action_scripts.get(&key) { + lua.scope(|scope| { + let connection_data = scope.create_userdata_ref_mut(lua_player).unwrap(); + + let config = get_config(); + + let file_name = format!("{}/{}", &config.world.scripts_location, action_script); + lua.load( + std::fs::read(&file_name).expect("Failed to locate scripts directory!"), + ) + .set_name("@".to_string() + &file_name) + .exec() + .unwrap(); + + let func: Function = lua.globals().get("doAction").unwrap(); + + effects_builder = Some(func.call::(connection_data).unwrap()); + + Ok(()) + }) + .unwrap(); + } else { + tracing::warn!("Action {key} isn't scripted yet! Ignoring..."); + } + } + + // tell them the action results + if let Some(effects_builder) = effects_builder { + let mut effects = [ActionEffect::default(); 8]; + effects[..effects_builder.effects.len()].copy_from_slice(&effects_builder.effects); + + if let Some(actor) = self.get_actor_mut(request.target.object_id) { + for effect in &effects_builder.effects { + match effect.kind { + EffectKind::Damage { amount, .. } => { + actor.hp = actor.hp.saturating_sub(amount as u32); + } + _ => todo!(), + } + } + + let actor = *actor; + self.update_hp_mp(actor.id, actor.hp, 10000).await; + } + + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::ActionResult, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::ActionResult(ActionResult { + main_target: request.target, + target_id_again: request.target, + action_id: request.action_key, + animation_lock_time: 0.6, + rotation: self.player_data.rotation, + action_animation_id: request.action_key as u16, // assuming action id == animation id + flag: 1, + effect_count: effects_builder.effects.len() as u8, + effects, + unk1: 2662353, + unk2: 3758096384, + hidden_animation: 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: SegmentData::Ipc { data: ipc }, + }) + .await; + + if let Some(actor) = self.get_actor(request.target.object_id) { + if actor.hp == 0 { + tracing::info!("Despawning {} because they died!", actor.id.0); + // if the actor died, despawn them + /*connection.handle + * .send(ToServer::ActorDespawned(connection.id, actor.id.0)) + * .await;*/ + } + } + } + } + + pub async fn cancel_action(&mut self) { + self.actor_control_self(ActorControlSelf { + category: ActorControlCategory::CancelCast {}, + }) + .await; + } } diff --git a/src/world/mod.rs b/src/world/mod.rs index 07e70de..485121c 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -5,7 +5,7 @@ mod chat_handler; pub use chat_handler::ChatHandler; mod connection; -pub use connection::{PlayerData, ZoneConnection}; +pub use connection::{ExtraLuaState, PlayerData, ZoneConnection}; mod database; pub use database::{CharacterData, WorldDatabase}; diff --git a/src/world/server.rs b/src/world/server.rs index 827578e..d1d10d4 100644 --- a/src/world/server.rs +++ b/src/world/server.rs @@ -502,6 +502,21 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i spawn, ); } + ToServer::ActionRequest(from_id, _from_actor_id, request) => { + // immediately send back to the client, for now + for (id, (handle, _)) in &mut data.clients { + let id = *id; + + if id == from_id { + let msg = FromServer::ActionComplete(request); + + if handle.send(msg).is_err() { + data.to_remove.push(id); + } + break; + } + } + } ToServer::Disconnected(from_id) => { data.to_remove.push(from_id); } diff --git a/src/world/zone.rs b/src/world/zone.rs index 6d0d1fe..8516fc9 100644 --- a/src/world/zone.rs +++ b/src/world/zone.rs @@ -43,7 +43,9 @@ impl Zone { tracing::info!("Loading {path}"); let lgb = LayerGroup::from_existing(&lgb_file); if lgb.is_none() { - tracing::warn!("Failed to parse {path}, this is most likely a bug in Physis and should be reported somewhere!") + tracing::warn!( + "Failed to parse {path}, this is most likely a bug in Physis and should be reported somewhere!" + ) } lgb };