mirror of
https://github.com/redstrate/Kawari.git
synced 2025-05-12 22:57:45 +00:00
Start adding support for propagating actor control state
This begins figuring out how we are going to be propagating actor control state: e.g. targets, poses, and other misc effects. I ended up sending client triggers to the global server state, who then creates the needed actor control packet for the other players. Now players can see what other players are targeting!
This commit is contained in:
parent
f338530e6d
commit
fd1fbe7188
7 changed files with 156 additions and 33 deletions
|
@ -164,6 +164,11 @@
|
||||||
"name": "Unk18",
|
"name": "Unk18",
|
||||||
"opcode": 116,
|
"opcode": 116,
|
||||||
"size": 16
|
"size": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ActorControlTarget",
|
||||||
|
"opcode": 277,
|
||||||
|
"size": 28
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ClientZoneIpcType": [
|
"ClientZoneIpcType": [
|
||||||
|
@ -178,7 +183,7 @@
|
||||||
"size": 72
|
"size": 72
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Unk1",
|
"name": "ClientTrigger",
|
||||||
"opcode": 428,
|
"opcode": 428,
|
||||||
"size": 32
|
"size": 32
|
||||||
},
|
},
|
||||||
|
|
|
@ -374,18 +374,9 @@ async fn client_loop(
|
||||||
// tell the other players we're here
|
// tell the other players we're here
|
||||||
connection.handle.send(ToServer::ActorSpawned(connection.id, Actor { id: ObjectId(connection.player_data.actor_id), hp: 100, spawn_index: 0 }, common)).await;
|
connection.handle.send(ToServer::ActorSpawned(connection.id, Actor { id: ObjectId(connection.player_data.actor_id), hp: 100, spawn_index: 0 }, common)).await;
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::Unk1 {
|
ClientZoneIpcData::ClientTrigger(trigger) => {
|
||||||
category, ..
|
// inform the server of our trigger, it will handle sending it to other clients
|
||||||
} => {
|
connection.handle.send(ToServer::ClientTrigger(connection.id, connection.player_data.actor_id, trigger.clone())).await;
|
||||||
tracing::info!("Recieved Unk1! {category:#?}");
|
|
||||||
|
|
||||||
/*match category {
|
|
||||||
3 => {
|
|
||||||
// set target
|
|
||||||
tracing::info!("Targeting actor {param1}");
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::Unk2 { .. } => {
|
ClientZoneIpcData::Unk2 { .. } => {
|
||||||
tracing::info!("Recieved Unk2!");
|
tracing::info!("Recieved Unk2!");
|
||||||
|
@ -853,7 +844,9 @@ async fn client_loop(
|
||||||
FromServer::Message(msg)=> connection.send_message(&msg).await,
|
FromServer::Message(msg)=> connection.send_message(&msg).await,
|
||||||
FromServer::ActorSpawn(actor, common) => connection.spawn_actor(actor, common).await,
|
FromServer::ActorSpawn(actor, common) => connection.spawn_actor(actor, common).await,
|
||||||
FromServer::ActorMove(actor_id, position, rotation) => connection.set_actor_position(actor_id, position, rotation).await,
|
FromServer::ActorMove(actor_id, position, rotation) => connection.set_actor_position(actor_id, position, rotation).await,
|
||||||
FromServer::ActorDespawn(actor_id) => connection.remove_actor(actor_id).await
|
FromServer::ActorDespawn(actor_id) => connection.remove_actor(actor_id).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,
|
||||||
},
|
},
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ use crate::common::{read_bool_from, write_bool_as};
|
||||||
|
|
||||||
use super::OnlineStatus;
|
use super::OnlineStatus;
|
||||||
|
|
||||||
|
// TODO: these are all somewhat related, but maybe should be separated?
|
||||||
|
|
||||||
// See https://github.com/awgil/ffxiv_reverse/blob/f35b6226c1478234ca2b7149f82d251cffca2f56/vnetlog/vnetlog/ServerIPC.cs#L266 for a REALLY useful list of known values
|
// See https://github.com/awgil/ffxiv_reverse/blob/f35b6226c1478234ca2b7149f82d251cffca2f56/vnetlog/vnetlog/ServerIPC.cs#L266 for a REALLY useful list of known values
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
@ -34,6 +36,11 @@ pub enum ActorControlCategory {
|
||||||
},
|
},
|
||||||
#[brw(magic = 0x261u16)]
|
#[brw(magic = 0x261u16)]
|
||||||
ToggleWireframeRendering(),
|
ToggleWireframeRendering(),
|
||||||
|
#[brw(magic = 0x32u16)]
|
||||||
|
SetTarget {
|
||||||
|
#[brw(pad_before = 22)] // actually full of info, and 2 bytes of padding at the beginning
|
||||||
|
actor_id: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -68,3 +75,19 @@ impl Default for ActorControlSelf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Has more padding than ActorControl?
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ActorControlTarget {
|
||||||
|
#[brw(pad_size_to = 28)] // take into account categories without params
|
||||||
|
pub category: ActorControlCategory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ActorControlTarget {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
category: ActorControlCategory::ToggleInvisibility { invisible: false },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
30
src/ipc/zone/client_trigger.rs
Normal file
30
src/ipc/zone/client_trigger.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum ClientTriggerCommand {
|
||||||
|
#[brw(magic = 0x3u16)]
|
||||||
|
SetTarget {
|
||||||
|
#[brw(pad_before = 2)]
|
||||||
|
actor_id: u32,
|
||||||
|
},
|
||||||
|
#[brw(magic = 0xC81u16)]
|
||||||
|
Unk1 {},
|
||||||
|
#[brw(magic = 0xC9u16)]
|
||||||
|
Unk2 {},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ClientTrigger {
|
||||||
|
#[brw(pad_size_to = 32)] // take into account categories without params
|
||||||
|
pub trigger: ClientTriggerCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClientTrigger {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
trigger: ClientTriggerCommand::SetTarget { actor_id: 0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ mod player_stats;
|
||||||
pub use player_stats::PlayerStats;
|
pub use player_stats::PlayerStats;
|
||||||
|
|
||||||
mod actor_control;
|
mod actor_control;
|
||||||
pub use actor_control::{ActorControl, ActorControlCategory, ActorControlSelf};
|
pub use actor_control::{ActorControl, ActorControlCategory, ActorControlSelf, ActorControlTarget};
|
||||||
|
|
||||||
mod init_zone;
|
mod init_zone;
|
||||||
pub use init_zone::InitZone;
|
pub use init_zone::InitZone;
|
||||||
|
@ -76,6 +76,9 @@ pub use item_operation::ItemOperation;
|
||||||
mod equip;
|
mod equip;
|
||||||
pub use equip::Equip;
|
pub use equip::Equip;
|
||||||
|
|
||||||
|
mod client_trigger;
|
||||||
|
pub use client_trigger::{ClientTrigger, ClientTriggerCommand};
|
||||||
|
|
||||||
use crate::common::ObjectTypeId;
|
use crate::common::ObjectTypeId;
|
||||||
use crate::common::Position;
|
use crate::common::Position;
|
||||||
use crate::common::read_string;
|
use crate::common::read_string;
|
||||||
|
@ -234,6 +237,8 @@ pub enum ServerZoneIpcData {
|
||||||
Unk18 {
|
Unk18 {
|
||||||
unk: [u8; 16], // all zero...
|
unk: [u8; 16], // all zero...
|
||||||
},
|
},
|
||||||
|
/// Used to control target information
|
||||||
|
ActorControlTarget(ActorControlTarget),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -252,19 +257,8 @@ pub enum ClientZoneIpcData {
|
||||||
// TODO: full of possibly interesting information
|
// TODO: full of possibly interesting information
|
||||||
unk: [u8; 72],
|
unk: [u8; 72],
|
||||||
},
|
},
|
||||||
/// FIXME: 32 bytes of something from the client, not sure what yet
|
#[br(pre_assert(*magic == ClientZoneIpcType::ClientTrigger))]
|
||||||
#[br(pre_assert(*magic == ClientZoneIpcType::Unk1))]
|
ClientTrigger(ClientTrigger),
|
||||||
Unk1 {
|
|
||||||
// 3 = target
|
|
||||||
category: u32,
|
|
||||||
param1: u32,
|
|
||||||
param2: u32,
|
|
||||||
param3: u32,
|
|
||||||
param4: u32,
|
|
||||||
param5: u32,
|
|
||||||
param6: u32,
|
|
||||||
param7: u32,
|
|
||||||
},
|
|
||||||
/// FIXME: 16 bytes of something from the client, not sure what yet
|
/// FIXME: 16 bytes of something from the client, not sure what yet
|
||||||
#[br(pre_assert(*magic == ClientZoneIpcType::Unk2))]
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk2))]
|
||||||
Unk2 {
|
Unk2 {
|
||||||
|
|
|
@ -16,10 +16,11 @@ use crate::{
|
||||||
ipc::{
|
ipc::{
|
||||||
chat::ServerChatIpcSegment,
|
chat::ServerChatIpcSegment,
|
||||||
zone::{
|
zone::{
|
||||||
ActorControlSelf, ClientZoneIpcSegment, CommonSpawn, ContainerInfo, DisplayFlag, Equip,
|
ActorControl, ActorControlSelf, ActorControlTarget, ClientTrigger,
|
||||||
InitZone, ItemInfo, Move, NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind,
|
ClientZoneIpcSegment, CommonSpawn, ContainerInfo, DisplayFlag, Equip, InitZone,
|
||||||
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList,
|
ItemInfo, Move, NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind, ServerZoneIpcData,
|
||||||
UpdateClassInfo, Warp, WeatherChange,
|
ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, Warp,
|
||||||
|
WeatherChange,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
opcodes::ServerZoneIpcType,
|
opcodes::ServerZoneIpcType,
|
||||||
|
@ -67,6 +68,10 @@ pub enum FromServer {
|
||||||
ActorMove(u32, Position, f32),
|
ActorMove(u32, Position, f32),
|
||||||
// An actor has despawned.
|
// An actor has despawned.
|
||||||
ActorDespawn(u32),
|
ActorDespawn(u32),
|
||||||
|
/// We need to update an actor
|
||||||
|
ActorControl(u32, ActorControl),
|
||||||
|
/// We need to update an actor's target'
|
||||||
|
ActorControlTarget(u32, ActorControlTarget),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -106,6 +111,7 @@ pub enum ToServer {
|
||||||
ActorSpawned(ClientId, Actor, CommonSpawn),
|
ActorSpawned(ClientId, Actor, CommonSpawn),
|
||||||
ActorMoved(ClientId, u32, Position, f32),
|
ActorMoved(ClientId, u32, Position, f32),
|
||||||
ActorDespawned(ClientId, u32),
|
ActorDespawned(ClientId, u32),
|
||||||
|
ClientTrigger(ClientId, u32, ClientTrigger),
|
||||||
ZoneLoaded(ClientId),
|
ZoneLoaded(ClientId),
|
||||||
Disconnected(ClientId),
|
Disconnected(ClientId),
|
||||||
FatalError(std::io::Error),
|
FatalError(std::io::Error),
|
||||||
|
@ -752,6 +758,45 @@ impl ZoneConnection {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn actor_control(&mut self, actor_id: u32, actor_control: ActorControl) {
|
||||||
|
let ipc = ServerZoneIpcSegment {
|
||||||
|
op_code: ServerZoneIpcType::ActorControl,
|
||||||
|
timestamp: timestamp_secs(),
|
||||||
|
data: ServerZoneIpcData::ActorControl(actor_control),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_segment(PacketSegment {
|
||||||
|
source_actor: actor_id,
|
||||||
|
target_actor: self.player_data.actor_id,
|
||||||
|
segment_type: SegmentType::Ipc,
|
||||||
|
data: SegmentData::Ipc { data: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn actor_control_target(&mut self, actor_id: u32, actor_control: ActorControlTarget) {
|
||||||
|
tracing::info!(
|
||||||
|
"we are sending actor control target to {actor_id}: {actor_control:#?} and WE ARE {:#?}",
|
||||||
|
self.player_data.actor_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let ipc = ServerZoneIpcSegment {
|
||||||
|
op_code: ServerZoneIpcType::ActorControlTarget,
|
||||||
|
timestamp: timestamp_secs(),
|
||||||
|
data: ServerZoneIpcData::ActorControlTarget(actor_control),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_segment(PacketSegment {
|
||||||
|
source_actor: actor_id,
|
||||||
|
target_actor: self.player_data.actor_id,
|
||||||
|
segment_type: SegmentType::Ipc,
|
||||||
|
data: SegmentData::Ipc { data: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_player_common_spawn(
|
pub fn get_player_common_spawn(
|
||||||
&self,
|
&self,
|
||||||
exit_position: Option<Position>,
|
exit_position: Option<Position>,
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tokio::sync::mpsc::Receiver;
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
|
||||||
use crate::{common::ObjectId, ipc::zone::CommonSpawn};
|
use crate::{
|
||||||
|
common::ObjectId,
|
||||||
|
ipc::zone::{ActorControlCategory, ActorControlTarget, ClientTriggerCommand, CommonSpawn},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{Actor, ClientHandle, ClientId, FromServer, ToServer};
|
use super::{Actor, ClientHandle, ClientId, FromServer, ToServer};
|
||||||
|
|
||||||
|
@ -113,6 +116,36 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ToServer::ClientTrigger(from_id, from_actor_id, trigger) => {
|
||||||
|
for (id, handle) in &mut data.clients {
|
||||||
|
let id = *id;
|
||||||
|
|
||||||
|
// there's no reason to tell the actor what it just did
|
||||||
|
if id == from_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("{:#?}", trigger);
|
||||||
|
|
||||||
|
match &trigger.trigger {
|
||||||
|
ClientTriggerCommand::SetTarget { actor_id } => {
|
||||||
|
let msg = FromServer::ActorControlTarget(
|
||||||
|
from_actor_id,
|
||||||
|
ActorControlTarget {
|
||||||
|
category: ActorControlCategory::SetTarget {
|
||||||
|
actor_id: *actor_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if handle.send(msg).is_err() {
|
||||||
|
to_remove.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => tracing::warn!("Server doesn't know what to do with {:#?}", trigger),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ToServer::Disconnected(from_id) => {
|
ToServer::Disconnected(from_id) => {
|
||||||
to_remove.push(from_id);
|
to_remove.push(from_id);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue