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::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<u32, String>,
|
||||
event_scripts: HashMap<u32, String>,
|
||||
command_scripts: HashMap<String, String>,
|
||||
}
|
||||
|
||||
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::<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(&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;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
|
|
@ -82,6 +82,8 @@ pub enum ActorControlCategory {
|
|||
festival3: u32,
|
||||
festival4: u32,
|
||||
},
|
||||
#[brw(magic = 0xFu16)]
|
||||
CancelCast {},
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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<u32, String>,
|
||||
pub event_scripts: HashMap<u32, String>,
|
||||
pub command_scripts: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[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::<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;
|
||||
|
||||
mod connection;
|
||||
pub use connection::{PlayerData, ZoneConnection};
|
||||
pub use connection::{ExtraLuaState, PlayerData, ZoneConnection};
|
||||
|
||||
mod database;
|
||||
pub use database::{CharacterData, WorldDatabase};
|
||||
|
|
|
@ -502,6 +502,21 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> 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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue