mirror of
https://github.com/redstrate/Kawari.git
synced 2025-06-30 11:47:45 +00:00
Send action requests to global server state
This is to lay the groundwork for keeping track of cast spell timings, and eventually networking actions to other players. See #36
This commit is contained in:
parent
01508fd506
commit
39beefbef3
7 changed files with 159 additions and 129 deletions
|
@ -1,4 +1,3 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
@ -10,19 +9,18 @@ use kawari::config::get_config;
|
||||||
use kawari::inventory::Item;
|
use kawari::inventory::Item;
|
||||||
use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment};
|
use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment};
|
||||||
use kawari::ipc::zone::{
|
use kawari::ipc::zone::{
|
||||||
ActionEffect, ActionResult, ClientTriggerCommand, ClientZoneIpcData, CommonSpawn, EffectKind,
|
ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList,
|
||||||
EventStart, GameMasterCommandType, GameMasterRank, OnlineStatus, ServerZoneIpcData,
|
|
||||||
ServerZoneIpcSegment, SocialListRequestType,
|
|
||||||
};
|
};
|
||||||
use kawari::ipc::zone::{
|
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::opcodes::{ServerChatIpcType, ServerZoneIpcType};
|
||||||
use kawari::packet::oodle::OodleNetwork;
|
use kawari::packet::oodle::OodleNetwork;
|
||||||
use kawari::packet::{
|
use kawari::packet::{
|
||||||
ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, send_keep_alive,
|
ConnectionType, PacketSegment, PacketState, SegmentData, SegmentType, send_keep_alive,
|
||||||
};
|
};
|
||||||
use kawari::world::{ChatHandler, Zone, ZoneConnection};
|
use kawari::world::{ChatHandler, ExtraLuaState, Zone, ZoneConnection};
|
||||||
use kawari::world::{
|
use kawari::world::{
|
||||||
ClientHandle, EffectsBuilder, Event, FromServer, LuaPlayer, PlayerData, ServerHandle,
|
ClientHandle, EffectsBuilder, Event, FromServer, LuaPlayer, PlayerData, ServerHandle,
|
||||||
StatusEffects, ToServer, WorldDatabase, handle_custom_ipc, server_main_loop,
|
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::sync::oneshot;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ExtraLuaState {
|
|
||||||
action_scripts: HashMap<u32, String>,
|
|
||||||
event_scripts: HashMap<u32, String>,
|
|
||||||
command_scripts: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_main_loop() -> (ServerHandle, JoinHandle<()>) {
|
fn spawn_main_loop() -> (ServerHandle, JoinHandle<()>) {
|
||||||
let (send, recv) = channel(64);
|
let (send, recv) = channel(64);
|
||||||
|
|
||||||
|
@ -683,116 +674,14 @@ async fn client_loop(
|
||||||
connection.change_zone(new_territory).await;
|
connection.change_zone(new_territory).await;
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::ActionRequest(request) => {
|
ClientZoneIpcData::ActionRequest(request) => {
|
||||||
let mut effects_builder = None;
|
connection
|
||||||
|
.handle
|
||||||
// run action script
|
.send(ToServer::ActionRequest(
|
||||||
{
|
connection.id,
|
||||||
let lua = lua.lock().unwrap();
|
connection.player_data.actor_id,
|
||||||
let state = lua.app_data_ref::<ExtraLuaState>().unwrap();
|
request.clone(),
|
||||||
|
))
|
||||||
let key = request.action_key;
|
.await;
|
||||||
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::<EffectsBuilder>(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;*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::Unk16 { .. } => {
|
ClientZoneIpcData::Unk16 { .. } => {
|
||||||
// no-op
|
// 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::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::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::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,
|
None => break,
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,8 @@ pub enum ActorControlCategory {
|
||||||
festival3: u32,
|
festival3: u32,
|
||||||
festival4: u32,
|
festival4: u32,
|
||||||
},
|
},
|
||||||
|
#[brw(magic = 0xFu16)]
|
||||||
|
CancelCast {},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
|
|
@ -11,7 +11,8 @@ use tokio::sync::mpsc::Sender;
|
||||||
use crate::{
|
use crate::{
|
||||||
common::Position,
|
common::Position,
|
||||||
ipc::zone::{
|
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),
|
ActorControlTarget(u32, ActorControlTarget),
|
||||||
/// We need to update the player actor
|
/// We need to update the player actor
|
||||||
ActorControlSelf(ActorControlSelf),
|
ActorControlSelf(ActorControlSelf),
|
||||||
|
/// Action has completed and needs to be executed
|
||||||
|
ActionComplete(ActionRequest),
|
||||||
|
/// Action has been cancelled
|
||||||
|
ActionCancelled(),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -92,6 +97,8 @@ pub enum ToServer {
|
||||||
DebugNewEnemy(ClientId, u32),
|
DebugNewEnemy(ClientId, u32),
|
||||||
/// Spawn a debug clone.
|
/// Spawn a debug clone.
|
||||||
DebugSpawnClone(ClientId, u32),
|
DebugSpawnClone(ClientId, u32),
|
||||||
|
/// Request to perform an action
|
||||||
|
ActionRequest(ClientId, u32, ActionRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use mlua::Function;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -14,8 +16,9 @@ use crate::{
|
||||||
ipc::{
|
ipc::{
|
||||||
chat::ServerChatIpcSegment,
|
chat::ServerChatIpcSegment,
|
||||||
zone::{
|
zone::{
|
||||||
ActorControl, ActorControlSelf, ActorControlTarget, ClientZoneIpcSegment, CommonSpawn,
|
ActionEffect, ActionRequest, ActionResult, ActorControl, ActorControlCategory,
|
||||||
ContainerInfo, DisplayFlag, Equip, GameMasterRank, InitZone, ItemInfo, Move, NpcSpawn,
|
ActorControlSelf, ActorControlTarget, ClientZoneIpcSegment, CommonSpawn, ContainerInfo,
|
||||||
|
DisplayFlag, EffectKind, Equip, GameMasterRank, InitZone, ItemInfo, Move, NpcSpawn,
|
||||||
ObjectKind, PlayerStats, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment,
|
ObjectKind, PlayerStats, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment,
|
||||||
StatusEffect, StatusEffectList, UpdateClassInfo, Warp, WeatherChange,
|
StatusEffect, StatusEffectList, UpdateClassInfo, Warp, WeatherChange,
|
||||||
},
|
},
|
||||||
|
@ -28,11 +31,19 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Actor, CharacterData, Event, LuaPlayer, StatusEffects, ToServer, WorldDatabase, Zone,
|
Actor, CharacterData, EffectsBuilder, Event, LuaPlayer, StatusEffects, ToServer, WorldDatabase,
|
||||||
|
Zone,
|
||||||
common::{ClientId, ServerHandle},
|
common::{ClientId, ServerHandle},
|
||||||
lua::Task,
|
lua::Task,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ExtraLuaState {
|
||||||
|
pub action_scripts: HashMap<u32, String>,
|
||||||
|
pub event_scripts: HashMap<u32, String>,
|
||||||
|
pub command_scripts: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct TeleportQuery {
|
pub struct TeleportQuery {
|
||||||
pub aetheryte_id: u16,
|
pub aetheryte_id: u16,
|
||||||
|
@ -834,4 +845,106 @@ impl ZoneConnection {
|
||||||
})
|
})
|
||||||
.await;
|
.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::<ExtraLuaState>().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::<EffectsBuilder>(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ mod chat_handler;
|
||||||
pub use chat_handler::ChatHandler;
|
pub use chat_handler::ChatHandler;
|
||||||
|
|
||||||
mod connection;
|
mod connection;
|
||||||
pub use connection::{PlayerData, ZoneConnection};
|
pub use connection::{ExtraLuaState, PlayerData, ZoneConnection};
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
pub use database::{CharacterData, WorldDatabase};
|
pub use database::{CharacterData, WorldDatabase};
|
||||||
|
|
|
@ -502,6 +502,21 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
|
||||||
spawn,
|
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) => {
|
ToServer::Disconnected(from_id) => {
|
||||||
data.to_remove.push(from_id);
|
data.to_remove.push(from_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,9 @@ impl Zone {
|
||||||
tracing::info!("Loading {path}");
|
tracing::info!("Loading {path}");
|
||||||
let lgb = LayerGroup::from_existing(&lgb_file);
|
let lgb = LayerGroup::from_existing(&lgb_file);
|
||||||
if lgb.is_none() {
|
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
|
lgb
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue