1
Fork 0
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:
Joshua Goins 2025-05-08 22:53:36 -04:00
parent f338530e6d
commit fd1fbe7188
7 changed files with 156 additions and 33 deletions

View file

@ -164,6 +164,11 @@
"name": "Unk18",
"opcode": 116,
"size": 16
},
{
"name": "ActorControlTarget",
"opcode": 277,
"size": 28
}
],
"ClientZoneIpcType": [
@ -178,7 +183,7 @@
"size": 72
},
{
"name": "Unk1",
"name": "ClientTrigger",
"opcode": 428,
"size": 32
},

View file

@ -374,18 +374,9 @@ async fn client_loop(
// 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;
}
ClientZoneIpcData::Unk1 {
category, ..
} => {
tracing::info!("Recieved Unk1! {category:#?}");
/*match category {
3 => {
// set target
tracing::info!("Targeting actor {param1}");
}
_ => {}
}*/
ClientZoneIpcData::ClientTrigger(trigger) => {
// 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;
}
ClientZoneIpcData::Unk2 { .. } => {
tracing::info!("Recieved Unk2!");
@ -853,7 +844,9 @@ async fn client_loop(
FromServer::Message(msg)=> connection.send_message(&msg).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::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,
}

View file

@ -4,6 +4,8 @@ use crate::common::{read_bool_from, write_bool_as};
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
#[binrw]
#[derive(Debug, Eq, PartialEq, Clone)]
@ -34,6 +36,11 @@ pub enum ActorControlCategory {
},
#[brw(magic = 0x261u16)]
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]
@ -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 },
}
}
}

View 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 },
}
}
}

View file

@ -24,7 +24,7 @@ mod player_stats;
pub use player_stats::PlayerStats;
mod actor_control;
pub use actor_control::{ActorControl, ActorControlCategory, ActorControlSelf};
pub use actor_control::{ActorControl, ActorControlCategory, ActorControlSelf, ActorControlTarget};
mod init_zone;
pub use init_zone::InitZone;
@ -76,6 +76,9 @@ pub use item_operation::ItemOperation;
mod equip;
pub use equip::Equip;
mod client_trigger;
pub use client_trigger::{ClientTrigger, ClientTriggerCommand};
use crate::common::ObjectTypeId;
use crate::common::Position;
use crate::common::read_string;
@ -234,6 +237,8 @@ pub enum ServerZoneIpcData {
Unk18 {
unk: [u8; 16], // all zero...
},
/// Used to control target information
ActorControlTarget(ActorControlTarget),
}
#[binrw]
@ -252,19 +257,8 @@ pub enum ClientZoneIpcData {
// TODO: full of possibly interesting information
unk: [u8; 72],
},
/// FIXME: 32 bytes of something from the client, not sure what yet
#[br(pre_assert(*magic == ClientZoneIpcType::Unk1))]
Unk1 {
// 3 = target
category: u32,
param1: u32,
param2: u32,
param3: u32,
param4: u32,
param5: u32,
param6: u32,
param7: u32,
},
#[br(pre_assert(*magic == ClientZoneIpcType::ClientTrigger))]
ClientTrigger(ClientTrigger),
/// FIXME: 16 bytes of something from the client, not sure what yet
#[br(pre_assert(*magic == ClientZoneIpcType::Unk2))]
Unk2 {

View file

@ -16,10 +16,11 @@ use crate::{
ipc::{
chat::ServerChatIpcSegment,
zone::{
ActorControlSelf, ClientZoneIpcSegment, CommonSpawn, ContainerInfo, DisplayFlag, Equip,
InitZone, ItemInfo, Move, NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind,
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList,
UpdateClassInfo, Warp, WeatherChange,
ActorControl, ActorControlSelf, ActorControlTarget, ClientTrigger,
ClientZoneIpcSegment, CommonSpawn, ContainerInfo, DisplayFlag, Equip, InitZone,
ItemInfo, Move, NpcSpawn, ObjectKind, PlayerStats, PlayerSubKind, ServerZoneIpcData,
ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, Warp,
WeatherChange,
},
},
opcodes::ServerZoneIpcType,
@ -67,6 +68,10 @@ pub enum FromServer {
ActorMove(u32, Position, f32),
// An actor has despawned.
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)]
@ -106,6 +111,7 @@ pub enum ToServer {
ActorSpawned(ClientId, Actor, CommonSpawn),
ActorMoved(ClientId, u32, Position, f32),
ActorDespawned(ClientId, u32),
ClientTrigger(ClientId, u32, ClientTrigger),
ZoneLoaded(ClientId),
Disconnected(ClientId),
FatalError(std::io::Error),
@ -752,6 +758,45 @@ impl ZoneConnection {
.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(
&self,
exit_position: Option<Position>,

View file

@ -1,7 +1,10 @@
use std::collections::HashMap;
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};
@ -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) => {
to_remove.push(from_id);
}