2025-05-02 23:51:34 -04:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use tokio::sync::mpsc::Receiver;
|
|
|
|
|
2025-05-08 22:53:36 -04:00
|
|
|
use crate::{
|
2025-06-18 20:38:55 -04:00
|
|
|
common::{CustomizeData, ObjectId, ObjectTypeId},
|
2025-05-08 23:03:51 -04:00
|
|
|
ipc::zone::{
|
2025-05-11 10:12:02 -04:00
|
|
|
ActorControl, ActorControlCategory, ActorControlSelf, ActorControlTarget, BattleNpcSubKind,
|
2025-05-09 20:09:22 -04:00
|
|
|
ClientTriggerCommand, CommonSpawn, NpcSpawn, ObjectKind,
|
2025-05-08 23:03:51 -04:00
|
|
|
},
|
2025-05-08 22:53:36 -04:00
|
|
|
};
|
2025-05-02 23:51:34 -04:00
|
|
|
|
|
|
|
use super::{Actor, ClientHandle, ClientId, FromServer, ToServer};
|
|
|
|
|
2025-06-18 20:38:55 -04:00
|
|
|
/// 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,
|
|
|
|
};
|
|
|
|
|
2025-06-18 20:28:03 -04:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
enum NetworkedActor {
|
|
|
|
Player(NpcSpawn),
|
|
|
|
NPC(NpcSpawn),
|
|
|
|
}
|
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
#[derive(Default, Debug, Clone)]
|
|
|
|
struct Instance {
|
2025-05-02 23:51:34 -04:00
|
|
|
// structure temporary, of course
|
2025-06-18 20:28:03 -04:00
|
|
|
actors: HashMap<ObjectId, NetworkedActor>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Instance {
|
|
|
|
fn find_actor(&self, id: ObjectId) -> Option<&NetworkedActor> {
|
|
|
|
self.actors.get(&id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn insert_npc(&mut self, id: ObjectId, spawn: NpcSpawn) {
|
|
|
|
self.actors.insert(id, NetworkedActor::NPC(spawn));
|
|
|
|
}
|
2025-06-18 20:49:05 -04:00
|
|
|
|
|
|
|
fn generate_actor_id() -> u32 {
|
|
|
|
// TODO: ensure we don't collide with another actor
|
|
|
|
fastrand::u32(..)
|
|
|
|
}
|
2025-05-02 23:51:34 -04:00
|
|
|
}
|
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
#[derive(Default, Debug, Clone)]
|
|
|
|
struct ClientState {
|
|
|
|
zone_id: u16,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Debug)]
|
|
|
|
struct WorldServer {
|
2025-06-18 20:38:55 -04:00
|
|
|
to_remove: Vec<ClientId>,
|
2025-05-09 19:27:18 -04:00
|
|
|
clients: HashMap<ClientId, (ClientHandle, ClientState)>,
|
|
|
|
/// Indexed by zone id
|
|
|
|
instances: HashMap<u16, Instance>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WorldServer {
|
|
|
|
/// Finds the instance associated with a zone, or None if it doesn't exist yet.
|
|
|
|
fn find_instance(&self, zone_id: u16) -> Option<&Instance> {
|
|
|
|
self.instances.get(&zone_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finds the instance associated with a zone, or creates it if it doesn't exist yet
|
|
|
|
fn find_instance_mut(&mut self, zone_id: u16) -> &mut Instance {
|
|
|
|
if self.instances.contains_key(&zone_id) {
|
|
|
|
self.instances.get_mut(&zone_id).unwrap()
|
|
|
|
} else {
|
|
|
|
self.instances.insert(zone_id, Instance::default());
|
|
|
|
self.instances.get_mut(&zone_id).unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finds the instance associated with an actor, or returns None if they are not found.
|
|
|
|
fn find_actor_instance_mut(&mut self, actor_id: u32) -> Option<&mut Instance> {
|
|
|
|
for (_, instance) in &mut self.instances {
|
|
|
|
if instance.actors.contains_key(&ObjectId(actor_id)) {
|
|
|
|
return Some(instance);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
2025-06-18 20:38:55 -04:00
|
|
|
|
|
|
|
/// Tell all the clients that a new NPC spawned.
|
2025-06-18 20:49:05 -04:00
|
|
|
fn send_npc(&mut self, actor: Actor, spawn: NpcSpawn) {
|
2025-06-18 20:38:55 -04:00
|
|
|
// TODO: only send in the relevant instance
|
|
|
|
for (id, (handle, _)) in &mut self.clients {
|
|
|
|
let id = *id;
|
|
|
|
|
2025-06-18 20:49:05 -04:00
|
|
|
let msg = FromServer::ActorSpawn(actor, spawn.clone());
|
2025-06-18 20:38:55 -04:00
|
|
|
|
|
|
|
if handle.send(msg).is_err() {
|
|
|
|
self.to_remove.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-05-09 19:27:18 -04:00
|
|
|
}
|
|
|
|
|
2025-05-02 23:51:34 -04:00
|
|
|
pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::io::Error> {
|
|
|
|
let mut data = WorldServer::default();
|
|
|
|
|
|
|
|
while let Some(msg) = recv.recv().await {
|
|
|
|
match msg {
|
|
|
|
ToServer::NewClient(handle) => {
|
2025-05-09 19:27:18 -04:00
|
|
|
data.clients
|
|
|
|
.insert(handle.id, (handle, ClientState::default()));
|
2025-05-02 23:51:34 -04:00
|
|
|
}
|
2025-05-09 19:27:18 -04:00
|
|
|
ToServer::ZoneLoaded(from_id, zone_id) => {
|
|
|
|
// create a new instance if nessecary
|
|
|
|
if !data.instances.contains_key(&zone_id) {
|
|
|
|
data.instances.insert(zone_id, Instance::default());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send existing player data, if any
|
|
|
|
if let Some(instance) = data.find_instance(zone_id).cloned() {
|
|
|
|
for (id, (handle, state)) in &mut data.clients {
|
|
|
|
let id = *id;
|
|
|
|
|
|
|
|
if id == from_id {
|
|
|
|
state.zone_id = zone_id;
|
|
|
|
|
|
|
|
// send existing player data
|
2025-06-18 20:28:03 -04:00
|
|
|
for (id, spawn) in &instance.actors {
|
|
|
|
let npc_spawn = match spawn {
|
|
|
|
NetworkedActor::Player(npc_spawn) => npc_spawn,
|
|
|
|
NetworkedActor::NPC(npc_spawn) => npc_spawn,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Note that we currently only support spawning via the NPC packet, hence why we don't need to differentiate here
|
2025-05-09 19:27:18 -04:00
|
|
|
let msg = FromServer::ActorSpawn(
|
|
|
|
Actor {
|
|
|
|
id: *id,
|
|
|
|
hp: 100,
|
|
|
|
spawn_index: 0,
|
|
|
|
},
|
2025-06-18 20:28:03 -04:00
|
|
|
npc_spawn.clone(),
|
2025-05-09 19:27:18 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
handle.send(msg).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-09 19:42:23 -04:00
|
|
|
let (client, _) = data.clients.get(&from_id).unwrap().clone();
|
|
|
|
|
|
|
|
// add the connection's actor to the table
|
|
|
|
{
|
|
|
|
let instance = data.find_instance_mut(zone_id);
|
2025-06-18 20:28:03 -04:00
|
|
|
instance.actors.insert(
|
|
|
|
ObjectId(client.actor_id),
|
|
|
|
NetworkedActor::Player(NpcSpawn {
|
|
|
|
common: client.common.clone(),
|
|
|
|
..Default::default()
|
|
|
|
}),
|
|
|
|
);
|
2025-05-09 19:42:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Then tell any clients in the zone that we spawned
|
2025-05-09 19:27:18 -04:00
|
|
|
for (id, (handle, state)) in &mut data.clients {
|
2025-05-02 23:51:34 -04:00
|
|
|
let id = *id;
|
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
// don't bother telling the client who told us
|
2025-05-02 23:51:34 -04:00
|
|
|
if id == from_id {
|
2025-05-09 19:27:18 -04:00
|
|
|
continue;
|
|
|
|
}
|
2025-05-02 23:51:34 -04:00
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
// skip any clients not in our zone
|
|
|
|
if state.zone_id != zone_id {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2025-05-09 19:42:23 -04:00
|
|
|
let msg = FromServer::ActorSpawn(
|
|
|
|
Actor {
|
|
|
|
id: ObjectId(client.actor_id),
|
|
|
|
hp: 0,
|
|
|
|
spawn_index: 0,
|
|
|
|
},
|
2025-06-18 20:28:03 -04:00
|
|
|
NpcSpawn {
|
|
|
|
common: client.common.clone(),
|
|
|
|
..Default::default()
|
|
|
|
},
|
2025-05-09 19:42:23 -04:00
|
|
|
);
|
2025-05-02 23:51:34 -04:00
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
if handle.send(msg).is_err() {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(id);
|
2025-05-02 23:51:34 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-05-09 19:42:23 -04:00
|
|
|
ToServer::LeftZone(from_id, actor_id, zone_id) => {
|
|
|
|
// when the actor leaves the zone, remove them from the instance
|
|
|
|
let current_instance = data.find_actor_instance_mut(actor_id).unwrap();
|
|
|
|
current_instance.actors.remove(&ObjectId(actor_id));
|
|
|
|
|
|
|
|
// Then tell any clients in the zone that we left
|
|
|
|
for (id, (handle, state)) in &mut data.clients {
|
2025-05-02 23:51:34 -04:00
|
|
|
let id = *id;
|
|
|
|
|
2025-05-09 19:42:23 -04:00
|
|
|
// don't bother telling the client who told us
|
2025-05-02 23:51:34 -04:00
|
|
|
if id == from_id {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2025-05-09 19:42:23 -04:00
|
|
|
// skip any clients not in our zone
|
|
|
|
if state.zone_id != zone_id {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let msg = FromServer::ActorDespawn(actor_id);
|
2025-05-02 23:51:34 -04:00
|
|
|
|
|
|
|
if handle.send(msg).is_err() {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(id);
|
2025-05-02 23:51:34 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-05-09 19:42:23 -04:00
|
|
|
ToServer::Message(from_id, msg) => {
|
|
|
|
for (id, (handle, _)) in &mut data.clients {
|
2025-05-02 23:51:34 -04:00
|
|
|
let id = *id;
|
|
|
|
|
|
|
|
if id == from_id {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2025-05-09 19:42:23 -04:00
|
|
|
let msg = FromServer::Message(msg.clone());
|
2025-05-02 23:51:34 -04:00
|
|
|
|
|
|
|
if handle.send(msg).is_err() {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(id);
|
2025-05-02 23:51:34 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ToServer::ActorMoved(from_id, actor_id, position, rotation) => {
|
2025-05-09 19:27:18 -04:00
|
|
|
if let Some(instance) = data.find_actor_instance_mut(actor_id) {
|
2025-06-18 20:28:03 -04:00
|
|
|
if let Some((_, spawn)) = instance
|
2025-05-09 19:27:18 -04:00
|
|
|
.actors
|
|
|
|
.iter_mut()
|
|
|
|
.find(|actor| *actor.0 == ObjectId(actor_id))
|
|
|
|
{
|
2025-06-18 20:28:03 -04:00
|
|
|
let common = match spawn {
|
|
|
|
NetworkedActor::Player(npc_spawn) => &mut npc_spawn.common,
|
|
|
|
NetworkedActor::NPC(npc_spawn) => &mut npc_spawn.common,
|
|
|
|
};
|
2025-05-09 19:27:18 -04:00
|
|
|
common.pos = position;
|
|
|
|
common.rotation = rotation;
|
|
|
|
}
|
2025-05-02 23:51:34 -04:00
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
for (id, (handle, _)) in &mut data.clients {
|
|
|
|
let id = *id;
|
2025-05-02 23:51:34 -04:00
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
if id == from_id {
|
|
|
|
continue;
|
|
|
|
}
|
2025-05-02 23:51:34 -04:00
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
let msg = FromServer::ActorMove(actor_id, position, rotation);
|
2025-05-02 23:51:34 -04:00
|
|
|
|
2025-05-09 19:27:18 -04:00
|
|
|
if handle.send(msg).is_err() {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(id);
|
2025-05-09 19:27:18 -04:00
|
|
|
}
|
2025-05-02 23:51:34 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-05-08 22:53:36 -04:00
|
|
|
ToServer::ClientTrigger(from_id, from_actor_id, trigger) => {
|
2025-05-09 19:27:18 -04:00
|
|
|
for (id, (handle, _)) in &mut data.clients {
|
2025-05-08 22:53:36 -04:00
|
|
|
let id = *id;
|
|
|
|
|
2025-05-11 10:12:02 -04:00
|
|
|
tracing::info!("{:#?}", trigger);
|
|
|
|
|
|
|
|
// handle player-to-server actions
|
2025-05-08 22:53:36 -04:00
|
|
|
if id == from_id {
|
2025-05-11 10:12:02 -04:00
|
|
|
match &trigger.trigger {
|
|
|
|
ClientTriggerCommand::TeleportQuery { aetheryte_id } => {
|
|
|
|
let msg = FromServer::ActorControlSelf(ActorControlSelf {
|
|
|
|
category: ActorControlCategory::TeleportStart {
|
|
|
|
insufficient_gil: 0,
|
|
|
|
aetheryte_id: *aetheryte_id,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if handle.send(msg).is_err() {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(id);
|
2025-05-11 10:12:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2025-05-08 22:53:36 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(id);
|
2025-05-08 22:53:36 -04:00
|
|
|
}
|
|
|
|
}
|
2025-05-08 23:03:51 -04:00
|
|
|
ClientTriggerCommand::ChangePose { unk1, pose } => {
|
|
|
|
let msg = FromServer::ActorControl(
|
|
|
|
from_actor_id,
|
|
|
|
ActorControl {
|
|
|
|
category: ActorControlCategory::Pose {
|
|
|
|
unk1: *unk1,
|
|
|
|
pose: *pose,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if handle.send(msg).is_err() {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(id);
|
2025-05-08 23:03:51 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ClientTriggerCommand::ReapplyPose { unk1, pose } => {
|
|
|
|
let msg = FromServer::ActorControl(
|
|
|
|
from_actor_id,
|
|
|
|
ActorControl {
|
|
|
|
category: ActorControlCategory::Pose {
|
|
|
|
unk1: *unk1,
|
|
|
|
pose: *pose,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if handle.send(msg).is_err() {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(id);
|
2025-05-08 23:03:51 -04:00
|
|
|
}
|
|
|
|
}
|
2025-05-08 22:53:36 -04:00
|
|
|
_ => tracing::warn!("Server doesn't know what to do with {:#?}", trigger),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-06-18 20:28:03 -04:00
|
|
|
ToServer::DebugNewNpc(_from_id, from_actor_id) => {
|
2025-06-18 20:49:05 -04:00
|
|
|
let actor_id = Instance::generate_actor_id();
|
2025-06-18 20:38:55 -04:00
|
|
|
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()
|
|
|
|
};
|
|
|
|
|
2025-06-18 20:49:05 -04:00
|
|
|
instance.insert_npc(ObjectId(actor_id), spawn.clone());
|
2025-06-18 20:38:55 -04:00
|
|
|
}
|
|
|
|
|
2025-06-18 20:49:05 -04:00
|
|
|
data.send_npc(
|
|
|
|
Actor {
|
|
|
|
id: ObjectId(actor_id),
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
spawn,
|
|
|
|
);
|
2025-06-18 20:38:55 -04:00
|
|
|
}
|
|
|
|
ToServer::DebugNewEnemy(_from_id, from_actor_id) => {
|
2025-06-18 20:49:05 -04:00
|
|
|
let actor_id = Instance::generate_actor_id();
|
2025-06-18 20:28:03 -04:00
|
|
|
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;
|
|
|
|
};
|
2025-05-09 20:09:22 -04:00
|
|
|
|
2025-06-18 20:28:03 -04:00
|
|
|
spawn = NpcSpawn {
|
2025-05-09 20:09:22 -04:00
|
|
|
aggression_mode: 1,
|
|
|
|
common: CommonSpawn {
|
|
|
|
hp_curr: 91,
|
|
|
|
hp_max: 91,
|
|
|
|
mp_curr: 100,
|
|
|
|
mp_max: 100,
|
|
|
|
spawn_index: 0, // not needed at this level
|
|
|
|
bnpc_base: 13498, // TODO: changing this prevents it from spawning...
|
|
|
|
bnpc_name: 405,
|
|
|
|
object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy),
|
|
|
|
level: 1,
|
|
|
|
battalion: 4,
|
|
|
|
model_chara: 297,
|
2025-06-18 20:28:03 -04:00
|
|
|
pos: player.common.pos,
|
2025-05-09 20:09:22 -04:00
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
..Default::default()
|
2025-06-18 20:28:03 -04:00
|
|
|
};
|
|
|
|
|
2025-06-18 20:49:05 -04:00
|
|
|
instance.insert_npc(ObjectId(actor_id), spawn.clone());
|
2025-06-18 20:28:03 -04:00
|
|
|
}
|
|
|
|
|
2025-06-18 20:49:05 -04:00
|
|
|
data.send_npc(
|
|
|
|
Actor {
|
|
|
|
id: ObjectId(actor_id),
|
2025-06-18 20:57:20 -04:00
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
spawn,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
ToServer::DebugSpawnClone(_from_id, from_actor_id) => {
|
|
|
|
let actor_id = Instance::generate_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: player.common.clone(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
instance.insert_npc(ObjectId(actor_id), spawn.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
data.send_npc(
|
|
|
|
Actor {
|
|
|
|
id: ObjectId(actor_id),
|
2025-06-18 20:49:05 -04:00
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
spawn,
|
|
|
|
);
|
2025-05-09 20:09:22 -04:00
|
|
|
}
|
2025-05-02 23:51:34 -04:00
|
|
|
ToServer::Disconnected(from_id) => {
|
2025-06-18 20:38:55 -04:00
|
|
|
data.to_remove.push(from_id);
|
2025-05-02 23:51:34 -04:00
|
|
|
}
|
|
|
|
ToServer::FatalError(err) => return Err(err),
|
|
|
|
}
|
2025-05-12 01:17:15 -04:00
|
|
|
|
|
|
|
// Remove any clients that errored out
|
2025-06-18 20:38:55 -04:00
|
|
|
for remove_id in data.to_remove.clone() {
|
2025-05-12 01:17:15 -04:00
|
|
|
// remove any actors they had
|
|
|
|
let mut actor_id = None;
|
|
|
|
for (id, (handle, _)) in &mut data.clients {
|
2025-06-18 20:38:55 -04:00
|
|
|
if *id == remove_id {
|
2025-05-12 01:17:15 -04:00
|
|
|
actor_id = Some(handle.actor_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(actor_id) = actor_id {
|
|
|
|
// remove them from the instance
|
|
|
|
let current_instance = data.find_actor_instance_mut(actor_id).unwrap();
|
|
|
|
current_instance.actors.remove(&ObjectId(actor_id));
|
|
|
|
}
|
|
|
|
|
|
|
|
data.clients.remove(&remove_id);
|
|
|
|
}
|
2025-05-02 23:51:34 -04:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|