1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-06-21 07:27:45 +00:00

Make !spawnnpc debug command networked

Next step is allocating actor IDs properly, instead of using a fixed
value.
This commit is contained in:
Joshua Goins 2025-06-18 20:38:55 -04:00
parent 3757f4e0db
commit 97cdc66ec7
3 changed files with 124 additions and 101 deletions

View file

@ -1,10 +1,9 @@
use crate::{ use crate::{
common::{CustomizeData, ObjectId, ObjectTypeId, timestamp_secs}, common::{ObjectId, ObjectTypeId, timestamp_secs},
inventory::Storage, inventory::Storage,
ipc::zone::{ ipc::zone::{
ActorControl, ActorControlCategory, ActorControlSelf, BattleNpcSubKind, ChatMessage, ActorControl, ActorControlCategory, ActorControlSelf, ChatMessage, EventStart, NpcSpawn,
CommonSpawn, EventStart, NpcSpawn, ObjectKind, OnlineStatus, ServerZoneIpcData, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment,
ServerZoneIpcSegment,
}, },
opcodes::ServerZoneIpcType, opcodes::ServerZoneIpcType,
packet::{PacketSegment, SegmentData, SegmentType}, packet::{PacketSegment, SegmentData, SegmentType},
@ -13,35 +12,6 @@ use crate::{
use super::{LuaPlayer, ZoneConnection}; use super::{LuaPlayer, ZoneConnection};
pub const CUSTOMIZE_DATA: CustomizeData = CustomizeData {
race: 4,
gender: 1,
age: 1,
height: 50,
subrace: 7,
face: 3,
hair: 5,
enable_highlights: 0,
skin_tone: 10,
right_eye_color: 75,
hair_tone: 50,
highlights: 0,
facial_features: 1,
facial_feature_color: 19,
eyebrows: 1,
left_eye_color: 75,
eyes: 1,
nose: 0,
jaw: 1,
mouth: 1,
lips_tone_fur_pattern: 169,
race_feature_size: 100,
race_feature_type: 1,
bust: 100,
face_paint: 0,
face_paint_color: 167,
};
pub struct ChatHandler {} pub struct ChatHandler {}
impl ChatHandler { impl ChatHandler {
@ -55,58 +25,18 @@ impl ChatHandler {
let parts: Vec<&str> = chat_message.message.split(' ').collect(); let parts: Vec<&str> = chat_message.message.split(' ').collect();
match parts[0] { match parts[0] {
"!spawnnpc" => { "!spawnnpc" => {
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::NpcSpawn,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::NpcSpawn(NpcSpawn {
common: CommonSpawn {
hp_curr: 100,
hp_max: 100,
mp_curr: 100,
mp_max: 100,
look: CUSTOMIZE_DATA,
spawn_index: connection.get_free_spawn_index(),
bnpc_base: 13498,
bnpc_name: 10261,
object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy),
target_id: ObjectTypeId {
object_id: ObjectId(connection.player_data.actor_id),
object_type: 0,
}, // target the player
level: 1,
models: [
0, // head
89, // body
89, // hands
89, // legs
89, // feet
0, // ears
0, // neck
0, // wrists
0, // left finger
0, // right finger
],
pos: connection.player_data.position,
..Default::default()
},
..Default::default()
}),
..Default::default()
};
connection connection
.send_segment(PacketSegment { .handle
source_actor: 0x106ad804, .send(ToServer::DebugNewNpc(
target_actor: connection.player_data.actor_id, connection.id,
segment_type: SegmentType::Ipc, connection.player_data.actor_id,
data: SegmentData::Ipc { data: ipc }, ))
})
.await; .await;
} }
"!spawnmonster" => { "!spawnmonster" => {
connection connection
.handle .handle
.send(ToServer::DebugNewNpc( .send(ToServer::DebugNewEnemy(
connection.id, connection.id,
connection.player_data.actor_id, connection.player_data.actor_id,
)) ))

View file

@ -88,7 +88,10 @@ pub enum ToServer {
Disconnected(ClientId), Disconnected(ClientId),
/// A fatal error occured. /// A fatal error occured.
FatalError(std::io::Error), FatalError(std::io::Error),
/// Spawn a friendly debug NPC.
DebugNewNpc(ClientId, u32), DebugNewNpc(ClientId, u32),
/// Spawn an enemy debug NPC.
DebugNewEnemy(ClientId, u32),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use tokio::sync::mpsc::Receiver; use tokio::sync::mpsc::Receiver;
use crate::{ use crate::{
common::ObjectId, common::{CustomizeData, ObjectId, ObjectTypeId},
ipc::zone::{ ipc::zone::{
ActorControl, ActorControlCategory, ActorControlSelf, ActorControlTarget, BattleNpcSubKind, ActorControl, ActorControlCategory, ActorControlSelf, ActorControlTarget, BattleNpcSubKind,
ClientTriggerCommand, CommonSpawn, NpcSpawn, ObjectKind, ClientTriggerCommand, CommonSpawn, NpcSpawn, ObjectKind,
@ -11,6 +11,36 @@ use crate::{
use super::{Actor, ClientHandle, ClientId, FromServer, ToServer}; use super::{Actor, ClientHandle, ClientId, FromServer, ToServer};
/// Used for the debug NPC.
pub const CUSTOMIZE_DATA: CustomizeData = CustomizeData {
race: 4,
gender: 1,
age: 1,
height: 50,
subrace: 7,
face: 3,
hair: 5,
enable_highlights: 0,
skin_tone: 10,
right_eye_color: 75,
hair_tone: 50,
highlights: 0,
facial_features: 1,
facial_feature_color: 19,
eyebrows: 1,
left_eye_color: 75,
eyes: 1,
nose: 0,
jaw: 1,
mouth: 1,
lips_tone_fur_pattern: 169,
race_feature_size: 100,
race_feature_type: 1,
bust: 100,
face_paint: 0,
face_paint_color: 167,
};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum NetworkedActor { enum NetworkedActor {
Player(NpcSpawn), Player(NpcSpawn),
@ -40,6 +70,7 @@ struct ClientState {
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct WorldServer { struct WorldServer {
to_remove: Vec<ClientId>,
clients: HashMap<ClientId, (ClientHandle, ClientState)>, clients: HashMap<ClientId, (ClientHandle, ClientState)>,
/// Indexed by zone id /// Indexed by zone id
instances: HashMap<u16, Instance>, instances: HashMap<u16, Instance>,
@ -70,11 +101,24 @@ impl WorldServer {
} }
None None
} }
/// Tell all the clients that a new NPC spawned.
fn send_npc(&mut self, spawn: NpcSpawn) {
// TODO: only send in the relevant instance
for (id, (handle, _)) in &mut self.clients {
let id = *id;
let msg = FromServer::SpawnNPC(spawn.clone());
if handle.send(msg).is_err() {
self.to_remove.push(id);
}
}
}
} }
pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::io::Error> { pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::io::Error> {
let mut data = WorldServer::default(); let mut data = WorldServer::default();
let mut to_remove = Vec::new();
while let Some(msg) = recv.recv().await { while let Some(msg) = recv.recv().await {
match msg { match msg {
@ -162,7 +206,7 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
); );
if handle.send(msg).is_err() { if handle.send(msg).is_err() {
to_remove.push(id); data.to_remove.push(id);
} }
} }
} }
@ -188,7 +232,7 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
let msg = FromServer::ActorDespawn(actor_id); let msg = FromServer::ActorDespawn(actor_id);
if handle.send(msg).is_err() { if handle.send(msg).is_err() {
to_remove.push(id); data.to_remove.push(id);
} }
} }
} }
@ -203,7 +247,7 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
let msg = FromServer::Message(msg.clone()); let msg = FromServer::Message(msg.clone());
if handle.send(msg).is_err() { if handle.send(msg).is_err() {
to_remove.push(id); data.to_remove.push(id);
} }
} }
} }
@ -232,7 +276,7 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
let msg = FromServer::ActorMove(actor_id, position, rotation); let msg = FromServer::ActorMove(actor_id, position, rotation);
if handle.send(msg).is_err() { if handle.send(msg).is_err() {
to_remove.push(id); data.to_remove.push(id);
} }
} }
} }
@ -255,7 +299,7 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
}); });
if handle.send(msg).is_err() { if handle.send(msg).is_err() {
to_remove.push(id); data.to_remove.push(id);
} }
} }
_ => {} _ => {}
@ -275,7 +319,7 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
); );
if handle.send(msg).is_err() { if handle.send(msg).is_err() {
to_remove.push(id); data.to_remove.push(id);
} }
} }
ClientTriggerCommand::ChangePose { unk1, pose } => { ClientTriggerCommand::ChangePose { unk1, pose } => {
@ -290,7 +334,7 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
); );
if handle.send(msg).is_err() { if handle.send(msg).is_err() {
to_remove.push(id); data.to_remove.push(id);
} }
} }
ClientTriggerCommand::ReapplyPose { unk1, pose } => { ClientTriggerCommand::ReapplyPose { unk1, pose } => {
@ -305,7 +349,7 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
); );
if handle.send(msg).is_err() { if handle.send(msg).is_err() {
to_remove.push(id); data.to_remove.push(id);
} }
} }
_ => tracing::warn!("Server doesn't know what to do with {:#?}", trigger), _ => tracing::warn!("Server doesn't know what to do with {:#?}", trigger),
@ -313,6 +357,60 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
} }
} }
ToServer::DebugNewNpc(_from_id, from_actor_id) => { ToServer::DebugNewNpc(_from_id, from_actor_id) => {
let spawn;
{
let Some(instance) = data.find_actor_instance_mut(from_actor_id) else {
break;
};
let Some(actor) = instance.find_actor(ObjectId(from_actor_id)) else {
break;
};
let NetworkedActor::Player(player) = actor else {
break;
};
spawn = NpcSpawn {
aggression_mode: 1,
common: CommonSpawn {
hp_curr: 100,
hp_max: 100,
mp_curr: 100,
mp_max: 100,
look: CUSTOMIZE_DATA,
bnpc_base: 13498,
bnpc_name: 10261,
object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy),
target_id: ObjectTypeId {
object_id: ObjectId(from_actor_id),
object_type: 0,
}, // target the player
level: 1,
models: [
0, // head
89, // body
89, // hands
89, // legs
89, // feet
0, // ears
0, // neck
0, // wrists
0, // left finger
0, // right finger
],
pos: player.common.pos,
..Default::default()
},
..Default::default()
};
instance.insert_npc(ObjectId(1), spawn.clone());
}
data.send_npc(spawn);
}
ToServer::DebugNewEnemy(_from_id, from_actor_id) => {
let spawn; let spawn;
{ {
let Some(instance) = data.find_actor_instance_mut(from_actor_id) else { let Some(instance) = data.find_actor_instance_mut(from_actor_id) else {
@ -350,28 +448,20 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
instance.insert_npc(ObjectId(1), spawn.clone()); instance.insert_npc(ObjectId(1), spawn.clone());
} }
for (id, (handle, _)) in &mut data.clients { data.send_npc(spawn);
let id = *id;
let msg = FromServer::SpawnNPC(spawn.clone());
if handle.send(msg).is_err() {
to_remove.push(id);
}
}
} }
ToServer::Disconnected(from_id) => { ToServer::Disconnected(from_id) => {
to_remove.push(from_id); data.to_remove.push(from_id);
} }
ToServer::FatalError(err) => return Err(err), ToServer::FatalError(err) => return Err(err),
} }
// Remove any clients that errored out // Remove any clients that errored out
for remove_id in &to_remove { for remove_id in data.to_remove.clone() {
// remove any actors they had // remove any actors they had
let mut actor_id = None; let mut actor_id = None;
for (id, (handle, _)) in &mut data.clients { for (id, (handle, _)) in &mut data.clients {
if *id == *remove_id { if *id == remove_id {
actor_id = Some(handle.actor_id); actor_id = Some(handle.actor_id);
} }
} }