From 3a7307ec27ed0df218ce92d7f5a7db22f54e6efc Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Wed, 1 Jul 2026 02:54:01 -0500 Subject: [PATCH 01/15] Despawn NPCs if their health is empty when spawned --- src/game_server/handlers/character.rs | 28 +++++-- src/game_server/handlers/minigame.rs | 2 +- src/game_server/handlers/saber_duel.rs | 3 +- src/game_server/handlers/zone.rs | 22 +++--- src/game_server/mod.rs | 104 ++++++++++++++----------- 5 files changed, 93 insertions(+), 66 deletions(-) diff --git a/src/game_server/handlers/character.rs b/src/game_server/handlers/character.rs index cf7d4dc5..64ae8484 100644 --- a/src/game_server/handlers/character.rs +++ b/src/game_server/handlers/character.rs @@ -301,6 +301,8 @@ pub struct BaseNpcConfig { pub synchronize_with: Option, #[serde(default = "default_true")] pub is_spawned: bool, + #[serde(default)] + pub removal_mode: RemovalMode, pub composite_effect_id: Option, #[serde(default = "default_true")] pub clickable: bool, @@ -344,6 +346,7 @@ pub struct BaseNpc { pub enable_gravity: bool, pub enable_tilt: bool, pub use_terrain_model: bool, + pub removal_mode: RemovalMode, pub attachments: Vec, pub composite_effect_id: Option, pub clickable: bool, @@ -358,7 +361,7 @@ pub struct BaseNpc { impl BaseNpc { pub fn add_packets( &self, - character: &CharacterStats, + character: &mut CharacterStats, override_is_spawned: bool, ) -> Vec> { if !character.is_spawned && !override_is_spawned { @@ -505,6 +508,7 @@ impl BaseNpc { } if character.health < character.max_health && self.show_health { + let hp_delta = character.health as i32 - character.max_health as i32; packets.push(GamePacket::serialize(&TunneledPacket { unknown1: true, inner: HitPointModification { @@ -513,10 +517,15 @@ impl BaseNpc { show_hp_delta: false, max_hp: character.max_health as i32, new_hp: character.health as i32, - hp_delta: 0, + hp_delta, critical: false, }, - })); + })) + } + + if character.health == 0 { + character.is_spawned = false; + packets.extend(character.remove_packets(self.removal_mode)); } packets @@ -2173,6 +2182,7 @@ pub struct BaseNpcTemplate { pub enable_gravity: bool, pub enable_tilt: bool, pub use_terrain_model: bool, + pub removal_mode: RemovalMode, pub attachments: Vec, pub composite_effect_id: Option, pub clickable: bool, @@ -2266,6 +2276,7 @@ impl BaseNpcTemplate { enable_gravity: config.enable_gravity, enable_tilt: config.enable_tilt, use_terrain_model: config.use_terrain_model, + removal_mode: config.removal_mode, attachments: Vec::new(), composite_effect_id: config.composite_effect_id, clickable: config.clickable, @@ -2369,6 +2380,7 @@ impl BaseNpcTemplate { enable_gravity: self.enable_gravity, enable_tilt: self.enable_tilt, use_terrain_model: self.use_terrain_model, + removal_mode: self.removal_mode, attachments: self.attachments.clone(), composite_effect_id: self.composite_effect_id, clickable: self.clickable, @@ -2480,13 +2492,15 @@ pub struct CharacterStats { impl CharacterStats { pub fn add_packets( - &self, + &mut self, override_is_spawned: bool, mount_configs: &BTreeMap, item_configs: &BTreeMap, customizations: &BTreeMap, ) -> Vec> { - let mut packets = match &self.character_type { + let character_type = self.character_type.clone(); + + let mut packets = match character_type { CharacterType::AmbientNpc(ambient_npc) => { ambient_npc.add_packets(self, override_is_spawned) } @@ -2494,9 +2508,9 @@ impl CharacterStats { player.add_packets(self, mount_configs, item_configs, customizations) } CharacterType::Fixture(house_guid, fixture) => fixture_packets( - *house_guid, + house_guid, Guid::guid(self), - fixture, + &fixture, self.pos, self.rot, self.scale, diff --git a/src/game_server/handlers/minigame.rs b/src/game_server/handlers/minigame.rs index 88b6882b..07952b36 100644 --- a/src/game_server/handlers/minigame.rs +++ b/src/game_server/handlers/minigame.rs @@ -2462,7 +2462,7 @@ pub fn prepare_active_minigame_instance( ), )); } - characters.into_iter().for_each(|character| { + characters.into_iter().for_each(|mut character| { let chunk = Character::chunk( character.stats.pos.x, character.stats.pos.z, diff --git a/src/game_server/handlers/saber_duel.rs b/src/game_server/handlers/saber_duel.rs index bed22e52..fb9d88e6 100644 --- a/src/game_server/handlers/saber_duel.rs +++ b/src/game_server/handlers/saber_duel.rs @@ -14,7 +14,7 @@ use crate::game_server::{ handlers::{ character::{ default_spawn_animation_id, BaseNpc, Character, CharacterType, HoverDescriptionMode, - MinigameMatchmakingGroup, MinigameStatus, PlayerInventory, + MinigameMatchmakingGroup, MinigameStatus, PlayerInventory, RemovalMode, }, inventory::{ attachments_from_equipped_items, player_has_saber_equipped, wield_type_from_inventory, @@ -674,6 +674,7 @@ impl SaberDuelGame { enable_gravity: true, enable_tilt: false, use_terrain_model: false, + removal_mode: RemovalMode::default(), attachments, composite_effect_id: None, sub_title_id: None, diff --git a/src/game_server/handlers/zone.rs b/src/game_server/handlers/zone.rs index 151bb86f..ab4be467 100644 --- a/src/game_server/handlers/zone.rs +++ b/src/game_server/handlers/zone.rs @@ -44,7 +44,7 @@ use super::{ guid::{Guid, GuidTable, GuidTableIndexer, GuidTableWriteHandle, IndexedGuid}, housing::prepare_init_house_packets, lock_enforcer::{ - CharacterLockRequest, CharacterReadGuard, CharacterTableWriteHandle, CharacterWriteGuard, + CharacterLockRequest, CharacterTableWriteHandle, CharacterWriteGuard, ZoneLockEnforcer, ZoneLockRequest, ZoneTableWriteHandle, }, mount::MountConfig, @@ -351,7 +351,7 @@ impl IndexedGuid for ZoneInstance { } #[macro_export] -macro_rules! diff_character_handles { +macro_rules! diff_character_write_handles { ($instance_guid:expr, $old_chunk:expr, $new_chunk:expr, $characters_table_write_handle:expr, $moved_character_guid:expr) => {{ let character_diffs = $crate::game_server::handlers::zone::ZoneInstance::diff_character_guids( @@ -365,7 +365,7 @@ macro_rules! diff_character_handles { let mut handles = BTreeMap::new(); for guid in character_diffs.character_diffs_for_moved_character.keys() { if let Some(character) = $characters_table_write_handle.get(*guid) { - handles.insert(*guid, character.read()); + handles.insert(*guid, character.write()); } } @@ -668,8 +668,8 @@ impl ZoneInstance { pub fn diff_character_broadcasts( moved_character_guid: u64, character_diffs: CharacterDiffResult, - characters_read: &BTreeMap>, - moved_character_handle: &Character, + characters_write: &mut BTreeMap>, + moved_character_handle: &mut Character, mount_configs: &BTreeMap, item_configs: &BTreeMap, customizations: &BTreeMap, @@ -680,7 +680,7 @@ impl ZoneInstance { let mut diff_packets: Vec> = Vec::new(); for (guid, add) in &character_diffs.character_diffs_for_moved_character { - if let Some(character) = characters_read.get(guid) { + if let Some(character) = characters_write.get_mut(guid) { if *add { diff_packets.append(&mut character.stats.add_packets( false, @@ -831,7 +831,7 @@ impl ZoneInstance { characters_table_write_handle.update_value_indices( moved_character_guid, |possible_character_write_handle, characters_table_write_handle| { - let Some(moved_character_write_handle) = + let Some(mut moved_character_write_handle) = possible_character_write_handle else { return; @@ -840,7 +840,7 @@ impl ZoneInstance { let (_, instance_guid, old_chunk) = moved_character_write_handle.index1(); - let (character_diffs, characters_read) = diff_character_handles!( + let (character_diffs, mut characters_write) = diff_character_write_handles!( instance_guid, old_chunk, new_chunk, @@ -850,7 +850,7 @@ impl ZoneInstance { // Move the character ZoneInstance::move_character_with_locks( - moved_character_write_handle, + &mut moved_character_write_handle, new_pos, new_rot, ); @@ -878,8 +878,8 @@ impl ZoneInstance { broadcasts.append(&mut ZoneInstance::diff_character_broadcasts( moved_character_guid, character_diffs, - &characters_read, - moved_character_write_handle, + &mut characters_write, + &mut moved_character_write_handle, game_server.mounts(), game_server.items(), game_server.customizations(), diff --git a/src/game_server/mod.rs b/src/game_server/mod.rs index 0efa4c4a..e1ca029b 100644 --- a/src/game_server/mod.rs +++ b/src/game_server/mod.rs @@ -2,6 +2,7 @@ use std::backtrace::Backtrace; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt::Display; use std::io::{Cursor, Error}; +use std::iter::once; use std::num::ParseIntError; use std::path::Path; use std::str::ParseBoolError; @@ -447,11 +448,17 @@ impl GameServer { )) .unwrap_or_default(); - let read_character_guids: Vec = character_diffs.character_diffs_for_moved_character.keys().copied().collect(); + let write_character_guids: Vec = once(player_guid(sender)) + .chain( + character_diffs + .character_diffs_for_moved_character + .keys() + .copied() + ).collect(); CharacterLockRequest { - read_guids: read_character_guids, - write_guids: vec![player_guid(sender)], - character_consumer: move |characters_table_read_handle, characters_read, mut characters_write, minigame_data_lock_enforcer| { + read_guids: Vec::new(), + write_guids: write_character_guids, + character_consumer: move |characters_table_read_handle, _, mut characters_write, minigame_data_lock_enforcer| { let Some((_, instance_guid, chunk)) = possible_index else { return Err(ProcessPacketError::new(ProcessPacketErrorType::ConstraintViolated, format!("Player {sender} sent a ready packet but is not in any zone"))); }; @@ -524,61 +531,66 @@ impl GameServer { }; sender_only_character_packets.push(GamePacket::serialize(&power)); - let mut character_broadcasts = Vec::new(); - - let Some(character_write_handle) = characters_write.get_mut(&player_guid(sender)) else { + let Some(mut character_write_handle) = characters_write.remove(&player_guid(sender)) else { return Err(ProcessPacketError::new(ProcessPacketErrorType::ConstraintViolated, format!("Unknown player {sender} sent a ready packet"))); }; character_write_handle.update_speed(|speed| speed.base = zone.speed); character_write_handle.stats.jump_height_multiplier.base = zone.jump_height_multiplier; - character_broadcasts.append(&mut ZoneInstance::diff_character_broadcasts( - player_guid(sender), - character_diffs, - &characters_read, - character_write_handle, - self.mounts(), - self.items(), - self.customizations() - )); - - let wield_type = TunneledPacket { - unknown1: true, - inner: UpdateWieldType { - guid: player_guid(sender), - wield_type: character_write_handle.stats.wield_type(), - }, - }; - let global_packets = vec![GamePacket::serialize(&wield_type)]; + let result = (|| { + let mut character_broadcasts = Vec::new(); + + character_broadcasts.append(&mut ZoneInstance::diff_character_broadcasts( + player_guid(sender), + character_diffs, + &mut characters_write, + &mut character_write_handle, + self.mounts(), + self.items(), + self.customizations() + )); - if let CharacterType::Player(player) = &character_write_handle.stats.character_type { - sender_only_character_packets.push(GamePacket::serialize(&TunneledPacket { + let wield_type = TunneledPacket { unknown1: true, - inner: InitCustomizations { - customizations: customizations_from_guids(player.customizations.values().cloned(), self.customizations()), + inner: UpdateWieldType { + guid: player_guid(sender), + wield_type: character_write_handle.stats.wield_type(), }, - })); + }; + let global_packets = vec![GamePacket::serialize(&wield_type)]; - let items = player.inventory.equipped_items(player.inventory.active_battle_class); - character_broadcasts.append(&mut update_saber_tints( - sender, - characters_table_read_handle, - instance_guid, - chunk, - &items, - player.inventory.active_battle_class, - character_write_handle.stats.wield_type(), - self - )); - } + if let CharacterType::Player(player) = &character_write_handle.stats.character_type { + sender_only_character_packets.push(GamePacket::serialize(&TunneledPacket { + unknown1: true, + inner: InitCustomizations { + customizations: customizations_from_guids(player.customizations.values().cloned(), self.customizations()), + }, + })); + + let items = player.inventory.equipped_items(player.inventory.active_battle_class); + character_broadcasts.append(&mut update_saber_tints( + sender, + characters_table_read_handle, + instance_guid, + chunk, + &items, + player.inventory.active_battle_class, + character_write_handle.stats.wield_type(), + self + )); + } + + let all_players_nearby = ZoneInstance::all_players_nearby(chunk, instance_guid, characters_table_read_handle); + character_broadcasts.push(Broadcast::Multi(all_players_nearby, global_packets)); - let all_players_nearby = ZoneInstance::all_players_nearby(chunk, instance_guid, characters_table_read_handle); - character_broadcasts.push(Broadcast::Multi(all_players_nearby, global_packets)); + character_broadcasts.push(Broadcast::Single(sender, sender_only_character_packets)); - character_broadcasts.push(Broadcast::Single(sender, sender_only_character_packets)); + Ok(character_broadcasts) + })(); - Ok(character_broadcasts) + characters_write.insert(player_guid(sender), character_write_handle); + result }, }) }, From a7c48bd84d700f1f7e4ba2f8a2c3ac730186720b Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Wed, 1 Jul 2026 03:13:59 -0500 Subject: [PATCH 02/15] Format --- src/game_server/handlers/zone.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game_server/handlers/zone.rs b/src/game_server/handlers/zone.rs index ab4be467..73bcf079 100644 --- a/src/game_server/handlers/zone.rs +++ b/src/game_server/handlers/zone.rs @@ -44,8 +44,8 @@ use super::{ guid::{Guid, GuidTable, GuidTableIndexer, GuidTableWriteHandle, IndexedGuid}, housing::prepare_init_house_packets, lock_enforcer::{ - CharacterLockRequest, CharacterTableWriteHandle, CharacterWriteGuard, - ZoneLockEnforcer, ZoneLockRequest, ZoneTableWriteHandle, + CharacterLockRequest, CharacterTableWriteHandle, CharacterWriteGuard, ZoneLockEnforcer, + ZoneLockRequest, ZoneTableWriteHandle, }, mount::MountConfig, unique_guid::{ From 7a969202296f1f8507a81dbd40699b0ecf5a4f6c Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Wed, 1 Jul 2026 04:08:13 -0500 Subject: [PATCH 03/15] Format --- src/game_server/handlers/zone.rs | 6 +-- src/game_server/mod.rs | 87 +++++++++++++++----------------- 2 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/game_server/handlers/zone.rs b/src/game_server/handlers/zone.rs index 73bcf079..82c94343 100644 --- a/src/game_server/handlers/zone.rs +++ b/src/game_server/handlers/zone.rs @@ -831,7 +831,7 @@ impl ZoneInstance { characters_table_write_handle.update_value_indices( moved_character_guid, |possible_character_write_handle, characters_table_write_handle| { - let Some(mut moved_character_write_handle) = + let Some(moved_character_write_handle) = possible_character_write_handle else { return; @@ -850,7 +850,7 @@ impl ZoneInstance { // Move the character ZoneInstance::move_character_with_locks( - &mut moved_character_write_handle, + moved_character_write_handle, new_pos, new_rot, ); @@ -879,7 +879,7 @@ impl ZoneInstance { moved_character_guid, character_diffs, &mut characters_write, - &mut moved_character_write_handle, + moved_character_write_handle, game_server.mounts(), game_server.items(), game_server.customizations(), diff --git a/src/game_server/mod.rs b/src/game_server/mod.rs index e1ca029b..1d017290 100644 --- a/src/game_server/mod.rs +++ b/src/game_server/mod.rs @@ -531,6 +531,8 @@ impl GameServer { }; sender_only_character_packets.push(GamePacket::serialize(&power)); + let mut character_broadcasts = Vec::new(); + let Some(mut character_write_handle) = characters_write.remove(&player_guid(sender)) else { return Err(ProcessPacketError::new(ProcessPacketErrorType::ConstraintViolated, format!("Unknown player {sender} sent a ready packet"))); }; @@ -538,59 +540,54 @@ impl GameServer { character_write_handle.update_speed(|speed| speed.base = zone.speed); character_write_handle.stats.jump_height_multiplier.base = zone.jump_height_multiplier; - let result = (|| { - let mut character_broadcasts = Vec::new(); - - character_broadcasts.append(&mut ZoneInstance::diff_character_broadcasts( - player_guid(sender), - character_diffs, - &mut characters_write, - &mut character_write_handle, - self.mounts(), - self.items(), - self.customizations() - )); + character_broadcasts.append(&mut ZoneInstance::diff_character_broadcasts( + player_guid(sender), + character_diffs, + &mut characters_write, + &mut character_write_handle, + self.mounts(), + self.items(), + self.customizations() + )); - let wield_type = TunneledPacket { + let wield_type = TunneledPacket { + unknown1: true, + inner: UpdateWieldType { + guid: player_guid(sender), + wield_type: character_write_handle.stats.wield_type(), + }, + }; + let global_packets = vec![GamePacket::serialize(&wield_type)]; + + if let CharacterType::Player(player) = &character_write_handle.stats.character_type { + sender_only_character_packets.push(GamePacket::serialize(&TunneledPacket { unknown1: true, - inner: UpdateWieldType { - guid: player_guid(sender), - wield_type: character_write_handle.stats.wield_type(), + inner: InitCustomizations { + customizations: customizations_from_guids(player.customizations.values().cloned(), self.customizations()), }, - }; - let global_packets = vec![GamePacket::serialize(&wield_type)]; - - if let CharacterType::Player(player) = &character_write_handle.stats.character_type { - sender_only_character_packets.push(GamePacket::serialize(&TunneledPacket { - unknown1: true, - inner: InitCustomizations { - customizations: customizations_from_guids(player.customizations.values().cloned(), self.customizations()), - }, - })); - - let items = player.inventory.equipped_items(player.inventory.active_battle_class); - character_broadcasts.append(&mut update_saber_tints( - sender, - characters_table_read_handle, - instance_guid, - chunk, - &items, - player.inventory.active_battle_class, - character_write_handle.stats.wield_type(), - self - )); - } + })); - let all_players_nearby = ZoneInstance::all_players_nearby(chunk, instance_guid, characters_table_read_handle); - character_broadcasts.push(Broadcast::Multi(all_players_nearby, global_packets)); + let items = player.inventory.equipped_items(player.inventory.active_battle_class); + character_broadcasts.append(&mut update_saber_tints( + sender, + characters_table_read_handle, + instance_guid, + chunk, + &items, + player.inventory.active_battle_class, + character_write_handle.stats.wield_type(), + self + )); + } - character_broadcasts.push(Broadcast::Single(sender, sender_only_character_packets)); + let all_players_nearby = ZoneInstance::all_players_nearby(chunk, instance_guid, characters_table_read_handle); + character_broadcasts.push(Broadcast::Multi(all_players_nearby, global_packets)); - Ok(character_broadcasts) - })(); + character_broadcasts.push(Broadcast::Single(sender, sender_only_character_packets)); characters_write.insert(player_guid(sender), character_write_handle); - result + + Ok(character_broadcasts) }, }) }, From 5c5158035d1cf77c773334057f755e81286af60e Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:39:05 -0500 Subject: [PATCH 04/15] Rework implementation --- src/game_server/handlers/character.rs | 60 +++++++++++++-------------- src/game_server/handlers/minigame.rs | 2 +- src/game_server/handlers/zone.rs | 20 ++++----- src/game_server/mod.rs | 23 ++++------ 4 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/game_server/handlers/character.rs b/src/game_server/handlers/character.rs index ed96639f..9eb7da3f 100644 --- a/src/game_server/handlers/character.rs +++ b/src/game_server/handlers/character.rs @@ -299,8 +299,8 @@ pub struct BaseNpcConfig { #[serde(default)] pub first_possible_procedures: Vec, pub synchronize_with: Option, - #[serde(default = "default_true")] - pub is_spawned: bool, + #[serde(default)] + pub force_despawn: bool, #[serde(default)] pub removal_mode: RemovalMode, pub composite_effect_id: Option, @@ -361,10 +361,10 @@ pub struct BaseNpc { impl BaseNpc { pub fn add_packets( &self, - character: &mut CharacterStats, + character: &CharacterStats, override_is_spawned: bool, ) -> Vec> { - if !character.is_spawned && !override_is_spawned { + if !character.is_spawned() && !override_is_spawned { return Vec::new(); } @@ -508,7 +508,6 @@ impl BaseNpc { } if character.health < character.max_health && self.show_health { - let hp_delta = character.health as i32 - character.max_health as i32; packets.push(GamePacket::serialize(&TunneledPacket { unknown1: true, inner: HitPointModification { @@ -517,15 +516,10 @@ impl BaseNpc { show_hp_delta: false, max_hp: character.max_health as i32, new_hp: character.health as i32, - hp_delta, + hp_delta: character.health as i32 - character.max_health as i32, critical: false, }, - })) - } - - if character.health == 0 { - character.is_spawned = false; - packets.extend(character.remove_packets(self.removal_mode)); + })); } packets @@ -779,7 +773,7 @@ impl OneShotInteractionTemplate { let mut packets_for_sender = Vec::new(); if self.despawn_npc { - character.is_spawned = false; + character.force_despawn = true; packets_for_all.extend(character.remove_packets(self.removal_mode)); } @@ -991,8 +985,8 @@ impl TickableStep { match self.spawned_state { SpawnedState::Always => { - if !character.is_spawned { - character.is_spawned = true; + if !character.is_spawned() { + character.force_despawn = false; pos_update = self.reselect_possible_pos(character); packets_for_all.extend(character.add_packets( false, @@ -1003,8 +997,8 @@ impl TickableStep { } } SpawnedState::OnFirstStepTick => { - if !character.is_spawned { - // Spawn the character without updating its state to prevent it from being visible + if !character.is_spawned() { + // Spawn the character without updating force_despawn to prevent it from being visible // to players joining the room mid-step pos_update = self.reselect_possible_pos(character); packets_for_all.extend(character.add_packets( @@ -1016,9 +1010,9 @@ impl TickableStep { } } SpawnedState::Despawn => { - // Skip checking if the character is spawned before despawning it and instead check if - // its state needs updating as OnFirstStepTick doesn't maintain states - character.is_spawned = false; + // Skip checking if the character is spawned before despawning it and instead ensure + // force_despawn is false as OnFirstStepTick doesn't maintain this field + character.force_despawn = true; packets_for_all.extend(character.remove_packets(self.removal_mode)); } SpawnedState::Keep => {} @@ -2158,7 +2152,7 @@ pub struct BaseNpcTemplate { pub tickable_procedures: HashMap, pub first_possible_procedures: Vec, pub synchronize_with: Option, - pub is_spawned: bool, + pub force_despawn: bool, pub physics: PhysicsState, pub max_distance_from_target: f32, pub max_distance_from_origin: f32, @@ -2251,7 +2245,7 @@ impl BaseNpcTemplate { interact_radius: config.interact_radius, auto_interact_radius: config.auto_interact_radius.unwrap_or(0.0), move_to_interact_offset: config.move_to_interact_offset, - is_spawned: config.is_spawned, + force_despawn: config.force_despawn, physics: config.physics, mount_id: None, wield_type: WieldType::None, @@ -2333,7 +2327,7 @@ impl BaseNpcTemplate { mount_multiplier: 1.0, }, cursor: self.cursor, - is_spawned: self.is_spawned, + force_despawn: self.force_despawn, physics: self.physics, name: None, squad_guid: None, @@ -2471,7 +2465,7 @@ pub struct CharacterStats { speed: CharacterStat, pub jump_height_multiplier: CharacterStat, pub cursor: Option, - pub is_spawned: bool, + pub force_despawn: bool, pub physics: PhysicsState, pub name: Option, pub squad_guid: Option, @@ -2491,16 +2485,18 @@ pub struct CharacterStats { } impl CharacterStats { + pub fn is_spawned(&self) -> bool { + !self.force_despawn && self.health > 0 + } + pub fn add_packets( - &mut self, + &self, override_is_spawned: bool, mount_configs: &BTreeMap, item_configs: &BTreeMap, customizations: &BTreeMap, ) -> Vec> { - let character_type = self.character_type.clone(); - - let mut packets = match character_type { + let mut packets = match &self.character_type { CharacterType::AmbientNpc(ambient_npc) => { ambient_npc.add_packets(self, override_is_spawned) } @@ -2508,9 +2504,9 @@ impl CharacterStats { player.add_packets(self, mount_configs, item_configs, customizations) } CharacterType::Fixture(house_guid, fixture) => fixture_packets( - house_guid, + *house_guid, Guid::guid(self), - &fixture, + fixture, self.pos, self.rot, self.scale, @@ -2720,7 +2716,7 @@ impl Character { character_type, mount: mount_id, cursor, - is_spawned: true, + force_despawn: false, physics: PhysicsState::default(), name: None, squad_guid: None, @@ -2790,7 +2786,7 @@ impl Character { character_type: CharacterType::Player(Box::new(data)), mount: None, cursor: None, - is_spawned: true, + force_despawn: false, physics: PhysicsState::default(), interact_radius: 0.0, auto_interact_radius: 0.0, diff --git a/src/game_server/handlers/minigame.rs b/src/game_server/handlers/minigame.rs index 6d52e480..d5bad956 100644 --- a/src/game_server/handlers/minigame.rs +++ b/src/game_server/handlers/minigame.rs @@ -2462,7 +2462,7 @@ pub fn prepare_active_minigame_instance( ), )); } - characters.into_iter().for_each(|mut character| { + characters.into_iter().for_each(|character| { let chunk = Character::chunk( character.stats.pos.x, character.stats.pos.z, diff --git a/src/game_server/handlers/zone.rs b/src/game_server/handlers/zone.rs index 5c061de5..5d7bb15e 100644 --- a/src/game_server/handlers/zone.rs +++ b/src/game_server/handlers/zone.rs @@ -44,8 +44,8 @@ use super::{ guid::{Guid, GuidTable, GuidTableIndexer, GuidTableWriteHandle, IndexedGuid}, housing::prepare_init_house_packets, lock_enforcer::{ - CharacterLockRequest, CharacterTableWriteHandle, CharacterWriteGuard, ZoneLockEnforcer, - ZoneLockRequest, ZoneTableWriteHandle, + CharacterLockRequest, CharacterReadGuard, CharacterTableWriteHandle, CharacterWriteGuard, + ZoneLockEnforcer, ZoneLockRequest, ZoneTableWriteHandle, }, mount::MountConfig, unique_guid::{ @@ -351,7 +351,7 @@ impl IndexedGuid for ZoneInstance { } #[macro_export] -macro_rules! diff_character_write_handles { +macro_rules! diff_character_handles { ($instance_guid:expr, $old_chunk:expr, $new_chunk:expr, $characters_table_write_handle:expr, $moved_character_guid:expr) => {{ let character_diffs = $crate::game_server::handlers::zone::ZoneInstance::diff_character_guids( @@ -365,7 +365,7 @@ macro_rules! diff_character_write_handles { let mut handles = BTreeMap::new(); for guid in character_diffs.character_diffs_for_moved_character.keys() { if let Some(character) = $characters_table_write_handle.get(*guid) { - handles.insert(*guid, character.write()); + handles.insert(*guid, character.read()); } } @@ -668,8 +668,8 @@ impl ZoneInstance { pub fn diff_character_broadcasts( moved_character_guid: u64, character_diffs: CharacterDiffResult, - characters_write: &mut BTreeMap>, - moved_character_handle: &mut Character, + characters_read: &BTreeMap>, + moved_character_handle: &Character, mount_configs: &BTreeMap, item_configs: &BTreeMap, customizations: &BTreeMap, @@ -680,7 +680,7 @@ impl ZoneInstance { let mut diff_packets: Vec> = Vec::new(); for (guid, add) in &character_diffs.character_diffs_for_moved_character { - if let Some(character) = characters_write.get_mut(guid) { + if let Some(character) = characters_read.get(guid) { if *add { diff_packets.append(&mut character.stats.add_packets( false, @@ -840,7 +840,7 @@ impl ZoneInstance { let (_, instance_guid, old_chunk) = moved_character_write_handle.index1(); - let (character_diffs, mut characters_write) = diff_character_write_handles!( + let (character_diffs, characters_read) = diff_character_handles!( instance_guid, old_chunk, new_chunk, @@ -878,7 +878,7 @@ impl ZoneInstance { broadcasts.append(&mut ZoneInstance::diff_character_broadcasts( moved_character_guid, character_diffs, - &mut characters_write, + &characters_read, moved_character_write_handle, game_server.mounts(), game_server.items(), @@ -1300,7 +1300,7 @@ pub fn interact_with_character( )); }; - if !target_read_handle.stats.is_spawned { + if !target_read_handle.stats.is_spawned() { return Err(ProcessPacketError::new( ProcessPacketErrorType::ConstraintViolated, format!("Received request to interact with inactive NPC {target} from {requester}"), diff --git a/src/game_server/mod.rs b/src/game_server/mod.rs index 1e115331..eb7fca8c 100644 --- a/src/game_server/mod.rs +++ b/src/game_server/mod.rs @@ -2,7 +2,6 @@ use std::backtrace::Backtrace; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt::Display; use std::io::{Cursor, Error}; -use std::iter::once; use std::num::ParseIntError; use std::path::Path; use std::str::ParseBoolError; @@ -449,17 +448,11 @@ impl GameServer { )) .unwrap_or_default(); - let write_character_guids: Vec = once(player_guid(sender)) - .chain( - character_diffs - .character_diffs_for_moved_character - .keys() - .copied() - ).collect(); + let read_character_guids: Vec = character_diffs.character_diffs_for_moved_character.keys().copied().collect(); CharacterLockRequest { - read_guids: Vec::new(), - write_guids: write_character_guids, - character_consumer: move |characters_table_read_handle, _, mut characters_write, minigame_data_lock_enforcer| { + read_guids: read_character_guids, + write_guids: vec![player_guid(sender)], + character_consumer: move |characters_table_read_handle, characters_read, mut characters_write, minigame_data_lock_enforcer| { let Some((_, instance_guid, chunk)) = possible_index else { return Err(ProcessPacketError::new(ProcessPacketErrorType::ConstraintViolated, format!("Player {sender} sent a ready packet but is not in any zone"))); }; @@ -534,7 +527,7 @@ impl GameServer { let mut character_broadcasts = Vec::new(); - let Some(mut character_write_handle) = characters_write.remove(&player_guid(sender)) else { + let Some(character_write_handle) = characters_write.get_mut(&player_guid(sender)) else { return Err(ProcessPacketError::new(ProcessPacketErrorType::ConstraintViolated, format!("Unknown player {sender} sent a ready packet"))); }; @@ -544,8 +537,8 @@ impl GameServer { character_broadcasts.append(&mut ZoneInstance::diff_character_broadcasts( player_guid(sender), character_diffs, - &mut characters_write, - &mut character_write_handle, + &characters_read, + character_write_handle, self.mounts(), self.items(), self.customizations() @@ -586,8 +579,6 @@ impl GameServer { character_broadcasts.push(Broadcast::Single(sender, sender_only_character_packets)); - characters_write.insert(player_guid(sender), character_write_handle); - Ok(character_broadcasts) }, }) From e64cccdda13144324cb4ff3360168dfc311726b4 Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:31:08 -0500 Subject: [PATCH 05/15] Change is_spawned in configs to force_despawn --- config/zones/jedi_temple/ambient_npcs.yaml | 4 +- config/zones/jedi_temple/lifeday_orbs.yaml | 64 +++++++++---------- .../members_only/lifeday_orbs.yaml | 22 +++---- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/config/zones/jedi_temple/ambient_npcs.yaml b/config/zones/jedi_temple/ambient_npcs.yaml index c3f0a72a..0ed54f93 100644 --- a/config/zones/jedi_temple/ambient_npcs.yaml +++ b/config/zones/jedi_temple/ambient_npcs.yaml @@ -583,7 +583,7 @@ jedi_temple: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true enable_gravity: false tickable_procedures: spawn: @@ -625,7 +625,7 @@ jedi_temple: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true enable_gravity: false tickable_procedures: spawn: diff --git a/config/zones/jedi_temple/lifeday_orbs.yaml b/config/zones/jedi_temple/lifeday_orbs.yaml index e7dc8254..84f14fc2 100644 --- a/config/zones/jedi_temple/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/lifeday_orbs.yaml @@ -13,7 +13,7 @@ jedi_temple: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true tickable_procedures: spawn: weight: 300 @@ -59,7 +59,7 @@ jedi_temple: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true tickable_procedures: spawn: weight: 300 @@ -105,7 +105,7 @@ jedi_temple: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true tickable_procedures: spawn: weight: 300 @@ -151,7 +151,7 @@ jedi_temple: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true tickable_procedures: spawn: weight: 150 @@ -211,7 +211,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -281,7 +281,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -362,7 +362,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -418,7 +418,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -482,7 +482,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -547,7 +547,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -612,7 +612,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -676,7 +676,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -741,7 +741,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -806,7 +806,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -871,7 +871,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -940,7 +940,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1009,7 +1009,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1082,7 +1082,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1142,7 +1142,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1206,7 +1206,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1271,7 +1271,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1340,7 +1340,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1404,7 +1404,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1469,7 +1469,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1534,7 +1534,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1606,7 +1606,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1683,7 +1683,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1768,7 +1768,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1828,7 +1828,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1900,7 +1900,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -1977,7 +1977,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -2054,7 +2054,7 @@ jedi_temple: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true show_name: false enable_interact_popup: false one_shot_interaction: diff --git a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml index 87d9f5a1..fe3e00a2 100644 --- a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml @@ -13,7 +13,7 @@ members_only: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true tickable_procedures: spawn: weight: 300 @@ -59,7 +59,7 @@ members_only: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true tickable_procedures: spawn: weight: 300 @@ -105,7 +105,7 @@ members_only: y: 0 z: 0 w: 0 - is_spawned: false + force_despawn: true tickable_procedures: spawn: weight: 150 @@ -161,7 +161,7 @@ members_only: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -226,7 +226,7 @@ members_only: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -299,7 +299,7 @@ members_only: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -363,7 +363,7 @@ members_only: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -431,7 +431,7 @@ members_only: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -500,7 +500,7 @@ members_only: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -565,7 +565,7 @@ members_only: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: @@ -621,7 +621,7 @@ members_only: z: 0 w: 0 cursor: 54 - is_spawned: false + force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: From df72a5495145ccea5105188a9ae3bd21b9a6829f Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:06:20 -0500 Subject: [PATCH 06/15] Refresh NPC health on TickableStep spawn --- src/game_server/handlers/character.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/game_server/handlers/character.rs b/src/game_server/handlers/character.rs index 9eb7da3f..09efba7c 100644 --- a/src/game_server/handlers/character.rs +++ b/src/game_server/handlers/character.rs @@ -987,6 +987,7 @@ impl TickableStep { SpawnedState::Always => { if !character.is_spawned() { character.force_despawn = false; + character.refresh_health(); pos_update = self.reselect_possible_pos(character); packets_for_all.extend(character.add_packets( false, @@ -1000,6 +1001,7 @@ impl TickableStep { if !character.is_spawned() { // Spawn the character without updating force_despawn to prevent it from being visible // to players joining the room mid-step + character.refresh_health(); pos_update = self.reselect_possible_pos(character); packets_for_all.extend(character.add_packets( true, // Override is_spawned @@ -2489,6 +2491,10 @@ impl CharacterStats { !self.force_despawn && self.health > 0 } + pub fn refresh_health(&mut self) { + self.health = self.max_health; + } + pub fn add_packets( &self, override_is_spawned: bool, From 869a35c3a7a87ba52cb22e6be5f89566c45a2c8f Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:06:53 -0500 Subject: [PATCH 07/15] Fix comment --- src/game_server/handlers/character.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_server/handlers/character.rs b/src/game_server/handlers/character.rs index 09efba7c..ae458243 100644 --- a/src/game_server/handlers/character.rs +++ b/src/game_server/handlers/character.rs @@ -1013,7 +1013,7 @@ impl TickableStep { } SpawnedState::Despawn => { // Skip checking if the character is spawned before despawning it and instead ensure - // force_despawn is false as OnFirstStepTick doesn't maintain this field + // force_despawn is true as OnFirstStepTick doesn't maintain this field character.force_despawn = true; packets_for_all.extend(character.remove_packets(self.removal_mode)); } From 5afd648f36c6cf7ccf6defcc925130da78030963 Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Sat, 4 Jul 2026 00:52:56 -0500 Subject: [PATCH 08/15] Add removal_mode to CharacterStats and remove from TickableStep & OneShotInteraction --- src/game_server/handlers/character.rs | 44 +++++--------------------- src/game_server/handlers/saber_duel.rs | 3 +- 2 files changed, 9 insertions(+), 38 deletions(-) diff --git a/src/game_server/handlers/character.rs b/src/game_server/handlers/character.rs index ae458243..6dbe1a44 100644 --- a/src/game_server/handlers/character.rs +++ b/src/game_server/handlers/character.rs @@ -346,7 +346,6 @@ pub struct BaseNpc { pub enable_gravity: bool, pub enable_tilt: bool, pub use_terrain_model: bool, - pub removal_mode: RemovalMode, pub attachments: Vec, pub composite_effect_id: Option, pub clickable: bool, @@ -704,8 +703,6 @@ pub struct OneShotInteractionConfig { #[serde(default)] pub hud_message: HudMessageType, #[serde(default)] - pub removal_mode: RemovalMode, - #[serde(default)] pub despawn_npc: bool, pub duration_millis: u64, } @@ -720,7 +717,6 @@ pub struct OneShotInteractionTemplate { pub composite_effect_delay_millis: u32, pub dialog_option_id: Option, pub hud_message: HudMessageType, - pub removal_mode: RemovalMode, pub despawn_npc: bool, pub duration_millis: u64, } @@ -754,7 +750,6 @@ impl OneShotInteractionTemplate { animation_delay_seconds: config.animation_delay_seconds, composite_effect_id: config.composite_effect_id, composite_effect_delay_millis: config.composite_effect_delay_millis, - removal_mode: config.removal_mode, despawn_npc: config.despawn_npc, duration_millis: config.duration_millis, } @@ -774,7 +769,7 @@ impl OneShotInteractionTemplate { if self.despawn_npc { character.force_despawn = true; - packets_for_all.extend(character.remove_packets(self.removal_mode)); + packets_for_all.extend(character.remove_packets(character.removal_mode)); } if let Some(animation_id) = self.one_shot_animation_id { @@ -942,8 +937,6 @@ pub struct TickableStep { #[serde(default)] pub script: ScriptType, #[serde(default)] - pub removal_mode: RemovalMode, - #[serde(default)] pub spawned_state: SpawnedState, #[serde(default)] pub cursor: CursorUpdate, @@ -1015,7 +1008,7 @@ impl TickableStep { // Skip checking if the character is spawned before despawning it and instead ensure // force_despawn is true as OnFirstStepTick doesn't maintain this field character.force_despawn = true; - packets_for_all.extend(character.remove_packets(self.removal_mode)); + packets_for_all.extend(character.remove_packets(character.removal_mode)); } SpawnedState::Keep => {} } @@ -1417,8 +1410,6 @@ impl TickableProcedure { is_interruptible: config.is_interruptible, }; - procedure.panic_if_removal_exceeds_duration(); - procedure } @@ -1538,28 +1529,6 @@ impl TickableProcedure { pub fn is_interruptible(&self) -> bool { self.is_interruptible } - - fn panic_if_removal_exceeds_duration(&self) { - for step in &self.steps { - if let RemovalMode::Graceful { - removal_delay_millis, - removal_effect_delay_millis, - fade_duration_millis, - .. - } = step.removal_mode - { - let total_removal_time = - removal_delay_millis + removal_effect_delay_millis + fade_duration_millis; - - if total_removal_time > step.min_duration_millis as u32 { - panic!( - "(Removal delay: {}) + (Effect Delay: {}) + (Fade duration: {}) exceeded (Step duration: {})", - removal_delay_millis, removal_effect_delay_millis, fade_duration_millis, step.min_duration_millis - ); - } - } - } - } } #[derive(Clone)] @@ -2147,6 +2116,7 @@ pub struct BaseNpcTemplate { pub cursor: Option, pub health: u16, pub max_health: u16, + pub removal_mode: RemovalMode, pub interact_radius: f32, pub auto_interact_radius: f32, pub move_to_interact_offset: f32, @@ -2178,7 +2148,6 @@ pub struct BaseNpcTemplate { pub enable_gravity: bool, pub enable_tilt: bool, pub use_terrain_model: bool, - pub removal_mode: RemovalMode, pub attachments: Vec, pub composite_effect_id: Option, pub clickable: bool, @@ -2244,6 +2213,7 @@ impl BaseNpcTemplate { cursor: config.cursor, health: config.health, max_health: config.max_health.unwrap_or(config.health), + removal_mode: config.removal_mode, interact_radius: config.interact_radius, auto_interact_radius: config.auto_interact_radius.unwrap_or(0.0), move_to_interact_offset: config.move_to_interact_offset, @@ -2272,7 +2242,6 @@ impl BaseNpcTemplate { enable_gravity: config.enable_gravity, enable_tilt: config.enable_tilt, use_terrain_model: config.use_terrain_model, - removal_mode: config.removal_mode, attachments: Vec::new(), composite_effect_id: config.composite_effect_id, clickable: config.clickable, @@ -2341,6 +2310,7 @@ impl BaseNpcTemplate { threat_table: self.enemy_prioritization.clone().into(), health: self.health, max_health: self.max_health, + removal_mode: self.removal_mode, composite_effect_tags: BTreeMap::new(), navmesh: self.navmesh.clone(), ability_height: self.ability_height, @@ -2376,7 +2346,6 @@ impl BaseNpcTemplate { enable_gravity: self.enable_gravity, enable_tilt: self.enable_tilt, use_terrain_model: self.use_terrain_model, - removal_mode: self.removal_mode, attachments: self.attachments.clone(), composite_effect_id: self.composite_effect_id, clickable: self.clickable, @@ -2482,6 +2451,7 @@ pub struct CharacterStats { pub threat_table: ThreatTable, pub health: u16, pub max_health: u16, + pub removal_mode: RemovalMode, pub composite_effect_tags: BTreeMap, pub navmesh: Option, } @@ -2750,6 +2720,7 @@ impl Character { threat_table: ThreatTable::default(), health: u16::MAX, max_health: u16::MAX, + removal_mode: RemovalMode::default(), composite_effect_tags: BTreeMap::new(), navmesh: None, ability_height: default_ability_height(), @@ -2821,6 +2792,7 @@ impl Character { threat_table: ThreatTable::default(), health: u16::MAX, max_health: u16::MAX, + removal_mode: RemovalMode::default(), composite_effect_tags: BTreeMap::new(), navmesh: None, ability_height: default_ability_height(), diff --git a/src/game_server/handlers/saber_duel.rs b/src/game_server/handlers/saber_duel.rs index 9e5cfc3a..c05aaf84 100644 --- a/src/game_server/handlers/saber_duel.rs +++ b/src/game_server/handlers/saber_duel.rs @@ -14,7 +14,7 @@ use crate::game_server::{ handlers::{ character::{ default_spawn_animation_id, BaseNpc, Character, CharacterType, HoverDescriptionMode, - MinigameMatchmakingGroup, MinigameStatus, PlayerInventory, RemovalMode, + MinigameMatchmakingGroup, MinigameStatus, PlayerInventory, }, inventory::{ attachments_from_equipped_items, player_has_saber_equipped, wield_type_from_inventory, @@ -674,7 +674,6 @@ impl SaberDuelGame { enable_gravity: true, enable_tilt: false, use_terrain_model: false, - removal_mode: RemovalMode::default(), attachments, composite_effect_id: None, sub_title_id: None, From cdc53f3f38a08f3e89dafd9125abb503a06ae5d5 Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Sat, 4 Jul 2026 01:49:38 -0500 Subject: [PATCH 09/15] Update removal_mode in configs --- config/zones/jedi_temple/ambient_npcs.yaml | 5 +- config/zones/jedi_temple/lifeday_orbs.yaml | 214 +++--------------- .../members_only/lifeday_orbs.yaml | 70 ++---- 3 files changed, 50 insertions(+), 239 deletions(-) diff --git a/config/zones/jedi_temple/ambient_npcs.yaml b/config/zones/jedi_temple/ambient_npcs.yaml index 0ed54f93..6b27532d 100644 --- a/config/zones/jedi_temple/ambient_npcs.yaml +++ b/config/zones/jedi_temple/ambient_npcs.yaml @@ -583,6 +583,8 @@ jedi_temple: y: 0 z: 0 w: 0 + removal_mode: &starfighter_removal_config !Graceful + enable_death_animation: false force_despawn: true enable_gravity: false tickable_procedures: @@ -598,7 +600,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful {} min_duration_millis: 6000 next_possible_procedures: - procedure: spawn @@ -625,6 +626,7 @@ jedi_temple: y: 0 z: 0 w: 0 + removal_mode: *starfighter_removal_config force_despawn: true enable_gravity: false tickable_procedures: @@ -640,7 +642,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful {} min_duration_millis: 6000 next_possible_procedures: - procedure: spawn diff --git a/config/zones/jedi_temple/lifeday_orbs.yaml b/config/zones/jedi_temple/lifeday_orbs.yaml index 84f14fc2..baa41585 100644 --- a/config/zones/jedi_temple/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/lifeday_orbs.yaml @@ -13,6 +13,9 @@ jedi_temple: y: 0 z: 0 w: 0 + removal_mode: &orb_removal_config !Graceful + fade_duration_millis: 1000 + removal_delay_millis: 0 force_despawn: true tickable_procedures: spawn: @@ -30,9 +33,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -59,6 +59,7 @@ jedi_temple: y: 0 z: 0 w: 0 + removal_mode: *orb_removal_config force_despawn: true tickable_procedures: spawn: @@ -76,9 +77,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -105,6 +103,7 @@ jedi_temple: y: 0 z: 0 w: 0 + removal_mode: *orb_removal_config force_despawn: true tickable_procedures: spawn: @@ -122,9 +121,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -151,6 +147,7 @@ jedi_temple: y: 0 z: 0 w: 0 + removal_mode: *orb_removal_config force_despawn: true tickable_procedures: spawn: @@ -168,9 +165,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -211,15 +205,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -236,9 +228,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -281,15 +270,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -306,9 +293,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -362,15 +346,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -388,9 +370,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -418,15 +397,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 100 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -444,9 +421,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -482,15 +456,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -507,9 +479,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -547,15 +516,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -572,9 +539,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -612,15 +576,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -638,9 +600,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -676,15 +635,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -701,9 +658,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -741,15 +695,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -766,9 +718,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -806,15 +755,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -831,9 +778,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -871,15 +815,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -896,9 +838,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -940,15 +879,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -965,9 +902,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1009,15 +943,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1034,9 +966,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1082,15 +1011,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1108,9 +1035,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -1142,15 +1066,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 100 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1168,9 +1090,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -1206,15 +1125,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1231,9 +1148,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1271,15 +1185,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1296,9 +1208,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1340,15 +1249,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1366,9 +1273,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -1404,15 +1308,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1429,9 +1331,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1469,15 +1368,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1494,9 +1391,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1534,15 +1428,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1560,9 +1452,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -1606,15 +1495,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1631,9 +1518,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1683,15 +1567,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1708,9 +1590,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1768,15 +1647,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1794,9 +1671,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -1828,15 +1702,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 100 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1854,9 +1726,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -1900,15 +1769,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -1925,9 +1792,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -1977,15 +1841,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -2002,9 +1864,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -2054,15 +1913,13 @@ jedi_temple: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true show_name: false enable_interact_popup: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -2080,9 +1937,6 @@ jedi_temple: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty diff --git a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml index fe3e00a2..8672bc4b 100644 --- a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml @@ -13,6 +13,9 @@ members_only: y: 0 z: 0 w: 0 + removal_mode: &orb_removal_config !Graceful + fade_duration_millis: 1000 + removal_delay_millis: 0 force_despawn: true tickable_procedures: spawn: @@ -30,9 +33,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -59,6 +59,7 @@ members_only: y: 0 z: 0 w: 0 + removal_mode: *orb_removal_config force_despawn: true tickable_procedures: spawn: @@ -76,9 +77,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -105,6 +103,7 @@ members_only: y: 0 z: 0 w: 0 + removal_mode: *orb_removal_config force_despawn: true tickable_procedures: spawn: @@ -122,9 +121,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -161,15 +157,13 @@ members_only: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -186,9 +180,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -226,15 +217,13 @@ members_only: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -251,9 +240,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -299,15 +285,13 @@ members_only: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -325,9 +309,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -363,15 +344,13 @@ members_only: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 100 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -389,9 +368,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -431,15 +407,13 @@ members_only: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -456,9 +430,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -500,15 +471,13 @@ members_only: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -525,9 +494,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: spawn @@ -565,15 +531,13 @@ members_only: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 25 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -591,9 +555,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty @@ -621,15 +582,13 @@ members_only: z: 0 w: 0 cursor: 54 + removal_mode: *orb_removal_config force_despawn: true enable_interact_popup: false show_name: false one_shot_interaction: award_credits: 100 despawn_npc: true - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 duration_millis: 1000 tickable_procedures: spawn: @@ -647,9 +606,6 @@ members_only: remove: steps: - spawned_state: Despawn - removal_mode: !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 min_duration_millis: 1000 next_possible_procedures: - procedure: empty From 5734b6c94ee43fcbdc843ffaf236d913276b3f91 Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Sat, 4 Jul 2026 01:56:17 -0500 Subject: [PATCH 10/15] Format --- src/game_server/handlers/character.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/game_server/handlers/character.rs b/src/game_server/handlers/character.rs index 6dbe1a44..11587267 100644 --- a/src/game_server/handlers/character.rs +++ b/src/game_server/handlers/character.rs @@ -1400,7 +1400,7 @@ impl TickableProcedure { (WeightedAliasIndex::new(weights), references) }; - let procedure = TickableProcedure { + TickableProcedure { steps: config.steps, current_step_index: None, last_step_change: Instant::now(), @@ -1408,9 +1408,7 @@ impl TickableProcedure { distribution: distribution.expect("Couldn't create weighted alias index"), next_possible_procedures, is_interruptible: config.is_interruptible, - }; - - procedure + } } pub fn tick( From e7f4d3a02f38169bc3f9fb988e41fc1828785a79 Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:24:07 -0500 Subject: [PATCH 11/15] Format yaml --- config/zones/jedi_temple/lifeday_orbs.yaml | 4 ++-- config/zones/jedi_temple/members_only/lifeday_orbs.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/zones/jedi_temple/lifeday_orbs.yaml b/config/zones/jedi_temple/lifeday_orbs.yaml index baa41585..0ca80dda 100644 --- a/config/zones/jedi_temple/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/lifeday_orbs.yaml @@ -14,8 +14,8 @@ jedi_temple: z: 0 w: 0 removal_mode: &orb_removal_config !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 + fade_duration_millis: 1000 + removal_delay_millis: 0 force_despawn: true tickable_procedures: spawn: diff --git a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml index 8672bc4b..17fe6944 100644 --- a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml @@ -14,8 +14,8 @@ members_only: z: 0 w: 0 removal_mode: &orb_removal_config !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 + fade_duration_millis: 1000 + removal_delay_millis: 0 force_despawn: true tickable_procedures: spawn: From c7564a5eb3c31a3845aadc6324a913590065ee3a Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:22:33 -0500 Subject: [PATCH 12/15] Fix indentation --- config/zones/jedi_temple/ambient_npcs.yaml | 5 +++-- config/zones/jedi_temple/lifeday_orbs.yaml | 7 ++++--- config/zones/jedi_temple/members_only/lifeday_orbs.yaml | 7 ++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/config/zones/jedi_temple/ambient_npcs.yaml b/config/zones/jedi_temple/ambient_npcs.yaml index 6b27532d..fb8520a7 100644 --- a/config/zones/jedi_temple/ambient_npcs.yaml +++ b/config/zones/jedi_temple/ambient_npcs.yaml @@ -583,8 +583,9 @@ jedi_temple: y: 0 z: 0 w: 0 - removal_mode: &starfighter_removal_config !Graceful - enable_death_animation: false + removal_mode: &starfighter_removal_config + !Graceful + enable_death_animation: false force_despawn: true enable_gravity: false tickable_procedures: diff --git a/config/zones/jedi_temple/lifeday_orbs.yaml b/config/zones/jedi_temple/lifeday_orbs.yaml index 0ca80dda..e182da6d 100644 --- a/config/zones/jedi_temple/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/lifeday_orbs.yaml @@ -13,9 +13,10 @@ jedi_temple: y: 0 z: 0 w: 0 - removal_mode: &orb_removal_config !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 + removal_mode: &orb_removal_config + !Graceful + fade_duration_millis: 1000 + removal_delay_millis: 0 force_despawn: true tickable_procedures: spawn: diff --git a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml index 17fe6944..1ac9a69a 100644 --- a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml @@ -13,9 +13,10 @@ members_only: y: 0 z: 0 w: 0 - removal_mode: &orb_removal_config !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 + removal_mode: &orb_removal_config + !Graceful + fade_duration_millis: 1000 + removal_delay_millis: 0 force_despawn: true tickable_procedures: spawn: From e75037673ed4cbc640b7cd804ada2c28b25e98d7 Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Sat, 4 Jul 2026 22:25:45 -0500 Subject: [PATCH 13/15] Fix indentation (again) --- config/zones/jedi_temple/ambient_npcs.yaml | 2 +- config/zones/jedi_temple/lifeday_orbs.yaml | 4 ++-- config/zones/jedi_temple/members_only/lifeday_orbs.yaml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/zones/jedi_temple/ambient_npcs.yaml b/config/zones/jedi_temple/ambient_npcs.yaml index fb8520a7..1488b4a9 100644 --- a/config/zones/jedi_temple/ambient_npcs.yaml +++ b/config/zones/jedi_temple/ambient_npcs.yaml @@ -585,7 +585,7 @@ jedi_temple: w: 0 removal_mode: &starfighter_removal_config !Graceful - enable_death_animation: false + enable_death_animation: false force_despawn: true enable_gravity: false tickable_procedures: diff --git a/config/zones/jedi_temple/lifeday_orbs.yaml b/config/zones/jedi_temple/lifeday_orbs.yaml index e182da6d..04674013 100644 --- a/config/zones/jedi_temple/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/lifeday_orbs.yaml @@ -15,8 +15,8 @@ jedi_temple: w: 0 removal_mode: &orb_removal_config !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 + fade_duration_millis: 1000 + removal_delay_millis: 0 force_despawn: true tickable_procedures: spawn: diff --git a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml index 1ac9a69a..979894c2 100644 --- a/config/zones/jedi_temple/members_only/lifeday_orbs.yaml +++ b/config/zones/jedi_temple/members_only/lifeday_orbs.yaml @@ -15,8 +15,8 @@ members_only: w: 0 removal_mode: &orb_removal_config !Graceful - fade_duration_millis: 1000 - removal_delay_millis: 0 + fade_duration_millis: 1000 + removal_delay_millis: 0 force_despawn: true tickable_procedures: spawn: From 79ff0c5384140794389ad175cc6acc82162d1b92 Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:58:57 -0500 Subject: [PATCH 14/15] Prevent possible overflow on hp delta calc Co-authored-by: soir20 <71418127+soir20@users.noreply.github.com> --- src/game_server/handlers/character.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_server/handlers/character.rs b/src/game_server/handlers/character.rs index 11587267..d3ea8a0c 100644 --- a/src/game_server/handlers/character.rs +++ b/src/game_server/handlers/character.rs @@ -515,7 +515,7 @@ impl BaseNpc { show_hp_delta: false, max_hp: character.max_health as i32, new_hp: character.health as i32, - hp_delta: character.health as i32 - character.max_health as i32, + hp_delta: (character.health as i32).saturating_sub(character.max_health as i32), critical: false, }, })); From 5d791cfdf9201d05387362c51c1792a013cdedf1 Mon Sep 17 00:00:00 2001 From: Delta-07 <166327194+Delta-07@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:25:33 -0500 Subject: [PATCH 15/15] reset spawn stats in single method --- src/game_server/handlers/character.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game_server/handlers/character.rs b/src/game_server/handlers/character.rs index d3ea8a0c..58e0cadc 100644 --- a/src/game_server/handlers/character.rs +++ b/src/game_server/handlers/character.rs @@ -944,7 +944,9 @@ pub struct TickableStep { } impl TickableStep { - pub fn reselect_possible_pos(&self, character: &CharacterStats) -> Option { + pub fn reset_for_spawn(&self, character: &mut CharacterStats) -> Option { + character.refresh_health(); + if character.possible_pos.is_empty() { return None; } @@ -980,8 +982,7 @@ impl TickableStep { SpawnedState::Always => { if !character.is_spawned() { character.force_despawn = false; - character.refresh_health(); - pos_update = self.reselect_possible_pos(character); + pos_update = self.reset_for_spawn(character); packets_for_all.extend(character.add_packets( false, mount_configs, @@ -994,8 +995,7 @@ impl TickableStep { if !character.is_spawned() { // Spawn the character without updating force_despawn to prevent it from being visible // to players joining the room mid-step - character.refresh_health(); - pos_update = self.reselect_possible_pos(character); + pos_update = self.reset_for_spawn(character); packets_for_all.extend(character.add_packets( true, // Override is_spawned mount_configs,